Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(392)

Side by Side Diff: chrome/browser/resources/google_now/background.js

Issue 12316075: Preventing race conditions in Google Now extension (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 internal and server errors. Collect UMAs on errors where 25 // TODO(vadimt): Report internal and server errors. Collect UMAs on errors where
26 // appropriate. Also consider logging errors or throwing exceptions. 26 // appropriate. Also consider logging errors or throwing exceptions.
27
28 // TODO(vadimt): Consider processing errors for all storage.set calls. 27 // TODO(vadimt): Consider processing errors for all storage.set calls.
28
29 // TODO(vadimt): Figure out the server name. Use it in the manifest and for 29 // TODO(vadimt): Figure out the server name. Use it in the manifest and for
30 // NOTIFICATION_CARDS_URL. Meanwhile, to use the feature, you need to manually 30 // NOTIFICATION_CARDS_URL. Meanwhile, to use the feature, you need to manually
31 // set the server name via local storage. 31 // set the server name via local storage.
32 /** 32 /**
33 * URL to retrieve notification cards. 33 * URL to retrieve notification cards.
34 */ 34 */
35 var NOTIFICATION_CARDS_URL = localStorage['server_url']; 35 var NOTIFICATION_CARDS_URL = localStorage['server_url'];
36 36
37 /** 37 /**
38 * 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
39 * code the server will send. 39 * code the server will send.
40 */ 40 */
41 var HTTP_OK = 200; 41 var HTTP_OK = 200;
42 42
43 /** 43 /**
44 * 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
45 * period from the server is not available. 45 * period from the server is not available.
46 */ 46 */
47 var INITIAL_POLLING_PERIOD_SECONDS = 300; // 5 minutes 47 var INITIAL_POLLING_PERIOD_SECONDS = 300; // 5 minutes
48 48
49 /** 49 /**
50 * 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
51 * period from the server is not available. 51 * period from the server is not available.
52 */ 52 */
53 var MAXIMUM_POLLING_PERIOD_SECONDS = 3600; // 1 hour 53 var MAXIMUM_POLLING_PERIOD_SECONDS = 3600; // 1 hour
54 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
55 var storage = chrome.storage.local; 67 var storage = chrome.storage.local;
56 68
57 /** 69 /////////////////////////////////////////////////////////////////////////
58 * Show a notification and remember information associated with it. 70 ////// ABSOLUTELY NOT FORGET TO MOVE ASSERT AND TASK MANAGER INTO SEPARATE FILE
71 ////// IN THIS CL !!!!!!!!!!!!!
72 ////////////////////////////////////////////////////////////////////////
73
74 /**
75 * Checks 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 verify(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);
86 }
87 }
88
89 /**
90 * Builds the object to manage tasks (mutually exclusive chains of events).
91 * @return {Object} Set of methods to manage 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.
108 * @type {Array.<Object.<TaskName, function()>>}
109 */
110 var queue = [];
111
112 /**
113 * Name of the current step of the currently running task if present,
114 * otherwise, null. For diagnostics only.
115 * It's set when the task is started and before each asynchronous operation.
116 */
117 var stepName = null;
118
119 /**
120 * Starts the first queued task.
121 */
122 function startFirst() {
123 verify(queue.length >= 1, 'startFirst: queue is empty');
124
125 // Set alarm to verify that the event page will unload in a reasonable time.
126 chrome.alarms.create(CANNOT_UNLOAD_ALARM_NAME,
127 {delayInMinutes: MAXIMUM_LOADED_TIME_MINUTES});
128
129 // Starts the oldest queued task, but doesn't remove it from the queue.
130 verify(stepName == null, 'tasks.startFirst: stepName is not null');
131 var entry = queue[0];
132 stepName = entry.name + '-initial';
133 entry.task();
134 }
135
136 /**
137 * Checks if a new task can't be added to a queue that contains an existing
138 * task.
139 * @param {string} newTaskName Name of the new task.
140 * @param {string} queuedTaskName Name of the task in the queue.
141 * @return {boolean} Whether the new task conflicts with the existing task.
142 */
143 function doConflict(newTaskName, queuedTaskName) {
rgustafson 2013/03/11 18:11:05 Things starting with do generally mean taking acti
vadimt 2013/03/11 21:31:53 Done.
144 if (newTaskName == TaskName.UPDATE_CARDS &&
145 queuedTaskName == TaskName.UPDATE_CARDS) {
146 // If a card update is requested while an old update is still in the
147 // queue, we don't need the new update.
148 return true;
149 }
150
151 return false;
152 }
153
154 /**
155 * Checks if a new task can be added to the task queue.
156 * @param {TaskName} taskName Name of the new task.
157 * @return {boolean} Whether the new task can be added.
158 */
159 function canQueue(taskName) {
160 for (var i = 0; i < queue.length; ++i)
161 if (doConflict(taskName, queue[i].name))
162 return false;
163
164 return true;
165 }
166
167 /**
168 * Adds a new task. If another task is not running, run the task immediately.
169 * If any task in the queue is not compatible with the task, ignore the new
170 * task. Otherwise, store the task for future execution.
skare_ 2013/03/11 19:46:26 silently ignoring the task will always be ok? seem
vadimt 2013/03/11 21:31:53 TaskManager::areConflicting() returns true if: (1)
171 * @param {TaskName} taskName Name of the task.
172 * @param {function()} task Code of the task.
173 */
174 function submit(taskName, task) {
175 if (!canQueue(taskName))
176 return;
177
178 queue.push({name: taskName, task: task});
179
180 if (queue.length == 1) {
181 startFirst();
182 }
183 }
184
185 /**
186 * Completes the current task and start the next queued task if available.
187 */
188 function finish() {
189 verify(queue.length >= 1, 'tasks.finish: The task queue is empty.');
190 queue.shift();
191 stepName = null;
192
193 if (queue.length >= 1)
194 startFirst();
195 }
196
197 /**
198 * Associates a name with the current step of the task. Used for diagnostics
199 * only. A task is a chain of asynchronous events; setStepName should be
200 * called before starting any asynchronous operation.
201 * @param {string} step Name of new step.
202 */
203 function setStepName(step) {
204 // TODO(vadimt): Pass UMA counters instead of step names.
205 stepName = step;
206 }
207
208 chrome.alarms.onAlarm.addListener(function(alarm) {
209 if (alarm.name == CANNOT_UNLOAD_ALARM_NAME) {
210 // Error if the event page wasn't unloaded after a reasonable timeout
211 // since starting the last task.
212 // TODO(vadimt): Uncomment the verify once this bug is fixed:
213 // crbug.com/177563
214 // verify(false, 'Event page didn\'t unload, queue = ' +
215 // JSON.stringify(tasks) + ', step = ' + stepName + ' (ignore this verify
216 // if devtools is attached).');
217 }
218 });
219
220 chrome.runtime.onSuspend.addListener(function() {
221 chrome.alarms.clear(CANNOT_UNLOAD_ALARM_NAME);
222 verify(queue.length == 0 && stepName == null,
223 'Incomplete task when unloading event page, queue = ' +
224 JSON.stringify(queue) + ', step = ' + stepName);
225 });
226
227 return {
228 submit: submit,
229 setStepName: setStepName,
230 finish: finish
231 };
232 }
233
234 var tasks = TaskManager();
235
236 /**
237 * Shows a notification and remember information associated with it.
skare_ 2013/03/11 19:53:07 tiny nit: s/remember/remembers
vadimt 2013/03/11 21:31:53 Done.
59 * @param {Object} card Google Now card represented as a set of parameters for 238 * @param {Object} card Google Now card represented as a set of parameters for
60 * showing a Chrome notification. 239 * showing a Chrome notification.
61 * @param {Object} notificationsUrlInfo Map from notification id to the 240 * @param {Object} notificationsUrlInfo Map from notification id to the
62 * notification's set of URLs. 241 * notification's set of URLs.
63 */ 242 */
64 function createNotification(card, notificationsUrlInfo) { 243 function createNotification(card, notificationsUrlInfo) {
65 // Create a notification or quietly update if it already exists. 244 // Create a notification or quietly update if it already exists.
66 // TODO(vadimt): Implement non-quiet updates. 245 // TODO(vadimt): Implement non-quiet updates.
67 chrome.notifications.create( 246 chrome.notifications.create(
68 card.notificationId, 247 card.notificationId,
69 card.notification, 248 card.notification,
70 function(assignedNotificationId) {}); 249 function() {});
71 250
72 notificationsUrlInfo[card.notificationId] = card.actionUrls; 251 notificationsUrlInfo[card.notificationId] = card.actionUrls;
73 } 252 }
74 253
75 /** 254 /**
76 * Parse JSON response from the notification server, show notifications and 255 * Parses JSON response from the notification server, show notifications and
77 * schedule next update. 256 * schedule next update.
78 * @param {string} response Server response. 257 * @param {string} response Server response.
79 */ 258 */
80 function parseAndShowNotificationCards(response) { 259 function parseAndShowNotificationCards(response) {
81 try { 260 try {
82 var parsedResponse = JSON.parse(response); 261 var parsedResponse = JSON.parse(response);
83 } catch (error) { 262 } catch (error) {
84 // TODO(vadimt): Report errors to the user. 263 // TODO(vadimt): Report errors to the user.
85 return; 264 return;
86 } 265 }
87 266
88 var cards = parsedResponse.cards; 267 var cards = parsedResponse.cards;
89 268
90 if (!(cards instanceof Array)) { 269 if (!(cards instanceof Array)) {
91 // TODO(vadimt): Report errors to the user. 270 // TODO(vadimt): Report errors to the user.
92 return; 271 return;
93 } 272 }
94 273
95 if (typeof parsedResponse.expiration_timestamp_seconds != 'number') { 274 if (typeof parsedResponse.expiration_timestamp_seconds != 'number') {
96 // TODO(vadimt): Report errors to the user. 275 // TODO(vadimt): Report errors to the user.
97 return; 276 return;
98 } 277 }
99 278
279 tasks.setStepName('parseAndShowNotificationCards-get-active-notifications');
100 storage.get('activeNotifications', function(items) { 280 storage.get('activeNotifications', function(items) {
101 // Mark existing notifications that received an update in this server 281 // Mark existing notifications that received an update in this server
102 // response. 282 // response.
103 for (var i = 0; i < cards.length; ++i) { 283 for (var i = 0; i < cards.length; ++i) {
104 var notificationId = cards[i].notificationId; 284 var notificationId = cards[i].notificationId;
105 if (notificationId in items.activeNotifications) 285 if (notificationId in items.activeNotifications)
106 items.activeNotifications[notificationId].hasUpdate = true; 286 items.activeNotifications[notificationId].hasUpdate = true;
107 } 287 }
108 288
109 // Delete notifications that didn't receive an update. 289 // Delete notifications that didn't receive an update.
110 for (var notificationId in items.activeNotifications) 290 for (var notificationId in items.activeNotifications)
111 if (items.activeNotifications.hasOwnProperty(notificationId) && 291 if (items.activeNotifications.hasOwnProperty(notificationId) &&
112 !items.activeNotifications[notificationId].hasUpdate) { 292 !items.activeNotifications[notificationId].hasUpdate) {
113 chrome.notifications.clear( 293 chrome.notifications.clear(
114 notificationId, 294 notificationId,
115 function(wasDeleted) {}); 295 function() {});
116 } 296 }
117 297
118 // Create/update notifications and store their new properties. 298 // Create/update notifications and store their new properties.
119 var notificationsUrlInfo = {}; 299 var notificationsUrlInfo = {};
120 300
121 for (var i = 0; i < cards.length; ++i) { 301 for (var i = 0; i < cards.length; ++i) {
122 try { 302 try {
123 createNotification(cards[i], notificationsUrlInfo); 303 createNotification(cards[i], notificationsUrlInfo);
124 } catch (error) { 304 } catch (error) {
125 // TODO(vadimt): Report errors to the user. 305 // TODO(vadimt): Report errors to the user.
126 } 306 }
127 } 307 }
128 storage.set({activeNotifications: notificationsUrlInfo}); 308 storage.set({activeNotifications: notificationsUrlInfo});
129 309
130 scheduleNextUpdate(parsedResponse.expiration_timestamp_seconds); 310 scheduleNextUpdate(parsedResponse.expiration_timestamp_seconds);
131 311
132 // Now that we got a valid response from the server, reset the retry period 312 // Now that we got a valid response from the server, reset the retry period
133 // to the initial value. This retry period will be used the next time we 313 // to the initial value. This retry period will be used the next time we
134 // fail to get the server-provided period. 314 // fail to get the server-provided period.
135 storage.set({retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS}); 315 storage.set({retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS});
316 tasks.finish();
136 }); 317 });
137 } 318 }
138 319
139 /** 320 /**
140 * Request notification cards from the server. 321 * Requests notification cards from the server.
141 * @param {string} requestParameters Query string for the request. 322 * @param {string} requestParameters Query string for the request.
142 */ 323 */
143 function requestNotificationCards(requestParameters) { 324 function requestNotificationCards(requestParameters) {
144 // TODO(vadimt): Figure out how to send user's identity to the server. 325 // TODO(vadimt): Figure out how to send user's identity to the server.
145 var request = new XMLHttpRequest(); 326 var request = new XMLHttpRequest();
146 327
147 request.responseType = 'text'; 328 request.responseType = 'text';
148 request.onload = function(event) { 329 request.onloadend = function() {
149 if (request.status == HTTP_OK) 330 if (request.status == HTTP_OK)
150 parseAndShowNotificationCards(request.response); 331 parseAndShowNotificationCards(request.response);
332 else
333 tasks.finish();
151 } 334 }
152 335
153 request.open( 336 request.open(
154 'GET', 337 'GET',
155 NOTIFICATION_CARDS_URL + '/notifications' + requestParameters, 338 NOTIFICATION_CARDS_URL + '/notifications' + requestParameters,
156 true); 339 true);
340 tasks.setStepName('requestNotificationCards-send-request');
157 request.send(); 341 request.send();
158 } 342 }
159 343
160 /** 344 /**
161 * Request notification cards from the server when we have geolocation. 345 * Requests notification cards from the server when we have geolocation.
162 * @param {Geoposition} position Location of this computer. 346 * @param {Geoposition} position Location of this computer.
163 */ 347 */
164 function requestNotificationCardsWithLocation(position) { 348 function requestNotificationCardsWithLocation(position) {
165 // TODO(vadimt): Should we use 'q' as the parameter name? 349 // TODO(vadimt): Should we use 'q' as the parameter name?
166 var requestParameters = 350 var requestParameters =
167 '?q=' + position.coords.latitude + 351 '?q=' + position.coords.latitude +
168 ',' + position.coords.longitude + 352 ',' + position.coords.longitude +
169 ',' + position.coords.accuracy; 353 ',' + position.coords.accuracy;
170 354
171 requestNotificationCards(requestParameters); 355 requestNotificationCards(requestParameters);
172 } 356 }
173 357
174 /** 358 /**
175 * Request notification cards from the server when we don't have geolocation. 359 * Requests notification cards from the server when we don't have geolocation.
176 * @param {PositionError} positionError Position error. 360 * @param {PositionError} positionError Position error.
177 */ 361 */
178 function requestNotificationCardsWithoutLocation(positionError) { 362 function requestNotificationCardsWithoutLocation(positionError) {
179 requestNotificationCards(''); 363 requestNotificationCards('');
180 } 364 }
181 365
182 /** 366 /**
183 * Obtain new location; request and show notification cards based on this 367 * Obtains new location; requests and shows notification cards based on this
184 * location. 368 * location.
185 */ 369 */
186 function updateNotificationsCards() { 370 function updateNotificationsCards() {
187 storage.get('retryDelaySeconds', function(items) { 371 tasks.submit(TaskName.UPDATE_CARDS, function() {
188 // Immediately schedule the update after the current retry period. Then, 372 tasks.setStepName('updateNotificationsCards-get-retryDelaySeconds');
189 // we'll use update time from the server if available. 373 storage.get('retryDelaySeconds', function(items) {
190 scheduleNextUpdate(items.retryDelaySeconds); 374 // Immediately schedule the update after the current retry period. Then,
191 375 // we'll use update time from the server if available.
192 // TODO(vadimt): Consider interrupting waiting for the next update if we 376 scheduleNextUpdate(items.retryDelaySeconds);
193 // detect that the network conditions have changed. Also, decide whether the 377
194 // exponential backoff is needed both when we are offline and when there are 378 // TODO(vadimt): Consider interrupting waiting for the next update if we
195 // failures on the server side. 379 // detect that the network conditions have changed. Also, decide whether
196 var newRetryDelaySeconds = 380 // the exponential backoff is needed both when we are offline and when
197 Math.min(items.retryDelaySeconds * 2 * (1 + 0.2 * Math.random()), 381 // there are failures on the server side.
198 MAXIMUM_POLLING_PERIOD_SECONDS); 382 var newRetryDelaySeconds =
199 storage.set({retryDelaySeconds: newRetryDelaySeconds}); 383 Math.min(items.retryDelaySeconds * 2 * (1 + 0.2 * Math.random()),
200 384 MAXIMUM_POLLING_PERIOD_SECONDS);
201 navigator.geolocation.getCurrentPosition( 385 storage.set({retryDelaySeconds: newRetryDelaySeconds});
202 requestNotificationCardsWithLocation, 386
203 requestNotificationCardsWithoutLocation); 387 tasks.setStepName('updateNotificationsCards-get-location');
388 navigator.geolocation.getCurrentPosition(
389 requestNotificationCardsWithLocation,
390 requestNotificationCardsWithoutLocation);
391 });
204 }); 392 });
205 } 393 }
206 394
207 /** 395 /**
208 * Opens URL corresponding to the clicked part of the notification. 396 * Opens URL corresponding to the clicked part of the notification.
209 * @param {string} notificationId Unique identifier of the notification. 397 * @param {string} notificationId Unique identifier of the notification.
210 * @param {function(Object): string} selector Function that extracts the url for 398 * @param {function(Object): string} selector Function that extracts the url for
211 * the clicked area from the button action URLs info. 399 * the clicked area from the button action URLs info.
212 */ 400 */
213 function onNotificationClicked(notificationId, selector) { 401 function onNotificationClicked(notificationId, selector) {
214 storage.get('activeNotifications', function(items) { 402 tasks.submit(TaskName.CARD_CLICKED, function() {
215 var actionUrls = items.activeNotifications[notificationId]; 403 tasks.setStepName('onNotificationClicked-get-activeNotifications');
216 if (typeof actionUrls != 'object') { 404 storage.get('activeNotifications', function(items) {
217 // TODO(vadimt): report an error. 405 var actionUrls = items.activeNotifications[notificationId];
218 return; 406 if (typeof actionUrls != 'object') {
219 } 407 // TODO(vadimt): report an error.
220 408 tasks.finish();
skare_ 2013/03/11 19:53:07 would it be useful in the future to have finish()
vadimt 2013/03/11 21:31:53 At this point, it doesn't seem so. finish() is to
221 var url = selector(actionUrls); 409 return;
222 410 }
223 if (typeof url != 'string') { 411
224 // TODO(vadimt): report an error. 412 var url = selector(actionUrls);
225 return; 413
226 } 414 if (typeof url != 'string') {
227 415 // TODO(vadimt): report an error.
228 chrome.tabs.create({url: url}); 416 tasks.finish();
417 return;
418 }
419
420 chrome.tabs.create({url: url});
421 tasks.finish();
422 });
229 }); 423 });
230 } 424 }
231 425
232 /** 426 /**
233 * Callback for chrome.notifications.onClosed event. 427 * Callback for chrome.notifications.onClosed event.
234 * @param {string} notificationId Unique identifier of the notification. 428 * @param {string} notificationId Unique identifier of the notification.
235 * @param {boolean} byUser Whether the notification was closed by the user. 429 * @param {boolean} byUser Whether the notification was closed by the user.
236 */ 430 */
237 function onNotificationClosed(notificationId, byUser) { 431 function onNotificationClosed(notificationId, byUser) {
238 if (byUser) { 432 if (byUser) {
239 // TODO(vadimt): Analyze possible race conditions between request for cards 433 tasks.submit(TaskName.DISMISS_CARD, function() {
240 // and dismissal. 434 // Deleting the notification in case it was re-added while this task was
241 // Send a dismiss request to the server. 435 // waiting in the queue.
242 var requestParameters = '?id=' + notificationId; 436 chrome.experimental.notification.clear(
243 var request = new XMLHttpRequest(); 437 notificationId,
244 request.responseType = 'text'; 438 function() {});
245 // TODO(vadimt): If the request fails, for example, because there is no 439
246 // internet connection, do retry with exponential backoff. 440 // Send a dismiss request to the server.
247 request.open( 441 var requestParameters = '?id=' + notificationId;
248 'GET', 442 var request = new XMLHttpRequest();
249 NOTIFICATION_CARDS_URL + '/dismiss' + requestParameters, 443 request.responseType = 'text';
250 true); 444 request.onloadend = function() {
251 request.send(); 445 tasks.finish();
252 } 446 }
253 } 447 // TODO(vadimt): If the request fails, for example, because there is no
254 448 // internet connection, do retry with exponential backoff.
255 /** 449 request.open(
256 * Schedule next update for notification cards. 450 'GET',
451 NOTIFICATION_CARDS_URL + '/dismiss' + requestParameters,
452 true);
453 tasks.setStepName('onNotificationClosed-send-request');
454 request.send();
455 });
456 }
457 }
458
459 /**
460 * Schedules next update for notification cards.
257 * @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
258 * event should fire. 462 * event should fire.
259 */ 463 */
260 function scheduleNextUpdate(delaySeconds) { 464 function scheduleNextUpdate(delaySeconds) {
261 // Schedule an alarm after the specified delay. 'periodInMinutes' is for the 465 // Schedule an alarm after the specified delay. 'periodInMinutes' is for the
262 // case when we fail to re-register the alarm. 466 // case when we fail to re-register the alarm.
263 chrome.alarms.create({ 467 var alarmInfo = {
264 delayInMinutes: delaySeconds / 60, 468 delayInMinutes: delaySeconds / 60,
265 periodInMinutes: MAXIMUM_POLLING_PERIOD_SECONDS / 60 469 periodInMinutes: MAXIMUM_POLLING_PERIOD_SECONDS / 60
266 }); 470 };
267 } 471
268 472 chrome.alarms.create(UPDATE_NOTIFICATIONS_ALARM_NAME, alarmInfo);
269 /** 473 }
270 * Initialize the event page on install or on browser startup. 474
475 /**
476 * Initializes the event page on install or on browser startup.
271 */ 477 */
272 function initialize() { 478 function initialize() {
273 var initialStorage = { 479 var initialStorage = {
274 activeNotifications: {}, 480 activeNotifications: {},
275 retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS 481 retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS
276 }; 482 };
277 storage.set(initialStorage, updateNotificationsCards); 483 storage.set(initialStorage);
484 updateNotificationsCards();
278 } 485 }
279 486
280 chrome.runtime.onInstalled.addListener(function(details) { 487 chrome.runtime.onInstalled.addListener(function(details) {
281 if (details.reason != 'chrome_update') 488 if (details.reason != 'chrome_update')
282 initialize(); 489 initialize();
283 }); 490 });
284 491
285 chrome.runtime.onStartup.addListener(function() { 492 chrome.runtime.onStartup.addListener(function() {
286 initialize(); 493 initialize();
287 }); 494 });
288 495
289 chrome.alarms.onAlarm.addListener(function(alarm) { 496 chrome.alarms.onAlarm.addListener(function(alarm) {
290 updateNotificationsCards(); 497 if (alarm.name == UPDATE_NOTIFICATIONS_ALARM_NAME)
498 updateNotificationsCards();
291 }); 499 });
292 500
293 chrome.notifications.onClicked.addListener( 501 chrome.notifications.onClicked.addListener(
294 function(notificationId) { 502 function(notificationId) {
295 onNotificationClicked(notificationId, function(actionUrls) { 503 onNotificationClicked(notificationId, function(actionUrls) {
296 return actionUrls.messageUrl; 504 return actionUrls.messageUrl;
297 }); 505 });
298 }); 506 });
299 507
300 chrome.notifications.onButtonClicked.addListener( 508 chrome.notifications.onButtonClicked.addListener(
301 function(notificationId, buttonIndex) { 509 function(notificationId, buttonIndex) {
302 onNotificationClicked(notificationId, function(actionUrls) { 510 onNotificationClicked(notificationId, function(actionUrls) {
303 if (!Array.isArray(actionUrls.buttonUrls)) 511 if (!Array.isArray(actionUrls.buttonUrls))
304 return undefined; 512 return undefined;
305 513
306 return actionUrls.buttonUrls[buttonIndex]; 514 return actionUrls.buttonUrls[buttonIndex];
307 }); 515 });
308 }); 516 });
309 517
310 chrome.notifications.onClosed.addListener(onNotificationClosed); 518 chrome.notifications.onClosed.addListener(onNotificationClosed);
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698