Commit 81b320db authored by Kevin Marshall's avatar Kevin Marshall Committed by Commit Bot

[Fuchsia] Support headless mode.

Allows for creation of HEADLESS Contexts, for browsing sessions without
graphical output.

Implements EnableHeadlessRendering() and DisableHeadlessRendering() on
Frames, which is used for processing DOM animation events in non-graphical
Contexts.

Bug: 1025045
Change-Id: I2cd8e428f4b6eadaafe17631b36b6d42b8d4ea25
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1918036Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarWez <wez@chromium.org>
Reviewed-by: default avatarSami Kyöstilä <skyostil@chromium.org>
Commit-Queue: Kevin Marshall <kmarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#721585}
parent 2904bab7
......@@ -102,6 +102,7 @@ component("web_engine_core") {
"//ui/base",
"//ui/base/ime",
"//ui/display",
"//ui/gfx",
"//ui/ozone",
"//ui/platform_window",
"//ui/wm",
......@@ -252,6 +253,7 @@ test("web_engine_browsertests") {
"browser/content_directory_browsertest.cc",
"browser/context_impl_browsertest.cc",
"browser/frame_impl_browsertest.cc",
"browser/headless_browsertest.cc",
"browser/media_browsertest.cc",
]
defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
......@@ -268,6 +270,7 @@ test("web_engine_browsertests") {
"//testing/gtest",
"//third_party/fuchsia-sdk/sdk:fuchsia-accessibility-semantics",
"//third_party/fuchsia-sdk/sdk:scenic_cpp",
"//ui/gfx",
"//ui/ozone",
]
}
......
......@@ -8,5 +8,7 @@ include_rules = [
"+services/service_manager",
"+third_party/widevine/cdm/widevine_cdm_common.h",
"+ui/base",
"+ui/gfx",
"+ui/gl/gl_switches.h",
"+ui/ozone/public",
]
......@@ -38,6 +38,8 @@
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host_platform.h"
#include "ui/base/ime/input_method_base.h"
#include "ui/gfx/switches.h"
#include "ui/ozone/public/ozone_switches.h"
#include "ui/platform_window/platform_window_init_properties.h"
#include "ui/wm/core/base_focus_rules.h"
#include "url/gurl.h"
......@@ -223,6 +225,10 @@ logging::LogSeverity ConsoleLogLevelToLoggingSeverity(
level);
}
bool IsHeadless() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kHeadless);
}
} // namespace
FrameImpl::FrameImpl(std::unique_ptr<content::WebContents> web_contents,
......@@ -248,7 +254,7 @@ FrameImpl::FrameImpl(std::unique_ptr<content::WebContents> web_contents,
}
FrameImpl::~FrameImpl() {
TearDownView();
DestroyWindowTreeHost();
context_->devtools_controller()->OnFrameDestroyed(web_contents_.get());
}
......@@ -272,23 +278,6 @@ FrameImpl::OriginScopedScript& FrameImpl::OriginScopedScript::operator=(
FrameImpl::OriginScopedScript::~OriginScopedScript() = default;
void FrameImpl::TearDownView() {
if (window_tree_host_) {
aura::client::SetFocusClient(root_window(), nullptr);
wm::SetActivationClient(root_window(), nullptr);
root_window()->RemovePreTargetHandler(focus_controller_.get());
web_contents_->GetNativeView()->Hide();
window_tree_host_->Hide();
window_tree_host_->compositor()->SetVisible(false);
window_tree_host_ = nullptr;
// Allows posted focus events to process before the FocusController is torn
// down.
base::DeleteSoon(FROM_HERE, {content::BrowserThread::UI},
std::move(focus_controller_));
}
}
void FrameImpl::ExecuteJavaScriptInternal(std::vector<std::string> origins,
fuchsia::mem::Buffer script,
ExecuteJavaScriptCallback callback,
......@@ -454,6 +443,30 @@ void FrameImpl::MaybeSendPopup() {
popup_ack_outstanding_ = true;
}
void FrameImpl::DestroyWindowTreeHost() {
if (!window_tree_host_)
return;
aura::client::SetFocusClient(root_window(), nullptr);
wm::SetActivationClient(root_window(), nullptr);
root_window()->RemovePreTargetHandler(focus_controller_.get());
web_contents_->GetNativeView()->Hide();
window_tree_host_->Hide();
window_tree_host_->compositor()->SetVisible(false);
window_tree_host_.reset();
// Allows posted focus events to process before the FocusController is torn
// down.
base::DeleteSoon(FROM_HERE, {content::BrowserThread::UI},
std::move(focus_controller_));
}
void FrameImpl::CloseAndDestroyFrame(zx_status_t error) {
DCHECK(binding_.is_bound());
binding_.Close(error);
context_->DestroyFrame(this);
}
void FrameImpl::OnPopupListenerDisconnected(zx_status_t status) {
ZX_LOG_IF(WARNING, status != ZX_ERR_PEER_CLOSED, status)
<< "Popup listener disconnected.";
......@@ -461,8 +474,14 @@ void FrameImpl::OnPopupListenerDisconnected(zx_status_t status) {
}
void FrameImpl::CreateView(fuchsia::ui::views::ViewToken view_token) {
if (IsHeadless()) {
LOG(WARNING) << "CreateView() called on a HEADLESS Context.";
CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
return;
}
// If a View to this Frame is already active then disconnect it.
TearDownView();
DestroyWindowTreeHost();
ui::PlatformWindowInitProperties properties;
properties.view_token = std::move(view_token);
......@@ -489,29 +508,8 @@ void FrameImpl::CreateView(fuchsia::ui::views::ViewToken view_token) {
ZX_LOG(ERROR, status) << "zx_object_duplicate";
}
window_tree_host_ =
std::make_unique<FrameWindowTreeHost>(std::move(properties));
window_tree_host_->InitHost();
window_tree_host_->window()->GetHost()->AddEventRewriter(
&discarding_event_filter_);
focus_controller_ =
std::make_unique<wm::FocusController>(new FrameFocusRules);
aura::client::SetFocusClient(root_window(), focus_controller_.get());
wm::SetActivationClient(root_window(), focus_controller_.get());
// Add hooks which automatically set the focus state when input events are
// received.
root_window()->AddPreTargetHandler(focus_controller_.get());
// Track child windows for enforcement of window management policies and
// propagate window manager events to them (e.g. window resizing).
root_window()->SetLayoutManager(new LayoutManagerImpl());
root_window()->AddChild(web_contents_->GetNativeView());
web_contents_->GetNativeView()->Show();
window_tree_host_->Show();
SetWindowTreeHost(
std::make_unique<FrameWindowTreeHost>(std::move(properties)));
}
void FrameImpl::GetNavigationController(
......@@ -694,8 +692,48 @@ void FrameImpl::SetUrlRequestRewriteRules(
SetUrlRequestRewriteRulesCallback callback) {
zx_status_t error = url_request_rewrite_rules_manager_.OnRulesUpdated(
std::move(rules), std::move(callback));
if (error != ZX_OK)
binding_.Close(error);
if (error != ZX_OK) {
CloseAndDestroyFrame(error);
return;
}
}
void FrameImpl::EnableHeadlessRendering() {
if (!IsHeadless()) {
LOG(ERROR) << "EnableHeadlessRendering() on non-HEADLESS Context.";
CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
return;
}
SetWindowTreeHost(std::make_unique<FrameWindowTreeHost>(
ui::PlatformWindowInitProperties()));
}
void FrameImpl::DisableHeadlessRendering() {
if (!IsHeadless()) {
LOG(ERROR)
<< "Attempted to disable headless rendering on non-HEADLESS Context.";
CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
return;
}
DestroyWindowTreeHost();
}
void FrameImpl::SetWindowTreeHost(
std::unique_ptr<aura::WindowTreeHost> window_tree_host) {
DCHECK(!window_tree_host_);
window_tree_host_ = std::move(window_tree_host);
window_tree_host_->InitHost();
focus_controller_ =
std::make_unique<wm::FocusController>(new FrameFocusRules);
aura::client::SetFocusClient(root_window(), focus_controller_.get());
wm::SetActivationClient(root_window(), focus_controller_.get());
root_window()->SetLayoutManager(new LayoutManagerImpl());
root_window()->AddChild(web_contents_->GetNativeView());
web_contents_->GetNativeView()->Show();
window_tree_host_->Show();
}
void FrameImpl::CloseContents(content::WebContents* source) {
......
......@@ -96,9 +96,6 @@ class FrameImpl : public fuchsia::web::Frame,
aura::Window* root_window() const { return window_tree_host_->window(); }
// Release the resources associated with the View, if one is active.
void TearDownView();
// Shared implementation for the ExecuteJavaScript[NoResult]() APIs.
void ExecuteJavaScriptInternal(std::vector<std::string> origins,
fuchsia::mem::Buffer script,
......@@ -110,6 +107,18 @@ class FrameImpl : public fuchsia::web::Frame,
void OnPopupListenerDisconnected(zx_status_t status);
// Sets the WindowTreeHost to use for this Frame. Only one WindowTreeHost can
// be set at a time.
void SetWindowTreeHost(
std::unique_ptr<aura::WindowTreeHost> window_tree_host);
// Destroys the WindowTreeHost along with its view or other associated
// resources.
void DestroyWindowTreeHost();
// Destroys |this| and sends the FIDL |error| to the client.
void CloseAndDestroyFrame(zx_status_t error);
// fuchsia::web::Frame implementation.
void CreateView(fuchsia::ui::views::ViewToken view_token) override;
void GetNavigationController(
......@@ -142,6 +151,8 @@ class FrameImpl : public fuchsia::web::Frame,
void SetUrlRequestRewriteRules(
std::vector<fuchsia::web::UrlRequestRewriteRule> rules,
SetUrlRequestRewriteRulesCallback callback) override;
void EnableHeadlessRendering() override;
void DisableHeadlessRendering() override;
// content::WebContentsDelegate implementation.
void CloseContents(content::WebContents* source) override;
......
// Copyright 2018 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 "base/auto_reset.h"
#include "base/macros.h"
#include "base/test/scoped_command_line.h"
#include "fuchsia/base/frame_test_util.h"
#include "fuchsia/base/test_navigation_listener.h"
#include "fuchsia/engine/switches.h"
#include "fuchsia/engine/test/test_data.h"
#include "fuchsia/engine/test/web_engine_browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/switches.h"
#include "ui/ozone/public/ozone_switches.h"
namespace {
// Defines a suite of tests that exercise browser-level configuration and
// functionality.
class HeadlessTest : public cr_fuchsia::WebEngineBrowserTest {
public:
HeadlessTest() {
set_test_server_root(base::FilePath(cr_fuchsia::kTestServerRoot));
}
~HeadlessTest() override = default;
protected:
void SetUp() override {
command_line_.GetProcessCommandLine()->AppendSwitchNative(
switches::kOzonePlatform, switches::kHeadless);
command_line_.GetProcessCommandLine()->AppendSwitch(switches::kHeadless);
cr_fuchsia::WebEngineBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
frame_ = CreateFrame();
frame_->GetNavigationController(controller_.NewRequest());
}
// Creates a Frame with |navigation_listener_| attached.
//
fuchsia::web::FramePtr CreateFrame() {
return WebEngineBrowserTest::CreateFrame(&navigation_listener_);
}
fuchsia::web::FramePtr frame_;
fuchsia::web::NavigationControllerPtr controller_;
cr_fuchsia::TestNavigationListener navigation_listener_;
base::test::ScopedCommandLine command_line_;
private:
DISALLOW_COPY_AND_ASSIGN(HeadlessTest);
};
IN_PROC_BROWSER_TEST_F(HeadlessTest, AnimationRunsWhenFrameAlreadyActive) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kAnimationUrl(
embedded_test_server()->GetURL("/css_animation.html"));
frame_->EnableHeadlessRendering();
cr_fuchsia::LoadUrlAndExpectResponse(
controller_.get(), fuchsia::web::LoadUrlParams(), kAnimationUrl.spec());
navigation_listener_.RunUntilUrlAndTitleEquals(kAnimationUrl,
"animation finished");
}
IN_PROC_BROWSER_TEST_F(HeadlessTest, AnimationNeverRunsWhenInactive) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kAnimationUrl(
embedded_test_server()->GetURL("/css_animation.html"));
cr_fuchsia::LoadUrlAndExpectResponse(
controller_.get(), fuchsia::web::LoadUrlParams(), kAnimationUrl.spec());
navigation_listener_.RunUntilUrlAndTitleEquals(kAnimationUrl,
"animation never started");
}
IN_PROC_BROWSER_TEST_F(HeadlessTest, ActivateFrameAfterDocumentLoad) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kAnimationUrl(
embedded_test_server()->GetURL("/css_animation.html"));
cr_fuchsia::LoadUrlAndExpectResponse(
controller_.get(), fuchsia::web::LoadUrlParams(), kAnimationUrl.spec());
navigation_listener_.RunUntilUrlAndTitleEquals(kAnimationUrl,
"animation never started");
frame_->EnableHeadlessRendering();
navigation_listener_.RunUntilUrlAndTitleEquals(kAnimationUrl,
"animation finished");
}
IN_PROC_BROWSER_TEST_F(HeadlessTest, DeactivationPreventsFutureAnimations) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kAnimationUrl(
embedded_test_server()->GetURL("/css_animation.html"));
// Activate the frame and load the page. The animation should run.
frame_->EnableHeadlessRendering();
cr_fuchsia::LoadUrlAndExpectResponse(
controller_.get(), fuchsia::web::LoadUrlParams(), kAnimationUrl.spec());
navigation_listener_.RunUntilUrlAndTitleEquals(kAnimationUrl,
"animation finished");
// Deactivate the page and reload it. The animation should no longer run.
frame_->DisableHeadlessRendering();
controller_->Reload(fuchsia::web::ReloadType::NO_CACHE);
navigation_listener_.RunUntilUrlAndTitleEquals(kAnimationUrl,
"animation never started");
}
} // namespace
......@@ -19,12 +19,16 @@
#include "fuchsia/engine/switches.h"
#include "gpu/command_buffer/service/gpu_switches.h"
#include "ui/aura/screen_ozone.h"
#include "ui/gfx/switches.h"
#include "ui/ozone/public/ozone_platform.h"
#include "ui/ozone/public/ozone_switches.h"
WebEngineBrowserMainParts::WebEngineBrowserMainParts(
const content::MainFunctionParams& parameters,
fidl::InterfaceRequest<fuchsia::web::Context> request)
: parameters_(parameters), request_(std::move(request)) {}
: parameters_(parameters), request_(std::move(request)) {
DCHECK(request_);
}
WebEngineBrowserMainParts::~WebEngineBrowserMainParts() {
display::Screen::SetScreenInstance(nullptr);
......@@ -51,7 +55,6 @@ void WebEngineBrowserMainParts::PreMainMessageLoopRun() {
browser_context_ = std::make_unique<WebEngineBrowserContext>(
base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kIncognito));
DCHECK(request_);
devtools_controller_ = WebEngineDevToolsController::CreateFromCommandLine(
*base::CommandLine::ForCurrentProcess());
context_service_ = std::make_unique<ContextImpl>(browser_context_.get(),
......
......@@ -50,7 +50,9 @@
#include "net/http/http_util.h"
#include "services/service_manager/sandbox/fuchsia/sandbox_policy_fuchsia.h"
#include "third_party/widevine/cdm/widevine_cdm_common.h"
#include "ui/gfx/switches.h"
#include "ui/gl/gl_switches.h"
#include "ui/ozone/public/ozone_switches.h"
namespace {
......@@ -287,7 +289,6 @@ void ContextProviderImpl::Create(
bool enable_vulkan = (features & fuchsia::web::ContextFeatureFlags::VULKAN) ==
fuchsia::web::ContextFeatureFlags::VULKAN;
bool enable_widevine =
(features & fuchsia::web::ContextFeatureFlags::WIDEVINE_CDM) ==
fuchsia::web::ContextFeatureFlags::WIDEVINE_CDM;
......@@ -309,9 +310,23 @@ void ContextProviderImpl::Create(
return;
}
const bool is_headless =
(features & fuchsia::web::ContextFeatureFlags::HEADLESS) ==
fuchsia::web::ContextFeatureFlags::HEADLESS;
if (is_headless) {
launch_command.AppendSwitchNative(switches::kOzonePlatform,
switches::kHeadless);
launch_command.AppendSwitch(switches::kHeadless);
}
if (enable_vulkan) {
DLOG(ERROR) << "Enabling Vulkan GPU acceleration.";
if (is_headless) {
LOG(ERROR) << "VULKAN and HEADLESS features cannot be used together.";
context_request.Close(ZX_ERR_INVALID_ARGS);
return;
}
DLOG(ERROR) << "Enabling Vulkan GPU acceleration.";
// Vulkan requires use of SkiaRenderer, configured to a use Vulkan context.
launch_command.AppendSwitch(switches::kUseVulkan);
launch_command.AppendSwitchASCII(switches::kEnableFeatures,
......
<html>
<head>
<style>
.animated {
left:0px;
width: 100px;
height: 100px;
background-color: red;
animation-name: anim;
animation-duration: 0.25s;
position: relative;
}
/* Standard syntax */
@keyframes anim {
0% {background-color:red; left:0px; top:0px;}
25% {background-color:yellow; left:200px; top:0px;}
50% {background-color:blue; left:200px; top:200px;}
75% {background-color:green; left:0px; top:200px;}
100% {background-color:red; left:0px; top:0px;}
}
</style>
<title>a document</title>
</head>
<body>
<div class="animated" id="animated">hello</div>
<script>
let animated = document.getElementById('animated');
let isStarted = false;
animated.addEventListener('animationstart', function() {
isStarted = true;
});
animated.addEventListener('animationend', function() {
document.title = 'animation finished';
});
// Indicates that no animation has run within a second of document load.
setTimeout(function() {
if (!isStarted)
document.title = 'animation never started';
}, 1000);
</script>
</body>
</html>
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