Add sync context dispatch restriction.

This adds a way to restrict on a per-channel basis that incoming messages may only be dispatched when that particular channel is sending a sync message (or in a message loop).

It does so to the PPAPI channels, which may not introduce a sync dependency circle.

BUG=chromiumos:13821
TEST=news.google.com with Pepper Flash (see bug)

Review URL: http://codereview.chromium.org/6810013

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@80892 0039d316-1c4b-4281-b951-d872f2087c98


CrOS-Libchrome-Original-Commit: 54af05f8ea8050e0ab93dbcc940ac4e1bc4068e5
diff --git a/ipc/ipc_sync_channel_unittest.cc b/ipc/ipc_sync_channel_unittest.cc
index 8e207bf..6952aac 100644
--- a/ipc/ipc_sync_channel_unittest.cc
+++ b/ipc/ipc_sync_channel_unittest.cc
@@ -158,6 +158,8 @@
     return overrided_thread_ ? overrided_thread_ : &listener_thread_;
   }
 
+  const base::Thread& ipc_thread() const { return ipc_thread_; }
+
  private:
   // Called on the listener thread to create the sync channel.
   void OnStart() {
@@ -1164,4 +1166,152 @@
   EXPECT_FALSE(server.send_result());
 }
 
+//-----------------------------------------------------------------------------
 
+namespace {
+
+class RestrictedDispatchServer : public Worker {
+ public:
+  RestrictedDispatchServer(WaitableEvent* sent_ping_event)
+      : Worker("restricted_channel", Channel::MODE_SERVER),
+        sent_ping_event_(sent_ping_event) { }
+
+  void OnDoPing(int ping) {
+    // Send an asynchronous message that unblocks the caller.
+    IPC::Message* msg = new SyncChannelTestMsg_Ping(ping);
+    msg->set_unblock(true);
+    Send(msg);
+    // Signal the event after the message has been sent on the channel, on the
+    // IPC thread.
+    ipc_thread().message_loop()->PostTask(FROM_HERE,
+        NewRunnableMethod(this, &RestrictedDispatchServer::OnPingSent));
+  }
+
+  base::Thread* ListenerThread() { return Worker::ListenerThread(); }
+
+ private:
+  bool OnMessageReceived(const Message& message) {
+    IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchServer, message)
+     IPC_MESSAGE_HANDLER(SyncChannelTestMsg_NoArgs, OnNoArgs)
+     IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Done, Done)
+    IPC_END_MESSAGE_MAP()
+    return true;
+  }
+
+  void OnPingSent() {
+    sent_ping_event_->Signal();
+  }
+
+  void OnNoArgs() { }
+  WaitableEvent* sent_ping_event_;
+};
+
+class NonRestrictedDispatchServer : public Worker {
+ public:
+  NonRestrictedDispatchServer()
+      : Worker("non_restricted_channel", Channel::MODE_SERVER) {}
+
+ private:
+  bool OnMessageReceived(const Message& message) {
+    IPC_BEGIN_MESSAGE_MAP(NonRestrictedDispatchServer, message)
+     IPC_MESSAGE_HANDLER(SyncChannelTestMsg_NoArgs, OnNoArgs)
+     IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Done, Done)
+    IPC_END_MESSAGE_MAP()
+    return true;
+  }
+
+  void OnNoArgs() { }
+};
+
+class RestrictedDispatchClient : public Worker {
+ public:
+  RestrictedDispatchClient(WaitableEvent* sent_ping_event,
+                           RestrictedDispatchServer* server,
+                           int* success)
+      : Worker("restricted_channel", Channel::MODE_CLIENT),
+        ping_(0),
+        server_(server),
+        success_(success),
+        sent_ping_event_(sent_ping_event) {}
+
+  void Run() {
+    // Incoming messages from our channel should only be dispatched when we
+    // send a message on that same channel.
+    channel()->SetRestrictDispatchToSameChannel(true);
+
+    server_->ListenerThread()->message_loop()->PostTask(FROM_HERE,
+        NewRunnableMethod(server_, &RestrictedDispatchServer::OnDoPing, 1));
+    sent_ping_event_->Wait();
+    Send(new SyncChannelTestMsg_NoArgs);
+    if (ping_ == 1)
+      ++*success_;
+    else
+      LOG(ERROR) << "Send failed to dispatch incoming message on same channel";
+
+    scoped_ptr<SyncChannel> non_restricted_channel(new SyncChannel(
+        "non_restricted_channel", Channel::MODE_CLIENT, this,
+        ipc_thread().message_loop(), true, shutdown_event()));
+
+    server_->ListenerThread()->message_loop()->PostTask(FROM_HERE,
+        NewRunnableMethod(server_, &RestrictedDispatchServer::OnDoPing, 2));
+    sent_ping_event_->Wait();
+    // Check that the incoming message is *not* dispatched when sending on the
+    // non restricted channel.
+    // TODO(piman): there is a possibility of a false positive race condition
+    // here, if the message that was posted on the server-side end of the pipe
+    // is not visible yet on the client side, but I don't know how to solve this
+    // without hooking into the internals of SyncChannel. I haven't seen it in
+    // practice (i.e. not setting SetRestrictDispatchToSameChannel does cause
+    // the following to fail).
+    non_restricted_channel->Send(new SyncChannelTestMsg_NoArgs);
+    if (ping_ == 1)
+      ++*success_;
+    else
+      LOG(ERROR) << "Send dispatched message from restricted channel";
+
+    Send(new SyncChannelTestMsg_NoArgs);
+    if (ping_ == 2)
+      ++*success_;
+    else
+      LOG(ERROR) << "Send failed to dispatch incoming message on same channel";
+
+    non_restricted_channel->Send(new SyncChannelTestMsg_Done);
+    non_restricted_channel.reset();
+    Send(new SyncChannelTestMsg_Done);
+    Done();
+  }
+
+ private:
+  bool OnMessageReceived(const Message& message) {
+    IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchClient, message)
+     IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Ping, OnPing)
+    IPC_END_MESSAGE_MAP()
+    return true;
+  }
+
+  void OnPing(int ping) {
+    ping_ = ping;
+  }
+
+  int ping_;
+  RestrictedDispatchServer* server_;
+  int* success_;
+  WaitableEvent* sent_ping_event_;
+};
+
+}  // namespace
+
+TEST_F(IPCSyncChannelTest, RestrictedDispatch) {
+  WaitableEvent sent_ping_event(false, false);
+
+  RestrictedDispatchServer* server =
+      new RestrictedDispatchServer(&sent_ping_event);
+  int success = 0;
+  std::vector<Worker*> workers;
+  workers.push_back(new NonRestrictedDispatchServer);
+  workers.push_back(server);
+  workers.push_back(
+      new RestrictedDispatchClient(&sent_ping_event, server, &success));
+  RunTest(workers);
+  EXPECT_EQ(3, success);
+}