Bug 933136 - Part 2: Add support to notify 'onpeerready' and 'onpeerlost' events from Chrome process. r=yoshi
authorSiddartha Pothapragada <Siddartha.Pothapragada@telekom.com>
Tue, 03 Dec 2013 13:22:21 -0800
changeset 174548 2a7e01f13840b3f6b7cb783f43a6d53ada0a96aa
parent 174547 404b5d26f2dea26c77300501e94b77167dee293f
child 174549 ca875ea4c03d8ae3d61f2b6eb94e55b9086dd6ac
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyoshi
bugs933136
milestone28.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 933136 - Part 2: Add support to notify 'onpeerready' and 'onpeerlost' events from Chrome process. r=yoshi Add two new P2P IPC messages 'CheckP2PRegistration' and 'NotifyUserAcceptedP2P' to establish User Communication on P2P UI from content process to chrome process. These IPC messages are needed to support basic PeerToPeer use cases.
dom/system/gonk/Nfc.js
dom/system/gonk/NfcContentHelper.js
dom/system/gonk/nfc_consts.js
dom/system/gonk/nsINfcContentHelper.idl
--- a/dom/system/gonk/Nfc.js
+++ b/dom/system/gonk/Nfc.js
@@ -46,16 +46,23 @@ const NFC_IPC_MSG_NAMES = [
   "NFC:ReadNDEF",
   "NFC:WriteNDEF",
   "NFC:GetDetailsNDEF",
   "NFC:MakeReadOnlyNDEF",
   "NFC:Connect",
   "NFC:Close"
 ];
 
