author Santo Pfingsten
 *  see https import { browser } from "webextension-polyfill-ts" ; import { FrameInfo } from "./frameInfo" ; const MIN_DEAD_FRAME_CHECK_INTERVAL = 1000 ; function frameDead ( tabId , frameId ) { return browser . tabs . executeScript ( tabId , { frameId , code : "1" } ) . then ( ( ) => null ) . catch ( ( ) => frameId ) ; } export class TabInfo { public readonly tabId : number ; public readonly cookieStoreId : string ; private readonly frameInfos : { [ s ] : FrameInfo ; } = { } ; private readonly checkDomainLeave : ( cookieStoreId : string , hostnames : Set < string > ) => void ; private lastDeadFrameCheck = 0 ; private scheduledDeadFrameCheck : ReturnType < typeof setTimeout > | null = null ; public constructor ( tabId , hostname , cookieStoreId , checkDomainLeave ) { this . tabId = tabId ; this . cookieStoreId = cookieStoreId ; this . getFrameInfo ( 0 ) . commitNavigation ( hostname ) ; this . checkDomainLeave = checkDomainLeave ; this . checkDeadFrames = this . checkDeadFrames . bind ( this ) ; } private checkDeadFrames ( ) { this . scheduledDeadFrameCheck = null ; this . lastDeadFrameCheck = Date . now ( ) ; const allFramesIdle = this . allFramesIdle ( ) ; Promise . all ( Object . getOwnPropertyNames ( this . frameInfos ) . filter ( ( key ) => key !== "0" && this . frameInfos [ key ] . isIdle ( ) ) . map ( ( key ) => parseInt ( key ) ) . map ( ( id ) => ( frameDead ( this . tabId , id ) ) ) ) . then ( ( frameIds ) => { if ( frameIds . length ) { const deadFrameHostnames = new Set < string > ( ) ; const deadFrameIds = frameIds . filter ( ( id ) => id !== null ) as number [ ] ; for ( const frameId of deadFrameIds ) { const frameInfo = this . frameInfos [ frameId ] ; if ( frameInfo ) { delete this . frameInfos [ frameId ] ; frameInfo . collectHostnames ( deadFrameHostnames ) ; } else { console . warn ( "frame info not found: " + frameId ) ; } } if ( deadFrameHostnames . size ) this . checkDomainLeave ( this . cookieStoreId , deadFrameHostnames ) ; } if ( ! allFramesIdle ) this . scheduleDeadFramesCheck ( ) ; } ) ; } public scheduleDeadFramesCheck ( ) { if ( ! this . scheduledDeadFrameCheck ) { const delta = Date . now ( ) - this . lastDeadFrameCheck ; if ( delta > MIN_DEAD_FRAME_CHECK_INTERVAL ) this . checkDeadFrames ( ) ; else this . scheduledDeadFrameCheck = setTimeout ( this . checkDeadFrames , MIN_DEAD_FRAME_CHECK_INTERVAL - delta ) ; } } public allFramesIdle ( ) { for ( const key in this . frameInfos ) { if ( ! this . frameInfos [ key ] . isIdle ( ) ) return false ; } return true ; } private getFrameInfo ( frameId ) { let frameInfo = this . frameInfos [ frameId ] ; if ( ! frameInfo ) frameInfo = this . frameInfos [ frameId ] = new FrameInfo ( ) ; return frameInfo ; } public prepareNavigation ( frameId , hostname ) { return this . getFrameInfo ( frameId ) . prepareNavigation ( hostname ) ; } public commitNavigation ( frameId , hostname ) { const hostnames = new Set < string > ( ) ; const frameInfo = this . getFrameInfo ( frameId ) ; if ( frameId === 0 ) { for ( const key in this . frameInfos ) { this . frameInfos [ key ] . collectHostnames ( hostnames ) ; delete this . frameInfos [ key ] ; } this . frameInfos [ 0 ] = frameInfo ; } else { frameInfo . collectHostnames ( hostnames ) ; } frameInfo . commitNavigation ( hostname ) ; return hostnames ; } public contains ( hostname , checkNext ) { for ( const key in this . frameInfos ) { if ( this . frameInfos [ key ] . matchHostname ( hostname , checkNext ) ) return true ; } return false ; } public containsRuleFP ( regex ) { return this . getFrameInfo ( 0 ) . matchRegexFP ( regex ) ; } public matchHostnameFP ( hostnameFP ) { return this . getFrameInfo ( 0 ) . matchHostnameFP ( hostnameFP ) ; } public containsHostnameFP ( hostnameFP ) { for ( const key in this . frameInfos ) { if ( this . frameInfos [ key ] . matchHostnameFP ( hostnameFP ) ) return true ; } return false ; } }