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

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

Issue 14743007: Retries with exponential backoff for dismissals (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: skare@ comments Created 7 years, 7 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
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'; 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
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
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
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
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
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
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);
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/resources/google_now/manifest.json » ('j') | chrome/browser/resources/google_now/manifest.json » ('J')

Powered by Google App Engine
This is Rietveld 408576698