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 */ | |
| 58 var UPDATE_CARDS_TASK_NAME = 'update-cards'; | |
|
skare_
2013/02/22 20:53:39
enum?
/** @enum {string} */
var Task = {
UPDATE
vadimt
2013/02/28 02:23:22
Done.
| |
| 59 var DISMISS_CARD_TASK_NAME = 'dismiss-card'; | |
| 60 var CARD_CLICKED_TASK_NAME = 'card-clicked'; | |
| 61 | |
| 62 /** | |
| 63 * Name of the alarm that triggers updating the cards. | |
|
skare_
2013/02/22 20:53:39
probably don't need this comment
vadimt
2013/02/28 02:23:22
Done.
| |
| 64 */ | |
| 65 var UPDATE_ALARM_NAME = 'UPDATE'; | |
|
skare_
2013/02/22 20:53:39
any advantage to having the two alarm names in the
vadimt
2013/02/28 02:23:22
We are speaking about UPDATE_NOTIFICATIONS_ALARM_N
skare_
2013/03/02 00:11:39
[ok]
yeah, figured this was the case; phrased as
vadimt
2013/03/06 21:04:38
Done.
| |
| 66 | |
| 54 var storage = chrome.storage.local; | 67 var storage = chrome.storage.local; |
| 55 | 68 |
| 56 /** | 69 /** |
| 70 * Check for internal errors. | |
|
skare_
2013/02/22 20:53:39
split into another file? Do we have access to a mo
vadimt
2013/02/28 02:23:22
This won't be an issue; I've added a comment to do
| |
| 71 * @param {boolean} condition Condition that must be true. | |
| 72 * @param {string} message Diagnostic message for the case when the condition is | |
| 73 * false. | |
| 74 */ | |
| 75 function assert(condition, message) { | |
| 76 // TODO(vadimt): Send UMAs instead of showing alert. | |
| 77 if (!condition) { | |
| 78 var errorText = 'ASSERT: ' + message; | |
| 79 console.error(errorText); | |
| 80 alert(errorText); | |
| 81 } | |
| 82 } | |
| 83 | |
| 84 /** | |
| 85 * Build the closure that manages tasks (mutually exclusive chains of events). | |
| 86 * @return {Object} Set of methods to control tasks. | |
| 87 */ | |
| 88 function getTaskManager() { | |
|
skare_
2013/02/22 20:53:39
you might want to just call this function TaskMana
vadimt
2013/02/28 02:23:22
Done.
| |
| 89 /** | |
| 90 * Name of the alarm that triggers the error saying that the event page cannot | |
| 91 * unload. | |
| 92 */ | |
| 93 var CANNOT_UNLOAD_ALARM_NAME = 'CANNOT-UNLOAD'; | |
| 94 | |
| 95 /** | |
| 96 * Maximal time we expect the event page to stay loaded after starting a task. | |
| 97 */ | |
| 98 var MAXIMUM_LOADED_TIME_MINUTES = 5; | |
| 99 | |
| 100 /** | |
| 101 * Queue of scheduled tasks. The first element, if present, corresponds to the | |
| 102 * currently running task. | |
| 103 */ | |
| 104 var queue = []; | |
| 105 | |
| 106 /** | |
| 107 * Name of the current step of the currently running task if present, | |
| 108 * otherwise, null. For diagnostics only. | |
| 109 * It's set when the task is started and before each asynchronous operation. | |
| 110 */ | |
| 111 var stepName = null; | |
| 112 | |
| 113 /** | |
| 114 * Start the first queued task. | |
| 115 */ | |
| 116 function startFirst() { | |
| 117 // Set alarm to verify that the event page will unload in a reasonable time. | |
| 118 chrome.alarms.create(CANNOT_UNLOAD_ALARM_NAME, | |
| 119 {delayInMinutes: MAXIMUM_LOADED_TIME_MINUTES}); | |
| 120 | |
| 121 // Start the oldest queued task, but not remove it from the queue. | |
| 122 assert(stepName == null, 'tasks.startFirst: stepName is not null'); | |
| 123 var entry = queue[0]; | |
| 124 stepName = entry.name + '-initial'; | |
| 125 entry.task(); | |
| 126 } | |
| 127 | |
| 128 /** | |
| 129 * Check if a new task can be added to a queue that contains an existing task. | |
| 130 * @param {string} newTaskName Name of the new task. | |
| 131 * @param {string} queuedTaskName Name of the task in the queue. | |
| 132 * @return {boolean} Whether the new task doesn't conflict with the existing | |
| 133 task. | |
|
skare_
2013/02/22 20:53:39
this line lost an asterisk
vadimt
2013/02/28 02:23:22
Done.
| |
| 134 */ | |
| 135 function isCompatible(newTaskName, queuedTaskName) { | |
| 136 if (newTaskName == UPDATE_CARDS_TASK_NAME && | |
| 137 queuedTaskName == UPDATE_CARDS_TASK_NAME) { | |
|
skare_
2013/02/22 20:53:39
one more space
vadimt
2013/02/28 02:23:22
Done.
| |
| 138 // If a card update is requested while an old update is still in the | |
| 139 // queue, we don't need the new update. | |
| 140 return false; | |
| 141 } | |
| 142 | |
| 143 return true; | |
| 144 } | |
| 145 | |
| 146 /** | |
| 147 * Check if a new task can be added to the task queue. | |
| 148 * @param {string} taskName Name of the new task. | |
| 149 * @return {boolean} Whether the new task can be added. | |
| 150 */ | |
| 151 function canQueue(taskName) { | |
| 152 for (var i = 0; i < queue.length; ++i) | |
| 153 if (!isCompatible(taskName, queue[i].name)) | |
| 154 return false; | |
| 155 | |
| 156 return true; | |
| 157 } | |
| 158 | |
| 159 /** | |
| 160 * Add a new task. If another task is not running, run the task immediately. | |
| 161 * If any task in the queue is not compatible with the task, ignore the new | |
| 162 * task. Otherwise, store the task for future execution. | |
| 163 * @param {string} taskName Name of the task. | |
| 164 * @param {function()} task Code of the task. | |
| 165 */ | |
| 166 function submit(taskName, task) { | |
| 167 if (!canQueue(taskName)) | |
| 168 return; | |
| 169 | |
| 170 queue.push({name: taskName, task: task}); | |
| 171 | |
| 172 if (queue.length == 1) { | |
| 173 startFirst(); | |
| 174 } | |
| 175 } | |
| 176 | |
| 177 /** | |
| 178 * Complete the current task and start the next queued task if available. | |
| 179 */ | |
| 180 function finish() { | |
| 181 assert(queue.length >= 1, 'tasks.finish: The task queue is empty.'); | |
| 182 queue.shift(); | |
| 183 stepName = null; | |
| 184 | |
| 185 if (queue.length >= 1) | |
| 186 startFirst(); | |
| 187 } | |
| 188 | |
| 189 /** | |
| 190 * Associate a name with the current step of the task. Used for diagnostics | |
| 191 * only. A task is a chain of asynchronous events; setStep should be called | |
| 192 * before starting any asynchronous operation. | |
| 193 * @param {string} step Name of new step. | |
| 194 */ | |
| 195 function setStep(step) { | |
| 196 stepName = step; | |
| 197 } | |
| 198 | |
| 199 chrome.alarms.onAlarm.addListener(function(alarm) { | |
| 200 if (alarm.name == CANNOT_UNLOAD_ALARM_NAME) { | |
| 201 // Error if the event page wasn't unloaded after a reasonable timeout | |
| 202 // since starting the last task. | |
| 203 // TODO(vadimt): Uncomment the assert once this bug is fixed: | |
| 204 // crbug.com/177563 | |
| 205 // assert(false, 'Event page didn\'t unload, queue = ' + | |
| 206 // JSON.stringify(tasks) + ', step = ' + stepName + ' (ignore this assert | |
| 207 // if devtools is attached).'); | |
| 208 } | |
| 209 }); | |
| 210 | |
| 211 chrome.runtime.onSuspend.addListener(function() { | |
| 212 chrome.alarms.clear(CANNOT_UNLOAD_ALARM_NAME); | |
| 213 assert(queue.length == 0 && stepName == null, | |
| 214 'Incomplete task when unloading event page, queue = ' + | |
| 215 JSON.stringify(queue) + ', step = ' + stepName); | |
| 216 }); | |
| 217 | |
| 218 return { | |
| 219 submit: submit, | |
| 220 setStep: setStep, | |
| 221 finish: finish | |
| 222 }; | |
| 223 } | |
| 224 | |
| 225 var tasks = getTaskManager(); | |
| 226 | |
| 227 /** | |
| 57 * Show a notification and remember information associated with it. | 228 * Show a notification and remember information associated with it. |
| 58 * @param {Object} card Google Now card represented as a set of parameters for | 229 * @param {Object} card Google Now card represented as a set of parameters for |
| 59 * showing a Chrome notification. | 230 * showing a Chrome notification. |
| 60 * @param {Object} notificationsUrlInfo Map from notification id to the | 231 * @param {Object} notificationsUrlInfo Map from notification id to the |
| 61 * notification's set of URLs. | 232 * notification's set of URLs. |
| 62 */ | 233 */ |
| 63 function createNotification(card, notificationsUrlInfo) { | 234 function createNotification(card, notificationsUrlInfo) { |
| 64 var notificationId = card.notificationId; | 235 var notificationId = card.notificationId; |
| 65 | 236 |
| 66 // Fill the information about button actions for the notification. This is a | 237 // Fill the information about button actions for the notification. This is a |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 106 if (!(cards instanceof Array)) { | 277 if (!(cards instanceof Array)) { |
| 107 // TODO(vadimt): Report errors to the user. | 278 // TODO(vadimt): Report errors to the user. |
| 108 return; | 279 return; |
| 109 } | 280 } |
| 110 | 281 |
| 111 if (typeof parsedResponse.expiration_timestamp_seconds != 'number') { | 282 if (typeof parsedResponse.expiration_timestamp_seconds != 'number') { |
| 112 // TODO(vadimt): Report errors to the user. | 283 // TODO(vadimt): Report errors to the user. |
| 113 return; | 284 return; |
| 114 } | 285 } |
| 115 | 286 |
| 287 tasks.setStep('parseAndShowNotificationCards-get-active-notifications'); | |
|
skare_
2013/02/22 20:53:39
so setStep is purely diagnostic information to kno
vadimt
2013/02/28 02:23:22
Yep. If a task quietly ends without calling finish
| |
| 116 storage.get('activeNotifications', function(items) { | 288 storage.get('activeNotifications', function(items) { |
| 117 // Mark existing notifications that received an update in this server | 289 // Mark existing notifications that received an update in this server |
| 118 // response. | 290 // response. |
| 119 for (var i = 0; i < cards.length; ++i) { | 291 for (var i = 0; i < cards.length; ++i) { |
| 120 var notificationId = cards[i].notificationId; | 292 var notificationId = cards[i].notificationId; |
| 121 if (notificationId in items.activeNotifications) | 293 if (notificationId in items.activeNotifications) |
| 122 items.activeNotifications[notificationId].hasUpdate = true; | 294 items.activeNotifications[notificationId].hasUpdate = true; |
| 123 } | 295 } |
| 124 | 296 |
| 125 // Delete notifications that didn't receive an update. | 297 // Delete notifications that didn't receive an update. |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 141 } | 313 } |
| 142 } | 314 } |
| 143 storage.set({activeNotifications: notificationsUrlInfo}); | 315 storage.set({activeNotifications: notificationsUrlInfo}); |
| 144 | 316 |
| 145 scheduleNextUpdate(parsedResponse.expiration_timestamp_seconds); | 317 scheduleNextUpdate(parsedResponse.expiration_timestamp_seconds); |
| 146 | 318 |
| 147 // Now that we got a valid response from the server, reset the retry period | 319 // Now that we got a valid response from the server, reset the retry period |
| 148 // to the initial value. This retry period will be used the next time we | 320 // to the initial value. This retry period will be used the next time we |
| 149 // fail to get the server-provided period. | 321 // fail to get the server-provided period. |
| 150 storage.set({retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS}); | 322 storage.set({retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS}); |
| 323 tasks.finish(); | |
| 151 }); | 324 }); |
| 152 } | 325 } |
| 153 | 326 |
| 154 /** | 327 /** |
| 155 * Request notification cards from the server. | 328 * Request notification cards from the server. |
| 156 * @param {string} requestParameters Query string for the request. | 329 * @param {string} requestParameters Query string for the request. |
| 157 */ | 330 */ |
| 158 function requestNotificationCards(requestParameters) { | 331 function requestNotificationCards(requestParameters) { |
| 159 // TODO(vadimt): Figure out how to send user's identity to the server. | 332 // TODO(vadimt): Figure out how to send user's identity to the server. |
| 160 var request = new XMLHttpRequest(); | 333 var request = new XMLHttpRequest(); |
| 161 | 334 |
| 162 request.responseType = 'text'; | 335 request.responseType = 'text'; |
| 163 request.onload = function(event) { | 336 request.onload = function(event) { |
| 164 if (request.status == HTTP_OK) | 337 if (request.status == HTTP_OK) |
| 165 parseAndShowNotificationCards(request.response); | 338 parseAndShowNotificationCards(request.response); |
| 339 else | |
| 340 tasks.finish(); | |
| 341 } | |
| 342 | |
| 343 request.onerror = function(event) { | |
| 344 tasks.finish(); | |
| 166 } | 345 } |
| 167 | 346 |
| 168 request.open( | 347 request.open( |
| 169 'GET', | 348 'GET', |
| 170 NOTIFICATION_CARDS_URL + '/notifications' + requestParameters, | 349 NOTIFICATION_CARDS_URL + '/notifications' + requestParameters, |
| 171 true); | 350 true); |
| 351 tasks.setStep('requestNotificationCards-send-request'); | |
| 172 request.send(); | 352 request.send(); |
| 173 } | 353 } |
| 174 | 354 |
| 175 /** | 355 /** |
| 176 * Request notification cards from the server when we have geolocation. | 356 * Request notification cards from the server when we have geolocation. |
| 177 * @param {Geoposition} position Location of this computer. | 357 * @param {Geoposition} position Location of this computer. |
| 178 */ | 358 */ |
| 179 function requestNotificationCardsWithLocation(position) { | 359 function requestNotificationCardsWithLocation(position) { |
| 180 // TODO(vadimt): Should we use 'q' as the parameter name? | 360 // TODO(vadimt): Should we use 'q' as the parameter name? |
| 181 var requestParameters = | 361 var requestParameters = |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 192 */ | 372 */ |
| 193 function requestNotificationCardsWithoutLocation(positionError) { | 373 function requestNotificationCardsWithoutLocation(positionError) { |
| 194 requestNotificationCards(''); | 374 requestNotificationCards(''); |
| 195 } | 375 } |
| 196 | 376 |
| 197 /** | 377 /** |
| 198 * Obtain new location; request and show notification cards based on this | 378 * Obtain new location; request and show notification cards based on this |
| 199 * location. | 379 * location. |
| 200 */ | 380 */ |
| 201 function updateNotificationsCards() { | 381 function updateNotificationsCards() { |
| 202 storage.get('retryDelaySeconds', function(items) { | 382 tasks.submit(UPDATE_CARDS_TASK_NAME, function() { |
| 203 // Immediately schedule the update after the current retry period. Then, | 383 tasks.setStep('updateNotificationsCards-get-retryDelaySeconds'); |
| 204 // we'll use update time from the server if available. | 384 storage.get('retryDelaySeconds', function(items) { |
| 205 scheduleNextUpdate(items.retryDelaySeconds); | 385 // Immediately schedule the update after the current retry period. Then, |
| 386 // we'll use update time from the server if available. | |
| 387 scheduleNextUpdate(items.retryDelaySeconds); | |
| 206 | 388 |
| 207 // TODO(vadimt): Consider interrupting waiting for the next update if we | 389 // TODO(vadimt): Consider interrupting waiting for the next update if we |
| 208 // detect that the network conditions have changed. Also, decide whether the | 390 // detect that the network conditions have changed. Also, decide whether |
| 209 // exponential backoff is needed both when we are offline and when there are | 391 // the exponential backoff is needed both when we are offline and when |
| 210 // failures on the server side. | 392 // there are failures on the server side. |
| 211 var newRetryDelaySeconds = | 393 var newRetryDelaySeconds = |
| 212 Math.min(items.retryDelaySeconds * 2 * (1 + 0.2 * Math.random()), | 394 Math.min(items.retryDelaySeconds * 2 * (1 + 0.2 * Math.random()), |
| 213 MAXIMUM_POLLING_PERIOD_SECONDS); | 395 MAXIMUM_POLLING_PERIOD_SECONDS); |
| 214 storage.set({retryDelaySeconds: newRetryDelaySeconds}); | 396 storage.set({retryDelaySeconds: newRetryDelaySeconds}); |
| 215 | 397 |
| 216 navigator.geolocation.getCurrentPosition( | 398 tasks.setStep('updateNotificationsCards-get-location'); |
| 217 requestNotificationCardsWithLocation, | 399 navigator.geolocation.getCurrentPosition( |
| 218 requestNotificationCardsWithoutLocation); | 400 requestNotificationCardsWithLocation, |
| 401 requestNotificationCardsWithoutLocation); | |
| 402 }); | |
| 219 }); | 403 }); |
| 220 } | 404 } |
| 221 | 405 |
| 222 /** | 406 /** |
| 223 * Opens URL corresponding to the clicked part of the notification. | 407 * Opens URL corresponding to the clicked part of the notification. |
| 224 * @param {string} notificationId Unique identifier of the notification. | 408 * @param {string} notificationId Unique identifier of the notification. |
| 225 * @param {string} area Name of the notification's clicked area. | 409 * @param {string} area Name of the notification's clicked area. |
| 226 */ | 410 */ |
| 227 function onNotificationClicked(notificationId, area) { | 411 function onNotificationClicked(notificationId, area) { |
| 228 storage.get('activeNotifications', function(items) { | 412 tasks.submit(CARD_CLICKED_TASK_NAME, function() { |
| 229 var notificationActions = items.activeNotifications[notificationId] || {}; | 413 tasks.setStep('onNotificationClicked-get-activeNotifications'); |
| 230 if (area in notificationActions) { | 414 storage.get('activeNotifications', function(items) { |
| 231 // Open URL specified for the clicked area. | 415 var notificationActions = items.activeNotifications[notificationId] || {}; |
| 232 // TODO(vadimt): Figure out whether to open link in a new tab etc. | 416 if (area in notificationActions) { |
| 233 chrome.windows.create({url: notificationActions[area]}); | 417 // Open URL specified for the clicked area. |
| 234 } | 418 // TODO(vadimt): Figure out whether to open link in a new tab etc. |
| 419 chrome.windows.create({url: notificationActions[area]}); | |
| 420 } | |
| 421 tasks.finish(); | |
| 422 }); | |
| 235 }); | 423 }); |
| 236 } | 424 } |
| 237 | 425 |
| 238 /** | 426 /** |
| 239 * Callback for chrome.experimental.notification.onClosed event. | 427 * Callback for chrome.experimental.notification.onClosed event. |
| 240 * @param {string} notificationId Unique identifier of the notification. | 428 * @param {string} notificationId Unique identifier of the notification. |
| 241 * @param {boolean} byUser Whether the notification was closed by the user. | 429 * @param {boolean} byUser Whether the notification was closed by the user. |
| 242 */ | 430 */ |
| 243 function onNotificationClosed(notificationId, byUser) { | 431 function onNotificationClosed(notificationId, byUser) { |
| 244 if (byUser) { | 432 if (byUser) { |
| 245 // TODO(vadimt): Analyze possible race conditions between request for cards | 433 tasks.submit(DISMISS_CARD_TASK_NAME, function() { |
| 246 // and dismissal. | 434 // Deleting the notification in case it was re-added while this task was |
| 247 // Send a dismiss request to the server. | 435 // waiting in the queue. |
| 248 var requestParameters = '?id=' + notificationId; | 436 chrome.experimental.notification.delete( |
| 249 var request = new XMLHttpRequest(); | 437 notificationId, |
| 250 request.responseType = 'text'; | 438 function(wasDeleted) {}); |
|
skare_
2013/02/22 20:53:39
unneeded param?
vadimt
2013/02/28 02:23:22
Done.
| |
| 251 // TODO(vadimt): If the request fails, for example, because there is no | 439 |
| 252 // internet connection, do retry with exponential backoff. | 440 // Send a dismiss request to the server. |
| 253 request.open( | 441 var requestParameters = '?id=' + notificationId; |
| 254 'GET', | 442 var request = new XMLHttpRequest(); |
| 255 NOTIFICATION_CARDS_URL + '/dismiss' + requestParameters, | 443 request.responseType = 'text'; |
| 256 true); | 444 request.onloadend = function(event) { |
| 257 request.send(); | 445 tasks.finish(); |
| 446 } | |
| 447 // TODO(vadimt): If the request fails, for example, because there is no | |
| 448 // internet connection, do retry with exponential backoff. | |
| 449 request.open( | |
| 450 'GET', | |
| 451 NOTIFICATION_CARDS_URL + '/dismiss' + requestParameters, | |
| 452 true); | |
| 453 tasks.setStep('onNotificationClosed-send-request'); | |
| 454 request.send(); | |
| 455 }); | |
| 258 } | 456 } |
| 259 } | 457 } |
| 260 | 458 |
| 261 /** | 459 /** |
| 262 * Schedule next update for notification cards. | 460 * Schedule next update for notification cards. |
| 263 * @param {int} delaySeconds Length of time in seconds after which the alarm | 461 * @param {int} delaySeconds Length of time in seconds after which the alarm |
| 264 * event should fire. | 462 * event should fire. |
| 265 */ | 463 */ |
| 266 function scheduleNextUpdate(delaySeconds) { | 464 function scheduleNextUpdate(delaySeconds) { |
| 267 // Schedule an alarm after the specified delay. 'periodInMinutes' is for the | 465 // Schedule an alarm after the specified delay. 'periodInMinutes' is for the |
| 268 // case when we fail to re-register the alarm. | 466 // case when we fail to re-register the alarm. |
| 269 chrome.alarms.create({ | 467 var alarmInfo = { |
| 270 delayInMinutes: delaySeconds / 60, | 468 delayInMinutes: delaySeconds / 60, |
| 271 periodInMinutes: MAXIMUM_POLLING_PERIOD_SECONDS / 60 | 469 periodInMinutes: MAXIMUM_POLLING_PERIOD_SECONDS / 60 |
| 272 }); | 470 }; |
| 471 | |
| 472 chrome.alarms.create(UPDATE_ALARM_NAME, alarmInfo); | |
| 273 } | 473 } |
| 274 | 474 |
| 275 /** | 475 /** |
| 276 * Initialize the event page on install or on browser startup. | 476 * Initialize the event page on install or on browser startup. |
| 277 */ | 477 */ |
| 278 function initialize() { | 478 function initialize() { |
| 279 var initialStorage = { | 479 var initialStorage = { |
| 280 activeNotifications: {}, | 480 activeNotifications: {}, |
| 281 retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS | 481 retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS |
| 282 }; | 482 }; |
| 283 storage.set(initialStorage, updateNotificationsCards); | 483 storage.set(initialStorage); |
| 484 updateNotificationsCards(); | |
| 284 } | 485 } |
| 285 | 486 |
| 286 chrome.runtime.onInstalled.addListener(function(details) { | 487 chrome.runtime.onInstalled.addListener(function(details) { |
| 287 if (details.reason != 'chrome_update') | 488 if (details.reason != 'chrome_update') |
| 288 initialize(); | 489 initialize(); |
| 289 }); | 490 }); |
| 290 | 491 |
| 291 chrome.runtime.onStartup.addListener(function() { | 492 chrome.runtime.onStartup.addListener(function() { |
| 292 initialize(); | 493 initialize(); |
| 293 }); | 494 }); |
| 294 | 495 |
| 295 chrome.alarms.onAlarm.addListener(function(alarm) { | 496 chrome.alarms.onAlarm.addListener(function(alarm) { |
| 296 updateNotificationsCards(); | 497 if (alarm.name == UPDATE_ALARM_NAME) |
|
skare_
2013/02/22 20:53:39
though I don't think it's in the style guide, you
vadimt
2013/02/28 02:23:22
As far as I recall, if the condition or the statem
skare_
2013/03/02 00:11:39
yes that's correct. And I was a member of Team Omi
rgustafson
2013/03/04 22:31:27
"Do not use braces for single-line logic blocks."
vadimt
2013/03/06 21:04:38
See rgustafson's note.
vadimt
2013/03/06 21:04:38
Thanks rgustafson@!
| |
| 498 updateNotificationsCards(); | |
| 297 }); | 499 }); |
| 298 | 500 |
| 299 chrome.experimental.notification.onClicked.addListener( | 501 chrome.experimental.notification.onClicked.addListener( |
| 300 function(notificationId) { | 502 function(notificationId) { |
| 301 onNotificationClicked(notificationId, 'message'); | 503 onNotificationClicked(notificationId, 'message'); |
| 302 }); | 504 }); |
| 303 | 505 |
| 304 chrome.experimental.notification.onButtonClicked.addListener( | 506 chrome.experimental.notification.onButtonClicked.addListener( |
| 305 function(notificationId, buttonIndex) { | 507 function(notificationId, buttonIndex) { |
| 306 onNotificationClicked(notificationId, 'button' + buttonIndex); | 508 onNotificationClicked(notificationId, 'button' + buttonIndex); |
| 307 }); | 509 }); |
| 308 | 510 |
| 309 chrome.experimental.notification.onClosed.addListener(onNotificationClosed); | 511 chrome.experimental.notification.onClosed.addListener(onNotificationClosed); |
| OLD | NEW |