diff --git a/remote/domains/content/Page.jsm b/remote/domains/content/Page.jsm index 80057967625a..cffe0867fe3f 100644 --- a/remote/domains/content/Page.jsm +++ b/remote/domains/content/Page.jsm @@ -37,6 +37,7 @@ class Page extends ContentProcessDomain { this.scriptsToEvaluateOnLoad = new Map(); this.worldsToEvaluateOnLoad = new Set(); + this._onFrameAttached = this._onFrameAttached.bind(this); this._onFrameNavigated = this._onFrameNavigated.bind(this); this._onScriptLoaded = this._onScriptLoaded.bind(this); @@ -55,7 +56,7 @@ class Page extends ContentProcessDomain { async enable() { if (!this.enabled) { - this.enabled = true; + this.session.contextObserver.on("frame-attached", this._onFrameAttached); this.session.contextObserver.on( "frame-navigated", this._onFrameNavigated @@ -82,11 +83,14 @@ class Page extends ContentProcessDomain { this.chromeEventHandler.addEventListener("pageshow", this, { mozSystemGroup: true, }); + + this.enabled = true; } } disable() { if (this.enabled) { + this.session.contextObserver.off("frame-attached", this._onFrameAttached); this.session.contextObserver.off( "frame-navigated", this._onFrameNavigated @@ -242,6 +246,14 @@ class Page extends ContentProcessDomain { return this.content.location.href; } + _onFrameAttached(name, { frameId, parentFrameId }) { + this.emit("Page.frameAttached", { + frameId, + parentFrameId, + stack: null, + }); + } + _onFrameNavigated(name, { frameId, window }) { const url = window.location.href; this.emit("Page.frameNavigated", { diff --git a/remote/observers/ContextObserver.jsm b/remote/observers/ContextObserver.jsm index e8665d8155ec..74756b58dfb9 100644 --- a/remote/observers/ContextObserver.jsm +++ b/remote/observers/ContextObserver.jsm @@ -32,6 +32,8 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { executeSoon } = ChromeUtils.import("chrome://remote/content/Sync.jsm"); +// Temporary flag to not emit frame related events until everything +// has been completely implemented, and Puppeteer is no longer busted. const FRAMES_ENABLED = Services.prefs.getBoolPref( "remote.frames.enabled", false @@ -55,6 +57,10 @@ class ContextObserver { }); Services.obs.addObserver(this, "inner-window-destroyed"); + + if (FRAMES_ENABLED) { + Services.obs.addObserver(this, "webnavigation-create"); + } } destructor() { @@ -67,7 +73,12 @@ class ContextObserver { this.chromeEventHandler.removeEventListener("pagehide", this, { mozSystemGroup: true, }); + Services.obs.removeObserver(this, "inner-window-destroyed"); + + if (FRAMES_ENABLED) { + Services.obs.removeObserver(this, "webnavigation-create"); + } } handleEvent({ type, target, persisted }) { @@ -114,9 +125,26 @@ class ContextObserver { } } - // "inner-window-destroyed" observer service listener observe(subject, topic, data) { - const innerWindowID = subject.QueryInterface(Ci.nsISupportsPRUint64).data; - this.emit("context-destroyed", { windowId: innerWindowID }); + switch (topic) { + 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; + } + } + + onDocShellCreated(docShell) { + const parent = docShell.browsingContext.parent; + + // TODO: Use a unique identifier for frames (bug 1605359) + this.emit("frame-attached", { + frameId: docShell.browsingContext.id.toString(), + parentFrameId: parent ? parent.id.toString() : null, + }); } } diff --git a/remote/test/browser/head.js b/remote/test/browser/head.js index 9dfcdda6cd54..d601294aea20 100644 --- a/remote/test/browser/head.js +++ b/remote/test/browser/head.js @@ -264,6 +264,48 @@ function toDataURL(src, doctype = "html") { return `data:${mime},${encodeURIComponent(doc)}`; } +function convertArgument(arg) { + if (typeof arg === "bigint") { + return { unserializableValue: `${arg.toString()}n` }; + } + if (Object.is(arg, -0)) { + return { unserializableValue: "-0" }; + } + if (Object.is(arg, Infinity)) { + return { unserializableValue: "Infinity" }; + } + if (Object.is(arg, -Infinity)) { + return { unserializableValue: "-Infinity" }; + } + if (Object.is(arg, NaN)) { + return { unserializableValue: "NaN" }; + } + + return { value: arg }; +} + +async function evaluate(client, contextId, pageFunction, ...args) { + const { Runtime } = client; + + if (typeof pageFunction === "string") { + return Runtime.evaluate({ + expression: pageFunction, + contextId, + returnByValue: true, + awaitPromise: true, + }); + } else if (typeof pageFunction === "function") { + return Runtime.callFunctionOn({ + functionDeclaration: pageFunction.toString(), + executionContextId: contextId, + arguments: args.map(convertArgument), + returnByValue: true, + awaitPromise: true, + }); + } + throw new Error("pageFunction: expected 'string' or 'function'"); +} + /** * Load a given URL in the currently selected tab */ @@ -271,7 +313,7 @@ async function loadURL(url, expectedURL = undefined) { expectedURL = expectedURL || url; const browser = gBrowser.selectedTab.linkedBrowser; - const loaded = BrowserTestUtils.browserLoaded(browser, false, expectedURL); + const loaded = BrowserTestUtils.browserLoaded(browser, true, expectedURL); BrowserTestUtils.loadURI(browser, url); await loaded; diff --git a/remote/test/browser/page/browser.ini b/remote/test/browser/page/browser.ini index d33dfedf3a8f..6a29adef3b81 100644 --- a/remote/test/browser/page/browser.ini +++ b/remote/test/browser/page/browser.ini @@ -14,6 +14,7 @@ support-files = [browser_bringToFront.js] [browser_captureScreenshot.js] [browser_createIsolatedWorld.js] +[browser_frameAttached.js] [browser_frameNavigated.js] [browser_getFrameTree.js] [browser_getLayoutMetrics.js] diff --git a/remote/test/browser/page/browser_frameAttached.js b/remote/test/browser/page/browser_frameAttached.js new file mode 100644 index 000000000000..4f63ef9f7fe2 --- /dev/null +++ b/remote/test/browser/page/browser_frameAttached.js @@ -0,0 +1,148 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const DOC = toDataURL("