forked from mirrors/gecko-dev
MozReview-Commit-ID: K9WuJuTdYHS --HG-- rename : devtools/shim/DevToolsShim.jsm => devtools/startup/DevToolsShim.jsm rename : devtools/shim/aboutdebugging-registration.js => devtools/startup/aboutdebugging-registration.js rename : devtools/shim/aboutdebugging.manifest => devtools/startup/aboutdebugging.manifest rename : devtools/shim/aboutdevtools/aboutdevtools-registration.js => devtools/startup/aboutdevtools/aboutdevtools-registration.js rename : devtools/shim/aboutdevtools/aboutdevtools.css => devtools/startup/aboutdevtools/aboutdevtools.css rename : devtools/shim/aboutdevtools/aboutdevtools.js => devtools/startup/aboutdevtools/aboutdevtools.js rename : devtools/shim/aboutdevtools/aboutdevtools.manifest => devtools/startup/aboutdevtools/aboutdevtools.manifest rename : devtools/shim/aboutdevtools/aboutdevtools.xhtml => devtools/startup/aboutdevtools/aboutdevtools.xhtml rename : devtools/shim/aboutdevtools/images/dev-edition-logo.svg => devtools/startup/aboutdevtools/images/dev-edition-logo.svg rename : devtools/shim/aboutdevtools/images/external-link.svg => devtools/startup/aboutdevtools/images/external-link.svg rename : devtools/shim/aboutdevtools/images/feature-console.svg => devtools/startup/aboutdevtools/images/feature-console.svg rename : devtools/shim/aboutdevtools/images/feature-debugger.svg => devtools/startup/aboutdevtools/images/feature-debugger.svg rename : devtools/shim/aboutdevtools/images/feature-inspector.svg => devtools/startup/aboutdevtools/images/feature-inspector.svg rename : devtools/shim/aboutdevtools/images/feature-memory.svg => devtools/startup/aboutdevtools/images/feature-memory.svg rename : devtools/shim/aboutdevtools/images/feature-network.svg => devtools/startup/aboutdevtools/images/feature-network.svg rename : devtools/shim/aboutdevtools/images/feature-performance.svg => devtools/startup/aboutdevtools/images/feature-performance.svg rename : devtools/shim/aboutdevtools/images/feature-responsive.svg => devtools/startup/aboutdevtools/images/feature-responsive.svg rename : devtools/shim/aboutdevtools/images/feature-storage.svg => devtools/startup/aboutdevtools/images/feature-storage.svg rename : devtools/shim/aboutdevtools/images/feature-visualediting.svg => devtools/startup/aboutdevtools/images/feature-visualediting.svg rename : devtools/shim/aboutdevtools/images/otter.svg => devtools/startup/aboutdevtools/images/otter.svg rename : devtools/shim/aboutdevtools/moz.build => devtools/startup/aboutdevtools/moz.build rename : devtools/shim/aboutdevtools/subscribe.css => devtools/startup/aboutdevtools/subscribe.css rename : devtools/shim/aboutdevtools/subscribe.js => devtools/startup/aboutdevtools/subscribe.js rename : devtools/shim/aboutdevtools/test/.eslintrc.js => devtools/startup/aboutdevtools/test/.eslintrc.js rename : devtools/shim/aboutdevtools/test/browser.ini => devtools/startup/aboutdevtools/test/browser.ini rename : devtools/shim/aboutdevtools/test/browser_aboutdevtools_closes_page.js => devtools/startup/aboutdevtools/test/browser_aboutdevtools_closes_page.js rename : devtools/shim/aboutdevtools/test/browser_aboutdevtools_enables_devtools.js => devtools/startup/aboutdevtools/test/browser_aboutdevtools_enables_devtools.js rename : devtools/shim/aboutdevtools/test/browser_aboutdevtools_focus_owner_tab.js => devtools/startup/aboutdevtools/test/browser_aboutdevtools_focus_owner_tab.js rename : devtools/shim/aboutdevtools/test/browser_aboutdevtools_reuse_existing.js => devtools/startup/aboutdevtools/test/browser_aboutdevtools_reuse_existing.js rename : devtools/shim/aboutdevtools/test/head.js => devtools/startup/aboutdevtools/test/head.js rename : devtools/shim/aboutdevtoolstoolbox-registration.js => devtools/startup/aboutdevtoolstoolbox-registration.js rename : devtools/shim/aboutdevtoolstoolbox.manifest => devtools/startup/aboutdevtoolstoolbox.manifest rename : devtools/shim/devtools-startup-prefs.js => devtools/startup/devtools-startup-prefs.js rename : devtools/shim/devtools-startup.js => devtools/startup/devtools-startup.js rename : devtools/shim/devtools-startup.manifest => devtools/startup/devtools-startup.manifest rename : devtools/shim/jar.mn => devtools/startup/jar.mn rename : devtools/shim/locales/en-US/aboutdevtools.dtd => devtools/startup/locales/en-US/aboutdevtools.dtd rename : devtools/shim/locales/en-US/aboutdevtools.properties => devtools/startup/locales/en-US/aboutdevtools.properties rename : devtools/shim/locales/en-US/key-shortcuts.properties => devtools/startup/locales/en-US/key-shortcuts.properties rename : devtools/shim/locales/en-US/startup.properties => devtools/startup/locales/en-US/startup.properties rename : devtools/shim/locales/jar.mn => devtools/startup/locales/jar.mn rename : devtools/shim/locales/moz.build => devtools/startup/locales/moz.build rename : devtools/shim/moz.build => devtools/startup/moz.build rename : devtools/shim/tests/browser/.eslintrc.js => devtools/startup/tests/browser/.eslintrc.js rename : devtools/shim/tests/browser/browser.ini => devtools/startup/tests/browser/browser.ini rename : devtools/shim/tests/browser/browser_shim_disable_devtools.js => devtools/startup/tests/browser/browser_shim_disable_devtools.js rename : devtools/shim/tests/unit/.eslintrc.js => devtools/startup/tests/unit/.eslintrc.js rename : devtools/shim/tests/unit/test_devtools_shim.js => devtools/startup/tests/unit/test_devtools_shim.js rename : devtools/shim/tests/unit/xpcshell.ini => devtools/startup/tests/unit/xpcshell.ini extra : rebase_source : 7867a5b103d01dc936091a71deeaf526e7f0e47a
370 lines
12 KiB
JavaScript
370 lines
12 KiB
JavaScript
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* vim: set sts=2 sw=2 et tw=80: */
|
|
"use strict";
|
|
|
|
/* exported getDevToolsTargetForContext, getInspectedWindowFront, getToolboxEvalOptions */
|
|
/* global getTargetTabIdForToolbox, getDevToolsTargetForContext, getInspectedWindowFront, getToolboxEvalOptions */
|
|
|
|
// The ext-* files are imported into the same scopes.
|
|
/* import-globals-from ext-browser.js */
|
|
|
|
/**
|
|
* This module provides helpers used by the other specialized `ext-devtools-*.js` modules
|
|
* and the implementation of the `devtools_page`.
|
|
*/
|
|
|
|
ChromeUtils.defineModuleGetter(this, "DevToolsShim",
|
|
"chrome://devtools-startup/content/DevToolsShim.jsm");
|
|
|
|
ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm");
|
|
|
|
var {
|
|
HiddenExtensionPage,
|
|
watchExtensionProxyContextLoad,
|
|
} = ExtensionParent;
|
|
|
|
// Map[extension -> DevToolsPageDefinition]
|
|
let devtoolsPageDefinitionMap = new Map();
|
|
|
|
let initDevTools;
|
|
|
|
/**
|
|
* Retrieve the devtools target for the devtools extension proxy context
|
|
* (lazily cloned from the target of the toolbox associated to the context
|
|
* the first time that it is accessed).
|
|
*
|
|
* @param {DevToolsExtensionPageContextParent} context
|
|
* A devtools extension proxy context.
|
|
*
|
|
* @returns {Promise<TabTarget>}
|
|
* The cloned devtools target associated to the context.
|
|
*/
|
|
global.getDevToolsTargetForContext = async (context) => {
|
|
if (context.devToolsTarget) {
|
|
await context.devToolsTarget.makeRemote();
|
|
return context.devToolsTarget;
|
|
}
|
|
|
|
if (!context.devToolsToolbox || !context.devToolsToolbox.target) {
|
|
throw new Error("Unable to get a TabTarget for a context not associated to any toolbox");
|
|
}
|
|
|
|
if (!context.devToolsToolbox.target.isLocalTab) {
|
|
throw new Error("Unexpected target type: only local tabs are currently supported.");
|
|
}
|
|
|
|
const tab = context.devToolsToolbox.target.tab;
|
|
context.devToolsTarget = DevToolsShim.createTargetForTab(tab);
|
|
|
|
await context.devToolsTarget.makeRemote();
|
|
|
|
return context.devToolsTarget;
|
|
};
|
|
|
|
/**
|
|
* Retrieve the devtools target for the devtools extension proxy context
|
|
* (lazily cloned from the target of the toolbox associated to the context
|
|
* the first time that it is accessed).
|
|
*
|
|
* @param {Toolbox} toolbox
|
|
* A devtools toolbox instance.
|
|
*
|
|
* @returns {number}
|
|
* The corresponding WebExtensions tabId.
|
|
*/
|
|
global.getTargetTabIdForToolbox = (toolbox) => {
|
|
let {target} = toolbox;
|
|
|
|
if (!target.isLocalTab) {
|
|
throw new Error("Unexpected target type: only local tabs are currently supported.");
|
|
}
|
|
|
|
let parentWindow = target.tab.linkedBrowser.ownerGlobal;
|
|
let tab = parentWindow.gBrowser.getTabForBrowser(target.tab.linkedBrowser);
|
|
|
|
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) {
|
|
// If there is not yet a front instance, then a lazily cloned target for the context is
|
|
// retrieved using the DevtoolsParentContextsManager helper (which is an asynchronous operation,
|
|
// because the first time that the target has been cloned, it is not ready to be used to create
|
|
// the front instance until it is connected to the remote debugger successfully).
|
|
const clonedTarget = await getDevToolsTargetForContext(context);
|
|
return DevToolsShim.createWebExtensionInspectedWindowFront(clonedTarget);
|
|
};
|
|
|
|
// Get the WebExtensionInspectedWindowActor eval options (needed to provide the $0 and inspect
|
|
// binding provided to the evaluated js code).
|
|
global.getToolboxEvalOptions = function(context) {
|
|
const options = {};
|
|
const toolbox = context.devToolsToolbox;
|
|
const selectedNode = toolbox.selection;
|
|
|
|
if (selectedNode && selectedNode.nodeFront) {
|
|
// If there is a selected node in the inspector, we hand over
|
|
// its actor id to the eval request in order to provide the "$0" binding.
|
|
options.toolboxSelectedNodeActorID = selectedNode.nodeFront.actorID;
|
|
}
|
|
|
|
// Provide the console actor ID to implement the "inspect" binding.
|
|
options.toolboxConsoleActorID = toolbox.target.form.consoleActor;
|
|
|
|
return options;
|
|
};
|
|
|
|
/**
|
|
* The DevToolsPage represents the "devtools_page" related to a particular
|
|
* Toolbox and WebExtension.
|
|
*
|
|
* The devtools_page contexts are invisible WebExtensions contexts, similar to the
|
|
* background page, associated to a single developer toolbox (e.g. If an add-on
|
|
* registers a devtools_page and the user opens 3 developer toolbox in 3 webpages,
|
|
* 3 devtools_page contexts will be created for that add-on).
|
|
*
|
|
* @param {Extension} extension
|
|
* The extension that owns the devtools_page.
|
|
* @param {Object} options
|
|
* @param {Toolbox} options.toolbox
|
|
* The developer toolbox instance related to this devtools_page.
|
|
* @param {string} options.url
|
|
* The path to the devtools page html page relative to the extension base URL.
|
|
* @param {DevToolsPageDefinition} options.devToolsPageDefinition
|
|
* The instance of the devToolsPageDefinition class related to this DevToolsPage.
|
|
*/
|
|
class DevToolsPage extends HiddenExtensionPage {
|
|
constructor(extension, options) {
|
|
super(extension, "devtools_page");
|
|
|
|
this.url = extension.baseURI.resolve(options.url);
|
|
this.toolbox = options.toolbox;
|
|
this.devToolsPageDefinition = options.devToolsPageDefinition;
|
|
|
|
this.unwatchExtensionProxyContextLoad = null;
|
|
|
|
this.waitForTopLevelContext = new Promise(resolve => {
|
|
this.resolveTopLevelContext = resolve;
|
|
});
|
|
}
|
|
|
|
async build() {
|
|
await this.createBrowserElement();
|
|
|
|
// Listening to new proxy contexts.
|
|
this.unwatchExtensionProxyContextLoad = watchExtensionProxyContextLoad(this, context => {
|
|
// Keep track of the toolbox and target associated to the context, which is
|
|
// needed by the API methods implementation.
|
|
context.devToolsToolbox = this.toolbox;
|
|
|
|
if (!this.topLevelContext) {
|
|
this.topLevelContext = context;
|
|
|
|
// Ensure this devtools page is destroyed, when the top level context proxy is
|
|
// closed.
|
|
this.topLevelContext.callOnClose(this);
|
|
|
|
this.resolveTopLevelContext(context);
|
|
}
|
|
});
|
|
|
|
extensions.emit("extension-browser-inserted", this.browser, {
|
|
devtoolsToolboxInfo: {
|
|
inspectedWindowTabId: getTargetTabIdForToolbox(this.toolbox),
|
|
themeName: DevToolsShim.getTheme(),
|
|
},
|
|
});
|
|
|
|
this.browser.loadURIWithFlags(this.url, {
|
|
triggeringPrincipal: this.extension.principal,
|
|
});
|
|
|
|
await this.waitForTopLevelContext;
|
|
}
|
|
|
|
close() {
|
|
if (this.closed) {
|
|
throw new Error("Unable to shutdown a closed DevToolsPage instance");
|
|
}
|
|
|
|
this.closed = true;
|
|
|
|
// Unregister the devtools page instance from the devtools page definition.
|
|
this.devToolsPageDefinition.forgetForTarget(this.toolbox.target);
|
|
|
|
// Unregister it from the resources to cleanup when the context has been closed.
|
|
if (this.topLevelContext) {
|
|
this.topLevelContext.forgetOnClose(this);
|
|
}
|
|
|
|
// Stop watching for any new proxy contexts from the devtools page.
|
|
if (this.unwatchExtensionProxyContextLoad) {
|
|
this.unwatchExtensionProxyContextLoad();
|
|
this.unwatchExtensionProxyContextLoad = null;
|
|
}
|
|
|
|
super.shutdown();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The DevToolsPageDefinitions class represents the "devtools_page" manifest property
|
|
* of a WebExtension.
|
|
*
|
|
* A DevToolsPageDefinition instance is created automatically when a WebExtension
|
|
* which contains the "devtools_page" manifest property has been loaded, and it is
|
|
* automatically destroyed when the related WebExtension has been unloaded,
|
|
* and so there will be at most one DevtoolsPageDefinition per add-on.
|
|
*
|
|
* Every time a developer tools toolbox is opened, the DevToolsPageDefinition creates
|
|
* and keep track of a DevToolsPage instance (which represents the actual devtools_page
|
|
* instance related to that particular toolbox).
|
|
*
|
|
* @param {Extension} extension
|
|
* The extension that owns the devtools_page.
|
|
* @param {string} url
|
|
* The path to the devtools page html page relative to the extension base URL.
|
|
*/
|
|
class DevToolsPageDefinition {
|
|
constructor(extension, url) {
|
|
initDevTools();
|
|
|
|
this.url = url;
|
|
this.extension = extension;
|
|
|
|
// Map[TabTarget -> DevToolsPage]
|
|
this.devtoolsPageForTarget = new Map();
|
|
}
|
|
|
|
onThemeChanged(themeName) {
|
|
Services.ppmm.broadcastAsyncMessage("Extension:DevToolsThemeChanged", {themeName});
|
|
}
|
|
|
|
buildForToolbox(toolbox) {
|
|
if (this.devtoolsPageForTarget.has(toolbox.target)) {
|
|
return Promise.reject(new Error("DevtoolsPage has been already created for this toolbox"));
|
|
}
|
|
|
|
const devtoolsPage = new DevToolsPage(this.extension, {
|
|
toolbox, url: this.url, devToolsPageDefinition: this,
|
|
});
|
|
|
|
// If this is the first DevToolsPage, subscribe to the theme-changed event
|
|
if (this.devtoolsPageForTarget.size === 0) {
|
|
DevToolsShim.on("theme-changed", this.onThemeChanged);
|
|
}
|
|
this.devtoolsPageForTarget.set(toolbox.target, devtoolsPage);
|
|
|
|
return devtoolsPage.build();
|
|
}
|
|
|
|
shutdownForTarget(target) {
|
|
if (this.devtoolsPageForTarget.has(target)) {
|
|
const devtoolsPage = this.devtoolsPageForTarget.get(target);
|
|
devtoolsPage.close();
|
|
|
|
// `devtoolsPage.close()` should remove the instance from the map,
|
|
// raise an exception if it is still there.
|
|
if (this.devtoolsPageForTarget.has(target)) {
|
|
throw new Error(`Leaked DevToolsPage instance for target "${target.toString()}"`);
|
|
}
|
|
|
|
// If this was the last DevToolsPage, unsubscribe from the theme-changed event
|
|
if (this.devtoolsPageForTarget.size === 0) {
|
|
DevToolsShim.off("theme-changed", this.onThemeChanged);
|
|
}
|
|
}
|
|
}
|
|
|
|
forgetForTarget(target) {
|
|
this.devtoolsPageForTarget.delete(target);
|
|
}
|
|
|
|
shutdown() {
|
|
for (let target of this.devtoolsPageForTarget.keys()) {
|
|
this.shutdownForTarget(target);
|
|
}
|
|
|
|
if (this.devtoolsPageForTarget.size > 0) {
|
|
throw new Error(
|
|
`Leaked ${this.devtoolsPageForTarget.size} DevToolsPage instances in devtoolsPageForTarget Map`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
let devToolsInitialized = false;
|
|
initDevTools = function() {
|
|
if (devToolsInitialized) {
|
|
return;
|
|
}
|
|
|
|
/* eslint-disable mozilla/balanced-listeners */
|
|
// Create a devtools page context for a new opened toolbox,
|
|
// based on the registered devtools_page definitions.
|
|
DevToolsShim.on("toolbox-created", toolbox => {
|
|
if (!toolbox.target.isLocalTab) {
|
|
// Only local tabs are currently supported (See Bug 1304378 for additional details
|
|
// related to remote targets support).
|
|
let msg = `Ignoring DevTools Toolbox for target "${toolbox.target.toString()}": ` +
|
|
`"${toolbox.target.name}" ("${toolbox.target.url}"). ` +
|
|
"Only local tab are currently supported by the WebExtensions DevTools API.";
|
|
let scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
|
|
scriptError.init(msg, null, null, null, null, Ci.nsIScriptError.warningFlag, "content javascript");
|
|
Services.console.logMessage(scriptError);
|
|
|
|
return;
|
|
}
|
|
|
|
for (let devtoolsPage of devtoolsPageDefinitionMap.values()) {
|
|
devtoolsPage.buildForToolbox(toolbox);
|
|
}
|
|
});
|
|
|
|
// Destroy a devtools page context for a destroyed toolbox,
|
|
// based on the registered devtools_page definitions.
|
|
DevToolsShim.on("toolbox-destroy", target => {
|
|
if (!target.isLocalTab) {
|
|
// Only local tabs are currently supported (See Bug 1304378 for additional details
|
|
// related to remote targets support).
|
|
return;
|
|
}
|
|
|
|
for (let devtoolsPageDefinition of devtoolsPageDefinitionMap.values()) {
|
|
devtoolsPageDefinition.shutdownForTarget(target);
|
|
}
|
|
});
|
|
/* eslint-enable mozilla/balanced-listeners */
|
|
|
|
devToolsInitialized = true;
|
|
};
|
|
|
|
this.devtools = class extends ExtensionAPI {
|
|
onManifestEntry(entryName) {
|
|
let {extension} = this;
|
|
let {manifest} = extension;
|
|
|
|
// Create and register a new devtools_page definition as specified in the
|
|
// "devtools_page" property in the extension manifest.
|
|
let devtoolsPageDefinition = new DevToolsPageDefinition(extension, manifest.devtools_page);
|
|
devtoolsPageDefinitionMap.set(extension, devtoolsPageDefinition);
|
|
}
|
|
|
|
onShutdown(reason) {
|
|
let {extension} = this;
|
|
|
|
// Destroy the registered devtools_page definition on extension shutdown.
|
|
if (devtoolsPageDefinitionMap.has(extension)) {
|
|
devtoolsPageDefinitionMap.get(extension).shutdown();
|
|
devtoolsPageDefinitionMap.delete(extension);
|
|
}
|
|
}
|
|
|
|
getAPI(context) {
|
|
return {
|
|
devtools: {},
|
|
};
|
|
}
|
|
};
|