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">
: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 {
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;
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
#voiceSearchButton {
......@@ -31,37 +67,92 @@
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 {
border: none;
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);
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;
text-shadow: var(--ntp-theme-text-shadow);
}
#customizeButton:not(:hover) {
background-color: var(--ntp-background-override-color);
:host-context([dir='ltr']) #backgroundImageAttribution {
left: 16px;
}
:host-context([dir='ltr']) #customizeButton {
:host-context([dir='rtl']) #backgroundImageAttribution {
right: 16px;
}
:host-context([dir='rtl']) #customizeButton {
left: 16px;
#backgroundImageAttribution:hover {
background: rgba(var(--google-grey-900-rgb), .1);
}
#customizeIcon {
-webkit-mask-image: url(icons/icon_pencil.svg);
#backgroundImageAttribution1Container {
align-items: center;
display: flex;
flex-direction: row;
}
#linkIcon {
-webkit-mask-image: url(icons/link.svg);
-webkit-mask-repeat: no-repeat;
-webkit-mask-size: 100%;
background-color: var(--text-color);
background-color: var(--ntp-theme-text-color);
height: 16px;
margin-inline-end: 8px;
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] {
--icon-button-color-active: var(--google-grey-refresh-300);
--icon-button-color: white;
......@@ -69,33 +160,57 @@
}
</style>
<div id="background"
style="background-color: [[rgbOrInherit_(theme_.backgroundColor)]];">
<button id="voiceSearchButton" on-click="onVoiceSearchClick_"
title="$i18n{voiceSearchButtonLabel}">
</button>
<ntp-most-visited id="mostVisited" dark$="[[theme_.isDark]]"
style="--icon-background-color:
[[rgbOrInherit_(theme_.shortcutBackgroundColor)]];
--tile-title-color:
[[rgbOrInherit_(theme_.shortcutTextColor)]];">
</ntp-most-visited>
<dom-if if="[[showCustomizeDialog_]]" restamp>
<template>
<ntp-customize-dialog on-close="onCustomizeDialogClose_"
theme="[[theme_]]">
</ntp-customize-dialog>
</template>
</dom-if>
<dom-if if="[[showVoiceSearchOverlay_]]" restamp>
<template>
<ntp-voice-search-overlay on-close="onVoiceSearchOverlayClose_">
</ntp-voice-search-overlay>
</template>
</dom-if>
<ntp-untrusted-iframe id="promo" path="promo" hidden$="[[!promoLoaded_]]">
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>
<cr-button id="customizeButton" on-click="onCustomizeClick_">
<div id="customizeIcon"></div>
$i18n{customizeButton}
</cr-button>
<div id="backgroundGradient" hidden="[[!showBackgroundImage_]]"></div>
<div id="content">
<button id="voiceSearchButton" on-click="onVoiceSearchClick_"
title="$i18n{voiceSearchButtonLabel}">
</button>
<ntp-most-visited id="mostVisited" dark$="[[theme_.isDark]]">
</ntp-most-visited>
<dom-if if="[[showCustomizeDialog_]]" restamp>
<template>
<ntp-customize-dialog on-close="onCustomizeDialogClose_"
theme="[[theme_]]">
</ntp-customize-dialog>
</template>
</dom-if>
<dom-if if="[[showVoiceSearchOverlay_]]" restamp>
<template>
<ntp-voice-search-overlay on-close="onVoiceSearchOverlayClose_">
</ntp-voice-search-overlay>
</template>
</dom-if>
<ntp-untrusted-iframe id="promo" path="promo" hidden$="[[!promoLoaded_]]">
</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_">
<div id="customizeIcon"></div>
$i18n{customizeButton}
</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>
......@@ -42,6 +42,19 @@ class AppElement extends PolymerElement {
/** @private */
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 {
rgbOrInherit_(skColor) {
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);
......@@ -2,12 +2,10 @@
:host {
--icon-size: 48px;
--tile-size: 112px;
--tile-margin: 16px;
--icon-background-color: rgb(229, 231, 232);
--tile-margin: 16px;
--icon-button-color: var(--google-grey-600);
--icon-button-color-active: var(--google-grey-refresh-700);
--tile-hover-color: rgba(var(--google-grey-900-rgb), .1);
--tile-title-color: var(--google-grey-800);
}
#container {
......@@ -28,7 +26,7 @@
-webkit-mask-image: url(chrome://resources/images/add.svg);
-webkit-mask-repeat: no-repeat;
-webkit-mask-size: 100%;
background-color: var(--tile-title-color);
background-color: var(--ntp-theme-text-color);
height: 24px;
width: 24px;
}
......@@ -68,7 +66,7 @@
.tile-icon {
align-items: center;
background-color: var(--icon-background-color);
background-color: var(--ntp-theme-shortcut-background-color);
border-radius: 50%;
display: flex;
height: var(--icon-size);
......@@ -82,11 +80,12 @@
}
.tile-title {
color: var(--tile-title-color);
color: var(--ntp-theme-text-color);
margin-top: 16px;
overflow: hidden;
text-align: center;
text-overflow: ellipsis;
text-shadow: var(--ntp-theme-text-shadow);
white-space: nowrap;
width: 88px;
}
......@@ -138,11 +137,9 @@
@media (prefers-color-scheme: dark) {
:host {
--icon-background-color: var(--google-grey-refresh-100);
--icon-button-color: var(--google-grey-400);
--icon-button-color-active: var(--google-grey-200);
--tile-hover-color: rgba(255, 255, 255, .1);
--tile-title-color: white;
}
}
</style>
......
......@@ -74,6 +74,8 @@
file="icons/googlemic_clr_24px.svg" type="BINDATA" compress="gzip" />
<include name="IDR_NEW_TAB_PAGE_MIC_SVG"
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"
file="${root_gen_dir}/chrome/browser/resources/new_tab_page/mini_page.js"
use_base_dir="false" type="BINDATA" compress="gzip" />
......
......@@ -88,6 +88,14 @@ struct Theme {
skia.mojom.SkColor shortcut_text_color;
// True if the theme is dark (e.g. NTP background color 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
// type. That should be optional since only some themes require it. However,
// making this field optional crashes JS.
......
......@@ -54,6 +54,21 @@ new_tab_page::mojom::ThemePtr MakeTheme(const NtpTheme& ntp_theme) {
theme->shortcut_background_color = ntp_theme.shortcut_color;
theme->shortcut_text_color = 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;
}
......@@ -78,7 +93,7 @@ NewTabPageHandler::NewTabPageHandler(
CHECK(web_contents_);
instant_service_->AddObserver(this);
ntp_background_service_->AddObserver(this);
page_->SetTheme(MakeTheme(*instant_service_->GetInitializedNtpTheme()));
instant_service_->UpdateNtpTheme();
}
NewTabPageHandler::~NewTabPageHandler() {
......
......@@ -5,7 +5,7 @@
import 'chrome://new-tab-page/app.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';
suite('NewTabPageAppTest', () => {
......@@ -58,6 +58,10 @@ suite('NewTabPageAppTest', () => {
shortcutBackgroundColor: {value: 0xff00ff00},
shortcutTextColor: {value: 0xff0000ff},
isDark: false,
backgroundImageUrl: null,
backgroundImageAttribution1: '',
backgroundImageAttribution2: '',
backgroundImageAttributionUrl: null,
};
// Act.
......@@ -69,7 +73,7 @@ suite('NewTabPageAppTest', () => {
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.
testProxy.callbackRouterRemote.setTheme({
type: newTabPage.mojom.ThemeType.DEFAULT,
......@@ -84,11 +88,14 @@ suite('NewTabPageAppTest', () => {
// Assert.
assertStyle(app.$.background, 'background-color', 'rgb(255, 0, 0)');
assertStyle(
app.shadowRoot.querySelector('ntp-most-visited'),
'--icon-background-color', 'rgb(0, 255, 0)');
assertStyle(
app.shadowRoot.querySelector('ntp-most-visited'), '--tile-title-color',
'rgb(0, 0, 255)');
app.$.background, '--ntp-theme-shortcut-background-color',
'rgb(0, 255, 0)');
assertStyle(app.$.background, '--ntp-theme-text-color', 'rgb(0, 0, 255)');
assertFalse(app.$.background.hasAttribute('has-background-image'));
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 () => {
......@@ -99,4 +106,59 @@ suite('NewTabPageAppTest', () => {
// Assert.
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', () => {
test('setting color styles tile color', () => {
// Act.
mostVisited.style.setProperty('--tile-title-color', 'blue');
mostVisited.style.setProperty('--icon-background-color', 'red');
mostVisited.style.setProperty('--ntp-theme-text-color', 'blue');
mostVisited.style.setProperty(
'--ntp-theme-shortcut-background-color', 'red');
// Assert.
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