Bug 685652 - we need a pushPermissionsEnv equivalent to pushPrefEnv in SpecialPowers. r=jdm
authorJoel Maher <jmaher@mozilla.com>
Fri, 29 Mar 2013 08:43:27 -0400
changeset 126698 68a3cd15473229494c17c39e7ce023baa0c3bb47
parent 126697 1e42f4a23d3089281af43bd07f6f0694a90760d0
child 126699 753f285620fe5c029e5d9b96199205db7585d737
push id24492
push userryanvm@gmail.com
push dateSat, 30 Mar 2013 23:31:27 +0000
treeherdermozilla-central@1932c6f78248 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
bugs685652
milestone22.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 685652 - we need a pushPermissionsEnv equivalent to pushPrefEnv in SpecialPowers. r=jdm
content/base/test/test_XHR_anon.html
testing/mochitest/tests/SimpleTest/TestRunner.js
testing/specialpowers/content/specialpowersAPI.js
--- a/content/base/test/test_XHR_anon.html
+++ b/content/base/test/test_XHR_anon.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Test for XMLHttpRequest with system privileges</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
-<body onload="runTests();">
+<body onload="setup();">
 <p id="display">
 <iframe id="loader"></iframe>
 </p>
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script class="testbody" type="application/javascript;version=1.8">
@@ -38,17 +38,16 @@ let am = {
   },
 }
 
 var tests = [ test1, test2, test2a, test3, test3, test3, test4, test4, test4, test5, test5, test5 ];
 
 function runTests() {
   if (!tests.length) {
     am.tearDown();
-    SpecialPowers.removePermission("systemXHR", document);
     SimpleTest.finish();
     return;
   }
 
   var test = tests.shift();
   test();
 }
 
@@ -162,15 +161,17 @@ function test5() {
   };
   xhr.onerror = function onerror() {
     ok(false, "Got an error event!");
     runTests();
   }
   xhr.send();
 }
 
-am.init();
-SpecialPowers.addPermission("systemXHR", true, document);
-SimpleTest.waitForExplicitFinish();
+function setup() {
+  am.init();
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTests);
+}
 </script>
 </pre>
 </body>
 </html>
