Bug 1533679 - Differentiate Sessions and Targets bound to a browser-element from the others. r=ato
authorAlexandre Poirot <poirot.alex@gmail.com>
Sun, 10 Mar 2019 12:51:09 +0000
changeset 521285 44e4bcd29904
parent 521284 735dcab78c93
child 521286 ea81ce60cfea
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersato
bugs1533679
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1533679 - Differentiate Sessions and Targets bound to a browser-element from the others. r=ato The Session used by MainProcessTarget isn't bound to any browser-element and so do not use any of the message manager to pipe message to the content process. Same for Target, the MainProcessTarget doesn't expose any browsing-context-related attribute. Depends on D22691 Differential Revision: https://phabricator.services.mozilla.com/D22692
remote/domains/Domains.jsm
remote/jar.mn
remote/sessions/ContentProcessSession.jsm
remote/sessions/Session.jsm
remote/sessions/TabSession.jsm
remote/targets/TabTarget.jsm
remote/targets/Target.jsm
remote/targets/Targets.jsm
--- a/remote/domains/Domains.jsm
+++ b/remote/domains/Domains.jsm
@@ -66,16 +66,23 @@ class Domains {
       inst.addEventListener(this.session);
 
       this.instances.set(name, inst);
     }
 
     return inst;
   }
 
