Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / resources / local_ntp / local_ntp.js
blob9d3ce86c972f9dda3d4b218865399a9a358bd1e0
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 /**
7  * @fileoverview The local InstantExtended NTP.
8  */
11 /**
12  * Controls rendering the new tab page for InstantExtended.
13  * @return {Object} A limited interface for testing the local NTP.
14  */
15 function LocalNTP() {
16 'use strict';
19 /**
20  * Alias for document.getElementById.
21  * @param {string} id The ID of the element to find.
22  * @return {HTMLElement} The found element or null if not found.
23  */
24 function $(id) {
25   return document.getElementById(id);
29 /**
30  * Specifications for an NTP design (not comprehensive).
31  *
32  * fakeboxWingSize: Extra distance for fakebox to extend beyond beyond the list
33  *   of tiles.
34  * fontFamily: Font family to use for title and thumbnail iframes.
35  * fontSize: Font size to use for the iframes, in px.
36  * mainClass: Class applied to #ntp-contents to control CSS.
37  * numTitleLines: Number of lines to display in titles.
38  * showFavicon: Whether to show favicon.
39  * thumbnailTextColor: The 4-component color that thumbnail iframe may use to
40  *   display text message in place of missing thumbnail.
41  * thumbnailFallback: (Optional) A value in THUMBNAIL_FALLBACK to specify the
42  *   thumbnail fallback strategy. If unassigned, then the thumbnail.html
43  *   iframe would handle the fallback.
44  * tileWidth: The width of each suggestion tile, in px.
45  * tileMargin: Spacing between successive tiles, in px.
46  * titleColor: The 4-component color of title text.
47  * titleColorAgainstDark: The 4-component color of title text against a dark
48  *   theme.
49  * titleTextAlign: (Optional) The alignment of title text. If unspecified, the
50  *   default value is 'center'.
51  * titleTextFade: (Optional) The number of pixels beyond which title
52  *   text begins to fade. This overrides the default ellipsis style.
53  *
54  * @type {{
55  *   fakeboxWingSize: number,
56  *   fontFamily: string,
57  *   fontSize: number,
58  *   mainClass: string,
59  *   numTitleLines: number,
60  *   showFavicon: boolean,
61  *   thumbnailTextColor: string,
62  *   thumbnailFallback: string|null|undefined,
63  *   tileWidth: number,
64  *   tileMargin: number,
65  *   titleColor: string,
66  *   titleColorAgainstDark: string,
67  *   titleTextAlign: string|null|undefined,
68  *   titleTextFade: number|null|undefined
69  * }}
70  */
71 var NTP_DESIGN = {
72   fakeboxWingSize: 0,
73   fontFamily: 'arial, sans-serif',
74   fontSize: 12,
75   mainClass: 'thumb-ntp',
76   numTitleLines: 1,
77   showFavicon: true,
78   thumbnailTextColor: [50, 50, 50, 255],
79   thumbnailFallback: 'dot',  // Draw single dot.
80   tileWidth: 154,
81   tileMargin: 16,
82   titleColor: [50, 50, 50, 255],
83   titleColorAgainstDark: [210, 210, 210, 255],
84   titleTextAlign: 'inherit',
85   titleTextFade: 122 - 36  // 112px wide title with 32 pixel fade at end.
89 /**
90  * Modifies NTP_DESIGN parameters for icon NTP.
91  */
92 function modifyNtpDesignForIcons() {
93   NTP_DESIGN.fakeboxWingSize = 132;
94   NTP_DESIGN.mainClass = 'icon-ntp';
95   NTP_DESIGN.numTitleLines = 2;
96   NTP_DESIGN.showFavicon = false;
97   NTP_DESIGN.thumbnailFallback = null;
98   NTP_DESIGN.tileWidth = 48 + 2 * 18;
99   NTP_DESIGN.tileMargin = 60 - 18 * 2;
100   NTP_DESIGN.titleColor = [120, 120, 120, 255];
101   NTP_DESIGN.titleColorAgainstDark = [210, 210, 210, 255];
102   NTP_DESIGN.titleTextAlign = 'center';
103   delete NTP_DESIGN.titleTextFade;
108  * Enum for classnames.
109  * @enum {string}
110  * @const
111  */
112 var CLASSES = {
113   ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme
114   DARK: 'dark',
115   DEFAULT_THEME: 'default-theme',
116   DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide',
117   FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive
118   FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox
119   // Applies drag focus style to the fakebox
120   FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused',
121   HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo',
122   HIDE_NOTIFICATION: 'mv-notice-hide',
123   LEFT_ALIGN_ATTRIBUTION: 'left-align-attribution',
124   // Vertically centers the most visited section for a non-Google provided page.
125   NON_GOOGLE_PAGE: 'non-google-page',
126   RTL: 'rtl'  // Right-to-left language text.
131  * Enum for HTML element ids.
132  * @enum {string}
133  * @const
134  */
135 var IDS = {
136   ATTRIBUTION: 'attribution',
137   ATTRIBUTION_TEXT: 'attribution-text',
138   CUSTOM_THEME_STYLE: 'ct-style',
139   FAKEBOX: 'fakebox',
140   FAKEBOX_INPUT: 'fakebox-input',
141   FAKEBOX_TEXT: 'fakebox-text',
142   LOGO: 'logo',
143   NOTIFICATION: 'mv-notice',
144   NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x',
145   NOTIFICATION_MESSAGE: 'mv-msg',
146   NTP_CONTENTS: 'ntp-contents',
147   RESTORE_ALL_LINK: 'mv-restore',
148   TILES: 'mv-tiles',
149   UNDO_LINK: 'mv-undo'
154  * Enum for keycodes.
155  * @enum {number}
156  * @const
157  */
158 var KEYCODE = {
159   ENTER: 13
164  * Enum for the state of the NTP when it is disposed.
165  * @enum {number}
166  * @const
167  */
168 var NTP_DISPOSE_STATE = {
169   NONE: 0,  // Preserve the NTP appearance and functionality
170   DISABLE_FAKEBOX: 1,
171   HIDE_FAKEBOX_AND_LOGO: 2
176  * The notification displayed when a page is blacklisted.
177  * @type {Element}
178  */
179 var notification;
183  * The container for the theme attribution.
184  * @type {Element}
185  */
186 var attribution;
190  * The "fakebox" - an input field that looks like a regular searchbox.  When it
191  * is focused, any text the user types goes directly into the omnibox.
192  * @type {Element}
193  */
194 var fakebox;
198  * The container for NTP elements.
199  * @type {Element}
200  */
201 var ntpContents;
205  * The last blacklisted tile rid if any, which by definition should not be
206  * filler.
207  * @type {?number}
208  */
209 var lastBlacklistedTile = null;
213  * Current number of tiles columns shown based on the window width, including
214  * those that just contain filler.
215  * @type {number}
216  */
217 var numColumnsShown = 0;
221  * The browser embeddedSearch.newTabPage object.
222  * @type {Object}
223  */
224 var ntpApiHandle;
228  * The browser embeddedSearch.searchBox object.
229  * @type {Object}
230  */
231 var searchboxApiHandle;
235  * The state of the NTP when a query is entered into the Omnibox.
236  * @type {NTP_DISPOSE_STATE}
237  */
238 var omniboxInputBehavior = NTP_DISPOSE_STATE.NONE;
242  * The state of the NTP when a query is entered into the Fakebox.
243  * @type {NTP_DISPOSE_STATE}
244  */
245 var fakeboxInputBehavior = NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO;
248 /** @type {number} @const */
249 var MAX_NUM_TILES_TO_SHOW = 8;
252 /** @type {number} @const */
253 var MIN_NUM_COLUMNS = 2;
256 /** @type {number} @const */
257 var MAX_NUM_COLUMNS = 4;
260 /** @type {number} @const */
261 var NUM_ROWS = 2;
265  * Minimum total padding to give to the left and right of the most visited
266  * section. Used to determine how many tiles to show.
267  * @type {number}
268  * @const
269  */
270 var MIN_TOTAL_HORIZONTAL_PADDING = 200;
274  * Heuristic to determine whether a theme should be considered to be dark, so
275  * the colors of various UI elements can be adjusted.
276  * @param {ThemeBackgroundInfo|undefined} info Theme background information.
277  * @return {boolean} Whether the theme is dark.
278  * @private
279  */
280 function getIsThemeDark(info) {
281   if (!info)
282     return false;
283   // Heuristic: light text implies dark theme.
284   var rgba = info.textColorRgba;
285   var luminance = 0.3 * rgba[0] + 0.59 * rgba[1] + 0.11 * rgba[2];
286   return luminance >= 128;
291  * Updates the NTP based on the current theme.
292  * @private
293  */
294 function renderTheme() {
295   var fakeboxText = $(IDS.FAKEBOX_TEXT);
296   if (fakeboxText) {
297     fakeboxText.innerHTML = '';
298     if (configData.translatedStrings.searchboxPlaceholder) {
299       fakeboxText.textContent =
300           configData.translatedStrings.searchboxPlaceholder;
301     }
302   }
304   var info = ntpApiHandle.themeBackgroundInfo;
305   var isThemeDark = getIsThemeDark(info);
306   ntpContents.classList.toggle(CLASSES.DARK, isThemeDark);
307   if (!info) {
308     return;
309   }
311   var background = [convertToRGBAColor(info.backgroundColorRgba),
312                     info.imageUrl,
313                     info.imageTiling,
314                     info.imageHorizontalAlignment,
315                     info.imageVerticalAlignment].join(' ').trim();
317   document.body.style.background = background;
318   document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo);
319   updateThemeAttribution(info.attributionUrl, info.imageHorizontalAlignment);
320   setCustomThemeStyle(info);
322   var themeinfo = {cmd: 'updateTheme'};
323   if (!info.usingDefaultTheme) {
324     themeinfo.tileBorderColor = convertToRGBAColor(info.sectionBorderColorRgba);
325     themeinfo.tileHoverBorderColor = convertToRGBAColor(info.headerColorRgba);
326   }
327   themeinfo.isThemeDark = isThemeDark;
329   var titleColor = NTP_DESIGN.titleColor;
330   if (!info.usingDefaultTheme && info.textColorRgba) {
331     titleColor = info.textColorRgba;
332   } else if (isThemeDark) {
333     titleColor = NTP_DESIGN.titleColorAgainstDark;
334   }
335   themeinfo.tileTitleColor = convertToRGBAColor(titleColor);
337   $('mv-single').contentWindow.postMessage(themeinfo, '*');
342  * Updates the NTP based on the current theme, then rerenders all tiles.
343  * @private
344  */
345 function onThemeChange() {
346   renderTheme();
351  * Updates the NTP style according to theme.
352  * @param {Object=} opt_themeInfo The information about the theme. If it is
353  * omitted the style will be reverted to the default.
354  * @private
355  */
356 function setCustomThemeStyle(opt_themeInfo) {
357   var customStyleElement = $(IDS.CUSTOM_THEME_STYLE);
358   var head = document.head;
359   if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) {
360     ntpContents.classList.remove(CLASSES.DEFAULT_THEME);
361     var themeStyle =
362       '#attribution {' +
363       '  color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
364       '}' +
365       '#mv-msg {' +
366       '  color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' +
367       '}' +
368       '#mv-notice-links span {' +
369       '  color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
370       '}' +
371       '#mv-notice-x {' +
372       '  -webkit-filter: drop-shadow(0 0 0 ' +
373           convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' +
374       '}' +
375       '.mv-page-ready .mv-mask {' +
376       '  border: 1px solid ' +
377           convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' +
378       '}' +
379       '.mv-page-ready:hover .mv-mask, .mv-page-ready .mv-focused ~ .mv-mask {' +
380       '  border-color: ' +
381           convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' +
382       '}';
384     if (customStyleElement) {
385       customStyleElement.textContent = themeStyle;
386     } else {
387       customStyleElement = document.createElement('style');
388       customStyleElement.type = 'text/css';
389       customStyleElement.id = IDS.CUSTOM_THEME_STYLE;
390       customStyleElement.textContent = themeStyle;
391       head.appendChild(customStyleElement);
392     }
394   } else {
395     ntpContents.classList.add(CLASSES.DEFAULT_THEME);
396     if (customStyleElement)
397       head.removeChild(customStyleElement);
398   }
403  * Renders the attribution if the URL is present, otherwise hides it.
404  * @param {string} url The URL of the attribution image, if any.
405  * @param {string} themeBackgroundAlignment The alignment of the theme
406  *  background image. This is used to compute the attribution's alignment.
407  * @private
408  */
409 function updateThemeAttribution(url, themeBackgroundAlignment) {
410   if (!url) {
411     setAttributionVisibility_(false);
412     return;
413   }
415   var attributionImage = attribution.querySelector('img');
416   if (!attributionImage) {
417     attributionImage = new Image();
418     attribution.appendChild(attributionImage);
419   }
420   attributionImage.style.content = url;
422   // To avoid conflicts, place the attribution on the left for themes that
423   // right align their background images.
424   attribution.classList.toggle(CLASSES.LEFT_ALIGN_ATTRIBUTION,
425                                themeBackgroundAlignment == 'right');
426   setAttributionVisibility_(true);
431  * Sets the visibility of the theme attribution.
432  * @param {boolean} show True to show the attribution.
433  * @private
434  */
435 function setAttributionVisibility_(show) {
436   if (attribution) {
437     attribution.style.display = show ? '' : 'none';
438   }
442  /**
443  * Converts an Array of color components into RRGGBBAA format.
444  * @param {Array<number>} color Array of rgba color components.
445  * @return {string} Color string in RRGGBBAA format.
446  * @private
447  */
448 function convertToRRGGBBAAColor(color) {
449   return color.map(function(t) {
450     return ('0' + t.toString(16)).slice(-2);  // To 2-digit, 0-padded hex.
451   }).join('');
455  /**
456  * Converts an Array of color components into RGBA format "rgba(R,G,B,A)".
457  * @param {Array<number>} color Array of rgba color components.
458  * @return {string} CSS color in RGBA format.
459  * @private
460  */
461 function convertToRGBAColor(color) {
462   return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' +
463                     color[3] / 255 + ')';
468  * Called when page data change.
469  */
470 function onMostVisitedChange() {
471   reloadTiles();
476  * Fetches new data, creates, and renders tiles.
477  */
478 function reloadTiles() {
479   var pages = ntpApiHandle.mostVisited;
480   var cmds = [];
481   for (var i = 0; i < Math.min(MAX_NUM_TILES_TO_SHOW, pages.length); ++i) {
482     cmds.push({cmd: 'tile', rid: pages[i].rid});
483   }
484   cmds.push({cmd: 'show', maxVisible: numColumnsShown * NUM_ROWS});
486   $('mv-single').contentWindow.postMessage(cmds, '*');
491  * Shows the blacklist notification and triggers a delay to hide it.
492  */
493 function showNotification() {
494   notification.classList.remove(CLASSES.HIDE_NOTIFICATION);
495   notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
496   notification.scrollTop;
497   notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION);
502  * Hides the blacklist notification.
503  */
504 function hideNotification() {
505   notification.classList.add(CLASSES.HIDE_NOTIFICATION);
506   notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
511  * Handles a click on the notification undo link by hiding the notification and
512  * informing Chrome.
513  */
514 function onUndo() {
515   hideNotification();
516   if (lastBlacklistedTile != null) {
517     ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedTile);
518   }
523  * Handles a click on the restore all notification link by hiding the
524  * notification and informing Chrome.
525  */
526 function onRestoreAll() {
527   hideNotification();
528   ntpApiHandle.undoAllMostVisitedDeletions();
533  * Recomputes the number of tile columns, and width of various contents based
534  * on the width of the window.
535  * @return {boolean} Whether the number of tile columns has changed.
536  */
537 function updateContentWidth() {
538   var tileRequiredWidth = NTP_DESIGN.tileWidth + NTP_DESIGN.tileMargin;
539   // If innerWidth is zero, then use the maximum snap size.
540   var maxSnapSize = MAX_NUM_COLUMNS * tileRequiredWidth -
541       NTP_DESIGN.tileMargin + MIN_TOTAL_HORIZONTAL_PADDING;
542   var innerWidth = window.innerWidth || maxSnapSize;
543   // Each tile has left and right margins that sum to NTP_DESIGN.tileMargin.
544   var availableWidth = innerWidth + NTP_DESIGN.tileMargin -
545       NTP_DESIGN.fakeboxWingSize * 2 - MIN_TOTAL_HORIZONTAL_PADDING;
546   var newNumColumns = Math.floor(availableWidth / tileRequiredWidth);
547   if (newNumColumns < MIN_NUM_COLUMNS)
548     newNumColumns = MIN_NUM_COLUMNS;
549   else if (newNumColumns > MAX_NUM_COLUMNS)
550     newNumColumns = MAX_NUM_COLUMNS;
552   if (numColumnsShown === newNumColumns)
553     return false;
555   numColumnsShown = newNumColumns;
556   // We add an extra pixel because rounding errors on different zooms can
557   // make the width shorter than it should be.
558   var tilesContainerWidth = Math.ceil(numColumnsShown * tileRequiredWidth) + 1;
559   $(IDS.TILES).style.width = tilesContainerWidth + 'px';
560   if (fakebox) {
561     // -2 to account for border.
562     var fakeboxWidth = (tilesContainerWidth - NTP_DESIGN.tileMargin - 2);
563     fakeboxWidth += NTP_DESIGN.fakeboxWingSize * 2;
564     fakebox.style.width = fakeboxWidth + 'px';
565   }
566   return true;
571  * Resizes elements because the number of tile columns may need to change in
572  * response to resizing. Also shows or hides extra tiles tiles according to the
573  * new width of the page.
574  */
575 function onResize() {
576   updateContentWidth();
577   $('mv-single').contentWindow.postMessage(
578     {cmd: 'tilesVisible', maxVisible: numColumnsShown * NUM_ROWS}, '*');
583  * Handles new input by disposing the NTP, according to where the input was
584  * entered.
585  */
586 function onInputStart() {
587   if (fakebox && isFakeboxFocused()) {
588     setFakeboxFocus(false);
589     setFakeboxDragFocus(false);
590     disposeNtp(true);
591   } else if (!isFakeboxFocused()) {
592     disposeNtp(false);
593   }
598  * Disposes the NTP, according to where the input was entered.
599  * @param {boolean} wasFakeboxInput True if the input was in the fakebox.
600  */
601 function disposeNtp(wasFakeboxInput) {
602   var behavior = wasFakeboxInput ? fakeboxInputBehavior : omniboxInputBehavior;
603   if (behavior == NTP_DISPOSE_STATE.DISABLE_FAKEBOX)
604     setFakeboxActive(false);
605   else if (behavior == NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO)
606     setFakeboxAndLogoVisibility(false);
611  * Restores the NTP (re-enables the fakebox and unhides the logo.)
612  */
613 function restoreNtp() {
614   setFakeboxActive(true);
615   setFakeboxAndLogoVisibility(true);
620  * @param {boolean} focus True to focus the fakebox.
621  */
622 function setFakeboxFocus(focus) {
623   document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus);
627  * @param {boolean} focus True to show a dragging focus to the fakebox.
628  */
629 function setFakeboxDragFocus(focus) {
630   document.body.classList.toggle(CLASSES.FAKEBOX_DRAG_FOCUS, focus);
634  * @return {boolean} True if the fakebox has focus.
635  */
636 function isFakeboxFocused() {
637   return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS) ||
638       document.body.classList.contains(CLASSES.FAKEBOX_DRAG_FOCUS);
643  * @param {boolean} enable True to enable the fakebox.
644  */
645 function setFakeboxActive(enable) {
646   document.body.classList.toggle(CLASSES.FAKEBOX_DISABLE, !enable);
651  * @param {!Event} event The click event.
652  * @return {boolean} True if the click occurred in an enabled fakebox.
653  */
654 function isFakeboxClick(event) {
655   return fakebox.contains(event.target) &&
656       !document.body.classList.contains(CLASSES.FAKEBOX_DISABLE);
661  * @param {boolean} show True to show the fakebox and logo.
662  */
663 function setFakeboxAndLogoVisibility(show) {
664   document.body.classList.toggle(CLASSES.HIDE_FAKEBOX_AND_LOGO, !show);
669  * Shortcut for document.getElementById.
670  * @param {string} id of the element.
671  * @return {HTMLElement} with the id.
672  */
673 function $(id) {
674   return document.getElementById(id);
679  * Utility function which creates an element with an optional classname and
680  * appends it to the specified parent.
681  * @param {Element} parent The parent to append the new element.
682  * @param {string} name The name of the new element.
683  * @param {string=} opt_class The optional classname of the new element.
684  * @return {Element} The new element.
685  */
686 function createAndAppendElement(parent, name, opt_class) {
687   var child = document.createElement(name);
688   if (opt_class)
689     child.classList.add(opt_class);
690   parent.appendChild(child);
691   return child;
696  * @param {!Element} element The element to register the handler for.
697  * @param {number} keycode The keycode of the key to register.
698  * @param {!Function} handler The key handler to register.
699  */
700 function registerKeyHandler(element, keycode, handler) {
701   element.addEventListener('keydown', function(event) {
702     if (event.keyCode == keycode)
703       handler(event);
704   });
709  * @return {Object} the handle to the embeddedSearch API.
710  */
711 function getEmbeddedSearchApiHandle() {
712   if (window.cideb)
713     return window.cideb;
714   if (window.chrome && window.chrome.embeddedSearch)
715     return window.chrome.embeddedSearch;
716   return null;
721  * Event handler for the focus changed and blacklist messages on link elements.
722  * Used to toggle visual treatment on the tiles (depending on the message).
723  * @param {Event} event Event received.
724  */
725 function handlePostMessage(event) {
726   var cmd = event.data.cmd;
727   var args = event.data;
728   if (cmd == 'tileBlacklisted') {
729     showNotification();
730     lastBlacklistedTile = args.tid;
732     ntpApiHandle.deleteMostVisitedItem(args.tid);
733   }
738  * Prepares the New Tab Page by adding listeners, rendering the current
739  * theme, the most visited pages section, and Google-specific elements for a
740  * Google-provided page.
741  */
742 function init() {
743   notification = $(IDS.NOTIFICATION);
744   attribution = $(IDS.ATTRIBUTION);
745   ntpContents = $(IDS.NTP_CONTENTS);
747   if (configData.isGooglePage) {
748     var logo = document.createElement('div');
749     logo.id = IDS.LOGO;
750     logo.title = 'Google';
752     fakebox = document.createElement('div');
753     fakebox.id = IDS.FAKEBOX;
754     var fakeboxHtml = [];
755     fakeboxHtml.push('<div id="' + IDS.FAKEBOX_TEXT + '"></div>');
756     fakeboxHtml.push('<input id="' + IDS.FAKEBOX_INPUT +
757         '" autocomplete="off" tabindex="-1" type="url" aria-hidden="true">');
758     fakeboxHtml.push('<div id="cursor"></div>');
759     fakebox.innerHTML = fakeboxHtml.join('');
761     ntpContents.insertBefore(fakebox, ntpContents.firstChild);
762     ntpContents.insertBefore(logo, ntpContents.firstChild);
763   } else {
764     document.body.classList.add(CLASSES.NON_GOOGLE_PAGE);
765   }
767   // Modify design for experimental icon NTP, if specified.
768   if (configData.useIcons)
769     modifyNtpDesignForIcons();
770   document.querySelector('#ntp-contents').classList.add(NTP_DESIGN.mainClass);
772   // Hide notifications after fade out, so we can't focus on links via keyboard.
773   notification.addEventListener('webkitTransitionEnd', hideNotification);
775   var notificationMessage = $(IDS.NOTIFICATION_MESSAGE);
776   notificationMessage.textContent =
777       configData.translatedStrings.thumbnailRemovedNotification;
779   var undoLink = $(IDS.UNDO_LINK);
780   undoLink.addEventListener('click', onUndo);
781   registerKeyHandler(undoLink, KEYCODE.ENTER, onUndo);
782   undoLink.textContent = configData.translatedStrings.undoThumbnailRemove;
784   var restoreAllLink = $(IDS.RESTORE_ALL_LINK);
785   restoreAllLink.addEventListener('click', onRestoreAll);
786   registerKeyHandler(restoreAllLink, KEYCODE.ENTER, onUndo);
787   restoreAllLink.textContent =
788       configData.translatedStrings.restoreThumbnailsShort;
790   $(IDS.ATTRIBUTION_TEXT).textContent =
791       configData.translatedStrings.attributionIntro;
793   var notificationCloseButton = $(IDS.NOTIFICATION_CLOSE_BUTTON);
794   createAndAppendElement(
795       notificationCloseButton, 'div', CLASSES.BLACKLIST_BUTTON_INNER);
796   notificationCloseButton.addEventListener('click', hideNotification);
798   window.addEventListener('resize', onResize);
799   updateContentWidth();
801   var topLevelHandle = getEmbeddedSearchApiHandle();
803   ntpApiHandle = topLevelHandle.newTabPage;
804   ntpApiHandle.onthemechange = onThemeChange;
805   ntpApiHandle.onmostvisitedchange = onMostVisitedChange;
807   ntpApiHandle.oninputstart = onInputStart;
808   ntpApiHandle.oninputcancel = restoreNtp;
810   if (ntpApiHandle.isInputInProgress)
811     onInputStart();
813   searchboxApiHandle = topLevelHandle.searchBox;
815   if (fakebox) {
816     // Listener for updating the key capture state.
817     document.body.onmousedown = function(event) {
818       if (isFakeboxClick(event))
819         searchboxApiHandle.startCapturingKeyStrokes();
820       else if (isFakeboxFocused())
821         searchboxApiHandle.stopCapturingKeyStrokes();
822     };
823     searchboxApiHandle.onkeycapturechange = function() {
824       setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
825     };
826     var inputbox = $(IDS.FAKEBOX_INPUT);
827     if (inputbox) {
828       inputbox.onpaste = function(event) {
829         event.preventDefault();
830         // Send pasted text to Omnibox.
831         var text = event.clipboardData.getData('text/plain');
832         if (text)
833           searchboxApiHandle.paste(text);
834       };
835       inputbox.ondrop = function(event) {
836         event.preventDefault();
837         var text = event.dataTransfer.getData('text/plain');
838         if (text) {
839           searchboxApiHandle.paste(text);
840         }
841         setFakeboxDragFocus(false);
842       };
843       inputbox.ondragenter = function() {
844         setFakeboxDragFocus(true);
845       };
846       inputbox.ondragleave = function() {
847         setFakeboxDragFocus(false);
848       };
849     }
851     // Update the fakebox style to match the current key capturing state.
852     setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
853   }
855   if (searchboxApiHandle.rtl) {
856     $(IDS.NOTIFICATION).dir = 'rtl';
857     // Grabbing the root HTML element.
858     document.documentElement.setAttribute('dir', 'rtl');
859     // Add class for setting alignments based on language directionality.
860     document.documentElement.classList.add(CLASSES.RTL);
861   }
863   var iframe = document.createElement('iframe');
864   // Change the order of tabbing the page to start with NTP tiles.
865   iframe.setAttribute('tabindex', '1');
866   iframe.id = 'mv-single';
868   var args = [];
870   if (searchboxApiHandle.rtl)
871     args.push('rtl=1');
872   if (window.configData.useIcons)
873     args.push('icons=1');
874   if (NTP_DESIGN.numTitleLines > 1)
875     args.push('ntl=' + NTP_DESIGN.numTitleLines);
877   args.push('removeTooltip=' +
878       encodeURIComponent(configData.translatedStrings.removeThumbnailTooltip));
880   iframe.src = '//most-visited/single.html?' + args.join('&');
881   $(IDS.TILES).appendChild(iframe);
883   iframe.onload = function() {
884     reloadTiles();
885     renderTheme();
886   };
888   window.addEventListener('message', handlePostMessage);
893  * Binds event listeners.
894  */
895 function listen() {
896   document.addEventListener('DOMContentLoaded', init);
899 return {
900   init: init,
901   listen: listen
905 if (!window.localNTPUnitTest) {
906   LocalNTP().listen();