Adds ViewManager::Connect

I'm nuking SetRoots as it is no longer needed.

BUG=365012
TEST=covered by tests
[email protected]

Review URL: https://codereview.chromium.org/304503006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@273340 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/mojo/mojo_services.gypi b/mojo/mojo_services.gypi
index b5ab1ff5..398835e8 100644
--- a/mojo/mojo_services.gypi
+++ b/mojo/mojo_services.gypi
@@ -339,10 +339,11 @@
             '../ui/aura/aura.gyp:aura',
             '../ui/gfx/gfx.gyp:gfx_geometry',
             '../ui/gl/gl.gyp:gl',
+            'mojo_application',
             'mojo_environment_chromium',
             'mojo_geometry_bindings',
             'mojo_geometry_lib',
-            'mojo_application',
+            'mojo_service_manager',
             'mojo_shell_test_support',
             'mojo_system_impl',
             'mojo_view_manager_bindings',
@@ -350,6 +351,8 @@
             'mojo_view_manager_run_unittests',
           ],
           'sources': [
+            'services/view_manager/test_change_tracker.cc',
+            'services/view_manager/test_change_tracker.h',
             'services/view_manager/view_manager_connection_unittest.cc',
           ],
         },
diff --git a/mojo/services/public/interfaces/view_manager/view_manager.mojom b/mojo/services/public/interfaces/view_manager/view_manager.mojom
index d63f7ca..ef50c45 100644
--- a/mojo/services/public/interfaces/view_manager/view_manager.mojom
+++ b/mojo/services/public/interfaces/view_manager/view_manager.mojom
@@ -88,15 +88,16 @@
                   handle<shared_buffer> buffer,
                   uint32 buffer_size) => (bool success);
 
-  // Sets the ids of the roots for the specified connection.
-  // TODO(sky): this is temporary for testing. This needs to be conveyed at
-  // creation time of a new connection.
-  SetRoots(uint16 connection_id, uint32[] nodes) => (bool success);
+  // Connects to |url| creating a connection that has the roots |nodes|. Fails
+  // if |nodes| is empty or contains nodes that were not created by this
+  // connection.
+  Connect(string url, uint32[] nodes) => (bool success);
 };
 
 // Changes to nodes/views are not sent to the connection that originated the
 // change. For example, if connection 1 attaches a view to a node (SetView())
 // connection 1 does not receive OnNodeViewReplaced().
+[Client=IViewManager]
 interface IViewManagerClient {
   // Invoked once the connection has been established. |connection_id| is the id
   // that uniquely identifies this connection. |next_server_change_id| is the
diff --git a/mojo/services/view_manager/DEPS b/mojo/services/view_manager/DEPS
index 81d1c97..78641ff 100644
--- a/mojo/services/view_manager/DEPS
+++ b/mojo/services/view_manager/DEPS
@@ -10,3 +10,9 @@
   "+ui/gfx",
   "+ui/gl",
 ]
+
+specific_include_rules = {
+  "view_manager_connection_unittest.cc": [
+    "+mojo/service_manager/service_manager.h",
+  ],
+}
diff --git a/mojo/services/view_manager/root_node_manager.cc b/mojo/services/view_manager/root_node_manager.cc
index eed3efb..37cda55d 100644
--- a/mojo/services/view_manager/root_node_manager.cc
+++ b/mojo/services/view_manager/root_node_manager.cc
@@ -5,6 +5,7 @@
 #include "mojo/services/view_manager/root_node_manager.h"
 
 #include "base/logging.h"
+#include "mojo/public/interfaces/service_provider/service_provider.mojom.h"
 #include "mojo/services/view_manager/view_manager_connection.h"
 #include "ui/aura/env.h"
 
@@ -35,7 +36,8 @@
 }
 
 RootNodeManager::RootNodeManager(ServiceProvider* service_provider)
-    : next_connection_id_(1),
+    : service_provider_(service_provider),
+      next_connection_id_(1),
       next_server_change_id_(1),
       change_source_(kRootConnection),
       is_processing_delete_node_(false),
@@ -44,6 +46,8 @@
 }
 
 RootNodeManager::~RootNodeManager() {
+  while (!connections_created_by_connect_.empty())
+    delete *(connections_created_by_connect_.begin());
   // All the connections should have been destroyed.
   DCHECK(connection_map_.empty());
 }
@@ -61,6 +65,17 @@
 
 void RootNodeManager::RemoveConnection(ViewManagerConnection* connection) {
   connection_map_.erase(connection->id());
+  connections_created_by_connect_.erase(connection);
+}
+
+void RootNodeManager::Connect(const String& url,
+                              const Array<TransportNodeId>& node_ids) {
+  MessagePipe pipe;
+  service_provider_->ConnectToService(url, pipe.handle1.Pass());
+  ViewManagerConnection* connection = new ViewManagerConnection(this);
+  connection->SetRoots(node_ids);
+  BindToPipe(connection, pipe.handle0.Pass());
+  connections_created_by_connect_.insert(connection);
 }
 
 ViewManagerConnection* RootNodeManager::GetConnection(
diff --git a/mojo/services/view_manager/root_node_manager.h b/mojo/services/view_manager/root_node_manager.h
index 3aed8bd4..001e3b2a 100644
--- a/mojo/services/view_manager/root_node_manager.h
+++ b/mojo/services/view_manager/root_node_manager.h
@@ -6,8 +6,10 @@
 #define MOJO_SERVICES_VIEW_MANAGER_ROOT_NODE_MANAGER_H_
 
 #include <map>
+#include <set>
 
 #include "base/basictypes.h"
+#include "mojo/public/cpp/bindings/array.h"
 #include "mojo/services/view_manager/ids.h"
 #include "mojo/services/view_manager/node.h"
 #include "mojo/services/view_manager/node_delegate.h"
@@ -65,6 +67,8 @@
   void AddConnection(ViewManagerConnection* connection);
   void RemoveConnection(ViewManagerConnection* connection);
 
+  void Connect(const String& url, const Array<TransportNodeId>& node_ids);
+
   // Returns the connection by id.
   ViewManagerConnection* GetConnection(TransportConnectionId connection_id);
 
@@ -129,6 +133,8 @@
 
   Context context_;
 
+  ServiceProvider* service_provider_;
+
   // ID to use for next ViewManagerConnection.
   TransportConnectionId next_connection_id_;
 
@@ -148,6 +154,10 @@
   // Root node.
   Node root_;
 
+  // Set of ViewManagerConnections created by way of Connect(). These have to be
+  // explicitly destroyed.
+  std::set<ViewManagerConnection*> connections_created_by_connect_;
+
   DISALLOW_COPY_AND_ASSIGN(RootNodeManager);
 };
 
