diff options
Diffstat (limited to 'remote/cdp/observers/ContextObserver.sys.mjs')
-rw-r--r-- | remote/cdp/observers/ContextObserver.sys.mjs | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/remote/cdp/observers/ContextObserver.sys.mjs b/remote/cdp/observers/ContextObserver.sys.mjs new file mode 100644 index 0000000000..d0d6fd1f93 --- /dev/null +++ b/remote/cdp/observers/ContextObserver.sys.mjs @@ -0,0 +1,178 @@ +/* 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/. */ + +/** + * Helper class to coordinate Runtime and Page events. + * Events have to be sent in the following order: + * - Runtime.executionContextDestroyed + * - Page.frameNavigated + * - Runtime.executionContextCreated + * + * This class also handles the special case of Pages going from/to the BF cache. + * When you navigate to a new URL, the previous document may be stored in the BF Cache. + * All its asynchronous operations are frozen (XHR, timeouts, ...) and a `pagehide` event + * is fired for this document. We then navigate to the new URL. + * If the user navigates back to the previous page, the page is resurected from the + * cache. A `pageshow` event is fired and its asynchronous operations are resumed. + * + * When a page is in the BF Cache, we should consider it as frozen and shouldn't try + * to execute any javascript. So that the ExecutionContext should be considered as + * being destroyed and the document navigated. + */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs", + + executeSoon: "chrome://remote/content/shared/Sync.sys.mjs", +}); + +export class ContextObserver { + constructor(chromeEventHandler) { + this.chromeEventHandler = chromeEventHandler; + lazy.EventEmitter.decorate(this); + + this._fissionEnabled = Services.appinfo.fissionAutostart; + + this.chromeEventHandler.addEventListener("DOMWindowCreated", this, { + mozSystemGroup: true, + }); + + // Listen for pageshow and pagehide to track pages going in/out to/from the BF Cache + this.chromeEventHandler.addEventListener("pageshow", this, { + mozSystemGroup: true, + }); + this.chromeEventHandler.addEventListener("pagehide", this, { + mozSystemGroup: true, + }); + + Services.obs.addObserver(this, "document-element-inserted"); + Services.obs.addObserver(this, "inner-window-destroyed"); + + // With Fission disabled the `DOMWindowCreated` event is fired too late. + // Use the `webnavigation-create` notification instead. + if (!this._fissionEnabled) { + Services.obs.addObserver(this, "webnavigation-create"); + } + Services.obs.addObserver(this, "webnavigation-destroy"); + } + + destructor() { + this.chromeEventHandler.removeEventListener("DOMWindowCreated", this, { + mozSystemGroup: true, + }); + this.chromeEventHandler.removeEventListener("pageshow", this, { + mozSystemGroup: true, + }); + this.chromeEventHandler.removeEventListener("pagehide", this, { + mozSystemGroup: true, + }); + + Services.obs.removeObserver(this, "document-element-inserted"); + Services.obs.removeObserver(this, "inner-window-destroyed"); + + if (!this._fissionEnabled) { + Services.obs.removeObserver(this, "webnavigation-create"); + } + Services.obs.removeObserver(this, "webnavigation-destroy"); + } + + handleEvent({ type, target, persisted }) { + const window = target.defaultView; + const frameId = window.browsingContext.id; + const id = window.windowGlobalChild.innerWindowId; + + switch (type) { + case "DOMWindowCreated": + // Do not pass `id` here as that's the new document ID instead of the old one + // that is destroyed. Instead, pass the frameId and let the listener figure out + // what ExecutionContext(s) to destroy. + this.emit("context-destroyed", { frameId }); + + // With Fission enabled the frame is attached early enough so that + // expected network requests and responses are handles afterward. + // Otherwise send the event when `webnavigation-create` is received. + if (this._fissionEnabled) { + this.emit("frame-attached", { frameId, window }); + } + + break; + + case "pageshow": + // `persisted` is true when this is about a page being resurected from BF Cache + if (!persisted) { + return; + } + // XXX(ochameau) we might have to emit FrameNavigate here to properly handle BF Cache + // scenario in Page domain events + this.emit("context-created", { windowId: id, window }); + this.emit("script-loaded", { windowId: id, window }); + break; + + case "pagehide": + // `persisted` is true when this is about a page being frozen into BF Cache + if (!persisted) { + return; + } + this.emit("context-destroyed", { windowId: id }); + break; + } + } + + observe(subject, topic, data) { + switch (topic) { + case "document-element-inserted": + const window = subject.defaultView; + + // Ignore events without a window and those from other tabs + if ( + !window || + window.docShell.chromeEventHandler !== this.chromeEventHandler + ) { + return; + } + + // Send when the document gets attached to the window, and its location + // is available. + this.emit("frame-navigated", { + frameId: window.browsingContext.id, + window, + }); + + const id = window.windowGlobalChild.innerWindowId; + this.emit("context-created", { windowId: id, window }); + // Delay script-loaded to allow context cleanup to happen first + lazy.executeSoon(() => { + this.emit("script-loaded", { windowId: id, window }); + }); + break; + case "inner-window-destroyed": + const windowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data; + this.emit("context-destroyed", { windowId }); + break; + case "webnavigation-create": + subject.QueryInterface(Ci.nsIDocShell); + this.onDocShellCreated(subject); + break; + case "webnavigation-destroy": + subject.QueryInterface(Ci.nsIDocShell); + this.onDocShellDestroyed(subject); + break; + } + } + + onDocShellCreated(docShell) { + this.emit("frame-attached", { + frameId: docShell.browsingContext.id, + window: docShell.browsingContext.window, + }); + } + + onDocShellDestroyed(docShell) { + this.emit("frame-detached", { + frameId: docShell.browsingContext.id, + }); + } +} |