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

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

Issue 24924002: Switching getting/dismissing cards to new protocol (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Typo Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 'use strict'; 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 12 matching lines...) Expand all
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 malformed server responses. 26 // TODO(vadimt): Consider sending JS stacks for malformed server responses.
27 27
28 /** 28 /**
29 * 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
30 * code the server will send. 30 * code the server will send.
31 */ 31 */
32 var HTTP_OK = 200; 32 var HTTP_OK = 200;
33 var HTTP_NOCONTENT = 204;
33 34
34 var HTTP_BAD_REQUEST = 400; 35 var HTTP_BAD_REQUEST = 400;
35 var HTTP_UNAUTHORIZED = 401; 36 var HTTP_UNAUTHORIZED = 401;
36 var HTTP_FORBIDDEN = 403; 37 var HTTP_FORBIDDEN = 403;
37 var HTTP_METHOD_NOT_ALLOWED = 405; 38 var HTTP_METHOD_NOT_ALLOWED = 405;
38 39
40 var MS_IN_SECOND = 1000;
41 var MS_IN_MINUTE = 60 * 1000;
42
39 /** 43 /**
40 * 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
41 * period from the server is not available. 45 * period from the server is not available.
42 */ 46 */
43 var INITIAL_POLLING_PERIOD_SECONDS = 5 * 60; // 5 minutes 47 var INITIAL_POLLING_PERIOD_SECONDS = 5 * 60; // 5 minutes
44 48
45 /** 49 /**
50 * Mininal period for polling for Google Now Notifications cards.
51 */
52 var MINIMUM_POLLING_PERIOD_SECONDS = 5 * 60; // 5 minutes
53
54 /**
46 * Maximal period for polling for Google Now Notifications cards to use when the 55 * Maximal period for polling for Google Now Notifications cards to use when the
47 * period from the server is not available. 56 * period from the server is not available.
48 */ 57 */
49 var MAXIMUM_POLLING_PERIOD_SECONDS = 60 * 60; // 1 hour 58 var MAXIMUM_POLLING_PERIOD_SECONDS = 60 * 60; // 1 hour
50 59
51 /** 60 /**
52 * Initial period for retrying the server request for dismissing cards. 61 * Initial period for retrying the server request for dismissing cards.
53 */ 62 */
54 var INITIAL_RETRY_DISMISS_PERIOD_SECONDS = 60; // 1 minute 63 var INITIAL_RETRY_DISMISS_PERIOD_SECONDS = 60; // 1 minute
55 64
(...skipping 24 matching lines...) Expand all
80 89
81 var WELCOME_TOAST_NOTIFICATION_ID = 'enable-now-toast'; 90 var WELCOME_TOAST_NOTIFICATION_ID = 'enable-now-toast';
82 91
83 /** 92 /**
84 * The indices of the buttons that are displayed on the welcome toast. 93 * The indices of the buttons that are displayed on the welcome toast.
85 * @enum {number} 94 * @enum {number}
86 */ 95 */
87 var ToastButtonIndex = {YES: 0, NO: 1}; 96 var ToastButtonIndex = {YES: 0, NO: 1};
88 97
89 /** 98 /**
99 * Notification as it's sent by the server.
100 *
101 * @typedef {{
102 * notificationId: string,
103 * chromeNotificationId: string,
104 * trigger: Object=,
105 * version: number,
robliao 2013/09/27 19:41:55 This is listed as optional in the protobuf on the
vadimt 2013/09/27 21:06:00 Contacted the Server Person.
106 * chromeNotificationOptions: Object,
107 * actionUrls: Object,
robliao 2013/09/27 19:41:55 So this this
vadimt 2013/09/27 21:06:00 See above.
108 * dismissal: Object
robliao 2013/09/27 19:41:55 And this
vadimt 2013/09/27 21:06:00 See above.
109 * }}
110 */
111 var UnmergedNotification;
112
113 /**
114 * Notification group as the client stores it. |cardsTimestamp| and |rank| are
115 * defined if |cards| is non-empty.
116 *
117 * @typedef {{
118 * cards: Array.<UnmergedNotification>,
119 * cardsTimestamp: number=,
120 * nextPollTime: number,
121 * rank: number=
122 * }}
123 */
124 var StorageGroup;
125
126 /**
90 * Checks if a new task can't be scheduled when another task is already 127 * Checks if a new task can't be scheduled when another task is already
91 * scheduled. 128 * scheduled.
92 * @param {string} newTaskName Name of the new task. 129 * @param {string} newTaskName Name of the new task.
93 * @param {string} scheduledTaskName Name of the scheduled task. 130 * @param {string} scheduledTaskName Name of the scheduled task.
94 * @return {boolean} Whether the new task conflicts with the existing task. 131 * @return {boolean} Whether the new task conflicts with the existing task.
95 */ 132 */
96 function areTasksConflicting(newTaskName, scheduledTaskName) { 133 function areTasksConflicting(newTaskName, scheduledTaskName) {
97 if (newTaskName == UPDATE_CARDS_TASK_NAME && 134 if (newTaskName == UPDATE_CARDS_TASK_NAME &&
98 scheduledTaskName == UPDATE_CARDS_TASK_NAME) { 135 scheduledTaskName == UPDATE_CARDS_TASK_NAME) {
99 // If a card update is requested while an old update is still scheduled, we 136 // If a card update is requested while an old update is still scheduled, we
(...skipping 11 matching lines...) Expand all
111 } 148 }
112 149
113 return false; 150 return false;
114 } 151 }
115 152
116 var tasks = buildTaskManager(areTasksConflicting); 153 var tasks = buildTaskManager(areTasksConflicting);
117 154
118 // Add error processing to API calls. 155 // Add error processing to API calls.
119 wrapper.instrumentChromeApiFunction('location.onLocationUpdate.addListener', 0); 156 wrapper.instrumentChromeApiFunction('location.onLocationUpdate.addListener', 0);
120 wrapper.instrumentChromeApiFunction('metricsPrivate.getVariationParams', 1); 157 wrapper.instrumentChromeApiFunction('metricsPrivate.getVariationParams', 1);
158 wrapper.instrumentChromeApiFunction('notifications.clear', 1);
121 wrapper.instrumentChromeApiFunction('notifications.create', 2); 159 wrapper.instrumentChromeApiFunction('notifications.create', 2);
122 wrapper.instrumentChromeApiFunction('notifications.update', 2); 160 wrapper.instrumentChromeApiFunction('notifications.update', 2);
123 wrapper.instrumentChromeApiFunction('notifications.getAll', 0); 161 wrapper.instrumentChromeApiFunction('notifications.getAll', 0);
124 wrapper.instrumentChromeApiFunction( 162 wrapper.instrumentChromeApiFunction(
125 'notifications.onButtonClicked.addListener', 0); 163 'notifications.onButtonClicked.addListener', 0);
126 wrapper.instrumentChromeApiFunction('notifications.onClicked.addListener', 0); 164 wrapper.instrumentChromeApiFunction('notifications.onClicked.addListener', 0);
127 wrapper.instrumentChromeApiFunction('notifications.onClosed.addListener', 0); 165 wrapper.instrumentChromeApiFunction('notifications.onClosed.addListener', 0);
128 wrapper.instrumentChromeApiFunction('omnibox.onInputEntered.addListener', 0); 166 wrapper.instrumentChromeApiFunction('omnibox.onInputEntered.addListener', 0);
129 wrapper.instrumentChromeApiFunction( 167 wrapper.instrumentChromeApiFunction(
130 'preferencesPrivate.googleGeolocationAccessEnabled.get', 168 'preferencesPrivate.googleGeolocationAccessEnabled.get',
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
190 chrome.metricsPrivate.recordValue(metricDescription, event); 228 chrome.metricsPrivate.recordValue(metricDescription, event);
191 } 229 }
192 230
193 /** 231 /**
194 * Adds authorization behavior to the request. 232 * Adds authorization behavior to the request.
195 * @param {XMLHttpRequest} request Server request. 233 * @param {XMLHttpRequest} request Server request.
196 * @param {function(boolean)} callbackBoolean Completion callback with 'success' 234 * @param {function(boolean)} callbackBoolean Completion callback with 'success'
197 * parameter. 235 * parameter.
198 */ 236 */
199 function setAuthorization(request, callbackBoolean) { 237 function setAuthorization(request, callbackBoolean) {
200 tasks.debugSetStepName('setAuthorization-isSignedIn');
201 authenticationManager.isSignedIn(function(token) { 238 authenticationManager.isSignedIn(function(token) {
202 if (!token) { 239 if (!token) {
203 callbackBoolean(false); 240 callbackBoolean(false);
204 return; 241 return;
205 } 242 }
206 243
207 request.setRequestHeader('Authorization', 'Bearer ' + token); 244 request.setRequestHeader('Authorization', 'Bearer ' + token);
208 245
209 // Instrument onloadend to remove stale auth tokens. 246 // Instrument onloadend to remove stale auth tokens.
210 var originalOnLoadEnd = request.onloadend; 247 var originalOnLoadEnd = request.onloadend;
211 request.onloadend = wrapper.wrapCallback(function(event) { 248 request.onloadend = wrapper.wrapCallback(function(event) {
212 if (request.status == HTTP_FORBIDDEN || 249 if (request.status == HTTP_FORBIDDEN ||
213 request.status == HTTP_UNAUTHORIZED) { 250 request.status == HTTP_UNAUTHORIZED) {
214 tasks.debugSetStepName('setAuthorization-removeToken');
215 authenticationManager.removeToken(token, function() { 251 authenticationManager.removeToken(token, function() {
216 originalOnLoadEnd(event); 252 originalOnLoadEnd(event);
217 }); 253 });
218 } else { 254 } else {
219 originalOnLoadEnd(event); 255 originalOnLoadEnd(event);
220 } 256 }
221 }); 257 });
222 258
223 callbackBoolean(true); 259 callbackBoolean(true);
224 }); 260 });
225 } 261 }
226 262
227 /** 263 /**
228 * Parses JSON response from the notification server, show notifications and 264 * Shows parsed and merged cards as notifications.
229 * schedule next update. 265 * @param {Object.<string, MergedCard>} cards Set of cards to show. The
230 * @param {string} response Server response. 266 * key is chromeNotificationId.
robliao 2013/09/27 19:41:55 The notification key is chromeNotificationId
vadimt 2013/09/27 21:06:00 Done.
231 * @param {function()} callback Completion callback.
232 */ 267 */
233 function parseAndShowNotificationCards(response, callback) { 268 function showNotificationCards(cards) {
robliao 2013/09/27 19:41:55 The server has removed the cards terminology and u
vadimt 2013/09/27 21:06:00 May be. But we already have cards.js etc, which sh
rgustafson 2013/09/30 21:26:09 No action, just thoughts: I'm okay with cards exis
234 console.log('parseAndShowNotificationCards ' + response); 269 console.log('showNotificationCards ' + JSON.stringify(cards));
235 try {
236 var parsedResponse = JSON.parse(response);
237 } catch (error) {
238 console.error('parseAndShowNotificationCards parse error: ' + error);
239 callback();
240 return;
241 }
242 270
243 var cards = parsedResponse.cards;
244
245 if (!(cards instanceof Array)) {
246 callback();
247 return;
248 }
249
250 if (typeof parsedResponse.next_poll_seconds != 'number') {
251 callback();
252 return;
253 }
254
255 tasks.debugSetStepName('parseAndShowNotificationCards-storage-get');
256 instrumented.storage.local.get(['notificationsData', 'recentDismissals'], 271 instrumented.storage.local.get(['notificationsData', 'recentDismissals'],
257 function(items) { 272 function(items) {
258 console.log('parseAndShowNotificationCards-get ' + 273 console.log('parseAndShowNotificationCards-get ' +
259 JSON.stringify(items)); 274 JSON.stringify(items));
260 items.notificationsData = items.notificationsData || {}; 275 items.notificationsData = items.notificationsData || {};
261 items.recentDismissals = items.recentDismissals || {}; 276 items.recentDismissals = items.recentDismissals || {};
262 277
263 tasks.debugSetStepName(
264 'parseAndShowNotificationCards-notifications-getAll');
265 instrumented.notifications.getAll(function(notifications) { 278 instrumented.notifications.getAll(function(notifications) {
266 console.log('parseAndShowNotificationCards-getAll ' + 279 console.log('parseAndShowNotificationCards-getAll ' +
267 JSON.stringify(notifications)); 280 JSON.stringify(notifications));
268 // TODO(vadimt): Figure out what to do when notifications are 281 // TODO(vadimt): Figure out what to do when notifications are
269 // disabled for our extension. 282 // disabled for our extension.
270 notifications = notifications || {}; 283 notifications = notifications || {};
271 284
272 // Build a set of non-expired recent dismissals. It will be used for 285 // Build a set of non-expired recent dismissals. It will be used for
273 // client-side filtering of cards. 286 // client-side filtering of cards.
274 var updatedRecentDismissals = {}; 287 var updatedRecentDismissals = {};
275 var currentTimeMs = Date.now(); 288 var currentTimeMs = Date.now();
276 for (var notificationId in items.recentDismissals) { 289 for (var chromeNotificationId in items.recentDismissals) {
277 if (currentTimeMs - items.recentDismissals[notificationId] < 290 if (currentTimeMs - items.recentDismissals[chromeNotificationId] <
278 DISMISS_RETENTION_TIME_MS) { 291 DISMISS_RETENTION_TIME_MS) {
279 updatedRecentDismissals[notificationId] = 292 updatedRecentDismissals[chromeNotificationId] =
280 items.recentDismissals[notificationId]; 293 items.recentDismissals[chromeNotificationId];
281 } 294 delete cards[chromeNotificationId];
282 }
283
284 // Mark existing notifications that received an update in this server
285 // response.
286 var updatedNotifications = {};
287
288 for (var i = 0; i < cards.length; ++i) {
289 var notificationId = cards[i].notificationId;
290 if (!(notificationId in updatedRecentDismissals) &&
291 notificationId in notifications) {
292 updatedNotifications[notificationId] = true;
293 } 295 }
294 } 296 }
295 297
296 // Delete notifications that didn't receive an update. 298 // Delete notifications that didn't receive an update.
297 for (var notificationId in notifications) { 299 for (var chromeNotificationId in notifications) {
298 console.log('parseAndShowNotificationCards-delete-check ' + 300 console.log('parseAndShowNotificationCards-delete-check ' +
299 notificationId); 301 chromeNotificationId);
300 if (!(notificationId in updatedNotifications)) { 302 if (!(chromeNotificationId in cards)) {
301 console.log('parseAndShowNotificationCards-delete ' + 303 console.log(
302 notificationId); 304 'showNotificationCards-delete ' + chromeNotificationId);
303 cardSet.clear(notificationId); 305 cardSet.clear(chromeNotificationId);
304 } 306 }
305 } 307 }
306 308
307 recordEvent(GoogleNowEvent.CARDS_PARSE_SUCCESS);
308
309 // Create/update notifications and store their new properties. 309 // Create/update notifications and store their new properties.
310 var newNotificationsData = {}; 310 var newNotificationsData = {};
311 for (var i = 0; i < cards.length; ++i) { 311 for (var chromeNotificationId in cards) {
312 var card = cards[i]; 312 var notificationData =
313 if (!(card.notificationId in updatedRecentDismissals)) { 313 items.notificationsData[chromeNotificationId];
314 var notificationData = 314 var previousVersion = notifications[chromeNotificationId] &&
315 items.notificationsData[card.notificationId]; 315 notificationData &&
316 var previousVersion = notifications[card.notificationId] && 316 notificationData.cardCreateInfo &&
317 notificationData && 317 notificationData.cardCreateInfo.version;
318 notificationData.cardCreateInfo && 318 newNotificationsData[chromeNotificationId] = cardSet.update(
319 notificationData.cardCreateInfo.version; 319 chromeNotificationId,
320 newNotificationsData[card.notificationId] = 320 cards[chromeNotificationId],
321 cardSet.update(card, previousVersion); 321 previousVersion);
322 }
323 } 322 }
324 323
325 updateCardsAttempts.start(parsedResponse.next_poll_seconds); 324 recordEvent(GoogleNowEvent.CARDS_PARSE_SUCCESS);
326 325
327 chrome.storage.local.set({ 326 chrome.storage.local.set({
328 notificationsData: newNotificationsData, 327 notificationsData: newNotificationsData,
329 recentDismissals: updatedRecentDismissals 328 recentDismissals: updatedRecentDismissals
330 }); 329 });
331 callback();
332 }); 330 });
333 }); 331 });
334 } 332 }
335 333
336 /** 334 /**
337 * Removes all cards and card state on Google Now close down. 335 * Removes all cards and card state on Google Now close down.
338 * For example, this occurs when the geolocation preference is unchecked in the 336 * For example, this occurs when the geolocation preference is unchecked in the
339 * content settings. 337 * content settings.
340 */ 338 */
341 function removeAllCards() { 339 function removeAllCards() {
342 console.log('removeAllCards'); 340 console.log('removeAllCards');
343 341
344 // TODO(robliao): Once Google Now clears its own checkbox in the 342 // TODO(robliao): Once Google Now clears its own checkbox in the
345 // notifications center and bug 260376 is fixed, the below clearing 343 // notifications center and bug 260376 is fixed, the below clearing
346 // code is no longer necessary. 344 // code is no longer necessary.
robliao 2013/09/27 19:41:55 Is this code still necessary?
vadimt 2013/09/27 21:06:00 We should keep it till we know how the final state
347 instrumented.notifications.getAll(function(notifications) { 345 instrumented.notifications.getAll(function(notifications) {
348 notifications = notifications || {}; 346 notifications = notifications || {};
349 for (var notificationId in notifications) { 347 for (var chromeNotificationId in notifications) {
350 chrome.notifications.clear(notificationId, function() {}); 348 instrumented.notifications.clear(chromeNotificationId, function() {});
351 } 349 }
352 chrome.storage.local.set({notificationsData: {}}); 350 chrome.storage.local.remove(['notificationsData', 'notificationGroups']);
351 });
352 }
353
354 /**
355 * Merges an unmerged notification into a merged card with same ID.
356 * @param {MergedCard=} mergedCard Existing merged card or undefined if a merged
357 * card doesn't exist (i.e. we see this ID for the first time while
358 * merging).
359 * @param {UnmergedNotification} unmergedNotification Notification as it was
360 * received from the server.
361 * @param {number} cardTimestamp The moment the wire card was received.
362 * @param {number} cardGroupRank Rank of the group of the wire card.
363 * @return {MergedCard} Result of merging |unmergedNotification| into
364 * |mergedCard|.
365 */
366 function mergeCards(
367 mergedCard, unmergedNotification, cardTimestamp, cardGroupRank) {
368 var result = mergedCard || {dismissals: []};
369
370 var priority = mergedCard ?
371 Math.max(
372 mergedCard.notification.priority,
373 unmergedNotification.chromeNotificationOptions.priority) :
374 unmergedNotification.chromeNotificationOptions.priority;
375
376 if (!mergedCard || cardGroupRank > mergedCard.groupRank) {
377 result.groupRank = cardGroupRank;
378 var showTime = unmergedNotification.trigger &&
379 unmergedNotification.trigger.showTimeSec &&
380 cardTimestamp + unmergedNotification.trigger.showTimeSec * MS_IN_SECOND;
381 var hideTime = unmergedNotification.trigger &&
382 unmergedNotification.trigger.hideTimeSec &&
383 cardTimestamp + unmergedNotification.trigger.hideTimeSec * MS_IN_SECOND;
384 result.trigger = {
385 showTime: showTime,
386 hideTime: hideTime
387 };
388 }
389
390 if (!mergedCard || cardTimestamp > mergedCard.timestamp) {
391 result.timestamp = cardTimestamp;
392 result.notification = unmergedNotification.chromeNotificationOptions;
393 result.actionUrls = unmergedNotification.actionUrls;
394 result.version = unmergedNotification.version;
395 }
396
397 result.notification.priority = priority;
398 var dismissalData = {
399 notificationId: unmergedNotification.notificationId,
400 parameters: unmergedNotification.dismissal
401 };
402 result.dismissals.push(dismissalData);
403
404 return result;
405 }
406
407 /**
408 * Merges a card group into a set of merged cards.
409 * @param {Object.<string, MergedCard>} mergedCards Set of merged cards.
410 * This is an input/output parameter.
411 * @param {StorageGroup} storageGroup Group to merge into the merged card set.
412 */
413 function mergeGroup(mergedCards, storageGroup) {
414 for (var i = 0; i < storageGroup.cards.length; i++) {
415 var card = storageGroup.cards[i];
416 mergedCards[card.chromeNotificationId] = mergeCards(
417 mergedCards[card.chromeNotificationId],
418 card,
419 storageGroup.cardsTimestamp,
420 storageGroup.rank);
421 }
422 }
423
424 /**
425 * Schedules next cards poll.
426 * @param {Object.<string, StorageGroup>} groups Map from group name to group
427 * information.
428 */
429 function scheduleNextPoll(groups) {
430 var nextPollTime = null;
robliao 2013/09/27 19:41:55 Units. Sounds like milliseconds?
vadimt 2013/09/27 21:06:00 I don't think that units should be used for absolu
431
432 for (var groupName in groups) {
433 var group = groups[groupName];
434 nextPollTime = nextPollTime === null ?
robliao 2013/09/27 19:41:55 We haven't been using === much (even though it's l
vadimt 2013/09/27 21:06:00 Done.
435 group.nextPollTime : Math.min(group.nextPollTime, nextPollTime);
robliao 2013/09/27 19:41:55 What are the value guarantees of group.nextPollTim
vadimt 2013/09/27 21:06:00 Pretty much arbitrary time; can be in the past, if
436 }
437
438 verify(nextPollTime !== null, 'scheduleNextPoll: nextPollTime is null');
robliao 2013/09/27 19:41:55 Same here. Alternatively typeof nextPollTime == 'n
vadimt 2013/09/27 21:06:00 Done.
439
440 var nextPollDelaySeconds = Math.max(
441 (nextPollTime - Date.now()) / MS_IN_SECOND,
442 MINIMUM_POLLING_PERIOD_SECONDS);
443 updateCardsAttempts.start(nextPollDelaySeconds);
444 }
445
446 /**
447 * Parses JSON response from the notification server, shows notifications and
448 * schedules next update.
449 * @param {string} response Server response.
450 */
451 function parseAndShowNotificationCards(response) {
452 console.log('parseAndShowNotificationCards ' + response);
453 var parsedResponse = JSON.parse(response);
454
455 var groups = parsedResponse.groups;
456
457 // Populate groups with corresponding cards.
458 if (parsedResponse.notifications) {
459 for (var i = 0; i != parsedResponse.notifications.length; ++i) {
460 var card = parsedResponse.notifications[i];
461 var group = groups[card.groupName];
462 group.cards = group.cards || [];
463 group.cards.push(card);
464 }
465 }
466
467 instrumented.storage.local.get('notificationGroups', function(items) {
468 console.log('parseAndShowNotificationCards-get ' + JSON.stringify(items));
469 items.notificationGroups = items.notificationGroups || {};
470
471 var now = Date.now();
472
473 // Build updated set of groups and merge cards from all groups into one set.
474 var updatedGroups = {};
475 var mergedCards = {};
476
477 for (var groupName in groups) {
478 var receivedGroup = groups[groupName];
479 var storageGroup = items.notificationGroups[groupName] || {
480 cards: [],
481 cardsTimestamp: undefined,
482 nextPollTime: now,
483 rank: undefined
484 };
485
486 if (receivedGroup.requested)
487 receivedGroup.cards = receivedGroup.cards || [];
488
489 if (receivedGroup.cards) {
490 storageGroup.cards = receivedGroup.cards;
491 storageGroup.cardsTimestamp = now;
492 storageGroup.rank = receivedGroup.rank;
493 }
494
495 if (receivedGroup.nextPollSeconds !== undefined) {
496 storageGroup.nextPollTime =
497 now + receivedGroup.nextPollSeconds * MS_IN_SECOND;
robliao 2013/09/27 19:41:55 Parens (Explicit order of ops)
vadimt 2013/09/27 21:06:00 Order of ops is self-explaining. Is this in style
robliao 2013/09/27 21:17:22 Self-explaining, but not everyone has the order me
vadimt 2013/09/30 17:07:04 OK, let's use sparingly, which mean not using :) O
498 }
499
500 updatedGroups[groupName] = storageGroup;
501
502 mergeGroup(mergedCards, storageGroup);
503 }
504
505 scheduleNextPoll(updatedGroups);
506
507 chrome.storage.local.set({notificationGroups: updatedGroups});
508
509 showNotificationCards(mergedCards);
353 }); 510 });
354 } 511 }
355 512
356 /** 513 /**
357 * Requests notification cards from the server. 514 * Requests notification cards from the server.
358 * @param {Location} position Location of this computer. 515 * @param {Location} position Location of this computer.
359 * @param {function()} callback Completion callback. 516 */
360 */ 517 function requestNotificationCards(position) {
361 function requestNotificationCards(position, callback) {
362 console.log('requestNotificationCards ' + JSON.stringify(position) + 518 console.log('requestNotificationCards ' + JSON.stringify(position) +
363 ' from ' + NOTIFICATION_CARDS_URL); 519 ' from ' + NOTIFICATION_CARDS_URL);
364 520
365 if (!NOTIFICATION_CARDS_URL) { 521 if (!NOTIFICATION_CARDS_URL)
366 callback();
367 return; 522 return;
368 }
369 523
370 recordEvent(GoogleNowEvent.REQUEST_FOR_CARDS_TOTAL); 524 recordEvent(GoogleNowEvent.REQUEST_FOR_CARDS_TOTAL);
371 525
372 // TODO(vadimt): Should we use 'q' as the parameter name? 526 instrumented.storage.local.get('notificationGroups', function(items) {
373 var requestParameters = 527 console.log('requestNotificationCards-storage-get ' +
374 'q=' + position.coords.latitude + 528 JSON.stringify(items));
375 ',' + position.coords.longitude + 529
376 ',' + position.coords.accuracy; 530 var now = Date.now();
377 531
378 var request = buildServerRequest('notifications', 532 var requestParameters = '?timeZoneOffsetMs=' +
379 'application/x-www-form-urlencoded'); 533 (-new Date().getTimezoneOffset() * MS_IN_MINUTE);
380 534
381 request.onloadend = function(event) { 535 if (items.notificationGroups) {
382 console.log('requestNotificationCards-onloadend ' + request.status); 536 for (var groupName in items.notificationGroups) {
383 if (request.status == HTTP_OK) { 537 var group = items.notificationGroups[groupName];
384 recordEvent(GoogleNowEvent.REQUEST_FOR_CARDS_SUCCESS); 538 if (group.nextPollTime <= now)
385 parseAndShowNotificationCards(request.response, callback); 539 requestParameters += ('&requestTypes=' + groupName);
386 } else { 540 }
387 callback(); 541 }
388 } 542
389 }; 543 console.log('requestNotificationCards: request=' + requestParameters);
390 544
391 setAuthorization(request, function(success) { 545 var request = buildServerRequest('GET',
392 if (success) { 546 'notifications' + requestParameters);
393 tasks.debugSetStepName('requestNotificationCards-send-request'); 547
394 request.send(requestParameters); 548 request.onloadend = function(event) {
395 } else { 549 console.log('requestNotificationCards-onloadend ' + request.status);
396 callback(); 550 if (request.status == HTTP_OK) {
397 } 551 recordEvent(GoogleNowEvent.REQUEST_FOR_CARDS_SUCCESS);
552 parseAndShowNotificationCards(request.response);
553 }
554 };
555
556 setAuthorization(request, function(success) {
557 if (success)
558 request.send();
559 });
398 }); 560 });
399 } 561 }
400 562
401 /** 563 /**
402 * Starts getting location for a cards update. 564 * Starts getting location for a cards update.
403 */ 565 */
404 function requestLocation() { 566 function requestLocation() {
405 console.log('requestLocation'); 567 console.log('requestLocation');
406 recordEvent(GoogleNowEvent.LOCATION_REQUEST); 568 recordEvent(GoogleNowEvent.LOCATION_REQUEST);
407 // TODO(vadimt): Figure out location request options. 569 // TODO(vadimt): Figure out location request options.
(...skipping 21 matching lines...) Expand all
429 } 591 }
430 592
431 /** 593 /**
432 * Obtains new location; requests and shows notification cards based on this 594 * Obtains new location; requests and shows notification cards based on this
433 * location. 595 * location.
434 * @param {Location} position Location of this computer. 596 * @param {Location} position Location of this computer.
435 */ 597 */
436 function updateNotificationsCards(position) { 598 function updateNotificationsCards(position) {
437 console.log('updateNotificationsCards ' + JSON.stringify(position) + 599 console.log('updateNotificationsCards ' + JSON.stringify(position) +
438 ' @' + new Date()); 600 ' @' + new Date());
439 tasks.add(UPDATE_CARDS_TASK_NAME, function(callback) { 601 tasks.add(UPDATE_CARDS_TASK_NAME, function() {
440 console.log('updateNotificationsCards-task-begin'); 602 console.log('updateNotificationsCards-task-begin');
441 updateCardsAttempts.isRunning(function(running) { 603 updateCardsAttempts.isRunning(function(running) {
442 if (running) { 604 if (running) {
443 updateCardsAttempts.planForNext(function() { 605 updateCardsAttempts.planForNext(function() {
444 processPendingDismissals(function(success) { 606 processPendingDismissals(function(success) {
445 if (success) { 607 if (success) {
446 // The cards are requested only if there are no unsent dismissals. 608 // The cards are requested only if there are no unsent dismissals.
447 requestNotificationCards(position, callback); 609 requestNotificationCards(position);
448 } else {
449 callback();
450 } 610 }
451 }); 611 });
452 }); 612 });
453 } 613 }
454 }); 614 });
455 }); 615 });
456 } 616 }
457 617
458 /** 618 /**
459 * Sends a server request to dismiss a card. 619 * Sends a server request to dismiss a card.
460 * @param {string} notificationId Unique identifier of the card. 620 * @param {string} chromeNotificationId chrome.notifications ID of the card.
461 * @param {number} dismissalTimeMs Time of the user's dismissal of the card in 621 * @param {number} dismissalTimeMs Time of the user's dismissal of the card in
462 * milliseconds since epoch. 622 * milliseconds since epoch.
463 * @param {Object} dismissalParameters Dismissal parameters. 623 * @param {DismissalData} dismissalData Data to build a dismissal request.
464 * @param {function(boolean)} callbackBoolean Completion callback with 'done' 624 * @param {function(boolean)} callbackBoolean Completion callback with 'done'
465 * parameter. 625 * parameter.
466 */ 626 */
467 function requestCardDismissal( 627 function requestCardDismissal(
468 notificationId, dismissalTimeMs, dismissalParameters, callbackBoolean) { 628 chromeNotificationId, dismissalTimeMs, dismissalData, callbackBoolean) {
469 console.log('requestDismissingCard ' + notificationId + ' from ' + 629 console.log('requestDismissingCard ' + chromeNotificationId + ' from ' +
470 NOTIFICATION_CARDS_URL); 630 NOTIFICATION_CARDS_URL);
471 631
472 var dismissalAge = Date.now() - dismissalTimeMs; 632 var dismissalAge = Date.now() - dismissalTimeMs;
473 633
474 if (dismissalAge > MAXIMUM_DISMISSAL_AGE_MS) { 634 if (dismissalAge > MAXIMUM_DISMISSAL_AGE_MS) {
475 callbackBoolean(true); 635 callbackBoolean(true);
476 return; 636 return;
477 } 637 }
478 638
479 recordEvent(GoogleNowEvent.DISMISS_REQUEST_TOTAL); 639 recordEvent(GoogleNowEvent.DISMISS_REQUEST_TOTAL);
480 var request = buildServerRequest('dismiss', 'application/json'); 640
641 var request = 'notifications/' + dismissalData.notificationId +
642 '?age=' + dismissalAge +
643 '&chromeNotificationId=' + chromeNotificationId;
644
645 for (var paramField in dismissalData.parameters)
646 request += ('&' + paramField + '=' + dismissalData.parameters[paramField]);
647
648 console.log('requestCardDismissal: request=' + request);
649
650 var request = buildServerRequest('DELETE', request);
481 request.onloadend = function(event) { 651 request.onloadend = function(event) {
482 console.log('requestDismissingCard-onloadend ' + request.status); 652 console.log('requestDismissingCard-onloadend ' + request.status);
483 if (request.status == HTTP_OK) 653 if (request.status == HTTP_NOCONTENT)
484 recordEvent(GoogleNowEvent.DISMISS_REQUEST_SUCCESS); 654 recordEvent(GoogleNowEvent.DISMISS_REQUEST_SUCCESS);
485 655
486 // A dismissal doesn't require further retries if it was successful or 656 // A dismissal doesn't require further retries if it was successful or
487 // doesn't have a chance for successful completion. 657 // doesn't have a chance for successful completion.
488 var done = request.status == HTTP_OK || 658 var done = request.status == HTTP_NOCONTENT ||
489 request.status == HTTP_BAD_REQUEST || 659 request.status == HTTP_BAD_REQUEST ||
490 request.status == HTTP_METHOD_NOT_ALLOWED; 660 request.status == HTTP_METHOD_NOT_ALLOWED;
491 callbackBoolean(done); 661 callbackBoolean(done);
492 }; 662 };
493 663
494 setAuthorization(request, function(success) { 664 setAuthorization(request, function(success) {
495 if (success) { 665 if (success)
496 tasks.debugSetStepName('requestCardDismissal-send-request'); 666 request.send();
497 667 else
498 var dismissalObject = {
499 id: notificationId,
500 age: dismissalAge,
501 dismissal: dismissalParameters
502 };
503 request.send(JSON.stringify(dismissalObject));
504 } else {
505 callbackBoolean(false); 668 callbackBoolean(false);
506 }
507 }); 669 });
508 } 670 }
509 671
510 /** 672 /**
511 * Tries to send dismiss requests for all pending dismissals. 673 * Tries to send dismiss requests for all pending dismissals.
512 * @param {function(boolean)} callbackBoolean Completion callback with 'success' 674 * @param {function(boolean)} callbackBoolean Completion callback with 'success'
513 * parameter. Success means that no pending dismissals are left. 675 * parameter. Success means that no pending dismissals are left.
514 */ 676 */
515 function processPendingDismissals(callbackBoolean) { 677 function processPendingDismissals(callbackBoolean) {
516 tasks.debugSetStepName('processPendingDismissals-storage-get');
517 instrumented.storage.local.get(['pendingDismissals', 'recentDismissals'], 678 instrumented.storage.local.get(['pendingDismissals', 'recentDismissals'],
518 function(items) { 679 function(items) {
519 console.log('processPendingDismissals-storage-get ' + 680 console.log('processPendingDismissals-storage-get ' +
520 JSON.stringify(items)); 681 JSON.stringify(items));
521 items.pendingDismissals = items.pendingDismissals || []; 682 items.pendingDismissals = items.pendingDismissals || [];
522 items.recentDismissals = items.recentDismissals || {}; 683 items.recentDismissals = items.recentDismissals || {};
523 684
524 var dismissalsChanged = false; 685 var dismissalsChanged = false;
525 686
526 function onFinish(success) { 687 function onFinish(success) {
(...skipping 10 matching lines...) Expand all
537 if (items.pendingDismissals.length == 0) { 698 if (items.pendingDismissals.length == 0) {
538 dismissalAttempts.stop(); 699 dismissalAttempts.stop();
539 onFinish(true); 700 onFinish(true);
540 return; 701 return;
541 } 702 }
542 703
543 // Send dismissal for the first card, and if successful, repeat 704 // Send dismissal for the first card, and if successful, repeat
544 // recursively with the rest. 705 // recursively with the rest.
545 var dismissal = items.pendingDismissals[0]; 706 var dismissal = items.pendingDismissals[0];
546 requestCardDismissal( 707 requestCardDismissal(
547 dismissal.notificationId, 708 dismissal.chromeNotificationId,
548 dismissal.time, 709 dismissal.time,
549 dismissal.parameters, 710 dismissal.dismissalData,
550 function(done) { 711 function(done) {
551 if (done) { 712 if (done) {
552 dismissalsChanged = true; 713 dismissalsChanged = true;
553 items.pendingDismissals.splice(0, 1); 714 items.pendingDismissals.splice(0, 1);
554 items.recentDismissals[dismissal.notificationId] = Date.now(); 715 items.recentDismissals[dismissal.chromeNotificationId] =
716 Date.now();
555 doProcessDismissals(); 717 doProcessDismissals();
556 } else { 718 } else {
557 onFinish(false); 719 onFinish(false);
558 } 720 }
559 }); 721 });
560 } 722 }
561 723
562 doProcessDismissals(); 724 doProcessDismissals();
563 }); 725 });
564 } 726 }
565 727
566 /** 728 /**
567 * Submits a task to send pending dismissals. 729 * Submits a task to send pending dismissals.
568 */ 730 */
569 function retryPendingDismissals() { 731 function retryPendingDismissals() {
570 tasks.add(RETRY_DISMISS_TASK_NAME, function(callback) { 732 tasks.add(RETRY_DISMISS_TASK_NAME, function() {
571 dismissalAttempts.planForNext(function() { 733 dismissalAttempts.planForNext(function() {
572 processPendingDismissals(function(success) { callback(); }); 734 processPendingDismissals(function(success) {});
573 }); 735 });
574 }); 736 });
575 } 737 }
576 738
577 /** 739 /**
578 * Opens URL corresponding to the clicked part of the notification. 740 * Opens URL corresponding to the clicked part of the notification.
579 * @param {string} notificationId Unique identifier of the notification. 741 * @param {string} chromeNotificationId chrome.notifications ID of the card.
580 * @param {function(Object): string} selector Function that extracts the url for 742 * @param {function(Object): string} selector Function that extracts the url for
581 * the clicked area from the button action URLs info. 743 * the clicked area from the button action URLs info.
582 */ 744 */
583 function onNotificationClicked(notificationId, selector) { 745 function onNotificationClicked(chromeNotificationId, selector) {
584 instrumented.storage.local.get('notificationsData', function(items) { 746 instrumented.storage.local.get('notificationsData', function(items) {
585 var notificationData = items && 747 var notificationData = items &&
586 items.notificationsData && 748 items.notificationsData &&
587 items.notificationsData[notificationId]; 749 items.notificationsData[chromeNotificationId];
588 750
589 if (!notificationData) 751 if (!notificationData)
590 return; 752 return;
591 753
592 var actionUrls = notificationData.actionUrls; 754 var actionUrls = notificationData.actionUrls;
593 if (typeof actionUrls != 'object') { 755 if (typeof actionUrls != 'object') {
594 return; 756 return;
595 } 757 }
596 758
597 var url = selector(actionUrls); 759 var url = selector(actionUrls);
(...skipping 22 matching lines...) Expand all
620 // The googlegeolocationaccessenabled preference change callback 782 // The googlegeolocationaccessenabled preference change callback
621 // will take care of starting the poll for cards. 783 // will take care of starting the poll for cards.
622 } else { 784 } else {
623 chrome.metricsPrivate.recordUserAction('GoogleNow.WelcomeToastClickedNo'); 785 chrome.metricsPrivate.recordUserAction('GoogleNow.WelcomeToastClickedNo');
624 onStateChange(); 786 onStateChange();
625 } 787 }
626 } 788 }
627 789
628 /** 790 /**
629 * Callback for chrome.notifications.onClosed event. 791 * Callback for chrome.notifications.onClosed event.
630 * @param {string} notificationId Unique identifier of the notification. 792 * @param {string} chromeNotificationId chrome.notifications ID of the card.
631 * @param {boolean} byUser Whether the notification was closed by the user. 793 * @param {boolean} byUser Whether the notification was closed by the user.
632 */ 794 */
633 function onNotificationClosed(notificationId, byUser) { 795 function onNotificationClosed(chromeNotificationId, byUser) {
634 if (!byUser) 796 if (!byUser)
635 return; 797 return;
636 798
637 if (notificationId == WELCOME_TOAST_NOTIFICATION_ID) { 799 if (chromeNotificationId == WELCOME_TOAST_NOTIFICATION_ID) {
638 // Even though they only closed the notification without clicking no, treat 800 // Even though they only closed the notification without clicking no, treat
639 // it as though they clicked No anwyay, and don't show the toast again. 801 // it as though they clicked No anwyay, and don't show the toast again.
640 chrome.metricsPrivate.recordUserAction('GoogleNow.WelcomeToastDismissed'); 802 chrome.metricsPrivate.recordUserAction('GoogleNow.WelcomeToastDismissed');
641 chrome.storage.local.set({userRespondedToToast: true}); 803 chrome.storage.local.set({userRespondedToToast: true});
642 return; 804 return;
643 } 805 }
644 806
645 // At this point we are guaranteed that the notification is a now card. 807 // At this point we are guaranteed that the notification is a now card.
646 chrome.metricsPrivate.recordUserAction('GoogleNow.Dismissed'); 808 chrome.metricsPrivate.recordUserAction('GoogleNow.Dismissed');
647 809
648 tasks.add(DISMISS_CARD_TASK_NAME, function(callback) { 810 tasks.add(DISMISS_CARD_TASK_NAME, function() {
649 dismissalAttempts.start(); 811 dismissalAttempts.start();
650 812
651 // Deleting the notification in case it was re-added while this task was 813 // Deleting the notification in case it was re-added while this task was
652 // scheduled, waiting for execution. 814 // scheduled, waiting for execution.
653 cardSet.clear(notificationId); 815 cardSet.clear(chromeNotificationId);
654 816
655 tasks.debugSetStepName('onNotificationClosed-storage-get'); 817 instrumented.storage.local.get(
656 instrumented.storage.local.get(['pendingDismissals', 'notificationsData'], 818 ['pendingDismissals', 'notificationsData'], function(items) {
657 function(items) { 819 items.pendingDismissals = items.pendingDismissals || [];
658 items.pendingDismissals = items.pendingDismissals || []; 820 items.notificationsData = items.notificationsData || {};
659 items.notificationsData = items.notificationsData || {};
660 821
661 var notificationData = items.notificationsData[notificationId]; 822 var notificationData = items.notificationsData[chromeNotificationId];
662 823
824 if (notificationData && notificationData.dismissals) {
825 for (var i = 0; i < notificationData.dismissals.length; i++) {
663 var dismissal = { 826 var dismissal = {
664 notificationId: notificationId, 827 chromeNotificationId: chromeNotificationId,
665 time: Date.now(), 828 time: Date.now(),
666 parameters: notificationData && notificationData.dismissalParameters 829 dismissalData: notificationData.dismissals[i]
667 }; 830 };
668 items.pendingDismissals.push(dismissal); 831 items.pendingDismissals.push(dismissal);
669 chrome.storage.local.set( 832 }
670 {pendingDismissals: items.pendingDismissals}); 833
671 processPendingDismissals(function(success) { callback(); }); 834 chrome.storage.local.set({pendingDismissals: items.pendingDismissals});
672 }); 835 }
836
837 processPendingDismissals(function(success) {});
838 });
673 }); 839 });
674 } 840 }
675 841
676 /** 842 /**
677 * Initializes the polling system to start monitoring location and fetching 843 * Initializes the polling system to start monitoring location and fetching
678 * cards. 844 * cards.
679 */ 845 */
680 function startPollingCards() { 846 function startPollingCards() {
681 // Create an update timer for a case when for some reason location request 847 // Create an update timer for a case when for some reason location request
682 // gets stuck. 848 // gets stuck.
(...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after
909 var buttons = [{title: 'Yes'}, {title: 'No'}]; 1075 var buttons = [{title: 'Yes'}, {title: 'No'}];
910 var options = { 1076 var options = {
911 type: 'basic', 1077 type: 'basic',
912 title: 'Enable Google Now Cards', 1078 title: 'Enable Google Now Cards',
913 message: 'Would you like to be shown Google Now cards?', 1079 message: 'Would you like to be shown Google Now cards?',
914 iconUrl: 'http://www.gstatic.com/googlenow/chrome/default.png', 1080 iconUrl: 'http://www.gstatic.com/googlenow/chrome/default.png',
915 priority: 2, 1081 priority: 2,
916 buttons: buttons 1082 buttons: buttons
917 }; 1083 };
918 instrumented.notifications.create(WELCOME_TOAST_NOTIFICATION_ID, options, 1084 instrumented.notifications.create(WELCOME_TOAST_NOTIFICATION_ID, options,
919 function(notificationId) {}); 1085 function(chromeNotificationId) {});
920 } 1086 }
921 1087
922 /** 1088 /**
923 * Hides the welcome toast. 1089 * Hides the welcome toast.
924 */ 1090 */
925 function hideWelcomeToast() { 1091 function hideWelcomeToast() {
926 chrome.notifications.clear( 1092 instrumented.notifications.clear(
927 WELCOME_TOAST_NOTIFICATION_ID, 1093 WELCOME_TOAST_NOTIFICATION_ID,
928 function() {}); 1094 function() {});
929 } 1095 }
930 1096
931 instrumented.runtime.onInstalled.addListener(function(details) { 1097 instrumented.runtime.onInstalled.addListener(function(details) {
932 console.log('onInstalled ' + JSON.stringify(details)); 1098 console.log('onInstalled ' + JSON.stringify(details));
933 if (details.reason != 'chrome_update') { 1099 if (details.reason != 'chrome_update') {
934 initialize(); 1100 initialize();
935 } 1101 }
936 }); 1102 });
(...skipping 12 matching lines...) Expand all
949 prefValue.value); 1115 prefValue.value);
950 onStateChange(); 1116 onStateChange();
951 }); 1117 });
952 1118
953 authenticationManager.addListener(function() { 1119 authenticationManager.addListener(function() {
954 console.log('signIn State Change'); 1120 console.log('signIn State Change');
955 onStateChange(); 1121 onStateChange();
956 }); 1122 });
957 1123
958 instrumented.notifications.onClicked.addListener( 1124 instrumented.notifications.onClicked.addListener(
959 function(notificationId) { 1125 function(chromeNotificationId) {
960 chrome.metricsPrivate.recordUserAction('GoogleNow.MessageClicked'); 1126 chrome.metricsPrivate.recordUserAction('GoogleNow.MessageClicked');
961 onNotificationClicked(notificationId, function(actionUrls) { 1127 onNotificationClicked(chromeNotificationId, function(actionUrls) {
962 return actionUrls.messageUrl; 1128 return actionUrls.messageUrl;
963 }); 1129 });
964 }); 1130 });
965 1131
966 instrumented.notifications.onButtonClicked.addListener( 1132 instrumented.notifications.onButtonClicked.addListener(
967 function(notificationId, buttonIndex) { 1133 function(chromeNotificationId, buttonIndex) {
968 if (notificationId == WELCOME_TOAST_NOTIFICATION_ID) { 1134 if (chromeNotificationId == WELCOME_TOAST_NOTIFICATION_ID) {
969 onToastNotificationClicked(buttonIndex); 1135 onToastNotificationClicked(buttonIndex);
970 } else { 1136 } else {
971 chrome.metricsPrivate.recordUserAction( 1137 chrome.metricsPrivate.recordUserAction(
972 'GoogleNow.ButtonClicked' + buttonIndex); 1138 'GoogleNow.ButtonClicked' + buttonIndex);
973 onNotificationClicked(notificationId, function(actionUrls) { 1139 onNotificationClicked(chromeNotificationId, function(actionUrls) {
974 var url = actionUrls.buttonUrls[buttonIndex]; 1140 var url = actionUrls.buttonUrls[buttonIndex];
975 verify(url, 'onButtonClicked: no url for a button'); 1141 verify(url, 'onButtonClicked: no url for a button');
976 return url; 1142 return url;
977 }); 1143 });
978 } 1144 }
979 }); 1145 });
980 1146
981 instrumented.notifications.onClosed.addListener(onNotificationClosed); 1147 instrumented.notifications.onClosed.addListener(onNotificationClosed);
982 1148
983 instrumented.location.onLocationUpdate.addListener(function(position) { 1149 instrumented.location.onLocationUpdate.addListener(function(position) {
984 recordEvent(GoogleNowEvent.LOCATION_UPDATE); 1150 recordEvent(GoogleNowEvent.LOCATION_UPDATE);
985 updateNotificationsCards(position); 1151 updateNotificationsCards(position);
986 }); 1152 });
987 1153
988 instrumented.omnibox.onInputEntered.addListener(function(text) { 1154 instrumented.omnibox.onInputEntered.addListener(function(text) {
989 localStorage['server_url'] = NOTIFICATION_CARDS_URL = text; 1155 localStorage['server_url'] = NOTIFICATION_CARDS_URL = text;
990 initialize(); 1156 initialize();
991 }); 1157 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698