diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /remote/shared/js-window-actors | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'remote/shared/js-window-actors')
3 files changed, 305 insertions, 0 deletions
diff --git a/remote/shared/js-window-actors/NavigationListenerActor.sys.mjs b/remote/shared/js-window-actors/NavigationListenerActor.sys.mjs new file mode 100644 index 0000000000..13335177c6 --- /dev/null +++ b/remote/shared/js-window-actors/NavigationListenerActor.sys.mjs @@ -0,0 +1,80 @@ +/* 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", + TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get()); + +let registered = false; +export function isNavigationListenerActorRegistered() { + return registered; +} + +/** + * Register the NavigationListener actor that will keep track of all ongoing + * navigations. + */ +export function registerNavigationListenerActor() { + if (registered) { + return; + } + + try { + ChromeUtils.registerWindowActor("NavigationListener", { + kind: "JSWindowActor", + parent: { + esModuleURI: + "chrome://remote/content/shared/js-window-actors/NavigationListenerParent.sys.mjs", + }, + child: { + esModuleURI: + "chrome://remote/content/shared/js-window-actors/NavigationListenerChild.sys.mjs", + events: { + DOMWindowCreated: {}, + }, + }, + allFrames: true, + messageManagerGroups: ["browsers"], + }); + registered = true; + + // Ensure the navigation listener is started in existing contexts. + for (const browser of lazy.TabManager.browsers) { + if (!browser?.browsingContext) { + continue; + } + + for (const context of browser.browsingContext.getAllBrowsingContextsInSubtree()) { + if (!context.currentWindowGlobal) { + continue; + } + + context.currentWindowGlobal + .getActor("NavigationListener") + // Note that "createActor" is not explicitly referenced in the child + // actor, this is only used to trigger the creation of the actor. + .sendAsyncMessage("createActor"); + } + } + } catch (e) { + if (e.name === "NotSupportedError") { + lazy.logger.warn(`NavigationListener actor is already registered!`); + } else { + throw e; + } + } +} + +export function unregisterNavigationListenerActor() { + if (!registered) { + return; + } + ChromeUtils.unregisterWindowActor("NavigationListener"); + registered = false; +} 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; + } + }; +} diff --git a/remote/shared/js-window-actors/NavigationListenerParent.sys.mjs b/remote/shared/js-window-actors/NavigationListenerParent.sys.mjs new file mode 100644 index 0000000000..334f9953d6 --- /dev/null +++ b/remote/shared/js-window-actors/NavigationListenerParent.sys.mjs @@ -0,0 +1,58 @@ +/* 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", + notifyLocationChanged: + "chrome://remote/content/shared/NavigationManager.sys.mjs", + notifyNavigationStarted: + "chrome://remote/content/shared/NavigationManager.sys.mjs", + notifyNavigationStopped: + "chrome://remote/content/shared/NavigationManager.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get()); + +export class NavigationListenerParent extends JSWindowActorParent { + async receiveMessage(message) { + try { + switch (message.name) { + case "NavigationListenerChild:locationChanged": { + lazy.notifyLocationChanged({ + contextDetails: message.data.contextDetails, + url: message.data.url, + }); + break; + } + case "NavigationListenerChild:navigationStarted": { + lazy.notifyNavigationStarted({ + contextDetails: message.data.contextDetails, + url: message.data.url, + }); + break; + } + case "NavigationListenerChild:navigationStopped": { + lazy.notifyNavigationStopped({ + contextDetails: message.data.contextDetails, + url: message.data.url, + }); + break; + } + default: + throw new Error("Unsupported message:" + message.name); + } + } catch (e) { + if (e instanceof TypeError) { + // Avoid error spam from errors due to unavailable browsing contexts. + lazy.logger.trace( + `Failed to handle a navigation listener message: ${e.message}` + ); + } else { + throw e; + } + } + } +} |