diff --git a/mojo/services/view_manager/test_change_tracker.cc b/mojo/services/view_manager/test_change_tracker.cc
new file mode 100644
index 0000000..bf8ff5c
--- /dev/null
+++ b/mojo/services/view_manager/test_change_tracker.cc
@@ -0,0 +1,228 @@
+// 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.
+
+#include "mojo/services/view_manager/test_change_tracker.h"
+
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+#include "mojo/services/public/cpp/view_manager/util.h"
+
+namespace mojo {
+
+// TODO(sky): remove this when Darin is done with cleanup.
+template <typename T>
+class MOJO_COMMON_EXPORT TypeConverter<T, T> {
+ public:
+  static T ConvertFrom(T input, Buffer* buf) {
+    return input;
+  }
+  static T ConvertTo(T input) {
+    return input;
+  }
+
+  MOJO_ALLOW_IMPLICIT_TYPE_CONVERSION();
+};
+
+namespace view_manager {
+namespace service {
+
+std::string NodeIdToString(TransportNodeId id) {
+  return (id == 0) ? "null" :
+      base::StringPrintf("%d,%d", HiWord(id), LoWord(id));
+}
+
+namespace {
+
+void INodesToTestNodes(const Array<INode>& data,
+                       std::vector<TestNode>* test_nodes) {
+  for (size_t i = 0; i < data.size(); ++i) {
+    TestNode node;
+    node.parent_id = data[i].parent_id();
+    node.node_id = data[i].node_id();
+    node.view_id = data[i].view_id();
+    test_nodes->push_back(node);
+  }
+}
+
+std::string RectToString(const gfx::Rect& rect) {
+  return base::StringPrintf("%d,%d %dx%d", rect.x(), rect.y(), rect.width(),
+                            rect.height());
+}
+
+std::string ChangeToDescription1(const Change& change) {
+  switch (change.type) {
+    case CHANGE_TYPE_CONNECTION_ESTABLISHED:
+      return "OnConnectionEstablished";
+
+    case CHANGE_TYPE_SERVER_CHANGE_ID_ADVANCED:
+      return base::StringPrintf(
+          "ServerChangeIdAdvanced %d", static_cast<int>(change.change_id));
+
+
+    case CHANGE_TYPE_NODE_BOUNDS_CHANGED:
+      return base::StringPrintf(
+          "BoundsChanged node=%s old_bounds=%s new_bounds=%s",
+          NodeIdToString(change.node_id).c_str(),
+          RectToString(change.bounds).c_str(),
+          RectToString(change.bounds2).c_str());
+
+    case CHANGE_TYPE_NODE_HIERARCHY_CHANGED:
+      return base::StringPrintf(
+            "HierarchyChanged change_id=%d node=%s new_parent=%s old_parent=%s",
+            static_cast<int>(change.change_id),
+            NodeIdToString(change.node_id).c_str(),
+            NodeIdToString(change.node_id2).c_str(),
+            NodeIdToString(change.node_id3).c_str());
+
+    case CHANGE_TYPE_NODE_DELETED:
+      return base::StringPrintf("NodeDeleted change_id=%d node=%s",
+                                static_cast<int>(change.change_id),
+                                NodeIdToString(change.node_id).c_str());
+
+    case CHANGE_TYPE_VIEW_DELETED:
+      return base::StringPrintf("ViewDeleted view=%s",
+                                NodeIdToString(change.view_id).c_str());
+
+    case CHANGE_TYPE_VIEW_REPLACED:
+      return base::StringPrintf(
+          "ViewReplaced node=%s new_view=%s old_view=%s",
+          NodeIdToString(change.node_id).c_str(),
+          NodeIdToString(change.view_id).c_str(),
+          NodeIdToString(change.view_id2).c_str());
+  }
+  return std::string();
+}
+
+}  // namespace
+
+std::vector<std::string> ChangesToDescription1(
+    const std::vector<Change>& changes) {
+  std::vector<std::string> strings(changes.size());
+  for (size_t i = 0; i < changes.size(); ++i)
+    strings[i] = ChangeToDescription1(changes[i]);
+  return strings;
+}
+
+std::string ChangeNodeDescription(const std::vector<Change>& changes) {
+  if (changes.size() != 1)
+    return std::string();
+  std::vector<std::string> node_strings(changes[0].nodes.size());
+  for (size_t i = 0; i < changes[0].nodes.size(); ++i)
+    node_strings[i] = "[" + changes[0].nodes[i].ToString() + "]";
+  return JoinString(node_strings, ',');
+}
+
+Change::Change()
+    : type(CHANGE_TYPE_CONNECTION_ESTABLISHED),
+      connection_id(0),
+      change_id(0),
+      node_id(0),
+      node_id2(0),
+      node_id3(0),
+      view_id(0),
+      view_id2(0) {}
+
+Change::~Change() {
+}
+
+TestChangeTracker::TestChangeTracker()
+    : delegate_(NULL) {
+}
+
+TestChangeTracker::~TestChangeTracker() {
+}
+
+void TestChangeTracker::OnViewManagerConnectionEstablished(
+    TransportConnectionId connection_id,
+    TransportChangeId next_server_change_id,
+    const Array<INode>& nodes) {
+  Change change;
+  change.type = CHANGE_TYPE_CONNECTION_ESTABLISHED;
+  change.connection_id = connection_id;
+  change.change_id = next_server_change_id;
+  INodesToTestNodes(nodes, &change.nodes);
+  AddChange(change);
+}
+
+void TestChangeTracker::OnServerChangeIdAdvanced(
+    TransportChangeId change_id) {
+  Change change;
+  change.type = CHANGE_TYPE_SERVER_CHANGE_ID_ADVANCED;
+  change.change_id = change_id;
+  AddChange(change);
+}
+
+void TestChangeTracker::OnNodeBoundsChanged(TransportNodeId node_id,
+                                            const Rect& old_bounds,
+                                            const Rect& new_bounds) {
+  Change change;
+  change.type = CHANGE_TYPE_NODE_BOUNDS_CHANGED;
+  change.node_id = node_id;
+  change.bounds = old_bounds;
+  change.bounds2 = new_bounds;
+  AddChange(change);
+}
+
+void TestChangeTracker::OnNodeHierarchyChanged(
+    TransportNodeId node_id,
+    TransportNodeId new_parent_id,
+    TransportNodeId old_parent_id,
+    TransportChangeId server_change_id,
+    const Array<INode>& nodes) {
+  Change change;
+  change.type = CHANGE_TYPE_NODE_HIERARCHY_CHANGED;
+  change.node_id = node_id;
+  change.node_id2 = new_parent_id;
+  change.node_id3 = old_parent_id;
+  change.change_id = server_change_id;
+  INodesToTestNodes(nodes, &change.nodes);
+  AddChange(change);
+}
+
+void TestChangeTracker::OnNodeDeleted(
+    TransportNodeId node_id,
+    TransportChangeId server_change_id) {
+  Change change;
+  change.type = CHANGE_TYPE_NODE_DELETED;
+  change.node_id = node_id;
+  change.change_id = server_change_id;
+  AddChange(change);
+}
+
+void TestChangeTracker::OnViewDeleted(TransportViewId view_id) {
+  Change change;
+  change.type = CHANGE_TYPE_VIEW_DELETED;
+  change.view_id = view_id;
+  AddChange(change);
+}
+
+void TestChangeTracker::OnNodeViewReplaced(TransportNodeId node_id,
+                                           TransportViewId new_view_id,
+                                           TransportViewId old_view_id) {
+  Change change;
+  change.type = CHANGE_TYPE_VIEW_REPLACED;
+  change.node_id = node_id;
+  change.view_id = new_view_id;
+  change.view_id2 = old_view_id;
+  AddChange(change);
+}
+
+void TestChangeTracker::AddChange(const Change& change) {
+  changes_.push_back(change);
+  if (delegate_)
+    delegate_->OnChangeAdded();
+}
+
+std::string TestNode::ToString() const {
+  return base::StringPrintf("node=%s parent=%s view=%s",
+                            NodeIdToString(node_id).c_str(),
+                            NodeIdToString(parent_id).c_str(),
+                            NodeIdToString(view_id).c_str());
+}
+
+}  // namespace service
+}  // namespace view_manager
+}  // namespace mojo
diff --git a/mojo/services/view_manager/test_change_tracker.h b/mojo/services/view_manager/test_change_tracker.h
new file mode 100644
index 0000000..066017a
--- /dev/null
+++ b/mojo/services/view_manager/test_change_tracker.h
@@ -0,0 +1,122 @@
+// 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.
+
+#ifndef MOJO_SERVICES_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_
+#define MOJO_SERVICES_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_
+
+#include <string>
+#include <vector>
+
+#include "mojo/public/cpp/bindings/array.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_types.h"
+#include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h"
+#include "ui/gfx/rect.h"
+
+namespace mojo {
+namespace view_manager {
+namespace service {
+
+enum ChangeType {
+  CHANGE_TYPE_CONNECTION_ESTABLISHED,
+  CHANGE_TYPE_SERVER_CHANGE_ID_ADVANCED,
+  CHANGE_TYPE_NODE_BOUNDS_CHANGED,
+  CHANGE_TYPE_NODE_HIERARCHY_CHANGED,
+  CHANGE_TYPE_NODE_DELETED,
+  CHANGE_TYPE_VIEW_DELETED,
+  CHANGE_TYPE_VIEW_REPLACED,
+};
+
+struct TestNode {
+  // Returns a string description of this.
+  std::string ToString() const;
+
+  TransportNodeId parent_id;
+  TransportNodeId node_id;
+  TransportNodeId view_id;
+};
+
+// Tracks a call to IViewManagerClient. See the individual functions for the
+// fields that are used.
+struct Change {
+  Change();
+  ~Change();
+
+  ChangeType type;
+  TransportConnectionId connection_id;
+  TransportChangeId change_id;
+  std::vector<TestNode> nodes;
+  TransportNodeId node_id;
+  TransportNodeId node_id2;
+  TransportNodeId node_id3;
+  TransportViewId view_id;
+  TransportViewId view_id2;
+  gfx::Rect bounds;
+  gfx::Rect bounds2;
+};
+
+// Converts Changes to string descriptions.
+std::vector<std::string> ChangesToDescription1(
+    const std::vector<Change>& changes);
+
+// Returns a string description of |changes[0].nodes|. Returns an empty string
+// if change.size() != 1.
+std::string ChangeNodeDescription(const std::vector<Change>& changes);
+
+// TestChangeTracker is used to record IViewManagerClient functions. It notifies
+// a delegate any time a change is added.
+class TestChangeTracker {
+ public:
+  // Used to notify the delegate when a change is added. A change corresponds to
+  // a single IViewManagerClient function.
+  class Delegate {
+   public:
+    virtual void OnChangeAdded() = 0;
+
+   protected:
+    virtual ~Delegate() {}
+  };
+
+  TestChangeTracker();
+  ~TestChangeTracker();
+
+  void set_delegate(Delegate* delegate) { delegate_ = delegate; }
+
+  std::vector<Change>* changes() { return &changes_; }
+
+  // Each of these functions generate a Change. There is one per
+  // IViewManagerClient function.
+  void OnViewManagerConnectionEstablished(
+      TransportConnectionId connection_id,
+      TransportChangeId next_server_change_id,
+      const Array<INode>& nodes);
+  void OnServerChangeIdAdvanced(TransportChangeId change_id);
+  void OnNodeBoundsChanged(TransportNodeId node_id,
+                           const Rect& old_bounds,
+                           const Rect& new_bounds);
+  void OnNodeHierarchyChanged(TransportNodeId node_id,
+                              TransportNodeId new_parent_id,
+                              TransportNodeId old_parent_id,
+                              TransportChangeId server_change_id,
+                              const Array<INode>& nodes);
+  void OnNodeDeleted(TransportNodeId node_id,
+                     TransportChangeId server_change_id);
+  void OnViewDeleted(TransportViewId view_id);
+  void OnNodeViewReplaced(TransportNodeId node_id,
+                          TransportViewId new_view_id,
+                          TransportViewId old_view_id);
+
+ private:
+  void AddChange(const Change& change);
+
+  Delegate* delegate_;
+  std::vector<Change> changes_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestChangeTracker);
+};
+
+}  // namespace service
+}  // namespace view_manager
+}  // namespace mojo
+
+#endif  // MOJO_SERVICES_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_
diff --git a/mojo/services/view_manager/view_manager_connection.cc b/mojo/services/view_manager/view_manager_connection.cc
index 498dc39..112773d 100644
--- a/mojo/services/view_manager/view_manager_connection.cc
+++ b/mojo/services/view_manager/view_manager_connection.cc
@@ -87,6 +87,16 @@
   return root_node_manager_->GetView(id);
 }
 
