• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
13cr.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   * Measure the width of a nav dot with a given title.
345   * @param {string} id The loadTimeData ID of the desired title.
346   * @return {number} The width of the nav dot.
347   */
348  function measureNavDot(id) {
349    var measuringDiv = $('fontMeasuringDiv');
350    measuringDiv.textContent = loadTimeData.getString(id);
351    // The 4 is for border and padding.
352    return Math.max(measuringDiv.clientWidth * 1.15 + 4, 80);
353  }
354
355  /**
356   * Fills in an invisible div with the longest dot title string so that
357   * its length may be measured and the nav dots sized accordingly.
358   */
359  function measureNavDots() {
360    var pxWidth = measureNavDot('appDefaultPageName');
361    if (loadTimeData.getBoolean('showMostvisited'))
362      pxWidth = Math.max(measureNavDot('mostvisited'), pxWidth);
363
364    var styleElement = document.createElement('style');
365    styleElement.type = 'text/css';
366    // max-width is used because if we run out of space, the nav dots will be
367    // shrunk.
368    styleElement.textContent = '.dot { max-width: ' + pxWidth + 'px; }';
369    document.querySelector('head').appendChild(styleElement);
370  }
371
372  /**
373   * Layout the footer so that the nav dots stay centered.
374   */
375  function layoutFooter() {
376    // We need the image to be loaded.
377    var logo = $('logo-img');
378    var logoImg = logo.querySelector('img');
379    if (!logoImg.complete) {
380      logoImg.onload = layoutFooter;
381      return;
382    }
383
384    var menu = $('footer-menu-container');
385    if (menu.clientWidth > logoImg.width)
386      logo.style.WebkitFlex = '0 1 ' + menu.clientWidth + 'px';
387    else
388      menu.style.WebkitFlex = '0 1 ' + logoImg.width + 'px';
389  }
390
391  function themeChanged(opt_hasAttribution) {
392    $('themecss').href = 'chrome://theme/css/new_tab_theme.css?' + Date.now();
393
394    if (typeof opt_hasAttribution != 'undefined') {
395      document.documentElement.setAttribute('hasattribution',
396                                            opt_hasAttribution);
397    }
398
399    updateAttribution();
400  }
401
402  function setBookmarkBarAttached(attached) {
403    document.documentElement.setAttribute('bookmarkbarattached', attached);
404  }
405
406  /**
407   * Attributes the attribution image at the bottom left.
408   */
409  function updateAttribution() {
410    var attribution = $('attribution');
411    if (document.documentElement.getAttribute('hasattribution') == 'true') {
412      attribution.hidden = false;
413    } else {
414      attribution.hidden = true;
415    }
416  }
417
418  /**
419   * Timeout ID.
420   * @type {number}
421   */
422  var notificationTimeout = 0;
423
424  /**
425   * Shows the notification bubble.
426   * @param {string|Node} message The notification message or node to use as
427   *     message.
428   * @param {Array.<{text: string, action: function()}>} links An array of
429   *     records describing the links in the notification. Each record should
430   *     have a 'text' attribute (the display string) and an 'action' attribute
431   *     (a function to run when the link is activated).
432   * @param {Function} opt_closeHandler The callback invoked if the user
433   *     manually dismisses the notification.
434   */
435  function showNotification(message, links, opt_closeHandler, opt_timeout) {
436    window.clearTimeout(notificationTimeout);
437
438    var span = document.querySelector('#notification > span');
439    if (typeof message == 'string') {
440      span.textContent = message;
441    } else {
442      span.textContent = '';  // Remove all children.
443      span.appendChild(message);
444    }
445
446    var linksBin = $('notificationLinks');
447    linksBin.textContent = '';
448    for (var i = 0; i < links.length; i++) {
449      var link = linksBin.ownerDocument.createElement('div');
450      link.textContent = links[i].text;
451      link.action = links[i].action;
452      link.onclick = function() {
453        this.action();
454        hideNotification();
455      };
456      link.setAttribute('role', 'button');
457      link.setAttribute('tabindex', 0);
458      link.className = 'link-button';
459      linksBin.appendChild(link);
460    }
461
462    function closeFunc(e) {
463      if (opt_closeHandler)
464        opt_closeHandler();
465      hideNotification();
466    }
467
468    document.querySelector('#notification button').onclick = closeFunc;
469    document.addEventListener('dragstart', closeFunc);
470
471    notificationContainer.hidden = false;
472    showNotificationOnCurrentPage();
473
474    newTabView.cardSlider.frame.addEventListener(
475        'cardSlider:card_change_ended', onCardChangeEnded);
476
477    var timeout = opt_timeout || 10000;
478    notificationTimeout = window.setTimeout(hideNotification, timeout);
479  }
480
481  /**
482   * Hide the notification bubble.
483   */
484  function hideNotification() {
485    notificationContainer.classList.add('inactive');
486
487    newTabView.cardSlider.frame.removeEventListener(
488        'cardSlider:card_change_ended', onCardChangeEnded);
489  }
490
491  /**
492   * Happens when 1 or more consecutive card changes end.
493   * @param {Event} e The cardSlider:card_change_ended event.
494   */
495  function onCardChangeEnded(e) {
496    // If we ended on the same page as we started, ignore.
497    if (newTabView.cardSlider.currentCardValue.notification)
498      return;
499
500    // Hide the notification the old page.
501    notificationContainer.classList.add('card-changed');
502
503    showNotificationOnCurrentPage();
504  }
505
506  /**
507   * Move and show the notification on the current page.
508   */
509  function showNotificationOnCurrentPage() {
510    var page = newTabView.cardSlider.currentCardValue;
511    doWhenAllSectionsReady(function() {
512      if (page != newTabView.cardSlider.currentCardValue)
513        return;
514
515      // NOTE: This moves the notification to inside of the current page.
516      page.notification = notificationContainer;
517
518      // Reveal the notification and instruct it to hide itself if ignored.
519      notificationContainer.classList.remove('inactive');
520
521      // Gives the browser time to apply this rule before we remove it (causing
522      // a transition).
523      window.setTimeout(function() {
524        notificationContainer.classList.remove('card-changed');
525      }, 0);
526    });
527  }
528
529  /**
530   * When done fading out, set hidden to true so the notification can't be
531   * tabbed to or clicked.
532   * @param {Event} e The webkitTransitionEnd event.
533   */
534  function onNotificationTransitionEnd(e) {
535    if (notificationContainer.classList.contains('inactive'))
536      notificationContainer.hidden = true;
537  }
538
539  function setRecentlyClosedTabs(dataItems) {
540    $('recently-closed-menu-button').dataItems = dataItems;
541    layoutFooter();
542  }
543
544  function setMostVisitedPages(data, hasBlacklistedUrls) {
545    newTabView.mostVisitedPage.data = data;
546    cr.dispatchSimpleEvent(document, 'sectionready', true, true);
547  }
548
549  function setSuggestionsPages(data, hasBlacklistedUrls) {
550    newTabView.suggestionsPage.data = data;
551  }
552
553  /**
554   * Set the dominant color for a node. This will be called in response to
555   * getFaviconDominantColor. The node represented by |id| better have a setter
556   * for stripeColor.
557   * @param {string} id The ID of a node.
558   * @param {string} color The color represented as a CSS string.
559   */
560  function setFaviconDominantColor(id, color) {
561    var node = $(id);
562    if (node)
563      node.stripeColor = color;
564  }
565
566  /**
567   * Updates the text displayed in the login container. If there is no text then
568   * the login container is hidden.
569   * @param {string} loginHeader The first line of text.
570   * @param {string} loginSubHeader The second line of text.
571   * @param {string} iconURL The url for the login status icon. If this is null
572        then the login status icon is hidden.
573   * @param {boolean} isUserSignedIn Indicates if the user is signed in or not.
574   */
575  function updateLogin(loginHeader, loginSubHeader, iconURL, isUserSignedIn) {
576    if (loginHeader || loginSubHeader) {
577      $('login-container').hidden = false;
578      $('login-status-header').innerHTML = loginHeader;
579      $('login-status-sub-header').innerHTML = loginSubHeader;
580      $('card-slider-frame').classList.add('showing-login-area');
581
582      if (iconURL) {
583        $('login-status-header-container').style.backgroundImage = url(iconURL);
584        $('login-status-header-container').classList.add('login-status-icon');
585      } else {
586        $('login-status-header-container').style.backgroundImage = 'none';
587        $('login-status-header-container').classList.remove(
588            'login-status-icon');
589      }
590    } else {
591      $('login-container').hidden = true;
592      $('card-slider-frame').classList.remove('showing-login-area');
593    }
594    if (shouldShowLoginBubble) {
595      window.setTimeout(loginBubble.show.bind(loginBubble), 0);
596      chrome.send('loginMessageSeen');
597      shouldShowLoginBubble = false;
598    } else if (loginBubble) {
599      loginBubble.reposition();
600    }
601    if (otherSessionsButton) {
602      otherSessionsButton.updateSignInState(isUserSignedIn);
603      layoutFooter();
604    }
605  }
606
607  /**
608   * Show the sync login UI.
609   * @param {Event} e The click event.
610   */
611  function showSyncLoginUI(e) {
612    var rect = e.currentTarget.getBoundingClientRect();
613    chrome.send('showSyncLoginUI',
614                [rect.left, rect.top, rect.width, rect.height]);
615  }
616
617  /**
618   * Logs the time to click for the specified item.
619   * @param {string} item The item to log the time-to-click.
620   */
621  function logTimeToClick(item) {
622    var timeToClick = Date.now() - startTime;
623    chrome.send('logTimeToClick',
624        ['NewTabPage.TimeToClick' + item, timeToClick]);
625  }
626
627  /**
628   * Wrappers to forward the callback to corresponding PageListView member.
629   */
630  function appAdded() {
631    return newTabView.appAdded.apply(newTabView, arguments);
632  }
633
634  function appMoved() {
635    return newTabView.appMoved.apply(newTabView, arguments);
636  }
637
638  function appRemoved() {
639    return newTabView.appRemoved.apply(newTabView, arguments);
640  }
641
642  function appsPrefChangeCallback() {
643    return newTabView.appsPrefChangedCallback.apply(newTabView, arguments);
644  }
645
646  function appLauncherPromoPrefChangeCallback() {
647    return newTabView.appLauncherPromoPrefChangeCallback.apply(newTabView,
648                                                               arguments);
649  }
650
651  function appsReordered() {
652    return newTabView.appsReordered.apply(newTabView, arguments);
653  }
654
655  function enterRearrangeMode() {
656    return newTabView.enterRearrangeMode.apply(newTabView, arguments);
657  }
658
659  function setForeignSessions(sessionList, isTabSyncEnabled) {
660    if (otherSessionsButton) {
661      otherSessionsButton.setForeignSessions(sessionList, isTabSyncEnabled);
662      layoutFooter();
663    }
664  }
665
666  function getAppsCallback() {
667    return newTabView.getAppsCallback.apply(newTabView, arguments);
668  }
669
670  function getAppsPageIndex() {
671    return newTabView.getAppsPageIndex.apply(newTabView, arguments);
672  }
673
674  function getCardSlider() {
675    return newTabView.cardSlider;
676  }
677
678  function leaveRearrangeMode() {
679    return newTabView.leaveRearrangeMode.apply(newTabView, arguments);
680  }
681
682  function saveAppPageName() {
683    return newTabView.saveAppPageName.apply(newTabView, arguments);
684  }
685
686  function setAppToBeHighlighted(appId) {
687    newTabView.highlightAppId = appId;
688  }
689
690  // Return an object with all the exports
691  return {
692    appAdded: appAdded,
693    appMoved: appMoved,
694    appRemoved: appRemoved,
695    appsPrefChangeCallback: appsPrefChangeCallback,
696    appLauncherPromoPrefChangeCallback: appLauncherPromoPrefChangeCallback,
697    enterRearrangeMode: enterRearrangeMode,
698    getAppsCallback: getAppsCallback,
699    getAppsPageIndex: getAppsPageIndex,
700    getCardSlider: getCardSlider,
701    onLoad: onLoad,
702    leaveRearrangeMode: leaveRearrangeMode,
703    logTimeToClick: logTimeToClick,
704    NtpFollowAction: NtpFollowAction,
705    saveAppPageName: saveAppPageName,
706    setAppToBeHighlighted: setAppToBeHighlighted,
707    setBookmarkBarAttached: setBookmarkBarAttached,
708    setForeignSessions: setForeignSessions,
709    setMostVisitedPages: setMostVisitedPages,
710    setSuggestionsPages: setSuggestionsPages,
711    setRecentlyClosedTabs: setRecentlyClosedTabs,
712    setFaviconDominantColor: setFaviconDominantColor,
713    showNotification: showNotification,
714    themeChanged: themeChanged,
715    updateLogin: updateLogin
716  };
717});
718
719document.addEventListener('DOMContentLoaded', ntp.onLoad);
720
721var toCssPx = cr.ui.toCssPx;
722