Commit 78b96e46 authored by Tibor Goldschwendt's avatar Tibor Goldschwendt Committed by Commit Bot

[webui][ntp] Show theme background image if set

This includes showing attributions and applying styles changes such as
text shadows.

Bug: 1032328
Change-Id: If28b2d98887baa802ae88bde68746100fabe2b44
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2090729
Commit-Queue: Tibor Goldschwendt <tiborg@chromium.org>
Reviewed-by: default avatarAlex Gough <ajgo@chromium.org>
Reviewed-by: default avatarEsmael Elmoslimany <aee@chromium.org>
Cr-Commit-Position: refs/heads/master@{#748833}
parent 2b09e25d
<style include="cr-shared-style"> <style include="cr-shared-style">
:host {
--ntp-theme-shortcut-background-color: rgb(229, 231, 232);
--ntp-theme-text-color: var(--google-grey-800);
--ntp-theme-text-shadow: none;
}
@media (prefers-color-scheme: dark) {
:host {
--ntp-theme-shortcut-background-color: var(--google-grey-refresh-100);
--ntp-theme-text-color: white;
}
}
:host([show-background-image_]) {
--ntp-theme-text-shadow: 0 0 16px rgba(0, 0, 0, .3);
}
#background { #background {
height: 100%;
position: relative;
width: 100%;
}
#background > * {
height: 100%;
position: absolute;
top: 0;
width: 100%;
}
#backgroundImage {
border: none;
}
#backgroundGradient {
background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.3));
}
#content {
align-items: center; align-items: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%;
width: 100%;
} }
#voiceSearchButton { #voiceSearchButton {
...@@ -31,37 +67,92 @@ ...@@ -31,37 +67,92 @@
width: 100%; width: 100%;
} }
#customizeButtonContainer {
background-color: var(--ntp-background-override-color);
border-radius: calc(0.5 * var(--cr-button-height));
bottom: 16px;
position: absolute;
}
:host-context([dir='ltr']) #customizeButtonContainer {
right: 16px;
}
:host-context([dir='rtl']) #customizeButtonContainer {
left: 16px;
}
#customizeButton { #customizeButton {
border: none; border: none;
border-radius: calc(0.5 * var(--cr-button-height)); border-radius: calc(0.5 * var(--cr-button-height));
bottom: 16px;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 1px 2px rgba(0, 0, 0, 0.23); box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 1px 2px rgba(0, 0, 0, 0.23);
font-weight: 400; font-weight: 400;
}
#customizeIcon {
-webkit-mask-image: url(icons/icon_pencil.svg);
-webkit-mask-repeat: no-repeat;
-webkit-mask-size: 100%;
background-color: var(--text-color);
height: 16px;
margin-inline-end: 8px;
width: 16px;
}
#backgroundImageAttribution {
border-radius: 8px;
bottom: 16px;
color: var(--ntp-theme-text-color);
line-height: 20px;
max-width: 50vw;
padding: 8px;
position: absolute; position: absolute;
text-shadow: var(--ntp-theme-text-shadow);
} }
#customizeButton:not(:hover) { :host-context([dir='ltr']) #backgroundImageAttribution {
background-color: var(--ntp-background-override-color); left: 16px;
} }
:host-context([dir='ltr']) #customizeButton { :host-context([dir='rtl']) #backgroundImageAttribution {
right: 16px; right: 16px;
} }
:host-context([dir='rtl']) #customizeButton { #backgroundImageAttribution:hover {
left: 16px; background: rgba(var(--google-grey-900-rgb), .1);
} }
#customizeIcon { #backgroundImageAttribution1Container {
-webkit-mask-image: url(icons/icon_pencil.svg); align-items: center;
display: flex;
flex-direction: row;
}
#linkIcon {
-webkit-mask-image: url(icons/link.svg);
-webkit-mask-repeat: no-repeat; -webkit-mask-repeat: no-repeat;
-webkit-mask-size: 100%; -webkit-mask-size: 100%;
background-color: var(--text-color); background-color: var(--ntp-theme-text-color);
height: 16px; height: 16px;
margin-inline-end: 8px; margin-inline-end: 8px;
width: 16px; width: 16px;
} }
#backgroundImageAttribution1,
#backgroundImageAttribution2 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#backgroundImageAttribution1 {
font-size: 0.875rem;
}
#backgroundImageAttribution2 {
font-size: 0.75rem;
}
ntp-most-visited[dark] { ntp-most-visited[dark] {
--icon-button-color-active: var(--google-grey-refresh-300); --icon-button-color-active: var(--google-grey-refresh-300);
--icon-button-color: white; --icon-button-color: white;
...@@ -69,15 +160,19 @@ ...@@ -69,15 +160,19 @@
} }
</style> </style>
<div id="background" <div id="background"
style="background-color: [[rgbOrInherit_(theme_.backgroundColor)]];"> style="background-color: [[rgbOrInherit_(theme_.backgroundColor)]];
--ntp-theme-text-color: [[rgbOrInherit_(theme_.shortcutTextColor)]];
--ntp-theme-shortcut-background-color:
[[rgbOrInherit_(theme_.shortcutBackgroundColor)]];">
<ntp-untrusted-iframe id="backgroundImage" path="[[backgroundImagePath_]]"
hidden="[[!showBackgroundImage_]]">
</ntp-untrusted-iframe>
<div id="backgroundGradient" hidden="[[!showBackgroundImage_]]"></div>
<div id="content">
<button id="voiceSearchButton" on-click="onVoiceSearchClick_" <button id="voiceSearchButton" on-click="onVoiceSearchClick_"
title="$i18n{voiceSearchButtonLabel}"> title="$i18n{voiceSearchButtonLabel}">
</button> </button>
<ntp-most-visited id="mostVisited" dark$="[[theme_.isDark]]" <ntp-most-visited id="mostVisited" dark$="[[theme_.isDark]]">
style="--icon-background-color:
[[rgbOrInherit_(theme_.shortcutBackgroundColor)]];
--tile-title-color:
[[rgbOrInherit_(theme_.shortcutTextColor)]];">
</ntp-most-visited> </ntp-most-visited>
<dom-if if="[[showCustomizeDialog_]]" restamp> <dom-if if="[[showCustomizeDialog_]]" restamp>
<template> <template>
...@@ -94,8 +189,28 @@ ...@@ -94,8 +189,28 @@
</dom-if> </dom-if>
<ntp-untrusted-iframe id="promo" path="promo" hidden$="[[!promoLoaded_]]"> <ntp-untrusted-iframe id="promo" path="promo" hidden$="[[!promoLoaded_]]">
</ntp-untrusted-iframe> </ntp-untrusted-iframe>
<!-- cr-button is transparent on hover. This leads to incorrect results when
a custom background is set. Therefore, wrap customize button in
container to enfore solid background color. -->
<div id="customizeButtonContainer">
<cr-button id="customizeButton" on-click="onCustomizeClick_"> <cr-button id="customizeButton" on-click="onCustomizeClick_">
<div id="customizeIcon"></div> <div id="customizeIcon"></div>
$i18n{customizeButton} $i18n{customizeButton}
</cr-button> </cr-button>
</div>
<a id="backgroundImageAttribution"
href="[[theme_.backgroundImageAttributionUrl.url]]"
hidden="[[!theme_.backgroundImageAttribution1]]">
<div id="backgroundImageAttribution1Container">
<div id="linkIcon"></div>
<div id="backgroundImageAttribution1">
[[theme_.backgroundImageAttribution1]]
</div>
</div>
<div id="backgroundImageAttribution2"
hidden="[[!theme_.backgroundImageAttribution2]]">
[[theme_.backgroundImageAttribution2]]
</div>
</a>
</div>
</div> </div>
...@@ -42,6 +42,19 @@ class AppElement extends PolymerElement { ...@@ -42,6 +42,19 @@ class AppElement extends PolymerElement {
/** @private */ /** @private */
showVoiceSearchOverlay_: Boolean, showVoiceSearchOverlay_: Boolean,
/** @private */
showBackgroundImage_: {
computed: 'computeShowBackgroundImage_(theme_)',
reflectToAttribute: true,
type: Boolean,
},
/** @private */
backgroundImagePath_: {
computed: 'computeBackgroundImagePath_(theme_)',
type: String,
},
}; };
} }
...@@ -112,6 +125,25 @@ class AppElement extends PolymerElement { ...@@ -112,6 +125,25 @@ class AppElement extends PolymerElement {
rgbOrInherit_(skColor) { rgbOrInherit_(skColor) {
return skColor ? skColorToRgb(skColor) : 'inherit'; return skColor ? skColorToRgb(skColor) : 'inherit';
} }
/**
* @return {boolean}
* @private
*/
computeShowBackgroundImage_() {
return !!this.theme_ && !!this.theme_.backgroundImageUrl;
}
/**
* @return {string}
* @private
*/
computeBackgroundImagePath_() {
if (!this.theme_ || !this.theme_.backgroundImageUrl) {
return '';
}
return `image?${this.theme_.backgroundImageUrl.url}`;
}
} }
customElements.define(AppElement.is, AppElement); customElements.define(AppElement.is, AppElement);
...@@ -3,11 +3,9 @@ ...@@ -3,11 +3,9 @@
--icon-size: 48px; --icon-size: 48px;
--tile-size: 112px; --tile-size: 112px;
--tile-margin: 16px; --tile-margin: 16px;
--icon-background-color: rgb(229, 231, 232);
--icon-button-color: var(--google-grey-600); --icon-button-color: var(--google-grey-600);
--icon-button-color-active: var(--google-grey-refresh-700); --icon-button-color-active: var(--google-grey-refresh-700);
--tile-hover-color: rgba(var(--google-grey-900-rgb), .1); --tile-hover-color: rgba(var(--google-grey-900-rgb), .1);
--tile-title-color: var(--google-grey-800);
} }
#container { #container {
...@@ -28,7 +26,7 @@ ...@@ -28,7 +26,7 @@
-webkit-mask-image: url(chrome://resources/images/add.svg); -webkit-mask-image: url(chrome://resources/images/add.svg);
-webkit-mask-repeat: no-repeat; -webkit-mask-repeat: no-repeat;
-webkit-mask-size: 100%; -webkit-mask-size: 100%;
background-color: var(--tile-title-color); background-color: var(--ntp-theme-text-color);
height: 24px; height: 24px;
width: 24px; width: 24px;
} }
...@@ -68,7 +66,7 @@ ...@@ -68,7 +66,7 @@
.tile-icon { .tile-icon {
align-items: center; align-items: center;
background-color: var(--icon-background-color); background-color: var(--ntp-theme-shortcut-background-color);
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
height: var(--icon-size); height: var(--icon-size);
...@@ -82,11 +80,12 @@ ...@@ -82,11 +80,12 @@
} }
.tile-title { .tile-title {
color: var(--tile-title-color); color: var(--ntp-theme-text-color);
margin-top: 16px; margin-top: 16px;
overflow: hidden; overflow: hidden;
text-align: center; text-align: center;
text-overflow: ellipsis; text-overflow: ellipsis;
text-shadow: var(--ntp-theme-text-shadow);
white-space: nowrap; white-space: nowrap;
width: 88px; width: 88px;
} }
...@@ -138,11 +137,9 @@ ...@@ -138,11 +137,9 @@
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:host { :host {
--icon-background-color: var(--google-grey-refresh-100);
--icon-button-color: var(--google-grey-400); --icon-button-color: var(--google-grey-400);
--icon-button-color-active: var(--google-grey-200); --icon-button-color-active: var(--google-grey-200);
--tile-hover-color: rgba(255, 255, 255, .1); --tile-hover-color: rgba(255, 255, 255, .1);
--tile-title-color: white;
} }
} }
</style> </style>
......
...@@ -74,6 +74,8 @@ ...@@ -74,6 +74,8 @@
file="icons/googlemic_clr_24px.svg" type="BINDATA" compress="gzip" /> file="icons/googlemic_clr_24px.svg" type="BINDATA" compress="gzip" />
<include name="IDR_NEW_TAB_PAGE_MIC_SVG" <include name="IDR_NEW_TAB_PAGE_MIC_SVG"
file="icons/mic.svg" type="BINDATA" compress="gzip" /> file="icons/mic.svg" type="BINDATA" compress="gzip" />
<include name="IDR_NEW_TAB_PAGE_LINK_SVG"
file="icons/link.svg" type="BINDATA" compress="gzip" />
<include name="IDR_NEW_TAB_PAGE_MINI_PAGE_JS" <include name="IDR_NEW_TAB_PAGE_MINI_PAGE_JS"
file="${root_gen_dir}/chrome/browser/resources/new_tab_page/mini_page.js" file="${root_gen_dir}/chrome/browser/resources/new_tab_page/mini_page.js"
use_base_dir="false" type="BINDATA" compress="gzip" /> use_base_dir="false" type="BINDATA" compress="gzip" />
......
...@@ -88,6 +88,14 @@ struct Theme { ...@@ -88,6 +88,14 @@ struct Theme {
skia.mojom.SkColor shortcut_text_color; skia.mojom.SkColor shortcut_text_color;
// True if the theme is dark (e.g. NTP background color is dark). // True if the theme is dark (e.g. NTP background color is dark).
bool is_dark; bool is_dark;
// URL to the background image. Can point to untrusted content.
url.mojom.Url? background_image_url;
// Human readable attributions of the background image.
string? background_image_attribution_1;
string? background_image_attribution_2;
// URL associated with the background image. Used for href.
url.mojom.Url? background_image_attribution_url;
// TODO(crbug.com/1040682): Additional info about the theme depending on the // TODO(crbug.com/1040682): Additional info about the theme depending on the
// type. That should be optional since only some themes require it. However, // type. That should be optional since only some themes require it. However,
// making this field optional crashes JS. // making this field optional crashes JS.
......
...@@ -54,6 +54,21 @@ new_tab_page::mojom::ThemePtr MakeTheme(const NtpTheme& ntp_theme) { ...@@ -54,6 +54,21 @@ new_tab_page::mojom::ThemePtr MakeTheme(const NtpTheme& ntp_theme) {
theme->shortcut_background_color = ntp_theme.shortcut_color; theme->shortcut_background_color = ntp_theme.shortcut_color;
theme->shortcut_text_color = ntp_theme.text_color; theme->shortcut_text_color = ntp_theme.text_color;
theme->is_dark = !color_utils::IsDark(ntp_theme.text_color); theme->is_dark = !color_utils::IsDark(ntp_theme.text_color);
if (!ntp_theme.custom_background_url.is_empty()) {
theme->background_image_url = ntp_theme.custom_background_url;
}
if (!ntp_theme.custom_background_attribution_line_1.empty()) {
theme->background_image_attribution_1 =
ntp_theme.custom_background_attribution_line_1;
}
if (!ntp_theme.custom_background_attribution_line_2.empty()) {
theme->background_image_attribution_2 =
ntp_theme.custom_background_attribution_line_2;
}
if (!ntp_theme.custom_background_attribution_action_url.is_empty()) {
theme->background_image_attribution_url =
ntp_theme.custom_background_attribution_action_url;
}
return theme; return theme;
} }
...@@ -78,7 +93,7 @@ NewTabPageHandler::NewTabPageHandler( ...@@ -78,7 +93,7 @@ NewTabPageHandler::NewTabPageHandler(
CHECK(web_contents_); CHECK(web_contents_);
instant_service_->AddObserver(this); instant_service_->AddObserver(this);
ntp_background_service_->AddObserver(this); ntp_background_service_->AddObserver(this);
page_->SetTheme(MakeTheme(*instant_service_->GetInitializedNtpTheme())); instant_service_->UpdateNtpTheme();
} }
NewTabPageHandler::~NewTabPageHandler() { NewTabPageHandler::~NewTabPageHandler() {
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import 'chrome://new-tab-page/app.js'; import 'chrome://new-tab-page/app.js';
import {BrowserProxy} from 'chrome://new-tab-page/browser_proxy.js'; import {BrowserProxy} from 'chrome://new-tab-page/browser_proxy.js';
import {assertStyle, createTestProxy} from 'chrome://test/new_tab_page/test_support.js'; import {assertNotStyle, assertStyle, createTestProxy} 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('NewTabPageAppTest', () => { suite('NewTabPageAppTest', () => {
...@@ -58,6 +58,10 @@ suite('NewTabPageAppTest', () => { ...@@ -58,6 +58,10 @@ suite('NewTabPageAppTest', () => {
shortcutBackgroundColor: {value: 0xff00ff00}, shortcutBackgroundColor: {value: 0xff00ff00},
shortcutTextColor: {value: 0xff0000ff}, shortcutTextColor: {value: 0xff0000ff},
isDark: false, isDark: false,
backgroundImageUrl: null,
backgroundImageAttribution1: '',
backgroundImageAttribution2: '',
backgroundImageAttributionUrl: null,
}; };
// Act. // Act.
...@@ -69,7 +73,7 @@ suite('NewTabPageAppTest', () => { ...@@ -69,7 +73,7 @@ suite('NewTabPageAppTest', () => {
theme, app.shadowRoot.querySelector('ntp-customize-dialog').theme); theme, app.shadowRoot.querySelector('ntp-customize-dialog').theme);
}); });
test('setting theme updates background color and most visited', async () => { test('setting theme updates ntp', async () => {
// Act. // Act.
testProxy.callbackRouterRemote.setTheme({ testProxy.callbackRouterRemote.setTheme({
type: newTabPage.mojom.ThemeType.DEFAULT, type: newTabPage.mojom.ThemeType.DEFAULT,
...@@ -84,11 +88,14 @@ suite('NewTabPageAppTest', () => { ...@@ -84,11 +88,14 @@ suite('NewTabPageAppTest', () => {
// Assert. // Assert.
assertStyle(app.$.background, 'background-color', 'rgb(255, 0, 0)'); assertStyle(app.$.background, 'background-color', 'rgb(255, 0, 0)');
assertStyle( assertStyle(
app.shadowRoot.querySelector('ntp-most-visited'), app.$.background, '--ntp-theme-shortcut-background-color',
'--icon-background-color', 'rgb(0, 255, 0)'); 'rgb(0, 255, 0)');
assertStyle( assertStyle(app.$.background, '--ntp-theme-text-color', 'rgb(0, 0, 255)');
app.shadowRoot.querySelector('ntp-most-visited'), '--tile-title-color', assertFalse(app.$.background.hasAttribute('has-background-image'));
'rgb(0, 0, 255)'); assertStyle(app.$.backgroundImage, 'display', 'none');
assertStyle(app.$.backgroundGradient, 'display', 'none');
assertStyle(app.$.backgroundImageAttribution, 'display', 'none');
assertStyle(app.$.backgroundImageAttribution2, 'display', 'none');
}); });
test('clicking voice search button opens voice search overlay', async () => { test('clicking voice search button opens voice search overlay', async () => {
...@@ -99,4 +106,59 @@ suite('NewTabPageAppTest', () => { ...@@ -99,4 +106,59 @@ suite('NewTabPageAppTest', () => {
// Assert. // Assert.
assertTrue(!!app.shadowRoot.querySelector('ntp-voice-search-overlay')); assertTrue(!!app.shadowRoot.querySelector('ntp-voice-search-overlay'));
}); });
test('setting background images shows iframe and gradient', async () => {
// Act.
const theme = {
type: newTabPage.mojom.ThemeType.DEFAULT,
info: {chromeThemeId: 0},
backgroundColor: {value: 0xffff0000},
shortcutBackgroundColor: {value: 0xff00ff00},
shortcutTextColor: {value: 0xff0000ff},
isDark: false,
backgroundImageUrl: {url: 'https://img.png'},
backgroundImageAttribution1: '',
backgroundImageAttribution2: '',
backgroundImageAttributionUrl: null,
};
// Act.
testProxy.callbackRouterRemote.setTheme(theme);
await testProxy.callbackRouterRemote.$.flushForTesting();
// Assert.
assertNotStyle(app.$.backgroundImage, 'display', 'none');
assertNotStyle(app.$.backgroundGradient, 'display', 'none');
assertNotStyle(app.$.backgroundImageAttribution, 'text-shadow', 'none');
assertEquals(app.$.backgroundImage.path, 'image?https://img.png');
});
test('setting attributions shows attributions', async function() {
// Act.
const theme = {
type: newTabPage.mojom.ThemeType.DEFAULT,
info: {chromeThemeId: 0},
backgroundColor: {value: 0xffff0000},
shortcutBackgroundColor: {value: 0xff00ff00},
shortcutTextColor: {value: 0xff0000ff},
isDark: false,
backgroundImageUrl: null,
backgroundImageAttribution1: 'foo',
backgroundImageAttribution2: 'bar',
backgroundImageAttributionUrl: {url: 'https://info.com'},
};
// Act.
testProxy.callbackRouterRemote.setTheme(theme);
await testProxy.callbackRouterRemote.$.flushForTesting();
// Assert.
assertNotStyle(app.$.backgroundImageAttribution, 'display', 'none');
assertNotStyle(app.$.backgroundImageAttribution2, 'display', 'none');
assertEquals(
app.$.backgroundImageAttribution.getAttribute('href'),
'https://info.com');
assertEquals(app.$.backgroundImageAttribution1.textContent.trim(), 'foo');
assertEquals(app.$.backgroundImageAttribution2.textContent.trim(), 'bar');
});
}); });
...@@ -613,8 +613,9 @@ suite('NewTabPageMostVisitedTest', () => { ...@@ -613,8 +613,9 @@ suite('NewTabPageMostVisitedTest', () => {
test('setting color styles tile color', () => { test('setting color styles tile color', () => {
// Act. // Act.
mostVisited.style.setProperty('--tile-title-color', 'blue'); mostVisited.style.setProperty('--ntp-theme-text-color', 'blue');
mostVisited.style.setProperty('--icon-background-color', 'red'); mostVisited.style.setProperty(
'--ntp-theme-shortcut-background-color', 'red');
// Assert. // Assert.
queryAll('.tile-title').forEach(tile => { queryAll('.tile-title').forEach(tile => {
......
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