Elim cr-checkbox
[chromium-blink-merge.git] / chrome / browser / resources / google_now / cards.js
blobd14479aca2052cbb73e260c7e989610b92a0d311
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
3 // found in the LICENSE file.
5 'use strict';
7 /**
8 * Show/hide trigger in a card.
10 * @typedef {{
11 * showTimeSec: (string|undefined),
12 * hideTimeSec: string
13 * }}
15 var Trigger;
17 /**
18 * ID of an individual (uncombined) notification.
19 * This ID comes directly from the server.
21 * @typedef {string}
23 var ServerNotificationId;
25 /**
26 * Data to build a dismissal request for a card from a specific group.
28 * @typedef {{
29 * notificationId: ServerNotificationId,
30 * parameters: Object
31 * }}
33 var DismissalData;
35 /**
36 * Urls that need to be opened when clicking a notification or its buttons.
38 * @typedef {{
39 * messageUrl: (string|undefined),
40 * buttonUrls: (Array<string>|undefined)
41 * }}
43 var ActionUrls;
45 /**
46 * ID of a combined notification.
47 * This is the ID used with chrome.notifications API.
49 * @typedef {string}
51 var ChromeNotificationId;
53 /**
54 * Notification as sent by the server.
56 * @typedef {{
57 * notificationId: ServerNotificationId,
58 * chromeNotificationId: ChromeNotificationId,
59 * trigger: Trigger,
60 * chromeNotificationOptions: Object,
61 * actionUrls: (ActionUrls|undefined),
62 * dismissal: Object,
63 * locationBased: (boolean|undefined),
64 * groupName: string,
65 * cardTypeId: (number|undefined)
66 * }}
68 var ReceivedNotification;
70 /**
71 * Received notification in a self-sufficient form that doesn't require group's
72 * timestamp to calculate show and hide times.
74 * @typedef {{
75 * receivedNotification: ReceivedNotification,
76 * showTime: (number|undefined),
77 * hideTime: number
78 * }}
80 var UncombinedNotification;
82 /**
83 * Card combined from potentially multiple groups.
85 * @typedef {Array<UncombinedNotification>}
87 var CombinedCard;
89 /**
90 * Data entry that we store for every Chrome notification.
91 * |timestamp| is the time when corresponding Chrome notification was created or
92 * updated last time by cardSet.update().
94 * @typedef {{
95 * actionUrls: (ActionUrls|undefined),
96 * cardTypeId: (number|undefined),
97 * timestamp: number,
98 * combinedCard: CombinedCard
99 * }}
102 var NotificationDataEntry;
105 * Names for tasks that can be created by the this file.
107 var UPDATE_CARD_TASK_NAME = 'update-card';
110 * Builds an object to manage notification card set.
111 * @return {Object} Card set interface.
113 function buildCardSet() {
114 var alarmPrefix = 'card-';
117 * Creates/updates/deletes a Chrome notification.
118 * @param {ChromeNotificationId} chromeNotificationId chrome.notifications ID
119 * of the card.
120 * @param {(ReceivedNotification|undefined)} receivedNotification Google Now
121 * card represented as a set of parameters for showing a Chrome
122 * notification, or null if the notification needs to be deleted.
123 * @param {function(ReceivedNotification)=} onCardShown Optional parameter
124 * called when each card is shown.
126 function updateNotification(
127 chromeNotificationId, receivedNotification, onCardShown) {
128 console.log(
129 'cardManager.updateNotification ' + chromeNotificationId + ' ' +
130 JSON.stringify(receivedNotification));
132 if (!receivedNotification) {
133 instrumented.notifications.clear(chromeNotificationId, function() {});
134 return;
137 // Try updating the notification.
138 instrumented.notifications.update(
139 chromeNotificationId,
140 receivedNotification.chromeNotificationOptions,
141 function(wasUpdated) {
142 if (!wasUpdated) {
143 // If the notification wasn't updated, it probably didn't exist.
144 // Create it.
145 console.log(
146 'cardManager.updateNotification ' + chromeNotificationId +
147 ' not updated, creating instead');
148 instrumented.notifications.create(
149 chromeNotificationId,
150 receivedNotification.chromeNotificationOptions,
151 function(newChromeNotificationId) {
152 if (!newChromeNotificationId || chrome.runtime.lastError) {
153 var errorMessage = chrome.runtime.lastError &&
154 chrome.runtime.lastError.message;
155 console.error('notifications.create: ID=' +
156 newChromeNotificationId + ', ERROR=' + errorMessage);
157 return;
160 if (onCardShown !== undefined)
161 onCardShown(receivedNotification);
168 * Iterates uncombined notifications in a combined card, determining for
169 * each whether it's visible at the specified moment.
170 * @param {CombinedCard} combinedCard The combined card in question.
171 * @param {number} timestamp Time for which to calculate visibility.
172 * @param {function(UncombinedNotification, boolean)} callback Function
173 * invoked for every uncombined notification in |combinedCard|.
174 * The boolean parameter indicates whether the uncombined notification is
175 * visible at |timestamp|.
177 function iterateUncombinedNotifications(combinedCard, timestamp, callback) {
178 for (var i = 0; i != combinedCard.length; ++i) {
179 var uncombinedNotification = combinedCard[i];
180 var shouldShow = !uncombinedNotification.showTime ||
181 uncombinedNotification.showTime <= timestamp;
182 var shouldHide = uncombinedNotification.hideTime <= timestamp;
184 callback(uncombinedNotification, shouldShow && !shouldHide);
189 * Refreshes (shows/hides) the notification corresponding to the combined card
190 * based on the current time and show-hide intervals in the combined card.
191 * @param {ChromeNotificationId} chromeNotificationId chrome.notifications ID
192 * of the card.
193 * @param {CombinedCard} combinedCard Combined cards with
194 * |chromeNotificationId|.
195 * @param {Object<StoredNotificationGroup>} notificationGroups Map from group
196 * name to group information.
197 * @param {function(ReceivedNotification)=} onCardShown Optional parameter
198 * called when each card is shown.
199 * @return {(NotificationDataEntry|undefined)} Notification data entry for
200 * this card. It's 'undefined' if the card's life is over.
202 function update(
203 chromeNotificationId, combinedCard, notificationGroups, onCardShown) {
204 console.log('cardManager.update ' + JSON.stringify(combinedCard));
206 chrome.alarms.clear(alarmPrefix + chromeNotificationId);
207 var now = Date.now();
208 /** @type {(UncombinedNotification|undefined)} */
209 var winningCard = undefined;
210 // Next moment of time when winning notification selection algotithm can
211 // potentially return a different notification.
212 /** @type {?number} */
213 var nextEventTime = null;
215 // Find a winning uncombined notification: a highest-priority notification
216 // that needs to be shown now.
217 iterateUncombinedNotifications(
218 combinedCard,
219 now,
220 function(uncombinedCard, visible) {
221 // If the uncombined notification is visible now and set the winning
222 // card to it if its priority is higher.
223 if (visible) {
224 if (!winningCard ||
225 uncombinedCard.receivedNotification.chromeNotificationOptions.
226 priority >
227 winningCard.receivedNotification.chromeNotificationOptions.
228 priority) {
229 winningCard = uncombinedCard;
233 // Next event time is the closest hide or show event.
234 if (uncombinedCard.showTime && uncombinedCard.showTime > now) {
235 if (!nextEventTime || nextEventTime > uncombinedCard.showTime)
236 nextEventTime = uncombinedCard.showTime;
238 if (uncombinedCard.hideTime > now) {
239 if (!nextEventTime || nextEventTime > uncombinedCard.hideTime)
240 nextEventTime = uncombinedCard.hideTime;
244 // Show/hide the winning card.
245 updateNotification(
246 chromeNotificationId,
247 winningCard && winningCard.receivedNotification,
248 onCardShown);
250 if (nextEventTime) {
251 // If we expect more events, create an alarm for the next one.
252 chrome.alarms.create(
253 alarmPrefix + chromeNotificationId, {when: nextEventTime});
255 // The trick with stringify/parse is to create a copy of action URLs,
256 // otherwise notifications data with 2 pointers to the same object won't
257 // be stored correctly to chrome.storage.
258 var winningActionUrls = winningCard &&
259 winningCard.receivedNotification.actionUrls &&
260 JSON.parse(JSON.stringify(
261 winningCard.receivedNotification.actionUrls));
262 var winningCardTypeId = winningCard &&
263 winningCard.receivedNotification.cardTypeId;
264 return {
265 actionUrls: winningActionUrls,
266 cardTypeId: winningCardTypeId,
267 timestamp: now,
268 combinedCard: combinedCard
270 } else {
271 // If there are no more events, we are done with this card. Note that all
272 // received notifications have hideTime.
273 verify(!winningCard, 'No events left, but card is shown.');
274 clearCardFromGroups(chromeNotificationId, notificationGroups);
275 return undefined;
280 * Removes dismissed part of a card and refreshes the card. Returns remaining
281 * dismissals for the combined card and updated notification data.
282 * @param {ChromeNotificationId} chromeNotificationId chrome.notifications ID
283 * of the card.
284 * @param {NotificationDataEntry} notificationData Stored notification entry
285 * for this card.
286 * @param {Object<StoredNotificationGroup>} notificationGroups Map from group
287 * name to group information.
288 * @return {{
289 * dismissals: Array<DismissalData>,
290 * notificationData: (NotificationDataEntry|undefined)
291 * }}
293 function onDismissal(
294 chromeNotificationId, notificationData, notificationGroups) {
295 /** @type {Array<DismissalData>} */
296 var dismissals = [];
297 /** @type {Array<UncombinedNotification>} */
298 var newCombinedCard = [];
300 // Determine which parts of the combined card need to be dismissed or to be
301 // preserved. We dismiss parts that were visible at the moment when the card
302 // was last updated.
303 iterateUncombinedNotifications(
304 notificationData.combinedCard,
305 notificationData.timestamp,
306 function(uncombinedCard, visible) {
307 if (visible) {
308 dismissals.push({
309 notificationId: uncombinedCard.receivedNotification.notificationId,
310 parameters: uncombinedCard.receivedNotification.dismissal
312 } else {
313 newCombinedCard.push(uncombinedCard);
317 return {
318 dismissals: dismissals,
319 notificationData: update(
320 chromeNotificationId, newCombinedCard, notificationGroups)
325 * Removes card information from |notificationGroups|.
326 * @param {ChromeNotificationId} chromeNotificationId chrome.notifications ID
327 * of the card.
328 * @param {Object<StoredNotificationGroup>} notificationGroups Map from group
329 * name to group information.
331 function clearCardFromGroups(chromeNotificationId, notificationGroups) {
332 console.log('cardManager.clearCardFromGroups ' + chromeNotificationId);
333 for (var groupName in notificationGroups) {
334 var group = notificationGroups[groupName];
335 for (var i = 0; i != group.cards.length; ++i) {
336 if (group.cards[i].chromeNotificationId == chromeNotificationId) {
337 group.cards.splice(i, 1);
338 break;
344 instrumented.alarms.onAlarm.addListener(function(alarm) {
345 console.log('cardManager.onAlarm ' + JSON.stringify(alarm));
347 if (alarm.name.indexOf(alarmPrefix) == 0) {
348 // Alarm to show the card.
349 tasks.add(UPDATE_CARD_TASK_NAME, function() {
350 /** @type {ChromeNotificationId} */
351 var chromeNotificationId = alarm.name.substring(alarmPrefix.length);
352 fillFromChromeLocalStorage({
353 /** @type {Object<ChromeNotificationId, NotificationDataEntry>} */
354 notificationsData: {},
355 /** @type {Object<StoredNotificationGroup>} */
356 notificationGroups: {}
357 }).then(function(items) {
358 console.log('cardManager.onAlarm.get ' + JSON.stringify(items));
360 var combinedCard =
361 (items.notificationsData[chromeNotificationId] &&
362 items.notificationsData[chromeNotificationId].combinedCard) || [];
364 var cardShownCallback = undefined;
365 if (localStorage['explanatoryCardsShown'] <
366 EXPLANATORY_CARDS_LINK_THRESHOLD) {
367 cardShownCallback = countExplanatoryCard;
370 items.notificationsData[chromeNotificationId] =
371 update(
372 chromeNotificationId,
373 combinedCard,
374 items.notificationGroups,
375 cardShownCallback);
377 chrome.storage.local.set(items);
383 return {
384 update: update,
385 onDismissal: onDismissal