// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "fuchsia_web/webengine/browser/context_impl.h"
#include <lib/fpromise/result.h>
#include <lib/zx/channel.h>
#include <lib/zx/handle.h>
#include <memory>
#include <utility>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/koid.h"
#include "base/fuchsia/mem_buffer_util.h"
#include "base/functional/bind.h"
#include "base/not_fatal_until.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/typed_macros.h"
#include "build/chromecast_buildflags.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "fuchsia_web/webengine/browser/frame_impl.h"
#include "fuchsia_web/webengine/browser/trace_event.h"
#include "fuchsia_web/webengine/browser/web_engine_devtools_controller.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "third_party/blink/public/mojom/webpreferences/web_preferences.mojom.h"
#include "third_party/perfetto/include/perfetto/tracing/track_event_args.h"
#if BUILDFLAG(ENABLE_CAST_RECEIVER)
#include "components/cast_streaming/browser/public/network_context_getter.h" // nogncheck
#endif
ContextImpl::ContextImpl(
std::unique_ptr<content::BrowserContext> browser_context,
inspect::Node inspect_node,
WebEngineDevToolsController* devtools_controller)
: browser_context_(std::move(browser_context)),
devtools_controller_(devtools_controller),
inspect_node_(std::move(inspect_node)),
cookie_manager_(base::BindRepeating(&ContextImpl::GetNetworkContext,
base::Unretained(this))) {
DCHECK(browser_context_);
DCHECK(devtools_controller_);
TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Context created",
perfetto::Flow::FromPointer(this));
}
ContextImpl::~ContextImpl() {
TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Context destroyed",
perfetto::TerminatingFlow::FromPointer(this));
}
void ContextImpl::DestroyFrame(FrameImpl* frame) {
auto iter = frames_.find(frame);
CHECK(iter != frames_.end(), base::NotFatalUntil::M130);
frames_.erase(iter);
}
bool ContextImpl::IsJavaScriptInjectionAllowed() {
return allow_javascript_injection_;
}
#if BUILDFLAG(ENABLE_CAST_RECEIVER)
void ContextImpl::SetCastStreamingEnabled() {
cast_streaming_enabled_ = true;
cast_streaming::SetNetworkContextGetter(base::BindRepeating(
&ContextImpl::GetNetworkContext, base::Unretained(this)));
}
#endif
void ContextImpl::CreateFrame(
fidl::InterfaceRequest<fuchsia::web::Frame> frame_request) {
TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Context.CreateFrame",
perfetto::Flow::FromPointer(this));
CreateFrameWithParams(fuchsia::web::CreateFrameParams(),
std::move(frame_request));
}
void ContextImpl::CreateFrameWithParams(
fuchsia::web::CreateFrameParams params,
fidl::InterfaceRequest<fuchsia::web::Frame> frame_request) {
if (!params.IsEmpty()) {
TRACE_EVENT(kWebEngineFidlCategory,
"fuchsia.web/Context.CreateFrameWithParams",
perfetto::Flow::FromPointer(this));
}
// Ensure the params can be cloned as required by CreateFrameForWebContents().
fuchsia::web::CreateFrameParams cloned_params;
zx_status_t status = params.Clone(&cloned_params);
if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "CreateFrameParams Clone() failed";
frame_request.Close(ZX_ERR_INVALID_ARGS);
return;
}
// Create a WebContents to host the new Frame.
content::WebContents::CreateParams create_params(browser_context_.get(),
nullptr);
create_params.initially_hidden = true;
auto web_contents = content::WebContents::Create(create_params);
CreateFrameForWebContents(std::move(web_contents), std::move(params),
std::move(frame_request));
}
FrameImpl* ContextImpl::CreateFrameForWebContents(
std::unique_ptr<content::WebContents> web_contents,
fuchsia::web::CreateFrameParams params,
fidl::InterfaceRequest<fuchsia::web::Frame> frame_request) {
DCHECK(frame_request.is_valid());
// Register the new Frame with the DevTools controller. The controller will
// reject registration if user-debugging is requested, but it is not enabled
// in the controller.
const bool user_debugging_requested =
params.has_enable_remote_debugging() && params.enable_remote_debugging();
if (!devtools_controller_->OnFrameCreated(web_contents.get(),
user_debugging_requested)) {
frame_request.Close(ZX_ERR_INVALID_ARGS);
return nullptr;
}
// |params.debug_name| is handled by FrameImpl.
// Verify the explicit sites filter error page content. If the parameter is
// present, it will be provided to the FrameImpl after it is created below.
std::optional<std::string> explicit_sites_filter_error_page;
if (params.has_explicit_sites_filter_error_page()) {
explicit_sites_filter_error_page =
base::StringFromMemData(params.explicit_sites_filter_error_page());
if (!explicit_sites_filter_error_page) {
frame_request.Close(ZX_ERR_INVALID_ARGS);
return nullptr;
}
}
// Wrap the WebContents into a FrameImpl owned by |this|.
auto inspect_node_name =
base::StringPrintf("frame-%lu", *base::GetKoid(frame_request.channel()));
auto frame_impl = std::make_unique<FrameImpl>(
std::move(web_contents), this, std::move(params),
inspect_node_.CreateChild(inspect_node_name), std::move(frame_request));
if (explicit_sites_filter_error_page) {
frame_impl->EnableExplicitSitesFilter(
std::move(*explicit_sites_filter_error_page));
}
FrameImpl* frame_ptr = frame_impl.get();
frames_.insert(std::move(frame_impl));
return frame_ptr;
}
void ContextImpl::GetCookieManager(
fidl::InterfaceRequest<fuchsia::web::CookieManager> request) {
TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Context.GetCookieManager",
perfetto::Flow::FromPointer(this));
cookie_manager_bindings_.AddBinding(&cookie_manager_, std::move(request));
}
void ContextImpl::GetRemoteDebuggingPort(
GetRemoteDebuggingPortCallback callback) {
TRACE_EVENT(kWebEngineFidlCategory,
"fuchsia.web/Context.GetRemoteDebuggingPort",
perfetto::Flow::FromPointer(this));
devtools_controller_->GetDevToolsPort(base::BindOnce(
[](GetRemoteDebuggingPortCallback callback, uint16_t port) {
if (port == 0) {
callback(fpromise::error(
fuchsia::web::ContextError::REMOTE_DEBUGGING_PORT_NOT_OPENED));
} else {
callback(fpromise::ok(port));
}
},
std::move(callback)));
}
FrameImpl* ContextImpl::GetFrameImplForTest(
fuchsia::web::FramePtr* frame_ptr) const {
DCHECK(frame_ptr);
// Find the FrameImpl whose channel is connected to |frame_ptr| by inspecting
// the "related" KOIDs of active FrameImpls.
zx_koid_t channel_koid = base::GetKoid(frame_ptr->channel()).value();
for (const std::unique_ptr<FrameImpl>& frame : frames_) {
zx_koid_t peer_koid =
base::GetRelatedKoid(*frame->GetBindingChannelForTest()) // IN-TEST
.value();
if (peer_koid == channel_koid)
return frame.get();
}
return nullptr;
}
network::mojom::NetworkContext* ContextImpl::GetNetworkContext() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return browser_context_->GetDefaultStoragePartition()->GetNetworkContext();
}