Bug 780707 - Contacts API: support prompting. r=dougt
authorGregor Wagner <anygregor@gmail.com>
Thu, 09 Aug 2012 11:34:57 -0700
changeset 107455 a96d79dd9d2c629974eda8949f5ea69ae16dc589
parent 107454 10237eeb36c43106e62ff36967dd9a12d0b3b519
child 107456 8b8c307c955c5b870f8463ed05cd45d405ef4148
push id1490
push userakeybl@mozilla.com
push dateMon, 08 Oct 2012 18:29:50 +0000
treeherdermozilla-beta@f335e7dacdc1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdougt
bugs780707
milestone17.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 780707 - Contacts API: support prompting. r=dougt
dom/Makefile.in
dom/contacts/ContactManager.js
dom/contacts/fallback/ContactDB.jsm
dom/contacts/fallback/ContactService.jsm
dom/contacts/tests/test_contacts_basics.html
dom/permission/Makefile.in
dom/permission/PermissionPromptHelper.jsm
--- a/dom/Makefile.in
+++ b/dom/Makefile.in
@@ -58,16 +58,17 @@ DIRS += \
   messages \
   power \
   settings \
   sms \
   mms \
   src \
   locales \
   network \
+  permission \
   plugins/base \
   plugins/ipc \
   indexedDB \
   system \
   ipc \
   identity \
   workers \
   camera \
--- a/dom/contacts/ContactManager.js
+++ b/dom/contacts/ContactManager.js
@@ -13,16 +13,17 @@ else
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
+Cu.import("resource://gre/modules/PermissionPromptHelper.jsm");
 
 XPCOMUtils.defineLazyGetter(Services, "DOMRequest", function() {
   return Cc["@mozilla.org/dom/dom-request-service;1"].getService(Ci.nsIDOMRequestService);
 });
 
 XPCOMUtils.defineLazyGetter(this, "cpmm", function() {
   return Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
 });
@@ -272,90 +273,30 @@ function ContactManager()
   debug("Constructor");
 }
 
 ContactManager.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
   _oncontactchange: null,
 
   set oncontactchange(aCallback) {
-    if (this.hasPrivileges)
+    debug("set oncontactchange");
+    let allowCallback = function() {
       this._oncontactchange = aCallback;
-    else
+    }.bind(this);
+    let cancelCallback = function() {
       throw Components.results.NS_ERROR_FAILURE;
+    }
+    this.askPermission("listen", null, allowCallback, cancelCallback);
   },
 
   get oncontactchange() {
     return this._oncontactchange;
   },
 
-  save: function save(aContact) {
-    let request;
-    if (this.hasPrivileges) {
-      debug("save: " + JSON.stringify(aContact) + " :" + aContact.id);
-      let newContact = {};
-      newContact.properties = {
-        name:            [],
-        honorificPrefix: [],
-        givenName:       [],
-        additionalName:  [],
-        familyName:      [],
-        honorificSuffix: [],
-        nickname:        [],
-        email:           [],
-        photo:           [],
-        url:             [],
-        category:        [],
-        adr:             [],
-        tel:             [],
-        org:             [],
-        jobTitle:        [],
-        bday:            null,
-        note:            [],
-        impp:            [],
-        anniversary:     null,
-        sex:             null,
-        genderIdentity:  null
-      };
-      for (let field in newContact.properties)
-        newContact.properties[field] = aContact[field];
-
-      let reason;
-      if (aContact.id == "undefined") {
-        // for example {25c00f01-90e5-c545-b4d4-21E2ddbab9e0} becomes
-        // 25c00f0190e5c545b4d421E2ddbab9e0
-        aContact.id = this._getRandomId().replace('-', '', 'g').replace('{', '').replace('}', '');
-        reason = "create";
-      } else {
-        reason = "update";
-      }
-
-      this._setMetaData(newContact, aContact);
-      debug("send: " + JSON.stringify(newContact));
-      request = this.createRequest();
-      cpmm.sendAsyncMessage("Contact:Save", {contact: newContact,
-                                             requestID: this.getRequestId({request: request, reason: reason })});
-      return request;
-    } else {
-      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
-    }
-  },
-
-  remove: function removeContact(aRecord) {
-    let request;
-    if (this.hasPrivileges) {
-      request = this.createRequest();
-      cpmm.sendAsyncMessage("Contact:Remove", {id: aRecord.id,
-                                               requestID: this.getRequestId({request: request, reason: "remove"})});
-      return request;
-    } else {
-      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
-    }
-  },
-
   _setMetaData: function(aNewContact, aRecord) {
     aNewContact.id = aRecord.id;
     aNewContact.published = aRecord.published;
     aNewContact.updated = aRecord.updated;
   },
 
   _convertContactsArray: function(aContacts) {
     let contacts = new Array();
@@ -403,81 +344,182 @@ ContactManager.prototype = {
       case "Contacts:Find:Return:KO":
       case "Contact:Save:Return:KO":
       case "Contact:Remove:Return:KO":
       case "Contacts:Clear:Return:KO":
         req = this.getRequest(msg.requestID);
         if (req)
           Services.DOMRequest.fireError(req.request, msg.errorMsg);
         break;
+      case "PermissionPromptHelper:AskPermission:OK":
+        debug("id: " + msg.requestID);
+        req = this.getRequest(msg.requestID);
+        if (!req) {
+          break;
+        }
+
+        if (msg.result == Ci.nsIPermissionManager.ALLOW_ACTION) {
+          req.allow();
+        } else {
+          req.cancel();
+        }
+        break;
       default: 
         debug("Wrong message: " + aMessage.name);
     }
     this.removeRequest(msg.requestID);
   },
 
