Bug 871445 - patch 10 - DataStore: indexedDB permissions, r=ehsan
☠☠ backed out by ca1c692e20cb ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 02 Oct 2013 11:30:34 -0400
changeset 149713 2767ff221c9240899735ad7667dfd50a8db6ede2
parent 149712 eae1be292a21ecf156a686207d246d24908106ae
child 149714 b694df7e2faaeed7debecaa0f90c2acea3894fc4
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersehsan
bugs871445
milestone27.0a1
Bug 871445 - patch 10 - DataStore: indexedDB permissions, r=ehsan
dom/apps/src/Webapps.jsm
dom/datastore/DataStore.jsm
dom/datastore/DataStoreChangeNotifier.jsm
dom/datastore/DataStoreDB.jsm
dom/datastore/DataStoreService.js
dom/datastore/DataStoreServiceInternal.jsm
dom/datastore/moz.build
dom/datastore/nsIDataStoreService.idl
dom/datastore/tests/test_app_install.html
dom/datastore/tests/test_arrays.html
dom/datastore/tests/test_basic.html
dom/datastore/tests/test_changes.html
dom/datastore/tests/test_oop.html
dom/datastore/tests/test_readonly.html
dom/datastore/tests/test_revision.html
testing/mochitest/manifest.webapp
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -279,16 +279,17 @@ this.DOMApplicationRegistry = {
   updateDataStoreForApp: function(aId) {
     if (!this.webapps[aId]) {
       return;
     }
 
     // Create or Update the DataStore for this app
     this._readManifests([{ id: aId }], (function(aResult) {
       this.updateDataStore(this.webapps[aId].localId,
+                           this.webapps[aId].origin,
                            this.webapps[aId].manifestURL,
                            aResult[0].manifest);
     }).bind(this));
   },
 
   updatePermissionsForApp: function updatePermissionsForApp(aId) {
     if (!this.webapps[aId]) {
       return;
@@ -525,58 +526,63 @@ this.DOMApplicationRegistry = {
         // At first run, install preloaded apps and set up their permissions.
         for (let id in this.webapps) {
           this.installPreinstalledApp(id);
           if (!this.webapps[id]) {
             continue;
           }
           this.updateOfflineCacheForApp(id);
           this.updatePermissionsForApp(id);
-          this.updateDataStoreForApp(id);
         }
         // Need to update the persisted list of apps since
         // installPreinstalledApp() removes the ones failing to install.
         this._saveApps();
       }
+
+      // DataStores must be initialized at startup.
+      for (let id in this.webapps) {
+        this.updateDataStoreForApp(id);
+      }
+
       this.registerAppsHandlers(runUpdate);
     }).bind(this);
 
     this.loadCurrentRegistry((function() {
 #ifdef MOZ_WIDGET_GONK
       // if first run, merge the system apps.
       if (runUpdate)
         this.installSystemApps(onAppsLoaded);
       else
         onAppsLoaded();
 #else
       onAppsLoaded();
 #endif
     }).bind(this));
   },
 
-  updateDataStore: function(aId, aManifestURL, aManifest) {
+  updateDataStore: function(aId, aOrigin, aManifestURL, aManifest) {
     if ('datastores-owned' in aManifest) {
       for (let name in aManifest['datastores-owned']) {
         let readonly = "access" in aManifest['datastores-owned'][name]
                          ? aManifest['datastores-owned'][name].access == 'readonly'
                          : false;
 
-        dataStoreService.installDataStore(aId, name, aManifestURL, readonly);
+        dataStoreService.installDataStore(aId, name, aOrigin, aManifestURL,
+                                          readonly);
       }
     }
 
     if ('datastores-access' in aManifest) {
       for (let name in aManifest['datastores-access']) {
         let readonly = ("readonly" in aManifest['datastores-access'][name]) &&
                        !aManifest['datastores-access'][name].readonly
                          ? false : true;
 
-        // The first release is always in readonly mode.
-        dataStoreService.installAccessDataStore(aId, name, aManifestURL,
-                                                /* readonly */ true);
+        dataStoreService.installAccessDataStore(aId, name, aOrigin,
+                                                aManifestURL, readonly);
       }
     }
   },
 
   // |aEntryPoint| is either the entry_point name or the null in which case we
   // use the root of the manifest.
   //
   // TODO Bug 908094 Refine _registerSystemMessagesForEntryPoint(...).
@@ -1429,17 +1435,18 @@ this.DOMApplicationRegistry = {
           this.updateAppHandlers(aOldManifest, aData, app);
           if (supportUseCurrentProfile()) {
             PermissionsInstaller.installPermissions(
               { manifest: aData,
                 origin: app.origin,
                 manifestURL: app.manifestURL },
               true);
           }
-          this.updateDataStore(this.webapps[id].localId, app.manifestURL, aData);
+          this.updateDataStore(this.webapps[id].localId, app.origin,
+                               app.manifestURL, aData);
           this.broadcastMessage("Webapps:PackageEvent",
                                 { type: "applied",
                                   manifestURL: app.manifestURL,
                                   app: app,
                                   manifest: aData });
         }).bind(this));
       }).bind(this));
     }).bind(this));
