diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-07-31 15:50:41 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-08-30 12:35:23 +0000 |
commit | 7b2ffa587235a47d4094787d72f38102089f402a (patch) | |
tree | 30e82af9cbab08a7fa028bb18f4f2987a3f74dfa /chromium/chrome/browser/resources/local_ntp/most_visited_single.js | |
parent | d94af01c90575348c4e81a418257f254b6f8d225 (diff) |
BASELINE: Update Chromium to 76.0.3809.94
Change-Id: I321c3f5f929c105aec0f98c5091ef6108822e647
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/chrome/browser/resources/local_ntp/most_visited_single.js')
-rw-r--r-- | chromium/chrome/browser/resources/local_ntp/most_visited_single.js | 700 |
1 files changed, 602 insertions, 98 deletions
diff --git a/chromium/chrome/browser/resources/local_ntp/most_visited_single.js b/chromium/chrome/browser/resources/local_ntp/most_visited_single.js index 0672392166c..4fc4e5d6c7f 100644 --- a/chromium/chrome/browser/resources/local_ntp/most_visited_single.js +++ b/chromium/chrome/browser/resources/local_ntp/most_visited_single.js @@ -3,7 +3,12 @@ * found in the LICENSE file. */ // Single iframe for NTP tiles. -(function() { + +/** + * Controls rendering the Most Visited iframe. + * @return {Object} A limited interface for testing the iframe. + */ +function MostVisited() { 'use strict'; @@ -43,8 +48,14 @@ const IDS = { */ const CLASSES = { FAILED_FAVICON: 'failed-favicon', // Applied when the favicon fails to load. + GRID_LAYOUT: 'grid-layout', + // Applied to the grid tile being moved while reordering. + GRID_REORDER: 'grid-reorder', + GRID_TILE: 'grid-tile', + GRID_TILE_CONTAINER: 'grid-tile-container', REORDER: 'reorder', // Applied to the tile being moved while reordering. - REORDERING: 'reordering', // Applied while we are reordering. + // Applied while we are reordering. Disables hover styling. + REORDERING: 'reordering', MAC_CHROMEOS: 'mac-chromeos', // Reduces font weight for MacOS and ChromeOS. // Material Design classes. MD_EMPTY_TILE: 'md-empty-tile', @@ -60,6 +71,7 @@ const CLASSES = { MD_TILE_INNER: 'md-tile-inner', MD_TITLE: 'md-title', MD_TITLE_CONTAINER: 'md-title-container', + NO_INITIAL_FADE: 'no-initial-fade', }; @@ -71,7 +83,7 @@ const CLASSES = { * @enum {number} * @const */ -var LOG_TYPE = { +const LOG_TYPE = { // All NTP tiles have finished loading (successfully or failing). NTP_ALL_TILES_LOADED: 11, // The data for all NTP tiles (title, URL, etc, but not the thumbnail image) @@ -95,7 +107,7 @@ var LOG_TYPE = { * @enum {number} * @const */ -var TileVisualType = { +const TileVisualType = { NONE: 0, ICON_REAL: 1, ICON_COLOR: 2, @@ -135,6 +147,14 @@ const MD_MAX_TILES_PER_ROW = 5; /** + * Height of a tile for Material Design. Keep in sync with + * most_visited_single.css. + * @const {number} + */ +const MD_TILE_HEIGHT = 128; + + +/** * Width of a tile for Material Design. Keep in sync with * most_visited_single.css. * @const {number} @@ -164,7 +184,7 @@ const DOMAIN_ORIGIN = '{{ORIGIN}}'; * at 1 because initially we're waiting for the "show" message from the parent. * @type {number} */ -var loadedCounter = 1; +let loadedCounter = 1; /** @@ -172,7 +192,7 @@ var loadedCounter = 1; * Works as a double-buffer that is shown when we receive a "show" postMessage. * @type {Element} */ -var tiles = null; +let tiles = null; /** @@ -189,7 +209,7 @@ let maxNumTiles = 8; * List of parameters passed by query args. * @type {Object} */ -var queryArgs = {}; +let queryArgs = {}; /** @@ -215,12 +235,455 @@ let isCustomLinksEnabled = false; /** + * True if the grid layout is enabled. + * @type {boolean} + */ +let isGridEnabled = false; + + +/** + * The current grid of tiles. + * @type {?Grid} + */ +let currGrid = null; + + +/** + * Called by tests to enable the grid layout. + */ +function enableGridLayoutForTesting() { + isGridEnabled = true; + document.body.classList.add(CLASSES.GRID_LAYOUT); +} + + +/** + * Additional API for Array. Moves the item at index |from| to index |to|. + * @param {number} from Index of the item to move. + * @param {number} to Index to move the item to. + */ +Array.prototype.move = function(from, to) { + this.splice(to, 0, this.splice(from, 1)[0]); +}; + + +/** + * Class that handles layouts and animations for the tile grid. This includes + * animations for adding, deleting, and reordering. + */ +class Grid { + constructor() { + /** @private {number} */ + this.tileHeight_ = 0; + /** @private {number} */ + this.tileWidth_ = 0; + /** @private {number} */ + this.tilesAlwaysVisible_ = 0; + /** + * The maximum number of tiles per row allowed by the grid parameters. + * @private {number} + */ + this.maxTilesPerRow_ = 0; + /** @private {number} */ + this.maxTiles_ = 0; + + /** @private {number} */ + this.gridWidth_ = 0; + /** + * The maximum number of tiles per row allowed by the window width. + * @private {number} + */ + this.maxTilesPerRowWindow_ = 0; + + /** @private {?Element} */ + this.container_ = null; + /** @private {?HTMLCollection} */ + this.tiles_ = null; + + /** + * Array that stores the {x,y} positions of the tile layout. + * @private {?Array<!Object>} + */ + this.position_ = null; + + /** + * Stores the current order of the tiles. Index corresponds to grid + * position, while value is the index of the tile in |this.tiles_|. + * @private {?Array<number>} + */ + this.order_ = null; + + /** @private {number} The index of the tile we're reordering. */ + this.itemToReorder_ = -1; + /** @private {number} The index to move the tile we're reordering to. */ + this.newIndexOfItemToReorder_ = -1; + } + + + /** + * Sets up the grid for the new tileset in |container|. The old tileset is + * discarded. + * @param {!Element} container The grid container element. + * @param {Object=} params Customizable parameters for the grid. Used in + * testing. + */ + init(container, params = {}) { + this.container_ = container; + + this.tileHeight_ = params.tileHeight || MD_TILE_HEIGHT; + this.tileWidth_ = params.tileWidth || MD_TILE_WIDTH; + this.tilesAlwaysVisible_ = + params.tilesAlwaysVisible || MD_NUM_TILES_ALWAYS_VISIBLE; + this.maxTilesPerRow_ = params.maxTilesPerRow || MD_MAX_TILES_PER_ROW; + this.maxTiles_ = params.maxTiles || maxNumTiles; + + this.maxTilesPerRowWindow_ = this.getMaxTilesPerRow_(); + + this.tiles_ = + this.container_.getElementsByClassName(CLASSES.GRID_TILE_CONTAINER); + if (this.tiles_.length > this.maxTiles_) { + throw new Error( + 'The number of tiles (' + this.tiles_.length + + ') exceeds the maximum (' + this.maxTiles_ + ').'); + } + this.position_ = new Array(this.maxTiles_); + this.order_ = new Array(this.maxTiles_); + for (let i = 0; i < this.maxTiles_; i++) { + this.position_[i] = {x: 0, y: 0}; + this.order_[i] = i; + } + + if (isCustomLinksEnabled || params.enableReorder) { + // Set up reordering for all tiles except the add shortcut button. + for (let i = 0; i < this.tiles_.length; i++) { + if (this.tiles_[i].getAttribute('add') !== 'true') { + this.setupReorder_(this.tiles_[i], i); + } + } + } + + this.updateLayout(); + } + + + /** + * Returns a grid tile wrapper that contains |tile|. + * @param {!Element} tile The tile element. + * @param {number} rid The tile's restricted id. + * @param {boolean} isAddButton True if this is the add shortcut button. + * @return {!Element} A grid tile wrapper. + */ + createGridTile(tile, rid, isAddButton) { + const gridTileContainer = document.createElement('div'); + gridTileContainer.className = CLASSES.GRID_TILE_CONTAINER; + gridTileContainer.setAttribute('rid', rid); + gridTileContainer.setAttribute('add', isAddButton); + const gridTile = document.createElement('div'); + gridTile.className = CLASSES.GRID_TILE; + gridTile.appendChild(tile); + gridTileContainer.appendChild(gridTile); + return gridTileContainer; + } + + + /** + * Updates the layout of the tiles. This is called for new tilesets and when + * the window is resized or zoomed. Translates each tile's + * |CLASSES.GRID_TILE_CONTAINER| to the correct position. + */ + updateLayout() { + const tilesPerRow = this.getTilesPerRow_(); + + this.gridWidth_ = tilesPerRow * this.tileWidth_; + this.container_.style.width = this.gridWidth_ + 'px'; + + const maxVisibleTiles = tilesPerRow * 2; + let x = 0; + let y = 0; + for (let i = 0; i < this.tiles_.length; i++) { + const tile = this.tiles_[i]; + // Reset the offset for row 2. + if (i === tilesPerRow) { + x = this.getRow2Offset_(tilesPerRow); + y = this.tileHeight_; + } + // Update the tile's position. + this.translate_(tile, x, y); + this.position_[i].x = x; + this.position_[i].y = y; + x += this.tileWidth_; // Increment for the next tile. + + // Update visibility for tiles that may be hidden by the iframe border in + // order to prevent keyboard navigation from reaching them. Ignores tiles + // that will always be visible, since changing 'display' prevents + // transitions from working. + if (i >= this.tilesAlwaysVisible_) { + const isVisible = i < maxVisibleTiles; + tile.style.display = isVisible ? 'block' : 'none'; + } + } + } + + + /** + * Called when the window is resized/zoomed. Recalculates maximums for the new + * window size and calls |updateLayout| if necessary. + */ + onResize() { + // Update the layout if the max number of tiles per row changes due to the + // new window size. + const maxPerRowWindow = this.getMaxTilesPerRow_(); + if (maxPerRowWindow !== this.maxTilesPerRowWindow_) { + this.maxTilesPerRowWindow_ = maxPerRowWindow; + this.updateLayout(); + } + } + + + /** + * Returns the number of tiles per row. This may be balanced in order to make + * even rows. + * @return {number} The number of tiles per row. + * @private + */ + getTilesPerRow_() { + const maxTilesPerRow = + Math.min(this.maxTilesPerRow_, this.maxTilesPerRowWindow_); + if (this.tiles_.length >= maxTilesPerRow * 2) { + // We have enough for two full rows, so just return the max. + return maxTilesPerRow; + } else if (this.tiles_.length > maxTilesPerRow) { + // We have have a little more than one full row, so we need to rebalance. + return Math.ceil(this.tiles_.length / 2); + } else { + // We have (less than) a full row, so just return the tiles we have. + return this.tiles_.length; + } + } + + + /** + * Returns the maximum number of tiles per row allowed by the window size. + * @return {number} The maximum number of tiles per row. + * @private + */ + getMaxTilesPerRow_() { + return Math.floor(window.innerWidth / this.tileWidth_); + } + + + /** + * Returns row 2's x offset from row 1 in px. This will either be 0 or half a + * tile length. + * @param {number} tilesPerRow The number of tiles per row. + * @return {number} The offset for row 2. + * @private + */ + getRow2Offset_(tilesPerRow) { + // An odd number of tiles requires a half tile offset in the second row, + // unless both rows are full (i.e. for smaller window widths). + if (this.tiles_.length % 2 === 1 && this.tiles_.length / tilesPerRow < 2) { + return Math.round(this.tileWidth_ / 2); + } + return 0; + } + + + /** + * Returns true if the browser is in RTL. + * @return {boolean} + * @private + */ + isRtl_() { + return document.documentElement.dir === 'rtl'; + } + + + /** + * Translates the |element| by (x, y). + * @param {?Element} element The element to apply the transform to. + * @param {number} x The x value. + * @param {number} y The y value. + * @private + */ + translate_(element, x, y) { + if (!element) { + throw new Error('Invalid element: cannot apply transform'); + } + const rtlX = x * (this.isRtl_() ? -1 : 1); + element.style.transform = 'translate(' + rtlX + 'px, ' + y + 'px)'; + } + + + /** + * Sets up event listeners necessary for tile reordering. + * @param {!Element} tile Tile on which to set the event listeners. + * @param {number} index The tile's index. + * @private + */ + setupReorder_(tile, index) { + tile.setAttribute('index', index); + // Listen for the drag event on the tile instead of the tile container. The + // tile container remains static during the reorder flow. + tile.firstChild.draggable = true; + tile.firstChild.addEventListener('dragstart', (event) => { + this.startReorder_(tile, event); + }); + // Listen for the mouseover event on the tile container. If this is placed + // on the tile instead, it can be triggered while the tile is translated to + // its new position. + tile.addEventListener('mouseover', (event) => { + this.reorderToIndex_(index); + }); + } + + + /** + * Starts the reorder flow. Updates the visual style of the held tile to + * indicate that it is being moved and sets up the relevant event listeners. + * @param {!Element} tile Tile that is being moved. + * @param {!Event} event The 'dragstart' event. Used to obtain the current + * cursor position + * @private + */ + startReorder_(tile, event) { + const index = Number(tile.getAttribute('index')); + + this.itemToReorder_ = index; + this.newIndexOfItemToReorder_ = index; + + // Apply reorder styling. + tile.classList.add(CLASSES.GRID_REORDER); + // Disable other hover/active styling for all tiles. + document.body.classList.add(CLASSES.REORDERING); + + // Set up event listeners for the reorder flow. + const mouseMove = this.trackCursor_(tile, event.pageX, event.pageY); + document.addEventListener('mousemove', mouseMove); + document.addEventListener('mouseup', () => { + document.removeEventListener('mousemove', mouseMove); + this.stopReorder_(tile); + }, {once: true}); + } + + + /** + * Stops the reorder flow. Resets the held tile's visual style and tells the + * EmbeddedSearchAPI that a tile has been moved. + * @param {!Element} tile Tile that has been moved. + * @private + */ + stopReorder_(tile) { + const index = Number(tile.getAttribute('index')); + + // Remove reorder styling. + tile.classList.remove(CLASSES.GRID_REORDER); + document.body.classList.remove(CLASSES.REORDERING); + + // Move the tile to its new position and notify EmbeddedSearchAPI that the + // tile has been moved. + this.applyReorder_(tile, this.newIndexOfItemToReorder_); + chrome.embeddedSearch.newTabPage.reorderCustomLink( + Number(this.tiles_[index].getAttribute('rid')), + this.newIndexOfItemToReorder_); + + this.itemToReorder_ = -1; + this.newIndexOfItemToReorder_ = -1; + } + + + /** + * Executed only when the reorder flow is ongoing. Inserts the currently held + * tile at |index| and shifts tiles accordingly. + * @param {number} index The index to insert the held tile at. + * @private + */ + reorderToIndex_(index) { + if (this.newIndexOfItemToReorder_ === index || + !document.body.classList.contains(CLASSES.REORDERING)) { + return; + } + + // Moves the held tile from its current position to |index|. + this.order_.move(this.newIndexOfItemToReorder_, index); + this.newIndexOfItemToReorder_ = index; + // Shift tiles according to the new order. + for (let i = 0; i < this.tiles_.length; i++) { + const tileIndex = this.order_[i]; + // Don't move the tile we're holding nor the add shortcut button. + if (tileIndex === this.itemToReorder_ || + this.tiles_[i].getAttribute('add') === 'true') { + continue; + } + this.applyReorder_(this.tiles_[tileIndex], i); + } + } + + + /** + * Translates the |tile|'s |CLASSES.GRID_TILE| from |index| to |newIndex|. + * This is done to prevent interference with event listeners on the |tile|'s + * |CLASSES.GRID_TILE_CONTAINER|, particularly 'mouseover'. + * @param {!Element} tile Tile that is being shifted. + * @param {number} newIndex New index for the tile. + * @private + */ + applyReorder_(tile, newIndex) { + if (tile.getAttribute('index') === null) { + throw new Error('Tile does not have an index.'); + } + const index = Number(tile.getAttribute('index')); + const x = this.position_[newIndex].x - this.position_[index].x; + const y = this.position_[newIndex].y - this.position_[index].y; + this.translate_(tile.children[0], x, y); + } + + + /** + * Moves |tile| so that it tracks the cursor's position. This is done by + * translating the |tile|'s |CLASSES.GRID_TILE|, which prevents interference + * with event listeners on the |tile|'s |CLASSES.GRID_TILE_CONTAINER|. + * @param {!Element} tile Tile that is being moved. + * @param {number} origCursorX Original x cursor position. + * @param {number} origCursorY Original y cursor position. + * @private + */ + trackCursor_(tile, origCursorX, origCursorY) { + const index = Number(tile.getAttribute('index')); + // RTL positions align with the right side of the grid. Therefore, the x + // value must be recalculated to align with the left. + const origPosX = this.isRtl_() ? + (this.gridWidth_ - (this.position_[index].x + this.tileWidth_)) : + this.position_[index].x; + const origPosY = this.position_[index].y; + + // Get the max translation allowed by the grid boundaries. This will be the + // x of the last tile in a row and the y of the tiles in the second row. + const maxTranslateX = this.gridWidth_ - this.tileWidth_; + const maxTranslateY = this.tileHeight_; + + const maxX = maxTranslateX - origPosX; + const maxY = maxTranslateY - origPosY; + const minX = 0 - origPosX; + const minY = 0 - origPosY; + + return (event) => { + // Do not exceed the iframe borders. + const x = Math.max(Math.min(event.pageX - origCursorX, maxX), minX); + const y = Math.max(Math.min(event.pageY - origCursorY, maxY), minY); + tile.firstChild.style.transform = 'translate(' + x + 'px, ' + y + 'px)'; + }; + } +} + + +/** * Log an event on the NTP. * @param {number} eventType Event from LOG_TYPE. */ -var logEvent = function(eventType) { +function logEvent(eventType) { chrome.embeddedSearch.newTabPage.logEvent(eventType); -}; +} /** * Log impression of an NTP tile. @@ -261,7 +724,7 @@ function logMostVisitedNavigation( * When we get to 0, we send a message to the parent window. * This is usually used as an EventListener of onload/onerror. */ -var countLoad = function() { +function countLoad() { loadedCounter -= 1; if (loadedCounter <= 0) { swapInNewTiles(); @@ -284,22 +747,22 @@ var countLoad = function() { // fresh tiles. loadedCounter = 1; } -}; +} /** * Handles postMessages coming from the host page to the iframe. * Mostly, it dispatches every command to handleCommand. */ -var handlePostMessage = function(event) { +function handlePostMessage(event) { if (event.data instanceof Array) { - for (var i = 0; i < event.data.length; ++i) { + for (let i = 0; i < event.data.length; ++i) { handleCommand(event.data[i]); } } else { handleCommand(event.data); } -}; +} /** @@ -307,8 +770,8 @@ var handlePostMessage = function(event) { * We try to keep the logic here to a minimum and just dispatch to the relevant * functions. */ -var handleCommand = function(data) { - var cmd = data.cmd; +function handleCommand(data) { + const cmd = data.cmd; if (cmd == 'tile') { addTile(data); @@ -324,37 +787,41 @@ var handleCommand = function(data) { } else { console.error('Unknown command: ' + JSON.stringify(data)); } -}; +} /** * Handler for the 'show' message from the host page. * @param {!Object} info Data received in the message. */ -var showTiles = function(info) { +function showTiles(info) { logEvent(LOG_TYPE.NTP_ALL_TILES_RECEIVED); utils.setPlatformClass(document.body); countLoad(); -}; +} /** * Handler for the 'updateTheme' message from the host page. * @param {!Object} info Data received in the message. */ -var updateTheme = function(info) { +function updateTheme(info) { document.body.style.setProperty('--tile-title-color', info.tileTitleColor); + document.body.style.setProperty( + '--icon-background-color', info.iconBackgroundColor); document.body.classList.toggle('dark-theme', info.isThemeDark); - document.body.classList.toggle('using-theme', info.isUsingTheme); + document.body.classList.toggle('use-title-container', info.useTitleContainer); + document.body.classList.toggle('custom-background', info.customBackground); + document.body.classList.toggle('use-white-add-icon', info.useWhiteAddIcon); document.documentElement.setAttribute('darkmode', info.isDarkMode); // Reduce font weight on the default(white) background for Mac and CrOS. document.body.classList.toggle( CLASSES.MAC_CHROMEOS, - !info.isThemeDark && !info.isUsingTheme && + !info.isThemeDark && !info.useTitleContainer && (navigator.userAgent.indexOf('Mac') > -1 || navigator.userAgent.indexOf('CrOS') > -1)); -}; +} /** @@ -363,26 +830,26 @@ var updateTheme = function(info) { * without saving. * @param {!Object} info Data received in the message. */ -var focusTileMenu = function(info) { - let tile = document.querySelector(`a.md-tile[data-tid="${info.tid}"]`); +function focusTileMenu(info) { + const tile = document.querySelector(`a.md-tile[data-tid="${info.tid}"]`); if (info.tid === -1 /* Add shortcut tile */) { tile.focus(); } else { tile.parentNode.childNodes[1].focus(); } -}; +} /** * Removes all old instances of |IDS.MV_TILES| that are pending for deletion. */ -var removeAllOldTiles = function() { - var parent = document.querySelector('#' + IDS.MOST_VISITED); - var oldList = parent.querySelectorAll('.mv-tiles-old'); - for (var i = 0; i < oldList.length; ++i) { +function removeAllOldTiles() { + const parent = document.querySelector('#' + IDS.MOST_VISITED); + const oldList = parent.querySelectorAll('.mv-tiles-old'); + for (let i = 0; i < oldList.length; ++i) { parent.removeChild(oldList[i]); } -}; +} /** @@ -390,14 +857,14 @@ var removeAllOldTiles = function() { * their thumbnail images, and we are ready to show the new tiles and drop the * old ones. */ -var swapInNewTiles = function() { +function swapInNewTiles() { // Store the tiles on the current closure. - var cur = tiles; + const cur = tiles; // Add an "add new custom link" button if we haven't reached the maximum // number of tiles. if (isCustomLinksEnabled && cur.childNodes.length < maxNumTiles) { - let data = { + const data = { 'tid': -1, 'title': queryArgs['addLink'], 'url': '', @@ -409,13 +876,10 @@ var swapInNewTiles = function() { tiles.appendChild(renderMaterialDesignTile(data)); } - var parent = document.querySelector('#' + IDS.MOST_VISITED); + const parent = document.querySelector('#' + IDS.MOST_VISITED); - // Only fade in the new tiles if there were tiles before. - var fadeIn = false; - var old = parent.querySelector('#' + IDS.MV_TILES); + const old = parent.querySelector('#' + IDS.MV_TILES); if (old) { - fadeIn = true; // Mark old tile DIV for removal after the transition animation is done. old.removeAttribute('id'); old.classList.add('mv-tiles-old'); @@ -431,27 +895,35 @@ var swapInNewTiles = function() { cur.id = IDS.MV_TILES; parent.appendChild(cur); - // Re-balance the tiles if there are more than |MD_MAX_TILES_PER_ROW| in order - // to make even rows. - if (cur.childNodes.length > MD_MAX_TILES_PER_ROW) { - cur.style.maxWidth = 'calc(var(--md-tile-width) * ' + - Math.ceil(cur.childNodes.length / 2) + ')'; + if (isGridEnabled) { + // Initialize the new tileset before modifying opacity. This will prevent + // the transform transition from applying after the tiles fade in. + currGrid.init(cur); + } else { + // Re-balance the tiles if there are more than |MD_MAX_TILES_PER_ROW| in + // order to make even rows. + if (cur.childNodes.length > MD_MAX_TILES_PER_ROW) { + cur.style.maxWidth = 'calc(var(--md-tile-width) * ' + + Math.ceil(cur.childNodes.length / 2) + ')'; + } } - // Prevent keyboard navigation to tiles that are not visible. - updateTileVisibility(); + const flushOpacity = () => window.getComputedStyle(cur).opacity; // getComputedStyle causes the initial style (opacity 0) to be applied, so // that when we then set it to 1, that triggers the CSS transition. - if (fadeIn) { - const style = window.getComputedStyle(cur).opacity; - } + flushOpacity(); cur.style.opacity = 1.0; + if (document.documentElement.classList.contains(CLASSES.NO_INITIAL_FADE)) { + flushOpacity(); + document.documentElement.classList.remove(CLASSES.NO_INITIAL_FADE); + } + // Make sure the tiles variable contain the next tileset we'll use if the host // page sends us an updated set of tiles. tiles = document.createElement('div'); -}; +} /** @@ -480,19 +952,16 @@ function updateTileVisibility() { * It's also used to fill up our tiles to |maxNumTiles| if necessary. * @param {?MostVisitedData} args Data for the tile to be rendered. */ -var addTile = function(args) { +function addTile(args) { if (isFinite(args.rid)) { // An actual suggestion. Grab the data from the embeddedSearch API. - var data = + const data = chrome.embeddedSearch.newTabPage.getMostVisitedItemData(args.rid); if (!data) { return; } data.tid = data.rid; - // Use a dark icon if dark mode is enabled. Keep value in sync with - // NtpIconSource. - data.dark = args.darkMode ? 'dark/' : ''; if (!data.faviconUrl) { data.faviconUrl = 'chrome-search://favicon/size/16@' + window.devicePixelRatio + 'x/' + data.renderViewId + '/' + data.tid; @@ -502,7 +971,7 @@ var addTile = function(args) { // An empty tile tiles.appendChild(renderTile(null)); } -}; +} /** * Called when the user decided to add a tile to the blacklist. @@ -510,8 +979,8 @@ var addTile = function(args) { * to the host page. * @param {Element} tile DOM node of the tile we want to remove. */ -var blacklistTile = function(tile) { - let tid = Number(tile.firstChild.getAttribute('data-tid')); +function blacklistTile(tile) { + const tid = Number(tile.firstChild.getAttribute('data-tid')); if (isCustomLinksEnabled) { chrome.embeddedSearch.newTabPage.deleteMostVisitedItem(tid); @@ -525,7 +994,7 @@ var blacklistTile = function(tile) { {cmd: 'tileBlacklisted', tid: Number(tid)}, DOMAIN_ORIGIN); }); } -}; +} /** @@ -598,19 +1067,19 @@ function setupReorder(tile) { // Cancel the timeout if the user drags the mouse off the tile and // releases or if the mouse if released. - let dragend = () => { + const dragend = () => { window.clearTimeout(timeout); }; document.addEventListener('dragend', dragend, {once: true}); - let mouseup = () => { + const mouseup = () => { if (event.button == 0 /* LEFT CLICK */) { window.clearTimeout(timeout); } }; document.addEventListener('mouseup', mouseup, {once: true}); - let timeoutFunc = (dragend_in, mouseup_in) => { + const timeoutFunc = (dragend_in, mouseup_in) => { if (!reordering) { startReorder(tile); } @@ -653,9 +1122,9 @@ function setupReorder(tile) { * construct an empty tile. isAddButton can only be set if custom links is * enabled. */ -var renderTile = function(data) { +function renderTile(data) { return renderMaterialDesignTile(data); -}; +} /** @@ -667,7 +1136,7 @@ var renderTile = function(data) { * @return {Element} */ function renderMaterialDesignTile(data) { - let mdTileContainer = document.createElement('div'); + const mdTileContainer = document.createElement('div'); mdTileContainer.role = 'none'; if (data == null) { @@ -681,7 +1150,7 @@ function renderMaterialDesignTile(data) { // This is set in the load/error event for the favicon image. let tileType = TileVisualType.NONE; - let mdTile = document.createElement('a'); + const mdTile = document.createElement('a'); mdTile.className = CLASSES.MD_TILE; mdTile.tabIndex = 0; mdTile.setAttribute('data-tid', data.tid); @@ -727,27 +1196,30 @@ function renderMaterialDesignTile(data) { }); utils.disableOutlineOnMouseClick(mdTile); - let mdTileInner = document.createElement('div'); + const mdTileInner = document.createElement('div'); mdTileInner.className = CLASSES.MD_TILE_INNER; - let mdIcon = document.createElement('div'); - mdIcon.className = CLASSES.MD_ICON; + const mdIcon = document.createElement('div'); + mdIcon.classList.add(CLASSES.MD_ICON); + mdIcon.classList.add(CLASSES.MD_ICON_BACKGROUND); if (data.isAddButton) { - let mdAdd = document.createElement('div'); + const mdAdd = document.createElement('div'); mdAdd.className = CLASSES.MD_ADD_ICON; - let addBackground = document.createElement('div'); + const addBackground = document.createElement('div'); addBackground.className = CLASSES.MD_ICON_BACKGROUND; addBackground.appendChild(mdAdd); mdIcon.appendChild(addBackground); } else { - let fi = document.createElement('img'); + const fi = document.createElement('img'); // Set title and alt to empty so screen readers won't say the image name. fi.title = ''; fi.alt = ''; - fi.src = 'chrome-search://ntpicon/size/24@' + window.devicePixelRatio + - 'x/' + data.dark + data.url; + const url = new URL('chrome-search://ntpicon/'); + url.searchParams.set('size', '24@' + window.devicePixelRatio + 'x'); + url.searchParams.set('url', data.url); + fi.src = url.toString(); loadedCounter += 1; fi.addEventListener('load', function(ev) { // Store the type for a potential later navigation. @@ -761,9 +1233,9 @@ function renderMaterialDesignTile(data) { countLoad(); }); fi.addEventListener('error', function(ev) { - let fallbackBackground = document.createElement('div'); + const fallbackBackground = document.createElement('div'); fallbackBackground.className = CLASSES.MD_ICON_BACKGROUND; - let fallbackLetter = document.createElement('div'); + const fallbackLetter = document.createElement('div'); fallbackLetter.className = CLASSES.MD_FALLBACK_LETTER; fallbackLetter.textContent = data.title.charAt(0).toUpperCase(); mdIcon.classList.add(CLASSES.FAILED_FAVICON); @@ -788,11 +1260,11 @@ function renderMaterialDesignTile(data) { mdTileInner.appendChild(mdIcon); - let mdTitleContainer = document.createElement('div'); + const mdTitleContainer = document.createElement('div'); mdTitleContainer.className = CLASSES.MD_TITLE_CONTAINER; - let mdTitle = document.createElement('div'); + const mdTitle = document.createElement('div'); mdTitle.className = CLASSES.MD_TITLE; - let mdTitleTextwrap = document.createElement('span'); + const mdTitleTextwrap = document.createElement('span'); mdTitleTextwrap.innerText = data.title; mdTitle.style.direction = data.direction || 'ltr'; mdTitleContainer.appendChild(mdTitle); @@ -802,7 +1274,7 @@ function renderMaterialDesignTile(data) { mdTitle.appendChild(mdTitleTextwrap); if (!data.isAddButton) { - let mdMenu = document.createElement('button'); + const mdMenu = document.createElement('button'); mdMenu.className = CLASSES.MD_MENU; if (isCustomLinksEnabled) { mdMenu.classList.add(CLASSES.MD_EDIT_MENU); @@ -837,20 +1309,24 @@ function renderMaterialDesignTile(data) { mdTileContainer.appendChild(mdMenu); } - // Enable reordering. - if (isCustomLinksEnabled && !data.isAddButton) { - mdTileContainer.draggable = 'true'; - setupReorder(mdTileContainer); + if (isGridEnabled) { + return currGrid.createGridTile( + mdTileContainer, data.tid, !!data.isAddButton); + } else { + // Enable reordering. + if (isCustomLinksEnabled && !data.isAddButton) { + mdTileContainer.draggable = 'true'; + setupReorder(mdTileContainer); + } + return mdTileContainer; } - - return mdTileContainer; } /** * Does some initialization and parses the query arguments passed to the iframe. */ -var init = function() { +function init() { // Create a new DOM element to hold the tiles. The tiles will be added // one-by-one via addTile, and the whole thing will be inserted into the page // in swapInNewTiles, after the parent has sent us the 'show' message, and all @@ -858,10 +1334,10 @@ var init = function() { tiles = document.createElement('div'); // Parse query arguments. - var query = window.location.search.substring(1).split('&'); + const query = window.location.search.substring(1).split('&'); queryArgs = {}; - for (var i = 0; i < query.length; ++i) { - var val = query[i].split('='); + for (let i = 0; i < query.length; ++i) { + const val = query[i].split('='); if (val[0] == '') { continue; } @@ -872,8 +1348,7 @@ var init = function() { // Enable RTL. if (queryArgs['rtl'] == '1') { - var html = document.querySelector('html'); - html.dir = 'rtl'; + document.documentElement.dir = 'rtl'; } // Enable custom links. @@ -881,26 +1356,55 @@ var init = function() { isCustomLinksEnabled = true; } + // Enable grid layout. + if (queryArgs['enableGrid'] == '1') { + isGridEnabled = true; + document.body.classList.add(CLASSES.GRID_LAYOUT); + } + // Set the maximum number of tiles to show. if (isCustomLinksEnabled) { maxNumTiles = MD_MAX_NUM_CUSTOM_LINK_TILES; } - // Throttle the resize event. + currGrid = new Grid(); + // Set up layout updates on window resize. Throttled according to + // |RESIZE_TIMEOUT_DELAY|. let resizeTimeout; window.onresize = () => { if (resizeTimeout) { - return; + window.clearTimeout(resizeTimeout); } resizeTimeout = window.setTimeout(() => { resizeTimeout = null; - updateTileVisibility(); + if (isGridEnabled) { + currGrid.onResize(); + } else { + updateTileVisibility(); + } }, RESIZE_TIMEOUT_DELAY); }; window.addEventListener('message', handlePostMessage); -}; +} -window.addEventListener('DOMContentLoaded', init); -})(); +/** + * Binds event listeners. + */ +function listen() { + document.addEventListener('DOMContentLoaded', init); +} + + +return { + Grid: Grid, // Exposed for testing. + init: init, // Exposed for testing. + enableGridLayoutForTesting: enableGridLayoutForTesting, + listen: listen, +}; +} + +if (!window.mostVisitedUnitTest) { + MostVisited().listen(); +} |