Commit be265fd1 authored by Kevin Marshall's avatar Kevin Marshall Committed by Commit Bot

[fuchsia] Add ThemeManager service.

Implements support for setting the light/dark color theme via the
fuchsia.web.Frame FIDL API or the fuchsia.settings.Display service.

Fixed: 1119521
Change-Id: Iab2fb9d38cabd1af2e57886563c203440b15d74a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2518261Reviewed-by: default avatarAlex Gough <ajgo@chromium.org>
Reviewed-by: default avatarDavid Dorwin <ddorwin@chromium.org>
Reviewed-by: default avatarSharon Yang <yangsharon@chromium.org>
Commit-Queue: Kevin Marshall <kmarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#827100}
parent 62d998ac
......@@ -106,6 +106,7 @@ component("web_engine_core") {
"//third_party/fuchsia-sdk/sdk/fidl/fuchsia.feedback",
"//third_party/fuchsia-sdk/sdk/fidl/fuchsia.math",
"//third_party/fuchsia-sdk/sdk/fidl/fuchsia.media.sessions2",
"//third_party/fuchsia-sdk/sdk/fidl/fuchsia.settings",
"//third_party/fuchsia-sdk/sdk/fidl/fuchsia.ui.gfx",
"//third_party/fuchsia-sdk/sdk/pkg/scenic_cpp",
"//third_party/fuchsia-sdk/sdk/pkg/sys_cpp",
......@@ -161,6 +162,8 @@ component("web_engine_core") {
"browser/navigation_policy_handler.h",
"browser/navigation_policy_throttle.cc",
"browser/navigation_policy_throttle.h",
"browser/theme_manager.cc",
"browser/theme_manager.h",
"browser/url_request_rewrite_rules_manager.cc",
"browser/url_request_rewrite_rules_manager.h",
"browser/web_engine_browser_context.cc",
......@@ -307,6 +310,7 @@ test("web_engine_browsertests") {
"browser/frame_impl_browsertest.cc",
"browser/headless_browsertest.cc",
"browser/media_browsertest.cc",
"browser/theme_manager_browsertest.cc",
]
defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
deps = [
......
......@@ -251,7 +251,8 @@ FrameImpl::FrameImpl(std::unique_ptr<content::WebContents> web_contents,
log_level_(kLogSeverityUnreachable),
url_request_rewrite_rules_manager_(web_contents_.get()),
binding_(this, std::move(frame_request)),
media_blocker_(web_contents_.get()) {
media_blocker_(web_contents_.get()),
theme_manager_(web_contents_.get()) {
DCHECK(!WebContentsToFrameImplMap()[web_contents_.get()]);
WebContentsToFrameImplMap()[web_contents_.get()] = this;
......@@ -866,6 +867,20 @@ void FrameImpl::GetPrivateMemorySize(GetPrivateMemorySizeCallback callback) {
callback(task_stats.mem_private_bytes);
}
void FrameImpl::SetPreferredTheme(fuchsia::settings::ThemeType theme) {
theme_manager_.SetTheme(theme, base::BindOnce(
[](FrameImpl* frame_impl, bool result) {
// TODO(crbug.com/1148454): Destroy the
// frame once a fake Display service is
// implemented.
// if (!result)
// frame_impl->CloseAndDestroyFrame
// ZX_ERR_INVALID_ARGS);
},
base::Unretained(this)));
}
void FrameImpl::ForceContentDimensions(
std::unique_ptr<fuchsia::ui::gfx::vec2> web_dips) {
if (!web_dips) {
......
......@@ -27,6 +27,7 @@
#include "fuchsia/engine/browser/event_filter.h"
#include "fuchsia/engine/browser/frame_permission_controller.h"
#include "fuchsia/engine/browser/navigation_controller_impl.h"
#include "fuchsia/engine/browser/theme_manager.h"
#include "fuchsia/engine/browser/url_request_rewrite_rules_manager.h"
#include "ui/aura/window_tree_host.h"
#include "ui/wm/core/focus_controller.h"
......@@ -190,6 +191,7 @@ class FrameImpl : public fuchsia::web::Frame,
const content::MediaPlayerId& id,
WebContentsObserver::MediaStoppedReason reason) override;
void GetPrivateMemorySize(GetPrivateMemorySizeCallback callback) override;
void SetPreferredTheme(fuchsia::settings::ThemeType theme) override;
// content::WebContentsDelegate implementation.
void CloseContents(content::WebContents* source) override;
......@@ -274,6 +276,8 @@ class FrameImpl : public fuchsia::web::Frame,
fidl::Binding<fuchsia::web::Frame> binding_;
media_control::MediaBlocker media_blocker_;
ThemeManager theme_manager_;
};
#endif // FUCHSIA_ENGINE_BROWSER_FRAME_IMPL_H_
// 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.
#include "fuchsia/engine/browser/theme_manager.h"
#include "base/check.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "third_party/blink/public/mojom/css/preferred_color_scheme.mojom.h"
#include "third_party/blink/public/mojom/webpreferences/web_preferences.mojom.h"
namespace {
using blink::mojom::PreferredColorScheme;
using fuchsia::settings::ThemeType;
constexpr PreferredColorScheme kFallbackColorScheme =
PreferredColorScheme::kLight;
PreferredColorScheme ThemeTypeToBlinkScheme(ThemeType type) {
switch (type) {
case ThemeType::LIGHT:
return PreferredColorScheme::kLight;
case ThemeType::DARK:
return PreferredColorScheme::kDark;
case ThemeType::DEFAULT:
case ThemeType::AUTO:
NOTREACHED();
return kFallbackColorScheme;
}
}
} // namespace
ThemeManager::ThemeManager(content::WebContents* web_contents)
: web_contents_(web_contents) {
DCHECK(web_contents_);
// Per the FIDL API, the default theme is LIGHT.
SetTheme(ThemeType::LIGHT, base::DoNothing());
}
ThemeManager::~ThemeManager() = default;
void ThemeManager::SetTheme(
ThemeType theme,
ThemeManager::OnSetThemeCompleteCallback on_set_complete) {
requested_theme_ = theme;
on_set_complete_ = std::move(on_set_complete);
switch (*requested_theme_) {
case ThemeType::AUTO:
if (!EnsureDisplayService()) {
OnDisplayServiceMissing();
return;
}
break;
case ThemeType::DEFAULT:
std::move(on_set_complete_).Run(false);
return;
case ThemeType::LIGHT:
case ThemeType::DARK:
break;
}
ApplyTheme();
}
bool ThemeManager::EnsureDisplayService() {
if (observed_display_service_error_)
return false;
if (display_service_)
return true;
display_service_ = base::ComponentContextForProcess()
->svc()
->Connect<fuchsia::settings::Display>();
display_service_.set_error_handler([this](zx_status_t status) {
ZX_LOG_IF(ERROR, status != ZX_ERR_PEER_CLOSED, status)
<< "fuchsia.settings.Display disconnected.";
observed_display_service_error_ = true;
// If the channel to the Display service was dropped before we received a
// response from WatchForDisplayChanges, then it's likely that the service
// isn't available in the namespace at all, which should be reported as
// an error on the Frame.
// Otherwise, if a failure was detected for a Display that was previously
// functioning, it should be treated as a transient issue and the last known
// system theme should be used.
if (requested_theme_ && (*requested_theme_ == ThemeType::AUTO) &&
!did_receive_first_watch_result_) {
OnDisplayServiceMissing();
}
});
WatchForDisplayChanges();
return true;
}
void ThemeManager::OnDisplayServiceMissing() {
LOG(ERROR) << "AUTO theme requires access to the "
"`fuchsia.settings.Display` service to work.";
if (on_set_complete_)
std::move(on_set_complete_).Run(false);
}
void ThemeManager::ApplyTheme() {
DCHECK(requested_theme_);
blink::web_pref::WebPreferences web_preferences =
web_contents_->GetOrCreateWebPreferences();
if (requested_theme_ == ThemeType::AUTO) {
if (!system_theme_) {
// Defer theme application until we receive a system theme.
return;
}
web_preferences.preferred_color_scheme =
ThemeTypeToBlinkScheme(*system_theme_);
} else {
DCHECK(requested_theme_ == ThemeType::LIGHT ||
requested_theme_ == ThemeType::DARK);
web_preferences.preferred_color_scheme =
ThemeTypeToBlinkScheme(*requested_theme_);
}
web_contents_->SetWebPreferences(web_preferences);
if (on_set_complete_)
std::move(on_set_complete_).Run(true);
}
void ThemeManager::WatchForDisplayChanges() {
DCHECK(display_service_);
// Will reply immediately for the first call of Watch(). Subsequent calls to
// Watch() will be replied to as changes occur.
display_service_->Watch(
fit::bind_member(this, &ThemeManager::OnWatchResultReceived));
}
void ThemeManager::OnWatchResultReceived(
fuchsia::settings::DisplaySettings settings) {
did_receive_first_watch_result_ = true;
if (settings.has_theme() && settings.theme().has_theme_type() &&
(settings.theme().theme_type() == ThemeType::DARK ||
settings.theme().theme_type() == ThemeType::LIGHT)) {
system_theme_ = settings.theme().theme_type();
} else {
system_theme_ = base::nullopt;
}
ApplyTheme();
WatchForDisplayChanges();
}
// 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.
#ifndef FUCHSIA_ENGINE_BROWSER_THEME_MANAGER_H_
#define FUCHSIA_ENGINE_BROWSER_THEME_MANAGER_H_
#include <fuchsia/settings/cpp/fidl.h>
#include <fuchsia/web/cpp/fidl.h>
#include <lib/sys/cpp/component_context.h>
#include "base/fuchsia/process_context.h"
#include "content/public/browser/web_contents.h"
#include "fuchsia/engine/web_engine_export.h"
class WEB_ENGINE_EXPORT ThemeManager {
public:
using OnSetThemeCompleteCallback = base::OnceCallback<void(bool)>;
explicit ThemeManager(content::WebContents* web_contents);
~ThemeManager();
ThemeManager(const ThemeManager&) = delete;
ThemeManager& operator=(const ThemeManager&) = delete;
// Sets the |theme| requested by the FIDL caller.
// If the |theme| is AUTO, then the theme provided by |display_service_|
// will be used.
// |on_set_complete| is run with |true| if the theme was set correctly,
// or |false| either if |theme| is invalid, or if |display_service_| is
// required but unavailable.
void SetTheme(fuchsia::settings::ThemeType theme,
OnSetThemeCompleteCallback on_set_complete);
private:
// Attempts to connect to the fuchsia.settings.Display service.
// Returns true if a connection was created, or if one already exists.
// Return false if the service is unavailable.
bool EnsureDisplayService();
void ApplyTheme();
void WatchForDisplayChanges();
void OnWatchResultReceived(fuchsia::settings::DisplaySettings settings);
void OnDisplayServiceMissing();
bool observed_display_service_error_ = false;
bool did_receive_first_watch_result_ = false;
base::Optional<fuchsia::settings::ThemeType> requested_theme_;
base::Optional<fuchsia::settings::ThemeType> system_theme_;
content::WebContents* web_contents_;
fuchsia::settings::DisplayPtr display_service_;
OnSetThemeCompleteCallback on_set_complete_;
};
#endif // FUCHSIA_ENGINE_BROWSER_THEME_MANAGER_H_
// 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.
#include <fuchsia/settings/cpp/fidl_test_base.h>
#include "base/fuchsia/scoped_service_binding.h"
#include "base/fuchsia/test_component_context_for_process.h"
#include "base/json/json_writer.h"
#include "base/macros.h"
#include "base/optional.h"
#include "base/strings/string_piece.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "fuchsia/base/frame_test_util.h"
#include "fuchsia/base/test_navigation_listener.h"
#include "fuchsia/engine/browser/frame_impl.h"
#include "fuchsia/engine/test/test_data.h"
#include "fuchsia/engine/test/web_engine_browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
constexpr char kCssDark[] = "dark";
constexpr char kCssLight[] = "light";
class ThemeManagerTest : public cr_fuchsia::WebEngineBrowserTest,
public fuchsia::settings::testing::Display_TestBase {
public:
ThemeManagerTest() {
set_test_server_root(base::FilePath(cr_fuchsia::kTestServerRoot));
}
~ThemeManagerTest() override = default;
ThemeManagerTest(const ThemeManagerTest&) = delete;
ThemeManagerTest& operator=(const ThemeManagerTest&) = delete;
protected:
void SetUpOnMainThread() override {
ASSERT_TRUE(embedded_test_server()->Start());
cr_fuchsia::WebEngineBrowserTest::SetUpOnMainThread();
component_context_.emplace(
base::TestComponentContextForProcess::InitialState::kEmpty);
display_binding_.emplace(component_context_->additional_services(), this);
frame_ = WebEngineBrowserTest::CreateFrame(&navigation_listener_);
base::RunLoop().RunUntilIdle();
frame_->GetNavigationController(controller_.NewRequest());
const std::string kPageTitle = "title 1";
const GURL kPageUrl = embedded_test_server()->GetURL("/title1.html");
cr_fuchsia::LoadUrlAndExpectResponse(
controller_.get(), fuchsia::web::LoadUrlParams(), kPageUrl.spec());
fuchsia::web::NavigationState state;
state.set_is_main_document_loaded(true);
state.set_title(kPageTitle);
navigation_listener_.RunUntilNavigationStateMatches(state);
}
// Reports the system |theme_type| via the Display FIDL service.
void ReportSystemTheme(fuchsia::settings::ThemeType theme_type) {
if (!watch_callback_) {
DCHECK(!on_watch_closure_);
base::RunLoop run_loop;
on_watch_closure_ = run_loop.QuitClosure();
run_loop.Run();
DCHECK(watch_callback_);
}
fuchsia::settings::DisplaySettings settings;
fuchsia::settings::Theme theme;
theme.set_theme_type(theme_type);
settings.set_theme(std::move(theme));
(*watch_callback_)(std::move(settings));
watch_callback_ = base::nullopt;
base::RunLoop().RunUntilIdle();
}
// Returns the name of the color scheme selected by the CSS feature matcher.
base::StringPiece QueryThemeFromCssFeature() {
content::WebContents* web_contents =
context_impl()->GetFrameImplForTest(&frame_)->web_contents_for_test();
for (const char* scheme : {kCssDark, kCssLight}) {
bool matches;
CHECK(ExecuteScriptAndExtractBool(
web_contents,
base::StringPrintf(
"window.domAutomationController.send(window."
"matchMedia('(prefers-color-scheme: %s)').matches)",
scheme),
&matches));
if (matches)
return scheme;
}
NOTREACHED();
return "";
}
bool SetTheme(fuchsia::settings::ThemeType theme) {
frame_->SetPreferredTheme(theme);
base::RunLoop().RunUntilIdle();
return frame_.is_bound();
}
protected:
// fuchsia::settings::Display implementation.
void Watch(WatchCallback callback) final {
watch_callback_ = std::move(callback);
if (on_watch_closure_)
std::move(on_watch_closure_).Run();
}
void NotImplemented_(const std::string&) final {}
base::Optional<base::TestComponentContextForProcess> component_context_;
base::Optional<
base::fuchsia::ScopedServiceBinding<fuchsia::settings::Display>>
display_binding_;
cr_fuchsia::TestNavigationListener navigation_listener_;
fuchsia::web::NavigationControllerPtr controller_;
fuchsia::web::FramePtr frame_;
base::OnceClosure on_watch_closure_;
base::Optional<WatchCallback> watch_callback_;
};
IN_PROC_BROWSER_TEST_F(ThemeManagerTest, Default) {
EXPECT_EQ(QueryThemeFromCssFeature(), kCssLight);
}
IN_PROC_BROWSER_TEST_F(ThemeManagerTest, LightAndDarkRequested) {
EXPECT_TRUE(SetTheme(fuchsia::settings::ThemeType::DARK));
EXPECT_EQ(QueryThemeFromCssFeature(), kCssDark);
EXPECT_TRUE(SetTheme(fuchsia::settings::ThemeType::LIGHT));
EXPECT_EQ(QueryThemeFromCssFeature(), kCssLight);
}
IN_PROC_BROWSER_TEST_F(ThemeManagerTest, UseDisplayService) {
frame_->SetPreferredTheme(fuchsia::settings::ThemeType::AUTO);
base::RunLoop().RunUntilIdle();
ReportSystemTheme(fuchsia::settings::ThemeType::DARK);
EXPECT_EQ(QueryThemeFromCssFeature(), kCssDark);
}
// Verify that the Frame connection will drop if the Display service is
// required but missing.
// TODO(crbug.com/1148454): Re-enable this test once the service availability
// validation is back in place.
IN_PROC_BROWSER_TEST_F(ThemeManagerTest, DISABLED_AutoWithMissingService) {
frame_->SetPreferredTheme(fuchsia::settings::ThemeType::AUTO);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(display_binding_->has_clients());
display_binding_ = base::nullopt;
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(display_binding_);
ASSERT_FALSE(frame_);
}
// Verify that invalid values from the Display service, such as AUTO,
// are discarded in lieu of the fallback light theme.
IN_PROC_BROWSER_TEST_F(ThemeManagerTest, HandleBadInputFromDisplayService) {
frame_->SetPreferredTheme(fuchsia::settings::ThemeType::AUTO);
ReportSystemTheme(fuchsia::settings::ThemeType::AUTO);
EXPECT_TRUE(SetTheme(fuchsia::settings::ThemeType::AUTO));
EXPECT_EQ(QueryThemeFromCssFeature(), kCssLight);
ReportSystemTheme(fuchsia::settings::ThemeType::DEFAULT);
EXPECT_EQ(QueryThemeFromCssFeature(), kCssLight);
}
} // namespace
......@@ -148,6 +148,9 @@ void CastComponent::StartComponent() {
kBindingsFailureExitCode,
fuchsia::sys::TerminationReason::INTERNAL_ERROR));
// Get the theme from the system service.
frame()->SetPreferredTheme(fuchsia::settings::ThemeType::AUTO);
// Media loading has to be unblocked by the agent via the
// ApplicationController.
frame()->SetBlockMediaLoading(true);
......
......@@ -43,6 +43,7 @@ static constexpr const char* kServices[] = {
"fuchsia.netstack.Netstack",
"fuchsia.posix.socket.Provider",
"fuchsia.process.Launcher",
"fuchsia.settings.Display",
"fuchsia.sysmem.Allocator",
"fuchsia.ui.input.ImeService",
"fuchsia.ui.input.ImeVisibilityService",
......
......@@ -23,6 +23,7 @@
"fuchsia.netstack.Netstack",
"fuchsia.posix.socket.Provider",
"fuchsia.process.Launcher",
"fuchsia.settings.Display",
"fuchsia.sysmem.Allocator",
"fuchsia.ui.input.ImeService",
"fuchsia.ui.input.ImeVisibilityService",
......
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