Bug 1699493 - [devtools] Migrate WebExtInspectedWindow front to a command. r=nchevobbe,rpl

- implement the new "inspected-window" command
- move WebExtensionInspectedWindowFront implement to the command, making the front empty
- migrate tests to use the commands instead of front
- stop maintaining the current top level target in ExtensionParent.jsm, no longer have to use watchTargets
- stop creating a new descriptor on each new target
- instead only pull one new dedicated "commands" for WebExt (still in ExtensionParent.jsm)
- remove TabDescriptor isDevToolsExtensionContext as we no longer need anything special in the descriptor
- remove now unused methods on DevToolsShims (createWebExtensionInspectedWindowFront, createDescriptorForTabForWebExtension)
- remove the now unused TabDescriptorFactory.createDescriptorForTab's "forceCreationForWebextension" option, as CommandsFactory.forTab always instantiate a brand new commands
- migrate webext to use the command instead of front

Differential Revision: https://phabricator.services.mozilla.com/D108994
This commit is contained in:
Alexandre Poirot 2021-04-01 10:31:43 +00:00
parent d48b91e66d
commit c8f91a99da
22 changed files with 307 additions and 366 deletions

View File

@ -15,7 +15,6 @@ module.exports = {
browserActionFor: true,
clickModifiersFromEvent: true,
getContainerForCookieStoreId: true,
getInspectedWindowFront: true,
getTargetTabIdForToolbox: true,
getToolboxEvalOptions: true,
isContainerCookieStoreId: true,

View File

@ -22,11 +22,11 @@ this.devtools_inspectedWindow = class extends ExtensionAPI {
devtools: {
inspectedWindow: {
async eval(expression, options) {
const front = await getInspectedWindowFront(context);
const toolboxEvalOptions = await getToolboxEvalOptions(context);
const evalOptions = Object.assign({}, options, toolboxEvalOptions);
const evalResult = await front.eval(
const commands = await context.getDevToolsCommands();
const evalResult = await commands.inspectedWindowCommand.eval(
callerInfo,
expression,
evalOptions
@ -39,8 +39,8 @@ this.devtools_inspectedWindow = class extends ExtensionAPI {
async reload(options) {
const { ignoreCache, userAgent, injectedScript } = options || {};
const front = await getInspectedWindowFront(context);
front.reload(callerInfo, {
const commands = await context.getDevToolsCommands();
commands.inspectedWindowCommand.reload(callerInfo, {
ignoreCache,
userAgent,
injectedScript,

View File

@ -571,10 +571,6 @@ const sidebarsById = new Map();
this.devtools_panels = class extends ExtensionAPI {
getAPI(context) {
// Lazily retrieved inspectedWindow actor front per child context
// (used by Sidebar.setExpression).
let waitForInspectedWindowFront;
// TODO - Bug 1448878: retrieve a more detailed callerInfo object,
// like the filename and lineNumber of the actual extension called
// in the child process.
@ -646,21 +642,14 @@ this.devtools_panels = class extends ExtensionAPI {
async setExpression(sidebarId, evalExpression, rootTitle) {
const sidebar = sidebarsById.get(sidebarId);
if (!waitForInspectedWindowFront) {
waitForInspectedWindowFront = getInspectedWindowFront(
context
);
}
const front = await waitForInspectedWindowFront;
const toolboxEvalOptions = await getToolboxEvalOptions(context);
const consoleFront = await context.devToolsToolbox.target.getFront(
"console"
);
const commands = await context.getDevToolsCommands();
const target = commands.targetCommand.targetFront;
const consoleFront = await target.getFront("console");
toolboxEvalOptions.consoleFront = consoleFront;
const evalResult = await front.eval(
const evalResult = await commands.inspectedWindowCommand.eval(
callerInfo,
evalExpression,
toolboxEvalOptions

View File

@ -54,13 +54,6 @@ global.getTargetTabIdForToolbox = toolbox => {
return tabTracker.getId(tab);
};
// Create an InspectedWindowFront instance for a given context (used in devtoools.inspectedWindow.eval
// and in sidebar.setExpression API methods).
global.getInspectedWindowFront = async function(context) {
const target = await context.getCurrentDevToolsTarget();
return DevToolsShim.createWebExtensionInspectedWindowFront(target);
};
// Get the WebExtensionInspectedWindowActor eval options (needed to provide the $0 and inspect
// binding provided to the evaluated js code).
global.getToolboxEvalOptions = async function(context) {

View File

@ -17,6 +17,12 @@ loader.lazyRequireGetter(
"devtools/client/framework/tab-descriptor-factory",
true
);
loader.lazyRequireGetter(
this,
"CommandsFactory",
"devtools/shared/commands/commands-factory",
true
);
loader.lazyRequireGetter(
this,
"ToolboxHostManager",
@ -706,21 +712,13 @@ DevTools.prototype = {
* Compatibility layer for web-extensions. Used by DevToolsShim for
* browser/components/extensions/ext-devtools.js
*
* web-extensions need to use dedicated instances of Target and cannot reuse the
* cached instances managed by DevTools target factory.
* web-extensions need to use dedicated instances of Commands and cannot reuse the
* cached instances managed by DevTools.
* Note that is will end up being cached in WebExtension codebase, via
* DevToolsExtensionPageContextParent.getDevToolsCommands.
*/
createDescriptorForTabForWebExtension: function(tab) {
return TabDescriptorFactory.createDescriptorForTab(tab, {
forceCreationForWebextension: true,
});
},
/**
* Compatibility layer for web-extensions. Used by DevToolsShim for
* browser/components/extensions/ext-devtools-inspectedWindow.js
*/
createWebExtensionInspectedWindowFront: function(tabTarget) {
return tabTarget.getFront("webExtensionInspectedWindow");
createCommandsForTabForWebExtension: function(tab) {
return CommandsFactory.forTab(tab);
},
/**

View File

@ -38,21 +38,10 @@ exports.TabDescriptorFactory = {
*
* @param {XULTab} tab
* The tab to use in creating a new descriptor.
* @param {Object} options
* - forceCreationForWebextension {Boolean}
* Only used from webextension codebase. When set to true, the
* factory will always create a new descriptor, even if we already
* created one for the provided tab. This descriptor will not be
* cached, and will not be returned via subsequent calls to this
* method.
*
* @return {TabDescriptorFront} The tab descriptor for the provided tab.
*/
async createDescriptorForTab(tab, { forceCreationForWebextension } = {}) {
if (forceCreationForWebextension) {
return this._createDescriptorForTab(tab);
}
async createDescriptorForTab(tab) {
let descriptor = descriptors.get(tab);
if (descriptor) {
return descriptor;

View File

@ -17,16 +17,8 @@ async function testTabDescriptorWithURL(url) {
info(`Test TabDescriptor against url ${url}\n`);
const tab = await addTab(url);
const descriptorPromises = [];
const sharedDescriptor = await TabDescriptorFactory.createDescriptorForTab(
tab
);
descriptorPromises.push(sharedDescriptor);
is(
sharedDescriptor.localTab,
tab,
"TabDescriptor's localTab is set correctly"
);
const descriptor = await TabDescriptorFactory.createDescriptorForTab(tab);
is(descriptor.localTab, tab, "TabDescriptor's localTab is set correctly");
info(
"Calling a second time createDescriptorForTab with the same tab, will return the same descriptor"
@ -34,58 +26,16 @@ async function testTabDescriptorWithURL(url) {
const secondDescriptor = await TabDescriptorFactory.createDescriptorForTab(
tab
);
is(sharedDescriptor, secondDescriptor, "second descriptor is the same");
is(descriptor, secondDescriptor, "second descriptor is the same");
info(
"forceCreationForWebextension allows to spawn new descriptor for the same tab"
);
const webExtDescriptor = await TabDescriptorFactory.createDescriptorForTab(
tab,
{ forceCreationForWebextension: true }
);
isnot(
sharedDescriptor,
webExtDescriptor,
"web extension descriptor is a new one"
);
is(
webExtDescriptor.localTab,
tab,
"web ext descriptor still refers to the same tab"
);
descriptorPromises.push(webExtDescriptor);
info("Instantiate many descriptor in parallel");
for (let i = 0; i < 10; i++) {
const descriptor = TabDescriptorFactory.createDescriptorForTab(tab, {
forceCreationForWebextension: true,
});
descriptorPromises.push(descriptor);
}
info("Wait for all descriptor to be resolved");
const descriptors = await Promise.all(descriptorPromises);
info("Wait for all targets to be created");
const targets = await Promise.all(
descriptors.map(async descriptor => {
return descriptor.getTarget();
})
);
info("Wait for descriptor's target");
const target = await descriptor.getTarget();
info("Call any method to ensure that each target works");
await Promise.all(
targets.map(async target => {
await target.logInPage("foo");
})
);
await target.logInPage("foo");
info("Destroy all the descriptors");
await Promise.all(
descriptors.map(async descriptor => {
await descriptor.destroy();
})
);
info("Destroy the descriptor");
await descriptor.destroy();
gBrowser.removeCurrentTab();
}

View File

@ -13,10 +13,6 @@ const {
registerFront,
} = require("devtools/shared/protocol");
const {
getAdHocFrontOrPrimitiveGrip,
} = require("devtools/client/fronts/object");
/**
* The corresponding Front object for the WebExtensionInspectedWindowActor.
*/
@ -29,68 +25,6 @@ class WebExtensionInspectedWindowFront extends FrontClassWithSpec(
// Attribute name from which to retrieve the actorID out of the target actor's form
this.formAttributeName = "webExtensionInspectedWindowActor";
}
/**
* Evaluate the provided javascript code in a target window.
*
* @param {Object} webExtensionCallerInfo - The addonId and the url (the addon base url
* or the url of the actual caller filename and lineNumber) used to log useful
* debugging information in the produced error logs and eval stack trace.
* @param {String} expression - The expression to evaluate.
* @param {Object} options - An option object. Check the actor method definition to see
* what properties it can hold (minus the `consoleFront` property which is defined
* below).
* @param {WebConsoleFront} options.consoleFront - An optional webconsole front. When
* set, the result will be either a primitive, a LongStringFront or an
* ObjectFront, and the WebConsoleActor corresponding to the console front will
* be used to generate those, which is needed if we want to handle ObjectFronts
* on the client.
*/
async eval(webExtensionCallerInfo, expression, options = {}) {
const { consoleFront } = options;
if (consoleFront) {
options.evalResultAsGrip = true;
options.toolboxConsoleActorID = consoleFront.actor;
delete options.consoleFront;
}
const response = await super.eval(
webExtensionCallerInfo,
expression,
options
);
// If no consoleFront was provided, we can directly return the response.
if (!consoleFront) {
return response;
}
if (
!response.hasOwnProperty("exceptionInfo") &&
!response.hasOwnProperty("valueGrip")
) {
throw new Error(
"Response does not have `exceptionInfo` or `valueGrip` property"
);
}
if (response.exceptionInfo) {
console.error(
response.exceptionInfo.description,
...(response.exceptionInfo.details || [])
);
return response;
}
// On the server, the valueGrip is created from the toolbox webconsole actor.
// If we want since the ObjectFront connection is inherited from the parent front, we
// need to set the console front as the parent front.
return getAdHocFrontOrPrimitiveGrip(
response.valueGrip,
consoleFront || this
);
}
}
exports.WebExtensionInspectedWindowFront = WebExtensionInspectedWindowFront;

View File

@ -44,10 +44,6 @@ class TabDescriptorFront extends DescriptorMixin(
// debugging).
this._localTab = null;
// Flipped when creating dedicated tab targets for DevTools WebExtensions.
// See toolkit/components/extensions/ExtensionParent.jsm .
this.isDevToolsExtensionContext = false;
this._onTargetDestroyed = this._onTargetDestroyed.bind(this);
this._handleTabEvent = this._handleTabEvent.bind(this);
}
@ -246,11 +242,6 @@ class TabDescriptorFront extends DescriptorMixin(
* Process change can go in both ways.
*/
async _onRemotenessChange() {
// The front that was created for DevTools page extension does not have corresponding toolbox.
if (this.isDevToolsExtensionContext) {
return;
}
// In a near future, this client side code should be replaced by actor code,
// notifying about new tab targets.
this.emit("remoteness-change", this._targetFront);

View File

@ -139,9 +139,7 @@ add_task(async function testSidebarSetObject() {
});
add_task(async function testSidebarSetExpressionResult() {
const inspectedWindowFront = await toolbox.target.getFront(
"webExtensionInspectedWindow"
);
const { commands } = toolbox;
const sidebar = inspector.getPanel(SIDEBAR_ID);
const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
@ -156,7 +154,7 @@ add_task(async function testSidebarSetExpressionResult() {
`;
const consoleFront = await toolbox.target.getFront("console");
let evalResult = await inspectedWindowFront.eval(
let evalResult = await commands.inspectedWindowCommand.eval(
fakeExtCallerInfo,
expression,
{
@ -228,7 +226,7 @@ add_task(async function testSidebarSetExpressionResult() {
info(
"Testing sidebar.setExpressionResult for an expression returning a longstring"
);
evalResult = await inspectedWindowFront.eval(
evalResult = await commands.inspectedWindowCommand.eval(
fakeExtCallerInfo,
`"ab ".repeat(10000)`,
{
@ -250,23 +248,23 @@ add_task(async function testSidebarSetExpressionResult() {
info(
"Testing sidebar.setExpressionResult for an expression returning a primitive"
);
evalResult = await inspectedWindowFront.eval(fakeExtCallerInfo, `1 + 2`, {
consoleFront,
});
evalResult = await commands.inspectedWindowCommand.eval(
fakeExtCallerInfo,
`1 + 2`,
{
consoleFront,
}
);
sidebar.setExpressionResult(evalResult);
const numberEl = await ContentTaskUtils.waitForCondition(
() => sidebarPanelContent.querySelector(".objectBox-number"),
"Wait for the result number element to be rendered"
);
is(numberEl.textContent, "3", `The "1 + 2" expression was evaluated as "3"`);
inspectedWindowFront.destroy();
});
add_task(async function testSidebarDOMNodeHighlighting() {
const inspectedWindowFront = await toolbox.target.getFront(
"webExtensionInspectedWindow"
);
const { commands } = toolbox;
const sidebar = inspector.getPanel(SIDEBAR_ID);
const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
@ -278,7 +276,7 @@ add_task(async function testSidebarDOMNodeHighlighting() {
const expression = "({ body: document.body })";
const consoleFront = await toolbox.target.getFront("console");
const evalResult = await inspectedWindowFront.eval(
const evalResult = await commands.inspectedWindowCommand.eval(
fakeExtCallerInfo,
expression,
{
@ -329,8 +327,6 @@ add_task(async function testSidebarDOMNodeHighlighting() {
await onNodeUnhighlight;
info("The node is no longer highlighted");
inspectedWindowFront.destroy();
});
add_task(async function testSidebarDOMNodeOpenInspector() {
@ -387,10 +383,6 @@ add_task(async function testSidebarDOMNodeOpenInspector() {
});
add_task(async function testSidebarSetExtensionPage() {
const inspectedWindowFront = await toolbox.target.getFront(
"webExtensionInspectedWindow"
);
const sidebar = inspector.getPanel(SIDEBAR_ID);
const sidebarPanelContent = inspector.sidebar.getTabPanel(SIDEBAR_ID);
@ -407,7 +399,6 @@ add_task(async function testSidebarSetExtensionPage() {
await testSetExtensionPageSidebarPanel(sidebarPanelContent, expectedURL);
inspectedWindowFront.destroy();
await SpecialPowers.popPrefEnv();
});

View File

@ -27,7 +27,6 @@ support-files =
doc_perf.html
error-actor.js
grid.html
inspectedwindow-reload-target.sjs
inspector-isScrollable-data.html
inspector-search-data.html
inspector-traversal-data.html
@ -170,4 +169,3 @@ fail-if = fission
[browser_stylesheets_getTextEmpty.js]
[browser_stylesheets_nested-iframes.js]
[browser_watcher-watchTargets-frames.js]
[browser_webextension_inspected_window.js]

View File

@ -8,6 +8,8 @@
// (please try to keep the list alphabetically sorted)
/*eslint sort-keys: "error"*/
const Commands = {
inspectedWindowCommand:
"devtools/shared/commands/inspected-window/inspected-window-command",
targetCommand: "devtools/shared/commands/target/target-command",
targetConfigurationCommand:
"devtools/shared/commands/target-configuration/target-configuration-command",

View File

@ -0,0 +1,106 @@
/* 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/. */
"use strict";
const {
getAdHocFrontOrPrimitiveGrip,
// eslint-disable-next-line mozilla/reject-some-requires
} = require("devtools/client/fronts/object");
/**
* For now, this class is mostly a wrapper around webExtInspectedWindow actor.
*/
class InspectedWindowCommand {
constructor({ commands }) {
this.commands = commands;
}
/**
* Return a promise that resolves to the related target actor's front.
* The Web Extension inspected window actor.
*
* @return {Promise<WebExtensionInspectedWindowFront>}
*/
getFront() {
return this.commands.targetCommand.targetFront.getFront(
"webExtensionInspectedWindow"
);
}
/**
* Evaluate the provided javascript code in a target window.
*
* @param {Object} webExtensionCallerInfo - The addonId and the url (the addon base url
* or the url of the actual caller filename and lineNumber) used to log useful
* debugging information in the produced error logs and eval stack trace.
* @param {String} expression - The expression to evaluate.
* @param {Object} options - An option object. Check the actor method definition to see
* what properties it can hold (minus the `consoleFront` property which is defined
* below).
* @param {WebConsoleFront} options.consoleFront - An optional webconsole front. When
* set, the result will be either a primitive, a LongStringFront or an
* ObjectFront, and the WebConsoleActor corresponding to the console front will
* be used to generate those, which is needed if we want to handle ObjectFronts
* on the client.
*/
async eval(webExtensionCallerInfo, expression, options = {}) {
const { consoleFront } = options;
if (consoleFront) {
options.evalResultAsGrip = true;
options.toolboxConsoleActorID = consoleFront.actor;
delete options.consoleFront;
}
const front = await this.getFront();
const response = await front.eval(
webExtensionCallerInfo,
expression,
options
);
// If no consoleFront was provided, we can directly return the response.
if (!consoleFront) {
return response;
}
if (
!response.hasOwnProperty("exceptionInfo") &&
!response.hasOwnProperty("valueGrip")
) {
throw new Error(
"Response does not have `exceptionInfo` or `valueGrip` property"
);
}
if (response.exceptionInfo) {
console.error(
response.exceptionInfo.description,
...(response.exceptionInfo.details || [])
);
return response;
}
// On the server, the valueGrip is created from the toolbox webconsole actor.
// If we want since the ObjectFront connection is inherited from the parent front, we
// need to set the console front as the parent front.
return getAdHocFrontOrPrimitiveGrip(
response.valueGrip,
consoleFront || this
);
}
/**
* For more information about the arguments, please have a look at
* the actor specification: devtools/shared/specs/addon/webextension-inspected-window.js
* or actor: devtools/server/actors/addon/webextension-inspected-window.js
*/
async reload(callerInfo, options) {
const front = await this.getFront();
return front.reload(callerInfo, options);
}
}
module.exports = InspectedWindowCommand;

View File

@ -0,0 +1,10 @@
# 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/.
DevToolsModules(
"inspected-window-command.js",
)
if CONFIG["MOZ_BUILD_APP"] != "mobile/android":
BROWSER_CHROME_MANIFESTS += ["tests/browser.ini"]

View File

@ -0,0 +1,7 @@
"use strict";
// General rule from /.eslintrc.js only accept folders matching **/test*/browser*/
// where is this folder doesn't match, so manually apply browser test config
module.exports = {
extends: ["plugin:mozilla/browser-test"],
};

View File

@ -0,0 +1,11 @@
[DEFAULT]
tags = devtools
subsuite = devtools
support-files =
!/devtools/client/shared/test/shared-head.js
!/devtools/client/shared/test/telemetry-test-helpers.js
!/devtools/client/shared/test/test-actor.js
head.js
inspectedwindow-reload-target.sjs
[browser_webextension_inspected_window.js]

View File

@ -3,7 +3,7 @@
"use strict";
const TEST_RELOAD_URL = `${MAIN_DOMAIN}/inspectedwindow-reload-target.sjs`;
const TEST_RELOAD_URL = `${URL_ROOT}/inspectedwindow-reload-target.sjs`;
async function setup(pageUrl) {
const extension = ExtensionTestUtils.loadExtension({
@ -23,32 +23,31 @@ async function setup(pageUrl) {
addonId: extension.id,
};
const target = await addTabTarget(pageUrl);
const tab = await addTab(pageUrl);
const { client } = target;
const webConsoleFront = await target.getFront("console");
const inspectedWindowFront = await target.getFront(
"webExtensionInspectedWindow"
const commands = await CommandsFactory.forTab(tab);
await commands.targetCommand.startListening();
const webConsoleFront = await commands.targetCommand.targetFront.getFront(
"console"
);
return {
client,
target,
webConsoleFront,
inspectedWindowFront,
commands,
extension,
fakeExtCallerInfo,
};
}
async function teardown({ client, extension }) {
await client.close();
DevToolsServer.destroy();
async function teardown({ commands, extension }) {
await commands.destroy();
gBrowser.removeCurrentTab();
await extension.unload();
}
function waitForNextTabNavigated(target) {
function waitForNextTabNavigated(commands) {
const target = commands.targetCommand.targetFront;
return new Promise(resolve => {
target.on("tabNavigated", function tabNavigatedListener(pkt) {
if (pkt.state == "stop" && !pkt.isFrameSwitching) {
@ -92,14 +91,9 @@ function collectEvalResults() {
}
add_task(async function test_successfull_inspectedWindowEval_result() {
const {
client,
inspectedWindowFront,
extension,
fakeExtCallerInfo,
} = await setup(MAIN_DOMAIN);
const { commands, extension, fakeExtCallerInfo } = await setup(URL_ROOT);
const result = await inspectedWindowFront.eval(
const result = await commands.inspectedWindowCommand.eval(
fakeExtCallerInfo,
"window.location",
{}
@ -108,7 +102,7 @@ add_task(async function test_successfull_inspectedWindowEval_result() {
ok(result.value, "Got a result from inspectedWindow eval");
is(
result.value.href,
MAIN_DOMAIN,
URL_ROOT,
"Got the expected window.location.href property value"
);
is(
@ -117,22 +111,25 @@ add_task(async function test_successfull_inspectedWindowEval_result() {
"Got the expected window.location.protocol property value"
);
await teardown({ client, extension });
await teardown({ commands, extension });
});
add_task(async function test_successfull_inspectedWindowEval_resultAsGrip() {
const {
client,
inspectedWindowFront,
commands,
extension,
fakeExtCallerInfo,
webConsoleFront,
} = await setup(MAIN_DOMAIN);
} = await setup(URL_ROOT);
let result = await inspectedWindowFront.eval(fakeExtCallerInfo, "window", {
evalResultAsGrip: true,
toolboxConsoleActorID: webConsoleFront.actor,
});
let result = await commands.inspectedWindowCommand.eval(
fakeExtCallerInfo,
"window",
{
evalResultAsGrip: true,
toolboxConsoleActorID: webConsoleFront.actor,
}
);
ok(result.valueGrip, "Got a result from inspectedWindow eval");
ok(result.valueGrip.actor, "Got a object actor as expected");
@ -144,9 +141,13 @@ add_task(async function test_successfull_inspectedWindowEval_resultAsGrip() {
);
// Test invalid evalResultAsGrip request.
result = await inspectedWindowFront.eval(fakeExtCallerInfo, "window", {
evalResultAsGrip: true,
});
result = await commands.inspectedWindowCommand.eval(
fakeExtCallerInfo,
"window",
{
evalResultAsGrip: true,
}
);
ok(
!result.value && !result.valueGrip,
@ -186,18 +187,13 @@ add_task(async function test_successfull_inspectedWindowEval_resultAsGrip() {
"Got the expected content in the error results's details"
);
await teardown({ client, extension });
await teardown({ commands, extension });
});
add_task(async function test_error_inspectedWindowEval_result() {
const {
client,
inspectedWindowFront,
extension,
fakeExtCallerInfo,
} = await setup(MAIN_DOMAIN);
const { commands, extension, fakeExtCallerInfo } = await setup(URL_ROOT);
const result = await inspectedWindowFront.eval(
const result = await commands.inspectedWindowCommand.eval(
fakeExtCallerInfo,
"window",
{}
@ -232,19 +228,16 @@ add_task(async function test_error_inspectedWindowEval_result() {
"Got the expected content in the error results's details"
);
await teardown({ client, extension });
await teardown({ commands, extension });
});
add_task(
async function test_system_principal_denied_error_inspectedWindowEval_result() {
const {
client,
inspectedWindowFront,
extension,
fakeExtCallerInfo,
} = await setup("about:addons");
const { commands, extension, fakeExtCallerInfo } = await setup(
"about:addons"
);
const result = await inspectedWindowFront.eval(
const result = await commands.inspectedWindowCommand.eval(
fakeExtCallerInfo,
"window",
{}
@ -276,19 +269,14 @@ add_task(
"Got the expected content in the error results's details"
);
await teardown({ client, extension });
await teardown({ commands, extension });
}
);
add_task(async function test_exception_inspectedWindowEval_result() {
const {
client,
inspectedWindowFront,
extension,
fakeExtCallerInfo,
} = await setup(MAIN_DOMAIN);
const { commands, extension, fakeExtCallerInfo } = await setup(URL_ROOT);
const result = await inspectedWindowFront.eval(
const result = await commands.inspectedWindowCommand.eval(
fakeExtCallerInfo,
"throw Error('fake eval error');",
{}
@ -314,25 +302,26 @@ add_task(async function test_exception_inspectedWindowEval_result() {
"Got the expected stack trace in the exception message"
);
await teardown({ client, extension });
await teardown({ commands, extension });
});
add_task(async function test_exception_inspectedWindowReload() {
const {
client,
webConsoleFront,
inspectedWindowFront,
commands,
extension,
fakeExtCallerInfo,
target,
} = await setup(`${TEST_RELOAD_URL}?test=cache`);
// Test reload with bypassCache=false.
const waitForNoBypassCacheReload = waitForNextTabNavigated(target);
const reloadResult = await inspectedWindowFront.reload(fakeExtCallerInfo, {
ignoreCache: false,
});
const waitForNoBypassCacheReload = waitForNextTabNavigated(commands);
const reloadResult = await commands.inspectedWindowCommand.reload(
fakeExtCallerInfo,
{
ignoreCache: false,
}
);
ok(
!reloadResult,
@ -353,8 +342,10 @@ add_task(async function test_exception_inspectedWindowReload() {
// Test reload with bypassCache=true.
const waitForForceBypassCacheReload = waitForNextTabNavigated(target);
await inspectedWindowFront.reload(fakeExtCallerInfo, { ignoreCache: true });
const waitForForceBypassCacheReload = waitForNextTabNavigated(commands);
await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {
ignoreCache: true,
});
await waitForForceBypassCacheReload;
@ -368,23 +359,21 @@ add_task(async function test_exception_inspectedWindowReload() {
"Got the expected result with reload forceBypassCache=true"
);
await teardown({ client, extension });
await teardown({ commands, extension });
});
add_task(async function test_exception_inspectedWindowReload_customUserAgent() {
const {
client,
webConsoleFront,
inspectedWindowFront,
commands,
extension,
fakeExtCallerInfo,
target,
} = await setup(`${TEST_RELOAD_URL}?test=user-agent`);
// Test reload with custom userAgent.
const waitForCustomUserAgentReload = waitForNextTabNavigated(target);
await inspectedWindowFront.reload(fakeExtCallerInfo, {
const waitForCustomUserAgentReload = waitForNextTabNavigated(commands);
await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {
userAgent: "Customized User Agent",
});
@ -402,8 +391,8 @@ add_task(async function test_exception_inspectedWindowReload_customUserAgent() {
// Test reload with no custom userAgent.
const waitForNoCustomUserAgentReload = waitForNextTabNavigated(target);
await inspectedWindowFront.reload(fakeExtCallerInfo, {});
const waitForNoCustomUserAgentReload = waitForNextTabNavigated(commands);
await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {});
await waitForNoCustomUserAgentReload;
@ -417,23 +406,21 @@ add_task(async function test_exception_inspectedWindowReload_customUserAgent() {
"Got the expected result with reload without a customized userAgent"
);
await teardown({ client, extension });
await teardown({ commands, extension });
});
add_task(async function test_exception_inspectedWindowReload_injectedScript() {
const {
client,
webConsoleFront,
inspectedWindowFront,
commands,
extension,
fakeExtCallerInfo,
target,
} = await setup(`${TEST_RELOAD_URL}?test=injected-script&frames=3`);
// Test reload with an injectedScript.
const waitForInjectedScriptReload = waitForNextTabNavigated(target);
await inspectedWindowFront.reload(fakeExtCallerInfo, {
const waitForInjectedScriptReload = waitForNextTabNavigated(commands);
await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {
injectedScript: `new ${injectedScript}`,
});
await waitForInjectedScriptReload;
@ -452,8 +439,8 @@ add_task(async function test_exception_inspectedWindowReload_injectedScript() {
// Test reload without an injectedScript.
const waitForNoInjectedScriptReload = waitForNextTabNavigated(target);
await inspectedWindowFront.reload(fakeExtCallerInfo, {});
const waitForNoInjectedScriptReload = waitForNextTabNavigated(commands);
await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {});
await waitForNoInjectedScriptReload;
const noInjectedScriptEval = await webConsoleFront.evaluateJSAsync(
@ -468,28 +455,26 @@ add_task(async function test_exception_inspectedWindowReload_injectedScript() {
"Got the expected result on reload with no injected script"
);
await teardown({ client, extension });
await teardown({ commands, extension });
});
add_task(async function test_exception_inspectedWindowReload_multiple_calls() {
const {
client,
webConsoleFront,
inspectedWindowFront,
commands,
extension,
fakeExtCallerInfo,
target,
} = await setup(`${TEST_RELOAD_URL}?test=user-agent`);
// Test reload with custom userAgent three times (and then
// check that only the first one has affected the page reload.
const waitForCustomUserAgentReload = waitForNextTabNavigated(target);
const waitForCustomUserAgentReload = waitForNextTabNavigated(commands);
inspectedWindowFront.reload(fakeExtCallerInfo, {
commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {
userAgent: "Customized User Agent 1",
});
inspectedWindowFront.reload(fakeExtCallerInfo, {
commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {
userAgent: "Customized User Agent 2",
});
@ -507,8 +492,8 @@ add_task(async function test_exception_inspectedWindowReload_multiple_calls() {
// Test reload with no custom userAgent.
const waitForNoCustomUserAgentReload = waitForNextTabNavigated(target);
await inspectedWindowFront.reload(fakeExtCallerInfo, {});
const waitForNoCustomUserAgentReload = waitForNextTabNavigated(commands);
await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {});
await waitForNoCustomUserAgentReload;
@ -522,23 +507,21 @@ add_task(async function test_exception_inspectedWindowReload_multiple_calls() {
"Got the expected result with reload without a customized userAgent"
);
await teardown({ client, extension });
await teardown({ commands, extension });
});
add_task(async function test_exception_inspectedWindowReload_stopped() {
const {
client,
webConsoleFront,
inspectedWindowFront,
commands,
extension,
fakeExtCallerInfo,
target,
} = await setup(`${TEST_RELOAD_URL}?test=injected-script&frames=3`);
// Test reload on a page that calls window.stop() immediately during the page loading
const waitForPageLoad = waitForNextTabNavigated(target);
await inspectedWindowFront.eval(
const waitForPageLoad = waitForNextTabNavigated(commands);
await commands.inspectedWindowCommand.eval(
fakeExtCallerInfo,
"window.location += '&stop=windowStop'"
);
@ -547,8 +530,8 @@ add_task(async function test_exception_inspectedWindowReload_stopped() {
await waitForPageLoad;
info("Starting a reload with an injectedScript");
const waitForInjectedScriptReload = waitForNextTabNavigated(target);
await inspectedWindowFront.reload(fakeExtCallerInfo, {
const waitForInjectedScriptReload = waitForNextTabNavigated(commands);
await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {
injectedScript: `new ${injectedScript}`,
});
await waitForInjectedScriptReload;
@ -570,8 +553,8 @@ add_task(async function test_exception_inspectedWindowReload_stopped() {
// Reload again with no options.
info("Reload the tab again without any reload options");
const waitForNoInjectedScriptReload = waitForNextTabNavigated(target);
await inspectedWindowFront.reload(fakeExtCallerInfo, {});
const waitForNoInjectedScriptReload = waitForNextTabNavigated(commands);
await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {});
await waitForNoInjectedScriptReload;
const noInjectedScriptEval = await webConsoleFront.evaluateJSAsync(
@ -589,7 +572,7 @@ add_task(async function test_exception_inspectedWindowReload_stopped() {
"No injectedScript should have been evaluated during the second reload"
);
await teardown({ client, extension });
await teardown({ commands, extension });
});
// TODO: check eval with $0 binding once implemented (Bug 1300590)

View File

@ -0,0 +1,13 @@
/* 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/. */
"use strict";
/* eslint no-unused-vars: [2, {"vars": "local"}] */
/* import-globals-from ../../../../client/shared/test/shared-head.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
this
);

View File

@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += [
"inspected-window",
"target",
"target-configuration",
]

View File

@ -313,8 +313,7 @@ const DevToolsShim = {
* therefore DevTools should always be available when they are called.
*/
const webExtensionsMethods = [
"createDescriptorForTabForWebExtension",
"createWebExtensionInspectedWindowFront",
"createCommandsForTabForWebExtension",
"getTheme",
"openBrowserConsole",
];

View File

@ -626,13 +626,13 @@ class DevToolsExtensionPageContextParent extends ExtensionPageContextParent {
constructor(...params) {
super(...params);
// We want to explicitly set `this._devToolsToolbox` as well to `null` here, but the
// toolbox is set during the processing of the parent's constructor, so it is currently
// not possible to set.
this._currentDevToolsTarget = null;
// Set all attributes that are lazily defined to `null` here.
//
// Note that we can't do that for `this._devToolsToolbox` because it will
// be defined when calling our parent constructor and so would override it back to `null`.
this._devToolsCommands = null;
this._onNavigatedListeners = null;
this._onTargetAvailable = this._onTargetAvailable.bind(this);
this._onResourceAvailable = this._onResourceAvailable.bind(this);
}
@ -671,30 +671,31 @@ class DevToolsExtensionPageContextParent extends ExtensionPageContextParent {
}
/**
* The returned target may be destroyed when navigating to another process and so,
* should only be used accordingly. That is to say, we can do an immediate action on it,
* but not listen to RDP events.
* @returns {Promise<TabTarget>}
* The current devtools target associated to the context.
* The returned "commands" object, exposing modules implemented from devtools/shared/commands.
* Each attribute being a static interface to communicate with the server backend.
*
* @returns {Promise<Object>}
*/
async getCurrentDevToolsTarget() {
if (!this._currentDevToolsTarget) {
if (!this._pendingWatchTargetsPromise) {
// When _onTargetAvailable is called, it will create a new target,
// via DevToolsShim.createDescriptorForTabForWebExtension. If this function
// is called multiple times before this._currentDevToolsTarget is populated,
// we don't want to create X new, duplicated targets, so we store the Promise
// returned by watchTargets, in order to properly wait on subsequent calls.
this._pendingWatchTargetsPromise = this.devToolsToolbox.targetList.watchTargets(
[this.devToolsToolbox.targetList.TYPES.FRAME],
this._onTargetAvailable
);
}
await this._pendingWatchTargetsPromise;
this._pendingWatchTargetsPromise = null;
async getDevToolsCommands() {
// Ensure that we try to instantiate a commands only once,
// even if createCommandsForTabForWebExtension is async.
if (this._devToolsCommandsPromise) {
return this._devToolsCommandsPromise;
}
if (this._devToolsCommands) {
return this._devToolsCommands;
}
return this._currentDevToolsTarget;
this._devToolsCommandsPromise = (async () => {
const commands = await DevToolsShim.createCommandsForTabForWebExtension(
this.devToolsToolbox.descriptorFront.localTab
);
await commands.targetCommand.startListening();
this._devToolsCommands = commands;
this._devToolsCommandsPromise = null;
return commands;
})();
return this._devToolsCommandsPromise;
}
unload() {
@ -703,11 +704,6 @@ class DevToolsExtensionPageContextParent extends ExtensionPageContextParent {
return;
}
this.devToolsToolbox.targetList.unwatchTargets(
[this.devToolsToolbox.targetList.TYPES.FRAME],
this._onTargetAvailable
);
if (this._onNavigatedListeners) {
this.devToolsToolbox.resourceWatcher.unwatchResources(
[this.devToolsToolbox.resourceWatcher.TYPES.DOCUMENT_EVENT],
@ -715,9 +711,9 @@ class DevToolsExtensionPageContextParent extends ExtensionPageContextParent {
);
}
if (this._currentDevToolsTarget) {
this._currentDevToolsTarget.destroy();
this._currentDevToolsTarget = null;
if (this._devToolsCommands) {
this._devToolsCommands.destroy();
this._devToolsCommands = null;
}
if (this._onNavigatedListeners) {
@ -730,25 +726,6 @@ class DevToolsExtensionPageContextParent extends ExtensionPageContextParent {
super.unload();
}
async _onTargetAvailable({ targetFront }) {
if (!targetFront.isTopLevel) {
return;
}
const descriptorFront = await DevToolsShim.createDescriptorForTabForWebExtension(
targetFront.localTab
);
// Update the TabDescriptor `isDevToolsExtensionContext` flag.
// This is a duplicated target, attached to no toolbox, DevTools needs to
// handle it differently compared to a regular top-level target.
descriptorFront.isDevToolsExtensionContext = true;
this._currentDevToolsTarget = await descriptorFront.getTarget();
await this._currentDevToolsTarget.attach();
}
async _onResourceAvailable(resources) {
for (const resource of resources) {
const { targetFront } = resource;