blob: 26b2fa063c42d1e3a3d34273f5b1e133c758ac50 [file] [log] [blame]
// 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 "extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_base.h"
#include "base/guid.h"
#include "base/lazy_instance.h"
#include "base/memory/weak_ptr.h"
#include "base/stl_util.h"
#include "components/guest_view/common/guest_view_constants.h"
#include "content/public/common/url_loader_throttle.h"
#include "content/public/common/webplugininfo.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "content/public/renderer/v8_value_converter.h"
#include "extensions/common/mojo/guest_view.mojom.h"
#include "extensions/renderer/extension_frame_helper.h"
#include "gin/arguments.h"
#include "gin/dictionary.h"
#include "gin/handle.h"
#include "gin/interceptor.h"
#include "gin/object_template_builder.h"
#include "gin/wrappable.h"
#include "ipc/ipc_sync_channel.h"
#include "services/network/public/cpp/features.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/public/web/web_associated_url_loader.h"
#include "third_party/blink/public/web/web_associated_url_loader_options.h"
#include "third_party/blink/public/web/web_frame.h"
#include "third_party/blink/public/web/web_local_frame.h"
namespace extensions {
namespace {
const char kPostMessageName[] = "postMessage";
base::LazyInstance<mojom::GuestViewAssociatedPtr>::Leaky g_guest_view;
mojom::GuestView* GetGuestView() {
if (!g_guest_view.Get()) {
content::RenderThread::Get()->GetChannel()->GetRemoteAssociatedInterface(
&g_guest_view.Get());
}
return g_guest_view.Get().get();
}
// The gin-backed scriptable object which is exposed by the BrowserPlugin for
// MimeHandlerViewContainerBase. This currently only implements "postMessage".
class ScriptableObject : public gin::Wrappable<ScriptableObject>,
public gin::NamedPropertyInterceptor {
public:
static gin::WrapperInfo kWrapperInfo;
static v8::Local<v8::Object> Create(
v8::Isolate* isolate,
base::WeakPtr<MimeHandlerViewContainerBase> container) {
ScriptableObject* scriptable_object =
new ScriptableObject(isolate, container);
return gin::CreateHandle(isolate, scriptable_object)
.ToV8()
.As<v8::Object>();
}
// gin::NamedPropertyInterceptor
v8::Local<v8::Value> GetNamedProperty(
v8::Isolate* isolate,
const std::string& identifier) override {
if (identifier == kPostMessageName) {
if (post_message_function_template_.IsEmpty()) {
post_message_function_template_.Reset(
isolate,
gin::CreateFunctionTemplate(
isolate,
base::BindRepeating(
&MimeHandlerViewContainerBase::PostJavaScriptMessage,
container_, isolate)));
}
v8::Local<v8::FunctionTemplate> function_template =
v8::Local<v8::FunctionTemplate>::New(isolate,
post_message_function_template_);
v8::Local<v8::Function> function;
if (function_template->GetFunction(isolate->GetCurrentContext())
.ToLocal(&function))
return function;
}
return v8::Local<v8::Value>();
}
private:
ScriptableObject(v8::Isolate* isolate,
base::WeakPtr<MimeHandlerViewContainerBase> container)
: gin::NamedPropertyInterceptor(isolate, this), container_(container) {}
// gin::Wrappable
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
v8::Isolate* isolate) override {
return gin::Wrappable<ScriptableObject>::GetObjectTemplateBuilder(isolate)
.AddNamedPropertyInterceptor();
}
base::WeakPtr<MimeHandlerViewContainerBase> container_;
v8::Persistent<v8::FunctionTemplate> post_message_function_template_;
};
// static
gin::WrapperInfo ScriptableObject::kWrapperInfo = {gin::kEmbedderNativeGin};
// Maps from content::RenderFrame to the set of MimeHandlerViewContainerBases
// within it.
base::LazyInstance<
std::map<content::RenderFrame*, std::set<MimeHandlerViewContainerBase*>>>::
DestructorAtExit g_mime_handler_view_container_base_map =
LAZY_INSTANCE_INITIALIZER;
} // namespace
// Stores a raw pointer to MimeHandlerViewContainerBase since this throttle's
// lifetime is shorter (it matches |container|'s loader_).
class MimeHandlerViewContainerBase::PluginResourceThrottle
: public content::URLLoaderThrottle {
public:
explicit PluginResourceThrottle(MimeHandlerViewContainerBase* container)
: container_(container) {}
~PluginResourceThrottle() override {}
private:
// content::URLLoaderThrottle overrides;
void WillProcessResponse(const GURL& response_url,
network::ResourceResponseHead* response_head,
bool* defer) override {
network::mojom::URLLoaderPtr dummy_new_loader;
mojo::MakeRequest(&dummy_new_loader);
network::mojom::URLLoaderClientPtr new_client;
network::mojom::URLLoaderClientRequest new_client_request =
mojo::MakeRequest(&new_client);
network::mojom::URLLoaderPtr original_loader;
network::mojom::URLLoaderClientRequest original_client;
delegate_->InterceptResponse(std::move(dummy_new_loader),
std::move(new_client_request),
&original_loader, &original_client);
auto transferrable_loader = content::mojom::TransferrableURLLoader::New();
transferrable_loader->url_loader = original_loader.PassInterface();
transferrable_loader->url_loader_client = std::move(original_client);
// Make a deep copy of ResourceResponseHead before passing it cross-thread.
auto resource_response = base::MakeRefCounted<network::ResourceResponse>();
resource_response->head = *response_head;
auto deep_copied_response = resource_response->DeepCopy();
transferrable_loader->head = std::move(deep_copied_response->head);
container_->SetEmbeddedLoader(std::move(transferrable_loader));
}
MimeHandlerViewContainerBase* container_;
DISALLOW_COPY_AND_ASSIGN(PluginResourceThrottle);
};
MimeHandlerViewContainerBase::MimeHandlerViewContainerBase(
content::RenderFrame* embedder_render_frame,
const content::WebPluginInfo& info,
const std::string& mime_type,
const GURL& original_url)
: plugin_path_(info.path.MaybeAsASCII()),
mime_type_(mime_type),
original_url_(original_url),
before_unload_control_binding_(this),
weak_factory_(this) {
DCHECK(!mime_type_.empty());
g_mime_handler_view_container_base_map.Get()[embedder_render_frame].insert(
this);
}
MimeHandlerViewContainerBase::~MimeHandlerViewContainerBase() {
if (loader_) {
DCHECK(is_embedded_);
loader_->Cancel();
}
if (auto* rf = GetEmbedderRenderFrame()) {
g_mime_handler_view_container_base_map.Get()[rf].erase(this);
if (g_mime_handler_view_container_base_map.Get()[rf].empty())
g_mime_handler_view_container_base_map.Get().erase(rf);
}
}
// static
std::vector<MimeHandlerViewContainerBase*>
MimeHandlerViewContainerBase::FromRenderFrame(
content::RenderFrame* render_frame) {
auto it = g_mime_handler_view_container_base_map.Get().find(render_frame);
if (it == g_mime_handler_view_container_base_map.Get().end())
return std::vector<MimeHandlerViewContainerBase*>();
return std::vector<MimeHandlerViewContainerBase*>(it->second.begin(),
it->second.end());
}
std::unique_ptr<content::URLLoaderThrottle>
MimeHandlerViewContainerBase::MaybeCreatePluginThrottle(const GURL& url) {
if (!waiting_to_create_throttle_ || url != original_url_)
return nullptr;
waiting_to_create_throttle_ = false;
return std::make_unique<PluginResourceThrottle>(this);
}
void MimeHandlerViewContainerBase::PostJavaScriptMessage(
v8::Isolate* isolate,
v8::Local<v8::Value> message) {
if (!guest_loaded_) {
pending_messages_.push_back(v8::Global<v8::Value>(isolate, message));
return;
}
auto* guest_proxy_frame = GetGuestProxyFrame();
v8::Context::Scope context_scope(
GetEmbedderRenderFrame()->GetWebFrame()->MainWorldScriptContext());
v8::Local<v8::Object> guest_proxy_window = guest_proxy_frame->GlobalProxy();
gin::Dictionary window_object(isolate, guest_proxy_window);
v8::Local<v8::Function> post_message;
if (!window_object.Get(std::string(kPostMessageName), &post_message))
return;
v8::Local<v8::Value> args[] = {
message,
// Post the message to any domain inside the browser plugin. The embedder
// should already know what is embedded.
gin::StringToV8(isolate, "*")};
GetEmbedderRenderFrame()->GetWebFrame()->CallFunctionEvenIfScriptDisabled(
post_message.As<v8::Function>(), guest_proxy_window, base::size(args),
args);
}
void MimeHandlerViewContainerBase::PostMessageFromValue(
const base::Value& message) {
blink::WebLocalFrame* frame = GetEmbedderRenderFrame()->GetWebFrame();
if (!frame)
return;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(frame->MainWorldScriptContext());
PostJavaScriptMessage(isolate,
content::V8ValueConverter::Create()->ToV8Value(
&message, frame->MainWorldScriptContext()));
}
content::RenderFrame* MimeHandlerViewContainerBase::GetEmbedderRenderFrame()
const {
return nullptr;
}
void MimeHandlerViewContainerBase::CreateMimeHandlerViewGuestIfNecessary() {
if (guest_created_)
return;
// When the network service is enabled, subresource requests like plugins are
// made directly from the renderer to the network service. So we need to
// intercept the URLLoader and send it to the browser so that it can forward
// it to the plugin.
if (base::FeatureList::IsEnabled(network::features::kNetworkService) &&
is_embedded_) {
if (transferrable_url_loader_.is_null())
return;
GetGuestView()->CreateEmbeddedMimeHandlerViewGuest(
GetEmbedderRenderFrame()->GetRoutingID(),
ExtensionFrameHelper::Get(GetEmbedderRenderFrame())->tab_id(),
original_url_, GetInstanceId(), GetElementSize(),
std::move(transferrable_url_loader_));
guest_created_ = true;
return;
}
if (view_id_.empty())
return;
// The loader has completed loading |view_id_| so we can dispose it.
if (loader_) {
DCHECK(is_embedded_);
loader_.reset();
}
DCHECK_NE(GetInstanceId(), guest_view::kInstanceIDNone);
if (!GetEmbedderRenderFrame())
return;
mime_handler::BeforeUnloadControlPtr before_unload_control;
if (!is_embedded_) {
before_unload_control_binding_.Bind(
mojo::MakeRequest(&before_unload_control));
}
GetGuestView()->CreateMimeHandlerViewGuest(
GetEmbedderRenderFrame()->GetRoutingID(), view_id_, GetInstanceId(),
GetElementSize(), std::move(before_unload_control));
guest_created_ = true;
}
void MimeHandlerViewContainerBase::DidCompleteLoad() {
if (!GetEmbedderRenderFrame())
return;
guest_loaded_ = true;
if (pending_messages_.empty())
return;
// Now that the guest has loaded, flush any unsent messages.
blink::WebLocalFrame* frame = GetEmbedderRenderFrame()->GetWebFrame();
if (!frame)
return;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(frame->MainWorldScriptContext());
for (const auto& pending_message : pending_messages_)
PostJavaScriptMessage(isolate,
v8::Local<v8::Value>::New(isolate, pending_message));
pending_messages_.clear();
}
void MimeHandlerViewContainerBase::SendResourceRequest() {
blink::WebLocalFrame* frame = GetEmbedderRenderFrame()->GetWebFrame();
blink::WebAssociatedURLLoaderOptions options;
DCHECK(!loader_);
loader_.reset(frame->CreateAssociatedURLLoader(options));
// The embedded plugin is allowed to be cross-origin and we should always
// send credentials/cookies with the request. So, use the default mode
// "no-cors" and credentials mode "include".
blink::WebURLRequest request(original_url_);
request.SetRequestContext(blink::WebURLRequest::kRequestContextObject);
// The plugin resource request should skip service workers since "plug-ins
// may get their security origins from their own urls".
// https://w3c.github.io/ServiceWorker/#implementer-concerns
request.SetSkipServiceWorker(true);
waiting_to_create_throttle_ = true;
loader_->LoadAsynchronously(request, this);
}
void MimeHandlerViewContainerBase::EmbedderRenderFrameWillBeGone() {
g_mime_handler_view_container_base_map.Get().erase(GetEmbedderRenderFrame());
}
void MimeHandlerViewContainerBase::SetEmbeddedLoader(
content::mojom::TransferrableURLLoaderPtr transferrable_url_loader) {
transferrable_url_loader_ = std::move(transferrable_url_loader);
transferrable_url_loader_->url = GURL(plugin_path_ + base::GenerateGUID());
CreateMimeHandlerViewGuestIfNecessary();
}
v8::Local<v8::Object> MimeHandlerViewContainerBase::GetScriptableObject(
v8::Isolate* isolate) {
if (scriptable_object_.IsEmpty()) {
v8::Local<v8::Object> object =
ScriptableObject::Create(isolate, weak_factory_.GetWeakPtr());
scriptable_object_.Reset(isolate, object);
}
return v8::Local<v8::Object>::New(isolate, scriptable_object_);
}
} // namespace extensions