-  find: function(aOptions) {
+  askPermission: function (aAccess, aReqeust, aAllowCallback, aCancelCallback) {
+    debug("askPermission for contacts");
+    let requestID = this.getRequestId({
+      request: aReqeust,
+      allow: function() {
+        aAllowCallback();
+      }.bind(this),
+      cancel : function() {
+        if (aCancelCallback) {
+          aCancelCallback()
+        } else if (request) {
+          Services.DOMRequest.fireError(request, "Not Allowed");
+        }
+      }.bind(this)
+    });
+
+    let principal = this._window.document.nodePrincipal;
+    cpmm.sendAsyncMessage("PermissionPromptHelper:AskPermission", {
+      type: "contacts",
+      access: aAccess,
+      requestID: requestID,
+      origin: principal.origin,
+      appID: principal.appId,
+      browserFlag: principal.isInBrowserElement
+    });
+  },
+
+  save: function save(aContact) {
     let request;
-    if (this.hasPrivileges) {
-      request = this.createRequest();
-      cpmm.sendAsyncMessage("Contacts:Find", {findOptions: aOptions, 
-                                              requestID: this.getRequestId({request: request, reason: "find"})});
-      return request;
+    debug("save: " + JSON.stringify(aContact) + " :" + aContact.id);
+    let newContact = {};
+    newContact.properties = {
+      name:            [],
+      honorificPrefix: [],
+      givenName:       [],
+      additionalName:  [],
+      familyName:      [],
+      honorificSuffix: [],
+      nickname:        [],
+      email:           [],
+      photo:           [],
+      url:             [],
+      category:        [],
+      adr:             [],
+      tel:             [],
+      org:             [],
+      jobTitle:        [],
+      bday:            null,
+      note:            [],
+      impp:            [],
+      anniversary:     null,
+      sex:             null,
+      genderIdentity:  null
+    };
+    for (let field in newContact.properties) {
+      newContact.properties[field] = aContact[field];
+    }
+
+    let reason;
+    if (aContact.id == "undefined") {
+      // for example {25c00f01-90e5-c545-b4d4-21E2ddbab9e0} becomes
+      // 25c00f0190e5c545b4d421E2ddbab9e0
+      aContact.id = this._getRandomId().replace('-', '', 'g').replace('{', '').replace('}', '');
+      reason = "create";
     } else {
-      debug("find not allowed");
-      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+      reason = "update";
     }
+
+    this._setMetaData(newContact, aContact);
+    debug("send: " + JSON.stringify(newContact));
+    request = this.createRequest();
+    let options = { contact: newContact };
+    let allowCallback = function() {
+      cpmm.sendAsyncMessage("Contact:Save", {requestID: this.getRequestId({request: request, reason: reason}), options: options});
+    }.bind(this)
+    this.askPermission(reason, request, allowCallback);
+    return request;
+  },
+
+  find: function(aOptions) {
+    debug("find! " + JSON.stringify(aOptions));
+    let request;
+    request = this.createRequest();
+    let options = { findOptions: aOptions };
+    let allowCallback = function() {
+      cpmm.sendAsyncMessage("Contacts:Find", {requestID: this.getRequestId({request: request, reason: "find"}), options: options});
+    }.bind(this)
+    this.askPermission("find", request, allowCallback);
+    return request;
+  },
+
+  remove: function removeContact(aRecord) {
+    let request;
+    request = this.createRequest();
+    let options = { id: aRecord.id };
+    let allowCallback = function() {
+      cpmm.sendAsyncMessage("Contact:Remove", {requestID: this.getRequestId({request: request, reason: "remove"}), options: options});
+    }.bind(this)
+    this.askPermission("remove", request, allowCallback);
+    return request;
   },
 
   clear: function() {
+    debug("clear");
     let request;
-    if (this.hasPrivileges) {
-      request = this.createRequest();
-      cpmm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId({request: request, reason: "remove"})});
-      return request;
-    } else {
-      debug("clear not allowed");
-      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
-    }
+    request = this.createRequest();
+    let options = {};
+    let allowCallback = function() {
+      cpmm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId({request: request, reason: "remove"}), options: options});
+    }.bind(this)
+    this.askPermission("remove", request, allowCallback);
+    return request;
   },
 
   getSimContacts: function(aType) {
     let request;
-    if (this.hasPrivileges) {
+    request = this.createRequest();
+
+    let allowCallback = function() {
       let callback = function(aType, aContacts) {
         debug("got SIM contacts: " + aType + " " + JSON.stringify(aContacts));
         let result = aContacts.map(function(c) { return { name: [c.alphaId], tel: [c.number] } });
         debug("result: " + JSON.stringify(result));
         Services.DOMRequest.fireSuccess(request, result);
       };
       debug("getSimContacts " + aType);
-      request = this.createRequest();
+
       mRIL.getICCContacts(aType, callback);
-      return request;
-    } else {
-      debug("getSimContacts not allowed");
+    }.bind(this);
+
+    let cancelCallback = function() {
       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
     }
+    this.askPermission("getSimContacts", request, allowCallback, cancelCallback);
+    return request;
   },
 
   init: function(aWindow) {
     // Set navigator.mozContacts to null.
     if (!Services.prefs.getBoolPref("dom.mozContacts.enabled"))
       return null;
 
     this.initHelper(aWindow, ["Contacts:Find:Return:OK", "Contacts:Find:Return:KO",
                               "Contacts:Clear:Return:OK", "Contacts:Clear:Return:KO",
                               "Contact:Save:Return:OK", "Contact:Save:Return:KO",
-                              "Contact:Remove:Return:OK", "Contact:Remove:Return:KO"]);
-
-    let perm = Services.perms.testExactPermissionFromPrincipal(aWindow.document.nodePrincipal, "contacts");
- 
-    //only pages with perm set can use the contacts
-    this.hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
-    debug("Contacts permission: " + this.hasPrivileges);
+                              "Contact:Remove:Return:OK", "Contact:Remove:Return:KO",
+                              "PermissionPromptHelper:AskPermission:OK"]);
   },
 
   // Called from DOMRequestIpcHelper
   uninit: function uninit() {
     debug("uninit call");
     if (this._oncontactchange)
       this._oncontactchange = null;
   },
