Bug 871445 - patch 9 - DataStore: child<->parent communication, r=ehsan
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 02 Oct 2013 13:27:15 -0400
changeset 163554 2a0afccd894bea12effbc3317b73a3ffed05df69
parent 163553 0e04ff490bf3b6eb318087bc72cf970c14418ca6
child 163555 4aa816bc20a84f42af803fb6207a599fe3a3c3b7
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs871445
milestone27.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 871445 - patch 9 - DataStore: child<->parent communication, r=ehsan
dom/datastore/DataStore.jsm
dom/datastore/DataStoreChangeNotifier.jsm
dom/datastore/DataStoreService.js
dom/datastore/tests/Makefile.in
dom/datastore/tests/test_oop.html
--- a/dom/datastore/DataStore.jsm
+++ b/dom/datastore/DataStore.jsm
@@ -1,17 +1,17 @@
 /* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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'
 
-this.EXPORTED_SYMBOLS = ["DataStore", "DataStoreAccess"];
+this.EXPORTED_SYMBOLS = ["DataStore"];
 
 function debug(s) {
   // dump('DEBUG DataStore: ' + s + '\n');
 }
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 const REVISION_ADDED = "added";
@@ -62,318 +62,78 @@ function parseIds(aId) {
     if (aId[i] === null) {
       return null;
     }
   }
 
   return aId;
 }
 
-/* Exposed DataStore object */
-function ExposedDataStore(aWindow, aDataStore, aReadOnly) {
-  debug("ExposedDataStore created");
-  this.init(aWindow, aDataStore, aReadOnly);
+/* DataStore object */
+this.DataStore = function(aWindow, aName, aOwner, aReadOnly) {
+  debug("DataStore created");
+  this.init(aWindow, aName, aOwner, aReadOnly);
 }
 
-ExposedDataStore.prototype = {
+this.DataStore.prototype = {
   classDescription: "DataStore XPCOM Component",
   classID: Components.ID("{db5c9602-030f-4bff-a3de-881a8de370f2}"),
   contractID: "@mozilla.org/dom/datastore;1",
   QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports]),
 
   callbacks: [],
 
-  init: function(aWindow, aDataStore, aReadOnly) {
-    debug("ExposedDataStore init");
-
-    this.window = aWindow;
-    this.dataStore = aDataStore;
-    this.isReadOnly = aReadOnly;
-  },
-
-  receiveMessage: function(aMessage) {
-    debug("receiveMessage");
-
-    if (aMessage.name != "DataStore:Changed:Return:OK") {
-      debug("Wrong message: " + aMessage.name);
-      return;
-    }
-
-    let self = this;
+  _window: null,
+  _name: null,
+  _owner: null,
+  _readOnly: null,
+  _revisionId: null,
 
-    this.dataStore.retrieveRevisionId(
-      function() {
-        let event = new self.window.DataStoreChangeEvent('change', aMessage.data);
-        self.__DOM_IMPL__.dispatchEvent(event);
-      },
-      // Forcing the reading of the revisionId
-      true
-    );
-  },
-
-  // Public interface :
+  init: function(aWindow, aName, aOwner, aReadOnly) {
+    debug("DataStore init");
 
-  get name() {
-    return this.dataStore.name;
-  },
-
-  get owner() {
-    return this.dataStore.owner;
-  },
+    this._window = aWindow;
+    this._name = aName;
+    this._owner = aOwner;
+    this._readOnly = aReadOnly;
 
-  get readOnly() {
-    return this.isReadOnly;
-  },
-
-  get: function(aId) {
-    aId = parseIds(aId);
-    if (aId === null) {
-      return throwInvalidArg(this.window);
-    }
+    this._db = new DataStoreDB();
+    this._db.init(aOwner, aName);
 
     let self = this;
-
-    // Promise<Object>
-    return this.dataStore.newDBPromise(this.window, "readonly",
-      function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
-               self.dataStore.getInternal(aStore,
-                                          Array.isArray(aId) ?  aId : [ aId ],
-                                          function(aResults) {
-          aResolve(Array.isArray(aId) ? aResults : aResults[0]);
-        });
+    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();
       }
-    );
-  },
-
-  update: function(aId, aObj) {
-    aId = parseInt(aId);
-    if (isNaN(aId) || aId <= 0) {
-      return throwInvalidArg(this.window);
-    }
-
-    if (this.isReadOnly) {
-      return throwReadOnly(this.window);
-    }
-
-    let self = this;
-
-    // Promise<void>
-    return this.dataStore.newDBPromise(this.window, "readwrite",
-      function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
-        self.dataStore.updateInternal(aResolve, aStore, aRevisionStore, aId, aObj);
-      }
-    );
-  },
-
-  add: function(aObj) {
-    if (this.isReadOnly) {
-      return throwReadOnly(this.window);
-    }
-
-    let self = this;
+    }, "inner-window-destroyed", false);
 
