| // 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/plugin_service_impl.h" |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/command_line.h" |
| #include "base/path_service.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/plugin_service_filter.h" |
| #include "content/public/browser/resource_context.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/test_browser_thread.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| |
| namespace content { |
| |
| const char kNPAPITestPluginMimeType[] = "application/vnd.npapi-test"; |
| |
| void OpenChannel(PluginProcessHost::Client* client) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| // Start opening the channel |
| PluginServiceImpl::GetInstance()->OpenChannelToNpapiPlugin( |
| 0, 0, GURL(), GURL(), kNPAPITestPluginMimeType, client); |
| } |
| |
| // Mock up of the Client and the Listener classes that would supply the |
| // communication channel with the plugin. |
| class MockPluginProcessHostClient : public PluginProcessHost::Client, |
| public IPC::Listener { |
| public: |
| MockPluginProcessHostClient(ResourceContext* context, bool expect_fail) |
| : context_(context), |
| channel_(NULL), |
| set_plugin_info_called_(false), |
| expect_fail_(expect_fail) { |
| } |
| |
| ~MockPluginProcessHostClient() override { |
| if (channel_) |
| BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, channel_); |
| } |
| |
| // PluginProcessHost::Client implementation. |
| int ID() override { return 42; } |
| bool OffTheRecord() override { return false; } |
| ResourceContext* GetResourceContext() override { return context_; } |
| void OnFoundPluginProcessHost(PluginProcessHost* host) override {} |
| void OnSentPluginChannelRequest() override {} |
| |
| void OnChannelOpened(const IPC::ChannelHandle& handle) override { |
| ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| ASSERT_TRUE(set_plugin_info_called_); |
| ASSERT_TRUE(!channel_); |
| channel_ = IPC::Channel::CreateClient(handle, this).release(); |
| ASSERT_TRUE(channel_->Connect()); |
| } |
| |
| void SetPluginInfo(const WebPluginInfo& info) override { |
| ASSERT_TRUE(info.mime_types.size()); |
| ASSERT_EQ(kNPAPITestPluginMimeType, info.mime_types[0].mime_type); |
| set_plugin_info_called_ = true; |
| } |
| |
| void OnError() override { Fail(); } |
| |
| // IPC::Listener implementation. |
| bool OnMessageReceived(const IPC::Message& message) override { |
| Fail(); |
| return false; |
| } |
| void OnChannelConnected(int32 peer_pid) override { |
| if (expect_fail_) |
| FAIL(); |
| QuitMessageLoop(); |
| } |
| void OnChannelError() override { Fail(); } |
| #if defined(OS_POSIX) |
| void OnChannelDenied() override { Fail(); } |
| void OnChannelListenError() override { Fail(); } |
| #endif |
| |
| private: |
| void Fail() { |
| if (!expect_fail_) |
| FAIL(); |
| QuitMessageLoop(); |
| } |
| |
| void QuitMessageLoop() { |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure()); |
| } |
| |
| ResourceContext* context_; |
| IPC::Channel* channel_; |
| bool set_plugin_info_called_; |
| bool expect_fail_; |
| DISALLOW_COPY_AND_ASSIGN(MockPluginProcessHostClient); |
| }; |
| |
| class MockPluginServiceFilter : public content::PluginServiceFilter { |
| public: |
| MockPluginServiceFilter() {} |
| |
| bool IsPluginAvailable(int render_process_id, |
| int render_view_id, |
| const void* context, |
| const GURL& url, |
| const GURL& policy_url, |
| WebPluginInfo* plugin) override { |
| return true; |
| } |
| |
| bool CanLoadPlugin(int render_process_id, |
| const base::FilePath& path) override { |
| return false; |
| } |
| }; |
| |
| class PluginServiceTest : public ContentBrowserTest { |
| public: |
| PluginServiceTest() {} |
| |
| ResourceContext* GetResourceContext() { |
| return shell()->web_contents()->GetBrowserContext()->GetResourceContext(); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| #if defined(OS_MACOSX) |
| base::FilePath browser_directory; |
| PathService::Get(base::DIR_MODULE, &browser_directory); |
| command_line->AppendSwitchPath(switches::kExtraPluginDir, |
| browser_directory.AppendASCII("plugins")); |
| #endif |
| // TODO(jam): since these plugin tests are running under Chrome, we need to |
| // tell it to disable its security features for old plugins. Once this is |
| // running under content_browsertests, these flags won't be needed. |
| // http://crbug.com/90448 |
| // switches::kAlwaysAuthorizePlugins |
| command_line->AppendSwitch("always-authorize-plugins"); |
| } |
| }; |
| |
| // Try to open a channel to the test plugin. Minimal plugin process spawning |
| // test for the PluginService interface. |
| IN_PROC_BROWSER_TEST_F(PluginServiceTest, OpenChannelToPlugin) { |
| if (!PluginServiceImpl::GetInstance()->NPAPIPluginsSupported()) |
| return; |
| MockPluginProcessHostClient mock_client(GetResourceContext(), false); |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&OpenChannel, &mock_client)); |
| RunMessageLoop(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PluginServiceTest, OpenChannelToDeniedPlugin) { |
| if (!PluginServiceImpl::GetInstance()->NPAPIPluginsSupported()) |
| return; |
| MockPluginServiceFilter filter; |
| PluginServiceImpl::GetInstance()->SetFilter(&filter); |
| MockPluginProcessHostClient mock_client(GetResourceContext(), true); |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&OpenChannel, &mock_client)); |
| RunMessageLoop(); |
| } |
| |
| // A strict mock that fails if any of the methods are called. They shouldn't be |
| // called since the request should get canceled before then. |
| class MockCanceledPluginServiceClient : public PluginProcessHost::Client { |
| public: |
| MockCanceledPluginServiceClient(ResourceContext* context) |
| : context_(context), |
| get_resource_context_called_(false) { |
| } |
| |
| virtual ~MockCanceledPluginServiceClient() {} |
| |
| // Client implementation. |
| MOCK_METHOD0(ID, int()); |
| virtual ResourceContext* GetResourceContext() override { |
| get_resource_context_called_ = true; |
| return context_; |
| } |
| MOCK_METHOD0(OffTheRecord, bool()); |
| MOCK_METHOD1(OnFoundPluginProcessHost, void(PluginProcessHost* host)); |
| MOCK_METHOD0(OnSentPluginChannelRequest, void()); |
| MOCK_METHOD1(OnChannelOpened, void(const IPC::ChannelHandle& handle)); |
| MOCK_METHOD1(SetPluginInfo, void(const WebPluginInfo& info)); |
| MOCK_METHOD0(OnError, void()); |
| |
| bool get_resource_context_called() const { |
| return get_resource_context_called_; |
| } |
| |
| private: |
| ResourceContext* context_; |
| bool get_resource_context_called_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockCanceledPluginServiceClient); |
| }; |
| |
| void QuitUIMessageLoopFromIOThread() { |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure()); |
| } |
| |
| void OpenChannelAndThenCancel(PluginProcessHost::Client* client) { |
| OpenChannel(client); |
| // Immediately cancel it. This is guaranteed to work since PluginService needs |
| // to consult its filter on the FILE thread. |
| PluginServiceImpl::GetInstance()->CancelOpenChannelToNpapiPlugin(client); |
| // Before we terminate the test, add a roundtrip through the FILE thread to |
| // make sure that it's had a chance to post back to the IO thread. Then signal |
| // the UI thread to stop and exit the test. |
| BrowserThread::PostTaskAndReply( |
| BrowserThread::FILE, FROM_HERE, |
| base::Bind(&base::DoNothing), |
| base::Bind(&QuitUIMessageLoopFromIOThread)); |
| } |
| |
| // Should not attempt to open a channel, since it should be canceled early on. |
| IN_PROC_BROWSER_TEST_F(PluginServiceTest, CancelOpenChannelToPluginService) { |
| ::testing::StrictMock<MockCanceledPluginServiceClient> mock_client( |
| GetResourceContext()); |
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| base::Bind(OpenChannelAndThenCancel, &mock_client)); |
| RunMessageLoop(); |
| EXPECT_TRUE(mock_client.get_resource_context_called()); |
| } |
| |
| class MockCanceledBeforeSentPluginProcessHostClient |
| : public MockCanceledPluginServiceClient { |
| public: |
| MockCanceledBeforeSentPluginProcessHostClient( |
| ResourceContext* context) |
| : MockCanceledPluginServiceClient(context), |
| set_plugin_info_called_(false), |
| on_found_plugin_process_host_called_(false), |
| host_(NULL) {} |
| |
| ~MockCanceledBeforeSentPluginProcessHostClient() override {} |
| |
| // Client implementation. |
| void SetPluginInfo(const WebPluginInfo& info) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| ASSERT_TRUE(info.mime_types.size()); |
| ASSERT_EQ(kNPAPITestPluginMimeType, info.mime_types[0].mime_type); |
| set_plugin_info_called_ = true; |
| } |
| void OnFoundPluginProcessHost(PluginProcessHost* host) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| set_on_found_plugin_process_host_called(); |
| set_host(host); |
| // This gets called right before we request the plugin<=>renderer channel, |
| // so we have to post a task to cancel it. |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&PluginProcessHost::CancelPendingRequest, |
| base::Unretained(host), |
| this)); |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, base::Bind(&QuitUIMessageLoopFromIOThread)); |
| } |
| |
| bool set_plugin_info_called() const { |
| return set_plugin_info_called_; |
| } |
| |
| bool on_found_plugin_process_host_called() const { |
| return on_found_plugin_process_host_called_; |
| } |
| |
| protected: |
| void set_on_found_plugin_process_host_called() { |
| on_found_plugin_process_host_called_ = true; |
| } |
| void set_host(PluginProcessHost* host) { |
| host_ = host; |
| } |
| |
| PluginProcessHost* host() const { return host_; } |
| |
| private: |
| bool set_plugin_info_called_; |
| bool on_found_plugin_process_host_called_; |
| PluginProcessHost* host_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockCanceledBeforeSentPluginProcessHostClient); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F( |
| PluginServiceTest, CancelBeforeSentOpenChannelToPluginProcessHost) { |
| if (!PluginServiceImpl::GetInstance()->NPAPIPluginsSupported()) |
| return; |
| ::testing::StrictMock<MockCanceledBeforeSentPluginProcessHostClient> |
| mock_client(GetResourceContext()); |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&OpenChannel, &mock_client)); |
| RunMessageLoop(); |
| EXPECT_TRUE(mock_client.get_resource_context_called()); |
| EXPECT_TRUE(mock_client.set_plugin_info_called()); |
| EXPECT_TRUE(mock_client.on_found_plugin_process_host_called()); |
| } |
| |
| class MockCanceledAfterSentPluginProcessHostClient |
| : public MockCanceledBeforeSentPluginProcessHostClient { |
| public: |
| MockCanceledAfterSentPluginProcessHostClient( |
| ResourceContext* context) |
| : MockCanceledBeforeSentPluginProcessHostClient(context), |
| on_sent_plugin_channel_request_called_(false) {} |
| ~MockCanceledAfterSentPluginProcessHostClient() override {} |
| |
| // Client implementation. |
| |
| int ID() override { return 42; } |
| bool OffTheRecord() override { return false; } |
| |
| // We override this guy again since we don't want to cancel yet. |
| void OnFoundPluginProcessHost(PluginProcessHost* host) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| set_on_found_plugin_process_host_called(); |
| set_host(host); |
| } |
| |
| void OnSentPluginChannelRequest() override { |
| on_sent_plugin_channel_request_called_ = true; |
| host()->CancelSentRequest(this); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure()); |
| } |
| |
| bool on_sent_plugin_channel_request_called() const { |
| return on_sent_plugin_channel_request_called_; |
| } |
| |
| private: |
| bool on_sent_plugin_channel_request_called_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockCanceledAfterSentPluginProcessHostClient); |
| }; |
| |
| // Should not attempt to open a channel, since it should be canceled early on. |
| IN_PROC_BROWSER_TEST_F( |
| PluginServiceTest, CancelAfterSentOpenChannelToPluginProcessHost) { |
| if (!PluginServiceImpl::GetInstance()->NPAPIPluginsSupported()) |
| return; |
| ::testing::StrictMock<MockCanceledAfterSentPluginProcessHostClient> |
| mock_client(GetResourceContext()); |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&OpenChannel, &mock_client)); |
| RunMessageLoop(); |
| EXPECT_TRUE(mock_client.get_resource_context_called()); |
| EXPECT_TRUE(mock_client.set_plugin_info_called()); |
| EXPECT_TRUE(mock_client.on_found_plugin_process_host_called()); |
| EXPECT_TRUE(mock_client.on_sent_plugin_channel_request_called()); |
| } |
| |
| } // namespace content |