blob: de7fb1070c185ef33d5210ee3358f9a914ec7e19 [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/bind.h"
#include "base/guid.h"
#include "base/lazy_instance.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/stl_util.h"
#include "components/guest_view/common/guest_view_constants.h"
#include "content/public/common/webplugininfo.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "extensions/common/guest_view/extensions_guest_view_messages.h"
#include "extensions/renderer/extension_frame_helper.h"
#include "ipc/ipc_sync_channel.h"
#include "services/network/public/cpp/resource_response.h"
#include "third_party/blink/public/common/loader/url_loader_throttle.h"
#include "third_party/blink/public/platform/web_url.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_document.h"
#include "third_party/blink/public/web/web_frame.h"
#include "third_party/blink/public/web/web_local_frame.h"
namespace extensions {
using UMAType = MimeHandlerViewUMATypes::Type;
namespace {
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();
}
// 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 blink::URLLoaderThrottle {
public:
explicit PluginResourceThrottle(
base::WeakPtr<MimeHandlerViewContainerBase> container)
: container_(container) {}
~PluginResourceThrottle() override {}
private:
// blink::URLLoaderThrottle overrides;
void WillProcessResponse(const GURL& response_url,
network::ResourceResponseHead* response_head,
bool* defer) override {
if (!container_) {
// In the embedder case if the plugin element is removed right after an
// ongoing request is made, MimeHandlerViewContainerBase is destroyed
// synchronously but the WebURLLoaderImpl corresponding to this throttle
// goes away asynchronously when ResourceLoader::CancelTimerFired() is
// called (see https://crbug.com/878359).
return;
}
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));
}
base::WeakPtr<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)
: original_url_(original_url),
plugin_path_(info.path.MaybeAsASCII()),
mime_type_(mime_type),
embedder_render_frame_routing_id_(embedder_render_frame->GetRoutingID()) {
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
PostMessageSupport::Delegate* PostMessageSupport::Delegate::FromWebLocalFrame(
blink::WebLocalFrame* web_local_frame) {
if (!web_local_frame->GetDocument().IsPluginDocument())
return nullptr;
auto mime_handlers = MimeHandlerViewContainerBase::FromRenderFrame(
content::RenderFrame::FromWebFrame(web_local_frame));
if (mime_handlers.empty())
return nullptr;
return mime_handlers.front();
}
// static
mojom::GuestView* MimeHandlerViewContainerBase::GuestView() {
return GetGuestView();
}
// 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<blink::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>(weak_factory_.GetWeakPtr());
}
void MimeHandlerViewContainerBase::DidReceiveData(const char* data,
int data_length) {
view_id_ += std::string(data, data_length);
}
void MimeHandlerViewContainerBase::DidFinishLoading() {
DCHECK(is_embedded_);
// Warning: It is possible that |this| gets destroyed after this line (when
// the MHVCB is of the frame type and the associated plugin element does not
// have a content frame).
CreateMimeHandlerViewGuestIfNecessary();
}
content::RenderFrame* MimeHandlerViewContainerBase::GetEmbedderRenderFrame()
const {
DCHECK_NE(embedder_render_frame_routing_id_, MSG_ROUTING_NONE);
return content::RenderFrame::FromRoutingID(embedder_render_frame_routing_id_);
}
void MimeHandlerViewContainerBase::CreateMimeHandlerViewGuestIfNecessary() {
if (guest_created_)
return;
auto* embedder_render_frame = GetEmbedderRenderFrame();
if (!embedder_render_frame) {
// TODO(ekaramad): How can this happen? We should destroy the container if
// this happens at all. The process is however different for a plugin-based
// container.
return;
}
auto* guest_view = GetGuestView();
// 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 (is_embedded_) {
if (transferrable_url_loader_.is_null())
return;
auto* extension_frame_helper =
ExtensionFrameHelper::Get(embedder_render_frame);
if (!extension_frame_helper)
return;
guest_view->CreateEmbeddedMimeHandlerViewGuest(
embedder_render_frame->GetRoutingID(), extension_frame_helper->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);
mojo::PendingRemote<mime_handler::BeforeUnloadControl> before_unload_control;
if (!is_embedded_) {
before_unload_control =
before_unload_control_receiver_.BindNewPipeAndPassRemote();
}
guest_view->CreateMimeHandlerViewGuest(
embedder_render_frame->GetRoutingID(), view_id_, GetInstanceId(),
GetElementSize(), std::move(before_unload_control));
guest_created_ = true;
}
void MimeHandlerViewContainerBase::DidLoadInternal() {
RecordInteraction(UMAType::kDidLoadExtension);
if (!GetEmbedderRenderFrame())
return;
guest_loaded_ = true;
post_message_support()->SetActive();
}
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::mojom::RequestContextType::OBJECT);
// 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());
// Warning: It is possible that |this| gets destroyed after this line (when
// the MHVCB is of the frame type and the associated plugin element does not
// have a content frame).
CreateMimeHandlerViewGuestIfNecessary();
}
void MimeHandlerViewContainerBase::SetShowBeforeUnloadDialog(
bool show_dialog,
SetShowBeforeUnloadDialogCallback callback) {
DCHECK(!is_embedded_);
GetEmbedderRenderFrame()
->GetWebFrame()
->GetDocument()
.SetShowBeforeUnloadDialog(show_dialog);
std::move(callback).Run();
}
v8::Local<v8::Object> MimeHandlerViewContainerBase::GetScriptableObjectInternal(
v8::Isolate* isolate) {
return post_message_support()->GetScriptableObject(isolate);
}
void MimeHandlerViewContainerBase::RecordInteraction(UMAType uma_type) {
base::UmaHistogramEnumeration(MimeHandlerViewUMATypes::kUMAName, uma_type);
}
} // namespace extensions