Create a RegisterViewAssociate method in ViewManager
Add tests for ViewManager and Views

#703

R=jeffbrown@google.com

Review URL: https://codereview.chromium.org/1949233002 .
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/ui/view_associates.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/ui/view_associates.mojom.dart
index eeed7cf..96f3d2a 100644
--- a/mojo/dart/packages/mojo_services/lib/mojo/ui/view_associates.mojom.dart
+++ b/mojo/dart/packages/mojo_services/lib/mojo/ui/view_associates.mojom.dart
@@ -1088,6 +1088,183 @@
   }
 }
 
+
+class _ViewAssociateOwnerServiceDescription implements service_describer.ServiceDescription {
+  dynamic getTopLevelInterface([Function responseFactory]) =>
+      responseFactory(null);
+
+  dynamic getTypeDefinition(String typeKey, [Function responseFactory]) =>
+      responseFactory(null);
+
+  dynamic getAllTypeDefinitions([Function responseFactory]) =>
+      responseFactory(null);
+}
+
+abstract class ViewAssociateOwner {
+  static const String serviceName = "mojo::ui::ViewAssociateOwner";
+}
+
+class _ViewAssociateOwnerProxyControl
+    extends bindings.ProxyMessageHandler
+    implements bindings.ProxyControl {
+  _ViewAssociateOwnerProxyControl.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) : super.fromEndpoint(endpoint);
+
+  _ViewAssociateOwnerProxyControl.fromHandle(
+      core.MojoHandle handle) : super.fromHandle(handle);
+
+  _ViewAssociateOwnerProxyControl.unbound() : super.unbound();
+
+  service_describer.ServiceDescription get serviceDescription =>
+      new _ViewAssociateOwnerServiceDescription();
+
+  String get serviceName => ViewAssociateOwner.serviceName;
+
+  void handleResponse(bindings.ServiceMessage message) {
+    switch (message.header.type) {
+      default:
+        proxyError("Unexpected message type: ${message.header.type}");
+        close(immediate: true);
+        break;
+    }
+  }
+
+  @override
+  String toString() {
+    var superString = super.toString();
+    return "_ViewAssociateOwnerProxyControl($superString)";
+  }
+}
+
+class ViewAssociateOwnerProxy
+    extends bindings.Proxy
+    implements ViewAssociateOwner {
+  ViewAssociateOwnerProxy.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint)
+      : super(new _ViewAssociateOwnerProxyControl.fromEndpoint(endpoint));
+
+  ViewAssociateOwnerProxy.fromHandle(core.MojoHandle handle)
+      : super(new _ViewAssociateOwnerProxyControl.fromHandle(handle));
+
+  ViewAssociateOwnerProxy.unbound()
+      : super(new _ViewAssociateOwnerProxyControl.unbound());
+
+  static ViewAssociateOwnerProxy newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For ViewAssociateOwnerProxy"));
+    return new ViewAssociateOwnerProxy.fromEndpoint(endpoint);
+  }
+
+  factory ViewAssociateOwnerProxy.connectToService(
+      bindings.ServiceConnector s, String url, [String serviceName]) {
+    ViewAssociateOwnerProxy p = new ViewAssociateOwnerProxy.unbound();
+    s.connectToService(url, p, serviceName);
+    return p;
+  }
+
+
+}
+
+class _ViewAssociateOwnerStubControl
+    extends bindings.StubMessageHandler
+    implements bindings.StubControl<ViewAssociateOwner> {
+  ViewAssociateOwner _impl;
+
+  _ViewAssociateOwnerStubControl.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint, [ViewAssociateOwner impl])
+      : super.fromEndpoint(endpoint, autoBegin: impl != null) {
+    _impl = impl;
+  }
+
+  _ViewAssociateOwnerStubControl.fromHandle(
+      core.MojoHandle handle, [ViewAssociateOwner impl])
+      : super.fromHandle(handle, autoBegin: impl != null) {
+    _impl = impl;
+  }
+
+  _ViewAssociateOwnerStubControl.unbound([this._impl]) : super.unbound();
+
+
+
+  dynamic handleMessage(bindings.ServiceMessage message) {
+    if (bindings.ControlMessageHandler.isControlMessage(message)) {
+      return bindings.ControlMessageHandler.handleMessage(this,
+                                                          0,
+                                                          message);
+    }
+    if (_impl == null) {
+      throw new core.MojoApiError("$this has no implementation set");
+    }
+    switch (message.header.type) {
+      default:
+        throw new bindings.MojoCodecError("Unexpected message name");
+        break;
+    }
+    return null;
+  }
+
+  ViewAssociateOwner get impl => _impl;
+  set impl(ViewAssociateOwner d) {
+    if (d == null) {
+      throw new core.MojoApiError("$this: Cannot set a null implementation");
+    }
+    if (isBound && (_impl == null)) {
+      beginHandlingEvents();
+    }
+    _impl = d;
+  }
+
+  @override
+  void bind(core.MojoMessagePipeEndpoint endpoint) {
+    super.bind(endpoint);
+    if (!isOpen && (_impl != null)) {
+      beginHandlingEvents();
+    }
+  }
+
+  @override
+  String toString() {
+    var superString = super.toString();
+    return "_ViewAssociateOwnerStubControl($superString)";
+  }
+
+  int get version => 0;
+
+  static service_describer.ServiceDescription _cachedServiceDescription;
+  static service_describer.ServiceDescription get serviceDescription {
+    if (_cachedServiceDescription == null) {
+      _cachedServiceDescription = new _ViewAssociateOwnerServiceDescription();
+    }
+    return _cachedServiceDescription;
+  }
+}
+
+class ViewAssociateOwnerStub
+    extends bindings.Stub<ViewAssociateOwner>
+    implements ViewAssociateOwner {
+  ViewAssociateOwnerStub.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint, [ViewAssociateOwner impl])
+      : super(new _ViewAssociateOwnerStubControl.fromEndpoint(endpoint, impl));
+
+  ViewAssociateOwnerStub.fromHandle(
+      core.MojoHandle handle, [ViewAssociateOwner impl])
+      : super(new _ViewAssociateOwnerStubControl.fromHandle(handle, impl));
+
+  ViewAssociateOwnerStub.unbound([ViewAssociateOwner impl])
+      : super(new _ViewAssociateOwnerStubControl.unbound(impl));
+
+  static ViewAssociateOwnerStub newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For ViewAssociateOwnerStub"));
+    return new ViewAssociateOwnerStub.fromEndpoint(endpoint);
+  }
+
+  static service_describer.ServiceDescription get serviceDescription =>
+      _ViewAssociateOwnerStubControl.serviceDescription;
+
+
+}
+
 const int _viewInspectorMethodGetHitTesterName = 0;
 const int _viewInspectorMethodResolveScenesName = 1;
 
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/ui/view_manager.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/ui/view_manager.mojom.dart
index a8dc87f..a970dfa 100644
--- a/mojo/dart/packages/mojo_services/lib/mojo/ui/view_manager.mojom.dart
+++ b/mojo/dart/packages/mojo_services/lib/mojo/ui/view_manager.mojom.dart
@@ -7,6 +7,7 @@
 import 'package:mojo/bindings.dart' as bindings;
 import 'package:mojo/core.dart' as core;
 import 'package:mojo/mojo/bindings/types/service_describer.mojom.dart' as service_describer;
+import 'package:mojo_services/mojo/ui/view_associates.mojom.dart' as view_associates_mojom;
 import 'package:mojo_services/mojo/ui/view_token.mojom.dart' as view_token_mojom;
 import 'package:mojo_services/mojo/ui/view_trees.mojom.dart' as view_trees_mojom;
 import 'package:mojo_services/mojo/ui/views.mojom.dart' as views_mojom;
@@ -220,8 +221,165 @@
   }
 }
 