-    // Promise<int>
-    return this.dataStore.newDBPromise(this.window, "readwrite",
-      function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
-        self.dataStore.addInternal(aResolve, aStore, aRevisionStore, aObj);
-      }
-    );
-  },
-
-  remove: function(aId) {
-    aId = parseInt(aId);
-    if (isNaN(aId) || aId <= 0) {
-      return throwInvalidArg(this.window);
-    }
-
-    if (this.isReadOnly) {
-      return throwReadOnly(this.window);
-    }
-
-    let self = this;
+    let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                      .getInterface(Ci.nsIDOMWindowUtils);
+    this._innerWindowID = util.currentInnerWindowID;
 
-    // Promise<void>
-    return this.dataStore.newDBPromise(this.window, "readwrite",
-      function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
-        self.dataStore.removeInternal(aResolve, aStore, aRevisionStore, aId);
-      }
-    );
-  },
-
-  clear: function() {
-    if (this.isReadOnly) {
-      return throwReadOnly(this.window);
-    }
-
-    let self = this;
-
-    // Promise<void>
-    return this.dataStore.newDBPromise(this.window, "readwrite",
-      function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
-        self.dataStore.clearInternal(aResolve, aStore, aRevisionStore);
-      }
-    );
-  },
-
-  get revisionId() {
-    return this.dataStore.revisionId;
+    cpmm.addMessageListener("DataStore:Changed:Return:OK", this);
+    cpmm.sendAsyncMessage("DataStore:RegisterForMessages",
+                          { store: this._name, owner: this._owner });
   },
 
