Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 'use strict'; | 5 'use strict'; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * @fileoverview The event page for Google Now for Chrome implementation. | 8 * @fileoverview The event page for Google Now for Chrome implementation. |
| 9 * The Google Now event page gets Google Now cards from the server and shows | 9 * The Google Now event page gets Google Now cards from the server and shows |
| 10 * them as Chrome notifications. | 10 * them as Chrome notifications. |
| 11 * The service performs periodic updating of Google Now cards. | 11 * The service performs periodic updating of Google Now cards. |
| 12 * Each updating of the cards includes 3 steps: | 12 * Each updating of the cards includes 3 steps: |
| 13 * 1. Obtaining the location of the machine; | 13 * 1. Obtaining the location of the machine; |
| 14 * 2. Making a server request based on that location; | 14 * 2. Making a server request based on that location; |
| 15 * 3. Showing the received cards as notifications. | 15 * 3. Showing the received cards as notifications. |
| 16 */ | 16 */ |
| 17 | 17 |
| 18 // TODO(vadimt): Use background permission to show notifications even when all | 18 // TODO(vadimt): Use background permission to show notifications even when all |
| 19 // browser windows are closed. | 19 // browser windows are closed. |
| 20 // TODO(vadimt): Remove the C++ implementation. | 20 // TODO(vadimt): Remove the C++ implementation. |
| 21 // TODO(vadimt): Decide what to do in incognito mode. | 21 // TODO(vadimt): Decide what to do in incognito mode. |
| 22 // TODO(vadimt): Gather UMAs. | 22 // TODO(vadimt): Gather UMAs. |
| 23 // TODO(vadimt): Honor the flag the enables Google Now integration. | 23 // TODO(vadimt): Honor the flag the enables Google Now integration. |
| 24 // TODO(vadimt): Figure out the final values of the constants. | 24 // TODO(vadimt): Figure out the final values of the constants. |
| 25 // TODO(vadimt): Report errors to the user. | 25 // TODO(vadimt): Report errors to the user. |
| 26 | 26 |
| 27 // TODO(vadimt): Figure out the server name. Use it in the manifest and for | 27 // TODO(vadimt): Figure out the server name. Use it in the manifest and for |
| 28 // TODO(vadimt): Consider processing errors for all storage.set calls. | |
| 29 // NOTIFICATION_CARDS_URL. Meanwhile, to use the feature, you need to manually | 28 // NOTIFICATION_CARDS_URL. Meanwhile, to use the feature, you need to manually |
| 30 // edit NOTIFICATION_CARDS_URL before building Chrome. | 29 // edit NOTIFICATION_CARDS_URL before building Chrome. |
| 30 // TODO(vadimt): Consider processing errors for all storage.set calls. | |
| 31 | |
| 31 /** | 32 /** |
| 32 * URL to retrieve notification cards. | 33 * URL to retrieve notification cards. |
| 33 */ | 34 */ |
| 34 var NOTIFICATION_CARDS_URL = ''; | 35 var NOTIFICATION_CARDS_URL = ''; |
| 35 | 36 |
| 36 /** | 37 /** |
| 37 * Standard response code for successful HTTP requests. This is the only success | 38 * Standard response code for successful HTTP requests. This is the only success |
| 38 * code the server will send. | 39 * code the server will send. |
| 39 */ | 40 */ |
| 40 var HTTP_OK = 200; | 41 var HTTP_OK = 200; |
| 41 | 42 |
| 42 /** | 43 /** |
| 43 * Initial period for polling for Google Now Notifications cards to use when the | 44 * Initial period for polling for Google Now Notifications cards to use when the |
| 44 * period from the server is not available. | 45 * period from the server is not available. |
| 45 */ | 46 */ |
| 46 var INITIAL_POLLING_PERIOD_SECONDS = 300; // 5 minutes | 47 var INITIAL_POLLING_PERIOD_SECONDS = 300; // 5 minutes |
| 47 | 48 |
| 48 /** | 49 /** |
| 49 * Maximal period for polling for Google Now Notifications cards to use when the | 50 * Maximal period for polling for Google Now Notifications cards to use when the |
| 50 * period from the server is not available. | 51 * period from the server is not available. |
| 51 */ | 52 */ |
| 52 var MAXIMUM_POLLING_PERIOD_SECONDS = 3600; // 1 hour | 53 var MAXIMUM_POLLING_PERIOD_SECONDS = 3600; // 1 hour |
| 53 | 54 |
| 55 /** | |
| 56 * Names for tasks that can be created by the extension. | |
| 57 * @enum {string} | |
| 58 */ | |
| 59 var TaskName = { | |
| 60 UPDATE_CARDS: 'update-cards', | |
| 61 DISMISS_CARD: 'dismiss-card', | |
| 62 CARD_CLICKED: 'card-clicked' | |
| 63 }; | |
| 64 | |
| 65 var UPDATE_NOTIFICATIONS_ALARM_NAME = 'UPDATE'; | |
| 66 | |
| 54 var storage = chrome.storage.local; | 67 var storage = chrome.storage.local; |
| 55 | 68 |
| 69 ///////////////////////////////////////////////////////////////////////// | |
| 70 ////// ABSOLUTELY NOT FORGET TO MOVE ASSERT AND TASK MANAGER INTO SEPARATE FILE | |
| 71 ////// IN THIS CL !!!!!!!!!!!!! | |
| 72 //////////////////////////////////////////////////////////////////////// | |
| 73 | |
| 74 /** | |
| 75 * Check for internal errors. | |
| 76 * @param {boolean} condition Condition that must be true. | |
| 77 * @param {string} message Diagnostic message for the case when the condition is | |
| 78 * false. | |
| 79 */ | |
| 80 function assert(condition, message) { | |
| 81 // TODO(vadimt): Send UMAs instead of showing alert. | |
| 82 if (!condition) { | |
| 83 var errorText = 'ASSERT: ' + message; | |
| 84 console.error(errorText); | |
| 85 alert(errorText); | |
|
skare_
2013/03/02 00:07:05
console.assert()?
skare_
2013/03/02 00:07:05
I didn't think alert() from a background page work
vadimt
2013/03/06 21:04:38
Then no one will notice it.
I'd prefer these impor
vadimt
2013/03/06 21:04:38
We checked that it works, remember :) ?
skare_
2013/03/06 22:35:57
[ok]
yes, but that was after this set of comments
vadimt
2013/03/06 22:56:35
No doubt about that.
skare_
2013/03/07 23:25:13
bringing this comment back from the dead. Would co
vadimt
2013/03/08 21:11:58
Renamed assert to verify.
This assert is for thin
rgustafson
2013/03/11 18:11:04
If it's stuff that should never happen and this wi
skare_
2013/03/11 18:37:12
re: the alert() line, would it be ok to not show a
vadimt
2013/03/11 21:31:53
The assert shouldn't happen in a different sense :
vadimt
2013/03/11 21:31:53
RE: re: the alert() line
Per offline talk, leaving
| |
| 86 } | |
| 87 } | |
| 88 | |
| 89 /** | |
| 90 * Build the closure that manages tasks (mutually exclusive chains of events). | |
|
skare_
2013/03/02 00:07:05
you could omit "build the closure" and just say e.
vadimt
2013/03/06 21:04:38
Done.
| |
| 91 * @return {Object} Set of methods to control tasks. | |
| 92 */ | |
| 93 function TaskManager() { | |
| 94 /** | |
| 95 * Name of the alarm that triggers the error saying that the event page cannot | |
| 96 * unload. | |
| 97 */ | |
| 98 var CANNOT_UNLOAD_ALARM_NAME = 'CANNOT-UNLOAD'; | |
| 99 | |
| 100 /** | |
| 101 * Maximal time we expect the event page to stay loaded after starting a task. | |
| 102 */ | |
| 103 var MAXIMUM_LOADED_TIME_MINUTES = 5; | |
| 104 | |
| 105 /** | |
| 106 * Queue of scheduled tasks. The first element, if present, corresponds to the | |
| 107 * currently running task. | |
|
skare_
2013/03/07 23:25:13
@type this --
{Array.<Task>} // where you'd typede
vadimt
2013/03/08 21:11:58
Coool
| |
| 108 */ | |
| 109 var queue = []; | |
| 110 | |
| 111 /** | |
| 112 * Name of the current step of the currently running task if present, | |
| 113 * otherwise, null. For diagnostics only. | |
| 114 * It's set when the task is started and before each asynchronous operation. | |
| 115 */ | |
| 116 var stepName = null; | |
| 117 | |
| 118 /** | |
| 119 * Start the first queued task. | |
| 120 */ | |
| 121 function startFirst() { | |
|
rgustafson
2013/03/04 22:31:27
startFirstTask() makes this more self explanatory
vadimt
2013/03/06 21:04:38
The idea is that these are members of TaskManager,
| |
| 122 // Set alarm to verify that the event page will unload in a reasonable time. | |
| 123 chrome.alarms.create(CANNOT_UNLOAD_ALARM_NAME, | |
| 124 {delayInMinutes: MAXIMUM_LOADED_TIME_MINUTES}); | |
| 125 | |
| 126 // Start the oldest queued task, but not remove it from the queue. | |
|
skare_
2013/03/02 00:07:05
nit: s/not/don't
vadimt
2013/03/06 21:04:38
Done.
| |
| 127 assert(stepName == null, 'tasks.startFirst: stepName is not null'); | |
| 128 var entry = queue[0]; | |
|
skare_
2013/03/02 00:07:05
maybe bail out if queue is empty here?
rgustafson
2013/03/04 22:31:27
This seems to be called only when there is a first
vadimt
2013/03/06 21:04:38
The method will be called only when the queue is n
vadimt
2013/03/06 21:04:38
Done.
vadimt
2013/03/06 21:04:38
Done.
skare_
2013/03/06 22:35:57
I still like bailing out on an empty queue (or lik
skare_
2013/03/07 23:25:13
[bringing this into latest round of comments too]
vadimt
2013/03/08 21:11:58
Per the offline talk, seems we are proceeding as i
skare_
2013/03/11 18:37:12
so long as whatever the assert() is replaced with
vadimt
2013/03/11 21:31:53
Added to verify():
// TODO(vadimt): Make sure the
| |
| 129 stepName = entry.name + '-initial'; | |
| 130 entry.task(); | |
| 131 } | |
| 132 | |
| 133 /** | |
| 134 * Check if a new task can be added to a queue that contains an existing task. | |
| 135 * @param {string} newTaskName Name of the new task. | |
| 136 * @param {string} queuedTaskName Name of the task in the queue. | |
| 137 * @return {boolean} Whether the new task doesn't conflict with the existing | |
|
skare_
2013/03/02 00:07:05
maybe invert logic -- isCompatible -> tasksConflic
vadimt
2013/03/06 21:04:38
Done.
| |
| 138 * task. | |
| 139 */ | |
| 140 function isCompatible(newTaskName, queuedTaskName) { | |
| 141 if (newTaskName == TaskName.UPDATE_CARDS && | |
| 142 queuedTaskName == TaskName.UPDATE_CARDS) { | |
| 143 // If a card update is requested while an old update is still in the | |
| 144 // queue, we don't need the new update. | |
| 145 return false; | |
| 146 } | |
| 147 | |
| 148 return true; | |
| 149 } | |
| 150 | |
| 151 /** | |
| 152 * Check if a new task can be added to the task queue. | |
|
skare_
2013/03/02 00:07:05
nit (throughout comments in the file): this is in
vadimt
2013/03/06 21:04:38
Done.
| |
| 153 * @param {TaskName} taskName Name of the new task. | |
| 154 * @return {boolean} Whether the new task can be added. | |
| 155 */ | |
| 156 function canQueue(taskName) { | |
| 157 for (var i = 0; i < queue.length; ++i) | |
| 158 if (!isCompatible(taskName, queue[i].name)) | |
| 159 return false; | |
| 160 | |
| 161 return true; | |
| 162 } | |
| 163 | |
| 164 /** | |
| 165 * Add a new task. If another task is not running, run the task immediately. | |
| 166 * If any task in the queue is not compatible with the task, ignore the new | |
| 167 * task. Otherwise, store the task for future execution. | |
| 168 * @param {TaskName} taskName Name of the task. | |
| 169 * @param {function()} task Code of the task. | |
| 170 */ | |
| 171 function submit(taskName, task) { | |
| 172 if (!canQueue(taskName)) | |
| 173 return; | |
| 174 | |
| 175 queue.push({name: taskName, task: task}); | |
| 176 | |
| 177 if (queue.length == 1) { | |
| 178 startFirst(); | |
|
skare_
2013/03/02 00:07:05
it's ok as used now now, but you're ok with the fa
vadimt
2013/03/06 21:04:38
Yes, I think that it's better than starting asynch
| |
| 179 } | |
| 180 } | |
| 181 | |
| 182 /** | |
| 183 * Complete the current task and start the next queued task if available. | |
| 184 */ | |
| 185 function finish() { | |
|
skare_
2013/03/02 00:07:05
high-level comment: a lot of JS patterns end up ha
vadimt
2013/03/06 21:04:38
I've considered this. I thought that this would be
skare_
2013/03/06 22:35:57
feel free to get other people to comment here but
vadimt
2013/03/06 22:56:35
Done.
| |
| 186 assert(queue.length >= 1, 'tasks.finish: The task queue is empty.'); | |
| 187 queue.shift(); | |
| 188 stepName = null; | |
| 189 | |
| 190 if (queue.length >= 1) | |
| 191 startFirst(); | |
| 192 } | |
| 193 | |
| 194 /** | |
| 195 * Associate a name with the current step of the task. Used for diagnostics | |
| 196 * only. A task is a chain of asynchronous events; setStep should be called | |
| 197 * before starting any asynchronous operation. | |
| 198 * @param {string} step Name of new step. | |
| 199 */ | |
| 200 function setStep(step) { | |
|
rgustafson
2013/03/04 22:31:27
Is there a reason you don't use this inside the Ta
vadimt
2013/03/06 21:04:38
One day, for example, I may want to add a "stepNam
skare_
2013/03/06 22:35:57
more general question -- could state name be instr
vadimt
2013/03/06 22:56:35
The plan about this is:
in the release, we'll prob
skare_
2013/03/07 23:25:13
would UMA error counters be slightly more granular
vadimt
2013/03/08 21:11:58
May be, yes, may be, no. We'll have to decide then
rgustafson
2013/03/11 18:11:05
Are we really expecting our event page being impro
skare_
2013/03/11 18:37:12
re: 'parent name' I meant hypothetically
myTask.se
skare_
2013/03/11 19:46:26
ignore setStep() naming comment; resolved in next
vadimt
2013/03/11 21:31:53
If I knew situations that cannot be caught with te
vadimt
2013/03/11 21:31:53
RE: re: 'parent name' I meant hypothetically
In th
vadimt
2013/03/11 21:31:53
(In a gloom voice:) Too late.
Already renamed to d
| |
| 201 stepName = step; | |
| 202 } | |
| 203 | |
| 204 chrome.alarms.onAlarm.addListener(function(alarm) { | |
| 205 if (alarm.name == CANNOT_UNLOAD_ALARM_NAME) { | |
| 206 // Error if the event page wasn't unloaded after a reasonable timeout | |
| 207 // since starting the last task. | |
| 208 // TODO(vadimt): Uncomment the assert once this bug is fixed: | |
| 209 // crbug.com/177563 | |
| 210 // assert(false, 'Event page didn\'t unload, queue = ' + | |
| 211 // JSON.stringify(tasks) + ', step = ' + stepName + ' (ignore this assert | |
| 212 // if devtools is attached).'); | |
| 213 } | |
| 214 }); | |
| 215 | |
| 216 chrome.runtime.onSuspend.addListener(function() { | |
| 217 chrome.alarms.clear(CANNOT_UNLOAD_ALARM_NAME); | |
| 218 assert(queue.length == 0 && stepName == null, | |
| 219 'Incomplete task when unloading event page, queue = ' + | |
| 220 JSON.stringify(queue) + ', step = ' + stepName); | |
| 221 }); | |
| 222 | |
| 223 return { | |
| 224 submit: submit, | |
| 225 setStep: setStep, | |
| 226 finish: finish | |
| 227 }; | |
| 228 } | |
| 229 | |
| 230 var tasks = TaskManager(); | |
| 231 | |
| 56 /** | 232 /** |
| 57 * Show a notification and remember information associated with it. | 233 * Show a notification and remember information associated with it. |
| 58 * @param {Object} card Google Now card represented as a set of parameters for | 234 * @param {Object} card Google Now card represented as a set of parameters for |
| 59 * showing a Chrome notification. | 235 * showing a Chrome notification. |
| 60 * @param {Object} notificationsUrlInfo Map from notification id to the | 236 * @param {Object} notificationsUrlInfo Map from notification id to the |
| 61 * notification's set of URLs. | 237 * notification's set of URLs. |
| 62 */ | 238 */ |
| 63 function createNotification(card, notificationsUrlInfo) { | 239 function createNotification(card, notificationsUrlInfo) { |
| 64 // Create a notification or quietly update if it already exists. | 240 // Create a notification or quietly update if it already exists. |
| 65 // TODO(vadimt): Implement non-quiet updates. | 241 // TODO(vadimt): Implement non-quiet updates. |
| 66 chrome.experimental.notification.create( | 242 chrome.experimental.notification.create( |
| 67 card.notificationId, | 243 card.notificationId, |
| 68 card.notification, | 244 card.notification, |
| 69 function(assignedNotificationId) {}); | 245 function() {}); |
| 70 | 246 |
| 71 notificationsUrlInfo[card.notificationId] = card.actionUrls; | 247 notificationsUrlInfo[card.notificationId] = card.actionUrls; |
| 72 } | 248 } |
| 73 | 249 |
| 74 /** | 250 /** |
| 75 * Parse JSON response from the notification server, show notifications and | 251 * Parse JSON response from the notification server, show notifications and |
| 76 * schedule next update. | 252 * schedule next update. |
| 77 * @param {string} response Server response. | 253 * @param {string} response Server response. |
| 78 */ | 254 */ |
| 79 function parseAndShowNotificationCards(response) { | 255 function parseAndShowNotificationCards(response) { |
| 80 try { | 256 try { |
| 81 var parsedResponse = JSON.parse(response); | 257 var parsedResponse = JSON.parse(response); |
| 82 } catch (error) { | 258 } catch (error) { |
| 83 // TODO(vadimt): Report errors to the user. | 259 // TODO(vadimt): Report errors to the user. |
| 84 return; | 260 return; |
| 85 } | 261 } |
| 86 | 262 |
| 87 var cards = parsedResponse.cards; | 263 var cards = parsedResponse.cards; |
| 88 | 264 |
| 89 if (!(cards instanceof Array)) { | 265 if (!(cards instanceof Array)) { |
| 90 // TODO(vadimt): Report errors to the user. | 266 // TODO(vadimt): Report errors to the user. |
| 91 return; | 267 return; |
| 92 } | 268 } |
| 93 | 269 |
| 94 if (typeof parsedResponse.expiration_timestamp_seconds != 'number') { | 270 if (typeof parsedResponse.expiration_timestamp_seconds != 'number') { |
| 95 // TODO(vadimt): Report errors to the user. | 271 // TODO(vadimt): Report errors to the user. |
| 96 return; | 272 return; |
| 97 } | 273 } |
| 98 | 274 |
| 275 tasks.setStep('parseAndShowNotificationCards-get-active-notifications'); | |
| 99 storage.get('activeNotifications', function(items) { | 276 storage.get('activeNotifications', function(items) { |
| 100 // Mark existing notifications that received an update in this server | 277 // Mark existing notifications that received an update in this server |
| 101 // response. | 278 // response. |
| 102 for (var i = 0; i < cards.length; ++i) { | 279 for (var i = 0; i < cards.length; ++i) { |
| 103 var notificationId = cards[i].notificationId; | 280 var notificationId = cards[i].notificationId; |
| 104 if (notificationId in items.activeNotifications) | 281 if (notificationId in items.activeNotifications) |
| 105 items.activeNotifications[notificationId].hasUpdate = true; | 282 items.activeNotifications[notificationId].hasUpdate = true; |
| 106 } | 283 } |
| 107 | 284 |
| 108 // Delete notifications that didn't receive an update. | 285 // Delete notifications that didn't receive an update. |
| 109 for (var notificationId in items.activeNotifications) | 286 for (var notificationId in items.activeNotifications) |
| 110 if (!items.activeNotifications[notificationId].hasUpdate) { | 287 if (!items.activeNotifications[notificationId].hasUpdate) { |
| 111 chrome.experimental.notification.clear( | 288 chrome.experimental.notification.clear( |
| 112 notificationId, | 289 notificationId, |
| 113 function(wasDeleted) {}); | 290 function() {}); |
| 114 } | 291 } |
| 115 | 292 |
| 116 // Create/update notifications and store their new properties. | 293 // Create/update notifications and store their new properties. |
| 117 var notificationsUrlInfo = {}; | 294 var notificationsUrlInfo = {}; |
| 118 | 295 |
| 119 for (var i = 0; i < cards.length; ++i) { | 296 for (var i = 0; i < cards.length; ++i) { |
| 120 try { | 297 try { |
| 121 createNotification(cards[i], notificationsUrlInfo); | 298 createNotification(cards[i], notificationsUrlInfo); |
| 122 } catch (error) { | 299 } catch (error) { |
| 123 // TODO(vadimt): Report errors to the user. | 300 // TODO(vadimt): Report errors to the user. |
| 124 } | 301 } |
| 125 } | 302 } |
| 126 storage.set({activeNotifications: notificationsUrlInfo}); | 303 storage.set({activeNotifications: notificationsUrlInfo}); |
| 127 | 304 |
| 128 scheduleNextUpdate(parsedResponse.expiration_timestamp_seconds); | 305 scheduleNextUpdate(parsedResponse.expiration_timestamp_seconds); |
| 129 | 306 |
| 130 // Now that we got a valid response from the server, reset the retry period | 307 // Now that we got a valid response from the server, reset the retry period |
| 131 // to the initial value. This retry period will be used the next time we | 308 // to the initial value. This retry period will be used the next time we |
| 132 // fail to get the server-provided period. | 309 // fail to get the server-provided period. |
| 133 storage.set({retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS}); | 310 storage.set({retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS}); |
| 311 tasks.finish(); | |
| 134 }); | 312 }); |
| 135 } | 313 } |
| 136 | 314 |
| 137 /** | 315 /** |
| 138 * Request notification cards from the server. | 316 * Request notification cards from the server. |
| 139 * @param {string} requestParameters Query string for the request. | 317 * @param {string} requestParameters Query string for the request. |
| 140 */ | 318 */ |
| 141 function requestNotificationCards(requestParameters) { | 319 function requestNotificationCards(requestParameters) { |
| 142 // TODO(vadimt): Figure out how to send user's identity to the server. | 320 // TODO(vadimt): Figure out how to send user's identity to the server. |
| 143 var request = new XMLHttpRequest(); | 321 var request = new XMLHttpRequest(); |
| 144 | 322 |
| 145 request.responseType = 'text'; | 323 request.responseType = 'text'; |
| 146 request.onload = function(event) { | 324 request.onload = function() { |
| 147 if (request.status == HTTP_OK) | 325 if (request.status == HTTP_OK) |
| 148 parseAndShowNotificationCards(request.response); | 326 parseAndShowNotificationCards(request.response); |
| 327 else | |
| 328 tasks.finish(); | |
| 329 } | |
| 330 | |
| 331 request.onerror = function() { | |
|
rgustafson
2013/03/04 22:31:27
Are there any other callbacks we need to handle li
vadimt
2013/03/06 21:04:38
Done.
| |
| 332 tasks.finish(); | |
| 149 } | 333 } |
| 150 | 334 |
| 151 request.open( | 335 request.open( |
| 152 'GET', | 336 'GET', |
| 153 NOTIFICATION_CARDS_URL + '/notifications' + requestParameters, | 337 NOTIFICATION_CARDS_URL + '/notifications' + requestParameters, |
| 154 true); | 338 true); |
| 339 tasks.setStep('requestNotificationCards-send-request'); | |
| 155 request.send(); | 340 request.send(); |
| 156 } | 341 } |
| 157 | 342 |
| 158 /** | 343 /** |
| 159 * Request notification cards from the server when we have geolocation. | 344 * Request notification cards from the server when we have geolocation. |
| 160 * @param {Geoposition} position Location of this computer. | 345 * @param {Geoposition} position Location of this computer. |
| 161 */ | 346 */ |
| 162 function requestNotificationCardsWithLocation(position) { | 347 function requestNotificationCardsWithLocation(position) { |
| 163 // TODO(vadimt): Should we use 'q' as the parameter name? | 348 // TODO(vadimt): Should we use 'q' as the parameter name? |
| 164 var requestParameters = | 349 var requestParameters = |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 175 */ | 360 */ |
| 176 function requestNotificationCardsWithoutLocation(positionError) { | 361 function requestNotificationCardsWithoutLocation(positionError) { |
| 177 requestNotificationCards(''); | 362 requestNotificationCards(''); |
| 178 } | 363 } |
| 179 | 364 |
| 180 /** | 365 /** |
| 181 * Obtain new location; request and show notification cards based on this | 366 * Obtain new location; request and show notification cards based on this |
| 182 * location. | 367 * location. |
| 183 */ | 368 */ |
| 184 function updateNotificationsCards() { | 369 function updateNotificationsCards() { |
| 185 storage.get('retryDelaySeconds', function(items) { | 370 tasks.submit(TaskName.UPDATE_CARDS, function() { |
| 186 // Immediately schedule the update after the current retry period. Then, | 371 tasks.setStep('updateNotificationsCards-get-retryDelaySeconds'); |
| 187 // we'll use update time from the server if available. | 372 storage.get('retryDelaySeconds', function(items) { |
| 188 scheduleNextUpdate(items.retryDelaySeconds); | 373 // Immediately schedule the update after the current retry period. Then, |
| 374 // we'll use update time from the server if available. | |
| 375 scheduleNextUpdate(items.retryDelaySeconds); | |
| 189 | 376 |
| 190 // TODO(vadimt): Consider interrupting waiting for the next update if we | 377 // TODO(vadimt): Consider interrupting waiting for the next update if we |
| 191 // detect that the network conditions have changed. Also, decide whether the | 378 // detect that the network conditions have changed. Also, decide whether |
| 192 // exponential backoff is needed both when we are offline and when there are | 379 // the exponential backoff is needed both when we are offline and when |
| 193 // failures on the server side. | 380 // there are failures on the server side. |
| 194 var newRetryDelaySeconds = | 381 var newRetryDelaySeconds = |
| 195 Math.min(items.retryDelaySeconds * 2 * (1 + 0.2 * Math.random()), | 382 Math.min(items.retryDelaySeconds * 2 * (1 + 0.2 * Math.random()), |
| 196 MAXIMUM_POLLING_PERIOD_SECONDS); | 383 MAXIMUM_POLLING_PERIOD_SECONDS); |
| 197 storage.set({retryDelaySeconds: newRetryDelaySeconds}); | 384 storage.set({retryDelaySeconds: newRetryDelaySeconds}); |
| 198 | 385 |
| 199 navigator.geolocation.getCurrentPosition( | 386 tasks.setStep('updateNotificationsCards-get-location'); |
| 200 requestNotificationCardsWithLocation, | 387 navigator.geolocation.getCurrentPosition( |
| 201 requestNotificationCardsWithoutLocation); | 388 requestNotificationCardsWithLocation, |
| 389 requestNotificationCardsWithoutLocation); | |
| 390 }); | |
| 202 }); | 391 }); |
| 203 } | 392 } |
| 204 | 393 |
| 205 /** | 394 /** |
| 206 * Opens URL corresponding to the clicked part of the notification. | 395 * Opens URL corresponding to the clicked part of the notification. |
| 207 * @param {string} notificationId Unique identifier of the notification. | 396 * @param {string} notificationId Unique identifier of the notification. |
| 208 * @param {function(Object): string} selector Function that extracts the url for | 397 * @param {function(Object): string} selector Function that extracts the url for |
| 209 * the clicked area from the button action URLs info. | 398 * the clicked area from the button action URLs info. |
| 210 */ | 399 */ |
| 211 function onNotificationClicked(notificationId, selector) { | 400 function onNotificationClicked(notificationId, selector) { |
| 212 storage.get('activeNotifications', function(items) { | 401 tasks.submit(TaskName.CARD_CLICKED, function() { |
| 213 var actionUrls = items.activeNotifications[notificationId]; | 402 tasks.setStep('onNotificationClicked-get-activeNotifications'); |
| 214 if (typeof actionUrls != 'object') { | 403 storage.get('activeNotifications', function(items) { |
| 215 // TODO(vadimt): report an error. | 404 var actionUrls = items.activeNotifications[notificationId]; |
| 216 return; | 405 if (typeof actionUrls != 'object') { |
| 217 } | 406 // TODO(vadimt): report an error. |
| 407 tasks.finish(); | |
| 408 return; | |
| 409 } | |
| 218 | 410 |
| 219 var url = selector(actionUrls); | 411 var url = selector(actionUrls); |
| 220 | 412 |
| 221 if (typeof url != 'string') { | 413 if (typeof url != 'string') { |
| 222 // TODO(vadimt): report an error. | 414 // TODO(vadimt): report an error. |
| 223 return; | 415 tasks.finish(); |
| 224 } | 416 return; |
| 417 } | |
| 225 | 418 |
| 226 chrome.tabs.create({url: url}); | 419 chrome.tabs.create({url: url}); |
| 420 tasks.finish(); | |
| 421 }); | |
| 227 }); | 422 }); |
| 228 } | 423 } |
| 229 | 424 |
| 230 /** | 425 /** |
| 231 * Callback for chrome.experimental.notification.onClosed event. | 426 * Callback for chrome.experimental.notification.onClosed event. |
| 232 * @param {string} notificationId Unique identifier of the notification. | 427 * @param {string} notificationId Unique identifier of the notification. |
| 233 * @param {boolean} byUser Whether the notification was closed by the user. | 428 * @param {boolean} byUser Whether the notification was closed by the user. |
| 234 */ | 429 */ |
| 235 function onNotificationClosed(notificationId, byUser) { | 430 function onNotificationClosed(notificationId, byUser) { |
| 236 if (byUser) { | 431 if (byUser) { |
| 237 // TODO(vadimt): Analyze possible race conditions between request for cards | 432 tasks.submit(TaskName.DISMISS_CARD, function() { |
| 238 // and dismissal. | 433 // Deleting the notification in case it was re-added while this task was |
| 239 // Send a dismiss request to the server. | 434 // waiting in the queue. |
| 240 var requestParameters = '?id=' + notificationId; | 435 chrome.experimental.notification.clear( |
|
rgustafson
2013/03/04 22:31:27
This is going to have a notification pop up for a
vadimt
2013/03/06 21:04:38
The reasoning for not doing this is that I expect
rgustafson
2013/03/11 18:11:05
I'm okay with this for now. Eventually, I think th
skare_
2013/03/11 18:37:12
especially if there's ever the ability to have the
| |
| 241 var request = new XMLHttpRequest(); | 436 notificationId, |
| 242 request.responseType = 'text'; | 437 function() {}); |
| 243 // TODO(vadimt): If the request fails, for example, because there is no | 438 |
| 244 // internet connection, do retry with exponential backoff. | 439 // Send a dismiss request to the server. |
| 245 request.open( | 440 var requestParameters = '?id=' + notificationId; |
| 246 'GET', | 441 var request = new XMLHttpRequest(); |
| 247 NOTIFICATION_CARDS_URL + '/dismiss' + requestParameters, | 442 request.responseType = 'text'; |
| 248 true); | 443 request.onloadend = function() { |
| 249 request.send(); | 444 tasks.finish(); |
| 445 } | |
| 446 // TODO(vadimt): If the request fails, for example, because there is no | |
| 447 // internet connection, do retry with exponential backoff. | |
| 448 request.open( | |
| 449 'GET', | |
| 450 NOTIFICATION_CARDS_URL + '/dismiss' + requestParameters, | |
| 451 true); | |
| 452 tasks.setStep('onNotificationClosed-send-request'); | |
| 453 request.send(); | |
| 454 }); | |
| 250 } | 455 } |
| 251 } | 456 } |
| 252 | 457 |
| 253 /** | 458 /** |
| 254 * Schedule next update for notification cards. | 459 * Schedule next update for notification cards. |
| 255 * @param {int} delaySeconds Length of time in seconds after which the alarm | 460 * @param {int} delaySeconds Length of time in seconds after which the alarm |
| 256 * event should fire. | 461 * event should fire. |
| 257 */ | 462 */ |
| 258 function scheduleNextUpdate(delaySeconds) { | 463 function scheduleNextUpdate(delaySeconds) { |
| 259 // Schedule an alarm after the specified delay. 'periodInMinutes' is for the | 464 // Schedule an alarm after the specified delay. 'periodInMinutes' is for the |
| 260 // case when we fail to re-register the alarm. | 465 // case when we fail to re-register the alarm. |
| 261 chrome.alarms.create({ | 466 var alarmInfo = { |
| 262 delayInMinutes: delaySeconds / 60, | 467 delayInMinutes: delaySeconds / 60, |
| 263 periodInMinutes: MAXIMUM_POLLING_PERIOD_SECONDS / 60 | 468 periodInMinutes: MAXIMUM_POLLING_PERIOD_SECONDS / 60 |
| 264 }); | 469 }; |
| 470 | |
| 471 chrome.alarms.create(UPDATE_NOTIFICATIONS_ALARM_NAME, alarmInfo); | |
| 265 } | 472 } |
| 266 | 473 |
| 267 /** | 474 /** |
| 268 * Initialize the event page on install or on browser startup. | 475 * Initialize the event page on install or on browser startup. |
| 269 */ | 476 */ |
| 270 function initialize() { | 477 function initialize() { |
| 271 var initialStorage = { | 478 var initialStorage = { |
| 272 activeNotifications: {}, | 479 activeNotifications: {}, |
| 273 retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS | 480 retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS |
| 274 }; | 481 }; |
| 275 storage.set(initialStorage, updateNotificationsCards); | 482 storage.set(initialStorage); |
| 483 updateNotificationsCards(); | |
|
rgustafson
2013/03/04 22:31:27
Couldn't retryDelaySeconds not be set when update
vadimt
2013/03/06 21:04:38
I had a long conversation with extensions guys who
rgustafson
2013/03/11 18:11:05
What exactly was their promise? A set followed by
vadimt
2013/03/11 21:31:53
Literally:
If get is executed after set(X), 'get'
| |
| 276 } | 484 } |
| 277 | 485 |
| 278 chrome.runtime.onInstalled.addListener(function(details) { | 486 chrome.runtime.onInstalled.addListener(function(details) { |
| 279 if (details.reason != 'chrome_update') | 487 if (details.reason != 'chrome_update') |
| 280 initialize(); | 488 initialize(); |
| 281 }); | 489 }); |
| 282 | 490 |
| 283 chrome.runtime.onStartup.addListener(function() { | 491 chrome.runtime.onStartup.addListener(function() { |
| 284 initialize(); | 492 initialize(); |
| 285 }); | 493 }); |
| 286 | 494 |
| 287 chrome.alarms.onAlarm.addListener(function(alarm) { | 495 chrome.alarms.onAlarm.addListener(function(alarm) { |
| 288 updateNotificationsCards(); | 496 if (alarm.name == UPDATE_NOTIFICATIONS_ALARM_NAME) |
| 497 updateNotificationsCards(); | |
| 289 }); | 498 }); |
| 290 | 499 |
| 291 chrome.experimental.notification.onClicked.addListener( | 500 chrome.experimental.notification.onClicked.addListener( |
| 292 function(notificationId) { | 501 function(notificationId) { |
| 293 onNotificationClicked(notificationId, function(actionUrls) { | 502 onNotificationClicked(notificationId, function(actionUrls) { |
| 294 return actionUrls.messageUrl; | 503 return actionUrls.messageUrl; |
| 295 }); | 504 }); |
| 296 }); | 505 }); |
| 297 | 506 |
| 298 chrome.experimental.notification.onButtonClicked.addListener( | 507 chrome.experimental.notification.onButtonClicked.addListener( |
| 299 function(notificationId, buttonIndex) { | 508 function(notificationId, buttonIndex) { |
| 300 onNotificationClicked(notificationId, function(actionUrls) { | 509 onNotificationClicked(notificationId, function(actionUrls) { |
| 301 if (!Array.isArray(actionUrls.buttonUrls)) | 510 if (!Array.isArray(actionUrls.buttonUrls)) |
| 302 return undefined; | 511 return undefined; |
| 303 | 512 |
| 304 return actionUrls.buttonUrls[buttonIndex]; | 513 return actionUrls.buttonUrls[buttonIndex]; |
| 305 }); | 514 }); |
| 306 }); | 515 }); |
| 307 | 516 |
| 308 chrome.experimental.notification.onClosed.addListener(onNotificationClosed); | 517 chrome.experimental.notification.onClosed.addListener(onNotificationClosed); |
| OLD | NEW |