Bug 871445 - patch 4 - DatAStore: permissions, owned/access, r=mounir, r=ehsan
☠☠ backed out by db83498e31f0 ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 11 Sep 2013 15:47:53 +0200
changeset 154543 93b050a79db4b5b18598ddc9be2927becde21d8c
parent 154542 ec3382ceef99f527916f270e95ba0a58af8ddbe2
child 154544 639ec7a627f89c9fa287537137418fc8c6ec2933
push id4254
push userakeybl@mozilla.com
push dateTue, 17 Sep 2013 14:18:33 +0000
treeherdermozilla-aurora@9edd56e694b0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmounir, ehsan
bugs871445
milestone26.0a1
Bug 871445 - patch 4 - DatAStore: permissions, owned/access, r=mounir, r=ehsan
dom/apps/src/Webapps.jsm
dom/datastore/DataStore.jsm
dom/datastore/DataStoreService.js
dom/datastore/nsIDataStoreService.idl
dom/datastore/tests/Makefile.in
dom/datastore/tests/file_app.sjs
dom/datastore/tests/file_app.template.webapp
dom/datastore/tests/file_app2.template.webapp
dom/datastore/tests/file_app2.template.webapp^headers^
dom/datastore/tests/file_app_install.html
dom/datastore/tests/file_basic.html
dom/datastore/tests/file_readonly.html
dom/datastore/tests/file_revision.html
dom/datastore/tests/test_app_install.html
dom/datastore/tests/test_basic.html
dom/datastore/tests/test_readonly.html
dom/datastore/tests/test_revision.html
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -536,26 +536,36 @@ this.DOMApplicationRegistry = {
         onAppsLoaded();
 #else
       onAppsLoaded();
 #endif
     }).bind(this));
   },
 
   updateDataStore: function(aId, aManifestURL, aManifest) {
-    if (!("datastores" in aManifest)) {
-      return;
+    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);
+      }
     }
 
