diff options
Diffstat (limited to 'chromium/content/browser/browser_plugin')
23 files changed, 4796 insertions, 0 deletions
diff --git a/chromium/content/browser/browser_plugin/OWNERS b/chromium/content/browser/browser_plugin/OWNERS new file mode 100644 index 00000000000..c92691f05e4 --- /dev/null +++ b/chromium/content/browser/browser_plugin/OWNERS @@ -0,0 +1 @@ +fsamuel@chromium.org diff --git a/chromium/content/browser/browser_plugin/browser_plugin_embedder.cc b/chromium/content/browser/browser_plugin/browser_plugin_embedder.cc new file mode 100644 index 00000000000..4a06aca32c9 --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_embedder.cc @@ -0,0 +1,232 @@ +// Copyright (c) 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/browser/browser_plugin/browser_plugin_embedder.h" + +#include "base/values.h" +#include "content/browser/browser_plugin/browser_plugin_guest.h" +#include "content/browser/browser_plugin/browser_plugin_guest_manager.h" +#include "content/browser/browser_plugin/browser_plugin_host_factory.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/common/browser_plugin/browser_plugin_constants.h" +#include "content/common/browser_plugin/browser_plugin_messages.h" +#include "content/common/drag_messages.h" +#include "content/common/gpu/gpu_messages.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/content_browser_client.h" +#include "content/public/browser/native_web_keyboard_event.h" +#include "content/public/browser/user_metrics.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/result_codes.h" +#include "content/public/common/url_constants.h" +#include "net/base/escape.h" + +namespace content { + +// static +BrowserPluginHostFactory* BrowserPluginEmbedder::factory_ = NULL; + +BrowserPluginEmbedder::BrowserPluginEmbedder(WebContentsImpl* web_contents) + : WebContentsObserver(web_contents), + next_get_render_view_request_id_(0) { +} + +BrowserPluginEmbedder::~BrowserPluginEmbedder() { + CleanUp(); +} + +// static +BrowserPluginEmbedder* BrowserPluginEmbedder::Create( + WebContentsImpl* web_contents) { + if (factory_) + return factory_->CreateBrowserPluginEmbedder(web_contents); + return new BrowserPluginEmbedder(web_contents); +} + +void BrowserPluginEmbedder::DragEnteredGuest(BrowserPluginGuest* guest) { + guest_dragging_over_ = guest->AsWeakPtr(); +} + +void BrowserPluginEmbedder::DragLeftGuest(BrowserPluginGuest* guest) { + // Avoid race conditions in switching between guests being hovered over by + // only un-setting if the caller is marked as the guest being dragged over. + if (guest_dragging_over_.get() == guest) { + guest_dragging_over_.reset(); + } +} + +void BrowserPluginEmbedder::StartDrag(BrowserPluginGuest* guest) { + guest_started_drag_ = guest->AsWeakPtr(); +} + +void BrowserPluginEmbedder::StopDrag(BrowserPluginGuest* guest) { + if (guest_started_drag_.get() == guest) { + guest_started_drag_.reset(); + } +} + +void BrowserPluginEmbedder::GetRenderViewHostAtPosition( + int x, int y, const WebContents::GetRenderViewHostCallback& callback) { + // Store the callback so we can call it later when we have the response. + pending_get_render_view_callbacks_.insert( + std::make_pair(next_get_render_view_request_id_, callback)); + Send(new BrowserPluginMsg_PluginAtPositionRequest( + routing_id(), + next_get_render_view_request_id_, + gfx::Point(x, y))); + ++next_get_render_view_request_id_; +} + +void BrowserPluginEmbedder::DidSendScreenRects() { + GetBrowserPluginGuestManager()->DidSendScreenRects( + static_cast<WebContentsImpl*>(web_contents())); +} + +bool BrowserPluginEmbedder::HandleKeyboardEvent( + const NativeWebKeyboardEvent& event) { + return GetBrowserPluginGuestManager()->UnlockMouseIfNecessary( + static_cast<WebContentsImpl*>(web_contents()), event); +} + +void BrowserPluginEmbedder::RenderProcessGone(base::TerminationStatus status) { + CleanUp(); +} + +bool BrowserPluginEmbedder::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(BrowserPluginEmbedder, message) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_AllocateInstanceID, + OnAllocateInstanceID) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_Attach, OnAttach) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_PluginAtPositionResponse, + OnPluginAtPositionResponse) + IPC_MESSAGE_HANDLER_GENERIC(DragHostMsg_UpdateDragCursor, + OnUpdateDragCursor(&handled)); + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void BrowserPluginEmbedder::DragSourceEndedAt(int client_x, int client_y, + int screen_x, int screen_y, WebKit::WebDragOperation operation) { + if (guest_started_drag_.get()) { + gfx::Point guest_offset = + guest_started_drag_->GetScreenCoordinates(gfx::Point()); + guest_started_drag_->DragSourceEndedAt(client_x - guest_offset.x(), + client_y - guest_offset.y(), screen_x, screen_y, operation); + } +} + +void BrowserPluginEmbedder::DragSourceMovedTo(int client_x, int client_y, + int screen_x, int screen_y) { + if (guest_started_drag_.get()) { + gfx::Point guest_offset = + guest_started_drag_->GetScreenCoordinates(gfx::Point()); + guest_started_drag_->DragSourceMovedTo(client_x - guest_offset.x(), + client_y - guest_offset.y(), screen_x, screen_y); + } +} + +void BrowserPluginEmbedder::SystemDragEnded() { + if (guest_started_drag_.get() && + (guest_started_drag_.get() != guest_dragging_over_.get())) + guest_started_drag_->EndSystemDrag(); + guest_started_drag_.reset(); + guest_dragging_over_.reset(); +} + +void BrowserPluginEmbedder::OnUpdateDragCursor(bool* handled) { + *handled = (guest_dragging_over_.get() != NULL); +} + +void BrowserPluginEmbedder::CleanUp() { + // CleanUp gets called when BrowserPluginEmbedder's WebContents goes away + // or the associated RenderViewHost is destroyed or swapped out. Therefore we + // don't need to care about the pending callbacks anymore. + pending_get_render_view_callbacks_.clear(); +} + +BrowserPluginGuestManager* + BrowserPluginEmbedder::GetBrowserPluginGuestManager() { + BrowserPluginGuestManager* guest_manager = static_cast<WebContentsImpl*>( + web_contents())->GetBrowserPluginGuestManager(); + if (!guest_manager) { + guest_manager = BrowserPluginGuestManager::Create(); + web_contents()->GetBrowserContext()->SetUserData( + browser_plugin::kBrowserPluginGuestManagerKeyName, guest_manager); + } + return guest_manager; +} + +void BrowserPluginEmbedder::OnAllocateInstanceID(int request_id) { + int instance_id = GetBrowserPluginGuestManager()->get_next_instance_id(); + Send(new BrowserPluginMsg_AllocateInstanceID_ACK( + routing_id(), request_id, instance_id)); +} + +void BrowserPluginEmbedder::OnAttach( + int instance_id, + const BrowserPluginHostMsg_Attach_Params& params, + const base::DictionaryValue& extra_params) { + if (!GetBrowserPluginGuestManager()->CanEmbedderAccessInstanceIDMaybeKill( + web_contents()->GetRenderProcessHost()->GetID(), instance_id)) + return; + + BrowserPluginGuest* guest = + GetBrowserPluginGuestManager()->GetGuestByInstanceID( + instance_id, web_contents()->GetRenderProcessHost()->GetID()); + + + if (guest) { + // There is an implicit order expectation here: + // 1. The content embedder is made aware of the attachment. + // 2. BrowserPluginGuest::Attach is called. + // 3. The content embedder issues queued events if any that happened + // prior to attachment. + GetContentClient()->browser()->GuestWebContentsAttached( + guest->GetWebContents(), + web_contents(), + extra_params); + guest->Attach(static_cast<WebContentsImpl*>(web_contents()), params); + return; + } + + scoped_ptr<base::DictionaryValue> copy_extra_params(extra_params.DeepCopy()); + guest = GetBrowserPluginGuestManager()->CreateGuest( + web_contents()->GetSiteInstance(), + instance_id, params, + copy_extra_params.Pass()); + if (guest) { + GetContentClient()->browser()->GuestWebContentsAttached( + guest->GetWebContents(), + web_contents(), + extra_params); + guest->Initialize(static_cast<WebContentsImpl*>(web_contents()), params); + } +} + +void BrowserPluginEmbedder::OnPluginAtPositionResponse( + int instance_id, int request_id, const gfx::Point& position) { + const std::map<int, WebContents::GetRenderViewHostCallback>::iterator + callback_iter = pending_get_render_view_callbacks_.find(request_id); + if (callback_iter == pending_get_render_view_callbacks_.end()) + return; + + RenderViewHost* render_view_host; + BrowserPluginGuest* guest = NULL; + if (instance_id != browser_plugin::kInstanceIDNone) { + guest = GetBrowserPluginGuestManager()->GetGuestByInstanceID( + instance_id, web_contents()->GetRenderProcessHost()->GetID()); + } + + if (guest) + render_view_host = guest->GetWebContents()->GetRenderViewHost(); + else // No plugin, use embedder's RenderViewHost. + render_view_host = web_contents()->GetRenderViewHost(); + + callback_iter->second.Run(render_view_host, position.x(), position.y()); + pending_get_render_view_callbacks_.erase(callback_iter); +} + +} // namespace content diff --git a/chromium/content/browser/browser_plugin/browser_plugin_embedder.h b/chromium/content/browser/browser_plugin/browser_plugin_embedder.h new file mode 100644 index 00000000000..52610632f59 --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_embedder.h @@ -0,0 +1,139 @@ +// Copyright (c) 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. + +// A BrowserPluginEmbedder handles messages coming from a BrowserPlugin's +// embedder that are not directed at any particular existing guest process. +// In the beginning, when a BrowserPlugin instance in the embedder renderer +// process requests an initial navigation, the WebContents for that renderer +// renderer creates a BrowserPluginEmbedder for itself. The +// BrowserPluginEmbedder, in turn, forwards the requests to a +// BrowserPluginGuestManager, which creates and manages the lifetime of the new +// guest. + +#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_EMBEDDER_H_ +#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_EMBEDDER_H_ + +#include <map> + +#include "base/memory/weak_ptr.h" +#include "base/values.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_observer.h" +#include "third_party/WebKit/public/web/WebDragOperation.h" + +struct BrowserPluginHostMsg_Attach_Params; +struct BrowserPluginHostMsg_ResizeGuest_Params; + +namespace gfx { +class Point; +} + +namespace content { + +class BrowserPluginGuest; +class BrowserPluginGuestManager; +class BrowserPluginHostFactory; +class RenderWidgetHostImpl; +class WebContentsImpl; +struct NativeWebKeyboardEvent; + +class CONTENT_EXPORT BrowserPluginEmbedder : public WebContentsObserver { + public: + virtual ~BrowserPluginEmbedder(); + + static BrowserPluginEmbedder* Create(WebContentsImpl* web_contents); + + // Returns the RenderViewHost at a point (|x|, |y|) asynchronously via + // |callback|. We need a roundtrip to renderer process to get this + // information. + void GetRenderViewHostAtPosition( + int x, + int y, + const WebContents::GetRenderViewHostCallback& callback); + + // Called when embedder's |rwh| has sent screen rects to renderer. + void DidSendScreenRects(); + + // Called when embedder's WebContentsImpl has unhandled keyboard input. + // Returns whether the BrowserPlugin has handled the keyboard event. + // Currently we are only interested in checking for the escape key to + // unlock hte guest's pointer lock. + bool HandleKeyboardEvent(const NativeWebKeyboardEvent& event); + + // Overrides factory for testing. Default (NULL) value indicates regular + // (non-test) environment. + static void set_factory_for_testing(BrowserPluginHostFactory* factory) { + factory_ = factory; + } + + // WebContentsObserver implementation. + virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + void DragSourceEndedAt(int client_x, int client_y, int screen_x, + int screen_y, WebKit::WebDragOperation operation); + + void DragSourceMovedTo(int client_x, int client_y, + int screen_x, int screen_y); + + void OnUpdateDragCursor(bool* handled); + + void DragEnteredGuest(BrowserPluginGuest* guest); + + void DragLeftGuest(BrowserPluginGuest* guest); + + void StartDrag(BrowserPluginGuest* guest); + + void StopDrag(BrowserPluginGuest* guest); + + void SystemDragEnded(); + + private: + friend class TestBrowserPluginEmbedder; + + BrowserPluginEmbedder(WebContentsImpl* web_contents); + + void CleanUp(); + + BrowserPluginGuestManager* GetBrowserPluginGuestManager(); + + // Message handlers. + + void OnAllocateInstanceID(int request_id); + void OnAttach(int instance_id, + const BrowserPluginHostMsg_Attach_Params& params, + const base::DictionaryValue& extra_params); + void OnPluginAtPositionResponse(int instance_id, + int request_id, + const gfx::Point& position); + + // Static factory instance (always NULL for non-test). + static BrowserPluginHostFactory* factory_; + + // Map that contains outstanding queries to |GetRenderViewHostAtPosition|. + // We need a roundtrip to the renderer process to retrieve the answer, + // so we store these callbacks until we hear back from the renderer. + typedef std::map<int, WebContents::GetRenderViewHostCallback> + GetRenderViewHostCallbackMap; + GetRenderViewHostCallbackMap pending_get_render_view_callbacks_; + // Next request id for BrowserPluginMsg_PluginAtPositionRequest query. + int next_get_render_view_request_id_; + + // Used to correctly update the cursor when dragging over a guest, and to + // handle a race condition when dropping onto the guest that started the drag + // (the race is that the dragend message arrives before the drop message so + // the drop never takes place). + // crbug.com/233571 + base::WeakPtr<BrowserPluginGuest> guest_dragging_over_; + + // Pointer to the guest that started the drag, used to forward necessary drag + // status messages to the correct guest. + base::WeakPtr<BrowserPluginGuest> guest_started_drag_; + + DISALLOW_COPY_AND_ASSIGN(BrowserPluginEmbedder); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_EMBEDDER_H_ diff --git a/chromium/content/browser/browser_plugin/browser_plugin_geolocation_permission_context.cc b/chromium/content/browser/browser_plugin/browser_plugin_geolocation_permission_context.cc new file mode 100644 index 00000000000..8a803c47120 --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_geolocation_permission_context.cc @@ -0,0 +1,94 @@ +// Copyright 2013 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/browser/browser_plugin/browser_plugin_geolocation_permission_context.h" + +#include "base/bind.h" +#include "content/browser/browser_plugin/browser_plugin_guest.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" + +namespace content { + +BrowserPluginGeolocationPermissionContext:: + BrowserPluginGeolocationPermissionContext() { +} + +BrowserPluginGeolocationPermissionContext:: + ~BrowserPluginGeolocationPermissionContext() { +} + +void BrowserPluginGeolocationPermissionContext::RequestGeolocationPermission( + int render_process_id, + int render_view_id, + int bridge_id, + const GURL& requesting_frame, + base::Callback<void(bool)> callback) { + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind( + &BrowserPluginGeolocationPermissionContext:: + RequestGeolocationPermission, + this, + render_process_id, + render_view_id, + bridge_id, + requesting_frame, + callback)); + return; + } + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // Note that callback.Run(true) allows geolocation access, callback.Run(false) + // denies geolocation access. + // We need to go to the renderer to ask embedder's js if we are allowed to + // have geolocation access. + RenderViewHost* rvh = RenderViewHost::FromID(render_process_id, + render_view_id); + if (rvh) { + DCHECK(rvh->GetProcess()->IsGuest()); + WebContentsImpl* guest_web_contents = + static_cast<WebContentsImpl*>(rvh->GetDelegate()->GetAsWebContents()); + BrowserPluginGuest* guest = guest_web_contents->GetBrowserPluginGuest(); + guest->AskEmbedderForGeolocationPermission(bridge_id, + requesting_frame, + callback); + } +} + +void BrowserPluginGeolocationPermissionContext:: + CancelGeolocationPermissionRequest(int render_process_id, + int render_view_id, + int bridge_id, + const GURL& requesting_frame) { + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind( + &BrowserPluginGeolocationPermissionContext:: + CancelGeolocationPermissionRequest, + this, + render_process_id, + render_view_id, + bridge_id, + requesting_frame)); + return; + } + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + RenderViewHost* rvh = RenderViewHost::FromID(render_process_id, + render_view_id); + if (rvh) { + DCHECK(rvh->GetProcess()->IsGuest()); + WebContentsImpl* guest_web_contents = + static_cast<WebContentsImpl*>(rvh->GetDelegate()->GetAsWebContents()); + BrowserPluginGuest* guest = guest_web_contents->GetBrowserPluginGuest(); + if (guest) + guest->CancelGeolocationRequest(bridge_id); + } +} + +} // namespace content diff --git a/chromium/content/browser/browser_plugin/browser_plugin_geolocation_permission_context.h b/chromium/content/browser/browser_plugin/browser_plugin_geolocation_permission_context.h new file mode 100644 index 00000000000..27cc9e3e4a7 --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_geolocation_permission_context.h @@ -0,0 +1,44 @@ +// Copyright 2013 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. + +#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GEOLOCATION_PERMISSION_CONTEXT_H_ +#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GEOLOCATION_PERMISSION_CONTEXT_H_ + +#include "content/public/browser/geolocation_permission_context.h" + +namespace content { + +// Browser plugin specific implementation of GeolocationPermissionContext. +// It manages Geolocation permissions flow for BrowserPluginGuest. When a guest +// requests gelocation permission, it delegates the request to embedder though +// embedder's javascript api. +// This runs on the I/O thread. We have to return to UI thread to talk to a +// BrowserPluginGuest. +class BrowserPluginGeolocationPermissionContext : + public GeolocationPermissionContext { + public: + BrowserPluginGeolocationPermissionContext(); + + // GeolocationPermissionContext implementation: + virtual void RequestGeolocationPermission( + int render_process_id, + int render_view_id, + int bridge_id, + const GURL& requesting_frame, + base::Callback<void(bool)> callback) OVERRIDE; + virtual void CancelGeolocationPermissionRequest( + int render_process_id, + int render_view_id, + int bridge_id, + const GURL& requesting_frame) OVERRIDE; + + private: + virtual ~BrowserPluginGeolocationPermissionContext(); + + DISALLOW_COPY_AND_ASSIGN(BrowserPluginGeolocationPermissionContext); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GEOLOCATION_PERMISSION_CONTEXT_H_ diff --git a/chromium/content/browser/browser_plugin/browser_plugin_guest.cc b/chromium/content/browser/browser_plugin/browser_plugin_guest.cc new file mode 100644 index 00000000000..31250b7d626 --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_guest.cc @@ -0,0 +1,1667 @@ +// Copyright (c) 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/browser/browser_plugin/browser_plugin_guest.h" + +#include <algorithm> + +#include "base/command_line.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/browser_plugin/browser_plugin_embedder.h" +#include "content/browser/browser_plugin/browser_plugin_guest_helper.h" +#include "content/browser/browser_plugin/browser_plugin_guest_manager.h" +#include "content/browser/browser_plugin/browser_plugin_host_factory.h" +#include "content/browser/browser_thread_impl.h" +#include "content/browser/loader/resource_dispatcher_host_impl.h" +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/browser/renderer_host/render_widget_host_impl.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/browser/web_contents/web_contents_view_guest.h" +#include "content/common/browser_plugin/browser_plugin_constants.h" +#include "content/common/browser_plugin/browser_plugin_messages.h" +#include "content/common/content_constants_internal.h" +#include "content/common/drag_messages.h" +#include "content/common/gpu/gpu_messages.h" +#include "content/common/input_messages.h" +#include "content/common/view_messages.h" +#include "content/port/browser/render_view_host_delegate_view.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/content_browser_client.h" +#include "content/public/browser/geolocation_permission_context.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/public/browser/resource_request_details.h" +#include "content/public/browser/user_metrics.h" +#include "content/public/browser/web_contents_view.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/drop_data.h" +#include "content/public/common/media_stream_request.h" +#include "content/public/common/result_codes.h" +#include "net/url_request/url_request.h" +#include "third_party/WebKit/public/web/WebCursorInfo.h" +#include "ui/base/keycodes/keyboard_codes.h" +#include "ui/surface/transport_dib.h" +#include "webkit/common/resource_type.h" + +#if defined(OS_MACOSX) +#include "content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h" +#endif + +namespace content { + +// static +BrowserPluginHostFactory* BrowserPluginGuest::factory_ = NULL; + +// Parent class for the various types of permission requests, each of which +// should be able to handle the response to their permission request. +class BrowserPluginGuest::PermissionRequest : + public base::RefCounted<BrowserPluginGuest::PermissionRequest> { + public: + virtual void Respond(bool should_allow, const std::string& user_input) = 0; + protected: + PermissionRequest() { + RecordAction(UserMetricsAction("BrowserPlugin.Guest.PermissionRequest")); + } + virtual ~PermissionRequest() {} + // Friend RefCounted so that the dtor can be non-public. + friend class base::RefCounted<BrowserPluginGuest::PermissionRequest>; +}; + +class BrowserPluginGuest::DownloadRequest : public PermissionRequest { + public: + explicit DownloadRequest(base::Callback<void(bool)> callback) + : callback_(callback) { + RecordAction( + UserMetricsAction("BrowserPlugin.Guest.PermissionRequest.Download")); + } + virtual void Respond(bool should_allow, + const std::string& user_input) OVERRIDE { + callback_.Run(should_allow); + } + + private: + virtual ~DownloadRequest() {} + base::Callback<void(bool)> callback_; +}; + +class BrowserPluginGuest::GeolocationRequest : public PermissionRequest { + public: + GeolocationRequest(GeolocationCallback callback, + int bridge_id, + base::WeakPtrFactory<BrowserPluginGuest>* weak_ptr_factory) + : callback_(callback), + bridge_id_(bridge_id), + weak_ptr_factory_(weak_ptr_factory) { + RecordAction( + UserMetricsAction("BrowserPlugin.Guest.PermissionRequest.Geolocation")); + } + + virtual void Respond(bool should_allow, + const std::string& user_input) OVERRIDE { + base::WeakPtr<BrowserPluginGuest> guest(weak_ptr_factory_->GetWeakPtr()); + + WebContents* web_contents = guest->embedder_web_contents(); + if (should_allow && web_contents) { + // If renderer side embedder decides to allow gelocation, we need to check + // if the app/embedder itself has geolocation access. + BrowserContext* browser_context = web_contents->GetBrowserContext(); + if (browser_context) { + GeolocationPermissionContext* geolocation_context = + browser_context->GetGeolocationPermissionContext(); + if (geolocation_context) { + base::Callback<void(bool)> geolocation_callback = base::Bind( + &BrowserPluginGuest::SetGeolocationPermission, + guest, + callback_, + bridge_id_); + geolocation_context->RequestGeolocationPermission( + web_contents->GetRenderProcessHost()->GetID(), + web_contents->GetRoutingID(), + // The geolocation permission request here is not initiated + // through WebGeolocationPermissionRequest. We are only interested + // in the fact whether the embedder/app has geolocation + // permission. Therefore we use an invalid |bridge_id|. + -1 /* bridge_id */, + web_contents->GetLastCommittedURL(), + geolocation_callback); + return; + } + } + } + guest->SetGeolocationPermission(callback_, bridge_id_, false); + } + + private: + virtual ~GeolocationRequest() {} + base::Callback<void(bool)> callback_; + int bridge_id_; + base::WeakPtrFactory<BrowserPluginGuest>* weak_ptr_factory_; +}; + +class BrowserPluginGuest::MediaRequest : public PermissionRequest { + public: + MediaRequest(const MediaStreamRequest& request, + const MediaResponseCallback& callback, + BrowserPluginGuest* guest) + : request_(request), + callback_(callback), + guest_(guest) { + RecordAction( + UserMetricsAction("BrowserPlugin.Guest.PermissionRequest.Media")); + } + + virtual void Respond(bool should_allow, + const std::string& user_input) OVERRIDE { + WebContentsImpl* web_contents = guest_->embedder_web_contents(); + if (should_allow && web_contents) { + // Re-route the request to the embedder's WebContents; the guest gets the + // permission this way. + web_contents->RequestMediaAccessPermission(request_, callback_); + } else { + // Deny the request. + callback_.Run(MediaStreamDevices(), scoped_ptr<MediaStreamUI>()); + } + } + + private: + virtual ~MediaRequest() {} + MediaStreamRequest request_; + MediaResponseCallback callback_; + BrowserPluginGuest* guest_; +}; + +class BrowserPluginGuest::NewWindowRequest : public PermissionRequest { + public: + NewWindowRequest(int instance_id, BrowserPluginGuest* guest) + : instance_id_(instance_id), + guest_(guest) { + RecordAction( + UserMetricsAction("BrowserPlugin.Guest.PermissionRequest.NewWindow")); + } + + virtual void Respond(bool should_allow, + const std::string& user_input) OVERRIDE { + int embedder_render_process_id = + guest_->embedder_web_contents()->GetRenderProcessHost()->GetID(); + BrowserPluginGuest* guest = + guest_->GetWebContents()->GetBrowserPluginGuestManager()-> + GetGuestByInstanceID(instance_id_, embedder_render_process_id); + if (!guest) { + LOG(INFO) << "Guest not found. Instance ID: " << instance_id_; + return; + } + + // If we do not destroy the guest then we allow the new window. + if (!should_allow) + guest->Destroy(); + } + + private: + virtual ~NewWindowRequest() {} + int instance_id_; + BrowserPluginGuest* guest_; +}; + +class BrowserPluginGuest::JavaScriptDialogRequest : public PermissionRequest { + public: + JavaScriptDialogRequest(const DialogClosedCallback& callback) + : callback_(callback) { + RecordAction( + UserMetricsAction( + "BrowserPlugin.Guest.PermissionRequest.JavaScriptDialog")); + } + + virtual void Respond(bool should_allow, + const std::string& user_input) OVERRIDE { + callback_.Run(should_allow, UTF8ToUTF16(user_input)); + } + + private: + virtual ~JavaScriptDialogRequest() {} + DialogClosedCallback callback_; +}; + +class BrowserPluginGuest::PointerLockRequest : public PermissionRequest { + public: + PointerLockRequest(BrowserPluginGuest* guest) + : guest_(guest) { + RecordAction( + UserMetricsAction("BrowserPlugin.Guest.PermissionRequest.PointerLock")); + } + + virtual void Respond(bool should_allow, + const std::string& user_input) OVERRIDE { + guest_->SendMessageToEmbedder( + new BrowserPluginMsg_SetMouseLock(guest_->instance_id(), should_allow)); + } + + private: + virtual ~PointerLockRequest() {} + BrowserPluginGuest* guest_; +}; + +namespace { +const size_t kNumMaxOutstandingPermissionRequests = 1024; + +std::string WindowOpenDispositionToString( + WindowOpenDisposition window_open_disposition) { + switch (window_open_disposition) { + case IGNORE_ACTION: + return "ignore"; + case SAVE_TO_DISK: + return "save_to_disk"; + case CURRENT_TAB: + return "current_tab"; + case NEW_BACKGROUND_TAB: + return "new_background_tab"; + case NEW_FOREGROUND_TAB: + return "new_foreground_tab"; + case NEW_WINDOW: + return "new_window"; + case NEW_POPUP: + return "new_popup"; + default: + NOTREACHED() << "Unknown Window Open Disposition"; + return "ignore"; + } +} + +std::string JavaScriptMessageTypeToString(JavaScriptMessageType message_type) { + switch (message_type) { + case JAVASCRIPT_MESSAGE_TYPE_ALERT: + return "alert"; + case JAVASCRIPT_MESSAGE_TYPE_CONFIRM: + return "confirm"; + case JAVASCRIPT_MESSAGE_TYPE_PROMPT: + return "prompt"; + default: + NOTREACHED() << "Unknown JavaScript Message Type."; + return "unknown"; + } +} + +// Called on IO thread. +static std::string RetrieveDownloadURLFromRequestId( + RenderViewHost* render_view_host, + int url_request_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + int render_process_id = render_view_host->GetProcess()->GetID(); + GlobalRequestID global_id(render_process_id, url_request_id); + net::URLRequest* url_request = + ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id); + if (url_request) + return url_request->url().possibly_invalid_spec(); + return std::string(); +} + +} // namespace + +class BrowserPluginGuest::EmbedderRenderViewHostObserver + : public RenderViewHostObserver { + public: + explicit EmbedderRenderViewHostObserver(BrowserPluginGuest* guest) + : RenderViewHostObserver( + guest->embedder_web_contents()->GetRenderViewHost()), + browser_plugin_guest_(guest) { + } + + virtual ~EmbedderRenderViewHostObserver() { + } + + // RenderViewHostObserver: + virtual void RenderViewHostDestroyed( + RenderViewHost* render_view_host) OVERRIDE { + browser_plugin_guest_->embedder_web_contents_ = NULL; + browser_plugin_guest_->Destroy(); + } + + private: + BrowserPluginGuest* browser_plugin_guest_; + + DISALLOW_COPY_AND_ASSIGN(EmbedderRenderViewHostObserver); +}; + +BrowserPluginGuest::BrowserPluginGuest( + int instance_id, + WebContentsImpl* web_contents, + BrowserPluginGuest* opener, + bool has_render_view) + : WebContentsObserver(web_contents), + weak_ptr_factory_(this), + embedder_web_contents_(NULL), + instance_id_(instance_id), + damage_buffer_sequence_id_(0), + damage_buffer_size_(0), + damage_buffer_scale_factor_(1.0f), + guest_device_scale_factor_(1.0f), + guest_hang_timeout_( + base::TimeDelta::FromMilliseconds(kHungRendererDelayMs)), + focused_(false), + mouse_locked_(false), + pending_lock_request_(false), + embedder_visible_(true), + next_permission_request_id_(browser_plugin::kInvalidPermissionRequestID), + has_render_view_(has_render_view), + last_seen_auto_size_enabled_(false) { + DCHECK(web_contents); + web_contents->SetDelegate(this); + if (opener) + opener_ = opener->AsWeakPtr(); + GetWebContents()->GetBrowserPluginGuestManager()->AddGuest(instance_id_, + GetWebContents()); +} + +bool BrowserPluginGuest::AddMessageToConsole(WebContents* source, + int32 level, + const string16& message, + int32 line_no, + const string16& source_id) { + if (!delegate_) + return false; + + delegate_->AddMessageToConsole(level, message, line_no, source_id); + return true; +} + +void BrowserPluginGuest::DestroyUnattachedWindows() { + // Destroy() reaches in and removes the BrowserPluginGuest from its opener's + // pending_new_windows_ set. To avoid mutating the set while iterating, we + // create a copy of the pending new windows set and iterate over the copy. + PendingWindowMap pending_new_windows(pending_new_windows_); + // Clean up unattached new windows opened by this guest. + for (PendingWindowMap::const_iterator it = pending_new_windows.begin(); + it != pending_new_windows.end(); ++it) { + it->first->Destroy(); + } + // All pending windows should be removed from the set after Destroy() is + // called on all of them. + DCHECK_EQ(0ul, pending_new_windows_.size()); +} + +void BrowserPluginGuest::RespondToPermissionRequest( + int request_id, + bool should_allow, + const std::string& user_input) { + RequestMap::iterator request_itr = permission_request_map_.find(request_id); + if (request_itr == permission_request_map_.end()) { + LOG(INFO) << "Not a valid request ID."; + return; + } + request_itr->second->Respond(should_allow, user_input); + permission_request_map_.erase(request_itr); +} + +int BrowserPluginGuest::RequestPermission( + BrowserPluginPermissionType permission_type, + scoped_refptr<BrowserPluginGuest::PermissionRequest> request, + const base::DictionaryValue& request_info) { + if (!delegate_) { + request->Respond(false, ""); + return browser_plugin::kInvalidPermissionRequestID; + } + + int request_id = ++next_permission_request_id_; + permission_request_map_[request_id] = request; + + BrowserPluginGuestDelegate::PermissionResponseCallback callback = + base::Bind(&BrowserPluginGuest::RespondToPermissionRequest, + AsWeakPtr(), + request_id); + // If BrowserPluginGuestDelegate hasn't handled the permission then we simply + // reject it immediately. + if (!delegate_->RequestPermission(permission_type, request_info, callback)) + callback.Run(false, ""); + + return request_id; +} + +void BrowserPluginGuest::Destroy() { + if (!attached() && opener()) + opener()->pending_new_windows_.erase(this); + DestroyUnattachedWindows(); + GetWebContents()->GetBrowserPluginGuestManager()->RemoveGuest(instance_id_); + delete GetWebContents(); +} + +bool BrowserPluginGuest::OnMessageReceivedFromEmbedder( + const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(BrowserPluginGuest, message) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_BuffersSwappedACK, + OnSwapBuffersACK) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_CompositorFrameACK, + OnCompositorFrameACK) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_DragStatusUpdate, + OnDragStatusUpdate) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_ExecuteEditCommand, + OnExecuteEditCommand) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_HandleInputEvent, + OnHandleInputEvent) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_LockMouse_ACK, OnLockMouseAck) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_NavigateGuest, OnNavigateGuest) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_PluginDestroyed, OnPluginDestroyed) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_ResizeGuest, OnResizeGuest) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_SetAutoSize, OnSetSize) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_SetEditCommandsForNextKeyEvent, + OnSetEditCommandsForNextKeyEvent) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_SetFocus, OnSetFocus) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_SetName, OnSetName) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_SetVisibility, OnSetVisibility) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_UnlockMouse_ACK, OnUnlockMouseAck) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_UpdateGeometry, OnUpdateGeometry) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_UpdateRect_ACK, OnUpdateRectACK) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void BrowserPluginGuest::Initialize( + WebContentsImpl* embedder_web_contents, + const BrowserPluginHostMsg_Attach_Params& params) { + focused_ = params.focused; + guest_visible_ = params.visible; + guest_window_rect_ = params.resize_guest_params.view_rect; + + if (!params.name.empty()) + name_ = params.name; + auto_size_enabled_ = params.auto_size_params.enable; + max_auto_size_ = params.auto_size_params.max_size; + min_auto_size_ = params.auto_size_params.min_size; + + // Once a BrowserPluginGuest has an embedder WebContents, it's considered to + // be attached. + embedder_web_contents_ = embedder_web_contents; + + WebContentsViewGuest* new_view = + static_cast<WebContentsViewGuest*>(GetWebContents()->GetView()); + new_view->OnGuestInitialized(embedder_web_contents->GetView()); + + // |render_view_host| manages the ownership of this BrowserPluginGuestHelper. + new BrowserPluginGuestHelper(this, GetWebContents()->GetRenderViewHost()); + + RendererPreferences* renderer_prefs = + GetWebContents()->GetMutableRendererPrefs(); + // Copy renderer preferences (and nothing else) from the embedder's + // WebContents to the guest. + // + // For GTK and Aura this is necessary to get proper renderer configuration + // values for caret blinking interval, colors related to selection and + // focus. + *renderer_prefs = *embedder_web_contents_->GetMutableRendererPrefs(); + + // We would like the guest to report changes to frame names so that we can + // update the BrowserPlugin's corresponding 'name' attribute. + // TODO(fsamuel): Remove this once http://crbug.com/169110 is addressed. + renderer_prefs->report_frame_name_changes = true; + // Navigation is disabled in Chrome Apps. We want to make sure guest-initiated + // navigations still continue to function inside the app. + renderer_prefs->browser_handles_all_top_level_requests = false; + + // Listen to embedder visibility changes so that the guest is in a 'shown' + // state if both the embedder is visible and the BrowserPlugin is marked as + // visible. + notification_registrar_.Add( + this, NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED, + Source<WebContents>(embedder_web_contents_)); + + embedder_rvh_observer_.reset(new EmbedderRenderViewHostObserver(this)); + + OnSetSize(instance_id_, params.auto_size_params, params.resize_guest_params); + + // Create a swapped out RenderView for the guest in the embedder render + // process, so that the embedder can access the guest's window object. + int guest_routing_id = + GetWebContents()->CreateSwappedOutRenderView( + embedder_web_contents_->GetSiteInstance()); + SendMessageToEmbedder( + new BrowserPluginMsg_GuestContentWindowReady(instance_id_, + guest_routing_id)); + + if (!params.src.empty()) + OnNavigateGuest(instance_id_, params.src); + + has_render_view_ = true; + + if (!embedder_web_contents_-> + GetWebkitPrefs().accelerated_compositing_enabled) { + WebPreferences prefs = GetWebContents()->GetWebkitPrefs(); + prefs.accelerated_compositing_enabled = false; + GetWebContents()->GetRenderViewHost()->UpdateWebkitPreferences(prefs); + } + + // Enable input method for guest if it's enabled for the embedder. + if (static_cast<RenderViewHostImpl*>( + embedder_web_contents_->GetRenderViewHost())->input_method_active()) { + RenderViewHostImpl* guest_rvh = static_cast<RenderViewHostImpl*>( + GetWebContents()->GetRenderViewHost()); + guest_rvh->SetInputMethodActive(true); + } +} + +BrowserPluginGuest::~BrowserPluginGuest() { + while (!pending_messages_.empty()) { + delete pending_messages_.front(); + pending_messages_.pop(); + } +} + +// static +BrowserPluginGuest* BrowserPluginGuest::Create( + int instance_id, + WebContentsImpl* web_contents, + scoped_ptr<base::DictionaryValue> extra_params) { + RecordAction(UserMetricsAction("BrowserPlugin.Guest.Create")); + BrowserPluginGuest* guest = NULL; + if (factory_) { + guest = factory_->CreateBrowserPluginGuest(instance_id, web_contents); + } else { + guest = new BrowserPluginGuest(instance_id, web_contents, NULL, false); + } + web_contents->SetBrowserPluginGuest(guest); + BrowserPluginGuestDelegate* delegate = NULL; + GetContentClient()->browser()->GuestWebContentsCreated( + web_contents, NULL, &delegate, extra_params.Pass()); + guest->SetDelegate(delegate); + return guest; +} + +// static +BrowserPluginGuest* BrowserPluginGuest::CreateWithOpener( + int instance_id, + WebContentsImpl* web_contents, + BrowserPluginGuest* opener, + bool has_render_view) { + BrowserPluginGuest* guest = + new BrowserPluginGuest( + instance_id, web_contents, opener, has_render_view); + web_contents->SetBrowserPluginGuest(guest); + BrowserPluginGuestDelegate* delegate = NULL; + GetContentClient()->browser()->GuestWebContentsCreated( + web_contents, opener->GetWebContents(), &delegate, + scoped_ptr<base::DictionaryValue>()); + guest->SetDelegate(delegate); + return guest; +} + +RenderWidgetHostView* BrowserPluginGuest::GetEmbedderRenderWidgetHostView() { + return embedder_web_contents_->GetRenderWidgetHostView(); +} + +void BrowserPluginGuest::UpdateVisibility() { + OnSetVisibility(instance_id_, visible()); +} + +// screen. +gfx::Rect BrowserPluginGuest::ToGuestRect(const gfx::Rect& bounds) { + gfx::Rect guest_rect(bounds); + guest_rect.Offset(guest_window_rect_.OffsetFromOrigin()); + return guest_rect; +} + +void BrowserPluginGuest::Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type) { + case NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED: { + DCHECK_EQ(Source<WebContents>(source).ptr(), embedder_web_contents_); + embedder_visible_ = *Details<bool>(details).ptr(); + UpdateVisibility(); + break; + } + default: + NOTREACHED() << "Unexpected notification sent."; + break; + } +} + +void BrowserPluginGuest::AddNewContents(WebContents* source, + WebContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture, + bool* was_blocked) { + if (was_blocked) + *was_blocked = false; + RequestNewWindowPermission(static_cast<WebContentsImpl*>(new_contents), + disposition, initial_pos, user_gesture); +} + +void BrowserPluginGuest::CanDownload( + RenderViewHost* render_view_host, + int request_id, + const std::string& request_method, + const base::Callback<void(bool)>& callback) { + if (permission_request_map_.size() >= kNumMaxOutstandingPermissionRequests) { + // Deny the download request. + callback.Run(false); + return; + } + + BrowserThread::PostTaskAndReplyWithResult( + BrowserThread::IO, FROM_HERE, + base::Bind(&RetrieveDownloadURLFromRequestId, + render_view_host, request_id), + base::Bind(&BrowserPluginGuest::DidRetrieveDownloadURLFromRequestId, + weak_ptr_factory_.GetWeakPtr(), + request_method, + callback)); +} + +void BrowserPluginGuest::CloseContents(WebContents* source) { + if (!delegate_) + return; + + delegate_->Close(); +} + +JavaScriptDialogManager* BrowserPluginGuest::GetJavaScriptDialogManager() { + return this; +} + +bool BrowserPluginGuest::HandleContextMenu(const ContextMenuParams& params) { + // TODO(fsamuel): We show the regular page context menu handler for now until + // we implement the Apps Context Menu API for Browser Plugin (see + // http://crbug.com/140315). + return false; // Will be handled by WebContentsViewGuest. +} + +void BrowserPluginGuest::HandleKeyboardEvent( + WebContents* source, + const NativeWebKeyboardEvent& event) { + if (!attached()) + return; + + if (UnlockMouseIfNecessary(event)) + return; + + if (delegate_ && delegate_->HandleKeyboardEvent(event)) + return; + + // Send the unhandled keyboard events back to the embedder to reprocess them. + // TODO(fsamuel): This introduces the possibility of out-of-order keyboard + // events because the guest may be arbitrarily delayed when responding to + // keyboard events. In that time, the embedder may have received and processed + // additional key events. This needs to be fixed as soon as possible. + // See http://crbug.com/229882. + embedder_web_contents_->GetDelegate()->HandleKeyboardEvent( + web_contents(), event); +} + +WebContents* BrowserPluginGuest::OpenURLFromTab(WebContents* source, + const OpenURLParams& params) { + // If the guest wishes to navigate away prior to attachment then we save the + // navigation to perform upon attachment. Navigation initializes a lot of + // state that assumes an embedder exists, such as RenderWidgetHostViewGuest. + // Navigation also resumes resource loading which we don't want to allow + // until attachment. + if (!attached()) { + PendingWindowMap::iterator it = opener()->pending_new_windows_.find(this); + if (it == opener()->pending_new_windows_.end()) + return NULL; + const NewWindowInfo& old_target_url = it->second; + NewWindowInfo new_window_info(params.url, old_target_url.name); + new_window_info.changed = new_window_info.url != old_target_url.url; + it->second = new_window_info; + return NULL; + } + // This can happen for cross-site redirects. + source->GetController().LoadURL( + params.url, params.referrer, params.transition, std::string()); + return source; +} + +void BrowserPluginGuest::WebContentsCreated(WebContents* source_contents, + int64 source_frame_id, + const string16& frame_name, + const GURL& target_url, + WebContents* new_contents) { + WebContentsImpl* new_contents_impl = + static_cast<WebContentsImpl*>(new_contents); + BrowserPluginGuest* guest = new_contents_impl->GetBrowserPluginGuest(); + guest->opener_ = AsWeakPtr(); + std::string guest_name = UTF16ToUTF8(frame_name); + guest->name_ = guest_name; + // Take ownership of the new guest until it is attached to the embedder's DOM + // tree to avoid leaking a guest if this guest is destroyed before attaching + // the new guest. + pending_new_windows_.insert( + std::make_pair(guest, NewWindowInfo(target_url, guest_name))); +} + +void BrowserPluginGuest::RendererUnresponsive(WebContents* source) { + RecordAction(UserMetricsAction("BrowserPlugin.Guest.Hung")); + if (!delegate_) + return; + delegate_->RendererUnresponsive(); +} + +void BrowserPluginGuest::RendererResponsive(WebContents* source) { + RecordAction(UserMetricsAction("BrowserPlugin.Guest.Responsive")); + if (!delegate_) + return; + delegate_->RendererResponsive(); +} + +void BrowserPluginGuest::RunFileChooser(WebContents* web_contents, + const FileChooserParams& params) { + embedder_web_contents_->GetDelegate()->RunFileChooser(web_contents, params); +} + +bool BrowserPluginGuest::ShouldFocusPageAfterCrash() { + // Rather than managing focus in WebContentsImpl::RenderViewReady, we will + // manage the focus ourselves. + return false; +} + +WebContentsImpl* BrowserPluginGuest::GetWebContents() { + return static_cast<WebContentsImpl*>(web_contents()); +} + +base::SharedMemory* BrowserPluginGuest::GetDamageBufferFromEmbedder( + const BrowserPluginHostMsg_ResizeGuest_Params& params) { + if (!attached()) { + LOG(WARNING) << "Attempting to map a damage buffer prior to attachment."; + return NULL; + } +#if defined(OS_WIN) + base::ProcessHandle handle = + embedder_web_contents_->GetRenderProcessHost()->GetHandle(); + scoped_ptr<base::SharedMemory> shared_buf( + new base::SharedMemory(params.damage_buffer_handle, false, handle)); +#elif defined(OS_POSIX) + scoped_ptr<base::SharedMemory> shared_buf( + new base::SharedMemory(params.damage_buffer_handle, false)); +#endif + if (!shared_buf->Map(params.damage_buffer_size)) { + LOG(WARNING) << "Unable to map the embedder's damage buffer."; + return NULL; + } + return shared_buf.release(); +} + +void BrowserPluginGuest::SetDamageBuffer( + const BrowserPluginHostMsg_ResizeGuest_Params& params) { + damage_buffer_.reset(GetDamageBufferFromEmbedder(params)); + // Sanity check: Verify that we've correctly shared the damage buffer memory + // between the embedder and browser processes. + DCHECK(!damage_buffer_ || + *static_cast<unsigned int*>(damage_buffer_->memory()) == 0xdeadbeef); + damage_buffer_sequence_id_ = params.damage_buffer_sequence_id; + damage_buffer_size_ = params.damage_buffer_size; + damage_view_size_ = params.view_rect.size(); + damage_buffer_scale_factor_ = params.scale_factor; +} + +gfx::Point BrowserPluginGuest::GetScreenCoordinates( + const gfx::Point& relative_position) const { + gfx::Point screen_pos(relative_position); + screen_pos += guest_window_rect_.OffsetFromOrigin(); + return screen_pos; +} + +bool BrowserPluginGuest::InAutoSizeBounds(const gfx::Size& size) const { + return size.width() <= max_auto_size_.width() && + size.height() <= max_auto_size_.height(); +} + +void BrowserPluginGuest::RequestNewWindowPermission( + WebContentsImpl* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_bounds, + bool user_gesture) { + BrowserPluginGuest* guest = new_contents->GetBrowserPluginGuest(); + PendingWindowMap::iterator it = pending_new_windows_.find(guest); + if (it == pending_new_windows_.end()) + return; + const NewWindowInfo& new_window_info = it->second; + + base::DictionaryValue request_info; + request_info.Set(browser_plugin::kInitialHeight, + base::Value::CreateIntegerValue(initial_bounds.height())); + request_info.Set(browser_plugin::kInitialWidth, + base::Value::CreateIntegerValue(initial_bounds.width())); + request_info.Set(browser_plugin::kTargetURL, + base::Value::CreateStringValue(new_window_info.url.spec())); + request_info.Set(browser_plugin::kName, + base::Value::CreateStringValue(new_window_info.name)); + request_info.Set(browser_plugin::kWindowID, + base::Value::CreateIntegerValue(guest->instance_id())); + request_info.Set(browser_plugin::kWindowOpenDisposition, + base::Value::CreateStringValue( + WindowOpenDispositionToString(disposition))); + + RequestPermission(BROWSER_PLUGIN_PERMISSION_TYPE_NEW_WINDOW, + new NewWindowRequest(guest->instance_id(), this), + request_info); +} + +bool BrowserPluginGuest::UnlockMouseIfNecessary( + const NativeWebKeyboardEvent& event) { + if (!mouse_locked_) + return false; + + embedder_web_contents()->GotResponseToLockMouseRequest(false); + return true; +} + +void BrowserPluginGuest::SendMessageToEmbedder(IPC::Message* msg) { + if (!attached()) { + // Some pages such as data URLs, javascript URLs, and about:blank + // do not load external resources and so they load prior to attachment. + // As a result, we must save all these IPCs until attachment and then + // forward them so that the embedder gets a chance to see and process + // the load events. + pending_messages_.push(msg); + return; + } + msg->set_routing_id(embedder_web_contents_->GetRoutingID()); + embedder_web_contents_->Send(msg); +} + +void BrowserPluginGuest::DragSourceEndedAt(int client_x, int client_y, + int screen_x, int screen_y, WebKit::WebDragOperation operation) { + web_contents()->GetRenderViewHost()->DragSourceEndedAt(client_x, client_y, + screen_x, screen_y, operation); +} + +void BrowserPluginGuest::DragSourceMovedTo(int client_x, int client_y, + int screen_x, int screen_y) { + web_contents()->GetRenderViewHost()->DragSourceMovedTo(client_x, client_y, + screen_x, screen_y); +} + +void BrowserPluginGuest::EndSystemDrag() { + RenderViewHostImpl* guest_rvh = static_cast<RenderViewHostImpl*>( + GetWebContents()->GetRenderViewHost()); + guest_rvh->DragSourceSystemDragEnded(); + // Issue a MouseUp event to get out of a selection state. + WebKit::WebMouseEvent mouse_event; + mouse_event.type = WebKit::WebInputEvent::MouseUp; + mouse_event.button = WebKit::WebMouseEvent::ButtonLeft; + guest_rvh->ForwardMouseEvent(mouse_event); +} + +void BrowserPluginGuest::SetDelegate(BrowserPluginGuestDelegate* delegate) { + DCHECK(!delegate_); + delegate_.reset(delegate); +} + +void BrowserPluginGuest::AskEmbedderForGeolocationPermission( + int bridge_id, + const GURL& requesting_frame, + const GeolocationCallback& callback) { + if (permission_request_map_.size() >= kNumMaxOutstandingPermissionRequests) { + // Deny the geolocation request. + callback.Run(false); + return; + } + + base::DictionaryValue request_info; + request_info.Set(browser_plugin::kURL, + base::Value::CreateStringValue(requesting_frame.spec())); + + int request_id = + RequestPermission(BROWSER_PLUGIN_PERMISSION_TYPE_GEOLOCATION, + new GeolocationRequest( + callback, bridge_id, &weak_ptr_factory_), + request_info); + + DCHECK(bridge_id_to_request_id_map_.find(bridge_id) == + bridge_id_to_request_id_map_.end()); + bridge_id_to_request_id_map_[bridge_id] = request_id; +} + +int BrowserPluginGuest::RemoveBridgeID(int bridge_id) { + std::map<int, int>::iterator bridge_itr = + bridge_id_to_request_id_map_.find(bridge_id); + if (bridge_itr == bridge_id_to_request_id_map_.end()) + return browser_plugin::kInvalidPermissionRequestID; + + int request_id = bridge_itr->second; + bridge_id_to_request_id_map_.erase(bridge_itr); + return request_id; +} + +void BrowserPluginGuest::CancelGeolocationRequest(int bridge_id) { + int request_id = RemoveBridgeID(bridge_id); + RequestMap::iterator request_itr = permission_request_map_.find(request_id); + if (request_itr == permission_request_map_.end()) + return; + permission_request_map_.erase(request_itr); +} + +void BrowserPluginGuest::SetGeolocationPermission(GeolocationCallback callback, + int bridge_id, + bool allowed) { + callback.Run(allowed); + RemoveBridgeID(bridge_id); +} + +void BrowserPluginGuest::SendQueuedMessages() { + if (!attached()) + return; + + while (!pending_messages_.empty()) { + IPC::Message* message = pending_messages_.front(); + pending_messages_.pop(); + SendMessageToEmbedder(message); + } +} + +void BrowserPluginGuest::DidCommitProvisionalLoadForFrame( + int64 frame_id, + bool is_main_frame, + const GURL& url, + PageTransition transition_type, + RenderViewHost* render_view_host) { + RecordAction(UserMetricsAction("BrowserPlugin.Guest.DidNavigate")); +} + +void BrowserPluginGuest::DidStopLoading(RenderViewHost* render_view_host) { + bool disable_dragdrop = !CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableBrowserPluginDragDrop); + if (disable_dragdrop) { + // Initiating a drag from inside a guest is currently not supported without + // the kEnableBrowserPluginDragDrop flag on a linux platform. So inject some + // JS to disable it. http://crbug.com/161112 + const char script[] = "window.addEventListener('dragstart', function() { " + " window.event.preventDefault(); " + "});"; + render_view_host->ExecuteJavascriptInWebFrame(string16(), + ASCIIToUTF16(script)); + } +} + +void BrowserPluginGuest::RenderViewReady() { + // TODO(fsamuel): Investigate whether it's possible to update state earlier + // here (see http://crbug.com/158151). + Send(new InputMsg_SetFocus(routing_id(), focused_)); + UpdateVisibility(); + RenderViewHost* rvh = GetWebContents()->GetRenderViewHost(); + if (auto_size_enabled_) + rvh->EnableAutoResize(min_auto_size_, max_auto_size_); + else + rvh->DisableAutoResize(damage_view_size_); + + Send(new ViewMsg_SetName(routing_id(), name_)); + + RenderWidgetHostImpl::From(rvh)-> + set_hung_renderer_delay_ms(guest_hang_timeout_); +} + +void BrowserPluginGuest::RenderProcessGone(base::TerminationStatus status) { + SendMessageToEmbedder(new BrowserPluginMsg_GuestGone(instance_id())); + switch (status) { + case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: + RecordAction(UserMetricsAction("BrowserPlugin.Guest.Killed")); + break; + case base::TERMINATION_STATUS_PROCESS_CRASHED: + RecordAction(UserMetricsAction("BrowserPlugin.Guest.Crashed")); + break; + case base::TERMINATION_STATUS_ABNORMAL_TERMINATION: + RecordAction(UserMetricsAction("BrowserPlugin.Guest.AbnormalDeath")); + break; + default: + break; + } + // TODO(fsamuel): Consider whether we should be clearing + // |permission_request_map_| here. + if (delegate_) + delegate_->GuestProcessGone(status); +} + +// static +void BrowserPluginGuest::AcknowledgeBufferPresent( + int route_id, + int gpu_host_id, + const std::string& mailbox_name, + uint32 sync_point) { + AcceleratedSurfaceMsg_BufferPresented_Params ack_params; + ack_params.mailbox_name = mailbox_name; + ack_params.sync_point = sync_point; + RenderWidgetHostImpl::AcknowledgeBufferPresent(route_id, + gpu_host_id, + ack_params); +} + +// static +bool BrowserPluginGuest::ShouldForwardToBrowserPluginGuest( + const IPC::Message& message) { + switch (message.type()) { + case BrowserPluginHostMsg_BuffersSwappedACK::ID: + case BrowserPluginHostMsg_CompositorFrameACK::ID: + case BrowserPluginHostMsg_DragStatusUpdate::ID: + case BrowserPluginHostMsg_ExecuteEditCommand::ID: + case BrowserPluginHostMsg_HandleInputEvent::ID: + case BrowserPluginHostMsg_LockMouse_ACK::ID: + case BrowserPluginHostMsg_NavigateGuest::ID: + case BrowserPluginHostMsg_PluginDestroyed::ID: + case BrowserPluginHostMsg_ResizeGuest::ID: + case BrowserPluginHostMsg_SetAutoSize::ID: + case BrowserPluginHostMsg_SetEditCommandsForNextKeyEvent::ID: + case BrowserPluginHostMsg_SetFocus::ID: + case BrowserPluginHostMsg_SetName::ID: + case BrowserPluginHostMsg_SetVisibility::ID: + case BrowserPluginHostMsg_UnlockMouse_ACK::ID: + case BrowserPluginHostMsg_UpdateGeometry::ID: + case BrowserPluginHostMsg_UpdateRect_ACK::ID: + return true; + default: + break; + } + return false; +} + +bool BrowserPluginGuest::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(BrowserPluginGuest, message) + IPC_MESSAGE_HANDLER(ViewHostMsg_HasTouchEventHandlers, + OnHasTouchEventHandlers) + IPC_MESSAGE_HANDLER(ViewHostMsg_LockMouse, OnLockMouse) + IPC_MESSAGE_HANDLER(ViewHostMsg_SetCursor, OnSetCursor) + #if defined(OS_MACOSX) + // MacOSX creates and populates platform-specific select drop-down menus + // whereas other platforms merely create a popup window that the guest + // renderer process paints inside. + IPC_MESSAGE_HANDLER(ViewHostMsg_ShowPopup, OnShowPopup) + #endif + IPC_MESSAGE_HANDLER(ViewHostMsg_ShowWidget, OnShowWidget) + IPC_MESSAGE_HANDLER(ViewHostMsg_TakeFocus, OnTakeFocus) + IPC_MESSAGE_HANDLER(ViewHostMsg_UnlockMouse, OnUnlockMouse) + IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateFrameName, OnUpdateFrameName) + IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateRect, OnUpdateRect) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void BrowserPluginGuest::Attach( + WebContentsImpl* embedder_web_contents, + BrowserPluginHostMsg_Attach_Params params) { + if (attached()) + return; + + // Clear parameters that get inherited from the opener. + params.storage_partition_id.clear(); + params.persist_storage = false; + params.src.clear(); + + // If a RenderView has already been created for this new window, then we need + // to initialize the browser-side state now so that the RenderViewHostManager + // does not create a new RenderView on navigation. + if (has_render_view_) { + static_cast<RenderViewHostImpl*>( + GetWebContents()->GetRenderViewHost())->Init(); + WebContentsViewGuest* new_view = + static_cast<WebContentsViewGuest*>(GetWebContents()->GetView()); + new_view->CreateViewForWidget(web_contents()->GetRenderViewHost()); + } + + // We need to do a navigation here if the target URL has changed between + // the time the WebContents was created and the time it was attached. + // We also need to do an initial navigation if a RenderView was never + // created for the new window in cases where there is no referrer. + PendingWindowMap::iterator it = opener()->pending_new_windows_.find(this); + if (it != opener()->pending_new_windows_.end()) { + const NewWindowInfo& new_window_info = it->second; + if (new_window_info.changed || !has_render_view_) + params.src = it->second.url.spec(); + } else { + NOTREACHED(); + } + + // Once a new guest is attached to the DOM of the embedder page, then the + // lifetime of the new guest is no longer managed by the opener guest. + opener()->pending_new_windows_.erase(this); + + // The guest's frame name takes precedence over the BrowserPlugin's name. + // The guest's frame name is assigned in + // BrowserPluginGuest::WebContentsCreated. + if (!name_.empty()) + params.name.clear(); + + Initialize(embedder_web_contents, params); + + // Inform the embedder of the guest's information. + // We pull the partition information from the site's URL, which is of the form + // guest://site/{persist}?{partition_name}. + const GURL& site_url = GetWebContents()->GetSiteInstance()->GetSiteURL(); + BrowserPluginMsg_Attach_ACK_Params ack_params; + ack_params.storage_partition_id = site_url.query(); + ack_params.persist_storage = + site_url.path().find("persist") != std::string::npos; + ack_params.name = name_; + SendMessageToEmbedder( + new BrowserPluginMsg_Attach_ACK(instance_id_, ack_params)); + + SendQueuedMessages(); + + RecordAction(UserMetricsAction("BrowserPlugin.Guest.Attached")); +} + +void BrowserPluginGuest::OnCompositorFrameACK( + int instance_id, + int route_id, + uint32 output_surface_id, + int renderer_host_id, + const cc::CompositorFrameAck& ack) { + RenderWidgetHostImpl::SendSwapCompositorFrameAck(route_id, + output_surface_id, + renderer_host_id, + ack); +} + +void BrowserPluginGuest::OnDragStatusUpdate(int instance_id, + WebKit::WebDragStatus drag_status, + const DropData& drop_data, + WebKit::WebDragOperationsMask mask, + const gfx::Point& location) { + RenderViewHost* host = GetWebContents()->GetRenderViewHost(); + switch (drag_status) { + case WebKit::WebDragStatusEnter: + embedder_web_contents_->GetBrowserPluginEmbedder()->DragEnteredGuest( + this); + host->DragTargetDragEnter(drop_data, location, location, mask, 0); + break; + case WebKit::WebDragStatusOver: + host->DragTargetDragOver(location, location, mask, 0); + break; + case WebKit::WebDragStatusLeave: + embedder_web_contents_->GetBrowserPluginEmbedder()->DragLeftGuest(this); + host->DragTargetDragLeave(); + break; + case WebKit::WebDragStatusDrop: + host->DragTargetDrop(location, location, 0); + EndSystemDrag(); + break; + case WebKit::WebDragStatusUnknown: + NOTREACHED(); + } +} + +void BrowserPluginGuest::OnExecuteEditCommand(int instance_id, + const std::string& name) { + Send(new InputMsg_ExecuteEditCommand(routing_id(), name, std::string())); +} + +void BrowserPluginGuest::OnHandleInputEvent( + int instance_id, + const gfx::Rect& guest_window_rect, + const WebKit::WebInputEvent* event) { + guest_window_rect_ = guest_window_rect; + // If the embedder's RWHV is destroyed then that means that the embedder's + // window has been closed but the embedder's WebContents has not yet been + // destroyed. Computing screen coordinates of a BrowserPlugin only makes sense + // if there is a visible embedder. + if (embedder_web_contents_->GetRenderWidgetHostView()) { + guest_screen_rect_ = guest_window_rect; + guest_screen_rect_.Offset( + embedder_web_contents_->GetRenderWidgetHostView()-> + GetViewBounds().OffsetFromOrigin()); + } + RenderViewHostImpl* guest_rvh = static_cast<RenderViewHostImpl*>( + GetWebContents()->GetRenderViewHost()); + + if (WebKit::WebInputEvent::isMouseEventType(event->type)) { + guest_rvh->ForwardMouseEvent( + *static_cast<const WebKit::WebMouseEvent*>(event)); + return; + } + + if (event->type == WebKit::WebInputEvent::MouseWheel) { + guest_rvh->ForwardWheelEvent( + *static_cast<const WebKit::WebMouseWheelEvent*>(event)); + return; + } + + if (WebKit::WebInputEvent::isKeyboardEventType(event->type)) { + RenderViewHostImpl* embedder_rvh = static_cast<RenderViewHostImpl*>( + embedder_web_contents_->GetRenderViewHost()); + if (!embedder_rvh->GetLastKeyboardEvent()) + return; + NativeWebKeyboardEvent keyboard_event( + *embedder_rvh->GetLastKeyboardEvent()); + guest_rvh->ForwardKeyboardEvent(keyboard_event); + return; + } + + if (WebKit::WebInputEvent::isTouchEventType(event->type)) { + guest_rvh->ForwardTouchEventWithLatencyInfo( + *static_cast<const WebKit::WebTouchEvent*>(event), + ui::LatencyInfo()); + return; + } + + if (WebKit::WebInputEvent::isGestureEventType(event->type)) { + guest_rvh->ForwardGestureEvent( + *static_cast<const WebKit::WebGestureEvent*>(event)); + return; + } +} + +void BrowserPluginGuest::OnLockMouse(bool user_gesture, + bool last_unlocked_by_target, + bool privileged) { + if (pending_lock_request_ || + (permission_request_map_.size() >= + kNumMaxOutstandingPermissionRequests)) { + // Immediately reject the lock because only one pointerLock may be active + // at a time. + Send(new ViewMsg_LockMouse_ACK(routing_id(), false)); + return; + } + pending_lock_request_ = true; + base::DictionaryValue request_info; + request_info.Set(browser_plugin::kUserGesture, + base::Value::CreateBooleanValue(user_gesture)); + request_info.Set(browser_plugin::kLastUnlockedBySelf, + base::Value::CreateBooleanValue(last_unlocked_by_target)); + request_info.Set(browser_plugin::kURL, + base::Value::CreateStringValue( + web_contents()->GetLastCommittedURL().spec())); + + RequestPermission(BROWSER_PLUGIN_PERMISSION_TYPE_POINTER_LOCK, + new PointerLockRequest(this), + request_info); +} + +void BrowserPluginGuest::OnLockMouseAck(int instance_id, bool succeeded) { + Send(new ViewMsg_LockMouse_ACK(routing_id(), succeeded)); + pending_lock_request_ = false; + if (succeeded) + mouse_locked_ = true; +} + +void BrowserPluginGuest::OnNavigateGuest( + int instance_id, + const std::string& src) { + GURL url(src); + // We do not load empty urls in web_contents. + // If a guest sets empty src attribute after it has navigated to some + // non-empty page, the action is considered no-op. This empty src navigation + // should never be sent to BrowserPluginGuest (browser process). + DCHECK(!src.empty()); + if (!src.empty()) { + // As guests do not swap processes on navigation, only navigations to + // normal web URLs are supported. No protocol handlers are installed for + // other schemes (e.g., WebUI or extensions), and no permissions or bindings + // can be granted to the guest process. + GetWebContents()->GetController().LoadURL(url, Referrer(), + PAGE_TRANSITION_AUTO_TOPLEVEL, + std::string()); + } +} + +void BrowserPluginGuest::OnPluginDestroyed(int instance_id) { + Destroy(); +} + +void BrowserPluginGuest::OnResizeGuest( + int instance_id, + const BrowserPluginHostMsg_ResizeGuest_Params& params) { + if (!params.size_changed) + return; + // BrowserPlugin manages resize flow control itself and does not depend + // on RenderWidgetHost's mechanisms for flow control, so we reset those flags + // here. If we are setting the size for the first time before navigating then + // BrowserPluginGuest does not yet have a RenderViewHost. + if (GetWebContents()->GetRenderViewHost()) { + RenderWidgetHostImpl* render_widget_host = + RenderWidgetHostImpl::From(GetWebContents()->GetRenderViewHost()); + render_widget_host->ResetSizeAndRepaintPendingFlags(); + + if (guest_device_scale_factor_ != params.scale_factor) { + guest_device_scale_factor_ = params.scale_factor; + render_widget_host->NotifyScreenInfoChanged(); + } + } + // When autosize is turned off and as a result there is a layout change, we + // send a sizechanged event. + if (!auto_size_enabled_ && last_seen_auto_size_enabled_ && + !params.view_rect.size().IsEmpty() && delegate_) { + delegate_->SizeChanged(last_seen_view_size_, params.view_rect.size()); + last_seen_auto_size_enabled_ = false; + } + // Invalid damage buffer means we are in HW compositing mode, + // so just resize the WebContents and repaint if needed. + if (!base::SharedMemory::IsHandleValid(params.damage_buffer_handle)) { + if (!params.view_rect.size().IsEmpty()) + GetWebContents()->GetView()->SizeContents(params.view_rect.size()); + if (params.repaint) + Send(new ViewMsg_Repaint(routing_id(), params.view_rect.size())); + return; + } + SetDamageBuffer(params); + GetWebContents()->GetView()->SizeContents(params.view_rect.size()); + if (params.repaint) + Send(new ViewMsg_Repaint(routing_id(), params.view_rect.size())); +} + +void BrowserPluginGuest::OnSetFocus(int instance_id, bool focused) { + if (focused_ == focused) + return; + focused_ = focused; + Send(new InputMsg_SetFocus(routing_id(), focused)); + if (!focused && mouse_locked_) + OnUnlockMouse(); +} + +void BrowserPluginGuest::OnSetName(int instance_id, const std::string& name) { + if (name == name_) + return; + name_ = name; + Send(new ViewMsg_SetName(routing_id(), name)); +} + +void BrowserPluginGuest::OnSetSize( + int instance_id, + const BrowserPluginHostMsg_AutoSize_Params& auto_size_params, + const BrowserPluginHostMsg_ResizeGuest_Params& resize_guest_params) { + bool old_auto_size_enabled = auto_size_enabled_; + gfx::Size old_max_size = max_auto_size_; + gfx::Size old_min_size = min_auto_size_; + auto_size_enabled_ = auto_size_params.enable; + max_auto_size_ = auto_size_params.max_size; + min_auto_size_ = auto_size_params.min_size; + if (auto_size_enabled_ && (!old_auto_size_enabled || + (old_max_size != max_auto_size_) || + (old_min_size != min_auto_size_))) { + GetWebContents()->GetRenderViewHost()->EnableAutoResize( + min_auto_size_, max_auto_size_); + // TODO(fsamuel): If we're changing autosize parameters, then we force + // the guest to completely repaint itself, because BrowserPlugin has + // allocated a new damage buffer and expects a full frame of pixels. + // Ideally, we shouldn't need to do this because we shouldn't need to + // allocate a new damage buffer unless |max_auto_size_| has changed. + // However, even in that case, layout may not change and so we may + // not get a full frame worth of pixels. + Send(new ViewMsg_Repaint(routing_id(), max_auto_size_)); + } else if (!auto_size_enabled_ && old_auto_size_enabled) { + GetWebContents()->GetRenderViewHost()->DisableAutoResize( + resize_guest_params.view_rect.size()); + } + OnResizeGuest(instance_id_, resize_guest_params); +} + +void BrowserPluginGuest::OnSetEditCommandsForNextKeyEvent( + int instance_id, + const std::vector<EditCommand>& edit_commands) { + Send(new InputMsg_SetEditCommandsForNextKeyEvent(routing_id(), + edit_commands)); +} + +void BrowserPluginGuest::OnSetVisibility(int instance_id, bool visible) { + guest_visible_ = visible; + if (embedder_visible_ && guest_visible_) + GetWebContents()->WasShown(); + else + GetWebContents()->WasHidden(); +} + +void BrowserPluginGuest::OnSwapBuffersACK(int instance_id, + int route_id, + int gpu_host_id, + const std::string& mailbox_name, + uint32 sync_point) { + AcknowledgeBufferPresent(route_id, gpu_host_id, mailbox_name, sync_point); + +// This is only relevant on MACOSX and WIN when threaded compositing +// is not enabled. In threaded mode, above ACK is sufficient. +#if defined(OS_MACOSX) || defined(OS_WIN) + RenderWidgetHostImpl* render_widget_host = + RenderWidgetHostImpl::From(GetWebContents()->GetRenderViewHost()); + render_widget_host->AcknowledgeSwapBuffersToRenderer(); +#endif // defined(OS_MACOSX) || defined(OS_WIN) +} + +void BrowserPluginGuest::OnUnlockMouse() { + SendMessageToEmbedder( + new BrowserPluginMsg_SetMouseLock(instance_id(), false)); +} + +void BrowserPluginGuest::OnUnlockMouseAck(int instance_id) { + // mouse_locked_ could be false here if the lock attempt was cancelled due + // to window focus, or for various other reasons before the guest was informed + // of the lock's success. + if (mouse_locked_) + Send(new ViewMsg_MouseLockLost(routing_id())); + mouse_locked_ = false; +} + +void BrowserPluginGuest::OnUpdateRectACK( + int instance_id, + bool needs_ack, + const BrowserPluginHostMsg_AutoSize_Params& auto_size_params, + const BrowserPluginHostMsg_ResizeGuest_Params& resize_guest_params) { + // Only the software path expects an ACK. + if (needs_ack) + Send(new ViewMsg_UpdateRect_ACK(routing_id())); + OnSetSize(instance_id_, auto_size_params, resize_guest_params); +} + +void BrowserPluginGuest::OnUpdateGeometry(int instance_id, + const gfx::Rect& view_rect) { + // The plugin has moved within the embedder without resizing or the + // embedder/container's view rect changing. + guest_window_rect_ = view_rect; + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + GetWebContents()->GetRenderViewHost()); + if (rvh) + rvh->SendScreenRects(); +} + +void BrowserPluginGuest::OnHasTouchEventHandlers(bool accept) { + SendMessageToEmbedder( + new BrowserPluginMsg_ShouldAcceptTouchEvents(instance_id(), accept)); +} + +void BrowserPluginGuest::OnSetCursor(const WebCursor& cursor) { + SendMessageToEmbedder(new BrowserPluginMsg_SetCursor(instance_id(), cursor)); +} + +#if defined(OS_MACOSX) +void BrowserPluginGuest::OnShowPopup( + const ViewHostMsg_ShowPopup_Params& params) { + gfx::Rect translated_bounds(params.bounds); + translated_bounds.Offset(guest_window_rect_.OffsetFromOrigin()); + BrowserPluginPopupMenuHelper popup_menu_helper( + embedder_web_contents_->GetRenderViewHost(), + GetWebContents()->GetRenderViewHost()); + popup_menu_helper.ShowPopupMenu(translated_bounds, + params.item_height, + params.item_font_size, + params.selected_item, + params.popup_items, + params.right_aligned, + params.allow_multiple_selection); +} +#endif + +void BrowserPluginGuest::OnShowWidget(int route_id, + const gfx::Rect& initial_pos) { + GetWebContents()->ShowCreatedWidget(route_id, initial_pos); +} + +void BrowserPluginGuest::OnTakeFocus(bool reverse) { + SendMessageToEmbedder( + new BrowserPluginMsg_AdvanceFocus(instance_id(), reverse)); +} + +void BrowserPluginGuest::OnUpdateFrameName(int frame_id, + bool is_top_level, + const std::string& name) { + if (!is_top_level) + return; + + name_ = name; + SendMessageToEmbedder(new BrowserPluginMsg_UpdatedName(instance_id_, name)); +} + +void BrowserPluginGuest::RequestMediaAccessPermission( + WebContents* web_contents, + const MediaStreamRequest& request, + const MediaResponseCallback& callback) { + if (permission_request_map_.size() >= kNumMaxOutstandingPermissionRequests) { + // Deny the media request. + callback.Run(MediaStreamDevices(), scoped_ptr<MediaStreamUI>()); + return; + } + + base::DictionaryValue request_info; + request_info.Set( + browser_plugin::kURL, + base::Value::CreateStringValue(request.security_origin.spec())); + + RequestPermission(BROWSER_PLUGIN_PERMISSION_TYPE_MEDIA, + new MediaRequest(request, callback, this), + request_info); +} + +void BrowserPluginGuest::RunJavaScriptDialog( + WebContents* web_contents, + const GURL& origin_url, + const std::string& accept_lang, + JavaScriptMessageType javascript_message_type, + const string16& message_text, + const string16& default_prompt_text, + const DialogClosedCallback& callback, + bool* did_suppress_message) { + if (permission_request_map_.size() >= kNumMaxOutstandingPermissionRequests) { + // Cancel the dialog. + callback.Run(false, string16()); + return; + } + base::DictionaryValue request_info; + request_info.Set( + browser_plugin::kDefaultPromptText, + base::Value::CreateStringValue(UTF16ToUTF8(default_prompt_text))); + request_info.Set( + browser_plugin::kMessageText, + base::Value::CreateStringValue(UTF16ToUTF8(message_text))); + request_info.Set( + browser_plugin::kMessageType, + base::Value::CreateStringValue( + JavaScriptMessageTypeToString(javascript_message_type))); + request_info.Set( + browser_plugin::kURL, + base::Value::CreateStringValue(origin_url.spec())); + + RequestPermission(BROWSER_PLUGIN_PERMISSION_TYPE_JAVASCRIPT_DIALOG, + new JavaScriptDialogRequest(callback), + request_info); +} + +void BrowserPluginGuest::RunBeforeUnloadDialog( + WebContents* web_contents, + const string16& message_text, + bool is_reload, + const DialogClosedCallback& callback) { + // This is called if the guest has a beforeunload event handler. + // This callback allows navigation to proceed. + callback.Run(true, string16()); +} + +bool BrowserPluginGuest::HandleJavaScriptDialog( + WebContents* web_contents, + bool accept, + const string16* prompt_override) { + return false; +} + +void BrowserPluginGuest::CancelActiveAndPendingDialogs( + WebContents* web_contents) { +} + +void BrowserPluginGuest::WebContentsDestroyed(WebContents* web_contents) { +} + +void BrowserPluginGuest::OnUpdateRect( + const ViewHostMsg_UpdateRect_Params& params) { + BrowserPluginMsg_UpdateRect_Params relay_params; + relay_params.view_size = params.view_size; + relay_params.scale_factor = params.scale_factor; + relay_params.is_resize_ack = ViewHostMsg_UpdateRect_Flags::is_resize_ack( + params.flags); + relay_params.needs_ack = params.needs_ack; + + bool size_changed = last_seen_view_size_ != params.view_size; + gfx::Size old_size = last_seen_view_size_; + last_seen_view_size_ = params.view_size; + + if ((auto_size_enabled_ || last_seen_auto_size_enabled_) && + size_changed && delegate_) { + delegate_->SizeChanged(old_size, last_seen_view_size_); + } + last_seen_auto_size_enabled_ = auto_size_enabled_; + + // HW accelerated case, acknowledge resize only + if (!params.needs_ack || !damage_buffer_) { + relay_params.damage_buffer_sequence_id = 0; + SendMessageToEmbedder( + new BrowserPluginMsg_UpdateRect(instance_id(), relay_params)); + return; + } + + // Only copy damage if the guest is in autosize mode and the guest's view size + // is less than the maximum size or the guest's view size is equal to the + // damage buffer's size and the guest's scale factor is equal to the damage + // buffer's scale factor. + // The scaling change can happen due to asynchronous updates of the DPI on a + // resolution change. + if (((auto_size_enabled_ && InAutoSizeBounds(params.view_size)) || + (params.view_size == damage_view_size())) && + params.scale_factor == damage_buffer_scale_factor()) { + TransportDIB* dib = GetWebContents()->GetRenderProcessHost()-> + GetTransportDIB(params.bitmap); + if (dib) { + size_t guest_damage_buffer_size = +#if defined(OS_WIN) + params.bitmap_rect.width() * + params.bitmap_rect.height() * 4; +#else + dib->size(); +#endif + size_t embedder_damage_buffer_size = damage_buffer_size_; + void* guest_memory = dib->memory(); + void* embedder_memory = damage_buffer_->memory(); + size_t size = std::min(guest_damage_buffer_size, + embedder_damage_buffer_size); + memcpy(embedder_memory, guest_memory, size); + } + } + relay_params.damage_buffer_sequence_id = damage_buffer_sequence_id_; + relay_params.bitmap_rect = params.bitmap_rect; + relay_params.scroll_delta = params.scroll_delta; + relay_params.scroll_rect = params.scroll_rect; + relay_params.copy_rects = params.copy_rects; + + SendMessageToEmbedder( + new BrowserPluginMsg_UpdateRect(instance_id(), relay_params)); +} + +void BrowserPluginGuest::DidRetrieveDownloadURLFromRequestId( + const std::string& request_method, + const base::Callback<void(bool)>& callback, + const std::string& url) { + if (url.empty()) { + callback.Run(false); + return; + } + + base::DictionaryValue request_info; + request_info.Set(browser_plugin::kRequestMethod, + base::Value::CreateStringValue(request_method)); + request_info.Set(browser_plugin::kURL, base::Value::CreateStringValue(url)); + + RequestPermission(BROWSER_PLUGIN_PERMISSION_TYPE_DOWNLOAD, + new DownloadRequest(callback), + request_info); +} + +} // namespace content diff --git a/chromium/content/browser/browser_plugin/browser_plugin_guest.h b/chromium/content/browser/browser_plugin/browser_plugin_guest.h new file mode 100644 index 00000000000..ffe8d566145 --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_guest.h @@ -0,0 +1,534 @@ +// Copyright (c) 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. + +// A BrowserPluginGuest is the browser side of a browser <--> embedder +// renderer channel. A BrowserPlugin (a WebPlugin) is on the embedder +// renderer side of browser <--> embedder renderer communication. +// +// BrowserPluginGuest lives on the UI thread of the browser process. It has a +// helper, BrowserPluginGuestHelper, which is a RenderViewHostObserver. The +// helper object intercepts messages (ViewHostMsg_*) directed at the browser +// process and redirects them to this class. Any messages about the guest render +// process that the embedder might be interested in receiving should be listened +// for here. +// +// BrowserPluginGuest is a WebContentsDelegate and WebContentsObserver for the +// guest WebContents. BrowserPluginGuest operates under the assumption that the +// guest will be accessible through only one RenderViewHost for the lifetime of +// the guest WebContents. Thus, cross-process navigation is not supported. + +#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_H_ +#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_H_ + +#include <map> +#include <queue> + +#include "base/compiler_specific.h" +#include "base/id_map.h" +#include "base/memory/shared_memory.h" +#include "base/memory/weak_ptr.h" +#include "base/values.h" +#include "content/common/edit_command.h" +#include "content/port/common/input_event_ack_state.h" +#include "content/public/browser/browser_plugin_guest_delegate.h" +#include "content/public/browser/javascript_dialog_manager.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/render_view_host_observer.h" +#include "content/public/browser/web_contents_delegate.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/common/browser_plugin_permission_type.h" +#include "third_party/WebKit/public/web/WebDragOperation.h" +#include "third_party/WebKit/public/web/WebDragStatus.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" +#include "ui/gfx/rect.h" +#include "ui/surface/transport_dib.h" + +struct BrowserPluginHostMsg_AutoSize_Params; +struct BrowserPluginHostMsg_Attach_Params; +struct BrowserPluginHostMsg_ResizeGuest_Params; +struct ViewHostMsg_CreateWindow_Params; +#if defined(OS_MACOSX) +struct ViewHostMsg_ShowPopup_Params; +#endif +struct ViewHostMsg_UpdateRect_Params; +class WebCursor; + +namespace cc { +class CompositorFrameAck; +} + +namespace WebKit { +class WebInputEvent; +} + +namespace content { + +class BrowserPluginHostFactory; +class BrowserPluginEmbedder; +class BrowserPluginGuestManager; +class RenderProcessHost; +class RenderWidgetHostView; +struct DropData; +struct MediaStreamRequest; + +// A browser plugin guest provides functionality for WebContents to operate in +// the guest role and implements guest-specific overrides for ViewHostMsg_* +// messages. +// +// When a guest is initially created, it is in an unattached state. That is, +// it is not visible anywhere and has no embedder WebContents assigned. +// A BrowserPluginGuest is said to be "attached" if it has an embedder. +// A BrowserPluginGuest can also create a new unattached guest via +// CreateNewWindow. The newly created guest will live in the same partition, +// which means it can share storage and can script this guest. +class CONTENT_EXPORT BrowserPluginGuest + : public JavaScriptDialogManager, + public NotificationObserver, + public WebContentsDelegate, + public WebContentsObserver, + public base::SupportsWeakPtr<BrowserPluginGuest> { + public: + typedef base::Callback<void(bool)> GeolocationCallback; + virtual ~BrowserPluginGuest(); + + static BrowserPluginGuest* Create( + int instance_id, + WebContentsImpl* web_contents, + scoped_ptr<base::DictionaryValue> extra_params); + + static BrowserPluginGuest* CreateWithOpener( + int instance_id, + WebContentsImpl* web_contents, + BrowserPluginGuest* opener, + bool has_render_view); + + // Destroys the guest WebContents and all its associated state, including + // this BrowserPluginGuest, and its new unattached windows. + void Destroy(); + + // Returns the identifier that uniquely identifies a browser plugin guest + // within an embedder. + int instance_id() const { return instance_id_; } + + // Overrides factory for testing. Default (NULL) value indicates regular + // (non-test) environment. + static void set_factory_for_testing(BrowserPluginHostFactory* factory) { + BrowserPluginGuest::factory_ = factory; + } + + bool OnMessageReceivedFromEmbedder(const IPC::Message& message); + + void Initialize(WebContentsImpl* embedder_web_contents, + const BrowserPluginHostMsg_Attach_Params& params); + + void set_guest_hang_timeout_for_testing(const base::TimeDelta& timeout) { + guest_hang_timeout_ = timeout; + } + + WebContentsImpl* embedder_web_contents() const { + return embedder_web_contents_; + } + + RenderWidgetHostView* GetEmbedderRenderWidgetHostView(); + + bool focused() const { return focused_; } + bool visible() const { return guest_visible_; } + void clear_damage_buffer() { damage_buffer_.reset(); } + + BrowserPluginGuest* opener() const { return opener_.get(); } + + // Returns whether the mouse pointer was unlocked. + bool UnlockMouseIfNecessary(const NativeWebKeyboardEvent& event); + + void UpdateVisibility(); + + // NotificationObserver implementation. + virtual void Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) OVERRIDE; + + // WebContentsObserver implementation. + virtual void DidCommitProvisionalLoadForFrame( + int64 frame_id, + bool is_main_frame, + const GURL& url, + PageTransition transition_type, + RenderViewHost* render_view_host) OVERRIDE; + virtual void DidStopLoading(RenderViewHost* render_view_host) OVERRIDE; + + virtual void RenderViewReady() OVERRIDE; + virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + // WebContentsDelegate implementation. + virtual bool AddMessageToConsole(WebContents* source, + int32 level, + const string16& message, + int32 line_no, + const string16& source_id) OVERRIDE; + // If a new window is created with target="_blank" and rel="noreferrer", then + // this method is called, indicating that the new WebContents is ready to be + // attached. + virtual void AddNewContents(WebContents* source, + WebContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture, + bool* was_blocked) OVERRIDE; + virtual void CanDownload(RenderViewHost* render_view_host, + int request_id, + const std::string& request_method, + const base::Callback<void(bool)>& callback) OVERRIDE; + virtual void CloseContents(WebContents* source) OVERRIDE; + virtual JavaScriptDialogManager* GetJavaScriptDialogManager() OVERRIDE; + virtual bool HandleContextMenu(const ContextMenuParams& params) OVERRIDE; + virtual void HandleKeyboardEvent( + WebContents* source, + const NativeWebKeyboardEvent& event) OVERRIDE; + virtual WebContents* OpenURLFromTab(WebContents* source, + const OpenURLParams& params) OVERRIDE; + virtual void WebContentsCreated(WebContents* source_contents, + int64 source_frame_id, + const string16& frame_name, + const GURL& target_url, + WebContents* new_contents) OVERRIDE; + virtual void RendererUnresponsive(WebContents* source) OVERRIDE; + virtual void RendererResponsive(WebContents* source) OVERRIDE; + virtual void RunFileChooser(WebContents* web_contents, + const FileChooserParams& params) OVERRIDE; + virtual bool ShouldFocusPageAfterCrash() OVERRIDE; + virtual void RequestMediaAccessPermission( + WebContents* web_contents, + const MediaStreamRequest& request, + const MediaResponseCallback& callback) OVERRIDE; + + // JavaScriptDialogManager implementation. + virtual void RunJavaScriptDialog( + WebContents* web_contents, + const GURL& origin_url, + const std::string& accept_lang, + JavaScriptMessageType javascript_message_type, + const string16& message_text, + const string16& default_prompt_text, + const DialogClosedCallback& callback, + bool* did_suppress_message) OVERRIDE; + virtual void RunBeforeUnloadDialog( + WebContents* web_contents, + const string16& message_text, + bool is_reload, + const DialogClosedCallback& callback) OVERRIDE; + virtual bool HandleJavaScriptDialog(WebContents* web_contents, + bool accept, + const string16* prompt_override) OVERRIDE; + virtual void CancelActiveAndPendingDialogs( + WebContents* web_contents) OVERRIDE; + virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE; + + // Exposes the protected web_contents() from WebContentsObserver. + WebContentsImpl* GetWebContents(); + + // Overridden in tests. + virtual void SetDamageBuffer( + const BrowserPluginHostMsg_ResizeGuest_Params& params); + + gfx::Point GetScreenCoordinates(const gfx::Point& relative_position) const; + + // Helper to send messages to embedder. This methods fills the message with + // the correct routing id. + // Overridden in test implementation since we want to intercept certain + // messages for testing. + virtual void SendMessageToEmbedder(IPC::Message* msg); + + // Returns whether the guest is attached to an embedder. + bool attached() const { return !!embedder_web_contents_; } + + // Attaches this BrowserPluginGuest to the provided |embedder_web_contents| + // and initializes the guest with the provided |params|. Attaching a guest + // to an embedder implies that this guest's lifetime is no longer managed + // by its opener, and it can begin loading resources. + void Attach(WebContentsImpl* embedder_web_contents, + BrowserPluginHostMsg_Attach_Params params); + + // Requests geolocation permission through Embedder JavaScript API. + void AskEmbedderForGeolocationPermission(int bridge_id, + const GURL& requesting_frame, + const GeolocationCallback& callback); + // Cancels pending geolocation request. + void CancelGeolocationRequest(int bridge_id); + + // Allow the embedder to call this for unhandled messages when + // BrowserPluginGuest is already destroyed. + static void AcknowledgeBufferPresent(int route_id, + int gpu_host_id, + const std::string& mailbox_name, + uint32 sync_point); + + // Returns whether BrowserPluginGuest is interested in receiving the given + // |message|. + static bool ShouldForwardToBrowserPluginGuest(const IPC::Message& message); + gfx::Rect ToGuestRect(const gfx::Rect& rect); + + void DragSourceEndedAt(int client_x, int client_y, int screen_x, + int screen_y, WebKit::WebDragOperation operation); + + void DragSourceMovedTo(int client_x, int client_y, + int screen_x, int screen_y); + + // Called when the drag started by this guest ends at an OS-level. + void EndSystemDrag(); + + // |this| takes ownership of |delegate|. + void SetDelegate(BrowserPluginGuestDelegate* delegate); + + void RespondToPermissionRequest(int request_id, + bool should_allow, + const std::string& user_input); + + private: + class EmbedderRenderViewHostObserver; + friend class TestBrowserPluginGuest; + + class DownloadRequest; + class GeolocationRequest; + class JavaScriptDialogRequest; + // MediaRequest because of naming conflicts with MediaStreamRequest. + class MediaRequest; + class NewWindowRequest; + class PermissionRequest; + class PointerLockRequest; + + BrowserPluginGuest(int instance_id, + WebContentsImpl* web_contents, + BrowserPluginGuest* opener, + bool has_render_view); + + // Destroy unattached new windows that have been opened by this + // BrowserPluginGuest. + void DestroyUnattachedWindows(); + + // Bridge IDs correspond to a geolocation request. This method will remove + // the bookkeeping for a particular geolocation request associated with the + // provided |bridge_id|. It returns the request ID of the geolocation request. + int RemoveBridgeID(int bridge_id); + + // Returns the |request_id| generated for the |request| provided. + int RequestPermission( + BrowserPluginPermissionType permission_type, + scoped_refptr<BrowserPluginGuest::PermissionRequest> request, + const base::DictionaryValue& request_info); + + base::SharedMemory* damage_buffer() const { return damage_buffer_.get(); } + const gfx::Size& damage_view_size() const { return damage_view_size_; } + float damage_buffer_scale_factor() const { + return damage_buffer_scale_factor_; + } + // Returns the damage buffer corresponding to the handle in resize |params|. + base::SharedMemory* GetDamageBufferFromEmbedder( + const BrowserPluginHostMsg_ResizeGuest_Params& params); + + bool InAutoSizeBounds(const gfx::Size& size) const; + + void RequestNewWindowPermission(WebContentsImpl* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_bounds, + bool user_gesture); + + // Message handlers for messages from embedder. + + void OnCompositorFrameACK(int instance_id, + int route_id, + uint32 output_surface_id, + int renderer_host_id, + const cc::CompositorFrameAck& ack); + + // Handles drag events from the embedder. + // When dragging, the drag events go to the embedder first, and if the drag + // happens on the browser plugin, then the plugin sends a corresponding + // drag-message to the guest. This routes the drag-message to the guest + // renderer. + void OnDragStatusUpdate(int instance_id, + WebKit::WebDragStatus drag_status, + const DropData& drop_data, + WebKit::WebDragOperationsMask drag_mask, + const gfx::Point& location); + // Instructs the guest to execute an edit command decoded in the embedder. + void OnExecuteEditCommand(int instance_id, + const std::string& command); + // Overriden in tests. + virtual void OnHandleInputEvent(int instance_id, + const gfx::Rect& guest_window_rect, + const WebKit::WebInputEvent* event); + void OnLockMouse(bool user_gesture, + bool last_unlocked_by_target, + bool privileged); + void OnLockMouseAck(int instance_id, bool succeeded); + void OnNavigateGuest(int instance_id, const std::string& src); + void OnPluginDestroyed(int instance_id); + // Grab the new damage buffer from the embedder, and resize the guest's + // web contents. + void OnResizeGuest(int instance_id, + const BrowserPluginHostMsg_ResizeGuest_Params& params); + // Overriden in tests. + virtual void OnSetFocus(int instance_id, bool focused); + // Sets the name of the guest so that other guests in the same partition can + // access it. + void OnSetName(int instance_id, const std::string& name); + // Updates the size state of the guest. + void OnSetSize( + int instance_id, + const BrowserPluginHostMsg_AutoSize_Params& auto_size_params, + const BrowserPluginHostMsg_ResizeGuest_Params& resize_guest_params); + void OnSetEditCommandsForNextKeyEvent( + int instance_id, + const std::vector<EditCommand>& edit_commands); + // The guest WebContents is visible if both its embedder is visible and + // the browser plugin element is visible. If either one is not then the + // WebContents is marked as hidden. A hidden WebContents will consume + // fewer GPU and CPU resources. + // + // When every WebContents in a RenderProcessHost is hidden, it will lower + // the priority of the process (see RenderProcessHostImpl::WidgetHidden). + // + // It will also send a message to the guest renderer process to cleanup + // resources such as dropping back buffers and adjusting memory limits (if in + // compositing mode, see CCLayerTreeHost::setVisible). + // + // Additionally, it will slow down Javascript execution and garbage + // collection. See RenderThreadImpl::IdleHandler (executed when hidden) and + // RenderThreadImpl::IdleHandlerInForegroundTab (executed when visible). + void OnSetVisibility(int instance_id, bool visible); + // Message from embedder acknowledging last HW buffer. + void OnSwapBuffersACK(int instance_id, + int route_id, + int gpu_host_id, + const std::string& mailbox_name, + uint32 sync_point); + void OnUnlockMouse(); + void OnUnlockMouseAck(int instance_id); + void OnUpdateGeometry(int instance_id, const gfx::Rect& view_rect); + void OnUpdateRectACK( + int instance_id, + bool needs_ack, + const BrowserPluginHostMsg_AutoSize_Params& auto_size_params, + const BrowserPluginHostMsg_ResizeGuest_Params& resize_guest_params); + + + // Message handlers for messages from guest. + + void OnDragStopped(); + void OnHandleInputEventAck( + WebKit::WebInputEvent::Type event_type, + InputEventAckState ack_result); + void OnHasTouchEventHandlers(bool accept); + void OnSetCursor(const WebCursor& cursor); + // On MacOSX popups are painted by the browser process. We handle them here + // so that they are positioned correctly. +#if defined(OS_MACOSX) + void OnShowPopup(const ViewHostMsg_ShowPopup_Params& params); +#endif + void OnShowWidget(int route_id, const gfx::Rect& initial_pos); + // Overriden in tests. + virtual void OnTakeFocus(bool reverse); + void OnUpdateFrameName(int frame_id, + bool is_top_level, + const std::string& name); + void OnUpdateRect(const ViewHostMsg_UpdateRect_Params& params); + + // Requests download permission through embedder JavaScript API after + // retrieving url information from IO thread. + void DidRetrieveDownloadURLFromRequestId( + const std::string& request_method, + const base::Callback<void(bool)>& callback, + const std::string& url); + + // Embedder sets permission to allow or deny geolocation request. + void SetGeolocationPermission( + GeolocationCallback callback, int bridge_id, bool allowed); + + // Forwards all messages from the |pending_messages_| queue to the embedder. + void SendQueuedMessages(); + + // Weak pointer used to ask GeolocationPermissionContext about geolocation + // permission. + base::WeakPtrFactory<BrowserPluginGuest> weak_ptr_factory_; + + // Static factory instance (always NULL for non-test). + static BrowserPluginHostFactory* factory_; + + NotificationRegistrar notification_registrar_; + scoped_ptr<EmbedderRenderViewHostObserver> embedder_rvh_observer_; + WebContentsImpl* embedder_web_contents_; + + std::map<int, int> bridge_id_to_request_id_map_; + + // An identifier that uniquely identifies a browser plugin guest within an + // embedder. + int instance_id_; + scoped_ptr<base::SharedMemory> damage_buffer_; + // An identifier that uniquely identifies a damage buffer. + uint32 damage_buffer_sequence_id_; + size_t damage_buffer_size_; + gfx::Size damage_view_size_; + float damage_buffer_scale_factor_; + float guest_device_scale_factor_; + gfx::Rect guest_window_rect_; + gfx::Rect guest_screen_rect_; + base::TimeDelta guest_hang_timeout_; + bool focused_; + bool mouse_locked_; + bool pending_lock_request_; + bool guest_visible_; + bool embedder_visible_; + std::string name_; + bool auto_size_enabled_; + gfx::Size max_auto_size_; + gfx::Size min_auto_size_; + + // Tracks the name, and target URL of the new window and whether or not it has + // changed since the WebContents has been created and before the new window + // has been attached to a BrowserPlugin. Once the first navigation commits, we + // no longer track this information. + struct NewWindowInfo { + bool changed; + GURL url; + std::string name; + NewWindowInfo(const GURL& url, const std::string& name) : + changed(false), + url(url), + name(name) {} + }; + typedef std::map<BrowserPluginGuest*, NewWindowInfo> PendingWindowMap; + PendingWindowMap pending_new_windows_; + base::WeakPtr<BrowserPluginGuest> opener_; + // A counter to generate a unique request id for a permission request. + // We only need the ids to be unique for a given BrowserPluginGuest. + int next_permission_request_id_; + + // A map to store relevant info for a request keyed by the request's id. + typedef std::map<int, scoped_refptr<PermissionRequest> > RequestMap; + RequestMap permission_request_map_; + + // Indicates that this BrowserPluginGuest has associated renderer-side state. + // This is used to determine whether or not to create a new RenderView when + // this guest is attached. + bool has_render_view_; + + // Last seen size of guest contents (by OnUpdateRect). + gfx::Size last_seen_view_size_; + // Last seen autosize attribute state (by OnUpdateRect). + bool last_seen_auto_size_enabled_; + + // This is a queue of messages that are destined to be sent to the embedder + // once the guest is attached to a particular embedder. + std::queue<IPC::Message*> pending_messages_; + + scoped_ptr<BrowserPluginGuestDelegate> delegate_; + + DISALLOW_COPY_AND_ASSIGN(BrowserPluginGuest); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_H_ diff --git a/chromium/content/browser/browser_plugin/browser_plugin_guest_helper.cc b/chromium/content/browser/browser_plugin/browser_plugin_guest_helper.cc new file mode 100644 index 00000000000..ee18b6e92e3 --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_guest_helper.cc @@ -0,0 +1,55 @@ +// Copyright (c) 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/browser/browser_plugin/browser_plugin_guest_helper.h" + +#include "content/browser/browser_plugin/browser_plugin_guest.h" +#include "content/common/drag_messages.h" +#include "content/common/view_messages.h" +#include "content/public/browser/render_view_host.h" + +namespace content { + +BrowserPluginGuestHelper::BrowserPluginGuestHelper( + BrowserPluginGuest* guest, + RenderViewHost* render_view_host) + : RenderViewHostObserver(render_view_host), + guest_(guest) { +} + +BrowserPluginGuestHelper::~BrowserPluginGuestHelper() { +} + +bool BrowserPluginGuestHelper::OnMessageReceived( + const IPC::Message& message) { + if (ShouldForwardToBrowserPluginGuest(message)) + return guest_->OnMessageReceived(message); + return false; +} + +// static +bool BrowserPluginGuestHelper::ShouldForwardToBrowserPluginGuest( + const IPC::Message& message) { + switch (message.type()) { + case DragHostMsg_StartDragging::ID: + case DragHostMsg_TargetDrop_ACK::ID: + case ViewHostMsg_HasTouchEventHandlers::ID: + case ViewHostMsg_SetCursor::ID: + #if defined(OS_MACOSX) + case ViewHostMsg_ShowPopup::ID: + #endif + case ViewHostMsg_ShowWidget::ID: + case ViewHostMsg_TakeFocus::ID: + case ViewHostMsg_UpdateFrameName::ID: + case ViewHostMsg_UpdateRect::ID: + case ViewHostMsg_LockMouse::ID: + case ViewHostMsg_UnlockMouse::ID: + return true; + default: + break; + } + return false; +} + +} // namespace content diff --git a/chromium/content/browser/browser_plugin/browser_plugin_guest_helper.h b/chromium/content/browser/browser_plugin/browser_plugin_guest_helper.h new file mode 100644 index 00000000000..dfe2e321691 --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_guest_helper.h @@ -0,0 +1,59 @@ +// Copyright (c) 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. + +#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_HELPER_H_ +#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_HELPER_H_ + +#include "content/port/common/input_event_ack_state.h" +#include "content/public/browser/render_view_host_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "third_party/WebKit/public/web/WebDragOperation.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" + +class WebCursor; +#if defined(OS_MACOSX) +struct ViewHostMsg_ShowPopup_Params; +#endif +struct ViewHostMsg_UpdateRect_Params; + +namespace gfx { +class Size; +} + +namespace content { +class BrowserPluginGuest; +class RenderViewHost; + +// Helper for BrowserPluginGuest. +// +// The purpose of this class is to intercept messages from the guest RenderView +// before they are handled by the standard message handlers in the browser +// process. This permits overriding standard behavior with BrowserPlugin- +// specific behavior. +// +// The lifetime of this class is managed by the associated RenderViewHost. A +// BrowserPluginGuestHelper is created whenever a BrowserPluginGuest is created. +class BrowserPluginGuestHelper : public RenderViewHostObserver { + public: + BrowserPluginGuestHelper(BrowserPluginGuest* guest, + RenderViewHost* render_view_host); + virtual ~BrowserPluginGuestHelper(); + + protected: + // RenderViewHostObserver implementation. + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + private: + // Returns whether a message should be forward to the helper's associated + // BrowserPluginGuest. + static bool ShouldForwardToBrowserPluginGuest(const IPC::Message& message); + + BrowserPluginGuest* guest_; + + DISALLOW_COPY_AND_ASSIGN(BrowserPluginGuestHelper); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_HELPER_H_ diff --git a/chromium/content/browser/browser_plugin/browser_plugin_guest_manager.cc b/chromium/content/browser/browser_plugin/browser_plugin_guest_manager.cc new file mode 100644 index 00000000000..75de2031333 --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_guest_manager.cc @@ -0,0 +1,274 @@ +// Copyright (c) 2013 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/browser/browser_plugin/browser_plugin_guest_manager.h" + +#include "base/command_line.h" +#include "content/browser/browser_plugin/browser_plugin_guest.h" +#include "content/browser/browser_plugin/browser_plugin_host_factory.h" +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/common/browser_plugin/browser_plugin_constants.h" +#include "content/common/browser_plugin/browser_plugin_messages.h" +#include "content/common/content_export.h" +#include "content/public/browser/user_metrics.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/result_codes.h" +#include "content/public/common/url_constants.h" +#include "net/base/escape.h" +#include "ui/base/keycodes/keyboard_codes.h" + +namespace content { + +// static +BrowserPluginHostFactory* BrowserPluginGuestManager::factory_ = NULL; + +BrowserPluginGuestManager::BrowserPluginGuestManager() + : next_instance_id_(browser_plugin::kInstanceIDNone) { +} + +BrowserPluginGuestManager::~BrowserPluginGuestManager() { +} + +// static +BrowserPluginGuestManager* BrowserPluginGuestManager::Create() { + if (factory_) + return factory_->CreateBrowserPluginGuestManager(); + return new BrowserPluginGuestManager(); +} + +BrowserPluginGuest* BrowserPluginGuestManager::CreateGuest( + SiteInstance* embedder_site_instance, + int instance_id, + const BrowserPluginHostMsg_Attach_Params& params, + scoped_ptr<base::DictionaryValue> extra_params) { + SiteInstance* guest_site_instance = NULL; + // Validate that the partition id coming from the renderer is valid UTF-8, + // since we depend on this in other parts of the code, such as FilePath + // creation. If the validation fails, treat it as a bad message and kill the + // renderer process. + if (!IsStringUTF8(params.storage_partition_id)) { + content::RecordAction(UserMetricsAction("BadMessageTerminate_BPGM")); + base::KillProcess( + embedder_site_instance->GetProcess()->GetHandle(), + content::RESULT_CODE_KILLED_BAD_MESSAGE, false); + return NULL; + } + + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + if (command_line.HasSwitch(switches::kSitePerProcess)) { + // When --site-per-process is specified, the behavior of BrowserPlugin + // as <webview> is broken and we use it for rendering out-of-process + // iframes instead. We use the src URL sent by the renderer to find the + // right process in which to place this instance. + // Note: Since BrowserPlugin doesn't support cross-process navigation, + // the instance will stay in the initially assigned process, regardless + // of the site it is navigated to. + // TODO(nasko): Fix this, and such that cross-process navigations are + // supported. + guest_site_instance = + embedder_site_instance->GetRelatedSiteInstance(GURL(params.src)); + } else { + const std::string& host = embedder_site_instance->GetSiteURL().host(); + + std::string url_encoded_partition = net::EscapeQueryParamValue( + params.storage_partition_id, false); + // The SiteInstance of a given webview tag is based on the fact that it's + // a guest process in addition to which platform application the tag + // belongs to and what storage partition is in use, rather than the URL + // that the tag is being navigated to. + GURL guest_site( + base::StringPrintf("%s://%s/%s?%s", chrome::kGuestScheme, + host.c_str(), + params.persist_storage ? "persist" : "", + url_encoded_partition.c_str())); + + // If we already have a webview tag in the same app using the same storage + // partition, we should use the same SiteInstance so the existing tag and + // the new tag can script each other. + guest_site_instance = GetGuestSiteInstance(guest_site); + if (!guest_site_instance) { + // Create the SiteInstance in a new BrowsingInstance, which will ensure + // that webview tags are also not allowed to send messages across + // different partitions. + guest_site_instance = SiteInstance::CreateForURL( + embedder_site_instance->GetBrowserContext(), guest_site); + } + } + + return WebContentsImpl::CreateGuest( + embedder_site_instance->GetBrowserContext(), + guest_site_instance, + instance_id, + extra_params.Pass()); +} + +BrowserPluginGuest* BrowserPluginGuestManager::GetGuestByInstanceID( + int instance_id, + int embedder_render_process_id) const { + if (!CanEmbedderAccessInstanceIDMaybeKill(embedder_render_process_id, + instance_id)) { + return NULL; + } + GuestInstanceMap::const_iterator it = + guest_web_contents_by_instance_id_.find(instance_id); + if (it == guest_web_contents_by_instance_id_.end()) + return NULL; + return static_cast<WebContentsImpl*>(it->second)->GetBrowserPluginGuest(); +} + +void BrowserPluginGuestManager::AddGuest(int instance_id, + WebContentsImpl* guest_web_contents) { + DCHECK(guest_web_contents_by_instance_id_.find(instance_id) == + guest_web_contents_by_instance_id_.end()); + guest_web_contents_by_instance_id_[instance_id] = guest_web_contents; +} + +void BrowserPluginGuestManager::RemoveGuest(int instance_id) { + DCHECK(guest_web_contents_by_instance_id_.find(instance_id) != + guest_web_contents_by_instance_id_.end()); + guest_web_contents_by_instance_id_.erase(instance_id); +} + +bool BrowserPluginGuestManager::CanEmbedderAccessInstanceIDMaybeKill( + int embedder_render_process_id, + int instance_id) const { + if (!CanEmbedderAccessInstanceID(embedder_render_process_id, instance_id)) { + // The embedder process is trying to access a guest it does not own. + content::RecordAction(UserMetricsAction("BadMessageTerminate_BPGM")); + base::KillProcess( + RenderProcessHost::FromID(embedder_render_process_id)->GetHandle(), + content::RESULT_CODE_KILLED_BAD_MESSAGE, false); + return false; + } + return true; +} + +void BrowserPluginGuestManager::OnMessageReceived(const IPC::Message& message, + int render_process_id) { + if (BrowserPluginGuest::ShouldForwardToBrowserPluginGuest(message)) { + int instance_id = 0; + // All allowed messages must have instance_id as their first parameter. + PickleIterator iter(message); + bool success = iter.ReadInt(&instance_id); + DCHECK(success); + BrowserPluginGuest* guest = + GetGuestByInstanceID(instance_id, render_process_id); + if (guest && guest->OnMessageReceivedFromEmbedder(message)) + return; + } + IPC_BEGIN_MESSAGE_MAP(BrowserPluginGuestManager, message) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_BuffersSwappedACK, + OnUnhandledSwapBuffersACK) + IPC_END_MESSAGE_MAP() +} + +// static +bool BrowserPluginGuestManager::CanEmbedderAccessGuest( + int embedder_render_process_id, + BrowserPluginGuest* guest) { + // The embedder can access the guest if it has not been attached and its + // opener's embedder lives in the same process as the given embedder. + if (!guest->attached()) { + if (!guest->opener()) + return false; + + return embedder_render_process_id == + guest->opener()->embedder_web_contents()->GetRenderProcessHost()-> + GetID(); + } + + return embedder_render_process_id == + guest->embedder_web_contents()->GetRenderProcessHost()->GetID(); +} + +bool BrowserPluginGuestManager::CanEmbedderAccessInstanceID( + int embedder_render_process_id, + int instance_id) const { + // The embedder is trying to access a guest with a negative or zero + // instance ID. + if (instance_id <= browser_plugin::kInstanceIDNone) + return false; + + // The embedder is trying to access an instance ID that has not yet been + // allocated by BrowserPluginGuestManager. This could cause instance ID + // collisions in the future, and potentially give one embedder access to a + // guest it does not own. + if (instance_id > next_instance_id_) + return false; + + GuestInstanceMap::const_iterator it = + guest_web_contents_by_instance_id_.find(instance_id); + if (it == guest_web_contents_by_instance_id_.end()) + return true; + BrowserPluginGuest* guest = + static_cast<WebContentsImpl*>(it->second)->GetBrowserPluginGuest(); + + return CanEmbedderAccessGuest(embedder_render_process_id, guest); +} + +SiteInstance* BrowserPluginGuestManager::GetGuestSiteInstance( + const GURL& guest_site) { + for (GuestInstanceMap::const_iterator it = + guest_web_contents_by_instance_id_.begin(); + it != guest_web_contents_by_instance_id_.end(); ++it) { + if (it->second->GetSiteInstance()->GetSiteURL() == guest_site) + return it->second->GetSiteInstance(); + } + return NULL; +} + +// We only get here during teardown if we have one last buffer pending, +// otherwise the ACK is handled by the guest. +void BrowserPluginGuestManager::OnUnhandledSwapBuffersACK( + int instance_id, + int route_id, + int gpu_host_id, + const std::string& mailbox_name, + uint32 sync_point) { + BrowserPluginGuest::AcknowledgeBufferPresent(route_id, + gpu_host_id, + mailbox_name, + sync_point); +} + +void BrowserPluginGuestManager::DidSendScreenRects( + WebContentsImpl* embedder_web_contents) { + // TODO(lazyboy): Generalize iterating over guest instances and performing + // actions on the guests. + for (GuestInstanceMap::iterator it = + guest_web_contents_by_instance_id_.begin(); + it != guest_web_contents_by_instance_id_.end(); ++it) { + BrowserPluginGuest* guest = it->second->GetBrowserPluginGuest(); + if (embedder_web_contents == guest->embedder_web_contents()) { + static_cast<RenderViewHostImpl*>( + guest->GetWebContents()->GetRenderViewHost())->SendScreenRects(); + } + } +} + +bool BrowserPluginGuestManager::UnlockMouseIfNecessary( + WebContentsImpl* embedder_web_contents, + const NativeWebKeyboardEvent& event) { + if ((event.type != WebKit::WebInputEvent::RawKeyDown) || + (event.windowsKeyCode != ui::VKEY_ESCAPE) || + (event.modifiers & WebKit::WebInputEvent::InputModifiers)) { + return false; + } + + // TODO(lazyboy): Generalize iterating over guest instances and performing + // actions on the guests. + for (GuestInstanceMap::iterator it = + guest_web_contents_by_instance_id_.begin(); + it != guest_web_contents_by_instance_id_.end(); ++it) { + BrowserPluginGuest* guest = it->second->GetBrowserPluginGuest(); + if (embedder_web_contents == guest->embedder_web_contents()) { + if (guest->UnlockMouseIfNecessary(event)) + return true; + } + } + return false; +} + +} // namespace content diff --git a/chromium/content/browser/browser_plugin/browser_plugin_guest_manager.h b/chromium/content/browser/browser_plugin/browser_plugin_guest_manager.h new file mode 100644 index 00000000000..a657bfa7a67 --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_guest_manager.h @@ -0,0 +1,137 @@ +// Copyright 2013 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. + +// A BrowserPluginGuestManager serves as a message router to BrowserPluginGuests +// for all guests within a given profile. +// Messages are routed to a particular guest instance based on an instance_id. + +#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_MANAGER_H_ +#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_MANAGER_H_ + +#include "base/basictypes.h" +#include "base/supports_user_data.h" +#include "base/values.h" +#include "content/common/content_export.h" +#include "ipc/ipc_message.h" + +struct BrowserPluginHostMsg_Attach_Params; +struct BrowserPluginHostMsg_ResizeGuest_Params; +class GURL; + +namespace gfx { +class Point; +} + +namespace IPC { +class Message; +} // namespace IPC + +namespace content { + +class BrowserPluginGuest; +class BrowserPluginHostFactory; +class RenderProcessHostImpl; +class RenderWidgetHostImpl; +class SiteInstance; +class WebContents; +class WebContentsImpl; +struct NativeWebKeyboardEvent; + +class CONTENT_EXPORT BrowserPluginGuestManager : + public base::SupportsUserData::Data { + public: + virtual ~BrowserPluginGuestManager(); + + static BrowserPluginGuestManager* Create(); + + // Overrides factory for testing. Default (NULL) value indicates regular + // (non-test) environment. + static void set_factory_for_testing(BrowserPluginHostFactory* factory) { + content::BrowserPluginGuestManager::factory_ = factory; + } + + // Gets the next available instance id. + int get_next_instance_id() { return ++next_instance_id_; } + + // Creates a guest WebContents with the provided |instance_id| and |params|. + // If params.src is present, the new guest will also be navigated to the + // provided URL. Optionally, the new guest may be attached to a + // |guest_opener|, and may be attached to a pre-selected |routing_id|. + BrowserPluginGuest* CreateGuest( + SiteInstance* embedder_site_instance, + int instance_id, + const BrowserPluginHostMsg_Attach_Params& params, + scoped_ptr<base::DictionaryValue> extra_params); + + // Returns a BrowserPluginGuest given an |instance_id|. Returns NULL if the + // guest wasn't found. If the embedder is not permitted to access the given + // |instance_id|, the embedder is killed, and NULL is returned. + BrowserPluginGuest* GetGuestByInstanceID( + int instance_id, + int embedder_render_process_id) const; + + // Adds a new |guest_web_contents| to the embedder (overridable in test). + virtual void AddGuest(int instance_id, WebContentsImpl* guest_web_contents); + + // Removes the guest with the given |instance_id| from this + // BrowserPluginGuestManager. + void RemoveGuest(int instance_id); + + // Returns whether the specified embedder is permitted to access the given + // |instance_id|, and kills the embedder if not. + bool CanEmbedderAccessInstanceIDMaybeKill(int embedder_render_process_id, + int instance_id) const; + + void DidSendScreenRects(WebContentsImpl* embedder_web_contents); + + bool UnlockMouseIfNecessary(WebContentsImpl* embedder_web_contents_, + const NativeWebKeyboardEvent& event); + + void OnMessageReceived(const IPC::Message& message, int render_process_id); + + private: + friend class TestBrowserPluginGuestManager; + + BrowserPluginGuestManager(); + + // Returns whether the given embedder process is allowed to access the + // provided |guest|. + static bool CanEmbedderAccessGuest(int embedder_render_process_id, + BrowserPluginGuest* guest); + + // Returns whether the given embedder process is allowed to use the provided + // |instance_id| or access the guest associated with the |instance_id|. If the + // embedder can, the method returns true. If the guest does not exist but the + // embedder can use that |instance_id|, then it returns true. If the embedder + // is not permitted to use that instance ID or access the associated guest, + // then it returns false. + bool CanEmbedderAccessInstanceID(int embedder_render_process_id, + int instance_id) const; + + // Returns an existing SiteInstance if the current profile has a guest of the + // given |guest_site|. + SiteInstance* GetGuestSiteInstance(const GURL& guest_site); + + // Message handlers. + void OnUnhandledSwapBuffersACK(int instance_id, + int route_id, + int gpu_host_id, + const std::string& mailbox_name, + uint32 sync_point); + + // Static factory instance (always NULL outside of tests). + static BrowserPluginHostFactory* factory_; + + // Contains guests' WebContents, mapping from their instance ids. + typedef std::map<int, WebContentsImpl*> GuestInstanceMap; + GuestInstanceMap guest_web_contents_by_instance_id_; + int next_instance_id_; + + DISALLOW_COPY_AND_ASSIGN(BrowserPluginGuestManager); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_MANAGER_H_ + diff --git a/chromium/content/browser/browser_plugin/browser_plugin_host_browsertest.cc b/chromium/content/browser/browser_plugin/browser_plugin_host_browsertest.cc new file mode 100644 index 00000000000..c50e8cfa8a3 --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_host_browsertest.cc @@ -0,0 +1,814 @@ +// 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 "base/command_line.h" +#include "base/memory/singleton.h" +#include "base/run_loop.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/test_timeouts.h" +#include "content/browser/browser_plugin/browser_plugin_guest.h" +#include "content/browser/browser_plugin/browser_plugin_host_factory.h" +#include "content/browser/browser_plugin/test_browser_plugin_embedder.h" +#include "content/browser/browser_plugin/test_browser_plugin_guest.h" +#include "content/browser/browser_plugin/test_browser_plugin_guest_manager.h" +#include "content/browser/child_process_security_policy_impl.h" +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/common/view_messages.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/render_view_host_observer.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/drop_data.h" +#include "content/public/test/browser_test_utils.h" +#include "content/public/test/test_utils.h" +#include "content/shell/shell.h" +#include "content/test/content_browser_test.h" +#include "content/test/content_browser_test_utils.h" +#include "net/base/net_util.h" +#include "net/test/embedded_test_server/embedded_test_server.h" +#include "net/test/embedded_test_server/http_request.h" +#include "net/test/embedded_test_server/http_response.h" +#include "net/test/spawned_test_server/spawned_test_server.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" + +using WebKit::WebInputEvent; +using WebKit::WebMouseEvent; +using content::BrowserPluginEmbedder; +using content::BrowserPluginGuest; +using content::BrowserPluginHostFactory; +using content::WebContentsImpl; + +namespace { + +const char kHTMLForGuest[] = + "data:text/html,<html><body>hello world</body></html>"; +const char kHTMLForGuestBusyLoop[] = + "data:text/html,<html><head><script type=\"text/javascript\">" + "function PauseMs(timems) {" + " document.title = \"start\";" + " var date = new Date();" + " var currDate = null;" + " do {" + " currDate = new Date();" + " } while (currDate - date < timems)" + "}" + "function StartPauseMs(timems) {" + " setTimeout(function() { PauseMs(timems); }, 0);" + "}" + "</script></head><body></body></html>"; +const char kHTMLForGuestTouchHandler[] = + "data:text/html,<html><body><div id=\"touch\">With touch</div></body>" + "<script type=\"text/javascript\">" + "function handler() {}" + "function InstallTouchHandler() { " + " document.getElementById(\"touch\").addEventListener(\"touchstart\", " + " handler);" + "}" + "function UninstallTouchHandler() { " + " document.getElementById(\"touch\").removeEventListener(\"touchstart\", " + " handler);" + "}" + "</script></html>"; +const char kHTMLForGuestWithTitle[] = + "data:text/html," + "<html><head><title>%s</title></head>" + "<body>hello world</body>" + "</html>"; +const char kHTMLForGuestAcceptDrag[] = + "data:text/html,<html><body>" + "<script>" + "function dropped() {" + " document.title = \"DROPPED\";" + "}" + "</script>" + "<textarea id=\"text\" style=\"width:100%; height: 100%\"" + " ondrop=\"dropped();\">" + "</textarea>" + "</body></html>"; +const char kHTMLForGuestWithSize[] = + "data:text/html," + "<html>" + "<body style=\"margin: 0px;\">" + "<img style=\"width: 100%; height: 400px;\"/>" + "</body>" + "</html>"; + +std::string GetHTMLForGuestWithTitle(const std::string& title) { + return base::StringPrintf(kHTMLForGuestWithTitle, title.c_str()); +} + +} // namespace + +namespace content { + +// Test factory for creating test instances of BrowserPluginEmbedder and +// BrowserPluginGuest. +class TestBrowserPluginHostFactory : public BrowserPluginHostFactory { + public: + virtual BrowserPluginGuestManager* + CreateBrowserPluginGuestManager() OVERRIDE { + guest_manager_instance_count_++; + if (message_loop_runner_.get()) + message_loop_runner_->Quit(); + return new TestBrowserPluginGuestManager(); + } + + virtual BrowserPluginGuest* CreateBrowserPluginGuest( + int instance_id, + WebContentsImpl* web_contents) OVERRIDE { + return new TestBrowserPluginGuest(instance_id, web_contents); + } + + // Also keeps track of number of instances created. + virtual BrowserPluginEmbedder* CreateBrowserPluginEmbedder( + WebContentsImpl* web_contents) OVERRIDE { + + return new TestBrowserPluginEmbedder(web_contents); + } + + // Singleton getter. + static TestBrowserPluginHostFactory* GetInstance() { + return Singleton<TestBrowserPluginHostFactory>::get(); + } + + // Waits for at least one embedder to be created in the test. Returns true if + // we have a guest, false if waiting times out. + void WaitForGuestManagerCreation() { + // Check if already have created an instance. + if (guest_manager_instance_count_ > 0) + return; + // Wait otherwise. + message_loop_runner_ = new MessageLoopRunner(); + message_loop_runner_->Run(); + } + + protected: + TestBrowserPluginHostFactory() : guest_manager_instance_count_(0) {} + virtual ~TestBrowserPluginHostFactory() {} + + private: + // For Singleton. + friend struct DefaultSingletonTraits<TestBrowserPluginHostFactory>; + + scoped_refptr<MessageLoopRunner> message_loop_runner_; + int guest_manager_instance_count_; + + DISALLOW_COPY_AND_ASSIGN(TestBrowserPluginHostFactory); +}; + +// Test factory class for browser plugin that creates guests with short hang +// timeout. +class TestShortHangTimeoutGuestFactory : public TestBrowserPluginHostFactory { + public: + virtual BrowserPluginGuest* CreateBrowserPluginGuest( + int instance_id, WebContentsImpl* web_contents) OVERRIDE { + BrowserPluginGuest* guest = + new TestBrowserPluginGuest(instance_id, web_contents); + guest->set_guest_hang_timeout_for_testing(TestTimeouts::tiny_timeout()); + return guest; + } + + // Singleton getter. + static TestShortHangTimeoutGuestFactory* GetInstance() { + return Singleton<TestShortHangTimeoutGuestFactory>::get(); + } + + protected: + TestShortHangTimeoutGuestFactory() {} + virtual ~TestShortHangTimeoutGuestFactory() {} + + private: + // For Singleton. + friend struct DefaultSingletonTraits<TestShortHangTimeoutGuestFactory>; + + DISALLOW_COPY_AND_ASSIGN(TestShortHangTimeoutGuestFactory); +}; + +// A transparent observer that can be used to verify that a RenderViewHost +// received a specific message. +class RenderViewHostMessageObserver : public RenderViewHostObserver { + public: + RenderViewHostMessageObserver(RenderViewHost* host, + uint32 message_id) + : RenderViewHostObserver(host), + message_id_(message_id), + message_received_(false) { + } + + virtual ~RenderViewHostMessageObserver() {} + + void WaitUntilMessageReceived() { + if (message_received_) + return; + message_loop_runner_ = new MessageLoopRunner(); + message_loop_runner_->Run(); + } + + void ResetState() { + message_received_ = false; + } + + // IPC::Listener implementation. + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { + if (message.type() == message_id_) { + message_received_ = true; + if (message_loop_runner_.get()) + message_loop_runner_->Quit(); + } + return false; + } + + private: + scoped_refptr<MessageLoopRunner> message_loop_runner_; + uint32 message_id_; + bool message_received_; + + DISALLOW_COPY_AND_ASSIGN(RenderViewHostMessageObserver); +}; + +class BrowserPluginHostTest : public ContentBrowserTest { + public: + BrowserPluginHostTest() + : test_embedder_(NULL), + test_guest_(NULL), + test_guest_manager_(NULL) {} + + virtual void SetUp() OVERRIDE { + // Override factory to create tests instances of BrowserPlugin*. + content::BrowserPluginEmbedder::set_factory_for_testing( + TestBrowserPluginHostFactory::GetInstance()); + content::BrowserPluginGuest::set_factory_for_testing( + TestBrowserPluginHostFactory::GetInstance()); + content::BrowserPluginGuestManager::set_factory_for_testing( + TestBrowserPluginHostFactory::GetInstance()); + + // On legacy windows, the AcceptDragEvents test needs this to pass. +#if defined(OS_WIN) && !defined(USE_AURA) + UseRealGLBindings(); +#endif + + ContentBrowserTest::SetUp(); + } + virtual void TearDown() OVERRIDE { + content::BrowserPluginEmbedder::set_factory_for_testing(NULL); + content::BrowserPluginGuest::set_factory_for_testing(NULL); + + ContentBrowserTest::TearDown(); + } + + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + // Enable browser plugin in content_shell for running test. + command_line->AppendSwitch(switches::kEnableBrowserPluginForAllViewTypes); + } + + static void SimulateSpaceKeyPress(WebContents* web_contents) { + SimulateKeyPress(web_contents, + ui::VKEY_SPACE, + false, // control. + false, // shift. + false, // alt. + false); // command. + } + + static void SimulateTabKeyPress(WebContents* web_contents) { + SimulateKeyPress(web_contents, + ui::VKEY_TAB, + false, // control. + false, // shift. + false, // alt. + false); // command. + } + + // Executes the javascript synchronously and makes sure the returned value is + // freed properly. + void ExecuteSyncJSFunction(RenderViewHost* rvh, const std::string& jscript) { + scoped_ptr<base::Value> value = + content::ExecuteScriptAndGetValue(rvh, jscript); + } + + bool IsAttributeNull(RenderViewHost* rvh, const std::string& attribute) { + scoped_ptr<base::Value> value = content::ExecuteScriptAndGetValue(rvh, + "document.getElementById('plugin').getAttribute('" + attribute + "');"); + return value->GetType() == Value::TYPE_NULL; + } + + // Removes all attributes in the comma-delimited string |attributes|. + void RemoveAttributes(RenderViewHost* rvh, const std::string& attributes) { + std::vector<std::string> attributes_list; + base::SplitString(attributes, ',', &attributes_list); + std::vector<std::string>::const_iterator itr; + for (itr = attributes_list.begin(); itr != attributes_list.end(); ++itr) { + ExecuteSyncJSFunction(rvh, "document.getElementById('plugin')" + "." + *itr + " = null;"); + } + } + + // This helper method does the following: + // 1. Start the test server and navigate the shell to |embedder_url|. + // 2. Execute custom pre-navigation |embedder_code| if provided. + // 3. Navigate the guest to the |guest_url|. + // 4. Verify that the guest has been created and has completed loading. + void StartBrowserPluginTest(const std::string& embedder_url, + const std::string& guest_url, + bool is_guest_data_url, + const std::string& embedder_code) { + ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); + GURL test_url(embedded_test_server()->GetURL(embedder_url)); + NavigateToURL(shell(), test_url); + + WebContentsImpl* embedder_web_contents = static_cast<WebContentsImpl*>( + shell()->web_contents()); + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + embedder_web_contents->GetRenderViewHost()); + // Focus the embedder. + rvh->Focus(); + // Activative IME. + rvh->SetInputMethodActive(true); + + // Allow the test to do some operations on the embedder before we perform + // the first navigation of the guest. + if (!embedder_code.empty()) + ExecuteSyncJSFunction(rvh, embedder_code); + + if (!is_guest_data_url) { + test_url = embedded_test_server()->GetURL(guest_url); + ExecuteSyncJSFunction( + rvh, base::StringPrintf("SetSrc('%s');", test_url.spec().c_str())); + } else { + ExecuteSyncJSFunction( + rvh, base::StringPrintf("SetSrc('%s');", guest_url.c_str())); + } + + // Wait to make sure embedder is created/attached to WebContents. + TestBrowserPluginHostFactory::GetInstance()->WaitForGuestManagerCreation(); + + test_embedder_ = static_cast<TestBrowserPluginEmbedder*>( + embedder_web_contents->GetBrowserPluginEmbedder()); + ASSERT_TRUE(test_embedder_); + + test_guest_manager_ = static_cast<TestBrowserPluginGuestManager*>( + embedder_web_contents->GetBrowserPluginGuestManager()); + ASSERT_TRUE(test_guest_manager_); + + test_guest_manager_->WaitForGuestAdded(); + + // Verify that we have exactly one guest. + const TestBrowserPluginGuestManager::GuestInstanceMap& instance_map = + test_guest_manager_->guest_web_contents_for_testing(); + EXPECT_EQ(1u, instance_map.size()); + + WebContentsImpl* test_guest_web_contents = static_cast<WebContentsImpl*>( + instance_map.begin()->second); + test_guest_ = static_cast<TestBrowserPluginGuest*>( + test_guest_web_contents->GetBrowserPluginGuest()); + test_guest_->WaitForLoadStop(); + } + + TestBrowserPluginEmbedder* test_embedder() const { return test_embedder_; } + TestBrowserPluginGuest* test_guest() const { return test_guest_; } + TestBrowserPluginGuestManager* test_guest_manager() const { + return test_guest_manager_; + } + + private: + TestBrowserPluginEmbedder* test_embedder_; + TestBrowserPluginGuest* test_guest_; + TestBrowserPluginGuestManager* test_guest_manager_; + DISALLOW_COPY_AND_ASSIGN(BrowserPluginHostTest); +}; + +// This test ensures that if guest isn't there and we resize the guest (from +// js), it remembers the size correctly. +// +// Initially we load an embedder with a guest without a src attribute (which has +// dimension 640x480), resize it to 100x200, and then we set the source to a +// sample guest. In the end we verify that the correct size has been set. +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, NavigateAfterResize) { + const gfx::Size nxt_size = gfx::Size(100, 200); + const std::string embedder_code = base::StringPrintf( + "SetSize(%d, %d);", nxt_size.width(), nxt_size.height()); + const char kEmbedderURL[] = "/browser_plugin_embedder.html"; + StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, embedder_code); + + // Wait for the guest to receive a damage buffer of size 100x200. + // This means the guest will be painted properly at that size. + test_guest()->WaitForDamageBufferWithSize(nxt_size); +} + +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, AdvanceFocus) { + const char kEmbedderURL[] = "/browser_plugin_focus.html"; + const char* kGuestURL = "/browser_plugin_focus_child.html"; + StartBrowserPluginTest(kEmbedderURL, kGuestURL, false, std::string()); + + SimulateMouseClick(test_embedder()->web_contents(), 0, + WebKit::WebMouseEvent::ButtonLeft); + BrowserPluginHostTest::SimulateTabKeyPress(test_embedder()->web_contents()); + // Wait until we focus into the guest. + test_guest()->WaitForFocus(); + + // TODO(fsamuel): A third Tab key press should not be necessary. + // The browser plugin will take keyboard focus but it will not + // focus an initial element. The initial element is dependent + // upon tab direction which WebKit does not propagate to the plugin. + // See http://crbug.com/147644. + BrowserPluginHostTest::SimulateTabKeyPress(test_embedder()->web_contents()); + BrowserPluginHostTest::SimulateTabKeyPress(test_embedder()->web_contents()); + BrowserPluginHostTest::SimulateTabKeyPress(test_embedder()->web_contents()); + test_guest()->WaitForAdvanceFocus(); +} + +// This test opens a page in http and then opens another page in https, forcing +// a RenderViewHost swap in the web_contents. We verify that the embedder in the +// web_contents gets cleared properly. +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, EmbedderChangedAfterSwap) { + net::SpawnedTestServer https_server( + net::SpawnedTestServer::TYPE_HTTPS, + net::SpawnedTestServer::kLocalhost, + base::FilePath(FILE_PATH_LITERAL("content/test/data"))); + ASSERT_TRUE(https_server.Start()); + + // 1. Load an embedder page with one guest in it. + const char kEmbedderURL[] = "/browser_plugin_embedder.html"; + StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, std::string()); + + // 2. Navigate to a URL in https, so we trigger a RenderViewHost swap. + GURL test_https_url(https_server.GetURL( + "files/browser_plugin_title_change.html")); + content::WindowedNotificationObserver swap_observer( + content::NOTIFICATION_WEB_CONTENTS_SWAPPED, + content::Source<WebContents>(test_embedder()->web_contents())); + NavigateToURL(shell(), test_https_url); + swap_observer.Wait(); + + TestBrowserPluginEmbedder* test_embedder_after_swap = + static_cast<TestBrowserPluginEmbedder*>( + static_cast<WebContentsImpl*>(shell()->web_contents())-> + GetBrowserPluginEmbedder()); + // Verify we have a no embedder in web_contents (since the new page doesn't + // have any browser plugin). + ASSERT_TRUE(!test_embedder_after_swap); + ASSERT_NE(test_embedder(), test_embedder_after_swap); +} + +// This test opens two pages in http and there is no RenderViewHost swap, +// therefore the embedder created on first page navigation stays the same in +// web_contents. +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, EmbedderSameAfterNav) { + const char kEmbedderURL[] = "/browser_plugin_embedder.html"; + StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, std::string()); + WebContentsImpl* embedder_web_contents = test_embedder()->web_contents(); + + // Navigate to another page in same host and port, so RenderViewHost swap + // does not happen and existing embedder doesn't change in web_contents. + GURL test_url_new(embedded_test_server()->GetURL( + "/browser_plugin_title_change.html")); + const string16 expected_title = ASCIIToUTF16("done"); + content::TitleWatcher title_watcher(shell()->web_contents(), expected_title); + NavigateToURL(shell(), test_url_new); + LOG(INFO) << "Start waiting for title"; + string16 actual_title = title_watcher.WaitAndGetTitle(); + EXPECT_EQ(expected_title, actual_title); + LOG(INFO) << "Done navigating to second page"; + + TestBrowserPluginEmbedder* test_embedder_after_nav = + static_cast<TestBrowserPluginEmbedder*>( + embedder_web_contents->GetBrowserPluginEmbedder()); + // Embedder must not change in web_contents. + ASSERT_EQ(test_embedder_after_nav, test_embedder()); +} + +// This test verifies that hiding the embedder also hides the guest. +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, BrowserPluginVisibilityChanged) { + const char kEmbedderURL[] = "/browser_plugin_embedder.html"; + StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, std::string()); + + // Hide the Browser Plugin. + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + test_embedder()->web_contents()->GetRenderViewHost()); + ExecuteSyncJSFunction( + rvh, "document.getElementById('plugin').style.visibility = 'hidden'"); + + // Make sure that the guest is hidden. + test_guest()->WaitUntilHidden(); +} + +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, EmbedderVisibilityChanged) { + const char kEmbedderURL[] = "/browser_plugin_embedder.html"; + StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, std::string()); + + // Hide the embedder. + test_embedder()->web_contents()->WasHidden(); + + // Make sure that hiding the embedder also hides the guest. + test_guest()->WaitUntilHidden(); +} + +// Verifies that installing/uninstalling touch-event handlers in the guest +// plugin correctly updates the touch-event handling state in the embedder. +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, AcceptTouchEvents) { + const char kEmbedderURL[] = "/browser_plugin_embedder.html"; + StartBrowserPluginTest( + kEmbedderURL, kHTMLForGuestTouchHandler, true, std::string()); + + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + test_embedder()->web_contents()->GetRenderViewHost()); + // The embedder should not have any touch event handlers at this point. + EXPECT_FALSE(rvh->has_touch_handler()); + + // Install the touch handler in the guest. This should cause the embedder to + // start listening for touch events too. + RenderViewHostMessageObserver observer(rvh, + ViewHostMsg_HasTouchEventHandlers::ID); + ExecuteSyncJSFunction(test_guest()->web_contents()->GetRenderViewHost(), + "InstallTouchHandler();"); + observer.WaitUntilMessageReceived(); + EXPECT_TRUE(rvh->has_touch_handler()); + + // Uninstalling the touch-handler in guest should cause the embedder to stop + // listening for touch events. + observer.ResetState(); + ExecuteSyncJSFunction(test_guest()->web_contents()->GetRenderViewHost(), + "UninstallTouchHandler();"); + observer.WaitUntilMessageReceived(); + EXPECT_FALSE(rvh->has_touch_handler()); +} + +// This tests verifies that reloading the embedder does not crash the browser +// and that the guest is reset. +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, ReloadEmbedder) { + const char kEmbedderURL[] = "/browser_plugin_embedder.html"; + StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, std::string()); + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + test_embedder()->web_contents()->GetRenderViewHost()); + + // Change the title of the page to 'modified' so that we know that + // the page has successfully reloaded when it goes back to 'embedder' + // in the next step. + { + const string16 expected_title = ASCIIToUTF16("modified"); + content::TitleWatcher title_watcher(test_embedder()->web_contents(), + expected_title); + + ExecuteSyncJSFunction(rvh, + base::StringPrintf("SetTitle('%s');", "modified")); + + string16 actual_title = title_watcher.WaitAndGetTitle(); + EXPECT_EQ(expected_title, actual_title); + } + + // Reload the embedder page, and verify that the reload was successful. + // Then navigate the guest to verify that the browser process does not crash. + { + const string16 expected_title = ASCIIToUTF16("embedder"); + content::TitleWatcher title_watcher(test_embedder()->web_contents(), + expected_title); + + test_embedder()->web_contents()->GetController().Reload(false); + string16 actual_title = title_watcher.WaitAndGetTitle(); + EXPECT_EQ(expected_title, actual_title); + + ExecuteSyncJSFunction( + test_embedder()->web_contents()->GetRenderViewHost(), + base::StringPrintf("SetSrc('%s');", kHTMLForGuest)); + test_guest_manager()->WaitForGuestAdded(); + + const TestBrowserPluginGuestManager::GuestInstanceMap& instance_map = + test_guest_manager()->guest_web_contents_for_testing(); + WebContentsImpl* test_guest_web_contents = static_cast<WebContentsImpl*>( + instance_map.begin()->second); + TestBrowserPluginGuest* new_test_guest = + static_cast<TestBrowserPluginGuest*>( + test_guest_web_contents->GetBrowserPluginGuest()); + ASSERT_TRUE(new_test_guest != NULL); + + // Wait for the guest to send an UpdateRectMsg, meaning it is ready. + new_test_guest->WaitForUpdateRectMsg(); + } +} + +// Always failing in the win7_aura try bot. See http://crbug.com/181107. +#if defined(OS_WIN) && defined(USE_AURA) +#define MAYBE_AcceptDragEvents DISABLED_AcceptDragEvents +#else +#define MAYBE_AcceptDragEvents AcceptDragEvents +#endif + +// Tests that a drag-n-drop over the browser plugin in the embedder happens +// correctly. +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, MAYBE_AcceptDragEvents) { + const char kEmbedderURL[] = "/browser_plugin_dragging.html"; + StartBrowserPluginTest( + kEmbedderURL, kHTMLForGuestAcceptDrag, true, std::string()); + + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + test_embedder()->web_contents()->GetRenderViewHost()); + + // Get a location in the embedder outside of the plugin. + base::ListValue *start, *end; + scoped_ptr<base::Value> value = + content::ExecuteScriptAndGetValue(rvh, "dragLocation()"); + ASSERT_TRUE(value->GetAsList(&start) && start->GetSize() == 2); + double start_x, start_y; + ASSERT_TRUE(start->GetDouble(0, &start_x) && start->GetDouble(1, &start_y)); + + // Get a location in the embedder that falls inside the plugin. + value = content::ExecuteScriptAndGetValue(rvh, "dropLocation()"); + ASSERT_TRUE(value->GetAsList(&end) && end->GetSize() == 2); + double end_x, end_y; + ASSERT_TRUE(end->GetDouble(0, &end_x) && end->GetDouble(1, &end_y)); + + DropData drop_data; + GURL url = GURL("https://www.domain.com/index.html"); + drop_data.url = url; + + // Pretend that the URL is being dragged over the embedder. Start the drag + // from outside the plugin, then move the drag inside the plugin and drop. + // This should trigger appropriate messages from the embedder to the guest, + // and end with a drop on the guest. The guest changes title when a drop + // happens. + const string16 expected_title = ASCIIToUTF16("DROPPED"); + content::TitleWatcher title_watcher(test_guest()->web_contents(), + expected_title); + + rvh->DragTargetDragEnter(drop_data, gfx::Point(start_x, start_y), + gfx::Point(start_x, start_y), WebKit::WebDragOperationEvery, 0); + rvh->DragTargetDragOver(gfx::Point(end_x, end_y), gfx::Point(end_x, end_y), + WebKit::WebDragOperationEvery, 0); + rvh->DragTargetDrop(gfx::Point(end_x, end_y), gfx::Point(end_x, end_y), 0); + + string16 actual_title = title_watcher.WaitAndGetTitle(); + EXPECT_EQ(expected_title, actual_title); +} + +// This test verifies that round trip postMessage works as expected. +// 1. The embedder posts a message 'testing123' to the guest. +// 2. The guest receives and replies to the message using the event object's +// source object: event.source.postMessage('foobar', '*') +// 3. The embedder receives the message and uses the event's source +// object to do one final reply: 'stop' +// 4. The guest receives the final 'stop' message. +// 5. The guest acks the 'stop' message with a 'stop_ack' message. +// 6. The embedder changes its title to 'main guest' when it sees the 'stop_ack' +// message. +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, PostMessage) { + const char* kTesting = "testing123"; + const char* kEmbedderURL = "/browser_plugin_embedder.html"; + const char* kGuestURL = "/browser_plugin_post_message_guest.html"; + StartBrowserPluginTest(kEmbedderURL, kGuestURL, false, std::string()); + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + test_embedder()->web_contents()->GetRenderViewHost()); + { + const string16 expected_title = ASCIIToUTF16("main guest"); + content::TitleWatcher title_watcher(test_embedder()->web_contents(), + expected_title); + + // By the time we get here 'contentWindow' should be ready because the + // guest has completed loading. + ExecuteSyncJSFunction( + rvh, base::StringPrintf("PostMessage('%s, false');", kTesting)); + + // The title will be updated to "main guest" at the last stage of the + // process described above. + string16 actual_title = title_watcher.WaitAndGetTitle(); + EXPECT_EQ(expected_title, actual_title); + } +} + +// This is the same as BrowserPluginHostTest.PostMessage but also +// posts a message to an iframe. +// TODO(fsamuel): This test should replace the previous test once postMessage +// iframe targeting is fixed (see http://crbug.com/153701). +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, DISABLED_PostMessageToIFrame) { + const char* kTesting = "testing123"; + const char* kEmbedderURL = "/browser_plugin_embedder.html"; + const char* kGuestURL = "/browser_plugin_post_message_guest.html"; + StartBrowserPluginTest(kEmbedderURL, kGuestURL, false, std::string()); + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + test_embedder()->web_contents()->GetRenderViewHost()); + { + const string16 expected_title = ASCIIToUTF16("main guest"); + content::TitleWatcher title_watcher(test_embedder()->web_contents(), + expected_title); + + ExecuteSyncJSFunction( + rvh, base::StringPrintf("PostMessage('%s, false');", kTesting)); + + // The title will be updated to "main guest" at the last stage of the + // process described above. + string16 actual_title = title_watcher.WaitAndGetTitle(); + EXPECT_EQ(expected_title, actual_title); + } + { + content::TitleWatcher ready_watcher(test_embedder()->web_contents(), + ASCIIToUTF16("ready")); + + RenderViewHostImpl* guest_rvh = static_cast<RenderViewHostImpl*>( + test_guest()->web_contents()->GetRenderViewHost()); + GURL test_url = embedded_test_server()->GetURL( + "/browser_plugin_post_message_guest.html"); + ExecuteSyncJSFunction( + guest_rvh, + base::StringPrintf( + "CreateChildFrame('%s');", test_url.spec().c_str())); + + string16 actual_title = ready_watcher.WaitAndGetTitle(); + EXPECT_EQ(ASCIIToUTF16("ready"), actual_title); + + content::TitleWatcher iframe_watcher(test_embedder()->web_contents(), + ASCIIToUTF16("iframe")); + ExecuteSyncJSFunction( + rvh, base::StringPrintf("PostMessage('%s', true);", kTesting)); + + // The title will be updated to "iframe" at the last stage of the + // process described above. + actual_title = iframe_watcher.WaitAndGetTitle(); + EXPECT_EQ(ASCIIToUTF16("iframe"), actual_title); + } +} + +// This test verifies that if a browser plugin is hidden before navigation, +// the guest starts off hidden. +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, HiddenBeforeNavigation) { + const char* kEmbedderURL = "/browser_plugin_embedder.html"; + const std::string embedder_code = + "document.getElementById('plugin').style.visibility = 'hidden'"; + StartBrowserPluginTest( + kEmbedderURL, kHTMLForGuest, true, embedder_code); + EXPECT_FALSE(test_guest()->visible()); +} + +// This test verifies that if a browser plugin is focused before navigation then +// the guest starts off focused. +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, FocusBeforeNavigation) { + const char* kEmbedderURL = "/browser_plugin_embedder.html"; + const std::string embedder_code = + "document.getElementById('plugin').focus();"; + StartBrowserPluginTest( + kEmbedderURL, kHTMLForGuest, true, embedder_code); + RenderViewHostImpl* guest_rvh = static_cast<RenderViewHostImpl*>( + test_guest()->web_contents()->GetRenderViewHost()); + // Verify that the guest is focused. + scoped_ptr<base::Value> value = + content::ExecuteScriptAndGetValue(guest_rvh, "document.hasFocus()"); + bool result = false; + ASSERT_TRUE(value->GetAsBoolean(&result)); + EXPECT_TRUE(result); +} + +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, FocusTracksEmbedder) { + const char* kEmbedderURL = "/browser_plugin_embedder.html"; + StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, std::string()); + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + test_embedder()->web_contents()->GetRenderViewHost()); + RenderViewHostImpl* guest_rvh = static_cast<RenderViewHostImpl*>( + test_guest()->web_contents()->GetRenderViewHost()); + { + // Focus the BrowserPlugin. This will have the effect of also focusing the + // current guest. + ExecuteSyncJSFunction(rvh, "document.getElementById('plugin').focus();"); + // Verify that key presses go to the guest. + SimulateSpaceKeyPress(test_embedder()->web_contents()); + test_guest()->WaitForInput(); + // Verify that the guest is focused. + scoped_ptr<base::Value> value = + content::ExecuteScriptAndGetValue(guest_rvh, "document.hasFocus()"); + bool result = false; + ASSERT_TRUE(value->GetAsBoolean(&result)); + EXPECT_TRUE(result); + } + // Blur the embedder. + test_embedder()->web_contents()->GetRenderViewHost()->Blur(); + test_guest()->WaitForBlur(); +} + +// Test for regression http://crbug.com/162961. +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, GetRenderViewHostAtPositionTest) { + const char kEmbedderURL[] = "/browser_plugin_embedder.html"; + const std::string embedder_code = + base::StringPrintf("SetSize(%d, %d);", 100, 100); + StartBrowserPluginTest(kEmbedderURL, kHTMLForGuestWithSize, true, + embedder_code); + // Check for render view host at position (150, 150) that is outside the + // bounds of our guest, so this would respond with the render view host of the + // embedder. + test_embedder()->WaitForRenderViewHostAtPosition(150, 150); + ASSERT_EQ(test_embedder()->web_contents()->GetRenderViewHost(), + test_embedder()->last_rvh_at_position_response()); +} + +// This test verifies that if IME is enabled in the embedder, it is also enabled +// in the guest. +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, VerifyInputMethodActive) { + const char* kEmbedderURL = "/browser_plugin_embedder.html"; + StartBrowserPluginTest(kEmbedderURL, kHTMLForGuest, true, std::string()); + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + test_guest()->web_contents()->GetRenderViewHost()); + EXPECT_TRUE(rvh->input_method_active()); +} + +} // namespace content diff --git a/chromium/content/browser/browser_plugin/browser_plugin_host_factory.h b/chromium/content/browser/browser_plugin/browser_plugin_host_factory.h new file mode 100644 index 00000000000..9bfc1e2d740 --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_host_factory.h @@ -0,0 +1,41 @@ +// Copyright (c) 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. + +#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_HOST_FACTORY_H_ +#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_HOST_FACTORY_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/strings/string16.h" +#include "content/common/content_export.h" + +struct BrowserPluginHostMsg_CreateGuest_Params; + +namespace content { + +class BrowserPluginEmbedder; +class BrowserPluginGuest; +class RenderViewHost; +class WebContentsImpl; + +// Factory to create BrowserPlugin embedder and guest. +class CONTENT_EXPORT BrowserPluginHostFactory { + public: + virtual BrowserPluginGuestManager* CreateBrowserPluginGuestManager() = 0; + + virtual BrowserPluginGuest* CreateBrowserPluginGuest( + int instance_id, + WebContentsImpl* web_contents) = 0; + + virtual BrowserPluginEmbedder* CreateBrowserPluginEmbedder( + WebContentsImpl* web_contents) = 0; + + protected: + virtual ~BrowserPluginHostFactory() {} +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_HOST_FACTORY_H_ diff --git a/chromium/content/browser/browser_plugin/browser_plugin_message_filter.cc b/chromium/content/browser/browser_plugin/browser_plugin_message_filter.cc new file mode 100644 index 00000000000..031797a46c4 --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_message_filter.cc @@ -0,0 +1,72 @@ +// Copyright (c) 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/browser/browser_plugin/browser_plugin_message_filter.h" + +#include "base/supports_user_data.h" +#include "content/browser/browser_plugin/browser_plugin_guest.h" +#include "content/browser/browser_plugin/browser_plugin_guest_manager.h" +#include "content/browser/web_contents/web_contents_impl.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/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_view_host.h" + +namespace content { + +BrowserPluginMessageFilter::BrowserPluginMessageFilter(int render_process_id, + bool is_guest) + : render_process_id_(render_process_id), + is_guest_(is_guest) { +} + +BrowserPluginMessageFilter::~BrowserPluginMessageFilter() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); +} + +bool BrowserPluginMessageFilter::OnMessageReceived( + const IPC::Message& message, + bool* message_was_ok) { + // Any message requested by a BrowserPluginGuest should be routed through + // a BrowserPluginGuestManager. + if (BrowserPluginGuest::ShouldForwardToBrowserPluginGuest(message)) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + BrowserPluginGuestManager* guest_manager = GetBrowserPluginGuestManager(); + if (guest_manager) + guest_manager->OnMessageReceived(message, render_process_id_); + // We always swallow messages destined for BrowserPluginGuestManager because + // we're on the UI thread and fallback code is expected to be run on the IO + // thread. + return true; + } + return false; +} + +void BrowserPluginMessageFilter::OnDestruct() const { + BrowserThread::DeleteOnIOThread::Destruct(this); +} + +void BrowserPluginMessageFilter::OverrideThreadForMessage( + const IPC::Message& message, BrowserThread::ID* thread) { + if (BrowserPluginGuest::ShouldForwardToBrowserPluginGuest(message)) + *thread = BrowserThread::UI; +} + +BrowserPluginGuestManager* + BrowserPluginMessageFilter::GetBrowserPluginGuestManager() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + RenderProcessHostImpl* host = static_cast<RenderProcessHostImpl*>( + RenderProcessHost::FromID(render_process_id_)); + if (!host) + return NULL; + + BrowserContext* browser_context = host->GetBrowserContext(); + return static_cast<BrowserPluginGuestManager*>( + browser_context->GetUserData( + browser_plugin::kBrowserPluginGuestManagerKeyName)); +} + +} // namespace content diff --git a/chromium/content/browser/browser_plugin/browser_plugin_message_filter.h b/chromium/content/browser/browser_plugin/browser_plugin_message_filter.h new file mode 100644 index 00000000000..829c1badcbf --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_message_filter.h @@ -0,0 +1,45 @@ +// Copyright (c) 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. + +#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_MESSAGE_FILTER_H_ +#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_MESSAGE_FILTER_H_ + +#include "content/public/browser/browser_message_filter.h" + +namespace content { + +class BrowserContext; +class BrowserPluginGuestManager; + +// This class filters out incoming IPC messages for the guest renderer process +// on the IPC thread before other message filters handle them. +class BrowserPluginMessageFilter : public BrowserMessageFilter { + public: + BrowserPluginMessageFilter(int render_process_id, bool is_guest); + + // BrowserMessageFilter implementation. + virtual void OverrideThreadForMessage( + const IPC::Message& message, + BrowserThread::ID* thread) OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) OVERRIDE; + virtual void OnDestruct() const OVERRIDE; + + private: + friend class BrowserThread; + friend class base::DeleteHelper<BrowserPluginMessageFilter>; + + virtual ~BrowserPluginMessageFilter(); + + BrowserPluginGuestManager* GetBrowserPluginGuestManager(); + + int render_process_id_; + int is_guest_; + + DISALLOW_COPY_AND_ASSIGN(BrowserPluginMessageFilter); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_MESSAGE_FILTER_H_ diff --git a/chromium/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h b/chromium/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h new file mode 100644 index 00000000000..2b462371001 --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h @@ -0,0 +1,34 @@ +// Copyright (c) 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. + +#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_POPUP_MENU_HELPER_MAC_H_ +#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_POPUP_MENU_HELPER_MAC_H_ + +#include "content/browser/renderer_host/popup_menu_helper_mac.h" + +namespace content { +class RenderViewHost; +class RenderViewHostImpl; + +// This class is similiar to PopupMenuHelperMac but positions the popup relative +// to the embedder, and issues a reply to the guest. +class BrowserPluginPopupMenuHelper : public PopupMenuHelper { + public: + // Creates a BrowserPluginPopupMenuHelper that positions popups relative to + // |embedder_rvh| and will notify |guest_rvh| when a user selects or cancels + // the popup. + BrowserPluginPopupMenuHelper(RenderViewHost* embedder_rvh, + RenderViewHost* guest_rvh); + + private: + virtual RenderWidgetHostViewMac* GetRenderWidgetHostView() const OVERRIDE; + + RenderViewHostImpl* embedder_rvh_; + + DISALLOW_COPY_AND_ASSIGN(BrowserPluginPopupMenuHelper); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_POPUP_MENU_HELPER_MAC_H_ diff --git a/chromium/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.mm b/chromium/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.mm new file mode 100644 index 00000000000..e2b5da55c00 --- /dev/null +++ b/chromium/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.mm @@ -0,0 +1,23 @@ +// Copyright (c) 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/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h" + +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/browser/renderer_host/render_widget_host_view_mac.h" + +namespace content { + +BrowserPluginPopupMenuHelper::BrowserPluginPopupMenuHelper( + RenderViewHost* embedder_rvh, RenderViewHost* guest_rvh) + : PopupMenuHelper(guest_rvh), + embedder_rvh_(static_cast<RenderViewHostImpl*>(embedder_rvh)) { +} + +RenderWidgetHostViewMac* + BrowserPluginPopupMenuHelper::GetRenderWidgetHostView() const { + return static_cast<RenderWidgetHostViewMac*>(embedder_rvh_->GetView()); +} + +} // namespace content diff --git a/chromium/content/browser/browser_plugin/test_browser_plugin_embedder.cc b/chromium/content/browser/browser_plugin/test_browser_plugin_embedder.cc new file mode 100644 index 00000000000..a513d0fd4be --- /dev/null +++ b/chromium/content/browser/browser_plugin/test_browser_plugin_embedder.cc @@ -0,0 +1,40 @@ +// Copyright (c) 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/browser/browser_plugin/test_browser_plugin_embedder.h" + +#include "content/browser/browser_plugin/browser_plugin_embedder.h" +#include "content/browser/web_contents/web_contents_impl.h" + +namespace content { + +TestBrowserPluginEmbedder::TestBrowserPluginEmbedder( + WebContentsImpl* web_contents) + : BrowserPluginEmbedder(web_contents), + last_rvh_at_position_response_(NULL) { +} + +TestBrowserPluginEmbedder::~TestBrowserPluginEmbedder() { +} + +void TestBrowserPluginEmbedder::GetRenderViewHostCallback( + RenderViewHost* rvh, int x, int y) { + last_rvh_at_position_response_ = rvh; + if (message_loop_runner_.get()) + message_loop_runner_->Quit(); +} + +void TestBrowserPluginEmbedder::WaitForRenderViewHostAtPosition(int x, int y) { + GetRenderViewHostAtPosition(x, y, + base::Bind(&TestBrowserPluginEmbedder::GetRenderViewHostCallback, + base::Unretained(this))); + message_loop_runner_ = new MessageLoopRunner(); + message_loop_runner_->Run(); +} + +WebContentsImpl* TestBrowserPluginEmbedder::web_contents() const { + return static_cast<WebContentsImpl*>(BrowserPluginEmbedder::web_contents()); +} + +} // namespace content diff --git a/chromium/content/browser/browser_plugin/test_browser_plugin_embedder.h b/chromium/content/browser/browser_plugin/test_browser_plugin_embedder.h new file mode 100644 index 00000000000..4673f926305 --- /dev/null +++ b/chromium/content/browser/browser_plugin/test_browser_plugin_embedder.h @@ -0,0 +1,47 @@ +// Copyright (c) 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. + +#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_EMBEDDER_H_ +#define CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_EMBEDDER_H_ + +#include "base/compiler_specific.h" +#include "content/browser/browser_plugin/browser_plugin_embedder.h" +#include "content/public/test/test_utils.h" + +namespace content { + +class BrowserPluginGuest; +class RenderViewHost; +class WebContentsImpl; + +// Test class for BrowserPluginEmbedder. +// +// Provides utilities to wait for certain state/messages in +// BrowserPluginEmbedder to be used in tests. +class TestBrowserPluginEmbedder : public BrowserPluginEmbedder { + public: + TestBrowserPluginEmbedder(WebContentsImpl* web_contents); + virtual ~TestBrowserPluginEmbedder(); + + // Asks the renderer process for RenderViewHost at (|x|, |y|) and waits until + // the response arrives. + void WaitForRenderViewHostAtPosition(int x, int y); + RenderViewHost* last_rvh_at_position_response() { + return last_rvh_at_position_response_; + } + + WebContentsImpl* web_contents() const; + + private: + void GetRenderViewHostCallback(RenderViewHost* rvh, int x, int y); + + scoped_refptr<MessageLoopRunner> message_loop_runner_; + RenderViewHost* last_rvh_at_position_response_; + + DISALLOW_COPY_AND_ASSIGN(TestBrowserPluginEmbedder); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_EMBEDDER_H_ diff --git a/chromium/content/browser/browser_plugin/test_browser_plugin_guest.cc b/chromium/content/browser/browser_plugin/test_browser_plugin_guest.cc new file mode 100644 index 00000000000..a6e62a3ba55 --- /dev/null +++ b/chromium/content/browser/browser_plugin/test_browser_plugin_guest.cc @@ -0,0 +1,250 @@ +// Copyright (c) 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/browser/browser_plugin/test_browser_plugin_guest.h" + +#include "base/test/test_timeouts.h" +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/common/browser_plugin/browser_plugin_messages.h" +#include "content/public/browser/notification_types.h" + +namespace content { + +class BrowserPluginGuest; + +TestBrowserPluginGuest::TestBrowserPluginGuest( + int instance_id, + WebContentsImpl* web_contents) + : BrowserPluginGuest(instance_id, web_contents, NULL, false), + update_rect_count_(0), + damage_buffer_call_count_(0), + exit_observed_(false), + focus_observed_(false), + blur_observed_(false), + advance_focus_observed_(false), + was_hidden_observed_(false), + set_damage_buffer_observed_(false), + input_observed_(false), + load_stop_observed_(false), + waiting_for_damage_buffer_with_size_(false), + last_damage_buffer_size_(gfx::Size()) { + // Listen to visibility changes so that a test can wait for these changes. + notification_registrar_.Add(this, + NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED, + Source<WebContents>(web_contents)); +} + +TestBrowserPluginGuest::~TestBrowserPluginGuest() { +} + +WebContentsImpl* TestBrowserPluginGuest::web_contents() const { + return static_cast<WebContentsImpl*>(BrowserPluginGuest::web_contents()); +} + +void TestBrowserPluginGuest::Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type) { + case NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED: { + bool visible = *Details<bool>(details).ptr(); + if (!visible) { + was_hidden_observed_ = true; + if (was_hidden_message_loop_runner_.get()) + was_hidden_message_loop_runner_->Quit(); + } + return; + } + } + + BrowserPluginGuest::Observe(type, source, details); +} + +void TestBrowserPluginGuest::SendMessageToEmbedder(IPC::Message* msg) { + if (msg->type() == BrowserPluginMsg_UpdateRect::ID) { + update_rect_count_++; + int instance_id = 0; + BrowserPluginMsg_UpdateRect_Params params; + BrowserPluginMsg_UpdateRect::Read(msg, &instance_id, ¶ms); + last_view_size_observed_ = params.view_size; + if (!expected_auto_view_size_.IsEmpty() && + expected_auto_view_size_ == params.view_size) { + if (auto_view_size_message_loop_runner_.get()) + auto_view_size_message_loop_runner_->Quit(); + } + if (send_message_loop_runner_.get()) + send_message_loop_runner_->Quit(); + } + BrowserPluginGuest::SendMessageToEmbedder(msg); +} + +void TestBrowserPluginGuest::WaitForUpdateRectMsg() { + // Check if we already got any UpdateRect message. + if (update_rect_count_ > 0) + return; + send_message_loop_runner_ = new MessageLoopRunner(); + send_message_loop_runner_->Run(); +} + +void TestBrowserPluginGuest::ResetUpdateRectCount() { + update_rect_count_ = 0; +} + +void TestBrowserPluginGuest::WaitForDamageBufferWithSize( + const gfx::Size& size) { + if (damage_buffer_call_count_ > 0 && last_damage_buffer_size_ == size) + return; + + expected_damage_buffer_size_ = size; + waiting_for_damage_buffer_with_size_ = true; + damage_buffer_message_loop_runner_ = new MessageLoopRunner(); + damage_buffer_message_loop_runner_->Run(); +} + +void TestBrowserPluginGuest::RenderProcessGone(base::TerminationStatus status) { + exit_observed_ = true; + if (status != base::TERMINATION_STATUS_NORMAL_TERMINATION && + status != base::TERMINATION_STATUS_STILL_RUNNING) + LOG(INFO) << "Guest crashed status: " << status; + if (crash_message_loop_runner_.get()) + crash_message_loop_runner_->Quit(); + BrowserPluginGuest::RenderProcessGone(status); +} + +void TestBrowserPluginGuest::OnHandleInputEvent( + int instance_id, + const gfx::Rect& guest_window_rect, + const WebKit::WebInputEvent* event) { + BrowserPluginGuest::OnHandleInputEvent(instance_id, + guest_window_rect, + event); + input_observed_ = true; + if (input_message_loop_runner_.get()) + input_message_loop_runner_->Quit(); +} + +void TestBrowserPluginGuest::WaitForExit() { + // Check if we already observed a guest crash, return immediately if so. + if (exit_observed_) + return; + + crash_message_loop_runner_ = new MessageLoopRunner(); + crash_message_loop_runner_->Run(); +} + +void TestBrowserPluginGuest::WaitForFocus() { + if (focus_observed_) { + focus_observed_ = false; + return; + } + focus_message_loop_runner_ = new MessageLoopRunner(); + focus_message_loop_runner_->Run(); + focus_observed_ = false; +} + +void TestBrowserPluginGuest::WaitForBlur() { + if (blur_observed_) { + blur_observed_ = false; + return; + } + blur_message_loop_runner_ = new MessageLoopRunner(); + blur_message_loop_runner_->Run(); + blur_observed_ = false; +} + +void TestBrowserPluginGuest::WaitForAdvanceFocus() { + if (advance_focus_observed_) + return; + advance_focus_message_loop_runner_ = new MessageLoopRunner(); + advance_focus_message_loop_runner_->Run(); +} + +void TestBrowserPluginGuest::WaitUntilHidden() { + if (was_hidden_observed_) { + was_hidden_observed_ = false; + return; + } + was_hidden_message_loop_runner_ = new MessageLoopRunner(); + was_hidden_message_loop_runner_->Run(); + was_hidden_observed_ = false; +} + +void TestBrowserPluginGuest::WaitForInput() { + if (input_observed_) { + input_observed_ = false; + return; + } + + input_message_loop_runner_ = new MessageLoopRunner(); + input_message_loop_runner_->Run(); + input_observed_ = false; +} + +void TestBrowserPluginGuest::WaitForLoadStop() { + if (load_stop_observed_) { + load_stop_observed_ = false; + return; + } + + load_stop_message_loop_runner_ = new MessageLoopRunner(); + load_stop_message_loop_runner_->Run(); + load_stop_observed_ = false; +} + +void TestBrowserPluginGuest::WaitForViewSize(const gfx::Size& view_size) { + if (last_view_size_observed_ == view_size) { + last_view_size_observed_ = gfx::Size(); + return; + } + + expected_auto_view_size_ = view_size; + auto_view_size_message_loop_runner_ = new MessageLoopRunner(); + auto_view_size_message_loop_runner_->Run(); + last_view_size_observed_ = gfx::Size(); +} + +void TestBrowserPluginGuest::OnSetFocus(int instance_id, bool focused) { + if (focused) { + focus_observed_ = true; + if (focus_message_loop_runner_.get()) + focus_message_loop_runner_->Quit(); + } else { + blur_observed_ = true; + if (blur_message_loop_runner_.get()) + blur_message_loop_runner_->Quit(); + } + BrowserPluginGuest::OnSetFocus(instance_id, focused); +} + +void TestBrowserPluginGuest::OnTakeFocus(bool reverse) { + advance_focus_observed_ = true; + if (advance_focus_message_loop_runner_.get()) + advance_focus_message_loop_runner_->Quit(); + BrowserPluginGuest::OnTakeFocus(reverse); +} + +void TestBrowserPluginGuest::SetDamageBuffer( + const BrowserPluginHostMsg_ResizeGuest_Params& params) { + ++damage_buffer_call_count_; + last_damage_buffer_size_ = params.view_rect.size(); + + if (waiting_for_damage_buffer_with_size_ && + expected_damage_buffer_size_ == params.view_rect.size() && + damage_buffer_message_loop_runner_.get()) { + damage_buffer_message_loop_runner_->Quit(); + waiting_for_damage_buffer_with_size_ = false; + } + + BrowserPluginGuest::SetDamageBuffer(params); +} + +void TestBrowserPluginGuest::DidStopLoading( + RenderViewHost* render_view_host) { + BrowserPluginGuest::DidStopLoading(render_view_host); + load_stop_observed_ = true; + if (load_stop_message_loop_runner_.get()) + load_stop_message_loop_runner_->Quit(); +} + +} // namespace content diff --git a/chromium/content/browser/browser_plugin/test_browser_plugin_guest.h b/chromium/content/browser/browser_plugin/test_browser_plugin_guest.h new file mode 100644 index 00000000000..7fccb002388 --- /dev/null +++ b/chromium/content/browser/browser_plugin/test_browser_plugin_guest.h @@ -0,0 +1,108 @@ +// Copyright (c) 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. + +#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_GUEST_H_ +#define CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_GUEST_H_ + +#include "base/compiler_specific.h" +#include "content/browser/browser_plugin/browser_plugin_guest.h" +#include "content/public/test/test_utils.h" +#include "ui/gfx/size.h" + +namespace content { + +class RenderProcessHost; +class RenderViewHost; +class WebContentsImpl; + +// Test class for BrowserPluginGuest. +// +// Provides utilities to wait for certain state/messages in BrowserPluginGuest +// to be used in tests. +class TestBrowserPluginGuest : public BrowserPluginGuest { + public: + TestBrowserPluginGuest(int instance_id, WebContentsImpl* web_contents); + virtual ~TestBrowserPluginGuest(); + + WebContentsImpl* web_contents() const; + + // NotificationObserver method override. + virtual void Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) OVERRIDE; + + // Overridden methods from BrowserPluginGuest to intercept in test objects. + virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE; + virtual void OnHandleInputEvent(int instance_id, + const gfx::Rect& guest_window_rect, + const WebKit::WebInputEvent* event) OVERRIDE; + virtual void OnSetFocus(int instance_id, bool focused) OVERRIDE; + virtual void OnTakeFocus(bool reverse) OVERRIDE; + virtual void SetDamageBuffer( + const BrowserPluginHostMsg_ResizeGuest_Params& params) OVERRIDE; + virtual void DidStopLoading(RenderViewHost* render_view_host) OVERRIDE; + + // Test utilities to wait for a event we are interested in. + // Waits until UpdateRect message is sent from the guest, meaning it is + // ready/rendered. + void WaitForUpdateRectMsg(); + void ResetUpdateRectCount(); + // Waits until a guest receives a damage buffer of the specified |size|. + void WaitForDamageBufferWithSize(const gfx::Size& size); + // Waits for focus to reach this guest. + void WaitForFocus(); + // Waits for blur to reach this guest. + void WaitForBlur(); + // Waits for focus to move out of this guest. + void WaitForAdvanceFocus(); + // Waits until the guest is hidden. + void WaitUntilHidden(); + // Waits until guest exits. + void WaitForExit(); + // Waits until input is observed. + void WaitForInput(); + // Waits until 'loadstop' is observed. + void WaitForLoadStop(); + // Waits until UpdateRect with a particular |view_size| is observed. + void WaitForViewSize(const gfx::Size& view_size); + + private: + // Overridden methods from BrowserPluginGuest to intercept in test objects. + virtual void SendMessageToEmbedder(IPC::Message* msg) OVERRIDE; + + int update_rect_count_; + int damage_buffer_call_count_; + bool exit_observed_; + bool focus_observed_; + bool blur_observed_; + bool advance_focus_observed_; + bool was_hidden_observed_; + bool set_damage_buffer_observed_; + bool input_observed_; + bool load_stop_observed_; + gfx::Size last_view_size_observed_; + gfx::Size expected_auto_view_size_; + + // For WaitForDamageBufferWithSize(). + bool waiting_for_damage_buffer_with_size_; + gfx::Size expected_damage_buffer_size_; + gfx::Size last_damage_buffer_size_; + + scoped_refptr<MessageLoopRunner> send_message_loop_runner_; + scoped_refptr<MessageLoopRunner> crash_message_loop_runner_; + scoped_refptr<MessageLoopRunner> focus_message_loop_runner_; + scoped_refptr<MessageLoopRunner> blur_message_loop_runner_; + scoped_refptr<MessageLoopRunner> advance_focus_message_loop_runner_; + scoped_refptr<MessageLoopRunner> was_hidden_message_loop_runner_; + scoped_refptr<MessageLoopRunner> damage_buffer_message_loop_runner_; + scoped_refptr<MessageLoopRunner> input_message_loop_runner_; + scoped_refptr<MessageLoopRunner> load_stop_message_loop_runner_; + scoped_refptr<MessageLoopRunner> auto_view_size_message_loop_runner_; + + DISALLOW_COPY_AND_ASSIGN(TestBrowserPluginGuest); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_GUEST_H_ diff --git a/chromium/content/browser/browser_plugin/test_browser_plugin_guest_manager.cc b/chromium/content/browser/browser_plugin/test_browser_plugin_guest_manager.cc new file mode 100644 index 00000000000..7d0b5ac23dd --- /dev/null +++ b/chromium/content/browser/browser_plugin/test_browser_plugin_guest_manager.cc @@ -0,0 +1,35 @@ +// Copyright (c) 2013 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/browser/browser_plugin/test_browser_plugin_guest_manager.h" + +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/public/test/test_utils.h" + +namespace content { + +TestBrowserPluginGuestManager::TestBrowserPluginGuestManager() { +} + +TestBrowserPluginGuestManager::~TestBrowserPluginGuestManager() { +} + +void TestBrowserPluginGuestManager::AddGuest( + int instance_id, + WebContentsImpl* guest_web_contents) { + BrowserPluginGuestManager::AddGuest(instance_id, guest_web_contents); + if (message_loop_runner_.get()) + message_loop_runner_->Quit(); +} + +void TestBrowserPluginGuestManager::WaitForGuestAdded() { + // Check if guests were already created. + if (guest_web_contents_by_instance_id_.size() > 0) + return; + // Wait otherwise. + message_loop_runner_ = new MessageLoopRunner(); + message_loop_runner_->Run(); +} + +} // namespace content diff --git a/chromium/content/browser/browser_plugin/test_browser_plugin_guest_manager.h b/chromium/content/browser/browser_plugin/test_browser_plugin_guest_manager.h new file mode 100644 index 00000000000..d7683cf7ae9 --- /dev/null +++ b/chromium/content/browser/browser_plugin/test_browser_plugin_guest_manager.h @@ -0,0 +1,51 @@ +// Copyright (c) 2013 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. + +#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_GUEST_MANAGER_H_ +#define CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_GUEST_MANAGER_H_ + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "content/browser/browser_plugin/browser_plugin_guest_manager.h" +#include "content/public/test/test_utils.h" + +FORWARD_DECLARE_TEST(BrowserPluginHostTest, ReloadEmbedder); + +namespace content { + +class WebContentsImpl; + +// Test class for BrowserPluginGuestManager. +// +// Provides utilities to wait for certain state/messages in +// BrowserPluginGuestManager to be used in tests. +class TestBrowserPluginGuestManager : public BrowserPluginGuestManager { + public: + typedef BrowserPluginGuestManager::GuestInstanceMap GuestInstanceMap; + + TestBrowserPluginGuestManager(); + virtual ~TestBrowserPluginGuestManager(); + + const GuestInstanceMap& guest_web_contents_for_testing() const { + return guest_web_contents_by_instance_id_; + } + + // Waits until at least one guest is added to the guest manager. + void WaitForGuestAdded(); + + private: + // BrowserPluginHostTest.ReloadEmbedder needs access to the GuestInstanceMap. + FRIEND_TEST_ALL_PREFIXES(BrowserPluginHostTest, ReloadEmbedder); + + // Overriden to intercept in test. + virtual void AddGuest(int instance_id, + WebContentsImpl* guest_web_contents) OVERRIDE; + + scoped_refptr<MessageLoopRunner> message_loop_runner_; + DISALLOW_COPY_AND_ASSIGN(TestBrowserPluginGuestManager); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_GUEST_MANAGER_H_ |