-  getChanges: function(aRevisionId) {
-    debug("GetChanges: " + aRevisionId);
-
-    if (aRevisionId === null || aRevisionId === undefined) {
-      return this.window.Promise.reject(
-        new this.window.DOMError("SyntaxError", "Invalid revisionId"));
-    }
-
+  newDBPromise: function(aTxnType, aFunction) {
     let self = this;
-
-    // Promise<DataStoreChanges>
-    return new this.window.Promise(function(aResolve, aReject) {
-      debug("GetChanges promise started");
-      self.dataStore.db.revisionTxn(
-        'readonly',
-        function(aTxn, aStore) {
-          debug("GetChanges transaction success");
-
-          let request = self.dataStore.db.getInternalRevisionId(
-            aRevisionId,
-            aStore,
-            function(aInternalRevisionId) {
-              if (aInternalRevisionId == undefined) {
-                aResolve(undefined);
-                return;
-              }
-
-              // This object is the return value of this promise.
-              // 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));
-              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;
-
-                    case REVISION_UPDATED:
-                      // We don't consider an update if this object has been added
-                      // or if it has been already modified by a previous
-                      // operation.
-                      if (!(data.objectId in changes.addedIds) &&
-                          !(data.objectId in changes.updatedIds)) {
-                        changes.updatedIds[data.objectId] = true;
-                      }
-                      break;
-
-                    case REVISION_REMOVED:
-                      let id = data.objectId;
-
-                      // If the object has been added in this range of revisions
-                      // we can ignore it and remove it from the list.
-                      if (id in changes.addedIds) {
-                        delete changes.addedIds[id];
-                      } else {
-                        changes.removedIds[id] = true;
-                      }
-
-                      if (id in changes.updatedIds) {
-                        delete changes.updatedIds[id];
-                      }
-                      break;
-                  }
-                }
-
-                // The last revisionId.
-                if (aEvent.target.result.length) {
-                  changes.revisionId = aEvent.target.result[aEvent.target.result.length - 1].revisionId;
-                }
-
-                // From maps to arrays.
-                changes.addedIds = Object.keys(changes.addedIds).map(function(aKey) { return parseInt(aKey, 10); });
-                changes.updatedIds = Object.keys(changes.updatedIds).map(function(aKey) { return parseInt(aKey, 10); });
-                changes.removedIds = Object.keys(changes.removedIds).map(function(aKey) { return parseInt(aKey, 10); });
-
-                let wrappedObject = ObjectWrapper.wrap(changes, self.window);
-                aResolve(wrappedObject);
-              };
-            }
-          );
-        },
-        function(aEvent) {
-          debug("GetChanges transaction failed");
-          aReject(createDOMError(self.window, aEvent));
-        }
-      );
-    });
-  },
-
-  getLength: function() {
-    let self = this;
-
-    // Promise<int>
-    return this.dataStore.newDBPromise(this.window, "readonly",
-      function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
-        self.dataStore.getLengthInternal(aResolve, aStore);
-      }
-    );
-  },
-
-  set onchange(aCallback) {
-    debug("Set OnChange");
-    this.__DOM_IMPL__.setEventHandler("onchange", aCallback);
-  },
-
-  get onchange() {
-    debug("Get OnChange");
-    return this.__DOM_IMPL__.getEventHandler("onchange");
-  }
-};
-
-/* DataStore object */
-
-function DataStore(aAppId, aName, aOwner, aReadOnly, aGlobalScope) {
-  this.appId = aAppId;
-  this.name = aName;
-  this.owner = aOwner;
-  this.readOnly = aReadOnly;
-
-  this.db = new DataStoreDB();
-  this.db.init(aOwner, aName, aGlobalScope);
-}
-
-DataStore.prototype = {
-  appId: null,
-  name: null,
-  owner: null,
-  readOnly: null,
-  revisionId: null,
-
-  newDBPromise: function(aWindow, aTxnType, aFunction) {
-    let db = this.db;
-    return new aWindow.Promise(function(aResolve, aReject) {
+    return new this._window.Promise(function(aResolve, aReject) {
       debug("DBPromise started");
-      db.txn(
+      self._db.txn(
         aTxnType,
         function(aTxn, aStore, aRevisionStore) {
           debug("DBPromise success");
           aFunction(aResolve, aReject, aTxn, aStore, aRevisionStore);
         },
         function(aEvent) {
           debug("DBPromise error");
-          aReject(createDOMError(aWindow, aEvent));
+          aReject(createDOMError(self._window, aEvent));
         }
       );
     });
   },
 
   getInternal: function(aStore, aIds, aCallback) {
     debug("GetInternal: " + aIds.toSource());
 
@@ -473,17 +233,17 @@ DataStore.prototype = {
 
   clearInternal: function(aResolve, aStore, aRevisionStore) {
     debug("ClearInternal");
 
     let self = this;
     let request = aStore.clear();
     request.onsuccess = function() {
       debug("ClearInternal success");
-      self.db.clearRevisions(aRevisionStore,
+      self._db.clearRevisions(aRevisionStore,
         function() {
           debug("Revisions cleared");
 
           self.addRevision(aRevisionStore, 0, REVISION_VOID,
             function() {
               debug("ClearInternal - revisionId increased");
               aResolve();
             }
@@ -501,99 +261,300 @@ DataStore.prototype = {
       debug("GetLengthInternal success: " + aEvent.target.result);
       // No wrap here because the result is always a int.
       aResolve(aEvent.target.result);
     };
   },
 
   addRevision: function(aRevisionStore, aId, aType, aSuccessCb) {
     let self = this;
-    this.db.addRevision(aRevisionStore, aId, aType,
+    this._db.addRevision(aRevisionStore, aId, aType,
       function(aRevisionId) {
-        self.revisionId = aRevisionId;
+        self._revisionId = aRevisionId;
         self.sendNotification(aId, aType, aRevisionId);
         aSuccessCb();
       }
     );
   },
 
-  retrieveRevisionId: function(aSuccessCb, aForced) {
-    if (this.revisionId != null && !aForced) {
-      aSuccessCb();
-      return;
-    }
-
+  retrieveRevisionId: function(aSuccessCb) {
     let self = this;
-    this.db.revisionTxn(
+    this._db.revisionTxn(
       'readwrite',
       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;
+                self._revisionId = aRevisionId;
                 aSuccessCb();
               }
             );
             return;
           }
 
-          self.revisionId = cursor.value.revisionId;
+          self._revisionId = cursor.value.revisionId;
           aSuccessCb();
         };
       }
     );
   },
 
-  exposeObject: function(aWindow, aReadOnly) {
-    debug("Exposing Object");
-
-    let exposedObject = new ExposedDataStore(aWindow, this, aReadOnly);
-    let object = aWindow.DataStore._create(aWindow, exposedObject);
-
-    Services.obs.addObserver(function(aSubject, aTopic, aData) {
-      let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
-      if (wId == exposedObject.innerWindowID) {
-        cpmm.removeMessageListener("DataStore:Changed:Return:OK", exposedObject);
-      }
-    }, "inner-window-destroyed", false);
-
-    let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.nsIDOMWindowUtils);
-    exposedObject.innerWindowID = util.currentInnerWindowID;
-
-    cpmm.addMessageListener("DataStore:Changed:Return:OK", exposedObject);
-    cpmm.sendAsyncMessage("DataStore:RegisterForMessages",
-                          { store: this.name, owner: this.owner });
-
-    return object;
-  },
-
-  delete: function() {
-    this.db.delete();
-  },
-
   sendNotification: function(aId, aOperation, aRevisionId) {
     debug("SendNotification");
     if (aOperation != REVISION_VOID) {
       cpmm.sendAsyncMessage("DataStore:Changed",
                             { store: this.name, owner: this.owner,
                               message: { revisionId: aRevisionId, id: aId,
                                          operation: aOperation } } );
     }
+  },
+
+  receiveMessage: function(aMessage) {
+    debug("receiveMessage");
+
+    if (aMessage.name != "DataStore:Changed:Return:OK") {
+      debug("Wrong message: " + aMessage.name);
+      return;
+    }
+
+    let self = this;
+
+    this.retrieveRevisionId(
+      function() {
+        let event = new self._window.DataStoreChangeEvent('change', aMessage.data);
+        self.__DOM_IMPL__.dispatchEvent(event);
+      }
+    );
+  },
+
+  // Public interface :
+
+  get name() {
+    return this._name;
+  },
+
+  get owner() {
+    return this._owner;
+  },
+
+  get readOnly() {
+    return this._readOnly;
+  },
+
+  get: function(aId) {
+    aId = parseIds(aId);
+    if (aId === null) {
+      return throwInvalidArg(this._window);
+    }
+
+    let self = this;
+
+    // Promise<Object>
+    return this.newDBPromise("readonly",
+      function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
+               self.getInternal(aStore,
+                                Array.isArray(aId) ?  aId : [ aId ],
+                                function(aResults) {
+          aResolve(Array.isArray(aId) ? aResults : aResults[0]);
+        });
+      }
+    );
+  },
+
+  update: function(aId, aObj) {
+    aId = parseInt(aId);
+    if (isNaN(aId) || aId <= 0) {
+      return throwInvalidArg(this._window);
+    }
+
+    if (this._readOnly) {
+      return throwReadOnly(this._window);
+    }
+
+    let self = this;
+
+    // Promise<void>
+    return this.newDBPromise("readwrite",
+      function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
+        self.updateInternal(aResolve, aStore, aRevisionStore, aId, aObj);
+      }
+    );
+  },
+
+  add: function(aObj) {
+    if (this._readOnly) {
+      return throwReadOnly(this._window);
+    }
+
+    let self = this;
+
+    // Promise<int>
+    return this.newDBPromise("readwrite",
+      function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
+        self.addInternal(aResolve, aStore, aRevisionStore, aObj);
+      }
+    );
+  },
+
+  remove: function(aId) {
+    aId = parseInt(aId);
+    if (isNaN(aId) || aId <= 0) {
+      return throwInvalidArg(this._window);
+    }
+
+    if (this._readOnly) {
+      return throwReadOnly(this._window);
+    }
+
+    let self = this;
+
+    // Promise<void>
+    return this.newDBPromise("readwrite",
+      function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
+        self.removeInternal(aResolve, aStore, aRevisionStore, aId);
+      }
+    );
+  },
+
+  clear: function() {
+    if (this._readOnly) {
+      return throwReadOnly(this._window);
+    }
+
+    let self = this;
+
+    // Promise<void>
+    return this.newDBPromise("readwrite",
+      function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
+        self.clearInternal(aResolve, aStore, aRevisionStore);
+      }
+    );
+  },
+
+  get revisionId() {
+    return this._revisionId;
+  },
+
+  getChanges: function(aRevisionId) {
+    debug("GetChanges: " + aRevisionId);
+
+    if (aRevisionId === null || aRevisionId === undefined) {
+      return this._window.Promise.reject(
+        new this._window.DOMError("SyntaxError", "Invalid revisionId"));
+    }
+
+    let self = this;
+
+    // Promise<DataStoreChanges>
+    return new this._window.Promise(function(aResolve, aReject) {
+      debug("GetChanges promise started");
+      self._db.revisionTxn(
+        'readonly',
+        function(aTxn, aStore) {
+          debug("GetChanges transaction success");
+
+          let request = self._db.getInternalRevisionId(
+            aRevisionId,
+            aStore,
+            function(aInternalRevisionId) {
+              if (aInternalRevisionId == undefined) {
+                aResolve(undefined);
+                return;
+              }
+
+              // This object is the return value of this promise.
+              // 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));
+              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;
+
+                    case REVISION_UPDATED:
+                      // We don't consider an update if this object has been added
+                      // or if it has been already modified by a previous
+                      // operation.
+                      if (!(data.objectId in changes.addedIds) &&
+                          !(data.objectId in changes.updatedIds)) {
+                        changes.updatedIds[data.objectId] = true;
+                      }
+                      break;
+
+                    case REVISION_REMOVED:
+                      let id = data.objectId;
+
+                      // If the object has been added in this range of revisions
+                      // we can ignore it and remove it from the list.
+                      if (id in changes.addedIds) {
+                        delete changes.addedIds[id];
+                      } else {
+                        changes.removedIds[id] = true;
+                      }
+
+                      if (id in changes.updatedIds) {
+                        delete changes.updatedIds[id];
+                      }
+                      break;
+                  }
+                }
+
+                // The last revisionId.
+                if (aEvent.target.result.length) {
+                  changes.revisionId = aEvent.target.result[aEvent.target.result.length - 1].revisionId;
+                }
+
+                // From maps to arrays.
+                changes.addedIds = Object.keys(changes.addedIds).map(function(aKey) { return parseInt(aKey, 10); });
+                changes.updatedIds = Object.keys(changes.updatedIds).map(function(aKey) { return parseInt(aKey, 10); });
+                changes.removedIds = Object.keys(changes.removedIds).map(function(aKey) { return parseInt(aKey, 10); });
+
+                let wrappedObject = ObjectWrapper.wrap(changes, self._window);
+                aResolve(wrappedObject);
+              };
+            }
+          );
+        },
+        function(aEvent) {
+          debug("GetChanges transaction failed");
+          aReject(createDOMError(self._window, aEvent));
+        }
+      );
+    });
+  },
+
+  getLength: function() {
+    let self = this;
+
+    // Promise<int>
+    return this.newDBPromise("readonly",
+      function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
+        self.getLengthInternal(aResolve, aStore);
+      }
+    );
+  },
+
+  set onchange(aCallback) {
+    debug("Set OnChange");
+    this.__DOM_IMPL__.setEventHandler("onchange", aCallback);
+  },
+
+  get onchange() {
+    debug("Get OnChange");
+    return this.__DOM_IMPL__.getEventHandler("onchange");
   }
 };
