Home | History | Annotate | Download | only in browser
      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/test_browser_thread.h"
     17 #include "content/public/test/test_utils.h"
     18 #include "content/shell/browser/shell.h"
     19 #include "content/test/content_browser_test.h"
     20 #include "testing/gmock/include/gmock/gmock.h"
     21 
     22 namespace content {
     23 
     24 const char kNPAPITestPluginMimeType[] = "application/vnd.npapi-test";
     25 
     26 void 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.
     35 class 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_ = new IPC::Channel(handle, IPC::Channel::MODE_CLIENT, this);
     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 
    118 class 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 
    135 class 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.
    161 IN_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 
    171 IN_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.
    185 class 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 
    218 void QuitUIMessageLoopFromIOThread() {
    219   BrowserThread::PostTask(
    220       BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure());
    221 }
    222 
    223 void 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.
    238 IN_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 
    247 class 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 
    307 IN_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 
    322 class 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.
    361 IN_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