diff --git a/devtools/server/actors/chrome.js b/devtools/server/actors/chrome.js index c2ca199637e2..f61041c6ddb9 100644 --- a/devtools/server/actors/chrome.js +++ b/devtools/server/actors/chrome.js @@ -10,6 +10,10 @@ 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. @@ -30,7 +34,17 @@ const makeDebugger = require("./utils/make-debugger"); * @param connection DebuggerServerConnection * The connection to the client. */ -function ChromeActor(connection) { + +/** + * 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 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. @@ -69,20 +83,15 @@ function ChromeActor(connection) { 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 = []; @@ -99,7 +108,7 @@ Object.defineProperty(ChromeActor.prototype, "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; @@ -114,7 +123,7 @@ ChromeActor.prototype.observe = function(subject, topic, data) { } }; -ChromeActor.prototype._attach = function() { +chromePrototype._attach = function() { if (this.attached) { return false; } @@ -140,7 +149,7 @@ ChromeActor.prototype._attach = function() { return undefined; }; -ChromeActor.prototype._detach = function() { +chromePrototype._detach = function() { if (!this.attached) { return false; } @@ -170,7 +179,7 @@ ChromeActor.prototype._detach = function() { /** * 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()) { @@ -185,7 +194,7 @@ ChromeActor.prototype.preNest = function() { /** * 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()) { @@ -196,3 +205,7 @@ ChromeActor.prototype.postNest = function(nestData) { windowUtils.suppressEventHandling(false); } }; + +chromePrototype.typeName = "Chrome"; +exports.chromePrototype = chromePrototype; +exports.ChromeActor = ActorClassWithSpec(tabSpec, chromePrototype); diff --git a/devtools/server/actors/tab.js b/devtools/server/actors/tab.js index a72b5b7274d4..b68873017457 100644 --- a/devtools/server/actors/tab.js +++ b/devtools/server/actors/tab.js @@ -611,7 +611,7 @@ TabActor.prototype = { } }, - onSwitchToFrame(request) { + switchToFrame(request) { let windowId = request.windowId; let win; @@ -633,12 +633,12 @@ TabActor.prototype = { return {}; }, - onListFrames(request) { + listFrames(request) { let windows = this._docShellsToWindows(this.docShells); return { frames: windows }; }, - onListWorkers(request) { + listWorkers(request) { if (!this.attached) { return { error: "wrongState" }; } @@ -669,7 +669,7 @@ TabActor.prototype = { }); }, - onLogInPage(request) { + logInPage(request) { let {text, category, flags} = request; let scriptErrorClass = Cc["@mozilla.org/scripterror;1"]; let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError); @@ -939,7 +939,7 @@ TabActor.prototype = { // Protocol Request Handlers - onAttach(request) { + attach(request) { if (this.exited) { return { type: "exited" }; } @@ -955,7 +955,7 @@ TabActor.prototype = { }; }, - onDetach(request) { + detach(request) { if (!this._detach()) { return { error: "wrongState" }; } @@ -966,7 +966,7 @@ TabActor.prototype = { /** * Bring the tab's window to front. */ - onFocus() { + focus() { if (this.window) { this.window.focus(); } @@ -976,7 +976,7 @@ TabActor.prototype = { /** * Reload the page in this tab. */ - onReload(request) { + reload(request) { let force = request && request.options && request.options.force; // Wait a tick so that the response packet can be dispatched before the // subsequent navigation event packet. @@ -989,26 +989,26 @@ TabActor.prototype = { this.webNavigation.reload(force ? Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE : Ci.nsIWebNavigation.LOAD_FLAGS_NONE); - }, "TabActor.prototype.onReload's delayed body")); + }, "TabActor.prototype.reload's delayed body")); return {}; }, /** * Navigate this tab to a new location */ - onNavigateTo(request) { + navigateTo(request) { // Wait a tick so that the response packet can be dispatched before the // subsequent navigation event packet. Services.tm.dispatchToMainThread(DevToolsUtils.makeInfallible(() => { this.window.location = request.url; - }, "TabActor.prototype.onNavigateTo's delayed body")); + }, "TabActor.prototype.navigateTo's delayed body")); return {}; }, /** * Reconfigure options. */ - onReconfigure(request) { + reconfigure(request) { let options = request.options || {}; if (!this.docShell) { @@ -1078,7 +1078,7 @@ TabActor.prototype = { let hasExplicitReloadFlag = "performReload" in options; if ((hasExplicitReloadFlag && options.performReload) || (!hasExplicitReloadFlag && reload)) { - this.onReload(); + this.reload(); } }, @@ -1464,17 +1464,17 @@ TabActor.prototype = { * The request types this actor can handle. */ TabActor.prototype.requestTypes = { - "attach": TabActor.prototype.onAttach, - "detach": TabActor.prototype.onDetach, - "focus": TabActor.prototype.onFocus, - "reload": TabActor.prototype.onReload, - "navigateTo": TabActor.prototype.onNavigateTo, - "reconfigure": TabActor.prototype.onReconfigure, + "attach": TabActor.prototype.attach, + "detach": TabActor.prototype.detach, + "focus": TabActor.prototype.focus, + "reload": TabActor.prototype.reload, + "navigateTo": TabActor.prototype.navigateTo, + "reconfigure": TabActor.prototype.reconfigure, "ensureCSSErrorReportingEnabled": TabActor.prototype.ensureCSSErrorReportingEnabled, - "switchToFrame": TabActor.prototype.onSwitchToFrame, - "listFrames": TabActor.prototype.onListFrames, - "listWorkers": TabActor.prototype.onListWorkers, - "logInPage": TabActor.prototype.onLogInPage, + "switchToFrame": TabActor.prototype.switchToFrame, + "listFrames": TabActor.prototype.listFrames, + "listWorkers": TabActor.prototype.listWorkers, + "logInPage": TabActor.prototype.logInPage, }; exports.TabActor = TabActor; diff --git a/devtools/server/actors/webextension.js b/devtools/server/actors/webextension.js index a060a0619186..75e12e194362 100644 --- a/devtools/server/actors/webextension.js +++ b/devtools/server/actors/webextension.js @@ -4,15 +4,17 @@ "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, "unwrapDebuggerObjectGlobal", "devtools/server/actors/thread", true); loader.lazyRequireGetter(this, "ChromeUtils"); - const FALLBACK_DOC_MESSAGE = "Your addon does not have any document opened yet."; /** @@ -52,9 +54,11 @@ const FALLBACK_DOC_MESSAGE = "Your addon does not have any document opened yet." * @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; @@ -102,23 +106,19 @@ function WebExtensionChildActor(conn, chromeGlobal, prefix, addonId) { 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; @@ -138,7 +138,7 @@ WebExtensionChildActor.prototype.exit = function() { // Private helpers. -WebExtensionChildActor.prototype._createFallbackWindow = function() { +webExtensionChildPrototype._createFallbackWindow = function() { if (this.fallbackWindow) { // Skip if there is already an existent fallback window. return; @@ -157,7 +157,7 @@ WebExtensionChildActor.prototype._createFallbackWindow = function() { 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). @@ -173,7 +173,7 @@ WebExtensionChildActor.prototype._destroyFallbackWindow = function() { // 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(); @@ -188,7 +188,7 @@ WebExtensionChildActor.prototype._searchForExtensionWindow = function() { // 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); @@ -207,13 +207,13 @@ WebExtensionChildActor.prototype._onDocShellDestroy = function(docShell) { } }; -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. @@ -234,7 +234,7 @@ WebExtensionChildActor.prototype._attach = function() { 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); @@ -245,7 +245,7 @@ WebExtensionChildActor.prototype._detach = function() { /** * 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) @@ -270,7 +270,7 @@ WebExtensionChildActor.prototype._docShellToWindow = function(docShell) { /** * 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 @@ -280,11 +280,11 @@ WebExtensionChildActor.prototype._docShellsToWindows = function(docshells) { }); }; -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); @@ -297,7 +297,7 @@ WebExtensionChildActor.prototype.isExtensionWindowDescendent = function(window) * 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); @@ -344,7 +344,7 @@ WebExtensionChildActor.prototype._allowSource = function(source) { * 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) { @@ -387,10 +387,12 @@ WebExtensionChildActor.prototype._shouldAddNewGlobalAsDebuggee = function(newGlo // 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); diff --git a/devtools/shared/protocol.js b/devtools/shared/protocol.js index 6671493706d7..46158e4076e6 100644 --- a/devtools/shared/protocol.js +++ b/devtools/shared/protocol.js @@ -1110,10 +1110,6 @@ exports.generateActorSpec = generateActorSpec; * the given actor prototype. Returns the actor prototype. */ var generateRequestHandlers = function(actorSpec, actorProto) { - if (actorProto._actorSpec) { - throw new Error("actorProto called twice on the same actor prototype!"); - } - actorProto.typeName = actorSpec.typeName; // Generate request handlers for each method definition diff --git a/devtools/shared/specs/index.js b/devtools/shared/specs/index.js index 7c5e964559df..61587233f3b1 100644 --- a/devtools/shared/specs/index.js +++ b/devtools/shared/specs/index.js @@ -204,6 +204,11 @@ const Types = exports.__TypesForTests = [ spec: "devtools/shared/specs/symbol-iterator", front: null, }, + { + types: ["tab"], + spec: "devtools/shared/specs/tab", + front: null, + }, { types: ["timeline"], spec: "devtools/shared/specs/timeline", diff --git a/devtools/shared/specs/moz.build b/devtools/shared/specs/moz.build index 04f33be23816..4c6792af16b9 100644 --- a/devtools/shared/specs/moz.build +++ b/devtools/shared/specs/moz.build @@ -41,6 +41,7 @@ DevToolsModules( 'styles.js', 'stylesheets.js', 'symbol-iterator.js', + 'tab.js', 'timeline.js', 'webaudio.js', 'webextension-inspected-window.js', diff --git a/devtools/shared/specs/tab.js b/devtools/shared/specs/tab.js new file mode 100644 index 000000000000..85b4339f1ba0 --- /dev/null +++ b/devtools/shared/specs/tab.js @@ -0,0 +1,111 @@ +/* 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 {types, generateActorSpec, RetVal, Option} = require("devtools/shared/protocol"); + +types.addDictType("tab.attach", { + type: "string", + threadActor: "number", + cacheDisabled: "boolean", + javascriptEnabled: "boolean", + traits: "json" +}); + +types.addDictType("tab.detach", { + error: "nullable:string", + type: "nullable:string" +}); + +types.addDictType("tab.switchtoframe", { + error: "nullable:string", + message: "nullable:string" +}); + +types.addDictType("tab.listframes", { + frames: "array:tab.window" +}); + +types.addDictType("tab.window", { + id: "string", + parentID: "nullable:string", + url: "string", + title: "string" +}); + +types.addDictType("tab.workers", { + error: "nullable:string" +}); + +types.addDictType("tab.reload", { + force: "boolean" +}); + +types.addDictType("tab.reconfigure", { + javascriptEnabled: "nullable:boolean", + cacheDisabled: "nullable:boolean", + serviceWorkersTestingEnabled: "nullable:boolean", + performReload: "nullable:boolean" +}); + +const tabSpec = generateActorSpec({ + typeName: "tab", + + methods: { + attach: { + request: {}, + response: RetVal("tab.attach") + }, + detach: { + request: {}, + response: RetVal("tab.detach") + }, + focus: { + request: {}, + response: {} + }, + reload: { + request: { + options: Option(0, "tab.reload"), + }, + response: {} + }, + navigateTo: { + request: { + url: Option(0, "string"), + }, + response: {} + }, + reconfigure: { + request: { + options: Option(0, "tab.reconfigure") + }, + response: {} + }, + switchToFrame: { + request: { + windowId: Option(0, "string") + }, + response: RetVal("tab.switchtoframe") + }, + listFrames: { + request: {}, + response: RetVal("tab.listframes") + }, + listWorkers: { + request: {}, + response: RetVal("tab.workers") + }, + logInPage: { + request: { + text: Option(0, "string"), + category: Option(0, "string"), + flags: Option(0, "string") + }, + response: {} + } + }, +}); + +exports.tabSpec = tabSpec;