diff options
Diffstat (limited to 'content/browser/browser_plugin')
14 files changed, 2057 insertions, 0 deletions
diff --git a/content/browser/browser_plugin/browser_plugin_embedder.cc b/content/browser/browser_plugin/browser_plugin_embedder.cc new file mode 100644 index 0000000..fef57e6 --- /dev/null +++ b/content/browser/browser_plugin/browser_plugin_embedder.cc @@ -0,0 +1,231 @@ +// 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 <set> + +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/time.h" +#include "content/browser/browser_plugin/browser_plugin_embedder_helper.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/public/browser/notification_details.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/web_contents_view.h" +#include "content/public/common/url_constants.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" +#include "ui/gfx/size.h" +#include "ui/surface/transport_dib.h" + +namespace content { + +// static +BrowserPluginHostFactory* BrowserPluginEmbedder::factory_ = NULL; + +BrowserPluginEmbedder::BrowserPluginEmbedder( + WebContentsImpl* web_contents, + RenderViewHost* render_view_host) + : WebContentsObserver(web_contents), + render_view_host_(render_view_host) { + // Listen to visibility changes so that an embedder hides its guests + // as well. + registrar_.Add(this, + NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED, + Source<WebContents>(web_contents)); + + // |render_view_host| manages the ownership of this BrowserPluginGuestHelper. + new BrowserPluginEmbedderHelper(this, render_view_host); +} + +BrowserPluginEmbedder::~BrowserPluginEmbedder() { + // Destroy guests that are managed by the current embedder. + DestroyGuests(); +} + +// static +BrowserPluginEmbedder* BrowserPluginEmbedder::Create( + WebContentsImpl* web_contents, + content::RenderViewHost* render_view_host) { + if (factory_) { + return factory_->CreateBrowserPluginEmbedder(web_contents, + render_view_host); + } + return new BrowserPluginEmbedder(web_contents, render_view_host); +} + +BrowserPluginGuest* BrowserPluginEmbedder::GetGuestByInstanceID( + int instance_id) const { + ContainerInstanceMap::const_iterator it = + guest_web_contents_by_instance_id_.find(instance_id); + if (it != guest_web_contents_by_instance_id_.end()) + return static_cast<WebContentsImpl*>(it->second)->GetBrowserPluginGuest(); + return NULL; +} + +void BrowserPluginEmbedder::AddGuest(int instance_id, + WebContents* guest_web_contents, + int64 frame_id) { + 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 BrowserPluginEmbedder::NavigateGuest(RenderViewHost* render_view_host, + int instance_id, + int64 frame_id, + const std::string& src, + const gfx::Size& size) { + BrowserPluginGuest* guest = GetGuestByInstanceID(instance_id); + WebContentsImpl* guest_web_contents = NULL; + GURL url(src); + if (!guest) { + const std::string& host = + render_view_host->GetSiteInstance()->GetSite().host(); + guest_web_contents = WebContentsImpl::CreateGuest( + web_contents()->GetBrowserContext(), + host, + instance_id); + + guest = guest_web_contents->GetBrowserPluginGuest(); + guest->set_embedder_render_process_host( + render_view_host->GetProcess()); + + guest_web_contents->GetMutableRendererPrefs()-> + throttle_input_events = false; + AddGuest(instance_id, guest_web_contents, frame_id); + guest_web_contents->SetDelegate(guest); + } else { + guest_web_contents = static_cast<WebContentsImpl*>(guest->web_contents()); + } + + // We ignore loading 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. + // TODO(lazyboy): The js shim for browser-plugin might need to reflect empty + // src ignoring in the shadow DOM element: http://crbug.com/149001. + if (!src.empty()) { + guest_web_contents->GetController().LoadURL(url, + Referrer(), + PAGE_TRANSITION_AUTO_SUBFRAME, + std::string()); + } + + if (!size.IsEmpty()) + guest_web_contents->GetView()->SizeContents(size); +} + +void BrowserPluginEmbedder::UpdateRectACK(int instance_id, + int message_id, + const gfx::Size& size) { + BrowserPluginGuest* guest = GetGuestByInstanceID(instance_id); + if (guest) + guest->UpdateRectACK(message_id, size); +} + +void BrowserPluginEmbedder::ResizeGuest(int instance_id, + TransportDIB* damage_buffer, +#if defined(OS_WIN) + int damage_buffer_size, +#endif + int width, + int height, + bool resize_pending, + float scale_factor) { + BrowserPluginGuest* guest = GetGuestByInstanceID(instance_id); + if (!guest) + return; + WebContentsImpl* guest_web_contents = + static_cast<WebContentsImpl*>(guest->web_contents()); + guest->SetDamageBuffer(damage_buffer, +#if defined(OS_WIN) + damage_buffer_size, +#endif + gfx::Size(width, height), + scale_factor); + if (!resize_pending) + guest_web_contents->GetView()->SizeContents(gfx::Size(width, height)); +} + +void BrowserPluginEmbedder::SetFocus(int instance_id, + bool focused) { + BrowserPluginGuest* guest = GetGuestByInstanceID(instance_id); + if (guest) + guest->SetFocus(focused); +} + +void BrowserPluginEmbedder::DestroyGuests() { + STLDeleteContainerPairSecondPointers( + guest_web_contents_by_instance_id_.begin(), + guest_web_contents_by_instance_id_.end()); + guest_web_contents_by_instance_id_.clear(); +} + +void BrowserPluginEmbedder::HandleInputEvent(int instance_id, + RenderViewHost* render_view_host, + const gfx::Rect& guest_rect, + const WebKit::WebInputEvent& event, + IPC::Message* reply_message) { + BrowserPluginGuest* guest = GetGuestByInstanceID(instance_id); + if (guest) + guest->HandleInputEvent(render_view_host, guest_rect, event, reply_message); +} + +void BrowserPluginEmbedder::DestroyGuestByInstanceID(int instance_id) { + BrowserPluginGuest* guest = GetGuestByInstanceID(instance_id); + if (guest) { + WebContents* guest_web_contents = guest->web_contents(); + + // Destroy the guest's web_contents. + delete guest_web_contents; + guest_web_contents_by_instance_id_.erase(instance_id); + } +} + +void BrowserPluginEmbedder::RenderViewDeleted( + RenderViewHost* render_view_host) { + DestroyGuests(); +} + +void BrowserPluginEmbedder::RenderViewGone(base::TerminationStatus status) { + DestroyGuests(); +} + +void BrowserPluginEmbedder::WebContentsVisibilityChanged(bool visible) { + // If the embedder is hidden we need to hide the guests as well. + for (ContainerInstanceMap::const_iterator it = + guest_web_contents_by_instance_id_.begin(); + it != guest_web_contents_by_instance_id_.end(); ++it) { + WebContents* web_contents = it->second; + if (visible) + web_contents->WasShown(); + else + web_contents->WasHidden(); + } +} + +void BrowserPluginEmbedder::PluginDestroyed(int instance_id) { + DestroyGuestByInstanceID(instance_id); +} + +void BrowserPluginEmbedder::Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type) { + case NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED: { + bool visible = *Details<bool>(details).ptr(); + WebContentsVisibilityChanged(visible); + break; + } + default: + NOTREACHED() << "Unexpected notification type: " << type; + } +} + +} // namespace content diff --git a/content/browser/browser_plugin/browser_plugin_embedder.h b/content/browser/browser_plugin/browser_plugin_embedder.h new file mode 100644 index 0000000..2a92f94 --- /dev/null +++ b/content/browser/browser_plugin/browser_plugin_embedder.h @@ -0,0 +1,144 @@ +// 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 has a list of guests it manages. +// In the beginning when a renderer sees one or more guests (BrowserPlugin +// instance(s)) and there is a request to navigate to them, the WebContents for +// that renderer creates a BrowserPluginEmbedder for itself. The +// BrowserPluginEmbedder, in turn, manages a set of BrowserPluginGuests -- one +// BrowserPluginGuest for each guest in the embedding WebContents. Note that +// each of these BrowserPluginGuest objects has its own WebContents. +// BrowserPluginEmbedder routes any messages directed to a guest from the +// renderer (BrowserPlugin) to the appropriate guest (identified by the guest's +// |instance_id|). +// +// BrowserPluginEmbedder is responsible for cleaning up the guests when the +// embedder frame navigates away to a different page or deletes the guests from +// the existing page. + +#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_EMBEDDER_H_ +#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_EMBEDDER_H_ + +#include <map> + +#include "base/compiler_specific.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/web_contents_observer.h" + +class TransportDIB; +class WebContentsImpl; + +namespace WebKit { +class WebInputEvent; +} + +namespace gfx { +class Rect; +class Size; +} + +namespace content { + +class BrowserPluginGuest; +class BrowserPluginHostFactory; + +// A browser plugin embedder provides functionality for WebContents to operate +// in the 'embedder' role. It manages list of guests inside the embedder. +// +// The embedder's WebContents manages the lifetime of the embedder. They are +// created when a renderer asks WebContents to navigate (for the first time) to +// some guest. It gets destroyed when either the WebContents goes away or there +// is a RenderViewHost swap in WebContents. +class CONTENT_EXPORT BrowserPluginEmbedder : public WebContentsObserver, + public NotificationObserver { + public: + typedef std::map<int, WebContents*> ContainerInstanceMap; + + virtual ~BrowserPluginEmbedder(); + + static BrowserPluginEmbedder* Create(WebContentsImpl* web_contents, + RenderViewHost* render_view_host); + + // Navigates in a guest (new or existing). + void NavigateGuest(RenderViewHost* render_view_host, + int instance_id, + int64 frame_id, + const std::string& src, + const gfx::Size& size); + + // WebContentsObserver implementation. + virtual void RenderViewDeleted(RenderViewHost* render_view_host) OVERRIDE; + virtual void RenderViewGone(base::TerminationStatus status) OVERRIDE; + + // NotificationObserver method override. + virtual void Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) OVERRIDE; + + // Overrides factory for testing. Default (NULL) value indicates regular + // (non-test) environment. + static void set_factory_for_testing(BrowserPluginHostFactory* factory) { + factory_ = factory; + } + + private: + friend class BrowserPluginEmbedderHelper; + friend class TestBrowserPluginEmbedder; + + BrowserPluginEmbedder(WebContentsImpl* web_contents, + RenderViewHost* render_view_host); + + // Returns a guest browser plugin delegate by its container ID specified + // in BrowserPlugin. + BrowserPluginGuest* GetGuestByInstanceID(int instance_id) const; + // Adds a new guest web_contents to the embedder (overridable in test). + virtual void AddGuest(int instance_id, + WebContents* guest_web_contents, + int64 frame_id); + void DestroyGuestByInstanceID(int instance_id); + void DestroyGuests(); + + // Message handlers (direct/indirect via BrowserPluginEmbedderHelper). + // Routes update rect ack message to the appropriate guest. + void UpdateRectACK(int instance_id, int message_id, const gfx::Size& size); + void SetFocus(int instance_id, bool focused); + void ResizeGuest(int instance_id, + TransportDIB* damage_buffer, +#if defined(OS_WIN) + int damage_buffer_size, +#endif + int width, + int height, + bool resize_pending, + float scale_factor); + // Handles input events sent from the BrowserPlugin (embedder's renderer + // process) by passing them to appropriate guest's input handler. + void HandleInputEvent(int instance_id, + RenderViewHost* render_view_host, + const gfx::Rect& guest_rect, + const WebKit::WebInputEvent& event, + IPC::Message* reply_message); + void PluginDestroyed(int instance_id); + + // Called when visiblity of web_contents changes, so the embedder will + // show/hide its guest. + void WebContentsVisibilityChanged(bool visible); + + // Static factory instance (always NULL for non-test). + static BrowserPluginHostFactory* factory_; + + // A scoped container for notification registries. + NotificationRegistrar registrar_; + + // Contains guests' WebContents, mapping from their instance ids. + ContainerInstanceMap guest_web_contents_by_instance_id_; + RenderViewHost* render_view_host_; + + DISALLOW_COPY_AND_ASSIGN(BrowserPluginEmbedder); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_EMBEDDER_H_ diff --git a/content/browser/browser_plugin/browser_plugin_embedder_helper.cc b/content/browser/browser_plugin/browser_plugin_embedder_helper.cc new file mode 100644 index 0000000..2bc9cdf --- /dev/null +++ b/content/browser/browser_plugin/browser_plugin_embedder_helper.cc @@ -0,0 +1,151 @@ +// 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_helper.h" + +#include "content/browser/browser_plugin/browser_plugin_embedder.h" +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/common/browser_plugin_messages.h" +#include "content/common/view_messages.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host_view.h" +#include "ui/gfx/size.h" + +namespace content { + +BrowserPluginEmbedderHelper::BrowserPluginEmbedderHelper( + BrowserPluginEmbedder* embedder, + RenderViewHost* render_view_host) + : RenderViewHostObserver(render_view_host), + embedder_(embedder) { +} + +BrowserPluginEmbedderHelper::~BrowserPluginEmbedderHelper() { +} + +bool BrowserPluginEmbedderHelper::Send(IPC::Message* message) { + return RenderViewHostObserver::Send(message); +} + +bool BrowserPluginEmbedderHelper::OnMessageReceived( + const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(BrowserPluginEmbedderHelper, message) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_NavigateGuest, + OnNavigateGuest); + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_ResizeGuest, OnResizeGuest) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_UpdateRect_ACK, OnUpdateRectACK); + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_SetFocus, OnSetFocus); + IPC_MESSAGE_HANDLER_GENERIC(BrowserPluginHostMsg_HandleInputEvent, + OnHandleInputEvent(*static_cast<const IPC::SyncMessage*>(&message), + &handled)) + IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_PluginDestroyed, + OnPluginDestroyed); + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void BrowserPluginEmbedderHelper::OnResizeGuest( + int instance_id, + const BrowserPluginHostMsg_ResizeGuest_Params& params) { + TransportDIB* damage_buffer = NULL; +#if defined(OS_WIN) + // On Windows we need to duplicate the handle from the remote process. + HANDLE section; + DuplicateHandle(render_view_host()->GetProcess()->GetHandle(), + params.damage_buffer_id.handle, + GetCurrentProcess(), + §ion, + STANDARD_RIGHTS_REQUIRED | FILE_MAP_READ | FILE_MAP_WRITE, + FALSE, 0); + damage_buffer = TransportDIB::Map(section); +#elif defined(OS_MACOSX) + // On OSX, the browser allocates all DIBs and keeps a file descriptor around + // for each. + damage_buffer = render_view_host()->GetProcess()-> + GetTransportDIB(params.damage_buffer_id); +#elif defined(OS_ANDROID) + damage_buffer = TransportDIB::Map(params.damage_buffer_id); +#elif defined(OS_POSIX) + damage_buffer = TransportDIB::Map(params.damage_buffer_id.shmkey); +#endif // defined(OS_POSIX) + DCHECK(damage_buffer); + // TODO(fsamuel): Schedule this later so that we don't stall the embedder for + // too long. + embedder_->ResizeGuest(instance_id, + damage_buffer, +#if defined(OS_WIN) + params.damage_buffer_size, +#endif + params.width, + params.height, + params.resize_pending, + params.scale_factor); +} + +void BrowserPluginEmbedderHelper::OnHandleInputEvent( + const IPC::SyncMessage& message, + bool* handled) { + *handled = true; + PickleIterator iter(message); + + // TODO(fsamuel): This appears to be a monotonically increasing value. + int instance_id = -1; + const char* guest_rect_data = NULL; + int guest_rect_data_length = -1; + const char* input_event_data = NULL; + int input_event_data_length = -1; + if (!iter.SkipBytes(4) || + !message.ReadInt(&iter, &instance_id) || + !message.ReadData(&iter, &guest_rect_data, &guest_rect_data_length) || + !message.ReadData(&iter, &input_event_data, &input_event_data_length)) { + *handled = false; + return; + } + const gfx::Rect* guest_rect = + reinterpret_cast<const gfx::Rect*>(guest_rect_data); + const WebKit::WebInputEvent* input_event = + reinterpret_cast<const WebKit::WebInputEvent*>(input_event_data); + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + render_view_host()); + + // Convert the window coordinates into screen coordinates. + gfx::Rect guest_screen_rect(*guest_rect); + if (rvh->GetView()) + guest_screen_rect.Offset(rvh->GetView()->GetViewBounds().origin()); + + IPC::Message* reply_message = + IPC::SyncMessage::GenerateReply(&message); + embedder_->HandleInputEvent(instance_id, + rvh, + guest_screen_rect, + *input_event, + reply_message); +} + +void BrowserPluginEmbedderHelper::OnNavigateGuest(int instance_id, + int64 frame_id, + const std::string& src, + const gfx::Size& size) { + embedder_->NavigateGuest(render_view_host(), instance_id, frame_id, src, + size); +} + +void BrowserPluginEmbedderHelper::OnUpdateRectACK(int instance_id, + int message_id, + const gfx::Size& size) { + embedder_->UpdateRectACK(instance_id, message_id, size); +} + +void BrowserPluginEmbedderHelper::OnSetFocus(int instance_id, bool focused) { + embedder_->SetFocus(instance_id, focused); +} + +void BrowserPluginEmbedderHelper::OnPluginDestroyed(int instance_id) { + embedder_->PluginDestroyed(instance_id); +} + +} // namespace content diff --git a/content/browser/browser_plugin/browser_plugin_embedder_helper.h b/content/browser/browser_plugin/browser_plugin_embedder_helper.h new file mode 100644 index 0000000..50e25e31 --- /dev/null +++ b/content/browser/browser_plugin/browser_plugin_embedder_helper.h @@ -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. + +#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_EMBEDDER_HELPER_H_ +#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_EMBEDDER_HELPER_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "content/public/browser/render_view_host_observer.h" + +namespace IPC { +class Message; +class SyncMessage; +} + +namespace gfx { +class Size; +} + +struct BrowserPluginHostMsg_ResizeGuest_Params; + +namespace content { + +class BrowserPluginEmbedder; +class RenderViewHost; + +// Helper for browser plugin embedder. +// +// A lot of messages coming from guests need to know the guest's RenderViewHost. +// BrowserPluginEmbedderHelper handles BrowserPluginHostMsg messages and +// relays them with their associated RenderViewHosts to its delegate where they +// will be handled. +// +// A BrowserPluginEmbedderHelper is created whenever a BrowserPluginEmbedder is +// created. BrowserPluginEmbedder's lifetime is managed by the associated +// RenderViewHost. Functions in this class is assumed to be run on UI thread. +class BrowserPluginEmbedderHelper : public RenderViewHostObserver { + public: + BrowserPluginEmbedderHelper(BrowserPluginEmbedder* embedder, + RenderViewHost* render_view_host); + virtual ~BrowserPluginEmbedderHelper(); + + // Make it public for sync IPCs. + virtual bool Send(IPC::Message* message) OVERRIDE; + + protected: + // RenderViewHostObserver implementation. + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + private: + // Message handlers. + void OnNavigateGuest(int instance_id, + int64 frame_id, + const std::string& src, + const gfx::Size& size); + void OnResizeGuest(int instance_id, + const BrowserPluginHostMsg_ResizeGuest_Params& params); + void OnUpdateRectACK(int instance_id, int message_id, const gfx::Size& size); + void OnHandleInputEvent(const IPC::SyncMessage& message, bool* handled); + void OnSetFocus(int instance_id, bool focused); + void OnPluginDestroyed(int instance_id); + + BrowserPluginEmbedder* embedder_; + + DISALLOW_COPY_AND_ASSIGN(BrowserPluginEmbedderHelper); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_EMBEDDER_HELPER_H_ diff --git a/content/browser/browser_plugin/browser_plugin_guest.cc b/content/browser/browser_plugin/browser_plugin_guest.cc new file mode 100644 index 0000000..374deec --- /dev/null +++ b/content/browser/browser_plugin/browser_plugin_guest.cc @@ -0,0 +1,280 @@ +// 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 "content/browser/browser_plugin/browser_plugin_guest_helper.h" +#include "content/browser/browser_plugin/browser_plugin_host_factory.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/common/browser_plugin_messages.h" +#include "content/common/view_messages.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/public/common/result_codes.h" +#include "content/browser/browser_plugin/browser_plugin_host_factory.h" +#include "ui/surface/transport_dib.h" + +namespace content { + +// static +BrowserPluginHostFactory* BrowserPluginGuest::factory_ = NULL; + +namespace { +const int kGuestHangTimeoutMs = 5000; +} + +BrowserPluginGuest::BrowserPluginGuest(int instance_id, + WebContentsImpl* web_contents, + RenderViewHost* render_view_host) + : WebContentsObserver(web_contents), + embedder_render_process_host_(NULL), + instance_id_(instance_id), +#if defined(OS_WIN) + damage_buffer_size_(0), +#endif + pending_update_counter_(0), + guest_hang_timeout_( + base::TimeDelta::FromMilliseconds(kGuestHangTimeoutMs)) { + DCHECK(web_contents); + // |render_view_host| manages the ownership of this BrowserPluginGuestHelper. + new BrowserPluginGuestHelper(this, render_view_host); +} + +BrowserPluginGuest::~BrowserPluginGuest() { +} + +// static +BrowserPluginGuest* BrowserPluginGuest::Create( + int instance_id, + WebContentsImpl* web_contents, + content::RenderViewHost* render_view_host) { + if (factory_) { + return factory_->CreateBrowserPluginGuest(instance_id, + web_contents, + render_view_host); + } + return new BrowserPluginGuest(instance_id, web_contents, render_view_host); +} + +bool BrowserPluginGuest::ViewTakeFocus(bool reverse) { + SendMessageToEmbedder( + new BrowserPluginMsg_AdvanceFocus(instance_id(), reverse)); + return true; +} + +void BrowserPluginGuest::RendererUnresponsive(WebContents* source) { + base::ProcessHandle process_handle = + web_contents()->GetRenderProcessHost()->GetHandle(); + base::KillProcess(process_handle, RESULT_CODE_HUNG, false); +} + +void BrowserPluginGuest::SetDamageBuffer( + TransportDIB* damage_buffer, +#if defined(OS_WIN) + int damage_buffer_size, +#endif + const gfx::Size& damage_view_size, + float scale_factor) { + // Sanity check: Verify that we've correctly shared the damage buffer memory + // between the embedder and browser processes. + DCHECK(*static_cast<unsigned int*>(damage_buffer->memory()) == 0xdeadbeef); + damage_buffer_.reset(damage_buffer); +#if defined(OS_WIN) + damage_buffer_size_ = damage_buffer_size; +#endif + damage_view_size_ = damage_view_size; + damage_buffer_scale_factor_ = scale_factor; +} + +void BrowserPluginGuest::UpdateRect( + RenderViewHost* render_view_host, + const ViewHostMsg_UpdateRect_Params& params) { + RenderWidgetHostImpl* render_widget_host = + RenderWidgetHostImpl::From(render_view_host); + render_widget_host->ResetSizeAndRepaintPendingFlags(); + // This handler is only of interest to us for the 2D software rendering path. + // needs_ack should always be true for the 2D path. + // TODO(fsamuel): Do we need to do something different in the 3D case? + if (!params.needs_ack) + return; + + // Only copy damage if 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 (params.view_size.width() == damage_view_size().width() && + params.view_size.height() == damage_view_size().height() && + params.scale_factor == damage_buffer_scale_factor()) { + TransportDIB* dib = render_view_host->GetProcess()-> + GetTransportDIB(params.bitmap); + if (dib) { +#if defined(OS_WIN) + size_t guest_damage_buffer_size = params.bitmap_rect.width() * + params.bitmap_rect.height() * 4; + size_t embedder_damage_buffer_size = damage_buffer_size_; +#else + size_t guest_damage_buffer_size = dib->size(); + size_t embedder_damage_buffer_size = damage_buffer_->size(); +#endif + 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); + } + } + DCHECK(embedder_render_process_host()); + BrowserPluginMsg_UpdateRect_Params relay_params; + relay_params.bitmap_rect = params.bitmap_rect; + relay_params.dx = params.dx; + relay_params.dy = params.dy; + relay_params.scroll_rect = params.scroll_rect; + relay_params.copy_rects = params.copy_rects; + 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); + + // We need to send the ACK to the same render_view_host that issued + // the UpdateRect. We keep track of this correspondence via a message_id. + int message_id = pending_update_counter_++; + pending_updates_.AddWithID(render_view_host, message_id); + + gfx::Size param_size = gfx::Size(params.view_size.width(), + params.view_size.height()); + + SendMessageToEmbedder(new BrowserPluginMsg_UpdateRect(instance_id(), + message_id, + relay_params)); +} + +void BrowserPluginGuest::UpdateRectACK(int message_id, const gfx::Size& size) { + RenderViewHost* render_view_host = pending_updates_.Lookup(message_id); + // If the guest has crashed since it sent the initial ViewHostMsg_UpdateRect + // then the pending_updates_ map will have been cleared. + if (!render_view_host) + return; + pending_updates_.Remove(message_id); + render_view_host->Send( + new ViewMsg_UpdateRect_ACK(render_view_host->GetRoutingID())); + if (!size.IsEmpty()) + render_view_host->GetView()->SetSize(size); +} + +void BrowserPluginGuest::HandleInputEvent(RenderViewHost* render_view_host, + const gfx::Rect& guest_rect, + const WebKit::WebInputEvent& event, + IPC::Message* reply_message) { + DCHECK(!pending_input_event_reply_.get()); + guest_rect_ = guest_rect; + RenderViewHostImpl* guest_rvh = static_cast<RenderViewHostImpl*>( + web_contents()->GetRenderViewHost()); + IPC::Message* message = new ViewMsg_HandleInputEvent( + guest_rvh->GetRoutingID()); + + // Copy the WebInputEvent and modify the event type. The guest expects + // WebInputEvent::RawKeyDowns and not KeyDowns. + scoped_array<char> input_buffer(new char[event.size]); + memcpy(input_buffer.get(), &event, event.size); + WebKit::WebInputEvent* input_event = + reinterpret_cast<WebKit::WebInputEvent*>(input_buffer.get()); + if (event.type == WebKit::WebInputEvent::KeyDown) + input_event->type = WebKit::WebInputEvent::RawKeyDown; + + message->WriteData(input_buffer.get(), event.size); + // TODO(fsamuel): What do we need to do here? This is for keyboard shortcuts. + if (input_event->type == WebKit::WebInputEvent::RawKeyDown) + message->WriteBool(false); + bool sent = guest_rvh->Send(message); + if (!sent) { + // If the embedder is waiting for a previous input ack, a new input message + // won't get sent to the guest. Reply immediately with handled = false so + // embedder doesn't hang. + BrowserPluginHostMsg_HandleInputEvent::WriteReplyParams( + reply_message, false /* handled */, cursor_); + embedder_render_process_host()->Send(reply_message); + return; + } + + pending_input_event_reply_.reset(reply_message); + // Input events are handled synchronously, meaning it blocks the embedder. We + // set a hang monitor here that will kill the guest process (5s timeout) if we + // don't receive an ack. This will kill all the guests that are running in the + // same process (undesired behavior). + // TODO(fsamuel,lazyboy): Find a way to get rid of guest process kill + // behavior. http://crbug.com/147272. + guest_rvh->StartHangMonitorTimeout(guest_hang_timeout_); +} + +void BrowserPluginGuest::HandleInputEventAck(RenderViewHost* render_view_host, + bool handled) { + RenderViewHostImpl* guest_rvh = + static_cast<RenderViewHostImpl*>(render_view_host); + guest_rvh->StopHangMonitorTimeout(); + DCHECK(pending_input_event_reply_.get()); + IPC::Message* reply_message = pending_input_event_reply_.release(); + BrowserPluginHostMsg_HandleInputEvent::WriteReplyParams(reply_message, + handled, + cursor_); + SendMessageToEmbedder(reply_message); +} + +void BrowserPluginGuest::SetFocus(bool focused) { + RenderViewHost* render_view_host = web_contents()->GetRenderViewHost(); + render_view_host->Send( + new ViewMsg_SetFocus(render_view_host->GetRoutingID(), focused)); +} + +void BrowserPluginGuest::ShowWidget(RenderViewHost* render_view_host, + int route_id, + const gfx::Rect& initial_pos) { + gfx::Rect screen_pos(initial_pos); + screen_pos.Offset(guest_rect_.origin()); + static_cast<WebContentsImpl*>(web_contents())->ShowCreatedWidget(route_id, + screen_pos); +} + +void BrowserPluginGuest::SetCursor(const WebCursor& cursor) { + cursor_ = cursor; +} + +void BrowserPluginGuest::DidCommitProvisionalLoadForFrame( + int64 frame_id, + bool is_main_frame, + const GURL& url, + PageTransition transition_type, + RenderViewHost* render_view_host) { + // Inform its embedder of the updated URL. + DCHECK(embedder_render_process_host()); + if (is_main_frame) + SendMessageToEmbedder(new BrowserPluginMsg_DidNavigate(instance_id(), url)); +} + +void BrowserPluginGuest::RenderViewGone(base::TerminationStatus status) { + DCHECK(embedder_render_process_host()); + if (pending_input_event_reply_.get()) { + IPC::Message* reply_message = pending_input_event_reply_.release(); + BrowserPluginHostMsg_HandleInputEvent::WriteReplyParams(reply_message, + false, + cursor_); + SendMessageToEmbedder(reply_message); + } + SendMessageToEmbedder(new BrowserPluginMsg_GuestCrashed(instance_id())); + IDMap<RenderViewHost>::const_iterator iter(&pending_updates_); + while (!iter.IsAtEnd()) { + pending_updates_.Remove(iter.GetCurrentKey()); + iter.Advance(); + } +} + +void BrowserPluginGuest::SendMessageToEmbedder(IPC::Message* msg) { + embedder_render_process_host()->Send(msg); +} + +} // namespace content diff --git a/content/browser/browser_plugin/browser_plugin_guest.h b/content/browser/browser_plugin/browser_plugin_guest.h new file mode 100644 index 0000000..27d438b --- /dev/null +++ b/content/browser/browser_plugin/browser_plugin_guest.h @@ -0,0 +1,174 @@ +// 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 represents the browser side of browser <--> renderer +// communication. A BrowserPlugin (a WebPlugin) is on the renderer side of +// browser <--> guest renderer communication. The 'guest' renderer is a +// <browser> tag. +// +// BrowserPluginGuest lives on the UI thread of the browser process. It has a +// helper, BrowserPluginGuestHelper, which is a RenderViewHostObserver. The +// helper object receives messages (ViewHostMsg_*) directed at the browser +// plugin and redirects them to this class. Any messages the embedder might be +// interested in knowing or modifying about the guest should be listened for +// here. +// +// Since BrowserPlugin is a WebPlugin, we need to provide overridden behaviors +// for messages like handleInputEvent, updateGeometry. Such messages get +// routed into BrowserPluginGuest via its embedder (BrowserPluginEmbedder). +// These are BrowserPluginHost_* messages sent from the BrowserPlugin. +// +// BrowserPluginGuest knows about its embedder process. Communication to +// renderer happens through the embedder process. +// +// A BrowserPluginGuest is also associated directly with the WebContents related +// to the BrowserPlugin. BrowserPluginGuest is a WebContentsDelegate and +// WebContentsObserver for the WebContents. + +#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_H_ +#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_H_ + +#include <map> + +#include "base/compiler_specific.h" +#include "base/id_map.h" +#include "base/time.h" +#include "content/public/browser/web_contents_delegate.h" +#include "content/public/browser/web_contents_observer.h" +#include "ui/gfx/rect.h" +#include "webkit/glue/webcursor.h" + +class TransportDIB; +struct ViewHostMsg_UpdateRect_Params; + +namespace WebKit { +class WebInputEvent; +} + +namespace content { + +class BrowserPluginHostFactory; +class BrowserPluginEmbedder; +class RenderProcessHost; + +// A browser plugin guest provides functionality for WebContents to operate in +// the guest role and implements guest specific overrides for ViewHostMsg_* +// messages. +// +// BrowserPluginEmbedder is responsible for creating and destroying a guest. +class CONTENT_EXPORT BrowserPluginGuest : public WebContentsDelegate, + public WebContentsObserver { + public: + virtual ~BrowserPluginGuest(); + + static BrowserPluginGuest* Create(int instance_id, + WebContentsImpl* web_contents, + content::RenderViewHost* render_view_host); + + // Overrides factory for testing. Default (NULL) value indicates regular + // (non-test) environment. + static void set_factory_for_testing(BrowserPluginHostFactory* factory) { + content::BrowserPluginGuest::factory_ = factory; + } + + void set_guest_hang_timeout_for_testing(const base::TimeDelta& timeout) { + guest_hang_timeout_ = timeout; + } + + // 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 RenderViewGone(base::TerminationStatus status) OVERRIDE; + + // WebContentsDelegate implementation. + virtual void RendererUnresponsive(WebContents* source) OVERRIDE; + + private: + friend class BrowserPluginEmbedder; + friend class BrowserPluginGuestHelper; + friend class TestBrowserPluginGuest; + + BrowserPluginGuest(int instance_id, + WebContentsImpl* web_contents, + RenderViewHost* render_view_host); + + void set_embedder_render_process_host( + RenderProcessHost* render_process_host) { + embedder_render_process_host_ = render_process_host; + } + RenderProcessHost* embedder_render_process_host() { + return embedder_render_process_host_; + } + // Returns the identifier that uniquely identifies a browser plugin guest + // within an embedder. + int instance_id() const { return instance_id_; } + TransportDIB* 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_; + } + void SetDamageBuffer(TransportDIB* damage_buffer, +#if defined(OS_WIN) + int damage_buffer_size, +#endif + const gfx::Size& damage_view_size, + float scale_factor); + + void UpdateRect(RenderViewHost* render_view_host, + const ViewHostMsg_UpdateRect_Params& params); + void UpdateRectACK(int message_id, const gfx::Size& size); + // Handles input event routed through the embedder (which is initiated in the + // browser plugin (renderer side of the embedder)). + void HandleInputEvent(RenderViewHost* render_view_host, + const gfx::Rect& guest_rect, + const WebKit::WebInputEvent& event, + IPC::Message* reply_message); + // Overrides default ShowWidget message so we show them on the correct + // coordinates. + void ShowWidget(RenderViewHost* render_view_host, + int route_id, + const gfx::Rect& initial_pos); + void SetCursor(const WebCursor& cursor); + // Handles input event acks so they are sent to browser plugin host (via + // embedder) instead of default view/widget host. + void HandleInputEventAck(RenderViewHost* render_view_host, bool handled); + + // Helper to send messages to embedder. Overridden in test implementation + // since we want to intercept certain messages for testing. + virtual void SendMessageToEmbedder(IPC::Message* msg); + // Overridden in tests. + virtual void SetFocus(bool focused); + // Overridden in tests. + virtual bool ViewTakeFocus(bool reverse); + + // Static factory instance (always NULL for non-test). + static content::BrowserPluginHostFactory* factory_; + + RenderProcessHost* embedder_render_process_host_; + // An identifier that uniquely identifies a browser plugin guest within an + // embedder. + int instance_id_; + scoped_ptr<TransportDIB> damage_buffer_; +#if defined(OS_WIN) + size_t damage_buffer_size_; +#endif + gfx::Size damage_view_size_; + float damage_buffer_scale_factor_; + scoped_ptr<IPC::Message> pending_input_event_reply_; + gfx::Rect guest_rect_; + WebCursor cursor_; + IDMap<RenderViewHost> pending_updates_; + int pending_update_counter_; + base::TimeDelta guest_hang_timeout_; + + DISALLOW_COPY_AND_ASSIGN(BrowserPluginGuest); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_H_ diff --git a/content/browser/browser_plugin/browser_plugin_guest_helper.cc b/content/browser/browser_plugin/browser_plugin_guest_helper.cc new file mode 100644 index 0000000..9ca3c10 --- /dev/null +++ b/content/browser/browser_plugin/browser_plugin_guest_helper.cc @@ -0,0 +1,62 @@ +// 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/browser/web_contents/web_contents_impl.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) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(BrowserPluginGuestHelper, message) + IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateRect, OnUpdateRect) + IPC_MESSAGE_HANDLER(ViewHostMsg_HandleInputEvent_ACK, OnHandleInputEventAck) + IPC_MESSAGE_HANDLER(ViewHostMsg_TakeFocus, OnTakeFocus) + IPC_MESSAGE_HANDLER(ViewHostMsg_ShowWidget, OnShowWidget) + IPC_MESSAGE_HANDLER(ViewHostMsg_SetCursor, OnSetCursor) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void BrowserPluginGuestHelper::OnUpdateRect( + const ViewHostMsg_UpdateRect_Params& params) { + guest_->UpdateRect(render_view_host(), params); +} + +void BrowserPluginGuestHelper::OnHandleInputEventAck( + WebKit::WebInputEvent::Type event_type, + bool processed) { + guest_->HandleInputEventAck(render_view_host(), processed); +} + +void BrowserPluginGuestHelper::OnTakeFocus(bool reverse) { + guest_->ViewTakeFocus(reverse); +} + +void BrowserPluginGuestHelper::OnShowWidget(int route_id, + const gfx::Rect& initial_pos) { + guest_->ShowWidget(render_view_host(), route_id, initial_pos); +} + +void BrowserPluginGuestHelper::OnSetCursor(const WebCursor& cursor) { + guest_->SetCursor(cursor); +} + +} // namespace content diff --git a/content/browser/browser_plugin/browser_plugin_guest_helper.h b/content/browser/browser_plugin/browser_plugin_guest_helper.h new file mode 100644 index 0000000..19b3c7c --- /dev/null +++ b/content/browser/browser_plugin/browser_plugin_guest_helper.h @@ -0,0 +1,61 @@ +// 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/public/browser/render_view_host_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" + +class WebCursor; +struct ViewHostMsg_UpdateRect_Params; + +namespace gfx { +class Size; +} + +namespace content { +class BrowserPluginGuest; +class RenderViewHost; + +// Helper for browser plugin guest. +// +// It overrides different WebContents messages that require special treatment +// for a WebContents to act as a guest. All functionality is handled by its +// delegate. This class exists so we have separation of messages requiring +// special handling, which can be moved to a message filter (IPC thread) for +// future optimization. +// +// 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: + // Message handlers + void OnUpdateRect(const ViewHostMsg_UpdateRect_Params& params); + void OnHandleInputEventAck(WebKit::WebInputEvent::Type event_type, + bool processed); + void OnTakeFocus(bool reverse); + void OnShowWidget(int route_id, const gfx::Rect& initial_pos); + void OnSetCursor(const WebCursor& cursor); + + BrowserPluginGuest* guest_; + // A scoped container for notification registries. + NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(BrowserPluginGuestHelper); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_GUEST_HELPER_H_ diff --git a/content/browser/browser_plugin/browser_plugin_host_browsertest.cc b/content/browser/browser_plugin/browser_plugin_host_browsertest.cc new file mode 100644 index 0000000..28686b4 --- /dev/null +++ b/content/browser/browser_plugin/browser_plugin_host_browsertest.cc @@ -0,0 +1,490 @@ +// 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 "base/memory/singleton.h" +#include "base/run_loop.h" +#include "base/test/test_timeouts.h" +#include "base/utf_string_conversions.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/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/test/browser_test_utils.h" +#include "content/public/test/test_utils.h" +#include "content/shell/shell.h" +#include "content/test/content_browser_test_utils.h" +#include "content/test/content_browser_test.h" +#include "net/base/net_util.h" +#include "net/test/test_server.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" + +using WebKit::WebInputEvent; +using WebKit::WebMouseEvent; +using content::BrowserPluginEmbedder; +using content::BrowserPluginGuest; +using content::BrowserPluginHostFactory; + +namespace content { + +const char* kHTMLForGuest = + "data:text/html,<html><body>hello world</body></html>"; +const char* kHTMLForGuestInfiniteLoop = + "data:text/html,<html><head><script type=\"text/javascript\">" + "function StartInfiniteLoop() {" + " setTimeout(function () {while (true) {} }, 0);" + "}" + "</script></head><body></body></html>"; + +// Test factory for creating test instances of BrowserPluginEmbedder and +// BrowserPluginGuest. +class TestBrowserPluginHostFactory : public BrowserPluginHostFactory { + public: + virtual BrowserPluginGuest* CreateBrowserPluginGuest( + int instance_id, + WebContentsImpl* web_contents, + RenderViewHost* render_view_host) OVERRIDE { + return new TestBrowserPluginGuest(instance_id, + web_contents, + render_view_host); + } + + // Also keeps track of number of instances created. + virtual BrowserPluginEmbedder* CreateBrowserPluginEmbedder( + WebContentsImpl* web_contents, + RenderViewHost* render_view_host) OVERRIDE { + embedder_instance_count_++; + if (message_loop_runner_) + message_loop_runner_->Quit(); + + return new TestBrowserPluginEmbedder(web_contents, render_view_host); + } + + // 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 WaitForEmbedderCreation() { + // Check if already have created instance. + if (embedder_instance_count_ > 0) + return; + // Wait otherwise. + message_loop_runner_ = new MessageLoopRunner(); + message_loop_runner_->Run(); + } + + protected: + TestBrowserPluginHostFactory() : embedder_instance_count_(0) {} + virtual ~TestBrowserPluginHostFactory() {} + + private: + // For Singleton. + friend struct DefaultSingletonTraits<TestBrowserPluginHostFactory>; + + scoped_refptr<MessageLoopRunner> message_loop_runner_; + int embedder_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, + RenderViewHost* render_view_host) OVERRIDE { + BrowserPluginGuest* guest = new TestBrowserPluginGuest(instance_id, + web_contents, + render_view_host); + 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); +}; + +class BrowserPluginHostTest : public ContentBrowserTest { + public: + BrowserPluginHostTest() {} + + 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()); + + ContentBrowserTest::SetUp(); + } + virtual void TearDown() OVERRIDE { + content::BrowserPluginEmbedder::set_factory_for_testing(NULL); + content::BrowserPluginGuest::set_factory_for_testing(NULL); + + ContentBrowserTest::TearDown(); + } + + static void SimulateTabKeyPress(WebContents* web_contents) { + SimulateKeyPress(web_contents, + ui::VKEY_TAB, + false, // control. + false, // shift. + false, // alt. + false); // command. + } + + private: + DISALLOW_COPY_AND_ASSIGN(BrowserPluginHostTest); +}; + +// This test loads a guest that has infinite loop, therefore it hangs the guest +// and eventually gets killed. +// TODO(lazyboy): This test is flaky on Windows, since this relies on +// RenderViewGone to be called and times out. http://crbug.com/151190. +#if defined(OS_WIN) +#define MAYBE_NavigateGuest DISABLED_NavigateGuest +#else +#define MAYBE_NavigateGuest NavigateGuest +#endif +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, MAYBE_NavigateGuest) { + // Override the hang timeout for guest to be very small. + content::BrowserPluginGuest::set_factory_for_testing( + TestShortHangTimeoutGuestFactory::GetInstance()); + ASSERT_TRUE(test_server()->Start()); + GURL test_url(test_server()->GetURL( + "files/browser_plugin_embedder_crash.html")); + NavigateToURL(shell(), test_url); + + WebContentsImpl* embedder_web_contents = static_cast<WebContentsImpl*>( + shell()->web_contents()); + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + embedder_web_contents->GetRenderViewHost()); + + rvh->ExecuteJavascriptAndGetValue(string16(), ASCIIToUTF16( + StringPrintf("SetSrc('%s');", kHTMLForGuestInfiniteLoop))); + + // Wait to make sure embedder is created/attached to WebContents. + TestBrowserPluginHostFactory::GetInstance()->WaitForEmbedderCreation(); + + TestBrowserPluginEmbedder* test_embedder = + static_cast<TestBrowserPluginEmbedder*>( + embedder_web_contents->GetBrowserPluginEmbedder()); + ASSERT_TRUE(test_embedder); + test_embedder->WaitForGuestAdded(); + + // Verify that we have exactly one guest. + const BrowserPluginEmbedder::ContainerInstanceMap& instance_map = + test_embedder->guest_web_contents_for_testing(); + EXPECT_EQ(1u, instance_map.size()); + + WebContentsImpl* test_guest_web_contents = static_cast<WebContentsImpl*>( + instance_map.begin()->second); + TestBrowserPluginGuest* test_guest = static_cast<TestBrowserPluginGuest*>( + test_guest_web_contents->GetBrowserPluginGuest()); + + // Wait for the guest to send an UpdateRectMsg, meaning it is ready. + test_guest->WaitForUpdateRectMsg(); + + test_guest_web_contents->GetRenderViewHost()->ExecuteJavascriptAndGetValue( + string16(), ASCIIToUTF16("StartInfiniteLoop();")); + + // Send a mouse event to the guest. + SimulateMouseClick(embedder_web_contents); + + // Expect the guest to crash. + test_guest->WaitForCrashed(); +} + +// 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) { + ASSERT_TRUE(test_server()->Start()); + GURL test_url(test_server()->GetURL( + "files/browser_plugin_embedder.html")); + NavigateToURL(shell(), test_url); + + WebContentsImpl* embedder_web_contents = static_cast<WebContentsImpl*>( + shell()->web_contents()); + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + embedder_web_contents->GetRenderViewHost()); + + int nxt_width = 100; + int nxt_height = 200; + rvh->ExecuteJavascriptAndGetValue(string16(), ASCIIToUTF16( + StringPrintf("SetSize(%d, %d);", nxt_width, nxt_height))); + + rvh->ExecuteJavascriptAndGetValue(string16(), ASCIIToUTF16( + StringPrintf("SetSrc('%s');", kHTMLForGuest))); + + // Wait to make sure embedder is created/attached to WebContents. + TestBrowserPluginHostFactory::GetInstance()->WaitForEmbedderCreation(); + + TestBrowserPluginEmbedder* test_embedder = + static_cast<TestBrowserPluginEmbedder*>( + embedder_web_contents->GetBrowserPluginEmbedder()); + ASSERT_TRUE(test_embedder); + test_embedder->WaitForGuestAdded(); + + // Verify that we have exactly one guest. + const BrowserPluginEmbedder::ContainerInstanceMap& instance_map = + test_embedder->guest_web_contents_for_testing(); + EXPECT_EQ(1u, instance_map.size()); + + WebContentsImpl* test_guest_web_contents = static_cast<WebContentsImpl*>( + instance_map.begin()->second); + TestBrowserPluginGuest* test_guest = static_cast<TestBrowserPluginGuest*>( + test_guest_web_contents->GetBrowserPluginGuest()); + + + // Wait for the guest to send an UpdateRectMsg, the dimensions should be + // 100 x 200. + test_guest->WaitForUpdateRectMsgWithSize(nxt_width, nxt_height); +} + +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, AdvanceFocus) { + ASSERT_TRUE(test_server()->Start()); + GURL test_url(test_server()->GetURL( + "files/browser_plugin_focus.html")); + NavigateToURL(shell(), test_url); + + WebContentsImpl* embedder_web_contents = static_cast<WebContentsImpl*>( + shell()->web_contents()); + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + embedder_web_contents->GetRenderViewHost()); + + test_url = test_server()->GetURL( + "files/browser_plugin_focus_child.html"); + rvh->ExecuteJavascriptAndGetValue(string16(), ASCIIToUTF16( + StringPrintf("SetSrc('%s');", test_url.spec().c_str()))); + + // Wait to make sure embedder is created/attached to WebContents. + TestBrowserPluginHostFactory::GetInstance()->WaitForEmbedderCreation(); + + TestBrowserPluginEmbedder* test_embedder = + static_cast<TestBrowserPluginEmbedder*>( + embedder_web_contents->GetBrowserPluginEmbedder()); + ASSERT_TRUE(test_embedder); + test_embedder->WaitForGuestAdded(); + + // Verify that we have exactly one guest. + const BrowserPluginEmbedder::ContainerInstanceMap& instance_map = + test_embedder->guest_web_contents_for_testing(); + EXPECT_EQ(1u, instance_map.size()); + + WebContentsImpl* test_guest_web_contents = static_cast<WebContentsImpl*>( + instance_map.begin()->second); + TestBrowserPluginGuest* test_guest = static_cast<TestBrowserPluginGuest*>( + test_guest_web_contents->GetBrowserPluginGuest()); + test_guest->WaitForUpdateRectMsg(); + + SimulateMouseClick(embedder_web_contents); + BrowserPluginHostTest::SimulateTabKeyPress(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(embedder_web_contents); + BrowserPluginHostTest::SimulateTabKeyPress(embedder_web_contents); + BrowserPluginHostTest::SimulateTabKeyPress(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) { + ASSERT_TRUE(test_server()->Start()); + net::TestServer https_server( + net::TestServer::TYPE_HTTPS, + net::TestServer::kLocalhost, + FilePath(FILE_PATH_LITERAL("content/test/data"))); + ASSERT_TRUE(https_server.Start()); + + // 1. Load an embedder page with one guest in it. + GURL test_url(test_server()->GetURL("files/browser_plugin_embedder.html")); + NavigateToURL(shell(), test_url); + + WebContentsImpl* embedder_web_contents = static_cast<WebContentsImpl*>( + shell()->web_contents()); + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + embedder_web_contents->GetRenderViewHost()); + rvh->ExecuteJavascriptAndGetValue(string16(), ASCIIToUTF16( + StringPrintf("SetSrc('%s');", kHTMLForGuest))); + + // Wait to make sure embedder is created/attached to WebContents. + TestBrowserPluginHostFactory::GetInstance()->WaitForEmbedderCreation(); + + TestBrowserPluginEmbedder* test_embedder_before_swap = + static_cast<TestBrowserPluginEmbedder*>( + embedder_web_contents->GetBrowserPluginEmbedder()); + ASSERT_TRUE(test_embedder_before_swap); + test_embedder_before_swap->WaitForGuestAdded(); + + // Verify that we have exactly one guest. + const BrowserPluginEmbedder::ContainerInstanceMap& instance_map = + test_embedder_before_swap->guest_web_contents_for_testing(); + EXPECT_EQ(1u, instance_map.size()); + + WebContentsImpl* test_guest_web_contents = static_cast<WebContentsImpl*>( + instance_map.begin()->second); + TestBrowserPluginGuest* test_guest = static_cast<TestBrowserPluginGuest*>( + test_guest_web_contents->GetBrowserPluginGuest()); + + // Wait for the guest to send an UpdateRectMsg, which means the guest is + // ready. + test_guest->WaitForUpdateRectMsg(); + + // 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>(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_before_swap, 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) { + ASSERT_TRUE(test_server()->Start()); + + GURL test_url(test_server()->GetURL("files/browser_plugin_embedder.html")); + NavigateToURL(shell(), test_url); + + WebContentsImpl* embedder_web_contents = static_cast<WebContentsImpl*>( + shell()->web_contents()); + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + embedder_web_contents->GetRenderViewHost()); + + rvh->ExecuteJavascriptAndGetValue(string16(), ASCIIToUTF16( + StringPrintf("SetSrc('%s');", kHTMLForGuest))); + + // Wait to make sure embedder is created/attached to WebContents. + TestBrowserPluginHostFactory::GetInstance()->WaitForEmbedderCreation(); + + TestBrowserPluginEmbedder* test_embedder = + static_cast<TestBrowserPluginEmbedder*>( + embedder_web_contents->GetBrowserPluginEmbedder()); + ASSERT_TRUE(test_embedder); + test_embedder->WaitForGuestAdded(); + + // Verify that we have exactly one guest. + const BrowserPluginEmbedder::ContainerInstanceMap& instance_map = + test_embedder->guest_web_contents_for_testing(); + EXPECT_EQ(1u, instance_map.size()); + + WebContentsImpl* test_guest_web_contents = static_cast<WebContentsImpl*>( + instance_map.begin()->second); + TestBrowserPluginGuest* test_guest = static_cast<TestBrowserPluginGuest*>( + test_guest_web_contents->GetBrowserPluginGuest()); + + // Wait for the guest to send an UpdateRectMsg, which means the guest is + // ready. + test_guest->WaitForUpdateRectMsg(); + + // 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(test_server()->GetURL( + "files/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); +} + +IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, VisibilityChanged) { + ASSERT_TRUE(test_server()->Start()); + GURL test_url(test_server()->GetURL( + "files/browser_plugin_focus.html")); + NavigateToURL(shell(), test_url); + + WebContentsImpl* embedder_web_contents = static_cast<WebContentsImpl*>( + shell()->web_contents()); + RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( + embedder_web_contents->GetRenderViewHost()); + + test_url = test_server()->GetURL( + "files/browser_plugin_focus_child.html"); + rvh->ExecuteJavascriptAndGetValue(string16(), ASCIIToUTF16( + StringPrintf("SetSrc('%s');", test_url.spec().c_str()))); + + // Wait to make sure embedder is created/attached to WebContents. + TestBrowserPluginHostFactory::GetInstance()->WaitForEmbedderCreation(); + + TestBrowserPluginEmbedder* test_embedder = + static_cast<TestBrowserPluginEmbedder*>( + embedder_web_contents->GetBrowserPluginEmbedder()); + ASSERT_TRUE(test_embedder); + test_embedder->WaitForGuestAdded(); + + // Verify that we have exactly one guest. + const BrowserPluginEmbedder::ContainerInstanceMap& instance_map = + test_embedder->guest_web_contents_for_testing(); + EXPECT_EQ(1u, instance_map.size()); + + WebContentsImpl* test_guest_web_contents = static_cast<WebContentsImpl*>( + instance_map.begin()->second); + TestBrowserPluginGuest* test_guest = static_cast<TestBrowserPluginGuest*>( + test_guest_web_contents->GetBrowserPluginGuest()); + + // Wait for the guest to send an UpdateRectMsg, meaning it is ready. + test_guest->WaitForUpdateRectMsg(); + + // Hide the embedder. + embedder_web_contents->WasHidden(); + + // Make sure that hiding the embedder also hides the guest. + test_guest->WaitUntilHidden(); +} + +} // namespace content diff --git a/content/browser/browser_plugin/browser_plugin_host_factory.h b/content/browser/browser_plugin/browser_plugin_host_factory.h new file mode 100644 index 0000000..b90b9e0 --- /dev/null +++ b/content/browser/browser_plugin/browser_plugin_host_factory.h @@ -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. + +#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/string16.h" +#include "content/common/content_export.h" + +class WebContentsImpl; + +namespace content { + +class BrowserPluginEmbedder; +class BrowserPluginGuest; +class RenderViewHost; + +// Factory to create BrowserPlugin embedder and guest. +class CONTENT_EXPORT BrowserPluginHostFactory { + public: + virtual BrowserPluginGuest* CreateBrowserPluginGuest( + int instance_id, + WebContentsImpl* web_contents, + RenderViewHost* render_view_host) = 0; + + virtual BrowserPluginEmbedder* CreateBrowserPluginEmbedder( + WebContentsImpl* web_contents, + RenderViewHost* render_view_host) = 0; + + protected: + virtual ~BrowserPluginHostFactory() {} +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_HOST_FACTORY_H_ diff --git a/content/browser/browser_plugin/test_browser_plugin_embedder.cc b/content/browser/browser_plugin/test_browser_plugin_embedder.cc new file mode 100644 index 0000000..8d0ae8f --- /dev/null +++ b/content/browser/browser_plugin/test_browser_plugin_embedder.cc @@ -0,0 +1,42 @@ +// 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 "base/time.h" +#include "content/browser/browser_plugin/browser_plugin_embedder.h" +#include "content/browser/browser_plugin/browser_plugin_guest.h" +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/public/test/test_utils.h" + +namespace content { + +TestBrowserPluginEmbedder::TestBrowserPluginEmbedder( + WebContentsImpl* web_contents, + RenderViewHost* render_view_host) + : BrowserPluginEmbedder(web_contents, render_view_host) { +} + +TestBrowserPluginEmbedder::~TestBrowserPluginEmbedder() { +} + +void TestBrowserPluginEmbedder::AddGuest(int instance_id, + WebContents* guest_web_contents, + int64 frame_id) { + BrowserPluginEmbedder::AddGuest(instance_id, guest_web_contents, frame_id); + if (message_loop_runner_) + message_loop_runner_->Quit(); +} + +void TestBrowserPluginEmbedder::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/content/browser/browser_plugin/test_browser_plugin_embedder.h b/content/browser/browser_plugin/test_browser_plugin_embedder.h new file mode 100644 index 0000000..6c62bdb --- /dev/null +++ b/content/browser/browser_plugin/test_browser_plugin_embedder.h @@ -0,0 +1,49 @@ +// 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" + +class WebContentsImpl; + +namespace content { + +class BrowserPluginGuest; +class RenderViewHost; + +// 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, + RenderViewHost* render_view_host); + virtual ~TestBrowserPluginEmbedder(); + + const ContainerInstanceMap& guest_web_contents_for_testing() const { + return guest_web_contents_by_instance_id_; + } + + // Waits until at least one guest is added to this embedder. + void WaitForGuestAdded(); + + private: + // Overridden to intercept in test. + virtual void AddGuest(int instance_id, + WebContents* guest_web_contents, + int64 frame_id) OVERRIDE; + + scoped_refptr<MessageLoopRunner> message_loop_runner_; + + DISALLOW_COPY_AND_ASSIGN(TestBrowserPluginEmbedder); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_EMBEDDER_H_ diff --git a/content/browser/browser_plugin/test_browser_plugin_guest.cc b/content/browser/browser_plugin/test_browser_plugin_guest.cc new file mode 100644 index 0000000..35f119e --- /dev/null +++ b/content/browser/browser_plugin/test_browser_plugin_guest.cc @@ -0,0 +1,171 @@ +// 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/browser_plugin/browser_plugin_guest.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_messages.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_types.h" +#include "content/public/test/test_utils.h" +#include "ui/gfx/size.h" + +namespace content { + +class BrowserPluginGuest; + +TestBrowserPluginGuest::TestBrowserPluginGuest( + int instance_id, + WebContentsImpl* web_contents, + RenderViewHost* render_view_host) + : BrowserPluginGuest(instance_id, web_contents, render_view_host), + update_rect_count_(0), + crash_observed_(false), + focus_observed_(false), + advance_focus_observed_(false), + was_hidden_observed_(false), + waiting_for_update_rect_msg_with_size_(false), + last_update_rect_width_(-1), + last_update_rect_height_(-1) { + // Listen to visibility changes so that a test can wait for these changes. + registrar_.Add(this, + NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED, + Source<WebContents>(web_contents)); +} + +TestBrowserPluginGuest::~TestBrowserPluginGuest() { +} + +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_) + was_hidden_message_loop_runner_->Quit(); + } + break; + } + default: + NOTREACHED() << "Unexpected notification type: " << type; + } +} + +void TestBrowserPluginGuest::SendMessageToEmbedder(IPC::Message* msg) { + if (msg->type() == BrowserPluginMsg_UpdateRect::ID) { + PickleIterator iter(*msg); + + int instance_id; + int message_id; + BrowserPluginMsg_UpdateRect_Params update_rect_params; + + if (!IPC::ReadParam(msg, &iter, &instance_id) || + !IPC::ReadParam(msg, &iter, &message_id) || + !IPC::ReadParam(msg, &iter, &update_rect_params)) { + NOTREACHED() << + "Cannot read BrowserPluginMsg_UpdateRect params from ipc message"; + } + last_update_rect_width_ = update_rect_params.view_size.width(); + last_update_rect_height_ = update_rect_params.view_size.height(); + update_rect_count_++; + if (waiting_for_update_rect_msg_with_size_ && + expected_width_ == last_update_rect_width_ && + expected_height_ == last_update_rect_height_) { + waiting_for_update_rect_msg_with_size_ = false; + if (send_message_loop_runner_) + send_message_loop_runner_->Quit(); + } else if (!waiting_for_update_rect_msg_with_size_) { + if (send_message_loop_runner_) + 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::WaitForUpdateRectMsgWithSize(int width, + int height) { + if (update_rect_count_ > 0 && + last_update_rect_width_ == width && + last_update_rect_height_ == height) { + // We already saw this message. + return; + } + waiting_for_update_rect_msg_with_size_ = true; + expected_width_ = width; + expected_height_ = height; + + send_message_loop_runner_ = new MessageLoopRunner(); + send_message_loop_runner_->Run(); +} + +void TestBrowserPluginGuest::RenderViewGone(base::TerminationStatus status) { + crash_observed_ = true; + LOG(INFO) << "Guest crashed"; + if (crash_message_loop_runner_) + crash_message_loop_runner_->Quit(); + BrowserPluginGuest::RenderViewGone(status); +} + +void TestBrowserPluginGuest::WaitForCrashed() { + // Check if we already observed a guest crash, return immediately if so. + if (crash_observed_) + return; + + crash_message_loop_runner_ = new MessageLoopRunner(); + crash_message_loop_runner_->Run(); +} + +void TestBrowserPluginGuest::WaitForFocus() { + if (focus_observed_) + return; + focus_message_loop_runner_ = new MessageLoopRunner(); + focus_message_loop_runner_->Run(); +} + +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::SetFocus(bool focused) { + focus_observed_ = true; + if (focus_message_loop_runner_) + focus_message_loop_runner_->Quit(); + BrowserPluginGuest::SetFocus(focused); +} + +bool TestBrowserPluginGuest::ViewTakeFocus(bool reverse) { + advance_focus_observed_ = true; + if (advance_focus_message_loop_runner_) + advance_focus_message_loop_runner_->Quit(); + return BrowserPluginGuest::ViewTakeFocus(reverse); +} + +} // namespace content diff --git a/content/browser/browser_plugin/test_browser_plugin_guest.h b/content/browser/browser_plugin/test_browser_plugin_guest.h new file mode 100644 index 0000000..7460505 --- /dev/null +++ b/content/browser/browser_plugin/test_browser_plugin_guest.h @@ -0,0 +1,90 @@ +// 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 "base/process_util.h" +#include "content/browser/browser_plugin/browser_plugin_guest.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/test/test_utils.h" + +class WebContentsImpl; + +namespace content { + +class RenderProcessHost; +class RenderViewHost; + +// Test class for BrowserPluginGuest. +// +// Provides utilities to wait for certain state/messages in BrowserPluginGuest +// to be used in tests. +class TestBrowserPluginGuest : public BrowserPluginGuest, + public NotificationObserver { + public: + TestBrowserPluginGuest(int instance_id, + WebContentsImpl* web_contents, + RenderViewHost* render_view_host); + virtual ~TestBrowserPluginGuest(); + + // 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 RenderViewGone(base::TerminationStatus status) OVERRIDE; + virtual void SetFocus(bool focused) OVERRIDE; + virtual bool ViewTakeFocus(bool reverse) 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(); + // Waits until UpdateRect message with a specific size is sent from the guest. + void WaitForUpdateRectMsgWithSize(int width, int height); + // Waits for focus to reach this guest. + void WaitForFocus(); + // Wait for focus to move out of this guest. + void WaitForAdvanceFocus(); + // Wait until the guest is hidden. + void WaitUntilHidden(); + // Waits until guest crashes. + void WaitForCrashed(); + + private: + // Overridden methods from BrowserPluginGuest to intercept in test objects. + virtual void SendMessageToEmbedder(IPC::Message* msg) OVERRIDE; + + int update_rect_count_; + bool crash_observed_; + bool focus_observed_; + bool advance_focus_observed_; + bool was_hidden_observed_; + + // For WaitForUpdateRectMsgWithSize(). + bool waiting_for_update_rect_msg_with_size_; + int expected_width_; + int expected_height_; + + int last_update_rect_width_; + int last_update_rect_height_; + + scoped_refptr<MessageLoopRunner> send_message_loop_runner_; + scoped_refptr<MessageLoopRunner> crash_message_loop_runner_; + scoped_refptr<MessageLoopRunner> focus_message_loop_runner_; + scoped_refptr<MessageLoopRunner> advance_focus_message_loop_runner_; + scoped_refptr<MessageLoopRunner> was_hidden_message_loop_runner_; + + // A scoped container for notification registries. + NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(TestBrowserPluginGuest); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_BROWSER_PLUGIN_TEST_BROWSER_PLUGIN_GUEST_H_ |