-
-/* DataStoreAccess */
-
-function DataStoreAccess(aAppId, aName, aOrigin, aReadOnly) {
-  this.appId = aAppId;
-  this.name = aName;
-  this.origin = aOrigin;
-  this.readOnly = aReadOnly;
-}
-
-DataStoreAccess.prototype = {};
--- a/dom/datastore/DataStoreChangeNotifier.jsm
+++ b/dom/datastore/DataStoreChangeNotifier.jsm
@@ -10,18 +10,16 @@ this.EXPORTED_SYMBOLS = ["DataStoreChang
 
 function debug(s) {
   //dump('DEBUG DataStoreChangeNotifier: ' + s + '\n');
 }
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
-const kFromDataStoreChangeNotifier = "fromDataStoreChangeNotifier";
-
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageBroadcaster");
 
 this.DataStoreChangeNotifier = {
   children: [],
   messages: [ "DataStore:Changed", "DataStore:RegisterForMessages",
               "child-process-shutdown" ],
--- a/dom/datastore/DataStoreService.js
+++ b/dom/datastore/DataStoreService.js
@@ -12,31 +12,46 @@ 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/DOMRequestHelper.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+                                   "@mozilla.org/childprocessmessagemanager;1",
+                                   "nsIMessageSender");
+
+XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
+                                   "@mozilla.org/parentprocessmessagemanager;1",
+                                   "nsIMessageBroadcaster");
 
 /* DataStoreService */
 
 const DATASTORESERVICE_CID = Components.ID('{d193d0e2-c677-4a7b-bb0a-19155b470f2e}');
 
 function DataStoreService() {
   debug('DataStoreService Constructor');
 
   let obs = Services.obs;
   if (!obs) {
     debug("DataStore Error: observer-service is null!");
     return;
   }
 
   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);