+void ViewManagerConnection::SetRoots(const Array<TransportNodeId>& node_ids) {
+  DCHECK_EQ(0, id_);  // Only valid before connection established.
+  NodeIdSet roots;
+  for (size_t i = 0; i < node_ids.size(); ++i) {
+    DCHECK(GetNode(NodeIdFromTransportId(node_ids[i])));
+    roots.insert(node_ids[i]);
+  }
+  roots_.swap(roots);
+}
+
 void ViewManagerConnection::ProcessNodeBoundsChanged(
     const Node* node,
     const gfx::Rect& old_bounds,
@@ -188,6 +198,11 @@
   client()->OnViewDeleted(ViewIdToTransportId(view));
 }
 
+void ViewManagerConnection::OnConnectionError() {
+  // TODO(sky): figure out if need to cleanup here if this
+  // ViewManagerConnection is the result of a Connect().
+}
+
 bool ViewManagerConnection::CanRemoveNodeFromParent(const Node* node) const {
   if (!node)
     return false;
@@ -248,6 +263,16 @@
       (IsNodeDescendantOfRoots(node) || node->id().connection_id == id_);
 }
 
+bool ViewManagerConnection::CanConnect(
+    const mojo::Array<uint32_t>& node_ids) const {
+  for (size_t i = 0; i < node_ids.size(); ++i) {
+    const Node* node = GetNode(NodeIdFromTransportId(node_ids[i]));
+    if (!node || node->id().connection_id != id_)
+      return false;
+  }
+  return node_ids.size() > 0;
+}
+
 bool ViewManagerConnection::DeleteNodeImpl(ViewManagerConnection* source,
                                            const NodeId& node_id) {
   DCHECK_EQ(node_id.connection_id, id_);
@@ -385,40 +410,6 @@
   return known_nodes_.count(NodeIdToTransportId(node->id())) > 0;
 }
 
-bool ViewManagerConnection::ProcessSetRoots(
-    TransportConnectionId source_connection_id,
-    const Array<TransportNodeId>& transport_node_ids) {
-  // TODO(sky): these DCHECKs can go away once this is part of a real API. Also
-  // make sure that when roots are set nodes are communicate to client. Code in
-  // ProcessNodeHierarchyChanged() is depending on this.
-  DCHECK(node_map_.empty());
-  DCHECK(view_map_.empty());
-
-  NodeIdSet roots;
-  for (size_t i = 0; i < transport_node_ids.size(); ++i) {
-    const Node* node = GetNode(NodeIdFromTransportId(transport_node_ids[i]));
-    // Only allow setting roots that are owned by the source connection.
-    if (!node || node->id().connection_id != source_connection_id)
-      return false;
-    roots.insert(transport_node_ids[i]);
-  }
-  roots_.swap(roots);
-
-  // TODO(sky): remove |known_nodes_.clear()| temporary while this is done here
-  // instead of at creation time.
-  known_nodes_.clear();
-  std::vector<const Node*> to_send;
-  for (NodeIdSet::const_iterator i = roots_.begin(); i != roots_.end(); ++i)
-    GetUnknownNodesFrom(GetNode(NodeIdFromTransportId(*i)), &to_send);
-  AllocationScope allocation_scope;
-  client()->OnViewManagerConnectionEstablished(
-      id_,
-      root_node_manager_->next_server_change_id(),
-      NodesToINodes(to_send));
-
-  return true;
-}
-
 Array<INode> ViewManagerConnection::NodesToINodes(
     const std::vector<const Node*>& nodes) {
   Array<INode>::Builder array_builder(nodes.size());
@@ -580,16 +571,6 @@
   callback.Run(true);
 }
 
-void ViewManagerConnection::SetRoots(
-    TransportConnectionId connection_id,
-    const Array<TransportNodeId>& transport_node_ids,
-    const Callback<void(bool)>& callback) {
-  ViewManagerConnection* connection =
-      root_node_manager_->GetConnection(connection_id);
-  callback.Run(connection &&
-               connection->ProcessSetRoots(id_, transport_node_ids));
-}
-
 void ViewManagerConnection::SetNodeBounds(
     TransportNodeId node_id,
     const Rect& bounds,
@@ -614,6 +595,15 @@
   callback.Run(true);
 }
 
+void ViewManagerConnection::Connect(const String& url,
+                                    const Array<uint32_t>& node_ids,
+                                    const Callback<void(bool)>& callback) {
+  const bool success = CanConnect(node_ids);
+  if (success)
+    root_node_manager_->Connect(url, node_ids);
+  callback.Run(success);
+}
+
 void ViewManagerConnection::OnNodeHierarchyChanged(const Node* node,
                                                    const Node* new_parent,
                                                    const Node* old_parent) {
@@ -628,10 +618,19 @@
 
 void ViewManagerConnection::OnConnectionEstablished() {
   DCHECK_EQ(0, id_);  // Should only get OnConnectionEstablished() once.
+
   id_ = root_node_manager_->GetAndAdvanceNextConnectionId();
+
   root_node_manager_->AddConnection(this);
+
   std::vector<const Node*> to_send;
-  GetUnknownNodesFrom(root_node_manager_->root(), &to_send);
+  if (roots_.empty()) {
+    GetUnknownNodesFrom(root_node_manager_->root(), &to_send);
+  } else {
+    for (NodeIdSet::const_iterator i = roots_.begin(); i != roots_.end(); ++i)
+      GetUnknownNodesFrom(GetNode(NodeIdFromTransportId(*i)), &to_send);
+  }
+
   AllocationScope allocation_scope;
   client()->OnViewManagerConnectionEstablished(
       id_,
diff --git a/mojo/services/view_manager/view_manager_connection.h b/mojo/services/view_manager/view_manager_connection.h
index a8849b3..9ae7d50 100644
--- a/mojo/services/view_manager/view_manager_connection.h
+++ b/mojo/services/view_manager/view_manager_connection.h
@@ -40,7 +40,7 @@
     : public InterfaceImpl<IViewManager>,
       public NodeDelegate {
  public:
-  ViewManagerConnection(RootNodeManager* root_node_manager);
+  explicit ViewManagerConnection(RootNodeManager* root_node_manager);
   virtual ~ViewManagerConnection();
 
   TransportConnectionId id() const { return id_; }
@@ -59,6 +59,8 @@
   }
   const View* GetView(const ViewId& id) const;
 
+  void SetRoots(const Array<TransportNodeId>& node_ids);
+
   // The following methods are invoked after the corresponding change has been
   // processed. They do the appropriate bookkeeping and update the client as
   // necessary.
@@ -80,6 +82,11 @@
                           bool originated_change);
   void ProcessViewDeleted(const ViewId& view, bool originated_change);
 
+  // TODO(sky): move this to private section (currently can't because of
+  // bindings).
+  // InterfaceImp overrides:
+  virtual void OnConnectionError() MOJO_OVERRIDE;
+
  private:
   typedef std::map<TransportConnectionSpecificNodeId, Node*> NodeMap;
   typedef std::map<TransportConnectionSpecificViewId, View*> ViewMap;
@@ -93,6 +100,7 @@
   bool CanDeleteView(const ViewId& view_id) const;
   bool CanSetView(const Node* node, const ViewId& view_id) const;
   bool CanGetNodeTree(const Node* node) const;
+  bool CanConnect(const mojo::Array<uint32_t>& node_ids) const;
 
   // Deletes a node owned by this connection. Returns true on success. |source|
   // is the connection that originated the change.
@@ -125,9 +133,6 @@
                                      const Node** old_parent,
                                      std::vector<const Node*>* to_send);
 
-  bool ProcessSetRoots(TransportConnectionId source_connection_id,
-                       const Array<TransportNodeId>& transport_node_ids);
-
   // Converts an array of Nodes to INodes. This assumes all the nodes are valid
   // for the client. The parent of nodes the client is not allowed to see are
   // set to NULL (in the returned INodes).
@@ -160,13 +165,12 @@
                                ScopedSharedBufferHandle buffer,
                                uint32_t buffer_size,
                                const Callback<void(bool)>& callback) OVERRIDE;
