OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 // 'use strict'; TODO(vadimt): Uncomment once crbug.com/237617 is fixed. | 5 // 'use strict'; TODO(vadimt): Uncomment once crbug.com/237617 is fixed. |
6 | 6 |
7 /** | 7 /** |
8 * @fileoverview The event page for Google Now for Chrome implementation. | 8 * @fileoverview The event page for Google Now for Chrome implementation. |
9 * The Google Now event page gets Google Now cards from the server and shows | 9 * The Google Now event page gets Google Now cards from the server and shows |
10 * them as Chrome notifications. | 10 * them as Chrome notifications. |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
57 /** | 57 /** |
58 * Time we keep dismissals after successful server dismiss requests. | 58 * Time we keep dismissals after successful server dismiss requests. |
59 */ | 59 */ |
60 var DISMISS_RETENTION_TIME_MS = 20 * 60 * 1000; // 20 minutes | 60 var DISMISS_RETENTION_TIME_MS = 20 * 60 * 1000; // 20 minutes |
61 | 61 |
62 /** | 62 /** |
63 * Names for tasks that can be created by the extension. | 63 * Names for tasks that can be created by the extension. |
64 */ | 64 */ |
65 var UPDATE_CARDS_TASK_NAME = 'update-cards'; | 65 var UPDATE_CARDS_TASK_NAME = 'update-cards'; |
66 var DISMISS_CARD_TASK_NAME = 'dismiss-card'; | 66 var DISMISS_CARD_TASK_NAME = 'dismiss-card'; |
67 var CARD_CLICKED_TASK_NAME = 'card-clicked'; | |
68 var RETRY_DISMISS_TASK_NAME = 'retry-dismiss'; | 67 var RETRY_DISMISS_TASK_NAME = 'retry-dismiss'; |
69 | 68 |
70 var LOCATION_WATCH_NAME = 'location-watch'; | 69 var LOCATION_WATCH_NAME = 'location-watch'; |
71 | 70 |
72 /** | 71 /** |
73 * Checks if a new task can't be scheduled when another task is already | 72 * Checks if a new task can't be scheduled when another task is already |
74 * scheduled. | 73 * scheduled. |
75 * @param {string} newTaskName Name of the new task. | 74 * @param {string} newTaskName Name of the new task. |
76 * @param {string} scheduledTaskName Name of the scheduled task. | 75 * @param {string} scheduledTaskName Name of the scheduled task. |
77 * @return {boolean} Whether the new task conflicts with the existing task. | 76 * @return {boolean} Whether the new task conflicts with the existing task. |
(...skipping 17 matching lines...) Expand all Loading... |
95 | 94 |
96 return false; | 95 return false; |
97 } | 96 } |
98 | 97 |
99 var tasks = buildTaskManager(areTasksConflicting); | 98 var tasks = buildTaskManager(areTasksConflicting); |
100 | 99 |
101 // Add error processing to API calls. | 100 // Add error processing to API calls. |
102 tasks.instrumentApiFunction(chrome.location.onLocationUpdate, 'addListener', 0); | 101 tasks.instrumentApiFunction(chrome.location.onLocationUpdate, 'addListener', 0); |
103 tasks.instrumentApiFunction(chrome.notifications, 'create', 2); | 102 tasks.instrumentApiFunction(chrome.notifications, 'create', 2); |
104 tasks.instrumentApiFunction(chrome.notifications, 'update', 2); | 103 tasks.instrumentApiFunction(chrome.notifications, 'update', 2); |
| 104 tasks.instrumentApiFunction(chrome.notifications, 'getAll', 0); |
105 tasks.instrumentApiFunction( | 105 tasks.instrumentApiFunction( |
106 chrome.notifications.onButtonClicked, 'addListener', 0); | 106 chrome.notifications.onButtonClicked, 'addListener', 0); |
107 tasks.instrumentApiFunction(chrome.notifications.onClicked, 'addListener', 0); | 107 tasks.instrumentApiFunction(chrome.notifications.onClicked, 'addListener', 0); |
108 tasks.instrumentApiFunction(chrome.notifications.onClosed, 'addListener', 0); | 108 tasks.instrumentApiFunction(chrome.notifications.onClosed, 'addListener', 0); |
109 tasks.instrumentApiFunction(chrome.runtime.onInstalled, 'addListener', 0); | 109 tasks.instrumentApiFunction(chrome.runtime.onInstalled, 'addListener', 0); |
110 tasks.instrumentApiFunction(chrome.runtime.onStartup, 'addListener', 0); | 110 tasks.instrumentApiFunction(chrome.runtime.onStartup, 'addListener', 0); |
111 tasks.instrumentApiFunction(chrome.tabs, 'create', 1); | 111 tasks.instrumentApiFunction(chrome.tabs, 'create', 1); |
112 tasks.instrumentApiFunction(storage, 'get', 1); | 112 tasks.instrumentApiFunction(storage, 'get', 1); |
113 | 113 |
114 var updateCardsAttempts = buildAttemptManager( | 114 var updateCardsAttempts = buildAttemptManager( |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
153 | 153 |
154 chrome.metricsPrivate.recordValue(metricDescription, event); | 154 chrome.metricsPrivate.recordValue(metricDescription, event); |
155 } | 155 } |
156 | 156 |
157 /** | 157 /** |
158 * Shows a notification and remembers information associated with it. | 158 * Shows a notification and remembers information associated with it. |
159 * @param {Object} card Google Now card represented as a set of parameters for | 159 * @param {Object} card Google Now card represented as a set of parameters for |
160 * showing a Chrome notification. | 160 * showing a Chrome notification. |
161 * @param {Object} notificationsData Map from notification id to the data | 161 * @param {Object} notificationsData Map from notification id to the data |
162 * associated with a notification. | 162 * associated with a notification. |
163 * @param {number} previousVersion The version of the shown card with this id, | 163 * @param {number=} opt_previousVersion The version of the shown card with this |
164 * if it exists, undefined otherwise. | 164 * id, if it exists, undefined otherwise. |
165 */ | 165 */ |
166 function showNotification(card, notificationsData, previousVersion) { | 166 function showNotification(card, notificationsData, opt_previousVersion) { |
167 console.log('showNotification ' + JSON.stringify(card) + ' ' + | 167 console.log('showNotification ' + JSON.stringify(card) + ' ' + |
168 previousVersion); | 168 opt_previousVersion); |
169 | 169 |
170 if (typeof card.version != 'number') { | 170 if (typeof card.version != 'number') { |
171 console.error('card.version is not a number'); | 171 console.error('card.version is not a number'); |
172 // Fix card version. | 172 // Fix card version. |
173 card.version = previousVersion !== undefined ? previousVersion : 0; | 173 card.version = opt_previousVersion !== undefined ? opt_previousVersion : 0; |
174 } | 174 } |
175 | 175 |
176 if (previousVersion !== card.version) { | 176 if (opt_previousVersion !== card.version) { |
177 try { | 177 try { |
178 // Delete a notification with the specified id if it already exists, and | 178 // Delete a notification with the specified id if it already exists, and |
179 // then create a notification. | 179 // then create a notification. |
180 chrome.notifications.create( | 180 chrome.notifications.create( |
181 card.notificationId, | 181 card.notificationId, |
182 card.notification, | 182 card.notification, |
183 function(notificationId) { | 183 function(notificationId) { |
184 if (!notificationId || chrome.runtime.lastError) { | 184 if (!notificationId || chrome.runtime.lastError) { |
185 var errorMessage = | 185 var errorMessage = |
186 chrome.runtime.lastError && chrome.runtime.lastError.message; | 186 chrome.runtime.lastError && chrome.runtime.lastError.message; |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
238 callback(); | 238 callback(); |
239 return; | 239 return; |
240 } | 240 } |
241 | 241 |
242 if (typeof parsedResponse.expiration_timestamp_seconds != 'number') { | 242 if (typeof parsedResponse.expiration_timestamp_seconds != 'number') { |
243 callback(); | 243 callback(); |
244 return; | 244 return; |
245 } | 245 } |
246 | 246 |
247 tasks.debugSetStepName('parseAndShowNotificationCards-storage-get'); | 247 tasks.debugSetStepName('parseAndShowNotificationCards-storage-get'); |
248 storage.get(['activeNotifications', 'recentDismissals'], function(items) { | 248 storage.get(['notificationsData', 'recentDismissals'], function(items) { |
249 console.log('parseAndShowNotificationCards-get ' + JSON.stringify(items)); | 249 console.log('parseAndShowNotificationCards-get ' + JSON.stringify(items)); |
250 items.activeNotifications = items.activeNotifications || {}; | 250 items.notificationsData = items.notificationsData || {}; |
251 items.recentDismissals = items.recentDismissals || {}; | 251 items.recentDismissals = items.recentDismissals || {}; |
252 | 252 |
253 // Build a set of non-expired recent dismissals. It will be used for | 253 tasks.debugSetStepName( |
254 // client-side filtering of cards. | 254 'parseAndShowNotificationCards-notifications-getAll'); |
255 var updatedRecentDismissals = {}; | 255 chrome.notifications.getAll(function(notifications) { |
256 var currentTimeMs = Date.now(); | 256 console.log('parseAndShowNotificationCards-getAll ' + |
257 for (var notificationId in items.recentDismissals) { | 257 JSON.stringify(notifications)); |
258 if (currentTimeMs - items.recentDismissals[notificationId] < | 258 // Build a set of non-expired recent dismissals. It will be used for |
259 DISMISS_RETENTION_TIME_MS) { | 259 // client-side filtering of cards. |
260 updatedRecentDismissals[notificationId] = | 260 var updatedRecentDismissals = {}; |
261 items.recentDismissals[notificationId]; | 261 var currentTimeMs = Date.now(); |
| 262 for (var notificationId in items.recentDismissals) { |
| 263 if (currentTimeMs - items.recentDismissals[notificationId] < |
| 264 DISMISS_RETENTION_TIME_MS) { |
| 265 updatedRecentDismissals[notificationId] = |
| 266 items.recentDismissals[notificationId]; |
| 267 } |
262 } | 268 } |
263 } | |
264 | 269 |
265 // Mark existing notifications that received an update in this server | 270 // Mark existing notifications that received an update in this server |
266 // response. | 271 // response. |
267 for (var i = 0; i < cards.length; ++i) { | 272 var updatedNotifications = {}; |
268 var notificationId = cards[i].notificationId; | 273 |
269 if (!(notificationId in updatedRecentDismissals) && | 274 for (var i = 0; i < cards.length; ++i) { |
270 notificationId in items.activeNotifications) { | 275 var notificationId = cards[i].notificationId; |
271 items.activeNotifications[notificationId].hasUpdate = true; | 276 if (!(notificationId in updatedRecentDismissals) && |
| 277 notificationId in notifications) { |
| 278 updatedNotifications[notificationId] = true; |
| 279 } |
272 } | 280 } |
273 } | |
274 | 281 |
275 // Delete notifications that didn't receive an update. | 282 // Delete notifications that didn't receive an update. |
276 for (var notificationId in items.activeNotifications) { | 283 for (var notificationId in notifications) { |
277 console.log('parseAndShowNotificationCards-delete-check ' + | 284 console.log('parseAndShowNotificationCards-delete-check ' + |
278 notificationId); | 285 notificationId); |
279 if (!items.activeNotifications[notificationId].hasUpdate) { | 286 if (!(notificationId in updatedNotifications)) { |
280 console.log('parseAndShowNotificationCards-delete ' + notificationId); | 287 console.log('parseAndShowNotificationCards-delete ' + notificationId); |
281 chrome.notifications.clear( | 288 chrome.notifications.clear( |
282 notificationId, | 289 notificationId, |
283 function() {}); | 290 function() {}); |
| 291 } |
284 } | 292 } |
285 } | |
286 | 293 |
287 recordEvent(DiagnosticEvent.CARDS_PARSE_SUCCESS); | 294 recordEvent(DiagnosticEvent.CARDS_PARSE_SUCCESS); |
288 | 295 |
289 // Create/update notifications and store their new properties. | 296 // Create/update notifications and store their new properties. |
290 var notificationsData = {}; | 297 var newNotificationsData = {}; |
291 for (var i = 0; i < cards.length; ++i) { | 298 for (var i = 0; i < cards.length; ++i) { |
292 var card = cards[i]; | 299 var card = cards[i]; |
293 if (!(card.notificationId in updatedRecentDismissals)) { | 300 if (!(card.notificationId in updatedRecentDismissals)) { |
294 var activeNotification = items.activeNotifications[card.notificationId]; | 301 var notificationData = items.notificationsData[card.notificationId]; |
295 showNotification(card, | 302 var previousVersion = notifications[card.notificationId] && |
296 notificationsData, | 303 notificationData && |
297 activeNotification && activeNotification.version); | 304 notificationData.previousVersion; |
| 305 showNotification(card, newNotificationsData, previousVersion); |
| 306 } |
298 } | 307 } |
299 } | |
300 | 308 |
301 updateCardsAttempts.start(parsedResponse.expiration_timestamp_seconds); | 309 updateCardsAttempts.start(parsedResponse.expiration_timestamp_seconds); |
302 | 310 |
303 storage.set({ | 311 storage.set({ |
304 activeNotifications: notificationsData, | 312 notificationsData: newNotificationsData, |
305 recentDismissals: updatedRecentDismissals | 313 recentDismissals: updatedRecentDismissals |
| 314 }); |
| 315 callback(); |
306 }); | 316 }); |
307 callback(); | |
308 }); | 317 }); |
309 } | 318 } |
310 | 319 |
311 /** | 320 /** |
312 * Requests notification cards from the server. | 321 * Requests notification cards from the server. |
313 * @param {Location} position Location of this computer. | 322 * @param {Location} position Location of this computer. |
314 * @param {function()} callback Completion callback. | 323 * @param {function()} callback Completion callback. |
315 */ | 324 */ |
316 function requestNotificationCards(position, callback) { | 325 function requestNotificationCards(position, callback) { |
317 console.log('requestNotificationCards ' + JSON.stringify(position) + | 326 console.log('requestNotificationCards ' + JSON.stringify(position) + |
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
473 }); | 482 }); |
474 } | 483 } |
475 | 484 |
476 /** | 485 /** |
477 * Opens URL corresponding to the clicked part of the notification. | 486 * Opens URL corresponding to the clicked part of the notification. |
478 * @param {string} notificationId Unique identifier of the notification. | 487 * @param {string} notificationId Unique identifier of the notification. |
479 * @param {function(Object): string} selector Function that extracts the url for | 488 * @param {function(Object): string} selector Function that extracts the url for |
480 * the clicked area from the button action URLs info. | 489 * the clicked area from the button action URLs info. |
481 */ | 490 */ |
482 function onNotificationClicked(notificationId, selector) { | 491 function onNotificationClicked(notificationId, selector) { |
483 tasks.add(CARD_CLICKED_TASK_NAME, function(callback) { | 492 storage.get('notificationsData', function(items) { |
484 tasks.debugSetStepName('onNotificationClicked-get-activeNotifications'); | 493 items.notificationsData = items.notificationsData || {}; |
485 storage.get('activeNotifications', function(items) { | |
486 items.activeNotifications = items.activeNotifications || {}; | |
487 | 494 |
488 var actionUrls = items.activeNotifications[notificationId].actionUrls; | 495 var notificationData = items.notificationsData[notificationId]; |
489 if (typeof actionUrls != 'object') { | |
490 callback(); | |
491 return; | |
492 } | |
493 | 496 |
494 var url = selector(actionUrls); | 497 if (!notificationData) { |
| 498 // 'notificationsData' in storage may not match the actual list of |
| 499 // notifications. |
| 500 return; |
| 501 } |
495 | 502 |
496 if (typeof url != 'string') { | 503 var actionUrls = notificationData.actionUrls; |
497 callback(); | 504 if (typeof actionUrls != 'object') { |
498 return; | 505 return; |
499 } | 506 } |
500 | 507 |
501 chrome.tabs.create({url: url}, function(tab) { | 508 var url = selector(actionUrls); |
502 if (!tab) | 509 |
503 chrome.windows.create({url: url}); | 510 if (typeof url != 'string') |
504 }); | 511 return; |
505 callback(); | 512 |
| 513 chrome.tabs.create({url: url}, function(tab) { |
| 514 if (!tab) |
| 515 chrome.windows.create({url: url}); |
506 }); | 516 }); |
507 }); | 517 }); |
508 } | 518 } |
509 | 519 |
510 /** | 520 /** |
511 * Callback for chrome.notifications.onClosed event. | 521 * Callback for chrome.notifications.onClosed event. |
512 * @param {string} notificationId Unique identifier of the notification. | 522 * @param {string} notificationId Unique identifier of the notification. |
513 * @param {boolean} byUser Whether the notification was closed by the user. | 523 * @param {boolean} byUser Whether the notification was closed by the user. |
514 */ | 524 */ |
515 function onNotificationClosed(notificationId, byUser) { | 525 function onNotificationClosed(notificationId, byUser) { |
(...skipping 27 matching lines...) Expand all Loading... |
543 } | 553 } |
544 | 554 |
545 /** | 555 /** |
546 * Initializes the event page on install or on browser startup. | 556 * Initializes the event page on install or on browser startup. |
547 */ | 557 */ |
548 function initialize() { | 558 function initialize() { |
549 // Create an update timer for a case when for some reason location request | 559 // Create an update timer for a case when for some reason location request |
550 // gets stuck. | 560 // gets stuck. |
551 updateCardsAttempts.start(MAXIMUM_POLLING_PERIOD_SECONDS); | 561 updateCardsAttempts.start(MAXIMUM_POLLING_PERIOD_SECONDS); |
552 | 562 |
553 var initialStorage = { | |
554 activeNotifications: {} | |
555 }; | |
556 storage.set(initialStorage); | |
557 | |
558 requestLocation(); | 563 requestLocation(); |
559 } | 564 } |
560 | 565 |
561 chrome.runtime.onInstalled.addListener(function(details) { | 566 chrome.runtime.onInstalled.addListener(function(details) { |
562 console.log('onInstalled ' + JSON.stringify(details)); | 567 console.log('onInstalled ' + JSON.stringify(details)); |
563 if (details.reason != 'chrome_update') { | 568 if (details.reason != 'chrome_update') { |
564 initialize(); | 569 initialize(); |
565 } | 570 } |
566 }); | 571 }); |
567 | 572 |
(...skipping 26 matching lines...) Expand all Loading... |
594 | 599 |
595 chrome.location.onLocationUpdate.addListener(function(position) { | 600 chrome.location.onLocationUpdate.addListener(function(position) { |
596 recordEvent(DiagnosticEvent.LOCATION_UPDATE); | 601 recordEvent(DiagnosticEvent.LOCATION_UPDATE); |
597 updateNotificationsCards(position); | 602 updateNotificationsCards(position); |
598 }); | 603 }); |
599 | 604 |
600 chrome.omnibox.onInputEntered.addListener(function(text) { | 605 chrome.omnibox.onInputEntered.addListener(function(text) { |
601 localStorage['server_url'] = NOTIFICATION_CARDS_URL = text; | 606 localStorage['server_url'] = NOTIFICATION_CARDS_URL = text; |
602 initialize(); | 607 initialize(); |
603 }); | 608 }); |
OLD | NEW |