Bug 871445 - patch 4 - DatAStore: permissions, owned/access, r=mounir, r=ehsan
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 02 Oct 2013 13:27:14 -0400
changeset 163549 ce3eceb7fa125532171e5985e5a4b1e6ae284b00
parent 163548 46b906bb782b2e12d82134ef205e6a11666205dc
child 163550 0b0557b547aa33c05a12660d9fff80f1585ec8f7
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)
reviewersmounir, ehsan
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 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
@@ -548,26 +548,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.
   //
   // TODO Bug 908094 Refine _registerSystemMessagesForEntryPoint(...).
   _registerSystemMessagesForEntryPoint: function(aManifest, aApp, 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(aResolve, aReject, aTxn, aStore, aRevisionStore) {
             self.updateInternal(aResolve, 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(aResolve, aReject, aTxn, aStore, aRevisionStore) {
             self.addInternal(aResolve, 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(aResolve, aReject, aTxn, aStore, aRevisionStore) {
             self.removeInternal(aResolve, aStore, aRevisionStore, aId);
           }
         );
       },
 
       clear: function DS_clear() {
-        if (self.readOnly) {
+        if (aReadOnly) {
           return self.throwReadOnly(aWindow);
         }
 
         // Promise<void>
         return self.newDBPromise(aWindow, "readwrite",
           function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
             self.clearInternal(aResolve, 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
@@ -32,16 +32,17 @@ function DataStoreService() {
   }
 
   obs.addObserver(this, 'webapps-clear-data', false);
 }
 
 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');
@@ -52,55 +53,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(resolve, reject) {
       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) {
         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) {
               resolve(results);
             }
           },
           function() {
             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();