+
+class _ViewManagerRegisterViewAssociateParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(32, 0)
+  ];
+  Object viewAssociate = null;
+  Object viewAssociateOwner = null;
+  String label = null;
+
+  _ViewManagerRegisterViewAssociateParams() : super(kVersions.last.size);
+
+  static _ViewManagerRegisterViewAssociateParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _ViewManagerRegisterViewAssociateParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _ViewManagerRegisterViewAssociateParams result = new _ViewManagerRegisterViewAssociateParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.viewAssociate = decoder0.decodeServiceInterface(8, false, view_associates_mojom.ViewAssociateProxy.newFromEndpoint);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.viewAssociateOwner = decoder0.decodeInterfaceRequest(16, false, view_associates_mojom.ViewAssociateOwnerStub.newFromEndpoint);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.label = decoder0.decodeString(24, true);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    try {
+      encoder0.encodeInterface(viewAssociate, 8, false);
+    } on bindings.MojoCodecError catch(e) {
+      e.message = "Error encountered while encoding field "
+          "viewAssociate of struct _ViewManagerRegisterViewAssociateParams: $e";
+      rethrow;
+    }
+    try {
+      encoder0.encodeInterfaceRequest(viewAssociateOwner, 16, false);
+    } on bindings.MojoCodecError catch(e) {
+      e.message = "Error encountered while encoding field "
+          "viewAssociateOwner of struct _ViewManagerRegisterViewAssociateParams: $e";
+      rethrow;
+    }
+    try {
+      encoder0.encodeString(label, 24, true);
+    } on bindings.MojoCodecError catch(e) {
+      e.message = "Error encountered while encoding field "
+          "label of struct _ViewManagerRegisterViewAssociateParams: $e";
+      rethrow;
+    }
+  }
+
+  String toString() {
+    return "_ViewManagerRegisterViewAssociateParams("
+           "viewAssociate: $viewAssociate" ", "
+           "viewAssociateOwner: $viewAssociateOwner" ", "
+           "label: $label" ")";
+  }
+
+  Map toJson() {
+    throw new bindings.MojoCodecError(
+        'Object containing handles cannot be encoded to JSON.');
+  }
+}
+
+
+class _ViewManagerFinishedRegisteringViewAssociatesParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(8, 0)
+  ];
+
+  _ViewManagerFinishedRegisteringViewAssociatesParams() : super(kVersions.last.size);
+
+  static _ViewManagerFinishedRegisteringViewAssociatesParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _ViewManagerFinishedRegisteringViewAssociatesParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _ViewManagerFinishedRegisteringViewAssociatesParams result = new _ViewManagerFinishedRegisteringViewAssociatesParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    encoder.getStructEncoderAtOffset(kVersions.last);
+  }
+
+  String toString() {
+    return "_ViewManagerFinishedRegisteringViewAssociatesParams("")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    return map;
+  }
+}
+
 const int _viewManagerMethodCreateViewName = 0;
 const int _viewManagerMethodCreateViewTreeName = 1;
+const int _viewManagerMethodRegisterViewAssociateName = 2;
+const int _viewManagerMethodFinishedRegisteringViewAssociatesName = 3;
 
 class _ViewManagerServiceDescription implements service_describer.ServiceDescription {
   dynamic getTopLevelInterface([Function responseFactory]) =>
@@ -238,6 +396,8 @@
   static const String serviceName = "mojo::ui::ViewManager";
   void createView(Object view, Object viewOwner, Object viewListener, String label);
   void createViewTree(Object viewTree, Object viewTreeListener, String label);
+  void registerViewAssociate(Object viewAssociate, Object viewAssociateOwner, String label);
+  void finishedRegisteringViewAssociates();
 }
 
 class _ViewManagerProxyControl
@@ -324,6 +484,27 @@
     ctrl.sendMessage(params,
         _viewManagerMethodCreateViewTreeName);
   }
+  void registerViewAssociate(Object viewAssociate, Object viewAssociateOwner, String label) {
+    if (!ctrl.isBound) {
+      ctrl.proxyError("The Proxy is closed.");
+      return;
+    }
+    var params = new _ViewManagerRegisterViewAssociateParams();
+    params.viewAssociate = viewAssociate;
+    params.viewAssociateOwner = viewAssociateOwner;
+    params.label = label;
+    ctrl.sendMessage(params,
+        _viewManagerMethodRegisterViewAssociateName);
+  }
+  void finishedRegisteringViewAssociates() {
+    if (!ctrl.isBound) {
+      ctrl.proxyError("The Proxy is closed.");
+      return;
+    }
+    var params = new _ViewManagerFinishedRegisteringViewAssociatesParams();
+    ctrl.sendMessage(params,
+        _viewManagerMethodFinishedRegisteringViewAssociatesName);
+  }
 }
 
 class _ViewManagerStubControl
@@ -367,6 +548,14 @@
             message.payload);
         _impl.createViewTree(params.viewTree, params.viewTreeListener, params.label);
         break;
+      case _viewManagerMethodRegisterViewAssociateName:
+        var params = _ViewManagerRegisterViewAssociateParams.deserialize(
+            message.payload);
+        _impl.registerViewAssociate(params.viewAssociate, params.viewAssociateOwner, params.label);
+        break;
+      case _viewManagerMethodFinishedRegisteringViewAssociatesName:
+        _impl.finishedRegisteringViewAssociates();
+        break;
       default:
         throw new bindings.MojoCodecError("Unexpected message name");
         break;
@@ -440,6 +629,12 @@
   void createViewTree(Object viewTree, Object viewTreeListener, String label) {
     return impl.createViewTree(viewTree, viewTreeListener, label);
   }
+  void registerViewAssociate(Object viewAssociate, Object viewAssociateOwner, String label) {
+    return impl.registerViewAssociate(viewAssociate, viewAssociateOwner, label);
+  }
+  void finishedRegisteringViewAssociates() {
+    return impl.finishedRegisteringViewAssociates();
+  }
 }
 
 
diff --git a/mojo/services/ui/views/interfaces/view_associates.mojom b/mojo/services/ui/views/interfaces/view_associates.mojom
index 8fcac9c..a1d575e 100644
--- a/mojo/services/ui/views/interfaces/view_associates.mojom
+++ b/mojo/services/ui/views/interfaces/view_associates.mojom
@@ -27,11 +27,6 @@
 // This mechanism is designed to avoid a potential explosion in complexity
 // if all features which depend on the state of views were implemented
 // in one place.
-//
-// TODO(jeffbrown): In the current implementation, the view manager binds
-// to a hard coded set of associates at start up time which can be overridden
-// from the command-line.  We should find a better way to register associates
-// once we decide how the system as a whole should be initialized.
 [ServiceName="mojo::ui::ViewAssociate"]
 interface ViewAssociate {
   // Connects to the associate.
@@ -65,6 +60,15 @@
                            handle<message_pipe> pipe);
 };
 
+// ViewAssociateOwner is used as a transferable reference which can
+// be passed to a ViewAssociate's intended container as part of a request to
+// register the ViewAssociate with the ViewManager. The ViewAssociateOwner will
+// get a connection error callback if the corresponding ViewAssociate is
+// destroyed, and similarly the ViewAssociate that it owns will be killed if
+// this object is destroyed.
+[ServiceName="mojo::ui::ViewAssociateOwner"]
+interface ViewAssociateOwner {};
+
 // Provides information about the services offered by an associate.
 struct ViewAssociateInfo {
   // The names of view services offered by the associate.
diff --git a/mojo/services/ui/views/interfaces/view_manager.mojom b/mojo/services/ui/views/interfaces/view_manager.mojom
index 6592ea9..68487bc 100644
--- a/mojo/services/ui/views/interfaces/view_manager.mojom
+++ b/mojo/services/ui/views/interfaces/view_manager.mojom
@@ -67,4 +67,30 @@
   CreateViewTree(mojo.ui.ViewTree& view_tree,
                  mojo.ui.ViewTreeListener view_tree_listener,
                  string? label);
+
+  // Registers a view associate with the view manager.
+  //
+  // The services provided by |view_associate| will be made available to views.
+  //
+  // You receive a |view_associate_owner| which will receive a connection error
+  // callback if |view_associate| is closed. Conversely, the service for
+  // |view_associate| will be destroyed if |view_associate_owner| is destroyed.
+  //
+  // |label| is used to identify the ViewAssociate for debug purposes.
+  //
+  // TODO(mikejurka): This method should only be made accessible to trusted
+  // services, once we have a security story
+  RegisterViewAssociate(mojo.ui.ViewAssociate view_associate,
+    mojo.ui.ViewAssociateOwner& view_associate_owner, string? label);
+
+  // Tells the view manager we've registered all our view associates.
+  //
+  // This allows it to prevent views from requesting services from ViewAssociate
+  // before all the ViewAssociates have been registered.
+  //
+  // TODO(mikejurka): This method should only be made accessible to trusted
+  // services, once we have a security story
+  // TODO(mikejurka): We should remove this method once we have a way of
+  // dynamically registering and unregistering ViewAssociates
+  FinishedRegisteringViewAssociates();
 };