--- a/dom/contacts/fallback/ContactDB.jsm
+++ b/dom/contacts/fallback/ContactDB.jsm
@@ -297,17 +297,16 @@ ContactDB.prototype = {
    *        Object specifying search options. Possible attributes:
    *        - filterBy
    *        - filterOp
    *        - filterValue
    *        - count
    */
   find: function find(aSuccessCb, aFailureCb, aOptions) {
     debug("ContactDB:find val:" + aOptions.filterValue + " by: " + aOptions.filterBy + " op: " + aOptions.filterOp + "\n");
-
     let self = this;
     this.newTxn("readonly", function (txn, store) {
       if (aOptions && (aOptions.filterOp == "equals" || aOptions.filterOp == "contains")) {
         self._findWithIndex(txn, store, aOptions);
       } else {
         self._findAll(txn, store, aOptions);
       }
     }, aSuccessCb, aFailureCb);
--- a/dom/contacts/fallback/ContactService.jsm
+++ b/dom/contacts/fallback/ContactService.jsm
@@ -53,29 +53,31 @@ let DOMContactManager = {
     if (this._db)
       this._db.close();
     this._db = null;
   },
 
   receiveMessage: function(aMessage) {
     debug("Fallback DOMContactManager::receiveMessage " + aMessage.name);
     let mm = aMessage.target.QueryInterface(Ci.nsIFrameMessageManager);
-    let msg = aMessage.json;
+    let msg = aMessage.data;
 
     /*
      * Sorting the contacts by sortBy field. sortBy can either be familyName or givenName.
      * If 2 entries have the same sortyBy field or no sortBy field is present, we continue 
      * sorting with the other sortyBy field.
      */
     function sortfunction(a, b){
       let x, y;
       let sortByNameSet = true;
       let result = 0;
-      let sortBy = msg.findOptions.sortBy;
-      let sortOrder = msg.findOptions.sortOrder;
+      let findOptions = msg.options.findOptions;
+      let sortBy = findOptions.sortBy;
+      let sortOrder = findOptions.sortOrder;
+      
       if (!a.properties[sortBy] || !(x = a.properties[sortBy][0].toLowerCase())) {
         sortByNameSet = false;
       }
 
       if (!b.properties[sortBy] || !(y = b.properties[sortBy][0].toLowerCase())) {
         if (sortByNameSet) {
           return sortOrder == 'ascending' ? 1 : -1;
         }
@@ -102,45 +104,50 @@ let DOMContactManager = {
 
     switch (aMessage.name) {
       case "Contacts:Find":
         let result = new Array();
         this._db.find(
           function(contacts) {
             for (let i in contacts)
               result.push(contacts[i]);
-            if (msg.findOptions.sortOrder !== 'undefined' && msg.findOptions.sortBy !== 'undefined') {
-              debug('sortBy: ' + msg.findOptions.sortBy + ', sortOrder: ' + msg.findOptions.sortOrder );
-              result.sort(sortfunction);
-              if (msg.findOptions.filterLimit)
-                result = result.slice(0, msg.findOptions.filterLimit);
+            if (msg.options && msg.options.findOptions) {
+              let findOptions = msg.options.findOptions;
+              if (findOptions.sortOrder !== 'undefined' && findOptions.sortBy !== 'undefined') {
+                debug('sortBy: ' + findOptions.sortBy + ', sortOrder: ' + findOptions.sortOrder );
+                result.sort(sortfunction);
+                if (findOptions.filterLimit)
+                  result = result.slice(0, findOptions.filterLimit);
+              }
             }
 
             debug("result:" + JSON.stringify(result));
             mm.sendAsyncMessage("Contacts:Find:Return:OK", {requestID: msg.requestID, contacts: result});
           }.bind(this),
           function(aErrorMsg) { mm.sendAsyncMessage("Contacts:Find:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }) }.bind(this),
-          msg.findOptions);
+          msg.options.findOptions);
         break;
       case "Contact:Save":
         this._db.saveContact(
-          msg.contact, 
-          function() { mm.sendAsyncMessage("Contact:Save:Return:OK", { requestID: msg.requestID, contactID: msg.contact.id }); }.bind(this),
+          msg.options.contact,
+          function() { mm.sendAsyncMessage("Contact:Save:Return:OK", { requestID: msg.requestID, contactID: msg.options.contact.id }); }.bind(this),
           function(aErrorMsg) { mm.sendAsyncMessage("Contact:Save:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)
         );
         break;
       case "Contact:Remove":
         this._db.removeContact(
-          msg.id, 
-          function() { mm.sendAsyncMessage("Contact:Remove:Return:OK", { requestID: msg.requestID, contactID: msg.id }); }.bind(this),
+          msg.options.id,
+          function() { mm.sendAsyncMessage("Contact:Remove:Return:OK", { requestID: msg.requestID, contactID: msg.options.id }); }.bind(this),
           function(aErrorMsg) { mm.sendAsyncMessage("Contact:Remove:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)
         );
         break;
       case "Contacts:Clear":
         this._db.clear(
           function() { mm.sendAsyncMessage("Contacts:Clear:Return:OK", { requestID: msg.requestID }); }.bind(this),
           function(aErrorMsg) { mm.sendAsyncMessage("Contacts:Clear:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)
         );
+      default:
+        debug("WRONG MESSAGE NAME: " + aMessage.name);
     }
   }
 }
 
 DOMContactManager.init();
--- a/dom/contacts/tests/test_contacts_basics.html
+++ b/dom/contacts/tests/test_contacts_basics.html
@@ -401,31 +401,31 @@ var steps = [
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding a new contact with properties1");
     createResult1 = new mozContact();
     createResult1.init(properties1);
+    mozContacts.oncontactchange = null;
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       checkContacts(properties1, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring tel1");
     var options = {filterBy: ["tel"],
                    filterOp: "contains",
                    filterValue: properties1.tel[1].number.substring(1,5)};
-    mozContacts.oncontactchange = null;
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, properties1);
       next();
     };
new file mode 100644
--- /dev/null
+++ b/dom/permission/Makefile.in
@@ -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/.
+
+DEPTH = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE         = dom
+LIBRARY_NAME   = jsdompermission_s
+
+XPIDL_MODULE   = dom_permission
+GRE_MODULE     = 1
+
+EXTRA_JS_MODULES =   \
+  PermissionPromptHelper.jsm \
+  $(NULL)
+
+include $(topsrcdir)/config/config.mk
+include $(topsrcdir)/ipc/chromium/chromium-config.mk
+include $(topsrcdir)/config/rules.mk
+
+DEFINES += -D_IMPL_NS_LAYOUT
new file mode 100644
--- /dev/null
+++ b/dom/permission/PermissionPromptHelper.jsm
@@ -0,0 +1,97 @@
+/* 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/. */
+
+/* PermissionPromptHelper checks the permissionDB for a given permission
+ * name and performs prompting if needed.
+ * Usage: send PermissionPromptHelper:AskPermission via the FrameMessageManager with:
+ * |origin|, |appID|, |browserFlag| -> used for getting the principal and
+ * |type| and |access| to call testExactPermissionFromPrincipal.
+ * Note that |access| isn't currently used.
+ * Other arugments are:
+ * requestID: ID that gets returned with the result message.
+ *
+ * Once the permission is checked, it returns with the message
+ * "PermissionPromptHelper:AskPermission:OK"
+ * The result contains the |result| e.g.Ci.nsIPermissionManager.ALLOW_ACTION
+ * and a requestID that
+ */
+
+"use strict";
+
+let DEBUG = 0;
+if (DEBUG)
+  debug = function (s) { dump("-*- Permission Prompt Helper component: " + s + "\n"); }
+else
+  debug = function (s) {}
+
+const Cu = Components.utils; 
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+let EXPORTED_SYMBOLS = ["PermissionPromptHelper"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
+  return Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
+});
+
+var permissionManager = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
+var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
+var appsService = Cc["@mozilla.org/AppsService;1"].getService(Ci.nsIAppsService);
+
+let PermissionPromptHelper = {
+  init: function() {
+    debug("Init");
+    ppmm.addMessageListener("PermissionPromptHelper:AskPermission", this);
+    Services.obs.addObserver(this, "profile-before-change", false);
+  },
+
+  askPermission: function(aMessage, aCallbacks) {
+    let msg = aMessage.json;
+    debug("askPerm: " + JSON.stringify(aMessage.json));
+    let uri = Services.io.newURI(msg.origin, null, null);
+    let principal = secMan.getAppCodebasePrincipal(uri, msg.appID, msg.browserFlag);
+    let perm = permissionManager.testExactPermissionFromPrincipal(principal, msg.type);
+
+    switch(perm) {
+      case Ci.nsIPermissionManager.ALLOW_ACTION:
+        aCallbacks.allow();
+        return;
+      case Ci.nsIPermissionManager.DENY_ACTION:
+        aCallbacks.cancel();
+        return;
+    }
+
+  // FIXXMEE PROMPT MAGIC! Bug 773114.
+  // We have to diplay the prompt here.
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    ppmm.removeMessageListener("PermissionPromptHelper:AskPermission", this);
+    Services.obs.removeObserver(this, "profile-before-change");
+    ppmm = null;
+  },
+
+  receiveMessage: function(aMessage) {
+    debug("PermissionPromptHelper::receiveMessage " + aMessage.name);
+    let mm = aMessage.target.QueryInterface(Ci.nsIFrameMessageManager);
+    let msg = aMessage.data;
+
+    let result;
+    if (aMessage.name == "PermissionPromptHelper:AskPermission") {
+      this.askPermission(aMessage, {
+        cancel: function() {
+          mm.sendAsyncMessage("PermissionPromptHelper:AskPermission:OK", {result: Ci.nsIPermissionManager.DENY_ACTION, requestID: msg.requestID});
+        },
+        allow: function() {
+          mm.sendAsyncMessage("PermissionPromptHelper:AskPermission:OK", {result: Ci.nsIPermissionManager.ALLOW_ACTION, requestID: msg.requestID});
+        }
+      });
+    }
+  }
+}
+
+PermissionPromptHelper.init();