-  virtual void SetRoots(
-      TransportConnectionId connection_id,
-      const Array<TransportNodeId>& transport_node_ids,
-      const Callback<void(bool)>& callback) OVERRIDE;
   virtual void SetNodeBounds(TransportNodeId node_id,
                              const Rect& bounds,
                              const Callback<void(bool)>& callback) OVERRIDE;
+  virtual void Connect(const mojo::String& url,
+                       const mojo::Array<uint32_t>& node_ids,
+                       const mojo::Callback<void(bool)>& callback) OVERRIDE;
 
   // Overridden from NodeDelegate:
   virtual void OnNodeHierarchyChanged(const Node* node,
diff --git a/mojo/services/view_manager/view_manager_connection_unittest.cc b/mojo/services/view_manager/view_manager_connection_unittest.cc
index 7153ed4..aaa78e5 100644
--- a/mojo/services/view_manager/view_manager_connection_unittest.cc
+++ b/mojo/services/view_manager/view_manager_connection_unittest.cc
@@ -7,17 +7,21 @@
 
 #include "base/bind.h"
 #include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/strings/stringprintf.h"
 #include "mojo/common/common_type_converters.h"
+#include "mojo/public/cpp/application/application.h"
 #include "mojo/public/cpp/application/connect.h"
 #include "mojo/public/cpp/bindings/allocation_scope.h"
 #include "mojo/public/cpp/environment/environment.h"
+#include "mojo/service_manager/service_manager.h"
 #include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
 #include "mojo/services/public/cpp/view_manager/util.h"
 #include "mojo/services/public/cpp/view_manager/view_manager_types.h"
 #include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h"
+#include "mojo/services/view_manager/test_change_tracker.h"
 #include "mojo/shell/shell_test_helper.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/geometry/rect.h"
@@ -45,6 +49,353 @@
 
 base::RunLoop* current_run_loop = NULL;
 
+const char kTestServiceURL[] = "mojo:test_url";
+
+void INodesToTestNodes(const Array<INode>& data,
+                       std::vector<TestNode>* test_nodes) {
+  for (size_t i = 0; i < data.size(); ++i) {
+    TestNode node;
+    node.parent_id = data[i].parent_id();
+    node.node_id = data[i].node_id();
+    node.view_id = data[i].view_id();
+    test_nodes->push_back(node);
+  }
+}
+
+// BackgroundConnection is used when a child ViewManagerConnection is created by
+// way of Connect(). BackgroundConnection is created on a background thread (the
+// thread where the shell lives). All public methods are to be invoked on the
+// main thread. They wait for a result (by spinning a nested message loop),
+// calling through to background thread, then the underlying IViewManager* and
+// respond back to the calling thread to return control to the test.
+class BackgroundConnection : public TestChangeTracker::Delegate {
+ public:
+  BackgroundConnection(TestChangeTracker* tracker,
+                       base::MessageLoop* loop)
+      : tracker_(tracker),
+        main_loop_(loop),
+        background_loop_(base::MessageLoop::current()),
+        view_manager_(NULL),
+        quit_count_(0) {
+    main_loop_->PostTask(FROM_HERE,
+                         base::Bind(&BackgroundConnection::SetInstance, this));
+  }
+
+  virtual ~BackgroundConnection() {
+    instance_ = NULL;
+  }
+
+  void set_view_manager(IViewManager* view_manager) {
+    view_manager_ = view_manager;
+  }
+
+  // Runs a message loop until the single instance has been created.
+  static BackgroundConnection* WaitForInstance() {
+    if (!instance_)
+      RunMainLoop();
+    return instance_;
+  }
+
+  // Runs the main loop until |count| changes have been received.
+  std::vector<Change> DoRunLoopUntilChangesCount(size_t count) {
+    background_loop_->PostTask(FROM_HERE,
+                               base::Bind(&BackgroundConnection::SetQuitCount,
+                                          base::Unretained(this), count));
+    // Run the current message loop. When the quit count is reached we'll quit.
+    RunMainLoop();
+    return changes_;
+  }
+
+  // The following functions mirror that of IViewManager. They bounce the
+  // function to the right thread and return the result.
+  bool CreateNode(TransportNodeId node_id) {
+    bool result = false;
+    background_loop_->PostTask(
+        FROM_HERE,
+        base::Bind(&BackgroundConnection::CreateNodeOnBackgroundThread,
+                   base::Unretained(this), node_id, &result));
+    RunMainLoop();
+    return result;
+  }
+  bool AddNode(TransportNodeId parent,
+               TransportNodeId child,
+               TransportChangeId server_change_id) {
+    bool result = false;
+    background_loop_->PostTask(
+        FROM_HERE,
+        base::Bind(&BackgroundConnection::AddNodeOnBackgroundThread,
+                   base::Unretained(this), parent, child, server_change_id,
+                   &result));
+    RunMainLoop();
+    return result;
+  }
+  bool RemoveNodeFromParent(TransportNodeId node_id,
+                            TransportChangeId server_change_id) {
+    bool result = false;
+    background_loop_->PostTask(
+        FROM_HERE,
+        base::Bind(
+            &BackgroundConnection::RemoveNodeFromParentOnBackgroundThread,
+            base::Unretained(this), node_id, server_change_id, &result));
+    RunMainLoop();
+    return result;
+  }
+  bool SetView(TransportNodeId node_id, TransportViewId view_id) {
+    bool result = false;
+    background_loop_->PostTask(
+        FROM_HERE,
+        base::Bind(
+            &BackgroundConnection::SetViewOnBackgroundThread,
+            base::Unretained(this), node_id, view_id, &result));
+    RunMainLoop();
+    return result;
+  }
+  bool CreateView(TransportViewId view_id) {
+    bool result = false;
+    background_loop_->PostTask(
+        FROM_HERE,
+        base::Bind(
+            &BackgroundConnection::CreateViewOnBackgroundThread,
+            base::Unretained(this), view_id, &result));
+    RunMainLoop();
+    return result;
+  }
+  void GetNodeTree(TransportNodeId node_id, std::vector<TestNode>* nodes) {
+    background_loop_->PostTask(
+        FROM_HERE,
+        base::Bind(
+            &BackgroundConnection::GetNodeTreeOnBackgroundThread,
+            base::Unretained(this), node_id, nodes));
+    RunMainLoop();
+  }
+
+ private:
+  void SetQuitCount(size_t count) {
+    DCHECK_EQ(background_loop_, base::MessageLoop::current());
+    if (tracker_->changes()->size() >= count) {
+      QuitCountReached();
+      return;
+    }
+    quit_count_ = count - tracker_->changes()->size();
+  }
+
+  static void RunMainLoop() {
+    DCHECK(!main_run_loop_);
+    main_run_loop_ = new base::RunLoop;
+    main_run_loop_->Run();
+    delete main_run_loop_;
+    main_run_loop_ = NULL;
+  }
+
+  void QuitCountReached() {
+    std::vector<Change> changes;
+    tracker_->changes()->swap(changes);
+    main_loop_->PostTask(
+        FROM_HERE,
+        base::Bind(&BackgroundConnection::QuitCountReachedOnMain,
+                   base::Unretained(this), changes));
+  }
+
+  void QuitCountReachedOnMain(const std::vector<Change>& changes) {
+    changes_ = changes;
+    DCHECK(main_run_loop_);
+    main_run_loop_->Quit();
+  }
+
+  static void SetInstance(BackgroundConnection* instance) {
+    DCHECK(!instance_);
+    instance_ = instance;
+    if (main_run_loop_)
+      main_run_loop_->Quit();
+  }
+
+  // Callbacks from the various IViewManager functions.
+  void GotResultOnBackgroundThread(bool* result_cache, bool result) {
+    *result_cache = result;
+    main_loop_->PostTask(
+        FROM_HERE,
+        base::Bind(&BackgroundConnection::QuitOnMainThread,
+                   base::Unretained(this)));
+  }
+
+  void GotNodeTreeOnBackgroundThread(std::vector<TestNode>* nodes,
+                                     const Array<INode>& results) {
+    INodesToTestNodes(results, nodes);
+    main_loop_->PostTask(
+        FROM_HERE,
+        base::Bind(&BackgroundConnection::QuitOnMainThread,
+                   base::Unretained(this)));
+  }
+
+  void QuitOnMainThread() {
+    DCHECK(main_run_loop_);
+    main_run_loop_->Quit();
+  }
+
+  // The following functions correspond to the IViewManager functions. These run
+  // on the background thread.
+  void CreateNodeOnBackgroundThread(TransportNodeId node_id, bool* result) {
+    view_manager_->CreateNode(
+        node_id,
+        base::Bind(&BackgroundConnection::GotResultOnBackgroundThread,
+                   base::Unretained(this), result));
+  }
+  void AddNodeOnBackgroundThread(TransportNodeId parent,
+                                 TransportNodeId child,
+                                 TransportChangeId server_change_id,
+                                 bool* result) {
+    view_manager_->AddNode(
+        parent, child, server_change_id,
+        base::Bind(&BackgroundConnection::GotResultOnBackgroundThread,
+                   base::Unretained(this), result));
+  }
+  void RemoveNodeFromParentOnBackgroundThread(
+      TransportNodeId node_id,
+      TransportChangeId server_change_id,
+      bool* result) {
+    view_manager_->RemoveNodeFromParent(node_id, server_change_id,
+        base::Bind(&BackgroundConnection::GotResultOnBackgroundThread,
+                   base::Unretained(this), result));
+  }
+  void SetViewOnBackgroundThread(TransportNodeId node_id,
+                                 TransportViewId view_id,
+                                 bool* result) {
+    view_manager_->SetView(node_id, view_id,
+        base::Bind(&BackgroundConnection::GotResultOnBackgroundThread,
+                   base::Unretained(this), result));
+  }
+  void CreateViewOnBackgroundThread(TransportViewId view_id, bool* result) {
+    view_manager_->CreateView(view_id,
+        base::Bind(&BackgroundConnection::GotResultOnBackgroundThread,
+                   base::Unretained(this), result));
+  }
+  void GetNodeTreeOnBackgroundThread(TransportNodeId node_id,
+                                     std::vector<TestNode>* nodes) {
+    view_manager_->GetNodeTree(node_id,
+        base::Bind(&BackgroundConnection::GotNodeTreeOnBackgroundThread,
+                   base::Unretained(this), nodes));
+  }
+
+  // TestChangeTracker::Delegate:
+  virtual void OnChangeAdded() OVERRIDE {
+    if (quit_count_ > 0 && --quit_count_ == 0)
+      QuitCountReached();
+  }
+
+  static BackgroundConnection* instance_;
+  static base::RunLoop* main_run_loop_;
+
+  TestChangeTracker* tracker_;
+
+  // MessageLoop of the test.
+  base::MessageLoop* main_loop_;
+
+  // MessageLoop BackgroundConnection lives on.
+  base::MessageLoop* background_loop_;
+
+  IViewManager* view_manager_;
+
+  // Number of changes we're waiting on until we quit the current loop.
+  size_t quit_count_;
+
+  std::vector<Change> changes_;
+
+  DISALLOW_COPY_AND_ASSIGN(BackgroundConnection);
+};
+
+// static
+BackgroundConnection* BackgroundConnection::instance_ = NULL;
+
+// static
+base::RunLoop* BackgroundConnection::main_run_loop_ = NULL;
+
+class TestViewManagerClientConnection
+    : public InterfaceImpl<IViewManagerClient> {
+ public:
+  explicit TestViewManagerClientConnection(base::MessageLoop* loop)
+      : connection_(&tracker_, loop) {
+    tracker_.set_delegate(&connection_);
+  }
+
+  // InterfaceImp:
+  virtual void OnConnectionEstablished() OVERRIDE {
+    connection_.set_view_manager(client());
+  }
+
+  // IViewMangerClient:
+  virtual void OnViewManagerConnectionEstablished(
+      TransportConnectionId connection_id,
+      TransportChangeId next_server_change_id,
+      const Array<INode>& nodes) OVERRIDE {
+    tracker_.OnViewManagerConnectionEstablished(
+        connection_id, next_server_change_id, nodes);
+  }
+  virtual void OnServerChangeIdAdvanced(
+      TransportChangeId next_server_change_id) OVERRIDE {
+    tracker_.OnServerChangeIdAdvanced(next_server_change_id);
+  }
+  virtual void OnNodeBoundsChanged(TransportNodeId node_id,
+                                   const Rect& old_bounds,
+                                   const Rect& new_bounds) OVERRIDE {
+    tracker_.OnNodeBoundsChanged(node_id, old_bounds, new_bounds);
+  }
+  virtual void OnNodeHierarchyChanged(
+      TransportNodeId node,
+      TransportNodeId new_parent,
+      TransportNodeId old_parent,
+      TransportChangeId server_change_id,
+      const Array<INode>& nodes) OVERRIDE {
+    tracker_.OnNodeHierarchyChanged(node, new_parent, old_parent,
+                                     server_change_id, nodes);
+  }
+  virtual void OnNodeDeleted(TransportNodeId node,
+                             TransportChangeId server_change_id) OVERRIDE {
+    tracker_.OnNodeDeleted(node, server_change_id);
+  }
+  virtual void OnViewDeleted(TransportViewId view) OVERRIDE {
+    tracker_.OnViewDeleted(view);
+  }
+  virtual void OnNodeViewReplaced(TransportNodeId node,
+                                  TransportViewId new_view_id,
+                                  TransportViewId old_view_id) OVERRIDE {
+    tracker_.OnNodeViewReplaced(node, new_view_id, old_view_id);
+  }
+
+ private:
+  TestChangeTracker tracker_;
+  BackgroundConnection connection_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestViewManagerClientConnection);
+};
+
+// Used with IViewManager::Connect(). Creates a TestViewManagerClientConnection,
+// which creates and owns the BackgroundConnection.
+class ConnectServiceLoader : public ServiceLoader {
+ public:
+  ConnectServiceLoader() : initial_loop_(base::MessageLoop::current()) {
+  }
+  virtual ~ConnectServiceLoader() {
+  }
+
+  // ServiceLoader:
+  virtual void LoadService(ServiceManager* manager,
+                           const GURL& url,
+                           ScopedMessagePipeHandle shell_handle) OVERRIDE {
+    scoped_ptr<Application> app(new Application(shell_handle.Pass()));
+    app->AddService<TestViewManagerClientConnection>(initial_loop_);
+    apps_.push_back(app.release());
+  }
+  virtual void OnServiceError(ServiceManager* manager,
+                              const GURL& url) OVERRIDE {
+  }
+
+ private:
+  base::MessageLoop* initial_loop_;
+  ScopedVector<Application> apps_;
+
+  DISALLOW_COPY_AND_ASSIGN(ConnectServiceLoader);
+};
+
 // Sets |current_run_loop| and runs it. It is expected that someone else quits
 // the loop.
 void DoRunLoop() {
@@ -56,21 +407,6 @@
   current_run_loop = NULL;
 }
 
