// 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/message_loop/message_loop.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.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; static base::LazyInstance 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, scoped_ptr 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.Pass()), 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(); 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_MESSAGE_UNHANDLED( handled = delegate_ && delegate_->OnMessageReceived(message)) 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, ¶m)) 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 frame(new cc::CompositorFrame); get<1>(param).frame.AssignTo(frame.get()); EnableCompositing(true); compositing_helper_->OnCompositorFrameSwapped( frame.Pass(), get<1>(param).producing_route_id, get<1>(param).output_surface_id, get<1>(param).producing_host_id, 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::MessageLoop::current()->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( 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::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( 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::MessageLoop::current()->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::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::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( 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 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_ = content::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& cut_outs_rects, bool is_visible) { int old_width = view_rect_.width(); int old_height = view_rect_.height(); view_rect_ = window_rect; if (!ready_) { if (delegate_) delegate_->Ready(); ready_ = true; } if (delegate_) { delegate_->DidResizeElement( gfx::Size(old_width, old_height), view_rect_.size()); } if (!attached()) return; if (old_width == window_rect.width && old_height == window_rect.height) { // 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& underlines, int selectionStart, int selectionEnd) { if (!attached()) return false; std::vector 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