[go: nahoru, domu]

Adds associated interface support to IPC::Channel

Provides a way of registering associated interface bindings with
an IPC::Channel endpoint and acquiring proxies to remote interfaces
on the other side.

Support for this is exposed by IPC::Channel but only implemented
in IPC::ChannelMojo.

This is part a series of CLs to support Channel-associated interfaces.

BUG=612500

Committed: https://crrev.com/dc88e5075878f16cde23b910d2ce19aa862129e4
Review-Url: https://codereview.chromium.org/2137353002
Cr-Original-Commit-Position: refs/heads/master@{#405316}
Cr-Commit-Position: refs/heads/master@{#405364}
diff --git a/ipc/BUILD.gn b/ipc/BUILD.gn
index 34cab68..8dba71d 100644
--- a/ipc/BUILD.gn
+++ b/ipc/BUILD.gn
@@ -167,6 +167,13 @@
   ]
 }
 
+mojom("test_interfaces") {
+  testonly = true
+  sources = [
+    "ipc_test.mojom",
+  ]
+}
+
 # This is provided as a separate target so other targets can provide param
 # traits implementations without necessarily linking to all of IPC.
 source_set("param_traits") {
@@ -235,6 +242,7 @@
       ":ipc",
       ":mojom",
       ":run_all_unittests",
+      ":test_interfaces",
       ":test_support",
       "//base",
       "//base:i18n",
diff --git a/ipc/ipc.gyp b/ipc/ipc.gyp
index 3226899..56242d6 100644
--- a/ipc/ipc.gyp
+++ b/ipc/ipc.gyp
@@ -61,6 +61,14 @@
       ],
     },
     {
+      'target_name': 'ipc_test_interfaces',
+      'type': 'static_library',
+      'sources': [
+        'ipc_test.mojom',
+      ],
+      'includes': [ '../mojo/mojom_bindings_generator.gypi' ],
+    },
+    {
       'target_name': 'ipc_run_all_unittests',
       'type': 'static_library',
       'dependencies': [
@@ -83,6 +91,7 @@
       'dependencies': [
         'ipc',
         'ipc_run_all_unittests',
+        'ipc_test_interfaces',
         'test_support_ipc',
         '../base/base.gyp:base',
         '../base/base.gyp:base_i18n',
diff --git a/ipc/ipc.mojom b/ipc/ipc.mojom
index 681da12..28d73fb 100644
--- a/ipc/ipc.mojom
+++ b/ipc/ipc.mojom
@@ -17,8 +17,14 @@
   Type type;
 };
 
+// A placeholder interface type since we don't yet support generic associated
+// message pipe handles.
+interface GenericInterface {};
+
 interface Channel {
   Receive(array<uint8> data, array<SerializedHandle>? handles);
+
+  GetAssociatedInterface(string name, associated GenericInterface& request);
 };
 
 // An interface for connecting a pair of Channel interfaces representing a
diff --git a/ipc/ipc_channel.h b/ipc/ipc_channel.h
index 09baf6c..1fc9c6c 100644
--- a/ipc/ipc_channel.h
+++ b/ipc/ipc_channel.h
@@ -18,6 +18,10 @@
 #include "ipc/ipc_channel_handle.h"
 #include "ipc/ipc_endpoint.h"
 #include "ipc/ipc_message.h"
+#include "mojo/public/cpp/bindings/associated_group.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+#include "mojo/public/cpp/bindings/associated_interface_request.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
 
 #if defined(OS_POSIX)
 #include <sys/types.h>
@@ -93,6 +97,62 @@
     CLOSE_FD_MESSAGE_TYPE = HELLO_MESSAGE_TYPE - 1
   };
 
+  // Helper interface a Channel may implement to expose support for associated
+  // Mojo interfaces.
+  class IPC_EXPORT AssociatedInterfaceSupport {
+   public:
+    using GenericAssociatedInterfaceFactory =
+        base::Callback<void(mojo::ScopedInterfaceEndpointHandle)>;
+
+    virtual ~AssociatedInterfaceSupport() {}
+
+    // Accesses the AssociatedGroup used to associate new interface endpoints
+    // with this Channel.
+    virtual mojo::AssociatedGroup* GetAssociatedGroup() = 0;
+
+    // Adds an interface factory to this channel for interface |name|.
+    virtual void AddGenericAssociatedInterface(
+        const std::string& name,
+        const GenericAssociatedInterfaceFactory& factory) = 0;
+
+    // Requests an associated interface from the remote endpoint.
+    virtual void GetGenericRemoteAssociatedInterface(
+        const std::string& name,
+        mojo::ScopedInterfaceEndpointHandle handle) = 0;
+
+    // Template helper to add an interface factory to this channel.
+    template <typename Interface>
+    using AssociatedInterfaceFactory =
+        base::Callback<void(mojo::AssociatedInterfaceRequest<Interface>)>;
+    template <typename Interface>
+    void AddAssociatedInterface(
+        const AssociatedInterfaceFactory<Interface>& factory) {
+      AddGenericAssociatedInterface(
+          Interface::Name_,
+          base::Bind(&BindAssociatedInterfaceRequest<Interface>, factory));
+    }
+
+    // Template helper to request a remote associated interface.
+    template <typename Interface>
+    void GetRemoteAssociatedInterface(
+        mojo::AssociatedInterfacePtr<Interface>* proxy) {
+      mojo::AssociatedInterfaceRequest<Interface> request =
+          mojo::GetProxy(proxy, GetAssociatedGroup());
+      GetGenericRemoteAssociatedInterface(
+          Interface::Name_, request.PassHandle());
+    }
+
+   private:
+    template <typename Interface>
+    static void BindAssociatedInterfaceRequest(
+        const AssociatedInterfaceFactory<Interface>& factory,
+        mojo::ScopedInterfaceEndpointHandle handle) {
+      mojo::AssociatedInterfaceRequest<Interface> request;
+      request.Bind(std::move(handle));
+      factory.Run(std::move(request));
+    }
+  };
+
   // The maximum message size in bytes. Attempting to receive a message of this
   // size or bigger results in a channel error.
   static const size_t kMaximumMessageSize = 128 * 1024 * 1024;
@@ -181,6 +241,11 @@
   // Get its own process id. This value is told to the peer.
   virtual base::ProcessId GetSelfPID() const = 0;
 
+  // Gets a helper for associating Mojo interfaces with this Channel.
+  //
+  // NOTE: Not all implementations support this.
+  virtual AssociatedInterfaceSupport* GetAssociatedInterfaceSupport();
+
   // Overridden from ipc::Sender.
   // Send a message over the Channel to the listener on the other end.
   //
diff --git a/ipc/ipc_channel_common.cc b/ipc/ipc_channel_common.cc
index 3002b665..1d5d2f99 100644
--- a/ipc/ipc_channel_common.cc
+++ b/ipc/ipc_channel_common.cc
@@ -84,6 +84,10 @@
 Channel::~Channel() {
 }
 
+Channel::AssociatedInterfaceSupport* Channel::GetAssociatedInterfaceSupport() {
+  return nullptr;
+}
+
 bool Channel::IsSendThreadSafe() const {
   return false;
 }
diff --git a/ipc/ipc_channel_mojo.cc b/ipc/ipc_channel_mojo.cc
index 22efa3a..70a12f4 100644
--- a/ipc/ipc_channel_mojo.cc
+++ b/ipc/ipc_channel_mojo.cc
@@ -307,6 +307,14 @@
   listener_->OnChannelError();
 }
 
+void ChannelMojo::OnAssociatedInterfaceRequest(
+    const std::string& name,
+    mojo::ScopedInterfaceEndpointHandle handle) {
+  auto iter = associated_interfaces_.find(name);
+  if (iter != associated_interfaces_.end())
+    iter->second.Run(std::move(handle));
+}
+
 void ChannelMojo::InitMessageReader(mojom::ChannelAssociatedPtrInfo sender,
                                     mojom::ChannelAssociatedRequest receiver,
                                     base::ProcessId peer_pid) {
@@ -392,6 +400,9 @@
   return bootstrap_->GetSelfPID();
 }
 
+Channel::AssociatedInterfaceSupport*
+ChannelMojo::GetAssociatedInterfaceSupport() { return this; }
+
 void ChannelMojo::OnMessageReceived(const Message& message) {
   TRACE_EVENT2("ipc,toplevel", "ChannelMojo::OnMessageReceived",
                "class", IPC_MESSAGE_ID_CLASS(message.type()),
@@ -468,4 +479,23 @@
   return MOJO_RESULT_OK;
 }
 
+mojo::AssociatedGroup* ChannelMojo::GetAssociatedGroup() {
+  DCHECK(bootstrap_);
+  return bootstrap_->GetAssociatedGroup();
+}
+
+void ChannelMojo::AddGenericAssociatedInterface(
+    const std::string& name,
+    const GenericAssociatedInterfaceFactory& factory) {
+  auto result = associated_interfaces_.insert({ name, factory });
+  DCHECK(result.second);
+}
+
+void ChannelMojo::GetGenericRemoteAssociatedInterface(
+    const std::string& name,
+    mojo::ScopedInterfaceEndpointHandle handle) {
+  DCHECK(message_reader_);
+  message_reader_->GetRemoteInterface(name, std::move(handle));
+}
+
 }  // namespace IPC
diff --git a/ipc/ipc_channel_mojo.h b/ipc/ipc_channel_mojo.h
index 774a82a..c267649 100644
--- a/ipc/ipc_channel_mojo.h
+++ b/ipc/ipc_channel_mojo.h
@@ -7,7 +7,9 @@
 
 #include <stdint.h>
 
+#include <map>
 #include <memory>
+#include <string>
 #include <vector>
 
 #include "base/macros.h"
@@ -37,6 +39,7 @@
 //
 class IPC_EXPORT ChannelMojo
     : public Channel,
+      public Channel::AssociatedInterfaceSupport,
       public MojoBootstrap::Delegate,
       public NON_EXPORTED_BASE(internal::MessagePipeReader::Delegate) {
  public:
@@ -62,6 +65,7 @@
   bool IsSendThreadSafe() const override;
   base::ProcessId GetPeerPID() const override;
   base::ProcessId GetSelfPID() const override;
+  Channel::AssociatedInterfaceSupport* GetAssociatedInterfaceSupport() override;
 
 #if defined(OS_POSIX) && !defined(OS_NACL_SFI)
   int GetClientFileDescriptor() const override;
@@ -82,6 +86,9 @@
                         mojom::ChannelAssociatedRequest receive_channel,
                         int32_t peer_pid) override;
   void OnBootstrapError() override;
+  void OnAssociatedInterfaceRequest(
+      const std::string& name,
+      mojo::ScopedInterfaceEndpointHandle handle) override;
 
   // MessagePipeReader::Delegate
   void OnMessageReceived(const Message& message) override;
@@ -96,6 +103,15 @@
                          mojom::ChannelAssociatedRequest receiver,
                          base::ProcessId peer_pid);
 
+  // Channel::AssociatedInterfaceSupport:
+  mojo::AssociatedGroup* GetAssociatedGroup() override;
+  void AddGenericAssociatedInterface(
+      const std::string& name,
+      const GenericAssociatedInterfaceFactory& factory) override;
+  void GetGenericRemoteAssociatedInterface(
+      const std::string& name,
+      mojo::ScopedInterfaceEndpointHandle handle) override;
+
   // ChannelMojo needs to kill its MessagePipeReader in delayed manner
   // because the channel wants to kill these readers during the
   // notifications invoked by them.
@@ -108,6 +124,9 @@
   std::unique_ptr<MojoBootstrap> bootstrap_;
   Listener* listener_;
 
+  std::map<std::string, GenericAssociatedInterfaceFactory>
+      associated_interfaces_;
+
   // Guards access to the fields below.
   mutable base::Lock lock_;
   std::unique_ptr<internal::MessagePipeReader, ReaderDeleter> message_reader_;
diff --git a/ipc/ipc_channel_mojo_unittest.cc b/ipc/ipc_channel_mojo_unittest.cc
index eec011b..e846c13 100644
--- a/ipc/ipc_channel_mojo_unittest.cc
+++ b/ipc/ipc_channel_mojo_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <stddef.h>
 #include <stdint.h>
+
 #include <memory>
 #include <utility>
 
@@ -17,6 +18,7 @@
 #include "base/pickle.h"
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
+#include "base/strings/stringprintf.h"
 #include "base/test/test_io_thread.h"
 #include "base/test/test_timeouts.h"
 #include "base/threading/thread.h"
