// 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/test_browser_thread.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace content {
const char kNPAPITestPluginMimeType[] = "application/vnd.npapi-test";
void OpenChannel(PluginProcessHost::Client* client) {
DCHECK(BrowserThread::CurrentlyOn(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) {
}
virtual ~MockPluginProcessHostClient() {
if (channel_)
BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, channel_);
}
// PluginProcessHost::Client implementation.
virtual int ID() OVERRIDE { return 42; }
virtual bool OffTheRecord() OVERRIDE { return false; }
virtual ResourceContext* GetResourceContext() OVERRIDE {
return context_;
}
virtual void OnFoundPluginProcessHost(PluginProcessHost* host) OVERRIDE {}
virtual void OnSentPluginChannelRequest() OVERRIDE {}
virtual void OnChannelOpened(const IPC::ChannelHandle& handle) OVERRIDE {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
ASSERT_TRUE(set_plugin_info_called_);
ASSERT_TRUE(!channel_);
channel_ = new IPC::Channel(handle, IPC::Channel::MODE_CLIENT, this);
ASSERT_TRUE(channel_->Connect());
}
virtual 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;
}
virtual void OnError() OVERRIDE {
Fail();
}
// IPC::Listener implementation.
virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
Fail();
return false;
}
virtual void OnChannelConnected(int32 peer_pid) OVERRIDE {
if (expect_fail_)
FAIL();
QuitMessageLoop();
}
virtual void OnChannelError() OVERRIDE {
Fail();
}
#if defined(OS_POSIX)
virtual void OnChannelDenied() OVERRIDE {
Fail();
}
virtual 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() {}
virtual 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; }
virtual 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();
}
virtual void SetUpCommandLine(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) {}
virtual ~MockCanceledBeforeSentPluginProcessHostClient() {}
// Client implementation.
virtual void SetPluginInfo(const WebPluginInfo& info) OVERRIDE {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
ASSERT_TRUE(info.mime_types.size());
ASSERT_EQ(kNPAPITestPluginMimeType, info.mime_types[0].mime_type);
set_plugin_info_called_ = true;
}
virtual void OnFoundPluginProcessHost(PluginProcessHost* host) OVERRIDE {
DCHECK(BrowserThread::CurrentlyOn(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) {}
virtual ~MockCanceledAfterSentPluginProcessHostClient() {}
// Client implementation.
virtual int ID() OVERRIDE { return 42; }
virtual bool OffTheRecord() OVERRIDE { return false; }
// We override this guy again since we don't want to cancel yet.
virtual void OnFoundPluginProcessHost(PluginProcessHost* host) OVERRIDE {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
set_on_found_plugin_process_host_called();
set_host(host);
}
virtual 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