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