--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -27,16 +27,17 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyModuleGetter(this, "NativeApp",
"resource://gre/modules/NativeMessaging.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
"resource://gre/modules/PromiseUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
"resource://gre/modules/Schemas.jsm");
const CATEGORY_EXTENSION_SCRIPTS_ADDON = "webextension-scripts-addon";
+const CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS = "webextension-scripts-devtools";
Cu.import("resource://gre/modules/ExtensionCommon.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
const {
DefaultMap,
EventManager,
SingletonEventManager,
@@ -458,16 +459,39 @@ var apiManager = new class extends Schem
registerSchemaAPI(namespace, envType, getAPI) {
if (envType == "addon_child") {
super.registerSchemaAPI(namespace, envType, getAPI);
}
}
}();
+var devtoolsAPIManager = new class extends SchemaAPIManager {
+ constructor() {
+ super("devtools");
+ this.initialized = false;
+ }
+
+ generateAPIs(...args) {
+ if (!this.initialized) {
+ this.initialized = true;
+ for (let [/* name */, value] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS)) {
+ this.loadScript(value);
+ }
+ }
+ return super.generateAPIs(...args);
+ }
+
+ registerSchemaAPI(namespace, envType, getAPI) {
+ if (envType == "devtools_child") {
+ super.registerSchemaAPI(namespace, envType, getAPI);
+ }
+ }
+}();
+
/**
* An object that runs an remote implementation of an API.
*/
class ProxyAPIImplementation extends SchemaAPIInterface {
/**
* @param {string} namespace The full path to the namespace that contains the
* `name` member. This may contain dots, e.g. "storage.local".
* @param {string} name The name of the method or property.
@@ -681,16 +705,29 @@ class ChildAPIManager {
// Do not generate content script APIs, unless explicitly allowed.
if (this.context.envType === "content_child" &&
!allowedContexts.includes("content")) {
return false;
}
if (allowedContexts.includes("addon_parent_only")) {
return false;
}
+
+ // Do not generate devtools APIs, unless explicitly allowed.
+ if (this.context.envType === "devtools_child" &&
+ !allowedContexts.includes("devtools")) {
+ return false;
+ }
+
+ // Do not generate devtools APIs, unless explicitly allowed.
+ if (this.context.envType !== "devtools_child" &&
+ allowedContexts.includes("devtools_only")) {
+ return false;
+ }
+
return true;
}
getImplementation(namespace, name) {
let obj = namespace.split(".").reduce(
(object, prop) => object && object[prop],
this.localApis);
@@ -706,42 +743,42 @@ class ChildAPIManager {
return new ProxyAPIImplementation(namespace, name, this);
}
hasPermission(permission) {
return this.context.extension.hasPermission(permission);
}
}
-class ExtensionPageContextChild extends BaseContext {
+class ExtensionBaseContextChild extends BaseContext {
/**
- * This ExtensionPageContextChild represents a privileged addon
- * execution environment that has full access to the WebExtensions
- * APIs (provided that the correct permissions have been requested).
- *
- * This is the child side of the ExtensionPageContextParent class
- * defined in ExtensionParent.jsm.
+ * This ExtensionBaseContextChild represents an addon execution environment
+ * that is running in an addon or devtools child process.
*
* @param {BrowserExtensionContent} extension This context's owner.
* @param {object} params
+ * @param {string} params.envType One of "addon_child" or "devtools_child".
* @param {nsIDOMWindow} params.contentWindow The window where the addon runs.
- * @param {string} params.viewType One of "background", "popup" or "tab".
- * "background" and "tab" are used by `browser.extension.getViews`.
- * "popup" is only used internally to identify page action and browser
- * action popups and options_ui pages.
+ * @param {string} params.viewType One of "background", "popup", "tab",
+ * "devtools_page" or "devtools_panel".
* @param {number} [params.tabId] This tab's ID, used if viewType is "tab".
*/
constructor(extension, params) {
- super("addon_child", extension);
+ if (!params.envType) {
+ throw new Error("Missing envType");
+ }
+
if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) {
// This check is temporary. It should be removed once the proxy creation
// is asynchronous.
- throw new Error("ExtensionPageContextChild cannot be created in child processes");
+ throw new Error("ExtensionContext cannot be created in child processes");
}
+ super(params.envType, extension);
+
let {viewType, uri, contentWindow, tabId} = params;
this.viewType = viewType;
this.uri = uri || extension.baseURI;
this.setContentWindow(contentWindow);
// This is the MessageSender property passed to extension.
// It can be augmented by the "page-open" hook.
@@ -764,18 +801,16 @@ class ExtensionPageContextChild extends
Schemas.exportLazyGetter(contentWindow, "chrome", () => {
let chromeApiWrapper = Object.create(this.childManager);
chromeApiWrapper.isChromeCompat = true;
let chromeObj = Cu.createObjectIn(contentWindow);
Schemas.inject(chromeObj, chromeApiWrapper);
return chromeObj;
});
-
- this.extension.views.add(this);
}
get cloneScope() {
return this.contentWindow;
}
get principal() {
return this.contentWindow.document.nodePrincipal;
@@ -803,41 +838,113 @@ class ExtensionPageContextChild extends
return;
}
if (this.contentWindow) {
this.contentWindow.close();
}
super.unload();
- this.extension.views.delete(this);
}
}
-defineLazyGetter(ExtensionPageContextChild.prototype, "messenger", function() {
+defineLazyGetter(ExtensionBaseContextChild.prototype, "messenger", function() {
let filter = {extensionId: this.extension.id};
let optionalFilter = {};
// Addon-generated messages (not necessarily from the same process as the
// addon itself) are sent to the main process, which forwards them via the
// parent process message manager. Specific replies can be sent to the frame
// message manager.
return new Messenger(this, [Services.cpmm, this.messageManager], this.sender,
filter, optionalFilter);
});
+class ExtensionPageContextChild extends ExtensionBaseContextChild {
+ /**
+ * This ExtensionPageContextChild represents a privileged addon
+ * execution environment that has full access to the WebExtensions
+ * APIs (provided that the correct permissions have been requested).
+ *
+ * This is the child side of the ExtensionPageContextParent class
+ * defined in ExtensionParent.jsm.
+ *
+ * @param {BrowserExtensionContent} extension This context's owner.
+ * @param {object} params
+ * @param {nsIDOMWindow} params.contentWindow The window where the addon runs.
+ * @param {string} params.viewType One of "background", "popup" or "tab".
+ * "background" and "tab" are used by `browser.extension.getViews`.
+ * "popup" is only used internally to identify page action and browser
+ * action popups and options_ui pages.
+ * @param {number} [params.tabId] This tab's ID, used if viewType is "tab".
+ */
+ constructor(extension, params) {
+ super(extension, Object.assign(params, {envType: "addon_child"}));
+
+ this.extension.views.add(this);
+ }
+
+ unload() {
+ super.unload();
+ this.extension.views.delete(this);
+ }
+}
+
defineLazyGetter(ExtensionPageContextChild.prototype, "childManager", function() {
let localApis = {};
apiManager.generateAPIs(this, localApis);
+ let childManager = new ChildAPIManager(this, this.messageManager, localApis, {
+ envType: "addon_parent",
+ viewType: this.viewType,
+ url: this.uri.spec,
+ incognito: this.incognito,
+ });
+
+ this.callOnClose(childManager);
+
if (this.viewType == "background") {
apiManager.global.initializeBackgroundPage(this.contentWindow);
}
+ return childManager;
+});
+
+class DevtoolsContextChild extends ExtensionBaseContextChild {
+ /**
+ * This DevtoolsContextChild represents a devtools-related addon execution
+ * environment that has access to the devtools API namespace and to the same subset
+ * of APIs available in a content script execution environment.
+ *
+ * @param {BrowserExtensionContent} extension This context's owner.
+ * @param {object} params
+ * @param {nsIDOMWindow} params.contentWindow The window where the addon runs.
+ * @param {string} params.viewType One of "devtools_page" or "devtools_panel".
+ * @param {object} [params.devtoolsToolboxInfo] This devtools toolbox's information,
+ * used if viewType is "devtools_page" or "devtools_panel".
+ */
+ constructor(extension, params) {
+ super(extension, Object.assign(params, {envType: "devtools_child"}));
+
+ this.devtoolsToolboxInfo = params.devtoolsToolboxInfo;
+
+ this.extension.devtoolsViews.add(this);
+ }
+
+ unload() {
+ super.unload();
+ this.extension.devtoolsViews.delete(this);
+ }
+}
+
+defineLazyGetter(DevtoolsContextChild.prototype, "childManager", function() {
+ let localApis = {};
+ devtoolsAPIManager.generateAPIs(this, localApis);
+
let childManager = new ChildAPIManager(this, this.messageManager, localApis, {
- envType: "addon_parent",
+ envType: "devtools_parent",
viewType: this.viewType,
url: this.uri.spec,
incognito: this.incognito,
});
this.callOnClose(childManager);
return childManager;
@@ -886,16 +993,21 @@ class ContentGlobal {
receiveMessage({name, data}) {
switch (name) {
case "Extension:InitExtensionView":
// The view type is initialized once and then fixed.
this.global.removeMessageListener("Extension:InitExtensionView", this);
let {viewType, url} = data;
this.viewType = viewType;
+
+ if (data.devtoolsToolboxInfo) {
+ this.devtoolsToolboxInfo = data.devtoolsToolboxInfo;
+ }
+
this.global.addEventListener("DOMContentLoaded", this);
if (url) {
// TODO(robwu): Remove this check. It is only here because the popup
// implementation does not always load a URL at the initialization,
// and the logic is too complex to fix at once.
let {document} = this.global.content;
this.initialDocuments.add(document);
document.location.replace(url);
@@ -986,21 +1098,31 @@ ExtensionChild = {
return;
}
let mm = contentWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIContentFrameMessageManager);
- let {viewType, tabId} = this.contentGlobals.get(mm).ensureInitialized();
+ let {
+ viewType, tabId,
+ devtoolsToolboxInfo,
+ } = this.contentGlobals.get(mm).ensureInitialized();
let uri = contentWindow.document.documentURIObject;
- context = new ExtensionPageContextChild(extension, {viewType, contentWindow, uri, tabId});
+ if (devtoolsToolboxInfo) {
+ context = new DevtoolsContextChild(extension, {
+ viewType, contentWindow, uri, tabId, devtoolsToolboxInfo,
+ });
+ } else {
+ context = new ExtensionPageContextChild(extension, {viewType, contentWindow, uri, tabId});
+ }
+
this.extensionContexts.set(windowId, context);
},
/**
* Close the ExtensionPageContextChild belonging to the given window, if any.
*
* @param {number} windowId The inner window ID of the destroyed context.
*/
--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -537,27 +537,30 @@ class LocalAPIImplementation extends Sch
* ext-*.js scripts and the instantiator can communicate with each other.
*/
class SchemaAPIManager extends EventEmitter {
/**
* @param {string} processType
* "main" - The main, one and only chrome browser process.
* "addon" - An addon process.
* "content" - A content process.
+ * "devtools" - A devtools process.
*/
constructor(processType) {
super();
this.processType = processType;
this.global = this._createExtGlobal();
this._scriptScopes = [];
this._schemaApis = {
addon_parent: [],
addon_child: [],
content_parent: [],
content_child: [],
+ devtools_parent: [],
+ devtools_child: [],
};
}
/**
* Create a global object that is used as the shared global for all ext-*.js
* scripts that are loaded via `loadScript`.
*
* @returns {object} A sandbox that is used as the global by `loadScript`.
@@ -603,16 +606,18 @@ class SchemaAPIManager extends EventEmit
* Intended to match the namespace of the generated API, but not used at
* the moment - see bugzil.la/1295774.
* @param {string} envType Restricts the API to contexts that run in the
* given environment. Must be one of the following:
* - "addon_parent" - addon APIs that runs in the main process.
* - "addon_child" - addon APIs that runs in an addon process.
* - "content_parent" - content script APIs that runs in the main process.
* - "content_child" - content script APIs that runs in a content process.
+ * - "devtools_parent" - devtools APIs that runs in the main process.
+ * - "devtools_child" - devtools APIs that runs in a devtools process.
* @param {function(BaseContext)} getAPI A function that returns an object
* that will be merged with |chrome| and |browser|. The next example adds
* the create, update and remove methods to the tabs API.
*
* registerSchemaAPI("tabs", "addon_parent", (context) => ({
* tabs: { create, update },
* }));
* registerSchemaAPI("tabs", "addon_parent", (context) => ({
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -90,17 +90,18 @@ let apiManager = new class extends Schem
this.loadScript(value);
}
this.initialized = promise;
return this.initialized;
}
registerSchemaAPI(namespace, envType, getAPI) {
- if (envType == "addon_parent" || envType == "content_parent") {
+ if (envType == "addon_parent" || envType == "content_parent" ||
+ envType == "devtools_parent") {
super.registerSchemaAPI(namespace, envType, getAPI);
}
}
}();
// Subscribes to messages related to the extension messaging API and forwards it
// to the relevant message manager. The "sender" field for the `onMessage` and
// `onConnect` events are updated if needed.
@@ -316,16 +317,18 @@ class ContentScriptContextParent extends
* background script, a tab page, or a popup, running in
* ExtensionChild.jsm.
*/
class ExtensionPageContextParent extends ProxyContextParent {
constructor(envType, extension, params, xulBrowser) {
super(envType, extension, params, xulBrowser, extension.principal);
this.viewType = params.viewType;
+
+ extension.emit("extension-proxy-context-load", this);
}
// The window that contains this context. This may change due to moving tabs.
get xulWindow() {
return this.xulBrowser.ownerGlobal;
}
get windowId() {
@@ -420,17 +423,17 @@ ParentAPIManager = {
}
let extension = GlobalManager.getExtension(extensionId);
if (!extension) {
throw new Error(`No WebExtension found with ID ${extensionId}`);
}
let context;
- if (envType == "addon_parent") {
+ if (envType == "addon_parent" || envType == "devtools_parent") {
// Privileged addon contexts can only be loaded in documents whose main
// frame is also the same addon.
if (principal.URI.prePath !== extension.baseURI.prePath ||
!target.contentPrincipal.subsumes(principal)) {
throw new Error(`Refused to create privileged WebExtension context for ${principal.URI.spec}`);
}
context = new ExtensionPageContextParent(envType, extension, data, target);
} else if (envType == "content_parent") {