-// Converts |id| into a string.
-std::string NodeIdToString(TransportNodeId id) {
-  return (id == 0) ? "null" :
-      base::StringPrintf("%d,%d", HiWord(id), LoWord(id));
-}
-
-// Converts |rect| into a string.
-std::string RectToString(const Rect& rect) {
-  return base::StringPrintf("%d,%d %dx%d",
-                            rect.position().x(),
-                            rect.position().y(),
-                            rect.size().width(),
-                            rect.size().height());
-}
-
 // Boolean callback. Sets |result_cache| to the value of |result| and quits
 // the run loop.
 void BooleanCallback(bool* result_cache, bool result) {
@@ -78,34 +414,10 @@
   current_run_loop->Quit();
 }
 
-struct TestNode {
-  std::string ToString() const {
-    return base::StringPrintf("node=%s parent=%s view=%s",
-                              NodeIdToString(node_id).c_str(),
-                              NodeIdToString(parent_id).c_str(),
-                              NodeIdToString(view_id).c_str());
-  }
-
-  TransportNodeId parent_id;
-  TransportNodeId node_id;
-  TransportNodeId view_id;
-};
-
-void INodesToTestNodes(const mojo::Array<INode>& data,
-                       std::vector<TestNode>* test_nodes) {
-  for (size_t i = 0; i < data.size(); ++i) {
-    TestNode node;
-    node.parent_id = data[i].parent_id();
-    node.node_id = data[i].node_id();
-    node.view_id = data[i].view_id();
-    test_nodes->push_back(node);
-  }
-}
-
 // Callback that results in a vector of INodes. The INodes are converted to
 // TestNodes.
 void INodesCallback(std::vector<TestNode>* test_nodes,
-                    const mojo::Array<INode>& data) {
+                    const Array<INode>& data) {
   INodesToTestNodes(data, test_nodes);
   current_run_loop->Quit();
 }