+  }
 }
 
 DataStoreService.prototype = {
   // Hash of DataStores
   stores: {},
   accessStores: {},
 
   installDataStore: function(aAppId, aName, aOwner, aReadOnly) {
@@ -44,109 +59,96 @@ DataStoreService.prototype = {
           aName + ', aOwner:' + aOwner + ', aReadOnly: ' +
           aReadOnly);
 
     if (aName in this.stores && aAppId in this.stores[aName]) {
       debug('This should not happen');
       return;
     }
 
-    let store = new DataStore(aAppId, aName, aOwner, aReadOnly);
-
     if (!(aName in this.stores)) {
       this.stores[aName] = {};
     }
 
-    this.stores[aName][aAppId] = store;
+    this.stores[aName][aAppId] = { owner: aOwner, readOnly: aReadOnly };
   },
 
   installAccessDataStore: function(aAppId, aName, aOwner, aReadOnly) {
     debug('installDataStore - appId: ' + aAppId + ', aName: ' +
           aName + ', aOwner:' + aOwner + ', aReadOnly: ' +
           aReadOnly);
 
     if (aName in this.accessStores && aAppId in this.accessStores[aName]) {
       debug('This should not happen');
       return;
     }
 
-    let accessStore = new DataStoreAccess(aAppId, aName, aOwner, aReadOnly);
-
     if (!(aName in this.accessStores)) {
       this.accessStores[aName] = {};
     }
 
-    this.accessStores[aName][aAppId] = accessStore;
+    this.accessStores[aName][aAppId] = { owner: aOwner, readOnly: aReadOnly };
   },
 
   getDataStores: function(aWindow, aName) {
     debug('getDataStores - aName: ' + aName);
-    let appId = aWindow.document.nodePrincipal.appId;
-
-    let self = this;
-    return new aWindow.Promise(function(resolve, reject) {
-      let matchingStores = [];
-
-      if (aName in self.stores) {
-        if (appId in self.stores[aName]) {
-          matchingStores.push({ store: self.stores[aName][appId],
-                                readonly: false });
-        }
-
-        for (var i in self.stores[aName]) {
-          if (i == appId) {
-            continue;
-          }
-
-          let access = self.getDataStoreAccess(self.stores[aName][i], appId);
-          if (!access) {
-            continue;
-          }
 
-          let readOnly = self.stores[aName][i].readOnly || access.readOnly;
-          matchingStores.push({ store: self.stores[aName][i],
-                                readonly: readOnly });
-        }
-      }
-
-      let callbackPending = matchingStores.length;
-      let results = [];
-
-      if (!callbackPending) {
-        resolve(results);
-        return;
-      }
+    // This method can be called in the child so we need to send a request to
+    // the parent and create DataStore object here.
 
-      for (let i = 0; i < matchingStores.length; ++i) {
-        let obj = matchingStores[i].store.exposeObject(aWindow,
-                                    matchingStores[i].readonly);
-        results.push(obj);
-
-        matchingStores[i].store.retrieveRevisionId(
-          function() {
-            --callbackPending;
-            if (!callbackPending) {
-              resolve(results);
-            }
-          },
-          // if the revision is already known, we don't need to retrieve it
-          // again.
-          false
-        );
-      }
+    return new aWindow.Promise(function(resolve, reject) {
+      new DataStoreServiceChild(aWindow, aName, resolve);
     });
   },
 
-  getDataStoreAccess: function(aStore, aAppId) {
-    if (!(aStore.name in this.accessStores) ||
-        !(aAppId in this.accessStores[aStore.name])) {
+  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;
+
+    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 });
+      }
+
+      for (var i in this.stores[msg.name]) {
+        if (i == appId) {
+          continue;
+        }
+
+        let access = this.getDataStoreAccess(msg.name, appId);
+        if (!access) {
+          continue;
+        }
+
+        let readOnly = this.stores[msg.name][i].readOnly || access.readOnly;
+        results.push({ store: this.stores[msg.name][i],
+                       readOnly: readOnly });
+      }
+    }
+
+    msg.stores = results;
+    aMessage.target.sendAsyncMessage("DataStore:Get:Return", msg);
+  },
+
+  getDataStoreAccess: function(aName, aAppId) {
+    if (!(aName in this.accessStores) ||
+        !(aAppId in this.accessStores[aName])) {
       return null;
     }
 
-    return this.accessStores[aStore.name][aAppId];
+    return this.accessStores[aName][aAppId];
   },
 
   observe: function observe(aSubject, aTopic, aData) {
     debug('getDataStores - aTopic: ' + aTopic);
     if (aTopic != 'webapps-clear-data') {
       return;
     }
 
@@ -155,17 +157,16 @@ 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.stores[key][params.appId].delete();
         delete this.stores[key][params.appId];
       }
 
       if (!this.stores[key].length) {
         delete this.stores[key];
       }
     }
   },
