Implement plugin side of sync EnumerateVideoCaptureDevices

This implements the plugin side of PPB_Flash::EnumerateVideoCaptureDevices which is a synchronous version of PPB_VideoCapture::EnumerateVideoDevices. The result is output into a PP_ArrayOutput. This also adds a unittest which does some basic testing of the messages sent, but mainly demonstrates how to write PluginResource unittests when dealing with sync messages (and adds some infrastructure to make it easy to do this).

Once VideoCapture is implemented as a new-style resource, the code for this will simplify a lot.

Review URL: https://chromiumcodereview.appspot.com/11039012

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@161265 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/ppapi/proxy/flash_resource.cc b/ppapi/proxy/flash_resource.cc
new file mode 100644
index 0000000..62e97e16
--- /dev/null
+++ b/ppapi/proxy/flash_resource.cc
@@ -0,0 +1,68 @@
+// 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/flash_resource.h"
+
+#include "ipc/ipc_message.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/proxy/dispatch_reply_message.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/array_writer.h"
+#include "ppapi/thunk/enter.h"
+
+namespace ppapi {
+namespace proxy {
+
+FlashResource::FlashResource(Connection connection, PP_Instance instance)
+    : PluginResource(connection, instance) {
+  SendCreateToRenderer(PpapiHostMsg_Flash_Create());
+}
+
+FlashResource::~FlashResource() {
+}
+
+thunk::PPB_Flash_Functions_API* FlashResource::AsPPB_Flash_Functions_API() {
+  return this;
+}
+
+int32_t FlashResource::EnumerateVideoCaptureDevices(
+    PP_Instance instance,
+    PP_Resource video_capture,
+    const PP_ArrayOutput& devices) {
+  ArrayWriter output;
+  output.set_pp_array_output(devices);
+  if (!output.is_valid())
+    return PP_ERROR_BADARGUMENT;
+
+  thunk::EnterResource<thunk::PPB_VideoCapture_API> enter(video_capture, true);
+  if (enter.failed())
+    return PP_ERROR_NOINTERFACE;
+
+  IPC::Message reply;
+  std::vector<ppapi::DeviceRefData> device_ref_data;
+  int32_t result = CallRendererSync(
+      PpapiHostMsg_Flash_EnumerateVideoCaptureDevices(
+          enter.resource()->host_resource()), &reply);
+  if (result != PP_OK)
+    return result;
+  PpapiPluginMsg_Flash_EnumerateVideoCaptureDevicesReply::Schema::Param p;
+  if (!PpapiPluginMsg_Flash_EnumerateVideoCaptureDevicesReply::Read(&reply, &p))
+    return PP_ERROR_FAILED;
+  device_ref_data = p.a;
+
+  std::vector<scoped_refptr<Resource> > device_resources;
+  for (size_t i = 0; i < device_ref_data.size(); ++i) {
+    scoped_refptr<Resource> resource(new PPB_DeviceRef_Shared(
+        OBJECT_IS_PROXY, instance, device_ref_data[i]));
+    device_resources.push_back(resource);
+  }
+
+  if (!output.StoreResourceVector(device_resources))
+    return PP_ERROR_FAILED;
+
+  return PP_OK;
+}
+
+}  // namespace proxy
+}  // namespace ppapi
diff --git a/ppapi/proxy/flash_resource.h b/ppapi/proxy/flash_resource.h
new file mode 100644
index 0000000..b5a4428
--- /dev/null
+++ b/ppapi/proxy/flash_resource.h
@@ -0,0 +1,41 @@
+// 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.
+
+#ifndef PPAPI_PROXY_FLASH_RESOURCE_H_
+#define PPAPI_PROXY_FLASH_RESOURCE_H_
+
+#include "ppapi/proxy/connection.h"
+#include "ppapi/proxy/plugin_resource.h"
+#include "ppapi/proxy/ppapi_proxy_export.h"
+#include "ppapi/thunk/ppb_flash_functions_api.h"
+
+struct PP_ArrayOutput;
+
+namespace ppapi {
+namespace proxy {
+
+class PPAPI_PROXY_EXPORT FlashResource
+    : public PluginResource,
+      public NON_EXPORTED_BASE(thunk::PPB_Flash_Functions_API) {
+ public:
+  FlashResource(Connection connection, PP_Instance instance);
+  virtual ~FlashResource();
+
+  // Resource overrides.
+  virtual thunk::PPB_Flash_Functions_API* AsPPB_Flash_Functions_API() OVERRIDE;
+
+  // PPB_Flash_Functions_API.
+  virtual int32_t EnumerateVideoCaptureDevices(
+      PP_Instance instance,
+      PP_Resource video_capture,
+      const PP_ArrayOutput& devices) OVERRIDE;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FlashResource);
+};
+
+}  // namespace proxy
+}  // namespace ppapi
+
+#endif  // PPAPI_PROXY_FLASH_RESOURCE_H_
diff --git a/ppapi/proxy/flash_resource_unittest.cc b/ppapi/proxy/flash_resource_unittest.cc
new file mode 100644
index 0000000..901e855
--- /dev/null
+++ b/ppapi/proxy/flash_resource_unittest.cc
@@ -0,0 +1,103 @@
+// 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/c/dev/ppb_video_capture_dev.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/private/ppb_flash.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/proxy/ppapi_proxy_test.h"
+#include "ppapi/shared_impl/scoped_pp_resource.h"
+#include "ppapi/thunk/thunk.h"
+
+namespace ppapi {
+namespace proxy {
+
+namespace {
+
+typedef PluginProxyTest FlashResourceTest;
+
+// This simulates the creation reply message of a VideoCapture resource. This
+// won't be necessary once VideoCapture is converted to the new-style proxy.
+class VideoCaptureCreationHandler : public IPC::Listener {
+ public:
+  VideoCaptureCreationHandler(ResourceMessageTestSink* test_sink,
+                              PP_Instance instance)
+      : test_sink_(test_sink),
+        instance_(instance) {
+  }
+  virtual ~VideoCaptureCreationHandler() {}
+
+  virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE {
+    if (msg.type() != ::PpapiHostMsg_PPBVideoCapture_Create::ID)
+      return false;
+
+    IPC::Message* reply_msg = IPC::SyncMessage::GenerateReply(&msg);
+    HostResource resource;
+    resource.SetHostResource(instance_, 12345);
+    PpapiHostMsg_PPBVideoCapture_Create::WriteReplyParams(reply_msg, resource);
+    test_sink_->SetSyncReplyMessage(reply_msg);
+    return true;
+  }
+ private:
+  ResourceMessageTestSink* test_sink_;
+  PP_Instance instance_;
+};
+
+void* Unused(void* user_data, uint32_t element_count, uint32_t element_size) {
+  return NULL;
+}
+
+}  // namespace
+
+// Does a test of EnumerateVideoCaptureDevices() and reply functionality in
+// the plugin side using the public C interfaces.
+TEST_F(FlashResourceTest, EnumerateVideoCaptureDevices) {
+  // TODO(raymes): This doesn't actually check that the data is converted from
+  // |ppapi::DeviceRefData| to |PPB_DeviceRef| correctly, just that the right
+  // messages are sent.
+
+  // Set up a sync call handler that should return this message.
+  std::vector<ppapi::DeviceRefData> reply_device_ref_data;
+  int32_t expected_result = PP_OK;
+  PpapiPluginMsg_Flash_EnumerateVideoCaptureDevicesReply reply_msg(
+      reply_device_ref_data);
+  ResourceSyncCallHandler enumerate_video_devices_handler(
+      &sink(),
+      PpapiHostMsg_Flash_EnumerateVideoCaptureDevices::ID,
+      expected_result,
+      reply_msg);
+  sink().AddFilter(&enumerate_video_devices_handler);
+
+  // Setup the handler to simulate creation of the video resource.
+  VideoCaptureCreationHandler video_creation_handler(&sink(), pp_instance());
+  sink().AddFilter(&video_creation_handler);
+
+  // Set up the arguments to the call.
+  ScopedPPResource video_capture(ScopedPPResource::PassRef(),
+      ::ppapi::thunk::GetPPB_VideoCapture_Dev_0_2_Thunk()->Create(
+          pp_instance()));
+  std::vector<PP_Resource> unused;
+  PP_ArrayOutput output;
+  output.GetDataBuffer = &Unused;
+  output.user_data = &unused;
+
+  // Make the call.
+  const PPB_Flash_12_6* flash_iface = ::ppapi::thunk::GetPPB_Flash_12_6_Thunk();
+  int32_t actual_result = flash_iface->EnumerateVideoCaptureDevices(
+      pp_instance(), video_capture.get(), output);
+
+  // Check the result is as expected.
+  EXPECT_EQ(expected_result, actual_result);
+
+  // Should have sent an "EnumerateVideoCaptureDevices" message.
+  ASSERT_TRUE(enumerate_video_devices_handler.last_handled_msg().type() ==
+      PpapiHostMsg_Flash_EnumerateVideoCaptureDevices::ID);
+
+  // Remove the filter or it will be destroyed before the sink() is destroyed.
+  sink().RemoveFilter(&enumerate_video_devices_handler);
+  sink().RemoveFilter(&video_creation_handler);
+}
+
+}  // namespace proxy
+}  // namespace ppapi
diff --git a/ppapi/proxy/plugin_dispatcher.cc b/ppapi/proxy/plugin_dispatcher.cc
index 51272fdd..8e05a1c 100644
--- a/ppapi/proxy/plugin_dispatcher.cc
+++ b/ppapi/proxy/plugin_dispatcher.cc
@@ -14,6 +14,7 @@
 #include "base/debug/trace_event.h"
 #include "ppapi/c/pp_errors.h"
 #include "ppapi/c/ppp_instance.h"