@@ -214,13 +526,18 @@
   return result;
 }
 
-bool SetRoots(IViewManager* view_manager,
-              TransportConnectionId connection_id,
-              const std::vector<uint32_t>& node_ids) {
+bool Connect(IViewManager* view_manager,
+             const std::string& url,
+             TransportNodeId id,
+             TransportNodeId id2) {
+  AllocationScope scope;
   bool result = false;
-  view_manager->SetRoots(connection_id,
-                         Array<uint32_t>::From(node_ids),
-                         base::Bind(&BooleanCallback, &result));
+  std::vector<TransportNodeId> node_ids;
+  node_ids.push_back(id);
+  if (id2 != 0)
+    node_ids.push_back(id2);
+  view_manager->Connect(url, Array<uint32_t>::From(node_ids),
+                        base::Bind(&BooleanCallback, &result));
   DoRunLoop();
   return result;
 }
@@ -229,12 +546,40 @@
 
 typedef std::vector<std::string> Changes;
 
+class MainLoopTrackerDelegate : public TestChangeTracker::Delegate {
+ public:
+  explicit MainLoopTrackerDelegate(TestChangeTracker* tracker)
+      : tracker_(tracker),
+        quit_count_(0) {}
+
+  void DoRunLoopUntilChangesCount(size_t count) {
+    if (tracker_->changes()->size() >= count)
+      return;
+    quit_count_ = count - tracker_->changes()->size();
+    DoRunLoop();
+  }
+
+  // TestChangeTracker::Delegate:
+  virtual void OnChangeAdded() OVERRIDE {
+    if (quit_count_ > 0 && --quit_count_ == 0)
+      current_run_loop->Quit();
+  }
+
+ private:
+  TestChangeTracker* tracker_;
+  size_t quit_count_;
+
+  DISALLOW_COPY_AND_ASSIGN(MainLoopTrackerDelegate);
+};
+
 class ViewManagerClientImpl : public IViewManagerClient {
  public:
   ViewManagerClientImpl()
       : id_(0),
         next_server_change_id_(0),
-        quit_count_(0) {}
+        main_loop_tracker_delegate_(&tracker_) {
+    tracker_.set_delegate(&main_loop_tracker_delegate_);
+  }
 
   TransportConnectionId id() const { return id_; }
 
@@ -249,8 +594,8 @@
   }
 
   Changes GetAndClearChanges() {
-    Changes changes;
-    changes.swap(changes_);
+    Changes changes = ChangesToDescription1(*tracker_.changes());
+    tracker_.changes()->clear();
     return changes;
   }
 
@@ -264,10 +609,7 @@
   }
 
   void DoRunLoopUntilChangesCount(size_t count) {
-    if (changes_.size() >= count)
-      return;
-    quit_count_ = count - changes_.size();
-    DoRunLoop();
+    main_loop_tracker_delegate_.DoRunLoopUntilChangesCount(count);
   }
 
  private:
@@ -275,103 +617,65 @@
   virtual void OnViewManagerConnectionEstablished(
       TransportConnectionId connection_id,
       TransportChangeId next_server_change_id,
-      const mojo::Array<INode>& nodes) OVERRIDE {
+      const Array<INode>& nodes) OVERRIDE {
     id_ = connection_id;
     next_server_change_id_ = next_server_change_id;
     initial_nodes_.clear();
     INodesToTestNodes(nodes, &initial_nodes_);
-    changes_.push_back("OnConnectionEstablished");
-    QuitIfNecessary();
+    tracker_.OnViewManagerConnectionEstablished(
+        connection_id, next_server_change_id, nodes);
   }
   virtual void OnServerChangeIdAdvanced(
-      uint32_t next_server_change_id) OVERRIDE {
-    changes_.push_back(
-        base::StringPrintf(
-            "ServerChangeIdAdvanced %d",
-            static_cast<int>(next_server_change_id)));
-    QuitIfNecessary();
+      TransportChangeId next_server_change_id) OVERRIDE {
+    tracker_.OnServerChangeIdAdvanced(next_server_change_id);
   }
   virtual void OnNodeBoundsChanged(TransportNodeId node_id,
                                    const Rect& old_bounds,
                                    const Rect& new_bounds) OVERRIDE {
-    changes_.push_back(
-        base::StringPrintf(
-            "BoundsChanged node=%s old_bounds=%s new_bounds=%s",
-            NodeIdToString(node_id).c_str(),
-            RectToString(old_bounds).c_str(),
-            RectToString(new_bounds).c_str()));
-    QuitIfNecessary();
+    tracker_.OnNodeBoundsChanged(node_id, old_bounds, new_bounds);
   }
   virtual void OnNodeHierarchyChanged(
       TransportNodeId node,
       TransportNodeId new_parent,
       TransportNodeId old_parent,
       TransportChangeId server_change_id,
-      const mojo::Array<INode>& nodes) OVERRIDE {
-    changes_.push_back(
-        base::StringPrintf(
-            "HierarchyChanged change_id=%d node=%s new_parent=%s old_parent=%s",
-            static_cast<int>(server_change_id),
-            NodeIdToString(node).c_str(),
-            NodeIdToString(new_parent).c_str(),
-            NodeIdToString(old_parent).c_str()));
+      const Array<INode>& nodes) OVERRIDE {
+    tracker_.OnNodeHierarchyChanged(node, new_parent, old_parent,
+                                    server_change_id, nodes);
     hierarchy_changed_nodes_.clear();
     INodesToTestNodes(nodes, &hierarchy_changed_nodes_);
-    QuitIfNecessary();
   }
   virtual void OnNodeDeleted(TransportNodeId node,
                              TransportChangeId server_change_id) OVERRIDE {
-    changes_.push_back(
-        base::StringPrintf(
-            "NodeDeleted change_id=%d node=%s",
-            static_cast<int>(server_change_id),
-            NodeIdToString(node).c_str()));
-    QuitIfNecessary();
+    tracker_.OnNodeDeleted(node, server_change_id);
   }
   virtual void OnViewDeleted(TransportViewId view) OVERRIDE {
-    changes_.push_back(
-        base::StringPrintf(
-            "ViewDeleted view=%s",
-            NodeIdToString(view).c_str()));
-    QuitIfNecessary();
+    tracker_.OnViewDeleted(view);
   }
   virtual void OnNodeViewReplaced(TransportNodeId node,
                                   TransportViewId new_view_id,
                                   TransportViewId old_view_id) OVERRIDE {
-    changes_.push_back(
-        base::StringPrintf(
-            "ViewReplaced node=%s new_view=%s old_view=%s",
-            NodeIdToString(node).c_str(),
-            NodeIdToString(new_view_id).c_str(),
-            NodeIdToString(old_view_id).c_str()));
-    QuitIfNecessary();
-  }
-
-  void QuitIfNecessary() {
-    if (quit_count_ > 0 && --quit_count_ == 0)
-      current_run_loop->Quit();
+    tracker_.OnNodeViewReplaced(node, new_view_id, old_view_id);
   }
 
   TransportConnectionId id_;
   TransportChangeId next_server_change_id_;
 
-  // Used to determine when/if to quit the run loop.
-  size_t quit_count_;
-
-  Changes changes_;
-
   // Set of nodes sent when connection created.
   std::vector<TestNode> initial_nodes_;
 
   // Nodes sent from last OnNodeHierarchyChanged.
   std::vector<TestNode> hierarchy_changed_nodes_;
 
