Home | History | Annotate | Download | only in ntp4
      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 promoBubble;
     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 when all sections are ready.
     57    * @type {number|undefined}
     58    * @private
     59    */
     60   var startTime;
     61 
     62   /**
     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.
     66    * @type {number}
     67    * @const
     68    */
     69   var DEFAULT_TRANSITION_TIME = 500;
     70 
     71   /**
     72    * See description for these values in ntp_stats.h.
     73    * @enum {number}
     74    */
     75   var NtpFollowAction = {
     76     CLICKED_TILE: 11,
     77     CLICKED_OTHER_NTP_PANE: 12,
     78     OTHER: 13
     79   };
     80 
     81   /**
     82    * Creates a NewTabView object. NewTabView extends PageListView with
     83    * new tab UI specific logics.
     84    * @constructor
     85    * @extends {PageListView}
     86    */
     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');
     93     }
     94     this.initialize(getRequiredElement('page-list'),
     95                     getRequiredElement('dot-list'),
     96                     getRequiredElement('card-slider-frame'),
     97                     getRequiredElement('trash'),
     98                     pageSwitcherStart, pageSwitcherEnd);
     99   }
    100 
    101   NewTabView.prototype = {
    102     __proto__: ntp.PageListView.prototype,
    103 
    104     /** @override */
    105     appendTilePage: function(page, title, titleIsEditable, opt_refNode) {
    106       ntp.PageListView.prototype.appendTilePage.apply(this, arguments);
    107 
    108       if (promoBubble)
    109         window.setTimeout(promoBubble.reposition.bind(promoBubble), 0);
    110     }
    111   };
    112 
    113   /**
    114    * Invoked at startup once the DOM is available to initialize the app.
    115    */
    116   function onLoad() {
    117     sectionsToWaitFor = 0;
    118     if (loadTimeData.getBoolean('showMostvisited'))
    119       sectionsToWaitFor++;
    120     if (loadTimeData.getBoolean('showApps')) {
    121       sectionsToWaitFor++;
    122       if (loadTimeData.getBoolean('showAppLauncherPromo')) {
    123         $('app-launcher-promo-close-button').addEventListener('click',
    124             function() { chrome.send('stopShowingAppLauncherPromo'); });
    125         $('apps-promo-learn-more').addEventListener('click',
    126             function() { chrome.send('onLearnMore'); });
    127       }
    128     }
    129     if (loadTimeData.getBoolean('isDiscoveryInNTPEnabled'))
    130       sectionsToWaitFor++;
    131     measureNavDots();
    132 
    133     // Load the current theme colors.
    134     themeChanged();
    135 
    136     newTabView = new NewTabView();
    137 
    138     notificationContainer = getRequiredElement('notification-container');
    139     notificationContainer.addEventListener(
    140         'webkitTransitionEnd', onNotificationTransitionEnd);
    141 
    142     if (loadTimeData.getBoolean('showRecentlyClosed')) {
    143       cr.ui.decorate($('recently-closed-menu-button'), ntp.RecentMenuButton);
    144       chrome.send('getRecentlyClosedTabs');
    145     } else {
    146       $('recently-closed-menu-button').hidden = true;
    147     }
    148 
    149     if (loadTimeData.getBoolean('showOtherSessionsMenu')) {
    150       otherSessionsButton = getRequiredElement('other-sessions-menu-button');
    151       cr.ui.decorate(otherSessionsButton, ntp.OtherSessionsMenuButton);
    152       otherSessionsButton.initialize(loadTimeData.getBoolean('isUserSignedIn'));
    153     } else {
    154       getRequiredElement('other-sessions-menu-button').hidden = true;
    155     }
    156 
    157     if (loadTimeData.getBoolean('showMostvisited')) {
    158       var mostVisited = new ntp.MostVisitedPage();
    159       // Move the footer into the most visited page if we are in "bare minimum"
    160       // mode.
    161       if (document.body.classList.contains('bare-minimum'))
    162         mostVisited.appendFooter(getRequiredElement('footer'));
    163       newTabView.appendTilePage(mostVisited,
    164                                 loadTimeData.getString('mostvisited'),
    165                                 false);
    166       chrome.send('getMostVisited');
    167     }
    168 
    169     if (loadTimeData.getBoolean('isDiscoveryInNTPEnabled')) {
    170       var suggestionsScript = document.createElement('script');
    171       suggestionsScript.src = 'suggestions_page.js';
    172       suggestionsScript.onload = function() {
    173          newTabView.appendTilePage(new ntp.SuggestionsPage(),
    174                                    loadTimeData.getString('suggestions'),
    175                                    false,
    176                                    (newTabView.appsPages.length > 0) ?
    177                                        newTabView.appsPages[0] : null);
    178          chrome.send('getSuggestions');
    179          cr.dispatchSimpleEvent(document, 'sectionready', true, true);
    180       };
    181       document.querySelector('head').appendChild(suggestionsScript);
    182     }
    183 
    184     if (!loadTimeData.getBoolean('showWebStoreIcon')) {
    185       var webStoreIcon = $('chrome-web-store-link');
    186       // Not all versions of the NTP have a footer, so this may not exist.
    187       if (webStoreIcon)
    188         webStoreIcon.hidden = true;
    189     } else {
    190       var webStoreLink = loadTimeData.getString('webStoreLink');
    191       var url = appendParam(webStoreLink, 'utm_source', 'chrome-ntp-launcher');
    192       $('chrome-web-store-link').href = url;
    193       $('chrome-web-store-link').addEventListener('click',
    194           onChromeWebStoreButtonClick);
    195     }
    196 
    197     // We need to wait for all the footer menu setup to be completed before
    198     // we can compute its layout.
    199     layoutFooter();
    200 
    201     if (loadTimeData.getString('login_status_message')) {
    202       loginBubble = new cr.ui.Bubble;
    203       loginBubble.anchorNode = $('login-container');
    204       loginBubble.arrowLocation = cr.ui.ArrowLocation.TOP_END;
    205       loginBubble.bubbleAlignment =
    206           cr.ui.BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE;
    207       loginBubble.deactivateToDismissDelay = 2000;
    208       loginBubble.closeButtonVisible = false;
    209 
    210       $('login-status-advanced').onclick = function() {
    211         chrome.send('showAdvancedLoginUI');
    212       };
    213       $('login-status-dismiss').onclick = loginBubble.hide.bind(loginBubble);
    214 
    215       var bubbleContent = $('login-status-bubble-contents');
    216       loginBubble.content = bubbleContent;
    217 
    218       // The anchor node won't be updated until updateLogin is called so don't
    219       // show the bubble yet.
    220       shouldShowLoginBubble = true;
    221     }
    222 
    223     if (loadTimeData.valueExists('bubblePromoText')) {
    224       promoBubble = new cr.ui.Bubble;
    225       promoBubble.anchorNode = getRequiredElement('promo-bubble-anchor');
    226       promoBubble.arrowLocation = cr.ui.ArrowLocation.BOTTOM_START;
    227       promoBubble.bubbleAlignment = cr.ui.BubbleAlignment.ENTIRELY_VISIBLE;
    228       promoBubble.deactivateToDismissDelay = 2000;
    229       promoBubble.content = parseHtmlSubset(
    230           loadTimeData.getString('bubblePromoText'), ['BR']);
    231 
    232       var bubbleLink = promoBubble.querySelector('a');
    233       if (bubbleLink) {
    234         bubbleLink.addEventListener('click', function(e) {
    235           chrome.send('bubblePromoLinkClicked');
    236         });
    237       }
    238 
    239       promoBubble.handleCloseEvent = function() {
    240         promoBubble.hide();
    241         chrome.send('bubblePromoClosed');
    242       };
    243       promoBubble.show();
    244       chrome.send('bubblePromoViewed');
    245     }
    246 
    247     var loginContainer = getRequiredElement('login-container');
    248     loginContainer.addEventListener('click', showSyncLoginUI);
    249     if (loadTimeData.getBoolean('shouldShowSyncLogin'))
    250       chrome.send('initializeSyncLogin');
    251 
    252     doWhenAllSectionsReady(function() {
    253       // Tell the slider about the pages.
    254       newTabView.updateSliderCards();
    255       // Mark the current page.
    256       newTabView.cardSlider.currentCardValue.navigationDot.classList.add(
    257           'selected');
    258 
    259       if (loadTimeData.valueExists('notificationPromoText')) {
    260         var promoText = loadTimeData.getString('notificationPromoText');
    261         var tags = ['IMG'];
    262         var attrs = {
    263           src: function(node, value) {
    264             return node.tagName == 'IMG' &&
    265                    /^data\:image\/(?:png|gif|jpe?g)/.test(value);
    266           },
    267         };
    268 
    269         var promo = parseHtmlSubset(promoText, tags, attrs);
    270         var promoLink = promo.querySelector('a');
    271         if (promoLink) {
    272           promoLink.addEventListener('click', function(e) {
    273             chrome.send('notificationPromoLinkClicked');
    274           });
    275         }
    276 
    277         showNotification(promo, [], function() {
    278           chrome.send('notificationPromoClosed');
    279         }, 60000);
    280         chrome.send('notificationPromoViewed');
    281       }
    282 
    283       cr.dispatchSimpleEvent(document, 'ntpLoaded', true, true);
    284       document.documentElement.classList.remove('starting-up');
    285 
    286       startTime = Date.now();
    287     });
    288 
    289     preventDefaultOnPoundLinkClicks();  // From webui/js/util.js.
    290     cr.ui.FocusManager.disableMouseFocusOnButtons();
    291   }
    292 
    293   /**
    294    * Launches the chrome web store app with the chrome-ntp-launcher
    295    * source.
    296    * @param {Event} e The click event.
    297    */
    298   function onChromeWebStoreButtonClick(e) {
    299     chrome.send('recordAppLaunchByURL',
    300                 [encodeURIComponent(this.href),
    301                  ntp.APP_LAUNCH.NTP_WEBSTORE_FOOTER]);
    302   }
    303 
    304   /*
    305    * The number of sections to wait on.
    306    * @type {number}
    307    */
    308   var sectionsToWaitFor = -1;
    309 
    310   /**
    311    * Queued callbacks which lie in wait for all sections to be ready.
    312    * @type {array}
    313    */
    314   var readyCallbacks = [];
    315 
    316   /**
    317    * Fired as each section of pages becomes ready.
    318    * @param {Event} e Each page's synthetic DOM event.
    319    */
    320   document.addEventListener('sectionready', function(e) {
    321     if (--sectionsToWaitFor <= 0) {
    322       while (readyCallbacks.length) {
    323         readyCallbacks.shift()();
    324       }
    325     }
    326   });
    327 
    328   /**
    329    * This is used to simulate a fire-once event (i.e. $(document).ready() in
    330    * jQuery or Y.on('domready') in YUI. If all sections are ready, the callback
    331    * is fired right away. If all pages are not ready yet, the function is queued
    332    * for later execution.
    333    * @param {function} callback The work to be done when ready.
    334    */
    335   function doWhenAllSectionsReady(callback) {
    336     assert(typeof callback == 'function');
    337     if (sectionsToWaitFor > 0)
    338       readyCallbacks.push(callback);
    339     else
    340       window.setTimeout(callback, 0);  // Do soon after, but asynchronously.
    341   }
    342 
    343   /**
    344    * Fills in an invisible div with the 'Most Visited' string so that
    345    * its length may be measured and the nav dots sized accordingly.
    346    */
    347   function measureNavDots() {
    348     var measuringDiv = $('fontMeasuringDiv');
    349     if (loadTimeData.getBoolean('showMostvisited'))
    350       measuringDiv.textContent = loadTimeData.getString('mostvisited');
    351 
    352     // The 4 is for border and padding.
    353     var pxWidth = Math.max(measuringDiv.clientWidth * 1.15 + 4, 80);
    354 
    355     var styleElement = document.createElement('style');
    356     styleElement.type = 'text/css';
    357     // max-width is used because if we run out of space, the nav dots will be
    358     // shrunk.
    359     styleElement.textContent = '.dot { max-width: ' + pxWidth + 'px; }';
    360     document.querySelector('head').appendChild(styleElement);
    361   }
    362 
    363   /**
    364    * Layout the footer so that the nav dots stay centered.
    365    */
    366   function layoutFooter() {
    367     var menu = $('footer-menu-container');
    368     var logo = $('logo-img');
    369     if (menu.clientWidth > logo.clientWidth)
    370       logo.style.WebkitFlex = '0 1 ' + menu.clientWidth + 'px';
    371     else
    372       menu.style.WebkitFlex = '0 1 ' + logo.clientWidth + 'px';
    373   }
    374 
    375   function themeChanged(opt_hasAttribution) {
    376     $('themecss').href = 'chrome://theme/css/new_tab_theme.css?' + Date.now();
    377 
    378     if (typeof opt_hasAttribution != 'undefined') {
    379       document.documentElement.setAttribute('hasattribution',
    380                                             opt_hasAttribution);
    381     }
    382 
    383     updateAttribution();
    384   }
    385 
    386   function setBookmarkBarAttached(attached) {
    387     document.documentElement.setAttribute('bookmarkbarattached', attached);
    388   }
    389 
    390   /**
    391    * Attributes the attribution image at the bottom left.
    392    */
    393   function updateAttribution() {
    394     var attribution = $('attribution');
    395     if (document.documentElement.getAttribute('hasattribution') == 'true') {
    396       attribution.hidden = false;
    397     } else {
    398       attribution.hidden = true;
    399     }
    400   }
    401 
    402   /**
    403    * Timeout ID.
    404    * @type {number}
    405    */
    406   var notificationTimeout = 0;
    407 
    408   /**
    409    * Shows the notification bubble.
    410    * @param {string|Node} message The notification message or node to use as
    411    *     message.
    412    * @param {Array.<{text: string, action: function()}>} links An array of
    413    *     records describing the links in the notification. Each record should
    414    *     have a 'text' attribute (the display string) and an 'action' attribute
    415    *     (a function to run when the link is activated).
    416    * @param {Function} opt_closeHandler The callback invoked if the user
    417    *     manually dismisses the notification.
    418    */
    419   function showNotification(message, links, opt_closeHandler, opt_timeout) {
    420     window.clearTimeout(notificationTimeout);
    421 
    422     var span = document.querySelector('#notification > span');
    423     if (typeof message == 'string') {
    424       span.textContent = message;
    425     } else {
    426       span.textContent = '';  // Remove all children.
    427       span.appendChild(message);
    428     }
    429 
    430     var linksBin = $('notificationLinks');
    431     linksBin.textContent = '';
    432     for (var i = 0; i < links.length; i++) {
    433       var link = linksBin.ownerDocument.createElement('div');
    434       link.textContent = links[i].text;
    435       link.action = links[i].action;
    436       link.onclick = function() {
    437         this.action();
    438         hideNotification();
    439       };
    440       link.setAttribute('role', 'button');
    441       link.setAttribute('tabindex', 0);
    442       link.className = 'link-button';
    443       linksBin.appendChild(link);
    444     }
    445 
    446     function closeFunc(e) {
    447       if (opt_closeHandler)
    448         opt_closeHandler();
    449       hideNotification();
    450     }
    451 
    452     document.querySelector('#notification button').onclick = closeFunc;
    453     document.addEventListener('dragstart', closeFunc);
    454 
    455     notificationContainer.hidden = false;
    456     showNotificationOnCurrentPage();
    457 
    458     newTabView.cardSlider.frame.addEventListener(
    459         'cardSlider:card_change_ended', onCardChangeEnded);
    460 
    461     var timeout = opt_timeout || 10000;
    462     notificationTimeout = window.setTimeout(hideNotification, timeout);
    463   }
    464 
    465   /**
    466    * Hide the notification bubble.
    467    */
    468   function hideNotification() {
    469     notificationContainer.classList.add('inactive');
    470 
    471     newTabView.cardSlider.frame.removeEventListener(
    472         'cardSlider:card_change_ended', onCardChangeEnded);
    473   }
    474 
    475   /**
    476    * Happens when 1 or more consecutive card changes end.
    477    * @param {Event} e The cardSlider:card_change_ended event.
    478    */
    479   function onCardChangeEnded(e) {
    480     // If we ended on the same page as we started, ignore.
    481     if (newTabView.cardSlider.currentCardValue.notification)
    482       return;
    483 
    484     // Hide the notification the old page.
    485     notificationContainer.classList.add('card-changed');
    486 
    487     showNotificationOnCurrentPage();
    488   }
    489 
    490   /**
    491    * Move and show the notification on the current page.
    492    */
    493   function showNotificationOnCurrentPage() {
    494     var page = newTabView.cardSlider.currentCardValue;
    495     doWhenAllSectionsReady(function() {
    496       if (page != newTabView.cardSlider.currentCardValue)
    497         return;
    498 
    499       // NOTE: This moves the notification to inside of the current page.
    500       page.notification = notificationContainer;
    501 
    502       // Reveal the notification and instruct it to hide itself if ignored.
    503       notificationContainer.classList.remove('inactive');
    504 
    505       // Gives the browser time to apply this rule before we remove it (causing
    506       // a transition).
    507       window.setTimeout(function() {
    508         notificationContainer.classList.remove('card-changed');
    509       }, 0);
    510     });
    511   }
    512 
    513   /**
    514    * When done fading out, set hidden to true so the notification can't be
    515    * tabbed to or clicked.
    516    * @param {Event} e The webkitTransitionEnd event.
    517    */
    518   function onNotificationTransitionEnd(e) {
    519     if (notificationContainer.classList.contains('inactive'))
    520       notificationContainer.hidden = true;
    521   }
    522 
    523   function setRecentlyClosedTabs(dataItems) {
    524     $('recently-closed-menu-button').dataItems = dataItems;
    525     layoutFooter();
    526   }
    527 
    528   function setMostVisitedPages(data, hasBlacklistedUrls) {
    529     newTabView.mostVisitedPage.data = data;
    530     cr.dispatchSimpleEvent(document, 'sectionready', true, true);
    531   }
    532 
    533   function setSuggestionsPages(data, hasBlacklistedUrls) {
    534     newTabView.suggestionsPage.data = data;
    535   }
    536 
    537   /**
    538    * Set the dominant color for a node. This will be called in response to
    539    * getFaviconDominantColor. The node represented by |id| better have a setter
    540    * for stripeColor.
    541    * @param {string} id The ID of a node.
    542    * @param {string} color The color represented as a CSS string.
    543    */
    544   function setFaviconDominantColor(id, color) {
    545     var node = $(id);
    546     if (node)
    547       node.stripeColor = color;
    548   }
    549 
    550   /**
    551    * Updates the text displayed in the login container. If there is no text then
    552    * the login container is hidden.
    553    * @param {string} loginHeader The first line of text.
    554    * @param {string} loginSubHeader The second line of text.
    555    * @param {string} iconURL The url for the login status icon. If this is null
    556         then the login status icon is hidden.
    557    * @param {boolean} isUserSignedIn Indicates if the user is signed in or not.
    558    */
    559   function updateLogin(loginHeader, loginSubHeader, iconURL, isUserSignedIn) {
    560     if (loginHeader || loginSubHeader) {
    561       $('login-container').hidden = false;
    562       $('login-status-header').innerHTML = loginHeader;
    563       $('login-status-sub-header').innerHTML = loginSubHeader;
    564       $('card-slider-frame').classList.add('showing-login-area');
    565 
    566       if (iconURL) {
    567         $('login-status-header-container').style.backgroundImage = url(iconURL);
    568         $('login-status-header-container').classList.add('login-status-icon');
    569       } else {
    570         $('login-status-header-container').style.backgroundImage = 'none';
    571         $('login-status-header-container').classList.remove(
    572             'login-status-icon');
    573       }
    574     } else {
    575       $('login-container').hidden = true;
    576       $('card-slider-frame').classList.remove('showing-login-area');
    577     }
    578     if (shouldShowLoginBubble) {
    579       window.setTimeout(loginBubble.show.bind(loginBubble), 0);
    580       chrome.send('loginMessageSeen');
    581       shouldShowLoginBubble = false;
    582     } else if (loginBubble) {
    583       loginBubble.reposition();
    584     }
    585     if (otherSessionsButton) {
    586       otherSessionsButton.updateSignInState(isUserSignedIn);
    587       layoutFooter();
    588     }
    589   }
    590 
    591   /**
    592    * Show the sync login UI.
    593    * @param {Event} e The click event.
    594    */
    595   function showSyncLoginUI(e) {
    596     var rect = e.currentTarget.getBoundingClientRect();
    597     chrome.send('showSyncLoginUI',
    598                 [rect.left, rect.top, rect.width, rect.height]);
    599   }
    600 
    601   /**
    602    * Logs the time to click for the specified item.
    603    * @param {string} item The item to log the time-to-click.
    604    */
    605   function logTimeToClick(item) {
    606     var timeToClick = Date.now() - startTime;
    607     chrome.send('logTimeToClick',
    608         ['NewTabPage.TimeToClick' + item, timeToClick]);
    609   }
    610 
    611   /**
    612    * Wrappers to forward the callback to corresponding PageListView member.
    613    */
    614   function appAdded() {
    615     return newTabView.appAdded.apply(newTabView, arguments);
    616   }
    617 
    618   function appMoved() {
    619     return newTabView.appMoved.apply(newTabView, arguments);
    620   }
    621 
    622   function appRemoved() {
    623     return newTabView.appRemoved.apply(newTabView, arguments);
    624   }
    625 
    626   function appsPrefChangeCallback() {
    627     return newTabView.appsPrefChangedCallback.apply(newTabView, arguments);
    628   }
    629 
    630   function appLauncherPromoPrefChangeCallback() {
    631     return newTabView.appLauncherPromoPrefChangeCallback.apply(newTabView,
    632                                                                arguments);
    633   }
    634 
    635   function appsReordered() {
    636     return newTabView.appsReordered.apply(newTabView, arguments);
    637   }
    638 
    639   function enterRearrangeMode() {
    640     return newTabView.enterRearrangeMode.apply(newTabView, arguments);
    641   }
    642 
    643   function setForeignSessions(sessionList, isTabSyncEnabled) {
    644     if (otherSessionsButton) {
    645       otherSessionsButton.setForeignSessions(sessionList, isTabSyncEnabled);
    646       layoutFooter();
    647     }
    648   }
    649 
    650   function getAppsCallback() {
    651     return newTabView.getAppsCallback.apply(newTabView, arguments);
    652   }
    653 
    654   function getAppsPageIndex() {
    655     return newTabView.getAppsPageIndex.apply(newTabView, arguments);
    656   }
    657 
    658   function getCardSlider() {
    659     return newTabView.cardSlider;
    660   }
    661 
    662   function leaveRearrangeMode() {
    663     return newTabView.leaveRearrangeMode.apply(newTabView, arguments);
    664   }
    665 
    666   function saveAppPageName() {
    667     return newTabView.saveAppPageName.apply(newTabView, arguments);
    668   }
    669 
    670   function setAppToBeHighlighted(appId) {
    671     newTabView.highlightAppId = appId;
    672   }
    673 
    674   // Return an object with all the exports
    675   return {
    676     appAdded: appAdded,
    677     appMoved: appMoved,
    678     appRemoved: appRemoved,
    679     appsPrefChangeCallback: appsPrefChangeCallback,
    680     appLauncherPromoPrefChangeCallback: appLauncherPromoPrefChangeCallback,
    681     enterRearrangeMode: enterRearrangeMode,
    682     getAppsCallback: getAppsCallback,
    683     getAppsPageIndex: getAppsPageIndex,
    684     getCardSlider: getCardSlider,
    685     onLoad: onLoad,
    686     leaveRearrangeMode: leaveRearrangeMode,
    687     logTimeToClick: logTimeToClick,
    688     NtpFollowAction: NtpFollowAction,
    689     saveAppPageName: saveAppPageName,
    690     setAppToBeHighlighted: setAppToBeHighlighted,
    691     setBookmarkBarAttached: setBookmarkBarAttached,
    692     setForeignSessions: setForeignSessions,
    693     setMostVisitedPages: setMostVisitedPages,
    694     setSuggestionsPages: setSuggestionsPages,
    695     setRecentlyClosedTabs: setRecentlyClosedTabs,
    696     setFaviconDominantColor: setFaviconDominantColor,
    697     showNotification: showNotification,
    698     themeChanged: themeChanged,
    699     updateLogin: updateLogin
    700   };
    701 });
    702 
    703 document.addEventListener('DOMContentLoaded', ntp.onLoad);
    704 
    705 var toCssPx = cr.ui.toCssPx;
    706