Bug 1299411 - Decouple Port implementation from API draft
authorRob Wu <rob@robwu.nl>
Sat, 24 Sep 2016 11:16:32 +0200
changeset 429203 488311924964a6a0de79665371ebe5fdc95de261
parent 429179 c6ccd71126ff514bfc44b53e2217562e29a0cc38
child 429204 b73635f95a04da1fe572fade4a2e00322c7bb521
push id33512
push userbmo:rob@robwu.nl
push dateTue, 25 Oct 2016 14:01:58 +0000
bugs1299411
milestone52.0a1
Bug 1299411 - Decouple Port implementation from API 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,
 };