diff --git a/mojo/tools/data/apptests b/mojo/tools/data/apptests
index b91dae3..9a8af7c 100644
--- a/mojo/tools/data/apptests
+++ b/mojo/tools/data/apptests
@@ -64,6 +64,9 @@
     "test": "mojo:ui_associates_apptests",
   },
   {
+    "test": "mojo:view_manager_apptests",
+  },
+  {
     "test": "mojo:url_response_disk_cache_apptests",
   },
   {
diff --git a/services/BUILD.gn b/services/BUILD.gn
index 6d4e00b..bfd560a 100644
--- a/services/BUILD.gn
+++ b/services/BUILD.gn
@@ -85,6 +85,7 @@
     "//services/media/common:apptests",
     "//services/native_support:apptests",
     "//services/prediction:apptests",
+    "//services/ui/view_manager:view_manager_apptests",
   ]
 
   # TODO: fix ffmpeg build
diff --git a/services/gfx/compositor/compositor_engine.cc b/services/gfx/compositor/compositor_engine.cc
index 1e18866..8ad5b0f 100644
--- a/services/gfx/compositor/compositor_engine.cc
+++ b/services/gfx/compositor/compositor_engine.cc
@@ -83,14 +83,18 @@
   }
 
   // Destroy any renderers using this scene.
+  std::vector<RendererState*> renderers_to_destroy;
   for (auto& renderer : renderers_) {
     if (renderer->root_scene() == scene_state) {
-      LOG(ERROR) << "Destroying renderer whose root scene has become "
-                    "unavailable: renderer="
-                 << renderer;
-      DestroyRenderer(renderer);
+      renderers_to_destroy.emplace_back(renderer);
     }
   }
+  for (auto& renderer : renderers_to_destroy) {
+    LOG(ERROR) << "Destroying renderer whose root scene has become "
+                  "unavailable: renderer="
+               << renderer;
+    DestroyRenderer(renderer);
+  }
 
   // Consider all dependent rendering to be invalidated.
   universe_.RemoveScene(scene_state->scene_token());
diff --git a/services/ui/launcher/launch_instance.cc b/services/ui/launcher/launch_instance.cc
index c2aa2cb..82169cb 100644
--- a/services/ui/launcher/launch_instance.cc
+++ b/services/ui/launcher/launch_instance.cc
@@ -4,6 +4,7 @@
 
 #include "services/ui/launcher/launch_instance.h"
 
+#include "base/bind.h"
 #include "base/command_line.h"
 #include "base/logging.h"
 #include "base/trace_event/trace_event.h"
@@ -18,9 +19,13 @@
 
 LaunchInstance::LaunchInstance(mojo::ApplicationImpl* app_impl,
                                const std::string& app_url,
+                               mojo::gfx::composition::Compositor* compositor,
+                               mojo::ui::ViewManager* view_manager,
                                const base::Closure& shutdown_callback)
     : app_impl_(app_impl),
       app_url_(app_url),
+      compositor_(compositor),
+      view_manager_(view_manager),
       shutdown_callback_(shutdown_callback),
       viewport_event_dispatcher_binding_(this) {}
 
@@ -30,16 +35,6 @@
   DVLOG(1) << "Launching " << app_url_;
   TRACE_EVENT0("launcher", __func__);
 
-  mojo::ConnectToService(app_impl_->shell(), "mojo:compositor_service",
-                         GetProxy(&compositor_));
-  compositor_.set_connection_error_handler(base::Bind(
-      &LaunchInstance::OnCompositorConnectionError, base::Unretained(this)));
-
-  mojo::ConnectToService(app_impl_->shell(), "mojo:view_manager_service",
-                         GetProxy(&view_manager_));
-  view_manager_.set_connection_error_handler(base::Bind(
-      &LaunchInstance::OnViewManagerConnectionError, base::Unretained(this)));
-
   InitViewport();
 
   mojo::ui::ViewProviderPtr client_view_provider;
@@ -50,16 +45,6 @@
                                    nullptr);
 }
 
-void LaunchInstance::OnCompositorConnectionError() {
-  LOG(ERROR) << "Exiting due to compositor connection error.";
-  shutdown_callback_.Run();
-}
-
-void LaunchInstance::OnViewManagerConnectionError() {
-  LOG(ERROR) << "Exiting due to view manager connection error.";
-  shutdown_callback_.Run();
-}
-
 void LaunchInstance::InitViewport() {
   mojo::ConnectToService(app_impl_->shell(), "mojo:native_viewport_service",
                          GetProxy(&viewport_));
@@ -92,7 +77,7 @@
   mojo::ContextProviderPtr context_provider;
   viewport_->GetContextProvider(GetProxy(&context_provider));
 
-  view_tree_.reset(new LauncherViewTree(compositor_.get(), view_manager_.get(),
+  view_tree_.reset(new LauncherViewTree(compositor_, view_manager_,
                                         context_provider.Pass(), metrics.Pass(),
                                         shutdown_callback_));
   view_tree_->SetRoot(client_view_owner_.Pass());
diff --git a/services/ui/launcher/launch_instance.h b/services/ui/launcher/launch_instance.h
index 644f31d..6a9c71b 100644
--- a/services/ui/launcher/launch_instance.h
+++ b/services/ui/launcher/launch_instance.h
@@ -24,6 +24,8 @@
  public:
   LaunchInstance(mojo::ApplicationImpl* app_impl,
                  const std::string& app_url,
+                 mojo::gfx::composition::Compositor* compositor,
+                 mojo::ui::ViewManager* view_manager,
                  const base::Closure& shutdown_callback);
   ~LaunchInstance() override;
 
@@ -34,9 +36,6 @@
   void OnEvent(mojo::EventPtr event,
                const mojo::Callback<void()>& callback) override;
 
-  void OnCompositorConnectionError();
-  void OnViewManagerConnectionError();
-
   void InitViewport();
   void OnViewportConnectionError();
   void OnViewportCreated(mojo::ViewportMetricsPtr metrics);
@@ -45,10 +44,10 @@
 
   mojo::ApplicationImpl* app_impl_;
   std::string app_url_;
-  base::Closure shutdown_callback_;
 
-  mojo::gfx::composition::CompositorPtr compositor_;
-  mojo::ui::ViewManagerPtr view_manager_;
+  mojo::gfx::composition::Compositor* compositor_;
+  mojo::ui::ViewManager* view_manager_;
+  base::Closure shutdown_callback_;
 
   mojo::NativeViewportPtr viewport_;
   mojo::Binding<NativeViewportEventDispatcher>
diff --git a/services/ui/launcher/launcher_app.cc b/services/ui/launcher/launcher_app.cc
index cc140a7..d301cc1 100644
--- a/services/ui/launcher/launcher_app.cc
+++ b/services/ui/launcher/launcher_app.cc
@@ -6,11 +6,13 @@
 
 #include "base/command_line.h"
 #include "base/logging.h"
+#include "base/strings/string_split.h"
 #include "base/trace_event/trace_event.h"
 #include "mojo/application/application_runner_chromium.h"
 #include "mojo/common/tracing_impl.h"
 #include "mojo/public/c/system/main.h"
 #include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/connect.h"
 #include "mojo/public/cpp/application/service_provider_impl.h"
 
 namespace launcher {
@@ -31,11 +33,64 @@
   tracing_.Initialize(app_impl_);
   TRACE_EVENT0("launcher", __func__);
 
+  InitCompositor();
+  InitViewManager();
+  InitViewAssociates(command_line->GetSwitchValueASCII("view_associate_urls"));
+
   for (size_t i = 0; i < command_line->GetArgs().size(); ++i) {
     Launch(command_line->GetArgs()[i]);
   }
 }
 
+void LauncherApp::InitCompositor() {
+  mojo::ConnectToService(app_impl_->shell(), "mojo:compositor_service",
+                         GetProxy(&compositor_));
+  compositor_.set_connection_error_handler(base::Bind(
+      &LauncherApp::OnCompositorConnectionError, base::Unretained(this)));
+}
+
+void LauncherApp::InitViewManager() {
+  mojo::ConnectToService(app_impl_->shell(), "mojo:view_manager_service",
+                         GetProxy(&view_manager_));
+  view_manager_.set_connection_error_handler(base::Bind(
+      &LauncherApp::OnViewManagerConnectionError, base::Unretained(this)));
+}
+
+void LauncherApp::InitViewAssociates(
+    const std::string& associate_urls_command_line_param) {
+  // Build up the list of ViewAssociates we are going to start
+  auto associate_urls =
+      SplitString(associate_urls_command_line_param, ",", base::KEEP_WHITESPACE,
+                  base::SPLIT_WANT_ALL);
+
+  // If there's nothing we got from the command line, use our own list
+  if (associate_urls.empty()) {
+    // TODO(jeffbrown): Replace this hardcoded list.
+    associate_urls.push_back("mojo:input_manager_service");
+  }
+
+  view_associate_owners_.reserve(associate_urls.size());
+
+  // Connect to ViewAssociates.
+  for (const auto& url : associate_urls) {
+    // Connect to the ViewAssociate.
+    DVLOG(2) << "Connecting to ViewAssociate " << url;
+    mojo::ui::ViewAssociatePtr view_associate;
+    mojo::ConnectToService(app_impl_->shell(), url, GetProxy(&view_associate));
+
+    // Wire up the associate to the ViewManager.
+    mojo::ui::ViewAssociateOwnerPtr view_associate_owner;
+    view_manager_->RegisterViewAssociate(view_associate.Pass(),
+                                         GetProxy(&view_associate_owner), url);
+
+    view_associate_owner.set_connection_error_handler(base::Bind(
+        &LauncherApp::OnViewAssociateConnectionError, base::Unretained(this)));
+
+    view_associate_owners_.push_back(view_associate_owner.Pass());
+  }
+  view_manager_->FinishedRegisteringViewAssociates();
+}
+
 bool LauncherApp::ConfigureIncomingConnection(
     mojo::ServiceProviderImpl* service_provider_impl) {
   // Only present the launcher interface to the shell.
@@ -52,8 +107,9 @@
 void LauncherApp::Launch(const mojo::String& application_url) {
   uint32_t next_id = next_id_++;
   std::unique_ptr<LaunchInstance> instance(new LaunchInstance(
-      app_impl_, application_url, base::Bind(&LauncherApp::OnLaunchTermination,
-                                             base::Unretained(this), next_id)));
+      app_impl_, application_url, compositor_.get(), view_manager_.get(),
+      base::Bind(&LauncherApp::OnLaunchTermination, base::Unretained(this),
+                 next_id)));
   instance->Launch();
   launch_instances_.emplace(next_id, std::move(instance));
 }
@@ -65,4 +121,19 @@
   }
 }
 