+#include "ppapi/proxy/flash_resource.h"
 #include "ppapi/proxy/gamepad_resource.h"
 #include "ppapi/proxy/interface_list.h"
 #include "ppapi/proxy/interface_proxy.h"
diff --git a/ppapi/proxy/plugin_dispatcher.h b/ppapi/proxy/plugin_dispatcher.h
index fe2999cf..017f957 100644
--- a/ppapi/proxy/plugin_dispatcher.h
+++ b/ppapi/proxy/plugin_dispatcher.h
@@ -34,6 +34,7 @@
 
 namespace proxy {
 
+class FlashResource;
 class GamepadResource;
 class ResourceMessageReplyParams;
 
@@ -49,8 +50,10 @@
   // When non-NULL, indicates the callback to execute when mouse lock is lost.
   scoped_refptr<TrackedCallback> mouse_lock_callback;
 
-  // Lazily created the first time the plugin requests gamepad data.
+  // The following are lazily created the first time the plugin requests them.
+  // (These are singleton-style resources).
   scoped_refptr<GamepadResource> gamepad_resource;
+  scoped_refptr<FlashResource> flash_resource;
 
   // Calls to |RequestSurroundingText()| are done by posted tasks. Track whether
   // a) a task is pending, to avoid redundant calls, and b) whether we should
diff --git a/ppapi/proxy/ppapi_messages.h b/ppapi/proxy/ppapi_messages.h
index 863cc4c6..238564c 100644
--- a/ppapi/proxy/ppapi_messages.h
+++ b/ppapi/proxy/ppapi_messages.h
@@ -1675,4 +1675,11 @@
                      uint32_t /* table */)
 IPC_MESSAGE_CONTROL1(PpapiPluginMsg_FlashFontFile_GetFontTableReply,
                      std::string /* output */)
+
+// Flash functions.
+IPC_MESSAGE_CONTROL0(PpapiHostMsg_Flash_Create)
+IPC_MESSAGE_CONTROL1(PpapiHostMsg_Flash_EnumerateVideoCaptureDevices,
+                     ppapi::HostResource /* video_capture */)
+IPC_MESSAGE_CONTROL1(PpapiPluginMsg_Flash_EnumerateVideoCaptureDevicesReply,
+                     std::vector<ppapi::DeviceRefData> /* devices */)
 #endif  // !defined(OS_NACL) && !defined(NACL_WIN64)
diff --git a/ppapi/proxy/ppb_flash_proxy.h b/ppapi/proxy/ppb_flash_proxy.h
index 03ecf89..477b1ae 100644
--- a/ppapi/proxy/ppb_flash_proxy.h
+++ b/ppapi/proxy/ppb_flash_proxy.h
@@ -32,6 +32,11 @@
 struct SerializedDirEntry;
 class SerializedVarReturnValue;
 
+/////////////////////////// WARNING:DEPRECTATED ////////////////////////////////
+// Please do not add any new functions to this proxy. They should be
+// implemented in the new-style resource proxy (see flash_resource.h).
+// TODO(raymes): All of these functions should be moved to the new-style proxy.
+////////////////////////////////////////////////////////////////////////////////
 class PPB_Flash_Proxy : public InterfaceProxy, public PPB_Flash_Shared {
  public:
   explicit PPB_Flash_Proxy(Dispatcher* dispatcher);
diff --git a/ppapi/proxy/ppb_instance_proxy.cc b/ppapi/proxy/ppb_instance_proxy.cc
index 5f0c1633..3734512 100644
--- a/ppapi/proxy/ppb_instance_proxy.cc
+++ b/ppapi/proxy/ppb_instance_proxy.cc
@@ -4,6 +4,7 @@
 
 #include "ppapi/proxy/ppb_instance_proxy.h"
 
+#include "build/build_config.h"
 #include "ppapi/c/pp_errors.h"
 #include "ppapi/c/pp_time.h"
 #include "ppapi/c/pp_var.h"
@@ -14,6 +15,7 @@
 #include "ppapi/c/private/pp_content_decryptor.h"
 #include "ppapi/proxy/content_decryptor_private_serializer.h"
 #include "ppapi/proxy/enter_proxy.h"
+#include "ppapi/proxy/flash_resource.h"
 #include "ppapi/proxy/gamepad_resource.h"
 #include "ppapi/proxy/host_dispatcher.h"
 #include "ppapi/proxy/plugin_dispatcher.h"
@@ -313,6 +315,28 @@
   return static_cast<PPB_Flash_Proxy*>(ip);
 }
 
