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'; TODO(vadimt): Uncomment once crbug.com/237617 is fixed. | 5 // 'use strict'; TODO(vadimt): Uncomment once crbug.com/237617 is fixed. |
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. |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
46 * period from the server is not available. | 46 * period from the server is not available. |
47 */ | 47 */ |
48 var INITIAL_POLLING_PERIOD_SECONDS = 5 * 60; // 5 minutes | 48 var INITIAL_POLLING_PERIOD_SECONDS = 5 * 60; // 5 minutes |
49 | 49 |
50 /** | 50 /** |
51 * Maximal period for polling for Google Now Notifications cards to use when the | 51 * Maximal period for polling for Google Now Notifications cards to use when the |
52 * period from the server is not available. | 52 * period from the server is not available. |
53 */ | 53 */ |
54 var MAXIMUM_POLLING_PERIOD_SECONDS = 60 * 60; // 1 hour | 54 var MAXIMUM_POLLING_PERIOD_SECONDS = 60 * 60; // 1 hour |
55 | 55 |
56 var UPDATE_NOTIFICATIONS_ALARM_NAME = 'UPDATE'; | 56 /** |
57 * Initial period for retrying the server request for dismissing cards. | |
58 */ | |
59 var INITIAL_RETRY_DISMISS_PERIOD_SECONDS = 60; // 1 minute | |
57 | 60 |
58 /** | 61 /** |
59 * Period for retrying the server request for dismissing cards. | 62 * Maximum period for retrying the server request for dismissing cards. |
60 */ | 63 */ |
61 var RETRY_DISMISS_PERIOD_SECONDS = 60; // 1 minute | 64 var MAXIMUM_RETRY_DISMISS_PERIOD_SECONDS = 60 * 60; // 1 hour |
62 | |
63 var RETRY_DISMISS_ALARM_NAME = 'RETRY_DISMISS'; | |
64 | 65 |
65 /** | 66 /** |
66 * Time we keep dismissals after successful server dismiss requests. | 67 * Time we keep dismissals after successful server dismiss requests. |
67 */ | 68 */ |
68 var DISMISS_RETENTION_TIME_MS = 20 * 60 * 1000; // 20 minutes | 69 var DISMISS_RETENTION_TIME_MS = 20 * 60 * 1000; // 20 minutes |
69 | 70 |
70 var storage = chrome.storage.local; | |
71 | |
72 /** | 71 /** |
73 * Names for tasks that can be created by the extension. | 72 * Names for tasks that can be created by the extension. |
74 */ | 73 */ |
75 var UPDATE_CARDS_TASK_NAME = 'update-cards'; | 74 var UPDATE_CARDS_TASK_NAME = 'update-cards'; |
76 var DISMISS_CARD_TASK_NAME = 'dismiss-card'; | 75 var DISMISS_CARD_TASK_NAME = 'dismiss-card'; |
77 var CARD_CLICKED_TASK_NAME = 'card-clicked'; | 76 var CARD_CLICKED_TASK_NAME = 'card-clicked'; |
78 var RETRY_DISMISS_TASK_NAME = 'retry-dismiss'; | 77 var RETRY_DISMISS_TASK_NAME = 'retry-dismiss'; |
79 | 78 |
80 var LOCATION_WATCH_NAME = 'location-watch'; | 79 var LOCATION_WATCH_NAME = 'location-watch'; |
81 | 80 |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
114 tasks.instrumentApiFunction(chrome.notifications, 'update', 2); | 113 tasks.instrumentApiFunction(chrome.notifications, 'update', 2); |
115 tasks.instrumentApiFunction( | 114 tasks.instrumentApiFunction( |
116 chrome.notifications.onButtonClicked, 'addListener', 0); | 115 chrome.notifications.onButtonClicked, 'addListener', 0); |
117 tasks.instrumentApiFunction(chrome.notifications.onClicked, 'addListener', 0); | 116 tasks.instrumentApiFunction(chrome.notifications.onClicked, 'addListener', 0); |
118 tasks.instrumentApiFunction(chrome.notifications.onClosed, 'addListener', 0); | 117 tasks.instrumentApiFunction(chrome.notifications.onClosed, 'addListener', 0); |
119 tasks.instrumentApiFunction(chrome.runtime.onInstalled, 'addListener', 0); | 118 tasks.instrumentApiFunction(chrome.runtime.onInstalled, 'addListener', 0); |
120 tasks.instrumentApiFunction(chrome.runtime.onStartup, 'addListener', 0); | 119 tasks.instrumentApiFunction(chrome.runtime.onStartup, 'addListener', 0); |
121 tasks.instrumentApiFunction(chrome.tabs, 'create', 1); | 120 tasks.instrumentApiFunction(chrome.tabs, 'create', 1); |
122 tasks.instrumentApiFunction(storage, 'get', 1); | 121 tasks.instrumentApiFunction(storage, 'get', 1); |
123 | 122 |
123 var updateCardsAttempts = buildAttemptManager( | |
124 'cards-update', | |
125 requestLocation, | |
126 INITIAL_POLLING_PERIOD_SECONDS, | |
127 MAXIMUM_POLLING_PERIOD_SECONDS); | |
128 var dismissalAttempts = buildAttemptManager( | |
129 'dismiss', | |
130 retryPendingDismissals, | |
131 INITIAL_RETRY_DISMISS_PERIOD_SECONDS, | |
132 MAXIMUM_RETRY_DISMISS_PERIOD_SECONDS); | |
133 | |
124 /** | 134 /** |
125 * Diagnostic event identifier. | 135 * Diagnostic event identifier. |
126 * @enum {number} | 136 * @enum {number} |
127 */ | 137 */ |
128 var DiagnosticEvent = { | 138 var DiagnosticEvent = { |
129 REQUEST_FOR_CARDS_TOTAL: 0, | 139 REQUEST_FOR_CARDS_TOTAL: 0, |
130 REQUEST_FOR_CARDS_SUCCESS: 1, | 140 REQUEST_FOR_CARDS_SUCCESS: 1, |
131 CARDS_PARSE_SUCCESS: 2, | 141 CARDS_PARSE_SUCCESS: 2, |
132 DISMISS_REQUEST_TOTAL: 3, | 142 DISMISS_REQUEST_TOTAL: 3, |
133 DISMISS_REQUEST_SUCCESS: 4, | 143 DISMISS_REQUEST_SUCCESS: 4, |
(...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
286 for (var i = 0; i < cards.length; ++i) { | 296 for (var i = 0; i < cards.length; ++i) { |
287 var card = cards[i]; | 297 var card = cards[i]; |
288 if (!(card.notificationId in updatedRecentDismissals)) { | 298 if (!(card.notificationId in updatedRecentDismissals)) { |
289 var activeNotification = items.activeNotifications[card.notificationId]; | 299 var activeNotification = items.activeNotifications[card.notificationId]; |
290 showNotification(card, | 300 showNotification(card, |
291 notificationsData, | 301 notificationsData, |
292 activeNotification && activeNotification.version); | 302 activeNotification && activeNotification.version); |
293 } | 303 } |
294 } | 304 } |
295 | 305 |
296 scheduleNextUpdate(parsedResponse.expiration_timestamp_seconds); | 306 updateCardsAttempts.start(parsedResponse.expiration_timestamp_seconds); |
skare_
2013/05/14 02:03:52
just as an opinion as a first-time reader, name fo
vadimt
2013/05/14 18:47:50
'Request' has connotations with a single network r
| |
297 | 307 |
298 // Now that we got a valid response from the server, reset the retry period | |
299 // to the initial value. This retry period will be used the next time we | |
300 // fail to get the server-provided period. | |
301 storage.set({ | 308 storage.set({ |
302 retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS, | |
303 activeNotifications: notificationsData, | 309 activeNotifications: notificationsData, |
304 recentDismissals: updatedRecentDismissals | 310 recentDismissals: updatedRecentDismissals |
305 }); | 311 }); |
306 callback(); | 312 callback(); |
307 }); | 313 }); |
308 } | 314 } |
309 | 315 |
310 /** | 316 /** |
311 * Requests notification cards from the server. | 317 * Requests notification cards from the server. |
312 * @param {Location} position Location of this computer. | 318 * @param {Location} position Location of this computer. |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
365 /** | 371 /** |
366 * Obtains new location; requests and shows notification cards based on this | 372 * Obtains new location; requests and shows notification cards based on this |
367 * location. | 373 * location. |
368 * @param {Location} position Location of this computer. | 374 * @param {Location} position Location of this computer. |
369 */ | 375 */ |
370 function updateNotificationsCards(position) { | 376 function updateNotificationsCards(position) { |
371 console.log('updateNotificationsCards ' + JSON.stringify(position) + | 377 console.log('updateNotificationsCards ' + JSON.stringify(position) + |
372 ' @' + new Date()); | 378 ' @' + new Date()); |
373 tasks.add(UPDATE_CARDS_TASK_NAME, function(callback) { | 379 tasks.add(UPDATE_CARDS_TASK_NAME, function(callback) { |
374 console.log('updateNotificationsCards-task-begin'); | 380 console.log('updateNotificationsCards-task-begin'); |
375 tasks.debugSetStepName('updateNotificationsCards-get-retryDelaySeconds'); | 381 updateCardsAttempts.planForNext(function() { |
376 storage.get('retryDelaySeconds', function(items) { | |
377 console.log('updateNotificationsCards-get-retryDelaySeconds ' + | |
378 JSON.stringify(items)); | |
379 // Immediately schedule the update after the current retry period. Then, | |
380 // we'll use update time from the server if available. | |
381 scheduleNextUpdate(items.retryDelaySeconds); | |
382 | |
383 // TODO(vadimt): Consider interrupting waiting for the next update if we | |
384 // detect that the network conditions have changed. Also, decide whether | |
385 // the exponential backoff is needed both when we are offline and when | |
386 // there are failures on the server side. | |
387 var newRetryDelaySeconds = | |
388 Math.min(items.retryDelaySeconds * 2 * (1 + 0.2 * Math.random()), | |
389 MAXIMUM_POLLING_PERIOD_SECONDS); | |
390 storage.set({retryDelaySeconds: newRetryDelaySeconds}); | |
391 | |
392 processPendingDismissals(function(success) { | 382 processPendingDismissals(function(success) { |
393 if (success) { | 383 if (success) { |
394 // The cards are requested only if there are no unsent dismissals. | 384 // The cards are requested only if there are no unsent dismissals. |
395 requestNotificationCards(position, callback); | 385 requestNotificationCards(position, callback); |
396 } else { | 386 } else { |
397 callback(); | 387 callback(); |
398 } | 388 } |
399 }); | 389 }); |
400 }); | 390 }); |
401 }); | 391 }); |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
453 storage.set({ | 443 storage.set({ |
454 pendingDismissals: items.pendingDismissals, | 444 pendingDismissals: items.pendingDismissals, |
455 recentDismissals: items.recentDismissals | 445 recentDismissals: items.recentDismissals |
456 }); | 446 }); |
457 } | 447 } |
458 callbackBoolean(success); | 448 callbackBoolean(success); |
459 } | 449 } |
460 | 450 |
461 function doProcessDismissals() { | 451 function doProcessDismissals() { |
462 if (items.pendingDismissals.length == 0) { | 452 if (items.pendingDismissals.length == 0) { |
463 chrome.alarms.clear(RETRY_DISMISS_ALARM_NAME); | 453 dismissalAttempts.stop(); |
464 onFinish(true); | 454 onFinish(true); |
465 return; | 455 return; |
466 } | 456 } |
467 | 457 |
468 // Send dismissal for the first card, and if successful, repeat | 458 // Send dismissal for the first card, and if successful, repeat |
469 // recursively with the rest. | 459 // recursively with the rest. |
470 var dismissal = items.pendingDismissals[0]; | 460 var dismissal = items.pendingDismissals[0]; |
471 requestCardDismissal( | 461 requestCardDismissal( |
472 dismissal.notificationId, dismissal.time, function(success) { | 462 dismissal.notificationId, dismissal.time, function(success) { |
473 if (success) { | 463 if (success) { |
474 dismissalsChanged = true; | 464 dismissalsChanged = true; |
475 items.pendingDismissals.splice(0, 1); | 465 items.pendingDismissals.splice(0, 1); |
476 items.recentDismissals[dismissal.notificationId] = Date.now(); | 466 items.recentDismissals[dismissal.notificationId] = Date.now(); |
477 doProcessDismissals(); | 467 doProcessDismissals(); |
478 } else { | 468 } else { |
479 onFinish(false); | 469 onFinish(false); |
480 } | 470 } |
481 }); | 471 }); |
482 } | 472 } |
483 | 473 |
484 doProcessDismissals(); | 474 doProcessDismissals(); |
485 }); | 475 }); |
486 } | 476 } |
487 | 477 |
488 /** | 478 /** |
489 * Submits a task to send pending dismissals. | 479 * Submits a task to send pending dismissals. |
490 */ | 480 */ |
491 function retryPendingDismissals() { | 481 function retryPendingDismissals() { |
492 tasks.add(RETRY_DISMISS_TASK_NAME, function(callback) { | 482 tasks.add(RETRY_DISMISS_TASK_NAME, function(callback) { |
493 processPendingDismissals(function(success) { callback(); }); | 483 dismissalAttempts.planForNext(function() { |
484 processPendingDismissals(function(success) { callback(); }); | |
485 }); | |
494 }); | 486 }); |
495 } | 487 } |
496 | 488 |
497 /** | 489 /** |
498 * Opens URL corresponding to the clicked part of the notification. | 490 * Opens URL corresponding to the clicked part of the notification. |
499 * @param {string} notificationId Unique identifier of the notification. | 491 * @param {string} notificationId Unique identifier of the notification. |
500 * @param {function(Object): string} selector Function that extracts the url for | 492 * @param {function(Object): string} selector Function that extracts the url for |
501 * the clicked area from the button action URLs info. | 493 * the clicked area from the button action URLs info. |
502 */ | 494 */ |
503 function onNotificationClicked(notificationId, selector) { | 495 function onNotificationClicked(notificationId, selector) { |
(...skipping 27 matching lines...) Expand all Loading... | |
531 * @param {string} notificationId Unique identifier of the notification. | 523 * @param {string} notificationId Unique identifier of the notification. |
532 * @param {boolean} byUser Whether the notification was closed by the user. | 524 * @param {boolean} byUser Whether the notification was closed by the user. |
533 */ | 525 */ |
534 function onNotificationClosed(notificationId, byUser) { | 526 function onNotificationClosed(notificationId, byUser) { |
535 if (!byUser) | 527 if (!byUser) |
536 return; | 528 return; |
537 | 529 |
538 chrome.metricsPrivate.recordUserAction('GoogleNow.Dismissed'); | 530 chrome.metricsPrivate.recordUserAction('GoogleNow.Dismissed'); |
539 | 531 |
540 tasks.add(DISMISS_CARD_TASK_NAME, function(callback) { | 532 tasks.add(DISMISS_CARD_TASK_NAME, function(callback) { |
541 // Schedule retrying dismissing until all dismissals go through. | 533 dismissalAttempts.start(); |
542 // TODO(vadimt): Implement exponential backoff and unify it with getting | |
543 // cards. | |
544 var alarmInfo = { | |
545 delayInMinutes: RETRY_DISMISS_PERIOD_SECONDS / 60, | |
546 periodInMinutes: RETRY_DISMISS_PERIOD_SECONDS / 60 | |
547 }; | |
548 | |
549 chrome.alarms.create(RETRY_DISMISS_ALARM_NAME, alarmInfo); | |
550 | 534 |
551 // Deleting the notification in case it was re-added while this task was | 535 // Deleting the notification in case it was re-added while this task was |
552 // scheduled, waiting for execution. | 536 // scheduled, waiting for execution. |
553 chrome.notifications.clear( | 537 chrome.notifications.clear( |
554 notificationId, | 538 notificationId, |
555 function() {}); | 539 function() {}); |
556 | 540 |
557 tasks.debugSetStepName('onNotificationClosed-get-pendingDismissals'); | 541 tasks.debugSetStepName('onNotificationClosed-get-pendingDismissals'); |
558 storage.get('pendingDismissals', function(items) { | 542 storage.get('pendingDismissals', function(items) { |
559 var dismissal = { | 543 var dismissal = { |
560 notificationId: notificationId, | 544 notificationId: notificationId, |
561 time: Date.now() | 545 time: Date.now() |
562 }; | 546 }; |
563 items.pendingDismissals.push(dismissal); | 547 items.pendingDismissals.push(dismissal); |
564 storage.set({pendingDismissals: items.pendingDismissals}); | 548 storage.set({pendingDismissals: items.pendingDismissals}); |
565 processPendingDismissals(function(success) { callback(); }); | 549 processPendingDismissals(function(success) { callback(); }); |
566 }); | 550 }); |
567 }); | 551 }); |
568 } | 552 } |
569 | 553 |
570 /** | 554 /** |
571 * Schedules next update for notification cards. | |
572 * @param {int} delaySeconds Length of time in seconds after which the alarm | |
573 * event should fire. | |
574 */ | |
575 function scheduleNextUpdate(delaySeconds) { | |
576 console.log('scheduleNextUpdate ' + delaySeconds); | |
577 // Schedule an alarm after the specified delay. 'periodInMinutes' is for the | |
578 // case when we fail to re-register the alarm. | |
579 var alarmInfo = { | |
580 delayInMinutes: delaySeconds / 60, | |
581 periodInMinutes: MAXIMUM_POLLING_PERIOD_SECONDS / 60 | |
582 }; | |
583 | |
584 chrome.alarms.create(UPDATE_NOTIFICATIONS_ALARM_NAME, alarmInfo); | |
585 } | |
586 | |
587 /** | |
588 * Initializes the event page on install or on browser startup. | 555 * Initializes the event page on install or on browser startup. |
589 */ | 556 */ |
590 function initialize() { | 557 function initialize() { |
558 // Create an update timer for a case when for some reason location request | |
559 // gets stuck. | |
560 updateCardsAttempts.start(MAXIMUM_POLLING_PERIOD_SECONDS); | |
561 | |
591 var initialStorage = { | 562 var initialStorage = { |
592 activeNotifications: {}, | 563 activeNotifications: {}, |
593 recentDismissals: {}, | 564 recentDismissals: {} |
594 retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS | |
595 }; | 565 }; |
596 storage.set(initialStorage); | 566 storage.set(initialStorage); |
597 | 567 |
598 // Create an update timer for a case when for some reason location request | |
599 // gets stuck. | |
600 scheduleNextUpdate(MAXIMUM_POLLING_PERIOD_SECONDS); | |
601 | |
602 requestLocation(); | 568 requestLocation(); |
603 } | 569 } |
604 | 570 |
605 chrome.runtime.onInstalled.addListener(function(details) { | 571 chrome.runtime.onInstalled.addListener(function(details) { |
606 console.log('onInstalled ' + JSON.stringify(details)); | 572 console.log('onInstalled ' + JSON.stringify(details)); |
607 if (details.reason != 'chrome_update') { | 573 if (details.reason != 'chrome_update') { |
608 storage.set({pendingDismissals: []}); | 574 storage.set({pendingDismissals: []}); |
609 initialize(); | 575 initialize(); |
610 } | 576 } |
611 }); | 577 }); |
612 | 578 |
613 chrome.runtime.onStartup.addListener(function() { | 579 chrome.runtime.onStartup.addListener(function() { |
614 console.log('onStartup'); | 580 console.log('onStartup'); |
615 initialize(); | 581 initialize(); |
616 }); | 582 }); |
617 | 583 |
618 chrome.alarms.onAlarm.addListener(function(alarm) { | |
619 if (alarm.name == UPDATE_NOTIFICATIONS_ALARM_NAME) | |
620 requestLocation(); | |
621 else if (alarm.name == RETRY_DISMISS_ALARM_NAME) | |
622 retryPendingDismissals(); | |
623 }); | |
624 | |
625 chrome.notifications.onClicked.addListener( | 584 chrome.notifications.onClicked.addListener( |
626 function(notificationId) { | 585 function(notificationId) { |
627 chrome.metricsPrivate.recordUserAction('GoogleNow.MessageClicked'); | 586 chrome.metricsPrivate.recordUserAction('GoogleNow.MessageClicked'); |
628 onNotificationClicked(notificationId, function(actionUrls) { | 587 onNotificationClicked(notificationId, function(actionUrls) { |
629 return actionUrls.messageUrl; | 588 return actionUrls.messageUrl; |
630 }); | 589 }); |
631 }); | 590 }); |
632 | 591 |
633 chrome.notifications.onButtonClicked.addListener( | 592 chrome.notifications.onButtonClicked.addListener( |
634 function(notificationId, buttonIndex) { | 593 function(notificationId, buttonIndex) { |
635 chrome.metricsPrivate.recordUserAction( | 594 chrome.metricsPrivate.recordUserAction( |
636 'GoogleNow.ButtonClicked' + buttonIndex); | 595 'GoogleNow.ButtonClicked' + buttonIndex); |
637 onNotificationClicked(notificationId, function(actionUrls) { | 596 onNotificationClicked(notificationId, function(actionUrls) { |
638 if (!Array.isArray(actionUrls.buttonUrls)) | 597 if (!Array.isArray(actionUrls.buttonUrls)) |
639 return undefined; | 598 return undefined; |
640 | 599 |
641 return actionUrls.buttonUrls[buttonIndex]; | 600 return actionUrls.buttonUrls[buttonIndex]; |
642 }); | 601 }); |
643 }); | 602 }); |
644 | 603 |
645 chrome.notifications.onClosed.addListener(onNotificationClosed); | 604 chrome.notifications.onClosed.addListener(onNotificationClosed); |
646 | 605 |
647 chrome.location.onLocationUpdate.addListener(updateNotificationsCards); | 606 chrome.location.onLocationUpdate.addListener(updateNotificationsCards); |
OLD | NEW |