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>2018-08-28 15:28:34 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-08-28 13:54:51 +0000
commit2a19c63448c84c1805fb1a585c3651318bb86ca7 (patch)
treeeb17888e8531aa6ee5e85721bd553b832a7e5156 /chromium/chrome/browser/resources/local_ntp/most_visited_single.js
parentb014812705fc80bff0a5c120dfcef88f349816dc (diff)
BASELINE: Update Chromium to 69.0.3497.70
Change-Id: I2b7b56e4e7a8b26656930def0d4575dc32b900a0 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@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.js368
1 files changed, 277 insertions, 91 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 cad4e9030e3..72886ffb499 100644
--- a/chromium/chrome/browser/resources/local_ntp/most_visited_single.js
+++ b/chromium/chrome/browser/resources/local_ntp/most_visited_single.js
@@ -8,21 +8,36 @@
/**
+ * Enum for ids.
+ * @enum {string}
+ * @const
+ */
+const IDS = {
+ MV_TILES: 'mv-tiles', // Most Visited tiles container.
+};
+
+
+/**
* Enum for classnames.
* @enum {string}
* @const
*/
const CLASSES = {
FAILED_FAVICON: 'failed-favicon', // Applied when the favicon fails to load.
- MATERIAL_DESIGN: 'md', // Applies Material Design styles to the page.
// Material Design classes.
+ MATERIAL_DESIGN: 'md', // Applies Material Design styles to the page.
MD_EMPTY_TILE: 'md-empty-tile',
+ MD_FALLBACK_BACKGROUND: 'md-fallback-background',
+ MD_FALLBACK_LETTER: 'md-fallback-letter',
MD_FAVICON: 'md-favicon',
- MD_LINK: 'md-link',
MD_ICON: 'md-icon',
MD_ICON_BACKGROUND: 'md-icon-background',
+ MD_ADD_ICON: 'md-add-icon',
+ MD_ADD_BACKGROUND: 'md-add-background',
MD_MENU: 'md-menu',
+ MD_EDIT_MENU: 'md-edit-menu',
MD_TILE: 'md-tile',
+ MD_TILE_CONTAINER: 'md-tile-container',
MD_TILE_INNER: 'md-tile-inner',
MD_TITLE: 'md-title',
MD_TITLE_CONTAINER: 'md-title-container',
@@ -45,6 +60,13 @@ var LOG_TYPE = {
// before the actual DOM elements have loaded (in particular the thumbnail
// images).
NTP_ALL_TILES_RECEIVED: 12,
+
+ // Shortcuts have been customized.
+ NTP_SHORTCUT_CUSTOMIZED: 39,
+ // The 'Add shortcut' link was clicked.
+ NTP_CUSTOMIZE_ADD_SHORTCUT_CLICKED: 44,
+ // The 'Edit shortcut' link was clicked.
+ NTP_CUSTOMIZE_EDIT_SHORTCUT_CLICKED: 45,
};
@@ -63,13 +85,11 @@ var TileVisualType = {
THUMBNAIL_FAILED: 8,
};
-
/**
- * Total number of tiles to show at any time. If the host page doesn't send
- * enough tiles, we fill them blank.
+ * Number of tiles per row for Material Design.
* @const {number}
*/
-var NUMBER_OF_TILES = 8;
+const MD_MAX_TILES_PER_ROW = 5;
/**
@@ -104,6 +124,16 @@ var tiles = null;
/**
+ * Maximum number of MostVisited tiles to show at any time. If the host page
+ * doesn't send enough tiles and custom links is not enabled, we fill them blank
+ * tiles. This can be changed depending on what feature is enabled. Set by the
+ * host page, while 8 is default.
+ * @type {number}
+ */
+let maxNumTiles = 8;
+
+
+/**
* List of parameters passed by query args.
* @type {Object}
*/
@@ -118,6 +148,20 @@ let isMDEnabled = false;
/**
+ * True if custom links is enabled.
+ * @type {boolean}
+ */
+let isCustomLinksEnabled = false;
+
+
+/**
+ * True if the tiles to be shown are custom links.
+ * @type {boolean}
+ */
+let tilesAreCustomLinks = false;
+
+
+/**
* Log an event on the NTP.
* @param {number} eventType Event from LOG_TYPE.
*/
@@ -127,7 +171,7 @@ var logEvent = function(eventType) {
/**
* Log impression of an NTP tile.
- * @param {number} tileIndex Position of the tile, >= 0 and < NUMBER_OF_TILES.
+ * @param {number} tileIndex Position of the tile, >= 0 and < |maxNumTiles|.
* @param {number} tileTitleSource The source of the tile's title as received
* from getMostVisitedItemData.
* @param {number} tileSource The tile's source as received from
@@ -144,7 +188,7 @@ function logMostVisitedImpression(
/**
* Log click on an NTP tile.
- * @param {number} tileIndex Position of the tile, >= 0 and < NUMBER_OF_TILES.
+ * @param {number} tileIndex Position of the tile, >= 0 and < |maxNumTiles|.
* @param {number} tileTitleSource The source of the tile's title as received
* from getMostVisitedItemData.
* @param {number} tileSource The tile's source as received from
@@ -169,7 +213,21 @@ var countLoad = function() {
if (loadedCounter <= 0) {
swapInNewTiles();
logEvent(LOG_TYPE.NTP_ALL_TILES_LOADED);
- window.parent.postMessage({cmd: 'loaded'}, DOMAIN_ORIGIN);
+ // Note that it's easiest to capture this when all custom links are loaded,
+ // rather than when the impression for each link is logged.
+ if (isCustomLinksEnabled && tilesAreCustomLinks) {
+ chrome.embeddedSearch.newTabPage.logEvent(
+ LOG_TYPE.NTP_SHORTCUT_CUSTOMIZED);
+ }
+ // Tell the parent page whether to show the restore default shortcuts option
+ // in the menu.
+ window.parent.postMessage(
+ {
+ cmd: 'loaded',
+ showRestoreDefault: (isCustomLinksEnabled && tilesAreCustomLinks)
+ },
+ DOMAIN_ORIGIN);
+ tilesAreCustomLinks = false;
// Reset to 1, so that any further 'show' message will cause us to swap in
// fresh tiles.
loadedCounter = 1;
@@ -231,6 +289,7 @@ var showTiles = function(info) {
var updateTheme = function(info) {
document.body.style.setProperty('--tile-title-color', info.tileTitleColor);
document.body.classList.toggle('dark-theme', info.isThemeDark);
+ document.body.classList.toggle('using-theme', info.isUsingTheme);
};
@@ -255,9 +314,24 @@ var swapInNewTiles = function() {
// Store the tiles on the current closure.
var cur = tiles;
- // Create empty tiles until we have NUMBER_OF_TILES.
- while (cur.childNodes.length < NUMBER_OF_TILES) {
- addTile({});
+ // 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 = {
+ 'tid': -1,
+ 'title': 'Add shortcut', // TODO(851293): Use translated strings.
+ 'url': '',
+ 'isAddButton': true,
+ };
+ tiles.appendChild(renderMaterialDesignTile(data));
+ }
+
+ // Create empty tiles until we have |maxNumTiles|. This is not required for
+ // the Material Design style tiles.
+ if (!isMDEnabled) {
+ while (cur.childNodes.length < maxNumTiles) {
+ addTile({});
+ }
}
var parent = document.querySelector('#most-visited');
@@ -281,11 +355,20 @@ var swapInNewTiles = function() {
// Add new tileset.
cur.id = 'mv-tiles';
parent.appendChild(cur);
+
+ // If this is Material Design, re-balance the tiles if there are more than
+ // |MD_MAX_TILES_PER_ROW| in order to make even rows.
if (isMDEnabled) {
// Called after appending to document so that css styles are active.
truncateTitleText(
parent.lastChild.querySelectorAll('.' + CLASSES.MD_TITLE));
+
+ if (cur.childNodes.length > MD_MAX_TILES_PER_ROW) {
+ cur.style.maxWidth =
+ 'calc(var(--md-tile-width) * ' + Math.ceil(cur.childNodes.length / 2);
+ }
}
+
// 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) {
@@ -298,10 +381,11 @@ var swapInNewTiles = function() {
tiles = document.createElement('div');
};
-
/**
- * Truncates titles that are longer than two lines and appends an ellipsis. Text
- * overflow in CSS ("text-overflow: ellipsis") cannot handle multiple lines.
+ * Truncates titles that are longer than one line and appends an ellipsis. Text
+ * overflow in CSS ("text-overflow: ellipsis") requires "overflow: hidden",
+ * which will cut off the title's text shadow. Only used for Material Design
+ * tiles.
*/
function truncateTitleText(titles) {
for (let i = 0; i < titles.length; i++) {
@@ -311,17 +395,16 @@ function truncateTitleText(titles) {
while (el.scrollHeight > el.offsetHeight && truncatedTitle.length > 0) {
el.innerText = (truncatedTitle = truncatedTitle.slice(0, -1)) + '...';
}
- if (truncatedTitle.length == 0) {
+ if (truncatedTitle.length === 0) {
console.error('Title truncation failed: ' + originalTitle);
}
}
}
-
/**
* Handler for the 'show' message from the host page, called when it wants to
* add a suggestion tile.
- * It's also used to fill up our tiles to NUMBER_OF_TILES if necessary.
+ * It's also used to fill up our tiles to |maxNumTiles| if necessary.
* @param {object} args Data for the tile to be rendered.
*/
var addTile = function(args) {
@@ -351,19 +434,34 @@ var addTile = function(args) {
* @param {Element} tile DOM node of the tile we want to remove.
*/
var blacklistTile = function(tile) {
- tile.classList.add('blacklisted');
- tile.addEventListener('transitionend', function(ev) {
- if (ev.propertyName != 'width')
- return;
+ let tid = isMDEnabled ? Number(tile.firstChild.getAttribute('data-tid')) :
+ Number(tile.getAttribute('data-tid'));
- window.parent.postMessage(
- {cmd: 'tileBlacklisted', tid: Number(tile.getAttribute('data-tid'))},
- DOMAIN_ORIGIN);
- });
+ if (isCustomLinksEnabled) {
+ chrome.embeddedSearch.newTabPage.deleteMostVisitedItem(tid);
+ } else {
+ tile.classList.add('blacklisted');
+ tile.addEventListener('transitionend', function(ev) {
+ if (ev.propertyName != 'width')
+ return;
+ window.parent.postMessage(
+ {cmd: 'tileBlacklisted', tid: Number(tid)}, DOMAIN_ORIGIN);
+ });
+ }
};
/**
+ * Starts edit custom link flow. Tells host page to show the edit custom link
+ * dialog and pre-populate it with data obtained using the link's id.
+ * @param {?number} tid Restricted id of the tile we want to edit.
+ */
+function editCustomLink(tid) {
+ window.parent.postMessage({cmd: 'startEditLink', tid: tid}, DOMAIN_ORIGIN);
+}
+
+
+/**
* Returns whether the given URL has a known, safe scheme.
* @param {string} url URL to check.
*/
@@ -374,9 +472,27 @@ var isSchemeAllowed = function(url) {
/**
+ * Disables the focus outline for |element| on mousedown.
+ * @param {Element} element The element to remove the focus outline from.
+ */
+function disableOutlineOnMouseClick(element) {
+ element.addEventListener('mousedown', (event) => {
+ element.classList.add('mouse-navigation');
+ let resetOutline = (event) => {
+ element.classList.remove('mouse-navigation');
+ element.removeEventListener('blur', resetOutline);
+ };
+ element.addEventListener('blur', resetOutline);
+ });
+}
+
+
+/**
* Renders a MostVisited tile to the DOM.
- * @param {object} data Object containing rid, url, title, favicon, thumbnail.
- * data is null if you want to construct an empty tile.
+ * @param {object} data Object containing rid, url, title, favicon, thumbnail,
+ * and optionally isAddButton. isAddButton is true if you want to construct
+ * an add custom link button. data is null if you want to construct an
+ * empty tile. isAddButton can only be set if custom links is enabled.
*/
var renderTile = function(data) {
if (isMDEnabled) {
@@ -540,24 +656,32 @@ var renderMostVisitedTile = function(data) {
/**
* Renders a MostVisited tile with Material Design styles.
- * @param {object} data Object containing rid, url, title, favicon. data is null
- * if you want to construct an empty tile.
+ * @param {object} data Object containing rid, url, title, favicon, and
+ * optionally isAddButton. isAddButton is if you want to construct an add
+ * custom link button. data is null if you want to construct an empty tile.
* @return {Element}
*/
function renderMaterialDesignTile(data) {
- let mdTile = document.createElement('a');
+ let mdTileContainer = document.createElement('div');
+ mdTileContainer.role = 'none';
if (data == null) {
- mdTile.className = CLASSES.MD_EMPTY_TILE;
- return mdTile;
+ mdTileContainer.className = CLASSES.MD_EMPTY_TILE;
+ return mdTileContainer;
}
+ mdTileContainer.className = CLASSES.MD_TILE_CONTAINER;
+
+ if (data.isCustomLink)
+ tilesAreCustomLinks = true;
// The tile will be appended to tiles.
const position = tiles.children.length;
// This is set in the load/error event for the favicon image.
let tileType = TileVisualType.NONE;
+ let mdTile = document.createElement('a');
mdTile.className = CLASSES.MD_TILE;
+ mdTile.tabIndex = 0;
mdTile.setAttribute('data-tid', data.tid);
mdTile.setAttribute('data-pos', position);
if (isSchemeAllowed(data.url)) {
@@ -567,71 +691,100 @@ function renderMaterialDesignTile(data) {
mdTile.title = data.title;
mdTile.addEventListener('click', function(ev) {
- logMostVisitedNavigation(
- position, data.tileTitleSource, data.tileSource, tileType,
- data.dataGenerationTime);
+ if (data.isAddButton) {
+ editCustomLink();
+ logEvent(LOG_TYPE.NTP_CUSTOMIZE_ADD_SHORTCUT_CLICKED);
+ } else {
+ logMostVisitedNavigation(
+ position, data.tileTitleSource, data.tileSource, tileType,
+ data.dataGenerationTime);
+ }
});
mdTile.addEventListener('keydown', function(event) {
- if (event.keyCode == 46 /* DELETE */ ||
- event.keyCode == 8 /* BACKSPACE */) {
+ if ((event.keyCode == 46 /* DELETE */ ||
+ event.keyCode == 8 /* BACKSPACE */) &&
+ !data.isAddButton) {
event.preventDefault();
event.stopPropagation();
- blacklistTile(this);
+ blacklistTile(mdTileContainer);
} else if (
event.keyCode == 13 /* ENTER */ || event.keyCode == 32 /* SPACE */) {
event.preventDefault();
this.click();
} else if (event.keyCode == 37 /* LEFT */) {
const tiles = document.querySelectorAll('#mv-tiles .' + CLASSES.MD_TILE);
- tiles[Math.max(this.getAttribute('data-pos') - 1, 0)].focus();
+ tiles[Math.max(Number(this.getAttribute('data-pos')) - 1, 0)].focus();
} else if (event.keyCode == 39 /* RIGHT */) {
const tiles = document.querySelectorAll('#mv-tiles .' + CLASSES.MD_TILE);
- tiles[Math.min(this.getAttribute('data-pos') + 1, tiles.length - 1)]
+ tiles[Math.min(
+ Number(this.getAttribute('data-pos')) + 1, tiles.length - 1)]
.focus();
}
});
+ disableOutlineOnMouseClick(mdTile);
let mdTileInner = document.createElement('div');
mdTileInner.className = CLASSES.MD_TILE_INNER;
let mdIcon = document.createElement('div');
mdIcon.className = CLASSES.MD_ICON;
- let mdIconBackground = document.createElement('div');
- mdIconBackground.className = CLASSES.MD_ICON_BACKGROUND;
let mdFavicon = document.createElement('div');
mdFavicon.className = CLASSES.MD_FAVICON;
- let fi = document.createElement('img');
- fi.src = data.faviconUrl;
- // Set title and alt to empty so screen readers won't say the image name.
- fi.title = '';
- fi.alt = '';
- loadedCounter += 1;
- fi.addEventListener('load', function(ev) {
- // Store the type for a potential later navigation.
- tileType = TileVisualType.ICON_REAL;
- logMostVisitedImpression(
- position, data.tileTitleSource, data.tileSource, tileType,
- data.dataGenerationTime);
- // Note: It's important to call countLoad last, because that might emit the
- // NTP_ALL_TILES_LOADED event, which must happen after the impression log.
- countLoad();
- });
- fi.addEventListener('error', function(ev) {
- mdFavicon.classList.add(CLASSES.FAILED_FAVICON);
- thumb.removeChild(img);
- // Store the type for a potential later navigation.
- tileType = TileVisualType.ICON_DEFAULT;
- logMostVisitedImpression(
- position, data.tileTitleSource, data.tileSource, tileType,
- data.dataGenerationTime);
- // Note: It's important to call countLoad last, because that might emit the
- // NTP_ALL_TILES_LOADED event, which must happen after the impression log.
- countLoad();
- });
- mdFavicon.appendChild(fi);
- mdIconBackground.appendChild(mdFavicon);
- mdIcon.appendChild(mdIconBackground);
+ if (data.isAddButton) {
+ let mdAdd = document.createElement('div');
+ mdAdd.className = CLASSES.MD_ADD_ICON;
+ let addBackground = document.createElement('div');
+ addBackground.className = CLASSES.MD_ADD_BACKGROUND;
+
+ addBackground.appendChild(mdAdd);
+ mdFavicon.appendChild(addBackground);
+ } else {
+ let 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.url;
+ loadedCounter += 1;
+ fi.addEventListener('load', function(ev) {
+ // Store the type for a potential later navigation.
+ tileType = TileVisualType.ICON_REAL;
+ logMostVisitedImpression(
+ position, data.tileTitleSource, data.tileSource, tileType,
+ data.dataGenerationTime);
+ // Note: It's important to call countLoad last, because that might emit
+ // the NTP_ALL_TILES_LOADED event, which must happen after the impression
+ // log.
+ countLoad();
+ });
+ fi.addEventListener('error', function(ev) {
+ let fallbackBackground = document.createElement('div');
+ fallbackBackground.className = CLASSES.MD_FALLBACK_BACKGROUND;
+ let fallbackLetter = document.createElement('div');
+ fallbackLetter.className = CLASSES.MD_FALLBACK_LETTER;
+ fallbackLetter.innerText = data.title.charAt(0).toUpperCase();
+ mdFavicon.classList.add(CLASSES.FAILED_FAVICON);
+
+ fallbackBackground.appendChild(fallbackLetter);
+ mdFavicon.removeChild(fi);
+ mdFavicon.appendChild(fallbackBackground);
+
+ // Store the type for a potential later navigation.
+ tileType = TileVisualType.ICON_DEFAULT;
+ logMostVisitedImpression(
+ position, data.tileTitleSource, data.tileSource, tileType,
+ data.dataGenerationTime);
+ // Note: It's important to call countLoad last, because that might emit
+ // the NTP_ALL_TILES_LOADED event, which must happen after the impression
+ // log.
+ countLoad();
+ });
+
+ mdFavicon.appendChild(fi);
+ }
+
+ mdIcon.appendChild(mdFavicon);
mdTileInner.appendChild(mdIcon);
let mdTitleContainer = document.createElement('div');
@@ -642,25 +795,46 @@ function renderMaterialDesignTile(data) {
mdTitle.style.direction = data.direction || 'ltr';
mdTitleContainer.appendChild(mdTitle);
mdTileInner.appendChild(mdTitleContainer);
+ mdTile.appendChild(mdTileInner);
+ mdTileContainer.appendChild(mdTile);
+
+ if (!data.isAddButton) {
+ let mdMenu = document.createElement('button');
+ mdMenu.className = CLASSES.MD_MENU;
+ if (isCustomLinksEnabled) {
+ mdMenu.classList.add(CLASSES.MD_EDIT_MENU);
+ mdMenu.title = queryArgs['editLinkTooltip'] || '';
+ mdMenu.setAttribute(
+ 'aria-label',
+ (queryArgs['editLinkTooltip'] || '') + ' ' + data.title);
+ mdMenu.addEventListener('click', function(ev) {
+ editCustomLink(data.tid);
+ ev.preventDefault();
+ ev.stopPropagation();
+ logEvent(LOG_TYPE.NTP_CUSTOMIZE_EDIT_SHORTCUT_CLICKED);
+ });
+ } else {
+ mdMenu.title = queryArgs['removeTooltip'] || '';
+ mdMenu.setAttribute(
+ 'aria-label', (queryArgs['removeTooltip'] || '') + ' ' + data.title);
+ mdMenu.addEventListener('click', function(ev) {
+ removeAllOldTiles();
+ blacklistTile(mdTileContainer);
+ ev.preventDefault();
+ ev.stopPropagation();
+ });
+ }
+ // Don't allow the event to bubble out to the containing tile, as that would
+ // trigger navigation to the tile URL.
+ mdMenu.addEventListener('keydown', function(ev) {
+ event.stopPropagation();
+ });
+ disableOutlineOnMouseClick(mdMenu);
- let mdMenu = document.createElement('button');
- mdMenu.className = CLASSES.MD_MENU;
- mdMenu.title = queryArgs['removeTooltip'] || '';
- mdMenu.addEventListener('click', function(ev) {
- removeAllOldTiles();
- blacklistTile(mdTile);
- ev.preventDefault();
- ev.stopPropagation();
- });
- // Don't allow the event to bubble out to the containing tile, as that would
- // trigger navigation to the tile URL.
- mdMenu.addEventListener('keydown', function(event) {
- event.stopPropagation();
- });
+ mdTileContainer.appendChild(mdMenu);
+ }
- mdTile.appendChild(mdTileInner);
- mdTile.appendChild(mdMenu);
- return mdTile;
+ return mdTileContainer;
}
@@ -684,6 +858,8 @@ var init = function() {
queryArgs[decodeURIComponent(val[0])] = decodeURIComponent(val[1]);
}
+ document.title = queryArgs['title'];
+
if ('ntl' in queryArgs) {
var ntl = parseInt(queryArgs['ntl'], 10);
if (isFinite(ntl))
@@ -702,6 +878,16 @@ var init = function() {
document.body.classList.add(CLASSES.MATERIAL_DESIGN);
}
+ // Enable custom links.
+ if (queryArgs['enableCustomLinks'] == '1') {
+ isCustomLinksEnabled = true;
+ }
+
+ // Set the maximum number of tiles to show.
+ if (isCustomLinksEnabled) {
+ maxNumTiles = 10;
+ }
+
window.addEventListener('message', handlePostMessage);
};