Bug 1176434 - Enabling indexedDB for content JS sandboxes, r=bent
authorMartin Thomson <martin.thomson@gmail.com>
Thu, 02 Jul 2015 13:30:15 -0700
changeset 251190 335fe5e5e8e5ab88fa11119f27432345291985ce
parent 251189 0fa2a8d57fc67161ce5b724ad131ea0c69e4bc43
child 251191 ee6ec5804e11c7d96c7dbf8fa17be42edd374433
push id61788
push usermartin.thomson@gmail.com
push dateThu, 02 Jul 2015 20:30:40 +0000
treeherdermozilla-inbound@335fe5e5e8e5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs1176434
milestone42.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 1176434 - Enabling indexedDB for content JS sandboxes, r=bent
dom/indexedDB/IDBFactory.cpp
dom/indexedDB/IDBFactory.h
dom/indexedDB/IndexedDatabaseManager.cpp
dom/indexedDB/test/mochitest.ini
dom/indexedDB/test/test_sandbox.html
dom/indexedDB/test/unit/test_sandbox.js
dom/indexedDB/test/unit/xpcshell-shared.ini
js/xpconnect/src/Sandbox.cpp
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -175,28 +175,36 @@ IDBFactory::CreateForWindow(nsPIDOMWindo
     loadContext && loadContext->UsePrivateBrowsing();
 
   factory.forget(aFactory);
   return NS_OK;
 }
 
 // static
 nsresult
-IDBFactory::CreateForChromeJS(JSContext* aCx,
-                              JS::Handle<JSObject*> aOwningObject,
-                              IDBFactory** aFactory)
+IDBFactory::CreateForMainThreadJS(JSContext* aCx,
+                                  JS::Handle<JSObject*> aOwningObject,
+                                  IDBFactory** aFactory)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(nsContentUtils::IsCallerChrome());
 
-  nsAutoPtr<PrincipalInfo> principalInfo(
-    new PrincipalInfo(SystemPrincipalInfo()));
+  nsAutoPtr<PrincipalInfo> principalInfo(new PrincipalInfo());
+  nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aOwningObject);
+  MOZ_ASSERT(principal);
+  bool isSystem;
+  if (!AllowedForPrincipal(principal, &isSystem)) {
+    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+  }
 
-  nsresult rv =
-    CreateForMainThreadJSInternal(aCx, aOwningObject, principalInfo, aFactory);
+  nsresult rv = PrincipalToPrincipalInfo(principal, principalInfo);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = CreateForMainThreadJSInternal(aCx, aOwningObject, principalInfo, aFactory);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   MOZ_ASSERT(!principalInfo);
 
   return NS_OK;
 }
--- a/dom/indexedDB/IDBFactory.h
+++ b/dom/indexedDB/IDBFactory.h
@@ -81,19 +81,19 @@ class IDBFactory final
   bool mPrivateBrowsingMode;
 
 public:
   static nsresult
   CreateForWindow(nsPIDOMWindow* aWindow,
                   IDBFactory** aFactory);
 
   static nsresult
-  CreateForChromeJS(JSContext* aCx,
-                    JS::Handle<JSObject*> aOwningObject,
-                    IDBFactory** aFactory);
+  CreateForMainThreadJS(JSContext* aCx,
+                        JS::Handle<JSObject*> aOwningObject,
+                        IDBFactory** aFactory);
 
   static nsresult
   CreateForDatastore(JSContext* aCx,
                     JS::Handle<JSObject*> aOwningObject,
                     IDBFactory** aFactory);
 
   static nsresult
   CreateForWorker(JSContext* aCx,
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -577,17 +577,16 @@ IndexedDatabaseManager::CommonPostHandle
 }
 
 // static
 bool
 IndexedDatabaseManager::DefineIndexedDB(JSContext* aCx,
                                         JS::Handle<JSObject*> aGlobal)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(nsContentUtils::IsCallerChrome(), "Only for chrome!");
   MOZ_ASSERT(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL,
              "Passed object is not a global object!");
 
   // We need to ensure that the manager has been created already here so that we
   // load preferences that may control which properties are exposed.
   if (NS_WARN_IF(!GetOrCreate())) {
     return false;
   }
@@ -604,19 +603,19 @@ IndexedDatabaseManager::DefineIndexedDB(
       !IDBRequestBinding::GetConstructorObject(aCx, aGlobal) ||
       !IDBTransactionBinding::GetConstructorObject(aCx, aGlobal) ||
       !IDBVersionChangeEventBinding::GetConstructorObject(aCx, aGlobal))
   {
     return false;
   }
 
   nsRefPtr<IDBFactory> factory;
