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

[webui][ntp] Add barebones doodle support

Supports showing the (preview) image of static and animated doodles as
well as iframing interactive doodles on the WebUI NTP. Support for
playing animated doodles, resizing interactive doodles, etc. will be
added later.

To support iframing the interactive doodle, this CL adds
chrome-untrusted://new-tab-page/iframe?<src> which lets us iframe
arbitrary pages into the WebUI NTP. The iframing works by embedding
an iframe pointing to <src> into an chrome-untrusted page, which in
turn is embedded into the WebUI NTP. This is necessary since we cannot
directly embed external pages into the WebUI NTP.

+ Adds support for a fresh-doodle query parameter to wait for the
  fetched doodle instead of displaying the cached one. Useful for
  testing.

+ Fixes vertical arrangement of elements on the WebUI NTP.

Bug: 1039910
Change-Id: I3791a20d392f3f8e8a6d36cec4c1e90f302fc04e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2109172
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@{#752607}
parent ad811d91
......@@ -13,6 +13,7 @@ js_type_check("closure_compile") {
":customize_dialog",
":fakebox",
":grid",
":logo",
":theme_icon",
":untrusted_iframe",
":utils",
......@@ -125,6 +126,12 @@ js_library("fakebox") {
]
}
js_library("logo") {
deps = [
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
]
}
polymer_modulizer("app") {
js_file = "app.js"
html_file = "app.html"
......@@ -197,6 +204,12 @@ polymer_modulizer("fakebox") {
html_type = "v3-ready"
}
polymer_modulizer("logo") {
js_file = "logo.js"
html_file = "logo.html"
html_type = "v3-ready"
}
group("polymer3_elements") {
public_deps = [
":app_module",
......@@ -206,6 +219,7 @@ group("polymer3_elements") {
":customize_themes_module",
":fakebox_module",
":grid_module",
":logo_module",
":mini_page_module",
":most_visited_module",
":theme_icon_module",
......
......@@ -43,17 +43,27 @@
flex-direction: column;
}
#oneGoogleBar {
pointer-events: none;
}
#oneGoogleBarSpacer {
height: 56px;
width: 100%;
}
#oneGoogleBar {
height: 100%;
pointer-events: none;
position: fixed;
width: 100%;
z-index: 1;
ntp-logo {
margin-bottom: 8px;
}
ntp-fakebox {
margin-bottom: 32px;
}
ntp-most-visited[dark] {
--icon-button-color-active: var(--google-grey-refresh-300);
--icon-button-color: white;
--tile-hover-color: rgba(255, 255, 255, .1);
}
#promo {
......@@ -150,12 +160,6 @@
#backgroundImageAttribution2 {
font-size: 0.75rem;
}
ntp-most-visited[dark] {
--icon-button-color-active: var(--google-grey-refresh-300);
--icon-button-color: white;
--tile-hover-color: rgba(255, 255, 255, .1);
}
</style>
<div id="background"
style="background-color: [[rgbOrInherit_(theme_.backgroundColor)]];
......@@ -168,9 +172,7 @@
<div id="backgroundGradient" hidden="[[!showBackgroundImage_]]"></div>
<div id="content">
<div id="oneGoogleBarSpacer"></div>
<ntp-untrusted-iframe id="oneGoogleBar" path="one-google-bar"
hidden$="[[!oneGoogleBarLoaded_]]">
</ntp-untrusted-iframe>
<ntp-logo></ntp-logo>
<ntp-fakebox id="fakebox" on-open-voice-search="onVoiceSearchClick_">
</ntp-fakebox>
<ntp-most-visited id="mostVisited" dark$="[[theme_.isDark]]">
......@@ -214,4 +216,7 @@
</div>
</a>
</div>
<ntp-untrusted-iframe id="oneGoogleBar" path="one-google-bar"
hidden$="[[!oneGoogleBarLoaded_]]">
</ntp-untrusted-iframe>
</div>
......@@ -8,6 +8,7 @@ import './customize_dialog.js';
import './voice_search_overlay.js';
import './untrusted_iframe.js';
import './fakebox.js';
import './logo.js';
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/shared_style_css.m.js';
......
<style>
:host {
height: 230px;
}
</style>
<img src="[[doodle_.content.image]]" hidden="[[!doodle_.content.image]]"></img>
<ntp-untrusted-iframe path="iframe?[[doodle_.content.url.url]]"
hidden="[[!doodle_.content.url]]">
</ntp-untrusted-iframe>
// Copyright 2020 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 './untrusted_iframe.js';
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {BrowserProxy} from './browser_proxy.js';
// Shows the Google logo or a doodle if available.
class LogoElement extends PolymerElement {
static get is() {
return 'ntp-logo';
}
static get template() {
return html`{__html_template__}`;
}
static get properties() {
return {
/** @private */
doodle_: Object,
};
}
constructor() {
super();
BrowserProxy.getInstance().handler.getDoodle().then(({doodle}) => {
this.doodle_ = doodle;
});
}
}
customElements.define(LogoElement.is, LogoElement);
......@@ -16,7 +16,6 @@
display: flex;
flex-wrap: wrap;
justify-content: center;
margin: 10px auto;
overflow: hidden;
transition: opacity 300ms ease-in-out;
width: calc(var(--content-width) + 12px);
......
......@@ -82,6 +82,9 @@
<include name="IDR_NEW_TAB_PAGE_FAKEBOX_JS"
file="${root_gen_dir}/chrome/browser/resources/new_tab_page/fakebox.js"
use_base_dir="false" type="BINDATA" compress="gzip" />
<include name="IDR_NEW_TAB_PAGE_LOGO_JS"
file="${root_gen_dir}/chrome/browser/resources/new_tab_page/logo.js"
use_base_dir="false" type="BINDATA" compress="gzip" />
</includes>
<structures>
<structure name="IDR_NEW_TAB_PAGE_NEW_TAB_PAGE_HTML"
......@@ -104,6 +107,8 @@
file="untrusted/promo.js" type="chrome_html" compress="gzip" />
<structure name="IDR_NEW_TAB_PAGE_UNTRUSTED_IMAGE_HTML"
file="untrusted/image.html" type="chrome_html" compress="gzip" />
<structure name="IDR_NEW_TAB_PAGE_UNTRUSTED_IFRAME_HTML"
file="untrusted/iframe.html" type="chrome_html" compress="gzip" />
</structures>
</release>
</grit>
<!doctype html>
<html>
<head>
<style>
html,
body,
iframe {
height: 100%;
width: 100%;
}
body {
margin: 0
}
iframe {
border: none;
}
</style>
</head>
<body>
<iframe src="$i18nRaw{url}"></iframe>
</body>
</html>
......@@ -1472,6 +1472,7 @@ jumbo_static_library("ui") {
"//components/network_session_configurator/common",
"//components/page_load_metrics/browser",
"//components/profile_metrics",
"//components/search_provider_logos:search_provider_logos",
"//components/ui_metrics",
"//components/url_formatter",
"//components/vector_icons",
......
......@@ -102,6 +102,20 @@ struct Theme {
ThemeInfo info;
};
// The contents of a doodle.
union DoodleContent {
// Doodle image encoded as data URL. Set for static and animated doodles.
string image;
// URL pointing to doodle page. Set for interactive doodles.
url.mojom.Url url;
};
// A doodle. Retrieved from the Google doodle server.
struct Doodle {
// The doodle content.
DoodleContent content;
};
// Used by the WebUI page to bootstrap bidirectional communication.
interface PageHandlerFactory {
// The WebUI page's |BrowserProxy| singleton calls this method when the page
......@@ -151,6 +165,8 @@ interface PageHandler {
FocusOmnibox();
// Pastes |text| into the omnibox.
PasteIntoOmnibox(string text);
// Gets current doodle if there is one.
GetDoodle() => (Doodle? doodle);
};
// WebUI-side handler for requests from the browser.
......
......@@ -4,6 +4,7 @@
#include "chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.h"
#include "base/base64.h"
#include "base/bind.h"
#include "base/i18n/rtl.h"
#include "base/strings/utf_string_conversions.h"
......@@ -14,10 +15,13 @@
#include "chrome/browser/search/chrome_colors/chrome_colors_service.h"
#include "chrome/browser/search/instant_service.h"
#include "chrome/browser/search/instant_service_factory.h"
#include "chrome/browser/search_provider_logos/logo_service_factory.h"
#include "chrome/browser/ui/search/omnibox_utils.h"
#include "chrome/common/search/generated_colors_info.h"
#include "chrome/common/search/instant_types.h"
#include "chrome/common/themes/autogenerated_theme_util.h"
#include "components/search_provider_logos/logo_service.h"
#include "components/search_provider_logos/switches.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/color_utils.h"
......@@ -86,11 +90,13 @@ NewTabPageHandler::NewTabPageHandler(
instant_service_(InstantServiceFactory::GetForProfile(profile)),
ntp_background_service_(
NtpBackgroundServiceFactory::GetForProfile(profile)),
logo_service_(LogoServiceFactory::GetForProfile(profile)),
page_{std::move(pending_page)},
receiver_{this, std::move(pending_page_handler)},
web_contents_(web_contents) {
CHECK(instant_service_);
CHECK(ntp_background_service_);
CHECK(logo_service_);
CHECK(web_contents_);
instant_service_->AddObserver(this);
ntp_background_service_->AddObserver(this);
......@@ -261,6 +267,27 @@ void NewTabPageHandler::PasteIntoOmnibox(const std::string& text) {
search::PasteIntoOmnibox(base::UTF8ToUTF16(text), web_contents_);
}
void NewTabPageHandler::GetDoodle(GetDoodleCallback callback) {
search_provider_logos::LogoCallbacks callbacks;
std::string fresh_doodle_param;
if (net::GetValueForKeyInQuery(web_contents_->GetLastCommittedURL(),
"fresh-doodle", &fresh_doodle_param) &&
fresh_doodle_param == "1") {
// In fresh-doodle mode, wait for the desired doodle to be downloaded.
callbacks.on_fresh_encoded_logo_available =
base::BindOnce(&NewTabPageHandler::OnLogoAvailable,
weak_ptr_factory_.GetWeakPtr(), std::move(callback));
} else {
// In regular mode, return cached doodle as it is available faster.
callbacks.on_cached_encoded_logo_available =
base::BindOnce(&NewTabPageHandler::OnLogoAvailable,
weak_ptr_factory_.GetWeakPtr(), std::move(callback));
}
// This will trigger re-downloading the doodle and caching it. This means that
// in regular mode a new doodle will be returned on subsequent NTP loads.
logo_service_->GetLogo(std::move(callbacks));
}
void NewTabPageHandler::NtpThemeChanged(const NtpTheme& ntp_theme) {
page_->SetTheme(MakeTheme(ntp_theme));
}
......@@ -345,3 +372,37 @@ void NewTabPageHandler::OnOmniboxFocusChanged(OmniboxFocusState state,
page_->SetFakeboxVisible(reason != OMNIBOX_FOCUS_CHANGE_TYPING);
}
}
void NewTabPageHandler::OnLogoAvailable(
GetDoodleCallback callback,
search_provider_logos::LogoCallbackReason type,
const base::Optional<search_provider_logos::EncodedLogo>& logo) {
if (!logo) {
std::move(callback).Run(nullptr);
return;
}
auto doodle = new_tab_page::mojom::Doodle::New();
switch (logo->metadata.type) {
case search_provider_logos::LogoType::SIMPLE:
case search_provider_logos::LogoType::ANIMATED: {
if (!logo->encoded_image) {
std::move(callback).Run(nullptr);
return;
}
std::string base64;
base::Base64Encode(logo->encoded_image->data(), &base64);
auto data_url =
base::StringPrintf("data:%s;base64,%s",
logo->metadata.mime_type.c_str(), base64.c_str());
doodle->content = new_tab_page::mojom::DoodleContent::NewImage(data_url);
} break;
case search_provider_logos::LogoType::INTERACTIVE:
doodle->content = new_tab_page::mojom::DoodleContent::NewUrl(
logo->metadata.full_page_url);
break;
default:
std::move(callback).Run(nullptr);
return;
}
std::move(callback).Run(std::move(doodle));
}
......@@ -12,6 +12,7 @@
#include "chrome/browser/ui/omnibox/omnibox_tab_helper.h"
#include "chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom.h"
#include "chrome/common/search/instant_types.h"
#include "components/search_provider_logos/logo_common.h"
#include "content/public/browser/web_contents_observer.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
......@@ -31,6 +32,10 @@ namespace content {
class WebContents;
} // namespace content
namespace search_provider_logos {
class LogoService;
} // namespace search_provider_logos
class NewTabPageHandler : public new_tab_page::mojom::PageHandler,
public InstantServiceObserver,
public NtpBackgroundServiceObserver,
......@@ -69,6 +74,7 @@ class NewTabPageHandler : public new_tab_page::mojom::PageHandler,
GetBackgroundImagesCallback callback) override;
void FocusOmnibox() override;
void PasteIntoOmnibox(const std::string& text) override;
void GetDoodle(GetDoodleCallback callback) override;
private:
// InstantServiceObserver:
......@@ -86,9 +92,15 @@ class NewTabPageHandler : public new_tab_page::mojom::PageHandler,
void OnOmniboxFocusChanged(OmniboxFocusState state,
OmniboxFocusChangeReason reason) override;
void OnLogoAvailable(
GetDoodleCallback callback,
search_provider_logos::LogoCallbackReason type,
const base::Optional<search_provider_logos::EncodedLogo>& logo);
chrome_colors::ChromeColorsService* chrome_colors_service_;
InstantService* instant_service_;
NtpBackgroundService* ntp_background_service_;
search_provider_logos::LogoService* logo_service_;
GURL last_blacklisted_;
GetBackgroundCollectionsCallback background_collections_callback_;
std::string images_request_collection_id_;
......@@ -96,6 +108,7 @@ class NewTabPageHandler : public new_tab_page::mojom::PageHandler,
mojo::Remote<new_tab_page::mojom::Page> page_;
mojo::Receiver<new_tab_page::mojom::PageHandler> receiver_;
content::WebContents* web_contents_;
base::WeakPtrFactory<NewTabPageHandler> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(NewTabPageHandler);
};
......
......@@ -71,6 +71,7 @@ void UntrustedSource::StartDataRequest(
const content::WebContents::Getter& wc_getter,
content::URLDataSource::GotDataCallback callback) {
const std::string path = url.has_path() ? url.path().substr(1) : "";
GURL url_param = GURL(url.query());
if (path == "one-google-bar" && one_google_bar_service_) {
one_google_bar_callbacks_.push_back(std::move(callback));
if (one_google_bar_callbacks_.size() == 1) {
......@@ -97,14 +98,24 @@ void UntrustedSource::StartDataRequest(
bundle.LoadDataResourceBytes(IDR_NEW_TAB_PAGE_UNTRUSTED_PROMO_JS));
return;
}
if (path == "image" && url.has_query()) {
if (path == "image" && url_param.is_valid() &&
url_param.SchemeIs(url::kHttpsScheme)) {
ui::TemplateReplacements replacements;
replacements["url"] = url.query();
replacements["url"] = url_param.spec();
std::string html =
FormatTemplate(IDR_NEW_TAB_PAGE_UNTRUSTED_IMAGE_HTML, replacements);
std::move(callback).Run(base::RefCountedString::TakeString(&html));
return;
}
if (path == "iframe" && url_param.is_valid() &&
url_param.SchemeIs(url::kHttpsScheme)) {
ui::TemplateReplacements replacements;
replacements["url"] = url_param.spec();
std::string html =
FormatTemplate(IDR_NEW_TAB_PAGE_UNTRUSTED_IFRAME_HTML, replacements);
std::move(callback).Run(base::RefCountedString::TakeString(&html));
return;
}
std::move(callback).Run(base::MakeRefCounted<base::RefCountedString>());
}
......@@ -137,7 +148,8 @@ bool UntrustedSource::ShouldServiceRequest(
}
const std::string path = url.path().substr(1);
return path == "one-google-bar" || path == "one_google_bar.js" ||
path == "promo" || path == "promo.js" || path == "image";
path == "promo" || path == "promo.js" || path == "image" ||
path == "iframe";
}
void UntrustedSource::OnOneGoogleBarDataUpdated() {
......
......@@ -28,6 +28,9 @@ suite('NewTabPageAppTest', () => {
testProxy.handler.setResultFor('getChromeThemes', Promise.resolve({
chromeThemes: [],
}));
testProxy.handler.setResultFor('getDoodle', Promise.resolve({
doodle: null,
}));
testProxy.setResultMapperFor('matchMedia', () => ({
addListener() {},
removeListener() {},
......
// Copyright 2020 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/logo.js';
import {BrowserProxy} from 'chrome://new-tab-page/browser_proxy.js';
import {assertNotStyle, assertStyle, createTestProxy} from 'chrome://test/new_tab_page/test_support.js';
import {flushTasks} from 'chrome://test/test_util.m.js';
suite('NewTabPageLogoTest', () => {
/**
* @implements {BrowserProxy}
* @extends {TestBrowserProxy}
*/
let testProxy;
async function createLogo(doodle) {
testProxy.handler.setResultFor('getDoodle', Promise.resolve({
doodle: doodle,
}));
const logo = document.createElement('ntp-logo');
document.body.appendChild(logo);
await flushTasks();
return logo;
}
setup(() => {
PolymerTest.clearBody();
testProxy = createTestProxy();
BrowserProxy.instance_ = testProxy;
});
test('setting static, animated doodle shows image', async () => {
// Act.
const logo = await createLogo({content: {image: 'data:foo'}});
// Assert.
const img = logo.shadowRoot.querySelector('img');
const iframe = logo.shadowRoot.querySelector('ntp-untrusted-iframe');
assertEquals(img.src, 'data:foo');
assertNotStyle(img, 'display', 'none');
assertStyle(iframe, 'display', 'none');
});
test('setting interactive doodle shows iframe', async () => {
// Act.
const logo = await createLogo({content: {url: {url: 'https://foo.com'}}});
// Assert.
const iframe = logo.shadowRoot.querySelector('ntp-untrusted-iframe');
const img = logo.shadowRoot.querySelector('img');
assertEquals(iframe.path, 'iframe?https://foo.com');
assertNotStyle(iframe, 'display', 'none');
assertStyle(img, 'display', 'none');
});
});
......@@ -146,3 +146,15 @@ var NewTabPageFakeboxTest = class extends NewTabPageBrowserTest {
TEST_F('NewTabPageFakeboxTest', 'All', function() {
mocha.run();
});
// eslint-disable-next-line no-var
var NewTabPageLogoTest = class extends NewTabPageBrowserTest {
/** @override */
get browsePreload() {
return 'chrome://new-tab-page/test_loader.html?module=new_tab_page/logo_test.js';
}
};
TEST_F('NewTabPageLogoTest', 'All', function() {
mocha.run();
});
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