Merge pull request #149 from maqnouch/patch-1

Fix typos in README.md
diff --git a/.gitignore b/.gitignore
index 5e2cce2..6b133e2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,7 +11,6 @@
 .pydevproject
 .checkstyle
 cscope.*
-packages/
 Session.vim
 tags
 Thumbs.db
@@ -68,3 +67,7 @@
 /third_party/yasm/source/patched-yasm/
 /tools/grit/
 /v8/
+# dart packages directories
+/mojo/dart/mojom/packages
+/mojo/dart/mojom/bin/packages
+/mojo/dart/apptest/packages
diff --git a/DEPS b/DEPS
index b16c545..9771e0a 100644
--- a/DEPS
+++ b/DEPS
@@ -25,8 +25,8 @@
   'v8_revision': '230d131d173ab2d60291d303177bc04ec3f6e519',
   'angle_revision': 'bdd419f9f5b006e913606e7363125942c8ae06bc',
   'buildtools_revision': '15308f469a704c45d15567fa69cd94ce07ad0e1b',
-  'dart_revision': '45304',
-  'dart_observatory_packages_revision': '43830',
+  'dart_revision': '45606',
+  'dart_observatory_packages_revision': '45565',
   'pdfium_revision': 'b0115665b0f33971f1b7077740d51e155583cec0',
   'boringssl_revision': '642f1498d056dbba3e50ed5a232ab2f482626dec',
   'lss_revision': 'e079768b7e3a94dcbe7d338496c0c3bde7151b6e',
@@ -256,6 +256,17 @@
     ],
   },
   {
+    'name': 'gn_mac',
+    'pattern': '.',
+    'action': [ 'download_from_google_storage',
+                '--no_resume',
+                '--platform=darwin',
+                '--no_auth',
+                '--bucket', 'chromium-gn',
+                '-s', 'src/buildtools/mac/gn.sha1',
+    ],
+  },
+  {
     'name': 'gn_win',
     'pattern': '.',
     'action': [ 'download_from_google_storage',
@@ -278,6 +289,17 @@
                 '-s', 'src/buildtools/linux64/clang-format.sha1',
     ],
   },
+  {
+    'name': 'clang_format_mac',
+    'pattern': '.',
+    'action': [ 'download_from_google_storage',
+                '--no_resume',
+                '--platform=darwin',
+                '--no_auth',
+                '--bucket', 'chromium-clang-format',
+                '-s', 'src/buildtools/mac/clang-format.sha1',
+    ],
+  },
   # Pull binutils for linux, enabled debug fission for faster linking /
   # debugging when used with clang on Ubuntu Precise.
   # https://code.google.com/p/chromium/issues/detail?id=352046
diff --git a/WATCHLISTS b/WATCHLISTS
index 10c22fe..fc4adbd 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -31,6 +31,7 @@
              'darin@chromium.org',
              'qsr+mojo@chromium.org',
              'viettrungluu+watch@chromium.org',
+             'gregsimon@chromium.org',
              'yzshen+watch@chromium.org'],
     'services': ['qsr+mojo@chromium.org',
                  'yzshen+watch@chromium.org'],
@@ -38,6 +39,7 @@
               'yzshen+watch@chromium.org'],
     'sky': ['abarth@chromium.org',
             'ojan@chromium.org',
+            'gregsimon@chromium.org',
             'qsr+mojo@chromium.org'],
   },
 }
diff --git a/mojo/BUILD.gn b/mojo/BUILD.gn
index ee4850d..d4ac711 100644
--- a/mojo/BUILD.gn
+++ b/mojo/BUILD.gn
@@ -53,7 +53,6 @@
   deps = [
     "//mojo/common:mojo_common_unittests",
     "//mojo/converters/surfaces/tests:mojo_surfaces_lib_unittests",
-    "//mojo/edk/js:tests",
     "//mojo/edk/system:tests",
     "//mojo/edk/test:public_tests",
     "//mojo/dart/embedder/test:dart_unittests",
@@ -65,9 +64,8 @@
     "//services/dart/dart_apptests",
     "//services/files:apptests",
     "//services/http_server:apptests",
-    "//services/js:js_apptests",
-    "//services/js:js_services_unittests",
     "//services/reaper:tests",
+    "//services/url_response_disk_cache:tests",
     "//services/view_manager:mojo_view_manager_client_apptests",
     "//services/view_manager:view_manager_service_apptests",
     "//services/view_manager:view_manager_service_unittests",
@@ -84,6 +82,16 @@
     ]
   }
 
+  # TODO(jamesr): We only support building V8 snapshot data on a linux host since it
+  # needs a 32 bit toolchain and we don't have one configured for mac hosts.
+  if (host_os == "linux") {
+    deps += [
+      "//mojo/edk/js:tests",
+      "//services/js:js_apptests",
+      "//services/js:js_services_unittests",
+    ]
+  }
+
   if (mojo_use_prebuilt_network_service) {
     deps += [ "//mojo/public/tools:copy_network_service_apptests" ]
   }
diff --git a/mojo/dart/embedder/BUILD.gn b/mojo/dart/embedder/BUILD.gn
index 95fcc46..e7ef21e 100644
--- a/mojo/dart/embedder/BUILD.gn
+++ b/mojo/dart/embedder/BUILD.gn
@@ -69,6 +69,7 @@
     "//mojo/dart/embedder/io/socket_patch.dart",
     "//mojo/dart/embedder/io/server_socket_patch.dart",
   ]
+  root_prefix = "//mojo/dart/embedder/"
   output = "$target_gen_dir/dart_embedder_patch_resources.cc"
   table_name = "dart_embedder_patch"
 }
@@ -107,6 +108,7 @@
     "//mojo/public/dart/src/application_connection.dart",
     "//mojo/public/dart/src/buffer.dart",
     "//mojo/public/dart/src/codec.dart",
+    "//mojo/public/dart/src/control_message.dart",
     "//mojo/public/dart/src/data_pipe.dart",
     "//mojo/public/dart/src/drain_data.dart",
     "//mojo/public/dart/src/event_stream.dart",
@@ -143,6 +145,7 @@
     "//mojo/dart/embedder/vmservice/resources.dart",
     "//mojo/dart/embedder/vmservice/server.dart",
   ]
+  root_prefix = "//mojo/dart/embedder/"
   input_directory = "$root_out_dir/observatory/deployed/web/"
   output = "$target_gen_dir/dart_embedder_service_isolate_resources.cc"
   table_name = "dart_embedder_service_isolate"
@@ -252,7 +255,7 @@
 
   outputs = [
     "$root_out_dir/observatory/build/web/index.html",
-    "$root_out_dir/observatory/build/web/index.html_bootstrap.dart.js",
+    "$root_out_dir/observatory/build/web/index.html.polymer.bootstrap.dart.js",
   ]
 }
 
@@ -266,7 +269,7 @@
   inputs = [
     script,
     "$root_out_dir/observatory/build/web/index.html",
-    "$root_out_dir/observatory/build/web/index.html_bootstrap.dart.js",
+    "$root_out_dir/observatory/build/web/index.html.polymer.bootstrap.dart.js",
   ]
 
   args = [
@@ -281,7 +284,7 @@
 
   outputs = [
     "$root_out_dir/observatory/deployed/web/index.html",
-    "$root_out_dir/observatory/deployed/web/index.html_bootstrap.dart.js",
+    "$root_out_dir/observatory/deployed/web/index.html.polymer.bootstrap.dart.js",
   ]
 }
 
diff --git a/mojo/dart/embedder/dart_debugger.cc b/mojo/dart/embedder/dart_debugger.cc
index 31e9ee4..684acc5 100644
--- a/mojo/dart/embedder/dart_debugger.cc
+++ b/mojo/dart/embedder/dart_debugger.cc
@@ -2,12 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <vector>
-
+#include "mojo/dart/embedder/dart_debugger.h"
 #include "dart/runtime/include/dart_api.h"
 #include "dart/runtime/include/dart_debugger_api.h"
 #include "dart/runtime/include/dart_native_api.h"
-#include "mojo/dart/embedder/dart_debugger.h"
+
 
 namespace mojo {
 namespace dart {
@@ -45,10 +44,9 @@
                                       intptr_t bp_id,
                                       const Dart_CodeLocation& loc) {
   Dart_EnterScope();
-  DartDebuggerIsolate* debugger_isolate =
-      FindIsolateById(isolate_id);
-  CHECK(debugger_isolate != nullptr);
-  debugger_isolate->MessageLoop();
+  intptr_t isolate_index = FindIsolateIndexById(isolate_id);
+  CHECK(isolate_index != -1);
+  (*isolates_)[isolate_index]->MessageLoop();
   Dart_ExitScope();
 }
 
@@ -56,10 +54,9 @@
                                           Dart_Handle exception,
                                           Dart_StackTrace stack_trace) {
   Dart_EnterScope();
-  DartDebuggerIsolate* debugger_isolate =
-      FindIsolateById(isolate_id);
-  CHECK(debugger_isolate != nullptr);
-  debugger_isolate->MessageLoop();
+  intptr_t isolate_index = FindIsolateIndexById(isolate_id);
+  CHECK(isolate_index != -1);
+  (*isolates_)[isolate_index]->MessageLoop();
   Dart_ExitScope();
 }
 
