Bug 978660 - no need to prompt if gUM is already granted. r=fabrice.
authorShih-Chiang Chien <schien@mozilla.com>
Tue, 13 May 2014 13:58:04 +0800
changeset 185992 7cdfb038dd19826f47e694cd24e2bb4d3e2bf0f9
parent 185991 b5b2303ba39f08b9ff9e3c81e4555a004c08c108
child 185993 288387f37b70025dfe935370864187d46b633ce9
push id44221
push userrjesup@wgate.com
push dateSat, 31 May 2014 11:07:58 +0000
treeherdermozilla-inbound@7cdfb038dd19 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs978660
milestone32.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 978660 - no need to prompt if gUM is already granted. r=fabrice.
b2g/components/ContentPermissionPrompt.js
b2g/components/test/mochitest/mochitest.ini
b2g/components/test/mochitest/test_permission_gum_remember.html
--- a/b2g/components/ContentPermissionPrompt.js
+++ b/b2g/components/ContentPermissionPrompt.js
@@ -38,16 +38,49 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "AudioManager",
                                    "@mozilla.org/telephony/audiomanager;1",
                                    "nsIAudioManager");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
                                   "resource://gre/modules/SystemAppProxy.jsm");
 
 /**
+ * Determine if a permission should be prompt to user or not.
+ *
+ * @param aPerm requested permission
+ * @param aAction the action according to principal
+ * @return true if prompt is required
+ */
+function shouldPrompt(aPerm, aAction) {
+  return ((aAction == Ci.nsIPermissionManager.PROMPT_ACTION) ||
+          (aAction == Ci.nsIPermissionManager.UNKNOWN_ACTION &&
+           PROMPT_FOR_UNKNOWN.indexOf(aPerm) >= 0));
+}
+
+/**
+ * Create the default choices for the requested permissions
+ *
+ * @param aTypesInfo requested permissions
+ * @return the default choices for permissions with options, return
+ *         undefined if no option in all requested permissions.
+ */
+function buildDefaultChoices(aTypesInfo) {
+  let choices;
+  for (let type of aTypesInfo) {
+    if (type.options.length > 0) {
+      if (!choices) {
+        choices = {};
+      }
+      choices[type.access] = type.options[0];
+    }
+  }
+  return choices;
+}
+
+/**
  * aTypesInfo is an array of {permission, access, action, deny} which keeps
  * the information of each permission. This arrary is initialized in
  * ContentPermissionPrompt.prompt and used among functions.
  *
  * aTypesInfo[].permission : permission name
  * aTypesInfo[].access     : permission name + request.access
  * aTypesInfo[].action     : the default action of this permission
  * aTypesInfo[].deny       : true if security manager denied this app's origin
@@ -57,19 +90,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
  *   aTypesInfo[].action is PROMPT_ACTION and aTypesInfo[].deny is false.
  */
 function rememberPermission(aTypesInfo, aPrincipal, aSession)
 {
   function convertPermToAllow(aPerm, aPrincipal)
   {
     let type =
       permissionManager.testExactPermissionFromPrincipal(aPrincipal, aPerm);
-    if (type == Ci.nsIPermissionManager.PROMPT_ACTION ||
-        (type == Ci.nsIPermissionManager.UNKNOWN_ACTION &&
-        PROMPT_FOR_UNKNOWN.indexOf(aPerm) >= 0)) {
+    if (shouldPrompt(aPerm, type)) {
       debug("add " + aPerm + " to permission manager with ALLOW_ACTION");
       if (!aSession) {
         permissionManager.addFromPrincipal(aPrincipal,
                                            aPerm,
                                            Ci.nsIPermissionManager.ALLOW_ACTION);
       } else if (PERMISSION_NO_SESSION.indexOf(aPerm) < 0) {
         permissionManager.addFromPrincipal(aPrincipal,
                                            aPerm,
@@ -99,32 +130,33 @@ function ContentPermissionPrompt() {}
 ContentPermissionPrompt.prototype = {
 
   handleExistingPermission: function handleExistingPermission(request,
                                                               typesInfo) {
     typesInfo.forEach(function(type) {
       type.action =
         Services.perms.testExactPermissionFromPrincipal(request.principal,
                                                         type.access);
-      if (type.action == Ci.nsIPermissionManager.UNKNOWN_ACTION &&
-          PROMPT_FOR_UNKNOWN.indexOf(type.access) >= 0) {
+      if (shouldPrompt(type.access, type.action)) {
         type.action = Ci.nsIPermissionManager.PROMPT_ACTION;
       }
     });
 
-    // If all permissions are allowed already, call allow() without prompting.
+    // If all permissions are allowed already and no more than one option,
+    // call allow() without prompting.
     let checkAllowPermission = function(type) {
-      if (type.action == Ci.nsIPermissionManager.ALLOW_ACTION) {
+      if (type.action == Ci.nsIPermissionManager.ALLOW_ACTION &&
+          type.options.length <= 1) {
         return true;
       }
       return false;
     }
     if (typesInfo.every(checkAllowPermission)) {
       debug("all permission requests are allowed");
-      request.allow();
+      request.allow(buildDefaultChoices(typesInfo));
       return true;
     }
 
     // If all permissions are DENY_ACTION or UNKNOWN_ACTION, call cancel()
     // without prompting.
     let checkDenyPermission = function(type) {
       if (type.action == Ci.nsIPermissionManager.DENY_ACTION ||
           type.action == Ci.nsIPermissionManager.UNKNOWN_ACTION) {
@@ -180,17 +212,18 @@ ContentPermissionPrompt.prototype = {
                                                                    type.access);
 
       if (result == Ci.nsIPermissionManager.ALLOW_ACTION ||
           result == Ci.nsIPermissionManager.PROMPT_ACTION) {
         type.deny = false;
       }
       return !type.deny;
     }
-    if (typesInfo.filter(notDenyAppPrincipal).length === 0) {
+    // Cancel the entire request if one of the requested permissions is denied
+    if (!typesInfo.every(notDenyAppPrincipal)) {
       request.cancel();
       return true;
     }
 
     return false;
   },
 
   handledByPermissionType: function handledByPermissionType(request, typesInfo) {
@@ -201,21 +234,16 @@ ContentPermissionPrompt.prototype = {
       }
     }
 
     return false;
   },
 
   _id: 0,
   prompt: function(request) {
-    if (secMan.isSystemPrincipal(request.principal)) {
-      request.allow();
-      return;
-    }
-
     // Initialize the typesInfo and set the default value.
     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") ?
@@ -229,16 +257,22 @@ ContentPermissionPrompt.prototype = {
       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 (secMan.isSystemPrincipal(request.principal)) {
+      request.allow(buildDefaultChoices(typesInfo));
+      return;
+    }
+
+
     if (typesInfo.length == 0) {
       request.cancel();
       return;
     }
 
     if(!this.checkMultipleRequest(typesInfo)) {
       request.cancel();
       return;
@@ -249,21 +283,19 @@ ContentPermissionPrompt.prototype = {
       return;
     }
 
     // returns true if the request was handled
     if (this.handleExistingPermission(request, typesInfo)) {
        return;
     }
 
-    // prompt PROMPT_ACTION request only.
-    typesInfo.forEach(function(aType, aIndex) {
-      if (aType.action != Ci.nsIPermissionManager.PROMPT_ACTION || aType.deny) {
-        typesInfo.splice(aIndex);
-      }
+    // prompt PROMPT_ACTION request or request with options.
+    typesInfo = typesInfo.filter(function(type) {
+      return !type.deny && (type.action == Ci.nsIPermissionManager.PROMPT_ACTION || type.options.length > 0) ;
     });
 
     let frame = request.element;
     let requestId = this._id++;
 
     if (!frame) {
       this.delegatePrompt(request, requestId, typesInfo);
       return;
@@ -361,29 +393,33 @@ ContentPermissionPrompt.prototype = {
     }
 
     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 isGranted = typesInfo.every(function(type) {
+      return type.action == Ci.nsIPermissionManager.ALLOW_ACTION;
+    });
     let permissions = {};
     for (let i in typesInfo) {
       debug("prompt " + typesInfo[i].permission);
       permissions[typesInfo[i].permission] = typesInfo[i].options;
     }
 
     let details = {
       type: type,
       permissions: permissions,
       id: requestId,
       origin: principal.origin,
       isApp: isApp,
-      remember: remember
+      remember: remember,
+      isGranted: isGranted,
     };
 
     if (isApp) {
       details.manifestURL = DOMApplicationRegistry.getManifestURLByLocalId(principal.appId);
     }
     SystemAppProxy.dispatchEvent(details);
   },
 
--- a/b2g/components/test/mochitest/mochitest.ini
+++ b/b2g/components/test/mochitest/mochitest.ini
@@ -6,9 +6,11 @@ support-files =
   systemapp_helper.js
 
 [test_sandbox_permission.html]
 run-if = toolkit == "gonk"
 [test_filepicker_path.html]
 run-if = toolkit == "gonk"
 [test_permission_deny.html]
 run-if = toolkit == "gonk"
+[test_permission_gum_remember.html]
+run-if = toolkit == "gonk"
 [test_systemapp.html]
new file mode 100644
--- /dev/null
+++ b/b2g/components/test/mochitest/test_permission_gum_remember.html
@@ -0,0 +1,166 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=978660
+-->
+<head>
+  <meta charset="utf-8">
+  <title>gUM Remember Permission Test</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=978660">Test remembering gUM Permission</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+const PROMPT_ACTION = SpecialPowers.Ci.nsIPermissionManager.PROMPT_ACTION;
+
+var gUrl = SimpleTest.getTestFileURL('permission_handler_chrome.js');
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+gScript.addMessageListener('permission-request', function(detail) {
+  ok(false, 'unexpected mozChromeEvent for permission prompt');
+  let response = {
+    id: detail.id,
+    type: 'permission-deny',
+    remember: false,
+  };
+  gScript.sendAsyncMessage('permission-response', response);
+});
+
+var gTests = [
+  {
+    'audio': true,
+    'video': {facingMode: 'environment', required: ['facingMode']},
+  },
+  {
+    'video': {facingMode: 'environment', required: ['facingMode']},
+  },
+  {
+    'audio': true,
+  },
+];
+
+function testGranted() {
+  info('test remember permission granted');
+  return new Promise(function(resolve, reject) {
+    let steps = [].concat(gTests);
+    function nextStep() {
+      if (steps.length > 0) {
+        let requestedType = steps.shift();
+        info('getUserMedia for ' + JSON.stringify(requestedType));
+        navigator.mozGetUserMedia(requestedType, function success() {
+          ok(true, 'expected gUM success');
+          nextStep();
+        }, function failure(err) {
+          ok(false, 'unexpected gUM fail: ' + err);
+          nextStep();
+        });
+      } else {
+        resolve();
+      }
+    }
+
+    SpecialPowers.pushPermissions([
+      {type: 'video-capture', allow: true, context: document},
+      {type: 'audio-capture', allow: true, context: document},
+    ], nextStep);
+  });
+}
+
+function testDenied() {
+  info('test remember permission denied');
+  return new Promise(function(resolve, reject) {
+    let steps = [].concat(gTests);
+    function nextStep() {
+      if (steps.length > 0) {
+        let requestedType = steps.shift();
+        info('getUserMedia for ' + JSON.stringify(requestedType));
+        navigator.mozGetUserMedia(requestedType, function success() {
+          ok(false, 'unexpected gUM success');
+          nextStep();
+        }, function failure(err) {
+          ok(true, 'expected gUM fail: ' + err);
+          nextStep();
+        });
+      } else {
+        resolve();
+      }
+    }
+
+    SpecialPowers.pushPermissions([
+      {type: 'video-capture', allow: false, context: document},
+      {type: 'audio-capture', allow: false, context: document},
+    ], nextStep);
+  });
+}
+
+function testPartialDeniedAudio() {
+  info('test remember permission partial denied: audio');
+  return new Promise(function(resolve, reject) {
+    info('getUserMedia for video and audio');
+    function nextStep() {
+      navigator.mozGetUserMedia({video: {facingMode: 'environment', required: ['facingMode']},
+                                 audio: true}, function success() {
+        ok(false, 'unexpected gUM success');
+        resolve();
+      }, function failure(err) {
+        ok(true, 'expected gUM fail: ' + err);
+        resolve();
+      });
+    }
+
+    SpecialPowers.pushPermissions([
+      {type: 'video-capture', allow: true, context: document},
+      {type: 'audio-capture', allow: false, context: document},
+    ], nextStep);
+  });
+}
+
+function testPartialDeniedVideo() {
+  info('test remember permission partial denied: video');
+  return new Promise(function(resolve, reject) {
+    info('getUserMedia for video and audio');
+    function nextStep() {
+      navigator.mozGetUserMedia({video: {facingMode: 'environment', required: ['facingMode']},
+                                 audio: true}, function success() {
+        ok(false, 'unexpected gUM success');
+        resolve();
+      }, function failure(err) {
+        ok(true, 'expected gUM fail: ' + err);
+        resolve();
+      });
+    }
+
+    SpecialPowers.pushPermissions([
+      {type: 'video-capture', allow: false, context: document},
+      {type: 'audio-capture', allow: true, context: document},
+    ], nextStep);
+  });
+}
+
+function runTests() {
+  testGranted()
+  .then(testDenied)
+  .then(testPartialDeniedAudio)
+  .then(testPartialDeniedVideo)
+  .then(function() {
+    info('test finished, teardown');
+    gScript.sendAsyncMessage('teardown', '');
+    gScript.destroy();
+    SimpleTest.finish();
+  });
+}
+
+SpecialPowers.pushPrefEnv({
+  'set': [
+    ['media.navigator.permission.disabled', false],
+  ]
+}, runTests);
+</script>
+</pre>
+</body>
+</html>