зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1597879 - Implement Page.addScriptToEvaluateOnNewDocument; r=remote-protocol-reviewers,whimboo,ato
Differential Revision: https://phabricator.services.mozilla.com/D55334 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
bfe666563e
Коммит
ab6bc896fd
|
@ -80,6 +80,10 @@ class ContextObserver {
|
|||
this.emit("context-destroyed", { frameId });
|
||||
this.emit("frame-navigated", { frameId, window });
|
||||
this.emit("context-created", { windowId: id, window });
|
||||
// Delay script-loaded to allow context cleanup to happen first
|
||||
Services.tm.dispatchToMainThread(() => {
|
||||
this.emit("script-loaded");
|
||||
});
|
||||
break;
|
||||
case "pageshow":
|
||||
// `persisted` is true when this is about a page being resurected from BF Cache
|
||||
|
@ -89,6 +93,7 @@ class ContextObserver {
|
|||
// 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");
|
||||
break;
|
||||
|
||||
case "pagehide":
|
||||
|
|
|
@ -6,14 +6,25 @@
|
|||
|
||||
var EXPORTED_SYMBOLS = ["Page"];
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
const { ContentProcessDomain } = ChromeUtils.import(
|
||||
"chrome://remote/content/domains/ContentProcessDomain.jsm"
|
||||
);
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { UnsupportedError } = ChromeUtils.import(
|
||||
"chrome://remote/content/Error.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
"uuidGen",
|
||||
"@mozilla.org/uuid-generator;1",
|
||||
"nsIUUIDGenerator"
|
||||
);
|
||||
|
||||
const {
|
||||
LOAD_FLAGS_BYPASS_CACHE,
|
||||
LOAD_FLAGS_BYPASS_PROXY,
|
||||
|
@ -26,12 +37,19 @@ class Page extends ContentProcessDomain {
|
|||
|
||||
this.enabled = false;
|
||||
this.lifecycleEnabled = false;
|
||||
// script id => { source, worldName }
|
||||
this.scriptsToEvaluateOnLoad = new Map();
|
||||
this.worldsToEvaluateOnLoad = new Set();
|
||||
|
||||
this._onFrameNavigated = this._onFrameNavigated.bind(this);
|
||||
this._onScriptLoaded = this._onScriptLoaded.bind(this);
|
||||
|
||||
this.contextObserver.on("script-loaded", this._onScriptLoaded);
|
||||
}
|
||||
|
||||
destructor() {
|
||||
this.setLifecycleEventsEnabled({ enabled: false });
|
||||
this.contextObserver.off("script-loaded", this._onScriptLoaded);
|
||||
this.disable();
|
||||
|
||||
super.destructor();
|
||||
|
@ -124,7 +142,33 @@ class Page extends ContentProcessDomain {
|
|||
};
|
||||
}
|
||||
|
||||
addScriptToEvaluateOnNewDocument() {}
|
||||
/**
|
||||
* Enqueues given script to be evaluated in every frame upon creation
|
||||
*
|
||||
* If `worldName` is specified, creates an execution context with the given name
|
||||
* and evaluates given script in it.
|
||||
*
|
||||
* At this time, queued scripts do not get evaluated, hence `source` is marked as
|
||||
* "unsupported".
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {string} options.source (not supported)
|
||||
* @param {string=} options.worldName
|
||||
* @return {string} Page.ScriptIdentifier
|
||||
*/
|
||||
addScriptToEvaluateOnNewDocument(options = {}) {
|
||||
const { source, worldName } = options;
|
||||
if (worldName) {
|
||||
this.worldsToEvaluateOnLoad.add(worldName);
|
||||
}
|
||||
const identifier = uuidGen
|
||||
.generateUUID()
|
||||
.toString()
|
||||
.slice(1, -1);
|
||||
this.scriptsToEvaluateOnLoad.set(identifier, { worldName, source });
|
||||
|
||||
return { identifier };
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an isolated world for the given frame.
|
||||
|
@ -186,6 +230,20 @@ class Page extends ContentProcessDomain {
|
|||
});
|
||||
}
|
||||
|
||||
_onScriptLoaded(name) {
|
||||
const Runtime = this.session.domains.get("Runtime");
|
||||
for (const world of this.worldsToEvaluateOnLoad) {
|
||||
Runtime._onContextCreated("context-created", {
|
||||
windowId: this.content.windowUtils.currentInnerWindowID,
|
||||
window: this.content,
|
||||
isDefault: false,
|
||||
contextName: world,
|
||||
contextType: "isolated",
|
||||
});
|
||||
}
|
||||
// TODO evaluate each onNewDoc script in the appropriate world
|
||||
}
|
||||
|
||||
emitLifecycleEvent(frameId, loaderId, name, timestamp) {
|
||||
if (this.lifecycleEnabled) {
|
||||
this.emit("Page.lifecycleEvent", { frameId, loaderId, name, timestamp });
|
||||
|
|
|
@ -16,5 +16,6 @@ support-files =
|
|||
[browser_javascriptDialog_otherTarget.js]
|
||||
[browser_javascriptDialog_prompt.js]
|
||||
[browser_lifecycleEvent.js]
|
||||
[browser_scriptToEvaluateOnNewDocument.js]
|
||||
[browser_reload.js]
|
||||
[browser_runtimeEvents.js]
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test Page.addScriptToEvaluateOnNewDocument and Page.removeScriptToEvaluateOnNewDocument
|
||||
|
||||
const DOC = toDataURL("default-test-page");
|
||||
const WORLD = "testWorld";
|
||||
|
||||
// TODO Bug 1601695 - Add support for `source` parameter
|
||||
add_task(async function addScript({ Page, Runtime }) {
|
||||
await loadURL(DOC);
|
||||
const { identifier: id1 } = await Page.addScriptToEvaluateOnNewDocument({
|
||||
source: "1 + 1;",
|
||||
});
|
||||
is(typeof id1, "string", "Script id should be a string");
|
||||
ok(id1.length > 0, "Script id is non-empty");
|
||||
const { identifier: id2 } = await Page.addScriptToEvaluateOnNewDocument({
|
||||
source: "1 + 1;",
|
||||
});
|
||||
ok(id2.length > 0, "Script id is non-empty");
|
||||
isnot(id1, id2, "Two scripts should have different ids");
|
||||
await Runtime.enable();
|
||||
// flush event for DOC default context
|
||||
await Runtime.executionContextCreated();
|
||||
await checkIsolatedContextAfterLoad(Runtime, toDataURL("<p>Hello"), []);
|
||||
});
|
||||
|
||||
add_task(async function addScriptAfterNavigation({ Page }) {
|
||||
await loadURL(DOC);
|
||||
const { identifier: id1 } = await Page.addScriptToEvaluateOnNewDocument({
|
||||
source: "1 + 1;",
|
||||
});
|
||||
is(typeof id1, "string", "Script id should be a string");
|
||||
ok(id1.length > 0, "Script id is non-empty");
|
||||
await loadURL(toDataURL("<p>Hello"));
|
||||
const { identifier: id2 } = await Page.addScriptToEvaluateOnNewDocument({
|
||||
source: "1 + 2;",
|
||||
});
|
||||
ok(id2.length > 0, "Script id is non-empty");
|
||||
isnot(id1, id2, "Two scripts should have different ids");
|
||||
});
|
||||
|
||||
add_task(async function addWithIsolatedWorldAndNavigate({ Page, Runtime }) {
|
||||
await Runtime.enable();
|
||||
const { frameId } = await Page.navigate({ url: DOC });
|
||||
await Page.addScriptToEvaluateOnNewDocument({
|
||||
source: "1 + 1;",
|
||||
worldName: WORLD,
|
||||
});
|
||||
const isolatedId = await Page.createIsolatedWorld({
|
||||
frameId,
|
||||
worldName: WORLD,
|
||||
grantUniversalAccess: true,
|
||||
});
|
||||
const contexts = await checkIsolatedContextAfterLoad(
|
||||
Runtime,
|
||||
toDataURL("<p>Next")
|
||||
);
|
||||
isnot(contexts[1].id, isolatedId, "The context has a new id");
|
||||
});
|
||||
|
||||
add_task(async function addWithIsolatedWorldNavigateTwice({ Page, Runtime }) {
|
||||
await Runtime.enable();
|
||||
await Page.addScriptToEvaluateOnNewDocument({
|
||||
source: "1 + 1;",
|
||||
worldName: WORLD,
|
||||
});
|
||||
await checkIsolatedContextAfterLoad(Runtime, DOC);
|
||||
await checkIsolatedContextAfterLoad(Runtime, toDataURL("<p>Hello"));
|
||||
});
|
||||
|
||||
add_task(async function addTwoScriptsWithIsolatedWorld({ Page, Runtime }) {
|
||||
await Runtime.enable();
|
||||
const names = [WORLD, "A_whole_new_world"];
|
||||
await Page.addScriptToEvaluateOnNewDocument({
|
||||
source: "1 + 1;",
|
||||
worldName: names[0],
|
||||
});
|
||||
await Page.addScriptToEvaluateOnNewDocument({
|
||||
source: "1 + 8;",
|
||||
worldName: names[1],
|
||||
});
|
||||
await checkIsolatedContextAfterLoad(Runtime, DOC, names);
|
||||
});
|
||||
|
||||
async function checkIsolatedContextAfterLoad(Runtime, url, names = [WORLD]) {
|
||||
// At least the default context will get created
|
||||
const expected = names.length + 1;
|
||||
const contextsCreated = new Promise(resolve => {
|
||||
const ctx = [];
|
||||
const unsubscribe = Runtime.executionContextCreated(payload => {
|
||||
ctx.push(payload.context);
|
||||
info(
|
||||
`Runtime.executionContextCreated: ${payload.context.auxData.type}` +
|
||||
`\n\turl ${payload.context.origin}`
|
||||
);
|
||||
if (ctx.length > expected) {
|
||||
unsubscribe();
|
||||
resolve(ctx);
|
||||
}
|
||||
});
|
||||
timeoutPromise(1000).then(() => {
|
||||
unsubscribe();
|
||||
resolve(ctx);
|
||||
});
|
||||
});
|
||||
await loadURL(url);
|
||||
const contexts = await contextsCreated;
|
||||
is(contexts.length, expected, "Expected number of contexts got created");
|
||||
is(contexts[0].auxData.isDefault, true, "Got default context");
|
||||
is(contexts[0].auxData.type, "default", "Got default context");
|
||||
is(contexts[0].name, "", "Get context with empty name");
|
||||
names.forEach((name, index) => {
|
||||
is(contexts[index + 1].name, name, "Get context with expected name");
|
||||
is(contexts[index + 1].auxData.isDefault, false, "Got isolated context");
|
||||
is(contexts[index + 1].auxData.type, "isolated", "Got isolated context");
|
||||
});
|
||||
return contexts;
|
||||
}
|
Загрузка…
Ссылка в новой задаче