blob: 03609458d0a80518d0dbd463ccd1061241143fd5 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.m.js';
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
import 'chrome://resources/cr_elements/cr_icons_css.m.js';
import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
import 'chrome://resources/cr_elements/cr_toast/cr_toast.m.js';
import 'chrome://resources/cr_elements/hidden_style_css.m.js';
import 'chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js';
import 'chrome://resources/mojo/mojo/public/mojom/base/text_direction.mojom-lite.js';
import {assert} from 'chrome://resources/js/assert.m.js';
import {isMac} from 'chrome://resources/js/cr.m.js';
import {FocusOutlineManager} from 'chrome://resources/js/cr/ui/focus_outline_manager.m.js';
import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
import {hasKeyModifiers} from 'chrome://resources/js/util.m.js';
import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {I18nBehavior, loadTimeData} from './i18n_setup.js';
import {NewTabPageProxy} from './new_tab_page_proxy.js';
import {WindowProxy} from './window_proxy.js';
/**
* @enum {number}
* @const
*/
const ScreenWidth = {
NARROW: 0,
MEDIUM: 1,
WIDE: 2,
};
/**
* @param {!HTMLElement} tile
* @private
*/
function resetTilePosition(tile) {
tile.style.position = '';
tile.style.left = '';
tile.style.top = '';
}
/**
* @param {!HTMLElement} tile
* @param {!{x: number, y: number}} point
* @private
*/
function setTilePosition(tile, {x, y}) {
tile.style.position = 'fixed';
tile.style.left = `${x}px`;
tile.style.top = `${y}px`;
}
/**
* @param {!Array<!DOMRect>} rects
* @param {number} x
* @param {number} y
* @return {number}
* @private
*/
function getHitIndex(rects, x, y) {
return rects.findIndex(
r => x >= r.left && x <= r.right && y >= r.top && y <= r.bottom);
}
/**
* Returns null if URL is not valid.
* @param {string} urlString
* @return {URL}
* @private
*/
function normalizeUrl(urlString) {
try {
const url = new URL(
urlString.includes('://') ? urlString : `https://${urlString}/`);
if (['http:', 'https:'].includes(url.protocol)) {
return url;
}
} catch (e) {
}
return null;
}
/**
* @polymer
* @extends {PolymerElement}
*/
class MostVisitedElement extends mixinBehaviors
([I18nBehavior], PolymerElement) {
static get is() {
return 'ntp-most-visited';
}
static get template() {
return html`{__html_template__}`;
}
static get properties() {
return {
/**
* When the tile icon background is dark, the add icon color is white for
* contrast. This can be used to determine the color of the tile hover as
* well.
*/
useWhiteAddIcon: {
type: Boolean,
reflectToAttribute: true,
},
/* If true wraps the tile titles in white pills. */
useTitlePill: {
type: Boolean,
reflectToAttribute: true,
},
/** @private */
columnCount_: {
type: Number,
computed: `computeColumnCount_(tiles_, screenWidth_, maxTiles_)`,
},
/** @private */
rowCount_: {
type: Number,
computed: 'computeRowCount_(columnCount_, tiles_)',
},
/** @private */
customLinksEnabled_: Boolean,
/** @private */
dialogTileTitle_: String,
/** @private */
dialogTileUrl_: {
type: String,
observer: 'onDialogTileUrlChange_',
},
/** @private */
dialogTileUrlInvalid_: {
type: Boolean,
value: false,
},
/** @private */
dialogTitle_: String,
/** @private */
dialogSaveDisabled_: {
type: Boolean,
computed: `computeDialogSaveDisabled_(dialogTitle_, dialogTileUrl_,
dialogShortcutAlreadyExists_)`,
},
/** @private */
dialogShortcutAlreadyExists_: {
type: Boolean,
computed: 'computeDialogShortcutAlreadyExists_(tiles_, dialogTileUrl_)',
},
/** @private */
dialogTileUrlError_: {
type: String,
computed: `computeDialogTileUrlError_(dialogTileUrl_,
dialogShortcutAlreadyExists_)`,
},
/**
* Used to hide hover style and cr-icon-button of tiles while the tiles
* are being reordered.
* @private
*/
reordering_: {
type: Boolean,
value: false,
reflectToAttribute: true,
},
/** @private */
maxTiles_: {
type: Number,
computed: 'computeMaxTiles_(customLinksEnabled_)',
},
/** @private */
maxVisibleTiles_: {
type: Number,
computed: 'computeMaxVisibleTiles_(columnCount_, rowCount_)',
},
/** @private */
showAdd_: {
type: Boolean,
value: false,
computed:
'computeShowAdd_(tiles_, maxVisibleTiles_, customLinksEnabled_)',
},
/** @private */
showToastButtons_: Boolean,
/** @private {!ScreenWidth} */
screenWidth_: Number,
/** @private {!Array<!newTabPage.mojom.MostVisitedTile>} */
tiles_: Array,
/** @private */
toastContent_: String,
/** @private */
visible_: {
type: Boolean,
reflectToAttribute: true,
},
};
}
/** @private {!Array<!HTMLElement>} */
get tileElements_() {
return /** @type {!Array<!HTMLElement>} */ (
Array.from(this.shadowRoot.querySelectorAll('.tile:not([hidden])')));
}
constructor() {
performance.mark('most-visited-creation-start');
super();
/** @private {boolean} */
this.adding_ = false;
const {callbackRouter, handler} = NewTabPageProxy.getInstance();
/** @private {!newTabPage.mojom.PageCallbackRouter} */
this.callbackRouter_ = callbackRouter;
/** @private {newTabPage.mojom.PageHandlerRemote} */
this.pageHandler_ = handler;
/** @private {?number} */
this.setMostVisitedInfoListenerId_ = null;
/** @private {number} */
this.actionMenuTargetIndex_ = -1;
/**
* This is the position of the mouse with respect to the top-left corner
* of the tile being dragged.
* @private {(!{x: number, y: number}|null)}
*/
this.dragOffset_ = null;
/** @private {!Array<!DOMRect>} */
this.tileRects_ = [];
}
/** @override */
connectedCallback() {
super.connectedCallback();
/** @private {boolean} */
this.isRtl_ = window.getComputedStyle(this)['direction'] === 'rtl';
/** @private {!EventTracker} */
this.eventTracker_ = new EventTracker();
this.setMostVisitedInfoListenerId_ =
this.callbackRouter_.setMostVisitedInfo.addListener(info => {
performance.measure('most-visited-mojo', 'most-visited-mojo-start');
this.visible_ = info.visible;
this.customLinksEnabled_ = info.customLinksEnabled;
this.tiles_ = info.tiles.slice(0, assert(this.maxTiles_));
});
performance.mark('most-visited-mojo-start');
this.eventTracker_.add(document, 'visibilitychange', () => {
// This updates the most visited tiles every time the NTP tab gets
// activated.
if (document.visibilityState === 'visible') {
this.pageHandler_.updateMostVisitedInfo();
}
});
this.pageHandler_.updateMostVisitedInfo();
FocusOutlineManager.forDocument(document);
}
/** @override */
disconnectedCallback() {
super.disconnectedCallback();
this.callbackRouter_.removeListener(
assert(this.setMostVisitedInfoListenerId_));
this.mediaListenerWideWidth_.removeListener(
assert(this.boundOnWidthChange_));
this.mediaListenerMediumWidth_.removeListener(
assert(this.boundOnWidthChange_));
this.ownerDocument.removeEventListener(
'keydown', this.boundOnDocumentKeyDown_);
this.eventTracker_.removeAll();
}
/** @override */
ready() {
super.ready();
/** @private {!Function} */
this.boundOnWidthChange_ = this.updateScreenWidth_.bind(this);
/** @private {!MediaQueryList} */
this.mediaListenerWideWidth_ =
WindowProxy.getInstance().matchMedia('(min-width: 672px)');
this.mediaListenerWideWidth_.addListener(this.boundOnWidthChange_);
/** @private {!MediaQueryList} */
this.mediaListenerMediumWidth_ =
WindowProxy.getInstance().matchMedia('(min-width: 560px)');
this.mediaListenerMediumWidth_.addListener(this.boundOnWidthChange_);
this.updateScreenWidth_();
/** @private {!function(Event)} */
this.boundOnDocumentKeyDown_ = e =>
this.onDocumentKeyDown_(/** @type {!KeyboardEvent} */ (e));
this.ownerDocument.addEventListener(
'keydown', this.boundOnDocumentKeyDown_);
performance.measure('most-visited-creation', 'most-visited-creation-start');
}
/** @private */
clearForceHover_() {
const forceHover = this.shadowRoot.querySelector('.force-hover');
if (forceHover) {
forceHover.classList.remove('force-hover');
}
}
/**
* @return {number}
* @private
*/
computeColumnCount_() {
let maxColumns = 3;
if (this.screenWidth_ === ScreenWidth.WIDE) {
maxColumns = 5;
} else if (this.screenWidth_ === ScreenWidth.MEDIUM) {
maxColumns = 4;
}
const shortcutCount = this.tiles_ ? this.tiles_.length : 0;
const canShowAdd = this.maxTiles_ > shortcutCount;
const tileCount =
Math.min(this.maxTiles_, shortcutCount + (canShowAdd ? 1 : 0));
const columnCount = tileCount <= maxColumns ?
tileCount :
Math.min(maxColumns, Math.ceil(tileCount / 2));
return columnCount || 3;
}
/**
* @return {number}
* @private
*/
computeRowCount_() {
if (this.columnCount_ === 0) {
return 0;
}
const shortcutCount = this.tiles_ ? this.tiles_.length : 0;
return this.columnCount_ <= shortcutCount ? 2 : 1;
}
/**
* @return {number}
* @private
*/
computeMaxTiles_() {
return this.customLinksEnabled_ ? 10 : 8;
}
/**
* @return {number}
* @private
*/
computeMaxVisibleTiles_() {
return this.columnCount_ * this.rowCount_;
}
/**
* @return {boolean}
* @private
*/
computeShowAdd_() {
return this.customLinksEnabled_ && this.tiles_ &&
this.tiles_.length < this.maxVisibleTiles_;
}
/**
* @return {boolean}
* @private
*/
computeDialogSaveDisabled_() {
return !this.dialogTileUrl_.trim() ||
normalizeUrl(this.dialogTileUrl_) === null ||
this.dialogShortcutAlreadyExists_;
}
/**
* @return {boolean}
* @private
*/
computeDialogShortcutAlreadyExists_() {
const dialogTileHref = (normalizeUrl(this.dialogTileUrl_) || {}).href;
if (!dialogTileHref) {
return false;
}
return (this.tiles_ || []).some(({url: {url}}, index) => {
if (index === this.actionMenuTargetIndex_) {
return false;
}
const otherUrl = normalizeUrl(url);
return otherUrl && otherUrl.href === dialogTileHref;
});
}
/**
* @return {string}
* @private
*/
computeDialogTileUrlError_() {
return loadTimeData.getString(
this.dialogShortcutAlreadyExists_ ? 'shortcutAlreadyExists' :
'invalidUrl');
}
/**
* If a pointer is over a tile rect that is different from the one being
* dragged, the dragging tile is moved to the new position. The reordering
* is done in the DOM and the by the |reorderMostVisitedTile()| call. This is
* done to prevent flicking between the time when the tiles are moved back to
* their original positions (by removing position absolute) and when the
* tiles are updated via a |setMostVisitedTiles()| call.
*
* |reordering_| is not set to false when the tiles are reordered. The callers
* will need to set it to false. This is necessary to handle a mouse drag
* issue.
* @param {number} x
* @param {number} y
* @private
*/
dragEnd_(x, y) {
if (!this.customLinksEnabled_) {
this.reordering_ = false;
return;
}
this.dragOffset_ = null;
const dragElement = this.shadowRoot.querySelector('.tile.dragging');
if (!dragElement) {
this.reordering_ = false;
return;
}
const dragIndex = this.$.tiles.modelForElement(dragElement).index;
dragElement.classList.remove('dragging');
this.tileElements_.forEach(resetTilePosition);
resetTilePosition(/** @type {!HTMLElement} */ (this.$.addShortcut));
const dropIndex = getHitIndex(this.tileRects_, x, y);
if (dragIndex !== dropIndex && dropIndex > -1) {
const [draggingTile] = this.tiles_.splice(dragIndex, 1);
this.tiles_.splice(dropIndex, 0, draggingTile);
this.notifySplices('tiles_', [
{
index: dragIndex,
removed: [draggingTile],
addedCount: 0,
object: this.tiles_,
type: 'splice',
},
{
index: dropIndex,
removed: [],
addedCount: 1,
object: this.tiles_,
type: 'splice',
},
]);
this.pageHandler_.reorderMostVisitedTile(draggingTile.url, dropIndex);
}
}
/**
* The positions of the tiles are updated based on the location of the
* pointer.
* @param {number} x
* @param {number} y
* @private
*/
dragOver_(x, y) {
const dragElement = this.shadowRoot.querySelector('.tile.dragging');
if (!dragElement) {
this.reordering_ = false;
return;
}
const dragIndex = this.$.tiles.modelForElement(dragElement).index;
setTilePosition(/** @type {!HTMLElement} */ (dragElement), {
x: x - this.dragOffset_.x,
y: y - this.dragOffset_.y,
});
const dropIndex = getHitIndex(this.tileRects_, x, y);
this.tileElements_.forEach((element, i) => {
let positionIndex;
if (i === dragIndex) {
return;
} else if (dropIndex === -1) {
positionIndex = i;
} else if (dragIndex < dropIndex && dragIndex <= i && i <= dropIndex) {
positionIndex = i - 1;
} else if (dragIndex > dropIndex && dragIndex >= i && i >= dropIndex) {
positionIndex = i + 1;
} else {
positionIndex = i;
}
setTilePosition(
/** @type {!HTMLElement} */ (element),
this.tileRects_[positionIndex]);
});
}
/**
* Sets up tile reordering for both drag and touch events. This method stores
* the following to be used in |dragOver_()| and |dragEnd_()|.
* |dragOffset_|: This is the mouse/touch offset with respect to the
* top/left corner of the tile being dragged. It is used to update the
* dragging tile location during the drag.
* |reordering_|: This is property/attribute used to hide the hover style
* and cr-icon-button of the tiles while they are being reordered.
* |tileRects_|: This is the rects of the tiles before the drag start. It is
* to determine which tile the pointer is over while dragging.
* @param {!HTMLElement} dragElement
* @param {number} x
* @param {number} y
* @private
*/
dragStart_(dragElement, x, y) {
// Need to clear the tile that has a forced hover style for when the drag
// started without moving the mouse after the last drag/drop.
this.clearForceHover_();
dragElement.classList.add('dragging');
const dragElementRect = dragElement.getBoundingClientRect();
this.dragOffset_ = {
x: x - dragElementRect.x,
y: y - dragElementRect.y,
};
const tileElements = this.tileElements_;
// Get all the rects first before setting the absolute positions.
this.tileRects_ = tileElements.map(t => t.getBoundingClientRect());
if (this.showAdd_) {
const element = /** @type {!HTMLElement} */ (this.$.addShortcut);
setTilePosition(element, element.getBoundingClientRect());
}
tileElements.forEach((tile, i) => {
setTilePosition(tile, this.tileRects_[i]);
});
this.reordering_ = true;
}
/**
* @param {!url.mojom.Url} url
* @return {string}
* @private
*/
getFaviconUrl_(url) {
const faviconUrl = new URL('chrome://favicon2/');
faviconUrl.searchParams.set('size', '24');
faviconUrl.searchParams.set('scale_factor', '1x');
faviconUrl.searchParams.set('show_fallback_monogram', '');
faviconUrl.searchParams.set('page_url', url.url);
return faviconUrl.href;
}
/**
* @return {string}
* @private
*/
getRestoreButtonText_() {
return loadTimeData.getString(
this.customLinksEnabled_ ? 'restoreDefaultLinks' :
'restoreThumbnailsShort');
}
/**
* @param {!newTabPage.mojom.MostVisitedTile} tile
* @return {string}
* @private
*/
getTileTitleDirectionClass_(tile) {
return tile.titleDirection === mojoBase.mojom.TextDirection.RIGHT_TO_LEFT ?
'title-rtl' :
'title-ltr';
}
/**
* @param {number} index
* @return {boolean}
* @private
*/
isHidden_(index) {
return index >= this.maxVisibleTiles_;
}
/** @private */
onAdd_() {
this.dialogTitle_ = loadTimeData.getString('addLinkTitle');
this.dialogTileTitle_ = '';
this.dialogTileUrl_ = '';
this.dialogTileUrlInvalid_ = false;
this.adding_ = true;
this.$.dialog.showModal();
}
/**
* @param {!KeyboardEvent} e
* @private
*/
onAddShortcutKeyDown_(e) {
if (hasKeyModifiers(e)) {
return;
}
if (!this.tiles_ || this.tiles_.length === 0) {
return;
}
const backKey = this.isRtl_ ? 'ArrowRight' : 'ArrowLeft';
if (e.key === backKey || e.key === 'ArrowUp') {
this.tileFocus_(this.tiles_.length - 1);
}
}
/** @private */
onDialogCancel_() {
this.actionMenuTargetIndex_ = -1;
this.$.dialog.cancel();
}
/** @private */
onDialogClose_() {
this.dialogTileUrl_ = '';
if (this.adding_) {
this.$.addShortcut.focus();
}
this.adding_ = false;
}
/** @private */
onDialogTileUrlBlur_() {
if (this.dialogTileUrl_.length > 0 &&
(normalizeUrl(this.dialogTileUrl_) === null ||
this.dialogShortcutAlreadyExists_)) {
this.dialogTileUrlInvalid_ = true;
}
}
/** @private */
onDialogTileUrlChange_() {
this.dialogTileUrlInvalid_ = false;
}
/**
* @param {!KeyboardEvent} e
* @private
*/
onDocumentKeyDown_(e) {
if (e.altKey || e.shiftKey) {
return;
}
const modifier = isMac ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey;
if (modifier && e.key === 'z') {
e.preventDefault();
this.onUndoClick_();
}
}
/**
* @param {!DragEvent} e
* @private
*/
onDragStart_(e) {
if (!this.customLinksEnabled_) {
return;
}
// |dataTransfer| is null in tests.
if (e.dataTransfer) {
// Remove the ghost image that appears when dragging.
e.dataTransfer.setDragImage(new Image(), 0, 0);
}
this.dragStart_(/** @type {!HTMLElement} */ (e.target), e.x, e.y);
const dragOver = e => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
this.dragOver_(e.x, e.y);
};
this.ownerDocument.addEventListener('dragover', dragOver);
this.ownerDocument.addEventListener('dragend', e => {
this.ownerDocument.removeEventListener('dragover', dragOver);
this.dragEnd_(e.x, e.y);
const dropIndex = getHitIndex(this.tileRects_, e.x, e.y);
if (dropIndex !== -1) {
this.tileElements_[dropIndex].classList.add('force-hover');
}
this.addEventListener('pointermove', () => {
this.clearForceHover_();
// When |reordering_| is true, the normal hover style is not shown.
// After a drop, the element that has hover is not correct. It will be
// after the mouse moves.
this.reordering_ = false;
}, {once: true});
}, {once: true});
}
/** @private */
onEdit_() {
this.$.actionMenu.close();
this.dialogTitle_ = loadTimeData.getString('editLinkTitle');
const tile = this.tiles_[this.actionMenuTargetIndex_];
this.dialogTileTitle_ = tile.title;
this.dialogTileUrl_ = tile.url.url;
this.dialogTileUrlInvalid_ = false;
this.$.dialog.showModal();
}
/** @private */
onRestoreDefaultsClick_() {
if (!this.$.toast.open || !this.showToastButtons_) {
return;
}
this.$.toast.hide();
this.pageHandler_.restoreMostVisitedDefaults();
}
/** @private */
async onRemove_() {
this.$.actionMenu.close();
await this.tileRemove_(this.actionMenuTargetIndex_);
this.actionMenuTargetIndex_ = -1;
}
/** @private */
async onSave_() {
const newUrl = {url: normalizeUrl(this.dialogTileUrl_).href};
this.$.dialog.close();
let newTitle = this.dialogTileTitle_.trim();
if (newTitle.length === 0) {
newTitle = this.dialogTileUrl_;
}
if (this.adding_) {
const {success} =
await this.pageHandler_.addMostVisitedTile(newUrl, newTitle);
this.toast_(success ? 'linkAddedMsg' : 'linkCantCreate', success);
} else {
const {url, title} = this.tiles_[this.actionMenuTargetIndex_];
if (url.url !== newUrl.url || title !== newTitle) {
const {success} = await this.pageHandler_.updateMostVisitedTile(
url, newUrl, newTitle);
this.toast_(success ? 'linkEditedMsg' : 'linkCantEdit', success);
}
this.actionMenuTargetIndex_ = -1;
}
}
/**
* @param {!Event} e
* @private
*/
onTileActionButtonClick_(e) {
e.preventDefault();
const {index} = this.$.tiles.modelForElement(e.target.parentElement);
this.actionMenuTargetIndex_ = index;
this.$.actionMenu.showAt(e.target);
}
/**
* @param {!Event} e
* @private
*/
onTileRemoveButtonClick_(e) {
e.preventDefault();
const {index} = this.$.tiles.modelForElement(e.target.parentElement);
this.tileRemove_(index);
}
/**
* @param {!Event} e
* @private
*/
onTileClick_(e) {
if (e.defaultPrevented) {
// Ignore previousely handled events.
return;
}
if (loadTimeData.getBoolean('handleMostVisitedNavigationExplicitly')) {
e.preventDefault(); // Prevents default browser action (navigation).
}
this.pageHandler_.onMostVisitedTileNavigation(
this.$.tiles.itemForElement(e.target),
this.$.tiles.indexForElement(e.target), e.button || 0, e.altKey,
e.ctrlKey, e.metaKey, e.shiftKey);
}
/**
* @param {!KeyboardEvent} e
* @private
*/
onTileKeyDown_(e) {
if (hasKeyModifiers(e)) {
return;
}
if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight' &&
e.key !== 'ArrowUp' && e.key !== 'ArrowDown' && e.key !== 'Delete') {
return;
}
const {index} = this.$.tiles.modelForElement(e.target);
if (e.key === 'Delete') {
this.tileRemove_(index);
return;
}
const advanceKey = this.isRtl_ ? 'ArrowLeft' : 'ArrowRight';
const delta = (e.key === advanceKey || e.key === 'ArrowDown') ? 1 : -1;
this.tileFocus_(Math.max(0, index + delta));
}
/** @private */
onUndoClick_() {
if (!this.$.toast.open || !this.showToastButtons_) {
return;
}
this.$.toast.hide();
this.pageHandler_.undoMostVisitedTileAction();
}
/**
* @param {!TouchEvent} e
* @private
*/
onTouchStart_(e) {
if (this.reordering_ || !this.customLinksEnabled_) {
return;
}
const tileElement = /** @type {HTMLElement} */ (e.composedPath().find(
el => el.classList && el.classList.contains('tile')));
if (!tileElement) {
return;
}
const {clientX, clientY} = e.changedTouches[0];
this.dragStart_(tileElement, clientX, clientY);
const touchMove = e => {
const {clientX, clientY} = e.changedTouches[0];
this.dragOver_(clientX, clientY);
};
const touchEnd = e => {
this.ownerDocument.removeEventListener('touchmove', touchMove);
tileElement.removeEventListener('touchend', touchEnd);
tileElement.removeEventListener('touchcancel', touchEnd);
const {clientX, clientY} = e.changedTouches[0];
this.dragEnd_(clientX, clientY);
this.reordering_ = false;
};
this.ownerDocument.addEventListener('touchmove', touchMove);
tileElement.addEventListener('touchend', touchEnd, {once: true});
tileElement.addEventListener('touchcancel', touchEnd, {once: true});
}
/**
* @param {number} index
* @private
*/
tileFocus_(index) {
if (index < 0) {
return;
}
const tileElements = this.tileElements_;
if (index < tileElements.length) {
tileElements[index].focus();
} else if (this.showAdd_ && index === tileElements.length) {
this.$.addShortcut.focus();
}
}
/**
* @param {string} msgId
* @param {boolean} showButtons
* @private
*/
toast_(msgId, showButtons) {
this.toastContent_ = loadTimeData.getString(msgId);
this.showToastButtons_ = showButtons;
this.$.toast.show();
}
/**
* @param {number} index
* @return {!Promise}
* @private
*/
async tileRemove_(index) {
const {url, isQueryTile} = this.tiles_[index];
this.pageHandler_.deleteMostVisitedTile(url);
// Do not show the toast buttons when a query tile is removed unless it is a
// custom link. Removal is not reversible for non custom link query tiles.
this.toast_(
'linkRemovedMsg',
/* showButtons= */ this.customLinksEnabled_ || !isQueryTile);
this.tileFocus_(index);
}
/** @private */
updateScreenWidth_() {
if (this.mediaListenerWideWidth_.matches) {
this.screenWidth_ = ScreenWidth.WIDE;
} else if (this.mediaListenerMediumWidth_.matches) {
this.screenWidth_ = ScreenWidth.MEDIUM;
} else {
this.screenWidth_ = ScreenWidth.NARROW;
}
}
/** @private */
onTilesRendered_() {
performance.measure('most-visited-rendered');
this.pageHandler_.onMostVisitedTilesRendered(
this.tiles_.slice(0, assert(this.maxVisibleTiles_)),
WindowProxy.getInstance().now());
}
}
customElements.define(MostVisitedElement.is, MostVisitedElement);