blob: 53a33688358986f26432e25a8421ec774fc367ea [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/webui/web_ui_impl.h"
#include <stddef.h>
#include <string>
#include <string_view>
#include <utility>
#include "base/containers/flat_map.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_writer.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/renderer_host/dip_util.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/web_contents/web_contents_view.h"
#include "content/browser/webui/url_data_manager_backend.h"
#include "content/browser/webui/web_ui_controller_factory_registry.h"
#include "content/browser/webui/web_ui_data_source_impl.h"
#include "content/browser/webui/web_ui_main_frame_observer.h"
#include "content/common/features.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_ui_controller.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/mojom/loader/local_resource_loader_config.mojom.h"
namespace content {
namespace {
template <typename Range>
std::u16string GetJavascriptCallImpl(std::string_view function_name,
const Range& args) {
std::vector<std::u16string> json_args;
for (const auto& arg : args) {
json_args.push_back(base::UTF8ToUTF16(*base::WriteJson(arg)));
}
std::u16string result(base::ASCIIToUTF16(function_name));
result.push_back('(');
result.append(base::JoinString(json_args, u","));
result.push_back(')');
result.push_back(';');
return result;
}
blink::mojom::LocalResourceLoaderConfigPtr CreateLocalResourceLoaderConfig(
URLDataManagerBackend* data_backend) {
auto loader_config = blink::mojom::LocalResourceLoaderConfig::New();
base::flat_map<url::Origin, blink::mojom::LocalResourceSourcePtr>&
loader_sources = loader_config->sources;
for (auto const& [source_name, data_source] : data_backend->data_sources()) {
// For a data source to be useful in the renderer process, it must have a
// map from path to resource ID. Only WebUIDataSourceImpls have a map from
// path to resource ID. Most URLDataSources are not WebUIDataSourceImpls,
// e.g. favicon, image, etc.
if (!data_source->IsWebUIDataSourceImpl()) {
continue;
}
auto* webui_data_source =
static_cast<WebUIDataSourceImpl*>(data_source.get());
url::Origin origin = webui_data_source->GetOrigin();
// We only support data sources that serve URLs of the form: chrome://*
if (origin.scheme() != kChromeUIScheme) {
continue;
}
auto loader_source = blink::mojom::LocalResourceSource::New();
webui_data_source->EnsureLoadTimeDataDefaultsAdded();
loader_source->headers =
URLDataManagerBackend::GetHeaders(webui_data_source, GURL("/"), "")
->raw_headers();
loader_source->should_replace_i18n_in_js =
data_source->source()->ShouldReplaceI18nInJS();
loader_source->path_to_resource_id_map.insert(
webui_data_source->path_to_idr_map().begin(),
webui_data_source->path_to_idr_map().end());
loader_source->replacement_strings.insert(
webui_data_source->source()->GetReplacements()->begin(),
webui_data_source->source()->GetReplacements()->end());
loader_sources[origin] = std::move(loader_source);
}
return loader_config;
}
bool IsForTestMessage(const std::string& message) {
return base::EndsWith(message, "ForTest") ||
base::EndsWith(message, "ForTesting");
}
} // namespace
const WebUI::TypeID WebUI::kNoWebUI = nullptr;
// static
std::u16string WebUI::GetJavascriptCall(
std::string_view function_name,
base::span<const base::ValueView> arg_list) {
return GetJavascriptCallImpl(function_name, arg_list);
}
// static
std::u16string WebUI::GetJavascriptCall(std::string_view function_name,
const base::Value::List& arg_list) {
return GetJavascriptCallImpl(function_name, arg_list);
}
WebUIImpl::WebUIImpl(WebContents* web_contents)
: requestable_schemes_({kChromeUIScheme, url::kFileScheme}),
web_contents_(web_contents),
web_contents_observer_(
std::make_unique<WebUIMainFrameObserver>(this, web_contents_)) {
DCHECK(web_contents_);
}
WebUIImpl::WebUIImpl(NavigationRequest* request)
: WebUIImpl(
WebContents::FromFrameTreeNodeId(request->GetFrameTreeNodeId())) {}
WebUIImpl::~WebUIImpl() {
// Delete the controller first, since it may also be keeping a pointer to some
// of the handlers and can call them at destruction.
// Note: Calling this might delete |web_content_| and |frame_host_|. The two
// pointers are now potentially dangling.
// See https://crbug.com/1308391
controller_.reset();
remote_.reset();
receiver_.reset();
}
void WebUIImpl::SetProperty(const std::string& name, const std::string& value) {
DCHECK(remote_);
remote_->SetProperty(name, value);
}
void WebUIImpl::Send(const std::string& message, base::Value::List args) {
const GURL& source_url = frame_host_->GetLastCommittedURL();
if (!ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
frame_host_->GetProcess()->GetDeprecatedID()) ||
!WebUIControllerFactoryRegistry::GetInstance()->IsURLAcceptableForWebUI(
web_contents_->GetBrowserContext(), source_url)) {
bad_message::ReceivedBadMessage(
frame_host_->GetProcess(),
bad_message::WEBUI_SEND_FROM_UNAUTHORIZED_PROCESS);
return;
}
if (base::EndsWith(message, "RequiringGesture",
base::CompareCase::SENSITIVE) &&
!web_contents_->HasRecentInteraction()) {
LOG(ERROR) << message << " received without recent user interaction";
return;
}
ProcessWebUIMessage(source_url, message, std::move(args));
}
void WebUIImpl::SetRenderFrameHost(RenderFrameHost* render_frame_host) {
frame_host_ =
static_cast<RenderFrameHostImpl*>(render_frame_host)->GetWeakPtr();
// Assert that we can only open WebUI for the active or speculative pages.
DCHECK(frame_host_->lifecycle_state() ==
RenderFrameHostImpl::LifecycleStateImpl::kActive ||
frame_host_->lifecycle_state() ==
RenderFrameHostImpl::LifecycleStateImpl::kSpeculative);
}
void WebUIImpl::WebUIRenderFrameCreated(RenderFrameHost* render_frame_host) {
controller_->WebUIRenderFrameCreated(render_frame_host);
#if BUILDFLAG(LOAD_WEBUI_FROM_DISK)
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kLoadWebUIfromDisk)) {
return;
}
#endif
if (base::FeatureList::IsEnabled(features::kWebUIInProcessResourceLoading)) {
CHECK(frame_host_);
frame_host_->UpdateLocalResourceLoader(GetLocalResourceLoaderConfig());
}
}
void WebUIImpl::RenderFrameHostUnloading() {
DisallowJavascriptOnAllHandlers();
}
void WebUIImpl::RenderFrameDeleted() {
DisallowJavascriptOnAllHandlers();
}
void WebUIImpl::SetUpMojoConnection() {
// TODO(nasko): WebUI mojo might be useful to be registered for
// subframes as well, though at this time there is no such usage but currently
// this is expected to be called only for outermost main frames.
if (frame_host_->GetParentOrOuterDocument())
return;
frame_host_->GetFrameBindingsControl()->BindWebUI(
remote_.BindNewEndpointAndPassReceiver(),
receiver_.BindNewEndpointAndPassRemote());
}
void WebUIImpl::TearDownMojoConnection() {
// This is expected to be called only for outermost main frames.
if (frame_host_->GetParentOrOuterDocument())
return;
remote_.reset();
receiver_.reset();
}
WebContents* WebUIImpl::GetWebContents() {
return web_contents_;
}
float WebUIImpl::GetDeviceScaleFactor() {
return GetScaleFactorForView(web_contents_->GetRenderWidgetHostView());
}
const std::u16string& WebUIImpl::GetOverriddenTitle() {
return overridden_title_;
}
void WebUIImpl::OverrideTitle(const std::u16string& title) {
overridden_title_ = title;
}
BindingsPolicySet WebUIImpl::GetBindings() {
return bindings_;
}
void WebUIImpl::SetBindings(BindingsPolicySet bindings) {
bindings_ = bindings;
}
const std::vector<std::string>& WebUIImpl::GetRequestableSchemes() {
return requestable_schemes_;
}
void WebUIImpl::AddRequestableScheme(const char* scheme) {
requestable_schemes_.push_back(scheme);
}
WebUIController* WebUIImpl::GetController() {
return controller_.get();
}
RenderFrameHost* WebUIImpl::GetRenderFrameHost() {
return frame_host_.get();
}
bool WebUIImpl::HasRenderFrameHost() const {
return !!frame_host_;
}
void WebUIImpl::SetController(std::unique_ptr<WebUIController> controller) {
DCHECK(controller);
controller_ = std::move(controller);
}
bool WebUIImpl::CanCallJavascript() {
return (ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
frame_host_->GetProcess()->GetDeprecatedID()) ||
// It's possible to load about:blank in a Web UI renderer.
// See http://crbug.com/42547
frame_host_->GetLastCommittedURL().spec() == url::kAboutBlankURL);
}
void WebUIImpl::CallJavascriptFunctionUnsafe(
std::string_view function_name,
base::span<const base::ValueView> args) {
DCHECK(base::IsStringASCII(function_name));
ExecuteJavascript(GetJavascriptCall(function_name, args));
}
void WebUIImpl::RegisterMessageCallback(std::string_view message,
MessageCallback callback) {
message_callbacks_.emplace(message, std::move(callback));
}
void WebUIImpl::ProcessWebUIMessage(const GURL& source_url,
const std::string& message,
base::Value::List args) {
if (controller_->OverrideHandleWebUIMessage(source_url, message, args))
return;
auto callback_pair = message_callbacks_.find(message);
if (callback_pair != message_callbacks_.end()) {
// Forward this message and content on.
callback_pair->second.Run(args);
return;
}
if (!IsForTestMessage(message)) {
DUMP_WILL_BE_NOTREACHED() << "Unhandled chrome.send(\"" << message << "\", "
<< args << "); from " << source_url;
}
}
std::vector<std::unique_ptr<WebUIMessageHandler>>*
WebUIImpl::GetHandlersForTesting() {
return &handlers_;
}
// WebUIImpl, protected: -------------------------------------------------------
void WebUIImpl::AddMessageHandler(
std::unique_ptr<WebUIMessageHandler> handler) {
DCHECK(!handler->web_ui());
handler->set_web_ui(this);
handler->RegisterMessages();
handlers_.push_back(std::move(handler));
}
void WebUIImpl::ExecuteJavascript(const std::u16string& javascript) {
// Silently ignore the request. Would be nice to clean-up WebUI so we
// could turn this into a CHECK(). http://crbug.com/516690.
if (!CanCallJavascript())
return;
frame_host_->ExecuteJavaScript(javascript, base::NullCallback());
}
void WebUIImpl::DisallowJavascriptOnAllHandlers() {
for (const std::unique_ptr<WebUIMessageHandler>& handler : handlers_)
handler->DisallowJavascript();
}
blink::mojom::LocalResourceLoaderConfigPtr
WebUIImpl::GetLocalResourceLoaderConfig() {
URLDataManagerBackend* data_backend =
URLDataManagerBackend::GetForBrowserContext(
web_contents_->GetBrowserContext());
return CreateLocalResourceLoaderConfig(data_backend);
}
// static
blink::mojom::LocalResourceLoaderConfigPtr
WebUIImpl::GetLocalResourceLoaderConfigForTesting(
URLDataManagerBackend* data_backend) {
return CreateLocalResourceLoaderConfig(data_backend);
}
} // namespace content