Bumping manifests a=b2g-bump
[gecko.git] / dom / requestsync / RequestSyncService.jsm
blobc13a6dd8ef50eacb6d8b296b744aa478c294a2ee
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 function debug(s) {
10   //dump('DEBUG RequestSyncService: ' + s + '\n');
13 const RSYNCDB_VERSION = 1;
14 const RSYNCDB_NAME = "requestSync";
15 const RSYNC_MIN_INTERVAL = 100;
17 const RSYNC_OPERATION_TIMEOUT = 120000 // 2 minutes
19 const RSYNC_STATE_ENABLED = "enabled";
20 const RSYNC_STATE_DISABLED = "disabled";
21 const RSYNC_STATE_WIFIONLY = "wifiOnly";
23 Cu.import('resource://gre/modules/IndexedDBHelper.jsm');
24 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
25 Cu.import("resource://gre/modules/Services.jsm");
26 Cu.importGlobalProperties(["indexedDB"]);
29 XPCOMUtils.defineLazyServiceGetter(this, "appsService",
30                                    "@mozilla.org/AppsService;1",
31                                    "nsIAppsService");
33 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
34                                    "@mozilla.org/childprocessmessagemanager;1",
35                                    "nsISyncMessageSender");
37 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
38                                    "@mozilla.org/parentprocessmessagemanager;1",
39                                    "nsIMessageBroadcaster");
41 XPCOMUtils.defineLazyServiceGetter(this, "systemMessenger",
42                                    "@mozilla.org/system-message-internal;1",
43                                    "nsISystemMessagesInternal");
45 XPCOMUtils.defineLazyServiceGetter(this, "secMan",
46                                    "@mozilla.org/scriptsecuritymanager;1",
47                                    "nsIScriptSecurityManager");
49 XPCOMUtils.defineLazyModuleGetter(this, "AlarmService",
50                                   "resource://gre/modules/AlarmService.jsm");
52 this.RequestSyncService = {
53   __proto__: IndexedDBHelper.prototype,
55   children: [],
57   _messages: [ "RequestSync:Register",
58                "RequestSync:Unregister",
59                "RequestSync:Registrations",
60                "RequestSync:Registration",
61                "RequestSyncManager:Registrations",
62                "RequestSyncManager:SetPolicy",
63                "RequestSyncManager:RunTask" ],
65   _pendingOperation: false,
66   _pendingMessages: [],
68   _registrations: {},
70   _wifi: false,
72   _activeTask: null,
73   _queuedTasks: [],
75   _timers: {},
76   _pendingRequests: {},
78   // Initialization of the RequestSyncService.
79   init: function() {
80     debug("init");
82     this._messages.forEach((function(msgName) {
83       ppmm.addMessageListener(msgName, this);
84     }).bind(this));
86     Services.obs.addObserver(this, 'xpcom-shutdown', false);
87     Services.obs.addObserver(this, 'webapps-clear-data', false);
88     Services.obs.addObserver(this, 'wifi-state-changed', false);
90     this.initDBHelper("requestSync", RSYNCDB_VERSION, [RSYNCDB_NAME]);
92     // Loading all the data from the database into the _registrations map.
93     // Any incoming message will be stored and processed when the async
94     // operation is completed.
96     let self = this;
97     this.dbTxn("readonly", function(aStore) {
98       aStore.openCursor().onsuccess = function(event) {
99         let cursor = event.target.result;
100         if (cursor) {
101           self.addRegistration(cursor.value);
102           cursor.continue();
103         }
104       }
105     },
106     function() {
107       debug("initialization done");
108     },
109     function() {
110       dump("ERROR!! RequestSyncService - Failed to retrieve data from the database.\n");
111     });
112   },
114   // Shutdown the RequestSyncService.
115   shutdown: function() {
116     debug("shutdown");
118     this._messages.forEach((function(msgName) {
119       ppmm.removeMessageListener(msgName, this);
120     }).bind(this));
122     Services.obs.removeObserver(this, 'xpcom-shutdown');
123     Services.obs.removeObserver(this, 'webapps-clear-data');
124     Services.obs.removeObserver(this, 'wifi-state-changed');
126     this.close();
128     // Removing all the registrations will delete the pending timers.
129     let self = this;
130     this.forEachRegistration(function(aObj) {
131       let key = self.principalToKey(aObj.principal);
132       self.removeRegistrationInternal(aObj.data.task, key);
133     });
134   },
136   observe: function(aSubject, aTopic, aData) {
137     debug("observe");
139     switch (aTopic) {
140       case 'xpcom-shutdown':
141         this.shutdown();
142         break;
144       case 'webapps-clear-data':
145         this.clearData(aSubject);
146         break;
148       case 'wifi-state-changed':
149         this.wifiStateChanged(aSubject == 'enabled');
150         break;
152       default:
153         debug("Wrong observer topic: " + aTopic);
154         break;
155     }
156   },
158   // When an app is uninstalled, we have to clean all its tasks.
159   clearData: function(aData) {
160     debug('clearData');
162     if (!aData) {
163       return;
164     }
166     let params =
167       aData.QueryInterface(Ci.mozIApplicationClearPrivateDataParams);
168     if (!params) {
169       return;
170     }
172     // At this point we don't have the origin, so we cannot create the full
173     // key. Using the partial one is enough to detect the uninstalled app.
174     let partialKey = params.appId + '|' + params.browserOnly + '|';
175     let dbKeys = [];
177     for (let key  in this._registrations) {
178       if (key.indexOf(partialKey) != 0) {
179         continue;
180       }
182       for (let task in this._registrations[key]) {
183         dbKeys = this._registrations[key][task].dbKey;
184         this.removeRegistrationInternal(task, key);
185       }
186     }
188     if (dbKeys.length == 0) {
189       return;
190     }
192     // Remove the tasks from the database.
193     this.dbTxn('readwrite', function(aStore) {
194       for (let i = 0; i < dbKeys.length; ++i) {
195         aStore.delete(dbKeys[i]);
196       }
197     },
198     function() {
199       debug("ClearData completed");
200     }, function() {
201       debug("ClearData failed");
202     });
203   },
205   // Creation of the schema for the database.
206   upgradeSchema: function(aTransaction, aDb, aOldVersion, aNewVersion) {
207     debug('updateSchema');
208     aDb.createObjectStore(RSYNCDB_NAME, { autoIncrement: true });
209   },
211   // This method generates the key for the indexedDB object storage.
212   principalToKey: function(aPrincipal) {
213     return aPrincipal.appId + '|' +
214            aPrincipal.isInBrowserElement + '|' +
215            aPrincipal.origin;
216   },
218   // Add a task to the _registrations map and create the timer if it's needed.
219   addRegistration: function(aObj) {
220     debug('addRegistration');
222     let key = this.principalToKey(aObj.principal);
223     if (!(key in this._registrations)) {
224       this._registrations[key] = {};
225     }
227     this.scheduleTimer(aObj);
228     this._registrations[key][aObj.data.task] = aObj;
229   },
231   // Remove a task from the _registrations map and delete the timer if it's
232   // needed. It also checks if the principal is correct before doing the real
233   // operation.
234   removeRegistration: function(aTaskName, aKey, aPrincipal) {
235     debug('removeRegistration');
237     if (!(aKey in this._registrations) ||
238         !(aTaskName in this._registrations[aKey])) {
239       return false;
240     }
242     // Additional security check.
243     if (!aPrincipal.equals(this._registrations[aKey][aTaskName].principal)) {
244       return false;
245     }
247     this.removeRegistrationInternal(aTaskName, aKey);
248     return true;
249   },
251   removeRegistrationInternal: function(aTaskName, aKey) {
252     debug('removeRegistrationInternal');
254     let obj = this._registrations[aKey][aTaskName];
256     this.removeTimer(obj);
258     // It can be that this task has been already schedulated.
259     this.removeTaskFromQueue(obj);
261     // It can be that this object is already in scheduled, or in the queue of a
262     // iDB transacation. In order to avoid rescheduling it, we must disable it.
263     obj.active = false;
265     delete this._registrations[aKey][aTaskName];
267     // Lets remove the key in case there are not tasks registered.
268     for (let key in this._registrations[aKey]) {
269       return;
270     }
271     delete this._registrations[aKey];
272   },
274   removeTaskFromQueue: function(aObj) {
275     let pos = this._queuedTasks.indexOf(aObj);
276     if (pos != -1) {
277       this._queuedTasks.splice(pos, 1);
278     }
279   },
281   // The communication from the exposed objects and the service is done using
282   // messages. This function receives and processes them.
283   receiveMessage: function(aMessage) {
284     debug("receiveMessage");
286     // We cannot process this request now.
287     if (this._pendingOperation) {
288       this._pendingMessages.push(aMessage);
289       return;
290     }
292     // The principal is used to validate the message.
293     if (!aMessage.principal) {
294       return;
295     }
297     let uri = Services.io.newURI(aMessage.principal.origin, null, null);
299     let principal;
300     try {
301       principal = secMan.getAppCodebasePrincipal(uri,
302         aMessage.principal.appId, aMessage.principal.isInBrowserElement);
303     } catch(e) {
304       return;
305     }
307     if (!principal) {
308       return;
309     }
311     switch (aMessage.name) {
312       case "RequestSync:Register":
313         this.register(aMessage.target, aMessage.data, principal);
314         break;
316       case "RequestSync:Unregister":
317         this.unregister(aMessage.target, aMessage.data, principal);
318         break;
320       case "RequestSync:Registrations":
321         this.registrations(aMessage.target, aMessage.data, principal);
322         break;
324       case "RequestSync:Registration":
325         this.registration(aMessage.target, aMessage.data, principal);
326         break;
328       case "RequestSyncManager:Registrations":
329         this.managerRegistrations(aMessage.target, aMessage.data, principal);
330         break;
332       case "RequestSyncManager:SetPolicy":
333         this.managerSetPolicy(aMessage.target, aMessage.data, principal);
334         break;
336       case "RequestSyncManager:RunTask":
337         this.managerRunTask(aMessage.target, aMessage.data, principal);
338         break;
340       default:
341         debug("Wrong message: " + aMessage.name);
342         break;
343     }
344   },
346   // Basic validation.
347   validateRegistrationParams: function(aParams) {
348     if (aParams === null) {
349       return false;
350     }
352     // We must have a page.
353     if (!("wakeUpPage" in aParams) ||
354         aParams.wakeUpPage.length == 0) {
355       return false;
356     }
358     let minInterval = RSYNC_MIN_INTERVAL;
359     try {
360       minInterval = Services.prefs.getIntPref("dom.requestSync.minInterval");
361     } catch(e) {}
363     if (!("minInterval" in aParams) ||
364         aParams.minInterval < minInterval) {
365       return false;
366     }
368     return true;
369   },
371   // Registration of a new task.
372   register: function(aTarget, aData, aPrincipal) {
373     debug("register");
375     if (!this.validateRegistrationParams(aData.params)) {
376       aTarget.sendAsyncMessage("RequestSync:Register:Return",
377                                { requestID: aData.requestID,
378                                  error: "ParamsError" } );
379       return;
380     }
382     let key = this.principalToKey(aPrincipal);
383     if (key in this._registrations &&
384         aData.task in this._registrations[key]) {
385       // if this task already exists we overwrite it.
386       this.removeRegistrationInternal(aData.task, key);
387     }
389     // This creates a RequestTaskFull object.
390     aData.params.task = aData.task;
391     aData.params.lastSync = 0;
392     aData.params.principal = aPrincipal;
394     aData.params.state = RSYNC_STATE_ENABLED;
395     if (aData.params.wifiOnly) {
396       aData.params.state = RSYNC_STATE_WIFIONLY;
397     }
399     aData.params.overwrittenMinInterval = 0;
401     let dbKey = aData.task + "|" +
402                 aPrincipal.appId + '|' +
403                 aPrincipal.isInBrowserElement + '|' +
404                 aPrincipal.origin;
406     let data = { principal: aPrincipal,
407                  dbKey: dbKey,
408                  data: aData.params,
409                  active: true };
411     let self = this;
412     this.dbTxn('readwrite', function(aStore) {
413       aStore.put(data, data.dbKey);
414     },
415     function() {
416       self.addRegistration(data);
417       aTarget.sendAsyncMessage("RequestSync:Register:Return",
418                                { requestID: aData.requestID });
419     },
420     function() {
421       aTarget.sendAsyncMessage("RequestSync:Register:Return",
422                                { requestID: aData.requestID,
423                                  error: "IndexDBError" } );
424     });
425   },
427   // Unregister a task.
428   unregister: function(aTarget, aData, aPrincipal) {
429     debug("unregister");
431     let key = this.principalToKey(aPrincipal);
432     if (!(key in this._registrations) ||
433         !(aData.task in this._registrations[key])) {
434       aTarget.sendAsyncMessage("RequestSync:Unregister:Return",
435                                { requestID: aData.requestID,
436                                  error: "UnknownTaskError" });
437       return;
438     }
440     let dbKey = this._registrations[key][aData.task].dbKey;
441     this.removeRegistration(aData.task, key, aPrincipal);
443     let self = this;
444     this.dbTxn('readwrite', function(aStore) {
445       aStore.delete(dbKey);
446     },
447     function() {
448       aTarget.sendAsyncMessage("RequestSync:Unregister:Return",
449                                { requestID: aData.requestID });
450     },
451     function() {
452       aTarget.sendAsyncMessage("RequestSync:Unregister:Return",
453                                { requestID: aData.requestID,
454                                  error: "IndexDBError" } );
455     });
456   },
458   // Get the list of registered tasks for this principal.
459   registrations: function(aTarget, aData, aPrincipal) {
460     debug("registrations");
462     let results = [];
463     let key = this.principalToKey(aPrincipal);
464     if (key in this._registrations) {
465       for (let i in this._registrations[key]) {
466         results.push(this.createPartialTaskObject(
467           this._registrations[key][i].data));
468       }
469     }
471     aTarget.sendAsyncMessage("RequestSync:Registrations:Return",
472                              { requestID: aData.requestID,
473                                results: results });
474   },
476   // Get a particular registered task for this principal.
477   registration: function(aTarget, aData, aPrincipal) {
478     debug("registration");
480     let results = null;
481     let key = this.principalToKey(aPrincipal);
482     if (key in this._registrations &&
483         aData.task in this._registrations[key]) {
484       results = this.createPartialTaskObject(
485         this._registrations[key][aData.task].data);
486     }
488     aTarget.sendAsyncMessage("RequestSync:Registration:Return",
489                              { requestID: aData.requestID,
490                                results: results });
491   },
493   // Get the list of the registered tasks.
494   managerRegistrations: function(aTarget, aData, aPrincipal) {
495     debug("managerRegistrations");
497     let results = [];
498     let self = this;
499     this.forEachRegistration(function(aObj) {
500       results.push(self.createFullTaskObject(aObj.data));
501     });
503     aTarget.sendAsyncMessage("RequestSyncManager:Registrations:Return",
504                              { requestID: aData.requestID,
505                                results: results });
506   },
508   // Set a policy to a task.
509   managerSetPolicy: function(aTarget, aData, aPrincipal) {
510     debug("managerSetPolicy");
512     let toSave = null;
513     let self = this;
514     this.forEachRegistration(function(aObj) {
515       if (aObj.data.task != aData.task) {
516         return;
517       }
519       if (aObj.principal.isInBrowserElement != aData.isInBrowserElement ||
520           aObj.principal.origin != aData.origin) {
521         return;
522       }
524       let app = appsService.getAppByLocalId(aObj.principal.appId);
525       if (app && app.manifestURL != aData.manifestURL ||
526           (!app && aData.manifestURL != "")) {
527         return;
528       }
530       if ("overwrittenMinInterval" in aData) {
531         aObj.data.overwrittenMinInterval = aData.overwrittenMinInterval;
532       }
534       aObj.data.state = aData.state;
536       if (toSave) {
537         dump("ERROR!! RequestSyncService - SetPolicy matches more than 1 task.\n");
538         return;
539       }
541       toSave = aObj;
542     });
544     if (!toSave) {
545       aTarget.sendAsyncMessage("RequestSyncManager:SetPolicy:Return",
546                                { requestID: aData.requestID, error: "UnknownTaskError" });
547       return;
548     }
550     this.updateObjectInDB(toSave, function() {
551       self.scheduleTimer(toSave);
552       aTarget.sendAsyncMessage("RequestSyncManager:SetPolicy:Return",
553                                { requestID: aData.requestID });
554     });
555   },
557   // Run a task now.
558   managerRunTask: function(aTarget, aData, aPrincipal) {
559     debug("runTask");
561     let task = null;
562     this.forEachRegistration(function(aObj) {
563       if (aObj.data.task != aData.task) {
564         return;
565       }
567       if (aObj.principal.isInBrowserElement != aData.isInBrowserElement ||
568           aObj.principal.origin != aData.origin) {
569         return;
570       }
572       let app = appsService.getAppByLocalId(aObj.principal.appId);
573       if (app && app.manifestURL != aData.manifestURL ||
574           (!app && aData.manifestURL != "")) {
575         return;
576       }
578       if (task) {
579         dump("ERROR!! RequestSyncService - RunTask matches more than 1 task.\n");
580         return;
581       }
583       task = aObj;
584     });
586     if (!task) {
587       aTarget.sendAsyncMessage("RequestSyncManager:RunTask:Return",
588                                { requestID: aData.requestID, error: "UnknownTaskError" });
589       return;
590     }
592     // Storing the requestID into the task for the callback.
593     this.storePendingRequest(task, aTarget, aData.requestID);
594     this.timeout(task);
595   },
597   // We cannot expose the full internal object to content but just a subset.
598   // This method creates this subset.
599   createPartialTaskObject: function(aObj) {
600     return { task: aObj.task,
601              lastSync: aObj.lastSync,
602              oneShot: aObj.oneShot,
603              minInterval: aObj.minInterval,
604              wakeUpPage: aObj.wakeUpPage,
605              wifiOnly: aObj.wifiOnly,
606              data: aObj.data };
607   },
609   createFullTaskObject: function(aObj) {
610     let obj = this.createPartialTaskObject(aObj);
612     obj.app = { manifestURL: '',
613                 origin: aObj.principal.origin,
614                 isInBrowserElement: aObj.principal.isInBrowserElement };
616     let app = appsService.getAppByLocalId(aObj.principal.appId);
617     if (app) {
618       obj.app.manifestURL = app.manifestURL;
619     }
621     obj.state = aObj.state;
622     obj.overwrittenMinInterval = aObj.overwrittenMinInterval;
623     return obj;
624   },
626   // Creation of the timer for a particular task object.
627   scheduleTimer: function(aObj) {
628     debug("scheduleTimer");
630     this.removeTimer(aObj);
632     // A  registration can be already inactive if it was 1 shot.
633     if (!aObj.active) {
634       return;
635     }
637     if (aObj.data.state == RSYNC_STATE_DISABLED) {
638       return;
639     }
641     // WifiOnly check.
642     if (aObj.data.state == RSYNC_STATE_WIFIONLY && !this._wifi) {
643       return;
644     }
646     this.createTimer(aObj);
647   },
649   timeout: function(aObj) {
650     debug("timeout");
652     if (this._activeTask) {
653       debug("queueing tasks");
654       // We have an active task, let's queue this as next task.
655       if (this._queuedTasks.indexOf(aObj) == -1) {
656         this._queuedTasks.push(aObj);
657       }
658       return;
659     }
661     let app = appsService.getAppByLocalId(aObj.principal.appId);
662     if (!app) {
663       dump("ERROR!! RequestSyncService - Failed to retrieve app data from a principal.\n");
664       aObj.active = false;
665       this.updateObjectInDB(aObj);
666       return;
667     }
669     let manifestURL = Services.io.newURI(app.manifestURL, null, null);
670     let pageURL = Services.io.newURI(aObj.data.wakeUpPage, null, aObj.principal.URI);
672     // Maybe need to be rescheduled?
673     if (this.hasPendingMessages('request-sync', manifestURL, pageURL)) {
674       this.scheduleTimer(aObj);
675       return;
676     }
678     this.removeTimer(aObj);
679     this._activeTask = aObj;
681     if (!manifestURL || !pageURL) {
682       dump("ERROR!! RequestSyncService - Failed to create URI for the page or the manifest\n");
683       aObj.active = false;
684       this.updateObjectInDB(aObj);
685       return;
686     }
688     // We don't want to run more than 1 task at the same time. We do this using
689     // the promise created by sendMessage(). But if the task takes more than
690     // RSYNC_OPERATION_TIMEOUT millisecs, we have to ignore the promise and
691     // continue processing other tasks.
693     let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
695     let done = false;
696     let self = this;
697     function taskCompleted() {
698       debug("promise or timeout for task calls taskCompleted");
700       if (!done) {
701         done = true;
702         self.operationCompleted();
703       }
705       timer.cancel();
706       timer = null;
707     }
709     let timeout = RSYNC_OPERATION_TIMEOUT;
710     try {
711       let tmp = Services.prefs.getIntPref("dom.requestSync.maxTaskTimeout");
712       timeout = tmp;
713     } catch(e) {}
715     timer.initWithCallback(function() {
716       debug("Task is taking too much, let's ignore the promise.");
717       taskCompleted();
718     }, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
720     // Sending the message.
721     debug("Sending message.");
722     let promise =
723       systemMessenger.sendMessage('request-sync',
724                                   this.createPartialTaskObject(aObj.data),
725                                   pageURL, manifestURL);
727     promise.then(function() {
728       debug("promise resolved");
729       taskCompleted();
730     }, function() {
731       debug("promise rejected");
732       taskCompleted();
733     });
734   },
736   operationCompleted: function() {
737     debug("operationCompleted");
739     if (!this._activeTask) {
740       dump("ERROR!! RequestSyncService - OperationCompleted called without an active task\n");
741       return;
742     }
744     // One shot? Then this is not active.
745     this._activeTask.active = !this._activeTask.data.oneShot;
746     this._activeTask.data.lastSync = new Date();
748     let pendingRequests = this.stealPendingRequests(this._activeTask);
749     for (let i = 0; i < pendingRequests.length; ++i) {
750       pendingRequests[i]
751           .target.sendAsyncMessage("RequestSyncManager:RunTask:Return",
752                                    { requestID: pendingRequests[i].requestID });
753     }
755     let self = this;
756     this.updateObjectInDB(this._activeTask, function() {
757       // SchedulerTimer creates a timer and a nsITimer cannot be cloned. This
758       // is the reason why this operation has to be done after storing the task
759       // into IDB.
760       if (!self._activeTask.data.oneShot) {
761         self.scheduleTimer(self._activeTask);
762       }
764       self.processNextTask();
765     });
766   },
768   processNextTask: function() {
769     debug("processNextTask");
771     this._activeTask = null;
773     if (this._queuedTasks.length == 0) {
774       return;
775     }
777     let task = this._queuedTasks.shift();
778     this.timeout(task);
779   },
781   hasPendingMessages: function(aMessageName, aManifestURL, aPageURL) {
782     let hasPendingMessages =
783       cpmm.sendSyncMessage("SystemMessageManager:HasPendingMessages",
784                            { type: aMessageName,
785                              pageURL: aPageURL.spec,
786                              manifestURL: aManifestURL.spec })[0];
788     debug("Pending messages: " + hasPendingMessages);
789     return hasPendingMessages;
790   },
792   // Update the object into the database.
793   updateObjectInDB: function(aObj, aCb) {
794     debug("updateObjectInDB");
796     this.dbTxn('readwrite', function(aStore) {
797       aStore.put(aObj, aObj.dbKey);
798     },
799     function() {
800       if (aCb) {
801         aCb();
802       }
803       debug("UpdateObjectInDB completed");
804     }, function() {
805       debug("UpdateObjectInDB failed");
806     });
807   },
809   pendingOperationStarted: function() {
810     debug('pendingOperationStarted');
811     this._pendingOperation = true;
812   },
814   pendingOperationDone: function() {
815     debug('pendingOperationDone');
817     this._pendingOperation = false;
819     // managing the pending messages now that the initialization is completed.
820     while (this._pendingMessages.length && !this._pendingOperation) {
821       this.receiveMessage(this._pendingMessages.shift());
822     }
823   },
825   // This method creates a transaction and runs callbacks. Plus it manages the
826   // pending operations system.
827   dbTxn: function(aType, aCb, aSuccessCb, aErrorCb) {
828     debug('dbTxn');
830     this.pendingOperationStarted();
832     let self = this;
833     this.newTxn(aType, RSYNCDB_NAME, function(aTxn, aStore) {
834       aCb(aStore);
835     },
836     function() {
837       self.pendingOperationDone();
838       aSuccessCb();
839     },
840     function() {
841       self.pendingOperationDone();
842       aErrorCb();
843     });
844   },
846   forEachRegistration: function(aCb) {
847     // This method is used also to remove registations from the map, so we have
848     // to make a new list and let _registations free to be used.
849     let list = [];
850     for (let key in this._registrations) {
851       for (let task in this._registrations[key]) {
852         list.push(this._registrations[key][task]);
853       }
854     }
856     for (let i = 0; i < list.length; ++i) {
857       aCb(list[i]);
858     }
859   },
861   wifiStateChanged: function(aEnabled) {
862     debug("onWifiStateChanged");
863     this._wifi = aEnabled;
865     if (!this._wifi) {
866       // Disable all the wifiOnly tasks.
867       let self = this;
868       this.forEachRegistration(function(aObj) {
869         if (aObj.data.state == RSYNC_STATE_WIFIONLY && self.hasTimer(aObj)) {
870           self.removeTimer(aObj);
872           // It can be that this task has been already schedulated.
873           self.removeTaskFromQueue(aObj);
874         }
875       });
876       return;
877     }
879     // Enable all the tasks.
880     let self = this;
881     this.forEachRegistration(function(aObj) {
882       if (aObj.active && !self.hasTimer(aObj)) {
883         if (!aObj.data.wifiOnly) {
884           dump("ERROR - Found a disabled task that is not wifiOnly.");
885         }
887         self.scheduleTimer(aObj);
888       }
889     });
890   },
892   createTimer: function(aObj) {
893     let interval = aObj.data.minInterval;
894     if (aObj.data.overwrittenMinInterval > 0) {
895       interval = aObj.data.overwrittenMinInterval;
896     }
898     AlarmService.add(
899       { date: new Date(Date.now() + interval * 1000),
900         ignoreTimezone: false },
901       () => this.timeout(aObj),
902       aTimerId => this._timers[aObj.dbKey] = aTimerId);
903   },
905   hasTimer: function(aObj) {
906     return (aObj.dbKey in this._timers);
907   },
909   removeTimer: function(aObj) {
910     if (aObj.dbKey in this._timers) {
911       AlarmService.remove(this._timers[aObj.dbKey]);
912       delete this._timers[aObj.dbKey];
913     }
914   },
916   storePendingRequest: function(aObj, aTarget, aRequestID) {
917     if (!(aObj.dbKey in this._pendingRequests)) {
918       this._pendingRequests[aObj.dbKey] = [];
919     }
921     this._pendingRequests[aObj.dbKey].push({ target: aTarget,
922                                              requestID: aRequestID });
923   },
925   stealPendingRequests: function(aObj) {
926     if (!(aObj.dbKey in this._pendingRequests)) {
927       return [];
928     }
930     let requests = this._pendingRequests[aObj.dbKey];
931     delete this._pendingRequests[aObj.dbKey];
932     return requests;
933   }
936 RequestSyncService.init();
938 this.EXPORTED_SYMBOLS = [""];