@@ -26,6 +28,7 @@
 #include "ipc/ipc_mojo_handle_attachment.h"
 #include "ipc/ipc_mojo_message_helper.h"
 #include "ipc/ipc_mojo_param_traits.h"
+#include "ipc/ipc_test.mojom.h"
 #include "ipc/ipc_test_base.h"
 #include "ipc/ipc_test_channel_listener.h"
 #include "mojo/edk/test/mojo_test_base.h"
@@ -60,6 +63,12 @@
 
 namespace {
 
+void SendString(IPC::Sender* sender, const std::string& str) {
+  IPC::Message* message = new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL);
+  message->WriteString(str);
+  ASSERT_TRUE(sender->Send(message));
+}
+
 class ListenerThatExpectsOK : public IPC::Listener {
  public:
   ListenerThatExpectsOK() : received_ok_(false) {}
@@ -83,12 +92,7 @@
     DCHECK(received_ok_);
   }
 
-  static void SendOK(IPC::Sender* sender) {
-    IPC::Message* message =
-        new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL);
-    message->WriteString(std::string("OK"));
-    ASSERT_TRUE(sender->Send(message));
-  }
+  static void SendOK(IPC::Sender* sender) { SendString(sender, "OK"); }
 
  private:
   bool received_ok_;
@@ -124,7 +128,7 @@
 
 class IPCChannelMojoTest : public testing::Test {
  public:
-  IPCChannelMojoTest() : io_thread_(base::TestIOThread::Mode::kAutoStart) {}
+  IPCChannelMojoTest() {}
 
   void TearDown() override { base::RunLoop().RunUntilIdle(); }
 
@@ -148,7 +152,6 @@
 
  private:
   base::MessageLoop message_loop_;
-  base::TestIOThread io_thread_;
   mojo::edk::test::MultiprocessTestHelper helper_;
   mojo::ScopedMessagePipeHandle handle_;
   std::unique_ptr<IPC::Channel> channel_;
@@ -582,6 +585,120 @@
   Close();
 }
 
+class ListenerWithSimpleAssociatedInterface
+    : public IPC::Listener,
+      public IPC::mojom::SimpleTestDriver {
+ public:
+  static const int kNumMessages;
+
+  ListenerWithSimpleAssociatedInterface() : binding_(this) {}
+
+  ~ListenerWithSimpleAssociatedInterface() override {}
+
+  bool OnMessageReceived(const IPC::Message& message) override {
+    base::PickleIterator iter(message);
+    std::string should_be_expected;
+    EXPECT_TRUE(iter.ReadString(&should_be_expected));
+    EXPECT_EQ(should_be_expected, next_expected_string_);
+    num_messages_received_++;
+    return true;
+  }
+
+  void OnChannelError() override {
+    DCHECK(received_quit_);
+  }
+
+  void RegisterInterfaceFactory(IPC::Channel* channel) {
+    channel->GetAssociatedInterfaceSupport()->AddAssociatedInterface(
+        base::Bind(&ListenerWithSimpleAssociatedInterface::BindRequest,
+                   base::Unretained(this)));
+  }
+
+ private:
+  // IPC::mojom::SimpleTestDriver:
+  void ExpectString(const mojo::String& str) override {
+    next_expected_string_ = str;
+  }
+
+  void RequestQuit(const RequestQuitCallback& callback) override {
+    EXPECT_EQ(kNumMessages, num_messages_received_);
+    received_quit_ = true;
+    callback.Run();
+    base::MessageLoop::current()->QuitWhenIdle();
+  }
+
+  void BindRequest(IPC::mojom::SimpleTestDriverAssociatedRequest request) {
+    DCHECK(!binding_.is_bound());
+    binding_.Bind(std::move(request));
+  }
+
+  std::string next_expected_string_;
+  int num_messages_received_ = 0;
+  bool received_quit_ = false;
+
+  mojo::AssociatedBinding<IPC::mojom::SimpleTestDriver> binding_;
+};
+
+const int ListenerWithSimpleAssociatedInterface::kNumMessages = 1000;
+
+class ListenerSendingAssociatedMessages : public IPC::Listener {
+ public:
+  ListenerSendingAssociatedMessages() {}
+
+  bool OnMessageReceived(const IPC::Message& message) override { return true; }
+
+  void OnChannelConnected(int32_t peer_pid) override {
+    DCHECK(channel_);
+    channel_->GetAssociatedInterfaceSupport()->GetRemoteAssociatedInterface(
+        &driver_);
+
+    // Send a bunch of interleaved messages, alternating between the associated
+    // interface and a legacy IPC::Message.
+    for (int i = 0; i < ListenerWithSimpleAssociatedInterface::kNumMessages;
+         ++i) {
+      std::string str = base::StringPrintf("Hello! %d", i);
+      driver_->ExpectString(str);
+      SendString(channel_, str);
+    }
+    driver_->RequestQuit(base::Bind(&OnQuitAck));
+  }
+
+  void set_channel(IPC::Channel* channel) { channel_ = channel; }
+
+ private:
+  static void OnQuitAck() { base::MessageLoop::current()->QuitWhenIdle(); }
+
+  IPC::Channel* channel_ = nullptr;
+  IPC::mojom::SimpleTestDriverAssociatedPtr driver_;
+};
+
+TEST_F(IPCChannelMojoTest, SimpleAssociatedInterface) {
+  InitWithMojo("SimpleAssociatedInterfaceClient");
+
+  ListenerWithSimpleAssociatedInterface listener;
+  CreateChannel(&listener);
+  ASSERT_TRUE(ConnectChannel());
+
+  listener.RegisterInterfaceFactory(channel());
+
+  base::RunLoop().Run();
+  channel()->Close();
+
+  EXPECT_TRUE(WaitForClientShutdown());
+  DestroyChannel();
+}
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(SimpleAssociatedInterfaceClient,
+                                    ChannelClient) {
+  ListenerSendingAssociatedMessages listener;
+  Connect(&listener);
+  listener.set_channel(channel());
+
+  base::RunLoop().Run();
+
+  Close();
+}
+
 #if defined(OS_POSIX)
 class ListenerThatExpectsFile : public IPC::Listener {
  public:
@@ -696,7 +813,7 @@
   Close();
 }
 
-#endif
+#endif  // defined(OS_POSIX)
 
 #if defined(OS_LINUX)
 
diff --git a/ipc/ipc_message_pipe_reader.cc b/ipc/ipc_message_pipe_reader.cc
index da2d85d..74c7285 100644
--- a/ipc/ipc_message_pipe_reader.cc
+++ b/ipc/ipc_message_pipe_reader.cc
@@ -115,6 +115,14 @@
   return result == MOJO_RESULT_OK;
 }
 
+void MessagePipeReader::GetRemoteInterface(
+    const std::string& name,
+    mojo::ScopedInterfaceEndpointHandle handle) {
+  mojom::GenericInterfaceAssociatedRequest request;
+  request.Bind(std::move(handle));
+  sender_->GetAssociatedInterface(name, std::move(request));
+}
+
 void MessagePipeReader::Receive(
     mojo::Array<uint8_t> data,
     mojo::Array<mojom::SerializedHandlePtr> handles) {
@@ -138,6 +146,14 @@
   delegate_->OnMessageReceived(message);
 }
 
