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.
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.
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() {
17 * NewTabView instance.
18 * @type {!Object|undefined}
23 * The 'notification-container' element.
24 * @type {!Element|undefined}
26 var notificationContainer
;
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
32 * @type {!Element|undefined}
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}
44 * true if |loginBubble| should be shown.
47 var shouldShowLoginBubble
= false;
50 * The 'other-sessions-menu-button' element.
51 * @type {!Element|undefined}
53 var otherSessionsButton
;
56 * The time when all sections are ready.
57 * @type {number|undefined}
63 * The time in milliseconds for most transitions. This should match what's
64 * in new_tab.css. Unfortunately there's no better way to try to time
65 * something to occur until after a transition has completed.
69 var DEFAULT_TRANSITION_TIME
= 500;
72 * See description for these values in ntp_stats.h.
75 var NtpFollowAction
= {
77 CLICKED_OTHER_NTP_PANE
: 12,
82 * Creates a NewTabView object. NewTabView extends PageListView with
83 * new tab UI specific logics.
85 * @extends {PageListView}
87 function NewTabView() {
88 var pageSwitcherStart
= null;
89 var pageSwitcherEnd
= null;
90 if (loadTimeData
.getValue('showApps')) {
91 pageSwitcherStart
= getRequiredElement('page-switcher-start');
92 pageSwitcherEnd
= getRequiredElement('page-switcher-end');
94 this.initialize(getRequiredElement('page-list'),
95 getRequiredElement('dot-list'),
96 getRequiredElement('card-slider-frame'),
97 getRequiredElement('trash'),
98 pageSwitcherStart
, pageSwitcherEnd
);
101 NewTabView
.prototype = {
102 __proto__
: ntp
.PageListView
.prototype,
105 appendTilePage: function(page
, title
, titleIsEditable
, opt_refNode
) {
106 ntp
.PageListView
.prototype.appendTilePage
.apply(this, arguments
);
109 window
.setTimeout(promoBubble
.reposition
.bind(promoBubble
), 0);
114 * Invoked at startup once the DOM is available to initialize the app.
117 sectionsToWaitFor
= loadTimeData
.getBoolean('showApps') ? 2 : 1;
118 if (loadTimeData
.getBoolean('isDiscoveryInNTPEnabled'))
122 // Load the current theme colors.
125 newTabView
= new NewTabView();
127 notificationContainer
= getRequiredElement('notification-container');
128 notificationContainer
.addEventListener(
129 'webkitTransitionEnd', onNotificationTransitionEnd
);
131 cr
.ui
.decorate($('recently-closed-menu-button'), ntp
.RecentMenuButton
);
132 chrome
.send('getRecentlyClosedTabs');
134 if (loadTimeData
.getBoolean('showOtherSessionsMenu')) {
135 otherSessionsButton
= getRequiredElement('other-sessions-menu-button');
136 cr
.ui
.decorate(otherSessionsButton
, ntp
.OtherSessionsMenuButton
);
137 otherSessionsButton
.initialize(loadTimeData
.getBoolean('isUserSignedIn'));
140 var mostVisited
= new ntp
.MostVisitedPage();
141 // Move the footer into the most visited page if we are in "bare minimum"
143 if (document
.body
.classList
.contains('bare-minimum'))
144 mostVisited
.appendFooter(getRequiredElement('footer'));
145 newTabView
.appendTilePage(mostVisited
,
146 loadTimeData
.getString('mostvisited'),
148 chrome
.send('getMostVisited');
150 if (loadTimeData
.getBoolean('isDiscoveryInNTPEnabled')) {
151 var suggestions_script
= document
.createElement('script');
152 suggestions_script
.src
= 'suggestions_page.js';
153 suggestions_script
.onload = function() {
154 newTabView
.appendTilePage(new ntp
.SuggestionsPage(),
155 loadTimeData
.getString('suggestions'),
157 (newTabView
.appsPages
.length
> 0) ?
158 newTabView
.appsPages
[0] : null);
159 chrome
.send('getSuggestions');
160 cr
.dispatchSimpleEvent(document
, 'sectionready', true, true);
162 document
.querySelector('head').appendChild(suggestions_script
);
165 var webStoreLink
= loadTimeData
.getString('webStoreLink');
166 var url
= appendParam(webStoreLink
, 'utm_source', 'chrome-ntp-launcher');
167 $('chrome-web-store-link').href
= url
;
168 $('chrome-web-store-link').addEventListener('click',
169 onChromeWebStoreButtonClick
);
171 if (loadTimeData
.getString('login_status_message')) {
172 loginBubble
= new cr
.ui
.Bubble
;
173 loginBubble
.anchorNode
= $('login-container');
174 loginBubble
.arrowLocation
= cr
.ui
.ArrowLocation
.TOP_END
;
175 loginBubble
.bubbleAlignment
=
176 cr
.ui
.BubbleAlignment
.BUBBLE_EDGE_TO_ANCHOR_EDGE
;
177 loginBubble
.deactivateToDismissDelay
= 2000;
178 loginBubble
.closeButtonVisible
= false;
180 $('login-status-advanced').onclick = function() {
181 chrome
.send('showAdvancedLoginUI');
183 $('login-status-dismiss').onclick
= loginBubble
.hide
.bind(loginBubble
);
185 var bubbleContent
= $('login-status-bubble-contents');
186 loginBubble
.content
= bubbleContent
;
188 // The anchor node won't be updated until updateLogin is called so don't
189 // show the bubble yet.
190 shouldShowLoginBubble
= true;
193 if (loadTimeData
.valueExists('bubblePromoText')) {
194 promoBubble
= new cr
.ui
.Bubble
;
195 promoBubble
.anchorNode
= getRequiredElement('promo-bubble-anchor');
196 promoBubble
.arrowLocation
= cr
.ui
.ArrowLocation
.BOTTOM_START
;
197 promoBubble
.bubbleAlignment
= cr
.ui
.BubbleAlignment
.ENTIRELY_VISIBLE
;
198 promoBubble
.deactivateToDismissDelay
= 2000;
199 promoBubble
.content
= parseHtmlSubset(
200 loadTimeData
.getString('bubblePromoText'), ['BR']);
202 var bubbleLink
= promoBubble
.querySelector('a');
204 bubbleLink
.addEventListener('click', function(e
) {
205 chrome
.send('bubblePromoLinkClicked');
209 promoBubble
.handleCloseEvent = function() {
211 chrome
.send('bubblePromoClosed');
214 chrome
.send('bubblePromoViewed');
217 var loginContainer
= getRequiredElement('login-container');
218 loginContainer
.addEventListener('click', showSyncLoginUI
);
219 chrome
.send('initializeSyncLogin');
221 doWhenAllSectionsReady(function() {
222 // Tell the slider about the pages.
223 newTabView
.updateSliderCards();
224 // Mark the current page.
225 newTabView
.cardSlider
.currentCardValue
.navigationDot
.classList
.add(
228 if (loadTimeData
.valueExists('notificationPromoText')) {
229 var promoText
= loadTimeData
.getString('notificationPromoText');
232 src: function(node
, value
) {
233 return node
.tagName
== 'IMG' &&
234 /^data\:image\/(?:png|gif|jpe?g)/.test(value
);
238 var promo
= parseHtmlSubset(promoText
, tags
, attrs
);
239 var promoLink
= promo
.querySelector('a');
241 promoLink
.addEventListener('click', function(e
) {
242 chrome
.send('notificationPromoLinkClicked');
246 showNotification(promo
, [], function() {
247 chrome
.send('notificationPromoClosed');
249 chrome
.send('notificationPromoViewed');
252 cr
.dispatchSimpleEvent(document
, 'ntpLoaded', true, true);
253 document
.documentElement
.classList
.remove('starting-up');
255 startTime
= Date
.now();
258 preventDefaultOnPoundLinkClicks(); // From shared/js/util.js.
262 * Launches the chrome web store app with the chrome-ntp-launcher
264 * @param {Event} e The click event.
266 function onChromeWebStoreButtonClick(e
) {
267 chrome
.send('recordAppLaunchByURL',
268 [encodeURIComponent(this.href
),
269 ntp
.APP_LAUNCH
.NTP_WEBSTORE_FOOTER
]);
273 * The number of sections to wait on.
276 var sectionsToWaitFor
= -1;
279 * Queued callbacks which lie in wait for all sections to be ready.
282 var readyCallbacks
= [];
285 * Fired as each section of pages becomes ready.
286 * @param {Event} e Each page's synthetic DOM event.
288 document
.addEventListener('sectionready', function(e
) {
289 if (--sectionsToWaitFor
<= 0) {
290 while (readyCallbacks
.length
) {
291 readyCallbacks
.shift()();
297 * This is used to simulate a fire-once event (i.e. $(document).ready() in
298 * jQuery or Y.on('domready') in YUI. If all sections are ready, the callback
299 * is fired right away. If all pages are not ready yet, the function is queued
300 * for later execution.
301 * @param {function} callback The work to be done when ready.
303 function doWhenAllSectionsReady(callback
) {
304 assert(typeof callback
== 'function');
305 if (sectionsToWaitFor
> 0)
306 readyCallbacks
.push(callback
);
308 window
.setTimeout(callback
, 0); // Do soon after, but asynchronously.
312 * Fills in an invisible div with the 'Most Visited' string so that
313 * its length may be measured and the nav dots sized accordingly.
315 function measureNavDots() {
316 var measuringDiv
= $('fontMeasuringDiv');
317 measuringDiv
.textContent
= loadTimeData
.getString('mostvisited');
318 // The 4 is for border and padding.
319 var pxWidth
= Math
.max(measuringDiv
.clientWidth
* 1.15 + 4, 80);
321 var styleElement
= document
.createElement('style');
322 styleElement
.type
= 'text/css';
323 // max-width is used because if we run out of space, the nav dots will be
325 styleElement
.textContent
= '.dot { max-width: ' + pxWidth
+ 'px; }';
326 document
.querySelector('head').appendChild(styleElement
);
329 function themeChanged(opt_hasAttribution
) {
330 $('themecss').href
= 'chrome://theme/css/new_tab_theme.css?' + Date
.now();
332 if (typeof opt_hasAttribution
!= 'undefined') {
333 document
.documentElement
.setAttribute('hasattribution',
340 function setBookmarkBarAttached(attached
) {
341 document
.documentElement
.setAttribute('bookmarkbarattached', attached
);
345 * Attributes the attribution image at the bottom left.
347 function updateAttribution() {
348 var attribution
= $('attribution');
349 if (document
.documentElement
.getAttribute('hasattribution') == 'true') {
350 $('attribution-img').src
=
351 'chrome://theme/IDR_THEME_NTP_ATTRIBUTION?' + Date
.now();
352 attribution
.hidden
= false;
354 attribution
.hidden
= true;
362 var notificationTimeout
= 0;
365 * Shows the notification bubble.
366 * @param {string|Node} message The notification message or node to use as
368 * @param {Array.<{text: string, action: function()}>} links An array of
369 * records describing the links in the notification. Each record should
370 * have a 'text' attribute (the display string) and an 'action' attribute
371 * (a function to run when the link is activated).
372 * @param {Function} opt_closeHandler The callback invoked if the user
373 * manually dismisses the notification.
375 function showNotification(message
, links
, opt_closeHandler
, opt_timeout
) {
376 window
.clearTimeout(notificationTimeout
);
378 var span
= document
.querySelector('#notification > span');
379 if (typeof message
== 'string') {
380 span
.textContent
= message
;
382 span
.textContent
= ''; // Remove all children.
383 span
.appendChild(message
);
386 var linksBin
= $('notificationLinks');
387 linksBin
.textContent
= '';
388 for (var i
= 0; i
< links
.length
; i
++) {
389 var link
= linksBin
.ownerDocument
.createElement('div');
390 link
.textContent
= links
[i
].text
;
391 link
.action
= links
[i
].action
;
392 link
.onclick = function() {
396 link
.setAttribute('role', 'button');
397 link
.setAttribute('tabindex', 0);
398 link
.className
= 'link-button';
399 linksBin
.appendChild(link
);
402 function closeFunc(e
) {
403 if (opt_closeHandler
)
408 document
.querySelector('#notification button').onclick
= closeFunc
;
409 document
.addEventListener('dragstart', closeFunc
);
411 notificationContainer
.hidden
= false;
412 showNotificationOnCurrentPage();
414 newTabView
.cardSlider
.frame
.addEventListener(
415 'cardSlider:card_change_ended', onCardChangeEnded
);
417 var timeout
= opt_timeout
|| 10000;
418 notificationTimeout
= window
.setTimeout(hideNotification
, timeout
);
422 * Hide the notification bubble.
424 function hideNotification() {
425 notificationContainer
.classList
.add('inactive');
427 newTabView
.cardSlider
.frame
.removeEventListener(
428 'cardSlider:card_change_ended', onCardChangeEnded
);
432 * Happens when 1 or more consecutive card changes end.
433 * @param {Event} e The cardSlider:card_change_ended event.
435 function onCardChangeEnded(e
) {
436 // If we ended on the same page as we started, ignore.
437 if (newTabView
.cardSlider
.currentCardValue
.notification
)
440 // Hide the notification the old page.
441 notificationContainer
.classList
.add('card-changed');
443 showNotificationOnCurrentPage();
447 * Move and show the notification on the current page.
449 function showNotificationOnCurrentPage() {
450 var page
= newTabView
.cardSlider
.currentCardValue
;
451 doWhenAllSectionsReady(function() {
452 if (page
!= newTabView
.cardSlider
.currentCardValue
)
455 // NOTE: This moves the notification to inside of the current page.
456 page
.notification
= notificationContainer
;
458 // Reveal the notification and instruct it to hide itself if ignored.
459 notificationContainer
.classList
.remove('inactive');
461 // Gives the browser time to apply this rule before we remove it (causing
463 window
.setTimeout(function() {
464 notificationContainer
.classList
.remove('card-changed');
470 * When done fading out, set hidden to true so the notification can't be
471 * tabbed to or clicked.
472 * @param {Event} e The webkitTransitionEnd event.
474 function onNotificationTransitionEnd(e
) {
475 if (notificationContainer
.classList
.contains('inactive'))
476 notificationContainer
.hidden
= true;
479 function setRecentlyClosedTabs(dataItems
) {
480 $('recently-closed-menu-button').dataItems
= dataItems
;
483 function setMostVisitedPages(data
, hasBlacklistedUrls
) {
484 newTabView
.mostVisitedPage
.data
= data
;
485 cr
.dispatchSimpleEvent(document
, 'sectionready', true, true);
488 function setSuggestionsPages(data
, hasBlacklistedUrls
) {
489 newTabView
.suggestionsPage
.data
= data
;
493 * Set the dominant color for a node. This will be called in response to
494 * getFaviconDominantColor. The node represented by |id| better have a setter
496 * @param {string} id The ID of a node.
497 * @param {string} color The color represented as a CSS string.
499 function setFaviconDominantColor(id
, color
) {
502 node
.stripeColor
= color
;
506 * Updates the text displayed in the login container. If there is no text then
507 * the login container is hidden.
508 * @param {string} loginHeader The first line of text.
509 * @param {string} loginSubHeader The second line of text.
510 * @param {string} iconURL The url for the login status icon. If this is null
511 then the login status icon is hidden.
512 * @param {boolean} isUserSignedIn Indicates if the user is signed in or not.
514 function updateLogin(loginHeader
, loginSubHeader
, iconURL
, isUserSignedIn
) {
515 if (loginHeader
|| loginSubHeader
) {
516 $('login-container').hidden
= false;
517 $('login-status-header').innerHTML
= loginHeader
;
518 $('login-status-sub-header').innerHTML
= loginSubHeader
;
519 $('card-slider-frame').classList
.add('showing-login-area');
522 $('login-status-header-container').style
.backgroundImage
= url(iconURL
);
523 $('login-status-header-container').classList
.add('login-status-icon');
525 $('login-status-header-container').style
.backgroundImage
= 'none';
526 $('login-status-header-container').classList
.remove(
527 'login-status-icon');
530 $('login-container').hidden
= true;
531 $('card-slider-frame').classList
.remove('showing-login-area');
533 if (shouldShowLoginBubble
) {
534 window
.setTimeout(loginBubble
.show
.bind(loginBubble
), 0);
535 chrome
.send('loginMessageSeen');
536 shouldShowLoginBubble
= false;
537 } else if (loginBubble
) {
538 loginBubble
.reposition();
540 if (otherSessionsButton
)
541 otherSessionsButton
.updateSignInState(isUserSignedIn
);
545 * Show the sync login UI.
546 * @param {Event} e The click event.
548 function showSyncLoginUI(e
) {
549 var rect
= e
.currentTarget
.getBoundingClientRect();
550 chrome
.send('showSyncLoginUI',
551 [rect
.left
, rect
.top
, rect
.width
, rect
.height
]);
555 * Logs the time to click for the specified item.
556 * @param {string} item The item to log the time-to-click.
558 function logTimeToClick(item
) {
559 var timeToClick
= Date
.now() - startTime
;
560 chrome
.send('logTimeToClick',
561 ['NewTabPage.TimeToClick' + item
, timeToClick
]);
565 * Wrappers to forward the callback to corresponding PageListView member.
567 function appAdded() {
568 return newTabView
.appAdded
.apply(newTabView
, arguments
);
571 function appMoved() {
572 return newTabView
.appMoved
.apply(newTabView
, arguments
);
575 function appRemoved() {
576 return newTabView
.appRemoved
.apply(newTabView
, arguments
);
579 function appsPrefChangeCallback() {
580 return newTabView
.appsPrefChangedCallback
.apply(newTabView
, arguments
);
583 function appsReordered() {
584 return newTabView
.appsReordered
.apply(newTabView
, arguments
);
587 function enterRearrangeMode() {
588 return newTabView
.enterRearrangeMode
.apply(newTabView
, arguments
);
591 function setForeignSessions(sessionList
, isTabSyncEnabled
) {
592 if (otherSessionsButton
)
593 otherSessionsButton
.setForeignSessions(sessionList
, isTabSyncEnabled
);
596 function getAppsCallback() {
597 return newTabView
.getAppsCallback
.apply(newTabView
, arguments
);
600 function getAppsPageIndex() {
601 return newTabView
.getAppsPageIndex
.apply(newTabView
, arguments
);
604 function getCardSlider() {
605 return newTabView
.cardSlider
;
608 function leaveRearrangeMode() {
609 return newTabView
.leaveRearrangeMode
.apply(newTabView
, arguments
);
612 function saveAppPageName() {
613 return newTabView
.saveAppPageName
.apply(newTabView
, arguments
);
616 function setAppToBeHighlighted(appId
) {
617 newTabView
.highlightAppId
= appId
;
620 // Return an object with all the exports
624 appRemoved
: appRemoved
,
625 appsPrefChangeCallback
: appsPrefChangeCallback
,
626 enterRearrangeMode
: enterRearrangeMode
,
627 getAppsCallback
: getAppsCallback
,
628 getAppsPageIndex
: getAppsPageIndex
,
629 getCardSlider
: getCardSlider
,
631 leaveRearrangeMode
: leaveRearrangeMode
,
632 logTimeToClick
: logTimeToClick
,
633 NtpFollowAction
: NtpFollowAction
,
634 saveAppPageName
: saveAppPageName
,
635 setAppToBeHighlighted
: setAppToBeHighlighted
,
636 setBookmarkBarAttached
: setBookmarkBarAttached
,
637 setForeignSessions
: setForeignSessions
,
638 setMostVisitedPages
: setMostVisitedPages
,
639 setSuggestionsPages
: setSuggestionsPages
,
640 setRecentlyClosedTabs
: setRecentlyClosedTabs
,
641 setFaviconDominantColor
: setFaviconDominantColor
,
642 showNotification
: showNotification
,
643 themeChanged
: themeChanged
,
644 updateLogin
: updateLogin
648 document
.addEventListener('DOMContentLoaded', ntp
.onLoad
);
650 var toCssPx
= cr
.ui
.toCssPx
;