Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / resources / ntp4 / new_tab.js
blob678be69906002c8e89a2f31ec1e0c57d60e3d461
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 // 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';
16   /**
17    * NewTabView instance.
18    * @type {!Object|undefined}
19    */
20   var newTabView;
22   /**
23    * The 'notification-container' element.
24    * @type {!Element|undefined}
25    */
26   var notificationContainer;
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 {!cr.ui.Bubble|undefined}
33    */
34   var promoBubble;
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 {!cr.ui.Bubble|undefined}
40    */
41   var loginBubble;
43   /**
44    * true if |loginBubble| should be shown.
45    * @type {boolean}
46    */
47   var shouldShowLoginBubble = false;
49   /**
50    * The time when all sections are ready.
51    * @type {number|undefined}
52    * @private
53    */
54   var startTime;
56   /**
57    * The time in milliseconds for most transitions.  This should match what's
58    * in new_tab.css.  Unfortunately there's no better way to try to time
59    * something to occur until after a transition has completed.
60    * @type {number}
61    * @const
62    */
63   var DEFAULT_TRANSITION_TIME = 500;
65   /**
66    * See description for these values in ntp_stats.h.
67    * @enum {number}
68    */
69   var NtpFollowAction = {
70     CLICKED_TILE: 11,
71     CLICKED_OTHER_NTP_PANE: 12,
72     OTHER: 13
73   };
75   /**
76    * Creates a NewTabView object. NewTabView extends PageListView with
77    * new tab UI specific logics.
78    * @constructor
79    * @extends {ntp.PageListView}
80    */
81   function NewTabView() {
82     var pageSwitcherStart;
83     var pageSwitcherEnd;
84     if (loadTimeData.getValue('showApps')) {
85       pageSwitcherStart = /** @type {!ntp.PageSwitcher} */(
86           getRequiredElement('page-switcher-start'));
87       pageSwitcherEnd = /** @type {!ntp.PageSwitcher} */(
88           getRequiredElement('page-switcher-end'));
89     }
90     this.initialize(getRequiredElement('page-list'),
91                     getRequiredElement('dot-list'),
92                     getRequiredElement('card-slider-frame'),
93                     getRequiredElement('trash'),
94                     pageSwitcherStart, pageSwitcherEnd);
95   }
97   NewTabView.prototype = {
98     __proto__: ntp.PageListView.prototype,
100     /** @override */
101     appendTilePage: function(page, title, titleIsEditable, opt_refNode) {
102       ntp.PageListView.prototype.appendTilePage.apply(this, arguments);
104       if (promoBubble)
105         window.setTimeout(promoBubble.reposition.bind(promoBubble), 0);
106     }
107   };
109   /**
110    * Invoked at startup once the DOM is available to initialize the app.
111    */
112   function onLoad() {
113     sectionsToWaitFor = 0;
114     if (loadTimeData.getBoolean('showApps')) {
115       sectionsToWaitFor++;
116       if (loadTimeData.getBoolean('showAppLauncherPromo')) {
117         $('app-launcher-promo-close-button').addEventListener('click',
118             function() { chrome.send('stopShowingAppLauncherPromo'); });
119         $('apps-promo-learn-more').addEventListener('click',
120             function() { chrome.send('onLearnMore'); });
121       }
122     }
123     measureNavDots();
125     // Load the current theme colors.
126     themeChanged();
128     newTabView = new NewTabView();
130     notificationContainer = getRequiredElement('notification-container');
131     notificationContainer.addEventListener(
132         'webkitTransitionEnd', onNotificationTransitionEnd);
134     if (!loadTimeData.getBoolean('showWebStoreIcon')) {
135       var webStoreIcon = $('chrome-web-store-link');
136       // Not all versions of the NTP have a footer, so this may not exist.
137       if (webStoreIcon)
138         webStoreIcon.hidden = true;
139     } else {
140       var webStoreLink = loadTimeData.getString('webStoreLink');
141       var url = appendParam(webStoreLink, 'utm_source', 'chrome-ntp-launcher');
142       $('chrome-web-store-link').href = url;
143       $('chrome-web-store-link').addEventListener('click',
144           onChromeWebStoreButtonClick);
145     }
147     // We need to wait for all the footer menu setup to be completed before
148     // we can compute its layout.
149     layoutFooter();
151     if (loadTimeData.getString('login_status_message')) {
152       loginBubble = new cr.ui.Bubble;
153       loginBubble.anchorNode = $('login-container');
154       loginBubble.arrowLocation = cr.ui.ArrowLocation.TOP_END;
155       loginBubble.bubbleAlignment =
156           cr.ui.BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE;
157       loginBubble.deactivateToDismissDelay = 2000;
158       loginBubble.closeButtonVisible = false;
160       $('login-status-advanced').onclick = function() {
161         chrome.send('showAdvancedLoginUI');
162       };
163       $('login-status-dismiss').onclick = loginBubble.hide.bind(loginBubble);
165       var bubbleContent = $('login-status-bubble-contents');
166       loginBubble.content = bubbleContent;
168       // The anchor node won't be updated until updateLogin is called so don't
169       // show the bubble yet.
170       shouldShowLoginBubble = true;
171     }
173     if (loadTimeData.valueExists('bubblePromoText')) {
174       promoBubble = new cr.ui.Bubble;
175       promoBubble.anchorNode = getRequiredElement('promo-bubble-anchor');
176       promoBubble.arrowLocation = cr.ui.ArrowLocation.BOTTOM_START;
177       promoBubble.bubbleAlignment = cr.ui.BubbleAlignment.ENTIRELY_VISIBLE;
178       promoBubble.deactivateToDismissDelay = 2000;
179       promoBubble.content = parseHtmlSubset(
180           loadTimeData.getString('bubblePromoText'), ['BR']);
182       var bubbleLink = promoBubble.querySelector('a');
183       if (bubbleLink) {
184         bubbleLink.addEventListener('click', function(e) {
185           chrome.send('bubblePromoLinkClicked');
186         });
187       }
189       promoBubble.handleCloseEvent = function() {
190         promoBubble.hide();
191         chrome.send('bubblePromoClosed');
192       };
193       promoBubble.show();
194       chrome.send('bubblePromoViewed');
195     }
197     $('login-container').addEventListener('click', showSyncLoginUI);
198     if (loadTimeData.getBoolean('shouldShowSyncLogin'))
199       chrome.send('initializeSyncLogin');
201     doWhenAllSectionsReady(function() {
202       // Tell the slider about the pages.
203       newTabView.updateSliderCards();
204       // Mark the current page.
205       newTabView.cardSlider.currentCardValue.navigationDot.classList.add(
206           'selected');
208       if (loadTimeData.valueExists('notificationPromoText')) {
209         var promoText = loadTimeData.getString('notificationPromoText');
210         var tags = ['IMG'];
211         var attrs = {
212           src: function(node, value) {
213             return node.tagName == 'IMG' &&
214                    /^data\:image\/(?:png|gif|jpe?g)/.test(value);
215           },
216         };
218         var promo = parseHtmlSubset(promoText, tags, attrs);
219         var promoLink = promo.querySelector('a');
220         if (promoLink) {
221           promoLink.addEventListener('click', function(e) {
222             chrome.send('notificationPromoLinkClicked');
223           });
224         }
226         showNotification(promo, [], function() {
227           chrome.send('notificationPromoClosed');
228         }, 60000);
229         chrome.send('notificationPromoViewed');
230       }
232       cr.dispatchSimpleEvent(document, 'ntpLoaded', true, true);
233       document.documentElement.classList.remove('starting-up');
235       startTime = Date.now();
236     });
237   }
239   /**
240    * Launches the chrome web store app with the chrome-ntp-launcher
241    * source.
242    * @param {Event} e The click event.
243    */
244   function onChromeWebStoreButtonClick(e) {
245     chrome.send('recordAppLaunchByURL',
246                 [encodeURIComponent(this.href),
247                  ntp.APP_LAUNCH.NTP_WEBSTORE_FOOTER]);
248   }
250   /**
251    * The number of sections to wait on.
252    * @type {number}
253    */
254   var sectionsToWaitFor = -1;
256   /**
257    * Queued callbacks which lie in wait for all sections to be ready.
258    * @type {Array}
259    */
260   var readyCallbacks = [];
262   /**
263    * Fired as each section of pages becomes ready.
264    * @param {Event} e Each page's synthetic DOM event.
265    */
266   document.addEventListener('sectionready', function(e) {
267     if (--sectionsToWaitFor <= 0) {
268       while (readyCallbacks.length) {
269         readyCallbacks.shift()();
270       }
271     }
272   });
274   /**
275    * This is used to simulate a fire-once event (i.e. $(document).ready() in
276    * jQuery or Y.on('domready') in YUI. If all sections are ready, the callback
277    * is fired right away. If all pages are not ready yet, the function is queued
278    * for later execution.
279    * @param {Function} callback The work to be done when ready.
280    */
281   function doWhenAllSectionsReady(callback) {
282     assert(typeof callback == 'function');
283     if (sectionsToWaitFor > 0)
284       readyCallbacks.push(callback);
285     else
286       window.setTimeout(callback, 0);  // Do soon after, but asynchronously.
287   }
289   /**
290    * Measure the width of a nav dot with a given title.
291    * @param {string} id The loadTimeData ID of the desired title.
292    * @return {number} The width of the nav dot.
293    */
294   function measureNavDot(id) {
295     var measuringDiv = $('fontMeasuringDiv');
296     measuringDiv.textContent = loadTimeData.getString(id);
297     // The 4 is for border and padding.
298     return Math.max(measuringDiv.clientWidth * 1.15 + 4, 80);
299   }
301   /**
302    * Fills in an invisible div with the longest dot title string so that
303    * its length may be measured and the nav dots sized accordingly.
304    */
305   function measureNavDots() {
306     var styleElement = document.createElement('style');
307     styleElement.type = 'text/css';
308     // max-width is used because if we run out of space, the nav dots will be
309     // shrunk.
310     var pxWidth = measureNavDot('appDefaultPageName');
311     styleElement.textContent = '.dot { max-width: ' + pxWidth + 'px; }';
312     document.querySelector('head').appendChild(styleElement);
313   }
315   /**
316    * Layout the footer so that the nav dots stay centered.
317    */
318   function layoutFooter() {
319     // We need the image to be loaded.
320     var logo = $('logo-img');
321     var logoImg = logo.querySelector('img');
322     if (!logoImg.complete) {
323       logoImg.onload = layoutFooter;
324       return;
325     }
327     var menu = $('footer-menu-container');
328     if (menu.clientWidth > logoImg.width)
329       logo.style.WebkitFlex = '0 1 ' + menu.clientWidth + 'px';
330     else
331       menu.style.WebkitFlex = '0 1 ' + logoImg.width + 'px';
332   }
334   /**
335    * @param {boolean=} opt_hasAttribution
336    */
337   function themeChanged(opt_hasAttribution) {
338     $('themecss').href = 'chrome://theme/css/new_tab_theme.css?' + Date.now();
340     if (typeof opt_hasAttribution != 'undefined') {
341       document.documentElement.setAttribute('hasattribution',
342                                             opt_hasAttribution);
343     }
345     updateAttribution();
346   }
348   function setBookmarkBarAttached(attached) {
349     document.documentElement.setAttribute('bookmarkbarattached', attached);
350   }
352   /**
353    * Attributes the attribution image at the bottom left.
354    */
355   function updateAttribution() {
356     var attribution = $('attribution');
357     if (document.documentElement.getAttribute('hasattribution') == 'true') {
358       attribution.hidden = false;
359     } else {
360       attribution.hidden = true;
361     }
362   }
364   /**
365    * Timeout ID.
366    * @type {number}
367    */
368   var notificationTimeout = 0;
370   /**
371    * Shows the notification bubble.
372    * @param {string|Node} message The notification message or node to use as
373    *     message.
374    * @param {Array<{text: string, action: function()}>} links An array of
375    *     records describing the links in the notification. Each record should
376    *     have a 'text' attribute (the display string) and an 'action' attribute
377    *     (a function to run when the link is activated).
378    * @param {Function=} opt_closeHandler The callback invoked if the user
379    *     manually dismisses the notification.
380    * @param {number=} opt_timeout
381    */
382   function showNotification(message, links, opt_closeHandler, opt_timeout) {
383     window.clearTimeout(notificationTimeout);
385     var span = document.querySelector('#notification > span');
386     if (typeof message == 'string') {
387       span.textContent = message;
388     } else {
389       span.textContent = '';  // Remove all children.
390       span.appendChild(message);
391     }
393     var linksBin = $('notificationLinks');
394     linksBin.textContent = '';
395     for (var i = 0; i < links.length; i++) {
396       var link = new ActionLink;
397       link.textContent = links[i].text;
398       link.action = links[i].action;
399       link.onclick = function() {
400         this.action();
401         hideNotification();
402       };
403       linksBin.appendChild(link);
404     }
406     function closeFunc(e) {
407       if (opt_closeHandler)
408         opt_closeHandler();
409       hideNotification();
410     }
412     document.querySelector('#notification button').onclick = closeFunc;
413     document.addEventListener('dragstart', closeFunc);
415     notificationContainer.hidden = false;
416     showNotificationOnCurrentPage();
418     newTabView.cardSlider.frame.addEventListener(
419         'cardSlider:card_change_ended', onCardChangeEnded);
421     var timeout = opt_timeout || 10000;
422     notificationTimeout = window.setTimeout(hideNotification, timeout);
423   }
425   /**
426    * Hide the notification bubble.
427    */
428   function hideNotification() {
429     notificationContainer.classList.add('inactive');
431     newTabView.cardSlider.frame.removeEventListener(
432         'cardSlider:card_change_ended', onCardChangeEnded);
433   }
435   /**
436    * Happens when 1 or more consecutive card changes end.
437    * @param {Event} e The cardSlider:card_change_ended event.
438    */
439   function onCardChangeEnded(e) {
440     // If we ended on the same page as we started, ignore.
441     if (newTabView.cardSlider.currentCardValue.notification)
442       return;
444     // Hide the notification the old page.
445     notificationContainer.classList.add('card-changed');
447     showNotificationOnCurrentPage();
448   }
450   /**
451    * Move and show the notification on the current page.
452    */
453   function showNotificationOnCurrentPage() {
454     var page = newTabView.cardSlider.currentCardValue;
455     doWhenAllSectionsReady(function() {
456       if (page != newTabView.cardSlider.currentCardValue)
457         return;
459       // NOTE: This moves the notification to inside of the current page.
460       page.notification = notificationContainer;
462       // Reveal the notification and instruct it to hide itself if ignored.
463       notificationContainer.classList.remove('inactive');
465       // Gives the browser time to apply this rule before we remove it (causing
466       // a transition).
467       window.setTimeout(function() {
468         notificationContainer.classList.remove('card-changed');
469       }, 0);
470     });
471   }
473   /**
474    * When done fading out, set hidden to true so the notification can't be
475    * tabbed to or clicked.
476    * @param {Event} e The webkitTransitionEnd event.
477    */
478   function onNotificationTransitionEnd(e) {
479     if (notificationContainer.classList.contains('inactive'))
480       notificationContainer.hidden = true;
481   }
483   /**
484    * Set the dominant color for a node. This will be called in response to
485    * getFaviconDominantColor. The node represented by |id| better have a setter
486    * for stripeColor.
487    * @param {string} id The ID of a node.
488    * @param {string} color The color represented as a CSS string.
489    */
490   function setFaviconDominantColor(id, color) {
491     var node = $(id);
492     if (node)
493       node.stripeColor = color;
494   }
496   /**
497    * Updates the text displayed in the login container. If there is no text then
498    * the login container is hidden.
499    * @param {string} loginHeader The first line of text.
500    * @param {string} loginSubHeader The second line of text.
501    * @param {string} iconURL The url for the login status icon. If this is null
502         then the login status icon is hidden.
503    * @param {boolean} isUserSignedIn Indicates if the user is signed in or not.
504    */
505   function updateLogin(loginHeader, loginSubHeader, iconURL, isUserSignedIn) {
506     /** @const */ var showLogin = loginHeader || loginSubHeader;
508     $('login-container').hidden = !showLogin;
509     $('login-container').classList.toggle('signed-in', isUserSignedIn);
510     $('card-slider-frame').classList.toggle('showing-login-area', !!showLogin);
512     if (showLogin) {
513       // TODO(dbeam): we should use .textContent instead to mitigate XSS.
514       $('login-status-header').innerHTML = loginHeader;
515       $('login-status-sub-header').innerHTML = loginSubHeader;
517       var headerContainer = $('login-status-header-container');
518       headerContainer.classList.toggle('login-status-icon', !!iconURL);
519       headerContainer.style.backgroundImage = iconURL ? url(iconURL) : 'none';
520     }
522     if (shouldShowLoginBubble) {
523       window.setTimeout(loginBubble.show.bind(loginBubble), 0);
524       chrome.send('loginMessageSeen');
525       shouldShowLoginBubble = false;
526     } else if (loginBubble) {
527       loginBubble.reposition();
528     }
529   }
531   /**
532    * Show the sync login UI.
533    * @param {Event} e The click event.
534    */
535   function showSyncLoginUI(e) {
536     var rect = e.currentTarget.getBoundingClientRect();
537     chrome.send('showSyncLoginUI',
538                 [rect.left, rect.top, rect.width, rect.height]);
539   }
541   /**
542    * Wrappers to forward the callback to corresponding PageListView member.
543    */
545   /**
546    * Called by chrome when a new app has been added to chrome or has been
547    * enabled if previously disabled.
548    * @param {Object} appData A data structure full of relevant information for
549    *     the app.
550    * @param {boolean=} opt_highlight Whether the app about to be added should
551    *     be highlighted.
552    */
553   function appAdded(appData, opt_highlight) {
554     newTabView.appAdded(appData, opt_highlight);
555   }
557   /**
558    * Called by chrome when an app has changed positions.
559    * @param {Object} appData The data for the app. This contains page and
560    *     position indices.
561    */
562   function appMoved(appData) {
563     newTabView.appMoved(appData);
564   }
566   /**
567    * Called by chrome when an existing app has been disabled or
568    * removed/uninstalled from chrome.
569    * @param {Object} appData A data structure full of relevant information for
570    *     the app.
571    * @param {boolean} isUninstall True if the app is being uninstalled;
572    *     false if the app is being disabled.
573    * @param {boolean} fromPage True if the removal was from the current page.
574    */
575   function appRemoved(appData, isUninstall, fromPage) {
576     newTabView.appRemoved(appData, isUninstall, fromPage);
577   }
579   /**
580    * Callback invoked by chrome whenever an app preference changes.
581    * @param {Object} data An object with all the data on available
582    *     applications.
583    */
584   function appsPrefChangeCallback(data) {
585     newTabView.appsPrefChangedCallback(data);
586   }
588   /**
589    * Callback invoked by chrome whenever the app launcher promo pref changes.
590    * @param {boolean} show Identifies if we should show or hide the promo.
591    */
592   function appLauncherPromoPrefChangeCallback(show) {
593     newTabView.appLauncherPromoPrefChangeCallback(show);
594   }
596   /**
597    * Called whenever tiles should be re-arranging themselves out of the way
598    * of a moving or insert tile.
599    */
600   function enterRearrangeMode() {
601     newTabView.enterRearrangeMode();
602   }
604   /**
605    * Callback invoked by chrome with the apps available.
606    *
607    * Note that calls to this function can occur at any time, not just in
608    * response to a getApps request. For example, when a user
609    * installs/uninstalls an app on another synchronized devices.
610    * @param {Object} data An object with all the data on available
611    *        applications.
612    */
613   function getAppsCallback(data) {
614     newTabView.getAppsCallback(data);
615   }
617   /**
618    * Return the index of the given apps page.
619    * @param {ntp.AppsPage} page The AppsPage we wish to find.
620    * @return {number} The index of |page| or -1 if it is not in the collection.
621    */
622   function getAppsPageIndex(page) {
623     return newTabView.getAppsPageIndex(page);
624   }
626   function getCardSlider() {
627     return newTabView.cardSlider;
628   }
630   /**
631    * Invoked whenever some app is released
632    */
633   function leaveRearrangeMode() {
634     newTabView.leaveRearrangeMode();
635   }
637   /**
638    * Save the name of an apps page.
639    * Store the apps page name into the preferences store.
640    * @param {ntp.AppsPage} appPage The app page for which we wish to save.
641    * @param {string} name The name of the page.
642    */
643   function saveAppPageName(appPage, name) {
644     newTabView.saveAppPageName(appPage, name);
645   }
647   function setAppToBeHighlighted(appId) {
648     newTabView.highlightAppId = appId;
649   }
651   // Return an object with all the exports
652   return {
653     appAdded: appAdded,
654     appMoved: appMoved,
655     appRemoved: appRemoved,
656     appsPrefChangeCallback: appsPrefChangeCallback,
657     appLauncherPromoPrefChangeCallback: appLauncherPromoPrefChangeCallback,
658     enterRearrangeMode: enterRearrangeMode,
659     getAppsCallback: getAppsCallback,
660     getAppsPageIndex: getAppsPageIndex,
661     getCardSlider: getCardSlider,
662     onLoad: onLoad,
663     leaveRearrangeMode: leaveRearrangeMode,
664     NtpFollowAction: NtpFollowAction,
665     saveAppPageName: saveAppPageName,
666     setAppToBeHighlighted: setAppToBeHighlighted,
667     setBookmarkBarAttached: setBookmarkBarAttached,
668     setFaviconDominantColor: setFaviconDominantColor,
669     showNotification: showNotification,
670     themeChanged: themeChanged,
671     updateLogin: updateLogin
672   };
675 document.addEventListener('DOMContentLoaded', ntp.onLoad);
677 var toCssPx = cr.ui.toCssPx;