@@ -1635,17 +1642,18 @@ this.DOMApplicationRegistry = {
           // Update the permissions for this app.
           PermissionsInstaller.installPermissions({
             manifest: app.manifest,
             origin: app.origin,
             manifestURL: aData.manifestURL
           }, true);
         }
 
-        this.updateDataStore(this.webapps[id].localId, app.manifestURL, app.manifest);
+        this.updateDataStore(this.webapps[id].localId, app.origin,
+                             app.manifestURL, app.manifest);
 
         app.name = manifest.name;
         app.csp = manifest.csp || "";
         app.role = manifest.role || "";
         app.updateTime = Date.now();
       } else {
         manifest = new ManifestHelper(aOldManifest, app.origin);
       }
@@ -2137,18 +2145,18 @@ this.DOMApplicationRegistry = {
           if (supportUseCurrentProfile()) {
             // Update the permissions for this app.
             PermissionsInstaller.installPermissions({ manifest: aManifest,
                                                       origin: appObject.origin,
                                                       manifestURL: appObject.manifestURL },
                                                     true);
           }
 
-          this.updateDataStore(this.webapps[aId].localId, appObject.manifestURL,
-                               aManifest);
+          this.updateDataStore(this.webapps[aId].localId, appObject.origin,
+                               appObject.manifestURL, aManifest);
 
           debug("About to fire Webapps:PackageEvent 'installed'");
           this.broadcastMessage("Webapps:PackageEvent",
                                 { type: "installed",
                                   manifestURL: appObject.manifestURL,
                                   app: app,
                                   manifest: aManifest });
           if (installSuccessCallback) {
@@ -2243,18 +2251,18 @@ this.DOMApplicationRegistry = {
         PermissionsInstaller.installPermissions({ origin: appObject.origin,
                                                   manifestURL: appObject.manifestURL,
                                                   manifest: jsonManifest },
                                                 isReinstall, (function() {
           this.uninstall(aData, aData.mm);
         }).bind(this));
       }
 
-      this.updateDataStore(this.webapps[id].localId, this.webapps[id].manifestURL,
-                           jsonManifest);
+      this.updateDataStore(this.webapps[id].localId,  this.webapps[id].origin,
+                           this.webapps[id].manifestURL, jsonManifest);
     }
 
     ["installState", "downloadAvailable",
      "downloading", "downloadSize", "readyToApplyDownload"].forEach(function(aProp) {
       aData.app[aProp] = appObject[aProp];
      });
 
     if (manifest.appcache_path) {
--- a/dom/datastore/DataStore.jsm
+++ b/dom/datastore/DataStore.jsm
@@ -98,17 +98,17 @@ this.DataStore.prototype = {
     this._db = new DataStoreDB();
     this._db.init(aOwner, aName);
 
     let self = this;
     Services.obs.addObserver(function(aSubject, aTopic, aData) {
       let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
       if (wId == self._innerWindowID) {
         cpmm.removeMessageListener("DataStore:Changed:Return:OK", self);
-        self._db.delete();
+        self._db.close();
       }
     }, "inner-window-destroyed", false);
 
     let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                       .getInterface(Ci.nsIDOMWindowUtils);
     this._innerWindowID = util.currentInnerWindowID;
 
     cpmm.addMessageListener("DataStore:Changed:Return:OK", this);
@@ -273,36 +273,28 @@ this.DataStore.prototype = {
         aSuccessCb();
       }
     );
   },
 
   retrieveRevisionId: function(aSuccessCb) {
     let self = this;
     this._db.revisionTxn(
-      'readwrite',
+      'readonly',
       function(aTxn, aRevisionStore) {
         debug("RetrieveRevisionId transaction success");
 
         let request = aRevisionStore.openCursor(null, 'prev');
         request.onsuccess = function(aEvent) {
           let cursor = aEvent.target.result;
-          if (!cursor) {
-            // If the revision doesn't exist, let's create the first one.
-            self.addRevision(aRevisionStore, 0, REVISION_VOID,
-              function(aRevisionId) {
-                self._revisionId = aRevisionId;
-                aSuccessCb();
-              }
-            );
-            return;
+          if (cursor) {
+            self._revisionId = cursor.value.revisionId;
           }
 
-          self._revisionId = cursor.value.revisionId;
-          aSuccessCb();
+          aSuccessCb(self._revisionId);
         };
       }
     );
   },
 
   sendNotification: function(aId, aOperation, aRevisionId) {
     debug("SendNotification");
     if (aOperation != REVISION_VOID) {
@@ -470,17 +462,17 @@ this.DataStore.prototype = {
               // Initially we use maps, and then we convert them in array.
               let changes = {
                 revisionId: '',
                 addedIds: {},
                 updatedIds: {},
                 removedIds: {}
               };
 
-              let request = aStore.mozGetAll(self._window.IDBKeyRange.lowerBound(aInternalRevisionId, true));
+              let request = aStore.mozGetAll(IDBKeyRange.lowerBound(aInternalRevisionId, true));
               request.onsuccess = function(aEvent) {
                 for (let i = 0; i < aEvent.target.result.length; ++i) {
                   let data = aEvent.target.result[i];
 
                   switch (data.operation) {
                     case REVISION_ADDED:
                       changes.addedIds[data.objectId] = true;
                       break;
--- a/dom/datastore/DataStoreChangeNotifier.jsm
+++ b/dom/datastore/DataStoreChangeNotifier.jsm
@@ -7,16 +7,19 @@
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 this.EXPORTED_SYMBOLS = ["DataStoreChangeNotifier"];
 
 function debug(s) {
   //dump('DEBUG DataStoreChangeNotifier: ' + s + '\n');
 }
 
+// DataStoreServiceInternal should not be converted into a lazy getter as it
+// runs code during initialization.
+Cu.import('resource://gre/modules/DataStoreServiceInternal.jsm');
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageBroadcaster");
 
 this.DataStoreChangeNotifier = {
--- a/dom/datastore/DataStoreDB.jsm
+++ b/dom/datastore/DataStoreDB.jsm
@@ -34,18 +34,18 @@ DataStoreDB.prototype = {
     debug('updateSchema');
     aDb.createObjectStore(DATASTOREDB_OBJECTSTORE_NAME, { autoIncrement: true });
     let store = aDb.createObjectStore(DATASTOREDB_REVISION,
                                       { autoIncrement: true,
                                         keyPath: 'internalRevisionId' });
     store.createIndex(DATASTOREDB_REVISION_INDEX, 'revisionId', { unique: true });
   },
 
-  init: function(aOrigin, aName) {
-    let dbName = aOrigin + '_' + aName;
+  init: function(aOwner, aName) {
+    let dbName = aName + '|' + aOwner;
     this.initDBHelper(dbName, DATASTOREDB_VERSION,
                       [DATASTOREDB_OBJECTSTORE_NAME, DATASTOREDB_REVISION]);
   },
 
   txn: function(aType, aCallback, aErrorCb) {
     debug('Transaction request');
     this.newTxn(
       aType,
--- a/dom/datastore/DataStoreService.js
+++ b/dom/datastore/DataStoreService.js
@@ -12,134 +12,335 @@ function debug(s) {
   // dump('DEBUG DataStoreService: ' + s + '\n');
 }
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://gre/modules/DataStore.jsm');
+Cu.import("resource://gre/modules/DataStoreDB.jsm");
 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageBroadcaster");
 
+XPCOMUtils.defineLazyServiceGetter(this, "permissionManager",
+                                   "@mozilla.org/permissionmanager;1",
+                                   "nsIPermissionManager");
+
+XPCOMUtils.defineLazyServiceGetter(this, "secMan",
+                                   "@mozilla.org/scriptsecuritymanager;1",
+                                   "nsIScriptSecurityManager");
+
 /* DataStoreService */
 
 const DATASTORESERVICE_CID = Components.ID('{d193d0e2-c677-4a7b-bb0a-19155b470f2e}');
+const REVISION_VOID = "void";
 
 function DataStoreService() {
   debug('DataStoreService Constructor');
 
-  let obs = Services.obs;
-  if (!obs) {
-    debug("DataStore Error: observer-service is null!");
-    return;
+  this.inParent = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+                    .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+
+  if (this.inParent) {
+    let obs = Services.obs;
+    if (!obs) {
+      debug("DataStore Error: observer-service is null!");
+      return;
+    }
+
+    obs.addObserver(this, 'webapps-clear-data', false);
   }
 
-  obs.addObserver(this, 'webapps-clear-data', false);
-
-  let inParent = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
-                   .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-  if (inParent) {
-    ppmm.addMessageListener("DataStore:Get", this);
-  }
+  let self = this;
+  cpmm.addMessageListener("datastore-first-revision-created",
+                          function(aMsg) { self.receiveMessage(aMsg); });
 }
 
 DataStoreService.prototype = {
+  inParent: false,
+
   // Hash of DataStores
   stores: {},
   accessStores: {},
+  pendingRequests: {},
 
-  installDataStore: function(aAppId, aName, aOwner, aReadOnly) {
+  installDataStore: function(aAppId, aName, aOrigin, aOwner, aReadOnly) {
     debug('installDataStore - appId: ' + aAppId + ', aName: ' +
-          aName + ', aOwner:' + aOwner + ', aReadOnly: ' +
-          aReadOnly);
+          aName + ', aOrigin: ' + aOrigin + ', aOwner:' + aOwner +
+          ', aReadOnly: ' + aReadOnly);
+
+    this.checkIfInParent();
 
     if (aName in this.stores && aAppId in this.stores[aName]) {
       debug('This should not happen');
       return;
     }
 
     if (!(aName in this.stores)) {
       this.stores[aName] = {};
     }
 
-    this.stores[aName][aAppId] = { owner: aOwner, readOnly: aReadOnly };
+    // A DataStore is enabled when it has a first valid revision.
+    this.stores[aName][aAppId] = { origin: aOrigin, owner: aOwner,
+                                   readOnly: aReadOnly, enabled: false };
+
+    this.addPermissions(aAppId, aName, aOrigin, aOwner, aReadOnly);
+
+    this.createFirstRevisionId(aAppId, aName, aOwner);
   },
 
-  installAccessDataStore: function(aAppId, aName, aOwner, aReadOnly) {
-    debug('installDataStore - appId: ' + aAppId + ', aName: ' +
-          aName + ', aOwner:' + aOwner + ', aReadOnly: ' +
-          aReadOnly);
+  installAccessDataStore: function(aAppId, aName, aOrigin, aOwner, aReadOnly) {
+    debug('installAccessDataStore - appId: ' + aAppId + ', aName: ' +
+          aName + ', aOrigin: ' + aOrigin + ', aOwner:' + aOwner +
+          ', aReadOnly: ' + aReadOnly);
+
+    this.checkIfInParent();
 
     if (aName in this.accessStores && aAppId in this.accessStores[aName]) {
       debug('This should not happen');
       return;
     }
 
     if (!(aName in this.accessStores)) {
       this.accessStores[aName] = {};
     }
 
-    this.accessStores[aName][aAppId] = { owner: aOwner, readOnly: aReadOnly };
+    this.accessStores[aName][aAppId] = { origin: aOrigin, owner: aOwner,
+                                         readOnly: aReadOnly };
+    this.addAccessPermissions(aAppId, aName, aOrigin, aOwner, aReadOnly);
+  },
+
+  checkIfInParent: function() {
+    if (!this.inParent) {
+      throw "DataStore can execute this operation just in the parent process";
+    }
+  },
+
+  createFirstRevisionId: function(aAppId, aName, aOwner) {
+    debug("createFirstRevisionId database: " + aName);
+
+    let self = this;
+    let db = new DataStoreDB();
+    db.init(aOwner, aName);
+    db.revisionTxn(
+      'readwrite',
+      function(aTxn, aRevisionStore) {
+        debug("createFirstRevisionId - transaction success");
+
+        let request = aRevisionStore.openCursor(null, 'prev');
+        request.onsuccess = function(aEvent) {
+          let cursor = aEvent.target.result;
+          if (!cursor) {
+            // If the revision doesn't exist, let's create the first one.
+            db.addRevision(aRevisionStore, 0, REVISION_VOID, function() {
+              debug("First revision created.");
+              if (aName in self.stores && aAppId in self.stores[aName]) {
+                self.stores[aName][aAppId].enabled = true;
+                ppmm.broadcastAsyncMessage('datastore-first-revision-created',
+                                           { name: aName, owner: aOwner });
+              }
+            });
+          }
+        };
+      }
+    );
+  },
+
+  addPermissions: function(aAppId, aName, aOrigin, aOwner, aReadOnly) {
+    // When a new DataStore is installed, the permissions must be set for the
+    // owner app.
+    let permission = "indexedDB-chrome-" + aName + '|' + aOwner;
+    this.resetPermissions(aAppId, aOrigin, aOwner, permission, aReadOnly);
+
+    // For any app that wants to have access to this DataStore we add the
+    // permissions.
+    if (aName in this.accessStores) {
+      for (let appId in this.accessStores[aName]) {
+        // ReadOnly is decided by the owner first.
+        let readOnly = aReadOnly || this.accessStores[aName][appId].readOnly;
+        this.resetPermissions(appId, this.accessStores[aName][appId].origin,
+                              this.accessStores[aName][appId].owner,
+                              permission, readOnly);
+      }
+    }
+  },
+
+  addAccessPermissions: function(aAppId, aName, aOrigin, aOwner, aReadOnly) {
+    // When an app wants to have access to a DataStore, the permissions must be
+    // set.
+    if (!(aName in this.stores)) {
+      return;
+    }
+
+    for (let appId in this.stores[aName]) {
+      let permission = "indexedDB-chrome-" + aName + '|' + this.stores[aName][appId].owner;
+      // The ReadOnly is decied by the owenr first.
+      let readOnly = this.stores[aName][appId].readOnly || aReadOnly;
+      this.resetPermissions(aAppId, aOrigin, aOwner, permission, readOnly);
+    }
+  },
+
+  resetPermissions: function(aAppId, aOrigin, aOwner, aPermission, aReadOnly) {
+    debug("ResetPermissions - appId: " + aAppId + " - origin: " + aOrigin +
+          " - owner: " + aOwner + " - permissions: " + aPermission +
+          " - readOnly: " + aReadOnly);
+
+    let uri = Services.io.newURI(aOrigin, null, null);
+    let principal = secMan.getAppCodebasePrincipal(uri, aAppId, false);
+
+    let result = permissionManager.testExactPermissionFromPrincipal(principal,
+                                                                    aPermission + '-write');
+
+    if (aReadOnly && result == Ci.nsIPermissionManager.ALLOW_ACTION) {
+      debug("Write permission removed");
+      permissionManager.removeFromPrincipal(principal, aPermission + '-write');
+    } else if (!aReadOnly && result != Ci.nsIPermissionManager.ALLOW_ACTION) {
+      debug("Write permission added");
+      permissionManager.addFromPrincipal(principal, aPermission + '-write',
+                                         Ci.nsIPermissionManager.ALLOW_ACTION);
+    }
+
+    result = permissionManager.testExactPermissionFromPrincipal(principal,
+                                                                aPermission + '-read');
+    if (result != Ci.nsIPermissionManager.ALLOW_ACTION) {
+      debug("Read permission added");
+      permissionManager.addFromPrincipal(principal, aPermission + '-read',
+                                         Ci.nsIPermissionManager.ALLOW_ACTION);
+    }
+
+    result = permissionManager.testExactPermissionFromPrincipal(principal, aPermission);
+    if (result != Ci.nsIPermissionManager.ALLOW_ACTION) {
+      debug("Generic permission added");
+      permissionManager.addFromPrincipal(principal, aPermission,
+                                         Ci.nsIPermissionManager.ALLOW_ACTION);
+    }
   },
 
   getDataStores: function(aWindow, aName) {
     debug('getDataStores - aName: ' + aName);
 
-    // This method can be called in the child so we need to send a request to
-    // the parent and create DataStore object here.
-
+    let self = this;
     return new aWindow.Promise(function(resolve, reject) {
-      new DataStoreServiceChild(aWindow, aName, resolve);
+      // If this request comes from the main process, we have access to the
+      // window, so we can skip the ipc communication.
+      if (self.inParent) {
+        let stores = self.getDataStoresInfo(aName, aWindow.document.nodePrincipal.appId);
+        self.getDataStoreCreate(aWindow, resolve, stores);
+      } else {
+        // This method can be called in the child so we need to send a request
+        // to the parent and create DataStore object here.
+        new DataStoreServiceChild(aWindow, aName, function(aStores) {
+          debug("DataStoreServiceChild callback!");
+          self.getDataStoreCreate(aWindow, resolve, aStores);
+        });
+      }
     });
   },
 
-  receiveMessage: function(aMessage) {
-    if (aMessage.name != 'DataStore:Get') {
-      return;
-    }
-
-    let msg = aMessage.data;
-
-    // This is a security issue and it will be fixed by Bug 916091
-    let appId = msg.appId;
+  getDataStoresInfo: function(aName, aAppId) {
+    debug('GetDataStoresInfo');
 
     let results = [];
 
-    if (msg.name in this.stores) {
-      if (appId in this.stores[msg.name]) {
-        results.push({ store: this.stores[msg.name][appId],
-                       readOnly: false });
+    if (aName in this.stores) {
+      if (aAppId in this.stores[aName]) {
+        results.push({ name: aName,
+                       owner: this.stores[aName][aAppId].owner,
+                       readOnly: false,
+                       enabled: this.stores[aName][aAppId].enabled });
       }
 
-      for (var i in this.stores[msg.name]) {
-        if (i == appId) {
+      for (var i in this.stores[aName]) {
+        if (i == aAppId) {
           continue;
         }
 
-        let access = this.getDataStoreAccess(msg.name, appId);
+        let access = this.getDataStoreAccess(aName, aAppId);
         if (!access) {
           continue;
         }
 
-        let readOnly = this.stores[msg.name][i].readOnly || access.readOnly;
-        results.push({ store: this.stores[msg.name][i],
-                       readOnly: readOnly });
+        let readOnly = this.stores[aName][i].readOnly || access.readOnly;
+        results.push({ name: aName,
+                       owner: this.stores[aName][i].owner,
+                       readOnly: readOnly,
+                       enabled: this.stores[aName][i].enabled });
+      }
+    }
+
+    return results;
+  },
+
+  getDataStoreCreate: function(aWindow, aResolve, aStores) {
+    debug("GetDataStoreCreate");
+
+    let results = [];
+
+    if (!aStores.length) {
+      aResolve(results);
+      return;
+    }
+
+    let pendingDataStores = [];
+
+    for (let i = 0; i < aStores.length; ++i) {
+      if (!aStores[i].enabled) {
+        pendingDataStores.push(aStores[i].owner);
       }
     }
 
-    msg.stores = results;
-    aMessage.target.sendAsyncMessage("DataStore:Get:Return", msg);
+    if (!pendingDataStores.length) {
+      this.getDataStoreResolve(aWindow, aResolve, aStores);
+      return;
+    }
+
+    if (!(aStores[0].name in this.pendingRequests)) {
+      this.pendingRequests[aStores[0].name] = [];
+    }
+
+    this.pendingRequests[aStores[0].name].push({ window: aWindow,
+                                                 resolve: aResolve,
+                                                 stores: aStores,
+                                                 pendingDataStores: pendingDataStores });
+  },
+
+  getDataStoreResolve: function(aWindow, aResolve, aStores) {
+    debug("GetDataStoreResolve");
+
+    let callbackPending = aStores.length;
+    let results = [];
+
+    if (!callbackPending) {
+      aResolve(results);
+      return;
+    }
+
+    for (let i = 0; i < aStores.length; ++i) {
+      let obj = new DataStore(aWindow, aStores[i].name,
+                              aStores[i].owner, aStores[i].readOnly);
+      let exposedObj = aWindow.DataStore._create(aWindow, obj);
+      results.push(exposedObj);
+
+      obj.retrieveRevisionId(
+        function() {
+          --callbackPending;
+          if (!callbackPending) {
+            aResolve(results);
+          }
+        }
+      );
+    }
   },
 
   getDataStoreAccess: function(aName, aAppId) {
     if (!(aName in this.accessStores) ||
         !(aAppId in this.accessStores[aName])) {
       return null;
     }
 
@@ -157,84 +358,98 @@ DataStoreService.prototype = {
 
     // DataStore is explosed to apps, not browser content.
     if (params.browserOnly) {
       return;
     }
 
     for (let key in this.stores) {
       if (params.appId in this.stores[key]) {
+        this.deleteDatabase(key, this.stores[key][params.appId].owner);
         delete this.stores[key][params.appId];
       }
 
       if (!this.stores[key].length) {
         delete this.stores[key];
       }
     }
   },
 
+  deleteDatabase: function(aName, aOwner) {
+    debug("delete database: " + aName);
+
+    let db = new DataStoreDB();
+    db.init(aOwner, aName);
+    db.delete();
+  },
+
+  receiveMessage: function(aMsg) {
+    debug("receiveMessage");
+    let data = aMsg.json;
+
+    if (!(data.name in this.pendingRequests)) {
+      return;
+    }
+
+    for (let i = 0; i < this.pendingRequests[data.name].length;) {
+      let pos = this.pendingRequests[data.name][i].pendingDataStores.indexOf(data.owner);
+      if (pos != -1) {
+        this.pendingRequests[data.name][i].pendingDataStores.splice(pos, 1);
+        if (!this.pendingRequests[data.name][i].pendingDataStores.length) {
+          this.getDataStoreResolve(this.pendingRequests[data.name][i].window,
+                                   this.pendingRequests[data.name][i].resolve,
+                                   this.pendingRequests[data.name][i].stores);
+          this.pendingRequests[data.name].splice(i, 1);
+          continue;
+        }
+      }
+
+      ++i;
+    }
+
+    if (!this.pendingRequests[data.name].length) {
+      delete this.pendingRequests[data.name];
+    }
+  },
+
   classID : DATASTORESERVICE_CID,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataStoreService,
                                          Ci.nsIObserver]),
   classInfo: XPCOMUtils.generateCI({
     classID: DATASTORESERVICE_CID,
     contractID: '@mozilla.org/datastore-service;1',
     interfaces: [Ci.nsIDataStoreService, Ci.nsIObserver],
     flags: Ci.nsIClassInfo.SINGLETON
   })
 };
 
 /* DataStoreServiceChild */
 
-function DataStoreServiceChild(aWindow, aName, aResolve) {
+function DataStoreServiceChild(aWindow, aName, aCallback) {
   debug("DataStoreServiceChild created");
-  this.init(aWindow, aName, aResolve);
+  this.init(aWindow, aName, aCallback);
 }
 
 DataStoreServiceChild.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
-  init: function(aWindow, aName, aResolve) {
-    this._window = aWindow;
-    this._name = aName;
-    this._resolve = aResolve;
+  init: function(aWindow, aName, aCallback) {
+    debug("DataStoreServiceChild init");
+    this._callback = aCallback;
 
     this.initDOMRequestHelper(aWindow, [ "DataStore:Get:Return" ]);
 
     // This is a security issue and it will be fixed by Bug 916091
     cpmm.sendAsyncMessage("DataStore:Get",
                           { name: aName, appId: aWindow.document.nodePrincipal.appId });
   },
 
   receiveMessage: function(aMessage) {
+    debug("DataStoreServiceChild receiveMessage");
     if (aMessage.name != 'DataStore:Get:Return') {
       return;
     }
-    let msg = aMessage.data;
-    let self = this;
-
-    let callbackPending = msg.stores.length;
-    let results = [];
-
-    if (!callbackPending) {
-      this._resolve(results);
-      return;
-    }
 
-    for (let i = 0; i < msg.stores.length; ++i) {
-      let obj = new DataStore(this._window, this._name,
-                              msg.stores[i].owner, msg.stores[i].readOnly);
-      let exposedObj = this._window.DataStore._create(this._window, obj);
-      results.push(exposedObj);
-
-      obj.retrieveRevisionId(
-        function() {
-          --callbackPending;
-          if (!callbackPending) {
-            self._resolve(results);
-          }
-        }
-      );
-    }
+    this._callback(aMessage.data.stores);
   }
 }
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DataStoreService]);
new file mode 100644
--- /dev/null
+++ b/dom/datastore/DataStoreServiceInternal.jsm
@@ -0,0 +1,51 @@
+/* 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/. */
+
+"use strict"
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+this.EXPORTED_SYMBOLS = ["DataStoreServiceInternal"];
+
+function debug(s) {
+  // dump('DEBUG DataStoreServiceInternal: ' + s + '\n');
+}
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
+                                   "@mozilla.org/parentprocessmessagemanager;1",
+                                   "nsIMessageBroadcaster");
+
+XPCOMUtils.defineLazyServiceGetter(this, "dataStoreService",
+                                   "@mozilla.org/datastore-service;1",
+                                   "nsIDataStoreService");
+
+this.DataStoreServiceInternal = {
+  init: function() {
+    debug("init");
+
+    let inParent = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+                      .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+    if (inParent) {
+      ppmm.addMessageListener("DataStore:Get", this);
+    }
+  },
+
+  receiveMessage: function(aMessage) {
+    debug("receiveMessage");
+
+    if (aMessage.name != 'DataStore:Get') {
+      return;
+    }
+
+    let msg = aMessage.data;
+
+    // This is a security issue and it will be fixed by Bug 916091
+    msg.stores = dataStoreService.getDataStoresInfo(msg.name, msg.appId);
+    aMessage.target.sendAsyncMessage("DataStore:Get:Return", msg);
+  }
+}
+
+DataStoreServiceInternal.init();
--- a/dom/datastore/moz.build
+++ b/dom/datastore/moz.build
@@ -18,9 +18,10 @@ EXTRA_COMPONENTS += [
     'DataStore.manifest',
     'DataStoreService.js',
 ]
 
 EXTRA_JS_MODULES += [
     'DataStore.jsm',
     'DataStoreChangeNotifier.jsm',
     'DataStoreDB.jsm',
+    'DataStoreServiceInternal.jsm',
 ]
--- a/dom/datastore/nsIDataStoreService.idl
+++ b/dom/datastore/nsIDataStoreService.idl
@@ -2,24 +2,34 @@
 /* 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 "nsISupports.idl"
 
 interface nsIDOMWindow;
 
-[scriptable, uuid(d193d0e2-c677-4a7b-bb0a-19155b470f2e)]
+[scriptable, uuid(bd02d09c-41ab-47b7-9319-57aa8e5059b0)]
 interface nsIDataStoreService : nsISupports
 {
   void installDataStore(in unsigned long appId,
                         in DOMString name,
+                        in DOMString originURL,
                         in DOMString manifestURL,
                         in boolean readOnly);
 
   void installAccessDataStore(in unsigned long appId,
                               in DOMString name,
+                              in DOMString originURL,
                               in DOMString manifestURL,
                               in boolean readOnly);
 
   nsISupports getDataStores(in nsIDOMWindow window,
                             in DOMString name);
+
+  // This is an array of objects composed by:
+  // - readOnly: boolean
+  // - name: DOMString
+  // - owner: DOMString
+  // - enabled: true/false - true if this dataStore is ready to be used.
+  jsval getDataStoresInfo(in DOMString name,
+                          in unsigned long appId);
 };
--- a/dom/datastore/tests/test_app_install.html
+++ b/dom/datastore/tests/test_app_install.html
@@ -5,30 +5,33 @@
   <title>Test for DataStore - install/uninstall apps</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <div id="container"></div>
   <script type="application/javascript;version=1.7">
 
- SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+  if (SpecialPowers.isMainProcess()) {
+    SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+  }
 
   SimpleTest.waitForExplicitFinish();
 
   var gBaseURL = 'http://test/tests/dom/datastore/tests/';
   var gHostedManifestURL = gBaseURL + 'file_app.sjs?testToken=file_app_install.html';
   var gGenerator = runTest();
 
   SpecialPowers.pushPermissions(
     [{ "type": "browser", "allow": 1, "context": document },
      { "type": "embed-apps", "allow": 1, "context": document },
      { "type": "webapps-manage", "allow": 1, "context": document }],
     function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, function() {
+      SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true],
+                                         ["dom.promise.enabled", true]]}, function() {
         gGenerator.next(); });
     });
 
   function continueTest() {
     try { gGenerator.next(); }
     catch(e) { dump("Got exception: " + e + "\n"); }
   }
 
@@ -41,16 +44,17 @@
     ok("getDataStores" in navigator, "getDataStores exists");
     is(typeof navigator.getDataStores, "function", "getDataStores exists and it's a function");
     navigator.getDataStores('foo').then(function(stores) {
       is(stores.length, 0, "getDataStores('foo') returns 0 elements");
       continueTest();
     }, cbError);
     yield undefined;
 
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
 
     SpecialPowers.autoConfirmAppInstall(continueTest);
     yield undefined;
 
     var request = navigator.mozApps.install(gHostedManifestURL);
     request.onerror = cbError;
     request.onsuccess = continueTest;
--- a/dom/datastore/tests/test_arrays.html
+++ b/dom/datastore/tests/test_arrays.html
@@ -79,16 +79,17 @@
       SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
     },
 
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
     },
 
     function() {
+      SpecialPowers.setAllAppsLaunchable(true);
       SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
       runTest();
     },
 
     // No confirmation needed when an app is installed
     function() {
       SpecialPowers.autoConfirmAppInstall(runTest);
     },
@@ -112,14 +113,17 @@
     var test = tests.shift();
     test();
   }
 
   function finish() {
     SimpleTest.finish();
   }
 
-  SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+  if (SpecialPowers.isMainProcess()) {
+    SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+  }
+
   SimpleTest.waitForExplicitFinish();
   runTest();
   </script>
 </body>
 </html>
--- a/dom/datastore/tests/test_basic.html
+++ b/dom/datastore/tests/test_basic.html
@@ -79,16 +79,17 @@
       SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
     },
 
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
     },
 
     function() {
+      SpecialPowers.setAllAppsLaunchable(true);
       SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
       runTest();
     },
 
     // No confirmation needed when an app is installed
     function() {
       SpecialPowers.autoConfirmAppInstall(runTest);
     },
@@ -112,14 +113,17 @@
     var test = tests.shift();
     test();
   }
 
   function finish() {
     SimpleTest.finish();
   }
 
-  SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+  if (SpecialPowers.isMainProcess()) {
+    SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+  }
+
   SimpleTest.waitForExplicitFinish();
   runTest();
   </script>
 </body>
 </html>
--- a/dom/datastore/tests/test_changes.html
+++ b/dom/datastore/tests/test_changes.html
@@ -124,16 +124,17 @@
     },
 
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
     },
 
     // Enabling mozBrowser
     function() {
+      SpecialPowers.setAllAppsLaunchable(true);
       SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true]]}, runTest);
     },
 
     // No confirmation needed when an app is installed
     function() {
       SpecialPowers.autoConfirmAppInstall(runTest);
     },
 
@@ -165,17 +166,20 @@
     var test = tests.shift();
     test();
   }
 
   function finish() {
     SimpleTest.finish();
   }
 
-  SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+  if (SpecialPowers.isMainProcess()) {
+    SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+  }
+
   SimpleTest.waitForExplicitFinish();
   runTest();
   </script>
 </pre>
 </body>
 </html>
 
 
--- a/dom/datastore/tests/test_oop.html
+++ b/dom/datastore/tests/test_oop.html
@@ -83,16 +83,17 @@
       SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
     },
 
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.ipc.browser_frames.oop_by_default", true]]}, runTest);
     },
 
     function() {
+      SpecialPowers.setAllAppsLaunchable(true);
       SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
       runTest();
     },
 
     // No confirmation needed when an app is installed
     function() {
       SpecialPowers.autoConfirmAppInstall(runTest);
     },
@@ -116,14 +117,17 @@
     var test = tests.shift();
     test();
   }
 
   function finish() {
     SimpleTest.finish();
   }
 
-  SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+  if (SpecialPowers.isMainProcess()) {
+    SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+  }
+
   SimpleTest.waitForExplicitFinish();
   runTest();
   </script>
 </body>
 </html>
--- a/dom/datastore/tests/test_readonly.html
+++ b/dom/datastore/tests/test_readonly.html
@@ -25,16 +25,17 @@
   }
 
   function cbError() {
     ok(false, "Error callback invoked");
     finish();
   }
 
   function runTest() {
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
 
     SpecialPowers.autoConfirmAppInstall(continueTest);
     yield undefined;
 
     var request = navigator.mozApps.install(gHostedManifestURL);
     request.onerror = cbError;
     request.onsuccess = continueTest;
@@ -89,16 +90,18 @@
     domParent.appendChild(ifr);
   }
 
   function finish() {
     SpecialPowers.clearUserPref("dom.mozBrowserFramesEnabled");
     SimpleTest.finish();
   }
 
-  SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+  if (SpecialPowers.isMainProcess()) {
+    SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+  }
+
   SimpleTest.waitForExplicitFinish();
-  SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, function() {
-    SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
-  });
+  SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
+                                     ["dom.datastore.enabled", true]]}, runTest);
   </script>
 </body>
 </html>
--- a/dom/datastore/tests/test_revision.html
+++ b/dom/datastore/tests/test_revision.html
@@ -89,16 +89,17 @@
     },
 
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
     },
 
     // Enabling mozBrowser
     function() {
+      SpecialPowers.setAllAppsLaunchable(true);
       SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true]]}, runTest);
     },
 
     // No confirmation needed when an app is installed
     function() {
       SpecialPowers.autoConfirmAppInstall(runTest);
     },
 
@@ -121,16 +122,19 @@
     var test = tests.shift();
     test();
   }
 
   function finish() {
     SimpleTest.finish();
   }
 
-  SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+  if (SpecialPowers.isMainProcess()) {
+    SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+  }
+
   SimpleTest.waitForExplicitFinish();
   runTest();
   </script>
 </pre>
 </body>
 </html>
 
--- a/testing/mochitest/manifest.webapp
+++ b/testing/mochitest/manifest.webapp
@@ -30,10 +30,14 @@
     "systemXHR":{}
   },
   "locales": {
     "en-US": {
       "name": "Mochitest",
       "description": "Mochitests"
     }
   },
+  "datastores-access" : {
+    "foo" : { "readonly": false },
+    "bar" : { "readonly": false }
+  },
   "default_locale": "en-US"
 }