Bug 1599413 - [remote] Implement Page.frameAttached. r=remote-protocol-reviewers,maja_zf

Differential Revision: https://phabricator.services.mozilla.com/D71292
This commit is contained in:
Henrik Skupin 2020-05-19 19:37:45 +00:00
Родитель 168073622b
Коммит 9b6a2efca4
5 изменённых файлов: 236 добавлений и 5 удалений

Просмотреть файл

@ -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", {

Просмотреть файл

@ -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,
});
}
}

Просмотреть файл

@ -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;

Просмотреть файл

@ -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]

Просмотреть файл

@ -0,0 +1,148 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const DOC = toDataURL("<div>foo</div>");
const DOC_IFRAME_MULTI = toDataURL(`
<iframe src='data:text/html,foo'></iframe>
<iframe src='data:text/html,bar'></iframe>
`);
const DOC_IFRAME_NESTED = toDataURL(`
<iframe src="data:text/html,<iframe src='data:text/html,foo'></iframe>">
</iframe>
`);
add_task(async function noEventWhenPageDomainDisabled({ client }) {
await runFrameAttachedTest(client, 0, async () => {
info("Navigate to a page with an iframe");
await loadURL(DOC_IFRAME_MULTI);
});
});
add_task(async function noEventAfterPageDomainDisabled({ client }) {
const { Page } = client;
await Page.enable();
await Page.disable();
await runFrameAttachedTest(client, 0, async () => {
info("Navigate to a page with an iframe");
await loadURL(DOC_IFRAME_MULTI);
});
});
add_task(async function noEventWhenNavigatingWithNoFrames({ client }) {
const { Page } = client;
await Page.enable();
info("Navigate to a page with iframes");
await loadURL(DOC_IFRAME_MULTI);
await runFrameAttachedTest(client, 0, async () => {
info("Navigate to a page without an iframe");
await loadURL(DOC);
});
});
add_task(async function eventWhenNavigatingWithFrames({ client }) {
const { Page } = client;
await Page.enable();
await runFrameAttachedTest(client, 2, async () => {
info("Navigate to a page with an iframe");
await loadURL(DOC_IFRAME_MULTI);
});
});
add_task(async function eventWhenNavigatingWithNestedFrames({ client }) {
const { Page } = client;
await Page.enable();
await runFrameAttachedTest(client, 2, async () => {
info("Navigate to a page with nested iframes");
await loadURL(DOC_IFRAME_NESTED);
});
});
add_task(async function eventWhenAttachingFrame({ client }) {
const { Page } = client;
await Page.enable();
await runFrameAttachedTest(client, 1, async () => {
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
const frame = content.document.createElement("iframe");
frame.src = "data:text/html,frame content";
const loaded = new Promise(resolve => (frame.onload = resolve));
content.document.body.appendChild(frame);
await loaded;
});
});
});
async function runFrameAttachedTest(client, expectedEventCount, callback) {
const { Page } = client;
const ATTACHED = "Page.frameAttached";
const history = new RecordEvents(expectedEventCount);
history.addRecorder({
event: Page.frameAttached,
eventName: ATTACHED,
messageFn: payload => {
return `Received ${ATTACHED} for frame id ${payload.frameId}`;
},
});
const framesBefore = await getFlattendFrameList();
await callback();
const framesAfter = await getFlattendFrameList();
const frameAttachedEvents = await history.record();
if (expectedEventCount == 0) {
is(frameAttachedEvents.length, 0, "Got no frame attached event");
return;
}
// check how many frames were attached or detached
const count = Math.abs(framesBefore.size - framesAfter.size);
is(count, expectedEventCount, "Expected amount of frames attached");
is(
frameAttachedEvents.length,
count,
"Received the expected amount of frameAttached events"
);
// extract the new or removed frames
const framesAll = new Map([...framesBefore, ...framesAfter]);
const expectedFrames = new Map(
[...framesAll].filter(([key, _value]) => {
return !framesBefore.has(key) && framesAfter.has(key);
})
);
frameAttachedEvents.forEach(({ payload }) => {
const { frameId, parentFrameId } = payload;
console.log(`Check frame id ${frameId}`);
const expectedFrame = expectedFrames.get(frameId);
ok(expectedFrame, `Found expected frame with id ${frameId}`);
is(
frameId,
expectedFrame.id,
"Got expected frame id for frameAttached event"
);
is(
parentFrameId,
expectedFrame.parentId,
"Got expected parent frame id for frameAttached event"
);
});
}