178 lines
6.1 KiB
JavaScript
178 lines
6.1 KiB
JavaScript
/* 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) {
|
|
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,
|
|
});
|
|
}
|
|
}
|