Bug 1450948 - Convert ChromeActor to protocol.js r=ochameau
MozReview-Commit-ID: 3rV5l6nwX9x
--- a/devtools/server/actors/chrome.js
+++ b/devtools/server/actors/chrome.js
@@ -5,16 +5,20 @@
"use strict";
const { Ci } = require("chrome");
const Services = require("Services");
const { DebuggerServer } = require("../main");
const { getChildDocShells, TabActor } = require("./tab");
const makeDebugger = require("./utils/make-debugger");
+const { extend } = require("devtools/shared/extend");
+const { ActorClassWithSpec, Actor } = require("devtools/shared/protocol");
+const { tabSpec } = require("devtools/shared/specs/tab");
+
/**
* 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.
*
* History lecture:
@@ -25,17 +29,26 @@ const makeDebugger = require("./utils/ma
* 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.
*
* @param connection DebuggerServerConnection
* The connection to the client.
*/
-function ChromeActor(connection) {
+
+/* We are creating a prototype object rather than a class here, so in order
+ * to inherit from the TabActor, we extend from a normal object, and apply the
+ * properties of the TabActor prototype
+ * */
+
+const chromePrototype = extend({}, TabActor.prototype);
+
+chromePrototype.initialize = function(connection) {
+ Actor.prototype.initialize.call(this, connection);
TabActor.call(this, connection);
// This creates a Debugger instance for chrome debugging all globals.
this.makeDebugger = makeDebugger.bind(null, {
findDebuggees: dbg => dbg.findAllGlobals(),
shouldAddNewGlobalAsDebuggee: () => true
});
@@ -64,62 +77,57 @@ function ChromeActor(connection) {
// 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;
+};
-ChromeActor.prototype = Object.create(TabActor.prototype);
-
-ChromeActor.prototype.constructor = ChromeActor;
-
-ChromeActor.prototype.isRootActor = true;
+chromePrototype.isRootActor = true;
/**
* Getter for the list of all docshells in this tabActor
* @return {Array}
*/
-Object.defineProperty(ChromeActor.prototype, "docShells", {
+Object.defineProperty(chromePrototype, "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));
}
return docShells;
}
});
-ChromeActor.prototype.observe = function(subject, topic, data) {
+chromePrototype.observe = function(subject, topic, data) {
TabActor.prototype.observe.call(this, subject, topic, data);
if (!this.attached) {
return;
}
subject.QueryInterface(Ci.nsIDocShell);
if (topic == "chrome-webnavigation-create") {
this._onDocShellCreated(subject);
} else if (topic == "chrome-webnavigation-destroy") {
this._onDocShellDestroy(subject);
}
};
-ChromeActor.prototype._attach = function() {
+chromePrototype._attach = function() {
if (this.attached) {
return false;
}
TabActor.prototype._attach.call(this);
// Listen for any new/destroyed chrome docshell
Services.obs.addObserver(this, "chrome-webnavigation-create");
@@ -135,17 +143,17 @@ ChromeActor.prototype._attach = function
if (docShell == this.docShell) {
continue;
}
this._progressListener.watch(docShell);
}
return undefined;
};
-ChromeActor.prototype._detach = function() {
+chromePrototype._detach = function() {
if (!this.attached) {
return false;
}
Services.obs.removeObserver(this, "chrome-webnavigation-create");
Services.obs.removeObserver(this, "chrome-webnavigation-destroy");
// Iterate over all top-level windows.
@@ -165,34 +173,38 @@ ChromeActor.prototype._detach = function
return undefined;
};
/* ThreadActor hooks. */
/**
* Prepare to enter a nested event loop by disabling debuggee events.
*/
-ChromeActor.prototype.preNest = function() {
+chromePrototype.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();
}
};
/**
* Prepare to exit a nested event loop by enabling debuggee events.
*/
-ChromeActor.prototype.postNest = function(nestData) {
+chromePrototype.postNest = function(nestData) {
// 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);
}
};
+
+chromePrototype.typeName = "Chrome";
+exports.chromePrototype = chromePrototype;
+exports.ChromeActor = ActorClassWithSpec(tabSpec, chromePrototype);
--- a/devtools/server/actors/webextension.js
+++ b/devtools/server/actors/webextension.js
@@ -1,24 +1,26 @@
/* 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 { extend } = require("devtools/shared/extend");
const { Ci, Cu, Cc } = require("chrome");
const Services = require("Services");
-const { ChromeActor } = require("./chrome");
+const { ChromeActor, chromePrototype } = require("./chrome");
const makeDebugger = require("./utils/make-debugger");
+const { ActorClassWithSpec } = require("devtools/shared/protocol");
+const { tabSpec } = require("devtools/shared/specs/tab");
loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
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.";
/**
* Creates a TabActor 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 ChromeActor (which inherits most of its
* implementation from TabActor).
* WebExtensionChildActor is created by a WebExtensionParentActor counterpart, when its
@@ -48,19 +50,21 @@ 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.
*/
-function WebExtensionChildActor(conn, chromeGlobal, prefix, addonId) {
- ChromeActor.call(this, conn);
+
+const webExtensionChildPrototype = extend({}, chromePrototype);
+webExtensionChildPrototype.initialize = function(conn, chromeGlobal, prefix, addonId) {
+ chromePrototype.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
// connect to the extension process).
@@ -98,33 +102,29 @@ function WebExtensionChildActor(conn, ch
// Try to discovery an existent extension page to attach (which will provide the initial
// URL shown in the window tittle when the addon debugger is opened).
let extensionWindow = this._searchForExtensionWindow();
if (extensionWindow) {
this._setWindow(extensionWindow);
}
-}
-exports.WebExtensionChildActor = WebExtensionChildActor;
+};
-WebExtensionChildActor.prototype = Object.create(ChromeActor.prototype);
-
-WebExtensionChildActor.prototype.actorPrefix = "webExtension";
-WebExtensionChildActor.prototype.constructor = WebExtensionChildActor;
+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.
-WebExtensionChildActor.prototype.isRootActor = true;
+webExtensionChildPrototype.isRootActor = true;
/**
* Called when the actor is removed from the connection.
*/
-WebExtensionChildActor.prototype.exit = function() {
+webExtensionChildPrototype.exit = function() {
if (this._chromeGlobal) {
let chromeGlobal = this._chromeGlobal;
this._chromeGlobal = null;
chromeGlobal.removeMessageListener("debug:webext_parent_exit", this._onParentExit);
chromeGlobal.sendAsyncMessage("debug:webext_child_exit", {
actor: this.actorID
@@ -134,17 +134,17 @@ WebExtensionChildActor.prototype.exit =
this.addon = null;
this.id = null;
return ChromeActor.prototype.exit.apply(this);
};
// Private helpers.
-WebExtensionChildActor.prototype._createFallbackWindow = function() {
+webExtensionChildPrototype._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).
@@ -153,48 +153,48 @@ WebExtensionChildActor.prototype._create
// 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;
};
-WebExtensionChildActor.prototype._destroyFallbackWindow = function() {
+webExtensionChildPrototype._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.
-WebExtensionChildActor.prototype._searchForExtensionWindow = function() {
+webExtensionChildPrototype._searchForExtensionWindow = function() {
let e = Services.ww.getWindowEnumerator(null);
while (e.hasMoreElements()) {
let window = e.getNext();
if (window.document.nodePrincipal.addonId == this.id) {
return window;
}
}
return undefined;
};
// Customized ChromeActor/TabActor hooks.
-WebExtensionChildActor.prototype._onDocShellDestroy = function(docShell) {
+webExtensionChildPrototype._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.
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
this._notifyDocShellDestroy(webProgress);
@@ -203,23 +203,23 @@ WebExtensionChildActor.prototype._onDocS
// 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);
}
};
-WebExtensionChildActor.prototype._onNewExtensionWindow = function(window) {
+webExtensionChildPrototype._onNewExtensionWindow = function(window) {
if (!this.window || this.window === this.fallbackWindow) {
this._changeTopLevelDocument(window);
}
};
-WebExtensionChildActor.prototype._attach = function() {
+webExtensionChildPrototype._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 (!this.window || this.window.document.nodePrincipal.addonId !== this.id) {
// Discovery an existent extension page to attach.
let extensionWindow = this._searchForExtensionWindow();
@@ -230,28 +230,28 @@ WebExtensionChildActor.prototype._attach
this._setWindow(extensionWindow);
}
}
// Call ChromeActor's _attach to listen for any new/destroyed chrome docshell
ChromeActor.prototype._attach.apply(this);
};
-WebExtensionChildActor.prototype._detach = function() {
+webExtensionChildPrototype._detach = function() {
// Call ChromeActor's _detach to unsubscribe new/destroyed chrome docshell listeners.
ChromeActor.prototype._detach.apply(this);
// Stop watching for new extension windows.
this._destroyFallbackWindow();
};
/**
* Return the json details related to a docShell.
*/
-WebExtensionChildActor.prototype._docShellToWindow = function(docShell) {
+webExtensionChildPrototype._docShellToWindow = function(docShell) {
const baseWindowDetails = ChromeActor.prototype._docShellToWindow.call(this, docShell);
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
let window = webProgress.DOMWindow;
// Collect the addonID from the document origin attributes and its sameType top level
// frame.
@@ -266,44 +266,44 @@ WebExtensionChildActor.prototype._docShe
addonID,
sameTypeRootAddonID,
});
};
/**
* Return an array of the json details related to an array/iterator of docShells.
*/
-WebExtensionChildActor.prototype._docShellsToWindows = function(docshells) {
+webExtensionChildPrototype._docShellsToWindows = function(docshells) {
return ChromeActor.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;
});
};
-WebExtensionChildActor.prototype.isExtensionWindow = function(window) {
+webExtensionChildPrototype.isExtensionWindow = function(window) {
return window.document.nodePrincipal.addonId == this.id;
};
-WebExtensionChildActor.prototype.isExtensionWindowDescendent = function(window) {
+webExtensionChildPrototype.isExtensionWindowDescendent = function(window) {
// Check if the source is coming from a descendant docShell of an extension window.
let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
let 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).
*/
-WebExtensionChildActor.prototype._allowSource = function(source) {
+webExtensionChildPrototype._allowSource = function(source) {
// Use the source.element to detect the allowed source, if any.
if (source.element) {
let 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.
@@ -340,17 +340,17 @@ WebExtensionChildActor.prototype._allowS
return false;
}
};
/**
* Return true if the given global is associated with this addon and should be
* added as a debuggee, false otherwise.
*/
-WebExtensionChildActor.prototype._shouldAddNewGlobalAsDebuggee = function(newGlobal) {
+webExtensionChildPrototype._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
@@ -383,15 +383,17 @@ WebExtensionChildActor.prototype._should
// Unable to retrieve the sandbox metadata.
}
return false;
};
// Handlers for the messages received from the parent actor.
-WebExtensionChildActor.prototype._onParentExit = function(msg) {
+webExtensionChildPrototype._onParentExit = function(msg) {
if (msg.json.actor !== this.actorID) {
return;
}
this.exit();
};
+
+exports.WebExtensionChildActor = ActorClassWithSpec(tabSpec, webExtensionChildPrototype);