+void LauncherApp::OnCompositorConnectionError() {
+  LOG(ERROR) << "Exiting due to compositor connection error.";
+  app_impl_->Terminate();
+}
+
+void LauncherApp::OnViewManagerConnectionError() {
+  LOG(ERROR) << "Exiting due to view manager connection error.";
+  app_impl_->Terminate();
+}
+
+void LauncherApp::OnViewAssociateConnectionError() {
+  LOG(ERROR) << "Exiting due to view associate connection error.";
+  app_impl_->Terminate();
+};
+
 }  // namespace launcher
diff --git a/services/ui/launcher/launcher_app.h b/services/ui/launcher/launcher_app.h
index dcf0d48..f04e645 100644
--- a/services/ui/launcher/launcher_app.h
+++ b/services/ui/launcher/launcher_app.h
@@ -24,6 +24,9 @@
  private:
   // |ApplicationDelegate|:
   void Initialize(mojo::ApplicationImpl* app_impl) override;
+  void InitCompositor();
+  void InitViewManager();
+  void InitViewAssociates(const std::string& associate_urls_command_line_param);
   bool ConfigureIncomingConnection(
       mojo::ServiceProviderImpl* service_provider_impl) override;
 
@@ -32,14 +35,23 @@
 
   void OnLaunchTermination(uint32_t id);
 
+  void OnCompositorConnectionError();
+  void OnViewManagerConnectionError();
+  void OnViewAssociateConnectionError();
+
   mojo::ApplicationImpl* app_impl_;
   mojo::TracingImpl tracing_;
 
   mojo::BindingSet<Launcher> bindings_;
   std::unordered_map<uint32_t, std::unique_ptr<LaunchInstance>>
       launch_instances_;
+
   uint32_t next_id_;
 
+  mojo::gfx::composition::CompositorPtr compositor_;
+  mojo::ui::ViewManagerPtr view_manager_;
+  std::vector<mojo::ui::ViewAssociateOwnerPtr> view_associate_owners_;
+
   DISALLOW_COPY_AND_ASSIGN(LauncherApp);
 };
 
diff --git a/services/ui/view_manager/BUILD.gn b/services/ui/view_manager/BUILD.gn
index b55b429..6b892ce 100644
--- a/services/ui/view_manager/BUILD.gn
+++ b/services/ui/view_manager/BUILD.gn
@@ -5,11 +5,8 @@
 import("//mojo/public/mojo_application.gni")
 import("//testing/test.gni")
 