-  if (NS_FAILED(IDBFactory::CreateForChromeJS(aCx,
-                                              aGlobal,
-                                              getter_AddRefs(factory)))) {
+  if (NS_FAILED(IDBFactory::CreateForMainThreadJS(aCx,
+                                                  aGlobal,
+                                                  getter_AddRefs(factory)))) {
     return false;
   }
 
   MOZ_ASSERT(factory, "This should never fail for chrome!");
 
   JS::Rooted<JS::Value> indexedDB(aCx);
   js::AssertSameCompartment(aCx, aGlobal);
   if (!GetOrCreateDOMReflector(aCx, factory, &indexedDB)) {
@@ -935,17 +934,17 @@ IndexedDatabaseManager::LoggingModePrefC
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!Preferences::GetBool(kPrefLoggingEnabled)) {
     sLoggingMode = Logging_Disabled;
     return;
   }
 
-  bool useProfiler = 
+  bool useProfiler =
 #if defined(DEBUG) || defined(MOZ_ENABLE_PROFILER_SPS)
     Preferences::GetBool(kPrefLoggingProfiler);
 #if !defined(MOZ_ENABLE_PROFILER_SPS)
   if (useProfiler) {
     NS_WARNING("IndexedDB cannot create profiler marks because this build does "
                "not have profiler extensions enabled!");
     useProfiler = false;
   }
--- a/dom/indexedDB/test/mochitest.ini
+++ b/dom/indexedDB/test/mochitest.ini
@@ -327,16 +327,18 @@ skip-if = (buildapp == 'b2g' && toolkit 
 [test_readwriteflush_disabled.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_remove_index.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_remove_objectStore.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || (buildapp == 'mulet') # Bug 931116 # TC: Bug 1144079 - Re-enable Mulet mochitests and reftests taskcluster-specific disables.
 [test_request_readyState.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
+[test_sandbox.html]
+skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_setVersion.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_setVersion_abort.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_setVersion_events.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_setVersion_exclusion.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_sandbox.html
@@ -0,0 +1,101 @@
+<!doctype html>
+<html>
+<head>
+  <title>indexedDB in JS Sandbox</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"></link>
+</head>
+<body>
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// This runs inside a same-origin sandbox.
+// The intent being to show that the data store is the same.
+function storeValue() {
+  function createDB_inner() {
+    var op = indexedDB.open('db');
+    op.onupgradeneeded = e => {
+      var db = e.target.result;
+      db.createObjectStore('store');
+    };
+    return new Promise(resolve => {
+      op.onsuccess = e => resolve(e.target.result);
+    });
+  }
+
+  function add(k, v) {
+    return createDB_inner().then(db => {
+      var tx = db.transaction('store', 'readwrite');
+      var store = tx.objectStore('store');
+      var op = store.add(v, k);
+      return new Promise((resolve, reject) => {
+        op.onsuccess = e => resolve(e.target.result);
+        op.onerror = _ => reject(op.error);
+        tx.onabort = _ => reject(tx.error);
+      });
+    });
+  }
+
+  return add('x', [ 10, {} ])
+    .then(_ => step_done(),
+          _ => ok(false, 'failed to store'));
+}
+
+function createDB_outer() {
+  var op = indexedDB.open('db');
+  op.onupgradeneeded = e => {
+    ok(false, 'upgrade should not be needed');
+    var db = e.target.result;
+    db.createObjectStore('store');
+  };
+  return new Promise(resolve => {
+    op.onsuccess = e => resolve(e.target.result);
+  });
+}
+
+function get(k) {
+  return createDB_outer().then(db => {
+      var tx = db.transaction('store', 'readonly');
+      var store = tx.objectStore('store');
+      var op = store.get(k);
+      return new Promise((resolve, reject) => {
+        op.onsuccess = e => resolve(e.target.result);
+        op.onerror = _ => reject(op.error);
+        tx.onabort = _ => reject(tx.error);
+      });
+  });
+}
+
+function runInSandbox(sandbox, testFunc) {
+  is(typeof testFunc, 'function');
+  var resolvePromise;
+  var testPromise = new Promise(r => resolvePromise = r);
+  SpecialPowers.Cu.exportFunction(_ => resolvePromise(), sandbox,
+                                  { defineAs: 'step_done' });
+  SpecialPowers.Cu.evalInSandbox('(' + testFunc.toSource() + ')()' +
+                                 '.then(step_done);', sandbox);
+  return testPromise;
+}
+
+// Use the window principal for the sandbox; location.origin is not sufficient.
+var sb = new SpecialPowers.Cu.Sandbox(window,
+                                      { wantGlobalProperties: ['indexedDB'] });
+
+sb.ok = SpecialPowers.Cu.exportFunction(ok, sb);
+
+Promise.resolve()
+  .then(_ => runInSandbox(sb, storeValue))
+  .then(_ => get('x'))
+  .then(x => {
+    ok(x, 'a value should be present');
+    is(x.length, 2);
+    is(x[0], 10);
+    is(typeof x[1], 'object');
+    is(Object.keys(x[1]).length, 0);
+  })
+  .then(_ => SimpleTest.finish());
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/unit/test_sandbox.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function exerciseInterface() {
+  function DB(name, store) {
+    this.name = name;
+    this.store = store;
+    this._db = this._create();
+  }
+
+  DB.prototype = {
+    _create: function() {
+      var op = indexedDB.open(this.name);
+      op.onupgradeneeded = e => {
+        var db = e.target.result;
+        db.createObjectStore(this.store);
+      };
+      return new Promise(resolve => {
+        op.onsuccess = e => resolve(e.target.result);
+      });
+    },
+
+    _result: function(tx, op) {
+      return new Promise((resolve, reject) => {
+        op.onsuccess = e => resolve(e.target.result);
+        op.onerror = () => reject(op.error);
+        tx.onabort = () => reject(tx.error);
+      });
+    },
+
+    get: function(k) {
+      return this._db.then(db => {
+        var tx = db.transaction(this.store, 'readonly');
+        var store = tx.objectStore(this.store);
+        return this._result(tx, store.get(k));
+      });
+    },
+
+    add: function(k, v) {
+      return this._db.then(db => {
+        var tx = db.transaction(this.store, 'readwrite');
+        var store = tx.objectStore(this.store);
+        return this._result(tx, store.add(v, k));
+      });
+    }
+  };
+
+  var db = new DB('data', 'base');
+  return db.add('x', [ 10, {} ])
+    .then(_ => db.get('x'))
+    .then(x => {
+      equal(x.length, 2);
+      equal(x[0], 10);
+      equal(typeof x[1], 'object');
+      equal(Object.keys(x[1]).length, 0);
+    });
+}
+
+function run_test() {
+  do_get_profile();
+
+  let Cu = Components.utils;
+  let sb = new Cu.Sandbox('https://www.example.com',
+                          { wantGlobalProperties: ['indexedDB'] });
+
+  sb.equal = equal;
+  var innerPromise = new Promise((resolve, reject) => {
+    sb.test_done = resolve;
+    sb.test_error = reject;
+  });
+  Cu.evalInSandbox('(' + exerciseInterface.toSource() + ')()' +
+                   '.then(test_done, test_error);', sb);
+
+  Cu.importGlobalProperties(['indexedDB']);
+  do_test_pending();
+  Promise.all([innerPromise, exerciseInterface()])
+    .then(do_test_finished);
+}
--- a/dom/indexedDB/test/unit/xpcshell-shared.ini
+++ b/dom/indexedDB/test/unit/xpcshell-shared.ini
@@ -54,16 +54,17 @@ skip-if = toolkit == 'android' # bug 107
 [test_overlapping_transactions.js]
 [test_persistenceType.js]
 [test_put_get_values.js]
 [test_put_get_values_autoIncrement.js]
 [test_readonly_transactions.js]
 [test_remove_index.js]
 [test_remove_objectStore.js]
 [test_request_readyState.js]
+[test_sandbox.js]
 [test_setVersion.js]
 [test_setVersion_abort.js]
 [test_setVersion_events.js]
 [test_setVersion_exclusion.js]
 [test_success_events_after_abort.js]
 [test_table_locks.js]
 [test_table_rollback.js]
 [test_traffic_jam.js]
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -911,17 +911,17 @@ xpc::GlobalProperties::Parse(JSContext* 
 }
 
 bool
 xpc::GlobalProperties::Define(JSContext* cx, JS::HandleObject obj)
 {
     if (CSS && !dom::CSSBinding::GetConstructorObject(cx, obj))
         return false;
 
-    if (indexedDB && AccessCheck::isChrome(obj) &&
+    if (indexedDB &&
         !IndexedDatabaseManager::DefineIndexedDB(cx, obj))
         return false;
 
     if (XMLHttpRequest &&
         !dom::XMLHttpRequestBinding::GetConstructorObject(cx, obj))
         return false;
 
     if (TextEncoder &&