--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -352,18 +352,18 @@ TabTarget.prototype = {
get isAddon() {
const isLegacyAddon = !!(this._form && this._form.actor &&
this._form.actor.match(/conn\d+\.addon(Target)?\d+/));
return isLegacyAddon || this.isWebExtension;
},
get isWebExtension() {
return !!(this._form && this._form.actor && (
- this._form.actor.match(/conn\d+\.webExtension\d+/) ||
- this._form.actor.match(/child\d+\/webExtension\d+/)
+ this._form.actor.match(/conn\d+\.webExtension(Target)?\d+/) ||
+ this._form.actor.match(/child\d+\/webExtension(Target)?\d+/)
));
},
get isLocalTab() {
return !!this._tab;
},
get isMultiProcess() {
@@ -422,17 +422,17 @@ TabTarget.prototype = {
} else if (this._form.isWebExtension &&
this.client.mainRoot.traits.webExtensionAddonConnect) {
// The addonTargetActor form is related to a WebExtensionParentActor instance,
// which isn't a target actor on its own, it is an actor living in the parent
// process with access to the addon metadata, it can control the addon (e.g.
// reloading it) and listen to the AddonManager events related to the lifecycle of
// the addon (e.g. when the addon is disabled or uninstalled).
// To retrieve the target actor instance, we call its "connect" method, (which
- // fetches the target actor form from a WebExtensionChildActor instance).
+ // fetches the target actor form from a WebExtensionTargetActor instance).
const {form} = await this._client.request({
to: this._form.actor, type: "connect",
});
this._form = form;
this._url = form.url;
this._title = form.title;
}
--- a/devtools/docs/backend/actor-hierarchy.md
+++ b/devtools/docs/backend/actor-hierarchy.md
@@ -81,27 +81,32 @@ RootActor (root.js)
| be used to reach any window in the parent process.
| Extends the abstract class BrowsingContextTargetActor.
| Returned by "getWindow" request to the root actor.
|
|-- ParentProcessTargetActor (parent-process.js)
| Targets all resources in the parent process of Firefox (chrome documents,
| JSMs, JS XPCOM, etc.).
| Extends the abstract class BrowsingContextTargetActor.
- | Extended by WebExtensionChildActor.
+ | Extended by WebExtensionTargetActor.
| Returned by "getProcess" request without any argument.
|
|-- ContentProcessTargetActor (content-process.js)
| Targets all resources in a content process of Firefox (chrome sandboxes,
| frame scripts, documents, etc.)
| Returned by "getProcess" request with a id argument, matching the
| targeted process.
|
- \-- AddonTargetActor (addon.js)
- Targets a legacy (non-WebExtension) add-on.
+ |-- AddonTargetActor (addon.js)
+ | Targets a legacy (non-WebExtension) add-on.
+ | Returned by "listAddons" request.
+ |
+ \-- WebExtensionTargetActor (webextension.js)
+ Targets a WebExtension add-on.
+ Extends ParentProcessTargetActor.
Returned by "listAddons" request.
```
## Target Actors
Those are the actors exposed by the root actors which are meant to track the
lifetime of a given target: tab, process, add-on, or worker. It also allows to
fetch the tab-scoped actors connected to this target, which are actors like
--- a/devtools/server/actors/addon/moz.build
+++ b/devtools/server/actors/addon/moz.build
@@ -4,10 +4,9 @@
# 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(
'addons.js',
'console.js',
'webextension-inspected-window.js',
'webextension-parent.js',
- 'webextension.js',
)
--- a/devtools/server/actors/addon/webextension-parent.js
+++ b/devtools/server/actors/addon/webextension-parent.js
@@ -8,17 +8,17 @@ const {DebuggerServer} = require("devtoo
const protocol = require("devtools/shared/protocol");
const {webExtensionSpec} = require("devtools/shared/specs/addon/webextension-parent");
loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
loader.lazyImporter(this, "ExtensionParent", "resource://gre/modules/ExtensionParent.jsm");
/**
* Creates the actor that represents the addon in the parent process, which connects
- * itself to a WebExtensionChildActor counterpart which is created in the
+ * itself to a WebExtensionTargetActor counterpart which is created in the
* extension process (or in the main process if the WebExtensions OOP mode is disabled).
*
* The WebExtensionParentActor subscribes itself as an AddonListener on the AddonManager
* and forwards this events to child actor (e.g. on addon reload or when the addon is
* uninstalled completely) and connects to the child extension process using a `browser`
* element provided by the extension internals (it is not related to any single extension,
* but it will be created automatically to the currently selected "WebExtensions OOP mode"
* and it persist across the extension reloads (it is destroyed once the actor exits).
--- a/devtools/server/actors/targets/moz.build
+++ b/devtools/server/actors/targets/moz.build
@@ -7,10 +7,11 @@
DevToolsModules(
'addon.js',
'browsing-context.js',
'chrome-window.js',
'content-process.js',
'frame-proxy.js',
'frame.js',
'parent-process.js',
+ 'webextension.js',
'worker.js',
)
--- a/devtools/server/actors/targets/parent-process.js
+++ b/devtools/server/actors/targets/parent-process.js
@@ -3,17 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/*
* Target actor for the entire parent process.
*
* This actor extends BrowsingContextTargetActor.
- * This actor is extended by WebExtensionChildActor.
+ * This actor is extended by WebExtensionTargetActor.
*
* See devtools/docs/backend/actor-hierarchy.md for more details.
*/
const { Ci } = require("chrome");
const Services = require("Services");
const { DebuggerServer } = require("devtools/server/main");
const {
rename from devtools/server/actors/addon/webextension.js
rename to devtools/server/actors/targets/webextension.js
--- a/devtools/server/actors/addon/webextension.js
+++ b/devtools/server/actors/targets/webextension.js
@@ -1,43 +1,58 @@
/* 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";
+/*
+ * Target actor for a WebExtension add-on.
+ *
+ * This actor extends ParentProcessTargetActor.
+ *
+ * See devtools/docs/backend/actor-hierarchy.md for more details.
+ */
+
const { extend } = require("devtools/shared/extend");
const { Ci, Cu, Cc } = require("chrome");
const Services = require("Services");
const {
ParentProcessTargetActor,
parentProcessTargetPrototype,
} = require("devtools/server/actors/targets/parent-process");
-const makeDebugger = require("./utils/make-debugger");
+const makeDebugger = require("devtools/server/actors/utils/make-debugger");
const { ActorClassWithSpec } = require("devtools/shared/protocol");
-const { browsingContextTargetSpec } = require("devtools/shared/specs/targets/browsing-context");
+const { webExtensionTargetSpec } = require("devtools/shared/specs/targets/webextension");
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/thread", true);
loader.lazyRequireGetter(this, "ChromeUtils");
const FALLBACK_DOC_MESSAGE = "Your addon does not have any document opened yet.";
/**
+ * Protocol.js expects only the prototype object, and does not maintain the prototype
+ * chain when it constructs the ActorClass. For this reason we are using `extend` to
+ * maintain the properties of BrowsingContextTargetActor.prototype
+ */
+const webExtensionTargetPrototype = extend({}, parentProcessTargetPrototype);
+
+/**
* Creates a target actor for debugging all the contexts associated to a target
* WebExtensions add-on running in a child extension process. Most of the implementation
* is inherited from ParentProcessTargetActor (which inherits most of its implementation
* from BrowsingContextTargetActor).
*
- * WebExtensionChildActor is created by a WebExtensionParentActor counterpart, when its
+ * WebExtensionTargetActor is created by a WebExtensionParentActor counterpart, when its
* parent actor's `connect` method has been called (on the listAddons RDP package),
* it runs in the same process that the extension is running into (which can be the main
* process if the extension is running in non-oop mode, or the child extension process
* if the extension is running in oop-mode).
*
- * A WebExtensionChildActor contains all tab actors, like a regular
+ * A WebExtensionTargetActor contains all tab actors, like a regular
* ParentProcessTargetActor or BrowsingContextTargetActor.
*
* History lecture:
* - The add-on actors used to not inherit BrowsingContextTargetActor 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, popups etc.),
@@ -54,20 +69,17 @@ const FALLBACK_DOC_MESSAGE = "Your addon
* @param {nsIMessageSender} chromeGlobal.
* The chromeGlobal where this actor has been injected by the
* DebuggerServer.connectToFrame method.
* @param {string} prefix
* the custom RDP prefix to use.
* @param {string} addonId
* the addonId of the target WebExtension.
*/
-
-const webExtensionChildPrototype = extend({}, parentProcessTargetPrototype);
-
-webExtensionChildPrototype.initialize = function(conn, chromeGlobal, prefix, addonId) {
+webExtensionTargetPrototype.initialize = function(conn, chromeGlobal, prefix, addonId) {
parentProcessTargetPrototype.initialize.call(this, conn);
this._chromeGlobal = chromeGlobal;
this._prefix = prefix;
this.id = addonId;
// Redefine the messageManager getter to return the chromeGlobal
// as the messageManager for this actor (which is the browser XUL
// element used by the parent actor running in the main process to
@@ -108,27 +120,25 @@ webExtensionChildPrototype.initialize =
// URL shown in the window tittle when the addon debugger is opened).
const extensionWindow = this._searchForExtensionWindow();
if (extensionWindow) {
this._setWindow(extensionWindow);
}
};
-webExtensionChildPrototype.typeName = "webExtension";
-
// 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.
-webExtensionChildPrototype.isRootActor = true;
+webExtensionTargetPrototype.isRootActor = true;
/**
* Called when the actor is removed from the connection.
*/
-webExtensionChildPrototype.exit = function() {
+webExtensionTargetPrototype.exit = function() {
if (this._chromeGlobal) {
const chromeGlobal = this._chromeGlobal;
this._chromeGlobal = null;
chromeGlobal.removeMessageListener("debug:webext_parent_exit", this._onParentExit);
chromeGlobal.sendAsyncMessage("debug:webext_child_exit", {
actor: this.actorID
@@ -138,17 +148,17 @@ webExtensionChildPrototype.exit = functi
this.addon = null;
this.id = null;
return ParentProcessTargetActor.prototype.exit.apply(this);
};
// Private helpers.
-webExtensionChildPrototype._createFallbackWindow = function() {
+webExtensionTargetPrototype._createFallbackWindow = function() {
if (this.fallbackWindow) {
// Skip if there is already an existent fallback window.
return;
}
// 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).
@@ -157,48 +167,48 @@ webExtensionChildPrototype._createFallba
// Save the reference to the fallback DOMWindow.
this.fallbackWindow = this.fallbackWebNav.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
// Insert the fallback doc message.
this.fallbackWindow.document.body.innerText = FALLBACK_DOC_MESSAGE;
};
-webExtensionChildPrototype._destroyFallbackWindow = function() {
+webExtensionTargetPrototype._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();
this.fallbackWebNav = null;
this.fallbackWindow = null;
}
};
// Discovery an extension page to use as a default target window.
// NOTE: This currently fail to discovery an extension page running in a
// windowless browser when running in non-oop mode, and the background page
// is set later using _onNewExtensionWindow.
-webExtensionChildPrototype._searchForExtensionWindow = function() {
+webExtensionTargetPrototype._searchForExtensionWindow = function() {
const e = Services.ww.getWindowEnumerator(null);
while (e.hasMoreElements()) {
const window = e.getNext();
if (window.document.nodePrincipal.addonId == this.id) {
return window;
}
}
return undefined;
};
// Customized ParentProcessTargetActor/BrowsingContextTargetActor hooks.
-webExtensionChildPrototype._onDocShellDestroy = function(docShell) {
+webExtensionTargetPrototype._onDocShellDestroy = function(docShell) {
// Stop watching this docshell (the unwatch() method will check if we
// started watching it before).
this._unwatchDocShell(docShell);
// Let the _onDocShellDestroy notify that the docShell has been destroyed.
const webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
this._notifyDocShellDestroy(webProgress);
@@ -207,23 +217,23 @@ webExtensionChildPrototype._onDocShellDe
// currently attached, switch to the fallback window
if (this.attached && docShell == this.docShell) {
// Creates a fallback window if it doesn't exist yet.
this._createFallbackWindow();
this._changeTopLevelDocument(this.fallbackWindow);
}
};
-webExtensionChildPrototype._onNewExtensionWindow = function(window) {
+webExtensionTargetPrototype._onNewExtensionWindow = function(window) {
if (!this.window || this.window === this.fallbackWindow) {
this._changeTopLevelDocument(window);
}
};
-webExtensionChildPrototype._attach = function() {
+webExtensionTargetPrototype._attach = function() {
// NOTE: we need to be sure that `this.window` can return a window before calling the
// ParentProcessTargetActor.onAttach, or the BrowsingContextTargetActor will not be
// subscribed to the child doc shell updates.
if (!this.window || this.window.document.nodePrincipal.addonId !== this.id) {
// Discovery an existent extension page to attach.
const extensionWindow = this._searchForExtensionWindow();
@@ -235,29 +245,29 @@ webExtensionChildPrototype._attach = fun
}
}
// Call ParentProcessTargetActor's _attach to listen for any new/destroyed chrome
// docshell.
ParentProcessTargetActor.prototype._attach.apply(this);
};
-webExtensionChildPrototype._detach = function() {
+webExtensionTargetPrototype._detach = function() {
// Call ParentProcessTargetActor's _detach to unsubscribe new/destroyed chrome docshell
// listeners.
ParentProcessTargetActor.prototype._detach.apply(this);
// Stop watching for new extension windows.
this._destroyFallbackWindow();
};
/**
* Return the json details related to a docShell.
*/
-webExtensionChildPrototype._docShellToWindow = function(docShell) {
+webExtensionTargetPrototype._docShellToWindow = function(docShell) {
const baseWindowDetails =
ParentProcessTargetActor.prototype._docShellToWindow.call(this, docShell);
const webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
const window = webProgress.DOMWindow;
// Collect the addonID from the document origin attributes and its sameType top level
@@ -273,44 +283,44 @@ webExtensionChildPrototype._docShellToWi
addonID,
sameTypeRootAddonID,
});
};
/**
* Return an array of the json details related to an array/iterator of docShells.
*/
-webExtensionChildPrototype._docShellsToWindows = function(docshells) {
+webExtensionTargetPrototype._docShellsToWindows = function(docshells) {
return ParentProcessTargetActor.prototype._docShellsToWindows.call(this, docshells)
.filter(windowDetails => {
// Filter the docShells based on the addon id of the window or
// its sameType top level frame.
return windowDetails.addonID === this.id ||
windowDetails.sameTypeRootAddonID === this.id;
});
};
-webExtensionChildPrototype.isExtensionWindow = function(window) {
+webExtensionTargetPrototype.isExtensionWindow = function(window) {
return window.document.nodePrincipal.addonId == this.id;
};
-webExtensionChildPrototype.isExtensionWindowDescendent = function(window) {
+webExtensionTargetPrototype.isExtensionWindowDescendent = function(window) {
// Check if the source is coming from a descendant docShell of an extension window.
const docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
const rootWin = docShell.sameTypeRootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
return this.isExtensionWindow(rootWin);
};
/**
* 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).
*/
-webExtensionChildPrototype._allowSource = function(source) {
+webExtensionTargetPrototype._allowSource = function(source) {
// Use the source.element to detect the allowed source, if any.
if (source.element) {
const domEl = unwrapDebuggerObjectGlobal(source.element);
return (this.isExtensionWindow(domEl.ownerGlobal) ||
this.isExtensionWindowDescendent(domEl.ownerGlobal));
}
// Fallback to check the uri if there is no source.element associated to the source.
@@ -347,17 +357,17 @@ webExtensionChildPrototype._allowSource
return false;
}
};
/**
* Return true if the given global is associated with this addon and should be
* added as a debuggee, false otherwise.
*/
-webExtensionChildPrototype._shouldAddNewGlobalAsDebuggee = function(newGlobal) {
+webExtensionTargetPrototype._shouldAddNewGlobalAsDebuggee = function(newGlobal) {
const global = unwrapDebuggerObjectGlobal(newGlobal);
if (global instanceof Ci.nsIDOMWindow) {
try {
global.document;
} catch (e) {
// The global might be a sandbox with a window object in its proto chain. If the
// window navigated away since the sandbox was created, it can throw a security
@@ -390,18 +400,18 @@ webExtensionChildPrototype._shouldAddNew
// Unable to retrieve the sandbox metadata.
}
return false;
};
// Handlers for the messages received from the parent actor.
-webExtensionChildPrototype._onParentExit = function(msg) {
+webExtensionTargetPrototype._onParentExit = function(msg) {
if (msg.json.actor !== this.actorID) {
return;
}
this.exit();
};
-exports.WebExtensionChildActor =
- ActorClassWithSpec(browsingContextTargetSpec, webExtensionChildPrototype);
+exports.WebExtensionTargetActor =
+ ActorClassWithSpec(webExtensionTargetSpec, webExtensionTargetPrototype);
--- a/devtools/server/startup/frame.js
+++ b/devtools/server/startup/frame.js
@@ -48,18 +48,18 @@ try {
Cu.blockThreadedExecution(() => {
const conn = DebuggerServer.connectToParent(prefix, mm);
conn.parentMessageManager = mm;
connections.set(prefix, conn);
let actor;
if (addonId) {
- const { WebExtensionChildActor } = require("devtools/server/actors/addon/webextension");
- actor = new WebExtensionChildActor(conn, chromeGlobal, prefix, addonId);
+ const { WebExtensionTargetActor } = require("devtools/server/actors/targets/webextension");
+ actor = new WebExtensionTargetActor(conn, chromeGlobal, prefix, addonId);
} else {
const { FrameTargetActor } = require("devtools/server/actors/targets/frame");
actor = new FrameTargetActor(conn, chromeGlobal);
}
const actorPool = new ActorPool(conn);
actorPool.addActor(actor);
conn.addActorPool(actorPool);
--- a/devtools/shared/specs/index.js
+++ b/devtools/shared/specs/index.js
@@ -245,16 +245,21 @@ const Types = exports.__TypesForTests =
front: null,
},
{
types: ["parentProcessTarget"],
spec: "devtools/shared/specs/targets/parent-process",
front: null,
},
{
+ types: ["webExtensionTarget"],
+ spec: "devtools/shared/specs/targets/webextension",
+ front: null,
+ },
+ {
types: ["workerTarget"],
spec: "devtools/shared/specs/targets/worker",
front: null,
},
{
types: ["timeline"],
spec: "devtools/shared/specs/timeline",
front: "devtools/shared/fronts/timeline",
--- a/devtools/shared/specs/targets/moz.build
+++ b/devtools/shared/specs/targets/moz.build
@@ -4,10 +4,11 @@
# 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(
'browsing-context.js',
'chrome-window.js',
'frame.js',
'parent-process.js',
+ 'webextension.js',
'worker.js',
)
--- a/devtools/shared/specs/targets/parent-process.js
+++ b/devtools/shared/specs/targets/parent-process.js
@@ -2,16 +2,19 @@
* 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 { generateActorSpec } = require("devtools/shared/protocol");
const { extend } = require("devtools/shared/extend");
const { browsingContextTargetSpecPrototype } = require("devtools/shared/specs/targets/browsing-context");
-const parentProcessTargetSpec = generateActorSpec(extend(
+const parentProcessTargetSpecPrototype = extend(
browsingContextTargetSpecPrototype,
{
typeName: "parentProcessTarget",
}
-));
+);
+const parentProcessTargetSpec = generateActorSpec(parentProcessTargetSpecPrototype);
+
+exports.parentProcessTargetSpecPrototype = parentProcessTargetSpecPrototype;
exports.parentProcessTargetSpec = parentProcessTargetSpec;
copy from devtools/shared/specs/targets/parent-process.js
copy to devtools/shared/specs/targets/webextension.js
--- a/devtools/shared/specs/targets/parent-process.js
+++ b/devtools/shared/specs/targets/webextension.js
@@ -1,17 +1,17 @@
/* 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 { generateActorSpec } = require("devtools/shared/protocol");
const { extend } = require("devtools/shared/extend");
-const { browsingContextTargetSpecPrototype } = require("devtools/shared/specs/targets/browsing-context");
+const { parentProcessTargetSpecPrototype } = require("devtools/shared/specs/targets/parent-process");
-const parentProcessTargetSpec = generateActorSpec(extend(
- browsingContextTargetSpecPrototype,
+const webExtensionTargetSpec = generateActorSpec(extend(
+ parentProcessTargetSpecPrototype,
{
- typeName: "parentProcessTarget",
+ typeName: "webExtensionTarget",
}
));
-exports.parentProcessTargetSpec = parentProcessTargetSpec;
+exports.webExtensionTargetSpec = webExtensionTargetSpec;