Bug 1450948 - Convert ChromeActor to protocol.js WIP draft
authorAlexandre Poirot <poirot.alex@gmail.com>
Tue, 10 Apr 2018 03:48:25 -0700
changeset 782956 2d59953f11efe8f46ad7a4c0587f06708cc7c046
parent 779502 83de58ddda2057f1cb949537f6b111e3b115ea3d
push id106582
push userbmo:ystartsev@mozilla.com
push dateMon, 16 Apr 2018 13:32:30 +0000
bugs1450948
milestone61.0a1
Bug 1450948 - Convert ChromeActor to protocol.js WIP MozReview-Commit-ID: 69cBeuRnNKX
devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js
devtools/server/actors/chrome.js
devtools/server/actors/tab.js
devtools/server/main.js
devtools/shared/client/debugger-client.js
devtools/shared/protocol.js
devtools/shared/specs/chrome.js
devtools/shared/specs/index.js
devtools/shared/specs/moz.build
devtools/shared/specs/tab.js
--- a/devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js
@@ -41,17 +41,19 @@ function test() {
   });
 }
 
 function testChromeActor() {
   gClient.getProcess().then(aResponse => {
     gClient.addListener("newGlobal", onNewGlobal);
 
     let actor = aResponse.form.actor;
+    dump(`%%%%%%%%%%%%%%%% Runs to here\n`);
     gClient.attachTab(actor, (response, tabClient) => {
+    dump(`%%%%%%%%%%%%%%%% does not run to here\n`);
       tabClient.attachThread(null, (aResponse, aThreadClient) => {
         gThreadClient = aThreadClient;
         gThreadClient.addListener("newSource", onNewSource);
 
         if (aResponse.error) {
           ok(false, "Couldn't attach to the chrome debugger.");
           gAttached.reject();
         } else {
--- 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,36 @@ 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);
   }
 };
+
+exports.ChromeActor = ActorClassWithSpec(tabSpec, chromePrototype);
--- a/devtools/server/actors/tab.js
+++ b/devtools/server/actors/tab.js
@@ -593,17 +593,17 @@ TabActor.prototype = {
   },
 
   _unwatchDocShell(docShell) {
     if (this._progressListener) {
       this._progressListener.unwatch(docShell);
     }
   },
 
-  onSwitchToFrame(request) {
+  switchToFrame(request) {
     let windowId = request.windowId;
     let win;
 
     try {
       win = Services.wm.getOuterWindowWithId(windowId);
     } catch (e) {
       // ignore
     }
@@ -615,22 +615,22 @@ TabActor.prototype = {
     }
 
     // Reply first before changing the document
     DevToolsUtils.executeSoon(() => this._changeTopLevelDocument(win));
 
     return {};
   },
 
-  onListFrames(request) {
+  listFrames(request) {
     let windows = this._docShellsToWindows(this.docShells);
     return { frames: windows };
   },
 
-  onListWorkers(request) {
+  listWorkers(request) {
     if (!this.attached) {
       return { error: "wrongState" };
     }
 
     if (this._workerActorList === null) {
       this._workerActorList = new WorkerActorList(this.conn, {
         type: Ci.nsIWorkerDebugger.TYPE_DEDICATED,
         window: this.window
@@ -651,17 +651,17 @@ TabActor.prototype = {
 
       return {
         "from": this.actorID,
         "workers": actors.map((actor) => actor.form())
       };
     });
   },
 
-  onLogInPage(request) {
+  logInPage(request) {
     let {text, category, flags} = request;
     let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
     let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
     scriptError.initWithWindowID(text, null, null, 0, 0, flags,
                                  category, getInnerId(this.window));
     Services.console.logMessage(scriptError);
     return {};
   },
@@ -921,86 +921,86 @@ TabActor.prototype = {
     this.conn.send({ from: this.actorID,
                      type: "tabDetached" });
 
     return true;
   },
 
   // Protocol Request Handlers
 
-  onAttach(request) {
+  attach(request) {
     if (this.exited) {
       return { type: "exited" };
     }
 
     this._attach();
 
     return {
       type: "tabAttached",
       threadActor: this.threadActor.actorID,
       cacheDisabled: this._getCacheDisabled(),
       javascriptEnabled: this._getJavascriptEnabled(),
       traits: this.traits,
     };
   },
 
-  onDetach(request) {
+  detach(request) {
     if (!this._detach()) {
       return { error: "wrongState" };
     }
 
     return { type: "detached" };
   },
 
   /**
    * Bring the tab's window to front.
    */
-  onFocus() {
+  focus() {
     if (this.window) {
       this.window.focus();
     }
     return {};
   },
 
   /**
    * 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.
     Services.tm.dispatchToMainThread(DevToolsUtils.makeInfallible(() => {
       // This won't work while the browser is shutting down and we don't really
       // care.
       if (Services.startup.shuttingDown) {
         return;
       }
       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) {
       // The tab is already closed.
       return {};
     }
     this._toggleDevToolsSettings(options);
 
@@ -1033,17 +1033,17 @@ TabActor.prototype = {
     }
 
     // Reload if:
     //  - there's an explicit `performReload` flag and it's true
     //  - there's no `performReload` flag, but it makes sense to do so
     let hasExplicitReloadFlag = "performReload" in options;
     if ((hasExplicitReloadFlag && options.performReload) ||
        (!hasExplicitReloadFlag && reload)) {
-      this.onReload();
+      this.reload();
     }
   },
 
   /**
    * Opposite of the _toggleDevToolsSettings method, that reset document state
    * when closing the toolbox.
    */
   _restoreDocumentSettings() {
@@ -1419,26 +1419,26 @@ 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,
-  "switchToFrame": TabActor.prototype.onSwitchToFrame,
-  "listFrames": TabActor.prototype.onListFrames,
-  "listWorkers": TabActor.prototype.onListWorkers,
-  "logInPage": TabActor.prototype.onLogInPage,
+  "attach": TabActor.prototype.attach,
+  "detach": TabActor.prototype.detach,
+  "focus": TabActor.prototype.focus,
+  "reload": TabActor.prototype.reload,
+  "navigateTo": TabActor.prototype.navigateTo,
+  "reconfigure": TabActor.prototype.reconfigure,
+  "switchToFrame": TabActor.prototype.switchToFrame,
+  "listFrames": TabActor.prototype.listFrames,
+  "listWorkers": TabActor.prototype.listWorkers,
+  "logInPage": TabActor.prototype.logInPage,
 };
 
 exports.TabActor = TabActor;
 
 /**
  * The DebuggerProgressListener object is an nsIWebProgressListener which
  * handles onStateChange events for the inspected browser. If the user tries to
  * navigate away from a paused page, the listener makes sure that the debuggee
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -1772,16 +1772,18 @@ DebuggerServerConnection.prototype = {
         ret = actor.requestTypes[packet.type].bind(actor)(packet, this);
       } catch (error) {
         let prefix = "error occurred while processing '" + packet.type;
         this.transport.send(this._unknownError(actor.actorID, prefix, error));
       } finally {
         this.currentPacket = undefined;
       }
     } else {
+      dump(`%%%%%%%%%%%%%%%%%%%%% actor = ${Object.keys(actor.requestTypes)}\n`);
+      dump(`%%%%%%%%%%%%%%%%%%%%% unrecognized packet type = ${packet.type}\n`);
       ret = { error: "unrecognizedPacketType",
               message: ("Actor " + actor.actorID +
                         " does not recognize the packet type " +
                         packet.type) };
     }
 
     // There will not be a return value if a bulk reply is sent.
     if (ret) {
--- a/devtools/shared/client/debugger-client.js
+++ b/devtools/shared/client/debugger-client.js
@@ -351,16 +351,17 @@ DebuggerClient.prototype = {
       DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
       return promise.resolve([cachedResponse, cachedTab]);
     }
 
     let packet = {
       to: tabActor,
       type: "attach"
     };
+    dump(`%%%%%%%%%%%%%%%%%%%%%%%%% Tab Actor: ${tabActor} \n`);
     return this.request(packet).then(response => {
       let tabClient;
       if (!response.error) {
         tabClient = new TabClient(this, response);
         this.registerClient(tabClient);
       }
       onResponse(response, tabClient);
       return [response, tabClient];
@@ -471,16 +472,17 @@ DebuggerClient.prototype = {
       return promise.resolve([{}, client]);
     }
 
     let packet = {
       to: threadActor,
       type: "attach",
       options,
     };
+    dump("attach thread: "+threadActor+"\n");
     return this.request(packet).then(response => {
       let threadClient;
       if (!response.error) {
         threadClient = new ThreadClient(this, threadActor);
         this.registerClient(threadClient);
       }
       onResponse(response, threadClient);
       return [response, threadClient];
--- a/devtools/shared/protocol.js
+++ b/devtools/shared/protocol.js
@@ -213,16 +213,17 @@ types.addArrayType = function(subtype) {
  *
  * Properties of the value that aren't included in the specializations
  * will be serialized as primitive values.
  *
  * @param object specializations
  *    A dict of property names => type
  */
 types.addDictType = function(name, specializations) {
+  dump(`%%%%%%%%%%%%%%%%%%%%%%% write dict type named ${name}\n`);
   return types.addType(name, {
     category: "dict",
     specializations: specializations,
     read: (v, ctx) => {
       let ret = {};
       for (let prop in v) {
         if (prop in specializations) {
           ret[prop] = types.getType(specializations[prop]).read(v[prop], ctx);
@@ -232,16 +233,17 @@ types.addDictType = function(name, speci
       }
       return ret;
     },
 
     write: (v, ctx) => {
       let ret = {};
       for (let prop in v) {
         if (prop in specializations) {
+          dump(`%%%%%%%%%%%%%%%%%%%%%%% write prop protocol ${prop}\n`);
           ret[prop] = types.getType(specializations[prop]).write(v[prop], ctx);
         } else {
           ret[prop] = v[prop];
         }
       }
       return ret;
     }
   });
@@ -1028,16 +1030,17 @@ var generateActorSpec = function(actorDe
         actorSpec[name] = types.addDictType(actorDesc.typeName + "__" + name, desc.value);
       }
     }
 
     if (desc.value._methodSpec) {
       let methodSpec = desc.value._methodSpec;
       let spec = {};
       spec.name = methodSpec.name || name;
+      dump(`%%%%%%%%%%%%%%%%%% method spec ${spec.name}\n`);
       spec.request = new Request(Object.assign({type: spec.name},
                                           methodSpec.request || undefined));
       spec.response = new Response(methodSpec.response || undefined);
       spec.release = methodSpec.release;
       spec.oneway = methodSpec.oneway;
 
       actorSpec.methods.push(spec);
     }
@@ -1098,16 +1101,17 @@ var generateRequestHandlers = function(a
         let args;
         try {
           args = spec.request.read(packet, this);
         } catch (ex) {
           console.error("Error reading request: " + packet.type);
           throw ex;
         }
 
+        dump(`%%%%%%%%%%%%%%%%%% spec name protocol ${spec.name}\n`);
         let ret = this[spec.name].apply(this, args);
 
         let sendReturn = (retToSend) => {
           if (spec.oneway) {
             // No need to send a response.
             return;
           }
 
new file mode 100644
--- /dev/null
+++ b/devtools/shared/specs/chrome.js
@@ -0,0 +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 {types, generateActorSpec, RetVal} = require("devtools/shared/protocol");
+
+types.addDictType("tab.attach", {
+  type: "string",
+  threadActor: "number",
+  cacheDisabled: "boolean",
+  javascriptEnabled: "boolean",
+  traits: "json",
+});
+const tabSpec = generateActorSpec({
+  typeName: "tab",
+
+  methods: {
+    attach: {
+      request: {},
+      response: RetVal("tab.attach")
+    }
+  },
+});
+
+exports.tabSpec = tabSpec;
--- a/devtools/shared/specs/index.js
+++ b/devtools/shared/specs/index.js
@@ -190,16 +190,21 @@ const Types = exports.__TypesForTests = 
     front: "devtools/shared/fronts/styles",
   },
   {
     types: ["mediarule", "stylesheet", "stylesheets"],
     spec: "devtools/shared/specs/stylesheets",
     front: "devtools/shared/fronts/stylesheets",
   },
   {
+    types: ["tab"],
+    spec: "devtools/shared/specs/tab",
+    front: null,
+  },
+  {
     types: ["timeline"],
     spec: "devtools/shared/specs/timeline",
     front: "devtools/shared/fronts/timeline",
   },
   {
     types: ["audionode", "webaudio"],
     spec: "devtools/shared/specs/webaudio",
     front: "devtools/shared/fronts/webaudio",
--- a/devtools/shared/specs/moz.build
+++ b/devtools/shared/specs/moz.build
@@ -34,15 +34,16 @@ DevToolsModules(
     'promises.js',
     'reflow.js',
     'script.js',
     'source.js',
     'storage.js',
     'string.js',
     'styles.js',
     'stylesheets.js',
+    'tab.js',
     'timeline.js',
     'webaudio.js',
     'webextension-inspected-window.js',
     'webextension-parent.js',
     'webgl.js',
     'worker.js'
 )
new file mode 100644
--- /dev/null
+++ b/devtools/shared/specs/tab.js
@@ -0,0 +1,114 @@
+/* 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, Arg, 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",
+  from: "nullable:string",
+});
+
+types.addDictType("tab.reloadrequest", {
+  options: "nullable:tab.reloadoptions"
+});
+
+types.addDictType("tab.reloadoptions", {
+  force: "boolean"
+});
+
+types.addDictType("tab.reconfigurerequest", {
+  javascriptEnabled: "boolean",
+  cacheDisabled: "boolean",
+  serviceWorkersTestingEnabled: "boolean",
+  performReload: "boolean",
+});
+
+types.addDictType("tab.navigateto", {
+  window: "string"
+});
+
+types.addDictType("tab.loginpagerequest", {
+  text: "string",
+  category: "string",
+  flags: "strng"
+});
+
+const tabSpec = generateActorSpec({
+  typeName: "tab",
+
+  methods: {
+    attach: {
+      request: {},
+      response: RetVal("tab.attach")
+    },
+    detach: {
+      request: {},
+      response: RetVal("tab.detach")
+    },
+    focus: {
+      request: {},
+      response: {}
+    },
+    reload: {
+      request: Arg(0, "tab.reloadrequest"),
+      response: {}
+    },
+    navigateTo: {
+      request: Arg(0, "tab.navigateto"),
+      response: {}
+    },
+    reconfigure: {
+      request: Option(0, "tab.reconfigurerequest"),
+      response: {}
+    },
+    switchToFrame: {
+      request: {},
+      response: RetVal("tab.switchtoframe")
+    },
+    listFrames: {
+      request: {},
+      response: RetVal("tab.listframes")
+    },
+    listWorkers: {
+      request: {},
+      response: RetVal("tab.workers")
+    },
+    logInPage: {
+      request: Arg(0, "tab.loginpagerequest"),
+      response: {}
+    }
+  },
+});
+
+exports.tabSpec = tabSpec;