1 // Copyright 2013 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.
7 * @fileoverview The local InstantExtended NTP.
11 * Controls rendering the new tab page for InstantExtended.
12 * @return {Object} A limited interface for testing the local NTP.
15 <include src="../../../../ui/webui/resources/js/assert.js">
20 * Enum for classnames.
25 ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme
26 BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation
27 BLACKLIST_BUTTON: 'mv-x',
28 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide',
29 FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive
30 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox
31 // Applies drag focus style to the fakebox
32 FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused',
33 FAVICON: 'mv-favicon',
34 HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation
35 HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo',
36 HIDE_NOTIFICATION: 'mv-notice-hide',
37 // Vertically centers the most visited section for a non-Google provided page.
38 NON_GOOGLE_PAGE: 'non-google-page',
39 PAGE: 'mv-page', // page tiles
40 PAGE_READY: 'mv-page-ready', // page tile when ready
41 ROW: 'mv-row', // tile row
42 RTL: 'rtl', // Right-to-left language text.
43 THUMBNAIL: 'mv-thumb',
44 THUMBNAIL_MASK: 'mv-mask',
51 * Enum for HTML element ids.
56 ATTRIBUTION: 'attribution',
57 ATTRIBUTION_TEXT: 'attribution-text',
58 CUSTOM_THEME_STYLE: 'ct-style',
60 FAKEBOX_INPUT: 'fakebox-input',
62 NOTIFICATION: 'mv-notice',
63 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x',
64 NOTIFICATION_MESSAGE: 'mv-msg',
65 NTP_CONTENTS: 'ntp-contents',
66 RESTORE_ALL_LINK: 'mv-restore',
84 * Enum for the state of the NTP when it is disposed.
88 var NTP_DISPOSE_STATE = {
89 NONE: 0, // Preserve the NTP appearance and functionality
91 HIDE_FAKEBOX_AND_LOGO: 2
96 * The JavaScript button event value for a middle click.
100 var MIDDLE_MOUSE_BUTTON = 1;
104 * Possible behaviors for navigateContentWindow.
107 var WindowOpenDisposition = {
109 NEW_BACKGROUND_TAB: 2
114 * The container for the tile elements.
121 * The notification displayed when a page is blacklisted.
128 * The container for the theme attribution.
135 * The "fakebox" - an input field that looks like a regular searchbox. When it
136 * is focused, any text the user types goes directly into the omnibox.
143 * The container for NTP elements.
150 * The array of rendered tiles, ordered by appearance.
151 * @type {!Array.<Tile>}
157 * The last blacklisted tile if any, which by definition should not be filler.
160 var lastBlacklistedTile = null;
164 * True if a page has been blacklisted and we're waiting on the
165 * onmostvisitedchange callback. See onMostVisitedChange() for how this
169 var isBlacklisting = false;
173 * Current number of tiles columns shown based on the window width, including
174 * those that just contain filler.
177 var numColumnsShown = 0;
181 * True if the user initiated the current most visited change and false
185 var userInitiatedMostVisitedChange = false;
189 * The browser embeddedSearch.newTabPage object.
196 * The browser embeddedSearch.searchBox object.
199 var searchboxApiHandle;
203 * The state of the NTP when a query is entered into the Omnibox.
204 * @type {NTP_DISPOSE_STATE}
206 var omniboxInputBehavior = NTP_DISPOSE_STATE.NONE;
210 * The state of the NTP when a query is entered into the Fakebox.
211 * @type {NTP_DISPOSE_STATE}
213 var fakeboxInputBehavior = NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO;
217 * Total tile width. Should be equal to mv-tile's width + 2 * border-width.
221 var TILE_WIDTH = 140;
225 * Margin between tiles. Should be equal to mv-tile's -webkit-margin-start.
229 var TILE_MARGIN_START = 20;
232 /** @type {number} @const */
233 var MAX_NUM_TILES_TO_SHOW = 8;
236 /** @type {number} @const */
237 var MIN_NUM_COLUMNS = 2;
240 /** @type {number} @const */
241 var MAX_NUM_COLUMNS = 4;
244 /** @type {number} @const */
249 * Minimum total padding to give to the left and right of the most visited
250 * section. Used to determine how many tiles to show.
254 var MIN_TOTAL_HORIZONTAL_PADDING = 200;
258 * The filename for a most visited iframe src which shows a page title.
262 var MOST_VISITED_TITLE_IFRAME = 'title.html';
266 * The filename for a most visited iframe src which shows a thumbnail image.
270 var MOST_VISITED_THUMBNAIL_IFRAME = 'thumbnail.html';
274 * The hex color for most visited tile elements.
278 var MOST_VISITED_COLOR = '777777';
282 * The font family for most visited tile elements.
286 var MOST_VISITED_FONT_FAMILY = 'arial, sans-serif';
290 * The font size for most visited tile elements.
294 var MOST_VISITED_FONT_SIZE = 11;
298 * Hide most visited tiles for at most this many milliseconds while painting.
302 var MOST_VISITED_PAINT_TIMEOUT_MSEC = 500;
306 * A Tile is either a rendering of a Most Visited page or "filler" used to
307 * pad out the section when not enough pages exist.
309 * @param {Element} elem The element for rendering the tile.
310 * @param {number=} opt_rid The RID for the corresponding Most Visited page.
311 * Should only be left unspecified when creating a filler tile.
314 function Tile(elem, opt_rid) {
315 /** @type {Element} */
318 /** @type {number|undefined} */
324 * Updates the NTP based on the current theme.
327 function onThemeChange() {
328 var info = ntpApiHandle.themeBackgroundInfo;
332 var background = [convertToRGBAColor(info.backgroundColorRgba),
335 info.imageHorizontalAlignment,
336 info.imageVerticalAlignment].join(' ').trim();
337 document.body.style.background = background;
338 document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo);
339 updateThemeAttribution(info.attributionUrl);
340 setCustomThemeStyle(info);
346 * Updates the NTP style according to theme.
347 * @param {Object=} opt_themeInfo The information about the theme. If it is
348 * omitted the style will be reverted to the default.
351 function setCustomThemeStyle(opt_themeInfo) {
352 var customStyleElement = $(IDS.CUSTOM_THEME_STYLE);
353 var head = document.head;
355 if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) {
358 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
361 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' +
363 '#mv-notice-links span {' +
364 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
367 ' -webkit-filter: drop-shadow(0 0 0 ' +
368 convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' +
371 ' border: 1px solid ' +
372 convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' +
374 '.mv-page-ready:hover, .mv-page-ready:focus {' +
376 convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' +
379 if (customStyleElement) {
380 customStyleElement.textContent = themeStyle;
382 customStyleElement = document.createElement('style');
383 customStyleElement.type = 'text/css';
384 customStyleElement.id = IDS.CUSTOM_THEME_STYLE;
385 customStyleElement.textContent = themeStyle;
386 head.appendChild(customStyleElement);
389 } else if (customStyleElement) {
390 head.removeChild(customStyleElement);
396 * Renders the attribution if the URL is present, otherwise hides it.
397 * @param {string} url The URL of the attribution image, if any.
400 function updateThemeAttribution(url) {
402 setAttributionVisibility_(false);
406 var attributionImage = attribution.querySelector('img');
407 if (!attributionImage) {
408 attributionImage = new Image();
409 attribution.appendChild(attributionImage);
411 attributionImage.style.content = url;
412 setAttributionVisibility_(true);
417 * Sets the visibility of the theme attribution.
418 * @param {boolean} show True to show the attribution.
421 function setAttributionVisibility_(show) {
423 attribution.style.display = show ? '' : 'none';
429 * Converts an Array of color components into RGBA format "rgba(R,G,B,A)".
430 * @param {Array.<number>} color Array of rgba color components.
431 * @return {string} CSS color in RGBA format.
434 function convertToRGBAColor(color) {
435 return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' +
436 color[3] / 255 + ')';
441 * Handles a new set of Most Visited page data.
443 function onMostVisitedChange() {
444 var pages = ntpApiHandle.mostVisited;
446 if (isBlacklisting) {
447 // Trigger the blacklist animation and re-render the tiles when it
449 var lastBlacklistedTileElement = lastBlacklistedTile.elem;
450 lastBlacklistedTileElement.addEventListener(
451 'webkitTransitionEnd', blacklistAnimationDone);
452 lastBlacklistedTileElement.classList.add(CLASSES.BLACKLIST);
455 // Otherwise render the tiles using the new data without animation.
457 for (var i = 0; i < MAX_NUM_TILES_TO_SHOW; ++i) {
458 tiles.push(createTile(pages[i], i));
460 if (!userInitiatedMostVisitedChange) {
461 tilesContainer.hidden = true;
462 window.setTimeout(function() {
463 if (tilesContainer) {
464 tilesContainer.hidden = false;
466 }, MOST_VISITED_PAINT_TIMEOUT_MSEC);
474 * Renders the current set of tiles.
476 function renderTiles() {
477 var rows = tilesContainer.children;
478 for (var i = 0; i < rows.length; ++i) {
479 removeChildren(rows[i]);
482 for (var i = 0, length = tiles.length;
483 i < Math.min(length, numColumnsShown * NUM_ROWS); ++i) {
484 rows[Math.floor(i / numColumnsShown)].appendChild(tiles[i].elem);
490 * Shows most visited tiles if all child iframes are loaded, and hides them
493 function updateMostVisitedVisibility() {
494 var iframes = tilesContainer.querySelectorAll('iframe');
496 for (var i = 0, numIframes = iframes.length; i < numIframes; i++) {
497 if (iframes[i].hidden) {
503 tilesContainer.hidden = false;
504 userInitiatedMostVisitedChange = false;
510 * Builds a URL to display a most visited tile component in an iframe.
511 * @param {string} filename The desired most visited component filename.
512 * @param {number} rid The restricted ID.
513 * @param {string} color The text color for text in the iframe.
514 * @param {string} fontFamily The font family for text in the iframe.
515 * @param {number} fontSize The font size for text in the iframe.
516 * @param {number} position The position of the iframe in the UI.
517 * @return {string} An URL to display the most visited component in an iframe.
519 function getMostVisitedIframeUrl(filename, rid, color, fontFamily, fontSize,
521 return 'chrome-search://most-visited/' + encodeURIComponent(filename) + '?' +
522 ['rid=' + encodeURIComponent(rid),
523 'c=' + encodeURIComponent(color),
524 'f=' + encodeURIComponent(fontFamily),
525 'fs=' + encodeURIComponent(fontSize),
526 'pos=' + encodeURIComponent(position)].join('&');
531 * Creates a Tile with the specified page data. If no data is provided, a
532 * filler Tile is created.
533 * @param {Object} page The page data.
534 * @param {number} position The position of the tile.
535 * @return {Tile} The new Tile.
537 function createTile(page, position) {
538 var tileElement = document.createElement('div');
539 tileElement.classList.add(CLASSES.TILE);
543 tileElement.classList.add(CLASSES.PAGE);
545 var navigateFunction = function() {
546 ntpApiHandle.navigateContentWindow(rid);
549 // The click handler for navigating to the page identified by the RID.
550 tileElement.addEventListener('click', navigateFunction);
552 // Make thumbnails tab-accessible.
553 tileElement.setAttribute('tabindex', '1');
554 registerKeyHandler(tileElement, KEYCODE.ENTER, navigateFunction);
556 // The iframe which renders the page title.
557 var titleElement = document.createElement('iframe');
558 titleElement.tabIndex = '-1';
560 // Why iframes have IDs:
562 // On navigating back to the NTP we see several onmostvisitedchange() events
563 // in series with incrementing RIDs. After the first event, a set of iframes
564 // begins loading RIDs n, n+1, ..., n+k-1; after the second event, these get
565 // destroyed and a new set begins loading RIDs n+k, n+k+1, ..., n+2k-1.
566 // Now due to crbug.com/68841, Chrome incorrectly loads the content for the
567 // first set of iframes into the most recent set of iframes.
569 // Giving iframes distinct ids seems to cause some invalidation and prevent
570 // associating the incorrect data.
572 // TODO(jered): Find and fix the root (probably Blink) bug.
574 titleElement.src = getMostVisitedIframeUrl(
575 MOST_VISITED_TITLE_IFRAME, rid, MOST_VISITED_COLOR,
576 MOST_VISITED_FONT_FAMILY, MOST_VISITED_FONT_SIZE, position);
578 // Keep this id here. See comment above.
579 titleElement.id = 'title-' + rid;
580 titleElement.hidden = true;
581 titleElement.onload = function() {
582 titleElement.hidden = false;
583 updateMostVisitedVisibility();
585 titleElement.className = CLASSES.TITLE;
586 tileElement.appendChild(titleElement);
588 // The iframe which renders either a thumbnail or domain element.
589 var thumbnailElement = document.createElement('iframe');
590 thumbnailElement.tabIndex = '-1';
591 thumbnailElement.src = getMostVisitedIframeUrl(
592 MOST_VISITED_THUMBNAIL_IFRAME, rid, MOST_VISITED_COLOR,
593 MOST_VISITED_FONT_FAMILY, MOST_VISITED_FONT_SIZE, position);
595 // Keep this id here. See comment above.
596 thumbnailElement.id = 'thumb-' + rid;
597 thumbnailElement.hidden = true;
598 thumbnailElement.onload = function() {
599 thumbnailElement.hidden = false;
600 tileElement.classList.add(CLASSES.PAGE_READY);
601 updateMostVisitedVisibility();
603 thumbnailElement.className = CLASSES.THUMBNAIL;
604 tileElement.appendChild(thumbnailElement);
606 // A mask to darken the thumbnail on focus.
607 var maskElement = createAndAppendElement(
608 tileElement, 'div', CLASSES.THUMBNAIL_MASK);
610 // The button used to blacklist this page.
611 var blacklistButton = createAndAppendElement(
612 tileElement, 'div', CLASSES.BLACKLIST_BUTTON);
613 var blacklistFunction = generateBlacklistFunction(rid);
614 blacklistButton.addEventListener('click', blacklistFunction);
615 blacklistButton.title = configData.translatedStrings.removeThumbnailTooltip;
617 // When a tile is focused, have delete also blacklist the page.
618 registerKeyHandler(tileElement, KEYCODE.DELETE, blacklistFunction);
620 // The page favicon, if any.
621 var faviconUrl = page.faviconUrl;
623 var favicon = createAndAppendElement(
624 tileElement, 'div', CLASSES.FAVICON);
625 favicon.style.backgroundImage = 'url(' + faviconUrl + ')';
627 return new Tile(tileElement, rid);
629 return new Tile(tileElement);
635 * Generates a function to be called when the page with the corresponding RID
637 * @param {number} rid The RID of the page being blacklisted.
638 * @return {function(Event)} A function which handles the blacklisting of the
639 * page by updating state variables and notifying Chrome.
641 function generateBlacklistFunction(rid) {
643 // Prevent navigation when the page is being blacklisted.
646 userInitiatedMostVisitedChange = true;
647 isBlacklisting = true;
648 tilesContainer.classList.add(CLASSES.HIDE_BLACKLIST_BUTTON);
649 lastBlacklistedTile = getTileByRid(rid);
650 ntpApiHandle.deleteMostVisitedItem(rid);
656 * Shows the blacklist notification and triggers a delay to hide it.
658 function showNotification() {
659 notification.classList.remove(CLASSES.HIDE_NOTIFICATION);
660 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
661 notification.scrollTop;
662 notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION);
667 * Hides the blacklist notification.
669 function hideNotification() {
670 notification.classList.add(CLASSES.HIDE_NOTIFICATION);
675 * Handles the end of the blacklist animation by showing the notification and
676 * re-rendering the new set of tiles.
678 function blacklistAnimationDone() {
680 isBlacklisting = false;
681 tilesContainer.classList.remove(CLASSES.HIDE_BLACKLIST_BUTTON);
682 lastBlacklistedTile.elem.removeEventListener(
683 'webkitTransitionEnd', blacklistAnimationDone);
684 // Need to call explicitly to re-render the tiles, since the initial
685 // onmostvisitedchange issued by the blacklist function only triggered
687 onMostVisitedChange();
692 * Handles a click on the notification undo link by hiding the notification and
696 userInitiatedMostVisitedChange = true;
698 var lastBlacklistedRID = lastBlacklistedTile.rid;
699 if (typeof lastBlacklistedRID != 'undefined')
700 ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedRID);
705 * Handles a click on the restore all notification link by hiding the
706 * notification and informing Chrome.
708 function onRestoreAll() {
709 userInitiatedMostVisitedChange = true;
711 ntpApiHandle.undoAllMostVisitedDeletions();
716 * Re-renders the tiles if the number of columns has changed. As a temporary
717 * fix for crbug/240510, updates the width of the fakebox and most visited tiles
720 function onResize() {
721 // If innerWidth is zero, then use the maximum snap size.
722 var innerWidth = window.innerWidth || 820;
724 // These values should remain in sync with local_ntp.css.
725 // TODO(jeremycho): Delete once the root cause of crbug/240510 is resolved.
726 var setWidths = function(tilesContainerWidth) {
727 tilesContainer.style.width = tilesContainerWidth + 'px';
729 fakebox.style.width = (tilesContainerWidth - 2) + 'px';
731 if (innerWidth >= 820)
733 else if (innerWidth >= 660)
738 var tileRequiredWidth = TILE_WIDTH + TILE_MARGIN_START;
739 // Adds margin-start to the available width to compensate the extra margin
740 // counted above for the first tile (which does not have a margin-start).
741 var availableWidth = innerWidth + TILE_MARGIN_START -
742 MIN_TOTAL_HORIZONTAL_PADDING;
743 var numColumnsToShow = Math.floor(availableWidth / tileRequiredWidth);
744 numColumnsToShow = Math.max(MIN_NUM_COLUMNS,
745 Math.min(MAX_NUM_COLUMNS, numColumnsToShow));
746 if (numColumnsToShow != numColumnsShown) {
747 numColumnsShown = numColumnsToShow;
754 * Returns the tile corresponding to the specified page RID.
755 * @param {number} rid The page RID being looked up.
756 * @return {Tile} The corresponding tile.
758 function getTileByRid(rid) {
759 for (var i = 0, length = tiles.length; i < length; ++i) {
769 * Handles new input by disposing the NTP, according to where the input was
772 function onInputStart() {
773 if (fakebox && isFakeboxFocused()) {
774 setFakeboxFocus(false);
775 setFakeboxDragFocus(false);
777 } else if (!isFakeboxFocused()) {
784 * Disposes the NTP, according to where the input was entered.
785 * @param {boolean} wasFakeboxInput True if the input was in the fakebox.
787 function disposeNtp(wasFakeboxInput) {
788 var behavior = wasFakeboxInput ? fakeboxInputBehavior : omniboxInputBehavior;
789 if (behavior == NTP_DISPOSE_STATE.DISABLE_FAKEBOX)
790 setFakeboxActive(false);
791 else if (behavior == NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO)
792 setFakeboxAndLogoVisibility(false);
797 * Restores the NTP (re-enables the fakebox and unhides the logo.)
799 function restoreNtp() {
800 setFakeboxActive(true);
801 setFakeboxAndLogoVisibility(true);
806 * @param {boolean} focus True to focus the fakebox.
808 function setFakeboxFocus(focus) {
809 document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus);
813 * @param {boolean} focus True to show a dragging focus to the fakebox.
815 function setFakeboxDragFocus(focus) {
816 document.body.classList.toggle(CLASSES.FAKEBOX_DRAG_FOCUS, focus);
820 * @return {boolean} True if the fakebox has focus.
822 function isFakeboxFocused() {
823 return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS) ||
824 document.body.classList.contains(CLASSES.FAKEBOX_DRAG_FOCUS);
829 * @param {boolean} enable True to enable the fakebox.
831 function setFakeboxActive(enable) {
832 document.body.classList.toggle(CLASSES.FAKEBOX_DISABLE, !enable);
837 * @param {!Event} event The click event.
838 * @return {boolean} True if the click occurred in an enabled fakebox.
840 function isFakeboxClick(event) {
841 return fakebox.contains(event.target) &&
842 !document.body.classList.contains(CLASSES.FAKEBOX_DISABLE);
847 * @param {boolean} show True to show the fakebox and logo.
849 function setFakeboxAndLogoVisibility(show) {
850 document.body.classList.toggle(CLASSES.HIDE_FAKEBOX_AND_LOGO, !show);
855 * Shortcut for document.getElementById.
856 * @param {string} id of the element.
857 * @return {HTMLElement} with the id.
860 return document.getElementById(id);
865 * Utility function which creates an element with an optional classname and
866 * appends it to the specified parent.
867 * @param {Element} parent The parent to append the new element.
868 * @param {string} name The name of the new element.
869 * @param {string=} opt_class The optional classname of the new element.
870 * @return {Element} The new element.
872 function createAndAppendElement(parent, name, opt_class) {
873 var child = document.createElement(name);
875 child.classList.add(opt_class);
876 parent.appendChild(child);
882 * Removes a node from its parent.
883 * @param {Node} node The node to remove.
885 function removeNode(node) {
886 node.parentNode.removeChild(node);
891 * Removes all the child nodes on a DOM node.
892 * @param {Node} node Node to remove children from.
894 function removeChildren(node) {
900 * @param {!Element} element The element to register the handler for.
901 * @param {number} keycode The keycode of the key to register.
902 * @param {!Function} handler The key handler to register.
904 function registerKeyHandler(element, keycode, handler) {
905 element.addEventListener('keydown', function(event) {
906 if (event.keyCode == keycode)
913 * @return {Object} the handle to the embeddedSearch API.
915 function getEmbeddedSearchApiHandle() {
918 if (window.chrome && window.chrome.embeddedSearch)
919 return window.chrome.embeddedSearch;
924 * Extract the desired navigation behavior from a click button.
925 * @param {number} button The Event#button property of a click event.
926 * @return {WindowOpenDisposition} The desired behavior for
927 * navigateContentWindow.
929 function getDispositionFromClickButton(button) {
930 if (button == MIDDLE_MOUSE_BUTTON)
931 return WindowOpenDisposition.NEW_BACKGROUND_TAB;
932 return WindowOpenDisposition.CURRENT_TAB;
937 * Prepares the New Tab Page by adding listeners, rendering the current
938 * theme, the most visited pages section, and Google-specific elements for a
939 * Google-provided page.
942 tilesContainer = $(IDS.TILES);
943 notification = $(IDS.NOTIFICATION);
944 attribution = $(IDS.ATTRIBUTION);
945 ntpContents = $(IDS.NTP_CONTENTS);
947 for (var i = 0; i < NUM_ROWS; i++) {
948 var row = document.createElement('div');
949 row.classList.add(CLASSES.ROW);
950 tilesContainer.appendChild(row);
953 if (configData.isGooglePage) {
954 var logo = document.createElement('div');
957 fakebox = document.createElement('div');
958 fakebox.id = IDS.FAKEBOX;
960 '<input id="' + IDS.FAKEBOX_INPUT +
961 '" autocomplete="off" tabindex="-1" aria-hidden="true">' +
962 '<div id=cursor></div>';
964 ntpContents.insertBefore(fakebox, ntpContents.firstChild);
965 ntpContents.insertBefore(logo, ntpContents.firstChild);
967 document.body.classList.add(CLASSES.NON_GOOGLE_PAGE);
970 var notificationMessage = $(IDS.NOTIFICATION_MESSAGE);
971 notificationMessage.textContent =
972 configData.translatedStrings.thumbnailRemovedNotification;
973 var undoLink = $(IDS.UNDO_LINK);
974 undoLink.addEventListener('click', onUndo);
975 registerKeyHandler(undoLink, KEYCODE.ENTER, onUndo);
976 undoLink.textContent = configData.translatedStrings.undoThumbnailRemove;
977 var restoreAllLink = $(IDS.RESTORE_ALL_LINK);
978 restoreAllLink.addEventListener('click', onRestoreAll);
979 registerKeyHandler(restoreAllLink, KEYCODE.ENTER, onUndo);
980 restoreAllLink.textContent =
981 configData.translatedStrings.restoreThumbnailsShort;
982 $(IDS.ATTRIBUTION_TEXT).textContent =
983 configData.translatedStrings.attributionIntro;
985 var notificationCloseButton = $(IDS.NOTIFICATION_CLOSE_BUTTON);
986 notificationCloseButton.addEventListener('click', hideNotification);
988 userInitiatedMostVisitedChange = false;
989 window.addEventListener('resize', onResize);
992 var topLevelHandle = getEmbeddedSearchApiHandle();
994 ntpApiHandle = topLevelHandle.newTabPage;
995 ntpApiHandle.onthemechange = onThemeChange;
996 ntpApiHandle.onmostvisitedchange = onMostVisitedChange;
998 ntpApiHandle.oninputstart = onInputStart;
999 ntpApiHandle.oninputcancel = restoreNtp;
1001 if (ntpApiHandle.isInputInProgress)
1005 onMostVisitedChange();
1007 searchboxApiHandle = topLevelHandle.searchBox;
1010 // Listener for updating the key capture state.
1011 document.body.onmousedown = function(event) {
1012 if (isFakeboxClick(event))
1013 searchboxApiHandle.startCapturingKeyStrokes();
1014 else if (isFakeboxFocused())
1015 searchboxApiHandle.stopCapturingKeyStrokes();
1017 searchboxApiHandle.onkeycapturechange = function() {
1018 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
1020 var inputbox = $(IDS.FAKEBOX_INPUT);
1022 inputbox.onpaste = function(event) {
1023 event.preventDefault();
1024 searchboxApiHandle.paste();
1026 inputbox.ondrop = function(event) {
1027 event.preventDefault();
1028 var text = event.dataTransfer.getData('text/plain');
1030 searchboxApiHandle.paste(text);
1033 inputbox.ondragenter = function() {
1034 setFakeboxDragFocus(true);
1036 inputbox.ondragleave = function() {
1037 setFakeboxDragFocus(false);
1041 // Update the fakebox style to match the current key capturing state.
1042 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
1045 if (searchboxApiHandle.rtl) {
1046 $(IDS.NOTIFICATION).dir = 'rtl';
1047 // Add class for setting alignments based on language directionality.
1048 document.body.classList.add(CLASSES.RTL);
1049 $(IDS.TILES).dir = 'rtl';
1055 * Binds event listeners.
1058 document.addEventListener('DOMContentLoaded', init);
1067 if (!window.localNTPUnitTest) {
1068 LocalNTP().listen();