| 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'; | 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. |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 69 var INITIAL_RETRY_DISMISS_PERIOD_SECONDS = 60; // 1 minute | 69 var INITIAL_RETRY_DISMISS_PERIOD_SECONDS = 60; // 1 minute |
| 70 | 70 |
| 71 /** | 71 /** |
| 72 * Maximum period for retrying the server request for dismissing cards. | 72 * Maximum period for retrying the server request for dismissing cards. |
| 73 */ | 73 */ |
| 74 var MAXIMUM_RETRY_DISMISS_PERIOD_SECONDS = 60 * 60; // 1 hour | 74 var MAXIMUM_RETRY_DISMISS_PERIOD_SECONDS = 60 * 60; // 1 hour |
| 75 | 75 |
| 76 /** | 76 /** |
| 77 * Time we keep retrying dismissals. | 77 * Time we keep retrying dismissals. |
| 78 */ | 78 */ |
| 79 var MAXIMUM_DISMISSAL_AGE_MS = 24 * 60 * 60 * 1000; // 1 day | 79 var MAXIMUM_DISMISSAL_AGE_MS = 24 * 60 * 60 * 1000; // 1 day |
| 80 | 80 |
| 81 /** | 81 /** |
| 82 * Time we keep dismissals after successful server dismiss requests. | 82 * Time we keep dismissals after successful server dismiss requests. |
| 83 */ | 83 */ |
| 84 var DISMISS_RETENTION_TIME_MS = 20 * 60 * 1000; // 20 minutes | 84 var DISMISS_RETENTION_TIME_MS = 20 * 60 * 1000; // 20 minutes |
| 85 | 85 |
| 86 /** | 86 /** |
| 87 * Default period for checking whether the user is opted in to Google Now. | 87 * Default period for checking whether the user is opted in to Google Now. |
| 88 */ | 88 */ |
| 89 var DEFAULT_OPTIN_CHECK_PERIOD_SECONDS = 60 * 60 * 24 * 7; // 1 week | 89 var DEFAULT_OPTIN_CHECK_PERIOD_SECONDS = 60 * 60 * 24 * 7; // 1 week |
| 90 | 90 |
| 91 /** | 91 /** |
| 92 * URL to open when the user clicked on a link for the our notification | 92 * URL to open when the user clicked on a link for the our notification |
| 93 * settings. | 93 * settings. |
| 94 */ | 94 */ |
| 95 var SETTINGS_URL = 'https://support.google.com/chrome/?p=ib_google_now_welcome'; | 95 var SETTINGS_URL = 'https://support.google.com/chrome/?p=ib_google_now_welcome'; |
| 96 | 96 |
| 97 /** | 97 /** |
| 98 * GCM registration URL. | 98 * GCM registration URL. |
| 99 */ | 99 */ |
| (...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 217 'notifications.onPermissionLevelChanged.addListener', 0); | 217 'notifications.onPermissionLevelChanged.addListener', 0); |
| 218 wrapper.instrumentChromeApiFunction( | 218 wrapper.instrumentChromeApiFunction( |
| 219 'notifications.onShowSettings.addListener', 0); | 219 'notifications.onShowSettings.addListener', 0); |
| 220 wrapper.instrumentChromeApiFunction('permissions.contains', 1); | 220 wrapper.instrumentChromeApiFunction('permissions.contains', 1); |
| 221 wrapper.instrumentChromeApiFunction('runtime.onInstalled.addListener', 0); | 221 wrapper.instrumentChromeApiFunction('runtime.onInstalled.addListener', 0); |
| 222 wrapper.instrumentChromeApiFunction('runtime.onStartup.addListener', 0); | 222 wrapper.instrumentChromeApiFunction('runtime.onStartup.addListener', 0); |
| 223 wrapper.instrumentChromeApiFunction('storage.onChanged.addListener', 0); | 223 wrapper.instrumentChromeApiFunction('storage.onChanged.addListener', 0); |
| 224 wrapper.instrumentChromeApiFunction('tabs.create', 1); | 224 wrapper.instrumentChromeApiFunction('tabs.create', 1); |
| 225 | 225 |
| 226 var updateCardsAttempts = buildAttemptManager( | 226 var updateCardsAttempts = buildAttemptManager( |
| 227 'cards-update', | 227 'cards-update', requestCards, INITIAL_POLLING_PERIOD_SECONDS, |
| 228 requestCards, | |
| 229 INITIAL_POLLING_PERIOD_SECONDS, | |
| 230 MAXIMUM_POLLING_PERIOD_SECONDS); | 228 MAXIMUM_POLLING_PERIOD_SECONDS); |
| 231 var optInPollAttempts = buildAttemptManager( | 229 var optInPollAttempts = buildAttemptManager( |
| 232 'optin', | 230 'optin', pollOptedInNoImmediateRecheck, INITIAL_POLLING_PERIOD_SECONDS, |
| 233 pollOptedInNoImmediateRecheck, | |
| 234 INITIAL_POLLING_PERIOD_SECONDS, | |
| 235 MAXIMUM_POLLING_PERIOD_SECONDS); | 231 MAXIMUM_POLLING_PERIOD_SECONDS); |
| 236 var optInRecheckAttempts = buildAttemptManager( | 232 var optInRecheckAttempts = buildAttemptManager( |
| 237 'optin-recheck', | 233 'optin-recheck', pollOptedInWithRecheck, |
| 238 pollOptedInWithRecheck, | 234 INITIAL_OPTIN_RECHECK_PERIOD_SECONDS, MAXIMUM_OPTIN_RECHECK_PERIOD_SECONDS); |
| 239 INITIAL_OPTIN_RECHECK_PERIOD_SECONDS, | |
| 240 MAXIMUM_OPTIN_RECHECK_PERIOD_SECONDS); | |
| 241 var dismissalAttempts = buildAttemptManager( | 235 var dismissalAttempts = buildAttemptManager( |
| 242 'dismiss', | 236 'dismiss', retryPendingDismissals, INITIAL_RETRY_DISMISS_PERIOD_SECONDS, |
| 243 retryPendingDismissals, | |
| 244 INITIAL_RETRY_DISMISS_PERIOD_SECONDS, | |
| 245 MAXIMUM_RETRY_DISMISS_PERIOD_SECONDS); | 237 MAXIMUM_RETRY_DISMISS_PERIOD_SECONDS); |
| 246 var cardSet = buildCardSet(); | 238 var cardSet = buildCardSet(); |
| 247 | 239 |
| 248 var authenticationManager = buildAuthenticationManager(); | 240 var authenticationManager = buildAuthenticationManager(); |
| 249 | 241 |
| 250 /** | 242 /** |
| 251 * Google Now UMA event identifier. | 243 * Google Now UMA event identifier. |
| 252 * @enum {number} | 244 * @enum {number} |
| 253 */ | 245 */ |
| 254 var GoogleNowEvent = { | 246 var GoogleNowEvent = { |
| (...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 361 * called when each card is shown. | 353 * called when each card is shown. |
| 362 * @return {Promise} A promise to show the notification groups as cards. | 354 * @return {Promise} A promise to show the notification groups as cards. |
| 363 */ | 355 */ |
| 364 function showNotificationGroups(notificationGroups, opt_onCardShown) { | 356 function showNotificationGroups(notificationGroups, opt_onCardShown) { |
| 365 /** @type {Object<ChromeNotificationId, CombinedCard>} */ | 357 /** @type {Object<ChromeNotificationId, CombinedCard>} */ |
| 366 var cards = combineCardsFromGroups(notificationGroups); | 358 var cards = combineCardsFromGroups(notificationGroups); |
| 367 console.log('showNotificationGroups ' + JSON.stringify(cards)); | 359 console.log('showNotificationGroups ' + JSON.stringify(cards)); |
| 368 | 360 |
| 369 return new Promise(function(resolve) { | 361 return new Promise(function(resolve) { |
| 370 instrumented.notifications.getAll(function(notifications) { | 362 instrumented.notifications.getAll(function(notifications) { |
| 371 console.log('showNotificationGroups-getAll ' + | 363 console.log( |
| 372 JSON.stringify(notifications)); | 364 'showNotificationGroups-getAll ' + JSON.stringify(notifications)); |
| 373 notifications = notifications || {}; | 365 notifications = notifications || {}; |
| 374 | 366 |
| 375 // Mark notifications that didn't receive an update as having received | 367 // Mark notifications that didn't receive an update as having received |
| 376 // an empty update. | 368 // an empty update. |
| 377 for (var chromeNotificationId in notifications) { | 369 for (var chromeNotificationId in notifications) { |
| 378 cards[chromeNotificationId] = cards[chromeNotificationId] || []; | 370 cards[chromeNotificationId] = cards[chromeNotificationId] || []; |
| 379 } | 371 } |
| 380 | 372 |
| 381 /** @type {Object<ChromeNotificationId, NotificationDataEntry>} */ | 373 /** @type {Object<ChromeNotificationId, NotificationDataEntry>} */ |
| 382 var notificationsData = {}; | 374 var notificationsData = {}; |
| 383 | 375 |
| 384 // Create/update/delete notifications. | 376 // Create/update/delete notifications. |
| 385 for (var chromeNotificationId in cards) { | 377 for (var chromeNotificationId in cards) { |
| 386 notificationsData[chromeNotificationId] = cardSet.update( | 378 notificationsData[chromeNotificationId] = cardSet.update( |
| 387 chromeNotificationId, | 379 chromeNotificationId, cards[chromeNotificationId], |
| 388 cards[chromeNotificationId], | 380 notificationGroups, opt_onCardShown); |
| 389 notificationGroups, | |
| 390 opt_onCardShown); | |
| 391 } | 381 } |
| 392 chrome.storage.local.set({notificationsData: notificationsData}); | 382 chrome.storage.local.set({notificationsData: notificationsData}); |
| 393 resolve(); | 383 resolve(); |
| 394 }); | 384 }); |
| 395 }); | 385 }); |
| 396 } | 386 } |
| 397 | 387 |
| 398 /** | 388 /** |
| 399 * Removes all cards and card state on Google Now close down. | 389 * Removes all cards and card state on Google Now close down. |
| 400 */ | 390 */ |
| (...skipping 22 matching lines...) Expand all Loading... |
| 423 */ | 413 */ |
| 424 function combineGroup(combinedCards, storedGroup) { | 414 function combineGroup(combinedCards, storedGroup) { |
| 425 for (var i = 0; i < storedGroup.cards.length; i++) { | 415 for (var i = 0; i < storedGroup.cards.length; i++) { |
| 426 /** @type {ReceivedNotification} */ | 416 /** @type {ReceivedNotification} */ |
| 427 var receivedNotification = storedGroup.cards[i]; | 417 var receivedNotification = storedGroup.cards[i]; |
| 428 | 418 |
| 429 /** @type {UncombinedNotification} */ | 419 /** @type {UncombinedNotification} */ |
| 430 var uncombinedNotification = { | 420 var uncombinedNotification = { |
| 431 receivedNotification: receivedNotification, | 421 receivedNotification: receivedNotification, |
| 432 showTime: receivedNotification.trigger.showTimeSec && | 422 showTime: receivedNotification.trigger.showTimeSec && |
| 433 (storedGroup.cardsTimestamp + | 423 (storedGroup.cardsTimestamp + |
| 434 receivedNotification.trigger.showTimeSec * MS_IN_SECOND), | 424 receivedNotification.trigger.showTimeSec * MS_IN_SECOND), |
| 435 hideTime: storedGroup.cardsTimestamp + | 425 hideTime: storedGroup.cardsTimestamp + |
| 436 receivedNotification.trigger.hideTimeSec * MS_IN_SECOND | 426 receivedNotification.trigger.hideTimeSec * MS_IN_SECOND |
| 437 }; | 427 }; |
| 438 | 428 |
| 439 var combinedCard = | 429 var combinedCard = |
| 440 combinedCards[receivedNotification.chromeNotificationId] || []; | 430 combinedCards[receivedNotification.chromeNotificationId] || []; |
| 441 combinedCard.push(uncombinedNotification); | 431 combinedCard.push(uncombinedNotification); |
| 442 combinedCards[receivedNotification.chromeNotificationId] = combinedCard; | 432 combinedCards[receivedNotification.chromeNotificationId] = combinedCard; |
| 443 } | 433 } |
| 444 } | 434 } |
| 445 | 435 |
| 446 /** | 436 /** |
| 447 * Calculates the soonest poll time from a map of groups as an absolute time. | 437 * Calculates the soonest poll time from a map of groups as an absolute time. |
| 448 * @param {Object<StoredNotificationGroup>} groups Map from group name to group | 438 * @param {Object<StoredNotificationGroup>} groups Map from group name to group |
| 449 * information. | 439 * information. |
| 450 * @return {number} The next poll time based off of the groups. | 440 * @return {number} The next poll time based off of the groups. |
| 451 */ | 441 */ |
| 452 function calculateNextPollTimeMilliseconds(groups) { | 442 function calculateNextPollTimeMilliseconds(groups) { |
| 453 var nextPollTime = null; | 443 var nextPollTime = null; |
| 454 | 444 |
| 455 for (var groupName in groups) { | 445 for (var groupName in groups) { |
| 456 var group = groups[groupName]; | 446 var group = groups[groupName]; |
| 457 if (group.nextPollTime !== undefined) { | 447 if (group.nextPollTime !== undefined) { |
| 458 nextPollTime = nextPollTime == null ? | 448 nextPollTime = nextPollTime == null ? |
| 459 group.nextPollTime : Math.min(group.nextPollTime, nextPollTime); | 449 group.nextPollTime : |
| 450 Math.min(group.nextPollTime, nextPollTime); |
| 460 } | 451 } |
| 461 } | 452 } |
| 462 | 453 |
| 463 // At least one of the groups must have nextPollTime. | 454 // At least one of the groups must have nextPollTime. |
| 464 verify(nextPollTime != null, 'calculateNextPollTime: nextPollTime is null'); | 455 verify(nextPollTime != null, 'calculateNextPollTime: nextPollTime is null'); |
| 465 return nextPollTime; | 456 return nextPollTime; |
| 466 } | 457 } |
| 467 | 458 |
| 468 /** | 459 /** |
| 469 * Schedules next cards poll. | 460 * Schedules next cards poll. |
| 470 * @param {Object<StoredNotificationGroup>} groups Map from group name to group | 461 * @param {Object<StoredNotificationGroup>} groups Map from group name to group |
| 471 * information. | 462 * information. |
| 472 */ | 463 */ |
| 473 function scheduleNextCardsPoll(groups) { | 464 function scheduleNextCardsPoll(groups) { |
| 474 var nextPollTimeMs = calculateNextPollTimeMilliseconds(groups); | 465 var nextPollTimeMs = calculateNextPollTimeMilliseconds(groups); |
| 475 | 466 |
| 476 var nextPollDelaySeconds = Math.max( | 467 var nextPollDelaySeconds = Math.max( |
| 477 (nextPollTimeMs - Date.now()) / MS_IN_SECOND, | 468 (nextPollTimeMs - Date.now()) / MS_IN_SECOND, |
| 478 MINIMUM_POLLING_PERIOD_SECONDS); | 469 MINIMUM_POLLING_PERIOD_SECONDS); |
| 479 updateCardsAttempts.start(nextPollDelaySeconds); | 470 updateCardsAttempts.start(nextPollDelaySeconds); |
| 480 } | 471 } |
| 481 | 472 |
| 482 /** | 473 /** |
| 483 * Schedules the next opt-in check poll. | 474 * Schedules the next opt-in check poll. |
| 484 */ | 475 */ |
| 485 function scheduleOptInCheckPoll() { | 476 function scheduleOptInCheckPoll() { |
| 486 instrumented.metricsPrivate.getVariationParams( | 477 instrumented.metricsPrivate.getVariationParams('GoogleNow', function(params) { |
| 487 'GoogleNow', function(params) { | |
| 488 var optinPollPeriodSeconds = | 478 var optinPollPeriodSeconds = |
| 489 parseInt(params && params.optinPollPeriodSeconds, 10) || | 479 parseInt(params && params.optinPollPeriodSeconds, 10) || |
| 490 DEFAULT_OPTIN_CHECK_PERIOD_SECONDS; | 480 DEFAULT_OPTIN_CHECK_PERIOD_SECONDS; |
| 491 optInPollAttempts.start(optinPollPeriodSeconds); | 481 optInPollAttempts.start(optinPollPeriodSeconds); |
| 492 }); | 482 }); |
| 493 } | 483 } |
| 494 | 484 |
| 495 /** | 485 /** |
| 496 * Combines notification groups into a set of Chrome notifications. | 486 * Combines notification groups into a set of Chrome notifications. |
| 497 * @param {Object<StoredNotificationGroup>} notificationGroups Map from group | 487 * @param {Object<StoredNotificationGroup>} notificationGroups Map from group |
| (...skipping 22 matching lines...) Expand all Loading... |
| 520 | 510 |
| 521 if (response.googleNowDisabled) { | 511 if (response.googleNowDisabled) { |
| 522 chrome.storage.local.set({googleNowEnabled: false}); | 512 chrome.storage.local.set({googleNowEnabled: false}); |
| 523 // Stop processing now. The state change will clear the cards. | 513 // Stop processing now. The state change will clear the cards. |
| 524 return Promise.reject(); | 514 return Promise.reject(); |
| 525 } | 515 } |
| 526 | 516 |
| 527 var receivedGroups = response.groups; | 517 var receivedGroups = response.groups; |
| 528 | 518 |
| 529 return fillFromChromeLocalStorage({ | 519 return fillFromChromeLocalStorage({ |
| 530 /** @type {Object<StoredNotificationGroup>} */ | 520 /** @type {Object<StoredNotificationGroup>} */ |
| 531 notificationGroups: {}, | 521 notificationGroups: {}, |
| 532 /** @type {Object<ServerNotificationId, number>} */ | 522 /** @type {Object<ServerNotificationId, number>} */ |
| 533 recentDismissals: {} | 523 recentDismissals: {} |
| 534 }).then(function(items) { | 524 }) |
| 535 console.log('processServerResponse-get ' + JSON.stringify(items)); | 525 .then(function(items) { |
| 526 console.log('processServerResponse-get ' + JSON.stringify(items)); |
| 536 | 527 |
| 537 // Build a set of non-expired recent dismissals. It will be used for | 528 // Build a set of non-expired recent dismissals. It will be used for |
| 538 // client-side filtering of cards. | 529 // client-side filtering of cards. |
| 539 /** @type {Object<ServerNotificationId, number>} */ | 530 /** @type {Object<ServerNotificationId, number>} */ |
| 540 var updatedRecentDismissals = {}; | 531 var updatedRecentDismissals = {}; |
| 541 var now = Date.now(); | 532 var now = Date.now(); |
| 542 for (var serverNotificationId in items.recentDismissals) { | 533 for (var serverNotificationId in items.recentDismissals) { |
| 543 var dismissalAge = now - items.recentDismissals[serverNotificationId]; | 534 var dismissalAge = now - items.recentDismissals[serverNotificationId]; |
| 544 if (dismissalAge < DISMISS_RETENTION_TIME_MS) { | 535 if (dismissalAge < DISMISS_RETENTION_TIME_MS) { |
| 545 updatedRecentDismissals[serverNotificationId] = | 536 updatedRecentDismissals[serverNotificationId] = |
| 546 items.recentDismissals[serverNotificationId]; | 537 items.recentDismissals[serverNotificationId]; |
| 547 } | 538 } |
| 548 } | 539 } |
| 549 | 540 |
| 550 // Populate groups with corresponding cards. | 541 // Populate groups with corresponding cards. |
| 551 if (response.notifications) { | 542 if (response.notifications) { |
| 552 for (var i = 0; i < response.notifications.length; ++i) { | 543 for (var i = 0; i < response.notifications.length; ++i) { |
| 553 /** @type {ReceivedNotification} */ | 544 /** @type {ReceivedNotification} */ |
| 554 var card = response.notifications[i]; | 545 var card = response.notifications[i]; |
| 555 if (!(card.notificationId in updatedRecentDismissals)) { | 546 if (!(card.notificationId in updatedRecentDismissals)) { |
| 556 var group = receivedGroups[card.groupName]; | 547 var group = receivedGroups[card.groupName]; |
| 557 group.cards = group.cards || []; | 548 group.cards = group.cards || []; |
| 558 group.cards.push(card); | 549 group.cards.push(card); |
| 550 } |
| 551 } |
| 559 } | 552 } |
| 560 } | |
| 561 } | |
| 562 | 553 |
| 563 // Build updated set of groups. | 554 // Build updated set of groups. |
| 564 var updatedGroups = {}; | 555 var updatedGroups = {}; |
| 565 | 556 |
| 566 for (var groupName in receivedGroups) { | 557 for (var groupName in receivedGroups) { |
| 567 var receivedGroup = receivedGroups[groupName]; | 558 var receivedGroup = receivedGroups[groupName]; |
| 568 var storedGroup = items.notificationGroups[groupName] || { | 559 var storedGroup = items.notificationGroups[groupName] || { |
| 569 cards: [], | 560 cards: [], |
| 570 cardsTimestamp: undefined, | 561 cardsTimestamp: undefined, |
| 571 nextPollTime: undefined, | 562 nextPollTime: undefined, |
| 572 rank: undefined | 563 rank: undefined |
| 573 }; | 564 }; |
| 574 | 565 |
| 575 if (receivedGroup.requested) | 566 if (receivedGroup.requested) |
| 576 receivedGroup.cards = receivedGroup.cards || []; | 567 receivedGroup.cards = receivedGroup.cards || []; |
| 577 | 568 |
| 578 if (receivedGroup.cards) { | 569 if (receivedGroup.cards) { |
| 579 // If the group contains a cards update, all its fields will get new | 570 // If the group contains a cards update, all its fields will get new |
| 580 // values. | 571 // values. |
| 581 storedGroup.cards = receivedGroup.cards; | 572 storedGroup.cards = receivedGroup.cards; |
| 582 storedGroup.cardsTimestamp = now; | 573 storedGroup.cardsTimestamp = now; |
| 583 storedGroup.rank = receivedGroup.rank; | 574 storedGroup.rank = receivedGroup.rank; |
| 584 storedGroup.nextPollTime = undefined; | 575 storedGroup.nextPollTime = undefined; |
| 585 // The code below assigns nextPollTime a defined value if | 576 // The code below assigns nextPollTime a defined value if |
| 586 // nextPollSeconds is specified in the received group. | 577 // nextPollSeconds is specified in the received group. |
| 587 // If the group's cards are not updated, and nextPollSeconds is | 578 // If the group's cards are not updated, and nextPollSeconds is |
| 588 // unspecified, this method doesn't change group's nextPollTime. | 579 // unspecified, this method doesn't change group's nextPollTime. |
| 589 } | 580 } |
| 590 | 581 |
| 591 // 'nextPollSeconds' may be sent even for groups that don't contain | 582 // 'nextPollSeconds' may be sent even for groups that don't contain |
| 592 // cards updates. | 583 // cards updates. |
| 593 if (receivedGroup.nextPollSeconds !== undefined) { | 584 if (receivedGroup.nextPollSeconds !== undefined) { |
| 594 storedGroup.nextPollTime = | 585 storedGroup.nextPollTime = |
| 595 now + receivedGroup.nextPollSeconds * MS_IN_SECOND; | 586 now + receivedGroup.nextPollSeconds * MS_IN_SECOND; |
| 596 } | 587 } |
| 597 | 588 |
| 598 updatedGroups[groupName] = storedGroup; | 589 updatedGroups[groupName] = storedGroup; |
| 599 } | 590 } |
| 600 | 591 |
| 601 scheduleNextCardsPoll(updatedGroups); | 592 scheduleNextCardsPoll(updatedGroups); |
| 602 return { | 593 return { |
| 603 updatedGroups: updatedGroups, | 594 updatedGroups: updatedGroups, |
| 604 recentDismissals: updatedRecentDismissals | 595 recentDismissals: updatedRecentDismissals |
| 605 }; | 596 }; |
| 606 }); | 597 }); |
| 607 } | 598 } |
| 608 | 599 |
| 609 /** | 600 /** |
| 610 * Update the Explanatory Total Cards Shown Count. | 601 * Update the Explanatory Total Cards Shown Count. |
| 611 */ | 602 */ |
| 612 function countExplanatoryCard() { | 603 function countExplanatoryCard() { |
| 613 localStorage['explanatoryCardsShown']++; | 604 localStorage['explanatoryCardsShown']++; |
| 614 } | 605 } |
| 615 | 606 |
| 616 /** | 607 /** |
| (...skipping 11 matching lines...) Expand all Loading... |
| 628 * @param {Array<string>} groupNames Names of groups that need to be refreshed. | 619 * @param {Array<string>} groupNames Names of groups that need to be refreshed. |
| 629 * @return {Promise} A promise to request the specified notification groups. | 620 * @return {Promise} A promise to request the specified notification groups. |
| 630 */ | 621 */ |
| 631 function requestNotificationGroupsFromServer(groupNames) { | 622 function requestNotificationGroupsFromServer(groupNames) { |
| 632 console.log( | 623 console.log( |
| 633 'requestNotificationGroupsFromServer from ' + NOTIFICATION_CARDS_URL + | 624 'requestNotificationGroupsFromServer from ' + NOTIFICATION_CARDS_URL + |
| 634 ', groupNames=' + JSON.stringify(groupNames)); | 625 ', groupNames=' + JSON.stringify(groupNames)); |
| 635 | 626 |
| 636 recordEvent(GoogleNowEvent.REQUEST_FOR_CARDS_TOTAL); | 627 recordEvent(GoogleNowEvent.REQUEST_FOR_CARDS_TOTAL); |
| 637 | 628 |
| 638 var requestParameters = '?timeZoneOffsetMs=' + | 629 var requestParameters = |
| 639 (-new Date().getTimezoneOffset() * MS_IN_MINUTE); | 630 '?timeZoneOffsetMs=' + (-new Date().getTimezoneOffset() * MS_IN_MINUTE); |
| 640 | 631 |
| 641 if (shouldShowExplanatoryCard()) { | 632 if (shouldShowExplanatoryCard()) { |
| 642 requestParameters += '&cardExplanation=true'; | 633 requestParameters += '&cardExplanation=true'; |
| 643 } | 634 } |
| 644 | 635 |
| 645 groupNames.forEach(function(groupName) { | 636 groupNames.forEach(function(groupName) { |
| 646 requestParameters += ('&requestTypes=' + groupName); | 637 requestParameters += ('&requestTypes=' + groupName); |
| 647 }); | 638 }); |
| 648 | 639 |
| 649 requestParameters += '&uiLocale=' + navigator.language; | 640 requestParameters += '&uiLocale=' + navigator.language; |
| 650 | 641 |
| 651 console.log( | 642 console.log( |
| 652 'requestNotificationGroupsFromServer: request=' + requestParameters); | 643 'requestNotificationGroupsFromServer: request=' + requestParameters); |
| 653 | 644 |
| 654 return requestFromServer('GET', 'notifications' + requestParameters).then( | 645 return requestFromServer('GET', 'notifications' + requestParameters) |
| 655 function(request) { | 646 .then(function(request) { |
| 656 console.log( | 647 console.log( |
| 657 'requestNotificationGroupsFromServer-received ' + request.status); | 648 'requestNotificationGroupsFromServer-received ' + request.status); |
| 658 if (request.status == HTTP_OK) { | 649 if (request.status == HTTP_OK) { |
| 659 recordEvent(GoogleNowEvent.REQUEST_FOR_CARDS_SUCCESS); | 650 recordEvent(GoogleNowEvent.REQUEST_FOR_CARDS_SUCCESS); |
| 660 return JSON.parse(request.responseText); | 651 return JSON.parse(request.responseText); |
| 661 } | 652 } |
| 662 }); | 653 }); |
| 663 } | 654 } |
| 664 | 655 |
| 665 /** | 656 /** |
| 666 * Performs an opt-in poll without an immediate recheck. | 657 * Performs an opt-in poll without an immediate recheck. |
| 667 * If the response is not opted-in, schedule an opt-in check poll. | 658 * If the response is not opted-in, schedule an opt-in check poll. |
| 668 */ | 659 */ |
| 669 function pollOptedInNoImmediateRecheck() { | 660 function pollOptedInNoImmediateRecheck() { |
| 670 requestAndUpdateOptedIn() | 661 requestAndUpdateOptedIn() |
| 671 .then(function(optedIn) { | 662 .then(function(optedIn) { |
| 672 if (!optedIn) { | 663 if (!optedIn) { |
| 673 // Request a repoll if we're not opted in. | 664 // Request a repoll if we're not opted in. |
| 674 return Promise.reject(); | 665 return Promise.reject(); |
| 675 } | 666 } |
| 676 }) | 667 }) |
| 677 .catch(function() { | 668 .catch(function() { |
| 678 scheduleOptInCheckPoll(); | 669 scheduleOptInCheckPoll(); |
| 679 }); | 670 }); |
| 680 } | 671 } |
| 681 | 672 |
| 682 /** | 673 /** |
| 683 * Requests the account opted-in state from the server and updates any | 674 * Requests the account opted-in state from the server and updates any |
| 684 * state as necessary. | 675 * state as necessary. |
| 685 * @return {Promise} A promise to request and update the opted-in state. | 676 * @return {Promise} A promise to request and update the opted-in state. |
| 686 * The promise resolves with the opt-in state. | 677 * The promise resolves with the opt-in state. |
| 687 */ | 678 */ |
| 688 function requestAndUpdateOptedIn() { | 679 function requestAndUpdateOptedIn() { |
| 689 console.log('requestOptedIn from ' + NOTIFICATION_CARDS_URL); | 680 console.log('requestOptedIn from ' + NOTIFICATION_CARDS_URL); |
| 690 | 681 |
| 691 return requestFromServer('GET', 'settings/optin').then(function(request) { | 682 return requestFromServer('GET', 'settings/optin') |
| 692 console.log( | 683 .then(function(request) { |
| 693 'requestOptedIn-received ' + request.status + ' ' + request.response); | 684 console.log( |
| 694 if (request.status == HTTP_OK) { | 685 'requestOptedIn-received ' + request.status + ' ' + |
| 695 var parsedResponse = JSON.parse(request.responseText); | 686 request.response); |
| 696 return parsedResponse.value; | 687 if (request.status == HTTP_OK) { |
| 697 } | 688 var parsedResponse = JSON.parse(request.responseText); |
| 698 }).then(function(optedIn) { | 689 return parsedResponse.value; |
| 699 chrome.storage.local.set({googleNowEnabled: optedIn}); | 690 } |
| 700 return optedIn; | 691 }) |
| 701 }); | 692 .then(function(optedIn) { |
| 693 chrome.storage.local.set({googleNowEnabled: optedIn}); |
| 694 return optedIn; |
| 695 }); |
| 702 } | 696 } |
| 703 | 697 |
| 704 /** | 698 /** |
| 705 * Determines the groups that need to be requested right now. | 699 * Determines the groups that need to be requested right now. |
| 706 * @return {Promise} A promise to determine the groups to request. | 700 * @return {Promise} A promise to determine the groups to request. |
| 707 */ | 701 */ |
| 708 function getGroupsToRequest() { | 702 function getGroupsToRequest() { |
| 709 return fillFromChromeLocalStorage({ | 703 return fillFromChromeLocalStorage({ |
| 710 /** @type {Object<StoredNotificationGroup>} */ | 704 /** @type {Object<StoredNotificationGroup>} */ |
| 711 notificationGroups: {} | 705 notificationGroups: {} |
| 712 }).then(function(items) { | 706 }) |
| 713 console.log('getGroupsToRequest-storage-get ' + JSON.stringify(items)); | 707 .then(function(items) { |
| 714 var groupsToRequest = []; | 708 console.log('getGroupsToRequest-storage-get ' + JSON.stringify(items)); |
| 715 var now = Date.now(); | 709 var groupsToRequest = []; |
| 710 var now = Date.now(); |
| 716 | 711 |
| 717 for (var groupName in items.notificationGroups) { | 712 for (var groupName in items.notificationGroups) { |
| 718 var group = items.notificationGroups[groupName]; | 713 var group = items.notificationGroups[groupName]; |
| 719 if (group.nextPollTime !== undefined && group.nextPollTime <= now) | 714 if (group.nextPollTime !== undefined && group.nextPollTime <= now) |
| 720 groupsToRequest.push(groupName); | 715 groupsToRequest.push(groupName); |
| 721 } | 716 } |
| 722 return groupsToRequest; | 717 return groupsToRequest; |
| 723 }); | 718 }); |
| 724 } | 719 } |
| 725 | 720 |
| 726 /** | 721 /** |
| 727 * Requests notification cards from the server. | 722 * Requests notification cards from the server. |
| 728 * @return {Promise} A promise to request the notification cards. | 723 * @return {Promise} A promise to request the notification cards. |
| 729 * Rejects if the cards won't be requested. | 724 * Rejects if the cards won't be requested. |
| 730 */ | 725 */ |
| 731 function requestNotificationCards() { | 726 function requestNotificationCards() { |
| 732 console.log('requestNotificationCards'); | 727 console.log('requestNotificationCards'); |
| 733 return getGroupsToRequest() | 728 return getGroupsToRequest() |
| 734 .then(requestNotificationGroupsFromServer) | 729 .then(requestNotificationGroupsFromServer) |
| 735 .then(processServerResponse) | 730 .then(processServerResponse) |
| 736 .then(function(processedResponse) { | 731 .then(function(processedResponse) { |
| 737 var onCardShown = | 732 var onCardShown = |
| 738 shouldShowExplanatoryCard() ? countExplanatoryCard : undefined; | 733 shouldShowExplanatoryCard() ? countExplanatoryCard : undefined; |
| 739 return showNotificationGroups( | 734 return showNotificationGroups( |
| 740 processedResponse.updatedGroups, onCardShown).then(function() { | 735 processedResponse.updatedGroups, onCardShown) |
| 736 .then(function() { |
| 741 chrome.storage.local.set({ | 737 chrome.storage.local.set({ |
| 742 notificationGroups: processedResponse.updatedGroups, | 738 notificationGroups: processedResponse.updatedGroups, |
| 743 recentDismissals: processedResponse.updatedRecentDismissals | 739 recentDismissals: processedResponse.updatedRecentDismissals |
| 744 }); | 740 }); |
| 745 recordEvent(GoogleNowEvent.CARDS_PARSE_SUCCESS); | 741 recordEvent(GoogleNowEvent.CARDS_PARSE_SUCCESS); |
| 746 } | 742 }); |
| 747 ); | |
| 748 }); | 743 }); |
| 749 } | 744 } |
| 750 | 745 |
| 751 /** | 746 /** |
| 752 * Determines if an immediate retry should occur based off of the given groups. | 747 * Determines if an immediate retry should occur based off of the given groups. |
| 753 * The NOR group is expected most often and less latency sensitive, so we will | 748 * The NOR group is expected most often and less latency sensitive, so we will |
| 754 * simply wait MAXIMUM_POLLING_PERIOD_SECONDS before trying again. | 749 * simply wait MAXIMUM_POLLING_PERIOD_SECONDS before trying again. |
| 755 * @param {Array<string>} groupNames Names of groups that need to be refreshed. | 750 * @param {Array<string>} groupNames Names of groups that need to be refreshed. |
| 756 * @return {boolean} Whether a retry should occur. | 751 * @return {boolean} Whether a retry should occur. |
| 757 */ | 752 */ |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 791 * Sends a server request to dismiss a card. | 786 * Sends a server request to dismiss a card. |
| 792 * @param {ChromeNotificationId} chromeNotificationId chrome.notifications ID of | 787 * @param {ChromeNotificationId} chromeNotificationId chrome.notifications ID of |
| 793 * the card. | 788 * the card. |
| 794 * @param {number} dismissalTimeMs Time of the user's dismissal of the card in | 789 * @param {number} dismissalTimeMs Time of the user's dismissal of the card in |
| 795 * milliseconds since epoch. | 790 * milliseconds since epoch. |
| 796 * @param {DismissalData} dismissalData Data to build a dismissal request. | 791 * @param {DismissalData} dismissalData Data to build a dismissal request. |
| 797 * @return {Promise} A promise to request the card dismissal, rejects on error. | 792 * @return {Promise} A promise to request the card dismissal, rejects on error. |
| 798 */ | 793 */ |
| 799 function requestCardDismissal( | 794 function requestCardDismissal( |
| 800 chromeNotificationId, dismissalTimeMs, dismissalData) { | 795 chromeNotificationId, dismissalTimeMs, dismissalData) { |
| 801 console.log('requestDismissingCard ' + chromeNotificationId + | 796 console.log( |
| 802 ' from ' + NOTIFICATION_CARDS_URL + | 797 'requestDismissingCard ' + chromeNotificationId + ' from ' + |
| 803 ', dismissalData=' + JSON.stringify(dismissalData)); | 798 NOTIFICATION_CARDS_URL + ', dismissalData=' + |
| 799 JSON.stringify(dismissalData)); |
| 804 | 800 |
| 805 var dismissalAge = Date.now() - dismissalTimeMs; | 801 var dismissalAge = Date.now() - dismissalTimeMs; |
| 806 | 802 |
| 807 if (dismissalAge > MAXIMUM_DISMISSAL_AGE_MS) { | 803 if (dismissalAge > MAXIMUM_DISMISSAL_AGE_MS) { |
| 808 return Promise.resolve(); | 804 return Promise.resolve(); |
| 809 } | 805 } |
| 810 | 806 |
| 811 recordEvent(GoogleNowEvent.DISMISS_REQUEST_TOTAL); | 807 recordEvent(GoogleNowEvent.DISMISS_REQUEST_TOTAL); |
| 812 | 808 |
| 813 var requestParameters = 'notifications/' + dismissalData.notificationId + | 809 var requestParameters = 'notifications/' + dismissalData.notificationId + |
| 814 '?age=' + dismissalAge + | 810 '?age=' + dismissalAge + '&chromeNotificationId=' + chromeNotificationId; |
| 815 '&chromeNotificationId=' + chromeNotificationId; | |
| 816 | 811 |
| 817 for (var paramField in dismissalData.parameters) | 812 for (var paramField in dismissalData.parameters) |
| 818 requestParameters += ('&' + paramField + | 813 requestParameters += |
| 819 '=' + dismissalData.parameters[paramField]); | 814 ('&' + paramField + '=' + dismissalData.parameters[paramField]); |
| 820 | 815 |
| 821 console.log('requestCardDismissal: requestParameters=' + requestParameters); | 816 console.log('requestCardDismissal: requestParameters=' + requestParameters); |
| 822 | 817 |
| 823 return requestFromServer('DELETE', requestParameters).then(function(request) { | 818 return requestFromServer('DELETE', requestParameters) |
| 824 console.log('requestDismissingCard-onloadend ' + request.status); | 819 .then(function(request) { |
| 825 if (request.status == HTTP_NOCONTENT) | 820 console.log('requestDismissingCard-onloadend ' + request.status); |
| 826 recordEvent(GoogleNowEvent.DISMISS_REQUEST_SUCCESS); | 821 if (request.status == HTTP_NOCONTENT) |
| 822 recordEvent(GoogleNowEvent.DISMISS_REQUEST_SUCCESS); |
| 827 | 823 |
| 828 // A dismissal doesn't require further retries if it was successful or | 824 // A dismissal doesn't require further retries if it was successful or |
| 829 // doesn't have a chance for successful completion. | 825 // doesn't have a chance for successful completion. |
| 830 return (request.status == HTTP_NOCONTENT) ? | 826 return (request.status == HTTP_NOCONTENT) ? Promise.resolve() : |
| 831 Promise.resolve() : | 827 Promise.reject(); |
| 832 Promise.reject(); | 828 }) |
| 833 }).catch(function(request) { | 829 .catch(function(request) { |
| 834 request = (typeof request === 'object') ? request : {}; | 830 request = (typeof request === 'object') ? request : {}; |
| 835 return (request.status == HTTP_BAD_REQUEST || | 831 return (request.status == HTTP_BAD_REQUEST || |
| 836 request.status == HTTP_METHOD_NOT_ALLOWED) ? | 832 request.status == HTTP_METHOD_NOT_ALLOWED) ? |
| 837 Promise.resolve() : | 833 Promise.resolve() : |
| 838 Promise.reject(); | 834 Promise.reject(); |
| 839 }); | 835 }); |
| 840 } | 836 } |
| 841 | 837 |
| 842 /** | 838 /** |
| 843 * Tries to send dismiss requests for all pending dismissals. | 839 * Tries to send dismiss requests for all pending dismissals. |
| 844 * @return {Promise} A promise to process the pending dismissals. | 840 * @return {Promise} A promise to process the pending dismissals. |
| 845 * The promise is rejected if a problem was encountered. | 841 * The promise is rejected if a problem was encountered. |
| 846 */ | 842 */ |
| 847 function processPendingDismissals() { | 843 function processPendingDismissals() { |
| 848 return fillFromChromeLocalStorage({ | 844 return fillFromChromeLocalStorage({ |
| 849 /** @type {Array<PendingDismissal>} */ | 845 /** @type {Array<PendingDismissal>} */ |
| 850 pendingDismissals: [], | 846 pendingDismissals: [], |
| 851 /** @type {Object<ServerNotificationId, number>} */ | 847 /** @type {Object<ServerNotificationId, number>} */ |
| 852 recentDismissals: {} | 848 recentDismissals: {} |
| 853 }).then(function(items) { | 849 }) |
| 854 console.log( | 850 .then(function(items) { |
| 855 'processPendingDismissals-storage-get ' + JSON.stringify(items)); | 851 console.log( |
| 852 'processPendingDismissals-storage-get ' + JSON.stringify(items)); |
| 856 | 853 |
| 857 var dismissalsChanged = false; | 854 var dismissalsChanged = false; |
| 858 | 855 |
| 859 function onFinish(success) { | 856 function onFinish(success) { |
| 860 if (dismissalsChanged) { | 857 if (dismissalsChanged) { |
| 861 chrome.storage.local.set({ | 858 chrome.storage.local.set({ |
| 862 pendingDismissals: items.pendingDismissals, | 859 pendingDismissals: items.pendingDismissals, |
| 863 recentDismissals: items.recentDismissals | 860 recentDismissals: items.recentDismissals |
| 864 }); | 861 }); |
| 865 } | 862 } |
| 866 return success ? Promise.resolve() : Promise.reject(); | 863 return success ? Promise.resolve() : Promise.reject(); |
| 867 } | 864 } |
| 868 | 865 |
| 869 function doProcessDismissals() { | 866 function doProcessDismissals() { |
| 870 if (items.pendingDismissals.length == 0) { | 867 if (items.pendingDismissals.length == 0) { |
| 871 dismissalAttempts.stop(); | 868 dismissalAttempts.stop(); |
| 872 return onFinish(true); | 869 return onFinish(true); |
| 873 } | 870 } |
| 874 | 871 |
| 875 // Send dismissal for the first card, and if successful, repeat | 872 // Send dismissal for the first card, and if successful, repeat |
| 876 // recursively with the rest. | 873 // recursively with the rest. |
| 877 /** @type {PendingDismissal} */ | 874 /** @type {PendingDismissal} */ |
| 878 var dismissal = items.pendingDismissals[0]; | 875 var dismissal = items.pendingDismissals[0]; |
| 879 return requestCardDismissal( | 876 return requestCardDismissal( |
| 880 dismissal.chromeNotificationId, | 877 dismissal.chromeNotificationId, dismissal.time, |
| 881 dismissal.time, | 878 dismissal.dismissalData) |
| 882 dismissal.dismissalData).then(function() { | 879 .then(function() { |
| 883 dismissalsChanged = true; | 880 dismissalsChanged = true; |
| 884 items.pendingDismissals.splice(0, 1); | 881 items.pendingDismissals.splice(0, 1); |
| 885 items.recentDismissals[dismissal.dismissalData.notificationId] = | 882 items.recentDismissals[dismissal.dismissalData.notificationId] = |
| 886 Date.now(); | 883 Date.now(); |
| 887 return doProcessDismissals(); | 884 return doProcessDismissals(); |
| 888 }).catch(function() { | 885 }) |
| 889 return onFinish(false); | 886 .catch(function() { |
| 890 }); | 887 return onFinish(false); |
| 891 } | 888 }); |
| 889 } |
| 892 | 890 |
| 893 return doProcessDismissals(); | 891 return doProcessDismissals(); |
| 894 }); | 892 }); |
| 895 } | 893 } |
| 896 | 894 |
| 897 /** | 895 /** |
| 898 * Submits a task to send pending dismissals. | 896 * Submits a task to send pending dismissals. |
| 899 */ | 897 */ |
| 900 function retryPendingDismissals() { | 898 function retryPendingDismissals() { |
| 901 tasks.add(RETRY_DISMISS_TASK_NAME, function() { | 899 tasks.add(RETRY_DISMISS_TASK_NAME, function() { |
| 902 processPendingDismissals().catch(dismissalAttempts.scheduleRetry); | 900 processPendingDismissals().catch(dismissalAttempts.scheduleRetry); |
| 903 }); | 901 }); |
| 904 } | 902 } |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 960 | 958 |
| 961 fillFromChromeLocalStorage({ | 959 fillFromChromeLocalStorage({ |
| 962 /** @type {Array<PendingDismissal>} */ | 960 /** @type {Array<PendingDismissal>} */ |
| 963 pendingDismissals: [], | 961 pendingDismissals: [], |
| 964 /** @type {Object<ChromeNotificationId, NotificationDataEntry>} */ | 962 /** @type {Object<ChromeNotificationId, NotificationDataEntry>} */ |
| 965 notificationsData: {}, | 963 notificationsData: {}, |
| 966 /** @type {Object<StoredNotificationGroup>} */ | 964 /** @type {Object<StoredNotificationGroup>} */ |
| 967 notificationGroups: {} | 965 notificationGroups: {} |
| 968 }).then(function(items) { | 966 }).then(function(items) { |
| 969 /** @type {NotificationDataEntry} */ | 967 /** @type {NotificationDataEntry} */ |
| 970 var notificationData = | 968 var notificationData = items.notificationsData[chromeNotificationId] || |
| 971 items.notificationsData[chromeNotificationId] || | 969 {timestamp: Date.now(), combinedCard: []}; |
| 972 { | |
| 973 timestamp: Date.now(), | |
| 974 combinedCard: [] | |
| 975 }; | |
| 976 | 970 |
| 977 var dismissalResult = | 971 var dismissalResult = cardSet.onDismissal( |
| 978 cardSet.onDismissal( | 972 chromeNotificationId, notificationData, items.notificationGroups); |
| 979 chromeNotificationId, | |
| 980 notificationData, | |
| 981 items.notificationGroups); | |
| 982 | 973 |
| 983 for (var i = 0; i < dismissalResult.dismissals.length; i++) { | 974 for (var i = 0; i < dismissalResult.dismissals.length; i++) { |
| 984 /** @type {PendingDismissal} */ | 975 /** @type {PendingDismissal} */ |
| 985 var dismissal = { | 976 var dismissal = { |
| 986 chromeNotificationId: chromeNotificationId, | 977 chromeNotificationId: chromeNotificationId, |
| 987 time: Date.now(), | 978 time: Date.now(), |
| 988 dismissalData: dismissalResult.dismissals[i] | 979 dismissalData: dismissalResult.dismissals[i] |
| 989 }; | 980 }; |
| 990 items.pendingDismissals.push(dismissal); | 981 items.pendingDismissals.push(dismissal); |
| 991 } | 982 } |
| (...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1079 unregisterFromGcm(); | 1070 unregisterFromGcm(); |
| 1080 } | 1071 } |
| 1081 } | 1072 } |
| 1082 | 1073 |
| 1083 /** | 1074 /** |
| 1084 * Enables or disables the Google Now background permission. | 1075 * Enables or disables the Google Now background permission. |
| 1085 * @param {boolean} backgroundEnable true to run in the background. | 1076 * @param {boolean} backgroundEnable true to run in the background. |
| 1086 * false to not run in the background. | 1077 * false to not run in the background. |
| 1087 */ | 1078 */ |
| 1088 function setBackgroundEnable(backgroundEnable) { | 1079 function setBackgroundEnable(backgroundEnable) { |
| 1089 instrumented.permissions.contains({permissions: ['background']}, | 1080 instrumented.permissions.contains( |
| 1090 function(hasPermission) { | 1081 {permissions: ['background']}, function(hasPermission) { |
| 1091 if (backgroundEnable != hasPermission) { | 1082 if (backgroundEnable != hasPermission) { |
| 1092 console.log('Action Taken setBackgroundEnable=' + backgroundEnable); | 1083 console.log('Action Taken setBackgroundEnable=' + backgroundEnable); |
| 1093 if (backgroundEnable) | 1084 if (backgroundEnable) |
| 1094 chrome.permissions.request({permissions: ['background']}); | 1085 chrome.permissions.request({permissions: ['background']}); |
| 1095 else | 1086 else |
| 1096 chrome.permissions.remove({permissions: ['background']}); | 1087 chrome.permissions.remove({permissions: ['background']}); |
| 1097 } else { | 1088 } else { |
| 1098 console.log('Action Ignored setBackgroundEnable=' + backgroundEnable); | 1089 console.log('Action Ignored setBackgroundEnable=' + backgroundEnable); |
| 1099 } | 1090 } |
| 1100 }); | 1091 }); |
| (...skipping 22 matching lines...) Expand all Loading... |
| 1123 * based off of the current state of Chrome. | 1114 * based off of the current state of Chrome. |
| 1124 * @param {boolean} signedIn true if the user is signed in. | 1115 * @param {boolean} signedIn true if the user is signed in. |
| 1125 * @param {boolean} canEnableBackground true if | 1116 * @param {boolean} canEnableBackground true if |
| 1126 * the background permission can be requested. | 1117 * the background permission can be requested. |
| 1127 * @param {boolean} notificationEnabled true if | 1118 * @param {boolean} notificationEnabled true if |
| 1128 * Google Now for Chrome is allowed to show notifications. | 1119 * Google Now for Chrome is allowed to show notifications. |
| 1129 * @param {boolean} googleNowEnabled true if | 1120 * @param {boolean} googleNowEnabled true if |
| 1130 * the Google Now is enabled for the user. | 1121 * the Google Now is enabled for the user. |
| 1131 */ | 1122 */ |
| 1132 function updateRunningState( | 1123 function updateRunningState( |
| 1133 signedIn, | 1124 signedIn, canEnableBackground, notificationEnabled, googleNowEnabled) { |
| 1134 canEnableBackground, | |
| 1135 notificationEnabled, | |
| 1136 googleNowEnabled) { | |
| 1137 console.log( | 1125 console.log( |
| 1138 'State Update signedIn=' + signedIn + ' ' + | 1126 'State Update signedIn=' + signedIn + ' ' + |
| 1139 'canEnableBackground=' + canEnableBackground + ' ' + | 1127 'canEnableBackground=' + canEnableBackground + ' ' + |
| 1140 'notificationEnabled=' + notificationEnabled + ' ' + | 1128 'notificationEnabled=' + notificationEnabled + ' ' + |
| 1141 'googleNowEnabled=' + googleNowEnabled); | 1129 'googleNowEnabled=' + googleNowEnabled); |
| 1142 | 1130 |
| 1143 var shouldPollCards = false; | 1131 var shouldPollCards = false; |
| 1144 var shouldPollOptInStatus = false; | 1132 var shouldPollOptInStatus = false; |
| 1145 var shouldSetBackground = false; | 1133 var shouldSetBackground = false; |
| 1146 | 1134 |
| (...skipping 19 matching lines...) Expand all Loading... |
| 1166 removeAllCards(); | 1154 removeAllCards(); |
| 1167 } | 1155 } |
| 1168 } | 1156 } |
| 1169 | 1157 |
| 1170 /** | 1158 /** |
| 1171 * Coordinates the behavior of Google Now for Chrome depending on | 1159 * Coordinates the behavior of Google Now for Chrome depending on |
| 1172 * Chrome and extension state. | 1160 * Chrome and extension state. |
| 1173 */ | 1161 */ |
| 1174 function onStateChange() { | 1162 function onStateChange() { |
| 1175 tasks.add(STATE_CHANGED_TASK_NAME, function() { | 1163 tasks.add(STATE_CHANGED_TASK_NAME, function() { |
| 1176 Promise.all([ | 1164 Promise |
| 1177 authenticationManager.isSignedIn(), | 1165 .all([ |
| 1178 canEnableBackground(), | 1166 authenticationManager.isSignedIn(), canEnableBackground(), |
| 1179 isNotificationsEnabled(), | 1167 isNotificationsEnabled(), isGoogleNowEnabled() |
| 1180 isGoogleNowEnabled()]) | 1168 ]) |
| 1181 .then(function(results) { | 1169 .then(function(results) { |
| 1182 updateRunningState.apply(null, results); | 1170 updateRunningState.apply(null, results); |
| 1183 }); | 1171 }); |
| 1184 }); | 1172 }); |
| 1185 } | 1173 } |
| 1186 | 1174 |
| 1187 /** | 1175 /** |
| 1188 * Determines if background mode should be requested. | 1176 * Determines if background mode should be requested. |
| 1189 * @return {Promise} A promise to determine if background can be enabled. | 1177 * @return {Promise} A promise to determine if background can be enabled. |
| 1190 */ | 1178 */ |
| 1191 function canEnableBackground() { | 1179 function canEnableBackground() { |
| 1192 return new Promise(function(resolve) { | 1180 return new Promise(function(resolve) { |
| 1193 instrumented.metricsPrivate.getVariationParams( | 1181 instrumented.metricsPrivate.getVariationParams( |
| 1194 'GoogleNow', | 1182 'GoogleNow', function(response) { |
| 1195 function(response) { | |
| 1196 resolve(!response || (response.canEnableBackground != 'false')); | 1183 resolve(!response || (response.canEnableBackground != 'false')); |
| 1197 }); | 1184 }); |
| 1198 }); | 1185 }); |
| 1199 } | 1186 } |
| 1200 | 1187 |
| 1201 /** | 1188 /** |
| 1202 * Checks if Google Now is enabled in the notifications center. | 1189 * Checks if Google Now is enabled in the notifications center. |
| 1203 * @return {Promise} A promise to determine if Google Now is enabled | 1190 * @return {Promise} A promise to determine if Google Now is enabled |
| 1204 * in the notifications center. | 1191 * in the notifications center. |
| 1205 */ | 1192 */ |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1247 }); | 1234 }); |
| 1248 } | 1235 } |
| 1249 | 1236 |
| 1250 /** | 1237 /** |
| 1251 * Returns a promise resolving to a GCM Notificaiton Key. May call | 1238 * Returns a promise resolving to a GCM Notificaiton Key. May call |
| 1252 * chrome.gcm.register() first if required. Rejects on registration failure. | 1239 * chrome.gcm.register() first if required. Rejects on registration failure. |
| 1253 * @return {Promise} A Promise that resolves to a fresh GCM Notification key. | 1240 * @return {Promise} A Promise that resolves to a fresh GCM Notification key. |
| 1254 */ | 1241 */ |
| 1255 function requestNewGcmNotificationKey() { | 1242 function requestNewGcmNotificationKey() { |
| 1256 return getGcmRegistrationId().then(function(gcmId) { | 1243 return getGcmRegistrationId().then(function(gcmId) { |
| 1257 authenticationManager.getAuthToken().then(function(token) { | 1244 authenticationManager.getAuthToken() |
| 1258 authenticationManager.getLogin().then(function(username) { | 1245 .then(function(token) { |
| 1259 return new Promise(function(resolve, reject) { | 1246 authenticationManager.getLogin().then(function(username) { |
| 1260 var xhr = new XMLHttpRequest(); | 1247 return new Promise(function(resolve, reject) { |
| 1261 xhr.responseType = 'application/json'; | 1248 var xhr = new XMLHttpRequest(); |
| 1262 xhr.open('POST', GCM_REGISTRATION_URL, true); | 1249 xhr.responseType = 'application/json'; |
| 1263 xhr.setRequestHeader('Content-Type', 'application/json'); | 1250 xhr.open('POST', GCM_REGISTRATION_URL, true); |
| 1264 xhr.setRequestHeader('Authorization', 'Bearer ' + token); | 1251 xhr.setRequestHeader('Content-Type', 'application/json'); |
| 1265 xhr.setRequestHeader('project_id', GCM_PROJECT_ID); | 1252 xhr.setRequestHeader('Authorization', 'Bearer ' + token); |
| 1266 var payload = { | 1253 xhr.setRequestHeader('project_id', GCM_PROJECT_ID); |
| 1267 'operation': 'add', | 1254 var payload = { |
| 1268 'notification_key_name': username, | 1255 'operation': 'add', |
| 1269 'registration_ids': [gcmId] | 1256 'notification_key_name': username, |
| 1270 }; | 1257 'registration_ids': [gcmId] |
| 1271 xhr.onloadend = function() { | 1258 }; |
| 1272 if (xhr.status != 200) { | 1259 xhr.onloadend = function() { |
| 1273 reject(); | 1260 if (xhr.status != 200) { |
| 1274 } | 1261 reject(); |
| 1275 var obj = JSON.parse(xhr.responseText); | 1262 } |
| 1276 var key = obj && obj.notification_key; | 1263 var obj = JSON.parse(xhr.responseText); |
| 1277 if (!key) { | 1264 var key = obj && obj.notification_key; |
| 1278 reject(); | 1265 if (!key) { |
| 1279 } | 1266 reject(); |
| 1280 console.log('gcm notification key POST: ' + key); | 1267 } |
| 1281 chrome.storage.local.set({gcmNotificationKey: key}); | 1268 console.log('gcm notification key POST: ' + key); |
| 1282 resolve(key); | 1269 chrome.storage.local.set({gcmNotificationKey: key}); |
| 1283 }; | 1270 resolve(key); |
| 1284 xhr.send(JSON.stringify(payload)); | 1271 }; |
| 1272 xhr.send(JSON.stringify(payload)); |
| 1273 }); |
| 1274 }); |
| 1275 }) |
| 1276 .catch(function() { |
| 1277 // Couldn't obtain a GCM ID. Ignore and fallback to polling. |
| 1285 }); | 1278 }); |
| 1286 }); | |
| 1287 }).catch(function() { | |
| 1288 // Couldn't obtain a GCM ID. Ignore and fallback to polling. | |
| 1289 }); | |
| 1290 }); | 1279 }); |
| 1291 } | 1280 } |
| 1292 | 1281 |
| 1293 /** | 1282 /** |
| 1294 * Returns a promise resolving to either a cached or new GCM registration ID. | 1283 * Returns a promise resolving to either a cached or new GCM registration ID. |
| 1295 * Rejects if registration fails. | 1284 * Rejects if registration fails. |
| 1296 * @return {Promise} A Promise that resolves to a GCM registration ID. | 1285 * @return {Promise} A Promise that resolves to a GCM registration ID. |
| 1297 */ | 1286 */ |
| 1298 function getGcmRegistrationId() { | 1287 function getGcmRegistrationId() { |
| 1299 return fillFromChromeLocalStorage({gcmRegistrationId: undefined}) | 1288 return fillFromChromeLocalStorage({gcmRegistrationId: undefined}) |
| (...skipping 14 matching lines...) Expand all Loading... |
| 1314 } | 1303 } |
| 1315 }); | 1304 }); |
| 1316 }); | 1305 }); |
| 1317 }); | 1306 }); |
| 1318 } | 1307 } |
| 1319 | 1308 |
| 1320 /** | 1309 /** |
| 1321 * Unregisters from GCM if previously registered. | 1310 * Unregisters from GCM if previously registered. |
| 1322 */ | 1311 */ |
| 1323 function unregisterFromGcm() { | 1312 function unregisterFromGcm() { |
| 1324 fillFromChromeLocalStorage({gcmRegistrationId: undefined}) | 1313 fillFromChromeLocalStorage({ |
| 1325 .then(function(items) { | 1314 gcmRegistrationId: undefined |
| 1326 if (items.gcmRegistrationId) { | 1315 }).then(function(items) { |
| 1327 console.log('Unregistering from gcm.'); | 1316 if (items.gcmRegistrationId) { |
| 1328 instrumented.gcm.unregister(function() { | 1317 console.log('Unregistering from gcm.'); |
| 1329 if (!chrome.runtime.lastError) { | 1318 instrumented.gcm.unregister(function() { |
| 1330 chrome.storage.local.remove( | 1319 if (!chrome.runtime.lastError) { |
| 1331 ['gcmNotificationKey', 'gcmRegistrationId']); | 1320 chrome.storage.local.remove( |
| 1332 } | 1321 ['gcmNotificationKey', 'gcmRegistrationId']); |
| 1333 }); | |
| 1334 } | 1322 } |
| 1335 }); | 1323 }); |
| 1324 } |
| 1325 }); |
| 1336 } | 1326 } |
| 1337 | 1327 |
| 1338 /** | 1328 /** |
| 1339 * Polls the optin state. | 1329 * Polls the optin state. |
| 1340 * Sometimes we get the response to the opted in result too soon during | 1330 * Sometimes we get the response to the opted in result too soon during |
| 1341 * push messaging. We'll recheck the optin state a few times before giving up. | 1331 * push messaging. We'll recheck the optin state a few times before giving up. |
| 1342 */ | 1332 */ |
| 1343 function pollOptedInWithRecheck() { | 1333 function pollOptedInWithRecheck() { |
| 1344 /** | 1334 /** |
| 1345 * Cleans up any state used to recheck the opt-in poll. | 1335 * Cleans up any state used to recheck the opt-in poll. |
| 1346 */ | 1336 */ |
| 1347 function clearPollingState() { | 1337 function clearPollingState() { |
| 1348 localStorage.removeItem('optedInCheckCount'); | 1338 localStorage.removeItem('optedInCheckCount'); |
| 1349 optInRecheckAttempts.stop(); | 1339 optInRecheckAttempts.stop(); |
| 1350 } | 1340 } |
| 1351 | 1341 |
| 1352 if (localStorage.optedInCheckCount === undefined) { | 1342 if (localStorage.optedInCheckCount === undefined) { |
| 1353 localStorage.optedInCheckCount = 0; | 1343 localStorage.optedInCheckCount = 0; |
| 1354 optInRecheckAttempts.start(); | 1344 optInRecheckAttempts.start(); |
| 1355 } | 1345 } |
| 1356 | 1346 |
| 1357 console.log(new Date() + | 1347 console.log( |
| 1358 ' checkOptedIn Attempt ' + localStorage.optedInCheckCount); | 1348 new Date() + ' checkOptedIn Attempt ' + localStorage.optedInCheckCount); |
| 1359 | 1349 |
| 1360 requestAndUpdateOptedIn().then(function(optedIn) { | 1350 requestAndUpdateOptedIn() |
| 1361 if (optedIn) { | 1351 .then(function(optedIn) { |
| 1362 clearPollingState(); | 1352 if (optedIn) { |
| 1363 return Promise.resolve(); | 1353 clearPollingState(); |
| 1364 } else { | 1354 return Promise.resolve(); |
| 1365 // If we're not opted in, reject to retry. | 1355 } else { |
| 1366 return Promise.reject(); | 1356 // If we're not opted in, reject to retry. |
| 1367 } | 1357 return Promise.reject(); |
| 1368 }).catch(function() { | 1358 } |
| 1369 if (localStorage.optedInCheckCount < 5) { | 1359 }) |
| 1370 localStorage.optedInCheckCount++; | 1360 .catch(function() { |
| 1371 optInRecheckAttempts.scheduleRetry(); | 1361 if (localStorage.optedInCheckCount < 5) { |
| 1372 } else { | 1362 localStorage.optedInCheckCount++; |
| 1373 clearPollingState(); | 1363 optInRecheckAttempts.scheduleRetry(); |
| 1374 } | 1364 } else { |
| 1375 }); | 1365 clearPollingState(); |
| 1366 } |
| 1367 }); |
| 1376 } | 1368 } |
| 1377 | 1369 |
| 1378 instrumented.runtime.onInstalled.addListener(function(details) { | 1370 instrumented.runtime.onInstalled.addListener(function(details) { |
| 1379 console.log('onInstalled ' + JSON.stringify(details)); | 1371 console.log('onInstalled ' + JSON.stringify(details)); |
| 1380 if (details.reason != 'chrome_update') { | 1372 if (details.reason != 'chrome_update') { |
| 1381 initialize(); | 1373 initialize(); |
| 1382 } | 1374 } |
| 1383 }); | 1375 }); |
| 1384 | 1376 |
| 1385 instrumented.runtime.onStartup.addListener(function() { | 1377 instrumented.runtime.onStartup.addListener(function() { |
| (...skipping 16 matching lines...) Expand all Loading... |
| 1402 }); | 1394 }); |
| 1403 | 1395 |
| 1404 initialize(); | 1396 initialize(); |
| 1405 }); | 1397 }); |
| 1406 | 1398 |
| 1407 authenticationManager.addListener(function() { | 1399 authenticationManager.addListener(function() { |
| 1408 console.log('signIn State Change'); | 1400 console.log('signIn State Change'); |
| 1409 onStateChange(); | 1401 onStateChange(); |
| 1410 }); | 1402 }); |
| 1411 | 1403 |
| 1412 instrumented.notifications.onClicked.addListener( | 1404 instrumented.notifications.onClicked.addListener(function( |
| 1413 function(chromeNotificationId) { | 1405 chromeNotificationId) { |
| 1414 chrome.metricsPrivate.recordUserAction('GoogleNow.MessageClicked'); | 1406 chrome.metricsPrivate.recordUserAction('GoogleNow.MessageClicked'); |
| 1415 onNotificationClicked(chromeNotificationId, | 1407 onNotificationClicked(chromeNotificationId, function(notificationDataEntry) { |
| 1416 function(notificationDataEntry) { | 1408 var actionUrls = notificationDataEntry.actionUrls; |
| 1417 var actionUrls = notificationDataEntry.actionUrls; | 1409 var url = actionUrls && actionUrls.messageUrl; |
| 1418 var url = actionUrls && actionUrls.messageUrl; | 1410 if (url) { |
| 1419 if (url) { | 1411 recordNotificationClick(notificationDataEntry.cardTypeId); |
| 1420 recordNotificationClick(notificationDataEntry.cardTypeId); | 1412 } |
| 1421 } | 1413 return url; |
| 1422 return url; | 1414 }); |
| 1423 }); | 1415 }); |
| 1424 }); | |
| 1425 | 1416 |
| 1426 instrumented.notifications.onButtonClicked.addListener( | 1417 instrumented.notifications.onButtonClicked.addListener(function( |
| 1427 function(chromeNotificationId, buttonIndex) { | 1418 chromeNotificationId, buttonIndex) { |
| 1428 chrome.metricsPrivate.recordUserAction( | 1419 chrome.metricsPrivate.recordUserAction( |
| 1429 'GoogleNow.ButtonClicked' + buttonIndex); | 1420 'GoogleNow.ButtonClicked' + buttonIndex); |
| 1430 onNotificationClicked(chromeNotificationId, | 1421 onNotificationClicked(chromeNotificationId, function(notificationDataEntry) { |
| 1431 function(notificationDataEntry) { | 1422 var actionUrls = notificationDataEntry.actionUrls; |
| 1432 var actionUrls = notificationDataEntry.actionUrls; | 1423 var url = actionUrls.buttonUrls[buttonIndex]; |
| 1433 var url = actionUrls.buttonUrls[buttonIndex]; | 1424 if (url) { |
| 1434 if (url) { | 1425 recordButtonClick(notificationDataEntry.cardTypeId, buttonIndex); |
| 1435 recordButtonClick(notificationDataEntry.cardTypeId, buttonIndex); | 1426 } else { |
| 1436 } else { | 1427 verify(false, 'onButtonClicked: no url for a button'); |
| 1437 verify(false, 'onButtonClicked: no url for a button'); | 1428 console.log( |
| 1438 console.log( | 1429 'buttonIndex=' + buttonIndex + ' ' + |
| 1439 'buttonIndex=' + buttonIndex + ' ' + | 1430 'chromeNotificationId=' + chromeNotificationId + ' ' + |
| 1440 'chromeNotificationId=' + chromeNotificationId + ' ' + | 1431 'notificationDataEntry=' + JSON.stringify(notificationDataEntry)); |
| 1441 'notificationDataEntry=' + | 1432 } |
| 1442 JSON.stringify(notificationDataEntry)); | 1433 return url; |
| 1443 } | 1434 }); |
| 1444 return url; | 1435 }); |
| 1445 }); | |
| 1446 }); | |
| 1447 | 1436 |
| 1448 instrumented.notifications.onClosed.addListener(onNotificationClosed); | 1437 instrumented.notifications.onClosed.addListener(onNotificationClosed); |
| 1449 | 1438 |
| 1450 instrumented.notifications.onPermissionLevelChanged.addListener( | 1439 instrumented.notifications.onPermissionLevelChanged.addListener(function( |
| 1451 function(permissionLevel) { | 1440 permissionLevel) { |
| 1452 console.log('Notifications permissionLevel Change'); | 1441 console.log('Notifications permissionLevel Change'); |
| 1453 onStateChange(); | 1442 onStateChange(); |
| 1454 }); | 1443 }); |
| 1455 | 1444 |
| 1456 instrumented.notifications.onShowSettings.addListener(function() { | 1445 instrumented.notifications.onShowSettings.addListener(function() { |
| 1457 openUrl(SETTINGS_URL); | 1446 openUrl(SETTINGS_URL); |
| 1458 }); | 1447 }); |
| 1459 | 1448 |
| 1460 // Handles state change notifications for the Google Now enabled bit. | 1449 // Handles state change notifications for the Google Now enabled bit. |
| 1461 instrumented.storage.onChanged.addListener(function(changes, areaName) { | 1450 instrumented.storage.onChanged.addListener(function(changes, areaName) { |
| 1462 if (areaName === 'local') { | 1451 if (areaName === 'local') { |
| 1463 if ('googleNowEnabled' in changes) { | 1452 if ('googleNowEnabled' in changes) { |
| 1464 onStateChange(); | 1453 onStateChange(); |
| 1465 } | 1454 } |
| 1466 } | 1455 } |
| 1467 }); | 1456 }); |
| 1468 | 1457 |
| 1469 instrumented.gcm.onMessage.addListener(function(message) { | 1458 instrumented.gcm.onMessage.addListener(function(message) { |
| 1470 console.log('gcm.onMessage ' + JSON.stringify(message)); | 1459 console.log('gcm.onMessage ' + JSON.stringify(message)); |
| 1471 if (!message || !message.data) { | 1460 if (!message || !message.data) { |
| 1472 return; | 1461 return; |
| 1473 } | 1462 } |
| 1474 | 1463 |
| 1475 var payload = message.data.payload; | 1464 var payload = message.data.payload; |
| 1476 var tag = message.data.tag; | 1465 var tag = message.data.tag; |
| 1477 if (payload.startsWith('REQUEST_CARDS')) { | 1466 if (payload.startsWith('REQUEST_CARDS')) { |
| 1478 tasks.add(ON_PUSH_MESSAGE_START_TASK_NAME, function() { | 1467 tasks.add(ON_PUSH_MESSAGE_START_TASK_NAME, function() { |
| 1479 // Accept promise rejection on failure since it's safer to do nothing, | 1468 // Accept promise rejection on failure since it's safer to do nothing, |
| 1480 // preventing polling the server when the payload really didn't change. | 1469 // preventing polling the server when the payload really didn't change. |
| 1481 fillFromChromeLocalStorage({ | 1470 fillFromChromeLocalStorage( |
| 1482 lastPollNowPayloads: {}, | 1471 { |
| 1483 /** @type {Object<StoredNotificationGroup>} */ | 1472 lastPollNowPayloads: {}, |
| 1484 notificationGroups: {} | 1473 /** @type {Object<StoredNotificationGroup>} */ |
| 1485 }, PromiseRejection.ALLOW).then(function(items) { | 1474 notificationGroups: {} |
| 1486 if (items.lastPollNowPayloads[tag] != payload) { | 1475 }, |
| 1487 items.lastPollNowPayloads[tag] = payload; | 1476 PromiseRejection.ALLOW) |
| 1477 .then(function(items) { |
| 1478 if (items.lastPollNowPayloads[tag] != payload) { |
| 1479 items.lastPollNowPayloads[tag] = payload; |
| 1488 | 1480 |
| 1489 items.notificationGroups['PUSH' + tag] = { | 1481 items.notificationGroups['PUSH' + tag] = { |
| 1490 cards: [], | 1482 cards: [], |
| 1491 nextPollTime: Date.now() | 1483 nextPollTime: Date.now() |
| 1492 }; | 1484 }; |
| 1493 | 1485 |
| 1494 chrome.storage.local.set({ | 1486 chrome.storage.local.set({ |
| 1495 lastPollNowPayloads: items.lastPollNowPayloads, | 1487 lastPollNowPayloads: items.lastPollNowPayloads, |
| 1496 notificationGroups: items.notificationGroups | 1488 notificationGroups: items.notificationGroups |
| 1489 }); |
| 1490 |
| 1491 pollOptedInWithRecheck(); |
| 1492 } |
| 1497 }); | 1493 }); |
| 1498 | |
| 1499 pollOptedInWithRecheck(); | |
| 1500 } | |
| 1501 }); | |
| 1502 }); | 1494 }); |
| 1503 } | 1495 } |
| 1504 }); | 1496 }); |
| OLD | NEW |