diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-08-28 15:28:34 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-08-28 13:54:51 +0000 |
commit | 2a19c63448c84c1805fb1a585c3651318bb86ca7 (patch) | |
tree | eb17888e8531aa6ee5e85721bd553b832a7e5156 /chromium/chrome/browser/resources/local_ntp/most_visited_single.js | |
parent | b014812705fc80bff0a5c120dfcef88f349816dc (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.js | 368 |
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); }; |