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 159466 93b050a79db4b5b18598ddc9be2927becde21d8c
parent 159465 ec3382ceef99f527916f270e95ba0a58af8ddbe2
child 159467 639ec7a627f89c9fa287537137418fc8c6ec2933
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmounir, ehsan
bugs871445
milestone26.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
@@ -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();