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

[webui][ntp] Support interactive doodles on themed NTPs

Adds feature flag WebUIThemeModeDoodles, which enables showing Doodles
on themed and dark mode NTPs. Furthermore, this CLs implements the dark
mode change protocol for interactive doodles. The protocol looks as
follows:

1. Append theme_messages=0 to the doodle URL. This tells the doodle that
   the mode change messaging protocol with version 0 is enabled.

2. Every time the mode changes postMessage
   {
     cmd: 'changeMode',
     dark: true|false,
   }
   to the doodle.

3. If the doodle postMessages
   {
     cmd: 'sendMode',
   }
   respond with the postMessage from 2. above. This is to work around
   a race condition where the NTP could postMessage the mode before the
   doodle has installed a message listener.

Bug: 688960
Change-Id: Icc1e333f33fd1f01a2c8d1d4a581fdf360ca7090
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2253220
Commit-Queue: Tibor Goldschwendt <tiborg@chromium.org>
Auto-Submit: Tibor Goldschwendt <tiborg@chromium.org>
Reviewed-by: default avatarMoe Ahmadi <mahmadi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#780392}
parent d486ab1a
......@@ -243,7 +243,7 @@
</template>
</dom-if>
<ntp-logo id="logo" doodle-allowed$="[[doodleAllowed_]]"
single-colored$="[[singleColoredLogo_]]">
single-colored$="[[singleColoredLogo_]]" dark="[[theme_.isDark]]">
</ntp-logo>
<ntp-fakebox id="fakebox" on-open-voice-search="onOpenVoiceSearch_"
hidden$="[[realboxEnabled_]]">
......
......@@ -576,7 +576,8 @@ class AppElement extends PolymerElement {
* @private
*/
computeDoodleAllowed_() {
return !this.showBackgroundImage_ && this.theme_ &&
return loadTimeData.getBoolean('themeModeDoodlesEnabled') ||
!this.showBackgroundImage_ && this.theme_ &&
this.theme_.type === newTabPage.mojom.ThemeType.DEFAULT &&
!this.theme_.isDark;
}
......
......@@ -9,9 +9,11 @@ import './doodle_share_dialog.js';
import {assert} from 'chrome://resources/js/assert.m.js';
import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {BrowserProxy} from './browser_proxy.js';
import {skColorToRgba} from './utils.js';
import {$$, skColorToRgba} from './utils.js';
/** @type {number} */
const SHARE_BUTTON_SIZE_PX = 26;
......@@ -48,6 +50,15 @@ class LogoElement extends PolymerElement {
value: false,
},
/**
* If true displays the dark mode doodle if possible.
* @type {boolean}
*/
dark: {
observer: 'onDarkChange_',
type: Boolean,
},
/** @private */
loaded_: Boolean,
......@@ -149,13 +160,16 @@ class LogoElement extends PolymerElement {
connectedCallback() {
super.connectedCallback();
this.eventTracker_.add(window, 'message', ({data}) => {
if (data['cmd'] !== 'resizeDoodle') {
return;
if (data['cmd'] === 'resizeDoodle') {
this.duration_ = assert(data.duration);
this.height_ = assert(data.height);
this.width_ = assert(data.width);
} else if (data['cmd'] === 'sendMode') {
this.sendMode_();
}
this.duration_ = assert(data.duration);
this.height_ = assert(data.height);
this.width_ = assert(data.width);
});
// Make sure the doodle gets the mode in case it has already requested it.
this.sendMode_();
}
/** @override */
......@@ -305,6 +319,33 @@ class LogoElement extends PolymerElement {
!!this.doodle_.content.imageDoodle.animationUrl;
}
/**
* Sends a postMessage to the interactive doodle whether the current theme is
* dark or light. Won't do anything if we don't have an interactive doodle or
* we haven't been told yet whether the current theme is dark or light.
* @private
*/
sendMode_() {
const iframe = $$(this, '#iframe');
if (!loadTimeData.getBoolean('themeModeDoodlesEnabled') ||
this.dark === undefined || !iframe) {
return;
}
BrowserProxy.getInstance().postMessage(
iframe,
{
cmd: 'changeMode',
dark: this.dark,
},
new URL(iframe.src).origin,
);
}
/** @private */
onDarkChange_() {
this.sendMode_();
}
/**
* @return {string}
* @private
......@@ -332,9 +373,15 @@ class LogoElement extends PolymerElement {
* @private
*/
computeIframeUrl_() {
return this.doodle_ && this.doodle_.content.interactiveDoodle &&
this.doodle_.content.interactiveDoodle.url.url ||
'';
if (this.doodle_ && this.doodle_.content.interactiveDoodle) {
const url = new URL(this.doodle_.content.interactiveDoodle.url.url);
if (loadTimeData.getBoolean('themeModeDoodlesEnabled')) {
url.searchParams.append('theme_messages', '0');
}
return url.href;
} else {
return '';
}
}
/**
......
......@@ -64,6 +64,10 @@ const base::Feature kWebUI{"NtpWebUI", base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kWebUIRealbox{"WebUIRealbox",
base::FEATURE_ENABLED_BY_DEFAULT};
// If enabled, the Doodle will be shown on themed and dark mode NTPs.
const base::Feature kWebUIThemeModeDoodles{"WebUIThemeModeDoodles",
base::FEATURE_DISABLED_BY_DEFAULT};
bool IsRealboxEnabled() {
if (!base::FeatureList::IsEnabled(omnibox::kNewSearchFeatures))
return false;
......
......@@ -20,6 +20,7 @@ extern const base::Feature kRealboxMatchOmniboxTheme;
extern const base::Feature kRealboxUseGoogleGIcon;
extern const base::Feature kWebUI;
extern const base::Feature kWebUIRealbox;
extern const base::Feature kWebUIThemeModeDoodles;
// Note: only exposed for about:flags. Use IsNtpRealboxEnabled() instead.
extern const base::Feature kRealbox;
......
......@@ -79,6 +79,10 @@ content::WebUIDataSource* CreateNewTabPageUiHtmlSource(Profile* profile) {
"oneGoogleBarModalOverlaysEnabled",
base::FeatureList::IsEnabled(ntp_features::kOneGoogleBarModalOverlays));
source->AddBoolean(
"themeModeDoodlesEnabled",
base::FeatureList::IsEnabled(ntp_features::kWebUIThemeModeDoodles));
static constexpr webui::LocalizedString kStrings[] = {
{"doneButton", IDS_DONE},
{"title", IDS_NEW_TAB_TITLE},
......
......@@ -112,6 +112,7 @@ suite('NewTabPageAppTest', () => {
assertStyle($$(app, '#backgroundImageAttribution2'), 'display', 'none');
assertTrue($$(app, '#logo').doodleAllowed);
assertFalse($$(app, '#logo').singleColored);
assertFalse($$(app, '#logo').dark);
});
test('setting 3p theme shows attribution', async () => {
......@@ -200,26 +201,51 @@ suite('NewTabPageAppTest', () => {
});
}
test('setting background image shows image, disallows doodle', async () => {
// Arrange.
const theme = createTheme();
theme.backgroundImage = {url: {url: 'https://img.png'}};
[true, false].forEach(themeModeDoodlesEnabled => {
const allows = themeModeDoodlesEnabled ? 'allows' : 'disallows';
test(`setting background image shows image, ${allows} doodle`, async () => {
// Arrange.
loadTimeData.overrideValues({themeModeDoodlesEnabled});
const theme = createTheme();
theme.backgroundImage = {url: {url: 'https://img.png'}};
// Act.
backgroundManager.resetResolver('setShowBackgroundImage');
testProxy.callbackRouterRemote.setTheme(theme);
await testProxy.callbackRouterRemote.$.flushForTesting();
// Act.
backgroundManager.resetResolver('setShowBackgroundImage');
testProxy.callbackRouterRemote.setTheme(theme);
await testProxy.callbackRouterRemote.$.flushForTesting();
// Assert.
assertEquals(1, backgroundManager.getCallCount('setShowBackgroundImage'));
assertTrue(await backgroundManager.whenCalled('setShowBackgroundImage'));
assertNotStyle(
$$(app, '#backgroundImageAttribution'), 'text-shadow', 'none');
assertEquals(1, backgroundManager.getCallCount('setBackgroundImage'));
assertEquals(
'https://img.png',
(await backgroundManager.whenCalled('setBackgroundImage')).url.url);
assertFalse($$(app, '#logo').doodleAllowed);
// Assert.
assertEquals(1, backgroundManager.getCallCount('setShowBackgroundImage'));
assertTrue(await backgroundManager.whenCalled('setShowBackgroundImage'));
assertNotStyle(
$$(app, '#backgroundImageAttribution'), 'text-shadow', 'none');
assertEquals(1, backgroundManager.getCallCount('setBackgroundImage'));
assertEquals(
'https://img.png',
(await backgroundManager.whenCalled('setBackgroundImage')).url.url);
if (themeModeDoodlesEnabled) {
assertTrue($$(app, '#logo').doodleAllowed);
} else {
assertFalse($$(app, '#logo').doodleAllowed);
}
});
test(`setting non-default theme ${allows} doodle`, async function() {
// Arrange.
const theme = createTheme();
theme.type = newTabPage.mojom.ThemeType.CHROME;
// Act.
testProxy.callbackRouterRemote.setTheme(theme);
await testProxy.callbackRouterRemote.$.flushForTesting();
// Assert.
if (themeModeDoodlesEnabled) {
assertTrue($$(app, '#logo').doodleAllowed);
} else {
assertFalse($$(app, '#logo').doodleAllowed);
}
});
});
test('setting attributions shows attributions', async function() {
......@@ -245,19 +271,6 @@ suite('NewTabPageAppTest', () => {
'bar', $$(app, '#backgroundImageAttribution2').textContent.trim());
});
test('setting non-default theme disallows doodle', async function() {
// Arrange.
const theme = createTheme();
theme.type = newTabPage.mojom.ThemeType.CHROME;
// Act.
testProxy.callbackRouterRemote.setTheme(theme);
await testProxy.callbackRouterRemote.$.flushForTesting();
// Assert.
assertFalse($$(app, '#logo').doodleAllowed);
});
test('setting logo color colors logo', async function() {
// Arrange.
const theme = createTheme();
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import {$$, BrowserProxy} from 'chrome://new-tab-page/new_tab_page.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {assertNotStyle, assertStyle, createTestProxy, keydown} from 'chrome://test/new_tab_page/test_support.js';
import {eventToPromise, flushTasks} from 'chrome://test/test_util.m.js';
......@@ -61,7 +62,7 @@ function createImageDoodle(width, height) {
};
}
suite('NewTabPageLogoTest', () => {
function createSuite(themeModeDoodlesEnabled) {
/**
* @implements {BrowserProxy}
* @extends {TestBrowserProxy}
......@@ -78,6 +79,10 @@ suite('NewTabPageLogoTest', () => {
return logo;
}
suiteSetup(() => {
loadTimeData.overrideValues({themeModeDoodlesEnabled});
});
setup(() => {
PolymerTest.clearBody();
......@@ -173,15 +178,60 @@ suite('NewTabPageLogoTest', () => {
}
}
});
logo.dark = false;
// Assert.
assertNotStyle($$(logo, '#doodle'), 'display', 'none');
assertEquals($$(logo, '#logo'), null);
assertEquals($$(logo, '#iframe').src, 'https://foo.com/');
assertNotStyle($$(logo, '#iframe'), 'display', 'none');
assertStyle($$(logo, '#iframe'), 'width', '200px');
assertStyle($$(logo, '#iframe'), 'height', '100px');
assertStyle($$(logo, '#imageContainer'), 'display', 'none');
if (themeModeDoodlesEnabled) {
assertEquals(
$$(logo, '#iframe').src, 'https://foo.com/?theme_messages=0');
assertEquals(1, testProxy.getCallCount('postMessage'));
const [iframe, {cmd, dark}, origin] =
await testProxy.whenCalled('postMessage');
assertEquals($$(logo, '#iframe'), iframe);
assertEquals('changeMode', cmd);
assertEquals(false, dark);
assertEquals('https://foo.com', origin);
} else {
assertEquals($$(logo, '#iframe').src, 'https://foo.com/');
}
});
test('message only after mode has been set', async () => {
// Act (no mode).
const logo = await createLogo({
content: {
interactiveDoodle: {
url: {url: 'https://foo.com'},
width: 200,
height: 100,
}
}
});
// Assert (no mode).
assertEquals(0, testProxy.getCallCount('postMessage'));
// Act (setting mode).
logo.dark = true;
// Assert (setting mode).
if (themeModeDoodlesEnabled) {
assertEquals(1, testProxy.getCallCount('postMessage'));
const [iframe, {cmd, dark}, origin] =
await testProxy.whenCalled('postMessage');
assertEquals($$(logo, '#iframe'), iframe);
assertEquals('changeMode', cmd);
assertEquals(true, dark);
assertEquals('https://foo.com', origin);
} else {
assertEquals(0, testProxy.getCallCount('postMessage'));
}
});
test('disallowing doodle shows logo', async () => {
......@@ -295,6 +345,30 @@ suite('NewTabPageLogoTest', () => {
assertEquals($$(logo, '#iframe').offsetWidth, width);
});
test('receiving mode message sends mode', async () => {
// Arrange.
const logo = await createLogo(
{content: {interactiveDoodle: {url: {url: 'https://foo.com'}}}});
logo.dark = false;
testProxy.resetResolver('postMessage');
// Act.
window.postMessage({cmd: 'sendMode'}, '*');
await flushTasks();
// Assert.
if (themeModeDoodlesEnabled) {
assertEquals(1, testProxy.getCallCount('postMessage'));
const [_, {cmd, dark}, origin] =
await testProxy.whenCalled('postMessage');
assertEquals('changeMode', cmd);
assertEquals(false, dark);
assertEquals('https://foo.com', origin);
} else {
assertEquals(0, testProxy.getCallCount('postMessage'));
}
});
test('clicking simple doodle opens link', async () => {
// Arrange.
const doodle = createImageDoodle();
......@@ -511,4 +585,13 @@ suite('NewTabPageLogoTest', () => {
assertEquals('supi', doodleId);
assertEquals('123', shareId);
});
}
suite('NewTabPageLogoTest', () => {
[true, false].forEach(themeModeDoodlesEnabled => {
const enabled = themeModeDoodlesEnabled ? 'enabled' : 'disabled';
suite(`theme mode doodles ${enabled}`, () => {
createSuite(themeModeDoodlesEnabled);
});
});
});
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