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