diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm index 6a51d909dfb..426e8d483b9 100644 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -541,16 +541,26 @@ this.DOMApplicationRegistry = { }, 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; + 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; - dataStoreService.installDataStore(aId, name, aManifestURL, readonly); + // The first release is always in readonly mode. + dataStoreService.installAccessDataStore(aId, name, aManifestURL, + /* readonly */ true); + } } }, diff --git a/dom/datastore/DataStore.jsm b/dom/datastore/DataStore.jsm index fdeb9d0abc5..714dc20c22f 100644 --- a/dom/datastore/DataStore.jsm +++ b/dom/datastore/DataStore.jsm @@ -6,7 +6,7 @@ 'use strict' -var EXPORTED_SYMBOLS = ["DataStore"]; +var EXPORTED_SYMBOLS = ["DataStore", "DataStoreAccess"]; function debug(s) { // dump('DEBUG DataStore: ' + s + '\n'); @@ -204,7 +204,7 @@ DataStore.prototype = { new aWindow.DOMError("ReadOnlyError", "DataStore in readonly mode")); }, - exposeObject: function(aWindow) { + exposeObject: function(aWindow, aReadOnly) { let self = this; let object = { @@ -219,7 +219,7 @@ DataStore.prototype = { }, get readOnly() { - return self.readOnly; + return aReadOnly; }, get: function DS_get(aId) { @@ -242,7 +242,7 @@ DataStore.prototype = { return self.throwInvalidArg(aWindow); } - if (self.readOnly) { + if (aReadOnly) { return self.throwReadOnly(aWindow); } @@ -255,7 +255,7 @@ DataStore.prototype = { }, add: function DS_add(aObj) { - if (self.readOnly) { + if (aReadOnly) { return self.throwReadOnly(aWindow); } @@ -273,7 +273,7 @@ DataStore.prototype = { return self.throwInvalidArg(aWindow); } - if (self.readOnly) { + if (aReadOnly) { return self.throwReadOnly(aWindow); } @@ -286,7 +286,7 @@ DataStore.prototype = { }, clear: function DS_clear() { - if (self.readOnly) { + if (aReadOnly) { return self.throwReadOnly(aWindow); } @@ -424,3 +424,15 @@ DataStore.prototype = { this.db.delete(); } }; + +/* DataStoreAccess */ + +function DataStoreAccess(aAppId, aName, aOrigin, aReadOnly) { + this.appId = aAppId; + this.name = aName; + this.origin = aOrigin; + this.readOnly = aReadOnly; +} + +DataStoreAccess.prototype = {}; + diff --git a/dom/datastore/DataStoreService.js b/dom/datastore/DataStoreService.js index 12ee0e28ea5..fad46e2d407 100644 --- a/dom/datastore/DataStoreService.js +++ b/dom/datastore/DataStoreService.js @@ -47,6 +47,7 @@ function DataStoreService() { DataStoreService.prototype = { // Hash of DataStores stores: {}, + accessStores: {}, installDataStore: function(aAppId, aName, aOwner, aReadOnly) { debug('installDataStore - appId: ' + aAppId + ', aName: ' + @@ -67,15 +68,52 @@ DataStoreService.prototype = { 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 }); } } @@ -88,10 +126,11 @@ DataStoreService.prototype = { } 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) { @@ -106,6 +145,15 @@ DataStoreService.prototype = { }); }, + 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') { diff --git a/dom/datastore/nsIDataStoreService.idl b/dom/datastore/nsIDataStoreService.idl index 26aaa3b22e4..ab9ba87b2da 100644 --- a/dom/datastore/nsIDataStoreService.idl +++ b/dom/datastore/nsIDataStoreService.idl @@ -15,6 +15,11 @@ interface nsIDataStoreService : nsISupports 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); }; diff --git a/dom/datastore/tests/Makefile.in b/dom/datastore/tests/Makefile.in index 52131807ef8..b13c1ba3a91 100644 --- a/dom/datastore/tests/Makefile.in +++ b/dom/datastore/tests/Makefile.in @@ -13,11 +13,17 @@ 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 diff --git a/dom/datastore/tests/file_app.sjs b/dom/datastore/tests/file_app.sjs index b0a1c3fbfc0..40ed36d7cad 100644 --- a/dom/datastore/tests/file_app.sjs +++ b/dom/datastore/tests/file_app.sjs @@ -2,9 +2,16 @@ 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 @@ -35,3 +42,12 @@ function readTemplate(path) { 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; +} diff --git a/dom/datastore/tests/file_app.template.webapp b/dom/datastore/tests/file_app.template.webapp index 20d01fcd495..46d34aea0c1 100644 --- a/dom/datastore/tests/file_app.template.webapp +++ b/dom/datastore/tests/file_app.template.webapp @@ -1,9 +1,13 @@ { "name": "Really Rapid Release (hosted)", "description": "Updated even faster than Firefox, 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" } } diff --git a/dom/datastore/tests/file_app2.template.webapp b/dom/datastore/tests/file_app2.template.webapp new file mode 100644 index 00000000000..42956c8b493 --- /dev/null +++ b/dom/datastore/tests/file_app2.template.webapp @@ -0,0 +1,10 @@ +{ + "name": "Really Rapid Release (hosted) - app 2", + "description": "Updated even faster than Firefox, just to annoy slashdotters.", + "launch_path": "/tests/dom/datastore/tests/file_readonly.html", + "icons": { "128": "default_icon" }, + "datastores-access" : { + "foo" : { "readonly": false, "description" : "This store is called foo" }, + "bar" : { "readonly": true, "description" : "This store is called bar" } + } +} diff --git a/dom/datastore/tests/file_app2.template.webapp^headers^ b/dom/datastore/tests/file_app2.template.webapp^headers^ new file mode 100644 index 00000000000..a2367b11c78 --- /dev/null +++ b/dom/datastore/tests/file_app2.template.webapp^headers^ @@ -0,0 +1 @@ +Content-Type: application/x-web-app-manifest+json diff --git a/dom/datastore/tests/file_app_install.html b/dom/datastore/tests/file_app_install.html new file mode 100644 index 00000000000..b48c87c403a --- /dev/null +++ b/dom/datastore/tests/file_app_install.html @@ -0,0 +1,40 @@ + + +
+ ++ ++ + + diff --git a/dom/datastore/tests/test_app_install.html b/dom/datastore/tests/test_app_install.html index b8f914af204..a58c2c30c5c 100644 --- a/dom/datastore/tests/test_app_install.html +++ b/dom/datastore/tests/test_app_install.html @@ -13,7 +13,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( @@ -23,7 +23,8 @@ function() { gGenerator.next() }); function continueTest() { - gGenerator.next(); + try { gGenerator.next(); } + catch(e) { dump("Got exception: " + e + "\n"); } } function cbError() { @@ -40,6 +41,8 @@ }, cbError); yield undefined; + SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true); + SpecialPowers.autoConfirmAppInstall(continueTest); yield undefined; @@ -53,42 +56,43 @@ 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(); } diff --git a/dom/datastore/tests/test_basic.html b/dom/datastore/tests/test_basic.html index 94c5038a946..0f26387db65 100644 --- a/dom/datastore/tests/test_basic.html +++ b/dom/datastore/tests/test_basic.html @@ -11,9 +11,8 @@