@@ -176,9 +177,64 @@ DataStoreService.prototype = {
   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) {
+  debug("DataStoreServiceChild created");
+  this.init(aWindow, aName, aResolve);
+}
+
+DataStoreServiceChild.prototype = {
+  __proto__: DOMRequestIpcHelper.prototype,
+
+  init: function(aWindow, aName, aResolve) {
+    this._window = aWindow;
+    this._name = aName;
+    this._resolve = aResolve;
+
+    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) {
+    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.NSGetFactory = XPCOMUtils.generateNSGetFactory([DataStoreService]);
--- a/dom/datastore/tests/Makefile.in
+++ b/dom/datastore/tests/Makefile.in
@@ -23,11 +23,12 @@ MOCHITEST_FILES = \
   test_changes.html \
   file_changes.html \
   file_changes2.html \
   file_app.sjs \
   file_app.template.webapp \
   file_app2.template.webapp \
   test_arrays.html \
   file_arrays.html \
+  test_oop.html \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
copy from dom/datastore/tests/test_basic.html
copy to dom/datastore/tests/test_oop.html
--- a/dom/datastore/tests/test_basic.html
+++ b/dom/datastore/tests/test_oop.html
@@ -79,16 +79,20 @@
       SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
     },
 
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
     },
 
     function() {
+      SpecialPowers.pushPrefEnv({"set": [["dom.ipc.browser_frames.oop_by_default", true]]}, runTest);
+    },
+
+    function() {
       SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
       runTest();
     },
 
     // No confirmation needed when an app is installed
     function() {
       SpecialPowers.autoConfirmAppInstall(runTest);
     },