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

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

Powered by Google App Engine
This is Rietveld 408576698