blob: a7baff4180e4e432dd328081de41058e2549dd58 [file] [log] [blame]
// Copyright 2012 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 "content/renderer/browser_plugin/browser_plugin.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/thread_task_runner_handle.h"
#include "content/common/browser_plugin/browser_plugin_constants.h"
#include "content/common/browser_plugin/browser_plugin_messages.h"
#include "content/common/view_messages.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/public/renderer/browser_plugin_delegate.h"
#include "content/public/renderer/content_renderer_client.h"
#include "content/renderer/browser_plugin/browser_plugin_manager.h"
#include "content/renderer/child_frame_compositing_helper.h"
#include "content/renderer/cursor_utils.h"
#include "content/renderer/drop_data_builder.h"
#include "content/renderer/render_thread_impl.h"
#include "content/renderer/sad_plugin.h"
#include "third_party/WebKit/public/platform/WebRect.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebElement.h"
#include "third_party/WebKit/public/web/WebInputEvent.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebPluginContainer.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "ui/events/keycodes/keyboard_codes.h"
using blink::WebCanvas;
using blink::WebPluginContainer;
using blink::WebPoint;
using blink::WebRect;
using blink::WebURL;
using blink::WebVector;
namespace {
using PluginContainerMap =
std::map<blink::WebPluginContainer*, content::BrowserPlugin*>;
static base::LazyInstance<PluginContainerMap> g_plugin_container_map =
LAZY_INSTANCE_INITIALIZER;
} // namespace
namespace content {
// static
BrowserPlugin* BrowserPlugin::GetFromNode(blink::WebNode& node) {
blink::WebPluginContainer* container = node.pluginContainer();
if (!container)
return nullptr;
PluginContainerMap* browser_plugins = g_plugin_container_map.Pointer();
PluginContainerMap::iterator it = browser_plugins->find(container);
return it == browser_plugins->end() ? nullptr : it->second;
}
BrowserPlugin::BrowserPlugin(RenderFrame* render_frame,
BrowserPluginDelegate* delegate)
: attached_(false),
render_frame_routing_id_(render_frame->GetRoutingID()),
container_(nullptr),
sad_guest_(nullptr),
guest_crashed_(false),
plugin_focused_(false),
visible_(true),
mouse_locked_(false),
ready_(false),
browser_plugin_instance_id_(browser_plugin::kInstanceIDNone),
contents_opaque_(true),
delegate_(delegate),
weak_ptr_factory_(this) {
browser_plugin_instance_id_ =
BrowserPluginManager::Get()->GetNextInstanceID();
if (delegate_)
delegate_->SetElementInstanceID(browser_plugin_instance_id_);
}
BrowserPlugin::~BrowserPlugin() {
if (compositing_helper_.get())
compositing_helper_->OnContainerDestroy();
if (delegate_)
delegate_->DidDestroyElement();
delegate_ = nullptr;
BrowserPluginManager::Get()->RemoveBrowserPlugin(browser_plugin_instance_id_);
}
bool BrowserPlugin::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(BrowserPlugin, message)
IPC_MESSAGE_HANDLER(BrowserPluginMsg_AdvanceFocus, OnAdvanceFocus)
IPC_MESSAGE_HANDLER_GENERIC(BrowserPluginMsg_CompositorFrameSwapped,
OnCompositorFrameSwapped(message))
IPC_MESSAGE_HANDLER(BrowserPluginMsg_GuestGone, OnGuestGone)
IPC_MESSAGE_HANDLER(BrowserPluginMsg_SetContentsOpaque, OnSetContentsOpaque)
IPC_MESSAGE_HANDLER(BrowserPluginMsg_SetCursor, OnSetCursor)
IPC_MESSAGE_HANDLER(BrowserPluginMsg_SetMouseLock, OnSetMouseLock)
IPC_MESSAGE_HANDLER(BrowserPluginMsg_SetTooltipText, OnSetTooltipText)
IPC_MESSAGE_HANDLER(BrowserPluginMsg_ShouldAcceptTouchEvents,
OnShouldAcceptTouchEvents)
IPC_END_MESSAGE_MAP()
return handled;
}
void BrowserPlugin::UpdateDOMAttribute(const std::string& attribute_name,
const base::string16& attribute_value) {
if (!container())
return;
blink::WebElement element = container()->element();
blink::WebString web_attribute_name =
blink::WebString::fromUTF8(attribute_name);
element.setAttribute(web_attribute_name, attribute_value);
}
void BrowserPlugin::Attach() {
Detach();
BrowserPluginHostMsg_Attach_Params attach_params;
attach_params.focused = ShouldGuestBeFocused();
attach_params.visible = visible_;
attach_params.view_rect = view_rect();
attach_params.is_full_page_plugin = false;
if (container()) {
blink::WebLocalFrame* frame = container()->element().document().frame();
attach_params.is_full_page_plugin =
frame->view()->mainFrame()->document().isPluginDocument();
}
BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_Attach(
render_frame_routing_id_,
browser_plugin_instance_id_,
attach_params));
attached_ = true;
}
void BrowserPlugin::Detach() {
if (!attached())
return;
attached_ = false;
guest_crashed_ = false;
EnableCompositing(false);
if (compositing_helper_.get()) {
compositing_helper_->OnContainerDestroy();
compositing_helper_ = nullptr;
}
BrowserPluginManager::Get()->Send(
new BrowserPluginHostMsg_Detach(browser_plugin_instance_id_));
}
void BrowserPlugin::DidCommitCompositorFrame() {
if (compositing_helper_.get())
compositing_helper_->DidCommitCompositorFrame();
}
void BrowserPlugin::OnAdvanceFocus(int browser_plugin_instance_id,
bool reverse) {
auto render_frame = RenderFrameImpl::FromRoutingID(render_frame_routing_id());
auto render_view = render_frame ? render_frame->GetRenderView() : nullptr;
if (!render_view)
return;
render_view->GetWebView()->advanceFocus(reverse);
}
void BrowserPlugin::OnCompositorFrameSwapped(const IPC::Message& message) {
if (!attached())
return;
BrowserPluginMsg_CompositorFrameSwapped::Param param;
if (!BrowserPluginMsg_CompositorFrameSwapped::Read(&message, &param))
return;
// Note that there is no need to send ACK for this message.
// If the guest has updated pixels then it is no longer crashed.
guest_crashed_ = false;
scoped_ptr<cc::CompositorFrame> frame(new cc::CompositorFrame);
base::get<1>(param).frame.AssignTo(frame.get());
EnableCompositing(true);
compositing_helper_->OnCompositorFrameSwapped(
frame.Pass(),
base::get<1>(param).producing_route_id,
base::get<1>(param).output_surface_id,
base::get<1>(param).producing_host_id,
base::get<1>(param).shared_memory_handle);
}
void BrowserPlugin::OnGuestGone(int browser_plugin_instance_id) {
guest_crashed_ = true;
// Turn off compositing so we can display the sad graphic. Changes to
// compositing state will show up at a later time after a layout and commit.
EnableCompositing(false);
// Queue up showing the sad graphic to give content embedders an opportunity
// to fire their listeners and potentially overlay the webview with custom
// behavior. If the BrowserPlugin is destroyed in the meantime, then the
// task will not be executed.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(&BrowserPlugin::ShowSadGraphic,
weak_ptr_factory_.GetWeakPtr()));
}
void BrowserPlugin::OnSetContentsOpaque(int browser_plugin_instance_id,
bool opaque) {
if (contents_opaque_ == opaque)
return;
contents_opaque_ = opaque;
if (compositing_helper_.get())
compositing_helper_->SetContentsOpaque(opaque);
}
void BrowserPlugin::OnSetCursor(int browser_plugin_instance_id,
const WebCursor& cursor) {
cursor_ = cursor;
}
void BrowserPlugin::OnSetMouseLock(int browser_plugin_instance_id,
bool enable) {
auto render_frame = RenderFrameImpl::FromRoutingID(render_frame_routing_id());
auto render_view = static_cast<RenderViewImpl*>(
render_frame ? render_frame->GetRenderView() : nullptr);
if (enable) {
if (mouse_locked_ || !render_view)
return;
render_view->mouse_lock_dispatcher()->LockMouse(this);
} else {
if (!mouse_locked_) {
OnLockMouseACK(false);
return;
}
if (!render_view)
return;
render_view->mouse_lock_dispatcher()->UnlockMouse(this);
}
}
void BrowserPlugin::OnSetTooltipText(int instance_id,
const base::string16& tooltip_text) {
// Show tooltip text by setting the BrowserPlugin's |title| attribute.
UpdateDOMAttribute("title", tooltip_text);
}
void BrowserPlugin::OnShouldAcceptTouchEvents(int browser_plugin_instance_id,
bool accept) {
if (container()) {
container()->requestTouchEventType(
accept ? WebPluginContainer::TouchEventRequestTypeRaw
: WebPluginContainer::TouchEventRequestTypeNone);
}
}
void BrowserPlugin::ShowSadGraphic() {
// If the BrowserPlugin is scheduled to be deleted, then container_ will be
// nullptr so we shouldn't attempt to access it.
if (container_)
container_->invalidate();
}
void BrowserPlugin::UpdateInternalInstanceId() {
// This is a way to notify observers of our attributes that this plugin is
// available in render tree.
// TODO(lazyboy): This should be done through the delegate instead. Perhaps
// by firing an event from there.
UpdateDOMAttribute(
"internalinstanceid",
base::UTF8ToUTF16(base::IntToString(browser_plugin_instance_id_)));
}
void BrowserPlugin::UpdateGuestFocusState(blink::WebFocusType focus_type) {
if (!attached())
return;
bool should_be_focused = ShouldGuestBeFocused();
BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_SetFocus(
browser_plugin_instance_id_,
should_be_focused,
focus_type));
}
bool BrowserPlugin::ShouldGuestBeFocused() const {
bool embedder_focused = false;
auto render_frame = RenderFrameImpl::FromRoutingID(render_frame_routing_id());
auto render_view = static_cast<RenderViewImpl*>(
render_frame ? render_frame->GetRenderView() : nullptr);
if (render_view)
embedder_focused = render_view->has_focus();
return plugin_focused_ && embedder_focused;
}
WebPluginContainer* BrowserPlugin::container() const {
return container_;
}
bool BrowserPlugin::initialize(WebPluginContainer* container) {
if (!container)
return false;
container_ = container;
container_->setWantsWheelEvents(true);
g_plugin_container_map.Get().insert(std::make_pair(container_, this));
BrowserPluginManager::Get()->AddBrowserPlugin(
browser_plugin_instance_id_, this);
// Defer attach call so that if there's any pending browser plugin
// destruction, then it can progress first.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(&BrowserPlugin::UpdateInternalInstanceId,
weak_ptr_factory_.GetWeakPtr()));
return true;
}
void BrowserPlugin::EnableCompositing(bool enable) {
bool enabled = !!compositing_helper_.get();
if (enabled == enable)
return;
if (enable) {
DCHECK(!compositing_helper_.get());
if (!compositing_helper_.get()) {
compositing_helper_ = ChildFrameCompositingHelper::CreateForBrowserPlugin(
weak_ptr_factory_.GetWeakPtr());
}
}
compositing_helper_->EnableCompositing(enable);
compositing_helper_->SetContentsOpaque(contents_opaque_);
if (!enable) {
DCHECK(compositing_helper_.get());
compositing_helper_->OnContainerDestroy();
compositing_helper_ = nullptr;
}
}
void BrowserPlugin::destroy() {
if (container_) {
// The BrowserPlugin's WebPluginContainer is deleted immediately after this
// call returns, so let's not keep a reference to it around.
g_plugin_container_map.Get().erase(container_);
}
container_ = nullptr;
// Will be a no-op if the mouse is not currently locked.
auto render_frame = RenderFrameImpl::FromRoutingID(render_frame_routing_id());
auto render_view = static_cast<RenderViewImpl*>(
render_frame ? render_frame->GetRenderView() : nullptr);
if (render_view)
render_view->mouse_lock_dispatcher()->OnLockTargetDestroyed(this);
base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
}
v8::Local<v8::Object> BrowserPlugin::v8ScriptableObject(v8::Isolate* isolate) {
return delegate_->V8ScriptableObject(isolate);
}
bool BrowserPlugin::supportsKeyboardFocus() const {
return true;
}
bool BrowserPlugin::supportsEditCommands() const {
return true;
}
bool BrowserPlugin::supportsInputMethod() const {
return true;
}
bool BrowserPlugin::canProcessDrag() const {
return true;
}
void BrowserPlugin::paint(WebCanvas* canvas, const WebRect& rect) {
if (guest_crashed_) {
if (!sad_guest_) // Lazily initialize bitmap.
sad_guest_ = GetContentClient()->renderer()->GetSadWebViewBitmap();
// content_shell does not have the sad plugin bitmap, so we'll paint black
// instead to make it clear that something went wrong.
if (sad_guest_) {
PaintSadPlugin(canvas, view_rect_, *sad_guest_);
return;
}
}
SkAutoCanvasRestore auto_restore(canvas, true);
canvas->translate(view_rect_.x(), view_rect_.y());
SkRect image_data_rect = SkRect::MakeXYWH(
SkIntToScalar(0),
SkIntToScalar(0),
SkIntToScalar(view_rect_.width()),
SkIntToScalar(view_rect_.height()));
canvas->clipRect(image_data_rect);
// Paint black or white in case we have nothing in our backing store or we
// need to show a gutter.
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setColor(guest_crashed_ ? SK_ColorBLACK : SK_ColorWHITE);
canvas->drawRect(image_data_rect, paint);
}
// static
bool BrowserPlugin::ShouldForwardToBrowserPlugin(
const IPC::Message& message) {
return IPC_MESSAGE_CLASS(message) == BrowserPluginMsgStart;
}
void BrowserPlugin::updateGeometry(const WebRect& window_rect,
const WebRect& clip_rect,
const WebRect& unobscured_rect,
const WebVector<WebRect>& cut_outs_rects,
bool is_visible) {
gfx::Rect old_view_rect = view_rect_;
view_rect_ = window_rect;
if (!ready_) {
if (delegate_)
delegate_->Ready();
ready_ = true;
}
if (delegate_ && (view_rect_.size() != old_view_rect.size()))
delegate_->DidResizeElement(view_rect_.size());
if (!attached())
return;
if (old_view_rect.size() == view_rect_.size()) {
// Let the browser know about the updated view rect.
BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_UpdateGeometry(
browser_plugin_instance_id_, view_rect_));
return;
}
}
void BrowserPlugin::updateFocus(bool focused, blink::WebFocusType focus_type) {
plugin_focused_ = focused;
UpdateGuestFocusState(focus_type);
}
void BrowserPlugin::updateVisibility(bool visible) {
if (visible_ == visible)
return;
visible_ = visible;
if (!attached())
return;
if (compositing_helper_.get())
compositing_helper_->UpdateVisibility(visible);
BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_SetVisibility(
browser_plugin_instance_id_,
visible));
}
bool BrowserPlugin::acceptsInputEvents() {
return true;
}
bool BrowserPlugin::handleInputEvent(const blink::WebInputEvent& event,
blink::WebCursorInfo& cursor_info) {
if (guest_crashed_ || !attached())
return false;
if (event.type == blink::WebInputEvent::ContextMenu)
return true;
if (blink::WebInputEvent::isKeyboardEventType(event.type) &&
!edit_commands_.empty()) {
BrowserPluginManager::Get()->Send(
new BrowserPluginHostMsg_SetEditCommandsForNextKeyEvent(
browser_plugin_instance_id_,
edit_commands_));
edit_commands_.clear();
}
BrowserPluginManager::Get()->Send(
new BrowserPluginHostMsg_HandleInputEvent(browser_plugin_instance_id_,
view_rect_,
&event));
GetWebKitCursorInfo(cursor_, &cursor_info);
return true;
}
bool BrowserPlugin::handleDragStatusUpdate(blink::WebDragStatus drag_status,
const blink::WebDragData& drag_data,
blink::WebDragOperationsMask mask,
const blink::WebPoint& position,
const blink::WebPoint& screen) {
if (guest_crashed_ || !attached())
return false;
BrowserPluginManager::Get()->Send(
new BrowserPluginHostMsg_DragStatusUpdate(
browser_plugin_instance_id_,
drag_status,
DropDataBuilder::Build(drag_data),
mask,
position));
return true;
}
void BrowserPlugin::didReceiveResponse(
const blink::WebURLResponse& response) {
}
void BrowserPlugin::didReceiveData(const char* data, int data_length) {
if (delegate_)
delegate_->DidReceiveData(data, data_length);
}
void BrowserPlugin::didFinishLoading() {
if (delegate_)
delegate_->DidFinishLoading();
}
void BrowserPlugin::didFailLoading(const blink::WebURLError& error) {
}
void BrowserPlugin::didFinishLoadingFrameRequest(const blink::WebURL& url,
void* notify_data) {
}
void BrowserPlugin::didFailLoadingFrameRequest(
const blink::WebURL& url,
void* notify_data,
const blink::WebURLError& error) {
}
bool BrowserPlugin::executeEditCommand(const blink::WebString& name) {
BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_ExecuteEditCommand(
browser_plugin_instance_id_,
name.utf8()));
// BrowserPlugin swallows edit commands.
return true;
}
bool BrowserPlugin::executeEditCommand(const blink::WebString& name,
const blink::WebString& value) {
edit_commands_.push_back(EditCommand(name.utf8(), value.utf8()));
// BrowserPlugin swallows edit commands.
return true;
}
bool BrowserPlugin::setComposition(
const blink::WebString& text,
const blink::WebVector<blink::WebCompositionUnderline>& underlines,
int selectionStart,
int selectionEnd) {
if (!attached())
return false;
std::vector<blink::WebCompositionUnderline> std_underlines;
for (size_t i = 0; i < underlines.size(); ++i) {
std_underlines.push_back(underlines[i]);
}
BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_ImeSetComposition(
browser_plugin_instance_id_,
text.utf8(),
std_underlines,
selectionStart,
selectionEnd));
// TODO(kochi): This assumes the IPC handling always succeeds.
return true;
}
bool BrowserPlugin::confirmComposition(
const blink::WebString& text,
blink::WebWidget::ConfirmCompositionBehavior selectionBehavior) {
if (!attached())
return false;
bool keep_selection = (selectionBehavior == blink::WebWidget::KeepSelection);
BrowserPluginManager::Get()->Send(
new BrowserPluginHostMsg_ImeConfirmComposition(
browser_plugin_instance_id_,
text.utf8(),
keep_selection));
// TODO(kochi): This assumes the IPC handling always succeeds.
return true;
}
void BrowserPlugin::extendSelectionAndDelete(int before, int after) {
if (!attached())
return;
BrowserPluginManager::Get()->Send(
new BrowserPluginHostMsg_ExtendSelectionAndDelete(
browser_plugin_instance_id_,
before,
after));
}
void BrowserPlugin::OnLockMouseACK(bool succeeded) {
mouse_locked_ = succeeded;
BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_LockMouse_ACK(
browser_plugin_instance_id_,
succeeded));
}
void BrowserPlugin::OnMouseLockLost() {
mouse_locked_ = false;
BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_UnlockMouse_ACK(
browser_plugin_instance_id_));
}
bool BrowserPlugin::HandleMouseLockedInputEvent(
const blink::WebMouseEvent& event) {
BrowserPluginManager::Get()->Send(
new BrowserPluginHostMsg_HandleInputEvent(browser_plugin_instance_id_,
view_rect_,
&event));
return true;
}
} // namespace content