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

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, 10 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 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
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
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
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);
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