1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/plugin_service_impl.h"
8 #include "base/bind_helpers.h"
9 #include "base/command_line.h"
10 #include "base/path_service.h"
11 #include "content/public/browser/browser_context.h"
12 #include "content/public/browser/plugin_service_filter.h"
13 #include "content/public/browser/resource_context.h"
14 #include "content/public/browser/web_contents.h"
15 #include "content/public/common/content_switches.h"
16 #include "content/public/test/test_browser_thread.h"
17 #include "content/public/test/test_utils.h"
18 #include "content/shell/shell.h"
19 #include "content/test/content_browser_test.h"
20 #include "testing/gmock/include/gmock/gmock.h"
21 #include "webkit/plugins/npapi/plugin_list.h"
22 #include "webkit/plugins/npapi/plugin_utils.h"
26 const char kNPAPITestPluginMimeType
[] = "application/vnd.npapi-test";
28 void OpenChannel(PluginProcessHost::Client
* client
) {
29 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
30 // Start opening the channel
31 PluginServiceImpl::GetInstance()->OpenChannelToNpapiPlugin(
32 0, 0, GURL(), GURL(), kNPAPITestPluginMimeType
, client
);
35 // Mock up of the Client and the Listener classes that would supply the
36 // communication channel with the plugin.
37 class MockPluginProcessHostClient
: public PluginProcessHost::Client
,
38 public IPC::Listener
{
40 MockPluginProcessHostClient(ResourceContext
* context
, bool expect_fail
)
43 set_plugin_info_called_(false),
44 expect_fail_(expect_fail
) {
47 virtual ~MockPluginProcessHostClient() {
49 BrowserThread::DeleteSoon(BrowserThread::IO
, FROM_HERE
, channel_
);
52 // PluginProcessHost::Client implementation.
53 virtual int ID() OVERRIDE
{ return 42; }
54 virtual bool OffTheRecord() OVERRIDE
{ return false; }
55 virtual ResourceContext
* GetResourceContext() OVERRIDE
{
58 virtual void OnFoundPluginProcessHost(PluginProcessHost
* host
) OVERRIDE
{}
59 virtual void OnSentPluginChannelRequest() OVERRIDE
{}
61 virtual void OnChannelOpened(const IPC::ChannelHandle
& handle
) OVERRIDE
{
62 ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO
));
63 ASSERT_TRUE(set_plugin_info_called_
);
64 ASSERT_TRUE(!channel_
);
65 channel_
= new IPC::Channel(handle
, IPC::Channel::MODE_CLIENT
, this);
66 ASSERT_TRUE(channel_
->Connect());
69 virtual void SetPluginInfo(const webkit::WebPluginInfo
& info
) OVERRIDE
{
70 ASSERT_TRUE(info
.mime_types
.size());
71 ASSERT_EQ(kNPAPITestPluginMimeType
, info
.mime_types
[0].mime_type
);
72 set_plugin_info_called_
= true;
75 virtual void OnError() OVERRIDE
{
79 // IPC::Listener implementation.
80 virtual bool OnMessageReceived(const IPC::Message
& message
) OVERRIDE
{
84 virtual void OnChannelConnected(int32 peer_pid
) OVERRIDE
{
89 virtual void OnChannelError() OVERRIDE
{
93 virtual void OnChannelDenied() OVERRIDE
{
96 virtual void OnChannelListenError() OVERRIDE
{
108 void QuitMessageLoop() {
109 BrowserThread::PostTask(
110 BrowserThread::UI
, FROM_HERE
, base::MessageLoop::QuitClosure());
113 ResourceContext
* context_
;
114 IPC::Channel
* channel_
;
115 bool set_plugin_info_called_
;
117 DISALLOW_COPY_AND_ASSIGN(MockPluginProcessHostClient
);
120 class MockPluginServiceFilter
: public content::PluginServiceFilter
{
122 MockPluginServiceFilter() {}
124 virtual bool IsPluginAvailable(
125 int render_process_id
,
129 const GURL
& policy_url
,
130 webkit::WebPluginInfo
* plugin
) OVERRIDE
{ return true; }
132 virtual bool CanLoadPlugin(
133 int render_process_id
,
134 const base::FilePath
& path
) OVERRIDE
{ return false; }
137 class PluginServiceTest
: public ContentBrowserTest
{
139 PluginServiceTest() {}
141 ResourceContext
* GetResourceContext() {
142 return shell()->web_contents()->GetBrowserContext()->GetResourceContext();
145 virtual void SetUpCommandLine(CommandLine
* command_line
) OVERRIDE
{
146 #if defined(OS_MACOSX)
147 base::FilePath browser_directory
;
148 PathService::Get(base::DIR_MODULE
, &browser_directory
);
149 command_line
->AppendSwitchPath(switches::kExtraPluginDir
,
150 browser_directory
.AppendASCII("plugins"));
152 // TODO(jam): since these plugin tests are running under Chrome, we need to
153 // tell it to disable its security features for old plugins. Once this is
154 // running under content_browsertests, these flags won't be needed.
155 // http://crbug.com/90448
156 // switches::kAlwaysAuthorizePlugins
157 command_line
->AppendSwitch("always-authorize-plugins");
161 // Try to open a channel to the test plugin. Minimal plugin process spawning
162 // test for the PluginService interface.
163 IN_PROC_BROWSER_TEST_F(PluginServiceTest
, OpenChannelToPlugin
) {
164 if (!webkit::npapi::NPAPIPluginsSupported())
166 MockPluginProcessHostClient
mock_client(GetResourceContext(), false);
167 BrowserThread::PostTask(
168 BrowserThread::IO
, FROM_HERE
,
169 base::Bind(&OpenChannel
, &mock_client
));
173 IN_PROC_BROWSER_TEST_F(PluginServiceTest
, OpenChannelToDeniedPlugin
) {
174 if (!webkit::npapi::NPAPIPluginsSupported())
176 MockPluginServiceFilter filter
;
177 PluginServiceImpl::GetInstance()->SetFilter(&filter
);
178 MockPluginProcessHostClient
mock_client(GetResourceContext(), true);
179 BrowserThread::PostTask(
180 BrowserThread::IO
, FROM_HERE
,
181 base::Bind(&OpenChannel
, &mock_client
));
185 // A strict mock that fails if any of the methods are called. They shouldn't be
186 // called since the request should get canceled before then.
187 class MockCanceledPluginServiceClient
: public PluginProcessHost::Client
{
189 MockCanceledPluginServiceClient(ResourceContext
* context
)
191 get_resource_context_called_(false) {
194 virtual ~MockCanceledPluginServiceClient() {}
196 // Client implementation.
197 MOCK_METHOD0(ID
, int());
198 virtual ResourceContext
* GetResourceContext() OVERRIDE
{
199 get_resource_context_called_
= true;
202 MOCK_METHOD0(OffTheRecord
, bool());
203 MOCK_METHOD1(OnFoundPluginProcessHost
, void(PluginProcessHost
* host
));
204 MOCK_METHOD0(OnSentPluginChannelRequest
, void());
205 MOCK_METHOD1(OnChannelOpened
, void(const IPC::ChannelHandle
& handle
));
206 MOCK_METHOD1(SetPluginInfo
, void(const webkit::WebPluginInfo
& info
));
207 MOCK_METHOD0(OnError
, void());
209 bool get_resource_context_called() const {
210 return get_resource_context_called_
;
214 ResourceContext
* context_
;
215 bool get_resource_context_called_
;
217 DISALLOW_COPY_AND_ASSIGN(MockCanceledPluginServiceClient
);
220 void QuitUIMessageLoopFromIOThread() {
221 BrowserThread::PostTask(
222 BrowserThread::UI
, FROM_HERE
, base::MessageLoop::QuitClosure());
225 void OpenChannelAndThenCancel(PluginProcessHost::Client
* client
) {
227 // Immediately cancel it. This is guaranteed to work since PluginService needs
228 // to consult its filter on the FILE thread.
229 PluginServiceImpl::GetInstance()->CancelOpenChannelToNpapiPlugin(client
);
230 // Before we terminate the test, add a roundtrip through the FILE thread to
231 // make sure that it's had a chance to post back to the IO thread. Then signal
232 // the UI thread to stop and exit the test.
233 BrowserThread::PostTaskAndReply(
234 BrowserThread::FILE, FROM_HERE
,
235 base::Bind(&base::DoNothing
),
236 base::Bind(&QuitUIMessageLoopFromIOThread
));
239 // Should not attempt to open a channel, since it should be canceled early on.
240 IN_PROC_BROWSER_TEST_F(PluginServiceTest
, CancelOpenChannelToPluginService
) {
241 ::testing::StrictMock
<MockCanceledPluginServiceClient
> mock_client(
242 GetResourceContext());
243 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
244 base::Bind(OpenChannelAndThenCancel
, &mock_client
));
246 EXPECT_TRUE(mock_client
.get_resource_context_called());
249 class MockCanceledBeforeSentPluginProcessHostClient
250 : public MockCanceledPluginServiceClient
{
252 MockCanceledBeforeSentPluginProcessHostClient(
253 ResourceContext
* context
)
254 : MockCanceledPluginServiceClient(context
),
255 set_plugin_info_called_(false),
256 on_found_plugin_process_host_called_(false),
259 virtual ~MockCanceledBeforeSentPluginProcessHostClient() {}
261 // Client implementation.
262 virtual void SetPluginInfo(const webkit::WebPluginInfo
& info
) OVERRIDE
{
263 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
264 ASSERT_TRUE(info
.mime_types
.size());
265 ASSERT_EQ(kNPAPITestPluginMimeType
, info
.mime_types
[0].mime_type
);
266 set_plugin_info_called_
= true;
268 virtual void OnFoundPluginProcessHost(PluginProcessHost
* host
) OVERRIDE
{
269 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
270 set_on_found_plugin_process_host_called();
272 // This gets called right before we request the plugin<=>renderer channel,
273 // so we have to post a task to cancel it.
274 base::MessageLoop::current()->PostTask(
276 base::Bind(&PluginProcessHost::CancelPendingRequest
,
277 base::Unretained(host
),
279 base::MessageLoop::current()->PostTask(
280 FROM_HERE
, base::Bind(&QuitUIMessageLoopFromIOThread
));
283 bool set_plugin_info_called() const {
284 return set_plugin_info_called_
;
287 bool on_found_plugin_process_host_called() const {
288 return on_found_plugin_process_host_called_
;
292 void set_on_found_plugin_process_host_called() {
293 on_found_plugin_process_host_called_
= true;
295 void set_host(PluginProcessHost
* host
) {
299 PluginProcessHost
* host() const { return host_
; }
302 bool set_plugin_info_called_
;
303 bool on_found_plugin_process_host_called_
;
304 PluginProcessHost
* host_
;
306 DISALLOW_COPY_AND_ASSIGN(MockCanceledBeforeSentPluginProcessHostClient
);
309 IN_PROC_BROWSER_TEST_F(
310 PluginServiceTest
, CancelBeforeSentOpenChannelToPluginProcessHost
) {
311 if (!webkit::npapi::NPAPIPluginsSupported())
313 ::testing::StrictMock
<MockCanceledBeforeSentPluginProcessHostClient
>
314 mock_client(GetResourceContext());
315 BrowserThread::PostTask(
316 BrowserThread::IO
, FROM_HERE
,
317 base::Bind(&OpenChannel
, &mock_client
));
319 EXPECT_TRUE(mock_client
.get_resource_context_called());
320 EXPECT_TRUE(mock_client
.set_plugin_info_called());
321 EXPECT_TRUE(mock_client
.on_found_plugin_process_host_called());
324 class MockCanceledAfterSentPluginProcessHostClient
325 : public MockCanceledBeforeSentPluginProcessHostClient
{
327 MockCanceledAfterSentPluginProcessHostClient(
328 ResourceContext
* context
)
329 : MockCanceledBeforeSentPluginProcessHostClient(context
),
330 on_sent_plugin_channel_request_called_(false) {}
331 virtual ~MockCanceledAfterSentPluginProcessHostClient() {}
333 // Client implementation.
335 virtual int ID() OVERRIDE
{ return 42; }
336 virtual bool OffTheRecord() OVERRIDE
{ return false; }
338 // We override this guy again since we don't want to cancel yet.
339 virtual void OnFoundPluginProcessHost(PluginProcessHost
* host
) OVERRIDE
{
340 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
341 set_on_found_plugin_process_host_called();
345 virtual void OnSentPluginChannelRequest() OVERRIDE
{
346 on_sent_plugin_channel_request_called_
= true;
347 host()->CancelSentRequest(this);
348 BrowserThread::PostTask(
349 BrowserThread::UI
, FROM_HERE
, base::MessageLoop::QuitClosure());
352 bool on_sent_plugin_channel_request_called() const {
353 return on_sent_plugin_channel_request_called_
;
357 bool on_sent_plugin_channel_request_called_
;
359 DISALLOW_COPY_AND_ASSIGN(MockCanceledAfterSentPluginProcessHostClient
);
362 // Should not attempt to open a channel, since it should be canceled early on.
363 IN_PROC_BROWSER_TEST_F(
364 PluginServiceTest
, CancelAfterSentOpenChannelToPluginProcessHost
) {
365 if (!webkit::npapi::NPAPIPluginsSupported())
367 ::testing::StrictMock
<MockCanceledAfterSentPluginProcessHostClient
>
368 mock_client(GetResourceContext());
369 BrowserThread::PostTask(
370 BrowserThread::IO
, FROM_HERE
,
371 base::Bind(&OpenChannel
, &mock_client
));
373 EXPECT_TRUE(mock_client
.get_resource_context_called());
374 EXPECT_TRUE(mock_client
.set_plugin_info_called());
375 EXPECT_TRUE(mock_client
.on_found_plugin_process_host_called());
376 EXPECT_TRUE(mock_client
.on_sent_plugin_channel_request_called());
379 } // namespace content