| // 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 "ppapi/proxy/ppapi_proxy_test.h" | |
| #include <tuple> | |
| #include "base/bind.h" | |
| #include "base/bind_helpers.h" | |
| #include "base/location.h" | |
| #include "base/observer_list.h" | |
| #include "base/process/process_handle.h" | |
| #include "base/run_loop.h" | |
| #include "base/single_thread_task_runner.h" | |
| #include "base/threading/thread_task_runner_handle.h" | |
| #include "ipc/ipc_sync_channel.h" | |
| #include "ipc/message_filter.h" | |
| #include "ppapi/c/pp_errors.h" | |
| #include "ppapi/c/private/ppb_proxy_private.h" | |
| #include "ppapi/proxy/ppapi_messages.h" | |
| #include "ppapi/proxy/ppb_message_loop_proxy.h" | |
| namespace ppapi { | |
| namespace proxy { | |
| namespace { | |
| // HostDispatcher requires a PPB_Proxy_Private, so we always provide a fallback | |
| // do-nothing implementation. | |
| void PluginCrashed(PP_Module module) { | |
| NOTREACHED(); | |
| }; | |
| PP_Instance GetInstanceForResource(PP_Resource resource) { | |
| // If a test relies on this, we need to implement it. | |
| NOTREACHED(); | |
| return 0; | |
| } | |
| void SetReserveInstanceIDCallback(PP_Module module, | |
| PP_Bool (*is_seen)(PP_Module, PP_Instance)) { | |
| // This function gets called in HostDispatcher's constructor. We simply don't | |
| // worry about Instance uniqueness in tests, so we can ignore the call. | |
| } | |
| void AddRefModule(PP_Module module) {} | |
| void ReleaseModule(PP_Module module) {} | |
| PP_Bool IsInModuleDestructor(PP_Module module) { return PP_FALSE; } | |
| PPB_Proxy_Private ppb_proxy_private = { | |
| &PluginCrashed, | |
| &GetInstanceForResource, | |
| &SetReserveInstanceIDCallback, | |
| &AddRefModule, | |
| &ReleaseModule, | |
| &IsInModuleDestructor | |
| }; | |
| // We allow multiple harnesses at a time to respond to 'GetInterface' calls. | |
| // We assume that only 1 harness's GetInterface function will ever support a | |
| // given interface name. In practice, there will either be only 1 GetInterface | |
| // handler (for PluginProxyTest or HostProxyTest), or there will be only 2 | |
| // GetInterface handlers (for TwoWayTest). In the latter case, one handler is | |
| // for the PluginProxyTestHarness and should only respond for PPP interfaces, | |
| // and the other handler is for the HostProxyTestHarness which should only | |
| // ever respond for PPB interfaces. | |
| base::ObserverList<ProxyTestHarnessBase>::Unchecked get_interface_handlers_; | |
| const void* MockGetInterface(const char* name) { | |
| for (auto& observer : get_interface_handlers_) { | |
| const void* interface = observer.GetInterface(name); | |
| if (interface) | |
| return interface; | |
| } | |
| if (strcmp(name, PPB_PROXY_PRIVATE_INTERFACE) == 0) | |
| return &ppb_proxy_private; | |
| return NULL; | |
| } | |
| void SetUpRemoteHarness(ProxyTestHarnessBase* harness, | |
| const IPC::ChannelHandle& handle, | |
| base::SingleThreadTaskRunner* ipc_task_runner, | |
| base::WaitableEvent* shutdown_event, | |
| base::WaitableEvent* harness_set_up) { | |
| harness->SetUpHarnessWithChannel(handle, ipc_task_runner, shutdown_event, | |
| false); | |
| harness_set_up->Signal(); | |
| } | |
| void TearDownRemoteHarness(ProxyTestHarnessBase* harness, | |
| base::WaitableEvent* harness_torn_down) { | |
| harness->TearDownHarness(); | |
| harness_torn_down->Signal(); | |
| } | |
| void RunTaskOnRemoteHarness(const base::Closure& task, | |
| base::WaitableEvent* task_complete) { | |
| task.Run(); | |
| task_complete->Signal(); | |
| } | |
| } // namespace | |
| // ProxyTestHarnessBase -------------------------------------------------------- | |
| ProxyTestHarnessBase::ProxyTestHarnessBase() : pp_module_(0x98765), | |
| pp_instance_(0x12345) { | |
| get_interface_handlers_.AddObserver(this); | |
| } | |
| ProxyTestHarnessBase::~ProxyTestHarnessBase() { | |
| get_interface_handlers_.RemoveObserver(this); | |
| } | |
| const void* ProxyTestHarnessBase::GetInterface(const char* name) { | |
| return registered_interfaces_[name]; | |
| } | |
| void ProxyTestHarnessBase::RegisterTestInterface(const char* name, | |
| const void* test_interface) { | |
| registered_interfaces_[name] = test_interface; | |
| } | |
| bool ProxyTestHarnessBase::SupportsInterface(const char* name) { | |
| sink().ClearMessages(); | |
| // IPC doesn't actually write to this when we send a message manually | |
| // not actually using IPC. | |
| bool unused_result = false; | |
| PpapiMsg_SupportsInterface msg(name, &unused_result); | |
| GetDispatcher()->OnMessageReceived(msg); | |
| const IPC::Message* reply_msg = | |
| sink().GetUniqueMessageMatching(IPC_REPLY_ID); | |
| EXPECT_TRUE(reply_msg); | |
| if (!reply_msg) | |
| return false; | |
| PpapiMsg_SupportsInterface::ReplyParam reply_data; | |
| EXPECT_TRUE(PpapiMsg_SupportsInterface::ReadReplyParam( | |
| reply_msg, &reply_data)); | |
| sink().ClearMessages(); | |
| return std::get<0>(reply_data); | |
| } | |
| // PluginProxyTestHarness ------------------------------------------------------ | |
| PluginProxyTestHarness::PluginProxyTestHarness( | |
| GlobalsConfiguration globals_config) | |
| : globals_config_(globals_config) { | |
| } | |
| PluginProxyTestHarness::~PluginProxyTestHarness() { | |
| } | |
| PpapiGlobals* PluginProxyTestHarness::GetGlobals() { | |
| return plugin_globals_.get(); | |
| } | |
| Dispatcher* PluginProxyTestHarness::GetDispatcher() { | |
| return plugin_dispatcher_.get(); | |
| } | |
| void PluginProxyTestHarness::SetUpHarness() { | |
| // These must be first since the dispatcher set-up uses them. | |
| CreatePluginGlobals(nullptr /* ipc_task_runner */); | |
| // Some of the methods called during set-up check that the lock is held. | |
| ProxyAutoLock lock; | |
| resource_tracker().DidCreateInstance(pp_instance()); | |
| plugin_dispatcher_.reset(new PluginDispatcher( | |
| &MockGetInterface, | |
| PpapiPermissions(), | |
| false)); | |
| plugin_dispatcher_->InitWithTestSink(&sink()); | |
| // The plugin proxy delegate is needed for | |
| // |PluginProxyDelegate::GetBrowserSender| which is used | |
| // in |ResourceCreationProxy::GetConnection| to get the channel to the | |
| // browser. In this case we just use the |plugin_dispatcher_| as the channel | |
| // for test purposes. | |
| plugin_delegate_mock_.set_browser_sender(plugin_dispatcher_.get()); | |
| PluginGlobals::Get()->SetPluginProxyDelegate(&plugin_delegate_mock_); | |
| plugin_dispatcher_->DidCreateInstance(pp_instance()); | |
| } | |
| void PluginProxyTestHarness::SetUpHarnessWithChannel( | |
| const IPC::ChannelHandle& channel_handle, | |
| base::SingleThreadTaskRunner* ipc_task_runner, | |
| base::WaitableEvent* shutdown_event, | |
| bool is_client) { | |
| // These must be first since the dispatcher set-up uses them. | |
| CreatePluginGlobals(ipc_task_runner); | |
| // Some of the methods called during set-up check that the lock is held. | |
| ProxyAutoLock lock; | |
| resource_tracker().DidCreateInstance(pp_instance()); | |
| plugin_delegate_mock_.Init(ipc_task_runner, shutdown_event); | |
| plugin_dispatcher_.reset(new PluginDispatcher( | |
| &MockGetInterface, | |
| PpapiPermissions(), | |
| false)); | |
| plugin_dispatcher_->InitPluginWithChannel(&plugin_delegate_mock_, | |
| base::kNullProcessId, | |
| channel_handle, | |
| is_client); | |
| plugin_delegate_mock_.set_browser_sender(plugin_dispatcher_.get()); | |
| PluginGlobals::Get()->SetPluginProxyDelegate(&plugin_delegate_mock_); | |
| plugin_dispatcher_->DidCreateInstance(pp_instance()); | |
| } | |
| void PluginProxyTestHarness::TearDownHarness() { | |
| { | |
| // Some of the methods called during tear-down check that the lock is held. | |
| ProxyAutoLock lock; | |
| plugin_dispatcher_->DidDestroyInstance(pp_instance()); | |
| plugin_dispatcher_.reset(); | |
| resource_tracker().DidDeleteInstance(pp_instance()); | |
| } | |
| plugin_globals_.reset(); | |
| } | |
| void PluginProxyTestHarness::CreatePluginGlobals( | |
| const scoped_refptr<base::TaskRunner>& ipc_task_runner) { | |
| if (globals_config_ == PER_THREAD_GLOBALS) { | |
| plugin_globals_.reset(new PluginGlobals(PpapiGlobals::PerThreadForTest(), | |
| ipc_task_runner)); | |
| PpapiGlobals::SetPpapiGlobalsOnThreadForTest(GetGlobals()); | |
| } else { | |
| plugin_globals_.reset(new PluginGlobals(ipc_task_runner)); | |
| } | |
| } | |
| base::SingleThreadTaskRunner* | |
| PluginProxyTestHarness::PluginDelegateMock::GetIPCTaskRunner() { | |
| return ipc_task_runner_; | |
| } | |
| base::WaitableEvent* | |
| PluginProxyTestHarness::PluginDelegateMock::GetShutdownEvent() { | |
| return shutdown_event_; | |
| } | |
| IPC::PlatformFileForTransit | |
| PluginProxyTestHarness::PluginDelegateMock::ShareHandleWithRemote( | |
| base::PlatformFile handle, | |
| base::ProcessId /* remote_pid */, | |
| bool should_close_source) { | |
| return IPC::GetPlatformFileForTransit(handle, | |
| should_close_source); | |
| } | |
| base::SharedMemoryHandle | |
| PluginProxyTestHarness::PluginDelegateMock::ShareSharedMemoryHandleWithRemote( | |
| const base::SharedMemoryHandle& handle, | |
| base::ProcessId /* remote_pid */) { | |
| return base::SharedMemory::DuplicateHandle(handle); | |
| } | |
| base::UnsafeSharedMemoryRegion PluginProxyTestHarness::PluginDelegateMock:: | |
| ShareUnsafeSharedMemoryRegionWithRemote( | |
| const base::UnsafeSharedMemoryRegion& region, | |
| base::ProcessId /* remote_pid */) { | |
| return region.Duplicate(); | |
| } | |
| base::ReadOnlySharedMemoryRegion PluginProxyTestHarness::PluginDelegateMock:: | |
| ShareReadOnlySharedMemoryRegionWithRemote( | |
| const base::ReadOnlySharedMemoryRegion& region, | |
| base::ProcessId /* remote_pid */) { | |
| return region.Duplicate(); | |
| } | |
| std::set<PP_Instance>* | |
| PluginProxyTestHarness::PluginDelegateMock::GetGloballySeenInstanceIDSet() { | |
| return &instance_id_set_; | |
| } | |
| uint32_t PluginProxyTestHarness::PluginDelegateMock::Register( | |
| PluginDispatcher* plugin_dispatcher) { | |
| return 0; | |
| } | |
| void PluginProxyTestHarness::PluginDelegateMock::Unregister( | |
| uint32_t plugin_dispatcher_id) {} | |
| IPC::Sender* PluginProxyTestHarness::PluginDelegateMock::GetBrowserSender() { | |
| return browser_sender_; | |
| } | |
| std::string PluginProxyTestHarness::PluginDelegateMock::GetUILanguage() { | |
| return std::string("en-US"); | |
| } | |
| void PluginProxyTestHarness::PluginDelegateMock::PreCacheFontForFlash( | |
| const void* logfontw) { | |
| } | |
| void PluginProxyTestHarness::PluginDelegateMock::SetActiveURL( | |
| const std::string& url) { | |
| } | |
| PP_Resource PluginProxyTestHarness::PluginDelegateMock::CreateBrowserFont( | |
| Connection connection, | |
| PP_Instance instance, | |
| const PP_BrowserFont_Trusted_Description& desc, | |
| const Preferences& prefs) { | |
| return 0; | |
| } | |
| // PluginProxyTest ------------------------------------------------------------- | |
| PluginProxyTest::PluginProxyTest() : PluginProxyTestHarness(SINGLETON_GLOBALS) { | |
| } | |
| PluginProxyTest::~PluginProxyTest() { | |
| } | |
| void PluginProxyTest::SetUp() { | |
| SetUpHarness(); | |
| } | |
| void PluginProxyTest::TearDown() { | |
| TearDownHarness(); | |
| } | |
| // PluginProxyMultiThreadTest -------------------------------------------------- | |
| PluginProxyMultiThreadTest::PluginProxyMultiThreadTest() { | |
| } | |
| PluginProxyMultiThreadTest::~PluginProxyMultiThreadTest() { | |
| } | |
| void PluginProxyMultiThreadTest::RunTest() { | |
| main_thread_task_runner_ = PpapiGlobals::Get()->GetMainThreadMessageLoop(); | |
| ASSERT_EQ(main_thread_task_runner_.get(), | |
| base::ThreadTaskRunnerHandle::Get().get()); | |
| nested_main_thread_message_loop_.reset(new base::RunLoop()); | |
| secondary_thread_.reset(new base::DelegateSimpleThread( | |
| this, "PluginProxyMultiThreadTest")); | |
| { | |
| ProxyAutoLock auto_lock; | |
| // MessageLoopResource assumes that the proxy lock has been acquired. | |
| secondary_thread_message_loop_ = new MessageLoopResource(pp_instance()); | |
| ASSERT_EQ(PP_OK, | |
| secondary_thread_message_loop_->PostWork( | |
| PP_MakeCompletionCallback( | |
| &PluginProxyMultiThreadTest::InternalSetUpTestOnSecondaryThread, | |
| this), | |
| 0)); | |
| } | |
| SetUpTestOnMainThread(); | |
| secondary_thread_->Start(); | |
| nested_main_thread_message_loop_->Run(); | |
| secondary_thread_->Join(); | |
| { | |
| ProxyAutoLock auto_lock; | |
| // The destruction requires a valid PpapiGlobals instance, so we should | |
| // explicitly release it. | |
| secondary_thread_message_loop_ = NULL; | |
| } | |
| secondary_thread_.reset(NULL); | |
| nested_main_thread_message_loop_.reset(NULL); | |
| main_thread_task_runner_ = NULL; | |
| } | |
| void PluginProxyMultiThreadTest::CheckOnThread(ThreadType thread_type) { | |
| ProxyAutoLock auto_lock; | |
| if (thread_type == MAIN_THREAD) { | |
| ASSERT_TRUE(MessageLoopResource::GetCurrent()->is_main_thread_loop()); | |
| } else { | |
| ASSERT_EQ(secondary_thread_message_loop_.get(), | |
| MessageLoopResource::GetCurrent()); | |
| } | |
| } | |
| void PluginProxyMultiThreadTest::PostQuitForMainThread() { | |
| main_thread_task_runner_->PostTask( | |
| FROM_HERE, base::Bind(&PluginProxyMultiThreadTest::QuitNestedLoop, | |
| base::Unretained(this))); | |
| } | |
| void PluginProxyMultiThreadTest::PostQuitForSecondaryThread() { | |
| ProxyAutoLock auto_lock; | |
| secondary_thread_message_loop_->PostQuit(PP_TRUE); | |
| } | |
| void PluginProxyMultiThreadTest::Run() { | |
| ProxyAutoLock auto_lock; | |
| ASSERT_EQ(PP_OK, secondary_thread_message_loop_->AttachToCurrentThread()); | |
| ASSERT_EQ(PP_OK, secondary_thread_message_loop_->Run()); | |
| secondary_thread_message_loop_->DetachFromThread(); | |
| } | |
| void PluginProxyMultiThreadTest::QuitNestedLoop() { | |
| nested_main_thread_message_loop_->Quit(); | |
| } | |
| // static | |
| void PluginProxyMultiThreadTest::InternalSetUpTestOnSecondaryThread( | |
| void* user_data, | |
| int32_t result) { | |
| EXPECT_EQ(PP_OK, result); | |
| PluginProxyMultiThreadTest* thiz = | |
| static_cast<PluginProxyMultiThreadTest*>(user_data); | |
| thiz->CheckOnThread(SECONDARY_THREAD); | |
| thiz->SetUpTestOnSecondaryThread(); | |
| } | |
| // HostProxyTestHarness -------------------------------------------------------- | |
| HostProxyTestHarness::HostProxyTestHarness(GlobalsConfiguration globals_config) | |
| : globals_config_(globals_config) { | |
| } | |
| HostProxyTestHarness::~HostProxyTestHarness() { | |
| } | |
| PpapiGlobals* HostProxyTestHarness::GetGlobals() { | |
| return host_globals_.get(); | |
| } | |
| Dispatcher* HostProxyTestHarness::GetDispatcher() { | |
| return host_dispatcher_.get(); | |
| } | |
| void HostProxyTestHarness::SetUpHarness() { | |
| // These must be first since the dispatcher set-up uses them. | |
| CreateHostGlobals(); | |
| host_dispatcher_.reset(new HostDispatcher( | |
| pp_module(), | |
| &MockGetInterface, | |
| PpapiPermissions::AllPermissions())); | |
| host_dispatcher_->InitWithTestSink(&sink()); | |
| HostDispatcher::SetForInstance(pp_instance(), host_dispatcher_.get()); | |
| } | |
| void HostProxyTestHarness::SetUpHarnessWithChannel( | |
| const IPC::ChannelHandle& channel_handle, | |
| base::SingleThreadTaskRunner* ipc_task_runner, | |
| base::WaitableEvent* shutdown_event, | |
| bool is_client) { | |
| // These must be first since the dispatcher set-up uses them. | |
| CreateHostGlobals(); | |
| delegate_mock_.Init(ipc_task_runner, shutdown_event); | |
| host_dispatcher_.reset(new HostDispatcher( | |
| pp_module(), | |
| &MockGetInterface, | |
| PpapiPermissions::AllPermissions())); | |
| ppapi::Preferences preferences; | |
| host_dispatcher_->InitHostWithChannel(&delegate_mock_, | |
| base::kNullProcessId, channel_handle, | |
| is_client, preferences); | |
| HostDispatcher::SetForInstance(pp_instance(), host_dispatcher_.get()); | |
| } | |
| void HostProxyTestHarness::TearDownHarness() { | |
| HostDispatcher::RemoveForInstance(pp_instance()); | |
| host_dispatcher_.reset(); | |
| host_globals_.reset(); | |
| } | |
| void HostProxyTestHarness::CreateHostGlobals() { | |
| disable_locking_.reset(new ProxyLock::LockingDisablerForTest); | |
| if (globals_config_ == PER_THREAD_GLOBALS) { | |
| host_globals_.reset(new TestGlobals(PpapiGlobals::PerThreadForTest())); | |
| PpapiGlobals::SetPpapiGlobalsOnThreadForTest(GetGlobals()); | |
| } else { | |
| host_globals_.reset(new TestGlobals()); | |
| } | |
| } | |
| base::SingleThreadTaskRunner* | |
| HostProxyTestHarness::DelegateMock::GetIPCTaskRunner() { | |
| return ipc_task_runner_; | |
| } | |
| base::WaitableEvent* HostProxyTestHarness::DelegateMock::GetShutdownEvent() { | |
| return shutdown_event_; | |
| } | |
| IPC::PlatformFileForTransit | |
| HostProxyTestHarness::DelegateMock::ShareHandleWithRemote( | |
| base::PlatformFile handle, | |
| base::ProcessId /* remote_pid */, | |
| bool should_close_source) { | |
| return IPC::GetPlatformFileForTransit(handle, | |
| should_close_source); | |
| } | |
| base::SharedMemoryHandle | |
| HostProxyTestHarness::DelegateMock::ShareSharedMemoryHandleWithRemote( | |
| const base::SharedMemoryHandle& handle, | |
| base::ProcessId /*remote_pid*/) { | |
| return base::SharedMemory::DuplicateHandle(handle); | |
| } | |
| base::UnsafeSharedMemoryRegion | |
| HostProxyTestHarness::DelegateMock::ShareUnsafeSharedMemoryRegionWithRemote( | |
| const base::UnsafeSharedMemoryRegion& region, | |
| base::ProcessId /*remote_pid*/) { | |
| return region.Duplicate(); | |
| } | |
| base::ReadOnlySharedMemoryRegion | |
| HostProxyTestHarness::DelegateMock::ShareReadOnlySharedMemoryRegionWithRemote( | |
| const base::ReadOnlySharedMemoryRegion& region, | |
| base::ProcessId /*remote_pid*/) { | |
| return region.Duplicate(); | |
| } | |
| // HostProxyTest --------------------------------------------------------------- | |
| HostProxyTest::HostProxyTest() : HostProxyTestHarness(SINGLETON_GLOBALS) { | |
| } | |
| HostProxyTest::~HostProxyTest() { | |
| } | |
| void HostProxyTest::SetUp() { | |
| SetUpHarness(); | |
| } | |
| void HostProxyTest::TearDown() { | |
| TearDownHarness(); | |
| } | |
| // TwoWayTest --------------------------------------------------------------- | |
| TwoWayTest::TwoWayTest(TwoWayTest::TwoWayTestMode test_mode) | |
| : test_mode_(test_mode), | |
| host_(ProxyTestHarnessBase::PER_THREAD_GLOBALS), | |
| plugin_(ProxyTestHarnessBase::PER_THREAD_GLOBALS), | |
| io_thread_("TwoWayTest_IOThread"), | |
| plugin_thread_("TwoWayTest_PluginThread"), | |
| remote_harness_(NULL), | |
| local_harness_(NULL), | |
| channel_created_(base::WaitableEvent::ResetPolicy::MANUAL, | |
| base::WaitableEvent::InitialState::NOT_SIGNALED), | |
| shutdown_event_(base::WaitableEvent::ResetPolicy::MANUAL, | |
| base::WaitableEvent::InitialState::NOT_SIGNALED) { | |
| if (test_mode == TEST_PPP_INTERFACE) { | |
| remote_harness_ = &plugin_; | |
| local_harness_ = &host_; | |
| } else { | |
| remote_harness_ = &host_; | |
| local_harness_ = &plugin_; | |
| } | |
| } | |
| TwoWayTest::~TwoWayTest() { | |
| shutdown_event_.Signal(); | |
| } | |
| void TwoWayTest::SetUp() { | |
| base::Thread::Options options; | |
| options.message_loop_type = base::MessageLoop::TYPE_IO; | |
| io_thread_.StartWithOptions(options); | |
| plugin_thread_.Start(); | |
| mojo::MessagePipe pipe; | |
| base::WaitableEvent remote_harness_set_up( | |
| base::WaitableEvent::ResetPolicy::MANUAL, | |
| base::WaitableEvent::InitialState::NOT_SIGNALED); | |
| plugin_thread_.task_runner()->PostTask( | |
| FROM_HERE, | |
| base::Bind(&SetUpRemoteHarness, remote_harness_, pipe.handle0.release(), | |
| base::RetainedRef(io_thread_.task_runner()), &shutdown_event_, | |
| &remote_harness_set_up)); | |
| remote_harness_set_up.Wait(); | |
| local_harness_->SetUpHarnessWithChannel( | |
| pipe.handle1.release(), io_thread_.task_runner().get(), &shutdown_event_, | |
| true); // is_client | |
| } | |
| void TwoWayTest::TearDown() { | |
| base::WaitableEvent remote_harness_torn_down( | |
| base::WaitableEvent::ResetPolicy::MANUAL, | |
| base::WaitableEvent::InitialState::NOT_SIGNALED); | |
| plugin_thread_.task_runner()->PostTask( | |
| FROM_HERE, base::Bind(&TearDownRemoteHarness, remote_harness_, | |
| &remote_harness_torn_down)); | |
| remote_harness_torn_down.Wait(); | |
| local_harness_->TearDownHarness(); | |
| io_thread_.Stop(); | |
| } | |
| void TwoWayTest::PostTaskOnRemoteHarness(const base::Closure& task) { | |
| base::WaitableEvent task_complete( | |
| base::WaitableEvent::ResetPolicy::MANUAL, | |
| base::WaitableEvent::InitialState::NOT_SIGNALED); | |
| plugin_thread_.task_runner()->PostTask( | |
| FROM_HERE, base::Bind(&RunTaskOnRemoteHarness, task, &task_complete)); | |
| task_complete.Wait(); | |
| } | |
| } // namespace proxy | |
| } // namespace ppapi |