Bug 898949 - Part 1, add optios in permission prompt for Video/Audio device selection. r=fabrice,khuey
authorShih-Chiang Chien <schien@mozilla.com>
Tue, 10 Sep 2013 17:41:59 +0200
changeset 170345 8f7defebd30a011617ee18277ee0daf7da968417
parent 170344 edb399194be952c8ab917eb5132e55dd9c0f3c5d
child 170346 ad7684d2890e8210fdf1aaf3aec6d82e05a77191
push idunknown
push userunknown
push dateunknown
reviewersfabrice, khuey
bugs898949
milestone30.0a1
Bug 898949 - Part 1, add optios in permission prompt for Video/Audio device selection. r=fabrice,khuey
b2g/components/ContentPermissionPrompt.js
content/base/src/nsDocument.cpp
dom/base/nsContentPermissionHelper.cpp
dom/base/nsContentPermissionHelper.h
dom/devicestorage/nsDeviceStorage.cpp
dom/devicestorage/nsDeviceStorage.h
dom/interfaces/base/nsIContentPermissionPrompt.idl
dom/ipc/PContentPermission.ipdlh
dom/ipc/PContentPermissionRequest.ipdl
dom/permission/PermissionPromptHelper.jsm
dom/src/geolocation/nsGeolocation.cpp
dom/src/notification/DesktopNotification.cpp
dom/src/notification/Notification.cpp
--- a/b2g/components/ContentPermissionPrompt.js
+++ b/b2g/components/ContentPermissionPrompt.js
@@ -217,21 +217,30 @@ ContentPermissionPrompt.prototype = {
     let typesInfo = [];
     let perms = request.types.QueryInterface(Ci.nsIArray);
     for (let idx = 0; idx < perms.length; idx++) {
       let perm = perms.queryElementAt(idx, Ci.nsIContentPermissionType);
       let tmp = {
         permission: perm.type,
         access: (perm.access && perm.access !== "unused") ?
                   perm.type + "-" + perm.access : perm.type,
+        options: [],
         deny: true,
         action: Ci.nsIPermissionManager.UNKNOWN_ACTION
       };
+
+      // Append available options, if any.
+      let options = perm.options.QueryInterface(Ci.nsIArray);
+      for (let i = 0; i < options.length; i++) {
+        let option = options.queryElementAt(i, Ci.nsISupportsString).data;
+        tmp.options.push(option);
+      }
       typesInfo.push(tmp);
     }
+
     if (typesInfo.length == 0) {
       request.cancel();
       return;
     }
 
     if(!this.checkMultipleRequest(typesInfo)) {
       request.cancel();
       return;
@@ -304,23 +313,23 @@ ContentPermissionPrompt.prototype = {
   cancelPrompt: function(request, requestId, typesInfo) {
     this.sendToBrowserWindow("cancel-permission-prompt", request, requestId,
                              typesInfo);
   },
 
   delegatePrompt: function(request, requestId, typesInfo, callback) {
 
     this.sendToBrowserWindow("permission-prompt", request, requestId, typesInfo,
-                             function(type, remember) {
+                             function(type, remember, choices) {
       if (type == "permission-allow") {
         rememberPermission(typesInfo, request.principal, !remember);
         if (callback) {
           callback();
         }
-        request.allow();
+        request.allow(choices);
         return;
       }
 
       let addDenyPermission = function(type) {
         debug("add " + type.permission +
               " to permission manager with DENY_ACTION");
         if (remember) {
           Services.perms.addFromPrincipal(request.principal, type.access,
@@ -349,30 +358,30 @@ ContentPermissionPrompt.prototype = {
 
     if (callback) {
       content.addEventListener("mozContentEvent", function contentEvent(evt) {
         let detail = evt.detail;
         if (detail.id != requestId)
           return;
         evt.target.removeEventListener(evt.type, contentEvent);
 
-        callback(detail.type, detail.remember);
+        callback(detail.type, detail.remember, detail.choices);
       })
     }
 
     let principal = request.principal;
     let isApp = principal.appStatus != Ci.nsIPrincipal.APP_STATUS_NOT_INSTALLED;
     let remember = (principal.appStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
                     principal.appStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED)
                     ? true
                     : request.remember;
     let permissions = {};
     for (let i in typesInfo) {
       debug("prompt " + typesInfo[i].permission);
-      permissions[typesInfo[i].permission] = [];
+      permissions[typesInfo[i].permission] = typesInfo[i].options;
     }
 
     let details = {
       type: type,
       permissions: permissions,
       id: requestId,
       origin: principal.origin,
       isApp: isApp,
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -11313,17 +11313,17 @@ public:
     nsDocument* doc = static_cast<nsDocument*>(d.get());
     if (doc->mAsyncFullscreenPending ||
         (doc->mHasFullscreenApprovedObserver && !doc->mIsApprovedForFullscreen)) {
       // We're still waiting for approval.
       return NS_OK;
     }
 
     if (doc->mIsApprovedForFullscreen || doc->mAllowRelocking) {
-      Allow();
+      Allow(JS::UndefinedHandleValue);
       return NS_OK;
     }
 
     // In non-fullscreen mode user input (or chrome caller) is required!
     // Also, don't let the page to try to get the permission too many times.
     if (!mUserInputOrChromeCaller ||
         doc->mCancelledPointerLockRequests > 2) {
       Handled();
@@ -11358,18 +11358,20 @@ public:
 
 NS_IMPL_ISUPPORTS_INHERITED1(nsPointerLockPermissionRequest,
                              nsRunnable,
                              nsIContentPermissionRequest)
 
 NS_IMETHODIMP
 nsPointerLockPermissionRequest::GetTypes(nsIArray** aTypes)
 {
+  nsTArray<nsString> emptyOptions;
   return CreatePermissionArray(NS_LITERAL_CSTRING("pointerLock"),
                                NS_LITERAL_CSTRING("unused"),
+                               emptyOptions,
                                aTypes);
 }
 
 NS_IMETHODIMP
 nsPointerLockPermissionRequest::GetPrincipal(nsIPrincipal** aPrincipal)
 {
   nsCOMPtr<nsIDocument> d = do_QueryReferent(mDocument);
   if (d) {
@@ -11404,18 +11406,20 @@ nsPointerLockPermissionRequest::Cancel()
   if (d) {
     static_cast<nsDocument*>(d.get())->mCancelledPointerLockRequests++;
     DispatchPointerLockError(d);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsPointerLockPermissionRequest::Allow()
-{
+nsPointerLockPermissionRequest::Allow(JS::HandleValue aChoices)
+{
+  MOZ_ASSERT(aChoices.isUndefined());
+
   nsCOMPtr<Element> e = do_QueryReferent(mElement);
   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument);
   nsDocument* d = static_cast<nsDocument*>(doc.get());
   if (!e || !d || gPendingPointerLockRequest != this ||
       e->GetCurrentDoc() != d ||
       (!mUserInputOrChromeCaller && !d->mIsApprovedForFullscreen)) {
     Handled();
     DispatchPointerLockError(d);
--- a/dom/base/nsContentPermissionHelper.cpp
+++ b/dom/base/nsContentPermissionHelper.cpp
@@ -14,16 +14,19 @@
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/PContentPermissionRequestParent.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/unused.h"
 #include "nsComponentManagerUtils.h"
 #include "nsArrayUtils.h"
 #include "nsIMutableArray.h"
 #include "nsContentPermissionHelper.h"
+#include "nsCxPusher.h"
+#include "nsJSUtils.h"
+#include "nsISupportsPrimitives.h"
 
 using mozilla::unused;          // <snicker>
 using namespace mozilla::dom;
 using namespace mozilla;
 
 namespace mozilla {
 namespace dom {
 
@@ -89,20 +92,22 @@ ContentPermissionRequestParent::IsBeingD
   // to send out any message now.
   TabParent* tabParent = static_cast<TabParent*>(Manager());
   return tabParent->IsDestroyed();
 }
 
 NS_IMPL_ISUPPORTS1(ContentPermissionType, nsIContentPermissionType)
 
 ContentPermissionType::ContentPermissionType(const nsACString& aType,
-                                             const nsACString& aAccess)
+                                             const nsACString& aAccess,
+                                             const nsTArray<nsString>& aOptions)
 {
   mType = aType;
   mAccess = aAccess;
+  mOptions = aOptions;
 }
 
 ContentPermissionType::~ContentPermissionType()
 {
 }
 
 NS_IMETHODIMP
 ContentPermissionType::GetType(nsACString& aType)
@@ -113,37 +118,70 @@ ContentPermissionType::GetType(nsACStrin
 
 NS_IMETHODIMP
 ContentPermissionType::GetAccess(nsACString& aAccess)
 {
   aAccess = mAccess;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+ContentPermissionType::GetOptions(nsIArray** aOptions)
+{
+  NS_ENSURE_ARG_POINTER(aOptions);
+
+  *aOptions = nullptr;
+
+  nsresult rv;
+  nsCOMPtr<nsIMutableArray> options =
+    do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // copy options into JS array
+  for (uint32_t i = 0; i < mOptions.Length(); ++i) {
+    nsCOMPtr<nsISupportsString> isupportsString =
+      do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = isupportsString->SetData(mOptions[i]);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = options->AppendElement(isupportsString, false);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  NS_ADDREF(*aOptions = options);
+  return NS_OK;
+}
+
 uint32_t
 ConvertPermissionRequestToArray(nsTArray<PermissionRequest>& aSrcArray,
                                 nsIMutableArray* aDesArray)
 {
   uint32_t len = aSrcArray.Length();
   for (uint32_t i = 0; i < len; i++) {
     nsRefPtr<ContentPermissionType> cpt =
-      new ContentPermissionType(aSrcArray[i].type(), aSrcArray[i].access());
+      new ContentPermissionType(aSrcArray[i].type(),
+                                aSrcArray[i].access(),
+                                aSrcArray[i].options());
     aDesArray->AppendElement(cpt, false);
   }
   return len;
 }
 
 nsresult
 CreatePermissionArray(const nsACString& aType,
                       const nsACString& aAccess,
+                      const nsTArray<nsString>& aOptions,
                       nsIArray** aTypesArray)
 {
   nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
   nsRefPtr<ContentPermissionType> permType = new ContentPermissionType(aType,
-                                                                       aAccess);
+                                                                       aAccess,
+                                                                       aOptions);
   types->AppendElement(permType, false);
   types.forget(aTypesArray);
 
   return NS_OK;
 }
 
 PContentPermissionRequestParent*
 CreateContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
@@ -243,23 +281,25 @@ nsContentPermissionRequestProxy::Cancel(
   }
 
   // Don't send out the delete message when the managing protocol (PBrowser) is
   // being destroyed and PContentPermissionRequest will soon be.
   if (mParent->IsBeingDestroyed()) {
     return NS_ERROR_FAILURE;
   }
 
-  unused << ContentPermissionRequestParent::Send__delete__(mParent, false);
+  nsTArray<PermissionChoice> emptyChoices;
+
+  unused << ContentPermissionRequestParent::Send__delete__(mParent, false, emptyChoices);
   mParent = nullptr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsContentPermissionRequestProxy::Allow()
+nsContentPermissionRequestProxy::Allow(JS::HandleValue aChoices)
 {
   if (mParent == nullptr) {
     return NS_ERROR_FAILURE;
   }
 
   // Don't send out the delete message when the managing protocol (PBrowser) is
   // being destroyed and PContentPermissionRequest will soon be.
   if (mParent->IsBeingDestroyed()) {
@@ -277,12 +317,42 @@ nsContentPermissionRequestProxy::Allow()
     if (mPermissionRequests[i].type().Equals("video-capture")) {
       GonkPermissionService::GetInstance()->addGrantInfo(
         "android.permission.CAMERA",
         static_cast<TabParent*>(mParent->Manager())->Manager()->Pid());
     }
   }
 #endif
 
-  unused << ContentPermissionRequestParent::Send__delete__(mParent, true);
+  nsTArray<PermissionChoice> choices;
+  if (aChoices.isNullOrUndefined()) {
+    // No choice is specified.
+  } else if (aChoices.isObject()) {
+    // Iterate through all permission types.
+    for (uint32_t i = 0; i < mPermissionRequests.Length(); ++i) {
+      nsCString type = mPermissionRequests[i].type();
+
+      mozilla::AutoSafeJSContext cx;
+      JS::Rooted<JSObject*> obj(cx, &aChoices.toObject());
+      JSAutoCompartment ac(cx, obj);
+
+      JS::Rooted<JS::Value> val(cx);
+
+      if (!JS_GetProperty(cx, obj, type.BeginReading(), &val) ||
+          !val.isString()) {
+        // no setting for the permission type, skip it
+      } else {
+        nsDependentJSString choice;
+        if (!choice.init(cx, val)) {
+          return NS_ERROR_FAILURE;
+        }
+        choices.AppendElement(PermissionChoice(type, choice));
+      }
+    }
+  } else {
+    MOZ_ASSERT(false, "SelectedChoices should be undefined or an JS object");
+    return NS_ERROR_FAILURE;
+  }
+
+  unused << ContentPermissionRequestParent::Send__delete__(mParent, true, choices);
   mParent = nullptr;
   return NS_OK;
 }
--- a/dom/base/nsContentPermissionHelper.h
+++ b/dom/base/nsContentPermissionHelper.h
@@ -30,29 +30,33 @@ class ContentPermissionRequestParent;
 class PContentPermissionRequestParent;
 
 class ContentPermissionType : public nsIContentPermissionType
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSICONTENTPERMISSIONTYPE
 
-  ContentPermissionType(const nsACString& aType, const nsACString& aAccess);
+  ContentPermissionType(const nsACString& aType,
+                        const nsACString& aAccess,
+                        const nsTArray<nsString>& aOptions);
   virtual ~ContentPermissionType();
 
 protected:
   nsCString mType;
   nsCString mAccess;
+  nsTArray<nsString> mOptions;
 };
 
 uint32_t ConvertPermissionRequestToArray(nsTArray<PermissionRequest>& aSrcArray,
                                          nsIMutableArray* aDesArray);
 
 nsresult CreatePermissionArray(const nsACString& aType,
                                const nsACString& aAccess,
+                               const nsTArray<nsString>& aOptions,
                                nsIArray** aTypesArray);
 
 PContentPermissionRequestParent*
 CreateContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
                                      Element* element,
                                      const IPC::Principal& principal);
 
 } // namespace dom
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -1569,20 +1569,22 @@ public:
 
   NS_FORWARD_NSICONTENTPERMISSIONREQUEST(mCursor->);
 
   DeviceStorageCursorRequest(nsDOMDeviceStorageCursor* aCursor)
     : mCursor(aCursor) { }
 
   ~DeviceStorageCursorRequest() {}
 
-  bool Recv__delete__(const bool& allow)
+  bool Recv__delete__(const bool& allow,
+                      const InfallibleTArray<PermissionChoice>& choices)
   {
+    MOZ_ASSERT(choices.IsEmpty(), "DeviceStorageCursor doesn't support permission choice");
     if (allow) {
-      Allow();
+      Allow(JS::UndefinedHandleValue);
     }
     else {
       Cancel();
     }
     return true;
   }
 
   void IPDLRelease()
@@ -1808,17 +1810,21 @@ nsDOMDeviceStorageCursor::GetStorageType
 NS_IMETHODIMP
 nsDOMDeviceStorageCursor::GetTypes(nsIArray** aTypes)
 {
   nsCString type;
   nsresult rv =
     DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType, type);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return CreatePermissionArray(type, NS_LITERAL_CSTRING("read"), aTypes);
+  nsTArray<nsString> emptyOptions;
+  return CreatePermissionArray(type,
+                               NS_LITERAL_CSTRING("read"),
+                               emptyOptions,
+                               aTypes);
 }
 
 NS_IMETHODIMP
 nsDOMDeviceStorageCursor::GetPrincipal(nsIPrincipal * *aRequestingPrincipal)
 {
   NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal);
   return NS_OK;
 }
@@ -1841,18 +1847,20 @@ NS_IMETHODIMP
 nsDOMDeviceStorageCursor::Cancel()
 {
   nsCOMPtr<PostErrorEvent> event
     = new PostErrorEvent(this, POST_ERROR_EVENT_PERMISSION_DENIED);
   return NS_DispatchToMainThread(event);
 }
 
 NS_IMETHODIMP
-nsDOMDeviceStorageCursor::Allow()
+nsDOMDeviceStorageCursor::Allow(JS::HandleValue aChoices)
 {
+  MOZ_ASSERT(aChoices.isUndefined());
+
   if (!mFile->IsSafePath()) {
     nsCOMPtr<nsIRunnable> r
       = new PostErrorEvent(this, POST_ERROR_EVENT_PERMISSION_DENIED);
     return NS_DispatchToMainThread(r);
   }
 
   if (XRE_GetProcessType() != GeckoProcessType_Default) {
     PDeviceStorageRequestChild* child
@@ -1892,20 +1900,23 @@ nsDOMDeviceStorageCursor::Continue(Error
 
   nsCOMPtr<ContinueCursorEvent> event = new ContinueCursorEvent(this);
   event->Continue();
 
   mOkToCallContinue = false;
 }
 
 bool
-nsDOMDeviceStorageCursor::Recv__delete__(const bool& allow)
+nsDOMDeviceStorageCursor::Recv__delete__(const bool& allow,
+                                         const InfallibleTArray<PermissionChoice>& choices)
 {
+  MOZ_ASSERT(choices.IsEmpty(), "DeviceStorageCursor doesn't support permission choice");
+
   if (allow) {
-    Allow();
+    Allow(JS::UndefinedHandleValue);
   }
   else {
     Cancel();
   }
   return true;
 }
 
 void
@@ -2417,17 +2428,17 @@ public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(DeviceStorageRequest,
                                            nsIContentPermissionRequest)
 
   NS_IMETHOD Run() {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) {
-      Allow();
+      Allow(JS::UndefinedHandleValue);
       return NS_OK;
     }
 
     if (XRE_GetProcessType() == GeckoProcessType_Content) {
 
       // because owner implements nsITabChild, we can assume that it is
       // the one and only TabChild.
       TabChild* child = TabChild::GetFrom(mWindow->GetDocShell());
@@ -2448,17 +2459,18 @@ public:
       }
       nsCString access;
       rv = DeviceStorageTypeChecker::GetAccessForRequest(
         DeviceStorageRequestType(mRequestType), access);
       if (NS_FAILED(rv)) {
         return rv;
       }
       nsTArray<PermissionRequest> permArray;
-      permArray.AppendElement(PermissionRequest(type, access));
+      nsTArray<nsString> emptyOptions;
+      permArray.AppendElement(PermissionRequest(type, access, emptyOptions));
       child->SendPContentPermissionRequestConstructor(
         this, permArray, IPC::Principal(mPrincipal));
 
       Sendprompt();
       return NS_OK;
     }
 
     nsCOMPtr<nsIContentPermissionPrompt> prompt
@@ -2480,17 +2492,18 @@ public:
 
     nsCString access;
     rv = DeviceStorageTypeChecker::GetAccessForRequest(
       DeviceStorageRequestType(mRequestType), access);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
-    return CreatePermissionArray(type, access, aTypes);
+    nsTArray<nsString> emptyOptions;
+    return CreatePermissionArray(type, access, emptyOptions, aTypes);
   }
 
   NS_IMETHOD GetPrincipal(nsIPrincipal * *aRequestingPrincipal)
   {
     NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal);
     return NS_OK;
   }
 
@@ -2509,19 +2522,20 @@ public:
   NS_IMETHOD Cancel()
   {
     nsCOMPtr<PostErrorEvent> event
       = new PostErrorEvent(mRequest.forget(),
                            POST_ERROR_EVENT_PERMISSION_DENIED);
     return NS_DispatchToMainThread(event);
   }
 
-  NS_IMETHOD Allow()
+  NS_IMETHOD Allow(JS::HandleValue aChoices)
   {
     MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(aChoices.isUndefined());
 
     if (!mRequest) {
       return NS_ERROR_FAILURE;
     }
 
     nsCOMPtr<nsIRunnable> r;
 
     switch(mRequestType) {
@@ -2767,20 +2781,23 @@ public:
         = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
       MOZ_ASSERT(target);
       target->Dispatch(r, NS_DISPATCH_NORMAL);
     }
 
     return NS_OK;
   }
 
-  bool Recv__delete__(const bool& allow)
+  bool Recv__delete__(const bool& allow,
+                      const InfallibleTArray<PermissionChoice>& choices)
   {
+    MOZ_ASSERT(choices.IsEmpty(), "DeviceStorage doesn't support permission choice");
+
     if (allow) {
-      Allow();
+      Allow(JS::UndefinedHandleValue);
     }
     else {
       Cancel();
     }
     return true;
   }
 
   void IPDLRelease()
@@ -3636,17 +3653,17 @@ nsDOMDeviceStorage::EnumerateInternal(co
   dsf->SetEditable(aEditable);
 
   nsRefPtr<nsDOMDeviceStorageCursor> cursor
     = new nsDOMDeviceStorageCursor(win, mPrincipal, dsf, since);
   nsRefPtr<DeviceStorageCursorRequest> r
     = new DeviceStorageCursorRequest(cursor);
 
   if (mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) {
-    r->Allow();
+    r->Allow(JS::UndefinedHandleValue);
     return cursor.forget();
   }
 
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     // because owner implements nsITabChild, we can assume that it is
     // the one and only TabChild.
     TabChild* child = TabChild::GetFrom(win->GetDocShell());
     if (!child) {
@@ -3658,17 +3675,20 @@ nsDOMDeviceStorage::EnumerateInternal(co
     r->AddRef();
 
     nsCString type;
     aRv = DeviceStorageTypeChecker::GetPermissionForType(mStorageType, type);
     if (aRv.Failed()) {
       return nullptr;
     }
     nsTArray<PermissionRequest> permArray;
-    permArray.AppendElement(PermissionRequest(type, NS_LITERAL_CSTRING("read")));
+    nsTArray<nsString> emptyOptions;
+    permArray.AppendElement(PermissionRequest(type,
+                                              NS_LITERAL_CSTRING("read"),
+                                              emptyOptions));
     child->SendPContentPermissionRequestConstructor(r,
                                                     permArray,
                                                     IPC::Principal(mPrincipal));
 
     r->Sendprompt();
 
     return cursor.forget();
   }
--- a/dom/devicestorage/nsDeviceStorage.h
+++ b/dom/devicestorage/nsDeviceStorage.h
@@ -199,17 +199,18 @@ public:
                            DeviceStorageFile* aFile,
                            PRTime aSince);
 
 
   nsTArray<nsRefPtr<DeviceStorageFile> > mFiles;
   bool mOkToCallContinue;
   PRTime mSince;
 
-  virtual bool Recv__delete__(const bool& allow) MOZ_OVERRIDE;
+  virtual bool Recv__delete__(const bool& allow,
+                              const InfallibleTArray<PermissionChoice>& choices) MOZ_OVERRIDE;
   virtual void IPDLRelease() MOZ_OVERRIDE;
 
   void GetStorageType(nsAString & aType);
 
   void RequestComplete() MOZ_OVERRIDE;
 
 private:
   ~nsDOMDeviceStorageCursor();
--- a/dom/interfaces/base/nsIContentPermissionPrompt.idl
+++ b/dom/interfaces/base/nsIContentPermissionPrompt.idl
@@ -7,29 +7,34 @@
 interface nsIPrincipal;
 interface nsIDOMWindow;
 interface nsIDOMElement;
 interface nsIArray;
 
 /**
  *  Interface provides the request type and its access.
  */
-[scriptable, uuid(384b6cc4-a66b-4bea-98e0-eb10562a9ba4)]
+[scriptable, uuid(ef4db3b8-ca9c-4b1d-8f81-fd88ec32af13)]
 interface nsIContentPermissionType : nsISupports {
   /**
    *  The type of the permission request, such as
    *  "geolocation".
    */
   readonly attribute ACString type;
 
   /**
    *  The access of the permission request, such as
    *  "read".
    */
   readonly attribute ACString access;
+
+  /**
+   * The array of available options.
+   */
+  readonly attribute nsIArray options; // ["choice1", "choice2"]
 };
 
 /**
  * Interface allows access to a content to request
  * permission to perform a privileged operation such as
  * geolocation.
  */
 [scriptable, uuid(69a39d88-d1c4-4ba9-9b19-bafc7a1bb783)]
@@ -54,17 +59,17 @@ interface nsIContentPermissionRequest : 
   readonly attribute nsIDOMWindow window;
   readonly attribute nsIDOMElement element;
 
   /**
    * allow or cancel the request
    */
 
   void cancel();
-  void allow();
+  void allow([optional] in jsval choices); // {"type1": "choice1", "type2": "choiceA"}
 };
 
 /**
  * Interface provides a way for the application to handle
  * the UI prompts associated with geo position.
  */
 [scriptable, function, uuid(F72DE90D-E954-4E69-9A61-917303029301)]
 interface nsIContentPermissionPrompt : nsISupports {
--- a/dom/ipc/PContentPermission.ipdlh
+++ b/dom/ipc/PContentPermission.ipdlh
@@ -3,12 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 namespace mozilla {
 namespace dom {
 
 struct PermissionRequest {
   nsCString type;
   nsCString access;
+  nsString[] options;
+};
+
+struct PermissionChoice {
+  nsCString type;
+  nsString choice;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/PContentPermissionRequest.ipdl
+++ b/dom/ipc/PContentPermissionRequest.ipdl
@@ -1,23 +1,24 @@
 /* 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/. */
 
 include protocol PBrowser;
+include PContentPermission;
 
 namespace mozilla {
 namespace dom {
 
 protocol PContentPermissionRequest
 {
   manager PBrowser;
 
 parent:
   prompt();
 
 child:
-  __delete__(bool allow);
+  __delete__(bool allow, PermissionChoice[] choices);
 };
 
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/permission/PermissionPromptHelper.jsm
+++ b/dom/permission/PermissionPromptHelper.jsm
@@ -69,20 +69,30 @@ this.PermissionPromptHelper = {
 
     if (permValue == Ci.nsIPermissionManager.DENY_ACTION ||
         permValue == Ci.nsIPermissionManager.UNKNOWN_ACTION) {
       aCallbacks.cancel();
       return;
     }
 
     if (permValue == Ci.nsIPermissionManager.PROMPT_ACTION) {
+
+      // create the options from permission request.
+      let options = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+      if (msg.options) {
+        for (let option of options) {
+          options.appendElement(option);
+        }
+      }
+
       // create an array with a nsIContentPermissionType element
       let type = {
         type: msg.type,
         access: msg.access ? msg.access : "unused",
+        options: options,
         QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionType])
       };
       let typeArray = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
       typeArray.appendElement(type, false);
 
       // create a nsIContentPermissionRequest
       let request = {
         types: typeArray,
@@ -117,19 +127,20 @@ this.PermissionPromptHelper = {
     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() {
+        allow: function(aChoice) {
           mm.sendAsyncMessage("PermissionPromptHelper:AskPermission:OK",
                               { result: Ci.nsIPermissionManager.ALLOW_ACTION,
+                                choice: aChoice,
                                 requestID: msg.requestID });
         }
       });
     }
   }
 }
 
 PermissionPromptHelper.init();
--- a/dom/src/geolocation/nsGeolocation.cpp
+++ b/dom/src/geolocation/nsGeolocation.cpp
@@ -81,17 +81,18 @@ class nsGeolocationRequest
   bool WantsHighAccuracy() {return !mShutdown && mOptions && mOptions->mEnableHighAccuracy;}
   void SetTimeoutTimer();
   void StopTimeoutTimer();
   void NotifyErrorAndShutdown(uint16_t);
   nsIPrincipal* GetPrincipal();
 
   ~nsGeolocationRequest();
 
-  virtual bool Recv__delete__(const bool& allow) MOZ_OVERRIDE;
+  virtual bool Recv__delete__(const bool& allow,
+                              const InfallibleTArray<PermissionChoice>& choices) MOZ_OVERRIDE;
   virtual void IPDLRelease() MOZ_OVERRIDE { Release(); }
 
   bool IsWatch() { return mIsWatchPositionRequest; }
   int32_t WatchId() { return mWatchId; }
  private:
   bool mIsWatchPositionRequest;
 
   nsCOMPtr<nsITimer> mTimeoutTimer;
@@ -190,17 +191,17 @@ public:
   RequestAllowEvent(int allow, nsGeolocationRequest* request)
     : mAllow(allow),
       mRequest(request)
   {
   }
 
   NS_IMETHOD Run() {
     if (mAllow) {
-      mRequest->Allow();
+      mRequest->Allow(JS::UndefinedHandleValue);
     } else {
       mRequest->Cancel();
     }
     return NS_OK;
   }
 
 private:
   bool mAllow;
@@ -376,18 +377,20 @@ nsGeolocationRequest::GetPrincipal(nsIPr
   principal.forget(aRequestingPrincipal);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsGeolocationRequest::GetTypes(nsIArray** aTypes)
 {
+  nsTArray<nsString> emptyOptions;
   return CreatePermissionArray(NS_LITERAL_CSTRING("geolocation"),
                                NS_LITERAL_CSTRING("unused"),
+                               emptyOptions,
                                aTypes);
 }
 
 NS_IMETHODIMP
 nsGeolocationRequest::GetWindow(nsIDOMWindow * *aRequestingWindow)
 {
   NS_ENSURE_ARG_POINTER(aRequestingWindow);
 
@@ -408,18 +411,20 @@ nsGeolocationRequest::GetElement(nsIDOME
 NS_IMETHODIMP
 nsGeolocationRequest::Cancel()
 {
   NotifyError(nsIDOMGeoPositionError::PERMISSION_DENIED);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsGeolocationRequest::Allow()
+nsGeolocationRequest::Allow(JS::HandleValue aChoices)
 {
+  MOZ_ASSERT(aChoices.isUndefined());
+
   // Kick off the geo device, if it isn't already running
   nsRefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService();
   nsresult rv = gs->StartDevice(GetPrincipal());
 
   if (NS_FAILED(rv)) {
     // Location provider error
     NotifyError(nsIDOMGeoPositionError::POSITION_UNAVAILABLE);
     return NS_OK;
@@ -595,20 +600,23 @@ nsGeolocationRequest::Shutdown()
   if (mOptions && mOptions->mEnableHighAccuracy) {
     nsRefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService();
     if (gs) {
       gs->UpdateAccuracy();
     }
   }
 }
 
-bool nsGeolocationRequest::Recv__delete__(const bool& allow)
+bool nsGeolocationRequest::Recv__delete__(const bool& allow,
+                                          const InfallibleTArray<PermissionChoice>& choices)
 {
+  MOZ_ASSERT(choices.IsEmpty(), "Geolocation doesn't support permission choice");
+
   if (allow) {
-    (void) Allow();
+    (void) Allow(JS::UndefinedHandleValue);
   } else {
     (void) Cancel();
   }
   return true;
 }
 ////////////////////////////////////////////////////
 // nsGeolocationService
 ////////////////////////////////////////////////////
@@ -1356,17 +1364,17 @@ Geolocation::WatchPositionReady(nsGeoloc
 
     return NS_OK;
   }
 
   if (!nsContentUtils::IsCallerChrome()) {
     return NS_ERROR_FAILURE;
   }
 
-  aRequest->Allow();
+  aRequest->Allow(JS::UndefinedHandleValue);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Geolocation::ClearWatch(int32_t aWatchId)
 {
   if (aWatchId < 0) {
@@ -1467,18 +1475,20 @@ Geolocation::RegisterRequestWithPrompt(n
     // because owner implements nsITabChild, we can assume that it is
     // the one and only TabChild.
     TabChild* child = TabChild::GetFrom(window->GetDocShell());
     if (!child) {
       return false;
     }
 
     nsTArray<PermissionRequest> permArray;
+    nsTArray<nsString> emptyOptions;
     permArray.AppendElement(PermissionRequest(NS_LITERAL_CSTRING("geolocation"),
-                                              NS_LITERAL_CSTRING("unused")));
+                                              NS_LITERAL_CSTRING("unused"),
+                                              emptyOptions));
 
     // Retain a reference so the object isn't deleted without IPDL's knowledge.
     // Corresponding release occurs in DeallocPContentPermissionRequest.
     request->AddRef();
     child->SendPContentPermissionRequestConstructor(request,
                                                     permArray,
                                                     IPC::Principal(mPrincipal));
 
--- a/dom/src/notification/DesktopNotification.cpp
+++ b/dom/src/notification/DesktopNotification.cpp
@@ -44,20 +44,22 @@ public:
     }
     return NS_OK;
   }
 
   ~DesktopNotificationRequest()
   {
   }
 
-  virtual bool Recv__delete__(const bool& aAllow) MOZ_OVERRIDE
+  virtual bool Recv__delete__(const bool& aAllow,
+                              const InfallibleTArray<PermissionChoice>& choices) MOZ_OVERRIDE
   {
+    MOZ_ASSERT(choices.IsEmpty(), "DesktopNotification doesn't support permission choice");
     if (aAllow) {
-      (void) Allow();
+      (void) Allow(JS::UndefinedHandleValue);
     } else {
      (void) Cancel();
     }
    return true;
   }
   virtual void IPDLRelease() MOZ_OVERRIDE { Release(); }
 
   nsRefPtr<DesktopNotification> mDesktopNotification;
@@ -174,19 +176,21 @@ DesktopNotification::Init()
     // the one and only TabChild for this docshell.
     TabChild* child = TabChild::GetFrom(GetOwner()->GetDocShell());
 
     // Retain a reference so the object isn't deleted without IPDL's knowledge.
     // Corresponding release occurs in DeallocPContentPermissionRequest.
     nsRefPtr<DesktopNotificationRequest> copy = request;
 
     nsTArray<PermissionRequest> permArray;
+    nsTArray<nsString> emptyOptions;
     permArray.AppendElement(PermissionRequest(
                             NS_LITERAL_CSTRING("desktop-notification"),
-                            NS_LITERAL_CSTRING("unused")));
+                            NS_LITERAL_CSTRING("unused"),
+                            emptyOptions));
     child->SendPContentPermissionRequestConstructor(copy.forget().get(),
                                                     permArray,
                                                     IPC::Principal(mPrincipal));
 
     request->Sendprompt();
     return;
   }
 
@@ -342,25 +346,28 @@ NS_IMETHODIMP
 DesktopNotificationRequest::Cancel()
 {
   nsresult rv = mDesktopNotification->SetAllow(false);
   mDesktopNotification = nullptr;
   return rv;
 }
 
 NS_IMETHODIMP
-DesktopNotificationRequest::Allow()
+DesktopNotificationRequest::Allow(JS::HandleValue aChoices)
 {
+  MOZ_ASSERT(aChoices.isUndefined());
   nsresult rv = mDesktopNotification->SetAllow(true);
   mDesktopNotification = nullptr;
   return rv;
 }
 
 NS_IMETHODIMP
 DesktopNotificationRequest::GetTypes(nsIArray** aTypes)
 {
+  nsTArray<nsString> emptyOptions;
   return CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"),
                                NS_LITERAL_CSTRING("unused"),
+                               emptyOptions,
                                aTypes);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/src/notification/Notification.cpp
+++ b/dom/src/notification/Notification.cpp
@@ -158,17 +158,18 @@ public:
   NotificationPermissionRequest(nsIPrincipal* aPrincipal, nsPIDOMWindow* aWindow,
                                 NotificationPermissionCallback* aCallback)
     : mPrincipal(aPrincipal), mWindow(aWindow),
       mPermission(NotificationPermission::Default),
       mCallback(aCallback) {}
 
   virtual ~NotificationPermissionRequest() {}
 
-  bool Recv__delete__(const bool& aAllow);
+  bool Recv__delete__(const bool& aAllow,
+                      const InfallibleTArray<PermissionChoice>& choices);
   void IPDLRelease() { Release(); }
 
 protected:
   nsresult CallCallback();
   nsresult DispatchCallback();
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsPIDOMWindow> mWindow;
   NotificationPermission mPermission;
@@ -264,19 +265,21 @@ NotificationPermissionRequest::Run()
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     // Retain a reference so the object isn't deleted without IPDL's knowledge.
     // Corresponding release occurs in DeallocPContentPermissionRequest.
     AddRef();
 
     nsTArray<PermissionRequest> permArray;
+    nsTArray<nsString> emptyOptions;
     permArray.AppendElement(PermissionRequest(
                             NS_LITERAL_CSTRING("desktop-notification"),
-                            NS_LITERAL_CSTRING("unused")));
+                            NS_LITERAL_CSTRING("unused"),
+                            emptyOptions));
     child->SendPContentPermissionRequestConstructor(this, permArray,
                                                     IPC::Principal(mPrincipal));
 
     Sendprompt();
     return NS_OK;
   }
 
   nsCOMPtr<nsIContentPermissionPrompt> prompt =
@@ -313,18 +316,20 @@ NotificationPermissionRequest::GetElemen
 NS_IMETHODIMP
 NotificationPermissionRequest::Cancel()
 {
   mPermission = NotificationPermission::Denied;
   return DispatchCallback();
 }
 
 NS_IMETHODIMP
-NotificationPermissionRequest::Allow()
+NotificationPermissionRequest::Allow(JS::HandleValue aChoices)
 {
+  MOZ_ASSERT(aChoices.isUndefined());
+
   mPermission = NotificationPermission::Granted;
   return DispatchCallback();
 }
 
 inline nsresult
 NotificationPermissionRequest::DispatchCallback()
 {
   if (!mCallback) {
@@ -342,26 +347,31 @@ NotificationPermissionRequest::CallCallb
   ErrorResult rv;
   mCallback->Call(mPermission, rv);
   return rv.ErrorCode();
 }
 
 NS_IMETHODIMP
 NotificationPermissionRequest::GetTypes(nsIArray** aTypes)
 {
+  nsTArray<nsString> emptyOptions;
   return CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"),
                                NS_LITERAL_CSTRING("unused"),
+                               emptyOptions,
                                aTypes);
 }
 
 bool
-NotificationPermissionRequest::Recv__delete__(const bool& aAllow)
+NotificationPermissionRequest::Recv__delete__(const bool& aAllow,
+                                              const InfallibleTArray<PermissionChoice>& choices)
 {
+  MOZ_ASSERT(choices.IsEmpty(), "Notification doesn't support permission choice");
+
   if (aAllow) {
-    (void) Allow();
+    (void) Allow(JS::UndefinedHandleValue);
   } else {
     (void) Cancel();
   }
   return true;
 }
 
 NS_IMPL_ISUPPORTS1(NotificationTask, nsIRunnable)