// 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/renderer/browser_plugin/browser_plugin_browsertest.h" #include "base/files/file_path.h" #include "base/memory/singleton.h" #include "base/path_service.h" #include "content/public/common/content_constants.h" #include "content/renderer/browser_plugin/browser_plugin.h" #include "content/renderer/browser_plugin/browser_plugin_manager_factory.h" #include "content/renderer/browser_plugin/mock_browser_plugin.h" #include "content/renderer/browser_plugin/mock_browser_plugin_manager.h" #include "content/renderer/render_thread_impl.h" #include "content/renderer/renderer_webkitplatformsupport_impl.h" #include "skia/ext/platform_canvas.h" #include "third_party/WebKit/public/web/WebCursorInfo.h" #include "third_party/WebKit/public/web/WebInputEvent.h" #include "third_party/WebKit/public/web/WebScriptSource.h" namespace { const char kHTMLForBrowserPluginObject[] = "<object id='browserplugin' width='640px' height='480px'" " src='foo' type='%s'>"; const char kHTMLForBrowserPluginWithAllAttributes[] = "<object id='browserplugin' width='640' height='480' type='%s'" " autosize maxheight='600' maxwidth='800' minheight='240'" " minwidth='320' name='Jim' partition='someid' src='foo'>"; const char kHTMLForSourcelessPluginObject[] = "<object id='browserplugin' width='640px' height='480px' type='%s'>"; const char kHTMLForPartitionedPluginObject[] = "<object id='browserplugin' width='640px' height='480px'" " src='foo' type='%s' partition='someid'>"; const char kHTMLForInvalidPartitionedPluginObject[] = "<object id='browserplugin' width='640px' height='480px'" " type='%s' partition='persist:'>"; const char kHTMLForPartitionedPersistedPluginObject[] = "<object id='browserplugin' width='640px' height='480px'" " src='foo' type='%s' partition='persist:someid'>"; std::string GetHTMLForBrowserPluginObject() { return base::StringPrintf(kHTMLForBrowserPluginObject, content::kBrowserPluginMimeType); } } // namespace namespace content { class TestContentRendererClient : public ContentRendererClient { public: TestContentRendererClient() : ContentRendererClient() { } virtual ~TestContentRendererClient() { } virtual bool AllowBrowserPlugin(WebKit::WebPluginContainer* container) const OVERRIDE { // Allow BrowserPlugin for tests. return true; } }; // Test factory for creating test instances of BrowserPluginManager. class TestBrowserPluginManagerFactory : public BrowserPluginManagerFactory { public: virtual MockBrowserPluginManager* CreateBrowserPluginManager( RenderViewImpl* render_view) OVERRIDE { return new MockBrowserPluginManager(render_view); } // Singleton getter. static TestBrowserPluginManagerFactory* GetInstance() { return Singleton<TestBrowserPluginManagerFactory>::get(); } protected: TestBrowserPluginManagerFactory() {} virtual ~TestBrowserPluginManagerFactory() {} private: // For Singleton. friend struct DefaultSingletonTraits<TestBrowserPluginManagerFactory>; DISALLOW_COPY_AND_ASSIGN(TestBrowserPluginManagerFactory); }; BrowserPluginTest::BrowserPluginTest() {} BrowserPluginTest::~BrowserPluginTest() {} void BrowserPluginTest::SetUp() { test_content_renderer_client_.reset(new TestContentRendererClient); SetRendererClientForTesting(test_content_renderer_client_.get()); BrowserPluginManager::set_factory_for_testing( TestBrowserPluginManagerFactory::GetInstance()); content::RenderViewTest::SetUp(); } void BrowserPluginTest::TearDown() { BrowserPluginManager::set_factory_for_testing( TestBrowserPluginManagerFactory::GetInstance()); content::RenderViewTest::TearDown(); test_content_renderer_client_.reset(); } std::string BrowserPluginTest::ExecuteScriptAndReturnString( const std::string& script) { v8::HandleScope handle_scope; v8::Handle<v8::Value> value = GetMainFrame()->executeScriptAndReturnValue( WebKit::WebScriptSource(WebKit::WebString::fromUTF8(script.c_str()))); if (value.IsEmpty() || !value->IsString()) return std::string(); v8::Local<v8::String> v8_str = value->ToString(); int length = v8_str->Utf8Length() + 1; scoped_ptr<char[]> str(new char[length]); v8_str->WriteUtf8(str.get(), length); return str.get(); } int BrowserPluginTest::ExecuteScriptAndReturnInt( const std::string& script) { v8::HandleScope handle_scope; v8::Handle<v8::Value> value = GetMainFrame()->executeScriptAndReturnValue( WebKit::WebScriptSource(WebKit::WebString::fromUTF8(script.c_str()))); if (value.IsEmpty() || !value->IsInt32()) return 0; return value->Int32Value(); } // A return value of false means that a value was not present. The return value // of the script is stored in |result| bool BrowserPluginTest::ExecuteScriptAndReturnBool( const std::string& script, bool* result) { v8::HandleScope handle_scope; v8::Handle<v8::Value> value = GetMainFrame()->executeScriptAndReturnValue( WebKit::WebScriptSource(WebKit::WebString::fromUTF8(script.c_str()))); if (value.IsEmpty() || !value->IsBoolean()) return false; *result = value->BooleanValue(); return true; } MockBrowserPlugin* BrowserPluginTest::GetCurrentPlugin() { BrowserPluginHostMsg_Attach_Params params; return GetCurrentPluginWithAttachParams(¶ms); } MockBrowserPlugin* BrowserPluginTest::GetCurrentPluginWithAttachParams( BrowserPluginHostMsg_Attach_Params* params) { int instance_id = 0; const IPC::Message* msg = browser_plugin_manager()->sink().GetUniqueMessageMatching( BrowserPluginHostMsg_Attach::ID); if (!msg) return NULL; BrowserPluginHostMsg_Attach::Read(msg, &instance_id, params); return static_cast<MockBrowserPlugin*>( browser_plugin_manager()->GetBrowserPlugin(instance_id)); } // This test verifies that an initial resize occurs when we instantiate the // browser plugin. This test also verifies that the browser plugin is waiting // for a BrowserPluginMsg_UpdateRect in response. We issue an UpdateRect, and // we observe an UpdateRect_ACK, with the |pending_damage_buffer_| reset, // indiciating that the BrowserPlugin is not waiting for any more UpdateRects to // satisfy its resize request. TEST_F(BrowserPluginTest, InitialResize) { LoadHTML(GetHTMLForBrowserPluginObject().c_str()); // Verify that the information in Attach is correct. BrowserPluginHostMsg_Attach_Params params; MockBrowserPlugin* browser_plugin = GetCurrentPluginWithAttachParams(¶ms); EXPECT_EQ(640, params.resize_guest_params.view_rect.width()); EXPECT_EQ(480, params.resize_guest_params.view_rect.height()); ASSERT_TRUE(browser_plugin); // Now the browser plugin is expecting a UpdateRect resize. int instance_id = browser_plugin->instance_id(); EXPECT_TRUE(browser_plugin->pending_damage_buffer_.get()); // Send the BrowserPlugin an UpdateRect equal to its container size with // the same damage buffer. That should clear |pending_damage_buffer_|. BrowserPluginMsg_UpdateRect_Params update_rect_params; update_rect_params.damage_buffer_sequence_id = browser_plugin->damage_buffer_sequence_id_; update_rect_params.view_size = gfx::Size(640, 480); update_rect_params.scale_factor = 1.0f; update_rect_params.is_resize_ack = true; update_rect_params.needs_ack = true; BrowserPluginMsg_UpdateRect msg(instance_id, update_rect_params); browser_plugin->OnMessageReceived(msg); EXPECT_FALSE(browser_plugin->pending_damage_buffer_.get()); } // This test verifies that all attributes (present at the time of writing) are // parsed on initialization. However, this test does minimal checking of // correct behavior. TEST_F(BrowserPluginTest, ParseAllAttributes) { std::string html = base::StringPrintf(kHTMLForBrowserPluginWithAllAttributes, content::kBrowserPluginMimeType); LoadHTML(html.c_str()); bool result; bool has_value = ExecuteScriptAndReturnBool( "document.getElementById('browserplugin').autosize", &result); EXPECT_TRUE(has_value); EXPECT_TRUE(result); int maxHeight = ExecuteScriptAndReturnInt( "document.getElementById('browserplugin').maxheight"); EXPECT_EQ(600, maxHeight); int maxWidth = ExecuteScriptAndReturnInt( "document.getElementById('browserplugin').maxwidth"); EXPECT_EQ(800, maxWidth); int minHeight = ExecuteScriptAndReturnInt( "document.getElementById('browserplugin').minheight"); EXPECT_EQ(240, minHeight); int minWidth = ExecuteScriptAndReturnInt( "document.getElementById('browserplugin').minwidth"); EXPECT_EQ(320, minWidth); std::string name = ExecuteScriptAndReturnString( "document.getElementById('browserplugin').name"); EXPECT_STREQ("Jim", name.c_str()); std::string partition = ExecuteScriptAndReturnString( "document.getElementById('browserplugin').partition"); EXPECT_STREQ("someid", partition.c_str()); std::string src = ExecuteScriptAndReturnString( "document.getElementById('browserplugin').src"); EXPECT_STREQ("foo", src.c_str()); } // Verify that the src attribute on the browser plugin works as expected. TEST_F(BrowserPluginTest, SrcAttribute) { LoadHTML(GetHTMLForBrowserPluginObject().c_str()); // Verify that we're reporting the correct URL to navigate to based on the // src attribute. { BrowserPluginHostMsg_Attach_Params params; MockBrowserPlugin* browser_plugin = GetCurrentPluginWithAttachParams(¶ms); ASSERT_TRUE(browser_plugin); EXPECT_EQ("foo", params.src); } browser_plugin_manager()->sink().ClearMessages(); // Navigate to bar and observe the associated // BrowserPluginHostMsg_NavigateGuest message. // Verify that the src attribute is updated as well. ExecuteJavaScript("document.getElementById('browserplugin').src = 'bar'"); { // Verify that we do not get a Attach on subsequent navigations. const IPC::Message* create_msg = browser_plugin_manager()->sink().GetUniqueMessageMatching( BrowserPluginHostMsg_Attach::ID); ASSERT_FALSE(create_msg); const IPC::Message* msg = browser_plugin_manager()->sink().GetUniqueMessageMatching( BrowserPluginHostMsg_NavigateGuest::ID); ASSERT_TRUE(msg); int instance_id = 0; std::string src; BrowserPluginHostMsg_NavigateGuest::Read(msg, &instance_id, &src); EXPECT_EQ("bar", src); std::string src_value = ExecuteScriptAndReturnString( "document.getElementById('browserplugin').src"); EXPECT_EQ("bar", src_value); } } TEST_F(BrowserPluginTest, ResizeFlowControl) { LoadHTML(GetHTMLForBrowserPluginObject().c_str()); MockBrowserPlugin* browser_plugin = GetCurrentPlugin(); ASSERT_TRUE(browser_plugin); int instance_id = browser_plugin->instance_id(); EXPECT_TRUE(browser_plugin->pending_damage_buffer_.get()); // Send an UpdateRect to the BrowserPlugin to make it use the pending damage // buffer. { // We send a stale UpdateRect to the BrowserPlugin. BrowserPluginMsg_UpdateRect_Params update_rect_params; update_rect_params.view_size = gfx::Size(640, 480); update_rect_params.scale_factor = 1.0f; update_rect_params.is_resize_ack = true; update_rect_params.needs_ack = true; // By sending |damage_buffer_sequence_id| back to BrowserPlugin on // UpdateRect, then the BrowserPlugin knows that the browser process has // received and has begun to use the |pending_damage_buffer_|. update_rect_params.damage_buffer_sequence_id = browser_plugin->damage_buffer_sequence_id_; BrowserPluginMsg_UpdateRect msg(instance_id, update_rect_params); browser_plugin->OnMessageReceived(msg); EXPECT_EQ(NULL, browser_plugin->pending_damage_buffer_.get()); } browser_plugin_manager()->sink().ClearMessages(); // Resize the browser plugin three times. ExecuteJavaScript("document.getElementById('browserplugin').width = '641px'"); ProcessPendingMessages(); ExecuteJavaScript("document.getElementById('browserplugin').width = '642px'"); ProcessPendingMessages(); ExecuteJavaScript("document.getElementById('browserplugin').width = '643px'"); ProcessPendingMessages(); // Expect to see one resize messsage in the sink. BrowserPlugin will not issue // subsequent resize requests until the first request is satisfied by the // guest. The rest of the messages could be // BrowserPluginHostMsg_UpdateGeometry msgs. EXPECT_LE(1u, browser_plugin_manager()->sink().message_count()); for (size_t i = 0; i < browser_plugin_manager()->sink().message_count(); ++i) { const IPC::Message* msg = browser_plugin_manager()->sink().GetMessageAt(i); if (msg->type() != BrowserPluginHostMsg_ResizeGuest::ID) EXPECT_EQ(msg->type(), BrowserPluginHostMsg_UpdateGeometry::ID); } const IPC::Message* msg = browser_plugin_manager()->sink().GetUniqueMessageMatching( BrowserPluginHostMsg_ResizeGuest::ID); ASSERT_TRUE(msg); BrowserPluginHostMsg_ResizeGuest_Params params; BrowserPluginHostMsg_ResizeGuest::Read(msg, &instance_id, ¶ms); EXPECT_EQ(641, params.view_rect.width()); EXPECT_EQ(480, params.view_rect.height()); // This indicates that the BrowserPlugin has sent out a previous resize // request but has not yet received an UpdateRect for that request. EXPECT_TRUE(browser_plugin->pending_damage_buffer_.get()); { // We send a stale UpdateRect to the BrowserPlugin. BrowserPluginMsg_UpdateRect_Params update_rect_params; update_rect_params.view_size = gfx::Size(641, 480); update_rect_params.scale_factor = 1.0f; update_rect_params.is_resize_ack = true; update_rect_params.needs_ack = true; update_rect_params.damage_buffer_sequence_id = browser_plugin->damage_buffer_sequence_id_; BrowserPluginMsg_UpdateRect msg(instance_id, update_rect_params); browser_plugin->OnMessageReceived(msg); // This tells us that the BrowserPlugin is still expecting another // UpdateRect with the most recent size. EXPECT_TRUE(browser_plugin->pending_damage_buffer_.get()); } // Send the BrowserPlugin another UpdateRect, but this time with a size // that matches the size of the container. { BrowserPluginMsg_UpdateRect_Params update_rect_params; update_rect_params.view_size = gfx::Size(643, 480); update_rect_params.scale_factor = 1.0f; update_rect_params.is_resize_ack = true; update_rect_params.needs_ack = true; update_rect_params.damage_buffer_sequence_id = browser_plugin->damage_buffer_sequence_id_; BrowserPluginMsg_UpdateRect msg(instance_id, update_rect_params); browser_plugin->OnMessageReceived(msg); // The BrowserPlugin has finally received an UpdateRect that satisifes // its current size, and so it is happy. EXPECT_FALSE(browser_plugin->pending_damage_buffer_.get()); } } TEST_F(BrowserPluginTest, GuestCrash) { LoadHTML(GetHTMLForBrowserPluginObject().c_str()); MockBrowserPlugin* browser_plugin = GetCurrentPlugin(); ASSERT_TRUE(browser_plugin); WebKit::WebCursorInfo cursor_info; // Send an event and verify that the event is deported. browser_plugin->handleInputEvent(WebKit::WebMouseEvent(), cursor_info); EXPECT_TRUE(browser_plugin_manager()->sink().GetUniqueMessageMatching( BrowserPluginHostMsg_HandleInputEvent::ID)); browser_plugin_manager()->sink().ClearMessages(); const char* kAddEventListener = "var msg;" "function exitListener(e) {" " msg = JSON.parse(e.detail).reason;" "}" "document.getElementById('browserplugin')." " addEventListener('-internal-exit', exitListener);"; ExecuteJavaScript(kAddEventListener); // Pretend that the guest has terminated normally. { BrowserPluginMsg_GuestGone msg( 0, 0, base::TERMINATION_STATUS_NORMAL_TERMINATION); browser_plugin->OnMessageReceived(msg); } // Verify that our event listener has fired. EXPECT_EQ("normal", ExecuteScriptAndReturnString("msg")); // Pretend that the guest has crashed. { BrowserPluginMsg_GuestGone msg( 0, 0, base::TERMINATION_STATUS_PROCESS_CRASHED); browser_plugin->OnMessageReceived(msg); } // Verify that our event listener has fired. EXPECT_EQ("crashed", ExecuteScriptAndReturnString("msg")); // Send an event and verify that events are no longer deported. browser_plugin->handleInputEvent(WebKit::WebMouseEvent(), cursor_info); EXPECT_FALSE(browser_plugin_manager()->sink().GetUniqueMessageMatching( BrowserPluginHostMsg_HandleInputEvent::ID)); } TEST_F(BrowserPluginTest, RemovePlugin) { LoadHTML(GetHTMLForBrowserPluginObject().c_str()); EXPECT_FALSE(browser_plugin_manager()->sink().GetUniqueMessageMatching( BrowserPluginHostMsg_PluginDestroyed::ID)); ExecuteJavaScript("x = document.getElementById('browserplugin'); " "x.parentNode.removeChild(x);"); ProcessPendingMessages(); EXPECT_TRUE(browser_plugin_manager()->sink().GetUniqueMessageMatching( BrowserPluginHostMsg_PluginDestroyed::ID)); } // This test verifies that PluginDestroyed messages do not get sent from a // BrowserPlugin that has never navigated. TEST_F(BrowserPluginTest, RemovePluginBeforeNavigation) { std::string html = base::StringPrintf(kHTMLForSourcelessPluginObject, content::kBrowserPluginMimeType); LoadHTML(html.c_str()); EXPECT_FALSE(browser_plugin_manager()->sink().GetUniqueMessageMatching( BrowserPluginHostMsg_PluginDestroyed::ID)); ExecuteJavaScript("x = document.getElementById('browserplugin'); " "x.parentNode.removeChild(x);"); ProcessPendingMessages(); EXPECT_FALSE(browser_plugin_manager()->sink().GetUniqueMessageMatching( BrowserPluginHostMsg_PluginDestroyed::ID)); } TEST_F(BrowserPluginTest, CustomEvents) { const char* kAddEventListener = "var url;" "function nav(e) {" " url = JSON.parse(e.detail).url;" "}" "document.getElementById('browserplugin')." " addEventListener('-internal-loadcommit', nav);"; const char* kRemoveEventListener = "document.getElementById('browserplugin')." " removeEventListener('-internal-loadcommit', nav);"; const char* kGetSrc = "document.getElementById('browserplugin').src"; const char* kGoogleURL = "http://www.google.com/"; const char* kGoogleNewsURL = "http://news.google.com/"; LoadHTML(GetHTMLForBrowserPluginObject().c_str()); ExecuteJavaScript(kAddEventListener); MockBrowserPlugin* browser_plugin = GetCurrentPlugin(); ASSERT_TRUE(browser_plugin); int instance_id = browser_plugin->instance_id(); { BrowserPluginMsg_LoadCommit_Params navigate_params; navigate_params.is_top_level = true; navigate_params.url = GURL(kGoogleURL); BrowserPluginMsg_LoadCommit msg(instance_id, navigate_params); browser_plugin->OnMessageReceived(msg); EXPECT_EQ(kGoogleURL, ExecuteScriptAndReturnString("url")); EXPECT_EQ(kGoogleURL, ExecuteScriptAndReturnString(kGetSrc)); } ExecuteJavaScript(kRemoveEventListener); { BrowserPluginMsg_LoadCommit_Params navigate_params; navigate_params.is_top_level = false; navigate_params.url = GURL(kGoogleNewsURL); BrowserPluginMsg_LoadCommit msg(instance_id, navigate_params); browser_plugin->OnMessageReceived(msg); // The URL variable should not change because we've removed the event // listener. EXPECT_EQ(kGoogleURL, ExecuteScriptAndReturnString("url")); // The src attribute should not change if this is a top-level navigation. EXPECT_EQ(kGoogleURL, ExecuteScriptAndReturnString(kGetSrc)); } } TEST_F(BrowserPluginTest, StopMethod) { const char* kCallStop = "document.getElementById('browserplugin').stop();"; LoadHTML(GetHTMLForBrowserPluginObject().c_str()); ExecuteJavaScript(kCallStop); EXPECT_TRUE(browser_plugin_manager()->sink().GetUniqueMessageMatching( BrowserPluginHostMsg_Stop::ID)); } TEST_F(BrowserPluginTest, ReloadMethod) { const char* kCallReload = "document.getElementById('browserplugin').reload();"; LoadHTML(GetHTMLForBrowserPluginObject().c_str()); ExecuteJavaScript(kCallReload); EXPECT_TRUE(browser_plugin_manager()->sink().GetUniqueMessageMatching( BrowserPluginHostMsg_Reload::ID)); } // Verify that the 'partition' attribute on the browser plugin is parsed // correctly. TEST_F(BrowserPluginTest, PartitionAttribute) { std::string html = base::StringPrintf(kHTMLForPartitionedPluginObject, content::kBrowserPluginMimeType); LoadHTML(html.c_str()); std::string partition_value = ExecuteScriptAndReturnString( "document.getElementById('browserplugin').partition"); EXPECT_STREQ("someid", partition_value.c_str()); html = base::StringPrintf(kHTMLForPartitionedPersistedPluginObject, content::kBrowserPluginMimeType); LoadHTML(html.c_str()); partition_value = ExecuteScriptAndReturnString( "document.getElementById('browserplugin').partition"); EXPECT_STREQ("persist:someid", partition_value.c_str()); // Verify that once HTML has defined a source and partition, we cannot change // the partition anymore. ExecuteJavaScript( "try {" " document.getElementById('browserplugin').partition = 'foo';" " document.title = 'success';" "} catch (e) { document.title = e.message; }"); std::string title = ExecuteScriptAndReturnString("document.title"); EXPECT_STREQ( "The object has already navigated, so its partition cannot be changed.", title.c_str()); // Load a browser tag without 'src' defined. html = base::StringPrintf(kHTMLForSourcelessPluginObject, content::kBrowserPluginMimeType); LoadHTML(html.c_str()); // Ensure we don't parse just "persist:" string and return exception. ExecuteJavaScript( "try {" " document.getElementById('browserplugin').partition = 'persist:';" " document.title = 'success';" "} catch (e) { document.title = e.message; }"); title = ExecuteScriptAndReturnString("document.title"); EXPECT_STREQ("Invalid partition attribute.", title.c_str()); } // This test verifies that BrowserPlugin enters an error state when the // partition attribute is invalid. TEST_F(BrowserPluginTest, InvalidPartition) { std::string html = base::StringPrintf(kHTMLForInvalidPartitionedPluginObject, content::kBrowserPluginMimeType); LoadHTML(html.c_str()); // Attempt to navigate with an invalid partition. { ExecuteJavaScript( "try {" " document.getElementById('browserplugin').src = 'bar';" " document.title = 'success';" "} catch (e) { document.title = e.message; }"); std::string title = ExecuteScriptAndReturnString("document.title"); EXPECT_STREQ("Invalid partition attribute.", title.c_str()); // Verify that the 'src' attribute has not been updated. EXPECT_EQ("", ExecuteScriptAndReturnString( "document.getElementById('browserplugin').src")); } // Verify that the BrowserPlugin accepts changes to its src attribue after // setting the partition to a valid value. ExecuteJavaScript( "document.getElementById('browserplugin').partition = 'persist:foo'"); ExecuteJavaScript("document.getElementById('browserplugin').src = 'bar'"); EXPECT_EQ("bar", ExecuteScriptAndReturnString( "document.getElementById('browserplugin').src")); ProcessPendingMessages(); // Verify that the BrowserPlugin does not 'deadlock': it can recover from // the partition ID error state. { ExecuteJavaScript( "try {" " document.getElementById('browserplugin').partition = 'persist:1337';" " document.title = 'success';" "} catch (e) { document.title = e.message; }"); std::string title = ExecuteScriptAndReturnString("document.title"); EXPECT_STREQ( "The object has already navigated, so its partition cannot be changed.", title.c_str()); ExecuteJavaScript("document.getElementById('browserplugin').src = '42'"); EXPECT_EQ("42", ExecuteScriptAndReturnString( "document.getElementById('browserplugin').src")); } } // Test to verify that after the first navigation, the partition attribute // cannot be modified. TEST_F(BrowserPluginTest, ImmutableAttributesAfterNavigation) { std::string html = base::StringPrintf(kHTMLForSourcelessPluginObject, content::kBrowserPluginMimeType); LoadHTML(html.c_str()); ExecuteJavaScript( "document.getElementById('browserplugin').partition = 'storage'"); std::string partition_value = ExecuteScriptAndReturnString( "document.getElementById('browserplugin').partition"); EXPECT_STREQ("storage", partition_value.c_str()); std::string src_value = ExecuteScriptAndReturnString( "document.getElementById('browserplugin').src"); EXPECT_STREQ("", src_value.c_str()); ExecuteJavaScript("document.getElementById('browserplugin').src = 'bar'"); ProcessPendingMessages(); { BrowserPluginHostMsg_Attach_Params params; MockBrowserPlugin* browser_plugin = GetCurrentPluginWithAttachParams(¶ms); ASSERT_TRUE(browser_plugin); EXPECT_STREQ("storage", params.storage_partition_id.c_str()); EXPECT_FALSE(params.persist_storage); EXPECT_STREQ("bar", params.src.c_str()); } // Setting the partition should throw an exception and the value should not // change. ExecuteJavaScript( "try {" " document.getElementById('browserplugin').partition = 'someid';" " document.title = 'success';" "} catch (e) { document.title = e.message; }"); std::string title = ExecuteScriptAndReturnString("document.title"); EXPECT_STREQ( "The object has already navigated, so its partition cannot be changed.", title.c_str()); partition_value = ExecuteScriptAndReturnString( "document.getElementById('browserplugin').partition"); EXPECT_STREQ("storage", partition_value.c_str()); } // This test verifies that we can mutate the event listener vector // within an event listener. TEST_F(BrowserPluginTest, RemoveEventListenerInEventListener) { const char* kAddEventListener = "var url;" "function nav(e) {" " url = JSON.parse(e.detail).url;" " document.getElementById('browserplugin')." " removeEventListener('-internal-loadcommit', nav);" "}" "document.getElementById('browserplugin')." " addEventListener('-internal-loadcommit', nav);"; const char* kGoogleURL = "http://www.google.com/"; const char* kGoogleNewsURL = "http://news.google.com/"; LoadHTML(GetHTMLForBrowserPluginObject().c_str()); ExecuteJavaScript(kAddEventListener); MockBrowserPlugin* browser_plugin = GetCurrentPlugin(); ASSERT_TRUE(browser_plugin); int instance_id = browser_plugin->instance_id(); { BrowserPluginMsg_LoadCommit_Params navigate_params; navigate_params.url = GURL(kGoogleURL); BrowserPluginMsg_LoadCommit msg(instance_id, navigate_params); browser_plugin->OnMessageReceived(msg); EXPECT_EQ(kGoogleURL, ExecuteScriptAndReturnString("url")); } { BrowserPluginMsg_LoadCommit_Params navigate_params; navigate_params.url = GURL(kGoogleNewsURL); BrowserPluginMsg_LoadCommit msg(instance_id, navigate_params); browser_plugin->OnMessageReceived(msg); // The URL variable should not change because we've removed the event // listener. EXPECT_EQ(kGoogleURL, ExecuteScriptAndReturnString("url")); } } // This test verifies that multiple event listeners fire that are registered // on a single event type. TEST_F(BrowserPluginTest, MultipleEventListeners) { const char* kAddEventListener = "var count = 0;" "function nava(u) {" " count++;" "}" "function navb(u) {" " count++;" "}" "document.getElementById('browserplugin')." " addEventListener('-internal-loadcommit', nava);" "document.getElementById('browserplugin')." " addEventListener('-internal-loadcommit', navb);"; const char* kGoogleURL = "http://www.google.com/"; LoadHTML(GetHTMLForBrowserPluginObject().c_str()); ExecuteJavaScript(kAddEventListener); MockBrowserPlugin* browser_plugin = GetCurrentPlugin(); ASSERT_TRUE(browser_plugin); int instance_id = browser_plugin->instance_id(); { BrowserPluginMsg_LoadCommit_Params navigate_params; navigate_params.url = GURL(kGoogleURL); BrowserPluginMsg_LoadCommit msg(instance_id, navigate_params); browser_plugin->OnMessageReceived(msg); EXPECT_EQ(2, ExecuteScriptAndReturnInt("count")); } } TEST_F(BrowserPluginTest, RemoveBrowserPluginOnExit) { LoadHTML(GetHTMLForBrowserPluginObject().c_str()); MockBrowserPlugin* browser_plugin = GetCurrentPlugin(); ASSERT_TRUE(browser_plugin); int instance_id = browser_plugin->instance_id(); const char* kAddEventListener = "function exitListener(e) {" " if (JSON.parse(e.detail).reason == 'killed') {" " var bp = document.getElementById('browserplugin');" " bp.parentNode.removeChild(bp);" " }" "}" "document.getElementById('browserplugin')." " addEventListener('-internal-exit', exitListener);"; ExecuteJavaScript(kAddEventListener); // Pretend that the guest has crashed. BrowserPluginMsg_GuestGone msg( instance_id, 0, base::TERMINATION_STATUS_PROCESS_WAS_KILLED); browser_plugin->OnMessageReceived(msg); ProcessPendingMessages(); EXPECT_EQ(NULL, browser_plugin_manager()->GetBrowserPlugin(instance_id)); } TEST_F(BrowserPluginTest, AutoSizeAttributes) { std::string html = base::StringPrintf(kHTMLForSourcelessPluginObject, content::kBrowserPluginMimeType); LoadHTML(html.c_str()); const char* kSetAutoSizeParametersAndNavigate = "var browserplugin = document.getElementById('browserplugin');" "browserplugin.autosize = true;" "browserplugin.minwidth = 42;" "browserplugin.minheight = 43;" "browserplugin.maxwidth = 1337;" "browserplugin.maxheight = 1338;" "browserplugin.src = 'foobar';"; const char* kDisableAutoSize = "document.getElementById('browserplugin').removeAttribute('autosize');"; int instance_id = 0; // Set some autosize parameters before navigating then navigate. // Verify that the BrowserPluginHostMsg_Attach message contains // the correct autosize parameters. ExecuteJavaScript(kSetAutoSizeParametersAndNavigate); ProcessPendingMessages(); BrowserPluginHostMsg_Attach_Params params; MockBrowserPlugin* browser_plugin = GetCurrentPluginWithAttachParams(¶ms); ASSERT_TRUE(browser_plugin); EXPECT_TRUE(params.auto_size_params.enable); EXPECT_EQ(42, params.auto_size_params.min_size.width()); EXPECT_EQ(43, params.auto_size_params.min_size.height()); EXPECT_EQ(1337, params.auto_size_params.max_size.width()); EXPECT_EQ(1338, params.auto_size_params.max_size.height()); // Verify that we are waiting for the browser process to grab the new // damage buffer. EXPECT_TRUE(browser_plugin->pending_damage_buffer_.get()); // Disable autosize. AutoSize state will not be sent to the guest until // the guest has responded to the last resize request. ExecuteJavaScript(kDisableAutoSize); ProcessPendingMessages(); const IPC::Message* auto_size_msg = browser_plugin_manager()->sink().GetUniqueMessageMatching( BrowserPluginHostMsg_SetAutoSize::ID); EXPECT_FALSE(auto_size_msg); // Send the BrowserPlugin an UpdateRect equal to its |max_size| with // the same damage buffer. BrowserPluginMsg_UpdateRect_Params update_rect_params; update_rect_params.damage_buffer_sequence_id = browser_plugin->damage_buffer_sequence_id_; update_rect_params.view_size = gfx::Size(1337, 1338); update_rect_params.scale_factor = 1.0f; update_rect_params.is_resize_ack = true; update_rect_params.needs_ack = true; BrowserPluginMsg_UpdateRect msg(instance_id, update_rect_params); browser_plugin->OnMessageReceived(msg); // Verify that the autosize state has been updated. { const IPC::Message* auto_size_msg = browser_plugin_manager()->sink().GetUniqueMessageMatching( BrowserPluginHostMsg_UpdateRect_ACK::ID); ASSERT_TRUE(auto_size_msg); int instance_id = 0; BrowserPluginHostMsg_AutoSize_Params auto_size_params; BrowserPluginHostMsg_ResizeGuest_Params resize_params; BrowserPluginHostMsg_UpdateRect_ACK::Read(auto_size_msg, &instance_id, &auto_size_params, &resize_params); EXPECT_FALSE(auto_size_params.enable); // These value are not populated (as an optimization) if autosize is // disabled. EXPECT_EQ(0, auto_size_params.min_size.width()); EXPECT_EQ(0, auto_size_params.min_size.height()); EXPECT_EQ(0, auto_size_params.max_size.width()); EXPECT_EQ(0, auto_size_params.max_size.height()); } } } // namespace content