| 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 3 steps: | 12 * Each updating of the cards includes 3 steps: |
| 13 * 1. Obtaining the location of the machine; | 13 * 1. Obtaining the location of the machine; |
| 14 * 2. Making a server request based on that location; | 14 * 2. Making a server request based on that location; |
| 15 * 3. Showing the received cards as notifications. | 15 * 3. Showing the received cards as notifications. |
| 16 */ | 16 */ |
| 17 | 17 |
| 18 // TODO(vadimt): Use background permission to show notifications even when all | 18 // TODO(vadimt): Use background permission to show notifications even when all |
| 19 // browser windows are closed. | 19 // browser windows are closed. |
| 20 // TODO(vadimt): Remove the C++ implementation. | 20 // TODO(vadimt): Remove the C++ implementation. |
| 21 // TODO(vadimt): Decide what to do in incognito mode. | 21 // TODO(vadimt): Decide what to do in incognito mode. |
| 22 // TODO(vadimt): Gather UMAs. | 22 // TODO(vadimt): Gather UMAs. |
| 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): Report internal and server errors. Collect UMAs on errors where | 25 // TODO(vadimt): Report internal and server errors. Collect UMAs on errors where |
| 26 // appropriate. Also consider logging errors or throwing exceptions. | 26 // appropriate. Also consider logging errors or throwing exceptions. |
| 27 | |
| 28 // TODO(vadimt): Consider processing errors for all storage.set calls. | 27 // TODO(vadimt): Consider processing errors for all storage.set calls. |
| 28 |
| 29 // TODO(vadimt): Figure out the server name. Use it in the manifest and for | 29 // TODO(vadimt): Figure out the server name. Use it in the manifest and for |
| 30 // NOTIFICATION_CARDS_URL. Meanwhile, to use the feature, you need to manually | 30 // NOTIFICATION_CARDS_URL. Meanwhile, to use the feature, you need to manually |
| 31 // set the server name via local storage. | 31 // set the server name via local storage. |
| 32 /** | 32 /** |
| 33 * URL to retrieve notification cards. | 33 * URL to retrieve notification cards. |
| 34 */ | 34 */ |
| 35 var NOTIFICATION_CARDS_URL = localStorage['server_url']; | 35 var NOTIFICATION_CARDS_URL = localStorage['server_url']; |
| 36 | 36 |
| 37 /** | 37 /** |
| 38 * Standard response code for successful HTTP requests. This is the only success | 38 * Standard response code for successful HTTP requests. This is the only success |
| 39 * code the server will send. | 39 * code the server will send. |
| 40 */ | 40 */ |
| 41 var HTTP_OK = 200; | 41 var HTTP_OK = 200; |
| 42 | 42 |
| 43 /** | 43 /** |
| 44 * Initial period for polling for Google Now Notifications cards to use when the | 44 * Initial period for polling for Google Now Notifications cards to use when the |
| 45 * period from the server is not available. | 45 * period from the server is not available. |
| 46 */ | 46 */ |
| 47 var INITIAL_POLLING_PERIOD_SECONDS = 300; // 5 minutes | 47 var INITIAL_POLLING_PERIOD_SECONDS = 300; // 5 minutes |
| 48 | 48 |
| 49 /** | 49 /** |
| 50 * Maximal period for polling for Google Now Notifications cards to use when the | 50 * Maximal period for polling for Google Now Notifications cards to use when the |
| 51 * period from the server is not available. | 51 * period from the server is not available. |
| 52 */ | 52 */ |
| 53 var MAXIMUM_POLLING_PERIOD_SECONDS = 3600; // 1 hour | 53 var MAXIMUM_POLLING_PERIOD_SECONDS = 3600; // 1 hour |
| 54 | 54 |
| 55 var UPDATE_NOTIFICATIONS_ALARM_NAME = 'UPDATE'; |
| 56 |
| 55 var storage = chrome.storage.local; | 57 var storage = chrome.storage.local; |
| 56 | 58 |
| 57 /** | 59 /** |
| 58 * Show a notification and remember information associated with it. | 60 * Names for tasks that can be created by the extension. |
| 61 */ |
| 62 var UPDATE_CARDS_TASK_NAME = 'update-cards'; |
| 63 var DISMISS_CARD_TASK_NAME = 'dismiss-card'; |
| 64 var CARD_CLICKED_TASK_NAME = 'card-clicked'; |
| 65 |
| 66 /** |
| 67 * Checks if a new task can't be scheduled when another task is already |
| 68 * scheduled. |
| 69 * @param {string} newTaskName Name of the new task. |
| 70 * @param {string} queuedTaskName Name of the task in the queue. |
| 71 * @return {boolean} Whether the new task conflicts with the existing task. |
| 72 */ |
| 73 function areTasksConflicting(newTaskName, queuedTaskName) { |
| 74 if (newTaskName == UPDATE_CARDS_TASK_NAME && |
| 75 queuedTaskName == UPDATE_CARDS_TASK_NAME) { |
| 76 // If a card update is requested while an old update is still scheduled, we |
| 77 // don't need the new update. |
| 78 return true; |
| 79 } |
| 80 |
| 81 return false; |
| 82 } |
| 83 |
| 84 var tasks = buildTaskManager(areTasksConflicting); |
| 85 |
| 86 /** |
| 87 * Shows a notification and remembers information associated with it. |
| 59 * @param {Object} card Google Now card represented as a set of parameters for | 88 * @param {Object} card Google Now card represented as a set of parameters for |
| 60 * showing a Chrome notification. | 89 * showing a Chrome notification. |
| 61 * @param {Object} notificationsUrlInfo Map from notification id to the | 90 * @param {Object} notificationsUrlInfo Map from notification id to the |
| 62 * notification's set of URLs. | 91 * notification's set of URLs. |
| 63 */ | 92 */ |
| 64 function createNotification(card, notificationsUrlInfo) { | 93 function createNotification(card, notificationsUrlInfo) { |
| 65 // Create a notification or quietly update if it already exists. | 94 // Create a notification or quietly update if it already exists. |
| 66 // TODO(vadimt): Implement non-quiet updates. | 95 // TODO(vadimt): Implement non-quiet updates. |
| 67 chrome.notifications.create( | 96 chrome.notifications.create( |
| 68 card.notificationId, | 97 card.notificationId, |
| 69 card.notification, | 98 card.notification, |
| 70 function(assignedNotificationId) {}); | 99 function() {}); |
| 71 | 100 |
| 72 notificationsUrlInfo[card.notificationId] = card.actionUrls; | 101 notificationsUrlInfo[card.notificationId] = card.actionUrls; |
| 73 } | 102 } |
| 74 | 103 |
| 75 /** | 104 /** |
| 76 * Parse JSON response from the notification server, show notifications and | 105 * Parses JSON response from the notification server, show notifications and |
| 77 * schedule next update. | 106 * schedule next update. |
| 78 * @param {string} response Server response. | 107 * @param {string} response Server response. |
| 79 */ | 108 * @param {function()} callback Completion callback. |
| 80 function parseAndShowNotificationCards(response) { | 109 */ |
| 110 function parseAndShowNotificationCards(response, callback) { |
| 81 try { | 111 try { |
| 82 var parsedResponse = JSON.parse(response); | 112 var parsedResponse = JSON.parse(response); |
| 83 } catch (error) { | 113 } catch (error) { |
| 84 // TODO(vadimt): Report errors to the user. | 114 // TODO(vadimt): Report errors to the user. |
| 85 return; | 115 return; |
| 86 } | 116 } |
| 87 | 117 |
| 88 var cards = parsedResponse.cards; | 118 var cards = parsedResponse.cards; |
| 89 | 119 |
| 90 if (!(cards instanceof Array)) { | 120 if (!(cards instanceof Array)) { |
| 91 // TODO(vadimt): Report errors to the user. | 121 // TODO(vadimt): Report errors to the user. |
| 92 return; | 122 return; |
| 93 } | 123 } |
| 94 | 124 |
| 95 if (typeof parsedResponse.expiration_timestamp_seconds != 'number') { | 125 if (typeof parsedResponse.expiration_timestamp_seconds != 'number') { |
| 96 // TODO(vadimt): Report errors to the user. | 126 // TODO(vadimt): Report errors to the user. |
| 97 return; | 127 return; |
| 98 } | 128 } |
| 99 | 129 |
| 130 tasks.debugSetStepName( |
| 131 'parseAndShowNotificationCards-get-active-notifications'); |
| 100 storage.get('activeNotifications', function(items) { | 132 storage.get('activeNotifications', function(items) { |
| 101 // Mark existing notifications that received an update in this server | 133 // Mark existing notifications that received an update in this server |
| 102 // response. | 134 // response. |
| 103 for (var i = 0; i < cards.length; ++i) { | 135 for (var i = 0; i < cards.length; ++i) { |
| 104 var notificationId = cards[i].notificationId; | 136 var notificationId = cards[i].notificationId; |
| 105 if (notificationId in items.activeNotifications) | 137 if (notificationId in items.activeNotifications) |
| 106 items.activeNotifications[notificationId].hasUpdate = true; | 138 items.activeNotifications[notificationId].hasUpdate = true; |
| 107 } | 139 } |
| 108 | 140 |
| 109 // Delete notifications that didn't receive an update. | 141 // Delete notifications that didn't receive an update. |
| 110 for (var notificationId in items.activeNotifications) | 142 for (var notificationId in items.activeNotifications) { |
| 111 if (!items.activeNotifications[notificationId].hasUpdate) { | 143 if (!items.activeNotifications[notificationId].hasUpdate) { |
| 112 chrome.notifications.clear( | 144 chrome.notifications.clear( |
| 113 notificationId, | 145 notificationId, |
| 114 function(wasDeleted) {}); | 146 function() {}); |
| 147 } |
| 115 } | 148 } |
| 116 | 149 |
| 117 // Create/update notifications and store their new properties. | 150 // Create/update notifications and store their new properties. |
| 118 var notificationsUrlInfo = {}; | 151 var notificationsUrlInfo = {}; |
| 119 | 152 |
| 120 for (var i = 0; i < cards.length; ++i) { | 153 for (var i = 0; i < cards.length; ++i) { |
| 121 try { | 154 try { |
| 122 createNotification(cards[i], notificationsUrlInfo); | 155 createNotification(cards[i], notificationsUrlInfo); |
| 123 } catch (error) { | 156 } catch (error) { |
| 124 // TODO(vadimt): Report errors to the user. | 157 // TODO(vadimt): Report errors to the user. |
| 125 } | 158 } |
| 126 } | 159 } |
| 127 storage.set({activeNotifications: notificationsUrlInfo}); | 160 storage.set({activeNotifications: notificationsUrlInfo}); |
| 128 | 161 |
| 129 scheduleNextUpdate(parsedResponse.expiration_timestamp_seconds); | 162 scheduleNextUpdate(parsedResponse.expiration_timestamp_seconds); |
| 130 | 163 |
| 131 // Now that we got a valid response from the server, reset the retry period | 164 // Now that we got a valid response from the server, reset the retry period |
| 132 // to the initial value. This retry period will be used the next time we | 165 // to the initial value. This retry period will be used the next time we |
| 133 // fail to get the server-provided period. | 166 // fail to get the server-provided period. |
| 134 storage.set({retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS}); | 167 storage.set({retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS}); |
| 168 callback(); |
| 135 }); | 169 }); |
| 136 } | 170 } |
| 137 | 171 |
| 138 /** | 172 /** |
| 139 * Request notification cards from the server. | 173 * Requests notification cards from the server. |
| 140 * @param {string} requestParameters Query string for the request. | 174 * @param {string} requestParameters Query string for the request. |
| 141 */ | 175 * @param {function()} callback Completion callback. |
| 142 function requestNotificationCards(requestParameters) { | 176 */ |
| 177 function requestNotificationCards(requestParameters, callback) { |
| 143 // TODO(vadimt): Figure out how to send user's identity to the server. | 178 // TODO(vadimt): Figure out how to send user's identity to the server. |
| 144 var request = new XMLHttpRequest(); | 179 var request = new XMLHttpRequest(); |
| 145 | 180 |
| 146 request.responseType = 'text'; | 181 request.responseType = 'text'; |
| 147 request.onload = function(event) { | 182 request.onloadend = function(event) { |
| 148 if (request.status == HTTP_OK) | 183 if (request.status == HTTP_OK) |
| 149 parseAndShowNotificationCards(request.response); | 184 parseAndShowNotificationCards(request.response, callback); |
| 150 } | 185 else |
| 186 callback(); |
| 187 }; |
| 151 | 188 |
| 152 request.open( | 189 request.open( |
| 153 'GET', | 190 'GET', |
| 154 NOTIFICATION_CARDS_URL + '/notifications' + requestParameters, | 191 NOTIFICATION_CARDS_URL + '/notifications' + requestParameters, |
| 155 true); | 192 true); |
| 193 tasks.debugSetStepName('requestNotificationCards-send-request'); |
| 156 request.send(); | 194 request.send(); |
| 157 } | 195 } |
| 158 | 196 |
| 159 /** | 197 /** |
| 160 * Request notification cards from the server when we have geolocation. | 198 * Requests notification cards from the server when we have geolocation. |
| 161 * @param {Geoposition} position Location of this computer. | 199 * @param {Geoposition} position Location of this computer. |
| 162 */ | 200 * @param {function()} callback Completion callback. |
| 163 function requestNotificationCardsWithLocation(position) { | 201 */ |
| 202 function requestNotificationCardsWithLocation(position, callback) { |
| 164 // TODO(vadimt): Should we use 'q' as the parameter name? | 203 // TODO(vadimt): Should we use 'q' as the parameter name? |
| 165 var requestParameters = | 204 var requestParameters = |
| 166 '?q=' + position.coords.latitude + | 205 '?q=' + position.coords.latitude + |
| 167 ',' + position.coords.longitude + | 206 ',' + position.coords.longitude + |
| 168 ',' + position.coords.accuracy; | 207 ',' + position.coords.accuracy; |
| 169 | 208 |
| 170 requestNotificationCards(requestParameters); | 209 requestNotificationCards(requestParameters, callback); |
| 171 } | 210 } |
| 172 | 211 |
| 173 /** | 212 /** |
| 174 * Request notification cards from the server when we don't have geolocation. | 213 * Obtains new location; requests and shows notification cards based on this |
| 175 * @param {PositionError} positionError Position error. | |
| 176 */ | |
| 177 function requestNotificationCardsWithoutLocation(positionError) { | |
| 178 requestNotificationCards(''); | |
| 179 } | |
| 180 | |
| 181 /** | |
| 182 * Obtain new location; request and show notification cards based on this | |
| 183 * location. | 214 * location. |
| 184 */ | 215 */ |
| 185 function updateNotificationsCards() { | 216 function updateNotificationsCards() { |
| 186 storage.get('retryDelaySeconds', function(items) { | 217 tasks.add(UPDATE_CARDS_TASK_NAME, function(callback) { |
| 187 // Immediately schedule the update after the current retry period. Then, | 218 tasks.debugSetStepName('updateNotificationsCards-get-retryDelaySeconds'); |
| 188 // we'll use update time from the server if available. | 219 storage.get('retryDelaySeconds', function(items) { |
| 189 scheduleNextUpdate(items.retryDelaySeconds); | 220 // Immediately schedule the update after the current retry period. Then, |
| 190 | 221 // we'll use update time from the server if available. |
| 191 // TODO(vadimt): Consider interrupting waiting for the next update if we | 222 scheduleNextUpdate(items.retryDelaySeconds); |
| 192 // detect that the network conditions have changed. Also, decide whether the | 223 |
| 193 // exponential backoff is needed both when we are offline and when there are | 224 // TODO(vadimt): Consider interrupting waiting for the next update if we |
| 194 // failures on the server side. | 225 // detect that the network conditions have changed. Also, decide whether |
| 195 var newRetryDelaySeconds = | 226 // the exponential backoff is needed both when we are offline and when |
| 196 Math.min(items.retryDelaySeconds * 2 * (1 + 0.2 * Math.random()), | 227 // there are failures on the server side. |
| 197 MAXIMUM_POLLING_PERIOD_SECONDS); | 228 var newRetryDelaySeconds = |
| 198 storage.set({retryDelaySeconds: newRetryDelaySeconds}); | 229 Math.min(items.retryDelaySeconds * 2 * (1 + 0.2 * Math.random()), |
| 199 | 230 MAXIMUM_POLLING_PERIOD_SECONDS); |
| 200 navigator.geolocation.getCurrentPosition( | 231 storage.set({retryDelaySeconds: newRetryDelaySeconds}); |
| 201 requestNotificationCardsWithLocation, | 232 |
| 202 requestNotificationCardsWithoutLocation); | 233 tasks.debugSetStepName('updateNotificationsCards-get-location'); |
| 234 navigator.geolocation.getCurrentPosition( |
| 235 function(position) { |
| 236 requestNotificationCardsWithLocation(position, callback); |
| 237 }, |
| 238 function() { |
| 239 requestNotificationCards('', callback); |
| 240 }); |
| 241 }); |
| 203 }); | 242 }); |
| 204 } | 243 } |
| 205 | 244 |
| 206 /** | 245 /** |
| 207 * Opens URL corresponding to the clicked part of the notification. | 246 * Opens URL corresponding to the clicked part of the notification. |
| 208 * @param {string} notificationId Unique identifier of the notification. | 247 * @param {string} notificationId Unique identifier of the notification. |
| 209 * @param {function(Object): string} selector Function that extracts the url for | 248 * @param {function(Object): string} selector Function that extracts the url for |
| 210 * the clicked area from the button action URLs info. | 249 * the clicked area from the button action URLs info. |
| 211 */ | 250 */ |
| 212 function onNotificationClicked(notificationId, selector) { | 251 function onNotificationClicked(notificationId, selector) { |
| 213 storage.get('activeNotifications', function(items) { | 252 tasks.add(CARD_CLICKED_TASK_NAME, function(callback) { |
| 214 var actionUrls = items.activeNotifications[notificationId]; | 253 tasks.debugSetStepName('onNotificationClicked-get-activeNotifications'); |
| 215 if (typeof actionUrls != 'object') { | 254 storage.get('activeNotifications', function(items) { |
| 216 // TODO(vadimt): report an error. | 255 var actionUrls = items.activeNotifications[notificationId]; |
| 217 return; | 256 if (typeof actionUrls != 'object') { |
| 218 } | 257 // TODO(vadimt): report an error. |
| 219 | 258 callback(); |
| 220 var url = selector(actionUrls); | 259 return; |
| 221 | 260 } |
| 222 if (typeof url != 'string') { | 261 |
| 223 // TODO(vadimt): report an error. | 262 var url = selector(actionUrls); |
| 224 return; | 263 |
| 225 } | 264 if (typeof url != 'string') { |
| 226 | 265 // TODO(vadimt): report an error. |
| 227 chrome.tabs.create({url: url}); | 266 callback(); |
| 267 return; |
| 268 } |
| 269 |
| 270 chrome.tabs.create({url: url}); |
| 271 callback(); |
| 272 }); |
| 228 }); | 273 }); |
| 229 } | 274 } |
| 230 | 275 |
| 231 /** | 276 /** |
| 232 * Callback for chrome.notifications.onClosed event. | 277 * Callback for chrome.notifications.onClosed event. |
| 233 * @param {string} notificationId Unique identifier of the notification. | 278 * @param {string} notificationId Unique identifier of the notification. |
| 234 * @param {boolean} byUser Whether the notification was closed by the user. | 279 * @param {boolean} byUser Whether the notification was closed by the user. |
| 235 */ | 280 */ |
| 236 function onNotificationClosed(notificationId, byUser) { | 281 function onNotificationClosed(notificationId, byUser) { |
| 237 if (byUser) { | 282 if (!byUser) |
| 238 // TODO(vadimt): Analyze possible race conditions between request for cards | 283 return; |
| 239 // and dismissal. | 284 |
| 285 tasks.add(DISMISS_CARD_TASK_NAME, function(callback) { |
| 286 // Deleting the notification in case it was re-added while this task was |
| 287 // waiting in the queue. |
| 288 chrome.notifications.clear( |
| 289 notificationId, |
| 290 function() {}); |
| 291 |
| 240 // Send a dismiss request to the server. | 292 // Send a dismiss request to the server. |
| 241 var requestParameters = '?id=' + notificationId; | 293 var requestParameters = '?id=' + notificationId; |
| 242 var request = new XMLHttpRequest(); | 294 var request = new XMLHttpRequest(); |
| 243 request.responseType = 'text'; | 295 request.responseType = 'text'; |
| 296 request.onloadend = function(event) { |
| 297 callback(); |
| 298 }; |
| 244 // TODO(vadimt): If the request fails, for example, because there is no | 299 // TODO(vadimt): If the request fails, for example, because there is no |
| 245 // internet connection, do retry with exponential backoff. | 300 // internet connection, do retry with exponential backoff. |
| 246 request.open( | 301 request.open( |
| 247 'GET', | 302 'GET', |
| 248 NOTIFICATION_CARDS_URL + '/dismiss' + requestParameters, | 303 NOTIFICATION_CARDS_URL + '/dismiss' + requestParameters, |
| 249 true); | 304 true); |
| 305 tasks.debugSetStepName('onNotificationClosed-send-request'); |
| 250 request.send(); | 306 request.send(); |
| 251 } | 307 }); |
| 252 } | 308 } |
| 253 | 309 |
| 254 /** | 310 /** |
| 255 * Schedule next update for notification cards. | 311 * Schedules next update for notification cards. |
| 256 * @param {int} delaySeconds Length of time in seconds after which the alarm | 312 * @param {int} delaySeconds Length of time in seconds after which the alarm |
| 257 * event should fire. | 313 * event should fire. |
| 258 */ | 314 */ |
| 259 function scheduleNextUpdate(delaySeconds) { | 315 function scheduleNextUpdate(delaySeconds) { |
| 260 // Schedule an alarm after the specified delay. 'periodInMinutes' is for the | 316 // Schedule an alarm after the specified delay. 'periodInMinutes' is for the |
| 261 // case when we fail to re-register the alarm. | 317 // case when we fail to re-register the alarm. |
| 262 chrome.alarms.create({ | 318 var alarmInfo = { |
| 263 delayInMinutes: delaySeconds / 60, | 319 delayInMinutes: delaySeconds / 60, |
| 264 periodInMinutes: MAXIMUM_POLLING_PERIOD_SECONDS / 60 | 320 periodInMinutes: MAXIMUM_POLLING_PERIOD_SECONDS / 60 |
| 265 }); | 321 }; |
| 266 } | 322 |
| 267 | 323 chrome.alarms.create(UPDATE_NOTIFICATIONS_ALARM_NAME, alarmInfo); |
| 268 /** | 324 } |
| 269 * Initialize the event page on install or on browser startup. | 325 |
| 326 /** |
| 327 * Initializes the event page on install or on browser startup. |
| 270 */ | 328 */ |
| 271 function initialize() { | 329 function initialize() { |
| 272 var initialStorage = { | 330 var initialStorage = { |
| 273 activeNotifications: {}, | 331 activeNotifications: {}, |
| 274 retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS | 332 retryDelaySeconds: INITIAL_POLLING_PERIOD_SECONDS |
| 275 }; | 333 }; |
| 276 storage.set(initialStorage, updateNotificationsCards); | 334 storage.set(initialStorage); |
| 335 updateNotificationsCards(); |
| 277 } | 336 } |
| 278 | 337 |
| 279 chrome.runtime.onInstalled.addListener(function(details) { | 338 chrome.runtime.onInstalled.addListener(function(details) { |
| 280 if (details.reason != 'chrome_update') | 339 if (details.reason != 'chrome_update') |
| 281 initialize(); | 340 initialize(); |
| 282 }); | 341 }); |
| 283 | 342 |
| 284 chrome.runtime.onStartup.addListener(function() { | 343 chrome.runtime.onStartup.addListener(function() { |
| 285 initialize(); | 344 initialize(); |
| 286 }); | 345 }); |
| 287 | 346 |
| 288 chrome.alarms.onAlarm.addListener(function(alarm) { | 347 chrome.alarms.onAlarm.addListener(function(alarm) { |
| 289 updateNotificationsCards(); | 348 if (alarm.name == UPDATE_NOTIFICATIONS_ALARM_NAME) |
| 349 updateNotificationsCards(); |
| 290 }); | 350 }); |
| 291 | 351 |
| 292 chrome.notifications.onClicked.addListener( | 352 chrome.notifications.onClicked.addListener( |
| 293 function(notificationId) { | 353 function(notificationId) { |
| 294 onNotificationClicked(notificationId, function(actionUrls) { | 354 onNotificationClicked(notificationId, function(actionUrls) { |
| 295 return actionUrls.messageUrl; | 355 return actionUrls.messageUrl; |
| 296 }); | 356 }); |
| 297 }); | 357 }); |
| 298 | 358 |
| 299 chrome.notifications.onButtonClicked.addListener( | 359 chrome.notifications.onButtonClicked.addListener( |
| 300 function(notificationId, buttonIndex) { | 360 function(notificationId, buttonIndex) { |
| 301 onNotificationClicked(notificationId, function(actionUrls) { | 361 onNotificationClicked(notificationId, function(actionUrls) { |
| 302 if (!Array.isArray(actionUrls.buttonUrls)) | 362 if (!Array.isArray(actionUrls.buttonUrls)) |
| 303 return undefined; | 363 return undefined; |
| 304 | 364 |
| 305 return actionUrls.buttonUrls[buttonIndex]; | 365 return actionUrls.buttonUrls[buttonIndex]; |
| 306 }); | 366 }); |
| 307 }); | 367 }); |
| 308 | 368 |
| 309 chrome.notifications.onClosed.addListener(onNotificationClosed); | 369 chrome.notifications.onClosed.addListener(onNotificationClosed); |
| OLD | NEW |