Merge "Add hidehiko to OWNERS."
diff --git a/Android.bp b/Android.bp
index 7ae795e..0d9a8ce 100644
--- a/Android.bp
+++ b/Android.bp
@@ -7,10 +7,12 @@
     srcs: [
         "ipc/ipc.mojom",
         "mojo/common/file.mojom",
+        "mojo/common/file_path.mojom",
         "mojo/common/string16.mojom",
         "mojo/common/text_direction.mojom",
         "mojo/common/time.mojom",
         "mojo/common/unguessable_token.mojom",
+        "mojo/common/values.mojom",
         "mojo/common/version.mojom",
         "mojo/public/interfaces/bindings/interface_control_messages.mojom",
         "mojo/public/interfaces/bindings/pipe_control_messages.mojom",
@@ -19,6 +21,46 @@
     ],
 }
 
+filegroup {
+    name: "mojo_sources",
+    srcs: [
+        "mojo/**/*.cc",
+    ],
+    exclude_srcs: [
+        // Unused in Chrome. Looks like mistakenly checked in.
+        // TODO(hidehiko): Remove this after the file is removed in Chrome
+        // repository. http://crrev.com/c/644531
+        "mojo/public/cpp/system/message.cc",
+
+        // No WTF support.
+        "mojo/public/cpp/bindings/lib/string_traits_wtf.cc",
+
+        // Exclude windows/mac/ios files.
+        "**/*_win.cc",
+        "mojo/edk/system/mach_port_relay.cc",
+
+        // Exclude js binding related files.
+        "mojo/edk/js/**/*",
+        "mojo/public/js/**/*",
+
+        // Exclude tests.
+        // Note that mojo/edk/embedder/test_embedder.cc needs to be included
+        // for Mojo support. cf) b/62071944.
+        "**/*_unittest.cc",
+        "**/*_unittests.cc",
+        "**/*_perftest.cc",
+        "mojo/android/javatests/**/*",
+        "mojo/edk/system/core_test_base.cc",
+        "mojo/edk/system/test_utils.cc",
+        "mojo/edk/test/**/*",
+        "mojo/public/c/system/tests/**/*",
+        "mojo/public/cpp/bindings/tests/**/*",
+        "mojo/public/cpp/system/tests/**/*",
+        "mojo/public/cpp/test_support/**/*",
+        "mojo/public/tests/**/*",
+    ],
+}
+
 // Python in Chrome repository requires still Python 2.
 python_defaults {
     name: "libmojo_scripts",
@@ -100,6 +142,9 @@
         "mojo/common/file.mojom.h",
         "mojo/common/file.mojom-shared.h",
         "mojo/common/file.mojom-shared-internal.h",
+        "mojo/common/file_path.mojom.h",
+        "mojo/common/file_path.mojom-shared.h",
+        "mojo/common/file_path.mojom-shared-internal.h",
         "mojo/common/string16.mojom.h",
         "mojo/common/string16.mojom-shared.h",
         "mojo/common/string16.mojom-shared-internal.h",
@@ -112,6 +157,9 @@
         "mojo/common/unguessable_token.mojom.h",
         "mojo/common/unguessable_token.mojom-shared.h",
         "mojo/common/unguessable_token.mojom-shared-internal.h",
+        "mojo/common/values.mojom.h",
+        "mojo/common/values.mojom-shared.h",
+        "mojo/common/values.mojom-shared-internal.h",
         "mojo/common/version.mojom.h",
         "mojo/common/version.mojom-shared.h",
         "mojo/common/version.mojom-shared-internal.h",
@@ -248,84 +296,7 @@
         "ipc/ipc_mojo_message_helper.cc",
         "ipc/ipc_mojo_param_traits.cc",
         "ipc/ipc_platform_file_attachment_posix.cc",
-        "mojo/android/system/base_run_loop.cc",
-        "mojo/android/system/core_impl.cc",
-        "mojo/android/system/watcher_impl.cc",
-        "mojo/common/common_custom_types_struct_traits.cc",
-        "mojo/edk/embedder/connection_params.cc",
-        "mojo/edk/embedder/embedder.cc",
-        "mojo/edk/embedder/entrypoints.cc",
-        "mojo/edk/embedder/platform_channel_pair.cc",
-        "mojo/edk/embedder/platform_channel_pair_posix.cc",
-        "mojo/edk/embedder/platform_channel_utils_posix.cc",
-        "mojo/edk/embedder/platform_handle.cc",
-        "mojo/edk/embedder/platform_handle_utils_posix.cc",
-        "mojo/edk/embedder/platform_shared_buffer.cc",
-        "mojo/edk/embedder/pending_process_connection.cc",
-        "mojo/edk/embedder/test_embedder.cc",
-        "mojo/edk/system/awakable_list.cc",
-        "mojo/edk/system/broker_host.cc",
-        "mojo/edk/system/broker_posix.cc",
-        "mojo/edk/system/channel.cc",
-        "mojo/edk/system/channel_posix.cc",
-        "mojo/edk/system/configuration.cc",
-        "mojo/edk/system/core.cc",
-        "mojo/edk/system/data_pipe_consumer_dispatcher.cc",
-        "mojo/edk/system/data_pipe_control_message.cc",
-        "mojo/edk/system/data_pipe_producer_dispatcher.cc",
-        "mojo/edk/system/dispatcher.cc",
-        "mojo/edk/system/handle_table.cc",
-        "mojo/edk/system/mapping_table.cc",
-        "mojo/edk/system/message_for_transit.cc",
-        "mojo/edk/system/message_pipe_dispatcher.cc",
-        "mojo/edk/system/node_channel.cc",
-        "mojo/edk/system/node_controller.cc",
-        "mojo/edk/system/platform_handle_dispatcher.cc",
-        "mojo/edk/system/ports/event.cc",
-        "mojo/edk/system/ports/message.cc",
-        "mojo/edk/system/ports/message_queue.cc",
-        "mojo/edk/system/ports/name.cc",
-        "mojo/edk/system/ports/node.cc",
-        "mojo/edk/system/ports/port.cc",
-        "mojo/edk/system/ports/port_ref.cc",
-        "mojo/edk/system/ports_message.cc",
-        "mojo/edk/system/request_context.cc",
-        "mojo/edk/system/shared_buffer_dispatcher.cc",
-        "mojo/edk/system/wait_set_dispatcher.cc",
-        "mojo/edk/system/waiter.cc",
-        "mojo/edk/system/watcher.cc",
-        "mojo/edk/system/watcher_set.cc",
-        "mojo/public/c/system/thunks.cc",
-        "mojo/public/cpp/bindings/lib/array_internal.cc",
-        "mojo/public/cpp/bindings/lib/associated_group.cc",
-        "mojo/public/cpp/bindings/lib/associated_group_controller.cc",
-        "mojo/public/cpp/bindings/lib/binding_state.cc",
-        "mojo/public/cpp/bindings/lib/connector.cc",
-        "mojo/public/cpp/bindings/lib/control_message_handler.cc",
-        "mojo/public/cpp/bindings/lib/control_message_proxy.cc",
-        "mojo/public/cpp/bindings/lib/filter_chain.cc",
-        "mojo/public/cpp/bindings/lib/fixed_buffer.cc",
-        "mojo/public/cpp/bindings/lib/interface_endpoint_client.cc",
-        "mojo/public/cpp/bindings/lib/message.cc",
-        "mojo/public/cpp/bindings/lib/message_buffer.cc",
-        "mojo/public/cpp/bindings/lib/message_builder.cc",
-        "mojo/public/cpp/bindings/lib/message_header_validator.cc",
-        "mojo/public/cpp/bindings/lib/multiplex_router.cc",
-        "mojo/public/cpp/bindings/lib/native_struct.cc",
-        "mojo/public/cpp/bindings/lib/native_struct_data.cc",
-        "mojo/public/cpp/bindings/lib/native_struct_serialization.cc",
-        "mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc",
-        "mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc",
-        "mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc",
-        "mojo/public/cpp/bindings/lib/serialization_context.cc",
-        "mojo/public/cpp/bindings/lib/sync_handle_registry.cc",
-        "mojo/public/cpp/bindings/lib/sync_handle_watcher.cc",
-        "mojo/public/cpp/bindings/lib/validation_context.cc",
-        "mojo/public/cpp/bindings/lib/validation_errors.cc",
-        "mojo/public/cpp/bindings/lib/validation_util.cc",
-        "mojo/public/cpp/system/buffer.cc",
-        "mojo/public/cpp/system/platform_handle.cc",
-        "mojo/public/cpp/system/watcher.cc",
+        ":mojo_sources",
     ],
 
     cflags: [
diff --git a/ipc/ipc_sync_message.h b/ipc/ipc_sync_message.h
index ed5204f..7f05551 100644
--- a/ipc/ipc_sync_message.h
+++ b/ipc/ipc_sync_message.h
@@ -17,10 +17,13 @@
 #include "build/build_config.h"
 #include "ipc/ipc_message.h"
 
+namespace base {
+class WaitableEvent;
+}
+
 namespace IPC {
 
 class MessageReplyDeserializer;
-class MojoEvent;
 
 class IPC_EXPORT SyncMessage : public Message {
  public:
@@ -90,12 +93,12 @@
 // When sending a synchronous message, this structure contains an object
 // that knows how to deserialize the response.
 struct PendingSyncMsg {
-  PendingSyncMsg(int id, MessageReplyDeserializer* d, MojoEvent* e)
-      : id(id), deserializer(d), done_event(e), send_result(false) { }
+  PendingSyncMsg(int id, MessageReplyDeserializer* d, base::WaitableEvent* e)
+      : id(id), deserializer(d), done_event(e), send_result(false) {}
 
   int id;
   MessageReplyDeserializer* deserializer;
-  MojoEvent* done_event;
+  base::WaitableEvent* done_event;
   bool send_result;
 };
 
diff --git a/mojo/README.md b/mojo/README.md
index 237d1d4..e1e7583 100644
--- a/mojo/README.md
+++ b/mojo/README.md
@@ -1,7 +1,142 @@
-Mojo
-====
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo
+This document is a subset of the [Mojo documentation](/mojo).
 
-[Mojo](https://www.chromium.org/developers/design-documents/mojo) is an IPC &
-binding mechanism for Chromium.
+[TOC]
 
-TODO(rockot): Describe the important subdirectories.
\ No newline at end of file
+## Getting Started With Mojo
+
+To get started using Mojo in applications which already support it (such as
+Chrome), the fastest path forward will be to look at the bindings documentation
+for your language of choice ([**C++**](#C_Bindings),
+[**JavaScript**](#JavaScript-Bindings), or [**Java**](#Java-Bindings)) as well
+as the documentation for the
+[**Mojom IDL and bindings generator**](/mojo/public/tools/bindings).
+
+If you're looking for information on creating and/or connecting to services, see
+the top-level [Services documentation](/services).
+
+For specific details regarding the conversion of old things to new things, check
+out [Converting Legacy Chrome IPC To Mojo](/ipc).
+
+## System Overview
+
+Mojo is a layered collection of runtime libraries providing a platform-agnostic
+abstraction of common IPC primitives, a message IDL format, and a bindings
+library with code generation for multiple target languages to facilitate
+convenient message passing across arbitrary inter- and intra-process boundaries.
+
+The documentation here is segmented according to the different isolated layers
+and libraries comprising the system. The basic hierarchy of features is as
+follows:
+
+![Mojo Library Layering: EDK on bottom, different language bindings on top, public system support APIs in the middle](https://docs.google.com/drawings/d/1aNbLfF-fejgzxCxH_b8xAaCVvftW8BGTH_EHD7nvU1w/pub?w=570&h=327)
+
+## Embedder Development Kit (EDK)
+Every process to be interconnected via Mojo IPC is called a **Mojo embedder**
+and needs to embed the
+[**Embedder Development Kit (EDK)**](/mojo/edk/embedder) library. The EDK
+exposes the means for an embedder to physically connect one process to another
+using any supported native IPC primitive (*e.g.,* a UNIX domain socket or
+Windows named pipe) on the host platform.
+
+Details regarding where and how an application process actually embeds and
+configures the EDK are generaly hidden from the rest of the application code,
+and applications instead use the public System and Bindings APIs to get things
+done within processes that embed Mojo.
+
+## C System API
+Once the EDK is initialized within a process, the public
+[**C System API**](/mojo/public/c/system) is usable on any thread for the
+remainder of the process's lifetime. This is a lightweight API with a relatively
+small (and eventually stable) ABI. Typically this API is not used directly, but
+it is the foundation upon which all remaining upper layers are built. It exposes
+the fundamental capabilities to create and interact with various types of Mojo
+handles including **message pipes**, **data pipes**, and **shared buffers**.
+
+## High-Level System APIs
+
+There is a relatively small, higher-level system API for each supported
+language, built upon the low-level C API. Like the C API, direct usage of these
+system APIs is rare compared to the bindings APIs, but it is sometimes desirable
+or necessary.
+
+### C++
+The [**C++ System API**](/mojo/public/cpp/system) provides a layer of
+C++ helper classes and functions to make safe System API usage easier:
+strongly-typed handle scopers, synchronous waiting operations, system handle
+wrapping and unwrapping helpers, common handle operations, and utilities for
+more easily watching handle state changes.
+
+### JavaScript
+The [**JavaScript APIs**](/mojo/public/js) are WIP. :)
+
+### Java
+The [**Java System API**](/mojo/public/java/system) provides helper classes for
+working with Mojo primitives, covering all basic functionality of the low-level
+C API.
+
+## High-Level Bindings APIs
+Typically developers do not use raw message pipe I/O directly, but instead
+define some set of interfaces which are used to generate code that message pipe
+usage feel like a more idiomatic method-calling interface in the target
+language of choice. This is the bindings layer.
+
+### Mojom IDL and Bindings Generator
+Interfaces are defined using the [**Mojom IDL**](/mojo/public/tools/bindings),
+which can be fed to the [**bindings generator**](/mojo/public/tools/bindings) to
+generate code in various supported languages. Generated code manages
+serialization and deserialization of messages between interface clients and
+implementations, simplifying the code -- and ultimately hiding the message pipe
+-- on either side of an interface connection.
+
+### C++ Bindings
+By far the most commonly used API defined by Mojo, the
+[**C++ Bindings API**](/mojo/public/cpp/bindings) exposes a robust set of
+features for interacting with message pipes via generated C++ bindings code,
+including support for sets of related bindings endpoints, associated interfaces,
+nested sync IPC, versioning, bad-message reporting, arbitrary message filter
+injection, and convenient test facilities.
+
+### JavaScript Bindings
+The [**JavaScript APIs**](/mojo/public/js) are WIP. :)
+
+### Java Bindings
+The [**Java Bindings API**](/mojo/public/java/bindings) provides helper classes
+for working with Java code emitted by the bindings generator.
+
+## FAQ
+
+### Why not protobuf? Why a new thing?
+There are number of potentially decent answers to this question, but the
+deal-breaker is that a useful IPC mechanism must support transfer of native
+object handles (*e.g.* file descriptors) across process boundaries. Other
+non-new IPC things that do support this capability (*e.g.* D-Bus) have their own
+substantial deficiencies.
+
+### Are message pipes expensive?
+No. As an implementation detail, creating a message pipe is essentially
+generating two random numbers and stuffing them into a hash table, along with a
+few tiny heap allocations.
+
+### So really, can I create like, thousands of them?
+Yes! Nobody will mind. Create millions if you like. (OK but maybe don't.)
+
+### Can I use in-process message pipes?
+Yes, and message pipe usage is identical regardless of whether the pipe actually
+crosses a process boundary -- in fact this detail is intentionally obscured.
+
+Message pipes which don't cross a process boundary are efficient: sent messages
+are never copied, and a write on one end will synchronously modify the message
+queue on the other end. When working with generated C++ bindings, for example,
+the net result is that an `InterfacePtr` on one thread sending a message to a
+`Binding` on another thread (or even the same thread) is effectively a
+`PostTask` to the `Binding`'s `TaskRunner` with the added -- but often small --
+costs of serialization, deserialization, validation, and some internal routing
+logic.
+
+### What about ____?
+
+Please post questions to
+[`chromium-mojo@chromium.org`](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-mojo)!
+The list is quite responsive.
+
diff --git a/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java b/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java
index 6783c09..1f8de94 100644
--- a/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java
+++ b/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java
@@ -5,7 +5,7 @@
 package org.chromium.mojo;
 
 import org.chromium.mojo.system.Core;
-import org.chromium.mojo.system.Core.WaitResult;
+import org.chromium.mojo.system.Core.HandleSignalsState;
 import org.chromium.mojo.system.DataPipe;
 import org.chromium.mojo.system.DataPipe.ConsumerHandle;
 import org.chromium.mojo.system.DataPipe.ProducerHandle;
@@ -35,14 +35,11 @@
     }
 
     /**
-     * @see Handle#wait(Core.HandleSignals, long)
+     * @see Handle#querySignalsState()
      */
     @Override
-    public WaitResult wait(Core.HandleSignals signals, long deadline) {
-        // Do nothing.
-        WaitResult result = new WaitResult();
-        result.setMojoResult(MojoResult.OK);
-        return result;
+    public HandleSignalsState querySignalsState() {
+        return null;
     }
 
     /**
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java
index 5affb8f..6aa1726 100644
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java
@@ -12,7 +12,6 @@
 import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiverWithResponder;
 import org.chromium.mojo.system.Core;
 import org.chromium.mojo.system.Core.HandleSignals;
-import org.chromium.mojo.system.Core.WaitResult;
 import org.chromium.mojo.system.Handle;
 import org.chromium.mojo.system.MessagePipeHandle;
 import org.chromium.mojo.system.MojoResult;
@@ -227,8 +226,6 @@
 
         // Confirm that the pipe was closed on the Router side.
         HandleSignals closedFlag = HandleSignals.none().setPeerClosed(true);
-        WaitResult result = mHandle.wait(closedFlag, 0);
-        assertEquals(MojoResult.OK, result.getMojoResult());
-        assertEquals(closedFlag, result.getHandleSignalsState().getSatisfiedSignals());
+        assertEquals(closedFlag, mHandle.querySignalsState().getSatisfiedSignals());
     }
 }
diff --git a/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java b/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java
index 77a9bda..5120198 100644
--- a/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java
+++ b/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java
@@ -9,9 +9,6 @@
 import org.chromium.mojo.MojoTestCase;
 import org.chromium.mojo.system.Core;
 import org.chromium.mojo.system.Core.HandleSignals;
-import org.chromium.mojo.system.Core.HandleSignalsState;
-import org.chromium.mojo.system.Core.WaitManyResult;
-import org.chromium.mojo.system.Core.WaitResult;
 import org.chromium.mojo.system.DataPipe;
 import org.chromium.mojo.system.Handle;
 import org.chromium.mojo.system.InvalidHandle;
@@ -30,7 +27,6 @@
 import java.util.Random;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Testing the core API.
@@ -76,22 +72,6 @@
         mHandlesToClose.add(handles.second);
     }
 
-    /**
-     * Runnable that will close the given handle.
-     */
-    private static class CloseHandle implements Runnable {
-        private Handle mHandle;
-
-        CloseHandle(Handle handle) {
-            mHandle = handle;
-        }
-
-        @Override
-        public void run() {
-            mHandle.close();
-        }
-    }
-
     private static void checkSendingMessage(MessagePipeHandle in, MessagePipeHandle out) {
         Random random = new Random();
 
@@ -184,46 +164,6 @@
     }
 
     /**
-     * Testing {@link Core#waitMany(List, long)}.
-     */
-    @SmallTest
-    public void testWaitMany() {
-        Core core = CoreImpl.getInstance();
-        Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
-        addHandlePairToClose(handles);
-
-        // Test waiting on handles of a newly created message pipe - each should be writable, but
-        // not readable.
-        List<Pair<Handle, Core.HandleSignals>> handlesToWaitOn =
-                new ArrayList<Pair<Handle, Core.HandleSignals>>();
-        handlesToWaitOn.add(
-                new Pair<Handle, Core.HandleSignals>(handles.second, Core.HandleSignals.READABLE));
-        handlesToWaitOn.add(
-                new Pair<Handle, Core.HandleSignals>(handles.first, Core.HandleSignals.WRITABLE));
-        WaitManyResult result = core.waitMany(handlesToWaitOn, 0);
-        assertEquals(MojoResult.OK, result.getMojoResult());
-        assertEquals(1, result.getHandleIndex());
-        for (HandleSignalsState state : result.getSignalStates()) {
-            assertEquals(HandleSignals.WRITABLE, state.getSatisfiedSignals());
-            assertEquals(ALL_SIGNALS, state.getSatisfiableSignals());
-        }
-
-        // Same test, but swap the handles around.
-        handlesToWaitOn.clear();
-        handlesToWaitOn.add(
-                new Pair<Handle, Core.HandleSignals>(handles.first, Core.HandleSignals.WRITABLE));
-        handlesToWaitOn.add(
-                new Pair<Handle, Core.HandleSignals>(handles.second, Core.HandleSignals.READABLE));
-        result = core.waitMany(handlesToWaitOn, 0);
-        assertEquals(MojoResult.OK, result.getMojoResult());
-        assertEquals(0, result.getHandleIndex());
-        for (HandleSignalsState state : result.getSignalStates()) {
-            assertEquals(HandleSignals.WRITABLE, state.getSatisfiedSignals());
-            assertEquals(ALL_SIGNALS, state.getSatisfiableSignals());
-        }
-    }
-
-    /**
      * Testing that Core can be retrieved from a handle.
      */
     @SmallTest
@@ -274,53 +214,14 @@
         Core core = CoreImpl.getInstance();
         Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
         addHandlePairToClose(handles);
-        // Test waiting on handles of a newly created message pipe.
-        WaitResult waitResult = handles.first.wait(
-                Core.HandleSignals.none().setReadable(true).setWritable(true), 0);
-        assertEquals(MojoResult.OK, waitResult.getMojoResult());
-        assertEquals(
-                HandleSignals.WRITABLE, waitResult.getHandleSignalsState().getSatisfiedSignals());
-        assertEquals(ALL_SIGNALS, waitResult.getHandleSignalsState().getSatisfiableSignals());
-
-        waitResult = handles.first.wait(Core.HandleSignals.WRITABLE, 0);
-        assertEquals(MojoResult.OK, waitResult.getMojoResult());
-        assertEquals(
-                HandleSignals.WRITABLE, waitResult.getHandleSignalsState().getSatisfiedSignals());
-        assertEquals(ALL_SIGNALS, waitResult.getHandleSignalsState().getSatisfiableSignals());
-
-        waitResult = handles.first.wait(Core.HandleSignals.READABLE, 0);
-        assertEquals(MojoResult.DEADLINE_EXCEEDED, waitResult.getMojoResult());
-        assertEquals(
-                HandleSignals.WRITABLE, waitResult.getHandleSignalsState().getSatisfiedSignals());
-        assertEquals(ALL_SIGNALS, waitResult.getHandleSignalsState().getSatisfiableSignals());
 
         // Testing read on an empty pipe.
         ResultAnd<MessagePipeHandle.ReadMessageResult> readResult =
                 handles.first.readMessage(null, 0, MessagePipeHandle.ReadFlags.NONE);
         assertEquals(MojoResult.SHOULD_WAIT, readResult.getMojoResult());
 
-        // Closing a pipe while waiting.
-        WORKER.schedule(new CloseHandle(handles.first), 10, TimeUnit.MILLISECONDS);
-        waitResult = handles.first.wait(Core.HandleSignals.READABLE, 1000000L);
-        assertEquals(MojoResult.CANCELLED, waitResult.getMojoResult());
-        assertEquals(
-                HandleSignals.none(), waitResult.getHandleSignalsState().getSatisfiedSignals());
-        assertEquals(
-                HandleSignals.none(), waitResult.getHandleSignalsState().getSatisfiableSignals());
-
-        handles = core.createMessagePipe(null);
-        addHandlePairToClose(handles);
-
-        // Closing the other pipe while waiting.
-        WORKER.schedule(new CloseHandle(handles.first), 10, TimeUnit.MILLISECONDS);
-        waitResult = handles.second.wait(Core.HandleSignals.READABLE, 1000000L);
-        assertEquals(MojoResult.FAILED_PRECONDITION, waitResult.getMojoResult());
-
-        // Waiting on a closed pipe.
-        waitResult = handles.second.wait(Core.HandleSignals.READABLE, 0);
-        assertEquals(MojoResult.FAILED_PRECONDITION, waitResult.getMojoResult());
-        waitResult = handles.second.wait(Core.HandleSignals.WRITABLE, 0);
-        assertEquals(MojoResult.FAILED_PRECONDITION, waitResult.getMojoResult());
+        handles.first.close();
+        handles.second.close();
     }
 
     /**
@@ -540,29 +441,6 @@
         Core core = CoreImpl.getInstance();
         Handle handle = InvalidHandle.INSTANCE;
 
-        // Checking wait.
-        boolean exception = false;
-        try {
-            core.wait(handle, Core.HandleSignals.WRITABLE, 0);
-        } catch (MojoException e) {
-            assertEquals(MojoResult.INVALID_ARGUMENT, e.getMojoResult());
-            exception = true;
-        }
-        assertTrue(exception);
-
-        // Checking waitMany.
-        exception = false;
-        try {
-            List<Pair<Handle, Core.HandleSignals>> handles =
-                    new ArrayList<Pair<Handle, Core.HandleSignals>>();
-            handles.add(Pair.create(handle, Core.HandleSignals.WRITABLE));
-            core.waitMany(handles, 0);
-        } catch (MojoException e) {
-            assertEquals(MojoResult.INVALID_ARGUMENT, e.getMojoResult());
-            exception = true;
-        }
-        assertTrue(exception);
-
         // Checking sending an invalid handle.
         // Until the behavior is changed on the C++ side, handle gracefully 2 different use case:
         // - Receive a INVALID_ARGUMENT exception
diff --git a/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java b/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java
index 6a99fe1..e14adb1 100644
--- a/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java
+++ b/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java
@@ -68,6 +68,17 @@
 
     private static class WatcherResult implements Callback {
         private int mResult = Integer.MIN_VALUE;
+        private MessagePipeHandle mReadPipe;
+
+        /**
+         * @param readPipe A MessagePipeHandle to read from when onResult triggers success.
+         */
+        public WatcherResult(MessagePipeHandle readPipe) {
+            mReadPipe = readPipe;
+        }
+        public WatcherResult() {
+            this(null);
+        }
 
         /**
          * @see Callback#onResult(int)
@@ -75,6 +86,11 @@
         @Override
         public void onResult(int result) {
             this.mResult = result;
+
+            if (result == MojoResult.OK && mReadPipe != null) {
+                mReadPipe.readMessage(
+                        null, 0, MessagePipeHandle.ReadFlags.none().setMayDiscard(true));
+            }
         }
 
         /**
@@ -93,7 +109,7 @@
         // Checking a correct result.
         Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null);
         addHandlePairToClose(handles);
-        final WatcherResult watcherResult = new WatcherResult();
+        final WatcherResult watcherResult = new WatcherResult(handles.first);
         assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
 
         mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult);
diff --git a/mojo/android/system/base_run_loop.cc b/mojo/android/system/base_run_loop.cc
index 6d9bd78..7993ba8 100644
--- a/mojo/android/system/base_run_loop.cc
+++ b/mojo/android/system/base_run_loop.cc
@@ -80,4 +80,3 @@
 
 }  // namespace android
 }  // namespace mojo
-
diff --git a/mojo/android/system/core_impl.cc b/mojo/android/system/core_impl.cc
index 4a23409..7d5a402 100644
--- a/mojo/android/system/core_impl.cc
+++ b/mojo/android/system/core_impl.cc
@@ -27,41 +27,6 @@
   return MojoGetTimeTicksNow();
 }
 
-static jint WaitMany(JNIEnv* env,
-                     const JavaParamRef<jobject>& jcaller,
-                     const JavaParamRef<jobject>& buffer,
-                     jlong deadline) {
-  // |buffer| contains, in this order
-  // input: The array of N handles (MojoHandle, 4 bytes each)
-  // input: The array of N signals (MojoHandleSignals, 4 bytes each)
-  // space for output: The array of N handle states (MojoHandleSignalsState, 8
-  //                   bytes each)
-  // space for output: The result index (uint32_t, 4 bytes)
-  uint8_t* buffer_start =
-      static_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));
-  DCHECK(buffer_start);
-  DCHECK_EQ(reinterpret_cast<uintptr_t>(buffer_start) % 8, 0u);
-  // Each handle of the input array contributes 4 (MojoHandle) + 4
-  // (MojoHandleSignals) + 8 (MojoHandleSignalsState) = 16 bytes to the size of
-  // the buffer.
-  const size_t size_per_handle = 16;
-  const size_t buffer_size = env->GetDirectBufferCapacity(buffer);
-  DCHECK_EQ((buffer_size - 4) % size_per_handle, 0u);
-
-  const size_t nb_handles = (buffer_size - 4) / size_per_handle;
-  const MojoHandle* handle_start =
-      reinterpret_cast<const MojoHandle*>(buffer_start);
-  const MojoHandleSignals* signals_start =
-      reinterpret_cast<const MojoHandleSignals*>(buffer_start + 4 * nb_handles);
-  MojoHandleSignalsState* states_start =
-      reinterpret_cast<MojoHandleSignalsState*>(buffer_start + 8 * nb_handles);
-  uint32_t* result_index =
-      reinterpret_cast<uint32_t*>(buffer_start + 16 * nb_handles);
-  *result_index = static_cast<uint32_t>(-1);
-  return MojoWaitMany(handle_start, signals_start, nb_handles, deadline,
-                      result_index, states_start);
-}
-
 static ScopedJavaLocalRef<jobject> CreateMessagePipe(
     JNIEnv* env,
     const JavaParamRef<jobject>& jcaller,
@@ -128,21 +93,16 @@
   return MojoClose(mojo_handle);
 }
 
-static jint Wait(JNIEnv* env,
-                 const JavaParamRef<jobject>& jcaller,
-                 const JavaParamRef<jobject>& buffer,
-                 jint mojo_handle,
-                 jint signals,
-                 jlong deadline) {
-  // Buffer contains space for the MojoHandleSignalsState
-  void* buffer_start = env->GetDirectBufferAddress(buffer);
-  DCHECK(buffer_start);
-  DCHECK_EQ(reinterpret_cast<const uintptr_t>(buffer_start) % 8, 0u);
-  DCHECK_EQ(sizeof(struct MojoHandleSignalsState),
+static jint QueryHandleSignalsState(JNIEnv* env,
+                                    const JavaParamRef<jobject>& jcaller,
+                                    jint mojo_handle,
+                                    const JavaParamRef<jobject>& buffer) {
+  MojoHandleSignalsState* signals_state =
+      static_cast<MojoHandleSignalsState*>(env->GetDirectBufferAddress(buffer));
+  DCHECK(signals_state);
+  DCHECK_EQ(sizeof(MojoHandleSignalsState),
             static_cast<size_t>(env->GetDirectBufferCapacity(buffer)));
-  struct MojoHandleSignalsState* signals_state =
-      static_cast<struct MojoHandleSignalsState*>(buffer_start);
-  return MojoWait(mojo_handle, signals, deadline, signals_state);
+  return MojoQueryHandleSignalsState(mojo_handle, signals_state);
 }
 
 static jint WriteMessage(JNIEnv* env,
diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java
index 8330586..173f801 100644
--- a/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java
+++ b/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java
@@ -8,6 +8,7 @@
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.MainDex;
 import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.Core.HandleSignalsState;
 import org.chromium.mojo.system.DataPipe;
 import org.chromium.mojo.system.DataPipe.ConsumerHandle;
 import org.chromium.mojo.system.DataPipe.ProducerHandle;
@@ -27,7 +28,6 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -91,61 +91,6 @@
     }
 
     /**
-     * @see Core#waitMany(List, long)
-     */
-    @Override
-    public WaitManyResult waitMany(List<Pair<Handle, HandleSignals>> handles, long deadline) {
-        // Allocate a direct buffer to allow native code not to reach back to java. The buffer
-        // layout will be:
-        // input: The array of handles (int, 4 bytes each)
-        // input: The array of signals (int, 4 bytes each)
-        // space for output: The array of handle states (2 ints, 8 bytes each)
-        // Space for output: The result index (int, 4 bytes)
-        // The handles and signals will be filled before calling the native method. When the native
-        // method returns, the handle states and the index will have been set.
-        ByteBuffer buffer = allocateDirectBuffer(handles.size() * 16 + 4);
-        int index = 0;
-        for (Pair<Handle, HandleSignals> handle : handles) {
-            buffer.putInt(HANDLE_SIZE * index, getMojoHandle(handle.first));
-            buffer.putInt(
-                    HANDLE_SIZE * handles.size() + FLAG_SIZE * index, handle.second.getFlags());
-            index++;
-        }
-        int code = nativeWaitMany(buffer, deadline);
-        WaitManyResult result = new WaitManyResult();
-        result.setMojoResult(filterMojoResultForWait(code));
-        result.setHandleIndex(buffer.getInt(handles.size() * 16));
-        if (result.getMojoResult() != MojoResult.INVALID_ARGUMENT
-                && result.getMojoResult() != MojoResult.RESOURCE_EXHAUSTED) {
-            HandleSignalsState[] states = new HandleSignalsState[handles.size()];
-            for (int i = 0; i < handles.size(); ++i) {
-                states[i] = new HandleSignalsState(
-                        new HandleSignals(buffer.getInt(8 * (handles.size() + i))),
-                        new HandleSignals(buffer.getInt(8 * (handles.size() + i) + 4)));
-            }
-            result.setSignalStates(Arrays.asList(states));
-        }
-        return result;
-    }
-
-    /**
-     * @see Core#wait(Handle, HandleSignals, long)
-     */
-    @Override
-    public WaitResult wait(Handle handle, HandleSignals signals, long deadline) {
-        // Allocate a direct buffer to allow native code not to reach back to java. Buffer will
-        // contain spaces to write the handle state.
-        ByteBuffer buffer = allocateDirectBuffer(8);
-        WaitResult result = new WaitResult();
-        result.setMojoResult(filterMojoResultForWait(
-                nativeWait(buffer, getMojoHandle(handle), signals.getFlags(), deadline)));
-        HandleSignalsState signalsState = new HandleSignalsState(
-                new HandleSignals(buffer.getInt(0)), new HandleSignals(buffer.getInt(4)));
-        result.setHandleSignalsState(signalsState);
-        return result;
-    }
-
-    /**
      * @see Core#createMessagePipe(MessagePipeHandle.CreateOptions)
      */
     @Override
@@ -262,6 +207,14 @@
         }
     }
 
+    HandleSignalsState queryHandleSignalsState(int mojoHandle) {
+        ByteBuffer buffer = allocateDirectBuffer(8);
+        int result = nativeQueryHandleSignalsState(mojoHandle, buffer);
+        if (result != MojoResult.OK) throw new MojoException(result);
+        return new HandleSignalsState(
+                new HandleSignals(buffer.getInt(0)), new HandleSignals(buffer.getInt(4)));
+    }
+
     /**
      * @see MessagePipeHandle#writeMessage(ByteBuffer, List, MessagePipeHandle.WriteFlags)
      */
@@ -525,8 +478,6 @@
 
     private native long nativeGetTimeTicksNow();
 
-    private native int nativeWaitMany(ByteBuffer buffer, long deadline);
-
     private native ResultAnd<IntegerPair> nativeCreateMessagePipe(ByteBuffer optionsBuffer);
 
     private native ResultAnd<IntegerPair> nativeCreateDataPipe(ByteBuffer optionsBuffer);
@@ -536,7 +487,7 @@
 
     private native int nativeClose(int mojoHandle);
 
-    private native int nativeWait(ByteBuffer buffer, int mojoHandle, int signals, long deadline);
+    private native int nativeQueryHandleSignalsState(int mojoHandle, ByteBuffer signalsStateBuffer);
 
     private native int nativeWriteMessage(
             int mojoHandle, ByteBuffer bytes, int numBytes, ByteBuffer handlesBuffer, int flags);
diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java b/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java
index a8870a8..4d149a4 100644
--- a/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java
+++ b/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java
@@ -7,8 +7,7 @@
 import android.util.Log;
 
 import org.chromium.mojo.system.Core;
-import org.chromium.mojo.system.Core.HandleSignals;
-import org.chromium.mojo.system.Core.WaitResult;
+import org.chromium.mojo.system.Core.HandleSignalsState;
 import org.chromium.mojo.system.Handle;
 import org.chromium.mojo.system.UntypedHandle;
 
@@ -63,11 +62,11 @@
     }
 
     /**
-     * @see org.chromium.mojo.system.Handle#wait(HandleSignals, long)
+     * @see org.chromium.mojo.system.Handle#querySignalsState()
      */
     @Override
-    public WaitResult wait(HandleSignals signals, long deadline) {
-        return mCore.wait(this, signals, deadline);
+    public HandleSignalsState querySignalsState() {
+        return mCore.queryHandleSignalsState(mMojoHandle);
     }
 
     /**
diff --git a/mojo/android/system/watcher_impl.cc b/mojo/android/system/watcher_impl.cc
index 09540fc..3344447 100644
--- a/mojo/android/system/watcher_impl.cc
+++ b/mojo/android/system/watcher_impl.cc
@@ -16,7 +16,7 @@
 #include "base/bind.h"
 #include "jni/WatcherImpl_jni.h"
 #include "mojo/public/cpp/system/handle.h"
-#include "mojo/public/cpp/system/watcher.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
 
 namespace mojo {
 namespace android {
@@ -27,7 +27,7 @@
 
 class WatcherImpl {
  public:
-  WatcherImpl() : watcher_(FROM_HERE) {}
+  WatcherImpl() : watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC) {}
 
   ~WatcherImpl() = default;
 
@@ -41,9 +41,8 @@
         base::Bind(&WatcherImpl::OnHandleReady, base::Unretained(this));
 
     MojoResult result =
-        watcher_.Start(mojo::Handle(static_cast<MojoHandle>(mojo_handle)),
+        watcher_.Watch(mojo::Handle(static_cast<MojoHandle>(mojo_handle)),
                        static_cast<MojoHandleSignals>(signals), ready_callback);
-
     if (result != MOJO_RESULT_OK)
       java_watcher_.Reset();
 
@@ -69,7 +68,7 @@
         result);
   }
 
-  Watcher watcher_;
+  SimpleWatcher watcher_;
   base::android::ScopedJavaGlobalRef<jobject> java_watcher_;
 
   DISALLOW_COPY_AND_ASSIGN(WatcherImpl);
diff --git a/mojo/common/data_pipe_drainer.cc b/mojo/common/data_pipe_drainer.cc
index 27bd893..e705c8d 100644
--- a/mojo/common/data_pipe_drainer.cc
+++ b/mojo/common/data_pipe_drainer.cc
@@ -17,10 +17,10 @@
                                  mojo::ScopedDataPipeConsumerHandle source)
     : client_(client),
       source_(std::move(source)),
-      handle_watcher_(FROM_HERE),
+      handle_watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC),
       weak_factory_(this) {
   DCHECK(client_);
-  handle_watcher_.Start(
+  handle_watcher_.Watch(
       source_.get(), MOJO_HANDLE_SIGNAL_READABLE,
       base::Bind(&DataPipeDrainer::WaitComplete, weak_factory_.GetWeakPtr()));
 }
diff --git a/mojo/common/data_pipe_drainer.h b/mojo/common/data_pipe_drainer.h
index d0366fa..5cff820 100644
--- a/mojo/common/data_pipe_drainer.h
+++ b/mojo/common/data_pipe_drainer.h
@@ -11,7 +11,7 @@
 #include "base/memory/weak_ptr.h"
 #include "mojo/common/mojo_common_export.h"
 #include "mojo/public/cpp/system/core.h"
-#include "mojo/public/cpp/system/watcher.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
 
 namespace mojo {
 namespace common {
@@ -36,7 +36,7 @@
 
   Client* client_;
   mojo::ScopedDataPipeConsumerHandle source_;
-  mojo::Watcher handle_watcher_;
+  mojo::SimpleWatcher handle_watcher_;
 
   base::WeakPtrFactory<DataPipeDrainer> weak_factory_;
 
diff --git a/mojo/common/data_pipe_utils.cc b/mojo/common/data_pipe_utils.cc
index bed5e85..9b069b8 100644
--- a/mojo/common/data_pipe_utils.cc
+++ b/mojo/common/data_pipe_utils.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "mojo/public/cpp/system/wait.h"
 
 namespace mojo {
 namespace common {
@@ -25,10 +26,7 @@
       if (bytes_written < num_bytes || result != MOJO_RESULT_OK)
         return false;
     } else if (result == MOJO_RESULT_SHOULD_WAIT) {
-      result = Wait(source.get(),
-                    MOJO_HANDLE_SIGNAL_READABLE,
-                    MOJO_DEADLINE_INDEFINITE,
-                    nullptr);
+      result = Wait(source.get(), MOJO_HANDLE_SIGNAL_READABLE);
       if (result != MOJO_RESULT_OK) {
         // If the producer handle was closed, then treat as EOF.
         return result == MOJO_RESULT_FAILED_PRECONDITION;
@@ -82,8 +80,7 @@
       if (it == source.end())
         return true;
     } else if (result == MOJO_RESULT_SHOULD_WAIT) {
-      result = Wait(destination.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
-                    MOJO_DEADLINE_INDEFINITE, nullptr);
+      result = Wait(destination.get(), MOJO_HANDLE_SIGNAL_WRITABLE);
       if (result != MOJO_RESULT_OK) {
         // If the consumer handle was closed, then treat as EOF.
         return result == MOJO_RESULT_FAILED_PRECONDITION;
diff --git a/mojo/edk/embedder/README.md b/mojo/edk/embedder/README.md
index 6def874..fc53bec 100644
--- a/mojo/edk/embedder/README.md
+++ b/mojo/edk/embedder/README.md
@@ -1,4 +1,9 @@
-# Mojo Embedder Development Kit (EDK)
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Embedder Development Kit (EDK)
+This document is a subset of the [Mojo documentation](/mojo).
+
+[TOC]
+
+## Overview
 
 The Mojo EDK is a (binary-unstable) API which enables a process to use Mojo both
 internally and for IPC to other Mojo-embedding processes.
@@ -8,6 +13,12 @@
 this fact, you should never reference any of the headers in `mojo/edk/system`
 directly, as everything there is considered to be an internal detail of the EDK.
 
+**NOTE:** Unless you are introducing a new binary entry point into the system
+(*e.g.,* a new executable with a new `main()` definition), you probably don't
+need to know anything about the EDK API. Most processes defined in the Chrome
+repo today already fully initialize the EDK so that Mojo's other public APIs
+"just work" out of the box.
+
 ## Basic Initialization
 
 In order to use Mojo in a given process, it's necessary to call
@@ -320,8 +331,16 @@
 Once you've bootstrapped your process connection with a real mojom interface,
 you can avoid any further mucking around with EDK APIs or raw message pipe
 handles, as everything beyond this point - including the passing of other
-interface pipes - can be handled eloquently using public bindings APIs.
+interface pipes - can be handled eloquently using
+[public bindings APIs](/mojo#High-Level-Bindings-APIs).
 
-See [additional Mojo documentation](
-    https://www.chromium.org/developers/design-documents/mojo) for more
-information.
+## Setting System Properties
+
+The public Mojo C System API exposes a
+[**`MojoGetProperty`**](/mojo/public/c/system#MojoGetProperty) function for
+querying global, embedder-defined property values. These can be set by calling:
+
+```
+mojo::edk::SetProperty(MojoPropertyType type, const void* value)
+```
+
diff --git a/mojo/edk/embedder/configuration.h b/mojo/edk/embedder/configuration.h
index 4b5618d..1990fb1 100644
--- a/mojo/edk/embedder/configuration.h
+++ b/mojo/edk/embedder/configuration.h
@@ -27,10 +27,6 @@
   // Maximum number of active memory mappings. The default is 1,000,000.
   size_t max_mapping_table_sze;
 
-  // Upper limit of |MojoWaitMany()|'s |num_handles|. The default is 1,000,000.
-  // Must be same as or smaller than |max_handle_table_size|.
-  size_t max_wait_many_num_handles;
-
   // Maximum data size of messages sent over message pipes, in bytes. The
   // default is 4MB.
   size_t max_message_num_bytes;
diff --git a/mojo/edk/embedder/embedder_unittest.cc b/mojo/edk/embedder/embedder_unittest.cc
index d5a87e5..388b45c 100644
--- a/mojo/edk/embedder/embedder_unittest.cc
+++ b/mojo/edk/embedder/embedder_unittest.cc
@@ -35,19 +35,13 @@
 #include "mojo/public/c/system/core.h"
 #include "mojo/public/cpp/system/handle.h"
 #include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/cpp/system/wait.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace mojo {
 namespace edk {
 namespace {
 
-const MojoHandleSignals kSignalReadadableWritable =
-    MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE;
-
-const MojoHandleSignals kSignalAll = MOJO_HANDLE_SIGNAL_READABLE |
-                                     MOJO_HANDLE_SIGNAL_WRITABLE |
-                                     MOJO_HANDLE_SIGNAL_PEER_CLOSED;
-
 // The multiprocess tests that use these don't compile on iOS.
 #if !defined(OS_IOS)
 const char kHelloWorld[] = "hello world";
@@ -70,50 +64,6 @@
   ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp));
 }
 
-// Test sending a MP which has read messages out of the OS pipe but which have
-// not been consumed using MojoReadMessage yet.
-TEST_F(EmbedderTest, SendReadableMessagePipe) {
-  MojoHandle server_mp, client_mp;
-  CreateMessagePipe(&server_mp, &client_mp);
-
-  MojoHandle server_mp2, client_mp2;
-  CreateMessagePipe(&server_mp2, &client_mp2);
-
-  // Write to server2 and wait for client2 to be readable before sending it.
-  // client2's MessagePipeDispatcher will have the message below in its
-  // message_queue_. For extra measures, also verify that this pending message
-  // can contain a message pipe.
-  MojoHandle server_mp3, client_mp3;
-  CreateMessagePipe(&server_mp3, &client_mp3);
-
-  const std::string kHello = "hello";
-  WriteMessageWithHandles(server_mp2, kHello, &client_mp3, 1);
-
-  MojoHandleSignalsState state;
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(client_mp2, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &state));
-  ASSERT_EQ(kSignalReadadableWritable, state.satisfied_signals);
-  ASSERT_EQ(kSignalAll, state.satisfiable_signals);
-
-  // Now send client2
-  WriteMessageWithHandles(server_mp, kHello, &client_mp2, 1);
-
-  MojoHandle port;
-  std::string message = ReadMessageWithHandles(client_mp, &port, 1);
-  EXPECT_EQ(kHello, message);
-
-  client_mp2 = port;
-  message = ReadMessageWithHandles(client_mp2, &client_mp3, 1);
-  EXPECT_EQ(kHello, message);
-
-  ASSERT_EQ(MOJO_RESULT_OK, MojoClose(server_mp3));
-  ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp3));
-  ASSERT_EQ(MOJO_RESULT_OK, MojoClose(server_mp2));
-  ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp2));
-  ASSERT_EQ(MOJO_RESULT_OK, MojoClose(server_mp));
-  ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp));
-}
-
 // Verifies that a MP with pending messages to be written can be sent and the
 // pending messages aren't dropped.
 TEST_F(EmbedderTest, SendMessagePipeWithWriteQueue) {
@@ -217,10 +167,8 @@
   // the reserved port.
   ignore_result(pair.PassClientHandle());
 
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWait(parent_mp.get().value(),
-                                     MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                                     MOJO_DEADLINE_INDEFINITE,
-                                     nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(parent_mp.get().value(),
+                                           MOJO_HANDLE_SIGNAL_PEER_CLOSED));
 }
 
 TEST_F(EmbedderTest, PipeSetup_LaunchFailure) {
@@ -234,10 +182,8 @@
   // called, any message pipes associated with it detect peer closure.
   process.reset();
 
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWait(parent_mp.get().value(),
-                                     MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                                     MOJO_DEADLINE_INDEFINITE,
-                                     nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(parent_mp.get().value(),
+                                           MOJO_HANDLE_SIGNAL_PEER_CLOSED));
 }
 
 // The sequence of messages sent is:
@@ -292,9 +238,7 @@
     // 10. Wait on |mp2| (which should eventually fail) and then close it.
     MojoHandleSignalsState state;
     ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-              MojoWait(mp2, MOJO_HANDLE_SIGNAL_READABLE,
-                       MOJO_DEADLINE_INDEFINITE,
-                       &state));
+              WaitForSignals(mp2, MOJO_HANDLE_SIGNAL_READABLE, &state));
     ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals);
     ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals);
 
@@ -336,8 +280,7 @@
   // 10. Wait on |mp1| (which should eventually fail) and then close it.
   MojoHandleSignalsState state;
   ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            MojoWait(mp1, MOJO_HANDLE_SIGNAL_READABLE,
-                      MOJO_DEADLINE_INDEFINITE, &state));
+            WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &state));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals);
   ASSERT_EQ(MOJO_RESULT_OK, MojoClose(mp1));
@@ -586,8 +529,7 @@
       ConnectToPeerProcess(CreateServerHandle(named_handle), peer_token);
   ClosePeerConnection(peer_token);
   EXPECT_EQ(MOJO_RESULT_OK,
-            Wait(server_pipe.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                 MOJO_DEADLINE_INDEFINITE, nullptr));
+            Wait(server_pipe.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED));
   base::MessageLoop message_loop;
   base::RunLoop run_loop;
   ScopedPlatformHandle client_handle;
@@ -617,8 +559,8 @@
 
   controller.ClosePeerConnection();
 
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWait(server_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                                     MOJO_DEADLINE_INDEFINITE, nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(server_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
 
   EXPECT_EQ(0, controller.WaitForShutdown());
 }
@@ -632,8 +574,7 @@
   WriteMessage(client_mp, "world!");
 
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(client_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                      MOJO_DEADLINE_INDEFINITE, nullptr));
+            WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
 }
 
 TEST_F(EmbedderTest, ClosePipeToConnectingPeer) {
@@ -643,16 +584,16 @@
 
   MojoHandle server_mp = controller.pipe();
 
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWait(server_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                                     MOJO_DEADLINE_INDEFINITE, nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(server_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
 
   EXPECT_EQ(0, controller.WaitForShutdown());
 }
 
 DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ClosePipeToConnectingPeerClient, EmbedderTest,
                                   client_mp) {
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(client_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                                     MOJO_DEADLINE_INDEFINITE, nullptr));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
 }
 
 #endif  // !defined(OS_IOS)
diff --git a/mojo/edk/embedder/entrypoints.cc b/mojo/edk/embedder/entrypoints.cc
index f09c5e1..9081368 100644
--- a/mojo/edk/embedder/entrypoints.cc
+++ b/mojo/edk/embedder/entrypoints.cc
@@ -13,7 +13,6 @@
 #include "mojo/public/c/system/functions.h"
 #include "mojo/public/c/system/message_pipe.h"
 #include "mojo/public/c/system/platform_handle.h"
-#include "mojo/public/c/system/wait_set.h"
 
 using mojo::edk::internal::g_core;
 
@@ -28,32 +27,35 @@
   return g_core->Close(handle);
 }
 
-MojoResult MojoWaitImpl(MojoHandle handle,
-                        MojoHandleSignals signals,
-                        MojoDeadline deadline,
-                        MojoHandleSignalsState* signals_state) {
-  return g_core->Wait(handle, signals, deadline, signals_state);
+MojoResult MojoQueryHandleSignalsStateImpl(
+    MojoHandle handle,
+    MojoHandleSignalsState* signals_state) {
+  return g_core->QueryHandleSignalsState(handle, signals_state);
 }
 
-MojoResult MojoWaitManyImpl(const MojoHandle* handles,
-                            const MojoHandleSignals* signals,
-                            uint32_t num_handles,
-                            MojoDeadline deadline,
-                            uint32_t* result_index,
-                            MojoHandleSignalsState* signals_states) {
-  return g_core->WaitMany(handles, signals, num_handles, deadline, result_index,
-                          signals_states);
+MojoResult MojoCreateWatcherImpl(MojoWatcherCallback callback,
+                                 MojoHandle* watcher_handle) {
+  return g_core->CreateWatcher(callback, watcher_handle);
 }
 
-MojoResult MojoWatchImpl(MojoHandle handle,
+MojoResult MojoArmWatcherImpl(MojoHandle watcher_handle,
+                              uint32_t* num_ready_contexts,
+                              uintptr_t* ready_contexts,
+                              MojoResult* ready_results,
+                              MojoHandleSignalsState* ready_signals_states) {
+  return g_core->ArmWatcher(watcher_handle, num_ready_contexts, ready_contexts,
+                            ready_results, ready_signals_states);
+}
+
+MojoResult MojoWatchImpl(MojoHandle watcher_handle,
+                         MojoHandle handle,
                          MojoHandleSignals signals,
-                         MojoWatchCallback callback,
                          uintptr_t context) {
-  return g_core->Watch(handle, signals, callback, context);
+  return g_core->Watch(watcher_handle, handle, signals, context);
 }
 
-MojoResult MojoCancelWatchImpl(MojoHandle handle, uintptr_t context) {
-  return g_core->CancelWatch(handle, context);
+MojoResult MojoCancelWatchImpl(MojoHandle watcher_handle, uintptr_t context) {
+  return g_core->CancelWatch(watcher_handle, context);
 }
 
 MojoResult MojoAllocMessageImpl(uint32_t num_bytes,
@@ -72,30 +74,6 @@
   return g_core->GetMessageBuffer(message, buffer);
 }
 
-MojoResult MojoCreateWaitSetImpl(MojoHandle* wait_set_handle) {
-  return g_core->CreateWaitSet(wait_set_handle);
-}
-
-MojoResult MojoAddHandleImpl(MojoHandle wait_set_handle,
-                             MojoHandle handle,
-                             MojoHandleSignals signals) {
-  return g_core->AddHandle(wait_set_handle, handle, signals);
-}
-
-MojoResult MojoRemoveHandleImpl(MojoHandle wait_set_handle, MojoHandle handle) {
-  return g_core->RemoveHandle(wait_set_handle, handle);
-}
-
-MojoResult MojoGetReadyHandlesImpl(
-    MojoHandle wait_set_handle,
-    uint32_t* count,
-    MojoHandle* handles,
-    MojoResult* results,
-    struct MojoHandleSignalsState* signals_states) {
-  return g_core->GetReadyHandles(wait_set_handle, count, handles, results,
-                                 signals_states);
-}
-
 MojoResult MojoCreateMessagePipeImpl(
     const MojoCreateMessagePipeOptions* options,
     MojoHandle* message_pipe_handle0,
@@ -267,8 +245,7 @@
   MojoSystemThunks system_thunks = {sizeof(MojoSystemThunks),
                                     MojoGetTimeTicksNowImpl,
                                     MojoCloseImpl,
-                                    MojoWaitImpl,
-                                    MojoWaitManyImpl,
+                                    MojoQueryHandleSignalsStateImpl,
                                     MojoCreateMessagePipeImpl,
                                     MojoWriteMessageImpl,
                                     MojoReadMessageImpl,
@@ -283,12 +260,10 @@
                                     MojoDuplicateBufferHandleImpl,
                                     MojoMapBufferImpl,
                                     MojoUnmapBufferImpl,
-                                    MojoCreateWaitSetImpl,
-                                    MojoAddHandleImpl,
-                                    MojoRemoveHandleImpl,
-                                    MojoGetReadyHandlesImpl,
+                                    MojoCreateWatcherImpl,
                                     MojoWatchImpl,
                                     MojoCancelWatchImpl,
+                                    MojoArmWatcherImpl,
                                     MojoFuseMessagePipesImpl,
                                     MojoWriteMessageNewImpl,
                                     MojoReadMessageNewImpl,
diff --git a/mojo/edk/js/core.cc b/mojo/edk/js/core.cc
index f3eec8c..baccc4c 100644
--- a/mojo/edk/js/core.cc
+++ b/mojo/edk/js/core.cc
@@ -21,6 +21,7 @@
 #include "gin/wrappable.h"
 #include "mojo/edk/js/drain_data.h"
 #include "mojo/edk/js/handle.h"
+#include "mojo/public/cpp/system/wait.h"
 
 namespace mojo {
 namespace edk {
@@ -35,19 +36,31 @@
   return MOJO_RESULT_OK;
 }
 
+gin::Dictionary QueryHandleSignalsState(const gin::Arguments& args,
+                                        mojo::Handle handle) {
+  gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+  if (!handle.is_valid()) {
+    dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT);
+  } else {
+    HandleSignalsState state = handle.QuerySignalsState();
+    dictionary.Set("result", MOJO_RESULT_OK);
+    dictionary.Set("satisfiedSignals", state.satisfied_signals);
+    dictionary.Set("satisfiableSignals", state.satisfiable_signals);
+  }
+  return dictionary;
+}
+
 gin::Dictionary WaitHandle(const gin::Arguments& args,
                            mojo::Handle handle,
-                           MojoHandleSignals signals,
-                           MojoDeadline deadline) {
+                           MojoHandleSignals signals) {
   v8::Isolate* isolate = args.isolate();
   gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate);
 
   MojoHandleSignalsState signals_state;
-  MojoResult result = mojo::Wait(handle, signals, deadline, &signals_state);
+  MojoResult result = Wait(handle, signals, &signals_state);
   dictionary.Set("result", result);
 
-  mojo::WaitManyResult wmv(result, 0);
-  if (!wmv.AreSignalsStatesValid()) {
+  if (result != MOJO_RESULT_OK && result != MOJO_RESULT_FAILED_PRECONDITION) {
     dictionary.Set("signalsState", v8::Null(isolate).As<v8::Value>());
   } else {
     gin::Dictionary signalsStateDict = gin::Dictionary::CreateEmpty(isolate);
@@ -60,40 +73,6 @@
   return dictionary;
 }
 
-gin::Dictionary WaitMany(const gin::Arguments& args,
-                         const std::vector<mojo::Handle>& handles,
-                         const std::vector<MojoHandleSignals>& signals,
-                         MojoDeadline deadline) {
-  v8::Isolate* isolate = args.isolate();
-  gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate);
-
-  std::vector<MojoHandleSignalsState> signals_states(signals.size());
-  mojo::WaitManyResult wmv =
-      mojo::WaitMany(handles, signals, deadline, &signals_states);
-  dictionary.Set("result", wmv.result);
-  if (wmv.IsIndexValid()) {
-    dictionary.Set("index", wmv.index);
-  } else {
-    dictionary.Set("index", v8::Null(isolate).As<v8::Value>());
-  }
-  if (wmv.AreSignalsStatesValid()) {
-    std::vector<gin::Dictionary> vec;
-    for (size_t i = 0; i < handles.size(); ++i) {
-      gin::Dictionary signalsStateDict = gin::Dictionary::CreateEmpty(isolate);
-      signalsStateDict.Set("satisfiedSignals",
-                           signals_states[i].satisfied_signals);
-      signalsStateDict.Set("satisfiableSignals",
-                           signals_states[i].satisfiable_signals);
-      vec.push_back(signalsStateDict);
-    }
-    dictionary.Set("signalsState", vec);
-  } else {
-    dictionary.Set("signalsState", v8::Null(isolate).As<v8::Value>());
-  }
-
-  return dictionary;
-}
-
 gin::Dictionary CreateMessagePipe(const gin::Arguments& args) {
   gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
   dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT);
@@ -388,8 +367,8 @@
             // TODO(mpcomplete): Should these just be methods on the JS Handle
             // object?
             .SetMethod("close", CloseHandle)
+            .SetMethod("queryHandleSignalsState", QueryHandleSignalsState)
             .SetMethod("wait", WaitHandle)
-            .SetMethod("waitMany", WaitMany)
             .SetMethod("createMessagePipe", CreateMessagePipe)
             .SetMethod("writeMessage", WriteMessage)
             .SetMethod("readMessage", ReadMessage)
@@ -424,8 +403,6 @@
             .SetValue("RESULT_BUSY", MOJO_RESULT_BUSY)
             .SetValue("RESULT_SHOULD_WAIT", MOJO_RESULT_SHOULD_WAIT)
 
-            .SetValue("DEADLINE_INDEFINITE", MOJO_DEADLINE_INDEFINITE)
-
             .SetValue("HANDLE_SIGNAL_NONE", MOJO_HANDLE_SIGNAL_NONE)
             .SetValue("HANDLE_SIGNAL_READABLE", MOJO_HANDLE_SIGNAL_READABLE)
             .SetValue("HANDLE_SIGNAL_WRITABLE", MOJO_HANDLE_SIGNAL_WRITABLE)
diff --git a/mojo/edk/js/drain_data.cc b/mojo/edk/js/drain_data.cc
index cfd0bb5..334ced3 100644
--- a/mojo/edk/js/drain_data.cc
+++ b/mojo/edk/js/drain_data.cc
@@ -23,7 +23,7 @@
 DrainData::DrainData(v8::Isolate* isolate, mojo::Handle handle)
     : isolate_(isolate),
       handle_(DataPipeConsumerHandle(handle.value())),
-      handle_watcher_(FROM_HERE) {
+      handle_watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC) {
   v8::Handle<v8::Context> context(isolate_->GetCurrentContext());
   runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr();
 
@@ -43,7 +43,7 @@
 }
 
 void DrainData::WaitForData() {
-  handle_watcher_.Start(
+  handle_watcher_.Watch(
       handle_.get(), MOJO_HANDLE_SIGNAL_READABLE,
       base::Bind(&DrainData::DataReady, base::Unretained(this)));
 }
diff --git a/mojo/edk/js/drain_data.h b/mojo/edk/js/drain_data.h
index 6e8555c..42da90f 100644
--- a/mojo/edk/js/drain_data.h
+++ b/mojo/edk/js/drain_data.h
@@ -10,7 +10,7 @@
 
 #include "gin/runner.h"
 #include "mojo/public/cpp/system/core.h"
-#include "mojo/public/cpp/system/watcher.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
 #include "v8/include/v8.h"
 
 namespace mojo {
@@ -52,7 +52,7 @@
 
   v8::Isolate* isolate_;
   ScopedDataPipeConsumerHandle handle_;
-  Watcher handle_watcher_;
+  SimpleWatcher handle_watcher_;
   base::WeakPtr<gin::Runner> runner_;
   v8::UniquePersistent<v8::Promise::Resolver> resolver_;
   std::vector<std::unique_ptr<DataBuffer>> data_buffers_;
diff --git a/mojo/edk/js/tests/BUILD.gn b/mojo/edk/js/tests/BUILD.gn
index 41850d7..f56c4b9 100644
--- a/mojo/edk/js/tests/BUILD.gn
+++ b/mojo/edk/js/tests/BUILD.gn
@@ -58,7 +58,6 @@
     "//mojo/edk/test:test_support",
     "//mojo/public/cpp/system",
     "//mojo/public/interfaces/bindings/tests:test_interfaces",
-    "//mojo/public/interfaces/bindings/tests:test_interfaces_experimental",
     "//mojo/public/js:tests",
   ]
 
diff --git a/mojo/edk/js/tests/js_to_cpp_tests.cc b/mojo/edk/js/tests/js_to_cpp_tests.cc
index e5e6bd1..b6b74e3 100644
--- a/mojo/edk/js/tests/js_to_cpp_tests.cc
+++ b/mojo/edk/js/tests/js_to_cpp_tests.cc
@@ -25,6 +25,7 @@
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/lib/validation_errors.h"
 #include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/system/wait.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace mojo {
@@ -76,9 +77,7 @@
 void CheckMessagePipe(MessagePipeHandle message_pipe_handle) {
   unsigned char buffer[100];
   uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
-  MojoResult result = Wait(
-      message_pipe_handle, MOJO_HANDLE_SIGNAL_READABLE,
-      MOJO_DEADLINE_INDEFINITE, nullptr);
+  MojoResult result = Wait(message_pipe_handle, MOJO_HANDLE_SIGNAL_READABLE);
   EXPECT_EQ(MOJO_RESULT_OK, result);
   result = ReadMessageRaw(
       message_pipe_handle, buffer, &buffer_size, 0, 0, 0);
diff --git a/mojo/edk/js/tests/run_js_unittests.cc b/mojo/edk/js/tests/run_js_unittests.cc
index a7b70b7..13e796b 100644
--- a/mojo/edk/js/tests/run_js_unittests.cc
+++ b/mojo/edk/js/tests/run_js_unittests.cc
@@ -7,6 +7,7 @@
 #include "base/path_service.h"
 #include "gin/modules/console.h"
 #include "gin/modules/module_registry.h"
+#include "gin/modules/timer.h"
 #include "gin/test/file_runner.h"
 #include "gin/test/gtest.h"
 #include "mojo/edk/js/core.h"
@@ -23,6 +24,7 @@
  public:
   TestRunnerDelegate() {
     AddBuiltinModule(gin::Console::kModuleName, gin::Console::GetModule);
+    AddBuiltinModule(gin::TimerModule::kName, gin::TimerModule::GetModule);
     AddBuiltinModule(Core::kModuleName, Core::GetModule);
     AddBuiltinModule(Threading::kModuleName, Threading::GetModule);
     AddBuiltinModule(Support::kModuleName, Support::GetModule);
@@ -45,34 +47,10 @@
 }
 
 // TODO(abarth): Should we autogenerate these stubs from GYP?
-TEST(JSTest, Codec) {
-  RunTest("codec_unittest.js", true);
-}
-
-TEST(JSTest, Connection) {
-  RunTest("connection_unittest.js", false);
-}
-
 TEST(JSTest, Core) {
   RunTest("core_unittest.js", true);
 }
 
-TEST(JSTest, InterfacePtr) {
-  RunTest("interface_ptr_unittest.js", false);
-}
-
-TEST(JSTest, SampleService) {
-  RunTest("sample_service_unittest.js", false);
-}
-
-TEST(JSTest, Struct) {
-  RunTest("struct_unittest.js", true);
-}
-
-TEST(JSTest, Union) {
-  RunTest("union_unittest.js", true);
-}
-
 TEST(JSTest, Validation) {
   RunTest("validation_unittest.js", true);
 }
diff --git a/mojo/edk/js/waiting_callback.cc b/mojo/edk/js/waiting_callback.cc
index fada039..6ad4bd0 100644
--- a/mojo/edk/js/waiting_callback.cc
+++ b/mojo/edk/js/waiting_callback.cc
@@ -32,7 +32,7 @@
     bool one_shot) {
   gin::Handle<WaitingCallback> waiting_callback = gin::CreateHandle(
       isolate, new WaitingCallback(isolate, callback, one_shot));
-  MojoResult result = waiting_callback->watcher_.Start(
+  MojoResult result = waiting_callback->watcher_.Watch(
       handle_wrapper->get(), signals,
       base::Bind(&WaitingCallback::OnHandleReady,
                  base::Unretained(waiting_callback.get())));
@@ -53,7 +53,7 @@
                                  v8::Handle<v8::Function> callback,
                                  bool one_shot)
     : one_shot_(one_shot),
-      watcher_(FROM_HERE),
+      watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC),
       weak_factory_(this) {
   v8::Handle<v8::Context> context = isolate->GetCurrentContext();
   runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr();
diff --git a/mojo/edk/js/waiting_callback.h b/mojo/edk/js/waiting_callback.h
index 1195a98..f97b389 100644
--- a/mojo/edk/js/waiting_callback.h
+++ b/mojo/edk/js/waiting_callback.h
@@ -12,7 +12,7 @@
 #include "gin/wrappable.h"
 #include "mojo/edk/js/handle.h"
 #include "mojo/public/cpp/system/core.h"
-#include "mojo/public/cpp/system/watcher.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
 
 namespace mojo {
 namespace edk {
@@ -54,7 +54,7 @@
   const bool one_shot_;
 
   base::WeakPtr<gin::Runner> runner_;
-  Watcher watcher_;
+  SimpleWatcher watcher_;
   base::WeakPtrFactory<WaitingCallback> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(WaitingCallback);
diff --git a/mojo/edk/system/BUILD.gn b/mojo/edk/system/BUILD.gn
index b0acf23..a68cd44 100644
--- a/mojo/edk/system/BUILD.gn
+++ b/mojo/edk/system/BUILD.gn
@@ -16,9 +16,6 @@
 
   sources = [
     "atomic_flag.h",
-    "awakable.h",
-    "awakable_list.cc",
-    "awakable_list.h",
     "broker.h",
     "broker_host.cc",
     "broker_host.h",
@@ -62,12 +59,10 @@
     "request_context.h",
     "shared_buffer_dispatcher.cc",
     "shared_buffer_dispatcher.h",
-    "wait_set_dispatcher.cc",
-    "wait_set_dispatcher.h",
-    "waiter.cc",
-    "waiter.h",
-    "watcher.cc",
-    "watcher.h",
+    "watch.cc",
+    "watch.h",
+    "watcher_dispatcher.cc",
+    "watcher_dispatcher.h",
     "watcher_set.cc",
     "watcher_set.h",
   ]
@@ -153,7 +148,6 @@
 
 test("mojo_system_unittests") {
   sources = [
-    "awakable_list_unittest.cc",
     "channel_unittest.cc",
     "core_test_base.cc",
     "core_test_base.h",
@@ -163,11 +157,8 @@
     "platform_handle_dispatcher_unittest.cc",
     "shared_buffer_dispatcher_unittest.cc",
     "shared_buffer_unittest.cc",
-    "wait_set_dispatcher_unittest.cc",
-    "waiter_test_utils.cc",
-    "waiter_test_utils.h",
-    "waiter_unittest.cc",
-    "watch_unittest.cc",
+    "signals_unittest.cc",
+    "watcher_unittest.cc",
   ]
 
   if (!is_ios) {
@@ -187,6 +178,7 @@
     "//mojo/edk/system/ports:tests",
     "//mojo/edk/test:run_all_unittests",
     "//mojo/edk/test:test_support",
+    "//mojo/public/cpp/system",
     "//testing/gmock",
     "//testing/gtest",
   ]
diff --git a/mojo/edk/system/awakable.h b/mojo/edk/system/awakable.h
deleted file mode 100644
index 2cb10f5..0000000
--- a/mojo/edk/system/awakable.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2013 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 MOJO_EDK_SYSTEM_AWAKABLE_H_
-#define MOJO_EDK_SYSTEM_AWAKABLE_H_
-
-#include <stdint.h>
-
-#include "mojo/edk/system/system_impl_export.h"
-#include "mojo/public/c/system/types.h"
-
-namespace mojo {
-namespace edk {
-
-// An interface that may be waited on |AwakableList|.
-class MOJO_SYSTEM_IMPL_EXPORT Awakable {
- public:
-  // |Awake()| must satisfy the following contract:
-  // * As this is called from any thread, this must be thread-safe.
-  // * As this is called inside a lock, this must not call anything that takes
-  //   "non-terminal" locks, i.e., those which are always safe to take.
-  // This should return false if this must not be called again for the same
-  // reason (e.g., for the same call to |AwakableList::Add()|).
-  virtual bool Awake(MojoResult result, uintptr_t context) = 0;
-
- protected:
-  Awakable() {}
-};
-
-}  // namespace edk
-}  // namespace mojo
-
-#endif  // MOJO_EDK_SYSTEM_AWAKABLE_H_
diff --git a/mojo/edk/system/awakable_list.cc b/mojo/edk/system/awakable_list.cc
deleted file mode 100644
index 2045f32..0000000
--- a/mojo/edk/system/awakable_list.cc
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2013 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 "mojo/edk/system/awakable_list.h"
-
-#include <algorithm>
-
-#include "base/logging.h"
-#include "mojo/edk/system/awakable.h"
-#include "mojo/edk/system/handle_signals_state.h"
-
-namespace mojo {
-namespace edk {
-
-AwakableList::AwakableList() {
-}
-
-AwakableList::~AwakableList() {
-  DCHECK(awakables_.empty());
-}
-
-void AwakableList::AwakeForStateChange(const HandleSignalsState& state) {
-  // Instead of deleting elements in-place, swap them with the last element and
-  // erase the elements from the end.
-  auto last = awakables_.end();
-  for (AwakeInfoList::iterator it = awakables_.begin(); it != last;) {
-    bool keep = true;
-    if (state.satisfies(it->signals))
-      keep = it->awakable->Awake(MOJO_RESULT_OK, it->context);
-    else if (!state.can_satisfy(it->signals))
-      keep = it->awakable->Awake(MOJO_RESULT_FAILED_PRECONDITION, it->context);
-
-    if (!keep) {
-      --last;
-      std::swap(*it, *last);
-    } else {
-      ++it;
-    }
-  }
-  awakables_.erase(last, awakables_.end());
-  watchers_.NotifyForStateChange(state);
-}
-
-void AwakableList::CancelAll() {
-  for (AwakeInfoList::iterator it = awakables_.begin(); it != awakables_.end();
-       ++it) {
-    it->awakable->Awake(MOJO_RESULT_CANCELLED, it->context);
-  }
-  awakables_.clear();
-  watchers_.NotifyClosed();
-}
-
-void AwakableList::Add(Awakable* awakable,
-                       MojoHandleSignals signals,
-                       uintptr_t context) {
-  awakables_.push_back(AwakeInfo(awakable, signals, context));
-}
-
-void AwakableList::Remove(Awakable* awakable) {
-  // We allow a thread to wait on the same handle multiple times simultaneously,
-  // so we need to scan the entire list and remove all occurrences of |waiter|.
-  auto last = awakables_.end();
-  for (AwakeInfoList::iterator it = awakables_.begin(); it != last;) {
-    if (it->awakable == awakable) {
-      --last;
-      std::swap(*it, *last);
-    } else {
-      ++it;
-    }
-  }
-  awakables_.erase(last, awakables_.end());
-}
-
-MojoResult AwakableList::AddWatcher(MojoHandleSignals signals,
-                                    const Watcher::WatchCallback& callback,
-                                    uintptr_t context,
-                                    const HandleSignalsState& current_state) {
-  return watchers_.Add(signals, callback, context, current_state);
-}
-
-MojoResult AwakableList::RemoveWatcher(uintptr_t context) {
-  return watchers_.Remove(context);
-}
-
-}  // namespace edk
-}  // namespace mojo
diff --git a/mojo/edk/system/awakable_list.h b/mojo/edk/system/awakable_list.h
deleted file mode 100644
index 355677f..0000000
--- a/mojo/edk/system/awakable_list.h
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2013 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 MOJO_EDK_SYSTEM_AWAKABLE_LIST_H_
-#define MOJO_EDK_SYSTEM_AWAKABLE_LIST_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <vector>
-
-#include "base/macros.h"
-#include "mojo/edk/system/system_impl_export.h"
-#include "mojo/edk/system/watcher.h"
-#include "mojo/edk/system/watcher_set.h"
-#include "mojo/public/c/system/types.h"
-
-namespace mojo {
-namespace edk {
-
-class Awakable;
-struct HandleSignalsState;
-
-// |AwakableList| tracks all the |Waiter|s that are waiting on a given
-// handle/|Dispatcher|. There should be a |AwakableList| for each handle that
-// can be waited on (in any way). In the simple case, the |AwakableList| is
-// owned by the |Dispatcher|, whereas in more complex cases it is owned by the
-// secondary object (see simple_dispatcher.* and the explanatory comment in
-// core.cc). This class is thread-unsafe (all concurrent access must be
-// protected by some lock).
-class MOJO_SYSTEM_IMPL_EXPORT AwakableList {
- public:
-  AwakableList();
-  ~AwakableList();
-
-  void AwakeForStateChange(const HandleSignalsState& state);
-  void CancelAll();
-  void Add(Awakable* awakable, MojoHandleSignals signals, uintptr_t context);
-  void Remove(Awakable* awakable);
-
-  // Add and remove Watchers to this AwakableList.
-  MojoResult AddWatcher(MojoHandleSignals signals,
-                        const Watcher::WatchCallback& callback,
-                        uintptr_t context,
-                        const HandleSignalsState& current_state);
-  MojoResult RemoveWatcher(uintptr_t context);
-
- private:
-  struct AwakeInfo {
-    AwakeInfo(Awakable* awakable, MojoHandleSignals signals, uintptr_t context)
-        : awakable(awakable), signals(signals), context(context) {}
-
-    Awakable* awakable;
-    MojoHandleSignals signals;
-    uintptr_t context;
-  };
-  using AwakeInfoList = std::vector<AwakeInfo>;
-
-  AwakeInfoList awakables_;
-
-  // TODO: Remove AwakableList and instead use WatcherSet directly in
-  // dispatchers.
-  WatcherSet watchers_;
-
-  DISALLOW_COPY_AND_ASSIGN(AwakableList);
-};
-
-}  // namespace edk
-}  // namespace mojo
-
-#endif  // MOJO_EDK_SYSTEM_AWAKABLE_LIST_H_
diff --git a/mojo/edk/system/awakable_list_unittest.cc b/mojo/edk/system/awakable_list_unittest.cc
deleted file mode 100644
index 9737fce..0000000
--- a/mojo/edk/system/awakable_list_unittest.cc
+++ /dev/null
@@ -1,356 +0,0 @@
-// Copyright 2013 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.
-
-// NOTE(vtl): Some of these tests are inherently flaky (e.g., if run on a
-// heavily-loaded system). Sorry. |test::EpsilonDeadline()| may be increased to
-// increase tolerance and reduce observed flakiness (though doing so reduces the
-// meaningfulness of the test).
-
-#include "mojo/edk/system/awakable_list.h"
-
-#include "mojo/edk/system/handle_signals_state.h"
-#include "mojo/edk/system/test_utils.h"
-#include "mojo/edk/system/waiter.h"
-#include "mojo/edk/system/waiter_test_utils.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace mojo {
-namespace edk {
-namespace {
-
-TEST(AwakableListTest, BasicCancel) {
-  MojoResult result;
-  uintptr_t context;
-
-  // Cancel immediately after thread start.
-  {
-    AwakableList awakable_list;
-    test::SimpleWaiterThread thread(&result, &context);
-    awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1);
-    thread.Start();
-    awakable_list.CancelAll();
-    // Double-remove okay:
-    awakable_list.Remove(thread.waiter());
-  }  // Join |thread|.
-  EXPECT_EQ(MOJO_RESULT_CANCELLED, result);
-  EXPECT_EQ(1u, context);
-
-  // Cancel before after thread start.
-  {
-    AwakableList awakable_list;
-    test::SimpleWaiterThread thread(&result, &context);
-    awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2);
-    awakable_list.CancelAll();
-    thread.Start();
-  }  // Join |thread|.
-  EXPECT_EQ(MOJO_RESULT_CANCELLED, result);
-  EXPECT_EQ(2u, context);
-
-  // Cancel some time after thread start.
-  {
-    AwakableList awakable_list;
-    test::SimpleWaiterThread thread(&result, &context);
-    awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3);
-    thread.Start();
-    test::Sleep(2 * test::EpsilonDeadline());
-    awakable_list.CancelAll();
-  }  // Join |thread|.
-  EXPECT_EQ(MOJO_RESULT_CANCELLED, result);
-  EXPECT_EQ(3u, context);
-}
-
-TEST(AwakableListTest, BasicAwakeSatisfied) {
-  MojoResult result;
-  uintptr_t context;
-
-  // Awake immediately after thread start.
-  {
-    AwakableList awakable_list;
-    test::SimpleWaiterThread thread(&result, &context);
-    awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1);
-    thread.Start();
-    awakable_list.AwakeForStateChange(HandleSignalsState(
-        MOJO_HANDLE_SIGNAL_READABLE,
-        MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE));
-    awakable_list.Remove(thread.waiter());
-  }  // Join |thread|.
-  EXPECT_EQ(MOJO_RESULT_OK, result);
-  EXPECT_EQ(1u, context);
-
-  // Awake before after thread start.
-  {
-    AwakableList awakable_list;
-    test::SimpleWaiterThread thread(&result, &context);
-    awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2);
-    awakable_list.AwakeForStateChange(HandleSignalsState(
-        MOJO_HANDLE_SIGNAL_WRITABLE,
-        MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE));
-    awakable_list.Remove(thread.waiter());
-    // Double-remove okay:
-    awakable_list.Remove(thread.waiter());
-    thread.Start();
-  }  // Join |thread|.
-  EXPECT_EQ(MOJO_RESULT_OK, result);
-  EXPECT_EQ(2u, context);
-
-  // Awake some time after thread start.
-  {
-    AwakableList awakable_list;
-    test::SimpleWaiterThread thread(&result, &context);
-    awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3);
-    thread.Start();
-    test::Sleep(2 * test::EpsilonDeadline());
-    awakable_list.AwakeForStateChange(HandleSignalsState(
-        MOJO_HANDLE_SIGNAL_READABLE,
-        MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE));
-    awakable_list.Remove(thread.waiter());
-  }  // Join |thread|.
-  EXPECT_EQ(MOJO_RESULT_OK, result);
-  EXPECT_EQ(3u, context);
-}
-
-TEST(AwakableListTest, BasicAwakeUnsatisfiable) {
-  MojoResult result;
-  uintptr_t context;
-
-  // Awake (for unsatisfiability) immediately after thread start.
-  {
-    AwakableList awakable_list;
-    test::SimpleWaiterThread thread(&result, &context);
-    awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1);
-    thread.Start();
-    awakable_list.AwakeForStateChange(HandleSignalsState(
-        MOJO_HANDLE_SIGNAL_NONE, MOJO_HANDLE_SIGNAL_WRITABLE));
-    awakable_list.Remove(thread.waiter());
-  }  // Join |thread|.
-  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
-  EXPECT_EQ(1u, context);
-
-  // Awake (for unsatisfiability) before after thread start.
-  {
-    AwakableList awakable_list;
-    test::SimpleWaiterThread thread(&result, &context);
-    awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2);
-    awakable_list.AwakeForStateChange(HandleSignalsState(
-        MOJO_HANDLE_SIGNAL_READABLE, MOJO_HANDLE_SIGNAL_READABLE));
-    awakable_list.Remove(thread.waiter());
-    thread.Start();
-  }  // Join |thread|.
-  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
-  EXPECT_EQ(2u, context);
-
-  // Awake (for unsatisfiability) some time after thread start.
-  {
-    AwakableList awakable_list;
-    test::SimpleWaiterThread thread(&result, &context);
-    awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3);
-    thread.Start();
-    test::Sleep(2 * test::EpsilonDeadline());
-    awakable_list.AwakeForStateChange(HandleSignalsState(
-        MOJO_HANDLE_SIGNAL_NONE, MOJO_HANDLE_SIGNAL_WRITABLE));
-    awakable_list.Remove(thread.waiter());
-    // Double-remove okay:
-    awakable_list.Remove(thread.waiter());
-  }  // Join |thread|.
-  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
-  EXPECT_EQ(3u, context);
-}
-
-TEST(AwakableListTest, MultipleAwakables) {
-  MojoResult result1;
-  MojoResult result2;
-  MojoResult result3;
-  MojoResult result4;
-  uintptr_t context1;
-  uintptr_t context2;
-  uintptr_t context3;
-  uintptr_t context4;
-
-  // Cancel two awakables.
-  {
-    AwakableList awakable_list;
-    test::SimpleWaiterThread thread1(&result1, &context1);
-    awakable_list.Add(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1);
-    thread1.Start();
-    test::SimpleWaiterThread thread2(&result2, &context2);
-    awakable_list.Add(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2);
-    thread2.Start();
-    test::Sleep(2 * test::EpsilonDeadline());
-    awakable_list.CancelAll();
-  }  // Join threads.
-  EXPECT_EQ(MOJO_RESULT_CANCELLED, result1);
-  EXPECT_EQ(1u, context1);
-  EXPECT_EQ(MOJO_RESULT_CANCELLED, result2);
-  EXPECT_EQ(2u, context2);
-
-  // Awake one awakable, cancel other.
-  {
-    AwakableList awakable_list;
-    test::SimpleWaiterThread thread1(&result1, &context1);
-    awakable_list.Add(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3);
-    thread1.Start();
-    test::SimpleWaiterThread thread2(&result2, &context2);
-    awakable_list.Add(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 4);
-    thread2.Start();
-    test::Sleep(2 * test::EpsilonDeadline());
-    awakable_list.AwakeForStateChange(HandleSignalsState(
-        MOJO_HANDLE_SIGNAL_READABLE,
-        MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE));
-    awakable_list.Remove(thread1.waiter());
-    awakable_list.CancelAll();
-  }  // Join threads.
-  EXPECT_EQ(MOJO_RESULT_OK, result1);
-  EXPECT_EQ(3u, context1);
-  EXPECT_EQ(MOJO_RESULT_CANCELLED, result2);
-  EXPECT_EQ(4u, context2);
-
-  // Cancel one awakable, awake other for unsatisfiability.
-  {
-    AwakableList awakable_list;
-    test::SimpleWaiterThread thread1(&result1, &context1);
-    awakable_list.Add(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 5);
-    thread1.Start();
-    test::SimpleWaiterThread thread2(&result2, &context2);
-    awakable_list.Add(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 6);
-    thread2.Start();
-    test::Sleep(2 * test::EpsilonDeadline());
-    awakable_list.AwakeForStateChange(HandleSignalsState(
-        MOJO_HANDLE_SIGNAL_NONE, MOJO_HANDLE_SIGNAL_READABLE));
-    awakable_list.Remove(thread2.waiter());
-    awakable_list.CancelAll();
-  }  // Join threads.
-  EXPECT_EQ(MOJO_RESULT_CANCELLED, result1);
-  EXPECT_EQ(5u, context1);
-  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result2);
-  EXPECT_EQ(6u, context2);
-
-  // Cancel one awakable, awake other for unsatisfiability.
-  {
-    AwakableList awakable_list;
-    test::SimpleWaiterThread thread1(&result1, &context1);
-    awakable_list.Add(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 7);
-    thread1.Start();
-
-    test::Sleep(1 * test::EpsilonDeadline());
-
-    // Should do nothing.
-    awakable_list.AwakeForStateChange(HandleSignalsState(
-        MOJO_HANDLE_SIGNAL_NONE,
-        MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE));
-
-    test::SimpleWaiterThread thread2(&result2, &context2);
-    awakable_list.Add(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 8);
-    thread2.Start();
-
-    test::Sleep(1 * test::EpsilonDeadline());
-
-    // Awake #1.
-    awakable_list.AwakeForStateChange(HandleSignalsState(
-        MOJO_HANDLE_SIGNAL_READABLE,
-        MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE));
-    awakable_list.Remove(thread1.waiter());
-
-    test::Sleep(1 * test::EpsilonDeadline());
-
-    test::SimpleWaiterThread thread3(&result3, &context3);
-    awakable_list.Add(thread3.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 9);
-    thread3.Start();
-
-    test::SimpleWaiterThread thread4(&result4, &context4);
-    awakable_list.Add(thread4.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 10);
-    thread4.Start();
-
-    test::Sleep(1 * test::EpsilonDeadline());
-
-    // Awake #2 and #3 for unsatisfiability.
-    awakable_list.AwakeForStateChange(HandleSignalsState(
-        MOJO_HANDLE_SIGNAL_NONE, MOJO_HANDLE_SIGNAL_READABLE));
-    awakable_list.Remove(thread2.waiter());
-    awakable_list.Remove(thread3.waiter());
-
-    // Cancel #4.
-    awakable_list.CancelAll();
-  }  // Join threads.
-  EXPECT_EQ(MOJO_RESULT_OK, result1);
-  EXPECT_EQ(7u, context1);
-  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result2);
-  EXPECT_EQ(8u, context2);
-  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result3);
-  EXPECT_EQ(9u, context3);
-  EXPECT_EQ(MOJO_RESULT_CANCELLED, result4);
-  EXPECT_EQ(10u, context4);
-}
-
-class KeepAwakable : public Awakable {
- public:
-  KeepAwakable() : awake_count(0) {}
-
-  bool Awake(MojoResult result, uintptr_t context) override {
-    awake_count++;
-    return true;
-  }
-
-  int awake_count;
-
-  DISALLOW_COPY_AND_ASSIGN(KeepAwakable);
-};
-
-class RemoveAwakable : public Awakable {
- public:
-  RemoveAwakable() : awake_count(0) {}
-
-  bool Awake(MojoResult result, uintptr_t context) override {
-    awake_count++;
-    return false;
-  }
-
-  int awake_count;
-
-  DISALLOW_COPY_AND_ASSIGN(RemoveAwakable);
-};
-
-TEST(AwakableListTest, KeepAwakablesReturningTrue) {
-  KeepAwakable keep0;
-  KeepAwakable keep1;
-  RemoveAwakable remove0;
-  RemoveAwakable remove1;
-  RemoveAwakable remove2;
-
-  HandleSignalsState hss(MOJO_HANDLE_SIGNAL_WRITABLE,
-                         MOJO_HANDLE_SIGNAL_WRITABLE);
-
-  AwakableList remove_all;
-  remove_all.Add(&remove0, MOJO_HANDLE_SIGNAL_WRITABLE, 0);
-  remove_all.Add(&remove1, MOJO_HANDLE_SIGNAL_WRITABLE, 0);
-
-  remove_all.AwakeForStateChange(hss);
-  EXPECT_EQ(remove0.awake_count, 1);
-  EXPECT_EQ(remove1.awake_count, 1);
-
-  remove_all.AwakeForStateChange(hss);
-  EXPECT_EQ(remove0.awake_count, 1);
-  EXPECT_EQ(remove1.awake_count, 1);
-
-  AwakableList remove_first;
-  remove_first.Add(&remove2, MOJO_HANDLE_SIGNAL_WRITABLE, 0);
-  remove_first.Add(&keep0, MOJO_HANDLE_SIGNAL_WRITABLE, 0);
-  remove_first.Add(&keep1, MOJO_HANDLE_SIGNAL_WRITABLE, 0);
-
-  remove_first.AwakeForStateChange(hss);
-  EXPECT_EQ(keep0.awake_count, 1);
-  EXPECT_EQ(keep1.awake_count, 1);
-  EXPECT_EQ(remove2.awake_count, 1);
-
-  remove_first.AwakeForStateChange(hss);
-  EXPECT_EQ(keep0.awake_count, 2);
-  EXPECT_EQ(keep1.awake_count, 2);
-  EXPECT_EQ(remove2.awake_count, 1);
-
-  remove_first.Remove(&keep0);
-  remove_first.Remove(&keep1);
-}
-
-}  // namespace
-}  // namespace edk
-}  // namespace mojo
diff --git a/mojo/edk/system/configuration.cc b/mojo/edk/system/configuration.cc
index 9aaed31..f5eb2b8 100644
--- a/mojo/edk/system/configuration.cc
+++ b/mojo/edk/system/configuration.cc
@@ -13,7 +13,6 @@
 Configuration g_configuration = {
     1000000,              // max_handle_table_size
     1000000,              // max_mapping_table_sze
-    1000000,              // max_wait_many_num_handles
     4 * 1024 * 1024,      // max_message_num_bytes
     10000,                // max_message_num_handles
     256 * 1024 * 1024,    // max_data_pipe_capacity_bytes
diff --git a/mojo/edk/system/core.cc b/mojo/edk/system/core.cc
index 1e0bf4e..360e8c3 100644
--- a/mojo/edk/system/core.cc
+++ b/mojo/edk/system/core.cc
@@ -33,8 +33,7 @@
 #include "mojo/edk/system/ports/node.h"
 #include "mojo/edk/system/request_context.h"
 #include "mojo/edk/system/shared_buffer_dispatcher.h"
-#include "mojo/edk/system/wait_set_dispatcher.h"
-#include "mojo/edk/system/waiter.h"
+#include "mojo/edk/system/watcher_dispatcher.h"
 
 namespace mojo {
 namespace edk {
@@ -48,15 +47,6 @@
 // pipes too; for now we just use a constant. This only affects bootstrap pipes.
 const uint64_t kUnknownPipeIdForDebug = 0x7f7f7f7f7f7f7f7fUL;
 
-void CallWatchCallback(MojoWatchCallback callback,
-                       uintptr_t context,
-                       MojoResult result,
-                       const HandleSignalsState& signals_state,
-                       MojoWatchNotificationFlags flags) {
-  callback(context, result, static_cast<MojoHandleSignalsState>(signals_state),
-      flags);
-}
-
 MojoResult MojoPlatformHandleToScopedPlatformHandle(
     const MojoPlatformHandle* platform_handle,
     ScopedPlatformHandle* out_handle) {
@@ -386,66 +376,61 @@
   return MOJO_RESULT_OK;
 }
 
-MojoResult Core::Wait(MojoHandle handle,
-                      MojoHandleSignals signals,
-                      MojoDeadline deadline,
-                      MojoHandleSignalsState* signals_state) {
+MojoResult Core::QueryHandleSignalsState(
+    MojoHandle handle,
+    MojoHandleSignalsState* signals_state) {
   RequestContext request_context;
-  uint32_t unused = static_cast<uint32_t>(-1);
-  HandleSignalsState hss;
-  MojoResult rv = WaitManyInternal(&handle, &signals, 1, deadline, &unused,
-                                   signals_state ? &hss : nullptr);
-  if (rv != MOJO_RESULT_INVALID_ARGUMENT && signals_state)
-    *signals_state = hss;
-  return rv;
-}
-
-MojoResult Core::WaitMany(const MojoHandle* handles,
-                          const MojoHandleSignals* signals,
-                          uint32_t num_handles,
-                          MojoDeadline deadline,
-                          uint32_t* result_index,
-                          MojoHandleSignalsState* signals_state) {
-  RequestContext request_context;
-  if (num_handles < 1)
+  scoped_refptr<Dispatcher> dispatcher = GetDispatcher(handle);
+  if (!dispatcher || !signals_state)
     return MOJO_RESULT_INVALID_ARGUMENT;
-  if (num_handles > GetConfiguration().max_wait_many_num_handles)
-    return MOJO_RESULT_RESOURCE_EXHAUSTED;
-
-  uint32_t index = static_cast<uint32_t>(-1);
-  MojoResult rv;
-  if (!signals_state) {
-    rv = WaitManyInternal(handles, signals, num_handles, deadline, &index,
-                          nullptr);
-  } else {
-    // Note: The |reinterpret_cast| is safe, since |HandleSignalsState| is a
-    // subclass of |MojoHandleSignalsState| that doesn't add any data members.
-    rv = WaitManyInternal(handles, signals, num_handles, deadline, &index,
-                          reinterpret_cast<HandleSignalsState*>(signals_state));
-  }
-  if (index != static_cast<uint32_t>(-1) && result_index)
-    *result_index = index;
-  return rv;
+  *signals_state = dispatcher->GetHandleSignalsState();
+  return MOJO_RESULT_OK;
 }
 
-MojoResult Core::Watch(MojoHandle handle,
+MojoResult Core::CreateWatcher(MojoWatcherCallback callback,
+                               MojoHandle* watcher_handle) {
+  RequestContext request_context;
+  if (!watcher_handle)
+    return MOJO_RESULT_INVALID_ARGUMENT;
+  *watcher_handle = AddDispatcher(new WatcherDispatcher(callback));
+  if (*watcher_handle == MOJO_HANDLE_INVALID)
+    return MOJO_RESULT_RESOURCE_EXHAUSTED;
+  return MOJO_RESULT_OK;
+}
+
+MojoResult Core::Watch(MojoHandle watcher_handle,
+                       MojoHandle handle,
                        MojoHandleSignals signals,
-                       MojoWatchCallback callback,
                        uintptr_t context) {
   RequestContext request_context;
+  scoped_refptr<Dispatcher> watcher = GetDispatcher(watcher_handle);
+  if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER)
+    return MOJO_RESULT_INVALID_ARGUMENT;
   scoped_refptr<Dispatcher> dispatcher = GetDispatcher(handle);
   if (!dispatcher)
     return MOJO_RESULT_INVALID_ARGUMENT;
-  return dispatcher->Watch(
-      signals, base::Bind(&CallWatchCallback, callback, context), context);
+  return watcher->WatchDispatcher(dispatcher, signals, context);
 }
 
-MojoResult Core::CancelWatch(MojoHandle handle, uintptr_t context) {
+MojoResult Core::CancelWatch(MojoHandle watcher_handle, uintptr_t context) {
   RequestContext request_context;
-  scoped_refptr<Dispatcher> dispatcher = GetDispatcher(handle);
-  if (!dispatcher)
+  scoped_refptr<Dispatcher> watcher = GetDispatcher(watcher_handle);
+  if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER)
     return MOJO_RESULT_INVALID_ARGUMENT;
-  return dispatcher->CancelWatch(context);
+  return watcher->CancelWatch(context);
+}
+
+MojoResult Core::ArmWatcher(MojoHandle watcher_handle,
+                            uint32_t* num_ready_contexts,
+                            uintptr_t* ready_contexts,
+                            MojoResult* ready_results,
+                            MojoHandleSignalsState* ready_signals_states) {
+  RequestContext request_context;
+  scoped_refptr<Dispatcher> watcher = GetDispatcher(watcher_handle);
+  if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER)
+    return MOJO_RESULT_INVALID_ARGUMENT;
+  return watcher->Arm(num_ready_contexts, ready_contexts, ready_results,
+                      ready_signals_states);
 }
 
 MojoResult Core::AllocMessage(uint32_t num_bytes,
@@ -529,83 +514,6 @@
   }
 }
 
-MojoResult Core::CreateWaitSet(MojoHandle* wait_set_handle) {
-  RequestContext request_context;
-  if (!wait_set_handle)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  scoped_refptr<WaitSetDispatcher> dispatcher = new WaitSetDispatcher();
-  MojoHandle h = AddDispatcher(dispatcher);
-  if (h == MOJO_HANDLE_INVALID) {
-    LOG(ERROR) << "Handle table full";
-    dispatcher->Close();
-    return MOJO_RESULT_RESOURCE_EXHAUSTED;
-  }
-
-  *wait_set_handle = h;
-  return MOJO_RESULT_OK;
-}
-
-MojoResult Core::AddHandle(MojoHandle wait_set_handle,
-                           MojoHandle handle,
-                           MojoHandleSignals signals) {
-  RequestContext request_context;
-  scoped_refptr<Dispatcher> wait_set_dispatcher(GetDispatcher(wait_set_handle));
-  if (!wait_set_dispatcher)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  scoped_refptr<Dispatcher> dispatcher(GetDispatcher(handle));
-  if (!dispatcher)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  return wait_set_dispatcher->AddWaitingDispatcher(dispatcher, signals, handle);
-}
-
-MojoResult Core::RemoveHandle(MojoHandle wait_set_handle,
-                              MojoHandle handle) {
-  RequestContext request_context;
-  scoped_refptr<Dispatcher> wait_set_dispatcher(GetDispatcher(wait_set_handle));
-  if (!wait_set_dispatcher)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  scoped_refptr<Dispatcher> dispatcher(GetDispatcher(handle));
-  if (!dispatcher)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  return wait_set_dispatcher->RemoveWaitingDispatcher(dispatcher);
-}
-
-MojoResult Core::GetReadyHandles(MojoHandle wait_set_handle,
-                                 uint32_t* count,
-                                 MojoHandle* handles,
-                                 MojoResult* results,
-                                 MojoHandleSignalsState* signals_states) {
-  RequestContext request_context;
-  if (!handles || !count || !(*count) || !results)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  scoped_refptr<Dispatcher> wait_set_dispatcher(GetDispatcher(wait_set_handle));
-  if (!wait_set_dispatcher)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  DispatcherVector awoken_dispatchers;
-  base::StackVector<uintptr_t, 16> contexts;
-  contexts->assign(*count, MOJO_HANDLE_INVALID);
-
-  MojoResult result = wait_set_dispatcher->GetReadyDispatchers(
-      count, &awoken_dispatchers, results, contexts->data());
-
-  if (result == MOJO_RESULT_OK) {
-    for (size_t i = 0; i < *count; i++) {
-      handles[i] = static_cast<MojoHandle>(contexts[i]);
-      if (signals_states)
-        signals_states[i] = awoken_dispatchers[i]->GetHandleSignalsState();
-    }
-  }
-
-  return result;
-}
-
 MojoResult Core::CreateMessagePipe(
     const MojoCreateMessagePipeOptions* options,
     MojoHandle* message_pipe_handle0,
@@ -1097,74 +1005,6 @@
   handles_.GetActiveHandlesForTest(handles);
 }
 
-MojoResult Core::WaitManyInternal(const MojoHandle* handles,
-                                  const MojoHandleSignals* signals,
-                                  uint32_t num_handles,
-                                  MojoDeadline deadline,
-                                  uint32_t* result_index,
-                                  HandleSignalsState* signals_states) {
-  CHECK(handles);
-  CHECK(signals);
-  DCHECK_GT(num_handles, 0u);
-  if (result_index) {
-    DCHECK_EQ(*result_index, static_cast<uint32_t>(-1));
-  }
-
-  // The primary caller of |WaitManyInternal()| is |Wait()|, which only waits on
-  // a single handle. In the common case of a single handle, this avoid a heap
-  // allocation.
-  base::StackVector<scoped_refptr<Dispatcher>, 1> dispatchers;
-  dispatchers->reserve(num_handles);
-  for (uint32_t i = 0; i < num_handles; i++) {
-    scoped_refptr<Dispatcher> dispatcher = GetDispatcher(handles[i]);
-    if (!dispatcher) {
-      if (result_index)
-        *result_index = i;
-      return MOJO_RESULT_INVALID_ARGUMENT;
-    }
-    dispatchers->push_back(dispatcher);
-  }
-
-  // TODO(vtl): Should make the waiter live (permanently) in TLS.
-  Waiter waiter;
-  waiter.Init();
-
-  uint32_t i;
-  MojoResult rv = MOJO_RESULT_OK;
-  for (i = 0; i < num_handles; i++) {
-    rv = dispatchers[i]->AddAwakable(
-        &waiter, signals[i], i, signals_states ? &signals_states[i] : nullptr);
-    if (rv != MOJO_RESULT_OK) {
-      if (result_index)
-        *result_index = i;
-      break;
-    }
-  }
-  uint32_t num_added = i;
-
-  if (rv == MOJO_RESULT_ALREADY_EXISTS) {
-    rv = MOJO_RESULT_OK;  // The i-th one is already "triggered".
-  } else if (rv == MOJO_RESULT_OK) {
-    uintptr_t uintptr_result = *result_index;
-    rv = waiter.Wait(deadline, &uintptr_result);
-    *result_index = static_cast<uint32_t>(uintptr_result);
-  }
-
-  // Make sure no other dispatchers try to wake |waiter| for the current
-  // |Wait()|/|WaitMany()| call. (Only after doing this can |waiter| be
-  // destroyed, but this would still be required if the waiter were in TLS.)
-  for (i = 0; i < num_added; i++) {
-    dispatchers[i]->RemoveAwakable(
-        &waiter, signals_states ? &signals_states[i] : nullptr);
-  }
-  if (signals_states) {
-    for (; i < num_handles; i++)
-      signals_states[i] = dispatchers[i]->GetHandleSignalsState();
-  }
-
-  return rv;
-}
-
 // static
 void Core::PassNodeControllerToIOThread(
     std::unique_ptr<NodeController> node_controller) {
diff --git a/mojo/edk/system/core.h b/mojo/edk/system/core.h
index 1e20a87..1f6d865 100644
--- a/mojo/edk/system/core.h
+++ b/mojo/edk/system/core.h
@@ -27,6 +27,7 @@
 #include "mojo/public/c/system/message_pipe.h"
 #include "mojo/public/c/system/platform_handle.h"
 #include "mojo/public/c/system/types.h"
+#include "mojo/public/c/system/watcher.h"
 #include "mojo/public/cpp/system/message_pipe.h"
 
 namespace base {
@@ -135,21 +136,20 @@
   // "mojo/public/c/system/functions.h":
   MojoTimeTicks GetTimeTicksNow();
   MojoResult Close(MojoHandle handle);
-  MojoResult Wait(MojoHandle handle,
-                  MojoHandleSignals signals,
-                  MojoDeadline deadline,
-                  MojoHandleSignalsState* signals_state);
-  MojoResult WaitMany(const MojoHandle* handles,
-                      const MojoHandleSignals* signals,
-                      uint32_t num_handles,
-                      MojoDeadline deadline,
-                      uint32_t* result_index,
-                      MojoHandleSignalsState* signals_states);
-  MojoResult Watch(MojoHandle handle,
+  MojoResult QueryHandleSignalsState(MojoHandle handle,
+                                     MojoHandleSignalsState* signals_state);
+  MojoResult CreateWatcher(MojoWatcherCallback callback,
+                           MojoHandle* watcher_handle);
+  MojoResult Watch(MojoHandle watcher_handle,
+                   MojoHandle handle,
                    MojoHandleSignals signals,
-                   MojoWatchCallback callback,
                    uintptr_t context);
-  MojoResult CancelWatch(MojoHandle handle, uintptr_t context);
+  MojoResult CancelWatch(MojoHandle watcher_handle, uintptr_t context);
+  MojoResult ArmWatcher(MojoHandle watcher_handle,
+                        uint32_t* num_ready_contexts,
+                        uintptr_t* ready_contexts,
+                        MojoResult* ready_results,
+                        MojoHandleSignalsState* ready_signals_states);
   MojoResult AllocMessage(uint32_t num_bytes,
                           const MojoHandle* handles,
                           uint32_t num_handles,
@@ -160,20 +160,6 @@
   MojoResult GetProperty(MojoPropertyType type, void* value);
 
   // These methods correspond to the API functions defined in
-  // "mojo/public/c/system/wait_set.h":
-  MojoResult CreateWaitSet(MojoHandle* wait_set_handle);
-  MojoResult AddHandle(MojoHandle wait_set_handle,
-                       MojoHandle handle,
-                       MojoHandleSignals signals);
-  MojoResult RemoveHandle(MojoHandle wait_set_handle,
-                          MojoHandle handle);
-  MojoResult GetReadyHandles(MojoHandle wait_set_handle,
-                             uint32_t* count,
-                             MojoHandle* handles,
-                             MojoResult* results,
-                             MojoHandleSignalsState* signals_states);
-
-  // These methods correspond to the API functions defined in
   // "mojo/public/c/system/message_pipe.h":
   MojoResult CreateMessagePipe(
       const MojoCreateMessagePipeOptions* options,
@@ -269,13 +255,6 @@
   void GetActiveHandlesForTest(std::vector<MojoHandle>* handles);
 
  private:
-  MojoResult WaitManyInternal(const MojoHandle* handles,
-                              const MojoHandleSignals* signals,
-                              uint32_t num_handles,
-                              MojoDeadline deadline,
-                              uint32_t* result_index,
-                              HandleSignalsState* signals_states);
-
   // Used to pass ownership of our NodeController over to the IO thread in the
   // event that we're torn down before said thread.
   static void PassNodeControllerToIOThread(
diff --git a/mojo/edk/system/core_test_base.cc b/mojo/edk/system/core_test_base.cc
index e98a55d..7751612 100644
--- a/mojo/edk/system/core_test_base.cc
+++ b/mojo/edk/system/core_test_base.cc
@@ -107,28 +107,6 @@
     return MOJO_RESULT_UNIMPLEMENTED;
   }
 
-  MojoResult AddAwakable(Awakable* awakable,
-                         MojoHandleSignals /*signals*/,
-                         uintptr_t /*context*/,
-                         HandleSignalsState* signals_state) override {
-    info_->IncrementAddAwakableCallCount();
-    if (signals_state)
-      *signals_state = HandleSignalsState();
-    if (info_->IsAddAwakableAllowed()) {
-      info_->AwakableWasAdded(awakable);
-      return MOJO_RESULT_OK;
-    }
-
-    return MOJO_RESULT_FAILED_PRECONDITION;
-  }
-
-  void RemoveAwakable(Awakable* /*awakable*/,
-                      HandleSignalsState* signals_state) override {
-    info_->IncrementRemoveAwakableCallCount();
-    if (signals_state)
-      *signals_state = HandleSignalsState();
-  }
-
  private:
   explicit MockDispatcher(CoreTestBase::MockHandleInfo* info) : info_(info) {
     CHECK(info_);
@@ -174,11 +152,7 @@
       end_write_data_call_count_(0),
       read_data_call_count_(0),
       begin_read_data_call_count_(0),
-      end_read_data_call_count_(0),
-      add_awakable_call_count_(0),
-      remove_awakable_call_count_(0),
-      add_awakable_allowed_(false) {
-}
+      end_read_data_call_count_(0) {}
 
 CoreTestBase_MockHandleInfo::~CoreTestBase_MockHandleInfo() {
 }
@@ -238,26 +212,6 @@
   return end_read_data_call_count_;
 }
 
-unsigned CoreTestBase_MockHandleInfo::GetAddAwakableCallCount() const {
-  base::AutoLock locker(lock_);
-  return add_awakable_call_count_;
-}
-
-unsigned CoreTestBase_MockHandleInfo::GetRemoveAwakableCallCount() const {
-  base::AutoLock locker(lock_);
-  return remove_awakable_call_count_;
-}
-
-size_t CoreTestBase_MockHandleInfo::GetAddedAwakableSize() const {
-  base::AutoLock locker(lock_);
-  return added_awakables_.size();
-}
-
-Awakable* CoreTestBase_MockHandleInfo::GetAddedAwakableAt(unsigned i) const {
-  base::AutoLock locker(lock_);
-  return added_awakables_[i];
-}
-
 void CoreTestBase_MockHandleInfo::IncrementCtorCallCount() {
   base::AutoLock locker(lock_);
   ctor_call_count_++;
@@ -313,31 +267,6 @@
   end_read_data_call_count_++;
 }
 
-void CoreTestBase_MockHandleInfo::IncrementAddAwakableCallCount() {
-  base::AutoLock locker(lock_);
-  add_awakable_call_count_++;
-}
-
-void CoreTestBase_MockHandleInfo::IncrementRemoveAwakableCallCount() {
-  base::AutoLock locker(lock_);
-  remove_awakable_call_count_++;
-}
-
-void CoreTestBase_MockHandleInfo::AllowAddAwakable(bool alllow) {
-  base::AutoLock locker(lock_);
-  add_awakable_allowed_ = alllow;
-}
-
-bool CoreTestBase_MockHandleInfo::IsAddAwakableAllowed() const {
-  base::AutoLock locker(lock_);
-  return add_awakable_allowed_;
-}
-
-void CoreTestBase_MockHandleInfo::AwakableWasAdded(Awakable* awakable) {
-  base::AutoLock locker(lock_);
-  added_awakables_.push_back(awakable);
-}
-
 }  // namespace test
 }  // namespace edk
 }  // namespace mojo
diff --git a/mojo/edk/system/core_test_base.h b/mojo/edk/system/core_test_base.h
index 3d2346a..3d156e3 100644
--- a/mojo/edk/system/core_test_base.h
+++ b/mojo/edk/system/core_test_base.h
@@ -18,7 +18,6 @@
 namespace edk {
 
 class Core;
-class Awakable;
 
 namespace test {
 
@@ -57,11 +56,6 @@
   unsigned GetReadDataCallCount() const;
   unsigned GetBeginReadDataCallCount() const;
   unsigned GetEndReadDataCallCount() const;
-  unsigned GetAddAwakableCallCount() const;
-  unsigned GetRemoveAwakableCallCount() const;
-
-  size_t GetAddedAwakableSize() const;
-  Awakable* GetAddedAwakableAt(unsigned i) const;
 
   // For use by |MockDispatcher|:
   void IncrementCtorCallCount();
@@ -75,12 +69,6 @@
   void IncrementReadDataCallCount();
   void IncrementBeginReadDataCallCount();
   void IncrementEndReadDataCallCount();
-  void IncrementAddAwakableCallCount();
-  void IncrementRemoveAwakableCallCount();
-
-  void AllowAddAwakable(bool alllow);
-  bool IsAddAwakableAllowed() const;
-  void AwakableWasAdded(Awakable*);
 
  private:
   mutable base::Lock lock_;  // Protects the following members.
@@ -95,11 +83,6 @@
   unsigned read_data_call_count_;
   unsigned begin_read_data_call_count_;
   unsigned end_read_data_call_count_;
-  unsigned add_awakable_call_count_;
-  unsigned remove_awakable_call_count_;
-
-  bool add_awakable_allowed_;
-  std::vector<Awakable*> added_awakables_;
 
   DISALLOW_COPY_AND_ASSIGN(CoreTestBase_MockHandleInfo);
 };
diff --git a/mojo/edk/system/core_unittest.cc b/mojo/edk/system/core_unittest.cc
index 814ce4b..0d60b48 100644
--- a/mojo/edk/system/core_unittest.cc
+++ b/mojo/edk/system/core_unittest.cc
@@ -10,9 +10,9 @@
 
 #include "base/bind.h"
 #include "mojo/edk/embedder/embedder_internal.h"
-#include "mojo/edk/system/awakable.h"
 #include "mojo/edk/system/core_test_base.h"
 #include "mojo/edk/system/test_utils.h"
+#include "mojo/public/cpp/system/wait.h"
 
 #if defined(OS_WIN)
 #include "base/win/windows_version.h"
@@ -97,72 +97,11 @@
   ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, core()->EndReadData(h, 0));
   ASSERT_EQ(1u, info.GetEndReadDataCallCount());
 
-  ASSERT_EQ(0u, info.GetAddAwakableCallCount());
-  ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, MOJO_DEADLINE_INDEFINITE,
-                         nullptr));
-  ASSERT_EQ(1u, info.GetAddAwakableCallCount());
-  ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, 0, nullptr));
-  ASSERT_EQ(2u, info.GetAddAwakableCallCount());
-  MojoHandleSignalsState hss = kFullMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, MOJO_DEADLINE_INDEFINITE,
-                         &hss));
-  ASSERT_EQ(3u, info.GetAddAwakableCallCount());
-  ASSERT_EQ(0u, hss.satisfied_signals);
-  ASSERT_EQ(0u, hss.satisfiable_signals);
-  ASSERT_EQ(
-      MOJO_RESULT_FAILED_PRECONDITION,
-      core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, 10 * 1000, nullptr));
-  ASSERT_EQ(4u, info.GetAddAwakableCallCount());
-  hss = kFullMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, 10 * 1000, &hss));
-  ASSERT_EQ(5u, info.GetAddAwakableCallCount());
-  ASSERT_EQ(0u, hss.satisfied_signals);
-  ASSERT_EQ(0u, hss.satisfiable_signals);
-
-  MojoHandleSignals handle_signals = ~MOJO_HANDLE_SIGNAL_NONE;
-  ASSERT_EQ(
-      MOJO_RESULT_FAILED_PRECONDITION,
-      core()->WaitMany(&h, &handle_signals, 1, MOJO_DEADLINE_INDEFINITE,
-                       nullptr, nullptr));
-  ASSERT_EQ(6u, info.GetAddAwakableCallCount());
-  uint32_t result_index = static_cast<uint32_t>(-1);
-  ASSERT_EQ(
-      MOJO_RESULT_FAILED_PRECONDITION,
-      core()->WaitMany(&h, &handle_signals, 1, MOJO_DEADLINE_INDEFINITE,
-                       &result_index, nullptr));
-  ASSERT_EQ(7u, info.GetAddAwakableCallCount());
-  ASSERT_EQ(0u, result_index);
-  hss = kFullMojoHandleSignalsState;
-  ASSERT_EQ(
-      MOJO_RESULT_FAILED_PRECONDITION,
-      core()->WaitMany(&h, &handle_signals, 1, MOJO_DEADLINE_INDEFINITE,
-                       nullptr, &hss));
-  ASSERT_EQ(8u, info.GetAddAwakableCallCount());
-  ASSERT_EQ(0u, hss.satisfied_signals);
-  ASSERT_EQ(0u, hss.satisfiable_signals);
-  result_index = static_cast<uint32_t>(-1);
-  hss = kFullMojoHandleSignalsState;
-  ASSERT_EQ(
-      MOJO_RESULT_FAILED_PRECONDITION,
-      core()->WaitMany(&h, &handle_signals, 1, MOJO_DEADLINE_INDEFINITE,
-                       &result_index, &hss));
-  ASSERT_EQ(9u, info.GetAddAwakableCallCount());
-  ASSERT_EQ(0u, result_index);
-  ASSERT_EQ(0u, hss.satisfied_signals);
-  ASSERT_EQ(0u, hss.satisfiable_signals);
-
   ASSERT_EQ(0u, info.GetDtorCallCount());
   ASSERT_EQ(0u, info.GetCloseCallCount());
   ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h));
   ASSERT_EQ(1u, info.GetCloseCallCount());
   ASSERT_EQ(1u, info.GetDtorCallCount());
-
-  // No awakables should ever have ever been added.
-  ASSERT_EQ(0u, info.GetRemoveAwakableCallCount());
 }
 
 TEST_F(CoreTest, InvalidArguments) {
@@ -181,125 +120,6 @@
     ASSERT_EQ(1u, info.GetCloseCallCount());
   }
 
-  // |Wait()|:
-  {
-    ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-              core()->Wait(MOJO_HANDLE_INVALID, ~MOJO_HANDLE_SIGNAL_NONE,
-                           MOJO_DEADLINE_INDEFINITE, nullptr));
-    ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-              core()->Wait(10, ~MOJO_HANDLE_SIGNAL_NONE,
-                           MOJO_DEADLINE_INDEFINITE, nullptr));
-
-    MojoHandleSignalsState hss = kFullMojoHandleSignalsState;
-    ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-              core()->Wait(MOJO_HANDLE_INVALID, ~MOJO_HANDLE_SIGNAL_NONE,
-                           MOJO_DEADLINE_INDEFINITE, &hss));
-    // On invalid argument, it shouldn't modify the handle signals state.
-    ASSERT_EQ(kFullMojoHandleSignalsState.satisfied_signals,
-              hss.satisfied_signals);
-    ASSERT_EQ(kFullMojoHandleSignalsState.satisfiable_signals,
-              hss.satisfiable_signals);
-    hss = kFullMojoHandleSignalsState;
-    ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-              core()->Wait(10, ~MOJO_HANDLE_SIGNAL_NONE,
-                           MOJO_DEADLINE_INDEFINITE, &hss));
-    // On invalid argument, it shouldn't modify the handle signals state.
-    ASSERT_EQ(kFullMojoHandleSignalsState.satisfied_signals,
-              hss.satisfied_signals);
-    ASSERT_EQ(kFullMojoHandleSignalsState.satisfiable_signals,
-              hss.satisfiable_signals);
-  }
-
-  // |WaitMany()|:
-  {
-    MojoHandle handles[2] = {MOJO_HANDLE_INVALID, MOJO_HANDLE_INVALID};
-    MojoHandleSignals signals[2] = {~MOJO_HANDLE_SIGNAL_NONE,
-                                    ~MOJO_HANDLE_SIGNAL_NONE};
-    ASSERT_EQ(
-        MOJO_RESULT_INVALID_ARGUMENT,
-        core()->WaitMany(handles, signals, 0, MOJO_DEADLINE_INDEFINITE,
-                         nullptr, nullptr));
-    ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-              core()->WaitMany(nullptr, signals, 0, MOJO_DEADLINE_INDEFINITE,
-                               nullptr, nullptr));
-    // If |num_handles| is invalid, it should leave |result_index| and
-    // |signals_states| alone.
-    // (We use -1 internally; make sure that doesn't leak.)
-    uint32_t result_index = 123;
-    MojoHandleSignalsState hss = kFullMojoHandleSignalsState;
-    ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-              core()->WaitMany(nullptr, signals, 0, MOJO_DEADLINE_INDEFINITE,
-                               &result_index, &hss));
-    ASSERT_EQ(123u, result_index);
-    ASSERT_EQ(kFullMojoHandleSignalsState.satisfied_signals,
-              hss.satisfied_signals);
-    ASSERT_EQ(kFullMojoHandleSignalsState.satisfiable_signals,
-              hss.satisfiable_signals);
-
-    ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-              core()->WaitMany(handles, nullptr, 0, MOJO_DEADLINE_INDEFINITE,
-                               nullptr, nullptr));
-    ASSERT_EQ(
-        MOJO_RESULT_INVALID_ARGUMENT,
-        core()->WaitMany(handles, signals, 1, MOJO_DEADLINE_INDEFINITE, nullptr,
-                         nullptr));
-    // But if a handle is bad, then it should set |result_index| but still leave
-    // |signals_states| alone.
-    result_index = static_cast<uint32_t>(-1);
-    hss = kFullMojoHandleSignalsState;
-    ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-              core()->WaitMany(
-                  handles, signals, 1, MOJO_DEADLINE_INDEFINITE, &result_index,
-                  &hss));
-    ASSERT_EQ(0u, result_index);
-    ASSERT_EQ(kFullMojoHandleSignalsState.satisfied_signals,
-              hss.satisfied_signals);
-    ASSERT_EQ(kFullMojoHandleSignalsState.satisfiable_signals,
-              hss.satisfiable_signals);
-
-    MockHandleInfo info[2];
-    handles[0] = CreateMockHandle(&info[0]);
-
-    result_index = static_cast<uint32_t>(-1);
-    hss = kFullMojoHandleSignalsState;
-    ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-              core()->WaitMany(
-                  handles, signals, 1, MOJO_DEADLINE_INDEFINITE, &result_index,
-                  &hss));
-    ASSERT_EQ(0u, result_index);
-    ASSERT_EQ(0u, hss.satisfied_signals);
-    ASSERT_EQ(0u, hss.satisfiable_signals);
-
-    // On invalid argument, it'll leave |signals_states| alone.
-    result_index = static_cast<uint32_t>(-1);
-    hss = kFullMojoHandleSignalsState;
-    ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-              core()->WaitMany(
-                  handles, signals, 2, MOJO_DEADLINE_INDEFINITE, &result_index,
-                  &hss));
-    ASSERT_EQ(1u, result_index);
-    ASSERT_EQ(kFullMojoHandleSignalsState.satisfied_signals,
-              hss.satisfied_signals);
-    ASSERT_EQ(kFullMojoHandleSignalsState.satisfiable_signals,
-              hss.satisfiable_signals);
-    handles[1] = handles[0] + 1;  // Invalid handle.
-    ASSERT_EQ(
-        MOJO_RESULT_INVALID_ARGUMENT,
-        core()->WaitMany(handles, signals, 2, MOJO_DEADLINE_INDEFINITE, nullptr,
-                         nullptr));
-    handles[1] = CreateMockHandle(&info[1]);
-    ASSERT_EQ(
-        MOJO_RESULT_FAILED_PRECONDITION,
-        core()->WaitMany(handles, signals, 2, MOJO_DEADLINE_INDEFINITE, nullptr,
-                         nullptr));
-
-    // TODO(vtl): Test one where we get "failed precondition" only for the
-    // second handle (and the first one is valid to wait on).
-
-    ASSERT_EQ(MOJO_RESULT_OK, core()->Close(handles[0]));
-    ASSERT_EQ(MOJO_RESULT_OK, core()->Close(handles[1]));
-  }
-
   // |CreateMessagePipe()|: Nothing to check (apart from things that cause
   // death).
 
@@ -451,22 +271,6 @@
   const char kMemoryCheckFailedRegex[] = "Check failed";
 #endif
 
-  // |WaitMany()|:
-  {
-    MojoHandle handle = MOJO_HANDLE_INVALID;
-    MojoHandleSignals signals = ~MOJO_HANDLE_SIGNAL_NONE;
-    ASSERT_DEATH_IF_SUPPORTED(
-        core()->WaitMany(nullptr, &signals, 1, MOJO_DEADLINE_INDEFINITE,
-                         nullptr, nullptr),
-        kMemoryCheckFailedRegex);
-    ASSERT_DEATH_IF_SUPPORTED(
-        core()->WaitMany(&handle, nullptr, 1, MOJO_DEADLINE_INDEFINITE, nullptr,
-                         nullptr),
-        kMemoryCheckFailedRegex);
-    // TODO(vtl): |result_index| and |signals_states| are optional. Test them
-    // with non-null invalid pointers?
-  }
-
   // |CreateMessagePipe()|:
   {
     MojoHandle h;
@@ -498,14 +302,9 @@
   }
 }
 
-// TODO(vtl): test |Wait()| and |WaitMany()| properly
-//  - including |WaitMany()| with the same handle more than once (with
-//    same/different signals)
-
 TEST_F(CoreTest, MessagePipe) {
   MojoHandle h[2];
   MojoHandleSignalsState hss[2];
-  uint32_t result_index;
 
   ASSERT_EQ(MOJO_RESULT_OK, core()->CreateMessagePipe(nullptr, &h[0], &h[1]));
   // Should get two distinct, valid handles.
@@ -514,15 +313,10 @@
   ASSERT_NE(h[0], h[1]);
 
   // Neither should be readable.
-  MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE,
-                                  MOJO_HANDLE_SIGNAL_READABLE};
-  result_index = static_cast<uint32_t>(-1);
   hss[0] = kEmptyMojoHandleSignalsState;
   hss[1] = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(
-      MOJO_RESULT_DEADLINE_EXCEEDED,
-      core()->WaitMany(h, signals, 2, 0, &result_index, hss));
-  ASSERT_EQ(static_cast<uint32_t>(-1), result_index);
+  EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[0], &hss[0]));
+  EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[1], &hss[1]));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals);
   ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[1].satisfied_signals);
@@ -539,34 +333,6 @@
   ASSERT_EQ('a', buffer[0]);
   ASSERT_EQ(1u, buffer_size);
 
-  // Both should be writable.
-  hss[0] = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_OK, core()->Wait(h[0], MOJO_HANDLE_SIGNAL_WRITABLE,
-                                         1000000000, &hss[0]));
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals);
-  ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals);
-  hss[0] = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_OK, core()->Wait(h[1], MOJO_HANDLE_SIGNAL_WRITABLE,
-                                         1000000000, &hss[0]));
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals);
-  ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals);
-
-  // Also check that |h[1]| is writable using |WaitMany()|.
-  signals[0] = MOJO_HANDLE_SIGNAL_READABLE;
-  signals[1] = MOJO_HANDLE_SIGNAL_WRITABLE;
-  result_index = static_cast<uint32_t>(-1);
-  hss[0] = kEmptyMojoHandleSignalsState;
-  hss[1] = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(
-      MOJO_RESULT_OK,
-      core()->WaitMany(h, signals, 2, MOJO_DEADLINE_INDEFINITE, &result_index,
-                       hss));
-  ASSERT_EQ(1u, result_index);
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals);
-  ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals);
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[1].satisfied_signals);
-  ASSERT_EQ(kAllSignals, hss[1].satisfiable_signals);
-
   // Write to |h[1]|.
   buffer[0] = 'b';
   ASSERT_EQ(
@@ -574,22 +340,9 @@
       core()->WriteMessage(h[1], buffer, 1, nullptr, 0,
                            MOJO_WRITE_MESSAGE_FLAG_NONE));
 
-  // Check that |h[0]| is now readable.
-  signals[0] = MOJO_HANDLE_SIGNAL_READABLE;
-  signals[1] = MOJO_HANDLE_SIGNAL_READABLE;
-  result_index = static_cast<uint32_t>(-1);
-  hss[0] = kEmptyMojoHandleSignalsState;
-  hss[1] = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(
-      MOJO_RESULT_OK,
-      core()->WaitMany(h, signals, 2, MOJO_DEADLINE_INDEFINITE, &result_index,
-                       hss));
-  ASSERT_EQ(0u, result_index);
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
-            hss[0].satisfied_signals);
-  ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals);
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[1].satisfied_signals);
-  ASSERT_EQ(kAllSignals, hss[1].satisfiable_signals);
+  // Wait for |h[0]| to become readable.
+  EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h[0]),
+                                       MOJO_HANDLE_SIGNAL_READABLE, &hss[0]));
 
   // Read from |h[0]|.
   // First, get only the size.
@@ -611,8 +364,7 @@
 
   // |h[0]| should no longer be readable.
   hss[0] = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
-            core()->Wait(h[0], MOJO_HANDLE_SIGNAL_READABLE, 0, &hss[0]));
+  EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[0], &hss[0]));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals);
   ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals);
 
@@ -627,28 +379,21 @@
   ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h[0]));
 
   // Wait for |h[1]| to learn about the other end's closure.
-  ASSERT_EQ(MOJO_RESULT_OK,
-            core()->Wait(h[1], MOJO_HANDLE_SIGNAL_PEER_CLOSED, 1000000000,
-                         &hss[0]));
+  EXPECT_EQ(
+      MOJO_RESULT_OK,
+      mojo::Wait(mojo::Handle(h[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss[1]));
 
   // Check that |h[1]| is no longer writable (and will never be).
-  hss[0] = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            core()->Wait(h[1], MOJO_HANDLE_SIGNAL_WRITABLE, 1000000000,
-                         &hss[0]));
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-            hss[0].satisfied_signals);
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-            hss[0].satisfiable_signals);
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+            hss[1].satisfied_signals);
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+            hss[1].satisfiable_signals);
 
   // Check that |h[1]| is still readable (for the moment).
-  hss[0] = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_OK, core()->Wait(h[1], MOJO_HANDLE_SIGNAL_READABLE,
-                                         1000000000, &hss[0]));
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-            hss[0].satisfied_signals);
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-            hss[0].satisfiable_signals);
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+            hss[1].satisfied_signals);
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+            hss[1].satisfiable_signals);
 
   // Discard a message from |h[1]|.
   ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
@@ -656,12 +401,10 @@
                                 MOJO_READ_MESSAGE_FLAG_MAY_DISCARD));
 
   // |h[1]| is no longer readable (and will never be).
-  hss[0] = kFullMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            core()->Wait(h[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
-                         &hss[0]));
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss[0].satisfied_signals);
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss[0].satisfiable_signals);
+  hss[1] = kFullMojoHandleSignalsState;
+  EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[1], &hss[1]));
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss[1].satisfied_signals);
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss[1].satisfiable_signals);
 
   // Try writing to |h[1]|.
   buffer[0] = 'e';
@@ -696,9 +439,8 @@
             core()->WriteMessage(h_passing[0], kHello, kHelloSize, nullptr, 0,
                                  MOJO_WRITE_MESSAGE_FLAG_NONE));
   hss = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_OK,
-            core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
-                         &hss));
+  EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]),
+                                       MOJO_HANDLE_SIGNAL_READABLE, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
             hss.satisfied_signals);
   ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
@@ -737,9 +479,8 @@
             core()->WriteMessage(h_passed[0], kHello, kHelloSize, nullptr, 0,
                                  MOJO_WRITE_MESSAGE_FLAG_NONE));
   hss = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_OK,
-            core()->Wait(h_passed[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
-                         &hss));
+  ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passed[1]),
+                                       MOJO_HANDLE_SIGNAL_READABLE, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
             hss.satisfied_signals);
   ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
@@ -759,9 +500,8 @@
                                  &h_passed[1], 1,
                                  MOJO_WRITE_MESSAGE_FLAG_NONE));
   hss = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_OK,
-            core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
-                         &hss));
+  ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]),
+                                       MOJO_HANDLE_SIGNAL_READABLE, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
             hss.satisfied_signals);
   ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
@@ -792,9 +532,8 @@
             core()->WriteMessage(h_passed[0], kHello, kHelloSize, nullptr, 0,
                                  MOJO_WRITE_MESSAGE_FLAG_NONE));
   hss = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_OK,
-            core()->Wait(h_received, MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
-                         &hss));
+  ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_received),
+                                       MOJO_HANDLE_SIGNAL_READABLE, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
             hss.satisfied_signals);
   ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
@@ -827,33 +566,15 @@
 
   // Producer should be never-readable, but already writable.
   hss = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(
-      MOJO_RESULT_FAILED_PRECONDITION,
-      core()->Wait(ph, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-            hss.satisfiable_signals);
-  hss = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_OK, core()->Wait(ph, MOJO_HANDLE_SIGNAL_WRITABLE, 0,
-                                         &hss));
+  EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ph, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
             hss.satisfiable_signals);
 
   // Consumer should be never-writable, and not yet readable.
   hss = kFullMojoHandleSignalsState;
-  ASSERT_EQ(
-      MOJO_RESULT_FAILED_PRECONDITION,
-      core()->Wait(ch, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
-  ASSERT_EQ(0u, hss.satisfied_signals);
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
-                MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
-            hss.satisfiable_signals);
-  hss = kFullMojoHandleSignalsState;
-  ASSERT_EQ(
-      MOJO_RESULT_DEADLINE_EXCEEDED,
-      core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
-  ASSERT_EQ(0u, hss.satisfied_signals);
+  EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss));
+  EXPECT_EQ(0u, hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
                 MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfiable_signals);
@@ -867,13 +588,12 @@
   ASSERT_EQ(2u, num_bytes);
 
   // Wait for the data to arrive to the consumer.
-  ASSERT_EQ(MOJO_RESULT_OK,
-            core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 1000000000, &hss));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss));
 
   // Consumer should now be readable.
   hss = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_OK, core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0,
-                                         &hss));
+  EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -924,7 +644,7 @@
 
   // Wait for the data to arrive to the consumer.
   ASSERT_EQ(MOJO_RESULT_OK,
-            core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 1000000000, &hss));
+            mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss));
 
   // Query how much data we have.
   num_bytes = 0;
@@ -964,7 +684,7 @@
 
   // Ensure the 3 bytes were read.
   ASSERT_EQ(MOJO_RESULT_OK,
-            core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 1000000000, &hss));
+            mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss));
 
   // Try a two-phase read of the remaining three bytes with peek. Should fail.
   const void* read_ptr = nullptr;
@@ -995,9 +715,7 @@
 
   // Consumer should now be no longer readable.
   hss = kFullMojoHandleSignalsState;
-  ASSERT_EQ(
-      MOJO_RESULT_DEADLINE_EXCEEDED,
-      core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+  EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss));
   EXPECT_EQ(0u, hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
                 MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
@@ -1009,14 +727,12 @@
   ASSERT_EQ(MOJO_RESULT_OK, core()->Close(ph));
 
   // Wait for this to get to the consumer.
-  ASSERT_EQ(MOJO_RESULT_OK,
-            core()->Wait(ch, MOJO_HANDLE_SIGNAL_PEER_CLOSED, 1000000000, &hss));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss));
 
   // The consumer should now be never-readable.
   hss = kFullMojoHandleSignalsState;
-  ASSERT_EQ(
-      MOJO_RESULT_FAILED_PRECONDITION,
-      core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+  EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
 
@@ -1049,9 +765,8 @@
             core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ch, 1,
                                  MOJO_WRITE_MESSAGE_FLAG_NONE));
   hss = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_OK,
-            core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
-                         &hss));
+  ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]),
+                                       MOJO_HANDLE_SIGNAL_READABLE, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
             hss.satisfied_signals);
   ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
@@ -1083,9 +798,8 @@
             core()->WriteData(ph, kWorld, &num_bytes,
                               MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
   hss = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_OK,
-            core()->Wait(ch_received, MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
-                         &hss));
+  EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(ch_received),
+                                       MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -1103,9 +817,8 @@
             core()->WriteMessage(h_passing[0], kWorld, kWorldSize, &ph, 1,
                                  MOJO_WRITE_MESSAGE_FLAG_NONE));
   hss = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_OK,
-            core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
-                         &hss));
+  ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]),
+                                       MOJO_HANDLE_SIGNAL_READABLE, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
             hss.satisfied_signals);
   ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
@@ -1137,9 +850,8 @@
             core()->WriteData(ph_received, kHello, &num_bytes,
                               MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
   hss = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_OK,
-            core()->Wait(ch_received, MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
-                         &hss));
+  EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(ch_received),
+                                       MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -1173,9 +885,8 @@
             core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ch, 1,
                                  MOJO_WRITE_MESSAGE_FLAG_NONE));
   ch = MOJO_HANDLE_INVALID;
-  ASSERT_EQ(MOJO_RESULT_OK,
-            core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
-                         nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]),
+                                       MOJO_HANDLE_SIGNAL_READABLE));
   num_bytes = kBufferSize;
   num_handles = arraysize(handles);
   ASSERT_EQ(MOJO_RESULT_OK,
@@ -1194,8 +905,8 @@
 
   // Wait for |ch| to be readable.
   hss = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_OK, core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE,
-                                         1000000000, &hss));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -1218,9 +929,8 @@
                                  MOJO_WRITE_MESSAGE_FLAG_NONE));
   ph = MOJO_HANDLE_INVALID;
   hss = kEmptyMojoHandleSignalsState;
-  ASSERT_EQ(MOJO_RESULT_OK,
-            core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
-                         &hss));
+  EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]),
+                                       MOJO_HANDLE_SIGNAL_READABLE, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
             hss.satisfied_signals);
   ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
diff --git a/mojo/edk/system/data_pipe_consumer_dispatcher.cc b/mojo/edk/system/data_pipe_consumer_dispatcher.cc
index c908e3a..f338732 100644
--- a/mojo/edk/system/data_pipe_consumer_dispatcher.cc
+++ b/mojo/edk/system/data_pipe_consumer_dispatcher.cc
@@ -80,6 +80,7 @@
       node_controller_(node_controller),
       control_port_(control_port),
       pipe_id_(pipe_id),
+      watchers_(this),
       shared_ring_buffer_(shared_ring_buffer) {
   if (initialized) {
     base::AutoLock lock(lock_);
@@ -97,34 +98,10 @@
   return CloseNoLock();
 }
 
-
-MojoResult DataPipeConsumerDispatcher::Watch(
-    MojoHandleSignals signals,
-    const Watcher::WatchCallback& callback,
-    uintptr_t context) {
-  base::AutoLock lock(lock_);
-
-  if (is_closed_ || in_transit_)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  return awakable_list_.AddWatcher(
-      signals, callback, context, GetHandleSignalsStateNoLock());
-}
-
-MojoResult DataPipeConsumerDispatcher::CancelWatch(uintptr_t context) {
-  base::AutoLock lock(lock_);
-
-  if (is_closed_ || in_transit_)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  return awakable_list_.RemoveWatcher(context);
-}
-
 MojoResult DataPipeConsumerDispatcher::ReadData(void* elements,
                                                 uint32_t* num_bytes,
                                                 MojoReadDataFlags flags) {
   base::AutoLock lock(lock_);
-  new_data_available_ = false;
 
   if (!shared_ring_buffer_ || in_transit_)
     return MOJO_RESULT_INVALID_ARGUMENT;
@@ -132,6 +109,9 @@
   if (in_two_phase_read_)
     return MOJO_RESULT_BUSY;
 
+  const bool had_new_data = new_data_available_;
+  new_data_available_ = false;
+
   if ((flags & MOJO_READ_DATA_FLAG_QUERY)) {
     if ((flags & MOJO_READ_DATA_FLAG_PEEK) ||
         (flags & MOJO_READ_DATA_FLAG_DISCARD))
@@ -140,6 +120,9 @@
     DVLOG_IF(2, elements)
         << "Query mode: ignoring non-null |elements|";
     *num_bytes = static_cast<uint32_t>(bytes_available_);
+
+    if (had_new_data)
+      watchers_.NotifyState(GetHandleSignalsStateNoLock());
     return MOJO_RESULT_OK;
   }
 
@@ -162,12 +145,16 @@
       all_or_none ? max_num_bytes_to_read : 0;
 
   if (min_num_bytes_to_read > bytes_available_) {
+    if (had_new_data)
+      watchers_.NotifyState(GetHandleSignalsStateNoLock());
     return peer_closed_ ? MOJO_RESULT_FAILED_PRECONDITION
                         : MOJO_RESULT_OUT_OF_RANGE;
   }
 
   uint32_t bytes_to_read = std::min(max_num_bytes_to_read, bytes_available_);
   if (bytes_to_read == 0) {
+    if (had_new_data)
+      watchers_.NotifyState(GetHandleSignalsStateNoLock());
     return peer_closed_ ? MOJO_RESULT_FAILED_PRECONDITION
                         : MOJO_RESULT_SHOULD_WAIT;
   }
@@ -199,6 +186,10 @@
     NotifyRead(bytes_to_read);
   }
 
+  // We may have just read the last available data and thus changed the signals
+  // state.
+  watchers_.NotifyState(GetHandleSignalsStateNoLock());
+
   return MOJO_RESULT_OK;
 }
 
@@ -206,7 +197,6 @@
                                                      uint32_t* buffer_num_bytes,
                                                      MojoReadDataFlags flags) {
   base::AutoLock lock(lock_);
-  new_data_available_ = false;
   if (!shared_ring_buffer_ || in_transit_)
     return MOJO_RESULT_INVALID_ARGUMENT;
 
@@ -219,7 +209,12 @@
       (flags & MOJO_READ_DATA_FLAG_PEEK))
     return MOJO_RESULT_INVALID_ARGUMENT;
 
+  const bool had_new_data = new_data_available_;
+  new_data_available_ = false;
+
   if (bytes_available_ == 0) {
+    if (had_new_data)
+      watchers_.NotifyState(GetHandleSignalsStateNoLock());
     return peer_closed_ ? MOJO_RESULT_FAILED_PRECONDITION
                         : MOJO_RESULT_SHOULD_WAIT;
   }
@@ -237,6 +232,9 @@
   *buffer_num_bytes = bytes_to_read;
   two_phase_max_bytes_read_ = bytes_to_read;
 
+  if (had_new_data)
+    watchers_.NotifyState(GetHandleSignalsStateNoLock());
+
   return MOJO_RESULT_OK;
 }
 
@@ -250,7 +248,6 @@
 
   CHECK(shared_ring_buffer_);
 
-  HandleSignalsState old_state = GetHandleSignalsStateNoLock();
   MojoResult rv;
   if (num_bytes_read > two_phase_max_bytes_read_ ||
       num_bytes_read % options_.element_num_bytes != 0) {
@@ -270,9 +267,7 @@
   in_two_phase_read_ = false;
   two_phase_max_bytes_read_ = 0;
 
-  HandleSignalsState new_state = GetHandleSignalsStateNoLock();
-  if (!new_state.equals(old_state))
-    awakable_list_.AwakeForStateChange(new_state);
+  watchers_.NotifyState(GetHandleSignalsStateNoLock());
 
   return rv;
 }
@@ -282,43 +277,22 @@
   return GetHandleSignalsStateNoLock();
 }
 
-MojoResult DataPipeConsumerDispatcher::AddAwakable(
-    Awakable* awakable,
-    MojoHandleSignals signals,
-    uintptr_t context,
-    HandleSignalsState* signals_state) {
+MojoResult DataPipeConsumerDispatcher::AddWatcherRef(
+    const scoped_refptr<WatcherDispatcher>& watcher,
+    uintptr_t context) {
   base::AutoLock lock(lock_);
-  if (!shared_ring_buffer_ || in_transit_) {
-    if (signals_state)
-      *signals_state = HandleSignalsState();
+  if (is_closed_ || in_transit_)
     return MOJO_RESULT_INVALID_ARGUMENT;
-  }
-  UpdateSignalsStateNoLock();
-  HandleSignalsState state = GetHandleSignalsStateNoLock();
-  if (state.satisfies(signals)) {
-    if (signals_state)
-      *signals_state = state;
-    return MOJO_RESULT_ALREADY_EXISTS;
-  }
-  if (!state.can_satisfy(signals)) {
-    if (signals_state)
-      *signals_state = state;
-    return MOJO_RESULT_FAILED_PRECONDITION;
-  }
-
-  awakable_list_.Add(awakable, signals, context);
-  return MOJO_RESULT_OK;
+  return watchers_.Add(watcher, context, GetHandleSignalsStateNoLock());
 }
 
-void DataPipeConsumerDispatcher::RemoveAwakable(
-    Awakable* awakable,
-    HandleSignalsState* signals_state) {
+MojoResult DataPipeConsumerDispatcher::RemoveWatcherRef(
+    WatcherDispatcher* watcher,
+    uintptr_t context) {
   base::AutoLock lock(lock_);
-  if ((!shared_ring_buffer_ || in_transit_) && signals_state)
-    *signals_state = HandleSignalsState();
-  else if (signals_state)
-    *signals_state = GetHandleSignalsStateNoLock();
-  awakable_list_.Remove(awakable);
+  if (is_closed_ || in_transit_)
+    return MOJO_RESULT_INVALID_ARGUMENT;
+  return watchers_.Remove(watcher, context);
 }
 
 void DataPipeConsumerDispatcher::StartSerialize(uint32_t* num_bytes,
@@ -463,7 +437,7 @@
   ring_buffer_mapping_.reset();
   shared_ring_buffer_ = nullptr;
 
-  awakable_list_.CancelAll();
+  watchers_.NotifyClosed();
   if (!transferred_) {
     base::AutoUnlock unlock(lock_);
     node_controller_->ClosePort(control_port_);
@@ -580,9 +554,8 @@
   if (has_new_data)
     new_data_available_ = true;
 
-  if (peer_closed_ != was_peer_closed || has_new_data) {
-    awakable_list_.AwakeForStateChange(GetHandleSignalsStateNoLock());
-  }
+  if (peer_closed_ != was_peer_closed || has_new_data)
+    watchers_.NotifyState(GetHandleSignalsStateNoLock());
 }
 
 }  // namespace edk
diff --git a/mojo/edk/system/data_pipe_consumer_dispatcher.h b/mojo/edk/system/data_pipe_consumer_dispatcher.h
index b323c16..120c7a3 100644
--- a/mojo/edk/system/data_pipe_consumer_dispatcher.h
+++ b/mojo/edk/system/data_pipe_consumer_dispatcher.h
@@ -16,10 +16,10 @@
 #include "mojo/edk/embedder/platform_handle_vector.h"
 #include "mojo/edk/embedder/platform_shared_buffer.h"
 #include "mojo/edk/embedder/scoped_platform_handle.h"
-#include "mojo/edk/system/awakable_list.h"
 #include "mojo/edk/system/dispatcher.h"
 #include "mojo/edk/system/ports/port_ref.h"
 #include "mojo/edk/system/system_impl_export.h"
+#include "mojo/edk/system/watcher_set.h"
 
 namespace mojo {
 namespace edk {
@@ -43,10 +43,6 @@
   // Dispatcher:
   Type GetType() const override;
   MojoResult Close() override;
-  MojoResult Watch(MojoHandleSignals signals,
-                   const Watcher::WatchCallback& callback,
-                   uintptr_t context) override;
-  MojoResult CancelWatch(uintptr_t context) override;
   MojoResult ReadData(void* elements,
                       uint32_t* num_bytes,
                       MojoReadDataFlags flags) override;
@@ -55,12 +51,10 @@
                            MojoReadDataFlags flags) override;
   MojoResult EndReadData(uint32_t num_bytes_read) override;
   HandleSignalsState GetHandleSignalsState() const override;
-  MojoResult AddAwakable(Awakable* awakable,
-                         MojoHandleSignals signals,
-                         uintptr_t context,
-                         HandleSignalsState* signals_state) override;
-  void RemoveAwakable(Awakable* awakable,
-                      HandleSignalsState* signals_state) override;
+  MojoResult AddWatcherRef(const scoped_refptr<WatcherDispatcher>& watcher,
+                           uintptr_t context) override;
+  MojoResult RemoveWatcherRef(WatcherDispatcher* watcher,
+                              uintptr_t context) override;
   void StartSerialize(uint32_t* num_bytes,
                       uint32_t* num_ports,
                       uint32_t* num_handles) override;
@@ -100,7 +94,7 @@
   // Guards access to the fields below.
   mutable base::Lock lock_;
 
-  AwakableList awakable_list_;
+  WatcherSet watchers_;
 
   scoped_refptr<PlatformSharedBuffer> shared_ring_buffer_;
   std::unique_ptr<PlatformSharedBufferMapping> ring_buffer_mapping_;
diff --git a/mojo/edk/system/data_pipe_producer_dispatcher.cc b/mojo/edk/system/data_pipe_producer_dispatcher.cc
index 8c1993a..b0102a6 100644
--- a/mojo/edk/system/data_pipe_producer_dispatcher.cc
+++ b/mojo/edk/system/data_pipe_producer_dispatcher.cc
@@ -79,6 +79,7 @@
       node_controller_(node_controller),
       control_port_(control_port),
       pipe_id_(pipe_id),
+      watchers_(this),
       shared_ring_buffer_(shared_ring_buffer),
       available_capacity_(options_.capacity_num_bytes) {
   if (initialized) {
@@ -97,28 +98,6 @@
   return CloseNoLock();
 }
 
-MojoResult DataPipeProducerDispatcher::Watch(
-    MojoHandleSignals signals,
-    const Watcher::WatchCallback& callback,
-    uintptr_t context) {
-  base::AutoLock lock(lock_);
-
-  if (is_closed_ || in_transit_)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  return awakable_list_.AddWatcher(
-      signals, callback, context, GetHandleSignalsStateNoLock());
-}
-
-MojoResult DataPipeProducerDispatcher::CancelWatch(uintptr_t context) {
-  base::AutoLock lock(lock_);
-
-  if (is_closed_ || in_transit_)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  return awakable_list_.RemoveWatcher(context);
-}
-
 MojoResult DataPipeProducerDispatcher::WriteData(const void* elements,
                                                  uint32_t* num_bytes,
                                                  MojoWriteDataFlags flags) {
@@ -149,8 +128,6 @@
   if (num_bytes_to_write == 0)
     return MOJO_RESULT_SHOULD_WAIT;
 
-  HandleSignalsState old_state = GetHandleSignalsStateNoLock();
-
   *num_bytes = num_bytes_to_write;
 
   CHECK(ring_buffer_mapping_);
@@ -176,9 +153,7 @@
   write_offset_ = (write_offset_ + num_bytes_to_write) %
       options_.capacity_num_bytes;
 
-  HandleSignalsState new_state = GetHandleSignalsStateNoLock();
-  if (!new_state.equals(old_state))
-    awakable_list_.AwakeForStateChange(new_state);
+  watchers_.NotifyState(GetHandleSignalsStateNoLock());
 
   base::AutoUnlock unlock(lock_);
   NotifyWrite(num_bytes_to_write);
@@ -193,6 +168,11 @@
   base::AutoLock lock(lock_);
   if (!shared_ring_buffer_ || in_transit_)
     return MOJO_RESULT_INVALID_ARGUMENT;
+
+  // These flags may not be used in two-phase mode.
+  if (flags & MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)
+    return MOJO_RESULT_INVALID_ARGUMENT;
+
   if (in_two_phase_write_)
     return MOJO_RESULT_BUSY;
   if (peer_closed_)
@@ -247,10 +227,8 @@
   in_two_phase_write_ = false;
 
   // If we're now writable, we *became* writable (since we weren't writable
-  // during the two-phase write), so awake producer awakables.
-  HandleSignalsState new_state = GetHandleSignalsStateNoLock();
-  if (new_state.satisfies(MOJO_HANDLE_SIGNAL_WRITABLE))
-    awakable_list_.AwakeForStateChange(new_state);
+  // during the two-phase write), so notify watchers.
+  watchers_.NotifyState(GetHandleSignalsStateNoLock());
 
   return rv;
 }
@@ -260,43 +238,22 @@
   return GetHandleSignalsStateNoLock();
 }
 
-MojoResult DataPipeProducerDispatcher::AddAwakable(
-    Awakable* awakable,
-    MojoHandleSignals signals,
-    uintptr_t context,
-    HandleSignalsState* signals_state) {
+MojoResult DataPipeProducerDispatcher::AddWatcherRef(
+    const scoped_refptr<WatcherDispatcher>& watcher,
+    uintptr_t context) {
   base::AutoLock lock(lock_);
-  if (!shared_ring_buffer_ || in_transit_) {
-    if (signals_state)
-      *signals_state = HandleSignalsState();
+  if (is_closed_ || in_transit_)
     return MOJO_RESULT_INVALID_ARGUMENT;
-  }
-  UpdateSignalsStateNoLock();
-  HandleSignalsState state = GetHandleSignalsStateNoLock();
-  if (state.satisfies(signals)) {
-    if (signals_state)
-      *signals_state = state;
-    return MOJO_RESULT_ALREADY_EXISTS;
-  }
-  if (!state.can_satisfy(signals)) {
-    if (signals_state)
-      *signals_state = state;
-    return MOJO_RESULT_FAILED_PRECONDITION;
-  }
-
-  awakable_list_.Add(awakable, signals, context);
-  return MOJO_RESULT_OK;
+  return watchers_.Add(watcher, context, GetHandleSignalsStateNoLock());
 }
 
-void DataPipeProducerDispatcher::RemoveAwakable(
-    Awakable* awakable,
-    HandleSignalsState* signals_state) {
+MojoResult DataPipeProducerDispatcher::RemoveWatcherRef(
+    WatcherDispatcher* watcher,
+    uintptr_t context) {
   base::AutoLock lock(lock_);
-  if ((!shared_ring_buffer_ || in_transit_) && signals_state)
-    *signals_state = HandleSignalsState();
-  else if (signals_state)
-    *signals_state = GetHandleSignalsStateNoLock();
-  awakable_list_.Remove(awakable);
+  if (is_closed_ || in_transit_)
+    return MOJO_RESULT_INVALID_ARGUMENT;
+  return watchers_.Remove(watcher, context);
 }
 
 void DataPipeProducerDispatcher::StartSerialize(uint32_t* num_bytes,
@@ -356,7 +313,9 @@
   DCHECK(in_transit_);
   in_transit_ = false;
   buffer_handle_for_transit_.reset();
-  awakable_list_.AwakeForStateChange(GetHandleSignalsStateNoLock());
+
+  HandleSignalsState state = GetHandleSignalsStateNoLock();
+  watchers_.NotifyState(state);
 }
 
 // static
@@ -439,7 +398,7 @@
   ring_buffer_mapping_.reset();
   shared_ring_buffer_ = nullptr;
 
-  awakable_list_.CancelAll();
+  watchers_.NotifyClosed();
   if (!transferred_) {
     base::AutoUnlock unlock(lock_);
     node_controller_->ClosePort(control_port_);
@@ -453,8 +412,7 @@
   lock_.AssertAcquired();
   HandleSignalsState rv;
   if (!peer_closed_) {
-    if (!in_two_phase_write_ && shared_ring_buffer_ &&
-        available_capacity_ > 0)
+    if (!in_two_phase_write_ && shared_ring_buffer_ && available_capacity_ > 0)
       rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_WRITABLE;
     rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_WRITABLE;
   } else {
@@ -541,7 +499,7 @@
 
   if (peer_closed_ != was_peer_closed ||
       available_capacity_ != previous_capacity) {
-    awakable_list_.AwakeForStateChange(GetHandleSignalsStateNoLock());
+    watchers_.NotifyState(GetHandleSignalsStateNoLock());
   }
 }
 
diff --git a/mojo/edk/system/data_pipe_producer_dispatcher.h b/mojo/edk/system/data_pipe_producer_dispatcher.h
index a55234a..1eddd5d 100644
--- a/mojo/edk/system/data_pipe_producer_dispatcher.h
+++ b/mojo/edk/system/data_pipe_producer_dispatcher.h
@@ -15,10 +15,10 @@
 #include "base/synchronization/lock.h"
 #include "mojo/edk/embedder/platform_handle_vector.h"
 #include "mojo/edk/embedder/platform_shared_buffer.h"
-#include "mojo/edk/system/awakable_list.h"
 #include "mojo/edk/system/dispatcher.h"
 #include "mojo/edk/system/ports/port_ref.h"
 #include "mojo/edk/system/system_impl_export.h"
+#include "mojo/edk/system/watcher_set.h"
 
 namespace mojo {
 namespace edk {
@@ -43,10 +43,6 @@
   // Dispatcher:
   Type GetType() const override;
   MojoResult Close() override;
-  MojoResult Watch(MojoHandleSignals signals,
-                   const Watcher::WatchCallback& callback,
-                   uintptr_t context) override;
-  MojoResult CancelWatch(uintptr_t context) override;
   MojoResult WriteData(const void* elements,
                        uint32_t* num_bytes,
                        MojoReadDataFlags flags) override;
@@ -55,12 +51,10 @@
                             MojoWriteDataFlags flags) override;
   MojoResult EndWriteData(uint32_t num_bytes_written) override;
   HandleSignalsState GetHandleSignalsState() const override;
-  MojoResult AddAwakable(Awakable* awakable,
-                         MojoHandleSignals signals,
-                         uintptr_t context,
-                         HandleSignalsState* signals_state) override;
-  void RemoveAwakable(Awakable* awakable,
-                      HandleSignalsState* signals_state) override;
+  MojoResult AddWatcherRef(const scoped_refptr<WatcherDispatcher>& watcher,
+                           uintptr_t context) override;
+  MojoResult RemoveWatcherRef(WatcherDispatcher* watcher,
+                              uintptr_t context) override;
   void StartSerialize(uint32_t* num_bytes,
                       uint32_t* num_ports,
                       uint32_t* num_handles) override;
@@ -103,7 +97,7 @@
   // Guards access to the fields below.
   mutable base::Lock lock_;
 
-  AwakableList awakable_list_;
+  WatcherSet watchers_;
 
   bool buffer_requested_ = false;
 
diff --git a/mojo/edk/system/data_pipe_unittest.cc b/mojo/edk/system/data_pipe_unittest.cc
index 610aeac..79c1f75 100644
--- a/mojo/edk/system/data_pipe_unittest.cc
+++ b/mojo/edk/system/data_pipe_unittest.cc
@@ -16,12 +16,11 @@
 #include "mojo/edk/embedder/embedder.h"
 #include "mojo/edk/embedder/platform_channel_pair.h"
 #include "mojo/edk/system/test_utils.h"
-#include "mojo/edk/system/waiter.h"
 #include "mojo/edk/test/mojo_test_base.h"
 #include "mojo/public/c/system/data_pipe.h"
 #include "mojo/public/c/system/functions.h"
 #include "mojo/public/c/system/message_pipe.h"
-#include "mojo/public/cpp/system/watcher.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace mojo {
@@ -162,8 +161,7 @@
   // Now wait for the other side to become readable.
   MojoHandleSignalsState state;
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &state));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &state));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             state.satisfied_signals);
 
@@ -249,8 +247,7 @@
 
   // Wait.
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -337,19 +334,12 @@
   Create(&options);
   MojoHandleSignalsState hss;
 
-  // Never readable.
-  hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            MojoWait(producer_, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+  // Never readable. Already writable.
+  hss = GetSignalsState(producer_);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
             hss.satisfiable_signals);
 
-  // Already writable.
-  hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
-
   // Write two elements.
   int32_t elements[2] = {123, 456};
   uint32_t num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0]));
@@ -358,8 +348,7 @@
 
   // Wait for data to become available to the consumer.
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -419,11 +408,7 @@
   // It should now be never-writable.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
-  hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+            WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
 }
@@ -444,8 +429,7 @@
   // It should be signaled.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
 }
@@ -466,8 +450,7 @@
   // It should be signaled.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
 }
@@ -485,8 +468,7 @@
   // Never writable.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_WRITABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss));
   EXPECT_EQ(0u, hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
                 MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
@@ -500,8 +482,7 @@
   // Wait for readability.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -516,8 +497,7 @@
   // Should still be readable.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
                 MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
@@ -534,8 +514,7 @@
   // Should still be readable.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
                 MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
@@ -559,8 +538,7 @@
   // Waiting should now succeed.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -573,8 +551,7 @@
   // Should still be readable.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_TRUE(hss.satisfied_signals & (MOJO_HANDLE_SIGNAL_READABLE |
                                        MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -584,8 +561,7 @@
   // Wait for the peer closed signal.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE |
                 MOJO_HANDLE_SIGNAL_PEER_CLOSED,
             hss.satisfied_signals);
@@ -605,8 +581,7 @@
   // Should be never-readable.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
 }
@@ -625,11 +600,10 @@
   EXPECT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true));
 
   // The consumer handle should appear to be readable and have new data.
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, nullptr));
   EXPECT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, nullptr));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE));
+  EXPECT_TRUE(GetSignalsState(consumer_).satisfied_signals &
+              MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE);
 
   // Now try to read a minimum of 6 elements.
   int32_t read_elements[6];
@@ -638,35 +612,30 @@
             MojoReadData(consumer_, read_elements, &num_read_bytes,
                          MOJO_READ_DATA_FLAG_ALL_OR_NONE));
 
-  // The consumer should still appear to be readable, but not with new data.
-  EXPECT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, 0, nullptr));
-  EXPECT_EQ(
-      MOJO_RESULT_DEADLINE_EXCEEDED,
-      MojoWait(consumer_, MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, 0, nullptr));
+  // The consumer should still appear to be readable but not with new data.
+  EXPECT_TRUE(GetSignalsState(consumer_).satisfied_signals &
+              MOJO_HANDLE_SIGNAL_READABLE);
+  EXPECT_FALSE(GetSignalsState(consumer_).satisfied_signals &
+               MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE);
 
   // Write four more elements.
   EXPECT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true));
   EXPECT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true));
 
-  // The consumer handle should once again appear to be readable with new data.
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, nullptr));
+  // The consumer handle should once again appear to be readable.
   EXPECT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, nullptr));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE));
 
-  // Read should succeed this time.
+  // Try again to read a minimum of 6 elements. Should succeed this time.
   EXPECT_EQ(MOJO_RESULT_OK,
             MojoReadData(consumer_, read_elements, &num_read_bytes,
                          MOJO_READ_DATA_FLAG_ALL_OR_NONE));
 
-  // And once again the consumer is unreadable.
-  EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, 0, nullptr));
-  EXPECT_EQ(
-      MOJO_RESULT_DEADLINE_EXCEEDED,
-      MojoWait(consumer_, MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, 0, nullptr));
+  // And now the consumer is unreadable.
+  EXPECT_FALSE(GetSignalsState(consumer_).satisfied_signals &
+               MOJO_HANDLE_SIGNAL_READABLE);
+  EXPECT_FALSE(GetSignalsState(consumer_).satisfied_signals &
+               MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE);
 }
 
 // Test with two-phase APIs and also closing the producer with an active
@@ -697,8 +666,7 @@
   // Wait for readability.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -719,8 +687,7 @@
   // Should still be readable.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
                 MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
@@ -743,8 +710,7 @@
   // Should be never-readable.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
 }
@@ -761,9 +727,7 @@
   MojoHandleSignalsState hss;
 
   // It should be writable.
-  hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+  hss = GetSignalsState(producer_);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
             hss.satisfiable_signals);
@@ -775,17 +739,13 @@
   EXPECT_GE(num_bytes, static_cast<uint32_t>(1u * sizeof(int32_t)));
 
   // At this point, it shouldn't be writable.
-  hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
-            MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+  hss = GetSignalsState(producer_);
   ASSERT_EQ(0u, hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
             hss.satisfiable_signals);
 
   // It shouldn't be readable yet either (we'll wait later).
-  hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+  hss = GetSignalsState(consumer_);
   ASSERT_EQ(0u, hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
                 MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
@@ -795,9 +755,7 @@
   ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(1u * sizeof(int32_t)));
 
   // It should immediately be writable again.
-  hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+  hss = GetSignalsState(producer_);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
             hss.satisfiable_signals);
@@ -805,8 +763,7 @@
   // It should become readable.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -824,8 +781,7 @@
   // It should be readable.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -843,17 +799,13 @@
   ASSERT_EQ(static_cast<uint32_t>(1u * sizeof(int32_t)), num_bytes);
 
   // At this point, it should still be writable.
-  hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+  hss = GetSignalsState(producer_);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
             hss.satisfiable_signals);
 
   // But not readable.
-  hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+  hss = GetSignalsState(consumer_);
   ASSERT_EQ(0u, hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
                 MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
@@ -863,9 +815,7 @@
   ASSERT_EQ(MOJO_RESULT_OK, EndReadData(0u));
 
   // It should be readable again.
-  hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+  hss = GetSignalsState(consumer_);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
                 MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
@@ -911,8 +861,7 @@
   // of data to become available.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE |
@@ -1002,8 +951,7 @@
   // Wait.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
@@ -1067,8 +1015,7 @@
 
   // Wait for data.
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_TRUE(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
                 MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
@@ -1095,8 +1042,7 @@
   while (total_num_bytes < 90) {
     // Wait to write.
     ASSERT_EQ(MOJO_RESULT_OK,
-              MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE,
-                       MOJO_DEADLINE_INDEFINITE, &hss));
+              WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss));
     ASSERT_EQ(hss.satisfied_signals, MOJO_HANDLE_SIGNAL_WRITABLE);
     ASSERT_EQ(hss.satisfiable_signals,
               MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED);
@@ -1231,8 +1177,7 @@
   // TODO(vtl): (See corresponding TODO in AllOrNone.)
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -1252,8 +1197,7 @@
   // Wait for producer to know that the consumer is closed.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
 
@@ -1323,8 +1267,7 @@
   // must also know about all the data that was sent.)
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
                 MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
@@ -1384,8 +1327,7 @@
   // Wait for the data.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -1411,8 +1353,7 @@
   // must also have received the extra data).
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
                 MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
@@ -1499,8 +1440,7 @@
   // TODO(vtl): (See corresponding TODO in AllOrNone.)
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -1569,8 +1509,7 @@
   // Wait for the data.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -1594,8 +1533,8 @@
             MojoWriteMessage(pipe0, nullptr, 0, &producer_, 1,
                              MOJO_WRITE_MESSAGE_FLAG_NONE));
   producer_ = MOJO_HANDLE_INVALID;
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &hss));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(pipe1, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   uint32_t num_handles = 1;
   ASSERT_EQ(MOJO_RESULT_OK,
             MojoReadMessage(pipe1, nullptr, 0, &producer_, &num_handles,
@@ -1612,8 +1551,7 @@
   // Wait for it.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             hss.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
@@ -1653,8 +1591,7 @@
   // Now wait for the other side to become readable and to see the peer closed.
   MojoHandleSignalsState state;
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                     MOJO_DEADLINE_INDEFINITE, &state));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
                 MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             state.satisfied_signals);
@@ -1671,8 +1608,8 @@
             MojoWriteMessage(pipe0, nullptr, 0, &consumer_, 1,
                              MOJO_WRITE_MESSAGE_FLAG_NONE));
   consumer_ = MOJO_HANDLE_INVALID;
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &state));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(pipe1, MOJO_HANDLE_SIGNAL_READABLE, &state));
   uint32_t num_handles = 1;
   ASSERT_EQ(MOJO_RESULT_OK,
             MojoReadMessage(pipe1, nullptr, 0, &consumer_, &num_handles,
@@ -1680,8 +1617,7 @@
   ASSERT_EQ(num_handles, 1u);
 
   ASSERT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                     MOJO_DEADLINE_INDEFINITE, &state));
+            WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
                 MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             state.satisfied_signals);
@@ -1716,8 +1652,8 @@
     }
 
     MojoHandleSignalsState hss = MojoHandleSignalsState();
-    EXPECT_EQ(MOJO_RESULT_OK, MojoWait(producer, MOJO_HANDLE_SIGNAL_WRITABLE,
-                                       MOJO_DEADLINE_INDEFINITE, &hss));
+    EXPECT_EQ(MOJO_RESULT_OK, test::MojoTestBase::WaitForSignals(
+                                  producer, MOJO_HANDLE_SIGNAL_WRITABLE, &hss));
     EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
     EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
               hss.satisfiable_signals);
@@ -1754,8 +1690,8 @@
     }
 
     MojoHandleSignalsState hss = MojoHandleSignalsState();
-    EXPECT_EQ(MOJO_RESULT_OK, MojoWait(consumer, MOJO_HANDLE_SIGNAL_READABLE,
-                                       MOJO_DEADLINE_INDEFINITE, &hss));
+    EXPECT_EQ(MOJO_RESULT_OK, test::MojoTestBase::WaitForSignals(
+                                  consumer, MOJO_HANDLE_SIGNAL_READABLE, &hss));
     // Peer could have become closed while we're still waiting for data.
     EXPECT_TRUE(MOJO_HANDLE_SIGNAL_READABLE & hss.satisfied_signals);
     EXPECT_TRUE(hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE);
@@ -1814,8 +1750,8 @@
     // Receive the consumer from the other side.
     producer_ = MOJO_HANDLE_INVALID;
     MojoHandleSignalsState hss = MojoHandleSignalsState();
-    ASSERT_EQ(MOJO_RESULT_OK, MojoWait(server_mp, MOJO_HANDLE_SIGNAL_READABLE,
-                                       MOJO_DEADLINE_INDEFINITE, &hss));
+    ASSERT_EQ(MOJO_RESULT_OK,
+              WaitForSignals(server_mp, MOJO_HANDLE_SIGNAL_READABLE, &hss));
     MojoHandle handles[2];
     uint32_t num_handles = arraysize(handles);
     ASSERT_EQ(MOJO_RESULT_OK,
@@ -1844,8 +1780,8 @@
   // Receive the data pipe from the other side.
   MojoHandle consumer = MOJO_HANDLE_INVALID;
   MojoHandleSignalsState hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(client_mp, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &hss));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   MojoHandle handles[2];
   uint32_t num_handles = arraysize(handles);
   ASSERT_EQ(MOJO_RESULT_OK,
@@ -1880,8 +1816,8 @@
   // Receive the producer from the other side.
   MojoHandle producer = MOJO_HANDLE_INVALID;
   hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(client_mp, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &hss));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   num_handles = arraysize(handles);
   ASSERT_EQ(MOJO_RESULT_OK,
             MojoReadMessage(client_mp, nullptr, 0, handles, &num_handles,
@@ -1921,8 +1857,7 @@
   std::string expected_message = ReadMessageWithHandles(h, &c, 1);
 
   // Wait for the consumer to become readable.
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWait(c, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(c, MOJO_HANDLE_SIGNAL_READABLE));
 
   // Drain the consumer and expect to find the given message.
   uint32_t num_bytes = static_cast<uint32_t>(expected_message.size());
@@ -1989,8 +1924,7 @@
     std::string expected_message = ReadMessageWithHandles(child, &c, 1);
 
     // Wait for the consumer to become readable.
-    EXPECT_EQ(MOJO_RESULT_OK, MojoWait(c, MOJO_HANDLE_SIGNAL_READABLE,
-                                       MOJO_DEADLINE_INDEFINITE, nullptr));
+    EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(c, MOJO_HANDLE_SIGNAL_READABLE));
 
     // Drain the consumer and expect to find the given message.
     uint32_t num_bytes = static_cast<uint32_t>(expected_message.size());
@@ -2017,19 +1951,17 @@
   MojoHandle* producers = &handles[0];
   MojoHandle* consumers = &handles[3];
 
-  // Wait on producer 0 using MojoWait.
+  // Wait on producer 0
   EXPECT_EQ(MOJO_RESULT_OK,
-            MojoWait(producers[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                     MOJO_DEADLINE_INDEFINITE, nullptr));
+            WaitForSignals(producers[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED));
 
-  // Wait on consumer 0 using MojoWait.
+  // Wait on consumer 0
   EXPECT_EQ(MOJO_RESULT_OK,
-            MojoWait(consumers[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                     MOJO_DEADLINE_INDEFINITE, nullptr));
+            WaitForSignals(consumers[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED));
 
   base::MessageLoop message_loop;
 
-  // Wait on producer 1 and consumer 1 using Watchers.
+  // Wait on producer 1 and consumer 1 using SimpleWatchers.
   {
     base::RunLoop run_loop;
     int count = 0;
@@ -2040,12 +1972,16 @@
             loop->Quit();
         },
         &run_loop, &count);
-    Watcher producer_watcher(FROM_HERE), consumer_watcher(FROM_HERE);
-    producer_watcher.Start(
-        Handle(producers[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, callback);
-    consumer_watcher.Start(
-        Handle(consumers[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, callback);
+    SimpleWatcher producer_watcher(FROM_HERE,
+                                   SimpleWatcher::ArmingPolicy::AUTOMATIC);
+    SimpleWatcher consumer_watcher(FROM_HERE,
+                                   SimpleWatcher::ArmingPolicy::AUTOMATIC);
+    producer_watcher.Watch(Handle(producers[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+                           callback);
+    consumer_watcher.Watch(Handle(consumers[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+                           callback);
     run_loop.Run();
+    EXPECT_EQ(2, count);
   }
 
   // Wait on producer 2 by polling with MojoWriteData.
diff --git a/mojo/edk/system/dispatcher.cc b/mojo/edk/system/dispatcher.cc
index 7d701b2..7cdbe91 100644
--- a/mojo/edk/system/dispatcher.cc
+++ b/mojo/edk/system/dispatcher.cc
@@ -22,9 +22,9 @@
 
 Dispatcher::DispatcherInTransit::~DispatcherInTransit() {}
 
-MojoResult Dispatcher::Watch(MojoHandleSignals signals,
-                             const Watcher::WatchCallback& callback,
-                             uintptr_t context) {
+MojoResult Dispatcher::WatchDispatcher(scoped_refptr<Dispatcher> dispatcher,
+                                       MojoHandleSignals signals,
+                                       uintptr_t context) {
   return MOJO_RESULT_INVALID_ARGUMENT;
 }
 
@@ -32,6 +32,13 @@
   return MOJO_RESULT_INVALID_ARGUMENT;
 }
 
+MojoResult Dispatcher::Arm(uint32_t* num_ready_contexts,
+                           uintptr_t* ready_contexts,
+                           MojoResult* ready_results,
+                           MojoHandleSignalsState* ready_signals_states) {
+  return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
 MojoResult Dispatcher::WriteMessage(std::unique_ptr<MessageForTransit> message,
                                     MojoWriteMessageFlags flags) {
   return MOJO_RESULT_INVALID_ARGUMENT;
@@ -115,16 +122,15 @@
   return HandleSignalsState();
 }
 
-MojoResult Dispatcher::AddAwakable(Awakable* awakable,
-                                   MojoHandleSignals signals,
-                                   uintptr_t context,
-                                   HandleSignalsState* signals_state) {
+MojoResult Dispatcher::AddWatcherRef(
+    const scoped_refptr<WatcherDispatcher>& watcher,
+    uintptr_t context) {
   return MOJO_RESULT_INVALID_ARGUMENT;
 }
 
-void Dispatcher::RemoveAwakable(Awakable* awakable,
-                                HandleSignalsState* handle_signals_state) {
-  NOTREACHED();
+MojoResult Dispatcher::RemoveWatcherRef(WatcherDispatcher* watcher,
+                                        uintptr_t context) {
+  return MOJO_RESULT_INVALID_ARGUMENT;
 }
 
 void Dispatcher::StartSerialize(uint32_t* num_bytes,
diff --git a/mojo/edk/system/dispatcher.h b/mojo/edk/system/dispatcher.h
index 9dca67f..db1f1f1 100644
--- a/mojo/edk/system/dispatcher.h
+++ b/mojo/edk/system/dispatcher.h
@@ -20,7 +20,7 @@
 #include "mojo/edk/system/handle_signals_state.h"
 #include "mojo/edk/system/ports/name.h"
 #include "mojo/edk/system/system_impl_export.h"
-#include "mojo/edk/system/watcher.h"
+#include "mojo/edk/system/watch.h"
 #include "mojo/public/c/system/buffer.h"
 #include "mojo/public/c/system/data_pipe.h"
 #include "mojo/public/c/system/message_pipe.h"
@@ -29,15 +29,13 @@
 namespace mojo {
 namespace edk {
 
-class Awakable;
 class Dispatcher;
 class MessageForTransit;
 
 using DispatcherVector = std::vector<scoped_refptr<Dispatcher>>;
 
 // A |Dispatcher| implements Mojo EDK calls that are associated with a
-// particular MojoHandle, with the exception of MojoWait and MojoWaitMany (
-// which are implemented directly in Core.).
+// particular MojoHandle.
 class MOJO_SYSTEM_IMPL_EXPORT Dispatcher
     : public base::RefCountedThreadSafe<Dispatcher> {
  public:
@@ -56,7 +54,7 @@
     DATA_PIPE_PRODUCER,
     DATA_PIPE_CONSUMER,
     SHARED_BUFFER,
-    WAIT_SET,
+    WATCHER,
 
     // "Private" types (not exposed via the public interface):
     PLATFORM_HANDLE = -1,
@@ -67,13 +65,16 @@
   virtual Type GetType() const = 0;
   virtual MojoResult Close() = 0;
 
-  ///////////// Watch API ////////////////////
+  ///////////// Watcher API ////////////////////
 
-  virtual MojoResult Watch(MojoHandleSignals signals,
-                           const Watcher::WatchCallback& callback,
-                           uintptr_t context);
-
+  virtual MojoResult WatchDispatcher(scoped_refptr<Dispatcher> dispatcher,
+                                     MojoHandleSignals signals,
+                                     uintptr_t context);
   virtual MojoResult CancelWatch(uintptr_t context);
+  virtual MojoResult Arm(uint32_t* num_ready_contexts,
+                         uintptr_t* ready_contexts,
+                         MojoResult* ready_results,
+                         MojoHandleSignalsState* ready_signals_states);
 
   ///////////// Message pipe API /////////////
 
@@ -158,31 +159,17 @@
   // threads.
   virtual HandleSignalsState GetHandleSignalsState() const;
 
-  // Adds an awakable to this dispatcher, which will be woken up when this
-  // object changes state to satisfy |signals| with context |context|. It will
-  // also be woken up when it becomes impossible for the object to ever satisfy
-  // |signals| with a suitable error status.
-  //
-  // If |signals_state| is non-null, on *failure* |*signals_state| will be set
-  // to the current handle signals state (on success, it is left untouched).
-  //
-  // Returns:
-  //  - |MOJO_RESULT_OK| if the awakable was added;
-  //  - |MOJO_RESULT_ALREADY_EXISTS| if |signals| is already satisfied;
-  //  - |MOJO_RESULT_INVALID_ARGUMENT| if the dispatcher has been closed; and
-  //  - |MOJO_RESULT_FAILED_PRECONDITION| if it is not (or no longer) possible
-  //    that |signals| will ever be satisfied.
-  virtual MojoResult AddAwakable(Awakable* awakable,
-                                 MojoHandleSignals signals,
-                                 uintptr_t context,
-                                 HandleSignalsState* signals_state);
+  // Adds a WatcherDispatcher reference to this dispatcher, to be notified of
+  // all subsequent changes to handle state including signal changes or closure.
+  // The reference is associated with a |context| for disambiguation of
+  // removals.
+  virtual MojoResult AddWatcherRef(
+      const scoped_refptr<WatcherDispatcher>& watcher,
+      uintptr_t context);
 
-  // Removes an awakable from this dispatcher. (It is valid to call this
-  // multiple times for the same |awakable| on the same object, so long as
-  // |AddAwakable()| was called at most once.) If |signals_state| is non-null,
-  // |*signals_state| will be set to the current handle signals state.
-  virtual void RemoveAwakable(Awakable* awakable,
-                              HandleSignalsState* signals_state);
+  // Removes a WatcherDispatcher reference from this dispatcher.
+  virtual MojoResult RemoveWatcherRef(WatcherDispatcher* watcher,
+                                      uintptr_t context);
 
   // Informs the caller of the total serialized size (in bytes) and the total
   // number of platform handles and ports needed to transfer this dispatcher
diff --git a/mojo/edk/system/handle_signals_state.h b/mojo/edk/system/handle_signals_state.h
index 1c47a28..f241278 100644
--- a/mojo/edk/system/handle_signals_state.h
+++ b/mojo/edk/system/handle_signals_state.h
@@ -5,45 +5,9 @@
 #ifndef MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_
 #define MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_
 
-#include "mojo/edk/system/system_impl_export.h"
-#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/handle_signals_state.h"
 
-namespace mojo {
-namespace edk {
-
-// Just "add" some constructors and methods to the C struct
-// |MojoHandleSignalsState| (for convenience). This should add no overhead.
-struct MOJO_SYSTEM_IMPL_EXPORT HandleSignalsState final
-    : public MojoHandleSignalsState {
-  HandleSignalsState() {
-    satisfied_signals = MOJO_HANDLE_SIGNAL_NONE;
-    satisfiable_signals = MOJO_HANDLE_SIGNAL_NONE;
-  }
-  HandleSignalsState(MojoHandleSignals satisfied,
-                     MojoHandleSignals satisfiable) {
-    satisfied_signals = satisfied;
-    satisfiable_signals = satisfiable;
-  }
-
-  bool equals(const HandleSignalsState& other) const {
-    return satisfied_signals == other.satisfied_signals &&
-           satisfiable_signals == other.satisfiable_signals;
-  }
-
-  bool satisfies(MojoHandleSignals signals) const {
-    return !!(satisfied_signals & signals);
-  }
-
-  bool can_satisfy(MojoHandleSignals signals) const {
-    return !!(satisfiable_signals & signals);
-  }
-
-  // (Copy and assignment allowed.)
-};
-static_assert(sizeof(HandleSignalsState) == sizeof(MojoHandleSignalsState),
-              "HandleSignalsState should add no overhead");
-
-}  // namespace edk
-}  // namespace mojo
+// TODO(rockot): Remove this header and use the C++ system library type
+// directly inside the EDK.
 
 #endif  // MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_
diff --git a/mojo/edk/system/message_pipe_dispatcher.cc b/mojo/edk/system/message_pipe_dispatcher.cc
index f27336b..1db56c0 100644
--- a/mojo/edk/system/message_pipe_dispatcher.cc
+++ b/mojo/edk/system/message_pipe_dispatcher.cc
@@ -164,7 +164,8 @@
     : node_controller_(node_controller),
       port_(port),
       pipe_id_(pipe_id),
-      endpoint_(endpoint) {
+      endpoint_(endpoint),
+      watchers_(this) {
   DVLOG(2) << "Creating new MessagePipeDispatcher for port " << port.name()
            << " [pipe_id=" << pipe_id << "; endpoint=" << endpoint << "]";
 
@@ -182,7 +183,7 @@
     base::AutoLock lock(signal_lock_);
     port0 = port_;
     port_closed_.Set(true);
-    awakables_.CancelAll();
+    watchers_.NotifyClosed();
   }
 
   ports::PortRef port1;
@@ -190,7 +191,7 @@
     base::AutoLock lock(other->signal_lock_);
     port1 = other->port_;
     other->port_closed_.Set(true);
-    other->awakables_.CancelAll();
+    other->watchers_.NotifyClosed();
   }
 
   // Both ports are always closed by this call.
@@ -209,27 +210,6 @@
   return CloseNoLock();
 }
 
-MojoResult MessagePipeDispatcher::Watch(MojoHandleSignals signals,
-                                        const Watcher::WatchCallback& callback,
-                                        uintptr_t context) {
-  base::AutoLock lock(signal_lock_);
-
-  if (port_closed_ || in_transit_)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  return awakables_.AddWatcher(
-      signals, callback, context, GetHandleSignalsStateNoLock());
-}
-
-MojoResult MessagePipeDispatcher::CancelWatch(uintptr_t context) {
-  base::AutoLock lock(signal_lock_);
-
-  if (port_closed_ || in_transit_)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  return awakables_.RemoveWatcher(context);
-}
-
 MojoResult MessagePipeDispatcher::WriteMessage(
     std::unique_ptr<MessageForTransit> message,
     MojoWriteMessageFlags flags) {
@@ -299,6 +279,12 @@
   }
 
   if (no_space) {
+    if (may_discard) {
+      // May have been the last message on the pipe. Need to update signals just
+      // in case.
+      base::AutoLock lock(signal_lock_);
+      watchers_.NotifyState(GetHandleSignalsStateNoLock());
+    }
     // |*num_handles| (and/or |*num_bytes| if |read_any_size| is false) wasn't
     // sufficient to hold this message's data. The message will still be in
     // queue unless MOJO_READ_MESSAGE_FLAG_MAY_DISCARD was set.
@@ -319,6 +305,13 @@
   // Alright! We have a message and the caller has provided sufficient storage
   // in which to receive it.
 
+  {
+    // We need to update anyone watching our signals in case that was the last
+    // available message.
+    base::AutoLock lock(signal_lock_);
+    watchers_.NotifyState(GetHandleSignalsStateNoLock());
+  }
+
   std::unique_ptr<PortsMessage> msg(
       static_cast<PortsMessage*>(ports_message.release()));
 
@@ -396,63 +389,21 @@
   return GetHandleSignalsStateNoLock();
 }
 
-MojoResult MessagePipeDispatcher::AddAwakable(
-    Awakable* awakable,
-    MojoHandleSignals signals,
-    uintptr_t context,
-    HandleSignalsState* signals_state) {
+MojoResult MessagePipeDispatcher::AddWatcherRef(
+    const scoped_refptr<WatcherDispatcher>& watcher,
+    uintptr_t context) {
   base::AutoLock lock(signal_lock_);
-
-  if (port_closed_ || in_transit_) {
-    if (signals_state)
-      *signals_state = HandleSignalsState();
+  if (port_closed_ || in_transit_)
     return MOJO_RESULT_INVALID_ARGUMENT;
-  }
-
-  HandleSignalsState state = GetHandleSignalsStateNoLock();
-
-  DVLOG(2) << "Getting signal state for pipe " << pipe_id_ << " endpoint "
-           << endpoint_ << " [awakable=" << awakable << "; port="
-           << port_.name() << "; signals=" << signals << "; satisfied="
-           << state.satisfied_signals << "; satisfiable="
-           << state.satisfiable_signals << "]";
-
-  if (state.satisfies(signals)) {
-    if (signals_state)
-      *signals_state = state;
-    DVLOG(2) << "Signals already set for " << port_.name();
-    return MOJO_RESULT_ALREADY_EXISTS;
-  }
-  if (!state.can_satisfy(signals)) {
-    if (signals_state)
-      *signals_state = state;
-    DVLOG(2) << "Signals impossible to satisfy for " << port_.name();
-    return MOJO_RESULT_FAILED_PRECONDITION;
-  }
-
-  DVLOG(2) << "Adding awakable to pipe " << pipe_id_ << " endpoint "
-           << endpoint_ << " [awakable=" << awakable << "; port="
-           << port_.name() << "; signals=" << signals << "]";
-
-  awakables_.Add(awakable, signals, context);
-  return MOJO_RESULT_OK;
+  return watchers_.Add(watcher, context, GetHandleSignalsStateNoLock());
 }
 
-void MessagePipeDispatcher::RemoveAwakable(Awakable* awakable,
-                                           HandleSignalsState* signals_state) {
+MojoResult MessagePipeDispatcher::RemoveWatcherRef(WatcherDispatcher* watcher,
+                                                   uintptr_t context) {
   base::AutoLock lock(signal_lock_);
-  if (port_closed_ || in_transit_) {
-    if (signals_state)
-      *signals_state = HandleSignalsState();
-  } else if (signals_state) {
-    *signals_state = GetHandleSignalsStateNoLock();
-  }
-
-  DVLOG(2) << "Removing awakable from pipe " << pipe_id_ << " endpoint "
-           << endpoint_ << " [awakable=" << awakable << "; port="
-           << port_.name() << "]";
-
-  awakables_.Remove(awakable);
+  if (port_closed_ || in_transit_)
+    return MOJO_RESULT_INVALID_ARGUMENT;
+  return watchers_.Remove(watcher, context);
 }
 
 void MessagePipeDispatcher::StartSerialize(uint32_t* num_bytes,
@@ -496,7 +447,7 @@
   in_transit_.Set(false);
 
   // Something may have happened while we were waiting for potential transit.
-  awakables_.AwakeForStateChange(GetHandleSignalsStateNoLock());
+  watchers_.NotifyState(GetHandleSignalsStateNoLock());
 }
 
 // static
@@ -531,7 +482,7 @@
     return MOJO_RESULT_INVALID_ARGUMENT;
 
   port_closed_.Set(true);
-  awakables_.CancelAll();
+  watchers_.NotifyClosed();
 
   if (!port_transferred_) {
     base::AutoUnlock unlock(signal_lock_);
@@ -596,7 +547,7 @@
   }
 #endif
 
-  awakables_.AwakeForStateChange(GetHandleSignalsStateNoLock());
+  watchers_.NotifyState(GetHandleSignalsStateNoLock());
 }
 
 }  // namespace edk
diff --git a/mojo/edk/system/message_pipe_dispatcher.h b/mojo/edk/system/message_pipe_dispatcher.h
index 6743222..574ad66 100644
--- a/mojo/edk/system/message_pipe_dispatcher.h
+++ b/mojo/edk/system/message_pipe_dispatcher.h
@@ -12,10 +12,10 @@
 
 #include "base/macros.h"
 #include "mojo/edk/system/atomic_flag.h"
-#include "mojo/edk/system/awakable_list.h"
 #include "mojo/edk/system/dispatcher.h"
 #include "mojo/edk/system/message_for_transit.h"
 #include "mojo/edk/system/ports/port_ref.h"
+#include "mojo/edk/system/watcher_set.h"
 
 namespace mojo {
 namespace edk {
@@ -48,10 +48,6 @@
   // Dispatcher:
   Type GetType() const override;
   MojoResult Close() override;
-  MojoResult Watch(MojoHandleSignals signals,
-                   const Watcher::WatchCallback& callback,
-                   uintptr_t context) override;
-  MojoResult CancelWatch(uintptr_t context) override;
   MojoResult WriteMessage(std::unique_ptr<MessageForTransit> message,
                           MojoWriteMessageFlags flags) override;
   MojoResult ReadMessage(std::unique_ptr<MessageForTransit>* message,
@@ -61,12 +57,10 @@
                          MojoReadMessageFlags flags,
                          bool read_any_size) override;
   HandleSignalsState GetHandleSignalsState() const override;
-  MojoResult AddAwakable(Awakable* awakable,
-                         MojoHandleSignals signals,
-                         uintptr_t context,
-                         HandleSignalsState* signals_state) override;
-  void RemoveAwakable(Awakable* awakable,
-                      HandleSignalsState* signals_state) override;
+  MojoResult AddWatcherRef(const scoped_refptr<WatcherDispatcher>& watcher,
+                           uintptr_t context) override;
+  MojoResult RemoveWatcherRef(WatcherDispatcher* watcher,
+                              uintptr_t context) override;
   void StartSerialize(uint32_t* num_bytes,
                       uint32_t* num_ports,
                       uint32_t* num_handles) override;
@@ -110,7 +104,7 @@
 
   bool port_transferred_ = false;
   AtomicFlag port_closed_;
-  AwakableList awakables_;
+  WatcherSet watchers_;
 
   DISALLOW_COPY_AND_ASSIGN(MessagePipeDispatcher);
 };
diff --git a/mojo/edk/system/message_pipe_perftest.cc b/mojo/edk/system/message_pipe_perftest.cc
index a6ce370..9866c47 100644
--- a/mojo/edk/system/message_pipe_perftest.cc
+++ b/mojo/edk/system/message_pipe_perftest.cc
@@ -47,8 +47,7 @@
                               0, MOJO_WRITE_MESSAGE_FLAG_NONE),
              MOJO_RESULT_OK);
     HandleSignalsState hss;
-    CHECK_EQ(MojoWait(mp, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE,
-                      &hss),
+    CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss),
              MOJO_RESULT_OK);
     uint32_t read_buffer_size = static_cast<uint32_t>(read_buffer_.size());
     CHECK_EQ(MojoReadMessage(mp, &read_buffer_[0], &read_buffer_size, nullptr,
@@ -98,9 +97,7 @@
     while (true) {
       // Wait for our end of the message pipe to be readable.
       HandleSignalsState hss;
-      MojoResult result =
-          MojoWait(mp, MOJO_HANDLE_SIGNAL_READABLE,
-                   MOJO_DEADLINE_INDEFINITE, &hss);
+      MojoResult result = WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss);
       if (result != MOJO_RESULT_OK) {
         rv = result;
         break;
diff --git a/mojo/edk/system/message_pipe_unittest.cc b/mojo/edk/system/message_pipe_unittest.cc
index 8f48950..e6f1ff6 100644
--- a/mojo/edk/system/message_pipe_unittest.cc
+++ b/mojo/edk/system/message_pipe_unittest.cc
@@ -100,8 +100,8 @@
   ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0])));
 
   MojoHandleSignalsState state;
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &state));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state));
 
   // Read from port 0.
   buffer[0] = 123;
@@ -124,8 +124,8 @@
   buffer[1] = 0;
   ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, sizeof(buffer[0])));
 
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &state));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &state));
 
   // Read from port 1 with buffer size 0 (should get the size of next message).
   // Also test that giving a null buffer is okay when the buffer size is 0.
@@ -154,8 +154,8 @@
   ASSERT_EQ(123456789, buffer[0]);
   ASSERT_EQ(456, buffer[1]);
 
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &state));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &state));
 
   // Read again from port 1.
   buffer[0] = 123;
@@ -179,8 +179,8 @@
   MojoClose(pipe0_);
   pipe0_ = MOJO_HANDLE_INVALID;
 
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                                     MOJO_DEADLINE_INDEFINITE, &state));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state));
 
   // Try to write from port 1 (to port 0).
   buffer[0] = 456789012;
@@ -215,8 +215,8 @@
   }
 
   MojoHandleSignalsState state;
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &state));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state));
 
   // Port 0 shouldn't be empty.
   buffer_size = 0;
@@ -241,8 +241,8 @@
   ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0])));
 
   MojoHandleSignalsState state;
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &state));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state));
 
   // Read/discard from port 0 (no buffer); get size.
   buffer_size = 0;
@@ -261,8 +261,8 @@
   ASSERT_EQ(MOJO_RESULT_OK,
             WriteMessage(pipe1_, buffer, sizeof(buffer[0])));
 
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &state));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state));
 
   // Read from port 0 (buffer big enough).
   buffer[0] = 123;
@@ -283,8 +283,8 @@
   buffer[1] = 0;
   ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0])));
 
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &state));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state));
 
   // Read/discard from port 0 (buffer too small); get size.
   buffer_size = 1;
@@ -302,8 +302,8 @@
   buffer[1] = 0;
   ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0])));
 
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &state));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state));
 
   // Discard from port 0.
   buffer_size = 1;
@@ -323,41 +323,27 @@
   const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer));
   uint32_t buffer_size;
 
-  // Always writable (until the other port is closed).
-  hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_WRITABLE, 0,
-                                     &hss));
+  // Always writable (until the other port is closed). Not yet readable. Peer
+  // not closed.
+  hss = GetSignalsState(pipe0_);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
   ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
   hss = MojoHandleSignalsState();
 
-  // Not yet readable.
-  ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
-            MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
-  ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
-
-  // The peer is not closed.
-  hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
-            MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, 0, &hss));
-  ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
-  ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
-
   // Write from port 0 (to port 1), to make port 1 readable.
   buffer[0] = 123456789;
   ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, kBufferSize));
 
   // Port 1 should already be readable now.
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &hss));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
             hss.satisfied_signals);
   ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
   // ... and still writable.
   hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_WRITABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &hss));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
             hss.satisfied_signals);
   ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
@@ -368,8 +354,8 @@
 
   // Port 1 should be signaled with peer closed.
   hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                                     MOJO_DEADLINE_INDEFINITE, &hss));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
             hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
@@ -379,8 +365,7 @@
   hss = MojoHandleSignalsState();
 
   ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_WRITABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
             hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
@@ -388,8 +373,8 @@
 
   // But it should still be readable.
   hss = MojoHandleSignalsState();
-  ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &hss));
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
             hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
@@ -404,8 +389,7 @@
   // Now port 1 should no longer be readable.
   hss = MojoHandleSignalsState();
   ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &hss));
+            WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
   ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
 }
@@ -453,9 +437,7 @@
   EXPECT_EQ(MOJO_RESULT_OK,
             MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE));
 
-  EXPECT_EQ(MOJO_RESULT_OK,
-            MojoWait(b, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE,
-                     nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE));
   uint32_t num_bytes = 0;
   uint32_t num_handles = 0;
   EXPECT_EQ(MOJO_RESULT_OK,
@@ -489,8 +471,7 @@
     WriteMessageWithHandles(h, "", handles, kPingPongHandlesPerIteration);
   }
 
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE));
   char msg[4];
   uint32_t num_bytes = 4;
   EXPECT_EQ(MOJO_RESULT_OK, ReadMessage(h, msg, &num_bytes));
@@ -675,9 +656,7 @@
   EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(b));
   EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c));
 
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWait(d, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                                     MOJO_DEADLINE_INDEFINITE, nullptr));
-
+  EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(d, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
   EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
 }
 
@@ -700,9 +679,7 @@
   EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c));
 
   EXPECT_EQ(kTestMessage, ReadMessage(d));
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWait(d, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                                     MOJO_DEADLINE_INDEFINITE, nullptr));
-
+  EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(d, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
   EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
 }
 
diff --git a/mojo/edk/system/multiprocess_message_pipe_unittest.cc b/mojo/edk/system/multiprocess_message_pipe_unittest.cc
index 498980c..37248d1 100644
--- a/mojo/edk/system/multiprocess_message_pipe_unittest.cc
+++ b/mojo/edk/system/multiprocess_message_pipe_unittest.cc
@@ -31,7 +31,8 @@
 #include "mojo/public/c/system/buffer.h"
 #include "mojo/public/c/system/functions.h"
 #include "mojo/public/c/system/types.h"
-#include "mojo/public/cpp/system/watcher.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "mojo/public/cpp/system/wait.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 
@@ -91,9 +92,7 @@
   for (;; rv = (rv + 1) % 100) {
     // Wait for our end of the message pipe to be readable.
     HandleSignalsState hss;
-    MojoResult result =
-        MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE,
-                 MOJO_DEADLINE_INDEFINITE, &hss);
+    MojoResult result = WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss);
     if (result != MOJO_RESULT_OK) {
       // It was closed, probably.
       CHECK_EQ(result, MOJO_RESULT_FAILED_PRECONDITION);
@@ -139,8 +138,7 @@
 
     HandleSignalsState hss;
     ASSERT_EQ(MOJO_RESULT_OK,
-              MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE,
-                       MOJO_DEADLINE_INDEFINITE, &hss));
+              WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss));
     // The child may or may not have closed its end of the message pipe and died
     // (and we may or may not know it yet), so our end may or may not appear as
     // writable.
@@ -179,8 +177,7 @@
     for (size_t i = 0; i < kNumMessages; i++) {
       HandleSignalsState hss;
       ASSERT_EQ(MOJO_RESULT_OK,
-                MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE,
-                         MOJO_DEADLINE_INDEFINITE, &hss));
+                WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss));
       // The child may or may not have closed its end of the message pipe and
       // died (and we may or may not know it yet), so our end may or may not
       // appear as writable.
@@ -208,8 +205,7 @@
     // "quitquitquit").
     HandleSignalsState hss;
     ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-              MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE,
-                       MOJO_DEADLINE_INDEFINITE, &hss));
+              WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss));
     ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
     ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
   END_CHILD_AND_EXPECT_EXIT_CODE(static_cast<int>(kNumMessages % 100));
@@ -219,8 +215,7 @@
                              h) {
   // Wait for the first message from our parent.
   HandleSignalsState hss;
-  CHECK_EQ(MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE,
-               MOJO_DEADLINE_INDEFINITE, &hss),
+  CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss),
            MOJO_RESULT_OK);
   // In this test, the parent definitely doesn't close its end of the message
   // pipe before we do.
@@ -265,8 +260,7 @@
 
   // Now wait for our parent to send us a message.
   hss = HandleSignalsState();
-  CHECK_EQ(MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE,
-                    MOJO_DEADLINE_INDEFINITE, &hss),
+  CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss),
            MOJO_RESULT_OK);
   CHECK_EQ(hss.satisfied_signals,
            MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
@@ -322,8 +316,7 @@
     // Wait for a message from the child.
     HandleSignalsState hss;
     ASSERT_EQ(MOJO_RESULT_OK,
-              MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE,
-                       MOJO_DEADLINE_INDEFINITE, &hss));
+              WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss));
     EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
     EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE));
 
@@ -359,8 +352,7 @@
     // Wait for |h| to become readable, which should fail.
     hss = HandleSignalsState();
     ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-              MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE,
-                       MOJO_DEADLINE_INDEFINITE, &hss));
+              WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss));
     ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
     ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
   END_CHILD()
@@ -369,8 +361,7 @@
 DEFINE_TEST_CLIENT_WITH_PIPE(CheckPlatformHandleFile,
                              MultiprocessMessagePipeTest, h) {
   HandleSignalsState hss;
-  CHECK_EQ(MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE,
-                    MOJO_DEADLINE_INDEFINITE, &hss),
+  CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss),
            MOJO_RESULT_OK);
   CHECK_EQ(hss.satisfied_signals,
            MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
@@ -455,8 +446,7 @@
     // Wait for it to become readable, which should fail.
     HandleSignalsState hss;
     ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-              MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE,
-                       MOJO_DEADLINE_INDEFINITE, &hss));
+              WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss));
     ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
     ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
   END_CHILD()
@@ -474,8 +464,7 @@
 DEFINE_TEST_CLIENT_WITH_PIPE(CheckMessagePipe, MultiprocessMessagePipeTest, h) {
   // Wait for the first message from our parent.
   HandleSignalsState hss;
-  CHECK_EQ(MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE,
-                    MOJO_DEADLINE_INDEFINITE, &hss),
+  CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss),
            MOJO_RESULT_OK);
   // In this test, the parent definitely doesn't close its end of the message
   // pipe before we do.
@@ -495,8 +484,7 @@
   CHECK_EQ(num_handlers, 1u);
 
   // Read data from the received message pipe.
-  CHECK_EQ(MojoWait(handles[0], MOJO_HANDLE_SIGNAL_READABLE,
-               MOJO_DEADLINE_INDEFINITE, &hss),
+  CHECK_EQ(WaitForSignals(handles[0], MOJO_HANDLE_SIGNAL_READABLE, &hss),
            MOJO_RESULT_OK);
   CHECK_EQ(hss.satisfied_signals,
            MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
@@ -547,8 +535,7 @@
     // Wait for a message from the child.
     HandleSignalsState hss;
     ASSERT_EQ(MOJO_RESULT_OK,
-              MojoWait(mp1, MOJO_HANDLE_SIGNAL_READABLE,
-                       MOJO_DEADLINE_INDEFINITE, &hss));
+              WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &hss));
     EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
     EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE));
 
@@ -585,8 +572,7 @@
     // Wait for a message from the child.
     HandleSignalsState hss;
     ASSERT_EQ(MOJO_RESULT_OK,
-              MojoWait(mp1, MOJO_HANDLE_SIGNAL_READABLE,
-                       MOJO_DEADLINE_INDEFINITE, &hss));
+              WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &hss));
     EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
     EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE));
 
@@ -604,8 +590,7 @@
 DEFINE_TEST_CLIENT_WITH_PIPE(DataPipeConsumer, MultiprocessMessagePipeTest, h) {
   // Wait for the first message from our parent.
   HandleSignalsState hss;
-  CHECK_EQ(MojoWait(h, MOJO_HANDLE_SIGNAL_READABLE,
-                    MOJO_DEADLINE_INDEFINITE, &hss),
+  CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss),
            MOJO_RESULT_OK);
   // In this test, the parent definitely doesn't close its end of the message
   // pipe before we do.
@@ -625,8 +610,7 @@
   CHECK_EQ(num_handlers, 1u);
 
   // Read data from the received message pipe.
-  CHECK_EQ(MojoWait(handles[0], MOJO_HANDLE_SIGNAL_READABLE,
-               MOJO_DEADLINE_INDEFINITE, &hss),
+  CHECK_EQ(WaitForSignals(handles[0], MOJO_HANDLE_SIGNAL_READABLE, &hss),
            MOJO_RESULT_OK);
   CHECK_EQ(hss.satisfied_signals,
            MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
@@ -677,8 +661,7 @@
     // Wait for a message from the child.
     HandleSignalsState hss;
     ASSERT_EQ(MOJO_RESULT_OK,
-              MojoWait(mp1, MOJO_HANDLE_SIGNAL_READABLE,
-                       MOJO_DEADLINE_INDEFINITE, &hss));
+              WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &hss));
     EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
     EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE));
 
@@ -795,16 +778,15 @@
   MojoHandle p;
   ReadMessageWithHandles(h, &p, 1);
 
-  std::vector<MojoHandle> handles(2);
-  handles[0] = h;
-  handles[1] = p;
+  std::vector<Handle> handles(2);
+  handles[0] = Handle(h);
+  handles[1] = Handle(p);
   std::vector<MojoHandleSignals> signals(2, MOJO_HANDLE_SIGNAL_READABLE);
   for (;;) {
-    uint32_t index;
-    CHECK_EQ(MojoWaitMany(handles.data(), signals.data(),
-                          static_cast<uint32_t>(handles.size()),
-                          MOJO_DEADLINE_INDEFINITE, &index, nullptr),
-             MOJO_RESULT_OK);
+    size_t index;
+    CHECK_EQ(
+        mojo::WaitMany(handles.data(), signals.data(), handles.size(), &index),
+        MOJO_RESULT_OK);
     DCHECK_LE(index, handles.size());
     if (index == 0) {
       // If data is available on the first pipe, it should be an exit command.
@@ -814,16 +796,16 @@
       // If the second pipe, it should be a new handle requesting echo service.
       MojoHandle echo_request;
       ReadMessageWithHandles(p, &echo_request, 1);
-      handles.push_back(echo_request);
+      handles.push_back(Handle(echo_request));
       signals.push_back(MOJO_HANDLE_SIGNAL_READABLE);
     } else {
       // Otherwise it was one of our established echo pipes. Echo!
-      WriteMessage(handles[index], ReadMessage(handles[index]));
+      WriteMessage(handles[index].value(), ReadMessage(handles[index].value()));
     }
   }
 
   for (size_t i = 1; i < handles.size(); ++i)
-    CloseHandle(handles[i]);
+    CloseHandle(handles[i].value());
 
   return 0;
 }
@@ -924,9 +906,7 @@
   EXPECT_EQ("bye", ReadMessage(p0));
 
   // We should still be able to observe peer closure from the other end.
-  EXPECT_EQ(MOJO_RESULT_OK,
-            MojoWait(p0, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                     MOJO_DEADLINE_INDEFINITE, nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(p0, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
 }
 
 // Parses commands from the parent pipe and does whatever it's asked to do.
@@ -1110,10 +1090,7 @@
                                   MultiprocessMessagePipeTest, h) {
   MojoHandle p;
   EXPECT_EQ("foo", ReadMessageWithHandles(h, &p, 1));
-
-  auto result = MojoWait(p, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                         MOJO_DEADLINE_INDEFINITE, nullptr);
-  EXPECT_EQ(MOJO_RESULT_OK, result);
+  EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(p, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
 }
 
 TEST_P(MultiprocessMessagePipeTestWithPeerSupport, SendPipeThenClosePeer) {
@@ -1159,9 +1136,8 @@
             ReadMessageWithHandles(application_client, &service_client, 1));
 
   // Wait for the service client to signal closure.
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWait(service_client,
-                                     MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                                     MOJO_DEADLINE_INDEFINITE, nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(service_client, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
 
   EXPECT_EQ(MOJO_RESULT_OK, MojoClose(service_client));
   EXPECT_EQ(MOJO_RESULT_OK, MojoClose(application_client));
@@ -1207,8 +1183,7 @@
   EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
 
   // We should be able to detect peer closure on |a|.
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWait(a, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                                     MOJO_DEADLINE_INDEFINITE, nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(a, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
 }
 
 DEFINE_TEST_CLIENT_TEST_WITH_PIPE(WriteCloseSendPeerClient,
@@ -1251,8 +1226,8 @@
     EXPECT_EQ("qux", ReadMessage(p));
 
     // Expect to have peer closure signaled.
-    EXPECT_EQ(MOJO_RESULT_OK, MojoWait(p, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                                       MOJO_DEADLINE_INDEFINITE, nullptr));
+    EXPECT_EQ(MOJO_RESULT_OK,
+              WaitForSignals(p, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
 
     WriteMessage(h, "quit");
   END_CHILD()
@@ -1265,21 +1240,22 @@
   MojoHandle handles[4];
   EXPECT_EQ("o_O", ReadMessageWithHandles(parent, handles, 4));
 
-  // Wait on handle 0 using MojoWait.
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWait(handles[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                                     MOJO_DEADLINE_INDEFINITE, nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            WaitForSignals(handles[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED));
 
   base::MessageLoop message_loop;
 
-  // Wait on handle 1 using a Watcher.
+  // Wait on handle 1 using a SimpleWatcher.
   {
     base::RunLoop run_loop;
-    Watcher watcher(FROM_HERE);
-    watcher.Start(Handle(handles[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                  base::Bind([] (base::RunLoop* loop, MojoResult result) {
-                    EXPECT_EQ(MOJO_RESULT_OK, result);
-                    loop->Quit();
-                  }, &run_loop));
+    SimpleWatcher watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+    watcher.Watch(Handle(handles[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+                  base::Bind(
+                      [](base::RunLoop* loop, MojoResult result) {
+                        EXPECT_EQ(MOJO_RESULT_OK, result);
+                        loop->Quit();
+                      },
+                      &run_loop));
     run_loop.Run();
   }
 
@@ -1347,8 +1323,7 @@
       WriteMessageWithHandles(child2, "hi", &d, 1);
 
       // Read a message from the pipe we sent to child1 and flag it as bad.
-      ASSERT_EQ(MOJO_RESULT_OK, MojoWait(a, MOJO_HANDLE_SIGNAL_READABLE,
-                                         MOJO_DEADLINE_INDEFINITE, nullptr));
+      ASSERT_EQ(MOJO_RESULT_OK, WaitForSignals(a, MOJO_HANDLE_SIGNAL_READABLE));
       uint32_t num_bytes = 0;
       MojoMessageHandle message;
       ASSERT_EQ(MOJO_RESULT_OK,
@@ -1360,8 +1335,7 @@
       EXPECT_EQ(MOJO_RESULT_OK, MojoFreeMessage(message));
 
       // Read a message from the pipe we sent to child2 and flag it as bad.
-      ASSERT_EQ(MOJO_RESULT_OK, MojoWait(c, MOJO_HANDLE_SIGNAL_READABLE,
-                                         MOJO_DEADLINE_INDEFINITE, nullptr));
+      ASSERT_EQ(MOJO_RESULT_OK, WaitForSignals(c, MOJO_HANDLE_SIGNAL_READABLE));
       ASSERT_EQ(MOJO_RESULT_OK,
                 MojoReadMessageNew(c, &message, &num_bytes, nullptr, 0,
                                    MOJO_READ_MESSAGE_FLAG_NONE));
diff --git a/mojo/edk/system/node_controller.cc b/mojo/edk/system/node_controller.cc
index 7bdb571..73b16b1 100644
--- a/mojo/edk/system/node_controller.cc
+++ b/mojo/edk/system/node_controller.cc
@@ -694,10 +694,17 @@
     return;
   }
 
-  // If we don't know who the peer is, queue the message for delivery. If this
-  // is the first message queued for the peer, we also ask the broker to
-  // introduce us to them.
+  // If we don't know who the peer is and we are the broker, we can only assume
+  // the peer is invalid, i.e., it's either a junk name or has already been
+  // disconnected.
+  scoped_refptr<NodeChannel> broker = GetBrokerChannel();
+  if (!broker) {
+    DVLOG(1) << "Dropping message for unknown peer: " << name;
+    return;
+  }
 
+  // If we aren't the broker, assume we just need to be introduced and queue
+  // until that can be either confirmed or denied by the broker.
   bool needs_introduction = false;
   {
     base::AutoLock lock(peers_lock_);
@@ -705,15 +712,8 @@
     needs_introduction = queue.empty();
     queue.emplace(std::move(channel_message));
   }
-
-  if (needs_introduction) {
-    scoped_refptr<NodeChannel> broker = GetBrokerChannel();
-    if (!broker) {
-      DVLOG(1) << "Dropping message for unknown peer: " << name;
-      return;
-    }
+  if (needs_introduction)
     broker->RequestIntroduction(name);
-  }
 }
 
 void NodeController::AcceptIncomingMessages() {
@@ -935,6 +935,16 @@
     return;
   }
 
+  {
+    base::AutoLock lock(reserved_ports_lock_);
+    auto it = pending_child_tokens_.find(from_node);
+    if (it != pending_child_tokens_.end()) {
+      std::string token = std::move(it->second);
+      pending_child_tokens_.erase(it);
+      pending_child_tokens_[child_name] = std::move(token);
+    }
+  }
+
   scoped_refptr<NodeChannel> channel = it->second;
   pending_children_.erase(it);
 
@@ -1155,6 +1165,7 @@
       return;
     }
     local_port = it->second.port;
+    reserved_ports_.erase(it);
   }
 
   int rv = node_->MergePorts(local_port, from_node, connector_port_name);
diff --git a/mojo/edk/system/ports/BUILD.gn b/mojo/edk/system/ports/BUILD.gn
index 37b2548..5c82761 100644
--- a/mojo/edk/system/ports/BUILD.gn
+++ b/mojo/edk/system/ports/BUILD.gn
@@ -21,6 +21,7 @@
     "port.cc",
     "port.h",
     "port_ref.cc",
+    "port_ref.h",
     "user_data.h",
   ]
 
diff --git a/mojo/edk/system/request_context.cc b/mojo/edk/system/request_context.cc
index 6370ab1..5de65d7 100644
--- a/mojo/edk/system/request_context.cc
+++ b/mojo/edk/system/request_context.cc
@@ -36,27 +36,34 @@
     // since we're starting over at the bottom of the stack.
     tls_context_->Set(nullptr);
 
-    MojoWatchNotificationFlags flags = MOJO_WATCH_NOTIFICATION_FLAG_NONE;
+    MojoWatcherNotificationFlags flags = MOJO_WATCHER_NOTIFICATION_FLAG_NONE;
     if (source_ == Source::SYSTEM)
-      flags |= MOJO_WATCH_NOTIFICATION_FLAG_FROM_SYSTEM;
+      flags |= MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM;
 
-    // We run all cancellation finalizers first. This is necessary because it's
-    // possible that one of the cancelled watchers has other pending finalizers
+    // We send all cancellation notifications first. This is necessary because
+    // it's possible that cancelled watches have other pending notifications
     // attached to this RequestContext.
     //
-    // From the application's perspective the watch has already been cancelled,
-    // so we have to honor our contract which guarantees no more notifications.
-    for (const scoped_refptr<Watcher>& watcher :
-            watch_cancel_finalizers_.container())
-      watcher->Cancel();
+    // From the application's perspective the watch is cancelled as soon as this
+    // notification is received, and dispatching the cancellation notification
+    // updates some internal Watch state to ensure no further notifications
+    // fire. Because notifications on a single Watch are mutually exclusive,
+    // this is sufficient to guarantee that MOJO_RESULT_CANCELLED is the last
+    // notification received; which is the guarantee the API makes.
+    for (const scoped_refptr<Watch>& watch :
+         watch_cancel_finalizers_.container()) {
+      static const HandleSignalsState closed_state = {0, 0};
+
+      // Establish a new RequestContext to capture and run any new notifications
+      // triggered by the callback invocation.
+      RequestContext inner_context(source_);
+      watch->InvokeCallback(MOJO_RESULT_CANCELLED, closed_state, flags);
+    }
 
     for (const WatchNotifyFinalizer& watch :
-        watch_notify_finalizers_.container()) {
-      // Establish a new request context for the extent of each callback to
-      // ensure that they don't themselves invoke callbacks while holding a
-      // watcher lock.
-      RequestContext request_context(source_);
-      watch.watcher->MaybeInvokeCallback(watch.result, watch.state, flags);
+         watch_notify_finalizers_.container()) {
+      RequestContext inner_context(source_);
+      watch.watch->InvokeCallback(watch.result, watch.state, flags);
     }
   } else {
     // It should be impossible for nested contexts to have finalizers.
@@ -71,18 +78,17 @@
   return g_current_context.Pointer()->Get();
 }
 
-void RequestContext::AddWatchNotifyFinalizer(
-    scoped_refptr<Watcher> watcher,
-    MojoResult result,
-    const HandleSignalsState& state) {
+void RequestContext::AddWatchNotifyFinalizer(scoped_refptr<Watch> watch,
+                                             MojoResult result,
+                                             const HandleSignalsState& state) {
   DCHECK(IsCurrent());
   watch_notify_finalizers_->push_back(
-      WatchNotifyFinalizer(std::move(watcher), result, state));
+      WatchNotifyFinalizer(std::move(watch), result, state));
 }
 
-void RequestContext::AddWatchCancelFinalizer(scoped_refptr<Watcher> watcher) {
+void RequestContext::AddWatchCancelFinalizer(scoped_refptr<Watch> watch) {
   DCHECK(IsCurrent());
-  watch_cancel_finalizers_->push_back(std::move(watcher));
+  watch_cancel_finalizers_->push_back(std::move(watch));
 }
 
 bool RequestContext::IsCurrent() const {
@@ -90,10 +96,10 @@
 }
 
 RequestContext::WatchNotifyFinalizer::WatchNotifyFinalizer(
-    scoped_refptr<Watcher> watcher,
+    scoped_refptr<Watch> watch,
     MojoResult result,
     const HandleSignalsState& state)
-    : watcher(std::move(watcher)), result(result), state(state) {}
+    : watch(std::move(watch)), result(result), state(state) {}
 
 RequestContext::WatchNotifyFinalizer::WatchNotifyFinalizer(
     const WatchNotifyFinalizer& other) = default;
diff --git a/mojo/edk/system/request_context.h b/mojo/edk/system/request_context.h
index 7aa0e69..d1f43bd 100644
--- a/mojo/edk/system/request_context.h
+++ b/mojo/edk/system/request_context.h
@@ -9,7 +9,7 @@
 #include "base/macros.h"
 #include "mojo/edk/system/handle_signals_state.h"
 #include "mojo/edk/system/system_impl_export.h"
-#include "mojo/edk/system/watcher.h"
+#include "mojo/edk/system/watch.h"
 
 namespace base {
 template<typename T> class ThreadLocalPointer;
@@ -49,43 +49,44 @@
 
   // Adds a finalizer to this RequestContext corresponding to a watch callback
   // which should be triggered in response to some handle state change. If
-  // the Watcher hasn't been cancelled by the time this RequestContext is
+  // the WatcherDispatcher hasn't been closed by the time this RequestContext is
   // destroyed, its WatchCallback will be invoked with |result| and |state|
   // arguments.
-  void AddWatchNotifyFinalizer(scoped_refptr<Watcher> watcher,
+  void AddWatchNotifyFinalizer(scoped_refptr<Watch> watch,
                                MojoResult result,
                                const HandleSignalsState& state);
 
-  // Adds a finalizer to this RequestContext which cancels a watch.
-  void AddWatchCancelFinalizer(scoped_refptr<Watcher> watcher);
+  // Adds a finalizer to this RequestContext corresponding to a watch callback
+  // which should be triggered to notify of watch cancellation. This appends to
+  // a separate finalizer list from AddWatchNotifyFinalizer, as pending
+  // cancellations must always preempt other pending notifications.
+  void AddWatchCancelFinalizer(scoped_refptr<Watch> watch);
 
  private:
   // Is this request context the current one?
   bool IsCurrent() const;
 
   struct WatchNotifyFinalizer {
-    WatchNotifyFinalizer(scoped_refptr<Watcher> watcher,
+    WatchNotifyFinalizer(scoped_refptr<Watch> watch,
                          MojoResult result,
                          const HandleSignalsState& state);
     WatchNotifyFinalizer(const WatchNotifyFinalizer& other);
     ~WatchNotifyFinalizer();
 
-    scoped_refptr<Watcher> watcher;
+    scoped_refptr<Watch> watch;
     MojoResult result;
     HandleSignalsState state;
   };
 
-  // Chosen by fair dice roll.
-  //
-  // TODO: We should measure the distribution of # of finalizers typical to
-  // any RequestContext and adjust this number accordingly. It's probably
-  // almost always 1, but 4 seems like a harmless upper bound for now.
-  static const size_t kStaticWatchFinalizersCapacity = 4;
+  // NOTE: This upper bound was chosen somewhat arbitrarily after observing some
+  // rare worst-case behavior in Chrome. A vast majority of RequestContexts only
+  // ever accumulate 0 or 1 finalizers.
+  static const size_t kStaticWatchFinalizersCapacity = 8;
 
   using WatchNotifyFinalizerList =
       base::StackVector<WatchNotifyFinalizer, kStaticWatchFinalizersCapacity>;
   using WatchCancelFinalizerList =
-      base::StackVector<scoped_refptr<Watcher>, kStaticWatchFinalizersCapacity>;
+      base::StackVector<scoped_refptr<Watch>, kStaticWatchFinalizersCapacity>;
 
   const Source source_;
 
diff --git a/mojo/edk/system/signals_unittest.cc b/mojo/edk/system/signals_unittest.cc
new file mode 100644
index 0000000..e8b0cd1
--- /dev/null
+++ b/mojo/edk/system/signals_unittest.cc
@@ -0,0 +1,76 @@
+// Copyright 2017 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 "mojo/edk/test/mojo_test_base.h"
+#include "mojo/public/c/system/buffer.h"
+#include "mojo/public/c/system/data_pipe.h"
+#include "mojo/public/c/system/functions.h"
+#include "mojo/public/c/system/message_pipe.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+using SignalsTest = test::MojoTestBase;
+
+TEST_F(SignalsTest, QueryInvalidArguments) {
+  MojoHandleSignalsState state = {0, 0};
+  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+            MojoQueryHandleSignalsState(MOJO_HANDLE_INVALID, &state));
+
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+            MojoQueryHandleSignalsState(a, nullptr));
+}
+
+TEST_F(SignalsTest, QueryMessagePipeSignals) {
+  MojoHandleSignalsState state = {0, 0};
+
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(a, &state));
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals);
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+                MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+            state.satisfiable_signals);
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state));
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals);
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+                MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+            state.satisfiable_signals);
+
+  WriteMessage(a, "ok");
+  EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE));
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state));
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+            state.satisfied_signals);
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+                MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+            state.satisfiable_signals);
+
+  EXPECT_EQ("ok", ReadMessage(b));
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state));
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals);
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+                MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+            state.satisfiable_signals);
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+
+  EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state));
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals);
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals);
+}
+
+}  // namespace
+}  // namespace edk
+}  // namespace mojo
diff --git a/mojo/edk/system/wait_set_dispatcher.cc b/mojo/edk/system/wait_set_dispatcher.cc
deleted file mode 100644
index edca415..0000000
--- a/mojo/edk/system/wait_set_dispatcher.cc
+++ /dev/null
@@ -1,312 +0,0 @@
-// Copyright 2015 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 "mojo/edk/system/wait_set_dispatcher.h"
-
-#include <stdint.h>
-
-#include <algorithm>
-#include <utility>
-
-#include "base/logging.h"
-#include "mojo/edk/system/awakable.h"
-
-namespace mojo {
-namespace edk {
-
-class WaitSetDispatcher::Waiter final : public Awakable {
- public:
-  explicit Waiter(WaitSetDispatcher* dispatcher) : dispatcher_(dispatcher) {}
-  ~Waiter() {}
-
-  // |Awakable| implementation.
-  bool Awake(MojoResult result, uintptr_t context) override {
-    // Note: This is called with various Mojo locks held.
-    dispatcher_->WakeDispatcher(result, context);
-    // Removes |this| from the dispatcher's list of waiters.
-    return false;
-  }
-
- private:
-  WaitSetDispatcher* const dispatcher_;
-};
-
-WaitSetDispatcher::WaitState::WaitState() {}
-
-WaitSetDispatcher::WaitState::WaitState(const WaitState& other) = default;
-
-WaitSetDispatcher::WaitState::~WaitState() {}
-
-WaitSetDispatcher::WaitSetDispatcher()
-    : waiter_(new WaitSetDispatcher::Waiter(this)) {}
-
-Dispatcher::Type WaitSetDispatcher::GetType() const {
-  return Type::WAIT_SET;
-}
-
-MojoResult WaitSetDispatcher::Close() {
-  base::AutoLock lock(lock_);
-
-  if (is_closed_)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-  is_closed_ = true;
-
-  {
-    base::AutoLock locker(awakable_lock_);
-    awakable_list_.CancelAll();
-  }
-
-  for (const auto& entry : waiting_dispatchers_)
-    entry.second.dispatcher->RemoveAwakable(waiter_.get(), nullptr);
-  waiting_dispatchers_.clear();
-
-  base::AutoLock locker(awoken_lock_);
-  awoken_queue_.clear();
-  processed_dispatchers_.clear();
-
-  return MOJO_RESULT_OK;
-}
-
-MojoResult WaitSetDispatcher::AddWaitingDispatcher(
-    const scoped_refptr<Dispatcher>& dispatcher,
-    MojoHandleSignals signals,
-    uintptr_t context) {
-  if (dispatcher == this)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  base::AutoLock lock(lock_);
-
-  if (is_closed_)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  uintptr_t dispatcher_handle = reinterpret_cast<uintptr_t>(dispatcher.get());
-  auto it = waiting_dispatchers_.find(dispatcher_handle);
-  if (it != waiting_dispatchers_.end()) {
-    return MOJO_RESULT_ALREADY_EXISTS;
-  }
-
-  const MojoResult result = dispatcher->AddAwakable(waiter_.get(), signals,
-                                                    dispatcher_handle, nullptr);
-  if (result == MOJO_RESULT_INVALID_ARGUMENT) {
-    // Dispatcher is closed.
-    return result;
-  } else if (result != MOJO_RESULT_OK) {
-    WakeDispatcher(result, dispatcher_handle);
-  }
-
-  WaitState state;
-  state.dispatcher = dispatcher;
-  state.context = context;
-  state.signals = signals;
-  bool inserted = waiting_dispatchers_.insert(
-      std::make_pair(dispatcher_handle, state)).second;
-  DCHECK(inserted);
-
-  return MOJO_RESULT_OK;
-}
-
-MojoResult WaitSetDispatcher::RemoveWaitingDispatcher(
-    const scoped_refptr<Dispatcher>& dispatcher) {
-  uintptr_t dispatcher_handle = reinterpret_cast<uintptr_t>(dispatcher.get());
-
-  base::AutoLock lock(lock_);
-  if (is_closed_)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  auto it = waiting_dispatchers_.find(dispatcher_handle);
-  if (it == waiting_dispatchers_.end())
-    return MOJO_RESULT_NOT_FOUND;
-
-  dispatcher->RemoveAwakable(waiter_.get(), nullptr);
-  // At this point, it should not be possible for |waiter_| to be woken with
-  // |dispatcher|.
-  waiting_dispatchers_.erase(it);
-
-  base::AutoLock locker(awoken_lock_);
-  int num_erased = 0;
-  for (auto it = awoken_queue_.begin(); it != awoken_queue_.end();) {
-    if (it->first == dispatcher_handle) {
-      it = awoken_queue_.erase(it);
-      num_erased++;
-    } else {
-      ++it;
-    }
-  }
-  // The dispatcher should only exist in the queue once.
-  DCHECK_LE(num_erased, 1);
-  processed_dispatchers_.erase(
-      std::remove(processed_dispatchers_.begin(), processed_dispatchers_.end(),
-                  dispatcher_handle),
-      processed_dispatchers_.end());
-
-  return MOJO_RESULT_OK;
-}
-
-MojoResult WaitSetDispatcher::GetReadyDispatchers(
-    uint32_t* count,
-    DispatcherVector* dispatchers,
-    MojoResult* results,
-    uintptr_t* contexts) {
-  base::AutoLock lock(lock_);
-
-  if (is_closed_)
-    return MOJO_RESULT_INVALID_ARGUMENT;
-
-  dispatchers->clear();
-
-  // Re-queue any already retrieved dispatchers. These should be the dispatchers
-  // that were returned on the last call to this function. This loop is
-  // necessary to preserve the logically level-triggering behaviour of waiting
-  // in Mojo. In particular, if no action is taken on a signal, that signal
-  // continues to be satisfied, and therefore a |MojoWait()| on that
-  // handle/signal continues to return immediately.
-  std::deque<uintptr_t> pending;
-  {
-    base::AutoLock locker(awoken_lock_);
-    pending.swap(processed_dispatchers_);
-  }
-  for (uintptr_t d : pending) {
-    auto it = waiting_dispatchers_.find(d);
-    // Anything in |processed_dispatchers_| should also be in
-    // |waiting_dispatchers_| since dispatchers are removed from both in
-    // |RemoveWaitingDispatcherImplNoLock()|.
-    DCHECK(it != waiting_dispatchers_.end());
-
-    // |awoken_mutex_| cannot be held here because
-    // |Dispatcher::AddAwakable()| acquires the Dispatcher's mutex. This
-    // mutex is held while running |WakeDispatcher()| below, which needs to
-    // acquire |awoken_mutex_|. Holding |awoken_mutex_| here would result in
-    // a deadlock.
-    const MojoResult result = it->second.dispatcher->AddAwakable(
-        waiter_.get(), it->second.signals, d, nullptr);
-
-    if (result == MOJO_RESULT_INVALID_ARGUMENT) {
-      // Dispatcher is closed. Implicitly remove it from the wait set since
-      // it may be impossible to remove using |MojoRemoveHandle()|.
-      waiting_dispatchers_.erase(it);
-    } else if (result != MOJO_RESULT_OK) {
-      WakeDispatcher(result, d);
-    }
-  }
-
-  const uint32_t max_woken = *count;
-  uint32_t num_woken = 0;
-
-  base::AutoLock locker(awoken_lock_);
-  while (!awoken_queue_.empty() && num_woken < max_woken) {
-    uintptr_t d = awoken_queue_.front().first;
-    MojoResult result = awoken_queue_.front().second;
-    awoken_queue_.pop_front();
-
-    auto it = waiting_dispatchers_.find(d);
-    DCHECK(it != waiting_dispatchers_.end());
-
-    results[num_woken] = result;
-    dispatchers->push_back(it->second.dispatcher);
-    if (contexts)
-      contexts[num_woken] = it->second.context;
-
-    if (result != MOJO_RESULT_CANCELLED) {
-      processed_dispatchers_.push_back(d);
-    } else {
-      // |MOJO_RESULT_CANCELLED| indicates that the dispatcher was closed.
-      // Return it, but also implcitly remove it from the wait set.
-      waiting_dispatchers_.erase(it);
-    }
-
-    num_woken++;
-  }
-
-  *count = num_woken;
-  if (!num_woken)
-    return MOJO_RESULT_SHOULD_WAIT;
-
-  return MOJO_RESULT_OK;
-}
-
-HandleSignalsState WaitSetDispatcher::GetHandleSignalsState() const {
-  base::AutoLock lock(lock_);
-  return GetHandleSignalsStateNoLock();
-}
-
-HandleSignalsState WaitSetDispatcher::GetHandleSignalsStateNoLock() const {
-  lock_.AssertAcquired();
-  if (is_closed_)
-    return HandleSignalsState();
-
-  HandleSignalsState rv;
-  rv.satisfiable_signals = MOJO_HANDLE_SIGNAL_READABLE;
-  base::AutoLock locker(awoken_lock_);
-  if (!awoken_queue_.empty() || !processed_dispatchers_.empty())
-    rv.satisfied_signals = MOJO_HANDLE_SIGNAL_READABLE;
-  return rv;
-}
-
-MojoResult WaitSetDispatcher::AddAwakable(Awakable* awakable,
-                                          MojoHandleSignals signals,
-                                          uintptr_t context,
-                                          HandleSignalsState* signals_state) {
-  base::AutoLock lock(lock_);
-  // |awakable_lock_| is acquired here instead of immediately before adding to
-  // |awakable_list_| because we need to check the signals state and add to
-  // |awakable_list_| as an atomic operation. If the pair isn't atomic, it is
-  // possible for the signals state to change after it is checked, but before
-  // the awakable is added. In that case, the added awakable won't be signalled.
-  base::AutoLock awakable_locker(awakable_lock_);
-  HandleSignalsState state(GetHandleSignalsStateNoLock());
-  if (state.satisfies(signals)) {
-    if (signals_state)
-      *signals_state = state;
-    return MOJO_RESULT_ALREADY_EXISTS;
-  }
-  if (!state.can_satisfy(signals)) {
-    if (signals_state)
-      *signals_state = state;
-    return MOJO_RESULT_FAILED_PRECONDITION;
-  }
-
-  awakable_list_.Add(awakable, signals, context);
-  return MOJO_RESULT_OK;
-}
-
-void WaitSetDispatcher::RemoveAwakable(Awakable* awakable,
-                                       HandleSignalsState* signals_state) {
-  {
-    base::AutoLock locker(awakable_lock_);
-    awakable_list_.Remove(awakable);
-  }
-  if (signals_state)
-    *signals_state = GetHandleSignalsState();
-}
-
-bool WaitSetDispatcher::BeginTransit() {
-  // You can't transfer wait sets!
-  return false;
-}
-
-WaitSetDispatcher::~WaitSetDispatcher() {
-  DCHECK(waiting_dispatchers_.empty());
-  DCHECK(awoken_queue_.empty());
-  DCHECK(processed_dispatchers_.empty());
-}
-
-void WaitSetDispatcher::WakeDispatcher(MojoResult result, uintptr_t context) {
-  {
-    base::AutoLock locker(awoken_lock_);
-
-    if (result == MOJO_RESULT_ALREADY_EXISTS)
-      result = MOJO_RESULT_OK;
-
-    awoken_queue_.push_back(std::make_pair(context, result));
-  }
-
-  base::AutoLock locker(awakable_lock_);
-  HandleSignalsState signals_state;
-  signals_state.satisfiable_signals = MOJO_HANDLE_SIGNAL_READABLE;
-  signals_state.satisfied_signals = MOJO_HANDLE_SIGNAL_READABLE;
-  awakable_list_.AwakeForStateChange(signals_state);
-}
-
-}  // namespace edk
-}  // namespace mojo
diff --git a/mojo/edk/system/wait_set_dispatcher.h b/mojo/edk/system/wait_set_dispatcher.h
deleted file mode 100644
index 619a1be..0000000
--- a/mojo/edk/system/wait_set_dispatcher.h
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright 2015 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 MOJO_EDK_SYSTEM_WAIT_SET_DISPATCHER_H_
-#define MOJO_EDK_SYSTEM_WAIT_SET_DISPATCHER_H_
-
-#include <stdint.h>
-
-#include <deque>
-#include <memory>
-#include <unordered_map>
-#include <utility>
-
-#include "base/macros.h"
-#include "base/synchronization/lock.h"
-#include "mojo/edk/system/awakable_list.h"
-#include "mojo/edk/system/dispatcher.h"
-#include "mojo/edk/system/system_impl_export.h"
-
-namespace mojo {
-namespace edk {
-
-class MOJO_SYSTEM_IMPL_EXPORT WaitSetDispatcher : public Dispatcher {
- public:
-  WaitSetDispatcher();
-
-  // Dispatcher:
-  Type GetType() const override;
-  MojoResult Close() override;
-  MojoResult AddWaitingDispatcher(const scoped_refptr<Dispatcher>& dispatcher,
-                                  MojoHandleSignals signals,
-                                  uintptr_t context) override;
-  MojoResult RemoveWaitingDispatcher(
-      const scoped_refptr<Dispatcher>& dispatcher) override;
-  MojoResult GetReadyDispatchers(uint32_t* count,
-                                 DispatcherVector* dispatchers,
-                                 MojoResult* results,
-                                 uintptr_t* contexts) override;
-  HandleSignalsState GetHandleSignalsState() const override;
-  MojoResult AddAwakable(Awakable* awakable,
-                         MojoHandleSignals signals,
-                         uintptr_t context,
-                         HandleSignalsState* signals_state) override;
-  void RemoveAwakable(Awakable* awakable,
-                      HandleSignalsState* signals_state) override;
-  bool BeginTransit() override;
-
- private:
-  // Internal implementation of Awakable.
-  class Waiter;
-
-  struct WaitState {
-    WaitState();
-    WaitState(const WaitState& other);
-    ~WaitState();
-
-    scoped_refptr<Dispatcher> dispatcher;
-    MojoHandleSignals signals;
-    uintptr_t context;
-  };
-
-  ~WaitSetDispatcher() override;
-
-  HandleSignalsState GetHandleSignalsStateNoLock() const;
-
-  // Signal that the dispatcher indexed by |context| has been woken up with
-  // |result| and is now ready.
-  void WakeDispatcher(MojoResult result, uintptr_t context);
-
-  // Guards |is_closed_|, |waiting_dispatchers_|, and |waiter_|.
-  //
-  // TODO: Consider removing this.
-  mutable base::Lock lock_;
-  bool is_closed_ = false;
-
-  // Map of dispatchers being waited on. Key is a Dispatcher* casted to a
-  // uintptr_t, and should be treated as an opaque value and not casted back.
-  std::unordered_map<uintptr_t, WaitState> waiting_dispatchers_;
-
-  // Separate lock that can be locked without locking |lock_|.
-  mutable base::Lock awoken_lock_;
-  // List of dispatchers that have been woken up. Any dispatcher in this queue
-  // will NOT currently be waited on.
-  std::deque<std::pair<uintptr_t, MojoResult>> awoken_queue_;
-  // List of dispatchers that have been woken up and retrieved.
-  std::deque<uintptr_t> processed_dispatchers_;
-
-  // Separate lock that can be locked without locking |lock_|.
-  base::Lock awakable_lock_;
-  // List of dispatchers being waited on.
-  AwakableList awakable_list_;
-
-  // Waiter used to wait on dispatchers.
-  std::unique_ptr<Waiter> waiter_;
-
-  DISALLOW_COPY_AND_ASSIGN(WaitSetDispatcher);
-};
-
-}  // namespace edk
-}  // namespace mojo
-
-#endif  // MOJO_EDK_SYSTEM_WAIT_SET_DISPATCHER_H_
diff --git a/mojo/edk/system/wait_set_dispatcher_unittest.cc b/mojo/edk/system/wait_set_dispatcher_unittest.cc
deleted file mode 100644
index 42ac865..0000000
--- a/mojo/edk/system/wait_set_dispatcher_unittest.cc
+++ /dev/null
@@ -1,493 +0,0 @@
-// Copyright 2015 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 "mojo/edk/system/wait_set_dispatcher.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <algorithm>
-
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "mojo/edk/embedder/embedder_internal.h"
-#include "mojo/edk/system/core.h"
-#include "mojo/edk/system/message_for_transit.h"
-#include "mojo/edk/system/message_pipe_dispatcher.h"
-#include "mojo/edk/system/request_context.h"
-#include "mojo/edk/system/test_utils.h"
-#include "mojo/edk/system/waiter.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace mojo {
-namespace edk {
-namespace {
-
-class WaitSetDispatcherTest : public ::testing::Test {
- public:
-  WaitSetDispatcherTest() {}
-  ~WaitSetDispatcherTest() override {}
-
-  void SetUp() override {
-    CreateMessagePipe(&dispatcher0_, &dispatcher1_);
-  }
-
-  void TearDown() override {
-    for (auto& d : dispatchers_to_close_)
-      d->Close();
-  }
-
-  MojoResult GetOneReadyDispatcher(
-      const scoped_refptr<WaitSetDispatcher>& wait_set,
-      scoped_refptr<Dispatcher>* ready_dispatcher,
-      uintptr_t* context) {
-    uint32_t count = 1;
-    MojoResult dispatcher_result = MOJO_RESULT_UNKNOWN;
-    DispatcherVector dispatchers;
-    MojoResult result = wait_set->GetReadyDispatchers(
-        &count, &dispatchers, &dispatcher_result, context);
-    if (result == MOJO_RESULT_OK) {
-      CHECK_EQ(1u, dispatchers.size());
-      *ready_dispatcher = dispatchers[0];
-      return dispatcher_result;
-    }
-    return result;
-  }
-
-  void CreateMessagePipe(scoped_refptr<MessagePipeDispatcher>* d0,
-                         scoped_refptr<MessagePipeDispatcher>* d1) {
-    MojoHandle h0, h1;
-    EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(nullptr, &h0, &h1));
-
-    Core* core = mojo::edk::internal::g_core;
-    *d0 = scoped_refptr<MessagePipeDispatcher>(
-        static_cast<MessagePipeDispatcher*>(core->GetDispatcher(h0).get()));
-    *d1 = scoped_refptr<MessagePipeDispatcher>(
-        static_cast<MessagePipeDispatcher*>(core->GetDispatcher(h1).get()));
-    pipe_id_generator_++;
-
-    dispatchers_to_close_.push_back(*d0);
-    dispatchers_to_close_.push_back(*d1);
-  }
-
-  void CloseOnShutdown(const scoped_refptr<Dispatcher>& dispatcher) {
-    dispatchers_to_close_.push_back(dispatcher);
-  }
-
-  void WriteMessage(MessagePipeDispatcher* dispatcher,
-                    const void* bytes,
-                    size_t num_bytes) {
-    Core* core = mojo::edk::internal::g_core;
-    MojoMessageHandle msg;
-    ASSERT_EQ(MOJO_RESULT_OK,
-              core->AllocMessage(static_cast<uint32_t>(num_bytes), nullptr, 0,
-                                 MOJO_ALLOC_MESSAGE_FLAG_NONE, &msg));
-    void* buffer;
-    ASSERT_EQ(MOJO_RESULT_OK, core->GetMessageBuffer(msg, &buffer));
-    memcpy(buffer, bytes, num_bytes);
-
-    std::unique_ptr<MessageForTransit> message(
-        reinterpret_cast<MessageForTransit*>(msg));
-    ASSERT_EQ(MOJO_RESULT_OK,
-              dispatcher->WriteMessage(std::move(message),
-                                       MOJO_WRITE_MESSAGE_FLAG_NONE));
-  }
-
-  void ReadMessage(MessagePipeDispatcher* dispatcher,
-                   void* bytes,
-                   uint32_t* num_bytes) {
-    std::unique_ptr<MessageForTransit> message;
-    ASSERT_EQ(MOJO_RESULT_OK,
-              dispatcher->ReadMessage(&message, num_bytes, nullptr, 0,
-                                      MOJO_READ_MESSAGE_FLAG_NONE, false));
-    memcpy(bytes, message->bytes(), *num_bytes);
-  }
-
- protected:
-  scoped_refptr<MessagePipeDispatcher> dispatcher0_;
-  scoped_refptr<MessagePipeDispatcher> dispatcher1_;
-
- private:
-  // We keep an active RequestContext for the duration of each test. It's unused
-  // since these tests don't rely on the MojoWatch API.
-  const RequestContext request_context_;
-
-  static uint64_t pipe_id_generator_;
-  DispatcherVector dispatchers_to_close_;
-
-  DISALLOW_COPY_AND_ASSIGN(WaitSetDispatcherTest);
-};
-
-// static
-uint64_t WaitSetDispatcherTest::pipe_id_generator_ = 1;
-
-TEST_F(WaitSetDispatcherTest, Basic) {
-  scoped_refptr<WaitSetDispatcher> wait_set = new WaitSetDispatcher();
-  CloseOnShutdown(wait_set);
-  ASSERT_EQ(MOJO_RESULT_OK,
-            wait_set->AddWaitingDispatcher(dispatcher0_,
-                                           MOJO_HANDLE_SIGNAL_READABLE, 1));
-  ASSERT_EQ(MOJO_RESULT_OK,
-            wait_set->AddWaitingDispatcher(dispatcher1_,
-                                           MOJO_HANDLE_SIGNAL_WRITABLE, 2));
-
-  Waiter w;
-  uintptr_t context = 0;
-  w.Init();
-  HandleSignalsState hss;
-  // |dispatcher1_| should already be writable.
-  EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
-            wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
-
-  scoped_refptr<Dispatcher> woken_dispatcher;
-  EXPECT_EQ(MOJO_RESULT_OK,
-            GetOneReadyDispatcher(wait_set, &woken_dispatcher, &context));
-  EXPECT_EQ(dispatcher1_, woken_dispatcher);
-  EXPECT_EQ(2u, context);
-  // If a ready dispatcher isn't removed, it will continue to be returned.
-  EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
-            wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
-  woken_dispatcher = nullptr;
-  context = 0;
-  EXPECT_EQ(MOJO_RESULT_OK,
-            GetOneReadyDispatcher(wait_set, &woken_dispatcher, &context));
-  EXPECT_EQ(dispatcher1_, woken_dispatcher);
-  EXPECT_EQ(2u, context);
-  ASSERT_EQ(MOJO_RESULT_OK, wait_set->RemoveWaitingDispatcher(dispatcher1_));
-
-  // No ready dispatcher.
-  hss = HandleSignalsState();
-  EXPECT_EQ(MOJO_RESULT_OK,
-            wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
-  EXPECT_FALSE(hss.satisfies(MOJO_HANDLE_SIGNAL_READABLE));
-  EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, w.Wait(0, nullptr));
-  EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
-            GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr));
-
-  // Write to |dispatcher1_|, which should make |dispatcher0_| readable.
-  char buffer[] = "abcd";
-  w.Init();
-  WriteMessage(dispatcher1_.get(), buffer, sizeof(buffer));
-  EXPECT_EQ(MOJO_RESULT_OK, w.Wait(MOJO_DEADLINE_INDEFINITE, nullptr));
-  woken_dispatcher = nullptr;
-  context = 0;
-  EXPECT_EQ(MOJO_RESULT_OK,
-            GetOneReadyDispatcher(wait_set, &woken_dispatcher, &context));
-  EXPECT_EQ(dispatcher0_, woken_dispatcher);
-  EXPECT_EQ(1u, context);
-
-  // Again, if a ready dispatcher isn't removed, it will continue to be
-  // returned.
-  woken_dispatcher = nullptr;
-  EXPECT_EQ(MOJO_RESULT_OK,
-            GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr));
-  EXPECT_EQ(dispatcher0_, woken_dispatcher);
-
-  wait_set->RemoveAwakable(&w, nullptr);
-}
-
-TEST_F(WaitSetDispatcherTest, HandleWithoutRemoving) {
-  scoped_refptr<WaitSetDispatcher> wait_set = new WaitSetDispatcher();
-  CloseOnShutdown(wait_set);
-  ASSERT_EQ(MOJO_RESULT_OK,
-            wait_set->AddWaitingDispatcher(dispatcher0_,
-                                           MOJO_HANDLE_SIGNAL_READABLE, 1));
-
-  Waiter w;
-  uintptr_t context = 0;
-  w.Init();
-  HandleSignalsState hss;
-  // No ready dispatcher.
-  hss = HandleSignalsState();
-  EXPECT_EQ(MOJO_RESULT_OK,
-            wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
-  EXPECT_FALSE(hss.satisfies(MOJO_HANDLE_SIGNAL_READABLE));
-  EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, w.Wait(0, nullptr));
-  scoped_refptr<Dispatcher> woken_dispatcher;
-  EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
-            GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr));
-
-  // The tested behaviour below should be repeatable.
-  for (size_t i = 0; i < 3; i++) {
-    // Write to |dispatcher1_|, which should make |dispatcher0_| readable.
-    char buffer[] = "abcd";
-    w.Init();
-    WriteMessage(dispatcher1_.get(), buffer, sizeof(buffer));
-    EXPECT_EQ(MOJO_RESULT_OK, w.Wait(MOJO_DEADLINE_INDEFINITE, nullptr));
-    woken_dispatcher = nullptr;
-    context = 0;
-    EXPECT_EQ(MOJO_RESULT_OK,
-              GetOneReadyDispatcher(wait_set, &woken_dispatcher, &context));
-    EXPECT_EQ(dispatcher0_, woken_dispatcher);
-    EXPECT_EQ(1u, context);
-
-    // Read from |dispatcher0_| which should change it's state to non-readable.
-    char read_buffer[sizeof(buffer) + 5];
-    uint32_t num_bytes = sizeof(read_buffer);
-    ReadMessage(dispatcher0_.get(), read_buffer, &num_bytes);
-    EXPECT_EQ(sizeof(buffer), num_bytes);
-
-    // No dispatchers are ready.
-    w.Init();
-    woken_dispatcher = nullptr;
-    context = 0;
-    EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
-              GetOneReadyDispatcher(wait_set, &woken_dispatcher, &context));
-    EXPECT_FALSE(woken_dispatcher);
-    EXPECT_EQ(0u, context);
-    EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, w.Wait(0, nullptr));
-  }
-
-  wait_set->RemoveAwakable(&w, nullptr);
-}
-
-TEST_F(WaitSetDispatcherTest, MultipleReady) {
-  scoped_refptr<WaitSetDispatcher> wait_set = new WaitSetDispatcher();
-  CloseOnShutdown(wait_set);
-
-  scoped_refptr<MessagePipeDispatcher> mp1_dispatcher0;
-  scoped_refptr<MessagePipeDispatcher> mp1_dispatcher1;
-  CreateMessagePipe(&mp1_dispatcher0, &mp1_dispatcher1);
-
-  ASSERT_EQ(MOJO_RESULT_OK,
-            wait_set->AddWaitingDispatcher(dispatcher0_,
-                                           MOJO_HANDLE_SIGNAL_READABLE, 0));
-  ASSERT_EQ(MOJO_RESULT_OK,
-            wait_set->AddWaitingDispatcher(dispatcher1_,
-                                           MOJO_HANDLE_SIGNAL_WRITABLE, 0));
-  ASSERT_EQ(MOJO_RESULT_OK,
-            wait_set->AddWaitingDispatcher(mp1_dispatcher0,
-                                           MOJO_HANDLE_SIGNAL_WRITABLE, 0));
-  ASSERT_EQ(MOJO_RESULT_OK,
-            wait_set->AddWaitingDispatcher(mp1_dispatcher1,
-                                           MOJO_HANDLE_SIGNAL_WRITABLE, 0));
-
-  Waiter w;
-  w.Init();
-  HandleSignalsState hss;
-  // The three writable dispatchers should be ready.
-  EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
-            wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
-
-  scoped_refptr<Dispatcher> woken_dispatcher;
-  EXPECT_EQ(MOJO_RESULT_OK,
-            GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr));
-  // Don't know which dispatcher was returned, just that it was one of the
-  // writable ones.
-  EXPECT_TRUE(woken_dispatcher == dispatcher1_ ||
-              woken_dispatcher == mp1_dispatcher0 ||
-              woken_dispatcher == mp1_dispatcher1);
-
-  DispatcherVector dispatchers_vector;
-  uint32_t count = 4;
-  MojoResult results[4];
-  EXPECT_EQ(MOJO_RESULT_OK,
-            wait_set->GetReadyDispatchers(&count,
-                                          &dispatchers_vector,
-                                          results,
-                                          nullptr));
-  EXPECT_EQ(3u, count);
-  std::sort(dispatchers_vector.begin(), dispatchers_vector.end());
-  DispatcherVector expected_dispatchers;
-  expected_dispatchers.push_back(dispatcher1_);
-  expected_dispatchers.push_back(mp1_dispatcher0);
-  expected_dispatchers.push_back(mp1_dispatcher1);
-  std::sort(expected_dispatchers.begin(), expected_dispatchers.end());
-  EXPECT_EQ(expected_dispatchers, dispatchers_vector);
-
-  // If a ready dispatcher isn't removed, it will continue to be returned.
-  EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
-            wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
-  count = 4;
-  dispatchers_vector.clear();
-  EXPECT_EQ(MOJO_RESULT_OK,
-            wait_set->GetReadyDispatchers(&count,
-                                          &dispatchers_vector,
-                                          results,
-                                          nullptr));
-  EXPECT_EQ(3u, count);
-  std::sort(dispatchers_vector.begin(), dispatchers_vector.end());
-  EXPECT_EQ(expected_dispatchers, dispatchers_vector);
-
-  // Remove one. It shouldn't be returned any longer.
-  ASSERT_EQ(MOJO_RESULT_OK,
-            wait_set->RemoveWaitingDispatcher(expected_dispatchers.back()));
-  expected_dispatchers.pop_back();
-  EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
-            wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
-  count = 4;
-  dispatchers_vector.clear();
-  EXPECT_EQ(MOJO_RESULT_OK,
-            wait_set->GetReadyDispatchers(&count,
-                                          &dispatchers_vector,
-                                          results,
-                                          nullptr));
-  EXPECT_EQ(2u, count);
-  std::sort(dispatchers_vector.begin(), dispatchers_vector.end());
-  EXPECT_EQ(expected_dispatchers, dispatchers_vector);
-
-  // Write to |dispatcher1_|, which should make |dispatcher0_| readable.
-  char buffer[] = "abcd";
-  w.Init();
-  WriteMessage(dispatcher1_.get(), buffer, sizeof(buffer));
-  {
-    Waiter mp_w;
-    mp_w.Init();
-    // Wait for |dispatcher0_| to be readable.
-    if (dispatcher0_->AddAwakable(&mp_w, MOJO_HANDLE_SIGNAL_READABLE, 0,
-                                  nullptr) == MOJO_RESULT_OK) {
-      EXPECT_EQ(MOJO_RESULT_OK, mp_w.Wait(MOJO_DEADLINE_INDEFINITE, 0));
-      dispatcher0_->RemoveAwakable(&mp_w, nullptr);
-    }
-  }
-  expected_dispatchers.push_back(dispatcher0_);
-  std::sort(expected_dispatchers.begin(), expected_dispatchers.end());
-  EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
-            wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
-  count = 4;
-  dispatchers_vector.clear();
-  EXPECT_EQ(MOJO_RESULT_OK,
-            wait_set->GetReadyDispatchers(&count,
-                                          &dispatchers_vector,
-                                          results,
-                                          nullptr));
-  EXPECT_EQ(3u, count);
-  std::sort(dispatchers_vector.begin(), dispatchers_vector.end());
-  EXPECT_EQ(expected_dispatchers, dispatchers_vector);
-}
-
-TEST_F(WaitSetDispatcherTest, InvalidParams) {
-  scoped_refptr<WaitSetDispatcher> wait_set = new WaitSetDispatcher();
-
-  // Can't add a wait set to itself.
-  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-            wait_set->AddWaitingDispatcher(wait_set,
-                                           MOJO_HANDLE_SIGNAL_READABLE, 0));
-
-  // Can't add twice.
-  EXPECT_EQ(MOJO_RESULT_OK,
-            wait_set->AddWaitingDispatcher(dispatcher0_,
-                                           MOJO_HANDLE_SIGNAL_READABLE, 0));
-  EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
-            wait_set->AddWaitingDispatcher(dispatcher0_,
-                                           MOJO_HANDLE_SIGNAL_READABLE, 0));
-
-  // Remove a dispatcher that wasn't added.
-  EXPECT_EQ(MOJO_RESULT_NOT_FOUND,
-            wait_set->RemoveWaitingDispatcher(dispatcher1_));
-
-  // Add to a closed wait set.
-  wait_set->Close();
-  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-            wait_set->AddWaitingDispatcher(dispatcher0_,
-                                           MOJO_HANDLE_SIGNAL_READABLE, 0));
-}
-
-TEST_F(WaitSetDispatcherTest, NotSatisfiable) {
-  scoped_refptr<WaitSetDispatcher> wait_set = new WaitSetDispatcher();
-  CloseOnShutdown(wait_set);
-
-  // Wait sets can only satisfy MOJO_HANDLE_SIGNAL_READABLE.
-  Waiter w;
-  w.Init();
-  HandleSignalsState hss;
-  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_NONE, hss.satisfied_signals);
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
-
-  hss = HandleSignalsState();
-  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_PEER_CLOSED, 0, &hss));
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_NONE, hss.satisfied_signals);
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
-}
-
-TEST_F(WaitSetDispatcherTest, ClosedDispatchers) {
-  scoped_refptr<WaitSetDispatcher> wait_set = new WaitSetDispatcher();
-  CloseOnShutdown(wait_set);
-
-  Waiter w;
-  w.Init();
-  HandleSignalsState hss;
-  // A dispatcher that was added and then closed will be cancelled.
-  ASSERT_EQ(MOJO_RESULT_OK,
-            wait_set->AddWaitingDispatcher(dispatcher0_,
-                                           MOJO_HANDLE_SIGNAL_READABLE, 0));
-  EXPECT_EQ(MOJO_RESULT_OK,
-            wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
-  dispatcher0_->Close();
-  EXPECT_EQ(MOJO_RESULT_OK, w.Wait(MOJO_DEADLINE_INDEFINITE, nullptr));
-  EXPECT_TRUE(
-      wait_set->GetHandleSignalsState().satisfies(MOJO_HANDLE_SIGNAL_READABLE));
-  scoped_refptr<Dispatcher> woken_dispatcher;
-  EXPECT_EQ(MOJO_RESULT_CANCELLED,
-            GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr));
-  EXPECT_EQ(dispatcher0_, woken_dispatcher);
-
-  // Dispatcher will be implicitly removed because it may be impossible to
-  // remove explicitly.
-  woken_dispatcher = nullptr;
-  EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
-            GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr));
-  EXPECT_EQ(MOJO_RESULT_NOT_FOUND,
-            wait_set->RemoveWaitingDispatcher(dispatcher0_));
-
-  // A dispatcher that's not satisfiable should give an error.
-  w.Init();
-  EXPECT_EQ(MOJO_RESULT_OK,
-            wait_set->AddWaitingDispatcher(dispatcher1_,
-                                           MOJO_HANDLE_SIGNAL_READABLE, 0));
-  EXPECT_EQ(MOJO_RESULT_OK, w.Wait(MOJO_DEADLINE_INDEFINITE, nullptr));
-  EXPECT_TRUE(
-      wait_set->GetHandleSignalsState().satisfies(MOJO_HANDLE_SIGNAL_READABLE));
-  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr));
-  EXPECT_EQ(dispatcher1_, woken_dispatcher);
-
-  wait_set->RemoveAwakable(&w, nullptr);
-}
-
-TEST_F(WaitSetDispatcherTest, NestedSets) {
-  scoped_refptr<WaitSetDispatcher> wait_set = new WaitSetDispatcher();
-  CloseOnShutdown(wait_set);
-  scoped_refptr<WaitSetDispatcher> nested_wait_set = new WaitSetDispatcher();
-  CloseOnShutdown(nested_wait_set);
-
-  Waiter w;
-  w.Init();
-  EXPECT_EQ(MOJO_RESULT_OK,
-            wait_set->AddWaitingDispatcher(nested_wait_set,
-                                           MOJO_HANDLE_SIGNAL_READABLE, 0));
-  EXPECT_EQ(MOJO_RESULT_OK,
-            wait_set->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, nullptr));
-  EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, w.Wait(0, nullptr));
-
-  // Writable signal is immediately satisfied by the message pipe.
-  w.Init();
-  EXPECT_EQ(MOJO_RESULT_OK,
-            nested_wait_set->AddWaitingDispatcher(
-                dispatcher0_, MOJO_HANDLE_SIGNAL_WRITABLE, 0));
-  EXPECT_EQ(MOJO_RESULT_OK, w.Wait(0, nullptr));
-  scoped_refptr<Dispatcher> woken_dispatcher;
-  EXPECT_EQ(MOJO_RESULT_OK,
-            GetOneReadyDispatcher(wait_set, &woken_dispatcher, nullptr));
-  EXPECT_EQ(nested_wait_set, woken_dispatcher);
-
-  wait_set->RemoveAwakable(&w, nullptr);
-}
-
-}  // namespace
-}  // namespace edk
-}  // namespace mojo
diff --git a/mojo/edk/system/waiter.cc b/mojo/edk/system/waiter.cc
deleted file mode 100644
index d98f3c6..0000000
--- a/mojo/edk/system/waiter.cc
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2013 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 "mojo/edk/system/waiter.h"
-
-#include <stdint.h>
-
-#include <limits>
-
-#include "base/logging.h"
-#include "base/time/time.h"
-
-namespace mojo {
-namespace edk {
-
-Waiter::Waiter()
-    : cv_(&lock_),
-#if DCHECK_IS_ON()
-      initialized_(false),
-#endif
-      awoken_(false),
-      awake_result_(MOJO_RESULT_INTERNAL),
-      awake_context_(static_cast<uint32_t>(-1)) {
-}
-
-Waiter::~Waiter() {
-}
-
-void Waiter::Init() {
-#if DCHECK_IS_ON()
-  initialized_ = true;
-#endif
-  awoken_ = false;
-  // NOTE(vtl): If performance ever becomes an issue, we can disable the setting
-  // of |awake_result_| (except the first one in |Awake()|) in Release builds.
-  awake_result_ = MOJO_RESULT_INTERNAL;
-}
-
-// TODO(vtl): Fast-path the |deadline == 0| case?
-MojoResult Waiter::Wait(MojoDeadline deadline, uintptr_t* context) {
-  base::AutoLock locker(lock_);
-
-#if DCHECK_IS_ON()
-  DCHECK(initialized_);
-  // It'll need to be re-initialized after this.
-  initialized_ = false;
-#endif
-
-  // Fast-path the already-awoken case:
-  if (awoken_) {
-    DCHECK_NE(awake_result_, MOJO_RESULT_INTERNAL);
-    if (context)
-      *context = awake_context_;
-    return awake_result_;
-  }
-
-  // |MojoDeadline| is actually a |uint64_t|, but we need a signed quantity.
-  // Treat any out-of-range deadline as "forever" (which is wrong, but okay
-  // since 2^63 microseconds is ~300000 years). Note that this also takes care
-  // of the |MOJO_DEADLINE_INDEFINITE| (= 2^64 - 1) case.
-  if (deadline > static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) {
-    do {
-      cv_.Wait();
-    } while (!awoken_);
-  } else {
-    // NOTE(vtl): This is very inefficient on POSIX, since pthreads condition
-    // variables take an absolute deadline.
-    const base::TimeTicks end_time =
-        base::TimeTicks::Now() +
-        base::TimeDelta::FromMicroseconds(static_cast<int64_t>(deadline));
-    do {
-      base::TimeTicks now_time = base::TimeTicks::Now();
-      if (now_time >= end_time)
-        return MOJO_RESULT_DEADLINE_EXCEEDED;
-
-      cv_.TimedWait(end_time - now_time);
-    } while (!awoken_);
-  }
-
-  DCHECK_NE(awake_result_, MOJO_RESULT_INTERNAL);
-  if (context)
-    *context = awake_context_;
-  return awake_result_;
-}
-
-bool Waiter::Awake(MojoResult result, uintptr_t context) {
-  base::AutoLock locker(lock_);
-
-  if (awoken_)
-    return true;
-
-  awoken_ = true;
-  awake_result_ = result;
-  awake_context_ = context;
-  cv_.Signal();
-  // |cv_.Wait()|/|cv_.TimedWait()| will return after |lock_| is released.
-  return true;
-}
-
-}  // namespace edk
-}  // namespace mojo
diff --git a/mojo/edk/system/waiter.h b/mojo/edk/system/waiter.h
deleted file mode 100644
index 897ecbe..0000000
--- a/mojo/edk/system/waiter.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2013 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 MOJO_EDK_SYSTEM_WAITER_H_
-#define MOJO_EDK_SYSTEM_WAITER_H_
-
-#include <stdint.h>
-
-#include "base/logging.h"
-#include "base/macros.h"
-#include "base/synchronization/condition_variable.h"
-#include "base/synchronization/lock.h"
-#include "mojo/edk/system/awakable.h"
-#include "mojo/edk/system/system_impl_export.h"
-#include "mojo/public/c/system/types.h"
-
-namespace mojo {
-namespace edk {
-
-// IMPORTANT (all-caps gets your attention, right?): |Waiter| methods are called
-// under other locks, in particular, |Dispatcher::lock_|s, so |Waiter| methods
-// must never call out to other objects (in particular, |Dispatcher|s). This
-// class is thread-safe.
-class MOJO_SYSTEM_IMPL_EXPORT Waiter final : public Awakable {
- public:
-  Waiter();
-  ~Waiter();
-
-  // A |Waiter| can be used multiple times; |Init()| should be called before
-  // each time it's used.
-  void Init();
-
-  // Waits until a suitable |Awake()| is called. (|context| may be null, in
-  // which case, obviously no context is ever returned.)
-  // Returns:
-  //   - The result given to the first call to |Awake()| (possibly before this
-  //     call to |Wait()|); in this case, |*context| is set to the value passed
-  //     to that call to |Awake()|.
-  //   - |MOJO_RESULT_DEADLINE_EXCEEDED| if the deadline was exceeded; in this
-  //     case |*context| is not modified.
-  //
-  // Usually, the context passed to |Awake()| will be the value passed to
-  // |Dispatcher::AddAwakable()|, which is usually the index to the array of
-  // handles passed to |MojoWaitMany()| (or 0 for |MojoWait()|).
-  //
-  // Typical |Awake()| results are:
-  //   - |MOJO_RESULT_OK| if one of the flags passed to
-  //     |MojoWait()|/|MojoWaitMany()| (hence |Dispatcher::AddAwakable()|) was
-  //     satisfied;
-  //   - |MOJO_RESULT_CANCELLED| if a handle (on which
-  //     |MojoWait()|/|MojoWaitMany()| was called) was closed (hence the
-  //     dispatcher closed); and
-  //   - |MOJO_RESULT_FAILED_PRECONDITION| if one of the set of flags passed to
-  //     |MojoWait()|/|MojoWaitMany()| cannot or can no longer be satisfied by
-  //     the corresponding handle (e.g., if the other end of a message or data
-  //     pipe is closed).
-  MojoResult Wait(MojoDeadline deadline, uintptr_t* context);
-
-  // Wake the waiter up with the given result and context (or no-op if it's been
-  // woken up already).
-  bool Awake(MojoResult result, uintptr_t context) override;
-
- private:
-  base::Lock lock_;             // Protects the following members.
-  base::ConditionVariable cv_;  // Associated to |lock_|.
-#if DCHECK_IS_ON()
-  bool initialized_;
-#endif
-  bool awoken_;
-  MojoResult awake_result_;
-  uintptr_t awake_context_;
-
-  DISALLOW_COPY_AND_ASSIGN(Waiter);
-};
-
-}  // namespace edk
-}  // namespace mojo
-
-#endif  // MOJO_EDK_SYSTEM_WAITER_H_
diff --git a/mojo/edk/system/waiter_test_utils.cc b/mojo/edk/system/waiter_test_utils.cc
deleted file mode 100644
index c7681e9..0000000
--- a/mojo/edk/system/waiter_test_utils.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2013 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 "mojo/edk/system/waiter_test_utils.h"
-
-namespace mojo {
-namespace edk {
-namespace test {
-
-SimpleWaiterThread::SimpleWaiterThread(MojoResult* result, uintptr_t* context)
-    : base::SimpleThread("waiter_thread"), result_(result), context_(context) {
-  waiter_.Init();
-  *result_ = 5420734;    // Totally invalid result.
-  *context_ = 23489023;  // "Random".
-}
-
-SimpleWaiterThread::~SimpleWaiterThread() {
-  Join();
-}
-
-void SimpleWaiterThread::Run() {
-  *result_ = waiter_.Wait(MOJO_DEADLINE_INDEFINITE, context_);
-}
-
-WaiterThread::WaiterThread(scoped_refptr<Dispatcher> dispatcher,
-                           MojoHandleSignals handle_signals,
-                           MojoDeadline deadline,
-                           uintptr_t context,
-                           bool* did_wait_out,
-                           MojoResult* result_out,
-                           uintptr_t* context_out,
-                           HandleSignalsState* signals_state_out)
-    : base::SimpleThread("waiter_thread"),
-      dispatcher_(dispatcher),
-      handle_signals_(handle_signals),
-      deadline_(deadline),
-      context_(context),
-      did_wait_out_(did_wait_out),
-      result_out_(result_out),
-      context_out_(context_out),
-      signals_state_out_(signals_state_out) {
-  *did_wait_out_ = false;
-  // Initialize these with invalid results (so that we'll be sure to catch any
-  // case where they're not set).
-  *result_out_ = 8542346;
-  *context_out_ = 89023444;
-  *signals_state_out_ = HandleSignalsState(~0u, ~0u);
-}
-
-WaiterThread::~WaiterThread() {
-  Join();
-}
-
-void WaiterThread::Run() {
-  waiter_.Init();
-
-  *result_out_ = dispatcher_->AddAwakable(&waiter_, handle_signals_, context_,
-                                          signals_state_out_);
-  if (*result_out_ != MOJO_RESULT_OK)
-    return;
-
-  *did_wait_out_ = true;
-  *result_out_ = waiter_.Wait(deadline_, context_out_);
-  dispatcher_->RemoveAwakable(&waiter_, signals_state_out_);
-}
-
-}  // namespace test
-}  // namespace edk
-}  // namespace mojo
diff --git a/mojo/edk/system/waiter_test_utils.h b/mojo/edk/system/waiter_test_utils.h
deleted file mode 100644
index eda1396..0000000
--- a/mojo/edk/system/waiter_test_utils.h
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2013 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 MOJO_EDK_SYSTEM_WAITER_TEST_UTILS_H_
-#define MOJO_EDK_SYSTEM_WAITER_TEST_UTILS_H_
-
-#include <stdint.h>
-
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/threading/simple_thread.h"
-#include "mojo/edk/system/dispatcher.h"
-#include "mojo/edk/system/handle_signals_state.h"
-#include "mojo/edk/system/waiter.h"
-#include "mojo/public/c/system/types.h"
-
-namespace mojo {
-namespace edk {
-namespace test {
-
-// This is a very simple thread that has a |Waiter|, on which it waits
-// indefinitely (and records the result). It will create and initialize the
-// |Waiter| on creation, but the caller must start the thread with |Start()|. It
-// will join the thread on destruction.
-//
-// One usually uses it like:
-//
-//    MojoResult result;
-//    {
-//      AwakableList awakable_list;
-//      test::SimpleWaiterThread thread(&result);
-//      awakable_list.Add(thread.waiter(), ...);
-//      thread.Start();
-//      ... some stuff to wake the waiter ...
-//      awakable_list.Remove(thread.waiter());
-//    }  // Join |thread|.
-//    EXPECT_EQ(..., result);
-//
-// There's a bit of unrealism in its use: In this sort of usage, calls such as
-// |Waiter::Init()|, |AddAwakable()|, and |RemoveAwakable()| are done in the
-// main (test) thread, not the waiter thread (as would actually happen in real
-// code). (We accept this unrealism for simplicity, since |AwakableList| is
-// thread-unsafe so making it more realistic would require adding nontrivial
-// synchronization machinery.)
-class SimpleWaiterThread : public base::SimpleThread {
- public:
-  // For the duration of the lifetime of this object, |*result| belongs to it
-  // (in the sense that it will write to it whenever it wants).
-  SimpleWaiterThread(MojoResult* result, uintptr_t* context);
-  ~SimpleWaiterThread() override;  // Joins the thread.
-
-  Waiter* waiter() { return &waiter_; }
-
- private:
-  void Run() override;
-
-  MojoResult* const result_;
-  uintptr_t* const context_;
-  Waiter waiter_;
-
-  DISALLOW_COPY_AND_ASSIGN(SimpleWaiterThread);
-};
-
-// This is a more complex and realistic thread that has a |Waiter|, on which it
-// waits for the given deadline (with the given flags). Unlike
-// |SimpleWaiterThread|, it requires the machinery of |Dispatcher|.
-class WaiterThread : public base::SimpleThread {
- public:
-  // Note: |*did_wait_out|, |*result_out|, |*context_out| and
-  // |*signals_state_out| "belong" to this object (i.e., may be modified by, on
-  // some other thread) while it's alive.
-  WaiterThread(scoped_refptr<Dispatcher> dispatcher,
-               MojoHandleSignals handle_signals,
-               MojoDeadline deadline,
-               uintptr_t context,
-               bool* did_wait_out,
-               MojoResult* result_out,
-               uintptr_t* context_out,
-               HandleSignalsState* signals_state_out);
-  ~WaiterThread() override;
-
- private:
-  void Run() override;
-
-  const scoped_refptr<Dispatcher> dispatcher_;
-  const MojoHandleSignals handle_signals_;
-  const MojoDeadline deadline_;
-  const uint32_t context_;
-  bool* const did_wait_out_;
-  MojoResult* const result_out_;
-  uintptr_t* const context_out_;
-  HandleSignalsState* const signals_state_out_;
-
-  Waiter waiter_;
-
-  DISALLOW_COPY_AND_ASSIGN(WaiterThread);
-};
-
-}  // namespace test
-}  // namespace edk
-}  // namespace mojo
-
-#endif  // MOJO_EDK_SYSTEM_WAITER_TEST_UTILS_H_
diff --git a/mojo/edk/system/waiter_unittest.cc b/mojo/edk/system/waiter_unittest.cc
deleted file mode 100644
index aa928ff..0000000
--- a/mojo/edk/system/waiter_unittest.cc
+++ /dev/null
@@ -1,298 +0,0 @@
-// Copyright 2013 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.
-
-// NOTE(vtl): Some of these tests are inherently flaky (e.g., if run on a
-// heavily-loaded system). Sorry. |test::EpsilonDeadline()| may be increased to
-// increase tolerance and reduce observed flakiness (though doing so reduces the
-// meaningfulness of the test).
-
-#include "mojo/edk/system/waiter.h"
-
-#include <stdint.h>
-
-#include "base/macros.h"
-#include "base/synchronization/lock.h"
-#include "base/threading/simple_thread.h"
-#include "mojo/edk/system/test_utils.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace mojo {
-namespace edk {
-namespace {
-
-const unsigned kPollTimeMs = 10;
-
-class WaitingThread : public base::SimpleThread {
- public:
-  explicit WaitingThread(MojoDeadline deadline)
-      : base::SimpleThread("waiting_thread"),
-        deadline_(deadline),
-        done_(false),
-        result_(MOJO_RESULT_UNKNOWN),
-        context_(static_cast<uintptr_t>(-1)) {
-    waiter_.Init();
-  }
-
-  ~WaitingThread() override { Join(); }
-
-  void WaitUntilDone(MojoResult* result,
-                     uintptr_t* context,
-                     MojoDeadline* elapsed) {
-    for (;;) {
-      {
-        base::AutoLock locker(lock_);
-        if (done_) {
-          *result = result_;
-          *context = context_;
-          *elapsed = elapsed_;
-          break;
-        }
-      }
-
-      test::Sleep(test::DeadlineFromMilliseconds(kPollTimeMs));
-    }
-  }
-
-  Waiter* waiter() { return &waiter_; }
-
- private:
-  void Run() override {
-    test::Stopwatch stopwatch;
-    MojoResult result;
-    uintptr_t context = static_cast<uintptr_t>(-1);
-    MojoDeadline elapsed;
-
-    stopwatch.Start();
-    result = waiter_.Wait(deadline_, &context);
-    elapsed = stopwatch.Elapsed();
-
-    {
-      base::AutoLock locker(lock_);
-      done_ = true;
-      result_ = result;
-      context_ = context;
-      elapsed_ = elapsed;
-    }
-  }
-
-  const MojoDeadline deadline_;
-  Waiter waiter_;  // Thread-safe.
-
-  base::Lock lock_;  // Protects the following members.
-  bool done_;
-  MojoResult result_;
-  uintptr_t context_;
-  MojoDeadline elapsed_;
-
-  DISALLOW_COPY_AND_ASSIGN(WaitingThread);
-};
-
-TEST(WaiterTest, Basic) {
-  MojoResult result;
-  uintptr_t context;
-  MojoDeadline elapsed;
-
-  // Finite deadline.
-
-  // Awake immediately after thread start.
-  {
-    WaitingThread thread(10 * test::EpsilonDeadline());
-    thread.Start();
-    thread.waiter()->Awake(MOJO_RESULT_OK, 1);
-    thread.WaitUntilDone(&result, &context, &elapsed);
-    EXPECT_EQ(MOJO_RESULT_OK, result);
-    EXPECT_EQ(1u, context);
-    EXPECT_LT(elapsed, test::EpsilonDeadline());
-  }
-
-  // Awake before after thread start.
-  {
-    WaitingThread thread(10 * test::EpsilonDeadline());
-    thread.waiter()->Awake(MOJO_RESULT_CANCELLED, 2);
-    thread.Start();
-    thread.WaitUntilDone(&result, &context, &elapsed);
-    EXPECT_EQ(MOJO_RESULT_CANCELLED, result);
-    EXPECT_EQ(2u, context);
-    EXPECT_LT(elapsed, test::EpsilonDeadline());
-  }
-
-  // Awake some time after thread start.
-  {
-    WaitingThread thread(10 * test::EpsilonDeadline());
-    thread.Start();
-    test::Sleep(2 * test::EpsilonDeadline());
-    thread.waiter()->Awake(1, 3);
-    thread.WaitUntilDone(&result, &context, &elapsed);
-    EXPECT_EQ(1u, result);
-    EXPECT_EQ(3u, context);
-    EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline());
-    EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline());
-  }
-
-  // Awake some longer time after thread start.
-  {
-    WaitingThread thread(10 * test::EpsilonDeadline());
-    thread.Start();
-    test::Sleep(5 * test::EpsilonDeadline());
-    thread.waiter()->Awake(2, 4);
-    thread.WaitUntilDone(&result, &context, &elapsed);
-    EXPECT_EQ(2u, result);
-    EXPECT_EQ(4u, context);
-    EXPECT_GT(elapsed, (5 - 1) * test::EpsilonDeadline());
-    EXPECT_LT(elapsed, (5 + 1) * test::EpsilonDeadline());
-  }
-
-  // Don't awake -- time out (on another thread).
-  {
-    WaitingThread thread(2 * test::EpsilonDeadline());
-    thread.Start();
-    thread.WaitUntilDone(&result, &context, &elapsed);
-    EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, result);
-    EXPECT_EQ(static_cast<uintptr_t>(-1), context);
-    EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline());
-    EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline());
-  }
-
-  // No (indefinite) deadline.
-
-  // Awake immediately after thread start.
-  {
-    WaitingThread thread(MOJO_DEADLINE_INDEFINITE);
-    thread.Start();
-    thread.waiter()->Awake(MOJO_RESULT_OK, 5);
-    thread.WaitUntilDone(&result, &context, &elapsed);
-    EXPECT_EQ(MOJO_RESULT_OK, result);
-    EXPECT_EQ(5u, context);
-    EXPECT_LT(elapsed, test::EpsilonDeadline());
-  }
-
-  // Awake before after thread start.
-  {
-    WaitingThread thread(MOJO_DEADLINE_INDEFINITE);
-    thread.waiter()->Awake(MOJO_RESULT_CANCELLED, 6);
-    thread.Start();
-    thread.WaitUntilDone(&result, &context, &elapsed);
-    EXPECT_EQ(MOJO_RESULT_CANCELLED, result);
-    EXPECT_EQ(6u, context);
-    EXPECT_LT(elapsed, test::EpsilonDeadline());
-  }
-
-  // Awake some time after thread start.
-  {
-    WaitingThread thread(MOJO_DEADLINE_INDEFINITE);
-    thread.Start();
-    test::Sleep(2 * test::EpsilonDeadline());
-    thread.waiter()->Awake(1, 7);
-    thread.WaitUntilDone(&result, &context, &elapsed);
-    EXPECT_EQ(1u, result);
-    EXPECT_EQ(7u, context);
-    EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline());
-    EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline());
-  }
-
-  // Awake some longer time after thread start.
-  {
-    WaitingThread thread(MOJO_DEADLINE_INDEFINITE);
-    thread.Start();
-    test::Sleep(5 * test::EpsilonDeadline());
-    thread.waiter()->Awake(2, 8);
-    thread.WaitUntilDone(&result, &context, &elapsed);
-    EXPECT_EQ(2u, result);
-    EXPECT_EQ(8u, context);
-    EXPECT_GT(elapsed, (5 - 1) * test::EpsilonDeadline());
-    EXPECT_LT(elapsed, (5 + 1) * test::EpsilonDeadline());
-  }
-}
-
-TEST(WaiterTest, TimeOut) {
-  test::Stopwatch stopwatch;
-  MojoDeadline elapsed;
-
-  Waiter waiter;
-  uintptr_t context = 123;
-
-  waiter.Init();
-  stopwatch.Start();
-  EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, &context));
-  elapsed = stopwatch.Elapsed();
-  EXPECT_LT(elapsed, test::EpsilonDeadline());
-  EXPECT_EQ(123u, context);
-
-  waiter.Init();
-  stopwatch.Start();
-  EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
-            waiter.Wait(2 * test::EpsilonDeadline(), &context));
-  elapsed = stopwatch.Elapsed();
-  EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline());
-  EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline());
-  EXPECT_EQ(123u, context);
-
-  waiter.Init();
-  stopwatch.Start();
-  EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
-            waiter.Wait(5 * test::EpsilonDeadline(), &context));
-  elapsed = stopwatch.Elapsed();
-  EXPECT_GT(elapsed, (5 - 1) * test::EpsilonDeadline());
-  EXPECT_LT(elapsed, (5 + 1) * test::EpsilonDeadline());
-  EXPECT_EQ(123u, context);
-}
-
-// The first |Awake()| should always win.
-TEST(WaiterTest, MultipleAwakes) {
-  MojoResult result;
-  uintptr_t context;
-  MojoDeadline elapsed;
-
-  {
-    WaitingThread thread(MOJO_DEADLINE_INDEFINITE);
-    thread.Start();
-    thread.waiter()->Awake(MOJO_RESULT_OK, 1);
-    thread.waiter()->Awake(1, 2);
-    thread.WaitUntilDone(&result, &context, &elapsed);
-    EXPECT_EQ(MOJO_RESULT_OK, result);
-    EXPECT_EQ(1u, context);
-    EXPECT_LT(elapsed, test::EpsilonDeadline());
-  }
-
-  {
-    WaitingThread thread(MOJO_DEADLINE_INDEFINITE);
-    thread.waiter()->Awake(1, 3);
-    thread.Start();
-    thread.waiter()->Awake(MOJO_RESULT_OK, 4);
-    thread.WaitUntilDone(&result, &context, &elapsed);
-    EXPECT_EQ(1u, result);
-    EXPECT_EQ(3u, context);
-    EXPECT_LT(elapsed, test::EpsilonDeadline());
-  }
-
-  {
-    WaitingThread thread(MOJO_DEADLINE_INDEFINITE);
-    thread.Start();
-    thread.waiter()->Awake(10, 5);
-    test::Sleep(2 * test::EpsilonDeadline());
-    thread.waiter()->Awake(20, 6);
-    thread.WaitUntilDone(&result, &context, &elapsed);
-    EXPECT_EQ(10u, result);
-    EXPECT_EQ(5u, context);
-    EXPECT_LT(elapsed, test::EpsilonDeadline());
-  }
-
-  {
-    WaitingThread thread(10 * test::EpsilonDeadline());
-    thread.Start();
-    test::Sleep(1 * test::EpsilonDeadline());
-    thread.waiter()->Awake(MOJO_RESULT_FAILED_PRECONDITION, 7);
-    test::Sleep(2 * test::EpsilonDeadline());
-    thread.waiter()->Awake(MOJO_RESULT_OK, 8);
-    thread.WaitUntilDone(&result, &context, &elapsed);
-    EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
-    EXPECT_EQ(7u, context);
-    EXPECT_GT(elapsed, (1 - 1) * test::EpsilonDeadline());
-    EXPECT_LT(elapsed, (1 + 1) * test::EpsilonDeadline());
-  }
-}
-
-}  // namespace
-}  // namespace edk
-}  // namespace mojo
diff --git a/mojo/edk/system/watch.cc b/mojo/edk/system/watch.cc
new file mode 100644
index 0000000..cf08ac3
--- /dev/null
+++ b/mojo/edk/system/watch.cc
@@ -0,0 +1,83 @@
+// Copyright 2017 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 "mojo/edk/system/watch.h"
+
+#include "mojo/edk/system/request_context.h"
+#include "mojo/edk/system/watcher_dispatcher.h"
+
+namespace mojo {
+namespace edk {
+
+Watch::Watch(const scoped_refptr<WatcherDispatcher>& watcher,
+             const scoped_refptr<Dispatcher>& dispatcher,
+             uintptr_t context,
+             MojoHandleSignals signals)
+    : watcher_(watcher),
+      dispatcher_(dispatcher),
+      context_(context),
+      signals_(signals) {}
+
+bool Watch::NotifyState(const HandleSignalsState& state,
+                        bool allowed_to_call_callback) {
+  AssertWatcherLockAcquired();
+
+  // NOTE: This method must NEVER call into |dispatcher_| directly, because it
+  // may be called while |dispatcher_| holds a lock.
+
+  MojoResult rv = MOJO_RESULT_SHOULD_WAIT;
+  RequestContext* const request_context = RequestContext::current();
+  if (state.satisfies(signals_)) {
+    rv = MOJO_RESULT_OK;
+    if (allowed_to_call_callback && rv != last_known_result_) {
+      request_context->AddWatchNotifyFinalizer(this, MOJO_RESULT_OK, state);
+    }
+  } else if (!state.can_satisfy(signals_)) {
+    rv = MOJO_RESULT_FAILED_PRECONDITION;
+    if (allowed_to_call_callback && rv != last_known_result_) {
+      request_context->AddWatchNotifyFinalizer(
+          this, MOJO_RESULT_FAILED_PRECONDITION, state);
+    }
+  }
+
+  last_known_signals_state_ =
+      *static_cast<const MojoHandleSignalsState*>(&state);
+  last_known_result_ = rv;
+  return ready();
+}
+
+void Watch::Cancel() {
+  RequestContext::current()->AddWatchCancelFinalizer(this);
+}
+
+void Watch::InvokeCallback(MojoResult result,
+                           const HandleSignalsState& state,
+                           MojoWatcherNotificationFlags flags) {
+  // We hold the lock through invocation to ensure that only one notification
+  // callback runs for this context at any given time.
+  base::AutoLock lock(notification_lock_);
+  if (result == MOJO_RESULT_CANCELLED) {
+    // Make sure cancellation is the last notification we dispatch.
+    DCHECK(!is_cancelled_);
+    is_cancelled_ = true;
+  } else if (is_cancelled_) {
+    return;
+  }
+
+  // NOTE: This will acquire |watcher_|'s internal lock. It's safe because a
+  // thread can only enter InvokeCallback() from within a RequestContext
+  // destructor where no dispatcher locks are held.
+  watcher_->InvokeWatchCallback(context_, result, state, flags);
+}
+
+Watch::~Watch() {}
+
+#if DCHECK_IS_ON()
+void Watch::AssertWatcherLockAcquired() const {
+  watcher_->lock_.AssertAcquired();
+}
+#endif
+
+}  // namespace edk
+}  // namespace mojo
diff --git a/mojo/edk/system/watch.h b/mojo/edk/system/watch.h
new file mode 100644
index 0000000..f277de9
--- /dev/null
+++ b/mojo/edk/system/watch.h
@@ -0,0 +1,124 @@
+// Copyright 2017 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 MOJO_EDK_SYSTEM_WATCH_H_
+#define MOJO_EDK_SYSTEM_WATCH_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "mojo/edk/system/atomic_flag.h"
+#include "mojo/edk/system/handle_signals_state.h"
+
+namespace mojo {
+namespace edk {
+
+class Dispatcher;
+class WatcherDispatcher;
+
+// Encapsulates the state associated with a single watch context within a
+// watcher.
+//
+// Every Watch has its own cancellation state, and is captured by RequestContext
+// notification finalizers to avoid redundant context resolution during
+// finalizer execution.
+class Watch : public base::RefCountedThreadSafe<Watch> {
+ public:
+  // Constructs a Watch which represents a watch within |watcher| associated
+  // with |context|, watching |dispatcher| for |signals|.
+  Watch(const scoped_refptr<WatcherDispatcher>& watcher,
+        const scoped_refptr<Dispatcher>& dispatcher,
+        uintptr_t context,
+        MojoHandleSignals signals);
+
+  // Notifies the Watch of a potential state change.
+  //
+  // If |allowed_to_call_callback| is true, this may add a notification
+  // finalizer to the current RequestContext to invoke the watcher's callback
+  // with this watch's context. See return values below.
+  //
+  // This is called directly by WatcherDispatcher whenever the Watch's observed
+  // dispatcher notifies the WatcherDispatcher of a state change.
+  //
+  // Returns |true| if the Watch entered or remains in a ready state as a result
+  // of the state change. If |allowed_to_call_callback| was true in this case,
+  // the Watch will have also attached a notification finalizer to the current
+  // RequestContext.
+  //
+  // Returns |false| if the
+  bool NotifyState(const HandleSignalsState& state,
+                   bool allowed_to_call_callback);
+
+  // Notifies the watch of cancellation ASAP. This will always be the last
+  // notification sent for the watch.
+  void Cancel();
+
+  // Finalizer method for RequestContexts. This method is invoked once for every
+  // notification finalizer added to a RequestContext by this object. This calls
+  // down into the WatcherDispatcher to do the actual notification call.
+  void InvokeCallback(MojoResult result,
+                      const HandleSignalsState& state,
+                      MojoWatcherNotificationFlags flags);
+
+  const scoped_refptr<Dispatcher>& dispatcher() const { return dispatcher_; }
+  uintptr_t context() const { return context_; }
+
+  MojoResult last_known_result() const {
+    AssertWatcherLockAcquired();
+    return last_known_result_;
+  }
+
+  MojoHandleSignalsState last_known_signals_state() const {
+    AssertWatcherLockAcquired();
+    return last_known_signals_state_;
+  }
+
+  bool ready() const {
+    AssertWatcherLockAcquired();
+    return last_known_result_ == MOJO_RESULT_OK ||
+           last_known_result_ == MOJO_RESULT_FAILED_PRECONDITION;
+  }
+
+ private:
+  friend class base::RefCountedThreadSafe<Watch>;
+
+  ~Watch();
+
+#if DCHECK_IS_ON()
+  void AssertWatcherLockAcquired() const;
+#else
+  void AssertWatcherLockAcquired() const {}
+#endif
+
+  const scoped_refptr<WatcherDispatcher> watcher_;
+  const scoped_refptr<Dispatcher> dispatcher_;
+  const uintptr_t context_;
+  const MojoHandleSignals signals_;
+
+  // The result code with which this Watch would notify if currently armed,
+  // based on the last known signaling state of |dispatcher_|. Guarded by the
+  // owning WatcherDispatcher's lock.
+  MojoResult last_known_result_ = MOJO_RESULT_UNKNOWN;
+
+  // The last known signaling state of |dispatcher_|. Guarded by the owning
+  // WatcherDispatcher's lock.
+  MojoHandleSignalsState last_known_signals_state_ = {0, 0};
+
+  // Guards |is_cancelled_| below and mutually excludes individual watch
+  // notification executions for this same watch context.
+  //
+  // Note that this should only be acquired from a RequestContext finalizer to
+  // ensure that no other internal locks are already held.
+  base::Lock notification_lock_;
+
+  // Guarded by |notification_lock_|.
+  bool is_cancelled_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(Watch);
+};
+
+}  // namespace edk
+}  // namespace mojo
+
+#endif  // MOJO_EDK_SYSTEM_WATCH_H_
diff --git a/mojo/edk/system/watch_unittest.cc b/mojo/edk/system/watch_unittest.cc
deleted file mode 100644
index ec28d94..0000000
--- a/mojo/edk/system/watch_unittest.cc
+++ /dev/null
@@ -1,480 +0,0 @@
-// 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.
-
-#include <functional>
-
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/message_loop/message_loop.h"
-#include "base/run_loop.h"
-#include "base/single_thread_task_runner.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "mojo/edk/system/request_context.h"
-#include "mojo/edk/test/mojo_test_base.h"
-#include "mojo/public/c/system/functions.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace mojo {
-namespace edk {
-namespace {
-
-void IgnoreResult(uintptr_t context,
-                  MojoResult result,
-                  MojoHandleSignalsState signals,
-                  MojoWatchNotificationFlags flags) {
-}
-
-// A test helper class for watching a handle. The WatchHelper instance is used
-// as a watch context for a single watch callback.
-class WatchHelper {
- public:
-  using Callback =
-      std::function<void(MojoResult result, MojoHandleSignalsState state)>;
-
-  WatchHelper() : task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
-  ~WatchHelper() {
-    CHECK(!watching_);
-  }
-
-  void Watch(MojoHandle handle,
-             MojoHandleSignals signals,
-             const Callback& callback) {
-    CHECK(!watching_);
-
-    handle_ = handle;
-    callback_ = callback;
-    watching_ = true;
-    CHECK_EQ(MOJO_RESULT_OK, MojoWatch(handle_, signals, &WatchHelper::OnNotify,
-                                       reinterpret_cast<uintptr_t>(this)));
-  }
-
-  bool is_watching() const { return watching_; }
-
-  void Cancel() {
-    CHECK_EQ(MOJO_RESULT_OK,
-             MojoCancelWatch(handle_, reinterpret_cast<uintptr_t>(this)));
-    CHECK(watching_);
-    watching_ = false;
-  }
-
- private:
-  static void OnNotify(uintptr_t context,
-                       MojoResult result,
-                       MojoHandleSignalsState state,
-                       MojoWatchNotificationFlags flags) {
-    WatchHelper* watcher = reinterpret_cast<WatchHelper*>(context);
-    watcher->task_runner_->PostTask(
-        FROM_HERE,
-        base::Bind(&NotifyOnMainThread, context, result, state, flags));
-  }
-
-  static void NotifyOnMainThread(uintptr_t context,
-                                 MojoResult result,
-                                 MojoHandleSignalsState state,
-                                 MojoWatchNotificationFlags flags) {
-    WatchHelper* watcher = reinterpret_cast<WatchHelper*>(context);
-    CHECK(watcher->watching_);
-    if (result == MOJO_RESULT_CANCELLED)
-      watcher->watching_ = false;
-    watcher->callback_(result, state);
-  }
-
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
-  bool watching_ = false;
-  MojoHandle handle_;
-  Callback callback_;
-
-  DISALLOW_COPY_AND_ASSIGN(WatchHelper);
-};
-
-class WatchTest : public test::MojoTestBase {
- public:
-  WatchTest() {}
-  ~WatchTest() override {}
-
- protected:
-
- private:
-  base::MessageLoop message_loop_;
-
-  DISALLOW_COPY_AND_ASSIGN(WatchTest);
-};
-
-TEST_F(WatchTest, NotifyBasic) {
-  MojoHandle a, b;
-  CreateMessagePipe(&a, &b);
-
-  base::RunLoop loop;
-  WatchHelper b_watcher;
-  b_watcher.Watch(
-      b, MOJO_HANDLE_SIGNAL_READABLE,
-      [&] (MojoResult result, MojoHandleSignalsState state) {
-        EXPECT_EQ(MOJO_RESULT_OK, result);
-        EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE,
-                  state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
-        EXPECT_TRUE(b_watcher.is_watching());
-        loop.Quit();
-      });
-
-  WriteMessage(a, "Hello!");
-  loop.Run();
-
-  EXPECT_TRUE(b_watcher.is_watching());
-  b_watcher.Cancel();
-
-  CloseHandle(a);
-  CloseHandle(b);
-}
-
-TEST_F(WatchTest, NotifyUnsatisfiable) {
-  MojoHandle a, b;
-  CreateMessagePipe(&a, &b);
-
-  base::RunLoop loop;
-  WatchHelper b_watcher;
-  b_watcher.Watch(
-      b, MOJO_HANDLE_SIGNAL_READABLE,
-      [&] (MojoResult result, MojoHandleSignalsState state) {
-        EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
-        EXPECT_EQ(0u,
-                  state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
-        EXPECT_EQ(0u,
-                  state.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE);
-        EXPECT_TRUE(b_watcher.is_watching());
-        loop.Quit();
-      });
-
-  CloseHandle(a);
-  loop.Run();
-
-  b_watcher.Cancel();
-
-  CloseHandle(b);
-}
-
-TEST_F(WatchTest, NotifyCancellation) {
-  MojoHandle a, b;
-  CreateMessagePipe(&a, &b);
-
-  base::RunLoop loop;
-  WatchHelper b_watcher;
-  b_watcher.Watch(
-      b, MOJO_HANDLE_SIGNAL_READABLE,
-      [&] (MojoResult result, MojoHandleSignalsState state) {
-        EXPECT_EQ(MOJO_RESULT_CANCELLED, result);
-        EXPECT_EQ(0u, state.satisfied_signals);
-        EXPECT_EQ(0u, state.satisfiable_signals);
-        EXPECT_FALSE(b_watcher.is_watching());
-        loop.Quit();
-      });
-
-  CloseHandle(b);
-  loop.Run();
-
-  CloseHandle(a);
-}
-
-TEST_F(WatchTest, InvalidArguemnts) {
-  MojoHandle a, b;
-  CreateMessagePipe(&a, &b);
-
-  uintptr_t context = reinterpret_cast<uintptr_t>(this);
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(a, MOJO_HANDLE_SIGNAL_READABLE,
-                                      &IgnoreResult, context));
-
-  // Can't cancel a watch that doesn't exist.
-  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoCancelWatch(a, ~context));
-  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoCancelWatch(b, context));
-
-  CloseHandle(a);
-  CloseHandle(b);
-
-  // Can't watch a handle that doesn't exist.
-  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-            MojoWatch(a, MOJO_HANDLE_SIGNAL_READABLE, &IgnoreResult, context));
-  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-            MojoWatch(b, MOJO_HANDLE_SIGNAL_READABLE, &IgnoreResult, context));
-}
-
-TEST_F(WatchTest, NoDuplicateContext) {
-  MojoHandle a, b;
-  CreateMessagePipe(&a, &b);
-
-  // Try to add the same watch twice; should fail.
-  uintptr_t context = reinterpret_cast<uintptr_t>(this);
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(a, MOJO_HANDLE_SIGNAL_READABLE,
-                                      &IgnoreResult, context));
-  EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
-      MojoWatch(a, MOJO_HANDLE_SIGNAL_READABLE, &IgnoreResult, context));
-
-  // Cancel and add it again; should be OK.
-  EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(a, context));
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(a, MOJO_HANDLE_SIGNAL_READABLE,
-                                      &IgnoreResult, context));
-
-  CloseHandle(a);
-  CloseHandle(b);
-}
-
-TEST_F(WatchTest, MultipleWatches) {
-  MojoHandle a, b;
-  CreateMessagePipe(&a, &b);
-
-  // Add multiple watchers to |b| and see that they are both notified by a
-  // single write to |a|.
-  base::RunLoop loop;
-  int expected_notifications = 2;
-  auto on_readable = [&] (MojoResult result, MojoHandleSignalsState state) {
-    EXPECT_EQ(MOJO_RESULT_OK, result);
-    EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE,
-              state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
-    EXPECT_GT(expected_notifications, 0);
-    if (--expected_notifications == 0)
-      loop.Quit();
-  };
-  WatchHelper watcher1;
-  WatchHelper watcher2;
-  watcher1.Watch(b, MOJO_HANDLE_SIGNAL_READABLE, on_readable);
-  watcher2.Watch(b, MOJO_HANDLE_SIGNAL_READABLE, on_readable);
-
-  WriteMessage(a, "Ping!");
-  loop.Run();
-
-  watcher1.Cancel();
-  watcher2.Cancel();
-
-  CloseHandle(a);
-  CloseHandle(b);
-}
-
-TEST_F(WatchTest, WatchWhileSatisfied) {
-  MojoHandle a, b;
-  CreateMessagePipe(&a, &b);
-
-  // Write to |a| and then start watching |b|. The callback should be invoked
-  // synchronously.
-  WriteMessage(a, "hey");
-  bool signaled = false;
-  WatchHelper b_watcher;
-  base::RunLoop loop;
-  b_watcher.Watch(
-      b, MOJO_HANDLE_SIGNAL_READABLE,
-      [&] (MojoResult result, MojoHandleSignalsState state) {
-        EXPECT_EQ(MOJO_RESULT_OK, result);
-        EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE,
-                  state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
-        signaled = true;
-        loop.Quit();
-      });
-  loop.Run();
-  EXPECT_TRUE(signaled);
-  b_watcher.Cancel();
-
-  CloseHandle(a);
-  CloseHandle(b);
-}
-
-TEST_F(WatchTest, WatchWhileUnsatisfiable) {
-  MojoHandle a, b;
-  CreateMessagePipe(&a, &b);
-
-  // Close |a| and then try to watch |b|. MojoWatch() should fail.
-  CloseHandle(a);
-  uintptr_t context = reinterpret_cast<uintptr_t>(this);
-  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            MojoWatch(b, MOJO_HANDLE_SIGNAL_READABLE, &IgnoreResult, context));
-
-  CloseHandle(b);
-}
-
-TEST_F(WatchTest, RespondFromCallback) {
-  MojoHandle a, b;
-  CreateMessagePipe(&a, &b);
-
-  // Watch |a| and |b|. Write to |a|, then write to |b| from within the callback
-  // which notifies it of the available message.
-  const std::string kTestMessage = "hello worlds.";
-  base::RunLoop loop;
-  WatchHelper b_watcher;
-  b_watcher.Watch(
-      b, MOJO_HANDLE_SIGNAL_READABLE,
-      [&] (MojoResult result, MojoHandleSignalsState state) {
-        EXPECT_EQ(MOJO_RESULT_OK, result);
-        EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE,
-                  state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
-        EXPECT_TRUE(b_watcher.is_watching());
-
-        // Echo a's message back to it.
-        WriteMessage(b, ReadMessage(b));
-      });
-
-  WatchHelper a_watcher;
-  a_watcher.Watch(
-      a, MOJO_HANDLE_SIGNAL_READABLE,
-      [&] (MojoResult result, MojoHandleSignalsState state) {
-        EXPECT_EQ(MOJO_RESULT_OK, result);
-        EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE,
-                  state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
-        EXPECT_TRUE(a_watcher.is_watching());
-
-        // Expect to receive back the message that was originally sent to |b|.
-        EXPECT_EQ(kTestMessage, ReadMessage(a));
-
-        loop.Quit();
-      });
-
-  WriteMessage(a, kTestMessage);
-  loop.Run();
-
-  a_watcher.Cancel();
-  b_watcher.Cancel();
-
-  CloseHandle(a);
-  CloseHandle(b);
-}
-
-TEST_F(WatchTest, WatchDataPipeConsumer) {
-  MojoHandle a, b;
-  CreateDataPipe(&a, &b, 64);
-
-  base::RunLoop loop;
-  WatchHelper b_watcher;
-  b_watcher.Watch(
-      b, MOJO_HANDLE_SIGNAL_READABLE,
-      [&] (MojoResult result, MojoHandleSignalsState state) {
-        EXPECT_EQ(MOJO_RESULT_OK, result);
-        EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE,
-                  state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
-        EXPECT_TRUE(b_watcher.is_watching());
-        loop.Quit();
-      });
-
-  WriteData(a, "Hello!");
-  loop.Run();
-
-  EXPECT_TRUE(b_watcher.is_watching());
-  b_watcher.Cancel();
-
-  CloseHandle(a);
-  CloseHandle(b);
-}
-
-TEST_F(WatchTest, WatchDataPipeProducer) {
-  MojoHandle a, b;
-  CreateDataPipe(&a, &b, 8);
-
-  // Fill the pipe to capacity so writes will block.
-  WriteData(a, "xxxxxxxx");
-
-  base::RunLoop loop;
-  WatchHelper a_watcher;
-  a_watcher.Watch(
-      a, MOJO_HANDLE_SIGNAL_WRITABLE,
-      [&] (MojoResult result, MojoHandleSignalsState state) {
-        EXPECT_EQ(MOJO_RESULT_OK, result);
-        EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE,
-                  state.satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
-        EXPECT_TRUE(a_watcher.is_watching());
-        loop.Quit();
-      });
-
-  EXPECT_EQ("xxxxxxxx", ReadData(b, 8));
-  loop.Run();
-
-  EXPECT_TRUE(a_watcher.is_watching());
-  a_watcher.Cancel();
-
-  CloseHandle(a);
-  CloseHandle(b);
-}
-
-TEST_F(WatchTest, WakeUpSelfWithinWatchCallback) {
-  MojoHandle a, b;
-  CreateMessagePipe(&a, &b);
-
-  int expected_notifications = 2;
-  base::RunLoop loop;
-  WatchHelper b_watcher;
-  b_watcher.Watch(
-      b, MOJO_HANDLE_SIGNAL_READABLE,
-      [&] (MojoResult result, MojoHandleSignalsState state) {
-        EXPECT_EQ(MOJO_RESULT_OK, result);
-        EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE,
-                  state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
-        EXPECT_TRUE(b_watcher.is_watching());
-        if (--expected_notifications == 0) {
-          loop.Quit();
-        } else {
-          // Trigger b's watch again from within this callback. This should be
-          // safe to do.
-          WriteMessage(a, "hey");
-        }
-      });
-
-  WriteMessage(a, "hey hey hey");
-  loop.Run();
-
-  b_watcher.Cancel();
-
-  CloseHandle(a);
-  CloseHandle(b);
-}
-
-TEST_F(WatchTest, NestedCancellation) {
-  // Verifies that cancellations in nested system request contexts preempt
-  // other notifications for the same watcher. This tests against the condition
-  // hit by http://crbug.com/622298.
-
-  MojoHandle a, b, c, d;
-  CreateMessagePipe(&a, &b);
-  CreateMessagePipe(&c, &d);
-
-  base::RunLoop loop;
-  bool a_watcher_run = false;
-  WatchHelper a_watcher;
-  a_watcher.Watch(
-      a, MOJO_HANDLE_SIGNAL_READABLE,
-      [&](MojoResult result, MojoHandleSignalsState state) {
-        a_watcher_run = true;
-      });
-
-  WatchHelper c_watcher;
-  c_watcher.Watch(
-      c, MOJO_HANDLE_SIGNAL_READABLE,
-      [&](MojoResult result, MojoHandleSignalsState state) {
-        // This will trigger a notification on |a_watcher| above to be executed
-        // once this handler finishes running...
-        CloseHandle(b);
-
-        // ...but this should prevent that notification from dispatching because
-        // |a_watcher| is now cancelled.
-        a_watcher.Cancel();
-
-        loop.Quit();
-      });
-
-  {
-    // Force "system" notifications for the synchronous behavior required to
-    // test this case.
-    mojo::edk::RequestContext request_context(
-        mojo::edk::RequestContext::Source::SYSTEM);
-
-    // Trigger the |c_watcher| callback above.
-    CloseHandle(d);
-  }
-
-  loop.Run();
-
-  EXPECT_FALSE(a_watcher.is_watching());
-  EXPECT_FALSE(a_watcher_run);
-
-  c_watcher.Cancel();
-
-  CloseHandle(a);
-  CloseHandle(c);
-}
-
-}  // namespace
-}  // namespace edk
-}  // namespace mojo
diff --git a/mojo/edk/system/watcher.cc b/mojo/edk/system/watcher.cc
deleted file mode 100644
index 25c2276..0000000
--- a/mojo/edk/system/watcher.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// 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.
-
-#include "mojo/edk/system/watcher.h"
-
-#include "mojo/edk/system/handle_signals_state.h"
-#include "mojo/edk/system/request_context.h"
-
-namespace mojo {
-namespace edk {
-
-Watcher::Watcher(MojoHandleSignals signals, const WatchCallback& callback)
-    : signals_(signals), callback_(callback) {
-}
-
-void Watcher::MaybeInvokeCallback(MojoResult result,
-                                  const HandleSignalsState& state,
-                                  MojoWatchNotificationFlags flags) {
-  base::AutoLock lock(lock_);
-  if (is_cancelled_)
-    return;
-
-  callback_.Run(result, state, flags);
-}
-
-void Watcher::NotifyForStateChange(const HandleSignalsState& signals_state) {
-  RequestContext* request_context = RequestContext::current();
-  if (signals_state.satisfies(signals_)) {
-    request_context->AddWatchNotifyFinalizer(
-        make_scoped_refptr(this), MOJO_RESULT_OK, signals_state);
-  } else if (!signals_state.can_satisfy(signals_)) {
-    request_context->AddWatchNotifyFinalizer(
-        make_scoped_refptr(this), MOJO_RESULT_FAILED_PRECONDITION,
-        signals_state);
-  }
-}
-
-void Watcher::NotifyClosed() {
-  static const HandleSignalsState closed_state = {0, 0};
-  RequestContext::current()->AddWatchNotifyFinalizer(
-      make_scoped_refptr(this), MOJO_RESULT_CANCELLED, closed_state);
-}
-
-void Watcher::Cancel() {
-  base::AutoLock lock(lock_);
-  is_cancelled_ = true;
-}
-
-Watcher::~Watcher() {}
-
-}  // namespace edk
-}  // namespace mojo
diff --git a/mojo/edk/system/watcher.h b/mojo/edk/system/watcher.h
deleted file mode 100644
index b6dc2e4..0000000
--- a/mojo/edk/system/watcher.h
+++ /dev/null
@@ -1,88 +0,0 @@
-// 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.
-
-#ifndef MOJO_EDK_SYSTEM_WATCHER_H_
-#define MOJO_EDK_SYSTEM_WATCHER_H_
-
-#include "base/callback.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/synchronization/lock.h"
-#include "mojo/public/c/system/functions.h"
-#include "mojo/public/c/system/types.h"
-
-namespace mojo {
-namespace edk {
-
-struct HandleSignalsState;
-
-// This object corresponds to a watch added by a single call to |MojoWatch()|.
-//
-// An event may occur at any time which should trigger a Watcher to run its
-// callback, but the callback needs to be deferred until all EDK locks are
-// released. At the same time, a watch may be cancelled at any time by
-// |MojoCancelWatch()| and it is not OK for the callback to be invoked after
-// that happens.
-//
-// Therefore a Watcher needs to have some associated thread-safe state to track
-// its cancellation, which is why it's ref-counted.
-class Watcher : public base::RefCountedThreadSafe<Watcher> {
- public:
-  using WatchCallback = base::Callback<void(MojoResult,
-                                            const HandleSignalsState&,
-                                            MojoWatchNotificationFlags)>;
-
-  // Constructs a new Watcher which watches for |signals| to be satisfied on a
-  // handle and which invokes |callback| either when one such signal is
-  // satisfied, or all such signals become unsatisfiable.
-  Watcher(MojoHandleSignals signals, const WatchCallback& callback);
-
-  // Runs the Watcher's callback with the given arguments if it hasn't been
-  // cancelled yet.
-  void MaybeInvokeCallback(MojoResult result,
-                           const HandleSignalsState& state,
-                           MojoWatchNotificationFlags flags);
-
-  // Notifies the Watcher of a state change. This may result in the Watcher
-  // adding a finalizer to the current RequestContext to invoke its callback,
-  // cancellation notwithstanding.
-  void NotifyForStateChange(const HandleSignalsState& signals_state);
-
-  // Notifies the Watcher of handle closure. This always results in the Watcher
-  // adding a finalizer to the current RequestContext to invoke its callback,
-  // cancellation notwithstanding.
-  void NotifyClosed();
-
-  // Explicitly cancels the watch, guaranteeing that its callback will never be
-  // be invoked again.
-  void Cancel();
-
- private:
-  friend class base::RefCountedThreadSafe<Watcher>;
-
-  ~Watcher();
-
-  // The set of signals which are watched by this Watcher.
-  const MojoHandleSignals signals_;
-
-  // The callback to invoke with a result and signal state any time signals in
-  // |signals_| are satisfied or become permanently unsatisfiable.
-  const WatchCallback callback_;
-
-  // Guards |is_cancelled_|.
-  base::Lock lock_;
-
-  // Indicates whether the watch has been cancelled. A |Watcher| may exist for a
-  // brief period of time after being cancelled if it's been attached as a
-  // RequestContext finalizer. In such cases the callback must not be invoked,
-  // hence this flag.
-  bool is_cancelled_ = false;
-
-  DISALLOW_COPY_AND_ASSIGN(Watcher);
-};
-
-}  // namespace edk
-}  // namespace mojo
-
-#endif  // MOJO_EDK_SYSTEM_WATCHER_H_
diff --git a/mojo/edk/system/watcher_dispatcher.cc b/mojo/edk/system/watcher_dispatcher.cc
new file mode 100644
index 0000000..409dd2a
--- /dev/null
+++ b/mojo/edk/system/watcher_dispatcher.cc
@@ -0,0 +1,232 @@
+// Copyright 2017 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 "mojo/edk/system/watcher_dispatcher.h"
+
+#include <algorithm>
+#include <limits>
+#include <map>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "mojo/edk/system/watch.h"
+
+namespace mojo {
+namespace edk {
+
+WatcherDispatcher::WatcherDispatcher(MojoWatcherCallback callback)
+    : callback_(callback) {}
+
+void WatcherDispatcher::NotifyHandleState(Dispatcher* dispatcher,
+                                          const HandleSignalsState& state) {
+  base::AutoLock lock(lock_);
+  auto it = watched_handles_.find(dispatcher);
+  if (it == watched_handles_.end())
+    return;
+
+  // Maybe fire a notification to the watch assoicated with this dispatcher,
+  // provided we're armed it cares about the new state.
+  if (it->second->NotifyState(state, armed_)) {
+    ready_watches_.insert(it->second.get());
+
+    // If we were armed and got here, we notified the watch. Disarm.
+    armed_ = false;
+  } else {
+    ready_watches_.erase(it->second.get());
+  }
+}
+
+void WatcherDispatcher::NotifyHandleClosed(Dispatcher* dispatcher) {
+  scoped_refptr<Watch> watch;
+  {
+    base::AutoLock lock(lock_);
+    auto it = watched_handles_.find(dispatcher);
+    if (it == watched_handles_.end())
+      return;
+
+    watch = std::move(it->second);
+
+    // Wipe out all state associated with the closed dispatcher.
+    watches_.erase(watch->context());
+    ready_watches_.erase(watch.get());
+    watched_handles_.erase(it);
+  }
+
+  // NOTE: It's important that this is called outside of |lock_| since it
+  // acquires internal Watch locks.
+  watch->Cancel();
+}
+
+void WatcherDispatcher::InvokeWatchCallback(
+    uintptr_t context,
+    MojoResult result,
+    const HandleSignalsState& state,
+    MojoWatcherNotificationFlags flags) {
+  {
+    // We avoid holding the lock during dispatch. It's OK for notification
+    // callbacks to close this watcher, and it's OK for notifications to race
+    // with closure, if for example the watcher is closed from another thread
+    // between this test and the invocation of |callback_| below.
+    //
+    // Because cancellation synchronously blocks all future notifications, and
+    // because notifications themselves are mutually exclusive for any given
+    // context, we still guarantee that a single MOJO_RESULT_CANCELLED result
+    // is the last notification received for any given context.
+    //
+    // This guarantee is sufficient to make safe, synchronized, per-context
+    // state management possible in user code.
+    base::AutoLock lock(lock_);
+    if (closed_ && result != MOJO_RESULT_CANCELLED)
+      return;
+  }
+
+  callback_(context, result, static_cast<MojoHandleSignalsState>(state), flags);
+}
+
+Dispatcher::Type WatcherDispatcher::GetType() const {
+  return Type::WATCHER;
+}
+
+MojoResult WatcherDispatcher::Close() {
+  // We swap out all the watched handle information onto the stack so we can
+  // call into their dispatchers without our own lock held.
+  std::map<uintptr_t, scoped_refptr<Watch>> watches;
+  {
+    base::AutoLock lock(lock_);
+    DCHECK(!closed_);
+    closed_ = true;
+    std::swap(watches, watches_);
+    watched_handles_.clear();
+  }
+
+  // Remove all refs from our watched dispatchers and fire cancellations.
+  for (auto& entry : watches) {
+    entry.second->dispatcher()->RemoveWatcherRef(this, entry.first);
+    entry.second->Cancel();
+  }
+
+  return MOJO_RESULT_OK;
+}
+
+MojoResult WatcherDispatcher::WatchDispatcher(
+    scoped_refptr<Dispatcher> dispatcher,
+    MojoHandleSignals signals,
+    uintptr_t context) {
+  // NOTE: Because it's critical to avoid acquiring any other dispatcher locks
+  // while |lock_| is held, we defer adding oursevles to the dispatcher until
+  // after we've updated all our own relevant state and released |lock_|.
+  {
+    base::AutoLock lock(lock_);
+    if (watches_.count(context) || watched_handles_.count(dispatcher.get()))
+      return MOJO_RESULT_ALREADY_EXISTS;
+
+    scoped_refptr<Watch> watch = new Watch(this, dispatcher, context, signals);
+    watches_.insert({context, watch});
+    auto result =
+        watched_handles_.insert(std::make_pair(dispatcher.get(), watch));
+    DCHECK(result.second);
+  }
+
+  MojoResult rv = dispatcher->AddWatcherRef(this, context);
+  if (rv != MOJO_RESULT_OK) {
+    // Oops. This was not a valid handle to watch. Undo the above work and
+    // fail gracefully.
+    base::AutoLock lock(lock_);
+    watches_.erase(context);
+    watched_handles_.erase(dispatcher.get());
+    return rv;
+  }
+
+  return MOJO_RESULT_OK;
+}
+
+MojoResult WatcherDispatcher::CancelWatch(uintptr_t context) {
+  // We may remove the last stored ref to the Watch below, so we retain
+  // a reference on the stack.
+  scoped_refptr<Watch> watch;
+  {
+    base::AutoLock lock(lock_);
+    auto it = watches_.find(context);
+    if (it == watches_.end())
+      return MOJO_RESULT_NOT_FOUND;
+    watch = it->second;
+    watches_.erase(it);
+  }
+
+  // Mark the watch as cancelled so no further notifications get through.
+  watch->Cancel();
+
+  // We remove the watcher ref for this context before updating any more
+  // internal watcher state, ensuring that we don't receiving further
+  // notifications for this context.
+  watch->dispatcher()->RemoveWatcherRef(this, context);
+
+  {
+    base::AutoLock lock(lock_);
+    auto handle_it = watched_handles_.find(watch->dispatcher().get());
+    DCHECK(handle_it != watched_handles_.end());
+    ready_watches_.erase(handle_it->second.get());
+    watched_handles_.erase(handle_it);
+  }
+
+  return MOJO_RESULT_OK;
+}
+
+MojoResult WatcherDispatcher::Arm(
+    uint32_t* num_ready_contexts,
+    uintptr_t* ready_contexts,
+    MojoResult* ready_results,
+    MojoHandleSignalsState* ready_signals_states) {
+  base::AutoLock lock(lock_);
+  if (num_ready_contexts &&
+      (!ready_contexts || !ready_results || !ready_signals_states)) {
+    return MOJO_RESULT_INVALID_ARGUMENT;
+  }
+
+  if (watched_handles_.empty())
+    return MOJO_RESULT_NOT_FOUND;
+
+  if (ready_watches_.empty()) {
+    // Fast path: No watches are ready to notify, so we're done.
+    armed_ = true;
+    return MOJO_RESULT_OK;
+  }
+
+  if (num_ready_contexts) {
+    DCHECK_LE(ready_watches_.size(), std::numeric_limits<uint32_t>::max());
+    *num_ready_contexts = std::min(
+        *num_ready_contexts, static_cast<uint32_t>(ready_watches_.size()));
+
+    WatchSet::const_iterator next_ready_iter = ready_watches_.begin();
+    if (last_watch_to_block_arming_) {
+      // Find the next watch to notify in simple round-robin order on the
+      // |ready_watches_| map, wrapping around to the beginning if necessary.
+      next_ready_iter = ready_watches_.find(last_watch_to_block_arming_);
+      if (next_ready_iter != ready_watches_.end())
+        ++next_ready_iter;
+      if (next_ready_iter == ready_watches_.end())
+        next_ready_iter = ready_watches_.begin();
+    }
+
+    for (size_t i = 0; i < *num_ready_contexts; ++i) {
+      const Watch* const watch = *next_ready_iter;
+      ready_contexts[i] = watch->context();
+      ready_results[i] = watch->last_known_result();
+      ready_signals_states[i] = watch->last_known_signals_state();
+
+      // Iterate and wrap around.
+      last_watch_to_block_arming_ = watch;
+      ++next_ready_iter;
+      if (next_ready_iter == ready_watches_.end())
+        next_ready_iter = ready_watches_.begin();
+    }
+  }
+
+  return MOJO_RESULT_FAILED_PRECONDITION;
+}
+
+WatcherDispatcher::~WatcherDispatcher() {}
+
+}  // namespace edk
+}  // namespace mojo
diff --git a/mojo/edk/system/watcher_dispatcher.h b/mojo/edk/system/watcher_dispatcher.h
new file mode 100644
index 0000000..605a315
--- /dev/null
+++ b/mojo/edk/system/watcher_dispatcher.h
@@ -0,0 +1,101 @@
+// Copyright 2017 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 MOJO_EDK_SYSTEM_WATCHER_DISPATCHER_H_
+#define MOJO_EDK_SYSTEM_WATCHER_DISPATCHER_H_
+
+#include <map>
+#include <set>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/system/handle_signals_state.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/watcher.h"
+
+namespace mojo {
+namespace edk {
+
+class Watch;
+
+// The dispatcher type which backs watcher handles.
+class WatcherDispatcher : public Dispatcher {
+ public:
+  // Constructs a new WatcherDispatcher which invokes |callback| when a
+  // registered watch observes some relevant state change.
+  explicit WatcherDispatcher(MojoWatcherCallback callback);
+
+  // Methods used by watched dispatchers to notify watchers of events.
+  void NotifyHandleState(Dispatcher* dispatcher,
+                         const HandleSignalsState& state);
+  void NotifyHandleClosed(Dispatcher* dispatcher);
+
+  // Method used by RequestContext (indirectly, via Watch) to complete
+  // notification operations from a safe stack frame to avoid reentrancy.
+  void InvokeWatchCallback(uintptr_t context,
+                           MojoResult result,
+                           const HandleSignalsState& state,
+                           MojoWatcherNotificationFlags flags);
+
+  // Dispatcher:
+  Type GetType() const override;
+  MojoResult Close() override;
+  MojoResult WatchDispatcher(scoped_refptr<Dispatcher> dispatcher,
+                             MojoHandleSignals signals,
+                             uintptr_t context) override;
+  MojoResult CancelWatch(uintptr_t context) override;
+  MojoResult Arm(uint32_t* num_ready_contexts,
+                 uintptr_t* ready_contexts,
+                 MojoResult* ready_results,
+                 MojoHandleSignalsState* ready_signals_states) override;
+
+ private:
+  friend class Watch;
+
+  using WatchSet = std::set<const Watch*>;
+
+  ~WatcherDispatcher() override;
+
+  const MojoWatcherCallback callback_;
+
+  // Guards access to the fields below.
+  //
+  // NOTE: This may be acquired while holding another dispatcher's lock, as
+  // watched dispatchers call into WatcherDispatcher methods which lock this
+  // when issuing state change notifications. WatcherDispatcher must therefore
+  // take caution to NEVER acquire other dispatcher locks while this is held.
+  base::Lock lock_;
+
+  bool armed_ = false;
+  bool closed_ = false;
+
+  // A mapping from context to Watch.
+  std::map<uintptr_t, scoped_refptr<Watch>> watches_;
+
+  // A mapping from watched dispatcher to Watch.
+  std::map<Dispatcher*, scoped_refptr<Watch>> watched_handles_;
+
+  // The set of all Watch instances which are currently ready to signal. This is
+  // used for efficient arming behavior, as it allows for O(1) discovery of
+  // whether or not arming can succeed and quick determination of who's
+  // responsible if it can't.
+  WatchSet ready_watches_;
+
+  // Tracks the last Watch whose state was returned by Arm(). This is used to
+  // ensure consistent round-robin behavior in the event that multiple Watches
+  // remain ready over the span of several Arm() attempts.
+  //
+  // NOTE: This pointer is only used to index |ready_watches_| and may point to
+  // an invalid object. It must therefore never be dereferenced.
+  const Watch* last_watch_to_block_arming_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(WatcherDispatcher);
+};
+
+}  // namespace edk
+}  // namespace mojo
+
+#endif  // MOJO_EDK_SYSTEM_WATCHER_DISPATCHER_H_
diff --git a/mojo/edk/system/watcher_set.cc b/mojo/edk/system/watcher_set.cc
index 878f29a..0355b58 100644
--- a/mojo/edk/system/watcher_set.cc
+++ b/mojo/edk/system/watcher_set.cc
@@ -4,54 +4,79 @@
 
 #include "mojo/edk/system/watcher_set.h"
 
-#include "mojo/edk/system/request_context.h"
-#include "mojo/public/c/system/types.h"
+#include <utility>
 
 namespace mojo {
 namespace edk {
 
-WatcherSet::WatcherSet() {}
+WatcherSet::WatcherSet(Dispatcher* owner) : owner_(owner) {}
 
-WatcherSet::~WatcherSet() {}
+WatcherSet::~WatcherSet() = default;
 
-void WatcherSet::NotifyForStateChange(const HandleSignalsState& state) {
+void WatcherSet::NotifyState(const HandleSignalsState& state) {
+  // Avoid notifying watchers if they have already seen this state.
+  if (last_known_state_.has_value() && state.equals(last_known_state_.value()))
+    return;
+  last_known_state_ = state;
   for (const auto& entry : watchers_)
-    entry.second->NotifyForStateChange(state);
+    entry.first->NotifyHandleState(owner_, state);
 }
 
 void WatcherSet::NotifyClosed() {
   for (const auto& entry : watchers_)
-    entry.second->NotifyClosed();
+    entry.first->NotifyHandleClosed(owner_);
 }
 
-MojoResult WatcherSet::Add(MojoHandleSignals signals,
-                           const Watcher::WatchCallback& callback,
+MojoResult WatcherSet::Add(const scoped_refptr<WatcherDispatcher>& watcher,
                            uintptr_t context,
                            const HandleSignalsState& current_state) {
-  auto it = watchers_.find(context);
-  if (it != watchers_.end())
+  auto it = watchers_.find(watcher.get());
+  if (it == watchers_.end()) {
+    auto result =
+        watchers_.insert(std::make_pair(watcher.get(), Entry{watcher}));
+    it = result.first;
+  }
+
+  if (!it->second.contexts.insert(context).second)
     return MOJO_RESULT_ALREADY_EXISTS;
 
-  if (!current_state.can_satisfy(signals))
-    return MOJO_RESULT_FAILED_PRECONDITION;
-
-  scoped_refptr<Watcher> watcher(new Watcher(signals, callback));
-  watchers_.insert(std::make_pair(context, watcher));
-
-  watcher->NotifyForStateChange(current_state);
-
+  if (last_known_state_.has_value() &&
+      !current_state.equals(last_known_state_.value())) {
+    // This new state may be relevant to everyone, in which case we just
+    // notify everyone.
+    NotifyState(current_state);
+  } else {
+    // Otherwise only notify the newly added Watcher.
+    watcher->NotifyHandleState(owner_, current_state);
+  }
   return MOJO_RESULT_OK;
 }
 
-MojoResult WatcherSet::Remove(uintptr_t context) {
-  auto it = watchers_.find(context);
+MojoResult WatcherSet::Remove(WatcherDispatcher* watcher, uintptr_t context) {
+  auto it = watchers_.find(watcher);
   if (it == watchers_.end())
-    return MOJO_RESULT_INVALID_ARGUMENT;
+    return MOJO_RESULT_NOT_FOUND;
 
-  RequestContext::current()->AddWatchCancelFinalizer(it->second);
-  watchers_.erase(it);
+  ContextSet& contexts = it->second.contexts;
+  auto context_it = contexts.find(context);
+  if (context_it == contexts.end())
+    return MOJO_RESULT_NOT_FOUND;
+
+  contexts.erase(context_it);
+  if (contexts.empty())
+    watchers_.erase(it);
+
   return MOJO_RESULT_OK;
 }
 
+WatcherSet::Entry::Entry(const scoped_refptr<WatcherDispatcher>& dispatcher)
+    : dispatcher(dispatcher) {}
+
+WatcherSet::Entry::Entry(Entry&& other) = default;
+
+WatcherSet::Entry::~Entry() = default;
+
+WatcherSet::Entry& WatcherSet::Entry::operator=(Entry&& other) = default;
+
 }  // namespace edk
 }  // namespace mojo
diff --git a/mojo/edk/system/watcher_set.h b/mojo/edk/system/watcher_set.h
index 8ae54a1..2b7ef2c 100644
--- a/mojo/edk/system/watcher_set.h
+++ b/mojo/edk/system/watcher_set.h
@@ -5,45 +5,62 @@
 #ifndef MOJO_EDK_SYSTEM_WATCHER_SET_H_
 #define MOJO_EDK_SYSTEM_WATCHER_SET_H_
 
-#include <unordered_map>
+#include <map>
 
-#include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
 #include "mojo/edk/system/handle_signals_state.h"
-#include "mojo/edk/system/watcher.h"
-#include "mojo/public/c/system/types.h"
+#include "mojo/edk/system/watcher_dispatcher.h"
 
 namespace mojo {
 namespace edk {
 
-// A WatcherSet maintains a set of Watchers attached to a single handle and
-// keyed on an arbitrary user context.
+// A WatcherSet maintains a set of references to WatcherDispatchers to be
+// notified when a handle changes state.
+//
+// Dispatchers which may be watched by a watcher should own a WatcherSet and
+// notify it of all relevant state changes.
 class WatcherSet {
  public:
-  WatcherSet();
+  // |owner| is the Dispatcher who owns this WatcherSet.
+  explicit WatcherSet(Dispatcher* owner);
   ~WatcherSet();
 
-  // Notifies all Watchers of a state change.
-  void NotifyForStateChange(const HandleSignalsState& state);
+  // Notifies all watchers of the handle's current signals state.
+  void NotifyState(const HandleSignalsState& state);
 
-  // Notifies all Watchers that their watched handle has been closed.
+  // Notifies all watchers that this handle has been closed.
   void NotifyClosed();
 
-  // Adds a new watcher to watch for signals in |signals| to be satisfied or
-  // unsatisfiable. |current_state| is the current signals state of the
-  // handle being watched.
-  MojoResult Add(MojoHandleSignals signals,
-                 const Watcher::WatchCallback& callback,
+  // Adds a new watcher+context.
+  MojoResult Add(const scoped_refptr<WatcherDispatcher>& watcher,
                  uintptr_t context,
                  const HandleSignalsState& current_state);
 
-  // Removes a watcher from the set.
-  MojoResult Remove(uintptr_t context);
+  // Removes a watcher+context.
+  MojoResult Remove(WatcherDispatcher* watcher, uintptr_t context);
 
  private:
-  // A map of watchers keyed on context value.
-  std::unordered_map<uintptr_t, scoped_refptr<Watcher>> watchers_;
+  using ContextSet = std::set<uintptr_t>;
+
+  struct Entry {
+    Entry(const scoped_refptr<WatcherDispatcher>& dispatcher);
+    Entry(Entry&& other);
+    ~Entry();
+
+    Entry& operator=(Entry&& other);
+
+    scoped_refptr<WatcherDispatcher> dispatcher;
+    ContextSet contexts;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(Entry);
+  };
+
+  Dispatcher* const owner_;
+  std::map<WatcherDispatcher*, Entry> watchers_;
+  base::Optional<HandleSignalsState> last_known_state_;
 
   DISALLOW_COPY_AND_ASSIGN(WatcherSet);
 };
diff --git a/mojo/edk/system/watcher_unittest.cc b/mojo/edk/system/watcher_unittest.cc
new file mode 100644
index 0000000..dd396cd
--- /dev/null
+++ b/mojo/edk/system/watcher_unittest.cc
@@ -0,0 +1,1637 @@
+// Copyright 2017 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 <stdint.h>
+
+#include <map>
+#include <memory>
+#include <set>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
+#include "base/time/time.h"
+#include "mojo/edk/test/mojo_test_base.h"
+#include "mojo/public/c/system/data_pipe.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/c/system/watcher.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+using WatcherTest = test::MojoTestBase;
+
+class WatchHelper {
+ public:
+  using ContextCallback =
+      base::Callback<void(MojoResult, MojoHandleSignalsState)>;
+
+  WatchHelper() {}
+  ~WatchHelper() {}
+
+  MojoResult CreateWatcher(MojoHandle* handle) {
+    return MojoCreateWatcher(&Notify, handle);
+  }
+
+  uintptr_t CreateContext(const ContextCallback& callback) {
+    return CreateContextWithCancel(callback, base::Closure());
+  }
+
+  uintptr_t CreateContextWithCancel(const ContextCallback& callback,
+                                    const base::Closure& cancel_callback) {
+    auto context = base::MakeUnique<NotificationContext>(callback);
+    NotificationContext* raw_context = context.get();
+    raw_context->SetCancelCallback(base::Bind(
+        [](std::unique_ptr<NotificationContext> context,
+           const base::Closure& cancel_callback) {
+          if (cancel_callback)
+            cancel_callback.Run();
+        },
+        base::Passed(&context), cancel_callback));
+    return reinterpret_cast<uintptr_t>(raw_context);
+  }
+
+ private:
+  class NotificationContext {
+   public:
+    explicit NotificationContext(const ContextCallback& callback)
+        : callback_(callback) {}
+
+    ~NotificationContext() {}
+
+    void SetCancelCallback(const base::Closure& cancel_callback) {
+      cancel_callback_ = cancel_callback;
+    }
+
+    void Notify(MojoResult result, MojoHandleSignalsState state) {
+      if (result == MOJO_RESULT_CANCELLED)
+        cancel_callback_.Run();
+      else
+        callback_.Run(result, state);
+    }
+
+   private:
+    const ContextCallback callback_;
+    base::Closure cancel_callback_;
+
+    DISALLOW_COPY_AND_ASSIGN(NotificationContext);
+  };
+
+  static void Notify(uintptr_t context,
+                     MojoResult result,
+                     MojoHandleSignalsState state,
+                     MojoWatcherNotificationFlags flags) {
+    reinterpret_cast<NotificationContext*>(context)->Notify(result, state);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(WatchHelper);
+};
+
+class ThreadedRunner : public base::SimpleThread {
+ public:
+  explicit ThreadedRunner(const base::Closure& callback)
+      : SimpleThread("ThreadedRunner"), callback_(callback) {}
+  ~ThreadedRunner() override {}
+
+  void Run() override { callback_.Run(); }
+
+ private:
+  const base::Closure callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(ThreadedRunner);
+};
+
+void ExpectNoNotification(uintptr_t context,
+                          MojoResult result,
+                          MojoHandleSignalsState state,
+                          MojoWatcherNotificationFlags flags) {
+  NOTREACHED();
+}
+
+void ExpectOnlyCancel(uintptr_t context,
+                      MojoResult result,
+                      MojoHandleSignalsState state,
+                      MojoWatcherNotificationFlags flags) {
+  EXPECT_EQ(result, MOJO_RESULT_CANCELLED);
+}
+
+TEST_F(WatcherTest, InvalidArguments) {
+  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+            MojoCreateWatcher(&ExpectNoNotification, nullptr));
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectNoNotification, &w));
+
+  // Try to watch unwatchable handles.
+  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+            MojoWatch(w, w, MOJO_HANDLE_SIGNAL_READABLE, 0));
+  MojoHandle buffer_handle = CreateBuffer(42);
+  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+            MojoWatch(w, buffer_handle, MOJO_HANDLE_SIGNAL_READABLE, 0));
+
+  // Try to cancel a watch on an invalid watcher handle.
+  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoCancelWatch(buffer_handle, 0));
+
+  // Try to arm an invalid handle.
+  EXPECT_EQ(
+      MOJO_RESULT_INVALID_ARGUMENT,
+      MojoArmWatcher(MOJO_HANDLE_INVALID, nullptr, nullptr, nullptr, nullptr));
+  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+            MojoArmWatcher(buffer_handle, nullptr, nullptr, nullptr, nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(buffer_handle));
+
+  // Try to arm with a non-null count but at least one null output buffer.
+  uint32_t num_ready_contexts = 1;
+  uintptr_t ready_context;
+  MojoResult ready_result;
+  MojoHandleSignalsState ready_state;
+  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+            MojoArmWatcher(w, &num_ready_contexts, nullptr, &ready_result,
+                           &ready_state));
+  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+            MojoArmWatcher(w, &num_ready_contexts, &ready_context, nullptr,
+                           &ready_state));
+  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+            MojoArmWatcher(w, &num_ready_contexts, &ready_context,
+                           &ready_result, nullptr));
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+}
+
+TEST_F(WatcherTest, WatchMessagePipeReadable) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+  WatchHelper helper;
+  int num_expected_notifications = 1;
+  const uintptr_t readable_a_context = helper.CreateContext(base::Bind(
+      [](base::WaitableEvent* event, int* expected_count, MojoResult result,
+         MojoHandleSignalsState state) {
+        EXPECT_GT(*expected_count, 0);
+        *expected_count -= 1;
+
+        EXPECT_EQ(MOJO_RESULT_OK, result);
+        event->Signal();
+      },
+      &event, &num_expected_notifications));
+
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  const char kMessage1[] = "hey hey hey hey";
+  const char kMessage2[] = "i said hey";
+  const char kMessage3[] = "what's goin' on?";
+
+  // Writing to |b| multiple times should notify exactly once.
+  WriteMessage(b, kMessage1);
+  WriteMessage(b, kMessage2);
+  event.Wait();
+
+  // This also shouldn't fire a notification; the watcher is still disarmed.
+  WriteMessage(b, kMessage3);
+
+  // Arming should fail with relevant information.
+  constexpr size_t kMaxReadyContexts = 10;
+  uint32_t num_ready_contexts = kMaxReadyContexts;
+  uintptr_t ready_contexts[kMaxReadyContexts];
+  MojoResult ready_results[kMaxReadyContexts];
+  MojoHandleSignalsState ready_states[kMaxReadyContexts];
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+                           ready_results, ready_states));
+  EXPECT_EQ(1u, num_ready_contexts);
+  EXPECT_EQ(readable_a_context, ready_contexts[0]);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+
+  // Flush the three messages from above.
+  EXPECT_EQ(kMessage1, ReadMessage(a));
+  EXPECT_EQ(kMessage2, ReadMessage(a));
+  EXPECT_EQ(kMessage3, ReadMessage(a));
+
+  // Now we can rearm the watcher.
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+}
+
+TEST_F(WatcherTest, CloseWatchedMessagePipeHandle) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+  WatchHelper helper;
+  const uintptr_t readable_a_context = helper.CreateContextWithCancel(
+      WatchHelper::ContextCallback(),
+      base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event));
+
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context));
+
+  // Test that closing a watched handle fires an appropriate notification, even
+  // when the watcher is unarmed.
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+  event.Wait();
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+}
+
+TEST_F(WatcherTest, CloseWatchedMessagePipeHandlePeer) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+  WatchHelper helper;
+  const uintptr_t readable_a_context = helper.CreateContext(base::Bind(
+      [](base::WaitableEvent* event, MojoResult result,
+         MojoHandleSignalsState state) {
+        EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
+        event->Signal();
+      },
+      &event));
+
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context));
+
+  // Test that closing a watched handle's peer with an armed watcher fires an
+  // appropriate notification.
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+  event.Wait();
+
+  // And now arming should fail with correct information about |a|'s state.
+  constexpr size_t kMaxReadyContexts = 10;
+  uint32_t num_ready_contexts = kMaxReadyContexts;
+  uintptr_t ready_contexts[kMaxReadyContexts];
+  MojoResult ready_results[kMaxReadyContexts];
+  MojoHandleSignalsState ready_states[kMaxReadyContexts];
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+                           ready_results, ready_states));
+  EXPECT_EQ(1u, num_ready_contexts);
+  EXPECT_EQ(readable_a_context, ready_contexts[0]);
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]);
+  EXPECT_TRUE(ready_states[0].satisfied_signals &
+              MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+  EXPECT_FALSE(ready_states[0].satisfiable_signals &
+               MOJO_HANDLE_SIGNAL_READABLE);
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+}
+
+TEST_F(WatcherTest, WatchDataPipeConsumerReadable) {
+  constexpr size_t kTestPipeCapacity = 64;
+  MojoHandle producer, consumer;
+  CreateDataPipe(&producer, &consumer, kTestPipeCapacity);
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+  WatchHelper helper;
+  int num_expected_notifications = 1;
+  const uintptr_t readable_consumer_context = helper.CreateContext(base::Bind(
+      [](base::WaitableEvent* event, int* expected_count, MojoResult result,
+         MojoHandleSignalsState state) {
+        EXPECT_GT(*expected_count, 0);
+        *expected_count -= 1;
+
+        EXPECT_EQ(MOJO_RESULT_OK, result);
+        event->Signal();
+      },
+      &event, &num_expected_notifications));
+
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_READABLE,
+                                      readable_consumer_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  const char kMessage1[] = "hey hey hey hey";
+  const char kMessage2[] = "i said hey";
+  const char kMessage3[] = "what's goin' on?";
+
+  // Writing to |producer| multiple times should notify exactly once.
+  WriteData(producer, kMessage1);
+  WriteData(producer, kMessage2);
+  event.Wait();
+
+  // This also shouldn't fire a notification; the watcher is still disarmed.
+  WriteData(producer, kMessage3);
+
+  // Arming should fail with relevant information.
+  constexpr size_t kMaxReadyContexts = 10;
+  uint32_t num_ready_contexts = kMaxReadyContexts;
+  uintptr_t ready_contexts[kMaxReadyContexts];
+  MojoResult ready_results[kMaxReadyContexts];
+  MojoHandleSignalsState ready_states[kMaxReadyContexts];
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+                           ready_results, ready_states));
+  EXPECT_EQ(1u, num_ready_contexts);
+  EXPECT_EQ(readable_consumer_context, ready_contexts[0]);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+
+  // Flush the three messages from above.
+  EXPECT_EQ(kMessage1, ReadData(consumer, sizeof(kMessage1) - 1));
+  EXPECT_EQ(kMessage2, ReadData(consumer, sizeof(kMessage2) - 1));
+  EXPECT_EQ(kMessage3, ReadData(consumer, sizeof(kMessage3) - 1));
+
+  // Now we can rearm the watcher.
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
+}
+
+TEST_F(WatcherTest, WatchDataPipeConsumerNewDataReadable) {
+  constexpr size_t kTestPipeCapacity = 64;
+  MojoHandle producer, consumer;
+  CreateDataPipe(&producer, &consumer, kTestPipeCapacity);
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+  WatchHelper helper;
+  int num_new_data_notifications = 0;
+  const uintptr_t new_data_context = helper.CreateContext(base::Bind(
+      [](base::WaitableEvent* event, int* notification_count, MojoResult result,
+         MojoHandleSignalsState state) {
+        *notification_count += 1;
+
+        EXPECT_EQ(MOJO_RESULT_OK, result);
+        event->Signal();
+      },
+      &event, &num_new_data_notifications));
+
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
+                      new_data_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  const char kMessage1[] = "hey hey hey hey";
+  const char kMessage2[] = "i said hey";
+  const char kMessage3[] = "what's goin' on?";
+
+  // Writing to |producer| multiple times should notify exactly once.
+  WriteData(producer, kMessage1);
+  WriteData(producer, kMessage2);
+  event.Wait();
+
+  // This also shouldn't fire a notification; the watcher is still disarmed.
+  WriteData(producer, kMessage3);
+
+  // Arming should fail with relevant information.
+  constexpr size_t kMaxReadyContexts = 10;
+  uint32_t num_ready_contexts = kMaxReadyContexts;
+  uintptr_t ready_contexts[kMaxReadyContexts];
+  MojoResult ready_results[kMaxReadyContexts];
+  MojoHandleSignalsState ready_states[kMaxReadyContexts];
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+                           ready_results, ready_states));
+  EXPECT_EQ(1u, num_ready_contexts);
+  EXPECT_EQ(new_data_context, ready_contexts[0]);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+
+  // Attempt to read more data than is available. Should fail but clear the
+  // NEW_DATA_READABLE signal.
+  char large_buffer[512];
+  uint32_t large_read_size = 512;
+  EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE,
+            MojoReadData(consumer, large_buffer, &large_read_size,
+                         MOJO_READ_DATA_FLAG_ALL_OR_NONE));
+
+  // Attempt to arm again. Should succeed.
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  // Write more data. Should notify.
+  event.Reset();
+  WriteData(producer, kMessage1);
+  event.Wait();
+
+  // Reading some data should clear NEW_DATA_READABLE again so we can rearm.
+  EXPECT_EQ(kMessage1, ReadData(consumer, sizeof(kMessage1) - 1));
+
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  EXPECT_EQ(2, num_new_data_notifications);
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
+}
+
+TEST_F(WatcherTest, WatchDataPipeProducerWritable) {
+  constexpr size_t kTestPipeCapacity = 8;
+  MojoHandle producer, consumer;
+  CreateDataPipe(&producer, &consumer, kTestPipeCapacity);
+
+  // Half the capacity of the data pipe.
+  const char kTestData[] = "aaaa";
+  static_assert((sizeof(kTestData) - 1) * 2 == kTestPipeCapacity,
+                "Invalid test data for this test.");
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+  WatchHelper helper;
+  int num_expected_notifications = 1;
+  const uintptr_t writable_producer_context = helper.CreateContext(base::Bind(
+      [](base::WaitableEvent* event, int* expected_count, MojoResult result,
+         MojoHandleSignalsState state) {
+        EXPECT_GT(*expected_count, 0);
+        *expected_count -= 1;
+
+        EXPECT_EQ(MOJO_RESULT_OK, result);
+        event->Signal();
+      },
+      &event, &num_expected_notifications));
+
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, producer, MOJO_HANDLE_SIGNAL_WRITABLE,
+                                      writable_producer_context));
+
+  // The producer is already writable, so arming should fail with relevant
+  // information.
+  constexpr size_t kMaxReadyContexts = 10;
+  uint32_t num_ready_contexts = kMaxReadyContexts;
+  uintptr_t ready_contexts[kMaxReadyContexts];
+  MojoResult ready_results[kMaxReadyContexts];
+  MojoHandleSignalsState ready_states[kMaxReadyContexts];
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+                           ready_results, ready_states));
+  EXPECT_EQ(1u, num_ready_contexts);
+  EXPECT_EQ(writable_producer_context, ready_contexts[0]);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+  EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+
+  // Write some data, but don't fill the pipe yet. Arming should fail again.
+  WriteData(producer, kTestData);
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+                           ready_results, ready_states));
+  EXPECT_EQ(1u, num_ready_contexts);
+  EXPECT_EQ(writable_producer_context, ready_contexts[0]);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+  EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+
+  // Write more data, filling the pipe to capacity. Arming should succeed now.
+  WriteData(producer, kTestData);
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  // Now read from the pipe, making the producer writable again. Should notify.
+  EXPECT_EQ(kTestData, ReadData(consumer, sizeof(kTestData) - 1));
+  event.Wait();
+
+  // Arming should fail again.
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+                           ready_results, ready_states));
+  EXPECT_EQ(1u, num_ready_contexts);
+  EXPECT_EQ(writable_producer_context, ready_contexts[0]);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+  EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+
+  // Fill the pipe once more and arm the watcher. Should succeed.
+  WriteData(producer, kTestData);
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
+};
+
+TEST_F(WatcherTest, CloseWatchedDataPipeConsumerHandle) {
+  constexpr size_t kTestPipeCapacity = 8;
+  MojoHandle producer, consumer;
+  CreateDataPipe(&producer, &consumer, kTestPipeCapacity);
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+  WatchHelper helper;
+  const uintptr_t readable_consumer_context = helper.CreateContextWithCancel(
+      WatchHelper::ContextCallback(),
+      base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event));
+
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_READABLE,
+                                      readable_consumer_context));
+
+  // Closing the consumer should fire a cancellation notification.
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
+  event.Wait();
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+}
+
+TEST_F(WatcherTest, CloseWatcherDataPipeConsumerHandlePeer) {
+  constexpr size_t kTestPipeCapacity = 8;
+  MojoHandle producer, consumer;
+  CreateDataPipe(&producer, &consumer, kTestPipeCapacity);
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+  WatchHelper helper;
+  const uintptr_t readable_consumer_context = helper.CreateContext(base::Bind(
+      [](base::WaitableEvent* event, MojoResult result,
+         MojoHandleSignalsState state) {
+        EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
+        event->Signal();
+      },
+      &event));
+
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_READABLE,
+                                      readable_consumer_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  // Closing the producer should fire a notification for an unsatisfiable watch.
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
+  event.Wait();
+
+  // Now attempt to rearm and expect appropriate error feedback.
+  constexpr size_t kMaxReadyContexts = 10;
+  uint32_t num_ready_contexts = kMaxReadyContexts;
+  uintptr_t ready_contexts[kMaxReadyContexts];
+  MojoResult ready_results[kMaxReadyContexts];
+  MojoHandleSignalsState ready_states[kMaxReadyContexts];
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+                           ready_results, ready_states));
+  EXPECT_EQ(1u, num_ready_contexts);
+  EXPECT_EQ(readable_consumer_context, ready_contexts[0]);
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]);
+  EXPECT_FALSE(ready_states[0].satisfiable_signals &
+               MOJO_HANDLE_SIGNAL_READABLE);
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
+}
+
+TEST_F(WatcherTest, CloseWatchedDataPipeProducerHandle) {
+  constexpr size_t kTestPipeCapacity = 8;
+  MojoHandle producer, consumer;
+  CreateDataPipe(&producer, &consumer, kTestPipeCapacity);
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+  WatchHelper helper;
+  const uintptr_t writable_producer_context = helper.CreateContextWithCancel(
+      WatchHelper::ContextCallback(),
+      base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event));
+
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, producer, MOJO_HANDLE_SIGNAL_WRITABLE,
+                                      writable_producer_context));
+
+  // Closing the consumer should fire a cancellation notification.
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
+  event.Wait();
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+}
+
+TEST_F(WatcherTest, CloseWatchedDataPipeProducerHandlePeer) {
+  constexpr size_t kTestPipeCapacity = 8;
+  MojoHandle producer, consumer;
+  CreateDataPipe(&producer, &consumer, kTestPipeCapacity);
+
+  const char kTestMessageFullCapacity[] = "xxxxxxxx";
+  static_assert(sizeof(kTestMessageFullCapacity) - 1 == kTestPipeCapacity,
+                "Invalid test message size for this test.");
+
+  // Make the pipe unwritable initially.
+  WriteData(producer, kTestMessageFullCapacity);
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+  WatchHelper helper;
+  const uintptr_t writable_producer_context = helper.CreateContext(base::Bind(
+      [](base::WaitableEvent* event, MojoResult result,
+         MojoHandleSignalsState state) {
+        EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
+        event->Signal();
+      },
+      &event));
+
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, producer, MOJO_HANDLE_SIGNAL_WRITABLE,
+                                      writable_producer_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  // Closing the consumer should fire a notification for an unsatisfiable watch,
+  // as the full data pipe can never be read from again and is therefore
+  // permanently full and unwritable.
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
+  event.Wait();
+
+  // Now attempt to rearm and expect appropriate error feedback.
+  constexpr size_t kMaxReadyContexts = 10;
+  uint32_t num_ready_contexts = kMaxReadyContexts;
+  uintptr_t ready_contexts[kMaxReadyContexts];
+  MojoResult ready_results[kMaxReadyContexts];
+  MojoHandleSignalsState ready_states[kMaxReadyContexts];
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+                           ready_results, ready_states));
+  EXPECT_EQ(1u, num_ready_contexts);
+  EXPECT_EQ(writable_producer_context, ready_contexts[0]);
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]);
+  EXPECT_FALSE(ready_states[0].satisfiable_signals &
+               MOJO_HANDLE_SIGNAL_WRITABLE);
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
+}
+
+TEST_F(WatcherTest, ArmWithNoWatches) {
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectNoNotification, &w));
+  EXPECT_EQ(MOJO_RESULT_NOT_FOUND,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+}
+
+TEST_F(WatcherTest, WatchDuplicateContext) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, 0));
+  EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
+            MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, 0));
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+}
+
+TEST_F(WatcherTest, CancelUnknownWatch) {
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectNoNotification, &w));
+  EXPECT_EQ(MOJO_RESULT_NOT_FOUND, MojoCancelWatch(w, 1234));
+}
+
+TEST_F(WatcherTest, ArmWithWatchAlreadySatisfied) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, a, MOJO_HANDLE_SIGNAL_WRITABLE, 0));
+
+  // |a| is always writable, so we can never arm this watcher.
+  constexpr size_t kMaxReadyContexts = 10;
+  uint32_t num_ready_contexts = kMaxReadyContexts;
+  uintptr_t ready_contexts[kMaxReadyContexts];
+  MojoResult ready_results[kMaxReadyContexts];
+  MojoHandleSignalsState ready_states[kMaxReadyContexts];
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+                           ready_results, ready_states));
+  EXPECT_EQ(1u, num_ready_contexts);
+  EXPECT_EQ(0u, ready_contexts[0]);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+  EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+}
+
+TEST_F(WatcherTest, ArmWithWatchAlreadyUnsatisfiable) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, 0));
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+
+  // |b| is closed and never wrote any messages, so |a| won't be readable again.
+  // MojoArmWatcher() should fail, incidcating as much.
+  constexpr size_t kMaxReadyContexts = 10;
+  uint32_t num_ready_contexts = kMaxReadyContexts;
+  uintptr_t ready_contexts[kMaxReadyContexts];
+  MojoResult ready_results[kMaxReadyContexts];
+  MojoHandleSignalsState ready_states[kMaxReadyContexts];
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+                           ready_results, ready_states));
+  EXPECT_EQ(1u, num_ready_contexts);
+  EXPECT_EQ(0u, ready_contexts[0]);
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]);
+  EXPECT_TRUE(ready_states[0].satisfied_signals &
+              MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+  EXPECT_FALSE(ready_states[0].satisfiable_signals &
+               MOJO_HANDLE_SIGNAL_READABLE);
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+}
+
+TEST_F(WatcherTest, MultipleWatches) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  base::WaitableEvent a_event(base::WaitableEvent::ResetPolicy::MANUAL,
+                              base::WaitableEvent::InitialState::NOT_SIGNALED);
+  base::WaitableEvent b_event(base::WaitableEvent::ResetPolicy::MANUAL,
+                              base::WaitableEvent::InitialState::NOT_SIGNALED);
+  WatchHelper helper;
+  int num_a_notifications = 0;
+  int num_b_notifications = 0;
+  auto notify_callback =
+      base::Bind([](base::WaitableEvent* event, int* notification_count,
+                    MojoResult result, MojoHandleSignalsState state) {
+        *notification_count += 1;
+        EXPECT_EQ(MOJO_RESULT_OK, result);
+        event->Signal();
+      });
+  uintptr_t readable_a_context = helper.CreateContext(
+      base::Bind(notify_callback, &a_event, &num_a_notifications));
+  uintptr_t readable_b_context = helper.CreateContext(
+      base::Bind(notify_callback, &b_event, &num_b_notifications));
+
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+
+  // Add two independent watch contexts to watch for |a| or |b| readability.
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, readable_b_context));
+
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  const char kMessage1[] = "things are happening";
+  const char kMessage2[] = "ok. ok. ok. ok.";
+  const char kMessage3[] = "plz wake up";
+
+  // Writing to |b| should signal |a|'s watch.
+  WriteMessage(b, kMessage1);
+  a_event.Wait();
+  a_event.Reset();
+
+  // Subsequent messages on |b| should not trigger another notification.
+  WriteMessage(b, kMessage2);
+  WriteMessage(b, kMessage3);
+
+  // Messages on |a| also shouldn't trigger |b|'s notification, since the
+  // watcher should be disarmed by now.
+  WriteMessage(a, kMessage1);
+  WriteMessage(a, kMessage2);
+  WriteMessage(a, kMessage3);
+
+  // Arming should fail. Since we only ask for at most one context's information
+  // that's all we should get back. Which one we get is unspecified.
+  constexpr size_t kMaxReadyContexts = 10;
+  uint32_t num_ready_contexts = 1;
+  uintptr_t ready_contexts[kMaxReadyContexts];
+  MojoResult ready_results[kMaxReadyContexts];
+  MojoHandleSignalsState ready_states[kMaxReadyContexts];
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+                           ready_results, ready_states));
+  EXPECT_EQ(1u, num_ready_contexts);
+  EXPECT_TRUE(ready_contexts[0] == readable_a_context ||
+              ready_contexts[0] == readable_b_context);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+  EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+
+  // Now try arming again, verifying that both contexts are returned.
+  num_ready_contexts = kMaxReadyContexts;
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+                           ready_results, ready_states));
+  EXPECT_EQ(2u, num_ready_contexts);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[1]);
+  EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+  EXPECT_TRUE(ready_states[1].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+  EXPECT_TRUE((ready_contexts[0] == readable_a_context &&
+               ready_contexts[1] == readable_b_context) ||
+              (ready_contexts[0] == readable_b_context &&
+               ready_contexts[1] == readable_a_context));
+
+  // Flush out the test messages so we should be able to successfully rearm.
+  EXPECT_EQ(kMessage1, ReadMessage(a));
+  EXPECT_EQ(kMessage2, ReadMessage(a));
+  EXPECT_EQ(kMessage3, ReadMessage(a));
+  EXPECT_EQ(kMessage1, ReadMessage(b));
+  EXPECT_EQ(kMessage2, ReadMessage(b));
+  EXPECT_EQ(kMessage3, ReadMessage(b));
+
+  // Add a watch which is always satisfied, so we can't arm. Arming should fail
+  // with only this new watch's information.
+  uintptr_t writable_c_context = helper.CreateContext(base::Bind(
+      [](MojoResult result, MojoHandleSignalsState state) { NOTREACHED(); }));
+  MojoHandle c, d;
+  CreateMessagePipe(&c, &d);
+
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, c, MOJO_HANDLE_SIGNAL_WRITABLE, writable_c_context));
+  num_ready_contexts = kMaxReadyContexts;
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+                           ready_results, ready_states));
+  EXPECT_EQ(1u, num_ready_contexts);
+  EXPECT_EQ(writable_c_context, ready_contexts[0]);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+  EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+
+  // Cancel the new watch and arming should succeed once again.
+  EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, writable_c_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
+}
+
+TEST_F(WatcherTest, NotifyOtherFromNotificationCallback) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  static const char kTestMessageToA[] = "hello a";
+  static const char kTestMessageToB[] = "hello b";
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+  WatchHelper helper;
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+
+  uintptr_t readable_a_context = helper.CreateContext(base::Bind(
+      [](MojoHandle w, MojoHandle a, MojoResult result,
+         MojoHandleSignalsState state) {
+        EXPECT_EQ(MOJO_RESULT_OK, result);
+        EXPECT_EQ("hello a", ReadMessage(a));
+
+        // Re-arm the watcher and signal |b|.
+        EXPECT_EQ(MOJO_RESULT_OK,
+                  MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+        WriteMessage(a, kTestMessageToB);
+      },
+      w, a));
+
+  uintptr_t readable_b_context = helper.CreateContext(base::Bind(
+      [](base::WaitableEvent* event, MojoHandle w, MojoHandle b,
+         MojoResult result, MojoHandleSignalsState state) {
+        EXPECT_EQ(MOJO_RESULT_OK, result);
+        EXPECT_EQ(kTestMessageToB, ReadMessage(b));
+        EXPECT_EQ(MOJO_RESULT_OK,
+                  MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+        event->Signal();
+      },
+      &event, w, b));
+
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, readable_b_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  // Send a message to |a|. The relevant watch context should be notified, and
+  // should in turn send a message to |b|, waking up the other context. The
+  // second context signals |event|.
+  WriteMessage(b, kTestMessageToA);
+  event.Wait();
+}
+
+TEST_F(WatcherTest, NotifySelfFromNotificationCallback) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  static const char kTestMessageToA[] = "hello a";
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+  WatchHelper helper;
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+
+  int expected_notifications = 10;
+  uintptr_t readable_a_context = helper.CreateContext(base::Bind(
+      [](int* expected_count, MojoHandle w, MojoHandle a, MojoHandle b,
+         base::WaitableEvent* event, MojoResult result,
+         MojoHandleSignalsState state) {
+        EXPECT_EQ(MOJO_RESULT_OK, result);
+        EXPECT_EQ("hello a", ReadMessage(a));
+
+        EXPECT_GT(*expected_count, 0);
+        *expected_count -= 1;
+        if (*expected_count == 0) {
+          event->Signal();
+          return;
+        } else {
+          // Re-arm the watcher and signal |a| again.
+          EXPECT_EQ(MOJO_RESULT_OK,
+                    MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+          WriteMessage(b, kTestMessageToA);
+        }
+      },
+      &expected_notifications, w, a, b, &event));
+
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  // Send a message to |a|. When the watch above is notified, it will rearm and
+  // send another message to |a|. This will happen until
+  // |expected_notifications| reaches 0.
+  WriteMessage(b, kTestMessageToA);
+  event.Wait();
+}
+
+TEST_F(WatcherTest, ImplicitCancelOtherFromNotificationCallback) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  MojoHandle c, d;
+  CreateMessagePipe(&c, &d);
+
+  static const char kTestMessageToA[] = "hi a";
+  static const char kTestMessageToC[] = "hi c";
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+  WatchHelper helper;
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+
+  uintptr_t readable_a_context = helper.CreateContextWithCancel(
+      base::Bind([](MojoResult result, MojoHandleSignalsState state) {
+        NOTREACHED();
+      }),
+      base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event));
+
+  uintptr_t readable_c_context = helper.CreateContext(base::Bind(
+      [](MojoHandle w, MojoHandle a, MojoHandle b, MojoHandle c,
+         MojoResult result, MojoHandleSignalsState state) {
+        EXPECT_EQ(MOJO_RESULT_OK, result);
+        EXPECT_EQ(kTestMessageToC, ReadMessage(c));
+
+        // Now rearm the watcher.
+        EXPECT_EQ(MOJO_RESULT_OK,
+                  MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+        // Must result in exactly ONE notification on the above context, for
+        // CANCELLED only. Because we cannot dispatch notifications until the
+        // stack unwinds, and because we must never dispatch non-cancellation
+        // notifications for a handle once it's been closed, we must be certain
+        // that cancellation due to closure preemptively invalidates any
+        // pending non-cancellation notifications queued on the current
+        // RequestContext, such as the one resulting from the WriteMessage here.
+        WriteMessage(b, kTestMessageToA);
+        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+
+        // Rearming should be fine since |a|'s watch should already be
+        // implicitly cancelled (even though the notification will not have
+        // been invoked yet.)
+        EXPECT_EQ(MOJO_RESULT_OK,
+                  MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+        // Nothing interesting should happen as a result of this.
+        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+      },
+      w, a, b, c));
+
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, c, MOJO_HANDLE_SIGNAL_READABLE, readable_c_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  WriteMessage(d, kTestMessageToC);
+  event.Wait();
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
+}
+
+TEST_F(WatcherTest, ExplicitCancelOtherFromNotificationCallback) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  MojoHandle c, d;
+  CreateMessagePipe(&c, &d);
+
+  static const char kTestMessageToA[] = "hi a";
+  static const char kTestMessageToC[] = "hi c";
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+  WatchHelper helper;
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+
+  uintptr_t readable_a_context = helper.CreateContext(base::Bind(
+      [](MojoResult result, MojoHandleSignalsState state) { NOTREACHED(); }));
+
+  uintptr_t readable_c_context = helper.CreateContext(base::Bind(
+      [](base::WaitableEvent* event, uintptr_t readable_a_context, MojoHandle w,
+         MojoHandle a, MojoHandle b, MojoHandle c, MojoResult result,
+         MojoHandleSignalsState state) {
+        EXPECT_EQ(MOJO_RESULT_OK, result);
+        EXPECT_EQ(kTestMessageToC, ReadMessage(c));
+
+        // Now rearm the watcher.
+        EXPECT_EQ(MOJO_RESULT_OK,
+                  MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+        // Should result in no notifications on the above context, because the
+        // watch will have been cancelled by the time the notification callback
+        // can execute.
+        WriteMessage(b, kTestMessageToA);
+        WriteMessage(b, kTestMessageToA);
+        EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, readable_a_context));
+
+        // Rearming should be fine now.
+        EXPECT_EQ(MOJO_RESULT_OK,
+                  MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+        // Nothing interesting should happen as a result of these.
+        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+
+        event->Signal();
+      },
+      &event, readable_a_context, w, a, b, c));
+
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, c, MOJO_HANDLE_SIGNAL_READABLE, readable_c_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  WriteMessage(d, kTestMessageToC);
+  event.Wait();
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
+}
+
+TEST_F(WatcherTest, NestedCancellation) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  MojoHandle c, d;
+  CreateMessagePipe(&c, &d);
+
+  static const char kTestMessageToA[] = "hey a";
+  static const char kTestMessageToC[] = "hey c";
+  static const char kTestMessageToD[] = "hey d";
+
+  // This is a tricky test. It establishes a watch on |b| using one watcher and
+  // watches on |c| and |d| using another watcher.
+  //
+  // A message is written to |d| to wake up |c|'s watch, and the notification
+  // handler for that event does the following:
+  //   1. Writes to |a| to eventually wake up |b|'s watcher.
+  //   2. Rearms |c|'s watcher.
+  //   3. Writes to |d| to eventually wake up |c|'s watcher again.
+  //
+  // Meanwhile, |b|'s watch notification handler cancels |c|'s watch altogether
+  // before writing to |c| to wake up |d|.
+  //
+  // The net result should be that |c|'s context only gets notified once (from
+  // the first write to |d| above) and everyone else gets notified as expected.
+
+  MojoHandle b_watcher;
+  MojoHandle cd_watcher;
+  WatchHelper helper;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&b_watcher));
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&cd_watcher));
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+  uintptr_t readable_d_context = helper.CreateContext(base::Bind(
+      [](base::WaitableEvent* event, MojoHandle d, MojoResult result,
+         MojoHandleSignalsState state) {
+        EXPECT_EQ(MOJO_RESULT_OK, result);
+        EXPECT_EQ(kTestMessageToD, ReadMessage(d));
+        event->Signal();
+      },
+      &event, d));
+
+  static int num_expected_c_notifications = 1;
+  uintptr_t readable_c_context = helper.CreateContext(base::Bind(
+      [](MojoHandle cd_watcher, MojoHandle a, MojoHandle c, MojoHandle d,
+         MojoResult result, MojoHandleSignalsState state) {
+        EXPECT_EQ(MOJO_RESULT_OK, result);
+        EXPECT_GT(num_expected_c_notifications--, 0);
+
+        // Trigger an eventual |readable_b_context| notification.
+        WriteMessage(a, kTestMessageToA);
+
+        EXPECT_EQ(kTestMessageToC, ReadMessage(c));
+        EXPECT_EQ(MOJO_RESULT_OK, MojoArmWatcher(cd_watcher, nullptr, nullptr,
+                                                 nullptr, nullptr));
+
+        // Trigger another eventual |readable_c_context| notification.
+        WriteMessage(d, kTestMessageToC);
+      },
+      cd_watcher, a, c, d));
+
+  uintptr_t readable_b_context = helper.CreateContext(base::Bind(
+      [](MojoHandle cd_watcher, uintptr_t readable_c_context, MojoHandle c,
+         MojoResult result, MojoHandleSignalsState state) {
+        EXPECT_EQ(MOJO_RESULT_OK,
+                  MojoCancelWatch(cd_watcher, readable_c_context));
+
+        EXPECT_EQ(MOJO_RESULT_OK, MojoArmWatcher(cd_watcher, nullptr, nullptr,
+                                                 nullptr, nullptr));
+
+        WriteMessage(c, kTestMessageToD);
+      },
+      cd_watcher, readable_c_context, c));
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(b_watcher, b, MOJO_HANDLE_SIGNAL_READABLE,
+                                      readable_b_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(cd_watcher, c, MOJO_HANDLE_SIGNAL_READABLE,
+                      readable_c_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(cd_watcher, d, MOJO_HANDLE_SIGNAL_READABLE,
+                      readable_d_context));
+
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(b_watcher, nullptr, nullptr, nullptr, nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(cd_watcher, nullptr, nullptr, nullptr, nullptr));
+
+  WriteMessage(d, kTestMessageToC);
+  event.Wait();
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(cd_watcher));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b_watcher));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
+}
+
+TEST_F(WatcherTest, CancelSelfInNotificationCallback) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  static const char kTestMessageToA[] = "hey a";
+
+  MojoHandle w;
+  WatchHelper helper;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+  static uintptr_t readable_a_context = helper.CreateContext(base::Bind(
+      [](base::WaitableEvent* event, MojoHandle w, MojoHandle a,
+         MojoResult result, MojoHandleSignalsState state) {
+        EXPECT_EQ(MOJO_RESULT_OK, result);
+
+        // There should be no problem cancelling this watch from its own
+        // notification invocation.
+        EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, readable_a_context));
+        EXPECT_EQ(kTestMessageToA, ReadMessage(a));
+
+        // Arming should fail because there are no longer any registered
+        // watches on the watcher.
+        EXPECT_EQ(MOJO_RESULT_NOT_FOUND,
+                  MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+        // And closing |a| should be fine (and should not invoke this
+        // notification with MOJO_RESULT_CANCELLED) for the same reason.
+        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+
+        event->Signal();
+      },
+      &event, w, a));
+
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  WriteMessage(b, kTestMessageToA);
+  event.Wait();
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+}
+
+TEST_F(WatcherTest, CloseWatcherInNotificationCallback) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  static const char kTestMessageToA1[] = "hey a";
+  static const char kTestMessageToA2[] = "hey a again";
+
+  MojoHandle w;
+  WatchHelper helper;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+  uintptr_t readable_a_context = helper.CreateContext(base::Bind(
+      [](base::WaitableEvent* event, MojoHandle w, MojoHandle a, MojoHandle b,
+         MojoResult result, MojoHandleSignalsState state) {
+        EXPECT_EQ(MOJO_RESULT_OK, result);
+        EXPECT_EQ(kTestMessageToA1, ReadMessage(a));
+        EXPECT_EQ(MOJO_RESULT_OK,
+                  MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+        // There should be no problem closing this watcher from its own
+        // notification callback.
+        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+
+        // And these should not trigger more notifications, because |w| has been
+        // closed already.
+        WriteMessage(b, kTestMessageToA2);
+        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+
+        event->Signal();
+      },
+      &event, w, a, b));
+
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  WriteMessage(b, kTestMessageToA1);
+  event.Wait();
+}
+
+TEST_F(WatcherTest, CloseWatcherAfterImplicitCancel) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  static const char kTestMessageToA[] = "hey a";
+
+  MojoHandle w;
+  WatchHelper helper;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+  uintptr_t readable_a_context = helper.CreateContext(base::Bind(
+      [](base::WaitableEvent* event, MojoHandle w, MojoHandle a,
+         MojoResult result, MojoHandleSignalsState state) {
+        EXPECT_EQ(MOJO_RESULT_OK, result);
+        EXPECT_EQ(kTestMessageToA, ReadMessage(a));
+        EXPECT_EQ(MOJO_RESULT_OK,
+                  MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+        // This will cue up a notification for |MOJO_RESULT_CANCELLED|...
+        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+
+        // ...but it should never fire because we close the watcher here.
+        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+
+        event->Signal();
+      },
+      &event, w, a));
+
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  WriteMessage(b, kTestMessageToA);
+  event.Wait();
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+}
+
+TEST_F(WatcherTest, OtherThreadCancelDuringNotification) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  static const char kTestMessageToA[] = "hey a";
+
+  MojoHandle w;
+  WatchHelper helper;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+
+  base::WaitableEvent wait_for_notification(
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+  base::WaitableEvent wait_for_cancellation(
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+  static bool callback_done = false;
+  uintptr_t readable_a_context = helper.CreateContextWithCancel(
+      base::Bind(
+          [](base::WaitableEvent* wait_for_notification, MojoHandle w,
+             MojoHandle a, MojoResult result, MojoHandleSignalsState state) {
+            EXPECT_EQ(MOJO_RESULT_OK, result);
+            EXPECT_EQ(kTestMessageToA, ReadMessage(a));
+
+            wait_for_notification->Signal();
+
+            // Give the other thread sufficient time to race with the completion
+            // of this callback. There should be no race, since the cancellation
+            // notification must be mutually exclusive to this notification.
+            base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
+
+            callback_done = true;
+          },
+          &wait_for_notification, w, a),
+      base::Bind(
+          [](base::WaitableEvent* wait_for_cancellation) {
+            EXPECT_TRUE(callback_done);
+            wait_for_cancellation->Signal();
+          },
+          &wait_for_cancellation));
+
+  ThreadedRunner runner(base::Bind(
+      [](base::WaitableEvent* wait_for_notification,
+         base::WaitableEvent* wait_for_cancellation, MojoHandle w,
+         uintptr_t readable_a_context) {
+        wait_for_notification->Wait();
+
+        // Cancel the watch while the notification is still running.
+        EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, readable_a_context));
+
+        wait_for_cancellation->Wait();
+
+        EXPECT_TRUE(callback_done);
+      },
+      &wait_for_notification, &wait_for_cancellation, w, readable_a_context));
+  runner.Start();
+
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr));
+
+  WriteMessage(b, kTestMessageToA);
+  runner.Join();
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+}
+
+TEST_F(WatcherTest, WatchesCancelEachOtherFromNotifications) {
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  static const char kTestMessageToA[] = "hey a";
+  static const char kTestMessageToB[] = "hey b";
+
+  base::WaitableEvent wait_for_a_to_notify(
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED);
+  base::WaitableEvent wait_for_b_to_notify(
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED);
+  base::WaitableEvent wait_for_a_to_cancel(
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED);
+  base::WaitableEvent wait_for_b_to_cancel(
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+  MojoHandle a_watcher;
+  MojoHandle b_watcher;
+  WatchHelper helper;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&a_watcher));
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&b_watcher));
+
+  // We set up two watchers, one on |a| and one on |b|. They cancel each other
+  // from within their respective watch notifications. This should be safe,
+  // i.e., it should not deadlock, in spite of the fact that we also guarantee
+  // mutually exclusive notification execution (including cancellations) on any
+  // given watch.
+  bool a_cancelled = false;
+  bool b_cancelled = false;
+  static uintptr_t readable_b_context;
+  uintptr_t readable_a_context = helper.CreateContextWithCancel(
+      base::Bind(
+          [](base::WaitableEvent* wait_for_a_to_notify,
+             base::WaitableEvent* wait_for_b_to_notify, MojoHandle b_watcher,
+             MojoHandle a, MojoResult result, MojoHandleSignalsState state) {
+            EXPECT_EQ(MOJO_RESULT_OK, result);
+            EXPECT_EQ(kTestMessageToA, ReadMessage(a));
+            wait_for_a_to_notify->Signal();
+            wait_for_b_to_notify->Wait();
+            EXPECT_EQ(MOJO_RESULT_OK,
+                      MojoCancelWatch(b_watcher, readable_b_context));
+            EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b_watcher));
+          },
+          &wait_for_a_to_notify, &wait_for_b_to_notify, b_watcher, a),
+      base::Bind(
+          [](base::WaitableEvent* wait_for_a_to_cancel,
+             base::WaitableEvent* wait_for_b_to_cancel, bool* a_cancelled) {
+            *a_cancelled = true;
+            wait_for_a_to_cancel->Signal();
+            wait_for_b_to_cancel->Wait();
+          },
+          &wait_for_a_to_cancel, &wait_for_b_to_cancel, &a_cancelled));
+
+  readable_b_context = helper.CreateContextWithCancel(
+      base::Bind(
+          [](base::WaitableEvent* wait_for_a_to_notify,
+             base::WaitableEvent* wait_for_b_to_notify,
+             uintptr_t readable_a_context, MojoHandle a_watcher, MojoHandle b,
+             MojoResult result, MojoHandleSignalsState state) {
+            EXPECT_EQ(MOJO_RESULT_OK, result);
+            EXPECT_EQ(kTestMessageToB, ReadMessage(b));
+            wait_for_b_to_notify->Signal();
+            wait_for_a_to_notify->Wait();
+            EXPECT_EQ(MOJO_RESULT_OK,
+                      MojoCancelWatch(a_watcher, readable_a_context));
+            EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a_watcher));
+          },
+          &wait_for_a_to_notify, &wait_for_b_to_notify, readable_a_context,
+          a_watcher, b),
+      base::Bind(
+          [](base::WaitableEvent* wait_for_a_to_cancel,
+             base::WaitableEvent* wait_for_b_to_cancel, bool* b_cancelled) {
+            *b_cancelled = true;
+            wait_for_b_to_cancel->Signal();
+            wait_for_a_to_cancel->Wait();
+          },
+          &wait_for_a_to_cancel, &wait_for_b_to_cancel, &b_cancelled));
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(a_watcher, a, MOJO_HANDLE_SIGNAL_READABLE,
+                                      readable_a_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(a_watcher, nullptr, nullptr, nullptr, nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(b_watcher, b, MOJO_HANDLE_SIGNAL_READABLE,
+                                      readable_b_context));
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoArmWatcher(b_watcher, nullptr, nullptr, nullptr, nullptr));
+
+  ThreadedRunner runner(
+      base::Bind([](MojoHandle b) { WriteMessage(b, kTestMessageToA); }, b));
+  runner.Start();
+
+  WriteMessage(a, kTestMessageToB);
+
+  wait_for_a_to_cancel.Wait();
+  wait_for_b_to_cancel.Wait();
+  runner.Join();
+
+  EXPECT_TRUE(a_cancelled);
+  EXPECT_TRUE(b_cancelled);
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+}
+
+TEST_F(WatcherTest, AlwaysCancel) {
+  // Basic sanity check to ensure that all possible ways to cancel a watch
+  // result in a final MOJO_RESULT_CANCELLED notification.
+
+  MojoHandle a, b;
+  CreateMessagePipe(&a, &b);
+
+  MojoHandle w;
+  WatchHelper helper;
+  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w));
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+  const base::Closure signal_event =
+      base::Bind(&base::WaitableEvent::Signal, base::Unretained(&event));
+
+  // Cancel via |MojoCancelWatch()|.
+  uintptr_t context = helper.CreateContextWithCancel(
+      WatchHelper::ContextCallback(), signal_event);
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, context));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, context));
+  event.Wait();
+  event.Reset();
+
+  // Cancel by closing the watched handle.
+  context = helper.CreateContextWithCancel(WatchHelper::ContextCallback(),
+                                           signal_event);
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, context));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+  event.Wait();
+  event.Reset();
+
+  // Cancel by closing the watcher handle.
+  context = helper.CreateContextWithCancel(WatchHelper::ContextCallback(),
+                                           signal_event);
+  EXPECT_EQ(MOJO_RESULT_OK,
+            MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, context));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+  event.Wait();
+
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+}
+
+TEST_F(WatcherTest, ArmFailureCirculation) {
+  // Sanity check to ensure that all ready handles will eventually be returned
+  // over a finite number of calls to MojoArmWatcher().
+
+  constexpr size_t kNumTestPipes = 100;
+  constexpr size_t kNumTestHandles = kNumTestPipes * 2;
+  MojoHandle handles[kNumTestHandles];
+
+  // Create a bunch of pipes and make sure they're all readable.
+  for (size_t i = 0; i < kNumTestPipes; ++i) {
+    CreateMessagePipe(&handles[i], &handles[i + kNumTestPipes]);
+    WriteMessage(handles[i], "hey");
+    WriteMessage(handles[i + kNumTestPipes], "hay");
+    WaitForSignals(handles[i], MOJO_HANDLE_SIGNAL_READABLE);
+    WaitForSignals(handles[i + kNumTestPipes], MOJO_HANDLE_SIGNAL_READABLE);
+  }
+
+  // Create a watcher and watch all of them.
+  MojoHandle w;
+  EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w));
+  for (size_t i = 0; i < kNumTestHandles; ++i) {
+    EXPECT_EQ(MOJO_RESULT_OK,
+              MojoWatch(w, handles[i], MOJO_HANDLE_SIGNAL_READABLE, i));
+  }
+
+  // Keep trying to arm |w| until every watch gets an entry in |ready_contexts|.
+  // If MojoArmWatcher() is well-behaved, this should terminate eventually.
+  std::set<uintptr_t> ready_contexts;
+  while (ready_contexts.size() < kNumTestHandles) {
+    uint32_t num_ready_contexts = 1;
+    uintptr_t ready_context;
+    MojoResult ready_result;
+    MojoHandleSignalsState ready_state;
+    EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+              MojoArmWatcher(w, &num_ready_contexts, &ready_context,
+                             &ready_result, &ready_state));
+    EXPECT_EQ(1u, num_ready_contexts);
+    EXPECT_EQ(MOJO_RESULT_OK, ready_result);
+    ready_contexts.insert(ready_context);
+  }
+
+  for (size_t i = 0; i < kNumTestHandles; ++i)
+    EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handles[i]));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w));
+}
+
+}  // namespace
+}  // namespace edk
+}  // namespace mojo
diff --git a/mojo/edk/test/mojo_test_base.cc b/mojo/edk/test/mojo_test_base.cc
index f1032d7..71a5e3b 100644
--- a/mojo/edk/test/mojo_test_base.cc
+++ b/mojo/edk/test/mojo_test_base.cc
@@ -5,13 +5,17 @@
 #include "mojo/edk/test/mojo_test_base.h"
 
 #include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
+#include "base/synchronization/waitable_event.h"
 #include "mojo/edk/embedder/embedder.h"
 #include "mojo/edk/system/handle_signals_state.h"
 #include "mojo/public/c/system/buffer.h"
 #include "mojo/public/c/system/data_pipe.h"
 #include "mojo/public/c/system/functions.h"
+#include "mojo/public/c/system/watcher.h"
+#include "mojo/public/cpp/system/wait.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 #if defined(OS_MACOSX) && !defined(OS_IOS)
@@ -22,7 +26,6 @@
 namespace edk {
 namespace test {
 
-
 #if defined(OS_MACOSX) && !defined(OS_IOS)
 namespace {
 base::MachPortBroker* g_mach_broker = nullptr;
@@ -130,9 +133,7 @@
     MojoHandle mp,
     MojoHandle* handles,
     uint32_t expected_num_handles) {
-  CHECK_EQ(MojoWait(mp, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE,
-                    nullptr),
-           MOJO_RESULT_OK);
+  CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK);
 
   uint32_t message_size = 0;
   uint32_t num_handles = 0;
@@ -154,9 +155,7 @@
 // static
 std::string MojoTestBase::ReadMessageWithOptionalHandle(MojoHandle mp,
                                                         MojoHandle* handle) {
-  CHECK_EQ(MojoWait(mp, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE,
-                    nullptr),
-           MOJO_RESULT_OK);
+  CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK);
 
   uint32_t message_size = 0;
   uint32_t num_handles = 0;
@@ -191,9 +190,7 @@
 void MojoTestBase::ReadMessage(MojoHandle mp,
                                char* data,
                                size_t num_bytes) {
-  CHECK_EQ(MojoWait(mp, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE,
-                    nullptr),
-           MOJO_RESULT_OK);
+  CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK);
 
   uint32_t message_size = 0;
   uint32_t num_handles = 0;
@@ -288,8 +285,7 @@
 
 // static
 void MojoTestBase::WriteData(MojoHandle producer, const std::string& data) {
-  CHECK_EQ(MojoWait(producer, MOJO_HANDLE_SIGNAL_WRITABLE,
-                    MOJO_DEADLINE_INDEFINITE, nullptr),
+  CHECK_EQ(WaitForSignals(producer, MOJO_HANDLE_SIGNAL_WRITABLE),
            MOJO_RESULT_OK);
   uint32_t num_bytes = static_cast<uint32_t>(data.size());
   CHECK_EQ(MojoWriteData(producer, data.data(), &num_bytes,
@@ -300,8 +296,7 @@
 
 // static
 std::string MojoTestBase::ReadData(MojoHandle consumer, size_t size) {
-  CHECK_EQ(MojoWait(consumer, MOJO_HANDLE_SIGNAL_READABLE,
-                    MOJO_DEADLINE_INDEFINITE, nullptr),
+  CHECK_EQ(WaitForSignals(consumer, MOJO_HANDLE_SIGNAL_READABLE),
            MOJO_RESULT_OK);
   std::vector<char> buffer(size);
   uint32_t num_bytes = static_cast<uint32_t>(size);
@@ -313,6 +308,20 @@
   return std::string(buffer.data(), buffer.size());
 }
 
+// static
+MojoHandleSignalsState MojoTestBase::GetSignalsState(MojoHandle handle) {
+  MojoHandleSignalsState signals_state;
+  CHECK_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(handle, &signals_state));
+  return signals_state;
+}
+
+// static
+MojoResult MojoTestBase::WaitForSignals(MojoHandle handle,
+                                        MojoHandleSignals signals,
+                                        MojoHandleSignalsState* state) {
+  return Wait(Handle(handle), signals, state);
+}
+
 }  // namespace test
 }  // namespace edk
 }  // namespace mojo
diff --git a/mojo/edk/test/mojo_test_base.h b/mojo/edk/test/mojo_test_base.h
index fa5b64c..35e2c2b 100644
--- a/mojo/edk/test/mojo_test_base.h
+++ b/mojo/edk/test/mojo_test_base.h
@@ -30,8 +30,6 @@
   ~MojoTestBase() override;
 
   using LaunchType = MultiprocessTestHelper::LaunchType;
-
- protected:
   using HandlerCallback = base::Callback<void(ScopedMessagePipeHandle)>;
 
   class ClientController {
@@ -152,6 +150,14 @@
   // Reads data from a data pipe.
   static std::string ReadData(MojoHandle consumer, size_t size);
 
+  // Queries the signals state of |handle|.
+  static MojoHandleSignalsState GetSignalsState(MojoHandle handle);
+
+  // Helper to block the calling thread waiting for signals to be raised.
+  static MojoResult WaitForSignals(MojoHandle handle,
+                                   MojoHandleSignals signals,
+                                   MojoHandleSignalsState* state = nullptr);
+
   void set_launch_type(LaunchType launch_type) { launch_type_ = launch_type; }
 
  private:
diff --git a/mojo/edk/test/multiprocess_test_helper.cc b/mojo/edk/test/multiprocess_test_helper.cc
index de6e2d9..cf37782 100644
--- a/mojo/edk/test/multiprocess_test_helper.cc
+++ b/mojo/edk/test/multiprocess_test_helper.cc
@@ -46,11 +46,13 @@
 const char kMojoNamedPipeName[] = "mojo-named-pipe-name";
 
 template <typename Func>
-int RunClientFunction(Func handler) {
+int RunClientFunction(Func handler, bool pass_pipe_ownership_to_main) {
   CHECK(MultiprocessTestHelper::primordial_pipe.is_valid());
   ScopedMessagePipeHandle pipe =
       std::move(MultiprocessTestHelper::primordial_pipe);
-  return handler(pipe.get().value());
+  MessagePipeHandle pipe_handle =
+      pass_pipe_ownership_to_main ? pipe.release() : pipe.get();
+  return handler(pipe_handle.value());
 }
 
 }  // namespace
@@ -58,7 +60,7 @@
 MultiprocessTestHelper::MultiprocessTestHelper() {}
 
 MultiprocessTestHelper::~MultiprocessTestHelper() {
-  CHECK(!test_child_.IsValid());
+  CHECK(!test_child_.process.IsValid());
 }
 
 ScopedMessagePipeHandle MultiprocessTestHelper::StartChild(
@@ -74,7 +76,7 @@
     const std::string& switch_value,
     LaunchType launch_type) {
   CHECK(!test_child_name.empty());
-  CHECK(!test_child_.IsValid());
+  CHECK(!test_child_.process.IsValid());
 
   std::string test_child_main = test_child_name + "TestChildMain";
 
@@ -168,22 +170,22 @@
   if (launch_type == LaunchType::CHILD ||
       launch_type == LaunchType::NAMED_CHILD) {
     DCHECK(server_handle.is_valid());
-    process.Connect(test_child_.Handle(),
+    process.Connect(test_child_.process.Handle(),
                     ConnectionParams(std::move(server_handle)),
                     process_error_callback_);
   }
 
-  CHECK(test_child_.IsValid());
+  CHECK(test_child_.process.IsValid());
   return pipe;
 }
 
 int MultiprocessTestHelper::WaitForChildShutdown() {
-  CHECK(test_child_.IsValid());
+  CHECK(test_child_.process.IsValid());
 
   int rv = -1;
-  WaitForMultiprocessTestChildExit(test_child_, TestTimeouts::action_timeout(),
-                                   &rv);
-  test_child_.Close();
+  WaitForMultiprocessTestChildExit(test_child_.process,
+                                   TestTimeouts::action_timeout(), &rv);
+  test_child_.process.Close();
   return rv;
 }
 
@@ -232,20 +234,25 @@
 
 // static
 int MultiprocessTestHelper::RunClientMain(
-    const base::Callback<int(MojoHandle)>& main) {
-  return RunClientFunction([main](MojoHandle handle){
-    return main.Run(handle);
-  });
+    const base::Callback<int(MojoHandle)>& main,
+    bool pass_pipe_ownership_to_main) {
+  return RunClientFunction(
+      [main](MojoHandle handle) { return main.Run(handle); },
+      pass_pipe_ownership_to_main);
 }
 
 // static
 int MultiprocessTestHelper::RunClientTestMain(
     const base::Callback<void(MojoHandle)>& main) {
-  return RunClientFunction([main](MojoHandle handle) {
-    main.Run(handle);
-    return (::testing::Test::HasFatalFailure() ||
-            ::testing::Test::HasNonfatalFailure()) ? 1 : 0;
-  });
+  return RunClientFunction(
+      [main](MojoHandle handle) {
+        main.Run(handle);
+        return (::testing::Test::HasFatalFailure() ||
+                ::testing::Test::HasNonfatalFailure())
+                   ? 1
+                   : 0;
+      },
+      true /* close_pipe_on_exit */);
 }
 
 // static
diff --git a/mojo/edk/test/multiprocess_test_helper.h b/mojo/edk/test/multiprocess_test_helper.h
index dd9bd6a..dc1c9bc 100644
--- a/mojo/edk/test/multiprocess_test_helper.h
+++ b/mojo/edk/test/multiprocess_test_helper.h
@@ -80,12 +80,13 @@
   // |EXPECT_TRUE(WaitForChildTestShutdown());|.
   bool WaitForChildTestShutdown();
 
-  const base::Process& test_child() const { return test_child_; }
+  const base::Process& test_child() const { return test_child_.process; }
 
   // Used by macros in mojo/edk/test/mojo_test_base.h to support multiprocess
   // test client initialization.
   static void ChildSetup();
-  static int RunClientMain(const base::Callback<int(MojoHandle)>& main);
+  static int RunClientMain(const base::Callback<int(MojoHandle)>& main,
+                           bool pass_pipe_ownership_to_main = false);
   static int RunClientTestMain(const base::Callback<void(MojoHandle)>& main);
 
   // For use (and only valid) in the child process:
@@ -93,7 +94,7 @@
 
  private:
   // Valid after |StartChild()| and before |WaitForChildShutdown()|.
-  base::Process test_child_;
+  base::SpawnChildResult test_child_;
 
   ProcessErrorCallback process_error_callback_;
 
diff --git a/mojo/public/README.md b/mojo/public/README.md
deleted file mode 100644
index dd91742..0000000
--- a/mojo/public/README.md
+++ /dev/null
@@ -1,43 +0,0 @@
-Mojo Public API
-===============
-
-The Mojo Public API is a binary stable API to the Mojo system.
-
-It consists of support for a number of programming languages (with a directory
-for each support language), some "build" tools and build-time requirements, and
-interface definitions for Mojo services (specified using an IDL).
-
-Note that there are various subdirectories named tests/. These contain tests of
-the code in the enclosing directory, and are not meant for use by Mojo
-applications.
-
-C/CPP/JS
---------
-
-The c/, cpp/, js/ subdirectories define the API for C, C++, and JavaScript,
-respectively.
-
-The basic principle for these directories is that they consist of the source
-files that one needs at build/deployment/run time (as appropriate for the
-language), organized in a natural way for the particular language.
-
-Interfaces
-----------
-
-The interfaces/ subdirectory contains Mojo IDL (a.k.a. .mojom) descriptions of
-standard Mojo services.
-
-Platform
---------
-
-The platform/ subdirectory contains any build-time requirements (e.g., static
-libraries) that may be needed to produce a Service library for certain
-platforms, such as a native shared library or as a NaCl binary.
-
-Tools
------
-
-The tools/ subdirectory contains tools that are useful/necessary at
-build/deployment time. These tools may be needed (as a practical necessity) to
-use the API in any given language, e.g., to generate bindings from Mojo IDL
-files.
diff --git a/mojo/public/c/README.md b/mojo/public/c/README.md
deleted file mode 100644
index 223c205..0000000
--- a/mojo/public/c/README.md
+++ /dev/null
@@ -1,22 +0,0 @@
-Mojo Public C API
-=================
-
-This directory contains C language bindings for the Mojo Public API.
-
-System
-------
-
-The system/ subdirectory provides definitions of the basic low-level API used by
-all Services (whether directly or indirectly). These consist primarily
-of the IPC primitives used to communicate with Mojo services.
-
-Though the message protocol is stable, the implementation of the transport is
-not, and access to the IPC mechanisms must be via the primitives defined in this
-directory.
-
-Test Support
-------------
-
-This directory contains a C API for running tests. This API is only available
-under special, specific test conditions. It is not meant for general use by Mojo
-applications.
diff --git a/mojo/public/c/system/BUILD.gn b/mojo/public/c/system/BUILD.gn
index c3b3d5f..08185c7 100644
--- a/mojo/public/c/system/BUILD.gn
+++ b/mojo/public/c/system/BUILD.gn
@@ -17,7 +17,7 @@
     "thunks.cc",
     "thunks.h",
     "types.h",
-    "wait_set.h",
+    "watcher.h",
   ]
 
   defines = [ "MOJO_SYSTEM_IMPLEMENTATION" ]
diff --git a/mojo/public/c/system/README.md b/mojo/public/c/system/README.md
new file mode 100644
index 0000000..2abe80f
--- /dev/null
+++ b/mojo/public/c/system/README.md
@@ -0,0 +1,869 @@
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C System API
+This document is a subset of the [Mojo documentation](/mojo).
+
+[TOC]
+
+## Overview
+The Mojo C System API is a lightweight API (with an eventually-stable ABI) upon
+which all higher layers of the Mojo system are built.
+
+This API exposes the fundamental capabilities to: create, read from, and write
+to **message pipes**; create, read from, and write to **data pipes**; create
+**shared buffers** and generate sharable handles to them; wrap platform-specific
+handle objects (such as **file descriptors**, **Windows handles**, and
+**Mach ports**) for seamless transit over message pipes; and efficiently watch
+handles for various types of state transitions.
+
+This document provides a brief guide to API usage with example code snippets.
+For a detailed API references please consult the headers in
+[//mojo/public/c/system](https://cs.chromium.org/chromium/src/mojo/public/c/system/).
+
+### A Note About Multithreading
+
+The Mojo C System API is entirely thread-agnostic. This means that all functions
+may be called from any thread in a process, and there are no restrictions on how
+many threads can use the same object at the same time.
+
+Of course this does not mean you can completely ignore potential concurrency
+issues -- such as a handle being closed on one thread while another thread is
+trying to perform an operation on the same handle -- but there is nothing
+fundamentally incorrect about using any given API or handle from multiple
+threads.
+
+### A Note About Synchronization
+
+Every Mojo API call is non-blocking and synchronously yields some kind of status
+result code, but the call's side effects -- such as affecting the state of
+one or more handles in the system -- may or may not occur asynchronously.
+
+Mojo objects can be observed for interesting state changes in a way that is
+thread-agnostic and in some ways similar to POSIX signal handlers: *i.e.*
+user-provided notification handlers may be invoked at any time on arbitrary
+threads in the process. It is entirely up to the API user to take appropriate
+measures to synchronize operations against other application state.
+
+The higher level [system](/mojo#High-Level-System-APIs) and
+[bindings](/mojo#High-Level-Bindings-APIs) APIs provide helpers to simplify Mojo
+usage in this regard, at the expense of some flexibility.
+
+## Result Codes
+
+Most API functions return a value of type `MojoResult`. This is an integral
+result code used to convey some meaningful level of detail about the result of a
+requested operation.
+
+See [//mojo/public/c/system/types.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/types.h)
+for different possible values. See documentation for individual API calls for
+more specific contextual meaning of various result codes.
+
+## Handles
+
+Every Mojo IPC primitive is identified by a generic, opaque integer handle of
+type `MojoHandle`. Handles can be acquired by creating new objects using various
+API calls, or by reading messages which contain attached handles.
+
+A `MojoHandle` can represent a message pipe endpoint, a data pipe consumer,
+a data pipe producer, a shared buffer reference, a wrapped native platform
+handle such as a POSIX file descriptor or a Windows system handle, or a watcher
+object (see [Signals & Watchers](#Signals-Watchers) below.)
+
+All types of handles except for watchers (which are an inherently local concept)
+can be attached to messages and sent over message pipes.
+
+Any `MojoHandle` may be closed by calling `MojoClose`:
+
+``` c
+MojoHandle x = DoSomethingToGetAValidHandle();
+MojoResult result = MojoClose(x);
+```
+
+If the handle passed to `MojoClose` was a valid handle, it will be closed and
+`MojoClose` returns `MOJO_RESULT_OK`. Otherwise it returns
+`MOJO_RESULT_INVALID_ARGUMENT`.
+
+Similar to native system handles on various popular platforms, `MojoHandle`
+values may be reused over time. Thus it is important to avoid logical errors
+which lead to misplaced handle ownership, double-closes, *etc.*
+
+## Message Pipes
+
+A message pipe is a bidirectional messaging channel which can carry arbitrary
+unstructured binary messages with zero or more `MojoHandle` attachments to be
+transferred from one end of a pipe to the other. Message pipes work seamlessly
+across process boundaries or within a single process.
+
+The [Embedder Development Kit (EDK)](/mojo/edk/embedder) provides the means to
+bootstrap one or more primordial cross-process message pipes, and it's up to
+Mojo embedders to expose this capability in some useful way. Once such a pipe is
+established, additional handles -- including other message pipe handles -- may
+be sent to a remote process using that pipe (or in turn, over other pipes sent
+over that pipe, or pipes sent over *that* pipe, and so on...)
+
+The public C System API exposes the ability to read and write messages on pipes
+and to create new message pipes.
+
+See [//mojo/public/c/system/message_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/message_pipe.h)
+for detailed message pipe API documentation.
+
+### Creating Message Pipes
+
+`MojoCreateMessagePipe` can be used to create a new message pipe:
+
+``` c
+MojoHandle a, b;
+MojoResult result = MojoCreateMessagePipe(NULL, &a, &b);
+```
+
+After this snippet, `result` should be `MOJO_RESULT_OK` (it's really hard for
+this to fail!), and `a` and `b` will contain valid Mojo handles, one for each
+end of the new message pipe.
+
+Any messages written to `a` are eventually readable from `b`, and any messages
+written to `b` are eventually readable from `a`. If `a` is closed at any point,
+`b` will eventually become aware of this fact; likewise if `b` is closed, `a`
+will become aware of that.
+
+The state of these conditions can be queried and watched asynchronously as
+described in the [Signals & Watchers](#Signals-Watchers) section below.
+
+### Allocating Messages
+
+In order to avoid redundant internal buffer copies, Mojo would like to allocate
+your message storage buffers for you. This is easy:
+
+``` c
+MojoMessageHandle message;
+MojoResult result = MojoAllocMessage(6, NULL, 0, MOJO_ALLOC_MESSAGE_FLAG_NONE,
+                                     &message);
+```
+
+Note that we have a special `MojoMessageHandle` type for message objects.
+
+The code above allocates a buffer for a message payload of 6 bytes with no
+handles attached.
+
+If we change our mind and decide not to send this message, we can delete it:
+
+``` c
+MojoResult result = MojoFreeMessage(message);
+```
+
+If we instead decide to send our newly allocated message, we first need to fill
+in the payload data with something interesting. How about a pleasant greeting:
+
+``` c
+void* buffer = NULL;
+MojoResult result = MojoGetMessageBuffer(message, &buffer);
+memcpy(buffer, "hello", 6);
+```
+
+Now we can write the message to a pipe. Note that attempting to write a message
+transfers ownership of the message object (and any attached handles) into the
+target pipe and there is therefore no need to subsequently call
+`MojoFreeMessage` on that message.
+
+### Writing Messages
+
+``` c
+result = MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE);
+```
+
+`MojoWriteMessage` is a *non-blocking* call: it always returns
+immediately. If its return code is `MOJO_RESULT_OK` the message will eventually
+find its way to the other end of the pipe -- assuming that end isn't closed
+first, of course. If the return code is anything else, the message is deleted
+and not transferred.
+
+In this case since we know `b` is still open, we also know the message will
+eventually arrive at `b`. `b` can be queried or watched to become aware of when
+the message arrives, but we'll ignore that complexity for now. See
+[Signals & Watchers](#Signals-Watchers) below for more information.
+
+*** aside
+**NOTE**: Although this is an implementation detail and not strictly guaranteed by the
+System API, it is true in the current implementation that the message will
+arrive at `b` before the above `MojoWriteMessage` call even returns, because `b`
+is in the same process as `a` and has never been transferred over another pipe.
+***
+
+### Reading Messages
+
+We can read a new message object from a pipe:
+
+``` c
+MojoMessageHandle message;
+uint32_t num_bytes;
+MojoResult result = MojoReadMessageNew(b, &message, &num_bytes, NULL, NULL,
+                                       MOJO_READ_MESSAGE_FLAG_NONE);
+```
+
+and map its buffer to retrieve the contents:
+
+``` c
+void* buffer = NULL;
+MojoResult result = MojoGetMessageBuffer(message, &buffer);
+printf("Pipe says: %s", (const char*)buffer);
+```
+
+`result` should be `MOJO_RESULT_OK` and this snippet should write `"hello"` to
+`stdout`.
+
+If we try were to try reading again now that there are no messages on `b`:
+
+``` c
+MojoMessageHandle message;
+MojoResult result = MojoReadMessageNew(b, &message, NULL, NULL, NULL,
+                                       MOJO_READ_MESSAGE_FLAG_NONE);
+```
+
+We'll get a `result` of `MOJO_RESULT_SHOULD_WAIT`, indicating that the pipe is
+not yet readable.
+
+### Messages With Handles
+
+Probably the most useful feature of Mojo IPC is that message pipes can carry
+arbitrary Mojo handles, including other message pipes. This is also
+straightforward.
+
+Here's an example which creates two pipes, using the first pipe to transfer
+one end of the second pipe. If you have a good imagination you can pretend the
+first pipe spans a process boundary, which makes the example more practically
+interesting:
+
+``` c
+MojoHandle a, b;
+MojoHandle c, d;
+MojoMessage message;
+
+// Allocate a message with an empty payload and handle |c| attached. Note that
+// this takes ownership of |c|, effectively invalidating its handle value.
+MojoResult result = MojoAllocMessage(0, &c, 1, MOJO_ALLOC_MESSAGE_FLAG_NONE,
+                                     message);
+
+result = MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE);
+
+// Some time later...
+uint32_t num_bytes;
+MojoHandle e;
+uint32_t num_handles = 1;
+MojoResult result = MojoReadMessageNew(b, &message, &num_bytes, &e,
+                                       &num_handles,
+                                       MOJO_READ_MESSAGE_FLAG_NONE);
+```
+
+At this point the handle in `e` is now referencing the same message pipe
+endpoint which was originally referenced by `c`.
+
+Note that `num_handles` above is initialized to 1 before we pass its address to
+`MojoReadMessageNew`. This is to indicate how much `MojoHandle` storage is
+available at the output buffer we gave it (`&e` above).
+
+If we didn't know how many handles to expect in an incoming message -- which is
+often the case -- we can use `MojoReadMessageNew` to query for this information
+first:
+
+``` c
+MojoMessageHandle message;
+uint32_t num_bytes = 0;
+uint32_t num_handles = 0;
+MojoResult result = MojoReadMessageNew(b, &message, &num_bytes, NULL,
+                                       &num_handles,
+                                       MOJO_READ_MESSAGE_FLAG_NONE);
+```
+
+If in this case there were a received message on `b` with some nonzero number
+of handles, `result` would be `MOJO_RESULT_RESOURCE_EXHAUSTED`, and both
+`num_bytes` and `num_handles` would be updated to reflect the payload size and
+number of attached handles on the next available message.
+
+It's also worth noting that if there did happen to be a message available with
+no payload and no handles (*i.e.* an empty message), this would actually return
+`MOJO_RESULT_OK`.
+
+## Data Pipes
+
+Data pipes provide an efficient unidirectional channel for moving large amounts
+of unframed data between two endpoints. Every data pipe has a fixed
+**element size** and **capacity**. Reads and writes must be done in sizes that
+are a multiple of the element size, and writes to the pipe can only be queued
+up to the pipe's capacity before reads must be done to make more space
+available.
+
+Every data pipe has a single **producer** handle used to write data into the
+pipe and a single **consumer** handle used to read data out of the pipe.
+
+Finally, data pipes support both immediate I/O -- reading into and writing out
+from user-supplied buffers -- as well as two-phase I/O, allowing callers to
+temporarily lock some portion of the data pipe in order to read or write its
+contents directly.
+
+See [//mojo/public/c/system/data_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/data_pipe.h)
+for detailed data pipe API documentation.
+
+### Creating Data Pipes
+
+Use `MojoCreateDataPipe` to create a new data pipe. The
+`MojoCreateDataPipeOptions` structure is used to configure the new pipe, but
+this can be omitted to assume the default options of a single-byte element size
+and an implementation-defined default capacity (64 kB at the time of this
+writing.)
+
+``` c
+MojoHandle producer, consumer;
+MojoResult result = MojoCreateDataPipe(NULL, &producer, &consumer);
+```
+
+### Immediate I/O
+
+Data can be written into or read out of a data pipe using buffers provided by
+the caller. This is generally more convenient than two-phase I/O but is
+also less efficient due to extra copying.
+
+``` c
+uint32_t num_bytes = 12;
+MojoResult result = MojoWriteData(producer, "datadatadata", &num_bytes,
+                                  MOJO_WRITE_DATA_FLAG_NONE);
+```
+
+The above snippet will attempt to write 12 bytes into the data pipe, which
+should succeed and return `MOJO_RESULT_OK`. If the available capacity on the
+pipe was less than the amount requested (the input value of `*num_bytes`) this
+will copy what it can into the pipe and return the number of bytes written in
+`*num_bytes`. If no data could be copied this will instead return
+`MOJO_RESULT_SHOULD_WAIT`.
+
+Reading from the consumer is a similar operation.
+
+``` c
+char buffer[64];
+uint32_t num_bytes = 64;
+MojoResult result = MojoReadData(consumer, buffer, &num_bytes,
+                                 MOJO_READ_DATA_FLAG_NONE);
+```
+
+This will attempt to read up to 64 bytes, returning the actual number of bytes
+read in `*num_bytes`.
+
+`MojoReadData` supports a number of interesting flags to change the behavior:
+you can peek at the data (copy bytes out without removing them from the pipe),
+query the number of bytes available without doing any actual reading of the
+contents, or discard data from the pipe without bothering to copy it anywhere.
+
+This also supports a `MOJO_READ_DATA_FLAG_ALL_OR_NONE` which ensures that the
+call succeeds **only** if the exact number of bytes requested could be read.
+Otherwise such a request will fail with `MOJO_READ_DATA_OUT_OF_RANGE`.
+
+### Two-Phase I/O
+
+Data pipes also support two-phase I/O operations, allowing a caller to
+temporarily lock a portion of the data pipe's storage for direct memory access.
+
+``` c
+void* buffer;
+uint32_t num_bytes = 1024;
+MojoResult result = MojoBeginWriteData(producer, &buffer, &num_bytes,
+                                       MOJO_WRITE_DATA_FLAG_NONE);
+```
+
+This requests write access to a region of up to 1024 bytes of the data pipe's
+next available capacity. Upon success, `buffer` will point to the writable
+storage and `num_bytes` will indicate the size of the buffer there.
+
+The caller should then write some data into the memory region and release it
+ASAP, indicating the number of bytes actually written:
+
+``` c
+memcpy(buffer, "hello", 6);
+MojoResult result = MojoEndWriteData(producer, 6);
+```
+
+Two-phase reads look similar:
+
+``` c
+void* buffer;
+uint32_t num_bytes = 1024;
+MojoResult result = MojoBeginReadData(consumer, &buffer, &num_bytes,
+                                      MOJO_READ_DATA_FLAG_NONE);
+// result should be MOJO_RESULT_OK, since there is some data available.
+
+printf("Pipe says: %s", (const char*)buffer);  // Should say "hello".
+
+result = MojoEndReadData(consumer, 1);  // Say we only consumed one byte.
+
+num_bytes = 1024;
+result = MojoBeginReadData(consumer, &buffer, &num_bytes,
+                           MOJO_READ_DATA_FLAG_NONE);
+printf("Pipe says: %s", (const char*)buffer);  // Should say "ello".
+result = MojoEndReadData(consumer, 5);
+```
+
+## Shared Buffers
+
+Shared buffers are chunks of memory which can be mapped simultaneously by
+multiple processes. Mojo provides a simple API to make these available to
+applications.
+
+See [//mojo/public/c/system/buffer.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/buffer.h)
+for detailed shared buffer API documentation.
+
+### Creating Buffer Handles
+
+Usage is straightforward. You can create a new buffer:
+
+``` c
+// Allocate a shared buffer of 4 kB.
+MojoHandle buffer;
+MojoResult result = MojoCreateSharedBuffer(NULL, 4096, &buffer);
+```
+
+You can also duplicate an existing shared buffer handle:
+
+``` c
+MojoHandle another_name_for_buffer;
+MojoResult result = MojoDuplicateBufferHandle(buffer, NULL,
+                                              &another_name_for_buffer);
+```
+
+This is useful if you want to retain a handle to the buffer while also sharing
+handles with one or more other clients. The allocated buffer remains valid as
+long as at least one shared buffer handle exists to reference it.
+
+### Mapping Buffers
+
+You can map (and later unmap) a specified range of the buffer to get direct
+memory access to its contents:
+
+``` c
+void* data;
+MojoResult result = MojoMapBuffer(buffer, 0, 64, &data,
+                                  MOJO_MAP_BUFFER_FLAG_NONE);
+
+*(int*)data = 42;
+result = MojoUnmapBuffer(data);
+```
+
+A buffer may have any number of active mappings at a time, in any number of
+processes.
+
+### Read-Only Handles
+
+An option can also be specified on `MojoDuplicateBufferHandle` to ensure
+that the newly duplicated handle can only be mapped to read-only memory:
+
+``` c
+MojoHandle read_only_buffer;
+MojoDuplicateBufferHandleOptions options;
+options.struct_size = sizeof(options);
+options.flags = MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY;
+MojoResult result = MojoDuplicateBufferHandle(buffer, &options,
+                                              &read_only_buffer);
+
+// Attempt to map and write to the buffer using the read-only handle:
+void* data;
+result = MojoMapBuffer(read_only_buffer, 0, 64, &data,
+                       MOJO_MAP_BUFFER_FLAG_NONE);
+*(int*)data = 42;  // CRASH
+```
+
+*** note
+**NOTE:** One important limitation of the current implementation is that
+read-only handles can only be produced from a handle that was originally created
+by `MojoCreateSharedBuffer` (*i.e.*, you cannot create a read-only duplicate
+from a non-read-only duplicate), and the handle cannot have been transferred
+over a message pipe first.
+***
+
+## Native Platform Handles (File Descriptors, Windows Handles, *etc.*)
+
+Native platform handles to system objects can be wrapped as Mojo handles for
+seamless transit over message pipes. Mojo currently supports wrapping POSIX
+file descriptors, Windows handles, and Mach ports.
+
+See [//mojo/public/c/system/platform_handle.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/platform_handle.h)
+for detailed platform handle API documentation.
+
+### Wrapping Basic Handle Types
+
+Wrapping a POSIX file descriptor is simple:
+
+``` c
+MojoPlatformHandle platform_handle;
+platform_handle.struct_size = sizeof(platform_handle);
+platform_handle.type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
+platform_handle.value = (uint64_t)fd;
+MojoHandle handle;
+MojoResult result = MojoWrapPlatformHandle(&platform_handle, &handle);
+```
+
+Note that at this point `handle` effectively owns the file descriptor
+and if you were to call `MojoClose(handle)`, the file descriptor would be closed
+too; but we're not going to close it here! We're going to pretend we've sent it
+over a message pipe, and now we want to unwrap it on the other side:
+
+``` c
+MojoPlatformHandle platform_handle;
+platform_handle.struct_size = sizeof(platform_handle);
+MojoResult result = MojoUnwrapPlatformHandle(handle, &platform_handle);
+int fd = (int)platform_handle.value;
+```
+
+The situation looks nearly identical for wrapping and unwrapping Windows handles
+and Mach ports.
+
+### Wrapping Shared Buffer Handles
+
+Unlike other handle types, shared buffers have special meaning in Mojo, and it
+may be desirable to wrap a native platform handle -- along with some extra
+metadata -- such that be treated like a real Mojo shared buffer handle.
+Conversely it can also be useful to unpack a Mojo shared buffer handle into
+a native platform handle which references the buffer object. Both of these
+things can be done using the `MojoWrapPlatformSharedBuffer` and
+`MojoUnwrapPlatformSharedBuffer` APIs.
+
+On Windows, the wrapped platform handle must always be a Windows handle to
+a file mapping object.
+
+On OS X, the wrapped platform handle must be a memory-object send right.
+
+On all other POSIX systems, the wrapped platform handle must be a file
+descriptor for a shared memory object.
+
+## Signals & Watchers
+
+Message pipe and data pipe (producer and consumer) handles can change state in
+ways that may be interesting to a Mojo API user. For example, you may wish to
+know when a message pipe handle has messages available to be read or when its
+peer has been closed. Such states are reflected by a fixed set of boolean
+signals on each pipe handle.
+
+### Signals
+
+Every message pipe and data pipe handle maintains a notion of
+**signaling state** which may be queried at any time. For example:
+
+``` c
+MojoHandle a, b;
+MojoCreateMessagePipe(NULL, &a, &b);
+
+MojoHandleSignalsState state;
+MojoResult result = MojoQueryHandleSignalsState(a, &state);
+```
+
+The `MojoHandleSignalsState` structure exposes two fields: `satisfied_signals`
+and `satisfiable_signals`. Both of these are bitmasks of the type
+`MojoHandleSignals` (see [//mojo/public/c/system/types.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/types.h)
+for more details.)
+
+The `satisfied_signals` bitmask indicates signals which were satisfied on the
+handle at the time of the call, while the `satisfiable_signals` bitmask
+indicates signals which were still possible to satisfy at the time of the call.
+It is thus by definition always true that:
+
+``` c
+(satisfied_signals | satisfiable_signals) == satisfiable_signals
+```
+
+In other words a signal obviously cannot be satisfied if it is no longer
+satisfiable. Furthermore once a signal is unsatisfiable, *i.e.* is no longer
+set in `sastisfiable_signals`, it can **never** become satisfiable again.
+
+To illustrate this more clearly, consider the message pipe created above. Both
+ends of the pipe are still open and neither has been written to yet. Thus both
+handles start out with the same signaling state:
+
+| Field                 | State |
+|-----------------------|-------|
+| `satisfied_signals`   | `MOJO_HANDLE_SIGNAL_WRITABLE`
+| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_WRITABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED`
+
+Writing a message to handle `b` will eventually alter the signaling state of `a`
+such that `MOJO_HANDLE_SIGNAL_READABLE` also becomes satisfied. If we were to
+then close `b`, the signaling state of `a` would look like:
+
+| Field                 | State |
+|-----------------------|-------|
+| `satisfied_signals`   | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED`
+| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED`
+
+Note that even though `a`'s peer is known to be closed (hence making `a`
+permanently unwritable) it remains readable because there's still an unread
+received message waiting to be read from `a`.
+
+Finally if we read the last message from `a` its signaling state becomes:
+
+| Field                 | State |
+|-----------------------|-------|
+| `satisfied_signals`   | `MOJO_HANDLE_SIGNAL_PEER_CLOSED`
+| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_PEER_CLOSED`
+
+and we know definitively that `a` can never be read from again.
+
+### Watching Signals
+
+The ability to query a handle's signaling state can be useful, but it's not
+sufficient to support robust and efficient pipe usage. Mojo watchers empower
+users with the ability to **watch** a handle's signaling state for interesting
+changes and automatically invoke a notification handler in response.
+
+When a watcher is created it must be bound to a function pointer matching
+the following signature, defined in
+[//mojo/public/c/system/watcher.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/watcher.h):
+
+``` c
+typedef void (*MojoWatcherNotificationCallback)(
+    uintptr_t context,
+    MojoResult result,
+    MojoHandleSignalsState signals_state,
+    MojoWatcherNotificationFlags flags);
+```
+
+The `context` argument corresponds to a specific handle being watched by the
+watcher (read more below), and the remaining arguments provide details regarding
+the specific reason for the notification. It's important to be aware that a
+watcher's registered handler may be called **at any time** and
+**on any thread**.
+
+It's also helpful to understand a bit about the mechanism by which the handler
+can be invoked. Essentially, any Mojo C System API call may elicit a handle
+state change of some kind. If such a change is relevant to conditions watched by
+a watcher, and that watcher is in a state which allows it raise a corresponding
+notification, its notification handler will be invoked synchronously some time
+before the outermost System API call on the current thread's stack returns.
+
+Handle state changes can also occur as a result of incoming IPC from an external
+process. If a pipe in the current process is connected to an endpoint in another
+process and the internal Mojo system receives an incoming message bound for the
+local endpoint, the arrival of that message will trigger a state change on the
+receiving handle and may thus invoke one or more watchers' notification handlers
+as a result.
+
+The `MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM` flag on the notification
+handler's `flags` argument is used to indicate whether the handler was invoked
+due to such an internal system IPC event (if the flag is set), or if it was
+invoked synchronously due to some local API call (if the flag is unset.)
+This distinction can be useful to make in certain cases to *e.g.* avoid
+accidental reentrancy in user code.
+
+### Creating a Watcher
+
+Creating a watcher is simple:
+
+``` c
+
+void OnNotification(uintptr_t context,
+                    MojoResult result,
+                    MojoHandleSignalsState signals_state,
+                    MojoWatcherNotificationFlags flags) {
+  // ...
+}
+
+MojoHandle w;
+MojoResult result = MojoCreateWatcher(&OnNotification, &w);
+```
+
+Like all other `MojoHandle` types, watchers may be destroyed by closing them
+with `MojoClose`. Unlike other `MojoHandle` types, watcher handles are **not**
+transferrable across message pipes.
+
+In order for a watcher to be useful, it has to watch at least one handle.
+
+### Adding a Handle to a Watcher
+
+Any given watcher can watch any given (message or data pipe) handle for some set
+of signaling conditions. A handle may be watched simultaneously by multiple
+watchers, and a single watcher can watch multiple different handles
+simultaneously.
+
+``` c
+MojoHandle a, b;
+MojoCreateMessagePipe(NULL, &a, &b);
+
+// Watch handle |a| for readability.
+const uintptr_t context = 1234;
+MojoResult result = MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, context);
+```
+
+We've successfully instructed watcher `w` to begin watching pipe handle `a` for
+readability. However, our recently created watcher is still in a **disarmed**
+state, meaning that it will never fire a notification pertaining to this watched
+signaling condition. It must be **armed** before that can happen.
+
+### Arming a Watcher
+
+In order for a watcher to invoke its notification handler in response to a
+relevant signaling state change on a watched handle, it must first be armed. A
+watcher may only be armed if none of its watched handles would elicit a
+notification immediately once armed.
+
+In this case `a` is clearly not yet readable, so arming should succeed:
+
+``` c
+MojoResult result = MojoArmWatcher(w, NULL, NULL, NULL, NULL);
+```
+
+Now we can write to `b` to make `a` readable:
+
+``` c
+MojoWriteMessage(b, NULL, 0, NULL, 0, MOJO_WRITE_MESSAGE_NONE);
+```
+
+Eventually -- and in practice possibly before `MojoWriteMessage` even
+returns -- this will cause `OnNotification` to be invoked on the calling thread
+with the `context` value (*i.e.* 1234) that was given when the handle was added
+to the watcher.
+
+The `result` parameter will be `MOJO_RESULT_OK` to indicate that the watched
+signaling condition has been *satisfied*. If the watched condition had instead
+become permanently *unsatisfiable* (*e.g.*, if `b` were instead closed), `result`
+would instead indicate `MOJO_RESULT_FAILED_PRECONDITION`.
+
+**NOTE:** Immediately before a watcher decides to invoke its notification
+handler, it automatically disarms itself to prevent another state change from
+eliciting another notification. Therefore a watcher must be repeatedly rearmed
+in order to continue dispatching signaling notifications.
+
+As noted above, arming a watcher may fail if any of the watched conditions for
+a handle are already partially satisfied or fully unsatisfiable. In that case
+the caller may provide buffers for `MojoArmWatcher` to store information about
+a subset of the relevant watches which caused it to fail:
+
+``` c
+// Provide some storage for information about watches that are already ready.
+uint32_t num_ready_contexts = 4;
+uintptr_t ready_contexts[4];
+MojoResult ready_results[4];
+struct MojoHandleSignalsStates ready_states[4];
+MojoResult result = MojoArmWatcher(w, &num_ready_contexts, ready_contexts,
+                                   ready_results, ready_states);
+```
+
+Because `a` is still readable this operation will fail with
+`MOJO_RESULT_FAILED_PRECONDITION`. The input value of `num_ready_contexts`
+informs `MojoArmWatcher` that it may store information regarding up to 4 watches
+which currently prevent arming. In this case of course there is only one active
+watch, so upon return we will see:
+
+* `num_ready_contexts` is `1`.
+* `ready_contexts[0]` is `1234`.
+* `ready_results[0]` is `MOJO_RESULT_OK`
+* `ready_states[0]` is the last known signaling state of handle `a`.
+
+In other words the stored information mirrors what would have been the
+notification handler's arguments if the watcher were allowed to arm and thus
+notify immediately.
+
+### Cancelling a Watch
+
+There are three ways a watch can be cancelled:
+
+* The watched handle is closed
+* The watcher handle is closed (in which case all of its watches are cancelled.)
+* `MojoCancelWatch` is explicitly called for a given `context`.
+
+In the above example this means any of the following operations will cancel the
+watch on `a`:
+
+``` c
+// Close the watched handle...
+MojoClose(a);
+
+// OR close the watcher handle...
+MojoClose(w);
+
+// OR explicitly cancel.
+MojoResult result = MojoCancelWatch(w, 1234);
+```
+
+In every case the watcher's notification handler is invoked for the cancelled
+watch(es) regardless of whether or not the watcher is or was armed at the time.
+The notification handler receives a `result` of `MOJO_RESULT_CANCELLED` for
+these notifications, and this is guaranteed to be the final notification for any
+given watch context.
+
+### Practical Watch Context Usage
+
+It is common and probably wise to treat a watch's `context` value as an opaque
+pointer to some thread-safe state associated in some way with the handle being
+watched. Here's a small example which uses a single watcher to watch both ends
+of a message pipe and accumulate a count of messages received at each end.
+
+``` c
+// NOTE: For the sake of simplicity this example code is not in fact
+// thread-safe. As long as there's only one thread running in the process and
+// no external process connections, this is fine.
+
+struct WatchedHandleState {
+  MojoHandle watcher;
+  MojoHandle handle;
+  int message_count;
+};
+
+void OnNotification(uintptr_t context,
+                    MojoResult result,
+                    MojoHandleSignalsState signals_state,
+                    MojoWatcherNotificationFlags flags) {
+  struct WatchedHandleState* state = (struct WatchedHandleState*)(context);
+  MojoResult rv;
+
+  if (result == MOJO_RESULT_CANCELLED) {
+    // Cancellation is always the last notification and is guaranteed to
+    // eventually happen for every context, assuming no handles are leaked. We
+    // treat this as an opportunity to free the WatchedHandleState.
+    free(state);
+    return;
+  }
+
+  if (result == MOJO_RESULT_FAILED_PRECONDITION) {
+    // No longer readable, i.e. the other handle must have been closed. Better
+    // cancel. Note that we could also just call MojoClose(state->watcher) here
+    // since we know |context| is its only registered watch.
+    MojoCancelWatch(state->watcher, context);
+    return;
+  }
+
+  // This is the only handle watched by the watcher, so as long as we can't arm
+  // the watcher we know something's up with this handle. Try to read messages
+  // until we can successfully arm again or something goes terribly wrong.
+  while (MojoArmWatcher(state->watcher, NULL, NULL, NULL, NULL) ==
+         MOJO_RESULT_FAILED_PRECONDITION) {
+    rv = MojoReadMessageNew(state->handle, NULL, NULL, NULL,
+                            MOJO_READ_MESSAGE_FLAG_MAY_DISCARD);
+    if (rv == MOJO_RESULT_OK) {
+      state->message_count++;
+    } else if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
+      MojoCancelWatch(state->watcher, context);
+      return;
+    }
+  }
+}
+
+MojoHandle a, b;
+MojoCreateMessagePipe(NULL, &a, &b);
+
+MojoHandle a_watcher, b_watcher;
+MojoCreateWatcher(&OnNotification, &a_watcher);
+MojoCreateWatcher(&OnNotification, &b_watcher)
+
+struct WatchedHandleState* a_state = malloc(sizeof(struct WatchedHandleState));
+a_state->watcher = a_watcher;
+a_state->handle = a;
+a_state->message_count = 0;
+
+struct WatchedHandleState* b_state = malloc(sizeof(struct WatchedHandleState));
+b_state->watcher = b_watcher;
+b_state->handle = b;
+b_state->message_count = 0;
+
+MojoWatch(a_watcher, a, MOJO_HANDLE_SIGNAL_READABLE, (uintptr_t)a_state);
+MojoWatch(b_watcher, b, MOJO_HANDLE_SIGNAL_READABLE, (uintptr_t)b_state);
+
+MojoArmWatcher(a_watcher, NULL, NULL, NULL, NULL);
+MojoArmWatcher(b_watcher, NULL, NULL, NULL, NULL);
+```
+
+Now any writes to `a` will increment `message_count` in `b_state`, and any
+writes to `b` will increment `message_count` in `a_state`.
+
+If either `a` or `b` is closed, both watches will be cancelled - one because
+watch cancellation is implicit in handle closure, and the other because its
+watcher will eventually detect that the handle is no longer readable.
diff --git a/mojo/public/c/system/buffer.h b/mojo/public/c/system/buffer.h
index 0f02737..285e0d7 100644
--- a/mojo/public/c/system/buffer.h
+++ b/mojo/public/c/system/buffer.h
@@ -2,10 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// This file contains types/constants and functions specific to buffers (and in
-// particular shared buffers).
-// TODO(vtl): Reorganize this file (etc.) to separate general buffer functions
-// from (shared) buffer creation.
+// This file contains types/constants and functions specific to shared buffers.
 //
 // Note: This header should be compilable as C.
 
@@ -20,15 +17,13 @@
 
 // |MojoCreateSharedBufferOptions|: Used to specify creation parameters for a
 // shared buffer to |MojoCreateSharedBuffer()|.
+//
 //   |uint32_t struct_size|: Set to the size of the
 //       |MojoCreateSharedBufferOptions| struct. (Used to allow for future
 //       extensions.)
+//
 //   |MojoCreateSharedBufferOptionsFlags flags|: Reserved for future use.
 //       |MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE|: No flags; default mode.
-//
-// TODO(vtl): Maybe add a flag to indicate whether the memory should be
-// executable or not?
-// TODO(vtl): Also a flag for discardable (ashmem-style) buffers.
 
 typedef uint32_t MojoCreateSharedBufferOptionsFlags;
 
@@ -50,17 +45,21 @@
 
 // |MojoDuplicateBufferHandleOptions|: Used to specify parameters in duplicating
 // access to a shared buffer to |MojoDuplicateBufferHandle()|.
+//
 //   |uint32_t struct_size|: Set to the size of the
 //       |MojoDuplicateBufferHandleOptions| struct. (Used to allow for future
 //       extensions.)
-//   |MojoDuplicateBufferHandleOptionsFlags flags|: Reserved for future use.
-//       |MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE|: No flags; default
-//       mode.
-//       |MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY|: The duplicate
-//       shared buffer can only be mapped read-only. A read-only duplicate can
-//       only be created before the buffer is passed over a message pipe.
 //
-// TODO(vtl): Add flags to remove writability (and executability)? Also, COW?
+//   |MojoDuplicateBufferHandleOptionsFlags flags|: Flags to control the
+//       behavior of |MojoDuplicateBufferHandle()|. May be some combination of
+//       the following:
+//
+//       |MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE|: No flags; default
+//           mode.
+//       |MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY|: The duplicate
+//           shared buffer can only be mapped read-only. A read-only duplicate
+//           may only be created before any handles to the buffer are passed
+//           over a message pipe.
 
 typedef uint32_t MojoDuplicateBufferHandleOptionsFlags;
 
@@ -102,18 +101,15 @@
 // label for pointer parameters.
 
 // Creates a buffer of size |num_bytes| bytes that can be shared between
-// applications (by duplicating the handle -- see |MojoDuplicateBufferHandle()|
-// -- and passing it over a message pipe). To access the buffer, one must call
-// |MojoMapBuffer()|.
+// processes. The returned handle may be duplicated any number of times by
+// |MojoDuplicateBufferHandle()|.
+//
+// To access the buffer's storage, one must call |MojoMapBuffer()|.
 //
 // |options| may be set to null for a shared buffer with the default options.
 //
 // On success, |*shared_buffer_handle| will be set to the handle for the shared
-// buffer. (On failure, it is not modified.)
-//
-// Note: While more than |num_bytes| bytes may apparently be
-// available/visible/readable/writable, trying to use those extra bytes is
-// undefined behavior.
+// buffer. On failure it is not modified.
 //
 // Returns:
 //   |MOJO_RESULT_OK| on success.
@@ -128,17 +124,14 @@
     uint64_t num_bytes,                                   // In.
     MojoHandle* shared_buffer_handle);                    // Out.
 
-// Duplicates the handle |buffer_handle| to a buffer. This creates another
-// handle (returned in |*new_buffer_handle| on success), which can then be sent
-// to another application over a message pipe, while retaining access to the
-// |buffer_handle| (and any mappings that it may have).
+// Duplicates the handle |buffer_handle| as a new shared buffer handle. On
+// success this returns the new handle in |*new_buffer_handle|. A shared buffer
+// remains allocated as long as there is at least one shared buffer handle
+// referencing it in at least one process in the system.
 //
 // |options| may be set to null to duplicate the buffer handle with the default
 // options.
 //
-// On success, |*shared_buffer_handle| will be set to the handle for the new
-// buffer handle. (On failure, it is not modified.)
-//
 // Returns:
 //   |MOJO_RESULT_OK| on success.
 //   |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
@@ -152,17 +145,16 @@
 // Maps the part (at offset |offset| of length |num_bytes|) of the buffer given
 // by |buffer_handle| into memory, with options specified by |flags|. |offset +
 // num_bytes| must be less than or equal to the size of the buffer. On success,
-// |*buffer| points to memory with the requested part of the buffer. (On
-// failure, it is not modified.)
+// |*buffer| points to memory with the requested part of the buffer. On
+// failure |*buffer| it is not modified.
 //
-// A single buffer handle may have multiple active mappings (possibly depending
-// on the buffer type). The permissions (e.g., writable or executable) of the
-// returned memory may depend on the properties of the buffer and properties
-// attached to the buffer handle as well as |flags|.
+// A single buffer handle may have multiple active mappings The permissions
+// (e.g., writable or executable) of the returned memory depend on th
+// properties of the buffer and properties attached to the buffer handle, as
+// well as |flags|.
 //
-// Note: Though data outside the specified range may apparently be
-// available/visible/readable/writable, trying to use those extra bytes is
-// undefined behavior.
+// A mapped buffer must eventually be unmapped by calling |MojoUnmapBuffer()|
+// with the value of |*buffer| returned by this function.
 //
 // Returns:
 //   |MOJO_RESULT_OK| on success.
@@ -179,8 +171,9 @@
 
 // Unmaps a buffer pointer that was mapped by |MojoMapBuffer()|. |buffer| must
 // have been the result of |MojoMapBuffer()| (not some other pointer inside
-// the mapped memory), and the entire mapping will be removed (partial unmapping
-// is not supported). A mapping may only be unmapped once.
+// the mapped memory), and the entire mapping will be removed.
+//
+// A mapping may only be unmapped once.
 //
 // Returns:
 //   |MOJO_RESULT_OK| on success.
diff --git a/mojo/public/c/system/core.h b/mojo/public/c/system/core.h
index 77d452b..03c0652 100644
--- a/mojo/public/c/system/core.h
+++ b/mojo/public/c/system/core.h
@@ -17,6 +17,6 @@
 #include "mojo/public/c/system/platform_handle.h"
 #include "mojo/public/c/system/system_export.h"
 #include "mojo/public/c/system/types.h"
-#include "mojo/public/c/system/wait_set.h"
+#include "mojo/public/c/system/watcher.h"
 
 #endif  // MOJO_PUBLIC_C_SYSTEM_CORE_H_
diff --git a/mojo/public/c/system/data_pipe.h b/mojo/public/c/system/data_pipe.h
index 4a7dcef..f51e36c 100644
--- a/mojo/public/c/system/data_pipe.h
+++ b/mojo/public/c/system/data_pipe.h
@@ -17,14 +17,19 @@
 
 // |MojoCreateDataPipeOptions|: Used to specify creation parameters for a data
 // pipe to |MojoCreateDataPipe()|.
+//
 //   |uint32_t struct_size|: Set to the size of the |MojoCreateDataPipeOptions|
 //       struct. (Used to allow for future extensions.)
+//
 //   |MojoCreateDataPipeOptionsFlags flags|: Used to specify different modes of
-//       operation.
-//     |MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE|: No flags; default mode.
+//       operation. May be some combination of the following values:
+//
+//       |MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE|: No flags; default mode.
+//
 //   |uint32_t element_num_bytes|: The size of an element, in bytes. All
 //       transactions and buffers will consist of an integral number of
 //       elements. Must be nonzero.
+//
 //   |uint32_t capacity_num_bytes|: The capacity of the data pipe, in number of
 //       bytes; must be a multiple of |element_num_bytes|. The data pipe will
 //       always be able to queue AT LEAST this much data. Set to zero to opt for
@@ -52,10 +57,11 @@
                    "MojoCreateDataPipeOptions has wrong size");
 
 // |MojoWriteDataFlags|: Used to specify different modes to |MojoWriteData()|
-// and |MojoBeginWriteData()|.
+// and |MojoBeginWriteData()|. May be some combination of the following values:
+//
 //   |MOJO_WRITE_DATA_FLAG_NONE| - No flags; default mode.
 //   |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| - Write either all the elements
-//       requested or none of them.
+//      requested or none of them.
 
 typedef uint32_t MojoWriteDataFlags;
 
@@ -68,10 +74,12 @@
 #endif
 
 // |MojoReadDataFlags|: Used to specify different modes to |MojoReadData()| and
-// |MojoBeginReadData()|.
+// |MojoBeginReadData()|. May be some combination of the following values:
+//
 //   |MOJO_READ_DATA_FLAG_NONE| - No flags; default mode.
 //   |MOJO_READ_DATA_FLAG_ALL_OR_NONE| - Read (or discard) either the requested
-//        number of elements or none.
+//        number of elements or none. NOTE: This flag is not currently supported
+//        by |MojoBeginReadData()|.
 //   |MOJO_READ_DATA_FLAG_DISCARD| - Discard (up to) the requested number of
 //        elements.
 //   |MOJO_READ_DATA_FLAG_QUERY| - Query the number of elements available to
@@ -106,9 +114,10 @@
 // label for pointer parameters.
 
 // Creates a data pipe, which is a unidirectional communication channel for
-// unframed data, with the given options. Data is unframed, but must come as
-// (multiples of) discrete elements, of the size given in |options|. See
-// |MojoCreateDataPipeOptions| for a description of the different options
+// unframed data. Data must be read and written in multiples of discrete
+// discrete elements of size given in |options|.
+//
+// See |MojoCreateDataPipeOptions| for a description of the different options
 // available for data pipes.
 //
 // |options| may be set to null for a data pipe with the default options (which
@@ -117,7 +126,7 @@
 //
 // On success, |*data_pipe_producer_handle| will be set to the handle for the
 // producer and |*data_pipe_consumer_handle| will be set to the handle for the
-// consumer. (On failure, they are not modified.)
+// consumer. On failure they are not modified.
 //
 // Returns:
 //   |MOJO_RESULT_OK| on success.
@@ -132,29 +141,22 @@
     MojoHandle* data_pipe_producer_handle,            // Out.
     MojoHandle* data_pipe_consumer_handle);           // Out.
 
-// Writes the given data to the data pipe producer given by
-// |data_pipe_producer_handle|. |elements| points to data of size |*num_bytes|;
-// |*num_bytes| should be a multiple of the data pipe's element size. If
+// Writes the data pipe producer given by |data_pipe_producer_handle|.
+//
+// |elements| points to data of size |*num_bytes|; |*num_bytes| must be a
+// multiple of the data pipe's element size. If
 // |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| is set in |flags|, either all the data
-// will be written or none is.
+// is written (if enough write capacity is available) or none is.
 //
-// On success, |*num_bytes| is set to the amount of data that was actually
-// written.
-//
-// Note: If the data pipe has the "may discard" option flag (specified on
-// creation), this will discard as much data as required to write the given
-// data, starting with the earliest written data that has not been consumed.
-// However, even with "may discard", if |*num_bytes| is greater than the data
-// pipe's capacity (and |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| is not set), this
-// will write the maximum amount possible (namely, the data pipe's capacity) and
-// set |*num_bytes| to that amount. It will *not* discard data from |elements|.
+// On success |*num_bytes| is set to the amount of data that was actually
+// written. On failure it is unmodified.
 //
 // Returns:
 //   |MOJO_RESULT_OK| on success.
 //   |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
 //       |data_pipe_producer_dispatcher| is not a handle to a data pipe
 //       producer or |*num_bytes| is not a multiple of the data pipe's element
-//       size).
+//       size.)
 //   |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer handle has been
 //       closed.
 //   |MOJO_RESULT_OUT_OF_RANGE| if |flags| has
@@ -166,8 +168,6 @@
 //   |MOJO_RESULT_SHOULD_WAIT| if no data can currently be written (and the
 //       consumer is still open) and |flags| does *not* have
 //       |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set.
-//
-// TODO(vtl): Should there be a way of querying how much data can be written?
 MOJO_SYSTEM_EXPORT MojoResult
     MojoWriteData(MojoHandle data_pipe_producer_handle,
                   const void* elements,
@@ -175,40 +175,26 @@
                   MojoWriteDataFlags flags);
 
 // Begins a two-phase write to the data pipe producer given by
-// |data_pipe_producer_handle|. On success, |*buffer| will be a pointer to which
-// the caller can write |*buffer_num_bytes| bytes of data. If flags has
-// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set, then the output value
-// |*buffer_num_bytes| will be at least as large as its input value, which must
-// also be a multiple of the element size (if |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE|
-// is not set, the input value of |*buffer_num_bytes| is ignored).
+// |data_pipe_producer_handle|. On success |*buffer| will be a pointer to which
+// the caller can write up to |*buffer_num_bytes| bytes of data.
 //
 // During a two-phase write, |data_pipe_producer_handle| is *not* writable.
-// E.g., if another thread tries to write to it, it will get |MOJO_RESULT_BUSY|;
-// that thread can then wait for |data_pipe_producer_handle| to become writable
-// again.
+// If another caller tries to write to it by calling |MojoWriteData()| or
+// |MojoBeginWriteData()|, their request will fail with |MOJO_RESULT_BUSY|.
 //
-// When |MojoBeginWriteData()| returns MOJO_RESULT_OK, and the caller has
-// finished writing data to |*buffer|, it should call |MojoEndWriteData()| to
-// specify the amount written and to complete the two-phase write.
-// |MojoEndWriteData()| need not be called for other return values.
-//
-// Note: If the data pipe has the "may discard" option flag (specified on
-// creation) and |flags| has |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set, this may
-// discard some data.
+// If |MojoBeginWriteData()| returns MOJO_RESULT_OK and once the caller has
+// finished writing data to |*buffer|, |MojoEndWriteData()| must be called to
+// indicate the amount of data actually written and to complete the two-phase
+// write operation. |MojoEndWriteData()| need not be called when
+// |MojoBeginWriteData()| fails.
 //
 // Returns:
 //   |MOJO_RESULT_OK| on success.
 //   |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
 //       |data_pipe_producer_handle| is not a handle to a data pipe producer or
-//       flags has |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set and
-//       |*buffer_num_bytes| is not a multiple of the element size).
+//       flags has |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set.
 //   |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer handle has been
 //       closed.
-//   |MOJO_RESULT_OUT_OF_RANGE| if |flags| has
-//       |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set and the required amount of data
-//       (specified by |*buffer_num_bytes|) cannot be written contiguously at
-//       this time. (Note that there may be space available for the required
-//       amount of data, but the "next" write position may not be large enough.)
 //   |MOJO_RESULT_BUSY| if there is already a two-phase write ongoing with
 //       |data_pipe_producer_handle| (i.e., |MojoBeginWriteData()| has been
 //       called, but not yet the matching |MojoEndWriteData()|).
@@ -220,17 +206,16 @@
                        uint32_t* buffer_num_bytes,  // In/out.
                        MojoWriteDataFlags flags);
 
-// Ends a two-phase write to the data pipe producer given by
-// |data_pipe_producer_handle| that was begun by a call to
-// |MojoBeginWriteData()| on the same handle. |num_bytes_written| should
-// indicate the amount of data actually written; it must be less than or equal
-// to the value of |*buffer_num_bytes| output by |MojoBeginWriteData()| and must
-// be a multiple of the element size. The buffer given by |*buffer| from
-// |MojoBeginWriteData()| must have been filled with exactly |num_bytes_written|
-// bytes of data.
+// Ends a two-phase write that was previously initiated by
+// |MojoBeginWriteData()| for the same |data_pipe_producer_handle|.
+//
+// |num_bytes_written| must indicate the number of bytes actually written into
+// the two-phase write buffer. It must be less than or equal to the value of
+// |*buffer_num_bytes| output by |MojoBeginWriteData()|, and it must be a
+// multiple of the data pipe's element size.
 //
 // On failure, the two-phase write (if any) is ended (so the handle may become
-// writable again, if there's space available) but no data written to |*buffer|
+// writable again if there's space available) but no data written to |*buffer|
 // is "put into" the data pipe.
 //
 // Returns:
@@ -297,36 +282,27 @@
 
 // Begins a two-phase read from the data pipe consumer given by
 // |data_pipe_consumer_handle|. On success, |*buffer| will be a pointer from
-// which the caller can read |*buffer_num_bytes| bytes of data. If flags has
-// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set, then the output value
-// |*buffer_num_bytes| will be at least as large as its input value, which must
-// also be a multiple of the element size (if |MOJO_READ_DATA_FLAG_ALL_OR_NONE|
-// is not set, the input value of |*buffer_num_bytes| is ignored). |flags| must
-// not have |MOJO_READ_DATA_FLAG_DISCARD|, |MOJO_READ_DATA_FLAG_QUERY|, or
-// |MOJO_READ_DATA_FLAG_PEEK| set.
+// which the caller can read up to |*buffer_num_bytes| bytes of data.
 //
 // During a two-phase read, |data_pipe_consumer_handle| is *not* readable.
-// E.g., if another thread tries to read from it, it will get
-// |MOJO_RESULT_BUSY|; that thread can then wait for |data_pipe_consumer_handle|
-// to become readable again.
+// If another caller tries to read from it by calling |MojoReadData()| or
+// |MojoBeginReadData()|, their request will fail with |MOJO_RESULT_BUSY|.
 //
-// Once the caller has finished reading data from |*buffer|, it should call
-// |MojoEndReadData()| to specify the amount read and to complete the two-phase
-// read.
+// Once the caller has finished reading data from |*buffer|, |MojoEndReadData()|
+// must be called to indicate the number of bytes read and to complete the
+// two-phase read operation.
+//
+// |flags| must not have |MOJO_READ_DATA_FLAG_DISCARD|,
+// |MOJO_READ_DATA_FLAG_QUERY|, |MOJO_READ_DATA_FLAG_PEEK|, or
+// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set.
 //
 // Returns:
 //   |MOJO_RESULT_OK| on success.
 //   |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
 //       |data_pipe_consumer_handle| is not a handle to a data pipe consumer,
-//       |flags| has |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set and
-//       |*buffer_num_bytes| is not a multiple of the element size, or |flags|
-//       has invalid flags set).
+//       or |flags| has invalid flags set.)
 //   |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe producer handle has been
 //       closed.
-//   |MOJO_RESULT_OUT_OF_RANGE| if |flags| has |MOJO_READ_DATA_FLAG_ALL_OR_NONE|
-//       set and the required amount of data (specified by |*buffer_num_bytes|)
-//       cannot be read from a contiguous buffer at this time. (Note that there
-//       may be the required amount of data, but it may not be contiguous.)
 //   |MOJO_RESULT_BUSY| if there is already a two-phase read ongoing with
 //       |data_pipe_consumer_handle| (i.e., |MojoBeginReadData()| has been
 //       called, but not yet the matching |MojoEndReadData()|).
diff --git a/mojo/public/c/system/functions.h b/mojo/public/c/system/functions.h
index f0a23d9..d0656c6 100644
--- a/mojo/public/c/system/functions.h
+++ b/mojo/public/c/system/functions.h
@@ -19,14 +19,6 @@
 extern "C" {
 #endif
 
-// A callback used to notify watchers registered via |MojoWatch()|. Called when
-// some watched signals are satisfied or become unsatisfiable. See the
-// documentation for |MojoWatch()| for more details.
-typedef void (*MojoWatchCallback)(uintptr_t context,
-                                  MojoResult result,
-                                  struct MojoHandleSignalsState signals_state,
-                                  MojoWatchNotificationFlags flags);
-
 // Note: Pointer parameters that are labelled "optional" may be null (at least
 // under some circumstances). Non-const pointer parameters are also labeled
 // "in", "out", or "in/out", to indicate how they are used. (Note that how/if
@@ -50,161 +42,24 @@
 //
 // Concurrent operations on |handle| may succeed (or fail as usual) if they
 // happen before the close, be cancelled with result |MOJO_RESULT_CANCELLED| if
-// they properly overlap (this is likely the case with |MojoWait()|, etc.), or
-// fail with |MOJO_RESULT_INVALID_ARGUMENT| if they happen after.
+// they properly overlap (this is likely the case with watchers), or fail with
+// |MOJO_RESULT_INVALID_ARGUMENT| if they happen after.
 MOJO_SYSTEM_EXPORT MojoResult MojoClose(MojoHandle handle);
 
-// Waits on the given handle until one of the following happens:
-//   - A signal indicated by |signals| is satisfied.
-//   - It becomes known that no signal indicated by |signals| will ever be
-//     satisfied. (See the description of the |MOJO_RESULT_CANCELLED| and
-//     |MOJO_RESULT_FAILED_PRECONDITION| return values below.)
-//   - Until |deadline| has passed.
+// Queries the last known signals state of a handle.
 //
-// If |deadline| is |MOJO_DEADLINE_INDEFINITE|, this will wait "forever" (until
-// one of the other wait termination conditions is satisfied). If |deadline| is
-// 0, this will return |MOJO_RESULT_DEADLINE_EXCEEDED| only if one of the other
-// termination conditions (e.g., a signal is satisfied, or all signals are
-// unsatisfiable) is not already satisfied.
-//
-// |signals_state| (optional): See documentation for |MojoHandleSignalsState|.
+// Note that no guarantees can be made about the accuracy of the returned
+// signals state by the time this returns, as other threads in the system may
+// change the handle's state at any time. Use with appropriate discretion.
 //
 // Returns:
-//   |MOJO_RESULT_OK| if some signal in |signals| was satisfied (or is already
-//       satisfied).
-//   |MOJO_RESULT_CANCELLED| if |handle| was closed (necessarily from another
-//       thread) during the wait.
-//   |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle (e.g., if
-//       it has already been closed). The |signals_state| value is unchanged.
-//   |MOJO_RESULT_DEADLINE_EXCEEDED| if the deadline has passed without any of
-//       the signals being satisfied.
-//   |MOJO_RESULT_FAILED_PRECONDITION| if it becomes known that none of the
-//       signals in |signals| can ever be satisfied (e.g., when waiting on one
-//       end of a message pipe and the other end is closed).
-//
-// If there are multiple waiters (on different threads, obviously) waiting on
-// the same handle and signal, and that signal becomes satisfied, all waiters
-// will be awoken.
+//   |MOJO_RESULT_OK| on success. |*signals_state| is populated with the
+//       last known signals state of |handle|.
+//   |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle or
+//       |signals_state| is null.
 MOJO_SYSTEM_EXPORT MojoResult
-MojoWait(MojoHandle handle,
-         MojoHandleSignals signals,
-         MojoDeadline deadline,
-         struct MojoHandleSignalsState* signals_state);  // Optional out.
-
-// Waits on |handles[0]|, ..., |handles[num_handles-1]| until:
-//   - (At least) one handle satisfies a signal indicated in its respective
-//     |signals[0]|, ..., |signals[num_handles-1]|.
-//   - It becomes known that no signal in some |signals[i]| will ever be
-//     satisfied.
-//   - |deadline| has passed.
-//
-// This means that |MojoWaitMany()| behaves as if |MojoWait()| were called on
-// each handle/signals pair simultaneously, completing when the first
-// |MojoWait()| would complete.
-//
-// See |MojoWait()| for more details about |deadline|.
-//
-// |result_index| (optional) is used to return the index of the handle that
-//     caused the call to return. For example, the index |i| (from 0 to
-//     |num_handles-1|) if |handle[i]| satisfies a signal from |signals[i]|. You
-//     must manually initialize this to a suitable sentinel value (e.g. -1)
-//     before you make this call because this value is not updated if there is
-//     no specific handle that causes the function to return. Pass null if you
-//     don't need this value to be returned.
-//
-// |signals_states| (optional) points to an array of size |num_handles| of
-//     MojoHandleSignalsState. See |MojoHandleSignalsState| for more details
-//     about the meaning of each array entry. This array is not an atomic
-//     snapshot. The array will be updated if the function does not return
-//     |MOJO_RESULT_INVALID_ARGUMENT| or |MOJO_RESULT_RESOURCE_EXHAUSTED|.
-//
-// Returns:
-//   |MOJO_RESULT_CANCELLED| if some |handle[i]| was closed (necessarily from
-//       another thread) during the wait.
-//   |MOJO_RESULT_RESOURCE_EXHAUSTED| if there are too many handles. The
-//       |signals_state| array is unchanged.
-//   |MOJO_RESULT_INVALID_ARGUMENT| if some |handle[i]| is not a valid handle
-//       (e.g., if it is zero or if it has already been closed). The
-//       |signals_state| array is unchanged.
-//   |MOJO_RESULT_DEADLINE_EXCEEDED| if the deadline has passed without any of
-//       handles satisfying any of its signals.
-//   |MOJO_RESULT_FAILED_PRECONDITION| if it is or becomes impossible that SOME
-//       |handle[i]| will ever satisfy any of the signals in |signals[i]|.
-MOJO_SYSTEM_EXPORT MojoResult
-MojoWaitMany(const MojoHandle* handles,
-             const MojoHandleSignals* signals,
-             uint32_t num_handles,
-             MojoDeadline deadline,
-             uint32_t* result_index,                          // Optional out
-             struct MojoHandleSignalsState* signals_states);  // Optional out
-
-// Watches the given handle for one of the following events to happen:
-//   - A signal indicated by |signals| is satisfied.
-//   - It becomes known that no signal indicated by |signals| will ever be
-//     satisfied. (See the description of the |MOJO_RESULT_CANCELLED| and
-//     |MOJO_RESULT_FAILED_PRECONDITION| return values below.)
-//   - The handle is closed.
-//
-// |handle|: The handle to watch. Must be an open message pipe or data pipe
-//     handle.
-// |signals|: The signals to watch for.
-// |callback|: A function to be called any time one of the above events happens.
-//     The function must be safe to call from any thread at any time.
-// |context|: User-provided context passed to |callback| when called. |context|
-//     is used to uniquely identify a registered watch and can be used to cancel
-//     the watch later using |MojoCancelWatch()|.
-//
-// Returns:
-//   |MOJO_RESULT_OK| if the watch has been successfully registered. Note that
-//       if the signals are already satisfied this may synchronously invoke
-//       |callback| before returning.
-//   |MOJO_RESULT_CANCELLED| if the watch was cancelled. In this case it is not
-//       necessary to explicitly call |MojoCancelWatch()|, and in fact it may be
-//       an error to do so as the handle may have been closed.
-//   |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not an open message pipe
-//       handle.
-//   |MOJO_RESULT_FAILED_PRECONDITION| if it is already known that |signals| can
-//       never be satisfied.
-//   |MOJO_RESULT_ALREADY_EXISTS| if there is already a watch registered for
-//       the same combination of |handle| and |context|.
-//
-// Callback result codes:
-//   The callback may be called at any time on any thread with one of the
-//   following result codes to indicate various events:
-//
-//   |MOJO_RESULT_OK| indicates that some signal in |signals| has been
-//       satisfied.
-//   |MOJO_RESULT_FAILED_PRECONDITION| indicates that no signals in |signals|
-//       can ever be satisfied again.
-//   |MOJO_RESULT_CANCELLED| indicates that the handle has been closed. In this
-//       case the watch is implicitly cancelled and there is no need to call
-//       |MojoCancelWatch()|.
-MOJO_SYSTEM_EXPORT MojoResult
-MojoWatch(MojoHandle handle,
-          MojoHandleSignals signals,
-          MojoWatchCallback callback,
-          uintptr_t context);
-
-// Cancels a handle watch corresponding to some prior call to |MojoWatch()|.
-//
-// NOTE: If the watch callback corresponding to |context| is currently running
-// this will block until the callback completes execution. It is therefore
-// illegal to call |MojoCancelWatch()| on a given |handle| and |context| from
-// within the associated callback itself, as this will always deadlock.
-//
-// After |MojoCancelWatch()| function returns, the watch's associated callback
-// will NEVER be called again by Mojo.
-//
-// |context|: The same user-provided context given to some prior call to
-//     |MojoWatch()|. Only the watch corresponding to this context will be
-//     cancelled.
-//
-// Returns:
-//     |MOJO_RESULT_OK| if the watch corresponding to |context| was cancelled.
-//     |MOJO_RESULT_INVALID_ARGUMENT| if no watch was registered with |context|
-//         for the given |handle|, or if |handle| is invalid.
-MOJO_SYSTEM_EXPORT MojoResult
-MojoCancelWatch(MojoHandle handle, uintptr_t context);
+MojoQueryHandleSignalsState(MojoHandle handle,
+                            struct MojoHandleSignalsState* signals_state);
 
 // Retrieves system properties. See the documentation for |MojoPropertyType| for
 // supported property types and their corresponding output value type.
diff --git a/mojo/public/c/system/platform_handle.h b/mojo/public/c/system/platform_handle.h
index 0b02357..7449c2e 100644
--- a/mojo/public/c/system/platform_handle.h
+++ b/mojo/public/c/system/platform_handle.h
@@ -45,12 +45,16 @@
 #define MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE ((MojoPlatformHandleType)3)
 #endif
 
-// |MojoPlatformHandle|: A handle to an OS object.
+// |MojoPlatformHandle|: A handle to a native platform object.
+//
 //     |uint32_t struct_size|: The size of this structure. Used for versioning
 //         to allow for future extensions.
+//
 //     |MojoPlatformHandleType type|: The type of handle stored in |value|.
+//
 //     |uint64_t value|: The value of this handle. Ignored if |type| is
-//         MOJO_PLATFORM_HANDLE_TYPE_INVALID.
+//         MOJO_PLATFORM_HANDLE_TYPE_INVALID. Otherwise the meaning of this
+//         value depends on the value of |type|.
 //
 
 struct MOJO_ALIGNAS(8) MojoPlatformHandle {
@@ -84,8 +88,9 @@
     ((MojoPlatformSharedBufferHandleFlags)1 << 0)
 #endif
 
-// Wraps a generic platform handle as a Mojo handle which can be transferred
-// over a message pipe. Takes ownership of the underlying platform object.
+// Wraps a native platform handle as a Mojo handle which can be transferred
+// over a message pipe. Takes ownership of the underlying native platform
+// object.
 //
 // |platform_handle|: The platform handle to wrap.
 //
@@ -103,12 +108,12 @@
 MojoWrapPlatformHandle(const struct MojoPlatformHandle* platform_handle,
                        MojoHandle* mojo_handle);  // Out
 
-// Unwraps a generic platform handle from a Mojo handle. If this call succeeds,
-// ownership of the underlying platform object is bound to the returned platform
-// handle and becomes the caller's responsibility. The Mojo handle is always
-// closed regardless of success or failure.
+// Unwraps a native platform handle from a Mojo handle. If this call succeeds,
+// ownership of the underlying platform object is assumed by the caller. The
+// The Mojo handle is always closed regardless of success or failure.
 //
-// |mojo_handle|: The Mojo handle from which to unwrap the platform handle.
+// |mojo_handle|: The Mojo handle from which to unwrap the native platform
+//     handle.
 //
 // Returns:
 //     |MOJO_RESULT_OK| if the handle was successfully unwrapped. In this case
@@ -119,11 +124,13 @@
 MojoUnwrapPlatformHandle(MojoHandle mojo_handle,
                          struct MojoPlatformHandle* platform_handle);  // Out
 
-// Wraps a platform shared buffer handle as a Mojo shared buffer handle which
-// can be transferred over a message pipe. Takes ownership of the platform
-// shared buffer handle.
+// Wraps a native platform shared buffer handle as a Mojo shared buffer handle
+// which can be used exactly like a shared buffer handle created by
+// |MojoCreateSharedBuffer()| or |MojoDuplicateBufferHandle()|.
 //
-// |platform_handle|: The platform handle to wrap. Must be a handle to a
+// Takes ownership of the native platform shared buffer handle.
+//
+// |platform_handle|: The platform handle to wrap. Must be a native handle to a
 //     shared buffer object.
 // |num_bytes|: The size of the shared buffer in bytes.
 // |flags|: Flags which influence the treatment of the shared buffer object. See
@@ -148,11 +155,11 @@
     MojoPlatformSharedBufferHandleFlags flags,
     MojoHandle* mojo_handle);  // Out
 
-// Unwraps a platform shared buffer handle from a Mojo shared buffer handle.
-// If this call succeeds, ownership of the underlying shared buffer object is
-// bound to the returned platform handle and becomes the caller's
-// responsibility. The Mojo handle is always closed regardless of success or
-// failure.
+// Unwraps a native platform shared buffer handle from a Mojo shared buffer
+// handle. If this call succeeds, ownership of the underlying shared buffer
+// object is assumed by the caller.
+//
+// The Mojo handle is always closed regardless of success or failure.
 //
 // |mojo_handle|: The Mojo shared buffer handle to unwrap.
 //
diff --git a/mojo/public/c/system/tests/BUILD.gn b/mojo/public/c/system/tests/BUILD.gn
index 0dd7052..bace63c 100644
--- a/mojo/public/c/system/tests/BUILD.gn
+++ b/mojo/public/c/system/tests/BUILD.gn
@@ -18,6 +18,7 @@
 
   deps = [
     "//mojo/public/c/system",
+    "//mojo/public/cpp/system",
     "//testing/gtest",
   ]
 }
diff --git a/mojo/public/c/system/tests/core_perftest.cc b/mojo/public/c/system/tests/core_perftest.cc
index 5d4e56b..cab465b 100644
--- a/mojo/public/c/system/tests/core_perftest.cc
+++ b/mojo/public/c/system/tests/core_perftest.cc
@@ -12,6 +12,7 @@
 
 #include "base/macros.h"
 #include "base/threading/simple_thread.h"
+#include "mojo/public/cpp/system/wait.h"
 #include "mojo/public/cpp/test_support/test_support.h"
 #include "mojo/public/cpp/test_support/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -85,8 +86,7 @@
       }
 
       if (result == MOJO_RESULT_SHOULD_WAIT) {
-        result = MojoWait(handle_, MOJO_HANDLE_SIGNAL_READABLE,
-                          MOJO_DEADLINE_INDEFINITE, nullptr);
+        result = mojo::Wait(mojo::Handle(handle_), MOJO_HANDLE_SIGNAL_READABLE);
         if (result == MOJO_RESULT_OK) {
           // Go to the top of the loop to read again.
           continue;
diff --git a/mojo/public/c/system/tests/core_unittest.cc b/mojo/public/c/system/tests/core_unittest.cc
index 8a54380..a9da255 100644
--- a/mojo/public/c/system/tests/core_unittest.cc
+++ b/mojo/public/c/system/tests/core_unittest.cc
@@ -9,6 +9,7 @@
 #include <stdint.h>
 #include <string.h>
 
+#include "mojo/public/cpp/system/wait.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace mojo {
@@ -31,7 +32,6 @@
 // Tests that everything that takes a handle properly recognizes it.
 TEST(CoreTest, InvalidHandle) {
   MojoHandle h0, h1;
-  MojoHandleSignals sig;
   char buffer[10] = {0};
   uint32_t buffer_size;
   void* write_pointer;
@@ -40,18 +40,8 @@
   // Close:
   EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(MOJO_HANDLE_INVALID));
 
-  // Wait:
-  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-            MojoWait(MOJO_HANDLE_INVALID, ~MOJO_HANDLE_SIGNAL_NONE, 1000000,
-                     nullptr));
-
-  h0 = MOJO_HANDLE_INVALID;
-  sig = ~MOJO_HANDLE_SIGNAL_NONE;
-  EXPECT_EQ(
-      MOJO_RESULT_INVALID_ARGUMENT,
-      MojoWaitMany(&h0, &sig, 1, MOJO_DEADLINE_INDEFINITE, nullptr, nullptr));
-
   // Message pipe:
+  h0 = MOJO_HANDLE_INVALID;
   EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
             MojoWriteMessage(h0, buffer, 3, nullptr, 0,
                              MOJO_WRITE_MESSAGE_FLAG_NONE));
@@ -98,23 +88,12 @@
   EXPECT_NE(h0, MOJO_HANDLE_INVALID);
   EXPECT_NE(h1, MOJO_HANDLE_INVALID);
 
-  // Shouldn't be readable, we haven't written anything.
+  // Shouldn't be readable, we haven't written anything. Should be writable.
   MojoHandleSignalsState state;
-  EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
-            MojoWait(h0, MOJO_HANDLE_SIGNAL_READABLE, 0, &state));
+  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(h0, &state));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals);
   EXPECT_EQ(kSignalAll, state.satisfiable_signals);
 
-  // Should be writable.
-  EXPECT_EQ(MOJO_RESULT_OK,
-            MojoWait(h0, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &state));
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals);
-  EXPECT_EQ(kSignalAll, state.satisfiable_signals);
-
-  // Last parameter is optional.
-  EXPECT_EQ(MOJO_RESULT_OK,
-            MojoWait(h0, MOJO_HANDLE_SIGNAL_WRITABLE, 0, nullptr));
-
   // Try to read.
   buffer_size = static_cast<uint32_t>(sizeof(buffer));
   EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
@@ -128,11 +107,12 @@
                                              0, MOJO_WRITE_MESSAGE_FLAG_NONE));
 
   // |h0| should be readable.
-  uint32_t result_index = 1;
+  size_t result_index = 1;
   MojoHandleSignalsState states[1];
   sig = MOJO_HANDLE_SIGNAL_READABLE;
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWaitMany(&h0, &sig, 1, MOJO_DEADLINE_INDEFINITE,
-                                         &result_index, states));
+  Handle handle0(h0);
+  EXPECT_EQ(MOJO_RESULT_OK,
+            mojo::WaitMany(&handle0, &sig, 1, &result_index, states));
 
   EXPECT_EQ(0u, result_index);
   EXPECT_EQ(kSignalReadadableWritable, states[0].satisfied_signals);
@@ -147,24 +127,22 @@
   EXPECT_STREQ(kHello, buffer);
 
   // |h0| should no longer be readable.
-  EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
-            MojoWait(h0, MOJO_HANDLE_SIGNAL_READABLE, 10, &state));
-
+  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(h0, &state));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals);
   EXPECT_EQ(kSignalAll, state.satisfiable_signals);
 
   // Close |h0|.
   EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0));
 
-  EXPECT_EQ(MOJO_RESULT_OK,
-            MojoWait(h1, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                     MOJO_DEADLINE_INDEFINITE, &state));
+  EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h1),
+                                       MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state));
 
   // |h1| should no longer be readable or writable.
   EXPECT_EQ(
       MOJO_RESULT_FAILED_PRECONDITION,
-      MojoWait(h1, MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
-               1000, &state));
+      mojo::Wait(mojo::Handle(h1),
+                 MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+                 &state));
 
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals);
@@ -188,18 +166,14 @@
 
   // The consumer |hc| shouldn't be readable.
   MojoHandleSignalsState state;
-  EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
-            MojoWait(hc, MOJO_HANDLE_SIGNAL_READABLE, 0, &state));
-
+  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(hc, &state));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_NONE, state.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
                 MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
             state.satisfiable_signals);
 
   // The producer |hp| should be writable.
-  EXPECT_EQ(MOJO_RESULT_OK,
-            MojoWait(hp, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &state));
-
+  EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(hp, &state));
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
             state.satisfiable_signals);
@@ -223,11 +197,12 @@
                                           MOJO_WRITE_MESSAGE_FLAG_NONE));
 
   // |hc| should be(come) readable.
-  uint32_t result_index = 1;
+  size_t result_index = 1;
   MojoHandleSignalsState states[1];
   sig = MOJO_HANDLE_SIGNAL_READABLE;
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWaitMany(&hc, &sig, 1, MOJO_DEADLINE_INDEFINITE,
-                                         &result_index, states));
+  Handle consumer_handle(hc);
+  EXPECT_EQ(MOJO_RESULT_OK,
+            mojo::WaitMany(&consumer_handle, &sig, 1, &result_index, states));
 
   EXPECT_EQ(0u, result_index);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
@@ -256,9 +231,8 @@
   EXPECT_EQ(MOJO_RESULT_OK, MojoClose(hp));
 
   // |hc| should still be readable.
-  EXPECT_EQ(MOJO_RESULT_OK,
-            MojoWait(hc, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-                     MOJO_DEADLINE_INDEFINITE, &state));
+  EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(hc),
+                                       MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state));
 
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
             state.satisfied_signals);
@@ -276,7 +250,7 @@
 
   // |hc| should no longer be readable.
   EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            MojoWait(hc, MOJO_HANDLE_SIGNAL_READABLE, 1000, &state));
+            mojo::Wait(mojo::Handle(hc), MOJO_HANDLE_SIGNAL_READABLE, &state));
 
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals);
   EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals);
diff --git a/mojo/public/c/system/tests/core_unittest_pure_c.c b/mojo/public/c/system/tests/core_unittest_pure_c.c
index fa3caa5..3164649 100644
--- a/mojo/public/c/system/tests/core_unittest_pure_c.c
+++ b/mojo/public/c/system/tests/core_unittest_pure_c.c
@@ -42,7 +42,6 @@
   // at the top. (MSVS 2013 is more reasonable.)
   MojoTimeTicks ticks;
   MojoHandle handle0, handle1;
-  MojoHandleSignals signals;
   const char kHello[] = "hello";
   char buffer[200] = {0};
   uint32_t num_bytes;
@@ -54,40 +53,15 @@
   EXPECT_NE(MOJO_RESULT_OK, MojoClose(handle0));
 
   EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-            MojoWait(handle0, ~MOJO_HANDLE_SIGNAL_NONE,
-                     MOJO_DEADLINE_INDEFINITE, NULL));
+            MojoQueryHandleSignalsState(handle0, NULL));
 
   handle1 = MOJO_HANDLE_INVALID;
   EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(NULL, &handle0, &handle1));
 
-  signals = MOJO_HANDLE_SIGNAL_READABLE;
-  uint32_t result_index = 123;
-  struct MojoHandleSignalsState states[1];
-  EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
-            MojoWaitMany(&handle0, &signals, 1, 1, &result_index, states));
-
-  // "Deadline exceeded" doesn't apply to a single handle, so this should leave
-  // |result_index| untouched.
-  EXPECT_EQ(123u, result_index);
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[0].satisfied_signals);
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
-                MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-            states[0].satisfiable_signals);
-
   EXPECT_EQ(MOJO_RESULT_OK,
             MojoWriteMessage(handle0, kHello, (uint32_t)sizeof(kHello), NULL,
                              0u, MOJO_WRITE_DATA_FLAG_NONE));
 
-  struct MojoHandleSignalsState state;
-  EXPECT_EQ(MOJO_RESULT_OK, MojoWait(handle1, MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &state));
-
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
-            state.satisfied_signals);
-  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
-                MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-            state.satisfiable_signals);
-
   num_bytes = (uint32_t)sizeof(buffer);
   EXPECT_EQ(MOJO_RESULT_OK, MojoReadMessage(handle1, buffer, &num_bytes, NULL,
                                             NULL, MOJO_READ_MESSAGE_FLAG_NONE));
diff --git a/mojo/public/c/system/thunks.cc b/mojo/public/c/system/thunks.cc
index d6bfd95..67c568f 100644
--- a/mojo/public/c/system/thunks.cc
+++ b/mojo/public/c/system/thunks.cc
@@ -22,23 +22,11 @@
   return g_thunks.Close(handle);
 }
 
-MojoResult MojoWait(MojoHandle handle,
-                    MojoHandleSignals signals,
-                    MojoDeadline deadline,
-                    struct MojoHandleSignalsState* signals_state) {
-  assert(g_thunks.Wait);
-  return g_thunks.Wait(handle, signals, deadline, signals_state);
-}
-
-MojoResult MojoWaitMany(const MojoHandle* handles,
-                        const MojoHandleSignals* signals,
-                        uint32_t num_handles,
-                        MojoDeadline deadline,
-                        uint32_t* result_index,
-                        struct MojoHandleSignalsState* signals_states) {
-  assert(g_thunks.WaitMany);
-  return g_thunks.WaitMany(handles, signals, num_handles, deadline,
-                           result_index, signals_states);
+MojoResult MojoQueryHandleSignalsState(
+    MojoHandle handle,
+    struct MojoHandleSignalsState* signals_state) {
+  assert(g_thunks.QueryHandleSignalsState);
+  return g_thunks.QueryHandleSignalsState(handle, signals_state);
 }
 
 MojoResult MojoCreateMessagePipe(const MojoCreateMessagePipeOptions* options,
@@ -158,44 +146,33 @@
   return g_thunks.UnmapBuffer(buffer);
 }
 
-MojoResult MojoCreateWaitSet(MojoHandle* wait_set) {
-  assert(g_thunks.CreateWaitSet);
-  return g_thunks.CreateWaitSet(wait_set);
+MojoResult MojoCreateWatcher(MojoWatcherCallback callback,
+                             MojoHandle* watcher_handle) {
+  assert(g_thunks.CreateWatcher);
+  return g_thunks.CreateWatcher(callback, watcher_handle);
 }
 
-MojoResult MojoAddHandle(MojoHandle wait_set,
-                         MojoHandle handle,
-                         MojoHandleSignals signals) {
-  assert(g_thunks.AddHandle);
-  return g_thunks.AddHandle(wait_set, handle, signals);
-}
-
-MojoResult MojoRemoveHandle(MojoHandle wait_set, MojoHandle handle) {
-  assert(g_thunks.RemoveHandle);
-  return g_thunks.RemoveHandle(wait_set, handle);
-}
-
-MojoResult MojoGetReadyHandles(MojoHandle wait_set,
-                               uint32_t* count,
-                               MojoHandle* handles,
-                               MojoResult* results,
-                               struct MojoHandleSignalsState* signals_states) {
-  assert(g_thunks.GetReadyHandles);
-  return g_thunks.GetReadyHandles(wait_set, count, handles, results,
-                                  signals_states);
-}
-
-MojoResult MojoWatch(MojoHandle handle,
+MojoResult MojoWatch(MojoHandle watcher_handle,
+                     MojoHandle handle,
                      MojoHandleSignals signals,
-                     MojoWatchCallback callback,
                      uintptr_t context) {
   assert(g_thunks.Watch);
-  return g_thunks.Watch(handle, signals, callback, context);
+  return g_thunks.Watch(watcher_handle, handle, signals, context);
 }
 
-MojoResult MojoCancelWatch(MojoHandle handle, uintptr_t context) {
+MojoResult MojoCancelWatch(MojoHandle watcher_handle, uintptr_t context) {
   assert(g_thunks.CancelWatch);
-  return g_thunks.CancelWatch(handle, context);
+  return g_thunks.CancelWatch(watcher_handle, context);
+}
+
+MojoResult MojoArmWatcher(MojoHandle watcher_handle,
+                          uint32_t* num_ready_contexts,
+                          uintptr_t* ready_contexts,
+                          MojoResult* ready_results,
+                          MojoHandleSignalsState* ready_signals_states) {
+  assert(g_thunks.ArmWatcher);
+  return g_thunks.ArmWatcher(watcher_handle, num_ready_contexts, ready_contexts,
+                             ready_results, ready_signals_states);
 }
 
 MojoResult MojoFuseMessagePipes(MojoHandle handle0, MojoHandle handle1) {
diff --git a/mojo/public/c/system/thunks.h b/mojo/public/c/system/thunks.h
index 161faf1..e61bb46 100644
--- a/mojo/public/c/system/thunks.h
+++ b/mojo/public/c/system/thunks.h
@@ -13,43 +13,18 @@
 #include "mojo/public/c/system/core.h"
 #include "mojo/public/c/system/system_export.h"
 
-// The embedder needs to bind the basic Mojo Core functions of a DSO to those of
-// the embedder when loading a DSO that is dependent on mojo_system.
-// The typical usage would look like:
-// base::ScopedNativeLibrary app_library(
-//     base::LoadNativeLibrary(app_path_, &error));
-// typedef MojoResult (*MojoSetSystemThunksFn)(MojoSystemThunks*);
-// MojoSetSystemThunksFn mojo_set_system_thunks_fn =
-//     reinterpret_cast<MojoSetSystemThunksFn>(app_library.GetFunctionPointer(
-//         "MojoSetSystemThunks"));
-// MojoSystemThunks system_thunks = MojoMakeSystemThunks();
-// size_t expected_size = mojo_set_system_thunks_fn(&system_thunks);
-// if (expected_size > sizeof(MojoSystemThunks)) {
-//   LOG(ERROR)
-//       << "Invalid DSO. Expected MojoSystemThunks size: "
-//       << expected_size;
-//   break;
-// }
-
-// Structure used to bind the basic Mojo Core functions of a DSO to those of
-// the embedder.
-// This is the ABI between the embedder and the DSO. It can only have new
-// functions added to the end. No other changes are supported.
+// Structure used to bind the basic Mojo Core functions to an embedder
+// implementation. This is intended to eventually be used as a stable ABI
+// between a Mojo embedder and some loaded application code, but for now it is
+// still effectively safe to rearrange entries as needed.
 #pragma pack(push, 8)
 struct MojoSystemThunks {
   size_t size;  // Should be set to sizeof(MojoSystemThunks).
   MojoTimeTicks (*GetTimeTicksNow)();
   MojoResult (*Close)(MojoHandle handle);
-  MojoResult (*Wait)(MojoHandle handle,
-                     MojoHandleSignals signals,
-                     MojoDeadline deadline,
-                     struct MojoHandleSignalsState* signals_state);
-  MojoResult (*WaitMany)(const MojoHandle* handles,
-                         const MojoHandleSignals* signals,
-                         uint32_t num_handles,
-                         MojoDeadline deadline,
-                         uint32_t* result_index,
-                         struct MojoHandleSignalsState* signals_states);
+  MojoResult (*QueryHandleSignalsState)(
+      MojoHandle handle,
+      struct MojoHandleSignalsState* signals_state);
   MojoResult (*CreateMessagePipe)(
       const struct MojoCreateMessagePipeOptions* options,
       MojoHandle* message_pipe_handle0,
@@ -103,23 +78,18 @@
                           void** buffer,
                           MojoMapBufferFlags flags);
   MojoResult (*UnmapBuffer)(void* buffer);
-
-  MojoResult (*CreateWaitSet)(MojoHandle* wait_set);
-  MojoResult (*AddHandle)(MojoHandle wait_set,
-                          MojoHandle handle,
-                          MojoHandleSignals signals);
-  MojoResult (*RemoveHandle)(MojoHandle wait_set,
-                             MojoHandle handle);
-  MojoResult (*GetReadyHandles)(MojoHandle wait_set,
-                                uint32_t* count,
-                                MojoHandle* handles,
-                                MojoResult* results,
-                                struct MojoHandleSignalsState* signals_states);
-  MojoResult (*Watch)(MojoHandle handle,
+  MojoResult (*CreateWatcher)(MojoWatcherCallback callback,
+                              MojoHandle* watcher_handle);
+  MojoResult (*Watch)(MojoHandle watcher_handle,
+                      MojoHandle handle,
                       MojoHandleSignals signals,
-                      MojoWatchCallback callback,
                       uintptr_t context);
-  MojoResult (*CancelWatch)(MojoHandle handle, uintptr_t context);
+  MojoResult (*CancelWatch)(MojoHandle watcher_handle, uintptr_t context);
+  MojoResult (*ArmWatcher)(MojoHandle watcher_handle,
+                           uint32_t* num_ready_contexts,
+                           uintptr_t* ready_contexts,
+                           MojoResult* ready_results,
+                           MojoHandleSignalsState* ready_signals_states);
   MojoResult (*FuseMessagePipes)(MojoHandle handle0, MojoHandle handle1);
   MojoResult (*WriteMessageNew)(MojoHandle message_pipe_handle,
                                 MojoMessageHandle message,
diff --git a/mojo/public/c/system/types.h b/mojo/public/c/system/types.h
index 7e02eeb..15813b6 100644
--- a/mojo/public/c/system/types.h
+++ b/mojo/public/c/system/types.h
@@ -14,9 +14,6 @@
 
 #include "mojo/public/c/system/macros.h"
 
-// TODO(vtl): Notes: Use of undefined flags will lead to undefined behavior
-// (typically they'll be ignored), not necessarily an error.
-
 // |MojoTimeTicks|: A time delta, in microseconds, the meaning of which is
 // source-dependent.
 
@@ -80,12 +77,10 @@
 //       the resource being invalidated.
 //   |MOJO_RESULT_SHOULD_WAIT| - The request cannot currently be completed
 //       (e.g., if the data requested is not yet available). The caller should
-//       wait for it to be feasible using |MojoWait()| or |MojoWaitMany()|.
+//       wait for it to be feasible using a watcher.
 //
 // The codes from |MOJO_RESULT_OK| to |MOJO_RESULT_DATA_LOSS| come from
 // Google3's canonical error codes.
-//
-// TODO(vtl): Add a |MOJO_RESULT_UNSATISFIABLE|?
 
 typedef uint32_t MojoResult;
 
@@ -141,11 +136,11 @@
 #define MOJO_DEADLINE_INDEFINITE ((MojoDeadline) - 1)
 #endif
 
-// |MojoHandleSignals|: Used to specify signals that can be waited on for a
+// |MojoHandleSignals|: Used to specify signals that can be watched for on a
 // handle (and which can be triggered), e.g., the ability to read or write to
 // the handle.
-//   |MOJO_HANDLE_SIGNAL_NONE| - No flags. |MojoWait()|, etc. will return
-//       |MOJO_RESULT_FAILED_PRECONDITION| if you attempt to wait on this.
+//   |MOJO_HANDLE_SIGNAL_NONE| - No flags. A registered watch will always fail
+//       to arm with |MOJO_RESULT_FAILED_PRECONDITION| when watching for this.
 //   |MOJO_HANDLE_SIGNAL_READABLE| - Can read (e.g., a message) from the handle.
 //   |MOJO_HANDLE_SIGNAL_WRITABLE| - Can write (e.g., a message) to the handle.
 //   |MOJO_HANDLE_SIGNAL_PEER_CLOSED| - The peer handle is closed.
@@ -171,8 +166,9 @@
 #define MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE ((MojoHandleSignals)1 << 3);
 #endif
 
-// |MojoHandleSignalsState|: Returned by wait functions to indicate the
-// signaling state of handles. Members are as follows:
+// |MojoHandleSignalsState|: Returned by watch notification callbacks and
+// |MojoQueryHandleSignalsState| functions to indicate the signaling state of
+// handles. Members are as follows:
 //   - |satisfied signals|: Bitmask of signals that were satisfied at some time
 //         before the call returned.
 //   - |satisfiable signals|: These are the signals that are possible to
@@ -189,24 +185,25 @@
 MOJO_STATIC_ASSERT(sizeof(MojoHandleSignalsState) == 8,
                    "MojoHandleSignalsState has wrong size");
 
-// |MojoWatchNotificationFlags|: Passed to a callback invoked as a result of
-// signals being raised on a handle watched by |MojoWatch()|. May take the
-// following values:
-//   |MOJO_WATCH_NOTIFICATION_FLAG_FROM_SYSTEM| - The callback is being invoked
-//       as a result of a system-level event rather than a direct API call from
-//       user code. This may be used as an indication that user code is safe to
-//       call without fear of reentry.
+// |MojoWatcherNotificationFlags|: Passed to a callback invoked by a watcher
+// when some observed signals are raised or a watched handle is closed. May take
+// on any combination of the following values:
+//
+//   |MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM| - The callback is being
+//       invoked as a result of a system-level event rather than a direct API
+//       call from user code. This may be used as an indication that user code
+//       is safe to call without fear of reentry.
 
-typedef uint32_t MojoWatchNotificationFlags;
+typedef uint32_t MojoWatcherNotificationFlags;
 
 #ifdef __cplusplus
-const MojoWatchNotificationFlags MOJO_WATCH_NOTIFICATION_FLAG_NONE = 0;
-const MojoWatchNotificationFlags MOJO_WATCH_NOTIFICATION_FLAG_FROM_SYSTEM =
+const MojoWatcherNotificationFlags MOJO_WATCHER_NOTIFICATION_FLAG_NONE = 0;
+const MojoWatcherNotificationFlags MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM =
     1 << 0;
 #else
-#define MOJO_WATCH_NOTIFICATION_FLAG_NONE ((MojoWatchNotificationFlags)0)
-#define MOJO_WATCH_NOTIFICATION_FLAG_FROM_SYSTEM \
-    ((MojoWatchNotificationFlags)1 << 0);
+#define MOJO_WATCHER_NOTIFICATION_FLAG_NONE ((MojoWatcherNotificationFlags)0)
+#define MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM \
+  ((MojoWatcherNotificationFlags)1 << 0);
 #endif
 
 // |MojoPropertyType|: Property types that can be passed to |MojoGetProperty()|
diff --git a/mojo/public/c/system/wait_set.h b/mojo/public/c/system/wait_set.h
deleted file mode 100644
index 3a127f5..0000000
--- a/mojo/public/c/system/wait_set.h
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2015 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.
-
-// This file contains types/constants and functions specific to wait sets.
-//
-// Note: This header should be compilable as C.
-
-#ifndef MOJO_PUBLIC_C_SYSTEM_WAIT_SET_H_
-#define MOJO_PUBLIC_C_SYSTEM_WAIT_SET_H_
-
-#include <stdint.h>
-
-#include "mojo/public/c/system/system_export.h"
-#include "mojo/public/c/system/types.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-// Note: See the comment in functions.h about the meaning of the "optional"
-// label for pointer parameters.
-
-// Creates a wait set. A wait set is a way to efficiently wait on multiple
-// handles.
-//
-// On success, |*wait_set_handle| will contain a handle to a wait set.
-//
-// Returns:
-//   |MOJO_RESULT_OK| on success.
-//   |MOJO_RESULT_INVALID_ARGUMENT| if |wait_set_handle| is null.
-//   |MOJO_RESULT_RESOURCE_EXHAUSTED| if a process/system/quota/etc. limit has
-//       been reached.
-MOJO_SYSTEM_EXPORT MojoResult MojoCreateWaitSet(
-    MojoHandle* wait_set_handle);  // Out.
-
-// Adds a wait on |handle| to |wait_set_handle|.
-//
-// A handle can only be added to any given wait set once, but may be added to
-// any number of different wait sets. To modify the signals being waited for,
-// the handle must first be removed, and then added with the new signals.
-//
-// If a handle is closed while still in the wait set, it is implicitly removed
-// from the set after being returned from |MojoGetReadyHandles()| with the
-// result |MOJO_RESULT_CANCELLED|.
-//
-// It is safe to add a handle to a wait set while performing a wait on another
-// thread. If the added handle already has its signals satisfied, the waiting
-// thread will be woken.
-//
-// Returns:
-//   |MOJO_RESULT_OK| if |handle| was successfully added to |wait_set_handle|.
-//   |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle, or
-//       |wait_set_handle| is not a valid wait set.
-//   |MOJO_RESULT_ALREADY_EXISTS| if |handle| already exists in
-//       |wait_set_handle|.
-MOJO_SYSTEM_EXPORT MojoResult MojoAddHandle(
-    MojoHandle wait_set_handle,
-    MojoHandle handle,
-    MojoHandleSignals signals);
-
-// Removes |handle| from |wait_set_handle|.
-//
-// It is safe to remove a handle from a wait set while performing a wait on
-// another thread. If handle has its signals satisfied while it is being
-// removed, the waiting thread may be woken up, but no handle may be available
-// when |MojoGetReadyHandles()| is called.
-//
-// Returns:
-//   |MOJO_RESULT_OK| if |handle| was successfully removed from
-//       |wait_set_handle|.
-//   |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle, or
-//       |wait_set_handle| is not a valid wait set.
-//   |MOJO_RESULT_NOT_FOUND| if |handle| does not exist in |wait_set_handle|.
-MOJO_SYSTEM_EXPORT MojoResult MojoRemoveHandle(
-    MojoHandle wait_set_handle,
-    MojoHandle handle);
-
-// Retrieves a set of ready handles from |wait_set_handle|. A handle is ready if
-// at least of of the following is true:
-//   - The handle's signals are satisfied.
-//   - It becomes known that no signal for the handle will ever be satisfied.
-//   - The handle is closed.
-//
-// A wait set may have ready handles when it satisfies the
-// |MOJO_HANDLE_SIGNAL_READABLE| signal. Since handles may be added and removed
-// from a wait set concurrently, it is possible for a wait set to satisfy
-// |MOJO_HANDLE_SIGNAL_READABLE|, but not have any ready handles when
-// |MojoGetReadyHandles()| is called. These spurious wake-ups must be gracefully
-// handled.
-//
-// |*count| on input, must contain the maximum number of ready handles to be
-// returned. On output, it will contain the number of ready handles returned.
-//
-// |handles| must point to an array of size |*count| of |MojoHandle|. It will be
-// populated with handles that are considered ready. The number of handles
-// returned will be in |*count|.
-//
-// |results| must point to an array of size |*count| of |MojoResult|. It will be
-// populated with the wait result of the corresponding handle in |*handles|.
-// Care should be taken that if a handle is closed on another thread, the handle
-// would be invalid, but the result may not be |MOJO_RESULT_CANCELLED|. See
-// documentation for |MojoWait()| for possible results.
-//
-// |signals_state| (optional) if non-null, must point to an array of size
-// |*count| of |MojoHandleSignalsState|. It will be populated with the signals
-// state of the corresponding handle in |*handles|. See documentation for
-// |MojoHandleSignalsState| for more details about the meaning of each array
-// entry. The array will always be updated for every returned handle.
-//
-// Mojo signals and satisfiability are logically 'level-triggered'. Therefore,
-// if a signal continues to be satisfied and is not removed from the wait set,
-// subsequent calls to |MojoGetReadyHandles()| will return the same handle.
-//
-// If multiple handles have their signals satisfied, the order in which handles
-// are returned is undefined. The same handle, if not removed, may be returned
-// in consecutive calls. Callers must not rely on any fairness and handles
-// could be starved if not acted on.
-//
-// Returns:
-//   |MOJO_RESULT_OK| if ready handles are available.
-//   |MOJO_RESULT_INVALID_ARGUMENT| if |wait_set_handle| is not a valid wait
-//       set, if |*count| is 0, or if either |count|, |handles|, or |results| is
-//       null.
-//   |MOJO_RESULT_SHOULD_WAIT| if there are no ready handles.
-MOJO_SYSTEM_EXPORT MojoResult MojoGetReadyHandles(
-    MojoHandle wait_set_handle,
-    uint32_t* count,                                 // In/out.
-    MojoHandle* handles,                             // Out.
-    MojoResult* results,                             // Out.
-    struct MojoHandleSignalsState *signals_states);  // Optional out.
-
-#ifdef __cplusplus
-}  // extern "C"
-#endif
-
-#endif  // MOJO_PUBLIC_C_SYSTEM_WAIT_SET_H_
diff --git a/mojo/public/c/system/watcher.h b/mojo/public/c/system/watcher.h
new file mode 100644
index 0000000..e32856b
--- /dev/null
+++ b/mojo/public/c/system/watcher.h
@@ -0,0 +1,184 @@
+// Copyright 2017 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 MOJO_PUBLIC_C_SYSTEM_WATCHER_H_
+#define MOJO_PUBLIC_C_SYSTEM_WATCHER_H_
+
+#include <stdint.h>
+
+#include "mojo/public/c/system/system_export.h"
+#include "mojo/public/c/system/types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// A callback used to notify watchers about events on their watched handles.
+//
+// See documentation for |MojoWatcherNotificationFlags| for details regarding
+// the possible values of |flags|.
+//
+// See documentation for |MojoWatch()| for details regarding the other arguments
+// this callback receives when called.
+typedef void (*MojoWatcherCallback)(uintptr_t context,
+                                    MojoResult result,
+                                    struct MojoHandleSignalsState signals_state,
+                                    MojoWatcherNotificationFlags flags);
+
+// Creates a new watcher.
+//
+// Watchers are used to trigger arbitrary code execution when one or more
+// handles change state to meet certain conditions.
+//
+// A newly registered watcher is initially disarmed and may be armed using
+// |MojoArmWatcher()|. A watcher is also always disarmed immediately before any
+// invocation of one or more notification callbacks in response to a single
+// handle's state changing in some relevant way.
+//
+// Parameters:
+//   |callback|: The |MojoWatcherCallback| to invoke any time the watcher is
+//       notified of an event. See |MojoWatch()| for details regarding arguments
+//       passed to the callback. Note that this may be called from any arbitrary
+//       thread.
+//   |watcher_handle|: The address at which to store the MojoHandle
+//       corresponding to the new watcher if successfully created.
+//
+// Returns:
+//   |MOJO_RESULT_OK| if the watcher has been successfully created.
+//   |MOJO_RESULT_RESOURCE_EXHAUSTED| if a handle could not be allocated for
+//       this watcher.
+MOJO_SYSTEM_EXPORT MojoResult MojoCreateWatcher(MojoWatcherCallback callback,
+                                                MojoHandle* watcher_handle);
+
+// Adds a watch to a watcher. This allows the watcher to fire notifications
+// regarding state changes on the handle corresponding to the arguments given.
+//
+// Note that notifications for a given watch context are guaranteed to be
+// mutually exclusive in execution: the callback will never be entered for a
+// given context while another invocation of the callback is still executing for
+// the same context. As a result it is generally a good idea to ensure that
+// callbacks do as little work as necessary in order to process the
+// notification.
+//
+// Parameters:
+//   |watcher_handle|: The watcher to which |handle| is to be added.
+//   |handle|: The handle to add to the watcher.
+//   |signals|: The signals to watch for on |handle|.
+//   |context|: An arbitrary context value given to any invocation of the
+//       watcher's callback when invoked as a result of some state change
+//       relevant to this combination of |handle| and |signals|. Must be
+//       unique within any given watcher.
+//
+// Callback parameters (see |MojoWatcherNotificationCallback| above):
+//   When the watcher invokes its callback as a result of some notification
+//   relevant to this watch operation, |context| receives the value given here
+//   and |signals_state| receives the last known signals state of this handle.
+//
+//   |result| is one of the following:
+//     |MOJO_RESULT_OK| if at least one of the watched signals is satisfied. The
+//         watcher must be armed for this notification to fire.
+//     |MOJO_RESULT_FAILED_PRECONDITION| if all of the watched signals are
+//         permanently unsatisfiable. The watcher must be armed for this
+//         notification to fire.
+//     |MOJO_RESULT_CANCELLED| if the watch has been cancelled. The may occur if
+//         the watcher has been closed, the watched handle has been closed, or
+//         the watch for |context| has been explicitly cancelled. This is always
+//         the last result received for any given context, and it is guaranteed
+//         to be received exactly once per watch, regardless of how the watch
+//         was cancelled.
+//
+// Returns:
+//   |MOJO_RESULT_OK| if the handle is now being watched by the watcher.
+//   |MOJO_RESULT_INVALID_ARGUMENT| if |watcher_handle| is not a watcher handle,
+//       |handle| is not a valid message pipe or data pipe handle.
+//   |MOJO_RESULT_ALREADY_EXISTS| if the watcher already has a watch registered
+//       for the given value of |context| or for the given |handle|.
+MOJO_SYSTEM_EXPORT MojoResult MojoWatch(MojoHandle watcher_handle,
+                                        MojoHandle handle,
+                                        MojoHandleSignals signals,
+                                        uintptr_t context);
+
+// Removes a watch from a watcher.
+//
+// This ensures that the watch is cancelled as soon as possible. Cancellation
+// may be deferred (or may even block) an aritrarily long time if the watch is
+// already dispatching one or more notifications.
+//
+// When cancellation is complete, the watcher's callback is invoked one final
+// time for |context|, with the result |MOJO_RESULT_CANCELLED|.
+//
+// The same behavior can be elicted by either closing the watched handle
+// associated with this context, or by closing |watcher_handle| itself. In the
+// lastter case, all registered contexts on the watcher are implicitly cancelled
+// in a similar fashion.
+//
+// Parameters:
+//   |watcher_handle|: The handle of the watcher from which to remove a watch.
+//   |context|: The context of the watch to be removed.
+//
+// Returns:
+//   |MOJO_RESULT_OK| if the watch has been cancelled.
+//   |MOJO_RESULT_INVALID_ARGUMENT| if |watcher_handle| is not a watcher handle.
+//   |MOJO_RESULT_NOT_FOUND| if there is no watch registered on this watcher for
+//       the given value of |context|.
+MOJO_SYSTEM_EXPORT MojoResult MojoCancelWatch(MojoHandle watcher_handle,
+                                              uintptr_t context);
+
+// Arms a watcher, enabling a single future event on one of the watched handles
+// to trigger a single notification for each relevant watch context associated
+// with that handle.
+//
+// Parameters:
+//   |watcher_handle|: The handle of the watcher.
+//   |num_ready_contexts|: An address pointing to the number of elements
+//       available for storage in the remaining output buffers. Optional and
+//       only used on failure. See |MOJO_RESULT_FAILED_PRECONDITION| below for
+//       more details.
+//   |ready_contexts|: An output buffer for contexts corresponding to the
+//       watches which would have notified if the watcher were armed. Optional
+//       and only uesd on failure. See |MOJO_RESULT_FAILED_PRECONDITION| below
+//       for more details.
+//   |ready_results|: An output buffer for MojoResult values corresponding to
+//       each context in |ready_contexts|. Optional and only used on failure.
+//       See |MOJO_RESULT_FAILED_PRECONDITION| below for more details.
+//   |ready_signals_states|: An output buffer for |MojoHandleSignalsState|
+//       structures corresponding to each context in |ready_contexts|. Optional
+//       and only used on failure. See |MOJO_RESULT_FAILED_PRECONDITION| below
+//       for more details.
+//
+// Returns:
+//   |MOJO_RESULT_OK| if the watcher has been successfully armed. All arguments
+//       other than |watcher_handle| are ignored in this case.
+//   |MOJO_RESULT_NOT_FOUND| if the watcher does not have any registered watch
+//       contexts. All arguments other than |watcher_handle| are ignored in this
+//       case.
+//   |MOJO_RESULT_INVALID_ARGUMENT| if |watcher_handle| is not a valid watcher
+//       handle, or if |num_ready_contexts| is non-null but any of the output
+//       buffer paramters is null.
+//   |MOJO_RESULT_FAILED_PRECONDITION| if one or more watches would have
+//       notified immediately upon arming the watcher. If |num_handles| is
+//       non-null, this assumes there is enough space for |*num_handles| entries
+//       in each of the subsequent output buffer arguments.
+//
+//       At most that many entries are placed in the output buffers,
+//       corresponding to the watches which would have signalled if the watcher
+//       had been armed successfully. The actual number of entries placed in the
+//       output buffers is written to |*num_ready_contexts| before returning.
+//
+//       If more than (input) |*num_ready_contexts| watch contexts were ready to
+//       notify, the subset presented in output buffers is arbitrary, but the
+//       implementation makes a best effort to circulate the outputs across
+//       consecutive calls so that callers may reliably avoid handle starvation.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoArmWatcher(MojoHandle watcher_handle,
+               uint32_t* num_ready_contexts,
+               uintptr_t* ready_contexts,
+               MojoResult* ready_results,
+               struct MojoHandleSignalsState* ready_signals_states);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // MOJO_PUBLIC_C_SYSTEM_WATCHER_H_
diff --git a/mojo/public/cpp/README.md b/mojo/public/cpp/README.md
deleted file mode 100644
index d0f1238..0000000
--- a/mojo/public/cpp/README.md
+++ /dev/null
@@ -1,38 +0,0 @@
-Mojo Public C++ API
-===================
-
-This directory contains C++ language bindings for the Mojo Public API.
-
-A number of subdirectories provide wrappers for the lower-level C APIs (in
-subdirectories of the same name, under mojo/public/c/). Typically, these
-wrappers provide increased convenience and/or type-safety.
-
-Other subdirectories provide support (static) libraries of various sorts. In
-this case, the organization is to have the public interface for the library
-defined in header files in the subdirectory itself and the implementation of the
-library at a lower level, under a lib (sub)subdirectory. A developer should be
-able to substitute their own implementation of any such support library, and
-expect other support libraries, which may depend on that library, to work
-properly.
-
-Bindings
---------
-
-The bindings/ subdirectory contains a support (static) library needed by the
-code generated by the bindings generator tool (in mojo/public/tools/bindings/),
-which translates Mojo IDL (.mojom) files into idiomatic C++ (among other
-languages).
-
-System
-------
-
-The system/ subdirectory contains C++ wrappers (and some additional helpers) of
-the API defined in mojo/public/c/system/, which defines the basic, "core" API,
-especially used to communicate with Mojo services.
-
-Test Support
-------------
-
-The test_support/ subdirectory contains C++ wrappers of the test-only API
-defined in mojo/public/c/test_support/. It is not meant for general use by Mojo
-applications.
diff --git a/mojo/public/cpp/bindings/BUILD.gn b/mojo/public/cpp/bindings/BUILD.gn
index 5c41384..bd87965 100644
--- a/mojo/public/cpp/bindings/BUILD.gn
+++ b/mojo/public/cpp/bindings/BUILD.gn
@@ -54,6 +54,7 @@
     "lib/associated_binding.cc",
     "lib/associated_group.cc",
     "lib/associated_group_controller.cc",
+    "lib/associated_interface_ptr.cc",
     "lib/associated_interface_ptr_state.h",
     "lib/binding_state.cc",
     "lib/binding_state.h",
@@ -102,6 +103,7 @@
     "lib/string_serialization.h",
     "lib/string_traits_string16.cc",
     "lib/sync_call_restrictions.cc",
+    "lib/sync_event_watcher.cc",
     "lib/sync_handle_registry.cc",
     "lib/sync_handle_watcher.cc",
     "lib/template_util.h",
@@ -137,6 +139,7 @@
     "strong_binding_set.h",
     "struct_ptr.h",
     "sync_call_restrictions.h",
+    "sync_event_watcher.h",
     "sync_handle_registry.h",
     "sync_handle_watcher.h",
     "thread_safe_interface_ptr.h",
diff --git a/mojo/public/cpp/bindings/README.md b/mojo/public/cpp/bindings/README.md
new file mode 100644
index 0000000..b37267a
--- /dev/null
+++ b/mojo/public/cpp/bindings/README.md
@@ -0,0 +1,1231 @@
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C++ Bindings API
+This document is a subset of the [Mojo documentation](/mojo).
+
+[TOC]
+
+## Overview
+The Mojo C++ Bindings API leverages the
+[C++ System API](/mojo/public/cpp/system) to provide a more natural set of
+primitives for communicating over Mojo message pipes. Combined with generated
+code from the [Mojom IDL and bindings generator](/mojo/public/tools/bindings),
+users can easily connect interface clients and implementations across arbitrary
+intra- and inter-process bounaries.
+
+This document provides a detailed guide to bindings API usage with example code
+snippets. For a detailed API references please consult the headers in
+[//mojo/public/cpp/bindings](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/).
+
+## Getting Started
+
+When a Mojom IDL file is processed by the bindings generator, C++ code is
+emitted in a series of `.h` and `.cc` files with names based on the input
+`.mojom` file. Suppose we create the following Mojom file at
+`//services/db/public/interfaces/db.mojom`:
+
+```
+module db.mojom;
+
+interface Table {
+  AddRow(int32 key, string data);
+};
+
+interface Database {
+  CreateTable(Table& table);
+};
+```
+
+And a GN target to generate the bindings in
+`//services/db/public/interfaces/BUILD.gn`:
+
+```
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("interfaces") {
+  sources = [
+    "db.mojom",
+  ]
+}
+```
+
+If we then build this target:
+
+```
+ninja -C out/r services/db/public/interfaces
+```
+
+This will produce several generated source files, some of which are relevant to
+C++ bindings. Two of these files are:
+
+```
+out/gen/services/business/public/interfaces/factory.mojom.cc
+out/gen/services/business/public/interfaces/factory.mojom.h
+```
+
+You can include the above generated header in your sources in order to use the
+definitions therein:
+
+``` cpp
+#include "services/business/public/interfaces/factory.mojom.h"
+
+class TableImpl : public db::mojom::Table {
+  // ...
+};
+```
+
+This document covers the different kinds of definitions generated by Mojom IDL
+for C++ consumers and how they can effectively be used to communicate across
+message pipes.
+
+*** note
+**NOTE:** Using C++ bindings from within Blink code is typically subject to
+special constraints which require the use of a different generated header.
+For details, see [Blink Type Mapping](#Blink-Type-Mapping).
+***
+
+## Interfaces
+
+Mojom IDL interfaces are translated to corresponding C++ (pure virtual) class
+interface definitions in the generated header, consisting of a single generated
+method signature for each request message on the interface. Internally there is
+also generated code for serialization and deserialization of messages, but this
+detail is hidden from bindings consumers.
+
+### Basic Usage
+
+Let's consider a new `//sample/logger.mojom` to define a simple logging
+interface which clients can use to log simple string messages:
+
+``` cpp
+module sample.mojom;
+
+interface Logger {
+  Log(string message);
+};
+```
+
+Running this through the bindings generator will produce a `logging.mojom.h`
+with the following definitions (modulo unimportant details):
+
+``` cpp
+namespace sample {
+namespace mojom {
+
+class Logger {
+  virtual ~Logger() {}
+
+  virtual void Log(const std::string& message) = 0;
+};
+
+using LoggerPtr = mojo::InterfacePtr<Logger>;
+using LoggerRequest = mojo::InterfaceRequest<Logger>;
+
+}  // namespace mojom
+}  // namespace sample
+```
+
+Makes sense. Let's take a closer look at those type aliases at the end.
+
+### InterfacePtr and InterfaceRequest
+
+You will notice the type aliases for `LoggerPtr` and
+`LoggerRequest` are using two of the most fundamental template types in the C++
+bindings library: **`InterfacePtr<T>`** and **`InterfaceRequest<T>`**.
+
+In the world of Mojo bindings libraries these are effectively strongly-typed
+message pipe endpoints. If an `InterfacePtr<T>` is bound to a message pipe
+endpoint, it can be dereferenced to make calls on an opaque `T` interface. These
+calls immediately serialize their arguments (using generated code) and write a
+corresponding message to the pipe.
+
+An `InterfaceRequest<T>` is essentially just a typed container to hold the other
+end of an `InterfacePtr<T>`'s pipe -- the receiving end -- until it can be
+routed to some implementation which will **bind** it. The `InterfaceRequest<T>`
+doesn't actually *do* anything other than hold onto a pipe endpoint and carry
+useful compile-time type information.
+
+![Diagram illustrating InterfacePtr and InterfaceRequest on either end of a message pipe](https://docs.google.com/drawings/d/17d5gvErbQ6DthEBMS7I1WhCh9bz0n12pvNjydzuRfTI/pub?w=600&h=100)
+
+So how do we create a strongly-typed message pipe?
+
+### Creating Interface Pipes
+
+One way to do this is by manually creating a pipe and binding each end:
+
+``` cpp
+#include "sample/logger.mojom.h"
+
+mojo::MessagePipe pipe;
+sample::mojom::LoggerPtr logger;
+sample::mojom::LoggerRequest request;
+
+logger.Bind(sample::mojom::LoggerPtrInfo(std::move(pipe.handle0), 0u));
+request.Bind(std::move(pipe.handle1));
+```
+
+That's pretty verbose, but the C++ Bindings library provides more convenient
+ways to accomplish the same thing. [interface_request.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/interface_request.h)
+defines a `MakeRequest` function:
+
+``` cpp
+sample::mojom::LoggerPtr logger;
+sample::mojom::LoggerRequest request = mojo::MakeRequest(&logger);
+```
+
+and the `InterfaceRequest<T>` constructor can also take an explicit
+`InterfacePtr<T>*` output argument:
+
+``` cpp
+sample::mojom::LoggerPtr logger;
+sample::mojom::LoggerRequest request(&logger);
+```
+
+Both of these last two snippets are equivalent to the first one.
+
+*** note
+**NOTE:** In the first example above you may notice usage of the `LoggerPtrInfo`
+type, which is a generated alias for `mojo::InterfacePtrInfo<Logger>`. This is
+similar to an `InterfaceRequest<T>` in that it merely holds onto a pipe handle
+and cannot actually read or write messages on the pipe. Both this type and
+`InterfaceRequest<T>` are safe to move freely from thread to thread, whereas a
+bound `InterfacePtr<T>` is bound to a single thread.
+
+An `InterfacePtr<T>` may be unbound by calling its `PassInterface()` method,
+which returns a new `InterfacePtrInfo<T>`. Conversely, an `InterfacePtr<T>` may
+bind (and thus take ownership of) an `InterfacePtrInfo<T>` so that interface
+calls can be made on the pipe.
+
+The thread-bound nature of `InterfacePtr<T>` is necessary to support safe
+dispatch of its [message responses](#Receiving-Responses) and
+[connection error notifications](#Connection-Errors).
+***
+
+Once the `LoggerPtr` is bound we can immediately begin calling `Logger`
+interface methods on it, which will immediately write messages into the pipe.
+These messages will stay queued on the receiving end of the pipe until someone
+binds to it and starts reading them.
+
+``` cpp
+logger->Log("Hello!");
+```
+
+This actually writes a `Log` message to the pipe.
+
+![Diagram illustrating a message traveling on a pipe from LoggerPtr to LoggerRequest](https://docs.google.com/a/google.com/drawings/d/1jWEc6jJIP2ed77Gg4JJ3EVC7hvnwcImNqQJywFwpT8g/pub?w=648&h=123)
+
+But as mentioned above, `InterfaceRequest` *doesn't actually do anything*, so
+that message will just sit on the pipe forever. We need a way to read messages
+off the other end of the pipe and dispatch them. We have to
+**bind the interface request**.
+
+### Binding an Interface Request
+
+There are many different helper classes in the bindings library for binding the
+receiving end of a message pipe. The most primitive among them is the aptly
+named `mojo::Binding<T>`. A `mojo::Binding<T>` bridges an implementation of `T`
+with a single bound message pipe endpoint (via a `mojo::InterfaceRequest<T>`),
+which it continuously watches for readability.
+
+Any time the bound pipe becomes readable, the `Binding` will schedule a task to
+read, deserialize (using generated code), and dispatch all available messages to
+the bound `T` implementation. Below is a sample implementation of the `Logger`
+interface. Notice that the implementation itself owns a `mojo::Binding`. This is
+a common pattern, since a bound implementation must outlive any `mojo::Binding`
+which binds it.
+
+``` cpp
+#include "base/logging.h"
+#include "base/macros.h"
+#include "sample/logger.mojom.h"
+
+class LoggerImpl : public sample::mojom::Logger {
+ public:
+  // NOTE: A common pattern for interface implementations which have one
+  // instance per client is to take an InterfaceRequest in the constructor.
+
+  explicit LoggerImpl(sample::mojom::LoggerRequest request)
+      : binding_(this, std::move(request)) {}
+  ~Logger() override {}
+
+  // sample::mojom::Logger:
+  void Log(const std::string& message) override {
+    LOG(ERROR) << "[Logger] " << message;
+  }
+
+ private:
+  mojo::Binding<sample::mojom::Logger> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(LoggerImpl);
+};
+```
+
+Now we can construct a `LoggerImpl` over our pending `LoggerRequest`, and the
+previously queued `Log` message will be dispatched ASAP on the `LoggerImpl`'s
+thread:
+
+``` cpp
+LoggerImpl impl(std::move(request));
+```
+
+The diagram below illustrates the following sequence of events, all set in
+motion by the above line of code:
+
+1. The `LoggerImpl` constructor is called, passing the `LoggerRequest` along
+   to the `Binding`.
+2. The `Binding` takes ownership of the `LoggerRequest`'s pipe endpoint and
+   begins watching it for readability. The pipe is readable immediately, so a
+   task is scheduled to read the pending `Log` message from the pipe ASAP.
+3. The `Log` message is read and deserialized, causing the `Binding` to invoke
+   the `Logger::Log` implementation on its bound `LoggerImpl`.
+
+![Diagram illustrating the progression of binding a request, reading a pending message, and dispatching it](https://docs.google.com/drawings/d/1c73-PegT4lmjfHoxhWrHTQXRvzxgb0wdeBa35WBwZ3Q/pub?w=550&h=500)
+
+As a result, our implementation will eventually log the client's `"Hello!"`
+message via `LOG(ERROR)`.
+
+*** note
+**NOTE:** Messages will only be read and dispatched from a pipe as long as the
+object which binds it (*i.e.* the `mojo::Binding` in the above example) remains
+alive.
+***
+
+### Receiving Responses
+
+Some Mojom interface methods expect a response. Suppose we modify our `Logger`
+interface so that the last logged line can be queried like so:
+
+``` cpp
+module sample.mojom;
+
+interface Logger {
+  Log(string message);
+  GetTail() => (string message);
+};
+```
+
+The generated C++ interface will now look like:
+
+``` cpp
+namespace sample {
+namespace mojom {
+
+class Logger {
+ public:
+  virtual ~Logger() {}
+
+  virtual void Log(const std::string& message) = 0;
+
+  using GetTailCallback = base::Callback<void(const std::string& message)>;
+
+  virtual void GetTail(const GetTailCallback& callback) = 0;
+}
+
+}  // namespace mojom
+}  // namespace sample
+```
+
+As before, both clients and implementations of this interface use the same
+signature for the `GetTail` method: implementations use the `callback` argument
+to *respond* to the request, while clients pass a `callback` argument to
+asynchronously `receive` the response. Here's an updated implementation:
+
+```cpp
+class LoggerImpl : public sample::mojom::Logger {
+ public:
+  // NOTE: A common pattern for interface implementations which have one
+  // instance per client is to take an InterfaceRequest in the constructor.
+
+  explicit LoggerImpl(sample::mojom::LoggerRequest request)
+      : binding_(this, std::move(request)) {}
+  ~Logger() override {}
+
+  // sample::mojom::Logger:
+  void Log(const std::string& message) override {
+    LOG(ERROR) << "[Logger] " << message;
+    lines_.push_back(message);
+  }
+
+  void GetTail(const GetTailCallback& callback) override {
+    callback.Run(lines_.back());
+  }
+
+ private:
+  mojo::Binding<sample::mojom::Logger> binding_;
+  std::vector<std::string> lines_;
+
+  DISALLOW_COPY_AND_ASSIGN(LoggerImpl);
+};
+```
+
+And an updated client call:
+
+``` cpp
+void OnGetTail(const std::string& message) {
+  LOG(ERROR) << "Tail was: " << message;
+}
+
+logger->GetTail(base::Bind(&OnGetTail));
+```
+
+Behind the scenes, the implementation-side callback is actually serializing the
+response arguments and writing them onto the pipe for delivery back to the
+client. Meanwhile the client-side callback is invoked by some internal logic
+which watches the pipe for an incoming response message, reads and deserializes
+it once it arrives, and then invokes the callback with the deserialized
+parameters.
+
+### Connection Errors
+
+If there are no remaining messages available on a pipe and the remote end has
+been closed, a connection error will be triggered on the local end. Connection
+errors may also be triggered by automatic forced local pipe closure due to
+*e.g.* a validation error when processing a received message.
+
+Regardless of the underlying cause, when a connection error is encountered on
+a binding endpoint, that endpoint's **connection error handler** (if set) is
+invoked. This handler is a simple `base::Closure` and may only be invoked
+*once* as long as the endpoint is bound to the same pipe. Typically clients and
+implementations use this handler to do some kind of cleanup or -- particuarly if
+the error was unexpected -- create a new pipe and attempt to establish a new
+connection with it.
+
+All message pipe-binding C++ objects (*e.g.*, `mojo::Binding<T>`,
+`mojo::InterfacePtr<T>`, *etc.*) support setting their connection error handler
+via a `set_connection_error_handler` method.
+
+We can set up another end-to-end `Logger` example to demonstrate error handler
+invocation:
+
+``` cpp
+sample::mojom::LoggerPtr logger;
+LoggerImpl impl(mojo::MakeRequest(&logger));
+impl.set_connection_error_handler(base::Bind([] { LOG(ERROR) << "Bye."; }));
+logger->Log("OK cool");
+logger.reset();  // Closes the client end.
+```
+
+As long as `impl` stays alive here, it will eventually receive the `Log` message
+followed immediately by an invocation of the bound callback which outputs
+`"Bye."`. Like all other bindings callbacks, a connection error handler will
+**never** be invoked once its corresponding binding object has been destroyed.
+
+In fact, suppose instead that `LoggerImpl` had set up the following error
+handler within its constructor:
+
+``` cpp
+LoggerImpl::LoggerImpl(sample::mojom::LoggerRequest request)
+    : binding_(this, std::move(request)) {
+  binding_.set_connection_error_handler(
+      base::Bind(&LoggerImpl::OnError, base::Unretained(this)));
+}
+
+void LoggerImpl::OnError() {
+  LOG(ERROR) << "Client disconnected! Purging log lines.";
+  lines_.clear();
+}
+```
+
+The use of `base::Unretained` is *safe* because the error handler will never be
+invoked beyond the lifetime of `binding_`, and `this` owns `binding_`.
+
+### A Note About Ordering
+
+As mentioned in the previous section, closing one end of a pipe will eventually
+trigger a connection error on the other end. However it's important to note that
+this event is itself ordered with respect to any other event (*e.g.* writing a
+message) on the pipe.
+
+This means that it's safe to write something contrived like:
+
+``` cpp
+void GoBindALogger(sample::mojom::LoggerRequest request) {
+  LoggerImpl impl(std::move(request));
+  base::RunLoop loop;
+  impl.set_connection_error_handler(loop.QuitClosure());
+  loop.Run();
+}
+
+void LogSomething() {
+  sample::mojom::LoggerPtr logger;
+  bg_thread->task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&GoBindALogger, mojo::MakeRequest(&logger)));
+  logger->Log("OK Computer");
+}
+```
+
+When `logger` goes out of scope it immediately closes its end of the message
+pipe, but the impl-side won't notice this until it receives the sent `Log`
+message. Thus the `impl` above will first log our message and *then* see a
+connection error and break out of the run loop.
+
+### Sending Interfaces Over Interfaces
+
+Now we know how to create interface pipes and use their Ptr and Request
+endpoints in some interesting ways. This still doesn't add up to interesting
+IPC! The bread and butter of Mojo IPC is the ability to transfer interface
+endpoints across other interfaces, so let's take a look at how to accomplish
+that.
+
+#### Sending Interface Requests
+
+Consider a new example Mojom in `//sample/db.mojom`:
+
+``` cpp
+module db.mojom;
+
+interface Table {
+  void AddRow(int32 key, string data);
+};
+
+interface Database {
+  AddTable(Table& table);
+};
+```
+
+As noted in the
+[Mojom IDL documentation](/mojo/public/tools/bindings#Primitive-Types),
+the `Table&` syntax denotes a `Table` interface request. This corresponds
+precisely to the `InterfaceRequest<T>` type discussed in the sections above, and
+in fact the generated code for these interfaces is approximately:
+
+``` cpp
+namespace db {
+namespace mojom {
+
+class Table {
+ public:
+  virtual ~Table() {}
+
+  virtual void AddRow(int32_t key, const std::string& data) = 0;
+}
+
+using TablePtr = mojo::InterfacePtr<Table>;
+using TableRequest = mojo::InterfaceRequest<Table>;
+
+class Database {
+ public:
+  virtual ~Database() {}
+
+  virtual void AddTable(TableRequest table);
+};
+
+using DatabasePtr = mojo::InterfacePtr<Database>;
+using DatabaseRequest = mojo::InterfaceRequest<Database>;
+
+}  // namespace mojom
+}  // namespace db
+```
+
+We can put this all together now with an implementation of `Table` and
+`Database`:
+
+``` cpp
+#include "sample/db.mojom.h"
+
+class TableImpl : public db::mojom:Table {
+ public:
+  explicit TableImpl(db::mojom::TableRequest request)
+      : binding_(this, std::move(request)) {}
+  ~TableImpl() override {}
+
+  // db::mojom::Table:
+  void AddRow(int32_t key, const std::string& data) override {
+    rows_.insert({key, data});
+  }
+
+ private:
+  mojo::Binding<db::mojom::Table> binding_;
+  std::map<int32_t, std::string> rows_;
+};
+
+class DatabaseImpl : public db::mojom::Database {
+ public:
+  explicit DatabaseImpl(db::mojom::DatabaseRequest request)
+      : binding_(this, std::move(request)) {}
+  ~DatabaseImpl() override {}
+
+  // db::mojom::Database:
+  void AddTable(db::mojom::TableRequest table) {
+    tables_.emplace_back(base::MakeUnique<TableImpl>(std::move(table)));
+  }
+
+ private:
+  mojo::Binding<db::mojom::Database> binding_;
+  std::vector<std::unique_ptr<TableImpl>> tables_;
+};
+```
+
+Pretty straightforward. The `Table&` Mojom paramter to `AddTable` translates to
+a C++ `db::mojom::TableRequest`, aliased from
+`mojo::InterfaceRequest<db::mojom::Table>`, which we know is just a
+strongly-typed message pipe handle. When `DatabaseImpl` gets an `AddTable` call,
+it constructs a new `TableImpl` and binds it to the received `TableRequest`.
+
+Let's see how this can be used.
+
+``` cpp
+db::mojom::DatabasePtr database;
+DatabaseImpl db_impl(mojo::MakeRequest(&database));
+
+db::mojom::TablePtr table1, table2;
+database->AddTable(mojo::MakeRequest(&table1));
+database->AddTable(mojo::MakeRequest(&table2));
+
+table1->AddRow(1, "hiiiiiiii");
+table2->AddRow(2, "heyyyyyy");
+```
+
+Notice that we can again start using the new `Table` pipes immediately, even
+while their `TableRequest` endpoints are still in transit.
+
+#### Sending InterfacePtrs
+
+Of course we can also send `InterfacePtr`s:
+
+``` cpp
+interface TableListener {
+  OnRowAdded(int32 key, string data);
+};
+
+interface Table {
+  AddRow(int32 key, string data);
+
+  AddListener(TableListener listener);
+};
+```
+
+This would generate a `Table::AddListener` signature like so:
+
+``` cpp
+  virtual void AddListener(TableListenerPtr listener) = 0;
+```
+
+and this could be used like so:
+
+``` cpp
+db::mojom::TableListenerPtr listener;
+TableListenerImpl impl(mojo::MakeRequest(&listener));
+table->AddListener(std::move(listener));
+```
+
+## Other Interface Binding Types
+
+The [Interfaces](#Interfaces) section above covers basic usage of the most
+common bindings object types: `InterfacePtr`, `InterfaceRequest`, and `Binding`.
+While these types are probably the most commonly used in practice, there are
+several other ways of binding both client- and implementation-side interface
+pipes.
+
+### Strong Bindings
+
+A **strong binding** exists as a standalone object which owns its interface
+implementation and automatically cleans itself up when its bound interface
+endpoint detects an error. The
+[**`MakeStrongBinding`**](https://cs.chromim.org/chromium/src//mojo/public/cpp/bindings/strong_binding.h)
+function is used to create such a binding.
+.
+
+``` cpp
+class LoggerImpl : public sample::mojom::Logger {
+ public:
+  LoggerImpl() {}
+  ~LoggerImpl() override {}
+
+  // sample::mojom::Logger:
+  void Log(const std::string& message) override {
+    LOG(ERROR) << "[Logger] " << message;
+  }
+
+ private:
+  // NOTE: This doesn't own any Binding object!
+};
+
+db::mojom::LoggerPtr logger;
+mojo::MakeStrongBinding(base::MakeUnique<DatabaseImpl>(),
+                        mojo::MakeRequest(&logger));
+
+logger->Log("NOM NOM NOM MESSAGES");
+```
+
+Now as long as `logger` remains open somewhere in the system, the bound
+`DatabaseImpl` on the other end will remain alive.
+
+### Binding Sets
+
+Sometimes it's useful to share a single implementation instance with multiple
+clients. [**`BindingSet`**](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/binding_set.h)
+makes this easy. Consider the Mojom:
+
+``` cpp
+module system.mojom;
+
+interface Logger {
+  Log(string message);
+};
+
+interface LoggerProvider {
+  GetLogger(Logger& logger);
+};
+```
+
+We can use `BindingSet` to bind multiple `Logger` requests to a single
+implementation instance:
+
+``` cpp
+class LogManager : public system::mojom::LoggerProvider,
+                   public system::mojom::Logger {
+ public:
+  explicit LogManager(system::mojom::LoggerProviderRequest request)
+      : provider_binding_(this, std::move(request)) {}
+  ~LogManager() {}
+
+  // system::mojom::LoggerProvider:
+  void GetLogger(LoggerRequest request) override {
+    logger_bindings_.AddBinding(this, std::move(request));
+  }
+
+  // system::mojom::Logger:
+  void Log(const std::string& message) override {
+    LOG(ERROR) << "[Logger] " << message;
+  }
+
+ private:
+  mojo::Binding<system::mojom::LoggerProvider> provider_binding_;
+  mojo::BindingSet<system::mojom::Logger> logger_bindings_;
+};
+
+```
+
+
+### InterfacePtr Sets
+
+Similar to the `BindingSet` above, sometimes it's useful to maintain a set of
+`InterfacePtr`s for *e.g.* a set of clients observing some event.
+[**`InterfacePtrSet`**](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/interface_ptr_set.h)
+is here to help. Take the Mojom:
+
+``` cpp
+module db.mojom;
+
+interface TableListener {
+  OnRowAdded(int32 key, string data);
+};
+
+interface Table {
+  AddRow(int32 key, string data);
+  AddListener(TableListener listener);
+};
+```
+
+An implementation of `Table` might look something like like this:
+
+``` cpp
+class TableImpl : public db::mojom::Table {
+ public:
+  TableImpl() {}
+  ~TableImpl() override {}
+
+  // db::mojom::Table:
+  void AddRow(int32_t key, const std::string& data) override {
+    rows_.insert({key, data});
+    listeners_.ForEach([key, &data](db::mojom::TableListener* listener) {
+      listener->OnRowAdded(key, data);
+    });
+  }
+
+  void AddListener(db::mojom::TableListenerPtr listener) {
+    listeners_.AddPtr(std::move(listener));
+  }
+
+ private:
+  mojo::InterfacePtrSet<db::mojom::Table> listeners_;
+  std::map<int32_t, std::string> rows_;
+};
+```
+
+## Associated Interfaces
+
+See [this document](https://www.chromium.org/developers/design-documents/mojo/associated-interfaces).
+
+TODO: Move the above doc into the repository markdown docs.
+
+## Synchronous Calls
+
+See [this document](https://www.chromium.org/developers/design-documents/mojo/synchronous-calls)
+
+TODO: Move the above doc into the repository markdown docs.
+
+## Type Mapping
+
+In many instances you might prefer that your generated C++ bindings use a more
+natural type to represent certain Mojom types in your interface methods. For one
+example consider a Mojom struct such as the `Rect` below:
+
+``` cpp
+module gfx.mojom;
+
+struct Rect {
+  int32 x;
+  int32 y;
+  int32 width;
+  int32 height;
+};
+
+interface Canvas {
+  void FillRect(Rect rect);
+};
+```
+
+The `Canvas` Mojom interface would normally generate a C++ interface like:
+
+``` cpp
+class Canvas {
+ public:
+  virtual void FillRect(RectPtr rect) = 0;
+};
+```
+
+However, the Chromium tree already defines a native
+[`gfx::Rect`](https://cs.chromium.org/chromium/src/ui/gfx/geometry/rect.h) which
+is equivalent in meaning but which also has useful helper methods. Instead of
+manually converting between a `gfx::Rect` and the Mojom-generated `RectPtr` at
+every message boundary, wouldn't it be nice if the Mojom bindings generator
+could instead generate:
+
+``` cpp
+class Canvas {
+ public:
+  virtual void FillRect(const gfx::Rect& rect) = 0;
+}
+```
+
+The correct answer is, "Yes! That would be nice!" And fortunately, it can!
+
+### Global Configuration
+
+While this feature is quite powerful, it introduces some unavoidable complexity
+into build system. This stems from the fact that type-mapping is an inherently
+viral concept: if `gfx::mojom::Rect` is mapped to `gfx::Rect` anywhere, the
+mapping needs to apply *everywhere*.
+
+For this reason we have a few global typemap configurations defined in
+[chromium_bindings_configuration.gni](https://cs.chromium.com/chromium/src/mojo/public/tools/bindings/chromium_bindings_configuration.gni)
+and
+[blink_bindings_configuration.gni](https://cs.chromium.com/chromium/src/mojo/public/tools/bindings/blink_bindings_configuration.gni). These configure the two supported [variants](#Variants) of Mojom generated
+bindings in the repository. Read more on this in the sections that follow.
+
+For now, let's take a look at how to express the mapping from `gfx::mojom::Rect`
+to `gfx::Rect`.
+
+### Defining `StructTraits`
+
+In order to teach generated bindings code how to serialize an arbitrary native
+type `T` as an arbitrary Mojom type `mojom::U`, we need to define an appropriate
+specialization of the
+[`mojo::StructTraits`](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/struct_traits.h)
+template.
+
+A valid specialization of `StructTraits` MUST define the following static
+methods:
+
+* A single static accessor for every field of the Mojom struct, with the exact
+  same name as the struct field. These accessors must all take a const ref to
+  an object of the native type, and must return a value compatible with the
+  Mojom struct field's type. This is used to safely and consistently extract
+  data from the native type during message serialization without incurring extra
+  copying costs.
+
+* A single static `Read` method which initializes an instance of the the native
+  type given a serialized representation of the Mojom struct. The `Read` method
+  must return a `bool` to indicate whether the incoming data is accepted
+  (`true`) or rejected (`false`).
+
+There are other methods a `StructTraits` specialization may define to satisfy
+some less common requirements. See
+[Advanced StructTraits Usage](#Advanced-StructTraits-Usage) for details.
+
+In order to define the mapping for `gfx::Rect`, we want the following
+`StructTraits` specialization, which we'll define in
+`//ui/gfx/geometry/mojo/geometry_struct_traits.h`:
+
+``` cpp
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/mojo/geometry.mojom.h"
+
+namespace mojo {
+
+template <>
+class StructTraits<gfx::mojom::RectDataView, gfx::Rect> {
+ public:
+  static int32_t x(const gfx::Rect& r) { return r.x(); }
+  static int32_t y(const gfx::Rect& r) { return r.y(); }
+  static int32_t width(const gfx::Rect& r) { return r.width(); }
+  static int32_t height(const gfx::Rect& r) { return r.height(); }
+
+  static bool Read(gfx::mojom::RectDataView data, gfx::Rect* out_rect);
+};
+
+}  // namespace mojo
+```
+
+And in `//ui/gfx/geometry/mojo/geometry_struct_traits.cc`:
+
+``` cpp
+#include "ui/gfx/geometry/mojo/geometry_struct_traits.h"
+
+namespace mojo {
+
+// static
+template <>
+bool StructTraits<gfx::mojom::RectDataView, gfx::Rect>::Read(
+    gfx::mojom::RectDataView data,
+  gfx::Rect* out_rect) {
+  if (data.width() < 0 || data.height() < 0)
+    return false;
+
+  out_rect->SetRect(data.x(), data.y(), data.width(), data.height());
+  return true;
+};
+
+}  // namespace mojo
+```
+
+Note that the `Read()` method returns `false` if either the incoming `width` or
+`height` fields are negative. This acts as a validation step during
+deserialization: if a client sends a `gfx::Rect` with a negative width or
+height, its message will be rejected and the pipe will be closed. In this way,
+type mapping can serve to enable custom validation logic in addition to making
+callsites and interface implemention more convenient.
+
+### Enabling a New Type Mapping
+
+We've defined the `StructTraits` necessary, but we still need to teach the
+bindings generator (and hence the build system) about the mapping. To do this we
+must create a **typemap** file, which uses familiar GN syntax to describe the
+new type mapping.
+
+Let's place this `geometry.typemap` file alongside our Mojom file:
+
+```
+mojom = "//ui/gfx/geometry/mojo/geometry.mojom"
+public_headers = [ "//ui/gfx/geometry/rect.h" ]
+traits_headers = [ "//ui/gfx/geometry/mojo/geometry_struct_traits.h" ]
+sources = [ "//ui/gfx/geometry/mojo/geometry_struct_traits.cc" ]
+public_deps = [ "//ui/gfx/geometry" ]
+type_mappings = [
+  "gfx.mojom.Rect=gfx::Rect",
+]
+```
+
+Let's look at each of the variables above:
+
+* `mojom`: Specifies the `mojom` file to which the typemap applies. Many
+  typemaps may apply to the same `mojom` file, but any given typemap may only
+  apply to a single `mojom` file.
+* `public_headers`: Additional headers required by any code which would depend
+  on the Mojom definition of `gfx.mojom.Rect` now that the typemap is applied.
+  Any headers required for the native target type definition should be listed
+  here.
+* `traits_headers`: Headers which contain the relevant `StructTraits`
+  specialization(s) for any type mappings described by this file.
+* `sources`: Any private implementation sources needed for the `StructTraits`
+  definition.
+* `public_deps`: Target dependencies exposed by the `public_headers` and
+  `traits_headers`.
+* `deps`: Target dependencies exposed by `sources` but not already covered by
+  `public_deps`.
+* `type_mappings`: A list of type mappings to be applied for this typemap. The
+  strings in this list are of the format `"MojomType=CppType"`, where
+  `MojomType` must be a fully qualified Mojom typename and `CppType` must be a
+  fully qualified C++ typename. Additional attributes may be specified in square
+  brackets following the `CppType`:
+    * `move_only`: The `CppType` is move-only and should be passed by value
+      in any generated method signatures. Note that `move_only` is transitive,
+      so containers of `MojomType` will translate to containers of `CppType`
+      also passed by value.
+    * `copyable_pass_by_value`: Forces values of type `CppType` to be passed by
+      value without moving them. Unlike `move_only`, this is not transitive.
+    * `nullable_is_same_type`: By default a non-nullable `MojomType` will be
+      mapped to `CppType` while a nullable `MojomType?` will be mapped to
+      `base::Optional<CppType>`. If this attribute is set, the `base::Optional`
+      wrapper is omitted for nullable `MojomType?` values, but the
+      `StructTraits` definition for this type mapping must define additional
+      `IsNull` and `SetToNull` methods. See
+      [Specializing Nullability](#Specializing-Nullability) below.
+
+
+Now that we have the typemap file we need to add it to a local list of typemaps
+that can be added to the global configuration. We create a new
+`//ui/gfx/typemaps.gni` file with the following contents:
+
+```
+typemaps = [
+  "//ui/gfx/geometry/mojo/geometry.typemap",
+]
+```
+
+And finally we can reference this file in the global default (Chromium) bindings
+configuration by adding it to `_typemap_imports` in
+[chromium_bindings_configuration.gni](https://cs.chromium.com/chromium/src/mojo/public/tools/bindings/chromium_bindings_configuration.gni):
+
+```
+_typemap_imports = [
+  ...,
+  "//ui/gfx/typemaps.gni",
+  ...,
+]
+```
+
+### StructTraits Reference
+
+Each of a `StructTraits` specialization's static getter methods -- one per
+struct field -- must return a type which can be used as a data source for the
+field during serialization. This is a quick reference mapping Mojom field type
+to valid getter return types:
+
+| Mojom Field Type             | C++ Getter Return Type |
+|------------------------------|------------------------|
+| `bool`                       | `bool`
+| `int8`                       | `int8_t`
+| `uint8`                      | `uint8_t`
+| `int16`                      | `int16_t`
+| `uint16`                     | `uint16_t`
+| `int32`                      | `int32_t`
+| `uint32`                     | `uint32_t`
+| `int64`                      | `int64_t`
+| `uint64`                     | `uint64_t`
+| `float`                      | `float`
+| `double`                     | `double`
+| `handle`                     | `mojo::ScopedHandle`
+| `handle<message_pipe>`       | `mojo::ScopedMessagePipeHandle`
+| `handle<data_pipe_consumer>` | `mojo::ScopedDataPipeConsumerHandle`
+| `handle<data_pipe_producer>` | `mojo::ScopedDataPipeProducerHandle`
+| `handle<shared_buffer>`      | `mojo::ScopedSharedBufferHandle`
+| `FooInterface`               | `FooInterfacePtr`
+| `FooInterface&`              | `FooInterfaceRequest`
+| `associated FooInterface`    | `FooAssociatedInterfacePtr`
+| `associated FooInterface&`   | `FooAssociatedInterfaceRequest`
+| `string`                     | Value or reference to any type `T` that has a `mojo::StringTraits` specialization defined. By default this includes `std::string`, `base::StringPiece`, and `WTF::String` (Blink).
+| `array<T>`                   | Value or reference to any type `T` that has a `mojo::ArrayTraits` specialization defined. By default this includes `std::vector<T>`, `mojo::CArray<T>`, and `WTF::Vector<T>` (Blink).
+| `map<K, V>`                  | Value or reference to any type `T` that has a `mojo::MapTraits` specialization defined. By default this includes `std::map<T>`, `mojo::unordered_map<T>`, and `WTF::HashMap<T>` (Blink).
+| `FooEnum`                    | Value of any type that has an appropriate `EnumTraits` specialization defined. By default this inlcudes only the generated `FooEnum` type.
+| `FooStruct`                  | Value or reference to any type that has an appropriate `StructTraits` specialization defined. By default this includes only the generated `FooStructPtr` type.
+| `FooUnion`                   | Value of reference to any type that has an appropriate `UnionTraits` specialization defined. By default this includes only the generated `FooUnionPtr` type.
+
+### Using Generated DataView Types
+
+Static `Read` methods on `StructTraits` specializations get a generated
+`FooDataView` argument (such as the `RectDataView` in the example above) which
+exposes a direct view of the serialized Mojom structure within an incoming
+message's contents. In order to make this as easy to work with as possible, the
+generated `FooDataView` types have a generated method corresponding to every
+struct field:
+
+* For POD field types (*e.g.* bools, floats, integers) these are simple accessor
+  methods with names identical to the field name. Hence in the `Rect` example we
+  can access things like `data.x()` and `data.width()`. The return types
+  correspond exactly to the mappings listed in the table above, under
+  [StructTraits Reference](#StructTraits-Reference).
+
+* For handle and interface types (*e.g* `handle` or `FooInterface&`) these
+  are named `TakeFieldName` (for a field named `field_name`) and they return an
+  appropriate move-only handle type by value. The return types correspond
+  exactly to the mappings listed in the table above, under
+  [StructTraits Reference](#StructTraits-Reference).
+
+* For all other field types (*e.g.*, enums, strings, arrays, maps, structs)
+  these are named `ReadFieldName` (for a field named `field_name`) and they
+  return a `bool` (to indicate success or failure in reading). On success they
+  fill their output argument with the deserialized field value. The output
+  argument may be a pointer to any type with an appropriate `StructTraits`
+  specialization defined, as mentioned in the table above, under
+  [StructTraits Reference](#StructTraits-Reference).
+
+An example would be useful here. Suppose we introduced a new Mojom struct:
+
+``` cpp
+struct RectPair {
+  Rect left;
+  Rect right;
+};
+```
+
+and a corresponding C++ type:
+
+``` cpp
+class RectPair {
+ public:
+  RectPair() {}
+
+  const gfx::Rect& left() const { return left_; }
+  const gfx::Rect& right() const { return right_; }
+
+  void Set(const gfx::Rect& left, const gfx::Rect& right) {
+    left_ = left;
+    right_ = right;
+  }
+
+  // ... some other stuff
+
+ private:
+  gfx::Rect left_;
+  gfx::Rect right_;
+};
+```
+
+Our traits to map `gfx::mojom::RectPair` to `gfx::RectPair` might look like
+this:
+
+``` cpp
+namespace mojo {
+
+template <>
+class StructTraits
+ public:
+  static const gfx::Rect& left(const gfx::RectPair& pair) {
+    return pair.left();
+  }
+
+  static const gfx::Rect& right(const gfx::RectPair& pair) {
+    return pair.right();
+  }
+
+  static bool Read(gfx::mojom::RectPairDataView data, gfx::RectPair* out_pair) {
+    gfx::Rect left, right;
+    if (!data.ReadLeft(&left) || !data.ReadRight(&right))
+      return false;
+    out_pair->Set(left, right);
+    return true;
+  }
+}  // namespace mojo
+```
+
+Generated `ReadFoo` methods always convert `multi_word_field_name` fields to
+`ReadMultiWordFieldName` methods.
+
+### Variants
+
+By now you may have noticed that additional C++ sources are generated when a
+Mojom is processed. These exist due to type mapping, and the source files we
+refer to throughout this docuemnt (namely `foo.mojom.cc` and `foo.mojom.h`) are
+really only one **variant** (the *default* or *chromium* variant) of the C++
+bindings for a given Mojom file.
+
+The only other variant currently defined in the tree is the *blink* variant,
+which produces a few additional files:
+
+```
+out/gen/sample/db.mojom-blink.cc
+out/gen/sample/db.mojom-blink.h
+```
+
+These files mirror the definitions in the default variant but with different
+C++ types in place of certain builtin field and parameter types. For example,
+Mojom strings are represented by `WTF::String` instead of `std::string`. To
+avoid symbol collisions, the variant's symbols are nested in an extra inner
+namespace, so Blink consumer of the interface might write something like:
+
+```
+#include "sample/db.mojom-blink.h"
+
+class TableImpl : public db::mojom::blink::Table {
+ public:
+  void AddRow(int32_t key, const WTF::String& data) override {
+    // ...
+  }
+};
+```
+
+In addition to using different C++ types for builtin strings, arrays, and maps,
+the global typemap configuration for default and "blink" variants are completely
+separate. To add a typemap for the Blink configuration, you can modify
+[blink_bindings_configuration.gni](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings/blink_bindings_configuration.gni).
+
+All variants share some definitions which are unaffected by differences in the
+type mapping configuration (enums, for example). These definitions are generated
+in *shared* sources:
+
+```
+out/gen/sample/db.mojom-shared.cc
+out/gen/sample/db.mojom-shared.h
+out/gen/sample/db.mojom-shared-internal.h
+```
+
+Including either variant's header (`db.mojom.h` or `db.mojom-blink.h`)
+implicitly includes the shared header, but you have on some occasions wish to
+include *only* the shared header in some instances.
+
+Finally, note that for `mojom` GN targets, there is implicitly a corresponding
+`mojom_{variant}` target defined for any supported bindings configuration. So
+for example if you've defined in `//sample/BUILD.gn`:
+
+```
+import("mojo/public/tools/bindings/mojom.gni")
+
+mojom("interfaces") {
+  sources = [
+    "db.mojom",
+  ]
+}
+```
+
+Code in Blink which wishes to use the generated Blink-variant definitions must
+depend on `"//sample:interfaces_blink"`.
+
+## Versioning Considerations
+
+For general documentation of versioning in the Mojom IDL see
+[Versioning](/mojo/public/tools/bindings#Versioning).
+
+This section briefly discusses some C++-specific considerations relevant to
+versioned Mojom types.
+
+### Querying Interface Versions
+
+`InterfacePtr` defines the following methods to query or assert remote interface
+version:
+
+```cpp
+void QueryVersion(const base::Callback<void(uint32_t)>& callback);
+```
+
+This queries the remote endpoint for the version number of its binding. When a
+response is received `callback` is invoked with the remote version number. Note
+that this value is cached by the `InterfacePtr` instance to avoid redundant
+queries.
+
+```cpp
+void RequireVersion(uint32_t version);
+```
+
+Informs the remote endpoint that a minimum version of `version` is required by
+the client. If the remote endpoint cannot support that version, it will close
+its end of the pipe immediately, preventing any other requests from being
+received.
+
+### Versioned Enums
+
+For convenience, every extensible enum has a generated helper function to
+determine whether a received enum value is known by the implementation's current
+version of the enum definition. For example:
+
+```cpp
+[Extensible]
+enum Department {
+  SALES,
+  DEV,
+  RESEARCH,
+};
+```
+
+generates the function in the same namespace as the generated C++ enum type:
+
+```cpp
+inline bool IsKnownEnumValue(Department value);
+```
+
+### Additional Documentation
+
+[Calling Mojo From Blink](https://www.chromium.org/developers/design-documents/mojo/calling-mojo-from-blink)
+:    A brief overview of what it looks like to use Mojom C++ bindings from
+     within Blink code.
diff --git a/mojo/public/cpp/bindings/associated_interface_ptr.h b/mojo/public/cpp/bindings/associated_interface_ptr.h
index 8e66f4e..8806a3e 100644
--- a/mojo/public/cpp/bindings/associated_interface_ptr.h
+++ b/mojo/public/cpp/bindings/associated_interface_ptr.h
@@ -18,6 +18,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "mojo/public/cpp/bindings/associated_interface_ptr_info.h"
 #include "mojo/public/cpp/bindings/associated_interface_request.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
 #include "mojo/public/cpp/bindings/connection_error_callback.h"
 #include "mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h"
 #include "mojo/public/cpp/bindings/lib/multiplex_router.h"
@@ -232,10 +233,9 @@
   return request;
 }
 
-// Like |GetProxy|, but the interface is never associated with any other
-// interface. The returned request can be bound directly to the corresponding
-// associated interface implementation, without first passing it through a
-// message pipe endpoint.
+// Like MakeRequest() above, but it creates a dedicated message pipe. The
+// returned request can be bound directly to an implementation, without being
+// first passed through a message pipe endpoint.
 //
 // This function has two main uses:
 //
@@ -245,7 +245,7 @@
 //  * When discarding messages sent on an interface, which can be done by
 //    discarding the returned request.
 template <typename Interface>
-AssociatedInterfaceRequest<Interface> GetIsolatedProxy(
+AssociatedInterfaceRequest<Interface> MakeIsolatedRequest(
     AssociatedInterfacePtr<Interface>* ptr) {
   MessagePipe pipe;
   scoped_refptr<internal::MultiplexRouter> router0 =
@@ -271,14 +271,12 @@
   return request;
 }
 
-// Creates an associated interface proxy in its own AssociatedGroup.
-// TODO(yzshen): Rename GetIsolatedProxy() to MakeIsolatedRequest(), and change
-// all callsites of this function to directly use that.
-template <typename Interface>
-AssociatedInterfaceRequest<Interface> MakeRequestForTesting(
-    AssociatedInterfacePtr<Interface>* ptr) {
-  return GetIsolatedProxy(ptr);
-}
+// |handle| is supposed to be the request of an associated interface. This
+// method associates the interface with a dedicated, disconnected message pipe.
+// That way, the corresponding associated interface pointer of |handle| can
+// safely make calls (although those calls are silently dropped).
+MOJO_CPP_BINDINGS_EXPORT void GetIsolatedInterface(
+    ScopedInterfaceEndpointHandle handle);
 
 }  // namespace mojo
 
diff --git a/mojo/public/cpp/bindings/binding.h b/mojo/public/cpp/bindings/binding.h
index 1da331b..88d2f4b 100644
--- a/mojo/public/cpp/bindings/binding.h
+++ b/mojo/public/cpp/bindings/binding.h
@@ -192,10 +192,10 @@
   // true if a method was successfully read and dispatched.
   //
   // This method may only be called if the object has been bound to a message
-  // pipe and there are no associated interfaces running.
+  // pipe. This returns once a message is received either on the master
+  // interface or any associated interfaces.
   bool WaitForIncomingMethodCall(
       MojoDeadline deadline = MOJO_DEADLINE_INDEFINITE) {
-    CHECK(!HasAssociatedInterfaces());
     return internal_state_.WaitForIncomingMethodCall(deadline);
   }
 
diff --git a/mojo/public/cpp/bindings/connector.h b/mojo/public/cpp/bindings/connector.h
index 01e9236..cb065c1 100644
--- a/mojo/public/cpp/bindings/connector.h
+++ b/mojo/public/cpp/bindings/connector.h
@@ -18,7 +18,7 @@
 #include "mojo/public/cpp/bindings/message.h"
 #include "mojo/public/cpp/bindings/sync_handle_watcher.h"
 #include "mojo/public/cpp/system/core.h"
-#include "mojo/public/cpp/system/watcher.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
 
 namespace base {
 class Lock;
@@ -156,7 +156,10 @@
   void SetWatcherHeapProfilerTag(const char* tag);
 
  private:
-  // Callback of mojo::Watcher.
+  class ActiveDispatchTracker;
+  class MessageLoopNestingObserver;
+
+  // Callback of mojo::SimpleWatcher.
   void OnWatcherHandleReady(MojoResult result);
   // Callback of SyncHandleWatcher.
   void OnSyncHandleWatcherHandleReady(MojoResult result);
@@ -188,7 +191,7 @@
   MessageReceiver* incoming_receiver_ = nullptr;
 
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
-  std::unique_ptr<Watcher> handle_watcher_;
+  std::unique_ptr<SimpleWatcher> handle_watcher_;
 
   bool error_ = false;
   bool drop_writes_ = false;
@@ -215,6 +218,14 @@
   // notification.
   const char* heap_profiler_tag_ = nullptr;
 
+  // A cached pointer to the MessageLoopNestingObserver for the MessageLoop on
+  // which this Connector was created.
+  MessageLoopNestingObserver* const nesting_observer_;
+
+  // |true| iff the Connector is currently dispatching a message. Used to detect
+  // nested dispatch operations.
+  bool is_dispatching_ = false;
+
   // Create a single weak ptr and use it everywhere, to avoid the malloc/free
   // cost of creating a new weak ptr whenever it is needed.
   // NOTE: This weak pointer is invalidated when the message pipe is closed or
diff --git a/mojo/public/cpp/bindings/interface_endpoint_client.h b/mojo/public/cpp/bindings/interface_endpoint_client.h
index 0aea756..b519fe9 100644
--- a/mojo/public/cpp/bindings/interface_endpoint_client.h
+++ b/mojo/public/cpp/bindings/interface_endpoint_client.h
@@ -96,7 +96,7 @@
   // state.
   bool Accept(Message* message) override;
   bool AcceptWithResponder(Message* message,
-                           MessageReceiver* responder) override;
+                           std::unique_ptr<MessageReceiver> responder) override;
 
   // The following methods are called by the router. They must be called
   // outside of the router's lock.
diff --git a/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc b/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc
new file mode 100644
index 0000000..78281ed
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc
@@ -0,0 +1,18 @@
+// Copyright 2017 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 "mojo/public/cpp/bindings/associated_interface_ptr.h"
+
+namespace mojo {
+
+void GetIsolatedInterface(ScopedInterfaceEndpointHandle handle) {
+  MessagePipe pipe;
+  scoped_refptr<internal::MultiplexRouter> router =
+      new internal::MultiplexRouter(std::move(pipe.handle0),
+                                    internal::MultiplexRouter::MULTI_INTERFACE,
+                                    false, base::ThreadTaskRunnerHandle::Get());
+  router->AssociateInterface(std::move(handle));
+}
+
+}  // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h
index 72f7960..a4b5188 100644
--- a/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h
+++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h
@@ -131,7 +131,7 @@
 
   void ForwardMessageWithResponder(Message message,
                                    std::unique_ptr<MessageReceiver> responder) {
-    endpoint_client_->AcceptWithResponder(&message, responder.release());
+    endpoint_client_->AcceptWithResponder(&message, std::move(responder));
   }
 
  private:
diff --git a/mojo/public/cpp/bindings/lib/connector.cc b/mojo/public/cpp/bindings/lib/connector.cc
index 4426def..d93e45e 100644
--- a/mojo/public/cpp/bindings/lib/connector.cc
+++ b/mojo/public/cpp/bindings/lib/connector.cc
@@ -8,20 +8,131 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/lazy_instance.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
 #include "base/synchronization/lock.h"
+#include "base/threading/thread_local.h"
 #include "mojo/public/cpp/bindings/lib/may_auto_lock.h"
 #include "mojo/public/cpp/bindings/sync_handle_watcher.h"
+#include "mojo/public/cpp/system/wait.h"
 
 namespace mojo {
 
+namespace {
+
+// The NestingObserver for each thread. Note that this is always a
+// Connector::MessageLoopNestingObserver; we use the base type here because that
+// subclass is private to Connector.
+base::LazyInstance<
+    base::ThreadLocalPointer<base::MessageLoop::NestingObserver>>::Leaky
+    g_tls_nesting_observer = LAZY_INSTANCE_INITIALIZER;
+
+}  // namespace
+
+// Used to efficiently maintain a doubly-linked list of all Connectors
+// currently dispatching on any given thread.
+class Connector::ActiveDispatchTracker {
+ public:
+  explicit ActiveDispatchTracker(const base::WeakPtr<Connector>& connector);
+  ~ActiveDispatchTracker();
+
+  void NotifyBeginNesting();
+
+ private:
+  const base::WeakPtr<Connector> connector_;
+  MessageLoopNestingObserver* const nesting_observer_;
+  ActiveDispatchTracker* outer_tracker_ = nullptr;
+  ActiveDispatchTracker* inner_tracker_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(ActiveDispatchTracker);
+};
+
+// Watches the MessageLoop on the current thread. Notifies the current chain of
+// ActiveDispatchTrackers when a nested message loop is started.
+class Connector::MessageLoopNestingObserver
+    : public base::MessageLoop::NestingObserver,
+      public base::MessageLoop::DestructionObserver {
+ public:
+  MessageLoopNestingObserver() {
+    base::MessageLoop::current()->AddNestingObserver(this);
+    base::MessageLoop::current()->AddDestructionObserver(this);
+  }
+
+  ~MessageLoopNestingObserver() override {}
+
+  // base::MessageLoop::NestingObserver:
+  void OnBeginNestedMessageLoop() override {
+    if (top_tracker_)
+      top_tracker_->NotifyBeginNesting();
+  }
+
+  // base::MessageLoop::DestructionObserver:
+  void WillDestroyCurrentMessageLoop() override {
+    base::MessageLoop::current()->RemoveNestingObserver(this);
+    base::MessageLoop::current()->RemoveDestructionObserver(this);
+    DCHECK_EQ(this, g_tls_nesting_observer.Get().Get());
+    g_tls_nesting_observer.Get().Set(nullptr);
+    delete this;
+  }
+
+  static MessageLoopNestingObserver* GetForThread() {
+    if (!base::MessageLoop::current() ||
+        !base::MessageLoop::current()->nesting_allowed())
+      return nullptr;
+    auto* observer = static_cast<MessageLoopNestingObserver*>(
+        g_tls_nesting_observer.Get().Get());
+    if (!observer) {
+      observer = new MessageLoopNestingObserver;
+      g_tls_nesting_observer.Get().Set(observer);
+    }
+    return observer;
+  }
+
+ private:
+  friend class ActiveDispatchTracker;
+
+  ActiveDispatchTracker* top_tracker_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(MessageLoopNestingObserver);
+};
+
+Connector::ActiveDispatchTracker::ActiveDispatchTracker(
+    const base::WeakPtr<Connector>& connector)
+    : connector_(connector), nesting_observer_(connector_->nesting_observer_) {
+  DCHECK(nesting_observer_);
+  if (nesting_observer_->top_tracker_) {
+    outer_tracker_ = nesting_observer_->top_tracker_;
+    outer_tracker_->inner_tracker_ = this;
+  }
+  nesting_observer_->top_tracker_ = this;
+}
+
+Connector::ActiveDispatchTracker::~ActiveDispatchTracker() {
+  if (nesting_observer_->top_tracker_ == this)
+    nesting_observer_->top_tracker_ = outer_tracker_;
+  else if (inner_tracker_)
+    inner_tracker_->outer_tracker_ = outer_tracker_;
+  if (outer_tracker_)
+    outer_tracker_->inner_tracker_ = inner_tracker_;
+}
+
+void Connector::ActiveDispatchTracker::NotifyBeginNesting() {
+  if (connector_ && connector_->handle_watcher_)
+    connector_->handle_watcher_->ArmOrNotify();
+  if (outer_tracker_)
+    outer_tracker_->NotifyBeginNesting();
+}
+
 Connector::Connector(ScopedMessagePipeHandle message_pipe,
                      ConnectorConfig config,
                      scoped_refptr<base::SingleThreadTaskRunner> runner)
     : message_pipe_(std::move(message_pipe)),
       task_runner_(std::move(runner)),
+      nesting_observer_(MessageLoopNestingObserver::GetForThread()),
       weak_factory_(this) {
   if (config == MULTI_THREADED_SEND)
     lock_.emplace();
@@ -77,16 +188,24 @@
 
   ResumeIncomingMethodCallProcessing();
 
-  MojoResult rv =
-      Wait(message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE, deadline, nullptr);
-  if (rv == MOJO_RESULT_SHOULD_WAIT || rv == MOJO_RESULT_DEADLINE_EXCEEDED)
+  // TODO(rockot): Use a timed Wait here. Nobody uses anything but 0 or
+  // INDEFINITE deadlines at present, so we only support those.
+  DCHECK(deadline == 0 || deadline == MOJO_DEADLINE_INDEFINITE);
+
+  MojoResult rv = MOJO_RESULT_UNKNOWN;
+  if (deadline == 0 && !message_pipe_->QuerySignalsState().readable())
     return false;
-  if (rv != MOJO_RESULT_OK) {
-    // Users that call WaitForIncomingMessage() should expect their code to be
-    // re-entered, so we call the error handler synchronously.
-    HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION, false);
-    return false;
+
+  if (deadline == MOJO_DEADLINE_INDEFINITE) {
+    rv = Wait(message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE);
+    if (rv != MOJO_RESULT_OK) {
+      // Users that call WaitForIncomingMessage() should expect their code to be
+      // re-entered, so we call the error handler synchronously.
+      HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION, false);
+      return false;
+    }
   }
+
   ignore_result(ReadSingleMessage(&rv));
   return (rv == MOJO_RESULT_OK);
 }
@@ -211,6 +330,7 @@
     HandleError(result != MOJO_RESULT_FAILED_PRECONDITION, false);
     return;
   }
+
   ReadAllAvailableMessages();
   // At this point, this object might have been deleted. Return.
 }
@@ -219,10 +339,11 @@
   CHECK(!paused_);
   DCHECK(!handle_watcher_);
 
-  handle_watcher_.reset(new Watcher(FROM_HERE, task_runner_));
+  handle_watcher_.reset(new SimpleWatcher(
+      FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL, task_runner_));
   if (heap_profiler_tag_)
     handle_watcher_->set_heap_profiler_tag(heap_profiler_tag_);
-  MojoResult rv = handle_watcher_->Start(
+  MojoResult rv = handle_watcher_->Watch(
       message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE,
       base::Bind(&Connector::OnWatcherHandleReady, base::Unretained(this)));
 
@@ -232,6 +353,8 @@
     task_runner_->PostTask(
         FROM_HERE,
         base::Bind(&Connector::OnWatcherHandleReady, weak_self_, rv));
+  } else {
+    handle_watcher_->ArmOrNotify();
   }
 
   if (allow_woken_up_by_others_) {
@@ -254,17 +377,25 @@
   *read_result = rv;
 
   if (rv == MOJO_RESULT_OK) {
+    base::Optional<ActiveDispatchTracker> dispatch_tracker;
+    if (!is_dispatching_ && nesting_observer_) {
+      is_dispatching_ = true;
+      dispatch_tracker.emplace(weak_self);
+    }
+
     receiver_result =
         incoming_receiver_ && incoming_receiver_->Accept(&message);
-  }
 
-  if (!weak_self)
-    return false;
+    if (!weak_self)
+      return false;
 
-  if (rv == MOJO_RESULT_SHOULD_WAIT)
+    if (dispatch_tracker) {
+      is_dispatching_ = false;
+      dispatch_tracker.reset();
+    }
+  } else if (rv == MOJO_RESULT_SHOULD_WAIT) {
     return true;
-
-  if (rv != MOJO_RESULT_OK) {
+  } else {
     HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION, false);
     return false;
   }
@@ -278,19 +409,36 @@
 
 void Connector::ReadAllAvailableMessages() {
   while (!error_) {
+    base::WeakPtr<Connector> weak_self = weak_self_;
     MojoResult rv;
 
-    if (!ReadSingleMessage(&rv)) {
-      // Return immediately without touching any members. |this| may have been
-      // destroyed.
+    // May delete |this.|
+    if (!ReadSingleMessage(&rv))
       return;
+
+    if (!weak_self || paused_)
+      return;
+
+    DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_SHOULD_WAIT);
+
+    if (rv == MOJO_RESULT_SHOULD_WAIT) {
+      // Attempt to re-arm the Watcher.
+      MojoResult ready_result;
+      MojoResult arm_result = handle_watcher_->Arm(&ready_result);
+      if (arm_result == MOJO_RESULT_OK)
+        return;
+
+      // The watcher is already ready to notify again.
+      DCHECK_EQ(MOJO_RESULT_FAILED_PRECONDITION, arm_result);
+
+      if (ready_result == MOJO_RESULT_FAILED_PRECONDITION) {
+        HandleError(false, false);
+        return;
+      }
+
+      // There's more to read now, so we'll just keep looping.
+      DCHECK_EQ(MOJO_RESULT_OK, ready_result);
     }
-
-    if (paused_)
-      return;
-
-    if (rv == MOJO_RESULT_SHOULD_WAIT)
-      break;
   }
 }
 
diff --git a/mojo/public/cpp/bindings/lib/control_message_handler.cc b/mojo/public/cpp/bindings/lib/control_message_handler.cc
index c90aada..1b7bb78 100644
--- a/mojo/public/cpp/bindings/lib/control_message_handler.cc
+++ b/mojo/public/cpp/bindings/lib/control_message_handler.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "base/logging.h"
+#include "base/macros.h"
 #include "mojo/public/cpp/bindings/lib/message_builder.h"
 #include "mojo/public/cpp/bindings/lib/serialization.h"
 #include "mojo/public/cpp/bindings/lib/validation_util.h"
@@ -80,19 +81,20 @@
 
 bool ControlMessageHandler::AcceptWithResponder(
     Message* message,
-    MessageReceiverWithStatus* responder) {
+    std::unique_ptr<MessageReceiverWithStatus> responder) {
   if (!ValidateControlRequestWithResponse(message))
     return false;
 
   if (message->header()->name == interface_control::kRunMessageId)
-    return Run(message, responder);
+    return Run(message, std::move(responder));
 
   NOTREACHED();
   return false;
 }
 
-bool ControlMessageHandler::Run(Message* message,
-                                MessageReceiverWithStatus* responder) {
+bool ControlMessageHandler::Run(
+    Message* message,
+    std::unique_ptr<MessageReceiverWithStatus> responder) {
   interface_control::internal::RunMessageParams_Data* params =
       reinterpret_cast<interface_control::internal::RunMessageParams_Data*>(
           message->mutable_payload());
@@ -124,9 +126,7 @@
       nullptr;
   Serialize<interface_control::RunResponseMessageParamsDataView>(
       response_params_ptr, builder.buffer(), &response_params, &context_);
-  bool ok = responder->Accept(builder.message());
-  ALLOW_UNUSED_LOCAL(ok);
-  delete responder;
+  ignore_result(responder->Accept(builder.message()));
 
   return true;
 }
diff --git a/mojo/public/cpp/bindings/lib/control_message_handler.h b/mojo/public/cpp/bindings/lib/control_message_handler.h
index 3c385e4..5d1f716 100644
--- a/mojo/public/cpp/bindings/lib/control_message_handler.h
+++ b/mojo/public/cpp/bindings/lib/control_message_handler.h
@@ -27,12 +27,13 @@
 
   // Call the following methods only if IsControlMessage() returned true.
   bool Accept(Message* message) override;
-  // Takes ownership of |responder|.
-  bool AcceptWithResponder(Message* message,
-                           MessageReceiverWithStatus* responder) override;
+  bool AcceptWithResponder(
+      Message* message,
+      std::unique_ptr<MessageReceiverWithStatus> responder) override;
 
  private:
-  bool Run(Message* message, MessageReceiverWithStatus* responder);
+  bool Run(Message* message,
+           std::unique_ptr<MessageReceiverWithStatus> responder);
   bool RunOrClosePipe(Message* message);
 
   uint32_t interface_version_;
diff --git a/mojo/public/cpp/bindings/lib/control_message_proxy.cc b/mojo/public/cpp/bindings/lib/control_message_proxy.cc
index 23de991..d082b49 100644
--- a/mojo/public/cpp/bindings/lib/control_message_proxy.cc
+++ b/mojo/public/cpp/bindings/lib/control_message_proxy.cc
@@ -85,9 +85,10 @@
   interface_control::internal::RunMessageParams_Data* params = nullptr;
   Serialize<interface_control::RunMessageParamsDataView>(
       params_ptr, builder.buffer(), &params, &context);
-  MessageReceiver* responder = new RunResponseForwardToCallback(callback);
-  if (!receiver->AcceptWithResponder(builder.message(), responder))
-    delete responder;
+  std::unique_ptr<MessageReceiver> responder =
+      base::MakeUnique<RunResponseForwardToCallback>(callback);
+  ignore_result(
+      receiver->AcceptWithResponder(builder.message(), std::move(responder)));
 }
 
 Message ConstructRunOrClosePipeMessage(
@@ -115,8 +116,7 @@
     interface_control::RunOrClosePipeInputPtr input_ptr) {
   Message message(ConstructRunOrClosePipeMessage(std::move(input_ptr)));
 
-  bool ok = receiver->Accept(&message);
-  ALLOW_UNUSED_LOCAL(ok);
+  ignore_result(receiver->Accept(&message));
 }
 
 void RunVersionCallback(
diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
index 3eca5a1..4682e72 100644
--- a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
+++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
@@ -234,8 +234,9 @@
   return controller_->SendMessage(message);
 }
 
-bool InterfaceEndpointClient::AcceptWithResponder(Message* message,
-                                                  MessageReceiver* responder) {
+bool InterfaceEndpointClient::AcceptWithResponder(
+    Message* message,
+    std::unique_ptr<MessageReceiver> responder) {
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(message->has_flag(Message::kFlagExpectsResponse));
   DCHECK(!handle_.pending_association());
@@ -261,15 +262,13 @@
     return false;
 
   if (!is_sync) {
-    // We assume ownership of |responder|.
-    async_responders_[request_id] = base::WrapUnique(responder);
+    async_responders_[request_id] = std::move(responder);
     return true;
   }
 
   SyncCallRestrictions::AssertSyncCallAllowed();
 
   bool response_received = false;
-  std::unique_ptr<MessageReceiver> sync_responder(responder);
   sync_responses_.insert(std::make_pair(
       request_id, base::MakeUnique<SyncResponseInfo>(&response_received)));
 
@@ -282,11 +281,10 @@
     auto iter = sync_responses_.find(request_id);
     DCHECK_EQ(&response_received, iter->second->response_received);
     if (response_received)
-      ignore_result(sync_responder->Accept(&iter->second->response));
+      ignore_result(responder->Accept(&iter->second->response));
     sync_responses_.erase(iter);
   }
 
-  // Return true means that we take ownership of |responder|.
   return true;
 }
 
@@ -375,17 +373,16 @@
   }
 
   if (message->has_flag(Message::kFlagExpectsResponse)) {
-    MessageReceiverWithStatus* responder =
-        new ResponderThunk(weak_ptr_factory_.GetWeakPtr(), task_runner_);
-    bool ok = false;
+    std::unique_ptr<MessageReceiverWithStatus> responder =
+        base::MakeUnique<ResponderThunk>(weak_ptr_factory_.GetWeakPtr(),
+                                         task_runner_);
     if (mojo::internal::ControlMessageHandler::IsControlMessage(message)) {
-      ok = control_message_handler_.AcceptWithResponder(message, responder);
+      return control_message_handler_.AcceptWithResponder(message,
+                                                          std::move(responder));
     } else {
-      ok = incoming_receiver_->AcceptWithResponder(message, responder);
+      return incoming_receiver_->AcceptWithResponder(message,
+                                                     std::move(responder));
     }
-    if (!ok)
-      delete responder;
-    return ok;
   } else if (message->has_flag(Message::kFlagIsResponse)) {
     uint64_t request_id = message->request_id();
 
diff --git a/mojo/public/cpp/bindings/lib/interface_ptr_state.h b/mojo/public/cpp/bindings/lib/interface_ptr_state.h
index 8f5b4ff..fa54979 100644
--- a/mojo/public/cpp/bindings/lib/interface_ptr_state.h
+++ b/mojo/public/cpp/bindings/lib/interface_ptr_state.h
@@ -163,7 +163,7 @@
   void ForwardMessageWithResponder(Message message,
                                    std::unique_ptr<MessageReceiver> responder) {
     ConfigureProxyIfNecessary();
-    endpoint_client_->AcceptWithResponder(&message, responder.release());
+    endpoint_client_->AcceptWithResponder(&message, std::move(responder));
   }
 
  private:
diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.cc b/mojo/public/cpp/bindings/lib/multiplex_router.cc
index 2da459a..ff7c678 100644
--- a/mojo/public/cpp/bindings/lib/multiplex_router.cc
+++ b/mojo/public/cpp/bindings/lib/multiplex_router.cc
@@ -14,11 +14,12 @@
 #include "base/memory/ptr_util.h"
 #include "base/single_thread_task_runner.h"
 #include "base/stl_util.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "mojo/public/cpp/bindings/interface_endpoint_client.h"
 #include "mojo/public/cpp/bindings/interface_endpoint_controller.h"
 #include "mojo/public/cpp/bindings/lib/may_auto_lock.h"
-#include "mojo/public/cpp/bindings/sync_handle_watcher.h"
+#include "mojo/public/cpp/bindings/sync_event_watcher.h"
 
 namespace mojo {
 namespace internal {
@@ -28,7 +29,7 @@
 // No one other than the router's |endpoints_| and |tasks_| should hold refs to
 // this object.
 class MultiplexRouter::InterfaceEndpoint
-    : public base::RefCounted<InterfaceEndpoint>,
+    : public base::RefCountedThreadSafe<InterfaceEndpoint>,
       public InterfaceEndpointController {
  public:
   InterfaceEndpoint(MultiplexRouter* router, InterfaceId id)
@@ -37,8 +38,7 @@
         closed_(false),
         peer_closed_(false),
         handle_created_(false),
-        client_(nullptr),
-        event_signalled_(false) {}
+        client_(nullptr) {}
 
   // ---------------------------------------------------------------------------
   // The following public methods are safe to call from any threads without
@@ -108,33 +108,20 @@
 
   void SignalSyncMessageEvent() {
     router_->AssertLockAcquired();
-    if (event_signalled_)
+    if (sync_message_event_signaled_)
       return;
-
-    event_signalled_ = true;
-    if (!sync_message_event_sender_.is_valid())
-      return;
-
-    MojoResult result =
-        WriteMessageRaw(sync_message_event_sender_.get(), nullptr, 0, nullptr,
-                        0, MOJO_WRITE_MESSAGE_FLAG_NONE);
-    DCHECK_EQ(MOJO_RESULT_OK, result);
+    sync_message_event_signaled_ = true;
+    if (sync_message_event_)
+      sync_message_event_->Signal();
   }
 
   void ResetSyncMessageSignal() {
     router_->AssertLockAcquired();
-
-    if (!event_signalled_)
+    if (!sync_message_event_signaled_)
       return;
-
-    event_signalled_ = false;
-    if (!sync_message_event_receiver_.is_valid())
-      return;
-
-    MojoResult result =
-        ReadMessageRaw(sync_message_event_receiver_.get(), nullptr, nullptr,
-                       nullptr, nullptr, MOJO_READ_MESSAGE_FLAG_MAY_DISCARD);
-    DCHECK_EQ(MOJO_RESULT_OK, result);
+    sync_message_event_signaled_ = false;
+    if (sync_message_event_)
+      sync_message_event_->Reset();
   }
 
   // ---------------------------------------------------------------------------
@@ -163,7 +150,7 @@
   }
 
  private:
-  friend class base::RefCounted<InterfaceEndpoint>;
+  friend class base::RefCountedThreadSafe<InterfaceEndpoint>;
 
   ~InterfaceEndpoint() override {
     router_->AssertLockAcquired();
@@ -174,14 +161,10 @@
     DCHECK(!sync_watcher_);
   }
 
-  void OnHandleReady(MojoResult result) {
+  void OnSyncEventSignaled() {
     DCHECK(task_runner_->BelongsToCurrentThread());
     scoped_refptr<MultiplexRouter> router_protector(router_);
 
-    // Because we never close |sync_message_event_{sender,receiver}_| before
-    // destruction or set a deadline, |result| should always be MOJO_RESULT_OK.
-    DCHECK_EQ(MOJO_RESULT_OK, result);
-
     MayAutoLock locker(&router_->lock_);
     scoped_refptr<InterfaceEndpoint> self_protector(this);
 
@@ -207,25 +190,18 @@
 
     {
       MayAutoLock locker(&router_->lock_);
-
-      if (!sync_message_event_sender_.is_valid()) {
-        MojoResult result =
-            CreateMessagePipe(nullptr, &sync_message_event_sender_,
-                              &sync_message_event_receiver_);
-        DCHECK_EQ(MOJO_RESULT_OK, result);
-
-        if (event_signalled_) {
-          // Reset the flag so that SignalSyncMessageEvent() will actually
-          // signal using the newly-created message pipe.
-          event_signalled_ = false;
-          SignalSyncMessageEvent();
-        }
+      if (!sync_message_event_) {
+        sync_message_event_.emplace(
+            base::WaitableEvent::ResetPolicy::MANUAL,
+            base::WaitableEvent::InitialState::NOT_SIGNALED);
+        if (sync_message_event_signaled_)
+          sync_message_event_->Signal();
       }
     }
-
-    sync_watcher_.reset(new SyncHandleWatcher(
-        sync_message_event_receiver_.get(), MOJO_HANDLE_SIGNAL_READABLE,
-        base::Bind(&InterfaceEndpoint::OnHandleReady, base::Unretained(this))));
+    sync_watcher_.reset(
+        new SyncEventWatcher(&sync_message_event_.value(),
+                             base::Bind(&InterfaceEndpoint::OnSyncEventSignaled,
+                                        base::Unretained(this))));
   }
 
   // ---------------------------------------------------------------------------
@@ -253,20 +229,18 @@
   // Not owned. It is null if no client is attached to this endpoint.
   InterfaceEndpointClient* client_;
 
-  // A message pipe used as an event to signal that sync messages are available.
-  // The message pipe handles are initialized under the router's lock and remain
-  // unchanged afterwards. They may be accessed outside of the router's lock
-  // later.
-  ScopedMessagePipeHandle sync_message_event_sender_;
-  ScopedMessagePipeHandle sync_message_event_receiver_;
-  bool event_signalled_;
+  // An event used to signal that sync messages are available. The event is
+  // initialized under the router's lock and remains unchanged afterwards. It
+  // may be accessed outside of the router's lock later.
+  base::Optional<base::WaitableEvent> sync_message_event_;
+  bool sync_message_event_signaled_ = false;
 
   // ---------------------------------------------------------------------------
   // The following members are only valid while a client is attached. They are
   // used exclusively on the client's thread. They may be accessed outside of
   // the router's lock.
 
-  std::unique_ptr<SyncHandleWatcher> sync_watcher_;
+  std::unique_ptr<SyncEventWatcher> sync_watcher_;
 
   DISALLOW_COPY_AND_ASSIGN(InterfaceEndpoint);
 };
diff --git a/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc b/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc
index 701108e..1029c2c 100644
--- a/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc
+++ b/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc
@@ -7,8 +7,8 @@
 #include <stddef.h>
 #include <utility>
 
-#include "base/compiler_specific.h"
 #include "base/logging.h"
+#include "base/macros.h"
 #include "mojo/public/cpp/bindings/lib/message_builder.h"
 #include "mojo/public/cpp/bindings/lib/serialization.h"
 #include "mojo/public/interfaces/bindings/pipe_control_messages.mojom.h"
@@ -44,8 +44,7 @@
     InterfaceId id,
     const base::Optional<DisconnectReason>& reason) {
   Message message(ConstructPeerEndpointClosedMessage(id, reason));
-  bool ok = receiver_->Accept(&message);
-  ALLOW_UNUSED_LOCAL(ok);
+  ignore_result(receiver_->Accept(&message));
 }
 
 // static
diff --git a/mojo/public/cpp/bindings/lib/serialization.h b/mojo/public/cpp/bindings/lib/serialization.h
index 359b02b..2a7d288 100644
--- a/mojo/public/cpp/bindings/lib/serialization.h
+++ b/mojo/public/cpp/bindings/lib/serialization.h
@@ -64,7 +64,10 @@
 }
 
 template <typename MojomType, typename DataArrayType, typename UserType>
-bool StructDeserializeImpl(const DataArrayType& input, UserType* output) {
+bool StructDeserializeImpl(const DataArrayType& input,
+                           UserType* output,
+                           bool (*validate_func)(const void*,
+                                                 ValidationContext*)) {
   static_assert(BelongsTo<MojomType, MojomTypeCategory::STRUCT>::value,
                 "Unexpected type.");
   using DataType = typename MojomTypeTraits<MojomType>::Data;
@@ -86,7 +89,7 @@
 
   ValidationContext validation_context(input_buffer, input.size(), 0, 0);
   bool result = false;
-  if (DataType::Validate(input_buffer, &validation_context)) {
+  if (validate_func(input_buffer, &validation_context)) {
     auto data = reinterpret_cast<DataType*>(input_buffer);
     SerializationContext context;
     result = Deserialize<MojomType>(data, output, &context);
diff --git a/mojo/public/cpp/bindings/lib/sync_event_watcher.cc b/mojo/public/cpp/bindings/lib/sync_event_watcher.cc
new file mode 100644
index 0000000..b1c97e3
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/sync_event_watcher.cc
@@ -0,0 +1,67 @@
+// Copyright 2017 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 "mojo/public/cpp/bindings/sync_event_watcher.h"
+
+#include "base/logging.h"
+
+namespace mojo {
+
+SyncEventWatcher::SyncEventWatcher(base::WaitableEvent* event,
+                                   const base::Closure& callback)
+    : event_(event),
+      callback_(callback),
+      registry_(SyncHandleRegistry::current()),
+      destroyed_(new base::RefCountedData<bool>(false)) {}
+
+SyncEventWatcher::~SyncEventWatcher() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (registered_)
+    registry_->UnregisterEvent(event_);
+  destroyed_->data = true;
+}
+
+void SyncEventWatcher::AllowWokenUpBySyncWatchOnSameThread() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  IncrementRegisterCount();
+}
+
+bool SyncEventWatcher::SyncWatch(const bool* should_stop) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  IncrementRegisterCount();
+  if (!registered_) {
+    DecrementRegisterCount();
+    return false;
+  }
+
+  // This object may be destroyed during the Wait() call. So we have to preserve
+  // the boolean that Wait uses.
+  auto destroyed = destroyed_;
+  const bool* should_stop_array[] = {should_stop, &destroyed->data};
+  bool result = registry_->Wait(should_stop_array, 2);
+
+  // This object has been destroyed.
+  if (destroyed->data)
+    return false;
+
+  DecrementRegisterCount();
+  return result;
+}
+
+void SyncEventWatcher::IncrementRegisterCount() {
+  register_request_count_++;
+  if (!registered_)
+    registered_ = registry_->RegisterEvent(event_, callback_);
+}
+
+void SyncEventWatcher::DecrementRegisterCount() {
+  DCHECK_GT(register_request_count_, 0u);
+  register_request_count_--;
+  if (register_request_count_ == 0 && registered_) {
+    registry_->UnregisterEvent(event_);
+    registered_ = false;
+  }
+}
+
+}  // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/sync_handle_registry.cc b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc
index 5ae763b..fd3df39 100644
--- a/mojo/public/cpp/bindings/lib/sync_handle_registry.cc
+++ b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc
@@ -37,8 +37,7 @@
   if (base::ContainsKey(handles_, handle))
     return false;
 
-  MojoResult result = MojoAddHandle(wait_set_handle_.get().value(),
-                                    handle.value(), handle_signals);
+  MojoResult result = wait_set_.AddHandle(handle, handle_signals);
   if (result != MOJO_RESULT_OK)
     return false;
 
@@ -51,19 +50,35 @@
   if (!base::ContainsKey(handles_, handle))
     return;
 
-  MojoResult result =
-      MojoRemoveHandle(wait_set_handle_.get().value(), handle.value());
+  MojoResult result = wait_set_.RemoveHandle(handle);
   DCHECK_EQ(MOJO_RESULT_OK, result);
   handles_.erase(handle);
 }
 
-bool SyncHandleRegistry::WatchAllHandles(const bool* should_stop[],
-                                         size_t count) {
+bool SyncHandleRegistry::RegisterEvent(base::WaitableEvent* event,
+                                       const base::Closure& callback) {
+  auto result = events_.insert({event, callback});
+  DCHECK(result.second);
+  MojoResult rv = wait_set_.AddEvent(event);
+  if (rv == MOJO_RESULT_OK)
+    return true;
+  DCHECK_EQ(MOJO_RESULT_ALREADY_EXISTS, rv);
+  return false;
+}
+
+void SyncHandleRegistry::UnregisterEvent(base::WaitableEvent* event) {
+  auto it = events_.find(event);
+  DCHECK(it != events_.end());
+  events_.erase(it);
+  MojoResult rv = wait_set_.RemoveEvent(event);
+  DCHECK_EQ(MOJO_RESULT_OK, rv);
+}
+
+bool SyncHandleRegistry::Wait(const bool* should_stop[], size_t count) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
-  MojoResult result;
-  uint32_t num_ready_handles;
-  MojoHandle ready_handle;
+  size_t num_ready_handles;
+  Handle ready_handle;
   MojoResult ready_handle_result;
 
   scoped_refptr<SyncHandleRegistry> preserver(this);
@@ -71,36 +86,30 @@
     for (size_t i = 0; i < count; ++i)
       if (*should_stop[i])
         return true;
-    do {
-      result = Wait(wait_set_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE,
-                    MOJO_DEADLINE_INDEFINITE, nullptr);
-      if (result != MOJO_RESULT_OK)
-        return false;
 
-      // TODO(yzshen): Theoretically it can reduce sync call re-entrancy if we
-      // give priority to the handle that is waiting for sync response.
-      num_ready_handles = 1;
-      result = MojoGetReadyHandles(wait_set_handle_.get().value(),
-                                   &num_ready_handles, &ready_handle,
-                                   &ready_handle_result, nullptr);
-      if (result != MOJO_RESULT_OK && result != MOJO_RESULT_SHOULD_WAIT)
-        return false;
-    } while (result == MOJO_RESULT_SHOULD_WAIT);
+    // TODO(yzshen): Theoretically it can reduce sync call re-entrancy if we
+    // give priority to the handle that is waiting for sync response.
+    base::WaitableEvent* ready_event = nullptr;
+    num_ready_handles = 1;
+    wait_set_.Wait(&ready_event, &num_ready_handles, &ready_handle,
+                   &ready_handle_result);
+    if (num_ready_handles) {
+      DCHECK_EQ(1u, num_ready_handles);
+      const auto iter = handles_.find(ready_handle);
+      iter->second.Run(ready_handle_result);
+    }
 
-    const auto iter = handles_.find(Handle(ready_handle));
-    iter->second.Run(ready_handle_result);
+    if (ready_event) {
+      const auto iter = events_.find(ready_event);
+      DCHECK(iter != events_.end());
+      iter->second.Run();
+    }
   };
 
   return false;
 }
 
 SyncHandleRegistry::SyncHandleRegistry() {
-  MojoHandle handle;
-  MojoResult result = MojoCreateWaitSet(&handle);
-  CHECK_EQ(MOJO_RESULT_OK, result);
-  wait_set_handle_.reset(Handle(handle));
-  CHECK(wait_set_handle_.is_valid());
-
   DCHECK(!g_current_sync_handle_watcher.Pointer()->Get());
   g_current_sync_handle_watcher.Pointer()->Set(this);
 }
diff --git a/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc b/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc
index 92b91f4..f20af56 100644
--- a/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc
+++ b/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc
@@ -41,11 +41,11 @@
     return false;
   }
 
-  // This object may be destroyed during the WatchAllHandles() call. So we have
-  // to preserve the boolean that WatchAllHandles uses.
+  // This object may be destroyed during the Wait() call. So we have to preserve
+  // the boolean that Wait uses.
   auto destroyed = destroyed_;
   const bool* should_stop_array[] = {should_stop, &destroyed->data};
-  bool result = registry_->WatchAllHandles(should_stop_array, 2);
+  bool result = registry_->Wait(should_stop_array, 2);
 
   // This object has been destroyed.
   if (destroyed->data)
diff --git a/mojo/public/cpp/bindings/message.h b/mojo/public/cpp/bindings/message.h
index 65d6cec..48e6900 100644
--- a/mojo/public/cpp/bindings/message.h
+++ b/mojo/public/cpp/bindings/message.h
@@ -183,14 +183,8 @@
   // responder) to handle the response message generated from the given
   // message. The responder's Accept method may be called during
   // AcceptWithResponder or some time after its return.
-  //
-  // NOTE: Upon returning true, AcceptWithResponder assumes ownership of
-  // |responder| and will delete it after calling |responder->Accept| or upon
-  // its own destruction.
-  //
-  // TODO(yzshen): consider changing |responder| to
-  // std::unique_ptr<MessageReceiver>.
-  virtual bool AcceptWithResponder(Message* message, MessageReceiver* responder)
+  virtual bool AcceptWithResponder(Message* message,
+                                   std::unique_ptr<MessageReceiver> responder)
       WARN_UNUSED_RESULT = 0;
 };
 
@@ -222,16 +216,9 @@
   // the responder) to handle the response message generated from the given
   // message. Any of the responder's methods (Accept or IsValid) may be called
   // during  AcceptWithResponder or some time after its return.
-  //
-  // NOTE: Upon returning true, AcceptWithResponder assumes ownership of
-  // |responder| and will delete it after calling |responder->Accept| or upon
-  // its own destruction.
-  //
-  // TODO(yzshen): consider changing |responder| to
-  // std::unique_ptr<MessageReceiver>.
   virtual bool AcceptWithResponder(Message* message,
-                                   MessageReceiverWithStatus* responder)
-      WARN_UNUSED_RESULT = 0;
+                                   std::unique_ptr<MessageReceiverWithStatus>
+                                       responder) WARN_UNUSED_RESULT = 0;
 };
 
 class MOJO_CPP_BINDINGS_EXPORT PassThroughFilter
diff --git a/mojo/public/cpp/bindings/sync_call_restrictions.h b/mojo/public/cpp/bindings/sync_call_restrictions.h
index 39a77a8..5529042 100644
--- a/mojo/public/cpp/bindings/sync_call_restrictions.h
+++ b/mojo/public/cpp/bindings/sync_call_restrictions.h
@@ -19,6 +19,10 @@
 class LevelDBMojoProxy;
 }
 
+namespace prefs {
+class PersistentPrefStoreClient;
+}
+
 namespace ui {
 class Gpu;
 }
@@ -58,6 +62,9 @@
   friend class ui::Gpu;  // http://crbug.com/620058
   // LevelDBMojoProxy makes same-process sync calls from the DB thread.
   friend class leveldb::LevelDBMojoProxy;
+  // Pref service connection is sync at startup.
+  friend class prefs::PersistentPrefStoreClient;
+
   // END ALLOWED USAGE.
 
   // BEGIN USAGE THAT NEEDS TO BE FIXED.
diff --git a/mojo/public/cpp/bindings/sync_event_watcher.h b/mojo/public/cpp/bindings/sync_event_watcher.h
new file mode 100644
index 0000000..6e25484
--- /dev/null
+++ b/mojo/public/cpp/bindings/sync_event_watcher.h
@@ -0,0 +1,68 @@
+// Copyright 2017 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 MOJO_PUBLIC_CPP_BINDINGS_SYNC_EVENT_WATCHER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_SYNC_EVENT_WATCHER_H_
+
+#include <stddef.h>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_checker.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "mojo/public/cpp/bindings/sync_handle_registry.h"
+
+namespace mojo {
+
+// SyncEventWatcher supports waiting on a base::WaitableEvent to signal while
+// also allowing other SyncEventWatchers and SyncHandleWatchers on the same
+// thread to wake up as needed.
+//
+// This class is not thread safe.
+class MOJO_CPP_BINDINGS_EXPORT SyncEventWatcher {
+ public:
+  SyncEventWatcher(base::WaitableEvent* event, const base::Closure& callback);
+
+  ~SyncEventWatcher();
+
+  // Registers |event_| with SyncHandleRegistry, so that when others perform
+  // sync watching on the same thread, |event_| will be watched along with them.
+  void AllowWokenUpBySyncWatchOnSameThread();
+
+  // Waits on |event_| plus all other events and handles registered with this
+  // thread's SyncHandleRegistry, running callbacks synchronously for any ready
+  // events and handles.
+  // This method:
+  //   - returns true when |should_stop| is set to true;
+  //   - return false when any error occurs, including this object being
+  //     destroyed during a callback.
+  bool SyncWatch(const bool* should_stop);
+
+ private:
+  void IncrementRegisterCount();
+  void DecrementRegisterCount();
+
+  base::WaitableEvent* const event_;
+  const base::Closure callback_;
+
+  // Whether |event_| has been registered with SyncHandleRegistry.
+  bool registered_ = false;
+
+  // If non-zero, |event_| should be registered with SyncHandleRegistry.
+  size_t register_request_count_ = 0;
+
+  scoped_refptr<SyncHandleRegistry> registry_;
+
+  scoped_refptr<base::RefCountedData<bool>> destroyed_;
+
+  base::ThreadChecker thread_checker_;
+
+  DISALLOW_COPY_AND_ASSIGN(SyncEventWatcher);
+};
+
+}  // namespace mojo
+
+#endif  // MOJO_PUBLIC_CPP_BINDINGS_SYNC_EVENT_WATCHER_H_
diff --git a/mojo/public/cpp/bindings/sync_handle_registry.h b/mojo/public/cpp/bindings/sync_handle_registry.h
index b5415af..afb3b56 100644
--- a/mojo/public/cpp/bindings/sync_handle_registry.h
+++ b/mojo/public/cpp/bindings/sync_handle_registry.h
@@ -5,14 +5,17 @@
 #ifndef MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_REGISTRY_H_
 #define MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_REGISTRY_H_
 
+#include <map>
 #include <unordered_map>
 
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/threading/thread_checker.h"
 #include "mojo/public/cpp/bindings/bindings_export.h"
 #include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/system/wait_set.h"
 
 namespace mojo {
 
@@ -33,29 +36,30 @@
 
   void UnregisterHandle(const Handle& handle);
 
-  // Waits on all the registered handles and runs callbacks synchronously for
-  // those ready handles.
+  // Registers a |base::WaitableEvent| which can be used to wake up
+  // Wait() before any handle signals. |event| is not owned, and if it signals
+  // during Wait(), |callback| is invoked. Returns |true| if registered
+  // successfully or |false| if |event| was already registered.
+  bool RegisterEvent(base::WaitableEvent* event, const base::Closure& callback);
+
+  void UnregisterEvent(base::WaitableEvent* event);
+
+  // Waits on all the registered handles and events and runs callbacks
+  // synchronously for any that become ready.
   // The method:
   //   - returns true when any element of |should_stop| is set to true;
   //   - returns false when any error occurs.
-  bool WatchAllHandles(const bool* should_stop[], size_t count);
+  bool Wait(const bool* should_stop[], size_t count);
 
  private:
   friend class base::RefCounted<SyncHandleRegistry>;
 
-  struct HandleHasher {
-    size_t operator()(const Handle& handle) const {
-      return std::hash<uint32_t>()(static_cast<uint32_t>(handle.value()));
-    }
-  };
-  using HandleMap = std::unordered_map<Handle, HandleCallback, HandleHasher>;
-
   SyncHandleRegistry();
   ~SyncHandleRegistry();
 
-  HandleMap handles_;
-
-  ScopedHandle wait_set_handle_;
+  WaitSet wait_set_;
+  std::map<Handle, HandleCallback> handles_;
+  std::map<base::WaitableEvent*, base::Closure> events_;
 
   base::ThreadChecker thread_checker_;
 
diff --git a/mojo/public/cpp/bindings/tests/BUILD.gn b/mojo/public/cpp/bindings/tests/BUILD.gn
index 6244226..668ca6d 100644
--- a/mojo/public/cpp/bindings/tests/BUILD.gn
+++ b/mojo/public/cpp/bindings/tests/BUILD.gn
@@ -49,9 +49,9 @@
     "//mojo/public/cpp/test_support:test_utils",
     "//mojo/public/interfaces/bindings/tests:test_associated_interfaces",
     "//mojo/public/interfaces/bindings/tests:test_export_component",
+    "//mojo/public/interfaces/bindings/tests:test_export_component2",
     "//mojo/public/interfaces/bindings/tests:test_exported_import",
     "//mojo/public/interfaces/bindings/tests:test_interfaces",
-    "//mojo/public/interfaces/bindings/tests:test_interfaces_experimental",
     "//mojo/public/interfaces/bindings/tests:test_struct_traits_interfaces",
     "//testing/gtest",
   ]
diff --git a/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc
index 625c49c..be225e4 100644
--- a/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc
@@ -1178,6 +1178,12 @@
   run_loop.Run();
 }
 
+TEST_F(AssociatedInterfaceTest, GetIsolatedInterface) {
+  IntegerSenderAssociatedPtr sender;
+  GetIsolatedInterface(MakeRequest(&sender).PassHandle());
+  sender->Send(42);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc b/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc
index 0c777ec..569eb51 100644
--- a/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/message_loop/message_loop.h"
@@ -30,18 +32,18 @@
                     base::WaitableEvent::InitialState::NOT_SIGNALED) {}
 
   bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
-                                  const base::Closure& task,
+                                  base::OnceClosure task,
                                   base::TimeDelta delay) override {
     NOTREACHED();
     return false;
   }
 
   bool PostDelayedTask(const tracked_objects::Location& from_here,
-                       const base::Closure& task,
+                       base::OnceClosure task,
                        base::TimeDelta delay) override {
     {
       base::AutoLock locker(lock_);
-      tasks_.push(task);
+      tasks_.push(std::move(task));
     }
     task_ready_.Signal();
     return true;
@@ -59,12 +61,12 @@
       {
         base::AutoLock locker(lock_);
         while (!tasks_.empty()) {
-          auto task = tasks_.front();
+          auto task = std::move(tasks_.front());
           tasks_.pop();
 
           {
             base::AutoUnlock unlocker(lock_);
-            task.Run();
+            std::move(task).Run();
             if (quit_called_)
               return;
           }
@@ -87,12 +89,12 @@
       {
         base::AutoLock locker(lock_);
         if (!tasks_.empty()) {
-          auto task = tasks_.front();
+          auto task = std::move(tasks_.front());
           tasks_.pop();
 
           {
             base::AutoUnlock unlocker(lock_);
-            task.Run();
+            std::move(task).Run();
             return;
           }
         }
@@ -110,7 +112,7 @@
 
   // Protect |tasks_|.
   base::Lock lock_;
-  std::queue<base::Closure> tasks_;
+  std::queue<base::OnceClosure> tasks_;
 
   DISALLOW_COPY_AND_ASSIGN(TestTaskRunner);
 };
diff --git a/mojo/public/cpp/bindings/tests/bindings_perftest.cc b/mojo/public/cpp/bindings/tests/bindings_perftest.cc
index 6a50de4..65b3c8c 100644
--- a/mojo/public/cpp/bindings/tests/bindings_perftest.cc
+++ b/mojo/public/cpp/bindings/tests/bindings_perftest.cc
@@ -160,8 +160,9 @@
     return true;
   }
 
-  bool AcceptWithResponder(Message* message,
-                           MessageReceiverWithStatus* responder) override {
+  bool AcceptWithResponder(
+      Message* message,
+      std::unique_ptr<MessageReceiverWithStatus> responder) override {
     NOTREACHED();
     return true;
   }
@@ -232,8 +233,9 @@
     return true;
   }
 
-  bool AcceptWithResponder(Message* message,
-                           MessageReceiverWithStatus* responder) override {
+  bool AcceptWithResponder(
+      Message* message,
+      std::unique_ptr<MessageReceiverWithStatus> responder) override {
     NOTREACHED();
     return true;
   }
diff --git a/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc
index 6797fe4..ef977af 100644
--- a/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/run_loop.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/system/wait.h"
 #include "mojo/public/cpp/test_support/test_utils.h"
 #include "mojo/public/interfaces/bindings/tests/sample_factory.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -111,8 +112,7 @@
 
     MojoHandleSignalsState state;
     ASSERT_EQ(MOJO_RESULT_OK,
-              MojoWait(pipe.get().value(), MOJO_HANDLE_SIGNAL_READABLE,
-                       MOJO_DEADLINE_INDEFINITE, &state));
+              mojo::Wait(pipe.get(), MOJO_HANDLE_SIGNAL_READABLE, &state));
     ASSERT_TRUE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
     ASSERT_EQ(MOJO_RESULT_OK,
               ReadDataRaw(
diff --git a/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc
index 31963e0..8950928 100644
--- a/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc
@@ -72,8 +72,8 @@
   MessageQueue message_queue;
   base::RunLoop run_loop;
   client0.AcceptWithResponder(
-      &request,
-      new MessageAccumulator(&message_queue, run_loop.QuitClosure()));
+      &request, base::MakeUnique<MessageAccumulator>(&message_queue,
+                                                     run_loop.QuitClosure()));
 
   run_loop.Run();
 
@@ -91,8 +91,8 @@
 
   base::RunLoop run_loop2;
   client0.AcceptWithResponder(
-      &request2,
-      new MessageAccumulator(&message_queue, run_loop2.QuitClosure()));
+      &request2, base::MakeUnique<MessageAccumulator>(&message_queue,
+                                                      run_loop2.QuitClosure()));
 
   run_loop2.Run();
 
@@ -117,7 +117,8 @@
   AllocRequestMessage(1, "hello", &request);
 
   MessageQueue message_queue;
-  client0.AcceptWithResponder(&request, new MessageAccumulator(&message_queue));
+  client0.AcceptWithResponder(
+      &request, base::MakeUnique<MessageAccumulator>(&message_queue));
 
   router1_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
   router0_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
@@ -134,8 +135,8 @@
   Message request2;
   AllocRequestMessage(1, "hello again", &request2);
 
-  client0.AcceptWithResponder(&request2,
-                              new MessageAccumulator(&message_queue));
+  client0.AcceptWithResponder(
+      &request2, base::MakeUnique<MessageAccumulator>(&message_queue));
 
   router1_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
   router0_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
@@ -167,8 +168,8 @@
   MessageQueue message_queue;
   base::RunLoop run_loop2;
   client0.AcceptWithResponder(
-      &request,
-      new MessageAccumulator(&message_queue, run_loop2.QuitClosure()));
+      &request, base::MakeUnique<MessageAccumulator>(&message_queue,
+                                                     run_loop2.QuitClosure()));
   run_loop.Run();
 
   // The request has been received but the response has not been sent yet.
@@ -194,8 +195,8 @@
 
   base::RunLoop run_loop4;
   client0.AcceptWithResponder(
-      &request2,
-      new MessageAccumulator(&message_queue, run_loop4.QuitClosure()));
+      &request2, base::MakeUnique<MessageAccumulator>(&message_queue,
+                                                      run_loop4.QuitClosure()));
   run_loop3.Run();
 
   // The request has been received but the response has not been sent yet.
@@ -246,7 +247,8 @@
   AllocRequestMessage(1, "hello", &request);
 
   MessageQueue message_queue;
-  client0.AcceptWithResponder(&request, new MessageAccumulator(&message_queue));
+  client0.AcceptWithResponder(
+      &request, base::MakeUnique<MessageAccumulator>(&message_queue));
   run_loop3.Run();
 
   // The request has been received but no response has been sent.
@@ -293,8 +295,8 @@
     AllocRequestMessage(1, "hello", &request);
 
     MessageQueue message_queue;
-    client0.AcceptWithResponder(&request,
-                                new MessageAccumulator(&message_queue));
+    client0.AcceptWithResponder(
+        &request, base::MakeUnique<MessageAccumulator>(&message_queue));
 
     run_loop.Run();
 
diff --git a/mojo/public/cpp/bindings/tests/router_test_util.cc b/mojo/public/cpp/bindings/tests/router_test_util.cc
index b9b93d8..9bab1cb 100644
--- a/mojo/public/cpp/bindings/tests/router_test_util.cc
+++ b/mojo/public/cpp/bindings/tests/router_test_util.cc
@@ -58,14 +58,13 @@
 
 bool ResponseGenerator::AcceptWithResponder(
     Message* message,
-    MessageReceiverWithStatus* responder) {
+    std::unique_ptr<MessageReceiverWithStatus> responder) {
   EXPECT_TRUE(message->has_flag(Message::kFlagExpectsResponse));
 
   bool result = SendResponse(message->name(), message->request_id(),
                              reinterpret_cast<const char*>(message->payload()),
-                             responder);
+                             responder.get());
   EXPECT_TRUE(responder->IsValid());
-  delete responder;
   return result;
 }
 
@@ -84,18 +83,16 @@
 LazyResponseGenerator::LazyResponseGenerator(const base::Closure& closure)
     : responder_(nullptr), name_(0), request_id_(0), closure_(closure) {}
 
-LazyResponseGenerator::~LazyResponseGenerator() {
-  delete responder_;
-}
+LazyResponseGenerator::~LazyResponseGenerator() = default;
 
 bool LazyResponseGenerator::AcceptWithResponder(
     Message* message,
-    MessageReceiverWithStatus* responder) {
+    std::unique_ptr<MessageReceiverWithStatus> responder) {
   name_ = message->name();
   request_id_ = message->request_id();
   request_string_ =
       std::string(reinterpret_cast<const char*>(message->payload()));
-  responder_ = responder;
+  responder_ = std::move(responder);
   if (!closure_.is_null()) {
     closure_.Run();
     closure_.Reset();
@@ -105,9 +102,8 @@
 
 void LazyResponseGenerator::Complete(bool send_response) {
   if (send_response) {
-    SendResponse(name_, request_id_, request_string_.c_str(), responder_);
+    SendResponse(name_, request_id_, request_string_.c_str(), responder_.get());
   }
-  delete responder_;
   responder_ = nullptr;
 }
 
diff --git a/mojo/public/cpp/bindings/tests/router_test_util.h b/mojo/public/cpp/bindings/tests/router_test_util.h
index c6fb372..dd6aff6 100644
--- a/mojo/public/cpp/bindings/tests/router_test_util.h
+++ b/mojo/public/cpp/bindings/tests/router_test_util.h
@@ -42,9 +42,9 @@
 
   bool Accept(Message* message) override;
 
-  bool AcceptWithResponder(Message* message,
-                           MessageReceiverWithStatus* responder) override;
-
+  bool AcceptWithResponder(
+      Message* message,
+      std::unique_ptr<MessageReceiverWithStatus> responder) override;
   bool SendResponse(uint32_t name,
                     uint64_t request_id,
                     const char* request_string,
@@ -58,8 +58,9 @@
 
   ~LazyResponseGenerator() override;
 
-  bool AcceptWithResponder(Message* message,
-                           MessageReceiverWithStatus* responder) override;
+  bool AcceptWithResponder(
+      Message* message,
+      std::unique_ptr<MessageReceiverWithStatus> responder) override;
 
   bool has_responder() const { return !!responder_; }
 
@@ -78,7 +79,7 @@
   // also sends a response.
   void Complete(bool send_response);
 
-  MessageReceiverWithStatus* responder_;
+  std::unique_ptr<MessageReceiverWithStatus> responder_;
   uint32_t name_;
   uint64_t request_id_;
   std::string request_string_;
diff --git a/mojo/public/cpp/bindings/tests/sample_service_unittest.cc b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc
index 579576f..1f95a27 100644
--- a/mojo/public/cpp/bindings/tests/sample_service_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc
@@ -295,8 +295,9 @@
     return stub.Accept(message);
   }
 
-  bool AcceptWithResponder(mojo::Message* message,
-                           mojo::MessageReceiver* responder) override {
+  bool AcceptWithResponder(
+      mojo::Message* message,
+      std::unique_ptr<mojo::MessageReceiver> responder) override {
     return false;
   }
 };
diff --git a/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc b/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc
index 4c06267..77b448a 100644
--- a/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc
@@ -15,6 +15,7 @@
 #include "mojo/public/cpp/bindings/tests/struct_with_traits_impl.h"
 #include "mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h"
 #include "mojo/public/cpp/bindings/tests/variant_test_util.h"
+#include "mojo/public/cpp/system/wait.h"
 #include "mojo/public/interfaces/bindings/tests/struct_with_traits.mojom.h"
 #include "mojo/public/interfaces/bindings/tests/test_native_types.mojom-blink.h"
 #include "mojo/public/interfaces/bindings/tests/test_native_types.mojom.h"
@@ -391,8 +392,7 @@
             WriteMessageRaw(mp.handle1.get(), kHello, kHelloSize, nullptr, 0,
                             MOJO_WRITE_MESSAGE_FLAG_NONE));
 
-  EXPECT_EQ(MOJO_RESULT_OK, Wait(received.get(), MOJO_HANDLE_SIGNAL_READABLE,
-                                 MOJO_DEADLINE_INDEFINITE, nullptr));
+  EXPECT_EQ(MOJO_RESULT_OK, Wait(received.get(), MOJO_HANDLE_SIGNAL_READABLE));
 
   char buffer[10] = {0};
   uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
diff --git a/mojo/public/cpp/bindings/tests/struct_unittest.cc b/mojo/public/cpp/bindings/tests/struct_unittest.cc
index 13ba507..a687052 100644
--- a/mojo/public/cpp/bindings/tests/struct_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/struct_unittest.cc
@@ -9,6 +9,7 @@
 
 #include "mojo/public/cpp/bindings/lib/fixed_buffer.h"
 #include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/interfaces/bindings/tests/test_export2.mojom.h"
 #include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -435,7 +436,7 @@
 
     // Initialize it to non-null.
     RectPtr output(Rect::New());
-    ASSERT_TRUE(Rect::Deserialize(std::move(data), &output));
+    ASSERT_TRUE(Rect::Deserialize(data, &output));
     EXPECT_TRUE(output.is_null());
   }
 
@@ -446,7 +447,7 @@
     EXPECT_FALSE(data.empty());
 
     EmptyStructPtr output;
-    ASSERT_TRUE(EmptyStruct::Deserialize(std::move(data), &output));
+    ASSERT_TRUE(EmptyStruct::Deserialize(data, &output));
     EXPECT_FALSE(output.is_null());
   }
 
@@ -457,7 +458,7 @@
     auto data = Rect::Serialize(&rect);
 
     RectPtr output;
-    ASSERT_TRUE(Rect::Deserialize(std::move(data), &output));
+    ASSERT_TRUE(Rect::Deserialize(data, &output));
     EXPECT_TRUE(output.Equals(cloned_rect));
   }
 
@@ -475,7 +476,7 @@
     // Make sure that the serialized result gets pointers encoded properly.
     auto cloned_data = data;
     NamedRegionPtr output;
-    ASSERT_TRUE(NamedRegion::Deserialize(std::move(cloned_data), &output));
+    ASSERT_TRUE(NamedRegion::Deserialize(cloned_data, &output));
     EXPECT_TRUE(output.Equals(cloned_region));
   }
 
@@ -485,7 +486,17 @@
     auto data = Rect::Serialize(&rect);
 
     NamedRegionPtr output;
-    EXPECT_FALSE(NamedRegion::Deserialize(std::move(data), &output));
+    EXPECT_FALSE(NamedRegion::Deserialize(data, &output));
+  }
+
+  {
+    // A struct from another component.
+    auto pair = test_export2::StringPair::New("hello", "world");
+    auto data = test_export2::StringPair::Serialize(&pair);
+
+    test_export2::StringPairPtr output;
+    ASSERT_TRUE(test_export2::StringPair::Deserialize(data, &output));
+    EXPECT_TRUE(output.Equals(pair));
   }
 }
 
diff --git a/mojo/public/cpp/bindings/tests/sync_method_unittest.cc b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
index d0e5f10..084e080 100644
--- a/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
@@ -202,6 +202,60 @@
 };
 
 template <typename Interface>
+using ImplTypeFor = typename ImplTraits<Interface>::Type;
+
+// A wrapper for either an InterfacePtr or scoped_refptr<ThreadSafeInterfacePtr>
+// that exposes the InterfacePtr interface.
+template <typename Interface>
+class PtrWrapper {
+ public:
+  explicit PtrWrapper(InterfacePtr<Interface> ptr) : ptr_(std::move(ptr)) {}
+
+  explicit PtrWrapper(
+      scoped_refptr<ThreadSafeInterfacePtr<Interface>> thread_safe_ptr)
+      : thread_safe_ptr_(thread_safe_ptr) {}
+
+  PtrWrapper(PtrWrapper&& other) = default;
+
+  Interface* operator->() {
+    return thread_safe_ptr_ ? thread_safe_ptr_->get() : ptr_.get();
+  }
+
+  void set_connection_error_handler(const base::Closure& error_handler) {
+    DCHECK(!thread_safe_ptr_);
+    ptr_.set_connection_error_handler(error_handler);
+  }
+
+  void reset() {
+    ptr_ = nullptr;
+    thread_safe_ptr_ = nullptr;
+  }
+
+ private:
+  InterfacePtr<Interface> ptr_;
+  scoped_refptr<ThreadSafeInterfacePtr<Interface>> thread_safe_ptr_;
+
+  DISALLOW_COPY_AND_ASSIGN(PtrWrapper);
+};
+
+// The type parameter for SyncMethodCommonTests for varying the Interface and
+// whether to use InterfacePtr or ThreadSafeInterfacePtr.
+template <typename InterfaceT, bool use_thread_safe_ptr>
+struct TestParams {
+  using Interface = InterfaceT;
+  static const bool kIsThreadSafeInterfacePtrTest = use_thread_safe_ptr;
+
+  static PtrWrapper<InterfaceT> Wrap(InterfacePtr<Interface> ptr) {
+    if (kIsThreadSafeInterfacePtrTest) {
+      return PtrWrapper<Interface>(
+          ThreadSafeInterfacePtr<Interface>::Create(std::move(ptr)));
+    } else {
+      return PtrWrapper<Interface>(std::move(ptr));
+    }
+  }
+};
+
+template <typename Interface>
 class TestSyncServiceThread {
  public:
   TestSyncServiceThread()
@@ -211,7 +265,7 @@
 
   void SetUp(InterfaceRequest<Interface> request) {
     CHECK(thread_.task_runner()->BelongsToCurrentThread());
-    impl_.reset(new typename ImplTraits<Interface>::Type(std::move(request)));
+    impl_.reset(new ImplTypeFor<Interface>(std::move(request)));
     impl_->set_ping_handler(
         [this](const typename Interface::PingCallback& callback) {
           {
@@ -236,7 +290,7 @@
  private:
   base::Thread thread_;
 
-  std::unique_ptr<typename ImplTraits<Interface>::Type> impl_;
+  std::unique_ptr<ImplTypeFor<Interface>> impl_;
 
   mutable base::Lock lock_;
   bool ping_called_;
@@ -334,12 +388,20 @@
 
 // TestSync (without associated interfaces) and TestSyncMaster (with associated
 // interfaces) exercise MultiplexRouter with different configurations.
-using InterfaceTypes = testing::Types<TestSync, TestSyncMaster>;
+// Each test is run once with an InterfacePtr and once with a
+// ThreadSafeInterfacePtr to ensure that they behave the same with respect to
+// sync calls.
+using InterfaceTypes = testing::Types<TestParams<TestSync, true>,
+                                      TestParams<TestSync, false>,
+                                      TestParams<TestSyncMaster, true>,
+                                      TestParams<TestSyncMaster, false>>;
 TYPED_TEST_CASE(SyncMethodCommonTest, InterfaceTypes);
 
 TYPED_TEST(SyncMethodCommonTest, CallSyncMethodAsynchronously) {
-  InterfacePtr<TypeParam> ptr;
-  typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr));
+  using Interface = typename TypeParam::Interface;
+  InterfacePtr<Interface> interface_ptr;
+  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
 
   base::RunLoop run_loop;
   ptr->Echo(123, base::Bind(&ExpectValueAndRunClosure, 123,
@@ -348,13 +410,16 @@
 }
 
 TYPED_TEST(SyncMethodCommonTest, BasicSyncCalls) {
-  InterfacePtr<TypeParam> ptr;
+  using Interface = typename TypeParam::Interface;
+  InterfacePtr<Interface> interface_ptr;
+  InterfaceRequest<Interface> request = MakeRequest(&interface_ptr);
+  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
 
-  TestSyncServiceThread<TypeParam> service_thread;
+  TestSyncServiceThread<Interface> service_thread;
   service_thread.thread()->task_runner()->PostTask(
-      FROM_HERE, base::Bind(&TestSyncServiceThread<TypeParam>::SetUp,
-                            base::Unretained(&service_thread),
-                            base::Passed(MakeRequest(&ptr))));
+      FROM_HERE,
+      base::Bind(&TestSyncServiceThread<Interface>::SetUp,
+                 base::Unretained(&service_thread), base::Passed(&request)));
   ASSERT_TRUE(ptr->Ping());
   ASSERT_TRUE(service_thread.ping_called());
 
@@ -364,8 +429,9 @@
 
   base::RunLoop run_loop;
   service_thread.thread()->task_runner()->PostTaskAndReply(
-      FROM_HERE, base::Bind(&TestSyncServiceThread<TypeParam>::TearDown,
-                            base::Unretained(&service_thread)),
+      FROM_HERE,
+      base::Bind(&TestSyncServiceThread<Interface>::TearDown,
+                 base::Unretained(&service_thread)),
       run_loop.QuitClosure());
   run_loop.Run();
 }
@@ -374,9 +440,11 @@
   // Test that an interface pointer waiting for a sync call response can be
   // reentered by a binding serving sync methods on the same thread.
 
-  InterfacePtr<TypeParam> ptr;
+  using Interface = typename TypeParam::Interface;
+  InterfacePtr<Interface> interface_ptr;
   // The binding lives on the same thread as the interface pointer.
-  typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr));
+  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
   int32_t output_value = -1;
   ASSERT_TRUE(ptr->Echo(42, &output_value));
   EXPECT_EQ(42, output_value);
@@ -386,8 +454,10 @@
   // Test that it won't result in crash or hang if an interface pointer is
   // destroyed while it is waiting for a sync call response.
 
-  InterfacePtr<TypeParam> ptr;
-  typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr));
+  using Interface = typename TypeParam::Interface;
+  InterfacePtr<Interface> interface_ptr;
+  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
   impl.set_ping_handler([&ptr](const TestSync::PingCallback& callback) {
     ptr.reset();
     callback.Run();
@@ -400,8 +470,10 @@
   // closed (and therefore the message pipe handle is closed) while the
   // corresponding interface pointer is waiting for a sync call response.
 
-  InterfacePtr<TypeParam> ptr;
-  typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr));
+  using Interface = typename TypeParam::Interface;
+  InterfacePtr<Interface> interface_ptr;
+  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
   impl.set_ping_handler([&impl](const TestSync::PingCallback& callback) {
     impl.binding()->Close();
     callback.Run();
@@ -413,8 +485,10 @@
   // Test that we can call a sync method on an interface ptr, while there is
   // already a sync call ongoing. The responses arrive in order.
 
-  InterfacePtr<TypeParam> ptr;
-  typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr));
+  using Interface = typename TypeParam::Interface;
+  InterfacePtr<Interface> interface_ptr;
+  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
 
   // The same variable is used to store the output of the two sync calls, in
   // order to test that responses are handled in the correct order.
@@ -439,8 +513,10 @@
   // Test that we can call a sync method on an interface ptr, while there is
   // already a sync call ongoing. The responses arrive out of order.
 
-  InterfacePtr<TypeParam> ptr;
-  typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr));
+  using Interface = typename TypeParam::Interface;
+  InterfacePtr<Interface> interface_ptr;
+  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
 
   // The same variable is used to store the output of the two sync calls, in
   // order to test that responses are handled in the correct order.
@@ -465,8 +541,10 @@
   // Test that while an interface pointer is waiting for the response to a sync
   // call, async responses are queued until the sync call completes.
 
-  InterfacePtr<TypeParam> ptr;
-  typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr));
+  using Interface = typename TypeParam::Interface;
+  InterfacePtr<Interface> interface_ptr;
+  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
 
   int32_t async_echo_request_value = -1;
   TestSync::AsyncEchoCallback async_echo_request_callback;
@@ -521,8 +599,10 @@
   // call, async requests for a binding running on the same thread are queued
   // until the sync call completes.
 
-  InterfacePtr<TypeParam> ptr;
-  typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr));
+  using Interface = typename TypeParam::Interface;
+  InterfacePtr<Interface> interface_ptr;
+  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
+  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
 
   bool async_echo_request_dispatched = false;
   impl.set_async_echo_handler([&async_echo_request_dispatched](
@@ -572,8 +652,14 @@
   // before the queued messages are processed, the connection error
   // notification is delayed until all the queued messages are processed.
 
-  InterfacePtr<TypeParam> ptr;
-  typename ImplTraits<TypeParam>::Type impl(MakeRequest(&ptr));
+  // ThreadSafeInterfacePtr doesn't guarantee that messages are delivered before
+  // error notifications, so skip it for this test.
+  if (TypeParam::kIsThreadSafeInterfacePtrTest)
+    return;
+
+  using Interface = typename TypeParam::Interface;
+  InterfacePtr<Interface> ptr;
+  ImplTypeFor<Interface> impl(MakeRequest(&ptr));
 
   int32_t async_echo_request_value = -1;
   TestSync::AsyncEchoCallback async_echo_request_callback;
@@ -648,14 +734,15 @@
   // the sync call to return false, and run the connection error handler
   // asynchronously.
 
+  using Interface = typename TypeParam::Interface;
   MessagePipe pipe;
 
-  InterfacePtr<TypeParam> ptr;
-  ptr.Bind(InterfacePtrInfo<TypeParam>(std::move(pipe.handle0), 0u));
+  InterfacePtr<Interface> interface_ptr;
+  interface_ptr.Bind(InterfacePtrInfo<Interface>(std::move(pipe.handle0), 0u));
+  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
 
   MessagePipeHandle raw_binding_handle = pipe.handle1.get();
-  typename ImplTraits<TypeParam>::Type impl(
-      MakeRequest<TypeParam>(std::move(pipe.handle1)));
+  ImplTypeFor<Interface> impl(MakeRequest<Interface>(std::move(pipe.handle1)));
 
   impl.set_echo_handler([&raw_binding_handle](
       int32_t value, const TestSync::EchoCallback& callback) {
@@ -670,17 +757,22 @@
 
   bool connection_error_dispatched = false;
   base::RunLoop run_loop;
-  ptr.set_connection_error_handler(
-      base::Bind(&SetFlagAndRunClosure, &connection_error_dispatched,
-                 run_loop.QuitClosure()));
+  // ThreadSafeInterfacePtr doesn't support setting connection error handlers.
+  if (!TypeParam::kIsThreadSafeInterfacePtrTest) {
+    ptr.set_connection_error_handler(base::Bind(&SetFlagAndRunClosure,
+                                                &connection_error_dispatched,
+                                                run_loop.QuitClosure()));
+  }
 
   int32_t result_value = -1;
   ASSERT_FALSE(ptr->Echo(456, &result_value));
   EXPECT_EQ(-1, result_value);
   ASSERT_FALSE(connection_error_dispatched);
 
-  run_loop.Run();
-  ASSERT_TRUE(connection_error_dispatched);
+  if (!TypeParam::kIsThreadSafeInterfacePtrTest) {
+    run_loop.Run();
+    ASSERT_TRUE(connection_error_dispatched);
+  }
 }
 
 TEST_F(SyncMethodAssociatedTest, ReenteredBySyncMethodAssoBindingOfSameRouter) {
diff --git a/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc b/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc
index 5028087..dc40143 100644
--- a/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc
@@ -19,7 +19,7 @@
   ASSERT_NE(map.end(), map.find(key));
   ASSERT_EQ(123, map.find(key)->value);
 
-  map.remove(key);
+  map.erase(key);
   ASSERT_EQ(0u, map.size());
 }
 
@@ -32,7 +32,7 @@
   ASSERT_NE(map.end(), map.find(key));
   ASSERT_EQ(123, map.find(key)->value);
 
-  map.remove(key);
+  map.erase(key);
   ASSERT_EQ(0u, map.size());
 }
 
diff --git a/mojo/public/cpp/bindings/thread_safe_interface_ptr.h b/mojo/public/cpp/bindings/thread_safe_interface_ptr.h
index bab6d22..740687f 100644
--- a/mojo/public/cpp/bindings/thread_safe_interface_ptr.h
+++ b/mojo/public/cpp/bindings/thread_safe_interface_ptr.h
@@ -10,12 +10,26 @@
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
+#include "base/stl_util.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "mojo/public/cpp/bindings/associated_group.h"
 #include "mojo/public/cpp/bindings/associated_interface_ptr.h"
 #include "mojo/public/cpp/bindings/interface_ptr.h"
 #include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
+#include "mojo/public/cpp/bindings/sync_event_watcher.h"
+
+// ThreadSafeInterfacePtr wraps a non-thread-safe InterfacePtr and proxies
+// messages to it. Async calls are posted to the thread that the InteracePtr is
+// bound to, and the responses are posted back. Sync calls are dispatched
+// directly if the call is made on the thread that the wrapped InterfacePtr is
+// bound to, or posted otherwise. It's important to be aware that sync calls
+// block both the calling thread and the InterfacePtr thread. That means that
+// you cannot make sync calls through a ThreadSafeInterfacePtr if the
+// underlying InterfacePtr is bound to a thread that cannot block, like the IO
+// thread.
 
 namespace mojo {
 
@@ -46,9 +60,15 @@
         task_runner_(task_runner),
         forward_(forward),
         forward_with_responder_(forward_with_responder),
-        associated_group_(associated_group) {}
+        associated_group_(associated_group),
+        sync_calls_(new InProgressSyncCalls()) {}
 
-  ~ThreadSafeForwarder() override {}
+  ~ThreadSafeForwarder() override {
+    // If there are ongoing sync calls signal their completion now.
+    base::AutoLock l(sync_calls_->lock);
+    for (const auto& pending_response : sync_calls_->pending_responses)
+      pending_response->event.Signal();
+  }
 
   ProxyType& proxy() { return proxy_; }
 
@@ -73,35 +93,130 @@
     return true;
   }
 
-  bool AcceptWithResponder(Message* message,
-                           MessageReceiver* response_receiver) override {
+  bool AcceptWithResponder(
+      Message* message,
+      std::unique_ptr<MessageReceiver> responder) override {
     if (!message->associated_endpoint_handles()->empty()) {
       // Please see comment for the DCHECK in the previous method.
       DCHECK(associated_group_.GetController());
       message->SerializeAssociatedEndpointHandles(
           associated_group_.GetController());
     }
-    auto responder = base::MakeUnique<ForwardToCallingThread>(
-        base::WrapUnique(response_receiver));
+
+    // Async messages are always posted (even if |task_runner_| runs tasks on
+    // this thread) to guarantee that two async calls can't be reordered.
+    if (!message->has_flag(Message::kFlagIsSync)) {
+      auto reply_forwarder =
+          base::MakeUnique<ForwardToCallingThread>(std::move(responder));
+      task_runner_->PostTask(
+          FROM_HERE, base::Bind(forward_with_responder_, base::Passed(message),
+                                base::Passed(&reply_forwarder)));
+      return true;
+    }
+
+    SyncCallRestrictions::AssertSyncCallAllowed();
+
+    // If the InterfacePtr is bound to this thread, dispatch it directly.
+    if (task_runner_->RunsTasksOnCurrentThread()) {
+      forward_with_responder_.Run(std::move(*message), std::move(responder));
+      return true;
+    }
+
+    // If the InterfacePtr is bound on another thread, post the call.
+    // TODO(yzshen, watk): We block both this thread and the InterfacePtr
+    // thread. Ideally only this thread would block.
+    auto response = make_scoped_refptr(new SyncResponseInfo());
+    auto response_signaler = base::MakeUnique<SyncResponseSignaler>(response);
     task_runner_->PostTask(
         FROM_HERE, base::Bind(forward_with_responder_, base::Passed(message),
-                              base::Passed(&responder)));
+                              base::Passed(&response_signaler)));
+
+    // Save the pending SyncResponseInfo so that if the sync call deletes
+    // |this|, we can signal the completion of the call to return from
+    // SyncWatch().
+    auto sync_calls = sync_calls_;
+    {
+      base::AutoLock l(sync_calls->lock);
+      sync_calls->pending_responses.push_back(response.get());
+    }
+
+    auto assign_true = [](bool* b) { *b = true; };
+    bool event_signaled = false;
+    SyncEventWatcher watcher(&response->event,
+                             base::Bind(assign_true, &event_signaled));
+    watcher.SyncWatch(&event_signaled);
+
+    {
+      base::AutoLock l(sync_calls->lock);
+      base::Erase(sync_calls->pending_responses, response.get());
+    }
+
+    if (response->received)
+      ignore_result(responder->Accept(&response->message));
+
     return true;
   }
 
+  // Data that we need to share between the threads involved in a sync call.
+  struct SyncResponseInfo
+      : public base::RefCountedThreadSafe<SyncResponseInfo> {
+    Message message;
+    bool received = false;
+    base::WaitableEvent event{base::WaitableEvent::ResetPolicy::MANUAL,
+                              base::WaitableEvent::InitialState::NOT_SIGNALED};
+
+   private:
+    friend class base::RefCountedThreadSafe<SyncResponseInfo>;
+  };
+
+  // A MessageReceiver that signals |response| when it either accepts the
+  // response message, or is destructed.
+  class SyncResponseSignaler : public MessageReceiver {
+   public:
+    explicit SyncResponseSignaler(scoped_refptr<SyncResponseInfo> response)
+        : response_(response) {}
+
+    ~SyncResponseSignaler() override {
+      // If Accept() was not called we must still notify the waiter that the
+      // sync call is finished.
+      if (response_)
+        response_->event.Signal();
+    }
+
+    bool Accept(Message* message) {
+      response_->message = std::move(*message);
+      response_->received = true;
+      response_->event.Signal();
+      response_ = nullptr;
+      return true;
+    }
+
+   private:
+    scoped_refptr<SyncResponseInfo> response_;
+  };
+
+  // A record of the pending sync responses for canceling pending sync calls
+  // when the owning ThreadSafeForwarder is destructed.
+  struct InProgressSyncCalls
+      : public base::RefCountedThreadSafe<InProgressSyncCalls> {
+    // |lock| protects access to |pending_responses|.
+    base::Lock lock;
+    std::vector<SyncResponseInfo*> pending_responses;
+  };
+
   class ForwardToCallingThread : public MessageReceiver {
    public:
     explicit ForwardToCallingThread(std::unique_ptr<MessageReceiver> responder)
         : responder_(std::move(responder)),
-          caller_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
-    }
+          caller_task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
 
    private:
     bool Accept(Message* message) {
       // The current instance will be deleted when this method returns, so we
       // have to relinquish the responder's ownership so it does not get
       // deleted.
-      caller_task_runner_->PostTask(FROM_HERE,
+      caller_task_runner_->PostTask(
+          FROM_HERE,
           base::Bind(&ForwardToCallingThread::CallAcceptAndDeleteResponder,
                      base::Passed(std::move(responder_)),
                      base::Passed(std::move(*message))));
@@ -123,6 +238,7 @@
   const ForwardMessageCallback forward_;
   const ForwardMessageWithResponderCallback forward_with_responder_;
   AssociatedGroup associated_group_;
+  scoped_refptr<InProgressSyncCalls> sync_calls_;
 
   DISALLOW_COPY_AND_ASSIGN(ThreadSafeForwarder);
 };
diff --git a/mojo/public/cpp/system/BUILD.gn b/mojo/public/cpp/system/BUILD.gn
index 0dc7af9..35087ef 100644
--- a/mojo/public/cpp/system/BUILD.gn
+++ b/mojo/public/cpp/system/BUILD.gn
@@ -29,11 +29,18 @@
     "data_pipe.h",
     "functions.h",
     "handle.h",
+    "handle_signals_state.h",
     "message.h",
     "message_pipe.h",
     "platform_handle.cc",
     "platform_handle.h",
+    "simple_watcher.cc",
+    "simple_watcher.h",
     "system_export.h",
+    "wait.cc",
+    "wait.h",
+    "wait_set.cc",
+    "wait_set.h",
     "watcher.cc",
     "watcher.h",
   ]
diff --git a/mojo/public/cpp/system/README.md b/mojo/public/cpp/system/README.md
new file mode 100644
index 0000000..782744f
--- /dev/null
+++ b/mojo/public/cpp/system/README.md
@@ -0,0 +1,396 @@
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C++ System API
+This document is a subset of the [Mojo documentation](/mojo).
+
+[TOC]
+
+## Overview
+The Mojo C++ System API provides a convenient set of helper classes and
+functions for working with Mojo primitives. Unlike the low-level
+[C API](/mojo/public/c/system) (upon which this is built) this library takes
+advantage of C++ language features and common STL and `//base` types to provide
+a slightly more idiomatic interface to the Mojo system layer, making it
+generally easier to use.
+
+This document provides a brief guide to API usage with example code snippets.
+For a detailed API references please consult the headers in
+[//mojo/public/cpp/system](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/).
+
+Note that all API symbols referenced in this document are implicitly in the
+top-level `mojo` namespace.
+
+## Scoped, Typed Handles
+
+All types of Mojo handles in the C API are simply opaque, integral `MojoHandle`
+values. The C++ API has more strongly typed wrappers defined for different
+handle types: `MessagePipeHandle`, `SharedBufferHandle`,
+`DataPipeConsumerHandle`, `DataPipeProducerHandle`, and `WatcherHandle`.
+
+Each of these also has a corresponding, move-only, scoped type for safer usage:
+`ScopedMessagePipeHandle`, `ScopedSharedBufferHandle`, and so on. When a scoped
+handle type is destroyed, its handle is automatically closed via `MojoClose`.
+When working with raw handles you should **always** prefer to use one of the
+scoped types for ownership.
+
+Similar to `std::unique_ptr`, scoped handle types expose a `get()` method to get
+at the underlying unscoped handle type as well as the `->` operator to
+dereference the scoper and make calls directly on the underlying handle type.
+
+## Message Pipes
+
+There are two ways to create a new message pipe using the C++ API. You may
+construct a `MessagePipe` object:
+
+``` cpp
+mojo::MessagePipe pipe;
+
+// NOTE: Because pipes are bi-directional there is no implicit semantic
+// difference between |handle0| or |handle1| here. They're just two ends of a
+// pipe. The choice to treat one as a "client" and one as a "server" is entirely
+// a the API user's decision.
+mojo::ScopedMessagePipeHandle client = std::move(pipe.handle0);
+mojo::ScopedMessagePipeHandle server = std::move(pipe.handle1);
+```
+
+or you may call `CreateMessagePipe`:
+
+``` cpp
+mojo::ScopedMessagePipeHandle client;
+mojo::ScopedMessagePipeHandle server;
+mojo::CreateMessagePipe(nullptr, &client, &server);
+```
+
+There are also some helper functions for constructing message objects and
+reading/writing them on pipes using the library's more strongly-typed C++
+handles:
+
+``` cpp
+mojo::ScopedMessageHandle message;
+mojo::AllocMessage(6, nullptr, 0, MOJO_ALLOC_MESSAGE_FLAG_NONE, &message);
+
+void *buffer;
+mojo::GetMessageBuffer(message.get(), &buffer);
+
+const std::string kMessage = "hello";
+std::copy(kMessage.begin(), kMessage.end(), static_cast<char*>(buffer));
+
+mojo::WriteMessageNew(client.get(), std::move(message),
+                      MOJO_WRITE_MESSAGE_FLAG_NONE);
+
+// Some time later...
+
+mojo::ScopedMessageHandle received_message;
+uint32_t num_bytes;
+mojo::ReadMessageNew(server.get(), &received_message, &num_bytes, nullptr,
+                     nullptr, MOJO_READ_MESSAGE_FLAG_NONE);
+```
+
+See [message_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/message_pipe.h)
+for detailed C++ message pipe API documentation.
+
+## Data Pipes
+
+Similar to [Message Pipes](#Message-Pipes), the C++ library has some simple
+helpers for more strongly-typed data pipe usage:
+
+``` cpp
+mojo::DataPipe pipe;
+mojo::ScopedDataPipeProducerHandle producer = std::move(pipe.producer);
+mojo::ScopedDataPipeConsumerHandle consumer = std::move(pipe.consumer);
+
+// Or alternatively:
+mojo::ScopedDataPipeProducerHandle producer;
+mojo::ScopedDataPipeConsumerHandle consumer;
+mojo::CreateDataPipe(null, &producer, &consumer);
+```
+
+// Reads from a data pipe. See |MojoReadData()| for complete documentation.
+inline MojoResult ReadDataRaw(DataPipeConsumerHandle data_pipe_consumer,
+                              void* elements,
+                              uint32_t* num_bytes,
+                              MojoReadDataFlags flags) {
+  return MojoReadData(data_pipe_consumer.value(), elements, num_bytes, flags);
+}
+
+// Begins a two-phase read
+C++ helpers which correspond directly to the
+[Data Pipe C API](/mojo/public/c/system#Data-Pipes) for immediate and two-phase
+I/O are provided as well. For example:
+
+``` cpp
+uint32_t num_bytes = 7;
+mojo::WriteDataRaw(producer.get(), "hihihi",
+                   &num_bytes, MOJO_WRITE_DATA_FLAG_NONE);
+
+// Some time later...
+
+char buffer[64];
+uint32_t num_bytes = 64;
+mojo::ReadDataRaw(consumer.get(), buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
+```
+
+See [data_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/data_pipe.h)
+for detailed C++ data pipe API documentation.
+
+## Shared Buffers
+
+A new shared buffers can be allocated like so:
+
+``` cpp
+mojo::ScopedSharedBufferHandle buffer =
+    mojo::ScopedSharedBufferHandle::Create(4096);
+```
+
+This new handle can be cloned arbitrarily many times by using the underlying
+handle's `Clone` method:
+
+``` cpp
+mojo::ScopedSharedBufferHandle another_handle = buffer->Clone();
+mojo::ScopedSharedBufferHandle read_only_handle =
+    buffer->Clone(mojo::SharedBufferHandle::AccessMode::READ_ONLY);
+```
+
+And finally the library also provides a scoper for mapping the shared buffer's
+memory:
+
+``` cpp
+mojo::ScopedSharedBufferMapping mapping = buffer->Map(64);
+static_cast<int*>(mapping.get()) = 42;
+
+mojo::ScopedSharedBufferMapping another_mapping = buffer->MapAtOffset(64, 4);
+static_cast<int*>(mapping.get()) = 43;
+```
+
+When `mapping` and `another_mapping` are destroyed, they automatically unmap
+their respective memory regions.
+
+See [buffer.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/buffer.h)
+for detailed C++ shared buffer API documentation.
+
+## Native Platform Handles (File Descriptors, Windows Handles, *etc.*)
+
+The C++ library provides several helpers for wrapping system handle types.
+These are specifically useful when working with a few `//base` types, namely
+`base::PlatformFile` and `base::SharedMemoryHandle`. See
+[platform_handle.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/platform_handle.h)
+for detailed C++ platform handle API documentation.
+
+## Signals & Watchers
+
+For an introduction to the concepts of handle signals and watchers, check out
+the C API's documentation on [Signals & Watchers](/mojo/public/c/system#Signals-Watchers).
+
+### Querying Signals
+
+Any C++ handle type's last known signaling state can be queried by calling the
+`QuerySignalsState` method on the handle:
+
+``` cpp
+mojo::MessagePipe message_pipe;
+mojo::DataPipe data_pipe;
+mojo::HandleSignalsState a = message_pipe.handle0->QuerySignalsState();
+mojo::HandleSignalsState b = data_pipe.consumer->QuerySignalsState();
+```
+
+The `HandleSignalsState` is a thin wrapper interface around the C API's
+`MojoHandleSignalsState` structure with convenient accessors for testing
+the signal bitmasks. Whereas when using the C API you might write:
+
+``` c
+struct MojoHandleSignalsState state;
+MojoQueryHandleSignalsState(handle0, &state);
+if (state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE) {
+  // ...
+}
+```
+
+the C++ API equivalent would be:
+
+``` cpp
+if (message_pipe.handle0->QuerySignalsState().readable()) {
+  // ...
+}
+```
+
+### Watching Handles
+
+The [`mojo::SimpleWatcher`](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/simple_watcher.h)
+class serves as a convenient helper for using the [low-level watcher API](/mojo/public/c/system#Signals-Watchers)
+to watch a handle for signaling state changes. A `SimpleWatcher` is bound to a
+single thread and always dispatches its notifications on a
+`base::SingleThreadTaskRunner`.
+
+`SimpleWatcher` has two possible modes of operation, selected at construction
+time by the `mojo::SimpleWatcher::ArmingPolicy` enum:
+
+* `MANUAL` mode requires the user to manually call `Arm` and/or `ArmOrNotify`
+  before any notifications will fire regarding the state of the watched handle.
+  Every time the notification callback is run, the `SimpleWatcher` must be
+  rearmed again before the next one can fire. See 
+  [Arming a Watcher](/mojo/public/c/system#Arming-a-Watcher) and the
+  documentation in `SimpleWatcher`'s header.
+
+* `AUTOMATIC` mode ensures that the `SimpleWatcher` always either is armed or
+  has a pending notification task queued for execution.
+
+`AUTOMATIC` mode is more convenient but can result in redundant notification
+tasks, especially if the provided callback does not make a strong effort to
+return the watched handle to an uninteresting signaling state (by *e.g.*,
+reading all its available messages when notified of readability.)
+
+Example usage:
+
+``` cpp
+class PipeReader {
+ public:
+  PipeReader(mojo::ScopedMessagePipeHandle pipe)
+      : pipe_(std::move(pipe)),
+        watcher_(mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC) {
+    // NOTE: base::Unretained is safe because the callback can never be run
+    // after SimpleWatcher destruction.
+    watcher_.Watch(pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE,
+                   base::Bind(&PipeReader::OnReadable, base::Unretained(this)));
+  }
+
+  ~PipeReader() {}
+
+ private:
+  void OnReadable(MojoResult result) {
+    while (result == MOJO_RESULT_OK) {
+      mojo::ScopedMessageHandle message;
+      uint32_t num_bytes;
+      result = mojo::ReadMessageNew(pipe_.get(), &message, &num_bytes, nullptr,
+                                    nullptr, MOJO_READ_MESSAGE_FLAG_NONE);
+      DCHECK_EQ(result, MOJO_RESULT_OK);
+      messages_.emplace_back(std::move(message));
+    }
+  }
+
+  mojo::ScopedMessagePipeHandle pipe_;
+  mojo::SimpleWatcher watcher_;
+  std::vector<mojo::ScopedMessageHandle> messages_;
+};
+
+mojo::MessagePipe pipe;
+PipeReader reader(std::move(pipe.handle0));
+
+// Written messages will asynchronously end up in |reader.messages_|.
+WriteABunchOfStuff(pipe.handle1.get());
+```
+
+## Synchronous Waiting
+
+The C++ System API defines some utilities to block a calling thread while
+waiting for one or more handles to change signaling state in an interesting way.
+These threads combine usage of the [low-level Watcher API](/mojo/public/c/system#Signals-Watchers)
+with common synchronization primitives (namely `base::WaitableEvent`.)
+
+While these API features should be used sparingly, they are sometimes necessary.
+
+See the documentation in 
+[wait.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait.h)
+and [wait_set.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait_set.h)
+for a more detailed API reference.
+
+### Waiting On a Single Handle
+
+The `mojo::Wait` function simply blocks the calling thread until a given signal
+mask is either partially satisfied or fully unsatisfiable on a given handle.
+
+``` cpp
+mojo::MessagePipe pipe;
+mojo::WriteMessageRaw(pipe.handle0.get(), "hey", 3, nullptr, nullptr,
+                      MOJO_WRITE_MESSAGE_FLAG_NONE);
+MojoResult result = mojo::Wait(pipe.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+DCHECK_EQ(result, MOJO_RESULT_OK);
+
+// Guaranteed to succeed because we know |handle1| is readable now.
+mojo::ScopedMessageHandle message;
+uint32_t num_bytes;
+mojo::ReadMessageNew(pipe.handle1.get(), &num_bytes, nullptr, nullptr,
+                     MOJO_READ_MESSAGE_FLAG_NONE);
+```
+
+`mojo::Wait` is most typically useful in limited testing scenarios.
+
+### Waiting On Multiple Handles
+
+`mojo::WaitMany` provides a simple API to wait on multiple handles
+simultaneously, returning when any handle's given signal mask is either
+partially satisfied or fully unsatisfiable.
+
+``` cpp
+mojo::MessagePipe a, b;
+GoDoSomethingWithPipes(std:move(a.handle1), std::move(b.handle1));
+
+mojo::MessagePipeHandle handles[2] = {a.handle0.get(), b.handle0.get()};
+MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE,
+                                MOJO_HANDLE_SIGNAL_READABLE};
+size_t ready_index;
+MojoResult result = mojo::WaitMany(handles, signals, 2, &ready_index);
+if (ready_index == 0) {
+  // a.handle0 was ready.
+} else {
+  // b.handle0 was ready.
+}
+```
+
+Similar to `mojo::Wait`, `mojo::WaitMany` is primarily useful in testing. When
+waiting on multiple handles in production code, you should almost always instead 
+use a more efficient and more flexible `mojo::WaitSet` as described in the next
+section.
+
+### Waiting On Handles and Events Simultaneously
+
+Typically when waiting on one or more handles to signal, the set of handles and
+conditions being waited upon do not change much between consecutive blocking
+waits. It's also often useful to be able to interrupt the blocking operation
+as efficiently as possible.
+
+[`mojo::WaitSet`](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait_set.h)
+is designed with these conditions in mind. A `WaitSet` maintains a persistent
+set of (not-owned) Mojo handles and `base::WaitableEvent`s, which may be
+explicitly added to or removed from the set at any time.
+
+The `WaitSet` may be waited upon repeatedly, each time blocking the calling
+thread until either one of the handles attains an interesting signaling state or
+one of the events is signaled. For example let's suppose we want to wait up to 5
+seconds for either one of two handles to become readable:
+
+``` cpp
+base::WaitableEvent timeout_event(
+    base::WaitableEvent::ResetPolicy::MANUAL,
+    base::WaitableEvent::InitialState::NOT_SIGNALED);
+mojo::MessagePipe a, b;
+
+GoDoStuffWithPipes(std::move(a.handle1), std::move(b.handle1));
+
+mojo::WaitSet wait_set;
+wait_set.AddHandle(a.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+wait_set.AddHandle(b.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+wait_set.AddEvent(&timeout_event);
+
+// Ensure the Wait() lasts no more than 5 seconds.
+bg_thread->task_runner()->PostDelayedTask(
+    FROM_HERE,
+    base::Bind([](base::WaitableEvent* e) { e->Signal(); }, &timeout_event);
+    base::TimeDelta::FromSeconds(5));
+
+base::WaitableEvent* ready_event = nullptr;
+size_t num_ready_handles = 1;
+mojo::Handle ready_handle;
+MojoResult ready_result;
+wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result);
+
+// The apex of thread-safety.
+bg_thread->Stop();
+
+if (ready_event) {
+  // The event signaled...
+}
+
+if (num_ready_handles > 0) {
+  // At least one of the handles signaled...
+  // NOTE: This and the above condition are not mutually exclusive. If handle
+  // signaling races with timeout, both things might be true.
+}
+```
diff --git a/mojo/public/cpp/system/functions.h b/mojo/public/cpp/system/functions.h
index 9cfe316..31edf57 100644
--- a/mojo/public/cpp/system/functions.h
+++ b/mojo/public/cpp/system/functions.h
@@ -21,12 +21,6 @@
   return MojoGetTimeTicksNow();
 }
 
-// The C++ wrappers for |MojoWait()| and |MojoWaitMany()| are defined in
-// "handle.h".
-// TODO(ggowan): Consider making the C and C++ APIs more consistent in the
-// organization of the functions into different header files (since in the C
-// API, those functions are defined in "functions.h").
-
 }  // namespace mojo
 
 #endif  // MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_
diff --git a/mojo/public/cpp/system/handle.h b/mojo/public/cpp/system/handle.h
index 5b2eb7b..781944e 100644
--- a/mojo/public/cpp/system/handle.h
+++ b/mojo/public/cpp/system/handle.h
@@ -13,6 +13,7 @@
 #include "base/macros.h"
 #include "mojo/public/c/system/functions.h"
 #include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/handle_signals_state.h"
 
 namespace mojo {
 
@@ -170,6 +171,14 @@
     DCHECK_EQ(MOJO_RESULT_OK, result);
   }
 
+  HandleSignalsState QuerySignalsState() const {
+    HandleSignalsState signals_state;
+    MojoResult result = MojoQueryHandleSignalsState(
+        value_, static_cast<MojoHandleSignalsState*>(&signals_state));
+    DCHECK_EQ(MOJO_RESULT_OK, result);
+    return signals_state;
+  }
+
  private:
   MojoHandle value_;
 
@@ -184,103 +193,6 @@
 static_assert(sizeof(ScopedHandle) == sizeof(Handle),
               "Bad size for C++ ScopedHandle");
 
-inline MojoResult Wait(Handle handle,
-                       MojoHandleSignals signals,
-                       MojoDeadline deadline,
-                       MojoHandleSignalsState* signals_state) {
-  return MojoWait(handle.value(), signals, deadline, signals_state);
-}
-
-const uint32_t kInvalidWaitManyIndexValue = static_cast<uint32_t>(-1);
-
-// Simplify the interpretation of the output from |MojoWaitMany()|.
-class WaitManyResult {
- public:
-  explicit WaitManyResult(MojoResult mojo_wait_many_result)
-      : result(mojo_wait_many_result), index(kInvalidWaitManyIndexValue) {}
-
-  WaitManyResult(MojoResult mojo_wait_many_result, uint32_t result_index)
-      : result(mojo_wait_many_result), index(result_index) {}
-
-  // A valid handle index is always returned if |WaitMany()| succeeds, but may
-  // or may not be returned if |WaitMany()| returns an error. Use this helper
-  // function to check if |index| is a valid index into the handle array.
-  bool IsIndexValid() const { return index != kInvalidWaitManyIndexValue; }
-
-  // The |signals_states| array is always returned by |WaitMany()| on success,
-  // but may or may not be returned if |WaitMany()| returns an error. Use this
-  // helper function to check if |signals_states| holds valid data.
-  bool AreSignalsStatesValid() const {
-    return result != MOJO_RESULT_INVALID_ARGUMENT &&
-           result != MOJO_RESULT_RESOURCE_EXHAUSTED;
-  }
-
-  MojoResult result;
-  uint32_t index;
-};
-
-// |HandleVectorType| and |FlagsVectorType| should be similar enough to
-// |std::vector<Handle>| and |std::vector<MojoHandleSignals>|, respectively:
-//  - They should have a (const) |size()| method that returns an unsigned type.
-//  - They must provide contiguous storage, with access via (const) reference to
-//    that storage provided by a (const) |operator[]()| (by reference).
-template <class HandleVectorType,
-          class FlagsVectorType,
-          class SignalsStateVectorType>
-inline WaitManyResult WaitMany(const HandleVectorType& handles,
-                               const FlagsVectorType& signals,
-                               MojoDeadline deadline,
-                               SignalsStateVectorType* signals_states) {
-  if (signals.size() != handles.size() ||
-      (signals_states && signals_states->size() != signals.size()))
-    return WaitManyResult(MOJO_RESULT_INVALID_ARGUMENT);
-  if (handles.size() >= kInvalidWaitManyIndexValue)
-    return WaitManyResult(MOJO_RESULT_RESOURCE_EXHAUSTED);
-
-  if (handles.size() == 0) {
-    return WaitManyResult(
-        MojoWaitMany(nullptr, nullptr, 0, deadline, nullptr, nullptr));
-  }
-
-  uint32_t result_index = kInvalidWaitManyIndexValue;
-  const Handle& first_handle = handles[0];
-  const MojoHandleSignals& first_signals = signals[0];
-  MojoHandleSignalsState* first_state =
-      signals_states ? &(*signals_states)[0] : nullptr;
-  MojoResult result =
-      MojoWaitMany(reinterpret_cast<const MojoHandle*>(&first_handle),
-                   &first_signals, static_cast<uint32_t>(handles.size()),
-                   deadline, &result_index, first_state);
-  return WaitManyResult(result, result_index);
-}
-
-// C++ 4.10, regarding pointer conversion, says that an integral null pointer
-// constant can be converted to |std::nullptr_t| (which is a typedef for
-// |decltype(nullptr)|). The opposite direction is not allowed.
-template <class HandleVectorType, class FlagsVectorType>
-inline WaitManyResult WaitMany(const HandleVectorType& handles,
-                               const FlagsVectorType& signals,
-                               MojoDeadline deadline,
-                               decltype(nullptr) signals_states) {
-  if (signals.size() != handles.size())
-    return WaitManyResult(MOJO_RESULT_INVALID_ARGUMENT);
-  if (handles.size() >= kInvalidWaitManyIndexValue)
-    return WaitManyResult(MOJO_RESULT_RESOURCE_EXHAUSTED);
-
-  if (handles.size() == 0) {
-    return WaitManyResult(
-        MojoWaitMany(nullptr, nullptr, 0, deadline, nullptr, nullptr));
-  }
-
-  uint32_t result_index = kInvalidWaitManyIndexValue;
-  const Handle& first_handle = handles[0];
-  const MojoHandleSignals& first_signals = signals[0];
-  MojoResult result = MojoWaitMany(
-      reinterpret_cast<const MojoHandle*>(&first_handle), &first_signals,
-      static_cast<uint32_t>(handles.size()), deadline, &result_index, nullptr);
-  return WaitManyResult(result, result_index);
-}
-
 // |Close()| takes ownership of the handle, since it'll invalidate it.
 // Note: There's nothing to do, since the argument will be destroyed when it
 // goes out of scope.
diff --git a/mojo/public/cpp/system/handle_signals_state.h b/mojo/public/cpp/system/handle_signals_state.h
new file mode 100644
index 0000000..9e2430f
--- /dev/null
+++ b/mojo/public/cpp/system/handle_signals_state.h
@@ -0,0 +1,83 @@
+// Copyright 2017 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 MOJO_PUBLIC_CPP_SYSTEM_HANDLE_SIGNALS_STATE_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_HANDLE_SIGNALS_STATE_H_
+
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace mojo {
+
+// A convenience wrapper around the MojoHandleSignalsState struct.
+struct MOJO_CPP_SYSTEM_EXPORT HandleSignalsState final
+    : public MojoHandleSignalsState {
+  HandleSignalsState() {
+    satisfied_signals = MOJO_HANDLE_SIGNAL_NONE;
+    satisfiable_signals = MOJO_HANDLE_SIGNAL_NONE;
+  }
+
+  HandleSignalsState(MojoHandleSignals satisfied,
+                     MojoHandleSignals satisfiable) {
+    satisfied_signals = satisfied;
+    satisfiable_signals = satisfiable;
+  }
+
+  bool operator==(const HandleSignalsState& other) const {
+    return satisfied_signals == other.satisfied_signals &&
+           satisfiable_signals == other.satisfiable_signals;
+  }
+
+  // TODO(rockot): Remove this in favor of operator==.
+  bool equals(const HandleSignalsState& other) const {
+    return satisfied_signals == other.satisfied_signals &&
+           satisfiable_signals == other.satisfiable_signals;
+  }
+
+  bool satisfies(MojoHandleSignals signals) const {
+    return !!(satisfied_signals & signals);
+  }
+
+  bool can_satisfy(MojoHandleSignals signals) const {
+    return !!(satisfiable_signals & signals);
+  }
+
+  // The handle is currently readable. May apply to a message pipe handle or
+  // data pipe consumer handle.
+  bool readable() const { return satisfies(MOJO_HANDLE_SIGNAL_READABLE); }
+
+  // The handle is currently writable. May apply to a message pipe handle or
+  // data pipe producer handle.
+  bool writable() const { return satisfies(MOJO_HANDLE_SIGNAL_WRITABLE); }
+
+  // The handle's peer is closed. May apply to any message pipe or data pipe
+  // handle.
+  bool peer_closed() const { return satisfies(MOJO_HANDLE_SIGNAL_PEER_CLOSED); }
+
+  // The handle will never be |readable()| again.
+  bool never_readable() const {
+    return !can_satisfy(MOJO_HANDLE_SIGNAL_READABLE);
+  }
+
+  // The handle will never be |writable()| again.
+  bool never_writable() const {
+    return !can_satisfy(MOJO_HANDLE_SIGNAL_WRITABLE);
+  }
+
+  // The handle can never indicate |peer_closed()|. Never true for message pipe
+  // or data pipe handles (they can always signal peer closure), but always true
+  // for other types of handles (they have no peer.)
+  bool never_peer_closed() const {
+    return !can_satisfy(MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+  }
+
+  // (Copy and assignment allowed.)
+};
+
+static_assert(sizeof(HandleSignalsState) == sizeof(MojoHandleSignalsState),
+              "HandleSignalsState should add no overhead");
+
+}  // namespace mojo
+
+#endif  // MOJO_PUBLIC_CPP_SYSTEM_HANDLE_SIGNALS_STATE_H_
diff --git a/mojo/public/cpp/system/simple_watcher.cc b/mojo/public/cpp/system/simple_watcher.cc
new file mode 100644
index 0000000..ae96faa
--- /dev/null
+++ b/mojo/public/cpp/system/simple_watcher.cc
@@ -0,0 +1,279 @@
+// Copyright 2017 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 "mojo/public/cpp/system/simple_watcher.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/trace_event/heap_profiler.h"
+#include "mojo/public/c/system/watcher.h"
+
+namespace mojo {
+
+// Thread-safe Context object used to dispatch watch notifications from a
+// arbitrary threads.
+class SimpleWatcher::Context : public base::RefCountedThreadSafe<Context> {
+ public:
+  // Creates a |Context| instance for a new watch on |watcher|, to watch
+  // |handle| for |signals|.
+  static scoped_refptr<Context> Create(
+      base::WeakPtr<SimpleWatcher> watcher,
+      scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+      WatcherHandle watcher_handle,
+      Handle handle,
+      MojoHandleSignals signals,
+      int watch_id,
+      MojoResult* watch_result) {
+    scoped_refptr<Context> context =
+        new Context(watcher, task_runner, watch_id);
+
+    // If MojoWatch succeeds, it assumes ownership of a reference to |context|.
+    // In that case, this reference is balanced in CallNotify() when |result| is
+    // |MOJO_RESULT_CANCELLED|.
+    context->AddRef();
+
+    *watch_result = MojoWatch(watcher_handle.value(), handle.value(), signals,
+                              context->value());
+    if (*watch_result != MOJO_RESULT_OK) {
+      // Balanced by the AddRef() above since watching failed.
+      context->Release();
+      return nullptr;
+    }
+
+    return context;
+  }
+
+  static void CallNotify(uintptr_t context_value,
+                         MojoResult result,
+                         MojoHandleSignalsState signals_state,
+                         MojoWatcherNotificationFlags flags) {
+    auto* context = reinterpret_cast<Context*>(context_value);
+    context->Notify(result, signals_state, flags);
+
+    // That was the last notification for the context. We can release the ref
+    // owned by the watch, which may in turn delete the Context.
+    if (result == MOJO_RESULT_CANCELLED)
+      context->Release();
+  }
+
+  uintptr_t value() const { return reinterpret_cast<uintptr_t>(this); }
+
+  void DisableCancellationNotifications() {
+    base::AutoLock lock(lock_);
+    enable_cancellation_notifications_ = false;
+  }
+
+ private:
+  friend class base::RefCountedThreadSafe<Context>;
+
+  Context(base::WeakPtr<SimpleWatcher> weak_watcher,
+          scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+          int watch_id)
+      : weak_watcher_(weak_watcher),
+        task_runner_(task_runner),
+        watch_id_(watch_id) {}
+  ~Context() {}
+
+  void Notify(MojoResult result,
+              MojoHandleSignalsState signals_state,
+              MojoWatcherNotificationFlags flags) {
+    if (result == MOJO_RESULT_CANCELLED) {
+      // The SimpleWatcher may have explicitly cancelled this watch, so we don't
+      // bother dispatching the notification - it would be ignored anyway.
+      //
+      // TODO(rockot): This shouldn't really be necessary, but there are already
+      // instances today where bindings object may be bound and subsequently
+      // closed due to pipe error, all before the thread's TaskRunner has been
+      // properly initialized.
+      base::AutoLock lock(lock_);
+      if (!enable_cancellation_notifications_)
+        return;
+    }
+
+    if ((flags & MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM) &&
+        task_runner_->RunsTasksOnCurrentThread() && weak_watcher_ &&
+        weak_watcher_->is_default_task_runner_) {
+      // System notifications will trigger from the task runner passed to
+      // mojo::edk::InitIPCSupport(). In Chrome this happens to always be the
+      // default task runner for the IO thread.
+      weak_watcher_->OnHandleReady(watch_id_, result);
+    } else {
+      task_runner_->PostTask(
+          FROM_HERE, base::Bind(&SimpleWatcher::OnHandleReady, weak_watcher_,
+                                watch_id_, result));
+    }
+  }
+
+  const base::WeakPtr<SimpleWatcher> weak_watcher_;
+  const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+  const int watch_id_;
+
+  base::Lock lock_;
+  bool enable_cancellation_notifications_ = true;
+
+  DISALLOW_COPY_AND_ASSIGN(Context);
+};
+
+SimpleWatcher::SimpleWatcher(const tracked_objects::Location& from_here,
+                             ArmingPolicy arming_policy,
+                             scoped_refptr<base::SingleThreadTaskRunner> runner)
+    : arming_policy_(arming_policy),
+      task_runner_(std::move(runner)),
+      is_default_task_runner_(task_runner_ ==
+                              base::ThreadTaskRunnerHandle::Get()),
+      heap_profiler_tag_(from_here.file_name()),
+      weak_factory_(this) {
+  MojoResult rv = CreateWatcher(&Context::CallNotify, &watcher_handle_);
+  DCHECK_EQ(MOJO_RESULT_OK, rv);
+  DCHECK(task_runner_->BelongsToCurrentThread());
+}
+
+SimpleWatcher::~SimpleWatcher() {
+  if (IsWatching())
+    Cancel();
+}
+
+bool SimpleWatcher::IsWatching() const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  return context_ != nullptr;
+}
+
+MojoResult SimpleWatcher::Watch(Handle handle,
+                                MojoHandleSignals signals,
+                                const ReadyCallback& callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(!IsWatching());
+  DCHECK(!callback.is_null());
+
+  callback_ = callback;
+  handle_ = handle;
+  watch_id_ += 1;
+
+  MojoResult watch_result = MOJO_RESULT_UNKNOWN;
+  context_ = Context::Create(weak_factory_.GetWeakPtr(), task_runner_,
+                             watcher_handle_.get(), handle_, signals, watch_id_,
+                             &watch_result);
+  if (!context_) {
+    handle_.set_value(kInvalidHandleValue);
+    callback_.Reset();
+    DCHECK_EQ(MOJO_RESULT_INVALID_ARGUMENT, watch_result);
+    return watch_result;
+  }
+
+  if (arming_policy_ == ArmingPolicy::AUTOMATIC)
+    ArmOrNotify();
+
+  return MOJO_RESULT_OK;
+}
+
+void SimpleWatcher::Cancel() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  // The watcher may have already been cancelled if the handle was closed.
+  if (!context_)
+    return;
+
+  // Prevent the cancellation notification from being dispatched to
+  // OnHandleReady() when cancellation is explicit. See the note in the
+  // implementation of DisableCancellationNotifications() above.
+  context_->DisableCancellationNotifications();
+
+  handle_.set_value(kInvalidHandleValue);
+  callback_.Reset();
+
+  // Ensure |context_| is unset by the time we call MojoCancelWatch, as may
+  // re-enter the notification callback and we want to ensure |context_| is
+  // unset by then.
+  scoped_refptr<Context> context;
+  std::swap(context, context_);
+  MojoResult rv =
+      MojoCancelWatch(watcher_handle_.get().value(), context->value());
+
+  // It's possible this cancellation could race with a handle closure
+  // notification, in which case the watch may have already been implicitly
+  // cancelled.
+  DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_NOT_FOUND);
+}
+
+MojoResult SimpleWatcher::Arm(MojoResult* ready_result) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  uint32_t num_ready_contexts = 1;
+  uintptr_t ready_context;
+  MojoResult local_ready_result;
+  MojoHandleSignalsState ready_state;
+  MojoResult rv =
+      MojoArmWatcher(watcher_handle_.get().value(), &num_ready_contexts,
+                     &ready_context, &local_ready_result, &ready_state);
+  if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
+    DCHECK(context_);
+    DCHECK_EQ(1u, num_ready_contexts);
+    DCHECK_EQ(context_->value(), ready_context);
+    if (ready_result)
+      *ready_result = local_ready_result;
+  }
+
+  return rv;
+}
+
+void SimpleWatcher::ArmOrNotify() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  // Already cancelled, nothing to do.
+  if (!IsWatching())
+    return;
+
+  MojoResult ready_result;
+  MojoResult rv = Arm(&ready_result);
+  if (rv == MOJO_RESULT_OK)
+    return;
+
+  DCHECK_EQ(MOJO_RESULT_FAILED_PRECONDITION, rv);
+  task_runner_->PostTask(FROM_HERE, base::Bind(&SimpleWatcher::OnHandleReady,
+                                               weak_factory_.GetWeakPtr(),
+                                               watch_id_, ready_result));
+}
+
+void SimpleWatcher::OnHandleReady(int watch_id, MojoResult result) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  // This notification may be for a previously watched context, in which case
+  // we just ignore it.
+  if (watch_id != watch_id_)
+    return;
+
+  ReadyCallback callback = callback_;
+  if (result == MOJO_RESULT_CANCELLED) {
+    // Implicit cancellation due to someone closing the watched handle. We clear
+    // the SimppleWatcher's state before dispatching this.
+    context_ = nullptr;
+    handle_.set_value(kInvalidHandleValue);
+    callback_.Reset();
+  }
+
+  // NOTE: It's legal for |callback| to delete |this|.
+  if (!callback.is_null()) {
+    TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION event(heap_profiler_tag_);
+
+    base::WeakPtr<SimpleWatcher> weak_self = weak_factory_.GetWeakPtr();
+    callback.Run(result);
+    if (!weak_self)
+      return;
+
+    if (unsatisfiable_)
+      return;
+
+    // Prevent |MOJO_RESULT_FAILED_PRECONDITION| task spam by only notifying
+    // at most once in AUTOMATIC arming mode.
+    if (result == MOJO_RESULT_FAILED_PRECONDITION)
+      unsatisfiable_ = true;
+
+    if (arming_policy_ == ArmingPolicy::AUTOMATIC && IsWatching())
+      ArmOrNotify();
+  }
+}
+
+}  // namespace mojo
diff --git a/mojo/public/cpp/system/simple_watcher.h b/mojo/public/cpp/system/simple_watcher.h
new file mode 100644
index 0000000..9001884
--- /dev/null
+++ b/mojo/public/cpp/system/simple_watcher.h
@@ -0,0 +1,215 @@
+// Copyright 2017 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 MOJO_PUBLIC_CPP_SYSTEM_SIMPLE_WATCHER_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_SIMPLE_WATCHER_H_
+
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/system_export.h"
+#include "mojo/public/cpp/system/watcher.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace mojo {
+
+// This provides a convenient thread-bound watcher implementation to safely
+// watch a single handle, dispatching state change notifications to an arbitrary
+// SingleThreadTaskRunner running on the same thread as the SimpleWatcher.
+//
+// SimpleWatcher exposes the concept of "arming" from the low-level Watcher API.
+// In general, a SimpleWatcher must be "armed" in order to dispatch a single
+// notification, and must then be rearmed before it will dispatch another. For
+// more details, see the documentation for ArmingPolicy and the Arm() and
+// ArmOrNotify() methods below.
+class MOJO_CPP_SYSTEM_EXPORT SimpleWatcher {
+ public:
+  // A callback to be called any time a watched handle changes state in some
+  // interesting way. The |result| argument indicates one of the following
+  // conditions depending on its value:
+  //
+  //   |MOJO_RESULT_OK|: One or more of the signals being watched is satisfied.
+  //
+  //   |MOJO_RESULT_FAILED_PRECONDITION|: None of the signals being watched can
+  //       ever be satisfied again.
+  //
+  //   |MOJO_RESULT_CANCELLED|: The watched handle has been closed. No further
+  //       notifications will be fired, as this equivalent to an implicit
+  //       CancelWatch().
+  //
+  // Note that unlike the first two conditions, this callback may be invoked
+  // with |MOJO_RESULT_CANCELLED| even while the SimpleWatcher is disarmed.
+  using ReadyCallback = base::Callback<void(MojoResult result)>;
+
+  // Selects how this SimpleWatcher is to be armed.
+  enum class ArmingPolicy {
+    // The SimpleWatcher is armed automatically on Watch() and rearmed again
+    // after every invocation of the ReadyCallback. There is no need to manually
+    // call Arm() on a SimpleWatcher using this policy. This mode is equivalent
+    // to calling ArmOrNotify() once after Watch() and once again after every
+    // dispatched notification in MANUAL mode.
+    //
+    // This provides a reasonable approximation of edge-triggered behavior,
+    // mitigating (but not completely eliminating) the potential for redundant
+    // notifications.
+    //
+    // NOTE: It is important when using AUTOMATIC policy that your ReadyCallback
+    // always attempt to change the state of the handle (e.g. read available
+    // messages on a message pipe.) Otherwise this will result in a potentially
+    // large number of avoidable redundant tasks.
+    //
+    // For perfect edge-triggered behavior, use MANUAL policy and manually Arm()
+    // the SimpleWatcher as soon as it becomes possible to do so again.
+    AUTOMATIC,
+
+    // The SimpleWatcher is never armed automatically. Arm() or ArmOrNotify()
+    // must be called manually before any non-cancellation notification can be
+    // dispatched to the ReadyCallback. See the documentation for Arm() and
+    // ArmNotify() methods below for more details.
+    MANUAL,
+  };
+
+  SimpleWatcher(const tracked_objects::Location& from_here,
+                ArmingPolicy arming_policy,
+                scoped_refptr<base::SingleThreadTaskRunner> runner =
+                    base::ThreadTaskRunnerHandle::Get());
+  ~SimpleWatcher();
+
+  // Indicates if the SimpleWatcher is currently watching a handle.
+  bool IsWatching() const;
+
+  // Starts watching |handle|. A SimpleWatcher may only watch one handle at a
+  // time, but it is safe to call this more than once as long as the previous
+  // watch has been cancelled (i.e. |IsWatching()| returns |false|.)
+  //
+  // If |handle| is not a valid watchable (message or data pipe) handle or
+  // |signals| is not a valid set of signals to watch, this returns
+  // |MOJO_RESULT_INVALID_ARGUMENT|.
+  //
+  // Otherwise |MOJO_RESULT_OK| is returned and the handle will be watched until
+  // either |handle| is closed, the SimpleWatcher is destroyed, or Cancel() is
+  // explicitly called.
+  //
+  // Once the watch is started, |callback| may be called at any time on the
+  // current thread until |Cancel()| is called or the handle is closed. Note
+  // that |callback| can be called for results other than
+  // |MOJO_RESULT_CANCELLED| only if the SimpleWatcher is currently armed. Use
+  // ArmingPolicy to configure how a SimpleWatcher is armed.
+  //
+  // |MOJO_RESULT_CANCELLED| may be dispatched even while the SimpleWatcher
+  // is disarmed, and no further notifications will be dispatched after that.
+  //
+  // Destroying the SimpleWatcher implicitly calls |Cancel()|.
+  MojoResult Watch(Handle handle,
+                   MojoHandleSignals signals,
+                   const ReadyCallback& callback);
+
+  // Cancels the current watch. Once this returns, the ReadyCallback previously
+  // passed to |Watch()| will never be called again for this SimpleWatcher.
+  //
+  // Note that when cancelled with an explicit call to |Cancel()| the
+  // ReadyCallback will not be invoked with a |MOJO_RESULT_CANCELLED| result.
+  void Cancel();
+
+  // Manually arms the SimpleWatcher.
+  //
+  // Arming the SimpleWatcher allows it to fire a single notification regarding
+  // some future relevant change in the watched handle's state. It's only valid
+  // to call Arm() while a handle is being watched (see Watch() above.)
+  //
+  // SimpleWatcher is always disarmed immediately before invoking its
+  // ReadyCallback and must be rearmed again before another notification can
+  // fire.
+  //
+  // If the watched handle already meets the watched signaling conditions -
+  // i.e., if it would have notified immediately once armed - the SimpleWatcher
+  // is NOT armed, and this call fails with a return value of
+  // |MOJO_RESULT_FAILED_PRECONDITION|. In that case, what would have been the
+  // result code for that immediate notification is instead placed in
+  // |*ready_result| if |ready_result| is non-null.
+  //
+  // If the watcher is successfully armed, this returns |MOJO_RESULT_OK| and
+  // |ready_result| is ignored.
+  MojoResult Arm(MojoResult* ready_result = nullptr);
+
+  // Manually arms the SimpleWatcher OR posts a task to invoke the ReadyCallback
+  // with the ready result of the failed arming attempt.
+  //
+  // This is meant as a convenient helper for a common usage of Arm(), and it
+  // ensures that the ReadyCallback will be invoked asynchronously again as soon
+  // as the watch's conditions are satisfied, assuming the SimpleWatcher isn't
+  // cancelled first.
+  //
+  // Unlike Arm() above, this can never fail.
+  void ArmOrNotify();
+
+  Handle handle() const { return handle_; }
+  ReadyCallback ready_callback() const { return callback_; }
+
+  // Sets the tag used by the heap profiler.
+  // |tag| must be a const string literal.
+  void set_heap_profiler_tag(const char* heap_profiler_tag) {
+    heap_profiler_tag_ = heap_profiler_tag;
+  }
+
+ private:
+  class Context;
+
+  void OnHandleReady(int watch_id, MojoResult result);
+
+  base::ThreadChecker thread_checker_;
+
+  // The policy used to determine how this SimpleWatcher is armed.
+  const ArmingPolicy arming_policy_;
+
+  // The TaskRunner of this SimpleWatcher's owning thread. This field is safe to
+  // access from any thread.
+  const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+  // Whether |task_runner_| is the same as base::ThreadTaskRunnerHandle::Get()
+  // for the thread.
+  const bool is_default_task_runner_;
+
+  ScopedWatcherHandle watcher_handle_;
+
+  // A thread-safe context object corresponding to the currently active watch,
+  // if any.
+  scoped_refptr<Context> context_;
+
+  // Fields below must only be accessed on the SimpleWatcher's owning thread.
+
+  // The handle currently under watch. Not owned.
+  Handle handle_;
+
+  // A simple counter to disambiguate notifications from multiple watch contexts
+  // in the event that this SimpleWatcher cancels and watches multiple times.
+  int watch_id_ = 0;
+
+  // The callback to call when the handle is signaled.
+  ReadyCallback callback_;
+
+  // Tracks if the SimpleWatcher has already notified of unsatisfiability. This
+  // is used to prevent redundant notifications in AUTOMATIC mode.
+  bool unsatisfiable_ = false;
+
+  // Tag used to ID memory allocations that originated from notifications in
+  // this watcher.
+  const char* heap_profiler_tag_ = nullptr;
+
+  base::WeakPtrFactory<SimpleWatcher> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(SimpleWatcher);
+};
+
+}  // namespace mojo
+
+#endif  // MOJO_PUBLIC_CPP_SYSTEM_SIMPLE_WATCHER_H_
diff --git a/mojo/public/cpp/system/tests/BUILD.gn b/mojo/public/cpp/system/tests/BUILD.gn
index 8f98b92..705d009 100644
--- a/mojo/public/cpp/system/tests/BUILD.gn
+++ b/mojo/public/cpp/system/tests/BUILD.gn
@@ -7,7 +7,10 @@
 
   sources = [
     "core_unittest.cc",
-    "watcher_unittest.cc",
+    "handle_signals_state_unittest.cc",
+    "simple_watcher_unittest.cc",
+    "wait_set_unittest.cc",
+    "wait_unittest.cc",
   ]
 
   deps = [
diff --git a/mojo/public/cpp/system/tests/core_unittest.cc b/mojo/public/cpp/system/tests/core_unittest.cc
index e503db0..40a94f0 100644
--- a/mojo/public/cpp/system/tests/core_unittest.cc
+++ b/mojo/public/cpp/system/tests/core_unittest.cc
@@ -13,6 +13,7 @@
 #include <map>
 #include <utility>
 
+#include "mojo/public/cpp/system/wait.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace mojo {
@@ -108,25 +109,15 @@
     EXPECT_EQ(kInvalidHandleValue, h.get().value());
 
     EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-              Wait(h.get(), ~MOJO_HANDLE_SIGNAL_NONE, 1000000, nullptr));
+              Wait(h.get(), ~MOJO_HANDLE_SIGNAL_NONE));
 
     std::vector<Handle> wh;
     wh.push_back(h.get());
     std::vector<MojoHandleSignals> sigs;
     sigs.push_back(~MOJO_HANDLE_SIGNAL_NONE);
-    WaitManyResult wait_many_result =
-        WaitMany(wh, sigs, MOJO_DEADLINE_INDEFINITE, nullptr);
-    EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, wait_many_result.result);
-    EXPECT_TRUE(wait_many_result.IsIndexValid());
-    EXPECT_FALSE(wait_many_result.AreSignalsStatesValid());
-
-    // Make sure that our specialized template correctly handles |NULL| as well
-    // as |nullptr|.
-    wait_many_result = WaitMany(wh, sigs, MOJO_DEADLINE_INDEFINITE, NULL);
-    EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, wait_many_result.result);
-    EXPECT_EQ(0u, wait_many_result.index);
-    EXPECT_TRUE(wait_many_result.IsIndexValid());
-    EXPECT_FALSE(wait_many_result.AreSignalsStatesValid());
+    size_t result_index;
+    MojoResult rv = WaitMany(wh.data(), sigs.data(), wh.size(), &result_index);
+    EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, rv);
   }
 
   // |MakeScopedHandle| (just compilation tests):
@@ -186,10 +177,7 @@
       // correctly.
       hv0 = h0.get().value();
       MojoHandle hv1 = h1.get().value();
-      MojoHandleSignalsState state;
-
-      EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
-                Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, 0, &state));
+      MojoHandleSignalsState state = h0->QuerySignalsState();
 
       EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals);
       EXPECT_EQ(kSignalAll, state.satisfiable_signals);
@@ -201,11 +189,12 @@
       sigs.push_back(MOJO_HANDLE_SIGNAL_READABLE);
       sigs.push_back(MOJO_HANDLE_SIGNAL_WRITABLE);
       std::vector<MojoHandleSignalsState> states(sigs.size());
-      WaitManyResult wait_many_result = WaitMany(wh, sigs, 1000, &states);
-      EXPECT_EQ(MOJO_RESULT_OK, wait_many_result.result);
-      EXPECT_EQ(1u, wait_many_result.index);
-      EXPECT_TRUE(wait_many_result.IsIndexValid());
-      EXPECT_TRUE(wait_many_result.AreSignalsStatesValid());
+
+      size_t result_index;
+      MojoResult rv = WaitMany(wh.data(), sigs.data(), wh.size(), &result_index,
+                               states.data());
+      EXPECT_EQ(MOJO_RESULT_OK, rv);
+      EXPECT_EQ(1u, result_index);
       EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[0].satisfied_signals);
       EXPECT_EQ(kSignalAll, states[0].satisfiable_signals);
       EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[1].satisfied_signals);
@@ -217,12 +206,10 @@
 
       // Make sure |h1| is closed.
       EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-                Wait(Handle(hv1), ~MOJO_HANDLE_SIGNAL_NONE,
-                     MOJO_DEADLINE_INDEFINITE, nullptr));
+                Wait(Handle(hv1), ~MOJO_HANDLE_SIGNAL_NONE));
 
       EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-                Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &state));
+                Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, &state));
 
       EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals);
       EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals);
@@ -248,8 +235,8 @@
                                 MOJO_WRITE_MESSAGE_FLAG_NONE));
 
       MojoHandleSignalsState state;
-      EXPECT_EQ(MOJO_RESULT_OK, Wait(h1.get(), MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &state));
+      EXPECT_EQ(MOJO_RESULT_OK,
+                Wait(h1.get(), MOJO_HANDLE_SIGNAL_READABLE, &state));
       EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals);
       EXPECT_EQ(kSignalAll, state.satisfiable_signals);
 
@@ -298,8 +285,8 @@
       EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handles[0]));
 
       // Read "hello" and the sent handle.
-      EXPECT_EQ(MOJO_RESULT_OK, Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE,
-                                     MOJO_DEADLINE_INDEFINITE, &state));
+      EXPECT_EQ(MOJO_RESULT_OK,
+                Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, &state));
       EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals);
       EXPECT_EQ(kSignalAll, state.satisfiable_signals);
 
@@ -326,8 +313,7 @@
       hv0 = handles[0];
 
       EXPECT_EQ(MOJO_RESULT_OK,
-                Wait(mp.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE,
-                     MOJO_DEADLINE_INDEFINITE, &state));
+                Wait(mp.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &state));
       EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals);
       EXPECT_EQ(kSignalAll, state.satisfiable_signals);
 
diff --git a/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc b/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc
new file mode 100644
index 0000000..82f538e
--- /dev/null
+++ b/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc
@@ -0,0 +1,42 @@
+// Copyright 2017 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 "mojo/public/cpp/system/handle_signals_state.h"
+
+#include "mojo/public/c/system/types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+using HandleSignalsStateTest = testing::Test;
+
+TEST_F(HandleSignalsStateTest, SanityCheck) {
+  // There's not much to test here. Just a quick sanity check to make sure the
+  // code compiles and the helper methods do what they're supposed to do.
+
+  HandleSignalsState empty_signals(MOJO_HANDLE_SIGNAL_NONE,
+                                   MOJO_HANDLE_SIGNAL_NONE);
+  EXPECT_FALSE(empty_signals.readable());
+  EXPECT_FALSE(empty_signals.writable());
+  EXPECT_FALSE(empty_signals.peer_closed());
+  EXPECT_TRUE(empty_signals.never_readable());
+  EXPECT_TRUE(empty_signals.never_writable());
+  EXPECT_TRUE(empty_signals.never_peer_closed());
+
+  HandleSignalsState all_signals(
+      MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+          MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+      MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+          MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+  EXPECT_TRUE(all_signals.readable());
+  EXPECT_TRUE(all_signals.writable());
+  EXPECT_TRUE(all_signals.peer_closed());
+  EXPECT_FALSE(all_signals.never_readable());
+  EXPECT_FALSE(all_signals.never_writable());
+  EXPECT_FALSE(all_signals.never_peer_closed());
+}
+
+}  // namespace
+}  // namespace mojo
diff --git a/mojo/public/cpp/system/tests/simple_watcher_unittest.cc b/mojo/public/cpp/system/tests/simple_watcher_unittest.cc
new file mode 100644
index 0000000..795f262
--- /dev/null
+++ b/mojo/public/cpp/system/tests/simple_watcher_unittest.cc
@@ -0,0 +1,277 @@
+// 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.
+
+#include "mojo/public/cpp/system/simple_watcher.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+template <typename Handler>
+void RunResultHandler(Handler f, MojoResult result) {
+  f(result);
+}
+
+template <typename Handler>
+SimpleWatcher::ReadyCallback OnReady(Handler f) {
+  return base::Bind(&RunResultHandler<Handler>, f);
+}
+
+SimpleWatcher::ReadyCallback NotReached() {
+  return OnReady([](MojoResult) { NOTREACHED(); });
+}
+
+class SimpleWatcherTest : public testing::Test {
+ public:
+  SimpleWatcherTest() {}
+  ~SimpleWatcherTest() override {}
+
+ private:
+  base::MessageLoop message_loop_;
+
+  DISALLOW_COPY_AND_ASSIGN(SimpleWatcherTest);
+};
+
+TEST_F(SimpleWatcherTest, WatchBasic) {
+  ScopedMessagePipeHandle a, b;
+  CreateMessagePipe(nullptr, &a, &b);
+
+  bool notified = false;
+  base::RunLoop run_loop;
+  SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+  EXPECT_EQ(MOJO_RESULT_OK,
+            b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
+                            OnReady([&](MojoResult result) {
+                              EXPECT_EQ(MOJO_RESULT_OK, result);
+                              notified = true;
+                              run_loop.Quit();
+                            })));
+  EXPECT_TRUE(b_watcher.IsWatching());
+
+  EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0,
+                                            MOJO_WRITE_MESSAGE_FLAG_NONE));
+  run_loop.Run();
+  EXPECT_TRUE(notified);
+
+  b_watcher.Cancel();
+}
+
+TEST_F(SimpleWatcherTest, WatchUnsatisfiable) {
+  ScopedMessagePipeHandle a, b;
+  CreateMessagePipe(nullptr, &a, &b);
+  a.reset();
+
+  SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL);
+  EXPECT_EQ(
+      MOJO_RESULT_OK,
+      b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached()));
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, b_watcher.Arm());
+}
+
+TEST_F(SimpleWatcherTest, WatchInvalidHandle) {
+  ScopedMessagePipeHandle a, b;
+  CreateMessagePipe(nullptr, &a, &b);
+  a.reset();
+  b.reset();
+
+  SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+  EXPECT_EQ(
+      MOJO_RESULT_INVALID_ARGUMENT,
+      b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached()));
+  EXPECT_FALSE(b_watcher.IsWatching());
+}
+
+TEST_F(SimpleWatcherTest, Cancel) {
+  ScopedMessagePipeHandle a, b;
+  CreateMessagePipe(nullptr, &a, &b);
+
+  base::RunLoop run_loop;
+  SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+  EXPECT_EQ(
+      MOJO_RESULT_OK,
+      b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached()));
+  EXPECT_TRUE(b_watcher.IsWatching());
+  b_watcher.Cancel();
+  EXPECT_FALSE(b_watcher.IsWatching());
+
+  // This should never trigger the watcher.
+  EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0,
+                                            MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                run_loop.QuitClosure());
+  run_loop.Run();
+}
+
+TEST_F(SimpleWatcherTest, CancelOnClose) {
+  ScopedMessagePipeHandle a, b;
+  CreateMessagePipe(nullptr, &a, &b);
+
+  base::RunLoop run_loop;
+  SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+  EXPECT_EQ(MOJO_RESULT_OK,
+            b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
+                            OnReady([&](MojoResult result) {
+                              EXPECT_EQ(MOJO_RESULT_CANCELLED, result);
+                              run_loop.Quit();
+                            })));
+  EXPECT_TRUE(b_watcher.IsWatching());
+
+  // This should trigger the watcher above.
+  b.reset();
+
+  run_loop.Run();
+
+  EXPECT_FALSE(b_watcher.IsWatching());
+}
+
+TEST_F(SimpleWatcherTest, CancelOnDestruction) {
+  ScopedMessagePipeHandle a, b;
+  CreateMessagePipe(nullptr, &a, &b);
+  base::RunLoop run_loop;
+  {
+    SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+    EXPECT_EQ(
+        MOJO_RESULT_OK,
+        b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached()));
+    EXPECT_TRUE(b_watcher.IsWatching());
+
+    // |b_watcher| should be cancelled when it goes out of scope.
+  }
+
+  // This should never trigger the watcher above.
+  EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0,
+                                            MOJO_WRITE_MESSAGE_FLAG_NONE));
+  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                run_loop.QuitClosure());
+  run_loop.Run();
+}
+
+TEST_F(SimpleWatcherTest, CloseAndCancel) {
+  ScopedMessagePipeHandle a, b;
+  CreateMessagePipe(nullptr, &a, &b);
+
+  SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+  EXPECT_EQ(MOJO_RESULT_OK,
+            b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
+                            OnReady([](MojoResult result) { FAIL(); })));
+  EXPECT_TRUE(b_watcher.IsWatching());
+
+  // This should trigger the watcher above...
+  b.reset();
+  // ...but the watcher is cancelled first.
+  b_watcher.Cancel();
+
+  EXPECT_FALSE(b_watcher.IsWatching());
+
+  base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(SimpleWatcherTest, UnarmedCancel) {
+  ScopedMessagePipeHandle a, b;
+  CreateMessagePipe(nullptr, &a, &b);
+
+  SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL);
+  base::RunLoop loop;
+  EXPECT_EQ(MOJO_RESULT_OK,
+            b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
+                            base::Bind(
+                                [](base::RunLoop* loop, MojoResult result) {
+                                  EXPECT_EQ(result, MOJO_RESULT_CANCELLED);
+                                  loop->Quit();
+                                },
+                                &loop)));
+
+  // This message write will not wake up the watcher since the watcher isn't
+  // armed. Instead, the cancellation will dispatch due to the reset below.
+  EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0,
+                                            MOJO_WRITE_MESSAGE_FLAG_NONE));
+  b.reset();
+  loop.Run();
+}
+
+TEST_F(SimpleWatcherTest, ManualArming) {
+  ScopedMessagePipeHandle a, b;
+  CreateMessagePipe(nullptr, &a, &b);
+
+  SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL);
+  base::RunLoop loop;
+  EXPECT_EQ(MOJO_RESULT_OK,
+            b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
+                            base::Bind(
+                                [](base::RunLoop* loop, MojoResult result) {
+                                  EXPECT_EQ(result, MOJO_RESULT_OK);
+                                  loop->Quit();
+                                },
+                                &loop)));
+  EXPECT_EQ(MOJO_RESULT_OK, b_watcher.Arm());
+
+  EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0,
+                                            MOJO_WRITE_MESSAGE_FLAG_NONE));
+  loop.Run();
+}
+
+TEST_F(SimpleWatcherTest, ManualArmOrNotifyWhileSignaled) {
+  ScopedMessagePipeHandle a, b;
+  CreateMessagePipe(nullptr, &a, &b);
+
+  base::RunLoop loop1;
+  SimpleWatcher b_watcher1(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL);
+  bool notified1 = false;
+  EXPECT_EQ(MOJO_RESULT_OK,
+            b_watcher1.Watch(
+                b.get(), MOJO_HANDLE_SIGNAL_READABLE,
+                base::Bind(
+                    [](base::RunLoop* loop, bool* notified, MojoResult result) {
+                      EXPECT_EQ(result, MOJO_RESULT_OK);
+                      *notified = true;
+                      loop->Quit();
+                    },
+                    &loop1, &notified1)));
+
+  base::RunLoop loop2;
+  SimpleWatcher b_watcher2(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL);
+  bool notified2 = false;
+  EXPECT_EQ(MOJO_RESULT_OK,
+            b_watcher2.Watch(
+                b.get(), MOJO_HANDLE_SIGNAL_READABLE,
+                base::Bind(
+                    [](base::RunLoop* loop, bool* notified, MojoResult result) {
+                      EXPECT_EQ(result, MOJO_RESULT_OK);
+                      *notified = true;
+                      loop->Quit();
+                    },
+                    &loop2, &notified2)));
+
+  // First ensure that |b| is readable.
+  EXPECT_EQ(MOJO_RESULT_OK, b_watcher1.Arm());
+  EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0,
+                                            MOJO_WRITE_MESSAGE_FLAG_NONE));
+  loop1.Run();
+
+  EXPECT_TRUE(notified1);
+  EXPECT_FALSE(notified2);
+  notified1 = false;
+
+  // Now verify that ArmOrNotify results in a notification.
+  b_watcher2.ArmOrNotify();
+  loop2.Run();
+
+  EXPECT_FALSE(notified1);
+  EXPECT_TRUE(notified2);
+}
+
+}  // namespace
+}  // namespace mojo
diff --git a/mojo/public/cpp/system/tests/wait_set_unittest.cc b/mojo/public/cpp/system/tests/wait_set_unittest.cc
new file mode 100644
index 0000000..d60cb45
--- /dev/null
+++ b/mojo/public/cpp/system/tests/wait_set_unittest.cc
@@ -0,0 +1,376 @@
+// Copyright 2017 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 "mojo/public/cpp/system/wait_set.h"
+
+#include <set>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/ptr_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/cpp/system/wait.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+using WaitSetTest = testing::Test;
+
+void WriteMessage(const ScopedMessagePipeHandle& handle,
+                  const base::StringPiece& message) {
+  MojoResult rv = WriteMessageRaw(handle.get(), message.data(),
+                                  static_cast<uint32_t>(message.size()),
+                                  nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE);
+  CHECK_EQ(MOJO_RESULT_OK, rv);
+}
+
+std::string ReadMessage(const ScopedMessagePipeHandle& handle) {
+  uint32_t num_bytes = 0;
+  uint32_t num_handles = 0;
+  MojoResult rv = ReadMessageRaw(handle.get(), nullptr, &num_bytes, nullptr,
+                                 &num_handles, MOJO_READ_MESSAGE_FLAG_NONE);
+  CHECK_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, rv);
+  CHECK_EQ(0u, num_handles);
+
+  std::vector<char> buffer(num_bytes);
+  rv = ReadMessageRaw(handle.get(), buffer.data(), &num_bytes, nullptr,
+                      &num_handles, MOJO_READ_MESSAGE_FLAG_NONE);
+  CHECK_EQ(MOJO_RESULT_OK, rv);
+  return std::string(buffer.data(), buffer.size());
+}
+
+class ThreadedRunner : public base::SimpleThread {
+ public:
+  explicit ThreadedRunner(const base::Closure& callback)
+      : SimpleThread("ThreadedRunner"), callback_(callback) {}
+  ~ThreadedRunner() override { Join(); }
+
+  void Run() override { callback_.Run(); }
+
+ private:
+  const base::Closure callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(ThreadedRunner);
+};
+
+TEST_F(WaitSetTest, Satisfied) {
+  WaitSet wait_set;
+  MessagePipe p;
+
+  const char kTestMessage1[] = "hello wake up";
+
+  // Watch only one handle and write to the other.
+
+  wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+  WriteMessage(p.handle0, kTestMessage1);
+
+  size_t num_ready_handles = 2;
+  Handle ready_handles[2];
+  MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN};
+  HandleSignalsState hss[2];
+  wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss);
+
+  EXPECT_EQ(1u, num_ready_handles);
+  EXPECT_EQ(p.handle1.get(), ready_handles[0]);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+  EXPECT_TRUE(hss[0].readable() && hss[0].writable() && !hss[0].peer_closed());
+
+  wait_set.RemoveHandle(p.handle1.get());
+
+  // Now watch only the other handle and write to the first one.
+
+  wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+  WriteMessage(p.handle1, kTestMessage1);
+
+  num_ready_handles = 2;
+  ready_results[0] = MOJO_RESULT_UNKNOWN;
+  ready_results[1] = MOJO_RESULT_UNKNOWN;
+  wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss);
+
+  EXPECT_EQ(1u, num_ready_handles);
+  EXPECT_EQ(p.handle0.get(), ready_handles[0]);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+  EXPECT_TRUE(hss[0].readable() && hss[0].writable() && !hss[0].peer_closed());
+
+  // Now wait on both of them.
+  wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+
+  num_ready_handles = 2;
+  ready_results[0] = MOJO_RESULT_UNKNOWN;
+  ready_results[1] = MOJO_RESULT_UNKNOWN;
+  wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss);
+  EXPECT_EQ(2u, num_ready_handles);
+  EXPECT_TRUE((ready_handles[0] == p.handle0.get() &&
+               ready_handles[1] == p.handle1.get()) ||
+              (ready_handles[0] == p.handle1.get() &&
+               ready_handles[1] == p.handle0.get()));
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[1]);
+  EXPECT_TRUE(hss[0].readable() && hss[0].writable() && !hss[0].peer_closed());
+  EXPECT_TRUE(hss[1].readable() && hss[1].writable() && !hss[1].peer_closed());
+
+  // Wait on both again, but with only enough output space for one result.
+  num_ready_handles = 1;
+  ready_results[0] = MOJO_RESULT_UNKNOWN;
+  wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss);
+  EXPECT_EQ(1u, num_ready_handles);
+  EXPECT_TRUE(ready_handles[0] == p.handle0.get() ||
+              ready_handles[0] == p.handle1.get());
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+
+  // Remove the ready handle from the set and wait one more time.
+  EXPECT_EQ(MOJO_RESULT_OK, wait_set.RemoveHandle(ready_handles[0]));
+
+  num_ready_handles = 1;
+  ready_results[0] = MOJO_RESULT_UNKNOWN;
+  wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss);
+  EXPECT_EQ(1u, num_ready_handles);
+  EXPECT_TRUE(ready_handles[0] == p.handle0.get() ||
+              ready_handles[0] == p.handle1.get());
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+
+  EXPECT_EQ(MOJO_RESULT_OK, wait_set.RemoveHandle(ready_handles[0]));
+
+  // The wait set should be empty now. Nothing to wait on.
+  num_ready_handles = 2;
+  wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results);
+  EXPECT_EQ(0u, num_ready_handles);
+}
+
+TEST_F(WaitSetTest, Unsatisfiable) {
+  MessagePipe p, q;
+  WaitSet wait_set;
+
+  wait_set.AddHandle(q.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+  wait_set.AddHandle(q.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+  wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+
+  size_t num_ready_handles = 2;
+  Handle ready_handles[2];
+  MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN};
+
+  p.handle1.reset();
+  wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results);
+  EXPECT_EQ(1u, num_ready_handles);
+  EXPECT_EQ(p.handle0.get(), ready_handles[0]);
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]);
+}
+
+TEST_F(WaitSetTest, CloseWhileWaiting) {
+  MessagePipe p;
+  WaitSet wait_set;
+
+  wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+
+  const Handle handle0_value = p.handle0.get();
+  ThreadedRunner close_after_delay(base::Bind(
+      [](ScopedMessagePipeHandle* handle) {
+        // Wait a little while, then close the handle.
+        base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+        handle->reset();
+      },
+      &p.handle0));
+  close_after_delay.Start();
+
+  size_t num_ready_handles = 2;
+  Handle ready_handles[2];
+  MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN};
+  wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results);
+  EXPECT_EQ(1u, num_ready_handles);
+  EXPECT_EQ(handle0_value, ready_handles[0]);
+  EXPECT_EQ(MOJO_RESULT_CANCELLED, ready_results[0]);
+
+  EXPECT_EQ(MOJO_RESULT_NOT_FOUND, wait_set.RemoveHandle(handle0_value));
+}
+
+TEST_F(WaitSetTest, CloseBeforeWaiting) {
+  MessagePipe p;
+  WaitSet wait_set;
+
+  wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+  wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+
+  Handle handle0_value = p.handle0.get();
+  Handle handle1_value = p.handle1.get();
+
+  p.handle0.reset();
+  p.handle1.reset();
+
+  // Ensure that the WaitSet user is always made aware of all cancellations even
+  // if they happen while not waiting, or they have to be returned over the span
+  // of multiple Wait() calls due to insufficient output storage.
+
+  size_t num_ready_handles = 1;
+  Handle ready_handle;
+  MojoResult ready_result = MOJO_RESULT_UNKNOWN;
+  wait_set.Wait(nullptr, &num_ready_handles, &ready_handle, &ready_result);
+  EXPECT_EQ(1u, num_ready_handles);
+  EXPECT_TRUE(ready_handle == handle0_value || ready_handle == handle1_value);
+  EXPECT_EQ(MOJO_RESULT_CANCELLED, ready_result);
+  EXPECT_EQ(MOJO_RESULT_NOT_FOUND, wait_set.RemoveHandle(handle0_value));
+
+  wait_set.Wait(nullptr, &num_ready_handles, &ready_handle, &ready_result);
+  EXPECT_EQ(1u, num_ready_handles);
+  EXPECT_TRUE(ready_handle == handle0_value || ready_handle == handle1_value);
+  EXPECT_EQ(MOJO_RESULT_CANCELLED, ready_result);
+  EXPECT_EQ(MOJO_RESULT_NOT_FOUND, wait_set.RemoveHandle(handle0_value));
+
+  // Nothing more to wait on.
+  wait_set.Wait(nullptr, &num_ready_handles, &ready_handle, &ready_result);
+  EXPECT_EQ(0u, num_ready_handles);
+}
+
+TEST_F(WaitSetTest, SatisfiedThenUnsatisfied) {
+  MessagePipe p;
+  WaitSet wait_set;
+
+  wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+  wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+
+  const char kTestMessage1[] = "testing testing testing";
+  WriteMessage(p.handle0, kTestMessage1);
+
+  size_t num_ready_handles = 2;
+  Handle ready_handles[2];
+  MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN};
+  wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results);
+  EXPECT_EQ(1u, num_ready_handles);
+  EXPECT_EQ(p.handle1.get(), ready_handles[0]);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+
+  EXPECT_EQ(kTestMessage1, ReadMessage(p.handle1));
+
+  ThreadedRunner write_after_delay(base::Bind(
+      [](ScopedMessagePipeHandle* handle) {
+        // Wait a little while, then write a message.
+        base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+        WriteMessage(*handle, "wakey wakey");
+      },
+      &p.handle1));
+  write_after_delay.Start();
+
+  num_ready_handles = 2;
+  wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results);
+  EXPECT_EQ(1u, num_ready_handles);
+  EXPECT_EQ(p.handle0.get(), ready_handles[0]);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]);
+}
+
+TEST_F(WaitSetTest, EventOnly) {
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::SIGNALED);
+  WaitSet wait_set;
+  wait_set.AddEvent(&event);
+
+  base::WaitableEvent* ready_event = nullptr;
+  size_t num_ready_handles = 1;
+  Handle ready_handle;
+  MojoResult ready_result = MOJO_RESULT_UNKNOWN;
+  wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result);
+  EXPECT_EQ(0u, num_ready_handles);
+  EXPECT_EQ(&event, ready_event);
+}
+
+TEST_F(WaitSetTest, EventAndHandle) {
+  const char kTestMessage[] = "hello hello";
+
+  MessagePipe p;
+  WriteMessage(p.handle0, kTestMessage);
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+  WaitSet wait_set;
+  wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+  wait_set.AddEvent(&event);
+
+  base::WaitableEvent* ready_event = nullptr;
+  size_t num_ready_handles = 1;
+  Handle ready_handle;
+  MojoResult ready_result = MOJO_RESULT_UNKNOWN;
+  wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result);
+  EXPECT_EQ(1u, num_ready_handles);
+  EXPECT_EQ(nullptr, ready_event);
+  EXPECT_EQ(p.handle1.get(), ready_handle);
+  EXPECT_EQ(MOJO_RESULT_OK, ready_result);
+
+  EXPECT_EQ(kTestMessage, ReadMessage(p.handle1));
+
+  ThreadedRunner signal_after_delay(base::Bind(
+      [](base::WaitableEvent* event) {
+        // Wait a little while, then close the handle.
+        base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+        event->Signal();
+      },
+      &event));
+  signal_after_delay.Start();
+
+  wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result);
+  EXPECT_EQ(0u, num_ready_handles);
+  EXPECT_EQ(&event, ready_event);
+}
+
+TEST_F(WaitSetTest, NoStarvation) {
+  const char kTestMessage[] = "wait for it";
+  const size_t kNumTestPipes = 50;
+  const size_t kNumTestEvents = 10;
+
+  // Create a bunch of handles and events which are always ready and add them
+  // to a shared WaitSet.
+
+  WaitSet wait_set;
+
+  MessagePipe pipes[kNumTestPipes];
+  for (size_t i = 0; i < kNumTestPipes; ++i) {
+    WriteMessage(pipes[i].handle0, kTestMessage);
+    Wait(pipes[i].handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+
+    WriteMessage(pipes[i].handle1, kTestMessage);
+    Wait(pipes[i].handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+
+    wait_set.AddHandle(pipes[i].handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+    wait_set.AddHandle(pipes[i].handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+  }
+
+  std::vector<std::unique_ptr<base::WaitableEvent>> events(kNumTestEvents);
+  for (auto& event_ptr : events) {
+    event_ptr = base::MakeUnique<base::WaitableEvent>(
+        base::WaitableEvent::ResetPolicy::MANUAL,
+        base::WaitableEvent::InitialState::NOT_SIGNALED);
+    event_ptr->Signal();
+    wait_set.AddEvent(event_ptr.get());
+  }
+
+  // Now verify that all handle and event signals are deteceted within a finite
+  // number of consecutive Wait() calls. Do it a few times for good measure.
+  for (size_t i = 0; i < 3; ++i) {
+    std::set<base::WaitableEvent*> ready_events;
+    std::set<Handle> ready_handles;
+    while (ready_events.size() < kNumTestEvents ||
+           ready_handles.size() < kNumTestPipes * 2) {
+      base::WaitableEvent* ready_event = nullptr;
+      size_t num_ready_handles = 1;
+      Handle ready_handle;
+      MojoResult ready_result = MOJO_RESULT_UNKNOWN;
+      wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle,
+                    &ready_result);
+      if (ready_event)
+        ready_events.insert(ready_event);
+
+      if (num_ready_handles) {
+        EXPECT_EQ(1u, num_ready_handles);
+        EXPECT_EQ(MOJO_RESULT_OK, ready_result);
+        ready_handles.insert(ready_handle);
+      }
+    }
+  }
+}
+
+}  // namespace
+}  // namespace mojo
diff --git a/mojo/public/cpp/system/tests/wait_unittest.cc b/mojo/public/cpp/system/tests/wait_unittest.cc
new file mode 100644
index 0000000..1d9d3c6
--- /dev/null
+++ b/mojo/public/cpp/system/tests/wait_unittest.cc
@@ -0,0 +1,321 @@
+// Copyright 2017 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 "mojo/public/cpp/system/wait.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/strings/string_piece.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
+#include "base/time/time.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/handle_signals_state.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+using WaitTest = testing::Test;
+using WaitManyTest = testing::Test;
+
+void WriteMessage(const ScopedMessagePipeHandle& handle,
+                  const base::StringPiece& message) {
+  MojoResult rv = WriteMessageRaw(handle.get(), message.data(),
+                                  static_cast<uint32_t>(message.size()),
+                                  nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE);
+  CHECK_EQ(MOJO_RESULT_OK, rv);
+}
+
+std::string ReadMessage(const ScopedMessagePipeHandle& handle) {
+  uint32_t num_bytes = 0;
+  uint32_t num_handles = 0;
+  MojoResult rv = ReadMessageRaw(handle.get(), nullptr, &num_bytes, nullptr,
+                                 &num_handles, MOJO_READ_MESSAGE_FLAG_NONE);
+  CHECK_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, rv);
+  CHECK_EQ(0u, num_handles);
+
+  std::vector<char> buffer(num_bytes);
+  rv = ReadMessageRaw(handle.get(), buffer.data(), &num_bytes, nullptr,
+                      &num_handles, MOJO_READ_MESSAGE_FLAG_NONE);
+  CHECK_EQ(MOJO_RESULT_OK, rv);
+  return std::string(buffer.data(), buffer.size());
+}
+
+class ThreadedRunner : public base::SimpleThread {
+ public:
+  explicit ThreadedRunner(const base::Closure& callback)
+      : SimpleThread("ThreadedRunner"), callback_(callback) {}
+  ~ThreadedRunner() override { Join(); }
+
+  void Run() override { callback_.Run(); }
+
+ private:
+  const base::Closure callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(ThreadedRunner);
+};
+
+TEST_F(WaitTest, InvalidArguments) {
+  Handle invalid_handle;
+
+  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+            Wait(invalid_handle, MOJO_HANDLE_SIGNAL_READABLE));
+
+  MessagePipe p;
+  Handle valid_handles[2] = {p.handle0.get(), p.handle1.get()};
+  Handle invalid_handles[2];
+  MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_NONE,
+                                  MOJO_HANDLE_SIGNAL_NONE};
+  size_t result_index = 0;
+  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+            WaitMany(invalid_handles, signals, 2, &result_index));
+  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+            WaitMany(nullptr, signals, 2, &result_index));
+  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+            WaitMany(valid_handles, nullptr, 2, &result_index));
+}
+
+TEST_F(WaitTest, Basic) {
+  MessagePipe p;
+
+  // Write to one end of the pipe and wait on the other.
+  const char kTestMessage1[] = "how about a nice game of chess?";
+  WriteMessage(p.handle0, kTestMessage1);
+  EXPECT_EQ(MOJO_RESULT_OK, Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE));
+
+  // And make sure we can also grab the handle signals state (with both the C
+  // and C++ library structs.)
+
+  MojoHandleSignalsState c_hss = {0, 0};
+  EXPECT_EQ(MOJO_RESULT_OK,
+            Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &c_hss));
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+            c_hss.satisfied_signals);
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+                MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+            c_hss.satisfiable_signals);
+
+  HandleSignalsState hss;
+  EXPECT_EQ(MOJO_RESULT_OK,
+            Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss));
+  EXPECT_TRUE(hss.readable() && hss.writable() && !hss.peer_closed());
+  EXPECT_FALSE(hss.never_readable() || hss.never_writable() ||
+               hss.never_peer_closed());
+
+  // Now close the writing end and wait for peer closure.
+
+  p.handle0.reset();
+  EXPECT_EQ(MOJO_RESULT_OK,
+            Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss));
+
+  // Still readable as there's still a message queued. No longer writable as
+  // peer closure has been detected.
+  EXPECT_TRUE(hss.readable() && hss.peer_closed() && !hss.writable());
+  EXPECT_TRUE(hss.never_writable() && !hss.never_readable() &&
+              !hss.never_peer_closed());
+
+  // Read the message and wait for readable again. Waiting should fail since
+  // there are no more messages and the peer is closed.
+  EXPECT_EQ(kTestMessage1, ReadMessage(p.handle1));
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss));
+
+  // Sanity check the signals state again.
+  EXPECT_TRUE(hss.peer_closed() && !hss.readable() && !hss.writable());
+  EXPECT_TRUE(hss.never_readable() && hss.never_writable() &&
+              !hss.never_peer_closed());
+}
+
+TEST_F(WaitTest, DelayedWrite) {
+  MessagePipe p;
+
+  ThreadedRunner write_after_delay(base::Bind(
+      [](ScopedMessagePipeHandle* handle) {
+        // Wait a little while, then write a message.
+        base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+        WriteMessage(*handle, "wakey wakey");
+      },
+      &p.handle0));
+  write_after_delay.Start();
+
+  HandleSignalsState hss;
+  EXPECT_EQ(MOJO_RESULT_OK,
+            Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss));
+  EXPECT_TRUE(hss.readable() && hss.writable() && !hss.peer_closed());
+  EXPECT_TRUE(!hss.never_readable() && !hss.never_writable() &&
+              !hss.never_peer_closed());
+}
+
+TEST_F(WaitTest, DelayedPeerClosure) {
+  MessagePipe p;
+
+  ThreadedRunner close_after_delay(base::Bind(
+      [](ScopedMessagePipeHandle* handle) {
+        // Wait a little while, then close the handle.
+        base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+        handle->reset();
+      },
+      &p.handle0));
+  close_after_delay.Start();
+
+  HandleSignalsState hss;
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss));
+  EXPECT_TRUE(!hss.readable() && !hss.writable() && hss.peer_closed());
+  EXPECT_TRUE(hss.never_readable() && hss.never_writable() &&
+              !hss.never_peer_closed());
+}
+
+TEST_F(WaitTest, CloseWhileWaiting) {
+  MessagePipe p;
+  ThreadedRunner close_after_delay(base::Bind(
+      [](ScopedMessagePipeHandle* handle) {
+        base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+        handle->reset();
+      },
+      &p.handle0));
+  close_after_delay.Start();
+  EXPECT_EQ(MOJO_RESULT_CANCELLED,
+            Wait(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE));
+}
+
+TEST_F(WaitManyTest, Basic) {
+  MessagePipe p;
+
+  const char kTestMessage1[] = "hello";
+  WriteMessage(p.handle0, kTestMessage1);
+
+  // Wait for either handle to become readable. Wait twice, just to verify that
+  // we can use either the C or C++ signaling state structure for the last
+  // argument.
+
+  Handle handles[2] = {p.handle0.get(), p.handle1.get()};
+  MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE,
+                                  MOJO_HANDLE_SIGNAL_READABLE};
+  size_t result_index = 0;
+  MojoHandleSignalsState c_hss[2];
+  HandleSignalsState hss[2];
+
+  EXPECT_EQ(MOJO_RESULT_OK,
+            WaitMany(handles, signals, 2, &result_index, c_hss));
+  EXPECT_EQ(1u, result_index);
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, c_hss[0].satisfied_signals);
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+                MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+            c_hss[0].satisfiable_signals);
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+            c_hss[1].satisfied_signals);
+  EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+                MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+            c_hss[1].satisfiable_signals);
+
+  EXPECT_EQ(MOJO_RESULT_OK, WaitMany(handles, signals, 2, &result_index, hss));
+  EXPECT_EQ(1u, result_index);
+  EXPECT_TRUE(!hss[0].readable() && hss[0].writable() && !hss[0].peer_closed());
+  EXPECT_TRUE(!hss[0].never_readable() && !hss[0].never_writable() &&
+              !hss[0].never_peer_closed());
+  EXPECT_TRUE(hss[1].readable() && hss[1].writable() && !hss[1].peer_closed());
+  EXPECT_TRUE(!hss[1].never_readable() && !hss[1].never_writable() &&
+              !hss[1].never_peer_closed());
+
+  // Close the writer and read the message. Try to wait again, and it should
+  // fail due to the conditions being unsatisfiable.
+
+  EXPECT_EQ(kTestMessage1, ReadMessage(p.handle1));
+  p.handle0.reset();
+
+  // handles[0] is invalid.
+  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+            WaitMany(handles, signals, 2, &result_index, hss));
+  handles[0] = handles[1];
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            WaitMany(handles, signals, 1, &result_index, hss));
+  EXPECT_EQ(0u, result_index);
+  EXPECT_TRUE(!hss[0].readable() && !hss[0].writable() && hss[0].peer_closed());
+  EXPECT_TRUE(hss[0].never_readable() && hss[0].never_writable() &&
+              !hss[0].never_peer_closed());
+}
+
+TEST_F(WaitManyTest, CloseWhileWaiting) {
+  MessagePipe p, q;
+
+  Handle handles[3] = {q.handle0.get(), q.handle1.get(), p.handle1.get()};
+  MojoHandleSignals signals[3] = {MOJO_HANDLE_SIGNAL_READABLE,
+                                  MOJO_HANDLE_SIGNAL_READABLE,
+                                  MOJO_HANDLE_SIGNAL_READABLE};
+
+  ThreadedRunner close_after_delay(base::Bind(
+      [](ScopedMessagePipeHandle* handle) {
+        base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+        handle->reset();
+      },
+      &p.handle1));
+  close_after_delay.Start();
+
+  size_t result_index = 0;
+  EXPECT_EQ(MOJO_RESULT_CANCELLED,
+            WaitMany(handles, signals, 3, &result_index));
+  EXPECT_EQ(2u, result_index);
+}
+
+TEST_F(WaitManyTest, DelayedWrite) {
+  MessagePipe p;
+
+  ThreadedRunner write_after_delay(base::Bind(
+      [](ScopedMessagePipeHandle* handle) {
+        // Wait a little while, then write a message.
+        base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+        WriteMessage(*handle, "wakey wakey");
+      },
+      &p.handle0));
+  write_after_delay.Start();
+
+  Handle handles[2] = {p.handle0.get(), p.handle1.get()};
+  MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE,
+                                  MOJO_HANDLE_SIGNAL_READABLE};
+  size_t result_index = 0;
+  HandleSignalsState hss[2];
+  EXPECT_EQ(MOJO_RESULT_OK, WaitMany(handles, signals, 2, &result_index, hss));
+  EXPECT_EQ(1u, result_index);
+  EXPECT_TRUE(!hss[0].readable() && hss[0].writable() && !hss[0].peer_closed());
+  EXPECT_TRUE(!hss[0].never_readable() && !hss[0].never_writable() &&
+              !hss[0].never_peer_closed());
+  EXPECT_TRUE(hss[1].readable() && hss[1].writable() && !hss[1].peer_closed());
+  EXPECT_TRUE(!hss[1].never_readable() && !hss[1].never_writable() &&
+              !hss[1].never_peer_closed());
+}
+
+TEST_F(WaitManyTest, DelayedPeerClosure) {
+  MessagePipe p, q;
+
+  ThreadedRunner close_after_delay(base::Bind(
+      [](ScopedMessagePipeHandle* handle) {
+        // Wait a little while, then close the handle.
+        base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+        handle->reset();
+      },
+      &p.handle0));
+  close_after_delay.Start();
+
+  Handle handles[3] = {q.handle0.get(), q.handle1.get(), p.handle1.get()};
+  MojoHandleSignals signals[3] = {MOJO_HANDLE_SIGNAL_READABLE,
+                                  MOJO_HANDLE_SIGNAL_READABLE,
+                                  MOJO_HANDLE_SIGNAL_READABLE};
+  size_t result_index = 0;
+  HandleSignalsState hss[3];
+  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+            WaitMany(handles, signals, 3, &result_index, hss));
+  EXPECT_EQ(2u, result_index);
+  EXPECT_TRUE(!hss[2].readable() && !hss[2].writable() && hss[2].peer_closed());
+  EXPECT_TRUE(hss[2].never_readable() && hss[2].never_writable() &&
+              !hss[2].never_peer_closed());
+}
+
+}  // namespace
+}  // namespace mojo
diff --git a/mojo/public/cpp/system/tests/watcher_unittest.cc b/mojo/public/cpp/system/tests/watcher_unittest.cc
deleted file mode 100644
index 9b59240..0000000
--- a/mojo/public/cpp/system/tests/watcher_unittest.cc
+++ /dev/null
@@ -1,181 +0,0 @@
-// 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.
-
-#include "mojo/public/cpp/system/watcher.h"
-
-#include <memory>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/macros.h"
-#include "base/message_loop/message_loop.h"
-#include "base/run_loop.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "mojo/public/c/system/types.h"
-#include "mojo/public/cpp/system/message_pipe.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace mojo {
-namespace {
-
-template <typename Handler>
-void RunResultHandler(Handler f, MojoResult result) { f(result); }
-
-template <typename Handler>
-Watcher::ReadyCallback OnReady(Handler f) {
-  return base::Bind(&RunResultHandler<Handler>, f);
-}
-
-Watcher::ReadyCallback NotReached() {
-  return OnReady([] (MojoResult) { NOTREACHED(); });
-}
-
-class WatcherTest : public testing::Test {
- public:
-  WatcherTest() {}
-  ~WatcherTest() override {}
-
- private:
-  base::MessageLoop message_loop_;
-
-  DISALLOW_COPY_AND_ASSIGN(WatcherTest);
-};
-
-TEST_F(WatcherTest, WatchBasic) {
-  ScopedMessagePipeHandle a, b;
-  CreateMessagePipe(nullptr, &a, &b);
-
-  bool notified = false;
-  base::RunLoop run_loop;
-  Watcher b_watcher(FROM_HERE);
-  EXPECT_EQ(MOJO_RESULT_OK,
-            b_watcher.Start(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
-                            OnReady([&] (MojoResult result) {
-                              EXPECT_EQ(MOJO_RESULT_OK, result);
-                              notified = true;
-                              run_loop.Quit();
-                            })));
-  EXPECT_TRUE(b_watcher.IsWatching());
-
-  EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0,
-                                            MOJO_WRITE_MESSAGE_FLAG_NONE));
-  run_loop.Run();
-  EXPECT_TRUE(notified);
-
-  b_watcher.Cancel();
-}
-
-TEST_F(WatcherTest, WatchUnsatisfiable) {
-  ScopedMessagePipeHandle a, b;
-  CreateMessagePipe(nullptr, &a, &b);
-  a.reset();
-
-  Watcher b_watcher(FROM_HERE);
-  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
-            b_watcher.Start(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
-                            NotReached()));
-  EXPECT_FALSE(b_watcher.IsWatching());
-}
-
-TEST_F(WatcherTest, WatchInvalidHandle) {
-  ScopedMessagePipeHandle a, b;
-  CreateMessagePipe(nullptr, &a, &b);
-  a.reset();
-  b.reset();
-
-  Watcher b_watcher(FROM_HERE);
-  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
-            b_watcher.Start(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
-                            NotReached()));
-  EXPECT_FALSE(b_watcher.IsWatching());
-}
-
-TEST_F(WatcherTest, Cancel) {
-  ScopedMessagePipeHandle a, b;
-  CreateMessagePipe(nullptr, &a, &b);
-
-  base::RunLoop run_loop;
-  Watcher b_watcher(FROM_HERE);
-  EXPECT_EQ(MOJO_RESULT_OK,
-            b_watcher.Start(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
-                            NotReached()));
-  EXPECT_TRUE(b_watcher.IsWatching());
-  b_watcher.Cancel();
-  EXPECT_FALSE(b_watcher.IsWatching());
-
-  // This should never trigger the watcher.
-  EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0,
-                                            MOJO_WRITE_MESSAGE_FLAG_NONE));
-
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, run_loop.QuitClosure());
-  run_loop.Run();
-}
-
-TEST_F(WatcherTest, CancelOnClose) {
-  ScopedMessagePipeHandle a, b;
-  CreateMessagePipe(nullptr, &a, &b);
-
-  base::RunLoop run_loop;
-  Watcher b_watcher(FROM_HERE);
-  EXPECT_EQ(MOJO_RESULT_OK,
-            b_watcher.Start(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
-                            OnReady([&] (MojoResult result) {
-                              EXPECT_EQ(MOJO_RESULT_CANCELLED, result);
-                              run_loop.Quit();
-                            })));
-  EXPECT_TRUE(b_watcher.IsWatching());
-
-  // This should trigger the watcher above.
-  b.reset();
-
-  run_loop.Run();
-
-  EXPECT_FALSE(b_watcher.IsWatching());
-}
-
-TEST_F(WatcherTest, CancelOnDestruction) {
-  ScopedMessagePipeHandle a, b;
-  CreateMessagePipe(nullptr, &a, &b);
-  base::RunLoop run_loop;
-  {
-    Watcher b_watcher(FROM_HERE);
-    EXPECT_EQ(MOJO_RESULT_OK,
-              b_watcher.Start(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
-                              NotReached()));
-    EXPECT_TRUE(b_watcher.IsWatching());
-
-    // |b_watcher| should be cancelled when it goes out of scope.
-  }
-
-  // This should never trigger the watcher above.
-  EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0,
-                                            MOJO_WRITE_MESSAGE_FLAG_NONE));
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, run_loop.QuitClosure());
-  run_loop.Run();
-}
-
-TEST_F(WatcherTest, CloseAndCancel) {
-  ScopedMessagePipeHandle a, b;
-  CreateMessagePipe(nullptr, &a, &b);
-
-  Watcher b_watcher(FROM_HERE);
-  EXPECT_EQ(MOJO_RESULT_OK,
-            b_watcher.Start(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
-                            OnReady([](MojoResult result) { FAIL(); })));
-  EXPECT_TRUE(b_watcher.IsWatching());
-
-  // This should trigger the watcher above...
-  b.reset();
-  // ...but the watcher is cancelled first.
-  b_watcher.Cancel();
-
-  EXPECT_FALSE(b_watcher.IsWatching());
-
-  base::RunLoop().RunUntilIdle();
-}
-
-}  // namespace
-}  // namespace mojo
diff --git a/mojo/public/cpp/system/wait.cc b/mojo/public/cpp/system/wait.cc
new file mode 100644
index 0000000..e4e124f
--- /dev/null
+++ b/mojo/public/cpp/system/wait.cc
@@ -0,0 +1,200 @@
+// Copyright 2017 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 "mojo/public/cpp/system/wait.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
+#include "mojo/public/c/system/watcher.h"
+#include "mojo/public/cpp/system/watcher.h"
+
+namespace mojo {
+namespace {
+
+class WatchContext : public base::RefCountedThreadSafe<WatchContext> {
+ public:
+  WatchContext()
+      : event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+               base::WaitableEvent::InitialState::NOT_SIGNALED) {}
+
+  base::WaitableEvent& event() { return event_; }
+  MojoResult wait_result() const { return wait_result_; }
+  MojoHandleSignalsState wait_state() const { return wait_state_; }
+  uintptr_t context_value() const { return reinterpret_cast<uintptr_t>(this); }
+
+  static void OnNotification(uintptr_t context_value,
+                             MojoResult result,
+                             MojoHandleSignalsState state,
+                             MojoWatcherNotificationFlags flags) {
+    auto* context = reinterpret_cast<WatchContext*>(context_value);
+    context->Notify(result, state);
+    if (result == MOJO_RESULT_CANCELLED) {
+      // Balanced in Wait() or WaitMany().
+      context->Release();
+    }
+  }
+
+ private:
+  friend class base::RefCountedThreadSafe<WatchContext>;
+
+  ~WatchContext() {}
+
+  void Notify(MojoResult result, MojoHandleSignalsState state) {
+    if (wait_result_ == MOJO_RESULT_UNKNOWN) {
+      wait_result_ = result;
+      wait_state_ = state;
+    }
+    event_.Signal();
+  }
+
+  base::WaitableEvent event_;
+
+  // NOTE: Although these are modified in Notify() which may be called from any
+  // thread, Notify() is guaranteed to never run concurrently with itself.
+  // Furthermore, they are only modified once, before |event_| signals; so there
+  // is no need for a WatchContext user to synchronize access to these fields
+  // apart from waiting on |event()|.
+  MojoResult wait_result_ = MOJO_RESULT_UNKNOWN;
+  MojoHandleSignalsState wait_state_ = {0, 0};
+
+  DISALLOW_COPY_AND_ASSIGN(WatchContext);
+};
+
+}  // namespace
+
+MojoResult Wait(Handle handle,
+                MojoHandleSignals signals,
+                MojoHandleSignalsState* signals_state) {
+  ScopedWatcherHandle watcher;
+  MojoResult rv = CreateWatcher(&WatchContext::OnNotification, &watcher);
+  DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+  scoped_refptr<WatchContext> context = new WatchContext;
+
+  // Balanced in WatchContext::OnNotification if MojoWatch() is successful.
+  // Otherwise balanced immediately below.
+  context->AddRef();
+
+  rv = MojoWatch(watcher.get().value(), handle.value(), signals,
+                 context->context_value());
+  if (rv == MOJO_RESULT_INVALID_ARGUMENT) {
+    // Balanced above.
+    context->Release();
+    return rv;
+  }
+  DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+  uint32_t num_ready_contexts = 1;
+  uintptr_t ready_context;
+  MojoResult ready_result;
+  MojoHandleSignalsState ready_state;
+  rv = MojoArmWatcher(watcher.get().value(), &num_ready_contexts,
+                      &ready_context, &ready_result, &ready_state);
+  if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
+    DCHECK_EQ(1u, num_ready_contexts);
+    if (signals_state)
+      *signals_state = ready_state;
+    return ready_result;
+  }
+
+  // Wait for the first notification only.
+  context->event().Wait();
+
+  ready_result = context->wait_result();
+  DCHECK_NE(MOJO_RESULT_UNKNOWN, ready_result);
+
+  if (signals_state)
+    *signals_state = context->wait_state();
+
+  return ready_result;
+}
+
+MojoResult WaitMany(const Handle* handles,
+                    const MojoHandleSignals* signals,
+                    size_t num_handles,
+                    size_t* result_index,
+                    MojoHandleSignalsState* signals_states) {
+  if (!handles || !signals)
+    return MOJO_RESULT_INVALID_ARGUMENT;
+
+  ScopedWatcherHandle watcher;
+  MojoResult rv = CreateWatcher(&WatchContext::OnNotification, &watcher);
+  DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+  std::vector<scoped_refptr<WatchContext>> contexts(num_handles);
+  std::vector<base::WaitableEvent*> events(num_handles);
+  for (size_t i = 0; i < num_handles; ++i) {
+    contexts[i] = new WatchContext();
+
+    // Balanced in WatchContext::OnNotification if MojoWatch() is successful.
+    // Otherwise balanced immediately below.
+    contexts[i]->AddRef();
+
+    MojoResult rv = MojoWatch(watcher.get().value(), handles[i].value(),
+                              signals[i], contexts[i]->context_value());
+    if (rv == MOJO_RESULT_INVALID_ARGUMENT) {
+      if (result_index)
+        *result_index = i;
+
+      // Balanced above.
+      contexts[i]->Release();
+
+      return MOJO_RESULT_INVALID_ARGUMENT;
+    }
+
+    events[i] = &contexts[i]->event();
+  }
+
+  uint32_t num_ready_contexts = 1;
+  uintptr_t ready_context = 0;
+  MojoResult ready_result = MOJO_RESULT_UNKNOWN;
+  MojoHandleSignalsState ready_state{0, 0};
+  rv = MojoArmWatcher(watcher.get().value(), &num_ready_contexts,
+                      &ready_context, &ready_result, &ready_state);
+
+  size_t index = num_handles;
+  if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
+    DCHECK_EQ(1u, num_ready_contexts);
+
+    // Most commonly we only watch a small number of handles. Just scan for
+    // the right index.
+    for (size_t i = 0; i < num_handles; ++i) {
+      if (contexts[i]->context_value() == ready_context) {
+        index = i;
+        break;
+      }
+    }
+  } else {
+    DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+    // Wait for one of the contexts to signal. First one wins.
+    index = base::WaitableEvent::WaitMany(events.data(), events.size());
+    ready_result = contexts[index]->wait_result();
+    ready_state = contexts[index]->wait_state();
+  }
+
+  DCHECK_NE(MOJO_RESULT_UNKNOWN, ready_result);
+  DCHECK_LT(index, num_handles);
+
+  if (result_index)
+    *result_index = index;
+
+  if (signals_states) {
+    for (size_t i = 0; i < num_handles; ++i) {
+      if (i == index) {
+        signals_states[i] = ready_state;
+      } else {
+        signals_states[i] = handles[i].QuerySignalsState();
+      }
+    }
+  }
+
+  return ready_result;
+}
+
+}  // namespace mojo
diff --git a/mojo/public/cpp/system/wait.h b/mojo/public/cpp/system/wait.h
new file mode 100644
index 0000000..808e44f
--- /dev/null
+++ b/mojo/public/cpp/system/wait.h
@@ -0,0 +1,75 @@
+// Copyright 2017 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 MOJO_PUBLIC_CPP_SYSTEM_WAIT_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_WAIT_H_
+
+#include <stddef.h>
+
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace mojo {
+
+// Blocks the calling thread, waiting for one or more signals in |signals| to be
+// become satisfied -- or for all of them to become unsatisfiable -- on the
+// given Handle.
+//
+// If |signals_state| is non-null, |handle| is valid, the wait is not cancelled
+// (see return values below), the last known signaling state of |handle| is
+// written to |*signals_state| before returning.
+//
+// Return values:
+//   |MOJO_RESULT_OK| if one or more signals in |signals| has been raised on
+//       |handle| .
+//   |MOJO_RESULT_FAILED_PRECONDITION| if the state of |handle| changes such
+//       that no signals in |signals| can ever be raised again.
+//   |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle.
+//   |MOJO_RESULT_CANCELLED| if the wait was cancelled because |handle| was
+//       closed by some other thread while waiting.
+MOJO_CPP_SYSTEM_EXPORT MojoResult
+Wait(Handle handle,
+     MojoHandleSignals signals,
+     MojoHandleSignalsState* signals_state = nullptr);
+
+// Waits on |handles[0]|, ..., |handles[num_handles-1]| until:
+//  - At least one handle satisfies a signal indicated in its respective
+//    |signals[0]|, ..., |signals[num_handles-1]|.
+//  - It becomes known that no signal in some |signals[i]| will ever be
+//    satisfied.
+//
+// This means that |WaitMany()| behaves as if |Wait()| were called on each
+// handle/signals pair simultaneously, completing when the first |Wait()| would
+// complete.
+//
+// If |signals_states| is non-null, all other arguments are valid, and the wait
+// is not cancelled (see return values below), the last known signaling state of
+// each Handle |handles[i]| is written to its corresponding entry in
+// |signals_states[i]| before returning.
+//
+// Returns values:
+//   |MOJO_RESULT_OK| if one of the Handles in |handles| had one or more of its
+//       correpsonding signals satisfied. |*result_index| contains the index
+//       of the Handle in question if |result_index| is non-null.
+//   |MOJO_RESULT_FAILED_PRECONDITION| if one of the Handles in |handles|
+//       changes state such that its corresponding signals become permanently
+//       unsatisfiable. |*result_index| contains the index of the handle in
+//       question if |result_index| is non-null.
+//   |MOJO_RESULT_INVALID_ARGUMENT| if any Handle in |handles| is invalid,
+//       or if either |handles| or |signals| is null.
+//   |MOJO_RESULT_CANCELLED| if the wait was cancelled because a handle in
+//       |handles| was closed by some other thread while waiting.
+//       |*result_index| contains the index of the closed Handle if
+//       |result_index| is non-null.
+MOJO_CPP_SYSTEM_EXPORT MojoResult
+WaitMany(const Handle* handles,
+         const MojoHandleSignals* signals,
+         size_t num_handles,
+         size_t* result_index = nullptr,
+         MojoHandleSignalsState* signals_states = nullptr);
+
+}  // namespace mojo
+
+#endif  // MOJO_PUBLIC_CPP_SYSTEM_WAIT_H_
diff --git a/mojo/public/cpp/system/wait_set.cc b/mojo/public/cpp/system/wait_set.cc
new file mode 100644
index 0000000..1728f81
--- /dev/null
+++ b/mojo/public/cpp/system/wait_set.cc
@@ -0,0 +1,371 @@
+// Copyright 2017 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 "mojo/public/cpp/system/wait_set.h"
+
+#include <algorithm>
+#include <limits>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/containers/stack_container.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "mojo/public/cpp/system/watcher.h"
+
+namespace mojo {
+
+class WaitSet::State : public base::RefCountedThreadSafe<State> {
+ public:
+  State()
+      : handle_event_(base::WaitableEvent::ResetPolicy::MANUAL,
+                      base::WaitableEvent::InitialState::NOT_SIGNALED) {
+    MojoResult rv = CreateWatcher(&Context::OnNotification, &watcher_handle_);
+    DCHECK_EQ(MOJO_RESULT_OK, rv);
+  }
+
+  void ShutDown() {
+    // NOTE: This may immediately invoke Notify for every context.
+    watcher_handle_.reset();
+  }
+
+  MojoResult AddEvent(base::WaitableEvent* event) {
+    auto result = user_events_.insert(event);
+    if (result.second)
+      return MOJO_RESULT_OK;
+    return MOJO_RESULT_ALREADY_EXISTS;
+  }
+
+  MojoResult RemoveEvent(base::WaitableEvent* event) {
+    auto it = user_events_.find(event);
+    if (it == user_events_.end())
+      return MOJO_RESULT_NOT_FOUND;
+    user_events_.erase(it);
+    return MOJO_RESULT_OK;
+  }
+
+  MojoResult AddHandle(Handle handle, MojoHandleSignals signals) {
+    DCHECK(watcher_handle_.is_valid());
+
+    scoped_refptr<Context> context = new Context(this, handle);
+
+    {
+      base::AutoLock lock(lock_);
+
+      if (handle_to_context_.count(handle))
+        return MOJO_RESULT_ALREADY_EXISTS;
+      DCHECK(!contexts_.count(context->context_value()));
+
+      handle_to_context_[handle] = context;
+      contexts_[context->context_value()] = context;
+    }
+
+    // Balanced in State::Notify() with MOJO_RESULT_CANCELLED if
+    // MojoWatch() succeeds. Otherwise balanced immediately below.
+    context->AddRef();
+
+    // This can notify immediately if the watcher is already armed. Don't hold
+    // |lock_| while calling it.
+    MojoResult rv = MojoWatch(watcher_handle_.get().value(), handle.value(),
+                              signals, context->context_value());
+    if (rv == MOJO_RESULT_INVALID_ARGUMENT) {
+      base::AutoLock lock(lock_);
+      handle_to_context_.erase(handle);
+      contexts_.erase(context->context_value());
+
+      // Balanced above.
+      context->Release();
+      return rv;
+    }
+    DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+    return rv;
+  }
+
+  MojoResult RemoveHandle(Handle handle) {
+    DCHECK(watcher_handle_.is_valid());
+
+    scoped_refptr<Context> context;
+    {
+      base::AutoLock lock(lock_);
+      auto it = handle_to_context_.find(handle);
+      if (it == handle_to_context_.end())
+        return MOJO_RESULT_NOT_FOUND;
+
+      context = std::move(it->second);
+      handle_to_context_.erase(it);
+
+      // Ensure that we never return this handle as a ready result again. Note
+      // that it's removal from |handle_to_context_| above ensures it will never
+      // be added back to this map.
+      ready_handles_.erase(handle);
+    }
+
+    // NOTE: This may enter the notification callback immediately, so don't hold
+    // |lock_| while calling it.
+    MojoResult rv = MojoCancelWatch(watcher_handle_.get().value(),
+                                    context->context_value());
+
+    // We don't really care whether or not this succeeds. In either case, the
+    // context was or will imminently be cancelled and moved from |contexts_|
+    // to |cancelled_contexts_|.
+    DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_NOT_FOUND);
+
+    {
+      // Always clear |cancelled_contexts_| in case it's accumulated any more
+      // entries since the last time we ran.
+      base::AutoLock lock(lock_);
+      cancelled_contexts_.clear();
+    }
+
+    return rv;
+  }
+
+  void Wait(base::WaitableEvent** ready_event,
+            size_t* num_ready_handles,
+            Handle* ready_handles,
+            MojoResult* ready_results,
+            MojoHandleSignalsState* signals_states) {
+    DCHECK(watcher_handle_.is_valid());
+    DCHECK(num_ready_handles);
+    DCHECK(ready_handles);
+    DCHECK(ready_results);
+    {
+      base::AutoLock lock(lock_);
+      if (ready_handles_.empty()) {
+        // No handles are currently in the ready set. Make sure the event is
+        // reset and try to arm the watcher.
+        handle_event_.Reset();
+
+        DCHECK_LE(*num_ready_handles, std::numeric_limits<uint32_t>::max());
+        uint32_t num_ready_contexts = static_cast<uint32_t>(*num_ready_handles);
+
+        base::StackVector<uintptr_t, 4> ready_contexts;
+        ready_contexts.container().resize(num_ready_contexts);
+        base::StackVector<MojoHandleSignalsState, 4> ready_states;
+        MojoHandleSignalsState* out_states = signals_states;
+        if (!out_states) {
+          // If the caller didn't provide a buffer for signal states, we provide
+          // our own locally. MojoArmWatcher() requires one if we want to handle
+          // arming failure properly.
+          ready_states.container().resize(num_ready_contexts);
+          out_states = ready_states.container().data();
+        }
+        MojoResult rv = MojoArmWatcher(
+            watcher_handle_.get().value(), &num_ready_contexts,
+            ready_contexts.container().data(), ready_results, out_states);
+
+        if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
+          // Simulate the handles becoming ready. We do this in lieu of
+          // returning the results immediately so as to avoid potentially
+          // starving user events. i.e., we always want to call WaitMany()
+          // below.
+          handle_event_.Signal();
+          for (size_t i = 0; i < num_ready_contexts; ++i) {
+            auto it = contexts_.find(ready_contexts.container()[i]);
+            DCHECK(it != contexts_.end());
+            ready_handles_[it->second->handle()] = {ready_results[i],
+                                                    out_states[i]};
+          }
+        } else if (rv == MOJO_RESULT_NOT_FOUND) {
+          // Nothing to watch. If there are no user events, always signal to
+          // avoid deadlock.
+          if (user_events_.empty())
+            handle_event_.Signal();
+        } else {
+          // Watcher must be armed now. No need to manually signal.
+          DCHECK_EQ(MOJO_RESULT_OK, rv);
+        }
+      }
+    }
+
+    // Build a local contiguous array of events to wait on. These are rotated
+    // across Wait() calls to avoid starvation, by virtue of the fact that
+    // WaitMany guarantees left-to-right priority when multiple events are
+    // signaled.
+
+    base::StackVector<base::WaitableEvent*, 4> events;
+    events.container().resize(user_events_.size() + 1);
+    if (waitable_index_shift_ > user_events_.size())
+      waitable_index_shift_ = 0;
+
+    size_t dest_index = waitable_index_shift_++;
+    events.container()[dest_index] = &handle_event_;
+    for (auto* e : user_events_) {
+      dest_index = (dest_index + 1) % events.container().size();
+      events.container()[dest_index] = e;
+    }
+
+    size_t index = base::WaitableEvent::WaitMany(events.container().data(),
+                                                 events.container().size());
+    base::AutoLock lock(lock_);
+
+    // Pop as many handles as we can out of the ready set and return them. Note
+    // that we do this regardless of which event signaled, as there may be
+    // ready handles in any case and they may be interesting to the caller.
+    *num_ready_handles = std::min(*num_ready_handles, ready_handles_.size());
+    for (size_t i = 0; i < *num_ready_handles; ++i) {
+      auto it = ready_handles_.begin();
+      ready_handles[i] = it->first;
+      ready_results[i] = it->second.result;
+      if (signals_states)
+        signals_states[i] = it->second.signals_state;
+      ready_handles_.erase(it);
+    }
+
+    // If the caller cares, let them know which user event unblocked us, if any.
+    if (ready_event) {
+      if (events.container()[index] == &handle_event_)
+        *ready_event = nullptr;
+      else
+        *ready_event = events.container()[index];
+    }
+  }
+
+ private:
+  friend class base::RefCountedThreadSafe<State>;
+
+  class Context : public base::RefCountedThreadSafe<Context> {
+   public:
+    Context(scoped_refptr<State> state, Handle handle)
+        : state_(state), handle_(handle) {}
+
+    Handle handle() const { return handle_; }
+
+    uintptr_t context_value() const {
+      return reinterpret_cast<uintptr_t>(this);
+    }
+
+    static void OnNotification(uintptr_t context,
+                               MojoResult result,
+                               MojoHandleSignalsState signals_state,
+                               MojoWatcherNotificationFlags flags) {
+      reinterpret_cast<Context*>(context)->Notify(result, signals_state);
+    }
+
+   private:
+    friend class base::RefCountedThreadSafe<Context>;
+
+    ~Context() {}
+
+    void Notify(MojoResult result, MojoHandleSignalsState signals_state) {
+      state_->Notify(handle_, result, signals_state, this);
+    }
+
+    const scoped_refptr<State> state_;
+    const Handle handle_;
+
+    DISALLOW_COPY_AND_ASSIGN(Context);
+  };
+
+  ~State() {}
+
+  void Notify(Handle handle,
+              MojoResult result,
+              MojoHandleSignalsState signals_state,
+              Context* context) {
+    base::AutoLock lock(lock_);
+
+    // This could be a cancellation notification following an explicit
+    // RemoveHandle(), in which case we really don't care and don't want to
+    // add it to the ready set. Only update and signal if that's not the case.
+    if (!handle_to_context_.count(handle)) {
+      DCHECK_EQ(MOJO_RESULT_CANCELLED, result);
+    } else {
+      ready_handles_[handle] = {result, signals_state};
+      handle_event_.Signal();
+    }
+
+    // Whether it's an implicit or explicit cancellation, erase from |contexts_|
+    // and append to |cancelled_contexts_|.
+    if (result == MOJO_RESULT_CANCELLED) {
+      contexts_.erase(context->context_value());
+      handle_to_context_.erase(handle);
+
+      // NOTE: We retain a context ref in |cancelled_contexts_| to ensure that
+      // this Context's heap address is not reused too soon. For example, it
+      // would otherwise be possible for the user to call AddHandle() from the
+      // WaitSet's thread immediately after this notification has fired on
+      // another thread, potentially reusing the same heap address for the newly
+      // added Context; and then they may call RemoveHandle() for this handle
+      // (not knowing its context has just been implicitly cancelled) and
+      // cause the new Context to be incorrectly removed from |contexts_|.
+      //
+      // This vector is cleared on the WaitSet's own thread every time
+      // RemoveHandle is called.
+      cancelled_contexts_.emplace_back(make_scoped_refptr(context));
+
+      // Balanced in State::AddHandle().
+      context->Release();
+    }
+  }
+
+  struct ReadyState {
+    ReadyState() = default;
+    ReadyState(MojoResult result, MojoHandleSignalsState signals_state)
+        : result(result), signals_state(signals_state) {}
+    ~ReadyState() = default;
+
+    MojoResult result = MOJO_RESULT_UNKNOWN;
+    MojoHandleSignalsState signals_state = {0, 0};
+  };
+
+  // Not guarded by lock. Must only be accessed from the WaitSet's owning
+  // thread.
+  ScopedWatcherHandle watcher_handle_;
+
+  base::Lock lock_;
+  std::map<uintptr_t, scoped_refptr<Context>> contexts_;
+  std::map<Handle, scoped_refptr<Context>> handle_to_context_;
+  std::map<Handle, ReadyState> ready_handles_;
+  std::vector<scoped_refptr<Context>> cancelled_contexts_;
+  std::set<base::WaitableEvent*> user_events_;
+
+  // Event signaled any time a handle notification is received.
+  base::WaitableEvent handle_event_;
+
+  // Offset by which to rotate the current set of waitable objects. This is used
+  // to guard against event starvation, as base::WaitableEvent::WaitMany gives
+  // preference to events in left-to-right order.
+  size_t waitable_index_shift_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(State);
+};
+
+WaitSet::WaitSet() : state_(new State) {}
+
+WaitSet::~WaitSet() {
+  state_->ShutDown();
+}
+
+MojoResult WaitSet::AddEvent(base::WaitableEvent* event) {
+  return state_->AddEvent(event);
+}
+
+MojoResult WaitSet::RemoveEvent(base::WaitableEvent* event) {
+  return state_->RemoveEvent(event);
+}
+
+MojoResult WaitSet::AddHandle(Handle handle, MojoHandleSignals signals) {
+  return state_->AddHandle(handle, signals);
+}
+
+MojoResult WaitSet::RemoveHandle(Handle handle) {
+  return state_->RemoveHandle(handle);
+}
+
+void WaitSet::Wait(base::WaitableEvent** ready_event,
+                   size_t* num_ready_handles,
+                   Handle* ready_handles,
+                   MojoResult* ready_results,
+                   MojoHandleSignalsState* signals_states) {
+  state_->Wait(ready_event, num_ready_handles, ready_handles, ready_results,
+               signals_states);
+}
+
+}  // namespace mojo
diff --git a/mojo/public/cpp/system/wait_set.h b/mojo/public/cpp/system/wait_set.h
new file mode 100644
index 0000000..5047a86
--- /dev/null
+++ b/mojo/public/cpp/system/wait_set.h
@@ -0,0 +1,124 @@
+// Copyright 2017 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 MOJO_PUBLIC_CPP_SYSTEM_WAIT_SET_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_WAIT_SET_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace base {
+class WaitableEvent;
+}
+
+namespace mojo {
+
+// WaitSet provides an efficient means of blocking a thread on any number of
+// events and Mojo handle state changes.
+//
+// Unlike WaitMany(), which incurs some extra setup cost for every call, a
+// WaitSet maintains some persistent accounting of the handles added or removed
+// from the set. A blocking wait operation (see  the Wait() method  below) can
+// then be performed multiple times for the same set of events and handles with
+// minimal additional setup per call.
+//
+// WaitSet is NOT thread-safe, so naturally handles and events may not be added
+// to or removed from the set while waiting.
+class MOJO_CPP_SYSTEM_EXPORT WaitSet {
+ public:
+  WaitSet();
+  ~WaitSet();
+
+  // Adds |event| to the set of events to wait on. If successful, any future
+  // Wait() on this WaitSet will wake up if the event is signaled.
+  //
+  // |event| is not owned.
+  //
+  // Return values:
+  //   |MOJO_RESULT_OK| if |event| has been successfully added.
+  //   |MOJO_RESULT_ALREADY_EXISTS| if |event| is already in this WaitSet.
+  MojoResult AddEvent(base::WaitableEvent* event);
+
+  // Removes |event| from the set of events to wait on.
+  //
+  // Return values:
+  //   |MOJO_RESULT_OK| if |event| has been successfully added.
+  //   |MOJO_RESULT_NOT_FOUND| if |event| was not in the set.
+  MojoResult RemoveEvent(base::WaitableEvent* event);
+
+  // Adds |handle| to the set of handles to wait on. If successful, any future
+  // Wait() on this WaitSet will wake up in the event that one or more signals
+  // in |signals| becomes satisfied on |handle| or all of them become
+  // permanently unsatisfiable.
+  //
+  // Return values:
+  //   |MOJO_RESULT_OK| if |handle| has been successfully added.
+  //   |MOJO_RESULT_ALREADY_EXISTS| if |handle| is already in this WaitSet.
+  //   |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle.
+  MojoResult AddHandle(Handle handle, MojoHandleSignals signals);
+
+  // Removes |handle| from the set of handles to wait on. Future calls to
+  // Wait() will be unaffected by the state of this handle.
+  //
+  // Return values:
+  //   |MOJO_RESULT_OK| if |handle| has been successfully removed.
+  //   |MOJO_RESULT_NOT_FOUND| if |handle| was not in the set.
+  MojoResult RemoveHandle(Handle handle);
+
+  // Waits on the current set of handles, waking up when one more of them meets
+  // the signaling conditions which were specified when they were added via
+  // AddHandle() above.
+  //
+  // |*num_ready_handles| on input must specify the number of entries available
+  // for output storage in |ready_handles| and |ready_result| (which must both
+  // be non-null). If |signals_states| is non-null it must also point to enough
+  // storage for |*num_ready_handles| MojoHandleSignalsState structures.
+  //
+  // Upon return, |*num_ready_handles| will contain the total number of handles
+  // whose information is stored in the given output buffers.
+  //
+  // If |ready_event| is non-null and the Wait() was unblocked by a user event
+  // signaling, the address of the event which signaled will be placed in
+  // |*ready_event|. Note that this is not necessarily exclusive to one or more
+  // handles also being ready. If |ready_event| is non-null and no user event
+  // was signaled for this Wait(), |*ready_event| will be null upon return.
+  //
+  // Every entry in |ready_handles| on output corresponds to one of the handles
+  // whose signaling state termianted the Wait() operation. Every corresponding
+  // entry in |ready_results| indicates the status of a ready handle according
+  // to the following result codes:
+  //   |MOJO_RESULT_OK| one or more signals for the handle has been satisfied.
+  //   |MOJO_RESULT_FAILED_PRECONDITION| all of the signals for the handle have
+  //       become permanently unsatisfiable.
+  //   |MOJO_RESULT_CANCELLED| if the handle has been closed from another
+  //       thread. NOTE: It is important to recognize that this means the
+  //       corresponding value in |ready_handles| is either invalid, or valid
+  //       but referring to a different handle (i.e. has already been reused) by
+  //       the time Wait() returns. The handle in question is automatically
+  //       removed from the WaitSet.
+  void Wait(base::WaitableEvent** ready_event,
+            size_t* num_ready_handles,
+            Handle* ready_handles,
+            MojoResult* ready_results,
+            MojoHandleSignalsState* signals_states = nullptr);
+
+ private:
+  class State;
+
+  // Thread-safe state associated with this WaitSet. Used to aggregate
+  // notifications from watched handles.
+  scoped_refptr<State> state_;
+
+  DISALLOW_COPY_AND_ASSIGN(WaitSet);
+};
+
+}  // namespace mojo
+
+#endif  // MOJO_PUBLIC_CPP_SYSTEM_WAIT_SET_H_
diff --git a/mojo/public/cpp/system/watcher.cc b/mojo/public/cpp/system/watcher.cc
index 55dcf40..0c62ba8 100644
--- a/mojo/public/cpp/system/watcher.cc
+++ b/mojo/public/cpp/system/watcher.cc
@@ -4,114 +4,17 @@
 
 #include "mojo/public/cpp/system/watcher.h"
 
-#include "base/bind.h"
-#include "base/location.h"
-#include "base/macros.h"
-#include "base/trace_event/heap_profiler.h"
 #include "mojo/public/c/system/functions.h"
 
 namespace mojo {
 
-Watcher::Watcher(const tracked_objects::Location& from_here,
-                 scoped_refptr<base::SingleThreadTaskRunner> runner)
-    : task_runner_(std::move(runner)),
-      is_default_task_runner_(task_runner_ ==
-                              base::ThreadTaskRunnerHandle::Get()),
-      heap_profiler_tag_(from_here.file_name()),
-      weak_factory_(this) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
-  weak_self_ = weak_factory_.GetWeakPtr();
-}
-
-Watcher::~Watcher() {
-  if(IsWatching())
-    Cancel();
-}
-
-bool Watcher::IsWatching() const {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  return handle_.is_valid();
-}
-
-MojoResult Watcher::Start(Handle handle,
-                          MojoHandleSignals signals,
-                          const ReadyCallback& callback) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  DCHECK(!IsWatching());
-  DCHECK(!callback.is_null());
-
-  callback_ = callback;
-  handle_ = handle;
-  MojoResult result = MojoWatch(handle_.value(), signals,
-                                &Watcher::CallOnHandleReady,
-                                reinterpret_cast<uintptr_t>(this));
-  if (result != MOJO_RESULT_OK) {
-    handle_.set_value(kInvalidHandleValue);
-    callback_.Reset();
-    DCHECK(result == MOJO_RESULT_FAILED_PRECONDITION ||
-           result == MOJO_RESULT_INVALID_ARGUMENT);
-    return result;
-  }
-
-  return MOJO_RESULT_OK;
-}
-
-void Watcher::Cancel() {
-  DCHECK(thread_checker_.CalledOnValidThread());
-
-  // The watch may have already been cancelled if the handle was closed.
-  if (!handle_.is_valid())
-    return;
-
-  MojoResult result =
-      MojoCancelWatch(handle_.value(), reinterpret_cast<uintptr_t>(this));
-  // |result| may be MOJO_RESULT_INVALID_ARGUMENT if |handle_| has closed, but
-  // OnHandleReady has not yet been called.
-  DCHECK(result == MOJO_RESULT_INVALID_ARGUMENT || result == MOJO_RESULT_OK);
-  handle_.set_value(kInvalidHandleValue);
-  callback_.Reset();
-}
-
-void Watcher::OnHandleReady(MojoResult result) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-
-  ReadyCallback callback = callback_;
-  if (result == MOJO_RESULT_CANCELLED) {
-    handle_.set_value(kInvalidHandleValue);
-    callback_.Reset();
-  }
-
-  // NOTE: It's legal for |callback| to delete |this|.
-  if (!callback.is_null()) {
-    TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION event(heap_profiler_tag_);
-    callback.Run(result);
-  }
-}
-
-// static
-void Watcher::CallOnHandleReady(uintptr_t context,
-                                MojoResult result,
-                                MojoHandleSignalsState signals_state,
-                                MojoWatchNotificationFlags flags) {
-  // NOTE: It is safe to assume the Watcher still exists because this callback
-  // will never be run after the Watcher's destructor.
-  //
-  // TODO: Maybe we should also expose |signals_state| through the Watcher API.
-  // Current HandleWatcher users have no need for it, so it's omitted here.
-  Watcher* watcher = reinterpret_cast<Watcher*>(context);
-
-  if ((flags & MOJO_WATCH_NOTIFICATION_FLAG_FROM_SYSTEM) &&
-      watcher->task_runner_->RunsTasksOnCurrentThread() &&
-      watcher->is_default_task_runner_) {
-    // System notifications will trigger from the task runner passed to
-    // mojo::edk::InitIPCSupport(). In Chrome this happens to always be the
-    // default task runner for the IO thread.
-    watcher->OnHandleReady(result);
-  } else {
-    watcher->task_runner_->PostTask(
-        FROM_HERE,
-        base::Bind(&Watcher::OnHandleReady, watcher->weak_self_, result));
-  }
+MojoResult CreateWatcher(MojoWatcherCallback callback,
+                         ScopedWatcherHandle* watcher_handle) {
+  MojoHandle handle;
+  MojoResult rv = MojoCreateWatcher(callback, &handle);
+  if (rv == MOJO_RESULT_OK)
+    watcher_handle->reset(WatcherHandle(handle));
+  return rv;
 }
 
 }  // namespace mojo
diff --git a/mojo/public/cpp/system/watcher.h b/mojo/public/cpp/system/watcher.h
index 236788b..d0a2578 100644
--- a/mojo/public/cpp/system/watcher.h
+++ b/mojo/public/cpp/system/watcher.h
@@ -5,121 +5,33 @@
 #ifndef MOJO_PUBLIC_CPP_SYSTEM_WATCHER_H_
 #define MOJO_PUBLIC_CPP_SYSTEM_WATCHER_H_
 
-#include "base/callback.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
-#include "base/single_thread_task_runner.h"
-#include "base/threading/thread_checker.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "mojo/public/c/system/types.h"
+#include "mojo/public/c/system/watcher.h"
 #include "mojo/public/cpp/system/handle.h"
 #include "mojo/public/cpp/system/system_export.h"
 
 namespace mojo {
 
-// A Watcher watches a single Mojo handle for signal state changes.
-//
-// NOTE: Watchers may only be used on threads which have a running MessageLoop.
-class MOJO_CPP_SYSTEM_EXPORT Watcher {
+// A strongly-typed representation of a |MojoHandle| for a watcher.
+class WatcherHandle : public Handle {
  public:
-  // A callback to be called any time a watched handle changes state in some
-  // interesting way. The |result| argument indicates one of the following
-  // conditions depending on its value:
-  //
-  //   |MOJO_RESULT_OK|: One or more of the signals being watched is satisfied.
-  //
-  //   |MOJO_RESULT_FAILED_PRECONDITION|: None of the signals being watched can
-  //       ever be satisfied again.
-  //
-  //   |MOJO_RESULT_CANCELLED|: The handle has been closed and the watch has
-  //       been cancelled implicitly.
-  using ReadyCallback = base::Callback<void(MojoResult result)>;
+  WatcherHandle() = default;
+  explicit WatcherHandle(MojoHandle value) : Handle(value) {}
 
-  Watcher(const tracked_objects::Location& from_here,
-          scoped_refptr<base::SingleThreadTaskRunner> runner =
-              base::ThreadTaskRunnerHandle::Get());
-
-  // NOTE: This destructor automatically calls |Cancel()| if the Watcher is
-  // still active.
-  ~Watcher();
-
-  // Indicates if the Watcher is currently watching a handle.
-  bool IsWatching() const;
-
-  // Starts watching |handle|. A Watcher may only watch one handle at a time,
-  // but it is safe to call this more than once as long as the previous watch
-  // has been cancelled (i.e. |is_watching()| returns |false|.)
-  //
-  // If no signals in |signals| can ever be satisfied for |handle|, this returns
-  // |MOJO_RESULT_FAILED_PRECONDITION|.
-  //
-  // If |handle| is not a valid watchable (message or data pipe) handle, this
-  // returns |MOJO_RESULT_INVALID_ARGUMENT|.
-  //
-  // Otherwise |MOJO_RESULT_OK| is returned and the handle will be watched until
-  // closure or cancellation.
-  //
-  // Once the watch is started, |callback| may be called at any time on the
-  // current thread until |Cancel()| is called or the handle is closed.
-  //
-  // Destroying the Watcher implicitly calls |Cancel()|.
-  MojoResult Start(Handle handle,
-                   MojoHandleSignals signals,
-                   const ReadyCallback& callback);
-
-  // Cancels the current watch. Once this returns, the callback previously
-  // passed to |Start()| will never be called again for this Watcher.
-  void Cancel();
-
-  Handle handle() const { return handle_; }
-  ReadyCallback ready_callback() const { return callback_; }
-
- // Sets the tag used by the heap profiler.
- // |tag| must be a const string literal.
- void set_heap_profiler_tag(const char* heap_profiler_tag) {
-   heap_profiler_tag_ = heap_profiler_tag;
- }
-
- private:
-  void OnHandleReady(MojoResult result);
-
-  static void CallOnHandleReady(uintptr_t context,
-                                MojoResult result,
-                                MojoHandleSignalsState signals_state,
-                                MojoWatchNotificationFlags flags);
-
-  base::ThreadChecker thread_checker_;
-
-  // The TaskRunner of this Watcher's owning thread. This field is safe to
-  // access from any thread.
-  const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
-  // Whether |task_runner_| is the same as base::ThreadTaskRunnerHandle::Get()
-  // for the thread.
-  const bool is_default_task_runner_;
-
-  // A persistent weak reference to this Watcher which can be passed to the
-  // Dispatcher any time this object should be signalled. Safe to access (but
-  // not to dereference!) from any thread.
-  base::WeakPtr<Watcher> weak_self_;
-
-  // Fields below must only be accessed on the Watcher's owning thread.
-
-  // The handle currently under watch. Not owned.
-  Handle handle_;
-
-  // The callback to call when the handle is signaled.
-  ReadyCallback callback_;
-
-  // Tag used to ID memory allocations that originated from notifications in
-  // this watcher.
-  const char* heap_profiler_tag_ = nullptr;
-
-  base::WeakPtrFactory<Watcher> weak_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(Watcher);
+  // Copying and assignment allowed.
 };
 
+static_assert(sizeof(WatcherHandle) == sizeof(Handle),
+              "Bad size for C++ WatcherHandle");
+
+typedef ScopedHandleBase<WatcherHandle> ScopedWatcherHandle;
+static_assert(sizeof(ScopedWatcherHandle) == sizeof(WatcherHandle),
+              "Bad size for C++ ScopedWatcherHandle");
+
+MOJO_CPP_SYSTEM_EXPORT MojoResult
+CreateWatcher(MojoWatcherCallback callback,
+              ScopedWatcherHandle* watcher_handle);
+
 }  // namespace mojo
 
 #endif  // MOJO_PUBLIC_CPP_SYSTEM_WATCHER_H_
diff --git a/mojo/public/cpp/test_support/lib/test_utils.cc b/mojo/public/cpp/test_support/lib/test_utils.cc
index 92288c4..7fe6f02 100644
--- a/mojo/public/cpp/test_support/lib/test_utils.cc
+++ b/mojo/public/cpp/test_support/lib/test_utils.cc
@@ -8,6 +8,7 @@
 #include <stdint.h>
 
 #include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/system/wait.h"
 #include "mojo/public/cpp/test_support/test_support.h"
 
 namespace mojo {
@@ -41,8 +42,7 @@
         assert(false);  // Looping endlessly!?
         return false;
       }
-      rv = Wait(handle, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE,
-                nullptr);
+      rv = Wait(handle, MOJO_HANDLE_SIGNAL_READABLE);
       if (rv != MOJO_RESULT_OK)
         return false;
       did_wait = true;
diff --git a/mojo/public/interfaces/bindings/BUILD.gn b/mojo/public/interfaces/bindings/BUILD.gn
index eab75c1..c2cadcd 100644
--- a/mojo/public/interfaces/bindings/BUILD.gn
+++ b/mojo/public/interfaces/bindings/BUILD.gn
@@ -15,3 +15,15 @@
   export_define = "MOJO_CPP_BINDINGS_IMPLEMENTATION"
   export_header = "mojo/public/cpp/bindings/bindings_export.h"
 }
+
+# TODO(yzshen): Remove this target and use the one above once
+# |use_new_js_bindings| becomes true by default.
+mojom("new_bindings") {
+  visibility = []
+  sources = [
+    "new_bindings/interface_control_messages.mojom",
+    "new_bindings/pipe_control_messages.mojom",
+  ]
+
+  use_new_js_bindings = true
+}
diff --git a/mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom b/mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom
new file mode 100644
index 0000000..e03ffd6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom
@@ -0,0 +1,67 @@
+// Copyright 2015 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.
+
+[JavaPackage="org.chromium.mojo.bindings.interfacecontrol"]
+module mojo.interface_control2;
+
+// For each user-defined interface, some control functions are provided by the
+// interface endpoints at both sides.
+
+////////////////////////////////////////////////////////////////////////////////
+// Run@0xFFFFFFFF(RunInput input) => (RunOutput? output);
+//
+// This control function runs the input command. If the command is not
+// supported, |output| is set to null; otherwise |output| stores the result,
+// whose type depends on the input.
+
+const uint32 kRunMessageId = 0xFFFFFFFF;
+
+struct RunMessageParams {
+  RunInput input;
+};
+union RunInput {
+  QueryVersion query_version;
+  FlushForTesting flush_for_testing;
+};
+
+struct RunResponseMessageParams {
+  RunOutput? output;
+};
+union RunOutput {
+  QueryVersionResult query_version_result;
+};
+
+// Queries the max supported version of the user-defined interface.
+// Sent by the interface client side.
+struct QueryVersion {
+};
+struct QueryVersionResult {
+  uint32 version;
+};
+
+// Sent by either side of the interface.
+struct FlushForTesting {
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// RunOrClosePipe@0xFFFFFFFE(RunOrClosePipeInput input);
+//
+// This control function runs the input command. If the operation fails or the
+// command is not supported, the message pipe is closed.
+
+const uint32 kRunOrClosePipeMessageId = 0xFFFFFFFE;
+
+struct RunOrClosePipeMessageParams {
+  RunOrClosePipeInput input;
+};
+union RunOrClosePipeInput {
+  RequireVersion require_version;
+};
+
+// If the specified version of the user-defined interface is not supported, the
+// function fails and the pipe is closed.
+// Sent by the interface client side.
+struct RequireVersion {
+  uint32 version;
+};
diff --git a/mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom b/mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom
new file mode 100644
index 0000000..69975fc
--- /dev/null
+++ b/mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom
@@ -0,0 +1,46 @@
+// Copyright 2015 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.
+
+[JavaPackage="org.chromium.mojo.bindings.pipecontrol"]
+module mojo.pipe_control2;
+
+// For each message pipe running user-defined interfaces, some control
+// functions are provided and used by the routers at both ends of the pipe, so
+// that they can coordinate to manage interface endpoints.
+// All these control messages will have the interface ID field in the message
+// header set to invalid.
+
+////////////////////////////////////////////////////////////////////////////////
+// RunOrClosePipe@0xFFFFFFFE(RunOrClosePipeInput input);
+//
+// This control function runs the input command. If the operation fails or the
+// command is not supported, the message pipe is closed.
+
+const uint32 kRunOrClosePipeMessageId = 0xFFFFFFFE;
+
+struct RunOrClosePipeMessageParams {
+  RunOrClosePipeInput input;
+};
+
+union RunOrClosePipeInput {
+  PeerAssociatedEndpointClosedEvent peer_associated_endpoint_closed_event;
+};
+
+// A user-defined reason about why the interface is disconnected.
+struct DisconnectReason {
+  uint32 custom_reason;
+  string description;
+};
+
+// An event to notify that an interface endpoint set up at the message sender
+// side has been closed.
+//
+// This event is omitted if the endpoint belongs to the master interface and
+// there is no disconnect reason specified.
+struct PeerAssociatedEndpointClosedEvent {
+  // The interface ID.
+  uint32 id;
+  DisconnectReason? disconnect_reason;
+};
+
diff --git a/mojo/public/interfaces/bindings/tests/BUILD.gn b/mojo/public/interfaces/bindings/tests/BUILD.gn
index 9b10db0..e496eb6 100644
--- a/mojo/public/interfaces/bindings/tests/BUILD.gn
+++ b/mojo/public/interfaces/bindings/tests/BUILD.gn
@@ -23,9 +23,11 @@
     "test_native_types.mojom",
     "test_structs.mojom",
     "test_sync_methods.mojom",
+    "test_unions.mojom",
     "validation_test_interfaces.mojom",
   ]
   public_deps = [
+    ":echo",
     ":test_mojom_import",
     ":test_mojom_import2",
   ]
@@ -84,6 +86,31 @@
   }
 }
 
+# Used to test that it is okay to call mojom::Foo::Serialize()/Deserialize()
+# even if the mojom target is linked into another component.
+#
+# We don't use |test_export_component| for this test because
+# //mojo/public/cpp/bindings/tests depends on both |test_export_component| and
+# |test_exported_import| and therefore actually get the shared cpp sources of
+# test_export.mojom from |test_exported_import|.
+component("test_export_component2") {
+  testonly = true
+  public_deps = [
+    ":test_export2",
+  ]
+}
+
+mojom("test_export2") {
+  testonly = true
+  sources = [
+    "test_export2.mojom",
+  ]
+  export_class_attribute = "MOJO_TEST_EXPORT"
+  export_define = "MOJO_TEST_IMPLEMENTATION=1"
+  export_header = "mojo/public/cpp/bindings/tests/mojo_test_export.h"
+  visibility = [ ":test_export_component2" ]
+}
+
 mojom("test_mojom_import") {
   testonly = true
   sources = [
@@ -123,13 +150,6 @@
   ]
 }
 
-mojom("test_interfaces_experimental") {
-  testonly = true
-  sources = [
-    "test_unions.mojom",
-  ]
-}
-
 mojom("test_associated_interfaces") {
   # These files are not included in the test_interfaces target because
   # associated interfaces are not supported by all bindings languages yet.
@@ -173,3 +193,12 @@
     ":test_interfaces",
   ]
 }
+
+mojom("echo") {
+  testonly = true
+  sources = [
+    "echo.mojom",
+    "echo_import.mojom",
+  ]
+  use_new_js_bindings = true
+}
diff --git a/mojo/public/interfaces/bindings/tests/echo.mojom b/mojo/public/interfaces/bindings/tests/echo.mojom
new file mode 100644
index 0000000..56c6063
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/echo.mojom
@@ -0,0 +1,12 @@
+// Copyright 2017 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 test.echo.mojom;
+
+import "echo_import.mojom";
+
+interface Echo {
+  EchoPoint(test.echo_import.mojom.Point point)
+      => (test.echo_import.mojom.Point result);
+};
diff --git a/mojo/public/interfaces/bindings/tests/echo_import.mojom b/mojo/public/interfaces/bindings/tests/echo_import.mojom
new file mode 100644
index 0000000..a024ce2
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/echo_import.mojom
@@ -0,0 +1,10 @@
+// Copyright 2017 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 test.echo_import.mojom;
+
+struct Point {
+  int32 x;
+  int32 y;
+};
diff --git a/mojo/public/interfaces/bindings/tests/test_export2.mojom b/mojo/public/interfaces/bindings/tests/test_export2.mojom
new file mode 100644
index 0000000..011395c
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/test_export2.mojom
@@ -0,0 +1,10 @@
+// Copyright 2017 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 mojo.test.test_export2;
+
+struct StringPair {
+  string s1;
+  string s2;
+};
diff --git a/mojo/public/java/bindings/README.md b/mojo/public/java/bindings/README.md
new file mode 100644
index 0000000..821a230
--- /dev/null
+++ b/mojo/public/java/bindings/README.md
@@ -0,0 +1,12 @@
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Java Bindings API
+This document is a subset of the [Mojo documentation](/mojo).
+
+[TOC]
+
+## Overview
+
+This document provides a brief guide to API usage with example code snippets.
+For a detailed API references please consult the class definitions in
+[this directory](https://cs.chromium.org/chromium/src/mojo/public/java/bindings/src/org/chromium/mojo/bindings/)
+
+TODO: Make the contents of this document less non-existent.
diff --git a/mojo/public/java/system/README.md b/mojo/public/java/system/README.md
new file mode 100644
index 0000000..3213e4c
--- /dev/null
+++ b/mojo/public/java/system/README.md
@@ -0,0 +1,25 @@
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Java System API
+This document is a subset of the [Mojo documentation](/mojo).
+
+[TOC]
+
+## Overview
+
+This document provides a brief guide to Java Mojo bindings usage with example
+code snippets.
+
+For a detailed API references please consult the class definitions in
+[this directory](https://cs.chromium.org/chromium/src/mojo/public/java/system/src/org/chromium/mojo/system/).
+
+*TODO: Make the contents of this document less non-existent.*
+
+## Message Pipes
+
+## Data Pipes
+
+## Shared Buffers
+
+## Native Platform Handles (File Descriptors, Windows Handles, *etc.*)
+
+## Watchers
+
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Core.java b/mojo/public/java/system/src/org/chromium/mojo/system/Core.java
index e5c6d08..40e4be3 100644
--- a/mojo/public/java/system/src/org/chromium/mojo/system/Core.java
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/Core.java
@@ -4,8 +4,6 @@
 
 package org.chromium.mojo.system;
 
-import java.util.List;
-
 /**
  * Core mojo interface giving access to the base operations. See |src/mojo/public/c/system/core.h|
  * for the underlying api.
@@ -129,142 +127,6 @@
     }
 
     /**
-     * Result for the |wait| method.
-     */
-    public static class WaitResult {
-        /**
-         * The result of the wait method.
-         * <p>
-         * |MojoResult.OK| if some signal in |signals| was satisfied (or is already satisfied).
-         * <p>
-         * |MojoResult.DEADLINE_EXCEEDED| if the deadline has passed without any of the signals
-         * being satisfied.
-         * <p>
-         * |MojoResult.CANCELLED| if |handle| is closed concurrently by another thread.
-         * <p>
-         * |MojoResult.FAILED_PRECONDITION| if it is or becomes impossible that any flag in
-         * |signals| will ever be satisfied (for example, if the other endpoint is closed).
-         */
-        private int mMojoResult;
-
-        /**
-         * The signaling state of handles.
-         */
-        private HandleSignalsState mHandleSignalsState;
-
-        /**
-         * Returns the mojoResult.
-         */
-        public int getMojoResult() {
-            return mMojoResult;
-        }
-
-        /**
-         * @param mojoResult the mojoResult to set
-         */
-        public void setMojoResult(int mojoResult) {
-            mMojoResult = mojoResult;
-        }
-
-        /**
-         * Returns the handleSignalsState.
-         */
-        public HandleSignalsState getHandleSignalsState() {
-            return mHandleSignalsState;
-        }
-
-        /**
-         * @param handleSignalsState the handleSignalsState to set
-         */
-        public void setHandleSignalsState(HandleSignalsState handleSignalsState) {
-            mHandleSignalsState = handleSignalsState;
-        }
-    }
-
-    /**
-     * Waits on the given |handle| until the state indicated by |signals| is satisfied or until
-     * |deadline| has passed.
-     *
-     * @return a |WaitResult|.
-     */
-    public WaitResult wait(Handle handle, HandleSignals signals, long deadline);
-
-    /**
-     * Result for the |waitMany| method.
-     */
-    public static class WaitManyResult {
-
-        /**
-         * See |wait| for the different possible values.
-         */
-        private int mMojoResult;
-
-        /**
-         * If |mojoResult| is |MojoResult.OK|, |handleIndex| is the index of the handle for which
-         * some flag was satisfied (or is already satisfied). If |mojoResult| is
-         * |MojoResult.CANCELLED| or |MojoResult.FAILED_PRECONDITION|, |handleIndex| is the index of
-         * the handle for which the issue occurred.
-         */
-        private int mHandleIndex;
-
-        /**
-         * The signaling state of handles. Will not be set if |mojoResult| is
-         * |MOJO_RESULT_INVALID_ARGUMENT| or |MOJO_RESULT_RESOURCE_EXHAUSTED|
-         */
-        private List<HandleSignalsState> mSignalStates;
-
-        /**
-         * Returns the mojoResult.
-         */
-        public int getMojoResult() {
-            return mMojoResult;
-        }
-
-        /**
-         * @param mojoResult the mojoResult to set
-         */
-        public void setMojoResult(int mojoResult) {
-            mMojoResult = mojoResult;
-        }
-
-        /**
-         * Returns the handleIndex.
-         */
-        public int getHandleIndex() {
-            return mHandleIndex;
-        }
-
-        /**
-         * @param handleIndex the handleIndex to set
-         */
-        public void setHandleIndex(int handleIndex) {
-            mHandleIndex = handleIndex;
-        }
-
-        /**
-         * Returns the signalStates.
-         */
-        public List<HandleSignalsState> getSignalStates() {
-            return mSignalStates;
-        }
-
-        /**
-         * @param signalStates the signalStates to set
-         */
-        public void setSignalStates(List<HandleSignalsState> signalStates) {
-            mSignalStates = signalStates;
-        }
-    }
-
-    /**
-     * Waits on handle in |handles| for at least one of them to satisfy the associated
-     * |HandleSignals|, or until |deadline| has passed.
-     *
-     * @returns a |WaitManyResult|.
-     */
-    public WaitManyResult waitMany(List<Pair<Handle, HandleSignals>> handles, long deadline);
-
-    /**
      * Creates a message pipe, which is a bidirectional communication channel for framed data (i.e.,
      * messages), with the given options. Messages can contain plain data and/or Mojo handles.
      *
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java b/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java
index 6181669..903f36d 100644
--- a/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java
@@ -4,7 +4,7 @@
 
 package org.chromium.mojo.system;
 
-import org.chromium.mojo.system.Core.WaitResult;
+import org.chromium.mojo.system.Core.HandleSignalsState;
 
 import java.io.Closeable;
 
@@ -25,9 +25,9 @@
     public void close();
 
     /**
-     * @see Core#wait(Handle, Core.HandleSignals, long)
+     * @return the last known signaling state of the handle.
      */
-    public WaitResult wait(Core.HandleSignals signals, long deadline);
+    public HandleSignalsState querySignalsState();
 
     /**
      * @return whether the handle is valid. A handle is valid until it has been explicitly closed or
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java
index 9c20fdd..f8b99c6 100644
--- a/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java
@@ -4,8 +4,7 @@
 
 package org.chromium.mojo.system;
 
-import org.chromium.mojo.system.Core.HandleSignals;
-import org.chromium.mojo.system.Core.WaitResult;
+import org.chromium.mojo.system.Core.HandleSignalsState;
 import org.chromium.mojo.system.DataPipe.ConsumerHandle;
 import org.chromium.mojo.system.DataPipe.ProducerHandle;
 
@@ -38,10 +37,10 @@
     }
 
     /**
-     * @see Handle#wait(Core.HandleSignals, long)
+     * @see Handle#querySignalsState()
      */
     @Override
-    public WaitResult wait(HandleSignals signals, long deadline) {
+    public HandleSignalsState querySignalsState() {
         throw new MojoException(MojoResult.INVALID_ARGUMENT);
     }
 
diff --git a/mojo/public/js/BUILD.gn b/mojo/public/js/BUILD.gn
index 0fae4b4..5ed57a1 100644
--- a/mojo/public/js/BUILD.gn
+++ b/mojo/public/js/BUILD.gn
@@ -14,6 +14,7 @@
 group("bindings") {
   data = [
     "$interfaces_bindings_gen_dir/interface_control_messages.mojom.js",
+    "$interfaces_bindings_gen_dir/pipe_control_messages.mojom.js",
     "bindings.js",
     "buffer.js",
     "codec.js",
@@ -22,6 +23,10 @@
     "interface_types.js",
     "lib/control_message_handler.js",
     "lib/control_message_proxy.js",
+    "lib/interface_endpoint_client.js",
+    "lib/interface_endpoint_handle.js",
+    "lib/pipe_control_message_handler.js",
+    "lib/pipe_control_message_proxy.js",
     "router.js",
     "support.js",
     "threading.js",
@@ -30,22 +35,54 @@
   ]
 
   deps = [
+    ":new_bindings",
     "//mojo/public/interfaces/bindings:bindings__generator",
   ]
 }
 
+action("new_bindings") {
+  new_bindings_js_files = [
+    # This must be the first file in the list, because it initializes global
+    # variable |mojoBindings| that the others need to refer to.
+    "new_bindings/base.js",
+
+    "$interfaces_bindings_gen_dir/new_bindings/interface_control_messages.mojom.js",
+    "new_bindings/bindings.js",
+    "new_bindings/buffer.js",
+    "new_bindings/codec.js",
+    "new_bindings/connector.js",
+    "new_bindings/interface_types.js",
+    "new_bindings/lib/control_message_handler.js",
+    "new_bindings/lib/control_message_proxy.js",
+    "new_bindings/router.js",
+    "new_bindings/unicode.js",
+    "new_bindings/validator.js",
+  ]
+  compiled_file = "$target_gen_dir/mojo_bindings.js"
+
+  # TODO(yzshen): Eventually we would like to use Closure Compiler to minify the
+  # bindings instead of simply concatenating the files.
+  script = "//v8/tools/concatenate-files.py"
+
+  sources = new_bindings_js_files
+  outputs = [
+    compiled_file,
+  ]
+
+  args = rebase_path(new_bindings_js_files)
+  args += [ rebase_path(compiled_file) ]
+
+  deps = [
+    "//mojo/public/interfaces/bindings:new_bindings__generator",
+  ]
+}
+
 group("tests") {
   testonly = true
 
   data = [
     "//mojo/public/interfaces/bindings/tests/data/validation/",
-    "tests/codec_unittest.js",
-    "tests/connection_unittest.js",
     "tests/core_unittest.js",
-    "tests/interface_ptr_unittest.js",
-    "tests/sample_service_unittest.js",
-    "tests/struct_unittest.js",
-    "tests/union_unittest.js",
     "tests/validation_test_input_parser.js",
     "tests/validation_unittest.js",
   ]
diff --git a/mojo/public/js/README.md b/mojo/public/js/README.md
new file mode 100644
index 0000000..b6eafe9
--- /dev/null
+++ b/mojo/public/js/README.md
@@ -0,0 +1,7 @@
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo JavaScript System and Bindings APIs
+This document is a subset of the [Mojo documentation](/mojo).
+
+**NOTE:** The JavaScript APIs are currently in flux and will stabilize soon.
+More info forthcoming ASAP!
+
+TODO: Make the contents of this document less non-existent.
diff --git a/mojo/public/js/bindings.js b/mojo/public/js/bindings.js
index f3e40d2..a944e2f 100644
--- a/mojo/public/js/bindings.js
+++ b/mojo/public/js/bindings.js
@@ -4,10 +4,12 @@
 
 define("mojo/public/js/bindings", [
   "mojo/public/js/core",
-  "mojo/public/js/lib/control_message_proxy",
   "mojo/public/js/interface_types",
+  "mojo/public/js/lib/interface_endpoint_client",
   "mojo/public/js/router",
-], function(core, controlMessageProxy, types, router) {
+], function(core, types, interfaceEndpointClient, router) {
+
+  var InterfaceEndpointClient = interfaceEndpointClient.InterfaceEndpointClient;
 
   // ---------------------------------------------------------------------------
 
@@ -27,12 +29,13 @@
 
     this.interfaceType_ = interfaceType;
     this.router_ = null;
+    this.interfaceEndpointClient_ = null;
     this.proxy_ = null;
 
-    // |router_| is lazily initialized. |handle_| is valid between bind() and
-    // the initialization of |router_|.
+    // |router_| and |interfaceEndpointClient_| are lazily initialized.
+    // |handle_| is valid between bind() and
+    // the initialization of |router_| and |interfaceEndpointClient_|.
     this.handle_ = null;
-    this.controlMessageProxy_ = null;
 
     if (ptrInfoOrHandle)
       this.bind(ptrInfoOrHandle);
@@ -57,6 +60,10 @@
   // immediately.
   InterfacePtrController.prototype.reset = function() {
     this.version = 0;
+    if (this.interfaceEndpointClient_) {
+      this.interfaceEndpointClient_.close();
+      this.interfaceEndpointClient_ = null;
+    }
     if (this.router_) {
       this.router_.close();
       this.router_ = null;
@@ -69,13 +76,20 @@
     }
   };
 
-  InterfacePtrController.prototype.setConnectionErrorHandler
-      = function(callback) {
+  InterfacePtrController.prototype.resetWithReason = function(reason) {
+    this.configureProxyIfNecessary_();
+    this.interfaceEndpointClient_.close(reason);
+    this.interfaceEndpointClient_ = null;
+    this.reset();
+  };
+
+  InterfacePtrController.prototype.setConnectionErrorHandler = function(
+      callback) {
     if (!this.isBound())
       throw new Error("Cannot set connection error handler if not bound.");
 
     this.configureProxyIfNecessary_();
-    this.router_.setErrorHandler(callback);
+    this.interfaceEndpointClient_.setConnectionErrorHandler(callback);
   };
 
   InterfacePtrController.prototype.passInterface = function() {
@@ -100,9 +114,9 @@
     return this.proxy_;
   };
 
-  InterfacePtrController.prototype.enableTestingMode = function() {
+  InterfacePtrController.prototype.waitForNextMessageForTesting = function() {
     this.configureProxyIfNecessary_();
-    return this.router_.enableTestingMode();
+    this.router_.waitForNextMessageForTesting();
   };
 
   InterfacePtrController.prototype.configureProxyIfNecessary_ = function() {
@@ -111,12 +125,15 @@
 
     this.router_ = new router.Router(this.handle_);
     this.handle_ = null;
-    this.router_ .setPayloadValidators([this.interfaceType_.validateResponse]);
 
-    this.controlMessageProxy_ = new
-        controlMessageProxy.ControlMessageProxy(this.router_);
+    this.interfaceEndpointClient_ = new InterfaceEndpointClient(
+        this.router_.createLocalEndpointHandle(types.kMasterInterfaceId),
+        this.router_);
 
-    this.proxy_ = new this.interfaceType_.proxyClass(this.router_);
+    this.interfaceEndpointClient_ .setPayloadValidators([
+        this.interfaceType_.validateResponse]);
+    this.proxy_ = new this.interfaceType_.proxyClass(
+        this.interfaceEndpointClient_);
   };
 
   InterfacePtrController.prototype.queryVersion = function() {
@@ -126,7 +143,7 @@
     }
 
     this.configureProxyIfNecessary_();
-    return this.controlMessageProxy_.queryVersion().then(
+    return this.interfaceEndpointClient_.queryVersion().then(
       onQueryVersion.bind(this));
   };
 
@@ -137,7 +154,7 @@
       return;
     }
     this.version = version;
-    this.controlMessageProxy_.requireVersion(version);
+    this.interfaceEndpointClient_.requireVersion(version);
   };
 
   // ---------------------------------------------------------------------------
@@ -159,6 +176,7 @@
     this.interfaceType_ = interfaceType;
     this.impl_ = impl;
     this.router_ = null;
+    this.interfaceEndpointClient_ = null;
     this.stub_ = null;
 
     if (requestOrHandle)
@@ -174,7 +192,7 @@
     // TODO(yzshen): Set the version of the interface pointer.
     this.bind(makeRequest(ptr));
     return ptr;
-  }
+  };
 
   Binding.prototype.bind = function(requestOrHandle) {
     this.close();
@@ -184,26 +202,45 @@
     if (!core.isHandle(handle))
       return;
 
+    this.router_ = new router.Router(handle);
+
     this.stub_ = new this.interfaceType_.stubClass(this.impl_);
-    this.router_ = new router.Router(handle, this.interfaceType_.kVersion);
-    this.router_.setIncomingReceiver(this.stub_);
-    this.router_ .setPayloadValidators([this.interfaceType_.validateRequest]);
+    this.interfaceEndpointClient_ = new InterfaceEndpointClient(
+        this.router_.createLocalEndpointHandle(types.kMasterInterfaceId),
+        this.router_, this.interfaceType_.kVersion);
+    this.interfaceEndpointClient_.setIncomingReceiver(this.stub_);
+    this.interfaceEndpointClient_ .setPayloadValidators([
+        this.interfaceType_.validateRequest]);
   };
 
   Binding.prototype.close = function() {
     if (!this.isBound())
       return;
 
+    if (this.interfaceEndpointClient_) {
+      this.interfaceEndpointClient_.close();
+      this.interfaceEndpointClient_ = null;
+    }
+
     this.router_.close();
     this.router_ = null;
     this.stub_ = null;
   };
 
+  Binding.prototype.closeWithReason = function(reason) {
+    if (this.interfaceEndpointClient_) {
+      this.interfaceEndpointClient_.close(reason);
+      this.interfaceEndpointClient_ = null;
+    }
+    this.close();
+  };
+
   Binding.prototype.setConnectionErrorHandler
       = function(callback) {
-    if (!this.isBound())
+    if (!this.isBound()) {
       throw new Error("Cannot set connection error handler if not bound.");
-    this.router_.setErrorHandler(callback);
+    }
+    this.interfaceEndpointClient_.setConnectionErrorHandler(callback);
   };
 
   Binding.prototype.unbind = function() {
@@ -216,8 +253,8 @@
     return result;
   };
 
-  Binding.prototype.enableTestingMode = function() {
-    return this.router_.enableTestingMode();
+  Binding.prototype.waitForNextMessageForTesting = function() {
+    this.router_.waitForNextMessageForTesting();
   };
 
   // ---------------------------------------------------------------------------
diff --git a/mojo/public/js/codec.js b/mojo/public/js/codec.js
index ff5d31a..ce58a8c 100644
--- a/mojo/public/js/codec.js
+++ b/mojo/public/js/codec.js
@@ -453,6 +453,10 @@
     return this.buffer.getUint32(kMessageFlagsOffset);
   };
 
+  Message.prototype.getInterfaceId = function() {
+    return this.buffer.getUint32(kMessageInterfaceIdOffset);
+  };
+
   Message.prototype.isResponse = function() {
     return (this.getFlags() & kMessageIsResponse) != 0;
   };
@@ -466,6 +470,10 @@
     this.buffer.setUint64(kMessageRequestIDOffset, requestID);
   };
 
+  Message.prototype.setInterfaceId = function(interfaceId) {
+    this.buffer.setUint32(kMessageInterfaceIdOffset, interfaceId);
+  };
+
 
   // MessageBuilder -----------------------------------------------------------
 
@@ -537,10 +545,6 @@
     this.payloadSize = message.buffer.byteLength - messageHeaderSize;
     var version = this.decoder.readUint32();
     var interface_id = this.decoder.readUint32();
-    if (interface_id != 0) {
-      throw new Error("Receiving non-zero interface ID. Associated interfaces " +
-                      "are not yet supported.");
-    }
     this.messageName = this.decoder.readUint32();
     this.flags = this.decoder.readUint32();
     // Skip the padding.
diff --git a/mojo/public/js/connector.js b/mojo/public/js/connector.js
index ee16be8..012e3c7 100644
--- a/mojo/public/js/connector.js
+++ b/mojo/public/js/connector.js
@@ -78,13 +78,8 @@
     this.errorHandler_ = handler;
   };
 
-  Connector.prototype.encounteredError = function() {
-    return this.error_;
-  };
-
   Connector.prototype.waitForNextMessageForTesting = function() {
-    var wait = core.wait(this.handle_, core.HANDLE_SIGNAL_READABLE,
-                         core.DEADLINE_INDEFINITE);
+    var wait = core.wait(this.handle_, core.HANDLE_SIGNAL_READABLE);
     this.readMore_(wait.result);
   };
 
@@ -97,9 +92,12 @@
       if (read.result == core.RESULT_SHOULD_WAIT)
         return;
       if (read.result != core.RESULT_OK) {
+        // TODO(wangjimmy): Add a handleError method to swap the handle to be
+        // closed with a dummy handle in the case when
+        // read.result != MOJO_RESULT_FAILED_PRECONDITION
         this.error_ = true;
         if (this.errorHandler_)
-          this.errorHandler_.onError(read.result);
+          this.errorHandler_.onError();
         return;
       }
       var messageBuffer = new buffer.Buffer(read.buffer);
diff --git a/mojo/public/js/constants.cc b/mojo/public/js/constants.cc
index 58cf274..a0ce7d4 100644
--- a/mojo/public/js/constants.cc
+++ b/mojo/public/js/constants.cc
@@ -16,7 +16,17 @@
     "mojo/public/js/lib/control_message_proxy";
 const char kInterfaceControlMessagesMojom[] =
     "mojo/public/interfaces/bindings/interface_control_messages.mojom";
+const char kInterfaceEndpointClientModuleName[] =
+    "mojo/public/js/lib/interface_endpoint_client";
+const char kInterfaceEndpointHandleModuleName[] =
+    "mojo/public/js/lib/interface_endpoint_handle";
 const char kInterfaceTypesModuleName[] = "mojo/public/js/interface_types";
+const char kPipeControlMessageHandlerModuleName[] =
+    "mojo/public/js/lib/pipe_control_message_handler";
+const char kPipeControlMessageProxyModuleName[] =
+    "mojo/public/js/lib/pipe_control_message_proxy";
+const char kPipeControlMessagesMojom[] =
+    "mojo/public/interfaces/bindings/pipe_control_messages.mojom";
 const char kRouterModuleName[] = "mojo/public/js/router";
 const char kUnicodeModuleName[] = "mojo/public/js/unicode";
 const char kValidatorModuleName[] = "mojo/public/js/validator";
diff --git a/mojo/public/js/constants.h b/mojo/public/js/constants.h
index 9d32d20..f561d73 100644
--- a/mojo/public/js/constants.h
+++ b/mojo/public/js/constants.h
@@ -15,7 +15,12 @@
 extern const char kControlMessageHandlerModuleName[];
 extern const char kControlMessageProxyModuleName[];
 extern const char kInterfaceControlMessagesMojom[];
+extern const char kInterfaceEndpointClientModuleName[];
+extern const char kInterfaceEndpointHandleModuleName[];
 extern const char kInterfaceTypesModuleName[];
+extern const char kPipeControlMessageHandlerModuleName[];
+extern const char kPipeControlMessageProxyModuleName[];
+extern const char kPipeControlMessagesMojom[];
 extern const char kRouterModuleName[];
 extern const char kUnicodeModuleName[];
 extern const char kValidatorModuleName[];
diff --git a/mojo/public/js/core.js b/mojo/public/js/core.js
index ef480ee..b2c4ee2 100644
--- a/mojo/public/js/core.js
+++ b/mojo/public/js/core.js
@@ -41,12 +41,6 @@
 var RESULT_SHOULD_WAIT;
 
 /**
- * MojoDeadline {number}: Used to specify deadlines (timeouts), in microseconds.
- * See core.h for more information.
- */
-var DEADLINE_INDEFINITE;
-
-/**
  * MojoHandleSignals: Used to specify signals that can be waited on for a handle
  *(and which can be triggered), e.g., the ability to read or write to
  * the handle.
@@ -142,29 +136,26 @@
 function close(handle) { [native code] }
 
 /**
+ * Queries the last known signaling state of |handle|.
+ *
+ * @param {MojoHandle} handle Handle to query.
+ * @return {object} An object of the form {
+ *     result,              // MOJO_RESULT_OK or MOJO_RESULT_INVALID_ARGUMENT
+ *     satisfiedSignals,    // MojoHandleSignals (see above)
+ *     satisfiableSignals,  // MojoHandleSignals
+ * }
+ */
+function queryHandleSignalsState(handle) { [native code] }
+
+/**
  * Waits on the given handle until a signal indicated by |signals| is
- * satisfied or until |deadline| is passed. See MojoWait for more information.
+ * satisfied or an error occurs.
  *
  * @param {MojoHandle} handle Handle to wait on.
  * @param {MojoHandleSignals} signals Specifies the condition to wait for.
- * @param {MojoDeadline} deadline Stops waiting if this is reached.
  * @return {MojoResult} Result code.
  */
-function wait(handle, signals, deadline) { [native code] }
-
-/**
- * Waits on |handles[0]|, ..., |handles[handles.length-1]| for at least one of
- * them to satisfy the state indicated by |flags[0]|, ...,
- * |flags[handles.length-1]|, respectively, or until |deadline| has passed.
- * See MojoWaitMany for more information.
- *
- * @param {Array.MojoHandle} handles Handles to wait on.
- * @param {Array.MojoHandleSignals} signals Specifies the condition to wait for,
- *   for each corresponding handle. Must be the same length as |handles|.
- * @param {MojoDeadline} deadline Stops waiting if this is reached.
- * @return {MojoResult} Result code.
- */
-function waitMany(handles, signals, deadline) { [native code] }
+function wait(handle, signals) { [native code] }
 
 /**
  * Creates a message pipe. This function always succeeds.
diff --git a/mojo/public/js/interface_types.js b/mojo/public/js/interface_types.js
index 01ea2d1..e8ed37a 100644
--- a/mojo/public/js/interface_types.js
+++ b/mojo/public/js/interface_types.js
@@ -6,6 +6,11 @@
   "mojo/public/js/core",
 ], function(core) {
 
+  // Constants ----------------------------------------------------------------
+  var kInterfaceIdNamespaceMask = 0x80000000;
+  var kMasterInterfaceId = 0x00000000;
+  var kInvalidInterfaceId = 0xFFFFFFFF;
+
   // ---------------------------------------------------------------------------
 
   function InterfacePtrInfo(handle, version) {
@@ -44,9 +49,22 @@
     this.handle = null;
   };
 
+  function isMasterInterfaceId(interfaceId) {
+    return interfaceId === kMasterInterfaceId;
+  }
+
+  function isValidInterfaceId(interfaceId) {
+    return interfaceId !== kInvalidInterfaceId;
+  }
+
   var exports = {};
   exports.InterfacePtrInfo = InterfacePtrInfo;
   exports.InterfaceRequest = InterfaceRequest;
+  exports.isMasterInterfaceId = isMasterInterfaceId;
+  exports.isValidInterfaceId = isValidInterfaceId;
+  exports.kInvalidInterfaceId = kInvalidInterfaceId;
+  exports.kMasterInterfaceId = kMasterInterfaceId;
+  exports.kInterfaceIdNamespaceMask = kInterfaceIdNamespaceMask;
 
   return exports;
 });
diff --git a/mojo/public/js/lib/control_message_handler.js b/mojo/public/js/lib/control_message_handler.js
index 81d9002..5da306e 100644
--- a/mojo/public/js/lib/control_message_handler.js
+++ b/mojo/public/js/lib/control_message_handler.js
@@ -3,10 +3,10 @@
 // found in the LICENSE file.
 
 define("mojo/public/js/lib/control_message_handler", [
-  "mojo/public/js/codec",
   "mojo/public/interfaces/bindings/interface_control_messages.mojom",
+  "mojo/public/js/codec",
   "mojo/public/js/validator",
-], function(codec, controlMessages, validator) {
+], function(controlMessages, codec, validator) {
 
   var Validator = validator.Validator;
 
@@ -89,18 +89,18 @@
   }
 
   function ControlMessageHandler(interface_version) {
-    this.interface_version = interface_version;
+    this.interface_version_ = interface_version;
   }
 
   ControlMessageHandler.prototype.accept = function(message) {
     validateControlRequestWithoutResponse(message);
-    return runOrClosePipe(message, this.interface_version);
+    return runOrClosePipe(message, this.interface_version_);
   };
 
   ControlMessageHandler.prototype.acceptWithResponder = function(message,
       responder) {
     validateControlRequestWithResponse(message);
-    return run(message, responder, this.interface_version);
+    return run(message, responder, this.interface_version_);
   };
 
   var exports = {};
diff --git a/mojo/public/js/lib/control_message_proxy.js b/mojo/public/js/lib/control_message_proxy.js
index d6c0734..b6f1d3c 100644
--- a/mojo/public/js/lib/control_message_proxy.js
+++ b/mojo/public/js/lib/control_message_proxy.js
@@ -10,14 +10,18 @@
 
   var Validator = validator.Validator;
 
-  function sendRunOrClosePipeMessage(receiver, runOrClosePipeMessageParams) {
+  function constructRunOrClosePipeMessage(runOrClosePipeInput) {
+    var runOrClosePipeMessageParams = new
+        controlMessages.RunOrClosePipeMessageParams();
+    runOrClosePipeMessageParams.input = runOrClosePipeInput;
+
     var messageName = controlMessages.kRunOrClosePipeMessageId;
     var payloadSize = controlMessages.RunOrClosePipeMessageParams.encodedSize;
     var builder = new codec.MessageBuilder(messageName, payloadSize);
     builder.encodeStruct(controlMessages.RunOrClosePipeMessageParams,
                          runOrClosePipeMessageParams);
     var message = builder.finish();
-    receiver.accept(message);
+    return message;
   }
 
   function validateControlResponse(message) {
@@ -71,7 +75,7 @@
   }
 
   function ControlMessageProxy(receiver) {
-    this.receiver = receiver;
+    this.receiver_ = receiver;
   }
 
   ControlMessageProxy.prototype.queryVersion = function() {
@@ -79,20 +83,18 @@
     runMessageParams.input = new controlMessages.RunInput();
     runMessageParams.input.query_version = new controlMessages.QueryVersion();
 
-    return sendRunMessage(this.receiver, runMessageParams).then(function(
+    return sendRunMessage(this.receiver_, runMessageParams).then(function(
         runResponseMessageParams) {
       return runResponseMessageParams.output.query_version_result.version;
     });
   };
 
   ControlMessageProxy.prototype.requireVersion = function(version) {
-    var runOrClosePipeMessageParams = new
-        controlMessages.RunOrClosePipeMessageParams();
-    runOrClosePipeMessageParams.input = new
-        controlMessages.RunOrClosePipeInput();
-    runOrClosePipeMessageParams.input.require_version = new
-        controlMessages.RequireVersion({'version': version});
-    sendRunOrClosePipeMessage(this.receiver, runOrClosePipeMessageParams);
+    var runOrClosePipeInput = new controlMessages.RunOrClosePipeInput();
+    runOrClosePipeInput.require_version = new controlMessages.RequireVersion({
+        'version': version});
+    var message = constructRunOrClosePipeMessage(runOrClosePipeInput);
+    this.receiver_.accept(message);
   };
 
   var exports = {};
diff --git a/mojo/public/js/lib/interface_endpoint_client.js b/mojo/public/js/lib/interface_endpoint_client.js
new file mode 100644
index 0000000..631c52e
--- /dev/null
+++ b/mojo/public/js/lib/interface_endpoint_client.js
@@ -0,0 +1,232 @@
+// Copyright 2017 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.
+
+define("mojo/public/js/lib/interface_endpoint_client", [
+  "console",
+  "mojo/public/js/codec",
+  "mojo/public/js/lib/control_message_handler",
+  "mojo/public/js/lib/control_message_proxy",
+  "mojo/public/js/lib/interface_endpoint_handle",
+  "mojo/public/js/validator",
+  "timer",
+], function(console,
+            codec,
+            controlMessageHandler,
+            controlMessageProxy,
+            interfaceEndpointHandle,
+            validator,
+            timer) {
+
+  var ControlMessageHandler = controlMessageHandler.ControlMessageHandler;
+  var ControlMessageProxy = controlMessageProxy.ControlMessageProxy;
+  var MessageReader = codec.MessageReader;
+  var Validator = validator.Validator;
+  var InterfaceEndpointHandle = interfaceEndpointHandle.InterfaceEndpointHandle;
+
+  function InterfaceEndpointClient(interfaceEndpointHandle, receiver,
+      interfaceVersion) {
+    this.controller_ = null;
+    this.encounteredError_ = false;
+    this.handle_ = interfaceEndpointHandle;
+    this.incomingReceiver_ = receiver;
+
+    if (interfaceVersion !== undefined) {
+      this.controlMessageHandler_ = new ControlMessageHandler(
+          interfaceVersion);
+    } else {
+      this.controlMessageProxy_ = new ControlMessageProxy(this);
+    }
+
+    this.nextRequestID_ = 0;
+    this.completers_ = new Map();
+    this.payloadValidators_ = [];
+    this.connectionErrorHandler_ = null;
+
+    if (interfaceEndpointHandle.pendingAssociation()) {
+      interfaceEndpointHandle.setAssociationEventHandler(
+          this.onAssociationEvent.bind(this));
+    } else {
+      this.initControllerIfNecessary_();
+    }
+  }
+
+  InterfaceEndpointClient.prototype.initControllerIfNecessary_ = function() {
+    if (this.controller_ || this.handle_.pendingAssociation()) {
+      return;
+    }
+
+    this.controller_ = this.handle_.groupController().attachEndpointClient(
+        this.handle_, this);
+  };
+
+  InterfaceEndpointClient.prototype.onAssociationEvent = function(
+      associationEvent) {
+    if (associationEvent ===
+        InterfaceEndpointHandle.AssociationEvent.ASSOCIATED) {
+      this.initControllerIfNecessary_();
+    } else if (associationEvent ===
+        InterfaceEndpointHandle.AssociationEvent
+                               .PEER_CLOSED_BEFORE_ASSOCIATION) {
+      timer.createOneShot(0, this.notifyError.bind(this,
+          this.handle_.disconnectReason()));
+    }
+  };
+
+  InterfaceEndpointClient.prototype.passHandle = function() {
+    if (!this.handle_.isValid()) {
+      return new InterfaceEndpointHandle();
+    }
+
+    // Used to clear the previously set callback.
+    this.handle_.setAssociationEventHandler(undefined);
+
+    if (this.controller_) {
+      this.controller_ = null;
+      this.handle_.groupController().detachEndpointClient(this.handle_);
+    }
+    var handle = this.handle_;
+    this.handle_ = null;
+    return handle;
+  };
+
+  InterfaceEndpointClient.prototype.close = function(reason) {
+    var handle = this.passHandle();
+    handle.reset(reason);
+  };
+
+  InterfaceEndpointClient.prototype.accept = function(message) {
+    if (this.encounteredError_) {
+      return false;
+    }
+
+    this.initControllerIfNecessary_();
+    return this.controller_.sendMessage(message);
+  };
+
+  InterfaceEndpointClient.prototype.acceptAndExpectResponse = function(
+      message) {
+    if (this.encounteredError_) {
+      return Promise.reject();
+    }
+
+    this.initControllerIfNecessary_();
+
+    // Reserve 0 in case we want it to convey special meaning in the future.
+    var requestID = this.nextRequestID_++;
+    if (requestID === 0)
+      requestID = this.nextRequestID_++;
+
+    message.setRequestID(requestID);
+    var result = this.controller_.sendMessage(message);
+    if (!result)
+      return Promise.reject(Error("Connection error"));
+
+    var completer = {};
+    this.completers_.set(requestID, completer);
+    return new Promise(function(resolve, reject) {
+      completer.resolve = resolve;
+      completer.reject = reject;
+    });
+  };
+
+  InterfaceEndpointClient.prototype.setPayloadValidators = function(
+      payloadValidators) {
+    this.payloadValidators_ = payloadValidators;
+  };
+
+  InterfaceEndpointClient.prototype.setIncomingReceiver = function(receiver) {
+    this.incomingReceiver_ = receiver;
+  };
+
+  InterfaceEndpointClient.prototype.setConnectionErrorHandler = function(
+      handler) {
+    this.connectionErrorHandler_ = handler;
+  };
+
+  InterfaceEndpointClient.prototype.handleIncomingMessage_ = function(
+      message) {
+    var noError = validator.validationError.NONE;
+    var messageValidator = new Validator(message);
+    var err = noError;
+    for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i)
+      err = this.payloadValidators_[i](messageValidator);
+
+    if (err == noError) {
+      return this.handleValidIncomingMessage_(message);
+    } else {
+      validator.reportValidationError(err);
+      return false;
+    }
+  };
+
+  InterfaceEndpointClient.prototype.handleValidIncomingMessage_ = function(
+      message) {
+    if (validator.isTestingMode()) {
+      return true;
+    }
+
+    if (this.encounteredError_) {
+      return false;
+    }
+
+    var ok = false;
+
+    if (message.expectsResponse()) {
+      if (controlMessageHandler.isControlMessage(message) &&
+          this.controlMessageHandler_) {
+        ok = this.controlMessageHandler_.acceptWithResponder(message, this);
+      } else if (this.incomingReceiver_) {
+        ok = this.incomingReceiver_.acceptWithResponder(message, this);
+      }
+    } else if (message.isResponse()) {
+      var reader = new MessageReader(message);
+      var requestID = reader.requestID;
+      var completer = this.completers_.get(requestID);
+      if (completer) {
+        this.completers_.delete(requestID);
+        completer.resolve(message);
+        ok = true;
+      } else {
+        console.log("Unexpected response with request ID: " + requestID);
+      }
+    } else {
+      if (controlMessageHandler.isControlMessage(message) &&
+          this.controlMessageHandler_) {
+        ok = this.controlMessageHandler_.accept(message);
+      } else if (this.incomingReceiver_) {
+        ok = this.incomingReceiver_.accept(message);
+      }
+    }
+    return ok;
+  };
+
+  InterfaceEndpointClient.prototype.notifyError = function(reason) {
+    if (this.encounteredError_) {
+      return;
+    }
+    this.encounteredError_ = true;
+
+    this.completers_.forEach(function(value) {
+      value.reject();
+    });
+    this.completers_.clear();  // Drop any responders.
+
+    if (this.connectionErrorHandler_) {
+      this.connectionErrorHandler_(reason);
+    }
+  };
+
+  InterfaceEndpointClient.prototype.queryVersion = function() {
+    return this.controlMessageProxy_.queryVersion();
+  };
+
+  InterfaceEndpointClient.prototype.requireVersion = function(version) {
+    this.controlMessageProxy_.requireVersion(version);
+  };
+
+  var exports = {};
+  exports.InterfaceEndpointClient = InterfaceEndpointClient;
+
+  return exports;
+});
diff --git a/mojo/public/js/lib/interface_endpoint_handle.js b/mojo/public/js/lib/interface_endpoint_handle.js
new file mode 100644
index 0000000..f48b89b
--- /dev/null
+++ b/mojo/public/js/lib/interface_endpoint_handle.js
@@ -0,0 +1,158 @@
+// Copyright 2017 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.
+
+define("mojo/public/js/lib/interface_endpoint_handle", [
+  "mojo/public/js/interface_types",
+  "timer",
+], function(types, timer) {
+
+  var AssociationEvent = {
+    // The interface has been associated with a message pipe.
+    ASSOCIATED: 'associated',
+    // The peer of this object has been closed before association.
+    PEER_CLOSED_BEFORE_ASSOCIATION: 'peer_closed_before_association'
+  };
+
+  function State(interfaceId, associatedGroupController) {
+    if (interfaceId === undefined) {
+      interfaceId = types.kInvalidInterfaceId;
+    }
+
+    this.interfaceId = interfaceId;
+    this.associatedGroupController = associatedGroupController;
+    this.pendingAssociation = false;
+    this.disconnectReason = null;
+    this.peerState_ = null;
+    this.associationEventHandler_ = null;
+  }
+
+  State.prototype.initPendingState = function(peer) {
+    this.pendingAssociation = true;
+    this.peerState_ = peer;
+  };
+
+  State.prototype.isValid = function() {
+    return this.pendingAssociation ||
+        types.isValidInterfaceId(this.interfaceId);
+  };
+
+  State.prototype.close = function(disconnectReason) {
+    var cachedGroupController;
+    var cachedPeerState;
+    var cachedId = types.kInvalidInterfaceId;
+
+    if (!this.pendingAssociation) {
+      if (types.isValidInterfaceId(this.interfaceId)) {
+        cachedGroupController = this.associatedGroupController;
+        this.associatedGroupController = null;
+        cachedId = this.interfaceId;
+        this.interfaceId = types.kInvalidInterfaceId;
+      }
+    } else {
+      this.pendingAssociation = false;
+      cachedPeerState = this.peerState_;
+      this.peerState_ = null;
+    }
+
+    if (cachedGroupController) {
+      cachedGroupController.closeEndpointHandle(cachedId,
+          disconnectReason);
+    } else if (cachedPeerState) {
+      cachedPeerState.onPeerClosedBeforeAssociation(disconnectReason);
+    }
+  };
+
+  State.prototype.runAssociationEventHandler = function(associationEvent) {
+    if (this.associationEventHandler_) {
+      var handler = this.associationEventHandler_;
+      this.associationEventHandler_ = null;
+      handler(associationEvent);
+    }
+  };
+
+  State.prototype.setAssociationEventHandler = function(handler) {
+    if (!this.pendingAssociation &&
+        !types.isValidInterfaceId(this.interfaceId)) {
+      return;
+    }
+
+    if (!handler) {
+      this.associationEventHandler_ = null;
+      return;
+    }
+
+    this.associationEventHandler_ = handler;
+    if (!this.pendingAssociation) {
+      timer.createOneShot(0, this.runAssociationEventHandler.bind(this,
+          AssociationEvent.ASSOCIATED));
+    } else if (!this.peerState_) {
+      timer.createOneShot(0, this.runAssociationEventHandler.bind(this,
+          AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION));
+    }
+  };
+
+  State.prototype.onAssociated = function(interfaceId,
+      associatedGroupController) {
+    if (!this.pendingAssociation) {
+      return;
+    }
+
+    this.pendingAssociation = false;
+    this.peerState_ = null;
+    this.interfaceId = interfaceId;
+    this.associatedGroupController = associatedGroupController;
+    this.runAssociationEventHandler(AssociationEvent.ASSOCIATED);
+  };
+
+  State.prototype.onPeerClosedBeforeAssociation = function(disconnectReason) {
+    if (!this.pendingAssociation) {
+      return;
+    }
+
+    this.peerState_ = null;
+    this.disconnectReason = disconnectReason;
+
+    this.runAssociationEventHandler(
+        AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION);
+  };
+
+  function InterfaceEndpointHandle(interfaceId, associatedGroupController) {
+    this.state_ = new State(interfaceId, associatedGroupController);
+  }
+
+  InterfaceEndpointHandle.prototype.isValid = function() {
+    return this.state_.isValid();
+  };
+
+  InterfaceEndpointHandle.prototype.pendingAssociation = function() {
+    return this.state_.pendingAssociation;
+  };
+
+  InterfaceEndpointHandle.prototype.id = function() {
+    return this.state_.interfaceId;
+  };
+
+  InterfaceEndpointHandle.prototype.groupController = function() {
+    return this.state_.associatedGroupController;
+  };
+
+  InterfaceEndpointHandle.prototype.disconnectReason = function() {
+    return this.state_.disconnectReason;
+  };
+
+  InterfaceEndpointHandle.prototype.setAssociationEventHandler = function(
+      handler) {
+    this.state_.setAssociationEventHandler(handler);
+  };
+
+  InterfaceEndpointHandle.prototype.reset = function(reason) {
+    this.state_.close(reason);
+    this.state_ = new State();
+  };
+
+  var exports = {};
+  exports.InterfaceEndpointHandle = InterfaceEndpointHandle;
+
+  return exports;
+});
diff --git a/mojo/public/js/lib/pipe_control_message_handler.js b/mojo/public/js/lib/pipe_control_message_handler.js
new file mode 100644
index 0000000..2eb45d1
--- /dev/null
+++ b/mojo/public/js/lib/pipe_control_message_handler.js
@@ -0,0 +1,61 @@
+// Copyright 2017 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.
+
+define("mojo/public/js/lib/pipe_control_message_handler", [
+  "mojo/public/interfaces/bindings/pipe_control_messages.mojom",
+  "mojo/public/js/codec",
+  "mojo/public/js/interface_types",
+  "mojo/public/js/validator",
+], function(pipeControlMessages, codec, types, validator) {
+
+  var Validator = validator.Validator;
+
+  function validateControlRequestWithoutResponse(message) {
+    var messageValidator = new Validator(message);
+    var error = messageValidator.validateMessageIsRequestWithoutResponse();
+    if (error != validator.validationError.NONE) {
+      throw error;
+    }
+
+    if (message.getName() != pipeControlMessages.kRunOrClosePipeMessageId) {
+      throw new Error("Control message name is not kRunOrClosePipeMessageId");
+    }
+
+    // Validate payload.
+    error = pipeControlMessages.RunOrClosePipeMessageParams.validate(
+        messageValidator, message.getHeaderNumBytes());
+    if (error != validator.validationError.NONE) {
+      throw error;
+    }
+  }
+
+  function runOrClosePipe(message, delegate) {
+    var reader = new codec.MessageReader(message);
+    var runOrClosePipeMessageParams = reader.decodeStruct(
+        pipeControlMessages.RunOrClosePipeMessageParams);
+    var event = runOrClosePipeMessageParams.input
+        .peer_associated_endpoint_closed_event;
+    return delegate.onPeerAssociatedEndpointClosed(event.id,
+        event.disconnect_reason);
+  }
+
+  function isPipeControlMessage(message) {
+    return !types.isValidInterfaceId(message.getInterfaceId());
+  }
+
+  function PipeControlMessageHandler(delegate) {
+    this.delegate_ = delegate;
+  }
+
+  PipeControlMessageHandler.prototype.accept = function(message) {
+    validateControlRequestWithoutResponse(message);
+    return runOrClosePipe(message, this.delegate_);
+  };
+
+  var exports = {};
+  exports.PipeControlMessageHandler = PipeControlMessageHandler;
+  exports.isPipeControlMessage = isPipeControlMessage;
+
+  return exports;
+});
diff --git a/mojo/public/js/lib/pipe_control_message_proxy.js b/mojo/public/js/lib/pipe_control_message_proxy.js
new file mode 100644
index 0000000..4b8e7a2
--- /dev/null
+++ b/mojo/public/js/lib/pipe_control_message_proxy.js
@@ -0,0 +1,56 @@
+// Copyright 2017 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.
+
+define("mojo/public/js/lib/pipe_control_message_proxy", [
+  "mojo/public/interfaces/bindings/pipe_control_messages.mojom",
+  "mojo/public/js/codec",
+  "mojo/public/js/interface_types",
+], function(pipeControlMessages, codec, types) {
+
+  function constructRunOrClosePipeMessage(runOrClosePipeInput) {
+    var runOrClosePipeMessageParams = new
+        pipeControlMessages.RunOrClosePipeMessageParams();
+    runOrClosePipeMessageParams.input = runOrClosePipeInput;
+
+    var messageName = pipeControlMessages.kRunOrClosePipeMessageId;
+    var payloadSize =
+        pipeControlMessages.RunOrClosePipeMessageParams.encodedSize;
+
+    var builder = new codec.MessageBuilder(messageName, payloadSize);
+    builder.encodeStruct(pipeControlMessages.RunOrClosePipeMessageParams,
+                         runOrClosePipeMessageParams);
+    var message = builder.finish();
+    message.setInterfaceId(types.kInvalidInterfaceId);
+    return message;
+  }
+
+  function PipeControlMessageProxy(receiver) {
+    this.receiver_ = receiver;
+  }
+
+  PipeControlMessageProxy.prototype.notifyPeerEndpointClosed = function(
+      interfaceId, reason) {
+    var message = this.constructPeerEndpointClosedMessage(interfaceId, reason);
+    this.receiver_.accept(message);
+  };
+
+  PipeControlMessageProxy.prototype.constructPeerEndpointClosedMessage =
+      function(interfaceId, reason) {
+    var event = new pipeControlMessages.PeerAssociatedEndpointClosedEvent();
+    event.id = interfaceId;
+    if (reason) {
+      event.disconnect_reason = new pipeControlMessages.DisconnectReason({
+          custom_reason: reason.custom_reason,
+          description: reason.description});
+    }
+    var runOrClosePipeInput = new pipeControlMessages.RunOrClosePipeInput();
+    runOrClosePipeInput.peer_associated_endpoint_closed_event = event;
+    return constructRunOrClosePipeMessage(runOrClosePipeInput);
+  };
+
+  var exports = {};
+  exports.PipeControlMessageProxy = PipeControlMessageProxy;
+
+  return exports;
+});
diff --git a/mojo/public/js/new_bindings/base.js b/mojo/public/js/new_bindings/base.js
new file mode 100644
index 0000000..db72d48
--- /dev/null
+++ b/mojo/public/js/new_bindings/base.js
@@ -0,0 +1,111 @@
+// Copyright 2017 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.
+
+'use strict';
+
+if (mojo && mojo.internal) {
+  throw new Error('The Mojo bindings library has been initialized.');
+}
+
+var mojo = mojo || {};
+mojo.internal = {};
+mojo.internal.global = this;
+mojo.config = {
+  // Whether to automatically load mojom dependencies.
+  // For example, if foo.mojom imports bar.mojom, |autoLoadMojomDeps| set to
+  // true means that loading foo.mojom.js will insert a <script> tag to load
+  // bar.mojom.js, if it hasn't been loaded.
+  //
+  // The URL of bar.mojom.js is determined by the relative path of bar.mojom
+  // (relative to the position of foo.mojom at build time) and the URL of
+  // foo.mojom.js. For exmple, if at build time the two mojom files are
+  // located at:
+  //   a/b/c/foo.mojom
+  //   a/b/d/bar.mojom
+  // and the URL of foo.mojom.js is:
+  //   http://example.org/scripts/b/c/foo.mojom.js
+  // then the URL of bar.mojom.js will be:
+  //   http://example.org/scripts/b/d/bar.mojom.js
+  //
+  // If you would like bar.mojom.js to live at a different location, you need
+  // to turn off |autoLoadMojomDeps| before loading foo.mojom.js, and manually
+  // load bar.mojom.js yourself. Similarly, you need to turn off the option if
+  // you merge bar.mojom.js and foo.mojom.js into a single file.
+  //
+  // Performance tip: Avoid loading the same mojom.js file multiple times.
+  // Assume that |autoLoadMojomDeps| is set to true:
+  // <!-- No duplicate loading; recommended. -->
+  // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
+  //
+  // <!-- No duplicate loading, although unnecessary. -->
+  // <script src="http://example.org/scripts/b/d/bar.mojom.js"></script>
+  // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
+  //
+  // <!-- Load bar.mojom.js twice; should be avoided. -->
+  // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
+  // <script src="http://example.org/scripts/b/d/bar.mojom.js"></script>
+  autoLoadMojomDeps: true
+};
+
+(function() {
+  var internal = mojo.internal;
+
+  var LoadState = {
+    PENDING_LOAD: 1,
+    LOADED: 2
+  };
+
+  var mojomRegistry = new Map();
+
+  function exposeNamespace(namespace) {
+    var current = internal.global;
+    var parts = namespace.split('.');
+
+    for (var part; parts.length && (part = parts.shift());) {
+      if (!current[part]) {
+        current[part] = {};
+      }
+      current = current[part];
+    }
+
+    return current;
+  }
+
+  function isMojomPendingLoad(id) {
+    return mojomRegistry.get(id) === LoadState.PENDING_LOAD;
+  }
+
+  function isMojomLoaded(id) {
+    return mojomRegistry.get(id) === LoadState.LOADED;
+  }
+
+  function markMojomPendingLoad(id) {
+    if (isMojomLoaded(id)) {
+      throw new Error('The following mojom file has been loaded: ' + id);
+    }
+
+    mojomRegistry.set(id, LoadState.PENDING_LOAD);
+  }
+
+  function markMojomLoaded(id) {
+    mojomRegistry.set(id, LoadState.LOADED);
+  }
+
+  function loadMojomIfNecessary(id, url) {
+    if (mojomRegistry.has(id)) {
+      return;
+    }
+
+    markMojomPendingLoad(id);
+    internal.global.document.write('<script type="text/javascript" src="' +
+                                   url + '"></script>');
+  }
+
+  internal.exposeNamespace = exposeNamespace;
+  internal.isMojomPendingLoad = isMojomPendingLoad;
+  internal.isMojomLoaded = isMojomLoaded;
+  internal.markMojomPendingLoad = markMojomPendingLoad;
+  internal.markMojomLoaded = markMojomLoaded;
+  internal.loadMojomIfNecessary = loadMojomIfNecessary;
+})();
diff --git a/mojo/public/js/new_bindings/bindings.js b/mojo/public/js/new_bindings/bindings.js
index f3e40d2..5b3b66e 100644
--- a/mojo/public/js/new_bindings/bindings.js
+++ b/mojo/public/js/new_bindings/bindings.js
@@ -2,19 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-define("mojo/public/js/bindings", [
-  "mojo/public/js/core",
-  "mojo/public/js/lib/control_message_proxy",
-  "mojo/public/js/interface_types",
-  "mojo/public/js/router",
-], function(core, controlMessageProxy, types, router) {
-
+(function() {
+  var internal = mojo.internal;
   // ---------------------------------------------------------------------------
 
   function makeRequest(interfacePtr) {
-    var pipe = core.createMessagePipe();
-    interfacePtr.ptr.bind(new types.InterfacePtrInfo(pipe.handle0, 0));
-    return new types.InterfaceRequest(pipe.handle1);
+    var pipe = Mojo.createMessagePipe();
+    interfacePtr.ptr.bind(new mojo.InterfacePtrInfo(pipe.handle0, 0));
+    return new mojo.InterfaceRequest(pipe.handle1);
   }
 
   // ---------------------------------------------------------------------------
@@ -41,7 +36,7 @@
   InterfacePtrController.prototype.bind = function(ptrInfoOrHandle) {
     this.reset();
 
-    if (ptrInfoOrHandle instanceof types.InterfacePtrInfo) {
+    if (ptrInfoOrHandle instanceof mojo.InterfacePtrInfo) {
       this.version = ptrInfoOrHandle.version;
       this.handle_ = ptrInfoOrHandle.handle;
     } else {
@@ -64,7 +59,7 @@
       this.proxy_ = null;
     }
     if (this.handle_) {
-      core.close(this.handle_);
+      this.handle_.close();
       this.handle_ = null;
     }
   };
@@ -82,12 +77,12 @@
     var result;
     if (this.router_) {
       // TODO(yzshen): Fix Router interface to support extracting handle.
-      result = new types.InterfacePtrInfo(
+      result = new mojo.InterfacePtrInfo(
           this.router_.connector_.handle_, this.version);
       this.router_.connector_.handle_ = null;
     } else {
       // This also handles the case when this object is not bound.
-      result = new types.InterfacePtrInfo(this.handle_, this.version);
+      result = new mojo.InterfacePtrInfo(this.handle_, this.version);
       this.handle_ = null;
     }
 
@@ -109,12 +104,11 @@
     if (!this.handle_)
       return;
 
-    this.router_ = new router.Router(this.handle_);
+    this.router_ = new internal.Router(this.handle_);
     this.handle_ = null;
     this.router_ .setPayloadValidators([this.interfaceType_.validateResponse]);
 
-    this.controlMessageProxy_ = new
-        controlMessageProxy.ControlMessageProxy(this.router_);
+    this.controlMessageProxy_ = new internal.ControlMessageProxy(this.router_);
 
     this.proxy_ = new this.interfaceType_.proxyClass(this.router_);
   };
@@ -179,13 +173,13 @@
   Binding.prototype.bind = function(requestOrHandle) {
     this.close();
 
-    var handle = requestOrHandle instanceof types.InterfaceRequest ?
+    var handle = requestOrHandle instanceof mojo.InterfaceRequest ?
         requestOrHandle.handle : requestOrHandle;
-    if (!core.isHandle(handle))
+    if (!(handle instanceof MojoHandle))
       return;
 
     this.stub_ = new this.interfaceType_.stubClass(this.impl_);
-    this.router_ = new router.Router(handle, this.interfaceType_.kVersion);
+    this.router_ = new internal.Router(handle, this.interfaceType_.kVersion);
     this.router_.setIncomingReceiver(this.stub_);
     this.router_ .setPayloadValidators([this.interfaceType_.validateRequest]);
   };
@@ -208,9 +202,9 @@
 
   Binding.prototype.unbind = function() {
     if (!this.isBound())
-      return new types.InterfaceRequest(null);
+      return new mojo.InterfaceRequest(null);
 
-    var result = new types.InterfaceRequest(this.router_.connector_.handle_);
+    var result = new mojo.InterfaceRequest(this.router_.connector_.handle_);
     this.router_.connector_.handle_ = null;
     this.close();
     return result;
@@ -273,13 +267,9 @@
       this.errorHandler_();
   };
 
-  var exports = {};
-  exports.InterfacePtrInfo = types.InterfacePtrInfo;
-  exports.InterfaceRequest = types.InterfaceRequest;
-  exports.makeRequest = makeRequest;
-  exports.InterfacePtrController = InterfacePtrController;
-  exports.Binding = Binding;
-  exports.BindingSet = BindingSet;
 
-  return exports;
-});
+  mojo.makeRequest = makeRequest;
+  mojo.Binding = Binding;
+  mojo.BindingSet = BindingSet;
+  mojo.InterfacePtrController = InterfacePtrController;
+})();
diff --git a/mojo/public/js/new_bindings/buffer.js b/mojo/public/js/new_bindings/buffer.js
index e35f695..c44058b 100644
--- a/mojo/public/js/new_bindings/buffer.js
+++ b/mojo/public/js/new_bindings/buffer.js
@@ -2,7 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-define("mojo/public/js/buffer", function() {
+(function() {
+  var internal = mojo.internal;
 
   var kHostIsLittleEndian = (function () {
     var endianArrayBuffer = new ArrayBuffer(2);
@@ -150,7 +151,5 @@
     this.dataView.setFloat64(offset, value, kHostIsLittleEndian);
   }
 
-  var exports = {};
-  exports.Buffer = Buffer;
-  return exports;
-});
+  internal.Buffer = Buffer;
+})();
diff --git a/mojo/public/js/new_bindings/codec.js b/mojo/public/js/new_bindings/codec.js
index ff5d31a..339fc16 100644
--- a/mojo/public/js/new_bindings/codec.js
+++ b/mojo/public/js/new_bindings/codec.js
@@ -2,11 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-define("mojo/public/js/codec", [
-  "mojo/public/js/buffer",
-  "mojo/public/js/interface_types",
-  "mojo/public/js/unicode",
-], function(buffer, types, unicode) {
+(function() {
+  var internal = mojo.internal;
 
   var kErrorUnsigned = "Passing negative value to unsigned";
   var kErrorArray = "Passing non Array for array type";
@@ -138,7 +135,7 @@
     var numberOfElements = this.readUint32();
     var base = this.next;
     this.next += numberOfElements;
-    return unicode.decodeUtf8String(
+    return internal.decodeUtf8String(
         new Uint8Array(this.buffer.arrayBuffer, base, numberOfElements));
   };
 
@@ -314,7 +311,7 @@
 
   Encoder.prototype.encodeString = function(val) {
     var base = this.next + kArrayHeaderSize;
-    var numberOfElements = unicode.encodeUtf8String(
+    var numberOfElements = internal.encodeUtf8String(
         val, new Uint8Array(this.buffer.arrayBuffer, base));
     var numberOfBytes = kArrayHeaderSize + numberOfElements;
     this.writeUint32(numberOfBytes);
@@ -389,7 +386,7 @@
     if (typeof(val) !== "string") {
       throw new Error(kErrorString);
     }
-    var encodedSize = kArrayHeaderSize + unicode.utf8Length(val);
+    var encodedSize = kArrayHeaderSize + internal.utf8Length(val);
     var encoder = this.createAndEncodeEncoder(encodedSize);
     encoder.encodeString(val);
   };
@@ -473,7 +470,7 @@
     // Currently, we don't compute the payload size correctly ahead of time.
     // Instead, we resize the buffer at the end.
     var numberOfBytes = kMessageHeaderSize + payloadSize;
-    this.buffer = new buffer.Buffer(numberOfBytes);
+    this.buffer = new internal.Buffer(numberOfBytes);
     this.handles = [];
     var encoder = this.createEncoder(kMessageHeaderSize);
     encoder.writeUint32(kMessageHeaderSize);
@@ -511,7 +508,7 @@
     // Currently, we don't compute the payload size correctly ahead of time.
     // Instead, we resize the buffer at the end.
     var numberOfBytes = kMessageWithRequestIDHeaderSize + payloadSize;
-    this.buffer = new buffer.Buffer(numberOfBytes);
+    this.buffer = new internal.Buffer(numberOfBytes);
     this.handles = [];
     var encoder = this.createEncoder(kMessageWithRequestIDHeaderSize);
     encoder.writeUint32(kMessageWithRequestIDHeaderSize);
@@ -814,7 +811,7 @@
   Interface.prototype.encodedSize = 8;
 
   Interface.prototype.decode = function(decoder) {
-    var interfacePtrInfo = new types.InterfacePtrInfo(
+    var interfacePtrInfo = new mojo.InterfacePtrInfo(
         decoder.decodeHandle(), decoder.readUint32());
     var interfacePtr = new this.cls();
     interfacePtr.ptr.bind(interfacePtrInfo);
@@ -823,7 +820,7 @@
 
   Interface.prototype.encode = function(encoder, val) {
     var interfacePtrInfo =
-        val ? val.ptr.passInterface() : new types.InterfacePtrInfo(null, 0);
+        val ? val.ptr.passInterface() : new mojo.InterfacePtrInfo(null, 0);
     encoder.encodeHandle(interfacePtrInfo.handle);
     encoder.writeUint32(interfacePtrInfo.version);
   };
@@ -840,7 +837,7 @@
   InterfaceRequest.encodedSize = 4;
 
   InterfaceRequest.decode = function(decoder) {
-    return new types.InterfaceRequest(decoder.decodeHandle());
+    return new mojo.InterfaceRequest(decoder.decodeHandle());
   };
 
   InterfaceRequest.encode = function(encoder, val) {
@@ -877,46 +874,44 @@
 
   NullableMapOf.prototype = Object.create(MapOf.prototype);
 
-  var exports = {};
-  exports.align = align;
-  exports.isAligned = isAligned;
-  exports.Message = Message;
-  exports.MessageBuilder = MessageBuilder;
-  exports.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder;
-  exports.MessageReader = MessageReader;
-  exports.kArrayHeaderSize = kArrayHeaderSize;
-  exports.kMapStructPayloadSize = kMapStructPayloadSize;
-  exports.kStructHeaderSize = kStructHeaderSize;
-  exports.kEncodedInvalidHandleValue = kEncodedInvalidHandleValue;
-  exports.kMessageHeaderSize = kMessageHeaderSize;
-  exports.kMessageWithRequestIDHeaderSize = kMessageWithRequestIDHeaderSize;
-  exports.kMessageExpectsResponse = kMessageExpectsResponse;
-  exports.kMessageIsResponse = kMessageIsResponse;
-  exports.Int8 = Int8;
-  exports.Uint8 = Uint8;
-  exports.Int16 = Int16;
-  exports.Uint16 = Uint16;
-  exports.Int32 = Int32;
-  exports.Uint32 = Uint32;
-  exports.Int64 = Int64;
-  exports.Uint64 = Uint64;
-  exports.Float = Float;
-  exports.Double = Double;
-  exports.String = String;
-  exports.Enum = Enum;
-  exports.NullableString = NullableString;
-  exports.PointerTo = PointerTo;
-  exports.NullablePointerTo = NullablePointerTo;
-  exports.ArrayOf = ArrayOf;
-  exports.NullableArrayOf = NullableArrayOf;
-  exports.PackedBool = PackedBool;
-  exports.Handle = Handle;
-  exports.NullableHandle = NullableHandle;
-  exports.Interface = Interface;
-  exports.NullableInterface = NullableInterface;
-  exports.InterfaceRequest = InterfaceRequest;
-  exports.NullableInterfaceRequest = NullableInterfaceRequest;
-  exports.MapOf = MapOf;
-  exports.NullableMapOf = NullableMapOf;
-  return exports;
-});
+  internal.align = align;
+  internal.isAligned = isAligned;
+  internal.Message = Message;
+  internal.MessageBuilder = MessageBuilder;
+  internal.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder;
+  internal.MessageReader = MessageReader;
+  internal.kArrayHeaderSize = kArrayHeaderSize;
+  internal.kMapStructPayloadSize = kMapStructPayloadSize;
+  internal.kStructHeaderSize = kStructHeaderSize;
+  internal.kEncodedInvalidHandleValue = kEncodedInvalidHandleValue;
+  internal.kMessageHeaderSize = kMessageHeaderSize;
+  internal.kMessageWithRequestIDHeaderSize = kMessageWithRequestIDHeaderSize;
+  internal.kMessageExpectsResponse = kMessageExpectsResponse;
+  internal.kMessageIsResponse = kMessageIsResponse;
+  internal.Int8 = Int8;
+  internal.Uint8 = Uint8;
+  internal.Int16 = Int16;
+  internal.Uint16 = Uint16;
+  internal.Int32 = Int32;
+  internal.Uint32 = Uint32;
+  internal.Int64 = Int64;
+  internal.Uint64 = Uint64;
+  internal.Float = Float;
+  internal.Double = Double;
+  internal.String = String;
+  internal.Enum = Enum;
+  internal.NullableString = NullableString;
+  internal.PointerTo = PointerTo;
+  internal.NullablePointerTo = NullablePointerTo;
+  internal.ArrayOf = ArrayOf;
+  internal.NullableArrayOf = NullableArrayOf;
+  internal.PackedBool = PackedBool;
+  internal.Handle = Handle;
+  internal.NullableHandle = NullableHandle;
+  internal.Interface = Interface;
+  internal.NullableInterface = NullableInterface;
+  internal.InterfaceRequest = InterfaceRequest;
+  internal.NullableInterfaceRequest = NullableInterfaceRequest;
+  internal.MapOf = MapOf;
+  internal.NullableMapOf = NullableMapOf;
+})();
diff --git a/mojo/public/js/new_bindings/connector.js b/mojo/public/js/new_bindings/connector.js
index ee16be8..7fa4822 100644
--- a/mojo/public/js/new_bindings/connector.js
+++ b/mojo/public/js/new_bindings/connector.js
@@ -2,15 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-define("mojo/public/js/connector", [
-  "mojo/public/js/buffer",
-  "mojo/public/js/codec",
-  "mojo/public/js/core",
-  "mojo/public/js/support",
-], function(buffer, codec, core, support) {
+(function() {
+  var internal = mojo.internal;
 
   function Connector(handle) {
-    if (!core.isHandle(handle))
+    if (!(handle instanceof MojoHandle))
       throw new Error("Connector: not a handle " + handle);
     this.handle_ = handle;
     this.dropWrites_ = false;
@@ -20,19 +16,18 @@
     this.errorHandler_ = null;
 
     if (handle) {
-      this.readWatcher_ = support.watch(handle,
-                                        core.HANDLE_SIGNAL_READABLE,
-                                        this.readMore_.bind(this));
+      this.readWatcher_ = handle.watch({readable: true},
+                                       this.readMore_.bind(this));
     }
   }
 
   Connector.prototype.close = function() {
     if (this.readWatcher_) {
-      support.cancelWatch(this.readWatcher_);
+      this.readWatcher_.cancel();
       this.readWatcher_ = null;
     }
     if (this.handle_ != null) {
-      core.close(this.handle_);
+      this.handle_.close();
       this.handle_ = null;
     }
   };
@@ -44,17 +39,15 @@
     if (this.dropWrites_)
       return true;
 
-    var result = core.writeMessage(this.handle_,
-                                   new Uint8Array(message.buffer.arrayBuffer),
-                                   message.handles,
-                                   core.WRITE_MESSAGE_FLAG_NONE);
+    var result = this.handle_.writeMessage(
+        new Uint8Array(message.buffer.arrayBuffer), message.handles);
     switch (result) {
-      case core.RESULT_OK:
+      case Mojo.RESULT_OK:
         // The handles were successfully transferred, so we don't own them
         // anymore.
         message.handles = [];
         break;
-      case core.RESULT_FAILED_PRECONDITION:
+      case Mojo.RESULT_FAILED_PRECONDITION:
         // There's no point in continuing to write to this pipe since the other
         // end is gone. Avoid writing any future messages. Hide write failures
         // from the caller since we'd like them to continue consuming any
@@ -83,33 +76,29 @@
   };
 
   Connector.prototype.waitForNextMessageForTesting = function() {
-    var wait = core.wait(this.handle_, core.HANDLE_SIGNAL_READABLE,
-                         core.DEADLINE_INDEFINITE);
-    this.readMore_(wait.result);
+    // TODO(yzshen): Change the tests that use this method.
+    throw new Error("Not supported!");
   };
 
   Connector.prototype.readMore_ = function(result) {
     for (;;) {
-      var read = core.readMessage(this.handle_,
-                                  core.READ_MESSAGE_FLAG_NONE);
+      var read = this.handle_.readMessage();
       if (this.handle_ == null) // The connector has been closed.
         return;
-      if (read.result == core.RESULT_SHOULD_WAIT)
+      if (read.result == Mojo.RESULT_SHOULD_WAIT)
         return;
-      if (read.result != core.RESULT_OK) {
+      if (read.result != Mojo.RESULT_OK) {
         this.error_ = true;
         if (this.errorHandler_)
           this.errorHandler_.onError(read.result);
         return;
       }
-      var messageBuffer = new buffer.Buffer(read.buffer);
-      var message = new codec.Message(messageBuffer, read.handles);
+      var messageBuffer = new internal.Buffer(read.buffer);
+      var message = new internal.Message(messageBuffer, read.handles);
       if (this.incomingReceiver_)
         this.incomingReceiver_.accept(message);
     }
   };
 
-  var exports = {};
-  exports.Connector = Connector;
-  return exports;
-});
+  internal.Connector = Connector;
+})();
diff --git a/mojo/public/js/new_bindings/interface_types.js b/mojo/public/js/new_bindings/interface_types.js
index 01ea2d1..c52f6c7 100644
--- a/mojo/public/js/new_bindings/interface_types.js
+++ b/mojo/public/js/new_bindings/interface_types.js
@@ -2,10 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-define("mojo/public/js/interface_types", [
-  "mojo/public/js/core",
-], function(core) {
-
+(function() {
   // ---------------------------------------------------------------------------
 
   function InterfacePtrInfo(handle, version) {
@@ -14,14 +11,14 @@
   }
 
   InterfacePtrInfo.prototype.isValid = function() {
-    return core.isHandle(this.handle);
+    return this.handle instanceof MojoHandle;
   };
 
   InterfacePtrInfo.prototype.close = function() {
     if (!this.isValid())
       return;
 
-    core.close(this.handle);
+    this.handle.close();
     this.handle = null;
     this.version = 0;
   };
@@ -33,20 +30,17 @@
   }
 
   InterfaceRequest.prototype.isValid = function() {
-    return core.isHandle(this.handle);
+    return this.handle instanceof MojoHandle;
   };
 
   InterfaceRequest.prototype.close = function() {
     if (!this.isValid())
       return;
 
-    core.close(this.handle);
+    this.handle.close();
     this.handle = null;
   };
 
-  var exports = {};
-  exports.InterfacePtrInfo = InterfacePtrInfo;
-  exports.InterfaceRequest = InterfaceRequest;
-
-  return exports;
-});
+  mojo.InterfacePtrInfo = InterfacePtrInfo;
+  mojo.InterfaceRequest = InterfaceRequest;
+})();
diff --git a/mojo/public/js/new_bindings/lib/control_message_handler.js b/mojo/public/js/new_bindings/lib/control_message_handler.js
index 81d9002..3f122fb 100644
--- a/mojo/public/js/new_bindings/lib/control_message_handler.js
+++ b/mojo/public/js/new_bindings/lib/control_message_handler.js
@@ -2,90 +2,88 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-define("mojo/public/js/lib/control_message_handler", [
-  "mojo/public/js/codec",
-  "mojo/public/interfaces/bindings/interface_control_messages.mojom",
-  "mojo/public/js/validator",
-], function(codec, controlMessages, validator) {
-
-  var Validator = validator.Validator;
+(function() {
+  var internal = mojo.internal;
 
   function validateControlRequestWithResponse(message) {
-    var messageValidator = new Validator(message);
+    var messageValidator = new internal.Validator(message);
     var error = messageValidator.validateMessageIsRequestExpectingResponse();
-    if (error !== validator.validationError.NONE) {
+    if (error !== internal.validationError.NONE) {
       throw error;
     }
 
-    if (message.getName() != controlMessages.kRunMessageId) {
+    if (message.getName() != mojo.interface_control2.kRunMessageId) {
       throw new Error("Control message name is not kRunMessageId");
     }
 
     // Validate payload.
-    error = controlMessages.RunMessageParams.validate(messageValidator,
+    error = mojo.interface_control2.RunMessageParams.validate(messageValidator,
         message.getHeaderNumBytes());
-    if (error != validator.validationError.NONE) {
+    if (error != internal.validationError.NONE) {
       throw error;
     }
   }
 
   function validateControlRequestWithoutResponse(message) {
-    var messageValidator = new Validator(message);
+    var messageValidator = new internal.Validator(message);
     var error = messageValidator.validateMessageIsRequestWithoutResponse();
-    if (error != validator.validationError.NONE) {
+    if (error != internal.validationError.NONE) {
       throw error;
     }
 
-    if (message.getName() != controlMessages.kRunOrClosePipeMessageId) {
+    if (message.getName() != mojo.interface_control2.kRunOrClosePipeMessageId) {
       throw new Error("Control message name is not kRunOrClosePipeMessageId");
     }
 
     // Validate payload.
-    error = controlMessages.RunOrClosePipeMessageParams.validate(
+    error = mojo.interface_control2.RunOrClosePipeMessageParams.validate(
         messageValidator, message.getHeaderNumBytes());
-    if (error != validator.validationError.NONE) {
+    if (error != internal.validationError.NONE) {
       throw error;
     }
   }
 
   function runOrClosePipe(message, interface_version) {
-    var reader = new codec.MessageReader(message);
+    var reader = new internal.MessageReader(message);
     var runOrClosePipeMessageParams = reader.decodeStruct(
-        controlMessages.RunOrClosePipeMessageParams);
+        mojo.interface_control2.RunOrClosePipeMessageParams);
     return interface_version >=
         runOrClosePipeMessageParams.input.require_version.version;
   }
 
   function run(message, responder, interface_version) {
-    var reader = new codec.MessageReader(message);
+    var reader = new internal.MessageReader(message);
     var runMessageParams =
-        reader.decodeStruct(controlMessages.RunMessageParams);
+        reader.decodeStruct(mojo.interface_control2.RunMessageParams);
     var runOutput = null;
 
     if (runMessageParams.input.query_version) {
-      runOutput = new controlMessages.RunOutput();
+      runOutput = new mojo.interface_control2.RunOutput();
       runOutput.query_version_result = new
-          controlMessages.QueryVersionResult({'version': interface_version});
+          mojo.interface_control2.QueryVersionResult(
+              {'version': interface_version});
     }
 
     var runResponseMessageParams = new
-        controlMessages.RunResponseMessageParams();
+        mojo.interface_control2.RunResponseMessageParams();
     runResponseMessageParams.output = runOutput;
 
-    var messageName = controlMessages.kRunMessageId;
-    var payloadSize = controlMessages.RunResponseMessageParams.encodedSize;
+    var messageName = mojo.interface_control2.kRunMessageId;
+    var payloadSize =
+        mojo.interface_control2.RunResponseMessageParams.encodedSize;
     var requestID = reader.requestID;
-    var builder = new codec.MessageWithRequestIDBuilder(messageName,
-        payloadSize, codec.kMessageIsResponse, requestID);
-    builder.encodeStruct(controlMessages.RunResponseMessageParams,
+    var builder = new internal.MessageWithRequestIDBuilder(messageName,
+        payloadSize, internal.kMessageIsResponse, requestID);
+    builder.encodeStruct(mojo.interface_control2.RunResponseMessageParams,
                          runResponseMessageParams);
     responder.accept(builder.finish());
     return true;
   }
 
-  function isControlMessage(message) {
-    return message.getName() == controlMessages.kRunMessageId ||
-           message.getName() == controlMessages.kRunOrClosePipeMessageId;
+  function isInterfaceControlMessage(message) {
+    return message.getName() == mojo.interface_control2.kRunMessageId ||
+           message.getName() ==
+               mojo.interface_control2.kRunOrClosePipeMessageId;
   }
 
   function ControlMessageHandler(interface_version) {
@@ -103,9 +101,6 @@
     return run(message, responder, this.interface_version);
   };
 
-  var exports = {};
-  exports.ControlMessageHandler = ControlMessageHandler;
-  exports.isControlMessage = isControlMessage;
-
-  return exports;
-});
+  internal.ControlMessageHandler = ControlMessageHandler;
+  internal.isInterfaceControlMessage = isInterfaceControlMessage;
+})();
diff --git a/mojo/public/js/new_bindings/lib/control_message_proxy.js b/mojo/public/js/new_bindings/lib/control_message_proxy.js
index d6c0734..1d57557 100644
--- a/mojo/public/js/new_bindings/lib/control_message_proxy.js
+++ b/mojo/public/js/new_bindings/lib/control_message_proxy.js
@@ -2,39 +2,35 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-define("mojo/public/js/lib/control_message_proxy", [
-  "mojo/public/interfaces/bindings/interface_control_messages.mojom",
-  "mojo/public/js/codec",
-  "mojo/public/js/validator",
-], function(controlMessages, codec, validator) {
-
-  var Validator = validator.Validator;
+(function() {
+  var internal = mojo.internal;
 
   function sendRunOrClosePipeMessage(receiver, runOrClosePipeMessageParams) {
-    var messageName = controlMessages.kRunOrClosePipeMessageId;
-    var payloadSize = controlMessages.RunOrClosePipeMessageParams.encodedSize;
-    var builder = new codec.MessageBuilder(messageName, payloadSize);
-    builder.encodeStruct(controlMessages.RunOrClosePipeMessageParams,
+    var messageName = mojo.interface_control2.kRunOrClosePipeMessageId;
+    var payloadSize =
+        mojo.interface_control2.RunOrClosePipeMessageParams.encodedSize;
+    var builder = new internal.MessageBuilder(messageName, payloadSize);
+    builder.encodeStruct(mojo.interface_control2.RunOrClosePipeMessageParams,
                          runOrClosePipeMessageParams);
     var message = builder.finish();
     receiver.accept(message);
   }
 
   function validateControlResponse(message) {
-    var messageValidator = new Validator(message);
+    var messageValidator = new internal.Validator(message);
     var error = messageValidator.validateMessageIsResponse();
-    if (error != validator.validationError.NONE) {
+    if (error != internal.validationError.NONE) {
       throw error;
     }
 
-    if (message.getName() != controlMessages.kRunMessageId) {
+    if (message.getName() != mojo.interface_control2.kRunMessageId) {
       throw new Error("Control message name is not kRunMessageId");
     }
 
     // Validate payload.
-    error = controlMessages.RunResponseMessageParams.validate(
+    error = mojo.interface_control2.RunResponseMessageParams.validate(
         messageValidator, message.getHeaderNumBytes());
-    if (error != validator.validationError.NONE) {
+    if (error != internal.validationError.NONE) {
       throw error;
     }
   }
@@ -42,9 +38,9 @@
   function acceptRunResponse(message) {
     validateControlResponse(message);
 
-    var reader = new codec.MessageReader(message);
+    var reader = new internal.MessageReader(message);
     var runResponseMessageParams = reader.decodeStruct(
-        controlMessages.RunResponseMessageParams);
+        mojo.interface_control2.RunResponseMessageParams);
 
     return Promise.resolve(runResponseMessageParams);
   }
@@ -59,12 +55,13 @@
   * @return {Promise} that resolves to a RunResponseMessageParams.
   */
   function sendRunMessage(receiver, runMessageParams) {
-    var messageName = controlMessages.kRunMessageId;
-    var payloadSize = controlMessages.RunMessageParams.encodedSize;
+    var messageName = mojo.interface_control2.kRunMessageId;
+    var payloadSize = mojo.interface_control2.RunMessageParams.encodedSize;
     // |requestID| is set to 0, but is later properly set by Router.
-    var builder = new codec.MessageWithRequestIDBuilder(messageName,
-        payloadSize, codec.kMessageExpectsResponse, 0);
-    builder.encodeStruct(controlMessages.RunMessageParams, runMessageParams);
+    var builder = new internal.MessageWithRequestIDBuilder(messageName,
+        payloadSize, internal.kMessageExpectsResponse, 0);
+    builder.encodeStruct(mojo.interface_control2.RunMessageParams,
+                         runMessageParams);
     var message = builder.finish();
 
     return receiver.acceptAndExpectResponse(message).then(acceptRunResponse);
@@ -75,9 +72,10 @@
   }
 
   ControlMessageProxy.prototype.queryVersion = function() {
-    var runMessageParams = new controlMessages.RunMessageParams();
-    runMessageParams.input = new controlMessages.RunInput();
-    runMessageParams.input.query_version = new controlMessages.QueryVersion();
+    var runMessageParams = new mojo.interface_control2.RunMessageParams();
+    runMessageParams.input = new mojo.interface_control2.RunInput();
+    runMessageParams.input.query_version =
+        new mojo.interface_control2.QueryVersion();
 
     return sendRunMessage(this.receiver, runMessageParams).then(function(
         runResponseMessageParams) {
@@ -87,16 +85,13 @@
 
   ControlMessageProxy.prototype.requireVersion = function(version) {
     var runOrClosePipeMessageParams = new
-        controlMessages.RunOrClosePipeMessageParams();
+        mojo.interface_control2.RunOrClosePipeMessageParams();
     runOrClosePipeMessageParams.input = new
-        controlMessages.RunOrClosePipeInput();
+        mojo.interface_control2.RunOrClosePipeInput();
     runOrClosePipeMessageParams.input.require_version = new
-        controlMessages.RequireVersion({'version': version});
+        mojo.interface_control2.RequireVersion({'version': version});
     sendRunOrClosePipeMessage(this.receiver, runOrClosePipeMessageParams);
   };
 
-  var exports = {};
-  exports.ControlMessageProxy = ControlMessageProxy;
-
-  return exports;
-});
+  internal.ControlMessageProxy = ControlMessageProxy;
+})();
diff --git a/mojo/public/js/new_bindings/router.js b/mojo/public/js/new_bindings/router.js
index e94c5eb..1272407 100644
--- a/mojo/public/js/new_bindings/router.js
+++ b/mojo/public/js/new_bindings/router.js
@@ -2,25 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-define("mojo/public/js/router", [
-  "console",
-  "mojo/public/js/codec",
-  "mojo/public/js/core",
-  "mojo/public/js/connector",
-  "mojo/public/js/lib/control_message_handler",
-  "mojo/public/js/validator",
-], function(console, codec, core, connector, controlMessageHandler, validator) {
-
-  var Connector = connector.Connector;
-  var MessageReader = codec.MessageReader;
-  var Validator = validator.Validator;
-  var ControlMessageHandler = controlMessageHandler.ControlMessageHandler;
+(function() {
+  var internal = mojo.internal;
 
   function Router(handle, interface_version, connectorFactory) {
-    if (!core.isHandle(handle))
+    if (!(handle instanceof MojoHandle))
       throw new Error("Router constructor: Not a handle");
     if (connectorFactory === undefined)
-      connectorFactory = Connector;
+      connectorFactory = internal.Connector;
     this.connector_ = new connectorFactory(handle);
     this.incomingReceiver_ = null;
     this.errorHandler_ = null;
@@ -31,7 +20,7 @@
 
     if (interface_version !== undefined) {
       this.controlMessageHandler_ = new
-          ControlMessageHandler(interface_version);
+          internal.ControlMessageHandler(interface_version);
     }
 
     this.connector_.setIncomingReceiver({
@@ -97,8 +86,8 @@
   };
 
   Router.prototype.handleIncomingMessage_ = function(message) {
-    var noError = validator.validationError.NONE;
-    var messageValidator = new Validator(message);
+    var noError = internal.validationError.NONE;
+    var messageValidator = new internal.Validator(message);
     var err = messageValidator.validateMessageHeader();
     for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i)
       err = this.payloadValidators_[i](messageValidator);
@@ -114,7 +103,7 @@
       return;
 
     if (message.expectsResponse()) {
-      if (controlMessageHandler.isControlMessage(message)) {
+      if (internal.isInterfaceControlMessage(message)) {
         if (this.controlMessageHandler_) {
           this.controlMessageHandler_.acceptWithResponder(message, this);
         } else {
@@ -128,7 +117,7 @@
         this.close();
       }
     } else if (message.isResponse()) {
-      var reader = new MessageReader(message);
+      var reader = new internal.MessageReader(message);
       var requestID = reader.requestID;
       var completer = this.completers_.get(requestID);
       if (completer) {
@@ -138,7 +127,7 @@
         console.log("Unexpected response with request ID: " + requestID);
       }
     } else {
-      if (controlMessageHandler.isControlMessage(message)) {
+      if (internal.isInterfaceControlMessage(message)) {
         if (this.controlMessageHandler_) {
           var ok = this.controlMessageHandler_.accept(message);
           if (ok) return;
@@ -156,7 +145,7 @@
       // TODO(yzshen): This should also trigger connection error handler.
       // Consider making accept() return a boolean and let the connector deal
       // with this, as the C++ code does.
-      console.log("Invalid message: " + validator.validationError[error]);
+      console.log("Invalid message: " + internal.validationError[error]);
 
       this.close();
       return;
@@ -197,7 +186,5 @@
       this.invalidMessageHandler_(error);
   };
 
-  var exports = {};
-  exports.Router = Router;
-  return exports;
-});
+  internal.Router = Router;
+})();
diff --git a/mojo/public/js/new_bindings/unicode.js b/mojo/public/js/new_bindings/unicode.js
index be2ba0e..6ed8839 100644
--- a/mojo/public/js/new_bindings/unicode.js
+++ b/mojo/public/js/new_bindings/unicode.js
@@ -7,7 +7,9 @@
  * stored in ArrayBuffers. There is much room for optimization in this code if
  * it proves necessary.
  */
-define("mojo/public/js/unicode", function() {
+(function() {
+  var internal = mojo.internal;
+
   /**
    * Decodes the UTF8 string from the given buffer.
    * @param {ArrayBufferView} buffer The buffer containing UTF8 string data.
@@ -43,9 +45,7 @@
     return utf8String.length;
   }
 
-  var exports = {};
-  exports.decodeUtf8String = decodeUtf8String;
-  exports.encodeUtf8String = encodeUtf8String;
-  exports.utf8Length = utf8Length;
-  return exports;
-});
+  internal.decodeUtf8String = decodeUtf8String;
+  internal.encodeUtf8String = encodeUtf8String;
+  internal.utf8Length = utf8Length;
+})();
diff --git a/mojo/public/js/new_bindings/validator.js b/mojo/public/js/new_bindings/validator.js
index fee742d..610112b 100644
--- a/mojo/public/js/new_bindings/validator.js
+++ b/mojo/public/js/new_bindings/validator.js
@@ -2,9 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-define("mojo/public/js/validator", [
-  "mojo/public/js/codec",
-], function(codec) {
+(function() {
+  var internal = mojo.internal;
 
   var validationError = {
     NONE: 'VALIDATION_ERROR_NONE',
@@ -30,32 +29,33 @@
   var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
 
   function isEnumClass(cls) {
-    return cls instanceof codec.Enum;
+    return cls instanceof internal.Enum;
   }
 
   function isStringClass(cls) {
-    return cls === codec.String || cls === codec.NullableString;
+    return cls === internal.String || cls === internal.NullableString;
   }
 
   function isHandleClass(cls) {
-    return cls === codec.Handle || cls === codec.NullableHandle;
+    return cls === internal.Handle || cls === internal.NullableHandle;
   }
 
   function isInterfaceClass(cls) {
-    return cls instanceof codec.Interface;
+    return cls instanceof internal.Interface;
   }
 
   function isInterfaceRequestClass(cls) {
-    return cls === codec.InterfaceRequest ||
-        cls === codec.NullableInterfaceRequest;
+    return cls === internal.InterfaceRequest ||
+        cls === internal.NullableInterfaceRequest;
   }
 
   function isNullable(type) {
-    return type === codec.NullableString || type === codec.NullableHandle ||
-        type === codec.NullableInterface ||
-        type === codec.NullableInterfaceRequest ||
-        type instanceof codec.NullableArrayOf ||
-        type instanceof codec.NullablePointerTo;
+    return type === internal.NullableString ||
+        type === internal.NullableHandle ||
+        type === internal.NullableInterface ||
+        type === internal.NullableInterfaceRequest ||
+        type instanceof internal.NullableArrayOf ||
+        type instanceof internal.NullablePointerTo;
   }
 
   function Validator(message) {
@@ -98,7 +98,7 @@
   };
 
   Validator.prototype.claimHandle = function(index) {
-    if (index === codec.kEncodedInvalidHandleValue)
+    if (index === internal.kEncodedInvalidHandleValue)
       return true;
 
     if (index < this.handleIndex || index >= this.handleIndexLimit)
@@ -119,7 +119,7 @@
   Validator.prototype.validateHandle = function(offset, nullable) {
     var index = this.message.buffer.getUint32(offset);
 
-    if (index === codec.kEncodedInvalidHandleValue)
+    if (index === internal.kEncodedInvalidHandleValue)
       return nullable ?
           validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE;
 
@@ -138,10 +138,10 @@
   };
 
   Validator.prototype.validateStructHeader = function(offset, minNumBytes) {
-    if (!codec.isAligned(offset))
+    if (!internal.isAligned(offset))
       return validationError.MISALIGNED_OBJECT;
 
-    if (!this.isValidRange(offset, codec.kStructHeaderSize))
+    if (!this.isValidRange(offset, internal.kStructHeaderSize))
       return validationError.ILLEGAL_MEMORY_RANGE;
 
     var numBytes = this.message.buffer.getUint32(offset);
@@ -182,7 +182,7 @@
 
   Validator.prototype.validateMessageHeader = function() {
 
-    var err = this.validateStructHeader(0, codec.kMessageHeaderSize);
+    var err = this.validateStructHeader(0, internal.kMessageHeaderSize);
     if (err != validationError.NONE)
       return err;
 
@@ -190,11 +190,11 @@
     var version = this.message.getHeaderVersion();
 
     var validVersionAndNumBytes =
-        (version == 0 && numBytes == codec.kMessageHeaderSize) ||
+        (version == 0 && numBytes == internal.kMessageHeaderSize) ||
         (version == 1 &&
-         numBytes == codec.kMessageWithRequestIDHeaderSize) ||
+         numBytes == internal.kMessageWithRequestIDHeaderSize) ||
         (version > 1 &&
-         numBytes >= codec.kMessageWithRequestIDHeaderSize);
+         numBytes >= internal.kMessageWithRequestIDHeaderSize);
     if (!validVersionAndNumBytes)
       return validationError.UNEXPECTED_STRUCT_HEADER;
 
@@ -322,13 +322,14 @@
       return mapIsNullable ?
           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
 
-    var mapEncodedSize = codec.kStructHeaderSize + codec.kMapStructPayloadSize;
+    var mapEncodedSize = internal.kStructHeaderSize +
+        internal.kMapStructPayloadSize;
     var err = this.validateStructHeader(structOffset, mapEncodedSize);
     if (err !== validationError.NONE)
         return err;
 
     // Validate the keys array.
-    var keysArrayPointerOffset = structOffset + codec.kStructHeaderSize;
+    var keysArrayPointerOffset = structOffset + internal.kStructHeaderSize;
     err = this.validateArrayPointer(
         keysArrayPointerOffset, keyClass.encodedSize, keyClass, false, [0], 0);
     if (err !== validationError.NONE)
@@ -337,7 +338,7 @@
     // Validate the values array.
     var valuesArrayPointerOffset = keysArrayPointerOffset + 8;
     var valuesArrayDimensions = [0]; // Validate the actual length below.
-    if (valueClass instanceof codec.ArrayOf)
+    if (valueClass instanceof internal.ArrayOf)
       valuesArrayDimensions =
           valuesArrayDimensions.concat(valueClass.dimensions());
     var err = this.validateArrayPointer(valuesArrayPointerOffset,
@@ -360,7 +361,7 @@
 
   Validator.prototype.validateStringPointer = function(offset, nullable) {
     return this.validateArrayPointer(
-        offset, codec.Uint8.encodedSize, codec.Uint8, nullable, [0], 0);
+        offset, internal.Uint8.encodedSize, internal.Uint8, nullable, [0], 0);
   };
 
   // Similar to Array_Data<T>::Validate()
@@ -369,10 +370,10 @@
   Validator.prototype.validateArray =
       function (offset, elementSize, elementType, expectedDimensionSizes,
                 currentDimension) {
-    if (!codec.isAligned(offset))
+    if (!internal.isAligned(offset))
       return validationError.MISALIGNED_OBJECT;
 
-    if (!this.isValidRange(offset, codec.kArrayHeaderSize))
+    if (!this.isValidRange(offset, internal.kArrayHeaderSize))
       return validationError.ILLEGAL_MEMORY_RANGE;
 
     var numBytes = this.message.buffer.getUint32(offset);
@@ -380,10 +381,10 @@
 
     // Note: this computation is "safe" because elementSize <= 8 and
     // numElements is a uint32.
-    var elementsTotalSize = (elementType === codec.PackedBool) ?
+    var elementsTotalSize = (elementType === internal.PackedBool) ?
         Math.ceil(numElements / 8) : (elementSize * numElements);
 
-    if (numBytes < codec.kArrayHeaderSize + elementsTotalSize)
+    if (numBytes < internal.kArrayHeaderSize + elementsTotalSize)
       return validationError.UNEXPECTED_ARRAY_HEADER;
 
     if (expectedDimensionSizes[currentDimension] != 0 &&
@@ -396,7 +397,7 @@
 
     // Validate the array's elements if they are pointers or handles.
 
-    var elementsOffset = offset + codec.kArrayHeaderSize;
+    var elementsOffset = offset + internal.kArrayHeaderSize;
     var nullable = isNullable(elementType);
 
     if (isHandleClass(elementType))
@@ -409,11 +410,11 @@
           elementsOffset, numElements, nullable);
     if (isStringClass(elementType))
       return this.validateArrayElements(
-          elementsOffset, numElements, codec.Uint8, nullable, [0], 0);
-    if (elementType instanceof codec.PointerTo)
+          elementsOffset, numElements, internal.Uint8, nullable, [0], 0);
+    if (elementType instanceof internal.PointerTo)
       return this.validateStructElements(
           elementsOffset, numElements, elementType.cls, nullable);
-    if (elementType instanceof codec.ArrayOf)
+    if (elementType instanceof internal.ArrayOf)
       return this.validateArrayElements(
           elementsOffset, numElements, elementType.cls, nullable,
           expectedDimensionSizes, currentDimension + 1);
@@ -430,7 +431,7 @@
 
   Validator.prototype.validateHandleElements =
       function(offset, numElements, nullable) {
-    var elementSize = codec.Handle.encodedSize;
+    var elementSize = internal.Handle.encodedSize;
     for (var i = 0; i < numElements; i++) {
       var elementOffset = offset + i * elementSize;
       var err = this.validateHandle(elementOffset, nullable);
@@ -442,7 +443,7 @@
 
   Validator.prototype.validateInterfaceElements =
       function(offset, numElements, nullable) {
-    var elementSize = codec.Interface.prototype.encodedSize;
+    var elementSize = internal.Interface.prototype.encodedSize;
     for (var i = 0; i < numElements; i++) {
       var elementOffset = offset + i * elementSize;
       var err = this.validateInterface(elementOffset, nullable);
@@ -454,7 +455,7 @@
 
   Validator.prototype.validateInterfaceRequestElements =
       function(offset, numElements, nullable) {
-    var elementSize = codec.InterfaceRequest.encodedSize;
+    var elementSize = internal.InterfaceRequest.encodedSize;
     for (var i = 0; i < numElements; i++) {
       var elementOffset = offset + i * elementSize;
       var err = this.validateInterfaceRequest(elementOffset, nullable);
@@ -468,7 +469,7 @@
   Validator.prototype.validateArrayElements =
       function(offset, numElements, elementClass, nullable,
                expectedDimensionSizes, currentDimension) {
-    var elementSize = codec.PointerTo.prototype.encodedSize;
+    var elementSize = internal.PointerTo.prototype.encodedSize;
     for (var i = 0; i < numElements; i++) {
       var elementOffset = offset + i * elementSize;
       var err = this.validateArrayPointer(
@@ -482,7 +483,7 @@
 
   Validator.prototype.validateStructElements =
       function(offset, numElements, structClass, nullable) {
-    var elementSize = codec.PointerTo.prototype.encodedSize;
+    var elementSize = internal.PointerTo.prototype.encodedSize;
     for (var i = 0; i < numElements; i++) {
       var elementOffset = offset + i * elementSize;
       var err =
@@ -495,7 +496,7 @@
 
   Validator.prototype.validateEnumElements =
       function(offset, numElements, enumClass) {
-    var elementSize = codec.Enum.prototype.encodedSize;
+    var elementSize = internal.Enum.prototype.encodedSize;
     for (var i = 0; i < numElements; i++) {
       var elementOffset = offset + i * elementSize;
       var err = this.validateEnum(elementOffset, enumClass);
@@ -505,8 +506,6 @@
     return validationError.NONE;
   };
 
-  var exports = {};
-  exports.validationError = validationError;
-  exports.Validator = Validator;
-  return exports;
-});
+  internal.validationError = validationError;
+  internal.Validator = Validator;
+})();
diff --git a/mojo/public/js/router.js b/mojo/public/js/router.js
index e94c5eb..89d9a2f 100644
--- a/mojo/public/js/router.js
+++ b/mojo/public/js/router.js
@@ -3,198 +3,264 @@
 // found in the LICENSE file.
 
 define("mojo/public/js/router", [
-  "console",
-  "mojo/public/js/codec",
-  "mojo/public/js/core",
   "mojo/public/js/connector",
-  "mojo/public/js/lib/control_message_handler",
+  "mojo/public/js/core",
+  "mojo/public/js/interface_types",
+  "mojo/public/js/lib/interface_endpoint_handle",
+  "mojo/public/js/lib/pipe_control_message_handler",
+  "mojo/public/js/lib/pipe_control_message_proxy",
   "mojo/public/js/validator",
-], function(console, codec, core, connector, controlMessageHandler, validator) {
+  "timer",
+], function(connector, core, types, interfaceEndpointHandle,
+      controlMessageHandler, controlMessageProxy, validator, timer) {
 
   var Connector = connector.Connector;
-  var MessageReader = codec.MessageReader;
+  var PipeControlMessageHandler =
+      controlMessageHandler.PipeControlMessageHandler;
+  var PipeControlMessageProxy = controlMessageProxy.PipeControlMessageProxy;
   var Validator = validator.Validator;
-  var ControlMessageHandler = controlMessageHandler.ControlMessageHandler;
+  var InterfaceEndpointHandle = interfaceEndpointHandle.InterfaceEndpointHandle;
 
-  function Router(handle, interface_version, connectorFactory) {
-    if (!core.isHandle(handle))
-      throw new Error("Router constructor: Not a handle");
-    if (connectorFactory === undefined)
-      connectorFactory = Connector;
-    this.connector_ = new connectorFactory(handle);
-    this.incomingReceiver_ = null;
-    this.errorHandler_ = null;
-    this.nextRequestID_ = 0;
-    this.completers_ = new Map();
-    this.payloadValidators_ = [];
-    this.testingController_ = null;
+  /**
+   * The state of |endpoint|. If both the endpoint and its peer have been
+   * closed, removes it from |endpoints_|.
+   * @enum {string}
+   */
+  var EndpointStateUpdateType = {
+    ENDPOINT_CLOSED: 'endpoint_closed',
+    PEER_ENDPOINT_CLOSED: 'peer_endpoint_closed'
+  };
 
-    if (interface_version !== undefined) {
-      this.controlMessageHandler_ = new
-          ControlMessageHandler(interface_version);
+  function check(condition, output) {
+    if (!condition) {
+      // testharness.js does not rethrow errors so the error stack needs to be
+      // included as a string in the error we throw for debugging layout tests.
+      throw new Error((new Error()).stack);
     }
-
-    this.connector_.setIncomingReceiver({
-        accept: this.handleIncomingMessage_.bind(this),
-    });
-    this.connector_.setErrorHandler({
-        onError: this.handleConnectionError_.bind(this),
-    });
   }
 
-  Router.prototype.close = function() {
-    this.completers_.clear();  // Drop any responders.
-    this.connector_.close();
-    this.testingController_ = null;
+  function InterfaceEndpoint(router, interfaceId) {
+    this.router_ = router;
+    this.id = interfaceId;
+    this.closed = false;
+    this.peerClosed = false;
+    this.handleCreated = false;
+    this.disconnectReason = null;
+    this.client = null;
+  }
+
+  InterfaceEndpoint.prototype.sendMessage = function(message) {
+    message.setInterfaceId(this.id);
+    return this.router_.connector_.accept(message);
+  };
+
+  function Router(handle, setInterfaceIdNamespaceBit) {
+    if (!core.isHandle(handle)) {
+      throw new Error("Router constructor: Not a handle");
+    }
+    if (setInterfaceIdNamespaceBit === undefined) {
+      setInterfaceIdNamespaceBit = false;
+    }
+
+    this.connector_ = new Connector(handle);
+
+    this.connector_.setIncomingReceiver({
+        accept: this.accept.bind(this),
+    });
+    this.connector_.setErrorHandler({
+        onError: this.onPipeConnectionError.bind(this),
+    });
+
+    this.setInterfaceIdNamespaceBit_ = setInterfaceIdNamespaceBit;
+    this.controlMessageHandler_ = new PipeControlMessageHandler(this);
+    this.controlMessageProxy_ = new PipeControlMessageProxy(this.connector_);
+    this.nextInterfaceIdValue = 1;
+    this.encounteredError_ = false;
+    this.endpoints_ = new Map();
+  }
+
+  Router.prototype.attachEndpointClient = function(
+      interfaceEndpointHandle, interfaceEndpointClient) {
+    check(types.isValidInterfaceId(interfaceEndpointHandle.id()));
+    check(interfaceEndpointClient);
+
+    var endpoint = this.endpoints_.get(interfaceEndpointHandle.id());
+    check(endpoint);
+    check(!endpoint.client);
+    check(!endpoint.closed);
+    endpoint.client = interfaceEndpointClient;
+
+    if (endpoint.peerClosed) {
+      timer.createOneShot(0,
+          endpoint.client.notifyError.bind(endpoint.client));
+    }
+
+    return endpoint;
+  };
+
+  Router.prototype.detachEndpointClient = function(
+      interfaceEndpointHandle) {
+    check(types.isValidInterfaceId(interfaceEndpointHandle.id()));
+    var endpoint = this.endpoints_.get(interfaceEndpointHandle.id());
+    check(endpoint);
+    check(endpoint.client);
+    check(!endpoint.closed);
+
+    endpoint.client = null;
+  };
+
+  Router.prototype.createLocalEndpointHandle = function(
+      interfaceId) {
+    if (!types.isValidInterfaceId(interfaceId)) {
+      return new InterfaceEndpointHandle();
+    }
+
+    var endpoint = this.endpoints_.get(interfaceId);
+
+    if (!endpoint) {
+      endpoint = new InterfaceEndpoint(this, interfaceId);
+      this.endpoints_.set(interfaceId, endpoint);
+
+      check(!endpoint.handleCreated);
+
+      if (this.encounteredError_) {
+        this.updateEndpointStateMayRemove(endpoint,
+            EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
+      }
+    } else {
+      // If the endpoint already exist, it is because we have received a
+      // notification that the peer endpoint has closed.
+      check(!endpoint.closed);
+      check(endpoint.peerClosed);
+
+      if (endpoint.handleCreated) {
+        return new InterfaceEndpointHandle();
+      }
+    }
+
+    endpoint.handleCreated = true;
+    return new InterfaceEndpointHandle(interfaceId, this);
   };
 
   Router.prototype.accept = function(message) {
-    this.connector_.accept(message);
-  };
-
-  Router.prototype.reject = function(message) {
-    // TODO(mpcomplete): no way to trasmit errors over a Connection.
-  };
-
-  Router.prototype.acceptAndExpectResponse = function(message) {
-    // Reserve 0 in case we want it to convey special meaning in the future.
-    var requestID = this.nextRequestID_++;
-    if (requestID == 0)
-      requestID = this.nextRequestID_++;
-
-    message.setRequestID(requestID);
-    var result = this.connector_.accept(message);
-    if (!result)
-      return Promise.reject(Error("Connection error"));
-
-    var completer = {};
-    this.completers_.set(requestID, completer);
-    return new Promise(function(resolve, reject) {
-      completer.resolve = resolve;
-      completer.reject = reject;
-    });
-  };
-
-  Router.prototype.setIncomingReceiver = function(receiver) {
-    this.incomingReceiver_ = receiver;
-  };
-
-  Router.prototype.setPayloadValidators = function(payloadValidators) {
-    this.payloadValidators_ = payloadValidators;
-  };
-
-  Router.prototype.setErrorHandler = function(handler) {
-    this.errorHandler_ = handler;
-  };
-
-  Router.prototype.encounteredError = function() {
-    return this.connector_.encounteredError();
-  };
-
-  Router.prototype.enableTestingMode = function() {
-    this.testingController_ = new RouterTestingController(this.connector_);
-    return this.testingController_;
-  };
-
-  Router.prototype.handleIncomingMessage_ = function(message) {
-    var noError = validator.validationError.NONE;
     var messageValidator = new Validator(message);
     var err = messageValidator.validateMessageHeader();
-    for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i)
-      err = this.payloadValidators_[i](messageValidator);
 
-    if (err == noError)
-      this.handleValidIncomingMessage_(message);
-    else
-      this.handleInvalidIncomingMessage_(message, err);
-  };
-
-  Router.prototype.handleValidIncomingMessage_ = function(message) {
-    if (this.testingController_)
-      return;
-
-    if (message.expectsResponse()) {
-      if (controlMessageHandler.isControlMessage(message)) {
-        if (this.controlMessageHandler_) {
-          this.controlMessageHandler_.acceptWithResponder(message, this);
-        } else {
-          this.close();
-        }
-      } else if (this.incomingReceiver_) {
-        this.incomingReceiver_.acceptWithResponder(message, this);
-      } else {
-        // If we receive a request expecting a response when the client is not
-        // listening, then we have no choice but to tear down the pipe.
-        this.close();
-      }
-    } else if (message.isResponse()) {
-      var reader = new MessageReader(message);
-      var requestID = reader.requestID;
-      var completer = this.completers_.get(requestID);
-      if (completer) {
-        this.completers_.delete(requestID);
-        completer.resolve(message);
-      } else {
-        console.log("Unexpected response with request ID: " + requestID);
-      }
+    var ok = false;
+    if (err !== validator.validationError.NONE) {
+      validator.reportValidationError(err);
+    } else if (controlMessageHandler.isPipeControlMessage(message)) {
+      ok = this.controlMessageHandler_.accept(message);
     } else {
-      if (controlMessageHandler.isControlMessage(message)) {
-        if (this.controlMessageHandler_) {
-          var ok = this.controlMessageHandler_.accept(message);
-          if (ok) return;
-        }
-        this.close();
-      } else if (this.incomingReceiver_) {
-        this.incomingReceiver_.accept(message);
+      var interfaceId = message.getInterfaceId();
+      var endpoint = this.endpoints_.get(interfaceId);
+      if (!endpoint || endpoint.closed) {
+        return true;
       }
+
+      if (!endpoint.client) {
+        // We need to wait until a client is attached in order to dispatch
+        // further messages.
+        return false;
+      }
+      ok = endpoint.client.handleIncomingMessage_(message);
     }
+
+    if (!ok) {
+      this.handleInvalidIncomingMessage_();
+    }
+    return ok;
   };
 
-  Router.prototype.handleInvalidIncomingMessage_ = function(message, error) {
-    if (!this.testingController_) {
+  Router.prototype.close = function() {
+    this.connector_.close();
+    // Closing the message pipe won't trigger connection error handler.
+    // Explicitly call onPipeConnectionError() so that associated endpoints
+    // will get notified.
+    this.onPipeConnectionError();
+  };
+
+  Router.prototype.waitForNextMessageForTesting = function() {
+    this.connector_.waitForNextMessageForTesting();
+  };
+
+  Router.prototype.handleInvalidIncomingMessage_ = function(message) {
+    if (!validator.isTestingMode()) {
       // TODO(yzshen): Consider notifying the embedder.
       // TODO(yzshen): This should also trigger connection error handler.
       // Consider making accept() return a boolean and let the connector deal
       // with this, as the C++ code does.
-      console.log("Invalid message: " + validator.validationError[error]);
-
       this.close();
       return;
     }
-
-    this.testingController_.onInvalidIncomingMessage(error);
   };
 
-  Router.prototype.handleConnectionError_ = function(result) {
-    this.completers_.forEach(function(value) {
-      value.reject(result);
-    });
-    if (this.errorHandler_)
-      this.errorHandler_();
-    this.close();
+  Router.prototype.onPeerAssociatedEndpointClosed = function(interfaceId,
+      reason) {
+    check(!types.isMasterInterfaceId(interfaceId) || reason);
+
+    var endpoint = this.endpoints_.get(interfaceId);
+    if (!endpoint) {
+      endpoint = new InterfaceEndpoint(this, interfaceId);
+      this.endpoints_.set(interfaceId, endpoint);
+    }
+
+    if (reason) {
+      endpoint.disconnectReason = reason;
+    }
+
+    if (!endpoint.peerClosed) {
+      if (endpoint.client) {
+        timer.createOneShot(0,
+            endpoint.client.notifyError.bind(endpoint.client, reason));
+      }
+      this.updateEndpointStateMayRemove(endpoint,
+          EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
+    }
+    return true;
   };
 
-  // The RouterTestingController is used in unit tests. It defeats valid message
-  // handling and delgates invalid message handling.
+  Router.prototype.onPipeConnectionError = function() {
+    this.encounteredError_ = true;
 
-  function RouterTestingController(connector) {
-    this.connector_ = connector;
-    this.invalidMessageHandler_ = null;
-  }
-
-  RouterTestingController.prototype.waitForNextMessage = function() {
-    this.connector_.waitForNextMessageForTesting();
+    for (var endpoint of this.endpoints_.values()) {
+      if (endpoint.client) {
+        timer.createOneShot(0,
+            endpoint.client.notifyError.bind(endpoint.client,
+              endpoint.disconnectReason));
+      }
+      this.updateEndpointStateMayRemove(endpoint,
+          EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
+    }
   };
 
-  RouterTestingController.prototype.setInvalidIncomingMessageHandler =
-      function(callback) {
-    this.invalidMessageHandler_ = callback;
+  Router.prototype.closeEndpointHandle = function(interfaceId, reason) {
+    if (!types.isValidInterfaceId(interfaceId)) {
+      return;
+    }
+    var endpoint = this.endpoints_.get(interfaceId);
+    check(endpoint);
+    check(!endpoint.client);
+    check(!endpoint.closed);
+
+    this.updateEndpointStateMayRemove(endpoint,
+        EndpointStateUpdateType.ENDPOINT_CLOSED);
+
+    if (!types.isMasterInterfaceId(interfaceId) || reason) {
+      this.controlMessageProxy_.notifyPeerEndpointClosed(interfaceId, reason);
+    }
   };
 
-  RouterTestingController.prototype.onInvalidIncomingMessage =
-      function(error) {
-    if (this.invalidMessageHandler_)
-      this.invalidMessageHandler_(error);
+  Router.prototype.updateEndpointStateMayRemove = function(endpoint,
+      endpointStateUpdateType) {
+    if (endpointStateUpdateType === EndpointStateUpdateType.ENDPOINT_CLOSED) {
+      endpoint.closed = true;
+    } else {
+      endpoint.peerClosed = true;
+    }
+    if (endpoint.closed && endpoint.peerClosed) {
+      this.endpoints_.delete(endpoint.id);
+    }
   };
 
   var exports = {};
diff --git a/mojo/public/js/tests/codec_unittest.js b/mojo/public/js/tests/codec_unittest.js
deleted file mode 100644
index 5fa4076..0000000
--- a/mojo/public/js/tests/codec_unittest.js
+++ /dev/null
@@ -1,314 +0,0 @@
-// Copyright 2014 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.
-
-define([
-    "gin/test/expect",
-    "mojo/public/js/codec",
-    "mojo/public/interfaces/bindings/tests/rect.mojom",
-    "mojo/public/interfaces/bindings/tests/sample_service.mojom",
-    "mojo/public/interfaces/bindings/tests/test_structs.mojom",
-  ], function(expect, codec, rect, sample, structs) {
-  testBar();
-  testFoo();
-  testNamedRegion();
-  testSingleBooleanStruct();
-  testTypes();
-  testAlign();
-  testUtf8();
-  testTypedPointerValidation();
-  this.result = "PASS";
-
-  function testBar() {
-    var bar = new sample.Bar();
-    bar.alpha = 1;
-    bar.beta = 2;
-    bar.gamma = 3;
-    bar.type = 0x08070605;
-    bar.extraProperty = "banana";
-
-    var messageName = 42;
-    var payloadSize = sample.Bar.encodedSize;
-
-    var builder = new codec.MessageBuilder(messageName, payloadSize);
-    builder.encodeStruct(sample.Bar, bar);
-
-    var message = builder.finish();
-
-    var expectedMemory = new Uint8Array([
-      24, 0, 0, 0,
-       0, 0, 0, 0,
-       0, 0, 0, 0,
-      42, 0, 0, 0,
-       0, 0, 0, 0,
-       0, 0, 0, 0,
-
-      16, 0, 0, 0,
-       0, 0, 0, 0,
-
-       1, 2, 3, 0,
-       5, 6, 7, 8,
-    ]);
-
-    var actualMemory = new Uint8Array(message.buffer.arrayBuffer);
-    expect(actualMemory).toEqual(expectedMemory);
-
-    var reader = new codec.MessageReader(message);
-
-    expect(reader.payloadSize).toBe(payloadSize);
-    expect(reader.messageName).toBe(messageName);
-
-    var bar2 = reader.decodeStruct(sample.Bar);
-
-    expect(bar2.alpha).toBe(bar.alpha);
-    expect(bar2.beta).toBe(bar.beta);
-    expect(bar2.gamma).toBe(bar.gamma);
-    expect("extraProperty" in bar2).toBeFalsy();
-  }
-
-  function testFoo() {
-    var foo = new sample.Foo();
-    foo.x = 0x212B4D5;
-    foo.y = 0x16E93;
-    foo.a = 1;
-    foo.b = 0;
-    foo.c = 3; // This will get truncated to one bit.
-    foo.bar = new sample.Bar();
-    foo.bar.alpha = 91;
-    foo.bar.beta = 82;
-    foo.bar.gamma = 73;
-    foo.data = [
-      4, 5, 6, 7, 8,
-    ];
-    foo.extra_bars = [
-      new sample.Bar(), new sample.Bar(), new sample.Bar(),
-    ];
-    for (var i = 0; i < foo.extra_bars.length; ++i) {
-      foo.extra_bars[i].alpha = 1 * i;
-      foo.extra_bars[i].beta = 2 * i;
-      foo.extra_bars[i].gamma = 3 * i;
-    }
-    foo.name = "I am a banana";
-    // This is supposed to be a handle, but we fake it with an integer.
-    foo.source = 23423782;
-    foo.array_of_array_of_bools = [
-      [true], [false, true]
-    ];
-    foo.array_of_bools = [
-      true, false, true, false, true, false, true, true
-    ];
-
-
-    var messageName = 31;
-    var payloadSize = 304;
-
-    var builder = new codec.MessageBuilder(messageName, payloadSize);
-    builder.encodeStruct(sample.Foo, foo);
-
-    var message = builder.finish();
-
-    var expectedMemory = new Uint8Array([
-      /*  0: */   24,    0,    0,    0,    0,    0,    0,    0,
-      /*  8: */    0,    0,    0,    0,   31,    0,    0,    0,
-      /* 16: */    0,    0,    0,    0,    0,    0,    0,    0,
-      /* 24: */   96,    0,    0,    0,    0,    0,    0,    0,
-      /* 32: */ 0xD5, 0xB4, 0x12, 0x02, 0x93, 0x6E, 0x01,    0,
-      /* 40: */    5,    0,    0,    0,    0,    0,    0,    0,
-      /* 48: */   72,    0,    0,    0,    0,    0,    0,    0,
-    ]);
-    // TODO(abarth): Test more of the message's raw memory.
-    var actualMemory = new Uint8Array(message.buffer.arrayBuffer,
-                                      0, expectedMemory.length);
-    expect(actualMemory).toEqual(expectedMemory);
-
-    var expectedHandles = [
-      23423782,
-    ];
-
-    expect(message.handles).toEqual(expectedHandles);
-
-    var reader = new codec.MessageReader(message);
-
-    expect(reader.payloadSize).toBe(payloadSize);
-    expect(reader.messageName).toBe(messageName);
-
-    var foo2 = reader.decodeStruct(sample.Foo);
-
-    expect(foo2.x).toBe(foo.x);
-    expect(foo2.y).toBe(foo.y);
-
-    expect(foo2.a).toBe(foo.a & 1 ? true : false);
-    expect(foo2.b).toBe(foo.b & 1 ? true : false);
-    expect(foo2.c).toBe(foo.c & 1 ? true : false);
-
-    expect(foo2.bar).toEqual(foo.bar);
-    expect(foo2.data).toEqual(foo.data);
-
-    expect(foo2.extra_bars).toEqual(foo.extra_bars);
-    expect(foo2.name).toBe(foo.name);
-    expect(foo2.source).toEqual(foo.source);
-
-    expect(foo2.array_of_bools).toEqual(foo.array_of_bools);
-  }
-
-  function createRect(x, y, width, height) {
-    var r = new rect.Rect();
-    r.x = x;
-    r.y = y;
-    r.width = width;
-    r.height = height;
-    return r;
-  }
-
-  // Verify that the references to the imported Rect type in test_structs.mojom
-  // are generated correctly.
-  function testNamedRegion() {
-    var r = new structs.NamedRegion();
-    r.name = "rectangle";
-    r.rects = new Array(createRect(1, 2, 3, 4), createRect(10, 20, 30, 40));
-
-    var builder = new codec.MessageBuilder(1, structs.NamedRegion.encodedSize);
-    builder.encodeStruct(structs.NamedRegion, r);
-    var reader = new codec.MessageReader(builder.finish());
-    var result = reader.decodeStruct(structs.NamedRegion);
-
-    expect(result.name).toEqual("rectangle");
-    expect(result.rects[0]).toEqual(createRect(1, 2, 3, 4));
-    expect(result.rects[1]).toEqual(createRect(10, 20, 30, 40));
-  }
-
-  // Verify that a single boolean field in a struct is correctly decoded to
-  // boolean type.
-  function testSingleBooleanStruct() {
-    var single_bool = new structs.SingleBoolStruct();
-    single_bool.value = true;
-
-    var builder = new codec.MessageBuilder(
-        1, structs.SingleBoolStruct.encodedSize);
-    builder.encodeStruct(structs.SingleBoolStruct, single_bool);
-    var reader = new codec.MessageReader(builder.finish());
-    var result = reader.decodeStruct(structs.SingleBoolStruct);
-
-    // Use toEqual() instead of toBeTruthy() to make sure the field type is
-    // actually boolean.
-    expect(result.value).toEqual(true);
-  }
-
-  function testTypes() {
-    function encodeDecode(cls, input, expectedResult, encodedSize) {
-      var messageName = 42;
-      var payloadSize = encodedSize || cls.encodedSize;
-
-      var builder = new codec.MessageBuilder(messageName, payloadSize);
-      builder.encodeStruct(cls, input)
-      var message = builder.finish();
-
-      var reader = new codec.MessageReader(message);
-      expect(reader.payloadSize).toBe(payloadSize);
-      expect(reader.messageName).toBe(messageName);
-      var result = reader.decodeStruct(cls);
-      expect(result).toEqual(expectedResult);
-    }
-    encodeDecode(codec.String, "banana", "banana", 24);
-    encodeDecode(codec.NullableString, null, null, 8);
-    encodeDecode(codec.Int8, -1, -1);
-    encodeDecode(codec.Int8, 0xff, -1);
-    encodeDecode(codec.Int16, -1, -1);
-    encodeDecode(codec.Int16, 0xff, 0xff);
-    encodeDecode(codec.Int16, 0xffff, -1);
-    encodeDecode(codec.Int32, -1, -1);
-    encodeDecode(codec.Int32, 0xffff, 0xffff);
-    encodeDecode(codec.Int32, 0xffffffff, -1);
-    encodeDecode(codec.Float, 1.0, 1.0);
-    encodeDecode(codec.Double, 1.0, 1.0);
-  }
-
-  function testAlign() {
-    var aligned = [
-      0, // 0
-      8, // 1
-      8, // 2
-      8, // 3
-      8, // 4
-      8, // 5
-      8, // 6
-      8, // 7
-      8, // 8
-      16, // 9
-      16, // 10
-      16, // 11
-      16, // 12
-      16, // 13
-      16, // 14
-      16, // 15
-      16, // 16
-      24, // 17
-      24, // 18
-      24, // 19
-      24, // 20
-    ];
-    for (var i = 0; i < aligned.length; ++i)
-      expect(codec.align(i)).toBe(aligned[i]);
-  }
-
-  function testUtf8() {
-    var str = "B\u03ba\u1f79";  // some UCS-2 codepoints
-    var messageName = 42;
-    var payloadSize = 24;
-
-    var builder = new codec.MessageBuilder(messageName, payloadSize);
-    var encoder = builder.createEncoder(8);
-    encoder.encodeStringPointer(str);
-    var message = builder.finish();
-    var expectedMemory = new Uint8Array([
-      /*  0: */   24,    0,    0,    0,    0,    0,    0,    0,
-      /*  8: */    0,    0,    0,    0,   42,    0,    0,    0,
-      /* 16: */    0,    0,    0,    0,    0,    0,    0,    0,
-      /* 24: */    8,    0,    0,    0,    0,    0,    0,    0,
-      /* 32: */   14,    0,    0,    0,    6,    0,    0,    0,
-      /* 40: */ 0x42, 0xCE, 0xBA, 0xE1, 0xBD, 0xB9,    0,    0,
-    ]);
-    var actualMemory = new Uint8Array(message.buffer.arrayBuffer);
-    expect(actualMemory.length).toEqual(expectedMemory.length);
-    expect(actualMemory).toEqual(expectedMemory);
-
-    var reader = new codec.MessageReader(message);
-    expect(reader.payloadSize).toBe(payloadSize);
-    expect(reader.messageName).toBe(messageName);
-    var str2 = reader.decoder.decodeStringPointer();
-    expect(str2).toEqual(str);
-  }
-
-  function testTypedPointerValidation() {
-    var encoder = new codec.MessageBuilder(42, 24).createEncoder(8);
-    function DummyClass() {};
-    var testCases = [
-      // method, args, invalid examples, valid examples
-      [encoder.encodeArrayPointer, [DummyClass], [75],
-          [[], null, undefined, new Uint8Array([])]],
-      [encoder.encodeStringPointer, [], [75, new String("foo")],
-          ["", "bar", null, undefined]],
-      [encoder.encodeMapPointer, [DummyClass, DummyClass], [75],
-          [new Map(), null, undefined]],
-    ];
-
-    testCases.forEach(function(test) {
-      var method = test[0];
-      var baseArgs = test[1];
-      var invalidExamples = test[2];
-      var validExamples = test[3];
-
-      var encoder = new codec.MessageBuilder(42, 24).createEncoder(8);
-      invalidExamples.forEach(function(invalid) {
-        expect(function() {
-          method.apply(encoder, baseArgs.concat(invalid));
-        }).toThrow();
-      });
-
-      validExamples.forEach(function(valid) {
-        var encoder = new codec.MessageBuilder(42, 24).createEncoder(8);
-        method.apply(encoder, baseArgs.concat(valid));
-      });
-    });
-  }
-});
diff --git a/mojo/public/js/tests/connection_unittest.js b/mojo/public/js/tests/connection_unittest.js
deleted file mode 100644
index feba87d..0000000
--- a/mojo/public/js/tests/connection_unittest.js
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright 2013 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.
-
-define([
-    "gin/test/expect",
-    "mojo/public/js/bindings",
-    "mojo/public/js/core",
-    "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom",
-    "mojo/public/interfaces/bindings/tests/sample_service.mojom",
-    "mojo/public/js/threading",
-    "gc",
-], function(expect,
-            bindings,
-            core,
-            sample_interfaces,
-            sample_service,
-            threading,
-            gc) {
-  testClientServer()
-      .then(testWriteToClosedPipe)
-      .then(testRequestResponse)
-      .then(function() {
-    this.result = "PASS";
-    gc.collectGarbage();  // should not crash
-    threading.quit();
-  }.bind(this)).catch(function(e) {
-    this.result = "FAIL: " + (e.stack || e);
-    threading.quit();
-  }.bind(this));
-
-  function testClientServer() {
-    // ServiceImpl ------------------------------------------------------------
-
-    function ServiceImpl() {
-    }
-
-    ServiceImpl.prototype.frobinate = function(foo, baz, port) {
-      expect(foo.name).toBe("Example name");
-      expect(baz).toBe(sample_service.Service.BazOptions.REGULAR);
-      expect(port.ptr.isBound()).toBeTruthy();
-      port.ptr.reset();
-
-      return Promise.resolve({result: 42});
-    };
-
-    var service = new sample_service.ServicePtr();
-    var serviceBinding = new bindings.Binding(sample_service.Service,
-                                              new ServiceImpl(),
-                                              bindings.makeRequest(service));
-    var sourcePipe = core.createMessagePipe();
-    var port = new sample_service.PortPtr();
-    var portRequest = bindings.makeRequest(port);
-
-    var foo = new sample_service.Foo();
-    foo.bar = new sample_service.Bar();
-    foo.name = "Example name";
-    foo.source = sourcePipe.handle0;
-    var promise = service.frobinate(
-        foo, sample_service.Service.BazOptions.REGULAR, port)
-            .then(function(response) {
-      expect(response.result).toBe(42);
-
-      service.ptr.reset();
-      serviceBinding.close();
-
-      return Promise.resolve();
-    });
-
-    // sourcePipe.handle1 hasn't been closed yet.
-    expect(core.close(sourcePipe.handle1)).toBe(core.RESULT_OK);
-
-    // portRequest.handle hasn't been closed yet.
-    expect(core.close(portRequest.handle)).toBe(core.RESULT_OK);
-
-    return promise;
-  }
-
-  function testWriteToClosedPipe() {
-    var service = new sample_service.ServicePtr();
-    // Discard the interface request.
-    bindings.makeRequest(service);
-    gc.collectGarbage();
-
-    var promise = service.frobinate(
-        null, sample_service.Service.BazOptions.REGULAR, null)
-            .then(function(response) {
-      return Promise.reject("Unexpected response");
-    }).catch(function(e) {
-      // We should observe the closed pipe.
-      return Promise.resolve();
-    });
-
-    return promise;
-  }
-
-  function testRequestResponse() {
-    // ProviderImpl ------------------------------------------------------------
-
-    function ProviderImpl() {
-    }
-
-    ProviderImpl.prototype.echoString = function(a) {
-      return Promise.resolve({a: a});
-    };
-
-    ProviderImpl.prototype.echoStrings = function(a, b) {
-      return Promise.resolve({a: a, b: b});
-    };
-
-    var provider = new sample_interfaces.ProviderPtr();
-    var providerBinding = new bindings.Binding(sample_interfaces.Provider,
-                                               new ProviderImpl(),
-                                               bindings.makeRequest(provider));
-    var promise = provider.echoString("hello").then(function(response) {
-      expect(response.a).toBe("hello");
-      return provider.echoStrings("hello", "world");
-    }).then(function(response) {
-      expect(response.a).toBe("hello");
-      expect(response.b).toBe("world");
-      // Mock a read failure, expect it to fail.
-      core.readMessage = function() {
-        return { result: core.RESULT_UNKNOWN };
-      };
-      return provider.echoString("goodbye");
-    }).then(function() {
-      throw Error("Expected echoString to fail.");
-    }, function(error) {
-      expect(error.message).toBe("Connection error: " + core.RESULT_UNKNOWN);
-
-      return Promise.resolve();
-    });
-
-    return promise;
-  }
-});
diff --git a/mojo/public/js/tests/core_unittest.js b/mojo/public/js/tests/core_unittest.js
index 395ed05..86a997f 100644
--- a/mojo/public/js/tests/core_unittest.js
+++ b/mojo/public/js/tests/core_unittest.js
@@ -89,35 +89,15 @@
   }
 
   function testReadAndWriteMessage(pipe) {
-    var wait = core.waitMany([], [], 0);
-    expect(wait.result).toBe(core.RESULT_INVALID_ARGUMENT);
-    expect(wait.index).toBe(null);
-    expect(wait.signalsState).toBe(null);
+    var state0 = core.queryHandleSignalsState(pipe.handle0);
+    expect(state0.result).toBe(core.RESULT_OK);
+    expect(state0.satisfiedSignals).toBe(core.HANDLE_SIGNAL_WRITABLE);
+    expect(state0.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
 
-    wait = core.wait(pipe.handle0, core.HANDLE_SIGNAL_READABLE, 0);
-    expect(wait.result).toBe(core.RESULT_DEADLINE_EXCEEDED);
-    expect(wait.signalsState.satisfiedSignals).toBe(
-           core.HANDLE_SIGNAL_WRITABLE);
-    expect(wait.signalsState.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
-
-    wait = core.waitMany(
-                  [pipe.handle0, pipe.handle1],
-                  [core.HANDLE_SIGNAL_READABLE,core.HANDLE_SIGNAL_READABLE],
-                  0);
-    expect(wait.result).toBe(core.RESULT_DEADLINE_EXCEEDED);
-    expect(wait.index).toBe(null);
-    expect(wait.signalsState[0].satisfiedSignals).toBe(
-           core.HANDLE_SIGNAL_WRITABLE);
-    expect(wait.signalsState[0].satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
-    expect(wait.signalsState[1].satisfiedSignals).toBe(
-           core.HANDLE_SIGNAL_WRITABLE);
-    expect(wait.signalsState[1].satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
-
-    wait = core.wait(pipe.handle0, core.HANDLE_SIGNAL_WRITABLE, 0);
-    expect(wait.result).toBe(core.RESULT_OK);
-    expect(wait.signalsState.satisfiedSignals).toBe(
-           core.HANDLE_SIGNAL_WRITABLE);
-    expect(wait.signalsState.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
+    var state1 = core.queryHandleSignalsState(pipe.handle1);
+    expect(state1.result).toBe(core.RESULT_OK);
+    expect(state1.satisfiedSignals).toBe(core.HANDLE_SIGNAL_WRITABLE);
+    expect(state1.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
 
     var senderData = new Uint8Array(42);
     for (var i = 0; i < senderData.length; ++i) {
@@ -130,14 +110,12 @@
 
     expect(result).toBe(core.RESULT_OK);
 
-    wait = core.wait(pipe.handle0, core.HANDLE_SIGNAL_WRITABLE, 0);
-    expect(wait.result).toBe(core.RESULT_OK);
-    expect(wait.signalsState.satisfiedSignals).toBe(
-        core.HANDLE_SIGNAL_WRITABLE);
-    expect(wait.signalsState.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
+    state0 = core.queryHandleSignalsState(pipe.handle0);
+    expect(state0.result).toBe(core.RESULT_OK);
+    expect(state0.satisfiedSignals).toBe(core.HANDLE_SIGNAL_WRITABLE);
+    expect(state0.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
 
-    wait = core.wait(pipe.handle1, core.HANDLE_SIGNAL_READABLE,
-                     core.DEADLINE_INDEFINITE);
+    var wait = core.wait(pipe.handle1, core.HANDLE_SIGNAL_READABLE);
     expect(wait.result).toBe(core.RESULT_OK);
     expect(wait.signalsState.satisfiedSignals).toBe(HANDLE_SIGNAL_READWRITABLE);
     expect(wait.signalsState.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
@@ -166,8 +144,7 @@
     expect(write.result).toBe(core.RESULT_OK);
     expect(write.numBytes).toBe(42);
 
-    var wait = core.wait(pipe.consumerHandle, core.HANDLE_SIGNAL_READABLE,
-                         core.DEADLINE_INDEFINITE);
+    var wait = core.wait(pipe.consumerHandle, core.HANDLE_SIGNAL_READABLE);
     expect(wait.result).toBe(core.RESULT_OK);
     var peeked = core.readData(
          pipe.consumerHandle,
diff --git a/mojo/public/js/tests/interface_ptr_unittest.js b/mojo/public/js/tests/interface_ptr_unittest.js
deleted file mode 100644
index 6203154..0000000
--- a/mojo/public/js/tests/interface_ptr_unittest.js
+++ /dev/null
@@ -1,240 +0,0 @@
-// 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.
-
-define([
-    "gin/test/expect",
-    "mojo/public/js/bindings",
-    "mojo/public/js/core",
-    "mojo/public/interfaces/bindings/tests/math_calculator.mojom",
-    "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom",
-    "mojo/public/js/threading",
-    "gc",
-], function(expect,
-            bindings,
-            core,
-            math,
-            sampleInterfaces,
-            threading,
-            gc) {
-  testIsBound()
-      .then(testEndToEnd)
-      .then(testReusable)
-      .then(testConnectionError)
-      .then(testPassInterface)
-      .then(testBindRawHandle)
-      .then(testQueryVersion)
-      .then(testRequireVersion)
-      .then(function() {
-    this.result = "PASS";
-    gc.collectGarbage();  // should not crash
-    threading.quit();
-  }.bind(this)).catch(function(e) {
-    this.result = "FAIL: " + (e.stack || e);
-    threading.quit();
-  }.bind(this));
-
-  function CalculatorImpl() {
-    this.total = 0;
-  }
-
-  CalculatorImpl.prototype.clear = function() {
-    this.total = 0;
-    return Promise.resolve({value: this.total});
-  };
-
-  CalculatorImpl.prototype.add = function(value) {
-    this.total += value;
-    return Promise.resolve({value: this.total});
-  };
-
-  CalculatorImpl.prototype.multiply = function(value) {
-    this.total *= value;
-    return Promise.resolve({value: this.total});
-  };
-
-  function IntegerAccessorImpl() {
-    this.integer = 0;
-  }
-
-  IntegerAccessorImpl.prototype.getInteger = function() {
-    return Promise.resolve({data: this.integer});
-  };
-
-  IntegerAccessorImpl.prototype.setInteger = function(value) {
-    this.integer = value;
-  };
-
-  function testIsBound() {
-    var calc = new math.CalculatorPtr();
-    expect(calc.ptr.isBound()).toBeFalsy();
-
-    var request = bindings.makeRequest(calc);
-    expect(calc.ptr.isBound()).toBeTruthy();
-
-    calc.ptr.reset();
-    expect(calc.ptr.isBound()).toBeFalsy();
-
-    return Promise.resolve();
-  }
-
-  function testEndToEnd() {
-    var calc = new math.CalculatorPtr();
-    var calcBinding = new bindings.Binding(math.Calculator,
-                                           new CalculatorImpl(),
-                                           bindings.makeRequest(calc));
-
-    var promise = calc.add(2).then(function(response) {
-      expect(response.value).toBe(2);
-      return calc.multiply(5);
-    }).then(function(response) {
-      expect(response.value).toBe(10);
-      return calc.clear();
-    }).then(function(response) {
-      expect(response.value).toBe(0);
-      return Promise.resolve();
-    });
-
-    return promise;
-  }
-
-  function testReusable() {
-    var calc = new math.CalculatorPtr();
-    var calcImpl1 = new CalculatorImpl();
-    var calcBinding1 = new bindings.Binding(math.Calculator,
-                                            calcImpl1,
-                                            bindings.makeRequest(calc));
-    var calcImpl2 = new CalculatorImpl();
-    var calcBinding2 = new bindings.Binding(math.Calculator, calcImpl2);
-
-    var promise = calc.add(2).then(function(response) {
-      expect(response.value).toBe(2);
-      calcBinding2.bind(bindings.makeRequest(calc));
-      return calc.add(2);
-    }).then(function(response) {
-      expect(response.value).toBe(2);
-      expect(calcImpl1.total).toBe(2);
-      expect(calcImpl2.total).toBe(2);
-      return Promise.resolve();
-    });
-
-    return promise;
-  }
-
-  function testConnectionError() {
-    var calc = new math.CalculatorPtr();
-    var calcBinding = new bindings.Binding(math.Calculator,
-                                           new CalculatorImpl(),
-                                           bindings.makeRequest(calc));
-
-    var promise = new Promise(function(resolve, reject) {
-      calc.ptr.setConnectionErrorHandler(function() {
-        resolve();
-      });
-      calcBinding.close();
-    });
-
-    return promise;
-  }
-
-  function testPassInterface() {
-    var calc = new math.CalculatorPtr();
-    var newCalc = null;
-    var calcBinding = new bindings.Binding(math.Calculator,
-                                           new CalculatorImpl(),
-                                           bindings.makeRequest(calc));
-
-    var promise = calc.add(2).then(function(response) {
-      expect(response.value).toBe(2);
-      newCalc = new math.CalculatorPtr();
-      newCalc.ptr.bind(calc.ptr.passInterface());
-      expect(calc.ptr.isBound()).toBeFalsy();
-      return newCalc.add(2);
-    }).then(function(response) {
-      expect(response.value).toBe(4);
-      return Promise.resolve();
-    });
-
-    return promise;
-  }
-
-  function testBindRawHandle() {
-    var pipe = core.createMessagePipe();
-    var calc = new math.CalculatorPtr(pipe.handle0);
-    var newCalc = null;
-    var calcBinding = new bindings.Binding(math.Calculator,
-                                           new CalculatorImpl(),
-                                           pipe.handle1);
-
-    var promise = calc.add(2).then(function(response) {
-      expect(response.value).toBe(2);
-      return Promise.resolve();
-    });
-
-    return promise;
-  }
-
-  function testQueryVersion() {
-    var integerAccessorPtr = new sampleInterfaces.IntegerAccessorPtr();
-
-    var integerAccessorBinding = new bindings.Binding(
-        sampleInterfaces.IntegerAccessor,
-        new IntegerAccessorImpl(),
-        bindings.makeRequest(integerAccessorPtr));
-    expect(integerAccessorPtr.ptr.version).toBe(0);
-
-    return integerAccessorPtr.ptr.queryVersion().then(function(version) {
-      expect(version).toBe(3);
-      expect(integerAccessorPtr.ptr.version).toBe(3);
-    });
-  }
-
-  function testRequireVersion() {
-    var integerAccessorImpl = new IntegerAccessorImpl();
-    var integerAccessorPtr = new sampleInterfaces.IntegerAccessorPtr();
-    var integerAccessorBinding = new bindings.Binding(
-        sampleInterfaces.IntegerAccessor,
-        integerAccessorImpl,
-        bindings.makeRequest(integerAccessorPtr));
-
-    // Inital version is 0.
-    expect(integerAccessorPtr.ptr.version).toBe(0);
-
-    function requireVersion1() {
-      integerAccessorPtr.ptr.requireVersion(1);
-      expect(integerAccessorPtr.ptr.version).toBe(1);
-      integerAccessorPtr.setInteger(123, sampleInterfaces.Enum.VALUE);
-      return integerAccessorPtr.getInteger().then(function(responseParams) {
-        expect(responseParams.data).toBe(123);
-      });
-    }
-
-    function requireVersion3() {
-      integerAccessorPtr.ptr.requireVersion(3);
-      expect(integerAccessorPtr.ptr.version).toBe(3);
-      integerAccessorPtr.setInteger(456, sampleInterfaces.Enum.VALUE);
-      return integerAccessorPtr.getInteger().then(function(responseParams) {
-        expect(responseParams.data).toBe(456);
-      });
-    }
-
-    // Require a version that is not supported by the impl side.
-    function requireVersion4() {
-      integerAccessorPtr.ptr.requireVersion(4);
-      expect(integerAccessorPtr.ptr.version).toBe(4);
-      integerAccessorPtr.setInteger(789, sampleInterfaces.Enum.VALUE);
-
-      var promise = new Promise(function(resolve, reject) {
-        integerAccessorPtr.ptr.setConnectionErrorHandler(function() {
-          // The call to setInteger() after requireVersion(4) is ignored.
-          expect(integerAccessorImpl.integer).toBe(456);
-          resolve();
-        });
-      });
-
-      return promise;
-    }
-
-    return requireVersion1().then(requireVersion3).then(requireVersion4);
-  }
-});
diff --git a/mojo/public/js/tests/sample_service_unittest.js b/mojo/public/js/tests/sample_service_unittest.js
deleted file mode 100644
index b9a2845..0000000
--- a/mojo/public/js/tests/sample_service_unittest.js
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright 2013 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.
-
-define([
-    "gin/test/expect",
-    "mojo/public/interfaces/bindings/tests/sample_service.mojom",
-    "mojo/public/interfaces/bindings/tests/sample_import.mojom",
-    "mojo/public/interfaces/bindings/tests/sample_import2.mojom",
-    "mojo/public/js/bindings",
-    "mojo/public/js/core",
-    "mojo/public/js/threading",
-  ], function(expect, sample, imported, imported2, bindings, core, threading) {
-  testDefaultValues()
-      .then(testSampleService)
-      .then(function() {
-    this.result = "PASS";
-    threading.quit();
-  }.bind(this)).catch(function(e) {
-    this.result = "FAIL: " + (e.stack || e);
-    threading.quit();
-  }.bind(this));
-
-  // Checks that values are set to the defaults if we don't override them.
-  function testDefaultValues() {
-    var bar = new sample.Bar();
-    expect(bar.alpha).toBe(255);
-    expect(bar.type).toBe(sample.Bar.Type.VERTICAL);
-
-    var foo = new sample.Foo();
-    expect(foo.name).toBe("Fooby");
-    expect(foo.a).toBeTruthy();
-    expect(foo.data).toBeNull();
-
-    var defaults = new sample.DefaultsTest();
-    expect(defaults.a0).toBe(-12);
-    expect(defaults.a1).toBe(sample.kTwelve);
-    expect(defaults.a2).toBe(1234);
-    expect(defaults.a3).toBe(34567);
-    expect(defaults.a4).toBe(123456);
-    expect(defaults.a5).toBe(3456789012);
-    expect(defaults.a6).toBe(-111111111111);
-    // JS doesn't have a 64 bit integer type so this is just checking that the
-    // expected and actual values have the same closest double value.
-    expect(defaults.a7).toBe(9999999999999999999);
-    expect(defaults.a8).toBe(0x12345);
-    expect(defaults.a9).toBe(-0x12345);
-    expect(defaults.a10).toBe(1234);
-    expect(defaults.a11).toBe(true);
-    expect(defaults.a12).toBe(false);
-    expect(defaults.a13).toBe(123.25);
-    expect(defaults.a14).toBe(1234567890.123);
-    expect(defaults.a15).toBe(1E10);
-    expect(defaults.a16).toBe(-1.2E+20);
-    expect(defaults.a17).toBe(1.23E-20);
-    expect(defaults.a20).toBe(sample.Bar.Type.BOTH);
-    expect(defaults.a21).toBeNull();
-    expect(defaults.a22).toBeTruthy();
-    expect(defaults.a22.shape).toBe(imported.Shape.RECTANGLE);
-    expect(defaults.a22.color).toBe(imported2.Color.BLACK);
-    expect(defaults.a21).toBeNull();
-    expect(defaults.a23).toBe(0xFFFFFFFFFFFFFFFF);
-    expect(defaults.a24).toBe(0x123456789);
-    expect(defaults.a25).toBe(-0x123456789);
-
-    return Promise.resolve();
-  }
-
-  function testSampleService() {
-    function ServiceImpl() {
-    }
-
-    ServiceImpl.prototype.frobinate = function(foo, baz, port) {
-      checkFoo(foo);
-      expect(baz).toBe(sample.Service.BazOptions.EXTRA);
-      expect(port.ptr.isBound()).toBeTruthy();
-      return Promise.resolve({result: 1234});
-    };
-
-    var foo = makeFoo();
-    checkFoo(foo);
-
-    var service = new sample.ServicePtr();
-    var request = bindings.makeRequest(service);
-    var serviceBinding = new bindings.Binding(
-        sample.Service, new ServiceImpl(), request);
-
-    var port = new sample.PortPtr();
-    bindings.makeRequest(port);
-    var promise = service.frobinate(
-        foo, sample.Service.BazOptions.EXTRA, port)
-            .then(function(response) {
-      expect(response.result).toBe(1234);
-
-      return Promise.resolve();
-    });
-
-    return promise;
-  }
-
-  function makeFoo() {
-    var bar = new sample.Bar();
-    bar.alpha = 20;
-    bar.beta = 40;
-    bar.gamma = 60;
-    bar.type = sample.Bar.Type.VERTICAL;
-
-    var extra_bars = new Array(3);
-    for (var i = 0; i < extra_bars.length; ++i) {
-      var base = i * 100;
-      var type = i % 2 ?
-          sample.Bar.Type.VERTICAL : sample.Bar.Type.HORIZONTAL;
-      extra_bars[i] = new sample.Bar();
-      extra_bars[i].alpha = base;
-      extra_bars[i].beta = base + 20;
-      extra_bars[i].gamma = base + 40;
-      extra_bars[i].type = type;
-    }
-
-    var data = new Array(10);
-    for (var i = 0; i < data.length; ++i) {
-      data[i] = data.length - i;
-    }
-
-    var foo = new sample.Foo();
-    foo.name = "foopy";
-    foo.x = 1;
-    foo.y = 2;
-    foo.a = false;
-    foo.b = true;
-    foo.c = false;
-    foo.bar = bar;
-    foo.extra_bars = extra_bars;
-    foo.data = data;
-
-    // TODO(yzshen): currently setting it to null will cause connection error,
-    // even if the field is defined as nullable. crbug.com/575753
-    foo.source = core.createMessagePipe().handle0;
-
-    return foo;
-  }
-
-  // Checks that the given |Foo| is identical to the one made by |makeFoo()|.
-  function checkFoo(foo) {
-    expect(foo.name).toBe("foopy");
-    expect(foo.x).toBe(1);
-    expect(foo.y).toBe(2);
-    expect(foo.a).toBeFalsy();
-    expect(foo.b).toBeTruthy();
-    expect(foo.c).toBeFalsy();
-    expect(foo.bar.alpha).toBe(20);
-    expect(foo.bar.beta).toBe(40);
-    expect(foo.bar.gamma).toBe(60);
-    expect(foo.bar.type).toBe(sample.Bar.Type.VERTICAL);
-
-    expect(foo.extra_bars.length).toBe(3);
-    for (var i = 0; i < foo.extra_bars.length; ++i) {
-      var base = i * 100;
-      var type = i % 2 ?
-          sample.Bar.Type.VERTICAL : sample.Bar.Type.HORIZONTAL;
-      expect(foo.extra_bars[i].alpha).toBe(base);
-      expect(foo.extra_bars[i].beta).toBe(base + 20);
-      expect(foo.extra_bars[i].gamma).toBe(base + 40);
-      expect(foo.extra_bars[i].type).toBe(type);
-    }
-
-    expect(foo.data.length).toBe(10);
-    for (var i = 0; i < foo.data.length; ++i)
-      expect(foo.data[i]).toBe(foo.data.length - i);
-
-    expect(core.isHandle(foo.source)).toBeTruthy();
-  }
-});
diff --git a/mojo/public/js/tests/struct_unittest.js b/mojo/public/js/tests/struct_unittest.js
deleted file mode 100644
index 691d51b..0000000
--- a/mojo/public/js/tests/struct_unittest.js
+++ /dev/null
@@ -1,279 +0,0 @@
-// Copyright 2014 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.
-
-define([
-    "gin/test/expect",
-    "mojo/public/interfaces/bindings/tests/rect.mojom",
-    "mojo/public/interfaces/bindings/tests/test_structs.mojom",
-    "mojo/public/js/codec",
-    "mojo/public/js/validator",
-], function(expect,
-            rect,
-            testStructs,
-            codec,
-            validator) {
-
-  function testConstructors() {
-    var r = new rect.Rect();
-    expect(r).toEqual(new rect.Rect({x:0, y:0, width:0, height:0}));
-    expect(r).toEqual(new rect.Rect({foo:100, bar:200}));
-
-    r.x = 10;
-    r.y = 20;
-    r.width = 30;
-    r.height = 40;
-    var rp = new testStructs.RectPair({first: r, second: r});
-    expect(rp.first).toEqual(r);
-    expect(rp.second).toEqual(r);
-
-    expect(new testStructs.RectPair({second: r}).first).toBeNull();
-
-    var nr = new testStructs.NamedRegion();
-    expect(nr.name).toBeNull();
-    expect(nr.rects).toBeNull();
-    expect(nr).toEqual(new testStructs.NamedRegion({}));
-
-    nr.name = "foo";
-    nr.rects = [r, r, r];
-    expect(nr).toEqual(new testStructs.NamedRegion({
-      name: "foo",
-      rects: [r, r, r],
-    }));
-
-    var e = new testStructs.EmptyStruct();
-    expect(e).toEqual(new testStructs.EmptyStruct({foo:123}));
-  }
-
-  function testNoDefaultFieldValues() {
-    var s = new testStructs.NoDefaultFieldValues();
-    expect(s.f0).toEqual(false);
-
-    // f1 - f10, number type fields
-    for (var i = 1; i <= 10; i++)
-      expect(s["f" + i]).toEqual(0);
-
-    // f11,12 strings, f13-22 handles, f23-f26 arrays, f27,28 structs
-    for (var i = 11; i <= 28; i++)
-      expect(s["f" + i]).toBeNull();
-  }
-
-  function testDefaultFieldValues() {
-    var s = new testStructs.DefaultFieldValues();
-    expect(s.f0).toEqual(true);
-
-    // f1 - f12, number type fields
-    for (var i = 1; i <= 12; i++)
-      expect(s["f" + i]).toEqual(100);
-
-    // f13,14 "foo"
-    for (var i = 13; i <= 14; i++)
-      expect(s["f" + i]).toEqual("foo");
-
-    // f15,16 a default instance of Rect
-    var r = new rect.Rect();
-    expect(s.f15).toEqual(r);
-    expect(s.f16).toEqual(r);
-  }
-
-  function testScopedConstants() {
-    expect(testStructs.ScopedConstants.TEN).toEqual(10);
-    expect(testStructs.ScopedConstants.ALSO_TEN).toEqual(10);
-
-    expect(testStructs.ScopedConstants.EType.E0).toEqual(0);
-    expect(testStructs.ScopedConstants.EType.E1).toEqual(1);
-    expect(testStructs.ScopedConstants.EType.E2).toEqual(10);
-    expect(testStructs.ScopedConstants.EType.E3).toEqual(10);
-    expect(testStructs.ScopedConstants.EType.E4).toEqual(11);
-
-    var s = new testStructs.ScopedConstants();
-    expect(s.f0).toEqual(0);
-    expect(s.f1).toEqual(1);
-    expect(s.f2).toEqual(10);
-    expect(s.f3).toEqual(10);
-    expect(s.f4).toEqual(11);
-    expect(s.f5).toEqual(10);
-    expect(s.f6).toEqual(10);
-  }
-
-  function structEncodeDecode(struct) {
-    var structClass = struct.constructor;
-    var builder = new codec.MessageBuilder(1234, structClass.encodedSize);
-    builder.encodeStruct(structClass, struct);
-    var message = builder.finish();
-
-    var messageValidator = new validator.Validator(message);
-    var err = structClass.validate(messageValidator, codec.kMessageHeaderSize);
-    expect(err).toEqual(validator.validationError.NONE);
-
-    var reader = new codec.MessageReader(message);
-    return reader.decodeStruct(structClass);
-  }
-
-  function testMapKeyTypes() {
-    var mapFieldsStruct = new testStructs.MapKeyTypes({
-      f0: new Map([[true, false], [false, true]]),  // map<bool, bool>
-      f1: new Map([[0, 0], [1, 127], [-1, -128]]),  // map<int8, int8>
-      f2: new Map([[0, 0], [1, 127], [2, 255]]),  // map<uint8, uint8>
-      f3: new Map([[0, 0], [1, 32767], [2, -32768]]),  // map<int16, int16>
-      f4: new Map([[0, 0], [1, 32768], [2, 0xFFFF]]),  // map<uint16, uint16>
-      f5: new Map([[0, 0], [1, 32767], [2, -32768]]),  // map<int32, int32>
-      f6: new Map([[0, 0], [1, 32768], [2, 0xFFFF]]),  // map<uint32, uint32>
-      f7: new Map([[0, 0], [1, 32767], [2, -32768]]),  // map<int64, int64>
-      f8: new Map([[0, 0], [1, 32768], [2, 0xFFFF]]),  // map<uint64, uint64>
-      f9: new Map([[1000.5, -50000], [100.5, 5000]]),  // map<float, float>
-      f10: new Map([[-100.5, -50000], [0, 50000000]]),  // map<double, double>
-      f11: new Map([["one", "two"], ["free", "four"]]),  // map<string, string>
-    });
-    var decodedStruct = structEncodeDecode(mapFieldsStruct);
-    expect(decodedStruct.f0).toEqual(mapFieldsStruct.f0);
-    expect(decodedStruct.f1).toEqual(mapFieldsStruct.f1);
-    expect(decodedStruct.f2).toEqual(mapFieldsStruct.f2);
-    expect(decodedStruct.f3).toEqual(mapFieldsStruct.f3);
-    expect(decodedStruct.f4).toEqual(mapFieldsStruct.f4);
-    expect(decodedStruct.f5).toEqual(mapFieldsStruct.f5);
-    expect(decodedStruct.f6).toEqual(mapFieldsStruct.f6);
-    expect(decodedStruct.f7).toEqual(mapFieldsStruct.f7);
-    expect(decodedStruct.f8).toEqual(mapFieldsStruct.f8);
-    expect(decodedStruct.f9).toEqual(mapFieldsStruct.f9);
-    expect(decodedStruct.f10).toEqual(mapFieldsStruct.f10);
-    expect(decodedStruct.f11).toEqual(mapFieldsStruct.f11);
-  }
-
-  function testMapValueTypes() {
-    var mapFieldsStruct = new testStructs.MapValueTypes({
-      // map<string, array<string>>
-      f0: new Map([["a", ["b", "c"]], ["d", ["e"]]]),
-      // map<string, array<string>?>
-      f1: new Map([["a", null], ["b", ["c", "d"]]]),
-      // map<string, array<string?>>
-      f2: new Map([["a", [null]], ["b", [null, "d"]]]),
-      // map<string, array<string,2>>
-      f3: new Map([["a", ["1", "2"]], ["b", ["1", "2"]]]),
-      // map<string, array<array<string, 2>?>>
-      f4: new Map([["a", [["1", "2"]]], ["b", [null]]]),
-      // map<string, array<array<string, 2>, 1>>
-      f5: new Map([["a", [["1", "2"]]]]),
-      // map<string, Rect?>
-      f6: new Map([["a", null]]),
-      // map<string, map<string, string>>
-      f7: new Map([["a", new Map([["b", "c"]])]]),
-      // map<string, array<map<string, string>>>
-      f8: new Map([["a", [new Map([["b", "c"]])]]]),
-      // map<string, handle>
-      f9: new Map([["a", 1234]]),
-      // map<string, array<handle>>
-      f10: new Map([["a", [1234, 5678]]]),
-      // map<string, map<string, handle>>
-      f11: new Map([["a", new Map([["b", 1234]])]]),
-    });
-    var decodedStruct = structEncodeDecode(mapFieldsStruct);
-    expect(decodedStruct.f0).toEqual(mapFieldsStruct.f0);
-    expect(decodedStruct.f1).toEqual(mapFieldsStruct.f1);
-    expect(decodedStruct.f2).toEqual(mapFieldsStruct.f2);
-    expect(decodedStruct.f3).toEqual(mapFieldsStruct.f3);
-    expect(decodedStruct.f4).toEqual(mapFieldsStruct.f4);
-    expect(decodedStruct.f5).toEqual(mapFieldsStruct.f5);
-    expect(decodedStruct.f6).toEqual(mapFieldsStruct.f6);
-    expect(decodedStruct.f7).toEqual(mapFieldsStruct.f7);
-    expect(decodedStruct.f8).toEqual(mapFieldsStruct.f8);
-    expect(decodedStruct.f9).toEqual(mapFieldsStruct.f9);
-    expect(decodedStruct.f10).toEqual(mapFieldsStruct.f10);
-    expect(decodedStruct.f11).toEqual(mapFieldsStruct.f11);
-  }
-
-  function testFloatNumberValues() {
-    var decodedStruct = structEncodeDecode(new testStructs.FloatNumberValues);
-    expect(decodedStruct.f0).toEqual(testStructs.FloatNumberValues.V0);
-    expect(decodedStruct.f1).toEqual(testStructs.FloatNumberValues.V1);
-    expect(decodedStruct.f2).toEqual(testStructs.FloatNumberValues.V2);
-    expect(decodedStruct.f3).toEqual(testStructs.FloatNumberValues.V3);
-    expect(decodedStruct.f4).toEqual(testStructs.FloatNumberValues.V4);
-    expect(decodedStruct.f5).toEqual(testStructs.FloatNumberValues.V5);
-    expect(decodedStruct.f6).toEqual(testStructs.FloatNumberValues.V6);
-    expect(decodedStruct.f7).toEqual(testStructs.FloatNumberValues.V7);
-    expect(decodedStruct.f8).toEqual(testStructs.FloatNumberValues.V8);
-    expect(decodedStruct.f9).toEqual(testStructs.FloatNumberValues.V9);
-  }
-
-  function testIntegerNumberValues() {
-    var decodedStruct = structEncodeDecode(new testStructs.IntegerNumberValues);
-    expect(decodedStruct.f0).toEqual(testStructs.IntegerNumberValues.V0);
-    expect(decodedStruct.f1).toEqual(testStructs.IntegerNumberValues.V1);
-    expect(decodedStruct.f2).toEqual(testStructs.IntegerNumberValues.V2);
-    expect(decodedStruct.f3).toEqual(testStructs.IntegerNumberValues.V3);
-    expect(decodedStruct.f4).toEqual(testStructs.IntegerNumberValues.V4);
-    expect(decodedStruct.f5).toEqual(testStructs.IntegerNumberValues.V5);
-    expect(decodedStruct.f6).toEqual(testStructs.IntegerNumberValues.V6);
-    expect(decodedStruct.f7).toEqual(testStructs.IntegerNumberValues.V7);
-    expect(decodedStruct.f8).toEqual(testStructs.IntegerNumberValues.V8);
-    expect(decodedStruct.f9).toEqual(testStructs.IntegerNumberValues.V9);
-    expect(decodedStruct.f10).toEqual(testStructs.IntegerNumberValues.V10);
-    expect(decodedStruct.f11).toEqual(testStructs.IntegerNumberValues.V11);
-    expect(decodedStruct.f12).toEqual(testStructs.IntegerNumberValues.V12);
-    expect(decodedStruct.f13).toEqual(testStructs.IntegerNumberValues.V13);
-    expect(decodedStruct.f14).toEqual(testStructs.IntegerNumberValues.V14);
-    expect(decodedStruct.f15).toEqual(testStructs.IntegerNumberValues.V15);
-    expect(decodedStruct.f16).toEqual(testStructs.IntegerNumberValues.V16);
-    expect(decodedStruct.f17).toEqual(testStructs.IntegerNumberValues.V17);
-    expect(decodedStruct.f18).toEqual(testStructs.IntegerNumberValues.V18);
-    expect(decodedStruct.f19).toEqual(testStructs.IntegerNumberValues.V19);
-  }
-
-  function testUnsignedNumberValues() {
-    var decodedStruct =
-        structEncodeDecode(new testStructs.UnsignedNumberValues);
-    expect(decodedStruct.f0).toEqual(testStructs.UnsignedNumberValues.V0);
-    expect(decodedStruct.f1).toEqual(testStructs.UnsignedNumberValues.V1);
-    expect(decodedStruct.f2).toEqual(testStructs.UnsignedNumberValues.V2);
-    expect(decodedStruct.f3).toEqual(testStructs.UnsignedNumberValues.V3);
-    expect(decodedStruct.f4).toEqual(testStructs.UnsignedNumberValues.V4);
-    expect(decodedStruct.f5).toEqual(testStructs.UnsignedNumberValues.V5);
-    expect(decodedStruct.f6).toEqual(testStructs.UnsignedNumberValues.V6);
-    expect(decodedStruct.f7).toEqual(testStructs.UnsignedNumberValues.V7);
-    expect(decodedStruct.f8).toEqual(testStructs.UnsignedNumberValues.V8);
-    expect(decodedStruct.f9).toEqual(testStructs.UnsignedNumberValues.V9);
-    expect(decodedStruct.f10).toEqual(testStructs.UnsignedNumberValues.V10);
-    expect(decodedStruct.f11).toEqual(testStructs.UnsignedNumberValues.V11);
-  }
-
-
-  function testBitArrayValues() {
-    var bitArraysStruct = new testStructs.BitArrayValues({
-      // array<bool, 1> f0;
-      f0: [true],
-      // array<bool, 7> f1;
-      f1: [true, false, true, false, true, false, true],
-      // array<bool, 9> f2;
-      f2: [true, false, true, false, true, false, true, false, true],
-      // array<bool> f3;
-      f3: [true, false, true, false, true, false, true, false],
-      // array<array<bool>> f4;
-      f4: [[true], [false], [true, false], [true, false, true, false]],
-      // array<array<bool>?> f5;
-      f5: [[true], null, null, [true, false, true, false]],
-      // array<array<bool, 2>?> f6;
-      f6: [[true, false], [true, false], [true, false]],
-    });
-    var decodedStruct = structEncodeDecode(bitArraysStruct);
-    expect(decodedStruct.f0).toEqual(bitArraysStruct.f0);
-    expect(decodedStruct.f1).toEqual(bitArraysStruct.f1);
-    expect(decodedStruct.f2).toEqual(bitArraysStruct.f2);
-    expect(decodedStruct.f3).toEqual(bitArraysStruct.f3);
-    expect(decodedStruct.f4).toEqual(bitArraysStruct.f4);
-    expect(decodedStruct.f5).toEqual(bitArraysStruct.f5);
-    expect(decodedStruct.f6).toEqual(bitArraysStruct.f6);
-  }
-
-  testConstructors();
-  testNoDefaultFieldValues();
-  testDefaultFieldValues();
-  testScopedConstants();
-  testMapKeyTypes();
-  testMapValueTypes();
-  testFloatNumberValues();
-  testIntegerNumberValues();
-  testUnsignedNumberValues();
-  testBitArrayValues();
-  this.result = "PASS";
-});
diff --git a/mojo/public/js/tests/union_unittest.js b/mojo/public/js/tests/union_unittest.js
deleted file mode 100644
index c3ee297..0000000
--- a/mojo/public/js/tests/union_unittest.js
+++ /dev/null
@@ -1,194 +0,0 @@
-// Copyright 2015 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.
-
-define([
-    "gin/test/expect",
-    "mojo/public/interfaces/bindings/tests/test_unions.mojom",
-    "mojo/public/js/codec",
-    "mojo/public/js/validator",
-], function(expect,
-            unions,
-            codec,
-            validator) {
-  function testConstructors() {
-    var u = new unions.PodUnion();
-    expect(u.$data).toEqual(null);
-    expect(u.$tag).toBeUndefined();
-
-    u.f_uint32 = 32;
-
-    expect(u.f_uint32).toEqual(32);
-    expect(u.$tag).toEqual(unions.PodUnion.Tags.f_uint32);
-
-    var u = new unions.PodUnion({f_uint64: 64});
-    expect(u.f_uint64).toEqual(64);
-    expect(u.$tag).toEqual(unions.PodUnion.Tags.f_uint64);
-    expect(function() {var v = u.f_uint32;}).toThrow();
-
-    expect(function() {
-      var u = new unions.PodUnion({
-        f_uint64: 64,
-        f_uint32: 32,
-      });
-    }).toThrow();
-
-    expect(function() {
-      var u = new unions.PodUnion({ foo: 64 }); }).toThrow();
-
-    expect(function() {
-      var u = new unions.PodUnion([1,2,3,4]); }).toThrow();
-  }
-
-  function structEncodeDecode(struct) {
-    var structClass = struct.constructor;
-    var builder = new codec.MessageBuilder(1234, structClass.encodedSize);
-    builder.encodeStruct(structClass, struct);
-
-    var message = builder.finish();
-
-    var messageValidator = new validator.Validator(message);
-    var err = structClass.validate(messageValidator, codec.kMessageHeaderSize);
-    expect(err).toEqual(validator.validationError.NONE);
-
-    var reader = new codec.MessageReader(message);
-    var view = reader.decoder.buffer.dataView;
-
-    return reader.decodeStruct(structClass);
-  }
-
-  function testBasicEncoding() {
-    var s = new unions.WrapperStruct({
-      pod_union: new unions.PodUnion({
-        f_uint64: 64})});
-
-    var decoded = structEncodeDecode(s);
-    expect(decoded).toEqual(s);
-
-    var s = new unions.WrapperStruct({
-      pod_union: new unions.PodUnion({
-        f_bool : true})});
-
-    var decoded = structEncodeDecode(s);
-    expect(decoded.pod_union.$tag).toEqual(unions.PodUnion.Tags.f_bool);
-    // Use toEqual() instead of toBeTruthy() to make sure the field type is
-    // actually boolean.
-    expect(decoded.pod_union.f_bool).toEqual(true);
-
-    var s = new unions.WrapperStruct({
-      object_union: new unions.ObjectUnion({
-        f_dummy: new unions.DummyStruct({
-          f_int8: 8})})});
-
-    var decoded = structEncodeDecode(s);
-    expect(decoded).toEqual(s);
-
-    var s = new unions.WrapperStruct({
-      object_union: new unions.ObjectUnion({
-        f_array_int8: [1, 2, 3]})});
-
-    var decoded = structEncodeDecode(s);
-    expect(decoded).toEqual(s);
-
-    var s = new unions.WrapperStruct({
-      object_union: new unions.ObjectUnion({
-        f_map_int8: new Map([
-          ["first", 1],
-          ["second", 2],
-        ])})});
-
-    var decoded = structEncodeDecode(s);
-    expect(decoded).toEqual(s);
-
-    // Encoding a union with no member set is an error.
-    var s = new unions.WrapperStruct({
-      object_union: new unions.ObjectUnion()});
-    expect(function() {
-      structEncodeDecode(s); }).toThrow();
-  }
-
-  function testUnionsInArrayEncoding() {
-    var s = new unions.SmallStruct({
-      pod_union_array: [
-        new unions.PodUnion({f_uint32: 32}),
-        new unions.PodUnion({f_uint64: 64}),
-      ]
-    });
-
-    var decoded = structEncodeDecode(s);
-    expect(decoded).toEqual(s);
-  }
-
-  function testUnionsInMapEncoding() {
-    var s = new unions.SmallStruct({
-      pod_union_map: new Map([
-        ["thirty-two", new unions.PodUnion({f_uint32: 32})],
-        ["sixty-four", new unions.PodUnion({f_uint64: 64})],
-      ])
-    });
-
-    var decoded = structEncodeDecode(s);
-    expect(decoded).toEqual(s);
-  }
-
-  function testNestedUnionsEncoding() {
-    var s = new unions.WrapperStruct({
-      object_union: new unions.ObjectUnion({
-        f_pod_union: new unions.PodUnion({f_uint32: 32})
-      })});
-    var decoded = structEncodeDecode(s);
-    expect(decoded).toEqual(s);
-  }
-
-  function structValidate(struct) {
-    var structClass = struct.constructor;
-    var builder = new codec.MessageBuilder(1234, structClass.encodedSize);
-    builder.encodeStruct(structClass, struct);
-
-    var message = builder.finish();
-
-    var messageValidator = new validator.Validator(message);
-    return structClass.validate(messageValidator, codec.kMessageHeaderSize);
-  }
-
-  function testNullUnionMemberValidation() {
-    var s = new unions.WrapperStruct({
-      object_union: new unions.ObjectUnion({
-        f_dummy: null})});
-
-    var err = structValidate(s);
-    expect(err).toEqual(validator.validationError.UNEXPECTED_NULL_POINTER);
-
-    var s = new unions.WrapperStruct({
-      object_union: new unions.ObjectUnion({
-        f_nullable: null})});
-
-    var err = structValidate(s);
-    expect(err).toEqual(validator.validationError.NONE);
-  }
-
-  function testNullUnionValidation() {
-    var s = new unions.SmallStructNonNullableUnion({
-      pod_union: null});
-
-    var err = structValidate(s);
-    expect(err).toEqual(validator.validationError.UNEXPECTED_NULL_UNION);
-
-    var s = new unions.WrapperStruct({
-      object_union: new unions.ObjectUnion({
-        f_pod_union: null})
-      });
-
-    var err = structValidate(s);
-    expect(err).toEqual(validator.validationError.UNEXPECTED_NULL_UNION);
-  }
-
-  testConstructors();
-  testBasicEncoding();
-  testUnionsInArrayEncoding();
-  testUnionsInMapEncoding();
-  testNestedUnionsEncoding();
-  testNullUnionMemberValidation();
-  testNullUnionValidation();
-  this.result = "PASS";
-});
diff --git a/mojo/public/js/tests/validation_unittest.js b/mojo/public/js/tests/validation_unittest.js
index 2a70248..2a07315 100644
--- a/mojo/public/js/tests/validation_unittest.js
+++ b/mojo/public/js/tests/validation_unittest.js
@@ -278,15 +278,9 @@
     expect(testMessagePipe.result).toBe(core.RESULT_OK);
 
     endpoint.bind(testMessagePipe.handle1);
-    var testingController = endpoint.enableTestingMode();
-
-    var validationError;
-    testingController.setInvalidIncomingMessageHandler(function(error) {
-      validationError = error;
-    });
+    var observer = validator.ValidationErrorObserverForTesting.getInstance();
 
     for (var i = 0; i < testFiles.length; i++) {
-      validationError = noError;
       var testMessage = readTestMessage(testFiles[i]);
       var handles = new Array(testMessage.handleCount);
 
@@ -297,8 +291,9 @@
           core.WRITE_MESSAGE_FLAG_NONE);
       expect(writeMessageValue).toBe(core.RESULT_OK);
 
-      testingController.waitForNextMessage();
-      checkValidationResult(testFiles[i], validationError);
+      endpoint.waitForNextMessageForTesting();
+      checkValidationResult(testFiles[i], observer.lastError);
+      observer.reset();
     }
 
     expect(core.close(testMessagePipe.handle0)).toBe(core.RESULT_OK);
@@ -333,6 +328,7 @@
   testIntegratedMessageHeaderValidation();
   testIntegratedResponseMessageValidation();
   testIntegratedRequestMessageValidation();
+  validator.clearTestingMode();
 
   this.result = "PASS";
 });
diff --git a/mojo/public/js/validator.js b/mojo/public/js/validator.js
index fee742d..283546d 100644
--- a/mojo/public/js/validator.js
+++ b/mojo/public/js/validator.js
@@ -28,6 +28,49 @@
   };
 
   var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
+  var gValidationErrorObserver = null;
+
+  function reportValidationError(error) {
+    if (gValidationErrorObserver) {
+      gValidationErrorObserver.setLastError(error);
+    }
+  }
+
+  var ValidationErrorObserverForTesting = (function() {
+    function Observer() {
+      this.lastError = validationError.NONE;
+      this.callback = null;
+    }
+
+    Observer.prototype.setLastError = function(error) {
+      this.lastError = error;
+      if (this.callback) {
+        this.callback(error);
+      }
+    };
+
+    Observer.prototype.reset = function(error) {
+      this.lastError = validationError.NONE;
+      this.callback = null;
+    };
+
+    return {
+      getInstance: function() {
+        if (!gValidationErrorObserver) {
+          gValidationErrorObserver = new Observer();
+        }
+        return gValidationErrorObserver;
+      }
+    };
+  })();
+
+  function isTestingMode() {
+    return Boolean(gValidationErrorObserver);
+  }
+
+  function clearTestingMode() {
+    gValidationErrorObserver = null;
+  }
 
   function isEnumClass(cls) {
     return cls instanceof codec.Enum;
@@ -180,6 +223,7 @@
     return fieldVersion <= structVersion;
   };
 
+  // TODO(wangjimmy): Add support for v2 messages.
   Validator.prototype.validateMessageHeader = function() {
 
     var err = this.validateStructHeader(0, codec.kMessageHeaderSize);
@@ -508,5 +552,9 @@
   var exports = {};
   exports.validationError = validationError;
   exports.Validator = Validator;
+  exports.ValidationErrorObserverForTesting = ValidationErrorObserverForTesting;
+  exports.reportValidationError = reportValidationError;
+  exports.isTestingMode = isTestingMode;
+  exports.clearTestingMode = clearTestingMode;
   return exports;
 });
diff --git a/mojo/public/tools/bindings/README.md b/mojo/public/tools/bindings/README.md
new file mode 100644
index 0000000..737c7e6
--- /dev/null
+++ b/mojo/public/tools/bindings/README.md
@@ -0,0 +1,749 @@
+# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojom IDL and Bindings Generator
+This document is a subset of the [Mojo documentation](/mojo).
+
+[TOC]
+
+## Overview
+
+Mojom is the IDL for Mojo bindings interfaces. Given a `.mojom` file, the
+[bindings generator](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings)
+outputs bindings for all supported languages: **C++**, **JavaScript**, and
+**Java**.
+
+For a trivial example consider the following hypothetical Mojom file we write to
+`//services/widget/public/interfaces/frobinator.mojom`:
+
+```
+module widget.mojom;
+
+interface Frobinator {
+  Frobinate();
+};
+```
+
+This defines a single [interface](#Interfaces) named `Frobinator` in a
+[module](#Modules) named `widget.mojom` (and thus fully qualified in Mojom as
+`widget.mojom.Frobinator`.) Note that many interfaces and/or other types of
+definitions may be included in a single Mojom file.
+
+If we add a corresponding GN target to
+`//services/widget/public/interfaces/BUILD.gn`:
+
+```
+import("mojo/public/tools/bindings/mojom.gni")
+
+mojom("interfaces") {
+  sources = [
+    "frobinator.mojom",
+  ]
+}
+```
+
+and then build this target:
+
+```
+ninja -C out/r services/widget/public/interfaces
+```
+
+we'll find several generated sources in our output directory:
+
+```
+out/r/gen/services/widget/public/interfaces/frobinator.mojom.cc
+out/r/gen/services/widget/public/interfaces/frobinator.mojom.h
+out/r/gen/services/widget/public/interfaces/frobinator.mojom.js
+out/r/gen/services/widget/public/interfaces/frobinator.mojom.srcjar
+...
+```
+
+Each of these generated source modules includes a set of definitions
+representing the Mojom contents within the target language. For more details
+regarding the generated outputs please see
+[documentation for individual target languages](#Generated-Code-For-Target-Languages).
+
+## Mojom Syntax
+
+Mojom IDL allows developers to define **structs**, **unions**, **interfaces**,
+**constants**, and **enums**, all within the context of a **module**. These
+definitions are used to generate code in the supported target languages at build
+time.
+
+Mojom files may **import** other Mojom files in order to reference their
+definitions.
+
+### Primitive Types
+Mojom supports a few basic data types which may be composed into structs or used
+for message parameters.
+
+| Type                          | Description
+|-------------------------------|-------------------------------------------------------|
+| `bool`                        | Boolean type (`true` or `false`.)
+| `int8`, `uint8`               | Signed or unsigned 8-bit integer.
+| `int16`, `uint16`             | Signed or unsigned 16-bit integer.
+| `int32`, `uint32`             | Signed or unsigned 32-bit integer.
+| `int64`, `uint64`             | Signed or unsigned 64-bit integer.
+| `float`, `double`             | 32- or 64-bit floating point number.
+| `string`                      | UTF-8 encoded string.
+| `array<T>`                    | Array of any Mojom type *T*; for example, `array<uint8>` or `array<array<string>>`.
+| `array<T, N>`                 | Fixed-length array of any Mojom type *T*. The parameter *N* must be an integral constant.
+| `map<S, T>`                   | Associated array maping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type.
+| `handle`                      | Generic Mojo handle. May be any type of handle, including a wrapped native platform handle.
+| `handle<message_pipe>`        | Generic message pipe handle.
+| `handle<shared_buffer>`       | Shared buffer handle.
+| `handle<data_pipe_producer>`  | Data pipe producer handle.
+| `handle<data_pipe_consumer>`  | Data pipe consumer handle.
+| *`InterfaceType`*             | Any user-defined Mojom interface type. This is sugar for a strongly-typed message pipe handle which should eventually be used to make outgoing calls on the interface.
+| *`InterfaceType&`*            | An interface request for any user-defined Mojom interface type. This is sugar for a more strongly-typed message pipe handle which is expected to receive request messages and should therefore eventually be bound to an implementation of the interface.
+| *`associated InterfaceType`*  | An associated interface handle. See [Associated Interfaces](#Associated-Interfaces)
+| *`associated InterfaceType&`* | An associated interface request. See [Associated Interfaces](#Associated-Interfaces)
+| *T*?                          | An optional (nullable) value. Primitive numeric types (integers, floats, booleans, and enums) are not nullable. All other types are nullable.
+
+### Modules
+
+Every Mojom file may optionally specify a single **module** to which it belongs.
+
+This is used strictly for aggregaging all defined symbols therein within a
+common Mojom namespace. The specific impact this has on generated binidngs code
+varies for each target language. For example, if the following Mojom is used to
+generate bindings:
+
+```
+module business.stuff;
+
+interface MoneyGenerator {
+  GenerateMoney();
+};
+```
+
+Generated C++ bindings will define a class interface `MoneyGenerator` in the
+`business::stuff` namespace, while Java bindings will define an interface
+`MoneyGenerator` in the `org.chromium.business.stuff` package. JavaScript
+bindings at this time are unaffected by module declarations.
+
+**NOTE:** By convention in the Chromium codebase, **all** Mojom files should
+declare a module name with at least (and preferrably exactly) one top-level name
+as well as an inner `mojom` module suffix. *e.g.*, `chrome.mojom`,
+`business.mojom`, *etc.*
+
+This convention makes it easy to tell which symbols are generated by Mojom when
+reading non-Mojom code, and it also avoids namespace collisions in the fairly
+common scenario where you have a real C++ or Java `Foo` along with a
+corresponding Mojom `Foo` for its serialized representation.
+
+### Imports
+
+If your Mojom references definitions from other Mojom files, you must **import**
+those files. Import syntax is as follows:
+
+```
+import "services/widget/public/interfaces/frobinator.mojom";
+```
+
+Import paths are always relative to the top-level directory.
+
+Note that circular imports are **not** supported.
+
+### Structs
+
+Structs are defined using the **struct** keyword, and they provide a way to
+group related fields together:
+
+``` cpp
+struct StringPair {
+  string first;
+  string second;
+};
+```
+
+Struct fields may be comprised of any of the types listed above in the
+[Primitive Types](#Primitive-Types) section.
+
+Default values may be specified as long as they are constant:
+
+``` cpp
+struct Request {
+  int32 id = -1;
+  string details;
+};
+```
+
+What follows is a fairly
+comprehensive example using the supported field types:
+
+``` cpp
+struct StringPair {
+  string first;
+  string second;
+};
+
+enum AnEnum {
+  YES,
+  NO
+};
+
+interface SampleInterface {
+  DoStuff();
+};
+
+struct AllTheThings {
+  // Note that these types can never be marked nullable!
+  bool boolean_value;
+  int8 signed_8bit_value = 42;
+  uint8 unsigned_8bit_value;
+  int16 signed_16bit_value;
+  uint16 unsigned_16bit_value;
+  int32 signed_32bit_value;
+  uint32 unsigned_32bit_value;
+  int64 signed_64bit_value;
+  uint64 unsigned_64bit_value;
+  float float_value_32bit;
+  double float_value_64bit;
+  AnEnum enum_value = AnEnum.YES;
+
+  // Strings may be nullable.
+  string? maybe_a_string_maybe_not;
+
+  // Structs may contain other structs. These may also be nullable.
+  StringPair some_strings;
+  StringPair? maybe_some_more_strings;
+
+  // In fact structs can also be nested, though in practice you must always make
+  // such fields nullable -- otherwise messages would need to be infinitely long
+  // in order to pass validation!
+  AllTheThings? more_things;
+
+  // Arrays may be templated over any Mojom type, and are always nullable:
+  array<int32> numbers;
+  array<int32>? maybe_more_numbers;
+
+  // Arrays of arrays of arrays... are fine.
+  array<array<array<AnEnum>>> this_works_but_really_plz_stop;
+
+  // The element type may be nullable if it's a type which is allowed to be
+  // nullable.
+  array<AllTheThings?> more_maybe_things;
+
+  // Fixed-size arrays get some extra validation on the receiving end to ensure
+  // that the correct number of elements is always received.
+  array<uint64, 2> uuid;
+
+  // Maps follow many of the same rules as arrays. Key types may be any
+  // non-handle, non-collection type, and value types may be any supported
+  // struct field type. Maps may also be nullable.
+  map<string, int32> one_map;
+  map<AnEnum, string>? maybe_another_map;
+  map<StringPair, AllTheThings?>? maybe_a_pretty_weird_but_valid_map;
+  map<StringPair, map<int32, array<map<string, string>?>?>?> ridiculous;
+
+  // And finally, all handle types are valid as struct fields and may be
+  // nullable. Note that interfaces and interface requests (the "Foo" and
+  // "Foo&" type syntax respectively) are just strongly-typed message pipe
+  // handles.
+  handle generic_handle;
+  handle<data_pipe_consumer> reader;
+  handle<data_pipe_producer>? maybe_writer;
+  handle<shared_buffer> dumping_ground;
+  handle<message_pipe> raw_message_pipe;
+  SampleInterface? maybe_a_sample_interface_client_pipe;
+  SampleInterface& non_nullable_sample_interface_request;
+  SampleInterface&? nullable_sample_interface_request;
+  associated SampleInterface associated_interface_client;
+  associated SampleInterface& associated_interface_request;
+  assocaited SampleInterface&? maybe_another_associated_request;
+};
+```
+
+For details on how all of these different types translate to usable generated
+code, see
+[documentation for individual target languages](#Generated-Code-For-Target-Languages).
+
+### Enumeration Types
+
+Enumeration types may be defined using the **enum** keyword either directly
+within a module or within the namespace of some struct or interface:
+
+```
+module business.mojom;
+
+enum Department {
+  SALES = 0,
+  DEV,
+};
+
+struct Employee {
+  enum Type {
+    FULL_TIME,
+    PART_TIME,
+  };
+
+  Type type;
+  // ...
+};
+```
+
+That that similar to C-style enums, individual values may be explicitly assigned
+within an enum definition. By default values are based at zero and incremenet by
+1 sequentially.
+
+The effect of nested definitions on generated bindings varies depending on the
+target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages)
+
+### Constants
+
+Constants may be defined using the **const** keyword either directly within a
+module or within the namespace of some struct or interface:
+
+```
+module business.mojom;
+
+const string kServiceName = "business";
+
+struct Employee {
+  const uint64 kInvalidId = 0;
+
+  enum Type {
+    FULL_TIME,
+    PART_TIME,
+  };
+
+  uint64 id = kInvalidId;
+  Type type;
+};
+```
+
+The effect of nested definitions on generated bindings varies depending on the
+target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages)
+
+### Interfaces
+
+An **interface** is a logical bundle of parameterized request messages. Each
+request message may optionally define a parameterized response message. Here's
+syntax to define an interface `Foo` with various kinds of requests:
+
+```
+interface Foo {
+  // A request which takes no arguments and expects no response.
+  MyMessage();
+
+  // A request which has some arguments and expects no response.
+  MyOtherMessage(string name, array<uint8> bytes);
+
+  // A request which expects a single-argument response.
+  MyMessageWithResponse(string command) => (bool success);
+
+  // A request which expects a response with multiple arguments.
+  MyMessageWithMoarResponse(string a, string b) => (int8 c, int8 d);
+};
+```
+
+Anything which is a valid struct field type (see [Structs](#Structs)) is also a
+valid request or response argument type. The type notation is the same for both.
+
+### Attributes
+
+Mojom definitions may have their meaning altered by **attributes**, specified
+with a syntax similar to Java or C# attributes. There are a handle of
+interesting attributes supported today.
+
+**`[Sync]`**
+:   The `Sync` attribute may be specified for any interface method which expects
+    a response. This makes it so that callers of the method can wait
+    synchronously for a response. See
+    [Synchronous Calls](/mojo/public/cpp/bindings#Synchronous-Calls) in the C++
+    bindings documentation. Note that sync calls are not currently supported in
+    other target languages.
+
+**`[Extensible]`**
+:   The `Extensible` attribute may be specified for any enum definition. This
+    essentially disables builtin range validation when receiving values of the
+    enum type in a message, allowing older bindings to tolerate unrecognized
+    values from newer versions of the enum.
+
+**`[Native]`**
+:   The `Native` attribute may be specified for an empty struct declaration to
+    provide a nominal bridge between Mojo IPC and legacy `IPC::ParamTraits` or
+    `IPC_STRUCT_TRAITS*` macros.
+    See [Using Legacy IPC Traits](/ipc#Using-Legacy-IPC-Traits) for more
+    details. Note support for this attribute is strictly limited to C++ bindings
+    generation.
+
+**`[MinVersion=N]`**
+:   The `MinVersion` attribute is used to specify the version at which a given
+    field, enum value, interface method, or method parameter was introduced.
+    See [Versioning](#Versioning) for more details.
+
+## Generated Code For Target Languages
+
+When the bindings generator successfully processes an input Mojom file, it emits
+corresponding code for each supported target language. For more details on how
+Mojom concepts translate to a given target langauge, please refer to the
+bindings API documentation for that language:
+
+* [C++ Bindings](/mojo/public/cpp/bindings)
+* [JavaScript Bindings](/mojo/public/js)
+* [Java Bindings](/mojo/public/java/bindings)
+
+## Message Validation
+
+Regardless of target language, all interface messages are validated during
+deserialization before they are dispatched to a receiving implementation of the
+interface. This helps to ensure consitent validation across interfaces without
+leaving the burden to developers and security reviewers every time a new message
+is added.
+
+If a message fails validation, it is never dispatched. Instead a **connection
+error** is raised on the binding object (see
+[C++ Connection Errors](/mojo/public/cpp/bindings#Connection-Errors),
+[Java Connection Errors](/mojo/public/java/bindings#Connection-Errors), or
+[JavaScript Connection Errors](/mojo/public/js#Connection-Errors) for details.)
+
+Some baseline level of validation is done automatically for primitive Mojom
+types.
+
+### Non-Nullable Objects
+
+Mojom fields or parameter values (*e.g.*, structs, interfaces, arrays, *etc.*)
+may be marked nullable in Mojom definitions (see
+[Primitive Types](#Primitive-Types).) If a field or parameter is **not** marked
+nullable but a message is received with a null value in its place, that message
+will fail validation.
+
+### Enums
+
+Enums declared in Mojom are automatically validated against the range of legal
+values. For example if a Mojom declares the enum:
+
+``` cpp
+enum AdvancedBoolean {
+  TRUE = 0,
+  FALSE = 1,
+  FILE_NOT_FOUND = 2,
+};
+```
+
+and a message is received with the integral value 3 (or anything other than 0,
+1, or 2) in place of some `AdvancedBoolean` field or parameter, the message will
+fail validation.
+
+*** note
+NOTE: It's possible to avoid this type of validation error by explicitly marking
+an enum as [Extensible](#Attributes) if you anticipate your enum being exchanged
+between two different versions of the binding interface. See
+[Versioning](#Versioning).
+***
+
+### Other failures
+
+There are a  host of internal validation errors that may occur when a malformed
+message is received, but developers should not be concerned with these
+specifically; in general they can only result from internal bindings bugs,
+compromised processes, or some remote endpoint making a dubious effort to
+manually encode their own bindings messages.
+
+### Custom Validation
+
+It's also possible for developers to define custom validation logic for specific
+Mojom struct types by exploiting the
+[type mapping](/mojo/public/cpp/bindings#Type-Mapping) system for C++ bindings.
+Messages rejected by custom validation logic trigger the same validation failure
+behavior as the built-in type validation routines.
+
+## Associated Interfaces
+
+As mentioned in the [Primitive Types](#Primitive-Types) section above, interface
+and interface request fields and parameters may be marked as `associated`. This
+essentially means that they are piggy-backed on some other interface's message
+pipe.
+
+Because individual interface message pipes operate independently there can be no
+relative ordering guarantees among them. Associated interfaces are useful when
+one interface needs to guarantee strict FIFO ordering with respect to one or
+more other interfaces, as they allow interfaces to share a single pipe.
+
+Currenly associated interfaces are only supported in generated C++ bindings.
+See the documentation for
+[C++ Associated Interfaces](/mojo/public/cpp/bindings#Associated-Interfaces).
+
+## Versioning
+
+### Overview
+
+*** note
+**NOTE:** You don't need to worry about versioning if you don't care about
+backwards compatibility. Specifically, all parts of Chrome are updated
+atomically today and there is not yet any possibility of any two Chrome
+processes communicating with two different versions of any given Mojom
+interface.
+***
+
+Services extend their interfaces to support new features over time, and clients
+want to use those new features when they are available. If services and clients
+are not updated at the same time, it's important for them to be able to
+communicate with each other using different snapshots (versions) of their
+interfaces.
+
+This document shows how to extend Mojom interfaces in a backwards-compatible
+way. Changing interfaces in a non-backwards-compatible way is not discussed,
+because in that case communication between different interface versions is
+impossible anyway.
+
+### Versioned Structs
+
+You can use the `MinVersion` [attribute](#Attributes) to indicate from which
+version a struct field is introduced. Assume you have the following struct:
+
+``` cpp
+struct Employee {
+  uint64 employee_id;
+  string name;
+};
+```
+
+and you would like to add a birthday field. You can do:
+
+``` cpp
+struct Employee {
+  uint64 employee_id;
+  string name;
+  [MinVersion=1] Date? birthday;
+};
+```
+
+By default, fields belong to version 0. New fields must be appended to the
+struct definition (*i.e*., existing fields must not change **ordinal value**)
+with the `MinVersion` attribute set to a number greater than any previous
+existing versions.
+
+**Ordinal value** refers to the relative positional layout of a struct's fields
+(and an interface's methods) when encoded in a message. Implicitly, ordinal
+numbers are assigned to fields according to lexical position. In the example
+above, `employee_id` has an ordinal value of 0 and `name` has an ordinal value
+of 1.
+
+Ordinal values can be specified explicitly using `**@**` notation, subject to
+the following hard constraints:
+
+* For any given struct or interface, if any field or method explicitly specifies
+    an ordinal value, all fields or methods must explicitly specify an ordinal
+    value.
+* For an *N*-field struct or *N*-method interface, the set of explicitly
+    assigned ordinal values must be limited to the range *[0, N-1]*.
+
+You may reorder fields, but you must ensure that the ordinal values of existing
+fields remain unchanged. For example, the following struct remains
+backwards-compatible:
+
+``` cpp
+struct Employee {
+  uint64 employee_id@0;
+  [MinVersion=1] Date? birthday@2;
+  string name@1;
+};
+```
+
+*** note
+**NOTE:** Newly added fields of Mojo object or handle types MUST be nullable.
+See [Primitive Types](#Primitive-Types).
+***
+
+### Versioned Interfaces
+
+There are two dimensions on which an interface can be extended
+
+**Appending New Parameters To Existing Methods**
+:   Parameter lists are treated as structs internally, so all the rules of
+    versioned structs apply to method parameter lists. The only difference is
+    that the version number is scoped to the whole interface rather than to any
+    individual parameter list.
+
+    Please note that adding a response to a message which did not previously
+    expect a response is a not a backwards-compatible change.
+
+**Appending New Methods**
+:   Similarly, you can reorder methods with explicit ordinal values as long as
+    the ordinal values of existing methods are unchanged.
+
+For example:
+
+``` cpp
+// Old version:
+interface HumanResourceDatabase {
+  AddEmployee(Employee employee) => (bool success);
+  QueryEmployee(uint64 id) => (Employee? employee);
+};
+
+// New version:
+interface HumanResourceDatabase {
+  AddEmployee(Employee employee) => (bool success);
+
+  QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)
+      => (Employee? employee,
+          [MinVersion=1] array<uint8>? finger_print);
+
+  [MinVersion=1]
+  AttachFingerPrint(uint64 id, array<uint8> finger_print)
+      => (bool success);
+};
+```
+
+Similar to [versioned structs](#Versioned-Structs), when you pass the parameter
+list of a request or response method to a destination using an older version of
+an interface, unrecognized fields are silently discarded. However, if the method
+call itself is not recognized, it is considered a validation error and the
+receiver will close its end of the interface pipe. For example, if a client on
+version 1 of the above interface sends an `AttachFingerPrint` request to an
+implementation of version 0, the client will be disconnected.
+
+Bindings target languages that support versioning expose means to query or
+assert the remote version from a client handle (*e.g.*, an
+`InterfacePtr<T>` in C++ bindings.)
+
+See
+[C++ Versioning Considerations](/mojo/public/cpp/bindings#Versioning-Considerations)
+and [Java Versioning Considerations](/mojo/public/java/bindings#Versioning-Considerations)
+
+### Versioned Enums
+
+**By default, enums are non-extensible**, which means that generated message
+validation code does not expect to see new values in the future. When an unknown
+value is seen for a non-extensible enum field or parameter, a validation error
+is raised.
+
+If you want an enum to be extensible in the future, you can apply the
+`[Extensible]` [attribute](#Attributes):
+
+``` cpp
+[Extensible]
+enum Department {
+  SALES,
+  DEV,
+};
+```
+
+And later you can extend this enum without breaking backwards compatibility:
+
+``` cpp
+[Extensible]
+enum Department {
+  SALES,
+  DEV,
+  [MinVersion=1] RESEARCH,
+};
+```
+
+*** note
+**NOTE:** For versioned enum definitions, the use of a `[MinVersion]` attribute
+is strictly for documentation purposes. It has no impact on the generated code.
+***
+
+With extensible enums, bound interface implementations may receive unknown enum
+values and will need to deal with them gracefully. See
+[C++ Versioning Considerations](/mojo/public/cpp/bindings#Versioning-Considerations)
+for details.
+
+## Grammar Reference
+
+Below is the (BNF-ish) context-free grammar of the Mojom language:
+
+```
+MojomFile = StatementList
+StatementList = Statement StatementList | Statement
+Statement = ModuleStatement | ImportStatement | Definition
+
+ModuleStatement = AttributeSection "module" Identifier ";"
+ImportStatement = "import" StringLiteral ";"
+Definition = Struct Union Interface Enum Const
+
+AttributeSection = "[" AttributeList "]"
+AttributeList = <empty> | NonEmptyAttributeList
+NonEmptyAttributeList = Attribute
+                      | Attribute "," NonEmptyAttributeList
+Attribute = Name
+          | Name "=" Name
+          | Name "=" Literal
+
+Struct = AttributeSection "struct" Name "{" StructBody "}" ";"
+       | AttributeSection "struct" Name ";"
+StructBody = <empty>
+           | StructBody Const
+           | StructBody Enum
+           | StructBody StructField
+StructField = AttributeSection TypeSpec Name Orginal Default ";"
+
+Union = AttributeSection "union" Name "{" UnionBody "}" ";"
+UnionBody = <empty> | UnionBody UnionField
+UnionField = AttributeSection TypeSpec Name Ordinal ";"
+
+Interface = AttributeSection "interface" Name "{" InterfaceBody "}" ";"
+InterfaceBody = <empty>
+              | InterfaceBody Const
+              | InterfaceBody Enum
+              | InterfaceBody Method
+Method = AttributeSection Name Ordinal "(" ParamterList ")" Response ";"
+ParameterList = <empty> | NonEmptyParameterList
+NonEmptyParameterList = Parameter
+                      | Parameter "," NonEmptyParameterList
+Parameter = AttributeSection TypeSpec Name Ordinal
+Response = <empty> | "=>" "(" ParameterList ")"
+
+TypeSpec = TypeName "?" | TypeName
+TypeName = BasicTypeName
+         | Array
+         | FixedArray
+         | Map
+         | InterfaceRequest
+BasicTypeName = Identifier | "associated" Identifier | HandleType | NumericType
+NumericType = "bool" | "int8" | "uint8" | "int16" | "uint16" | "int32"
+            | "uint32" | "int64" | "uint64" | "float" | "double"
+HandleType = "handle" | "handle" "<" SpecificHandleType ">"
+SpecificHandleType = "message_pipe"
+                   | "shared_buffer"
+                   | "data_pipe_consumer"
+                   | "data_pipe_producer"
+Array = "array" "<" TypeSpec ">"
+FixedArray = "array" "<" TypeSpec "," IntConstDec ">"
+Map = "map" "<" Identifier "," TypeSpec ">"
+InterfaceRequest = Identifier "&" | "associated" Identifier "&"
+
+Ordinal = <empty> | OrdinalValue
+
+Default = <empty> | "=" Constant
+
+Enum = AttributeSection "enum" Name "{" NonEmptyEnumValueList "}" ";"
+     | AttributeSection "enum" Name "{" NonEmptyEnumValueList "," "}" ";"
+NonEmptyEnumValueList = EnumValue | NonEmptyEnumValueList "," EnumValue
+EnumValue = AttributeSection Name
+          | AttributeSection Name "=" Integer
+          | AttributeSection Name "=" Identifier
+
+Const = "const" TypeSpec Name "=" Constant ";"
+
+Constant = Literal | Identifier ";"
+
+Identifier = Name | Name "." Identifier
+
+Literal = Integer | Float | "true" | "false" | "default" | StringLiteral
+
+Integer = IntConst | "+" IntConst | "-" IntConst
+IntConst = IntConstDec | IntConstHex
+
+Float = FloatConst | "+" FloatConst | "-" FloatConst
+
+; The rules below are for tokens matched strictly according to the given regexes
+
+Identifier = /[a-zA-Z_][0-9a-zA-Z_]*/
+IntConstDec = /0|(1-9[0-9]*)/
+IntConstHex = /0[xX][0-9a-fA-F]+/
+OrdinalValue = /@(0|(1-9[0-9]*))/
+FloatConst = ... # Imagine it's close enough to C-style float syntax.
+StringLiteral = ... # Imagine it's close enough to C-style string literals, including escapes.
+```
+
+## Additional Documentation
+
+[Mojom Message Format](https://docs.google.com/document/d/13pv9cFh5YKuBggDBQ1-AL8VReF-IYpFOFpRfvWFrwio/edit)
+:    Describes the wire format used by Mojo bindings interfaces over message
+     pipes.
+
+[Input Format of Mojom Message Validation Tests](https://docs.google.com/document/d/1-y-2IYctyX2NPaLxJjpJfzVNWCC2SR2MJAD9MpIytHQ/edit)
+:    Describes a text format used to facilitate bindings message validation
+     tests.
diff --git a/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
index 831157f..ca36723 100644
--- a/mojo/public/tools/bindings/chromium_bindings_configuration.gni
+++ b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
@@ -21,6 +21,7 @@
   "//device/gamepad/public/interfaces/typemaps.gni",
   "//device/generic_sensor/public/interfaces/typemaps.gni",
   "//device/usb/public/interfaces/typemaps.gni",
+  "//extensions/common/typemaps.gni",
   "//gpu/ipc/common/typemaps.gni",
   "//media/capture/mojo/typemaps.gni",
   "//media/mojo/interfaces/typemaps.gni",
@@ -31,6 +32,7 @@
   "//services/resource_coordinator/public/cpp/typemaps.gni",
   "//services/service_manager/public/cpp/typemaps.gni",
   "//services/ui/gpu/interfaces/typemaps.gni",
+  "//services/ui/public/interfaces/cursor/typemaps.gni",
   "//services/ui/public/interfaces/ime/typemaps.gni",
   "//services/video_capture/public/interfaces/typemaps.gni",
   "//skia/public/interfaces/typemaps.gni",
@@ -40,6 +42,7 @@
   "//ui/events/devices/mojo/typemaps.gni",
   "//ui/events/mojo/typemaps.gni",
   "//ui/gfx/typemaps.gni",
+  "//ui/latency/mojo/typemaps.gni",
   "//ui/message_center/mojo/typemaps.gni",
   "//url/mojo/typemaps.gni",
 ]
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
index a23b107..aba1838 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
@@ -169,14 +169,14 @@
                   "&serialization_context")}}
 
   bool result = false;
-  mojo::MessageReceiver* responder =
+  std::unique_ptr<mojo::MessageReceiver> responder(
       new {{class_name}}_{{method.name}}_HandleSyncResponse(
           &result
 {%-     for param in method.response_parameters -%}
           , param_{{param.name}}
-{%-     endfor %});
-  if (!receiver_->AcceptWithResponder(builder.message(), responder))
-    delete responder;
+{%-     endfor %}));
+  ignore_result(receiver_->AcceptWithResponder(builder.message(),
+                                               std::move(responder)));
   return result;
 }
 {%-   endif %}
@@ -200,15 +200,15 @@
                   "&serialization_context")}}
 
 {%- if method.response_parameters != None %}
-  mojo::MessageReceiver* responder =
-      new {{class_name}}_{{method.name}}_ForwardToCallback(std::move(callback));
-  if (!receiver_->AcceptWithResponder(builder.message(), responder))
-    delete responder;
+  std::unique_ptr<mojo::MessageReceiver> responder(
+      new {{class_name}}_{{method.name}}_ForwardToCallback(
+          std::move(callback)));
+  ignore_result(receiver_->AcceptWithResponder(builder.message(),
+                                               std::move(responder)));
 {%- else %}
-  bool ok = receiver_->Accept(builder.message());
-  // This return value may be ignored as !ok implies the Connector has
+  // This return value may be ignored as false implies the Connector has
   // encountered an error, which will be visible through other means.
-  ALLOW_UNUSED_LOCAL(ok);
+  ignore_result(receiver_->Accept(builder.message()));
 {%- endif %}
 }
 {%- endfor %}
@@ -226,10 +226,10 @@
   static {{class_name}}::{{method.name}}Callback CreateCallback(
       uint64_t request_id,
       bool is_sync,
-      mojo::MessageReceiverWithStatus* responder) {
+      std::unique_ptr<mojo::MessageReceiverWithStatus> responder) {
     std::unique_ptr<{{class_name}}_{{method.name}}_ProxyToResponder> proxy(
         new {{class_name}}_{{method.name}}_ProxyToResponder(
-            request_id, is_sync, responder));
+            request_id, is_sync, std::move(responder)));
     return base::Bind(&{{class_name}}_{{method.name}}_ProxyToResponder::Run,
                       base::Passed(&proxy));
   }
@@ -245,17 +245,17 @@
 #endif
     // If the Callback was dropped then deleting the responder will close
     // the pipe so the calling application knows to stop waiting for a reply.
-    delete responder_;
+    responder_ = nullptr;
   }
 
  private:
   {{class_name}}_{{method.name}}_ProxyToResponder(
       uint64_t request_id,
       bool is_sync,
-      mojo::MessageReceiverWithStatus* responder)
+      std::unique_ptr<mojo::MessageReceiverWithStatus> responder)
       : request_id_(request_id),
         is_sync_(is_sync),
-        responder_(responder) {
+        responder_(std::move(responder)) {
   }
 
   void Run(
@@ -264,7 +264,7 @@
 
   uint64_t request_id_;
   bool is_sync_;
-  mojo::MessageReceiverWithStatus* responder_;
+  std::unique_ptr<mojo::MessageReceiverWithStatus> responder_;
 
   DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_ProxyToResponder);
 };
@@ -285,12 +285,10 @@
 
   {{build_message(response_params_struct, "in_%s", params_description,
                   "&serialization_context")}}
-  bool ok = responder_->Accept(builder.message());
-  ALLOW_UNUSED_LOCAL(ok);
-  // TODO(darin): !ok returned here indicates a malformed message, and that may
-  // be good reason to close the connection. However, we don't have a way to do
-  // that from here. We should add a way.
-  delete responder_;
+  ignore_result(responder_->Accept(builder.message()));
+  // TODO(darin): Accept() returning false indicates a malformed message, and
+  // that may be good reason to close the connection. However, we don't have a
+  // way to do that from here. We should add a way.
   responder_ = nullptr;
 }
 {%-   endif -%}
@@ -334,7 +332,7 @@
 bool {{class_name}}StubDispatch::AcceptWithResponder(
     {{interface.name}}* impl,
     mojo::Message* message,
-    mojo::MessageReceiverWithStatus* responder) {
+    std::unique_ptr<mojo::MessageReceiverWithStatus> responder) {
 {%- if interface.methods %}
   switch (message->header()->name) {
 {%-   for method in interface.methods %}
@@ -350,7 +348,8 @@
       {{class_name}}::{{method.name}}Callback callback =
           {{class_name}}_{{method.name}}_ProxyToResponder::CreateCallback(
               message->request_id(),
-              message->has_flag(mojo::Message::kFlagIsSync), responder);
+              message->has_flag(mojo::Message::kFlagIsSync),
+              std::move(responder));
       // A null |impl| means no implementation was bound.
       assert(impl);
       TRACE_EVENT0("mojom", "{{class_name}}::{{method.name}}");
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl
index 9f01348..79ab46f 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl
@@ -1,9 +1,10 @@
 class {{export_attribute}} {{interface.name}}StubDispatch {
  public:
   static bool Accept({{interface.name}}* impl, mojo::Message* message);
-  static bool AcceptWithResponder({{interface.name}}* impl,
-                                  mojo::Message* message,
-                                  mojo::MessageReceiverWithStatus* responder);
+  static bool AcceptWithResponder(
+      {{interface.name}}* impl,
+      mojo::Message* message,
+      std::unique_ptr<mojo::MessageReceiverWithStatus> responder);
 };
 
 template <typename ImplRefTraits =
@@ -28,11 +29,11 @@
 
   bool AcceptWithResponder(
       mojo::Message* message,
-      mojo::MessageReceiverWithStatus* responder) override {
+      std::unique_ptr<mojo::MessageReceiverWithStatus> responder) override {
     if (ImplRefTraits::IsNull(sink_))
       return false;
     return {{interface.name}}StubDispatch::AcceptWithResponder(
-        ImplRefTraits::GetRawPointer(&sink_), message, responder);
+        ImplRefTraits::GetRawPointer(&sink_), message, std::move(responder));
   }
 
  private:
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl
index acdad5e..804a46b 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl
@@ -40,6 +40,7 @@
 #include <utility>
 
 #include "base/callback.h"
+#include "base/macros.h"
 #include "base/optional.h"
 #include "mojo/public/cpp/bindings/associated_interface_ptr.h"
 #include "mojo/public/cpp/bindings/associated_interface_ptr_info.h"
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl
index 9e6e46f..7ad9b4e 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl
@@ -73,7 +73,7 @@
                           UserType* output) {
     return mojo::internal::StructDeserializeImpl<
         {{struct.name}}::DataView, {{serialization_result_type}}>(
-            input, output);
+            input, output, Validate);
   }
 
 {#--- Struct members #}
@@ -83,8 +83,11 @@
   {{type}} {{name}};
 {%- endfor %}
 
-{%- if struct|contains_move_only_members %}
  private:
+  static bool Validate(const void* data,
+                       mojo::internal::ValidationContext* validation_context);
+
+{%- if struct|contains_move_only_members %}
   DISALLOW_COPY_AND_ASSIGN({{struct.name}});
 {%- endif %}
 };
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl
index e75543f..ab8c22d 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl
@@ -31,3 +31,9 @@
   return seed;
 }
 {%- endif %}
+
+bool {{struct.name}}::Validate(
+    const void* data,
+    mojo::internal::ValidationContext* validation_context) {
+  return Data_::Validate(data, validation_context);
+}
diff --git a/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl
index 54e2d4e..11e319c 100644
--- a/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl
@@ -93,7 +93,7 @@
 {%- if method.response_parameters != None %}
     case k{{interface.name}}_{{method.name}}_Name:
       var params = reader.decodeStruct({{interface.name}}_{{method.name}}_Params);
-      return this.{{method.name|stylize_method}}(
+      this.{{method.name|stylize_method}}(
 {%- for parameter in method.parameters -%}
 params.{{parameter.name}}{% if not loop.last %}, {% endif -%}
 {%- endfor %}).then(function(response) {
@@ -111,10 +111,11 @@
         var message = builder.finish();
         responder.accept(message);
       });
+      return true;
 {%- endif %}
 {%- endfor %}
     default:
-      return Promise.reject(Error("Unhandled message: " + reader.messageName));
+      return false;
     }
   };
 
diff --git a/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl b/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl
index 3ce4ab6..3637b19 100644
--- a/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl
@@ -2,26 +2,69 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+{%- if use_new_js_bindings %}
+
+'use strict';
+
+(function() {
+  var mojomId = '{{module.path}}';
+  if (mojo.internal.isMojomLoaded(mojomId)) {
+    console.warn('The following mojom is loaded multiple times: ' + mojomId);
+    return;
+  }
+  mojo.internal.markMojomLoaded(mojomId);
+
+  // TODO(yzshen): Define these aliases to minimize the differences between the
+  // old/new modes. Remove them when the old mode goes away.
+  var bindings = mojo;
+  var codec = mojo.internal;
+  var validator = mojo.internal;
+
+{%-   for import in imports %}
+  var {{import.unique_name}} =
+      mojo.internal.exposeNamespace('{{import.module.namespace}}');
+  if (mojo.config.autoLoadMojomDeps) {
+    mojo.internal.loadMojomIfNecessary(
+        '{{import.module.path}}',
+        new URL(
+            '{{import.module|get_relative_path(module)}}.js',
+            document.currentScript.src).href);
+  }
+{%-   endfor %}
+
+{% include "module_definition.tmpl" %}
+})();
+
+{%- else %}
+
 define("{{module.path}}", [
-{%- if module.path != "mojo/public/interfaces/bindings/interface_control_messages.mojom" %}
+{%- if module.path !=
+         "mojo/public/interfaces/bindings/interface_control_messages.mojom" and
+       module.path !=
+         "mojo/public/interfaces/bindings/pipe_control_messages.mojom" %}
     "mojo/public/js/bindings",
-{%- endif %}
+{%-   endif %}
     "mojo/public/js/codec",
     "mojo/public/js/core",
     "mojo/public/js/validator",
-{%- for import in imports %}
+{%-   for import in imports %}
     "{{import.module.path}}",
-{%- endfor %}
+{%-   endfor %}
 ], function(
-{%- if module.path != "mojo/public/interfaces/bindings/interface_control_messages.mojom" -%}
+{%- if module.path !=
+         "mojo/public/interfaces/bindings/interface_control_messages.mojom" and
+       module.path !=
+         "mojo/public/interfaces/bindings/pipe_control_messages.mojom" -%}
 bindings, {% endif -%}
 codec, core, validator
-{%- for import in imports -%}
+{%-   for import in imports -%}
     , {{import.unique_name}}
-{%- endfor -%}
+{%-   endfor -%}
 ) {
 
 {%- include "module_definition.tmpl" %}
 
   return exports;
 });
+
+{%- endif %}
diff --git a/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl
index ddfef72..a119ee9 100644
--- a/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl
@@ -1,5 +1,5 @@
 {#--- Constants #}
-{%-  for constant in module.constants %}
+{%- for constant in module.constants %}
   var {{constant.name}} = {{constant.value|expression_to_text}};
 {%- endfor %}
 
@@ -25,8 +25,13 @@
 {%-   include "interface_definition.tmpl" %}
 {%- endfor %}
 
+{%- if use_new_js_bindings %}
+  var exports = mojo.internal.exposeNamespace("{{module.namespace}}");
+{%- else %}
   var exports = {};
-{%-  for constant in module.constants %}
+{%- endif %}
+
+{%- for constant in module.constants %}
   exports.{{constant.name}} = {{constant.name}};
 {%- endfor %}
 {%- for enum in enums %}
@@ -41,10 +46,4 @@
 {%- for interface in interfaces %}
   exports.{{interface.name}} = {{interface.name}};
   exports.{{interface.name}}Ptr = {{interface.name}}Ptr;
-{#--- Interface Client #}
-{%-   if interface.client in interfaces|map(attribute='name') %}
-  exports.{{interface.name}}.client = {{interface.client}};
-{%-   elif interface.client in imported_interfaces %}
-  exports.{{interface.name}}.client = {{imported_interfaces[interface.client]}};
-{%-   endif %}
 {%- endfor %}
diff --git a/mojo/public/tools/bindings/generators/mojom_js_generator.py b/mojo/public/tools/bindings/generators/mojom_js_generator.py
index 0eedb31..ab9635e 100644
--- a/mojo/public/tools/bindings/generators/mojom_js_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_js_generator.py
@@ -7,6 +7,7 @@
 import mojom.generate.generator as generator
 import mojom.generate.module as mojom
 import mojom.generate.pack as pack
+import os
 from mojom.generate.template_expander import UseJinja
 
 _kind_to_javascript_default_value = {
@@ -324,6 +325,9 @@
 def IsEnumField(field):
   return mojom.IsEnumKind(field.kind)
 
+def GetRelativePath(module, base_module):
+  return os.path.relpath(module.path, os.path.dirname(base_module.path))
+
 
 class Generator(generator.Generator):
 
@@ -348,6 +352,7 @@
     "is_union_field": IsUnionField,
     "js_type": JavaScriptType,
     "payload_size": JavaScriptPayloadSize,
+    "get_relative_path": GetRelativePath,
     "stylize_method": generator.StudlyCapsToCamel,
     "union_decode_snippet": JavaScriptUnionDecodeSnippet,
     "union_encode_snippet": JavaScriptUnionEncodeSnippet,
@@ -368,6 +373,7 @@
       "module": self.module,
       "structs": self.GetStructs() + self.GetStructsFromMethods(),
       "unions": self.GetUnions(),
+      "use_new_js_bindings": self.use_new_js_bindings,
       "interfaces": self.GetInterfaces(),
       "imported_interfaces": self.GetImportedInterfaces(),
     }
diff --git a/mojo/public/tools/bindings/mojom.gni b/mojo/public/tools/bindings/mojom.gni
index 2466636..4a244fb 100644
--- a/mojo/public/tools/bindings/mojom.gni
+++ b/mojo/public/tools/bindings/mojom.gni
@@ -134,6 +134,13 @@
 #   cpp_only (optional)
 #       If set to true, only the C++ bindings targets will be generated.
 #
+#   use_new_js_bindings (optional)
+#       If set to true, the generated JS code will use the new module loading
+#       approach and the core API exposed by Web IDL.
+#
+#       TODO(yzshen): Switch all existing users to use_new_js_bindings=true and
+#       remove the old mode.
+#
 # The following parameters are used to support the component build. They are
 # needed so that bindings which are linked with a component can use the same
 # export settings for classes. The first three are for the chromium variant, and
@@ -434,6 +441,11 @@
         if (defined(invoker.use_once_callback) && invoker.use_once_callback) {
           args += [ "--use_once_callback" ]
         }
+
+        if (defined(invoker.use_new_js_bindings) &&
+            invoker.use_new_js_bindings) {
+          args += [ "--use_new_js_bindings" ]
+        }
       }
     }
 
diff --git a/mojo/public/tools/bindings/mojom_bindings_generator.py b/mojo/public/tools/bindings/mojom_bindings_generator.py
index a5fb51b..a9650d7 100755
--- a/mojo/public/tools/bindings/mojom_bindings_generator.py
+++ b/mojo/public/tools/bindings/mojom_bindings_generator.py
@@ -7,7 +7,7 @@
 
 
 import argparse
-import importlib
+import imp
 import json
 import os
 import pprint
@@ -43,9 +43,9 @@
 
 
 _BUILTIN_GENERATORS = {
-  "c++": "mojom_cpp_generator",
-  "javascript": "mojom_js_generator",
-  "java": "mojom_java_generator",
+  "c++": "mojom_cpp_generator.py",
+  "javascript": "mojom_js_generator.py",
+  "java": "mojom_java_generator.py",
 }
 
 
@@ -57,11 +57,14 @@
   generators = {}
   for generator_name in [s.strip() for s in generators_string.split(",")]:
     language = generator_name.lower()
-    if language not in _BUILTIN_GENERATORS:
+    if language in _BUILTIN_GENERATORS:
+      generator_name = os.path.join(script_dir, "generators",
+                                    _BUILTIN_GENERATORS[language])
+    else:
       print "Unknown generator name %s" % generator_name
       sys.exit(1)
-    generator_module = importlib.import_module(
-        "generators.%s" % _BUILTIN_GENERATORS[language])
+    generator_module = imp.load_source(os.path.basename(generator_name)[:-3],
+                                       generator_name)
     generators[language] = generator_module
   return generators
 
@@ -164,6 +167,7 @@
             variant=args.variant, bytecode_path=args.bytecode_path,
             for_blink=args.for_blink,
             use_once_callback=args.use_once_callback,
+            use_new_js_bindings=args.use_new_js_bindings,
             export_attribute=args.export_attribute,
             export_header=args.export_header,
             generate_non_variant_code=args.generate_non_variant_code)
@@ -295,6 +299,10 @@
       "--use_once_callback", action="store_true",
       help="Use base::OnceCallback instead of base::RepeatingCallback.")
   generate_parser.add_argument(
+      "--use_new_js_bindings", action="store_true",
+      help="Use the new module loading approach and the core API exposed by "
+      "Web IDL. This option only affects the JavaScript bindings.")
+  generate_parser.add_argument(
       "--export_attribute", type=str, default="",
       help="Optional attribute to specify on class declaration to export it "
       "for the component build.")
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/generator.py b/mojo/public/tools/bindings/pylib/mojom/generate/generator.py
index e4ab373..0e64af7 100644
--- a/mojo/public/tools/bindings/pylib/mojom/generate/generator.py
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/generator.py
@@ -38,8 +38,8 @@
   # files to stdout.
   def __init__(self, module, output_dir=None, typemap=None, variant=None,
                bytecode_path=None, for_blink=False, use_once_callback=False,
-               export_attribute=None, export_header=None,
-               generate_non_variant_code=False):
+               use_new_js_bindings=False, export_attribute=None,
+               export_header=None, generate_non_variant_code=False):
     self.module = module
     self.output_dir = output_dir
     self.typemap = typemap or {}
@@ -47,6 +47,7 @@
     self.bytecode_path = bytecode_path
     self.for_blink = for_blink
     self.use_once_callback = use_once_callback
+    self.use_new_js_bindings = use_new_js_bindings
     self.export_attribute = export_attribute
     self.export_header = export_header
     self.generate_non_variant_code = generate_non_variant_code