blob: a46896fc56378dc5b188ee00f441ad9d57097994 [file] [log] [blame]
// Copyright 2021 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 "chrome/browser/chrome_browser_main_parts_fuchsia.h"
#include <fuchsia/ui/app/cpp/fidl.h>
#include <fuchsia/ui/composition/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/ui/scenic/cpp/commands.h>
#include <lib/ui/scenic/cpp/resources.h>
#include <lib/ui/scenic/cpp/session.h>
#include <lib/ui/scenic/cpp/view_identity.h>
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/check.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/process_context.h"
#include "base/fuchsia/process_lifecycle.h"
#include "base/fuchsia/scoped_service_binding.h"
#include "base/notreached.h"
#include "base/numerics/clamped_math.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "ui/gfx/geometry/size.h"
#include "ui/ozone/public/ozone_switches.h"
#include "ui/platform_window/fuchsia/initialize_presenter_api_view.h"
namespace {
// Checks the supported ozone platform with Scenic if no arg is specified.
// TODO(crbug.com/1230150): Delete this after Flatland migration is completed.
void HandleOzonePlatformArgs() {
base::CommandLine* const launch_args = base::CommandLine::ForCurrentProcess();
if (launch_args->HasSwitch(switches::kOzonePlatform))
return;
fuchsia::ui::scenic::ScenicSyncPtr scenic;
zx_status_t status =
base::ComponentContextForProcess()->svc()->Connect(scenic.NewRequest());
if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "Couldn't connect to Scenic.";
return;
}
bool scenic_uses_flatland = false;
scenic->UsesFlatland(&scenic_uses_flatland);
launch_args->AppendSwitchNative(switches::kOzonePlatform,
scenic_uses_flatland ? "flatland" : "scenic");
}
fuchsia::ui::views::ViewRef CloneViewRef(
const fuchsia::ui::views::ViewRef& view_ref) {
fuchsia::ui::views::ViewRef dup;
zx_status_t status =
view_ref.reference.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup.reference);
ZX_CHECK(status == ZX_OK, status) << "zx_object_duplicate";
return dup;
}
// ViewProviderScenic ----------------------------------------------------------
// ViewProvider implementation that provides a single view and exposes all
// requested views from OzonePlatformScenic inside it. This class owns the top
// level Scenic session.
// TODO(crbug.com/1230150): Delete ViewProviderScenic after Flatland migration
// is completed.
class ViewProviderScenic : public fuchsia::ui::app::ViewProvider {
public:
ViewProviderScenic()
: scenic_(base::ComponentContextForProcess()
->svc()
->Connect<fuchsia::ui::scenic::Scenic>()),
scenic_session_(scenic_.get(), focuser_.NewRequest()) {
// This is safe since the callback is overwritten in dtor.
ui::fuchsia::SetScenicViewPresenter(base::BindRepeating(
&ViewProviderScenic::PresentView, base::Unretained(this)));
scenic_.set_error_handler([](zx_status_t status) {
ZX_LOG(ERROR, status) << " Scenic lost.";
// Terminate here so that e.g. a Scenic crash results in the browser
// immediately terminating, without generating a cascading crash report.
base::Process::TerminateCurrentProcessImmediately(1);
});
scenic_session_.set_event_handler(
fit::bind_member(this, &ViewProviderScenic::OnScenicEvents));
}
ViewProviderScenic(const ViewProviderScenic&) = delete;
ViewProviderScenic& operator=(const ViewProviderScenic&) = delete;
~ViewProviderScenic() override {
ui::fuchsia::SetScenicViewPresenter(
ui::fuchsia::ScenicPresentViewCallback());
scenic_.Unbind();
}
void PresentView(fuchsia::ui::views::ViewHolderToken view_holder_token,
fuchsia::ui::views::ViewRef view_ref) {
ScenicSubViewData subview = {
.view_holder = scenic::ViewHolder(&scenic_session_,
std::move(view_holder_token).value,
"subview-holder"),
.view_ref = std::move(view_ref)};
if (view_) {
if (view_properties_) {
subview.view_holder.SetViewProperties(*view_properties_);
}
node_->AddChild(subview.view_holder);
Present();
}
subviews_.push_back(std::move(subview));
}
// fuchsia::ui::app::ViewProvider overrides.
void CreateView(
zx::eventpair token,
fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services,
fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services)
override {
CreateViewWithViewRef(std::move(token), {}, {});
}
void CreateViewWithViewRef(zx::eventpair token,
fuchsia::ui::views::ViewRefControl control_ref,
fuchsia::ui::views::ViewRef view_ref) override {
if (view_) {
LOG(WARNING) << "Unexpected spurious call to CreateViewWithViewRef(). "
"Deleting previously created view.";
subviews_.clear();
is_node_attached_ = false;
view_has_focus_ = false;
node_.reset();
view_.reset();
view_properties_ = absl::nullopt;
}
view_ = std::make_unique<scenic::View>(
&scenic_session_, fuchsia::ui::views::ViewToken({std::move(token)}),
std::move(control_ref), std::move(view_ref), "root-view");
node_ = std::make_unique<scenic::EntityNode>(&scenic_session_);
for (auto& subview : subviews_) {
node_->AddChild(subview.view_holder);
}
Present();
}
void CreateView2(fuchsia::ui::app::CreateView2Args view_args) override {
// Unexpected call to CreateView2(). OzonePlatformScenic cannot handle
// Flatland tokens. Make sure the correct Ozone platform is set.
NOTREACHED();
}
private:
struct ScenicSubViewData {
scenic::ViewHolder view_holder;
fuchsia::ui::views::ViewRef view_ref;
};
void OnScenicEvents(std::vector<fuchsia::ui::scenic::Event> events) {
for (const auto& event : events) {
if (event.is_gfx() && event.gfx().is_view_properties_changed()) {
if (event.gfx().view_properties_changed().view_id != view_->id()) {
LOG(WARNING) << "Received event for unknown view.";
return;
}
UpdateViewProperties(event.gfx().view_properties_changed().properties);
} else if (event.is_input() && event.input().is_focus()) {
view_has_focus_ = event.input().focus().focused;
FocusView();
}
}
}
void UpdateViewProperties(
const fuchsia::ui::gfx::ViewProperties& view_properties) {
const float width =
view_properties.bounding_box.max.x - view_properties.bounding_box.min.x;
const float height =
view_properties.bounding_box.max.y - view_properties.bounding_box.min.y;
if (width == 0 || height == 0) {
if (is_node_attached_) {
node_->Detach();
is_node_attached_ = false;
}
} else {
if (!is_node_attached_) {
view_->AddChild(*node_);
is_node_attached_ = true;
}
}
view_properties_ = view_properties;
for (auto& subview : subviews_) {
subview.view_holder.SetViewProperties(*view_properties_);
}
Present();
}
void FocusView(int tries = 2) {
if (tries == 0) {
LOG(ERROR) << "Unable to pass focus to chrome window.";
return;
}
if (!subviews_.empty() && view_has_focus_) {
focuser_->RequestFocus({CloneViewRef(subviews_.front().view_ref)},
[this, tries](auto result) {
if (result.is_err()) {
FocusView(tries - 1);
}
});
}
}
void Present() {
scenic_session_.Present(
zx_clock_get_monotonic(),
[this](fuchsia::images::PresentationInfo info) { FocusView(); });
}
fuchsia::ui::scenic::ScenicPtr scenic_;
fuchsia::ui::views::FocuserPtr focuser_;
scenic::Session scenic_session_;
// The view created by this ViewProvider. The view is created lazily when a
// request is received.
std::unique_ptr<scenic::View> view_;
// Entity node for the |view_|.
std::unique_ptr<scenic::EntityNode> node_;
// True if the root EntityNode has been added to the View.
bool is_node_attached_ = false;
// True is the root view has focus.
bool view_has_focus_ = false;
// The holders for all the views that are presented.
std::vector<ScenicSubViewData> subviews_;
// The properties of the top level view. They are forwarded to the embedded
// views.
absl::optional<fuchsia::ui::gfx::ViewProperties> view_properties_;
};
// ViewProviderFlatland --------------------------------------------------------
// ViewProvider implementation that provides a single view and exposes all
// requested views from OzonePlatformFlatland inside it. This class owns the top
// level Flatland session.
class ViewProviderFlatland : public fuchsia::ui::app::ViewProvider {
public:
ViewProviderFlatland() {
// This is safe since the callback is overwritten in dtor.
ui::fuchsia::SetFlatlandViewPresenter(base::BindRepeating(
&ViewProviderFlatland::PresentView, base::Unretained(this)));
flatland_.set_error_handler([](zx_status_t status) {
ZX_LOG(ERROR, status) << " Flatland server disconnected.";
// Terminate here so that e.g. a Scenic crash results in the browser
// immediately terminating, without generating a cascading crash report.
base::Process::TerminateCurrentProcessImmediately(1);
});
flatland_->SetDebugName("ChromeBrowserMainPartsFuchsia");
flatland_.events().OnError =
[](fuchsia::ui::composition::FlatlandError error) {
LOG(ERROR) << "Flatland error: " << static_cast<int>(error);
};
flatland_.events().OnNextFrameBegin =
fit::bind_member(this, &ViewProviderFlatland::OnNextFrameBegin);
// Each Flatland session requires defining a root transform.
flatland_->CreateTransform(kRootTransformId);
flatland_->SetRootTransform(kRootTransformId);
}
ViewProviderFlatland(const ViewProviderFlatland&) = delete;
ViewProviderFlatland& operator=(const ViewProviderFlatland&) = delete;
~ViewProviderFlatland() override {
ui::fuchsia::SetFlatlandViewPresenter(
ui::fuchsia::FlatlandPresentViewCallback());
}
// fuchsia::ui::app::ViewProvider overrides.
void CreateView(
zx::eventpair token,
fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services,
fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services)
override {
// Unexpected call to CreateView(). OzonePlatformFlatland cannot handle Gfx
// tokens. Make sure the correct Ozone platform is set.
NOTREACHED();
}
void CreateViewWithViewRef(zx::eventpair token,
fuchsia::ui::views::ViewRefControl control_ref,
fuchsia::ui::views::ViewRef view_ref) override {
// Unexpected call to CreateViewWithViewRef(). OzonePlatformFlatland cannot
// handle Gfx tokens. Make sure the correct Ozone platform is set.
NOTREACHED();
}
void CreateView2(fuchsia::ui::app::CreateView2Args view_args) override {
if (parent_viewport_watcher_.is_bound()) {
LOG(ERROR) << "Unexpected spurious call to CreateView2().";
return;
}
auto view_identity = scenic::NewViewIdentityOnCreation();
fuchsia::ui::composition::ViewBoundProtocols flatland_view_protocols;
flatland_view_protocols.set_view_ref_focused(
view_ref_focused_.NewRequest());
flatland_view_protocols.set_view_focuser(focuser_.NewRequest());
flatland_->CreateView2(std::move(*view_args.mutable_view_creation_token()),
std::move(view_identity),
std::move(flatland_view_protocols),
parent_viewport_watcher_.NewRequest());
for (auto& subview : subviews_) {
flatland_->AddChild(kRootTransformId, subview.transform_id);
}
// No need to call Present() because OnGetLayout() will trigger Present()
// after receiving the proper size.
parent_viewport_watcher_->GetLayout(
fit::bind_member(this, &ViewProviderFlatland::OnGetLayout));
view_ref_focused_->Watch(
fit::bind_member(this, &ViewProviderFlatland::OnViewRefFocused));
}
void PresentView(
fuchsia::ui::views::ViewportCreationToken viewport_creation_token) {
// Flatland requires a size to be set in CreateViewport() calls, so wait for
// receiving size before handling the presentation request. Flatland
// guarantees that the size returned in OnGetLayout() hanging get is
// non-zero. Size can be zero if we are running this code before
// OnGetLayout().
if (view_size_.IsZero()) {
pending_present_views_.push_back(std::move(viewport_creation_token));
return;
}
FlatlandSubViewData subview;
subview.transform_id = {next_id_++};
subview.content_id = {next_id_++};
flatland_->CreateTransform(subview.transform_id);
fuchsia::ui::composition::ViewportProperties properties;
properties.set_logical_size({static_cast<uint32_t>(view_size_.width()),
static_cast<uint32_t>(view_size_.height())});
flatland_->CreateViewport(
subview.content_id, std::move(viewport_creation_token),
std::move(properties), subview.child_view_watcher.NewRequest());
flatland_->SetContent(subview.transform_id, subview.content_id);
flatland_->AddChild(kRootTransformId, subview.transform_id);
subviews_.push_back(std::move(subview));
MaybePresent();
subviews_.back().child_view_watcher->GetViewRef(
[this, index = subviews_.size() - 1](fuchsia::ui::views::ViewRef ref) {
subviews_[index].child_view_ref = std::move(ref);
RequestFocus();
});
}
private:
struct FlatlandSubViewData {
fuchsia::ui::composition::TransformId transform_id;
fuchsia::ui::composition::ContentId content_id;
fuchsia::ui::composition::ChildViewWatcherPtr child_view_watcher;
fuchsia::ui::views::ViewRef child_view_ref;
};
void OnGetLayout(fuchsia::ui::composition::LayoutInfo info) {
const bool first_received_size = view_size_.IsZero();
view_size_.SetSize(info.logical_size().width, info.logical_size().height);
// If there were PresentView() calls before receiving OnGetLayout(), execute
// them.
if (first_received_size) {
for (auto& present_view : std::exchange(pending_present_views_, {})) {
PresentView(std::move(present_view));
}
} else {
for (const auto& subview : subviews_) {
fuchsia::ui::composition::ViewportProperties properties;
properties.set_logical_size(info.logical_size());
flatland_->SetViewportProperties(subview.content_id,
std::move(properties));
}
MaybePresent();
}
// Queue another hanging get in case the size changes.
parent_viewport_watcher_->GetLayout(
fit::bind_member(this, &ViewProviderFlatland::OnGetLayout));
}
void OnViewRefFocused(fuchsia::ui::views::FocusState focus_state) {
view_has_focus_ = focus_state.focused();
RequestFocus();
view_ref_focused_->Watch(
fit::bind_member(this, &ViewProviderFlatland::OnViewRefFocused));
}
void RequestFocus() {
if (!view_has_focus_ || subviews_.empty() ||
!subviews_.front().child_view_ref.reference.is_valid())
return;
focuser_->RequestFocus(
CloneViewRef(subviews_.front().child_view_ref),
[](fuchsia::ui::views::Focuser_RequestFocus_Result result) {
DCHECK(!result.is_err());
});
}
void MaybePresent() {
if (present_credits_ == 0) {
present_after_receiving_credits_ = true;
return;
}
present_after_receiving_credits_ = false;
--present_credits_;
Present();
}
void Present() {
fuchsia::ui::composition::PresentArgs present_args;
present_args.set_requested_presentation_time(0);
present_args.set_acquire_fences({});
present_args.set_release_fences({});
present_args.set_unsquashable(false);
flatland_->Present(std::move(present_args));
}
void OnNextFrameBegin(
fuchsia::ui::composition::OnNextFrameBeginValues values) {
present_credits_ =
base::ClampAdd(present_credits_, values.additional_present_credits());
if (present_after_receiving_credits_) {
MaybePresent();
}
}
fuchsia::ui::composition::FlatlandPtr flatland_ = {
base::ComponentContextForProcess()
->svc()
->Connect<fuchsia::ui::composition::Flatland>()};
// The counter used for limiting the number of Present calls to ensure that
// |flatland_|will not be shut down because if presenting more times than
// allowed.
uint32_t present_credits_ = 1;
// Root transform for |flatland_|. All |subviews_| are added as children.
static const fuchsia::ui::composition::TransformId kRootTransformId;
// Autoincrementing value of the next ID to use for |flatland_|.
uint64_t next_id_ = 2;
// True if we should queue Present() after receiving credits on
// OnNextFrameBegin().
bool present_after_receiving_credits_ = false;
// True if the top level view has focus.
bool view_has_focus_ = false;
// Protocol for watching focus changes.
fuchsia::ui::views::ViewRefFocusedPtr view_ref_focused_;
// Protocol for setting focus changes.
fuchsia::ui::views::FocuserPtr focuser_;
// Protocol for watching size changes.
fuchsia::ui::composition::ParentViewportWatcherPtr parent_viewport_watcher_;
// The layout size of the View occupied by |flatland_| in logical pixels.
gfx::Size view_size_;
// Pending ViewportCreationTokens to be processed and added as |subviews_|.
std::vector<fuchsia::ui::views::ViewportCreationToken> pending_present_views_;
// The holders for all the views that are presented.
std::vector<FlatlandSubViewData> subviews_;
};
// static
constexpr fuchsia::ui::composition::TransformId
ViewProviderFlatland::kRootTransformId{1};
} // namespace
// ViewProviderRouter ----------------------------------------------------------
// ViewProvider implementation that delegates calls to the correct Ozone
// platform's ViewProvider.
// TODO(crbug.com/1230150): Delete ViewProviderRouter after moving |binding_|
// to ViewProviderFlatland after migration is completed.
class ChromeBrowserMainPartsFuchsia::ViewProviderRouter
: public fuchsia::ui::app::ViewProvider {
public:
ViewProviderRouter(std::unique_ptr<ViewProviderScenic> scenic,
std::unique_ptr<ViewProviderFlatland> flatland)
: scenic_(std::move(scenic)), flatland_(std::move(flatland)) {
DCHECK(scenic_);
DCHECK(flatland_);
}
ViewProviderRouter(const ViewProviderRouter&) = delete;
ViewProviderRouter& operator=(const ViewProviderRouter&) = delete;
~ViewProviderRouter() override = default;
// fuchsia::ui::app::ViewProvider overrides.
void CreateView(
zx::eventpair token,
fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services,
fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services)
override {
// Chrome is initialized with either Scenic or flatland Ozone platform.
// Scenic and Flatland calls cannot be combined.
DCHECK(scenic_);
flatland_.reset();
scenic_->CreateView(std::move(token), std::move(incoming_services),
std::move(outgoing_services));
}
void CreateViewWithViewRef(zx::eventpair token,
fuchsia::ui::views::ViewRefControl control_ref,
fuchsia::ui::views::ViewRef view_ref) override {
// Chrome is initialized with either Scenic or flatland Ozone platform.
// Scenic and Flatland calls cannot be combined.
DCHECK(scenic_);
flatland_.reset();
scenic_->CreateViewWithViewRef(std::move(token), std::move(control_ref),
std::move(view_ref));
}
void CreateView2(fuchsia::ui::app::CreateView2Args view_args) override {
// Chrome is initialized with either Scenic or flatland Ozone platform.
// Scenic and Flatland calls cannot be combined.
DCHECK(flatland_);
scenic_.reset();
flatland_->CreateView2(std::move(view_args));
}
private:
const base::ScopedServiceBinding<fuchsia::ui::app::ViewProvider> binding_ = {
base::ComponentContextForProcess()->outgoing().get(), this};
std::unique_ptr<ViewProviderScenic> scenic_;
std::unique_ptr<ViewProviderFlatland> flatland_;
};
// ChromeBrowserMainPartsFuchsia -----------------------------------------------
ChromeBrowserMainPartsFuchsia::ChromeBrowserMainPartsFuchsia(
content::MainFunctionParams parameters,
StartupData* startup_data)
: ChromeBrowserMainParts(std::move(parameters), startup_data) {}
ChromeBrowserMainPartsFuchsia::~ChromeBrowserMainPartsFuchsia() = default;
void ChromeBrowserMainPartsFuchsia::ShowMissingLocaleMessageBox() {
// Locale data should be bundled for all possible platform locales,
// so crash here to make missing-locale states more visible.
CHECK(false);
}
int ChromeBrowserMainPartsFuchsia::PreEarlyInitialization() {
HandleOzonePlatformArgs();
return ChromeBrowserMainParts::PreEarlyInitialization();
}
int ChromeBrowserMainPartsFuchsia::PreMainMessageLoopRun() {
// Register the ViewProvider API.
view_provider_ = std::make_unique<ViewProviderRouter>(
std::make_unique<ViewProviderScenic>(),
std::make_unique<ViewProviderFlatland>());
zx_status_t status =
base::ComponentContextForProcess()->outgoing()->ServeFromStartupInfo();
ZX_CHECK(status == ZX_OK, status);
// Publish the fuchsia.process.lifecycle.Lifecycle service to allow graceful
// teardown. If there is a |ui_task| then this is a browser-test and graceful
// shutdown is not required.
if (!parameters().ui_task) {
lifecycle_ = std::make_unique<base::ProcessLifecycle>(
base::BindOnce(&chrome::ExitIgnoreUnloadHandlers));
}
return ChromeBrowserMainParts::PreMainMessageLoopRun();
}
void ChromeBrowserMainPartsFuchsia::PostMainMessageLoopRun() {
// |view_provider_| owns ViewProviderScenic and ViewProviderFlatland. They own
// the Scenic channels so resetting here will unbind.
view_provider_.reset();
ChromeBrowserMainParts::PostMainMessageLoopRun();
}