diff --git a/dom/requestsync/RequestSyncApp.jsm b/dom/requestsync/RequestSyncApp.jsm new file mode 100644 index 000000000000..69ad349d5e53 --- /dev/null +++ b/dom/requestsync/RequestSyncApp.jsm @@ -0,0 +1,48 @@ +/* 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'; + +this.EXPORTED_SYMBOLS = ['RequestSyncApp']; + +function debug(s) { + //dump('DEBUG RequestSyncApp: ' + s + '\n'); +} + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); + +this.RequestSyncApp = function(aData) { + debug('created'); + + let keys = [ 'origin', 'manifestURL', 'isInBrowserElement' ]; + for (let i = 0; i < keys.length; ++i) { + if (!(keys[i] in aData)) { + dump("ERROR - RequestSyncApp must receive a full app object: " + keys[i] + " missing."); + throw "ERROR!"; + } + + this["_" + keys[i]] = aData[keys[i]]; + } +} + +this.RequestSyncApp.prototype = { + classDescription: 'RequestSyncApp XPCOM Component', + classID: Components.ID('{5a0b64db-a2be-4f08-a6c5-8bf2e3ae0c57}'), + contractID: '@mozilla.org/dom/request-sync-manager;1', + QueryInterface: XPCOMUtils.generateQI([]), + + get origin() { + return this._origin; + }, + + get manifestURL() { + return this._manifestURL; + }, + + get isInBrowserElement() { + return this._isInBrowserElement; + } +}; diff --git a/dom/requestsync/RequestSyncManager.js b/dom/requestsync/RequestSyncManager.js index 8cb5f5ea060e..0e9e53b7ac43 100644 --- a/dom/requestsync/RequestSyncManager.js +++ b/dom/requestsync/RequestSyncManager.js @@ -12,6 +12,8 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; Cu.import("resource://gre/modules/DOMRequestHelper.jsm"); Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import('resource://gre/modules/RequestSyncApp.jsm'); +Cu.import('resource://gre/modules/RequestSyncTask.jsm'); XPCOMUtils.defineLazyServiceGetter(this, "cpmm", "@mozilla.org/childprocessmessagemanager;1", @@ -31,7 +33,8 @@ RequestSyncManager.prototype = { Ci.nsIObserver, Ci.nsIDOMGlobalPropertyInitializer]), - _messages: [ "RequestSyncManager:Registrations:Return" ], + _messages: [ "RequestSyncManager:Registrations:Return", + "RequestSyncManager:SetPolicy:Return" ], init: function(aWindow) { debug("init"); @@ -55,6 +58,42 @@ RequestSyncManager.prototype = { return this.sendMessage("RequestSyncManager:Registrations", {}); }, + setPolicy: function(aTask, aOrigin, aManifestURL, aIsInBrowserElement, + aState, aOverwrittenMinInterval) { + debug('setPolicy'); + + return this.sendMessage("RequestSyncManager:SetPolicy", + { task: aTask, + origin: aOrigin, + manifestURL: aManifestURL, + isInBrowserElement: aIsInBrowserElement, + state: aState, + overwrittenMinInterval: aOverwrittenMinInterval }); + }, + + registrationsResult: function(aData) { + debug("registrationsResult"); + + let results = new this._window.Array(); + for (let i = 0; i < aData.length; ++i) { + if (!("app" in aData[i])) { + dump("ERROR - Serialization error in RequestSyncManager.\n"); + continue; + } + + let app = new RequestSyncApp(aData[i].app); + let exposedApp = + this._window.RequestSyncApp._create(this._window, app); + + let task = new RequestSyncTask(this, this._window, exposedApp, aData[i]); + let exposedTask = + this._window.RequestSyncTask._create(this._window, task); + + results.push(exposedTask); + } + return results; + }, + receiveMessage: function(aMessage) { debug('receiveMessage'); @@ -68,6 +107,11 @@ RequestSyncManager.prototype = { return; } + if (aMessage.name == 'RequestSyncManager:Registrations:Return') { + req.resolve(this.registrationsResult(aMessage.data.results)); + return; + } + if ('results' in aMessage.data) { req.resolve(Cu.cloneInto(aMessage.data.results, this._window)); return; diff --git a/dom/requestsync/RequestSyncService.jsm b/dom/requestsync/RequestSyncService.jsm index dbd8d3422d1c..d1a705db4c4b 100644 --- a/dom/requestsync/RequestSyncService.jsm +++ b/dom/requestsync/RequestSyncService.jsm @@ -16,6 +16,10 @@ const RSYNC_MIN_INTERVAL = 100; const RSYNC_OPERATION_TIMEOUT = 120000 // 2 minutes +const RSYNC_STATE_ENABLED = "enabled"; +const RSYNC_STATE_DISABLED = "disabled"; +const RSYNC_STATE_WIFIONLY = "wifiOnly"; + Cu.import('resource://gre/modules/IndexedDBHelper.jsm'); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); @@ -47,9 +51,12 @@ this.RequestSyncService = { children: [], - _messages: [ "RequestSync:Register", "RequestSync:Unregister", - "RequestSync:Registrations", "RequestSync:Registration", - "RequestSyncManager:Registrations" ], + _messages: [ "RequestSync:Register", + "RequestSync:Unregister", + "RequestSync:Registrations", + "RequestSync:Registration", + "RequestSyncManager:Registrations", + "RequestSyncManager:SetPolicy" ], _pendingOperation: false, _pendingMessages: [], @@ -316,6 +323,10 @@ this.RequestSyncService = { this.managerRegistrations(aMessage.target, aMessage.data, principal); break; + case "RequestSyncManager:SetPolicy": + this.managerSetPolicy(aMessage.target, aMessage.data, principal); + break; + default: debug("Wrong message: " + aMessage.name); break; @@ -370,6 +381,13 @@ this.RequestSyncService = { aData.params.lastSync = 0; aData.params.principal = aPrincipal; + aData.params.state = RSYNC_STATE_ENABLED; + if (aData.params.wifiOnly) { + aData.params.state = RSYNC_STATE_WIFIONLY; + } + + aData.params.overwrittenMinInterval = 0; + let dbKey = aData.task + "|" + aPrincipal.appId + '|' + aPrincipal.isInBrowserElement + '|' + @@ -478,6 +496,51 @@ this.RequestSyncService = { results: results }); }, + // Set a policy to a task. + managerSetPolicy: function(aTarget, aData, aPrincipal) { + debug("managerSetPolicy"); + + let toSave = null; + let self = this; + this.forEachRegistration(function(aObj) { + if (aObj.principal.isInBrowserElement != aData.isInBrowserElement || + aObj.principal.origin != aData.origin) { + return; + } + + let app = appsService.getAppByLocalId(aObj.principal.appId); + if (app && app.manifestURL != aData.manifestURL || + (!app && aData.manifestURL != "")) { + return; + } + + if ("overwrittenMinInterval" in aData) { + aObj.data.overwrittenMinInterval = aData.overwrittenMinInterval; + } + + aObj.data.state = aData.state; + + if (toSave) { + dump("ERROR!! RequestSyncService - SetPolicy matches more than 1 task.\n"); + return; + } + + toSave = aObj; + }); + + if (!toSave) { + aTarget.sendAsyncMessage("RequestSyncManager:SetPolicy:Return", + { requestID: aData.requestID, error: "UnknownTaskError" }); + return; + } + + this.updateObjectInDB(toSave, function() { + self.scheduleTimer(toSave); + aTarget.sendAsyncMessage("RequestSyncManager:SetPolicy:Return", + { requestID: aData.requestID }); + }); + }, + // We cannot expose the full internal object to content but just a subset. // This method creates this subset. createPartialTaskObject: function(aObj) { @@ -502,6 +565,8 @@ this.RequestSyncService = { obj.app.manifestURL = app.manifestURL; } + obj.state = aObj.state; + obj.overwrittenMinInterval = aObj.overwrittenMinInterval; return obj; }, @@ -509,21 +574,35 @@ this.RequestSyncService = { scheduleTimer: function(aObj) { debug("scheduleTimer"); + if (aObj.timer) { + aObj.timer.cancel(); + aObj.timer = null; + } + // A registration can be already inactive if it was 1 shot. if (!aObj.active) { return; } + if (aObj.data.state == RSYNC_STATE_DISABLED) { + return; + } + // WifiOnly check. - if (aObj.data.wifiOnly && !this._wifi) { + if (aObj.data.state == RSYNC_STATE_WIFIONLY && !this._wifi) { return; } aObj.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + let interval = aObj.data.minInterval; + if (aObj.data.overwrittenMinInterval > 0) { + interval = aObj.data.overwrittenMinInterval; + } + let self = this; aObj.timer.initWithCallback(function() { self.timeout(aObj); }, - aObj.data.minInterval * 1000, + interval * 1000, Ci.nsITimer.TYPE_ONE_SHOT); }, @@ -739,7 +818,7 @@ this.RequestSyncService = { // Disable all the wifiOnly tasks. let self = this; this.forEachRegistration(function(aObj) { - if (aObj.data.wifiOnly && aObj.timer) { + if (aObj.data.state == RSYNC_STATE_WIFIONLY && aObj.timer) { aObj.timer.cancel(); aObj.timer = null; diff --git a/dom/requestsync/RequestSyncTask.jsm b/dom/requestsync/RequestSyncTask.jsm new file mode 100644 index 000000000000..4360e3de3884 --- /dev/null +++ b/dom/requestsync/RequestSyncTask.jsm @@ -0,0 +1,101 @@ +/* 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'; + +this.EXPORTED_SYMBOLS = ['RequestSyncTask']; + +function debug(s) { + //dump('DEBUG RequestSyncTask: ' + s + '\n'); +} + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); + +this.RequestSyncTask = function(aManager, aWindow, aApp, aData) { + debug('created'); + + this._manager = aManager; + this._window = aWindow; + this._app = aApp; + + let keys = [ 'task', 'lastSync', 'oneShot', 'minInterval', 'wakeUpPage', + 'wifiOnly', 'data', 'state', 'overwrittenMinInterval' ]; + for (let i = 0; i < keys.length; ++i) { + if (!(keys[i] in aData)) { + dump("ERROR - RequestSyncTask must receive a fully app object: " + keys[i] + " missing."); + throw "ERROR!"; + } + + this["_" + keys[i]] = aData[keys[i]]; + } +} + +this.RequestSyncTask.prototype = { + classDescription: 'RequestSyncTask XPCOM Component', + classID: Components.ID('{a1e1c9c6-ce42-49d4-b8b4-fbd686d8fdd9}'), + contractID: '@mozilla.org/dom/request-sync-manager;1', + QueryInterface: XPCOMUtils.generateQI([]), + + get app() { + return this._app; + }, + + get state() { + return this._state; + }, + + get overwrittenMinInterval() { + return this._overwrittenMinInterval; + }, + + get task() { + return this._task; + }, + + get lastSync() { + return this._lastSync; + }, + + get wakeUpPage() { + return this._wakeUpPage; + }, + + get oneShot() { + return this._oneShot; + }, + + get minInterval() { + return this._minInterval; + }, + + get wifiOnly() { + return this._wifiOnly; + }, + + get data() { + return this._data; + }, + + setPolicy: function(aState, aOverwrittenMinInterval) { + debug("setPolicy"); + let self = this; + + return new this._window.Promise(function(aResolve, aReject) { + let p = self._manager.setPolicy(self._task, self._app.origin, + self._app.manifestURL, + self._app.isInBrowserElement, + aState, + aOverwrittenMinInterval); + + // Set the new value only when the promise is resolved. + p.then(function() { + self._state = aState; + self._overwrittenMinInterval = aOverwrittenMinInterval; + aResolve(); + }, aReject); + }); + } +}; diff --git a/dom/requestsync/moz.build b/dom/requestsync/moz.build index 481da0c886b0..3dbdf4b3168e 100644 --- a/dom/requestsync/moz.build +++ b/dom/requestsync/moz.build @@ -17,7 +17,9 @@ EXTRA_COMPONENTS += [ ] EXTRA_JS_MODULES += [ + 'RequestSyncApp.jsm', 'RequestSyncService.jsm', + 'RequestSyncTask.jsm', ] SOURCES += [ diff --git a/dom/requestsync/tests/common_basic.js b/dom/requestsync/tests/common_basic.js index 3301c4135b67..bf0f4fd0aa84 100644 --- a/dom/requestsync/tests/common_basic.js +++ b/dom/requestsync/tests/common_basic.js @@ -151,7 +151,7 @@ function test_managerRegistrationsEmpty() { genericError); } -function test_managerRegistrations() { +function test_managerRegistrations(state, overwrittenMinInterval) { navigator.syncManager.registrations().then( function(results) { is(results.length, 1, "navigator.sync.registrations() should not return an empty array."); @@ -166,7 +166,22 @@ function test_managerRegistrations() { ok("manifestURL" in results[0].app, "navigator.sync.registrations()[0].app.manifestURL is correct"); is(results[0].app.origin, 'http://mochi.test:8888', "navigator.sync.registrations()[0].app.origin is correct"); is(results[0].app.isInBrowserElement, false, "navigator.sync.registrations()[0].app.isInBrowserElement is correct"); + is(results[0].state, state, "navigator.sync.registrations()[0].state is correct"); + is(results[0].overwrittenMinInterval, overwrittenMinInterval, "navigator.sync.registrations()[0].overwrittenMinInterval is correct"); + ok("setPolicy" in results[0], "navigator.sync.registrations()[0].setPolicy is correct"); runTests(); }, genericError); } + +function test_managerSetPolicy(state, overwrittenMinInterval) { + navigator.syncManager.registrations().then( + function(results) { + results[0].setPolicy(state, overwrittenMinInterval).then( + function() { + ok(state, results[0].state, "State matches"); + ok(overwrittenMinInterval, results[0].overwrittenMinInterval, "OverwrittenMinInterval matches"); + runTests(); + }, genericError); + }).catch(genericError); +} diff --git a/dom/requestsync/tests/test_basic.html b/dom/requestsync/tests/test_basic.html index ddab2a297a00..e95974cad6ff 100644 --- a/dom/requestsync/tests/test_basic.html +++ b/dom/requestsync/tests/test_basic.html @@ -38,12 +38,18 @@ // overwrite the same registration. test_register, - test_managerRegistrations, + function() { test_managerRegistrations('wifiOnly', 0); }, test_registrations, test_registrationEmpty, test_registration, + function() { test_managerSetPolicy('disabled', 123); }, + function() { test_managerRegistrations('disabled', 123); }, + + function() { test_managerSetPolicy('enabled', 42); }, + function() { test_managerRegistrations('enabled', 42); }, + test_unregister, test_unregisterDuplicate, diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 48a2e4a108ec..456cd4cd0de9 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -1209,6 +1209,10 @@ var interfaceNamesInGlobalScope = "SVGZoomEvent", // IMPORTANT: Do not change this list without review from a DOM peer! {name: "RequestSyncManager", b2g: true, pref: "dom.requestSync.enabled", permission: ["requestsync-manager"] }, +// IMPORTANT: Do not change this list without review from a DOM peer! + {name: "RequestSyncApp", b2g: true, pref: "dom.requestSync.enabled", permission: ["requestsync-manager"] }, +// IMPORTANT: Do not change this list without review from a DOM peer! + {name: "RequestSyncTask", b2g: true, pref: "dom.requestSync.enabled", permission: ["requestsync-manager"] }, // IMPORTANT: Do not change this list without review from a DOM peer! {name: "Telephony", b2g: true, pref: "dom.telephony.enabled"}, // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/webidl/RequestSyncManager.webidl b/dom/webidl/RequestSyncManager.webidl index 701e80b10f60..632081499580 100644 --- a/dom/webidl/RequestSyncManager.webidl +++ b/dom/webidl/RequestSyncManager.webidl @@ -4,16 +4,43 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ -// Rappresentation of the app in the RequestTaskFull. -dictionary RequestTaskApp { - USVString origin; - USVString manifestURL; - boolean isInBrowserElement; +[AvailableIn=CertifiedApps, + Pref="dom.requestSync.enabled", + CheckPermissions="requestsync-manager", + JSImplementation="@mozilla.org/dom/request-sync-task-app;1"] +interface RequestSyncApp { + readonly attribute USVString origin; + readonly attribute USVString manifestURL; + readonly attribute boolean isInBrowserElement; }; +enum RequestSyncTaskPolicyState { "enabled", "disabled", "wifiOnly" }; + // Like a normal task, but with info about the app. -dictionary RequestTaskFull : RequestTask { - RequestTaskApp app; +[AvailableIn=CertifiedApps, + Pref="dom.requestSync.enabled", + CheckPermissions="requestsync-manager", + JSImplementation="@mozilla.org/dom/request-sync-task-manager;1"] +interface RequestSyncTask { + // This object describes the app that is owning the task. + readonly attribute RequestSyncApp app; + + // Using setPolicy it's possible to owerwrite the state and the minInterval. + readonly attribute RequestSyncTaskPolicyState state; + readonly attribute long overwrittenMinInterval; + + // These attributes are taken from the configuration of the task: + + readonly attribute USVString task; + readonly attribute DOMTimeStamp lastSync; + readonly attribute USVString wakeUpPage; + readonly attribute boolean oneShot; + readonly attribute long minInterval; + readonly attribute boolean wifiOnly; + readonly attribute any data; + + Promise setPolicy(RequestSyncTaskPolicyState aState, + optional long ovewrittenMinInterval); }; [NavigatorProperty="syncManager", @@ -23,5 +50,5 @@ dictionary RequestTaskFull : RequestTask { JSImplementation="@mozilla.org/dom/request-sync-manager;1"] // This interface will be used only by the B2G SystemApp interface RequestSyncManager { - Promise> registrations(); + Promise> registrations(); }; diff --git a/dom/webidl/RequestSyncScheduler.webidl b/dom/webidl/RequestSyncScheduler.webidl index 3df115bff465..1bd33ac12fdc 100644 --- a/dom/webidl/RequestSyncScheduler.webidl +++ b/dom/webidl/RequestSyncScheduler.webidl @@ -15,7 +15,7 @@ dictionary RequestTaskParams { // This is the dictionary you can have back from registration{s}(). -dictionary RequestTask : RequestTaskParams { +dictionary RequestTaskFull : RequestTaskParams { USVString task = ""; // Last synchonization date.. maybe it's useful to know. @@ -33,6 +33,6 @@ interface RequestSyncScheduler { Promise unregister(USVString task); // Useful methods to get registrations - Promise> registrations(); - Promise registration(USVString task); + Promise> registrations(); + Promise registration(USVString task); };