Bug 1299411 - Decouple Port implementation from API r=aswan
authorRob Wu <rob@robwu.nl>
Sat, 24 Sep 2016 11:16:32 +0200
changeset 320058 a41f871e2d1b37754bbd1001c36c075511b49342
parent 320057 657c36b85c688b9f77a79f796157edf87bc75a86
child 320059 502aaf0691ccecb5988d719cf904fb52c992dce3
push id20749
push userryanvm@gmail.com
push dateSat, 29 Oct 2016 13:21:21 +0000
treeherderfx-team@1b170b39ed6b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1299411
milestone52.0a1
Bug 1299411 - Decouple Port implementation from API r=aswan Decoupled the API from the implementation. From now on it is possible to create Port instances without generating an API. This allows us to internally use Ports to pass around messages with minimal overhead (in the form of unnecessary clones of messages). This will be used by native messaging. This commit has no behavioral change, it is mostly moving around some code and storing the internal message listener in a set. MozReview-Commit-ID: 4h0LNJvTH9R
toolkit/components/extensions/ExtensionUtils.jsm
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -1219,16 +1219,17 @@ function Port(context, senderMM, receive
   this.senderMM = senderMM;
   this.receiverMMs = receiverMMs;
   this.name = name;
   this.id = id;
   this.sender = sender;
   this.recipient = recipient;
   this.disconnected = false;
   this.disconnectListeners = new Set();
+  this.unregisterMessageFuncs = new Set();
 
   // Common options for onMessage and onDisconnect.
   this.handlerBase = {
     messageFilterStrict: {portId: id},
     filterMessage: (sender, recipient) => {
       if (!sender.contextId) {
         Cu.reportError("Missing sender.contextId in message to Port");
         return false;
@@ -1249,69 +1250,103 @@ Port.prototype = {
     let portObj = Cu.createObjectIn(this.context.cloneScope);
 
     let publicAPI = {
       name: this.name,
       disconnect: () => {
         this.disconnect();
       },
       postMessage: json => {
-        if (this.disconnected) {
-          throw new this.context.cloneScope.Error("Attempt to postMessage on disconnected port");
-        }
-
-        this._sendMessage("Extension:Port:PostMessage", json);
+        this.postMessage(json);
       },
       onDisconnect: new EventManager(this.context, "Port.onDisconnect", fire => {
-        let listener = () => {
-          if (this.context.active && !this.disconnected) {
-            fire.withoutClone(portObj);
-          }
-        };
-
-        this.disconnectListeners.add(listener);
-        return () => {
-          this.disconnectListeners.delete(listener);
-        };
+        return this.registerOnDisconnect(() => fire.withoutClone(portObj));
       }).api(),
       onMessage: new EventManager(this.context, "Port.onMessage", fire => {
-        let handler = Object.assign({
-          receiveMessage: ({data}) => {
-            if (this.context.active && !this.disconnected) {
-              fire(data);
-            }
-          },
-        }, this.handlerBase);
-
-        MessageChannel.addListener(this.receiverMMs, "Extension:Port:PostMessage", handler);
-        return () => {
-          MessageChannel.removeListener(this.receiverMMs, "Extension:Port:PostMessage", handler);
-        };
+        return this.registerOnMessage(msg => {
+          fire(msg);
+        });
       }).api(),
     };
 
     if (this.sender) {
       publicAPI.sender = this.sender;
     }
 
     injectAPI(publicAPI, portObj);
     return portObj;
   },
 
+  postMessage(json) {
+    if (this.disconnected) {
+      throw new this.context.cloneScope.Error("Attempt to postMessage on disconnected port");
+    }
+
+    this._sendMessage("Extension:Port:PostMessage", json);
+  },
+
+  /**
+   * Register a callback that is called when the port is disconnected by the
+   * *other* end. The callback is automatically unregistered when the port or
+   * context is closed.
+   *
+   * @param {function} callback Called when the other end disconnects the port.
+   * @returns {function} Function to unregister the listener.
+   */
+  registerOnDisconnect(callback) {
+    let listener = () => {
+      if (this.context.active && !this.disconnected) {
+        callback();
+      }
+    };
+    this.disconnectListeners.add(listener);
+    return () => {
+      this.disconnectListeners.delete(listener);
+    };
+  },
+
+  /**
+   * Register a callback that is called when a message is received. The callback
+   * is automatically unregistered when the port or context is closed.
+   *
+   * @param {function} callback Called when a message is received.
+   * @returns {function} Function to unregister the listener.
+   */
+  registerOnMessage(callback) {
+    let handler = Object.assign({
+      receiveMessage: ({data}) => {
+        if (this.context.active && !this.disconnected) {
+          callback(data);
+        }
+      },
+    }, this.handlerBase);
+
+    let unregister = () => {
+      this.unregisterMessageFuncs.delete(unregister);
+      MessageChannel.removeListener(this.receiverMMs, "Extension:Port:PostMessage", handler);
+    };
+    MessageChannel.addListener(this.receiverMMs, "Extension:Port:PostMessage", handler);
+    this.unregisterMessageFuncs.add(unregister);
+    return unregister;
+  },
+
   _sendMessage(message, data) {
     let options = {
       recipient: Object.assign({}, this.recipient, {portId: this.id}),
       responseType: MessageChannel.RESPONSE_NONE,
     };
 
     return this.context.sendMessage(this.senderMM, message, data, options);
   },
 
   handleDisconnection() {
     MessageChannel.removeListener(this.receiverMMs, "Extension:Port:Disconnect", this.disconnectHandler);
+    for (let unregister of this.unregisterMessageFuncs) {
+      unregister();
+    }
     this.context.forgetOnClose(this);
     this.disconnected = true;
   },
 
   disconnectByOtherEnd() {
     if (this.disconnected) {
       return;
     }
@@ -2117,15 +2152,16 @@ this.ExtensionUtils = {
   BaseContext,
   DefaultWeakMap,
   EventEmitter,
   EventManager,
   IconDetails,
   LocalAPIImplementation,
   LocaleData,
   Messenger,
+  Port,
   PlatformInfo,
   SchemaAPIInterface,
   SingletonEventManager,
   SpreadArgs,
   ChildAPIManager,
   SchemaAPIManager,
 };