| // 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 <stddef.h> | 
 | #include <stdint.h> | 
 |  | 
 | #include "base/compiler_specific.h" | 
 | #include "base/macros.h" | 
 | #include "ppapi/c/pp_errors.h" | 
 | #include "ppapi/proxy/connection.h" | 
 | #include "ppapi/proxy/device_enumeration_resource_helper.h" | 
 | #include "ppapi/proxy/plugin_message_filter.h" | 
 | #include "ppapi/proxy/plugin_resource.h" | 
 | #include "ppapi/proxy/plugin_resource_tracker.h" | 
 | #include "ppapi/proxy/plugin_var_tracker.h" | 
 | #include "ppapi/proxy/ppapi_message_utils.h" | 
 | #include "ppapi/proxy/ppapi_messages.h" | 
 | #include "ppapi/proxy/ppapi_proxy_test.h" | 
 | #include "ppapi/shared_impl/ppb_device_ref_shared.h" | 
 | #include "ppapi/shared_impl/proxy_lock.h" | 
 | #include "ppapi/shared_impl/var.h" | 
 | #include "ppapi/thunk/enter.h" | 
 | #include "ppapi/thunk/ppb_device_ref_api.h" | 
 | #include "ppapi/thunk/thunk.h" | 
 |  | 
 | namespace ppapi { | 
 | namespace proxy { | 
 |  | 
 | namespace { | 
 |  | 
 | typedef PluginProxyTest DeviceEnumerationResourceHelperTest; | 
 |  | 
 | Connection GetConnection(PluginProxyTestHarness* harness) { | 
 |   CHECK(harness->GetGlobals()->IsPluginGlobals()); | 
 |  | 
 |   return Connection( | 
 |       static_cast<PluginGlobals*>(harness->GetGlobals())->GetBrowserSender(), | 
 |       harness->plugin_dispatcher()); | 
 | } | 
 |  | 
 | bool CompareDeviceRef(PluginVarTracker* var_tracker, | 
 |                       PP_Resource resource, | 
 |                       const DeviceRefData& expected) { | 
 |   thunk::EnterResourceNoLock<thunk::PPB_DeviceRef_API> enter(resource, true); | 
 |   if (enter.failed()) | 
 |     return false; | 
 |  | 
 |   if (expected.type != enter.object()->GetType()) | 
 |     return false; | 
 |  | 
 |   PP_Var name_pp_var = enter.object()->GetName(); | 
 |   bool result = false; | 
 |   do { | 
 |     Var* name_var = var_tracker->GetVar(name_pp_var); | 
 |     if (!name_var) | 
 |       break; | 
 |     StringVar* name_string_var = name_var->AsStringVar(); | 
 |     if (!name_string_var) | 
 |       break; | 
 |     if (expected.name != name_string_var->value()) | 
 |       break; | 
 |  | 
 |     result = true; | 
 |   } while (false); | 
 |   var_tracker->ReleaseVar(name_pp_var); | 
 |   return result; | 
 | } | 
 |  | 
 | class TestResource : public PluginResource { | 
 |  public: | 
 |   TestResource(Connection connection, PP_Instance instance) | 
 |       : PluginResource(connection, instance), | 
 |         device_enumeration_(this) { | 
 |   } | 
 |  | 
 |   ~TestResource() override {} | 
 |  | 
 |   void OnReplyReceived(const ResourceMessageReplyParams& params, | 
 |                        const IPC::Message& msg) override { | 
 |     if (!device_enumeration_.HandleReply(params, msg)) | 
 |       PluginResource::OnReplyReceived(params, msg); | 
 |   } | 
 |  | 
 |   DeviceEnumerationResourceHelper& device_enumeration() { | 
 |     return device_enumeration_; | 
 |   } | 
 |  | 
 |  private: | 
 |   DeviceEnumerationResourceHelper device_enumeration_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(TestResource); | 
 | }; | 
 |  | 
 | class TestCallback { | 
 |  public: | 
 |   TestCallback() : called_(false), result_(PP_ERROR_FAILED) { | 
 |   } | 
 |   ~TestCallback() { | 
 |     CHECK(called_); | 
 |   } | 
 |  | 
 |   PP_CompletionCallback MakeCompletionCallback() { | 
 |     return PP_MakeCompletionCallback(&CompletionCallbackBody, this); | 
 |   } | 
 |  | 
 |   bool called() const { return called_; } | 
 |   int32_t result() const { return result_; } | 
 |  | 
 |  private: | 
 |   static void CompletionCallbackBody(void* user_data, int32_t result) { | 
 |     TestCallback* callback = static_cast<TestCallback*>(user_data); | 
 |  | 
 |     CHECK(!callback->called_); | 
 |     callback->called_ = true; | 
 |     callback->result_ = result; | 
 |   } | 
 |  | 
 |   bool called_; | 
 |   int32_t result_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(TestCallback); | 
 | }; | 
 |  | 
 | class TestArrayOutput { | 
 |  public: | 
 |   explicit TestArrayOutput(PluginResourceTracker* resource_tracker) | 
 |       : data_(NULL), | 
 |         count_(0), | 
 |         resource_tracker_(resource_tracker) { | 
 |   } | 
 |  | 
 |   ~TestArrayOutput() { | 
 |     if (count_ > 0) { | 
 |       for (size_t i = 0; i < count_; ++i) | 
 |         resource_tracker_->ReleaseResource(data_[i]); | 
 |       delete [] data_; | 
 |     } | 
 |   } | 
 |  | 
 |   PP_ArrayOutput MakeArrayOutput() { | 
 |     PP_ArrayOutput array_output = { &GetDataBuffer, this }; | 
 |     return array_output; | 
 |   } | 
 |  | 
 |   const PP_Resource* data() const { return data_; } | 
 |   uint32_t count() const { return count_; } | 
 |  | 
 |  private: | 
 |   static void* GetDataBuffer(void* user_data, | 
 |                              uint32_t element_count, | 
 |                              uint32_t element_size) { | 
 |     CHECK_EQ(element_size, sizeof(PP_Resource)); | 
 |  | 
 |     TestArrayOutput* output = static_cast<TestArrayOutput*>(user_data); | 
 |     CHECK(!output->data_); | 
 |  | 
 |     output->count_ = element_count; | 
 |     if (element_count > 0) | 
 |       output->data_ = new PP_Resource[element_count]; | 
 |     else | 
 |       output->data_ = NULL; | 
 |  | 
 |     return output->data_; | 
 |   } | 
 |  | 
 |   PP_Resource* data_; | 
 |   uint32_t count_; | 
 |   PluginResourceTracker* resource_tracker_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(TestArrayOutput); | 
 | }; | 
 |  | 
 | class TestMonitorDeviceChange { | 
 |  public: | 
 |   explicit TestMonitorDeviceChange(PluginVarTracker* var_tracker) | 
 |       : called_(false), | 
 |         same_as_expected_(false), | 
 |         var_tracker_(var_tracker) { | 
 |   } | 
 |  | 
 |   ~TestMonitorDeviceChange() {} | 
 |  | 
 |   void SetExpectedResult(const std::vector<DeviceRefData>& expected) { | 
 |     called_ = false; | 
 |     same_as_expected_ = false; | 
 |     expected_ = expected; | 
 |   } | 
 |  | 
 |   bool called() const { return called_; } | 
 |  | 
 |   bool same_as_expected() const { return same_as_expected_; } | 
 |  | 
 |   static void MonitorDeviceChangeCallback(void* user_data, | 
 |                                           uint32_t device_count, | 
 |                                           const PP_Resource devices[]) { | 
 |     ProxyAutoLock lock; | 
 |     TestMonitorDeviceChange* helper = | 
 |         static_cast<TestMonitorDeviceChange*>(user_data); | 
 |     CHECK(!helper->called_); | 
 |  | 
 |     helper->called_ = true; | 
 |     helper->same_as_expected_ = false; | 
 |     if (device_count != helper->expected_.size()) | 
 |       return; | 
 |     for (size_t i = 0; i < device_count; ++i) { | 
 |       if (!CompareDeviceRef(helper->var_tracker_, devices[i], | 
 |                             helper->expected_[i])) { | 
 |         return; | 
 |       } | 
 |     } | 
 |     helper->same_as_expected_ = true; | 
 |   } | 
 |  | 
 |  private: | 
 |   bool called_; | 
 |   bool same_as_expected_; | 
 |   std::vector<DeviceRefData> expected_; | 
 |   PluginVarTracker* var_tracker_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(TestMonitorDeviceChange); | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | TEST_F(DeviceEnumerationResourceHelperTest, EnumerateDevices) { | 
 |   ProxyAutoLock lock; | 
 |  | 
 |   scoped_refptr<TestResource> resource( | 
 |       new TestResource(GetConnection(this), pp_instance())); | 
 |   DeviceEnumerationResourceHelper& device_enumeration = | 
 |       resource->device_enumeration(); | 
 |  | 
 |   TestArrayOutput output(&resource_tracker()); | 
 |   TestCallback callback; | 
 |   scoped_refptr<TrackedCallback> tracked_callback( | 
 |       new TrackedCallback(resource.get(), callback.MakeCompletionCallback())); | 
 |   int32_t result = device_enumeration.EnumerateDevices(output.MakeArrayOutput(), | 
 |                                                        tracked_callback); | 
 |   ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); | 
 |  | 
 |   // Should have sent an EnumerateDevices message. | 
 |   ResourceMessageCallParams params; | 
 |   IPC::Message msg; | 
 |   ASSERT_TRUE(sink().GetFirstResourceCallMatching( | 
 |       PpapiHostMsg_DeviceEnumeration_EnumerateDevices::ID, ¶ms, &msg)); | 
 |  | 
 |   // Synthesize a response. | 
 |   ResourceMessageReplyParams reply_params(params.pp_resource(), | 
 |                                           params.sequence()); | 
 |   reply_params.set_result(PP_OK); | 
 |   std::vector<DeviceRefData> data; | 
 |   DeviceRefData data_item; | 
 |   data_item.type = PP_DEVICETYPE_DEV_AUDIOCAPTURE; | 
 |   data_item.name = "name_1"; | 
 |   data_item.id = "id_1"; | 
 |   data.push_back(data_item); | 
 |   data_item.type = PP_DEVICETYPE_DEV_VIDEOCAPTURE; | 
 |   data_item.name = "name_2"; | 
 |   data_item.id = "id_2"; | 
 |   data.push_back(data_item); | 
 |  | 
 |   { | 
 |     ProxyAutoUnlock unlock; | 
 |     PluginMessageFilter::DispatchResourceReplyForTest( | 
 |         reply_params, | 
 |         PpapiPluginMsg_DeviceEnumeration_EnumerateDevicesReply(data)); | 
 |   } | 
 |   EXPECT_TRUE(callback.called()); | 
 |   EXPECT_EQ(PP_OK, callback.result()); | 
 |   EXPECT_EQ(2U, output.count()); | 
 |   for (size_t i = 0; i < output.count(); ++i) | 
 |     EXPECT_TRUE(CompareDeviceRef(&var_tracker(), output.data()[i], data[i])); | 
 | } | 
 |  | 
 | TEST_F(DeviceEnumerationResourceHelperTest, MonitorDeviceChange) { | 
 |   ProxyAutoLock lock; | 
 |  | 
 |   scoped_refptr<TestResource> resource( | 
 |       new TestResource(GetConnection(this), pp_instance())); | 
 |   DeviceEnumerationResourceHelper& device_enumeration = | 
 |       resource->device_enumeration(); | 
 |  | 
 |   TestMonitorDeviceChange helper(&var_tracker()); | 
 |  | 
 |   int32_t result = device_enumeration.MonitorDeviceChange( | 
 |       &TestMonitorDeviceChange::MonitorDeviceChangeCallback, &helper); | 
 |   ASSERT_EQ(PP_OK, result); | 
 |  | 
 |   // Should have sent a MonitorDeviceChange message. | 
 |   ResourceMessageCallParams params; | 
 |   IPC::Message msg; | 
 |   ASSERT_TRUE(sink().GetFirstResourceCallMatching( | 
 |       PpapiHostMsg_DeviceEnumeration_MonitorDeviceChange::ID, ¶ms, &msg)); | 
 |   sink().ClearMessages(); | 
 |  | 
 |   uint32_t callback_id = 0; | 
 |   ASSERT_TRUE(UnpackMessage<PpapiHostMsg_DeviceEnumeration_MonitorDeviceChange>( | 
 |       msg, &callback_id)); | 
 |  | 
 |   ResourceMessageReplyParams reply_params(params.pp_resource(), 0); | 
 |   reply_params.set_result(PP_OK); | 
 |   std::vector<DeviceRefData> data; | 
 |  | 
 |   helper.SetExpectedResult(data); | 
 |  | 
 |   { | 
 |     ProxyAutoUnlock unlock; | 
 |     // Synthesize a response with no device. | 
 |     PluginMessageFilter::DispatchResourceReplyForTest( | 
 |         reply_params, | 
 |         PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange( | 
 |             callback_id, data)); | 
 |   } | 
 |   EXPECT_TRUE(helper.called() && helper.same_as_expected()); | 
 |  | 
 |   DeviceRefData data_item; | 
 |   data_item.type = PP_DEVICETYPE_DEV_AUDIOCAPTURE; | 
 |   data_item.name = "name_1"; | 
 |   data_item.id = "id_1"; | 
 |   data.push_back(data_item); | 
 |   data_item.type = PP_DEVICETYPE_DEV_VIDEOCAPTURE; | 
 |   data_item.name = "name_2"; | 
 |   data_item.id = "id_2"; | 
 |   data.push_back(data_item); | 
 |  | 
 |   helper.SetExpectedResult(data); | 
 |  | 
 |   { | 
 |     ProxyAutoUnlock unlock; | 
 |     // Synthesize a response with some devices. | 
 |     PluginMessageFilter::DispatchResourceReplyForTest( | 
 |         reply_params, | 
 |         PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange( | 
 |             callback_id, data)); | 
 |   } | 
 |   EXPECT_TRUE(helper.called() && helper.same_as_expected()); | 
 |  | 
 |   TestMonitorDeviceChange helper2(&var_tracker()); | 
 |  | 
 |   result = device_enumeration.MonitorDeviceChange( | 
 |       &TestMonitorDeviceChange::MonitorDeviceChangeCallback, &helper2); | 
 |   ASSERT_EQ(PP_OK, result); | 
 |  | 
 |   // Should have sent another MonitorDeviceChange message. | 
 |   ResourceMessageCallParams params2; | 
 |   IPC::Message msg2; | 
 |   ASSERT_TRUE(sink().GetFirstResourceCallMatching( | 
 |       PpapiHostMsg_DeviceEnumeration_MonitorDeviceChange::ID, ¶ms2, &msg2)); | 
 |   sink().ClearMessages(); | 
 |  | 
 |   uint32_t callback_id2 = 0; | 
 |   ASSERT_TRUE(UnpackMessage<PpapiHostMsg_DeviceEnumeration_MonitorDeviceChange>( | 
 |       msg2, &callback_id2)); | 
 |  | 
 |   helper.SetExpectedResult(data); | 
 |   helper2.SetExpectedResult(data); | 
 |   { | 
 |     ProxyAutoUnlock unlock; | 
 |     // |helper2| should receive the result while |helper| shouldn't. | 
 |     PluginMessageFilter::DispatchResourceReplyForTest( | 
 |         reply_params, | 
 |         PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange( | 
 |             callback_id2, data)); | 
 |   } | 
 |   EXPECT_TRUE(helper2.called() && helper2.same_as_expected()); | 
 |   EXPECT_FALSE(helper.called()); | 
 |  | 
 |   helper.SetExpectedResult(data); | 
 |   helper2.SetExpectedResult(data); | 
 |   { | 
 |     ProxyAutoUnlock unlock; | 
 |     // Even if a message with |callback_id| arrives. |helper| shouldn't receive | 
 |     // the result. | 
 |     PluginMessageFilter::DispatchResourceReplyForTest( | 
 |         reply_params, | 
 |         PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange( | 
 |             callback_id, data)); | 
 |   } | 
 |   EXPECT_FALSE(helper2.called()); | 
 |   EXPECT_FALSE(helper.called()); | 
 |  | 
 |   result = device_enumeration.MonitorDeviceChange(NULL, NULL); | 
 |   ASSERT_EQ(PP_OK, result); | 
 |  | 
 |   // Should have sent a StopMonitoringDeviceChange message. | 
 |   ResourceMessageCallParams params3; | 
 |   IPC::Message msg3; | 
 |   ASSERT_TRUE(sink().GetFirstResourceCallMatching( | 
 |       PpapiHostMsg_DeviceEnumeration_StopMonitoringDeviceChange::ID, | 
 |       ¶ms3, &msg3)); | 
 |   sink().ClearMessages(); | 
 |  | 
 |   helper2.SetExpectedResult(data); | 
 |   { | 
 |     ProxyAutoUnlock unlock; | 
 |     // |helper2| shouldn't receive any result any more. | 
 |     PluginMessageFilter::DispatchResourceReplyForTest( | 
 |         reply_params, | 
 |         PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange( | 
 |             callback_id2, data)); | 
 |   } | 
 |   EXPECT_FALSE(helper2.called()); | 
 | } | 
 |  | 
 | }  // namespace proxy | 
 | }  // namespace ppapi |