+thunk::PPB_Flash_Functions_API* PPB_Instance_Proxy::GetFlashFunctionsAPI(
+    PP_Instance instance) {
+#if !defined(OS_NACL) && !defined(NACL_WIN64)
+  InstanceData* data = static_cast<PluginDispatcher*>(dispatcher())->
+      GetInstanceData(instance);
+  if (!data)
+    return NULL;
+
+  if (!data->flash_resource.get()) {
+    Connection connection(
+        PluginGlobals::Get()->plugin_proxy_delegate()->GetBrowserSender(),
+        dispatcher());
+    data->flash_resource = new FlashResource(connection, instance);
+  }
+  return data->flash_resource.get();
+#else
+  // Flash functions aren't implemented for nacl.
+  NOTIMPLEMENTED();
+  return NULL;
+#endif  // !defined(OS_NACL) && !defined(NACL_WIN64)
+}
+
 thunk::PPB_Gamepad_API* PPB_Instance_Proxy::GetGamepadAPI(
     PP_Instance instance) {
   InstanceData* data = static_cast<PluginDispatcher*>(dispatcher())->
diff --git a/ppapi/proxy/ppb_instance_proxy.h b/ppapi/proxy/ppb_instance_proxy.h
index 61155b6d..274d1e30 100644
--- a/ppapi/proxy/ppb_instance_proxy.h
+++ b/ppapi/proxy/ppb_instance_proxy.h
@@ -70,6 +70,8 @@
   virtual PP_Bool GetScreenSize(PP_Instance instance,
                                 PP_Size* size) OVERRIDE;
   virtual thunk::PPB_Flash_API* GetFlashAPI() OVERRIDE;
+  virtual thunk::PPB_Flash_Functions_API* GetFlashFunctionsAPI(
+      PP_Instance instance) OVERRIDE;
   virtual thunk::PPB_Gamepad_API* GetGamepadAPI(PP_Instance instance) OVERRIDE;
   virtual int32_t RequestInputEvents(PP_Instance instance,
                                      uint32_t event_classes) OVERRIDE;
diff --git a/ppapi/proxy/resource_message_test_sink.cc b/ppapi/proxy/resource_message_test_sink.cc
index 7daf435..a2920e3f 100644
--- a/ppapi/proxy/resource_message_test_sink.cc
+++ b/ppapi/proxy/resource_message_test_sink.cc
@@ -42,6 +42,30 @@
 ResourceMessageTestSink::~ResourceMessageTestSink() {
 }
 
+bool ResourceMessageTestSink::Send(IPC::Message* msg) {
+  int message_id = 0;
+  scoped_ptr<IPC::MessageReplyDeserializer> reply_deserializer;
+  if (msg->is_sync()) {
+    reply_deserializer.reset(
+        static_cast<IPC::SyncMessage*>(msg)->GetReplyDeserializer());
+    message_id = IPC::SyncMessage::GetMessageId(*msg);
+  }
+  bool result = IPC::TestSink::Send(msg);  // Deletes |msg|.
+  if (sync_reply_msg_.get()) {
+    // |sync_reply_msg_| should always be a reply to the pending sync message.
+    DCHECK(IPC::SyncMessage::IsMessageReplyTo(*sync_reply_msg_.get(),
+                                              message_id));
+    reply_deserializer->SerializeOutputParameters(*sync_reply_msg_.get());
+    sync_reply_msg_.reset(NULL);
+  }
+  return result;
+}
+
+void ResourceMessageTestSink::SetSyncReplyMessage(IPC::Message* reply_msg) {
+  DCHECK(!sync_reply_msg_.get());
+  sync_reply_msg_.reset(reply_msg);
+}
+
 bool ResourceMessageTestSink::GetFirstResourceCallMatching(
     uint32 id,
     ResourceMessageCallParams* params,
@@ -60,5 +84,43 @@
       *this, id, params, nested_msg);
 }
 
