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. |
11 * The service performs periodic updating of Google Now cards. | 11 * The service performs periodic updating of Google Now cards. |
12 * Each updating of the cards includes 4 steps: | 12 * Each updating of the cards includes 4 steps: |
13 * 1. Obtaining the location of the machine; | 13 * 1. Obtaining the location of the machine; |
14 * 2. Processing requests for cards dismissals that are not yet sent to the | 14 * 2. Processing requests for cards dismissals that are not yet sent to the |
15 * server; | 15 * server; |
16 * 3. Making a server request based on that location; | 16 * 3. Making a server request based on that location; |
17 * 4. Showing the received cards as notifications. | 17 * 4. Showing the received cards as notifications. |
18 */ | 18 */ |
19 | 19 |
20 // TODO(vadimt): Use background permission to show notifications even when all | 20 // TODO(vadimt): Use background permission to show notifications even when all |
21 // browser windows are closed. | 21 // browser windows are closed. |
22 // TODO(vadimt): Decide what to do in incognito mode. | 22 // TODO(vadimt): Decide what to do in incognito mode. |
23 // TODO(vadimt): Honor the flag the enables Google Now integration. | 23 // TODO(vadimt): Honor the flag the enables Google Now integration. |
24 // TODO(vadimt): Figure out the final values of the constants. | 24 // TODO(vadimt): Figure out the final values of the constants. |
25 // TODO(vadimt): Remove 'console' calls. | 25 // TODO(vadimt): Remove 'console' calls. |
26 // TODO(vadimt): Consider sending JS stacks for chrome.* API errors and | 26 // TODO(vadimt): Consider sending JS stacks for malformed server responses. |
27 // malformed server responses. | |
28 | 27 |
29 /** | 28 /** |
30 * Standard response code for successful HTTP requests. This is the only success | 29 * Standard response code for successful HTTP requests. This is the only success |
31 * code the server will send. | 30 * code the server will send. |
32 */ | 31 */ |
33 var HTTP_OK = 200; | 32 var HTTP_OK = 200; |
34 | 33 |
35 var HTTP_BAD_REQUEST = 400; | 34 var HTTP_BAD_REQUEST = 400; |
36 var HTTP_UNAUTHORIZED = 401; | 35 var HTTP_UNAUTHORIZED = 401; |
37 var HTTP_FORBIDDEN = 403; | 36 var HTTP_FORBIDDEN = 403; |
(...skipping 213 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
251 if (typeof parsedResponse.next_poll_seconds != 'number') { | 250 if (typeof parsedResponse.next_poll_seconds != 'number') { |
252 callback(); | 251 callback(); |
253 return; | 252 return; |
254 } | 253 } |
255 | 254 |
256 tasks.debugSetStepName('parseAndShowNotificationCards-storage-get'); | 255 tasks.debugSetStepName('parseAndShowNotificationCards-storage-get'); |
257 instrumented.storage.local.get(['notificationsData', 'recentDismissals'], | 256 instrumented.storage.local.get(['notificationsData', 'recentDismissals'], |
258 function(items) { | 257 function(items) { |
259 console.log('parseAndShowNotificationCards-get ' + | 258 console.log('parseAndShowNotificationCards-get ' + |
260 JSON.stringify(items)); | 259 JSON.stringify(items)); |
261 items.notificationsData = items.notificationsData || {}; | 260 items.notificationsData = items.notificationsData || {}; |
robliao
2013/08/22 00:01:56
Is a check here necessary too?
vadimt
2013/08/22 01:24:28
Yes, but this would cause conflicts with other CLs
| |
262 items.recentDismissals = items.recentDismissals || {}; | 261 items.recentDismissals = items.recentDismissals || {}; |
263 | 262 |
264 tasks.debugSetStepName( | 263 tasks.debugSetStepName( |
265 'parseAndShowNotificationCards-notifications-getAll'); | 264 'parseAndShowNotificationCards-notifications-getAll'); |
266 instrumented.notifications.getAll(function(notifications) { | 265 instrumented.notifications.getAll(function(notifications) { |
267 console.log('parseAndShowNotificationCards-getAll ' + | 266 console.log('parseAndShowNotificationCards-getAll ' + |
268 JSON.stringify(notifications)); | 267 JSON.stringify(notifications)); |
269 // TODO(vadimt): Figure out what to do when notifications are | 268 // TODO(vadimt): Figure out what to do when notifications are |
270 // disabled for our extension. | 269 // disabled for our extension. |
271 notifications = notifications || {}; | 270 notifications = notifications || {}; |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
339 * For example, this occurs when the geolocation preference is unchecked in the | 338 * For example, this occurs when the geolocation preference is unchecked in the |
340 * content settings. | 339 * content settings. |
341 */ | 340 */ |
342 function removeAllCards() { | 341 function removeAllCards() { |
343 console.log('removeAllCards'); | 342 console.log('removeAllCards'); |
344 | 343 |
345 // TODO(robliao): Once Google Now clears its own checkbox in the | 344 // TODO(robliao): Once Google Now clears its own checkbox in the |
346 // notifications center and bug 260376 is fixed, the below clearing | 345 // notifications center and bug 260376 is fixed, the below clearing |
347 // code is no longer necessary. | 346 // code is no longer necessary. |
348 instrumented.notifications.getAll(function(notifications) { | 347 instrumented.notifications.getAll(function(notifications) { |
348 notifications = notifications || {}; | |
349 for (var notificationId in notifications) { | 349 for (var notificationId in notifications) { |
350 chrome.notifications.clear(notificationId, function() {}); | 350 chrome.notifications.clear(notificationId, function() {}); |
351 } | 351 } |
352 chrome.storage.local.set({notificationsData: {}}); | 352 chrome.storage.local.set({notificationsData: {}}); |
353 }); | 353 }); |
354 } | 354 } |
355 | 355 |
356 /** | 356 /** |
357 * Requests notification cards from the server. | 357 * Requests notification cards from the server. |
358 * @param {Location} position Location of this computer. | 358 * @param {Location} position Location of this computer. |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
398 }); | 398 }); |
399 } | 399 } |
400 | 400 |
401 /** | 401 /** |
402 * Starts getting location for a cards update. | 402 * Starts getting location for a cards update. |
403 */ | 403 */ |
404 function requestLocation() { | 404 function requestLocation() { |
405 console.log('requestLocation'); | 405 console.log('requestLocation'); |
406 recordEvent(GoogleNowEvent.LOCATION_REQUEST); | 406 recordEvent(GoogleNowEvent.LOCATION_REQUEST); |
407 // TODO(vadimt): Figure out location request options. | 407 // TODO(vadimt): Figure out location request options. |
408 chrome.metricsPrivate.getVariationParams('GoogleNow', function(params) { | 408 instrumented.metricsPrivate.getVariationParams('GoogleNow', function(params) { |
409 var minDistanceInMeters = | 409 var minDistanceInMeters = |
410 parseInt(params && params.minDistanceInMeters, 10) || | 410 parseInt(params && params.minDistanceInMeters, 10) || |
411 100; | 411 100; |
412 var minTimeInMilliseconds = | 412 var minTimeInMilliseconds = |
413 parseInt(params && params.minTimeInMilliseconds, 10) || | 413 parseInt(params && params.minTimeInMilliseconds, 10) || |
414 180000; // 3 minutes. | 414 180000; // 3 minutes. |
415 | 415 |
416 chrome.location.watchLocation(LOCATION_WATCH_NAME, { | 416 chrome.location.watchLocation(LOCATION_WATCH_NAME, { |
417 minDistanceInMeters: minDistanceInMeters, | 417 minDistanceInMeters: minDistanceInMeters, |
418 minTimeInMilliseconds: minTimeInMilliseconds | 418 minTimeInMilliseconds: minTimeInMilliseconds |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
511 * Tries to send dismiss requests for all pending dismissals. | 511 * Tries to send dismiss requests for all pending dismissals. |
512 * @param {function(boolean)} callbackBoolean Completion callback with 'success' | 512 * @param {function(boolean)} callbackBoolean Completion callback with 'success' |
513 * parameter. Success means that no pending dismissals are left. | 513 * parameter. Success means that no pending dismissals are left. |
514 */ | 514 */ |
515 function processPendingDismissals(callbackBoolean) { | 515 function processPendingDismissals(callbackBoolean) { |
516 tasks.debugSetStepName('processPendingDismissals-storage-get'); | 516 tasks.debugSetStepName('processPendingDismissals-storage-get'); |
517 instrumented.storage.local.get(['pendingDismissals', 'recentDismissals'], | 517 instrumented.storage.local.get(['pendingDismissals', 'recentDismissals'], |
518 function(items) { | 518 function(items) { |
519 console.log('processPendingDismissals-storage-get ' + | 519 console.log('processPendingDismissals-storage-get ' + |
520 JSON.stringify(items)); | 520 JSON.stringify(items)); |
521 items.pendingDismissals = items.pendingDismissals || []; | 521 items.pendingDismissals = items.pendingDismissals || []; |
robliao
2013/08/22 00:01:56
Should we check items here?
vadimt
2013/08/22 01:24:28
Conflicts, see above.
| |
522 items.recentDismissals = items.recentDismissals || {}; | 522 items.recentDismissals = items.recentDismissals || {}; |
523 | 523 |
524 var dismissalsChanged = false; | 524 var dismissalsChanged = false; |
525 | 525 |
526 function onFinish(success) { | 526 function onFinish(success) { |
527 if (dismissalsChanged) { | 527 if (dismissalsChanged) { |
528 chrome.storage.local.set({ | 528 chrome.storage.local.set({ |
529 pendingDismissals: items.pendingDismissals, | 529 pendingDismissals: items.pendingDismissals, |
530 recentDismissals: items.recentDismissals | 530 recentDismissals: items.recentDismissals |
531 }); | 531 }); |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
575 } | 575 } |
576 | 576 |
577 /** | 577 /** |
578 * Opens URL corresponding to the clicked part of the notification. | 578 * Opens URL corresponding to the clicked part of the notification. |
579 * @param {string} notificationId Unique identifier of the notification. | 579 * @param {string} notificationId Unique identifier of the notification. |
580 * @param {function(Object): string} selector Function that extracts the url for | 580 * @param {function(Object): string} selector Function that extracts the url for |
581 * the clicked area from the button action URLs info. | 581 * the clicked area from the button action URLs info. |
582 */ | 582 */ |
583 function onNotificationClicked(notificationId, selector) { | 583 function onNotificationClicked(notificationId, selector) { |
584 instrumented.storage.local.get('notificationsData', function(items) { | 584 instrumented.storage.local.get('notificationsData', function(items) { |
585 items.notificationsData = items.notificationsData || {}; | 585 if (!items || !items.notificationsData) |
586 return; | |
586 | 587 |
587 var notificationData = items.notificationsData[notificationId]; | 588 var notificationData = items.notificationsData[notificationId]; |
skare_
2013/08/21 23:12:32
no action required but consider
var nd = items &&
rgustafson
2013/08/22 00:46:14
+1, will return below anyways.
vadimt
2013/08/22 01:24:28
Done.
| |
588 | 589 |
589 if (!notificationData) { | 590 if (!notificationData) { |
590 // 'notificationsData' in storage may not match the actual list of | 591 // 'notificationsData' in storage may not match the actual list of |
591 // notifications. | 592 // notifications. |
592 return; | 593 return; |
593 } | 594 } |
594 | 595 |
595 var actionUrls = notificationData.actionUrls; | 596 var actionUrls = notificationData.actionUrls; |
596 if (typeof actionUrls != 'object') { | 597 if (typeof actionUrls != 'object') { |
597 return; | 598 return; |
598 } | 599 } |
599 | 600 |
600 var url = selector(actionUrls); | 601 var url = selector(actionUrls); |
601 | 602 if (!url) |
rgustafson
2013/08/22 00:46:14
This is verified already in the selector. if state
vadimt
2013/08/22 01:24:28
Not in selector in 'instrumented.notifications.onC
| |
602 if (typeof url != 'string') | |
603 return; | 603 return; |
604 | 604 |
605 instrumented.tabs.create({url: url}, function(tab) { | 605 instrumented.tabs.create({url: url}, function(tab) { |
606 if (tab) | 606 if (tab) |
607 chrome.windows.update(tab.windowId, {focused: true}); | 607 chrome.windows.update(tab.windowId, {focused: true}); |
608 else | 608 else |
609 chrome.windows.create({url: url, focused: true}); | 609 chrome.windows.create({url: url, focused: true}); |
610 }); | 610 }); |
611 }); | 611 }); |
612 } | 612 } |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
652 tasks.add(DISMISS_CARD_TASK_NAME, function(callback) { | 652 tasks.add(DISMISS_CARD_TASK_NAME, function(callback) { |
653 dismissalAttempts.start(); | 653 dismissalAttempts.start(); |
654 | 654 |
655 // Deleting the notification in case it was re-added while this task was | 655 // Deleting the notification in case it was re-added while this task was |
656 // scheduled, waiting for execution. | 656 // scheduled, waiting for execution. |
657 cardSet.clear(notificationId); | 657 cardSet.clear(notificationId); |
658 | 658 |
659 tasks.debugSetStepName('onNotificationClosed-storage-get'); | 659 tasks.debugSetStepName('onNotificationClosed-storage-get'); |
660 instrumented.storage.local.get(['pendingDismissals', 'notificationsData'], | 660 instrumented.storage.local.get(['pendingDismissals', 'notificationsData'], |
661 function(items) { | 661 function(items) { |
662 items.pendingDismissals = items.pendingDismissals || []; | 662 items.pendingDismissals = items.pendingDismissals || []; |
robliao
2013/08/22 00:01:56
Check?
vadimt
2013/08/22 01:24:28
Conflicts.
| |
663 items.notificationsData = items.notificationsData || {}; | 663 items.notificationsData = items.notificationsData || {}; |
664 | 664 |
665 var notificationData = items.notificationsData[notificationId]; | 665 var notificationData = items.notificationsData[notificationId]; |
666 | 666 |
667 var dismissal = { | 667 var dismissal = { |
668 notificationId: notificationId, | 668 notificationId: notificationId, |
669 time: Date.now(), | 669 time: Date.now(), |
670 parameters: notificationData && notificationData.dismissalParameters | 670 parameters: notificationData && notificationData.dismissalParameters |
671 }; | 671 }; |
672 items.pendingDismissals.push(dismissal); | 672 items.pendingDismissals.push(dismissal); |
(...skipping 200 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
873 'GoogleNow', | 873 'GoogleNow', |
874 function(response) { | 874 function(response) { |
875 var enableBackground = | 875 var enableBackground = |
876 (!response || (response.enableBackground != 'false')); | 876 (!response || (response.enableBackground != 'false')); |
877 tasks.debugSetStepName( | 877 tasks.debugSetStepName( |
878 'onStateChange-get-googleGeolocationAccessEnabledPref'); | 878 'onStateChange-get-googleGeolocationAccessEnabledPref'); |
879 instrumented. | 879 instrumented. |
880 preferencesPrivate. | 880 preferencesPrivate. |
881 googleGeolocationAccessEnabled. | 881 googleGeolocationAccessEnabled. |
882 get({}, function(prefValue) { | 882 get({}, function(prefValue) { |
883 var geolocationEnabled = !!prefValue.value; | 883 var geolocationEnabled = !!prefValue.value; |
robliao
2013/08/22 00:01:56
Is any Chrome API subject to returning undefined?
vadimt
2013/08/22 01:24:28
No. Only those that are explicitly implemented to
robliao
2013/08/22 18:01:13
As written, it cannot fail.
On 2013/08/22 01:24:28
| |
884 tasks.debugSetStepName( | 884 tasks.debugSetStepName( |
885 'onStateChange-get-userRespondedToToast'); | 885 'onStateChange-get-userRespondedToToast'); |
886 instrumented.storage.local.get( | 886 instrumented.storage.local.get( |
887 'userRespondedToToast', | 887 'userRespondedToToast', |
888 function(items) { | 888 function(items) { |
889 var userRespondedToToast = !!items.userRespondedToToast; | 889 var userRespondedToToast = |
890 !items || !!items.userRespondedToToast; | |
rgustafson
2013/08/22 00:46:14
Could this get the user in a weird state if there
vadimt
2013/08/22 01:24:28
Deep comment again. If we are polling, the final s
rgustafson
2013/08/22 19:07:32
We'll hide the toast and disable ourselves for jus
vadimt
2013/08/22 19:34:55
https://code.google.com/p/chromium/issues/detail?i
| |
890 updateRunningState( | 891 updateRunningState( |
891 signedIn, | 892 signedIn, |
892 geolocationEnabled, | 893 geolocationEnabled, |
893 userRespondedToToast, | 894 userRespondedToToast, |
894 enableBackground, | 895 enableBackground, |
895 callback); | 896 callback); |
896 }); | 897 }); |
897 }); | 898 }); |
898 }); | 899 }); |
899 }); | 900 }); |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
967 }); | 968 }); |
968 | 969 |
969 instrumented.notifications.onButtonClicked.addListener( | 970 instrumented.notifications.onButtonClicked.addListener( |
970 function(notificationId, buttonIndex) { | 971 function(notificationId, buttonIndex) { |
971 if (notificationId == WELCOME_TOAST_NOTIFICATION_ID) { | 972 if (notificationId == WELCOME_TOAST_NOTIFICATION_ID) { |
972 onToastNotificationClicked(buttonIndex); | 973 onToastNotificationClicked(buttonIndex); |
973 } else { | 974 } else { |
974 chrome.metricsPrivate.recordUserAction( | 975 chrome.metricsPrivate.recordUserAction( |
975 'GoogleNow.ButtonClicked' + buttonIndex); | 976 'GoogleNow.ButtonClicked' + buttonIndex); |
976 onNotificationClicked(notificationId, function(actionUrls) { | 977 onNotificationClicked(notificationId, function(actionUrls) { |
977 if (!Array.isArray(actionUrls.buttonUrls)) | 978 verify(actionUrls.buttonUrls[buttonIndex], |
rgustafson
2013/08/22 00:46:14
pull actionUrls.buttonUrls[buttonIndex] out into u
vadimt
2013/08/22 01:24:28
Done.
| |
978 return undefined; | 979 'onButtonClicked: no url for a button'); |
979 | |
980 return actionUrls.buttonUrls[buttonIndex]; | 980 return actionUrls.buttonUrls[buttonIndex]; |
981 }); | 981 }); |
982 } | 982 } |
983 }); | 983 }); |
984 | 984 |
985 instrumented.notifications.onClosed.addListener(onNotificationClosed); | 985 instrumented.notifications.onClosed.addListener(onNotificationClosed); |
986 | 986 |
987 instrumented.location.onLocationUpdate.addListener(function(position) { | 987 instrumented.location.onLocationUpdate.addListener(function(position) { |
988 recordEvent(GoogleNowEvent.LOCATION_UPDATE); | 988 recordEvent(GoogleNowEvent.LOCATION_UPDATE); |
989 updateNotificationsCards(position); | 989 updateNotificationsCards(position); |
990 }); | 990 }); |
991 | 991 |
992 instrumented.omnibox.onInputEntered.addListener(function(text) { | 992 instrumented.omnibox.onInputEntered.addListener(function(text) { |
993 localStorage['server_url'] = NOTIFICATION_CARDS_URL = text; | 993 localStorage['server_url'] = NOTIFICATION_CARDS_URL = text; |
994 initialize(); | 994 initialize(); |
995 }); | 995 }); |
OLD | NEW |