+  TestChangeTracker tracker_;
+  MainLoopTrackerDelegate main_loop_tracker_delegate_;
+
   DISALLOW_COPY_AND_ASSIGN(ViewManagerClientImpl);
 };
 
 class ViewManagerConnectionTest : public testing::Test {
  public:
-  ViewManagerConnectionTest() {}
+  ViewManagerConnectionTest() : background_connection_(NULL) {}
 
   virtual void SetUp() OVERRIDE {
     test_helper_.Init();
@@ -383,6 +687,10 @@
 
     client_.WaitForId();
     client_.GetAndClearChanges();
+
+    test_helper_.SetLoaderForURL(
+        scoped_ptr<ServiceLoader>(new ConnectServiceLoader()),
+        GURL(kTestServiceURL));
   }
 
  protected:
@@ -397,22 +705,22 @@
     client2_.GetAndClearChanges();
   }
 
-  void EstablishSecondConnectionWithRoot(TransportNodeId root_id) {
-    EstablishSecondConnection();
-    client2_.ClearId();
+  std::vector<Change> EstablishBackgroundConnectionWithRoots(
+      TransportNodeId id1,
+      TransportNodeId id2) {
+    Connect(view_manager_.get(), kTestServiceURL, id1, id2);
+    background_connection_ = BackgroundConnection::WaitForInstance();
+    return background_connection_->DoRunLoopUntilChangesCount(1);
+  }
 
-    AllocationScope scope;
-    std::vector<uint32_t> roots;
-    roots.push_back(root_id);
-    ASSERT_TRUE(SetRoots(view_manager_.get(), 2, roots));
-    client2_.DoRunLoopUntilChangesCount(1);
-    Changes changes(client2_.GetAndClearChanges());
+  void EstablishBackgroundConnectionWithRoot1() {
+    std::vector<Change> changes(
+        EstablishBackgroundConnectionWithRoots(CreateNodeId(1, 1), 0));
+    ASSERT_NO_FATAL_FAILURE();
     ASSERT_EQ(1u, changes.size());
-    EXPECT_EQ("OnConnectionEstablished", changes[0]);
-    ASSERT_NE(0u, client2_.id());
-    const std::vector<TestNode>& nodes(client2_.initial_nodes());
-    ASSERT_EQ(1u, nodes.size());
-    EXPECT_EQ("node=1,1 parent=null view=null", nodes[0].ToString());
+    EXPECT_EQ("OnConnectionEstablished", ChangesToDescription1(changes)[0]);
+    EXPECT_EQ("[node=1,1 parent=null view=null]",
+              ChangeNodeDescription(changes));
   }
 
   void DestroySecondConnection() {
@@ -428,6 +736,8 @@
   ViewManagerClientImpl client2_;
   IViewManagerPtr view_manager2_;
 
+  BackgroundConnection* background_connection_;
+
   DISALLOW_COPY_AND_ASSIGN(ViewManagerConnectionTest);
 };
 
@@ -1222,23 +1532,15 @@
                       1));
 
   // Establish the second connection and give it the roots 1 and 3.
-  EstablishSecondConnection();
-  client2_.ClearId();
   {
-    AllocationScope scope;
-    std::vector<uint32_t> roots;
-    roots.push_back(CreateNodeId(1, 1));
-    roots.push_back(CreateNodeId(1, 3));
-    ASSERT_TRUE(SetRoots(view_manager_.get(), 2, roots));
-    client2_.DoRunLoopUntilChangesCount(1);
-    Changes changes(client2_.GetAndClearChanges());
+    std::vector<Change> changes(EstablishBackgroundConnectionWithRoots(
+                                    CreateNodeId(1, 1), CreateNodeId(1, 3)));
+    ASSERT_NO_FATAL_FAILURE();
     ASSERT_EQ(1u, changes.size());
-    EXPECT_EQ("OnConnectionEstablished", changes[0]);
-    ASSERT_NE(0u, client2_.id());
-    const std::vector<TestNode>& nodes(client2_.initial_nodes());
-    ASSERT_EQ(2u, nodes.size());
-    EXPECT_EQ("node=1,1 parent=null view=null", nodes[0].ToString());
-    EXPECT_EQ("node=1,3 parent=null view=null", nodes[1].ToString());
+    EXPECT_EQ("OnConnectionEstablished", ChangesToDescription1(changes)[0]);
+    EXPECT_EQ("[node=1,1 parent=null view=null],"
+              "[node=1,3 parent=null view=null]",
+              ChangeNodeDescription(changes));
   }
 
   // Create 4 and add it to the root, connection 2 should only get id advanced.
@@ -1248,8 +1550,8 @@
                         CreateNodeId(0, 1),
                         CreateNodeId(client_.id(), 4),
                         2));
-    client2_.DoRunLoopUntilChangesCount(1);
-    Changes changes(client2_.GetAndClearChanges());
+    Changes changes = ChangesToDescription1(
+        background_connection_->DoRunLoopUntilChangesCount(1));
     ASSERT_EQ(1u, changes.size());
     EXPECT_EQ("ServerChangeIdAdvanced 3", changes[0]);
   }
@@ -1260,15 +1562,15 @@
                         CreateNodeId(1, 3),
                         CreateNodeId(1, 4),
                         3));
-    client2_.DoRunLoopUntilChangesCount(1);
-    Changes changes(client2_.GetAndClearChanges());
-    ASSERT_EQ(1u, changes.size());
+    std::vector<Change> changes =
+        background_connection_->DoRunLoopUntilChangesCount(1);
+    Changes change_strings(ChangesToDescription1(changes));
+    ASSERT_EQ(1u, change_strings.size());
     EXPECT_EQ(
         "HierarchyChanged change_id=3 node=1,4 new_parent=1,3 "
-        "old_parent=null", changes[0]);
-    const std::vector<TestNode>& nodes(client2_.hierarchy_changed_nodes());
-    ASSERT_EQ(1u, nodes.size());
-    EXPECT_EQ("node=1,4 parent=1,3 view=null", nodes[0].ToString());
+        "old_parent=null", change_strings[0]);
+    EXPECT_EQ("[node=1,4 parent=1,3 view=null]",
+              ChangeNodeDescription(changes));
   }
 
   // Move 4 under 2, since 2 isn't a root client should get a delete.
@@ -1277,8 +1579,8 @@
                         CreateNodeId(1, 2),
                         CreateNodeId(1, 4),
                         4));
-    client2_.DoRunLoopUntilChangesCount(1);
-    Changes changes(client2_.GetAndClearChanges());
+    Changes changes = ChangesToDescription1(
+        background_connection_->DoRunLoopUntilChangesCount(1));
     ASSERT_EQ(1u, changes.size());
     EXPECT_EQ("NodeDeleted change_id=4 node=1,4", changes[0]);
   }
@@ -1289,8 +1591,8 @@
     ASSERT_TRUE(DeleteNode(view_manager_.get(), CreateNodeId(client_.id(), 4)));
     ASSERT_TRUE(client_.GetAndClearChanges().empty());
 
-    client2_.DoRunLoopUntilChangesCount(1);
-    Changes changes(client2_.GetAndClearChanges());
+    Changes changes = ChangesToDescription1(
+        background_connection_->DoRunLoopUntilChangesCount(1));
     ASSERT_EQ(1u, changes.size());
     EXPECT_EQ("ServerChangeIdAdvanced 6", changes[0]);
   }
@@ -1302,26 +1604,19 @@
   ASSERT_TRUE(CreateNode(view_manager_.get(), 1, 1));
   ASSERT_TRUE(CreateNode(view_manager_.get(), 1, 2));
 
-  // Establish the second connection and give it the root 1.
-  ASSERT_NO_FATAL_FAILURE(
-      EstablishSecondConnectionWithRoot(CreateNodeId(1, 1)));
+  ASSERT_NO_FATAL_FAILURE(EstablishBackgroundConnectionWithRoot1());
 
   // Try to move 2 to be a child of 1 from connection 2. This should fail as 2
   // should not be able to access 1.
-  ASSERT_FALSE(AddNode(view_manager2_.get(),
-                       CreateNodeId(1, 1),
-                       CreateNodeId(1, 2),
-                       1));
+  ASSERT_FALSE(background_connection_->AddNode(
+                   CreateNodeId(1, 1), CreateNodeId(1, 2), 1));
 
   // Try to reparent 1 to the root. A connection is not allowed to reparent its
   // roots.
-  ASSERT_FALSE(AddNode(view_manager2_.get(),
-                       CreateNodeId(0, 1),
-                       CreateNodeId(1, 1),
-                       1));
+  ASSERT_FALSE(background_connection_->AddNode(CreateNodeId(0, 1),
+                                               CreateNodeId(1, 1), 1));
 }
 
-
 // Verify RemoveNodeFromParent fails for nodes that are descendants of the
 // roots.
 TEST_F(ViewManagerConnectionTest, CantRemoveNodesInOtherRoots) {
@@ -1339,30 +1634,24 @@
                       2));
 
   // Establish the second connection and give it the root 1.
-  ASSERT_NO_FATAL_FAILURE(
-      EstablishSecondConnectionWithRoot(CreateNodeId(1, 1)));
+  ASSERT_NO_FATAL_FAILURE(EstablishBackgroundConnectionWithRoot1());
 
   // Connection 2 should not be able to remove node 2 or 1 from its parent.
-  ASSERT_FALSE(RemoveNodeFromParent(view_manager2_.get(),
-                                    CreateNodeId(1, 2),
-                                    3));
-  ASSERT_FALSE(RemoveNodeFromParent(view_manager2_.get(),
-                                    CreateNodeId(1, 1),
-                                    3));
+  ASSERT_FALSE(background_connection_->RemoveNodeFromParent(
+                   CreateNodeId(1, 2), 3));
+  ASSERT_FALSE(background_connection_->RemoveNodeFromParent(CreateNodeId(1, 1),
+                                                            3));
 
   // Create nodes 10 and 11 in 2.
-  ASSERT_TRUE(CreateNode(view_manager2_.get(), 2, 10));
-  ASSERT_TRUE(CreateNode(view_manager2_.get(), 2, 11));
+  ASSERT_TRUE(background_connection_->CreateNode(CreateNodeId(2, 10)));
+  ASSERT_TRUE(background_connection_->CreateNode(CreateNodeId(2, 11)));
 
   // Parent 11 to 10.
-  ASSERT_TRUE(AddNode(view_manager2_.get(),
-                      CreateNodeId(client2_.id(), 10),
-                      CreateNodeId(client2_.id(), 11),
-                      3));
+  ASSERT_TRUE(background_connection_->AddNode(CreateNodeId(2, 10),
+                                              CreateNodeId(2, 11), 3));
   // Remove 11 from 10.
-  ASSERT_TRUE(RemoveNodeFromParent(view_manager2_.get(),
-                                   CreateNodeId(2, 11),
-                                   4));
+  ASSERT_TRUE(background_connection_->RemoveNodeFromParent(
+                  CreateNodeId(2, 11), 4));
 
   // Verify nothing was actually removed.
   {
@@ -1391,21 +1680,17 @@
                       CreateNodeId(client_.id(), 2),
                       2));
 
-  // Establish the second connection and give it the root 1.
-  ASSERT_NO_FATAL_FAILURE(
-      EstablishSecondConnectionWithRoot(CreateNodeId(1, 1)));
+  ASSERT_NO_FATAL_FAILURE(EstablishBackgroundConnectionWithRoot1());
 
   // Create a view in the second connection.
-  ASSERT_TRUE(CreateView(view_manager2_.get(), 2, 51));
+  ASSERT_TRUE(background_connection_->CreateView(CreateViewId(2, 51)));
 
   // Connection 2 should be able to set the view on node 1 (it's root), but not
   // on 2.
-  ASSERT_TRUE(SetView(view_manager2_.get(),
-                      CreateNodeId(client_.id(), 1),
-                      CreateViewId(client2_.id(), 51)));
-  ASSERT_FALSE(SetView(view_manager2_.get(),
-                       CreateNodeId(client_.id(), 2),
-                       CreateViewId(client2_.id(), 51)));
+  ASSERT_TRUE(background_connection_->SetView(CreateNodeId(client_.id(), 1),
+                                              CreateViewId(2, 51)));
+  ASSERT_FALSE(background_connection_->SetView(CreateNodeId(client_.id(), 2),
+                                               CreateViewId(2, 51)));
 }
 
 // Verify GetNodeTree fails for nodes that are not descendants of the roots.
@@ -1423,27 +1708,38 @@
                       CreateNodeId(client_.id(), 2),
                       2));
 
-  // Establish the second connection and give it the root 1.
-  ASSERT_NO_FATAL_FAILURE(
-      EstablishSecondConnectionWithRoot(CreateNodeId(1, 1)));
+  ASSERT_NO_FATAL_FAILURE(EstablishBackgroundConnectionWithRoot1());
 
   AllocationScope scope;
   std::vector<TestNode> nodes;
 
   // Should get nothing for the root.
-  GetNodeTree(view_manager2_.get(), CreateNodeId(0, 1), &nodes);
+  background_connection_->GetNodeTree(CreateNodeId(0, 1), &nodes);
   ASSERT_TRUE(nodes.empty());
 
   // Should get nothing for node 2.
-  GetNodeTree(view_manager2_.get(), CreateNodeId(1, 2), &nodes);
+  background_connection_->GetNodeTree(CreateNodeId(1, 2), &nodes);
   ASSERT_TRUE(nodes.empty());
 
   // Should get node 1 if asked for.
-  GetNodeTree(view_manager2_.get(), CreateNodeId(1, 1), &nodes);
+  background_connection_->GetNodeTree(CreateNodeId(1, 1), &nodes);
   ASSERT_EQ(1u, nodes.size());
   EXPECT_EQ("node=1,1 parent=null view=null", nodes[0].ToString());
 }
 
+// Verify GetNodeTree fails for nodes that are not descendants of the roots.
+TEST_F(ViewManagerConnectionTest, Connect) {
+  ASSERT_TRUE(CreateNode(view_manager_.get(), 1, 1));
+  ASSERT_TRUE(Connect(view_manager_.get(), kTestServiceURL, CreateNodeId(1, 1),
+                      0));
+  BackgroundConnection* instance = BackgroundConnection::WaitForInstance();
+  ASSERT_TRUE(instance != NULL);
+  Changes changes(
+      ChangesToDescription1(instance->DoRunLoopUntilChangesCount(1)));;
+  ASSERT_EQ(1u, changes.size());
+  EXPECT_EQ("OnConnectionEstablished", changes[0]);
+}
+
 // TODO(sky): add coverage of test that destroys connections and ensures other
 // connections get deletion notification (or advanced server id).
 
diff --git a/mojo/shell/shell_test_helper.cc b/mojo/shell/shell_test_helper.cc
index a198e15..4b8f0b8 100644
--- a/mojo/shell/shell_test_helper.cc
+++ b/mojo/shell/shell_test_helper.cc
@@ -7,6 +7,7 @@
 #include "base/command_line.h"
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
+#include "mojo/service_manager/service_loader.h"
 #include "mojo/shell/context.h"
 #include "mojo/shell/init.h"
 
@@ -31,6 +32,12 @@
   state->service_provider_handle = state->test_api->GetServiceProviderHandle();
 }
 
+void SetLoaderForURLOnShellThread(ShellTestHelper::State* state,
+                                  scoped_ptr<ServiceLoader> loader,
+                                  const GURL& url) {
+  state->context->service_manager()->SetLoaderForURL(loader.Pass(), url);
+}
+
 }  // namespace
 
 class ShellTestHelper::TestServiceProvider : public ServiceProvider {
@@ -79,6 +86,14 @@
   run_loop_->Run();
 }
 
+void ShellTestHelper::SetLoaderForURL(scoped_ptr<ServiceLoader> loader,
+                                      const GURL& url) {
+  service_provider_thread_.message_loop()->message_loop_proxy()->PostTask(
+      FROM_HERE,
+      base::Bind(&SetLoaderForURLOnShellThread, state_, base::Passed(&loader),
+                 url));
+}
+
 void ShellTestHelper::OnServiceProviderStarted() {
   DCHECK(state_);
   local_service_provider_.reset(new TestServiceProvider);
diff --git a/mojo/shell/shell_test_helper.h b/mojo/shell/shell_test_helper.h
index ab495cb..56ed4b1 100644
--- a/mojo/shell/shell_test_helper.h
+++ b/mojo/shell/shell_test_helper.h
@@ -12,12 +12,17 @@
 #include "mojo/public/cpp/environment/environment.h"
 #include "mojo/public/interfaces/service_provider/service_provider.mojom.h"
 
+class GURL;
+
 namespace base {
 class MessageLoopProxy;
 class RunLoop;
 }
 
 namespace mojo {
+
+class ServiceLoader;
+
 namespace shell {
 
 // ShellTestHelper is useful for tests to establish a connection to the
@@ -37,6 +42,10 @@
   // ServiceProvider.
   ServiceProvider* service_provider() { return service_provider_.get(); }
 
+  // Sets a ServiceLoader for the specified URL. |loader| is ultimately used on
+  // the thread this class spawns.
+  void SetLoaderForURL(scoped_ptr<ServiceLoader> loader, const GURL& url);
+
  private:
   class TestServiceProvider;