Bumping gaia.json for 2 gaia revision(s) a=gaia-bump
[gecko.git] / dom / datastore / DataStoreChangeNotifier.jsm
blobb0dbf56707d47e61cbfb51a3bbe0d567dfc21553
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict"
7 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
9 this.EXPORTED_SYMBOLS = ["DataStoreChangeNotifier"];
11 function debug(s) {
12   //dump('DEBUG DataStoreChangeNotifier: ' + s + '\n');
15 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
16 Cu.import("resource://gre/modules/Services.jsm");
18 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
19                                    "@mozilla.org/parentprocessmessagemanager;1",
20                                    "nsIMessageBroadcaster");
22 XPCOMUtils.defineLazyServiceGetter(this, "dataStoreService",
23                                    "@mozilla.org/datastore-service;1",
24                                    "nsIDataStoreService");
26 XPCOMUtils.defineLazyServiceGetter(this, "systemMessenger",
27                                    "@mozilla.org/system-message-internal;1",
28                                    "nsISystemMessagesInternal");
30 var kSysMsgOnChangeShortTimeoutSec =
31     Services.prefs.getIntPref("dom.datastore.sysMsgOnChangeShortTimeoutSec");
32 var kSysMsgOnChangeLongTimeoutSec =
33     Services.prefs.getIntPref("dom.datastore.sysMsgOnChangeLongTimeoutSec");
35 this.DataStoreChangeNotifier = {
36   children: [],
37   messages: [ "DataStore:Changed", "DataStore:RegisterForMessages",
38               "DataStore:UnregisterForMessages",
39               "child-process-shutdown" ],
41   // These hashes are used for storing the mapping between the datastore
42   // identifiers (name | owner manifest URL) and their correspondent timers. 
43   // The object literal is defined as below:
44   //
45   // {
46   //   "datastore name 1|owner manifest URL 1": timer1,
47   //   "datastore name 2|owner manifest URL 2": timer2,
48   //   ...
49   // }
50   sysMsgOnChangeShortTimers: {},
51   sysMsgOnChangeLongTimers: {},
53   init: function() {
54     debug("init");
56     this.messages.forEach((function(msgName) {
57       ppmm.addMessageListener(msgName, this);
58     }).bind(this));
60     Services.obs.addObserver(this, 'xpcom-shutdown', false);
61   },
63   observe: function(aSubject, aTopic, aData) {
64     debug("observe");
66     switch (aTopic) {
67       case 'xpcom-shutdown':
68         this.messages.forEach((function(msgName) {
69           ppmm.removeMessageListener(msgName, this);
70         }).bind(this));
72         Services.obs.removeObserver(this, 'xpcom-shutdown');
73         ppmm = null;
74         break;
76       default:
77         debug("Wrong observer topic: " + aTopic);
78         break;
79     }
80   },
82   broadcastMessage: function broadcastMessage(aData) {
83     debug("broadcast");
85     this.children.forEach(function(obj) {
86       if (obj.store == aData.store && obj.owner == aData.owner) {
87         obj.mm.sendAsyncMessage("DataStore:Changed:Return:OK", aData);
88       }
89     });
90   },
92   broadcastSystemMessage: function(aStore, aOwner) {
93     debug("broadcastSystemMessage");
95     // Clear relevant timers.
96     var storeKey = aStore + "|" + aOwner;
97     var shortTimer = this.sysMsgOnChangeShortTimers[storeKey];
98     if (shortTimer) {
99       shortTimer.cancel();
100       delete this.sysMsgOnChangeShortTimers[storeKey];
101     }
102     var longTimer = this.sysMsgOnChangeLongTimers[storeKey];
103     if (longTimer) {
104       longTimer.cancel();
105       delete this.sysMsgOnChangeLongTimers[storeKey];
106     }
108     // Get all the manifest URLs of the apps which can access the datastore.
109     var manifestURLs = dataStoreService.getAppManifestURLsForDataStore(aStore);
110     var enumerate = manifestURLs.enumerate();
111     while (enumerate.hasMoreElements()) {
112       var manifestURL = enumerate.getNext().QueryInterface(Ci.nsISupportsString);
113       debug("Notify app " + manifestURL + " of datastore updates");
114       // Send the system message 'datastore-update-{store name}' to all the
115       // pages for these apps. With the manifest URL of the owner in the message
116       // payload, it notifies the consumer a sync operation should be performed.
117       systemMessenger.sendMessage("datastore-update-" + aStore,
118                                   { owner: aOwner },
119                                   null,
120                                   Services.io.newURI(manifestURL, null, null));
121     }
122   },
124   // Use the following logic to broadcast system messages in a moderate pattern.
125   // 1. When an entry is changed, start a short timer and a long timer.
126   // 2. If an entry is changed while the short timer is running, reset it.
127   //    Do not reset the long timer.
128   // 3. Once either fires, broadcast the system message and cancel both timers.
129   setSystemMessageTimeout: function(aStore, aOwner) {
130     debug("setSystemMessageTimeout");
132     var storeKey = aStore + "|" + aOwner;
134     // Reset the short timer.
135     var shortTimer = this.sysMsgOnChangeShortTimers[storeKey];
136     if (!shortTimer) {
137       shortTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
138       this.sysMsgOnChangeShortTimers[storeKey] = shortTimer;
139     } else {
140       shortTimer.cancel();
141     }
142     shortTimer.initWithCallback({ notify: this.broadcastSystemMessage.bind(this, aStore, aOwner) },
143                                 kSysMsgOnChangeShortTimeoutSec * 1000,
144                                 Ci.nsITimer.TYPE_ONE_SHOT);
146     // Set the long timer if necessary.
147     if (!this.sysMsgOnChangeLongTimers[storeKey]) {
148       var longTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
149       this.sysMsgOnChangeLongTimers[storeKey] = longTimer;
150       longTimer.initWithCallback({ notify: this.broadcastSystemMessage.bind(this, aStore, aOwner) },
151                                  kSysMsgOnChangeLongTimeoutSec * 1000,
152                                  Ci.nsITimer.TYPE_ONE_SHOT);
153     }
154   },
156   receiveMessage: function(aMessage) {
157     debug("receiveMessage ");
159     // No check has to be done when the message is 'child-process-shutdown'.
160     if (aMessage.name != "child-process-shutdown") {
161       if (!("principal" in aMessage)) {
162         return;
163       }
164       let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
165                      .getService(Ci.nsIScriptSecurityManager);
166       let uri = Services.io.newURI(aMessage.principal.origin, null, null);
167       let principal = secMan.getAppCodebasePrincipal(uri,
168         aMessage.principal.appId, aMessage.principal.isInBrowserElement);
169       if (!principal || !dataStoreService.checkPermission(principal)) {
170         return;
171       }
172     }
174     switch (aMessage.name) {
175       case "DataStore:Changed":
176         this.broadcastMessage(aMessage.data);
177         if (Services.prefs.getBoolPref("dom.sysmsg.enabled")) {
178           this.setSystemMessageTimeout(aMessage.data.store, aMessage.data.owner);
179         }
180         break;
182       case "DataStore:RegisterForMessages":
183         debug("Register!");
185         for (let i = 0; i < this.children.length; ++i) {
186           if (this.children[i].mm == aMessage.target &&
187               this.children[i].store == aMessage.data.store &&
188               this.children[i].owner == aMessage.data.owner) {
189             debug("Register on existing index: " + i);
190             this.children[i].windows.push(aMessage.data.innerWindowID);
191             return;
192           }
193         }
195         this.children.push({ mm: aMessage.target,
196                              store: aMessage.data.store,
197                              owner: aMessage.data.owner,
198                              windows: [ aMessage.data.innerWindowID ]});
199         break;
201       case "DataStore:UnregisterForMessages":
202         debug("Unregister");
204         for (let i = 0; i < this.children.length; ++i) {
205           if (this.children[i].mm == aMessage.target) {
206             debug("Unregister index: " + i);
208             var pos = this.children[i].windows.indexOf(aMessage.data.innerWindowID);
209             if (pos != -1) {
210               this.children[i].windows.splice(pos, 1);
211             }
213             if (this.children[i].windows.length) {
214               continue;
215             }
217             debug("Unregister delete index: " + i);
218             this.children.splice(i, 1);
219             --i;
220           }
221         }
222         break;
224       case "child-process-shutdown":
225         debug("Child process shutdown");
227         for (let i = 0; i < this.children.length; ++i) {
228           if (this.children[i].mm == aMessage.target) {
229             debug("Unregister index: " + i);
230             this.children.splice(i, 1);
231             --i;
232           }
233         }
234         break;
236       default:
237         debug("Wrong message: " + aMessage.name);
238     }
239   }
242 DataStoreChangeNotifier.init();