OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2012 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. | |
4 | |
5 /** | |
6 * @fileoverview New tab page | |
7 * This is the main code for the new tab page used by touch-enabled Chrome | |
8 * browsers. For now this is still a prototype. | |
9 */ | |
10 | |
11 // Use an anonymous function to enable strict mode just for this file (which | |
12 // will be concatenated with other files when embedded in Chrome | |
13 cr.define('ntp', function() { | |
14 'use strict'; | |
15 | |
16 /** | |
17 * NewTabView instance. | |
18 * @type {!Object|undefined} | |
19 */ | |
20 var newTabView; | |
21 | |
22 /** | |
23 * The 'notification-container' element. | |
24 * @type {!Element|undefined} | |
25 */ | |
26 var notificationContainer; | |
27 | |
28 /** | |
29 * If non-null, an info bubble for showing messages to the user. It points at | |
30 * the Most Visited label, and is used to draw more attention to the | |
31 * navigation dot UI. | |
32 * @type {!Element|undefined} | |
33 */ | |
34 var infoBubble; | |
35 | |
36 /** | |
37 * If non-null, an bubble confirming that the user has signed into sync. It | |
38 * points at the login status at the top of the page. | |
39 * @type {!Element|undefined} | |
40 */ | |
41 var loginBubble; | |
42 | |
43 /** | |
44 * true if |loginBubble| should be shown. | |
45 * @type {Boolean} | |
46 */ | |
47 var shouldShowLoginBubble = false; | |
48 | |
49 /** | |
50 * The 'other-sessions-menu-button' element. | |
51 * @type {!Element|undefined} | |
52 */ | |
53 var otherSessionsButton; | |
54 | |
55 /** | |
56 * The time in milliseconds for most transitions. This should match what's | |
57 * in new_tab.css. Unfortunately there's no better way to try to time | |
58 * something to occur until after a transition has completed. | |
59 * @type {number} | |
60 * @const | |
61 */ | |
62 var DEFAULT_TRANSITION_TIME = 500; | |
63 | |
64 /** | |
65 * See description for these values in ntp_stats.h. | |
66 * @enum {number} | |
67 */ | |
68 var NtpFollowAction = { | |
69 CLICKED_TILE: 11, | |
70 CLICKED_OTHER_NTP_PANE: 12, | |
71 OTHER: 13 | |
72 }; | |
73 | |
74 /** | |
75 * Creates a NewTabView object. NewTabView extends PageListView with | |
76 * new tab UI specific logics. | |
77 * @constructor | |
78 * @extends {PageListView} | |
79 */ | |
80 function NewTabView() { | |
81 var pageSwitcherStart = null; | |
82 var pageSwitcherEnd = null; | |
83 if (loadTimeData.getValue('showApps')) { | |
84 pageSwitcherStart = getRequiredElement('page-switcher-start'); | |
85 pageSwitcherEnd = getRequiredElement('page-switcher-end'); | |
86 } | |
87 this.initialize(getRequiredElement('page-list'), | |
88 getRequiredElement('dot-list'), | |
89 getRequiredElement('card-slider-frame'), | |
90 getRequiredElement('trash'), | |
91 pageSwitcherStart, pageSwitcherEnd); | |
92 } | |
93 | |
94 NewTabView.prototype = { | |
95 __proto__: ntp.PageListView.prototype, | |
96 | |
97 /** @inheritDoc */ | |
98 appendTilePage: function(page, title, titleIsEditable, opt_refNode) { | |
99 ntp.PageListView.prototype.appendTilePage.apply(this, arguments); | |
100 | |
101 if (infoBubble) | |
102 window.setTimeout(infoBubble.reposition.bind(infoBubble), 0); | |
103 } | |
104 }; | |
105 | |
106 /** | |
107 * Invoked at startup once the DOM is available to initialize the app. | |
108 */ | |
109 function onLoad() { | |
110 sectionsToWaitFor = loadTimeData.getBoolean('showApps') ? 2 : 1; | |
111 if (loadTimeData.getBoolean('isSuggestionsPageEnabled')) | |
112 sectionsToWaitFor++; | |
113 measureNavDots(); | |
114 | |
115 // Load the current theme colors. | |
116 themeChanged(); | |
117 | |
118 newTabView = new NewTabView(); | |
119 | |
120 notificationContainer = getRequiredElement('notification-container'); | |
121 notificationContainer.addEventListener( | |
122 'webkitTransitionEnd', onNotificationTransitionEnd); | |
123 | |
124 cr.ui.decorate($('recently-closed-menu-button'), ntp.RecentMenuButton); | |
125 chrome.send('getRecentlyClosedTabs'); | |
126 | |
127 if (loadTimeData.getBoolean('showOtherSessionsMenu')) { | |
128 otherSessionsButton = getRequiredElement('other-sessions-menu-button'); | |
129 cr.ui.decorate(otherSessionsButton, ntp.OtherSessionsMenuButton); | |
130 otherSessionsButton.initialize(loadTimeData.getBoolean('isUserSignedIn')); | |
131 } | |
132 | |
133 var mostVisited = new ntp.MostVisitedPage(); | |
134 // Move the footer into the most visited page if we are in "bare minimum" | |
135 // mode. | |
136 if (document.body.classList.contains('bare-minimum')) | |
137 mostVisited.appendFooter(getRequiredElement('footer')); | |
138 newTabView.appendTilePage(mostVisited, | |
139 loadTimeData.getString('mostvisited'), | |
140 false); | |
141 chrome.send('getMostVisited'); | |
142 | |
143 if (loadTimeData.getBoolean('isSuggestionsPageEnabled')) { | |
144 var suggestions_script = document.createElement('script'); | |
145 suggestions_script.src = 'suggestions_page.js'; | |
146 suggestions_script.onload = function() { | |
147 newTabView.appendTilePage(new ntp.SuggestionsPage(), | |
148 loadTimeData.getString('suggestions'), | |
149 false, | |
150 (newTabView.appsPages.length > 0) ? | |
151 newTabView.appsPages[0] : null); | |
152 chrome.send('getSuggestions'); | |
153 cr.dispatchSimpleEvent(document, 'sectionready', true, true); | |
154 }; | |
155 document.querySelector('head').appendChild(suggestions_script); | |
156 } | |
157 | |
158 var webStoreLink = loadTimeData.getString('webStoreLink'); | |
159 var url = appendParam(webStoreLink, 'utm_source', 'chrome-ntp-launcher'); | |
160 $('chrome-web-store-link').href = url; | |
161 $('chrome-web-store-link').addEventListener('click', | |
162 onChromeWebStoreButtonClick); | |
163 | |
164 if (loadTimeData.getString('login_status_message')) { | |
165 loginBubble = new cr.ui.Bubble; | |
166 loginBubble.anchorNode = $('login-container'); | |
167 loginBubble.setArrowLocation(cr.ui.ArrowLocation.TOP_END); | |
168 loginBubble.bubbleAlignment = | |
169 cr.ui.BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE; | |
170 loginBubble.deactivateToDismissDelay = 2000; | |
171 loginBubble.setCloseButtonVisible(false); | |
172 | |
173 $('login-status-advanced').onclick = function() { | |
174 chrome.send('showAdvancedLoginUI'); | |
175 }; | |
176 $('login-status-dismiss').onclick = loginBubble.hide.bind(loginBubble); | |
177 | |
178 var bubbleContent = $('login-status-bubble-contents'); | |
179 loginBubble.content = bubbleContent; | |
180 | |
181 // The anchor node won't be updated until updateLogin is called so don't | |
182 // show the bubble yet. | |
183 shouldShowLoginBubble = true; | |
184 } | |
185 | |
186 var loginContainer = getRequiredElement('login-container'); | |
187 loginContainer.addEventListener('click', showSyncLoginUI); | |
188 chrome.send('initializeSyncLogin'); | |
189 | |
190 doWhenAllSectionsReady(function() { | |
191 | |
192 // TODO(xci) new! | |
193 // TODO(pedrosimonetti): find a better place to put this code. Every, | |
194 // card needs to call layout the first time is shown. | |
195 newTabView.cardSlider.currentCardValue.layout_(); | |
196 | |
197 // Tell the slider about the pages. | |
198 newTabView.updateSliderCards(); | |
199 // Mark the current page. | |
200 newTabView.cardSlider.currentCardValue.navigationDot.classList.add( | |
201 'selected'); | |
202 | |
203 if (loadTimeData.valueExists('serverpromo')) { | |
204 var promo = loadTimeData.getString('serverpromo'); | |
205 var tags = ['IMG']; | |
206 var attrs = { | |
207 src: function(node, value) { | |
208 return node.tagName == 'IMG' && | |
209 /^data\:image\/(?:png|gif|jpe?g)/.test(value); | |
210 }, | |
211 }; | |
212 showNotification(parseHtmlSubset(promo, tags, attrs), [], function() { | |
213 chrome.send('closeNotificationPromo'); | |
214 }, 60000); | |
215 chrome.send('notificationPromoViewed'); | |
216 } | |
217 | |
218 cr.dispatchSimpleEvent(document, 'ntpLoaded', true, true); | |
219 document.documentElement.classList.remove('starting-up'); | |
220 }); | |
221 } | |
222 | |
223 /** | |
224 * Launches the chrome web store app with the chrome-ntp-launcher | |
225 * source. | |
226 * @param {Event} e The click event. | |
227 */ | |
228 function onChromeWebStoreButtonClick(e) { | |
229 chrome.send('recordAppLaunchByURL', | |
230 [encodeURIComponent(this.href), | |
231 ntp.APP_LAUNCH.NTP_WEBSTORE_FOOTER]); | |
232 } | |
233 | |
234 /* | |
235 * The number of sections to wait on. | |
236 * @type {number} | |
237 */ | |
238 var sectionsToWaitFor = -1; | |
239 | |
240 /** | |
241 * Queued callbacks which lie in wait for all sections to be ready. | |
242 * @type {array} | |
243 */ | |
244 var readyCallbacks = []; | |
245 | |
246 /** | |
247 * Fired as each section of pages becomes ready. | |
248 * @param {Event} e Each page's synthetic DOM event. | |
249 */ | |
250 document.addEventListener('sectionready', function(e) { | |
251 if (--sectionsToWaitFor <= 0) { | |
252 while (readyCallbacks.length) { | |
253 readyCallbacks.shift()(); | |
254 } | |
255 } | |
256 }); | |
257 | |
258 /** | |
259 * This is used to simulate a fire-once event (i.e. $(document).ready() in | |
260 * jQuery or Y.on('domready') in YUI. If all sections are ready, the callback | |
261 * is fired right away. If all pages are not ready yet, the function is queued | |
262 * for later execution. | |
263 * @param {function} callback The work to be done when ready. | |
264 */ | |
265 function doWhenAllSectionsReady(callback) { | |
266 assert(typeof callback == 'function'); | |
267 if (sectionsToWaitFor > 0) | |
268 readyCallbacks.push(callback); | |
269 else | |
270 window.setTimeout(callback, 0); // Do soon after, but asynchronously. | |
271 } | |
272 | |
273 /** | |
274 * Fills in an invisible div with the 'Most Visited' string so that | |
275 * its length may be measured and the nav dots sized accordingly. | |
276 */ | |
277 function measureNavDots() { | |
278 var measuringDiv = $('fontMeasuringDiv'); | |
279 measuringDiv.textContent = loadTimeData.getString('mostvisited'); | |
280 // The 4 is for border and padding. | |
281 var pxWidth = Math.max(measuringDiv.clientWidth * 1.15 + 4, 80); | |
282 | |
283 var styleElement = document.createElement('style'); | |
284 styleElement.type = 'text/css'; | |
285 // max-width is used because if we run out of space, the nav dots will be | |
286 // shrunk. | |
287 styleElement.textContent = '.dot { max-width: ' + pxWidth + 'px; }'; | |
288 document.querySelector('head').appendChild(styleElement); | |
289 } | |
290 | |
291 function themeChanged(opt_hasAttribution) { | |
292 $('themecss').href = 'chrome://theme/css/new_tab_theme.css?' + Date.now(); | |
293 | |
294 if (typeof opt_hasAttribution != 'undefined') { | |
295 document.documentElement.setAttribute('hasattribution', | |
296 opt_hasAttribution); | |
297 } | |
298 | |
299 updateAttribution(); | |
300 } | |
301 | |
302 function setBookmarkBarAttached(attached) { | |
303 document.documentElement.setAttribute('bookmarkbarattached', attached); | |
304 } | |
305 | |
306 /** | |
307 * Attributes the attribution image at the bottom left. | |
308 */ | |
309 function updateAttribution() { | |
310 var attribution = $('attribution'); | |
311 if (document.documentElement.getAttribute('hasattribution') == 'true') { | |
312 $('attribution-img').src = | |
313 'chrome://theme/IDR_THEME_NTP_ATTRIBUTION?' + Date.now(); | |
314 attribution.hidden = false; | |
315 } else { | |
316 attribution.hidden = true; | |
317 } | |
318 } | |
319 | |
320 /** | |
321 * Timeout ID. | |
322 * @type {number} | |
323 */ | |
324 var notificationTimeout = 0; | |
325 | |
326 /** | |
327 * Shows the notification bubble. | |
328 * @param {string|Node} message The notification message or node to use as | |
329 * message. | |
330 * @param {Array.<{text: string, action: function()}>} links An array of | |
331 * records describing the links in the notification. Each record should | |
332 * have a 'text' attribute (the display string) and an 'action' attribute | |
333 * (a function to run when the link is activated). | |
334 * @param {Function} opt_closeHandler The callback invoked if the user | |
335 * manually dismisses the notification. | |
336 */ | |
337 function showNotification(message, links, opt_closeHandler, opt_timeout) { | |
338 window.clearTimeout(notificationTimeout); | |
339 | |
340 var span = document.querySelector('#notification > span'); | |
341 if (typeof message == 'string') { | |
342 span.textContent = message; | |
343 } else { | |
344 span.textContent = ''; // Remove all children. | |
345 span.appendChild(message); | |
346 } | |
347 | |
348 var linksBin = $('notificationLinks'); | |
349 linksBin.textContent = ''; | |
350 for (var i = 0; i < links.length; i++) { | |
351 var link = linksBin.ownerDocument.createElement('div'); | |
352 link.textContent = links[i].text; | |
353 link.action = links[i].action; | |
354 link.onclick = function() { | |
355 this.action(); | |
356 hideNotification(); | |
357 }; | |
358 link.setAttribute('role', 'button'); | |
359 link.setAttribute('tabindex', 0); | |
360 link.className = 'link-button'; | |
361 linksBin.appendChild(link); | |
362 } | |
363 | |
364 function closeFunc(e) { | |
365 if (opt_closeHandler) | |
366 opt_closeHandler(); | |
367 hideNotification(); | |
368 } | |
369 | |
370 document.querySelector('#notification button').onclick = closeFunc; | |
371 document.addEventListener('dragstart', closeFunc); | |
372 | |
373 notificationContainer.hidden = false; | |
374 showNotificationOnCurrentPage(); | |
375 | |
376 newTabView.cardSlider.frame.addEventListener( | |
377 'cardSlider:card_change_ended', onCardChangeEnded); | |
378 | |
379 var timeout = opt_timeout || 10000; | |
380 notificationTimeout = window.setTimeout(hideNotification, timeout); | |
381 } | |
382 | |
383 /** | |
384 * Hide the notification bubble. | |
385 */ | |
386 function hideNotification() { | |
387 notificationContainer.classList.add('inactive'); | |
388 | |
389 newTabView.cardSlider.frame.removeEventListener( | |
390 'cardSlider:card_change_ended', onCardChangeEnded); | |
391 } | |
392 | |
393 /** | |
394 * Happens when 1 or more consecutive card changes end. | |
395 * @param {Event} e The cardSlider:card_change_ended event. | |
396 */ | |
397 function onCardChangeEnded(e) { | |
398 // If we ended on the same page as we started, ignore. | |
399 if (newTabView.cardSlider.currentCardValue.notification) | |
400 return; | |
401 | |
402 // Hide the notification the old page. | |
403 notificationContainer.classList.add('card-changed'); | |
404 | |
405 showNotificationOnCurrentPage(); | |
406 } | |
407 | |
408 /** | |
409 * Move and show the notification on the current page. | |
410 */ | |
411 function showNotificationOnCurrentPage() { | |
412 var page = newTabView.cardSlider.currentCardValue; | |
413 doWhenAllSectionsReady(function() { | |
414 if (page != newTabView.cardSlider.currentCardValue) | |
415 return; | |
416 | |
417 // NOTE: This moves the notification to inside of the current page. | |
418 page.notification = notificationContainer; | |
419 | |
420 // Reveal the notification and instruct it to hide itself if ignored. | |
421 notificationContainer.classList.remove('inactive'); | |
422 | |
423 // Gives the browser time to apply this rule before we remove it (causing | |
424 // a transition). | |
425 window.setTimeout(function() { | |
426 notificationContainer.classList.remove('card-changed'); | |
427 }, 0); | |
428 }); | |
429 } | |
430 | |
431 /** | |
432 * When done fading out, set hidden to true so the notification can't be | |
433 * tabbed to or clicked. | |
434 * @param {Event} e The webkitTransitionEnd event. | |
435 */ | |
436 function onNotificationTransitionEnd(e) { | |
437 if (notificationContainer.classList.contains('inactive')) | |
438 notificationContainer.hidden = true; | |
439 } | |
440 | |
441 function setRecentlyClosedTabs(dataItems) { | |
442 $('recently-closed-menu-button').dataItems = dataItems; | |
443 } | |
444 | |
445 function setMostVisitedPages(data, hasBlacklistedUrls) { | |
446 newTabView.mostVisitedPage.data = data; | |
447 cr.dispatchSimpleEvent(document, 'sectionready', true, true); | |
448 } | |
449 | |
450 function setSuggestionsPages(data, hasBlacklistedUrls) { | |
451 newTabView.suggestionsPage.data = data; | |
452 } | |
453 | |
454 function getThumbnailUrl(url) { | |
455 return 'chrome://thumb/' + url; | |
456 } | |
457 | |
458 /** | |
459 * Set the dominant color for a node. This will be called in response to | |
460 * getFaviconDominantColor. The node represented by |id| better have a setter | |
461 * for stripeColor. | |
462 * @param {string} id The ID of a node. | |
463 * @param {string} color The color represented as a CSS string. | |
464 */ | |
465 function setStripeColor(id, color) { | |
466 var node = $(id); | |
467 if (node) | |
468 node.stripeColor = color; | |
469 } | |
470 | |
471 /** | |
472 * Updates the text displayed in the login container. If there is no text then | |
473 * the login container is hidden. | |
474 * @param {string} loginHeader The first line of text. | |
475 * @param {string} loginSubHeader The second line of text. | |
476 * @param {string} iconURL The url for the login status icon. If this is null | |
477 then the login status icon is hidden. | |
478 * @param {boolean} isUserSignedIn Indicates if the user is signed in or not. | |
479 */ | |
480 function updateLogin(loginHeader, loginSubHeader, iconURL, isUserSignedIn) { | |
481 if (loginHeader || loginSubHeader) { | |
482 $('login-container').hidden = false; | |
483 $('login-status-header').innerHTML = loginHeader; | |
484 $('login-status-sub-header').innerHTML = loginSubHeader; | |
485 $('card-slider-frame').classList.add('showing-login-area'); | |
486 | |
487 if (iconURL) { | |
488 $('login-status-header-container').style.backgroundImage = url(iconURL); | |
489 $('login-status-header-container').classList.add('login-status-icon'); | |
490 } else { | |
491 $('login-status-header-container').style.backgroundImage = 'none'; | |
492 $('login-status-header-container').classList.remove( | |
493 'login-status-icon'); | |
494 } | |
495 } else { | |
496 $('login-container').hidden = true; | |
497 $('card-slider-frame').classList.remove('showing-login-area'); | |
498 } | |
499 if (shouldShowLoginBubble) { | |
500 window.setTimeout(loginBubble.show.bind(loginBubble), 0); | |
501 chrome.send('loginMessageSeen'); | |
502 shouldShowLoginBubble = false; | |
503 } else if (loginBubble) { | |
504 loginBubble.reposition(); | |
505 } | |
506 if (otherSessionsButton) | |
507 otherSessionsButton.updateSignInState(isUserSignedIn); | |
508 } | |
509 | |
510 /** | |
511 * Show the sync login UI. | |
512 * @param {Event} e The click event. | |
513 */ | |
514 function showSyncLoginUI(e) { | |
515 var rect = e.currentTarget.getBoundingClientRect(); | |
516 chrome.send('showSyncLoginUI', | |
517 [rect.left, rect.top, rect.width, rect.height]); | |
518 } | |
519 | |
520 /** | |
521 * Wrappers to forward the callback to corresponding PageListView member. | |
522 */ | |
523 function appAdded() { | |
524 return newTabView.appAdded.apply(newTabView, arguments); | |
525 } | |
526 | |
527 function appMoved() { | |
528 return newTabView.appMoved.apply(newTabView, arguments); | |
529 } | |
530 | |
531 function appRemoved() { | |
532 return newTabView.appRemoved.apply(newTabView, arguments); | |
533 } | |
534 | |
535 function appsPrefChangeCallback() { | |
536 return newTabView.appsPrefChangedCallback.apply(newTabView, arguments); | |
537 } | |
538 | |
539 function appsReordered() { | |
540 return newTabView.appsReordered.apply(newTabView, arguments); | |
541 } | |
542 | |
543 function enterRearrangeMode() { | |
544 return newTabView.enterRearrangeMode.apply(newTabView, arguments); | |
545 } | |
546 | |
547 function setForeignSessions(sessionList, isTabSyncEnabled) { | |
548 if (otherSessionsButton) | |
549 otherSessionsButton.setForeignSessions(sessionList, isTabSyncEnabled); | |
550 } | |
551 | |
552 function getAppsCallback() { | |
553 return newTabView.getAppsCallback.apply(newTabView, arguments); | |
554 } | |
555 | |
556 function getAppsPageIndex() { | |
557 return newTabView.getAppsPageIndex.apply(newTabView, arguments); | |
558 } | |
559 | |
560 function getCardSlider() { | |
561 return newTabView.cardSlider; | |
562 } | |
563 | |
564 function leaveRearrangeMode() { | |
565 return newTabView.leaveRearrangeMode.apply(newTabView, arguments); | |
566 } | |
567 | |
568 function saveAppPageName() { | |
569 return newTabView.saveAppPageName.apply(newTabView, arguments); | |
570 } | |
571 | |
572 function setAppToBeHighlighted(appId) { | |
573 newTabView.highlightAppId = appId; | |
574 } | |
575 | |
576 // Return an object with all the exports | |
577 return { | |
578 appAdded: appAdded, | |
579 appMoved: appMoved, | |
580 appRemoved: appRemoved, | |
581 appsPrefChangeCallback: appsPrefChangeCallback, | |
582 enterRearrangeMode: enterRearrangeMode, | |
583 getAppsCallback: getAppsCallback, | |
584 getAppsPageIndex: getAppsPageIndex, | |
585 getCardSlider: getCardSlider, | |
586 onLoad: onLoad, | |
587 leaveRearrangeMode: leaveRearrangeMode, | |
588 NtpFollowAction: NtpFollowAction, | |
589 saveAppPageName: saveAppPageName, | |
590 setAppToBeHighlighted: setAppToBeHighlighted, | |
591 setBookmarkBarAttached: setBookmarkBarAttached, | |
592 setForeignSessions: setForeignSessions, | |
593 setMostVisitedPages: setMostVisitedPages, | |
594 setSuggestionsPages: setSuggestionsPages, | |
595 setRecentlyClosedTabs: setRecentlyClosedTabs, | |
596 getThumbnailUrl: getThumbnailUrl, | |
jeremycho_google
2012/07/31 03:09:16
Alphabetize.
pedrosimonetti2
2012/08/03 18:14:01
Done.
| |
597 setStripeColor: setStripeColor, | |
598 showNotification: showNotification, | |
599 themeChanged: themeChanged, | |
600 updateLogin: updateLogin | |
601 }; | |
602 }); | |
603 | |
604 document.addEventListener('DOMContentLoaded', ntp.onLoad); | |
605 | |
606 var toCssPx = cr.ui.toCssPx; | |
OLD | NEW |