Commit 34799188 authored by Tibor Goldschwendt's avatar Tibor Goldschwendt Committed by Commit Bot

[webui-ntp] Add Chrome themes and make customize dialog interactive

This CL loads the Chrome themes into the customize dialog and enables
applying a theme when clicking on the respective icon.

Bug: 1032327
Change-Id: I971ad2fb3f7b2088a789f6570c5217680e981a74
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1966500
Auto-Submit: Tibor Goldschwendt <tiborg@chromium.org>
Commit-Queue: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#726469}
parent e4ba7f96
......@@ -186,6 +186,7 @@ if (!is_android) {
deps = [
"//chrome/browser/resources/new_tab_page:polymer3_elements",
"//chrome/browser/ui/webui/new_tab_page:mojo_bindings_js",
"//skia/public/mojom:mojom_js",
]
# The .grd contains references to generated files.
......
......@@ -11,6 +11,7 @@ js_type_check("closure_compile") {
":app",
":browser_proxy",
":customize_dialog",
":theme_icon",
]
}
......@@ -53,6 +54,12 @@ js_library("customize_dialog") {
]
}
js_library("theme_icon") {
deps = [
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
]
}
polymer_modulizer("app") {
js_file = "app.js"
html_file = "app.html"
......@@ -71,10 +78,17 @@ polymer_modulizer("customize_dialog") {
html_type = "v3-ready"
}
polymer_modulizer("theme_icon") {
js_file = "theme_icon.js"
html_file = "theme_icon.html"
html_type = "v3-ready"
}
group("polymer3_elements") {
deps = [
":app_module",
":customize_dialog_module",
":most_visited_module",
":theme_icon_module",
]
}
......@@ -5,6 +5,7 @@
import 'chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js';
import 'chrome://resources/mojo/url/mojom/url.mojom-lite.js';
import './skcolor.mojom-lite.js';
import './new_tab_page.mojom-lite.js';
import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
......
<style>
::part(dialog) {
height: 528px;
min-width: 800px;
}
::part(wrapper) {
height: 100%;
}
div[slot=body] {
overflow-y: scroll;
min-height: 423px;
text-align: center;
}
#color-container {
#themesContainer {
display: inline-grid;
grid-column-gap: 25px;
grid-row-gap: 25px;
grid-template-columns: repeat(6, auto);
}
.tile {
ntp-theme-icon {
align-self: center;
height: 64px;
justify-self: center;
width: 64px;
}
#autogeneratedTheme {
--ntp-theme-icon-frame-color: var(--google-grey-refresh-100);
--ntp-theme-icon-active-tab-color: white;
--ntp-theme-icon-stroke-color: var(--google-grey-refresh-300);
}
#defaultTheme {
--ntp-theme-icon-frame-color: rgb(222, 225, 230);
--ntp-theme-icon-active-tab-color: white;
}
</style>
<cr-dialog id="dialog">
<div slot="body">
<div id="color-container">
<dom-repeat items="[[colors_]]">
<input id="colorPicker" type="color" on-change="onCustomFrameColorChange_"
hidden>
</input>
<iron-selector id="themesContainer" selected-attribute="selected">
<!-- TODO(crbug.com/1032327): Add color picker icon. -->
<ntp-theme-icon id="autogeneratedTheme" title="$i18n{colorPickerLabel}"
on-click="onAutogeneratedThemeClick_">
</ntp-theme-icon>
<ntp-theme-icon id="defaultTheme" title="$i18n{defaultColorLabel}"
on-click="onDefaultThemeClick_">
</ntp-theme-icon>
<dom-repeat id="themes" items="[[themes_]]">
<template>
<div
class="tile"
style="background-image: url([[item.icon]])"
title="[[item.label]]">
</div>
<ntp-theme-icon title="[[item.label]]" on-click="onChromeThemeClick_"
style="--ntp-theme-icon-frame-color: [[item.frameColor]];
--ntp-theme-icon-active-tab-color: [[item.activeTabColor]];">
</ntp-theme-icon>
</template>
</dom-repeat>
</div>
</iron-selector>
</div>
<div slot="button-container">
<cr-button class="cancel-button" on-click="onCancelClick_">
......
......@@ -4,15 +4,56 @@
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/polymer/v3_0/iron-selector/iron-selector.js';
import './theme_icon.js';
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
/** @typedef {{label:string, icon:string}} */
let Color;
import {BrowserProxy} from './browser_proxy.js';
/**
* @typedef {{
* id:number,
* label:string,
* frameColor:string,
* activeTabColor:string,
* }}
*/
let ChromeTheme;
/**
* @param {skia.mojom.SkColor} skColor
* @return {string}
*/
function skColorToRgb(skColor) {
const r = (skColor.value >> 16) & 0xff;
const g = (skColor.value >> 8) & 0xff;
const b = skColor.value & 0xff;
return `rgb(${r}, ${g}, ${b})`;
}
/**
* @param {string} hexColor
* @return {skia.mojom.SkColor}
*/
function hexColorToSkColor(hexColor) {
if (!/^#[0-9a-f]{6}$/.test(hexColor)) {
return {value: 0};
}
const r = parseInt(hexColor.substring(1, 3), 16);
const g = parseInt(hexColor.substring(3, 5), 16);
const b = parseInt(hexColor.substring(5, 7), 16);
return {value: 0xff000000 + (r << 16) + (g << 8) + b};
}
/**
* Dialog that lets the user customize the NTP such as the background color or
* image.
* TODO(crbug.com/1032327): Display currently selected color on open.
* TODO(crbug.com/1032327): Persist theme selection.
* TODO(crbug.com/1032327): Add keyboard support.
* TODO(crbug.com/1032328): Add support for selecting background image.
* TODO(crbug.com/1032333): Add support for selecting shortcuts vs most visited.
*/
class CustomizeDialogElement extends PolymerElement {
static get is() {
......@@ -25,35 +66,30 @@ class CustomizeDialogElement extends PolymerElement {
static get properties() {
return {
/** @private {!Array<!Color>} */
colors_: Array,
/** @private {!Array<!ChromeTheme>} */
themes_: Array,
};
}
constructor() {
super();
// Create a few rows of sample data.
// TODO(crbug.com/1030459): Add real data source.
this.colors_ = [];
for (let i = 0; i < 20; i++) {
this.colors_.push({
label: 'Warm grey',
icon:
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iNjQiIGhlaWdodD0iNjQiPjxkZWZzPjxwYXRoIGQ9Ik0zMiA2NEMxNC4zNCA2NCAwIDQ5LjY2IDAgMzJTMTQuMzQgMCAzMiAwczMyIDE0LjM0IDMyIDMyLTE0LjM0IDMyLTMyIDMyeiIgaWQ9ImEiLz48bGluZWFyR3JhZGllbnQgaWQ9ImIiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMzIiIHkxPSIzMiIgeDI9IjMyLjA4IiB5Mj0iMzIiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiNGRkZGRkYiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNFM0RCRDciLz48L2xpbmVhckdyYWRpZW50PjxjbGlwUGF0aCBpZD0iYyI+PHVzZSB4bGluazpocmVmPSIjYSIvPjwvY2xpcFBhdGg+PC9kZWZzPjx1c2UgeGxpbms6aHJlZj0iI2EiIGZpbGw9InVybCgjYikiLz48ZyBjbGlwLXBhdGg9InVybCgjYykiPjx1c2UgeGxpbms6aHJlZj0iI2EiIGZpbGwtb3BhY2l0eT0iMCIgc3Ryb2tlPSIjRTNEQkQ3IiBzdHJva2Utd2lkdGg9IjIiLz48L2c+PC9zdmc+',
});
this.colors_.push(
{
label: 'Cool grey',
icon:
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iNjQiIGhlaWdodD0iNjQiPjxkZWZzPjxwYXRoIGQ9Ik0zMiA2NEMxNC4zNCA2NCAwIDQ5LjY2IDAgMzJTMTQuMzQgMCAzMiAwczMyIDE0LjM0IDMyIDMyLTE0LjM0IDMyLTMyIDMyeiIgaWQ9ImEiLz48bGluZWFyR3JhZGllbnQgaWQ9ImIiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMzIiIHkxPSIzMiIgeDI9IjMyLjA4IiB5Mj0iMzIiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiNEOURBREYiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNBN0FCQjciLz48L2xpbmVhckdyYWRpZW50PjxjbGlwUGF0aCBpZD0iYyI+PHVzZSB4bGluazpocmVmPSIjYSIvPjwvY2xpcFBhdGg+PC9kZWZzPjx1c2UgeGxpbms6aHJlZj0iI2EiIGZpbGw9InVybCgjYikiLz48ZyBjbGlwLXBhdGg9InVybCgjYykiPjx1c2UgeGxpbms6aHJlZj0iI2EiIGZpbGwtb3BhY2l0eT0iMCIgc3Ryb2tlPSIjQTdBQkI3IiBzdHJva2Utd2lkdGg9IjIiLz48L2c+PC9zdmc+',
},
);
}
/** @private {newTabPage.mojom.PageHandlerRemote} */
this.pageHandler_ = BrowserProxy.getInstance().handler;
}
/** @override */
connectedCallback() {
super.connectedCallback();
this.$.dialog.showModal();
this.pageHandler_.getChromeThemes().then(({themes}) => {
this.themes_ =
themes.map(theme => ({
id: theme.id,
label: theme.label,
frameColor: skColorToRgb(theme.frameColor),
activeTabColor: skColorToRgb(theme.activeTabColor),
}));
this.$.dialog.showModal();
});
}
/** @private */
......@@ -65,6 +101,34 @@ class CustomizeDialogElement extends PolymerElement {
onDoneClick_() {
this.$.dialog.close();
}
/**
* @param {!Event} e
* @private
*/
onCustomFrameColorChange_(e) {
this.pageHandler_.applyAutogeneratedTheme(
hexColorToSkColor(e.target.value));
}
/** @private */
onAutogeneratedThemeClick_() {
this.$.colorPicker.click();
}
/** @private */
onDefaultThemeClick_() {
this.pageHandler_.applyDefaultTheme();
}
/**
* @param {!Event} e
* @private
*/
onChromeThemeClick_(e) {
this.pageHandler_.applyChromeTheme(
this.$.themes.itemForElement(e.target).id);
}
}
customElements.define(CustomizeDialogElement.is, CustomizeDialogElement);
......@@ -12,6 +12,9 @@
</outputs>
<release seq="1">
<includes>
<include name="IDR_NEW_TAB_PAGE_SKCOLOR_MOJO_LITE_JS"
file="${root_gen_dir}/skia/public/mojom/skcolor.mojom-lite.js"
use_base_dir="false" type="BINDATA" compress="gzip" />
<include name="IDR_NEW_TAB_PAGE_MOJO_LITE_JS"
file="${root_gen_dir}/chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom-lite.js"
use_base_dir="false" type="BINDATA" compress="gzip" />
......@@ -24,6 +27,9 @@
<include name="IDR_NEW_TAB_PAGE_CUSTOMIZE_DIALOG_JS"
file="${root_gen_dir}/chrome/browser/resources/new_tab_page/customize_dialog.js"
use_base_dir="false" type="BINDATA" compress="gzip" />
<include name="IDR_NEW_TAB_PAGE_THEME_ICON_JS"
file="${root_gen_dir}/chrome/browser/resources/new_tab_page/theme_icon.js"
use_base_dir="false" type="BINDATA" compress="gzip" />
</includes>
<structures>
<structure name="IDR_NEW_TAB_PAGE_NEW_TAB_PAGE_HTML"
......
<style>
:host,
svg {
height: 72px;
width: 72px;
}
#ring {
fill: rgba(var(--google-blue-600-rgb), 0.4);
visibility: hidden;
}
:host([selected]) #ring {
visibility: visible;
}
#circle {
fill: url(#gradient);
stroke: var(--ntp-theme-icon-stroke-color,
var(--ntp-theme-icon-frame-color));
stroke-width: 1;
}
#leftColor {
stop-color: var(--ntp-theme-icon-active-tab-color);
}
#rightColor {
stop-color: var(--ntp-theme-icon-frame-color);
}
</style>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="32" y1="32"
x2="32.08" y2="32">
<stop id="leftColor" offset="0%"></stop>
<stop id="rightColor" offset="100%"></stop>
</linearGradient>
</defs>
<!-- TODO(crbug.com/1032327): Add check mark when selected. -->
<circle id="ring" cx="36" cy="36" r="36"></circle>
<circle id="circle" cx="36" cy="36" r="32"></circle>
</svg>
// 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 {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
/**
* Represents a theme. Displayed as a circle with each half colored based on
* the custom CSS properties |--ntp-theme-icon-frame-color| and
* |--ntp-theme-icon-active-tab-color|. Can be selected by setting the
* |selected| attribute.
*/
class ThemeIconElement extends PolymerElement {
static get is() {
return 'ntp-theme-icon';
}
static get template() {
return html`{__html_template__}`;
}
}
customElements.define(ThemeIconElement.is, ThemeIconElement);
......@@ -1398,6 +1398,8 @@ jumbo_static_library("ui") {
"//chrome/browser/ui/webui/app_management:mojo_bindings",
"//chrome/common:buildflags",
"//chrome/common:search_mojom",
"//chrome/common/search:generate_chrome_colors_info",
"//chrome/common/themes:autogenerated_theme_util",
"//chrome/services/app_service/public/cpp:app_update",
"//chrome/services/app_service/public/mojom",
"//components/feedback/proto",
......
......@@ -10,6 +10,7 @@ mojom("mojo_bindings") {
]
public_deps = [
"//skia/public/mojom",
"//url/mojom:url_mojom_gurl",
]
}
......@@ -4,6 +4,7 @@
module new_tab_page.mojom;
import "skia/public/mojom/skcolor.mojom";
import "url/mojom/url.mojom";
struct MostVisitedTile {
......@@ -17,6 +18,19 @@ struct MostVisitedInfo {
array<MostVisitedTile> tiles;
};
// A predefined theme provided by Chrome. Created from data embedded in the
// Chrome binary.
struct ChromeTheme {
// Theme identifier.
uint32 id;
// Localized string of the theme name.
string label;
// The theme's frame color.
skia.mojom.SkColor frame_color;
// The theme's active tab color.
skia.mojom.SkColor active_tab_color;
};
// Used by the WebUI page to bootstrap bidirectional communication.
interface PageHandlerFactory {
// The WebUI page's |BrowserProxy| singleton calls this method when the page
......@@ -42,6 +56,14 @@ interface PageHandler {
UpdateMostVisitedTile(url.mojom.Url url, url.mojom.Url new_url,
string new_title)
=> (bool success);
// Returns the predefined Chrome themes.
GetChromeThemes() => (array<ChromeTheme> themes);
// Applies the default theme.
ApplyDefaultTheme();
// Applies autogenerated theme for the given color.
ApplyAutogeneratedTheme(skia.mojom.SkColor frame_color);
// Applies the predefined Chrome theme with the given ID.
ApplyChromeTheme(uint32 id);
};
// WebUI-side handler for requests from the browser.
......
......@@ -8,16 +8,23 @@
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ntp_tiles/chrome_most_visited_sites_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/chrome_colors/chrome_colors_factory.h"
#include "chrome/browser/search/chrome_colors/chrome_colors_service.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/search/generated_colors_info.h"
#include "chrome/common/themes/autogenerated_theme_util.h"
#include "components/prefs/pref_service.h"
#include "ui/base/l10n/l10n_util.h"
NewTabPageHandler::NewTabPageHandler(
mojo::PendingReceiver<new_tab_page::mojom::PageHandler>
pending_page_handler,
mojo::PendingRemote<new_tab_page::mojom::Page> pending_page,
Profile* profile)
: page_{std::move(pending_page)},
: chrome_colors_service_(
chrome_colors::ChromeColorsFactory::GetForProfile(profile)),
page_{std::move(pending_page)},
pref_service_(profile->GetPrefs()),
receiver_{this, std::move(pending_page_handler)} {
most_visited_sites_ = ChromeMostVisitedSitesFactory::NewForProfile(profile);
......@@ -106,6 +113,37 @@ void NewTabPageHandler::UndoMostVisitedTileAction() {
}
}
void NewTabPageHandler::GetChromeThemes(GetChromeThemesCallback callback) {
std::vector<new_tab_page::mojom::ChromeThemePtr> themes;
for (size_t i = 0; i < chrome_colors::kNumColorsInfo; i++) {
const auto& color_info = chrome_colors::kGeneratedColorsInfo[i];
auto theme_colors = GetAutogeneratedThemeColors(color_info.color);
auto theme = new_tab_page::mojom::ChromeTheme::New();
theme->id = i;
theme->label = l10n_util::GetStringUTF8(color_info.label_id);
theme->frame_color = theme_colors.frame_color;
theme->active_tab_color = theme_colors.active_tab_color;
themes.push_back(std::move(theme));
}
std::move(callback).Run(std::move(themes));
}
void NewTabPageHandler::ApplyDefaultTheme() {
chrome_colors_service_->ApplyDefaultTheme(web_contents());
}
void NewTabPageHandler::ApplyAutogeneratedTheme(const SkColor& frame_color) {
chrome_colors_service_->ApplyAutogeneratedTheme(frame_color, web_contents());
}
void NewTabPageHandler::ApplyChromeTheme(uint32_t id) {
if (id >= chrome_colors::kNumColorsInfo)
return;
auto color_info = chrome_colors::kGeneratedColorsInfo[id];
chrome_colors_service_->ApplyAutogeneratedTheme(color_info.color,
web_contents());
}
void NewTabPageHandler::OnCustomLinksEnableChange() {
most_visited_sites_->EnableCustomLinks(IsCustomLinksEnabled());
page_->SetCustomLinksEnabled(IsCustomLinksEnabled());
......
......@@ -23,6 +23,10 @@ class GURL;
class PrefService;
class Profile;
namespace chrome_colors {
class ChromeColorsService;
} // namespace chrome_colors
class NewTabPageHandler : public content::WebContentsObserver,
public ntp_tiles::MostVisitedSites::Observer,
public new_tab_page::mojom::PageHandler {
......@@ -46,6 +50,10 @@ class NewTabPageHandler : public content::WebContentsObserver,
const std::string& new_title,
UpdateMostVisitedTileCallback callback) override;
void UndoMostVisitedTileAction() override;
void GetChromeThemes(GetChromeThemesCallback callback) override;
void ApplyDefaultTheme() override;
void ApplyAutogeneratedTheme(const SkColor& frame_color) override;
void ApplyChromeTheme(uint32_t id) override;
private:
bool IsCustomLinksEnabled();
......@@ -62,6 +70,7 @@ class NewTabPageHandler : public content::WebContentsObserver,
// Data source for NTP tiles (aka Most Visited tiles). May be null.
std::unique_ptr<ntp_tiles::MostVisitedSites> most_visited_sites_;
chrome_colors::ChromeColorsService* chrome_colors_service_;
mojo::Remote<new_tab_page::mojom::Page> page_;
......
......@@ -59,12 +59,16 @@ content::WebUIDataSource* CreateNewTabPageUiHtmlSource() {
{"urlField", IDS_NTP_CUSTOM_LINKS_URL},
// Customize button and dialog.
{"customizeButton", IDS_NTP_CUSTOMIZE_BUTTON_LABEL},
{"cancelButton", IDS_CANCEL},
{"colorPickerLabel", IDS_NTP_CUSTOMIZE_COLOR_PICKER_LABEL},
{"customizeButton", IDS_NTP_CUSTOMIZE_BUTTON_LABEL},
{"defaultColorLabel", IDS_NTP_CUSTOMIZE_DEFAULT_LABEL},
{"doneButton", IDS_DONE},
};
AddLocalizedStringsBulk(source, kStrings);
source->AddResourcePath("skcolor.mojom-lite.js",
IDR_NEW_TAB_PAGE_SKCOLOR_MOJO_LITE_JS);
source->AddResourcePath("new_tab_page.mojom-lite.js",
IDR_NEW_TAB_PAGE_MOJO_LITE_JS);
webui::SetupWebUIDataSource(
......
......@@ -2,46 +2,94 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Need to import that otherwise we get an "Uncaught ReferenceError: mojo is not
// defined" error.
// TODO(tiborg): Refactor so that this import is not needed anymore.
import 'chrome://new-tab-page/browser_proxy.js';
import 'chrome://new-tab-page/customize_dialog.js';
import {BrowserProxy} from 'chrome://new-tab-page/browser_proxy.js';
import {assertStyle, TestProxy} from 'chrome://test/new_tab_page/test_support.js';
import {flushTasks} from 'chrome://test/test_util.m.js';
suite('NewTabPageCustomizeDialogTest', () => {
/** @type {!CustomizeDialogElement} */
let customizeDialog;
/** @type {TestProxy} */
let testProxy;
/** @return {!CustomizeDialogElement} */
function createCustomizeDialog() {
const customizeDialog = document.createElement('ntp-customize-dialog');
document.body.appendChild(customizeDialog);
return customizeDialog;
}
setup(() => {
PolymerTest.clearBody();
customizeDialog = document.createElement('ntp-customize-dialog');
document.body.appendChild(customizeDialog);
testProxy = new TestProxy();
BrowserProxy.instance_ = testProxy;
});
test('setting colors shows color tiles', async () => {
// Act.
customizeDialog.colors_ = [
test('setting themes shows theme tiles', async () => {
// Arrange.
const themes = [
{
label: 'color_0',
icon: 'data_0',
id: 0,
label: 'theme_0',
frameColor: {value: 0x000000}, // white.
activeTabColor: {value: 0x0000ff}, // blue.
},
{
label: 'color_1',
icon: 'data_1',
id: 1,
label: 'theme_1',
frameColor: {value: 0xff0000}, // red.
activeTabColor: {value: 0x00ff00}, // green.
},
];
testProxy.handler.setResultFor(
'getChromeThemes', Promise.resolve({'themes': themes}));
const getChromeThemesCalled =
testProxy.handler.whenCalled('getChromeThemes');
// Act.
const customizeDialog = createCustomizeDialog();
await getChromeThemesCalled;
await flushTasks();
// Assert.
const colorTiles =
Array.from(customizeDialog.shadowRoot.querySelectorAll('.tile'));
assertEquals(colorTiles.length, 2);
assertEquals(colorTiles[0].getAttribute('title'), 'color_0');
assertEquals(colorTiles[0].style['background-image'], 'url("data_0")');
assertEquals(colorTiles[1].getAttribute('title'), 'color_1');
assertEquals(colorTiles[1].style['background-image'], 'url("data_1")');
const tiles = customizeDialog.shadowRoot.querySelectorAll('ntp-theme-icon');
assertEquals(tiles.length, 4);
assertEquals(tiles[2].getAttribute('title'), 'theme_0');
assertStyle(tiles[2], '--ntp-theme-icon-frame-color', 'rgb(0, 0, 0)');
assertStyle(
tiles[2], '--ntp-theme-icon-active-tab-color', 'rgb(0, 0, 255)');
assertEquals(tiles[3].getAttribute('title'), 'theme_1');
assertStyle(tiles[3], '--ntp-theme-icon-frame-color', 'rgb(255, 0, 0)');
assertStyle(
tiles[3], '--ntp-theme-icon-active-tab-color', 'rgb(0, 255, 0)');
});
test('clicking default theme calls applying default theme', async () => {
// Arrange.
const customizeDialog = createCustomizeDialog();
const applyDefaultThemeCalled =
testProxy.handler.whenCalled('applyDefaultTheme');
// Act.
customizeDialog.$.defaultTheme.click();
// Assert.
await applyDefaultThemeCalled;
});
test('selecting color calls applying autogenerated theme', async () => {
// Arrange.
const customizeDialog = createCustomizeDialog();
const applyAutogeneratedThemeCalled =
testProxy.handler.whenCalled('applyAutogeneratedTheme');
// Act.
customizeDialog.$.colorPicker.value = '#ff0000';
customizeDialog.$.colorPicker.dispatchEvent(new Event('change'));
// Assert.
const {value} = await applyAutogeneratedThemeCalled;
assertEquals(value, 0xffff0000);
});
});
......@@ -62,3 +62,15 @@ var NewTabPageCustomizeDialogTest = class extends NewTabPageBrowserTest {
TEST_F('NewTabPageCustomizeDialogTest', 'All', function() {
mocha.run();
});
// eslint-disable-next-line no-var
var NewTabPageThemeIconTest = class extends NewTabPageBrowserTest {
/** @override */
get browsePreload() {
return 'chrome://new-tab-page/test_loader.html?module=new_tab_page/theme_icon_test.js';
}
};
TEST_F('NewTabPageThemeIconTest', 'All', function() {
mocha.run();
});
......@@ -2,6 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js';
import 'chrome://resources/mojo/url/mojom/url.mojom-lite.js';
import 'chrome://new-tab-page/skcolor.mojom-lite.js';
import 'chrome://new-tab-page/new_tab_page.mojom-lite.js';
import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
export class TestProxy {
......@@ -22,9 +27,6 @@ export class TestProxy {
class FakePageHandler {
/** @param {newTabPage.mojom.PageInterface} */
constructor(callbackRouterRemote) {
/** @private {newTabPage.mojom.PageInterface} */
this.callbackRouterRemote_ = callbackRouterRemote;
/** @private {TestBrowserProxy} */
this.callTracker_ = new TestBrowserProxy([
'addMostVisitedTile',
......@@ -33,6 +35,10 @@ class FakePageHandler {
'restoreMostVisitedDefaults',
'undoMostVisitedTileAction',
'updateMostVisitedTile',
'getChromeThemes',
'applyDefaultTheme',
'applyAutogeneratedTheme',
'applyChromeTheme',
]);
}
......@@ -44,6 +50,14 @@ class FakePageHandler {
return this.callTracker_.whenCalled(methodName);
}
/**
* @param {string} methodName
* @param {*} value
*/
setResultFor(methodName, value) {
this.callTracker_.setResultFor(methodName, value);
}
/** @override */
addMostVisitedTile(url, title) {
this.callTracker_.methodCalled('addMostVisitedTile', [url, title]);
......@@ -77,6 +91,28 @@ class FakePageHandler {
'updateMostVisitedTile', [url, newUrl, newTitle]);
return {success: true};
}
/** @override */
getChromeThemes() {
this.callTracker_.methodCalled('getChromeThemes');
return this.callTracker_.getResultFor(
'getChromeThemes', Promise.resolve({themes: []}));
}
/** @override */
applyDefaultTheme() {
this.callTracker_.methodCalled('applyDefaultTheme');
}
/** @override */
applyAutogeneratedTheme(frameColor) {
this.callTracker_.methodCalled('applyAutogeneratedTheme', frameColor);
}
/** @override */
applyChromeTheme(id) {
this.callTracker_.methodCalled('applyChromeTheme', id);
}
}
/**
......@@ -86,3 +122,14 @@ class FakePageHandler {
export function keydown(element, key) {
element.dispatchEvent(new KeyboardEvent('keydown', {key: key}));
}
/**
* Asserts the computed style value for an element.
* @param {!HTMLElement} element The element.
* @param {string} name The name of the style to assert.
* @param {string} expected The expected style value.
*/
export function assertStyle(element, name, expected) {
const actual = window.getComputedStyle(element).getPropertyValue(name).trim();
assertEquals(expected, actual);
}
// 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://new-tab-page/theme_icon.js';
import {assertStyle} from 'chrome://test/new_tab_page/test_support.js';
suite('NewTabPageThemeIconTest', () => {
/** @type {!ThemeIconElement} */
let themeIcon;
/**
* @return {!NodeList<!HTMLElement>}
* @private
*/
function queryAll(selector) {
return themeIcon.shadowRoot.querySelectorAll(selector);
}
/**
* @return {!HTMLElement}
* @private
*/
function query(selector) {
return themeIcon.shadowRoot.querySelector(selector);
}
setup(() => {
PolymerTest.clearBody();
themeIcon = document.createElement('ntp-theme-icon');
document.body.appendChild(themeIcon);
});
test('setting frame color sets stroke and gradient', () => {
// Act.
themeIcon.style.setProperty('--ntp-theme-icon-frame-color', 'red');
// Assert.
assertStyle(query('#circle'), 'stroke', 'rgb(255, 0, 0)');
assertStyle(
queryAll('#gradient > stop')[1], 'stop-color', 'rgb(255, 0, 0)');
});
test('setting active tab color sets gradient', async () => {
// Act.
themeIcon.style.setProperty('--ntp-theme-icon-active-tab-color', 'red');
// Assert.
assertStyle(
queryAll('#gradient > stop')[0], 'stop-color', 'rgb(255, 0, 0)');
});
test('setting explicit stroke color sets different stroke', async () => {
// Act.
themeIcon.style.setProperty('--ntp-theme-icon-frame-color', 'red');
themeIcon.style.setProperty('--ntp-theme-icon-stroke-color', 'blue');
// Assert.
assertStyle(query('#circle'), 'stroke', 'rgb(0, 0, 255)');
assertStyle(
queryAll('#gradient > stop')[1], 'stop-color', 'rgb(255, 0, 0)');
});
test('selecting icon shows ring', async () => {
// Act.
themeIcon.setAttribute('selected', true);
// Assert.
assertStyle(query('#ring'), 'visibility', 'visible');
});
});
......@@ -7,7 +7,8 @@
/**
* @typedef {{resolver: !PromiseResolver,
* callCount: number}}
* callCount: number,
* result?: *}}
*/
let MethodData;
......@@ -104,6 +105,27 @@ let MethodData;
return this.getMethodData_(methodName).callCount;
}
/**
* Sets the return value of a method.
* @param {string} methodName
* @paran {*} value
*/
setResultFor(methodName, value) {
this.getMethodData_(methodName).result = value;
}
/**
* Returns the return value of a method or the default value if no return
* value is registered.
* @param {string} methodName
* @param {*} defaultValue
* @return {*}
*/
getResultFor(methodName, defaultValue) {
const methodData = this.getMethodData_(methodName);
return 'result' in methodData ? methodData.result : defaultValue;
}
/**
* Try to give programmers help with mistyped methodNames.
* @param {string} methodName
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment