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 "base/basictypes.h" 6 #include "base/compiler_specific.h" 7 #include "ppapi/c/pp_errors.h" 8 #include "ppapi/proxy/connection.h" 9 #include "ppapi/proxy/device_enumeration_resource_helper.h" 10 #include "ppapi/proxy/plugin_message_filter.h" 11 #include "ppapi/proxy/plugin_resource.h" 12 #include "ppapi/proxy/plugin_resource_tracker.h" 13 #include "ppapi/proxy/plugin_var_tracker.h" 14 #include "ppapi/proxy/ppapi_message_utils.h" 15 #include "ppapi/proxy/ppapi_messages.h" 16 #include "ppapi/proxy/ppapi_proxy_test.h" 17 #include "ppapi/shared_impl/ppb_device_ref_shared.h" 18 #include "ppapi/shared_impl/proxy_lock.h" 19 #include "ppapi/shared_impl/var.h" 20 #include "ppapi/thunk/enter.h" 21 #include "ppapi/thunk/ppb_device_ref_api.h" 22 #include "ppapi/thunk/thunk.h" 23 24 namespace ppapi { 25 namespace proxy { 26 27 namespace { 28 29 typedef PluginProxyTest DeviceEnumerationResourceHelperTest; 30 31 Connection GetConnection(PluginProxyTestHarness* harness) { 32 CHECK(harness->GetGlobals()->IsPluginGlobals()); 33 34 return Connection( 35 static_cast<PluginGlobals*>(harness->GetGlobals())->GetBrowserSender(), 36 harness->plugin_dispatcher()); 37 } 38 39 bool CompareDeviceRef(PluginVarTracker* var_tracker, 40 PP_Resource resource, 41 const DeviceRefData& expected) { 42 thunk::EnterResourceNoLock<thunk::PPB_DeviceRef_API> enter(resource, true); 43 if (enter.failed()) 44 return false; 45 46 if (expected.type != enter.object()->GetType()) 47 return false; 48 49 PP_Var name_pp_var = enter.object()->GetName(); 50 bool result = false; 51 do { 52 Var* name_var = var_tracker->GetVar(name_pp_var); 53 if (!name_var) 54 break; 55 StringVar* name_string_var = name_var->AsStringVar(); 56 if (!name_string_var) 57 break; 58 if (expected.name != name_string_var->value()) 59 break; 60 61 result = true; 62 } while (false); 63 var_tracker->ReleaseVar(name_pp_var); 64 return result; 65 } 66 67 class TestResource : public PluginResource { 68 public: 69 TestResource(Connection connection, PP_Instance instance) 70 : PluginResource(connection, instance), 71 device_enumeration_(this) { 72 } 73 74 virtual ~TestResource() {} 75 76 virtual void OnReplyReceived(const ResourceMessageReplyParams& params, 77 const IPC::Message& msg) OVERRIDE { 78 if (!device_enumeration_.HandleReply(params, msg)) 79 PluginResource::OnReplyReceived(params, msg); 80 } 81 82 DeviceEnumerationResourceHelper& device_enumeration() { 83 return device_enumeration_; 84 } 85 86 private: 87 DeviceEnumerationResourceHelper device_enumeration_; 88 89 DISALLOW_COPY_AND_ASSIGN(TestResource); 90 }; 91 92 class TestCallback { 93 public: 94 TestCallback() : called_(false), result_(PP_ERROR_FAILED) { 95 } 96 ~TestCallback() { 97 CHECK(called_); 98 } 99 100 PP_CompletionCallback MakeCompletionCallback() { 101 return PP_MakeCompletionCallback(&CompletionCallbackBody, this); 102 } 103 104 bool called() const { return called_; } 105 int32_t result() const { return result_; } 106 107 private: 108 static void CompletionCallbackBody(void* user_data, int32_t result) { 109 TestCallback* callback = static_cast<TestCallback*>(user_data); 110 111 CHECK(!callback->called_); 112 callback->called_ = true; 113 callback->result_ = result; 114 } 115 116 bool called_; 117 int32_t result_; 118 119 DISALLOW_COPY_AND_ASSIGN(TestCallback); 120 }; 121 122 class TestArrayOutput { 123 public: 124 explicit TestArrayOutput(PluginResourceTracker* resource_tracker) 125 : data_(NULL), 126 count_(0), 127 resource_tracker_(resource_tracker) { 128 } 129 130 ~TestArrayOutput() { 131 if (count_ > 0) { 132 for (size_t i = 0; i < count_; ++i) 133 resource_tracker_->ReleaseResource(data_[i]); 134 delete [] data_; 135 } 136 } 137 138 PP_ArrayOutput MakeArrayOutput() { 139 PP_ArrayOutput array_output = { &GetDataBuffer, this }; 140 return array_output; 141 } 142 143 const PP_Resource* data() const { return data_; } 144 uint32_t count() const { return count_; } 145 146 private: 147 static void* GetDataBuffer(void* user_data, 148 uint32_t element_count, 149 uint32_t element_size) { 150 CHECK_EQ(element_size, sizeof(PP_Resource)); 151 152 TestArrayOutput* output = static_cast<TestArrayOutput*>(user_data); 153 CHECK(!output->data_); 154 155 output->count_ = element_count; 156 if (element_count > 0) 157 output->data_ = new PP_Resource[element_count]; 158 else 159 output->data_ = NULL; 160 161 return output->data_; 162 } 163 164 PP_Resource* data_; 165 uint32_t count_; 166 PluginResourceTracker* resource_tracker_; 167 168 DISALLOW_COPY_AND_ASSIGN(TestArrayOutput); 169 }; 170 171 class TestMonitorDeviceChange { 172 public: 173 explicit TestMonitorDeviceChange(PluginVarTracker* var_tracker) 174 : called_(false), 175 same_as_expected_(false), 176 var_tracker_(var_tracker) { 177 } 178 179 ~TestMonitorDeviceChange() {} 180 181 void SetExpectedResult(const std::vector<DeviceRefData>& expected) { 182 called_ = false; 183 same_as_expected_ = false; 184 expected_ = expected; 185 } 186 187 bool called() const { return called_; } 188 189 bool same_as_expected() const { return same_as_expected_; } 190 191 static void MonitorDeviceChangeCallback(void* user_data, 192 uint32_t device_count, 193 const PP_Resource devices[]) { 194 ProxyAutoLock lock; 195 TestMonitorDeviceChange* helper = 196 static_cast<TestMonitorDeviceChange*>(user_data); 197 CHECK(!helper->called_); 198 199 helper->called_ = true; 200 helper->same_as_expected_ = false; 201 if (device_count != helper->expected_.size()) 202 return; 203 for (size_t i = 0; i < device_count; ++i) { 204 if (!CompareDeviceRef(helper->var_tracker_, devices[i], 205 helper->expected_[i])) { 206 return; 207 } 208 } 209 helper->same_as_expected_ = true; 210 } 211 212 private: 213 bool called_; 214 bool same_as_expected_; 215 std::vector<DeviceRefData> expected_; 216 PluginVarTracker* var_tracker_; 217 218 DISALLOW_COPY_AND_ASSIGN(TestMonitorDeviceChange); 219 }; 220 221 } // namespace 222 223 TEST_F(DeviceEnumerationResourceHelperTest, EnumerateDevices) { 224 ProxyAutoLock lock; 225 226 scoped_refptr<TestResource> resource( 227 new TestResource(GetConnection(this), pp_instance())); 228 DeviceEnumerationResourceHelper& device_enumeration = 229 resource->device_enumeration(); 230 231 TestArrayOutput output(&resource_tracker()); 232 TestCallback callback; 233 scoped_refptr<TrackedCallback> tracked_callback( 234 new TrackedCallback(resource.get(), callback.MakeCompletionCallback())); 235 int32_t result = device_enumeration.EnumerateDevices(output.MakeArrayOutput(), 236 tracked_callback); 237 ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); 238 239 // Should have sent an EnumerateDevices message. 240 ResourceMessageCallParams params; 241 IPC::Message msg; 242 ASSERT_TRUE(sink().GetFirstResourceCallMatching( 243 PpapiHostMsg_DeviceEnumeration_EnumerateDevices::ID, ¶ms, &msg)); 244 245 // Synthesize a response. 246 ResourceMessageReplyParams reply_params(params.pp_resource(), 247 params.sequence()); 248 reply_params.set_result(PP_OK); 249 std::vector<DeviceRefData> data; 250 DeviceRefData data_item; 251 data_item.type = PP_DEVICETYPE_DEV_AUDIOCAPTURE; 252 data_item.name = "name_1"; 253 data_item.id = "id_1"; 254 data.push_back(data_item); 255 data_item.type = PP_DEVICETYPE_DEV_VIDEOCAPTURE; 256 data_item.name = "name_2"; 257 data_item.id = "id_2"; 258 data.push_back(data_item); 259 260 { 261 ProxyAutoUnlock unlock; 262 PluginMessageFilter::DispatchResourceReplyForTest( 263 reply_params, 264 PpapiPluginMsg_DeviceEnumeration_EnumerateDevicesReply(data)); 265 } 266 EXPECT_TRUE(callback.called()); 267 EXPECT_EQ(PP_OK, callback.result()); 268 EXPECT_EQ(2U, output.count()); 269 for (size_t i = 0; i < output.count(); ++i) 270 EXPECT_TRUE(CompareDeviceRef(&var_tracker(), output.data()[i], data[i])); 271 } 272 273 TEST_F(DeviceEnumerationResourceHelperTest, MonitorDeviceChange) { 274 ProxyAutoLock lock; 275 276 scoped_refptr<TestResource> resource( 277 new TestResource(GetConnection(this), pp_instance())); 278 DeviceEnumerationResourceHelper& device_enumeration = 279 resource->device_enumeration(); 280 281 TestMonitorDeviceChange helper(&var_tracker()); 282 283 int32_t result = device_enumeration.MonitorDeviceChange( 284 &TestMonitorDeviceChange::MonitorDeviceChangeCallback, &helper); 285 ASSERT_EQ(PP_OK, result); 286 287 // Should have sent a MonitorDeviceChange message. 288 ResourceMessageCallParams params; 289 IPC::Message msg; 290 ASSERT_TRUE(sink().GetFirstResourceCallMatching( 291 PpapiHostMsg_DeviceEnumeration_MonitorDeviceChange::ID, ¶ms, &msg)); 292 sink().ClearMessages(); 293 294 uint32_t callback_id = 0; 295 ASSERT_TRUE(UnpackMessage<PpapiHostMsg_DeviceEnumeration_MonitorDeviceChange>( 296 msg, &callback_id)); 297 298 ResourceMessageReplyParams reply_params(params.pp_resource(), 0); 299 reply_params.set_result(PP_OK); 300 std::vector<DeviceRefData> data; 301 302 helper.SetExpectedResult(data); 303 304 { 305 ProxyAutoUnlock unlock; 306 // Synthesize a response with no device. 307 PluginMessageFilter::DispatchResourceReplyForTest( 308 reply_params, 309 PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange( 310 callback_id, data)); 311 } 312 EXPECT_TRUE(helper.called() && helper.same_as_expected()); 313 314 DeviceRefData data_item; 315 data_item.type = PP_DEVICETYPE_DEV_AUDIOCAPTURE; 316 data_item.name = "name_1"; 317 data_item.id = "id_1"; 318 data.push_back(data_item); 319 data_item.type = PP_DEVICETYPE_DEV_VIDEOCAPTURE; 320 data_item.name = "name_2"; 321 data_item.id = "id_2"; 322 data.push_back(data_item); 323 324 helper.SetExpectedResult(data); 325 326 { 327 ProxyAutoUnlock unlock; 328 // Synthesize a response with some devices. 329 PluginMessageFilter::DispatchResourceReplyForTest( 330 reply_params, 331 PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange( 332 callback_id, data)); 333 } 334 EXPECT_TRUE(helper.called() && helper.same_as_expected()); 335 336 TestMonitorDeviceChange helper2(&var_tracker()); 337 338 result = device_enumeration.MonitorDeviceChange( 339 &TestMonitorDeviceChange::MonitorDeviceChangeCallback, &helper2); 340 ASSERT_EQ(PP_OK, result); 341 342 // Should have sent another MonitorDeviceChange message. 343 ResourceMessageCallParams params2; 344 IPC::Message msg2; 345 ASSERT_TRUE(sink().GetFirstResourceCallMatching( 346 PpapiHostMsg_DeviceEnumeration_MonitorDeviceChange::ID, ¶ms2, &msg2)); 347 sink().ClearMessages(); 348 349 uint32_t callback_id2 = 0; 350 ASSERT_TRUE(UnpackMessage<PpapiHostMsg_DeviceEnumeration_MonitorDeviceChange>( 351 msg2, &callback_id2)); 352 353 helper.SetExpectedResult(data); 354 helper2.SetExpectedResult(data); 355 { 356 ProxyAutoUnlock unlock; 357 // |helper2| should receive the result while |helper| shouldn't. 358 PluginMessageFilter::DispatchResourceReplyForTest( 359 reply_params, 360 PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange( 361 callback_id2, data)); 362 } 363 EXPECT_TRUE(helper2.called() && helper2.same_as_expected()); 364 EXPECT_FALSE(helper.called()); 365 366 helper.SetExpectedResult(data); 367 helper2.SetExpectedResult(data); 368 { 369 ProxyAutoUnlock unlock; 370 // Even if a message with |callback_id| arrives. |helper| shouldn't receive 371 // the result. 372 PluginMessageFilter::DispatchResourceReplyForTest( 373 reply_params, 374 PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange( 375 callback_id, data)); 376 } 377 EXPECT_FALSE(helper2.called()); 378 EXPECT_FALSE(helper.called()); 379 380 result = device_enumeration.MonitorDeviceChange(NULL, NULL); 381 ASSERT_EQ(PP_OK, result); 382 383 // Should have sent a StopMonitoringDeviceChange message. 384 ResourceMessageCallParams params3; 385 IPC::Message msg3; 386 ASSERT_TRUE(sink().GetFirstResourceCallMatching( 387 PpapiHostMsg_DeviceEnumeration_StopMonitoringDeviceChange::ID, 388 ¶ms3, &msg3)); 389 sink().ClearMessages(); 390 391 helper2.SetExpectedResult(data); 392 { 393 ProxyAutoUnlock unlock; 394 // |helper2| shouldn't receive any result any more. 395 PluginMessageFilter::DispatchResourceReplyForTest( 396 reply_params, 397 PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange( 398 callback_id2, data)); 399 } 400 EXPECT_FALSE(helper2.called()); 401 } 402 403 } // namespace proxy 404 } // namespace ppapi 405