-    for (let name in aManifest.datastores) {
-      let readonly = ("readonly" in aManifest.datastores[name]) &&
-                     !aManifest.datastores[name].readonly
-                       ? false : true;
-
-      dataStoreService.installDataStore(aId, name, 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);
+      }
     }
   },
 
   // |aEntryPoint| is either the entry_point name or the null in which case we
   // use the root of the manifest.
   _registerSystemMessagesForEntryPoint: function(aManifest, aApp, aEntryPoint) {
     let root = aManifest;
     if (aEntryPoint && aManifest.entry_points[aEntryPoint]) {
--- 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'
 
-var EXPORTED_SYMBOLS = ["DataStore"];
+var EXPORTED_SYMBOLS = ["DataStore", "DataStoreAccess"];
 
 function debug(s) {
   // dump('DEBUG DataStore: ' + s + '\n');
 }
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 const REVISION_ADDED = "added";
@@ -199,32 +199,32 @@ DataStore.prototype = {
       new aWindow.DOMError("SyntaxError", "Non-numeric or invalid id"));
   },
 
   throwReadOnly: function(aWindow) {
     return aWindow.Promise.reject(
       new aWindow.DOMError("ReadOnlyError", "DataStore in readonly mode"));
   },
 
-  exposeObject: function(aWindow) {
+  exposeObject: function(aWindow, aReadOnly) {
     let self = this;
     let object = {
 
       // Public interface :
 
       get name() {
         return self.name;
       },
 
       get owner() {
         return self.owner;
       },
 
       get readOnly() {
-        return self.readOnly;
+        return aReadOnly;
       },
 
       get: function DS_get(aId) {
         aId = parseInt(aId);
         if (isNaN(aId) || aId <= 0) {
           return self.throwInvalidArg(aWindow);
         }
 
@@ -237,30 +237,30 @@ DataStore.prototype = {
       },
 
       update: function DS_update(aId, aObj) {
         aId = parseInt(aId);
         if (isNaN(aId) || aId <= 0) {
           return self.throwInvalidArg(aWindow);
         }
 
-        if (self.readOnly) {
+        if (aReadOnly) {
           return self.throwReadOnly(aWindow);
         }
 
         // Promise<void>
         return self.newDBPromise(aWindow, "readwrite",
           function(aResolver, aTxn, aStore, aRevisionStore) {
             self.updateInternal(aWindow, aResolver, aStore, aRevisionStore, aId, aObj);
           }
         );
       },
 
       add: function DS_add(aObj) {
-        if (self.readOnly) {
+        if (aReadOnly) {
           return self.throwReadOnly(aWindow);
         }
 
         // Promise<int>
         return self.newDBPromise(aWindow, "readwrite",
           function(aResolver, aTxn, aStore, aRevisionStore) {
             self.addInternal(aWindow, aResolver, aStore, aRevisionStore, aObj);
           }
@@ -268,30 +268,30 @@ DataStore.prototype = {
       },
 
       remove: function DS_remove(aId) {
         aId = parseInt(aId);
         if (isNaN(aId) || aId <= 0) {
           return self.throwInvalidArg(aWindow);
         }
 
-        if (self.readOnly) {
+        if (aReadOnly) {
           return self.throwReadOnly(aWindow);
         }
 
         // Promise<void>
         return self.newDBPromise(aWindow, "readwrite",
           function(aResolver, aTxn, aStore, aRevisionStore) {
             self.removeInternal(aResolver, aStore, aRevisionStore, aId);
           }
         );
       },
 
       clear: function DS_clear() {
-        if (self.readOnly) {
+        if (aReadOnly) {
           return self.throwReadOnly(aWindow);
         }
 
         // Promise<void>
         return self.newDBPromise(aWindow, "readwrite",
           function(aResolver, aTxn, aStore, aRevisionStore) {
             self.clearInternal(aWindow, aResolver, aStore, aRevisionStore);
           }
@@ -419,8 +419,20 @@ DataStore.prototype = {
 
     return object;
   },
 
   delete: function() {
     this.db.delete();
   }
 };
+
+/* DataStoreAccess */
+
+function DataStoreAccess(aAppId, aName, aOrigin, aReadOnly) {
+  this.appId = aAppId;
+  this.name = aName;
+  this.origin = aOrigin;
+  this.readOnly = aReadOnly;
+}
+
+DataStoreAccess.prototype = {};
+
--- a/dom/datastore/DataStoreService.js
+++ b/dom/datastore/DataStoreService.js
@@ -42,16 +42,17 @@ function DataStoreService() {
   }
 
   idbManager.initWindowless(GLOBAL_SCOPE);
 }
 
 DataStoreService.prototype = {
   // Hash of DataStores
   stores: {},
+  accessStores: {},
 
   installDataStore: function(aAppId, aName, aOwner, aReadOnly) {
     debug('installDataStore - appId: ' + aAppId + ', aName: ' +
           aName + ', aOwner:' + aOwner + ', aReadOnly: ' +
           aReadOnly);
 
     if (aName in this.stores && aAppId in this.stores[aName]) {
       debug('This should not happen');
@@ -62,55 +63,102 @@ DataStoreService.prototype = {
 
     if (!(aName in this.stores)) {
       this.stores[aName] = {};
     }
 
     this.stores[aName][aAppId] = store;
   },
 
+  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;
+  },
+
   getDataStores: function(aWindow, aName) {
     debug('getDataStores - aName: ' + aName);
+    let appId = aWindow.document.nodePrincipal.appId;
+
     let self = this;
     return new aWindow.Promise(function(resolver) {
       let matchingStores = [];
 
       if (aName in self.stores) {
-        for (let appId in self.stores[aName]) {
-          matchingStores.push(self.stores[aName][appId]);
+        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) {
         resolver.resolve(results);
         return;
       }
 
       for (let i = 0; i < matchingStores.length; ++i) {
-        let obj = matchingStores[i].exposeObject(aWindow);
+        let obj = matchingStores[i].store.exposeObject(aWindow,
+                                    matchingStores[i].readonly);
         results.push(obj);
 
-        matchingStores[i].retrieveRevisionId(
+        matchingStores[i].store.retrieveRevisionId(
           function() {
             --callbackPending;
             if (!callbackPending) {
               resolver.resolve(results);
             }
           },
           function() {
             resolver.reject();
           }
         );
       }
     });
   },
 
+  getDataStoreAccess: function(aStore, aAppId) {
+    if (!(aStore.name in this.accessStores) ||
+        !(aAppId in this.accessStores[aStore.name])) {
+      return null;
+    }
+
+    return this.accessStores[aStore.name][aAppId];
+  },
+
   observe: function observe(aSubject, aTopic, aData) {
     debug('getDataStores - aTopic: ' + aTopic);
     if (aTopic != 'webapps-clear-data') {
       return;
     }
 
     let params =
       aSubject.QueryInterface(Ci.mozIApplicationClearPrivateDataParams);
--- a/dom/datastore/nsIDataStoreService.idl
+++ b/dom/datastore/nsIDataStoreService.idl
@@ -10,11 +10,16 @@ interface nsIDOMWindow;
 [scriptable, uuid(d193d0e2-c677-4a7b-bb0a-19155b470f2e)]
 interface nsIDataStoreService : nsISupports
 {
   void installDataStore(in unsigned long appId,
                         in DOMString name,
                         in DOMString manifestURL,
                         in boolean readOnly);
 
+  void installAccessDataStore(in unsigned long appId,
+                              in DOMString name,
+                              in DOMString manifestURL,
+                              in boolean readOnly);
+
   nsISupports getDataStores(in nsIDOMWindow window,
                             in DOMString name);
 };
--- a/dom/datastore/tests/Makefile.in
+++ b/dom/datastore/tests/Makefile.in
@@ -8,16 +8,22 @@ srcdir           = @srcdir@
 VPATH            = @srcdir@
 
 relativesrcdir   = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MOCHITEST_FILES = \
   test_app_install.html \
+  file_app_install.html \
   test_readonly.html \
+  file_readonly.html \
   test_basic.html \
+  file_basic.html \
   test_revision.html \
+  file_revision.html \
   file_app.sjs \
   file_app.template.webapp \
+  file_app2.template.webapp \
+  file_app2.template.webapp^headers^ \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
--- a/dom/datastore/tests/file_app.sjs
+++ b/dom/datastore/tests/file_app.sjs
@@ -1,15 +1,22 @@
 var gBasePath = "tests/dom/datastore/tests/";
 var gAppTemplatePath = "tests/dom/datastore/tests/file_app.template.webapp";
 
 function handleRequest(request, response) {
+  var query = getQuery(request);
+
+  var testToken = '';
+  if ('testToken' in query) {
+    testToken = query.testToken;
+  }
+
   var template = gBasePath + 'file_app.template.webapp';
   response.setHeader("Content-Type", "application/x-web-app-manifest+json", false);
-  response.write(readTemplate(template));
+  response.write(readTemplate(template).replace(/TESTTOKEN/g, testToken));
 }
 
 // Copy-pasted incantations. There ought to be a better way to synchronously read
 // a file into a string, but I guess we're trying to discourage that.
 function readTemplate(path) {
   var file = Components.classes["@mozilla.org/file/directory_service;1"].
                         getService(Components.interfaces.nsIProperties).
                         get("CurWorkD", Components.interfaces.nsILocalFile);
@@ -30,8 +37,17 @@ function readTemplate(path) {
     do {
       read = cis.readString(0xffffffff, str); // read as much as we can and put it in str.value
       data += str.value;
     } while (read != 0);
   }
   cis.close();
   return data;
 }
+
+function getQuery(request) {
+  var query = {};
+  request.queryString.split('&').forEach(function (val) {
+    var [name, value] = val.split('=');
+    query[name] = unescape(value);
+  });
+  return query;
+}
--- a/dom/datastore/tests/file_app.template.webapp
+++ b/dom/datastore/tests/file_app.template.webapp
@@ -1,10 +1,14 @@
 {
   "name": "Really Rapid Release (hosted)",
   "description": "Updated even faster than <a href='http://mozilla.org'>Firefox</a>, just to annoy slashdotters.",
-  "launch_path": "/tests/dom/datastore/tests/file_app.sjs",
+  "launch_path": "/tests/dom/datastore/tests/TESTTOKEN",
   "icons": { "128": "default_icon" },
-  "datastores" : {
+  "datastores-owned" : {
+    "foo" : { "access": "readwrite", "description" : "This store is called foo" },
+    "bar" : { "access": "readonly", "description" : "This store is called bar" }
+  },
+  "datastores-access" : {
     "foo" : { "readonly": false, "description" : "This store is called foo" },
     "bar" : { "readonly": true, "description" : "This store is called bar" }
   }
 }
copy from dom/datastore/tests/file_app.template.webapp
copy to dom/datastore/tests/file_app2.template.webapp
--- a/dom/datastore/tests/file_app.template.webapp
+++ b/dom/datastore/tests/file_app2.template.webapp
@@ -1,10 +1,10 @@
 {
-  "name": "Really Rapid Release (hosted)",
+  "name": "Really Rapid Release (hosted) - app 2",
   "description": "Updated even faster than <a href='http://mozilla.org'>Firefox</a>, just to annoy slashdotters.",
-  "launch_path": "/tests/dom/datastore/tests/file_app.sjs",
+  "launch_path": "/tests/dom/datastore/tests/file_readonly.html",
   "icons": { "128": "default_icon" },
-  "datastores" : {
+  "datastores-access" : {
     "foo" : { "readonly": false, "description" : "This store is called foo" },
     "bar" : { "readonly": true, "description" : "This store is called bar" }
   }
 }
new file mode 100644
--- /dev/null
+++ b/dom/datastore/tests/file_app2.template.webapp^headers^
@@ -0,0 +1,1 @@
+Content-Type: application/x-web-app-manifest+json
new file mode 100644
--- /dev/null
+++ b/dom/datastore/tests/file_app_install.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for DataStore - install/uninstall apps</title>
+<body>
+  <script type="application/javascript;version=1.7">
+
+   function is(a, b, msg) {
+     alert((a === b ? 'OK' : 'KO') + ' ' + msg)
+   }
+
+   function ok(a, msg) {
+     alert((a ? 'OK' : 'KO')+ ' ' + msg)
+   }
+
+   function cbError() {
+     alert('KO error');
+   }
+
+   function finish() {
+     alert('DONE');
+   }
+
+   navigator.getDataStores('foo').then(function(stores) {
+     is(stores.length, 1, "getDataStores('foo') returns 1 element");
+     is(stores[0].name, 'foo', 'The dataStore.name is foo');
+     ok(stores[0].owner, 'The dataStore.owner exists');
+     is(stores[0].readOnly, false, 'The dataStore foo is not in readonly');
+
+     navigator.getDataStores('bar').then(function(stores) {
+       is(stores.length, 1, "getDataStores('bar') returns 1 element");
+       is(stores[0].name, 'bar', 'The dataStore.name is bar');
+       ok(stores[0].owner, 'The dataStore.owner exists');
+       is(stores[0].readOnly, false, 'The dataStore bar is in readonly');
+       finish();
+     }, cbError);
+   }, cbError);
+  </script>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/datastore/tests/file_basic.html
@@ -0,0 +1,185 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for DataStore - basic operation on a readonly db</title>
+</head>
+<body>
+<div id="container"></div>
+  <script type="application/javascript;version=1.7">
+
+  var gStore;
+
+  function is(a, b, msg) {
+    alert((a === b ? 'OK' : 'KO') + ' ' + msg)
+  }
+
+  function ok(a, msg) {
+    alert((a ? 'OK' : 'KO')+ ' ' + msg)
+  }
+
+  function cbError() {
+    alert('KO error');
+  }
+
+  function finish() {
+    alert('DONE');
+  }
+
+  function testGetDataStores() {
+    navigator.getDataStores('foo').then(function(stores) {
+      is(stores.length, 1, "getDataStores('foo') returns 1 element");
+      is(stores[0].name, 'foo', 'The dataStore.name is foo');
+      is(stores[0].readOnly, false, 'The dataStore foo is not in readonly');
+
+      var store = stores[0];
+      ok("get" in store, "store.get exists");
+      ok("update" in store, "store.update exists");
+      ok("add" in store, "store.add exists");
+      ok("remove" in store, "store.remove exists");
+      ok("clear" in store, "store.clear exists");
+
+      gStore = stores[0];
+
+      runTest();
+    }, cbError);
+  }
+
+  function testStoreErrorGet(id) {
+    gStore.get(id).then(function(what) {
+      ok(false, "store.get(" + id + ") retrieves data");
+    }, function(error) {
+      ok(true, "store.get() failed properly because the id is non-valid");
+      ok(error instanceof DOMError, "error is a DOMError");
+      is(error.name, "SyntaxError", "Error is a syntax error");
+    }).then(runTest, cbError);
+  }
+
+  function testStoreErrorUpdate(id) {
+    gStore.update(id, "foo").then(function(what) {
+      ok(false, "store.update(" + id + ") retrieves data");
+    }, function(error) {
+      ok(true, "store.update() failed properly because the id is non-valid");
+      ok(error instanceof DOMError, "error is a DOMError");
+      is(error.name, "SyntaxError", "Error is a syntax error");
+    }).then(runTest, cbError);
+  }
+
+  function testStoreErrorRemove(id) {
+    gStore.remove(id).then(function(what) {
+      ok(false, "store.remove(" + id + ") retrieves data");
+    }, function(error) {
+      ok(true, "store.remove() failed properly because the id is non-valid");
+      ok(error instanceof DOMError, "error is a DOMError");
+      is(error.name, "SyntaxError", "Error is a syntax error");
+    }).then(runTest, cbError);
+  }
+
+  function testStoreGet(id, value) {
+    gStore.get(id).then(function(what) {
+      ok(true, "store.get() retrieves data");
+      is(what, value, "store.get(" + id + ") returns " + value);
+    }, function() {
+      ok(false, "store.get(" + id + ") retrieves data");
+    }).then(runTest, cbError);
+  }
+
+  function testStoreAdd(value) {
+    return gStore.add(value).then(function(what) {
+      ok(true, "store.add() is called");
+      ok(what > 0, "store.add() returns something");
+      return what;
+    }, cbError);
+  }
+
+  function testStoreUpdate(id, value) {
+    return gStore.update(id, value).then(function() {
+      ok(true, "store.update() is called");
+    }, cbError);
+  }
+
+  function testStoreRemove(id) {
+    return gStore.remove(id).then(function() {
+      ok(true, "store.remove() is called");
+    }, cbError);
+  }
+
+  function testStoreClear() {
+    return gStore.clear().then(function() {
+      ok(true, "store.clear() is called");
+    }, cbError);
+  }
+
+  var tests = [
+    // Test for GetDataStore
+    testGetDataStores,
+
+    // Broken ID
+    function() { testStoreErrorGet('hello world'); },
+    function() { testStoreErrorGet(true); },
+    function() { testStoreErrorGet(null); },
+
+    // Unknown ID
+    function() { testStoreGet(42, undefined); },
+    function() { testStoreGet(42, undefined); }, // twice
+
+    // Add + Get - number
+    function() { testStoreAdd(42).then(function(id) {
+                   gId = id; runTest(); }, cbError); },
+    function() { testStoreGet(gId, 42); },
+    function() { testStoreGet(gId + "", 42); },
+
+    // Add + Get - boolean
+    function() { testStoreAdd(true).then(function(id) {
+                   gId = id; runTest(); }, cbError); },
+    function() { testStoreGet(gId, true); },
+
+    // Add + Get - string
+    function() { testStoreAdd("hello world").then(function(id) {
+                   gId = id; runTest(); }, cbError); },
+    function() { testStoreGet(gId, "hello world"); },
+
+    // Broken update
+    function() { testStoreErrorUpdate('hello world'); },
+    function() { testStoreErrorUpdate(true); },
+    function() { testStoreErrorUpdate(null); },
+
+    // Update + Get - string
+    function() { testStoreUpdate(gId, "hello world 2").then(function() {
+                   runTest(); }, cbError); },
+    function() { testStoreGet(gId, "hello world 2"); },
+
+    // Broken remove
+    function() { testStoreErrorRemove('hello world'); },
+    function() { testStoreErrorRemove(true); },
+    function() { testStoreErrorRemove(null); },
+
+    // Remove
+    function() { testStoreRemove(gId).then(function(what) {
+                   runTest(); }, cbError); },
+    function() { testStoreGet(gId).catch(function() {
+                   runTest(); }); },
+
+    // Remove - wrong ID
+    function() { testStoreRemove(gId).then(function(what) {
+                   runTest(); }, cbError); },
+
+    // Clear
+    function() { testStoreClear().then(function(what) {
+                   runTest(); }, cbError); },
+  ];
+
+  function runTest() {
+    if (!tests.length) {
+      finish();
+      return;
+    }
+
+    var test = tests.shift();
+    test();
+  }
+
+  runTest();
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/datastore/tests/file_readonly.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for DataStore - basic operation on a readonly db</title>
+</head>
+<body>
+  <div id="container"></div>
+  <script type="application/javascript;version=1.7">
+
+  function is(a, b, msg) {
+    dump((a === b ? 'OK' : 'KO') + ' ' + msg + "\n")
+    alert((a === b ? 'OK' : 'KO') + ' ' + msg)
+  }
+
+  function ok(a, msg) {
+    alert((a ? 'OK' : 'KO')+ ' ' + msg)
+  }
+
+  function cbError() {
+    alert('KO error');
+  }
+
+  function finish() {
+    alert('DONE');
+  }
+
+  function runTest() {
+    navigator.getDataStores('bar').then(function(stores) {
+      is(stores.length, 1, "getDataStores('bar') returns 1 element");
+      is(stores[0].name, 'bar', 'The dataStore.name is bar');
+      is(stores[0].readOnly, true, 'The dataStore bar is eadonly');
+
+      var store = stores[0];
+      ok("get" in store, "store.get exists");
+      ok("update" in store, "store.update exists");
+      ok("add" in store, "store.add exists");
+      ok("remove" in store, "store.remove exists");
+      ok("clear" in store, "store.clear exists");
+
+      var f = store.clear();
+      f = f.then(cbError, function() {
+        ok(true, "store.clear() fails because the db is readonly");
+        return store.remove(123);
+      });
+
+      f = f.then(cbError, function() {
+        ok(true, "store.remove() fails because the db is readonly");
+        return store.add(123, true);
+      });
+
+      f = f.then(cbError, function() {
+        ok(true, "store.add() fails because the db is readonly");
+        return store.update(123, {});
+      })
+
+      f = f.then(cbError, function() {
+        ok(true, "store.update() fails because the db is readonly");
+      })
+
+      f.then(function() {
+        // All done.
+        ok(true, "All done");
+        finish();
+      });
+    }, cbError);
+  }
+
+  runTest();
+  </script>
+</body>
+</html>
copy from dom/datastore/tests/test_revision.html
copy to dom/datastore/tests/file_revision.html
--- a/dom/datastore/tests/test_revision.html
+++ b/dom/datastore/tests/file_revision.html
@@ -1,42 +1,43 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Test for DataStore - basic operation on a readonly db</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
   <script type="application/javascript;version=1.7">
 
-  var gBaseURL = 'http://test/tests/dom/datastore/tests/';
-  var gHostedManifestURL = gBaseURL + 'file_app.sjs';
-  var gApp;
   var gStore;
   var gPreviousRevisionId = '';
 
-  function cbError() {
-    ok(false, "Error callback invoked");
-    finish();
+  function is(a, b, msg) {
+    alert((a === b ? 'OK' : 'KO') + ' ' + msg)
+  }
+
+  function isnot(a, b, msg) {
+    alert((a !== b ? 'OK' : 'KO') + ' ' + msg)
   }
 
-  function installApp() {
-    var request = navigator.mozApps.install(gHostedManifestURL);
-    request.onerror = cbError;
-    request.onsuccess = function() {
-      gApp = request.result;
-      runTest();
-    }
+  function ok(a, msg) {
+    alert((a ? 'OK' : 'KO')+ ' ' + msg)
+  }
+
+  function cbError() {
+    alert('KO error');
+  }
+
+  function finish() {
+    alert('DONE');
   }
 
   function testGetDataStores() {
     navigator.getDataStores('foo').then(function(stores) {
       is(stores.length, 1, "getDataStores('foo') returns 1 element");
       is(stores[0].name, 'foo', 'The dataStore.name is foo');
       is(stores[0].readOnly, false, 'The dataStore foo is not in readonly');
 
@@ -90,62 +91,30 @@
          JSON.stringify(what.updatedIds) + " | " + JSON.stringify(changes.updatedIds));
       is(JSON.stringify(changes.removedIds),
          JSON.stringify(what.removedIds), "store.revisions - removedIds: " +
          JSON.stringify(what.removedIds) + " | " + JSON.stringify(changes.removedIds));
       runTest();
     }, cbError);
   }
 
-  function uninstallApp() {
-    // Uninstall the app.
-    request = navigator.mozApps.mgmt.uninstall(gApp);
-    request.onerror = cbError;
-    request.onsuccess = function() {
-      // All done.
-      ok(true, "All done");
-      runTest();
-    }
-  }
-
   function testStoreRevisionIdChanged() {
     isnot(gStore.revisionId, gPreviousRevisionId, "Revision changed");
     gPreviousRevisionId = gStore.revisionId;
     runTest();
   }
 
   function testStoreRevisionIdNotChanged() {
     is(gStore.revisionId, gPreviousRevisionId, "Revision changed");
     runTest();
   }
 
   var revisions = [];
 
   var tests = [
-    // Permissions
-    function() {
-      SpecialPowers.pushPermissions(
-        [{ "type": "browser", "allow": 1, "context": document },
-         { "type": "embed-apps", "allow": 1, "context": document },
-         { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
-    },
-
-    // Preferences
-    function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
-    },
-
-    // No confirmation needed when an app is installed
-    function() {
-      SpecialPowers.autoConfirmAppInstall(runTest);
-    },
-
-    // Installing the app
-    installApp,
-
     // Test for GetDataStore
     testGetDataStores,
 
     // The first revision is not empty
     testStoreRevisionIdChanged,
 
     // wrong revision ID
     function() { testStoreWrongRevisions('foobar'); },
@@ -202,34 +171,26 @@
     function() { testStoreRevisions(revisions[5], { addedIds: [], updatedIds: [], removedIds: [] }); },
 
     function() { testStoreRemove(3, false); },
     testStoreRevisionIdNotChanged,
 
     // Remove
     function() { testStoreRemove(42, false); },
     testStoreRevisionIdNotChanged,
-
-    // Uninstall the app
-    uninstallApp
   ];
 
   function runTest() {
     if (!tests.length) {
       finish();
       return;
     }
 
     var test = tests.shift();
     test();
   }
 
-  function finish() {
-    SimpleTest.finish();
-  }
-
-  SimpleTest.waitForExplicitFinish();
   runTest();
   </script>
 </pre>
 </body>
 </html>
 
--- a/dom/datastore/tests/test_app_install.html
+++ b/dom/datastore/tests/test_app_install.html
@@ -8,90 +8,94 @@
 </head>
 <body>
 <div id="container"></div>
   <script type="application/javascript;version=1.7">
 
   SimpleTest.waitForExplicitFinish();
 
   var gBaseURL = 'http://test/tests/dom/datastore/tests/';
-  var gHostedManifestURL = gBaseURL + 'file_app.sjs';
+  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() { gGenerator.next() });
 
   function continueTest() {
-    gGenerator.next();
+    try { gGenerator.next(); }
+    catch(e) { dump("Got exception: " + e + "\n"); }
   }
 
   function cbError() {
     ok(false, "Error callback invoked");
     finish();
   }
 
   function runTest() {
     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.setBoolPref("dom.mozBrowserFramesEnabled", true);
+
     SpecialPowers.autoConfirmAppInstall(continueTest);
     yield undefined;
 
     var request = navigator.mozApps.install(gHostedManifestURL);
     request.onerror = cbError;
     request.onsuccess = continueTest;
     yield undefined;
 
     var app = request.result;
     isnot(app, null, "App is non-null");
     is(app.manifest.description, "Updated even faster than Firefox, just to annoy slashdotters.",
        "Manifest is HTML-sanitized");
 
-    navigator.getDataStores('foo').then(function(stores) {
-      is(stores.length, 1, "getDataStores('foo') returns 1 element");
-      is(stores[0].name, 'foo', 'The dataStore.name is foo');
-      is(stores[0].owner, 'http://test/tests/dom/datastore/tests/file_app.sjs', 'The dataStore.owner exists');
-      is(stores[0].readOnly, false, 'The dataStore foo is not in readonly');
-      continueTest();
-    }, cbError);
-    yield undefined;
+    var ifr = document.createElement('iframe');
+    ifr.setAttribute('mozbrowser', 'true');
+    ifr.setAttribute('mozapp', app.manifestURL);
+    ifr.setAttribute('src', app.manifest.launch_path);
+    var domParent = document.getElementById('container');
 
-    navigator.getDataStores('bar').then(function(stores) {
-      is(stores.length, 1, "getDataStores('bar') returns 1 element");
-      is(stores[0].name, 'bar', 'The dataStore.name is bar');
-      is(stores[0].owner, 'http://test/tests/dom/datastore/tests/file_app.sjs', 'The dataStore.owner exists');
-      is(stores[0].readOnly, true, 'The dataStore bar is in readonly');
-      continueTest();
-    }, cbError);
-    yield undefined;
+    // Set us up to listen for messages from the app.
+    var listener = function(e) {
+      var message = e.detail.message;
+      if (/^OK/.exec(message)) {
+        ok(true, "Message from app: " + message);
+      } else if (/KO/.exec(message)) {
+        ok(false, "Message from app: " + message);
+      } else if (/DONE/.exec(message)) {
+        ok(true, "Messaging from app complete");
+        ifr.removeEventListener('mozbrowsershowmodalprompt', listener);
+        domParent.removeChild(ifr);
 
-    // Uninstall the app.
-    request = navigator.mozApps.mgmt.uninstall(app);
-    request.onerror = cbError;
-    request.onsuccess = continueTest;
-    yield undefined;
+        // Uninstall the app.
+        request = navigator.mozApps.mgmt.uninstall(app);
+        request.onerror = cbError;
+        request.onsuccess = function() {
+          // All done.
+          ok(true, "All done");
+          finish();
+        }
+      }
+    }
 
-    navigator.getDataStores('foo').then(function(stores) {
-      is(stores.length, 0, "getDataStores('foo') returns 0 elements");
-      continueTest();
-    }, cbError);
-    yield undefined;
+    // This event is triggered when the app calls "alert".
+    ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
 
-    // All done.
-    info("All done");
-    finish();
+    domParent.appendChild(ifr);
   }
 
   function finish() {
+    SpecialPowers.clearUserPref("dom.mozBrowserFramesEnabled");
     SimpleTest.finish();
   }
 
   </script>
 </body>
 </html>
--- a/dom/datastore/tests/test_basic.html
+++ b/dom/datastore/tests/test_basic.html
@@ -6,208 +6,100 @@
   <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">
 
   var gBaseURL = 'http://test/tests/dom/datastore/tests/';
-  var gHostedManifestURL = gBaseURL + 'file_app.sjs';
+  var gHostedManifestURL = gBaseURL + 'file_app.sjs?testToken=file_basic.html';
   var gApp;
-  var gStore;
 
   function cbError() {
     ok(false, "Error callback invoked");
     finish();
   }
 
   function installApp() {
     var request = navigator.mozApps.install(gHostedManifestURL);
     request.onerror = cbError;
     request.onsuccess = function() {
       gApp = request.result;
       runTest();
     }
   }
 
-  function testGetDataStores() {
-    navigator.getDataStores('foo').then(function(stores) {
-      is(stores.length, 1, "getDataStores('foo') returns 1 element");
-      is(stores[0].name, 'foo', 'The dataStore.name is foo');
-      is(stores[0].readOnly, false, 'The dataStore foo is not in readonly');
-
-      var store = stores[0];
-      ok("get" in store, "store.get exists");
-      ok("update" in store, "store.update exists");
-      ok("add" in store, "store.add exists");
-      ok("remove" in store, "store.remove exists");
-      ok("clear" in store, "store.clear exists");
-
-      gStore = stores[0];
-
-      runTest();
-    }, cbError);
-  }
-
-  function testStoreErrorGet(id) {
-    gStore.get(id).then(function(what) {
-      ok(false, "store.get(" + id + ") retrieves data");
-    }, function(error) {
-      ok(true, "store.get() failed properly because the id is non-valid");
-      ok(error instanceof DOMError, "error is a DOMError");
-      is(error.name, "SyntaxError", "Error is a syntax error");
-    }).then(runTest, cbError);
-  }
-
-  function testStoreErrorUpdate(id) {
-    gStore.update(id, "foo").then(function(what) {
-      ok(false, "store.update(" + id + ") retrieves data");
-    }, function(error) {
-      ok(true, "store.update() failed properly because the id is non-valid");
-      ok(error instanceof DOMError, "error is a DOMError");
-      is(error.name, "SyntaxError", "Error is a syntax error");
-    }).then(runTest, cbError);
-  }
-
-  function testStoreErrorRemove(id) {
-    gStore.remove(id).then(function(what) {
-      ok(false, "store.remove(" + id + ") retrieves data");
-    }, function(error) {
-      ok(true, "store.remove() failed properly because the id is non-valid");
-      ok(error instanceof DOMError, "error is a DOMError");
-      is(error.name, "SyntaxError", "Error is a syntax error");
-    }).then(runTest, cbError);
-  }
-
-  function testStoreGet(id, value) {
-    gStore.get(id).then(function(what) {
-      ok(true, "store.get() retrieves data");
-      is(what, value, "store.get(" + id + ") returns " + value);
-    }, function() {
-      ok(false, "store.get(" + id + ") retrieves data");
-    }).then(runTest, cbError);
-  }
-
-  function testStoreAdd(value) {
-    return gStore.add(value).then(function(what) {
-      ok(true, "store.add() is called");
-      ok(what > 0, "store.add() returns something");
-      return what;
-    }, cbError);
-  }
-
-  function testStoreUpdate(id, value) {
-    return gStore.update(id, value).then(function(what) {
-      ok(true, "store.update() is called");
-      is(id, what, "store.update(" + id + ") updates the correct id");
-    }, cbError);
-  }
-
-  function testStoreRemove(id) {
-    return gStore.remove(id).then(function() {
-      ok(true, "store.remove() is called");
-    }, cbError);
-  }
-
-  function testStoreClear() {
-    return gStore.clear().then(function() {
-      ok(true, "store.clear() is called");
-    }, cbError);
-  }
-
   function uninstallApp() {
     // Uninstall the app.
-    request = navigator.mozApps.mgmt.uninstall(gApp);
+    var request = navigator.mozApps.mgmt.uninstall(gApp);
     request.onerror = cbError;
     request.onsuccess = function() {
       // All done.
       info("All done");
       runTest();
     }
   }
 
+  function testApp() {
+    var ifr = document.createElement('iframe');
+    ifr.setAttribute('mozbrowser', 'true');
+    ifr.setAttribute('mozapp', gApp.manifestURL);
+    ifr.setAttribute('src', gApp.manifest.launch_path);
+    var domParent = document.getElementById('container');
+
+    // Set us up to listen for messages from the app.
+    var listener = function(e) {
+      var message = e.detail.message;
+      if (/^OK/.exec(message)) {
+        ok(true, "Message from app: " + message);
+      } else if (/KO/.exec(message)) {
+        ok(false, "Message from app: " + message);
+      } else if (/DONE/.exec(message)) {
+        ok(true, "Messaging from app complete");
+        ifr.removeEventListener('mozbrowsershowmodalprompt', listener);
+        domParent.removeChild(ifr);
+        runTest();
+      }
+    }
+
+    // This event is triggered when the app calls "alert".
+    ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
+    domParent.appendChild(ifr);
+  }
+
   var tests = [
     // Permissions
     function() {
       SpecialPowers.pushPermissions(
         [{ "type": "browser", "allow": 1, "context": document },
          { "type": "embed-apps", "allow": 1, "context": document },
          { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
     },
 
     // Preferences
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
     },
 
+    function() {
+      SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
+      runTest();
+    },
+
     // No confirmation needed when an app is installed
     function() {
       SpecialPowers.autoConfirmAppInstall(runTest);
     },
 
     // Installing the app
     installApp,
 
-    // Test for GetDataStore
-    testGetDataStores,
-
-    // Broken ID
-    function() { testStoreErrorGet('hello world'); },
-    function() { testStoreErrorGet(true); },
-    function() { testStoreErrorGet(null); },
-
-    // Unknown ID
-    function() { testStoreGet(42, undefined); },
-    function() { testStoreGet(42, undefined); }, // twice
-
-    // Add + Get - number
-    function() { testStoreAdd(42).then(function(id) {
-                   gId = id; runTest(); }, cbError); },
-    function() { testStoreGet(gId, 42); },
-    function() { testStoreGet(gId+"", 42); },
-
-    // Add + Get - boolean
-    function() { testStoreAdd(true).then(function(id) {
-                   gId = id; runTest(); }, cbError); },
-    function() { testStoreGet(gId, true); },
-
-    // Add + Get - string
-    function() { testStoreAdd("hello world").then(function(id) {
-                   gId = id; runTest(); }, cbError); },
-    function() { testStoreGet(gId, "hello world"); },
-
-    // Broken update
-    function() { testStoreErrorUpdate('hello world'); },
-    function() { testStoreErrorUpdate(true); },
-    function() { testStoreErrorUpdate(null); },
-
-    // Update + Get - string
-    function() { testStoreUpdate(gId, "hello world 2").then(function() {
-                   runTest(); }, cbError); },
-    function() { testStoreGet(gId, "hello world 2"); },
-
-    // Broken remove
-    function() { testStoreErrorRemove('hello world'); },
-    function() { testStoreErrorRemove(true); },
-    function() { testStoreErrorRemove(null); },
-
-    // Remove
-    function() { testStoreRemove(gId).then(function(what) {
-                   runTest(); }, cbError); },
-    function() { testStoreGet(gId).catch(function() {
-                   runTest(); }); },
-
-    // Remove - wrong ID
-    function() { testStoreRemove(gId).then(function(what) {
-                   runTest(); }, cbError); },
-
-    // Clear
-    function() { testStoreClear().then(function(what) {
-                   runTest(); }, cbError); },
+    // Run tests in app
+    testApp,
 
     // Uninstall the app
     uninstallApp
   ];
 
   function runTest() {
     if (!tests.length) {
       finish();
--- a/dom/datastore/tests/test_readonly.html
+++ b/dom/datastore/tests/test_readonly.html
@@ -5,17 +5,18 @@
   <title>Test for DataStore - basic operation on a readonly db</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">
   var gBaseURL = 'http://test/tests/dom/datastore/tests/';
-  var gHostedManifestURL = gBaseURL + 'file_app.sjs';
+  var gHostedManifestURL = gBaseURL + 'file_app.sjs?testToken=file_readonly.html';
+  var gHostedManifestURL2 = 'http://example.com/tests/dom/datastore/tests/file_app2.template.webapp';
   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() { gGenerator.next() });
 
@@ -25,72 +26,77 @@
   }
 
   function cbError() {
     ok(false, "Error callback invoked");
     finish();
   }
 
   function runTest() {
+    SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
+
     SpecialPowers.autoConfirmAppInstall(continueTest);
     yield undefined;
 
     var request = navigator.mozApps.install(gHostedManifestURL);
     request.onerror = cbError;
     request.onsuccess = continueTest;
     yield undefined;
 
     var app = request.result;
 
-    navigator.getDataStores('bar').then(function(stores) {
-      is(stores.length, 1, "getDataStores('bar') returns 1 element");
-      is(stores[0].name, 'bar', 'The dataStore.name is bar');
-      is(stores[0].readOnly, true, 'The dataStore bar is readonly');
+    request = navigator.mozApps.install(gHostedManifestURL2);
+    request.onerror = cbError;
+    request.onsuccess = continueTest;
+    yield undefined;
 
-      var store = stores[0];
-      ok("get" in store, "store.get exists");
-      ok("update" in store, "store.update exists");
-      ok("add" in store, "store.add exists");
-      ok("remove" in store, "store.remove exists");
-      ok("clear" in store, "store.clear exists");
+    var app2 = request.result;
+
+    var ifr = document.createElement('iframe');
+    ifr.setAttribute('mozbrowser', 'true');
+    ifr.setAttribute('mozapp', app2.manifestURL);
+    ifr.setAttribute('src', 'http://example.com/tests/dom/datastore/tests/file_readonly.html');
+    var domParent = document.getElementById('container');
 
-      var f = store.clear();
-      f = f.then(cbError, function() {
-        ok(true, "store.clear() fails because the db is readonly");
-        return store.remove(123);
-      });
-
-      f = f.then(cbError, function() {
-        ok(true, "store.remove() fails because the db is readonly");
-        return store.add(123, true);
-      });
+    // Set us up to listen for messages from the app.
+    var listener = function(e) {
+      var message = e.detail.message;
+      if (/^OK/.exec(message)) {
+        ok(true, "Message from app: " + message);
+      } else if (/KO/.exec(message)) {
+        ok(false, "Message from app: " + message);
+      } else if (/DONE/.exec(message)) {
+        ok(true, "Messaging from app complete");
+        ifr.removeEventListener('mozbrowsershowmodalprompt', listener);
+        domParent.removeChild(ifr);
 
-      f = f.then(cbError, function() {
-        ok(true, "store.add() fails because the db is readonly");
-        return store.update(123, {});
-      })
-
-      f = f.then(cbError, function() {
-        ok(true, "store.update() fails because the db is readonly");
-      })
-
-      f.then(function() {
         // Uninstall the app.
         request = navigator.mozApps.mgmt.uninstall(app);
         request.onerror = cbError;
         request.onsuccess = function() {
-          // All done.
-          info("All done");
-          finish();
+          // Uninstall app2
+          request = navigator.mozApps.mgmt.uninstall(app2);
+          request.onerror = cbError;
+          request.onsuccess = function() {
+            // All done.
+            info("All done");
+            finish();
+          }
         }
-      });
-    }, cbError);
+      }
+    }
+
+    // This event is triggered when the app calls "alert".
+    ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
+
+    domParent.appendChild(ifr);
   }
 
   function finish() {
+    SpecialPowers.clearUserPref("dom.mozBrowserFramesEnabled");
     SimpleTest.finish();
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
   </script>
 </body>
 </html>
--- a/dom/datastore/tests/test_revision.html
+++ b/dom/datastore/tests/test_revision.html
@@ -10,17 +10,17 @@
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
   <script type="application/javascript;version=1.7">
 
   var gBaseURL = 'http://test/tests/dom/datastore/tests/';
-  var gHostedManifestURL = gBaseURL + 'file_app.sjs';
+  var gHostedManifestURL = gBaseURL + 'file_app.sjs?testToken=file_revision.html';
   var gApp;
   var gStore;
   var gPreviousRevisionId = '';
 
   function cbError() {
     ok(false, "Error callback invoked");
     finish();
   }
@@ -29,97 +29,52 @@
     var request = navigator.mozApps.install(gHostedManifestURL);
     request.onerror = cbError;
     request.onsuccess = function() {
       gApp = request.result;
       runTest();
     }
   }
 
-  function testGetDataStores() {
-    navigator.getDataStores('foo').then(function(stores) {
-      is(stores.length, 1, "getDataStores('foo') returns 1 element");
-      is(stores[0].name, 'foo', 'The dataStore.name is foo');
-      is(stores[0].readOnly, false, 'The dataStore foo is not in readonly');
-
-      gStore = stores[0];
-
-      runTest();
-    }, cbError);
-  }
-
-  function testStoreAdd(value, expectedId) {
-    return gStore.add(value).then(function(id) {
-      is(id, expectedId, "store.add() is called");
-      runTest();
-    }, cbError);
-  }
-
-  function testStoreUpdate(id, value) {
-    return gStore.update(id, value).then(function(retId) {
-      is(id, retId, "store.update() is called with the right id");
-      runTest();
-    }, cbError);
-  }
-
-  function testStoreRemove(id, expectedSuccess) {
-    return gStore.remove(id).then(function(success) {
-      is(success, expectedSuccess, "store.remove() returns the right value");
-      runTest();
-    }, cbError);
-  }
-
-  function testStoreRevisionId() {
-    is(/[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}/.test(gStore.revisionId), true, "store.revisionId returns something");
-    runTest();
-  }
-
-  function testStoreWrongRevisions(id) {
-    return gStore.getChanges(id).then(
-      function(what) {
-        is(what, undefined, "Wrong revisionId == undefined object");
-        runTest();
-      }, cbError);
-  }
-
-  function testStoreRevisions(id, changes) {
-    return gStore.getChanges(id).then(function(what) {
-      is(JSON.stringify(changes.addedIds),
-         JSON.stringify(what.addedIds), "store.revisions - addedIds: " +
-         JSON.stringify(what.addedIds) + " | " + JSON.stringify(changes.addedIds));
-      is(JSON.stringify(changes.updatedIds),
-         JSON.stringify(what.updatedIds), "store.revisions - updatedIds: " +
-         JSON.stringify(what.updatedIds) + " | " + JSON.stringify(changes.updatedIds));
-      is(JSON.stringify(changes.removedIds),
-         JSON.stringify(what.removedIds), "store.revisions - removedIds: " +
-         JSON.stringify(what.removedIds) + " | " + JSON.stringify(changes.removedIds));
-      runTest();
-    }, cbError);
-  }
-
   function uninstallApp() {
     // Uninstall the app.
-    request = navigator.mozApps.mgmt.uninstall(gApp);
+    var request = navigator.mozApps.mgmt.uninstall(gApp);
     request.onerror = cbError;
     request.onsuccess = function() {
       // All done.
       ok(true, "All done");
       runTest();
     }
   }
 
-  function testStoreRevisionIdChanged() {
-    isnot(gStore.revisionId, gPreviousRevisionId, "Revision changed");
-    gPreviousRevisionId = gStore.revisionId;
-    runTest();
-  }
+  function testApp() {
+    var ifr = document.createElement('iframe');
+    ifr.setAttribute('mozbrowser', 'true');
+    ifr.setAttribute('mozapp', gApp.manifestURL);
+    ifr.setAttribute('src', gApp.manifest.launch_path);
+    var domParent = document.getElementById('content');
 
-  function testStoreRevisionIdNotChanged() {
-    is(gStore.revisionId, gPreviousRevisionId, "Revision changed");
-    runTest();
+    // Set us up to listen for messages from the app.
+    var listener = function(e) {
+      var message = e.detail.message;
+      if (/^OK/.exec(message)) {
+        ok(true, "Message from app: " + message);
+      } else if (/KO/.exec(message)) {
+        ok(false, "Message from app: " + message);
+      } else if (/DONE/.exec(message)) {
+        ok(true, "Messaging from app complete");
+        ifr.removeEventListener('mozbrowsershowmodalprompt', listener);
+        domParent.removeChild(ifr);
+        runTest();
+      }
+    }
+
+    // This event is triggered when the app calls "alert".
+    ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
+    domParent.appendChild(ifr);
   }
 
   var revisions = [];
 
   var tests = [
     // Permissions
     function() {
       SpecialPowers.pushPermissions(
@@ -128,90 +83,31 @@
          { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
     },
 
     // Preferences
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
     },
 
+    // Enabling mozBrowser
+    function() {
+      SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true]]}, runTest);
+    },
+
     // No confirmation needed when an app is installed
     function() {
       SpecialPowers.autoConfirmAppInstall(runTest);
     },
 
     // Installing the app
     installApp,
 
-    // Test for GetDataStore
-    testGetDataStores,
-
-    // The first revision is not empty
-    testStoreRevisionIdChanged,
-
-    // wrong revision ID
-    function() { testStoreWrongRevisions('foobar'); },
-
-    // Add
-    function() { testStoreAdd({ number: 42 }, 1); },
-    function() { revisions.push(gStore.revisionId); testStoreRevisionId(); },
-    testStoreRevisionIdChanged,
-    function() { testStoreRevisions(revisions[0], { addedIds: [], updatedIds: [], removedIds: [] }); },
-
-    // Add
-    function() { testStoreAdd({ number: 42 }, 2); },
-    function() { revisions.push(gStore.revisionId); runTest(); },
-    testStoreRevisionIdChanged,
-    function() { testStoreRevisions(revisions[0], { addedIds: [2], updatedIds: [], removedIds: [] }); },
-    function() { testStoreRevisions(revisions[1], { addedIds: [], updatedIds: [], removedIds: [] }); },
-
-    // Add
-    function() { testStoreAdd({ number: 42 }, 3); },
-    function() { revisions.push(gStore.revisionId); runTest(); },
-    testStoreRevisionIdChanged,
-    function() { testStoreRevisions(revisions[0], { addedIds: [2,3], updatedIds: [], removedIds: [] }); },
-    function() { testStoreRevisions(revisions[1], { addedIds: [3], updatedIds: [], removedIds: [] }); },
-    function() { testStoreRevisions(revisions[2], { addedIds: [], updatedIds: [], removedIds: [] }); },
-
-    // Update
-    function() { testStoreUpdate(3, { number: 43 }); },
-    function() { revisions.push(gStore.revisionId); runTest(); },
-    testStoreRevisionIdChanged,
-    function() { testStoreRevisions(revisions[0], { addedIds: [2,3], updatedIds: [], removedIds: [] }); },
-    function() { testStoreRevisions(revisions[1], { addedIds: [3], updatedIds: [], removedIds: [] }); },
-    function() { testStoreRevisions(revisions[2], { addedIds: [], updatedIds: [3], removedIds: [] }); },
-    function() { testStoreRevisions(revisions[3], { addedIds: [], updatedIds: [], removedIds: [] }); },
-
-    // Update
-    function() { testStoreUpdate(3, { number: 42 }); },
-    function() { revisions.push(gStore.revisionId); runTest(); },
-    testStoreRevisionIdChanged,
-    function() { testStoreRevisions(revisions[0], { addedIds: [2,3], updatedIds: [], removedIds: [] }); },
-    function() { testStoreRevisions(revisions[1], { addedIds: [3], updatedIds: [], removedIds: [] }); },
-    function() { testStoreRevisions(revisions[2], { addedIds: [], updatedIds: [3], removedIds: [] }); },
-    function() { testStoreRevisions(revisions[3], { addedIds: [], updatedIds: [3], removedIds: [] }); },
-    function() { testStoreRevisions(revisions[4], { addedIds: [], updatedIds: [], removedIds: [] }); },
-
-    // Remove
-    function() { testStoreRemove(3, true); },
-    function() { revisions.push(gStore.revisionId); runTest(); },
-    testStoreRevisionIdChanged,
-    function() { testStoreRevisions(revisions[0], { addedIds: [2], updatedIds: [], removedIds: [] }); },
-    function() { testStoreRevisions(revisions[1], { addedIds: [], updatedIds: [], removedIds: [] }); },
-    function() { testStoreRevisions(revisions[2], { addedIds: [], updatedIds: [], removedIds: [3] }); },
-    function() { testStoreRevisions(revisions[3], { addedIds: [], updatedIds: [], removedIds: [3] }); },
-    function() { testStoreRevisions(revisions[4], { addedIds: [], updatedIds: [], removedIds: [3] }); },
-    function() { testStoreRevisions(revisions[5], { addedIds: [], updatedIds: [], removedIds: [] }); },
-
-    function() { testStoreRemove(3, false); },
-    testStoreRevisionIdNotChanged,
-
-    // Remove
-    function() { testStoreRemove(42, false); },
-    testStoreRevisionIdNotChanged,
+    // Run tests in app
+    testApp,
 
     // Uninstall the app
     uninstallApp
   ];
 
   function runTest() {
     if (!tests.length) {
       finish();