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

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

Issue 107033002: Combining cards instead of merging (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rgustafson's comments Created 7 years 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 2013 The Chromium Authors. All rights reserved. 1 // Copyright 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 * Show/hide trigger in a card. 8 * Show/hide trigger in a card.
9 * 9 *
10 * @typedef {{ 10 * @typedef {{
11 * showTime: number=, 11 * showTimeSec: (string|undefined),
12 * hideTime: number= 12 * hideTimeSec: string
13 * }} 13 * }}
14 */ 14 */
15 var Trigger; 15 var Trigger;
16 16
17 /** 17 /**
18 * ID of an individual (uncombined) notification.
19 *
20 * @typedef {string}
21 */
22 var NotificationId;
23
24 /**
18 * Data to build a dismissal request for a card from a specific group. 25 * Data to build a dismissal request for a card from a specific group.
19 * 26 *
20 * @typedef {{ 27 * @typedef {{
21 * notificationId: string, 28 * notificationId: NotificationId,
22 * parameters: Object 29 * parameters: Object
23 * }} 30 * }}
24 */ 31 */
25 var DismissalData; 32 var DismissalData;
26 33
27 /** 34 /**
28 * Card merged from potentially multiple groups. 35 * Urls that need to be opened when clicking a notification or its buttons.
29 * 36 *
30 * @typedef {{ 37 * @typedef {{
38 * messageUrl: (string|undefined),
39 * buttonUrls: (Array.<string>|undefined)
40 * }}
41 */
42 var ActionUrls;
43
44 /**
45 * ID of a combined notification. This is the ID used with chrome.notifications
46 * API.
47 *
48 * @typedef {string}
49 */
50 var ChromeNotificationId;
51
52 /**
53 * Notification as sent by the server.
54 *
55 * @typedef {{
56 * notificationId: NotificationId,
57 * chromeNotificationId: ChromeNotificationId,
31 * trigger: Trigger, 58 * trigger: Trigger,
32 * version: number, 59 * version: number,
60 * chromeNotificationOptions: Object,
61 * actionUrls: (ActionUrls|undefined),
62 * dismissal: Object,
63 * locationBased: (boolean|undefined),
64 * groupName: string
65 * }}
66 */
67 var ReceivedNotification;
68
69 /**
70 * Received notification in a self-sufficient form that doesn't require group's
71 * timestamp to calculate show and hide times.
72 *
73 * @typedef {{
74 * receivedNotification: ReceivedNotification,
75 * showTime: (number|undefined),
76 * hideTime: number
77 * }}
78 */
79 var UncombinedNotification;
80
81 /**
82 * Card combined from potentially multiple groups.
83 *
84 * @typedef {Array.<UncombinedNotification>}
85 */
86 var CombinedCard;
87
88 /**
89 * Data entry that we store for every Chrome notification.
90 * |timestamp| is the time when corresponding Chrome notification was created or
91 * updated last time by cardSet.update().
92 *
93 * @typedef {{
94 * actionUrls: (ActionUrls|undefined),
33 * timestamp: number, 95 * timestamp: number,
34 * notification: Object, 96 * combinedCard: CombinedCard
35 * actionUrls: Object=, 97 * }}
36 * groupRank: number, 98 *
37 * dismissals: Array.<DismissalData>, 99 */
38 * locationBased: boolean= 100 var NotificationDataEntry;
39 * }}
40 */
41 var MergedCard;
42
43 /**
44 * Set of parameters for creating card notification.
45 *
46 * @typedef {{
47 * notification: Object,
48 * hideTime: number=,
49 * version: number,
50 * previousVersion: number=,
51 * locationBased: boolean=
52 * }}
53 */
54 var CardCreateInfo;
55 101
56 /** 102 /**
57 * Names for tasks that can be created by the this file. 103 * Names for tasks that can be created by the this file.
58 */ 104 */
59 var SHOW_CARD_TASK_NAME = 'show-card'; 105 var UPDATE_CARD_TASK_NAME = 'update-card';
60 var CLEAR_CARD_TASK_NAME = 'clear-card';
61 106
62 /** 107 /**
63 * Builds an object to manage notification card set. 108 * Builds an object to manage notification card set.
64 * @return {Object} Card set interface. 109 * @return {Object} Card set interface.
65 */ 110 */
66 function buildCardSet() { 111 function buildCardSet() {
67 var cardShowPrefix = 'card-show-'; 112 var alarmPrefix = 'card-';
68 var cardHidePrefix = 'card-hide-'; 113
69 114 /**
70 /** 115 * Creates/updates/deletes a Chrome notification.
71 * Schedules hiding a notification. 116 * @param {ChromeNotificationId} cardId Card ID.
72 * @param {string} cardId Card ID. 117 * @param {?ReceivedNotification} receivedNotification Google Now card
73 * @param {number=} opt_timeHide If specified, epoch time to hide the card. If 118 * represented as a set of parameters for showing a Chrome notification,
74 * undefined, the card will be kept shown at least until next update. 119 * or null if the notification needs to be deleted.
75 */ 120 * @param {function(ReceivedNotification)=} onCardShown Optional parameter
76 function scheduleHiding(cardId, opt_timeHide) { 121 * called when each card is shown.
77 if (opt_timeHide === undefined) 122 */
78 return; 123 function updateNotification(cardId, receivedNotification, onCardShown) {
79 124 console.log('cardManager.updateNotification ' + cardId + ' ' +
80 var alarmName = cardHidePrefix + cardId; 125 JSON.stringify(receivedNotification));
81 var alarmInfo = {when: opt_timeHide}; 126
82 chrome.alarms.create(alarmName, alarmInfo); 127 if (!receivedNotification) {
83 } 128 instrumented.notifications.clear(cardId, function() {});
84
85 /**
86 * Shows a notification.
87 * @param {string} cardId Card ID.
88 * @param {CardCreateInfo} cardCreateInfo Google Now card represented as a set
89 * of parameters for showing a Chrome notification.
90 * @param {function(CardCreateInfo)=} onCardShown Optional parameter called
91 * when each card is shown.
92 */
93 function showNotification(cardId, cardCreateInfo, onCardShown) {
94 console.log('cardManager.showNotification ' + cardId + ' ' +
95 JSON.stringify(cardCreateInfo));
96
97 if (cardCreateInfo.hideTime <= Date.now()) {
98 console.log('cardManager.showNotification ' + cardId + ': expired');
99 // Card has expired. Schedule hiding to delete asociated information.
100 scheduleHiding(cardId, cardCreateInfo.hideTime);
101 return; 129 return;
102 } 130 }
103 131
104 if (cardCreateInfo.previousVersion !== cardCreateInfo.version) { 132 // Try updating the notification.
rgustafson 2013/12/06 23:35:16 So the only way to make a notification retoast now
vadimt 2013/12/06 23:52:58 --- Correct
105 // Delete a notification with the specified id if it already exists, and 133 instrumented.notifications.update(
106 // then create a notification. 134 cardId,
107 instrumented.notifications.create( 135 receivedNotification.chromeNotificationOptions,
108 cardId, 136 function(wasUpdated) {
109 cardCreateInfo.notification, 137 if (!wasUpdated) {
110 function(newNotificationId) { 138 // If the notification wasn't updated, it probably didn't exist.
111 if (!newNotificationId || chrome.runtime.lastError) { 139 // Create it.
112 var errorMessage = chrome.runtime.lastError && 140 console.log('cardManager.updateNotification ' + cardId +
113 chrome.runtime.lastError.message; 141 ' failed to update, creating');
114 console.error('notifications.create: ID=' + newNotificationId + 142 instrumented.notifications.create(
115 ', ERROR=' + errorMessage); 143 cardId,
116 return; 144 receivedNotification.chromeNotificationOptions,
117 } 145 function(newNotificationId) {
118 146 if (!newNotificationId || chrome.runtime.lastError) {
119 if (onCardShown !== undefined) 147 var errorMessage = chrome.runtime.lastError &&
120 onCardShown(cardCreateInfo); 148 chrome.runtime.lastError.message;
121 149 console.error('notifications.create: ID=' +
122 scheduleHiding(cardId, cardCreateInfo.hideTime); 150 newNotificationId + ', ERROR=' + errorMessage);
151 return;
152 }
153
154 if (onCardShown !== undefined)
155 onCardShown(receivedNotification);
156 });
157 }
158 });
159 }
160
161 /**
162 * Enumerates uncombined notifications in a combined card, determining for
rgustafson 2013/12/06 23:35:16 Enumerating implies listing everything. A list is
vadimt 2013/12/06 23:52:58 Done.
163 * each whether it's visible at the specified moment.
164 * @param {CombinedCard} combinedCard The combined card in question.
165 * @param {number} timestamp Time for which to calculate visibility.
166 * @param {function(UncombinedNotification, boolean)} callback Function
167 * invoked for every uncombined notification in |combinedCard|.
168 * The boolean parameter indicates whether the uncombined notification is
169 * visible at |timestamp|.
170 */
171 function enumerateUncombinedNotifications(combinedCard, timestamp, callback) {
172 for (var i = 0; i != combinedCard.length; ++i) {
173 var uncombinedNotification = combinedCard[i];
174 var shouldShow = !uncombinedNotification.showTime ||
175 uncombinedNotification.showTime <= timestamp;
176 var shouldHide = uncombinedNotification.hideTime <= timestamp;
177
178 callback(uncombinedNotification, shouldShow && !shouldHide);
179 }
180 }
181
182 /**
183 * Refreshes (shows/hides) the notification corresponding to the combined card
184 * based on the current time and show-hide intervals in the combined card.
185 * @param {ChromeNotificationId} cardId Card ID.
186 * @param {CombinedCard} combinedCard Combined cards with |cardId|.
187 * @param {function(ReceivedNotification)=} onCardShown Optional parameter
188 * called when each card is shown.
189 * @return {(NotificationDataEntry|undefined)} Notification data entry for
190 * this card. It's 'undefined' if the card's life is over.
191 */
192 function update(cardId, combinedCard, onCardShown) {
193 console.log('cardManager.update ' + JSON.stringify(combinedCard));
194
195 chrome.alarms.clear(alarmPrefix + cardId);
196 var now = Date.now();
197 /** @type {?UncombinedNotification} */
198 var winningCard = null;
199 // Next moment of time when winning notification selection algotithm can
200 // potentially return a different notification.
201 /** @type {?number} */
202 var nextEventTime = null;
203
204 // Find a winning uncombined notification: a highest-priority notification
205 // that needs to be shown now.
206 enumerateUncombinedNotifications(
207 combinedCard, now, function(uncombinedCard, visible) {
208 // If the uncombined notification is visible now and set the winning card
rgustafson 2013/12/06 23:35:16 Is this the right tab level for a function that is
vadimt 2013/12/06 23:52:58 Done.
209 // to it if its priority is higher.
210 if (visible) {
211 if (!winningCard ||
212 uncombinedCard.receivedNotification.chromeNotificationOptions.
213 priority >
214 winningCard.receivedNotification.chromeNotificationOptions.
215 priority) {
216 winningCard = uncombinedCard;
217 }
218 }
219
220 // Next event time is the closest hide or show event.
221 if (uncombinedCard.showTime && uncombinedCard.showTime > now) {
222 if (!nextEventTime || nextEventTime > uncombinedCard.showTime)
223 nextEventTime = uncombinedCard.showTime;
224 }
225 if (uncombinedCard.hideTime > now) {
226 if (!nextEventTime || nextEventTime > uncombinedCard.hideTime)
227 nextEventTime = uncombinedCard.hideTime;
228 }
229 });
230
231 // Show/hide the winning card.
232 updateNotification(
233 cardId, winningCard && winningCard.receivedNotification, onCardShown);
234
235 if (nextEventTime) {
236 // If we expect more events, create an alarm for the next one.
237 chrome.alarms.create(alarmPrefix + cardId, {when: nextEventTime});
238
239 // The trick with stringify/parse is to create a copy of action URLs,
240 // otherwise notifications data with 2 pointers to the same object won't
241 // be stored correctly to chrome.storage.
242 var winningActionUrls = winningCard &&
243 JSON.parse(JSON.stringify(
244 winningCard.receivedNotification.actionUrls));
245
246 return {
247 actionUrls: winningActionUrls,
248 timestamp: now,
249 combinedCard: combinedCard
250 };
251 } else {
252 // If there are no more events, we are done with this card.
253 verify(!winningCard, 'No events left, but card is shown.');
254 clearCardFromGroups(cardId);
255 return undefined;
256 }
257 }
258
259 /**
260 * Removes dismissed part of a card and refreshes the card. Returns remaining
261 * dismissals for the combined card and updated notification data.
262 * @param {ChromeNotificationId} cardId Card ID.
263 * @param {NotificationDataEntry} notificationData Stored notification entry
264 * for this card.
265 * @return {{
266 * dismissals: Array.<DismissalData>,
267 * notificationData: (NotificationDataEntry|undefined)
268 * }}
269 */
270 function onDismissal(cardId, notificationData) {
271 var dismissals = [];
272 var newCombinedCard = [];
273
274 // Determine which parts of the combined card need to be dismissed or to be
275 // preserved. We dismiss parts that were visible at the moment when the card
276 // was last time updated.
rgustafson 2013/12/06 23:35:16 was last updated
vadimt 2013/12/06 23:52:58 Done.
277 enumerateUncombinedNotifications(
278 notificationData.combinedCard,
279 notificationData.timestamp,
280 function(uncombinedCard, visible) {
281 if (visible) {
282 dismissals.push({
283 notificationId: uncombinedCard.receivedNotification.notificationId,
284 parameters: uncombinedCard.receivedNotification.dismissal
123 }); 285 });
124 } else { 286 } else {
125 // Update existing notification. 287 newCombinedCard.push(uncombinedCard);
126 instrumented.notifications.update( 288 }
127 cardId, 289 });
128 cardCreateInfo.notification, 290
129 function(wasUpdated) { 291 return {
130 if (!wasUpdated || chrome.runtime.lastError) { 292 dismissals: dismissals,
131 var errorMessage = chrome.runtime.lastError && 293 notificationData: update(cardId, newCombinedCard)
132 chrome.runtime.lastError.message;
133 console.error('notifications.update: UPDATED=' + wasUpdated +
134 ', ERROR=' + errorMessage);
135 return;
136 }
137
138 scheduleHiding(cardId, cardCreateInfo.hideTime);
139 });
140 }
141 }
142
143 /**
144 * Updates/creates a card notification with new data.
145 * @param {string} cardId Card ID.
146 * @param {MergedCard} card Google Now card from the server.
147 * @param {number=} previousVersion The version of the shown card with
148 * this id, if it exists, undefined otherwise.
149 * @param {function(CardCreateInfo)=} onCardShown Optional parameter called
150 * when each card is shown.
151 * @return {Object} Notification data entry for this card.
152 */
153 function update(cardId, card, previousVersion, onCardShown) {
154 console.log('cardManager.update ' + JSON.stringify(card) + ' ' +
155 previousVersion);
156
157 chrome.alarms.clear(cardHidePrefix + cardId);
158
159 var cardCreateInfo = {
160 notification: card.notification,
161 hideTime: card.trigger.hideTime,
162 version: card.version,
163 previousVersion: previousVersion,
164 locationBased: card.locationBased
165 }; 294 };
166 295 }
167 var shownImmediately = false; 296
168 var cardShowAlarmName = cardShowPrefix + cardId; 297 /**
169 if (card.trigger.showTime && card.trigger.showTime > Date.now()) { 298 * Removes a card information from 'notificationGroups'.
rgustafson 2013/12/06 23:35:16 Removes card information
vadimt 2013/12/06 23:52:58 Done.
170 // Card needs to be shown later. 299 * @param {ChromeNotificationId} cardId Card ID.
171 console.log('cardManager.update: postponed'); 300 */
172 var alarmInfo = { 301 function clearCardFromGroups(cardId) {
173 when: card.trigger.showTime 302 console.log('cardManager.clearCardFromGroups ' + cardId);
174 }; 303
175 chrome.alarms.create(cardShowAlarmName, alarmInfo); 304 instrumented.storage.local.get('notificationGroups', function(items) {
176 } else { 305 items = items || {};
177 // Card needs to be shown immediately. 306 /** @type {Object.<string, StoredNotificationGroup>} */
178 console.log('cardManager.update: immediate'); 307 items.notificationGroups = items.notificationGroups || {};
179 chrome.alarms.clear(cardShowAlarmName); 308
180 showNotification(cardId, cardCreateInfo, onCardShown); 309 for (var groupName in items.notificationGroups) {
181 } 310 var group = items.notificationGroups[groupName];
182 311 for (var i = 0; i != group.cards.length; ++i) {
183 return { 312 if (group.cards[i].chromeNotificationId == cardId) {
184 actionUrls: card.actionUrls, 313 group.cards.splice(i, 1);
185 cardCreateInfo: cardCreateInfo, 314 break;
186 dismissals: card.dismissals 315 }
187 }; 316 }
188 } 317 }
189 318
190 /** 319 chrome.storage.local.set(items);
191 * Removes a card notification. 320 });
192 * @param {string} cardId Card ID.
193 * @param {boolean} clearStorage True if the information associated with the
194 * card should be erased from chrome.storage.
195 */
196 function clear(cardId, clearStorage) {
197 console.log('cardManager.clear ' + cardId);
198
199 chrome.notifications.clear(cardId, function() {});
200 chrome.alarms.clear(cardShowPrefix + cardId);
201 chrome.alarms.clear(cardHidePrefix + cardId);
202
203 if (clearStorage) {
204 instrumented.storage.local.get(
205 ['notificationsData', 'notificationGroups'],
206 function(items) {
207 items = items || {};
208 items.notificationsData = items.notificationsData || {};
209 items.notificationGroups = items.notificationGroups || {};
210
211 delete items.notificationsData[cardId];
212
213 for (var groupName in items.notificationGroups) {
214 var group = items.notificationGroups[groupName];
215 for (var i = 0; i != group.cards.length; ++i) {
216 if (group.cards[i].chromeNotificationId == cardId) {
217 group.cards.splice(i, 1);
218 break;
219 }
220 }
221 }
222
223 chrome.storage.local.set(items);
224 });
225 }
226 } 321 }
227 322
228 instrumented.alarms.onAlarm.addListener(function(alarm) { 323 instrumented.alarms.onAlarm.addListener(function(alarm) {
229 console.log('cardManager.onAlarm ' + JSON.stringify(alarm)); 324 console.log('cardManager.onAlarm ' + JSON.stringify(alarm));
230 325
231 if (alarm.name.indexOf(cardShowPrefix) == 0) { 326 if (alarm.name.indexOf(alarmPrefix) == 0) {
232 // Alarm to show the card. 327 // Alarm to show the card.
233 tasks.add(SHOW_CARD_TASK_NAME, function() { 328 tasks.add(UPDATE_CARD_TASK_NAME, function() {
234 var cardId = alarm.name.substring(cardShowPrefix.length); 329 var cardId = alarm.name.substring(alarmPrefix.length);
235 instrumented.storage.local.get('notificationsData', function(items) { 330 instrumented.storage.local.get('notificationsData', function(items) {
236 console.log('cardManager.onAlarm.get ' + JSON.stringify(items)); 331 console.log('cardManager.onAlarm.get ' + JSON.stringify(items));
237 if (!items || !items.notificationsData) 332 items = items || {};
238 return; 333 /** @type {Object.<string, NotificationDataEntry>} */
239 var notificationData = items.notificationsData[cardId]; 334 items.notificationsData = items.notificationsData || {};
240 if (!notificationData) 335 var combinedCard =
241 return; 336 (items.notificationsData[cardId] &&
337 items.notificationsData[cardId].combinedCard) || [];
242 338
243 var cardShownCallback = undefined; 339 var cardShownCallback = undefined;
244 if (localStorage['locationCardsShown'] < 340 if (localStorage['locationCardsShown'] <
245 LOCATION_CARDS_LINK_THRESHOLD) { 341 LOCATION_CARDS_LINK_THRESHOLD) {
246 cardShownCallback = countLocationCard; 342 cardShownCallback = countLocationCard;
247 } 343 }
248 344
249 showNotification( 345 items.notificationsData[cardId] =
250 cardId, notificationData.cardCreateInfo, cardShownCallback); 346 update(cardId, combinedCard, cardShownCallback);
347
348 chrome.storage.local.set(items);
251 }); 349 });
252 }); 350 });
253 } else if (alarm.name.indexOf(cardHidePrefix) == 0) {
254 // Alarm to hide the card.
255 tasks.add(CLEAR_CARD_TASK_NAME, function() {
256 var cardId = alarm.name.substring(cardHidePrefix.length);
257 clear(cardId, true);
258 });
259 } 351 }
260 }); 352 });
261 353
262 return { 354 return {
263 update: update, 355 update: update,
264 clear: clear 356 onDismissal: onDismissal
265 }; 357 };
266 } 358 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698