blob: 886ad70d8c819aa8ae5b3d5309a542a5e59fa386 [file] [log] [blame]
// Copyright 2019 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/runners/cast/cast_component.h"
#include <lib/fidl/cpp/binding.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <algorithm>
#include <string>
#include <utility>
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/path_service.h"
#include "base/task/current_thread.h"
#include "components/cast/message_port/message_port_fuchsia.h"
#include "fuchsia/base/agent_manager.h"
#include "fuchsia/base/mem_buffer_util.h"
#include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
#include "fuchsia/runners/cast/cast_runner.h"
#include "fuchsia/runners/cast/cast_streaming.h"
#include "fuchsia/runners/cast/create_web_message.h"
#include "fuchsia/runners/common/web_component.h"
namespace {
constexpr int kBindingsFailureExitCode = 129;
constexpr int kRewriteRulesProviderDisconnectExitCode = 130;
fuchsia::web::ConsoleLogLevel SeverityToConsoleLogLevel(
fuchsia::diagnostics::Severity severity) {
switch (severity) {
case fuchsia::diagnostics::Severity::TRACE:
case fuchsia::diagnostics::Severity::DEBUG:
return fuchsia::web::ConsoleLogLevel::DEBUG;
case fuchsia::diagnostics::Severity::INFO:
return fuchsia::web::ConsoleLogLevel::INFO;
case fuchsia::diagnostics::Severity::WARN:
return fuchsia::web::ConsoleLogLevel::WARN;
case fuchsia::diagnostics::Severity::ERROR:
return fuchsia::web::ConsoleLogLevel::ERROR;
case fuchsia::diagnostics::Severity::FATAL:
// FATAL means none per the FIDL definition.
return fuchsia::web::ConsoleLogLevel::NONE;
}
// The safest thing to do for unrecognized values is to not log.
return fuchsia::web::ConsoleLogLevel::NONE;
}
} // namespace
CastComponent::Params::Params() = default;
CastComponent::Params::Params(Params&&) = default;
CastComponent::Params::~Params() = default;
bool CastComponent::Params::AreComplete() const {
if (application_config.IsEmpty())
return false;
if (!api_bindings_client->HasBindings())
return false;
if (!initial_url_rewrite_rules)
return false;
if (!media_session_id)
return false;
return true;
}
CastComponent::CastComponent(base::StringPiece debug_name,
WebContentRunner* runner,
CastComponent::Params params,
bool is_headless)
: WebComponent(debug_name,
runner,
std::move(params.startup_context),
std::move(params.controller_request)),
is_headless_(is_headless),
agent_manager_(std::move(params.agent_manager)),
application_config_(std::move(params.application_config)),
url_rewrite_rules_provider_(std::move(params.url_rewrite_rules_provider)),
initial_url_rewrite_rules_(
std::move(params.initial_url_rewrite_rules.value())),
api_bindings_client_(std::move(params.api_bindings_client)),
application_context_(params.application_context.Bind()),
media_session_id_(params.media_session_id.value()),
headless_disconnect_watch_(FROM_HERE) {
base::AutoReset<bool> constructor_active_reset(&constructor_active_, true);
}
CastComponent::~CastComponent() = default;
void CastComponent::SetOnDestroyedCallback(base::OnceClosure on_destroyed) {
on_destroyed_ = std::move(on_destroyed);
}
void CastComponent::StartComponent() {
if (application_config_.has_enable_remote_debugging() &&
application_config_.enable_remote_debugging()) {
WebComponent::EnableRemoteDebugging();
}
WebComponent::StartComponent();
connector_ = std::make_unique<NamedMessagePortConnectorFuchsia>(frame());
url_rewrite_rules_provider_.set_error_handler([this](zx_status_t status) {
ZX_LOG_IF(ERROR, status != ZX_OK, status)
<< "UrlRequestRewriteRulesProvider disconnected.";
DestroyComponent(kRewriteRulesProviderDisconnectExitCode,
fuchsia::sys::TerminationReason::INTERNAL_ERROR);
});
OnRewriteRulesReceived(std::move(initial_url_rewrite_rules_));
frame()->SetMediaSessionId(media_session_id_);
frame()->ConfigureInputTypes(fuchsia::web::InputTypes::ALL,
fuchsia::web::AllowInputState::DENY);
if (application_config_.has_initial_min_console_log_severity()) {
frame()->SetJavaScriptLogLevel(SeverityToConsoleLogLevel(
application_config_.initial_min_console_log_severity()));
}
if (IsAppConfigForCastStreaming(application_config_)) {
// TODO(crbug.com/1082821): Remove this once the Cast Streaming Receiver
// component has been implemented.
// Register the MessagePort for the Cast Streaming Receiver.
std::unique_ptr<cast_api_bindings::MessagePort> message_port_for_web_engine;
std::unique_ptr<cast_api_bindings::MessagePort> message_port_for_agent;
cast_api_bindings::MessagePort::CreatePair(&message_port_for_agent,
&message_port_for_web_engine);
frame()->PostMessage(
kCastStreamingMessagePortOrigin,
CreateWebMessage("", std::move(message_port_for_web_engine)),
[this](fuchsia::web::Frame_PostMessage_Result result) {
if (result.is_err()) {
DestroyComponent(kBindingsFailureExitCode,
fuchsia::sys::TerminationReason::INTERNAL_ERROR);
}
});
api_bindings_client_->OnPortConnected(kCastStreamingMessagePortName,
std::move(message_port_for_agent));
}
api_bindings_client_->AttachToFrame(
frame(), connector_.get(),
base::BindOnce(&CastComponent::DestroyComponent, base::Unretained(this),
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);
if (application_config_.has_force_content_dimensions()) {
frame()->ForceContentDimensions(std::make_unique<fuchsia::ui::gfx::vec2>(
application_config_.force_content_dimensions()));
}
application_controller_ = std::make_unique<ApplicationControllerImpl>(
frame(), application_context_.get());
// Apply application-specific web permissions to the fuchsia.web.Frame.
if (application_config_.has_permissions()) {
// TODO(crbug.com/1136994): Replace this with the PermissionManager API
// when available.
const std::string origin =
GURL(application_config_.web_url()).GetOrigin().spec();
for (auto& permission : application_config_.permissions()) {
fuchsia::web::PermissionDescriptor permission_clone;
zx_status_t status = permission.Clone(&permission_clone);
ZX_DCHECK(status == ZX_OK, status);
const bool all_origins =
permission_clone.has_type() &&
(permission_clone.type() ==
fuchsia::web::PermissionType::PROTECTED_MEDIA_IDENTIFIER);
frame()->SetPermissionState(std::move(permission_clone),
all_origins ? "*" : origin,
fuchsia::web::PermissionState::GRANTED);
}
}
}
void CastComponent::DestroyComponent(int64_t exit_code,
fuchsia::sys::TerminationReason reason) {
DCHECK(!constructor_active_);
std::move(on_destroyed_).Run();
// If the component EXITED then pass the |exit_code| to the Agent, to allow it
// to distinguish graceful termination from crashes.
if (reason == fuchsia::sys::TerminationReason::EXITED &&
application_controller_) {
application_context_->OnApplicationExit(exit_code);
}
// frame() is about to be destroyed, so there is no need to perform cleanup
// such as removing before-load JavaScripts.
api_bindings_client_->DetachFromFrame(frame());
connector_->DetachFromFrame();
WebComponent::DestroyComponent(exit_code, reason);
}
void CastComponent::OnRewriteRulesReceived(
std::vector<fuchsia::web::UrlRequestRewriteRule> rewrite_rules) {
frame()->SetUrlRequestRewriteRules(std::move(rewrite_rules), [this]() {
url_rewrite_rules_provider_->GetUrlRequestRewriteRules(
fit::bind_member(this, &CastComponent::OnRewriteRulesReceived));
});
}
void CastComponent::OnNavigationStateChanged(
fuchsia::web::NavigationState change,
OnNavigationStateChangedCallback callback) {
if (change.has_is_main_document_loaded() &&
change.is_main_document_loaded()) {
std::string connect_message;
std::unique_ptr<cast_api_bindings::MessagePort> connect_port;
connector_->GetConnectMessage(&connect_message, &connect_port);
// Send the NamedMessagePortConnector handshake to the page.
frame()->PostMessage(
"*", CreateWebMessage(connect_message, std::move(connect_port)),
[](fuchsia::web::Frame_PostMessage_Result result) {
DCHECK(result.is_response());
});
}
WebComponent::OnNavigationStateChanged(std::move(change),
std::move(callback));
}
void CastComponent::CreateView(
zx::eventpair view_token,
fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services,
fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services) {
scenic::ViewRefPair view_ref_pair = scenic::ViewRefPair::New();
CreateViewWithViewRef(std::move(view_token),
std::move(view_ref_pair.control_ref),
std::move(view_ref_pair.view_ref));
}
void CastComponent::CreateViewWithViewRef(
zx::eventpair view_token,
fuchsia::ui::views::ViewRefControl control_ref,
fuchsia::ui::views::ViewRef view_ref) {
if (is_headless_) {
// For headless CastComponents, |view_token| does not actually connect to a
// Scenic View. It is merely used as a conduit for propagating termination
// signals.
headless_view_token_ = std::move(view_token);
base::CurrentIOThread::Get()->WatchZxHandle(
headless_view_token_.get(), false /* persistent */,
ZX_SOCKET_PEER_CLOSED, &headless_disconnect_watch_, this);
frame()->EnableHeadlessRendering();
return;
}
WebComponent::CreateViewWithViewRef(
std::move(view_token), std::move(control_ref), std::move(view_ref));
}
void CastComponent::OnZxHandleSignalled(zx_handle_t handle,
zx_signals_t signals) {
DCHECK_EQ(signals, ZX_SOCKET_PEER_CLOSED);
DCHECK(is_headless_);
frame()->DisableHeadlessRendering();
}