s a // fragment navigation. this.#trace( lazy.truncate`Location=fragmentNavigated: ${location.spec}`, context.id ); lazy.notifyFragmentNavigated(payload); return; } this.#trace( lazy.truncate`Location=sameDocumentChanged: ${location.spec}`, context.id ); lazy.notifySameDocumentChanged(payload); } }; #onStateChange = (progress, request, stateFlags, status) => { const context = progress.browsingContext; const targetURI = this.#getTargetURI(request); const isBindingAborted = status == Cr.NS_BINDING_ABORTED; const isStart = !!(stateFlags & Ci.nsIWebProgressListener.STATE_START); const isStop = !!(stateFlags & Ci.nsIWebProgressListener.STATE_STOP); if (lazy.Log.isTraceLevelOrMore) { const isNetwork = !!( stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK ); this.#trace( `Loading state: flags: ${stateFlags}, status: ${status}, ` + ` isStart: ${isStart}, isStop: ${isStop}, isNetwork: ${isNetwork},` + ` isBindingAborted: ${isBindingAborted},` + lazy.truncate` targetURI: ${targetURI?.spec}`, context.id ); } const url = targetURI?.spec; const isUncommittedInitialDocument = lazy.isUncommittedInitialDocument(context); if (isUncommittedInitialDocument && url === "about:blank") { this.#trace("Skip initial navigation to about:blank", context.id); return; } try { if (isStart) { lazy.notifyNavigationStarted({ contextDetails: { context }, url, }); return; } if (isStop && !isBindingAborted) { const errorName = ChromeUtils.getXPCOMErrorName(status); if (this.#isContentBlocked(errorName)) { lazy.notifyNavigationFailed({ contextDetails: { context }, errorName, status, url, }); } else { lazy.notifyNavigationStopped({ contextDetails: { context }, status, url, }); } } } catch (e) { if (e.name === "InvalidStateError") { // We'll arrive here if we no longer have our manager, so we can // just swallow this error. return; } throw e; } }; startListening() { if (this.#listening) { return; } this.#contextListener.startListening(); // Start listening for navigation on all existing contexts. this.#getAllBrowsingContexts().forEach(browsingContext => this.#startWatchingBrowsingContextNavigation(browsingContext) ); this.#listening = true; } stopListening() { if (!this.#listening) { return; } this.#contextListener.stopListening(); for (const webProgress of this.#monitoredWebProgress.keys()) { try { webProgress.removeProgressListener(this.#listener); } catch (e) { this.#trace(`Failed to remove the progress listener`); } } this.#monitoredWebProgress = new Map(); this.#listening = false; } #getAllBrowsingContexts() { return lazy.TabManager.getBrowsers().flatMap(browser => browser.browsingContext.getAllBrowsingContextsInSubtree() ); } #getTargetURI(request) { try { return request.QueryInterface(Ci.nsIChannel).originalURI; } catch (e) {} return null; } #isContentBlocked(blockedReason) { return [ // If content is blocked with e.g. CSP meta tag. "NS_ERROR_CONTENT_BLOCKED", // If a resource load was blocked because of the CSP header. "NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION", // If a resource load was blocked because of the Cross-Origin-Embedder-Policy header. "NS_ERROR_DOM_COEP_FAILED", // If a resource load was blocked because of the X-Frame-Options header. "NS_ERROR_XFO_VIOLATION", ].includes(blockedReason); } #onContextAttached = async (eventName, data) => { const { browsingContext } = data; if (browsingContext.isContent) { this.#startWatchingBrowsingContextNavigation(browsingContext); } }; #onContextDiscarded = async (eventName, data = {}) => { const { browsingContext } = data; if (browsingContext.isContent) { this.#stopWatchingBrowsingContextNavigation(browsingContext); } }; #startWatchingBrowsingContextNavigation(browsingContext) { if (browsingContext.parent) { // Frame contexts will be monitored through the webprogress listener of // the top window. return; } this.#trace( `Start watching updates for browsing context`, browsingContext.id ); const webProgress = browsingContext.webProgress; if (!webProgress) { this.#trace( `No web progress attached to this browsing context, bailing out`, browsingContext.id ); return; } if (!this.#monitoredWebProgress.has(webProgress)) { this.#trace( `The web progress was not monitored yet, adding a progress listener`, browsingContext.id ); this.#monitoredWebProgress.set(webProgress, new Set()); webProgress.addProgressListener( this.#listener, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | Ci.nsIWebProgress.NOTIFY_LOCATION ); } this.#monitoredWebProgress.get(webProgress).add(browsingContext); } #stopWatchingBrowsingContextNavigation(browsingContext) { if (browsingContext.parent) { // Frame contexts will be monitored through the webprogress listener of // the top window. return; } this.#trace( `Stop watching updates for browsing context`, browsingContext.id ); const webProgress = browsingContext.webProgress; if (!webProgress) { this.#trace( `No web progress attached to this browsing context, bailing out`, browsingContext.id ); return; } const contexts = this.#monitoredWebProgress.get(webProgress); if (!contexts) { this.#trace( `No browsing context tracked for the web progress, bailing out`, browsingContext.id ); return; } contexts.delete(browsingContext); if (!contexts.size) { this.#trace( `All browsing contexts for this web progress deleted, removing the progress listener`, browsingContext.id ); try { webProgress.removeProgressListener(this.#listener); } catch (e) { this.#trace( `Failed to remove the progress listener`, browsingContext.id ); } this.#trace( `Removing the web progress from monitored web progress`, browsingContext.id ); this.#monitoredWebProgress.delete(webProgress); } } #trace(message, contextId = null) { if (contextId !== null) { lazy.logger.trace( `${this.constructor.name} ${message} [context=${contextId}]` ); } else { lazy.logger.trace(`${this.constructor.name} ${message}`); } } } PK