--- a/testing/mochitest/tests/SimpleTest/TestRunner.js
+++ b/testing/mochitest/tests/SimpleTest/TestRunner.js
@@ -476,17 +476,17 @@ TestRunner.testFinished = function(tests
         } else {
             interstitialURL = "/tests/SimpleTest/iframe-between-tests.html";
         }
         TestRunner._makeIframe(interstitialURL, 0);
     }
 
     SpecialPowers.executeAfterFlushingMessageQueue(function() {
         cleanUpCrashDumpFiles();
-        SpecialPowers.flushPrefEnv(runNextTest);
+        SpecialPowers.flushPermissions(function () { SpecialPowers.flushPrefEnv(runNextTest); });
     });
 };
 
 TestRunner.testUnloaded = function() {
     if (SpecialPowers.isDebugBuild) {
         var newAssertionCount = SpecialPowers.assertionCount();
         var numAsserts = newAssertionCount - TestRunner._lastAssertionCount;
         TestRunner._lastAssertionCount = newAssertionCount;
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -19,16 +19,19 @@ function SpecialPowersAPI() {
   this._consoleListeners = [];
   this._encounteredCrashDumpFiles = [];
   this._unexpectedCrashDumpFiles = { };
   this._crashDumpDir = null;
   this._mfl = null;
   this._prefEnvUndoStack = [];
   this._pendingPrefs = [];
   this._applyingPrefs = false;
+  this._permissionsUndoStack = [];
+  this._pendingPermissions = [];
+  this._applyingPermissions = false;
   this._fm = null;
   this._cb = null;
 }
 
 function bindDOMWindowUtils(aWindow) {
   if (!aWindow)
     return
 
@@ -489,16 +492,160 @@ SpecialPowersAPI.prototype = {
     };
     var crashDumpFiles = this._sendSyncMessage("SPProcessCrashService", message)[0];
     crashDumpFiles.forEach(function(aFilename) {
       self._unexpectedCrashDumpFiles[aFilename] = true;
     });
     return crashDumpFiles;
   },
 
+  /* apply permissions to the system and when the test case is finished (SimpleTest.finish())
+     we will revert the permission back to the original.
+
+     inPermissions is an array of objects where each object has a type, action, context, ex:
+     [{'type': 'SystemXHR', 'allow': 1, 'context': document}, 
+      {'type': 'SystemXHR', 'allow': 0, 'context': document}]
+
+    allow is a boolean and can be true/false or 1/0
+  */
+  pushPermissions: function(inPermissions, callback) {
+    var pendingPermissions = [];
+    var cleanupPermissions = [];
+
+    for (var p in inPermissions) {
+        var permission = inPermissions[p];
+        var originalValue = Ci.nsIPermissionManager.UNKNOWN_ACTION;
+        if (this.testPermission(permission.type, Ci.nsIPermissionManager.ALLOW_ACTION, permission.context)) {
+          originalValue = Ci.nsIPermissionManager.ALLOW_ACTION;
+        } else if (this.testPermission(permission.type, Ci.nsIPermissionManager.DENY_ACTION, permission.context)) {
+          originalValue = Ci.nsIPermissionManager.DENY_ACTION;
+        }
+
+        let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(permission.context);
+
+        let perm = permission.allow ? Ci.nsIPermissionManager.ALLOW_ACTION
+                           : Ci.nsIPermissionManager.DENY_ACTION;
+
+        if (originalValue == perm) {
+          continue;
+        }
+        pendingPermissions.push({'op': 'add', 'type': permission.type, 'permission': perm, 'value': perm, 'url': url, 'appId': appId, 'isInBrowserElement': isInBrowserElement});
+
+        /* Push original permissions value or clear into cleanup array */
+        var cleanupTodo = {'op': 'add', 'type': permission.type, 'permission': perm, 'value': perm, 'url': url, 'appId': appId, 'isInBrowserElement': isInBrowserElement};
+        if (originalValue == Ci.nsIPermissionManager.UNKNOWN_ACTION) {
+          cleanupTodo.op = 'remove';
+        } else {
+          cleeanupTodo.value = originalValue;
+        }
+        cleanupPermissions.push(cleanupTodo);
+    }
+
+    if (pendingPermissions.length > 0) {
+      // The callback needs to be delayed twice. One delay is because the pref
+      // service doesn't guarantee the order it calls its observers in, so it
+      // may notify the observer holding the callback before the other
+      // observers have been notified and given a chance to make the changes
+      // that the callback checks for. The second delay is because pref
+      // observers often defer making their changes by posting an event to the
+      // event loop.
+      function delayedCallback() {
+        function delayAgain() {
+          content.window.setTimeout(callback, 0);
+        }
+        content.window.setTimeout(delayAgain, 0);
+      }
+      this._permissionsUndoStack.push(cleanupPermissions);
+      this._pendingPermissions.push([pendingPermissions, delayedCallback]);
+      this._applyPermissions();
+    } else {
+      content.window.setTimeout(callback, 0);
+    }
+  },
+
+  popPermissions: function(callback) {
+    if (this._permissionsUndoStack.length > 0) {
+      // See pushPermissions comment regarding delay.
+      function delayedCallback() {
+        function delayAgain() {
+          content.window.setTimeout(callback, 0);
+        }
+        content.window.setTimeout(delayAgain, 0);
+      }
+      let cb = callback ? delayedCallback : null;
+      /* Each pop from the stack will yield an object {op/type/permission/value/url/appid/isInBrowserElement} or null */
+      this._pendingPermissions.push([this._permissionsUndoStack.pop(), cb]);
+      this._applyPermissions();
+    } else {
+      content.window.setTimeout(callback, 0);
+    }
+  },
+
+  flushPermissions: function(callback) {
+    while (this._permissionsUndoStack.length > 1)
+      this.popPermissions(null);
+
+    this.popPermissions(callback);
+  },
+
+
+  _permissionObserver: {
+    _lastPermission: {},
+    _callBack: null,
+    _nextCallback: null,
+
+    observe: function (aSubject, aTopic, aData)
+    {
+      if (aTopic == "perm-changed") {
+        var permission = aSubject.QueryInterface(Ci.nsIPermission);
+        if (permission.type == this._lastPermission.type) {
+          var os = Components.classes["@mozilla.org/observer-service;1"]
+                             .getService(Components.interfaces.nsIObserverService);
+          os.removeObserver(this, "perm-changed");
+          content.window.setTimeout(this._callback, 0);
+          content.window.setTimeout(this._nextCallback, 0);
+        }
+      }
+    }
+  },
+
+  /*
+    Iterate through one atomic set of permissions actions and perform allow/deny as appropriate.
+    All actions performed must modify the relevant permission.
+  */
+  _applyPermissions: function() {
+    if (this._applyingPermissions || this._pendingPermissions.length <= 0) {
+      return;
+    }
+
+    /* Set lock and get prefs from the _pendingPrefs queue */
+    this._applyingPermissions = true;
+    var transaction = this._pendingPermissions.shift();
+    var pendingActions = transaction[0];
+    var callback = transaction[1];
+    lastPermission = pendingActions[pendingActions.length-1];
+
+    var self = this;
+    var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+    this._permissionObserver._lastPermission = lastPermission;
+    this._permissionObserver._callback = callback;
+    this._permissionObserver._nextCallback = function () {
+        self._applyingPermissions = false;
+        // Now apply any permissions that may have been queued while we were applying
+        self._applyPermissions();
+    }
+
+    os.addObserver(this._permissionObserver, "perm-changed", false);
+
+    for (var idx in pendingActions) {
+      var perm = pendingActions[idx];
+      this._sendSyncMessage('SPPermissionManager', perm)[0];
+    }
+  },
+
   /*
    * Take in a list of pref changes to make, and invoke |callback| once those
    * changes have taken effect.  When the test finishes, these changes are
    * reverted.
    *
    * |inPrefs| must be an object with up to two properties: "set" and "clear".
    * pushPrefEnv will set prefs as indicated in |inPrefs.set| and will unset
    * the prefs indicated in |inPrefs.clear|.