summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/resources/local_ntp/most_visited_single.js
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2019-07-31 15:50:41 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2019-08-30 12:35:23 +0000
commit7b2ffa587235a47d4094787d72f38102089f402a (patch)
tree30e82af9cbab08a7fa028bb18f4f2987a3f74dfa /chromium/chrome/browser/resources/local_ntp/most_visited_single.js
parentd94af01c90575348c4e81a418257f254b6f8d225 (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.js700
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();
+}