-mojo_native_application("view_manager") {
-  output_name = "view_manager_service"
-
+source_set("common") {
   sources = [
-    "main.cc",
     "view_associate_table.cc",
     "view_associate_table.h",
     "view_container_state.cc",
@@ -32,13 +29,12 @@
     "view_tree_state.h",
   ]
 
-  deps = [
+  public_deps = [
     "//base",
     "//mojo/application",
     "//mojo/common",
     "//mojo/common:tracing_impl",
     "//mojo/converters/geometry",
-    "//mojo/environment:chromium",
     "//mojo/public/cpp/bindings:bindings",
     "//mojo/services/geometry/cpp",
     "//mojo/services/gfx/composition/cpp",
@@ -47,3 +43,40 @@
     "//mojo/services/ui/views/interfaces",
   ]
 }
+
+mojo_native_application("view_manager") {
+  output_name = "view_manager_service"
+
+  sources = [
+    "main.cc",
+  ]
+
+  deps = [
+    ":common",
+    "//mojo/environment:chromium",
+  ]
+}
+
+mojo_native_application("view_manager_apptests") {
+  output_name = "view_manager_apptests"
+
+  testonly = true
+
+  sources = [
+    "tests/mock_view_associate.cc",
+    "tests/mock_view_associate.h",
+    "tests/view_associate_table_test.cc",
+    "tests/view_manager_test.cc",
+    "tests/view_manager_test_base.cc",
+    "tests/view_manager_test_base.h",
+  ]
+
+  deps = [
+    ":common",
+    "//base",
+    "//mojo/application:test_support",
+    "//mojo/public/cpp/bindings",
+    "//mojo/public/cpp/bindings:bindings",
+    "//mojo/ui/associates:mocks",
+  ]
+}
diff --git a/services/ui/view_manager/tests/mock_view_associate.cc b/services/ui/view_manager/tests/mock_view_associate.cc
new file mode 100644
index 0000000..4c464bf
--- /dev/null
+++ b/services/ui/view_manager/tests/mock_view_associate.cc
@@ -0,0 +1,33 @@
+// 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 "services/ui/view_manager/tests/mock_view_associate.h"
+
+namespace view_manager {
+namespace test {
+
+MockViewAssociate::MockViewAssociate() {}
+MockViewAssociate::~MockViewAssociate() {}
+
+void MockViewAssociate::Connect(
+    mojo::InterfaceHandle<mojo::ui::ViewInspector> inspector,
+    const ConnectCallback& callback) {
+  connect_invokecount++;
+
+  auto info = mojo::ui::ViewAssociateInfo::New();
+  callback.Run(info.Pass());
+}
+
+void MockViewAssociate::ConnectToViewService(
+    mojo::ui::ViewTokenPtr view_token,
+    const mojo::String& service_name,
+    mojo::ScopedMessagePipeHandle client_handle) {}
+
+void MockViewAssociate::ConnectToViewTreeService(
+    mojo::ui::ViewTreeTokenPtr view_tree_token,
+    const mojo::String& service_name,
+    mojo::ScopedMessagePipeHandle client_handle) {}
+
+}  // namespace test
+}  // namespace view_manager
diff --git a/services/ui/view_manager/tests/mock_view_associate.h b/services/ui/view_manager/tests/mock_view_associate.h
new file mode 100644
index 0000000..337348c
--- /dev/null
+++ b/services/ui/view_manager/tests/mock_view_associate.h
@@ -0,0 +1,33 @@
+// 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/services/ui/views/interfaces/view_manager.mojom.h"
+#include "mojo/services/ui/views/interfaces/views.mojom.h"
+
+namespace view_manager {
+namespace test {
+
+class MockViewAssociate : public mojo::ui::ViewAssociate {
+ public:
+  MockViewAssociate();
+  ~MockViewAssociate() override;
+
+  void Connect(mojo::InterfaceHandle<mojo::ui::ViewInspector> inspector,
+               const ConnectCallback& callback) override;
+
+  void ConnectToViewService(
+      mojo::ui::ViewTokenPtr view_token,
+      const mojo::String& service_name,
+      mojo::ScopedMessagePipeHandle client_handle) override;
+
+  void ConnectToViewTreeService(
+      mojo::ui::ViewTreeTokenPtr view_tree_token,
+      const mojo::String& service_name,
+      mojo::ScopedMessagePipeHandle client_handle) override;
+
+  int connect_invokecount = 0;
+};
+
+}  // namespace test
+}  // namespace view_manager
diff --git a/services/ui/view_manager/tests/view_associate_table_test.cc b/services/ui/view_manager/tests/view_associate_table_test.cc
new file mode 100644
index 0000000..01eb4b3
--- /dev/null
+++ b/services/ui/view_manager/tests/view_associate_table_test.cc
@@ -0,0 +1,58 @@
+// 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/services/ui/views/interfaces/view_manager.mojom.h"
+#include "mojo/services/ui/views/interfaces/views.mojom.h"
+#include "mojo/ui/associates/mock_view_inspector.h"
+#include "services/ui/view_manager/tests/mock_view_associate.h"
+#include "services/ui/view_manager/tests/view_manager_test_base.h"
+#include "services/ui/view_manager/view_associate_table.h"
+
+namespace view_manager {
+namespace test {
+
+class ViewAssociateTableTest : public ViewManagerTestBase {
+ public:
+  ViewAssociateTableTest() {}
+  ~ViewAssociateTableTest() override {}
+
+  void SetUp() override { ViewManagerTestBase::SetUp(); }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ViewAssociateTableTest);
+};
+
+TEST_F(ViewAssociateTableTest, RegisterViewAssociateThenCloseIt) {
+  // Create a mock view registry
+  mojo::ui::MockViewInspector mock_view_inspector;
+
+  // Create a view associate table
+  ViewAssociateTable view_associate_table;
+
+  {
+    // Create and bind a MockViewAssociate
+    mojo::ui::ViewAssociatePtr associate;
+    MockViewAssociate mock_view_associate;
+    mojo::Binding<mojo::ui::ViewAssociate> view_associate_binding(
+        &mock_view_associate, mojo::GetProxy(&associate));
+
+    // call ViewAssociateTable::RegisterViewAssociate
+    EXPECT_EQ((size_t)0, view_associate_table.associate_count());
+
+    mojo::ui::ViewAssociateOwnerPtr view_associate_owner;
+    view_associate_table.RegisterViewAssociate(
+        &mock_view_inspector, associate.Pass(),
+        mojo::GetProxy(&view_associate_owner), "test_view_associate");
+    KICK_MESSAGE_LOOP_WHILE(view_associate_table.associate_count() != 1);
+    EXPECT_EQ((size_t)1, view_associate_table.associate_count());
+  }
+
+  // ViewAssociate has been destroyed (since it's out of scope now)
+  // Make sure it's been removed
+  KICK_MESSAGE_LOOP_WHILE(view_associate_table.associate_count() != 0);
+  EXPECT_EQ((size_t)0, view_associate_table.associate_count());
+}
+
+}  // namespace test
+}  // namespace view_manager
diff --git a/services/ui/view_manager/tests/view_manager_test.cc b/services/ui/view_manager/tests/view_manager_test.cc
new file mode 100644
index 0000000..13e30c6
--- /dev/null
+++ b/services/ui/view_manager/tests/view_manager_test.cc
@@ -0,0 +1,174 @@
+// 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/application/application_impl.h"
+#include "mojo/public/cpp/application/connect.h"
+#include "mojo/services/ui/views/interfaces/view_manager.mojom.h"
+#include "mojo/services/ui/views/interfaces/views.mojom.h"
+#include "services/ui/view_manager/tests/mock_view_associate.h"
+#include "services/ui/view_manager/tests/view_manager_test_base.h"
+
+namespace view_manager {
+namespace test {
+
+class MockViewListener : public mojo::ui::ViewListener {
+ public:
+  MockViewListener() {}
+  ~MockViewListener() override {}
+
+  void OnPropertiesChanged(
+      uint32_t scene_version,
+      mojo::ui::ViewPropertiesPtr properties,
+      const OnPropertiesChangedCallback& callback) override {}
+};
+
+class ViewManagerTest : public ViewManagerTestBase {
+ public:
+  ViewManagerTest() {}
+  ~ViewManagerTest() override {}
+
+  void SetUp() override {
+    ViewManagerTestBase::SetUp();
+
+    // Connect to view manager
+    mojo::ConnectToService(shell(), "mojo:view_manager_service",
+                           mojo::GetProxy(&view_manager_));
+  }
+
+ protected:
+  mojo::ui::ViewManagerPtr view_manager_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ViewManagerTest);
+};
+
+TEST_F(ViewManagerTest, CreateAViewManagerAndView) {
+  // Create and bind a mock view listener
+  mojo::ui::ViewListenerPtr view_listener;
+  MockViewListener mock_view_listener;
+  mojo::Binding<mojo::ui::ViewListener> view_listener_binding(
+      &mock_view_listener, mojo::GetProxy(&view_listener));
+
+  // Create a view
+  mojo::ui::ViewPtr view;
+  mojo::ui::ViewOwnerPtr client_view_owner;
+  view_manager_->CreateView(mojo::GetProxy(&view),
+                            mojo::GetProxy(&client_view_owner),
+                            view_listener.Pass(), "test_view");
+
+  // Call View::GetToken. Check that you get the callback.
+  int view_token_callback_invokecount = 0;
+  auto view_token_callback = [&view_token_callback_invokecount](
+      mojo::ui::ViewTokenPtr token) { view_token_callback_invokecount++; };
+
+  EXPECT_EQ(0, view_token_callback_invokecount);
+  view->GetToken(view_token_callback);
+
+  KICK_MESSAGE_LOOP_WHILE(view_token_callback_invokecount != 1);
+
+  EXPECT_EQ(1, view_token_callback_invokecount);
+}
+
+TEST_F(ViewManagerTest, ConnectAMockViewAssociate) {
+  // Create and bind a MockViewAssociate
+  mojo::InterfaceHandle<mojo::ui::ViewAssociate> associate;
+  MockViewAssociate mock_view_associate;
+  mojo::Binding<mojo::ui::ViewAssociate> view_associate_binding(
+      &mock_view_associate, mojo::GetProxy(&associate));
+
+  // Call ViewManager::RegisterViewAssociate. MockViewAssociate::Connect
+  // should be called back
+  EXPECT_EQ(0, mock_view_associate.connect_invokecount);
+  mojo::ui::ViewAssociateOwnerPtr view_associate_owner;
+  view_manager_->RegisterViewAssociate(associate.Pass(),
+                                       mojo::GetProxy(&view_associate_owner),
+                                       "test_view_associate");
+
+  KICK_MESSAGE_LOOP_WHILE(mock_view_associate.connect_invokecount != 1);
+
+  EXPECT_EQ(1, mock_view_associate.connect_invokecount);
+}
+
+TEST_F(ViewManagerTest, DisconnectAMockViewAssociate) {
+  mojo::ui::ViewAssociateOwnerPtr view_associate_owner;
+  int owner_connection_error_callback_invokecount = 0;
+
+  {
+    // Create and bind a MockViewAssociate
+    mojo::InterfaceHandle<mojo::ui::ViewAssociate> associate;
+    MockViewAssociate mock_view_associate;
+    mojo::Binding<mojo::ui::ViewAssociate> view_associate_binding(
+        &mock_view_associate, mojo::GetProxy(&associate));
+
+    // Call ViewManager::RegisterViewAssociate. MockViewAssociate::Connect
+    // should be called back
+    EXPECT_EQ(0, mock_view_associate.connect_invokecount);
+
+    view_manager_->RegisterViewAssociate(associate.Pass(),
+                                         mojo::GetProxy(&view_associate_owner),
+                                         "test_view_associate_xyz");
+
+    // set a callback for errors
+    view_associate_owner.set_connection_error_handler(
+        // use lambda function as callback
+        [&owner_connection_error_callback_invokecount]() {
+          owner_connection_error_callback_invokecount++;
+        });
+
+    KICK_MESSAGE_LOOP_WHILE(mock_view_associate.connect_invokecount != 1);
+
+    EXPECT_EQ(1, mock_view_associate.connect_invokecount);
+
+    EXPECT_EQ(0, owner_connection_error_callback_invokecount);
+  }
+
+  // mock_view_associate is out of scope, should be destroyed
+  // we expect to get a connection error from the owner
+  KICK_MESSAGE_LOOP_WHILE(owner_connection_error_callback_invokecount != 1)
+
+  EXPECT_EQ(1, owner_connection_error_callback_invokecount);
+}
+
+TEST_F(ViewManagerTest, DisconnectAViewAssociateOwner) {
+  // Create and bind a MockViewAssociate
+  mojo::InterfaceHandle<mojo::ui::ViewAssociate> associate;
+  MockViewAssociate mock_view_associate;
+  mojo::Binding<mojo::ui::ViewAssociate> view_associate_binding(
+      &mock_view_associate, mojo::GetProxy(&associate));
+
+  // set a callback for errors
+  int connection_error_callback_invokecount = 0;
+  view_associate_binding.set_connection_error_handler(
+      // use lambda function as callback
+      [&connection_error_callback_invokecount]() {
+        connection_error_callback_invokecount++;
+      });
+
+  {
+    mojo::ui::ViewAssociateOwnerPtr view_associate_owner;
+
+    // Call ViewManager::RegisterViewAssociate. MockViewAssociate::Connect
+    // should be called back
+    EXPECT_EQ(0, mock_view_associate.connect_invokecount);
+
+    view_manager_->RegisterViewAssociate(associate.Pass(),
+                                         mojo::GetProxy(&view_associate_owner),
+                                         "test_view_associate_xyz");
+
+    KICK_MESSAGE_LOOP_WHILE(mock_view_associate.connect_invokecount != 1);
+
+    EXPECT_EQ(1, mock_view_associate.connect_invokecount);
+
+    EXPECT_EQ(0, connection_error_callback_invokecount);
+  }
+
+  // view_associate_owner is out of scope, should be destroyed
+  // we expect to get a connection error from the view associate
+  KICK_MESSAGE_LOOP_WHILE(connection_error_callback_invokecount != 1)
+
+  EXPECT_EQ(1, connection_error_callback_invokecount);
+}
+
+}  // namespace test
+}  // namespace view_manager
diff --git a/services/ui/view_manager/tests/view_manager_test_base.cc b/services/ui/view_manager/tests/view_manager_test_base.cc
new file mode 100644
index 0000000..e123720
--- /dev/null
+++ b/services/ui/view_manager/tests/view_manager_test_base.cc
@@ -0,0 +1,38 @@
+// 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 "services/ui/view_manager/tests/view_manager_test_base.h"
+
+#include "base/run_loop.h"
+#include "mojo/public/cpp/application/application_test_base.h"
+#include "mojo/public/cpp/application/connect.h"
+#include "mojo/services/ui/views/interfaces/view_manager.mojom.h"
+#include "mojo/services/ui/views/interfaces/views.mojom.h"
+
+namespace view_manager {
+namespace test {
+
+ViewManagerTestBase::ViewManagerTestBase() : weak_factory_(this) {}
+
+ViewManagerTestBase::~ViewManagerTestBase() {}
+
+void ViewManagerTestBase::SetUp() {
+  mojo::test::ApplicationTestBase::SetUp();
+  quit_message_loop_callback_ =
+      base::Bind(&ViewManagerTestBase::QuitMessageLoopCallback,
+                 weak_factory_.GetWeakPtr());
+}
+
+void ViewManagerTestBase::QuitMessageLoopCallback() {
+  base::MessageLoop::current()->Quit();
+}
+
+void ViewManagerTestBase::KickMessageLoop() {
+  base::MessageLoop::current()->PostDelayedTask(
+      FROM_HERE, quit_message_loop_callback_, kDefaultMessageDelay);
+  base::MessageLoop::current()->Run();
+}
+
+}  // namespace test
+}  // namespace view_manager
diff --git a/services/ui/view_manager/tests/view_manager_test_base.h b/services/ui/view_manager/tests/view_manager_test_base.h
new file mode 100644
index 0000000..4198be4
--- /dev/null
+++ b/services/ui/view_manager/tests/view_manager_test_base.h
@@ -0,0 +1,39 @@
+// 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 "base/bind.h"
+#include "mojo/public/cpp/application/application_test_base.h"
+
+namespace view_manager {
+namespace test {
+
+const base::TimeDelta kDefaultMessageDelay =
+    base::TimeDelta::FromMilliseconds(10);
+
+// Run message loop until condition is true (timeout after 400*10ms = 4000ms)
+#define KICK_MESSAGE_LOOP_WHILE(x)     \
+  for (int i = 0; x && i < 400; i++) { \
+    KickMessageLoop();                 \
+  }
+
+class ViewManagerTestBase : public mojo::test::ApplicationTestBase {
+ public:
+  ViewManagerTestBase();
+  ~ViewManagerTestBase() override;
+
+  void SetUp() override;
+  void KickMessageLoop();
+
+ protected:
+  base::Closure quit_message_loop_callback_;
+  base::WeakPtrFactory<ViewManagerTestBase> weak_factory_;
+
+ private:
+  void QuitMessageLoopCallback();
+
+  DISALLOW_COPY_AND_ASSIGN(ViewManagerTestBase);
+};
+
+}  // namespace test
+}  // namespace view_manager
diff --git a/services/ui/view_manager/view_associate_table.cc b/services/ui/view_manager/view_associate_table.cc
index 2088696..4acb471 100644
--- a/services/ui/view_manager/view_associate_table.cc
+++ b/services/ui/view_manager/view_associate_table.cc
@@ -23,39 +23,56 @@
 
 ViewAssociateTable::~ViewAssociateTable() {}
 
