diff options
Diffstat (limited to '')
-rw-r--r-- | remote/shared/js-window-actors/NavigationListenerChild.sys.mjs | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/remote/shared/js-window-actors/NavigationListenerChild.sys.mjs b/remote/shared/js-window-actors/NavigationListenerChild.sys.mjs new file mode 100644 index 0000000000..a2cd8ccf10 --- /dev/null +++ b/remote/shared/js-window-actors/NavigationListenerChild.sys.mjs @@ -0,0 +1,167 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + Log: "chrome://remote/content/shared/Log.sys.mjs", + truncate: "chrome://remote/content/shared/Format.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get()); + +export class NavigationListenerChild extends JSWindowActorChild { + #listener; + #webProgress; + + constructor() { + super(); + + this.#listener = { + onLocationChange: this.#onLocationChange, + onStateChange: this.#onStateChange, + QueryInterface: ChromeUtils.generateQI([ + "nsIWebProgressListener", + "nsISupportsWeakReference", + ]), + }; + this.#webProgress = null; + } + + actorCreated() { + this.#webProgress = this.manager.browsingContext.docShell + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress); + + this.#webProgress.addProgressListener( + this.#listener, + Ci.nsIWebProgress.NOTIFY_LOCATION | + Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT + ); + } + + didDestroy() { + try { + this.#webProgress.removeProgressListener(this.#listener); + } catch (e) { + // Ignore potential errors if the window global was already destroyed. + } + } + + // Note: we rely on events and messages to trigger the actor creation, but + // all the logic is in the actorCreated callback. The handleEvent and + // receiveMessage methods are only there as placeholders to avoid errors. + + /** + * See note above + */ + handleEvent(event) {} + + /** + * See note above + */ + receiveMessage(message) {} + + /** + * A browsing context might be replaced before reaching the parent process, + * instead we serialize enough information to retrieve the navigable in the + * parent process. + * + * If the browsing context is top level, then the browserId can be used to + * find the browser element and the new browsing context. + * Otherwise (frames) the browsing context should not be replaced and the + * browsing context id should be enough to find the browsing context. + * + * @param {BrowsingContext} browsingContext + * The browsing context for which we want to get details. + * @returns {object} + * An object that returns the following properties: + * - browserId: browser id for this browsing context + * - browsingContextId: browsing context id + * - isTopBrowsingContext: flag that indicates if the browsing context is + * top level + * + */ + #getBrowsingContextDetails(browsingContext) { + return { + browserId: browsingContext.browserId, + browsingContextId: browsingContext.id, + isTopBrowsingContext: browsingContext.parent === null, + }; + } + + #getTargetURI(request) { + try { + return request.QueryInterface(Ci.nsIChannel).originalURI; + } catch (e) {} + + return null; + } + + #onLocationChange = (progress, request, location, stateFlags) => { + if (stateFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) { + const context = progress.browsingContext; + + lazy.logger.trace( + `[${context.id}] NavigationListener onLocationChange,` + + lazy.truncate` location: ${location.spec}` + ); + + this.sendAsyncMessage("NavigationListenerChild:locationChanged", { + contextDetails: this.#getBrowsingContextDetails(context), + url: location.spec, + }); + } + }; + + #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 + ); + lazy.logger.trace( + `[${context.id}] NavigationListener onStateChange,` + + ` stateFlags: ${stateFlags}, status: ${status}, isStart: ${isStart},` + + ` isStop: ${isStop}, isNetwork: ${isNetwork},` + + ` isBindingAborted: ${isBindingAborted},` + + lazy.truncate` targetURI: ${targetURI?.spec}` + ); + } + + try { + if (isStart) { + this.sendAsyncMessage("NavigationListenerChild:navigationStarted", { + contextDetails: this.#getBrowsingContextDetails(context), + url: targetURI?.spec, + }); + + return; + } + + if (isStop && !isBindingAborted) { + // Skip NS_BINDING_ABORTED state changes as this can happen during a + // browsing context + process change and we should get the real stop state + // change from the correct process later. + this.sendAsyncMessage("NavigationListenerChild:navigationStopped", { + contextDetails: this.#getBrowsingContextDetails(context), + url: targetURI?.spec, + }); + } + } 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; + } + }; +} |