From d4a12c91c07e28c0319b8b05a6bc8e264d3bfa2b Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Wed, 11 Sep 2013 15:47:56 +0200 Subject: [PATCH] Bug 871445 patch 5 - DataStore: onchange, r=ehsan, r=bent --- b2g/chrome/content/shell.js | 1 + dom/datastore/DataStore.jsm | 114 ++++++++++- dom/datastore/DataStoreChangeNotifier.jsm | 110 +++++++++++ dom/datastore/DataStoreService.js | 6 +- dom/datastore/moz.build | 1 + dom/datastore/tests/Makefile.in | 4 +- dom/datastore/tests/file_app.sjs | 7 +- dom/datastore/tests/file_app.template.webapp | 4 - dom/datastore/tests/file_app2.template.webapp | 2 +- .../tests/file_app2.template.webapp^headers^ | 1 - dom/datastore/tests/file_changes.html | 133 +++++++++++++ dom/datastore/tests/file_changes2.html | 46 +++++ dom/datastore/tests/test_app_install.html | 2 + dom/datastore/tests/test_basic.html | 4 +- dom/datastore/tests/test_changes.html | 177 ++++++++++++++++++ dom/datastore/tests/test_readonly.html | 8 +- dom/datastore/tests/test_revision.html | 1 + 17 files changed, 597 insertions(+), 24 deletions(-) create mode 100644 dom/datastore/DataStoreChangeNotifier.jsm delete mode 100644 dom/datastore/tests/file_app2.template.webapp^headers^ create mode 100644 dom/datastore/tests/file_changes.html create mode 100644 dom/datastore/tests/file_changes2.html create mode 100644 dom/datastore/tests/test_changes.html diff --git a/b2g/chrome/content/shell.js b/b2g/chrome/content/shell.js index 7b8b44507a7..f7080aefbe5 100644 --- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -6,6 +6,7 @@ Cu.import('resource://gre/modules/ContactService.jsm'); Cu.import('resource://gre/modules/SettingsChangeNotifier.jsm'); +Cu.import('resource://gre/modules/DataStoreChangeNotifier.jsm'); Cu.import('resource://gre/modules/AlarmService.jsm'); Cu.import('resource://gre/modules/ActivitiesService.jsm'); Cu.import('resource://gre/modules/PermissionPromptHelper.jsm'); diff --git a/dom/datastore/DataStore.jsm b/dom/datastore/DataStore.jsm index 714dc20c22f..a1b8b00298c 100644 --- a/dom/datastore/DataStore.jsm +++ b/dom/datastore/DataStore.jsm @@ -6,7 +6,7 @@ 'use strict' -var EXPORTED_SYMBOLS = ["DataStore", "DataStoreAccess"]; +this.EXPORTED_SYMBOLS = ["DataStore", "DataStoreAccess"]; function debug(s) { // dump('DEBUG DataStore: ' + s + '\n'); @@ -22,6 +22,11 @@ const REVISION_VOID = "void"; Cu.import("resource://gre/modules/DataStoreDB.jsm"); Cu.import("resource://gre/modules/ObjectWrapper.jsm"); Cu.import('resource://gre/modules/Services.jsm'); +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); + +XPCOMUtils.defineLazyServiceGetter(this, "cpmm", + "@mozilla.org/childprocessmessagemanager;1", + "nsIMessageSender"); /* Helper function */ function createDOMError(aWindow, aEvent) { @@ -161,13 +166,14 @@ DataStore.prototype = { this.db.addRevision(aRevisionStore, aId, aType, function(aRevisionId) { self.revisionId = aRevisionId; + self.sendNotification(aId, aType, aRevisionId); aSuccessCb(); } ); }, - retrieveRevisionId: function(aSuccessCb) { - if (this.revisionId != null) { + retrieveRevisionId: function(aSuccessCb, aForced) { + if (this.revisionId != null && !aForced) { aSuccessCb(); return; } @@ -183,7 +189,12 @@ DataStore.prototype = { let cursor = aEvent.target.result; if (!cursor) { // If the revision doesn't exist, let's create the first one. - self.addRevision(aRevisionStore, 0, REVISION_VOID, aSuccessCb); + self.addRevision(aRevisionStore, 0, REVISION_VOID, + function(aRevisionId) { + self.revisionId = aRevisionId; + aSuccessCb(); + } + ); return; } @@ -207,6 +218,7 @@ DataStore.prototype = { exposeObject: function(aWindow, aReadOnly) { let self = this; let object = { + callbacks: [], // Public interface : @@ -398,8 +410,34 @@ DataStore.prototype = { }); }, + set onchange(aCallback) { + debug("Set OnChange"); + this.onchangeCb = aCallback; + }, + + get onchange() { + debug("Get OnChange"); + return this.onchangeCb; + }, + + addEventListener: function(aName, aCallback) { + debug("addEventListener:" + aName); + if (aName != 'change') { + return; + } + + this.callbacks.push(aCallback); + }, + + removeEventListener: function(aName, aCallback) { + debug('removeEventListener'); + let pos = this.callbacks.indexOf(aCallback); + if (pos != -1) { + this.callbacks.splice(pos, 1); + } + }, + /* TODO: - attribute EventHandler onchange; getAll(), getLength() */ @@ -413,15 +451,79 @@ DataStore.prototype = { remove: 'r', clear: 'r', revisionId: 'r', - getChanges: 'r' + getChanges: 'r', + onchange: 'rw', + addEventListener: 'r', + removeEventListener: 'r' + }, + + receiveMessage: function(aMessage) { + debug("receiveMessage"); + + if (aMessage.name != "DataStore:Changed:Return:OK") { + debug("Wrong message: " + aMessage.name); + return; + } + + self.retrieveRevisionId( + function() { + if (object.onchangeCb || object.callbacks.length) { + let wrappedData = ObjectWrapper.wrap(aMessage.data, aWindow); + + // This array is used to avoid that a callback adds/removes + // another eventListener. + var cbs = []; + if (object.onchangeCb) { + cbs.push(object.onchangeCb); + } + + for (let i = 0; i < object.callbacks.length; ++i) { + cbs.push(object.callbacks[i]); + } + + for (let i = 0; i < cbs.length; ++i) { + try { + cbs[i](wrappedData); + } catch(e) {} + } + } + }, + // Forcing the reading of the revisionId + true + ); } }; + Services.obs.addObserver(function(aSubject, aTopic, aData) { + let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data; + if (wId == object.innerWindowID) { + cpmm.removeMessageListener("DataStore:Changed:Return:OK", object); + } + }, "inner-window-destroyed", false); + + let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + object.innerWindowID = util.currentInnerWindowID; + + cpmm.addMessageListener("DataStore:Changed:Return:OK", object); + cpmm.sendAsyncMessage("DataStore:RegisterForMessages", + { store: this.name, owner: this.owner }); + return object; }, delete: function() { this.db.delete(); + }, + + sendNotification: function(aId, aOperation, aRevisionId) { + debug("SendNotification"); + if (aOperation != REVISION_VOID) { + cpmm.sendAsyncMessage("DataStore:Changed", + { store: this.name, owner: this.owner, + message: { revisionId: aRevisionId, id: aId, + operation: aOperation } } ); + } } }; diff --git a/dom/datastore/DataStoreChangeNotifier.jsm b/dom/datastore/DataStoreChangeNotifier.jsm new file mode 100644 index 00000000000..a20de55cf0d --- /dev/null +++ b/dom/datastore/DataStoreChangeNotifier.jsm @@ -0,0 +1,110 @@ +/* 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" + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +this.EXPORTED_SYMBOLS = ["DataStoreChangeNotifier"]; + +function debug(s) { + //dump('DEBUG DataStoreChangeNotifier: ' + s + '\n'); +} + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +const kFromDataStoreChangeNotifier = "fromDataStoreChangeNotifier"; + +XPCOMUtils.defineLazyServiceGetter(this, "ppmm", + "@mozilla.org/parentprocessmessagemanager;1", + "nsIMessageBroadcaster"); + +this.DataStoreChangeNotifier = { + children: [], + messages: [ "DataStore:Changed", "DataStore:RegisterForMessages", + "child-process-shutdown" ], + + init: function() { + debug("init"); + + this.messages.forEach((function(msgName) { + ppmm.addMessageListener(msgName, this); + }).bind(this)); + + Services.obs.addObserver(this, 'xpcom-shutdown', false); + }, + + observe: function(aSubject, aTopic, aData) { + debug("observe"); + + switch (aTopic) { + case 'xpcom-shutdown': + this.messages.forEach((function(msgName) { + ppmm.removeMessageListener(msgName, this); + }).bind(this)); + + Services.obs.removeObserver(this, 'xpcom-shutdown'); + ppmm = null; + break; + + default: + debug("Wrong observer topic: " + aTopic); + break; + } + }, + + broadcastMessage: function broadcastMessage(aMsgName, aData) { + debug("Broadast"); + this.children.forEach(function(obj) { + if (obj.store == aData.store && obj.owner == aData.owner) { + obj.mm.sendAsyncMessage(aMsgName, aData.message); + } + }); + }, + + receiveMessage: function(aMessage) { + debug("receiveMessage"); + + switch (aMessage.name) { + case "DataStore:Changed": + this.broadcastMessage("DataStore:Changed:Return:OK", aMessage.data); + break; + + case "DataStore:RegisterForMessages": + debug("Register!"); + + for (let i = 0; i < this.children.length; ++i) { + if (this.children[i].mm == aMessage.target && + this.children[i].store == aMessage.data.store && + this.children[i].owner == aMessage.data.owner) { + return; + } + } + + this.children.push({ mm: aMessage.target, + store: aMessage.data.store, + owner: aMessage.data.owner }); + break; + + case "child-process-shutdown": + debug("Unregister"); + + for (let i = 0; i < this.children.length;) { + if (this.children[i].mm == aMessage.target) { + debug("Unregister index: " + i); + this.children.splice(i, 1); + } else { + ++i; + } + } + break; + + default: + debug("Wrong message: " + aMessage.name); + } + } +} + +DataStoreChangeNotifier.init(); diff --git a/dom/datastore/DataStoreService.js b/dom/datastore/DataStoreService.js index fad46e2d407..e9cadb45f09 100644 --- a/dom/datastore/DataStoreService.js +++ b/dom/datastore/DataStoreService.js @@ -137,9 +137,9 @@ DataStoreService.prototype = { resolver.resolve(results); } }, - function() { - resolver.reject(); - } + // if the revision is already known, we don't need to retrieve it + // again. + false ); } }); diff --git a/dom/datastore/moz.build b/dom/datastore/moz.build index 8957fd0dc8b..09efc251f4d 100644 --- a/dom/datastore/moz.build +++ b/dom/datastore/moz.build @@ -21,5 +21,6 @@ EXTRA_COMPONENTS += [ EXTRA_JS_MODULES += [ 'DataStore.jsm', + 'DataStoreChangeNotifier.jsm', 'DataStoreDB.jsm', ] diff --git a/dom/datastore/tests/Makefile.in b/dom/datastore/tests/Makefile.in index b13c1ba3a91..99b939895aa 100644 --- a/dom/datastore/tests/Makefile.in +++ b/dom/datastore/tests/Makefile.in @@ -20,10 +20,12 @@ MOCHITEST_FILES = \ file_basic.html \ test_revision.html \ file_revision.html \ + test_changes.html \ + file_changes.html \ + file_changes2.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 40ed36d7cad..f4d5b3569a7 100644 --- a/dom/datastore/tests/file_app.sjs +++ b/dom/datastore/tests/file_app.sjs @@ -1,5 +1,4 @@ var gBasePath = "tests/dom/datastore/tests/"; -var gAppTemplatePath = "tests/dom/datastore/tests/file_app.template.webapp"; function handleRequest(request, response) { var query = getQuery(request); @@ -9,7 +8,11 @@ function handleRequest(request, response) { testToken = query.testToken; } - var template = gBasePath + 'file_app.template.webapp'; + var template = 'file_app.template.webapp'; + if ('template' in query) { + template = query.template; + } + var template = gBasePath + template; response.setHeader("Content-Type", "application/x-web-app-manifest+json", false); response.write(readTemplate(template).replace(/TESTTOKEN/g, testToken)); } diff --git a/dom/datastore/tests/file_app.template.webapp b/dom/datastore/tests/file_app.template.webapp index 46d34aea0c1..69ee31cfce7 100644 --- a/dom/datastore/tests/file_app.template.webapp +++ b/dom/datastore/tests/file_app.template.webapp @@ -6,9 +6,5 @@ "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 index 42956c8b493..25752e208c3 100644 --- a/dom/datastore/tests/file_app2.template.webapp +++ b/dom/datastore/tests/file_app2.template.webapp @@ -1,7 +1,7 @@ { "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", + "launch_path": "/tests/dom/datastore/tests/TESTTOKEN", "icons": { "128": "default_icon" }, "datastores-access" : { "foo" : { "readonly": false, "description" : "This store is called foo" }, diff --git a/dom/datastore/tests/file_app2.template.webapp^headers^ b/dom/datastore/tests/file_app2.template.webapp^headers^ deleted file mode 100644 index a2367b11c78..00000000000 --- a/dom/datastore/tests/file_app2.template.webapp^headers^ +++ /dev/null @@ -1 +0,0 @@ -Content-Type: application/x-web-app-manifest+json diff --git a/dom/datastore/tests/file_changes.html b/dom/datastore/tests/file_changes.html new file mode 100644 index 00000000000..745f1bcc659 --- /dev/null +++ b/dom/datastore/tests/file_changes.html @@ -0,0 +1,133 @@ + + + + + Test for DataStore - basic operation on a readonly db + + +

+ +
+  
+
+ + diff --git a/dom/datastore/tests/file_changes2.html b/dom/datastore/tests/file_changes2.html new file mode 100644 index 00000000000..4da5841b7bf --- /dev/null +++ b/dom/datastore/tests/file_changes2.html @@ -0,0 +1,46 @@ + + + + + Test for DataStore - basic operation on a readonly db + + +

+ +
+  
+
+ + diff --git a/dom/datastore/tests/test_app_install.html b/dom/datastore/tests/test_app_install.html index a58c2c30c5c..0011a0d2208 100644 --- a/dom/datastore/tests/test_app_install.html +++ b/dom/datastore/tests/test_app_install.html @@ -10,6 +10,8 @@
diff --git a/dom/datastore/tests/test_changes.html b/dom/datastore/tests/test_changes.html new file mode 100644 index 00000000000..95b27250e59 --- /dev/null +++ b/dom/datastore/tests/test_changes.html @@ -0,0 +1,177 @@ + + + + + Test for DataStore - basic operation on a readonly db + + + + +

+ +
+  
+
+ + + + diff --git a/dom/datastore/tests/test_readonly.html b/dom/datastore/tests/test_readonly.html index a8309cdc404..ee7acffcf8f 100644 --- a/dom/datastore/tests/test_readonly.html +++ b/dom/datastore/tests/test_readonly.html @@ -9,9 +9,8 @@
diff --git a/dom/datastore/tests/test_revision.html b/dom/datastore/tests/test_revision.html index c5b6216cf37..3968c860591 100644 --- a/dom/datastore/tests/test_revision.html +++ b/dom/datastore/tests/test_revision.html @@ -122,6 +122,7 @@ SimpleTest.finish(); } + SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm"); SimpleTest.waitForExplicitFinish(); runTest();