// Copyright (c) 2006-2008 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 "chrome/renderer/webplugin_delegate_proxy.h" #include #include "generated_resources.h" #include "base/logging.h" #include "base/ref_counted.h" #include "base/string_util.h" #include "base/gfx/size.h" #include "base/gfx/native_widget_types.h" #include "chrome/app/chrome_dll_resource.h" #include "chrome/common/gfx/chrome_canvas.h" #include "chrome/common/gfx/emf.h" #include "chrome/common/l10n_util.h" #include "chrome/common/plugin_messages.h" #include "chrome/common/render_messages.h" #include "chrome/common/resource_bundle.h" #include "chrome/common/win_util.h" #include "chrome/plugin/npobject_proxy.h" #include "chrome/plugin/npobject_stub.h" #include "chrome/renderer/render_thread.h" #include "chrome/renderer/render_view.h" #include "googleurl/src/gurl.h" #include "net/base/mime_util.h" #include "webkit/glue/glue_util.h" #include "webkit/glue/webframe.h" #include "webkit/glue/webkit_glue.h" #include "webkit/glue/webplugin.h" #include "webkit/glue/webview.h" // Proxy for WebPluginResourceClient. The object owns itself after creation, // deleting itself after its callback has been called. class ResourceClientProxy : public WebPluginResourceClient { public: ResourceClientProxy(PluginChannelHost* channel, int instance_id) : channel_(channel), instance_id_(instance_id), resource_id_(0), notify_needed_(false), notify_data_(NULL), multibyte_response_expected_(false) { } ~ResourceClientProxy() { } void Initialize(int resource_id, const std::string &url, bool notify_needed, void *notify_data, void* existing_stream) { resource_id_ = resource_id; url_ = url; notify_needed_ = notify_needed; notify_data_ = notify_data; PluginMsg_URLRequestReply_Params params; params.resource_id = resource_id; params.url = url_; params.notify_needed = notify_needed_; params.notify_data = notify_data_; params.stream = existing_stream; multibyte_response_expected_ = (existing_stream != NULL); channel_->Send(new PluginMsg_HandleURLRequestReply(instance_id_, params)); } // PluginResourceClient implementation: void WillSendRequest(const GURL& url) { DCHECK(channel_ != NULL); channel_->Send(new PluginMsg_WillSendRequest(instance_id_, resource_id_, url)); } void DidReceiveResponse(const std::string& mime_type, const std::string& headers, uint32 expected_length, uint32 last_modified, bool request_is_seekable, bool* cancel) { DCHECK(channel_ != NULL); PluginMsg_DidReceiveResponseParams params; params.id = resource_id_; params.mime_type = mime_type; params.headers = headers; params.expected_length = expected_length; params.last_modified = last_modified; params.request_is_seekable = request_is_seekable; // Grab a reference on the underlying channel so it does not get // deleted from under us. scoped_refptr channel_ref(channel_); channel_->Send(new PluginMsg_DidReceiveResponse(instance_id_, params, cancel)); } void DidReceiveData(const char* buffer, int length, int data_offset) { DCHECK(channel_ != NULL); DCHECK(length > 0); std::vector data; data.resize(static_cast(length)); memcpy(&data.front(), buffer, length); // Grab a reference on the underlying channel so it does not get // deleted from under us. scoped_refptr channel_ref(channel_); channel_->Send(new PluginMsg_DidReceiveData(instance_id_, resource_id_, data, data_offset)); } void DidFinishLoading() { DCHECK(channel_ != NULL); channel_->Send(new PluginMsg_DidFinishLoading(instance_id_, resource_id_)); channel_ = NULL; MessageLoop::current()->DeleteSoon(FROM_HERE, this); } void DidFail() { DCHECK(channel_ != NULL); channel_->Send(new PluginMsg_DidFail(instance_id_, resource_id_)); channel_ = NULL; MessageLoop::current()->DeleteSoon(FROM_HERE, this); } bool IsMultiByteResponseExpected() { return multibyte_response_expected_; } private: int resource_id_; int instance_id_; scoped_refptr channel_; std::string url_; bool notify_needed_; void* notify_data_; // Set to true if the response expected is a multibyte response. // For e.g. response for a HTTP byte range request. bool multibyte_response_expected_; }; WebPluginDelegateProxy* WebPluginDelegateProxy::Create( const GURL& url, const std::string& mime_type, const std::string& clsid, RenderView* render_view) { return new WebPluginDelegateProxy(mime_type, clsid, render_view); } WebPluginDelegateProxy::WebPluginDelegateProxy(const std::string& mime_type, const std::string& clsid, RenderView* render_view) : render_view_(render_view), mime_type_(mime_type), clsid_(clsid), plugin_(NULL), windowless_(false), npobject_(NULL), send_deferred_update_geometry_(false), sad_plugin_(NULL), window_script_object_(NULL), transparent_(false), invalidate_pending_(false) { } WebPluginDelegateProxy::~WebPluginDelegateProxy() { } void WebPluginDelegateProxy::PluginDestroyed() { plugin_ = NULL; if (npobject_) { // When we destroy the plugin instance, the NPObjectStub NULLs out its // pointer to the npobject (see NPObjectStub::OnChannelError). Therefore, // we release the object before destroying the instance to avoid leaking. NPN_ReleaseObject(npobject_); npobject_ = NULL; } if (window_script_object_) { // The ScriptController deallocates this object independent of its ref count // to avoid leaks if the plugin forgets to release it. So mark the object // invalid to avoid accessing it past this point. window_script_object_->set_proxy(NULL); window_script_object_->set_invalid(); } if (channel_host_) { channel_host_->RemoveRoute(instance_id_); Send(new PluginMsg_DestroyInstance(instance_id_)); } render_view_->PluginDestroyed(this); MessageLoop::current()->DeleteSoon(FROM_HERE, this); } void WebPluginDelegateProxy::FlushGeometryUpdates() { if (send_deferred_update_geometry_) { send_deferred_update_geometry_ = false; Send(new PluginMsg_UpdateGeometry(instance_id_, plugin_rect_, deferred_clip_rect_, NULL, NULL)); } } bool WebPluginDelegateProxy::Initialize(const GURL& url, char** argn, char** argv, int argc, WebPlugin* plugin, bool load_manually) { std::wstring channel_name; FilePath plugin_path; if (!g_render_thread->Send(new ViewHostMsg_OpenChannelToPlugin( url, mime_type_, clsid_, webkit_glue::GetWebKitLocale(), &channel_name, &plugin_path))) return false; MessageLoop* ipc_message_loop = g_render_thread->owner_loop(); scoped_refptr channel_host = PluginChannelHost::GetPluginChannelHost(channel_name, ipc_message_loop); if (!channel_host.get()) return false; int instance_id; bool result = channel_host->Send(new PluginMsg_CreateInstance( mime_type_, &instance_id)); if (!result) return false; plugin_path_ = plugin_path; channel_host_ = channel_host; instance_id_ = instance_id; channel_host_->AddRoute(instance_id_, this, false); // Now tell the PluginInstance in the plugin process to initialize. PluginMsg_Init_Params params; params.containing_window = gfx::NativeViewFromId(render_view_->host_window()); params.url = url; for (int i = 0; i < argc; ++i) { params.arg_names.push_back(argn[i]); params.arg_values.push_back(argv[i]); if (LowerCaseEqualsASCII(params.arg_names.back(), "wmode") && LowerCaseEqualsASCII(params.arg_values.back(), "transparent")) { transparent_ = true; } } params.load_manually = load_manually; params.modal_dialog_event = render_view_->modal_dialog_event()->handle(); plugin_ = plugin; result = false; IPC::Message* msg = new PluginMsg_Init(instance_id_, params, &result); Send(msg); return result; } bool WebPluginDelegateProxy::Send(IPC::Message* msg) { if (!channel_host_) { DLOG(WARNING) << "dropping message because channel host is null"; delete msg; return false; } return channel_host_->Send(msg); } void WebPluginDelegateProxy::SendJavaScriptStream(const std::string& url, const std::wstring& result, bool success, bool notify_needed, int notify_data) { PluginMsg_SendJavaScriptStream* msg = new PluginMsg_SendJavaScriptStream(instance_id_, url, result, success, notify_needed, notify_data); Send(msg); } void WebPluginDelegateProxy::DidReceiveManualResponse( const std::string& url, const std::string& mime_type, const std::string& headers, uint32 expected_length, uint32 last_modified) { PluginMsg_DidReceiveResponseParams params; params.id = 0; params.mime_type = mime_type; params.headers = headers; params.expected_length = expected_length; params.last_modified = last_modified; Send(new PluginMsg_DidReceiveManualResponse(instance_id_, url, params)); } void WebPluginDelegateProxy::DidReceiveManualData(const char* buffer, int length) { DCHECK(length > 0); std::vector data; data.resize(static_cast(length)); memcpy(&data.front(), buffer, length); Send(new PluginMsg_DidReceiveManualData(instance_id_, data)); } void WebPluginDelegateProxy::DidFinishManualLoading() { Send(new PluginMsg_DidFinishManualLoading(instance_id_)); } void WebPluginDelegateProxy::DidManualLoadFail() { Send(new PluginMsg_DidManualLoadFail(instance_id_)); } FilePath WebPluginDelegateProxy::GetPluginPath() { return plugin_path_; } void WebPluginDelegateProxy::InstallMissingPlugin() { Send(new PluginMsg_InstallMissingPlugin(instance_id_)); } void WebPluginDelegateProxy::OnMessageReceived(const IPC::Message& msg) { IPC_BEGIN_MESSAGE_MAP(WebPluginDelegateProxy, msg) IPC_MESSAGE_HANDLER(PluginHostMsg_SetWindow, OnSetWindow) IPC_MESSAGE_HANDLER(PluginHostMsg_CancelResource, OnCancelResource) IPC_MESSAGE_HANDLER(PluginHostMsg_InvalidateRect, OnInvalidateRect) IPC_MESSAGE_HANDLER(PluginHostMsg_GetWindowScriptNPObject, OnGetWindowScriptNPObject) IPC_MESSAGE_HANDLER(PluginHostMsg_GetPluginElement, OnGetPluginElement) IPC_MESSAGE_HANDLER(PluginHostMsg_SetCookie, OnSetCookie) IPC_MESSAGE_HANDLER(PluginHostMsg_GetCookies, OnGetCookies) IPC_MESSAGE_HANDLER(PluginHostMsg_ShowModalHTMLDialog, OnShowModalHTMLDialog) IPC_MESSAGE_HANDLER(PluginHostMsg_MissingPluginStatus, OnMissingPluginStatus) IPC_MESSAGE_HANDLER(PluginHostMsg_URLRequest, OnHandleURLRequest) IPC_MESSAGE_HANDLER(PluginHostMsg_GetCPBrowsingContext, OnGetCPBrowsingContext) IPC_MESSAGE_HANDLER(PluginHostMsg_CancelDocumentLoad, OnCancelDocumentLoad) IPC_MESSAGE_HANDLER(PluginHostMsg_InitiateHTTPRangeRequest, OnInitiateHTTPRangeRequest) IPC_MESSAGE_UNHANDLED_ERROR() IPC_END_MESSAGE_MAP() } void WebPluginDelegateProxy::OnChannelError() { if (plugin_) plugin_->Invalidate(); render_view_->PluginCrashed(plugin_path_); } void WebPluginDelegateProxy::UpdateGeometry( const gfx::Rect& window_rect, const gfx::Rect& clip_rect) { plugin_rect_ = window_rect; if (!windowless_) { deferred_clip_rect_ = clip_rect; send_deferred_update_geometry_ = true; return; } HANDLE transport_store_handle = NULL; HANDLE background_store_handle = NULL; if (!backing_store_canvas_.get() || (window_rect.width() != backing_store_canvas_->getDevice()->width() || window_rect.height() != backing_store_canvas_->getDevice()->height())) { // Create a shared memory section that the plugin paints into // asynchronously. ResetWindowlessBitmaps(); if (!window_rect.IsEmpty()) { if (!CreateBitmap(&backing_store_, &backing_store_canvas_) || !CreateBitmap(&transport_store_, &transport_store_canvas_) || (transparent_ && !CreateBitmap(&background_store_, &background_store_canvas_))) { DCHECK(false); ResetWindowlessBitmaps(); return; } transport_store_handle = transport_store_->handle(); if (background_store_.get()) background_store_handle = background_store_->handle(); } } IPC::Message* msg = new PluginMsg_UpdateGeometry( instance_id_, window_rect, clip_rect, transport_store_handle, background_store_handle); msg->set_unblock(true); Send(msg); } // Copied from render_widget.cc static size_t GetPaintBufSize(const gfx::Rect& rect) { // TODO(darin): protect against overflow return 4 * rect.width() * rect.height(); } void WebPluginDelegateProxy::ResetWindowlessBitmaps() { backing_store_.reset(); transport_store_.reset(); backing_store_canvas_.reset(); transport_store_canvas_.reset(); background_store_.reset(); background_store_canvas_.release(); backing_store_painted_ = gfx::Rect(); } bool WebPluginDelegateProxy::CreateBitmap( scoped_ptr* memory, scoped_ptr* canvas) { size_t size = GetPaintBufSize(plugin_rect_); scoped_ptr new_shared_memory(new base::SharedMemory()); if (!new_shared_memory->Create(L"", false, true, size)) return false; scoped_ptr new_canvas(new skia::PlatformCanvasWin); if (!new_canvas->initialize(plugin_rect_.width(), plugin_rect_.height(), true, new_shared_memory->handle())) { return false; } memory->swap(new_shared_memory); canvas->swap(new_canvas); return true; } void WebPluginDelegateProxy::Paint(HDC hdc, const gfx::Rect& damaged_rect) { // If the plugin is no longer connected (channel crashed) draw a crashed // plugin bitmap if (!channel_host_->channel_valid()) { PaintSadPlugin(hdc, damaged_rect); return; } // No paint events for windowed plugins. if (!windowless_) return; // We got a paint before the plugin's coordinates, so there's no buffer to // copy from. if (!backing_store_canvas_.get()) return; // Limit the damaged rectangle to whatever is contained inside the plugin // rectangle, as that's the rectangle that we'll bitblt to the hdc. gfx::Rect rect = damaged_rect.Intersect(plugin_rect_); bool background_changed = false; if (background_store_canvas_.get() && BackgroundChanged(hdc, rect)) { background_changed = true; HDC background_hdc = background_store_canvas_->getTopPlatformDevice().getBitmapDC(); BitBlt(background_hdc, rect.x()-plugin_rect_.x(), rect.y()-plugin_rect_.y(), rect.width(), rect.height(), hdc, rect.x(), rect.y(), SRCCOPY); } gfx::Rect offset_rect = rect; offset_rect.Offset(-plugin_rect_.x(), -plugin_rect_.y()); if (background_changed || !backing_store_painted_.Contains(offset_rect)) { Send(new PluginMsg_Paint(instance_id_, offset_rect)); CopyFromTransportToBacking(offset_rect); } HDC backing_hdc = backing_store_canvas_->getTopPlatformDevice().getBitmapDC(); BitBlt(hdc, rect.x(), rect.y(), rect.width(), rect.height(), backing_hdc, rect.x()-plugin_rect_.x(), rect.y()-plugin_rect_.y(), SRCCOPY); if (invalidate_pending_) { // Only send the PaintAck message if this paint is in response to an // invalidate from the plugin, since this message acts as an access token // to ensure only one process is using the transport dib at a time. invalidate_pending_ = false; Send(new PluginMsg_DidPaint(instance_id_)); } } bool WebPluginDelegateProxy::BackgroundChanged( HDC hdc, const gfx::Rect& rect) { HBITMAP hbitmap = static_cast(GetCurrentObject(hdc, OBJ_BITMAP)); if (hbitmap == NULL) { NOTREACHED(); return true; } BITMAP bitmap = { 0 }; int result = GetObject(hbitmap, sizeof(bitmap), &bitmap); if (!result) { NOTREACHED(); return true; } XFORM xf; if (!GetWorldTransform(hdc, &xf)) { NOTREACHED(); return true; } int row_byte_size = rect.width() * (bitmap.bmBitsPixel / 8); for (int y = rect.y(); y < rect.bottom(); y++) { char* hdc_row_start = static_cast(bitmap.bmBits) + (y + static_cast(xf.eDy)) * bitmap.bmWidthBytes + (rect.x() + static_cast(xf.eDx)) * (bitmap.bmBitsPixel / 8); // getAddr32 doesn't use the translation units, so we have to subtract // the plugin origin from the coordinates. uint32_t* canvas_row_start = background_store_canvas_->getDevice()->accessBitmap(true).getAddr32( rect.x() - plugin_rect_.x(), y - plugin_rect_.y()); if (memcmp(hdc_row_start, canvas_row_start, row_byte_size) != 0) return true; } return false; } void WebPluginDelegateProxy::Print(HDC hdc) { PluginMsg_PrintResponse_Params params = { 0 }; Send(new PluginMsg_Print(instance_id_, ¶ms)); base::SharedMemory memory(params.shared_memory, true); if (!memory.Map(params.size)) { NOTREACHED(); return; } gfx::Emf emf; if (!emf.CreateFromData(memory.memory(), params.size)) { NOTREACHED(); return; } // Playback the buffer. emf.Playback(hdc, NULL); } NPObject* WebPluginDelegateProxy::GetPluginScriptableObject() { if (npobject_) return NPN_RetainObject(npobject_); int route_id = MSG_ROUTING_NONE; void* npobject_ptr; Send(new PluginMsg_GetPluginScriptableObject( instance_id_, &route_id, &npobject_ptr)); if (route_id == MSG_ROUTING_NONE) return NULL; npobject_ = NPObjectProxy::Create( channel_host_.get(), route_id, npobject_ptr, NULL); return NPN_RetainObject(npobject_); } void WebPluginDelegateProxy::DidFinishLoadWithReason(NPReason reason) { Send(new PluginMsg_DidFinishLoadWithReason(instance_id_, reason)); } void WebPluginDelegateProxy::SetFocus() { Send(new PluginMsg_SetFocus(instance_id_)); } bool WebPluginDelegateProxy::HandleEvent(NPEvent* event, WebCursor* cursor) { bool handled; // A windowless plugin can enter a modal loop in the context of a // NPP_HandleEvent call, in which case we need to pump messages to // the plugin. We pass of the corresponding event handle to the // plugin process, which is set if the plugin does enter a modal loop. IPC::SyncMessage* message = new PluginMsg_HandleEvent(instance_id_, *event, &handled, cursor); message->set_pump_messages_event(modal_loop_pump_messages_event_.get()); Send(message); return handled; } int WebPluginDelegateProxy::GetProcessId() { return channel_host_->peer_pid(); } void WebPluginDelegateProxy::OnSetWindow( HWND window, HANDLE modal_loop_pump_messages_event) { windowless_ = window == NULL; if (plugin_) plugin_->SetWindow(window, modal_loop_pump_messages_event); DCHECK(modal_loop_pump_messages_event_ == NULL); if (modal_loop_pump_messages_event) { modal_loop_pump_messages_event_.reset( new base::WaitableEvent(modal_loop_pump_messages_event)); } else { modal_loop_pump_messages_event_.reset(); } } void WebPluginDelegateProxy::OnCancelResource(int id) { if (plugin_) plugin_->CancelResource(id); } void WebPluginDelegateProxy::OnInvalidateRect(const gfx::Rect& rect) { if (!plugin_) return; invalidate_pending_ = true; CopyFromTransportToBacking(rect); plugin_->InvalidateRect(rect); } void WebPluginDelegateProxy::OnGetWindowScriptNPObject( int route_id, bool* success, void** npobject_ptr) { *success = false; NPObject* npobject = NULL; if (plugin_) npobject = plugin_->GetWindowScriptNPObject(); if (!npobject) return; // The stub will delete itself when the proxy tells it that it's released, or // otherwise when the channel is closed. NPObjectStub* stub = new NPObjectStub(npobject, channel_host_.get(), route_id); window_script_object_ = stub; window_script_object_->set_proxy(this); *success = true; *npobject_ptr = npobject; } void WebPluginDelegateProxy::OnGetPluginElement( int route_id, bool* success, void** npobject_ptr) { *success = false; NPObject* npobject = NULL; if (plugin_) npobject = plugin_->GetPluginElement(); if (!npobject) return; // The stub will delete itself when the proxy tells it that it's released, or // otherwise when the channel is closed. NPObjectStub* stub = new NPObjectStub(npobject, channel_host_.get(), route_id); *success = true; *npobject_ptr = npobject; } void WebPluginDelegateProxy::OnSetCookie(const GURL& url, const GURL& policy_url, const std::string& cookie) { if (plugin_) plugin_->SetCookie(url, policy_url, cookie); } void WebPluginDelegateProxy::OnGetCookies(const GURL& url, const GURL& policy_url, std::string* cookies) { DCHECK(cookies); if (plugin_) *cookies = plugin_->GetCookies(url, policy_url); } void WebPluginDelegateProxy::OnShowModalHTMLDialog( const GURL& url, int width, int height, const std::string& json_arguments, std::string* json_retval) { DCHECK(json_retval); if (render_view_) render_view_->ShowModalHTMLDialog(url, width, height, json_arguments, json_retval); } void WebPluginDelegateProxy::OnMissingPluginStatus(int status) { if (render_view_) render_view_->OnMissingPluginStatus(this, status); } void WebPluginDelegateProxy::OnGetCPBrowsingContext(uint32* context) { *context = render_view_ ? render_view_->GetCPBrowsingContext() : 0; } void WebPluginDelegateProxy::PaintSadPlugin(HDC hdc, const gfx::Rect& rect) { const int width = plugin_rect_.width(); const int height = plugin_rect_.height(); ChromeCanvas canvas(width, height, false); SkPaint paint; paint.setStyle(SkPaint::kFill_Style); paint.setColor(SK_ColorBLACK); canvas.drawRectCoords(0, 0, SkIntToScalar(width), SkIntToScalar(height), paint); if (!sad_plugin_) { sad_plugin_ = ResourceBundle::GetSharedInstance().GetBitmapNamed( IDR_SAD_PLUGIN); } if (sad_plugin_) { canvas.DrawBitmapInt(*sad_plugin_, std::max(0, (width - sad_plugin_->width())/2), std::max(0, (height - sad_plugin_->height())/2)); } canvas.getTopPlatformDevice().drawToHDC( hdc, plugin_rect_.x(), plugin_rect_.y(), NULL); return; } void WebPluginDelegateProxy::CopyFromTransportToBacking(const gfx::Rect& rect) { if (!backing_store_canvas_.get()) return; // Copy the damaged rect from the transport bitmap to the backing store. HDC backing = backing_store_canvas_->getTopPlatformDevice().getBitmapDC(); HDC transport = transport_store_canvas_->getTopPlatformDevice().getBitmapDC(); BitBlt(backing, rect.x(), rect.y(), rect.width(), rect.height(), transport, rect.x(), rect.y(), SRCCOPY); backing_store_painted_ = backing_store_painted_.Union(rect); } void WebPluginDelegateProxy::OnHandleURLRequest( const PluginHostMsg_URLRequest_Params& params) { const char* data = NULL; if (params.buffer.size()) data = ¶ms.buffer[0]; const char* target = NULL; if (params.target.length()) target = params.target.c_str(); plugin_->HandleURLRequest(params.method.c_str(), params.is_javascript_url, target, static_cast(params.buffer.size()), data, params.is_file_data, params.notify, params.url.c_str(), params.notify_data, params.popups_allowed); } WebPluginResourceClient* WebPluginDelegateProxy::CreateResourceClient( int resource_id, const std::string &url, bool notify_needed, void* notify_data, void* npstream) { ResourceClientProxy* proxy = new ResourceClientProxy(channel_host_, instance_id_); proxy->Initialize(resource_id, url, notify_needed, notify_data, npstream); return proxy; } void WebPluginDelegateProxy::URLRequestRouted(const std::string& url, bool notify_needed, void* notify_data) { Send(new PluginMsg_URLRequestRouted(instance_id_, url, notify_needed, notify_data)); } void WebPluginDelegateProxy::OnCancelDocumentLoad() { plugin_->CancelDocumentLoad(); } void WebPluginDelegateProxy::OnInitiateHTTPRangeRequest( const std::string& url, const std::string& range_info, HANDLE existing_stream, bool notify_needed, HANDLE notify_data) { plugin_->InitiateHTTPRangeRequest(url.c_str(), range_info.c_str(), existing_stream, notify_needed, notify_data); }