From 6e0cfd237571d5d444e66394a40cb6240b5bd9fa Mon Sep 17 00:00:00 2001 From: jrburke Date: Tue, 27 Jan 2015 21:45:02 -0800 Subject: [PATCH] Bug 1098289 - Migrate Email to use navigator.sync (RequestSync) API (Phase1) --- Makefile | 17 +- js/cronsync.js | 35 +-- js/main-frame-setup.js | 2 +- js/worker-support/cronsync-main.js | 202 ++++++++---------- .../chrome/content/loggest-chrome-runner.js | 5 +- test/manifest.webapp | 5 +- test/unit/resources/th_main.js | 8 +- test/unit/resources/window_shims.js | 7 - test/unit/test_account_updates.js | 18 +- .../unit/test_cronsync_wait_for_completion.js | 2 +- 10 files changed, 148 insertions(+), 153 deletions(-) diff --git a/Makefile b/Makefile index f61e7d2b..53d96a54 100644 --- a/Makefile +++ b/Makefile @@ -74,13 +74,22 @@ download-b2g: b2g gaia-symlink: echo "You need to create a symlink 'gaia-symlink' pointing at the gaia dir" +SYS=$(shell uname -s) B2GBD := b2g-builddir-symlink ifeq ($(wildcard b2g-bindir-symlink),) - B2GBIND := $(B2GBD)/dist/bin - RUNB2G := $(B2GBIND)/b2g + B2GBIND := $(B2GBD)/dist/bin + RUNB2G := $(B2GBIND)/b2g else - B2GBIND := b2g-bindir-symlink - RUNB2G := $(B2GBIND)/b2g-bin + # OS X has trouble launching the executable via the symlink, gets a "Couldn't + # load XPCOM" error, so resolve the symlink first. Do not generically use + # readlink on all platforms, since it behaves slightly differently, and only + # the OS X platform seems to exhibit this problem. + ifeq ($(SYS),Darwin) + B2GBIND=`readlink b2g-bindir-symlink` + else + B2GBIND := b2g-bindir-symlink + endif + RUNB2G := $(B2GBIND)/b2g-bin endif ARBPLD=arbpl-dir-symlink diff --git a/js/cronsync.js b/js/cronsync.js index 31ba53c7..8c638d4a 100644 --- a/js/cronsync.js +++ b/js/cronsync.js @@ -10,7 +10,7 @@ * server for general responsiveness and so the user can use the device * offline. * - * We use mozAlarm to schedule ourselves to wake up when our next + * We use navigator.sync to schedule ourselves to wake up when our next * synchronization should occur. * * All synchronization occurs in parallel because we want the interval that we @@ -166,9 +166,9 @@ function CronSync(universe, _logParent) { this.sendCronSync = $router.registerSimple('cronsync', function(data) { var args = data.args; switch (data.cmd) { - case 'alarm': - debug('received an alarm via a message handler'); - this.onAlarm.apply(this, args); + case 'requestSync': + debug('received a requestSync via a message handler'); + this.onRequestSync.apply(this, args); break; case 'syncEnsured': debug('received an syncEnsured via a message handler'); @@ -194,9 +194,9 @@ CronSync.prototype = { * Makes sure there is a sync timer set up for all accounts. */ ensureSync: function() { - // Only execute ensureSync if it is not already in progress. - // Otherwise, due to async timing of mozAlarm setting, could - // end up with two alarms for the same ID. + // Only execute ensureSync if it is not already in progress. Otherwise, due + // to async timing of navigator.sync setting, could end up with two sync + // tasks for the same ID. if (!this._completedEnsureSync) return; @@ -352,8 +352,8 @@ CronSync.prototype = { }.bind(this)); }, - onAlarm: function(accountIds) { - this._LOG.alarmFired(accountIds); + onRequestSync: function(accountIds) { + this._LOG.requestSyncFired(accountIds); if (!accountIds) return; @@ -367,7 +367,7 @@ CronSync.prototype = { this._universe.__notifyStartedCronSync(accountIds); // Make sure the acount IDs are still valid. This is to protect agains - // an account deletion that did not clean up any alarms correctly. + // an account deletion that did not clean up any sync tasks correctly. accountIds.forEach(function(id) { accounts.some(function(account) { if (account.id === id) { @@ -381,10 +381,10 @@ CronSync.prototype = { // Flip switch to say account syncing is in progress. this._syncAccountsDone = false; - // Make sure next alarm is set up. In the case of a cold start + // Make sure next sync is set up. In the case of a cold start // background sync, this is a bit redundant in that the startup // of the mailuniverse would trigger this work. However, if the - // app is already running, need to be sure next alarm is set up, + // app is already running, need to be sure next sync is set up, // so ensure the next sync is set up here. Do it here instead of // after a sync in case an error in sync would prevent the next // sync from getting scheduled. @@ -444,8 +444,9 @@ CronSync.prototype = { /** * Checks for "sync all done", which means the ensureSync call completed, and - * new alarms for next sync are set, and the account syncs have finished. If - * those two things are true, then notify the universe that the sync is done. + * new sync tasks for next sync are set, and the account syncs have finished. + * If those two things are true, then notify the universe that the sync is + * done. */ _checkSyncDone: function() { if (!this._completedEnsureSync || !this._syncAccountsDone) @@ -462,9 +463,9 @@ CronSync.prototype = { /** * Called from cronsync-main once ensureSync as set - * any alarms needed. Need to wait for it before + * any sync tasks needed. Need to wait for it before * signaling sync is done because otherwise the app - * could get closed down before the alarm additions + * could get closed down before the sync task additions * succeed. */ onSyncEnsured: function() { @@ -483,7 +484,7 @@ var LOGFAB = exports.LOGFAB = $log.register($module, { CronSync: { type: $log.DAEMON, events: { - alarmFired: { accountIds: false }, + requestSyncFired: { accountIds: false }, killSlices: { count: false }, syncSkipped: { id: true }, }, diff --git a/js/main-frame-setup.js b/js/main-frame-setup.js index 7b16b1e7..2191703a 100644 --- a/js/main-frame-setup.js +++ b/js/main-frame-setup.js @@ -5,7 +5,7 @@ * Main: Spawns worker * Worker: Loads core JS * Worker: 'hello' => main - * Main: 'hello' => worker with online status and mozAlarms status + * Main: 'hello' => worker with online status and navigator.sync status * Worker: Creates MailUniverse * Worker 'mailbridge'.'hello' => main * Main: Creates MailAPI, sends event to UI diff --git a/js/worker-support/cronsync-main.js b/js/worker-support/cronsync-main.js index 9d4aa90e..ae9bbdac 100644 --- a/js/worker-support/cronsync-main.js +++ b/js/worker-support/cronsync-main.js @@ -9,15 +9,6 @@ define(function(require) { console.log('cronsync-main: ' + str); } - function makeData(accountIds, interval, date) { - return { - type: 'sync', - accountIds: accountIds, - interval: interval, - timestamp: date.getTime() - }; - } - // Creates a string key from an array of string IDs. Uses a space // separator since that cannot show up in an ID. function makeAccountKey(accountIds) { @@ -33,8 +24,9 @@ define(function(require) { // Makes sure two arrays have the same values, account IDs. function hasSameValues(ary1, ary2) { - if (ary1.length !== ary2.length) + if (ary1.length !== ary2.length) { return false; + } var hasMismatch = ary1.some(function(item, i) { return item !== ary2[i]; @@ -44,13 +36,10 @@ define(function(require) { } if (navigator.mozSetMessageHandler) { - navigator.mozSetMessageHandler('alarm', function onAlarm(alarm) { - // Do not bother with alarms that are not sync alarms. - var data = alarm.data; - if (!data || data.type !== 'sync') - return; - - // Need to acquire the wake locks during this alarm notification + navigator.mozSetMessageHandler('request-sync', + function onRequestSync(e) { + var data = e.data; + // Need to acquire the wake locks during this notification // turn of the event loop -- later turns are not guaranteed to // be up and running. However, knowing when to release the locks // is only known to the front end, so publish event about it. @@ -72,7 +61,10 @@ define(function(require) { makeAccountKey(data.accountIds), locks); } - dispatcher._sendMessage('alarm', [data.accountIds, data.interval]); + debug('request-sync started at ' + (new Date())); + + dispatcher._sendMessage('requestSync', + [data.accountIds, data.interval]); }); } @@ -103,161 +95,157 @@ define(function(require) { }, /** - * Clears all sync-based alarms. Normally not called, except perhaps for + * Clears all sync-based tasks. Normally not called, except perhaps for * tests or debugging. */ clearAll: function() { - var mozAlarms = navigator.mozAlarms; - if (!mozAlarms) + var navSync = navigator.sync; + if (!navSync) { return; + } - var r = mozAlarms.getAll(); - - r.onsuccess = function(event) { - var alarms = event.target.result; - if (!alarms) + navSync.registrations().then(function(registrations) { + if (!registrations.length) { return; + } - alarms.forEach(function(alarm) { - if (alarm.data && alarm.data.type === 'sync') - mozAlarms.remove(alarm.id); + registrations.forEach(function(registeredTask) { + navSync.unregister(registeredTask.task); }); - }.bind(this); - r.onerror = function(err) { - console.error('cronsync-main clearAll mozAlarms.getAll: error: ' + - err); - }.bind(this); + }.bind(this), + function(err) { + console.error('cronsync-main clearAll navigator.sync.registrations ' + + 'error: ' + err); + }.bind(this)); }, /** - * Makes sure there is an alarm set for every account in + * Makes sure there is an sync task set for every account in * the list. * @param {Object} syncData. An object with keys that are * 'interval' + intervalInMilliseconds, and values are arrays * of account IDs that should be synced at that interval. */ ensureSync: function (syncData) { - var mozAlarms = navigator.mozAlarms; - if (!mozAlarms) { - console.warn('no mozAlarms support!'); + var navSync = navigator.sync; + if (!navSync) { + console.warn('no navigator.sync support!'); + // Let backend know work has finished, even though it was a no-op. + this._sendMessage('syncEnsured'); return; } debug('ensureSync called'); - var request = mozAlarms.getAll(); - - request.onsuccess = function(event) { + navSync.registrations().then(function(registrations) { debug('success!'); - var alarms = event.target.result; - // If there are no alarms a falsey value may be returned. We want - // to not die and also make sure to signal we completed, so just make - // an empty list. - if (!alarms) { - alarms = []; - } - - // Find all IDs being tracked by alarms - var expiredAlarmIds = [], - okAlarmIntervals = {}, - uniqueAlarms = {}; - - alarms.forEach(function(alarm) { - // Only care about sync alarms. - if (!alarm.data || !alarm.data.type || alarm.data.type !== 'sync') - return; + // Find all IDs being tracked by sync tasks + var expiredTasks = [], + okTaskIntervals = {}, + uniqueTasks = {}; - var intervalKey = 'interval' + alarm.data.interval, + registrations.forEach(function(task) { + // minInterval in seconds, but use milliseconds for sync values + // internally. + var intervalKey = 'interval' + (task.minInterval * 1000), wantedAccountIds = syncData[intervalKey]; if (!wantedAccountIds || !hasSameValues(wantedAccountIds, - alarm.data.accountIds)) { - debug('account array mismatch, canceling existing alarm'); - expiredAlarmIds.push(alarm.id); + task.data.accountIds)) { + debug('account array mismatch, canceling existing sync task'); + expiredTasks.push(task); } else { - // Confirm the existing alarm is still good. + // Confirm the existing sync task is still good. var interval = toInterval(intervalKey), - now = Date.now(), - alarmTime = alarm.data.timestamp, accountKey = makeAccountKey(wantedAccountIds); - // If the interval is nonzero, and there is no other alarm found + // If the interval is nonzero, and there is no other task found // for that account combo, and if it is not in the past and if it // is not too far in the future, it is OK to keep. - if (interval && !uniqueAlarms.hasOwnProperty(accountKey) && - alarmTime > now && alarmTime < now + interval) { - debug('existing alarm is OK'); - uniqueAlarms[accountKey] = true; - okAlarmIntervals[intervalKey] = true; + if (interval && !uniqueTasks.hasOwnProperty(accountKey)) { + debug('existing sync task is OK: ' + interval); + uniqueTasks[accountKey] = true; + okTaskIntervals[intervalKey] = true; } else { - debug('existing alarm is out of interval range, canceling'); - expiredAlarmIds.push(alarm.id); + debug('existing sync task is out of interval range, canceling'); + expiredTasks.push(task); } } }); - expiredAlarmIds.forEach(function(alarmId) { - mozAlarms.remove(alarmId); + expiredTasks.forEach(function(expiredTask) { + navSync.unregister(expiredTask.task); }); - var alarmMax = 0, - alarmCount = 0, + var taskMax = 0, + taskCount = 0, self = this; - // Called when alarms are confirmed to be set. + // Called when sync tasks are confirmed to be set. function done() { - alarmCount += 1; - if (alarmCount < alarmMax) + taskCount += 1; + if (taskCount < taskMax) { return; + } debug('ensureSync completed'); // Indicate ensureSync has completed because the - // back end is waiting to hear alarm was set before - // triggering sync complete. + // back end is waiting to hear sync task was set + // before triggering sync complete. self._sendMessage('syncEnsured'); } Object.keys(syncData).forEach(function(intervalKey) { - // Skip if the existing alarm is already good. - if (okAlarmIntervals.hasOwnProperty(intervalKey)) + // Skip if the existing sync task is already good. + if (okTaskIntervals.hasOwnProperty(intervalKey)) { return; + } var interval = toInterval(intervalKey), - accountIds = syncData[intervalKey], - date = new Date(Date.now() + interval); + accountIds = syncData[intervalKey]; // Do not set an timer for a 0 interval, bad things happen. - if (!interval) + if (!interval) { return; + } - alarmMax += 1; - - var alarmRequest = mozAlarms.add(date, 'ignoreTimezone', - makeData(accountIds, interval, date)); - - alarmRequest.onsuccess = function() { - debug('success: mozAlarms.add for ' + 'IDs: ' + accountIds + + taskMax += 1; + + navSync.register('interval' + interval, { + // minInterval is in seconds. + minInterval: interval / 1000, + oneShot: false, + data: { + accountIds: accountIds, + interval: interval + }, + wifiOnly: false, + // TODO: allow this to be more generic, getting this passed in + // from the page using this module. This assumes the current page + // without query strings or fragment IDs is the desired entry point. + wakeUpPage: location.href.split('?')[0].split('#')[0] }) + .then(function() { + debug('success: navigator.sync.register for ' + 'IDs: ' + + accountIds + ' at ' + interval + 'ms'); done(); - }; - - alarmRequest.onerror = function(err) { - console.error('cronsync-main mozAlarms.add for IDs: ' + + }, function(err) { + console.error('cronsync-main navigator.sync.register for IDs: ' + accountIds + ' failed: ' + err); - }; + }); }); - // If no alarms were added, indicate ensureSync is done. - if (!alarmMax) + // If no sync tasks were added, indicate ensureSync is done. + if (!taskMax) { done(); - }.bind(this); - - request.onerror = function(err) { - console.error('cronsync-main ensureSync mozAlarms.getAll: error: ' + - err); - }; + } + }.bind(this), + function(err) { + console.error('cronsync-main ensureSync navigator.sync.register: ' + + 'error: ' + err); + }); } }; diff --git a/test-runner/chrome/content/loggest-chrome-runner.js b/test-runner/chrome/content/loggest-chrome-runner.js index 86594303..1f8825a8 100644 --- a/test-runner/chrome/content/loggest-chrome-runner.js +++ b/test-runner/chrome/content/loggest-chrome-runner.js @@ -35,13 +35,13 @@ Cu.import("resource://gre/modules/osfile.jsm"); //////////////////////////////////////////////////////////////////////////////// // Import important services that b2g's shell.js loads // -// For example, if we want mozAlarms to work, we have to import its service! +// For example, if we want requestSync to work, we have to import its service! // (Commented out stuff was in shell.js but we don't think we need it or // absolutely don't want it.) 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/AlarmService.jsm'); Cu.import('resource://gre/modules/ActivitiesService.jsm'); Cu.import('resource://gre/modules/NotificationDB.jsm'); //Cu.import('resource://gre/modules/Payment.jsm'); @@ -50,6 +50,7 @@ Cu.import("resource://gre/modules/AppsUtils.jsm"); //Cu.import('resource://gre/modules/Keyboard.jsm'); //Cu.import('resource://gre/modules/ErrorPage.jsm'); //Cu.import('resource://gre/modules/AlertsHelper.jsm'); +Cu.import('resource://gre/modules/RequestSyncService.jsm'); Cu.import('resource://gre/modules/Webapps.jsm'); DOMApplicationRegistry.allAppsLaunchable = true; diff --git a/test/manifest.webapp b/test/manifest.webapp index 195c1090..88ebe8bb 100644 --- a/test/manifest.webapp +++ b/test/manifest.webapp @@ -8,11 +8,10 @@ "url": "https://github.com/mozilla-b2g/gaia-email-libs-and-more" }, "messages": [ - { "alarm": "/index.html" }, - { "notification": "/index.html" } + { "notification": "/index.html" }, + { "request-sync": "/index.html" } ], "permissions": { - "alarms":{}, "audio-channel-notification":{}, "contacts":{ "access": "readcreate" }, "desktop-notification":{}, diff --git a/test/unit/resources/th_main.js b/test/unit/resources/th_main.js index 20f56b08..233b3da7 100644 --- a/test/unit/resources/th_main.js +++ b/test/unit/resources/th_main.js @@ -609,8 +609,8 @@ var TestUniverseMixins = { * do_cronsync_releaseOutbox for each account to un-wedge them. We wedge * both of them every time. * - * We trigger the cronsync by directly poking its onAlarm method. It has no - * cleverness that would defeat this (at this time). + * We trigger the cronsync by directly poking its onRequestSync method. It + * has no cleverness that would defeat this (at this time). * * @param {Object} opts * @param {TestAccount[]} accounts @@ -688,7 +688,7 @@ var TestUniverseMixins = { var accountIds = []; - this.eCronSync.expect_alarmFired(); + this.eCronSync.expect_requestSyncFired(); this.eCronSync.expect_cronSync_begin(); this.eCronSync.expect_ensureSync_begin(); this.eCronSync.expect_syncAccounts_begin(); @@ -710,7 +710,7 @@ var TestUniverseMixins = { this.expect_apiCronSyncStartReported(accountIds); - this.universe._cronSync.onAlarm(accountIds); + this.universe._cronSync.onRequestSync(accountIds); }.bind(this)); }, diff --git a/test/unit/resources/window_shims.js b/test/unit/resources/window_shims.js index 4a2f3378..0f23122d 100644 --- a/test/unit/resources/window_shims.js +++ b/test/unit/resources/window_shims.js @@ -161,13 +161,6 @@ var _window_mixin = { return req; }, }, - // By default we start up disabled, so it's not really a biggie either way. - mozAlarms: { - add: function() {}, - get: function() {}, - getAll: function() {}, - remove: function() {}, - }, getDeviceStorage: function(ds) { return { diff --git a/test/unit/test_account_updates.js b/test/unit/test_account_updates.js index 875ea357..940981ed 100644 --- a/test/unit/test_account_updates.js +++ b/test/unit/test_account_updates.js @@ -57,13 +57,17 @@ TD.commonCase('modifyAccount updates should be reflected', function(T, RT) { T.group('verify the changes took'); T.check('verify', eLazy, function() { - var mailAccount = testUniverse.allAccountsSlice.items[0]; - var modifyKeys = Object.keys(modifyValues); - modifyKeys.forEach(function(key) { - var current = mailAccount[key]; - var modifyTo = modifyValues[key]; - eLazy.expect_namedValue(key, modifyTo); - eLazy.namedValue(key, current); + // Ping to make sure modifyAccount work fully completes and notifies out + // before testing slice results. + testUniverse.MailAPI.ping(function() { + var mailAccount = testUniverse.allAccountsSlice.items[0]; + var modifyKeys = Object.keys(modifyValues); + modifyKeys.forEach(function(key) { + var current = mailAccount[key]; + var modifyTo = modifyValues[key]; + eLazy.expect_namedValue(key, modifyTo); + eLazy.namedValue(key, current); + }); }); }); diff --git a/test/unit/test_cronsync_wait_for_completion.js b/test/unit/test_cronsync_wait_for_completion.js index 47d6f3d8..d98d92f7 100644 --- a/test/unit/test_cronsync_wait_for_completion.js +++ b/test/unit/test_cronsync_wait_for_completion.js @@ -120,7 +120,7 @@ TD.commonCase('cronsync waits for completion', function(T, RT) { { top: true, bottom: true, grow: false, newCount: null }, { syncedToDawnOfTime: true }); - // We are actually running with the real, actual mozAlarms API powering us. + // We are actually running with the real, actual sync API powering us. // So what we want is a value that is sufficiently far in the future that it // won't fire during the test but it's also not ridiculous. We pick an hour. var SYNC_INTERVAL = 60 * 60 * 1000;