--- a/.eslintignore
+++ b/.eslintignore
@@ -111,16 +111,17 @@ devtools/client/webide/**
!devtools/client/webide/components/webideCli.js
devtools/server/**
!devtools/server/child.js
!devtools/server/css-logic.js
!devtools/server/main.js
!devtools/server/actors/inspector.js
!devtools/server/actors/highlighters/eye-dropper.js
!devtools/server/actors/webbrowser.js
+!devtools/server/actors/webextension.js
!devtools/server/actors/styles.js
!devtools/server/actors/string.js
!devtools/server/actors/csscoverage.js
devtools/shared/*.js
!devtools/shared/css-lexer.js
!devtools/shared/defer.js
!devtools/shared/event-emitter.js
!devtools/shared/task.js
--- a/devtools/client/framework/attach-thread.js
+++ b/devtools/client/framework/attach-thread.js
@@ -84,26 +84,26 @@ function attachThread(toolbox) {
box.PRIORITY_WARNING_HIGH
);
}
deferred.resolve(threadClient);
});
};
- if (target.isAddon) {
- // Attaching an addon
+ if (target.isTabActor) {
+ // Attaching a tab, a browser process, or a WebExtensions add-on.
+ target.activeTab.attachThread(threadOptions, handleResponse);
+ } else if (target.isAddon) {
+ // Attaching a legacy addon.
target.client.attachAddon(actor, res => {
target.client.attachThread(res.threadActor, handleResponse);
});
- } else if (target.isTabActor) {
- // Attaching a normal thread
- target.activeTab.attachThread(threadOptions, handleResponse);
- } else {
- // Attaching the browser debugger
+ } else {
+ // Attaching an old browser debugger or a content process.
target.client.attachThread(chromeDebugger, handleResponse);
}
return deferred.promise;
}
function detachThread(threadClient) {
threadClient.removeListener("paused");
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -343,18 +343,25 @@ TabTarget.prototype = {
return this._url;
},
get isRemote() {
return !this.isLocalTab;
},
get isAddon() {
+ return !!(this._form && this._form.actor && (
+ this._form.actor.match(/conn\d+\.addon\d+/) ||
+ this._form.actor.match(/conn\d+\.webExtension\d+/)
+ ));
+ },
+
+ get isWebExtension() {
return !!(this._form && this._form.actor &&
- this._form.actor.match(/conn\d+\.addon\d+/));
+ this._form.actor.match(/conn\d+\.webExtension\d+/));
},
get isLocalTab() {
return !!this._tab;
},
get isMultiProcess() {
return !this.window;
--- a/devtools/client/framework/toolbox-process-window.js
+++ b/devtools/client/framework/toolbox-process-window.js
@@ -39,17 +39,21 @@ var connect = Task.async(function*() {
});
gClient = new DebuggerClient(transport);
gClient.connect().then(() => {
let addonID = getParameterByName("addonID");
if (addonID) {
gClient.listAddons(({addons}) => {
let addonActor = addons.filter(addon => addon.id === addonID).pop();
- openToolbox({ form: addonActor, chrome: true, isTabActor: false });
+ openToolbox({
+ form: addonActor,
+ chrome: true,
+ isTabActor: addonActor.isWebExtension ? true : false
+ });
});
} else {
gClient.getProcess().then(aResponse => {
openToolbox({ form: aResponse.form, chrome: true });
});
}
});
});
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -923,17 +923,17 @@ Toolbox.prototype = {
Services.focus.moveFocus(win, elm, type, 0);
});
},
/**
* Add buttons to the UI as specified in the devtools.toolbox.toolbarSpec pref
*/
_buildButtons: function () {
- if (!this.target.isAddon) {
+ if (!this.target.isAddon || this.target.isWebExtension) {
this._buildPickerButton();
}
this.setToolboxButtonsVisibility();
// Old servers don't have a GCLI Actor, so just return
if (!this.target.hasActor("gcli")) {
return promise.resolve();
--- a/devtools/server/actors/addon.js
+++ b/devtools/server/actors/addon.js
@@ -129,16 +129,19 @@ BrowserAddonActor.prototype = {
onUninstalled: function BAA_onUninstalled(aAddon) {
if (aAddon != this._addon) {
return;
}
if (this.attached) {
this.onDetach();
+
+ // The BrowserAddonActor is not a TabActor and it has to send
+ // "tabDetached" directly to close the devtools toolbox window.
this.conn.send({ from: this.actorID, type: "tabDetached" });
}
this.disconnect();
},
onAttach: function BAA_onAttach() {
if (this.exited) {
--- a/devtools/server/actors/moz.build
+++ b/devtools/server/actors/moz.build
@@ -57,11 +57,12 @@ DevToolsModules(
'styleeditor.js',
'styles.js',
'stylesheets.js',
'timeline.js',
'webapps.js',
'webaudio.js',
'webbrowser.js',
'webconsole.js',
+ 'webextension.js',
'webgl.js',
'worker.js',
)
--- a/devtools/server/actors/webbrowser.js
+++ b/devtools/server/actors/webbrowser.js
@@ -18,16 +18,17 @@ var DevToolsUtils = require("devtools/sh
var { assert } = DevToolsUtils;
var { TabSources } = require("./utils/TabSources");
var makeDebugger = require("./utils/make-debugger");
loader.lazyRequireGetter(this, "RootActor", "devtools/server/actors/root", true);
loader.lazyRequireGetter(this, "ThreadActor", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "BrowserAddonActor", "devtools/server/actors/addon", true);
+loader.lazyRequireGetter(this, "WebExtensionActor", "devtools/server/actors/webextension", true);
loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker", true);
loader.lazyRequireGetter(this, "ServiceWorkerRegistrationActorList", "devtools/server/actors/worker", true);
loader.lazyRequireGetter(this, "ProcessActorList", "devtools/server/actors/process", true);
loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
loader.lazyImporter(this, "ExtensionContent", "resource://gre/modules/ExtensionContent.jsm");
// Assumptions on events module:
// events needs to be dispatched synchronously,
@@ -884,16 +885,24 @@ function TabActor(connection) {
}
// XXX (bug 710213): TabActor attach/detach/exit/disconnect is a
// *complete* mess, needs to be rethought asap.
TabActor.prototype = {
traits: null,
+ // Optional console API listener options (e.g. used by the WebExtensionActor to
+ // filter console messages by addonID), set to an empty (no options) object by default.
+ consoleAPIListenerOptions: {},
+
+ // Optional TabSources filter function (e.g. used by the WebExtensionActor to filter
+ // sources by addonID), allow all sources by default.
+ _allowSource() { return true; },
+
get exited() {
return this._exited;
},
get attached() {
return !!this._attached;
},
@@ -1054,17 +1063,17 @@ TabActor.prototype = {
}
// Abrupt closing of the browser window may leave callbacks without a
// currentURI.
return null;
},
get sources() {
if (!this._sources) {
- this._sources = new TabSources(this.threadActor);
+ this._sources = new TabSources(this.threadActor, this._allowSource);
}
return this._sources;
},
/**
* This is called by BrowserTabList.getList for existing tab actors prior to
* calling |form| below. It can be used to do any async work that may be
* needed to assemble the form.
@@ -1359,27 +1368,38 @@ TabActor.prototype = {
// Ignore the parent of the original document on non-e10s firefox,
// as we get the xul window as parent and don't care about it.
if (window.parent && window != this._originalWindow) {
parentID = window.parent
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
}
+
+ // Collect the addonID from the document origin attributes.
+ let addonID = window.document.nodePrincipal.originAttributes.addonId;
+
return {
- id: id,
+ id,
+ parentID,
+ addonID,
url: window.location.href,
title: window.document.title,
- parentID: parentID
};
});
},
_notifyDocShellsUpdate(docshells) {
let windows = this._docShellsToWindows(docshells);
+
+ // Do not send the `frameUpdate` event if the windows array is empty.
+ if (windows.length == 0) {
+ return;
+ }
+
this.conn.send({ from: this.actorID,
type: "frameUpdate",
frames: windows
});
},
_updateChildDocShells() {
this._notifyDocShellsUpdate(this.docShells);
@@ -2262,17 +2282,22 @@ function BrowserAddonList(connection) {
}
BrowserAddonList.prototype.getList = function () {
let deferred = promise.defer();
AddonManager.getAllAddons((addons) => {
for (let addon of addons) {
let actor = this._actorByAddonId.get(addon.id);
if (!actor) {
- actor = new BrowserAddonActor(this._connection, addon);
+ if (addon.isWebExtension) {
+ actor = new WebExtensionActor(this._connection, addon);
+ } else {
+ actor = new BrowserAddonActor(this._connection, addon);
+ }
+
this._actorByAddonId.set(addon.id, actor);
}
}
deferred.resolve([...this._actorByAddonId].map(([_, actor]) => actor));
});
return deferred.promise;
};
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -589,18 +589,21 @@ WebConsoleActor.prototype =
this.consoleServiceListener =
new ConsoleServiceListener(window, this);
this.consoleServiceListener.init();
}
startedListeners.push(listener);
break;
case "ConsoleAPI":
if (!this.consoleAPIListener) {
+ // Create the consoleAPIListener (and apply the filtering options defined
+ // in the parent actor).
this.consoleAPIListener =
- new ConsoleAPIListener(window, this);
+ new ConsoleAPIListener(window, this,
+ this.parentActor.consoleAPIListenerOptions);
this.consoleAPIListener.init();
}
startedListeners.push(listener);
break;
case "NetworkActivity":
if (!this.networkMonitor) {
// Create a StackTraceCollector that's going to be shared both by the
// NetworkMonitorChild (getting messages about requests from parent) and
copy from devtools/server/actors/chrome.js
copy to devtools/server/actors/webextension.js
--- a/devtools/server/actors/chrome.js
+++ b/devtools/server/actors/webextension.js
@@ -1,185 +1,333 @@
/* 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 { Ci } = require("chrome");
+const { Ci, Cu } = require("chrome");
const Services = require("Services");
-const { DebuggerServer } = require("../main");
-const { getChildDocShells, TabActor } = require("./webbrowser");
+const { ChromeActor } = require("./chrome");
const makeDebugger = require("./utils/make-debugger");
+var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var { assert } = DevToolsUtils;
+
+loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
+loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
+
+loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
+loader.lazyImporter(this, "XPIProvider", "resource://gre/modules/addons/XPIProvider.jsm");
+
+const FALLBACK_DOC_MESSAGE = "Your addon does not have any document opened yet.";
+
/**
- * Creates a TabActor for debugging all the chrome content in the
- * current process. Most of the implementation is inherited from TabActor.
- * ChromeActor is a child of RootActor, it can be instanciated via
- * RootActor.getProcess request.
- * ChromeActor exposes all tab actors via its form() request, like TabActor.
+ * Creates a TabActor for debugging all the contexts associated to a target WebExtensions
+ * add-on.
+ * Most of the implementation is inherited from ChromeActor (which inherits most of its
+ * implementation from TabActor).
+ * WebExtensionActor is a child of RootActor, it can be retrieved via
+ * RootActor.listAddons request.
+ * WebExtensionActor exposes all tab actors via its form() request, like TabActor.
*
* History lecture:
- * All tab actors used to also be registered as global actors,
- * so that the root actor was also exposing tab actors for the main process.
- * Tab actors ended up having RootActor as parent actor,
- * but more and more features of the tab actors were relying on TabActor.
- * So we are now exposing a process actor that offers the same API as TabActor
- * by inheriting its functionality.
- * Global actors are now only the actors that are meant to be global,
- * and are no longer related to any specific scope/document.
+ * The add-on actors used to not inherit TabActor because of the different way the
+ * add-on APIs where exposed to the add-on itself, and for this reason the Addon Debugger
+ * has only a sub-set of the feature available in the Tab or in the Browser Toolbox.
+ * In a WebExtensions add-on all the provided contexts (background and popup pages etc.),
+ * besides the Content Scripts which run in the content process, hooked to an existent
+ * tab, by creating a new WebExtensionActor which inherits from ChromeActor, we can
+ * provide a full features Addon Toolbox (which is basically like a BrowserToolbox which
+ * filters the visible sources and frames to the one that are related to the target
+ * add-on).
*
- * @param aConnection DebuggerServerConnection
+ * @param conn DebuggerServerConnection
* The connection to the client.
+ * @param addon AddonWrapper
+ * The target addon.
*/
-function ChromeActor(aConnection) {
- TabActor.call(this, aConnection);
+function WebExtensionActor(conn, addon) {
+ ChromeActor.call(this, conn);
+
+ this.id = addon.id;
+ this.addon = addon;
+
+ // Bind the _allowSource helper to this, it is used in the
+ // TabActor to lazily create the TabSources instance.
+ this._allowSource = this._allowSource.bind(this);
- // This creates a Debugger instance for chrome debugging all globals.
+ // Set the consoleAPIListener filtering options
+ // (retrieved and used in the related webconsole child actor).
+ this.consoleAPIListenerOptions = {
+ addonId: addon.id,
+ };
+
+ // This creates a Debugger instance for debugging all the add-on globals.
this.makeDebugger = makeDebugger.bind(null, {
- findDebuggees: dbg => dbg.findAllGlobals(),
- shouldAddNewGlobalAsDebuggee: () => true
+ findDebuggees: dbg => {
+ return dbg.findAllGlobals().filter(this._shouldAddNewGlobalAsDebuggee);
+ },
+ shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee.bind(this),
});
- // Ensure catching the creation of any new content docshell
- this.listenForNewDocShells = true;
+ // Discover the preferred debug global for the target addon
+ this.preferredTargetWindow = null;
+ this._findAddonPreferredTargetWindow();
+
+ AddonManager.addAddonListener(this);
+}
+exports.WebExtensionActor = WebExtensionActor;
+
+WebExtensionActor.prototype = Object.create(ChromeActor.prototype);
+
+WebExtensionActor.prototype.actorPrefix = "webExtension";
+WebExtensionActor.prototype.constructor = WebExtensionActor;
+
+// NOTE: This is needed to catch in the webextension webconsole all the
+// errors raised by the WebExtension internals that are not currently
+// associated with any window.
+WebExtensionActor.prototype.isRootActor = true;
+
+WebExtensionActor.prototype.form = function () {
+ assert(this.actorID, "addon should have an actorID.");
+
+ let baseForm = ChromeActor.prototype.form.call(this);
- // Defines the default docshell selected for the tab actor
- let window = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
+ return Object.assign(baseForm, {
+ actor: this.actorID,
+ id: this.id,
+ name: this.addon.name,
+ url: this.addon.sourceURI ? this.addon.sourceURI.spec : undefined,
+ iconURL: this.addon.iconURL,
+ debuggable: this.addon.isDebuggable,
+ temporarilyInstalled: this.addon.temporarilyInstalled,
+ isWebExtension: this.addon.isWebExtension,
+ });
+};
- // Default to any available top level window if there is no expected window
- // (for example when we open firefox with -webide argument)
- if (!window) {
- window = Services.wm.getMostRecentWindow(null);
+WebExtensionActor.prototype._attach = function () {
+ // NOTE: we need to be sure that `this.window` can return a
+ // window before calling the ChromeActor.onAttach, or the TabActor
+ // will not be subscribed to the child doc shell updates.
+
+ // If a preferredTargetWindow exists, set it as the target for this actor
+ // when the client request to attach this actor.
+ if (this.preferredTargetWindow) {
+ this._setWindow(this.preferredTargetWindow);
+ } else {
+ this._createFallbackWindow();
}
- // On xpcshell, there is no window/docshell
- let docShell = window ? window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDocShell)
- : null;
- Object.defineProperty(this, "docShell", {
- value: docShell,
- configurable: true
- });
-}
-exports.ChromeActor = ChromeActor;
+
+ // Call ChromeActor's _attach to listen for any new/destroyed chrome docshell
+ ChromeActor.prototype._attach.apply(this);
+};
-ChromeActor.prototype = Object.create(TabActor.prototype);
+WebExtensionActor.prototype._detach = function () {
+ this._destroyFallbackWindow();
-ChromeActor.prototype.constructor = ChromeActor;
-
-ChromeActor.prototype.isRootActor = true;
+ // Call ChromeActor's _detach to unsubscribe new/destroyed chrome docshell listeners.
+ ChromeActor.prototype._detach.apply(this);
+};
/**
- * Getter for the list of all docshells in this tabActor
- * @return {Array}
+ * Called when the actor is removed from the connection.
+ */
+WebExtensionActor.prototype.exit = function () {
+ AddonManager.removeAddonListener(this);
+
+ this.preferredTargetWindow = null;
+ this.addon = null;
+ this.id = null;
+
+ return ChromeActor.prototype.exit.apply(this);
+};
+
+// Addon Specific Remote Debugging requestTypes and methods.
+
+/**
+ * Reloads the addon.
*/
-Object.defineProperty(ChromeActor.prototype, "docShells", {
- get: function () {
- // Iterate over all top-level windows and all their docshells.
- let docShells = [];
- let e = Services.ww.getWindowEnumerator();
- while (e.hasMoreElements()) {
- let window = e.getNext();
- let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell);
- docShells = docShells.concat(getChildDocShells(docShell));
- }
+WebExtensionActor.prototype.onReload = function () {
+ return this.addon.reload()
+ .then(() => {
+ // send an empty response
+ return {};
+ });
+};
- return docShells;
- }
-});
-
-ChromeActor.prototype.observe = function (aSubject, aTopic, aData) {
- TabActor.prototype.observe.call(this, aSubject, aTopic, aData);
- if (!this.attached) {
- return;
- }
- if (aTopic == "chrome-webnavigation-create") {
- aSubject.QueryInterface(Ci.nsIDocShell);
- this._onDocShellCreated(aSubject);
- } else if (aTopic == "chrome-webnavigation-destroy") {
- this._onDocShellDestroy(aSubject);
+/**
+ * Set the preferred global for the add-on (called from the AddonManager).
+ */
+WebExtensionActor.prototype.setOptions = function (addonOptions) {
+ if ("global" in addonOptions) {
+ // Set the proposed debug global as the preferred target window
+ // (the actor will eventually set it as the target once it is attached)
+ this.preferredTargetWindow = addonOptions.global;
}
};
-ChromeActor.prototype._attach = function () {
- if (this.attached) {
- return false;
+// AddonManagerListener callbacks.
+
+WebExtensionActor.prototype.onInstalled = function (addon) {
+ if (addon.id != this.id) {
+ return;
}
- TabActor.prototype._attach.call(this);
+ // Update the AddonManager's addon object on reload/update.
+ this.addon = addon;
+};
- // Listen for any new/destroyed chrome docshell
- Services.obs.addObserver(this, "chrome-webnavigation-create", false);
- Services.obs.addObserver(this, "chrome-webnavigation-destroy", false);
+WebExtensionActor.prototype.onUninstalled = function (addon) {
+ if (addon != this.addon) {
+ return;
+ }
- // Iterate over all top-level windows.
- let docShells = [];
- let e = Services.ww.getWindowEnumerator();
- while (e.hasMoreElements()) {
- let window = e.getNext();
- let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell);
- if (docShell == this.docShell) {
- continue;
- }
- this._progressListener.watch(docShell);
+ this.exit();
+};
+
+WebExtensionActor.prototype.onPropertyChanged = function (addon, changedPropNames) {
+ if (addon != this.addon) {
+ return;
+ }
+
+ // Refresh the preferred debug global on disabled/reloaded/upgraded addon.
+ if (changedPropNames.includes("debugGlobal")) {
+ this._findAddonPreferredTargetWindow();
}
};
-ChromeActor.prototype._detach = function () {
- if (!this.attached) {
- return false;
+// Private helpers
+
+WebExtensionActor.prototype._createFallbackWindow = function () {
+ if (this.fallbackWindow) {
+ // Skip if there is already an existent fallback window.
+ return;
}
- Services.obs.removeObserver(this, "chrome-webnavigation-create");
- Services.obs.removeObserver(this, "chrome-webnavigation-destroy");
+ // Create an empty hidden window as a fallback (e.g. the background page could be
+ // not defined for the target add-on or not yet when the actor instance has been
+ // created).
+ this.fallbackWebNav = Services.appShell.createWindowlessBrowser(true);
+ this.fallbackWebNav.loadURI(
+ `data:text/html;charset=utf-8,${FALLBACK_DOC_MESSAGE}`,
+ 0, null, null, null
+ );
- // Iterate over all top-level windows.
- let docShells = [];
- let e = Services.ww.getWindowEnumerator();
- while (e.hasMoreElements()) {
- let window = e.getNext();
- let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell);
- if (docShell == this.docShell) {
- continue;
- }
- this._progressListener.unwatch(docShell);
- }
+ this.fallbackDocShell = this.fallbackWebNav
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell);
- TabActor.prototype._detach.call(this);
+ Object.defineProperty(this, "docShell", {
+ value: this.fallbackDocShell,
+ configurable: true
+ });
+
+ // Save the reference to the fallback DOMWindow
+ this.fallbackWindow = this.fallbackDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
};
-/* ThreadActor hooks. */
+WebExtensionActor.prototype._destroyFallbackWindow = function () {
+ if (this.fallbackWebNav) {
+ // Explicitly close the fallback windowless browser to prevent it to leak
+ // (and to prevent it to freeze devtools xpcshell tests).
+ this.fallbackWebNav.loadURI("about:blank", 0, null, null, null);
+ this.fallbackWebNav.close();
-/**
- * Prepare to enter a nested event loop by disabling debuggee events.
- */
-ChromeActor.prototype.preNest = function () {
- // Disable events in all open windows.
- let e = Services.wm.getEnumerator(null);
- while (e.hasMoreElements()) {
- let win = e.getNext();
- let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- windowUtils.suppressEventHandling(true);
- windowUtils.suspendTimeouts();
+ this.fallbackWebNav = null;
+ this.fallbackWindow = null;
}
};
/**
- * Prepare to exit a nested event loop by enabling debuggee events.
+ * Discover the preferred debug global and switch to it if the addon has been attached.
*/
-ChromeActor.prototype.postNest = function (aNestData) {
- // Enable events in all open windows.
- let e = Services.wm.getEnumerator(null);
- while (e.hasMoreElements()) {
- let win = e.getNext();
- let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- windowUtils.resumeTimeouts();
- windowUtils.suppressEventHandling(false);
+WebExtensionActor.prototype._findAddonPreferredTargetWindow = function () {
+ return new Promise(resolve => {
+ let activeAddon = XPIProvider.activeAddons.get(this.id);
+
+ if (!activeAddon) {
+ // The addon is not active, the background page is going to be destroyed,
+ // navigate to the fallback window (if it already exists).
+ resolve(null);
+ } else {
+ AddonManager.getAddonByInstanceID(activeAddon.instanceID)
+ .then(privateWrapper => {
+ let targetWindow = privateWrapper.getDebugGlobal();
+
+ // Do not use the preferred global if it is not a DOMWindow as expected.
+ if (!(targetWindow instanceof Ci.nsIDOMWindow)) {
+ targetWindow = null;
+ }
+
+ resolve(targetWindow);
+ });
+ }
+ }).then(preferredTargetWindow => {
+ this.preferredTargetWindow = preferredTargetWindow;
+
+ if (!preferredTargetWindow) {
+ // Create a fallback window if no preferred target window has been found.
+ this._createFallbackWindow();
+ } else if (this.attached) {
+ // Change the top level document if the actor is already attached.
+ this._changeTopLevelDocument(preferredTargetWindow);
+ }
+ });
+};
+
+/**
+ * Return an array of the json details related to an array/iterator of docShells.
+ */
+WebExtensionActor.prototype._docShellsToWindows = function (docshells) {
+ return ChromeActor.prototype._docShellsToWindows.call(this, docshells)
+ .filter(windowDetails => {
+ // filter the docShells based on the addon id
+ return windowDetails.addonID == this.id;
+ });
+};
+
+/**
+ * Return true if the given source is associated with this addon and should be
+ * added to the visible sources (retrieved and used by the webbrowser actor module).
+ */
+WebExtensionActor.prototype._allowSource = function (source) {
+ try {
+ let uri = Services.io.newURI(source.url, null, null);
+ let addonID = mapURIToAddonID(uri);
+
+ return addonID == this.id;
+ } catch (e) {
+ return false;
}
};
+
+/**
+ * Return true if the given global is associated with this addon and should be
+ * added as a debuggee, false otherwise.
+ */
+WebExtensionActor.prototype._shouldAddNewGlobalAsDebuggee = function (newGlobal) {
+ const global = unwrapDebuggerObjectGlobal(newGlobal);
+
+ if (global instanceof Ci.nsIDOMWindow) {
+ return global.document.nodePrincipal.originAttributes.addonId == this.id;
+ }
+
+ try {
+ // This will fail for non-Sandbox objects, hence the try-catch block.
+ let metadata = Cu.getSandboxMetadata(global);
+ if (metadata) {
+ return metadata.addonID === this.id;
+ }
+ } catch (e) {
+ // Unable to retrieve the sandbox metadata.
+ }
+
+ return false;
+};
+
+/**
+ * Override WebExtensionActor requestTypes:
+ * - redefined `reload`, which should reload the target addon
+ * (instead of the entire browser as the regular ChromeActor does).
+ */
+WebExtensionActor.prototype.requestTypes.reload = WebExtensionActor.prototype.onReload;