| // 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() { |
| <include src="../../../../ui/webui/resources/js/assert.js"> |
| |
| |
| |
| /** |
| * 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', |
| RECENT_TABS: 'recent-tabs', |
| 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.<Tile>} |
| */ |
| 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.<number>} 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 = |
| '<input id="' + IDS.FAKEBOX_INPUT + |
| '" autocomplete="off" tabindex="-1" aria-hidden="true">' + |
| '<div id=cursor></div>'; |
| |
| ntpContents.insertBefore(fakebox, ntpContents.firstChild); |
| ntpContents.insertBefore(logo, ntpContents.firstChild); |
| } else { |
| document.body.classList.add(CLASSES.NON_GOOGLE_PAGE); |
| } |
| |
| var recentTabsText = configData.translatedStrings.recentTabs; |
| if (recentTabsText) { |
| var recentTabsLink = document.createElement('span'); |
| recentTabsLink.id = IDS.RECENT_TABS; |
| recentTabsLink.addEventListener('click', function(event) { |
| ntpApiHandle.navigateContentWindow( |
| 'chrome://history', getDispositionFromClickButton(event.button)); |
| }); |
| recentTabsLink.textContent = recentTabsText; |
| ntpContents.appendChild(recentTabsLink); |
| // Move the attribution up to prevent it from overlapping. |
| attribution.style.bottom = '28px'; |
| } |
| |
| 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(); |
| } |