summaryrefslogtreecommitdiffstats
path: root/remote/cdp/observers/ContextObserver.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'remote/cdp/observers/ContextObserver.sys.mjs')
-rw-r--r--remote/cdp/observers/ContextObserver.sys.mjs178
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,
+ });
+ }
+}