Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / resources / ntp4 / new_tab.js
blob6d90cf5358673d6559905e98bba63b0fe98957be
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.
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  */
11 /**
12  * @typedef {{direction: string,
13  *            filler: (boolean|undefined),
14  *            title: string,
15  *            url: string}}
16  * @see chrome/browser/ui/webui/ntp/most_visited_handler.cc
17  */
18 var PageData;
20 // Use an anonymous function to enable strict mode just for this file (which
21 // will be concatenated with other files when embedded in Chrome
22 cr.define('ntp', function() {
23   'use strict';
25   /**
26    * NewTabView instance.
27    * @type {!Object|undefined}
28    */
29   var newTabView;
31   /**
32    * The 'notification-container' element.
33    * @type {!Element|undefined}
34    */
35   var notificationContainer;
37   /**
38    * If non-null, an info bubble for showing messages to the user. It points at
39    * the Most Visited label, and is used to draw more attention to the
40    * navigation dot UI.
41    * @type {!cr.ui.Bubble|undefined}
42    */
43   var promoBubble;
45   /**
46    * If non-null, an bubble confirming that the user has signed into sync. It
47    * points at the login status at the top of the page.
48    * @type {!cr.ui.Bubble|undefined}
49    */
50   var loginBubble;
52   /**
53    * true if |loginBubble| should be shown.
54    * @type {boolean}
55    */
56   var shouldShowLoginBubble = false;
58   /**
59    * The 'other-sessions-menu-button' element.
60    * @type {!ntp.OtherSessionsMenuButton|undefined}
61    */
62   var otherSessionsButton;
64   /**
65    * The time when all sections are ready.
66    * @type {number|undefined}
67    * @private
68    */
69   var startTime;
71   /**
72    * The time in milliseconds for most transitions.  This should match what's
73    * in new_tab.css.  Unfortunately there's no better way to try to time
74    * something to occur until after a transition has completed.
75    * @type {number}
76    * @const
77    */
78   var DEFAULT_TRANSITION_TIME = 500;
80   /**
81    * See description for these values in ntp_stats.h.
82    * @enum {number}
83    */
84   var NtpFollowAction = {
85     CLICKED_TILE: 11,
86     CLICKED_OTHER_NTP_PANE: 12,
87     OTHER: 13
88   };
90   /**
91    * Creates a NewTabView object. NewTabView extends PageListView with
92    * new tab UI specific logics.
93    * @constructor
94    * @extends {ntp.PageListView}
95    */
96   function NewTabView() {
97     var pageSwitcherStart;
98     var pageSwitcherEnd;
99     if (loadTimeData.getValue('showApps')) {
100       pageSwitcherStart = /** @type {!ntp.PageSwitcher} */(
101           getRequiredElement('page-switcher-start'));
102       pageSwitcherEnd = /** @type {!ntp.PageSwitcher} */(
103           getRequiredElement('page-switcher-end'));
104     }
105     this.initialize(getRequiredElement('page-list'),
106                     getRequiredElement('dot-list'),
107                     getRequiredElement('card-slider-frame'),
108                     getRequiredElement('trash'),
109                     pageSwitcherStart, pageSwitcherEnd);
110   }
112   NewTabView.prototype = {
113     __proto__: ntp.PageListView.prototype,
115     /** @override */
116     appendTilePage: function(page, title, titleIsEditable, opt_refNode) {
117       ntp.PageListView.prototype.appendTilePage.apply(this, arguments);
119       if (promoBubble)
120         window.setTimeout(promoBubble.reposition.bind(promoBubble), 0);
121     }
122   };
124   /**
125    * Invoked at startup once the DOM is available to initialize the app.
126    */
127   function onLoad() {
128     sectionsToWaitFor = 0;
129     if (loadTimeData.getBoolean('showMostvisited'))
130       sectionsToWaitFor++;
131     if (loadTimeData.getBoolean('showApps')) {
132       sectionsToWaitFor++;
133       if (loadTimeData.getBoolean('showAppLauncherPromo')) {
134         $('app-launcher-promo-close-button').addEventListener('click',
135             function() { chrome.send('stopShowingAppLauncherPromo'); });
136         $('apps-promo-learn-more').addEventListener('click',
137             function() { chrome.send('onLearnMore'); });
138       }
139     }
140     if (loadTimeData.getBoolean('isDiscoveryInNTPEnabled'))
141       sectionsToWaitFor++;
142     measureNavDots();
144     // Load the current theme colors.
145     themeChanged();
147     newTabView = new NewTabView();
149     notificationContainer = getRequiredElement('notification-container');
150     notificationContainer.addEventListener(
151         'webkitTransitionEnd', onNotificationTransitionEnd);
153     if (loadTimeData.getBoolean('showRecentlyClosed')) {
154       cr.ui.decorate(getRequiredElement('recently-closed-menu-button'),
155           ntp.RecentMenuButton);
156       chrome.send('getRecentlyClosedTabs');
157     } else {
158       $('recently-closed-menu-button').hidden = true;
159     }
161     if (loadTimeData.getBoolean('showOtherSessionsMenu')) {
162       otherSessionsButton = /** @type {!ntp.OtherSessionsMenuButton} */(
163           getRequiredElement('other-sessions-menu-button'));
164       cr.ui.decorate(otherSessionsButton, ntp.OtherSessionsMenuButton);
165       otherSessionsButton.initialize(loadTimeData.getBoolean('isUserSignedIn'));
166     } else {
167       getRequiredElement('other-sessions-menu-button').hidden = true;
168     }
170     if (loadTimeData.getBoolean('showMostvisited')) {
171       var mostVisited = new ntp.MostVisitedPage();
172       // Move the footer into the most visited page if we are in "bare minimum"
173       // mode.
174       if (document.body.classList.contains('bare-minimum'))
175         mostVisited.appendFooter(getRequiredElement('footer'));
176       newTabView.appendTilePage(mostVisited,
177                                 loadTimeData.getString('mostvisited'),
178                                 false);
179       chrome.send('getMostVisited');
180     }
182     if (loadTimeData.getBoolean('isDiscoveryInNTPEnabled')) {
183       var suggestionsScript = document.createElement('script');
184       suggestionsScript.src = 'suggestions_page.js';
185       suggestionsScript.onload = function() {
186          newTabView.appendTilePage(new ntp.SuggestionsPage(),
187                                    loadTimeData.getString('suggestions'),
188                                    false,
189                                    (newTabView.appsPages.length > 0) ?
190                                        newTabView.appsPages[0] : null);
191          chrome.send('getSuggestions');
192          cr.dispatchSimpleEvent(document, 'sectionready', true, true);
193       };
194       document.querySelector('head').appendChild(suggestionsScript);
195     }
197     if (!loadTimeData.getBoolean('showWebStoreIcon')) {
198       var webStoreIcon = $('chrome-web-store-link');
199       // Not all versions of the NTP have a footer, so this may not exist.
200       if (webStoreIcon)
201         webStoreIcon.hidden = true;
202     } else {
203       var webStoreLink = loadTimeData.getString('webStoreLink');
204       var url = appendParam(webStoreLink, 'utm_source', 'chrome-ntp-launcher');
205       $('chrome-web-store-link').href = url;
206       $('chrome-web-store-link').addEventListener('click',
207           onChromeWebStoreButtonClick);
208     }
210     // We need to wait for all the footer menu setup to be completed before
211     // we can compute its layout.
212     layoutFooter();
214     if (loadTimeData.getString('login_status_message')) {
215       loginBubble = new cr.ui.Bubble;
216       loginBubble.anchorNode = $('login-container');
217       loginBubble.arrowLocation = cr.ui.ArrowLocation.TOP_END;
218       loginBubble.bubbleAlignment =
219           cr.ui.BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE;
220       loginBubble.deactivateToDismissDelay = 2000;
221       loginBubble.closeButtonVisible = false;
223       $('login-status-advanced').onclick = function() {
224         chrome.send('showAdvancedLoginUI');
225       };
226       $('login-status-dismiss').onclick = loginBubble.hide.bind(loginBubble);
228       var bubbleContent = $('login-status-bubble-contents');
229       loginBubble.content = bubbleContent;
231       // The anchor node won't be updated until updateLogin is called so don't
232       // show the bubble yet.
233       shouldShowLoginBubble = true;
234     }
236     if (loadTimeData.valueExists('bubblePromoText')) {
237       promoBubble = new cr.ui.Bubble;
238       promoBubble.anchorNode = getRequiredElement('promo-bubble-anchor');
239       promoBubble.arrowLocation = cr.ui.ArrowLocation.BOTTOM_START;
240       promoBubble.bubbleAlignment = cr.ui.BubbleAlignment.ENTIRELY_VISIBLE;
241       promoBubble.deactivateToDismissDelay = 2000;
242       promoBubble.content = parseHtmlSubset(
243           loadTimeData.getString('bubblePromoText'), ['BR']);
245       var bubbleLink = promoBubble.querySelector('a');
246       if (bubbleLink) {
247         bubbleLink.addEventListener('click', function(e) {
248           chrome.send('bubblePromoLinkClicked');
249         });
250       }
252       promoBubble.handleCloseEvent = function() {
253         promoBubble.hide();
254         chrome.send('bubblePromoClosed');
255       };
256       promoBubble.show();
257       chrome.send('bubblePromoViewed');
258     }
260     $('login-container').addEventListener('click', showSyncLoginUI);
261     if (loadTimeData.getBoolean('shouldShowSyncLogin'))
262       chrome.send('initializeSyncLogin');
264     doWhenAllSectionsReady(function() {
265       // Tell the slider about the pages.
266       newTabView.updateSliderCards();
267       // Mark the current page.
268       newTabView.cardSlider.currentCardValue.navigationDot.classList.add(
269           'selected');
271       if (loadTimeData.valueExists('notificationPromoText')) {
272         var promoText = loadTimeData.getString('notificationPromoText');
273         var tags = ['IMG'];
274         var attrs = {
275           src: function(node, value) {
276             return node.tagName == 'IMG' &&
277                    /^data\:image\/(?:png|gif|jpe?g)/.test(value);
278           },
279         };
281         var promo = parseHtmlSubset(promoText, tags, attrs);
282         var promoLink = promo.querySelector('a');
283         if (promoLink) {
284           promoLink.addEventListener('click', function(e) {
285             chrome.send('notificationPromoLinkClicked');
286           });
287         }
289         showNotification(promo, [], function() {
290           chrome.send('notificationPromoClosed');
291         }, 60000);
292         chrome.send('notificationPromoViewed');
293       }
295       cr.dispatchSimpleEvent(document, 'ntpLoaded', true, true);
296       document.documentElement.classList.remove('starting-up');
298       startTime = Date.now();
299     });
300   }
302   /**
303    * Launches the chrome web store app with the chrome-ntp-launcher
304    * source.
305    * @param {Event} e The click event.
306    */
307   function onChromeWebStoreButtonClick(e) {
308     chrome.send('recordAppLaunchByURL',
309                 [encodeURIComponent(this.href),
310                  ntp.APP_LAUNCH.NTP_WEBSTORE_FOOTER]);
311   }
313   /**
314    * The number of sections to wait on.
315    * @type {number}
316    */
317   var sectionsToWaitFor = -1;
319   /**
320    * Queued callbacks which lie in wait for all sections to be ready.
321    * @type {Array}
322    */
323   var readyCallbacks = [];
325   /**
326    * Fired as each section of pages becomes ready.
327    * @param {Event} e Each page's synthetic DOM event.
328    */
329   document.addEventListener('sectionready', function(e) {
330     if (--sectionsToWaitFor <= 0) {
331       while (readyCallbacks.length) {
332         readyCallbacks.shift()();
333       }
334     }
335   });
337   /**
338    * This is used to simulate a fire-once event (i.e. $(document).ready() in
339    * jQuery or Y.on('domready') in YUI. If all sections are ready, the callback
340    * is fired right away. If all pages are not ready yet, the function is queued
341    * for later execution.
342    * @param {Function} callback The work to be done when ready.
343    */
344   function doWhenAllSectionsReady(callback) {
345     assert(typeof callback == 'function');
346     if (sectionsToWaitFor > 0)
347       readyCallbacks.push(callback);
348     else
349       window.setTimeout(callback, 0);  // Do soon after, but asynchronously.
350   }
352   /**
353    * Measure the width of a nav dot with a given title.
354    * @param {string} id The loadTimeData ID of the desired title.
355    * @return {number} The width of the nav dot.
356    */
357   function measureNavDot(id) {
358     var measuringDiv = $('fontMeasuringDiv');
359     measuringDiv.textContent = loadTimeData.getString(id);
360     // The 4 is for border and padding.
361     return Math.max(measuringDiv.clientWidth * 1.15 + 4, 80);
362   }
364   /**
365    * Fills in an invisible div with the longest dot title string so that
366    * its length may be measured and the nav dots sized accordingly.
367    */
368   function measureNavDots() {
369     var pxWidth = measureNavDot('appDefaultPageName');
370     if (loadTimeData.getBoolean('showMostvisited'))
371       pxWidth = Math.max(measureNavDot('mostvisited'), pxWidth);
373     var styleElement = document.createElement('style');
374     styleElement.type = 'text/css';
375     // max-width is used because if we run out of space, the nav dots will be
376     // shrunk.
377     styleElement.textContent = '.dot { max-width: ' + pxWidth + 'px; }';
378     document.querySelector('head').appendChild(styleElement);
379   }
381   /**
382    * Layout the footer so that the nav dots stay centered.
383    */
384   function layoutFooter() {
385     // We need the image to be loaded.
386     var logo = $('logo-img');
387     var logoImg = logo.querySelector('img');
388     if (!logoImg.complete) {
389       logoImg.onload = layoutFooter;
390       return;
391     }
393     var menu = $('footer-menu-container');
394     if (menu.clientWidth > logoImg.width)
395       logo.style.WebkitFlex = '0 1 ' + menu.clientWidth + 'px';
396     else
397       menu.style.WebkitFlex = '0 1 ' + logoImg.width + 'px';
398   }
400   /**
401    * @param {boolean=} opt_hasAttribution
402    */
403   function themeChanged(opt_hasAttribution) {
404     $('themecss').href = 'chrome://theme/css/new_tab_theme.css?' + Date.now();
406     if (typeof opt_hasAttribution != 'undefined') {
407       document.documentElement.setAttribute('hasattribution',
408                                             opt_hasAttribution);
409     }
411     updateAttribution();
412   }
414   function setBookmarkBarAttached(attached) {
415     document.documentElement.setAttribute('bookmarkbarattached', attached);
416   }
418   /**
419    * Attributes the attribution image at the bottom left.
420    */
421   function updateAttribution() {
422     var attribution = $('attribution');
423     if (document.documentElement.getAttribute('hasattribution') == 'true') {
424       attribution.hidden = false;
425     } else {
426       attribution.hidden = true;
427     }
428   }
430   /**
431    * Timeout ID.
432    * @type {number}
433    */
434   var notificationTimeout = 0;
436   /**
437    * Shows the notification bubble.
438    * @param {string|Node} message The notification message or node to use as
439    *     message.
440    * @param {Array<{text: string, action: function()}>} links An array of
441    *     records describing the links in the notification. Each record should
442    *     have a 'text' attribute (the display string) and an 'action' attribute
443    *     (a function to run when the link is activated).
444    * @param {Function=} opt_closeHandler The callback invoked if the user
445    *     manually dismisses the notification.
446    * @param {number=} opt_timeout
447    */
448   function showNotification(message, links, opt_closeHandler, opt_timeout) {
449     window.clearTimeout(notificationTimeout);
451     var span = document.querySelector('#notification > span');
452     if (typeof message == 'string') {
453       span.textContent = message;
454     } else {
455       span.textContent = '';  // Remove all children.
456       span.appendChild(message);
457     }
459     var linksBin = $('notificationLinks');
460     linksBin.textContent = '';
461     for (var i = 0; i < links.length; i++) {
462       var link = new ActionLink;
463       link.textContent = links[i].text;
464       link.action = links[i].action;
465       link.onclick = function() {
466         this.action();
467         hideNotification();
468       };
469       linksBin.appendChild(link);
470     }
472     function closeFunc(e) {
473       if (opt_closeHandler)
474         opt_closeHandler();
475       hideNotification();
476     }
478     document.querySelector('#notification button').onclick = closeFunc;
479     document.addEventListener('dragstart', closeFunc);
481     notificationContainer.hidden = false;
482     showNotificationOnCurrentPage();
484     newTabView.cardSlider.frame.addEventListener(
485         'cardSlider:card_change_ended', onCardChangeEnded);
487     var timeout = opt_timeout || 10000;
488     notificationTimeout = window.setTimeout(hideNotification, timeout);
489   }
491   /**
492    * Hide the notification bubble.
493    */
494   function hideNotification() {
495     notificationContainer.classList.add('inactive');
497     newTabView.cardSlider.frame.removeEventListener(
498         'cardSlider:card_change_ended', onCardChangeEnded);
499   }
501   /**
502    * Happens when 1 or more consecutive card changes end.
503    * @param {Event} e The cardSlider:card_change_ended event.
504    */
505   function onCardChangeEnded(e) {
506     // If we ended on the same page as we started, ignore.
507     if (newTabView.cardSlider.currentCardValue.notification)
508       return;
510     // Hide the notification the old page.
511     notificationContainer.classList.add('card-changed');
513     showNotificationOnCurrentPage();
514   }
516   /**
517    * Move and show the notification on the current page.
518    */
519   function showNotificationOnCurrentPage() {
520     var page = newTabView.cardSlider.currentCardValue;
521     doWhenAllSectionsReady(function() {
522       if (page != newTabView.cardSlider.currentCardValue)
523         return;
525       // NOTE: This moves the notification to inside of the current page.
526       page.notification = notificationContainer;
528       // Reveal the notification and instruct it to hide itself if ignored.
529       notificationContainer.classList.remove('inactive');
531       // Gives the browser time to apply this rule before we remove it (causing
532       // a transition).
533       window.setTimeout(function() {
534         notificationContainer.classList.remove('card-changed');
535       }, 0);
536     });
537   }
539   /**
540    * When done fading out, set hidden to true so the notification can't be
541    * tabbed to or clicked.
542    * @param {Event} e The webkitTransitionEnd event.
543    */
544   function onNotificationTransitionEnd(e) {
545     if (notificationContainer.classList.contains('inactive'))
546       notificationContainer.hidden = true;
547   }
549   function setRecentlyClosedTabs(dataItems) {
550     $('recently-closed-menu-button').dataItems = dataItems;
551     layoutFooter();
552   }
554   /**
555    * @param {Array<PageData>} data
556    * @param {boolean} hasBlacklistedUrls
557    */
558   function setMostVisitedPages(data, hasBlacklistedUrls) {
559     newTabView.mostVisitedPage.data = data;
560     cr.dispatchSimpleEvent(document, 'sectionready', true, true);
561   }
563   function setSuggestionsPages(data, hasBlacklistedUrls) {
564     newTabView.suggestionsPage.data = data;
565   }
567   /**
568    * Set the dominant color for a node. This will be called in response to
569    * getFaviconDominantColor. The node represented by |id| better have a setter
570    * for stripeColor.
571    * @param {string} id The ID of a node.
572    * @param {string} color The color represented as a CSS string.
573    */
574   function setFaviconDominantColor(id, color) {
575     var node = $(id);
576     if (node)
577       node.stripeColor = color;
578   }
580   /**
581    * Updates the text displayed in the login container. If there is no text then
582    * the login container is hidden.
583    * @param {string} loginHeader The first line of text.
584    * @param {string} loginSubHeader The second line of text.
585    * @param {string} iconURL The url for the login status icon. If this is null
586         then the login status icon is hidden.
587    * @param {boolean} isUserSignedIn Indicates if the user is signed in or not.
588    */
589   function updateLogin(loginHeader, loginSubHeader, iconURL, isUserSignedIn) {
590     /** @const */ var showLogin = loginHeader || loginSubHeader;
592     $('login-container').hidden = !showLogin;
593     $('card-slider-frame').classList.toggle('showing-login-area', !!showLogin);
595     if (showLogin) {
596       // TODO(dbeam): we should use .textContent instead to mitigate XSS.
597       $('login-status-header').innerHTML = loginHeader;
598       $('login-status-sub-header').innerHTML = loginSubHeader;
600       var headerContainer = $('login-status-header-container');
601       headerContainer.classList.toggle('login-status-icon', !!iconURL);
602       headerContainer.style.backgroundImage = iconURL ? url(iconURL) : 'none';
603     }
605     if (shouldShowLoginBubble) {
606       window.setTimeout(loginBubble.show.bind(loginBubble), 0);
607       chrome.send('loginMessageSeen');
608       shouldShowLoginBubble = false;
609     } else if (loginBubble) {
610       loginBubble.reposition();
611     }
612     if (otherSessionsButton) {
613       otherSessionsButton.updateSignInState(isUserSignedIn);
614       layoutFooter();
615     }
616   }
618   /**
619    * Show the sync login UI.
620    * @param {Event} e The click event.
621    */
622   function showSyncLoginUI(e) {
623     var rect = e.currentTarget.getBoundingClientRect();
624     chrome.send('showSyncLoginUI',
625                 [rect.left, rect.top, rect.width, rect.height]);
626   }
628   /**
629    * Logs the time to click for the specified item.
630    * @param {string} item The item to log the time-to-click.
631    */
632   function logTimeToClick(item) {
633     var timeToClick = Date.now() - startTime;
634     chrome.send('logTimeToClick',
635         ['NewTabPage.TimeToClick' + item, timeToClick]);
636   }
638   /**
639    * Wrappers to forward the callback to corresponding PageListView member.
640    */
642   /**
643    * Called by chrome when a new app has been added to chrome or has been
644    * enabled if previously disabled.
645    * @param {Object} appData A data structure full of relevant information for
646    *     the app.
647    * @param {boolean=} opt_highlight Whether the app about to be added should
648    *     be highlighted.
649    */
650   function appAdded(appData, opt_highlight) {
651     newTabView.appAdded(appData, opt_highlight);
652   }
654   /**
655    * Called by chrome when an app has changed positions.
656    * @param {Object} appData The data for the app. This contains page and
657    *     position indices.
658    */
659   function appMoved(appData) {
660     newTabView.appMoved(appData);
661   }
663   /**
664    * Called by chrome when an existing app has been disabled or
665    * removed/uninstalled from chrome.
666    * @param {Object} appData A data structure full of relevant information for
667    *     the app.
668    * @param {boolean} isUninstall True if the app is being uninstalled;
669    *     false if the app is being disabled.
670    * @param {boolean} fromPage True if the removal was from the current page.
671    */
672   function appRemoved(appData, isUninstall, fromPage) {
673     newTabView.appRemoved(appData, isUninstall, fromPage);
674   }
676   /**
677    * Callback invoked by chrome whenever an app preference changes.
678    * @param {Object} data An object with all the data on available
679    *     applications.
680    */
681   function appsPrefChangeCallback(data) {
682     newTabView.appsPrefChangedCallback(data);
683   }
685   /**
686    * Callback invoked by chrome whenever the app launcher promo pref changes.
687    * @param {boolean} show Identifies if we should show or hide the promo.
688    */
689   function appLauncherPromoPrefChangeCallback(show) {
690     newTabView.appLauncherPromoPrefChangeCallback(show);
691   }
693   /**
694    * Called whenever tiles should be re-arranging themselves out of the way
695    * of a moving or insert tile.
696    */
697   function enterRearrangeMode() {
698     newTabView.enterRearrangeMode();
699   }
701   function setForeignSessions(sessionList, isTabSyncEnabled) {
702     if (otherSessionsButton) {
703       otherSessionsButton.setForeignSessions(sessionList, isTabSyncEnabled);
704       layoutFooter();
705     }
706   }
708   /**
709    * Callback invoked by chrome with the apps available.
710    *
711    * Note that calls to this function can occur at any time, not just in
712    * response to a getApps request. For example, when a user
713    * installs/uninstalls an app on another synchronized devices.
714    * @param {Object} data An object with all the data on available
715    *        applications.
716    */
717   function getAppsCallback(data) {
718     newTabView.getAppsCallback(data);
719   }
721   /**
722    * Return the index of the given apps page.
723    * @param {ntp.AppsPage} page The AppsPage we wish to find.
724    * @return {number} The index of |page| or -1 if it is not in the collection.
725    */
726   function getAppsPageIndex(page) {
727     return newTabView.getAppsPageIndex(page);
728   }
730   function getCardSlider() {
731     return newTabView.cardSlider;
732   }
734   /**
735    * Invoked whenever some app is released
736    */
737   function leaveRearrangeMode() {
738     newTabView.leaveRearrangeMode();
739   }
741   /**
742    * Save the name of an apps page.
743    * Store the apps page name into the preferences store.
744    * @param {ntp.AppsPage} appPage The app page for which we wish to save.
745    * @param {string} name The name of the page.
746    */
747   function saveAppPageName(appPage, name) {
748     newTabView.saveAppPageName(appPage, name);
749   }
751   function setAppToBeHighlighted(appId) {
752     newTabView.highlightAppId = appId;
753   }
755   // Return an object with all the exports
756   return {
757     appAdded: appAdded,
758     appMoved: appMoved,
759     appRemoved: appRemoved,
760     appsPrefChangeCallback: appsPrefChangeCallback,
761     appLauncherPromoPrefChangeCallback: appLauncherPromoPrefChangeCallback,
762     enterRearrangeMode: enterRearrangeMode,
763     getAppsCallback: getAppsCallback,
764     getAppsPageIndex: getAppsPageIndex,
765     getCardSlider: getCardSlider,
766     onLoad: onLoad,
767     leaveRearrangeMode: leaveRearrangeMode,
768     logTimeToClick: logTimeToClick,
769     NtpFollowAction: NtpFollowAction,
770     saveAppPageName: saveAppPageName,
771     setAppToBeHighlighted: setAppToBeHighlighted,
772     setBookmarkBarAttached: setBookmarkBarAttached,
773     setForeignSessions: setForeignSessions,
774     setMostVisitedPages: setMostVisitedPages,
775     setSuggestionsPages: setSuggestionsPages,
776     setRecentlyClosedTabs: setRecentlyClosedTabs,
777     setFaviconDominantColor: setFaviconDominantColor,
778     showNotification: showNotification,
779     themeChanged: themeChanged,
780     updateLogin: updateLogin
781   };
784 document.addEventListener('DOMContentLoaded', ntp.onLoad);
786 var toCssPx = cr.ui.toCssPx;