blob: c691b82378c8bfa5a5e8024994a5a8bd2b6a57a8 [file] [log] [blame]
// Copyright 2018 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.
'use strict';
// TODO(crbug.com/937570): After the RP launches this should be renamed to
// customizationMenu along with the file, and large parts can be
// refactored/removed.
const customize = {};
/**
* The browser embeddedSearch.newTabPage object.
* @type {Object}
*/
let ntpApiHandle;
/**
* The different types of events that are logged from the NTP. This enum is
* used to transfer information from the NTP JavaScript to the renderer and is
* not used as a UMA enum histogram's logged value.
* Note: Keep in sync with common/ntp_logging_events.h
* @enum {number}
* @const
*/
customize.LOG_TYPE = {
// The 'Chrome backgrounds' menu item was clicked.
NTP_CUSTOMIZE_CHROME_BACKGROUNDS_CLICKED: 40,
// The 'Upload an image' menu item was clicked.
NTP_CUSTOMIZE_LOCAL_IMAGE_CLICKED: 41,
// The 'Restore default background' menu item was clicked.
// NOTE: NTP_CUSTOMIZE_RESTORE_BACKGROUND_CLICKED (42) is logged on the
// backend.
// The attribution link on a customized background image was clicked.
NTP_CUSTOMIZE_ATTRIBUTION_CLICKED: 43,
// The 'Restore default shortcuts' menu item was clicked.
NTP_CUSTOMIZE_RESTORE_SHORTCUTS_CLICKED: 46,
// A collection was selected in the 'Chrome backgrounds' dialog.
NTP_CUSTOMIZE_CHROME_BACKGROUND_SELECT_COLLECTION: 47,
// An image was selected in the 'Chrome backgrounds' dialog.
NTP_CUSTOMIZE_CHROME_BACKGROUND_SELECT_IMAGE: 48,
// 'Cancel' was clicked in the 'Chrome backgrounds' dialog.
NTP_CUSTOMIZE_CHROME_BACKGROUND_CANCEL: 49,
// NOTE: NTP_CUSTOMIZE_CHROME_BACKGROUND_DONE (50) is logged on the backend.
// The richer picker was opened.
NTP_CUSTOMIZATION_MENU_OPENED: 64,
// 'Cancel' was clicked in the richer picker.
NTP_CUSTOMIZATION_MENU_CANCEL: 65,
// 'Done' was clicked in the richer picker.
NTP_CUSTOMIZATION_MENU_DONE: 66,
// 'Upload from device' selected in the richer picker.
NTP_BACKGROUND_UPLOAD_FROM_DEVICE: 67,
// A collection tile was selected in the richer picker.
NTP_BACKGROUND_OPEN_COLLECTION: 68,
// A image tile was selected in the richer picker.
NTP_BACKGROUND_SELECT_IMAGE: 69,
// The back arrow was clicked in the richer picker.
NTP_BACKGROUND_BACK_CLICK: 72,
// The 'No background' tile was selected in the richer picker.
NTP_BACKGROUND_DEFAULT_SELECTED: 73,
// The custom links option in the shortcuts submenu was clicked.
NTP_CUSTOMIZE_SHORTCUT_CUSTOM_LINKS_CLICKED: 78,
// The Most Visited option in the shortcuts submenu was clicked.
NTP_CUSTOMIZE_SHORTCUT_MOST_VISITED_CLICKED: 79,
// The visibility toggle in the shortcuts submenu was clicked.
NTP_CUSTOMIZE_SHORTCUT_VISIBILITY_TOGGLE_CLICKED: 80,
// The 'refresh daily' toggle was licked in the richer picker.
NTP_BACKGROUND_REFRESH_TOGGLE_CLICKED: 81,
};
/**
* Enum for key codes.
* @enum {number}
* @const
*/
customize.KEYCODES = {
BACKSPACE: 8,
DOWN: 40,
ENTER: 13,
ESC: 27,
LEFT: 37,
RIGHT: 39,
SPACE: 32,
TAB: 9,
UP: 38,
};
/**
* Array for keycodes corresponding to arrow keys.
* @type Array
* @const
*/
customize.arrowKeys = [/*Left*/ 37, /*Up*/ 38, /*Right*/ 39, /*Down*/ 40];
/**
* Enum for HTML element ids.
* @enum {string}
* @const
*/
customize.IDS = {
ATTR1: 'attr1',
ATTR2: 'attr2',
ATTRIBUTIONS: 'custom-bg-attr',
BACK_CIRCLE: 'bg-sel-back-circle',
BACKGROUNDS_DEFAULT: 'backgrounds-default',
BACKGROUNDS_DEFAULT_ICON: 'backgrounds-default-icon',
BACKGROUNDS_BUTTON: 'backgrounds-button',
BACKGROUNDS_IMAGE_MENU: 'backgrounds-image-menu',
BACKGROUNDS_MENU: 'backgrounds-menu',
BACKGROUNDS_UPLOAD: 'backgrounds-upload',
BACKGROUNDS_UPLOAD_ICON: 'backgrounds-upload-icon',
CANCEL: 'bg-sel-footer-cancel',
COLOR_PICKER: 'color-picker',
COLOR_PICKER_TILE: 'color-picker-tile',
COLOR_PICKER_CONTAINER: 'color-picker-container',
COLOR_PICKER_ICON: 'color-picker-icon',
COLORS_BUTTON: 'colors-button',
COLORS_DEFAULT_ICON: 'colors-default-icon',
COLORS_THEME: 'colors-theme',
COLORS_THEME_NAME: 'colors-theme-name',
COLORS_THEME_UNINSTALL: 'colors-theme-uninstall',
COLORS_THEME_WEBSTORE_LINK: 'colors-theme-link',
COLORS_MENU: 'colors-menu',
CUSTOMIZATION_MENU: 'customization-menu',
CUSTOM_BG: 'custom-bg',
CUSTOM_BG_PREVIEW: 'custom-bg-preview',
CUSTOM_LINKS_RESTORE_DEFAULT: 'custom-links-restore-default',
CUSTOM_LINKS_RESTORE_DEFAULT_TEXT: 'custom-links-restore-default-text',
DEFAULT_WALLPAPERS: 'edit-bg-default-wallpapers',
DEFAULT_WALLPAPERS_TEXT: 'edit-bg-default-wallpapers-text',
DONE: 'bg-sel-footer-done',
EDIT_BG: 'edit-bg',
EDIT_BG_DIALOG: 'edit-bg-dialog',
EDIT_BG_DIVIDER: 'edit-bg-divider',
EDIT_BG_MENU: 'edit-bg-menu',
MENU_BACK_CIRCLE: 'menu-back-circle',
MENU_CANCEL: 'menu-cancel',
MENU_DONE: 'menu-done',
MENU_TITLE: 'menu-title',
LINK_ICON: 'link-icon',
MENU: 'bg-sel-menu',
OPTIONS_TITLE: 'edit-bg-title',
REFRESH_DAILY_WRAPPER: 'refresh-daily-wrapper',
REFRESH_TOGGLE: 'refresh-daily-toggle',
RESTORE_DEFAULT: 'edit-bg-restore-default',
RESTORE_DEFAULT_TEXT: 'edit-bg-restore-default-text',
SHORTCUTS_BUTTON: 'shortcuts-button',
SHORTCUTS_HIDE: 'sh-hide',
SHORTCUTS_HIDE_TOGGLE: 'sh-hide-toggle',
SHORTCUTS_MENU: 'shortcuts-menu',
SHORTCUTS_OPTION_CUSTOM_LINKS: 'sh-option-cl',
SHORTCUTS_OPTION_MOST_VISITED: 'sh-option-mv',
UPLOAD_IMAGE: 'edit-bg-upload-image',
UPLOAD_IMAGE_TEXT: 'edit-bg-upload-image-text',
TILES: 'bg-sel-tiles',
TITLE: 'bg-sel-title',
};
/**
* Enum for classnames.
* @enum {string}
* @const
*/
customize.CLASSES = {
ATTR_SMALL: 'attr-small',
ATTR_COMMON: 'attr-common',
ATTR_LINK: 'attr-link',
COLLECTION_DIALOG: 'is-col-sel',
COLLECTION_SELECTED: 'bg-selected', // Highlight selected tile
COLLECTION_TILE: 'bg-sel-tile', // Preview tile for background customization
COLLECTION_TILE_BG: 'bg-sel-tile-bg',
COLLECTION_TITLE: 'bg-sel-tile-title', // Title of a background image
HIDDEN_SELECTED: 'hidden-selected',
IMAGE_DIALOG: 'is-img-sel',
ON_IMAGE_MENU: 'on-img-menu',
OPTION: 'bg-option',
OPTION_DISABLED: 'bg-option-disabled', // The menu option is disabled.
MENU_SHOWN: 'menu-shown',
MOUSE_NAV: 'using-mouse-nav',
SELECTED: 'selected',
SELECTED_BORDER: 'selected-border',
SELECTED_CHECK: 'selected-check',
SELECTED_CIRCLE: 'selected-circle',
SINGLE_ATTR: 'single-attr',
VISIBLE: 'visible'
};
/**
* Enum for background option menu entries, in the order they appear in the UI.
* @enum {number}
* @const
*/
customize.MENU_ENTRIES = {
CHROME_BACKGROUNDS: 0,
UPLOAD_IMAGE: 1,
CUSTOM_LINKS_RESTORE_DEFAULT: 2,
RESTORE_DEFAULT: 3,
};
/**
* The semi-transparent, gradient overlay for the custom background. Intended
* to improve readability of NTP elements/text.
* @type {string}
* @const
*/
customize.CUSTOM_BACKGROUND_OVERLAY =
'linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.3))';
/**
* Number of rows in the custom background dialog to preload.
* @type {number}
* @const
*/
customize.ROWS_TO_PRELOAD = 3;
// These should match the corresponding values in local_ntp.js, that control the
// mv-notice element.
customize.delayedHideNotification = -1;
customize.NOTIFICATION_TIMEOUT = 10000;
/**
* Were the background tiles already created.
* @type {boolean}
*/
customize.builtTiles = false;
/**
* The default title for the richer picker.
* @type {string}
*/
customize.richerPicker_defaultTitle = '';
/**
* Called when the error notification should be shown.
* @type {?Function}
*/
customize.showErrorNotification = null;
/**
* Called when the custom link notification should be hidden.
* @type {?Function}
*/
customize.hideCustomLinkNotification = null;
/**
* Id of the currently open collection.
* @type {string}
*/
customize.currentCollectionId = '';
/**
* The currently active Background submenu. This can be the collections page or
* a collection's image menu. Defaults to the collections page.
* @type {Object}
*/
customize.richerPicker_openBackgroundSubmenu = {
menuId: customize.IDS.BACKGROUNDS_MENU,
title: '',
};
/**
* The currently selected submenu (i.e. Background, Shortcuts, etc.) in the
* richer picker.
* @type {Object}
*/
customize.richerPicker_selectedSubmenu = {
menuButton: null, // The submenu's button element in the sidebar.
menu: null, // The submenu's menu element.
// The submenu's title. Will usually be |customize.richerPicker_defaultTitle|
// unless this is a background collection's image menu.
title: '',
};
/**
* The currently selected options in the customization menu.
* @type {Object}
*/
customize.selectedOptions = {
background: null, // Contains the background image tile.
// The data associated with a currently selected background.
backgroundData: null,
// Contains the selected shortcut type's DOM element, i.e. either custom links
// or most visited.
shortcutType: null,
shortcutsAreHidden: false,
color: null, // Contains the selected color tile's DOM element.
};
/**
* The preselected options in the richer picker.
* @type {Object}
*/
customize.preselectedOptions = {
// Contains the selected type's DOM element, i.e. either custom links or most
// visited.
shortcutType: null,
shortcutsAreHidden: false,
colorsMenuTile: null, // Selected tile for Colors menu.
backgroundsMenuTile: null, // Selected tile for Backgrounds menu.
};
/**
* Whether tiles for Colors menu already loaded.
* @type {boolean}
*/
customize.colorsMenuLoaded = false;
/**
* Default color for custom color picker in hex format.
* @type {string}
*/
customize.defaultCustomColor = '#000000';
/**
* Custom color picked in hex format.
* @type {string}
*/
customize.customColorPicked = customize.defaultCustomColor;
/**
* Sets the visibility of the settings menu and individual options depending on
* their respective features.
*/
customize.setMenuVisibility = function() {
// Reset all hidden values.
$(customize.IDS.EDIT_BG).hidden = false;
$(customize.IDS.DEFAULT_WALLPAPERS).hidden = false;
$(customize.IDS.UPLOAD_IMAGE).hidden = false;
$(customize.IDS.RESTORE_DEFAULT).hidden = false;
$(customize.IDS.EDIT_BG_DIVIDER).hidden = false;
$(customize.IDS.COLORS_BUTTON).hidden = !configData.chromeColors;
$(customize.IDS.COLOR_PICKER_CONTAINER)
.classList.toggle(
customize.CLASSES.VISIBLE, configData.chromeColorsCustomColorPicker);
};
/**
* Called when user changes the theme.
*/
customize.onThemeChange = function() {
// Hide the settings menu or individual options if the related features are
// disabled.
customize.setMenuVisibility();
// If theme changed after Colors menu was loaded, then reload theme info.
if (customize.colorsMenuLoaded) {
customize.colorsMenuOnThemeChange();
}
};
/**
* Display custom background image attributions on the page.
* @param {string} attributionLine1 First line of attribution.
* @param {string} attributionLine2 Second line of attribution.
* @param {string} attributionActionUrl Url to learn more about the image.
*/
customize.setAttribution = function(
attributionLine1, attributionLine2, attributionActionUrl) {
const attributionBox = $(customize.IDS.ATTRIBUTIONS);
const attr1 = document.createElement('span');
attr1.id = customize.IDS.ATTR1;
const attr2 = document.createElement('span');
attr2.id = customize.IDS.ATTR2;
if (attributionLine1 !== '') {
// Shouldn't be changed from textContent for security assurances.
attr1.textContent = attributionLine1;
attr1.classList.add(customize.CLASSES.ATTR_COMMON);
$(customize.IDS.ATTRIBUTIONS).appendChild(attr1);
}
if (attributionLine2 !== '') {
// Shouldn't be changed from textContent for security assurances.
attr2.textContent = attributionLine2;
attr2.classList.add(customize.CLASSES.ATTR_SMALL);
attr2.classList.add(customize.CLASSES.ATTR_COMMON);
attributionBox.appendChild(attr2);
}
if (attributionActionUrl !== '') {
const attr = (attributionLine2 !== '' ? attr2 : attr1);
attr.classList.add(customize.CLASSES.ATTR_LINK);
const linkIcon = document.createElement('div');
linkIcon.id = customize.IDS.LINK_ICON;
// Enlarge link-icon when there is only one line of attribution
if (attributionLine2 === '') {
linkIcon.classList.add(customize.CLASSES.SINGLE_ATTR);
}
attr.insertBefore(linkIcon, attr.firstChild);
attributionBox.classList.add(customize.CLASSES.ATTR_LINK);
attributionBox.href = attributionActionUrl;
attributionBox.onclick = function() {
ntpApiHandle.logEvent(
customize.LOG_TYPE.NTP_CUSTOMIZE_ATTRIBUTION_CLICKED);
};
attributionBox.style.cursor = 'pointer';
}
};
customize.clearAttribution = function() {
const attributions = $(customize.IDS.ATTRIBUTIONS);
attributions.removeAttribute('href');
attributions.className = '';
attributions.style.cursor = 'default';
while (attributions.firstChild) {
attributions.removeChild(attributions.firstChild);
}
};
customize.unselectTile = function() {
if (configData.richerPicker) {
return;
}
$(customize.IDS.DONE).disabled = true;
customize.selectedOptions.background = null;
$(customize.IDS.DONE).tabIndex = -1;
};
/**
* Remove all collection tiles from the container when the dialog
* is closed.
*/
customize.resetSelectionDialog = function() {
$(customize.IDS.TILES).scrollTop = 0;
const tileContainer = $(customize.IDS.TILES);
while (tileContainer.firstChild) {
tileContainer.removeChild(tileContainer.firstChild);
}
customize.unselectTile();
};
/**
* Apply selected styling to |menuButton| and make corresponding |menu| visible.
* If |title| is not specified, the default title will be used.
* @param {?Element} menuButton The sidebar button element to apply styling to.
* @param {?Element} menu The submenu element to apply styling to.
* @param {string=} title The submenu's title.
*/
customize.richerPicker_showSubmenu = function(menuButton, menu, title = '') {
if (!menuButton || !menu) {
return;
}
customize.richerPicker_hideOpenSubmenu();
if (!title) { // Use the default title if not specified.
title = customize.richerPicker_defaultTitle;
}
// Save this as the currently open submenu.
customize.richerPicker_selectedSubmenu.menuButton = menuButton;
customize.richerPicker_selectedSubmenu.menu = menu;
customize.richerPicker_selectedSubmenu.title = title;
menuButton.classList.toggle(customize.CLASSES.SELECTED, true);
menu.classList.toggle(customize.CLASSES.MENU_SHOWN, true);
$(customize.IDS.MENU_TITLE).textContent = title;
menuButton.setAttribute('aria-selected', true);
// Indicate if this is a Background collection's image menu, which will enable
// the back button.
$(customize.IDS.CUSTOMIZATION_MENU)
.classList.toggle(
customize.CLASSES.ON_IMAGE_MENU,
menu.id === customize.IDS.BACKGROUNDS_IMAGE_MENU);
};
/**
* Hides the currently open submenu if any.
*/
customize.richerPicker_hideOpenSubmenu = function() {
if (!customize.richerPicker_selectedSubmenu.menuButton) {
return; // No submenu is open.
}
customize.richerPicker_selectedSubmenu.menuButton.classList.toggle(
customize.CLASSES.SELECTED, false);
customize.richerPicker_selectedSubmenu.menu.classList.toggle(
customize.CLASSES.MENU_SHOWN, false);
$(customize.IDS.MENU_TITLE).textContent = customize.richerPicker_defaultTitle;
customize.richerPicker_selectedSubmenu.menuButton.setAttribute(
'aria-selected', false);
customize.richerPicker_selectedSubmenu.menuButton = null;
customize.richerPicker_selectedSubmenu.menu = null;
customize.richerPicker_selectedSubmenu.title =
customize.richerPicker_defaultTitle;
};
/**
* Remove image tiles and maybe swap back to main background menu.
*/
customize.richerPicker_resetImageMenu = function() {
const backgroundMenu = $(customize.IDS.BACKGROUNDS_MENU);
const imageMenu = $(customize.IDS.BACKGROUNDS_IMAGE_MENU);
const menu = $(customize.IDS.CUSTOMIZATION_MENU);
const menuTitle = $(customize.IDS.MENU_TITLE);
imageMenu.innerHTML = '';
menu.classList.toggle(customize.CLASSES.ON_IMAGE_MENU, false);
customize.richerPicker_showSubmenu(
$(customize.IDS.BACKGROUNDS_BUTTON), backgroundMenu);
customize.richerPicker_openBackgroundSubmenu.menuId =
customize.IDS.BACKGROUNDS_MENU;
customize.richerPicker_openBackgroundSubmenu.title = '';
backgroundMenu.scrollTop = 0;
};
/**
* Close the collection selection dialog and cleanup the state
* @param {?Element} menu The dialog to be closed
*/
customize.closeCollectionDialog = function(menu) {
if (!menu) {
return;
}
menu.close();
customize.resetSelectionDialog();
};
/**
* Close and reset the dialog if this is not the richer picker, and set the
* background.
* @param {string} url The url of the selected background.
*/
customize.setBackground = function(
url, attributionLine1, attributionLine2, attributionActionUrl,
collection_id) {
if (!configData.richerPicker) {
customize.closeCollectionDialog($(customize.IDS.MENU));
}
window.chrome.embeddedSearch.newTabPage.setBackgroundInfo(
url, attributionLine1, attributionLine2, attributionActionUrl,
collection_id);
};
/**
* Apply selected shortcut options.
*/
customize.richerPicker_setShortcutOptions = function() {
const shortcutTypeChanged = customize.preselectedOptions.shortcutType !==
customize.selectedOptions.shortcutType;
if (customize.preselectedOptions.shortcutsAreHidden !==
customize.selectedOptions.shortcutsAreHidden) {
// Only trigger a notification if |toggleMostVisitedOrCustomLinks| will not
// be called immediately after. Successive |onmostvisitedchange| events can
// interfere with each other.
chrome.embeddedSearch.newTabPage.toggleShortcutsVisibility(
!shortcutTypeChanged);
}
if (shortcutTypeChanged) {
chrome.embeddedSearch.newTabPage.toggleMostVisitedOrCustomLinks();
}
};
/**
* Creates a tile for the customization menu with a title.
* @param {string} id The id for the new element.
* @param {string} imageUrl The background image url for the new element.
* @param {string} name The name for the title of the new element.
* @param {Object} dataset The dataset for the new element.
* @param {?Function} onClickInteraction Function for onclick interaction.
* @param {?Function} onKeyInteraction Function for onkeydown interaction.
*/
customize.createTileWithTitle = function(
id, imageUrl, name, dataset, onClickInteraction, onKeyInteraction) {
const tile = customize.createTileThumbnail(
id, imageUrl, dataset, onClickInteraction, onKeyInteraction);
tile.setAttribute('aria-label', name);
tile.title = name;
customize.fadeInImageTile(tile, imageUrl, null);
const title = document.createElement('div');
title.classList.add(customize.CLASSES.COLLECTION_TITLE);
title.textContent = name;
tile.appendChild(title);
const tileBackground = document.createElement('div');
tileBackground.classList.add(customize.CLASSES.COLLECTION_TILE_BG);
tileBackground.appendChild(tile);
return tileBackground;
};
/**
* Creates a tile for the customization menu without a title.
* @param {string} id The id for the new element.
* @param {string} imageUrl The background image url for the new element.
* @param {Object} dataset The dataset for the new element.
* @param {?Function} onClickInteraction Function for onclick interaction.
* @param {?Function} onKeyInteraction Function for onkeydown interaction.
*/
customize.createTileWithoutTitle = function(
id, imageUrl, dataset, onClickInteraction, onKeyInteraction) {
const tile = customize.createTileThumbnail(
id, imageUrl, dataset, onClickInteraction, onKeyInteraction);
customize.fadeInImageTile(tile, imageUrl, null);
const tileBackground = document.createElement('div');
tileBackground.classList.add(customize.CLASSES.COLLECTION_TILE_BG);
tileBackground.appendChild(tile);
return tileBackground;
};
/**
* Create a tile thumbnail with image for customization menu.
* @param {string} id The id for the new element.
* @param {string} imageUrl The background image url for the new element.
* @param {Object} dataset The dataset for the new element.
* @param {?Function} onClickInteraction Function for onclick interaction.
* @param {?Function} onKeyInteraction Function for onkeydown interaction.
*/
customize.createTileThumbnail = function(
id, imageUrl, dataset, onClickInteraction, onKeyInteraction) {
const tile = document.createElement('div');
tile.id = id;
tile.classList.add(customize.CLASSES.COLLECTION_TILE);
tile.style.backgroundImage = 'url(' + imageUrl + ')';
for (const key in dataset) {
tile.dataset[key] = dataset[key];
}
tile.tabIndex = -1;
// Accessibility support for screen readers.
tile.setAttribute('role', 'button');
tile.setAttribute('aria-pressed', false);
tile.onclick = onClickInteraction;
tile.onkeydown = onKeyInteraction;
return tile;
};
/**
* Get the number of tiles in a row according to current window width.
* @return {number} the number of tiles per row
*/
customize.getTilesWide = function() {
// Browser window can only fit two columns. Should match "#bg-sel-menu" width.
if ($(customize.IDS.MENU).offsetWidth < 517) {
return 2;
} else if ($(customize.IDS.MENU).offsetWidth < 356) {
// Browser window can only fit one column. Should match @media (max-width:
// 356) "#bg-sel-menu" width.
return 1;
}
return 3;
};
/**
* @param {number} deltaX Change in the x direction.
* @param {number} deltaY Change in the y direction.
* @param {Element} current The current tile.
*/
customize.richerPicker_getNextTile = function(deltaX, deltaY, current) {
const menu = $(customize.IDS.CUSTOMIZATION_MENU);
const container = customize.richerPicker_selectedSubmenu.menu;
const tiles = Array.from(
container.getElementsByClassName(customize.CLASSES.COLLECTION_TILE_BG));
let nextIndex = tiles.indexOf(current.parentElement);
if (deltaX != 0) {
nextIndex += deltaX;
} else if (deltaY != 0) {
const startingTop = current.parentElement.getBoundingClientRect().top;
const startingLeft = current.parentElement.getBoundingClientRect().left;
// Search until a tile in a different row and the same column is found.
while (tiles[nextIndex] &&
(tiles[nextIndex].getBoundingClientRect().top == startingTop ||
tiles[nextIndex].getBoundingClientRect().left != startingLeft)) {
nextIndex += deltaY;
}
}
if (tiles[nextIndex]) {
return tiles[nextIndex].children[0];
}
return null;
};
/**
* Get the next tile when the arrow keys are used to navigate the grid.
* Returns null if the tile doesn't exist.
* @param {number} deltaX Change in the x direction.
* @param {number} deltaY Change in the y direction.
* @param {Element} currentElem The current tile.
*/
customize.getNextTile = function(deltaX, deltaY, currentElem) {
if (configData.richerPicker) {
return customize.richerPicker_getNextTile(deltaX, deltaY, currentElem);
}
const current = currentElem.dataset.tileIndex;
let idPrefix = 'coll_tile_';
if ($(customize.IDS.MENU)
.classList.contains(customize.CLASSES.IMAGE_DIALOG)) {
idPrefix = 'img_tile_';
}
if (deltaX != 0) {
const target = parseInt(current, /*radix=*/ 10) + deltaX;
return $(idPrefix + target);
} else if (deltaY != 0) {
let target = parseInt(current, /*radix=*/ 10);
let nextTile = $(idPrefix + target);
const startingTop = nextTile.getBoundingClientRect().top;
const startingLeft = nextTile.getBoundingClientRect().left;
// Search until a tile in a different row and the same column is found.
while (nextTile &&
(nextTile.getBoundingClientRect().top == startingTop ||
nextTile.getBoundingClientRect().left != startingLeft)) {
target += deltaY;
nextTile = $(idPrefix + target);
}
return nextTile;
}
};
/**
* Event handler when a key is pressed on a tile in customization menu.
* @param {Event} event The event attributes for the interaction.
*/
customize.tileOnKeyDownInteraction = function(event) {
const tile = event.currentTarget;
if (event.keyCode === customize.KEYCODES.ENTER ||
event.keyCode === customize.KEYCODES.SPACE) {
event.preventDefault();
event.stopPropagation();
if (tile.onClickOverride) {
tile.onClickOverride(event);
return;
}
tile.onclick(event);
} else if (customize.arrowKeys.includes(event.keyCode)) {
// Handle arrow key navigation.
event.preventDefault();
let target = null;
if (event.keyCode === customize.KEYCODES.LEFT) {
target = customize.getNextTile(
window.chrome.embeddedSearch.searchBox.rtl ? 1 : -1, 0,
/** @type HTMLElement */ (tile));
} else if (event.keyCode === customize.KEYCODES.UP) {
target = customize.getNextTile(0, -1, /** @type HTMLElement */ (tile));
} else if (event.keyCode === customize.KEYCODES.RIGHT) {
target = customize.getNextTile(
window.chrome.embeddedSearch.searchBox.rtl ? -1 : 1, 0,
/** @type HTMLElement */ (tile));
} else if (event.keyCode === customize.KEYCODES.DOWN) {
target = customize.getNextTile(0, 1, /** @type HTMLElement */ (tile));
}
if (target) {
target.focus();
} else {
tile.focus();
}
}
};
/**
* Show dialog for selecting a Chrome background.
*/
customize.showCollectionSelectionDialog = function() {
const tileContainer = configData.richerPicker ?
$(customize.IDS.BACKGROUNDS_MENU) :
$(customize.IDS.TILES);
if (configData.richerPicker && customize.builtTiles) {
tileContainer.focus();
return;
}
customize.builtTiles = true;
const menu = configData.richerPicker ? $(customize.IDS.CUSTOMIZATION_MENU) :
$(customize.IDS.MENU);
if (!menu.open) {
menu.showModal();
}
// Create dialog header.
$(customize.IDS.TITLE).textContent =
configData.translatedStrings.selectChromeWallpaper;
if (!configData.richerPicker) {
menu.classList.add(customize.CLASSES.COLLECTION_DIALOG);
menu.classList.remove(customize.CLASSES.IMAGE_DIALOG);
}
const tileOnClickInteraction = function(event) {
let tile = event.target;
if (tile.classList.contains(customize.CLASSES.COLLECTION_TITLE)) {
tile = tile.parentNode;
}
// Load images for selected collection.
const imgElement = $('ntp-images-loader');
if (imgElement) {
imgElement.parentNode.removeChild(imgElement);
}
const imgScript = document.createElement('script');
imgScript.id = 'ntp-images-loader';
imgScript.src = 'chrome-search://local-ntp/ntp-background-images.js?' +
'collection_id=' + tile.dataset.id;
if (configData.richerPicker) {
ntpApiHandle.logEvent(customize.LOG_TYPE.NTP_BACKGROUND_OPEN_COLLECTION);
} else {
ntpApiHandle.logEvent(
customize.LOG_TYPE.NTP_CUSTOMIZE_CHROME_BACKGROUND_SELECT_COLLECTION);
}
document.body.appendChild(imgScript);
imgScript.onload = function() {
// Verify that the individual image data was successfully loaded.
const imageDataLoaded =
(collImg.length > 0 && collImg[0].collectionId == tile.dataset.id);
// Dependent upon the success of the load, populate the image selection
// dialog or close the current dialog.
if (imageDataLoaded) {
$(customize.IDS.BACKGROUNDS_MENU)
.classList.toggle(customize.CLASSES.MENU_SHOWN, false);
$(customize.IDS.BACKGROUNDS_IMAGE_MENU)
.classList.toggle(customize.CLASSES.MENU_SHOWN, true);
// In the RP the upload or default tile may be selected.
if (!configData.richerPicker) {
customize.resetSelectionDialog();
}
customize.showImageSelectionDialog(
tile.dataset.name, tile.dataset.tileIndex);
} else {
customize.handleError(collImgErrors);
}
};
};
// Create dialog tiles.
for (let i = 0; i < coll.length; ++i) {
const id = coll[i].collectionId;
const name = coll[i].collectionName;
const imageUrl = coll[i].previewImageUrl;
const dataset = {'id': id, 'name': name, 'tileIndex': i};
const tile = customize.createTileWithTitle(
'coll_tile_' + i, imageUrl, name, dataset, tileOnClickInteraction,
customize.tileOnKeyDownInteraction);
tileContainer.appendChild(tile);
}
// Attach event listeners for upload and default tiles
$(customize.IDS.BACKGROUNDS_UPLOAD_ICON).onkeydown =
customize.tileOnKeyDownInteraction;
$(customize.IDS.BACKGROUNDS_DEFAULT_ICON).onkeydown =
customize.tileOnKeyDownInteraction;
$(customize.IDS.BACKGROUNDS_UPLOAD_ICON).onClickOverride =
$(customize.IDS.BACKGROUNDS_UPLOAD).onkeydown;
$(customize.IDS.BACKGROUNDS_DEFAULT_ICON).onClickOverride =
$(customize.IDS.BACKGROUNDS_DEFAULT).onkeydown;
tileContainer.focus();
};
/**
* Return true if any shortcut option is selected.
* Note: Shortcut options are preselected according to current user settings.
*/
customize.richerPicker_isShortcutOptionSelected = function() {
// Check if the currently selected options are not the preselection.
const notPreselectedType = customize.preselectedOptions.shortcutType !==
customize.selectedOptions.shortcutType;
const notPreselectedHidden =
customize.preselectedOptions.shortcutsAreHidden !==
customize.selectedOptions.shortcutsAreHidden;
return notPreselectedType || notPreselectedHidden;
};
/**
* Apply styling to a selected option in the richer picker (i.e. the selected
* background image, shortcut type, and color).
* @param {?Element} option The option to apply styling to.
*/
customize.richerPicker_applySelectedState = function(option) {
if (!option) {
return;
}
option.parentElement.classList.toggle(customize.CLASSES.SELECTED, true);
// Create and append a blue checkmark to the selected option.
const selectedCircle = document.createElement('div');
const selectedCheck = document.createElement('div');
selectedCircle.classList.add(customize.CLASSES.SELECTED_CIRCLE);
selectedCheck.classList.add(customize.CLASSES.SELECTED_CHECK);
option.appendChild(selectedCircle);
option.appendChild(selectedCheck);
option.setAttribute('aria-pressed', true);
};
/**
* Remove styling from a selected option in the richer picker (i.e. the selected
* background image, shortcut type, and color).
* @param {?Element} option The option to remove styling from.
*/
customize.richerPicker_removeSelectedState = function(option) {
if (!option) {
return;
}
option.parentElement.classList.toggle(customize.CLASSES.SELECTED, false);
// Remove all blue checkmarks from the selected option (this includes the
// checkmark and the encompassing circle).
const select = option.querySelectorAll(
'.' + customize.CLASSES.SELECTED_CHECK + ', .' +
customize.CLASSES.SELECTED_CIRCLE);
select.forEach((element) => {
element.remove();
});
option.setAttribute('aria-pressed', false);
};
/**
* Preview an image as a custom backgrounds.
* @param {!Element} tile The tile that was selected.
*/
customize.richerPicker_previewImage = function(tile) {
if (!configData.richerPicker) {
return;
}
// Set preview images at 720p by replacing the params in the url.
const background = $(customize.IDS.CUSTOM_BG);
const preview = $(customize.IDS.CUSTOM_BG_PREVIEW);
if (tile.id === customize.IDS.BACKGROUNDS_DEFAULT_ICON) {
preview.dataset.hasImage = false;
preview.style.backgroundImage = '';
preview.style.backgroundColor = 'transparent';
} else if (tile.id === customize.IDS.BACKGROUNDS_UPLOAD_ICON) {
// No previews for uploaded images.
return;
} else {
preview.dataset.hasImage = true;
const re = /w\d+\-h\d+/;
preview.style.backgroundImage =
tile.style.backgroundImage.replace(re, 'w1280-h720');
preview.dataset.attributionLine1 = tile.dataset.attributionLine1;
preview.dataset.attributionLine2 = tile.dataset.attributionLine2;
preview.dataset.attributionActionUrl = tile.dataset.attributionActionUrl;
}
background.style.opacity = 0;
preview.style.opacity = 1;
preview.dataset.hasPreview = true;
ntpApiHandle.onthemechange();
};
/**
* Remove a preview image of a custom backgrounds.
*/
customize.richerPicker_unpreviewImage = function() {
if (!configData.richerPicker) {
return;
}
const preview = $(customize.IDS.CUSTOM_BG_PREVIEW);
if (!preview.dataset || !preview.dataset.hasPreview) {
return;
}
preview.style.opacity = 0;
preview.style.backgroundImage = '';
preview.style.backgroundColor = 'transparent';
preview.dataset.hasPreview = false;
$(customize.IDS.CUSTOM_BG).style.opacity = 1;
ntpApiHandle.onthemechange();
};
/**
* Handles background selection. Apply styling to the selected background tile
* in the richer picker, preview the background, and enable the done button.
* @param {?Element} tile The selected background tile.
*/
customize.richerPicker_selectBackgroundTile = function(tile) {
if (!tile) {
return;
}
if (tile.parentElement.classList.contains(customize.CLASSES.SELECTED)) {
// If the clicked tile is already selected do nothing.
return;
} else if (customize.selectedOptions.background) {
// Deselect any currently selected tile.
customize.richerPicker_removeSelectedState(
customize.selectedOptions.background);
}
$(customize.IDS.REFRESH_TOGGLE).checked = false;
// Remove any existing preview.
customize.richerPicker_unpreviewImage();
customize.selectedOptions.background = tile;
customize.selectedOptions.backgroundData = {
id: tile.id,
url: tile.dataset.url || '',
attr1: tile.dataset.attributionLine1 || '',
attr2: tile.dataset.attributionLine2 || '',
attrUrl: tile.dataset.attributionActionUrl || '',
collectionId: '',
};
customize.richerPicker_applySelectedState(tile);
// Don't apply a preview for a preselected image, as it's already the
// page background.
if (customize.preselectedOptions.backgroundsMenuTile !== tile) {
customize.richerPicker_previewImage(tile);
}
};
/**
* Handles shortcut type selection. Apply styling to a selected shortcut option
* and enable the done button.
* @param {?Element} shortcutType The shortcut type option's element.
*/
customize.richerPicker_selectShortcutType = function(shortcutType) {
if (!shortcutType ||
customize.selectedOptions.shortcutType === shortcutType) {
return; // The option has already been selected.
}
// Clear the previous selection, if any.
if (customize.selectedOptions.shortcutType) {
customize.richerPicker_removeSelectedState(
customize.selectedOptions.shortcutType);
}
customize.selectedOptions.shortcutType = shortcutType;
customize.richerPicker_applySelectedState(shortcutType);
};
/**
* Handles hide shortcuts toggle. Apply/remove styling for the toggle and
* enable/disable the done button.
* Note: If the toggle is enabled, the options for shortcut type will appear
* "disabled".
* @param {boolean} areHidden True if the shortcuts are hidden, i.e. the toggle
* is on.
*/
customize.richerPicker_toggleShortcutHide = function(areHidden) {
// (De)select the shortcut hide option.
$(customize.IDS.SHORTCUTS_HIDE)
.classList.toggle(customize.CLASSES.SELECTED, areHidden);
$(customize.IDS.SHORTCUTS_HIDE_TOGGLE).checked = areHidden;
$(customize.IDS.SHORTCUTS_MENU)
.classList.toggle(customize.CLASSES.HIDDEN_SELECTED, areHidden);
customize.selectedOptions.shortcutsAreHidden = areHidden;
};
/**
* Handles the "refresh daily" toggle.
* @param {boolean} toggledOn True if the toggle has been enabled.
*/
customize.richerPicker_toggleRefreshDaily = function(toggledOn) {
$(customize.IDS.REFRESH_TOGGLE).checked = toggledOn;
if (!toggledOn) {
customize.richerPicker_selectBackgroundTile(
$(customize.IDS.BACKGROUNDS_DEFAULT_ICON));
return;
}
if (customize.selectedOptions.background) {
customize.richerPicker_removeSelectedState(
customize.selectedOptions.background);
}
customize.selectedOptions.background = null;
customize.selectedOptions.backgroundData = {
id: '',
url: '',
attr1: '',
attr2: '',
attrUrl: '',
collectionId: customize.currentCollectionId,
};
};
/**
* Apply border and checkmark when a tile is selected
* @param {!Element} tile The tile to apply styling to.
*/
customize.applySelectedState = function(tile) {
tile.classList.add(customize.CLASSES.COLLECTION_SELECTED);
const selectedBorder = document.createElement('div');
const selectedCircle = document.createElement('div');
const selectedCheck = document.createElement('div');
selectedBorder.classList.add(customize.CLASSES.SELECTED_BORDER);
selectedCircle.classList.add(customize.CLASSES.SELECTED_CIRCLE);
selectedCheck.classList.add(customize.CLASSES.SELECTED_CHECK);
selectedBorder.appendChild(selectedCircle);
selectedBorder.appendChild(selectedCheck);
tile.appendChild(selectedBorder);
tile.dataset.oldLabel = tile.getAttribute('aria-label');
tile.setAttribute(
'aria-label',
tile.dataset.oldLabel + ' ' + configData.translatedStrings.selectedLabel);
};
/**
* Remove border and checkmark when a tile is un-selected
* @param {!Element} tile The tile to remove styling from.
*/
customize.removeSelectedState = function(tile) {
tile.classList.remove(customize.CLASSES.COLLECTION_SELECTED);
tile.removeChild(tile.firstChild);
tile.setAttribute('aria-label', tile.dataset.oldLabel);
};
/**
* Show dialog for selecting an image. Image data should previously have been
* loaded into collImg via
* chrome-search://local-ntp/ntp-background-images.js?collection_id=<collection_id>
* @param {string} dialogTitle The title to be displayed at the top of the
* dialog.
* @param {number} collIndex The index of the collection this image menu belongs
* to.
*/
customize.showImageSelectionDialog = function(dialogTitle, collIndex) {
const firstNTile = customize.ROWS_TO_PRELOAD * customize.getTilesWide();
const tileContainer = configData.richerPicker ?
$(customize.IDS.BACKGROUNDS_IMAGE_MENU) :
$(customize.IDS.TILES);
const menu = configData.richerPicker ? $(customize.IDS.CUSTOMIZATION_MENU) :
$(customize.IDS.MENU);
if (configData.richerPicker) {
menu.classList.toggle(customize.CLASSES.ON_IMAGE_MENU, true);
customize.richerPicker_showSubmenu(
$(customize.IDS.BACKGROUNDS_BUTTON), tileContainer, dialogTitle);
// Save the current image menu. Used to restore to when the Background
// submenu is reopened.
customize.richerPicker_openBackgroundSubmenu.menuId =
customize.IDS.BACKGROUNDS_IMAGE_MENU;
customize.richerPicker_openBackgroundSubmenu.title = dialogTitle;
} else {
$(customize.IDS.TITLE).textContent = dialogTitle;
menu.classList.remove(customize.CLASSES.COLLECTION_DIALOG);
menu.classList.add(customize.CLASSES.IMAGE_DIALOG);
}
const tileInteraction = function(tile) {
if (customize.selectedOptions.background && !configData.richerPicker) {
customize.removeSelectedState(customize.selectedOptions.background);
if (customize.selectedOptions.background.id === tile.id) {
customize.unselectTile();
return;
}
}
if (configData.richerPicker) {
if (!customize.selectedOptions.backgroundData ||
customize.selectedOptions.backgroundData.id !== tile.id) {
ntpApiHandle.logEvent(customize.LOG_TYPE.NTP_BACKGROUND_SELECT_IMAGE);
}
customize.richerPicker_selectBackgroundTile(tile);
} else {
customize.applySelectedState(tile);
customize.selectedOptions.background = tile;
}
$(customize.IDS.DONE).tabIndex = 0;
// Turn toggle off when an image is selected.
$(customize.IDS.DONE).disabled = false;
ntpApiHandle.logEvent(
customize.LOG_TYPE.NTP_CUSTOMIZE_CHROME_BACKGROUND_SELECT_IMAGE);
};
const tileOnClickInteraction = function(event) {
const clickCount = event.detail;
// Control + option + space will fire the onclick event with 0 clickCount.
if (clickCount <= 1) {
tileInteraction(event.currentTarget);
} else if (
clickCount === 2 &&
customize.selectedOptions.background === event.currentTarget) {
customize.setBackground(
event.currentTarget.dataset.url,
event.currentTarget.dataset.attributionLine1,
event.currentTarget.dataset.attributionLine2,
event.currentTarget.dataset.attributionActionUrl,
/*collection_id=*/ '');
}
};
const preLoadTiles = [];
const postLoadTiles = [];
for (let i = 0; i < collImg.length; ++i) {
const dataset = {};
dataset.attributionLine1 =
(collImg[i].attributions[0] !== undefined ? collImg[i].attributions[0] :
'');
dataset.attributionLine2 =
(collImg[i].attributions[1] !== undefined ? collImg[i].attributions[1] :
'');
dataset.attributionActionUrl = collImg[i].attributionActionUrl;
dataset.url = collImg[i].imageUrl;
dataset.tileIndex = i;
let tileId = 'img_tile_' + i;
if (configData.richerPicker) {
tileId = 'coll_' + collIndex + '_' + tileId;
}
const tile = customize.createTileThumbnail(
tileId, collImg[i].thumbnailImageUrl, dataset, tileOnClickInteraction,
customize.tileOnKeyDownInteraction);
tile.setAttribute('aria-label', collImg[i].attributions[0]);
tile.title = collImg[i].attributions[0];
// Load the first |ROWS_TO_PRELOAD| rows of tiles.
if (i < firstNTile) {
preLoadTiles.push(tile);
} else {
postLoadTiles.push(tile);
}
const tileBackground = document.createElement('div');
tileBackground.classList.add(customize.CLASSES.COLLECTION_TILE_BG);
tileBackground.appendChild(tile);
tileContainer.appendChild(tileBackground);
}
let tileGetsLoaded = 0;
for (const tile of preLoadTiles) {
customize.loadTile(tile, collImg, () => {
// After the preloaded tiles finish loading, the rest of the tiles start
// loading.
if (++tileGetsLoaded === preLoadTiles.length) {
postLoadTiles.forEach(
(tile) => customize.loadTile(tile, collImg, null));
}
});
}
customize.currentCollectionId = collImg[0].collectionId;
$(customize.IDS.REFRESH_TOGGLE).checked = false;
// If an image tile was previously selected re-select it now.
if (customize.selectedOptions.backgroundData) {
const selected = $(customize.selectedOptions.backgroundData.id);
if (selected) {
customize.richerPicker_selectBackgroundTile(selected);
} else if (
customize.selectedOptions.backgroundData.collectionId ===
customize.currentCollectionId) {
$(customize.IDS.REFRESH_TOGGLE).checked = true;
}
} else {
customize.richerPicker_preselectBackgroundOption();
}
$(customize.IDS.REFRESH_DAILY_WRAPPER).hidden = false;
if (configData.richerPicker) {
$(customize.IDS.BACKGROUNDS_IMAGE_MENU).focus();
} else {
$(customize.IDS.TILES).focus();
}
};
/**
* Add background image src to the tile and add animation for the tile once it
* successfully loaded.
* @param {!Object} tile the tile that needs to be loaded.
* @param {!Object} imageData the source imageData.
* @param {?Function} countLoad If not null, called after the tile finishes
* loading.
*/
customize.loadTile = function(tile, imageData, countLoad) {
tile.style.backgroundImage =
'url(' + imageData[tile.dataset.tileIndex].thumbnailImageUrl + ')';
customize.fadeInImageTile(
tile, imageData[tile.dataset.tileIndex].thumbnailImageUrl, countLoad);
};
/**
* Fade in effect for both collection and image tile. Once the image
* successfully loads, we can assume the background image with the same source
* has also loaded. Then, we set opacity for the tile to start the animation.
* @param {!Object} tile The tile to add the fade in animation to.
* @param {string} imageUrl the image url for the tile
* @param {?Function} countLoad If not null, called after the tile finishes
* loading.
*/
customize.fadeInImageTile = function(tile, imageUrl, countLoad) {
const image = new Image();
image.onload = () => {
tile.style.opacity = '1';
if (countLoad) {
countLoad();
}
};
image.src = imageUrl;
};
/**
* Load the NTPBackgroundCollections script. It'll create a global
* variable name "coll" which is a dict of background collections data.
*/
customize.loadChromeBackgrounds = function() {
const collElement = $('ntp-collection-loader');
if (collElement) {
collElement.parentNode.removeChild(collElement);
}
const collScript = document.createElement('script');
collScript.id = 'ntp-collection-loader';
collScript.src = 'chrome-search://local-ntp/ntp-background-collections.js?' +
'collection_type=background';
collScript.onload = function() {
if (configData.richerPicker) {
customize.showCollectionSelectionDialog();
}
};
document.body.appendChild(collScript);
};
/**
* Close dialog when an image is selected via the file picker.
*/
customize.closeCustomizationDialog = function() {
if (configData.richerPicker) {
$(customize.IDS.CUSTOMIZATION_MENU).close();
} else {
$(customize.IDS.EDIT_BG_DIALOG).close();
}
};
/**
* Get the next visible option. There are times when various combinations of
* options are hidden.
* @param {number} current_index Index of the option the key press occurred on.
* @param {number} deltaY Direction to search in, -1 for up, 1 for down.
*/
customize.getNextOption = function(current_index, deltaY) {
// Create array corresponding to the menu. Important that this is in the same
// order as the MENU_ENTRIES enum, so we can index into it.
const entries = [];
entries.push($(customize.IDS.DEFAULT_WALLPAPERS));
entries.push($(customize.IDS.UPLOAD_IMAGE));
entries.push($(customize.IDS.CUSTOM_LINKS_RESTORE_DEFAULT));
entries.push($(customize.IDS.RESTORE_DEFAULT));
let idx = current_index;
do {
idx = idx + deltaY;
if (idx === -1) {
idx = 3;
}
if (idx === 4) {
idx = 0;
}
} while (
idx !== current_index &&
(entries[idx].hidden ||
entries[idx].classList.contains(customize.CLASSES.OPTION_DISABLED)));
return entries[idx];
};
/**
* Hide custom background options based on the network state
* @param {boolean} online The current state of the network
*/
customize.networkStateChanged = function(online) {
$(customize.IDS.DEFAULT_WALLPAPERS).hidden = !online;
};
/**
* Open the customization menu and set it to the default submenu (Background).
*/
customize.richerPicker_openCustomizationMenu = function() {
ntpApiHandle.logEvent(customize.LOG_TYPE.NTP_CUSTOMIZATION_MENU_OPENED);
customize.richerPicker_showSubmenu(
$(customize.IDS.BACKGROUNDS_BUTTON), $(customize.IDS.BACKGROUNDS_MENU));
customize.richerPicker_preselectShortcutOptions();
customize.richerPicker_preselectBackgroundOption();
customize.loadChromeBackgrounds();
customize.loadColorsMenu();
if (!$(customize.IDS.CUSTOMIZATION_MENU).open) {
$(customize.IDS.CUSTOMIZATION_MENU).showModal();
}
};
/**
* Reset the selected options in the customization menu.
*/
customize.richerPicker_resetSelectedOptions = function() {
// Reset background selection.
customize.richerPicker_removeSelectedState(
customize.selectedOptions.background);
customize.selectedOptions.background = null;
customize.selectedOptions.backgroundData = null;
customize.resetColorsSelectedOptions();
customize.richerPicker_preselectShortcutOptions();
};
/**
* Preselect the shortcut type and visibility to reflect the current state on
* the page.
*/
customize.richerPicker_preselectShortcutOptions = function() {
const shortcutType = chrome.embeddedSearch.newTabPage.isUsingMostVisited ?
$(customize.IDS.SHORTCUTS_OPTION_MOST_VISITED) :
$(customize.IDS.SHORTCUTS_OPTION_CUSTOM_LINKS);
const shortcutsAreHidden =
!chrome.embeddedSearch.newTabPage.areShortcutsVisible;
customize.preselectedOptions.shortcutType = shortcutType;
customize.preselectedOptions.shortcutsAreHidden = shortcutsAreHidden;
customize.richerPicker_selectShortcutType(shortcutType);
customize.richerPicker_toggleShortcutHide(shortcutsAreHidden);
};
/**
* Preselect the background tile that corresponds to the current page
* background.
*/
customize.richerPicker_preselectBackgroundOption = function() {
if (!configData.richerPicker) {
return;
}
customize.preselectedOptions.backgroundsMenuTile = null;
const themeInfo = assert(ntpApiHandle.themeBackgroundInfo);
if (!themeInfo.customBackgroundConfigured) {
// Default.
customize.preselectedOptions.backgroundsMenuTile =
$(customize.IDS.BACKGROUNDS_DEFAULT_ICON);
} else if (themeInfo.imageUrl.includes(
'chrome-search://local-ntp/background.jpg')) {
// Local image.
customize.preselectedOptions.backgroundsMenuTile =
$(customize.IDS.BACKGROUNDS_UPLOAD_ICON);
} else if (
themeInfo.collectionId !== '' &&
customize.currentCollectionId == themeInfo.collectionId) {
// Daily refresh.
$(customize.IDS.REFRESH_TOGGLE).checked = true;
} else if (!customize.selectedOptions.backgroundData) {
// Image tile. Only if another background hasn't already been selected.
customize.preselectedOptions.backgroundsMenuTile =
document.querySelector('[data-url="' + themeInfo.imageUrl + '"]');
}
customize.richerPicker_selectBackgroundTile(
customize.preselectedOptions.backgroundsMenuTile);
customize.selectedOptions.backgroundData = null;
};
/**
* Resets the customization menu.
*/
customize.richerPicker_resetCustomizationMenu = function() {
customize.richerPicker_resetSelectedOptions();
customize.richerPicker_resetImageMenu();
customize.richerPicker_hideOpenSubmenu();
customize.resetColorPicker();
};
/**
* Close and reset the customization menu.
*/
customize.richerPicker_closeCustomizationMenu = function() {
$(customize.IDS.BACKGROUNDS_MENU).scrollTop = 0;
$(customize.IDS.CUSTOMIZATION_MENU).close();
customize.richerPicker_resetCustomizationMenu();
customize.richerPicker_unpreviewImage();
};
/**
* Cancel customization, revert any changes, and close the richer picker.
*/
customize.richerPicker_cancelCustomization = function() {
ntpApiHandle.logEvent(customize.LOG_TYPE.NTP_CUSTOMIZATION_MENU_CANCEL);
if (customize.isColorOptionSelected()) {
customize.cancelColor();
}
customize.richerPicker_closeCustomizationMenu();
};
/**
* Apply the currently selected customization options and close the richer
* picker.
*/
customize.richerPicker_applyCustomization = function() {
if (customize.isBackgroundOptionSelected()) {
customize.setBackground(
customize.selectedOptions.backgroundData.url,
customize.selectedOptions.backgroundData.attr1,
customize.selectedOptions.backgroundData.attr2,
customize.selectedOptions.backgroundData.attrUrl,
customize.selectedOptions.backgroundData.collectionId);
}
if (customize.richerPicker_isShortcutOptionSelected()) {
customize.richerPicker_setShortcutOptions();
}
if (customize.isColorOptionSelected()) {
customize.confirmColor();
}
customize.richerPicker_closeCustomizationMenu();
};
/**
* Initialize the settings menu, custom backgrounds dialogs, and custom
* links menu items. Set the text and event handlers for the various
* elements.
* @param {!Function} showErrorNotification Called when the error notification
* should be displayed.
* @param {!Function} hideCustomLinkNotification Called when the custom link
* notification should be hidden.
*/
customize.init = function(showErrorNotification, hideCustomLinkNotification) {
ntpApiHandle = window.chrome.embeddedSearch.newTabPage;
const editDialog = $(customize.IDS.EDIT_BG_DIALOG);
$(customize.IDS.OPTIONS_TITLE).textContent =
configData.translatedStrings.customizeThisPage;
if (configData.richerPicker) {
// Store the main menu title so it can be restored if needed.
customize.richerPicker_defaultTitle =
$(customize.IDS.MENU_TITLE).textContent;
}
$(customize.IDS.EDIT_BG)
.setAttribute(
'aria-label', configData.translatedStrings.customizeThisPage);
$(customize.IDS.EDIT_BG)
.setAttribute('title', configData.translatedStrings.customizeThisPage);
// Selecting a local image for the background should close the picker.
if (configData.richerPicker) {
ntpApiHandle.onlocalbackgroundselected = () => {
customize.selectedOptions.backgroundData = null;
customize.richerPicker_applyCustomization();
};
}
// Edit gear icon interaction events.
const editBackgroundInteraction = function() {
if (configData.richerPicker) {
customize.richerPicker_openCustomizationMenu();
} else {
editDialog.showModal();
}
};
$(customize.IDS.EDIT_BG).onclick = function(event) {
$(customize.IDS.CUSTOMIZATION_MENU)
.classList.add(customize.CLASSES.MOUSE_NAV);
editDialog.classList.add(customize.CLASSES.MOUSE_NAV);
editBackgroundInteraction();
};
$(customize.IDS.MENU_CANCEL).onclick = function(event) {
customize.richerPicker_cancelCustomization();
};
// Find the first menu option that is not hidden or disabled.
const findFirstMenuOption = () => {
const editMenu = $(customize.IDS.EDIT_BG_MENU);
for (let i = 1; i < editMenu.children.length; i++) {
const option = editMenu.children[i];
if (option.classList.contains(customize.CLASSES.OPTION) &&
!option.hidden &&
!option.classList.contains(customize.CLASSES.OPTION_DISABLED)) {
option.focus();
return;
}
}
};
$(customize.IDS.EDIT_BG).onkeydown = function(event) {
if (event.keyCode === customize.KEYCODES.ENTER ||
event.keyCode === customize.KEYCODES.SPACE) {
// no default behavior for ENTER
event.preventDefault();
editDialog.classList.remove(customize.CLASSES.MOUSE_NAV);
editBackgroundInteraction();
findFirstMenuOption();
}
};
// Interactions to close the customization option dialog.
const editDialogInteraction = function() {
editDialog.close();
};
editDialog.onclick = function(event) {
editDialog.classList.add(customize.CLASSES.MOUSE_NAV);
if (event.target === editDialog) {
editDialogInteraction();
}
};
editDialog.onkeydown = function(event) {
if (event.keyCode === customize.KEYCODES.ESC) {
editDialogInteraction();
} else if (
editDialog.classList.contains(customize.CLASSES.MOUSE_NAV) &&
(event.keyCode === customize.KEYCODES.TAB ||
event.keyCode === customize.KEYCODES.UP ||
event.keyCode === customize.KEYCODES.DOWN)) {
// When using tab in mouse navigation mode, select the first option
// available.
event.preventDefault();
findFirstMenuOption();
editDialog.classList.remove(customize.CLASSES.MOUSE_NAV);
} else if (event.keyCode === customize.KEYCODES.TAB) {
// If keyboard navigation is attempted, remove mouse-only mode.
editDialog.classList.remove(customize.CLASSES.MOUSE_NAV);
} else if (customize.arrowKeys.includes(event.keyCode)) {
event.preventDefault();
editDialog.classList.remove(customize.CLASSES.MOUSE_NAV);
}
};
customize.initCustomLinksItems(hideCustomLinkNotification);
customize.initCustomBackgrounds(showErrorNotification);
};
/**
* Initialize custom link items in the settings menu dialog. Set the text
* and event handlers for the various elements.
* @param {!Function} hideCustomLinkNotification Called when the custom link
* notification should be hidden.
*/
customize.initCustomLinksItems = function(hideCustomLinkNotification) {
customize.hideCustomLinkNotification = hideCustomLinkNotification;
const editDialog = $(customize.IDS.EDIT_BG_DIALOG);
const menu = $(customize.IDS.MENU);
$(customize.IDS.CUSTOM_LINKS_RESTORE_DEFAULT_TEXT).textContent =
configData.translatedStrings.restoreDefaultLinks;
// Interactions with the "Restore default shortcuts" option.
const customLinksRestoreDefaultInteraction = function() {
editDialog.close();
customize.hideCustomLinkNotification();
window.chrome.embeddedSearch.newTabPage.resetCustomLinks();
ntpApiHandle.logEvent(
customize.LOG_TYPE.NTP_CUSTOMIZE_RESTORE_SHORTCUTS_CLICKED);
};
$(customize.IDS.CUSTOM_LINKS_RESTORE_DEFAULT).onclick = () => {
if (!$(customize.IDS.CUSTOM_LINKS_RESTORE_DEFAULT)
.classList.contains(customize.CLASSES.OPTION_DISABLED)) {
customLinksRestoreDefaultInteraction();
}
};
$(customize.IDS.CUSTOM_LINKS_RESTORE_DEFAULT).onkeydown = function(event) {
if (event.keyCode === customize.KEYCODES.ENTER) {
customLinksRestoreDefaultInteraction();
} else if (event.keyCode === customize.KEYCODES.UP) {
// Handle arrow key navigation.
event.preventDefault();
customize
.getNextOption(
customize.MENU_ENTRIES.CUSTOM_LINKS_RESTORE_DEFAULT, -1)
.focus();
} else if (event.keyCode === customize.KEYCODES.DOWN) {
event.preventDefault();
customize
.getNextOption(customize.MENU_ENTRIES.CUSTOM_LINKS_RESTORE_DEFAULT, 1)
.focus();
}
};
};
/**
* Initialize the settings menu and custom backgrounds dialogs. Set the
* text and event handlers for the various elements.
* @param {!Function} showErrorNotification Called when the error notification
* should be displayed.
*/
customize.initCustomBackgrounds = function(showErrorNotification) {
customize.showErrorNotification = showErrorNotification;
const editDialog = $(customize.IDS.EDIT_BG_DIALOG);
const menu = $(customize.IDS.MENU);
$(customize.IDS.DEFAULT_WALLPAPERS_TEXT).textContent =
configData.translatedStrings.defaultWallpapers;
$(customize.IDS.UPLOAD_IMAGE_TEXT).textContent =
configData.translatedStrings.uploadImage;
$(customize.IDS.RESTORE_DEFAULT_TEXT).textContent =
configData.translatedStrings.restoreDefaultBackground;
$(customize.IDS.DONE).textContent =
configData.translatedStrings.selectionDone;
$(customize.IDS.CANCEL).textContent =
configData.translatedStrings.selectionCancel;
window.addEventListener('online', function(event) {
customize.networkStateChanged(true);
});
window.addEventListener('offline', function(event) {
customize.networkStateChanged(false);
});
if (!window.navigator.onLine) {
customize.networkStateChanged(false);
}
$(customize.IDS.BACK_CIRCLE)
.setAttribute('aria-label', configData.translatedStrings.backLabel);
$(customize.IDS.CANCEL)
.setAttribute('aria-label', configData.translatedStrings.selectionCancel);
$(customize.IDS.DONE)
.setAttribute('aria-label', configData.translatedStrings.selectionDone);
$(customize.IDS.DONE).disabled = true;
// Interactions with the "Upload an image" option.
const uploadImageInteraction = function() {
window.chrome.embeddedSearch.newTabPage.selectLocalBackgroundImage();
if (configData.richerPicker) {
ntpApiHandle.logEvent(
customize.LOG_TYPE.NTP_BACKGROUND_UPLOAD_FROM_DEVICE);
} else {
ntpApiHandle.logEvent(
customize.LOG_TYPE.NTP_CUSTOMIZE_LOCAL_IMAGE_CLICKED);
}
};
$(customize.IDS.UPLOAD_IMAGE).onclick = (event) => {
if (!$(customize.IDS.UPLOAD_IMAGE)
.classList.contains(customize.CLASSES.OPTION_DISABLED)) {
uploadImageInteraction();
}
};
$(customize.IDS.UPLOAD_IMAGE).onkeydown = function(event) {
if (event.keyCode === customize.KEYCODES.ENTER) {
uploadImageInteraction();
}
// Handle arrow key navigation.
if (event.keyCode === customize.KEYCODES.UP) {
event.preventDefault();
customize.getNextOption(customize.MENU_ENTRIES.UPLOAD_IMAGE, -1).focus();
}
if (event.keyCode === customize.KEYCODES.DOWN) {
event.preventDefault();
customize.getNextOption(customize.MENU_ENTRIES.UPLOAD_IMAGE, 1).focus();
}
};
// Interactions with the "Restore default background" option.
const restoreDefaultInteraction = function() {
editDialog.close();
customize.clearAttribution();
window.chrome.embeddedSearch.newTabPage.resetBackgroundInfo();
};
$(customize.IDS.RESTORE_DEFAULT).onclick = (event) => {
if (!$(customize.IDS.RESTORE_DEFAULT)
.classList.contains(customize.CLASSES.OPTION_DISABLED)) {
restoreDefaultInteraction();
}
};
$(customize.IDS.RESTORE_DEFAULT).onkeydown = function(event) {
if (event.keyCode === customize.KEYCODES.ENTER) {
restoreDefaultInteraction();
}
// Handle arrow key navigation.
if (event.keyCode === customize.KEYCODES.UP) {
event.preventDefault();
customize.getNextOption(customize.MENU_ENTRIES.RESTORE_DEFAULT, -1)
.focus();
}
if (event.keyCode === customize.KEYCODES.DOWN) {
event.preventDefault();
customize.getNextOption(customize.MENU_ENTRIES.RESTORE_DEFAULT, 1)
.focus();
}
};
// Interactions with the "Chrome backgrounds" option.
const defaultWallpapersInteraction = function(event) {
customize.loadChromeBackgrounds();
$('ntp-collection-loader').onload = function() {
editDialog.close();
if (typeof coll != 'undefined' && coll.length > 0) {
customize.showCollectionSelectionDialog();
} else {
customize.handleError(collErrors);
}
};
ntpApiHandle.logEvent(
customize.LOG_TYPE.NTP_CUSTOMIZE_CHROME_BACKGROUNDS_CLICKED);
};
$(customize.IDS.DEFAULT_WALLPAPERS).onclick = function(event) {
$(customize.IDS.MENU).classList.add(customize.CLASSES.MOUSE_NAV);
defaultWallpapersInteraction(event);
};
$(customize.IDS.DEFAULT_WALLPAPERS).onkeydown = function(event) {
if (event.keyCode === customize.KEYCODES.ENTER) {
$(customize.IDS.MENU).classList.remove(customize.CLASSES.MOUSE_NAV);
defaultWallpapersInteraction(event);
}
// Handle arrow key navigation.
if (event.keyCode === customize.KEYCODES.UP) {
event.preventDefault();
customize.getNextOption(customize.MENU_ENTRIES.CHROME_BACKGROUNDS, -1)
.focus();
}
if (event.keyCode === customize.KEYCODES.DOWN) {
event.preventDefault();
customize.getNextOption(customize.MENU_ENTRIES.CHROME_BACKGROUNDS, 1)
.focus();
}
};
// Escape and Backspace handling for the background picker dialog.
menu.onkeydown = function(event) {
if (event.keyCode === customize.KEYCODES.SPACE) {
$(customize.IDS.TILES).scrollTop += $(customize.IDS.TILES).offsetHeight;
event.stopPropagation();
event.preventDefault();
}
if (event.keyCode === customize.KEYCODES.ESC ||
event.keyCode === customize.KEYCODES.BACKSPACE) {
event.preventDefault();
event.stopPropagation();
if (menu.classList.contains(customize.CLASSES.COLLECTION_DIALOG)) {
menu.close();
customize.resetSelectionDialog();
} else {
customize.resetSelectionDialog();
customize.showCollectionSelectionDialog();
}
}
// If keyboard navigation is attempted, remove mouse-only mode.
if (event.keyCode === customize.KEYCODES.TAB ||
event.keyCode === customize.KEYCODES.LEFT ||
event.keyCode === customize.KEYCODES.UP ||
event.keyCode === customize.KEYCODES.RIGHT ||
event.keyCode === customize.KEYCODES.DOWN) {
menu.classList.remove(customize.CLASSES.MOUSE_NAV);
}
};
// Interactions with the back arrow on the image selection dialog.
const backInteraction = function(event) {
if (configData.richerPicker) {
ntpApiHandle.logEvent(customize.LOG_TYPE.NTP_BACKGROUND_BACK_CLICK);
customize.richerPicker_resetImageMenu();
}
customize.resetSelectionDialog();
customize.showCollectionSelectionDialog();
};
$(customize.IDS.BACK_CIRCLE).onclick = backInteraction;
$(customize.IDS.MENU_BACK_CIRCLE).onclick = backInteraction;
$(customize.IDS.BACK_CIRCLE).onkeyup = function(event) {
if (event.keyCode === customize.KEYCODES.ENTER ||
event.keyCode === customize.KEYCODES.SPACE) {
backInteraction(event);
}
};
$(customize.IDS.MENU_BACK_CIRCLE).onkeyup = function(event) {
if (event.keyCode === customize.KEYCODES.ENTER ||
event.keyCode === customize.KEYCODES.SPACE) {
backInteraction(event);
}
};
// Pressing Spacebar on the back arrow shouldn't scroll the dialog.
$(customize.IDS.BACK_CIRCLE).onkeydown = function(event) {
if (event.keyCode === customize.KEYCODES.SPACE) {
event.stopPropagation();
}
};
// Interactions with the cancel button on the background picker dialog.
$(customize.IDS.CANCEL).onclick = function(event) {
customize.closeCollectionDialog(menu);
ntpApiHandle.logEvent(
customize.LOG_TYPE.NTP_CUSTOMIZE_CHROME_BACKGROUND_CANCEL);
};
$(customize.IDS.CANCEL).onkeydown = function(event) {
if (event.keyCode === customize.KEYCODES.ENTER ||
event.keyCode === customize.KEYCODES.SPACE) {
customize.closeCollectionDialog(menu);
ntpApiHandle.logEvent(
customize.LOG_TYPE.NTP_CUSTOMIZE_CHROME_BACKGROUND_CANCEL);
}
};
// Interactions with the done button on the background picker dialog.
const doneInteraction = function(event) {
const done = configData.richerPicker ? $(customize.IDS.MENU_DONE) :
$(customize.IDS.DONE);
if (configData.richerPicker) {
ntpApiHandle.logEvent(customize.LOG_TYPE.NTP_CUSTOMIZATION_MENU_DONE);
customize.richerPicker_applyCustomization();
} else if (customize.selectedOptions.background) {
// Also closes the customization menu.
customize.setBackground(
customize.selectedOptions.background.dataset.url,
customize.selectedOptions.background.dataset.attributionLine1,
customize.selectedOptions.background.dataset.attributionLine2,
customize.selectedOptions.background.dataset.attributionActionUrl,
'');
}
};
$(customize.IDS.DONE).onclick = doneInteraction;
$(customize.IDS.MENU_DONE).onclick = doneInteraction;
$(customize.IDS.DONE).onkeyup = function(event) {
if (event.keyCode === customize.KEYCODES.ENTER) {
doneInteraction(event);
}
};
// On any arrow key event in the tiles area, focus the first tile.
$(customize.IDS.TILES).onkeydown = function(event) {
if (document.activeElement === $(customize.IDS.TILES) &&
customize.arrowKeys.includes(event.keyCode)) {
event.preventDefault();
if ($(customize.IDS.MENU)
.classList.contains(customize.CLASSES.COLLECTION_DIALOG)) {
$('coll_tile_0').focus();
} else {
document.querySelector('[id$="img_tile_0"]').focus();
}
}
};
$(customize.IDS.BACKGROUNDS_MENU).onkeydown = function(event) {
if (document.activeElement === $(customize.IDS.BACKGROUNDS_MENU) &&
customize.arrowKeys.includes(event.keyCode)) {
event.preventDefault();
$(customize.IDS.BACKGROUNDS_UPLOAD_ICON).focus();
}
};
$(customize.IDS.BACKGROUNDS_IMAGE_MENU).onkeydown = function(event) {
if (document.activeElement === $(customize.IDS.BACKGROUNDS_IMAGE_MENU) &&
customize.arrowKeys.includes(event.keyCode)) {
event.preventDefault();
document.querySelector('[id$="img_tile_0"]').focus();
}
};
$(customize.IDS.BACKGROUNDS_UPLOAD).onclick = uploadImageInteraction;
$(customize.IDS.BACKGROUNDS_UPLOAD).onkeydown = function(event) {
if (event.keyCode === customize.KEYCODES.ENTER ||
event.keyCode === customize.KEYCODES.SPACE) {
uploadImageInteraction();
}
};
$(customize.IDS.BACKGROUNDS_DEFAULT).onclick = function(event) {
const tile = $(customize.IDS.BACKGROUNDS_DEFAULT_ICON);
tile.dataset.url = '';
tile.dataset.attributionLine1 = '';
tile.dataset.attributionLine2 = '';
tile.dataset.attributionActionUrl = '';
if (!$(customize.IDS.BACKGROUNDS_DEFAULT)
.classList.contains(customize.CLASSES.SELECTED)) {
ntpApiHandle.logEvent(customize.LOG_TYPE.NTP_BACKGROUND_DEFAULT_SELECTED);
customize.richerPicker_selectBackgroundTile(tile);
}
};
$(customize.IDS.BACKGROUNDS_DEFAULT).onkeydown = function(event) {
if (event.keyCode === customize.KEYCODES.ENTER ||
event.keyCode === customize.KEYCODES.SPACE) {
$(customize.IDS.BACKGROUNDS_DEFAULT).onclick(event);
}
};
const richerPicker = $(customize.IDS.CUSTOMIZATION_MENU);
richerPicker.onmousedown = function(event) {
richerPicker.classList.add(customize.CLASSES.MOUSE_NAV);
};
richerPicker.onkeydown = function(event) {
richerPicker.classList.remove(customize.CLASSES.MOUSE_NAV);
if (event.keyCode === customize.KEYCODES.BACKSPACE &&
customize.richerPicker_selectedSubmenu.menu.id ===
customize.IDS.BACKGROUNDS_IMAGE_MENU) {
backInteraction(event);
} else if (
event.keyCode === customize.KEYCODES.ESC ||
event.keyCode === customize.KEYCODES.BACKSPACE) {
customize.richerPicker_cancelCustomization();
}
};
const richerPickerOpenBackgrounds = function() {
// Open the previously open Background submenu, if applicable.
customize.richerPicker_showSubmenu(
$(customize.IDS.BACKGROUNDS_BUTTON),
$(customize.richerPicker_openBackgroundSubmenu.menuId),
customize.richerPicker_openBackgroundSubmenu.title);
};
$(customize.IDS.BACKGROUNDS_BUTTON).onclick = richerPickerOpenBackgrounds;
$(customize.IDS.BACKGROUNDS_BUTTON).onkeydown = function(event) {
if (event.keyCode === customize.KEYCODES.ENTER ||
event.keyCode === customize.KEYCODES.SPACE) {
richerPickerOpenBackgrounds();
}
};
const clOption = $(customize.IDS.SHORTCUTS_OPTION_CUSTOM_LINKS);
const mvOption = $(customize.IDS.SHORTCUTS_OPTION_MOST_VISITED);
const hideToggle = $(customize.IDS.SHORTCUTS_HIDE_TOGGLE);
const rtl = window.chrome.embeddedSearch.searchBox.rtl;
const forwardArrowKey =
rtl ? customize.KEYCODES.LEFT : customize.KEYCODES.RIGHT;
const backArrowKey = rtl ? customize.KEYCODES.RIGHT : customize.KEYCODES.LEFT;
$(customize.IDS.SHORTCUTS_MENU).onkeydown = function(event) {
if (customize.arrowKeys.includes(event.keyCode)) {
clOption.focus();
}
};
clOption.onclick = function() {
if (customize.selectedOptions.shortcutType !== clOption) {
ntpApiHandle.logEvent(
customize.LOG_TYPE.NTP_CUSTOMIZE_SHORTCUT_CUSTOM_LINKS_CLICKED);
}
customize.richerPicker_selectShortcutType(clOption);
// Selecting a shortcut type will turn off hidden shortcuts.
customize.richerPicker_toggleShortcutHide(false);
};
clOption.onkeydown = function(event) {
if (customize.arrowKeys.includes(event.keyCode)) {
// Handle arrow key navigation.
event.preventDefault();
event.stopPropagation();
richerPicker.classList.remove(customize.CLASSES.MOUSE_NAV);
if (event.keyCode === forwardArrowKey) {
mvOption.focus();
} else if (event.keyCode === customize.KEYCODES.DOWN) {
hideToggle.focus();
}
}
};
clOption.onkeyup = function(event) {
if (event.keyCode === customize.KEYCODES.ENTER ||
event.keyCode === customize.KEYCODES.SPACE) {
clOption.click();
}
};
mvOption.onclick = function() {
if (customize.selectedOptions.shortcutType !== mvOption) {
ntpApiHandle.logEvent(
customize.LOG_TYPE.NTP_CUSTOMIZE_SHORTCUT_MOST_VISITED_CLICKED);
}
customize.richerPicker_selectShortcutType(mvOption);
// Selecting a shortcut type will turn off hidden shortcuts.
customize.richerPicker_toggleShortcutHide(false);
};
mvOption.onkeydown = function(event) {
if (customize.arrowKeys.includes(event.keyCode)) {
// Handle arrow key navigation.
event.preventDefault();
event.stopPropagation();
richerPicker.classList.remove(customize.CLASSES.MOUSE_NAV);
if (event.keyCode === backArrowKey) {
clOption.focus();
} else if (
event.keyCode === forwardArrowKey ||
event.keyCode === customize.KEYCODES.DOWN) {
hideToggle.focus();
}
}
};
mvOption.onkeyup = function(event) {
if (event.keyCode === customize.KEYCODES.ENTER ||
event.keyCode === customize.KEYCODES.SPACE) {
mvOption.click();
}
};
hideToggle.onchange = function(event) {
customize.richerPicker_toggleShortcutHide(hideToggle.checked);
ntpApiHandle.logEvent(
customize.LOG_TYPE.NTP_CUSTOMIZE_SHORTCUT_VISIBILITY_TOGGLE_CLICKED);
};
hideToggle.onkeydown = function(event) {
if (customize.arrowKeys.includes(event.keyCode)) {
// Handle arrow key navigation.
event.preventDefault();
event.stopPropagation();
richerPicker.classList.remove(customize.CLASSES.MOUSE_NAV);
if (event.keyCode === backArrowKey ||
event.keyCode === customize.KEYCODES.UP) {
mvOption.focus();
}
}
};
hideToggle.onkeyup = function(event) {
// Handle enter since, unlike space, it does not trigger a click event.
if (event.keyCode === customize.KEYCODES.ENTER) {
hideToggle.click();
}
};
hideToggle.onclick = function(event) {
// Enter and space fire the 'onclick' event (which will remove special
// keyboard navigation styling) unless propagation is stopped.
event.stopPropagation();
};
const refreshToggle = $(customize.IDS.REFRESH_TOGGLE);
refreshToggle.onchange = function(event) {
ntpApiHandle.logEvent(
customize.LOG_TYPE.NTP_BACKGROUND_REFRESH_TOGGLE_CLICKED);
customize.richerPicker_toggleRefreshDaily(refreshToggle.checked);
};
refreshToggle.onkeyup = function(event) {
// Handle enter since, unlike space, it does not trigger a click event.
if (event.keyCode === customize.KEYCODES.ENTER) {
refreshToggle.click();
}
};
refreshToggle.onclick = function(event) {
// Enter and space fire the 'onclick' event (which will remove special
// keyboard navigation styling) unless propagation is stopped.
event.stopPropagation();
};
const richerPickerOpenShortcuts = function() {
customize.richerPicker_showSubmenu(
$(customize.IDS.SHORTCUTS_BUTTON), $(customize.IDS.SHORTCUTS_MENU));
};
$(customize.IDS.SHORTCUTS_BUTTON).onclick = richerPickerOpenShortcuts;
$(customize.IDS.SHORTCUTS_BUTTON).onkeydown = function(event) {
if (event.keyCode === customize.KEYCODES.ENTER ||
event.keyCode === customize.KEYCODES.SPACE) {
richerPickerOpenShortcuts();
}
};
$(customize.IDS.COLORS_BUTTON).onclick = function() {
customize.richerPicker_showSubmenu(
$(customize.IDS.COLORS_BUTTON), $(customize.IDS.COLORS_MENU));
ntpApiHandle.getColorsInfo();
};
};
customize.handleError = function(errors) {
const unavailableString = configData.translatedStrings.backgroundsUnavailable;
if (errors != 'undefined') {
// Network errors.
if (errors.net_error) {
if (errors.net_error_no != 0) {
const onClick = () => {
window.open(
'https://chrome://network-error/' + errors.net_error_no,
'_blank');
};
customize.showErrorNotification(
configData.translatedStrings.connectionError,
configData.translatedStrings.moreInfo, onClick);
} else {
customize.showErrorNotification(
configData.translatedStrings.connectionErrorNoPeriod);
}
} else if (errors.service_error) { // Service errors.
customize.showErrorNotification(unavailableString);
}
return;
}
// Generic error when we can't tell what went wrong.
customize.showErrorNotification(unavailableString);
};
/**
* Handles color selection. Apply styling to the selected color in the richer
* picker and enable the done button.
* @param {?Element} tile The selected color tile.
*/
customize.updateColorsMenuTileSelection = function(tile) {
if (!tile) {
return;
}
// Clear the previous selection, if any.
if (customize.selectedOptions.color) {
customize.richerPicker_removeSelectedState(customize.selectedOptions.color);
}
customize.selectedOptions.color = tile;
customize.richerPicker_applySelectedState(tile);
};
/**
* Called when a color tile is clicked. Applies the color, and the selected
* style on the tile.
* @param {Event} event The event attributes for the interaction.
*/
customize.colorTileInteraction = function(event) {
customize.updateColorsMenuTileSelection(
/** @type HTMLElement */ (event.currentTarget));
const id = parseInt(event.currentTarget.dataset.id, 10);
if (id) {
ntpApiHandle.applyAutogeneratedTheme(
id, event.currentTarget.dataset.color.split(','));
}
};
/**
* Called when the default theme tile is clicked. Applies the default theme, and
* the selected style on the tile.
* @param {Event} event The event attributes for the interaction.
*/
customize.defaultThemeTileInteraction = function(event) {
customize.updateColorsMenuTileSelection(
/** @type HTMLElement */ (event.currentTarget));
ntpApiHandle.applyDefaultTheme();
};
/**
* Called when a new color is picked with color picker. Applies the color, and
* the selected style on the tile.
* @param {Event} event The event attributes for the interaction.
*/
customize.colorPickerTileInteraction = function(event) {
const hex = event.currentTarget.value;
const r = parseInt(hex.substring(1, 3), 16);
const g = parseInt(hex.substring(3, 5), 16);
const b = parseInt(hex.substring(5, 7), 16);
// If the picker is preselected and the user picks a new color, we need to
// treat the picker as a new selection and not a preselection.
if (customize.preselectedOptions.colorsMenuTile ===
$(customize.IDS.COLOR_PICKER_TILE)) {
customize.preselectedOptions.colorsMenuTile = null;
}
customize.updateColorsMenuTileSelection($(customize.IDS.COLOR_PICKER_TILE));
ntpApiHandle.applyAutogeneratedTheme(0, [r, g, b, 255]);
};
/**
* Loads Colors menu elements and initializes the selected state.
*/
customize.loadColorsMenu = function() {
if (!customize.colorsMenuLoaded) {
customize.loadColorMenuTiles();
customize.colorsMenuLoaded = true;
}
customize.resetColorsSelectedOptions();
customize.colorsMenuOnThemeChange();
};
/**
* Loads Colors menu tiles, e.g. color picker, default tile and color tiles.
*/
customize.loadColorMenuTiles = function() {
const colorsColl = ntpApiHandle.getColorsInfo();
for (let i = 0; i < colorsColl.length; ++i) {
// After 4 color tiles create an empty tile to take the place of the color
// picker. This is done so that the rest of the colors don't move if color
// picker is not present.
if (!configData.chromeColorsCustomColorPicker && i == 4) {
$(customize.IDS.COLORS_MENU).appendChild(document.createElement('div'));
}
const id = 'color_' + i;
const imageUrl = colorsColl[i].icon;
const dataset = {'color': colorsColl[i].color, 'id': colorsColl[i].id};
const tile = customize.createTileWithoutTitle(
id, imageUrl, dataset, customize.colorTileInteraction,
customize.tileOnKeyDownInteraction);
tile.firstElementChild.setAttribute('aria-label', colorsColl[i].label);
tile.firstElementChild.setAttribute('title', colorsColl[i].label);
$(customize.IDS.COLORS_MENU).appendChild(tile);
}
// Configure the default tile.
$(customize.IDS.COLORS_DEFAULT_ICON).onclick =
customize.defaultThemeTileInteraction;
$(customize.IDS.COLORS_DEFAULT_ICON).onkeydown =
customize.tileOnKeyDownInteraction;
// On arrow keys focus the first element.
$(customize.IDS.COLORS_MENU).onkeydown = function(event) {
if (document.activeElement === $(customize.IDS.COLORS_MENU) &&
customize.arrowKeys.includes(event.keyCode)) {
if (configData.chromeColorsCustomColorPicker) {
$(customize.IDS.COLOR_PICKER_TILE).focus();
} else {
$(customize.IDS.COLORS_DEFAULT_ICON).focus();
}
event.preventDefault();
}
};
// Configure custom color picker.
if (configData.chromeColorsCustomColorPicker) {
$(customize.IDS.COLOR_PICKER_TILE).onclick = function(event) {
$(customize.IDS.COLOR_PICKER).value = customize.customColorPicked;
$(customize.IDS.COLOR_PICKER).click();
};
$(customize.IDS.COLOR_PICKER_TILE).onkeydown =
customize.tileOnKeyDownInteraction;
$(customize.IDS.COLOR_PICKER).onchange =
customize.colorPickerTileInteraction;
}
};
/**
* Update webstore theme info and preselect Colors menu tile according to
* the theme update.
*/
customize.colorsMenuOnThemeChange = function() {
// Update webstore theme information.
const themeInfo = assert(ntpApiHandle.themeBackgroundInfo);
if (themeInfo.themeId && themeInfo.themeName) {
$(customize.IDS.COLORS_THEME).classList.add(customize.CLASSES.VISIBLE);
$(customize.IDS.COLORS_THEME_NAME).innerHTML = themeInfo.themeName;
$(customize.IDS.COLORS_THEME_WEBSTORE_LINK).href =
'https://chrome.google.com/webstore/detail/' + themeInfo.themeId;
$(customize.IDS.COLORS_THEME_UNINSTALL).onclick =
ntpApiHandle.useDefaultTheme;
// Clear the previous selection, if any.
if (customize.selectedOptions.color) {
customize.richerPicker_removeSelectedState(
customize.selectedOptions.color);
customize.selectedOptions.color = null;
}
if (!customize.preselectedOptions.colorsMenuTile) {
customize.preselectedOptions.colorsMenuTile =
$(customize.IDS.COLORS_THEME);
}
} else {
$(customize.IDS.COLORS_THEME).classList.remove(customize.CLASSES.VISIBLE);
// Select the tile corresponding to the current theme/color.
customize.colorsMenuPreselectTile();
}
};
/**
* Preselect Colors menu tile according to the theme info.
*/
customize.colorsMenuPreselectTile = function() {
const themeInfo = assert(ntpApiHandle.themeBackgroundInfo);
let tile;
if (themeInfo.usingDefaultTheme) {
tile = $(customize.IDS.COLORS_DEFAULT_ICON);
} else if (themeInfo.colorId && themeInfo.colorId > 0) {
// Color from predefined set is selected.
const tiles = Array.from(
$(customize.IDS.COLORS_MENU)
.getElementsByClassName(customize.CLASSES.COLLECTION_TILE));
for (let i = 0; i < tiles.length; i++) {
if (tiles[i].dataset && tiles[i].dataset.id == themeInfo.colorId) {
tile = tiles[i];
break;
}
}
} else if (
configData.chromeColorsCustomColorPicker && themeInfo.colorDark &&
themeInfo.colorLight && themeInfo.colorPicked) {
// Custom color is selected.
tile = $(customize.IDS.COLOR_PICKER_TILE);
// Update color picker tile colors.
customize.customColorPicked = colorArrayToHex(themeInfo.colorPicked);
$(customize.IDS.COLORS_MENU)
.style.setProperty(
'--custom-color-border', colorArrayToHex(themeInfo.colorDark));
$(customize.IDS.COLORS_MENU)
.style.setProperty(
'--custom-color-dark', colorArrayToHex(themeInfo.colorDark));
$(customize.IDS.COLORS_MENU)
.style.setProperty(
'--custom-color-light', colorArrayToHex(themeInfo.colorLight));
$(customize.IDS.COLOR_PICKER_ICON)
.classList.toggle('white', themeInfo.isNtpBackgroundDark);
}
if (tile && tile !== customize.selectedOptions.color) {
if (!customize.preselectedOptions.colorsMenuTile) {
customize.preselectedOptions.colorsMenuTile = tile;
}
customize.updateColorsMenuTileSelection(
/** @type HTMLElement */ (tile));
}
};
/**
* Indicates whether a color other then preselected one was selected on Colors
* menu.
*/
customize.isColorOptionSelected = function() {
return customize.preselectedOptions.colorsMenuTile !==
customize.selectedOptions.color;
};
/**
* Indicates whether a background tile other then the preselected one was
* selected on the backgrounds menu.
*/
customize.isBackgroundOptionSelected = function() {
return customize.selectedOptions.backgroundData &&
(!customize.preselectedOptions.backgroundsMenuTile ||
customize.selectedOptions.backgroundData.id !=
customize.preselectedOptions.backgroundsMenuTile.id);
};
/**
* Permanently applies the color changes. Called when the done button is
* pressed.
*/
customize.confirmColor = function() {
ntpApiHandle.confirmThemeChanges();
};
/**
* Reverts the applied (but not confirmed) color changes. Called when the cancel
* button is pressed.
*/
customize.cancelColor = function() {
ntpApiHandle.revertThemeChanges();
};
/**
* Reset color picker to its default state.
*/
customize.resetColorPicker = function() {
customize.customColorPicked = customize.defaultCustomColor;
$(customize.IDS.COLOR_PICKER).value = null;
$(customize.IDS.COLORS_MENU).style.setProperty('--custom-color-border', '');
$(customize.IDS.COLORS_MENU).style.setProperty('--custom-color-dark', '');
$(customize.IDS.COLORS_MENU).style.setProperty('--custom-color-light', '');
$(customize.IDS.COLOR_PICKER_ICON).classList.toggle('white', false);
};
/**
* Reset color selection.
*/
customize.resetColorsSelectedOptions = function() {
customize.richerPicker_removeSelectedState(customize.selectedOptions.color);
customize.selectedOptions.color = null;
customize.preselectedOptions.colorsMenuTile = null;
};
/**
* Converts an RGBA component into hex format.
* @param {number} c RGBA component.
* @return {string} RGBA component in hex format.
*/
function rgbComponentToHex(c) {
const hex = c.toString(16);
return hex.length == 1 ? '0' + hex : hex;
}
/**
* Converts an Array of color components into hex format "#000000".
* @param {Array<number>} color Array of RGBA color components.
* @return {string} color in hex format.
*/
function colorArrayToHex(color) {
if (!color || color.length != 4) {
return '';
}
return '#' + rgbComponentToHex(color[0]) + rgbComponentToHex(color[1]) +
rgbComponentToHex(color[2]);
}