@@ -69,11 +66,10 @@
   if (kind == Dart_IsolateEvent::kCreated) {
     AddIsolate(isolate_id);
   } else {
-    DartDebuggerIsolate* debugger_isolate =
-        FindIsolateById(isolate_id);
-    CHECK(debugger_isolate != nullptr);
+    intptr_t isolate_index = FindIsolateIndexById(isolate_id);
+    CHECK(isolate_index != -1);
     if (kind == Dart_IsolateEvent::kInterrupted) {
-      debugger_isolate->MessageLoop();
+      (*isolates_)[isolate_index]->MessageLoop();
     } else {
       CHECK(kind == Dart_IsolateEvent::kShutdown);
       RemoveIsolate(isolate_id);
@@ -85,10 +81,9 @@
 void DartDebugger::NotifyIsolate(Dart_Isolate isolate) {
   base::AutoLock al(*lock_);
   Dart_IsolateId isolate_id = Dart_GetIsolateId(isolate);
-  DartDebuggerIsolate* debugger_isolate =
-      FindIsolateByIdLocked(isolate_id);
-  if (debugger_isolate != nullptr) {
-    debugger_isolate->Notify();
+  intptr_t isolate_index = FindIsolateIndexByIdLocked(isolate_id);
+  if (isolate_index >= 0) {
+    (*isolates_)[isolate_index]->Notify();
   }
 }
 
@@ -98,40 +93,38 @@
   Dart_SetBreakpointResolvedHandler(BptResolvedHandler);
   Dart_SetExceptionThrownHandler(ExceptionThrownHandler);
   lock_ = new base::Lock();
+  isolates_ = new std::vector<std::unique_ptr<DartDebuggerIsolate>>();
 }
 
-DartDebuggerIsolate* DartDebugger::FindIsolateById(Dart_IsolateId id) {
+intptr_t DartDebugger::FindIsolateIndexById(Dart_IsolateId id) {
   base::AutoLock al(*lock_);
-  return FindIsolateByIdLocked(id);
+  return FindIsolateIndexByIdLocked(id);
 }
 
-DartDebuggerIsolate* DartDebugger::FindIsolateByIdLocked(
+intptr_t DartDebugger::FindIsolateIndexByIdLocked(
       Dart_IsolateId id) {
   lock_->AssertAcquired();
-  for (size_t i = 0; i < isolates_.size(); i++) {
-    DartDebuggerIsolate* isolate = isolates_[i];
-    if (id == isolate->id()) {
-      return isolate;
+  for (size_t i = 0; i < isolates_->size(); i++) {
+    if ((*isolates_)[i]->id() == id) {
+      return i;
     }
   }
-  return nullptr;
+  return -1;
 }
 
-DartDebuggerIsolate* DartDebugger::AddIsolate(Dart_IsolateId id) {
+void DartDebugger::AddIsolate(Dart_IsolateId id) {
   base::AutoLock al(*lock_);
-  CHECK(FindIsolateByIdLocked(id) == nullptr);
-  DartDebuggerIsolate* debugger_isolate =
-      new DartDebuggerIsolate(id);
-  isolates_.push_back(debugger_isolate);
-  return debugger_isolate;
+  CHECK(FindIsolateIndexByIdLocked(id) == -1);
+  std::unique_ptr<DartDebuggerIsolate> debugger_isolate =
+      std::unique_ptr<DartDebuggerIsolate>(new DartDebuggerIsolate(id));
+  isolates_->push_back(std::move(debugger_isolate));
 }
 
 void DartDebugger::RemoveIsolate(Dart_IsolateId id) {
   base::AutoLock al(*lock_);
-  for (size_t i = 0; i < isolates_.size(); i++) {
-    DartDebuggerIsolate* isolate = isolates_[i];
-    if (id == isolate->id()) {
-      isolates_.erase(isolates_.begin() + i);
+  for (size_t i = 0; i < isolates_->size(); i++) {
+    if (id == (*isolates_)[i]->id()) {
+      isolates_->erase(isolates_->begin() + i);
       return;
     }
   }
@@ -139,7 +132,8 @@
 }
 
 base::Lock* DartDebugger::lock_ = nullptr;
-std::vector<DartDebuggerIsolate*> DartDebugger::isolates_;
+std::vector<std::unique_ptr<DartDebuggerIsolate>>* DartDebugger::isolates_ =
+    nullptr;
 
-}  // namespace apps
+}  // namespace dart
 }  // namespace mojo
diff --git a/mojo/dart/embedder/dart_debugger.h b/mojo/dart/embedder/dart_debugger.h
index fe7ef2d..7db7986 100644
--- a/mojo/dart/embedder/dart_debugger.h
+++ b/mojo/dart/embedder/dart_debugger.h
@@ -5,6 +5,9 @@
 #ifndef MOJO_DART_EMBEDDER_DART_DEBUGGER_H_
 #define MOJO_DART_EMBEDDER_DART_DEBUGGER_H_
 
+#include <memory>
+#include <vector>
+
 #include "dart/runtime/include/dart_api.h"
 #include "dart/runtime/include/dart_debugger_api.h"
 #include "dart/runtime/include/dart_native_api.h"
@@ -60,16 +63,16 @@
 
   static void NotifyIsolate(Dart_Isolate isolate);
 
-  static DartDebuggerIsolate* FindIsolateById(Dart_IsolateId id);
+  static intptr_t FindIsolateIndexById(Dart_IsolateId id);
 
-  static DartDebuggerIsolate* FindIsolateByIdLocked(Dart_IsolateId id);
+  static intptr_t FindIsolateIndexByIdLocked(Dart_IsolateId id);
 
-  static DartDebuggerIsolate* AddIsolate(Dart_IsolateId id);
+  static void AddIsolate(Dart_IsolateId id);
 
   static void RemoveIsolate(Dart_IsolateId id);
 
   static base::Lock* lock_;
-  static std::vector<DartDebuggerIsolate*> isolates_;
+  static std::vector<std::unique_ptr<DartDebuggerIsolate>>* isolates_;
 
   friend class DartDebuggerIsolate;
 };
diff --git a/mojo/dart/embedder/embedder.gni b/mojo/dart/embedder/embedder.gni
index b5d3de3..dd9dcdf 100644
--- a/mojo/dart/embedder/embedder.gni
+++ b/mojo/dart/embedder/embedder.gni
@@ -48,6 +48,7 @@
 #   String output (name of output file)
 #   List inputs (list of input files to be included)
 #   String table_name (name of symbol for resource table)
+#   String root_prefix (base directory of resources)
 # Optional invoker inputs:
 #   String input_directory (directory of resources that are recursively added)
 #   List deps
@@ -71,6 +72,8 @@
 
     inputs = [ script ] + invoker.inputs
 
+    root_prefix = rebase_path(invoker.root_prefix)
+
     args = [
       "--output",
       rebase_path(output),
@@ -81,7 +84,7 @@
       "--table_name",
       invoker.table_name,
       "--root_prefix",
-      rebase_path("//mojo/dart/embedder/"),
+      root_prefix,
     ]
     if (defined(invoker.input_directory)) {
       args += [
diff --git a/mojo/dart/embedder/test/BUILD.gn b/mojo/dart/embedder/test/BUILD.gn
index 96320c9..6796c97 100644
--- a/mojo/dart/embedder/test/BUILD.gn
+++ b/mojo/dart/embedder/test/BUILD.gn
@@ -16,6 +16,7 @@
     "//mojo/public/dart/src/application_connection.dart",
     "//mojo/public/dart/src/buffer.dart",
     "//mojo/public/dart/src/codec.dart",
+    "//mojo/public/dart/src/control_message.dart",
     "//mojo/public/dart/src/data_pipe.dart",
     "//mojo/public/dart/src/drain_data.dart",
     "//mojo/public/dart/src/event_stream.dart",
diff --git a/mojo/dart/embedder/test/run_dart_tests.cc b/mojo/dart/embedder/test/run_dart_tests.cc
index d0efe61..8d3ae1f 100644
--- a/mojo/dart/embedder/test/run_dart_tests.cc
+++ b/mojo/dart/embedder/test/run_dart_tests.cc
@@ -140,6 +140,10 @@
   RunTest("exception_test.dart", false, nullptr, 0);
 }
 
+TEST(DartTest, control_messages_test) {
+  RunTest("control_messages_test.dart", false, nullptr, 0);
+}
+
 TEST(DartTest, handle_finalizer_test) {
   const int kNumArgs = 2;
   const char* args[kNumArgs];
diff --git a/mojo/dart/embedder/vmservice/server.dart b/mojo/dart/embedder/vmservice/server.dart
index 188b532..4e069f9 100644
--- a/mojo/dart/embedder/vmservice/server.dart
+++ b/mojo/dart/embedder/vmservice/server.dart
@@ -35,17 +35,9 @@
     }
   }
 
-  post(var serial, dynamic result) {
+  post(dynamic result) {
     try {
-      if (serial == null && result is! String) {
-        socket.add(result);
-      } else {
-        Map map = {
-          'id': serial,
-          'result': result
-        };
-        socket.add(JSON.encode(map));
-      }
+      socket.add(result);
     } catch (_) {
       print("Ignoring error posting over WebSocket.");
     }
@@ -67,7 +59,7 @@
 
   HttpRequestClient(this.request, VMService service) : super(service);
 
-  post(var serial, String result) {
+  post(String result) {
     request.response..headers.contentType = jsonContentType
                     ..write(result)
                     ..close();
diff --git a/mojo/dart/mojom/AUTHORS b/mojo/dart/mojom/AUTHORS
new file mode 100644
index 0000000..2efca50
--- /dev/null
+++ b/mojo/dart/mojom/AUTHORS
@@ -0,0 +1,518 @@
+# Names should be added to this file with this pattern:
+#
+# For individuals:
+#   Name <email address>
+#
+# For organizations:
+#   Organization <fnmatch pattern>
+#
+# See python fnmatch module documentation for more information.
+
+Aaron Jacobs <samusaaron3@gmail.com>
+Aaron Leventhal <aaronlevbugs@gmail.com>
+Aaron Randolph <aaron.randolph@gmail.com>
+Abhishek Agarwal <abhishek.a21@samsung.com>
+Adam Roben <adam@github.com>
+Adam Treat <adam.treat@samsung.com>
+Addanki Gandhi Kishor <kishor.ag@samsung.com>
+Adenilson Cavalcanti <a.cavalcanti@samsung.com>
+Aditya Bhargava <heuristicist@gmail.com>
+Ajay Berwal <ajay.berwal@samsung.com>
+Ajith Kumar V <ajith.v@samsung.com>
+Alex Gartrell <agartrell@cmu.edu>
+Alex Henrie <alexhenrie24@gmail.com>
+Alex Scheele <alexscheele@gmail.com>
+Alexander Sulfrian <alexander@sulfrian.net>
+Alexandre Abreu <wiss1976@gmail.com>
+Alexandru Chiculita <achicu@adobe.com>
+Alexey Korepanov <alexkorep@gmail.com>
+Alexis Menard <alexis.menard@intel.com>
+Alfredo Hernandez <ahernandez.miralles@gmail.com>
+Ali Vathi <ali.akbar@gmail.com>
+Amit Sarkar <amit.srkr@samsung.com>
+Amogh Bihani <amogh.bihani@samsung.com>
+Amruth Raj <amruthraj@motorola.com>
+Amruth Raj <ckqr36@motorola.com>
+Anand Ratn <anand.ratn@samsung.com>
+Anastasios Cassiotis <tom.cassiotis@gmail.com>
+Ancil George <ancilgeorge@samsung.com>
+Andrei Parvu <andrei.prv@gmail.com>
+Andrei Parvu <parvu@adobe.com>
+Andrew Brampton <me@bramp.net>
+Andrew Tulloch <andrew@tullo.ch>
+Anish Patankar <anish.p@samsung.com>
+Ankit Kumar <ankit2.kumar@samsung.com>
+Anssi Hannula <anssi.hannula@iki.fi>
+Antonio Gomes <a1.gomes@sisa.samsung.com>
+Anuj Kumar Sharma <anujk.sharma@samsung.com>
+Arnaud Renevier <a.renevier@samsung.com>
+Arpita Bahuguna <a.bah@samsung.com>
+Arthur Lussos <developer0420@gmail.com>
+Arun Kulkarni <kulkarni.a@samsung.com>
+Arun Kumar <arun87.kumar@samsung.com>
+Arun Mankuzhi <arun.m@samsung.com>
+Arunoday Sarkar <a.sarkar.arun@gmail.com>
+Arunprasad Rajkumar <ararunprasad@gmail.com>
+Arunprasad Rajkumar <arurajku@cisco.com>
+Attila Dusnoki <dati91@gmail.com>
+Avinaash Doreswamy <avi.nitk@samsung.com>
+Balazs Kelemen <b.kelemen@samsung.com>
+Behara Mani Shyam Patro <behara.ms@samsung.com>
+Bem Jones-Bey <bemajaniman@gmail.com>
+Bem Jones-Bey <bjonesbe@adobe.com>
+Ben Karel <eschew@gmail.com>
+Benjamin Jemlich <pcgod99@gmail.com>
+Bernard Cafarelli <voyageur@gentoo.org>
+Bhanukrushana Rout <b.rout@samsung.com>
+Bobby Powers <bobbypowers@gmail.com>
+Brendan Long <self@brendanlong.com>
+Brian G. Merrell <bgmerrell@gmail.com>
+Brian Konzman, SJ <b.g.konzman@gmail.com>
+Brian Merrell, Novell Inc. <bgmerrell@gmail.com>
+Bruno Calvignac <bruno@flock.com>
+Bruno de Oliveira Abinader <bruno.d@partner.samsung.com>
+Bryan Donlan <bdonlan@gmail.com>
+Byungwoo Lee <bw80.lee@samsung.com>
+Caio Marcelo de Oliveira Filho <caio.de.oliveira.filho@intel.com>
+Caitlin Potter <caitpotter88@gmail.com>
+Catalin Badea <badea@adobe.com>
+Cem Kocagil <cem.kocagil@gmail.com>
+Chamal De Silva <chamalsl@yahoo.com>
+Chandra Shekar Vallala <brk376@motorola.com>
+Chang Shu <c.shu@samsung.com>
+ChangSeok Oh <shivamidow@gmail.com>
+Changbin Shao <changbin.shao@intel.com>
+Changjun Yang <changjun.yang@intel.com>
+Chansik Yun <chansik.yun@gmail.com>
+Chaobin Zhang <zhchbin@gmail.com>
+Chris Greene <cwgreene@amazon.com>
+Chris Harrelson <chrishtr@gmail.com>
+Christophe Dumez <ch.dumez@samsung.com>
+Christopher Dale <chrelad@gmail.com>
+Clemens Fruhwirth <clemens@endorphin.org>
+Clement Scheelfeldt Skau <clementskau@gmail.com>
+Clinton Staley <clintstaley@chromium.org>
+Clinton Staley <clintstaley@gmail.com>
+Craig Schlenter <craig.schlenter@gmail.com>
+Daegyu Lee <na7jun8gi@gmail.com>
+Dai Chunyang <chunyang.dai@intel.com>
+Daniel Bomar <dbdaniel42@gmail.com>
+Daniel Imms <daniimms@amazon.com>
+Daniel Johnson <danielj41@gmail.com>
+Daniel Nishi <dhnishi@gmail.com>
+Daniel Shaulov <dshaulov@ptc.com>
+Daniel Trebbien <dtrebbien@gmail.com>
+Darshini KN <kn.darshini@samsung.com>
+David Benjamin <davidben@mit.edu>
+David Erceg <erceg.david@gmail.com>
+David Futcher <david.mike.futcher@gmail.com>
+David McAllister <mcdavid@amazon.com>
+Deepak Dilip Borade <deepak.db@samsung.com>
+Deepak Mittal <deepak.m1@samsung.com>
+Deepak Singla <deepak.sa@samsung.com>
+Derek Halman <d.halman@gmail.com>
+Devlin Cronin <rdevlin.cronin@gmail.com>
+Diego Ferreiro Val <elfogris@gmail.com>
+Dillon Sellars <dill.sellars@gmail.com>
+Divya Bansal <divya.bansal@samsung.com>
+Dominic Jodoin <dominic.jodoin@gmail.com>
+Dominik Röttsches <dominik.rottsches@intel.com>
+Don Woodward <woodward@adobe.com>
+Dongjun Kim <djmix.kim@samsung.com>
+Dongseong Hwang <dongseong.hwang@intel.com>
+Dongwoo Joshua Im <dw.im@samsung.com>
+Douglas F. Turner <doug.turner@gmail.com>
+Eduardo Lima (Etrunko) <eduardo.lima@intel.com>
+Edward Crossman <tedoc2000@gmail.com>
+Eero Häkkinen <e.hakkinen@samsung.com>
+Egor Starkov <egor.starkov@samsung.com>
+Ehsan Akhgari <ehsan.akhgari@gmail.com>
+Elan Ruusamäe <elan.ruusamae@gmail.com>
+Eric Ahn <byungwook.ahn@gmail.com>
+Eric Rescorla <ekr@rtfm.com>
+Erik Hill <erikghill@gmail.com>
+Erik Sjölund <erik.sjolund@gmail.com>
+Eriq Augustine <eriq.augustine@gmail.com>
+Etienne Laurin <etienne@atnnn.com>
+Evan Peterson <evan.peterson.ep@gmail.com>
+Evan Wallace <evan.exe@gmail.com>
+Evangelos Foutras <evangelos@foutrelis.com>
+Evgeniy Dushistov <dushistov@gmail.com>
+Evgeny Agafonchikov <evgeny.agafonchikov@akvelon.com>
+Fabien Tassin <fta@sofaraway.org>
+Felix H. Dahlke <fhd@ubercode.de>
+Fernando Jiménez Moreno <ferjmoreno@gmail.com>
+François Beaufort <beaufort.francois@gmail.com>
+Francois Kritzinger <francoisk777@gmail.com>
+Frédéric Wang <fred.wang@free.fr>
+Gaetano Mendola <mendola@gmail.com>
+Gajendra N <gajendra.n@samsung.com>
+Gajendra Singh <wxjg68@motorola.com>
+Gao Chun <chun.gao@intel.com>
+Gao Chun <gaochun.dev@gmail.com>
+George Liaskos <geo.liaskos@gmail.com>
+Giuseppe Iuculano <giuseppe@iuculano.it>
+Glenn Adams <glenn@chromium.org>
+Gnanasekar Somanathan <gnanasekar.s@samsung.com>
+Gordana Cmiljanovic <gordana.cmiljanovic@imgtec.com>
+Goutham Jagannatha <wrm364@motorola.com>
+Gregory Davis <gpdavis.chromium@gmail.com>
+Grzegorz Czajkowski <g.czajkowski@samsung.com>
+Guangzhen Li <guangzhen.li@intel.com>
+Gurpreet Kaur <k.gurpreet@samsung.com>
+Gyuyoung Kim <gyuyoung.kim@samsung.com>
+Habib Virji <habib.virji@samsung.com>
+Haitao Feng <haitao.feng@intel.com>
+Halton Huo <halton.huo@intel.com>
+Haojian Wu <hokein.wu@gmail.com>
+Harpreet Singh Khurana <harpreet.sk@samsung.com>
+Hautio Kari <khautio@gmail.com>
+Heejin R. Chung <heejin.r.chung@samsung.com>
+Heeyoun Lee <heeyoun.lee@samsung.com>
+Himanshu Joshi <h.joshi@samsung.com>
+Hong Zheng <hong.zheng@intel.com>
+Hongbo Min <hongbo.min@intel.com>
+Horia Olaru <horia.olaru@gmail.com>
+Horia Olaru <olaru@adobe.com>
+Hosung You <hosung.you@samsung.com>
+Hwanseung Lee <rucifer1217@gmail.com>
+Hyunki Baik <hyunki.baik@samsung.com>
+Hyungwook Lee <withlhw@gmail.com>
+Ibrar Ahmed <ibrar.ahmad@gmail.com>
+Ian Hickson <github.com@spam.hixie.ch>
+Ion Rosca <rosca@adobe.com>
+J. Ryan Stinnett <jryans@chromium.org>
+Jacob Mandelson <jacob@mandelson.org>
+Jaehun Lim <ljaehun.lim@samsung.com>
+Jaekyeom Kim <btapiz@gmail.com>
+Jaime Soriano Pastor <jsorianopastor@gmail.com>
+Jake Helfert <jake@helfert.us>
+Jakob Weigert <jakob.j.w@googlemail.com>
+James Choi <jchoi42@pha.jhu.edu>
+James Vega <vega.james@gmail.com>
+James Wei <james.wei@intel.com>
+James Willcox <jwillcox@litl.com>
+Janwar Dinata <j.dinata@gmail.com>
+Jared Shumway <jaredshumway94@gmail.com>
+Jared Wein <weinjared@gmail.com>
+Jay Soffian <jaysoffian@gmail.com>
+Jeado Ko <haibane84@gmail.com>
+Jeongeun Kim <je_julie.kim@samsung.com>
+Jesse Miller <jesse@jmiller.biz>
+Jesus Sanchez-Palencia <jesus.sanchez-palencia.fernandez.fil@intel.com>
+Jiajia Qin <jiajia.qin@intel.com>
+Jie Chen <jie.a.chen@intel.com>
+Jihun Brent Kim <devgrapher@gmail.com>
+Jin Yang <jin.a.yang@intel.com>
+Jincheol Jo <jincheol.jo@navercorp.com>
+Jingwei Liu <kingweiliu@gmail.com>
+Jingyi Wei <wjywbs@gmail.com>
+Jinho Bang <jinho.bang@samsung.com>
+Jinwoo Song <jinwoo7.song@samsung.com>
+Jitendra Kumar Sahoo <jitendra.ks@samsung.com>
+Joe Knoll <joe.knoll@workday.com>
+Joe Thomas <mhx348@motorola.com>
+Joel Stanley <joel@jms.id.au>
+Johannes Rudolph <johannes.rudolph@googlemail.com>
+John McCutchan <john@johnmccutchan.com>
+John Yani <vanuan@gmail.com>
+John Yoo <nearbyh13@gmail.com>
+Jonathan Frazer <listedegarde@gmail.com>
+Jonathan Hacker <jhacker@arcanefour.com>
+Jongsoo Lee <leejongsoo@gmail.com>
+Joone Hur <joone.hur@intel.com>
+Jorge Villatoro <jorge@tomatocannon.com>
+Joseph Gentle <josephg@gmail.com>
+Josh Triplett <josh@joshtriplett.org>
+Josh Triplett <josh.triplett@intel.com>
+Joshua Lock <joshua.lock@intel.com>
+Joshua Roesslein <jroesslein@gmail.com>
+Josué Ratelle <jorat1346@gmail.com>
+Juhui Lee <juhui24.lee@samsung.com>
+Julien Racle <jracle@logitech.com>
+Jun Jiang <jun.a.jiang@intel.com>
+JungJik Lee <jungjik.lee@samsung.com>
+Junmin Zhu <junmin.zhu@intel.com>
+Kal Conley <kcconley@gmail.com>
+Kalyan Kondapally <kalyan.kondapally@intel.com>
+Kamil Jiwa <kamil.jiwa@gmail.com>
+Kangil Han <kangil.han@samsung.com>
+Kangyuan Shu <kangyuan.shu@intel.com>
+Kaspar Brand <googlecontrib@velox.ch>
+Kaustubh Atrawalkar <kaustubh.ra@gmail.com>
+Kaustubh Atrawalkar <kaustubh.a@samsung.com>
+Keene Pan <keenepan@linpus.com>
+Kenneth Rohde Christiansen <kenneth.r.christiansen@intel.com>
+Keonho Kim <keonho07.kim@samsung.com>
+Kevin Lee Helpingstine <sig11@reprehensible.net>
+Kevin M. McCormick <mckev@amazon.com>
+Kihong Kwon <kihong.kwon@samsung.com>
+Kim Christensen <kimworking@gmail.com>
+Kingshuk Jana <kingshuk.j@samsung.com>
+Klemen Forstnerič <klemen.forstneric@gmail.com>
+Krishna Chaitanya <krish.botta@samsung.com>
+Krzysztof Wolanski <k.wolanski@samsung.com>
+Kunal Thakar <kunalt@gmail.com>
+Kushal Pisavadia <kushi.p@gmail.com>
+Kwangho Shin <k_h.shin@samsung.com>
+Kyle Nahrgang <kpn24@drexel.edu>
+Kyungtae Kim <ktf.kim@samsung.com>
+Kyung Yeol Kim <chitacan@gmail.com>
+Laszlo Gombos <l.gombos@samsung.com>
+Laszlo Radanyi <bekkra@gmail.com>
+Lauren Yeun Kim <lauren.yeun.kim@gmail.com>
+Lauri Oherd <lauri.oherd@gmail.com>
+Leith Bade <leith@leithalweapon.geek.nz>
+Li Yin <li.yin@intel.com>
+Lionel Landwerlin <lionel.g.landwerlin@intel.com>
+Lorenzo Stoakes <lstoakes@gmail.com>
+Lu Guanqun <guanqun.lu@gmail.com>
+Lucie Brozkova <lucinka.brozkova@gmail.com>
+Luke Inman-Semerau <luke.semerau@gmail.com>
+Luke Zarko <lukezarko@gmail.com>
+Maarten Lankhorst <m.b.lankhorst@gmail.com>
+Magnus Danielsson <fuzzac@gmail.com>
+Mahesh Kulkarni <mahesh.kk@samsung.com>
+Malcolm Wang <malcolm.2.wang@gmail.com>
+Manish Chhajer <chhajer.m@samsung.com>
+Manuel Braun <thembrown@gmail.com>
+Mao Yujie <maojie0924@gmail.com>
+Mao Yujie <yujie.mao@intel.com>
+Marco Rodrigues <gothicx@gmail.com>
+Mario Sanchez Prada <mario.prada@samsung.com>
+Mark Hahnenberg <mhahnenb@andrew.cmu.edu>
+Mark Seaborn <mrs@mythic-beasts.com>
+Martin Bednorz <m.s.bednorz@gmail.com>
+Masahiro Yado <yado.masa@gmail.com>
+Matheus Bratfisch <matheusbrat@gmail.com>
+Mathias Bynens <mathias@qiwi.be>
+Mathieu Meisser <mmeisser@logitech.com>
+Matt Arpidone <mma.public@gmail.com>
+Matthew Robertson <matthewrobertson03@gmail.com>
+Matthew Turk <matthewturk@gmail.com>
+Matthew Willis <appamatto@gmail.com>
+Matthias Reitinger <reimarvin@gmail.com>
+Max Perepelitsyn <pph34r@gmail.com>
+Max Vujovic <mvujovic@adobe.com>
+Mayur Kankanwadi <mayurk.vk@samsung.com>
+Michael Gilbert <floppymaster@gmail.com>
+Michael Schechter <mike.schechter@gmail.com>
+Michael Zugelder <michael@zugelder.org>
+Mihai Maerean <mmaerean@adobe.com>
+Mihai Tica <mihai.o.tica@gmail.com>
+Mihai Tica <mitica@adobe.com>
+Mike Tilburg <mtilburg@adobe.com>
+Mikhail Pozdnyakov <mikhail.pozdnyakov@intel.com>
+Mingmin Xie <melvinxie@gmail.com>
+Mirela Budaes <mbudaes@gmail.com>
+Mirela Budaes <mbudaes@adobe.com>
+Mitchell Rosen <mitchellwrosen@chromium.org>
+Miyoung Shin <myid.shin@samsung.com>
+Mohamed I. Hammad <ibraaaa@gmail.com>
+Mohamed Mansour <m0.interactive@gmail.com>
+Mohammed Wajahat Ali Siddiqui <wajahat.s@samsung.com>
+Mohan Reddy <mohan.reddy@samsung.com>
+Mrunal Kapade <mrunal.kapade@intel.com>
+Myles C. Maxfield <mymax@amazon.com>
+Nagarjuna Atluri <nagarjuna.a@samsung.com>
+Naiem Shaik <naiem.shaik@gmail.com>
+Naoki Takano <takano.naoki@gmail.com>
+Naveen Bobbili <naveenbobbili@motorola.com>
+Naveen Bobbili <qghc36@motorola.com>
+Naveen Kumar S G <naveensg@samsung.com>
+Nayan Kumar K <qtc746@motorola.com>
+Nedeljko Babic <nedeljko.babic@imgtec.com>
+Nikhil Bansal <n.bansal@samsung.com>
+Nikita Ofitserov <himikof@gmail.com>
+Ningxin Hu <ningxin.hu@intel.com>
+Nitish Mehrotra <nitish.m@samsung.com>
+Pan Deng <pan.deng@intel.com>
+Parag Radke <nrqv63@motorola.com>
+Patrasciuc Sorin Cristian <cristian.patrasciuc@gmail.com>
+Patrick Riordan <patrickriordan177@gmail.com>
+Paul Adolph <padolph@netflix.com>
+Paul Kehrer <paul.l.kehrer@gmail.com>
+Paul Lind <paul.lind@imgtec.com>
+Paul Nettleship <pnettleship@gmail.com>
+Paul Robinson <paulrobinson85@googlemail.com>
+Paul Roskell <blurrech@gmail.com>
+Paul Wicks <pwicks86@gmail.com>
+Pavan Kumar Emani <pavan.e@samsung.com>
+Pavel Ivanov <paivanof@gmail.com>
+Paweł Hajdan jr <phajdan.jr@gmail.com>
+Peng Jiang <leiyi.jp@gmail.com>
+Petar Jovanovic <petarj@mips.com>
+Peter Beverloo <peter@chromium.org>
+Peter Bright <drpizza@quiscalusmexicanus.org>
+Peter Brophy <pbrophy@adobe.com>
+Peter Collingbourne <peter@pcc.me.uk>
+Peter Gal <pgal.u-szeged@partner.samsung.com>
+Peter Molnar <pmolnar.u-szeged@partner.samsung.com>
+Philippe Beauchamp <philippe.beauchamp@gmail.com>
+Philippe Beaudoin <philippe.beaudoin@gmail.com>
+Pierre-Antoine LaFayette <pierre.lafayette@gmail.com>
+Po-Chun Chang <pochang0403@gmail.com>
+Pramod Begur Srinath <pramod.bs@samsung.com>
+Prashant Hiremath <prashhir@cisco.com>
+Prashant Nevase <prashant.n@samsung.com>
+Praveen Akkiraju <praveen.anp@samsung.com>
+Pritam Nikam <pritam.nikam@samsung.com>
+Puttaraju R <puttaraju.r@samsung.com>
+Qiankun Miao <qiankun.miao@intel.com>
+Qing Zhang <qing.zhang@intel.com>
+Radu Stavila <stavila@adobe.com>
+Raghavendra Ghatage <r.ghatage@samsung.com>
+Raman Tenneti <raman.tenneti@gmail.com>
+Ramkumar Gokarnesan <ramkumar.gokarnesan@gmail.com>
+Ramkumar Ramachandra <artagnon@gmail.com>
+Randy Posynick <randy.posynick@gmail.com>
+Raphael Kubo da Costa <raphael.kubo.da.costa@intel.com>
+Ravi Phaneendra Kasibhatla <r.kasibhatla@samsung.com>
+Ravi Phaneendra Kasibhatla <ravi.kasibhatla@motorola.com>
+Renata Hodovan <rhodovan.u-szeged@partner.samsung.com>
+Rene Bolldorf <rb@radix.io>
+Rene Ladan <r.c.ladan@gmail.com>
+Rijubrata Bhaumik <rijubrata.bhaumik@intel.com>
+Rob Buis <rob.buis@samsung.com>
+Rob Wu <rob@robwu.nl>
+Robert Bear Travis <bear.travis@gmail.com>
+Robert Bear Travis <betravis@adobe.com>
+Robert Bradford <robert.bradford@intel.com>
+Robert Goldberg <goldberg@adobe.com>
+Robert Hogan <robhogan@gmail.com>
+Robert Nagy <robert.nagy@gmail.com>
+Robert Sesek <rsesek@bluestatic.org>
+Roland Takacs <rtakacs.u-szeged@partner.samsung.com>
+Rosen Dash <nqk836@motorola.com>
+Rosen Dash <rosen.dash@gmail.com>
+ruben <chromium@hybridsource.org>
+Ruben Terrazas <rubentopo@gmail.com>
+Ruiyi Luo <luoruiyi2008@gmail.com>
+Ryan Norton <rnorton10@gmail.com>
+Ryan Sleevi <ryan-chromium-dev@sleevi.com>
+Ryuan Choi <ryuan.choi@samsung.com>
+Saikrishna Arcot <saiarcot895@gmail.com>
+Salvatore Iovene <salvatore.iovene@intel.com>
+Sam Larison <qufighter@gmail.com>
+Sam McDonald <sam@sammcd.com>
+Sanjoy Pal <ncj674@motorola.com>
+Sanjoy Pal <sanjoy.pal@samsung.com>
+Sanne Wouda <sanne.wouda@gmail.com>
+Sarath Singapati <s.singapati@samsung.com>
+Saravanan KR <sramajay@cisco.com>
+Sathish Kuppuswamy <sathish.kuppuswamy@intel.com>
+Satoshi Matsuzaki <satoshi.matsuzaki@gmail.com>
+Sayan Nayak <sayan.nayak@samsung.com>
+Scott Blomquist <sblom@microsoft.com>
+Sean Bryant <sean@cyberwang.net>
+Seo Sanghyeon <sanxiyn@gmail.com>
+Seokju Kwon <seokju.kwon@gmail.com>
+Sergey Putilin <p.sergey@samsung.com>
+Sergio Carlos Morales Angeles <carloschilazo@gmail.com>
+Sergiy Byelozyorov <rryk.ua@gmail.com>
+Seshadri Mahalingam <seshadri.mahalingam@gmail.com>
+Sevan Janiyan <venture37@geeklan.co.uk>
+ShankarGanesh K <blr.bmlab@gmail.com>
+Shez Baig <sbaig1@bloomberg.net>
+Shiliu Wang <aofdwsl@gmail.com>
+Shiliu Wang <shiliu.wang@intel.com>
+Shilpa Shri <shilpa.shri@samsung.com>
+Shouqun Liu <shouqun.liu@intel.com>
+Shreeram Kushwaha <shreeram.k@samsung.com>
+Shreyas Gopal <shreyas.g@samsung.com>
+Shreyas VA <v.a.shreyas@gmail.com>
+Siba Samal <siba.samal@samsung.com>
+Simon Arlott <simon.arlott@gmail.com>
+Siva Kumar Gunturi <siva.gunturi@samsung.com>
+Sohan Jyoti Ghosh <sohan.jyoti@samsung.com>
+Song YeWen <ffmpeg@gmail.com>
+Soren Dreijer <dreijerbit@gmail.com>
+Srirama Chandra Sekhar Mogali <srirama.m@samsung.com>
+Stephen Searles <stephen.searles@gmail.com>
+Steven Pennington <spenn@engr.uvic.ca>
+Subrahmanya Praveen Munukutla <sataya.m@samsung.com>
+Suchit Agrawal <a.suchit@samsung.com>
+Sudarsana Babu Nagineni <sudarsana.nagineni@intel.com>
+Sudarshan Parthasarathy <sudarshan.p@samsung.com>
+Sungguk Lim <limasdf@gmail.com>
+Sungmann Cho <sungmann.cho@gmail.com>
+Sungmann Cho <sungmann.cho@navercorp.com>
+Suyash Sengar <suyash.s@samsung.com>
+Sunil Ratnu <sunil.ratnu@samsung.com>
+Sylvain Zimmer <sylvinus@gmail.com>
+Szymon Piechowicz <szymonpiechowicz@o2.pl>
+Takeshi Kurosawa <taken.spc@gmail.com>
+Tapu Kumar Ghose <ghose.tapu@gmail.com>
+Taylor Price <trprice@gmail.com>
+Ted Kim <neot0000@gmail.com>
+Ted Vessenes <tedvessenes@gmail.com>
+Teodora Novkovic <teodora.petrovic@gmail.com>
+Thiago Farina <thiago.farina@gmail.com>
+Thiago Marcos P. Santos <thiago.santos@intel.com>
+Thomas Butter <tbutter@gmail.com>
+Tiago Vignatti <tiago.vignatti@intel.com>
+Tim Ansell <mithro@mithis.com>
+Timo Reimann <ttr314@googlemail.com>
+Torsten Kurbad <google@tk-webart.de>
+Trevor Perrin <unsafe@trevp.net>
+U. Artie Eoff <ullysses.a.eoff@intel.com>
+Umar Hansa <umar.hansa@gmail.com>
+Vamshikrishna Yellenki <vamshi@motorola.com>
+Vani Hegde <vani.hegde@samsung.com>
+Vedran Šajatović <vedran.sajatovic@gmail.com>
+Vernon Tang <vt@foilhead.net>
+Viatcheslav Ostapenko <sl.ostapenko@samsung.com>
+Victor Costan <costan@gmail.com>
+Viet-Trung Luu <viettrungluu@gmail.com>
+Vinay Anantharaman <vinaya@adobe.com>
+Vipul Bhasin <vipul.bhasin@gmail.com>
+Visa Putkinen <v.putkinen@partner.samsung.com>
+Vivek Galatage <vivek.vg@samsung.com>
+Will Hirsch <chromium@willhirsch.co.uk>
+Xiang Long <xiang.long@intel.com>
+Xinchao He <hexinchao@gmail.com>
+Xing Zhang <xzhang@adobe.com>
+Xu Samuel <samuel.xu@intel.com>
+Xuefei Ren <xrenishere@gmail.com>
+Xun Sun <xun.sun@intel.com>
+Yael Aharon <yael.aharon@intel.com>
+Yair Yogev <progame@chromium.org>
+Yang Gu <yang.gu@intel.com>
+Yarin Kaul <yarin.kaul@gmail.com>
+Ye Liu <cbakgly@gmail.com>
+Yi Shen <yi.shen@samsung.com>
+Yoav Weiss <yoav@yoav.ws>
+Yoav Zilberberg <yoav.zilberberg@gmail.com>
+Yong Shin <sy3620@gmail.com>
+Yongsheng Zhu <yongsheng.zhu@intel.com>
+Yoshinori Sano <yoshinori.sano@gmail.com>
+YoungKi Hong <simon.hong81@gmail.com>
+Youngsun Suh <zard17@gmail.com>
+Yumikiyo Osanai <yumios.art@gmail.com>
+Yuri Gorobets <yuri.gorobets@gmail.com>
+Zeno Albisser <zeno.albisser@digia.com>
+Zhaoze Zhou <zhaoze.zhou@partner.samsung.com>
+Zheng Chuang <zhengchuangscu@gmail.com>
+Zhenyu Liang <zhenyu.liang@intel.com>
+Zhenyu Shan <zhenyu.shan@intel.com>
+Ziran Sun <ziran.sun@samsung.com>
+Yongha Lee <yongha78.lee@samsung.com>
+方觉 (Fang Jue) <fangjue23303@gmail.com>
+Yupei Wang <perryuwang@tencent.com>
+Peng Hu <penghu@tencent.com>
+WenSheng He <wensheng.he@samsung.com>
+Raghu Ram Nagaraj <r.nagaraj@samsung.com>
+
+BlackBerry Limited <*@blackberry.com>
+Code Aurora Forum <*@codeaurora.org>
+Comodo CA Limited
+Google Inc. <*@google.com>
+Igalia S.L. <*@igalia.com>
+NVIDIA Corporation <*@nvidia.com>
+Opera Software ASA <*@opera.com>
+The Chromium Authors <*@chromium.org>
+The MathWorks, Inc. <binod.pant@mathworks.com>
+Torchmobile Inc.
+Venture 3 Systems LLC <*@venture3systems.com>
+Yandex LLC <*@yandex-team.ru>
+ARM Holdings <*@arm.com>
diff --git a/mojo/dart/mojom/CHANGELOG.md b/mojo/dart/mojom/CHANGELOG.md
new file mode 100644
index 0000000..9ab89f3
--- /dev/null
+++ b/mojo/dart/mojom/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.0.1
+  * Initial placeholder package for generated mojom bindings.
+
diff --git a/mojo/dart/mojom/LICENSE b/mojo/dart/mojom/LICENSE
new file mode 100644
index 0000000..a32e00c
--- /dev/null
+++ b/mojo/dart/mojom/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/mojo/dart/mojom/README.md b/mojo/dart/mojom/README.md
new file mode 100644
index 0000000..8932851
--- /dev/null
+++ b/mojo/dart/mojom/README.md
@@ -0,0 +1,17 @@
+mojom
+====
+
+This package is a placeholder for generated mojom bindings.
+
+Generated Mojo bindings in other pub packages should be installed into this
+package by saying the following after `pub get`:
+
+```
+$ pub run mojom:generate
+```
+If desired, additional directories holding .mojom.dart files can be specified;
+their contents will be installed to this package as well:
+
+```
+$ pub run mojom:generate -a </path/to/mojom/dir>
+```
diff --git a/mojo/dart/mojom/bin/generate.dart b/mojo/dart/mojom/bin/generate.dart
new file mode 100644
index 0000000..e579655
--- /dev/null
+++ b/mojo/dart/mojom/bin/generate.dart
@@ -0,0 +1,135 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/*
+* This script should be invoked via 'pub run' for an application that consumes
+* pub packages containing generated Mojo bindings (.mojom.dart files).
+*
+* It should be invoked as follows after 'pub get':
+* $ pub run mojom:generate
+*/
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:args/args.dart' as args;
+import 'package:path/path.dart' as path;
+
+bool verbose;
+bool dryRun;
+
+bool isMojomDart(String path) => path.endsWith('.mojom.dart');
+
+// Copies .mojom.dart files from a particular package's mojom directory to
+// the mojom package directory.
+//
+// mojomDirectory: A pub packages mojom directory, i.e. $PACKAGE/mojom.
+// mojomPackage: The mojom package directory.
+copyMojomDirContents(Directory mojomDirectory, Directory mojomPackage) async {
+  assert(await mojomDirectory.exists());
+  await for (var mojoms in mojomDirectory.list(recursive: true)) {
+    if (mojoms is! File) continue;
+    if (!isMojomDart(mojoms.path)) continue;
+    if (verbose) print("Found $mojoms");
+
+    final relative = path.relative(mojoms.path, from: mojomDirectory.path);
+    final dest = path.join(mojomPackage.path, relative);
+    final destDirectory = new Directory(path.dirname(dest));
+
+    if (verbose || dryRun) {
+      print('Copying $mojoms to $dest');
+    }
+
+    if (!dryRun) {
+      final File source = new File(mojoms.path);
+      if (verbose) print("Ensuring $destDirectory exists");
+      await destDirectory.create(recursive: true);
+      source.copy(dest);
+    }
+  }
+}
+
+// If a package has a mojom directory, uses copyMojomDirContents to install them
+// in the mojom package.
+//
+// package: The directory of a pub package in an application's packages
+//          directory.
+// mojoPackage: The mojom package directory in an application's packages
+//              directory
+installPackageMojoms(Directory package, Directory mojomPackage) async {
+  if (package.path == mojomPackage.path) return;
+  if (verbose) print("package = $package");
+  final mojomDirectory = new Directory(path.join(package.path, 'mojom'));
+  if (verbose) print("looking for = $mojomDirectory");
+  if (await mojomDirectory.exists()) {
+    copyMojomDirContents(mojomDirectory, mojomPackage);
+  } else if (verbose) {
+    print("$mojomDirectory not found");
+  }
+}
+
+main(List<String> arguments) async {
+  final parser = new args.ArgParser()
+    ..addOption('package-root',
+        abbr: 'p',
+        defaultsTo: path.join(Directory.current.path, 'packages'),
+        help: 'An absolute path to an application\'s package root')
+    ..addOption('additional-mojom-dir',
+        abbr: 'a',
+        allowMultiple: true,
+        help: 'Absolute path to an additional directory containing mojom.dart'
+        'files to put in the mojom package. May be specified multiple times.')
+    ..addFlag('dry-run',
+        abbr: 'd',
+        defaultsTo: false,
+        help: 'Print the copy operations that would have been run, but'
+        'do not copy anything.')
+    ..addFlag('verbose', abbr: 'v', defaultsTo: false);
+  final result = parser.parse(arguments);
+  verbose = result['verbose'];
+  dryRun = result['dry-run'];
+
+  final packages = new Directory(result['package-root']);
+  if (!packages.isAbsolute) {
+    print("The --package-root parameter should be an absolute path.");
+    exit(1);
+  }
+  if (verbose) print("packages = $packages");
+  if (!(await packages.exists())) {
+    print("The packages directory $packages does not exist");
+    exit(1);
+  }
+
+  var additional_mojom_dirs = [];
+  for (var mojom_dir_path in result['additional-mojom-dir']) {
+    final mojom_dir = new Directory(mojom_dir_path);
+    if (!mojom_dir.isAbsolute) {
+      print("All --additional-mojom-dir parameters should be absolute paths.");
+      exit(1);
+    }
+    if (!(await mojom_dir.exists())) {
+      print("The additional mojom directory $mojom_dir does not exist");
+      exit(1);
+    }
+    additional_mojom_dirs.add(mojom_dir);
+  }
+  if (verbose) print("additional_mojom_dirs = $additional_mojom_dirs");
+
+  final mojomPackage = new Directory(path.join(packages.path, 'mojom'));
+  if (verbose) print("mojom package = $mojomPackage");
+  if (!(await mojomPackage.exists())) {
+    print("The mojom package directory $mojomPackage does not exist");
+    exit(1);
+  }
+
+  await for (var package in packages.list()) {
+    if (package is Directory) {
+      installPackageMojoms(package, mojomPackage);
+    }
+  }
+
+  for (var mojom_dir in additional_mojom_dirs) {
+    copyMojomDirContents(mojom_dir, mojomPackage);
+  }
+}
diff --git a/mojo/dart/mojom/lib/README.md b/mojo/dart/mojom/lib/README.md
new file mode 100644
index 0000000..f608b86
--- /dev/null
+++ b/mojo/dart/mojom/lib/README.md
@@ -0,0 +1,8 @@
+mojom
+====
+
+Files will appear here after running the generate script (see the top-level
+documentation for this package).
+
+This file is a placeholder to ensure that the mojom package gets installed
+under "packages/" when it is added as a dependency.
diff --git a/mojo/dart/mojom/pubspec.lock b/mojo/dart/mojom/pubspec.lock
new file mode 100644
index 0000000..053fdb2
--- /dev/null
+++ b/mojo/dart/mojom/pubspec.lock
@@ -0,0 +1,11 @@
+# Generated by pub
+# See http://pub.dartlang.org/doc/glossary.html#lockfile
+packages:
+  args:
+    description: args
+    source: hosted
+    version: "0.13.0"
+  path:
+    description: path
+    source: hosted
+    version: "1.3.5"
diff --git a/mojo/dart/mojom/pubspec.yaml b/mojo/dart/mojom/pubspec.yaml
new file mode 100644
index 0000000..36bcd5f
--- /dev/null
+++ b/mojo/dart/mojom/pubspec.yaml
@@ -0,0 +1,10 @@
+name: mojom
+version: 0.0.3
+author: Chromium Authors <mojo-dev@googlegroups.com>
+description: Placeholder for mojom bindings.
+homepage: https://github.com/domokit/mojo
+dependencies:
+  path: '>=1.3.5 <2.0.0'
+  args: '>=0.13.0 <1.0.0'
+environment:
+  sdk: '>=1.9.0 <2.0.0'
diff --git a/mojo/dart/test/control_messages_test.dart b/mojo/dart/test/control_messages_test.dart
new file mode 100644
index 0000000..38cdf92
--- /dev/null
+++ b/mojo/dart/test/control_messages_test.dart
@@ -0,0 +1,108 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:isolate';
+import 'dart:typed_data';
+
+import 'package:mojo/dart/testing/expect.dart';
+import 'package:mojo/public/dart/bindings.dart' as bindings;
+import 'package:mojo/public/dart/core.dart' as core;
+import 'package:mojom/sample/sample_interfaces.mojom.dart' as sample;
+
+// Bump this if sample_interfaces.mojom adds higher versions.
+const maxVersion = 3;
+
+// Implementation of IntegerAccessor.
+class IntegerAccessorImpl implements sample.IntegerAccessor {
+  // Some initial value.
+  int _value = 0;
+
+  Future<sample.IntegerAccessorGetIntegerResponseParams>
+      getInteger([Function responseFactory = null]) {
+    return new Future.value(responseFactory(_value, sample.Enum_VALUE));
+  }
+
+  void setInteger(int data, int type) {
+    Expect.equals(sample.Enum_VALUE, type);
+    // Update data.
+    _value = data;
+  }
+}
+
+// Returns [proxy, stub].
+List buildConnectedProxyAndStub() {
+  var pipe = new core.MojoMessagePipe();
+  var proxy = new sample.IntegerAccessorProxy.fromEndpoint(pipe.endpoints[0]);
+  var impl = new IntegerAccessorImpl();
+  var stub =
+      new sample.IntegerAccessorStub.fromEndpoint(pipe.endpoints[1], impl);
+  return [proxy, stub];
+}
+
+void closeProxyAndStub(List ps) {
+  var proxy = ps[0];
+  var stub = ps[1];
+  proxy.close();
+  stub.close();
+}
+
+testQueryVersion() async {
+  var ps = buildConnectedProxyAndStub();
+  var proxy = ps[0];
+  // The version starts at 0.
+  Expect.equals(0, proxy.version);
+  // We are talking to an implementation that supports version maxVersion.
+  var providedVersion = await proxy.queryVersion();
+  Expect.equals(maxVersion, providedVersion);
+  // The proxy's version has been updated.
+  Expect.equals(providedVersion, proxy.version);
+  closeProxyAndStub(ps);
+}
+
+testRequireVersionSuccess() async {
+  var ps = buildConnectedProxyAndStub();
+  var proxy = ps[0];
+  Expect.equals(0, proxy.version);
+  // Require version maxVersion.
+  proxy.requireVersion(maxVersion);
+  // Make a request and get a response.
+  var response = await proxy.ptr.getInteger();
+  Expect.equals(0, response.data);
+  closeProxyAndStub(ps);
+}
+
+testRequireVersionDisconnect() async {
+  var ps = buildConnectedProxyAndStub();
+  var proxy = ps[0];
+  Expect.equals(0, proxy.version);
+  // Require version maxVersion.
+  proxy.requireVersion(maxVersion);
+  Expect.equals(maxVersion, proxy.version);
+  // Set integer.
+  proxy.ptr.setInteger(34, sample.Enum_VALUE);
+  // Get integer.
+  var response = await proxy.ptr.getInteger();
+  Expect.equals(34, response.data);
+  // Require version maxVersion + 1
+  proxy.requireVersion(maxVersion + 1);
+  // Version number is updated synchronously.
+  Expect.equals(maxVersion + 1, proxy.version);
+  // Get integer, expect a failure.
+  bool exceptionCaught = false;
+  try {
+    response = await proxy.ptr.getInteger();
+    Expect.fail('Should have an exception.');
+  } catch(e) {
+    exceptionCaught = true;
+  }
+  Expect.isTrue(exceptionCaught);
+  closeProxyAndStub(ps);
+}
+
+main() async {
+  await testQueryVersion();
+  await testRequireVersionSuccess();
+  await testRequireVersionDisconnect();
+}
diff --git a/mojo/devtools/common/devtoolslib/apptest_dart.py b/mojo/devtools/common/devtoolslib/apptest_dart.py
index b21ac84..30946ab 100644
--- a/mojo/devtools/common/devtoolslib/apptest_dart.py
+++ b/mojo/devtools/common/devtoolslib/apptest_dart.py
@@ -13,12 +13,11 @@
 
 
 def _dart_apptest_output_test(output):
-  # Fail on output with dart unittests' "FAIL:"/"ERROR:" or a lack of "PASS:".
+  # Fail on output with dart unittests' "FAIL:" or a lack of "PASS:".
   # The latter condition ensures failure on broken command lines or output.
   # Check output instead of exit codes because mojo_shell always exits with 0.
   if (not output or
       '\nFAIL: ' in output or
-      '\nERROR: ' in output or
       '\nPASS: ' not in output):
     return False
   return True
diff --git a/mojo/public/cpp/application/application_impl.h b/mojo/public/cpp/application/application_impl.h
index 8bc970a..294cfff 100644
--- a/mojo/public/cpp/application/application_impl.h
+++ b/mojo/public/cpp/application/application_impl.h
@@ -99,6 +99,13 @@
   // Quits the main run loop for this application.
   static void Terminate();
 
+ protected:
+  // Application implementation.
+  void AcceptConnection(const String& requestor_url,
+                        InterfaceRequest<ServiceProvider> services,
+                        ServiceProviderPtr exposed_services,
+                        const String& url) override;
+
  private:
   class ShellPtrWatcher;
 
@@ -111,11 +118,6 @@
   }
 
   // Application implementation.
-  void AcceptConnection(const String& requestor_url,
-                        InterfaceRequest<ServiceProvider> services,
-                        ServiceProviderPtr exposed_services,
-                        const String& url) override;
-
   void RequestQuit() override;
 
   typedef std::vector<internal::ServiceRegistry*> ServiceRegistryList;
diff --git a/mojo/public/cpp/bindings/tests/union_unittest.cc b/mojo/public/cpp/bindings/tests/union_unittest.cc
index 3253232..65a23fa 100644
--- a/mojo/public/cpp/bindings/tests/union_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/union_unittest.cc
@@ -4,6 +4,7 @@
 
 #include <vector>
 #include "mojo/public/cpp/bindings/array.h"
+#include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/lib/array_internal.h"
 #include "mojo/public/cpp/bindings/lib/array_serialization.h"
 #include "mojo/public/cpp/bindings/lib/bounds_checker.h"
@@ -11,6 +12,7 @@
 #include "mojo/public/cpp/bindings/string.h"
 #include "mojo/public/cpp/environment/environment.h"
 #include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/cpp/utility/run_loop.h"
 #include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h"
 #include "mojo/public/interfaces/bindings/tests/test_unions.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -1043,5 +1045,90 @@
   free(raw_buf);
 }
 
+class SmallCacheImpl : public SmallCache {
+ public:
+  SmallCacheImpl() : int_value_(0) {}
+  ~SmallCacheImpl() override {}
+  int64_t int_value() const { return int_value_; }
+
+ private:
+  void SetIntValue(int64_t int_value) override { int_value_ = int_value; }
+  void GetIntValue(const GetIntValueCallback& callback) override {
+    callback.Run(int_value_);
+  }
+
+  int64_t int_value_;
+};
+
+TEST(UnionTest, InterfaceInUnion) {
+  Environment env;
+  RunLoop run_loop;
+  SmallCacheImpl impl;
+  SmallCachePtr ptr;
+  Binding<SmallCache> bindings(&impl, GetProxy(&ptr));
+
+  HandleUnionPtr handle(HandleUnion::New());
+  handle->set_f_small_cache(ptr.Pass());
+
+  handle->get_f_small_cache()->SetIntValue(10);
+  run_loop.RunUntilIdle();
+  EXPECT_EQ(10, impl.int_value());
+}
+
+TEST(UnionTest, InterfaceInUnionSerialization) {
+  Environment env;
+  RunLoop run_loop;
+  SmallCacheImpl impl;
+  SmallCachePtr ptr;
+  Binding<SmallCache> bindings(&impl, GetProxy(&ptr));
+
+  HandleUnionPtr handle(HandleUnion::New());
+  handle->set_f_small_cache(ptr.Pass());
+  size_t size = GetSerializedSize_(handle, false);
+  EXPECT_EQ(16U, size);
+
+  mojo::internal::FixedBuffer buf(size);
+  internal::HandleUnion_Data* data = nullptr;
+  SerializeUnion_(handle.Pass(), &buf, &data, false);
+
+  std::vector<Handle> handles;
+  data->EncodePointersAndHandles(&handles);
+  EXPECT_EQ(1U, handles.size());
+  data->DecodePointersAndHandles(&handles);
+
+  HandleUnionPtr handle2(HandleUnion::New());
+  Deserialize_(data, &handle2);
+
+  handle2->get_f_small_cache()->SetIntValue(10);
+  run_loop.RunUntilIdle();
+  EXPECT_EQ(10, impl.int_value());
+}
+
+class UnionInterfaceImpl : public UnionInterface {
+ public:
+  UnionInterfaceImpl() {}
+  ~UnionInterfaceImpl() override {}
+
+ private:
+  void Echo(PodUnionPtr in, const EchoCallback& callback) override {
+    callback.Run(in.Pass());
+  }
+};
+
+TEST(UnionTest, UnionInInterface) {
+  Environment env;
+  RunLoop run_loop;
+  UnionInterfaceImpl impl;
+  UnionInterfacePtr ptr;
+  Binding<UnionInterface> bindings(&impl, GetProxy(&ptr));
+
+  PodUnionPtr pod(PodUnion::New());
+  pod->set_f_int16(16);
+
+  ptr->Echo(pod.Pass(),
+            [](PodUnionPtr out) { EXPECT_EQ(16, out->get_f_int16()); });
+  run_loop.RunUntilIdle();
+}
+
 }  // namespace test
 }  // namespace mojo
diff --git a/mojo/public/dart/BUILD.gn b/mojo/public/dart/BUILD.gn
index 711eee3..6d0be78 100644
--- a/mojo/public/dart/BUILD.gn
+++ b/mojo/public/dart/BUILD.gn
@@ -9,6 +9,7 @@
   "src/application.dart",
   "src/buffer.dart",
   "src/codec.dart",
+  "src/control_message.dart",
   "src/data_pipe.dart",
   "src/drain_data.dart",
   "src/event_stream.dart",
@@ -29,5 +30,6 @@
   sources = dart_mojo_sdk_sources
   deps = [
     "../interfaces/application",
+    "../interfaces/bindings",
   ]
 }
diff --git a/mojo/public/dart/bindings.dart b/mojo/public/dart/bindings.dart
index e68afec..3447874 100644
--- a/mojo/public/dart/bindings.dart
+++ b/mojo/public/dart/bindings.dart
@@ -9,7 +9,9 @@
 import 'dart:typed_data';
 
 import 'package:mojo/public/dart/core.dart' as core;
+import 'package:mojom/mojo/interface_control_messages.mojom.dart' as icm;
 
+part 'src/control_message.dart';
 part 'src/codec.dart';
 part 'src/message.dart';
 part 'src/proxy.dart';
diff --git a/mojo/public/dart/src/codec.dart b/mojo/public/dart/src/codec.dart
index 89e32c2..07bad35 100644
--- a/mojo/public/dart/src/codec.dart
+++ b/mojo/public/dart/src/codec.dart
@@ -201,11 +201,10 @@
 
   void encodeInterface(
       core.MojoEventStreamListener interface, int offset, bool nullable) {
-    // Set the version field to 0 for now.
-    encodeUint32(0, offset + kSerializedHandleSize);
-
     if (interface == null) {
       encodeInvalideHandle(offset, nullable);
+      // Set the version field to 0.
+      encodeUint32(0, offset + kSerializedHandleSize);
       return;
     }
     if (interface is Stub) {
@@ -214,6 +213,8 @@
       interface.bind(pipe.endpoints[0]);
       interface.listen();
       encodeMessagePipeHandle(pipe.endpoints[1], offset, nullable);
+      // Set the version to the version in the stub.
+      encodeUint32(interface.version, offset + kSerializedHandleSize);
     } else if (interface is Proxy) {
       assert(interface.isBound);
       if (!interface.isOpen) {
@@ -222,6 +223,8 @@
         interface.listen();
       }
       encodeMessagePipeHandle(interface.endpoint, offset, nullable);
+      // Set the version to the current version of the proxy.
+      encodeUint32(interface.version, offset + kSerializedHandleSize);
     } else {
       throw new MojoCodecError(
           'Trying to encode an unknown MojoEventStreamListener');
@@ -594,9 +597,14 @@
 
   ProxyBase decodeServiceInterface(
       int offset, bool nullable, Function clientFactory) {
-    // Ignore the version field for now.
     var endpoint = decodeMessagePipeHandle(offset, nullable);
-    return endpoint.handle.isValid ? clientFactory(endpoint) : null;
+    var version = decodeUint32(offset + kSerializedHandleSize);
+    if (!endpoint.handle.isValid) {
+      return null;
+    }
+    ProxyBase client = clientFactory(endpoint);
+    client.impl._version = version;
+    return client;
   }
 
   Stub decodeInterfaceRequest(
diff --git a/mojo/public/dart/src/control_message.dart b/mojo/public/dart/src/control_message.dart
new file mode 100644
index 0000000..de71a98
--- /dev/null
+++ b/mojo/public/dart/src/control_message.dart
@@ -0,0 +1,61 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of bindings;
+
+// Handles InterfaceControlMessages for a stub.
+class ControlMessageHandler {
+  static bool isControlMessage(ServiceMessage message) =>
+      _isRun(message) || _isRunOrClose(message);
+
+  static bool _isRun(ServiceMessage message) =>
+      (message.header.type == icm.kRunMessageId);
+
+  static bool _isRunOrClose(ServiceMessage message) =>
+      (message.header.type == icm.kRunOrClosePipeMessageId);
+
+  static Future<Message> handleMessage(Stub stub,
+                                       int interface_version,
+                                       ServiceMessage message) {
+    assert(isControlMessage(message));
+    if (_isRun(message)) {
+      return _handleRun(stub, interface_version, message);
+    } else {
+      assert(_isRunOrClose(message));
+      return _handleRunOrClose(stub, interface_version, message);
+    }
+  }
+
+  static Future<Message> _handleRun(Stub stub,
+                                    int interface_version,
+                                    ServiceMessage message) {
+    // Construct RunMessage response.
+    var response = new icm.RunResponseMessageParams();
+    response.reserved0 = 16;
+    response.reserved1 = 0;
+    response.queryVersionResult = new icm.QueryVersionResult();
+    response.queryVersionResult.version = interface_version;
+    // Return response.
+    return new Future.value(
+        stub.buildResponseWithId(response,
+                                 icm.kRunMessageId,
+                                 message.header.requestId,
+                                 MessageHeader.kMessageIsResponse));
+  }
+
+  static Future<Message> _handleRunOrClose(Stub stub,
+                                           int interface_version,
+                                           ServiceMessage message) {
+    // Deserialize message.
+    var params = icm.RunOrClosePipeMessageParams.deserialize(message.payload);
+    // Grab required version.
+    var requiredVersion = params.requireVersion.version;
+    if (interface_version >= requiredVersion) {
+      // Stub meets the requirements.
+      return;
+    }
+    // Stub does not implement required version. Close the pipe immediately.
+    stub.close(immediate: true);
+  }
+}
diff --git a/mojo/public/dart/src/proxy.dart b/mojo/public/dart/src/proxy.dart
index 863e734..528e13a 100644
--- a/mojo/public/dart/src/proxy.dart
+++ b/mojo/public/dart/src/proxy.dart
@@ -13,6 +13,10 @@
 abstract class Proxy extends core.MojoEventStreamListener {
   Map<int, Completer> _completerMap;
   int _nextId = 0;
+  int _version = 0;
+  /// Version of this interface that the remote side supports. Updated when a
+  /// call to [queryVersion] or [requireVersion] is made.
+  int get version => _version;
 
   Proxy.fromEndpoint(core.MojoMessagePipeEndpoint endpoint)
       : _completerMap = {},
@@ -39,6 +43,10 @@
     result = endpoint.read(bytes, result.bytesRead, handles);
     assert(result.status.isOk || result.status.isResourceExhausted);
     var message = new ServiceMessage.fromMessage(new Message(bytes, handles));
+    if (ControlMessageHandler.isControlMessage(message)) {
+      _handleControlMessageResponse(message);
+      return;
+    }
     handleResponse(message);
   }
 
@@ -96,6 +104,63 @@
     var superString = super.toString();
     return "Proxy(${superString})";
   }
+
+  /// Queries the max version that the remote side supports.
+  /// Updates [version].
+  Future<int> queryVersion() async {
+    var params = new icm.RunMessageParams();
+    params.reserved0 = 16;
+    params.reserved1 = 0;
+    params.queryVersion = new icm.QueryVersion();
+    var response = await
+        sendMessageWithRequestId(params,
+                                 icm.kRunMessageId,
+                                 -1,
+                                 MessageHeader.kMessageExpectsResponse);
+    _version = response.queryVersionResult.version;
+    return _version;
+  }
+
+  /// If the remote side doesn't support the [requiredVersion], it will close
+  /// its end of the message pipe asynchronously. This does nothing if it's
+  /// already known that the remote side supports [requiredVersion].
+  /// Updates [version].
+  void requireVersion(int requiredVersion) {
+    if (requiredVersion <= _version) {
+      // Already supported.
+      return;
+    }
+
+    // If the remote end doesn't close the pipe, we know that it supports
+    // required version.
+    _version = requiredVersion;
+
+    var params = new icm.RunOrClosePipeMessageParams();
+    params.reserved0 = 16;
+    params.reserved1 = 0;
+    params.requireVersion = new icm.RequireVersion();
+    params.requireVersion.version = requiredVersion;
+    // TODO(johnmccutchan): We've set _version above but if this sendMessage
+    // throws an exception we may not have sent the RunOrClose message. Should
+    // we reset _version in that case?
+    sendMessage(params, icm.kRunOrClosePipeMessageId);
+  }
+
+  _handleControlMessageResponse(ServiceMessage message) {
+    // We only expect to see Run messages.
+    assert(message.header.type == icm.kRunMessageId);
+    var response = icm.RunResponseMessageParams.deserialize(message.payload);
+    if (!message.header.hasRequestId) {
+      throw 'Expected a message with a valid request Id.';
+    }
+    Completer c = completerMap[message.header.requestId];
+    if (c == null) {
+      throw 'Message had unknown request Id: ${message.header.requestId}';
+    }
+    completerMap.remove(message.header.requestId);
+    assert(!c.isCompleted);
+    c.complete(response);
+  }
 }
 
 // Generated Proxy classes implement this interface.
diff --git a/mojo/public/dart/src/stub.dart b/mojo/public/dart/src/stub.dart
index 87298bf..babba68 100644
--- a/mojo/public/dart/src/stub.dart
+++ b/mojo/public/dart/src/stub.dart
@@ -125,4 +125,6 @@
     var superString = super.toString();
     return "Stub(${superString})";
   }
+
+  int get version;
 }
diff --git a/mojo/public/interfaces/bindings/tests/BUILD.gn b/mojo/public/interfaces/bindings/tests/BUILD.gn
index 5cdbda1..6604860 100644
--- a/mojo/public/interfaces/bindings/tests/BUILD.gn
+++ b/mojo/public/interfaces/bindings/tests/BUILD.gn
@@ -24,14 +24,16 @@
 }
 
 mojom("versioning_test_service_interfaces") {
-  testonly = true
+  # FIXME: Dart packaged applications cannot depend on testonly mojoms.
+  # testonly = true
   sources = [
     "versioning_test_service.mojom",
   ]
 }
 
 mojom("versioning_test_client_interfaces") {
-  testonly = true
+  # FIXME: Dart packaged applications cannot depend on testonly mojoms.
+  # testonly = true
   sources = [
     "versioning_test_client.mojom",
   ]
diff --git a/mojo/public/interfaces/bindings/tests/test_unions.mojom b/mojo/public/interfaces/bindings/tests/test_unions.mojom
index 4e7a540..28d7aa4 100644
--- a/mojo/public/interfaces/bindings/tests/test_unions.mojom
+++ b/mojo/public/interfaces/bindings/tests/test_unions.mojom
@@ -40,6 +40,7 @@
   handle<data_pipe_consumer> f_data_pipe_consumer;
   handle<data_pipe_producer> f_data_pipe_producer;
   handle<shared_buffer> f_shared_buffer;
+  SmallCache f_small_cache;
 };
 
 struct DummyStruct {
@@ -61,3 +62,12 @@
 struct SmallObjStruct {
   ObjectUnion obj_union;
 };
+
+interface SmallCache {
+  SetIntValue(int64 int_value);
+  GetIntValue() => (int64 int_value);
+};
+
+interface UnionInterface {
+  Echo(PodUnion in) => (PodUnion out);
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl
index 487d2a5..087499a 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl
@@ -62,8 +62,12 @@
     case {{enum_name}}::{{field.name|upper}}: {
 {%-   if field.kind|is_object_kind %}
       mojo::internal::Encode(&data.f_{{field.name}}, handles);
-{%-   elif field.kind|is_any_handle_kind or field.kind|is_interface_kind %}
+{%-   elif field.kind|is_any_handle_kind %}
       mojo::internal::EncodeHandle(&data.f_{{field.name}}, handles);
+{%-   elif field.kind|is_interface_kind %}
+      mojo::internal::EncodeHandle(
+          reinterpret_cast<mojo::internal::Interface_Data*>(
+              &data.f_{{field.name}}), handles);
 {%-   endif %}
       return;
     }
@@ -78,8 +82,12 @@
     case {{enum_name}}::{{field.name|upper}}: {
 {%-   if field.kind|is_object_kind %}
       mojo::internal::Decode(&data.f_{{field.name}}, handles);
-{%-   elif field.kind|is_any_handle_kind or field.kind|is_interface_kind %}
+{%-   elif field.kind|is_any_handle_kind %}
       mojo::internal::DecodeHandle(&data.f_{{field.name}}, handles);
+{%-   elif field.kind|is_interface_kind %}
+      mojo::internal::DecodeHandle(
+          reinterpret_cast<mojo::internal::Interface_Data*>(
+              &data.f_{{field.name}}), handles);
 {%-   endif %}
       return;
     }
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_definition.tmpl
index c9cc307..f07c487 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_definition.tmpl
@@ -70,6 +70,12 @@
 {%    elif field.kind|is_any_handle_kind %}
         result->data.f_{{field.name}} =
             input_acc.data()->{{field.name}}->release().value();
+{%    elif field.kind|is_interface_kind %}
+        mojo::internal::Interface_Data* {{field.name}} =
+            reinterpret_cast<mojo::internal::Interface_Data*>(
+                &result->data.f_{{field.name}});
+        mojo::internal::InterfacePointerToData(
+            input_acc.data()->{{field.name}}->Pass(), {{field.name}});
 {%    else %}
         result->data.f_{{field.name}} = input_acc.data()->{{field.name}};
 {%-   endif %}
@@ -100,6 +106,14 @@
         {{field.kind|cpp_wrapper_type}}* {{field.name}} =
             reinterpret_cast<{{field.kind|cpp_wrapper_type}}*>(&input->data.f_{{field.name}});
         result->set_{{field.name}}({{field.name}}->Pass());
+{%    elif field.kind|is_interface_kind %}
+        {{field.kind|cpp_wrapper_type}} {{field.name}}_out;
+        mojo::internal::Interface_Data* {{field.name}}_in =
+            reinterpret_cast<mojo::internal::Interface_Data*>(
+                &input->data.f_{{field.name}});
+        mojo::internal::InterfaceDataToPointer(
+            {{field.name}}_in, &{{field.name}}_out);
+        result->set_{{field.name}}({{field.name}}_out.Pass());
 {%    elif field.kind|is_enum_kind %}
         result->set_{{field.name}}(static_cast<{{field.kind|cpp_wrapper_type}}>(input->data.f_{{field.name}}));
 {%    else %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl
index 2f931bf..5e43c43 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl
@@ -39,7 +39,8 @@
     Union_() {}
     ~Union_() {}
 {%  for field in union.fields %}
-{%    if field.kind|is_object_kind or field.kind|is_any_handle_kind -%}
+{%    if field.kind|is_object_kind or field.kind|is_any_handle_kind
+          or field.kind|is_interface_kind -%}
     {{field.kind|cpp_wrapper_type}}* {{field.name}};
 {%    else -%}
     {{field.kind|cpp_wrapper_type}} {{field.name}};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl
index cb4e785..0f19a15 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl
@@ -43,7 +43,8 @@
   switch (tag_) {
 {%  for field in union.fields %}
     case Tag::{{field.name|upper}}:
-{%    if field.kind|is_object_kind or field.kind|is_any_handle_kind %}
+{%    if field.kind|is_object_kind or field.kind|is_any_handle_kind
+          or field.kind|is_interface_kind %}
       return mojo::internal::ValueTraits<{{field.kind|cpp_wrapper_type}}>::Equals(*(data_.{{field.name}}), *(other.data_.{{field.name}}));
 {%-   else %}
       return mojo::internal::ValueTraits<{{field.kind|cpp_wrapper_type}}>::Equals(data_.{{field.name}}, other.data_.{{field.name}});
@@ -61,7 +62,8 @@
 
 {{field.kind|cpp_union_getter_return_type}} {{union.name}}::get_{{field.name}}() const {
   MOJO_DCHECK(tag_ == Tag::{{field.name|upper}});
-{%    if field.kind|is_object_kind or field.kind|is_any_handle_kind %}
+{%    if field.kind|is_object_kind or field.kind|is_any_handle_kind
+          or field.kind|is_interface_kind %}
   return *(data_.{{field.name}});
 {%-   else %}
   return data_.{{field.name}};
@@ -72,7 +74,7 @@
   SwitchActive(Tag::{{field.name|upper}});
 {%    if field.kind|is_string_kind %}
   *(data_.{{field.name}}) = {{field.name}};
-{%    elif field.kind|is_object_kind %}
+{%    elif field.kind|is_object_kind or field.kind|is_interface_kind %}
   *(data_.{{field.name}}) = {{field.name}}.Pass();
 {%    elif field.kind|is_any_handle_kind %}
   data_.{{field.name}}->reset({{field.name}}.release());
@@ -95,7 +97,8 @@
   switch (new_active) {
 {%  for field in union.fields %}
     case Tag::{{field.name|upper}}:
-{%    if field.kind|is_object_kind or field.kind|is_any_handle_kind %}
+{%    if field.kind|is_object_kind or field.kind|is_any_handle_kind
+          or field.kind|is_interface_kind %}
       data_.{{field.name}} = new {{field.kind|cpp_wrapper_type}}();
 {%-   endif %}
       break;
@@ -109,7 +112,8 @@
   switch (tag_) {
 {%  for field in union.fields %}
     case Tag::{{field.name|upper}}:
-{%    if field.kind|is_object_kind or field.kind|is_any_handle_kind %}
+{%    if field.kind|is_object_kind or field.kind|is_any_handle_kind
+          or field.kind|is_interface_kind %}
       delete data_.{{field.name}};
 {%-   endif %}
       break;
diff --git a/mojo/public/tools/bindings/generators/dart_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/dart_templates/interface_definition.tmpl
index 0fbcb49..6e3cae4 100644
--- a/mojo/public/tools/bindings/generators/dart_templates/interface_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/dart_templates/interface_definition.tmpl
@@ -160,6 +160,14 @@
 
   Future close({bool immediate: false}) => impl.close(immediate: immediate);
 
+  int get version => impl.version;
+
+  Future<int> queryVersion() => impl.queryVersion();
+
+  void requireVersion(int requiredVersion) {
+    impl.requireVersion(requiredVersion);
+  }
+
   String toString() {
     return "{{interface|name}}Proxy($impl)";
   }
@@ -202,6 +210,11 @@
 {%- endfor %}
 
   Future<bindings.Message> handleMessage(bindings.ServiceMessage message) {
+    if (bindings.ControlMessageHandler.isControlMessage(message)) {
+      return bindings.ControlMessageHandler.handleMessage(this,
+                                                          {{interface.version}},
+                                                          message);
+    }
     assert(_impl != null);
     switch (message.header.type) {
 {%- for method in interface.methods %}
@@ -241,7 +254,7 @@
   }
 
   {{interface|name}} get impl => _impl;
-      set impl({{interface|name}} d) {
+  set impl({{interface|name}} d) {
     assert(_impl == null);
     _impl = d;
   }
@@ -250,4 +263,6 @@
     var superString = super.toString();
     return "{{interface|name}}Stub($superString)";
   }
+
+  int get version => {{interface.version}};
 }
diff --git a/mojo/public/tools/bindings/generators/dart_templates/struct_definition.tmpl b/mojo/public/tools/bindings/generators/dart_templates/struct_definition.tmpl
index 9f62100..a883e0a 100644
--- a/mojo/public/tools/bindings/generators/dart_templates/struct_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/dart_templates/struct_definition.tmpl
@@ -117,9 +117,12 @@
       // 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)
-            throw new bindings.MojoCodecError(
-                'Header doesn\'t correspond to any known 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) {
diff --git a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
index 70dc94b..f321c44 100644
--- a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
@@ -230,6 +230,8 @@
 def GetCppUnionFieldType(kind):
   if mojom.IsAnyHandleKind(kind):
     return "MojoHandle"
+  if mojom.IsInterfaceKind(kind):
+    return "uint64_t"
   if mojom.IsEnumKind(kind):
     return "int32_t"
   if mojom.IsUnionKind(kind):
@@ -240,7 +242,7 @@
 def GetUnionGetterReturnType(kind):
   if (mojom.IsStructKind(kind) or mojom.IsUnionKind(kind) or
       mojom.IsArrayKind(kind) or mojom.IsMapKind(kind) or
-      mojom.IsAnyHandleKind(kind)):
+      mojom.IsAnyHandleKind(kind) or mojom.IsInterfaceKind(kind)):
     return "%s&" % GetCppWrapperType(kind)
   return GetCppResultWrapperType(kind)
 
diff --git a/mojo/public/tools/download_network_service.py b/mojo/public/tools/download_network_service.py
index 43b1172..05c28ba 100755
--- a/mojo/public/tools/download_network_service.py
+++ b/mojo/public/tools/download_network_service.py
@@ -15,10 +15,6 @@
 sys.path.insert(0, os.path.join(_CURRENT_PATH, "pylib"))
 import gs
 
-if not sys.platform.startswith("linux"):
-  print "Not supported for your platform"
-  sys.exit(0)
-
 script_dir = os.path.dirname(os.path.realpath(__file__))
 
 
diff --git a/mojo/services/url_response_disk_cache/public/interfaces/BUILD.gn b/mojo/services/url_response_disk_cache/public/interfaces/BUILD.gn
new file mode 100644
index 0000000..aac931e
--- /dev/null
+++ b/mojo/services/url_response_disk_cache/public/interfaces/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/module_args/mojo.gni")
+import("$mojo_sdk_root/mojo/public/tools/bindings/mojom.gni")
+
+mojom("interfaces") {
+  sources = [
+    "url_response_disk_cache.mojom",
+  ]
+
+  import_dirs = [ get_path_info("../../../", "abspath") ]
+
+  if (defined(mojo_network_service_root)) {
+    import_dirs += [ mojo_network_service_root ]
+  } else {
+    mojo_network_service_root = "../../.."
+  }
+
+  deps = [
+    "$mojo_network_service_root/network/public/interfaces",
+  ]
+}
diff --git a/mojo/services/url_response_disk_cache/public/interfaces/url_response_disk_cache.mojom b/mojo/services/url_response_disk_cache/public/interfaces/url_response_disk_cache.mojom
new file mode 100644
index 0000000..b808b6e
--- /dev/null
+++ b/mojo/services/url_response_disk_cache/public/interfaces/url_response_disk_cache.mojom
@@ -0,0 +1,35 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module mojo;
+
+import "mojo/services/network/public/interfaces/url_loader.mojom";
+
+// This service allows client to efficiently cache and retrieve url response
+// content on disk. In particular, it allows the shell and content handlers to
+// efficiently cache and retrieve mojo applications.
+// TODO(qsr): At the moment, the url response disk cache only handles ETag. If
+//            either the cached version or the given response do not contain
+//            ETags, the entry will be invalidated. It should be extended to
+//            handle all the other http cache mechanisms for better performance
+//            with http servers that do not support ETags.
+interface URLResponseDiskCache {
+
+   // Given a URLResponse, returns a pair of paths. |file_path| is a file
+   // containing the body of the response. |cache_dir_path| is a directory that
+   // the applicaton can use to store content. This service guarantee that
+   // |cache_dir_path| will be emptied when |file_path| content changes. For
+   // example, a content handler that is backed by a VM that compiles files
+   // could have the VM use this directory to cache the compiled files.
+   GetFile(mojo.URLResponse response) =>
+       (array<uint8>? file_path, array<uint8>? cache_dir_path);
+
+   // Given a URLResponse that is expected to have a zipped body, returns a
+   // pair of paths. |extracted_dir_path| is a directory containing the unzipped
+   // body of the response. |cache_dir_path| is a directory that the applicaton
+   // can use to store content. This service guarantee that |cache_dir_path|
+   // will be emptied when |extracted_dir_path| content changes.
+   GetExtractedContent(mojo.URLResponse response) =>
+       (array<uint8>? extracted_dir_path, array<uint8>? cache_dir_path);
+};
diff --git a/mojo/tools/data/apptests b/mojo/tools/data/apptests
index e5616bf..148bf79 100644
--- a/mojo/tools/data/apptests
+++ b/mojo/tools/data/apptests
@@ -121,4 +121,7 @@
       # http server.
       "test": "mojo:shell_apptests",
     },
+    {
+      "test": "mojo:url_response_disk_cache_apptests",
+    },
   ]
diff --git a/services/BUILD.gn b/services/BUILD.gn
index 84876b8..ef45031 100644
--- a/services/BUILD.gn
+++ b/services/BUILD.gn
@@ -13,10 +13,10 @@
     "//services/gles2:lib",
     "//services/http_server",
     "//services/icu_data",
-    "//services/js",
     "//services/kiosk_wm",
     "//services/native_viewport",
     "//services/reaper",
+    "//services/url_response_disk_cache",
     "//services/surfaces",
     "//services/test_service:bindings",
     "//services/tracing",
@@ -36,4 +36,10 @@
   if (is_linux) {
     deps += [ "//services/python" ]
   }
+
+  # TODO(jamesr): We only support building V8 snapshot data on a linux host since it
+  # needs a 32 bit toolchain and we don't have one configured for mac hosts.
+  if (host_os == "linux") {
+    deps += [ "//services/js" ]
+  }
 }
diff --git a/services/dart/dart_apptests/BUILD.gn b/services/dart/dart_apptests/BUILD.gn
index 6b44108..8d2723b 100644
--- a/services/dart/dart_apptests/BUILD.gn
+++ b/services/dart/dart_apptests/BUILD.gn
@@ -11,9 +11,12 @@
     "io_internet_address_apptests.dart",
     "main.dart",
     "pingpong_apptests.dart",
+    "versioning_apptests.dart",
   ]
   deps = [
     "//mojo/dart/apptest",
+    "//mojo/public/interfaces/bindings/tests:versioning_test_client_interfaces",
+    "//mojo/public/interfaces/bindings/tests:versioning_test_service_interfaces",
     "//services/dart/test:echo_service_interface",
     "//services/dart/test:pingpong_service_interface",
   ]
diff --git a/services/dart/dart_apptests/main.dart b/services/dart/dart_apptests/main.dart
index 3c165af..7f5dfe9 100644
--- a/services/dart/dart_apptests/main.dart
+++ b/services/dart/dart_apptests/main.dart
@@ -9,10 +9,12 @@
 import 'pingpong_apptests.dart' as pingpong;
 import 'io_http_apptests.dart' as io_http;
 import 'io_internet_address_apptests.dart' as io_internet_address;
+import 'versioning_apptests.dart' as versioning;
 
 main(List args) {
   runAppTests(args[0], [echo.echoApptests,
                         io_internet_address.tests,
                         io_http.tests,
-                        pingpong.pingpongApptests]);
+                        pingpong.pingpongApptests,
+                        versioning.tests]);
 }
diff --git a/services/dart/dart_apptests/versioning_apptests.dart b/services/dart/dart_apptests/versioning_apptests.dart
new file mode 100644
index 0000000..4365cb3
--- /dev/null
+++ b/services/dart/dart_apptests/versioning_apptests.dart
@@ -0,0 +1,139 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library versioning_apptests;
+
+import 'dart:async';
+
+import 'package:apptest/apptest.dart';
+import 'package:mojo/public/dart/application.dart';
+import 'package:mojo/public/dart/bindings.dart';
+import 'package:mojo/public/dart/core.dart';
+import 'package:mojom/mojo/test/versioning/versioning_test_client.mojom.dart';
+
+tests(Application application, String url) {
+  group('Versioning Apptests', () {
+    test('Struct', () async {
+      // The service side uses a newer version of Employee definition that
+      // includes the 'birthday' field.
+
+      // Connect to human resource database.
+      var databaseProxy = new HumanResourceDatabaseProxy.unbound();
+      application.connectToService("mojo:versioning_test_service",
+                                   databaseProxy);
+
+      // Query database and get a response back (even though the client does not
+      // know about the birthday field).
+      bool retrieveFingerPrint = true;
+      var response =
+          await databaseProxy.ptr.queryEmployee(1, retrieveFingerPrint);
+      expect(response.employee.employeeId, equals(1));
+      expect(response.employee.name, equals("Homer Simpson"));
+      expect(response.employee.department, equals(Department_DEV));
+      expect(response.fingerPrint, isNotNull);
+
+      // Pass an Employee struct to the service side that lacks the 'birthday'
+      // field.
+      var newEmployee = new Employee();
+      newEmployee.employeeId = 2;
+      newEmployee.name = "Marge Simpson";
+      newEmployee.department = Department_SALES;
+      response = await databaseProxy.ptr.addEmployee(newEmployee);
+      expect(response.success, isTrue);
+
+      // Query for employee #2.
+      retrieveFingerPrint = false;
+      response = await databaseProxy.ptr.queryEmployee(2, retrieveFingerPrint);
+      expect(response.employee.employeeId, equals(2));
+      expect(response.employee.name, equals("Marge Simpson"));
+      expect(response.employee.department, equals(Department_SALES));
+      expect(response.fingerPrint, isNull);
+
+      // Disconnect from database.
+      databaseProxy.close();
+    });
+
+    test('QueryVersion', () async {
+      // Connect to human resource database.
+      var databaseProxy = new HumanResourceDatabaseProxy.unbound();
+      application.connectToService("mojo:versioning_test_service",
+                                   databaseProxy);
+      // Query the version.
+      var version = await databaseProxy.queryVersion();
+      // Expect it to be 1.
+      expect(version, equals(1));
+      // Disconnect from database.
+      databaseProxy.close();
+    });
+
+    test('RequireVersion', () async {
+      // Connect to human resource database.
+      var databaseProxy = new HumanResourceDatabaseProxy.unbound();
+      application.connectToService("mojo:versioning_test_service",
+                                   databaseProxy);
+
+      // Require version 1.
+      databaseProxy.requireVersion(1);
+      expect(databaseProxy.version, equals(1));
+
+      // Query for employee #3.
+      var retrieveFingerPrint = false;
+      var response =
+          await databaseProxy.ptr.queryEmployee(3, retrieveFingerPrint);
+
+      // Got some kind of response.
+      expect(response, isNotNull);
+
+      // Require version 3 (which cannot be satisfied).
+      databaseProxy.requireVersion(3);
+      expect(databaseProxy.version, equals(3));
+
+      // Query for employee #1, observe that an exception was thrown.
+      bool exceptionCaught = false;
+      try {
+        response =
+            await databaseProxy.ptr.queryEmployee(1, retrieveFingerPrint);
+        fail('Exception should be thrown.');
+      } catch(e) {
+        exceptionCaught = true;
+      }
+      expect(exceptionCaught, isTrue);
+
+      // No need to disconnect from database because we were disconnected by
+      // the requireVersion control message.
+    });
+
+    test('CallNonexistentMethod', () async {
+      // Connect to human resource database.
+      var databaseProxy = new HumanResourceDatabaseProxy.unbound();
+      application.connectToService("mojo:versioning_test_service",
+                                   databaseProxy);
+      const fingerPrintLength = 128;
+      var fingerPrint = new List(fingerPrintLength);
+      for (var i = 0; i < fingerPrintLength; i++) {
+        fingerPrint[i] = i + 13;
+      }
+      // Although the client side doesn't know whether the service side supports
+      // version 1, calling a version 1 method succeeds as long as the service
+      // side supports version 1.
+      var response = await databaseProxy.ptr.attachFingerPrint(1, fingerPrint);
+      expect(response.success, isTrue);
+
+      // Calling a version 2 method (which the service side doesn't support)
+      // closes the pipe.
+      bool exceptionCaught = false;
+      try {
+        response =
+            await await databaseProxy.ptr.listEmployeeIds();
+        fail('Exception should be thrown.');
+      } catch(e) {
+        exceptionCaught = true;
+      }
+      expect(exceptionCaught, isTrue);
+
+      // No need to disconnect from database because we were disconnected by
+      // the call to a version 2 method.
+    });
+  });
+}
diff --git a/services/tracing/tracing.mojom b/services/tracing/tracing.mojom
index 7c53b43..fd95c07 100644
--- a/services/tracing/tracing.mojom
+++ b/services/tracing/tracing.mojom
@@ -10,6 +10,8 @@
 // TraceDataCollector interface up until Stop() is called.
 
 interface TraceController {
+  // Categories can either be the empty string to mean the default set of
+  // categories or a comma-delimited list of categories to trace.
   StartTracing(string categories, TraceDataCollector collector);
   StopTracing();
 };
diff --git a/services/url_response_disk_cache/BUILD.gn b/services/url_response_disk_cache/BUILD.gn
new file mode 100644
index 0000000..92a4a32
--- /dev/null
+++ b/services/url_response_disk_cache/BUILD.gn
@@ -0,0 +1,90 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/module_args/mojo.gni")
+import("//mojo/public/mojo_application.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+import("//mojo/tools/embed/rules.gni")
+
+source_set("url_response_disk_cache") {
+  sources = [
+    "url_response_disk_cache_app.cc",
+    "url_response_disk_cache_app.h",
+    "url_response_disk_cache_impl.cc",
+    "url_response_disk_cache_impl.h",
+  ]
+
+  deps = [
+    ":bindings",
+    "//base",
+    "//mojo/application",
+    "//mojo/common",
+    "//mojo/environment:chromium",
+    "//mojo/public/cpp/application",
+    "//mojo/public/cpp/system",
+    "//mojo/services/url_response_disk_cache/public/interfaces",
+    "//third_party/zlib:zip",
+    "//url:url",
+  ]
+}
+
+mojom("bindings") {
+  sources = [
+    "url_response_disk_cache_entry.mojom",
+  ]
+}
+
+mojo_native_application("tests") {
+  output_name = "url_response_disk_cache_apptests"
+
+  testonly = true
+
+  sources = [
+    "url_response_disk_cache_apptest.cc",
+  ]
+
+  deps = [
+    ":embed_test_data",
+    "//base",
+    "//mojo/application",
+    "//mojo/application:test_support",
+    "//mojo/common",
+    "//mojo/public/cpp/bindings",
+    "//mojo/public/cpp/system:system",
+    "//mojo/services/network/public/interfaces:interfaces",
+    "//mojo/services/url_response_disk_cache/public/interfaces",
+  ]
+
+  data_deps = [ ":url_response_disk_cache" ]
+}
+
+action("test_data") {
+  script = "//mojo/public/tools/gn/zip.py"
+  inputs = [
+    "test_data/file1",
+    "test_data/file2",
+  ]
+  output = "$target_out_dir/test_data.zip"
+  outputs = [
+    output,
+  ]
+  rebase_base_dir = rebase_path("test_data", root_build_dir)
+  rebase_inputs = rebase_path(inputs, root_build_dir)
+  rebase_output = rebase_path(output, root_build_dir)
+  args = [
+    "--base-dir=$rebase_base_dir",
+    "--inputs=$rebase_inputs",
+    "--output=$rebase_output",
+  ]
+}
+
+embed_file("embed_test_data") {
+  source = "$target_out_dir/test_data.zip"
+  namespace = "mojo"
+  variable = "kTestData"
+
+  deps = [
+    ":test_data",
+  ]
+}
diff --git a/services/url_response_disk_cache/test_data/file1 b/services/url_response_disk_cache/test_data/file1
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/services/url_response_disk_cache/test_data/file1
@@ -0,0 +1 @@
+hello
diff --git a/services/url_response_disk_cache/test_data/file2 b/services/url_response_disk_cache/test_data/file2
new file mode 100644
index 0000000..cc628cc
--- /dev/null
+++ b/services/url_response_disk_cache/test_data/file2
@@ -0,0 +1 @@
+world
diff --git a/services/url_response_disk_cache/url_response_disk_cache_app.cc b/services/url_response_disk_cache/url_response_disk_cache_app.cc
new file mode 100644
index 0000000..5219b2d
--- /dev/null
+++ b/services/url_response_disk_cache/url_response_disk_cache_app.cc
@@ -0,0 +1,42 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/url_response_disk_cache/url_response_disk_cache_app.h"
+
+#include "services/url_response_disk_cache/url_response_disk_cache_impl.h"
+
+namespace mojo {
+
+namespace {
+
+const size_t kMaxBlockingPoolThreads = 3;
+
+}  // namespace
+
+URLResponseDiskCacheApp::URLResponseDiskCacheApp() {
+}
+
+URLResponseDiskCacheApp::~URLResponseDiskCacheApp() {
+  if (worker_pool_)
+    worker_pool_->Shutdown();
+}
+
+bool URLResponseDiskCacheApp::ConfigureIncomingConnection(
+    ApplicationConnection* connection) {
+  connection->AddService<URLResponseDiskCache>(this);
+  return true;
+}
+
+void URLResponseDiskCacheApp::Create(
+    ApplicationConnection* connection,
+    InterfaceRequest<URLResponseDiskCache> request) {
+  if (!worker_pool_) {
+    worker_pool_ = new base::SequencedWorkerPool(kMaxBlockingPoolThreads,
+                                                 "URLResponseDiskCachePool");
+  }
+  new URLResponseDiskCacheImpl(
+      worker_pool_, connection->GetRemoteApplicationURL(), request.Pass());
+}
+
+}  // namespace mojo
diff --git a/services/url_response_disk_cache/url_response_disk_cache_app.h b/services/url_response_disk_cache/url_response_disk_cache_app.h
new file mode 100644
index 0000000..8e21eda
--- /dev/null
+++ b/services/url_response_disk_cache/url_response_disk_cache_app.h
@@ -0,0 +1,38 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_URL_RESPONSE_DISK_CACHE_URL_RESPONSE_DISK_CACHE_APP_H_
+#define SERVICES_URL_RESPONSE_DISK_CACHE_URL_RESPONSE_DISK_CACHE_APP_H_
+
+#include "base/macros.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/services/url_response_disk_cache/public/interfaces/url_response_disk_cache.mojom.h"
+
+namespace mojo {
+
+class URLResponseDiskCacheApp : public ApplicationDelegate,
+                                public InterfaceFactory<URLResponseDiskCache> {
+ public:
+  URLResponseDiskCacheApp();
+  ~URLResponseDiskCacheApp() override;
+
+ private:
+  // ApplicationDelegate
+  bool ConfigureIncomingConnection(ApplicationConnection* connection) override;
+
+  // InterfaceFactory<URLResponseDiskCache>
+  void Create(ApplicationConnection* connection,
+              InterfaceRequest<URLResponseDiskCache> request) override;
+
+  scoped_refptr<base::SequencedWorkerPool> worker_pool_;
+
+  DISALLOW_COPY_AND_ASSIGN(URLResponseDiskCacheApp);
+};
+
+}  // namespace mojo
+
+#endif  // SERVICES_URL_RESPONSE_DISK_CACHE_URL_RESPONSE_DISK_CACHE_APP_H_
diff --git a/services/url_response_disk_cache/url_response_disk_cache_apptest.cc b/services/url_response_disk_cache/url_response_disk_cache_apptest.cc
new file mode 100644
index 0000000..6b543de
--- /dev/null
+++ b/services/url_response_disk_cache/url_response_disk_cache_apptest.cc
@@ -0,0 +1,267 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_util.h"
+#include "base/rand_util.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/application_test_base.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/services/network/public/interfaces/url_loader.mojom.h"
+#include "mojo/services/url_response_disk_cache/public/interfaces/url_response_disk_cache.mojom.h"
+#include "services/url_response_disk_cache/kTestData.h"
+
+namespace mojo {
+
+namespace {
+
+class URLResponseDiskCacheAppTest : public mojo::test::ApplicationTestBase {
+ public:
+  URLResponseDiskCacheAppTest() : ApplicationTestBase() {}
+  ~URLResponseDiskCacheAppTest() override {}
+
+  void SetUp() override {
+    mojo::test::ApplicationTestBase::SetUp();
+    application_impl()->ConnectToService("mojo:url_response_disk_cache",
+                                         &url_response_disk_cache_);
+  }
+
+ protected:
+  URLResponseDiskCachePtr url_response_disk_cache_;
+
+  DISALLOW_COPY_AND_ASSIGN(URLResponseDiskCacheAppTest);
+};
+
+base::FilePath toPath(Array<uint8_t> path) {
+  if (path.is_null()) {
+    return base::FilePath();
+  }
+  return base::FilePath(
+      std::string(reinterpret_cast<char*>(&path.front()), path.size()));
+}
+
+}  // namespace
+
+TEST_F(URLResponseDiskCacheAppTest, GetFile) {
+  URLResponsePtr url_response = mojo::URLResponse::New();
+  url_response->url = "http://www.example.com/1";
+  url_response->headers = Array<String>(1);
+  url_response->headers[0] = base::StringPrintf("ETag: %f", base::RandDouble());
+  DataPipe pipe;
+  std::string content = base::RandBytesAsString(32);
+  uint32_t num_bytes = content.size();
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WriteDataRaw(pipe.producer_handle.get(), content.c_str(),
+                         &num_bytes, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+  ASSERT_EQ(content.size(), num_bytes);
+  pipe.producer_handle.reset();
+  url_response->body = pipe.consumer_handle.Pass();
+  base::FilePath file;
+  base::FilePath cache_dir;
+  url_response_disk_cache_->GetFile(
+      url_response.Pass(),
+      [&file, &cache_dir](Array<uint8_t> received_file_path,
+                          Array<uint8_t> received_cache_dir_path) {
+        file = toPath(received_file_path.Pass());
+        cache_dir = toPath(received_cache_dir_path.Pass());
+      });
+  url_response_disk_cache_.WaitForIncomingMethodCall();
+  ASSERT_FALSE(file.empty());
+  std::string received_content;
+  ASSERT_TRUE(base::ReadFileToString(file, &received_content));
+  EXPECT_EQ(content, received_content);
+}
+
+TEST_F(URLResponseDiskCacheAppTest, GetExtractedContent) {
+  URLResponsePtr url_response = mojo::URLResponse::New();
+  url_response->url = "http://www.example.com/2";
+  url_response->headers = Array<String>(1);
+  url_response->headers[0] = base::StringPrintf("ETag: %f", base::RandDouble());
+  DataPipe pipe;
+  std::string content = base::RandBytesAsString(32);
+  uint32_t num_bytes = kTestData.size;
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WriteDataRaw(pipe.producer_handle.get(), kTestData.data, &num_bytes,
+                         MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+  ASSERT_EQ(kTestData.size, num_bytes);
+  pipe.producer_handle.reset();
+  url_response->body = pipe.consumer_handle.Pass();
+  base::FilePath extracted_dir;
+  base::FilePath cache_dir;
+  url_response_disk_cache_->GetExtractedContent(
+      url_response.Pass(),
+      [&extracted_dir, &cache_dir](Array<uint8_t> received_extracted_dir,
+                                   Array<uint8_t> received_cache_dir_path) {
+        extracted_dir = toPath(received_extracted_dir.Pass());
+        cache_dir = toPath(received_cache_dir_path.Pass());
+      });
+  url_response_disk_cache_.WaitForIncomingMethodCall();
+  ASSERT_FALSE(extracted_dir.empty());
+  base::FilePath file1 = extracted_dir.Append("file1");
+  std::string file_content;
+  ASSERT_TRUE(base::ReadFileToString(file1, &file_content));
+  EXPECT_EQ("hello\n", file_content);
+  base::FilePath file2 = extracted_dir.Append("file2");
+  ASSERT_TRUE(base::ReadFileToString(file2, &file_content));
+  EXPECT_EQ("world\n", file_content);
+}
+
+TEST_F(URLResponseDiskCacheAppTest, CacheTest) {
+  URLResponsePtr url_response = mojo::URLResponse::New();
+  url_response->url = "http://www.example.com/3";
+  url_response->headers = Array<String>(1);
+  std::string etag = base::StringPrintf("ETag: %f", base::RandDouble());
+  url_response->headers[0] = etag;
+  DataPipe pipe1;
+  std::string content = base::RandBytesAsString(32);
+  uint32_t num_bytes = content.size();
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WriteDataRaw(pipe1.producer_handle.get(), content.c_str(),
+                         &num_bytes, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+  ASSERT_EQ(content.size(), num_bytes);
+  pipe1.producer_handle.reset();
+  url_response->body = pipe1.consumer_handle.Pass();
+  base::FilePath file;
+  base::FilePath cache_dir;
+  url_response_disk_cache_->GetFile(
+      url_response.Pass(),
+      [&file, &cache_dir](Array<uint8_t> received_file_path,
+                          Array<uint8_t> received_cache_dir_path) {
+        file = toPath(received_file_path.Pass());
+        cache_dir = toPath(received_cache_dir_path.Pass());
+      });
+  url_response_disk_cache_.WaitForIncomingMethodCall();
+  ASSERT_FALSE(file.empty());
+  std::string received_content;
+  ASSERT_TRUE(base::ReadFileToString(file, &received_content));
+  EXPECT_EQ(content, received_content);
+  base::FilePath saved_file = cache_dir.Append("file");
+  EXPECT_FALSE(base::PathExists(saved_file));
+  std::string cached_content = base::RandBytesAsString(32);
+  ASSERT_TRUE(base::WriteFile(saved_file, cached_content.data(),
+                              cached_content.size()));
+
+  // Request using a response for the same URL, with the same etag, but a
+  // different content. The cached value should be returned.
+  url_response = mojo::URLResponse::New();
+  url_response->url = "http://www.example.com/3";
+  url_response->headers = Array<String>(1);
+  url_response->headers[0] = etag;
+  DataPipe pipe2;
+  std::string new_content = base::RandBytesAsString(32);
+  num_bytes = new_content.size();
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WriteDataRaw(pipe2.producer_handle.get(), new_content.c_str(),
+                         &num_bytes, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+  ASSERT_EQ(new_content.size(), num_bytes);
+  pipe2.producer_handle.reset();
+  url_response->body = pipe2.consumer_handle.Pass();
+  url_response_disk_cache_->GetFile(
+      url_response.Pass(),
+      [&file, &cache_dir](Array<uint8_t> received_file_path,
+                          Array<uint8_t> received_cache_dir_path) {
+        file = toPath(received_file_path.Pass());
+        cache_dir = toPath(received_cache_dir_path.Pass());
+      });
+  url_response_disk_cache_.WaitForIncomingMethodCall();
+  ASSERT_FALSE(file.empty());
+  ASSERT_TRUE(base::ReadFileToString(file, &received_content));
+  EXPECT_EQ(content, received_content);
+  saved_file = cache_dir.Append("file");
+  EXPECT_TRUE(base::PathExists(saved_file));
+  std::string received_cached_content;
+  ASSERT_TRUE(base::ReadFileToString(saved_file, &received_cached_content));
+  EXPECT_EQ(cached_content, received_cached_content);
+
+  // Request using a response for the same URL, with the a different etag. Check
+  // that the new content is returned, and the cached files is deleted.
+  url_response = mojo::URLResponse::New();
+  url_response->url = "http://www.example.com/3";
+  url_response->headers = Array<String>(1);
+  url_response->headers[0] = base::StringPrintf("ETag: %f", base::RandDouble());
+  DataPipe pipe3;
+  new_content = base::RandBytesAsString(32);
+  num_bytes = new_content.size();
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WriteDataRaw(pipe3.producer_handle.get(), new_content.c_str(),
+                         &num_bytes, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+  ASSERT_EQ(new_content.size(), num_bytes);
+  pipe3.producer_handle.reset();
+  url_response->body = pipe3.consumer_handle.Pass();
+  url_response_disk_cache_->GetFile(
+      url_response.Pass(),
+      [&file, &cache_dir](Array<uint8_t> received_file_path,
+                          Array<uint8_t> received_cache_dir_path) {
+        file = toPath(received_file_path.Pass());
+        cache_dir = toPath(received_cache_dir_path.Pass());
+      });
+  url_response_disk_cache_.WaitForIncomingMethodCall();
+  ASSERT_FALSE(file.empty());
+  ASSERT_TRUE(base::ReadFileToString(file, &received_content));
+  EXPECT_EQ(new_content, received_content);
+  saved_file = cache_dir.Append("file");
+  EXPECT_FALSE(base::PathExists(saved_file));
+  ASSERT_TRUE(base::WriteFile(saved_file, cached_content.data(),
+                              cached_content.size()));
+
+  // Request using a response without an etag header. Check that the new
+  // content is returned, and the cached files is deleted.
+  url_response = mojo::URLResponse::New();
+  url_response->url = "http://www.example.com/3";
+  DataPipe pipe4;
+  new_content = base::RandBytesAsString(32);
+  num_bytes = new_content.size();
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WriteDataRaw(pipe4.producer_handle.get(), new_content.c_str(),
+                         &num_bytes, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+  ASSERT_EQ(new_content.size(), num_bytes);
+  pipe4.producer_handle.reset();
+  url_response->body = pipe4.consumer_handle.Pass();
+  url_response_disk_cache_->GetFile(
+      url_response.Pass(),
+      [&file, &cache_dir](Array<uint8_t> received_file_path,
+                          Array<uint8_t> received_cache_dir_path) {
+        file = toPath(received_file_path.Pass());
+        cache_dir = toPath(received_cache_dir_path.Pass());
+      });
+  url_response_disk_cache_.WaitForIncomingMethodCall();
+  ASSERT_FALSE(file.empty());
+  ASSERT_TRUE(base::ReadFileToString(file, &received_content));
+  EXPECT_EQ(new_content, received_content);
+  saved_file = cache_dir.Append("file");
+  EXPECT_FALSE(base::PathExists(saved_file));
+  ASSERT_TRUE(base::WriteFile(saved_file, cached_content.data(),
+                              cached_content.size()));
+
+  // Request using a response with an etag header while the cache version
+  // doesn't have one. Check that the new content is returned, and the cached
+  // files is deleted.
+  url_response = mojo::URLResponse::New();
+  url_response->url = "http://www.example.com/3";
+  DataPipe pipe5;
+  new_content = base::RandBytesAsString(32);
+  num_bytes = new_content.size();
+  ASSERT_EQ(MOJO_RESULT_OK,
+            WriteDataRaw(pipe5.producer_handle.get(), new_content.c_str(),
+                         &num_bytes, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+  ASSERT_EQ(new_content.size(), num_bytes);
+  pipe5.producer_handle.reset();
+  url_response->body = pipe5.consumer_handle.Pass();
+  url_response_disk_cache_->GetFile(
+      url_response.Pass(),
+      [&file, &cache_dir](Array<uint8_t> received_file_path,
+                          Array<uint8_t> received_cache_dir_path) {
+        file = toPath(received_file_path.Pass());
+        cache_dir = toPath(received_cache_dir_path.Pass());
+      });
+  url_response_disk_cache_.WaitForIncomingMethodCall();
+  ASSERT_FALSE(file.empty());
+  ASSERT_TRUE(base::ReadFileToString(file, &received_content));
+  EXPECT_EQ(new_content, received_content);
+  saved_file = cache_dir.Append("file");
+  EXPECT_FALSE(base::PathExists(saved_file));
+}
+
+}  // namespace mojo
diff --git a/services/url_response_disk_cache/url_response_disk_cache_entry.mojom b/services/url_response_disk_cache/url_response_disk_cache_entry.mojom
new file mode 100644
index 0000000..c68fa2d
--- /dev/null
+++ b/services/url_response_disk_cache/url_response_disk_cache_entry.mojom
@@ -0,0 +1,12 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module mojo;
+
+// One entry in the service cache.
+struct CacheEntry {
+  string url;
+  string content_path;
+  array<string>? headers;
+};
diff --git a/services/url_response_disk_cache/url_response_disk_cache_impl.cc b/services/url_response_disk_cache/url_response_disk_cache_impl.cc
new file mode 100644
index 0000000..9804c91
--- /dev/null
+++ b/services/url_response_disk_cache/url_response_disk_cache_impl.cc
@@ -0,0 +1,328 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/url_response_disk_cache/url_response_disk_cache_impl.h"
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/important_file_writer.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "mojo/common/data_pipe_utils.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/bindings/lib/fixed_buffer.h"
+#include "services/url_response_disk_cache/url_response_disk_cache_entry.mojom.h"
+#include "third_party/zlib/google/zip_reader.h"
+#include "url/gurl.h"
+
+namespace mojo {
+
+namespace {
+
+const char kEtagHeader[] = "etag";
+
+template <typename T>
+void Serialize(T input, std::string* output) {
+  typedef typename mojo::internal::WrapperTraits<T>::DataType DataType;
+  size_t size = GetSerializedSize_(input);
+  mojo::internal::FixedBuffer buf(size);
+  DataType data_type;
+  Serialize_(input.Pass(), &buf, &data_type);
+  std::vector<Handle> handles;
+  data_type->EncodePointersAndHandles(&handles);
+  void* serialized_data = buf.Leak();
+  *output = std::string(static_cast<char*>(serialized_data), size);
+  free(serialized_data);
+}
+
+template <typename T>
+void Deserialize(std::string input, T* output) {
+  typedef typename mojo::internal::WrapperTraits<T>::DataType DataType;
+  DataType data_type = reinterpret_cast<DataType>(&input[0]);
+  std::vector<Handle> handles;
+  data_type->DecodePointersAndHandles(&handles);
+  Deserialize_(data_type, output);
+}
+
+Array<uint8_t> PathToArray(const base::FilePath& path) {
+  if (path.empty())
+    return Array<uint8_t>();
+  const std::string& string = path.value();
+  Array<uint8_t> result(string.size());
+  memcpy(&result.front(), string.data(), string.size());
+  return result.Pass();
+}
+
+// Encode a string in ascii. This uses # as an escape character. It also escapes
+// ':' because it is an usual path separator.
+std::string EncodeString(const std::string& string) {
+  std::string result = "";
+  for (size_t i = 0; i < string.size(); ++i) {
+    unsigned char c = string[i];
+    if (c >= 32 && c < 128 && c != '#' && c != ':') {
+      result += c;
+    } else {
+      result += base::StringPrintf("#%02x", c);
+    }
+  }
+  return result;
+}
+
+// This service use a directory under HOME to store all of its data,
+base::FilePath GetBaseDirectory() {
+  return base::FilePath(getenv("HOME")).Append(".mojo_url_response_disk_cache");
+}
+
+// Returns the directory used store cached data for the given |url|, under
+// |base_directory|.
+base::FilePath GetDirName(base::FilePath base_directory,
+                          const std::string& url) {
+  // TODO(qsr): If the speed of directory traversal is problematic, this might
+  // need to change to use less directories.
+  return base_directory.Append(EncodeString(url));
+}
+
+// Returns the directory that the consumer can use to cache its own data.
+base::FilePath GetConsumerCacheDirectory(const base::FilePath& main_cache) {
+  return main_cache.Append("consumer_cache");
+}
+
+// Returns the path of the sentinel used to keep track of a zipped response has
+// already been extracted.
+base::FilePath GetExtractedSentinel(const base::FilePath& main_cache) {
+  return main_cache.Append("extracted_sentinel");
+}
+
+// Runs the given callback. If |success| is false, call back with an error.
+// Otherwise, store |entry| in |entry_path|, then call back with the given
+// paths.
+void RunCallbackWithSuccess(
+    const URLResponseDiskCacheImpl::FilePathPairCallback& callback,
+    const base::FilePath& content_path,
+    const base::FilePath& cache_dir,
+    const base::FilePath& entry_path,
+    CacheEntryPtr entry,
+    bool success) {
+  if (!success) {
+    callback.Run(base::FilePath(), base::FilePath());
+    return;
+  }
+  std::string serialized_entry;
+  Serialize(entry.Pass(), &serialized_entry);
+  // We can ignore write error, as it will just force to clear the cache on the
+  // next request.
+  base::ImportantFileWriter::WriteFileAtomically(entry_path, serialized_entry);
+  callback.Run(content_path, cache_dir);
+}
+
+// Run the given mojo callback with the given paths.
+void RunMojoCallback(
+    const Callback<void(Array<uint8_t>, Array<uint8_t>)>& callback,
+    const base::FilePath& path1,
+    const base::FilePath& path2) {
+  callback.Run(PathToArray(path1), PathToArray(path2));
+}
+
+// Returns the list of values for the given |header_name| in the given list of
+// headers.
+std::vector<std::string> GetHeaderValues(const std::string& header_name,
+                                         const Array<String>& headers) {
+  std::vector<std::string> result;
+  for (std::string header : headers.storage()) {
+    if (StartsWithASCII(header, header_name, false)) {
+      auto begin = header.begin();
+      auto end = header.end();
+      begin += header_name.size();
+      // Extract the content of the header by finding the remaining string after
+      // the ':' and stripping all spaces.
+      while (begin < end && *begin != ':')
+        begin++;
+      if (begin < end) {
+        begin++;
+        while (begin < end && *begin == ' ')
+          begin++;
+        while (end > begin && *(end - 1) == ' ')
+          end--;
+        if (begin < end) {
+          result.push_back(std::string(begin, end));
+        }
+      }
+    }
+  }
+  return result;
+}
+
+// Returns whether the given directory |dir| constains a valid entry file for
+// the given |response|. If this is the case and |output| is not |nullptr|, then
+// the deserialized entry is returned in |*output|.
+bool IsCacheEntryValid(const base::FilePath& dir,
+                       URLResponse* response,
+                       CacheEntryPtr* output) {
+  // Find the entry file, and deserialize it.
+  base::FilePath entry_path = dir.Append("entry");
+  if (!base::PathExists(entry_path))
+    return false;
+  std::string serialized_entry;
+  if (!ReadFileToString(entry_path, &serialized_entry))
+    return false;
+  CacheEntryPtr entry;
+  Deserialize(serialized_entry, &entry);
+
+  // If |entry| or |response| has not headers, it is not possible to check if
+  // the entry is valid, so returns |false|.
+  if (entry->headers.is_null() || response->headers.is_null())
+    return false;
+
+  // Only handle etag for the moment.
+  std::string etag_header_name = kEtagHeader;
+  std::vector<std::string> entry_etags =
+      GetHeaderValues(etag_header_name, entry->headers);
+  if (entry_etags.size() == 0)
+    return false;
+  std::vector<std::string> response_etags =
+      GetHeaderValues(etag_header_name, response->headers);
+  if (response_etags.size() == 0)
+    return false;
+
+  // Looking for the first etag header.
+  bool result = entry_etags[0] == response_etags[0];
+
+  // Returns |entry| if requested.
+  if (output)
+    *output = entry.Pass();
+
+  return result;
+}
+
+}  // namespace
+
+URLResponseDiskCacheImpl::URLResponseDiskCacheImpl(
+    scoped_refptr<base::SequencedWorkerPool> worker_pool,
+    const std::string& remote_application_url,
+    InterfaceRequest<URLResponseDiskCache> request)
+    : worker_pool_(worker_pool), binding_(this, request.Pass()) {
+  base_directory_ = GetBaseDirectory();
+  // The cached files are shared only for application of the same origin.
+  if (remote_application_url != "") {
+    base_directory_ = base_directory_.Append(
+        EncodeString(GURL(remote_application_url).GetOrigin().spec()));
+  }
+}
+
+URLResponseDiskCacheImpl::~URLResponseDiskCacheImpl() {
+}
+
+void URLResponseDiskCacheImpl::GetFile(URLResponsePtr response,
+                                       const GetFileCallback& callback) {
+  return GetFileInternal(response.Pass(),
+                         base::Bind(&RunMojoCallback, callback));
+}
+
+void URLResponseDiskCacheImpl::GetExtractedContent(
+    URLResponsePtr response,
+    const GetExtractedContentCallback& callback) {
+  base::FilePath dir = GetDirName(base_directory_, response->url);
+  base::FilePath extracted_dir = dir.Append("extracted");
+  if (IsCacheEntryValid(dir, response.get(), nullptr) &&
+      PathExists(GetExtractedSentinel(dir))) {
+    callback.Run(PathToArray(extracted_dir), PathToArray(dir));
+    return;
+  }
+
+  GetFileInternal(
+      response.Pass(),
+      base::Bind(&URLResponseDiskCacheImpl::GetExtractedContentInternal,
+                 base::Unretained(this), base::Bind(&RunMojoCallback, callback),
+                 extracted_dir));
+}
+
+void URLResponseDiskCacheImpl::GetFileInternal(
+    URLResponsePtr response,
+    const FilePathPairCallback& callback) {
+  base::FilePath dir = GetDirName(base_directory_, response->url);
+
+  // Check if the response is cached and valid. If that's the case, returns the
+  // cached value.
+  CacheEntryPtr entry;
+  if (IsCacheEntryValid(dir, response.get(), &entry)) {
+    callback.Run(base::FilePath(entry->content_path),
+                 GetConsumerCacheDirectory(dir));
+    return;
+  }
+
+  // As the response was either not cached or the cached value is not valid, if
+  // the cache directory for the response exists, it needs to be cleaned.
+  if (base::PathExists(dir)) {
+    base::FilePath to_delete;
+    CHECK(CreateTemporaryDirInDir(base_directory_, "to_delete", &to_delete));
+    CHECK(Move(dir, to_delete));
+    worker_pool_->PostTask(
+        FROM_HERE,
+        base::Bind(base::IgnoreResult(&base::DeleteFile), to_delete, true));
+  }
+
+  // If the response has not a valid body, and it is not possible to create
+  // either the cache directory or the consumer cache directory, returns an
+  // error.
+  if (!response->body.is_valid() ||
+      !base::CreateDirectoryAndGetError(dir, nullptr) ||
+      !base::CreateDirectoryAndGetError(GetConsumerCacheDirectory(dir),
+                                        nullptr)) {
+    callback.Run(base::FilePath(), base::FilePath());
+    return;
+  }
+
+  // Fill the entry values for the request.
+  base::FilePath entry_path = dir.Append("entry");
+  base::FilePath content;
+  CHECK(CreateTemporaryFileInDir(dir, &content));
+  entry = CacheEntry::New();
+  entry->url = response->url;
+  entry->content_path = content.value();
+  entry->headers = response->headers.Pass();
+  // Asynchronously copy the response body to the cached file. The entry is send
+  // to the callback so that it is saved on disk only if the copy of the body
+  // succeded.
+  common::CopyToFile(response->body.Pass(), content, worker_pool_.get(),
+                     base::Bind(&RunCallbackWithSuccess, callback, content,
+                                GetConsumerCacheDirectory(dir), entry_path,
+                                base::Passed(entry.Pass())));
+}
+
+void URLResponseDiskCacheImpl::GetExtractedContentInternal(
+    const FilePathPairCallback& callback,
+    const base::FilePath& extracted_dir,
+    const base::FilePath& content,
+    const base::FilePath& dir) {
+  // If it is not possible to get the cached file, returns an error.
+  if (content.empty()) {
+    callback.Run(base::FilePath(), base::FilePath());
+    return;
+  }
+
+  // Unzip the content to the extracted directory. In case of any error, returns
+  // an error.
+  zip::ZipReader reader;
+  if (!reader.Open(content)) {
+    callback.Run(base::FilePath(), base::FilePath());
+    return;
+  }
+  while (reader.HasMore()) {
+    bool success = reader.OpenCurrentEntryInZip();
+    success = success && reader.ExtractCurrentEntryIntoDirectory(extracted_dir);
+    success = success && reader.AdvanceToNextEntry();
+    if (!success) {
+      callback.Run(base::FilePath(), base::FilePath());
+      return;
+    }
+  }
+  // We can ignore write error, as it will just force to clear the cache on the
+  // next request.
+  WriteFile(GetExtractedSentinel(dir), nullptr, 0);
+  callback.Run(extracted_dir, GetConsumerCacheDirectory(dir));
+}
+
+}  // namespace mojo
diff --git a/services/url_response_disk_cache/url_response_disk_cache_impl.h b/services/url_response_disk_cache/url_response_disk_cache_impl.h
new file mode 100644
index 0000000..1154edc
--- /dev/null
+++ b/services/url_response_disk_cache/url_response_disk_cache_impl.h
@@ -0,0 +1,58 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_URL_RESPONSE_DISK_CACHE_URL_RESPONSE_DISK_CACHE_IMPL_H_
+#define SERVICES_URL_RESPONSE_DISK_CACHE_URL_RESPONSE_DISK_CACHE_IMPL_H_
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/services/url_response_disk_cache/public/interfaces/url_response_disk_cache.mojom.h"
+
+namespace mojo {
+
+class URLResponseDiskCacheImpl : public URLResponseDiskCache {
+ public:
+  using FilePathPairCallback =
+      base::Callback<void(const base::FilePath&, const base::FilePath&)>;
+
+  URLResponseDiskCacheImpl(scoped_refptr<base::SequencedWorkerPool> worker_pool,
+                           const std::string& remote_application_url,
+                           InterfaceRequest<URLResponseDiskCache> request);
+  ~URLResponseDiskCacheImpl() override;
+
+ private:
+  // URLResponseDiskCache
+  void GetFile(mojo::URLResponsePtr response,
+               const GetFileCallback& callback) override;
+  void GetExtractedContent(
+      mojo::URLResponsePtr response,
+      const GetExtractedContentCallback& callback) override;
+
+  // As |GetFile|, but uses FilePath instead of mojo arrays.
+  void GetFileInternal(mojo::URLResponsePtr response,
+                       const FilePathPairCallback& callback);
+
+  // Internal implementation of |GetExtractedContent|. The parameters are:
+  // |callback|: The callback to return values to the caller. It uses FilePath
+  //             instead of mojo arrays.
+  // |cache_dir|: The cache dir to return to the consumer.
+  // |content|: The content of the body of the response.
+  // |dir|: The base directory under which all data for the response is stored.
+  void GetExtractedContentInternal(const FilePathPairCallback& callback,
+                                   const base::FilePath& cache_dir,
+                                   const base::FilePath& content,
+                                   const base::FilePath& dir);
+
+  scoped_refptr<base::SequencedWorkerPool> worker_pool_;
+  base::FilePath base_directory_;
+  StrongBinding<URLResponseDiskCache> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(URLResponseDiskCacheImpl);
+};
+
+}  // namespace mojo
+
+#endif  // SERVICES_URL_RESPONSE_DISK_CACHE_URL_RESPONSE_DISK_CACHE_IMPL_H_
diff --git a/shell/BUILD.gn b/shell/BUILD.gn
index fd8ff5c..5827d86 100644
--- a/shell/BUILD.gn
+++ b/shell/BUILD.gn
@@ -123,6 +123,8 @@
 # TODO(vtl): Split this target into parent/child/common libs.
 source_set("lib") {
   sources = [
+    "background_application_loader.cc",
+    "background_application_loader.h",
     "child_process_host.cc",
     "child_process_host.h",
     "command_line_util.cc",
@@ -141,6 +143,8 @@
     "tracer.h",
     "url_resolver.cc",
     "url_resolver.h",
+    "url_response_disk_cache_loader.cc",
+    "url_response_disk_cache_loader.h",
   ]
 
   deps = [
@@ -156,6 +160,7 @@
     "//mojo/public/interfaces/application",
     "//mojo/services/network/public/interfaces",
     "//shell/application_manager",
+    "//services/url_response_disk_cache",
     "//services/tracing:bindings",
     "//url",
   ]
@@ -170,8 +175,6 @@
       "android/android_handler.h",
       "android/android_handler_loader.cc",
       "android/android_handler_loader.h",
-      "android/background_application_loader.cc",
-      "android/background_application_loader.h",
       "android/intent_receiver_manager_factory.cc",
       "android/intent_receiver_manager_factory.h",
       "android/intent_receiver_manager_impl.cc",
@@ -376,6 +379,7 @@
 
 test("mojo_shell_tests") {
   sources = [
+    "background_application_loader_unittest.cc",
     "child_process_host_unittest.cc",
     "command_line_util_unittest.cc",
     "context_unittest.cc",
@@ -412,8 +416,6 @@
   ]
 
   if (is_android) {
-    sources += [ "android/background_application_loader_unittest.cc" ]
-
     deps += [ ":jni_headers" ]
 
     apk_deps = [
diff --git a/shell/android/android_handler.cc b/shell/android/android_handler.cc
index 036be29..9d3181b 100644
--- a/shell/android/android_handler.cc
+++ b/shell/android/android_handler.cc
@@ -8,6 +8,8 @@
 #include "base/android/jni_string.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
 #include "base/scoped_native_library.h"
 #include "base/trace_event/trace_event.h"
 #include "jni/AndroidHandler_jni.h"
@@ -43,8 +45,7 @@
   // Load the library, so that we can set the application context there if
   // needed.
   // TODO(vtl): We'd use a ScopedNativeLibrary, but it doesn't have .get()!
-  base::NativeLibrary app_library =
-      LoadNativeApplication(app_path, NativeApplicationCleanup::DELETE);
+  base::NativeLibrary app_library = LoadNativeApplication(app_path);
   if (!app_library)
     return;
 
@@ -90,20 +91,38 @@
   uintptr_t tracing_id = reinterpret_cast<uintptr_t>(this);
   TRACE_EVENT_ASYNC_BEGIN1("android_handler", "AndroidHandler::RunApplication",
                            tracing_id, "url", std::string(response->url));
-  ScopedJavaLocalRef<jstring> j_archive_path =
-      Java_AndroidHandler_getNewTempArchivePath(env, GetApplicationContext());
-  base::FilePath archive_path(
-      ConvertJavaStringToUTF8(env, j_archive_path.obj()));
+  base::FilePath extracted_dir;
+  base::FilePath cache_dir;
+  {
+    base::MessageLoop loop;
+    handler_task_runner_->PostTask(
+        FROM_HERE,
+        base::Bind(&AndroidHandler::ExtractApplication, base::Unretained(this),
+                   base::Unretained(&extracted_dir),
+                   base::Unretained(&cache_dir), base::Passed(response.Pass()),
+                   base::Bind(base::IgnoreResult(
+                                  &base::SingleThreadTaskRunner::PostTask),
+                              loop.task_runner(), FROM_HERE,
+                              base::MessageLoop::QuitWhenIdleClosure())));
+    base::RunLoop().Run();
+  }
 
-  mojo::common::BlockingCopyToFile(response->body.Pass(), archive_path);
+  ScopedJavaLocalRef<jstring> j_extracted_dir =
+      ConvertUTF8ToJavaString(env, extracted_dir.value());
+  ScopedJavaLocalRef<jstring> j_cache_dir =
+      ConvertUTF8ToJavaString(env, cache_dir.value());
   RunAndroidApplicationFn run_android_application_fn = &RunAndroidApplication;
   Java_AndroidHandler_bootstrap(
-      env, GetApplicationContext(), tracing_id, j_archive_path.obj(),
+      env, GetApplicationContext(), tracing_id, j_extracted_dir.obj(),
+      j_cache_dir.obj(),
       application_request.PassMessagePipe().release().value(),
       reinterpret_cast<jlong>(run_android_application_fn));
 }
 
 void AndroidHandler::Initialize(mojo::ApplicationImpl* app) {
+  handler_task_runner_ = base::MessageLoop::current()->task_runner();
+  app->ConnectToService("mojo:url_response_disk_cache",
+                        &url_response_disk_cache_);
 }
 
 bool AndroidHandler::ConfigureIncomingConnection(
@@ -113,6 +132,28 @@
   return true;
 }
 
+void AndroidHandler::ExtractApplication(base::FilePath* extracted_dir,
+                                        base::FilePath* cache_dir,
+                                        mojo::URLResponsePtr response,
+                                        const base::Closure& callback) {
+  url_response_disk_cache_->GetExtractedContent(
+      response.Pass(),
+      [extracted_dir, cache_dir, callback](mojo::Array<uint8_t> extracted_path,
+                                           mojo::Array<uint8_t> cache_path) {
+        if (extracted_path.is_null()) {
+          *extracted_dir = base::FilePath();
+          *cache_dir = base::FilePath();
+        } else {
+          *extracted_dir = base::FilePath(
+              std::string(reinterpret_cast<char*>(&extracted_path.front()),
+                          extracted_path.size()));
+          *cache_dir = base::FilePath(std::string(
+              reinterpret_cast<char*>(&cache_path.front()), cache_path.size()));
+        }
+        callback.Run();
+      });
+}
+
 bool RegisterAndroidHandlerJni(JNIEnv* env) {
   return RegisterNativesImpl(env);
 }
diff --git a/shell/android/android_handler.h b/shell/android/android_handler.h
index f2779ff..19adf36 100644
--- a/shell/android/android_handler.h
+++ b/shell/android/android_handler.h
@@ -7,10 +7,12 @@
 
 #include <jni.h>
 
+#include "base/single_thread_task_runner.h"
 #include "mojo/application/content_handler_factory.h"
 #include "mojo/public/cpp/application/application_delegate.h"
 #include "mojo/public/cpp/application/interface_factory_impl.h"
 #include "mojo/services/content_handler/public/interfaces/content_handler.mojom.h"
+#include "mojo/services/url_response_disk_cache/public/interfaces/url_response_disk_cache.mojom.h"
 #include "shell/android/intent_receiver_manager_factory.h"
 
 namespace base {
@@ -36,8 +38,15 @@
       mojo::InterfaceRequest<mojo::Application> application_request,
       mojo::URLResponsePtr response) override;
 
+  void ExtractApplication(base::FilePath* extracted_dir,
+                          base::FilePath* cache_dir,
+                          mojo::URLResponsePtr response,
+                          const base::Closure& callback);
+
   mojo::ContentHandlerFactory content_handler_factory_;
   IntentReceiverManagerFactory intent_receiver_manager_factory_;
+  mojo::URLResponseDiskCachePtr url_response_disk_cache_;
+  scoped_refptr<base::SingleThreadTaskRunner> handler_task_runner_;
 
   MOJO_DISALLOW_COPY_AND_ASSIGN(AndroidHandler);
 };
diff --git a/shell/android/apk/src/org/chromium/mojo/shell/AndroidHandler.java b/shell/android/apk/src/org/chromium/mojo/shell/AndroidHandler.java
index 138ed5a..9719f19 100644
--- a/shell/android/apk/src/org/chromium/mojo/shell/AndroidHandler.java
+++ b/shell/android/apk/src/org/chromium/mojo/shell/AndroidHandler.java
@@ -14,7 +14,6 @@
 import org.chromium.base.TraceEvent;
 
 import java.io.File;
-import java.io.IOException;
 import java.lang.reflect.Constructor;
 
 /**
@@ -36,33 +35,19 @@
     // File extensions used to identify application libraries in the provided archive.
     private static final String JAVA_LIBRARY_SUFFIX = ".dex.jar";
     private static final String NATIVE_LIBRARY_SUFFIX = ".so";
-    // Filename sections used for naming temporary files holding application files.
-    private static final String ARCHIVE_PREFIX = "archive";
-    private static final String ARCHIVE_SUFFIX = ".zip";
 
-    // Directories used to hold temporary files. These are cleared when clearTemporaryFiles() is
-    // called.
-    private static final String DEX_OUTPUT_DIRECTORY = "dex_output";
-    private static final String APP_DIRECTORY = "applications";
-    private static final String ASSET_DIRECTORY = "assets";
-
-    /**
-     * Deletes directories holding the temporary files. This should be called early on shell startup
-     * to clean up after the previous run.
-     */
-    static void clearTemporaryFiles(Context context) {
-        FileHelper.deleteRecursively(getDexOutputDir(context));
-        FileHelper.deleteRecursively(getAppDir(context));
-        FileHelper.deleteRecursively(getAssetDir(context));
-    }
-
-    /**
-     * Returns the path at which the native part should save the application archive.
-     */
-    @CalledByNative
-    private static String getNewTempArchivePath(Context context) throws IOException {
-        return File.createTempFile(ARCHIVE_PREFIX, ARCHIVE_SUFFIX,
-                getAppDir(context)).getAbsolutePath();
+    // Recursively finds the first file in |dir| whose name ends with |suffix|. Returns |null| if
+    // none is found.
+    static File find(File dir, String suffix) {
+        for (File child : dir.listFiles()) {
+            if (child.isDirectory()) {
+                File result = find(child, suffix);
+                if (result != null) return result;
+            } else {
+                if (child.getName().endsWith(suffix)) return child;
+            }
+        }
+        return null;
     }
 
     /**
@@ -70,56 +55,59 @@
      *
      * @param context the application context
      * @param tracingId opaque id, used for tracing.
-     * @param archivePath the path of the archive containing the application to be run
+     * @param extractedPath the path of the directory containing the application to be run
+     * @param cachePath the path of a cache directory that can be used to extract assets
      * @param handle handle to the shell to be passed to the native application. On the Java side
      *            this is opaque payload.
      * @param runApplicationPtr pointer to the function that will set the native thunks and call
      *            into the application MojoMain. On the Java side this is opaque payload.
      */
     @CalledByNative
-    private static boolean bootstrap(Context context, long tracingId, String archivePath,
-            int handle, long runApplicationPtr) {
-        File bootstrap_java_library;
-        File bootstrap_native_library;
-        try {
-            TraceEvent.begin("ExtractBootstrapJavaLibrary");
-            bootstrap_java_library = FileHelper.extractFromAssets(context, BOOTSTRAP_JAVA_LIBRARY,
-                    getAssetDir(context), true);
-            TraceEvent.end("ExtractBootstrapJavaLibrary");
-            TraceEvent.begin("ExtractBootstrapNativeLibrary");
-            bootstrap_native_library = FileHelper.extractFromAssets(context,
-                    BOOTSTRAP_NATIVE_LIBRARY, getAssetDir(context), true);
-            TraceEvent.end("ExtractBootstrapNativeLibrary");
-        } catch (Exception e) {
-            Log.e(TAG, "Extraction of bootstrap files from assets failed.", e);
-            return false;
-        }
+    private static boolean bootstrap(Context context, long tracingId, String extractedPath,
+            String cachePath, int handle, long runApplicationPtr) {
+        File extractedDir = new File(extractedPath);
+        File cacheDir = new File(cachePath);
+        File compiledDexDir = new File(cacheDir, "dex");
+        File assetDir = new File(cacheDir, "asset");
+        assetDir.mkdirs();
+        File preparedSentinel = new File(cacheDir, "prepared");
 
-        File application_java_library;
-        File application_native_library;
-        try {
-            File archive = new File(archivePath);
-            TraceEvent.begin("ExtractApplicationJavaLibrary");
-            application_java_library = FileHelper.extractFromArchive(archive, JAVA_LIBRARY_SUFFIX,
-                    getAppDir(context));
-            TraceEvent.end("ExtractApplicationJavaLibrary");
-            TraceEvent.begin("ExtractApplicationNativeLibrary");
-            application_native_library = FileHelper.extractFromArchive(archive,
-                    NATIVE_LIBRARY_SUFFIX, getAppDir(context));
-            TraceEvent.end("ExtractApplicationNativeLibrary");
-        } catch (Exception e) {
-            Log.e(TAG, "Extraction of application files from the archive failed.", e);
-            return false;
+        // If the sentinel doesn't exist, extract the assets from the apk.
+        if (!preparedSentinel.exists()) {
+            compiledDexDir.mkdirs();
+            try {
+                TraceEvent.begin("ExtractBootstrapJavaLibrary");
+                FileHelper.extractFromAssets(context, BOOTSTRAP_JAVA_LIBRARY, assetDir, false);
+                TraceEvent.end("ExtractBootstrapJavaLibrary");
+                TraceEvent.begin("ExtractBootstrapNativeLibrary");
+                FileHelper.extractFromAssets(context, BOOTSTRAP_NATIVE_LIBRARY, assetDir, false);
+                TraceEvent.end("ExtractBootstrapNativeLibrary");
+                TraceEvent.begin("MoveBootstrapNativeLibrary");
+                // Rename the bootstrap library to prevent dlopen to think it is alread opened.
+                new File(assetDir, BOOTSTRAP_NATIVE_LIBRARY)
+                        .renameTo(File.createTempFile("bootstrap", ".so", assetDir));
+                TraceEvent.end("MoveBootstrapNativeLibrary");
+                new java.io.FileOutputStream(preparedSentinel).close();
+            } catch (Exception e) {
+                Log.e(TAG, "Extraction of bootstrap files from assets failed.", e);
+                return false;
+            }
         }
+        // Find the 4 files needed to execute the android application.
+        File bootstrap_java_library = new File(assetDir, BOOTSTRAP_JAVA_LIBRARY);
+        File bootstrap_native_library = find(assetDir, NATIVE_LIBRARY_SUFFIX);
+        File application_java_library = find(extractedDir, JAVA_LIBRARY_SUFFIX);
+        File application_native_library = find(extractedDir, NATIVE_LIBRARY_SUFFIX);
 
+        // Compile the java files.
         String dexPath = bootstrap_java_library.getAbsolutePath() + File.pathSeparator
                 + application_java_library.getAbsolutePath();
         TraceEvent.begin("CreateDexClassLoader");
         DexClassLoader bootstrapLoader = new DexClassLoader(dexPath,
-                getDexOutputDir(context).getAbsolutePath(), null,
-                ClassLoader.getSystemClassLoader());
+                compiledDexDir.getAbsolutePath(), null, ClassLoader.getSystemClassLoader());
         TraceEvent.end("CreateDexClassLoader");
 
+        // Create the instance of the Bootstrap class from the compiled java file and run it.
         try {
             Class<?> loadedClass = bootstrapLoader.loadClass(BOOTSTRAP_CLASS);
             Class<? extends Runnable> bootstrapClass = loadedClass.asSubclass(Runnable.class);
@@ -136,15 +124,4 @@
         return true;
     }
 
-    private static File getDexOutputDir(Context context) {
-        return context.getDir(DEX_OUTPUT_DIRECTORY, Context.MODE_PRIVATE);
-    }
-
-    private static File getAppDir(Context context) {
-        return context.getDir(APP_DIRECTORY, Context.MODE_PRIVATE);
-    }
-
-    private static File getAssetDir(Context context) {
-        return context.getDir(ASSET_DIRECTORY, Context.MODE_PRIVATE);
-    }
 }
diff --git a/shell/android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java b/shell/android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java
index 18e4050..0dbdeab 100644
--- a/shell/android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java
+++ b/shell/android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java
@@ -23,24 +23,16 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        clearTemporaryFiles();
         initializeJavaUtils();
         initializeNative();
     }
 
     /**
-     * Deletes the temporary files and directories created in the previous run of the application.
-     * This is important regardless of cleanups on exit, as the previous run could have crashed.
-     */
-    private void clearTemporaryFiles() {
-        AndroidHandler.clearTemporaryFiles(this);
-    }
-
-    /**
      * Initializes Java-side utils.
      */
     private void initializeJavaUtils() {
-        PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX, getApplicationContext());
+        PathUtils.setPrivateDataDirectorySuffix(
+                PRIVATE_DATA_DIRECTORY_SUFFIX, getApplicationContext());
     }
 
     /**
diff --git a/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java b/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java
index ab834af..e815967 100644
--- a/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java
+++ b/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java
@@ -29,6 +29,10 @@
     private static final String NETWORK_LIBRARY_APP = "network_service.mojo";
     // Directory where the child executable will be extracted.
     private static final String CHILD_DIRECTORY = "child";
+    // Directory to set TMPDIR to.
+    private static final String TMP_DIRECTORY = "tmp";
+    // Directory to set HOME to.
+    private static final String HOME_DIRECTORY = "home";
     // Name of the child executable.
     private static final String MOJO_SHELL_CHILD_EXECUTABLE = "mojo_shell_child";
     // Path to the default origin of mojo: apps.
@@ -69,7 +73,8 @@
             nativeInit(applicationContext, mojoShellChild.getAbsolutePath(),
                     parametersList.toArray(new String[parametersList.size()]),
                     getLocalAppsDir(applicationContext).getAbsolutePath(),
-                    getTmpDir(applicationContext).getAbsolutePath());
+                    getTmpDir(applicationContext).getAbsolutePath(),
+                    getHomeDir(applicationContext).getAbsolutePath());
             sInitialized = true;
         } catch (Exception e) {
             Log.e(TAG, "ShellMain initialization failed.", e);
@@ -109,7 +114,11 @@
     }
 
     private static File getTmpDir(Context context) {
-        return new File(context.getCacheDir(), "tmp");
+        return new File(context.getCacheDir(), TMP_DIRECTORY);
+    }
+
+    private static File getHomeDir(Context context) {
+        return context.getDir(HOME_DIRECTORY, Context.MODE_PRIVATE);
     }
 
     @CalledByNative
@@ -121,7 +130,7 @@
      * Initializes the native system. This API should be called only once per process.
      **/
     private static native void nativeInit(Context context, String mojoShellChildPath,
-            String[] parameters, String bundledAppsDirectory, String tmpDir);
+            String[] parameters, String bundledAppsDirectory, String tmpDir, String homeDir);
 
     private static native boolean nativeStart();
 
diff --git a/shell/android/main.cc b/shell/android/main.cc
index ac72bcb..122925b 100644
--- a/shell/android/main.cc
+++ b/shell/android/main.cc
@@ -24,10 +24,10 @@
 #include "mojo/common/message_pump_mojo.h"
 #include "mojo/services/window_manager/public/interfaces/window_manager.mojom.h"
 #include "shell/android/android_handler_loader.h"
-#include "shell/android/background_application_loader.h"
 #include "shell/android/native_viewport_application_loader.h"
 #include "shell/android/ui_application_loader_android.h"
 #include "shell/application_manager/application_loader.h"
+#include "shell/background_application_loader.h"
 #include "shell/command_line_util.h"
 #include "shell/context.h"
 #include "shell/init.h"
@@ -181,17 +181,23 @@
                  jstring mojo_shell_child_path,
                  jobjectArray jparameters,
                  jstring j_local_apps_directory,
-                 jstring j_tmp_dir) {
+                 jstring j_tmp_dir,
+                 jstring j_home_dir) {
   g_internal_data.Get().main_activity.Reset(env, activity);
   // Initially, the shell runner is not ready.
   g_internal_data.Get().shell_runner_ready.reset(
       new base::WaitableEvent(true, false));
 
   std::string tmp_dir = base::android::ConvertJavaStringToUTF8(env, j_tmp_dir);
-  // Setting the TMPDIR environment variable so that applications can use it.
+  // Setting the TMPDIR and HOME environment variables so that applications can
+  // use it.
   // TODO(qsr) We will need our subprocesses to inherit this.
   int return_value = setenv("TMPDIR", tmp_dir.c_str(), 1);
   DCHECK_EQ(return_value, 0);
+  return_value = setenv(
+      "HOME", base::android::ConvertJavaStringToUTF8(env, j_home_dir).c_str(),
+      1);
+  DCHECK_EQ(return_value, 0);
 
   base::android::ScopedJavaLocalRef<jobject> scoped_activity(env, activity);
   base::android::InitApplicationContext(env, scoped_activity);
@@ -206,8 +212,12 @@
   Tracer* tracer = new Tracer;
   g_internal_data.Get().tracer.reset(tracer);
   bool trace_startup = command_line->HasSwitch(switches::kTraceStartup);
-  if (trace_startup)
-    tracer->Start(command_line->GetSwitchValueASCII(switches::kTraceStartup));
+  if (trace_startup) {
+    tracer->Start(
+        command_line->GetSwitchValueASCII(switches::kTraceStartup),
+        command_line->GetSwitchValueASCII(switches::kTraceStartupDuration),
+        tmp_dir + "/mojo_shell.trace");
+  }
 
   g_internal_data.Get().shell_runner.reset(
       new MojoShellRunner(base::FilePath(base::android::ConvertJavaStringToUTF8(
@@ -228,14 +238,7 @@
 
   g_internal_data.Get().java_message_loop.reset(new base::MessageLoopForUI);
   base::MessageLoopForUI::current()->Start();
-
-  if (trace_startup) {
-    g_internal_data.Get().java_message_loop->PostDelayedTask(
-        FROM_HERE,
-        base::Bind(&Tracer::StopAndFlushToFile, base::Unretained(tracer),
-                   tmp_dir + "/mojo_shell.trace"),
-        base::TimeDelta::FromSeconds(5));
-  }
+  tracer->DidCreateMessageLoop();
 
   // TODO(abarth): At which point should we switch to cross-platform
   // initialization?
diff --git a/shell/application_manager/BUILD.gn b/shell/application_manager/BUILD.gn
index 72a90c8..e5ebb5d 100644
--- a/shell/application_manager/BUILD.gn
+++ b/shell/application_manager/BUILD.gn
@@ -33,6 +33,7 @@
     "//mojo/common",
     "//mojo/public/interfaces/application:application",
     "//mojo/services/network/public/interfaces",
+    "//mojo/services/url_response_disk_cache/public/interfaces",
     "//url",
   ]
   deps = [
diff --git a/shell/application_manager/application_manager.cc b/shell/application_manager/application_manager.cc
index e1a6125..abaa11a 100644
--- a/shell/application_manager/application_manager.cc
+++ b/shell/application_manager/application_manager.cc
@@ -178,23 +178,19 @@
       parameters);
 
   if (resolved_url.SchemeIsFile()) {
-    new LocalFetcher(
-        resolved_url, GetBaseURLAndQuery(resolved_url, nullptr),
-        base::Bind(callback, NativeApplicationCleanup::DONT_DELETE));
+    new LocalFetcher(resolved_url, GetBaseURLAndQuery(resolved_url, nullptr),
+                     callback);
     return;
   }
 
-  if (!network_service_)
+  if (!network_service_) {
     ConnectToService(GURL("mojo:network_service"), &network_service_);
-
-  const NativeApplicationCleanup cleanup =
-      base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kDontDeleteOnDownload)
-          ? NativeApplicationCleanup::DONT_DELETE
-          : NativeApplicationCleanup::DELETE;
+    ConnectToService(GURL("mojo:url_response_disk_cache"),
+                     &url_response_disk_cache_);
+  }
 
   new NetworkFetcher(disable_cache_, resolved_url, network_service_.get(),
-                     base::Bind(callback, cleanup));
+                     url_response_disk_cache_.get(), callback);
 }
 
 bool ApplicationManager::ConnectToRunningApplication(
@@ -274,7 +270,6 @@
     ServiceProviderPtr exposed_services,
     const base::Closure& on_application_end,
     const std::vector<std::string>& parameters,
-    NativeApplicationCleanup cleanup,
     scoped_ptr<Fetcher> fetcher) {
   if (!fetcher) {
     // Network error. Drop |application_request| to tell requestor.
@@ -342,13 +337,12 @@
       blocking_pool_,
       base::Bind(&ApplicationManager::RunNativeApplication,
                  weak_ptr_factory_.GetWeakPtr(), base::Passed(request.Pass()),
-                 options, cleanup, base::Passed(fetcher.Pass())));
+                 options, base::Passed(fetcher.Pass())));
 }
 
 void ApplicationManager::RunNativeApplication(
     InterfaceRequest<Application> application_request,
     const NativeRunnerFactory::Options& options,
-    NativeApplicationCleanup cleanup,
     scoped_ptr<Fetcher> fetcher,
     const base::FilePath& path,
     bool path_exists) {
@@ -367,7 +361,7 @@
                path.AsUTF8Unsafe());
   NativeRunner* runner = native_runner_factory_->Create(options).release();
   native_runners_.push_back(runner);
-  runner->Start(path, cleanup, application_request.Pass(),
+  runner->Start(path, application_request.Pass(),
                 base::Bind(&ApplicationManager::CleanupRunner,
                            weak_ptr_factory_.GetWeakPtr(), runner));
 }
diff --git a/shell/application_manager/application_manager.h b/shell/application_manager/application_manager.h
index ded733f..a638786 100644
--- a/shell/application_manager/application_manager.h
+++ b/shell/application_manager/application_manager.h
@@ -15,6 +15,7 @@
 #include "mojo/public/interfaces/application/application.mojom.h"
 #include "mojo/public/interfaces/application/service_provider.mojom.h"
 #include "mojo/services/network/public/interfaces/network_service.mojom.h"
+#include "mojo/services/url_response_disk_cache/public/interfaces/url_response_disk_cache.mojom.h"
 #include "shell/application_manager/application_loader.h"
 #include "shell/application_manager/identity.h"
 #include "shell/application_manager/native_runner.h"
@@ -188,13 +189,11 @@
       mojo::ServiceProviderPtr exposed_services,
       const base::Closure& on_application_end,
       const std::vector<std::string>& parameters,
-      NativeApplicationCleanup cleanup,
       scoped_ptr<Fetcher> fetcher);
 
   void RunNativeApplication(
       mojo::InterfaceRequest<mojo::Application> application_request,
       const NativeRunnerFactory::Options& options,
-      NativeApplicationCleanup cleanup,
       scoped_ptr<Fetcher> fetcher,
       const base::FilePath& file_path,
       bool path_exists);
@@ -232,6 +231,7 @@
 
   base::SequencedWorkerPool* blocking_pool_;
   mojo::NetworkServicePtr network_service_;
+  mojo::URLResponseDiskCachePtr url_response_disk_cache_;
   MimeTypeToURLMap mime_type_to_url_;
   ScopedVector<NativeRunner> native_runners_;
   bool disable_cache_;
diff --git a/shell/application_manager/local_fetcher.cc b/shell/application_manager/local_fetcher.cc
index ad08913..58d2a2d 100644
--- a/shell/application_manager/local_fetcher.cc
+++ b/shell/application_manager/local_fetcher.cc
@@ -5,6 +5,7 @@
 #include "shell/application_manager/local_fetcher.h"
 
 #include "base/bind.h"
+#include "base/files/file_enumerator.h"
 #include "base/files/file_util.h"
 #include "base/format_macros.h"
 #include "base/message_loop/message_loop.h"
@@ -13,6 +14,7 @@
 #include "base/trace_event/trace_event.h"
 #include "mojo/common/common_type_converters.h"
 #include "mojo/common/data_pipe_utils.h"
+#include <sys/stat.h>
 #include "url/url_util.h"
 
 namespace shell {
@@ -56,11 +58,14 @@
   response->url = mojo::String::From(url_);
   mojo::DataPipe data_pipe;
   response->body = data_pipe.consumer_handle.Pass();
-  int64 file_size;
-  if (base::GetFileSize(path_, &file_size)) {
-    response->headers = mojo::Array<mojo::String>(1);
+  base::stat_wrapper_t stat_result;
+  if (stat64(path_.value().c_str(), &stat_result) == 0) {
+    response->headers = mojo::Array<mojo::String>(2);
     response->headers[0] =
-        base::StringPrintf("Content-Length: %" PRId64, file_size);
+        base::StringPrintf("Content-Length: %" PRId64, stat_result.st_size);
+    response->headers[1] = base::StringPrintf(
+        "ETag: \"%" PRId64 "-%" PRId64 "-%" PRId64 "\"", stat_result.st_dev,
+        stat_result.st_ino, static_cast<uint64_t>(stat_result.st_mtime));
   }
   mojo::common::CopyFromFile(path_, data_pipe.producer_handle.Pass(), skip,
                              task_runner, base::Bind(&IgnoreResult));
diff --git a/shell/application_manager/native_runner.h b/shell/application_manager/native_runner.h
index e18f214..e50bb08 100644
--- a/shell/application_manager/native_runner.h
+++ b/shell/application_manager/native_runner.h
@@ -24,16 +24,15 @@
   virtual ~NativeRunner() {}
 
   // Loads the app in the file at |app_path| and runs it on some other
-  // thread/process. If |cleanup| is |DELETE|, this takes ownership of the file.
+  // thread/process.
   // |app_completed_callback| is posted (to the thread on which |Start()| was
   // called) after |MojoMain()| completes, or on any error (including if it
   // fails to start).
-  // TODO(vtl): |app_path| and |cleanup| should probably be moved to the
-  // factory's Create(). Rationale: The factory may need information from the
-  // file to decide what kind of NativeRunner to make.
+  // TODO(vtl): |app_path| should probably be moved to the factory's Create().
+  // Rationale: The factory may need information from the file to decide what
+  // kind of NativeRunner to make.
   virtual void Start(
       const base::FilePath& app_path,
-      NativeApplicationCleanup cleanup,
       mojo::InterfaceRequest<mojo::Application> application_request,
       const base::Closure& app_completed_callback) = 0;
 };
diff --git a/shell/application_manager/network_fetcher.cc b/shell/application_manager/network_fetcher.cc
index 50b9e11..ca5fdd8 100644
--- a/shell/application_manager/network_fetcher.cc
+++ b/shell/application_manager/network_fetcher.cc
@@ -26,13 +26,16 @@
 
 namespace shell {
 
-NetworkFetcher::NetworkFetcher(bool disable_cache,
-                               const GURL& url,
-                               mojo::NetworkService* network_service,
-                               const FetchCallback& loader_callback)
+NetworkFetcher::NetworkFetcher(
+    bool disable_cache,
+    const GURL& url,
+    mojo::NetworkService* network_service,
+    mojo::URLResponseDiskCache* url_response_disk_cache,
+    const FetchCallback& loader_callback)
     : Fetcher(loader_callback),
       disable_cache_(false),
       url_(url),
+      url_response_disk_cache_(url_response_disk_cache),
       weak_ptr_factory_(this) {
   StartNetworkRequest(url, network_service);
 }
@@ -148,14 +151,17 @@
   return base::Move(old_path, *new_path);
 }
 
-void NetworkFetcher::CopyCompleted(
+void NetworkFetcher::OnFileRetrievedFromCache(
     base::Callback<void(const base::FilePath&, bool)> callback,
-    bool success) {
+    mojo::Array<uint8_t> path_as_array,
+    mojo::Array<uint8_t> cache_dir) {
+  bool success = !path_as_array.is_null();
   if (success) {
+    path_ = base::FilePath(std::string(
+        reinterpret_cast<char*>(&path_as_array.front()), path_as_array.size()));
     if (base::CommandLine::ForCurrentProcess()->HasSwitch(
             switches::kPredictableAppFilenames)) {
       // The copy completed, now move to $TMP/$APP_ID.mojo before the dlopen.
-      success = false;
       base::FilePath new_path;
       if (RenameToAppId(url_, path_, &new_path)) {
         if (base::PathExists(new_path)) {
@@ -176,17 +182,12 @@
 void NetworkFetcher::AsPath(
     base::TaskRunner* task_runner,
     base::Callback<void(const base::FilePath&, bool)> callback) {
-  if (!path_.empty() || !response_) {
-    base::MessageLoop::current()->PostTask(
-        FROM_HERE, base::Bind(callback, path_, base::PathExists(path_)));
-    return;
-  }
+  // This should only called once, when we have a response.
+  DCHECK(response_.get());
 
-  base::CreateTemporaryFile(&path_);
-  mojo::common::CopyToFile(
-      response_->body.Pass(), path_, task_runner,
-      base::Bind(&NetworkFetcher::CopyCompleted, weak_ptr_factory_.GetWeakPtr(),
-                 callback));
+  url_response_disk_cache_->GetFile(
+      response_.Pass(), base::Bind(&NetworkFetcher::OnFileRetrievedFromCache,
+                                   weak_ptr_factory_.GetWeakPtr(), callback));
 }
 
 std::string NetworkFetcher::MimeType() {
diff --git a/shell/application_manager/network_fetcher.h b/shell/application_manager/network_fetcher.h
index f8632ad..c512b70 100644
--- a/shell/application_manager/network_fetcher.h
+++ b/shell/application_manager/network_fetcher.h
@@ -10,6 +10,7 @@
 #include "base/files/file_path.h"
 #include "base/memory/weak_ptr.h"
 #include "mojo/services/network/public/interfaces/url_loader.mojom.h"
+#include "mojo/services/url_response_disk_cache/public/interfaces/url_response_disk_cache.mojom.h"
 #include "url/gurl.h"
 
 namespace mojo {
@@ -24,6 +25,7 @@
   NetworkFetcher(bool disable_cache,
                  const GURL& url,
                  mojo::NetworkService* network_service,
+                 mojo::URLResponseDiskCache* url_response_disk_cache,
                  const FetchCallback& loader_callback);
 
   ~NetworkFetcher() override;
@@ -50,8 +52,10 @@
                             const base::FilePath& old_path,
                             base::FilePath* new_path);
 
-  void CopyCompleted(base::Callback<void(const base::FilePath&, bool)> callback,
-                     bool success);
+  void OnFileRetrievedFromCache(
+      base::Callback<void(const base::FilePath&, bool)> callback,
+      mojo::Array<uint8_t> path_as_array,
+      mojo::Array<uint8_t> cache_dir);
 
   void AsPath(
       base::TaskRunner* task_runner,
@@ -70,6 +74,7 @@
 
   bool disable_cache_;
   const GURL url_;
+  mojo::URLResponseDiskCache* url_response_disk_cache_;
   mojo::URLLoaderPtr url_loader_;
   mojo::URLResponsePtr response_;
   base::FilePath path_;
diff --git a/shell/android/background_application_loader.cc b/shell/background_application_loader.cc
similarity index 97%
rename from shell/android/background_application_loader.cc
rename to shell/background_application_loader.cc
index 849d312..476f60a 100644
--- a/shell/android/background_application_loader.cc
+++ b/shell/background_application_loader.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "shell/android/background_application_loader.h"
+#include "shell/background_application_loader.h"
 
 #include "base/bind.h"
 #include "base/run_loop.h"
diff --git a/shell/android/background_application_loader.h b/shell/background_application_loader.h
similarity index 92%
rename from shell/android/background_application_loader.h
rename to shell/background_application_loader.h
index 5362963..6d9cb6d 100644
--- a/shell/android/background_application_loader.h
+++ b/shell/background_application_loader.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef SHELL_ANDROID_BACKGROUND_APPLICATION_LOADER_H_
-#define SHELL_ANDROID_BACKGROUND_APPLICATION_LOADER_H_
+#ifndef SHELL_BACKGROUND_APPLICATION_LOADER_H_
+#define SHELL_BACKGROUND_APPLICATION_LOADER_H_
 
 #include "base/macros.h"
 #include "base/memory/scoped_ptr.h"
@@ -60,4 +60,4 @@
 
 }  // namespace shell
 
-#endif  // SHELL_ANDROID_BACKGROUND_APPLICATION_LOADER_H_
+#endif  // SHELL_BACKGROUND_APPLICATION_LOADER_H_
diff --git a/shell/android/background_application_loader_unittest.cc b/shell/background_application_loader_unittest.cc
similarity index 90%
rename from shell/android/background_application_loader_unittest.cc
rename to shell/background_application_loader_unittest.cc
index 17291b4..b9af70c 100644
--- a/shell/android/background_application_loader_unittest.cc
+++ b/shell/background_application_loader_unittest.cc
@@ -2,9 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "shell/android/background_application_loader.h"
+#include "shell/background_application_loader.h"
 
 #include "mojo/public/interfaces/application/application.mojom.h"
+#include "shell/context.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace shell {
@@ -31,6 +32,7 @@
 
 // Tests that the loader can start and stop gracefully.
 TEST(BackgroundApplicationLoaderTest, StartStop) {
+  Context::EnsureEmbedderIsInitialized();
   scoped_ptr<ApplicationLoader> real_loader(new DummyLoader());
   BackgroundApplicationLoader loader(real_loader.Pass(), "test",
                                      base::MessageLoop::TYPE_DEFAULT);
@@ -39,6 +41,7 @@
 // Tests that the loader can load a service that is well behaved (quits
 // itself).
 TEST(BackgroundApplicationLoaderTest, Load) {
+  Context::EnsureEmbedderIsInitialized();
   scoped_ptr<ApplicationLoader> real_loader(new DummyLoader());
   BackgroundApplicationLoader loader(real_loader.Pass(), "test",
                                      base::MessageLoop::TYPE_DEFAULT);
diff --git a/shell/child_controller.mojom b/shell/child_controller.mojom
index 7313410..a0bf785 100644
--- a/shell/child_controller.mojom
+++ b/shell/child_controller.mojom
@@ -7,9 +7,8 @@
 import "mojo/public/interfaces/application/application.mojom";
 
 interface ChildController {
-  // Starts the app at the given path (deleting it if |clean_app_path| is true).
+  // Starts the app at the given path.
   StartApp(string app_path,
-           bool clean_app_path,
            mojo.Application& application_request) => (int32 result);
 
   // Exits the child process now (with no cleanup), with the given exit code.
diff --git a/shell/child_main.cc b/shell/child_main.cc
index 6fc14b3..1e3834e 100644
--- a/shell/child_main.cc
+++ b/shell/child_main.cc
@@ -216,7 +216,6 @@
 
   // |ChildController| methods:
   void StartApp(const mojo::String& app_path,
-                bool clean_app_path,
                 mojo::InterfaceRequest<mojo::Application> application_request,
                 const StartAppCallback& on_app_complete) override {
     DVLOG(2) << "ChildControllerImpl::StartApp(" << app_path << ", ...)";
@@ -225,9 +224,6 @@
     on_app_complete_ = on_app_complete;
     unblocker_.Unblock(base::Bind(&ChildControllerImpl::StartAppOnMainThread,
                                   base::FilePath::FromUTF8Unsafe(app_path),
-                                  clean_app_path
-                                      ? NativeApplicationCleanup::DELETE
-                                      : NativeApplicationCleanup::DONT_DELETE,
                                   base::Passed(&application_request)));
   }
 
@@ -255,7 +251,6 @@
 
   static void StartAppOnMainThread(
       const base::FilePath& app_path,
-      NativeApplicationCleanup cleanup,
       mojo::InterfaceRequest<mojo::Application> application_request) {
     // TODO(vtl): This is copied from in_process_native_runner.cc.
     DVLOG(2) << "Loading/running Mojo app from " << app_path.value()
@@ -263,7 +258,7 @@
 
     // We intentionally don't unload the native library as its lifetime is the
     // same as that of the process.
-    base::NativeLibrary app_library = LoadNativeApplication(app_path, cleanup);
+    base::NativeLibrary app_library = LoadNativeApplication(app_path);
     RunNativeApplication(app_library, application_request.Pass());
   }
 
diff --git a/shell/child_process_host.cc b/shell/child_process_host.cc
index d0ab5dd..8660162 100644
--- a/shell/child_process_host.cc
+++ b/shell/child_process_host.cc
@@ -61,14 +61,13 @@
 
 void ChildProcessHost::StartApp(
     const mojo::String& app_path,
-    bool clean_app_path,
     mojo::InterfaceRequest<mojo::Application> application_request,
     const ChildController::StartAppCallback& on_app_complete) {
   DCHECK(controller_);
 
   on_app_complete_ = on_app_complete;
   controller_->StartApp(
-      app_path, clean_app_path, application_request.Pass(),
+      app_path, application_request.Pass(),
       base::Bind(&ChildProcessHost::AppCompleted, base::Unretained(this)));
 }
 
diff --git a/shell/child_process_host.h b/shell/child_process_host.h
index 86a6afa..c0cef79 100644
--- a/shell/child_process_host.h
+++ b/shell/child_process_host.h
@@ -52,7 +52,6 @@
   // |on_app_complete| will *always* get called, even on connection error (or
   // even if the child process failed to start at all).
   void StartApp(const mojo::String& app_path,
-                bool clean_app_path,
                 mojo::InterfaceRequest<mojo::Application> application_request,
                 const ChildController::StartAppCallback& on_app_complete);
   void ExitNow(int32_t exit_code);
diff --git a/shell/child_process_host_unittest.cc b/shell/child_process_host_unittest.cc
index 31ace06..bb1817b 100644
--- a/shell/child_process_host_unittest.cc
+++ b/shell/child_process_host_unittest.cc
@@ -83,7 +83,7 @@
   application_request.Bind(mp.handle0.Pass());
   // This won't actually be called, but the callback should be run.
   MojoResult result = MOJO_RESULT_INTERNAL;
-  child_process_host.StartApp("/does_not_exist/cbvgyuio", false,
+  child_process_host.StartApp("/does_not_exist/cbvgyuio",
                               application_request.Pass(), [&result](int32_t r) {
                                 result = r;
                                 base::MessageLoop::current()->QuitWhenIdle();
diff --git a/shell/context.cc b/shell/context.cc
index 8aa8a7a..331efa1 100644
--- a/shell/context.cc
+++ b/shell/context.cc
@@ -31,12 +31,14 @@
 #include "services/tracing/tracing.mojom.h"
 #include "shell/application_manager/application_loader.h"
 #include "shell/application_manager/application_manager.h"
+#include "shell/background_application_loader.h"
 #include "shell/command_line_util.h"
 #include "shell/filename_util.h"
 #include "shell/in_process_native_runner.h"
 #include "shell/out_of_process_native_runner.h"
 #include "shell/switches.h"
 #include "shell/tracer.h"
+#include "shell/url_response_disk_cache_loader.h"
 #include "url/gurl.h"
 
 using mojo::ServiceProvider;
@@ -258,6 +260,12 @@
 
   mojo_shell_child_path_ = shell_child_path;
 
+  application_manager()->SetLoaderForURL(
+      make_scoped_ptr(new BackgroundApplicationLoader(
+          make_scoped_ptr(new URLResponseDiskCacheLoader()),
+          "url_response_disk_cache", base::MessageLoop::TYPE_DEFAULT)),
+      GURL("mojo:url_response_disk_cache"));
+
   EnsureEmbedderIsInitialized();
   task_runners_.reset(
       new TaskRunners(base::MessageLoop::current()->message_loop_proxy()));
diff --git a/shell/desktop/main.cc b/shell/desktop/main.cc
index 6139245..ca79c4c 100644
--- a/shell/desktop/main.cc
+++ b/shell/desktop/main.cc
@@ -35,7 +35,8 @@
       << " [--" << switches::kDisableCache << "]"
       << " [--" << switches::kEnableMultiprocess << "]"
       << " [--" << switches::kOrigin << "=<url-lib-path>]"
-      << " [--" << switches::kTraceStartup << "]"
+      << " [--" << switches::kTraceStartup << "[=\"list,of,categories\"]]"
+      << " [--" << switches::kTraceStartupDuration << "=<seconds>]"
       << " [--" << switches::kURLMappings << "=from1=to1,from2=to2]"
       << " [--" << switches::kPredictableAppFilenames << "]"
       << " [--" << switches::kWaitForDebugger << "]"
@@ -77,8 +78,12 @@
   }
 
   bool trace_startup = command_line.HasSwitch(switches::kTraceStartup);
-  if (trace_startup)
-    tracer.Start(command_line.GetSwitchValueASCII(switches::kTraceStartup));
+  if (trace_startup) {
+    tracer.Start(
+        command_line.GetSwitchValueASCII(switches::kTraceStartup),
+        command_line.GetSwitchValueASCII(switches::kTraceStartupDuration),
+        "mojo_shell.trace");
+  }
 
   if (command_line.HasSwitch(switches::kCPUProfile)) {
 #if !defined(NDEBUG) || !defined(ENABLE_PROFILING)
@@ -94,13 +99,7 @@
   shell::Context shell_context(&tracer);
   {
     base::MessageLoop message_loop;
-    if (trace_startup) {
-      message_loop.PostDelayedTask(
-          FROM_HERE, base::Bind(&shell::Tracer::StopAndFlushToFile,
-                                base::Unretained(&tracer), "mojo_shell.trace"),
-          base::TimeDelta::FromSeconds(5));
-    }
-
+    tracer.DidCreateMessageLoop();
     if (!shell_context.Init()) {
       Usage();
       return 1;
@@ -123,6 +122,6 @@
   if (command_line.HasSwitch(switches::kCPUProfile))
     base::debug::StopProfiling();
   if (trace_startup)
-    tracer.StopAndFlushToFile("mojo_shell.trace");
+    tracer.StopAndFlushToFile();
   return 0;
 }
diff --git a/shell/in_process_native_runner.cc b/shell/in_process_native_runner.cc
index f12708d..f5ae3ee 100644
--- a/shell/in_process_native_runner.cc
+++ b/shell/in_process_native_runner.cc
@@ -14,7 +14,7 @@
 namespace shell {
 
 InProcessNativeRunner::InProcessNativeRunner(Context* context)
-    : cleanup_(NativeApplicationCleanup::DONT_DELETE), app_library_(nullptr) {
+    : app_library_(nullptr) {
 }
 
 InProcessNativeRunner::~InProcessNativeRunner() {
@@ -30,11 +30,9 @@
 
 void InProcessNativeRunner::Start(
     const base::FilePath& app_path,
-    NativeApplicationCleanup cleanup,
     mojo::InterfaceRequest<mojo::Application> application_request,
     const base::Closure& app_completed_callback) {
   app_path_ = app_path;
-  cleanup_ = cleanup;
 
   DCHECK(!application_request_.is_pending());
   application_request_ = application_request.Pass();
@@ -56,7 +54,7 @@
            << " thread id=" << base::PlatformThread::CurrentId();
 
   // TODO(vtl): ScopedNativeLibrary doesn't have a .get() method!
-  base::NativeLibrary app_library = LoadNativeApplication(app_path_, cleanup_);
+  base::NativeLibrary app_library = LoadNativeApplication(app_path_);
   app_library_.Reset(app_library);
   RunNativeApplication(app_library, application_request_.Pass());
   app_completed_callback_runner_.Run();
diff --git a/shell/in_process_native_runner.h b/shell/in_process_native_runner.h
index 0d5e870..41065c1 100644
--- a/shell/in_process_native_runner.h
+++ b/shell/in_process_native_runner.h
@@ -28,7 +28,6 @@
 
   // |NativeRunner| method:
   void Start(const base::FilePath& app_path,
-             NativeApplicationCleanup cleanup,
              mojo::InterfaceRequest<mojo::Application> application_request,
              const base::Closure& app_completed_callback) override;
 
@@ -37,7 +36,6 @@
   void Run() override;
 
   base::FilePath app_path_;
-  NativeApplicationCleanup cleanup_;
   mojo::InterfaceRequest<mojo::Application> application_request_;
   base::Callback<bool(void)> app_completed_callback_runner_;
 
diff --git a/shell/native_application_support.cc b/shell/native_application_support.cc
index 378a014..f82b4ef 100644
--- a/shell/native_application_support.cc
+++ b/shell/native_application_support.cc
@@ -42,14 +42,11 @@
 
 }  // namespace
 
-base::NativeLibrary LoadNativeApplication(const base::FilePath& app_path,
-                                          NativeApplicationCleanup cleanup) {
+base::NativeLibrary LoadNativeApplication(const base::FilePath& app_path) {
   DVLOG(2) << "Loading Mojo app in process from library: " << app_path.value();
 
   base::NativeLibraryLoadError error;
   base::NativeLibrary app_library = base::LoadNativeLibrary(app_path, &error);
-  if (cleanup == NativeApplicationCleanup::DELETE)
-    DeleteFile(app_path, false);
   LOG_IF(ERROR, !app_library)
       << "Failed to load app library (error: " << error.ToString() << ")";
   return app_library;
diff --git a/shell/native_application_support.h b/shell/native_application_support.h
index 2c3b0db..5e24d4b 100644
--- a/shell/native_application_support.h
+++ b/shell/native_application_support.h
@@ -18,19 +18,15 @@
 
 namespace shell {
 
-enum class NativeApplicationCleanup { DELETE, DONT_DELETE };
-
 // Loads the native Mojo application from the DSO specified by |app_path|.
 // Returns the |base::NativeLibrary| for the application on success (or null on
-// failure). If |cleanup| is |DELETE|, it will delete |app_path| (regardless of
-// sucess or failure).
+// failure).
 //
 // Note: The caller may choose to eventually unload the returned DSO. If so,
 // this should be done only after the thread on which |LoadNativeApplication()|
 // and |RunNativeApplication()| were called has terminated, so that any
 // thread-local destructors have been executed.
-base::NativeLibrary LoadNativeApplication(const base::FilePath& app_path,
-                                          NativeApplicationCleanup cleanup);
+base::NativeLibrary LoadNativeApplication(const base::FilePath& app_path);
 
 // Runs the native Mojo application from the DSO that was loaded using
 // |LoadNativeApplication()|; this tolerates |app_library| being null. This
diff --git a/shell/native_runner_unittest.cc b/shell/native_runner_unittest.cc
index 2b732c0..fe792c9 100644
--- a/shell/native_runner_unittest.cc
+++ b/shell/native_runner_unittest.cc
@@ -32,7 +32,6 @@
     base::MessageLoop::current()->Quit();
   }
   void Start(const base::FilePath& app_path,
-             NativeApplicationCleanup cleanup,
              mojo::InterfaceRequest<mojo::Application> application_request,
              const base::Closure& app_completed_callback) override {
     state_->runner_was_started = true;
diff --git a/shell/out_of_process_native_runner.cc b/shell/out_of_process_native_runner.cc
index da3abdb..39cf1f7 100644
--- a/shell/out_of_process_native_runner.cc
+++ b/shell/out_of_process_native_runner.cc
@@ -29,7 +29,6 @@
 
 void OutOfProcessNativeRunner::Start(
     const base::FilePath& app_path,
-    NativeApplicationCleanup cleanup,
     mojo::InterfaceRequest<mojo::Application> application_request,
     const base::Closure& app_completed_callback) {
   app_path_ = app_path;
@@ -42,8 +41,7 @@
 
   // TODO(vtl): |app_path.AsUTF8Unsafe()| is unsafe.
   child_process_host_->StartApp(
-      app_path.AsUTF8Unsafe(), cleanup == NativeApplicationCleanup::DELETE,
-      application_request.Pass(),
+      app_path.AsUTF8Unsafe(), application_request.Pass(),
       base::Bind(&OutOfProcessNativeRunner::AppCompleted,
                  base::Unretained(this)));
 }
diff --git a/shell/out_of_process_native_runner.h b/shell/out_of_process_native_runner.h
index ff86668..23e0372 100644
--- a/shell/out_of_process_native_runner.h
+++ b/shell/out_of_process_native_runner.h
@@ -26,7 +26,6 @@
 
   // |NativeRunner| method:
   void Start(const base::FilePath& app_path,
-             NativeApplicationCleanup cleanup,
              mojo::InterfaceRequest<mojo::Application> application_request,
              const base::Closure& app_completed_callback) override;
 
diff --git a/shell/switches.cc b/shell/switches.cc
index 518d668..5606fca 100644
--- a/shell/switches.cc
+++ b/shell/switches.cc
@@ -70,6 +70,10 @@
 // seconds or when the shell exits.
 const char kTraceStartup[] = "trace-startup";
 
+// Sets the time in seconds until startup tracing ends. If omitted a default of
+// 5 seconds is used.
+const char kTraceStartupDuration[] = "trace-startup-duration";
+
 // Specifies a set of mappings to apply when resolving urls. The value is a set
 // of ',' separated mappings, where each mapping consists of a pair of urls
 // giving the to/from url to map. For example, 'a=b,c=d' contains two mappings,
@@ -91,6 +95,7 @@
                               kOrigin,
                               kPredictableAppFilenames,
                               kTraceStartup,
+                              kTraceStartupDuration,
                               kURLMappings};
 
 const std::set<std::string> GetAllSwitches() {
diff --git a/shell/switches.h b/shell/switches.h
index c6bb68e..a1ff4c4 100644
--- a/shell/switches.h
+++ b/shell/switches.h
@@ -26,6 +26,7 @@
 extern const char kOrigin[];
 extern const char kPredictableAppFilenames[];
 extern const char kTraceStartup[];
+extern const char kTraceStartupDuration[];
 extern const char kURLMappings[];
 
 extern const std::set<std::string> GetAllSwitches();
diff --git a/shell/tracer.cc b/shell/tracer.cc
index 09981e1..329360a 100644
--- a/shell/tracer.cc
+++ b/shell/tracer.cc
@@ -8,6 +8,7 @@
 #include <string.h>
 
 #include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/thread.h"
 #include "base/trace_event/trace_event.h"
@@ -21,26 +22,46 @@
 Tracer::~Tracer() {
 }
 
-void Tracer::Start(const std::string& categories) {
+void Tracer::Start(const std::string& categories,
+                   const std::string& duration_seconds_str,
+                   const std::string& filename) {
+  trace_duration_secs_ = 5;
+  if (!duration_seconds_str.empty()) {
+    CHECK(base::StringToInt(duration_seconds_str, &trace_duration_secs_))
+        << "Could not parse --trace-startup-duration value "
+        << duration_seconds_str;
+  }
   tracing_ = true;
+  trace_filename_ = filename;
+  categories_ = categories;
   base::trace_event::CategoryFilter category_filter(categories);
   base::trace_event::TraceLog::GetInstance()->SetEnabled(
       category_filter, base::trace_event::TraceLog::RECORDING_MODE,
       base::trace_event::TraceOptions(base::trace_event::RECORD_UNTIL_FULL));
 }
 
+void Tracer::DidCreateMessageLoop() {
+  if (!tracing_)
+    return;
+
+  base::MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&shell::Tracer::StopAndFlushToFile, base::Unretained(this)),
+      base::TimeDelta::FromSeconds(trace_duration_secs_));
+}
+
 void Tracer::StartCollectingFromTracingService(
     tracing::TraceCoordinatorPtr coordinator) {
   coordinator_ = coordinator.Pass();
   mojo::DataPipe data_pipe;
-  coordinator_->Start(data_pipe.producer_handle.Pass(), "*");
+  coordinator_->Start(data_pipe.producer_handle.Pass(), categories_);
   drainer_.reset(new mojo::common::DataPipeDrainer(
       this, data_pipe.consumer_handle.Pass()));
 }
 
-void Tracer::StopAndFlushToFile(const std::string& filename) {
+void Tracer::StopAndFlushToFile() {
   if (tracing_)
-    StopTracingAndFlushToDisk(filename);
+    StopTracingAndFlushToDisk();
 }
 
 void Tracer::ConnectToController(
@@ -49,11 +70,10 @@
   impl->set_tracing_already_started(tracing_);
 }
 
-void Tracer::StopTracingAndFlushToDisk(const std::string& filename) {
+void Tracer::StopTracingAndFlushToDisk() {
   tracing_ = false;
-  trace_file_ = fopen(filename.c_str(), "w+");
+  trace_file_ = fopen(trace_filename_.c_str(), "w+");
   PCHECK(trace_file_);
-  trace_filename_ = filename;
   static const char kStart[] = "{\"traceEvents\":[";
   PCHECK(fwrite(kStart, 1, strlen(kStart), trace_file_) == strlen(kStart));
 
@@ -78,7 +98,7 @@
       flush_thread.message_loop()->PostTask(
           FROM_HERE,
           base::Bind(&Tracer::EndTraceAndFlush, base::Unretained(this),
-                     filename,
+                     trace_filename_,
                      base::Bind(&base::WaitableEvent::Signal,
                                 base::Unretained(&flush_complete_event))));
       base::trace_event::TraceLog::GetInstance()
diff --git a/shell/tracer.h b/shell/tracer.h
index bcd4968..a32cbb8 100644
--- a/shell/tracer.h
+++ b/shell/tracer.h
@@ -29,24 +29,32 @@
   Tracer();
   ~Tracer() override;
 
-  // Starts tracing the current process with the given set of categories.
-  void Start(const std::string& categories);
+  // Starts tracing the current process with the given set of categories. The
+  // tracing results will be saved into the specified filename when
+  // StopAndFlushToFile() is called.
+  void Start(const std::string& categories,
+             const std::string& duration_seconds_str,
+             const std::string& filename);
+
+  // Notifies the tracer that a message loop has been created. If startup
+  // tracing is active the tracer can use this to schedule when to stop tracing.
+  void DidCreateMessageLoop();
 
   // Starts collecting data from the tracing service with the given set of
   // categories.
   void StartCollectingFromTracingService(
       tracing::TraceCoordinatorPtr coordinator);
 
-  // Stops tracing and flushes all collected trace data to the given filename.
-  // Blocks until the file write is complete. May be called after the message
-  // loop is shut down.
-  void StopAndFlushToFile(const std::string& filename);
+  // Stops tracing and flushes all collected trace data to the file specified in
+  // Start(). Blocks until the file write is complete. May be called after the
+  // message loop is shut down.
+  void StopAndFlushToFile();
 
   void ConnectToController(
       mojo::InterfaceRequest<tracing::TraceController> request);
 
  private:
-  void StopTracingAndFlushToDisk(const std::string& filename);
+  void StopTracingAndFlushToDisk();
 
   // Called from the flush thread. When all data is collected this runs
   // |done_callback| on the flush thread.
@@ -75,6 +83,10 @@
 
   // Whether we're currently tracing.
   bool tracing_;
+  // How long to trace after message loop creation.
+  int trace_duration_secs_;
+  // Categories to trace.
+  std::string categories_;
 
   // Whether we've written the first chunk.
   bool first_chunk_written_;
diff --git a/shell/url_response_disk_cache_loader.cc b/shell/url_response_disk_cache_loader.cc
new file mode 100644
index 0000000..3371cb5
--- /dev/null
+++ b/shell/url_response_disk_cache_loader.cc
@@ -0,0 +1,23 @@
+// 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 "shell/url_response_disk_cache_loader.h"
+
+namespace shell {
+
+URLResponseDiskCacheLoader::URLResponseDiskCacheLoader() {
+}
+
+URLResponseDiskCacheLoader::~URLResponseDiskCacheLoader() {
+}
+
+void URLResponseDiskCacheLoader::Load(
+    const GURL& url,
+    mojo::InterfaceRequest<mojo::Application> application_request) {
+  DCHECK(application_request.is_pending());
+  application_.reset(new mojo::ApplicationImpl(&url_response_disk_cache_,
+                                               application_request.Pass()));
+}
+
+}  // namespace shell
diff --git a/shell/url_response_disk_cache_loader.h b/shell/url_response_disk_cache_loader.h
new file mode 100644
index 0000000..4f75433
--- /dev/null
+++ b/shell/url_response_disk_cache_loader.h
@@ -0,0 +1,35 @@
+// 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 SHELL_URL_RESPONSE_DISK_CACHE_LOADER_H_
+#define SHELL_URL_RESPONSE_DISK_CACHE_LOADER_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "services/url_response_disk_cache/url_response_disk_cache_app.h"
+#include "shell/application_manager/application_loader.h"
+
+namespace shell {
+
+class URLResponseDiskCacheLoader : public ApplicationLoader {
+ public:
+  URLResponseDiskCacheLoader();
+  ~URLResponseDiskCacheLoader() override;
+
+ private:
+  // ApplicationLoader overrides:
+  void Load(
+      const GURL& url,
+      mojo::InterfaceRequest<mojo::Application> application_request) override;
+
+  mojo::URLResponseDiskCacheApp url_response_disk_cache_;
+  scoped_ptr<mojo::ApplicationImpl> application_;
+
+  DISALLOW_COPY_AND_ASSIGN(URLResponseDiskCacheLoader);
+};
+
+}  // namespace shell
+
+#endif  // SHELL_URL_RESPONSE_DISK_CACHE_LOADER_H_
diff --git a/sky/engine/bindings/BUILD.gn b/sky/engine/bindings/BUILD.gn
index b7dbafe..fd6a30c 100644
--- a/sky/engine/bindings/BUILD.gn
+++ b/sky/engine/bindings/BUILD.gn
@@ -30,6 +30,9 @@
     "scheduled_action.cc",
     "scheduled_action.h",
   ]
+
+  defines = [ "DART_IO_SECURE_SOCKET_DISABLED" ]
+
   deps = [
     "//base",
     "//dart/runtime/bin:libdart_withcore",
@@ -38,12 +41,14 @@
     "//sky/engine/platform:platform",
     "//sky/engine/tonic",
     "//sky/engine/wtf",
+    "//dart/runtime/bin:embedded_dart_io",
     ":generated_bindings",
     ":snapshot_cc",
   ]
   include_dirs = [
     "..",
     "$root_build_dir",
+    rebase_path("//dart/runtime"),
   ]
 }
 
diff --git a/sky/engine/bindings/builtin.cc b/sky/engine/bindings/builtin.cc
index 81fd9d3..3fa5c39 100644
--- a/sky/engine/bindings/builtin.cc
+++ b/sky/engine/bindings/builtin.cc
@@ -6,6 +6,7 @@
 #include "sky/engine/bindings/builtin.h"
 
 #include "base/logging.h"
+#include "bin/io_natives.h"
 #include "dart/runtime/include/dart_api.h"
 #include "gen/sky/bindings/DartGlobal.h"
 #include "sky/engine/bindings/builtin_natives.h"
@@ -31,6 +32,7 @@
      BuiltinNatives::NativeLookup},
     {"dart:sky", true, skySnapshotSymbolizer, skySnapshotResolver},
     {"dart:mojo.internal", true, MojoNativeSymbol, MojoNativeLookup},
+    {"dart:io", true, dart::bin::IONativeSymbol, dart::bin::IONativeLookup },
 };
 
 }  // namespace
diff --git a/sky/engine/bindings/builtin.h b/sky/engine/bindings/builtin.h
index 8c3bb05..93f67a2 100644
--- a/sky/engine/bindings/builtin.h
+++ b/sky/engine/bindings/builtin.h
@@ -18,6 +18,7 @@
     kBuiltinLibrary,
     kSkyLibrary,
     kMojoInternalLibrary,
+    kIOLibrary,
     kInvalidLibrary,
   };
 
diff --git a/sky/engine/bindings/builtin_natives.cc b/sky/engine/bindings/builtin_natives.cc
index 55584a3..90bec2e 100644
--- a/sky/engine/bindings/builtin_natives.cc
+++ b/sky/engine/bindings/builtin_natives.cc
@@ -87,7 +87,8 @@
   return closure;
 }
 
-static void InitDartInternal(Dart_Handle builtin_library) {
+static void InitDartInternal(Dart_Handle builtin_library,
+                             BuiltinNatives::IsolateType isolate_type) {
   Dart_Handle print = GetClosure(builtin_library, "_getPrintClosure");
   Dart_Handle timer = GetClosure(builtin_library, "_getCreateTimerClosure");
 
@@ -96,35 +97,55 @@
   DART_CHECK_VALID(Dart_SetField(
       internal_library, ToDart("_printClosure"), print));
 
-  Dart_Handle vm_hooks_name = ToDart("VMLibraryHooks");
-  Dart_Handle vm_hooks = Dart_GetClass(internal_library, vm_hooks_name);
-  DART_CHECK_VALID(vm_hooks);
-  Dart_Handle timer_name = ToDart("timerFactory");
-  DART_CHECK_VALID(Dart_SetField(vm_hooks, timer_name, timer));
+  if (isolate_type == BuiltinNatives::MainIsolate) {
+    Dart_Handle vm_hooks_name = ToDart("VMLibraryHooks");
+    Dart_Handle vm_hooks = Dart_GetClass(internal_library, vm_hooks_name);
+    DART_CHECK_VALID(vm_hooks);
+    Dart_Handle timer_name = ToDart("timerFactory");
+    DART_CHECK_VALID(Dart_SetField(vm_hooks, timer_name, timer));
+  } else {
+    CHECK(isolate_type == BuiltinNatives::DartIOIsolate);
+    Dart_Handle io_lib = DartBuiltin::LookupLibrary("dart:io");
+    Dart_Handle setup_hooks = Dart_NewStringFromCString("_setupHooks");
+    DART_CHECK_VALID(Dart_Invoke(io_lib, setup_hooks, 0, NULL));
+    Dart_Handle isolate_lib = DartBuiltin::LookupLibrary("dart:isolate");
+    DART_CHECK_VALID(Dart_Invoke(isolate_lib, setup_hooks, 0, NULL));
+  }
 }
 
-static void InitDartCore(Dart_Handle builtin) {
+static void InitDartCore(Dart_Handle builtin,
+                         BuiltinNatives::IsolateType isolate_type) {
   Dart_Handle get_base_url = GetClosure(builtin, "_getGetBaseURLClosure");
   Dart_Handle core_library = DartBuiltin::LookupLibrary("dart:core");
   DART_CHECK_VALID(Dart_SetField(core_library,
       ToDart("_uriBaseClosure"), get_base_url));
 }
 
-static void InitDartAsync(Dart_Handle builtin_library) {
-  Dart_Handle schedule_microtask =
-      GetClosure(builtin_library, "_getScheduleMicrotaskClosure");
+static void InitDartAsync(Dart_Handle builtin_library,
+                          BuiltinNatives::IsolateType isolate_type) {
+  Dart_Handle schedule_microtask;
+  if (isolate_type == BuiltinNatives::MainIsolate) {
+    schedule_microtask =
+        GetClosure(builtin_library, "_getScheduleMicrotaskClosure");
+  } else {
+    CHECK(isolate_type == BuiltinNatives::DartIOIsolate);
+    Dart_Handle isolate_lib = DartBuiltin::LookupLibrary("dart:isolate");
+    Dart_Handle method_name =
+        Dart_NewStringFromCString("_getIsolateScheduleImmediateClosure");
+    schedule_microtask = Dart_Invoke(isolate_lib, method_name, 0, NULL);
+  }
   Dart_Handle async_library = DartBuiltin::LookupLibrary("dart:async");
   Dart_Handle set_schedule_microtask = ToDart("_setScheduleImmediateClosure");
   DART_CHECK_VALID(Dart_Invoke(async_library, set_schedule_microtask, 1,
                                &schedule_microtask));
 }
 
-void BuiltinNatives::Init() {
+void BuiltinNatives::Init(IsolateType isolate_type) {
   Dart_Handle builtin = Builtin::LoadAndCheckLibrary(Builtin::kBuiltinLibrary);
   DART_CHECK_VALID(builtin);
-  InitDartInternal(builtin);
-  InitDartCore(builtin);
-  InitDartAsync(builtin);
+  InitDartInternal(builtin, isolate_type);
+  InitDartCore(builtin, isolate_type);
+  InitDartAsync(builtin, isolate_type);
 }
 
 // Implementation of native functions which are used for some
@@ -163,6 +184,7 @@
   if (LogIfError(closure) || !Dart_IsClosure(closure))
     return;
   DartState* dart_state = DartState::Current();
+  CHECK(dart_state);
   Microtask::enqueueMicrotask(base::Bind(&ExecuteMicrotask,
     dart_state->GetWeakPtr(), DartValue::Create(dart_state, closure)));
 }
@@ -182,6 +204,7 @@
   DART_CHECK_VALID(Dart_GetNativeBooleanArgument(args, 2, &repeating));
 
   DOMDartState* state = DOMDartState::Current();
+  CHECK(state);
   int timer_id = DOMTimer::install(state->document(),
                                    ScheduledAction::Create(state, closure),
                                    milliseconds,
@@ -194,6 +217,7 @@
   DART_CHECK_VALID(Dart_GetNativeIntegerArgument(args, 0, &timer_id));
 
   DOMDartState* state = DOMDartState::Current();
+  CHECK(state);
   DOMTimer::removeByID(state->document(), timer_id);
 }
 
diff --git a/sky/engine/bindings/builtin_natives.h b/sky/engine/bindings/builtin_natives.h
index a08fc19..3f525bc 100644
--- a/sky/engine/bindings/builtin_natives.h
+++ b/sky/engine/bindings/builtin_natives.h
@@ -12,12 +12,17 @@
 
 class BuiltinNatives {
  public:
+  enum IsolateType {
+    MainIsolate,
+    DartIOIsolate,
+  };
+
   static Dart_NativeFunction NativeLookup(Dart_Handle name,
                                           int argument_count,
                                           bool* auto_setup_scope);
   static const uint8_t* NativeSymbol(Dart_NativeFunction native_function);
 
-  static void Init();
+  static void Init(IsolateType isolate_type);
 
  private:
   DISALLOW_IMPLICIT_CONSTRUCTORS(BuiltinNatives);
diff --git a/sky/engine/bindings/snapshot.dart b/sky/engine/bindings/snapshot.dart
index 51fd6f4..d620521 100644
--- a/sky/engine/bindings/snapshot.dart
+++ b/sky/engine/bindings/snapshot.dart
@@ -6,6 +6,7 @@
 import 'dart:collection';
 import 'dart:convert';
 import 'dart:core';
+import 'dart:io';
 import 'dart:isolate';
 import 'dart:math';
 import 'dart:mojo.internal';
diff --git a/sky/engine/core/BUILD.gn b/sky/engine/core/BUILD.gn
index d01e064..661835c 100644
--- a/sky/engine/core/BUILD.gn
+++ b/sky/engine/core/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//sky/engine/build/scripts/scripts.gni")
 import("//sky/engine/core/core.gni")
+import("//mojo/dart/embedder/embedder.gni")
 
 visibility = [ "//sky/engine/*" ]
 
@@ -52,20 +53,40 @@
   ]
 }
 
+dart_embedder_resources("generate_sky_embedder_service_isolate_resources_cc") {
+  deps = [
+    "//mojo/dart/embedder:deploy_observatory",
+  ]
+  inputs = [
+    "//sky/engine/core/script/dart_service_isolate/loader.dart",
+    "//sky/engine/core/script/dart_service_isolate/main.dart",
+    "//sky/engine/core/script/dart_service_isolate/resources.dart",
+    "//sky/engine/core/script/dart_service_isolate/server.dart",
+  ]
+  root_prefix = "//sky/engine/core/script/"
+  input_directory = "$root_out_dir/observatory/deployed/web/"
+  output = "$target_gen_dir/sky_embedder_service_isolate_resources.cc"
+  table_name = "sky_embedder_service_isolate"
+}
+
 static_library("core") {
   output_name = "sky_core"
 
   deps = [
     ":core_generated",
+    ":generate_sky_embedder_service_isolate_resources_cc",
     ":libraries",
     ":prerequisites",
     "//sky/engine/platform",
     "//sky/engine/bindings",
+    "//dart/runtime/bin:embedded_dart_io",
     "//dart/runtime/bin:libdart_withcore",
   ]
 
   sources = sky_core_files
 
+  sources += [ "$target_gen_dir/sky_embedder_service_isolate_resources.cc" ]
+
   include_dirs = [
     # Needed for dart_mirrors_api.h in dart_controller.cc
     rebase_path("//dart/runtime"),
diff --git a/sky/engine/core/core.gni b/sky/engine/core/core.gni
index 5ecbe0a..2a78c74 100644
--- a/sky/engine/core/core.gni
+++ b/sky/engine/core/core.gni
@@ -1009,12 +1009,17 @@
   "rendering/VerticalPositionCache.h",
   "script/dart_controller.cc",
   "script/dart_controller.h",
+  "script/dart_debugger.cc",
+  "script/dart_debugger.h",
   "script/dart_dependency_catcher.cc",
   "script/dart_dependency_catcher.h",
   "script/dart_loader.cc",
   "script/dart_loader.h",
+  "script/dart_service_isolate.cc",
+  "script/dart_service_isolate.h",
   "script/dom_dart_state.cc",
   "script/dom_dart_state.h",
+  "script/monitor.h",
 ]
 
 core_idl_files = get_path_info([
diff --git a/sky/engine/core/script/dart_controller.cc b/sky/engine/core/script/dart_controller.cc
index dc7ce4c..e03cb34 100644
--- a/sky/engine/core/script/dart_controller.cc
+++ b/sky/engine/core/script/dart_controller.cc
@@ -9,6 +9,7 @@
 #include "base/logging.h"
 #include "base/single_thread_task_runner.h"
 #include "base/trace_event/trace_event.h"
+#include "dart/runtime/bin/embedded_dart_io.h"
 #include "dart/runtime/include/dart_mirrors_api.h"
 #include "sky/engine/bindings/builtin.h"
 #include "sky/engine/bindings/builtin_natives.h"
@@ -21,8 +22,10 @@
 #include "sky/engine/core/html/imports/HTMLImport.h"
 #include "sky/engine/core/html/imports/HTMLImportChild.h"
 #include "sky/engine/core/loader/FrameLoaderClient.h"
+#include "sky/engine/core/script/dart_debugger.h"
 #include "sky/engine/core/script/dart_dependency_catcher.h"
 #include "sky/engine/core/script/dart_loader.h"
+#include "sky/engine/core/script/dart_service_isolate.h"
 #include "sky/engine/core/script/dom_dart_state.h"
 #include "sky/engine/public/platform/Platform.h"
 #include "sky/engine/tonic/dart_api_scope.h"
@@ -176,9 +179,36 @@
       String(url_name) == DART_VM_SERVICE_ISOLATE_NAME;
 }
 
+static void EnsureHandleWatcherStarted() {
+  static bool handle_watcher_started = false;
+  if (handle_watcher_started)
+    return;
+
+  // TODO(dart): Call Dart_Cleanup (ensure the handle watcher isolate is closed)
+  // during shutdown.
+  Dart_Handle mojo_core_lib =
+      Builtin::LoadAndCheckLibrary(Builtin::kMojoInternalLibrary);
+  CHECK(!LogIfError((mojo_core_lib)));
+  Dart_Handle handle_watcher_type = Dart_GetType(
+      mojo_core_lib,
+      Dart_NewStringFromCString("MojoHandleWatcher"),
+      0,
+      nullptr);
+  CHECK(!LogIfError(handle_watcher_type));
+  CHECK(!LogIfError(Dart_Invoke(
+      handle_watcher_type,
+      Dart_NewStringFromCString("_start"),
+      0,
+      nullptr)));
+
+  // RunLoop until the handle watcher isolate is spun-up.
+  CHECK(!LogIfError(Dart_RunLoop()));
+  handle_watcher_started = true;
+}
+
 // TODO(rafaelw): Right now this only supports the creation of the handle
-// watcher isolate. Presumably, we'll want application isolates to spawn their
-// own isolates.
+// watcher isolate and the service isolate. Presumably, we'll want application
+// isolates to spawn their own isolates.
 static Dart_Isolate IsolateCreateCallback(const char* script_uri,
                                           const char* main,
                                           const char* package_root,
@@ -186,8 +216,34 @@
                                           char** error) {
 
   if (IsServiceIsolateURL(script_uri)) {
-    return Dart_CreateIsolate(script_uri, "main", kDartIsolateSnapshotBuffer,
-          nullptr, error);
+    CHECK(kDartIsolateSnapshotBuffer);
+    DartState* dart_state = new DartState();
+    Dart_Isolate isolate = Dart_CreateIsolate(script_uri,
+                                              "main",
+                                              kDartIsolateSnapshotBuffer,
+                                              nullptr,
+                                              error);
+    CHECK(isolate) << error;
+    dart_state->set_isolate(isolate);
+    CHECK(Dart_IsServiceIsolate(isolate));
+    CHECK(!LogIfError(Dart_SetLibraryTagHandler(LibraryTagHandler)));
+    {
+      DartApiScope apiScope;
+      Builtin::SetNativeResolver(Builtin::kBuiltinLibrary);
+      Builtin::SetNativeResolver(Builtin::kMojoInternalLibrary);
+      Builtin::SetNativeResolver(Builtin::kIOLibrary);
+      BuiltinNatives::Init(BuiltinNatives::DartIOIsolate);
+      // Start the handle watcher from the service isolate so it isn't available
+      // for debugging or general Observatory interaction.
+      EnsureHandleWatcherStarted();
+      std::string ip = "127.0.0.1";
+      const intptr_t port = 0;  // Automatic port assignment.
+      const bool service_isolate_booted =
+          DartServiceIsolate::Startup(ip, port, LibraryTagHandler, error);
+      CHECK(service_isolate_booted) << error;
+    }
+    Dart_ExitIsolate();
+    return isolate;
   }
 
   // Create & start the handle watcher isolate
@@ -204,6 +260,7 @@
     DartApiScope apiScope;
     Builtin::SetNativeResolver(Builtin::kBuiltinLibrary);
     Builtin::SetNativeResolver(Builtin::kMojoInternalLibrary);
+    Builtin::SetNativeResolver(Builtin::kIOLibrary);
 
     // Ensure the isolate has a root library.
     Dart_LoadScript(Dart_NewStringFromCString("dart:empty"),
@@ -233,33 +290,6 @@
       base::Bind(&CallHandleMessage, DartState::From(dest_isolate)->GetWeakPtr()));
 }
 
-static void EnsureHandleWatcherStarted() {
-  static bool handle_watcher_started = false;
-  if (handle_watcher_started)
-    return;
-
-  // TODO(dart): Call Dart_Cleanup (ensure the handle watcher isolate is closed)
-  // during shutdown.
-  Dart_Handle mojo_core_lib =
-      Builtin::LoadAndCheckLibrary(Builtin::kMojoInternalLibrary);
-  CHECK(!LogIfError((mojo_core_lib)));
-  Dart_Handle handle_watcher_type = Dart_GetType(
-      mojo_core_lib,
-      Dart_NewStringFromCString("MojoHandleWatcher"),
-      0,
-      nullptr);
-  CHECK(!LogIfError(handle_watcher_type));
-  CHECK(!LogIfError(Dart_Invoke(
-      handle_watcher_type,
-      Dart_NewStringFromCString("_start"),
-      0,
-      nullptr)));
-
-  // RunLoop until the handle watcher isolate is spun-up.
-  CHECK(!LogIfError(Dart_RunLoop()));
-  handle_watcher_started = true;
-}
-
 void DartController::CreateIsolateFor(Document* document) {
   DCHECK(document);
   CHECK(kDartIsolateSnapshotBuffer);
@@ -283,7 +313,8 @@
 
     Builtin::SetNativeResolver(Builtin::kBuiltinLibrary);
     Builtin::SetNativeResolver(Builtin::kMojoInternalLibrary);
-    BuiltinNatives::Init();
+    Builtin::SetNativeResolver(Builtin::kIOLibrary);
+    BuiltinNatives::Init(BuiltinNatives::MainIsolate);
 
     builtin_sky_ = adoptPtr(new BuiltinSky(dart_state()));
     dart_state()->class_library().set_provider(builtin_sky_.get());
@@ -312,13 +343,20 @@
   argv = kCheckedModeArgs;
 #endif
 
+  dart::bin::BootstrapDartIo();
+
   CHECK(Dart_SetVMFlags(argc, argv));
+  // This should be called before calling Dart_Initialize.
+  DartDebugger::InitDebugger();
   CHECK(Dart_Initialize(kDartVmIsolateSnapshotBuffer,
                         IsolateCreateCallback,
                         nullptr,  // Isolate interrupt callback.
                         UnhandledExceptionCallback, IsolateShutdownCallback,
                         // File IO callbacks.
                         nullptr, nullptr, nullptr, nullptr, nullptr));
+  // Wait for load port- ensures handle watcher and service isolates are
+  // running.
+  Dart_ServiceWaitForLoadPort();
 }
 
 } // namespace blink
diff --git a/sky/engine/core/script/dart_debugger.cc b/sky/engine/core/script/dart_debugger.cc
new file mode 100644
index 0000000..f7c1c49
--- /dev/null
+++ b/sky/engine/core/script/dart_debugger.cc
@@ -0,0 +1,138 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sky/engine/core/script/dart_debugger.h"
+
+#include "dart/runtime/include/dart_api.h"
+#include "dart/runtime/include/dart_debugger_api.h"
+#include "dart/runtime/include/dart_native_api.h"
+
+
+namespace blink {
+
+void DartDebuggerIsolate::MessageLoop() {
+  MonitorLocker ml(&monitor_);
+  // Request notification on isolate messages.  This allows us to
+  // respond to vm service messages while at breakpoint.
+  Dart_SetMessageNotifyCallback(DartDebugger::NotifyIsolate);
+  while (true) {
+    // Handle all available vm service messages, up to a resume
+    // request.
+    bool resume = false;
+    while (!resume && Dart_HasServiceMessages()) {
+      monitor_.Exit();
+      resume = Dart_HandleServiceMessages();
+      monitor_.Enter();
+    }
+    if (resume) {
+      break;
+    }
+    ml.Wait();
+  }
+  Dart_SetMessageNotifyCallback(nullptr);
+}
+
+void DartDebugger::BptResolvedHandler(Dart_IsolateId isolate_id,
+                                      intptr_t bp_id,
+                                      const Dart_CodeLocation& location) {
+  // Nothing to do here. Service event is dispatched to let Observatory know
+  // that a breakpoint was resolved.
+}
+
+void DartDebugger::PausedEventHandler(Dart_IsolateId isolate_id,
+                                      intptr_t bp_id,
+                                      const Dart_CodeLocation& loc) {
+  Dart_EnterScope();
+  intptr_t isolate_index = FindIsolateIndexById(isolate_id);
+  CHECK(isolate_index != -1);
+  (*isolates_)[isolate_index]->MessageLoop();
+  Dart_ExitScope();
+}
+
+void DartDebugger::ExceptionThrownHandler(Dart_IsolateId isolate_id,
+                                          Dart_Handle exception,
+                                          Dart_StackTrace stack_trace) {
+  Dart_EnterScope();
+  intptr_t isolate_index = FindIsolateIndexById(isolate_id);
+  CHECK(isolate_index != -1);
+  (*isolates_)[isolate_index]->MessageLoop();
+  Dart_ExitScope();
+}
+
+void DartDebugger::IsolateEventHandler(Dart_IsolateId isolate_id,
+                                       Dart_IsolateEvent kind) {
+  Dart_EnterScope();
+  if (kind == Dart_IsolateEvent::kCreated) {
+    AddIsolate(isolate_id);
+  } else {
+    intptr_t isolate_index = FindIsolateIndexById(isolate_id);
+    CHECK(isolate_index != -1);
+    if (kind == Dart_IsolateEvent::kInterrupted) {
+      (*isolates_)[isolate_index]->MessageLoop();
+    } else {
+      CHECK(kind == Dart_IsolateEvent::kShutdown);
+      RemoveIsolate(isolate_id);
+    }
+  }
+  Dart_ExitScope();
+}
+
+void DartDebugger::NotifyIsolate(Dart_Isolate isolate) {
+  base::AutoLock al(*lock_);
+  Dart_IsolateId isolate_id = Dart_GetIsolateId(isolate);
+  intptr_t isolate_index = FindIsolateIndexByIdLocked(isolate_id);
+  if (isolate_index >= 0) {
+    (*isolates_)[isolate_index]->Notify();
+  }
+}
+
+void DartDebugger::InitDebugger() {
+  Dart_SetIsolateEventHandler(IsolateEventHandler);
+  Dart_SetPausedEventHandler(PausedEventHandler);
+  Dart_SetBreakpointResolvedHandler(BptResolvedHandler);
+  Dart_SetExceptionThrownHandler(ExceptionThrownHandler);
+  lock_ = new base::Lock();
+  isolates_ = new std::vector<std::unique_ptr<DartDebuggerIsolate>>();
+}
+
+intptr_t DartDebugger::FindIsolateIndexById(Dart_IsolateId id) {
+  base::AutoLock al(*lock_);
+  return FindIsolateIndexByIdLocked(id);
+}
+
+intptr_t DartDebugger::FindIsolateIndexByIdLocked(
+      Dart_IsolateId id) {
+  lock_->AssertAcquired();
+  for (size_t i = 0; i < isolates_->size(); i++) {
+    if ((*isolates_)[i]->id() == id) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+void DartDebugger::AddIsolate(Dart_IsolateId id) {
+  base::AutoLock al(*lock_);
+  CHECK(FindIsolateIndexByIdLocked(id) == -1);
+  std::unique_ptr<DartDebuggerIsolate> debugger_isolate =
+      std::unique_ptr<DartDebuggerIsolate>(new DartDebuggerIsolate(id));
+  isolates_->push_back(std::move(debugger_isolate));
+}
+
+void DartDebugger::RemoveIsolate(Dart_IsolateId id) {
+  base::AutoLock al(*lock_);
+  for (size_t i = 0; i < isolates_->size(); i++) {
+    if (id == (*isolates_)[i]->id()) {
+      isolates_->erase(isolates_->begin() + i);
+      return;
+    }
+  }
+  NOTREACHED();
+}
+
+base::Lock* DartDebugger::lock_ = nullptr;
+std::vector<std::unique_ptr<DartDebuggerIsolate>>* DartDebugger::isolates_ =
+    nullptr;
+
+}  // namespace blink
diff --git a/sky/engine/core/script/dart_debugger.h b/sky/engine/core/script/dart_debugger.h
new file mode 100644
index 0000000..8c93b6c
--- /dev/null
+++ b/sky/engine/core/script/dart_debugger.h
@@ -0,0 +1,81 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SKY_ENGINE_CORE_SCRIPT_DART_DEBUGGER_H_
+#define SKY_ENGINE_CORE_SCRIPT_DART_DEBUGGER_H_
+
+#include <memory>
+#include <vector>
+
+#include "dart/runtime/include/dart_api.h"
+#include "dart/runtime/include/dart_debugger_api.h"
+#include "dart/runtime/include/dart_native_api.h"
+#include "sky/engine/core/script/monitor.h"
+
+namespace base {
+  class Lock;
+}
+
+namespace blink {
+
+class DartDebuggerIsolate {
+ public:
+  DartDebuggerIsolate(Dart_IsolateId id)
+      : id_(id) {
+  }
+
+  Dart_IsolateId id() const {
+    return id_;
+  }
+
+  void Notify() {
+    monitor_.Notify();
+  }
+
+  void MessageLoop();
+
+ private:
+  const Dart_IsolateId id_;
+  Monitor monitor_;
+};
+
+class DartDebugger {
+ public:
+  static void InitDebugger();
+
+ private:
+  static void BptResolvedHandler(Dart_IsolateId isolate_id,
+                                 intptr_t bp_id,
+                                 const Dart_CodeLocation& location);
+
+  static void PausedEventHandler(Dart_IsolateId isolate_id,
+                                 intptr_t bp_id,
+                                 const Dart_CodeLocation& loc);
+
+  static void ExceptionThrownHandler(Dart_IsolateId isolate_id,
+                                     Dart_Handle exception,
+                                     Dart_StackTrace stack_trace);
+
+  static void IsolateEventHandler(Dart_IsolateId isolate_id,
+                                  Dart_IsolateEvent kind);
+
+  static void NotifyIsolate(Dart_Isolate isolate);
+
+  static intptr_t FindIsolateIndexById(Dart_IsolateId id);
+
+  static intptr_t FindIsolateIndexByIdLocked(Dart_IsolateId id);
+
+  static void AddIsolate(Dart_IsolateId id);
+
+  static void RemoveIsolate(Dart_IsolateId id);
+
+  static base::Lock* lock_;
+  static std::vector<std::unique_ptr<DartDebuggerIsolate>>* isolates_;
+
+  friend class DartDebuggerIsolate;
+};
+
+}  // namespace blink
+
+#endif  // SKY_ENGINE_CORE_SCRIPT_DART_DEBUGGER_H_
\ No newline at end of file
diff --git a/sky/engine/core/script/dart_service_isolate.cc b/sky/engine/core/script/dart_service_isolate.cc
new file mode 100644
index 0000000..d1bf545
--- /dev/null
+++ b/sky/engine/core/script/dart_service_isolate.cc
@@ -0,0 +1,304 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "dart_service_isolate.h"
+
+#include "base/logging.h"
+#include "dart/runtime/include/dart_api.h"
+#include "sky/engine/tonic/dart_error.h"
+#include "sky/engine/tonic/dart_string.h"
+
+#define RETURN_ERROR_HANDLE(handle)                             \
+  if (Dart_IsError(handle)) {                                   \
+    return handle;                                              \
+  }
+
+#define SHUTDOWN_ON_ERROR(handle)                               \
+  if (Dart_IsError(handle)) {                                   \
+    *error = strdup(Dart_GetError(handle));                     \
+    Dart_ExitScope();                                           \
+    Dart_ShutdownIsolate();                                     \
+    return false;                                               \
+  }
+
+#define kLibrarySourceNamePrefix "/dart_service_isolate"
+static const char* kServiceIsolateScript = "main.dart";
+
+struct ResourcesEntry {
+  const char* path_;
+  const char* resource_;
+  int length_;
+};
+
+namespace mojo {
+  namespace dart {
+    extern ResourcesEntry __sky_embedder_service_isolate_resources_[];
+  }
+}
+
+namespace blink {
+
+class Resources {
+ public:
+  static const int kNoSuchInstance = -1;
+  static int ResourceLookup(const char* path, const char** resource) {
+    ResourcesEntry* table = ResourcesTable();
+    for (int i = 0; table[i].path_ != NULL; i++) {
+      const ResourcesEntry& entry = table[i];
+      if (strcmp(path, entry.path_) == 0) {
+        *resource = entry.resource_;
+        DCHECK(entry.length_ > 0);
+        return entry.length_;
+      }
+    }
+    return kNoSuchInstance;
+  }
+
+  static const char* Path(int idx) {
+    DCHECK(idx >= 0);
+    ResourcesEntry* entry = At(idx);
+    if (entry == NULL) {
+      return NULL;
+    }
+    DCHECK(entry->path_ != NULL);
+    return entry->path_;
+  }
+
+ private:
+  static ResourcesEntry* At(int idx) {
+    DCHECK(idx >= 0);
+    ResourcesEntry* table = ResourcesTable();
+    for (int i = 0; table[i].path_ != NULL; i++) {
+      if (idx == i) {
+        return &table[i];
+      }
+    }
+    return NULL;
+  }
+  static ResourcesEntry* ResourcesTable() {
+    return &mojo::dart::__sky_embedder_service_isolate_resources_[0];
+  }
+};
+
+void DartServiceIsolate::TriggerResourceLoad(Dart_NativeArguments args) {
+  Dart_Handle library = Dart_RootLibrary();
+  DCHECK(!Dart_IsError(library));
+  Dart_Handle result = LoadResources(library);
+  DCHECK(!Dart_IsError(result));
+}
+
+void DartServiceIsolate::NotifyServerState(Dart_NativeArguments args) {
+  // NO-OP.
+}
+
+void DartServiceIsolate::Shutdown(Dart_NativeArguments args) {
+  // NO-OP.
+}
+
+DartBuiltin::Natives DartServiceIsolate::native_entries_[] = {
+  {"ServiceIsolate_TriggerResourceLoad", TriggerResourceLoad, 0 },
+  {"ServiceIsolate_NotifyServerState", NotifyServerState, 2 },
+  {"ServiceIsolate_Shutdown", Shutdown, 0 },
+};
+
+Dart_NativeFunction DartServiceIsolate::NativeResolver(Dart_Handle name,
+                                                       int argument_count,
+                                                       bool* auto_setup_scope) {
+  CHECK(builtins_);
+  return builtins_->Resolver(name, argument_count, auto_setup_scope);
+}
+
+const uint8_t* DartServiceIsolate::NativeSymbolizer(
+    Dart_NativeFunction native_function) {
+  CHECK(builtins_);
+  return builtins_->Symbolizer(native_function);
+}
+
+Dart_LibraryTagHandler DartServiceIsolate::embedder_tag_handler_ = nullptr;
+DartBuiltin* DartServiceIsolate::builtins_ = nullptr;
+
+bool DartServiceIsolate::Startup(std::string server_ip,
+                                 intptr_t server_port,
+                                 Dart_LibraryTagHandler embedder_tag_handler,
+                                 char** error) {
+  Dart_Isolate isolate = Dart_CurrentIsolate();
+  CHECK(isolate);
+
+  // Remember the embedder's library tag handler.
+  embedder_tag_handler_ = embedder_tag_handler;
+  CHECK(embedder_tag_handler_);
+
+  // Setup native entries.
+  builtins_ =
+      new DartBuiltin(&DartServiceIsolate::native_entries_[0],
+                      arraysize(native_entries_));
+
+  Dart_Handle result;
+
+  // Use our own library tag handler when loading service isolate sources.
+  Dart_SetLibraryTagHandler(DartServiceIsolate::LibraryTagHandler);
+  // Load main script.
+  Dart_Handle library = LoadScript(kServiceIsolateScript);
+  DCHECK(library != Dart_Null());
+  SHUTDOWN_ON_ERROR(library);
+  // Setup native entry resolution.
+  result = Dart_SetNativeResolver(library, NativeResolver, NativeSymbolizer);
+
+  SHUTDOWN_ON_ERROR(result);
+  // Finalize loading.
+  result = Dart_FinalizeLoading(false);
+  SHUTDOWN_ON_ERROR(result);
+
+  // Make runnable.
+  Dart_ExitScope();
+  Dart_ExitIsolate();
+  bool retval = Dart_IsolateMakeRunnable(isolate);
+  if (!retval) {
+    Dart_EnterIsolate(isolate);
+    Dart_ShutdownIsolate();
+    *error = strdup("Invalid isolate state - Unable to make it runnable.");
+    return false;
+  }
+  Dart_EnterIsolate(isolate);
+  Dart_EnterScope();
+
+  library = Dart_RootLibrary();
+  SHUTDOWN_ON_ERROR(library);
+
+  // Set the HTTP server's ip.
+  result = Dart_SetField(library,
+                         Dart_NewStringFromCString("_ip"),
+                         Dart_NewStringFromCString(server_ip.c_str()));
+  SHUTDOWN_ON_ERROR(result);
+  // If we have a port specified, start the server immediately.
+  bool auto_start = server_port >= 0;
+  if (server_port < 0) {
+    // Adjust server_port to port 0 which will result in the first available
+    // port when the HTTP server is started.
+    server_port = 0;
+  }
+  // Set the HTTP's servers port.
+  result = Dart_SetField(library,
+                         Dart_NewStringFromCString("_port"),
+                         Dart_NewInteger(server_port));
+  SHUTDOWN_ON_ERROR(result);
+  result = Dart_SetField(library,
+                         Dart_NewStringFromCString("_autoStart"),
+                         Dart_NewBoolean(auto_start));
+  SHUTDOWN_ON_ERROR(result);
+  return true;
+}
+
+Dart_Handle DartServiceIsolate::GetSource(const char* name) {
+  const intptr_t kBufferSize = 512;
+  char buffer[kBufferSize];
+  snprintf(&buffer[0], kBufferSize-1, "%s/%s", kLibrarySourceNamePrefix, name);
+  const char* vmservice_source = NULL;
+  int r = Resources::ResourceLookup(buffer, &vmservice_source);
+  DCHECK(r != Resources::kNoSuchInstance);
+  return Dart_NewStringFromCString(vmservice_source);
+}
+
+Dart_Handle DartServiceIsolate::LoadScript(const char* name) {
+  Dart_Handle url = Dart_NewStringFromCString("dart:vmservice_sky");
+  Dart_Handle source = GetSource(name);
+  return Dart_LoadScript(url, source, 0, 0);
+}
+
+Dart_Handle DartServiceIsolate::LoadSource(Dart_Handle library, const char* name) {
+  Dart_Handle url = Dart_NewStringFromCString(name);
+  Dart_Handle source = GetSource(name);
+  return Dart_LoadSource(library, url, source, 0, 0);
+}
+
+Dart_Handle DartServiceIsolate::LoadResource(Dart_Handle library,
+                                    const char* resource_name) {
+  // Prepare for invoke call.
+  Dart_Handle name = Dart_NewStringFromCString(resource_name);
+  RETURN_ERROR_HANDLE(name);
+  const char* data_buffer = NULL;
+  int data_buffer_length = Resources::ResourceLookup(resource_name,
+                                                     &data_buffer);
+  DCHECK(data_buffer_length != Resources::kNoSuchInstance);
+  Dart_Handle data_list = Dart_NewTypedData(Dart_TypedData_kUint8,
+                                            data_buffer_length);
+  RETURN_ERROR_HANDLE(data_list);
+  Dart_TypedData_Type type = Dart_TypedData_kInvalid;
+  void* data_list_buffer = NULL;
+  intptr_t data_list_buffer_length = 0;
+  Dart_Handle result = Dart_TypedDataAcquireData(data_list, &type,
+                                                 &data_list_buffer,
+                                                 &data_list_buffer_length);
+  RETURN_ERROR_HANDLE(result);
+  DCHECK(data_buffer_length == data_list_buffer_length);
+  DCHECK(data_list_buffer != NULL);
+  DCHECK(type = Dart_TypedData_kUint8);
+  memmove(data_list_buffer, &data_buffer[0], data_buffer_length);
+  result = Dart_TypedDataReleaseData(data_list);
+  RETURN_ERROR_HANDLE(result);
+
+  // Make invoke call.
+  const intptr_t kNumArgs = 2;
+  Dart_Handle args[kNumArgs] = { name, data_list };
+  result = Dart_Invoke(library, Dart_NewStringFromCString("_addResource"),
+                       kNumArgs, args);
+  return result;
+}
+
+Dart_Handle DartServiceIsolate::LoadResources(Dart_Handle library) {
+  Dart_Handle result = Dart_Null();
+  intptr_t prefixLen = strlen(kLibrarySourceNamePrefix);
+  for (intptr_t i = 0; Resources::Path(i) != NULL; i++) {
+    const char* path = Resources::Path(i);
+    // If it doesn't begin with kLibrarySourceNamePrefix it is a frontend
+    // resource.
+    if (strncmp(path, kLibrarySourceNamePrefix, prefixLen) != 0) {
+      result = LoadResource(library, path);
+      if (Dart_IsError(result)) {
+        break;
+      }
+    }
+  }
+  return result;
+}
+
+Dart_Handle DartServiceIsolate::LibraryTagHandler(Dart_LibraryTag tag,
+                                         Dart_Handle library,
+                                         Dart_Handle url) {
+  if (!Dart_IsLibrary(library)) {
+    return Dart_NewApiError("not a library");
+  }
+  if (!Dart_IsString(url)) {
+    return Dart_NewApiError("url is not a string");
+  }
+  const char* url_string = NULL;
+  Dart_Handle result = Dart_StringToCString(url, &url_string);
+  if (Dart_IsError(result)) {
+    return result;
+  }
+  Dart_Handle library_url = Dart_LibraryUrl(library);
+  const char* library_url_string = NULL;
+  result = Dart_StringToCString(library_url, &library_url_string);
+  if (Dart_IsError(result)) {
+    return result;
+  }
+  if (tag == Dart_kImportTag) {
+    // Embedder handles all requests for external libraries.
+    return embedder_tag_handler_(tag, library, url);
+  }
+  DCHECK((tag == Dart_kSourceTag) || (tag == Dart_kCanonicalizeUrl));
+  if (tag == Dart_kCanonicalizeUrl) {
+    // url is already canonicalized.
+    return url;
+  }
+  // Get source from builtin resources.
+  Dart_Handle source = GetSource(url_string);
+  if (Dart_IsError(source)) {
+    return source;
+  }
+  return Dart_LoadSource(library, url, source, 0, 0);
+}
+
+
+}  // namespace blink
diff --git a/sky/engine/core/script/dart_service_isolate.h b/sky/engine/core/script/dart_service_isolate.h
new file mode 100644
index 0000000..450ad16
--- /dev/null
+++ b/sky/engine/core/script/dart_service_isolate.h
@@ -0,0 +1,55 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SKY_ENGINE_CORE_SCRIPT_DART_SERVICE_ISOLATE_H_
+#define SKY_ENGINE_CORE_SCRIPT_DART_SERVICE_ISOLATE_H_
+
+#include <string>
+
+#include "include/dart_api.h"
+#include "sky/engine/config.h"
+#include "sky/engine/tonic/dart_builtin.h"
+
+namespace blink {
+
+class DartServiceIsolate {
+ public:
+  static bool Bootstrap();
+
+  static bool Startup(std::string server_ip,
+                      intptr_t server_port,
+                      Dart_LibraryTagHandler embedder_tag_handler,
+                      char** error);
+
+ private:
+  // Native entries.
+  static void TriggerResourceLoad(Dart_NativeArguments args);
+  static void NotifyServerState(Dart_NativeArguments args);
+  static void Shutdown(Dart_NativeArguments args);
+  // Native entry resolution.
+  static Dart_NativeFunction NativeResolver(Dart_Handle name,
+                                            int argument_count,
+                                            bool* auto_setup_scope);
+  static const uint8_t* NativeSymbolizer(Dart_NativeFunction native_function);
+  static DartBuiltin::Natives native_entries_[];
+  static DartBuiltin* builtins_;
+
+  // Script loading.
+  static Dart_Handle GetSource(const char* name);
+  static Dart_Handle LoadScript(const char* name);
+  static Dart_Handle LoadSource(Dart_Handle library, const char* name);
+  static Dart_Handle LibraryTagHandler(Dart_LibraryTag tag, Dart_Handle library,
+                                       Dart_Handle url);
+
+  // Observatory resource loading.
+  static Dart_Handle LoadResources(Dart_Handle library);
+  static Dart_Handle LoadResource(Dart_Handle library, const char* name);
+
+  static Dart_LibraryTagHandler embedder_tag_handler_;
+};
+
+
+}  // namespace blink
+
+#endif  // SKY_ENGINE_CORE_SCRIPT_DART_SERVICE_ISOLATE_H_
diff --git a/sky/engine/core/script/dart_service_isolate/loader.dart b/sky/engine/core/script/dart_service_isolate/loader.dart
new file mode 100644
index 0000000..a8d0d94
--- /dev/null
+++ b/sky/engine/core/script/dart_service_isolate/loader.dart
@@ -0,0 +1,11 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of dart_controller_service_isolate;
+
+_processLoadRequest(request) {
+  var sp = request[0];
+  var uri = Uri.parse(request[1]);
+  sp.send('Service isolate loading not supported by embedder (uri = $uri).');
+}
\ No newline at end of file
diff --git a/sky/engine/core/script/dart_service_isolate/main.dart b/sky/engine/core/script/dart_service_isolate/main.dart
new file mode 100644
index 0000000..1eb027d
--- /dev/null
+++ b/sky/engine/core/script/dart_service_isolate/main.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library sky_shell_dart_controller_service_isolate;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:isolate';
+import 'dart:vmservice';
+
+part 'loader.dart';
+part 'resources.dart';
+part 'server.dart';
+
+// The TCP ip/port that the HTTP server listens on.
+int _port;
+String _ip;
+// Should the HTTP server auto start?
+bool _autoStart;
+
+// HTTP server.
+Server server;
+
+_onShutdown() {
+  if (server != null) {
+    server.close(true).catchError((e, st) {
+      print(e);
+    }).whenComplete(_shutdown);
+  } else {
+    _shutdown();
+  }
+}
+
+void _bootServer() {
+  // Load resources.
+  _triggerResourceLoad();
+  // Lazily create service.
+  var service = new VMService();
+  service.onShutdown = _onShutdown;
+  // Lazily create server.
+  server = new Server(service, _ip, _port);
+}
+
+main() {
+  if (_autoStart) {
+    _bootServer();
+    if (server != null) {
+      server.startup();
+    }
+  }
+  scriptLoadPort.handler = _processLoadRequest;
+  // It's just here to push an event on the event loop so that we invoke the
+  // scheduled microtasks.
+  Timer.run(() {});
+  return scriptLoadPort;
+}
+
+_shutdown() native "ServiceIsolate_Shutdown";
diff --git a/sky/engine/core/script/dart_service_isolate/resources.dart b/sky/engine/core/script/dart_service_isolate/resources.dart
new file mode 100644
index 0000000..11e347e
--- /dev/null
+++ b/sky/engine/core/script/dart_service_isolate/resources.dart
@@ -0,0 +1,50 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of dart_controller_service_isolate;
+
+String detectMimeType(String name) {
+  var extensionStart = name.lastIndexOf('.');
+  var extension = name.substring(extensionStart+1);
+  switch (extension) {
+    case 'html':
+      return 'text/html; charset=UTF-8';
+    case 'dart':
+      return 'application/dart; charset=UTF-8';
+    case 'js':
+      return 'application/javascript; charset=UTF-8';
+    case 'css':
+      return 'text/css; charset=UTF-8';
+    case 'gif':
+      return 'image/gif';
+    case 'png':
+      return 'image/png';
+    case 'jpg':
+      return 'image/jpeg';
+    case 'jpeg':
+      return 'image/jpeg';
+    case 'svg':
+      return 'image/svg+xml';
+    default:
+      return 'text/plain';
+  }
+}
+
+
+class Resource {
+  final String name;
+  final String mimeType;
+  final List<int> data;
+  Resource(this.name, this.mimeType, this.data);
+  static final Map<String, Resource> resources = new Map<String, Resource>();
+}
+
+
+_addResource(String name, List<int> data) {
+  var mimeType = detectMimeType(name);
+  Resource resource = new Resource(name, mimeType, data);
+  Resource.resources[name] = resource;
+}
+
+_triggerResourceLoad() native "ServiceIsolate_TriggerResourceLoad";
diff --git a/sky/engine/core/script/dart_service_isolate/server.dart b/sky/engine/core/script/dart_service_isolate/server.dart
new file mode 100644
index 0000000..4e069f9
--- /dev/null
+++ b/sky/engine/core/script/dart_service_isolate/server.dart
@@ -0,0 +1,225 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of dart_controller_service_isolate;
+
+class WebSocketClient extends Client {
+  static const int PARSE_ERROR_CODE = 4000;
+  static const int BINARY_MESSAGE_ERROR_CODE = 4001;
+  static const int NOT_MAP_ERROR_CODE = 4002;
+  final WebSocket socket;
+
+  WebSocketClient(this.socket, VMService service) : super(service) {
+    socket.listen((message) => onWebSocketMessage(message));
+    socket.done.then((_) => close());
+  }
+
+  onWebSocketMessage(message) {
+    if (message is String) {
+      var map;
+      try {
+        map = JSON.decode(message);
+      } catch (e) {
+        socket.close(PARSE_ERROR_CODE, 'Message parse error: $e');
+        return;
+      }
+      if (map is! Map) {
+        socket.close(NOT_MAP_ERROR_CODE, 'Message must be a JSON map.');
+        return;
+      }
+      var serial = map['id'];
+      onMessage(serial, new Message.fromJsonRpc(map));
+    } else {
+      socket.close(BINARY_MESSAGE_ERROR_CODE, 'Message must be a string.');
+    }
+  }
+
+  post(dynamic result) {
+    try {
+      socket.add(result);
+    } catch (_) {
+      print("Ignoring error posting over WebSocket.");
+    }
+  }
+
+  dynamic toJson() {
+    Map map = super.toJson();
+    map['type'] = 'WebSocketClient';
+    map['socket'] = '$socket';
+    return map;
+  }
+}
+
+
+class HttpRequestClient extends Client {
+  static ContentType jsonContentType =
+      new ContentType("application", "json", charset: "utf-8");
+  final HttpRequest request;
+
+  HttpRequestClient(this.request, VMService service) : super(service);
+
+  post(String result) {
+    request.response..headers.contentType = jsonContentType
+                    ..write(result)
+                    ..close();
+    close();
+  }
+
+  dynamic toJson() {
+    Map map = super.toJson();
+    map['type'] = 'HttpRequestClient';
+    map['request'] = '$request';
+    return map;
+  }
+}
+
+class Server {
+  static const WEBSOCKET_PATH = '/ws';
+  static const ROOT_REDIRECT_PATH = '/index.html';
+
+  final VMService _service;
+  final String _ip;
+  final int _port;
+
+  HttpServer _server;
+  bool get running => _server != null;
+  bool _displayMessages = false;
+
+  Server(this._service, this._ip, this._port) {
+    _displayMessages = (_ip != '127.0.0.1' || _port != 8181);
+  }
+
+  bool _shouldServeObservatory(HttpRequest request) {
+    if (request.headers['Observatory-Version'] != null) {
+      // Request is already coming from Observatory.
+      return false;
+    }
+    // TODO(johnmccutchan): Test with obscure browsers.
+    if (request.headers.value(HttpHeaders.USER_AGENT).contains('Mozilla')) {
+      // Request is coming from a browser but not Observatory application.
+      // Serve Observatory and let the Observatory make the real request.
+      return true;
+    }
+    // All other user agents are assumed to be textual.
+    return false;
+  }
+
+  _onServerShutdown() {
+  }
+
+  _serverError(error, stackTrace) {
+    _onServerShutdown();
+  }
+
+  _serverDone() {
+    _onServerShutdown();
+  }
+
+  _requestHandler(HttpRequest request) {
+    // Allow cross origin requests with 'observatory' header.
+    request.response.headers.add('Access-Control-Allow-Origin', '*');
+    request.response.headers.add('Access-Control-Allow-Headers',
+                                 'Observatory-Version');
+
+    if (request.method != 'GET') {
+      // Not a GET request. Do nothing.
+      request.response.close();
+      return;
+    }
+
+    final String path =
+          request.uri.path == '/' ? ROOT_REDIRECT_PATH : request.uri.path;
+
+    if (path == WEBSOCKET_PATH) {
+      WebSocketTransformer.upgrade(request).then((WebSocket webSocket) {
+        new WebSocketClient(webSocket, _service);
+      });
+      return;
+    }
+
+    var resource = Resource.resources[path];
+    if (resource == null && _shouldServeObservatory(request)) {
+      resource = Resource.resources[ROOT_REDIRECT_PATH];
+      assert(resource != null);
+    }
+    if (resource != null) {
+      // Serving up a static resource (e.g. .css, .html, .png).
+      request.response.headers.contentType =
+          ContentType.parse(resource.mimeType);
+      request.response.add(resource.data);
+      request.response.close();
+      return;
+    }
+    var message = new Message.fromUri(request.uri);
+    var client = new HttpRequestClient(request, _service);
+    client.onMessage(null, message);
+  }
+
+  Future startup() {
+    if (_server != null) {
+      // Already running.
+      return new Future.value(this);
+    }
+
+    // Startup HTTP server.
+    var address = new InternetAddress('127.0.0.1');
+    return HttpServer.bind(address, _port).then((s) {
+      _server = s;
+      _server.listen(_requestHandler,
+                     onError: _serverError,
+                     onDone: _serverDone,
+                     cancelOnError: true);
+      var ip = _server.address.address.toString();
+      if (_displayMessages) {
+        var port = _server.port.toString();
+        print('Observatory listening on http://$ip:$port');
+      }
+      // Server is up and running.
+      _notifyServerState(ip, _server.port);
+      return this;
+    }).catchError((e, st) {
+      print('Could not start Observatory HTTP server:\n$e\n$st\n');
+      _notifyServerState("", 0);
+      return this;
+    });
+  }
+
+  close(bool force) {
+    if (_server == null) {
+      return new Future.value(null);
+    }
+    return _server.close(force: force);
+  }
+
+  Future shutdown(bool forced) {
+    if (_server == null) {
+      // Not started.
+      return new Future.value(this);
+    }
+
+    // Force displaying of status messages if we are forcibly shutdown.
+    _displayMessages = _displayMessages || forced;
+
+    // Shutdown HTTP server and subscription.
+    var ip = _server.address.address.toString();
+    var port = _server.port.toString();
+    return close(forced).then((_) {
+      if (_displayMessages) {
+        print('Observatory no longer listening on http://$ip:$port');
+      }
+      _server = null;
+      _notifyServerState("", 0);
+      return this;
+    }).catchError((e, st) {
+      _server = null;
+      print('Could not shutdown Observatory HTTP server:\n$e\n$st\n');
+      _notifyServerState("", 0);
+      return this;
+    });
+  }
+
+}
+
+_notifyServerState(String ip, int port)
+    native "ServiceIsolate_NotifyServerState";
diff --git a/sky/engine/core/script/monitor.h b/sky/engine/core/script/monitor.h
new file mode 100644
index 0000000..af784ec
--- /dev/null
+++ b/sky/engine/core/script/monitor.h
@@ -0,0 +1,75 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SKY_ENGINE_CORE_SCRIPT_MONITOR_H_
+#define SKY_ENGINE_CORE_SCRIPT_MONITOR_H_
+
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+
+namespace blink {
+
+class Monitor {
+ public:
+  Monitor() {
+    lock_ = new base::Lock();
+    condition_variable_ = new base::ConditionVariable(lock_);
+  }
+
+  ~Monitor() {
+    delete condition_variable_;
+    delete lock_;
+  }
+
+  void Enter() {
+    lock_->Acquire();
+  }
+
+  void Exit() {
+    lock_->Release();
+  }
+
+  void Notify() {
+    condition_variable_->Signal();
+  }
+
+  void Wait() {
+    condition_variable_->Wait();
+  }
+
+ private:
+  base::Lock* lock_;
+  base::ConditionVariable* condition_variable_;
+  DISALLOW_COPY_AND_ASSIGN(Monitor);
+};
+
+class MonitorLocker {
+ public:
+  explicit MonitorLocker(Monitor* monitor) : monitor_(monitor) {
+    CHECK(monitor_);
+    monitor_->Enter();
+  }
+
+  virtual ~MonitorLocker() {
+    monitor_->Exit();
+  }
+
+  void Wait() {
+    return monitor_->Wait();
+  }
+
+  void Notify() {
+    monitor_->Notify();
+  }
+
+ private:
+  Monitor* const monitor_;
+
+  DISALLOW_COPY_AND_ASSIGN(MonitorLocker);
+};
+
+}  // namespace blink
+
+
+#endif  // SKY_ENGINE_CORE_SCRIPT_MONITOR_H_
\ No newline at end of file
diff --git a/sky/examples/stocks/lib/stock_menu.dart b/sky/examples/stocks/lib/stock_menu.dart
index 9023ed2..03b0998 100644
--- a/sky/examples/stocks/lib/stock_menu.dart
+++ b/sky/examples/stocks/lib/stock_menu.dart
@@ -4,7 +4,7 @@
 
 import 'package:sky/framework/components/popup_menu.dart';
 import 'package:sky/framework/fn.dart';
-import 'package:sky/framework/theme/view-configuration.dart';
+import 'package:sky/framework/theme/view_configuration.dart';
 
 class StockMenu extends Component {
   static final Style _style = new Style('''
diff --git a/sky/framework/README.md b/sky/framework/README.md
index c937981..a15a276 100644
--- a/sky/framework/README.md
+++ b/sky/framework/README.md
@@ -78,7 +78,7 @@
   Checkbox({ Object key, this.onChanged, this.checked }) : super(key: key);
 ```
 
-What this means is that the `Checkbox` component is *never* "owns" the state of
+What this means is that the `Checkbox` component *never* "owns" the state of
 the checkbox. It's current state is handed into the `checked` parameter, and
 when a click occurs, the checkbox invokes its `onChanged` callback with the
 value it thinks it should be changed to -- but it never directly changes the
diff --git a/sky/framework/components/action_bar.dart b/sky/framework/components/action_bar.dart
index 0e34fba..e0b2ddf 100644
--- a/sky/framework/components/action_bar.dart
+++ b/sky/framework/components/action_bar.dart
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import '../fn.dart';
-import '../theme/view-configuration.dart';
+import '../theme/view_configuration.dart';
 import 'material.dart';
 
 class ActionBar extends Component {
diff --git a/sky/framework/components/drawer_header.dart b/sky/framework/components/drawer_header.dart
index da00446..c1c1d52 100644
--- a/sky/framework/components/drawer_header.dart
+++ b/sky/framework/components/drawer_header.dart
@@ -4,7 +4,7 @@
 
 import '../fn.dart';
 import '../theme/colors.dart';
-import '../theme/view-configuration.dart';
+import '../theme/view_configuration.dart';
 
 class DrawerHeader extends Component {
   static final Style _style = new Style('''
diff --git a/sky/framework/components/fixed_height_scrollable.dart b/sky/framework/components/fixed_height_scrollable.dart
index 3f3e973..2838acb 100644
--- a/sky/framework/components/fixed_height_scrollable.dart
+++ b/sky/framework/components/fixed_height_scrollable.dart
@@ -6,7 +6,6 @@
 import '../debug/tracing.dart';
 import '../fn.dart';
 import 'dart:math' as math;
-import 'dart:sky' as sky;
 import 'dart:async';
 import 'scrollable.dart';
 
@@ -52,14 +51,11 @@
       var item = root.firstChild.firstChild;
       if (item == null)
         return;
-      sky.ClientRect scrollRect = root.getBoundingClientRect();
-      sky.ClientRect itemRect = item.getBoundingClientRect();
-      assert(scrollRect.height > 0);
-      assert(itemRect.height > 0);
-
       setState(() {
-        _height = scrollRect.height;
-        _itemHeight = itemRect.height;
+        _height = root.height;
+        assert(_height > 0);
+        _itemHeight = item.height;
+        assert(_itemHeight > 0);
         scrollBehavior.containerHeight = _height;
         scrollBehavior.contentsHeight = _itemHeight * _itemCount;
       });
@@ -82,7 +78,6 @@
           'transform: translateY(${(-scrollOffset).toStringAsFixed(2)}px)';
       } else {
         drawCount = (_height / _itemHeight).round() + 1;
-        double alignmentOffset = math.max(0.0, scrollOffset);
         double alignmentDelta = -scrollOffset % _itemHeight;
         if (alignmentDelta != 0.0)
           alignmentDelta -= _itemHeight;
diff --git a/sky/framework/components/ink_splash.dart b/sky/framework/components/ink_splash.dart
index 0d147ad..3bd18e1 100644
--- a/sky/framework/components/ink_splash.dart
+++ b/sky/framework/components/ink_splash.dart
@@ -5,19 +5,19 @@
 import '../animation/animated_value.dart';
 import '../animation/curves.dart';
 import '../fn.dart';
-import '../theme/view-configuration.dart' as config;
+import '../theme/view_configuration.dart' as config;
 import 'dart:async';
 import 'dart:math' as math;
-import 'dart:sky' as sky;
+import '../layout.dart';
 
 const double _kSplashConfirmedDuration = 350.0;
 const double _kSplashUnconfirmedDuration = config.kDefaultLongPressTimeout;
 const double _kSplashAbortDuration = 100.0;
 const double _kSplashInitialDelay = 0.0; // we could delay initially in case the user scrolls
 
-double _getSplashTargetSize(sky.ClientRect rect, double x, double y) {
-  return 2.0 * math.max(math.max(x - rect.left, rect.right - x),
-                        math.max(y - rect.top, rect.bottom - y));
+double _getSplashTargetSize(Rect rect, double x, double y) {
+  return 2.0 * math.max(math.max(x - rect.x, rect.x + rect.width - x),
+                        math.max(y - rect.y, rect.y + rect.height - y));
 }
 
 class SplashController {
@@ -56,10 +56,10 @@
     _size.stop();
   }
 
-  SplashController(sky.ClientRect rect, double x, double y,
+  SplashController(Rect rect, double x, double y,
                    { this.pointer, Function onDone })
-      : _offsetX = x - rect.left,
-        _offsetY = y - rect.top,
+      : _offsetX = x - rect.x,
+        _offsetY = y - rect.y,
         _targetSize = _getSplashTargetSize(rect, x, y) {
 
     _styleStream = _size.onValueChanged.map((p) {
diff --git a/sky/framework/components/ink_well.dart b/sky/framework/components/ink_well.dart
index 096fa00..cc7181b 100644
--- a/sky/framework/components/ink_well.dart
+++ b/sky/framework/components/ink_well.dart
@@ -45,14 +45,13 @@
     );
   }
 
-  sky.ClientRect _getBoundingRect() => (getRoot() as sky.Element).getBoundingClientRect();
-
   void _startSplash(sky.GestureEvent event) {
     setState(() {
       if (_splashes == null)
         _splashes = new LinkedHashSet<SplashController>();
       var splash;
-      splash = new SplashController(_getBoundingRect(), event.x, event.y,
+      var root = getRoot();
+      splash = new SplashController(root.rect, event.x, event.y,
                                     pointer: event.primaryPointer,
                                     onDone: () { _splashDone(splash); });
       _splashes.add(splash);
diff --git a/sky/framework/components/popup_menu.dart b/sky/framework/components/popup_menu.dart
index 4375eef..90e0994 100644
--- a/sky/framework/components/popup_menu.dart
+++ b/sky/framework/components/popup_menu.dart
@@ -92,8 +92,8 @@
   void _measureSize() {
     setState(() {
       var root = getRoot();
-      _width = root.clientWidth;
-      _height = root.clientHeight;
+      _width = root.width.round();
+      _height = root.height.round();
     });
   }
 
diff --git a/sky/framework/components/scrollable.dart b/sky/framework/components/scrollable.dart
index 49310c1..3858a25 100644
--- a/sky/framework/components/scrollable.dart
+++ b/sky/framework/components/scrollable.dart
@@ -6,7 +6,7 @@
 import '../animation/mechanics.dart';
 import '../animation/scroll_behavior.dart';
 import '../fn.dart';
-import '../theme/view-configuration.dart' as config;
+import '../theme/view_configuration.dart' as config;
 import 'dart:math' as math;
 import 'dart:sky' as sky;
 
diff --git a/sky/framework/fn.dart b/sky/framework/fn.dart
index 850ffd2..78672ce 100644
--- a/sky/framework/fn.dart
+++ b/sky/framework/fn.dart
@@ -8,51 +8,15 @@
 import 'dart:collection';
 import 'dart:sky' as sky;
 import 'reflect.dart' as reflect;
+import 'layout.dart';
+
+export 'layout.dart' show Style;
 
 final sky.Tracing _tracing = sky.window.tracing;
 
 final bool _shouldLogRenderDuration = false;
 final bool _shouldTrace = false;
 
-class Style {
-  final String _className;
-  static final Map<String, Style> _cache = new HashMap<String, Style>();
-
-  static int _nextStyleId = 1;
-
-  static String _getNextClassName() { return "style${_nextStyleId++}"; }
-
-  Style extend(Style other) {
-    var className = "$_className ${other._className}";
-
-    return _cache.putIfAbsent(className, () {
-      return new Style._internal(className);
-    });
-  }
-
-  factory Style(String styles) {
-    return _cache.putIfAbsent(styles, () {
-      var className = _getNextClassName();
-      sky.Element styleNode = sky.document.createElement('style');
-      styleNode.setChild(new sky.Text(".$className { $styles }"));
-      sky.document.appendChild(styleNode);
-      return new Style._internal(className);
-    });
-  }
-
-  Style._internal(this._className);
-}
-
-void _parentInsertBefore(sky.ParentNode parent,
-                         sky.Node node,
-                         sky.Node ref) {
-  if (ref != null) {
-    ref.insertBefore([node]);
-  } else {
-    parent.appendChild(node);
-  }
-}
-
 enum _SyncOperation { IDENTICAL, INSERTION, STATEFUL, STATELESS, REMOVAL }
 
 /*
@@ -63,7 +27,7 @@
   String _key;
   UINode _parent;
   UINode get parent => _parent;
-  sky.Node _root;
+  RenderCSS _root;
   bool _defunct = false;
 
   UINode({ Object key }) {
@@ -74,7 +38,7 @@
   // if the |old| node has become stateful and should be retained.
   bool _willSync(UINode old) => false;
 
-  void _sync(UINode old, sky.ParentNode host, sky.Node insertBefore);
+  void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore);
 
   void _remove() {
     _defunct = true;
@@ -118,8 +82,8 @@
   }
 
   // Returns the child which should be retained as the child of this node.
-  UINode _syncChild(UINode node, UINode oldNode, sky.ParentNode host,
-      sky.Node insertBefore) {
+  UINode _syncChild(UINode node, UINode oldNode, RenderCSSContainer host,
+      RenderCSS insertBefore) {
 
     assert(oldNode == null || node._key == oldNode._key);
 
@@ -139,7 +103,7 @@
       _traceSync(_SyncOperation.STATEFUL, node._key);
       oldNode._sync(node, host, insertBefore);
       node._defunct = true;
-      assert(oldNode._root is sky.Node);
+      assert(oldNode._root is RenderCSS);
       return oldNode;
     }
 
@@ -154,7 +118,7 @@
     if (oldNode != null)
       oldNode._defunct = true;
 
-    assert(node._root is sky.Node);
+    assert(node._root is RenderCSS);
     return node;
   }
 }
@@ -164,14 +128,16 @@
 
   ContentNode(UINode content) : this.content = content, super(key: content._key);
 
-  void _sync(UINode old, sky.ParentNode host, sky.Node insertBefore) {
+  void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore) {
     UINode oldContent = old == null ? null : (old as ContentNode).content;
     content = _syncChild(content, oldContent, host, insertBefore);
+    assert(content._root != null);
     _root = content._root;
   }
 
   void _remove() {
-    _removeChild(content);
+    if (content != null)
+      _removeChild(content);
     super._remove();
   }
 }
@@ -183,31 +149,33 @@
 }
 
 /*
- * SkyNodeWrappers correspond to a desired state of a sky.Node. They are fully
+ * SkyNodeWrappers correspond to a desired state of a RenderCSS. They are fully
  * immutable, with one exception: A UINode which is a Component which lives within
  * an SkyElementWrapper's children list, may be replaced with the "old" instance if it
  * has become stateful.
  */
 abstract class SkyNodeWrapper extends UINode {
 
-  static final Map<sky.Node, SkyNodeWrapper> _nodeMap =
-      new HashMap<sky.Node, SkyNodeWrapper>();
+  static final Map<RenderCSS, SkyNodeWrapper> _nodeMap =
+      new HashMap<RenderCSS, SkyNodeWrapper>();
 
-  static SkyNodeWrapper _getMounted(sky.Node node) => _nodeMap[node];
+  static SkyNodeWrapper _getMounted(RenderCSS node) => _nodeMap[node];
 
   SkyNodeWrapper({ Object key }) : super(key: key);
 
   SkyNodeWrapper get _emptyNode;
 
-  sky.Node _createNode();
+  RenderCSS _createNode();
 
-  void _sync(UINode old, sky.ParentNode host, sky.Node insertBefore) {
+  void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore) {
     if (old == null) {
       _root = _createNode();
-      _parentInsertBefore(host, _root, insertBefore);
+      assert(_root != null);
+      host.add(_root, before: insertBefore);
       old = _emptyNode;
     } else {
       _root = old._root;
+      assert(_root != null);
     }
 
     _nodeMap[_root] = this;
@@ -216,9 +184,14 @@
 
   void _syncNode(SkyNodeWrapper old);
 
+  void _removeChild(UINode node) {
+    assert(_root is RenderCSSContainer);
+    _root.remove(node._root);
+    super._removeChild(node);
+  }
+
   void _remove() {
     assert(_root != null);
-    _root.remove();
     _nodeMap.remove(_root);
     super._remove();
   }
@@ -314,12 +287,12 @@
   }
 
   static void _dispatchEvent(sky.Event e) {
-    UINode target = SkyNodeWrapper._getMounted(e.target);
+    UINode target = SkyNodeWrapper._getMounted(bridgeEventTargetToRenderNode(e.target));
 
     // TODO(rafaelw): StopPropagation?
     while (target != null) {
       if (target is EventListenerNode) {
-        (target as EventListenerNode)._handleEvent(e);
+        target._handleEvent(e);
       }
 
       target = target._parent;
@@ -332,7 +305,7 @@
     }
   }
 
-  void _sync(UINode old, sky.ParentNode host, sky.Node insertBefore) {
+  void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore) {
     for (var type in listeners.keys) {
       _ensureDocumentListener(type);
     }
@@ -354,19 +327,15 @@
 
   SkyNodeWrapper get _emptyNode => _emptyText;
 
-  static final Style _displayParagraph = new Style('display:paragraph');
-
-  sky.Node _createNode() {
-    return sky.document.createElement('div')
-                           ..setChild(new sky.Text(this.data))
-                           ..setAttribute('class', _displayParagraph._className);
+  RenderCSSText _root;
+  RenderCSS _createNode() {
+    return new RenderCSSText(this, this.data);
   }
 
   void _syncNode(SkyNodeWrapper old) {
     if (old == _emptyText)
       return; // we set inside _createNode();
-
-    (_root.firstChild as sky.Text).data = data;
+    _root.data = data;
   }
 }
 
@@ -374,16 +343,10 @@
 
 abstract class SkyElementWrapper extends SkyNodeWrapper {
 
-  String get _tagName;
-
-  sky.Node _createNode() => sky.document.createElement(_tagName);
-
   final List<UINode> children;
   final Style style;
   final String inlineStyle;
 
-  String _class;
-
   SkyElementWrapper({
     Object key,
     List<UINode> children,
@@ -396,17 +359,18 @@
   }
 
   void _remove() {
-    super._remove();
-    if (children != null) {
-      for (var child in children) {
-        _removeChild(child);
-      }
+    assert(children != null);
+    for (var child in children) {
+      assert(child != null);
+      _removeChild(child);
     }
+    super._remove();
   }
 
   bool _debugHasDuplicateIds() {
     var idSet = new HashSet<String>();
     for (var child in children) {
+      assert(child != null);
       if (child is Text) {
         continue; // Text nodes all have the same key and are never reordered.
       }
@@ -419,42 +383,28 @@
     return false;
   }
 
-  void _ensureClass() {
-    if (_class == null) {
-      List<Style> styles = new List<Style>();
-      if (style != null) {
-        styles.add(style);
-      }
-
-      UINode parent = _parent;
-      while (parent != null && parent is! SkyNodeWrapper) {
-        if (parent is StyleNode && (parent as StyleNode).style != null)
-          styles.add((parent as StyleNode).style);
-
-        parent = parent._parent;
-      }
-
-      _class = styles.map((s) => s._className).join(' ');
-    }
-  }
-
   void _syncNode(SkyNodeWrapper old) {
     SkyElementWrapper oldSkyElementWrapper = old as SkyElementWrapper;
-    sky.Element root = _root as sky.Element;
 
-    _ensureClass();
-    if (_class != oldSkyElementWrapper._class && _class != '')
-      root.setAttribute('class', _class);
+    List<Style> styles = new List<Style>();
+    if (style != null)
+      styles.add(style);
+    UINode parent = _parent;
+    while (parent != null && parent is! SkyNodeWrapper) {
+      if (parent is StyleNode && parent.style != null)
+        styles.add(parent.style);
+      parent = parent._parent;
+    }
+    _root.updateStyles(styles);
 
-    if (inlineStyle != oldSkyElementWrapper.inlineStyle)
-      root.setAttribute('style', inlineStyle);
+    _root.updateInlineStyle(inlineStyle);
 
     _syncChildren(oldSkyElementWrapper);
   }
 
   void _syncChildren(SkyElementWrapper oldSkyElementWrapper) {
-    sky.Element root = _root as sky.Element;
-    assert(root != null);
+    if (_root is! RenderCSSContainer)
+      return;
 
     var startIndex = 0;
     var endIndex = children.length;
@@ -463,12 +413,13 @@
     var oldStartIndex = 0;
     var oldEndIndex = oldChildren.length;
 
-    sky.Node nextSibling = null;
+    RenderCSS nextSibling = null;
     UINode currentNode = null;
     UINode oldNode = null;
 
     void sync(int atIndex) {
       children[atIndex] = _syncChild(currentNode, oldNode, _root, nextSibling);
+      assert(children[atIndex] != null);
     }
 
     // Scan backwards from end of list while nodes can be directly synced
@@ -484,7 +435,6 @@
       endIndex--;
       oldEndIndex--;
       sync(endIndex);
-      nextSibling = currentNode._root;
     }
 
     HashMap<String, UINode> oldNodeIdMap = null;
@@ -526,19 +476,22 @@
         return false;
 
       oldNodeIdMap[currentNode._key] = null; // mark it reordered.
-      _parentInsertBefore(root, oldNode._root, nextSibling);
+      assert(_root is RenderCSSContainer);
+      assert(oldNode._root is RenderCSSContainer);
+      oldSkyElementWrapper._root.remove(oldNode._root);
+      _root.add(oldNode._root, before: nextSibling);
       return true;
     }
 
     // Scan forwards, this time we may re-order;
-    nextSibling = root.firstChild;
+    nextSibling = _root.firstChild;
     while (startIndex < endIndex && oldStartIndex < oldEndIndex) {
       currentNode = children[startIndex];
       oldNode = oldChildren[oldStartIndex];
 
       if (currentNode._key == oldNode._key) {
         assert(currentNode.runtimeType == oldNode.runtimeType);
-        nextSibling = nextSibling.nextSibling;
+        nextSibling = _root.childAfter(nextSibling);
         sync(startIndex);
         startIndex++;
         advanceOldStartIndex();
@@ -571,7 +524,7 @@
 
 class Container extends SkyElementWrapper {
 
-  String get _tagName => 'div';
+  RenderCSS _createNode() => new RenderCSSContainer(this);
 
   static final Container _emptyContainer = new Container();
 
@@ -590,49 +543,10 @@
   );
 }
 
-abstract class LayoutContainer extends Container {
-
-  LayoutContainer({
-    Object key,
-    List<UINode> children,
-    Style style,
-    String inlineStyle
-  }) : super(
-    key: key,
-    children: children,
-    style: style,
-    inlineStyle: inlineStyle
-  );
-
-  sky.Node _createNode() {
-    var result = super._createNode();
-    result.setLayoutManager(() => layout(_root));
-    return result;
-  }
-
-  // If we ever reuse sky nodes for different classes, then we should 
-  // call _root.setLayoutManager(null) during _remove() here.
-
-  void _syncNode(SkyNodeWrapper old) {
-    super._syncNode(old);
-    _root.setLayoutManager(() => layout(_root));
-    _root.setNeedsLayout();
-  }
-
-  void layout(sky.Element skyNode);
-  // set skyNode.width (e.g., set it to skyNode.parentNode.width)
-  // for each skyNode.getChildNodes()[i]:
-  //   call .layout()
-  //   set .x, .y
-  //   set .width if you want to force a width
-  //   set .height if you want to force a height
-  // set skyNode.height
-
-}
-
 class Image extends SkyElementWrapper {
 
-  String get _tagName => 'img';
+  RenderCSSImage _root;
+  RenderCSSImage _createNode() => new RenderCSSImage(this, this.src, this.width, this.height);
 
   static final Image _emptyImage = new Image();
 
@@ -659,56 +573,7 @@
 
   void _syncNode(UINode old) {
     super._syncNode(old);
-
-    Image oldImage = old as Image;
-    sky.HTMLImageElement skyImage = _root as sky.HTMLImageElement;
-
-    if (src != oldImage.src)
-      skyImage.src = src;
-
-    if (width != oldImage.width)
-      skyImage.style['width'] = '${width}px';
-
-    if (height != oldImage.height)
-      skyImage.style['height'] = '${height}px';
-  }
-}
-
-class Anchor extends SkyElementWrapper {
-
-  String get _tagName => 'a';
-
-  static final Anchor _emptyAnchor = new Anchor();
-
-  UINode get _emptyNode => _emptyAnchor;
-
-  final String href;
-  final int width;
-  final int height;
-
-  Anchor({
-    Object key,
-    List<UINode> children,
-    Style style,
-    String inlineStyle,
-    this.width,
-    this.height,
-    this.href
-  }) : super(
-    key: key,
-    children: children,
-    style: style,
-    inlineStyle: inlineStyle
-  );
-
-  void _syncNode(UINode old) {
-    super._syncNode(old);
-
-    Anchor oldAnchor = old as Anchor;
-    sky.HTMLAnchorElement skyAnchor = _root as sky.HTMLAnchorElement;
-
-    if (href != oldAnchor.href)
-      skyAnchor.href = href;
+    _root.configure(this.src, this.width, this.height);
   }
 }
 
@@ -789,9 +654,6 @@
   bool get _isBuilding => _currentlyBuilding == this;
   bool _dirty = true;
 
-  sky.Node get _host => _root.parentNode;
-  sky.Node get _insertionPoint => _root == null ? _root : _root.nextSibling;
-
   UINode _built;
   final int _order;
   static int _currentOrder = 0;
@@ -835,7 +697,7 @@
 
   // TODO(rafaelw): It seems wrong to expose DOM at all. This is presently
   // needed to get sizing info.
-  sky.Node getRoot() => _root;
+  RenderCSS getRoot() => _root;
 
   void _remove() {
     assert(_built != null);
@@ -871,7 +733,7 @@
    * 3) Syncing against an old version
    *      assert(_built == null && old != null)
    */
-  void _sync(UINode old, sky.ParentNode host, sky.Node insertBefore) {
+  void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore) {
     assert(!_defunct);
     assert(_built == null || old == null);
 
@@ -898,15 +760,15 @@
     _built = _syncChild(_built, oldBuilt, host, insertBefore);
     _dirty = false;
     _root = _built._root;
+    assert(_root != null);
   }
 
   void _buildIfDirty() {
     if (!_dirty || _defunct)
       return;
 
-    assert(_host != null);
     _trace('$_key rebuilding...');
-    _sync(null, _host, _insertionPoint);
+    _sync(null, null, null); // TODO(ianh): figure out how passing "null, null, null" here is ok
   }
 
   void scheduleBuild() {
@@ -927,11 +789,18 @@
 }
 
 abstract class App extends Component {
-  sky.Node _host;
+  RenderCSS _host;
 
   App() : super(stateful: true) {
-    _host = sky.document.createElement('div');
-    sky.document.appendChild(_host);
+    _host = new RenderCSSRoot(this);
     _scheduleComponentForRender(this);
   }
+
+  void _buildIfDirty() {
+    if (!_dirty || _defunct)
+      return;
+
+    _trace('$_key rebuilding...');
+    _sync(null, _host, _root);
+  }
 }
diff --git a/sky/framework/layout.dart b/sky/framework/layout.dart
new file mode 100644
index 0000000..eb5ff2f
--- /dev/null
+++ b/sky/framework/layout.dart
@@ -0,0 +1,406 @@
+library layout;
+
+import 'node.dart';
+import 'dart:sky' as sky;
+import 'dart:collection';
+
+// UTILS
+
+// Bridge to legacy CSS-like style specification
+// Eventually we'll replace this with something else
+class Style {
+  final String _className;
+  static final Map<String, Style> _cache = new HashMap<String, Style>();
+
+  static int _nextStyleId = 1;
+
+  static String _getNextClassName() { return "style${_nextStyleId++}"; }
+
+  Style extend(Style other) {
+    var className = "$_className ${other._className}";
+
+    return _cache.putIfAbsent(className, () {
+      return new Style._internal(className);
+    });
+  }
+
+  factory Style(String styles) {
+    return _cache.putIfAbsent(styles, () {
+      var className = _getNextClassName();
+      sky.Element styleNode = sky.document.createElement('style');
+      styleNode.setChild(new sky.Text(".$className { $styles }"));
+      sky.document.appendChild(styleNode);
+      return new Style._internal(className);
+    });
+  }
+
+  Style._internal(this._className);
+}
+
+class Rect {
+  const Rect(this.x, this.y, this.width, this.height);
+  final double x;
+  final double y;
+  final double width;
+  final double height;
+}
+
+
+// ABSTRACT LAYOUT
+
+class ParentData {
+  void detach() {
+    detachSiblings();
+  }
+  void detachSiblings() { } // workaround for lack of inter-class mixins in Dart
+}
+
+abstract class RenderNode extends Node {
+
+  // LAYOUT
+
+  // parentData is only for use by the RenderNode that actually lays this
+  // node out, and any other nodes who happen to know exactly what
+  // kind of node that is.
+  ParentData parentData;
+  void setupPos(RenderNode child) {
+    // override this to setup .parentData correctly for your class
+    if (child.parentData is! ParentData)
+      child.parentData = new ParentData();
+  }
+
+  void setAsChild(RenderNode child) { // only for use by subclasses
+    // call this whenever you decide a node is a child
+    assert(child != null);
+    setupPos(child);
+    super.setAsChild(child);
+  }
+  void dropChild(RenderNode child) { // only for use by subclasses
+    assert(child != null);
+    assert(child.parentData != null);
+    child.parentData.detach();
+    super.dropChild(child);
+  }
+
+}
+
+abstract class RenderBox extends RenderNode { }
+
+
+// GENERIC MIXIN FOR RENDER NODES THAT TAKE A LIST OF CHILDREN
+
+abstract class ContainerParentDataMixin<ChildType extends RenderNode> {
+  ChildType previousSibling;
+  ChildType nextSibling;
+  void detachSiblings() {
+    if (previousSibling != null) {
+      assert(previousSibling.parentData is ContainerParentDataMixin<ChildType>);
+      assert(previousSibling != this);
+      assert(previousSibling.parentData.nextSibling == this);
+      previousSibling.parentData.nextSibling = nextSibling;
+    }
+    if (nextSibling != null) {
+      assert(nextSibling.parentData is ContainerParentDataMixin<ChildType>);
+      assert(nextSibling != this);
+      assert(nextSibling.parentData.previousSibling == this);
+      nextSibling.parentData.previousSibling = previousSibling;
+    }
+    previousSibling = null;
+    nextSibling = null;
+  }
+}
+
+abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentDataType extends ContainerParentDataMixin<ChildType>> implements RenderNode {
+  // abstract class that has only InlineNode children
+
+  bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) {
+    assert(child.parentData is ParentDataType);
+    while (child.parentData.previousSibling != null) {
+      assert(child.parentData.previousSibling != child);
+      child = child.parentData.previousSibling;
+      assert(child.parentData is ParentDataType);
+    }
+    return child == equals;
+  }
+  bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) {
+    assert(child.parentData is ParentDataType);
+    while (child.parentData.nextSibling != null) {
+      assert(child.parentData.nextSibling != child);
+      child = child.parentData.nextSibling;
+      assert(child.parentData is ParentDataType);
+    }
+    return child == equals;
+  }
+
+  ChildType _firstChild;
+  ChildType _lastChild;
+  void add(ChildType child, { ChildType before }) {
+    assert(child != this);
+    assert(before != this);
+    assert(child != before);
+    assert(child != _firstChild);
+    assert(child != _lastChild);
+    setAsChild(child);
+    assert(child.parentData is ParentDataType);
+    assert(child.parentData.nextSibling == null);
+    assert(child.parentData.previousSibling == null);
+    if (before == null) {
+      // append at the end (_lastChild)
+      child.parentData.previousSibling = _lastChild;
+      if (_lastChild != null) {
+        assert(_lastChild.parentData is ParentDataType);
+        _lastChild.parentData.nextSibling = child;
+      }
+      _lastChild = child;
+      if (_firstChild == null)
+        _firstChild = child;
+    } else {
+      assert(_firstChild != null);
+      assert(_lastChild != null);
+      assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild));
+      assert(_debugUltimateNextSiblingOf(before, equals: _lastChild));
+      assert(before.parentData is ParentDataType);
+      if (before.parentData.previousSibling == null) {
+        // insert at the start (_firstChild); we'll end up with two or more children
+        assert(before == _firstChild);
+        child.parentData.nextSibling = before;
+        before.parentData.previousSibling = child;
+        _firstChild = child;
+      } else {
+        // insert in the middle; we'll end up with three or more children
+        // set up links from child to siblings
+        child.parentData.previousSibling = before.parentData.previousSibling;
+        child.parentData.nextSibling = before;
+        // set up links from siblings to child
+        assert(child.parentData.previousSibling.parentData is ParentDataType);
+        assert(child.parentData.nextSibling.parentData is ParentDataType);
+        child.parentData.previousSibling.parentData.nextSibling = child;
+        child.parentData.nextSibling.parentData.previousSibling = child;
+        assert(before.parentData.previousSibling == child);
+      }
+    }
+    markNeedsLayout();
+  }
+  void remove(ChildType child) {
+    assert(child.parentData is ParentDataType);
+    assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild));
+    assert(_debugUltimateNextSiblingOf(child, equals: _lastChild));
+    if (child.parentData.previousSibling == null) {
+      assert(_firstChild == child);
+      _firstChild = child.parentData.nextSibling;
+    } else {
+      assert(child.parentData.previousSibling.parentData is ParentDataType);
+      child.parentData.previousSibling.parentData.nextSibling = child.parentData.nextSibling;
+    }
+    if (child.parentData.nextSibling == null) {
+      assert(_lastChild == child);
+      _lastChild = child.parentData.previousSibling;
+    } else {
+      assert(child.parentData.nextSibling.parentData is ParentDataType);
+      child.parentData.nextSibling.parentData.previousSibling = child.parentData.previousSibling;
+    }
+    child.parentData.previousSibling = null;
+    child.parentData.nextSibling = null;
+    dropChild(child);
+    markNeedsLayout();
+  }
+  void redepthChildren() {
+    ChildType child = _firstChild;
+    while (child != null) {
+      redepthChild(child);
+      assert(child.parentData is ParentDataType);
+      child = child.parentData.nextSibling;
+    }
+  }
+  void attachChildren() {
+    ChildType child = _firstChild;
+    while (child != null) {
+      child.attach();
+      assert(child.parentData is ParentDataType);
+      child = child.parentData.nextSibling;
+    }
+  }
+  void detachChildren() {
+    ChildType child = _firstChild;
+    while (child != null) {
+      child.detach();
+      assert(child.parentData is ParentDataType);
+      child = child.parentData.nextSibling;
+    }
+  }
+
+  ChildType get firstChild => _firstChild;
+  ChildType get lastChild => _lastChild;
+  ChildType childAfter(ChildType child) {
+    assert(child.parentData is ParentDataType);
+    return child.parentData.nextSibling;
+  }
+
+}
+
+
+// CSS SHIMS
+
+abstract class RenderCSS extends RenderBox {
+
+  dynamic debug;
+  sky.Element _skyElement;
+
+  RenderCSS(this.debug) {
+    _skyElement = createSkyElement();
+    registerEventTarget(_skyElement, this);
+  }
+
+  sky.Element createSkyElement();
+
+  void updateStyles(List<Style> styles) {
+    _skyElement.setAttribute('class', styles.map((s) => s._className).join(' '));
+  }
+
+  void updateInlineStyle(String newStyle) {
+    _skyElement.setAttribute('style', newStyle);
+  }
+
+  double get width {
+    sky.ClientRect rect = _skyElement.getBoundingClientRect();
+    return rect.width;
+  }
+
+  double get height {
+    sky.ClientRect rect = _skyElement.getBoundingClientRect();
+    return rect.height;
+  }
+
+  Rect get rect {
+    sky.ClientRect rect = _skyElement.getBoundingClientRect();
+    return new Rect(rect.left, rect.top, rect.width, rect.height);
+  }
+
+}
+
+class CSSParentData extends ParentData with ContainerParentDataMixin<RenderCSS> { }
+
+class RenderCSSContainer extends RenderCSS with ContainerRenderNodeMixin<RenderCSS, CSSParentData> {
+
+  RenderCSSContainer(debug) : super(debug);
+
+  void setupPos(RenderNode child) {
+    if (child.parentData is! CSSParentData)
+      child.parentData = new CSSParentData();
+  }
+
+  sky.Element createSkyElement() => sky.document.createElement('div')
+                                               ..setAttribute('debug', debug.toString());
+
+  void markNeedsLayout() { }
+
+  void add(RenderCSS child, { RenderCSS before }) {
+    if (before != null) {
+      assert(before._skyElement.parentNode != null);
+      assert(before._skyElement.parentNode == _skyElement);
+    }
+    super.add(child, before: before);
+    if (before != null) {
+      before._skyElement.insertBefore([child._skyElement]);
+      assert(child._skyElement.parentNode != null);
+      assert(child._skyElement.parentNode == _skyElement);
+      assert(child._skyElement.parentNode == before._skyElement.parentNode);
+    } else {
+      _skyElement.appendChild(child._skyElement);
+    }    
+  }
+  void remove(RenderCSS child) {
+    child._skyElement.remove();
+    super.remove(child);
+  }
+
+}
+
+class RenderCSSText extends RenderCSS {
+
+  RenderCSSText(debug, String newData) : super(debug) {
+    data = newData;
+  }
+
+  static final Style _displayParagraph = new Style('display:paragraph');
+
+  sky.Element createSkyElement() {
+    return sky.document.createElement('div')
+                      ..setChild(new sky.Text())
+                      ..setAttribute('class', _displayParagraph._className)
+                      ..setAttribute('debug', debug.toString());
+  }
+
+  void set data (String value) {
+    (_skyElement.firstChild as sky.Text).data = value;
+  }
+
+}
+
+class RenderCSSImage extends RenderCSS {
+
+  RenderCSSImage(debug, String src, num width, num height) : super(debug) {
+    configure(src, width, height);
+  }
+
+  sky.Element createSkyElement() {
+    return sky.document.createElement('img')
+                      ..setAttribute('debug', debug.toString());
+  }
+
+  void configure(String src, num width, num height) {
+    if (_skyElement.getAttribute('src') != src)
+      _skyElement.setAttribute('src', src);
+    _skyElement.style['width'] = '${width}px';
+    _skyElement.style['height'] = '${height}px';
+  }
+
+}
+
+class RenderCSSRoot extends RenderCSSContainer {
+  RenderCSSRoot(debug) : super(debug);
+  sky.Element createSkyElement() {
+    var result = super.createSkyElement();
+    assert(result != null);
+    sky.document.appendChild(result);
+    return result;
+  }
+}
+
+
+// legacy tools
+Map<sky.EventTarget, RenderNode> _eventTargetRegistry = {};
+void registerEventTarget(sky.EventTarget e, RenderNode n) {
+  _eventTargetRegistry[e] = n;
+}
+RenderNode bridgeEventTargetToRenderNode(sky.EventTarget e) {
+  return _eventTargetRegistry[e];
+}
+
+
+
+
+String _attributes(node) {
+  if (node is! sky.Element) return '';
+  var result = '';
+  var attrs = node.getAttributes();
+  for (var attr in attrs)
+    result += ' ${attr.name}="${attr.value}"';
+  return result;
+}
+
+void _serialiseDOM(node, [String prefix = '']) {
+  if (node is sky.Text) {
+    print(prefix + 'text: "' + node.data.replaceAll('\n', '\\n') + '"');
+    return;
+  }
+  print(prefix + node.toString() + _attributes(node));
+  var children = node.getChildNodes();
+  prefix = prefix + '  ';
+  for (var child in children)
+    _serialiseDOM(child, prefix);
+}
+
+void dumpState() {
+  _serialiseDOM(sky.document);
+}
diff --git a/sky/framework/node.dart b/sky/framework/node.dart
new file mode 100644
index 0000000..ec9a1f9
--- /dev/null
+++ b/sky/framework/node.dart
@@ -0,0 +1,60 @@
+library node;
+
+class Node {
+
+  // Nodes always have a 'depth' greater than their ancestors'.
+  // There's no guarantee regarding depth between siblings. The depth
+  // of a node is used to ensure that nodes are processed in depth
+  // order. The 'depth' of a child can be more than one greater than
+  // the 'depth' of the parent, because the 'depth' values are never
+  // decreased: all that matters is that it's greater than the parent.
+  // Consider a tree with a root node A, a child B, and a grandchild
+  // C. Initially, A will have 'depth' 0, B 'depth' 1, and C 'depth'
+  // 2. If C is moved to be a child of A, sibling of B, then the
+  // numbers won't change. C's 'depth' will still be 2.
+
+  int _depth = 0;
+  int get depth => _depth;
+  void redepthChild(Node child) { // internal, do not call
+    assert(child._attached == _attached);
+    if (child._depth <= _depth) {
+      child._depth = _depth + 1;
+      child.redepthChildren();
+    }
+  }
+  void redepthChildren() { // internal, do not call
+    // override this in subclasses with child nodes
+    // simply call redepthChild(child) for each child
+  }
+
+  bool _attached = false;
+  bool get attached => _attached;
+  void attach() {
+    // override this in subclasses with child nodes
+    // simply call attach() for each child then call your superclass
+    _attached = true;
+    attachChildren();
+  }
+  attachChildren() { } // workaround for lack of inter-class mixins in Dart
+  void detach() {
+    // override this in subclasses with child nodes
+    // simply call detach() for each child then call your superclass
+    _attached = false;
+    detachChildren();
+  }
+  detachChildren() { } // workaround for lack of inter-class mixins in Dart
+
+  void setAsChild(Node child) { // only for use by subclasses
+    assert(child != null);
+    if (attached)
+      child.attach();
+    redepthChild(child);
+  }
+  void dropChild(Node child) { // only for use by subclasses
+    assert(child != null);
+    assert(child.attached == attached);
+    if (attached)
+      child.detach();
+  }
+
+}
diff --git a/sky/framework/theme/view-configuration.dart b/sky/framework/theme/view_configuration.dart
similarity index 100%
rename from sky/framework/theme/view-configuration.dart
rename to sky/framework/theme/view_configuration.dart
diff --git a/sky/sdk/README.md b/sky/sdk/README.md
index b28384e..eca9cf0 100644
--- a/sky/sdk/README.md
+++ b/sky/sdk/README.md
@@ -1,3 +1,14 @@
+Contributing
+============
+
+[sky_sdk](https://github.com/domokit/sky_sdk) is generated from the
+[mojo repository](https://github.com/domokit/mojo) using
+[deploy_sdk.py](https://github.com/domokit/mojo/blob/master/sky/tools/deploy_sdk.py)
+Static files (including this README.md) are located under
+[sky/sdk](https://github.com/domokit/mojo/tree/master/sky/sdk).  Pull
+requests and issue reports are glady accepted at the
+[mojo repository](https://github.com/domokit/mojo)!
+
 Sky
 ===
 
@@ -77,6 +88,9 @@
 with the hierarchy returned last frame. Any differences are then applied as
 mutations to the physical hierarchy retained by the engine.
 
+Skip down to "Running a Sky application" to learn how to load and run this
+example on your device.
+
 For examples, please see the [examples directory](examples/).
 
 Services
@@ -139,9 +153,10 @@
 
 2. ``pub get`` to set up a copy of the sky package in the app directory.
 
-3. ``./packages/sky/sky_tool start --install``
-   The --install flag is only necessary to install SkyDemo.apk if not already
-   installed on the device.
+3. ``./packages/sky/sky_tool start`` to start the dev server and upload your
+   app to the device.
+   (NOTE: add a ``--install`` flag to install SkyDemo.apk if not already
+   installed on the device.)
 
 4. Use ``adb logcat`` to view any errors or Dart print() output from the app.
    ``adb logcat -s chromium`` can be used to filter only adb messages from
diff --git a/sky/tools/deploy_sdk.py b/sky/tools/deploy_sdk.py
index c510afe..838afe8 100755
--- a/sky/tools/deploy_sdk.py
+++ b/sky/tools/deploy_sdk.py
@@ -174,10 +174,27 @@
     # Mojo package, lots of overlap with gen, must be copied:
     copy(src_path('mojo/public'), sdk_path('packages/mojo/lib/public'),
         dart_filter)
-    mojom_dirs = [ os.path.join(build_dir, 'gen/dart-gen/mojom') ]
-    mojom_dirs += args.extra_mojom_dirs
-    for mojom_dir in mojom_dirs:
-      copy(mojom_dir, sdk_path('packages/mojom/lib/'), gen_filter)
+
+    # By convention the generated .mojom.dart files in a pub package
+    # go under $PACKAGE/lib/mojom.
+    # The mojo package owns all the .mojom.dart files that are not in the 'sky'
+    # mojom module.
+    def non_sky_gen_filter(path):
+        if os.path.isdir(path) and path.endswith('sky'):
+            return False
+        return gen_filter(path)
+    mojo_package_mojom_dir = sdk_path('packages/mojo/lib/mojom')
+    copy(os.path.join(build_dir, 'gen/dart-gen/mojom'), mojo_package_mojom_dir,
+         non_sky_gen_filter)
+
+    # The Sky package owns the .mojom.dart files in the 'sky' mojom module.
+    def sky_gen_filter(path):
+        if os.path.isfile(path) and not os.path.dirname(path).endswith('sky'):
+            return False
+        return gen_filter(path)
+    sky_package_mojom_dir = sdk_path('packages/sky/lib/mojom')
+    copy(os.path.join(build_dir, 'gen/dart-gen/mojom'), sky_package_mojom_dir,
+         sky_gen_filter)
 
     # Mojo SDK additions:
     copy_or_link(src_path('mojo/public/dart/bindings.dart'),
@@ -205,11 +222,14 @@
         ensure_dir_exists(packages_dir)
         make_relative_symlink(sdk_path('packages/mojo/lib'),
             os.path.join(packages_dir, 'mojo'))
-        make_relative_symlink(sdk_path('packages/mojom/lib'),
-            os.path.join(packages_dir, 'mojom'))
         make_relative_symlink(sdk_path('packages/sky/lib'),
             os.path.join(packages_dir, 'sky'))
 
+        mojom_dirs = [ mojo_package_mojom_dir, sky_package_mojom_dir ]
+        mojom_dirs += args.extra_mojom_dirs
+        for mojom_dir in mojom_dirs:
+          copy(mojom_dir, os.path.join(packages_dir, 'mojom'), gen_filter)
+
     if should_commit:
         # Kinda a hack to make a prettier build dir for the commit:
         script_path = os.path.relpath(os.path.abspath(__file__), SRC_ROOT)
diff --git a/sky/tools/webkitpy/layout_tests/controllers/single_test_runner.py b/sky/tools/webkitpy/layout_tests/controllers/single_test_runner.py
index 31d4d8c..56af871 100644
--- a/sky/tools/webkitpy/layout_tests/controllers/single_test_runner.py
+++ b/sky/tools/webkitpy/layout_tests/controllers/single_test_runner.py
@@ -307,6 +307,10 @@
             failures.append(test_failures.FailureMissingAudio())
         return failures
 
+    # FIXME: This won't be needed once we have flags for Sky that suppress the
+    # Observatory messages.
+    _filter_observatory_messages = re.compile(r"^CONSOLE: Observatory listening on.*\n", re.MULTILINE)
+
     def _get_normalized_output_text(self, output):
         """Returns the normalized text output, i.e. the output in which
         the end-of-line characters are normalized to "\n"."""
@@ -314,7 +318,9 @@
         # changed to "\r\n" by our system (Python/Cygwin), resulting in
         # "\r\r\n", when, in fact, we wanted to compare the text output with
         # the normalized text expectation files.
-        return output.replace("\r\r\n", "\r\n").replace("\r\n", "\n")
+        normalized_lines = output.replace("\r\r\n", "\r\n").replace("\r\n", "\n")
+        normalized_lines = re.sub(self._filter_observatory_messages, r"", normalized_lines)
+        return normalized_lines
 
     # FIXME: This function also creates the image diff. Maybe that work should
     # be handled elsewhere?
diff --git a/ui/gl/BUILD.gn b/ui/gl/BUILD.gn
index 04dea9c..99e03a1 100644
--- a/ui/gl/BUILD.gn
+++ b/ui/gl/BUILD.gn
@@ -207,10 +207,6 @@
       "//ui/ozone:ozone_base",
     ]
   }
-
-  if (is_android) {
-    deps += [ "//ui/android:ui_java" ]
-  }
 }
 
 source_set("gl_unittest_utils") {