+ResourceSyncCallHandler::ResourceSyncCallHandler(
+    ResourceMessageTestSink* test_sink,
+    uint32 incoming_type,
+    int32_t result,
+    const IPC::Message& reply_msg)
+    : test_sink_(test_sink),
+      incoming_type_(incoming_type),
+      result_(result),
+      reply_msg_(reply_msg) {
+}
+
+ResourceSyncCallHandler::~ResourceSyncCallHandler() {
+}
+
+bool ResourceSyncCallHandler::OnMessageReceived(const IPC::Message& msg) {
+  if (msg.type() != PpapiHostMsg_ResourceSyncCall::ID)
+    return false;
+  PpapiHostMsg_ResourceSyncCall::Schema::SendParam send_params;
+  bool success = PpapiHostMsg_ResourceSyncCall::ReadSendParam(
+      &msg, &send_params);
+  DCHECK(success);
+  ResourceMessageCallParams call_params = send_params.a;
+  IPC::Message call_msg = send_params.b;
+  if (call_msg.type() != incoming_type_)
+    return false;
+  IPC::Message* wrapper_reply_msg = IPC::SyncMessage::GenerateReply(&msg);
+  ResourceMessageReplyParams reply_params(call_params.pp_resource(),
+                                          call_params.sequence());
+  reply_params.set_result(result_);
+  PpapiHostMsg_ResourceSyncCall::WriteReplyParams(
+      wrapper_reply_msg, reply_params, reply_msg_);
+  test_sink_->SetSyncReplyMessage(wrapper_reply_msg);
+
+  // Stash a copy of the message for inspection later.
+  last_handled_msg_ = call_msg;
+  return true;
+}
+
 }  // namespace proxy
 }  // namespace ppapi
diff --git a/ppapi/proxy/resource_message_test_sink.h b/ppapi/proxy/resource_message_test_sink.h
index bf74bec..7694fb0 100644
--- a/ppapi/proxy/resource_message_test_sink.h
+++ b/ppapi/proxy/resource_message_test_sink.h
@@ -6,6 +6,7 @@
 #define PPAPI_PROXY_RESOURCE_MESSAGE_TEST_SINK_H_
 
 #include "ipc/ipc_test_sink.h"
+#include "ppapi/c/pp_stdint.h"
 
 namespace ppapi {
 namespace proxy {
@@ -20,6 +21,14 @@
   ResourceMessageTestSink();
   virtual ~ResourceMessageTestSink();
 
+  // IPC::TestSink.
+  // Overridden to handle sync messages.
+  virtual bool Send(IPC::Message* msg) OVERRIDE;
+
+  // Sets the reply message that will be returned to the next sync message sent.
+  // This test sink owns any reply messages passed into this method.
+  void SetSyncReplyMessage(IPC::Message* reply_msg);
+
   // Searches the queue for the first resource call message with a nested
   // message matching the given ID. On success, returns true and populates the
   // givem params and nested message.
@@ -33,6 +42,46 @@
       uint32 id,
       ResourceMessageReplyParams* params,
       IPC::Message* nested_msg);
+
+ private:
+  scoped_ptr<IPC::Message> sync_reply_msg_;
+};
+
+// This is a message handler which generates reply messages for synchronous
+// resource calls. This allows unit testing of the plugin side of resources
+// which send sync messages. If you want to reply to a sync message type named
+// |PpapiHostMsg_X_Y| with |PpapiPluginMsg_X_YReply| then usage would be as
+// follows (from within |PluginProxyTest|s):
+//
+// PpapiHostMsg_X_YReply my_reply;
+// ResourceSyncCallHandler handler(&sink(),
+//                                 PpapiHostMsg_X_Y::ID,
+//                                 PP_OK,
+//                                 my_reply);
+// sink().AddFilter(&handler);
+// // Do stuff to send a sync message ...
+// // You can check handler.last_handled_msg() to ensure the correct message was
+// // handled.
+// sink().RemoveFilter(&handler);
+class ResourceSyncCallHandler : public IPC::Listener {
+ public:
+  ResourceSyncCallHandler(ResourceMessageTestSink* test_sink,
+                          uint32 incoming_type,
+                          int32_t result,
+                          const IPC::Message& reply_msg);
+  virtual ~ResourceSyncCallHandler();
+
+  // IPC::Listener.
+  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+  IPC::Message last_handled_msg() { return last_handled_msg_; }
+
+ private:
+  ResourceMessageTestSink* test_sink_;
+  uint32 incoming_type_;
+  int32_t result_;
+  IPC::Message reply_msg_;
+  IPC::Message last_handled_msg_;
 };
 
 }  // namespace proxy