+  /**
+   * Tells if a Domain of the given name is available
+   */
+  has(name) {
+    return name in this.modules;
+  }
+
   get size() {
     return this.instances.size;
   }
 
   /** Calls destructor on each domain and clears the cache. */
   clear() {
     for (const inst of this.instances.values()) {
       inst.destructor();
--- a/remote/jar.mn
+++ b/remote/jar.mn
@@ -13,20 +13,21 @@ remote.jar:
   content/RecommendedPreferences.jsm (RecommendedPreferences.jsm)
   content/Sync.jsm (Sync.jsm)
   content/WindowManager.jsm (WindowManager.jsm)
 
   # sessions
   content/sessions/frame-script.js (sessions/frame-script.js)
   content/sessions/ContentProcessSession.jsm (sessions/ContentProcessSession.jsm)
   content/sessions/Session.jsm (sessions/Session.jsm)
+  content/sessions/TabSession.jsm (sessions/TabSession.jsm)
 
   # targets
-  content/targets/Target.jsm (targets/Target.jsm)
   content/targets/MainProcessTarget.jsm (targets/MainProcessTarget.jsm)
+  content/targets/TabTarget.jsm (targets/TabTarget.jsm)
   content/targets/Targets.jsm (targets/Targets.jsm)
 
   # domains
   content/domains/Domain.jsm (domains/Domain.jsm)
   content/domains/Domains.jsm (domains/Domains.jsm)
   content/domains/ContentProcessDomain.jsm (domains/ContentProcessDomain.jsm)
   content/domains/ContentProcessDomains.jsm (domains/ContentProcessDomains.jsm)
   content/domains/ParentProcessDomains.jsm (domains/ParentProcessDomains.jsm)
--- a/remote/sessions/ContentProcessSession.jsm
+++ b/remote/sessions/ContentProcessSession.jsm
@@ -31,17 +31,17 @@ class ContentProcessSession {
   }
 
   // Domain event listener
 
   onEvent(eventName, params) {
     this.messageManager.sendAsyncMessage("remote:event", {
       browsingContextId: this.browsingContext.id,
       event: {
-        method: eventName,
+        eventName,
         params,
       },
     });
   }
 
   // nsIMessageListener
 
   async receiveMessage({name, data}) {
--- a/remote/sessions/Session.jsm
+++ b/remote/sessions/Session.jsm
@@ -14,110 +14,79 @@ const {formatError} = ChromeUtils.import
  * A session represents exactly one client WebSocket connection.
  *
  * Every new WebSocket connections is associated with one session that
  * deals with despatching incoming command requests to the right
  * target, sending back responses, and propagating events originating
  * from domains.
  */
 class Session {
-  constructor(connection, target) {
+  constructor(connection, target, id) {
     this.connection = connection;
     this.target = target;
+    this.id = id;
 
-    this.connection.onmessage = this.dispatch.bind(this);
+    this.destructor = this.destructor.bind(this);
+
+    this.connection.onmessage = this.onMessage.bind(this);
+    this.connection.transport.on("close", this.destructor);
 
     this.domains = new Domains(this, ParentProcessDomains);
-    this.mm.addMessageListener("remote:event", this);
-    this.mm.addMessageListener("remote:result", this);
-    this.mm.addMessageListener("remote:error", this);
-
-    this.mm.loadFrameScript("chrome://remote/content/sessions/frame-script.js", false);
   }
 
   destructor() {
     this.domains.clear();
-    this.connection.onmessage = null;
-
-    this.mm.sendAsyncMessage("remote:destroy", {
-      browsingContextId: this.browsingContext.id,
-    });
-
-    this.mm.removeMessageListener("remote:event", this);
-    this.mm.removeMessageListener("remote:result", this);
-    this.mm.removeMessageListener("remote:error", this);
   }
 
-  async dispatch({id, method, params}) {
+  async onMessage({id, method, params}) {
     try {
       if (typeof id == "undefined") {
         throw new TypeError("Message missing 'id' field");
       }
       if (typeof method == "undefined") {
         throw new TypeError("Message missing 'method' field");
       }
 
       const [domainName, methodName] = Domains.splitMethod(method);
-      if (this.domains.domainSupportsMethod(domainName, methodName)) {
-        await this.executeInParent(id, domainName, methodName, params);
-      } else {
-        this.executeInChild(id, domainName, methodName, params);
-      }
+      await this.execute(id, domainName, methodName, params);
     } catch (e) {
-      const error = formatError(e, {stack: true});
-      this.connection.send({id, error: { message: error }});
+      this.onError(id, e);
     }
   }
 
-  async executeInParent(id, domain, method, params) {
+  async execute(id, domain, method, params) {
     const inst = this.domains.get(domain);
     const result = await inst[method](params);
-    this.connection.send({id, result});
+    this.onResult(id, result);
   }
 
-  executeInChild(id, domain, method, params) {
-    this.mm.sendAsyncMessage("remote:request", {
-      browsingContextId: this.browsingContext.id,
-      request: {id, domain, method, params},
+  onResult(id, result) {
+    this.connection.send({
+      id,
+      sessionId: this.id,
+      result,
     });
   }
 
-  get mm() {
-    return this.target.mm;
-  }
-
-  get browsingContext() {
-    return this.target.browsingContext;
+  onError(id, error) {
+    this.connection.send({
+      id,
+      sessionId: this.id,
+      error: {
+        message: formatError(error, {stack: true}),
+      },
+    });
   }
 
   // Domain event listener
 
   onEvent(eventName, params) {
     this.connection.send({
+      sessionId: this.id,
       method: eventName,
       params,
     });
   }
 
-  // nsIMessageListener
-
-  receiveMessage({name, data}) {
-    const {id, result, event, error} = data;
-
-    switch (name) {
-    case "remote:result":
-      this.connection.send({id, result});
-      break;
-
-    case "remote:event":
-      this.connection.send(event);
-      break;
-
-    case "remote:error":
-      this.connection.send({id, error: { message: formatError(error, {stack: true}) }});
-      break;
-    }
-  }
-
   toString() {
-    return `[object Session ${this.connection.id}]`;
+    return `[object ${this.constructor.name} ${this.connection.id}]`;
   }
 }
copy from remote/sessions/Session.jsm
copy to remote/sessions/TabSession.jsm
--- a/remote/sessions/Session.jsm
+++ b/remote/sessions/TabSession.jsm
@@ -1,123 +1,93 @@
 /* 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";
 
-var EXPORTED_SYMBOLS = ["Session"];
+var EXPORTED_SYMBOLS = ["TabSession"];
 
-const {ParentProcessDomains} = ChromeUtils.import("chrome://remote/content/domains/ParentProcessDomains.jsm");
 const {Domains} = ChromeUtils.import("chrome://remote/content/domains/Domains.jsm");
-const {formatError} = ChromeUtils.import("chrome://remote/content/Error.jsm");
+const {Session} = ChromeUtils.import("chrome://remote/content/sessions/Session.jsm");
 
-/**
- * A session represents exactly one client WebSocket connection.
- *
- * Every new WebSocket connections is associated with one session that
- * deals with despatching incoming command requests to the right
- * target, sending back responses, and propagating events originating
- * from domains.
- */
-class Session {
-  constructor(connection, target) {
-    this.connection = connection;
-    this.target = target;
+class TabSession extends Session {
+  constructor(connection, target, id) {
+    super(connection, target, id);
 
-    this.connection.onmessage = this.dispatch.bind(this);
-
-    this.domains = new Domains(this, ParentProcessDomains);
     this.mm.addMessageListener("remote:event", this);
     this.mm.addMessageListener("remote:result", this);
     this.mm.addMessageListener("remote:error", this);
 
     this.mm.loadFrameScript("chrome://remote/content/sessions/frame-script.js", false);
   }
 
   destructor() {
-    this.domains.clear();
-    this.connection.onmessage = null;
+    super.destructor();
 
     this.mm.sendAsyncMessage("remote:destroy", {
       browsingContextId: this.browsingContext.id,
     });
 
     this.mm.removeMessageListener("remote:event", this);
     this.mm.removeMessageListener("remote:result", this);
     this.mm.removeMessageListener("remote:error", this);
   }
 
-  async dispatch({id, method, params}) {
+  async onMessage({id, method, params}) {
     try {
       if (typeof id == "undefined") {
         throw new TypeError("Message missing 'id' field");
       }
       if (typeof method == "undefined") {
         throw new TypeError("Message missing 'method' field");
       }
 
       const [domainName, methodName] = Domains.splitMethod(method);
-      if (this.domains.domainSupportsMethod(domainName, methodName)) {
-        await this.executeInParent(id, domainName, methodName, params);
+      if (typeof domainName == "undefined" || typeof methodName == "undefined") {
+        throw new TypeError("'method' field is incorrect and doesn't define a domain " +
+                            "name and method separated by a dot.");
+      }
+      if (this.domains.has(domainName)) {
+        await this.execute(id, domainName, methodName, params);
       } else {
         this.executeInChild(id, domainName, methodName, params);
       }
     } catch (e) {
-      const error = formatError(e, {stack: true});
-      this.connection.send({id, error: { message: error }});
+      this.onError(id, e);
     }
   }
 
-  async executeInParent(id, domain, method, params) {
-    const inst = this.domains.get(domain);
-    const result = await inst[method](params);
-    this.connection.send({id, result});
-  }
-
   executeInChild(id, domain, method, params) {
     this.mm.sendAsyncMessage("remote:request", {
       browsingContextId: this.browsingContext.id,
       request: {id, domain, method, params},
     });
   }
 
   get mm() {
     return this.target.mm;
   }
 
   get browsingContext() {
     return this.target.browsingContext;
   }
 
-  // Domain event listener
-
-  onEvent(eventName, params) {
-    this.connection.send({
-      method: eventName,
-      params,
-    });
-  }
-
   // nsIMessageListener
 
   receiveMessage({name, data}) {
     const {id, result, event, error} = data;
 
     switch (name) {
     case "remote:result":
-      this.connection.send({id, result});
+      this.onResult(id, result);
       break;
 
     case "remote:event":
-      this.connection.send(event);
+      this.onEvent(event.eventName, event.params);
       break;
 
     case "remote:error":
-      this.connection.send({id, error: { message: formatError(error, {stack: true}) }});
+      this.onError(id, error);
       break;
     }
   }
-
-  toString() {
-    return `[object Session ${this.connection.id}]`;
-  }
 }
rename from remote/targets/Target.jsm
rename to remote/targets/TabTarget.jsm
--- a/remote/targets/Target.jsm
+++ b/remote/targets/TabTarget.jsm
@@ -1,34 +1,36 @@
 /* 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";
 
-var EXPORTED_SYMBOLS = ["Target"];
+var EXPORTED_SYMBOLS = ["TabTarget"];
 
 const {Connection} = ChromeUtils.import("chrome://remote/content/Connection.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const {Session} = ChromeUtils.import("chrome://remote/content/sessions/Session.jsm");
+const {TabSession} = ChromeUtils.import("chrome://remote/content/sessions/TabSession.jsm");
 const {WebSocketDebuggerTransport} = ChromeUtils.import("chrome://remote/content/server/WebSocketTransport.jsm");
 const {WebSocketServer} = ChromeUtils.import("chrome://remote/content/server/WebSocket.jsm");
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "Favicons",
     "@mozilla.org/browser/favicon-service;1", "nsIFaviconService");
 
 /**
- * A debugging target.
- *
- * Targets can be a document (page), an OOP frame, a background
- * document, or a worker.  They can all run in dedicated process or frame.
+ * Target for a local tab or a remoted frame.
  */
-class Target {
-  constructor(browser) {
+class TabTarget {
+  /**
+   * @param Targets targets
+   * @param BrowserElement browser
+   */
+  constructor(targets, browser) {
+    this.targets = targets;
     this.browser = browser;
     this.sessions = new Map();
   }
 
   connect() {
     Services.obs.addObserver(this, "message-manager-disconnect");
   }
 
@@ -123,17 +125,17 @@ class Target {
   }
 
   // nsIHttpRequestHandler
 
   async handle(request, response) {
     const so = await WebSocketServer.upgrade(request, response);
     const transport = new WebSocketDebuggerTransport(so);
     const conn = new Connection(transport);
-    this.sessions.set(conn, new Session(conn, this));
+    this.sessions.set(conn, new TabSession(conn, this));
   }
 
   // nsIObserver
 
   observe(subject, topic, data) {
     if (subject === this.mm && subject == "message-manager-disconnect") {
       // disconnect debugging target if <browser> is disconnected,
       // otherwise this is just a host process change
--- a/remote/targets/Targets.jsm
+++ b/remote/targets/Targets.jsm
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["Targets"];
 
 const {EventEmitter} = ChromeUtils.import("resource://gre/modules/EventEmitter.jsm");
 const {MessagePromise} = ChromeUtils.import("chrome://remote/content/Sync.jsm");
-const {Target} = ChromeUtils.import("chrome://remote/content/targets/Target.jsm");
+const {TabTarget} = ChromeUtils.import("chrome://remote/content/targets/TabTarget.jsm");
 const {MainProcessTarget} = ChromeUtils.import("chrome://remote/content/targets/MainProcessTarget.jsm");
 
 class Targets {
   constructor() {
     // browser context ID -> Target<XULElement>
     this._targets = new Map();
 
     EventEmitter.decorate(this);
@@ -24,17 +24,17 @@ class Targets {
     // The tab may just have been created and not fully initialized yet.
     // Target class expects BrowserElement.browsingContext to be defined
     // whereas it is asynchronously set by the custom element class.
     // At least ensure that this property is set before instantiating the target.
     if (!browser.browsingContext) {
       await new MessagePromise(browser.messageManager, "Browser:Init");
     }
 
-    const target = new Target(browser);
+    const target = new TabTarget(this, browser);
     target.connect();
     this._targets.set(target.id, target);
     this.emit("connect", target);
   }
 
   /** @param {BrowserElement} browser */
   disconnect(browser) {
     // Ignore the browsers that haven't had time to initialize.