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.
4
5#include "content/browser/plugin_service_impl.h"
6
7#include "base/bind.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/content_browser_test.h"
17#include "content/public/test/test_browser_thread.h"
18#include "content/public/test/test_utils.h"
19#include "content/shell/browser/shell.h"
20#include "testing/gmock/include/gmock/gmock.h"
21
22namespace content {
23
24const char kNPAPITestPluginMimeType[] = "application/vnd.npapi-test";
25
26void OpenChannel(PluginProcessHost::Client* client) {
27  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
28  // Start opening the channel
29  PluginServiceImpl::GetInstance()->OpenChannelToNpapiPlugin(
30      0, 0, GURL(), GURL(), kNPAPITestPluginMimeType, client);
31}
32
33// Mock up of the Client and the Listener classes that would supply the
34// communication channel with the plugin.
35class MockPluginProcessHostClient : public PluginProcessHost::Client,
36                                    public IPC::Listener {
37 public:
38  MockPluginProcessHostClient(ResourceContext* context, bool expect_fail)
39      : context_(context),
40        channel_(NULL),
41        set_plugin_info_called_(false),
42        expect_fail_(expect_fail) {
43  }
44
45  virtual ~MockPluginProcessHostClient() {
46    if (channel_)
47      BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, channel_);
48  }
49
50  // PluginProcessHost::Client implementation.
51  virtual int ID() OVERRIDE { return 42; }
52  virtual bool OffTheRecord() OVERRIDE { return false; }
53  virtual ResourceContext* GetResourceContext() OVERRIDE {
54    return context_;
55  }
56  virtual void OnFoundPluginProcessHost(PluginProcessHost* host) OVERRIDE {}
57  virtual void OnSentPluginChannelRequest() OVERRIDE {}
58
59  virtual void OnChannelOpened(const IPC::ChannelHandle& handle) OVERRIDE {
60    ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
61    ASSERT_TRUE(set_plugin_info_called_);
62    ASSERT_TRUE(!channel_);
63    channel_ = IPC::Channel::CreateClient(handle, this).release();
64    ASSERT_TRUE(channel_->Connect());
65  }
66
67  virtual void SetPluginInfo(const WebPluginInfo& info) OVERRIDE {
68    ASSERT_TRUE(info.mime_types.size());
69    ASSERT_EQ(kNPAPITestPluginMimeType, info.mime_types[0].mime_type);
70    set_plugin_info_called_ = true;
71  }
72
73  virtual void OnError() OVERRIDE {
74    Fail();
75  }
76
77  // IPC::Listener implementation.
78  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
79    Fail();
80    return false;
81  }
82  virtual void OnChannelConnected(int32 peer_pid) OVERRIDE {
83    if (expect_fail_)
84      FAIL();
85    QuitMessageLoop();
86  }
87  virtual void OnChannelError() OVERRIDE {
88    Fail();
89  }
90#if defined(OS_POSIX)
91  virtual void OnChannelDenied() OVERRIDE {
92    Fail();
93  }
94  virtual void OnChannelListenError() OVERRIDE {
95    Fail();
96  }
97#endif
98
99 private:
100  void Fail() {
101    if (!expect_fail_)
102      FAIL();
103    QuitMessageLoop();
104  }
105
106  void QuitMessageLoop() {
107    BrowserThread::PostTask(
108        BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure());
109  }
110
111  ResourceContext* context_;
112  IPC::Channel* channel_;
113  bool set_plugin_info_called_;
114  bool expect_fail_;
115  DISALLOW_COPY_AND_ASSIGN(MockPluginProcessHostClient);
116};
117
118class MockPluginServiceFilter : public content::PluginServiceFilter {
119 public:
120  MockPluginServiceFilter() {}
121
122  virtual bool IsPluginAvailable(
123      int render_process_id,
124      int render_view_id,
125      const void* context,
126      const GURL& url,
127      const GURL& policy_url,
128      WebPluginInfo* plugin) OVERRIDE { return true; }
129
130  virtual bool CanLoadPlugin(
131      int render_process_id,
132      const base::FilePath& path) OVERRIDE { return false; }
133};
134
135class PluginServiceTest : public ContentBrowserTest {
136 public:
137  PluginServiceTest() {}
138
139  ResourceContext* GetResourceContext() {
140    return shell()->web_contents()->GetBrowserContext()->GetResourceContext();
141  }
142
143  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
144#if defined(OS_MACOSX)
145    base::FilePath browser_directory;
146    PathService::Get(base::DIR_MODULE, &browser_directory);
147    command_line->AppendSwitchPath(switches::kExtraPluginDir,
148                                   browser_directory.AppendASCII("plugins"));
149#endif
150    // TODO(jam): since these plugin tests are running under Chrome, we need to
151    // tell it to disable its security features for old plugins. Once this is
152    // running under content_browsertests, these flags won't be needed.
153    // http://crbug.com/90448
154    // switches::kAlwaysAuthorizePlugins
155    command_line->AppendSwitch("always-authorize-plugins");
156  }
157};
158
159// Try to open a channel to the test plugin. Minimal plugin process spawning
160// test for the PluginService interface.
161IN_PROC_BROWSER_TEST_F(PluginServiceTest, OpenChannelToPlugin) {
162  if (!PluginServiceImpl::GetInstance()->NPAPIPluginsSupported())
163    return;
164  MockPluginProcessHostClient mock_client(GetResourceContext(), false);
165  BrowserThread::PostTask(
166      BrowserThread::IO, FROM_HERE,
167      base::Bind(&OpenChannel, &mock_client));
168  RunMessageLoop();
169}
170
171IN_PROC_BROWSER_TEST_F(PluginServiceTest, OpenChannelToDeniedPlugin) {
172  if (!PluginServiceImpl::GetInstance()->NPAPIPluginsSupported())
173    return;
174  MockPluginServiceFilter filter;
175  PluginServiceImpl::GetInstance()->SetFilter(&filter);
176  MockPluginProcessHostClient mock_client(GetResourceContext(), true);
177  BrowserThread::PostTask(
178      BrowserThread::IO, FROM_HERE,
179      base::Bind(&OpenChannel, &mock_client));
180  RunMessageLoop();
181}
182
183// A strict mock that fails if any of the methods are called. They shouldn't be
184// called since the request should get canceled before then.
185class MockCanceledPluginServiceClient : public PluginProcessHost::Client {
186 public:
187  MockCanceledPluginServiceClient(ResourceContext* context)
188      : context_(context),
189        get_resource_context_called_(false) {
190  }
191
192  virtual ~MockCanceledPluginServiceClient() {}
193
194  // Client implementation.
195  MOCK_METHOD0(ID, int());
196  virtual ResourceContext* GetResourceContext() OVERRIDE {
197    get_resource_context_called_ = true;
198    return context_;
199  }
200  MOCK_METHOD0(OffTheRecord, bool());
201  MOCK_METHOD1(OnFoundPluginProcessHost, void(PluginProcessHost* host));
202  MOCK_METHOD0(OnSentPluginChannelRequest, void());
203  MOCK_METHOD1(OnChannelOpened, void(const IPC::ChannelHandle& handle));
204  MOCK_METHOD1(SetPluginInfo, void(const WebPluginInfo& info));
205  MOCK_METHOD0(OnError, void());
206
207  bool get_resource_context_called() const {
208    return get_resource_context_called_;
209  }
210
211 private:
212  ResourceContext* context_;
213  bool get_resource_context_called_;
214
215  DISALLOW_COPY_AND_ASSIGN(MockCanceledPluginServiceClient);
216};
217
218void QuitUIMessageLoopFromIOThread() {
219  BrowserThread::PostTask(
220      BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure());
221}
222
223void OpenChannelAndThenCancel(PluginProcessHost::Client* client) {
224  OpenChannel(client);
225  // Immediately cancel it. This is guaranteed to work since PluginService needs
226  // to consult its filter on the FILE thread.
227  PluginServiceImpl::GetInstance()->CancelOpenChannelToNpapiPlugin(client);
228  // Before we terminate the test, add a roundtrip through the FILE thread to
229  // make sure that it's had a chance to post back to the IO thread. Then signal
230  // the UI thread to stop and exit the test.
231  BrowserThread::PostTaskAndReply(
232      BrowserThread::FILE, FROM_HERE,
233      base::Bind(&base::DoNothing),
234      base::Bind(&QuitUIMessageLoopFromIOThread));
235}
236
237// Should not attempt to open a channel, since it should be canceled early on.
238IN_PROC_BROWSER_TEST_F(PluginServiceTest, CancelOpenChannelToPluginService) {
239  ::testing::StrictMock<MockCanceledPluginServiceClient> mock_client(
240      GetResourceContext());
241  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
242                          base::Bind(OpenChannelAndThenCancel, &mock_client));
243  RunMessageLoop();
244  EXPECT_TRUE(mock_client.get_resource_context_called());
245}
246
247class MockCanceledBeforeSentPluginProcessHostClient
248    : public MockCanceledPluginServiceClient {
249 public:
250  MockCanceledBeforeSentPluginProcessHostClient(
251      ResourceContext* context)
252      : MockCanceledPluginServiceClient(context),
253        set_plugin_info_called_(false),
254        on_found_plugin_process_host_called_(false),
255        host_(NULL) {}
256
257  virtual ~MockCanceledBeforeSentPluginProcessHostClient() {}
258
259  // Client implementation.
260  virtual void SetPluginInfo(const WebPluginInfo& info) OVERRIDE {
261    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
262    ASSERT_TRUE(info.mime_types.size());
263    ASSERT_EQ(kNPAPITestPluginMimeType, info.mime_types[0].mime_type);
264    set_plugin_info_called_ = true;
265  }
266  virtual void OnFoundPluginProcessHost(PluginProcessHost* host) OVERRIDE {
267    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
268    set_on_found_plugin_process_host_called();
269    set_host(host);
270    // This gets called right before we request the plugin<=>renderer channel,
271    // so we have to post a task to cancel it.
272    base::MessageLoop::current()->PostTask(
273        FROM_HERE,
274        base::Bind(&PluginProcessHost::CancelPendingRequest,
275                   base::Unretained(host),
276                   this));
277    base::MessageLoop::current()->PostTask(
278        FROM_HERE, base::Bind(&QuitUIMessageLoopFromIOThread));
279  }
280
281  bool set_plugin_info_called() const {
282    return set_plugin_info_called_;
283  }
284
285  bool on_found_plugin_process_host_called() const {
286    return on_found_plugin_process_host_called_;
287  }
288
289 protected:
290  void set_on_found_plugin_process_host_called() {
291    on_found_plugin_process_host_called_ = true;
292  }
293  void set_host(PluginProcessHost* host) {
294    host_ = host;
295  }
296
297  PluginProcessHost* host() const { return host_; }
298
299 private:
300  bool set_plugin_info_called_;
301  bool on_found_plugin_process_host_called_;
302  PluginProcessHost* host_;
303
304  DISALLOW_COPY_AND_ASSIGN(MockCanceledBeforeSentPluginProcessHostClient);
305};
306
307IN_PROC_BROWSER_TEST_F(
308    PluginServiceTest, CancelBeforeSentOpenChannelToPluginProcessHost) {
309  if (!PluginServiceImpl::GetInstance()->NPAPIPluginsSupported())
310    return;
311  ::testing::StrictMock<MockCanceledBeforeSentPluginProcessHostClient>
312      mock_client(GetResourceContext());
313  BrowserThread::PostTask(
314      BrowserThread::IO, FROM_HERE,
315      base::Bind(&OpenChannel, &mock_client));
316  RunMessageLoop();
317  EXPECT_TRUE(mock_client.get_resource_context_called());
318  EXPECT_TRUE(mock_client.set_plugin_info_called());
319  EXPECT_TRUE(mock_client.on_found_plugin_process_host_called());
320}
321
322class MockCanceledAfterSentPluginProcessHostClient
323    : public MockCanceledBeforeSentPluginProcessHostClient {
324 public:
325  MockCanceledAfterSentPluginProcessHostClient(
326      ResourceContext* context)
327      : MockCanceledBeforeSentPluginProcessHostClient(context),
328        on_sent_plugin_channel_request_called_(false) {}
329  virtual ~MockCanceledAfterSentPluginProcessHostClient() {}
330
331  // Client implementation.
332
333  virtual int ID() OVERRIDE { return 42; }
334  virtual bool OffTheRecord() OVERRIDE { return false; }
335
336  // We override this guy again since we don't want to cancel yet.
337  virtual void OnFoundPluginProcessHost(PluginProcessHost* host) OVERRIDE {
338    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
339    set_on_found_plugin_process_host_called();
340    set_host(host);
341  }
342
343  virtual void OnSentPluginChannelRequest() OVERRIDE {
344    on_sent_plugin_channel_request_called_ = true;
345    host()->CancelSentRequest(this);
346    BrowserThread::PostTask(
347        BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure());
348  }
349
350  bool on_sent_plugin_channel_request_called() const {
351    return on_sent_plugin_channel_request_called_;
352  }
353
354 private:
355  bool on_sent_plugin_channel_request_called_;
356
357  DISALLOW_COPY_AND_ASSIGN(MockCanceledAfterSentPluginProcessHostClient);
358};
359
360// Should not attempt to open a channel, since it should be canceled early on.
361IN_PROC_BROWSER_TEST_F(
362    PluginServiceTest, CancelAfterSentOpenChannelToPluginProcessHost) {
363  if (!PluginServiceImpl::GetInstance()->NPAPIPluginsSupported())
364    return;
365  ::testing::StrictMock<MockCanceledAfterSentPluginProcessHostClient>
366      mock_client(GetResourceContext());
367  BrowserThread::PostTask(
368      BrowserThread::IO, FROM_HERE,
369      base::Bind(&OpenChannel, &mock_client));
370  RunMessageLoop();
371  EXPECT_TRUE(mock_client.get_resource_context_called());
372  EXPECT_TRUE(mock_client.set_plugin_info_called());
373  EXPECT_TRUE(mock_client.on_found_plugin_process_host_called());
374  EXPECT_TRUE(mock_client.on_sent_plugin_channel_request_called());
375}
376
377}  // namespace content
378