+const NFC_IPC_PEER_MSG_NAMES = [
+  "NFC:RegisterPeerTarget",
+  "NFC:UnregisterPeerTarget",
+  "NFC:CheckP2PRegistration",
+  "NFC:NotifyUserAcceptedP2P"
+];
+
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageBroadcaster");
 XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
                                    "@mozilla.org/system-message-internal;1",
                                    "nsISystemMessagesInternal");
 XPCOMUtils.defineLazyServiceGetter(this, "gSystemWorkerManager",
                                    "@mozilla.org/telephony/system-worker-manager;1",
@@ -73,16 +80,20 @@ XPCOMUtils.defineLazyGetter(this, "gMess
 
     nfc: null,
 
     // Manage message targets in terms of sessionToken. Only the authorized and
     // registered contents can receive related messages.
     targetsBySessionTokens: {},
     sessionTokens: [],
 
+    // Manage registered Peer Targets
+    peerTargetsMap: {},
+    currentPeerAppId: null,
+
     init: function init(nfc) {
       this.nfc = nfc;
 
       Services.obs.addObserver(this, "xpcom-shutdown", false);
       this._registerMessageListeners();
     },
 
     _shutdown: function _shutdown() {
@@ -92,23 +103,31 @@ XPCOMUtils.defineLazyGetter(this, "gMess
       this._unregisterMessageListeners();
     },
 
     _registerMessageListeners: function _registerMessageListeners() {
       ppmm.addMessageListener("child-process-shutdown", this);
       for (let msgname of NFC_IPC_MSG_NAMES) {
         ppmm.addMessageListener(msgname, this);
       }
+
+      for (let msgname of NFC_IPC_PEER_MSG_NAMES) {
+        ppmm.addMessageListener(msgname, this);
+      }
     },
 
     _unregisterMessageListeners: function _unregisterMessageListeners() {
       ppmm.removeMessageListener("child-process-shutdown", this);
       for (let msgname of NFC_IPC_MSG_NAMES) {
         ppmm.removeMessageListener(msgname, this);
       }
+
+      for (let msgname of NFC_IPC_PEER_MSG_NAMES) {
+        ppmm.removeMessageListener(msgname, this);
+      }
       ppmm = null;
     },
 
     _registerMessageTarget: function _registerMessageTarget(sessionToken, target) {
       let targets = this.targetsBySessionTokens[sessionToken];
       if (!targets) {
         targets = this.targetsBySessionTokens[sessionToken] = [];
         let list = this.sessionTokens;
@@ -166,16 +185,92 @@ XPCOMUtils.defineLazyGetter(this, "gMess
         return;
       }
 
       for (let target of targets) {
         target.sendAsyncMessage(message, options);
       }
     },
 
+    registerPeerTarget: function registerPeerTarget(msg) {
+      let appInfo = msg.json;
+      // Sanity check on PeerEvent
+      if (!this.isValidPeerEvent(appInfo.event)) {
+        return;
+      }
+      let targets = this.peerTargetsMap;
+      let targetInfo = targets[appInfo.appId];
+      // If the application Id is already registered
+      if (targetInfo) {
+        // If the event is not registered
+        if (targetInfo.event !== appInfo.event) {
+          // Update the event field ONLY
+          targetInfo.event |= appInfo.event;
+        }
+        // Otherwise event is already registered, return!
+        return;
+      }
+      // Target not registered yet! Add to the target map
+
+      // Registered targetInfo target consists of 2 fields (values)
+      // target : Target to notify the right content for peer notifications
+      // event  : NFC_PEER_EVENT_READY (0x01) Or NFC_PEER_EVENT_LOST (0x02)
+      let newTargetInfo = { target : msg.target,
+                            event  : appInfo.event };
+      targets[appInfo.appId] = newTargetInfo;
+    },
+
+    unregisterPeerTarget: function unregisterPeerTarget(msg) {
+      let appInfo = msg.json;
+      // Sanity check on PeerEvent
+      if (!this.isValidPeerEvent(appInfo.event)) {
+        return;
+      }
+      let targets = this.peerTargetsMap;
+      let targetInfo = targets[appInfo.appId];
+      if (targetInfo) {
+        // Application Id registered and the event exactly matches.
+        if (targetInfo.event === appInfo.event) {
+          // Remove the target from the list of registered targets
+          delete targets[appInfo.appId]
+        }
+        else {
+          // Otherwise, update the event field ONLY, by removing the event flag
+          targetInfo.event &= ~appInfo.event;
+        }
+      }
+    },
+
+    isRegisteredP2PTarget: function isRegisteredP2PTarget(appId, event) {
+      let targetInfo = this.peerTargetsMap[appId];
+      // Check if it is a registered target for the 'event'
+      return ((targetInfo != null) && (targetInfo.event & event !== 0));
+    },
+
+    notifyPeerEvent: function notifyPeerEvent(appId, event) {
+      let targetInfo = this.peerTargetsMap[appId];
+      // Check if the application id is a registeredP2PTarget
+      if (this.isRegisteredP2PTarget(appId, event)) {
+        targetInfo.target.sendAsyncMessage("NFC:PeerEvent", {
+          event: event,
+          sessionToken: this.nfc.sessionTokenMap[this.nfc._currentSessionId]
+        });
+        return;
+      }
+      debug("Application ID : " + appId + " is not a registered target" +
+                             "for the event " + event + " notification");
+    },
+
+    isValidPeerEvent: function isValidPeerEvent(event) {
+      // Valid values : 0x01, 0x02 Or 0x03
+      return ((event === NFC.NFC_PEER_EVENT_READY) ||
+              (event === NFC.NFC_PEER_EVENT_LOST)  ||
+              (event === (NFC.NFC_PEER_EVENT_READY | NFC.NFC_PEER_EVENT_LOST)));
+    },
+
     /**
      * nsIMessageListener interface methods.
      */
 
     receiveMessage: function receiveMessage(msg) {
       debug("Received '" + msg.name + "' message from content process");
       if (msg.name == "child-process-shutdown") {
         // By the time we receive child-process-shutdown, the child process has
@@ -186,29 +281,71 @@ XPCOMUtils.defineLazyGetter(this, "gMess
       }
 
       if (NFC_IPC_MSG_NAMES.indexOf(msg.name) != -1) {
         if (!msg.target.assertPermission("nfc-read")) {
           debug("Nfc message " + msg.name +
                 " from a content process with no 'nfc-read' privileges.");
           return null;
         }
+      } else if (NFC_IPC_PEER_MSG_NAMES.indexOf(msg.name) != -1) {
+        if (!msg.target.assertPermission("nfc-write")) {
+          debug("Nfc Peer message  " + msg.name +
+                " from a content process with no 'nfc-write' privileges.");
+          return null;
+        }
+
+        // Add extra permission check for two IPC Peer events:
+        // 'NFC:CheckP2PRegistration' , 'NFC:NotifyUserAcceptedP2P'
+        if ((msg.name == "NFC:CheckP2PRegistration") ||
+            (msg.name == "NFC:NotifyUserAcceptedP2P")) {
+          // ONLY privileged Content can send these two events
+          if (!msg.target.assertPermission("nfc-manager")) {
+            debug("NFC message " + message.name +
+                  " from a content process with no 'nfc-manager' privileges.");
+            return null;
+          }
+        }
       } else {
         debug("Ignoring unknown message type: " + msg.name);
         return null;
       }
 
       switch (msg.name) {
         case "NFC:SetSessionToken":
           this._registerMessageTarget(this.nfc.sessionTokenMap[this.nfc._currentSessionId], msg.target);
           debug("Registering target for this SessionToken : " +
                 this.nfc.sessionTokenMap[this.nfc._currentSessionId]);
-          return null;
+          break;
+        case "NFC:RegisterPeerTarget":
+          this.registerPeerTarget(msg);
+          break;
+        case "NFC:UnregisterPeerTarget":
+          this.unregisterPeerTarget(msg);
+          break;
+        case "NFC:CheckP2PRegistration":
+          // Check if the application id is a valid registered target.
+          // (It should have registered for NFC_PEER_EVENT_READY).
+          let isRegistered = this.isRegisteredP2PTarget(msg.json.appId,
+                                                        NFC.NFC_PEER_EVENT_READY);
+          // Remember the current AppId if registered.
+          this.currentPeerAppId = (isRegistered) ? msg.json.appId : null;
+          let status = (isRegistered) ? NFC.GECKO_NFC_ERROR_SUCCESS :
+                                        NFC.GECKO_NFC_ERROR_GENERIC_FAILURE;
+          // Notify the content process immediately of the status
+          msg.target.sendAsyncMessage(msg.name + "Response", {
+            status: status,
+            requestId: msg.json.requestId
+          });
+          break;
+        case "NFC:NotifyUserAcceptedP2P":
+          // Notify the 'NFC_PEER_EVENT_READY' since user has acknowledged
+          this.notifyPeerEvent(msg.json.appId, NFC.NFC_PEER_EVENT_READY);
+          break;
       }
-
       return null;
     },
 
     /**
      * nsIObserver interface methods.
      */
 
     observe: function observe(subject, topic, data) {
@@ -306,36 +443,43 @@ Nfc.prototype = {
    */
   onmessage: function onmessage(event) {
     let message = event.data;
     debug("Received message from NFC worker: " + JSON.stringify(message));
 
     switch (message.type) {
       case "techDiscovered":
         this._currentSessionId = message.sessionId;
+
         // Check if the session token already exists. If exists, continue to use the same one.
         // If not, generate a new token.
         if (!this.sessionTokenMap[this._currentSessionId]) {
           this.sessionTokenMap[this._currentSessionId] = UUIDGenerator.generateUUID().toString();
         }
         // Update the upper layers with a session token (alias)
         message.sessionToken = this.sessionTokenMap[this._currentSessionId];
         // Do not expose the actual session to the content
         delete message.sessionId;
+
         gSystemMessenger.broadcastMessage("nfc-manager-tech-discovered", message);
         break;
       case "techLost":
         gMessageManager._unregisterMessageTarget(this.sessionTokenMap[this._currentSessionId], null);
+
         // Update the upper layers with a session token (alias)
         message.sessionToken = this.sessionTokenMap[this._currentSessionId];
         // Do not expose the actual session to the content
         delete message.sessionId;
+
         gSystemMessenger.broadcastMessage("nfc-manager-tech-lost", message);
+        // Notify 'PeerLost' to appropriate registered target, if any
+        gMessageManager.notifyPeerEvent(this.currentPeerAppId, NFC.NFC_PEER_EVENT_LOST);
         delete this.sessionTokenMap[this._currentSessionId];
         this._currentSessionId = null;
+        this.currentPeerAppId = null;
         break;
      case "ConfigResponse":
         gSystemMessenger.broadcastMessage("nfc-powerlevel-change", message);
         break;
       case "ConnectResponse": // Fall through.
       case "CloseResponse":
       case "GetDetailsNDEFResponse":
       case "ReadNDEFResponse":
--- a/dom/system/gonk/NfcContentHelper.js
+++ b/dom/system/gonk/NfcContentHelper.js
@@ -42,44 +42,51 @@ const NFCCONTENTHELPER_CID =
   Components.ID("{4d72c120-da5f-11e1-9b23-0800200c9a66}");
 
 const NFC_IPC_MSG_NAMES = [
   "NFC:ReadNDEFResponse",
   "NFC:WriteNDEFResponse",
   "NFC:GetDetailsNDEFResponse",
   "NFC:MakeReadOnlyNDEFResponse",
   "NFC:ConnectResponse",
-  "NFC:CloseResponse"
+  "NFC:CloseResponse",
+  "NFC:CheckP2PRegistrationResponse",
+  "NFC:PeerEvent"
 ];
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsISyncMessageSender");
 
 function NfcContentHelper() {
   this.initDOMRequestHelper(/* aWindow */ null, NFC_IPC_MSG_NAMES);
   Services.obs.addObserver(this, "xpcom-shutdown", false);
 
   this._requestMap = [];
+
+  // Maintains an array of PeerEvent related callbacks, mainly
+  // one for 'peerReady' and another for 'peerLost'.
+  this.peerEventsCallbackMap = {};
 }
 
 NfcContentHelper.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsINfcContentHelper,
                                          Ci.nsISupportsWeakReference,
                                          Ci.nsIObserver]),
   classID:   NFCCONTENTHELPER_CID,
   classInfo: XPCOMUtils.generateCI({
     classID:          NFCCONTENTHELPER_CID,
     classDescription: "NfcContentHelper",
     interfaces:       [Ci.nsINfcContentHelper]
   }),
 
   _requestMap: null,
+  peerEventsCallbackMap: null,
 
   /* TODO: Bug 815526: This is a limitation when a DOMString is used in sequences of Moz DOM Objects.
    *       Strings such as 'type', 'id' 'payload' will not be acccessible to NfcWorker.
    *       Therefore this function exists till the bug is addressed.
    */
   encodeNdefRecords: function encodeNdefRecords(records) {
     let encodedRecords = [];
     for (let i = 0; i < records.length; i++) {
@@ -140,23 +147,21 @@ NfcContentHelper.prototype = {
     return request;
   },
 
   writeNDEF: function writeNDEF(window, records, sessionToken) {
     if (window == null) {
       throw Components.Exception("Can't get window object",
                                   Cr.NS_ERROR_UNEXPECTED);
     }
-
     let request = Services.DOMRequest.createRequest(window);
     let requestId = btoa(this.getRequestId(request));
     this._requestMap[requestId] = window;
 
     let encodedRecords = this.encodeNdefRecords(records);
-
     cpmm.sendAsyncMessage("NFC:WriteNDEF", {
       requestId: requestId,
       sessionToken: sessionToken,
       records: encodedRecords
     });
     return request;
   },
 
@@ -205,16 +210,73 @@ NfcContentHelper.prototype = {
 
     cpmm.sendAsyncMessage("NFC:Close", {
       requestId: requestId,
       sessionToken: sessionToken
     });
     return request;
   },
 
+  registerTargetForPeerEvent: function registerTargetForPeerEvent(window,
+                                                  appId, event, callback) {
+    if (window == null) {
+      throw Components.Exception("Can't get window object",
+                                  Cr.NS_ERROR_UNEXPECTED);
+    }
+    this.peerEventsCallbackMap[event] = callback;
+    cpmm.sendAsyncMessage("NFC:RegisterPeerTarget", {
+      appId: appId,
+      event: event
+    });
+  },
+
+  unregisterTargetForPeerEvent: function unregisterTargetForPeerEvent(window,
+                                                                appId, event) {
+    if (window == null) {
+      throw Components.Exception("Can't get window object",
+                                  Cr.NS_ERROR_UNEXPECTED);
+    }
+    let callback = this.peerEventsCallbackMap[event];
+    if (callback != null) {
+      delete this.peerEventsCallbackMap[event];
+    }
+
+    cpmm.sendAsyncMessage("NFC:UnregisterPeerTarget", {
+      appId: appId,
+      event: event
+    });
+  },
+
+  checkP2PRegistration: function checkP2PRegistration(window, appId) {
+    if (window == null) {
+      throw Components.Exception("Can't get window object",
+                                  Cr.NS_ERROR_UNEXPECTED);
+    }
+    let request = Services.DOMRequest.createRequest(window);
+    let requestId = btoa(this.getRequestId(request));
+    this._requestMap[requestId] = window;
+
+    cpmm.sendAsyncMessage("NFC:CheckP2PRegistration", {
+      appId: appId,
+      requestId: requestId
+    });
+    return request;
+  },
+
+  notifyUserAcceptedP2P: function notifyUserAcceptedP2P(window, appId) {
+    if (window == null) {
+      throw Components.Exception("Can't get window object",
+                                  Cr.NS_ERROR_UNEXPECTED);
+    }
+
+    cpmm.sendAsyncMessage("NFC:NotifyUserAcceptedP2P", {
+      appId: appId
+    });
+  },
+
   // nsIObserver
 
   observe: function observe(subject, topic, data) {
     if (topic == "xpcom-shutdown") {
       this.removeMessageListener();
       Services.obs.removeObserver(this, "xpcom-shutdown");
       cpmm = null;
     }
@@ -254,18 +316,29 @@ NfcContentHelper.prototype = {
       case "NFC:ReadNDEFResponse":
         this.handleReadNDEFResponse(message.json);
         break;
       case "NFC:ConnectResponse": // Fall through.
       case "NFC:CloseResponse":
       case "NFC:WriteNDEFResponse":
       case "NFC:MakeReadOnlyNDEFResponse":
       case "NFC:GetDetailsNDEFResponse":
+      case "NFC:CheckP2PRegistrationResponse":
         this.handleResponse(message.json);
         break;
+      case "NFC:PeerEvent":
+        let callback = this.peerEventsCallbackMap[message.json.event];
+        if (callback) {
+          callback.peerNotification(message.json.event,
+                                    message.json.sessionToken);
+        } else {
+          debug("PeerEvent: No valid callback registered for the event " +
+                message.json.event);
+        }
+        break;
     }
   },
 
   handleReadNDEFResponse: function handleReadNDEFResponse(message) {
     debug("ReadNDEFResponse(" + JSON.stringify(message) + ")");
     let requester = this._requestMap[message.requestId];
     if (!requester) {
        debug("ReadNDEFResponse Invalid requester=" + requester +
--- a/dom/system/gonk/nfc_consts.js
+++ b/dom/system/gonk/nfc_consts.js
@@ -62,10 +62,13 @@ this.NFC_POWER_LEVEL_DISABLED       = 0;
 this.NFC_POWER_LEVEL_LOW            = 1;
 this.NFC_POWER_LEVEL_ENABLED        = 2;
 
 this.TOPIC_MOZSETTINGS_CHANGED      = "mozsettings-changed";
 this.TOPIC_XPCOM_SHUTDOWN           = "xpcom-shutdown";
 this.SETTING_NFC_ENABLED            = "nfc.enabled";
 this.SETTING_NFC_POWER_LEVEL        = "nfc.powerlevel";
 
+this.NFC_PEER_EVENT_READY = 0x01;
+this.NFC_PEER_EVENT_LOST  = 0x02;
+
 // Allow this file to be imported via Components.utils.import().
 this.EXPORTED_SYMBOLS = Object.keys(this);
--- a/dom/system/gonk/nsINfcContentHelper.idl
+++ b/dom/system/gonk/nsINfcContentHelper.idl
@@ -2,21 +2,99 @@
  * 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/. */
 
 #include "nsISupports.idl"
 #include "nsIDOMDOMRequest.idl"
 
 interface nsIVariant;
 
-[scriptable, uuid(28c8f240-da8c-11e1-9b23-0800200c9a66)]
+[scriptable, function, uuid(271f48b0-c884-4f0b-a348-e29824c95168)]
+interface nsINfcPeerCallback : nsISupports
+{
+  /**
+   * Callback function used to notify NFC peer events.
+   *
+   * @param event
+   *        An event indicating 'PeerReady' or 'PeerLost'
+   *        One of NFC_EVENT_PEER_XXXX
+   *
+   * @param sessionToken
+   *        SessionToken received from Chrome process
+   */
+   void peerNotification(in unsigned long event,
+                         in DOMString sessionToken);
+};
+
+[scriptable, uuid(91c2760a-f41c-4174-ad68-614840d4e201)]
 interface nsINfcContentHelper : nsISupports
 {
+  const long NFC_EVENT_PEER_READY = 0x01;
+  const long NFC_EVENT_PEER_LOST  = 0x02;
+
   void setSessionToken(in DOMString sessionToken);
 
   nsIDOMDOMRequest getDetailsNDEF(in nsIDOMWindow window, in DOMString sessionToken);
   nsIDOMDOMRequest readNDEF(in nsIDOMWindow window, in DOMString sessionToken);
   nsIDOMDOMRequest writeNDEF(in nsIDOMWindow window, in nsIVariant records, in DOMString sessionToken);
   nsIDOMDOMRequest makeReadOnlyNDEF(in nsIDOMWindow window, in DOMString sessionToken);
 
   nsIDOMDOMRequest connect(in nsIDOMWindow window, in unsigned long techType, in DOMString sessionToken);
   nsIDOMDOMRequest close(in nsIDOMWindow window, in DOMString sessionToken);
+
+ /**
+  * Register the given application id with Chrome process
+  *
+  * @param window
+  *        Current window
+  *
+  * @param appId
+  *        Application ID to be registered
+  *
+  * @param event
+  *       Event to be registered. Either NFC_EVENT_PEER_READY or NFC_EVENT_PEER_LOST
+  *
+  * @param callback
+  *       Callback that is used to notify upper layers whenever PeerEvents happen.
+  */
+  void registerTargetForPeerEvent(in nsIDOMWindow window,
+                                  in unsigned long appId,
+                                  in octet event,
+                                  in nsINfcPeerCallback callback);
+ /**
+  * Unregister the given application id with Chrome process
+  *
+  * @param window
+  *        Current window
+  *
+  * @param appId
+  *        Application ID to be registered
+  *
+  * @param event
+  *       Event to be unregistered. Either NFC_EVENT_PEER_READY or NFC_EVENT_PEER_LOST
+  */
+  void unregisterTargetForPeerEvent(in nsIDOMWindow window,
+                                    in unsigned long appId,
+                                    in octet event);
+ /**
+  * Checks if the given application's id is a registered peer target (with the Chrome process)
+  *
+  * @param window
+  *        Current window
+  *
+  * @param appId
+  *        Application ID to be updated with Chrome process
+  *
+  * Returns DOMRequest, if appId is registered then 'onsuccess' is called else 'onerror'
+  */
+  nsIDOMDOMRequest checkP2PRegistration(in nsIDOMWindow window, in unsigned long appId);
+
+ /**
+  * Notify the Chrome process that user has accepted to share nfc message on P2P UI
+  *
+  * @param window
+  *        Current window
+  *
+  * @param appId
+  *        Application ID that is capable of handling NFC_EVENT_PEER_READY event
+  */
+  void notifyUserAcceptedP2P(in nsIDOMWindow window, in unsigned long appId);
 };