// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview The local InstantExtended NTP. */ /** * Controls rendering the new tab page for InstantExtended. * @return {Object} A limited interface for testing the local NTP. */ function LocalNTP() { /** * Enum for classnames. * @enum {string} * @const */ var CLASSES = { ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation BLACKLIST_BUTTON: 'mv-x', DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide', FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox // Applies drag focus style to the fakebox FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused', FAVICON: 'mv-favicon', HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo', HIDE_NOTIFICATION: 'mv-notice-hide', // Vertically centers the most visited section for a non-Google provided page. NON_GOOGLE_PAGE: 'non-google-page', PAGE: 'mv-page', // page tiles PAGE_READY: 'mv-page-ready', // page tile when ready ROW: 'mv-row', // tile row RTL: 'rtl', // Right-to-left language text. THUMBNAIL: 'mv-thumb', THUMBNAIL_MASK: 'mv-mask', TILE: 'mv-tile', TITLE: 'mv-title' }; /** * Enum for HTML element ids. * @enum {string} * @const */ var IDS = { ATTRIBUTION: 'attribution', ATTRIBUTION_TEXT: 'attribution-text', CUSTOM_THEME_STYLE: 'ct-style', FAKEBOX: 'fakebox', FAKEBOX_INPUT: 'fakebox-input', LOGO: 'logo', NOTIFICATION: 'mv-notice', NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x', NOTIFICATION_MESSAGE: 'mv-msg', NTP_CONTENTS: 'ntp-contents', RESTORE_ALL_LINK: 'mv-restore', TILES: 'mv-tiles', UNDO_LINK: 'mv-undo' }; /** * Enum for keycodes. * @enum {number} * @const */ var KEYCODE = { DELETE: 46, ENTER: 13 }; /** * Enum for the state of the NTP when it is disposed. * @enum {number} * @const */ var NTP_DISPOSE_STATE = { NONE: 0, // Preserve the NTP appearance and functionality DISABLE_FAKEBOX: 1, HIDE_FAKEBOX_AND_LOGO: 2 }; /** * The JavaScript button event value for a middle click. * @type {number} * @const */ var MIDDLE_MOUSE_BUTTON = 1; /** * Possible behaviors for navigateContentWindow. * @enum {number} */ var WindowOpenDisposition = { CURRENT_TAB: 1, NEW_BACKGROUND_TAB: 2 }; /** * The container for the tile elements. * @type {Element} */ var tilesContainer; /** * The notification displayed when a page is blacklisted. * @type {Element} */ var notification; /** * The container for the theme attribution. * @type {Element} */ var attribution; /** * The "fakebox" - an input field that looks like a regular searchbox. When it * is focused, any text the user types goes directly into the omnibox. * @type {Element} */ var fakebox; /** * The container for NTP elements. * @type {Element} */ var ntpContents; /** * The array of rendered tiles, ordered by appearance. * @type {!Array.} */ var tiles = []; /** * The last blacklisted tile if any, which by definition should not be filler. * @type {?Tile} */ var lastBlacklistedTile = null; /** * True if a page has been blacklisted and we're waiting on the * onmostvisitedchange callback. See onMostVisitedChange() for how this * is used. * @type {boolean} */ var isBlacklisting = false; /** * Current number of tiles columns shown based on the window width, including * those that just contain filler. * @type {number} */ var numColumnsShown = 0; /** * True if the user initiated the current most visited change and false * otherwise. * @type {boolean} */ var userInitiatedMostVisitedChange = false; /** * The browser embeddedSearch.newTabPage object. * @type {Object} */ var ntpApiHandle; /** * The browser embeddedSearch.searchBox object. * @type {Object} */ var searchboxApiHandle; /** * The state of the NTP when a query is entered into the Omnibox. * @type {NTP_DISPOSE_STATE} */ var omniboxInputBehavior = NTP_DISPOSE_STATE.NONE; /** * The state of the NTP when a query is entered into the Fakebox. * @type {NTP_DISPOSE_STATE} */ var fakeboxInputBehavior = NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO; /** * Total tile width. Should be equal to mv-tile's width + 2 * border-width. * @private {number} * @const */ var TILE_WIDTH = 140; /** * Margin between tiles. Should be equal to mv-tile's -webkit-margin-start. * @private {number} * @const */ var TILE_MARGIN_START = 20; /** @type {number} @const */ var MAX_NUM_TILES_TO_SHOW = 8; /** @type {number} @const */ var MIN_NUM_COLUMNS = 2; /** @type {number} @const */ var MAX_NUM_COLUMNS = 4; /** @type {number} @const */ var NUM_ROWS = 2; /** * Minimum total padding to give to the left and right of the most visited * section. Used to determine how many tiles to show. * @type {number} * @const */ var MIN_TOTAL_HORIZONTAL_PADDING = 200; /** * The filename for a most visited iframe src which shows a page title. * @type {string} * @const */ var MOST_VISITED_TITLE_IFRAME = 'title.html'; /** * The filename for a most visited iframe src which shows a thumbnail image. * @type {string} * @const */ var MOST_VISITED_THUMBNAIL_IFRAME = 'thumbnail.html'; /** * The hex color for most visited tile elements. * @type {string} * @const */ var MOST_VISITED_COLOR = '777777'; /** * The font family for most visited tile elements. * @type {string} * @const */ var MOST_VISITED_FONT_FAMILY = 'arial, sans-serif'; /** * The font size for most visited tile elements. * @type {number} * @const */ var MOST_VISITED_FONT_SIZE = 11; /** * Hide most visited tiles for at most this many milliseconds while painting. * @type {number} * @const */ var MOST_VISITED_PAINT_TIMEOUT_MSEC = 500; /** * A Tile is either a rendering of a Most Visited page or "filler" used to * pad out the section when not enough pages exist. * * @param {Element} elem The element for rendering the tile. * @param {number=} opt_rid The RID for the corresponding Most Visited page. * Should only be left unspecified when creating a filler tile. * @constructor */ function Tile(elem, opt_rid) { /** @type {Element} */ this.elem = elem; /** @type {number|undefined} */ this.rid = opt_rid; } /** * Updates the NTP based on the current theme. * @private */ function onThemeChange() { var info = ntpApiHandle.themeBackgroundInfo; if (!info) return; var background = [convertToRGBAColor(info.backgroundColorRgba), info.imageUrl, info.imageTiling, info.imageHorizontalAlignment, info.imageVerticalAlignment].join(' ').trim(); document.body.style.background = background; document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo); updateThemeAttribution(info.attributionUrl); setCustomThemeStyle(info); renderTiles(); } /** * Updates the NTP style according to theme. * @param {Object=} opt_themeInfo The information about the theme. If it is * omitted the style will be reverted to the default. * @private */ function setCustomThemeStyle(opt_themeInfo) { var customStyleElement = $(IDS.CUSTOM_THEME_STYLE); var head = document.head; if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) { var themeStyle = '#attribution {' + ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' + '}' + '#mv-msg {' + ' color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' + '}' + '#mv-notice-links span {' + ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' + '}' + '#mv-notice-x {' + ' -webkit-filter: drop-shadow(0 0 0 ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' + '}' + '.mv-page-ready {' + ' border: 1px solid ' + convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' + '}' + '.mv-page-ready:hover, .mv-page-ready:focus {' + ' border-color: ' + convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' + '}'; if (customStyleElement) { customStyleElement.textContent = themeStyle; } else { customStyleElement = document.createElement('style'); customStyleElement.type = 'text/css'; customStyleElement.id = IDS.CUSTOM_THEME_STYLE; customStyleElement.textContent = themeStyle; head.appendChild(customStyleElement); } } else if (customStyleElement) { head.removeChild(customStyleElement); } } /** * Renders the attribution if the URL is present, otherwise hides it. * @param {string} url The URL of the attribution image, if any. * @private */ function updateThemeAttribution(url) { if (!url) { setAttributionVisibility_(false); return; } var attributionImage = attribution.querySelector('img'); if (!attributionImage) { attributionImage = new Image(); attribution.appendChild(attributionImage); } attributionImage.style.content = url; setAttributionVisibility_(true); } /** * Sets the visibility of the theme attribution. * @param {boolean} show True to show the attribution. * @private */ function setAttributionVisibility_(show) { if (attribution) { attribution.style.display = show ? '' : 'none'; } } /** * Converts an Array of color components into RGBA format "rgba(R,G,B,A)". * @param {Array.} color Array of rgba color components. * @return {string} CSS color in RGBA format. * @private */ function convertToRGBAColor(color) { return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + color[3] / 255 + ')'; } /** * Handles a new set of Most Visited page data. */ function onMostVisitedChange() { var pages = ntpApiHandle.mostVisited; if (isBlacklisting) { // Trigger the blacklist animation and re-render the tiles when it // completes. var lastBlacklistedTileElement = lastBlacklistedTile.elem; lastBlacklistedTileElement.addEventListener( 'webkitTransitionEnd', blacklistAnimationDone); lastBlacklistedTileElement.classList.add(CLASSES.BLACKLIST); } else { // Otherwise render the tiles using the new data without animation. tiles = []; for (var i = 0; i < MAX_NUM_TILES_TO_SHOW; ++i) { tiles.push(createTile(pages[i], i)); } if (!userInitiatedMostVisitedChange) { tilesContainer.hidden = true; window.setTimeout(function() { if (tilesContainer) { tilesContainer.hidden = false; } }, MOST_VISITED_PAINT_TIMEOUT_MSEC); } renderTiles(); } } /** * Renders the current set of tiles. */ function renderTiles() { var rows = tilesContainer.children; for (var i = 0; i < rows.length; ++i) { removeChildren(rows[i]); } for (var i = 0, length = tiles.length; i < Math.min(length, numColumnsShown * NUM_ROWS); ++i) { rows[Math.floor(i / numColumnsShown)].appendChild(tiles[i].elem); } } /** * Shows most visited tiles if all child iframes are loaded, and hides them * otherwise. */ function updateMostVisitedVisibility() { var iframes = tilesContainer.querySelectorAll('iframe'); var ready = true; for (var i = 0, numIframes = iframes.length; i < numIframes; i++) { if (iframes[i].hidden) { ready = false; break; } } if (ready) { tilesContainer.hidden = false; userInitiatedMostVisitedChange = false; } } /** * Builds a URL to display a most visited tile component in an iframe. * @param {string} filename The desired most visited component filename. * @param {number} rid The restricted ID. * @param {string} color The text color for text in the iframe. * @param {string} fontFamily The font family for text in the iframe. * @param {number} fontSize The font size for text in the iframe. * @param {number} position The position of the iframe in the UI. * @return {string} An URL to display the most visited component in an iframe. */ function getMostVisitedIframeUrl(filename, rid, color, fontFamily, fontSize, position) { return 'chrome-search://most-visited/' + encodeURIComponent(filename) + '?' + ['rid=' + encodeURIComponent(rid), 'c=' + encodeURIComponent(color), 'f=' + encodeURIComponent(fontFamily), 'fs=' + encodeURIComponent(fontSize), 'pos=' + encodeURIComponent(position)].join('&'); } /** * Creates a Tile with the specified page data. If no data is provided, a * filler Tile is created. * @param {Object} page The page data. * @param {number} position The position of the tile. * @return {Tile} The new Tile. */ function createTile(page, position) { var tileElement = document.createElement('div'); tileElement.classList.add(CLASSES.TILE); if (page) { var rid = page.rid; tileElement.classList.add(CLASSES.PAGE); var navigateFunction = function() { ntpApiHandle.navigateContentWindow(rid); }; // The click handler for navigating to the page identified by the RID. tileElement.addEventListener('click', navigateFunction); // Make thumbnails tab-accessible. tileElement.setAttribute('tabindex', '1'); registerKeyHandler(tileElement, KEYCODE.ENTER, navigateFunction); // The iframe which renders the page title. var titleElement = document.createElement('iframe'); titleElement.tabIndex = '-1'; // Why iframes have IDs: // // On navigating back to the NTP we see several onmostvisitedchange() events // in series with incrementing RIDs. After the first event, a set of iframes // begins loading RIDs n, n+1, ..., n+k-1; after the second event, these get // destroyed and a new set begins loading RIDs n+k, n+k+1, ..., n+2k-1. // Now due to crbug.com/68841, Chrome incorrectly loads the content for the // first set of iframes into the most recent set of iframes. // // Giving iframes distinct ids seems to cause some invalidation and prevent // associating the incorrect data. // // TODO(jered): Find and fix the root (probably Blink) bug. titleElement.src = getMostVisitedIframeUrl( MOST_VISITED_TITLE_IFRAME, rid, MOST_VISITED_COLOR, MOST_VISITED_FONT_FAMILY, MOST_VISITED_FONT_SIZE, position); // Keep this id here. See comment above. titleElement.id = 'title-' + rid; titleElement.hidden = true; titleElement.onload = function() { titleElement.hidden = false; updateMostVisitedVisibility(); }; titleElement.className = CLASSES.TITLE; tileElement.appendChild(titleElement); // The iframe which renders either a thumbnail or domain element. var thumbnailElement = document.createElement('iframe'); thumbnailElement.tabIndex = '-1'; thumbnailElement.src = getMostVisitedIframeUrl( MOST_VISITED_THUMBNAIL_IFRAME, rid, MOST_VISITED_COLOR, MOST_VISITED_FONT_FAMILY, MOST_VISITED_FONT_SIZE, position); // Keep this id here. See comment above. thumbnailElement.id = 'thumb-' + rid; thumbnailElement.hidden = true; thumbnailElement.onload = function() { thumbnailElement.hidden = false; tileElement.classList.add(CLASSES.PAGE_READY); updateMostVisitedVisibility(); }; thumbnailElement.className = CLASSES.THUMBNAIL; tileElement.appendChild(thumbnailElement); // A mask to darken the thumbnail on focus. var maskElement = createAndAppendElement( tileElement, 'div', CLASSES.THUMBNAIL_MASK); // The button used to blacklist this page. var blacklistButton = createAndAppendElement( tileElement, 'div', CLASSES.BLACKLIST_BUTTON); var blacklistFunction = generateBlacklistFunction(rid); blacklistButton.addEventListener('click', blacklistFunction); blacklistButton.title = configData.translatedStrings.removeThumbnailTooltip; // When a tile is focused, have delete also blacklist the page. registerKeyHandler(tileElement, KEYCODE.DELETE, blacklistFunction); // The page favicon, if any. var faviconUrl = page.faviconUrl; if (faviconUrl) { var favicon = createAndAppendElement( tileElement, 'div', CLASSES.FAVICON); favicon.style.backgroundImage = 'url(' + faviconUrl + ')'; } return new Tile(tileElement, rid); } else { return new Tile(tileElement); } } /** * Generates a function to be called when the page with the corresponding RID * is blacklisted. * @param {number} rid The RID of the page being blacklisted. * @return {function(Event)} A function which handles the blacklisting of the * page by updating state variables and notifying Chrome. */ function generateBlacklistFunction(rid) { return function(e) { // Prevent navigation when the page is being blacklisted. e.stopPropagation(); userInitiatedMostVisitedChange = true; isBlacklisting = true; tilesContainer.classList.add(CLASSES.HIDE_BLACKLIST_BUTTON); lastBlacklistedTile = getTileByRid(rid); ntpApiHandle.deleteMostVisitedItem(rid); }; } /** * Shows the blacklist notification and triggers a delay to hide it. */ function showNotification() { notification.classList.remove(CLASSES.HIDE_NOTIFICATION); notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); notification.scrollTop; notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION); } /** * Hides the blacklist notification. */ function hideNotification() { notification.classList.add(CLASSES.HIDE_NOTIFICATION); } /** * Handles the end of the blacklist animation by showing the notification and * re-rendering the new set of tiles. */ function blacklistAnimationDone() { showNotification(); isBlacklisting = false; tilesContainer.classList.remove(CLASSES.HIDE_BLACKLIST_BUTTON); lastBlacklistedTile.elem.removeEventListener( 'webkitTransitionEnd', blacklistAnimationDone); // Need to call explicitly to re-render the tiles, since the initial // onmostvisitedchange issued by the blacklist function only triggered // the animation. onMostVisitedChange(); } /** * Handles a click on the notification undo link by hiding the notification and * informing Chrome. */ function onUndo() { userInitiatedMostVisitedChange = true; hideNotification(); var lastBlacklistedRID = lastBlacklistedTile.rid; if (typeof lastBlacklistedRID != 'undefined') ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedRID); } /** * Handles a click on the restore all notification link by hiding the * notification and informing Chrome. */ function onRestoreAll() { userInitiatedMostVisitedChange = true; hideNotification(); ntpApiHandle.undoAllMostVisitedDeletions(); } /** * Re-renders the tiles if the number of columns has changed. As a temporary * fix for crbug/240510, updates the width of the fakebox and most visited tiles * container. */ function onResize() { // If innerWidth is zero, then use the maximum snap size. var innerWidth = window.innerWidth || 820; // These values should remain in sync with local_ntp.css. // TODO(jeremycho): Delete once the root cause of crbug/240510 is resolved. var setWidths = function(tilesContainerWidth) { tilesContainer.style.width = tilesContainerWidth + 'px'; if (fakebox) fakebox.style.width = (tilesContainerWidth - 2) + 'px'; }; if (innerWidth >= 820) setWidths(620); else if (innerWidth >= 660) setWidths(460); else setWidths(300); var tileRequiredWidth = TILE_WIDTH + TILE_MARGIN_START; // Adds margin-start to the available width to compensate the extra margin // counted above for the first tile (which does not have a margin-start). var availableWidth = innerWidth + TILE_MARGIN_START - MIN_TOTAL_HORIZONTAL_PADDING; var numColumnsToShow = Math.floor(availableWidth / tileRequiredWidth); numColumnsToShow = Math.max(MIN_NUM_COLUMNS, Math.min(MAX_NUM_COLUMNS, numColumnsToShow)); if (numColumnsToShow != numColumnsShown) { numColumnsShown = numColumnsToShow; renderTiles(); } } /** * Returns the tile corresponding to the specified page RID. * @param {number} rid The page RID being looked up. * @return {Tile} The corresponding tile. */ function getTileByRid(rid) { for (var i = 0, length = tiles.length; i < length; ++i) { var tile = tiles[i]; if (tile.rid == rid) return tile; } return null; } /** * Handles new input by disposing the NTP, according to where the input was * entered. */ function onInputStart() { if (fakebox && isFakeboxFocused()) { setFakeboxFocus(false); setFakeboxDragFocus(false); disposeNtp(true); } else if (!isFakeboxFocused()) { disposeNtp(false); } } /** * Disposes the NTP, according to where the input was entered. * @param {boolean} wasFakeboxInput True if the input was in the fakebox. */ function disposeNtp(wasFakeboxInput) { var behavior = wasFakeboxInput ? fakeboxInputBehavior : omniboxInputBehavior; if (behavior == NTP_DISPOSE_STATE.DISABLE_FAKEBOX) setFakeboxActive(false); else if (behavior == NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO) setFakeboxAndLogoVisibility(false); } /** * Restores the NTP (re-enables the fakebox and unhides the logo.) */ function restoreNtp() { setFakeboxActive(true); setFakeboxAndLogoVisibility(true); } /** * @param {boolean} focus True to focus the fakebox. */ function setFakeboxFocus(focus) { document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus); } /** * @param {boolean} focus True to show a dragging focus to the fakebox. */ function setFakeboxDragFocus(focus) { document.body.classList.toggle(CLASSES.FAKEBOX_DRAG_FOCUS, focus); } /** * @return {boolean} True if the fakebox has focus. */ function isFakeboxFocused() { return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS) || document.body.classList.contains(CLASSES.FAKEBOX_DRAG_FOCUS); } /** * @param {boolean} enable True to enable the fakebox. */ function setFakeboxActive(enable) { document.body.classList.toggle(CLASSES.FAKEBOX_DISABLE, !enable); } /** * @param {!Event} event The click event. * @return {boolean} True if the click occurred in an enabled fakebox. */ function isFakeboxClick(event) { return fakebox.contains(event.target) && !document.body.classList.contains(CLASSES.FAKEBOX_DISABLE); } /** * @param {boolean} show True to show the fakebox and logo. */ function setFakeboxAndLogoVisibility(show) { document.body.classList.toggle(CLASSES.HIDE_FAKEBOX_AND_LOGO, !show); } /** * Shortcut for document.getElementById. * @param {string} id of the element. * @return {HTMLElement} with the id. */ function $(id) { return document.getElementById(id); } /** * Utility function which creates an element with an optional classname and * appends it to the specified parent. * @param {Element} parent The parent to append the new element. * @param {string} name The name of the new element. * @param {string=} opt_class The optional classname of the new element. * @return {Element} The new element. */ function createAndAppendElement(parent, name, opt_class) { var child = document.createElement(name); if (opt_class) child.classList.add(opt_class); parent.appendChild(child); return child; } /** * Removes a node from its parent. * @param {Node} node The node to remove. */ function removeNode(node) { node.parentNode.removeChild(node); } /** * Removes all the child nodes on a DOM node. * @param {Node} node Node to remove children from. */ function removeChildren(node) { node.innerHTML = ''; } /** * @param {!Element} element The element to register the handler for. * @param {number} keycode The keycode of the key to register. * @param {!Function} handler The key handler to register. */ function registerKeyHandler(element, keycode, handler) { element.addEventListener('keydown', function(event) { if (event.keyCode == keycode) handler(event); }); } /** * @return {Object} the handle to the embeddedSearch API. */ function getEmbeddedSearchApiHandle() { if (window.cideb) return window.cideb; if (window.chrome && window.chrome.embeddedSearch) return window.chrome.embeddedSearch; return null; } /** * Extract the desired navigation behavior from a click button. * @param {number} button The Event#button property of a click event. * @return {WindowOpenDisposition} The desired behavior for * navigateContentWindow. */ function getDispositionFromClickButton(button) { if (button == MIDDLE_MOUSE_BUTTON) return WindowOpenDisposition.NEW_BACKGROUND_TAB; return WindowOpenDisposition.CURRENT_TAB; } /** * Prepares the New Tab Page by adding listeners, rendering the current * theme, the most visited pages section, and Google-specific elements for a * Google-provided page. */ function init() { tilesContainer = $(IDS.TILES); notification = $(IDS.NOTIFICATION); attribution = $(IDS.ATTRIBUTION); ntpContents = $(IDS.NTP_CONTENTS); for (var i = 0; i < NUM_ROWS; i++) { var row = document.createElement('div'); row.classList.add(CLASSES.ROW); tilesContainer.appendChild(row); } if (configData.isGooglePage) { var logo = document.createElement('div'); logo.id = IDS.LOGO; fakebox = document.createElement('div'); fakebox.id = IDS.FAKEBOX; fakebox.innerHTML = '' + '
'; ntpContents.insertBefore(fakebox, ntpContents.firstChild); ntpContents.insertBefore(logo, ntpContents.firstChild); } else { document.body.classList.add(CLASSES.NON_GOOGLE_PAGE); } var notificationMessage = $(IDS.NOTIFICATION_MESSAGE); notificationMessage.textContent = configData.translatedStrings.thumbnailRemovedNotification; var undoLink = $(IDS.UNDO_LINK); undoLink.addEventListener('click', onUndo); registerKeyHandler(undoLink, KEYCODE.ENTER, onUndo); undoLink.textContent = configData.translatedStrings.undoThumbnailRemove; var restoreAllLink = $(IDS.RESTORE_ALL_LINK); restoreAllLink.addEventListener('click', onRestoreAll); registerKeyHandler(restoreAllLink, KEYCODE.ENTER, onUndo); restoreAllLink.textContent = configData.translatedStrings.restoreThumbnailsShort; $(IDS.ATTRIBUTION_TEXT).textContent = configData.translatedStrings.attributionIntro; var notificationCloseButton = $(IDS.NOTIFICATION_CLOSE_BUTTON); notificationCloseButton.addEventListener('click', hideNotification); userInitiatedMostVisitedChange = false; window.addEventListener('resize', onResize); onResize(); var topLevelHandle = getEmbeddedSearchApiHandle(); ntpApiHandle = topLevelHandle.newTabPage; ntpApiHandle.onthemechange = onThemeChange; ntpApiHandle.onmostvisitedchange = onMostVisitedChange; ntpApiHandle.oninputstart = onInputStart; ntpApiHandle.oninputcancel = restoreNtp; if (ntpApiHandle.isInputInProgress) onInputStart(); onThemeChange(); onMostVisitedChange(); searchboxApiHandle = topLevelHandle.searchBox; if (fakebox) { // Listener for updating the key capture state. document.body.onmousedown = function(event) { if (isFakeboxClick(event)) searchboxApiHandle.startCapturingKeyStrokes(); else if (isFakeboxFocused()) searchboxApiHandle.stopCapturingKeyStrokes(); }; searchboxApiHandle.onkeycapturechange = function() { setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled); }; var inputbox = $(IDS.FAKEBOX_INPUT); if (inputbox) { inputbox.onpaste = function(event) { event.preventDefault(); searchboxApiHandle.paste(); }; inputbox.ondrop = function(event) { event.preventDefault(); var text = event.dataTransfer.getData('text/plain'); if (text) { searchboxApiHandle.paste(text); } }; inputbox.ondragenter = function() { setFakeboxDragFocus(true); }; inputbox.ondragleave = function() { setFakeboxDragFocus(false); }; } // Update the fakebox style to match the current key capturing state. setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled); } if (searchboxApiHandle.rtl) { $(IDS.NOTIFICATION).dir = 'rtl'; // Add class for setting alignments based on language directionality. document.body.classList.add(CLASSES.RTL); $(IDS.TILES).dir = 'rtl'; } } /** * Binds event listeners. */ function listen() { document.addEventListener('DOMContentLoaded', init); } return { init: init, listen: listen }; } if (!window.localNTPUnitTest) { LocalNTP().listen(); }