-void ViewAssociateTable::ConnectAssociates(
-    mojo::ApplicationImpl* app_impl,
+void ViewAssociateTable::RegisterViewAssociate(
     mojo::ui::ViewInspector* inspector,
-    const std::vector<std::string>& urls,
-    const AssociateConnectionErrorCallback& connection_error_callback) {
-  DCHECK(app_impl);
+    mojo::ui::ViewAssociatePtr associate,
+    mojo::InterfaceRequest<mojo::ui::ViewAssociateOwner>
+        view_associate_owner_request,
+    const mojo::String& label) {
   DCHECK(inspector);
+  DCHECK(associate.is_bound());
 
-  for (auto& url : urls) {
-    DVLOG(1) << "Connecting to view associate: url=" << url;
-    associates_.emplace_back(new AssociateData(url, inspector));
-    AssociateData* data = associates_.back().get();
+  std::string sanitized_label =
+      label.get().substr(0, mojo::ui::kLabelMaxLength);
+  associates_.emplace_back(
+      new AssociateData(sanitized_label, associate.Pass(), this, inspector));
+  AssociateData* data = associates_.back().get();
 
-    mojo::ConnectToService(app_impl->shell(), url, GetProxy(&data->associate));
-    data->associate.set_connection_error_handler(
-        base::Bind(connection_error_callback, url));
+  data->BindOwner(view_associate_owner_request.Pass());
 
-    mojo::ui::ViewInspectorPtr inspector;
-    data->inspector_binding.Bind(GetProxy(&inspector));
-    data->associate->Connect(
-        inspector.Pass(),
-        base::Bind(&ViewAssociateTable::OnConnected, base::Unretained(this),
-                   pending_connection_count_));
+  // Set it to use our error handler.
+  data->associate.set_connection_error_handler(
+      base::Bind(&ViewAssociateTable::OnAssociateConnectionError,
+                 base::Unretained(this), data));
 
-    pending_connection_count_++;
-  }
+  data->associate_owner.set_connection_error_handler(
+      base::Bind(&ViewAssociateTable::OnAssociateOwnerConnectionError,
+                 base::Unretained(this), data));
+
+  // Connect the associate to our view inspector.
+  mojo::ui::ViewInspectorPtr inspector_ptr;
+  data->inspector_binding.Bind(GetProxy(&inspector_ptr));
+  data->associate->Connect(
+      inspector_ptr.Pass(),
+      base::Bind(&ViewAssociateTable::OnConnected, base::Unretained(this),
+                 pending_connection_count_));
+
+  // Wait for the associate to connect to our view inspector.
+  pending_connection_count_++;
+}
+
+void ViewAssociateTable::FinishedRegisteringViewAssociates() {
+  waiting_to_register_associates_ = false;
+
+  // If no more pending connections, kick off deferred work
+  CompleteDeferredWorkIfReady();
 }
 
 void ViewAssociateTable::ConnectToViewService(
     mojo::ui::ViewTokenPtr view_token,
     const mojo::String& service_name,
     mojo::ScopedMessagePipeHandle client_handle) {
-  if (pending_connection_count_) {
+  if (waiting_to_register_associates_ || pending_connection_count_) {
     deferred_work_.push_back(
         base::Bind(&ViewAssociateTable::ConnectToViewService,
                    base::Unretained(this), base::Passed(view_token.Pass()),
@@ -68,7 +85,7 @@
     if (Contains(data->info->view_service_names, service_name)) {
       DVLOG(2) << "Connecting to view service: view_token=" << view_token
                << ", service_name=" << service_name
-               << ", associate_url=" << data->url;
+               << ", associate_label=" << data->label;
       DCHECK(data->associate);
       data->associate->ConnectToViewService(view_token.Pass(), service_name,
                                             client_handle.Pass());
@@ -81,11 +98,42 @@
   // Allow pipe to be closed as an indication of failure.
 }
 
+bool ViewAssociateTable::RemoveAssociateData(AssociateData* associate_data,
+                                             std::string& label) {
+  for (auto it = associates_.begin(); it != associates_.end(); it++) {
+    AssociateData* data = it->get();
+    if (associate_data == data) {
+      label = data->label;
+      associates_.erase(it);
+      return true;
+    }
+  }
+  return false;
+}
+
+void ViewAssociateTable::OnAssociateConnectionError(
+    AssociateData* associate_data) {
+  std::string label;
+  bool removed = RemoveAssociateData(associate_data, label);
+  DCHECK(removed);
+  DVLOG(2) << "ViewAssociate disconnected, removing from table"
+           << ", associate_label=" << label;
+}
+
+void ViewAssociateTable::OnAssociateOwnerConnectionError(
+    AssociateData* associate_data) {
+  std::string label;
+  bool removed = RemoveAssociateData(associate_data, label);
+  DCHECK(removed);
+  DVLOG(2) << "ViewAssociateOwner disconnected, removing from table"
+           << ", associate_label=" << label;
+}
+
 void ViewAssociateTable::ConnectToViewTreeService(
     mojo::ui::ViewTreeTokenPtr view_tree_token,
     const mojo::String& service_name,
     mojo::ScopedMessagePipeHandle client_handle) {
-  if (pending_connection_count_) {
+  if (waiting_to_register_associates_ || pending_connection_count_) {
     deferred_work_.push_back(
         base::Bind(&ViewAssociateTable::ConnectToViewTreeService,
                    base::Unretained(this), base::Passed(view_tree_token.Pass()),
@@ -98,7 +146,7 @@
     if (Contains(data->info->view_tree_service_names, service_name)) {
       DVLOG(2) << "Connecting to view tree service: view_tree_token="
                << view_tree_token << ", service_name=" << service_name
-               << ", associate_url=" << data->url;
+               << ", associate_label=" << data->label;
       DCHECK(data->associate);
       data->associate->ConnectToViewTreeService(
           view_tree_token.Pass(), service_name, client_handle.Pass());
@@ -117,28 +165,44 @@
   DCHECK(pending_connection_count_);
   DCHECK(!associates_[index]->info);
 
-  DVLOG(1) << "Connected to view associate: url=" << associates_[index]->url
+  DVLOG(1) << "Connected to view associate: label=" << associates_[index]->label
            << ", info=" << info;
   associates_[index]->info = info.Pass();
 
   pending_connection_count_--;
-  if (!pending_connection_count_)
-    CompleteDeferredWork();
+  CompleteDeferredWorkIfReady();
 }
 
-void ViewAssociateTable::CompleteDeferredWork() {
-  DCHECK(!pending_connection_count_);
+void ViewAssociateTable::CompleteDeferredWorkIfReady() {
+  // We check to see if all the ViewAssociates have been registered, and if
+  // they connected to us. Otherwise, we keep the work deferred.
+  if (!waiting_to_register_associates_ && !pending_connection_count_) {
+    for (auto& work : deferred_work_)
+      work.Run();
+    deferred_work_.clear();
+  }
+}
 
-  for (auto& work : deferred_work_)
-    work.Run();
-  deferred_work_.clear();
+size_t ViewAssociateTable::associate_count() {
+  return associates_.size();
 }
 
 ViewAssociateTable::AssociateData::AssociateData(
-    const std::string& url,
+    const std::string& label,
+    mojo::ui::ViewAssociatePtr associate,
+    mojo::ui::ViewAssociateOwner* associate_owner_impl,
     mojo::ui::ViewInspector* inspector)
-    : url(url), inspector_binding(inspector) {}
+    : label(label),
+      associate(associate.Pass()),
+      associate_owner(associate_owner_impl),
+      inspector_binding(inspector) {}
 
 ViewAssociateTable::AssociateData::~AssociateData() {}
 
+void ViewAssociateTable::AssociateData::BindOwner(
+    mojo::InterfaceRequest<mojo::ui::ViewAssociateOwner>
+        view_associate_owner_request) {
+  associate_owner.Bind(view_associate_owner_request.Pass());
+}
+
 }  // namespace view_manager
diff --git a/services/ui/view_manager/view_associate_table.h b/services/ui/view_manager/view_associate_table.h
index 9cdc71b..4952498 100644
--- a/services/ui/view_manager/view_associate_table.h
+++ b/services/ui/view_manager/view_associate_table.h
@@ -14,26 +14,26 @@
 #include "mojo/common/binding_set.h"
 #include "mojo/public/cpp/application/application_impl.h"
 #include "mojo/services/ui/views/interfaces/view_associates.mojom.h"
+#include "mojo/services/ui/views/interfaces/view_manager.mojom.h"
 
 namespace view_manager {
 
 // Maintains a table of all connected view associates.
-class ViewAssociateTable {
+class ViewAssociateTable : public mojo::ui::ViewAssociateOwner {
  public:
   using AssociateConnectionErrorCallback =
       base::Callback<void(const std::string&)>;
 
   ViewAssociateTable();
-  ~ViewAssociateTable();
+  ~ViewAssociateTable() override;
 
-  // Begins connecting to the view associates.
-  // Invokes |connection_error_callback| if an associate connection fails
-  // and provides the associate's url.
-  void ConnectAssociates(
-      mojo::ApplicationImpl* app_impl,
+  void RegisterViewAssociate(
       mojo::ui::ViewInspector* inspector,
-      const std::vector<std::string>& urls,
-      const AssociateConnectionErrorCallback& connection_error_callback);
+      mojo::ui::ViewAssociatePtr associate,
+      mojo::InterfaceRequest<mojo::ui::ViewAssociateOwner> view_associate_owner,
+      const mojo::String& label);
+
+  void FinishedRegisteringViewAssociates();
 
   // Connects to services offered by the view associates.
   void ConnectToViewService(mojo::ui::ViewTokenPtr view_token,
@@ -45,22 +45,35 @@
 
   void OnConnected(uint32_t index, mojo::ui::ViewAssociateInfoPtr info);
 
-  void CompleteDeferredWork();
+  size_t associate_count();
 
  private:
   struct AssociateData {
-    AssociateData(const std::string& url, mojo::ui::ViewInspector* inspector);
+    AssociateData(const std::string& label,
+                  mojo::ui::ViewAssociatePtr associate,
+                  mojo::ui::ViewAssociateOwner* associate_owner_impl,
+                  mojo::ui::ViewInspector* inspector);
     ~AssociateData();
 
-    const std::string url;
+    void BindOwner(mojo::InterfaceRequest<mojo::ui::ViewAssociateOwner>
+                       view_associate_owner_request);
+
+    const std::string label;
     mojo::ui::ViewAssociatePtr associate;
+    mojo::Binding<mojo::ui::ViewAssociateOwner> associate_owner;
     mojo::ui::ViewAssociateInfoPtr info;
     mojo::Binding<mojo::ui::ViewInspector> inspector_binding;
   };
 
+  bool RemoveAssociateData(AssociateData* associate_data, std::string& label);
+  void CompleteDeferredWorkIfReady();
+  void OnAssociateOwnerConnectionError(AssociateData* associate_data);
+  void OnAssociateConnectionError(AssociateData* associate_data);
+
   std::vector<std::unique_ptr<AssociateData>> associates_;
 
   uint32_t pending_connection_count_ = 0u;
+  bool waiting_to_register_associates_ = true;
   std::vector<base::Closure> deferred_work_;
 
   DISALLOW_COPY_AND_ASSIGN(ViewAssociateTable);
diff --git a/services/ui/view_manager/view_manager_app.cc b/services/ui/view_manager/view_manager_app.cc
index 171d8bc..3ea2d69 100644
--- a/services/ui/view_manager/view_manager_app.cc
+++ b/services/ui/view_manager/view_manager_app.cc
@@ -44,19 +44,6 @@
 
   // Create the registry.
   registry_.reset(new ViewRegistry(compositor.Pass()));
-
-  // Connect to associates.
-  // TODO(jeffbrown): Consider making the launcher register associates
-  // with the view manager or perhaps per view tree.
-  std::vector<std::string> associate_urls = command_line->GetArgs();
-  if (associate_urls.empty()) {
-    // TODO(jeffbrown): Replace this hardcoded list.
-    associate_urls.push_back("mojo:input_manager_service");
-  }
-  registry_->ConnectAssociates(
-      app_impl_, associate_urls,
-      base::Bind(&ViewManagerApp::OnAssociateConnectionError,
-                 base::Unretained(this)));
 }
 
 bool ViewManagerApp::ConfigureIncomingConnection(
@@ -76,11 +63,6 @@
   Shutdown();
 }
 
-void ViewManagerApp::OnAssociateConnectionError(const std::string& url) {
-  LOG(ERROR) << "Exiting due to view associate connection error: url=" << url;
-  Shutdown();
-}
-
 void ViewManagerApp::Shutdown() {
   app_impl_->Terminate();
 }
diff --git a/services/ui/view_manager/view_manager_app.h b/services/ui/view_manager/view_manager_app.h
index 2c83451..05e0844 100644
--- a/services/ui/view_manager/view_manager_app.h
+++ b/services/ui/view_manager/view_manager_app.h
@@ -30,7 +30,6 @@
       mojo::ServiceProviderImpl* service_provider_impl) override;
 
   void OnCompositorConnectionError();
-  void OnAssociateConnectionError(const std::string& url);
 
   void Shutdown();
 
diff --git a/services/ui/view_manager/view_manager_impl.cc b/services/ui/view_manager/view_manager_impl.cc
index 7e443b2..975bc40 100644
--- a/services/ui/view_manager/view_manager_impl.cc
+++ b/services/ui/view_manager/view_manager_impl.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/bind.h"
 #include "services/ui/view_manager/view_impl.h"
 #include "services/ui/view_manager/view_tree_impl.h"
 
@@ -36,4 +37,19 @@
       label);
 }
 
+// TODO(mikejurka): This should only be called by trusted code (ie launcher),
+// once we have a security story.
+void ViewManagerImpl::RegisterViewAssociate(
+    mojo::InterfaceHandle<mojo::ui::ViewAssociate> view_associate,
+    mojo::InterfaceRequest<mojo::ui::ViewAssociateOwner> view_associate_owner,
+    const mojo::String& label) {
+  registry_->RegisterViewAssociate(
+      registry_, mojo::ui::ViewAssociatePtr::Create(std::move(view_associate)),
+      view_associate_owner.Pass(), label);
+}
+
+void ViewManagerImpl::FinishedRegisteringViewAssociates() {
+  registry_->FinishedRegisteringViewAssociates();
+}
+
 }  // namespace view_manager
diff --git a/services/ui/view_manager/view_manager_impl.h b/services/ui/view_manager/view_manager_impl.h
index a84e933..1b4ca09 100644
--- a/services/ui/view_manager/view_manager_impl.h
+++ b/services/ui/view_manager/view_manager_impl.h
@@ -28,6 +28,11 @@
       mojo::InterfaceRequest<mojo::ui::ViewTree> view_tree_request,
       mojo::InterfaceHandle<mojo::ui::ViewTreeListener> view_tree_listener,
       const mojo::String& label) override;
+  void RegisterViewAssociate(
+      mojo::InterfaceHandle<mojo::ui::ViewAssociate> view_associate,
+      mojo::InterfaceRequest<mojo::ui::ViewAssociateOwner> view_associate_owner,
+      const mojo::String& label) override;
+  void FinishedRegisteringViewAssociates() override;
 
   ViewRegistry* registry_;
 
diff --git a/services/ui/view_manager/view_registry.cc b/services/ui/view_manager/view_registry.cc
index aee3a46..1a70f73 100644
--- a/services/ui/view_manager/view_registry.cc
+++ b/services/ui/view_manager/view_registry.cc
@@ -59,14 +59,21 @@
 
 ViewRegistry::~ViewRegistry() {}
 
-void ViewRegistry::ConnectAssociates(
-    mojo::ApplicationImpl* app_impl,
-    const std::vector<std::string>& urls,
-    const AssociateConnectionErrorCallback& connection_error_callback) {
-  associate_table_.ConnectAssociates(app_impl, this, urls,
-                                     connection_error_callback);
+// REGISTERING ASSOCIATES
+
+void ViewRegistry::RegisterViewAssociate(
+    mojo::ui::ViewInspector* view_inspector,
+    mojo::ui::ViewAssociatePtr view_associate,
+    mojo::InterfaceRequest<mojo::ui::ViewAssociateOwner> view_associate_owner,
+    const mojo::String& label) {
+  associate_table_.RegisterViewAssociate(view_inspector, view_associate.Pass(),
+                                         view_associate_owner.Pass(), label);
 }
 
+void ViewRegistry::FinishedRegisteringViewAssociates() {
+  associate_table_.FinishedRegisteringViewAssociates();
+};
+
 // CREATE / DESTROY VIEWS
 
 void ViewRegistry::CreateView(
diff --git a/services/ui/view_manager/view_registry.h b/services/ui/view_manager/view_registry.h
index 9550e29..1b1e17c 100644
--- a/services/ui/view_manager/view_registry.h
+++ b/services/ui/view_manager/view_registry.h
@@ -31,14 +31,6 @@
   explicit ViewRegistry(mojo::gfx::composition::CompositorPtr compositor);
   ~ViewRegistry() override;
 
-  // Begins connecting to the view associates.
-  // Invokes |connection_error_callback| if an associate connection fails
-  // and provides the associate's url.
-  void ConnectAssociates(
-      mojo::ApplicationImpl* app_impl,
-      const std::vector<std::string>& urls,
-      const AssociateConnectionErrorCallback& connection_error_callback);
-
   // VIEW MANAGER REQUESTS
 
   // Creates a view and returns its ViewToken.
@@ -54,6 +46,14 @@
       mojo::ui::ViewTreeListenerPtr view_tree_listener,
       const mojo::String& label);
 
+  void RegisterViewAssociate(
+      mojo::ui::ViewInspector* view_inspector,
+      mojo::ui::ViewAssociatePtr view_associate,
+      mojo::InterfaceRequest<mojo::ui::ViewAssociateOwner> view_associate_owner,
+      const mojo::String& label);
+
+  void FinishedRegisteringViewAssociates();
+
   // VIEW STUB REQUESTS
 
   void OnViewResolved(ViewStub* view_stub, mojo::ui::ViewTokenPtr view_token);