Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / resources / local_ntp / local_ntp.js
blobd91f0c575521d9470fc5fa397f8328007858cba3
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: 156,
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   // Vertically centers the most visited section for a non-Google provided page.
124   NON_GOOGLE_PAGE: 'non-google-page',
125   RTL: 'rtl'  // Right-to-left language text.
130  * Enum for HTML element ids.
131  * @enum {string}
132  * @const
133  */
134 var IDS = {
135   ATTRIBUTION: 'attribution',
136   ATTRIBUTION_TEXT: 'attribution-text',
137   CUSTOM_THEME_STYLE: 'ct-style',
138   FAKEBOX: 'fakebox',
139   FAKEBOX_INPUT: 'fakebox-input',
140   FAKEBOX_TEXT: 'fakebox-text',
141   LOGO: 'logo',
142   NOTIFICATION: 'mv-notice',
143   NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x',
144   NOTIFICATION_MESSAGE: 'mv-msg',
145   NTP_CONTENTS: 'ntp-contents',
146   RESTORE_ALL_LINK: 'mv-restore',
147   TILES: 'mv-tiles',
148   UNDO_LINK: 'mv-undo'
153  * Enum for keycodes.
154  * @enum {number}
155  * @const
156  */
157 var KEYCODE = {
158   ENTER: 13
163  * Enum for the state of the NTP when it is disposed.
164  * @enum {number}
165  * @const
166  */
167 var NTP_DISPOSE_STATE = {
168   NONE: 0,  // Preserve the NTP appearance and functionality
169   DISABLE_FAKEBOX: 1,
170   HIDE_FAKEBOX_AND_LOGO: 2
175  * The notification displayed when a page is blacklisted.
176  * @type {Element}
177  */
178 var notification;
182  * The container for the theme attribution.
183  * @type {Element}
184  */
185 var attribution;
189  * The "fakebox" - an input field that looks like a regular searchbox.  When it
190  * is focused, any text the user types goes directly into the omnibox.
191  * @type {Element}
192  */
193 var fakebox;
197  * The container for NTP elements.
198  * @type {Element}
199  */
200 var ntpContents;
204  * The last blacklisted tile rid if any, which by definition should not be
205  * filler.
206  * @type {?number}
207  */
208 var lastBlacklistedTile = null;
212  * Current number of tiles columns shown based on the window width, including
213  * those that just contain filler.
214  * @type {number}
215  */
216 var numColumnsShown = 0;
220  * The browser embeddedSearch.newTabPage object.
221  * @type {Object}
222  */
223 var ntpApiHandle;
227  * The browser embeddedSearch.searchBox object.
228  * @type {Object}
229  */
230 var searchboxApiHandle;
234  * The state of the NTP when a query is entered into the Omnibox.
235  * @type {NTP_DISPOSE_STATE}
236  */
237 var omniboxInputBehavior = NTP_DISPOSE_STATE.NONE;
241  * The state of the NTP when a query is entered into the Fakebox.
242  * @type {NTP_DISPOSE_STATE}
243  */
244 var fakeboxInputBehavior = NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO;
247 /** @type {number} @const */
248 var MAX_NUM_TILES_TO_SHOW = 8;
251 /** @type {number} @const */
252 var MIN_NUM_COLUMNS = 2;
255 /** @type {number} @const */
256 var MAX_NUM_COLUMNS = 4;
259 /** @type {number} @const */
260 var NUM_ROWS = 2;
264  * Minimum total padding to give to the left and right of the most visited
265  * section. Used to determine how many tiles to show.
266  * @type {number}
267  * @const
268  */
269 var MIN_TOTAL_HORIZONTAL_PADDING = 200;
273  * Heuristic to determine whether a theme should be considered to be dark, so
274  * the colors of various UI elements can be adjusted.
275  * @param {ThemeBackgroundInfo|undefined} info Theme background information.
276  * @return {boolean} Whether the theme is dark.
277  * @private
278  */
279 function getIsThemeDark(info) {
280   if (!info)
281     return false;
282   // Heuristic: light text implies dark theme.
283   var rgba = info.textColorRgba;
284   var luminance = 0.3 * rgba[0] + 0.59 * rgba[1] + 0.11 * rgba[2];
285   return luminance >= 128;
290  * Updates the NTP based on the current theme.
291  * @private
292  */
293 function renderTheme() {
294   var fakeboxText = $(IDS.FAKEBOX_TEXT);
295   if (fakeboxText) {
296     fakeboxText.innerHTML = '';
297     if (configData.translatedStrings.searchboxPlaceholder) {
298       fakeboxText.textContent =
299           configData.translatedStrings.searchboxPlaceholder;
300     }
301   }
303   var info = ntpApiHandle.themeBackgroundInfo;
304   var isThemeDark = getIsThemeDark(info);
305   ntpContents.classList.toggle(CLASSES.DARK, isThemeDark);
306   if (!info) {
307     return;
308   }
310   var background = [convertToRGBAColor(info.backgroundColorRgba),
311                     info.imageUrl,
312                     info.imageTiling,
313                     info.imageHorizontalAlignment,
314                     info.imageVerticalAlignment].join(' ').trim();
316   document.body.style.background = background;
317   document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo);
318   updateThemeAttribution(info.attributionUrl);
319   setCustomThemeStyle(info);
321   var themeinfo = {cmd: 'updateTheme'};
322   if (!info.usingDefaultTheme) {
323     themeinfo.tileBorderColor = convertToRGBAColor(info.sectionBorderColorRgba);
324     themeinfo.tileHoverBorderColor = convertToRGBAColor(info.headerColorRgba);
325   }
326   themeinfo.isThemeDark = isThemeDark;
328   var titleColor = NTP_DESIGN.titleColor;
329   if (!info.usingDefaultTheme && info.textColorRgba) {
330     titleColor = info.textColorRgba;
331   } else if (isThemeDark) {
332     titleColor = NTP_DESIGN.titleColorAgainstDark;
333   }
334   themeinfo.tileTitleColor = convertToRGBAColor(titleColor);
336   $('mv-single').contentWindow.postMessage(themeinfo, '*');
341  * Updates the NTP based on the current theme, then rerenders all tiles.
342  * @private
343  */
344 function onThemeChange() {
345   renderTheme();
350  * Updates the NTP style according to theme.
351  * @param {Object=} opt_themeInfo The information about the theme. If it is
352  * omitted the style will be reverted to the default.
353  * @private
354  */
355 function setCustomThemeStyle(opt_themeInfo) {
356   var customStyleElement = $(IDS.CUSTOM_THEME_STYLE);
357   var head = document.head;
358   if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) {
359     ntpContents.classList.remove(CLASSES.DEFAULT_THEME);
360     var themeStyle =
361       '#attribution {' +
362       '  color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
363       '}' +
364       '#mv-msg {' +
365       '  color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' +
366       '}' +
367       '#mv-notice-links span {' +
368       '  color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
369       '}' +
370       '#mv-notice-x {' +
371       '  -webkit-filter: drop-shadow(0 0 0 ' +
372           convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' +
373       '}' +
374       '.mv-page-ready .mv-mask {' +
375       '  border: 1px solid ' +
376           convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' +
377       '}' +
378       '.mv-page-ready:hover .mv-mask, .mv-page-ready .mv-focused ~ .mv-mask {' +
379       '  border-color: ' +
380           convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' +
381       '}';
383     if (customStyleElement) {
384       customStyleElement.textContent = themeStyle;
385     } else {
386       customStyleElement = document.createElement('style');
387       customStyleElement.type = 'text/css';
388       customStyleElement.id = IDS.CUSTOM_THEME_STYLE;
389       customStyleElement.textContent = themeStyle;
390       head.appendChild(customStyleElement);
391     }
393   } else {
394     ntpContents.classList.add(CLASSES.DEFAULT_THEME);
395     if (customStyleElement)
396       head.removeChild(customStyleElement);
397   }
402  * Renders the attribution if the URL is present, otherwise hides it.
403  * @param {string} url The URL of the attribution image, if any.
404  * @private
405  */
406 function updateThemeAttribution(url) {
407   if (!url) {
408     setAttributionVisibility_(false);
409     return;
410   }
412   var attributionImage = attribution.querySelector('img');
413   if (!attributionImage) {
414     attributionImage = new Image();
415     attribution.appendChild(attributionImage);
416   }
417   attributionImage.style.content = url;
418   setAttributionVisibility_(true);
423  * Sets the visibility of the theme attribution.
424  * @param {boolean} show True to show the attribution.
425  * @private
426  */
427 function setAttributionVisibility_(show) {
428   if (attribution) {
429     attribution.style.display = show ? '' : 'none';
430   }
434  /**
435  * Converts an Array of color components into RRGGBBAA format.
436  * @param {Array<number>} color Array of rgba color components.
437  * @return {string} Color string in RRGGBBAA format.
438  * @private
439  */
440 function convertToRRGGBBAAColor(color) {
441   return color.map(function(t) {
442     return ('0' + t.toString(16)).slice(-2);  // To 2-digit, 0-padded hex.
443   }).join('');
447  /**
448  * Converts an Array of color components into RGBA format "rgba(R,G,B,A)".
449  * @param {Array<number>} color Array of rgba color components.
450  * @return {string} CSS color in RGBA format.
451  * @private
452  */
453 function convertToRGBAColor(color) {
454   return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' +
455                     color[3] / 255 + ')';
460  * Called when page data change.
461  */
462 function onMostVisitedChange() {
463   reloadTiles();
468  * Fetches new data, creates, and renders tiles.
469  */
470 function reloadTiles() {
471   var pages = ntpApiHandle.mostVisited;
472   var cmds = [];
473   for (var i = 0; i < Math.min(MAX_NUM_TILES_TO_SHOW, pages.length); ++i) {
474     cmds.push({cmd: 'tile', rid: pages[i].rid});
475   }
476   cmds.push({cmd: 'show', maxVisible: numColumnsShown * NUM_ROWS});
478   $('mv-single').contentWindow.postMessage(cmds, '*');
483  * Shows the blacklist notification and triggers a delay to hide it.
484  */
485 function showNotification() {
486   notification.classList.remove(CLASSES.HIDE_NOTIFICATION);
487   notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
488   notification.scrollTop;
489   notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION);
494  * Hides the blacklist notification.
495  */
496 function hideNotification() {
497   notification.classList.add(CLASSES.HIDE_NOTIFICATION);
498   notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
503  * Handles a click on the notification undo link by hiding the notification and
504  * informing Chrome.
505  */
506 function onUndo() {
507   hideNotification();
508   if (lastBlacklistedTile != null) {
509     ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedTile);
510   }
515  * Handles a click on the restore all notification link by hiding the
516  * notification and informing Chrome.
517  */
518 function onRestoreAll() {
519   hideNotification();
520   ntpApiHandle.undoAllMostVisitedDeletions();
525  * Recomputes the number of tile columns, and width of various contents based
526  * on the width of the window.
527  * @return {boolean} Whether the number of tile columns has changed.
528  */
529 function updateContentWidth() {
530   var tileRequiredWidth = NTP_DESIGN.tileWidth + NTP_DESIGN.tileMargin;
531   // If innerWidth is zero, then use the maximum snap size.
532   var maxSnapSize = MAX_NUM_COLUMNS * tileRequiredWidth -
533       NTP_DESIGN.tileMargin + MIN_TOTAL_HORIZONTAL_PADDING;
534   var innerWidth = window.innerWidth || maxSnapSize;
535   // Each tile has left and right margins that sum to NTP_DESIGN.tileMargin.
536   var availableWidth = innerWidth + NTP_DESIGN.tileMargin -
537       NTP_DESIGN.fakeboxWingSize * 2 - MIN_TOTAL_HORIZONTAL_PADDING;
538   var newNumColumns = Math.floor(availableWidth / tileRequiredWidth);
539   if (newNumColumns < MIN_NUM_COLUMNS)
540     newNumColumns = MIN_NUM_COLUMNS;
541   else if (newNumColumns > MAX_NUM_COLUMNS)
542     newNumColumns = MAX_NUM_COLUMNS;
544   if (numColumnsShown === newNumColumns)
545     return false;
547   numColumnsShown = newNumColumns;
548   var tilesContainerWidth = numColumnsShown * tileRequiredWidth;
549   $(IDS.TILES).style.width = tilesContainerWidth + 'px';
550   if (fakebox) {
551     // -2 to account for border.
552     var fakeboxWidth = (tilesContainerWidth - NTP_DESIGN.tileMargin - 2);
553     fakeboxWidth += NTP_DESIGN.fakeboxWingSize * 2;
554     fakebox.style.width = fakeboxWidth + 'px';
555   }
556   return true;
561  * Resizes elements because the number of tile columns may need to change in
562  * response to resizing. Also shows or hides extra tiles tiles according to the
563  * new width of the page.
564  */
565 function onResize() {
566   updateContentWidth();
567   $('mv-single').contentWindow.postMessage(
568     {cmd: 'tilesVisible', maxVisible: numColumnsShown * NUM_ROWS}, '*');
573  * Handles new input by disposing the NTP, according to where the input was
574  * entered.
575  */
576 function onInputStart() {
577   if (fakebox && isFakeboxFocused()) {
578     setFakeboxFocus(false);
579     setFakeboxDragFocus(false);
580     disposeNtp(true);
581   } else if (!isFakeboxFocused()) {
582     disposeNtp(false);
583   }
588  * Disposes the NTP, according to where the input was entered.
589  * @param {boolean} wasFakeboxInput True if the input was in the fakebox.
590  */
591 function disposeNtp(wasFakeboxInput) {
592   var behavior = wasFakeboxInput ? fakeboxInputBehavior : omniboxInputBehavior;
593   if (behavior == NTP_DISPOSE_STATE.DISABLE_FAKEBOX)
594     setFakeboxActive(false);
595   else if (behavior == NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO)
596     setFakeboxAndLogoVisibility(false);
601  * Restores the NTP (re-enables the fakebox and unhides the logo.)
602  */
603 function restoreNtp() {
604   setFakeboxActive(true);
605   setFakeboxAndLogoVisibility(true);
610  * @param {boolean} focus True to focus the fakebox.
611  */
612 function setFakeboxFocus(focus) {
613   document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus);
617  * @param {boolean} focus True to show a dragging focus to the fakebox.
618  */
619 function setFakeboxDragFocus(focus) {
620   document.body.classList.toggle(CLASSES.FAKEBOX_DRAG_FOCUS, focus);
624  * @return {boolean} True if the fakebox has focus.
625  */
626 function isFakeboxFocused() {
627   return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS) ||
628       document.body.classList.contains(CLASSES.FAKEBOX_DRAG_FOCUS);
633  * @param {boolean} enable True to enable the fakebox.
634  */
635 function setFakeboxActive(enable) {
636   document.body.classList.toggle(CLASSES.FAKEBOX_DISABLE, !enable);
641  * @param {!Event} event The click event.
642  * @return {boolean} True if the click occurred in an enabled fakebox.
643  */
644 function isFakeboxClick(event) {
645   return fakebox.contains(event.target) &&
646       !document.body.classList.contains(CLASSES.FAKEBOX_DISABLE);
651  * @param {boolean} show True to show the fakebox and logo.
652  */
653 function setFakeboxAndLogoVisibility(show) {
654   document.body.classList.toggle(CLASSES.HIDE_FAKEBOX_AND_LOGO, !show);
659  * Shortcut for document.getElementById.
660  * @param {string} id of the element.
661  * @return {HTMLElement} with the id.
662  */
663 function $(id) {
664   return document.getElementById(id);
669  * Utility function which creates an element with an optional classname and
670  * appends it to the specified parent.
671  * @param {Element} parent The parent to append the new element.
672  * @param {string} name The name of the new element.
673  * @param {string=} opt_class The optional classname of the new element.
674  * @return {Element} The new element.
675  */
676 function createAndAppendElement(parent, name, opt_class) {
677   var child = document.createElement(name);
678   if (opt_class)
679     child.classList.add(opt_class);
680   parent.appendChild(child);
681   return child;
686  * @param {!Element} element The element to register the handler for.
687  * @param {number} keycode The keycode of the key to register.
688  * @param {!Function} handler The key handler to register.
689  */
690 function registerKeyHandler(element, keycode, handler) {
691   element.addEventListener('keydown', function(event) {
692     if (event.keyCode == keycode)
693       handler(event);
694   });
699  * @return {Object} the handle to the embeddedSearch API.
700  */
701 function getEmbeddedSearchApiHandle() {
702   if (window.cideb)
703     return window.cideb;
704   if (window.chrome && window.chrome.embeddedSearch)
705     return window.chrome.embeddedSearch;
706   return null;
711  * Event handler for the focus changed and blacklist messages on link elements.
712  * Used to toggle visual treatment on the tiles (depending on the message).
713  * @param {Event} event Event received.
714  */
715 function handlePostMessage(event) {
716   var cmd = event.data.cmd;
717   var args = event.data;
718   if (cmd == 'tileBlacklisted') {
719     showNotification();
720     lastBlacklistedTile = args.tid;
722     ntpApiHandle.deleteMostVisitedItem(args.tid);
723   }
728  * Prepares the New Tab Page by adding listeners, rendering the current
729  * theme, the most visited pages section, and Google-specific elements for a
730  * Google-provided page.
731  */
732 function init() {
733   notification = $(IDS.NOTIFICATION);
734   attribution = $(IDS.ATTRIBUTION);
735   ntpContents = $(IDS.NTP_CONTENTS);
737   if (configData.isGooglePage) {
738     var logo = document.createElement('div');
739     logo.id = IDS.LOGO;
740     logo.title = 'Google';
742     fakebox = document.createElement('div');
743     fakebox.id = IDS.FAKEBOX;
744     var fakeboxHtml = [];
745     fakeboxHtml.push('<div id="' + IDS.FAKEBOX_TEXT + '"></div>');
746     fakeboxHtml.push('<input id="' + IDS.FAKEBOX_INPUT +
747         '" autocomplete="off" tabindex="-1" type="url" aria-hidden="true">');
748     fakeboxHtml.push('<div id="cursor"></div>');
749     fakebox.innerHTML = fakeboxHtml.join('');
751     ntpContents.insertBefore(fakebox, ntpContents.firstChild);
752     ntpContents.insertBefore(logo, ntpContents.firstChild);
753   } else {
754     document.body.classList.add(CLASSES.NON_GOOGLE_PAGE);
755   }
757   // Modify design for experimental icon NTP, if specified.
758   if (configData.useIcons)
759     modifyNtpDesignForIcons();
760   document.querySelector('#ntp-contents').classList.add(NTP_DESIGN.mainClass);
762   // Hide notifications after fade out, so we can't focus on links via keyboard.
763   notification.addEventListener('webkitTransitionEnd', hideNotification);
765   var notificationMessage = $(IDS.NOTIFICATION_MESSAGE);
766   notificationMessage.textContent =
767       configData.translatedStrings.thumbnailRemovedNotification;
769   var undoLink = $(IDS.UNDO_LINK);
770   undoLink.addEventListener('click', onUndo);
771   registerKeyHandler(undoLink, KEYCODE.ENTER, onUndo);
772   undoLink.textContent = configData.translatedStrings.undoThumbnailRemove;
774   var restoreAllLink = $(IDS.RESTORE_ALL_LINK);
775   restoreAllLink.addEventListener('click', onRestoreAll);
776   registerKeyHandler(restoreAllLink, KEYCODE.ENTER, onUndo);
777   restoreAllLink.textContent =
778       configData.translatedStrings.restoreThumbnailsShort;
780   $(IDS.ATTRIBUTION_TEXT).textContent =
781       configData.translatedStrings.attributionIntro;
783   var notificationCloseButton = $(IDS.NOTIFICATION_CLOSE_BUTTON);
784   createAndAppendElement(
785       notificationCloseButton, 'div', CLASSES.BLACKLIST_BUTTON_INNER);
786   notificationCloseButton.addEventListener('click', hideNotification);
788   window.addEventListener('resize', onResize);
789   updateContentWidth();
791   var topLevelHandle = getEmbeddedSearchApiHandle();
793   ntpApiHandle = topLevelHandle.newTabPage;
794   ntpApiHandle.onthemechange = onThemeChange;
795   ntpApiHandle.onmostvisitedchange = onMostVisitedChange;
797   ntpApiHandle.oninputstart = onInputStart;
798   ntpApiHandle.oninputcancel = restoreNtp;
800   if (ntpApiHandle.isInputInProgress)
801     onInputStart();
803   searchboxApiHandle = topLevelHandle.searchBox;
805   if (fakebox) {
806     // Listener for updating the key capture state.
807     document.body.onmousedown = function(event) {
808       if (isFakeboxClick(event))
809         searchboxApiHandle.startCapturingKeyStrokes();
810       else if (isFakeboxFocused())
811         searchboxApiHandle.stopCapturingKeyStrokes();
812     };
813     searchboxApiHandle.onkeycapturechange = function() {
814       setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
815     };
816     var inputbox = $(IDS.FAKEBOX_INPUT);
817     if (inputbox) {
818       inputbox.onpaste = function(event) {
819         event.preventDefault();
820         // Send pasted text to Omnibox.
821         var text = event.clipboardData.getData('text/plain');
822         if (text)
823           searchboxApiHandle.paste(text);
824       };
825       inputbox.ondrop = function(event) {
826         event.preventDefault();
827         var text = event.dataTransfer.getData('text/plain');
828         if (text) {
829           searchboxApiHandle.paste(text);
830         }
831         setFakeboxDragFocus(false);
832       };
833       inputbox.ondragenter = function() {
834         setFakeboxDragFocus(true);
835       };
836       inputbox.ondragleave = function() {
837         setFakeboxDragFocus(false);
838       };
839     }
841     // Update the fakebox style to match the current key capturing state.
842     setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
843   }
845   if (searchboxApiHandle.rtl) {
846     $(IDS.NOTIFICATION).dir = 'rtl';
847     // Grabbing the root HTML element.
848     document.documentElement.setAttribute('dir', 'rtl');
849     // Add class for setting alignments based on language directionality.
850     document.documentElement.classList.add(CLASSES.RTL);
851   }
853   var iframe = document.createElement('iframe');
854   // Change the order of tabbing the page to start with NTP tiles.
855   iframe.setAttribute('tabindex', '1');
856   iframe.id = 'mv-single';
858   var args = [];
860   if (searchboxApiHandle.rtl)
861     args.push('rtl=1');
862   if (window.configData.useIcons)
863     args.push('icons=1');
864   if (NTP_DESIGN.numTitleLines > 1)
865     args.push('ntl=' + NTP_DESIGN.numTitleLines);
867   args.push('removeTooltip=' +
868       encodeURIComponent(configData.translatedStrings.removeThumbnailTooltip));
870   iframe.src = '//most-visited/single.html?' + args.join('&');
871   $(IDS.TILES).appendChild(iframe);
873   iframe.onload = function() {
874     reloadTiles();
875     renderTheme();
876   };
878   window.addEventListener('message', handlePostMessage);
883  * Binds event listeners.
884  */
885 function listen() {
886   document.addEventListener('DOMContentLoaded', init);
889 return {
890   init: init,
891   listen: listen
895 if (!window.localNTPUnitTest) {
896   LocalNTP().listen();