+void MessagePipeReader::GetAssociatedInterface(
+    const mojo::String& name,
+    mojom::GenericInterfaceAssociatedRequest request) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (delegate_)
+    delegate_->OnAssociatedInterfaceRequest(name, request.PassHandle());
+}
+
 void MessagePipeReader::OnPipeError(MojoResult error) {
   DCHECK(thread_checker_.CalledOnValidThread());
   if (delegate_)
diff --git a/ipc/ipc_message_pipe_reader.h b/ipc/ipc_message_pipe_reader.h
index b15579d..5aec440 100644
--- a/ipc/ipc_message_pipe_reader.h
+++ b/ipc/ipc_message_pipe_reader.h
@@ -15,8 +15,10 @@
 #include "base/macros.h"
 #include "base/threading/thread_checker.h"
 #include "ipc/ipc.mojom.h"
+#include "ipc/ipc_export.h"
 #include "ipc/ipc_message.h"
 #include "mojo/public/cpp/bindings/associated_binding.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
 #include "mojo/public/cpp/system/core.h"
 #include "mojo/public/cpp/system/message_pipe.h"
 
@@ -41,12 +43,15 @@
 // be called on any thread. All |Delegate| functions will be called on the IO
 // thread.
 //
-class MessagePipeReader : public mojom::Channel {
+class IPC_EXPORT MessagePipeReader : public NON_EXPORTED_BASE(mojom::Channel) {
  public:
   class Delegate {
    public:
     virtual void OnMessageReceived(const Message& message) = 0;
     virtual void OnPipeError() = 0;
+    virtual void OnAssociatedInterfaceRequest(
+        const std::string& name,
+        mojo::ScopedInterfaceEndpointHandle handle) = 0;
   };
 
   // Delay the object deletion using the current message loop.
@@ -91,6 +96,10 @@
   // thread.
   bool Send(std::unique_ptr<Message> message);
 
+  // Requests an associated interface from the other end of the pipe.
+  void GetRemoteInterface(const std::string& name,
+                          mojo::ScopedInterfaceEndpointHandle handle);
+
   base::ProcessId GetPeerPid() const { return peer_pid_; }
 
  protected:
@@ -101,6 +110,9 @@
   // mojom::Channel:
   void Receive(mojo::Array<uint8_t> data,
                mojo::Array<mojom::SerializedHandlePtr> handles) override;
+  void GetAssociatedInterface(
+      const mojo::String& name,
+      mojom::GenericInterfaceAssociatedRequest request) override;
 
   // |delegate_| is null once the message pipe is closed.
   Delegate* delegate_;
diff --git a/ipc/ipc_mojo_bootstrap.cc b/ipc/ipc_mojo_bootstrap.cc
index dc8ffdc8..425f7948 100644
--- a/ipc/ipc_mojo_bootstrap.cc
+++ b/ipc/ipc_mojo_bootstrap.cc
@@ -591,6 +591,11 @@
     endpoint_client_->set_connection_error_handler(handler);
   }
 
+  mojo::AssociatedGroup* associated_group() {
+    DCHECK(controller_);
+    return controller_->associated_group();
+  }
+
   void Bind(mojo::ScopedMessagePipeHandle handle) {
     DCHECK(!controller_);
     controller_ =
@@ -621,6 +626,9 @@
  private:
   // MojoBootstrap implementation.
   void Connect() override;
+  mojo::AssociatedGroup* GetAssociatedGroup() override {
+    return bootstrap_.associated_group();
+  }
 
   void OnInitDone(int32_t peer_pid);
 
@@ -680,6 +688,9 @@
  private:
   // MojoBootstrap implementation.
   void Connect() override;
+  mojo::AssociatedGroup* GetAssociatedGroup() override {
+    return binding_.associated_group();
+  }
 
   // mojom::Bootstrap implementation.
   void Init(mojom::ChannelAssociatedRequest receive_channel,
diff --git a/ipc/ipc_mojo_bootstrap.h b/ipc/ipc_mojo_bootstrap.h
index 312003a..1d632d3 100644
--- a/ipc/ipc_mojo_bootstrap.h
+++ b/ipc/ipc_mojo_bootstrap.h
@@ -15,6 +15,7 @@
 #include "ipc/ipc.mojom.h"
 #include "ipc/ipc_channel.h"
 #include "ipc/ipc_listener.h"
+#include "mojo/public/cpp/bindings/associated_group.h"
 #include "mojo/public/cpp/system/message_pipe.h"
 
 namespace IPC {
@@ -51,6 +52,8 @@
   // Start the handshake over the underlying message pipe.
   virtual void Connect() = 0;
 
+  virtual mojo::AssociatedGroup* GetAssociatedGroup() = 0;
+
   // GetSelfPID returns our PID.
   base::ProcessId GetSelfPID() const;
 
diff --git a/ipc/ipc_test.mojom b/ipc/ipc_test.mojom
new file mode 100644
index 0000000..9c1b30e
--- /dev/null
+++ b/ipc/ipc_test.mojom
@@ -0,0 +1,10 @@
+// Copyright 2016 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.
+
+module IPC.mojom;
+
+interface SimpleTestDriver {
+  ExpectString(string str);
+  RequestQuit() => ();
+};