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) { ...@@ -186,6 +186,7 @@ if (!is_android) {
deps = [ deps = [
"//chrome/browser/resources/new_tab_page:polymer3_elements", "//chrome/browser/resources/new_tab_page:polymer3_elements",
"//chrome/browser/ui/webui/new_tab_page:mojo_bindings_js", "//chrome/browser/ui/webui/new_tab_page:mojo_bindings_js",
"//skia/public/mojom:mojom_js",
] ]
# The .grd contains references to generated files. # The .grd contains references to generated files.
......
...@@ -11,6 +11,7 @@ js_type_check("closure_compile") { ...@@ -11,6 +11,7 @@ js_type_check("closure_compile") {
":app", ":app",
":browser_proxy", ":browser_proxy",
":customize_dialog", ":customize_dialog",
":theme_icon",
] ]
} }
...@@ -53,6 +54,12 @@ js_library("customize_dialog") { ...@@ -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") { polymer_modulizer("app") {
js_file = "app.js" js_file = "app.js"
html_file = "app.html" html_file = "app.html"
...@@ -71,10 +78,17 @@ polymer_modulizer("customize_dialog") { ...@@ -71,10 +78,17 @@ polymer_modulizer("customize_dialog") {
html_type = "v3-ready" 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") { group("polymer3_elements") {
deps = [ deps = [
":app_module", ":app_module",
":customize_dialog_module", ":customize_dialog_module",
":most_visited_module", ":most_visited_module",
":theme_icon_module",
] ]
} }
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js'; import 'chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js';
import 'chrome://resources/mojo/url/mojom/url.mojom-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 './new_tab_page.mojom-lite.js';
import {addSingletonGetter} from 'chrome://resources/js/cr.m.js'; import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
......
<style> <style>
::part(dialog) { ::part(dialog) {
height: 528px;
min-width: 800px; min-width: 800px;
} }
::part(wrapper) {
height: 100%;
}
div[slot=body] { div[slot=body] {
overflow-y: scroll; min-height: 423px;
text-align: center; text-align: center;
} }
#color-container { #themesContainer {
display: inline-grid; display: inline-grid;
grid-column-gap: 25px; grid-column-gap: 25px;
grid-row-gap: 25px; grid-row-gap: 25px;
grid-template-columns: repeat(6, auto); grid-template-columns: repeat(6, auto);
} }
.tile { ntp-theme-icon {
align-self: center; align-self: center;
height: 64px;
justify-self: center; 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> </style>
<cr-dialog id="dialog"> <cr-dialog id="dialog">
<div slot="body"> <div slot="body">
<div id="color-container"> <input id="colorPicker" type="color" on-change="onCustomFrameColorChange_"
<dom-repeat items="[[colors_]]"> 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> <template>
<div <ntp-theme-icon title="[[item.label]]" on-click="onChromeThemeClick_"
class="tile" style="--ntp-theme-icon-frame-color: [[item.frameColor]];
style="background-image: url([[item.icon]])" --ntp-theme-icon-active-tab-color: [[item.activeTabColor]];">
title="[[item.label]]"> </ntp-theme-icon>
</div>
</template> </template>
</dom-repeat> </dom-repeat>
</div> </iron-selector>
</div> </div>
<div slot="button-container"> <div slot="button-container">
<cr-button class="cancel-button" on-click="onCancelClick_"> <cr-button class="cancel-button" on-click="onCancelClick_">
......
...@@ -4,15 +4,56 @@ ...@@ -4,15 +4,56 @@
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js'; import 'chrome://resources/cr_elements/cr_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'; import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
/** @typedef {{label:string, icon:string}} */ import {BrowserProxy} from './browser_proxy.js';
let Color;
/**
* @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 * Dialog that lets the user customize the NTP such as the background color or
* image. * 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 { class CustomizeDialogElement extends PolymerElement {
static get is() { static get is() {
...@@ -25,35 +66,30 @@ class CustomizeDialogElement extends PolymerElement { ...@@ -25,35 +66,30 @@ class CustomizeDialogElement extends PolymerElement {
static get properties() { static get properties() {
return { return {
/** @private {!Array<!Color>} */ /** @private {!Array<!ChromeTheme>} */
colors_: Array, themes_: Array,
}; };
} }
constructor() { constructor() {
super(); super();
// Create a few rows of sample data. /** @private {newTabPage.mojom.PageHandlerRemote} */
// TODO(crbug.com/1030459): Add real data source. this.pageHandler_ = BrowserProxy.getInstance().handler;
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+',
},
);
}
} }
/** @override */
connectedCallback() { connectedCallback() {
super.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 */ /** @private */
...@@ -65,6 +101,34 @@ class CustomizeDialogElement extends PolymerElement { ...@@ -65,6 +101,34 @@ class CustomizeDialogElement extends PolymerElement {
onDoneClick_() { onDoneClick_() {
this.$.dialog.close(); 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); customElements.define(CustomizeDialogElement.is, CustomizeDialogElement);
...@@ -12,6 +12,9 @@ ...@@ -12,6 +12,9 @@
</outputs> </outputs>
<release seq="1"> <release seq="1">
<includes> <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" <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" 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" /> use_base_dir="false" type="BINDATA" compress="gzip" />
...@@ -24,6 +27,9 @@ ...@@ -24,6 +27,9 @@
<include name="IDR_NEW_TAB_PAGE_CUSTOMIZE_DIALOG_JS" <include name="IDR_NEW_TAB_PAGE_CUSTOMIZE_DIALOG_JS"
file="${root_gen_dir}/chrome/browser/resources/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" /> 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> </includes>
<structures> <structures>
<structure name="IDR_NEW_TAB_PAGE_NEW_TAB_PAGE_HTML" <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") { ...@@ -1398,6 +1398,8 @@ jumbo_static_library("ui") {
"//chrome/browser/ui/webui/app_management:mojo_bindings", "//chrome/browser/ui/webui/app_management:mojo_bindings",
"//chrome/common:buildflags", "//chrome/common:buildflags",
"//chrome/common:search_mojom", "//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/cpp:app_update",
"//chrome/services/app_service/public/mojom", "//chrome/services/app_service/public/mojom",
"//components/feedback/proto", "//components/feedback/proto",
......
...@@ -10,6 +10,7 @@ mojom("mojo_bindings") { ...@@ -10,6 +10,7 @@ mojom("mojo_bindings") {
] ]
public_deps = [ public_deps = [
"//skia/public/mojom",
"//url/mojom:url_mojom_gurl", "//url/mojom:url_mojom_gurl",
] ]
} }
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
module new_tab_page.mojom; module new_tab_page.mojom;
import "skia/public/mojom/skcolor.mojom";
import "url/mojom/url.mojom"; import "url/mojom/url.mojom";
struct MostVisitedTile { struct MostVisitedTile {
...@@ -17,6 +18,19 @@ struct MostVisitedInfo { ...@@ -17,6 +18,19 @@ struct MostVisitedInfo {
array<MostVisitedTile> tiles; 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. // Used by the WebUI page to bootstrap bidirectional communication.
interface PageHandlerFactory { interface PageHandlerFactory {
// The WebUI page's |BrowserProxy| singleton calls this method when the page // The WebUI page's |BrowserProxy| singleton calls this method when the page
...@@ -42,6 +56,14 @@ interface PageHandler { ...@@ -42,6 +56,14 @@ interface PageHandler {
UpdateMostVisitedTile(url.mojom.Url url, url.mojom.Url new_url, UpdateMostVisitedTile(url.mojom.Url url, url.mojom.Url new_url,
string new_title) string new_title)
=> (bool success); => (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. // WebUI-side handler for requests from the browser.
......
...@@ -8,16 +8,23 @@ ...@@ -8,16 +8,23 @@
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ntp_tiles/chrome_most_visited_sites_factory.h" #include "chrome/browser/ntp_tiles/chrome_most_visited_sites_factory.h"
#include "chrome/browser/profiles/profile.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/browser/search_engines/template_url_service_factory.h"
#include "chrome/common/pref_names.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 "components/prefs/pref_service.h"
#include "ui/base/l10n/l10n_util.h"
NewTabPageHandler::NewTabPageHandler( NewTabPageHandler::NewTabPageHandler(
mojo::PendingReceiver<new_tab_page::mojom::PageHandler> mojo::PendingReceiver<new_tab_page::mojom::PageHandler>
pending_page_handler, pending_page_handler,
mojo::PendingRemote<new_tab_page::mojom::Page> pending_page, mojo::PendingRemote<new_tab_page::mojom::Page> pending_page,
Profile* profile) Profile* profile)
: page_{std::move(pending_page)}, : chrome_colors_service_(
chrome_colors::ChromeColorsFactory::GetForProfile(profile)),
page_{std::move(pending_page)},
pref_service_(profile->GetPrefs()), pref_service_(profile->GetPrefs()),
receiver_{this, std::move(pending_page_handler)} { receiver_{this, std::move(pending_page_handler)} {
most_visited_sites_ = ChromeMostVisitedSitesFactory::NewForProfile(profile); most_visited_sites_ = ChromeMostVisitedSitesFactory::NewForProfile(profile);
...@@ -106,6 +113,37 @@ void NewTabPageHandler::UndoMostVisitedTileAction() { ...@@ -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() { void NewTabPageHandler::OnCustomLinksEnableChange() {
most_visited_sites_->EnableCustomLinks(IsCustomLinksEnabled()); most_visited_sites_->EnableCustomLinks(IsCustomLinksEnabled());
page_->SetCustomLinksEnabled(IsCustomLinksEnabled()); page_->SetCustomLinksEnabled(IsCustomLinksEnabled());
......
...@@ -23,6 +23,10 @@ class GURL; ...@@ -23,6 +23,10 @@ class GURL;
class PrefService; class PrefService;
class Profile; class Profile;
namespace chrome_colors {
class ChromeColorsService;
} // namespace chrome_colors
class NewTabPageHandler : public content::WebContentsObserver, class NewTabPageHandler : public content::WebContentsObserver,
public ntp_tiles::MostVisitedSites::Observer, public ntp_tiles::MostVisitedSites::Observer,
public new_tab_page::mojom::PageHandler { public new_tab_page::mojom::PageHandler {
...@@ -46,6 +50,10 @@ class NewTabPageHandler : public content::WebContentsObserver, ...@@ -46,6 +50,10 @@ class NewTabPageHandler : public content::WebContentsObserver,
const std::string& new_title, const std::string& new_title,
UpdateMostVisitedTileCallback callback) override; UpdateMostVisitedTileCallback callback) override;
void UndoMostVisitedTileAction() 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: private:
bool IsCustomLinksEnabled(); bool IsCustomLinksEnabled();
...@@ -62,6 +70,7 @@ class NewTabPageHandler : public content::WebContentsObserver, ...@@ -62,6 +70,7 @@ class NewTabPageHandler : public content::WebContentsObserver,
// Data source for NTP tiles (aka Most Visited tiles). May be null. // Data source for NTP tiles (aka Most Visited tiles). May be null.
std::unique_ptr<ntp_tiles::MostVisitedSites> most_visited_sites_; std::unique_ptr<ntp_tiles::MostVisitedSites> most_visited_sites_;
chrome_colors::ChromeColorsService* chrome_colors_service_;
mojo::Remote<new_tab_page::mojom::Page> page_; mojo::Remote<new_tab_page::mojom::Page> page_;
......
...@@ -59,12 +59,16 @@ content::WebUIDataSource* CreateNewTabPageUiHtmlSource() { ...@@ -59,12 +59,16 @@ content::WebUIDataSource* CreateNewTabPageUiHtmlSource() {
{"urlField", IDS_NTP_CUSTOM_LINKS_URL}, {"urlField", IDS_NTP_CUSTOM_LINKS_URL},
// Customize button and dialog. // Customize button and dialog.
{"customizeButton", IDS_NTP_CUSTOMIZE_BUTTON_LABEL},
{"cancelButton", IDS_CANCEL}, {"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}, {"doneButton", IDS_DONE},
}; };
AddLocalizedStringsBulk(source, kStrings); 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", source->AddResourcePath("new_tab_page.mojom-lite.js",
IDR_NEW_TAB_PAGE_MOJO_LITE_JS); IDR_NEW_TAB_PAGE_MOJO_LITE_JS);
webui::SetupWebUIDataSource( webui::SetupWebUIDataSource(
......
...@@ -2,46 +2,94 @@ ...@@ -2,46 +2,94 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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 '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'; import {flushTasks} from 'chrome://test/test_util.m.js';
suite('NewTabPageCustomizeDialogTest', () => { suite('NewTabPageCustomizeDialogTest', () => {
/** @type {!CustomizeDialogElement} */ /** @type {TestProxy} */
let customizeDialog; let testProxy;
/** @return {!CustomizeDialogElement} */
function createCustomizeDialog() {
const customizeDialog = document.createElement('ntp-customize-dialog');
document.body.appendChild(customizeDialog);
return customizeDialog;
}
setup(() => { setup(() => {
PolymerTest.clearBody(); PolymerTest.clearBody();
customizeDialog = document.createElement('ntp-customize-dialog'); testProxy = new TestProxy();
document.body.appendChild(customizeDialog); BrowserProxy.instance_ = testProxy;
}); });
test('setting colors shows color tiles', async () => { test('setting themes shows theme tiles', async () => {
// Act. // Arrange.
customizeDialog.colors_ = [ const themes = [
{ {
label: 'color_0', id: 0,
icon: 'data_0', label: 'theme_0',
frameColor: {value: 0x000000}, // white.
activeTabColor: {value: 0x0000ff}, // blue.
}, },
{ {
label: 'color_1', id: 1,
icon: 'data_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(); await flushTasks();
// Assert. // Assert.
const colorTiles = const tiles = customizeDialog.shadowRoot.querySelectorAll('ntp-theme-icon');
Array.from(customizeDialog.shadowRoot.querySelectorAll('.tile')); assertEquals(tiles.length, 4);
assertEquals(colorTiles.length, 2); assertEquals(tiles[2].getAttribute('title'), 'theme_0');
assertEquals(colorTiles[0].getAttribute('title'), 'color_0'); assertStyle(tiles[2], '--ntp-theme-icon-frame-color', 'rgb(0, 0, 0)');
assertEquals(colorTiles[0].style['background-image'], 'url("data_0")'); assertStyle(
assertEquals(colorTiles[1].getAttribute('title'), 'color_1'); tiles[2], '--ntp-theme-icon-active-tab-color', 'rgb(0, 0, 255)');
assertEquals(colorTiles[1].style['background-image'], 'url("data_1")'); 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 { ...@@ -62,3 +62,15 @@ var NewTabPageCustomizeDialogTest = class extends NewTabPageBrowserTest {
TEST_F('NewTabPageCustomizeDialogTest', 'All', function() { TEST_F('NewTabPageCustomizeDialogTest', 'All', function() {
mocha.run(); 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 @@ ...@@ -2,6 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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'; import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
export class TestProxy { export class TestProxy {
...@@ -22,9 +27,6 @@ export class TestProxy { ...@@ -22,9 +27,6 @@ export class TestProxy {
class FakePageHandler { class FakePageHandler {
/** @param {newTabPage.mojom.PageInterface} */ /** @param {newTabPage.mojom.PageInterface} */
constructor(callbackRouterRemote) { constructor(callbackRouterRemote) {
/** @private {newTabPage.mojom.PageInterface} */
this.callbackRouterRemote_ = callbackRouterRemote;
/** @private {TestBrowserProxy} */ /** @private {TestBrowserProxy} */
this.callTracker_ = new TestBrowserProxy([ this.callTracker_ = new TestBrowserProxy([
'addMostVisitedTile', 'addMostVisitedTile',
...@@ -33,6 +35,10 @@ class FakePageHandler { ...@@ -33,6 +35,10 @@ class FakePageHandler {
'restoreMostVisitedDefaults', 'restoreMostVisitedDefaults',
'undoMostVisitedTileAction', 'undoMostVisitedTileAction',
'updateMostVisitedTile', 'updateMostVisitedTile',
'getChromeThemes',
'applyDefaultTheme',
'applyAutogeneratedTheme',
'applyChromeTheme',
]); ]);
} }
...@@ -44,6 +50,14 @@ class FakePageHandler { ...@@ -44,6 +50,14 @@ class FakePageHandler {
return this.callTracker_.whenCalled(methodName); return this.callTracker_.whenCalled(methodName);
} }
/**
* @param {string} methodName
* @param {*} value
*/
setResultFor(methodName, value) {
this.callTracker_.setResultFor(methodName, value);
}
/** @override */ /** @override */
addMostVisitedTile(url, title) { addMostVisitedTile(url, title) {
this.callTracker_.methodCalled('addMostVisitedTile', [url, title]); this.callTracker_.methodCalled('addMostVisitedTile', [url, title]);
...@@ -77,6 +91,28 @@ class FakePageHandler { ...@@ -77,6 +91,28 @@ class FakePageHandler {
'updateMostVisitedTile', [url, newUrl, newTitle]); 'updateMostVisitedTile', [url, newUrl, newTitle]);
return {success: true}; 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 { ...@@ -86,3 +122,14 @@ class FakePageHandler {
export function keydown(element, key) { export function keydown(element, key) {
element.dispatchEvent(new KeyboardEvent('keydown', {key: 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 @@ ...@@ -7,7 +7,8 @@
/** /**
* @typedef {{resolver: !PromiseResolver, * @typedef {{resolver: !PromiseResolver,
* callCount: number}} * callCount: number,
* result?: *}}
*/ */
let MethodData; let MethodData;
...@@ -104,6 +105,27 @@ let MethodData; ...@@ -104,6 +105,27 @@ let MethodData;
return this.getMethodData_(methodName).callCount; 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. * Try to give programmers help with mistyped methodNames.
* @param {string} methodName * @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