|  | // 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(), 0); | 
|  | } | 
|  |  | 
|  | 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 |