Import Cobalt 20.master.0.243969

Includes the following patches:
  https://cobalt-review.googlesource.com/c/cobalt/+/5610
    by r97922153@gmail.com
diff --git a/src/base/base.gyp b/src/base/base.gyp
index e442340..5a7fb5d 100644
--- a/src/base/base.gyp
+++ b/src/base/base.gyp
@@ -698,6 +698,7 @@
       'defines': [
         'GMOCK_NO_MOVE_MOCK',
         'STARBOARD_OLD_ICU',
+        'BASE_DONT_ENFORCE_THREAD_NAME_LENGTH',
       ],
       'sources': [
         'at_exit_unittest.cc',
diff --git a/src/base/base_paths_starboard.cc b/src/base/base_paths_starboard.cc
index 275c183..2cd0d46 100644
--- a/src/base/base_paths_starboard.cc
+++ b/src/base/base_paths_starboard.cc
@@ -15,6 +15,7 @@
 #include "base/base_paths.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
+#include "starboard/configuration_constants.h"
 #include "starboard/system.h"
 
 namespace base {
@@ -22,15 +23,15 @@
 // This is where we can control the path for placement of a lot of file
 // resources for cobalt.
 bool PathProviderStarboard(int key, FilePath *result) {
-  char path[SB_FILE_MAX_PATH] = {0};
+  std::vector<char> path(kSbFileMaxPath, 0);
   switch (key) {
     case base::FILE_EXE:
     case base::FILE_MODULE: {
-      bool success = SbSystemGetPath(kSbSystemPathExecutableFile, path,
-                      SB_ARRAY_SIZE_INT(path));
+      bool success = SbSystemGetPath(kSbSystemPathExecutableFile, path.data(),
+                                     path.size());
       DCHECK(success);
       if (success) {
-        *result = FilePath(path);
+        *result = FilePath(path.data());
         return true;
       }
       DLOG(ERROR) << "FILE_EXE not defined.";
@@ -40,11 +41,11 @@
     case base::DIR_EXE:
     case base::DIR_MODULE:
     case base::DIR_ASSETS: {
-      bool success = SbSystemGetPath(kSbSystemPathContentDirectory, path,
-                                     SB_ARRAY_SIZE_INT(path));
+      bool success = SbSystemGetPath(kSbSystemPathContentDirectory, path.data(),
+                                     path.size());
       DCHECK(success);
       if (success) {
-        *result = FilePath(path);
+        *result = FilePath(path.data());
         return true;
       }
       DLOG(ERROR) << "DIR_EXE/DIR_MODULE not defined.";
@@ -53,12 +54,12 @@
 
 #if defined(ENABLE_TEST_DATA)
     case base::DIR_TEST_DATA: {
-      bool success = SbSystemGetPath(kSbSystemPathContentDirectory, path,
-                                     SB_ARRAY_SIZE_INT(path));
+      bool success = SbSystemGetPath(kSbSystemPathContentDirectory, path.data(),
+                                     path.size());
       DCHECK(success);
       if (success) {
         // Append "test" to match the output of copy_test_data.gypi
-        *result = FilePath(path).Append(FILE_PATH_LITERAL("test"));
+        *result = FilePath(path.data()).Append(FILE_PATH_LITERAL("test"));
         return true;
       }
       DLOG(ERROR) << "DIR_TEST_DATA not defined.";
@@ -67,10 +68,10 @@
 #endif  // ENABLE_TEST_DATA
 
     case base::DIR_CACHE: {
-      bool success = SbSystemGetPath(kSbSystemPathCacheDirectory, path,
-                                     SB_ARRAY_SIZE_INT(path));
+      bool success = SbSystemGetPath(kSbSystemPathCacheDirectory, path.data(),
+                                     path.size());
       if (success) {
-        *result = FilePath(path);
+        *result = FilePath(path.data());
         return true;
       }
       DLOG(INFO) << "DIR_CACHE not defined.";
@@ -78,11 +79,11 @@
     }
 
     case base::DIR_TEMP: {
-      bool success = SbSystemGetPath(kSbSystemPathTempDirectory, path,
-                                     SB_ARRAY_SIZE_INT(path));
+      bool success =
+          SbSystemGetPath(kSbSystemPathTempDirectory, path.data(), path.size());
       DCHECK(success);
       if (success) {
-        *result = FilePath(path);
+        *result = FilePath(path.data());
         return true;
       }
       DLOG(ERROR) << "DIR_TEMP not defined.";
@@ -94,18 +95,18 @@
       return PathProviderStarboard(base::DIR_CACHE, result);
 
     case base::DIR_SYSTEM_FONTS:
-      if (SbSystemGetPath(kSbSystemPathFontDirectory, path,
-                          SB_ARRAY_SIZE_INT(path))) {
-        *result = FilePath(path);
+      if (SbSystemGetPath(kSbSystemPathFontDirectory, path.data(),
+                          path.size())) {
+        *result = FilePath(path.data());
         return true;
       }
       DLOG(INFO) << "DIR_SYSTEM_FONTS not defined.";
       return false;
 
     case base::DIR_SYSTEM_FONTS_CONFIGURATION:
-      if (SbSystemGetPath(kSbSystemPathFontConfigurationDirectory, path,
-                          SB_ARRAY_SIZE_INT(path))) {
-        *result = FilePath(path);
+      if (SbSystemGetPath(kSbSystemPathFontConfigurationDirectory, path.data(),
+                          path.size())) {
+        *result = FilePath(path.data());
         return true;
       }
       DLOG(INFO) << "DIR_SYSTEM_FONTS_CONFIGURATION not defined.";
diff --git a/src/base/bind_internal.h b/src/base/bind_internal.h
index 3ce3cab..8f38935 100644
--- a/src/base/bind_internal.h
+++ b/src/base/bind_internal.h
@@ -10,11 +10,11 @@
 
 #include "base/callback_internal.h"
 #include "base/compiler_specific.h"
-#include "base/cpp14oncpp11.h"
 #include "base/memory/raw_scoped_refptr_mismatch_checker.h"
 #include "base/memory/weak_ptr.h"
 #include "base/template_util.h"
 #include "build/build_config.h"
+#include "nb/cpp14oncpp11.h"
 
 #if defined(OS_MACOSX) && !HAS_FEATURE(objc_arc)
 #include "base/mac/scoped_block.h"
diff --git a/src/base/containers/flat_map.h b/src/base/containers/flat_map.h
index 1d25e27..4f0de16 100644
--- a/src/base/containers/flat_map.h
+++ b/src/base/containers/flat_map.h
@@ -10,9 +10,9 @@
 #include <utility>
 
 #include "base/containers/flat_tree.h"
-#include "base/cpp14oncpp11.h"
 #include "base/logging.h"
 #include "base/template_util.h"
+#include "nb/cpp14oncpp11.h"
 
 namespace base {
 
diff --git a/src/base/containers/flat_tree.h b/src/base/containers/flat_tree.h
index ee8adf1..bca6cbd 100644
--- a/src/base/containers/flat_tree.h
+++ b/src/base/containers/flat_tree.h
@@ -10,8 +10,8 @@
 #include <type_traits>
 #include <vector>
 
-#include "base/cpp14oncpp11.h"
 #include "base/template_util.h"
+#include "nb/cpp14oncpp11.h"
 
 namespace base {
 
diff --git a/src/base/containers/span.h b/src/base/containers/span.h
index 6d7e77d..ddcd8a0 100644
--- a/src/base/containers/span.h
+++ b/src/base/containers/span.h
@@ -18,9 +18,9 @@
 #include <type_traits>
 #include <utility>
 
-#include "base/cpp14oncpp11.h"
 #include "base/logging.h"
 #include "base/stl_util.h"
+#include "nb/cpp14oncpp11.h"
 
 namespace base {
 
diff --git a/src/base/containers/span_unittest.cc b/src/base/containers/span_unittest.cc
index 69cd88b..9123743 100644
--- a/src/base/containers/span_unittest.cc
+++ b/src/base/containers/span_unittest.cc
@@ -9,8 +9,8 @@
 #include <string>
 #include <vector>
 
-#include "base/cpp14oncpp11.h"
 #include "base/macros.h"
+#include "nb/cpp14oncpp11.h"
 #include "starboard/types.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/src/base/cpp14oncpp11.h b/src/base/cpp14oncpp11.h
deleted file mode 100644
index ae23cd3..0000000
--- a/src/base/cpp14oncpp11.h
+++ /dev/null
@@ -1,221 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_CPP14ONCPP11_H_
-#define BASE_CPP14ONCPP11_H_
-
-#include <memory>
-
-#include "starboard/configuration.h"
-
-#if defined(STARBOARD)
-
-// This file contains a collection of implementations of C++14 std classes
-// and functions, if possible.  If it is not possible to reproduce C++14
-// functionality, this class provides facilities to help with porting.
-
-#if __cplusplus < 201402L
-// Starboard is currently targetting C++11, not C++14, so unfortunately we
-// do away with constexpr usage.
-#define CONSTEXPR
-// This type of STATIC_ASSERT is only used in unit tests so far for convenience
-// purpose.
-#define STATIC_ASSERT(value, message) EXPECT_TRUE(value)
-// In classes like base::span constexpr is desired while CHECK() is the only
-// obstacle, disabling CHECK in C++11 version is preferred.
-#define CHECK14(expr)
-#else
-#define CONSTEXPR constexpr
-#define STATIC_ASSERT(value, message) static_assert(value, message)
-#define CHECK14(expr) CHECK(expr)
-#endif
-
-#if __cplusplus < 201402L
-namespace {
-template <int Index, class TargetType, class SkipType, class... Types>
-struct get_internal {
-  typedef typename get_internal<Index + 1, TargetType, Types...>::type type;
-  // The line above will expand until TargetType is found,
-  // Only the index of the get_internal not expanded will return.
-  static constexpr int index = Index;
-};
-
-template <int Index, class TargetType, class... Types>
-struct get_internal<Index, TargetType, TargetType, Types...> {
-  typedef get_internal type;
-  // This line is for the case that first type matches TargetType.
-  static constexpr int index = Index;
-};
-}  // namespace
-
-namespace std {
-// 201402L is the official C++14 standard version. But some platforms are
-// capable of some C++14 features, has 201300 as their C++ version.
-#if __cplusplus < 201300L
-
-#if !defined(SB_IS_COMPILER_MSVC)
-template <class TargetType, class... Types>
-TargetType get(std::tuple<Types...> tuple) {
-  return std::get<get_internal<0, TargetType, Types...>::type::index>(tuple);
-}
-
-template <size_t... Ints>
-class index_sequence {};
-#endif
-
-namespace detail {
-template <size_t N>
-class GetSequenceHelper {
- public:
-  template <typename T>
-  struct AddIndex {};
-  template <size_t... Ints>
-  struct AddIndex<index_sequence<Ints...>> {
-    typedef index_sequence<Ints..., N - 1> type;
-  };
-
-  typedef typename AddIndex<typename GetSequenceHelper<N - 1>::type>::type type;
-};
-
-template <>
-class GetSequenceHelper<0> {
- public:
-  typedef index_sequence<> type;
-};
-
-
-}  // namespace detail
-
-template <class T>
-struct type_teller {
-  typedef std::unique_ptr<T> object_type;
-};
-
-template <class T>
-struct type_teller<T[]> {
-  typedef std::unique_ptr<T[]> array_type;
-};
-
-#if !defined(SB_IS_COMPILER_MSVC)
-template <typename T, typename... Args>
-typename type_teller<T>::object_type make_unique(Args&&... args) {
-  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
-}
-
-template <typename T>
-typename type_teller<T>::array_type make_unique(size_t n) {
-  typedef typename remove_extent<T>::type underlying_type;
-  return std::unique_ptr<T>(new underlying_type[n]());
-}
-
-// In C++14, less<void> has a special meaning and defines the is_transparent
-// member meaning that we can compare heterogeneous types (T and U).
-template <> struct less<void> {
-    template <class T, class U> auto operator()(T&& t, U&& u) const
-    -> decltype(std::forward<T>(t) < std::forward<U>(u)) {
-      return t < u;
-    }
-
-    typedef int is_transparent;
-};
-
-template <size_t N>
-using make_index_sequence = typename detail::GetSequenceHelper<N>::type;
-
-template<typename T>
-using decay_t = typename decay<T>::type;
-#endif
-
-template<bool B, typename T, typename F>
-using conditional_t = typename conditional<B,T,F>::type;
-
-template<bool B, typename T = void>
-using enable_if_t = typename enable_if<B,T>::type;
-
-template <std::size_t I, typename T>
-using tuple_element_t = typename tuple_element<I, T>::type;
-
-template<typename T>
-using remove_reference_t = typename remove_reference<T>::type;
-
-template<typename T>
-using remove_pointer_t = typename remove_pointer<T>::type;
-
-template<typename T>
-using remove_cv_t = typename remove_cv<T>::type;
-
-template<typename T>
-using remove_const_t = typename remove_const<T>::type;
-
-template<typename T>
-using remove_volatile_t = typename remove_volatile<T>::type;
-#endif  // __cplusplus < 201300L
-
-#if !defined(SB_IS_COMPILER_MSVC)
-template<typename T>
-using add_cv_t = typename add_cv<T>::type;
-
-template<typename T>
-using add_const_t = typename add_const<T>::type;
-
-template<typename T>
-using add_volatile_t = typename add_volatile<T>::type;
-
-template< class C >
-auto rbegin( C& c ) -> decltype(c.rbegin()) {
-  return c.rbegin();
-}
-
-template< class C >
-auto rbegin( const C& c ) -> decltype(c.rbegin()) {
-  return c.rbegin();
-}
-
-template< class T, size_t N >
-reverse_iterator<T*> rbegin( T (&array)[N] ) {
-  return reverse_iterator<T*>(array + N);
-}
-
-template <class C>
-constexpr auto cbegin(const C& c) -> decltype(std::begin(c)) {
-  return std::begin(c);
-}
-
-template< class C >
-auto crbegin( const C& c ) -> decltype(std::rbegin(c)) {
-  return std::rbegin(c);
-}
-
-template< class C >
-auto rend( C& c ) -> decltype(c.rend()) {
-  return c.rend();
-}
-
-template< class C >
-auto rend( const C& c ) -> decltype(c.rend()) {
-  return c.rend();
-}
-
-template< class T, size_t N >
-reverse_iterator<T*> rend( T (&array)[N] ) {
-  return reverse_iterator<T*>(array);
-}
-
-template< class C >
-auto crend( const C& c ) -> decltype(std::rend(c)) {
-  return std::rend(c);
-}
-
-template <class C>
-constexpr auto cend(const C& c) -> decltype(std::end(c)) {
-  return std::end(c);
-}
-#endif
-
-}  // namespace std
-#endif
-
-#endif  // defined(STARBOARD)
-
-#endif  // BASE_CPP14ONCPP11_H_
diff --git a/src/base/files/file_enumerator_starboard.cc b/src/base/files/file_enumerator_starboard.cc
index 70e9590..c346707 100644
--- a/src/base/files/file_enumerator_starboard.cc
+++ b/src/base/files/file_enumerator_starboard.cc
@@ -17,6 +17,7 @@
 #include "base/files/file_util.h"
 #include "base/threading/thread_restrictions.h"
 #include "starboard/common/string.h"
+#include "starboard/configuration_constants.h"
 #include "starboard/directory.h"
 #include "starboard/file.h"
 #include "starboard/memory.h"
@@ -112,7 +113,7 @@
   bool found_dot_dot = false;
 
 #if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
-  std::vector<char> entry(SB_FILE_MAX_NAME);
+  std::vector<char> entry(kSbFileMaxName);
 
   while (SbDirectoryGetNext(dir, entry.data(), entry.size())) {
     const char dot_dot_str[] = "..";
diff --git a/src/base/files/file_util_starboard.cc b/src/base/files/file_util_starboard.cc
index 96f39c2..be80ab1 100644
--- a/src/base/files/file_util_starboard.cc
+++ b/src/base/files/file_util_starboard.cc
@@ -28,6 +28,7 @@
 #include "base/threading/scoped_blocking_call.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
+#include "starboard/configuration_constants.h"
 #include "starboard/directory.h"
 #include "starboard/file.h"
 #include "starboard/system.h"
@@ -249,14 +250,14 @@
 }
 
 bool GetTempDir(FilePath *path) {
-  char buffer[SB_FILE_MAX_PATH + 1] = {0};
-  bool result = SbSystemGetPath(kSbSystemPathTempDirectory, buffer,
-                                SB_ARRAY_SIZE_INT(buffer));
+  std::vector<char> buffer(kSbFileMaxPath + 1, 0);
+  bool result =
+      SbSystemGetPath(kSbSystemPathTempDirectory, buffer.data(), buffer.size());
   if (!result) {
     return false;
   }
 
-  *path = FilePath(buffer);
+  *path = FilePath(buffer.data());
   if (DirectoryExists(*path)) {
     return true;
   }
diff --git a/src/base/logging.cc b/src/base/logging.cc
index 718d52a..dbb5be3 100644
--- a/src/base/logging.cc
+++ b/src/base/logging.cc
@@ -14,6 +14,7 @@
 #include "starboard/common/log.h"
 #include "starboard/common/mutex.h"
 #include "starboard/configuration.h"
+#include "starboard/configuration_constants.h"
 #include "starboard/file.h"
 #include "starboard/system.h"
 #include "starboard/time.h"
@@ -248,11 +249,10 @@
 #if defined(STARBOARD)
   // On Starboard, we politely ask for the log directory, like a civilized
   // platform.
-  char path[SB_FILE_MAX_PATH + 1];
-  SbSystemGetPath(kSbSystemPathDebugOutputDirectory, path,
-                  SB_ARRAY_SIZE_INT(path));
-  PathString log_file = path;
-  log_file += SB_FILE_SEP_STRING "debug.log";
+  std::vector<char> path(kSbFileMaxPath + 1);
+  SbSystemGetPath(kSbSystemPathDebugOutputDirectory, path.data(), path.size());
+  PathString log_file = path.data();
+  log_file += std::string(kSbFileSepString) + "debug.log";
   return log_file;
 #else
 #if defined(OS_WIN)
diff --git a/src/base/memory/ptr_util.h b/src/base/memory/ptr_util.h
index a0809a8..0f61dd7 100644
--- a/src/base/memory/ptr_util.h
+++ b/src/base/memory/ptr_util.h
@@ -8,7 +8,7 @@
 #include <memory>
 #include <utility>
 
-#include "base/cpp14oncpp11.h"
+#include "nb/cpp14oncpp11.h"
 
 namespace base {
 
diff --git a/src/base/memory/scoped_refptr.h b/src/base/memory/scoped_refptr.h
index 7b47e88..e730fdc 100644
--- a/src/base/memory/scoped_refptr.h
+++ b/src/base/memory/scoped_refptr.h
@@ -10,9 +10,9 @@
 #include <utility>
 
 #include "base/compiler_specific.h"
-#include "base/cpp14oncpp11.h"
 #include "base/logging.h"
 #include "base/macros.h"
+#include "nb/cpp14oncpp11.h"
 #include "starboard/types.h"
 
 template <class T>
diff --git a/src/base/numerics/checked_math_impl.h b/src/base/numerics/checked_math_impl.h
index 36599ed..a4562bd 100644
--- a/src/base/numerics/checked_math_impl.h
+++ b/src/base/numerics/checked_math_impl.h
@@ -11,9 +11,9 @@
 #include <limits>
 #include <type_traits>
 
-#include "base/cpp14oncpp11.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/numerics/safe_math_shared_impl.h"
+#include "nb/cpp14oncpp11.h"
 #include "starboard/types.h"
 
 namespace base {
diff --git a/src/base/numerics/safe_math_shared_impl.h b/src/base/numerics/safe_math_shared_impl.h
index 257b2f4..02d04ba 100644
--- a/src/base/numerics/safe_math_shared_impl.h
+++ b/src/base/numerics/safe_math_shared_impl.h
@@ -12,8 +12,8 @@
 #include <limits>
 #include <type_traits>
 
-#include "base/cpp14oncpp11.h"
 #include "base/numerics/safe_conversions.h"
+#include "nb/cpp14oncpp11.h"
 
 // Where available use builtin math overflow support on Clang and GCC.
 #if !defined(__native_client__) &&                         \
diff --git a/src/base/optional.h b/src/base/optional.h
index 804e3d5..005810a 100644
--- a/src/base/optional.h
+++ b/src/base/optional.h
@@ -11,10 +11,10 @@
 #include <new>
 #endif
 
-#include "base/cpp14oncpp11.h"
 #include "base/logging.h"
 #include "base/template_util.h"
 #include "base/thread_annotations.h"
+#include "nb/cpp14oncpp11.h"
 
 namespace base {
 
diff --git a/src/base/optional_unittest.cc b/src/base/optional_unittest.cc
index d514568..4e56b96 100644
--- a/src/base/optional_unittest.cc
+++ b/src/base/optional_unittest.cc
@@ -9,7 +9,7 @@
 #include <string>
 #include <vector>
 
-#include "base/cpp14oncpp11.h"
+#include "nb/cpp14oncpp11.h"
 #include "starboard/configuration.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/src/base/profiler/stack_sampling_profiler.cc b/src/base/profiler/stack_sampling_profiler.cc
index 0db05e9..22788b1 100644
--- a/src/base/profiler/stack_sampling_profiler.cc
+++ b/src/base/profiler/stack_sampling_profiler.cc
@@ -297,7 +297,7 @@
     next_collection_id;
 
 StackSamplingProfiler::SamplingThread::SamplingThread()
-    : Thread("StackSamplingProfiler") {}
+    : Thread("StackSamplProfl") {}
 
 StackSamplingProfiler::SamplingThread::~SamplingThread() = default;
 
diff --git a/src/base/stl_util_unittest.cc b/src/base/stl_util_unittest.cc
index 50dac03..2865b8e 100644
--- a/src/base/stl_util_unittest.cc
+++ b/src/base/stl_util_unittest.cc
@@ -22,9 +22,9 @@
 #include <vector>
 
 #include "base/containers/queue.h"
-#include "base/cpp14oncpp11.h"
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
+#include "nb/cpp14oncpp11.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/src/base/strings/char_traits.h b/src/base/strings/char_traits.h
index 835e79f..5a5d0a4 100644
--- a/src/base/strings/char_traits.h
+++ b/src/base/strings/char_traits.h
@@ -6,7 +6,7 @@
 #define BASE_STRINGS_CHAR_TRAITS_H_
 
 #include "base/compiler_specific.h"
-#include "base/cpp14oncpp11.h"
+#include "nb/cpp14oncpp11.h"
 #include "starboard/types.h"
 
 namespace base {
diff --git a/src/base/strings/char_traits_unittest.cc b/src/base/strings/char_traits_unittest.cc
index 85ea62a..805044c 100644
--- a/src/base/strings/char_traits_unittest.cc
+++ b/src/base/strings/char_traits_unittest.cc
@@ -6,7 +6,7 @@
 #include "base/strings/string16.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-#include "base/cpp14oncpp11.h"
+#include "nb/cpp14oncpp11.h"
 
 namespace base {
 
diff --git a/src/base/strings/pattern.cc b/src/base/strings/pattern.cc
index d691b0f..d71f7dc 100644
--- a/src/base/strings/pattern.cc
+++ b/src/base/strings/pattern.cc
@@ -4,8 +4,8 @@
 
 #include "base/strings/pattern.h"
 
-#include "base/cpp14oncpp11.h"
 #include "base/third_party/icu/icu_utf.h"
+#include "nb/cpp14oncpp11.h"
 
 namespace base {
 
diff --git a/src/base/strings/string_piece.h b/src/base/strings/string_piece.h
index 791a223..5c151ce 100644
--- a/src/base/strings/string_piece.h
+++ b/src/base/strings/string_piece.h
@@ -26,11 +26,11 @@
 #include <string>
 
 #include "base/base_export.h"
-#include "base/cpp14oncpp11.h"
 #include "base/logging.h"
 #include "base/strings/char_traits.h"
 #include "base/strings/string16.h"
 #include "base/strings/string_piece_forward.h"
+#include "nb/cpp14oncpp11.h"
 #include "starboard/types.h"
 
 namespace base {
diff --git a/src/base/strings/string_piece_unittest.cc b/src/base/strings/string_piece_unittest.cc
index feebfc7..498cbd9 100644
--- a/src/base/strings/string_piece_unittest.cc
+++ b/src/base/strings/string_piece_unittest.cc
@@ -4,10 +4,10 @@
 
 #include <string>
 
-#include "base/cpp14oncpp11.h"
 #include "base/strings/string16.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
+#include "nb/cpp14oncpp11.h"
 #include "starboard/types.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/src/base/strings/utf_string_conversions.cc b/src/base/strings/utf_string_conversions.cc
index 6d9b771..1f77ffb 100644
--- a/src/base/strings/utf_string_conversions.cc
+++ b/src/base/strings/utf_string_conversions.cc
@@ -4,12 +4,12 @@
 
 #include "base/strings/utf_string_conversions.h"
 
-#include "base/cpp14oncpp11.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversion_utils.h"
 #include "base/third_party/icu/icu_utf.h"
 #include "build/build_config.h"
+#include "nb/cpp14oncpp11.h"
 #include "starboard/types.h"
 
 namespace base {
diff --git a/src/base/task/lazy_task_runner.h b/src/base/task/lazy_task_runner.h
index bd4683c..237c0d1 100644
--- a/src/base/task/lazy_task_runner.h
+++ b/src/base/task/lazy_task_runner.h
@@ -10,7 +10,6 @@
 #include "base/atomicops.h"
 #include "base/callback.h"
 #include "base/compiler_specific.h"
-#include "base/cpp14oncpp11.h"
 #include "base/lazy_instance_helpers.h"
 #include "base/sequenced_task_runner.h"
 #include "base/single_thread_task_runner.h"
@@ -18,6 +17,7 @@
 #include "base/task/task_scheduler/scheduler_lock.h"
 #include "base/task/task_traits.h"
 #include "build/build_config.h"
+#include "nb/cpp14oncpp11.h"
 
 // Lazy(Sequenced|SingleThread|COMSTA)TaskRunner lazily creates a TaskRunner.
 //
diff --git a/src/base/task/task_scheduler/scheduler_single_thread_task_runner_manager_unittest.cc b/src/base/task/task_scheduler/scheduler_single_thread_task_runner_manager_unittest.cc
index 60529ca..8d4e0b0 100644
--- a/src/base/task/task_scheduler/scheduler_single_thread_task_runner_manager_unittest.cc
+++ b/src/base/task/task_scheduler/scheduler_single_thread_task_runner_manager_unittest.cc
@@ -6,7 +6,6 @@
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
-#include "base/cpp14oncpp11.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/statistics_recorder.h"
 #include "base/synchronization/atomic_flag.h"
@@ -25,6 +24,7 @@
 #include "base/threading/thread.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
+#include "nb/cpp14oncpp11.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 #if defined(OS_WIN)
diff --git a/src/base/task/task_scheduler/scheduler_worker_pool_impl.cc b/src/base/task/task_scheduler/scheduler_worker_pool_impl.cc
index da61163..f3a3f94 100644
--- a/src/base/task/task_scheduler/scheduler_worker_pool_impl.cc
+++ b/src/base/task/task_scheduler/scheduler_worker_pool_impl.cc
@@ -25,6 +25,7 @@
 #include "base/threading/scoped_blocking_call.h"
 #include "base/threading/thread_checker.h"
 #include "base/threading/thread_restrictions.h"
+#include "starboard/configuration_constants.h"
 
 #if defined(OS_WIN)
 #include "base/win/scoped_com_initializer.h"
@@ -50,7 +51,7 @@
     "TaskScheduler.NumTasksBetweenWaits.";
 constexpr char kNumThreadsHistogramPrefix[] = "TaskScheduler.NumWorkers.";
 #ifdef STARBOARD
-const size_t kMaxNumberOfWorkers = SB_MAX_THREADS;
+const size_t kMaxNumberOfWorkers = kSbMaxThreads;
 #else
 constexpr size_t kMaxNumberOfWorkers = 256;
 #endif
diff --git a/src/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc b/src/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc
index 816415f..18c7038 100644
--- a/src/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc
+++ b/src/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc
@@ -46,6 +46,7 @@
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "build/build_config.h"
+#include "starboard/configuration_constants.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 #if defined(OS_WIN)
@@ -1415,7 +1416,7 @@
 // leaves the pool in a valid state with regards to max tasks.
 TEST_F(TaskSchedulerWorkerPoolBlockingTest, MaximumWorkersTest) {
 #ifdef STARBOARD
-  const size_t kMaxNumberOfWorkers = SB_MAX_THREADS;
+  const size_t kMaxNumberOfWorkers = kSbMaxThreads;
 #else
   constexpr size_t kMaxNumberOfWorkers = 256;
 #endif
@@ -1665,7 +1666,7 @@
 // test for https://crbug.com/810464.
 TEST_F(TaskSchedulerWorkerPoolImplStartInBodyTest, RacyCleanup) {
 #ifdef STARBOARD
-  const size_t kLocalMaxTasks = SB_MAX_THREADS;
+  const size_t kLocalMaxTasks = kSbMaxThreads;
 #else
 #if defined(OS_FUCHSIA)
   // Fuchsia + QEMU doesn't deal well with *many* threads being
diff --git a/src/base/task/task_scheduler/service_thread.cc b/src/base/task/task_scheduler/service_thread.cc
index 256de56..c2bea34 100644
--- a/src/base/task/task_scheduler/service_thread.cc
+++ b/src/base/task/task_scheduler/service_thread.cc
@@ -25,7 +25,7 @@
 
 ServiceThread::ServiceThread(const TaskTracker* task_tracker,
                              RepeatingClosure report_heartbeat_metrics_callback)
-    : Thread("TaskSchedulerServiceThread"),
+    : Thread("TaskSchdSvcThd"),
       task_tracker_(task_tracker),
       report_heartbeat_metrics_callback_(
           std::move(report_heartbeat_metrics_callback)) {}
diff --git a/src/base/task/task_scheduler/task_scheduler_impl.cc b/src/base/task/task_scheduler/task_scheduler_impl.cc
index 580d9ca..9a6c76d 100644
--- a/src/base/task/task_scheduler/task_scheduler_impl.cc
+++ b/src/base/task/task_scheduler/task_scheduler_impl.cc
@@ -11,7 +11,6 @@
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/compiler_specific.h"
-#include "base/cpp14oncpp11.h"
 #include "base/message_loop/message_loop.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/stl_util.h"
@@ -25,6 +24,7 @@
 #include "base/task/task_scheduler/task.h"
 #include "base/task/task_scheduler/task_tracker.h"
 #include "base/time/time.h"
+#include "nb/cpp14oncpp11.h"
 
 namespace base {
 namespace internal {
diff --git a/src/base/task/task_traits_details.h b/src/base/task/task_traits_details.h
index a8ec8a6..6f31746 100644
--- a/src/base/task/task_traits_details.h
+++ b/src/base/task/task_traits_details.h
@@ -10,7 +10,7 @@
 #include <type_traits>
 #include <utility>
 
-#include "base/cpp14oncpp11.h"
+#include "nb/cpp14oncpp11.h"
 
 namespace base {
 namespace trait_helpers {
diff --git a/src/base/task/task_traits_extension.h b/src/base/task/task_traits_extension.h
index 5de352f..d03d5b0 100644
--- a/src/base/task/task_traits_extension.h
+++ b/src/base/task/task_traits_extension.h
@@ -11,7 +11,7 @@
 
 #include "base/base_export.h"
 #include "base/task/task_traits_details.h"
-#include "base/cpp14oncpp11.h"
+#include "nb/cpp14oncpp11.h"
 #include "starboard/types.h"
 
 namespace base {
diff --git a/src/base/task/task_traits_extension_unittest.cc b/src/base/task/task_traits_extension_unittest.cc
index 9964b2a..394cdbe 100644
--- a/src/base/task/task_traits_extension_unittest.cc
+++ b/src/base/task/task_traits_extension_unittest.cc
@@ -4,8 +4,8 @@
 
 #include "base/task/task_traits.h"
 
-#include "base/cpp14oncpp11.h"
 #include "base/task/test_task_traits_extension.h"
+#include "nb/cpp14oncpp11.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
diff --git a/src/base/task/task_traits_unittest.cc b/src/base/task/task_traits_unittest.cc
index 1e29515..41e76e8 100644
--- a/src/base/task/task_traits_unittest.cc
+++ b/src/base/task/task_traits_unittest.cc
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/task/task_traits.h"
-#include "base/cpp14oncpp11.h"
+#include "nb/cpp14oncpp11.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/src/base/task/test_task_traits_extension.h b/src/base/task/test_task_traits_extension.h
index 9626b61..c5b6a99 100644
--- a/src/base/task/test_task_traits_extension.h
+++ b/src/base/task/test_task_traits_extension.h
@@ -7,8 +7,8 @@
 
 #include <utility>
 
-#include "base/cpp14oncpp11.h"
 #include "base/task/task_traits.h"
+#include "nb/cpp14oncpp11.h"
 
 namespace base {
 
diff --git a/src/base/template_util.h b/src/base/template_util.h
index 83b6edb..8fea7be 100644
--- a/src/base/template_util.h
+++ b/src/base/template_util.h
@@ -13,7 +13,7 @@
 #include <vector>
 
 #include "build/build_config.h"
-#include "cpp14oncpp11.h"
+#include "nb/cpp14oncpp11.h"
 
 // Some versions of libstdc++ have partial support for type_traits, but misses
 // a smaller subset while removing some of the older non-standard stuff. Assume
diff --git a/src/base/threading/scoped_blocking_call_unittest.cc b/src/base/threading/scoped_blocking_call_unittest.cc
index 2d581d1..3ea9950 100644
--- a/src/base/threading/scoped_blocking_call_unittest.cc
+++ b/src/base/threading/scoped_blocking_call_unittest.cc
@@ -6,9 +6,9 @@
 
 #include <memory>
 
-#include "base/cpp14oncpp11.h"
 #include "base/macros.h"
 #include "base/test/gtest_util.h"
+#include "nb/cpp14oncpp11.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/src/base/threading/thread.h b/src/base/threading/thread.h
index c9fde99..a27dc2d 100644
--- a/src/base/threading/thread.h
+++ b/src/base/threading/thread.h
@@ -115,6 +115,15 @@
   // name is a display string to identify the thread.
   explicit Thread(const std::string& name);
 
+#if !defined(BASE_DONT_ENFORCE_THREAD_NAME_LENGTH)
+  // Constructor, checks length of name literal at compile-time
+  template <size_t N>
+  explicit Thread(char const (&name)[N]) : Thread(std::string(name)) {
+    // 16 is common to Linux and other Starboard platforms
+    static_assert(N <= 16, "Thread name too long, max 16");
+  }
+#endif
+
   // Destroys the thread, stopping it if necessary.
   //
   // NOTE: ALL SUBCLASSES OF Thread MUST CALL Stop() IN THEIR DESTRUCTORS (or
diff --git a/src/base/time/time.h b/src/base/time/time.h
index 8ee1b6d..1ee6446 100644
--- a/src/base/time/time.h
+++ b/src/base/time/time.h
@@ -58,10 +58,10 @@
 
 #include "base/base_export.h"
 #include "base/compiler_specific.h"
-#include "base/cpp14oncpp11.h"
 #include "base/logging.h"
 #include "base/numerics/safe_math.h"
 #include "build/build_config.h"
+#include "nb/cpp14oncpp11.h"
 
 #if defined(STARBOARD)
 #include "starboard/time.h"
diff --git a/src/base/time/time_unittest.cc b/src/base/time/time_unittest.cc
index a8a4a58..c9e5a3e 100644
--- a/src/base/time/time_unittest.cc
+++ b/src/base/time/time_unittest.cc
@@ -15,10 +15,10 @@
 
 #include "base/build_time.h"
 #include "base/compiler_specific.h"
-#include "base/cpp14oncpp11.h"
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/strings/stringprintf.h"
+#include "nb/cpp14oncpp11.h"
 #if defined(STARBOARD)
 #include "base/test/time_helpers.h"
 #endif  // defined(STARBOARD)
diff --git a/src/base/trace_event/memory_usage_estimator.h b/src/base/trace_event/memory_usage_estimator.h
index 24f144f..a8fdccf 100644
--- a/src/base/trace_event/memory_usage_estimator.h
+++ b/src/base/trace_event/memory_usage_estimator.h
@@ -26,10 +26,10 @@
 #include "base/containers/linked_list.h"
 #include "base/containers/mru_cache.h"
 #include "base/containers/queue.h"
-#include "base/cpp14oncpp11.h"
 #include "base/stl_util.h"
 #include "base/strings/string16.h"
 #include "base/template_util.h"
+#include "nb/cpp14oncpp11.h"
 #include "starboard/types.h"
 
 // Composable memory usage estimators.
diff --git a/src/base/trace_event/memory_usage_estimator_unittest.cc b/src/base/trace_event/memory_usage_estimator_unittest.cc
index de47df2..cf9b4c5 100644
--- a/src/base/trace_event/memory_usage_estimator_unittest.cc
+++ b/src/base/trace_event/memory_usage_estimator_unittest.cc
@@ -6,10 +6,10 @@
 
 #include <stdlib.h>
 
-#include "base/cpp14oncpp11.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/string16.h"
 #include "build/build_config.h"
+#include "nb/cpp14oncpp11.h"
 #include "starboard/types.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/src/base/tuple.h b/src/base/tuple.h
index 24670c1..fc95737 100644
--- a/src/base/tuple.h
+++ b/src/base/tuple.h
@@ -28,8 +28,8 @@
 #include <tuple>
 #include <utility>
 
-#include "base/cpp14oncpp11.h"
 #include "build/build_config.h"
+#include "nb/cpp14oncpp11.h"
 #include "starboard/types.h"
 
 namespace base {
diff --git a/src/cobalt/CHANGELOG.md b/src/cobalt/CHANGELOG.md
index cce7b0f..01a2817 100644
--- a/src/cobalt/CHANGELOG.md
+++ b/src/cobalt/CHANGELOG.md
@@ -4,12 +4,13 @@
 
 ## Version 21
 
- - **DevTools and WebDriver listen to ANY interface, except on Linux.**
+ - **DevTools and WebDriver listen to ANY interface, except on desktop PCs.**
 
    DevTools and WebDriver servers listen to connections on any network interface
-   by default, except on Linux where they listen only to loopback (localhost) by
-   default. A new "--dev_servers_listen_ip" command line parameter can be used
-   to specify a different interface for both of them to listen to.
+   by default, except on desktop PCs (i.e. Linux and Win32) where they listen
+   only to loopback (localhost) by default. A new "--dev_servers_listen_ip"
+   command line parameter can be used to specify a different interface for both
+   of them to listen to.
 
  - **DevTools shows asynchronous stack traces.**
 
@@ -207,6 +208,12 @@
      minimum framerate causing Cobalt to rerender the display even if nothing has
      changed after the specified interval.
 
+### Version 20.lts.3
+ - **Improvements and Bug Fixes**
+
+   - Fix a bug where deep links that are fired before the WebModule is loaded
+     were ignored. Now Cobalt stores the last deep link fired before WebModule
+     is loaded & handles the deep link once the WebModule is loaded.
 
 ## Version 19
  - **Add support for V8 JavaScript Engine**
diff --git a/src/cobalt/audio/async_audio_decoder.cc b/src/cobalt/audio/async_audio_decoder.cc
index fca7ced..174cc57 100644
--- a/src/cobalt/audio/async_audio_decoder.cc
+++ b/src/cobalt/audio/async_audio_decoder.cc
@@ -44,7 +44,7 @@
 
 }  // namespace
 
-AsyncAudioDecoder::AsyncAudioDecoder() : thread_("AsyncAudioDecoder") {
+AsyncAudioDecoder::AsyncAudioDecoder() : thread_("AsyncAudioDec") {
   thread_.Start();
 }
 
diff --git a/src/cobalt/base/base.gyp b/src/cobalt/base/base.gyp
index f849ff4..b8e5f75 100644
--- a/src/cobalt/base/base.gyp
+++ b/src/cobalt/base/base.gyp
@@ -33,6 +33,7 @@
         'clock.h',
         'cobalt_paths.h',
         'compiler.h',
+        'console_log.h',
         'c_val.cc',
         'c_val.h',
         'c_val_collection_entry_stats.h',
@@ -65,6 +66,7 @@
         'polymorphic_downcast.h',
         'polymorphic_equatable.h',
         'ref_counted_lock.h',
+        'source_location.cc',
         'source_location.h',
         'startup_timer.cc',
         'startup_timer.h',
diff --git a/src/cobalt/base/console_log.h b/src/cobalt/base/console_log.h
new file mode 100644
index 0000000..b42a4ba
--- /dev/null
+++ b/src/cobalt/base/console_log.h
@@ -0,0 +1,57 @@
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_BASE_CONSOLE_LOG_H_
+#define COBALT_BASE_CONSOLE_LOG_H_
+
+#include <sstream>
+
+#include "base/logging.h"
+
+namespace base {
+
+// This class serves a similar purpose to LogMessage in logging.h, but for the
+// CLOG macro that sends the message to the JS console through DebuggerHooks.
+class ConsoleMessage {
+ public:
+  ConsoleMessage(::logging::LogSeverity severity,
+                 const DebuggerHooks& debugger_hooks)
+      : severity_(severity), debugger_hooks_(debugger_hooks) {}
+
+  ~ConsoleMessage() { debugger_hooks_.ConsoleLog(severity_, stream_.str()); }
+
+  std::ostream& stream() { return stream_; }
+
+ private:
+  ::logging::LogSeverity severity_;
+  const DebuggerHooks& debugger_hooks_;
+  std::ostringstream stream_;
+};
+
+#if defined(ENABLE_DEBUGGER)
+#define CLOG_IS_ON true
+#else
+#define CLOG_IS_ON false
+#endif
+
+#define CLOG_STREAM(severity, debugger_hooks) \
+  ::base::ConsoleMessage(::logging::LOG_##severity, (debugger_hooks)).stream()
+#define CLOG(severity, debugger_hooks) \
+  LAZY_STREAM(CLOG_STREAM(severity, debugger_hooks), CLOG_IS_ON)
+#define CLOG_IF(severity, debugger_hooks, condition) \
+  LAZY_STREAM(CLOG_STREAM(severity, debugger_hooks), CLOG_IS_ON && (condition))
+
+}  // namespace base
+
+#endif  // COBALT_BASE_CONSOLE_LOG_H_
diff --git a/src/cobalt/base/debugger_hooks.h b/src/cobalt/base/debugger_hooks.h
index f555dd1..be8df6e 100644
--- a/src/cobalt/base/debugger_hooks.h
+++ b/src/cobalt/base/debugger_hooks.h
@@ -16,6 +16,8 @@
 
 #include <string>
 
+#include "base/logging.h"
+
 namespace base {
 
 // Interface to allow the WebModule and the various objects implementing the
@@ -23,6 +25,12 @@
 // directly access the DebugModule.
 class DebuggerHooks {
  public:
+  DebuggerHooks() = default;
+  DebuggerHooks(const DebuggerHooks&) = delete;
+  DebuggerHooks* operator=(const DebuggerHooks&) = delete;
+  DebuggerHooks(DebuggerHooks&&) = delete;
+  DebuggerHooks* operator=(DebuggerHooks&&) = delete;
+
   // Indicates whether an asynchronous task will run at most once or if it might
   // run multiple times.
   enum class AsyncTaskFrequency {
@@ -30,6 +38,10 @@
     kRecurring,
   };
 
+  // Logs a message to the JavaScript console.
+  virtual void ConsoleLog(::logging::LogSeverity severity,
+                          std::string message) const = 0;
+
   // Record the JavaScript stack on the WebModule thread at the point a task is
   // initiated that will run at a later time (on the same thread), allowing it
   // to be seen as the originator when breaking in the asynchronous task.
@@ -61,20 +73,22 @@
 // Helper to start & finish async tasks using RAII.
 class ScopedAsyncTask {
  public:
-  ScopedAsyncTask(DebuggerHooks* debugger_hooks, const void* task)
+  ScopedAsyncTask(const DebuggerHooks& debugger_hooks, const void* task)
       : debugger_hooks_(debugger_hooks), task_(task) {
-    debugger_hooks_->AsyncTaskStarted(task_);
+    debugger_hooks_.AsyncTaskStarted(task_);
   }
-  ~ScopedAsyncTask() { debugger_hooks_->AsyncTaskFinished(task_); }
+  ~ScopedAsyncTask() { debugger_hooks_.AsyncTaskFinished(task_); }
 
  private:
-  DebuggerHooks* debugger_hooks_;
+  const DebuggerHooks& debugger_hooks_;
   const void* const task_;
 };
 
 // Null implementation for gold builds and tests where there is no debugger.
 class NullDebuggerHooks : public DebuggerHooks {
  public:
+  void ConsoleLog(::logging::LogSeverity severity,
+                  std::string message) const override {}
   void AsyncTaskScheduled(const void* task, const std::string& name,
                           AsyncTaskFrequency frequency) const override {}
   void AsyncTaskStarted(const void* task) const override {}
diff --git a/src/cobalt/base/path_provider.cc b/src/cobalt/base/path_provider.cc
index ccdfbf6..ae07949 100644
--- a/src/cobalt/base/path_provider.cc
+++ b/src/cobalt/base/path_provider.cc
@@ -20,13 +20,14 @@
 #include "base/files/file_util.h"
 #include "base/logging.h"
 #include "cobalt/base/cobalt_paths.h"
+#include "starboard/configuration_constants.h"
 #include "starboard/system.h"
 
 namespace {
 base::FilePath GetOrCreateDirectory(SbSystemPathId path_id) {
-  std::unique_ptr<char[]> path(new char[SB_FILE_MAX_PATH]);
+  std::unique_ptr<char[]> path(new char[kSbFileMaxPath]);
   path[0] = '\0';
-  if (SbSystemGetPath(path_id, path.get(), SB_FILE_MAX_PATH)) {
+  if (SbSystemGetPath(path_id, path.get(), kSbFileMaxPath)) {
     base::FilePath directory(path.get());
     if (base::PathExists(directory) || base::CreateDirectory(directory)) {
       return directory;
diff --git a/src/cobalt/base/source_location.cc b/src/cobalt/base/source_location.cc
new file mode 100644
index 0000000..56fa0f3
--- /dev/null
+++ b/src/cobalt/base/source_location.cc
@@ -0,0 +1,29 @@
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/base/source_location.h"
+
+#include <iostream>
+#include <string>
+
+namespace base {
+
+std::ostream& operator<<(std::ostream& out_stream,
+                         const SourceLocation& location) {
+  out_stream << location.file_path << "(" << location.line_number << ","
+             << location.column_number << ")";
+  return out_stream;
+}
+
+}  // namespace base
diff --git a/src/cobalt/base/source_location.h b/src/cobalt/base/source_location.h
index 9850091..e4a5e5c 100644
--- a/src/cobalt/base/source_location.h
+++ b/src/cobalt/base/source_location.h
@@ -15,6 +15,7 @@
 #ifndef COBALT_BASE_SOURCE_LOCATION_H_
 #define COBALT_BASE_SOURCE_LOCATION_H_
 
+#include <ostream>
 #include <string>
 
 #include "base/logging.h"
@@ -47,6 +48,9 @@
   int column_number;
 };
 
+std::ostream& operator<<(std::ostream& out_stream,
+                         const SourceLocation& location);
+
 // Offsets the location within embedded source relatively to the start
 // of embedded source.
 //
diff --git a/src/cobalt/base/token.h b/src/cobalt/base/token.h
index e2621a1..1ff90c7 100644
--- a/src/cobalt/base/token.h
+++ b/src/cobalt/base/token.h
@@ -33,7 +33,7 @@
 // instead of by reference.
 class Token {
  public:
-  static const uint32 kHashSlotCount = 4 * 1024;
+  static const uint32 kHashSlotCount = 8 * 1024;
   static const uint32 kStringsPerSlot = 4;
 
   Token();
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_anonymous_indexed_getter_interface.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_anonymous_indexed_getter_interface.cc
index 7a34bae..8bab19b 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_anonymous_indexed_getter_interface.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_anonymous_indexed_getter_interface.cc
@@ -135,6 +135,7 @@
 void IndexedPropertyEnumeratorCallback(
     const v8::PropertyCallbackInfo<v8::Array>& info) {
   v8::Isolate* isolate = info.GetIsolate();
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
   v8::Local<v8::Object> object = info.Holder();
   AnonymousIndexedGetterInterface* impl =
           script::v8c::shared_bindings::get_impl_from_object<
@@ -145,7 +146,7 @@
   const uint32_t length = impl->length();
   v8::Local<v8::Array> array = v8::Array::New(isolate, length);
   for (uint32_t i = 0; i < length; ++i) {
-    array->Set(i, v8::Integer::New(isolate, i));
+    array->Set(context, i, v8::Integer::New(isolate, i)).Check();
   }
   info.GetReturnValue().Set(array);
 }
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_anonymous_named_getter_interface.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_anonymous_named_getter_interface.cc
index 4f2c6cc..dd39476 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_anonymous_named_getter_interface.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_anonymous_named_getter_interface.cc
@@ -128,7 +128,8 @@
 void IndexedPropertyGetterCallback(
     uint32_t index,
     const v8::PropertyCallbackInfo<v8::Value>& info) {
-  v8::Local<v8::String> as_string = v8::Integer::New(info.GetIsolate(), index)->ToString();
+  v8::Local<v8::String> as_string = (v8::Integer::New(info.GetIsolate(), index)->ToString(
+                                     info.GetIsolate()->GetCurrentContext())).ToLocalChecked();
   NamedPropertyGetterCallback(as_string, info);
 }
 
@@ -219,7 +220,8 @@
     uint32_t index,
     v8::Local<v8::Value> value,
     const v8::PropertyCallbackInfo<v8::Value>& info) {
-  v8::Local<v8::String> as_string = v8::Integer::New(info.GetIsolate(), index)->ToString();
+  v8::Local<v8::String> as_string = (v8::Integer::New(info.GetIsolate(), index)->ToString(
+                                     info.GetIsolate()->GetCurrentContext())).ToLocalChecked();
   NamedPropertySetterCallback(as_string, value, info);
 }
 
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_anonymous_named_indexed_getter_interface.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_anonymous_named_indexed_getter_interface.cc
index 27aaca4..ee8f7f8 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_anonymous_named_indexed_getter_interface.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_anonymous_named_indexed_getter_interface.cc
@@ -244,6 +244,7 @@
 void IndexedPropertyEnumeratorCallback(
     const v8::PropertyCallbackInfo<v8::Array>& info) {
   v8::Isolate* isolate = info.GetIsolate();
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
   v8::Local<v8::Object> object = info.Holder();
   AnonymousNamedIndexedGetterInterface* impl =
           script::v8c::shared_bindings::get_impl_from_object<
@@ -254,7 +255,7 @@
   const uint32_t length = impl->length();
   v8::Local<v8::Array> array = v8::Array::New(isolate, length);
   for (uint32_t i = 0; i < length; ++i) {
-    array->Set(i, v8::Integer::New(isolate, i));
+    array->Set(context, i, v8::Integer::New(isolate, i)).Check();
   }
   info.GetReturnValue().Set(array);
 }
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_constructor_interface.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_constructor_interface.cc
index 4ec70f5..7a58046 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_constructor_interface.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_constructor_interface.cc
@@ -152,6 +152,7 @@
 
 void Constructor(const v8::FunctionCallbackInfo<v8::Value>& info) {
   v8::Isolate* isolate = info.GetIsolate();
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
 
   switch(info.Length()) {
     case(0): {
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_derived_getter_setter_interface.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_derived_getter_setter_interface.cc
index 53fe18e..ae86a6d 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_derived_getter_setter_interface.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_derived_getter_setter_interface.cc
@@ -248,6 +248,7 @@
 void IndexedPropertyEnumeratorCallback(
     const v8::PropertyCallbackInfo<v8::Array>& info) {
   v8::Isolate* isolate = info.GetIsolate();
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
   v8::Local<v8::Object> object = info.Holder();
   DerivedGetterSetterInterface* impl =
           script::v8c::shared_bindings::get_impl_from_object<
@@ -258,7 +259,7 @@
   const uint32_t length = impl->length();
   v8::Local<v8::Array> array = v8::Array::New(isolate, length);
   for (uint32_t i = 0; i < length; ++i) {
-    array->Set(i, v8::Integer::New(isolate, i));
+    array->Set(context, i, v8::Integer::New(isolate, i)).Check();
   }
   info.GetReturnValue().Set(array);
 }
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_indexed_getter_interface.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_indexed_getter_interface.cc
index 960e6aa..6c0a8af 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_indexed_getter_interface.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_indexed_getter_interface.cc
@@ -135,6 +135,7 @@
 void IndexedPropertyEnumeratorCallback(
     const v8::PropertyCallbackInfo<v8::Array>& info) {
   v8::Isolate* isolate = info.GetIsolate();
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
   v8::Local<v8::Object> object = info.Holder();
   IndexedGetterInterface* impl =
           script::v8c::shared_bindings::get_impl_from_object<
@@ -145,7 +146,7 @@
   const uint32_t length = impl->length();
   v8::Local<v8::Array> array = v8::Array::New(isolate, length);
   for (uint32_t i = 0; i < length; ++i) {
-    array->Set(i, v8::Integer::New(isolate, i));
+    array->Set(context, i, v8::Integer::New(isolate, i)).Check();
   }
   info.GetReturnValue().Set(array);
 }
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_named_getter_interface.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_named_getter_interface.cc
index efafcad..dfdc772 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_named_getter_interface.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_named_getter_interface.cc
@@ -128,7 +128,8 @@
 void IndexedPropertyGetterCallback(
     uint32_t index,
     const v8::PropertyCallbackInfo<v8::Value>& info) {
-  v8::Local<v8::String> as_string = v8::Integer::New(info.GetIsolate(), index)->ToString();
+  v8::Local<v8::String> as_string = (v8::Integer::New(info.GetIsolate(), index)->ToString(
+                                     info.GetIsolate()->GetCurrentContext())).ToLocalChecked();
   NamedPropertyGetterCallback(as_string, info);
 }
 
@@ -219,7 +220,8 @@
     uint32_t index,
     v8::Local<v8::Value> value,
     const v8::PropertyCallbackInfo<v8::Value>& info) {
-  v8::Local<v8::String> as_string = v8::Integer::New(info.GetIsolate(), index)->ToString();
+  v8::Local<v8::String> as_string = (v8::Integer::New(info.GetIsolate(), index)->ToString(
+                                     info.GetIsolate()->GetCurrentContext())).ToLocalChecked();
   NamedPropertySetterCallback(as_string, value, info);
 }
 
@@ -255,7 +257,8 @@
     uint32_t index,
     const v8::PropertyCallbackInfo<v8::Boolean>& info) {
   v8::Isolate* isolate = info.GetIsolate();
-  v8::Local<v8::String> as_string = v8::Integer::New(info.GetIsolate(), index)->ToString();
+  v8::Local<v8::String> as_string = (v8::Integer::New(info.GetIsolate(), index)->ToString(
+                                     info.GetIsolate()->GetCurrentContext())).ToLocalChecked();
   NamedPropertyDeleterCallback(as_string, info);
 }
 
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_named_indexed_getter_interface.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_named_indexed_getter_interface.cc
index cb2a42d..68d9eb3 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_named_indexed_getter_interface.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_named_indexed_getter_interface.cc
@@ -244,6 +244,7 @@
 void IndexedPropertyEnumeratorCallback(
     const v8::PropertyCallbackInfo<v8::Array>& info) {
   v8::Isolate* isolate = info.GetIsolate();
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
   v8::Local<v8::Object> object = info.Holder();
   NamedIndexedGetterInterface* impl =
           script::v8c::shared_bindings::get_impl_from_object<
@@ -254,7 +255,7 @@
   const uint32_t length = impl->length();
   v8::Local<v8::Array> array = v8::Array::New(isolate, length);
   for (uint32_t i = 0; i < length; ++i) {
-    array->Set(i, v8::Integer::New(isolate, i));
+    array->Set(context, i, v8::Integer::New(isolate, i)).Check();
   }
   info.GetReturnValue().Set(array);
 }
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_operations_test_interface.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_operations_test_interface.cc
index 4f4e6e1..f80ed45 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_operations_test_interface.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_operations_test_interface.cc
@@ -568,6 +568,7 @@
 
 void overloadedFunctionMethod(const v8::FunctionCallbackInfo<v8::Value>& info) {
   v8::Isolate* isolate = info.GetIsolate();
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
 
   switch(info.Length()) {
     case(0): {
@@ -587,7 +588,7 @@
       WrapperFactory* wrapper_factory = V8cGlobalEnvironment::GetFromIsolate(isolate)->wrapper_factory();
       v8::Local<v8::Object> object;
       if (arg->IsObject()) {
-        object = arg->ToObject();
+        object = arg->ToObject(context).ToLocalChecked();
       }
       if (arg->IsNumber()) {
         overloadedFunctionMethod2(
@@ -613,7 +614,7 @@
       WrapperFactory* wrapper_factory = V8cGlobalEnvironment::GetFromIsolate(isolate)->wrapper_factory();
       v8::Local<v8::Object> object;
       if (arg->IsObject()) {
-        object = arg->ToObject();
+        object = arg->ToObject(context).ToLocalChecked();
       }
       if (arg->IsObject() ? wrapper_factory->DoesObjectImplementInterface(object, base::GetTypeId<ArbitraryInterface>()) : false) {
         overloadedFunctionMethod5(
@@ -714,6 +715,7 @@
 
 void overloadedNullableMethod(const v8::FunctionCallbackInfo<v8::Value>& info) {
   v8::Isolate* isolate = info.GetIsolate();
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
 
   switch(info.Length()) {
     case(1): {
@@ -723,7 +725,7 @@
       WrapperFactory* wrapper_factory = V8cGlobalEnvironment::GetFromIsolate(isolate)->wrapper_factory();
       v8::Local<v8::Object> object;
       if (arg->IsObject()) {
-        object = arg->ToObject();
+        object = arg->ToObject(context).ToLocalChecked();
       }
       if (arg->IsNullOrUndefined()) {
         overloadedNullableMethod2(
@@ -1104,6 +1106,7 @@
 
 void overloadedFunctionStaticMethod(const v8::FunctionCallbackInfo<v8::Value>& info) {
   v8::Isolate* isolate = info.GetIsolate();
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
 
   switch(info.Length()) {
     case(1): {
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_static_properties_interface.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_static_properties_interface.cc
index e92c7c4..fa44b28 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_static_properties_interface.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_static_properties_interface.cc
@@ -316,6 +316,7 @@
 
 void staticFunctionStaticMethod(const v8::FunctionCallbackInfo<v8::Value>& info) {
   v8::Isolate* isolate = info.GetIsolate();
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
 
   switch(info.Length()) {
     case(0): {
@@ -335,7 +336,7 @@
       WrapperFactory* wrapper_factory = V8cGlobalEnvironment::GetFromIsolate(isolate)->wrapper_factory();
       v8::Local<v8::Object> object;
       if (arg->IsObject()) {
-        object = arg->ToObject();
+        object = arg->ToObject(context).ToLocalChecked();
       }
       if (arg->IsNumber()) {
         staticFunctionStaticMethod2(
@@ -361,7 +362,7 @@
       WrapperFactory* wrapper_factory = V8cGlobalEnvironment::GetFromIsolate(isolate)->wrapper_factory();
       v8::Local<v8::Object> object;
       if (arg->IsObject()) {
-        object = arg->ToObject();
+        object = arg->ToObject(context).ToLocalChecked();
       }
       if (arg->IsObject() ? wrapper_factory->DoesObjectImplementInterface(object, base::GetTypeId<ArbitraryInterface>()) : false) {
         staticFunctionStaticMethod5(
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_test_enum.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_test_enum.cc
index e81c5e1..54840fc 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_test_enum.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_test_enum.cc
@@ -71,7 +71,8 @@
   // JSValue -> IDL enum algorithm described here:
   // http://heycam.github.io/webidl/#es-enumeration
   // 1. Let S be the result of calling ToString(V).
-  v8::MaybeLocal<v8::String> maybe_string = value->ToString(isolate->GetCurrentContext());
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
+  v8::MaybeLocal<v8::String> maybe_string = value->ToString(context);
   v8::Local<v8::String> string;
   if (!maybe_string.ToLocal(&string)) {
     exception_state->SetSimpleException(cobalt::script::kConvertToEnumFailed);
@@ -81,37 +82,37 @@
   bool match = false;
 // 3. Return the enumeration value of type E that is equal to S.
  if (
-      NewInternalString(isolate, "alpha")->Equals(value))
+      NewInternalString(isolate, "alpha")->Equals(context, value).ToChecked())
   {
     *out_enum = cobalt::bindings::testing::kTestEnumAlpha;
   }
  else  if (
-      NewInternalString(isolate, "beta")->Equals(value))
+      NewInternalString(isolate, "beta")->Equals(context, value).ToChecked())
   {
     *out_enum = cobalt::bindings::testing::kTestEnumBeta;
   }
  else  if (
-      NewInternalString(isolate, "gamma")->Equals(value))
+      NewInternalString(isolate, "gamma")->Equals(context, value).ToChecked())
   {
     *out_enum = cobalt::bindings::testing::kTestEnumGamma;
   }
  else  if (
-      NewInternalString(isolate, "enum-with-dashes")->Equals(value))
+      NewInternalString(isolate, "enum-with-dashes")->Equals(context, value).ToChecked())
   {
     *out_enum = cobalt::bindings::testing::kTestEnumEnumWithDashes;
   }
  else  if (
-      NewInternalString(isolate, "enum with spaces")->Equals(value))
+      NewInternalString(isolate, "enum with spaces")->Equals(context, value).ToChecked())
   {
     *out_enum = cobalt::bindings::testing::kTestEnumEnumWithSpaces;
   }
  else  if (
-      NewInternalString(isolate, "terrible----enum")->Equals(value))
+      NewInternalString(isolate, "terrible----enum")->Equals(context, value).ToChecked())
   {
     *out_enum = cobalt::bindings::testing::kTestEnumTerribleEnum;
   }
  else  if (
-      NewInternalString(isolate, "this is a terrible @#$%#$% enum")->Equals(value))
+      NewInternalString(isolate, "this is a terrible @#$%#$% enum")->Equals(context, value).ToChecked())
   {
     *out_enum = cobalt::bindings::testing::kTestEnumThisIsATerribleEnum;
   }
diff --git a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_window.cc b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_window.cc
index e1fb14a..f21dc3a 100644
--- a/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_window.cc
+++ b/src/cobalt/bindings/generated/v8c/testing/cobalt/bindings/testing/v8c_window.cc
@@ -850,7 +850,8 @@
   // Intentionally not an |EntryScope|, since the context doesn't exist yet.
   v8::Isolate::Scope isolate_scope(isolate_);
   v8::HandleScope handle_scope(isolate_);
-  v8::Local<v8::ObjectTemplate> global_object_template = V8cWindow::GetTemplate(isolate_)->InstanceTemplate();
+  v8::Local<v8::ObjectTemplate> global_object_template =
+                V8cWindow::GetTemplate(isolate_)->InstanceTemplate();
 
   v8::Local<v8::Context> context =
       v8::Context::New(isolate_, nullptr, global_object_template);
@@ -867,7 +868,8 @@
   v8::Local<v8::Object> global_object = context->Global();
   new WrapperPrivate(isolate_, global_interface, global_object);
 
-  auto actual_global_object = global_object->GetPrototype()->ToObject();
+  auto actual_global_object = global_object->GetPrototype()->
+                                          ToObject(context).ToLocalChecked();
   new WrapperPrivate(isolate_, global_interface, actual_global_object);
 
   wrapper_factory_->RegisterWrappableType(
@@ -1083,7 +1085,8 @@
 void GlobalEnvironment::CreateGlobalObject<Window>(
     const scoped_refptr<Window>& global_interface,
     EnvironmentSettings* environment_settings) {
-  base::polymorphic_downcast<v8c::V8cGlobalEnvironment*>(this)->CreateGlobalObject(global_interface, environment_settings);
+  base::polymorphic_downcast<v8c::V8cGlobalEnvironment*>(this)->
+                  CreateGlobalObject(global_interface, environment_settings);
 }
 
 }  // namespace script
diff --git a/src/cobalt/bindings/templates/dictionary.h.template b/src/cobalt/bindings/templates/dictionary.h.template
index e1f9de0..d94b588 100644
--- a/src/cobalt/bindings/templates/dictionary.h.template
+++ b/src/cobalt/bindings/templates/dictionary.h.template
@@ -182,6 +182,8 @@
 
 {% endfor %}
 
+ using is_a_generated_dict = std::true_type;
+
  private:
 {% for member in members %}
 {% if not member.default_value %}
diff --git a/src/cobalt/bindings/testing/bindings_sandbox_main.cc b/src/cobalt/bindings/testing/bindings_sandbox_main.cc
index 3eedf11..b10bfac 100644
--- a/src/cobalt/bindings/testing/bindings_sandbox_main.cc
+++ b/src/cobalt/bindings/testing/bindings_sandbox_main.cc
@@ -25,15 +25,27 @@
 
 namespace {
 
-int SandboxMain(int argc, char** argv) {
+cobalt::script::StandaloneJavascriptRunner* g_javascript_runner = NULL;
+
+void StartApplication(int argc, char** argv, const char* /*link */,
+                      const base::Closure& quit_closure) {
   scoped_refptr<Window> test_window = new Window();
   cobalt::script::JavaScriptEngine::Options javascript_engine_options;
-  StandaloneJavascriptRunner standalone_runner(javascript_engine_options,
-                                               test_window);
-  standalone_runner.RunInteractive();
-  return 0;
+
+  DCHECK(!g_javascript_runner);
+  g_javascript_runner = new cobalt::script::StandaloneJavascriptRunner(
+      base::MessageLoop::current()->task_runner(), javascript_engine_options,
+      test_window);
+  DCHECK(g_javascript_runner);
+  g_javascript_runner->RunUntilDone(quit_closure);
+}
+
+void StopApplication() {
+  DCHECK(g_javascript_runner);
+  delete g_javascript_runner;
+  g_javascript_runner = NULL;
 }
 
 }  // namespace
 
-COBALT_WRAP_SIMPLE_MAIN(SandboxMain);
+COBALT_WRAP_BASE_MAIN(StartApplication, StopApplication);
diff --git a/src/cobalt/bindings/testing/union_type_bindings_test.cc b/src/cobalt/bindings/testing/union_type_bindings_test.cc
index 9674290..01e4a76 100644
--- a/src/cobalt/bindings/testing/union_type_bindings_test.cc
+++ b/src/cobalt/bindings/testing/union_type_bindings_test.cc
@@ -17,6 +17,7 @@
 
 #include "testing/gtest/include/gtest/gtest.h"
 
+using ::testing::ContainsRegex;
 using ::testing::InSequence;
 using ::testing::Return;
 using ::testing::SaveArg;
@@ -165,6 +166,108 @@
   EXPECT_STREQ("true", result.c_str());
 }
 
+TEST_F(UnionTypesBindingsTest, ConvertDictFromJS) {
+  InSequence dummy;
+
+  // Union can still accept a string
+  UnionTypesInterface::UnionDictPropertyType property;
+  EXPECT_CALL(test_mock(), set_union_with_dictionary_property(_))
+      .WillOnce(SaveArg<0>(&property));
+  EXPECT_TRUE(
+      EvaluateScript("test.unionWithDictionaryProperty = 'foo';", NULL));
+  ASSERT_TRUE(property.IsType<std::string>());
+  EXPECT_EQ("foo", property.AsType<std::string>());
+
+  // Dictionary is properly converted and populated
+  EXPECT_CALL(test_mock(), set_union_with_dictionary_property(_))
+      .WillOnce(SaveArg<0>(&property));
+  EXPECT_TRUE(
+      EvaluateScript("test.unionWithDictionaryProperty = "
+                     "{ 'stringMember' : 'foo', 'longMember' : 42 };",
+                     NULL));
+  ASSERT_TRUE(property.IsType<TestDictionary>());
+  EXPECT_TRUE(property.AsType<TestDictionary>().has_string_member());
+  EXPECT_EQ(property.AsType<TestDictionary>().string_member(), "foo");
+  EXPECT_TRUE(property.AsType<TestDictionary>().has_long_member());
+  EXPECT_EQ(property.AsType<TestDictionary>().long_member(), 42);
+
+  // Derived dictionary can be converted, and takes precedence over an object
+  UnionTypesInterface::UnionObjectsPropertyType other_property;
+  EXPECT_CALL(test_mock(), set_union_dicts_objects_property(_))
+      .WillOnce(SaveArg<0>(&other_property));
+  EXPECT_TRUE(
+      EvaluateScript("test.unionDictsObjectsProperty = "
+                     "{ 'additionalMember' : true, 'longMember' : 2 };",
+                     NULL));
+  ASSERT_TRUE(other_property.IsType<DerivedDictionary>());
+  EXPECT_EQ(true,
+            other_property.AsType<DerivedDictionary>().additional_member());
+  EXPECT_EQ(2, other_property.AsType<DerivedDictionary>().long_member());
+
+  // Union can still accept an arbitrary object as well
+  EXPECT_CALL(test_mock(), set_union_dicts_objects_property(_))
+      .WillOnce(SaveArg<0>(&other_property));
+  EXPECT_TRUE(EvaluateScript(
+      "test.unionDictsObjectsProperty = new ArbitraryInterface();", NULL));
+  ASSERT_TRUE(other_property.IsType<scoped_refptr<ArbitraryInterface> >());
+  EXPECT_TRUE(other_property.AsType<scoped_refptr<ArbitraryInterface> >());
+}
+
+TEST_F(UnionTypesBindingsTest, ConvertDictToJS) {
+  InSequence dummy;
+
+  // Return a string
+  std::string result;
+  EXPECT_CALL(test_mock(), union_with_dictionary_property())
+      .WillOnce(Return(
+          UnionTypesInterface::UnionDictPropertyType(std::string("foo"))));
+  EXPECT_TRUE(EvaluateScript(
+      "typeof test.unionWithDictionaryProperty == \"string\";", &result));
+  EXPECT_EQ("true", result);
+
+  // Return a dictionary with default member set
+  EXPECT_CALL(test_mock(), union_with_dictionary_property())
+      .WillOnce(
+          Return(UnionTypesInterface::UnionDictPropertyType(TestDictionary())));
+  EXPECT_TRUE(EvaluateScript(
+      "test.unionWithDictionaryProperty['memberWithDefault']", &result));
+  EXPECT_EQ("5", result);
+
+  // Return an arbitrary object
+  EXPECT_CALL(test_mock(), union_dicts_objects_property())
+      .WillOnce(Return(UnionTypesInterface::UnionObjectsPropertyType(
+          base::WrapRefCounted(new ArbitraryInterface()))));
+  EXPECT_TRUE(EvaluateScript(
+      "Object.getPrototypeOf(test.unionDictsObjectsProperty) === "
+      "ArbitraryInterface.prototype;",
+      &result));
+  EXPECT_EQ("true", result);
+
+  // Return a derived dictionary with default boolean member set
+  EXPECT_CALL(test_mock(), union_dicts_objects_property())
+      .WillOnce(Return(
+          UnionTypesInterface::UnionObjectsPropertyType(DerivedDictionary())));
+  EXPECT_TRUE(EvaluateScript(
+      "test.unionDictsObjectsProperty['additionalMember'] == false", &result));
+  EXPECT_EQ("true", result);
+}
+
+TEST_F(UnionTypesBindingsTest, ConvertDictFromJSInvalid) {
+  InSequence dummy;
+
+  std::string result;
+  EXPECT_FALSE(EvaluateScript("test.unionDictsObjectsProperty = 42;", &result));
+  EXPECT_THAT(result, ContainsRegex("TypeError:"));
+
+  EXPECT_FALSE(
+      EvaluateScript("test.unionDictsObjectsProperty = 'foo';", &result));
+  EXPECT_THAT(result, ContainsRegex("TypeError:"));
+
+  EXPECT_FALSE(
+      EvaluateScript("test.unionDictsObjectsProperty = true;", &result));
+  EXPECT_THAT(result, ContainsRegex("TypeError:"));
+}
+
 }  // namespace testing
 }  // namespace bindings
 }  // namespace cobalt
diff --git a/src/cobalt/bindings/testing/union_types_interface.h b/src/cobalt/bindings/testing/union_types_interface.h
index a44f1a0..39e9ff4 100644
--- a/src/cobalt/bindings/testing/union_types_interface.h
+++ b/src/cobalt/bindings/testing/union_types_interface.h
@@ -19,6 +19,8 @@
 
 #include "cobalt/bindings/testing/arbitrary_interface.h"
 #include "cobalt/bindings/testing/base_interface.h"
+#include "cobalt/bindings/testing/derived_dictionary.h"
+#include "cobalt/bindings/testing/test_dictionary.h"
 #include "cobalt/script/union_type.h"
 #include "cobalt/script/wrappable.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -37,6 +39,11 @@
   typedef script::UnionType2<scoped_refptr<BaseInterface>, std::string>
       UnionBasePropertyType;
 
+  using UnionDictPropertyType =
+      script::UnionType3<std::string, double, TestDictionary>;
+  using UnionObjectsPropertyType =
+      script::UnionType2<DerivedDictionary, scoped_refptr<ArbitraryInterface>>;
+
   MOCK_METHOD0(union_property, UnionPropertyType());
   MOCK_METHOD1(set_union_property, void(const UnionPropertyType&));
 
@@ -52,6 +59,14 @@
   MOCK_METHOD0(union_base_property, UnionBasePropertyType());
   MOCK_METHOD1(set_union_base_property, void(const UnionBasePropertyType&));
 
+  MOCK_METHOD0(union_with_dictionary_property, UnionDictPropertyType());
+  MOCK_METHOD1(set_union_with_dictionary_property,
+               void(const UnionDictPropertyType &));
+
+  MOCK_METHOD0(union_dicts_objects_property, UnionObjectsPropertyType());
+  MOCK_METHOD1(set_union_dicts_objects_property,
+               void(const UnionObjectsPropertyType &));
+
   DEFINE_WRAPPABLE_TYPE(UnionTypesInterface);
 };
 
diff --git a/src/cobalt/bindings/testing/union_types_interface.idl b/src/cobalt/bindings/testing/union_types_interface.idl
index 4e31edd..9676c92 100644
--- a/src/cobalt/bindings/testing/union_types_interface.idl
+++ b/src/cobalt/bindings/testing/union_types_interface.idl
@@ -17,4 +17,6 @@
   attribute (double or DOMString?) unionWithNullableMemberProperty;
   attribute (double or DOMString)? nullableUnionProperty;
   attribute (BaseInterface or DOMString) unionBaseProperty;
+  attribute (DOMString or double or TestDictionary) unionWithDictionaryProperty;
+  attribute (DerivedDictionary or ArbitraryInterface) unionDictsObjectsProperty;
 };
diff --git a/src/cobalt/black_box_tests/black_box_cobalt_runner.py b/src/cobalt/black_box_tests/black_box_cobalt_runner.py
index 1ca3ea9..fb0783c 100644
--- a/src/cobalt/black_box_tests/black_box_cobalt_runner.py
+++ b/src/cobalt/black_box_tests/black_box_cobalt_runner.py
@@ -11,7 +11,6 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
 """The base class for black box tests."""
 
 from __future__ import absolute_import
@@ -32,6 +31,22 @@
 class BlackBoxCobaltRunner(cobalt_runner.CobaltRunner):
   """Custom CobaltRunner made for BlackBoxTests' need."""
 
+  def __init__(self,
+               launcher_params,
+               url,
+               log_file=None,
+               target_params=None,
+               success_message=None):
+    # For black box tests, don't log inline script warnings, we intend to
+    # explicitly control timings for suspends and resumes, so we are not
+    # concerned about a "suspend at the wrong time".
+    if target_params is None:
+      target_params = []
+    target_params.append('--silence_inline_script_warnings')
+
+    super(BlackBoxCobaltRunner, self).__init__(launcher_params, url, log_file,
+                                               target_params, success_message)
+
   def JSTestsSucceeded(self):
     """Check test assertions in HTML page."""
 
diff --git a/src/cobalt/black_box_tests/black_box_tests.py b/src/cobalt/black_box_tests/black_box_tests.py
index bfc8ce5..09954c0 100644
--- a/src/cobalt/black_box_tests/black_box_tests.py
+++ b/src/cobalt/black_box_tests/black_box_tests.py
@@ -26,10 +26,10 @@
 
 import _env  # pylint: disable=unused-import
 from cobalt.black_box_tests import black_box_cobalt_runner
-from cobalt.tools.automated_testing import cobalt_runner
 from proxy_server import ProxyServer
 from starboard.tools import abstract_launcher
 from starboard.tools import build
+from starboard.tools import command_line
 
 _PORT_SELECTION_RETRY_LIMIT = 10
 _PORT_SELECTION_RANGE = [5000, 7000]
@@ -60,8 +60,8 @@
 ]
 # Location of test files.
 _TEST_DIR_PATH = 'cobalt.black_box_tests.tests.'
-# Platform dependent device parameters.
-_device_params = None
+# Platform configuration and device information parameters.
+_launcher_params = None
 # Binding address used to create the test server.
 _binding_address = None
 # Port used to create the web platform test http server.
@@ -72,8 +72,8 @@
 
   def __init__(self, *args, **kwargs):
     super(BlackBoxTestCase, self).__init__(*args, **kwargs)
-    self.device_params = _device_params
-    self.platform_config = build.GetPlatformConfig(_device_params.platform)
+    self.launcher_params = _launcher_params
+    self.platform_config = build.GetPlatformConfig(_launcher_params.platform)
     self.cobalt_config = self.platform_config.GetApplicationConfiguration(
         'cobalt')
 
@@ -89,10 +89,12 @@
 
   def CreateCobaltRunner(self, url, target_params=None):
     all_target_params = list(target_params) if target_params else []
-    if _device_params.target_params is not None:
-      all_target_params += _device_params.target_params
+    if _launcher_params.target_params is not None:
+      all_target_params += _launcher_params.target_params
     new_runner = black_box_cobalt_runner.BlackBoxCobaltRunner(
-        device_params=_device_params, url=url, target_params=all_target_params)
+        launcher_params=_launcher_params,
+        url=url,
+        target_params=all_target_params)
     return new_runner
 
   def GetBindingAddress(self):
@@ -102,15 +104,17 @@
     return _wpt_http_port
 
 
-def LoadTests(platform, config, device_id, out_directory):
+def LoadTests(launcher_params):
   launcher = abstract_launcher.LauncherFactory(
-      platform,
+      launcher_params.platform,
       'cobalt',
-      config,
-      device_id=device_id,
+      launcher_params.config,
+      device_id=launcher_params.device_id,
       target_params=None,
       output_file=None,
-      out_directory=out_directory)
+      out_directory=launcher_params.out_directory,
+      loader_platform=launcher_params.loader_platform,
+      loader_config=launcher_params.loader_config)
 
   test_targets = _TESTS_NO_SIGNAL
 
@@ -138,10 +142,10 @@
                wpt_http_port=None):
     logging.basicConfig(level=logging.DEBUG)
 
-    # Setup global variables used by test cases
-    global _device_params
-    _device_params = cobalt_runner.GetDeviceParamsFromCommandLine()
-    # Keep other modules from seeing these args
+    # Setup global variables used by test cases.
+    global _launcher_params
+    _launcher_params = command_line.CreateLauncherParams()
+    # Keep other modules from seeing these args.
     sys.argv = sys.argv[:1]
     global _binding_address
     _binding_address = server_binding_address
@@ -151,7 +155,7 @@
       wpt_http_port = str(self.GetUnusedPort([server_binding_address]))
     global _wpt_http_port
     _wpt_http_port = wpt_http_port
-    _device_params.target_params.append(
+    _launcher_params.target_params.append(
         '--web-platform-test-server=http://web-platform.test:%s' %
         wpt_http_port)
 
@@ -161,8 +165,8 @@
       proxy_port = str(self.GetUnusedPort([server_binding_address]))
     if proxy_address is None:
       proxy_address = server_binding_address
-    _device_params.target_params.append('--proxy=%s:%s' %
-                                        (proxy_address, proxy_port))
+    _launcher_params.target_params.append('--proxy=%s:%s' %
+                                          (proxy_address, proxy_port))
 
     self.proxy_port = proxy_port
     self.test_name = test_name
@@ -189,9 +193,7 @@
         suite = unittest.TestLoader().loadTestsFromModule(
             importlib.import_module(_TEST_DIR_PATH + self.test_name))
       else:
-        suite = LoadTests(_device_params.platform, _device_params.config,
-                          _device_params.device_id,
-                          _device_params.out_directory)
+        suite = LoadTests(_launcher_params)
       return_code = not unittest.TextTestRunner(
           verbosity=0, stream=sys.stdout).run(suite).wasSuccessful()
       return return_code
@@ -268,7 +270,7 @@
   # Running this script on the command line and importing this file are
   # different and create two modules.
   # Import this module to ensure we are using the same module as the tests to
-  # make module-owned variables like device_param accessible to the tests.
+  # make module-owned variables like launcher_param accessible to the tests.
   main_module = importlib.import_module(
       'cobalt.black_box_tests.black_box_tests')
   sys.exit(main_module.main())
diff --git a/src/cobalt/black_box_tests/proxy_server.py b/src/cobalt/black_box_tests/proxy_server.py
index df6c03b..ee29a39 100644
--- a/src/cobalt/black_box_tests/proxy_server.py
+++ b/src/cobalt/black_box_tests/proxy_server.py
@@ -32,7 +32,7 @@
 
 class ProxyServer(object):
 
-  def __init__(self, hostname='127.0.0.1', port='8000', host_resolve_map=None):
+  def __init__(self, hostname='0.0.0.0', port='8000', host_resolve_map=None):
     self.command = [
         'python',
         os.path.join(SRC_DIR, 'third_party', 'proxy_py', 'proxy.py'),
diff --git a/src/cobalt/black_box_tests/testdata/web_debugger.html b/src/cobalt/black_box_tests/testdata/web_debugger.html
index aee0e79..e1ca907 100644
--- a/src/cobalt/black_box_tests/testdata/web_debugger.html
+++ b/src/cobalt/black_box_tests/testdata/web_debugger.html
@@ -7,7 +7,7 @@
 </head>
 
 <body>
-  <h1>
+  <h1 class="name" data-who="robot">
     <span>Web debugger</span>
   </h1>
   <div id="test">
@@ -15,7 +15,12 @@
       <div id="A1" />
       <div id="A2" />
     </div>
-    <div id="B" />
+    <div id="B">
+      <span id="B1">
+      </span>
+    </div>
+    <span id="C">
+    </span>
   </div>
   <script>
     setupFinished();
diff --git a/src/cobalt/black_box_tests/tests/signal_handler_doesnt_crash.py b/src/cobalt/black_box_tests/tests/signal_handler_doesnt_crash.py
index 443ffce..e2962d7 100644
--- a/src/cobalt/black_box_tests/tests/signal_handler_doesnt_crash.py
+++ b/src/cobalt/black_box_tests/tests/signal_handler_doesnt_crash.py
@@ -28,7 +28,7 @@
   """Flood the process with CONT signals, ensuring signal handler is robust."""
 
   def setUp(self):
-    if 'linux' not in self.device_params.platform:
+    if 'linux' not in self.launcher_params.platform:
       self.skipTest('This test needs POSIX system signal handlers')
 
   def test_simple(self):
diff --git a/src/cobalt/black_box_tests/tests/web_debugger.py b/src/cobalt/black_box_tests/tests/web_debugger.py
index 68553c2..7baa7ba 100644
--- a/src/cobalt/black_box_tests/tests/web_debugger.py
+++ b/src/cobalt/black_box_tests/tests/web_debugger.py
@@ -11,7 +11,6 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
 """Connect to the web debugger and run some commands."""
 
 # pylint: disable=g-doc-args,g-doc-return-or-yield,g-doc-exception
@@ -33,8 +32,8 @@
 
 sys.path.append(
     os.path.join(
-        os.path.dirname(__file__), '..', '..', '..',
-        'third_party', 'websocket-client'))
+        os.path.dirname(__file__), '..', '..', '..', 'third_party',
+        'websocket-client'))
 import websocket  # pylint: disable=g-bad-import-order,g-import-not-at-top
 
 # Set to True to add additional logging to debug the test.
@@ -49,6 +48,14 @@
     super(DebuggerCommandError, self).__init__(code + error['message'])
 
 
+class DebuggerEventError(Exception):
+  """Exception when an unexpected event is received."""
+
+  def __init__(self, expected, actual):
+    super(DebuggerEventError,
+          self).__init__('Waiting for {} but got {}'.format(expected, actual))
+
+
 class JavaScriptError(Exception):
   """Exception when a JavaScript exception occurs in an evaluation."""
 
@@ -67,7 +74,7 @@
   waiting for a command response or event.
   """
 
-  def __init__(self, ws_url, timeout=3):
+  def __init__(self, ws_url, timeout=1):
     self.ws_url = ws_url
     self.timeout = timeout
     self.last_id = 0
@@ -124,10 +131,10 @@
     while command_id not in self.responses:
       try:
         self._receive_message()
-      except websocket.WebSocketTimeoutException as err:
-        # Show which command response we were waiting for that timed out.
-        err.args = ('Command', self.commands[command_id])
-        raise
+      except websocket.WebSocketTimeoutException:
+        method = self.commands[command_id]['method']
+        raise DebuggerCommandError(
+            {'message': 'Timeout waiting for response to ' + method})
     self.commands.pop(command_id)
     return self.responses.pop(command_id)
 
@@ -135,22 +142,35 @@
     """Wait for an event to arrive.
 
     Fails the test with an exception if the WebSocket times out without
-    receiving the event.
+    receiving the event, or another event arrives first.
     """
-    # Check if we already received the event.
-    for event in self.events:
-      if event['method'] == method:
-        self.events.remove(event)
-        return event
-    # Receive messages until the event we want arrives.
-    while not self.events or self.events[-1]['method'] != method:
+
+    # "Debugger.scriptParsed" events get sent as artifacts of the debugger
+    # backend implementation running its own injected code, so they are ignored
+    # unless that's what we're waiting for.
+    allow_script_parsed = (method == 'Debugger.scriptParsed')
+
+    # Pop already-received events from the event queue.
+    def _next_event():
+      while self.events:
+        event = self.events.pop(0)
+        if allow_script_parsed or event['method'] != 'Debugger.scriptParsed':
+          return event
+      return None
+
+    # Take the next event in the queue. If the queue is empty, receive messages
+    # until an event arrives and is put in the queue for us to take.
+    while True:
+      event = _next_event()
+      if event:
+        break
       try:
         self._receive_message()
-      except websocket.WebSocketTimeoutException as err:
-        # Show which event we were waiting for that timed out.
-        err.args = ('Event', method)
-        raise
-    return self.events.pop()
+      except websocket.WebSocketTimeoutException:
+        raise DebuggerEventError(method, 'None (timeout)')
+    if method != event['method']:
+      raise DebuggerEventError(method, event['method'])
+    return event
 
   def _receive_message(self):
     """Receives one message and stores it in either responses or events."""
@@ -175,6 +195,8 @@
 
   def evaluate_js(self, expression):
     """Helper for the 'Runtime.evaluate' command to run some JavaScript."""
+    if _DEBUG:
+      logging.debug('JavaScript eval -------- %s', expression)
     response = self.run_command('Runtime.evaluate', {
         'contextId': self.context_id,
         'expression': expression,
@@ -193,11 +215,12 @@
     return val
 
   def setUp(self):
-    platform_vars = self.platform_config.GetVariables(self.device_params.config)
+    platform_vars = self.platform_config.GetVariables(
+        self.launcher_params.config)
     if platform_vars['javascript_engine'] != 'v8':
       self.skipTest('DevTools requires V8')
 
-    cobalt_vars = self.cobalt_config.GetVariables(self.device_params.config)
+    cobalt_vars = self.cobalt_config.GetVariables(self.launcher_params.config)
     if not cobalt_vars['enable_debugger']:
       self.skipTest('DevTools is disabled on this platform')
 
@@ -209,6 +232,16 @@
     self.runner.WaitForJSTestsSetup()
     self.debugger.enable_runtime()
 
+  def tearDown(self):
+    self.debugger.evaluate_js('onEndTest()')
+    self.assertTrue(self.runner.JSTestsSucceeded())
+    unprocessed_events = [
+        e['method']
+        for e in self.debugger.events
+        if e['method'] != 'Debugger.scriptParsed'
+    ]
+    self.assertEqual([], unprocessed_events)
+
   def create_debugger_connection(self):
     devtools_url = self.runner.GetCval('Cobalt.Server.DevTools')
     parts = list(urlparse.urlsplit(devtools_url))
@@ -234,11 +267,7 @@
     console_event = self.debugger.wait_event('Runtime.consoleAPICalled')
     self.assertEqual('hello', console_event['params']['args'][0]['value'])
 
-    # End the test.
-    self.debugger.evaluate_js('onEndTest()')
-    self.assertTrue(self.runner.JSTestsSucceeded())
-
-  def test_dom(self):
+  def test_dom_tree(self):
     self.debugger.run_command('DOM.enable')
 
     doc_response = self.debugger.run_command('DOM.getDocument')
@@ -255,123 +284,543 @@
     body_node = html_node['children'][1]
     self.assertEqual('BODY', body_node['nodeName'])
 
-    # body:
+    # <body>
     #   <h1><span>Web debugger</span></h1>
     #   <div#test>
     #     <div#A><div#A1/><div#A2/></div#A>
-    #     <div#B/>
+    #     <div#B><span#B1/></div#B>
+    #     <span#C/>
     #   </div#test>
-    self.debugger.run_command('DOM.requestChildNodes', {
-        'nodeId': body_node['nodeId'],
-        'depth': -1,  # entire subtree
-    })
+    #   <script/>
+    # </body>
+
+    # Request children of BODY including the entire subtree.
+    self.debugger.run_command(
+        'DOM.requestChildNodes',
+        {
+            'nodeId': body_node['nodeId'],
+            'depth': -1,  # entire subtree
+        })
     child_nodes_event = self.debugger.wait_event('DOM.setChildNodes')
+    self.assertEqual(3, len(child_nodes_event['params']['nodes']))
 
     h1 = child_nodes_event['params']['nodes'][0]
-    span = h1['children'][0]
-    text = span['children'][0]
+    span_h1 = h1['children'][0]
+    text_h1 = span_h1['children'][0]
+    self.assertEqual(1, len(h1['children']))
+    self.assertEqual(1, len(span_h1['children']))
+    self.assertNotIn('children', text_h1)
+    self.assertEqual(1, h1['childNodeCount'])
+    self.assertEqual(1, span_h1['childNodeCount'])  # non-whitespace text
+    self.assertEqual(0, text_h1['childNodeCount'])
     self.assertEqual('H1', h1['nodeName'])
-    self.assertEqual('SPAN', span['nodeName'])
-    self.assertEqual('#text', text['nodeName'])
-    self.assertEqual('Web debugger', text['nodeValue'])
+    self.assertEqual('SPAN', span_h1['nodeName'])
+    self.assertEqual('#text', text_h1['nodeName'])
+    self.assertEqual('Web debugger', text_h1['nodeValue'])
 
-    test_div = child_nodes_event['params']['nodes'][1]
-    child_a = test_div['children'][0]
-    child_a1 = child_a['children'][0]
-    child_a2 = child_a['children'][1]
-    child_b = test_div['children'][1]
-    self.assertEqual(2, test_div['childNodeCount'])
-    self.assertEqual(2, child_a['childNodeCount'])
-    self.assertEqual(0, child_b['childNodeCount'])
-    self.assertEqual(['id', 'test'], test_div['attributes'])
-    self.assertEqual(['id', 'A'], child_a['attributes'])
-    self.assertEqual(['id', 'A1'], child_a1['attributes'])
-    self.assertEqual(['id', 'A2'], child_a2['attributes'])
-    self.assertEqual(['id', 'B'], child_b['attributes'])
-    self.assertEqual([], child_b['children'])
+    div_test = child_nodes_event['params']['nodes'][1]
+    div_a = div_test['children'][0]
+    div_a1 = div_a['children'][0]
+    div_a2 = div_a['children'][1]
+    div_b = div_test['children'][1]
+    span_b1 = div_b['children'][0]
+    text_b1 = span_b1['children'][0]  # 1 deeper than requested
+    span_c = div_test['children'][2]
+    text_c = span_c['children'][0]
+    self.assertEqual(3, len(div_test['children']))
+    self.assertEqual(2, len(div_a['children']))
+    self.assertNotIn('children', div_a1)
+    self.assertNotIn('children', div_a2)
+    self.assertEqual(1, len(div_b['children']))
+    self.assertEqual(1, len(span_b1['children']))
+    self.assertNotIn('children', text_b1)
+    self.assertEqual(1, len(span_c['children']))
+    self.assertNotIn('children', text_c)
+    self.assertEqual(3, div_test['childNodeCount'])
+    self.assertEqual(2, div_a['childNodeCount'])
+    self.assertEqual(0, div_a1['childNodeCount'])
+    self.assertEqual(0, div_a2['childNodeCount'])
+    self.assertEqual(1, div_b['childNodeCount'])
+    self.assertEqual(0, span_b1['childNodeCount'])  # whitespace text
+    self.assertEqual(0, text_b1['childNodeCount'])
+    self.assertEqual(0, span_c['childNodeCount'])  # whitespace text
+    self.assertEqual(0, text_c['childNodeCount'])
+    self.assertEqual(['id', 'test'], div_test['attributes'])
+    self.assertEqual(['id', 'A'], div_a['attributes'])
+    self.assertEqual(['id', 'A1'], div_a1['attributes'])
+    self.assertEqual(['id', 'A2'], div_a2['attributes'])
+    self.assertEqual(['id', 'B'], div_b['attributes'])
+    self.assertEqual(['id', 'B1'], span_b1['attributes'])
+    self.assertEqual(['id', 'C'], span_c['attributes'])
+    self.assertEqual(3, text_b1['nodeType'])  # Text
+    self.assertEqual('', text_b1['nodeValue'].strip())
+    self.assertEqual(3, text_c['nodeType'])  # Text
+    self.assertEqual('', text_c['nodeValue'].strip())
 
-    # Repeat, but only to depth 2 - not reporting children of A & B.
+    # Request children of BODY to depth 2.
+    # Not reporting children of <div#A> & <div#B>.
+    # Reporting lone text child of <span#C>
     self.debugger.run_command('DOM.requestChildNodes', {
         'nodeId': body_node['nodeId'],
         'depth': 2,
     })
     child_nodes_event = self.debugger.wait_event('DOM.setChildNodes')
+    self.assertEqual(3, len(child_nodes_event['params']['nodes']))
 
-    test_div = child_nodes_event['params']['nodes'][1]
-    child_a = test_div['children'][0]
-    child_b = test_div['children'][1]
-    self.assertFalse('children' in child_a)
-    self.assertFalse('children' in child_b)
-    self.assertEqual(2, test_div['childNodeCount'])
-    self.assertEqual(2, child_a['childNodeCount'])
-    self.assertEqual(0, child_b['childNodeCount'])
-    self.assertEqual(['id', 'test'], test_div['attributes'])
-    self.assertEqual(['id', 'A'], child_a['attributes'])
-    self.assertEqual(['id', 'B'], child_b['attributes'])
+    h1 = child_nodes_event['params']['nodes'][0]
+    span_h1 = h1['children'][0]
+    text_h1 = span_h1['children'][0]  # 1 deeper than requested
+    self.assertEqual('H1', h1['nodeName'])
+    self.assertEqual('SPAN', span_h1['nodeName'])
+    self.assertEqual('#text', text_h1['nodeName'])
+    self.assertEqual('Web debugger', text_h1['nodeValue'])
 
-    # Repeat, to default depth of 1 - not reporting children of "#test".
+    div_test = child_nodes_event['params']['nodes'][1]
+    div_a = div_test['children'][0]
+    div_b = div_test['children'][1]
+    span_c = div_test['children'][2]
+    text_c = span_c['children'][0]  # 1 deeper than requested
+    self.assertEqual(3, len(div_test['children']))
+    self.assertNotIn('children', div_a)
+    self.assertNotIn('children', div_b)
+    self.assertEqual(1, len(span_c['children']))
+    self.assertNotIn('children', text_c)
+    self.assertEqual(3, div_test['childNodeCount'])
+    self.assertEqual(2, div_a['childNodeCount'])
+    self.assertEqual(1, div_b['childNodeCount'])
+    self.assertEqual(0, span_c['childNodeCount'])  # whitespace text
+    self.assertEqual(['id', 'test'], div_test['attributes'])
+    self.assertEqual(['id', 'A'], div_a['attributes'])
+    self.assertEqual(['id', 'B'], div_b['attributes'])
+    self.assertEqual(['id', 'C'], span_c['attributes'])
+    self.assertEqual(3, text_c['nodeType'])  # Text
+    self.assertEqual('', text_c['nodeValue'].strip())
+
+    # Request children of BODY to default depth of 1.
+    # Not reporting children of <div#test>
     self.debugger.run_command('DOM.requestChildNodes', {
         'nodeId': body_node['nodeId'],
     })
     child_nodes_event = self.debugger.wait_event('DOM.setChildNodes')
+    self.assertEqual(3, len(child_nodes_event['params']['nodes']))
 
-    test_div = child_nodes_event['params']['nodes'][1]
-    self.assertFalse('children' in test_div)
-    self.assertEqual(2, test_div['childNodeCount'])
-    self.assertEqual(['id', 'test'], test_div['attributes'])
+    h1 = child_nodes_event['params']['nodes'][0]
+    div_test = child_nodes_event['params']['nodes'][1]
+    self.assertNotIn('children', h1)
+    self.assertNotIn('children', div_test)
+    self.assertEqual(1, h1['childNodeCount'])
+    self.assertEqual(3, div_test['childNodeCount'])
+    self.assertEqual(['id', 'test'], div_test['attributes'])
 
-    # Get the test div as a remote object, and request it as a node.
-    # This sends a 'DOM.setChildNodes' event for each node up to the root.
-    eval_result = self.debugger.evaluate_js('document.getElementById("test")')
+  def test_dom_remote_object(self):
+    self.debugger.run_command('DOM.enable')
+
+    doc_response = self.debugger.run_command('DOM.getDocument')
+    doc_root = doc_response['result']['root']
+    html_node = doc_root['children'][0]
+    body_node = html_node['children'][1]
+    self.debugger.run_command('DOM.requestChildNodes', {
+        'nodeId': body_node['nodeId'],
+    })
+    child_nodes_event = self.debugger.wait_event('DOM.setChildNodes')
+    div_test = child_nodes_event['params']['nodes'][1]
+
+    # Get <div#A1> as a remote object, and request it as a node.
+    # This sends 'DOM.setChildNodes' events for unknown nodes on the path from
+    # <div#test>, which is the nearest ancestor that was already reported.
+    eval_result = self.debugger.evaluate_js('document.getElementById("A1")')
     node_response = self.debugger.run_command('DOM.requestNode', {
         'objectId': eval_result['result']['objectId'],
     })
-    self.assertEqual(test_div['nodeId'],
-                     node_response['result']['nodeId'])
 
-    # Event reporting the requested <div#test>
-    node_event = self.debugger.wait_event('DOM.setChildNodes')
-    self.assertEqual(test_div['nodeId'],
-                     node_event['params']['nodes'][0]['nodeId'])
-    self.assertEqual(body_node['nodeId'],
-                     node_event['params']['parentId'])
+    # Event reporting the children of <div#test>
+    child_nodes_event = self.debugger.wait_event('DOM.setChildNodes')
+    self.assertEqual(div_test['nodeId'],
+                     child_nodes_event['params']['parentId'])
+    div_a = child_nodes_event['params']['nodes'][0]
+    div_b = child_nodes_event['params']['nodes'][1]
+    self.assertEqual(['id', 'A'], div_a['attributes'])
+    self.assertEqual(['id', 'B'], div_b['attributes'])
 
-    # Event reporting the parent <body>
-    node_event = self.debugger.wait_event('DOM.setChildNodes')
-    self.assertEqual(body_node['nodeId'],
-                     node_event['params']['nodes'][0]['nodeId'])
-    self.assertEqual(html_node['nodeId'],
-                     node_event['params']['parentId'])
+    # Event reporting the children of <div#A>
+    child_nodes_event = self.debugger.wait_event('DOM.setChildNodes')
+    self.assertEqual(div_a['nodeId'], child_nodes_event['params']['parentId'])
+    div_a1 = child_nodes_event['params']['nodes'][0]
+    div_a2 = child_nodes_event['params']['nodes'][1]
+    self.assertEqual(['id', 'A1'], div_a1['attributes'])
+    self.assertEqual(['id', 'A2'], div_a2['attributes'])
 
-    # Event reporting the parent <html>
-    node_event = self.debugger.wait_event('DOM.setChildNodes')
-    self.assertEqual(html_node['nodeId'],
-                     node_event['params']['nodes'][0]['nodeId'])
-    self.assertEqual(doc_root['nodeId'], node_event['params']['parentId'])
+    # The node we requested now has an ID as reported the last event.
+    self.assertEqual(div_a1['nodeId'], node_response['result']['nodeId'])
 
     # Round trip resolving test div to an object, then back to a node.
     resolve_response = self.debugger.run_command('DOM.resolveNode', {
-        'nodeId': test_div['nodeId'],
+        'nodeId': div_test['nodeId'],
     })
     node_response = self.debugger.run_command('DOM.requestNode', {
         'objectId': resolve_response['result']['object']['objectId'],
     })
-    self.assertEqual(test_div['nodeId'],
-                     node_response['result']['nodeId'])
+    self.assertEqual(div_test['nodeId'], node_response['result']['nodeId'])
 
-    # Event reporting the requested <div#test>
-    node_event = self.debugger.wait_event('DOM.setChildNodes')
-    self.assertEqual(test_div['nodeId'],
-                     node_event['params']['nodes'][0]['nodeId'])
-    self.assertEqual(body_node['nodeId'],
-                     node_event['params']['parentId'])
-    # Ignore the other two events reporting the parents.
-    node_event = self.debugger.wait_event('DOM.setChildNodes')
-    node_event = self.debugger.wait_event('DOM.setChildNodes')
+  def test_dom_childlist_mutation(self):
+    self.debugger.run_command('DOM.enable')
 
-    # End the test.
-    self.debugger.evaluate_js('onEndTest()')
-    self.assertTrue(self.runner.JSTestsSucceeded())
+    doc_response = self.debugger.run_command('DOM.getDocument')
+    doc_root = doc_response['result']['root']
+    # document: <html><head></head><body></body></html>
+    html_node = doc_root['children'][0]
+    head_node = html_node['children'][0]
+    body_node = html_node['children'][1]
+    self.assertEqual('BODY', body_node['nodeName'])
+
+    # getDocument should return HEAD and BODY, but none of their children.
+    self.assertEqual(3, head_node['childNodeCount'])  # title, script, script
+    self.assertEqual(3, body_node['childNodeCount'])  # h1, div, script
+    self.assertNotIn('children', head_node)
+    self.assertNotIn('children', body_node)
+
+    # Request 1 level of children in the BODY
+    self.debugger.run_command('DOM.requestChildNodes', {
+        'nodeId': body_node['nodeId'],
+        'depth': 1,
+    })
+    child_nodes_event = self.debugger.wait_event('DOM.setChildNodes')
+    self.assertEqual(body_node['childNodeCount'],
+                     len(child_nodes_event['params']['nodes']))
+    h1 = child_nodes_event['params']['nodes'][0]
+    div_test = child_nodes_event['params']['nodes'][1]
+    self.assertEqual(['id', 'test'], div_test['attributes'])
+    self.assertEqual(1, h1['childNodeCount'])  # span
+    self.assertEqual(3, div_test['childNodeCount'])  # div A, div B, span C
+    self.assertNotIn('children', h1)
+    self.assertNotIn('children', div_test)
+
+    # Insert a node as a child of a node whose children aren't yet reported.
+    self.debugger.evaluate_js(
+        'elem = document.createElement("span");'
+        'elem.id = "child";'
+        'elem.textContent = "foo";'
+        'document.getElementById("test").appendChild(elem);')
+    count_event = self.debugger.wait_event('DOM.childNodeCountUpdated')
+    self.assertEqual(div_test['nodeId'], count_event['params']['nodeId'])
+    self.assertEqual(div_test['childNodeCount'] + 1,
+                     count_event['params']['childNodeCount'])
+
+    # Remove a child from a node whose children aren't yet reported.
+    self.debugger.evaluate_js('elem = document.getElementById("test");'
+                              'elem.removeChild(elem.lastChild);')
+    count_event = self.debugger.wait_event('DOM.childNodeCountUpdated')
+    self.assertEqual(div_test['nodeId'], count_event['params']['nodeId'])
+    self.assertEqual(div_test['childNodeCount'],
+                     count_event['params']['childNodeCount'])
+
+    # Request the children of the test div to repeat the insert/remove tests
+    # after its children have been reported.
+    self.debugger.run_command('DOM.requestChildNodes', {
+        'nodeId': div_test['nodeId'],
+        'depth': 1,
+    })
+    child_nodes_event = self.debugger.wait_event('DOM.setChildNodes')
+    self.assertEqual(div_test['nodeId'],
+                     child_nodes_event['params']['parentId'])
+    self.assertEqual(div_test['childNodeCount'],
+                     len(child_nodes_event['params']['nodes']))
+
+    # Insert a node as a child of a node whose children have been reported.
+    self.debugger.evaluate_js(
+        'elem = document.createElement("span");'
+        'elem.id = "child";'
+        'elem.textContent = "foo";'
+        'document.getElementById("test").appendChild(elem);')
+    inserted_event = self.debugger.wait_event('DOM.childNodeInserted')
+    self.assertEqual(div_test['nodeId'],
+                     inserted_event['params']['parentNodeId'])
+    self.assertEqual(child_nodes_event['params']['nodes'][-1]['nodeId'],
+                     inserted_event['params']['previousNodeId'])
+
+    # Remove a child from a node whose children have been reported.
+    self.debugger.evaluate_js('elem = document.getElementById("test");'
+                              'elem.removeChild(elem.lastChild);')
+    removed_event = self.debugger.wait_event('DOM.childNodeRemoved')
+    self.assertEqual(div_test['nodeId'],
+                     removed_event['params']['parentNodeId'])
+    self.assertEqual(inserted_event['params']['node']['nodeId'],
+                     removed_event['params']['nodeId'])
+
+    # Move a subtree to another part of the DOM that has not yet been reported.
+    # (Get the original children of <div#test> to depth 1)
+    self.debugger.run_command('DOM.requestChildNodes', {
+        'nodeId': div_test['nodeId'],
+        'depth': 1,
+    })
+    child_nodes_event = self.debugger.wait_event('DOM.setChildNodes')
+    orig_num_children = len(child_nodes_event['params']['nodes'])
+    orig_div_a = child_nodes_event['params']['nodes'][0]
+    orig_span_c = child_nodes_event['params']['nodes'][2]
+    self.assertEqual(['id', 'A'], orig_div_a['attributes'])
+    self.assertEqual(['id', 'C'], orig_span_c['attributes'])
+    # (Move <span#C> into <div#A>)
+    self.debugger.evaluate_js('a = document.getElementById("A");'
+                              'c = document.getElementById("C");'
+                              'a.appendChild(c);')
+    removed_event = self.debugger.wait_event('DOM.childNodeRemoved')
+    self.assertEqual(orig_span_c['nodeId'], removed_event['params']['nodeId'])
+    count_event = self.debugger.wait_event('DOM.childNodeCountUpdated')
+    self.assertEqual(orig_div_a['nodeId'], count_event['params']['nodeId'])
+    self.assertEqual(orig_div_a['childNodeCount'] + 1,
+                     count_event['params']['childNodeCount'])
+    # (Check the moved children of <div#test>)
+    self.debugger.run_command('DOM.requestChildNodes', {
+        'nodeId': div_test['nodeId'],
+        'depth': 2,
+    })
+    child_nodes_event = self.debugger.wait_event('DOM.setChildNodes')
+    div_a = child_nodes_event['params']['nodes'][0]
+    moved_span_c = div_a['children'][2]
+    self.assertEqual(orig_num_children - 1,
+                     len(child_nodes_event['params']['nodes']))
+    self.assertEqual(['id', 'C'], moved_span_c['attributes'])
+    self.assertNotEqual(orig_span_c['nodeId'], moved_span_c['nodeId'])
+
+    # Move a subtree to another part of the DOM that has already been reported.
+    # (Move <div#B> into <div#A>)
+    orig_div_b = child_nodes_event['params']['nodes'][1]
+    orig_span_b1 = orig_div_b['children'][0]
+    self.debugger.evaluate_js('a = document.getElementById("A");'
+                              'b = document.getElementById("B");'
+                              'a.appendChild(b);')
+    removed_event = self.debugger.wait_event('DOM.childNodeRemoved')
+    self.assertEqual(orig_div_b['nodeId'], removed_event['params']['nodeId'])
+    inserted_event = self.debugger.wait_event('DOM.childNodeInserted')
+    moved_div_b = inserted_event['params']['node']
+    self.assertEqual(['id', 'B'], moved_div_b['attributes'])
+    self.assertNotEqual(orig_div_b['nodeId'], moved_div_b['nodeId'])
+    # (Check the children of moved <div#B>)
+    self.debugger.run_command('DOM.requestChildNodes', {
+        'nodeId': moved_div_b['nodeId'],
+    })
+    child_nodes_event = self.debugger.wait_event('DOM.setChildNodes')
+    moved_span_b1 = child_nodes_event['params']['nodes'][0]
+    self.assertEqual(['id', 'B1'], moved_span_b1['attributes'])
+    self.assertNotEqual(orig_span_b1['nodeId'], moved_span_b1['nodeId'])
+
+    # Replace a subtree with innerHTML
+    # (replace all children of <div#B>)
+    inner_html = "<div id='D'>\\n</div>"
+    self.debugger.evaluate_js('b = document.getElementById("B");'
+                              'b.innerHTML = "%s"' % inner_html)
+    removed_event = self.debugger.wait_event('DOM.childNodeRemoved')
+    self.assertEqual(moved_span_b1['nodeId'], removed_event['params']['nodeId'])
+    inserted_event = self.debugger.wait_event('DOM.childNodeInserted')
+    self.assertEqual(moved_div_b['nodeId'],
+                     inserted_event['params']['parentNodeId'])
+    div_d = inserted_event['params']['node']
+    self.assertEqual(['id', 'D'], div_d['attributes'])
+    self.assertEqual(0, div_d['childNodeCount'])  # Whitespace not reported.
+
+  def test_dom_text_mutation(self):
+    self.debugger.run_command('DOM.enable')
+
+    doc_response = self.debugger.run_command('DOM.getDocument')
+    doc_root = doc_response['result']['root']
+    # document: <html><head></head><body></body></html>
+    html_node = doc_root['children'][0]
+    body_node = html_node['children'][1]
+
+    # Request 2 levels of children in the BODY
+    self.debugger.run_command('DOM.requestChildNodes', {
+        'nodeId': body_node['nodeId'],
+        'depth': 2,
+    })
+    child_nodes_event = self.debugger.wait_event('DOM.setChildNodes')
+    div_test = child_nodes_event['params']['nodes'][1]
+
+    # Unrequested lone text node at depth+1 in <h1><span>.
+    h1 = child_nodes_event['params']['nodes'][0]
+    h1_span = h1['children'][0]
+    h1_text = h1_span['children'][0]
+    self.assertEqual(h1['childNodeCount'], len(h1['children']))
+    self.assertEqual(3, h1_text['nodeType'])  # Text
+    self.assertEqual('Web debugger', h1_text['nodeValue'])
+
+    # Unrequested lone whitespace text node at depth+1 in <span#B1>
+    div_b = div_test['children'][1]
+    self.debugger.run_command('DOM.requestChildNodes', {
+        'nodeId': div_b['nodeId'],
+        'depth': 1,
+    })
+    child_nodes_event = self.debugger.wait_event('DOM.setChildNodes')
+    span_b1 = child_nodes_event['params']['nodes'][0]
+    text_b1 = span_b1['children'][0]
+    self.assertEqual(3, text_b1['nodeType'])  # Text
+    self.assertEqual('', text_b1['nodeValue'].strip())
+
+    # Changing a text node's value mutates character data.
+    self.debugger.evaluate_js('text = document.getElementById("B1").firstChild;'
+                              'text.nodeValue = "Hello";')
+    data_event = self.debugger.wait_event('DOM.characterDataModified')
+    self.assertEqual(text_b1['nodeId'], data_event['params']['nodeId'])
+    self.assertEqual('Hello', data_event['params']['characterData'])
+
+    # Setting whitespace in a lone text node reports it removed.
+    self.debugger.evaluate_js('text = document.getElementById("B1").firstChild;'
+                              'text.nodeValue = "";')
+    removed_event = self.debugger.wait_event('DOM.childNodeRemoved')
+    self.assertEqual(span_b1['nodeId'], removed_event['params']['parentNodeId'])
+    self.assertEqual(text_b1['nodeId'], removed_event['params']['nodeId'])
+
+    # Setting text in a lone whitespace text node reports it inserted.
+    self.debugger.evaluate_js('text = document.getElementById("B1").firstChild;'
+                              'text.nodeValue = "Revived";')
+    inserted_event = self.debugger.wait_event('DOM.childNodeInserted')
+    self.assertEqual(span_b1['nodeId'],
+                     inserted_event['params']['parentNodeId'])
+    self.assertEqual(0, inserted_event['params']['previousNodeId'])
+    self.assertEqual(3, inserted_event['params']['node']['nodeType'])  # Text
+    self.assertEqual('Revived', inserted_event['params']['node']['nodeValue'])
+    self.assertNotEqual(text_b1['nodeId'],
+                        inserted_event['params']['node']['nodeId'])
+
+    # Setting text in a whitespace sibling text node reports it inserted.
+    self.debugger.evaluate_js(
+        'text = document.getElementById("B1").nextSibling;'
+        'text.nodeValue = "Filled";')
+    inserted_event = self.debugger.wait_event('DOM.childNodeInserted')
+    self.assertEqual(div_b['nodeId'], inserted_event['params']['parentNodeId'])
+    self.assertEqual(span_b1['nodeId'],
+                     inserted_event['params']['previousNodeId'])
+    self.assertEqual(3, inserted_event['params']['node']['nodeType'])  # Text
+    self.assertEqual('Filled', inserted_event['params']['node']['nodeValue'])
+    self.assertNotEqual(text_b1['nodeId'],
+                        inserted_event['params']['node']['nodeId'])
+
+    # Setting whitespace in a sibling text node reports it removed.
+    self.debugger.evaluate_js(
+        'text = document.getElementById("B1").nextSibling;'
+        'text.nodeValue = "";')
+    removed_event = self.debugger.wait_event('DOM.childNodeRemoved')
+    self.assertEqual(div_b['nodeId'], removed_event['params']['parentNodeId'])
+    self.assertEqual(inserted_event['params']['node']['nodeId'],
+                     removed_event['params']['nodeId'])
+
+    # Setting textContent removes all children and inserts a new text node.
+    self.debugger.evaluate_js(
+        'document.getElementById("B").textContent = "One";')
+    removed_event = self.debugger.wait_event('DOM.childNodeRemoved')
+    self.assertEqual(div_b['nodeId'], removed_event['params']['parentNodeId'])
+    self.assertEqual(span_b1['nodeId'], removed_event['params']['nodeId'])
+    inserted_event = self.debugger.wait_event('DOM.childNodeInserted')
+    self.assertEqual(div_b['nodeId'], inserted_event['params']['parentNodeId'])
+    self.assertEqual(0, inserted_event['params']['previousNodeId'])
+    self.assertEqual(3, inserted_event['params']['node']['nodeType'])  # Text
+    self.assertEqual('One', inserted_event['params']['node']['nodeValue'])
+
+    # Setting empty textContent removes all children without inserting anything.
+    self.debugger.evaluate_js('document.getElementById("B").textContent = "";')
+    removed_event = self.debugger.wait_event('DOM.childNodeRemoved')
+    self.assertEqual(div_b['nodeId'], removed_event['params']['parentNodeId'])
+    self.assertEqual(inserted_event['params']['node']['nodeId'],
+                     removed_event['params']['nodeId'])
+
+    # Setting textContent over empty text only inserts a new text node.
+    self.debugger.evaluate_js(
+        'document.getElementById("B").textContent = "Two";')
+    inserted_event = self.debugger.wait_event('DOM.childNodeInserted')
+    self.assertEqual(div_b['nodeId'], inserted_event['params']['parentNodeId'])
+    self.assertEqual(0, inserted_event['params']['previousNodeId'])
+    self.assertEqual(3, inserted_event['params']['node']['nodeType'])  # Text
+    self.assertEqual('Two', inserted_event['params']['node']['nodeValue'])
+
+    # Setting textContent over other text replaces the text nodes.
+    self.debugger.evaluate_js(
+        'document.getElementById("B").textContent = "Three";')
+    removed_event = self.debugger.wait_event('DOM.childNodeRemoved')
+    self.assertEqual(div_b['nodeId'], removed_event['params']['parentNodeId'])
+    self.assertEqual(inserted_event['params']['node']['nodeId'],
+                     removed_event['params']['nodeId'])
+    inserted_event = self.debugger.wait_event('DOM.childNodeInserted')
+    self.assertEqual(div_b['nodeId'], inserted_event['params']['parentNodeId'])
+    self.assertEqual(0, inserted_event['params']['previousNodeId'])
+    self.assertEqual(3, inserted_event['params']['node']['nodeType'])  # Text
+    self.assertEqual('Three', inserted_event['params']['node']['nodeValue'])
+
+    # Setting whitespace textContent removes all children without any insertion.
+    self.debugger.evaluate_js(
+        'document.getElementById("B").textContent = "\\n";')
+    removed_event = self.debugger.wait_event('DOM.childNodeRemoved')
+    self.assertEqual(div_b['nodeId'], removed_event['params']['parentNodeId'])
+    self.assertEqual(inserted_event['params']['node']['nodeId'],
+                     removed_event['params']['nodeId'])
+
+    # Setting textContent over whitespace text only inserts a new text node.
+    self.debugger.evaluate_js(
+        'document.getElementById("B").textContent = "Four";')
+    inserted_event = self.debugger.wait_event('DOM.childNodeInserted')
+    self.assertEqual(div_b['nodeId'], inserted_event['params']['parentNodeId'])
+    self.assertEqual(0, inserted_event['params']['previousNodeId'])
+    self.assertEqual(3, inserted_event['params']['node']['nodeType'])  # Text
+    self.assertEqual('Four', inserted_event['params']['node']['nodeValue'])
+
+  def test_dom_attribute_mutation(self):
+    self.debugger.run_command('DOM.enable')
+
+    doc_response = self.debugger.run_command('DOM.getDocument')
+    doc_root = doc_response['result']['root']
+    # document: <html><head></head><body></body></html>
+    html_node = doc_root['children'][0]
+    body_node = html_node['children'][1]
+
+    # Request 1 level of children in the BODY
+    self.debugger.run_command('DOM.requestChildNodes', {
+        'nodeId': body_node['nodeId'],
+        'depth': 1,
+    })
+    child_nodes_event = self.debugger.wait_event('DOM.setChildNodes')
+    h1 = child_nodes_event['params']['nodes'][0]
+
+    def assert_attr_modified(statement, name, value):
+      self.debugger.evaluate_js(
+          'elem = document.getElementsByTagName("H1")[0];' + statement)
+      attr_event = self.debugger.wait_event('DOM.attributeModified')
+      self.assertEqual(attr_event['params']['nodeId'], h1['nodeId'])
+      self.assertEqual(attr_event['params']['name'], name)
+      self.assertEqual(attr_event['params']['value'], value)
+
+    def assert_attr_removed(statement, name):
+      self.debugger.evaluate_js(
+          'elem = document.getElementsByTagName("H1")[0];' + statement)
+      attr_event = self.debugger.wait_event('DOM.attributeRemoved')
+      self.assertEqual(attr_event['params']['nodeId'], h1['nodeId'])
+      self.assertEqual(attr_event['params']['name'], name)
+
+    # Add a normal attribute.
+    assert_attr_modified('elem.id = "foo"', 'id', 'foo')
+    # Change a normal attribute.
+    assert_attr_modified('elem.id = "bar"', 'id', 'bar')
+    # Change a normal attribute with setAttribute.
+    assert_attr_modified('elem.setAttribute("id", "baz")', 'id', 'baz')
+    # Remove a normal attribute.
+    assert_attr_removed('elem.removeAttribute("id")', 'id')
+
+    # Change the className (from 'name', as set in web_debugger.html).
+    assert_attr_modified('elem.className = "zzyzx"', 'class', 'zzyzx')
+
+    # Change a dataset value (from 'robot', as set in web_debugger.html).
+    assert_attr_modified('elem.dataset.who = "person"', 'data-who', 'person')
+    # Add a dataset value.
+    assert_attr_modified('elem.dataset.what = "thing"', 'data-what', 'thing')
+    # Remove a data attribute with delete operator.
+    # TODO: Fix this - MutationObserver is not reporting this mutation,
+    # assert_attr_removed('delete elem.dataset.who', 'data-who')
+
+    # Change a data attribute with setAttribute.
+    assert_attr_modified('elem.setAttribute("data-what", "object")',
+                         'data-what', 'object')
+    # Add a data attribute with setAttribute.
+    assert_attr_modified('elem.setAttribute("data-where", "place")',
+                         'data-where', 'place')
+    # Remove a data attribute with removeAttribute.
+    assert_attr_removed('elem.removeAttribute("data-what")', 'data-what')
 
   def assert_paused(self, expected_stacks):
     """Checks that the debugger is paused at a breakpoint.
@@ -385,6 +834,10 @@
       expected_stacks: A list of lists of strings with the expected function
         names in the call stacks in a series of asynchronous executions.
     """
+    # Expect an anonymous frame from the backend eval that's running the code
+    # we sent in the "Runtime.evaluate" command.
+    expected_stacks[-1] += ['']
+
     paused_event = self.debugger.wait_event('Debugger.paused')
     try:
       call_stacks = []
@@ -392,7 +845,7 @@
       call_frames = paused_event['params']['callFrames']
       call_stack = [frame['functionName'] for frame in call_frames]
       call_stacks.append(call_stack)
-      # Then asynchronous stacks that preceeded the main stack.
+      # Then asynchronous stacks that preceded the main stack.
       async_trace = paused_event['params'].get('asyncStackTrace')
       while async_trace:
         call_frames = async_trace['callFrames']
@@ -403,6 +856,7 @@
     finally:
       # We must resume in order to avoid hanging if something goes wrong.
       self.debugger.run_command('Debugger.resume')
+    self.debugger.wait_event('Debugger.resumed')
 
   def test_debugger_breakpoint(self):
     self.debugger.run_command('Debugger.enable')
@@ -447,8 +901,7 @@
         [
             'asyncA',
             'testSetTimeout',
-            '',  # Anonymous function for the 'Runtime.evaluate' command.
-        ]
+        ],
     ])
 
     # Check the breakpoint within a promise "then" after being resolved.
@@ -461,8 +914,7 @@
         [
             'waitPromise',
             'testPromise',
-            '',  # Anonymous function for the 'Runtime.evaluate' command.
-        ]
+        ],
     ])
 
     # Check the breakpoint after async await for a promise to resolve.
@@ -475,8 +927,7 @@
         [
             'asyncAwait',
             'testAsyncFunction',
-            '',  # Anonymous function for the 'Runtime.evaluate' command.
-        ]
+        ],
     ])
 
     # Check the breakpoint within an XHR event handler.
@@ -489,8 +940,7 @@
         [
             'doXHR',
             'testXHR',
-            '',  # Anonymous function for the 'Runtime.evaluate' command.
-        ]
+        ],
     ])
 
     # Check the breakpoint within a MutationObserver.
@@ -503,7 +953,6 @@
         [
             'doSetAttribute',
             'testMutate',
-            '',  # Anonymous function for the 'Runtime.evaluate' command.
         ],
     ])
 
@@ -517,7 +966,6 @@
         [
             'doRequestAnimationFrame',
             'testAnimationFrame',
-            '',  # Anonymous function for the 'Runtime.evaluate' command.
         ],
     ])
 
@@ -531,10 +979,5 @@
         [
             'setSourceListener',
             'testMediaSource',
-            '',  # Anonymous function for the 'Runtime.evaluate' command.
         ],
     ])
-
-    # End the test.
-    self.debugger.evaluate_js('onEndTest()')
-    self.assertTrue(self.runner.JSTestsSucceeded())
diff --git a/src/cobalt/black_box_tests/tests/web_platform_tests.py b/src/cobalt/black_box_tests/tests/web_platform_tests.py
index 283b5d6..c2a1609 100644
--- a/src/cobalt/black_box_tests/tests/web_platform_tests.py
+++ b/src/cobalt/black_box_tests/tests/web_platform_tests.py
@@ -12,7 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
 from __future__ import absolute_import
 from __future__ import division
 from __future__ import print_function
@@ -29,12 +28,13 @@
   """Launch web platform tests."""
 
   def setUp(self):
-    if self.device_params.config not in ['debug', 'devel']:
+    if self.launcher_params.config not in ['debug', 'devel']:
       self.skipTest('Can only run web platform tests on debug or devel config.')
 
   def test_simple(self):
-    with WebPlatformTestServer(binding_address=self.GetBindingAddress(),
-                               wpt_http_port=self.GetWptHttpPort()):
+    with WebPlatformTestServer(
+        binding_address=self.GetBindingAddress(),
+        wpt_http_port=self.GetWptHttpPort()):
       target_params = []
 
       filters = self.cobalt_config.GetWebPlatformTestFilters()
@@ -46,8 +46,10 @@
         if filter == test_filter.FILTER_ALL:
           return
         if isinstance(filter, test_filter.TestFilter):
-          if filter.config and filter.config != self.device_params.config:
+          if filter.config and filter.config != self.launcher_params.config:
             continue
+          if filter.test_name and filter.test_name == test_filter.FILTER_ALL:
+            return
           used_filters.append(filter.test_name)
         else:
           used_filters.append(filter)
@@ -56,17 +58,19 @@
         target_params.append('--gtest_filter=-{}'.format(
             ':'.join(used_filters)))
 
-      if self.device_params.target_params:
-        target_params += self.device_params.target_params
+      if self.launcher_params.target_params:
+        target_params += self.launcher_params.target_params
 
       launcher = abstract_launcher.LauncherFactory(
-          self.device_params.platform,
+          self.launcher_params.platform,
           'web_platform_tests',
-          self.device_params.config,
-          device_id=self.device_params.device_id,
+          self.launcher_params.config,
+          device_id=self.launcher_params.device_id,
           target_params=target_params,
           output_file=None,
-          out_directory=self.device_params.out_directory,
-          env_variables={'ASAN_OPTIONS': 'intercept_tls_get_addr=0'})
+          out_directory=self.launcher_params.out_directory,
+          env_variables={'ASAN_OPTIONS': 'intercept_tls_get_addr=0'},
+          loader_platform=self.launcher_params.loader_platform,
+          loader_config=self.launcher_params.loader_config)
       status = launcher.Run()
       self.assertEqual(status, 0)
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index 2684cb9..1541f70 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -66,6 +66,7 @@
 #include "cobalt/system_window/input_event.h"
 #include "cobalt/trace_event/scoped_trace_to_file.h"
 #include "starboard/configuration.h"
+#include "starboard/system.h"
 #include "url/gurl.h"
 
 using cobalt::cssom::ViewportSize;
@@ -96,8 +97,14 @@
 #else
   ip_v6 = false;
 #endif
+  // Default to INADDR_ANY
   std::string listen_ip(ip_v6 ? "::" : "0.0.0.0");
 
+  // Desktop PCs default to loopback.
+  if (SbSystemGetDeviceType() == kSbSystemDeviceTypeDesktopPC) {
+    listen_ip = ip_v6 ? "::1" : "127.0.0.1";
+  }
+
 #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
   if (command_line->HasSwitch(switches::kDevServersListenIp)) {
@@ -687,8 +694,8 @@
                                         : base::kApplicationStateStarted),
                         &event_dispatcher_, account_manager_.get(), options));
 #if SB_IS(EVERGREEN)
-  updater_module_.reset(new updater::UpdaterModule(
-      message_loop_, browser_module_->GetNetworkModule()));
+  updater_module_.reset(
+      new updater::UpdaterModule(browser_module_->GetNetworkModule()));
 #endif
   UpdateUserAgent();
 
@@ -791,11 +798,6 @@
         base::TimeDelta::FromSeconds(duration_in_seconds));
   }
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
-
-#if SB_IS(EVERGREEN)
-  // Run the first update check after the application is started.
-  updater_module_->Update();
-#endif
 }
 
 Application::~Application() {
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index 9b4b07b..59982d1 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -380,6 +380,9 @@
     options_.web_module_options.limit_performance_timer_resolution = false;
   }
 
+  options_.web_module_options.enable_inline_script_warnings =
+      !command_line->HasSwitch(switches::kSilenceInlineScriptWarnings);
+
 #if defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
   options_.web_module_options.enable_partial_layout =
       !command_line->HasSwitch(switches::kDisablePartialLayout);
diff --git a/src/cobalt/browser/memory_tracker/tool.cc b/src/cobalt/browser/memory_tracker/tool.cc
index 2a627e5..d262e2f 100644
--- a/src/cobalt/browser/memory_tracker/tool.cc
+++ b/src/cobalt/browser/memory_tracker/tool.cc
@@ -35,6 +35,7 @@
 #include "nb/analytics/memory_tracker_helpers.h"
 #include "starboard/common/log.h"
 #include "starboard/configuration.h"
+#include "starboard/configuration_constants.h"
 #include "starboard/double.h"
 #include "starboard/file.h"
 
@@ -121,12 +122,12 @@
     const int back_idx_signed = static_cast<int>(path.length()) - 1;
     if (back_idx_signed >= 0) {
       const size_t idx = back_idx_signed;
-      if (path[idx] == SB_FILE_SEP_CHAR) {
+      if (path[idx] == kSbFileSepChar) {
         path.erase(idx);
       }
     }
   }
-  path.push_back(SB_FILE_SEP_CHAR);
+  path.push_back(kSbFileSepChar);
   path.append(filename);
   int flags = kSbFileCreateAlways | kSbFileWrite;
   bool created_ok = false;
diff --git a/src/cobalt/browser/memory_tracker/tool/internal_fragmentation_tool.cc b/src/cobalt/browser/memory_tracker/tool/internal_fragmentation_tool.cc
index 7f2422b..223af84 100644
--- a/src/cobalt/browser/memory_tracker/tool/internal_fragmentation_tool.cc
+++ b/src/cobalt/browser/memory_tracker/tool/internal_fragmentation_tool.cc
@@ -23,6 +23,8 @@
 #include "cobalt/browser/memory_tracker/tool/params.h"
 #include "cobalt/browser/memory_tracker/tool/util.h"
 #include "nb/bit_cast.h"
+// Starboard headers.
+#include "starboard/configuration_constants.h"
 
 namespace cobalt {
 namespace browser {
@@ -148,7 +150,7 @@
   MemoryBytesHistogramCSV histogram_table;
   histogram_table.set_title("Internal Fragmentation - Probably undercounts");
 
-  FragmentationProcessor visitor(SB_MEMORY_PAGE_SIZE);
+  FragmentationProcessor visitor(kSbMemoryPageSize);
 
   // If we get a finish signal then this will break out of the loop.
   while (!params->wait_for_finish_signal(250 * kSbTimeMillisecond)) {
diff --git a/src/cobalt/browser/memory_tracker/tool/log_writer_tool.cc b/src/cobalt/browser/memory_tracker/tool/log_writer_tool.cc
index 0e58e29..3729103 100644
--- a/src/cobalt/browser/memory_tracker/tool/log_writer_tool.cc
+++ b/src/cobalt/browser/memory_tracker/tool/log_writer_tool.cc
@@ -22,6 +22,7 @@
 #include "starboard/client_porting/poem/string_poem.h"
 #include "starboard/common/string.h"
 #include "starboard/configuration.h"
+#include "starboard/configuration_constants.h"
 #include "starboard/types.h"
 
 namespace cobalt {
@@ -135,12 +136,12 @@
     const int back_idx_signed = static_cast<int>(path.length()) - 1;
     if (back_idx_signed >= 0) {
       const size_t idx = back_idx_signed;
-      if (path[idx] == SB_FILE_SEP_CHAR) {
+      if (path[idx] == kSbFileSepChar) {
         path.erase(idx);
       }
     }
   }
-  path.push_back(SB_FILE_SEP_CHAR);
+  path.push_back(kSbFileSepChar);
   path.append("memory_log.txt");
   return path;
 }
diff --git a/src/cobalt/browser/screen_shot_writer.cc b/src/cobalt/browser/screen_shot_writer.cc
index 93dccc7..8b27ca4 100644
--- a/src/cobalt/browser/screen_shot_writer.cc
+++ b/src/cobalt/browser/screen_shot_writer.cc
@@ -27,7 +27,7 @@
 namespace browser {
 
 ScreenShotWriter::ScreenShotWriter(renderer::Pipeline* pipeline)
-    : pipeline_(pipeline), screenshot_thread_("Screenshot IO thread") {
+    : pipeline_(pipeline), screenshot_thread_("ScreenshotIOThd") {
   DCHECK(pipeline);
   screenshot_thread_.Start();
 }
diff --git a/src/cobalt/browser/splash_screen_cache.cc b/src/cobalt/browser/splash_screen_cache.cc
index 0dfa22b..43cb6f7 100644
--- a/src/cobalt/browser/splash_screen_cache.cc
+++ b/src/cobalt/browser/splash_screen_cache.cc
@@ -16,6 +16,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/hash.h"
 #include "base/optional.h"
@@ -23,6 +24,7 @@
 #include "base/synchronization/lock.h"
 #include "cobalt/base/get_application_key.h"
 #include "starboard/common/string.h"
+#include "starboard/configuration_constants.h"
 #include "starboard/directory.h"
 #include "starboard/file.h"
 
@@ -30,21 +32,23 @@
 namespace browser {
 namespace {
 bool CreateDirsForKey(const std::string& key) {
-  char path[SB_FILE_MAX_PATH] = {0};
-  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path, SB_FILE_MAX_PATH)) {
+  std::vector<char> path(kSbFileMaxPath, 0);
+  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path.data(),
+                       kSbFileMaxPath)) {
     return false;
   }
   std::size_t prev_found = 0;
-  std::size_t found = key.find(SB_FILE_SEP_STRING);
-  SbStringConcat(path, SB_FILE_SEP_STRING, SB_FILE_MAX_PATH);
+  std::size_t found = key.find(kSbFileSepString);
+  SbStringConcat(path.data(), kSbFileSepString, kSbFileMaxPath);
   while (found != std::string::npos) {
-    SbStringConcat(path, key.substr(prev_found, found - prev_found).c_str(),
-                   SB_FILE_MAX_PATH);
-    if (!SbDirectoryCreate(path)) {
+    SbStringConcat(path.data(),
+                   key.substr(prev_found, found - prev_found).c_str(),
+                   kSbFileMaxPath);
+    if (!SbDirectoryCreate(path.data())) {
       return false;
     }
     prev_found = found;
-    found = key.find(SB_FILE_SEP_STRING, prev_found + 1);
+    found = key.find(kSbFileSepString, prev_found + 1);
   }
   return true;
 }
@@ -67,14 +71,15 @@
     return false;
   }
 
-  char path[SB_FILE_MAX_PATH] = {0};
-  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path, SB_FILE_MAX_PATH)) {
+  std::vector<char> path(kSbFileMaxPath, 0);
+  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path.data(),
+                       kSbFileMaxPath)) {
     return false;
   }
   if (!CreateDirsForKey(key)) {
     return false;
   }
-  std::string full_path = path + (SB_FILE_SEP_STRING + key);
+  std::string full_path = std::string(path.data()) + kSbFileSepString + key;
   starboard::ScopedFile cache_file(
       full_path.c_str(), kSbFileCreateAlways | kSbFileWrite, NULL, NULL);
 
@@ -84,11 +89,12 @@
 
 bool SplashScreenCache::IsSplashScreenCached(const std::string& key) const {
   base::AutoLock lock(lock_);
-  char path[SB_FILE_MAX_PATH] = {0};
-  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path, SB_FILE_MAX_PATH)) {
+  std::vector<char> path(kSbFileMaxPath, 0);
+  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path.data(),
+                       kSbFileMaxPath)) {
     return false;
   }
-  std::string full_path = path + (SB_FILE_SEP_STRING + key);
+  std::string full_path = std::string(path.data()) + kSbFileSepString + key;
   return !key.empty() && SbFileExists(full_path.c_str());
 }
 
@@ -98,12 +104,13 @@
   if (!result) {
     return 0;
   }
-  char path[SB_FILE_MAX_PATH] = {0};
-  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path, SB_FILE_MAX_PATH)) {
+  std::vector<char> path(kSbFileMaxPath, 0);
+  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path.data(),
+                       kSbFileMaxPath)) {
     result->reset();
     return 0;
   }
-  std::string full_path = path + (SB_FILE_SEP_STRING + key);
+  std::string full_path = std::string(path.data()) + kSbFileSepString + key;
   starboard::ScopedFile cache_file(full_path.c_str(),
                                    kSbFileOpenOnly | kSbFileRead, NULL, NULL);
   SbFileInfo info;
@@ -127,29 +134,29 @@
     return base::nullopt;
   }
 
-  char path[SB_FILE_MAX_PATH] = {0};
+  std::vector<char> path(kSbFileMaxPath, 0);
   bool has_cache_dir =
-      SbSystemGetPath(kSbSystemPathCacheDirectory, path, SB_FILE_MAX_PATH);
+      SbSystemGetPath(kSbSystemPathCacheDirectory, path.data(), kSbFileMaxPath);
   if (!has_cache_dir) {
     return base::nullopt;
   }
 
   std::string subpath = "";
-  std::string subcomponent = SB_FILE_SEP_STRING + std::string("splash_screen");
-  if (SbStringConcat(path, subcomponent.c_str(), SB_FILE_MAX_PATH) >=
-      SB_FILE_MAX_PATH) {
+  std::string subcomponent = kSbFileSepString + std::string("splash_screen");
+  if (SbStringConcat(path.data(), subcomponent.c_str(), kSbFileMaxPath) >=
+      static_cast<int>(kSbFileMaxPath)) {
     return base::nullopt;
   }
   subpath += "splash_screen";
-  subcomponent = SB_FILE_SEP_STRING + *encoded_url;
-  if (SbStringConcat(path, subcomponent.c_str(), SB_FILE_MAX_PATH) >=
-      SB_FILE_MAX_PATH) {
+  subcomponent = kSbFileSepString + *encoded_url;
+  if (SbStringConcat(path.data(), subcomponent.c_str(), kSbFileMaxPath) >=
+      static_cast<int>(kSbFileMaxPath)) {
     return base::nullopt;
   }
   subpath += subcomponent;
-  subcomponent = SB_FILE_SEP_STRING + std::string("splash.html");
-  if (SbStringConcat(path, subcomponent.c_str(), SB_FILE_MAX_PATH) >
-      SB_FILE_MAX_PATH) {
+  subcomponent = kSbFileSepString + std::string("splash.html");
+  if (SbStringConcat(path.data(), subcomponent.c_str(), kSbFileMaxPath) >
+      static_cast<int>(kSbFileMaxPath)) {
     return base::nullopt;
   }
   subpath += subcomponent;
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc
index 127556b..3665aa4 100644
--- a/src/cobalt/browser/switches.cc
+++ b/src/cobalt/browser/switches.cc
@@ -351,6 +351,14 @@
     "hardware-accelerated Skia rasterizer.  While it depends on the platform, "
     "this setting may affect GPU memory usage.";
 
+const char kSilenceInlineScriptWarnings[] = "silence_inline_script_warnings";
+const char kSilenceInlineScriptWarningsHelp[] =
+    "Prevents Cobalt from logging warnings when it encounters a non-async "
+    "<script> tag inlined within HTML.  Cobalt fails to deal with these "
+    "resources properly when suspending or resuming, so the warning usually "
+    "indicates a bug if the web app intends to support suspending and resuming "
+    "properly.";
+
 const char kSkiaCacheSizeInBytes[] = "skia_cache_size_in_bytes";
 const char kSkiaCacheSizeInBytesHelp[] =
     "Determines the capacity of the skia cache.  The Skia cache is maintained "
@@ -463,6 +471,7 @@
         {kRetainRemoteTypefaceCacheDuringSuspend,
          kRetainRemoteTypefaceCacheDuringSuspendHelp},
         {kScratchSurfaceCacheSizeInBytes, kScratchSurfaceCacheSizeInBytesHelp},
+        {kSilenceInlineScriptWarnings, kSilenceInlineScriptWarningsHelp},
         {kSkiaCacheSizeInBytes, kSkiaCacheSizeInBytesHelp},
         {kSkiaTextureAtlasDimensions, kSkiaTextureAtlasDimensionsHelp},
         {kSoftwareSurfaceCacheSizeInBytes,
diff --git a/src/cobalt/browser/switches.h b/src/cobalt/browser/switches.h
index 9d400e7..6d516f9 100644
--- a/src/cobalt/browser/switches.h
+++ b/src/cobalt/browser/switches.h
@@ -141,6 +141,8 @@
 extern const char kRetainRemoteTypefaceCacheDuringSuspendHelp[];
 extern const char kScratchSurfaceCacheSizeInBytes[];
 extern const char kScratchSurfaceCacheSizeInBytesHelp[];
+extern const char kSilenceInlineScriptWarnings[];
+extern const char kSilenceInlineScriptWarningsHelp[];
 extern const char kSkiaCacheSizeInBytes[];
 extern const char kSkiaCacheSizeInBytesHelp[];
 extern const char kSkiaTextureAtlasDimensions[];
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index 3b10308..d1c4ad2 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -518,7 +518,8 @@
   supports_map_to_mesh = css_parser::Parser::kDoesNotSupportMapToMesh;
 #endif
 
-  css_parser_ = css_parser::Parser::Create(supports_map_to_mesh);
+  css_parser_ =
+      css_parser::Parser::Create(debugger_hooks_, supports_map_to_mesh);
   DCHECK(css_parser_);
 
   dom_parser_.reset(new dom_parser::Parser(
@@ -619,7 +620,7 @@
       kDOMMaxElementDepth, fetcher_factory_.get(), data.network_module,
       media_source_registry_.get(), blob_registry_.get(),
       data.can_play_type_handler, javascript_engine_.get(),
-      global_environment_.get(), &debugger_hooks_,
+      global_environment_.get(), debugger_hooks_,
       &mutation_observer_task_manager_, data.options.dom_settings_options));
   DCHECK(environment_settings_);
 
@@ -687,8 +688,9 @@
                  base::Unretained(this)),
       base::Bind(&WebModule::Impl::OnStopDispatchEvent, base::Unretained(this)),
       data.options.provide_screenshot_function, &synchronous_loader_interrupt_,
-      data.ui_nav_root, data.options.csp_insecure_allowed_token,
-      data.dom_max_element_depth, data.options.video_playback_rate_multiplier,
+      data.options.enable_inline_script_warnings, data.ui_nav_root,
+      data.options.csp_insecure_allowed_token, data.dom_max_element_depth,
+      data.options.video_playback_rate_multiplier,
 #if defined(ENABLE_TEST_RUNNER)
       data.options.layout_trigger == layout::LayoutManager::kTestRunnerMode
           ? dom::Window::kClockTypeTestRunner
diff --git a/src/cobalt/browser/web_module.h b/src/cobalt/browser/web_module.h
index 8c33295..4796e89 100644
--- a/src/cobalt/browser/web_module.h
+++ b/src/cobalt/browser/web_module.h
@@ -135,6 +135,16 @@
     // Whether Cobalt is forbidden to render without receiving CSP headers.
     csp::CSPHeaderPolicy require_csp;
 
+    // If true, Cobalt will log a warning each time it parses a non-async
+    // <script> tag inlined in HTML.  Cobalt has a known issue where if it is
+    // paused or suspended while loading inlined <script> tags, it will abort
+    // the script fetch and silently fail without any follow up actions.  It is
+    // recommended that production code always avoid non-async <script> tags
+    // inlined in HTML.  This is likely not an issue for tests, however, where
+    // we control the suspend/resume activities, so this flag can be used in
+    // these cases to disable the warning.
+    bool enable_inline_script_warnings = true;
+
     // Encoded image cache capacity in bytes.
     int encoded_image_cache_capacity = 1024 * 1024;
 
diff --git a/src/cobalt/build/all.gyp b/src/cobalt/build/all.gyp
index e484055..36bfb6e 100644
--- a/src/cobalt/build/all.gyp
+++ b/src/cobalt/build/all.gyp
@@ -94,7 +94,7 @@
         '<(DEPTH)/sql/sql.gyp:sql_unittests_deploy',
       ],
       'conditions': [
-        ['has_elf_loader == "True"', {
+        ['has_elf_loader == "True" and sb_evergreen != 1', {
           'dependencies': [
             '<(DEPTH)/starboard/elf_loader/elf_loader.gyp:elf_loader_test_deploy',
           ],
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index e411972..497b1fc 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-239287
\ No newline at end of file
+243969
\ No newline at end of file
diff --git a/src/cobalt/build/cobalt_configuration.gypi b/src/cobalt/build/cobalt_configuration.gypi
index 995c8d6..7e556be 100644
--- a/src/cobalt/build/cobalt_configuration.gypi
+++ b/src/cobalt/build/cobalt_configuration.gypi
@@ -180,8 +180,10 @@
     # milliseconds and can be a floating point number. Keep in mind that
     # swapping frames may take some additional processing time, so it may be
     # better to specify a lower delay. For example, '33' instead of '33.33'
-    # for 30 Hz refresh.
-    'cobalt_minimum_frame_time_in_milliseconds%': '16.0',
+    # for 30 Hz refresh. This value is deprecated in favor of the usage of
+    # CobaltExtensionGraphicsApi::GetMinimumFrameIntervalInMilliseconds API.
+    # The default value has been moved into cobalt/renderer/pipeline.cc.
+    'cobalt_minimum_frame_time_in_milliseconds%': '-1',
 
     # Cobalt will call eglSwapInterval() and specify this value before calling
     # eglSwapBuffers() each frame.
@@ -228,7 +230,7 @@
     # Set to "true" to enable v8 snapshot generation at Cobalt build time.
     'cobalt_v8_buildtime_snapshot%': '<(cobalt_v8_buildtime_snapshot)',
 
-    'cobalt_enable_quic': 1,
+    'cobalt_enable_quic%': 1,
 
     # Cache parameters
 
diff --git a/src/cobalt/build/cobalt_configuration.py b/src/cobalt/build/cobalt_configuration.py
index bd661d4..07a07f3 100644
--- a/src/cobalt/build/cobalt_configuration.py
+++ b/src/cobalt/build/cobalt_configuration.py
@@ -132,6 +132,7 @@
         'dom_parser_test',
         'dom_test',
         'extension_test',
+        'graphics_system_test',
         'layout_test',
         'layout_tests',
         'loader_test',
diff --git a/src/cobalt/css_parser/parser.cc b/src/cobalt/css_parser/parser.cc
index f04862b..9d957c0 100644
--- a/src/cobalt/css_parser/parser.cc
+++ b/src/cobalt/css_parser/parser.cc
@@ -32,6 +32,7 @@
 #include "base/synchronization/lock.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
+#include "cobalt/base/console_log.h"
 #include "cobalt/css_parser/grammar.h"
 #include "cobalt/css_parser/margin_or_padding_shorthand.h"
 #include "cobalt/css_parser/property_declaration.h"
@@ -500,7 +501,8 @@
     message_stream << std::endl
                    << std::string(
                           preamble.length() + column_number - substr_start - 1,
-                          ' ') << '^';
+                          ' ')
+                   << '^';
   }
 
   return message_stream.str();
@@ -549,17 +551,30 @@
 
 namespace {
 
-void LogWarningCallback(const std::string& message) { LOG(WARNING) << message; }
+void LogWarningCallback(const ::base::DebuggerHooks* debugger_hooks,
+                        const std::string& message) {
+  CLOG(WARNING, *debugger_hooks) << message;
+}
 
-void LogErrorCallback(const std::string& message) { LOG(ERROR) << message; }
+void LogErrorCallback(const ::base::DebuggerHooks* debugger_hooks,
+                      const std::string& message) {
+  CLOG(ERROR, *debugger_hooks) << message;
+}
 
 }  // namespace
 
 std::unique_ptr<Parser> Parser::Create(
+    const ::base::DebuggerHooks& debugger_hooks,
     SupportsMapToMeshFlag supports_map_to_mesh) {
-  return base::WrapUnique(new Parser(::base::Bind(&LogWarningCallback),
-                                     ::base::Bind(&LogErrorCallback),
-                                     Parser::kVerbose, supports_map_to_mesh));
+  return base::WrapUnique(new Parser(
+      ::base::Bind(&LogWarningCallback, ::base::Unretained(&debugger_hooks)),
+      ::base::Bind(&LogErrorCallback, ::base::Unretained(&debugger_hooks)),
+      Parser::kVerbose, supports_map_to_mesh));
+}
+
+std::unique_ptr<Parser> Parser::Create() {
+  static ::base::NullDebuggerHooks null_debugger_hooks;
+  return Parser::Create(null_debugger_hooks, Parser::kSupportsMapToMesh);
 }
 
 Parser::Parser(const OnMessageCallback& on_warning_callback,
diff --git a/src/cobalt/css_parser/parser.h b/src/cobalt/css_parser/parser.h
index cccf149..7740788 100644
--- a/src/cobalt/css_parser/parser.h
+++ b/src/cobalt/css_parser/parser.h
@@ -19,6 +19,7 @@
 #include <string>
 
 #include "base/callback.h"
+#include "cobalt/base/debugger_hooks.h"
 #include "cobalt/cssom/css_parser.h"
 
 namespace cobalt {
@@ -37,7 +38,12 @@
   // for the map-to-mesh CSS filter.  If disabled, "filter: map-to-mesh(...)"
   // will result in a parse error.
   static std::unique_ptr<Parser> Create(
-      SupportsMapToMeshFlag supports_map_to_mesh = kSupportsMapToMesh);
+      const ::base::DebuggerHooks& debugger_hooks,
+      SupportsMapToMeshFlag supports_map_to_mesh);
+  // No-args version for tests that don't particularly care about console
+  // logging or map-to-mesh.
+  static std::unique_ptr<Parser> Create();
+
   ~Parser();
 
   scoped_refptr<cssom::CSSStyleSheet> ParseStyleSheet(
diff --git a/src/cobalt/debug/backend/command_map.h b/src/cobalt/debug/backend/command_map.h
index b929808..49632a1 100644
--- a/src/cobalt/debug/backend/command_map.h
+++ b/src/cobalt/debug/backend/command_map.h
@@ -19,6 +19,7 @@
 #include <string>
 
 #include "base/bind.h"
+#include "base/optional.h"
 #include "cobalt/debug/backend/debug_dispatcher.h"
 #include "cobalt/debug/command.h"
 #include "cobalt/debug/json_object.h"
@@ -29,7 +30,7 @@
 
 // Type of an agent member function that implements a protocol command.
 template <class T>
-using CommandFn = void (T::*)(const Command& command);
+using CommandFn = void (T::*)(Command command);
 
 // A map of method names to agent command functions. Provides a standard
 // implementation of the top-level domain command handler.
@@ -44,17 +45,17 @@
   // Calls the mapped method implementation.
   // Passes ownership of the command to the mapped method, otherwise returns
   // ownership of the not-run command for a fallback JS implementation.
-  std::unique_ptr<Command> RunCommand(std::unique_ptr<Command> command) {
+  base::Optional<Command> RunCommand(Command command) {
     // If the domain matches, trim it and the dot from the method name.
     const std::string& method =
-        (domain_ == command->GetDomain())
-            ? command->GetMethod().substr(domain_.size() + 1)
-            : command->GetMethod();
+        (domain_ == command.GetDomain())
+            ? command.GetMethod().substr(domain_.size() + 1)
+            : command.GetMethod();
     auto iter = this->find(method);
-    if (iter == this->end()) return command;
+    if (iter == this->end()) return base::make_optional(std::move(command));
     auto command_fn = iter->second;
-    (agent_->*command_fn)(*command);
-    return nullptr;
+    (agent_->*command_fn)(std::move(command));
+    return base::nullopt;
   }
 
   // Binds |RunCommand| to a callback to be registered with |DebugDispatcher|.
diff --git a/src/cobalt/debug/backend/console_agent.cc b/src/cobalt/debug/backend/console_agent.cc
index 955790e..a5ebfed 100644
--- a/src/cobalt/debug/backend/console_agent.cc
+++ b/src/cobalt/debug/backend/console_agent.cc
@@ -59,9 +59,9 @@
   return JSONObject();
 }
 
-void ConsoleAgent::Disable(const Command& command) { command.SendResponse(); }
+void ConsoleAgent::Disable(Command command) { command.SendResponse(); }
 
-void ConsoleAgent::Enable(const Command& command) { command.SendResponse(); }
+void ConsoleAgent::Enable(Command command) { command.SendResponse(); }
 
 void ConsoleAgent::OnMessageAdded(const std::string& text,
                                   dom::Console::Level level) {
diff --git a/src/cobalt/debug/backend/console_agent.h b/src/cobalt/debug/backend/console_agent.h
index d851a72..e3c9b2b 100644
--- a/src/cobalt/debug/backend/console_agent.h
+++ b/src/cobalt/debug/backend/console_agent.h
@@ -48,8 +48,8 @@
     ConsoleAgent* console_agent_;
   };
 
-  void Enable(const Command& command);
-  void Disable(const Command& command);
+  void Enable(Command command);
+  void Disable(Command command);
 
   // Called by |console_listener_| when a new message is output.
   void OnMessageAdded(const std::string& text, dom::Console::Level level);
diff --git a/src/cobalt/debug/backend/content/dom_agent.js b/src/cobalt/debug/backend/content/dom_agent.js
index f702565..88231e6 100644
--- a/src/cobalt/debug/backend/content/dom_agent.js
+++ b/src/cobalt/debug/backend/content/dom_agent.js
@@ -15,110 +15,147 @@
 (function(debugBackend) {
 
 // Attach methods to handle commands in the 'DOM' devtools domain.
-// https://chromedevtools.github.io/devtools-protocol/1-3/DOM
-var commands = debugBackend.DOM = {};
+// https://chromedevtools.github.io/devtools-protocol/tot/DOM
+let commands = debugBackend.DOM = {};
 
-// Creates and returns a new Node object corresponding to the document node,
-// including its children up to a default depth.
-// https://chromedevtools.github.io/devtools-protocol/1-3/DOM#method-getDocument
+// Creates and returns a new devtools.Node object corresponding to the document
+// DOM node, including its children up to a default depth.
+// https://chromedevtools.github.io/devtools-protocol/tot/DOM#method-getDocument
 commands.getDocument = function(params) {
-  var result = {};
+  let result = {};
   result.root = _getNodeWithChildren(document, 2);
   result.root.documentURL = document.URL;
   return JSON.stringify(result);
 }
 
-// Creates an array of Node objects corresponding to the children of the
-// specified node, and returns them via an event. A depth may be specified,
-// where a negative depth means to return all descendants. If no depth is
-// specified, the default is 1, a single level.
-// https://chromedevtools.github.io/devtools-protocol/1-3/DOM#method-requestChildNodes
+// Creates an array of devtools.Node objects corresponding to the children of
+// the DOM node specified in the command params, and returns them via an event.
+// A depth may be specified, where a negative depth means to return all
+// descendants. If no depth is specified, the default is 1, a single level.
+// https://chromedevtools.github.io/devtools-protocol/tot/DOM#method-requestChildNodes
 commands.requestChildNodes = function(params) {
-  var node = commands._findNode(params);
-  var depth = params.depth || 1;
-  var result = {};
-  result.parentId = params.nodeId;
-  result.nodes = _getChildNodes(node, depth);
-
-  // Send the result via an event, and an empty response.
-  debugBackend.sendEvent('DOM.setChildNodes', JSON.stringify(result));
+  let node = commands._findNode(params);
+  _reportChildren(node, params.depth)
   return '{}';
 }
 
-// Finds the node corresponding to a remote objectId. Also sends all nodes on
-// the path from the requested one to the root as a series of setChildNodes
+// Finds the node corresponding to a remote objectId. Also sends all unreported
+// nodes from the root to the requested node as a series of DOM.setChildNodes
 // events.
-// https://chromedevtools.github.io/devtools-protocol/1-3/DOM#method-requestNode
+// https://chromedevtools.github.io/devtools-protocol/tot/DOM#method-requestNode
 commands.requestNode = function(params) {
-  var node = commands._findNode(params);
-  var nodeInfo = new devtools.Node(node);
-  var result = {};
-  result.nodeId = nodeInfo.nodeId;
-
-  var parent = node.parentNode;
-  while (parent) {
-    var parentInfo = new devtools.Node(parent);
-    var params = {};
-    params.parentId = parentInfo.nodeId;
-    params.nodes = [];
-    params.nodes.push(nodeInfo);
-    debugBackend.sendEvent('DOM.setChildNodes', JSON.stringify(params));
-    node = parent;
-    nodeInfo = parentInfo;
-    parent = parent.parentNode;
+  let node = commands._findNode(params);
+  if (!_getNodeId(node)) {
+    _reportPathFromRoot(node.parentNode);
   }
-
-  return JSON.stringify(result);
+  return JSON.stringify({nodeId: _getNodeId(node)});
 }
 
 // Returns a Runtime.RemoteObject corresponding to a node.
-// https://chromedevtools.github.io/devtools-protocol/1-3/DOM#method-resolveNode
+// https://chromedevtools.github.io/devtools-protocol/tot/DOM#method-resolveNode
 commands.resolveNode = function(params) {
-  var node = commands._findNode(params);
-  var result = {};
+  let node = commands._findNode(params);
+  let result = {};
   result.object =
       JSON.parse(debugBackend.createRemoteObject(node, params.objectGroup));
 
   return JSON.stringify(result);
 }
 
-// Creates and returns a Node object that represents the specified node.
-// Adds the node's children up to the specified depth. A negative depth will
-// cause all descendants to be added.
-var _getNodeWithChildren = function(node, depth) {
-  var result = new devtools.Node(node);
-  if (depth != 0) {
-    result.children = _getChildNodes(node, depth);
+// Creates and returns a devtools.Node object that represents the specified DOM
+// node. Adds the node's children up to the specified depth. A negative depth
+// will cause all descendants to be added. All returned children are added to
+// the node store, and should be reported to the client to maintain integrity of
+// the node store holding only nodes the client knows about.
+function _getNodeWithChildren(node, depth) {
+  let result = new devtools.Node(node);
+  let children = _getChildNodes(node, depth);
+  if (children.length) {
+    result.children = children;
   }
   return result;
 }
 
-// Creates and returns an array of Node objects corresponding to the children
-// of the specified node, recursing on each on up to the specified depth.
-var _getChildNodes = function(node, depth) {
-  if (!node.childNodes) {
+// Creates and returns an array of devtools.Node objects corresponding to the
+// children of the specified DOM node, recursing on each on down to the
+// specified depth. All returned children are added to the node store, and
+// should be reported to the client to maintain integrity of the node store
+// holding only nodes the client knows about.
+function _getChildNodes(node, depth) {
+  let children;
+  // Special-case the only text child - pretend the children were requested.
+  if (node.firstChild && !node.firstChild.nextSibling &&
+      node.firstChild.nodeType === Node.TEXT_NODE) {
+    children = [node.firstChild];
+  } else if (depth != 0) {  // Negative depth recurses the whole tree.
+    let child_nodes = Array.from(node.childNodes);
+    children = child_nodes.filter((child) => !_isWhitespace(child));
+
+    // Since we don't report whitespace text nodes they won't be in the node
+    // store and won't otherwise be observed. However, we still need to observe
+    // them to report if they get set to non-whitespace.
+    child_nodes.filter(_isWhitespace)
+        .forEach((child) => _nodeObserver.observe(child, _observerConfig));
+  } else {
+    // Children not requested, so don't set |childrenReported|.
     return [];
   }
 
-  var children = [];
-  for (var i = 0; i < node.childNodes.length; i++) {
-    var child = node.childNodes[i];
-    if (!_nodeIsIgnorable(child)) {
-      children.push(_getNodeWithChildren(child, depth - 1));
-    }
-  }
-  return children;
+  let nodeId = _getNodeId(node);
+  _nodeStore.get(nodeId).childrenReported = true;
+  return children.map((child) => _getNodeWithChildren(child, depth - 1));
 }
 
-// Finds a node specified by either nodeId or objectId (to get a node from its
-// corresponding remote object). This is "exported" as a pseudo-command in the
-// DOM domain for other agents to use.
+// Whether the children of a node have been reported to the frontend.
+function _areChildrenReported(nodeId) {
+    let nodeInfo = _nodeStore.get(nodeId);
+    return nodeInfo && nodeInfo.childrenReported;
+}
+
+// Sends DOM.setChildNode events to report all nodes not yet known to the client
+// from the root down to the specified DOM node.
+function _reportPathFromRoot(node) {
+  // Report nothing if we get to a disconnected root.
+  if (!node) {
+    return false;
+  }
+  // Stop recursing when we get to a node that has already been reported, and
+  // report its children first before unwinding the recursion down the tree.
+  if (_getNodeId(node)) {
+    _reportChildren(node);
+    return true;
+  }
+  // Recurse up first to report in top-down order, and report nothing if we
+  // reached a disconnected root.
+  if (!_reportPathFromRoot(node.parentNode)) {
+    return false;
+  }
+  // All ancestors are now reported, so report the node's children.
+  _reportChildren(node);
+  return true;
+}
+
+// Sends a DOM.setChildNodes event reporting the children of a DOM node, and
+// their children recursively to the requested depth.
+function _reportChildren(node, depth) {
+  let nodeId = _addNode(node);
+  let children = _getChildNodes(node, depth || 1);
+  let params = {
+    parentId: nodeId,
+    nodes: children,
+  };
+  debugBackend.sendEvent('DOM.setChildNodes', JSON.stringify(params));
+}
+
+// Finds a DOM node specified by either nodeId or objectId (to get a node from
+// its corresponding remote object). This is "exported" as a pseudo-command in
+// the DOM domain for other agents to use.
 commands._findNode = function(params) {
-  if (params.nodeId != null) {
-    return _nodeStore[params.nodeId];
+  if (params.nodeId) {
+    return _nodeStore.get(params.nodeId).node;
   }
 
-  if (params.objectId != null) {
+  if (params.objectId) {
     return debugBackend.lookupRemoteObjectId(params.objectId);
   }
 
@@ -126,37 +163,173 @@
   return null;
 }
 
-// Adds a node to the internal node store and returns a unique id that can
-// be used to access it again.
-var _addNode = function(node) {
-  // If we've already added this node, then use the same nodeId.
-  for (var i = 0; i < _nodeStore.length; i++) {
-    if (_nodeStore[i] === node) {
-      return i;
-    }
+// Adds a DOM node to the internal node store and returns a unique id that can
+// be used to access it again. If the node is already in the node store, its
+// existing id is returned.
+function _addNode(node) {
+  let nodeId = _getNodeId(node);
+  if (!nodeId) {
+    nodeId = _nextNodeId++;
+    // The map goes both ways: DOM node <=> node ID.
+    _nodeStore.set(nodeId, {node: node});
+    _nodeStore.set(node, nodeId);
+    _nodeObserver.observe(node, _observerConfig);
   }
-
-  var nodeId = _nextNodeId++;
-  _nodeStore[nodeId] = node;
   return nodeId;
 }
 
-// Whether a node is ignorable. We ignore text nodes with white-space only
-// content, as they just clutter up the DOM tree.
-var _nodeIsIgnorable = function(node) {
-  return node.nodeType == Node.TEXT_NODE &&
-      !(/[^\t\n\r ]/.test(node.textContent));
+// Removes a DOM node and its children from the internal node store.
+function _removeNode(node) {
+  let nodeId = _getNodeId(node);
+  if (!nodeId) return;
+  Array.from(node.childNodes).forEach(_removeNode);
+  _nodeStore.delete(node);
+  _nodeStore.delete(nodeId);
 }
 
-var _nodeStore = [];
-var _nextNodeId = 0;
+// Returns the node id of a DOM node if it's already known, else undefined.
+function _getNodeId(node) {
+  return _nodeStore.get(node);
+}
+
+// MutationObserver callback to send events when nodes are changed.
+function _onNodeMutated(mutationsList) {
+  for (let mutation of mutationsList) {
+    if (mutation.type === 'childList') {
+      _onChildListMutated(mutation);
+    } else if (mutation.type === 'attributes') {
+      _onAttributesMutated(mutation);
+    } else if (mutation.type === 'characterData') {
+      _onCharacterDataMutated(mutation);
+    }
+  }
+}
+
+function _onChildListMutated(mutation) {
+  let parentNodeId = _getNodeId(mutation.target);
+  if (!_areChildrenReported(parentNodeId)) {
+    // The mutated node hasn't been expanded in the Element tree so we haven't
+    // yet reported any of its children to the frontend. Just report that the
+    // number of children changed, without reporting the actual child nodes.
+    let params = {
+      nodeId: parentNodeId,
+      childNodeCount: _countChildNodes(mutation.target),
+    };
+    debugBackend.sendEvent('DOM.childNodeCountUpdated', JSON.stringify(params));
+  } else {
+    // The mutated node has already been expanded in the Element tree so the
+    // frontend already knows about its children. Report the removed/inserted
+    // nodes. Report removed nodes first so that replacements (e.g. setting
+    // textContent) are coherent.
+    Array.from(mutation.removedNodes)
+        .forEach((n) => _onNodeRemoved(parentNodeId, n));
+    Array.from(mutation.addedNodes)
+        .forEach((n) => _onNodeInserted(parentNodeId, n));
+  }
+}
+
+// Report to the frontend when a DOM node is inserted.
+function _onNodeInserted(parentNodeId, node) {
+  if (_isWhitespace(node)) return;
+
+  // Forget anything we knew about an existing subtree that gets re-attached.
+  _removeNode(node);
+
+  let params = {
+    parentNodeId: parentNodeId,
+    previousNodeId: _getNodeId(_getPreviousSibling(node)) || 0,
+    node: new devtools.Node(node),
+  };
+  debugBackend.sendEvent('DOM.childNodeInserted', JSON.stringify(params));
+}
+
+// Report to the frontend when a DOM node is removed.
+function _onNodeRemoved(parentNodeId, node) {
+  let nodeId = _getNodeId(node);
+  if (!parentNodeId || !nodeId) return;
+
+  let params = {
+    parentNodeId: parentNodeId,
+    nodeId: _getNodeId(node),
+  };
+  debugBackend.sendEvent('DOM.childNodeRemoved', JSON.stringify(params));
+  _removeNode(node);
+}
+
+function _onAttributesMutated(mutation) {
+  let params = {
+    nodeId: _getNodeId(mutation.target),
+    name: mutation.attributeName,
+  };
+  if (mutation.target.hasAttribute(mutation.attributeName)) {
+    params.value = mutation.target.getAttribute(mutation.attributeName);
+    debugBackend.sendEvent('DOM.attributeModified', JSON.stringify(params));
+  } else {
+    debugBackend.sendEvent('DOM.attributeRemoved', JSON.stringify(params));
+  }
+}
+
+function _onCharacterDataMutated(mutation) {
+  let nodeId = _getNodeId(mutation.target);
+  let parentNodeId = _getNodeId(mutation.target.parentNode);
+  // If a node changes to/from whitespace, treat it as inserted/removed.
+  if (!nodeId) {
+    _onNodeInserted(parentNodeId, mutation.target);
+    return;
+  } else if (_isWhitespace(mutation.target)) {
+    _onNodeRemoved(parentNodeId, mutation.target);
+    return;
+  }
+
+  let params = {
+    nodeId: nodeId,
+    characterData: mutation.target.textContent,
+  };
+  debugBackend.sendEvent('DOM.characterDataModified', JSON.stringify(params));
+}
+
+// Whether a DOM node is a whitespace-only text node.
+// (These are not reported to the frontend.)
+function _isWhitespace(node) {
+  return node.nodeType === Node.TEXT_NODE &&
+      !(/[^\t\n\r ]/.test(node.nodeValue));
+}
+
+// Returns the count of non-whitespace children of a DOM node.
+function _countChildNodes(node) {
+  let countCallback = (count, child) => count + (_isWhitespace(child) ? 0 : 1);
+  return Array.from(node.childNodes || []).reduce(countCallback, 0);
+}
+
+// Returns the non-whitespace previous sibling to the DOM node, if any.
+function _getPreviousSibling(node) {
+  do {
+    node = node.previousSibling;
+  } while(node && _isWhitespace(node));
+  return node;
+}
+
+// TODO: Don't use an actual MutationObserver since the page under test can
+// disconnect it from the nodes being observed. Instead set _onNodeMutated() as
+// a callback on DebugBackend and hook it up to MutationObserverTaskManager to
+// always run when notifying actual MutationObservers.
+const _nodeObserver = new MutationObserver(_onNodeMutated);
+const _observerConfig = {
+  attributes: true,
+  childList: true,
+  characterData: true,
+};
+
+let _nodeStore = new Map();
+let _nextNodeId = 1;
 
 // Namespace for constructors of types defined in the Devtools protocol.
-var devtools = {};
+let devtools = {};
 
-// Creates a new Node object, which is the type used to return information
-// about nodes to devtools. All Node objects are added to |nodeStore|,
-// so they can be retrieved later via |nodeId|.
+// Constructor for devtools.Node object, which is the type used to return
+// information about nodes to the frontend. The associated DOM node is added to
+// |nodeStore| since all devtools.Node objects are expected to be reported to
+// the frontend, which can reference them later via |nodeId|.
 // https://chromedevtools.github.io/devtools-protocol/tot/DOM#type-Node
 devtools.Node = function(node) {
   this.nodeId = _addNode(node);
@@ -164,11 +337,11 @@
   this.nodeName = node.nodeName;
   this.nodeType = node.nodeType;
   this.nodeValue = node.nodeValue || '';
-  this.childNodeCount = _getChildNodes(node, 1).length;
+  this.childNodeCount = _countChildNodes(node);
 
   if (node.attributes) {
     this.attributes = [];
-    for (var i = 0; i < node.attributes.length; i++) {
+    for (let i = 0; i < node.attributes.length; i++) {
       this.attributes.push(node.attributes[i].name);
       this.attributes.push(node.attributes[i].value);
     }
diff --git a/src/cobalt/debug/backend/css_agent.cc b/src/cobalt/debug/backend/css_agent.cc
index 1de669c..270fc57 100644
--- a/src/cobalt/debug/backend/css_agent.cc
+++ b/src/cobalt/debug/backend/css_agent.cc
@@ -50,7 +50,7 @@
   return JSONObject();
 }
 
-void CSSAgent::Enable(const Command& command) {
+void CSSAgent::Enable(Command command) {
   if (script_loaded_) {
     command.SendResponse();
   } else {
@@ -59,7 +59,7 @@
   }
 }
 
-void CSSAgent::Disable(const Command& command) { command.SendResponse(); }
+void CSSAgent::Disable(Command command) { command.SendResponse(); }
 
 CSSAgent::CSSStyleRuleSequence CSSAgent::GetMatchingCSSRules(
     const scoped_refptr<dom::Element>& element) {
diff --git a/src/cobalt/debug/backend/css_agent.h b/src/cobalt/debug/backend/css_agent.h
index 027ce75..6941c5d 100644
--- a/src/cobalt/debug/backend/css_agent.h
+++ b/src/cobalt/debug/backend/css_agent.h
@@ -43,8 +43,8 @@
   DEFINE_WRAPPABLE_TYPE(CSSAgent);
 
  private:
-  void Enable(const Command& command);
-  void Disable(const Command& command);
+  void Enable(Command command);
+  void Disable(Command command);
 
   // Helper object to connect to the debug dispatcher, etc.
   DebugDispatcher* dispatcher_;
diff --git a/src/cobalt/debug/backend/debug_backend.cc b/src/cobalt/debug/backend/debug_backend.cc
index 4dbf2c6..80b487c 100644
--- a/src/cobalt/debug/backend/debug_backend.cc
+++ b/src/cobalt/debug/backend/debug_backend.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "cobalt/debug/backend/debug_backend.h"
+
 #include "base/memory/ptr_util.h"
 
 namespace cobalt {
@@ -29,7 +30,7 @@
 }
 
 void DebugBackend::SendEvent(const std::string& method,
-                             const base::Optional<std::string>& params) {
+                             const std::string& params) {
   on_event_callback_.Run(method, params);
 }
 
diff --git a/src/cobalt/debug/backend/debug_backend.h b/src/cobalt/debug/backend/debug_backend.h
index 54238a9..e952aed 100644
--- a/src/cobalt/debug/backend/debug_backend.h
+++ b/src/cobalt/debug/backend/debug_backend.h
@@ -18,7 +18,6 @@
 #include <string>
 
 #include "base/callback.h"
-#include "base/optional.h"
 #include "cobalt/debug/backend/css_agent.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/script_debugger.h"
@@ -37,7 +36,7 @@
   // Callback to forward asynchronous protocol events to the frontend.
   // See: https://chromedevtools.github.io/devtools-protocol/
   typedef base::Callback<void(const std::string& method,
-                              const base::Optional<std::string>& params)>
+                              const std::string& params)>
       OnEventCallback;
 
   DebugBackend(script::GlobalEnvironment* global_environment,
@@ -49,8 +48,7 @@
   void UnbindAgents() { css_agent_ = nullptr; }
 
   // Sends a protocol event to the debugger frontend.
-  void SendEvent(const std::string& method,
-                 const base::Optional<std::string>& params);
+  void SendEvent(const std::string& method, const std::string& params);
 
   // Returns the RemoteObject JSON representation of the given object for the
   // debugger frontend.
diff --git a/src/cobalt/debug/backend/debug_backend.idl b/src/cobalt/debug/backend/debug_backend.idl
index a757b0a..c2a50c9 100644
--- a/src/cobalt/debug/backend/debug_backend.idl
+++ b/src/cobalt/debug/backend/debug_backend.idl
@@ -16,7 +16,7 @@
   Conditional=ENABLE_DEBUGGER
 ]
 interface DebugBackend {
-  void sendEvent(DOMString method, DOMString? params);
+  void sendEvent(DOMString method, optional DOMString params = "{}");
 
   // Convert between JS objects and a remote representation for the frontend.
   DOMString createRemoteObject(object obj, DOMString group);
diff --git a/src/cobalt/debug/backend/debug_dispatcher.cc b/src/cobalt/debug/backend/debug_dispatcher.cc
index 7454404..4f54097 100644
--- a/src/cobalt/debug/backend/debug_dispatcher.cc
+++ b/src/cobalt/debug/backend/debug_dispatcher.cc
@@ -82,7 +82,7 @@
   clients_.erase(client);
 }
 
-void DebugDispatcher::SendCommand(std::unique_ptr<Command> command) {
+void DebugDispatcher::SendCommand(Command command) {
   // Create a closure that will run the command and the response callback.
   // The task is either posted to the debug target (WebModule) thread if
   // that thread is running normally, or added to a queue of debugger tasks
@@ -98,7 +98,7 @@
   }
 }
 
-void DebugDispatcher::DispatchCommand(std::unique_ptr<Command> command) {
+void DebugDispatcher::DispatchCommand(Command command) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   // This workaround allows both the overlay console and remote DevTools to
@@ -106,29 +106,30 @@
   // command, we first inject a "Runtime.disable" command so that the V8
   // Inspector will send the "Runtime.executionContextCreated" event for every
   // "Runtime.enable" command rather than just for the first one.
-  if (command->GetMethod() == "Runtime.enable") {
-    DispatchCommand(std::make_unique<Command>(
-        "Runtime.disable", "", base::Bind(&NoOpResponseCallback)));
+  if (command.GetMethod() == "Runtime.enable") {
+    DispatchCommand(
+        Command("Runtime.disable", "", base::Bind(&NoOpResponseCallback)));
   }
 
-  DomainRegistry::iterator iter = domain_registry_.find(command->GetDomain());
+  DomainRegistry::iterator iter = domain_registry_.find(command.GetDomain());
   if (iter != domain_registry_.end()) {
-    command = iter->second.Run(std::move(command));
+    auto opt_command = iter->second.Run(std::move(command));
     // The agent command implementation kept the command to send the response.
-    if (!command) return;
+    if (!opt_command) return;
+    command = std::move(*opt_command);
   }
 
   // The agent didn't have a native implementation. Try to run a
   // JavaScript implementation (which the agent would have loaded at the
   // same time as it registered its domain command handler).
   JSONObject response =
-      RunScriptCommand(command->GetMethod(), command->GetParams());
+      RunScriptCommand(command.GetMethod(), command.GetParams());
   if (response) {
-    command->SendResponse(response);
+    command.SendResponse(response);
   } else {
-    DLOG(WARNING) << "Command not implemented: " << command->GetMethod();
-    command->SendErrorResponse(Command::kMethodNotFound,
-                               "Command not implemented");
+    DLOG(WARNING) << "Command not implemented: " << command.GetMethod();
+    command.SendErrorResponse(Command::kMethodNotFound,
+                              "Command not implemented");
   }
 }
 
@@ -174,13 +175,14 @@
 
 void DebugDispatcher::SendEvent(const std::string& method,
                                 const JSONObject& params) {
-  base::Optional<std::string> json_params;
-  if (params) json_params = JSONStringify(params);
-  SendEvent(method, json_params);
+  SendEvent(method, JSONStringify(params));
 }
 
-void DebugDispatcher::SendEvent(
-    const std::string& method, const base::Optional<std::string>& json_params) {
+void DebugDispatcher::SendEvent(const std::string& method,
+                                const std::string& json_params) {
+  DCHECK(!json_params.empty());
+  DCHECK_EQ(json_params.front(), '{');
+  DCHECK_EQ(json_params.back(), '}');
   for (auto* client : clients_) {
     client->OnEvent(method, json_params);
   }
@@ -211,12 +213,12 @@
     if (result) {
       response->Set("result", std::unique_ptr<base::Value>(result.release()));
     }
-  } else if (!json_result.empty()) {
+  } else if (json_result.empty()) {
+    // Unimplemented commands aren't successful, and |json_result| is empty.
+    response.reset();
+  } else {
     // On error, |json_result| is the error message.
     response->SetString("error.message", json_result);
-  } else {
-    // An empty error means the method isn't implemented so return no response.
-    response.reset();
   }
   return response;
 }
diff --git a/src/cobalt/debug/backend/debug_dispatcher.h b/src/cobalt/debug/backend/debug_dispatcher.h
index c114035..99660c9 100644
--- a/src/cobalt/debug/backend/debug_dispatcher.h
+++ b/src/cobalt/debug/backend/debug_dispatcher.h
@@ -110,8 +110,7 @@
   // is supported, ownership of the command parameter should be kept and used to
   // send the response. If the command is not supported, the command should be
   // returned so the dispatcher can try calling a JS fallback implementation.
-  typedef base::Callback<std::unique_ptr<Command>(
-      std::unique_ptr<Command> command)>
+  typedef base::Callback<base::Optional<Command>(Command command)>
       CommandHandler;
 
   DebugDispatcher(script::ScriptDebugger* script_debugger,
@@ -137,11 +136,11 @@
   void RemoveDomain(const std::string& domain);
 
   // Sends a protocol event to the frontend.
-  void SendEvent(const std::string& method, const JSONObject& params);
+  void SendEvent(const std::string& method,
+                 const JSONObject& params = JSONObject());
 
   // Sends a protocol event to the frontend.
-  void SendEvent(const std::string& method,
-                 const base::Optional<std::string>& params);
+  void SendEvent(const std::string& method, const std::string& params);
 
   // Calls |method| in |script_runner_| and creates a response object from
   // the result.
@@ -155,7 +154,7 @@
   // called from any thread - the command will be run on the dispatcher's
   // message loop, and the response will be sent to the callback and message
   // loop held in the command object.
-  void SendCommand(std::unique_ptr<Command> command);
+  void SendCommand(Command command);
 
   // Sets or unsets the paused state and calls |HandlePause| if set.
   // Must be called on the debug target (WebModule) thread.
@@ -180,7 +179,7 @@
   // name in the command registry and running the corresponding function.
   // The response callback will be run on the message loop specified in the
   // info structure with the result as an argument.
-  void DispatchCommand(std::unique_ptr<Command> command);
+  void DispatchCommand(Command command);
 
   // Called by |SendCommand| if a debugger command is received while script
   // execution is paused.
diff --git a/src/cobalt/debug/backend/debug_module.cc b/src/cobalt/debug/backend/debug_module.cc
index 5434789..f8274a9 100644
--- a/src/cobalt/debug/backend/debug_module.cc
+++ b/src/cobalt/debug/backend/debug_module.cc
@@ -243,7 +243,7 @@
 }
 
 void DebugModule::SendEvent(const std::string& method,
-                            const base::Optional<std::string>& params) {
+                            const std::string& params) {
   DCHECK(debug_dispatcher_);
   debug_dispatcher_->SendEvent(method, params);
 }
diff --git a/src/cobalt/debug/backend/debug_module.h b/src/cobalt/debug/backend/debug_module.h
index 11f34bd..732de2b 100644
--- a/src/cobalt/debug/backend/debug_module.h
+++ b/src/cobalt/debug/backend/debug_module.h
@@ -117,8 +117,7 @@
                      base::WaitableEvent* created);
 
   // Sends a protocol event to the frontend through |DebugDispatcher|.
-  void SendEvent(const std::string& method,
-                 const base::Optional<std::string>& params);
+  void SendEvent(const std::string& method, const std::string& params);
 
   // script::ScriptDebugger::Delegate implementation.
   void OnScriptDebuggerPause() override;
diff --git a/src/cobalt/debug/backend/debugger_hooks_impl.cc b/src/cobalt/debug/backend/debugger_hooks_impl.cc
index ef17028..6cff320 100644
--- a/src/cobalt/debug/backend/debugger_hooks_impl.cc
+++ b/src/cobalt/debug/backend/debugger_hooks_impl.cc
@@ -14,12 +14,29 @@
 
 #include "cobalt/debug/backend/debugger_hooks_impl.h"
 
+#include <sstream>
+
+#include "base/json/string_escape.h"
 #include "cobalt/script/script_debugger.h"
 
 namespace cobalt {
 namespace debug {
 namespace backend {
 
+namespace {
+
+// Indexed by ::logging::LogSeverity
+const char* kConsoleMethodName[] = {
+    "info",   // LOG_INFO
+    "warn",   // LOG_WARNING
+    "error",  // LOG_ERROR
+    "error",  // LOG_FATAL - there is no console.fatal() function.
+};
+static_assert(::logging::LOG_NUM_SEVERITIES == arraysize(kConsoleMethodName),
+              "Incorrect count of kConsoleMethodName");
+
+}  // namespace
+
 void DebuggerHooksImpl::AttachDebugger(
     script::ScriptDebugger* script_debugger) {
   script_debugger_ = script_debugger;
@@ -32,6 +49,15 @@
   script_debugger_ = nullptr;
 }
 
+void DebuggerHooksImpl::ConsoleLog(::logging::LogSeverity severity,
+                                   std::string message) const {
+  if (!script_debugger_) return;
+  std::ostringstream js_code;
+  js_code << "console." << kConsoleMethodName[severity] << '('
+          << base::GetQuotedJSONString(message) << ')';
+  script_debugger_->EvaluateDebuggerScript(js_code.str(), nullptr);
+}
+
 void DebuggerHooksImpl::AsyncTaskScheduled(const void* task,
                                            const std::string& name,
                                            AsyncTaskFrequency frequency) const {
diff --git a/src/cobalt/debug/backend/debugger_hooks_impl.h b/src/cobalt/debug/backend/debugger_hooks_impl.h
index a856cb1..ef5928b 100644
--- a/src/cobalt/debug/backend/debugger_hooks_impl.h
+++ b/src/cobalt/debug/backend/debugger_hooks_impl.h
@@ -31,6 +31,9 @@
 
 class DebuggerHooksImpl : public base::DebuggerHooks {
  public:
+  void ConsoleLog(::logging::LogSeverity severity,
+                  std::string message) const override;
+
   void AsyncTaskScheduled(const void* task, const std::string& name,
                           AsyncTaskFrequency frequency) const override;
   void AsyncTaskStarted(const void* task) const override;
diff --git a/src/cobalt/debug/backend/dom_agent.cc b/src/cobalt/debug/backend/dom_agent.cc
index f3d5322..188dcde 100644
--- a/src/cobalt/debug/backend/dom_agent.cc
+++ b/src/cobalt/debug/backend/dom_agent.cc
@@ -47,7 +47,7 @@
   return JSONObject();
 }
 
-void DOMAgent::Enable(const Command& command) {
+void DOMAgent::Enable(Command command) {
   if (script_loaded_) {
     command.SendResponse();
   } else {
@@ -56,7 +56,7 @@
   }
 }
 
-void DOMAgent::Disable(const Command& command) { command.SendResponse(); }
+void DOMAgent::Disable(Command command) { command.SendResponse(); }
 
 }  // namespace backend
 }  // namespace debug
diff --git a/src/cobalt/debug/backend/dom_agent.h b/src/cobalt/debug/backend/dom_agent.h
index 6e59ffb..0ccd4c9 100644
--- a/src/cobalt/debug/backend/dom_agent.h
+++ b/src/cobalt/debug/backend/dom_agent.h
@@ -31,8 +31,8 @@
   JSONObject Freeze();
 
  private:
-  void Enable(const Command& command);
-  void Disable(const Command& command);
+  void Enable(Command command);
+  void Disable(Command command);
 
   DebugDispatcher* dispatcher_;
 
diff --git a/src/cobalt/debug/backend/log_agent.cc b/src/cobalt/debug/backend/log_agent.cc
index 61ef9e2..64f3790 100644
--- a/src/cobalt/debug/backend/log_agent.cc
+++ b/src/cobalt/debug/backend/log_agent.cc
@@ -85,12 +85,12 @@
   return agent_state;
 }
 
-void LogAgent::Enable(const Command& command) {
+void LogAgent::Enable(Command command) {
   enabled_ = true;
   command.SendResponse();
 }
 
-void LogAgent::Disable(const Command& command) {
+void LogAgent::Disable(Command command) {
   enabled_ = false;
   command.SendResponse();
 }
diff --git a/src/cobalt/debug/backend/log_agent.h b/src/cobalt/debug/backend/log_agent.h
index 3419f43..da8ecab 100644
--- a/src/cobalt/debug/backend/log_agent.h
+++ b/src/cobalt/debug/backend/log_agent.h
@@ -36,8 +36,8 @@
   JSONObject Freeze();
 
  private:
-  void Enable(const Command& command);
-  void Disable(const Command& command);
+  void Enable(Command command);
+  void Disable(Command command);
 
   // Called by LogMessageHandler for each log message.
   // May be called from any thread.
diff --git a/src/cobalt/debug/backend/overlay_agent.cc b/src/cobalt/debug/backend/overlay_agent.cc
index 449675c..f3e944f 100644
--- a/src/cobalt/debug/backend/overlay_agent.cc
+++ b/src/cobalt/debug/backend/overlay_agent.cc
@@ -102,7 +102,7 @@
   return JSONObject();
 }
 
-void OverlayAgent::Enable(const Command& command) {
+void OverlayAgent::Enable(Command command) {
   if (script_loaded_) {
     enabled_ = true;
     command.SendResponse();
@@ -112,12 +112,12 @@
   }
 }
 
-void OverlayAgent::Disable(const Command& command) {
+void OverlayAgent::Disable(Command command) {
   enabled_ = false;
   command.SendResponse();
 }
 
-void OverlayAgent::HighlightNode(const Command& command) {
+void OverlayAgent::HighlightNode(Command command) {
   if (!enabled_) {
     command.SendErrorResponse(Command::kInvalidRequest,
                               "Overlay inspector not enabled.");
@@ -146,13 +146,13 @@
   command.SendResponse();
 }
 
-void OverlayAgent::HighlightRect(const Command& command) {
+void OverlayAgent::HighlightRect(Command command) {
   JSONObject params = JSONParse(command.GetParams());
   render_layer_->SetFrontLayer(RenderHighlightRect(params.get()));
   command.SendResponse();
 }
 
-void OverlayAgent::HideHighlight(const Command& command) {
+void OverlayAgent::HideHighlight(Command command) {
   render_layer_->SetFrontLayer(scoped_refptr<render_tree::Node>());
   command.SendResponse();
 }
diff --git a/src/cobalt/debug/backend/overlay_agent.h b/src/cobalt/debug/backend/overlay_agent.h
index 4b7425b..7f408c1 100644
--- a/src/cobalt/debug/backend/overlay_agent.h
+++ b/src/cobalt/debug/backend/overlay_agent.h
@@ -33,12 +33,12 @@
   JSONObject Freeze();
 
  private:
-  void Enable(const Command& command);
-  void Disable(const Command& command);
+  void Enable(Command command);
+  void Disable(Command command);
 
-  void HighlightNode(const Command& command);
-  void HighlightRect(const Command& command);
-  void HideHighlight(const Command& command);
+  void HighlightNode(Command command);
+  void HighlightRect(Command command);
+  void HideHighlight(Command command);
 
   DebugDispatcher* dispatcher_;
 
diff --git a/src/cobalt/debug/backend/page_agent.cc b/src/cobalt/debug/backend/page_agent.cc
index 42d399a..c5be165 100644
--- a/src/cobalt/debug/backend/page_agent.cc
+++ b/src/cobalt/debug/backend/page_agent.cc
@@ -68,18 +68,18 @@
   return JSONObject();
 }
 
-void PageAgent::Disable(const Command& command) { command.SendResponse(); }
+void PageAgent::Disable(Command command) { command.SendResponse(); }
 
-void PageAgent::Enable(const Command& command) { command.SendResponse(); }
+void PageAgent::Enable(Command command) { command.SendResponse(); }
 
-void PageAgent::Reload(const Command& command) {
+void PageAgent::Reload(Command command) {
   // We don't care about the 'ignoreCache' parameter since navigating creates a
   // new WebModule with a new cache (i.e. cache is always cleared on navigate).
   window_->location()->Reload();
   command.SendResponse();
 }
 
-void PageAgent::GetResourceTree(const Command& command) {
+void PageAgent::GetResourceTree(Command command) {
   JSONObject response(new base::DictionaryValue());
   JSONObject frame(new base::DictionaryValue());
   frame->SetString("id", "Cobalt");
@@ -93,7 +93,7 @@
   command.SendResponse(response);
 }
 
-void PageAgent::SetOverlayMessage(const Command& command) {
+void PageAgent::SetOverlayMessage(Command command) {
   std::string message;
   JSONObject params = JSONParse(command.GetParams());
   bool got_message = false;
diff --git a/src/cobalt/debug/backend/page_agent.h b/src/cobalt/debug/backend/page_agent.h
index da2e894..a67d631 100644
--- a/src/cobalt/debug/backend/page_agent.h
+++ b/src/cobalt/debug/backend/page_agent.h
@@ -42,11 +42,11 @@
   JSONObject Freeze();
 
  private:
-  void Enable(const Command& command);
-  void Disable(const Command& command);
-  void Reload(const Command& command);
-  void GetResourceTree(const Command& command);
-  void SetOverlayMessage(const Command& command);
+  void Enable(Command command);
+  void Disable(Command command);
+  void Reload(Command command);
+  void GetResourceTree(Command command);
+  void SetOverlayMessage(Command command);
 
   dom::Window* window_;
   std::unique_ptr<RenderLayer> render_layer_;
diff --git a/src/cobalt/debug/backend/runtime_agent.cc b/src/cobalt/debug/backend/runtime_agent.cc
index 70c7acb..cd11287 100644
--- a/src/cobalt/debug/backend/runtime_agent.cc
+++ b/src/cobalt/debug/backend/runtime_agent.cc
@@ -56,7 +56,7 @@
   return JSONObject();
 }
 
-void RuntimeAgent::Enable(const Command& command) {
+void RuntimeAgent::Enable(Command command) {
   if (!script_loaded_) {
     command.SendErrorResponse(Command::kInternalError,
                               "Cannot create Runtime inspector.");
@@ -76,9 +76,9 @@
   command.SendResponse();
 }
 
-void RuntimeAgent::Disable(const Command& command) { command.SendResponse(); }
+void RuntimeAgent::Disable(Command command) { command.SendResponse(); }
 
-void RuntimeAgent::CompileScript(const Command& command) {
+void RuntimeAgent::CompileScript(Command command) {
   // TODO: Parse the JS without eval-ing it... This is to support:
   // a) Multi-line input from the devtools console
   // b) https://developers.google.com/web/tools/chrome-devtools/snippets
diff --git a/src/cobalt/debug/backend/runtime_agent.h b/src/cobalt/debug/backend/runtime_agent.h
index 3be3f37..777b74c 100644
--- a/src/cobalt/debug/backend/runtime_agent.h
+++ b/src/cobalt/debug/backend/runtime_agent.h
@@ -37,9 +37,9 @@
   JSONObject Freeze();
 
  private:
-  void CompileScript(const Command& command);
-  void Disable(const Command& command);
-  void Enable(const Command& command);
+  void CompileScript(Command command);
+  void Disable(Command command);
+  void Enable(Command command);
 
   DebugDispatcher* dispatcher_;
   dom::Window* window_;
diff --git a/src/cobalt/debug/backend/script_debugger_agent.cc b/src/cobalt/debug/backend/script_debugger_agent.cc
index c2c312f..4b8fe1b 100644
--- a/src/cobalt/debug/backend/script_debugger_agent.cc
+++ b/src/cobalt/debug/backend/script_debugger_agent.cc
@@ -61,8 +61,7 @@
   return agent_state;
 }
 
-std::unique_ptr<Command> ScriptDebuggerAgent::RunCommand(
-    std::unique_ptr<Command> command) {
+base::Optional<Command> ScriptDebuggerAgent::RunCommand(Command command) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   // Use an internal ID to store the pending command until we get a response.
@@ -70,26 +69,27 @@
 
   JSONObject message(new base::DictionaryValue());
   message->SetInteger(kId, command_id);
-  message->SetString(kMethod, command->GetMethod());
-  JSONObject params = JSONParse(command->GetParams());
+  message->SetString(kMethod, command.GetMethod());
+  JSONObject params = JSONParse(command.GetParams());
   if (params) {
     message->Set(kParams, std::move(params));
   }
 
   // Store the pending command before dispatching it so that we can find it if
   // the script debugger sends a synchronous response before returning.
-  std::string method = command->GetMethod();
+  std::string method = command.GetMethod();
   pending_commands_.emplace(command_id, std::move(command));
   if (script_debugger_->DispatchProtocolMessage(method,
                                                 JSONStringify(message))) {
     // The command has been dispached; keep ownership of it in the map.
-    return nullptr;
+    return base::nullopt;
   }
 
   // Take the command back out of the map and return it for fallback.
-  command = std::move(pending_commands_.at(command_id));
+  auto opt_command =
+      base::make_optional(std::move(pending_commands_.at(command_id)));
   pending_commands_.erase(command_id);
-  return command;
+  return opt_command;
 }
 
 void ScriptDebuggerAgent::SendCommandResponse(
@@ -104,7 +104,7 @@
   // Use the stripped ID to lookup the command it's a response for.
   auto iter = pending_commands_.find(command_id);
   if (iter != pending_commands_.end()) {
-    iter->second->SendResponse(response);
+    iter->second.SendResponse(response);
     pending_commands_.erase(iter);
   } else {
     DLOG(ERROR) << "Spurious debugger response: " << json_response;
diff --git a/src/cobalt/debug/backend/script_debugger_agent.h b/src/cobalt/debug/backend/script_debugger_agent.h
index 7e906f0..a406209 100644
--- a/src/cobalt/debug/backend/script_debugger_agent.h
+++ b/src/cobalt/debug/backend/script_debugger_agent.h
@@ -19,6 +19,7 @@
 #include <set>
 #include <string>
 
+#include "base/optional.h"
 #include "base/threading/thread_checker.h"
 #include "cobalt/debug/backend/debug_dispatcher.h"
 #include "cobalt/debug/backend/script_debugger_agent.h"
@@ -41,7 +42,7 @@
   bool IsSupportedDomain(const std::string& domain) {
     return supported_domains_.count(domain) != 0;
   }
-  std::unique_ptr<Command> RunCommand(std::unique_ptr<Command> command);
+  base::Optional<Command> RunCommand(Command command);
   void SendCommandResponse(const std::string& json_response);
   void SendEvent(const std::string& json_event);
 
@@ -53,7 +54,7 @@
   const std::set<std::string> supported_domains_;
 
   int last_command_id_ = 0;
-  std::map<int, std::unique_ptr<Command>> pending_commands_;
+  std::map<int, Command> pending_commands_;
 };
 
 }  // namespace backend
diff --git a/src/cobalt/debug/backend/tracing_agent.cc b/src/cobalt/debug/backend/tracing_agent.cc
index 1c7754e..15af1c5 100644
--- a/src/cobalt/debug/backend/tracing_agent.cc
+++ b/src/cobalt/debug/backend/tracing_agent.cc
@@ -54,7 +54,7 @@
   return JSONObject();
 }
 
-void TracingAgent::End(const Command& command) {
+void TracingAgent::End(Command command) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (!tracing_started_) {
     command.SendErrorResponse(Command::kInvalidRequest, "Tracing not started");
@@ -66,7 +66,7 @@
   script_debugger_->StopTracing();
 }
 
-void TracingAgent::Start(const Command& command) {
+void TracingAgent::Start(Command command) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (tracing_started_) {
     command.SendErrorResponse(Command::kInvalidRequest,
@@ -114,8 +114,7 @@
 
 void TracingAgent::FlushTraceEvents() {
   SendDataCollectedEvent();
-  dispatcher_->SendEvent(std::string(kInspectorDomain) + ".tracingComplete",
-                         JSONObject());
+  dispatcher_->SendEvent(std::string(kInspectorDomain) + ".tracingComplete");
 }
 
 void TracingAgent::SendDataCollectedEvent() {
diff --git a/src/cobalt/debug/backend/tracing_agent.h b/src/cobalt/debug/backend/tracing_agent.h
index 3b1bc2c..3a5e4a6 100644
--- a/src/cobalt/debug/backend/tracing_agent.h
+++ b/src/cobalt/debug/backend/tracing_agent.h
@@ -41,8 +41,8 @@
   void FlushTraceEvents() override;
 
  private:
-  void End(const Command& command);
-  void Start(const Command& command);
+  void End(Command command);
+  void Start(Command command);
 
   void SendDataCollectedEvent();
 
diff --git a/src/cobalt/debug/command.h b/src/cobalt/debug/command.h
index f403d48..f81449f 100644
--- a/src/cobalt/debug/command.h
+++ b/src/cobalt/debug/command.h
@@ -16,6 +16,7 @@
 
 #include <string>
 
+#include "base/logging.h"
 #include "base/message_loop/message_loop.h"
 #include "cobalt/debug/debug_client.h"
 #include "cobalt/debug/json_object.h"
@@ -48,27 +49,54 @@
         domain_(method_, 0, method_.find('.')),
         json_params_(json_params),
         callback_(response_callback),
-        task_runner_(base::MessageLoop::current()->task_runner()) {}
+        task_runner_(base::MessageLoop::current()->task_runner()),
+        response_sent_(false) {
+    DCHECK(!method_.empty());
+    DCHECK(!domain_.empty());
+    DCHECK(!callback_.is_null());
+  }
 
-  Command(Command&) = delete;
-  Command(Command&&) = delete;
+  // Not copyable.
+  Command(const Command&) = delete;
+  Command& operator=(const Command&) = delete;
+
+  // Movable.
+  Command(Command&&) = default;
+  Command& operator=(Command&&) = default;
+
+  ~Command() {
+    // A response must be sent for all commands, except for the residual null
+    // objects that have been moved-from.
+    DCHECK(response_sent_ || is_null()) << "No response sent for " << method_;
+  }
+
+  // Returns true if this instance has been moved-from and is no longer active.
+  bool is_null() {
+    // We use the default move constructor, and rely on callback's is_null()
+    // to detect whether it has been moved-from. A DCHECK in our constructor
+    // ensures that only moved-from callbacks are null.
+    return callback_.is_null();
+  }
 
   const std::string& GetMethod() const { return method_; }
   const std::string& GetDomain() const { return domain_; }
   const std::string& GetParams() const { return json_params_; }
 
-  void SendResponse(const std::string& json_response) const {
+  void SendResponse(const std::string& json_response) {
+    DCHECK(!is_null()) << "Sending response for null Command";
+    DCHECK(!response_sent_) << "Response already sent for " << method_;
+    response_sent_ = true;
     task_runner_->PostTask(FROM_HERE, base::Bind(callback_, json_response));
   }
 
-  void SendResponse(const JSONObject& response) const {
-    SendResponse(response ? JSONStringify(response) : "{}");
+  void SendResponse(const JSONObject& response) {
+    SendResponse(JSONStringify(response));
   }
 
-  void SendResponse() const { SendResponse(JSONObject()); }
+  void SendResponse() { SendResponse(JSONObject()); }
 
   void SendErrorResponse(ErrorCode error_code,
-                         const std::string& error_message) const {
+                         const std::string& error_message) {
     JSONObject error_response(new base::DictionaryValue());
     error_response->SetInteger("error.code", error_code);
     error_response->SetString("error.message", error_message);
@@ -81,6 +109,7 @@
   std::string json_params_;
   DebugClient::ResponseCallback callback_;
   base::SingleThreadTaskRunner* task_runner_;
+  bool response_sent_;
 };
 
 }  // namespace debug
diff --git a/src/cobalt/debug/console/content/debug_commands.js b/src/cobalt/debug/console/content/debug_commands.js
index 8c4456b..8e0f560 100644
--- a/src/cobalt/debug/console/content/debug_commands.js
+++ b/src/cobalt/debug/console/content/debug_commands.js
@@ -12,118 +12,123 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-function initDebugCommands() {
-  let debugCommands = {};
+function DebugCommands(debugConsole) {
+  this.debugConsole = debugConsole;
+  this.consoleValues = debugConsole.consoleValues;
 
-  debugCommands.cvalList = function() {
-    var result = consoleValues.listAll();
+  this.commandRegistry = {};
+  this.addBuiltinCommands();
+  this.addConsoleCommands();
+}
+
+DebugCommands.prototype.addBuiltinCommands = function() {
+
+  this.commandRegistry.cvalList = () => {
+    let result = this.consoleValues.listAll();
     printToMessageLog(MessageLog.INTERACTIVE, result);
-  }
-  debugCommands.cvalList.shortHelp = 'List all registered console values.';
-  debugCommands.cvalList.longHelp =
+  };
+  this.commandRegistry.cvalList.shortHelp =
+      'List all registered console values.';
+  this.commandRegistry.cvalList.longHelp =
       'List all registered console values that can be displayed.\n' +
       'You can change what subset is displayed in the HUD using ' +
       'the cvalAdd and cvalRemove debug methods.';
 
-  debugCommands.cvalAdd = function(substringToMatch) {
-    var result = consoleValues.addActive(substringToMatch);
+  this.commandRegistry.cvalAdd = (substringToMatch) => {
+    let result = this.consoleValues.addActive(substringToMatch);
     printToMessageLog(MessageLog.INTERACTIVE, result);
     // After each change, save the active set with the default key.
     this.cvalSave();
-  }
-  debugCommands.cvalAdd.shortHelp =
+  };
+  this.commandRegistry.cvalAdd.shortHelp =
       'Adds one or more consoles value to the HUD.';
-  debugCommands.cvalAdd.longHelp =
+  this.commandRegistry.cvalAdd.longHelp =
       'Adds any of the registered consolve values (displayed with cvalList) ' +
       'to the HUD whose name matches one of the specified space-separated '
       'prefixes.';
 
-  debugCommands.cvalRemove = function(substringToMatch) {
-    var result = consoleValues.removeActive(substringToMatch);
+  this.commandRegistry.cvalRemove = (substringToMatch) => {
+    let result = this.consoleValues.removeActive(substringToMatch);
     printToMessageLog(MessageLog.INTERACTIVE, result);
     // After each change, save the active set with the default key.
     this.cvalSave();
-  }
-  debugCommands.cvalRemove.shortHelp =
+  };
+  this.commandRegistry.cvalRemove.shortHelp =
       'Removes one or more consoles value from the HUD.';
-  debugCommands.cvalRemove.longHelp =
+  this.commandRegistry.cvalRemove.longHelp =
       'Removes any of the consolve values displayed in the HUD ' +
       'whose name matches one of the specified space-separated prefixes.';
 
-  debugCommands.cvalSave = function(key) {
-    var result = consoleValues.saveActiveSet(key);
+  this.commandRegistry.cvalSave = (key) => {
+    let result = this.consoleValues.saveActiveSet(key);
     printToMessageLog(MessageLog.INTERACTIVE, result);
-  }
-  debugCommands.cvalSave.shortHelp =
+  };
+  this.commandRegistry.cvalSave.shortHelp =
       'Saves the current set of console values displayed in the HUD.';
-  debugCommands.cvalSave.longHelp =
+  this.commandRegistry.cvalSave.longHelp =
       'Saves the set of console values currently displayed in the HUD ' +
       'to web local storage using the specified key. Saved display sets can ' +
       'be reloaded later using the cvalLoad debug method and the same key.\n' +
       'If no key is specified, uses a default value.';
 
-  debugCommands.cvalLoad = function(key) {
-    var result = consoleValues.loadActiveSet(key);
+  this.commandRegistry.cvalLoad = (key) => {
+    let result = this.consoleValues.loadActiveSet(key);
     printToMessageLog(MessageLog.INTERACTIVE, result);
-  }
-  debugCommands.cvalLoad.shortHelp =
+  };
+  this.commandRegistry.cvalLoad.shortHelp =
       'Loads a previously stored set of console values displayed in the HUD.';
-  debugCommands.cvalLoad.longHelp =
+  this.commandRegistry.cvalLoad.longHelp =
       'Loads the set of console values currently displayed in the HUD ' +
       'from a set previously saved in web local storage using the cvalSave ' +
       'debug method and the same key.\n' +
       'If no key is specified, uses a default value.';
 
-  debugCommands.history = history;
-  debugCommands.history.shortHelp = 'Display command history.';
-  debugCommands.history.longHelp =
+  this.commandRegistry.history = this.history.bind(this);
+  this.commandRegistry.history.shortHelp = 'Display command history.';
+  this.commandRegistry.history.longHelp =
       'Display a list of all previously executed commands with an '+
       'index. You can re-execute any of the commands from the ' +
       'history by typing "!" followed by the index of that command.'
 
-  debugCommands.help = help.bind(this, debugCommands);
-  debugCommands.help.shortHelp =
+  this.commandRegistry.help = this.help.bind(this);
+  this.commandRegistry.help.shortHelp =
       'Display this message, or detail for a specific command.';
-  debugCommands.help.longHelp =
+  this.commandRegistry.help.longHelp =
       'With no arguments, displays a summary of all commands. If the name of ' +
       'a command is specified, displays additional details about that command.';
 
-  debugCommands.dir = dir;
-  debugCommands.dir.shortHelp =
+  this.commandRegistry.dir = this.dir.bind(this);
+  this.commandRegistry.dir.shortHelp =
       'Lists the properties of an object in the main web module.';
-  debugCommands.dir.longHelp =
+  this.commandRegistry.dir.longHelp =
       'Lists the properties of the specified object in the main web module. ' +
       'Remember to enclose the name of the object in quotes.';
 
-  debugCommands.debugger = function() {
-    return debuggerClient;
-  }
-  debugCommands.debugger.shortHelp =
+  this.commandRegistry.debugger = () => {
+    return this.debugConsole.debuggerClient;
+  };
+  this.commandRegistry.debugger.shortHelp =
       'Get the debugger client';
-  debugCommands.debugger.longHelp =
+  this.commandRegistry.debugger.longHelp =
       'Get the debugger client. The debugger client can be used to issue ' +
       'JavaScript debugging commands to the main web module.';
-
-  addConsoleCommands(debugCommands);
-
-  return debugCommands;
 }
 
-function help(debugCommands, command) {
-  var helpString = '';
+DebugCommands.prototype.help = function(command) {
+  let helpString = '';
   if (command) {
     // Detailed help on a specific command.
-    if (debugCommands[command]) {
-      helpString = debugCommands[command].longHelp;
+    if (this.commandRegistry[command]) {
+      helpString = this.commandRegistry[command].longHelp;
     } else {
       helpString = 'Command "' + command + '" not found.';
     }
   } else {
     // Summary help for all commands.
     helpString = 'Cobalt Debug Console commands:\n\n';
-    for (cmd in debugCommands) {
+    for (cmd in this.commandRegistry) {
       helpString += 'debug.' + cmd + '() - ';
-      helpString += debugCommands[cmd].shortHelp + '\n';
+      helpString += this.commandRegistry[cmd].shortHelp + '\n';
     }
     helpString +=
         '\nYou are entering JavaScript, so remember to use parentheses, ' +
@@ -135,34 +140,51 @@
   printToMessageLog(MessageLog.INTERACTIVE, helpString);
 }
 
-function history() {
-  var history = commandInput.getHistory();
-  for (var i = 0; i < history.length; i += 1) {
+DebugCommands.prototype.history = function() {
+  let history = this.debugConsole.commandInput.getHistory();
+  for (let i = 0; i < history.length; i += 1) {
     printToMessageLog(MessageLog.INTERACTIVE, i + ' ' + history[i]);
   }
 }
 
-function dir(objectName) {
-  var js = '(function(obj) {' +
-           '  var properties = obj + "\\n";' +
+DebugCommands.prototype.dir = function(objectName) {
+  let js = '(function(obj) {' +
+           '  let properties = obj + "\\n";' +
            '  for (p in obj) { properties += p + "\\n"; }' +
            '  return properties;' +
            '}(' + objectName + '))';
-  executeMain(js);
+  this.debugConsole.executeMain(js);
 }
 
-function addConsoleCommands(debugCommands) {
-  var consoleCommands = window.debugHub.consoleCommands;
-  for (var i = 0; i < consoleCommands.length; i++) {
-    var c = consoleCommands[i];
-    addOneConsoleCommand(debugCommands, c.command, c.shortHelp, c.longHelp);
+DebugCommands.prototype.addConsoleCommands = function() {
+  let consoleCommands = window.debugHub.consoleCommands;
+  for (let i = 0; i < consoleCommands.length; i++) {
+    let c = consoleCommands[i];
+    this.addOneConsoleCommand(c.command, c.shortHelp, c.longHelp);
   }
 }
 
-function addOneConsoleCommand(debugCommands, command, shortHelp, longHelp) {
-  debugCommands[command] = function(message) {
+DebugCommands.prototype.addOneConsoleCommand = function(
+    command, shortHelp, longHelp) {
+  this.commandRegistry[command] = (message) => {
     window.debugHub.sendConsoleCommand(command, message);
+  };
+  this.commandRegistry[command].shortHelp = shortHelp;
+  this.commandRegistry[command].longHelp = longHelp;
+}
+
+// Run a registered debugger command. If the command appears to be a method of
+// the virtual "debug" (or shorthand "d") object it is run in the debug console
+// web module and true is returned. Otherwise the command is not run and false
+// is returned.
+DebugCommands.prototype.executeCommand = function(command) {
+  if (command.trim().indexOf('debug.') == 0 ||
+      command.trim().indexOf('d.') == 0) {
+    // The "debug" and "d" objects exist only while evaluating the command.
+    let debug = this.commandRegistry;
+    let d = this.commandRegistry;
+    eval(command);
+    return true;
   }
-  debugCommands[command].shortHelp = shortHelp;
-  debugCommands[command].longHelp = longHelp;
+  return false;
 }
diff --git a/src/cobalt/debug/console/content/debug_console.html b/src/cobalt/debug/console/content/debug_console.html
index baed2fd..714724a 100644
--- a/src/cobalt/debug/console/content/debug_console.html
+++ b/src/cobalt/debug/console/content/debug_console.html
@@ -5,14 +5,30 @@
   <link rel="stylesheet" type="text/css" href="debug_console.css">
 </head>
 
-<script type="text/javascript" src="console_manager.js"></script>
-<script type="text/javascript" src="debug_console.js"></script>
-<script type="text/javascript" src="console_values.js"></script>
-<script type="text/javascript" src="message_log.js"></script>
-<script type="text/javascript" src="command_input.js"></script>
-<script type="text/javascript" src="debug_commands.js"></script>
-<script type="text/javascript" src="debugger_client.js"></script>
-<script type="text/javascript" src="media_console.js"></script>
+<script type="text/javascript">
+  const scripts = [
+    'console_manager.js',
+    'debug_console.js',
+    'console_values.js',
+    'message_log.js',
+    'command_input.js',
+    'debug_commands.js',
+    'debugger_client.js',
+    'media_console.js',
+  ];
+
+  // Load each script one after the other.
+  function LoadScript(index) {
+    var script = document.createElement('script');
+    if (index < scripts.length - 1) {
+      // Load the next script after this one finishes loading.
+      script.onload = LoadScript.bind(this, index + 1);
+    }
+    script.src = scripts[index];
+    document.head.appendChild(script);
+  }
+  LoadScript(0);
+</script>
 
 <body>
   <div id="hudFrame">
diff --git a/src/cobalt/debug/console/content/debug_console.js b/src/cobalt/debug/console/content/debug_console.js
index 088b744..2fe9820 100644
--- a/src/cobalt/debug/console/content/debug_console.js
+++ b/src/cobalt/debug/console/content/debug_console.js
@@ -33,7 +33,7 @@
   let loadResult = this.consoleValues.loadActiveSet();
   this.printToMessageLog(MessageLog.INTERACTIVE, loadResult);
 
-  this.debugCommands = initDebugCommands();
+  this.debugCommands = new DebugCommands(this);
 }
 
 DebugConsole.prototype.printToMessageLog = function(severity, message) {
@@ -82,27 +82,13 @@
     return true;
   } else if (command.trim() == 'help') {
     // Treat 'help' as a special case for users not expecting JS execution.
-    help();
+    this.debugCommands.help();
     this.commandInput.clearCurrentCommand();
     return true;
   }
   return false;
 }
 
-// JavaScript commands executed in this (debug console) web module.
-// The only commands we execute here are methods of the debug object
-// (or its shorthand equivalent).
-DebugConsole.prototype.executeDebug = function(command) {
-  if (command.trim().indexOf('debug.') == 0 ||
-      command.trim().indexOf('d.') == 0) {
-    let debug = this.debugCommands;
-    let d = this.debugCommands;
-    eval(command);
-    return true;
-  }
-  return false;
-}
-
 // Execute a command as JavaScript in the main web module.
 // Use the debugger evaluate command, which gives us Command Line API access
 // and rich results with object preview.
@@ -124,7 +110,7 @@
     return;
   }
   this.commandInput.storeAndClearCurrentCommand();
-  if (this.executeDebug(command)) {
+  if (this.debugCommands.executeCommand(command)) {
     this.printToMessageLog(MessageLog.INTERACTIVE, '');
     return;
   }
diff --git a/src/cobalt/debug/console/debug_hub.cc b/src/cobalt/debug/console/debug_hub.cc
index 2d9ecdc..a9f2e03 100644
--- a/src/cobalt/debug/console/debug_hub.cc
+++ b/src/cobalt/debug/console/debug_hub.cc
@@ -148,7 +148,7 @@
 }
 
 void DebugHub::OnDebugClientEvent(const std::string& method,
-                                  const base::Optional<std::string>& params) {
+                                  const std::string& params) {
   // Pass to the onEvent handler. The handler will notify the JavaScript
   // listener on the message loop the listener was registered on.
   on_event_->DispatchEvent(method, params);
diff --git a/src/cobalt/debug/console/debug_hub.h b/src/cobalt/debug/console/debug_hub.h
index 319e862..0fa8a55 100644
--- a/src/cobalt/debug/console/debug_hub.h
+++ b/src/cobalt/debug/console/debug_hub.h
@@ -114,9 +114,8 @@
       const base::Optional<std::string>& response) const;
 
   // DebugClient::Delegate implementation.
-  void OnDebugClientEvent(
-      const std::string& method,
-      const base::Optional<std::string>& json_params) override;
+  void OnDebugClientEvent(const std::string& method,
+                          const std::string& json_params) override;
   void OnDebugClientDetach(const std::string& reason) override;
 
  private:
diff --git a/src/cobalt/debug/console/debugger_event_target.cc b/src/cobalt/debug/console/debugger_event_target.cc
index e3d8098..3b65b28 100644
--- a/src/cobalt/debug/console/debugger_event_target.cc
+++ b/src/cobalt/debug/console/debugger_event_target.cc
@@ -30,8 +30,8 @@
   }
 }
 
-void DebuggerEventTarget::DispatchEvent(
-    const std::string& method, const base::Optional<std::string>& json_params) {
+void DebuggerEventTarget::DispatchEvent(const std::string& method,
+                                        const std::string& json_params) {
   base::AutoLock auto_lock(lock_);
 
   for (ListenerSet::const_iterator it = listeners_.begin();
diff --git a/src/cobalt/debug/console/debugger_event_target.h b/src/cobalt/debug/console/debugger_event_target.h
index 62413a4..92b6fca 100644
--- a/src/cobalt/debug/console/debugger_event_target.h
+++ b/src/cobalt/debug/console/debugger_event_target.h
@@ -62,8 +62,7 @@
 
   // Dispatches a debugger event to the registered listeners.
   // May be called from any thread.
-  void DispatchEvent(const std::string& method,
-                     const base::Optional<std::string>& json_params);
+  void DispatchEvent(const std::string& method, const std::string& json_params);
 
   // Called from JavaScript to register an event listener callback.
   // May be called from any thread.
diff --git a/src/cobalt/debug/console/debugger_event_target.idl b/src/cobalt/debug/console/debugger_event_target.idl
index 9d42e52..9feb526 100644
--- a/src/cobalt/debug/console/debugger_event_target.idl
+++ b/src/cobalt/debug/console/debugger_event_target.idl
@@ -22,4 +22,4 @@
     void addListener(DebuggerEventCallback callback);
 };
 
-callback DebuggerEventCallback = void(DOMString method, DOMString? json_params);
+callback DebuggerEventCallback = void(DOMString method, DOMString json_params);
diff --git a/src/cobalt/debug/debug_client.cc b/src/cobalt/debug/debug_client.cc
index cfb9269..4ec0879 100644
--- a/src/cobalt/debug/debug_client.cc
+++ b/src/cobalt/debug/debug_client.cc
@@ -42,7 +42,7 @@
 }
 
 void DebugClient::OnEvent(const std::string& method,
-                          const base::Optional<std::string>& json_params) {
+                          const std::string& json_params) {
   DCHECK(delegate_);
   delegate_->OnDebugClientEvent(method, json_params);
 }
@@ -67,8 +67,7 @@
     DLOG(WARNING) << "Debug client is not attached to dispatcher.";
     return;
   }
-  dispatcher_->SendCommand(
-      std::make_unique<Command>(method, json_params, callback));
+  dispatcher_->SendCommand(Command(method, json_params, callback));
 }
 
 }  // namespace debug
diff --git a/src/cobalt/debug/debug_client.h b/src/cobalt/debug/debug_client.h
index 2a21895..7ec6bf5 100644
--- a/src/cobalt/debug/debug_client.h
+++ b/src/cobalt/debug/debug_client.h
@@ -58,9 +58,8 @@
     // Event handlers called by the debug dispatcher from its thread. The
     // implementation is responsible for posting the event to its own message
     // loop if necessary.
-    virtual void OnDebugClientEvent(
-        const std::string& method,
-        const base::Optional<std::string>& json_params) = 0;
+    virtual void OnDebugClientEvent(const std::string& method,
+                                    const std::string& json_params) = 0;
 
     virtual void OnDebugClientDetach(const std::string& reason) = 0;
 
@@ -93,8 +92,7 @@
   void OnDetach(const std::string& reason);
 
   // Called by the dispatcher when a debugging event occurs.
-  void OnEvent(const std::string& method,
-               const base::Optional<std::string>& json_params);
+  void OnEvent(const std::string& method, const std::string& json_params);
 
   // No ownership. Access must be protected by |dispatcher_lock_|.
   backend::DebugDispatcher* dispatcher_;
diff --git a/src/cobalt/debug/json_object.cc b/src/cobalt/debug/json_object.cc
index 1512135..4a89448 100644
--- a/src/cobalt/debug/json_object.cc
+++ b/src/cobalt/debug/json_object.cc
@@ -36,7 +36,7 @@
 JSONObject JSONParse(const std::string& json) { return JSONParse(json, NULL); }
 
 std::string JSONStringify(const JSONObject& json_object) {
-  DCHECK(json_object);
+  if (!json_object) return "{}";
   std::string json;
   base::JSONWriter::Write(*(json_object.get()), &json);
   return json;
diff --git a/src/cobalt/debug/remote/debug_web_server.cc b/src/cobalt/debug/remote/debug_web_server.cc
index 1f27f07..afda887 100644
--- a/src/cobalt/debug/remote/debug_web_server.cc
+++ b/src/cobalt/debug/remote/debug_web_server.cc
@@ -260,8 +260,8 @@
                              kNetworkTrafficAnnotation);
 }
 
-void DebugWebServer::OnDebugClientEvent(
-    const std::string& method, const base::Optional<std::string>& json_params) {
+void DebugWebServer::OnDebugClientEvent(const std::string& method,
+                                        const std::string& json_params) {
   // Squelch the Cobalt-specific log message meant only for the overlay console.
   if (method == kLogBrowserEntryAdded) {
     return;
@@ -278,12 +278,7 @@
 
   JSONObject event(new base::DictionaryValue());
   event->SetString(kMethodField, method);
-  JSONObject params;
-  if (json_params) params = JSONParse(json_params.value());
-  // |params| may be NULL if event does not use them.
-  if (params) {
-    event->Set(kParamsField, std::move(params));
-  }
+  event->Set(kParamsField, JSONParse(json_params));
   server_->SendOverWebSocket(websocket_id_, JSONStringify(event),
                              kNetworkTrafficAnnotation);
 }
diff --git a/src/cobalt/debug/remote/debug_web_server.h b/src/cobalt/debug/remote/debug_web_server.h
index af57a25..1f1fbe1 100644
--- a/src/cobalt/debug/remote/debug_web_server.h
+++ b/src/cobalt/debug/remote/debug_web_server.h
@@ -59,9 +59,8 @@
   void OnDebuggerResponse(int id, const base::Optional<std::string>& response);
 
   // DebugClient::Delegate implementation.
-  void OnDebugClientEvent(
-      const std::string& method,
-      const base::Optional<std::string>& json_params) override;
+  void OnDebugClientEvent(const std::string& method,
+                          const std::string& json_params) override;
 
   void OnDebugClientDetach(const std::string& reason) override;
 
diff --git a/src/cobalt/doc/voice_search.md b/src/cobalt/doc/voice_search.md
index 65319fc..c79f84c 100644
--- a/src/cobalt/doc/voice_search.md
+++ b/src/cobalt/doc/voice_search.md
@@ -11,14 +11,13 @@
 In both approaches, in order to check whether to enable voice control or not,
 web apps will call the [MediaDevices.enumerateDevices()](https://www.w3.org/TR/mediacapture-streams/#dom-mediadevices-enumeratedevices%28%29)
 Web API function within which Cobalt will in turn call a subset of the
-[Starboard SbMicrophone API](../../starboard/microphone.h).  In both approaches,
-`SB_HAS_MICROPHONE` must be defined to 1.
+[Starboard SbMicrophone API](../../starboard/microphone.h).
 
 ## MediaRecorder API
 
 To enable the MediaRecorder API in Cobalt, the complete
 [SbMicrophone API](../../starboard/microphone.h) must be implemented, and
-the `SB_HAS_SPEECH_RECOGNIZER` must be defined to 0.
+`SbSpeechRecognizerIsSupported()` must return `false`.
 
 ## Speech Recognition API
 
@@ -28,9 +27,8 @@
 
 ### Specific instructions to enable voice search
 
-1. Define the `SB_HAS_SPEECH_RECOGNIZER` flag to have the value 1 in your
-   platform's `configuration_public.h` file, and implement the
-   [SbSpeechRecognizer API](../../starboard/speech_recognizer.h).
+1. Implement `SbSpeechRecognizerIsSupported()` to return `true`, and implement
+   the [SbSpeechRecognizer API](../../starboard/speech_recognizer.h).
 2. Implement the following subset of the
    [SbMicrophone API](../../starboard/microphone.h):
     - `SbMicrophoneGetAvailable()`
@@ -42,7 +40,8 @@
    return `false`.
 3. The YouTube app will display the mic icon on the search page when it detects
    valid microphone input devices using `MediaDevices.enumerateDevices()`.
-4. With `SB_HAS_SPEECH_RECOGNIZER` defined to 1, Cobalt will use the platform's
+4. With `SbSpeechRecognizerIsSupported()` implemented to return `true`, Cobalt
+   will use the platform's
    [Starboard SbSpeechRecognizer API](../../starboard/speech_recognizer.h)
    implementation, and it will not actually read directly from the microphone
    via the [Starboard SbMicrophone API](../../starboard/microphone.h).
diff --git a/src/cobalt/dom/animation_frame_request_callback_list.cc b/src/cobalt/dom/animation_frame_request_callback_list.cc
index 2b27de6..0ceaea3 100644
--- a/src/cobalt/dom/animation_frame_request_callback_list.cc
+++ b/src/cobalt/dom/animation_frame_request_callback_list.cc
@@ -31,7 +31,7 @@
   frame_request_callbacks_.emplace_back(
       new FrameRequestCallbackWithCancelledFlag(owner_,
                                                 frame_request_callback));
-  debugger_hooks_->AsyncTaskScheduled(
+  debugger_hooks_.AsyncTaskScheduled(
       frame_request_callbacks_.back().get(), "requestAnimationFrame",
       base::DebuggerHooks::AsyncTaskFrequency::kOneshot);
   return static_cast<int32>(frame_request_callbacks_.size());
@@ -43,7 +43,7 @@
   const size_t handle = static_cast<size_t>(in_handle);
   if (handle > 0 && handle <= frame_request_callbacks_.size()) {
     auto& callback = frame_request_callbacks_.at(handle - 1);
-    debugger_hooks_->AsyncTaskCanceled(callback.get());
+    debugger_hooks_.AsyncTaskCanceled(callback.get());
     callback->cancelled = true;
   }
 }
diff --git a/src/cobalt/dom/animation_frame_request_callback_list.h b/src/cobalt/dom/animation_frame_request_callback_list.h
index a809cd9..1c87fd4 100644
--- a/src/cobalt/dom/animation_frame_request_callback_list.h
+++ b/src/cobalt/dom/animation_frame_request_callback_list.h
@@ -39,7 +39,7 @@
   typedef script::ScriptValue<FrameRequestCallback> FrameRequestCallbackArg;
 
   explicit AnimationFrameRequestCallbackList(
-      script::Wrappable* const owner, base::DebuggerHooks* const debugger_hooks)
+      script::Wrappable* const owner, const base::DebuggerHooks& debugger_hooks)
       : owner_(owner), debugger_hooks_(debugger_hooks) {}
 
   int32 RequestAnimationFrame(
@@ -68,7 +68,7 @@
       InternalList;
 
   script::Wrappable* const owner_;
-  base::DebuggerHooks* const debugger_hooks_;
+  const base::DebuggerHooks& debugger_hooks_;
   // Our list of frame request callbacks.
   InternalList frame_request_callbacks_;
 };
diff --git a/src/cobalt/dom/dom.gyp b/src/cobalt/dom/dom.gyp
index 0eceae5..f60ca4e 100644
--- a/src/cobalt/dom/dom.gyp
+++ b/src/cobalt/dom/dom.gyp
@@ -344,6 +344,7 @@
         '<(DEPTH)/cobalt/web_animations/web_animations.gyp:web_animations',
         '<(DEPTH)/nb/nb.gyp:nb',
         '<(DEPTH)/net/net.gyp:net',
+        '<(DEPTH)/third_party/icu/icu.gyp:icuuc',
         '<(DEPTH)/url/url.gyp:url',
       ],
       'conditions': [
diff --git a/src/cobalt/dom/dom_settings.cc b/src/cobalt/dom/dom_settings.cc
index 9490e3b..d525a3b 100644
--- a/src/cobalt/dom/dom_settings.cc
+++ b/src/cobalt/dom/dom_settings.cc
@@ -28,7 +28,7 @@
     media::CanPlayTypeHandler* can_play_type_handler,
     script::JavaScriptEngine* engine,
     script::GlobalEnvironment* global_environment,
-    base::DebuggerHooks* debugger_hooks,
+    const base::DebuggerHooks& debugger_hooks,
     MutationObserverTaskManager* mutation_observer_task_manager,
     const Options& options)
     : max_dom_element_depth_(max_dom_element_depth),
diff --git a/src/cobalt/dom/dom_settings.h b/src/cobalt/dom/dom_settings.h
index 804b02a..ca8d674 100644
--- a/src/cobalt/dom/dom_settings.h
+++ b/src/cobalt/dom/dom_settings.h
@@ -60,7 +60,7 @@
               media::CanPlayTypeHandler* can_play_type_handler,
               script::JavaScriptEngine* engine,
               script::GlobalEnvironment* global_environment_proxy,
-              base::DebuggerHooks* debugger_hooks,
+              const base::DebuggerHooks& debugger_hooks,
               MutationObserverTaskManager* mutation_observer_task_manager,
               const Options& options = Options());
   ~DOMSettings() override;
@@ -93,7 +93,7 @@
   media::CanPlayTypeHandler* can_play_type_handler() const {
     return can_play_type_handler_;
   }
-  base::DebuggerHooks* debugger_hooks() const { return debugger_hooks_; }
+  const base::DebuggerHooks& debugger_hooks() const { return debugger_hooks_; }
   MutationObserverTaskManager* mutation_observer_task_manager() const {
     return mutation_observer_task_manager_;
   }
@@ -116,7 +116,7 @@
   media::CanPlayTypeHandler* can_play_type_handler_;
   script::JavaScriptEngine* javascript_engine_;
   script::GlobalEnvironment* global_environment_;
-  base::DebuggerHooks* debugger_hooks_;
+  const base::DebuggerHooks& debugger_hooks_;
   MutationObserverTaskManager* mutation_observer_task_manager_;
 
   DISALLOW_COPY_AND_ASSIGN(DOMSettings);
diff --git a/src/cobalt/dom/event_target.cc b/src/cobalt/dom/event_target.cc
index 367d872..f8dba40 100644
--- a/src/cobalt/dom/event_target.cc
+++ b/src/cobalt/dom/event_target.cc
@@ -37,9 +37,7 @@
     : debugger_hooks_(
           base::polymorphic_downcast<DOMSettings*>(settings)->debugger_hooks()),
       unpack_onerror_events_(onerror_event_parameter_handling ==
-                             kUnpackOnErrorEvents) {
-  DCHECK(debugger_hooks_);
-}
+                             kUnpackOnErrorEvents) {}
 
 void EventTarget::AddEventListener(const std::string& type,
                                    const EventListenerScriptValue& listener,
@@ -68,7 +66,7 @@
   for (EventListenerInfos::iterator iter = event_listener_infos_.begin();
        iter != event_listener_infos_.end(); ++iter) {
     if ((*iter)->EqualTo(listener_info)) {
-      debugger_hooks_->AsyncTaskCanceled((*iter)->task());
+      debugger_hooks_.AsyncTaskCanceled((*iter)->task());
       event_listener_infos_.erase(iter);
       return;
     }
@@ -276,7 +274,7 @@
     for (EventListenerInfos::iterator iter = event_listener_infos_.begin();
          iter != event_listener_infos_.end(); ++iter) {
       if ((*iter)->is_attribute() && (*iter)->type() == listener_info->type()) {
-        debugger_hooks_->AsyncTaskCanceled((*iter)->task());
+        debugger_hooks_.AsyncTaskCanceled((*iter)->task());
         event_listener_infos_.erase(iter);
         break;
       }
@@ -296,7 +294,7 @@
     }
   }
 
-  debugger_hooks_->AsyncTaskScheduled(
+  debugger_hooks_.AsyncTaskScheduled(
       listener_info->task(), listener_info->type().c_str(),
       base::DebuggerHooks::AsyncTaskFrequency::kRecurring);
   event_listener_infos_.push_back(std::move(listener_info));
diff --git a/src/cobalt/dom/event_target.h b/src/cobalt/dom/event_target.h
index 82715ce..eae5c6a 100644
--- a/src/cobalt/dom/event_target.h
+++ b/src/cobalt/dom/event_target.h
@@ -469,7 +469,7 @@
   DEFINE_WRAPPABLE_TYPE(EventTarget);
   void TraceMembers(script::Tracer* tracer) override;
 
-  base::DebuggerHooks* debugger_hooks() { return debugger_hooks_; }
+  const base::DebuggerHooks& debugger_hooks() { return debugger_hooks_; }
 
  private:
   typedef std::vector<std::unique_ptr<EventTargetListenerInfo>>
@@ -485,7 +485,7 @@
 
   EventListenerInfos event_listener_infos_;
 
-  base::DebuggerHooks* debugger_hooks_;
+  const base::DebuggerHooks& debugger_hooks_;
 
   // Tracks whether this current event listener should unpack the onerror
   // event object when calling its callback.  This is needed to implement
diff --git a/src/cobalt/dom/event_target_test.cc b/src/cobalt/dom/event_target_test.cc
index 1e09756..3e4c69c 100644
--- a/src/cobalt/dom/event_target_test.cc
+++ b/src/cobalt/dom/event_target_test.cc
@@ -50,7 +50,7 @@
  protected:
   EventTargetTest()
       : environment_settings_(0, nullptr, nullptr, nullptr, nullptr, nullptr,
-                              nullptr, nullptr, &debugger_hooks_, nullptr,
+                              nullptr, nullptr, debugger_hooks_, nullptr,
                               DOMSettings::Options()) {}
 
   StrictMock<test::MockDebuggerHooks> debugger_hooks_;
diff --git a/src/cobalt/dom/html_element.cc b/src/cobalt/dom/html_element.cc
index 7b7483c..dc43248 100644
--- a/src/cobalt/dom/html_element.cc
+++ b/src/cobalt/dom/html_element.cc
@@ -57,7 +57,10 @@
 #include "cobalt/dom/html_unknown_element.h"
 #include "cobalt/dom/html_video_element.h"
 #include "cobalt/dom/rule_matching.h"
+#include "cobalt/dom/text.h"
 #include "cobalt/loader/image/animated_image_tracker.h"
+#include "third_party/icu/source/common/unicode/uchar.h"
+#include "third_party/icu/source/common/unicode/utf8.h"
 
 using cobalt::cssom::ViewportSize;
 
@@ -166,10 +169,10 @@
   // return the conforming value associated with the state the attribute is in,
   // or the empty string if the attribute is in a state with no associated
   // keyword value.
-  // https://dev.w3.org/html5/spec-preview/global-attributes.html#the-directionality
-  // https://dev.w3.org/html5/spec-preview/common-dom-interfaces.html#limited-to-only-known-values
-  // NOTE: Value "auto" is not supported.
-  if (dir_ == kDirLeftToRight) {
+  // https://html.spec.whatwg.org/commit-snapshots/ebcac971c2add28a911283899da84ec509876c44/#the-dir-attribute
+  if (dir_ == kDirAuto) {
+    return "auto";
+  } else if (dir_ == kDirLeftToRight) {
     return "ltr";
   } else if (dir_ == kDirRightToLeft) {
     return "rtl";
@@ -1392,9 +1395,10 @@
 
 void HTMLElement::SetDir(const std::string& value) {
   // https://html.spec.whatwg.org/commit-snapshots/ebcac971c2add28a911283899da84ec509876c44/#the-dir-attribute
-  // NOTE: Value "auto" is not supported.
   auto previous_dir = dir_;
-  if (value == "ltr") {
+  if (value == "auto") {
+    dir_ = kDirAuto;
+  } else if (value == "ltr") {
     dir_ = kDirLeftToRight;
   } else if (value == "rtl") {
     dir_ = kDirRightToLeft;
@@ -1424,6 +1428,145 @@
   }
 }
 
+namespace {
+// This is similar to base rtl.h's GetStringDirection; however, this takes a
+// utf8 string and only pays attention to L, AL, and R character types.
+HTMLElement::DirState GetStringDirection(const std::string& utf8_string) {
+  int32_t length = static_cast<int32_t>(utf8_string.length());
+  for (int32_t index = 0; index < length;) {
+    int32_t ch;
+    U8_NEXT(utf8_string.data(), index, length, ch);
+    if (ch < 0) {
+      LOG(ERROR) << "Unable to determine directionality of " << utf8_string;
+      break;
+    }
+
+    int32_t property = u_getIntPropertyValue(ch, UCHAR_BIDI_CLASS);
+    if (property == U_LEFT_TO_RIGHT) {
+      return HTMLElement::kDirLeftToRight;
+    }
+    if (property == U_RIGHT_TO_LEFT ||
+        property == U_RIGHT_TO_LEFT_ARABIC) {
+      return HTMLElement::kDirRightToLeft;
+    }
+  }
+  return HTMLElement::kDirNotDefined;
+}
+}  // namespace
+
+// This is similar to dir_state() except it will resolve kDirAuto to
+// kDirLeftToRight or kDirRightToLeft according to the spec:
+//   https://html.spec.whatwg.org/commit-snapshots/ebcac971c2add28a911283899da84ec509876c44/#the-directionality
+// If "dir" was not defined for this element, then this function will return
+// kDirNotDefined.
+HTMLElement::DirState HTMLElement::GetUsedDirState() {
+  // If the element's dir attribute is in the auto state
+  // If the element is a bdi element and the dir attribute is not in a defined
+  //   state (i.e. it is not present or has an invalid value)
+  if (dir_ != kDirAuto) {
+    return dir_;
+  }
+
+  // Find the first character in tree order that matches the following criteria:
+  //   The character is from a Text node that is a descendant of the element
+  //     whose directionality is being determined.
+  //   The character is of bidirectional character type L, AL, or R. [BIDI]
+  //   The character is not in a Text node that has an ancestor element that is
+  //     a descendant of the element whose directionality is being determined
+  //     and that is either:
+  //       A bdi element.
+  //       A script element.
+  //       A style element.
+  //       A textarea element.
+  //       An element with a dir attribute in a defined state.
+  //   If such a character is found and it is of bidirectional character type
+  //     AL or R, the directionality of the element is 'rtl'.
+  //   If such a character is found and it is of bidirectional character type
+  //     L, the directionality of the element is 'ltr'.
+
+  // A tree is a finite hierarchical tree structure. In tree order is preorder,
+  // depth-first traversal of a tree.
+  //   https://dom.spec.whatwg.org/#concept-tree-order
+  std::vector<Node*> stack;
+
+  // Add children in reverse order so that pop_back() will result in preorder
+  // depth-first traversal.
+  for (Node* child_node = last_child(); child_node;
+       child_node = child_node->previous_sibling()) {
+    stack.push_back(child_node);
+  }
+
+  while (!stack.empty()) {
+    Node* node = stack.back();
+    stack.pop_back();
+
+    Text* text = node->AsText();
+    if (text) {
+      // If the text has strong directionality, then return it.
+      DirState dir = GetStringDirection(text->text());
+      if (dir != kDirNotDefined) {
+        return dir;
+      }
+    }
+
+    // Traverse children only if this is not:
+    //   A bdi element.
+    //   A script element.
+    //   A style element.
+    //   A textarea element.
+    //   An element with a dir attribute in a defined state.
+    Element* element = node->AsElement();
+    if (element) {
+      HTMLElement* html_element = element->AsHTMLElement();
+      if (html_element) {
+        if (html_element->AsHTMLScriptElement() ||
+            html_element->AsHTMLStyleElement() ||
+            html_element->dir_state() != kDirNotDefined) {
+          continue;
+        }
+      }
+    }
+
+    for (Node* child_node = node->last_child(); child_node;
+         child_node = child_node->previous_sibling()) {
+      stack.push_back(child_node);
+    }
+  }
+
+  // Otherwise, if the element is a document element, the directionality of
+  //   the element is 'ltr'.
+  if (IsDocumentElement()) {
+    return kDirLeftToRight;
+  }
+
+  // Although the spec says to use the parent's directionality, the W3C test
+  // (the-dir-attribute-069.html) says to default to LTR. Chrome follows the
+  // W3C expectation, so follow Chrome. Additional discussion here:
+  //   https://github.com/w3c/i18n-drafts/issues/235
+  // The following code block which implements the spec is left for reference.
+#if 0
+  // Otherwise, the directionality of the element is the same as the element's
+  //   parent element's directionality.
+  for (Node* ancestor_node = parent_node(); ancestor_node;
+       ancestor_node = ancestor_node->parent_node()) {
+    Element* ancestor_element = ancestor_node->AsElement();
+    if (!ancestor_element) {
+      continue;
+    }
+    HTMLElement* ancestor_html_element = ancestor_element->AsHTMLElement();
+    if (!ancestor_html_element) {
+      continue;
+    }
+    if (ancestor_html_element->dir_state() == kDirNotDefined) {
+      continue;
+    }
+    return ancestor_html_element->GetUsedDirState();
+  }
+#endif
+
+  return kDirLeftToRight;
+}
+
 // Algorithm:
 //   https://html.spec.whatwg.org/commit-snapshots/ebcac971c2add28a911283899da84ec509876c44/#the-directionality
 Directionality HTMLElement::directionality() {
diff --git a/src/cobalt/dom/html_element.h b/src/cobalt/dom/html_element.h
index d1f548d..c71d733 100644
--- a/src/cobalt/dom/html_element.h
+++ b/src/cobalt/dom/html_element.h
@@ -135,8 +135,8 @@
   };
 
   // https://html.spec.whatwg.org/commit-snapshots/ebcac971c2add28a911283899da84ec509876c44/#the-dir-attribute
-  // NOTE: 'auto' is not supported.
   enum DirState {
+    kDirAuto,
     kDirLeftToRight,
     kDirRightToLeft,
     kDirNotDefined,
@@ -237,6 +237,13 @@
   // enumerated state rather than string.
   DirState dir_state() const { return dir_; }
 
+  // This is similar to dir_state() except it will resolve kDirAuto to
+  // kDirLeftToRight or kDirRightToLeft according to the spec:
+  //   https://html.spec.whatwg.org/commit-snapshots/ebcac971c2add28a911283899da84ec509876c44/#the-directionality
+  // If "dir" was not defined for this element, then this function will return
+  // kDirNotDefined.
+  virtual DirState GetUsedDirState();
+
   // Rule matching related methods.
   //
   // Returns the rule matching state of this element.
@@ -351,6 +358,12 @@
   // https://www.w3.org/TR/html5/semantics.html#the-root-element.
   bool IsRootElement();
 
+  // Returns true if this is a document element.
+  // https://dom.spec.whatwg.org/#document-element
+  bool IsDocumentElement() const {
+    return parent_node() && parent_node()->IsDocument();
+  }
+
   DEFINE_WRAPPABLE_TYPE(HTMLElement);
 
  protected:
diff --git a/src/cobalt/dom/html_element_context.cc b/src/cobalt/dom/html_element_context.cc
index 7e55422..6d90e5f 100644
--- a/src/cobalt/dom/html_element_context.cc
+++ b/src/cobalt/dom/html_element_context.cc
@@ -45,7 +45,7 @@
       dom_stat_tracker_(NULL),
       page_visibility_state_weak_ptr_factory_(&page_visibility_state_),
       video_playback_rate_multiplier_(1.f),
-      sync_load_thread_("Synchronous Load"),
+      sync_load_thread_("SynchronousLoad"),
       html_element_factory_(new HTMLElementFactory()) {
   sync_load_thread_.Start();
 }
@@ -70,7 +70,7 @@
     const std::string& font_language_script,
     base::ApplicationState initial_application_state,
     base::WaitableEvent* synchronous_loader_interrupt,
-    float video_playback_rate_multiplier)
+    bool enable_inline_script_warnings, float video_playback_rate_multiplier)
     : environment_settings_(environment_settings),
       fetcher_factory_(fetcher_factory),
       loader_factory_(loader_factory),
@@ -94,7 +94,8 @@
       page_visibility_state_weak_ptr_factory_(&page_visibility_state_),
       video_playback_rate_multiplier_(video_playback_rate_multiplier),
       synchronous_loader_interrupt_(synchronous_loader_interrupt),
-      sync_load_thread_("Synchronous Load"),
+      enable_inline_script_warnings_(enable_inline_script_warnings),
+      sync_load_thread_("SynchronousLoad"),
       html_element_factory_(new HTMLElementFactory()) {
   sync_load_thread_.Start();
 }
diff --git a/src/cobalt/dom/html_element_context.h b/src/cobalt/dom/html_element_context.h
index 71d761c..56d4295 100644
--- a/src/cobalt/dom/html_element_context.h
+++ b/src/cobalt/dom/html_element_context.h
@@ -74,6 +74,7 @@
       const std::string& font_language_script,
       base::ApplicationState initial_application_state,
       base::WaitableEvent* synchronous_loader_interrupt,
+      bool enable_inline_script_warnings = false,
       float video_playback_rate_multiplier = 1.0);
   ~HTMLElementContext();
 
@@ -118,6 +119,10 @@
     return synchronous_loader_interrupt_;
   }
 
+  bool enable_inline_script_warnings() const {
+    return enable_inline_script_warnings_;
+  }
+
   loader::image::AnimatedImageTracker* animated_image_tracker() const {
     return animated_image_tracker_;
   }
@@ -185,6 +190,7 @@
       page_visibility_state_weak_ptr_factory_;
   const float video_playback_rate_multiplier_;
   base::WaitableEvent* synchronous_loader_interrupt_ = nullptr;
+  bool enable_inline_script_warnings_;
 
   base::Thread sync_load_thread_;
   std::unique_ptr<HTMLElementFactory> html_element_factory_;
diff --git a/src/cobalt/dom/html_element_test.cc b/src/cobalt/dom/html_element_test.cc
index 25ad26b..2bc1ac2 100644
--- a/src/cobalt/dom/html_element_test.cc
+++ b/src/cobalt/dom/html_element_test.cc
@@ -224,9 +224,8 @@
     html_element->set_dir("rtl");
     EXPECT_EQ("rtl", html_element->dir());
 
-    // Value "auto" is not supported.
     html_element->set_dir("auto");
-    EXPECT_EQ("", html_element->dir());
+    EXPECT_EQ("auto", html_element->dir());
 
     html_element->SetAttribute("Dir", "rtl");
     EXPECT_EQ("rtl", html_element->dir());
diff --git a/src/cobalt/dom/html_html_element.cc b/src/cobalt/dom/html_html_element.cc
index 3663b9f..aff89d9 100644
--- a/src/cobalt/dom/html_html_element.cc
+++ b/src/cobalt/dom/html_html_element.cc
@@ -14,11 +14,32 @@
 
 #include "cobalt/dom/html_html_element.h"
 
+#include "cobalt/dom/document.h"
+#include "cobalt/dom/html_body_element.h"
+
 namespace cobalt {
 namespace dom {
 
 // static
 const char HTMLHtmlElement::kTagName[] = "html";
 
+HTMLElement::DirState HTMLHtmlElement::GetUsedDirState() {
+  // The principal writing mode of the document is determined by the used
+  // writing-mode, direction, and text-orientation values of the root element.
+  // As a special case for handling HTML documents, if the root element has a
+  // body child element, the used value of the of writing-mode and direction
+  // properties on root element are taken from the computed writing-mode and
+  // direction of the first such child element instead of from the root
+  // element's own values. The UA may also propagate the value of
+  // text-orientation in this manner. Note that this does not affect the
+  // computed values of writing-mode, direction, or text-orientation of the
+  // root element itself.
+  // https://www.w3.org/TR/css-writing-modes-3/#principal-flow
+  if (node_document() && node_document()->body()) {
+    return node_document()->body()->GetUsedDirState();
+  }
+  return HTMLElement::GetUsedDirState();
+}
+
 }  // namespace dom
 }  // namespace cobalt
diff --git a/src/cobalt/dom/html_html_element.h b/src/cobalt/dom/html_html_element.h
index 9dce632..e34652f 100644
--- a/src/cobalt/dom/html_html_element.h
+++ b/src/cobalt/dom/html_html_element.h
@@ -36,6 +36,8 @@
   // Custom, not in any spec.
   scoped_refptr<HTMLHtmlElement> AsHTMLHtmlElement() override { return this; }
 
+  DirState GetUsedDirState() override;
+
   DEFINE_WRAPPABLE_TYPE(HTMLHtmlElement);
 
  private:
diff --git a/src/cobalt/dom/html_script_element.cc b/src/cobalt/dom/html_script_element.cc
index 4f2b23f..cdb642e 100644
--- a/src/cobalt/dom/html_script_element.cc
+++ b/src/cobalt/dom/html_script_element.cc
@@ -22,6 +22,7 @@
 #include "base/compiler_specific.h"
 #include "base/strings/string_util.h"
 #include "base/trace_event/trace_event.h"
+#include "cobalt/base/console_log.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/document.h"
@@ -248,6 +249,23 @@
     // Option 2
     // If the element has a src attribute, and the element has been flagged as
     // "parser-inserted", and the element does not have an async attribute.
+
+    if (owner_document()
+            ->html_element_context()
+            ->enable_inline_script_warnings()) {
+      CLOG(WARNING, debugger_hooks())
+          << "A request to synchronously load a script is being made as "
+             "a result of a non-async <script> tag inlined within HTML. "
+             "You should avoid this in Cobalt because if the app is "
+             "suspended while loading the script, Cobalt's current "
+             "logic is to abort the load and without any retries or "
+             "signals. To avoid difficult to diagnose suspend/resume "
+             "bugs, it is recommended to use JavaScript to create a "
+             "script element and load it async. The <script> reference "
+             "appears at: \""
+          << inline_script_location_ << "\" and its src is \"" << src() << "\"";
+    }
+
     load_option_ = 2;
   } else if (HasAttribute("src") && !async()) {
     // Option 4
@@ -400,8 +418,7 @@
       const std::string& text = content.value_or(base::EmptyString());
       if (bypass_csp || text.empty() ||
           csp_delegate->AllowInline(CspDelegate::kScript,
-                                    inline_script_location_,
-                                    text)) {
+                                    inline_script_location_, text)) {
         fetched_last_url_origin_ = document_->location()->GetOriginAsObject();
         ExecuteInternal();
       } else {
@@ -428,7 +445,8 @@
   if (!error) return;
 
   TRACE_EVENT0("cobalt::dom", "HTMLScriptElement::OnSyncLoadingComplete()");
-  LOG(ERROR) << *error;
+  LOG(ERROR) << "Error during synchronous script load referenced from \""
+             << inline_script_location_ << "\" : " << *error;
 }
 
 // Algorithm for OnContentProduced:
diff --git a/src/cobalt/dom/mutation_observer.cc b/src/cobalt/dom/mutation_observer.cc
index 0472f70..83f819d 100644
--- a/src/cobalt/dom/mutation_observer.cc
+++ b/src/cobalt/dom/mutation_observer.cc
@@ -85,7 +85,7 @@
 MutationObserver::MutationObserver(
     const NativeMutationCallback& native_callback,
     MutationObserverTaskManager* task_manager,
-    base::DebuggerHooks* debugger_hooks)
+    const base::DebuggerHooks& debugger_hooks)
     : task_manager_(task_manager), debugger_hooks_(debugger_hooks) {
   callback_.reset(new NativeCallback(native_callback));
   task_manager_->OnMutationObserverCreated(this);
@@ -142,7 +142,7 @@
   record_queue_.push_back(record);
   task_manager_->QueueMutationObserverMicrotask();
   MutationRecord* task = record.get();
-  debugger_hooks_->AsyncTaskScheduled(
+  debugger_hooks_.AsyncTaskScheduled(
       task, record->type().c_str(),
       base::DebuggerHooks::AsyncTaskFrequency::kOneshot);
 }
@@ -180,7 +180,7 @@
 void MutationObserver::CancelDebuggerAsyncTasks() {
   for (auto record : record_queue_) {
     MutationRecord* task = record.get();
-    debugger_hooks_->AsyncTaskCanceled(task);
+    debugger_hooks_.AsyncTaskCanceled(task);
   }
 }
 
diff --git a/src/cobalt/dom/mutation_observer.h b/src/cobalt/dom/mutation_observer.h
index c7f50ef..9b983e4 100644
--- a/src/cobalt/dom/mutation_observer.h
+++ b/src/cobalt/dom/mutation_observer.h
@@ -60,7 +60,7 @@
   // code.
   MutationObserver(const NativeMutationCallback& native_callback,
                    MutationObserverTaskManager* task_manager,
-                   base::DebuggerHooks* debugger_hooks);
+                   const base::DebuggerHooks& debugger_hooks);
 
   // Web Api: MutationObserver
   MutationObserver(script::EnvironmentSettings* settings,
@@ -112,7 +112,7 @@
   WeakNodeVector observed_nodes_;
   MutationRecordSequence record_queue_;
   MutationObserverTaskManager* task_manager_;
-  base::DebuggerHooks* debugger_hooks_;
+  const base::DebuggerHooks& debugger_hooks_;
 };
 }  // namespace dom
 }  // namespace cobalt
diff --git a/src/cobalt/dom/mutation_observer_test.cc b/src/cobalt/dom/mutation_observer_test.cc
index edae725..23991d4 100644
--- a/src/cobalt/dom/mutation_observer_test.cc
+++ b/src/cobalt/dom/mutation_observer_test.cc
@@ -72,7 +72,7 @@
     return new MutationObserver(
         base::Bind(&MutationCallbackMock::NativeMutationCallback,
                    base::Unretained(&callback_mock_)),
-        &task_manager_, &debugger_hooks_);
+        &task_manager_, debugger_hooks_);
   }
 
   ChildListMutationArguments CreateChildListMutationArguments() {
diff --git a/src/cobalt/dom/navigator.cc b/src/cobalt/dom/navigator.cc
index f9507de..c57b886 100644
--- a/src/cobalt/dom/navigator.cc
+++ b/src/cobalt/dom/navigator.cc
@@ -15,6 +15,7 @@
 #include "cobalt/dom/navigator.h"
 
 #include <memory>
+#include <vector>
 
 #include "base/optional.h"
 #include "cobalt/dom/captions/system_caption_settings.h"
@@ -24,6 +25,7 @@
 #include "cobalt/media_capture/media_devices.h"
 #include "cobalt/media_session/media_session_client.h"
 #include "cobalt/script/script_value_factory.h"
+#include "starboard/configuration_constants.h"
 #include "starboard/file.h"
 #include "starboard/media.h"
 
@@ -54,15 +56,16 @@
 const std::string& Navigator::language() const { return language_; }
 
 base::Optional<std::string> GetFilenameForLicenses() {
-  char buffer[SB_FILE_MAX_PATH + 1] = {0};
-  bool got_path = SbSystemGetPath(kSbSystemPathContentDirectory, buffer,
-                                  SB_ARRAY_SIZE_INT(buffer));
+  const size_t kBufferSize = kSbFileMaxPath + 1;
+  std::vector<char> buffer(kBufferSize, 0);
+  bool got_path = SbSystemGetPath(kSbSystemPathContentDirectory, buffer.data(),
+                                  static_cast<int>(kBufferSize));
   if (!got_path) {
     SB_DLOG(ERROR) << "Cannot get content path for licenses files.";
     return base::Optional<std::string>();
   }
 
-  return std::string(buffer).append(kLicensesRelativePath);
+  return std::string(buffer.data()).append(kLicensesRelativePath);
 }
 
 const std::string Navigator::licenses() const {
diff --git a/src/cobalt/dom/node.cc b/src/cobalt/dom/node.cc
index e411663..a86bc8f 100644
--- a/src/cobalt/dom/node.cc
+++ b/src/cobalt/dom/node.cc
@@ -19,6 +19,7 @@
 
 #include "base/lazy_instance.h"
 #include "base/trace_event/trace_event.h"
+#include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/cssom/css_rule_visitor.h"
 #include "cobalt/cssom/css_style_rule.h"
 #include "cobalt/dom/cdata_section.h"
@@ -26,6 +27,7 @@
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/document_type.h"
 #include "cobalt/dom/dom_exception.h"
+#include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/element.h"
 #include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/html_collection.h"
@@ -436,6 +438,12 @@
 
 Text* Node::AsText() { return NULL; }
 
+const base::DebuggerHooks& Node::debugger_hooks() const {
+  return base::polymorphic_downcast<DOMSettings*>(
+             node_document()->html_element_context()->environment_settings())
+      ->debugger_hooks();
+}
+
 void Node::TraceMembers(script::Tracer* tracer) {
   EventTarget::TraceMembers(tracer);
 
diff --git a/src/cobalt/dom/node.h b/src/cobalt/dom/node.h
index 921a31a..e1d7614 100644
--- a/src/cobalt/dom/node.h
+++ b/src/cobalt/dom/node.h
@@ -22,6 +22,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
+#include "cobalt/base/debugger_hooks.h"
 #include "cobalt/base/token.h"
 #include "cobalt/dom/event_target.h"
 #include "cobalt/dom/mutation_observer.h"
@@ -215,6 +216,9 @@
   // The returned node generation will be never equal to kInvalidNodeGeneration.
   uint32_t node_generation() const { return node_generation_; }
 
+  // Returns the DebuggerHooks for the WebModule associated with this Node.
+  const base::DebuggerHooks& debugger_hooks() const;
+
   // Children classes implement this method to support type-safe visiting via
   // double dispatch.
   virtual void Accept(NodeVisitor* visitor) = 0;
diff --git a/src/cobalt/dom/on_screen_keyboard_test.cc b/src/cobalt/dom/on_screen_keyboard_test.cc
index bd2b4ac..36883b2 100644
--- a/src/cobalt/dom/on_screen_keyboard_test.cc
+++ b/src/cobalt/dom/on_screen_keyboard_test.cc
@@ -296,7 +296,22 @@
 
 #if SB_API_VERSION >= SB_ON_SCREEN_KEYBOARD_REQUIRED_VERSION || \
     SB_HAS(ON_SCREEN_KEYBOARD)
+
+bool SkipLocale() {
+#if SB_API_VERSION >= SB_ON_SCREEN_KEYBOARD_REQUIRED_VERSION
+  bool skipTests = !SbWindowOnScreenKeyboardIsSupported();
+  if (skipTests) {
+    SB_LOG(INFO) << "On screen keyboard not supported. Test skipped.";
+  }
+  return skipTests;
+#else
+  return false;
+#endif
+}
+
 TEST_F(OnScreenKeyboardTest, ObjectExists) {
+  if (SkipLocale()) return;
+
   std::string result;
   EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard;", &result));
 
@@ -333,6 +348,8 @@
 }
 
 TEST_F(OnScreenKeyboardTest, ShowAndHide) {
+  if (SkipLocale()) return;
+
   // Not shown.
   std::string result;
   EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.shown;", &result));
@@ -363,6 +380,8 @@
 }
 
 TEST_F(OnScreenKeyboardTest, ShowAndHideMultipleTimes) {
+  if (SkipLocale()) return;
+
   std::string result;
   {
     InSequence seq;
@@ -396,6 +415,8 @@
 }
 
 TEST_F(OnScreenKeyboardTest, Data) {
+  if (SkipLocale()) return;
+
   std::string result = "(empty)";
   EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.data;", &result));
   EXPECT_EQ("", result);
@@ -409,6 +430,8 @@
 }
 
 TEST_F(OnScreenKeyboardTest, FocusAndBlur) {
+  if (SkipLocale()) return;
+
   std::string result;
 
   {
@@ -426,6 +449,8 @@
       bindings::testing::IsAcceptablePrototypeString("Promise", result));
 }
 TEST_F(OnScreenKeyboardTest, FocusAndBlurMultipleTimes) {
+  if (SkipLocale()) return;
+
   std::string result;
   {
     InSequence seq;
@@ -454,6 +479,8 @@
 }
 
 TEST_F(OnScreenKeyboardTest, ShowEventAttribute) {
+  if (SkipLocale()) return;
+
   EXPECT_CALL(*(on_screen_keyboard_bridge()),
               ShowMock(window()->on_screen_keyboard()->data()))
       .Times(3);
@@ -478,6 +505,8 @@
 }
 
 TEST_F(OnScreenKeyboardTest, ShowEventListeners) {
+  if (SkipLocale()) return;
+
   std::string result;
   EXPECT_CALL(*(on_screen_keyboard_bridge()),
               ShowMock(window()->on_screen_keyboard()->data()));
@@ -502,6 +531,8 @@
 }
 
 TEST_F(OnScreenKeyboardTest, HideEventAttribute) {
+  if (SkipLocale()) return;
+
   EXPECT_CALL(*(on_screen_keyboard_bridge()), HideMock()).Times(3);
   const char let_script[] = R"(
     let promise;
@@ -524,6 +555,8 @@
 }
 
 TEST_F(OnScreenKeyboardTest, HideEventListeners) {
+  if (SkipLocale()) return;
+
   std::string result;
   EXPECT_CALL(*(on_screen_keyboard_bridge()), HideMock());
   const char script[] = R"(
@@ -547,6 +580,8 @@
 }
 
 TEST_F(OnScreenKeyboardTest, FocusEventAttribute) {
+  if (SkipLocale()) return;
+
   EXPECT_CALL(*(on_screen_keyboard_bridge()), FocusMock()).Times(3);
   const char let_script[] = R"(
     let promise;
@@ -569,6 +604,8 @@
 }
 
 TEST_F(OnScreenKeyboardTest, FocusEventListeners) {
+  if (SkipLocale()) return;
+
   std::string result;
   EXPECT_CALL(*(on_screen_keyboard_bridge()), FocusMock());
   const char script[] = R"(
@@ -592,6 +629,8 @@
 }
 
 TEST_F(OnScreenKeyboardTest, BlurEventAttribute) {
+  if (SkipLocale()) return;
+
   EXPECT_CALL(*(on_screen_keyboard_bridge()), BlurMock()).Times(3);
   const char let_script[] = R"(
     let promise;
@@ -614,6 +653,8 @@
 }
 
 TEST_F(OnScreenKeyboardTest, BlurEventListeners) {
+  if (SkipLocale()) return;
+
   std::string result;
   EXPECT_CALL(*(on_screen_keyboard_bridge()), BlurMock());
   const char script[] = R"(
@@ -637,6 +678,8 @@
 }
 
 TEST_F(OnScreenKeyboardTest, BoundingRect) {
+  if (SkipLocale()) return;
+
   std::string result;
   EXPECT_CALL(*(on_screen_keyboard_bridge()), BoundingRectMock())
       .WillOnce(::testing::Return(nullptr));
@@ -645,6 +688,8 @@
 }
 
 TEST_F(OnScreenKeyboardTest, KeepFocus) {
+  if (SkipLocale()) return;
+
   std::string result;
   {
     InSequence seq;
diff --git a/src/cobalt/dom/testing/stub_environment_settings.h b/src/cobalt/dom/testing/stub_environment_settings.h
index 81e11f7..03c7266 100644
--- a/src/cobalt/dom/testing/stub_environment_settings.h
+++ b/src/cobalt/dom/testing/stub_environment_settings.h
@@ -26,7 +26,7 @@
  public:
   explicit StubEnvironmentSettings(const Options& options = Options())
       : DOMSettings(0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
-                    nullptr, &null_debugger_hooks_, nullptr, options) {}
+                    nullptr, null_debugger_hooks_, nullptr, options) {}
   ~StubEnvironmentSettings() override {}
 
  private:
diff --git a/src/cobalt/dom/testing/stub_window.h b/src/cobalt/dom/testing/stub_window.h
index c059eab..d38a786 100644
--- a/src/cobalt/dom/testing/stub_window.h
+++ b/src/cobalt/dom/testing/stub_window.h
@@ -66,7 +66,7 @@
             ? std::move(environment_settings)
             : std::unique_ptr<script::EnvironmentSettings>(new DOMSettings(
                   0, NULL, NULL, NULL, NULL, NULL, engine_.get(),
-                  global_environment(), &null_debugger_hooks_, NULL));
+                  global_environment(), null_debugger_hooks_, NULL));
     window_ = new dom::Window(
         environment_settings_.get(), cssom::ViewportSize(1920, 1080), 1.f,
         base::kApplicationStateStarted, css_parser_.get(), dom_parser_.get(),
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc
index 62edced..524880f 100644
--- a/src/cobalt/dom/window.cc
+++ b/src/cobalt/dom/window.cc
@@ -130,6 +130,7 @@
     const ScreenshotManager::ProvideScreenshotFunctionCallback&
         screenshot_function_callback,
     base::WaitableEvent* synchronous_loader_interrupt,
+    bool enable_inline_script_warnings,
     const scoped_refptr<ui_navigation::NavItem>& ui_nav_root,
     int csp_insecure_allowed_token, int dom_max_element_depth,
     float video_playback_rate_multiplier, ClockType clock_type,
@@ -154,7 +155,7 @@
           reduced_image_cache_capacity_manager, remote_typeface_cache,
           mesh_cache, dom_stat_tracker, font_language_script,
           initial_application_state, synchronous_loader_interrupt,
-          video_playback_rate_multiplier)),
+          enable_inline_script_warnings, video_playback_rate_multiplier)),
       performance_(new Performance(MakePerformanceClock(clock_type))),
       ALLOW_THIS_IN_INITIALIZER_LIST(document_(new Document(
           html_element_context_.get(),
diff --git a/src/cobalt/dom/window.h b/src/cobalt/dom/window.h
index 27fa7a9..27bd732 100644
--- a/src/cobalt/dom/window.h
+++ b/src/cobalt/dom/window.h
@@ -173,6 +173,7 @@
       const ScreenshotManager::ProvideScreenshotFunctionCallback&
           screenshot_function_callback,
       base::WaitableEvent* synchronous_loader_interrupt,
+      bool enable_inline_script_warnings = false,
       const scoped_refptr<ui_navigation::NavItem>& ui_nav_root = nullptr,
       int csp_insecure_allowed_token = 0, int dom_max_element_depth = 0,
       float video_playback_rate_multiplier = 1.f,
diff --git a/src/cobalt/dom/window_timers.cc b/src/cobalt/dom/window_timers.cc
index be3bc87..283208c 100644
--- a/src/cobalt/dom/window_timers.cc
+++ b/src/cobalt/dom/window_timers.cc
@@ -43,7 +43,7 @@
                             base::Unretained(this), handle));
     timers_[handle] = new TimerInfo(
         owner_, std::unique_ptr<base::internal::TimerBase>(timer), handler);
-    debugger_hooks_->AsyncTaskScheduled(
+    debugger_hooks_.AsyncTaskScheduled(
         timers_[handle], "SetTimeout",
         base::DebuggerHooks::AsyncTaskFrequency::kOneshot);
   } else {
@@ -71,7 +71,7 @@
                             base::Unretained(this), handle));
     timers_[handle] = new TimerInfo(
         owner_, std::unique_ptr<base::internal::TimerBase>(timer), handler);
-    debugger_hooks_->AsyncTaskScheduled(
+    debugger_hooks_.AsyncTaskScheduled(
         timers_[handle], "SetInterval",
         base::DebuggerHooks::AsyncTaskFrequency::kRecurring);
   } else {
@@ -84,14 +84,14 @@
 void WindowTimers::ClearInterval(int handle) {
   Timers::iterator timer = timers_.find(handle);
   if (timer != timers_.end()) {
-    debugger_hooks_->AsyncTaskCanceled(timer->second);
+    debugger_hooks_.AsyncTaskCanceled(timer->second);
     timers_.erase(timer);
   }
 }
 
 void WindowTimers::ClearAllIntervalsAndTimeouts() {
   for (auto& timer_entry : timers_) {
-    debugger_hooks_->AsyncTaskCanceled(timer_entry.second);
+    debugger_hooks_.AsyncTaskCanceled(timer_entry.second);
   }
   timers_.clear();
 }
@@ -100,7 +100,7 @@
   callbacks_active_ = false;
   // Immediately cancel any pending timers.
   for (auto& timer_entry : timers_) {
-    debugger_hooks_->AsyncTaskCanceled(timer_entry.second);
+    debugger_hooks_.AsyncTaskCanceled(timer_entry.second);
     timer_entry.second = nullptr;
   }
 }
@@ -149,7 +149,7 @@
   // If the timer is not deleted and is not running, it means it is an oneshot
   // timer and has just fired the shot, and it should be deleted now.
   if (timer != timers_.end() && !timer->second->timer()->IsRunning()) {
-    debugger_hooks_->AsyncTaskCanceled(timer->second);
+    debugger_hooks_.AsyncTaskCanceled(timer->second);
     timers_.erase(timer);
   }
 
diff --git a/src/cobalt/dom/window_timers.h b/src/cobalt/dom/window_timers.h
index f17f0d8..73bec1f 100644
--- a/src/cobalt/dom/window_timers.h
+++ b/src/cobalt/dom/window_timers.h
@@ -33,12 +33,10 @@
   typedef script::CallbackFunction<void()> TimerCallback;
   typedef script::ScriptValue<TimerCallback> TimerCallbackArg;
   explicit WindowTimers(script::Wrappable* const owner,
-                        base::DebuggerHooks* debugger_hooks)
+                        const base::DebuggerHooks& debugger_hooks)
       : current_timer_index_(0),
         owner_(owner),
-        debugger_hooks_(debugger_hooks) {
-    DCHECK(debugger_hooks_);
-  }
+        debugger_hooks_(debugger_hooks) {}
   ~WindowTimers() {}
 
   int SetTimeout(const TimerCallbackArg& handler, int timeout);
@@ -88,7 +86,7 @@
   Timers timers_;
   int current_timer_index_;
   script::Wrappable* const owner_;
-  base::DebuggerHooks* debugger_hooks_;
+  const base::DebuggerHooks& debugger_hooks_;
 
   // Set to false when we're about to shutdown, to ensure that no new JavaScript
   // is fired as we are waiting for it to drain.
diff --git a/src/cobalt/extension/installation_manager.h b/src/cobalt/extension/installation_manager.h
index 4a99cda..6f1e13b 100644
--- a/src/cobalt/extension/installation_manager.h
+++ b/src/cobalt/extension/installation_manager.h
@@ -19,6 +19,7 @@
 
 #include "starboard/configuration.h"
 
+#define IM_EXT_INVALID_INDEX -1
 #define IM_EXT_ERROR -1
 #define IM_EXT_SUCCESS 0
 
diff --git a/src/cobalt/input/input_device_manager_fuzzer.cc b/src/cobalt/input/input_device_manager_fuzzer.cc
index d699237..02dcf1f 100644
--- a/src/cobalt/input/input_device_manager_fuzzer.cc
+++ b/src/cobalt/input/input_device_manager_fuzzer.cc
@@ -91,7 +91,7 @@
     KeyboardEventCallback keyboard_event_callback)
     : keyboard_event_callback_(keyboard_event_callback),
       next_key_index_(0),
-      thread_("InputDeviceManagerFuzzer") {
+      thread_("InputDevMgrFuzz") {
   key_infos_.push_back(KeyInfo(kKeyCodes, arraysize(kKeyCodes),
                                base::TimeDelta::FromMilliseconds(400)));
   // Modify the key_infos_ to use different input patterns.  For example, the
diff --git a/src/cobalt/layout/block_container_box.cc b/src/cobalt/layout/block_container_box.cc
index b5f1fbf..a5fe9e9 100644
--- a/src/cobalt/layout/block_container_box.cc
+++ b/src/cobalt/layout/block_container_box.cc
@@ -36,6 +36,7 @@
 // Updates used values of "width" and "margin" properties based on
 // https://www.w3.org/TR/CSS21/visudet.html#Computing_widths_and_margins.
 void BlockContainerBox::UpdateContentWidthAndMargins(
+    BaseDirection containing_block_direction,
     LayoutUnit containing_block_width, bool shrink_to_fit_width_forced,
     bool width_depends_on_containing_block,
     const base::Optional<LayoutUnit>& maybe_left,
@@ -62,8 +63,8 @@
     switch (forced_level) {
       case kBlockLevel:
         UpdateWidthAssumingBlockLevelInFlowBox(
-            containing_block_width, maybe_nulled_width, maybe_margin_left,
-            maybe_margin_right);
+            containing_block_direction, containing_block_width,
+            maybe_nulled_width, maybe_margin_left, maybe_margin_right);
         break;
       case kInlineLevel:
         UpdateWidthAssumingInlineLevelInFlowBox(
@@ -85,6 +86,8 @@
     const base::Optional<LayoutUnit>& maybe_height) {
   LayoutParams child_layout_params;
   LayoutParams absolute_child_layout_params;
+  child_layout_params.containing_block_direction = base_direction_;
+  absolute_child_layout_params.containing_block_direction = base_direction_;
   if (AsAnonymousBlockBox()) {
     // Anonymous block boxes are ignored when resolving percentage values
     // that would refer to it: the closest non-anonymous ancestor box is used
@@ -184,7 +187,8 @@
     base::Optional<LayoutUnit> maybe_right = GetUsedRightIfNotAuto(
         computed_style(), layout_params.containing_block_size);
 
-    UpdateContentWidthAndMargins(layout_params.containing_block_size.width(),
+    UpdateContentWidthAndMargins(layout_params.containing_block_direction,
+                                 layout_params.containing_block_size.width(),
                                  layout_params.shrink_to_fit_width_forced,
                                  width_depends_on_containing_block, maybe_left,
                                  maybe_right, maybe_margin_left,
@@ -200,6 +204,7 @@
         &max_width_depends_on_containing_block);
     if (maybe_max_width && width() > maybe_max_width.value()) {
       UpdateContentWidthAndMargins(
+          layout_params.containing_block_direction,
           layout_params.containing_block_size.width(),
           layout_params.shrink_to_fit_width_forced,
           max_width_depends_on_containing_block, maybe_left, maybe_right,
@@ -216,6 +221,7 @@
         &min_width_depends_on_containing_block);
     if (maybe_min_width && (width() < maybe_min_width.value_or(LayoutUnit()))) {
       UpdateContentWidthAndMargins(
+          layout_params.containing_block_direction,
           layout_params.containing_block_size.width(),
           layout_params.shrink_to_fit_width_forced,
           min_width_depends_on_containing_block, maybe_left, maybe_right,
@@ -587,6 +593,7 @@
 //     + "padding-right" + "border-right-width" + "margin-right"
 //     = width of containing block
 void BlockContainerBox::UpdateWidthAssumingBlockLevelInFlowBox(
+    BaseDirection containing_block_direction,
     LayoutUnit containing_block_width,
     const base::Optional<LayoutUnit>& maybe_width,
     const base::Optional<LayoutUnit>& possibly_overconstrained_margin_left,
@@ -599,8 +606,8 @@
   if (maybe_width) {
     set_width(*maybe_width);
     UpdateHorizontalMarginsAssumingBlockLevelInFlowBox(
-        containing_block_width, GetBorderBoxWidth(), maybe_margin_left,
-        maybe_margin_right);
+        containing_block_direction, containing_block_width, GetBorderBoxWidth(),
+        maybe_margin_left, maybe_margin_right);
   } else {
     // If "width" is set to "auto", any other "auto" values become "0" and
     // "width" follows from the resulting equality.
@@ -638,6 +645,7 @@
     LayoutUnit containing_block_width,
     const base::Optional<LayoutUnit>& maybe_height) {
   LayoutParams child_layout_params;
+  child_layout_params.containing_block_direction = base_direction_;
   // The available width is the width of the containing block minus
   // the used values of "margin-left", "border-left-width", "padding-left",
   // "padding-right", "border-right-width", "margin-right".
diff --git a/src/cobalt/layout/block_container_box.h b/src/cobalt/layout/block_container_box.h
index 21ace30..3334991 100644
--- a/src/cobalt/layout/block_container_box.h
+++ b/src/cobalt/layout/block_container_box.h
@@ -100,6 +100,7 @@
 
  private:
   void UpdateContentWidthAndMargins(
+      BaseDirection containing_block_direction,
       LayoutUnit containing_block_width, bool shrink_to_fit_width_forced,
       bool width_depends_on_containing_block,
       const base::Optional<LayoutUnit>& maybe_left,
@@ -134,6 +135,7 @@
       const FormattingContext& formatting_context);
 
   void UpdateWidthAssumingBlockLevelInFlowBox(
+      BaseDirection containing_block_direction,
       LayoutUnit containing_block_width,
       const base::Optional<LayoutUnit>& maybe_width,
       const base::Optional<LayoutUnit>& maybe_margin_left,
diff --git a/src/cobalt/layout/block_level_replaced_box.cc b/src/cobalt/layout/block_level_replaced_box.cc
index b9bd115..59f4442 100644
--- a/src/cobalt/layout/block_level_replaced_box.cc
+++ b/src/cobalt/layout/block_level_replaced_box.cc
@@ -40,6 +40,7 @@
 Box::Level BlockLevelReplacedBox::GetLevel() const { return kBlockLevel; }
 
 void BlockLevelReplacedBox::UpdateHorizontalMargins(
+    BaseDirection containing_block_direction,
     LayoutUnit containing_block_width, LayoutUnit border_box_width,
     const base::Optional<LayoutUnit>& maybe_margin_left,
     const base::Optional<LayoutUnit>& maybe_margin_right) {
@@ -47,8 +48,8 @@
   // normal flow.
   //   https://www.w3.org/TR/CSS21/visudet.html#block-replaced-width
   UpdateHorizontalMarginsAssumingBlockLevelInFlowBox(
-      containing_block_width, border_box_width, maybe_margin_left,
-      maybe_margin_right);
+      containing_block_direction, containing_block_width,
+      border_box_width, maybe_margin_left, maybe_margin_right);
 }
 
 #ifdef COBALT_BOX_DUMP_ENABLED
diff --git a/src/cobalt/layout/block_level_replaced_box.h b/src/cobalt/layout/block_level_replaced_box.h
index a4cad34..144c3af 100644
--- a/src/cobalt/layout/block_level_replaced_box.h
+++ b/src/cobalt/layout/block_level_replaced_box.h
@@ -53,6 +53,7 @@
 
   // From |ReplacedBox|.
   void UpdateHorizontalMargins(
+      BaseDirection containing_block_direction,
       LayoutUnit containing_block_width, LayoutUnit border_box_width,
       const base::Optional<LayoutUnit>& maybe_margin_left,
       const base::Optional<LayoutUnit>& maybe_margin_right) override;
diff --git a/src/cobalt/layout/box.cc b/src/cobalt/layout/box.cc
index ac4ebed..f881901 100644
--- a/src/cobalt/layout/box.cc
+++ b/src/cobalt/layout/box.cc
@@ -1980,6 +1980,7 @@
 
 // Based on https://www.w3.org/TR/CSS21/visudet.html#blockwidth.
 void Box::UpdateHorizontalMarginsAssumingBlockLevelInFlowBox(
+    BaseDirection containing_block_direction,
     LayoutUnit containing_block_width, LayoutUnit border_box_width,
     const base::Optional<LayoutUnit>& possibly_overconstrained_margin_left,
     const base::Optional<LayoutUnit>& possibly_overconstrained_margin_right) {
@@ -2000,20 +2001,22 @@
     maybe_margin_right = maybe_margin_right.value_or(LayoutUnit());
   }
 
-  if (maybe_margin_left) {
-    // If all of the above have a computed value other than "auto", the values
-    // are said to be "over-constrained" and the specified value of
-    // "margin-right" is ignored and the value is calculated so as to make
-    // the equality true.
-    //
-    // If there is exactly one value specified as "auto", its used value
-    // follows from the equality.
+  // If all of the above have a computed value other than "auto", the values
+  // are said to be "over-constrained" and one of the used values will have to
+  // be different from its computed value. If the "direction" property of the
+  // containing block has the value "ltr", the specified value of "margin-right"
+  // is ignored and the value is calculated so as to make the equality true. If
+  // the value of "direction" is "rtl", this happens to "margin-left" instead.
+  //
+  // If there is exactly one value specified as "auto", its used value follows
+  // from the equality.
+  if (maybe_margin_left &&
+        (!maybe_margin_right ||
+         containing_block_direction == kLeftToRightBaseDirection)) {
     set_margin_left(*maybe_margin_left);
     set_margin_right(containing_block_width - *maybe_margin_left -
                      border_box_width);
   } else if (maybe_margin_right) {
-    // If there is exactly one value specified as "auto", its used value
-    // follows from the equality.
     set_margin_left(containing_block_width - border_box_width -
                     *maybe_margin_right);
     set_margin_right(*maybe_margin_right);
diff --git a/src/cobalt/layout/box.h b/src/cobalt/layout/box.h
index 8195c59..a058f3b 100644
--- a/src/cobalt/layout/box.h
+++ b/src/cobalt/layout/box.h
@@ -62,7 +62,8 @@
   LayoutParams()
       : shrink_to_fit_width_forced(false),
         freeze_width(false),
-        freeze_height(false) {}
+        freeze_height(false),
+        containing_block_direction(kLeftToRightBaseDirection) {}
 
   // Normally the used values of "width", "margin-left", and "margin-right" are
   // calculated by choosing the 1 out of 10 algorithms based on the computed
@@ -91,11 +92,17 @@
   //   https://www.w3.org/TR/CSS21/visuren.html#containing-block
   SizeLayoutUnit containing_block_size;
 
+  // Margin calculations can depend on the direction property of the containing
+  // block.
+  //   https://www.w3.org/TR/CSS21/visudet.html#blockwidth
+  BaseDirection containing_block_direction;
+
   bool operator==(const LayoutParams& rhs) const {
     return shrink_to_fit_width_forced == rhs.shrink_to_fit_width_forced &&
            freeze_width == rhs.freeze_width &&
            freeze_height == rhs.freeze_height &&
-           containing_block_size == rhs.containing_block_size;
+           containing_block_size == rhs.containing_block_size &&
+           containing_block_direction == rhs.containing_block_direction;
   }
 
   base::Optional<LayoutUnit> maybe_margin_top;
@@ -772,6 +779,7 @@
   // https://www.w3.org/TR/CSS21/visudet.html#blockwidth and
   // https://www.w3.org/TR/CSS21/visudet.html#block-replaced-width.
   void UpdateHorizontalMarginsAssumingBlockLevelInFlowBox(
+      BaseDirection containing_block_direction,
       LayoutUnit containing_block_width, LayoutUnit border_box_width,
       const base::Optional<LayoutUnit>& possibly_overconstrained_margin_left,
       const base::Optional<LayoutUnit>& possibly_overconstrained_margin_right);
diff --git a/src/cobalt/layout/box_generator.cc b/src/cobalt/layout/box_generator.cc
index b286b23..d632aa8 100644
--- a/src/cobalt/layout/box_generator.cc
+++ b/src/cobalt/layout/box_generator.cc
@@ -423,7 +423,7 @@
       : element_dir_(element_dir),
         css_computed_style_declaration_(css_computed_style_declaration),
         context_(context),
-        has_scoped_directional_embedding_(false),
+        has_scoped_directional_isolate_(false),
         paragraph_(paragraph),
         paragraph_scoped_(false) {}
   ~ContainerBoxGenerator();
@@ -440,10 +440,10 @@
       css_computed_style_declaration_;
   const BoxGenerator::Context* context_;
 
-  // If a directional embedding was added to the paragraph by this container box
+  // If a directional isolate was added to the paragraph by this container box
   // and needs to be popped in the destructor:
-  // http://unicode.org/reports/tr9/#Explicit_Directional_Embeddings
-  bool has_scoped_directional_embedding_;
+  // http://unicode.org/reports/tr9/#Explicit_Directional_Isolates
+  bool has_scoped_directional_isolate_;
 
   scoped_refptr<Paragraph>* paragraph_;
   scoped_refptr<Paragraph> prior_paragraph_;
@@ -453,14 +453,14 @@
 };
 
 ContainerBoxGenerator::~ContainerBoxGenerator() {
-  // If there's a scoped directional embedding, then it needs to popped from
+  // If there's a scoped directional isolate, then it needs to popped from
   // the paragraph so that this box does not impact the directionality of later
   // boxes in the paragraph.
-  // http://unicode.org/reports/tr9/#Terminating_Explicit_Directional_Embeddings_and_Overrides
-  if (has_scoped_directional_embedding_) {
+  // http://unicode.org/reports/tr9/#Terminating_Explicit_Directional_Isolates
+  if (has_scoped_directional_isolate_) {
     (*paragraph_)
         ->AppendCodePoint(
-            Paragraph::kPopDirectionalFormattingCharacterCodePoint);
+            Paragraph::kPopDirectionalIsolateCodePoint);
   }
 
   if (paragraph_scoped_) {
@@ -472,7 +472,7 @@
     if (prior_paragraph_->IsClosed()) {
       *paragraph_ = new Paragraph(
           prior_paragraph_->GetLocale(), prior_paragraph_->base_direction(),
-          prior_paragraph_->GetDirectionalEmbeddingStack(),
+          prior_paragraph_->GetDirectionalFormattingStack(),
           context_->line_break_iterator, context_->character_break_iterator);
     } else {
       *paragraph_ = prior_paragraph_;
@@ -530,17 +530,17 @@
     // in the formatting context.
     case cssom::KeywordValue::kInline:
       // If the creating HTMLElement had an explicit directionality, then append
-      // a directional embedding to the paragraph. This will be popped from the
+      // a directional isolate to the paragraph. This will be popped from the
       // paragraph, when the ContainerBoxGenerator goes out of scope.
       // https://dev.w3.org/html5/spec-preview/global-attributes.html#the-directionality
-      // http://unicode.org/reports/tr9/#Explicit_Directional_Embeddings
+      // http://unicode.org/reports/tr9/#Explicit_Directional_Isolates
       // http://unicode.org/reports/tr9/#Markup_And_Formatting
       if (element_dir_ == DirState::kDirLeftToRight) {
-        has_scoped_directional_embedding_ = true;
-        (*paragraph_)->AppendCodePoint(Paragraph::kLeftToRightEmbedCodePoint);
+        has_scoped_directional_isolate_ = true;
+        (*paragraph_)->AppendCodePoint(Paragraph::kLeftToRightIsolateCodePoint);
       } else if (element_dir_ == DirState::kDirRightToLeft) {
-        has_scoped_directional_embedding_ = true;
-        (*paragraph_)->AppendCodePoint(Paragraph::kRightToLeftEmbedCodePoint);
+        has_scoped_directional_isolate_ = true;
+        (*paragraph_)->AppendCodePoint(Paragraph::kRightToLeftIsolateCodePoint);
       }
 
       // If the paragraph has not started yet, then add a no-break space to it,
@@ -683,7 +683,7 @@
   } else if (element_dir_ == DirState::kDirRightToLeft) {
     base_direction = kRightToLeftBaseDirection;
   } else {
-    base_direction = prior_paragraph_->GetDirectionalEmbeddingStackDirection();
+    base_direction = prior_paragraph_->GetDirectionalFormattingStackDirection();
   }
 
   if (close_prior_paragraph == kCloseParagraph) {
@@ -691,7 +691,7 @@
   }
 
   *paragraph_ = new Paragraph(prior_paragraph_->GetLocale(), base_direction,
-                              Paragraph::DirectionalEmbeddingStack(),
+                              Paragraph::DirectionalFormattingStack(),
                               context_->line_break_iterator,
                               context_->character_break_iterator);
 }
@@ -969,7 +969,7 @@
       html_element->css_computed_style_declaration());
 
   ContainerBoxGenerator container_box_generator(
-      html_element->dir_state(),
+      html_element->GetUsedDirState(),
       html_element == context_->ignore_background_element
           ? StripBackground(element_style)
           : element_style,
diff --git a/src/cobalt/layout/container_box.cc b/src/cobalt/layout/container_box.cc
index acdddff..18122e9 100644
--- a/src/cobalt/layout/container_box.cc
+++ b/src/cobalt/layout/container_box.cc
@@ -404,10 +404,12 @@
     // property of the containing block is 'ltr', the value of 'left' wins and
     // 'right' becomes -'left'. If 'direction' of the containing block is 'rtl',
     // 'right' wins and 'left' is ignored.
-
-    // TODO: Take into account the value of the 'direction' property, which
-    //       doesn't exist at the time of this writing.
-    offset.set_x(*maybe_left);
+    if (child_layout_params.containing_block_direction ==
+        kLeftToRightBaseDirection) {
+      offset.set_x(*maybe_left);
+    } else {
+      offset.set_x(-*maybe_right);
+    }
   }
 
   // The 'top' and 'bottom' properties move relatively positioned element(s) up
diff --git a/src/cobalt/layout/flex_container_box.cc b/src/cobalt/layout/flex_container_box.cc
index 8bb4127..bd58db9 100644
--- a/src/cobalt/layout/flex_container_box.cc
+++ b/src/cobalt/layout/flex_container_box.cc
@@ -252,6 +252,7 @@
   if (main_direction_is_horizontal) {
     if (!layout_params.freeze_width) {
       UpdateContentWidthAndMargins(
+          layout_params.containing_block_direction,
           layout_params.containing_block_size.width(),
           layout_params.shrink_to_fit_width_forced,
           width_depends_on_containing_block, maybe_left, maybe_right,
diff --git a/src/cobalt/layout/initial_containing_block.cc b/src/cobalt/layout/initial_containing_block.cc
index b7e3072..b1e6300 100644
--- a/src/cobalt/layout/initial_containing_block.cc
+++ b/src/cobalt/layout/initial_containing_block.cc
@@ -103,8 +103,14 @@
   initial_style_state->SetData(initial_containing_block_style);
   initial_style_state->set_animations(new web_animations::AnimationSet());
 
+  BaseDirection base_direction = kLeftToRightBaseDirection;
+  auto html = document->html();
+  if (html && html->GetUsedDirState() == dom::HTMLElement::kDirRightToLeft) {
+    base_direction = kRightToLeftBaseDirection;
+  }
+
   results.box = base::WrapRefCounted(new BlockLevelBlockContainerBox(
-      initial_style_state, kLeftToRightBaseDirection, used_style_provider,
+      initial_style_state, base_direction, used_style_provider,
       layout_stat_tracker));
 
   return results;
diff --git a/src/cobalt/layout/inline_level_replaced_box.cc b/src/cobalt/layout/inline_level_replaced_box.cc
index 9b27d0c..e5f3b7f 100644
--- a/src/cobalt/layout/inline_level_replaced_box.cc
+++ b/src/cobalt/layout/inline_level_replaced_box.cc
@@ -64,9 +64,11 @@
 }
 
 void InlineLevelReplacedBox::UpdateHorizontalMargins(
+    BaseDirection containing_block_direction,
     LayoutUnit containing_block_width, LayoutUnit border_box_width,
     const base::Optional<LayoutUnit>& maybe_margin_left,
     const base::Optional<LayoutUnit>& maybe_margin_right) {
+  SB_UNREFERENCED_PARAMETER(containing_block_direction);
   SB_UNREFERENCED_PARAMETER(containing_block_width);
   SB_UNREFERENCED_PARAMETER(border_box_width);
 
diff --git a/src/cobalt/layout/inline_level_replaced_box.h b/src/cobalt/layout/inline_level_replaced_box.h
index 66f66eb..b50887c 100644
--- a/src/cobalt/layout/inline_level_replaced_box.h
+++ b/src/cobalt/layout/inline_level_replaced_box.h
@@ -60,6 +60,7 @@
 
   // From |ReplacedBox|.
   void UpdateHorizontalMargins(
+      BaseDirection containing_block_direction,
       LayoutUnit containing_block_width, LayoutUnit border_box_width,
       const base::Optional<LayoutUnit>& maybe_margin_left,
       const base::Optional<LayoutUnit>& maybe_margin_right) override;
diff --git a/src/cobalt/layout/layout.cc b/src/cobalt/layout/layout.cc
index b298940..4dcd17d 100644
--- a/src/cobalt/layout/layout.cc
+++ b/src/cobalt/layout/layout.cc
@@ -107,7 +107,7 @@
 
     ScopedParagraph scoped_paragraph(
         new Paragraph(locale, (*initial_containing_block)->base_direction(),
-                      Paragraph::DirectionalEmbeddingStack(),
+                      Paragraph::DirectionalFormattingStack(),
                       line_break_iterator, character_break_iterator));
     BoxGenerator::Context context(
         used_style_provider, layout_stat_tracker, line_break_iterator,
diff --git a/src/cobalt/layout/paragraph.cc b/src/cobalt/layout/paragraph.cc
index 8ee2b56..55ccd1e 100644
--- a/src/cobalt/layout/paragraph.cc
+++ b/src/cobalt/layout/paragraph.cc
@@ -32,12 +32,12 @@
 
 Paragraph::Paragraph(
     const icu::Locale& locale, BaseDirection base_direction,
-    const DirectionalEmbeddingStack& directional_embedding_stack,
+    const DirectionalFormattingStack& directional_formatting_stack,
     icu::BreakIterator* line_break_iterator,
     icu::BreakIterator* character_break_iterator)
     : locale_(locale),
       base_direction_(base_direction),
-      directional_embedding_stack_(directional_embedding_stack),
+      directional_formatting_stack_(directional_formatting_stack),
       line_break_iterator_(line_break_iterator),
       character_break_iterator_(character_break_iterator),
       is_closed_(false),
@@ -48,15 +48,15 @@
   level_runs_.push_back(
       BidiLevelRun(0, ConvertBaseDirectionToBidiLevel(base_direction)));
 
-  // Walk through the passed in directional embedding stack and add each
-  // embedding to the text. This allows a paragraph to continue the directional
+  // Walk through the passed in directional formatting stack and add each
+  // formatting to the text. This allows a paragraph to continue the directional
   // state of a prior paragraph.
   DCHECK(unicode_text_.isEmpty());
-  for (size_t i = 0; i < directional_embedding_stack_.size(); ++i) {
-    if (directional_embedding_stack_[i] == kRightToLeftDirectionalEmbedding) {
-      unicode_text_ += base::unicode::kRightToLeftEmbeddingCharacter;
+  for (size_t i = 0; i < directional_formatting_stack_.size(); ++i) {
+    if (directional_formatting_stack_[i] == kRightToLeftDirectionalIsolate) {
+      unicode_text_ += base::unicode::kRightToLeftIsolateCharacter;
     } else {
-      unicode_text_ += base::unicode::kLeftToRightEmbeddingCharacter;
+      unicode_text_ += base::unicode::kLeftToRightIsolateCharacter;
     }
   }
 }
@@ -83,22 +83,19 @@
 }
 
 int32 Paragraph::AppendCodePoint(CodePoint code_point) {
-  // TODO: Switch from appending directional embedding characters to
-  // using directional isolate characters as soon as we upgrade to the latest
-  // version of ICU. Our current version doesn't support them.
   int32 start_position = GetTextEndPosition();
   DCHECK(!is_closed_);
   if (!is_closed_) {
     switch (code_point) {
-      case kLeftToRightEmbedCodePoint:
-        // If this is a directional embedding that is being added, then add it
-        // to the directional embedding stack. This guarantees that a
-        // corresponding pop directional formatting will later be added to the
+      case kLeftToRightIsolateCodePoint:
+        // If this is a directional isolate that is being added, then add it
+        // to the directional formatting stack. This guarantees that a
+        // corresponding pop directional isolate will later be added to the
         // text and allows later paragraphs to copy the directional state.
-        // http://unicode.org/reports/tr9/#Explicit_Directional_Embeddings
-        directional_embedding_stack_.push_back(
-            kLeftToRightDirectionalEmbedding);
-        unicode_text_ += base::unicode::kLeftToRightEmbeddingCharacter;
+        // http://unicode.org/reports/tr9/#Explicit_Directional_Isolates
+        directional_formatting_stack_.push_back(
+            kLeftToRightDirectionalIsolate);
+        unicode_text_ += base::unicode::kLeftToRightIsolateCharacter;
         break;
       case kLineFeedCodePoint:
         unicode_text_ += base::unicode::kNewLineCharacter;
@@ -109,19 +106,19 @@
       case kObjectReplacementCharacterCodePoint:
         unicode_text_ += base::unicode::kObjectReplacementCharacter;
         break;
-      case kPopDirectionalFormattingCharacterCodePoint:
-        directional_embedding_stack_.pop_back();
-        unicode_text_ += base::unicode::kPopDirectionalFormattingCharacter;
+      case kPopDirectionalIsolateCodePoint:
+        directional_formatting_stack_.pop_back();
+        unicode_text_ += base::unicode::kPopDirectionalIsolateCharacter;
         break;
-      case kRightToLeftEmbedCodePoint:
-        // If this is a directional embedding that is being added, then add it
-        // to the directional embedding stack. This guarantees that a
-        // corresponding pop directional formatting will later be added to the
-        // text and allows later paragraphs to to copy the directional state.
-        // http://unicode.org/reports/tr9/#Explicit_Directional_Embeddings
-        directional_embedding_stack_.push_back(
-            kRightToLeftDirectionalEmbedding);
-        unicode_text_ += base::unicode::kRightToLeftEmbeddingCharacter;
+      case kRightToLeftIsolateCodePoint:
+        // If this is a directional isolate that is being added, then add it
+        // to the directional formatting stack. This guarantees that a
+        // corresponding pop directional isolate will later be added to the
+        // text and allows later paragraphs to copy the directional state.
+        // http://unicode.org/reports/tr9/#Explicit_Directional_Isolates
+        directional_formatting_stack_.push_back(
+            kRightToLeftDirectionalIsolate);
+        unicode_text_ += base::unicode::kRightToLeftIsolateCharacter;
         break;
     }
   }
@@ -249,11 +246,11 @@
 
 const icu::Locale& Paragraph::GetLocale() const { return locale_; }
 
-BaseDirection Paragraph::GetDirectionalEmbeddingStackDirection() const {
-  size_t stack_size = directional_embedding_stack_.size();
+BaseDirection Paragraph::GetDirectionalFormattingStackDirection() const {
+  size_t stack_size = directional_formatting_stack_.size();
   if (stack_size > 0) {
-    if (directional_embedding_stack_[stack_size - 1] ==
-        kRightToLeftDirectionalEmbedding) {
+    if (directional_formatting_stack_[stack_size - 1] ==
+        kRightToLeftDirectionalIsolate) {
       return kRightToLeftBaseDirection;
     } else {
       return kLeftToRightBaseDirection;
@@ -291,21 +288,21 @@
 
 int32 Paragraph::GetTextEndPosition() const { return unicode_text_.length(); }
 
-const Paragraph::DirectionalEmbeddingStack&
-Paragraph::GetDirectionalEmbeddingStack() const {
-  return directional_embedding_stack_;
+const Paragraph::DirectionalFormattingStack&
+Paragraph::GetDirectionalFormattingStack() const {
+  return directional_formatting_stack_;
 }
 
 void Paragraph::Close() {
   DCHECK(!is_closed_);
   if (!is_closed_) {
-    // Terminate all of the explicit directional embeddings that were previously
+    // Terminate all of the explicit directional isolates that were previously
     // added. However, do not clear the stack. A subsequent paragraph may need
     // to copy it.
-    // http://unicode.org/reports/tr9/#Terminating_Explicit_Directional_Embeddings_and_Overrides
-    for (size_t count = directional_embedding_stack_.size(); count > 0;
+    // http://unicode.org/reports/tr9/#Terminating_Explicit_Directional_Isolates
+    for (size_t count = directional_formatting_stack_.size(); count > 0;
          --count) {
-      unicode_text_ += base::unicode::kPopDirectionalFormattingCharacter;
+      unicode_text_ += base::unicode::kPopDirectionalIsolateCharacter;
     }
 
     is_closed_ = true;
diff --git a/src/cobalt/layout/paragraph.h b/src/cobalt/layout/paragraph.h
index 570b865..3feb5d8 100644
--- a/src/cobalt/layout/paragraph.h
+++ b/src/cobalt/layout/paragraph.h
@@ -63,18 +63,20 @@
   };
 
   enum CodePoint {
-    kLeftToRightEmbedCodePoint,
+    kLeftToRightIsolateCodePoint,
     kLineFeedCodePoint,
     kNoBreakSpaceCodePoint,
     kObjectReplacementCharacterCodePoint,
-    kPopDirectionalFormattingCharacterCodePoint,
-    kRightToLeftEmbedCodePoint,
+    kPopDirectionalIsolateCodePoint,
+    kRightToLeftIsolateCodePoint,
   };
 
-  // http://unicode.org/reports/tr9/#Explicit_Directional_Embeddings
-  enum DirectionalEmbedding {
-    kLeftToRightDirectionalEmbedding,
-    kRightToLeftDirectionalEmbedding,
+  // http://unicode.org/reports/tr9/#Directional_Formatting_Characters
+  enum DirectionalFormatting {
+    // Setting the "dir" attribute corresponds to using directional isolates.
+    //   http://unicode.org/reports/tr9/#Markup_And_Formatting
+    kLeftToRightDirectionalIsolate,
+    kRightToLeftDirectionalIsolate,
   };
 
   enum TextOrder {
@@ -87,10 +89,10 @@
     kUppercaseTextTransform,
   };
 
-  typedef std::vector<DirectionalEmbedding> DirectionalEmbeddingStack;
+  typedef std::vector<DirectionalFormatting> DirectionalFormattingStack;
 
   Paragraph(const icu::Locale& locale, BaseDirection base_direction,
-            const DirectionalEmbeddingStack& directional_embedding_stack,
+            const DirectionalFormattingStack& directional_formatting_stack,
             icu::BreakIterator* line_break_iterator,
             icu::BreakIterator* character_break_iterator);
 
@@ -128,9 +130,9 @@
   const icu::Locale& GetLocale() const;
   BaseDirection base_direction() const { return base_direction_; }
 
-  // Return the direction of the top directional embedding in the paragraph's
+  // Return the direction of the top directional formatting in the paragraph's
   // stack or the base direction if the stack is empty.
-  BaseDirection GetDirectionalEmbeddingStackDirection() const;
+  BaseDirection GetDirectionalFormattingStackDirection() const;
 
   int GetBidiLevel(int32 position) const;
   bool IsRTL(int32 position) const;
@@ -138,10 +140,10 @@
   bool GetNextRunPosition(int32 position, int32* next_run_position) const;
   int32 GetTextEndPosition() const;
 
-  // Return the full directional embedding stack for the paragraph. This allows
+  // Return the full directional formatting stack for the paragraph. This allows
   // newly created paragraphs to copy the directional state of a preceding
   // paragraph.
-  const DirectionalEmbeddingStack& GetDirectionalEmbeddingStack() const;
+  const DirectionalFormattingStack& GetDirectionalFormattingStack() const;
 
   // Close the paragraph so that it becomes immutable and generates the text
   // runs.
@@ -215,9 +217,9 @@
   // The base direction of the paragraph.
   const BaseDirection base_direction_;
 
-  // The stack tracking all active directional embeddings in the paragraph.
-  // http://unicode.org/reports/tr9/#Explicit_Directional_Embeddings
-  DirectionalEmbeddingStack directional_embedding_stack_;
+  // The stack tracking all active directional formatting in the paragraph.
+  // http://unicode.org/reports/tr9/#Directional_Formatting_Characters
+  DirectionalFormattingStack directional_formatting_stack_;
 
   // The line break iterator to use when splitting the text boxes derived from
   // this text paragraph across multiple lines.
diff --git a/src/cobalt/layout/replaced_box.cc b/src/cobalt/layout/replaced_box.cc
index ab107f5..0542809 100644
--- a/src/cobalt/layout/replaced_box.cc
+++ b/src/cobalt/layout/replaced_box.cc
@@ -594,7 +594,8 @@
   base::Optional<LayoutUnit> maybe_margin_right = GetUsedMarginRightIfNotAuto(
       computed_style(), layout_params.containing_block_size);
   LayoutUnit border_box_width = GetBorderBoxWidth();
-  UpdateHorizontalMargins(layout_params.containing_block_size.width(),
+  UpdateHorizontalMargins(layout_params.containing_block_direction,
+                          layout_params.containing_block_size.width(),
                           border_box_width, maybe_margin_left,
                           maybe_margin_right);
 
diff --git a/src/cobalt/layout/replaced_box.h b/src/cobalt/layout/replaced_box.h
index b684d4f..71ac2d8 100644
--- a/src/cobalt/layout/replaced_box.h
+++ b/src/cobalt/layout/replaced_box.h
@@ -98,6 +98,7 @@
   // https://www.w3.org/TR/CSS21/visudet.html#block-replaced-width and
   // https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width.
   virtual void UpdateHorizontalMargins(
+      BaseDirection containing_block_direction,
       LayoutUnit containing_block_width, LayoutUnit border_box_width,
       const base::Optional<LayoutUnit>& maybe_margin_left,
       const base::Optional<LayoutUnit>& maybe_margin_right) = 0;
diff --git a/src/cobalt/layout_tests/layout_snapshot.cc b/src/cobalt/layout_tests/layout_snapshot.cc
index d14081f..26f8fe5 100644
--- a/src/cobalt/layout_tests/layout_snapshot.cc
+++ b/src/cobalt/layout_tests/layout_snapshot.cc
@@ -85,8 +85,10 @@
   browser::WebModule::Options web_module_options;
   web_module_options.layout_trigger = layout::LayoutManager::kTestRunnerMode;
   web_module_options.image_cache_capacity = kImageCacheCapacity;
-
   web_module_options.provide_screenshot_function = screenshot_provider;
+  // We assume that we won't suspend/resume while running the tests, and so
+  // we take advantage of the convenience of inline script tags.
+  web_module_options.enable_inline_script_warnings = false;
 
   // Prepare a slot for our results to be placed when ready.
   base::Optional<browser::WebModule::LayoutResults> results;
diff --git a/src/cobalt/layout_tests/layout_tests.cc b/src/cobalt/layout_tests/layout_tests.cc
index 14be2be..32db038 100644
--- a/src/cobalt/layout_tests/layout_tests.cc
+++ b/src/cobalt/layout_tests/layout_tests.cc
@@ -27,6 +27,9 @@
 #include "cobalt/layout_tests/test_utils.h"
 #include "cobalt/math/size.h"
 #include "cobalt/render_tree/animations/animate_node.h"
+#include "cobalt/renderer/backend/default_graphics_system.h"
+#include "cobalt/renderer/backend/graphics_context.h"
+#include "cobalt/renderer/backend/graphics_system.h"
 #include "cobalt/renderer/render_tree_pixel_tester.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -99,6 +102,7 @@
 };
 
 void RunTest(const TestInfo& test_info,
+             renderer::backend::GraphicsContext* graphics_context,
              renderer::RenderTreePixelTester::Options pixel_tester_options) {
   // Output the name of the current input file so that it is visible in test
   // output.
@@ -130,7 +134,7 @@
 
   renderer::RenderTreePixelTester pixel_tester(
       viewport_size.width_height(), GetTestInputRootDirectory(),
-      GetTestOutputRootDirectory(), pixel_tester_options);
+      GetTestOutputRootDirectory(), graphics_context, pixel_tester_options);
 
   browser::WebModule::LayoutResults layout_results = SnapshotURL(
       test_info.url, viewport_size, pixel_tester.GetResourceProvider(),
@@ -170,18 +174,47 @@
 }  // namespace
 
 // This test does a fuzzy pixel compare with the expected output.
-class Layout : public ::testing::TestWithParam<TestInfo> {};
+class Layout : public ::testing::TestWithParam<TestInfo> {
+ public:
+  static void SetUpTestCase();
+  static void TearDownTestCase();
+
+ protected:
+  static renderer::backend::GraphicsSystem* graphics_system_;
+  static renderer::backend::GraphicsContext* graphics_context_;
+};
+
+// static
+renderer::backend::GraphicsSystem* Layout::graphics_system_ = nullptr;
+// static
+renderer::backend::GraphicsContext* Layout::graphics_context_ = nullptr;
+
+// static
+void Layout::SetUpTestCase() {
+  graphics_system_ = renderer::backend::CreateDefaultGraphicsSystem().release();
+  graphics_context_ = graphics_system_->CreateGraphicsContext().release();
+}
+
+// static
+void Layout::TearDownTestCase() {
+  delete graphics_context_;
+  graphics_context_ = nullptr;
+  delete graphics_system_;
+  graphics_system_ = nullptr;
+}
+
 TEST_P(Layout, Test) {
-  RunTest(GetParam(), renderer::RenderTreePixelTester::Options());
+  RunTest(GetParam(), graphics_context_,
+          renderer::RenderTreePixelTester::Options());
 }
 
 // This test does an exact pixel compare with the expected output.
-class LayoutExact : public ::testing::TestWithParam<TestInfo> {};
+class LayoutExact : public Layout {};
 TEST_P(LayoutExact, Test) {
   renderer::RenderTreePixelTester::Options pixel_tester_options;
   pixel_tester_options.gaussian_blur_sigma = 0;
   pixel_tester_options.acceptable_channel_range = 0;
-  RunTest(GetParam(), pixel_tester_options);
+  RunTest(GetParam(), graphics_context_, pixel_tester_options);
 }
 
 // Cobalt-specific test cases.
@@ -291,6 +324,12 @@
     CSSOMViewLayoutTests, Layout,
     ::testing::ValuesIn(EnumerateLayoutTests("cssom-view")),
     GetTestName());
+// "dir" attribute tests.
+// https://html.spec.whatwg.org/multipage/dom.html#the-dir-attribute
+INSTANTIATE_TEST_CASE_P(
+    DirAttributeLayoutTests, Layout,
+    ::testing::ValuesIn(EnumerateLayoutTests("the-dir-attribute")),
+    GetTestName());
 
 // JavaScript HTML5 WebAPIs (https://www.w3.org/TR/html5/webappapis.html) test
 // cases.
diff --git a/src/cobalt/layout_tests/testdata/bidi/layout_tests.txt b/src/cobalt/layout_tests/testdata/bidi/layout_tests.txt
index d1eda44..299dd93 100644
--- a/src/cobalt/layout_tests/testdata/bidi/layout_tests.txt
+++ b/src/cobalt/layout_tests/testdata/bidi/layout_tests.txt
@@ -12,5 +12,6 @@
 numbers-should-have-weak-directionality-1
 numbers-should-have-weak-directionality-2
 numbers-should-have-weak-directionality-3
-paragraph-should-maintain-directional-stack-with-nested-inline-container-blocks
-whitespace-should-be-handled-properly-with-bidirectional-text
\ No newline at end of file
+# Neither the current actual nor expected matches Chrome.
+#paragraph-should-maintain-directional-stack-with-nested-inline-container-blocks
+whitespace-should-be-handled-properly-with-bidirectional-text
diff --git a/src/cobalt/layout_tests/testdata/cobalt/div-with-border-of-non-integer-size-expected.png b/src/cobalt/layout_tests/testdata/cobalt/div-with-border-of-non-integer-size-expected.png
new file mode 100644
index 0000000..9f26166
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/cobalt/div-with-border-of-non-integer-size-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/cobalt/div-with-border-of-non-integer-size.html b/src/cobalt/layout_tests/testdata/cobalt/div-with-border-of-non-integer-size.html
new file mode 100644
index 0000000..25be8a1
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/cobalt/div-with-border-of-non-integer-size.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!--
+| Verify the content and borders are rendered
+| correctly when borders may require antialiasing.
+-->
+<html>
+<head>
+  <style>
+    body {
+      background: #AAA;
+      font: 20px sans-serif;
+      margin: 10px;
+    }
+    div {
+      margin: 25px;
+      border: 15.5px solid #333;
+      padding: 10px;
+      width: 200px;
+    }
+    #C {
+      background-color: #000;
+    }
+  </style>
+</head>
+<body>
+      <div id="C"><span>C</span>
+      </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt b/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt
index f2740d3..3e9cf42 100644
--- a/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt
+++ b/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt
@@ -5,6 +5,7 @@
 changing-css-text-triggers-layout
 console-trace-should-not-crash
 divs-with-background-color-and-text
+div-with-border-of-non-integer-size
 fixed-width-divs-with-background-color
 font-weight
 image-from-blob
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/10-3-3-overconstrained-should-respect-direction-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/10-3-3-overconstrained-should-respect-direction-expected.png
new file mode 100644
index 0000000..3af96ae
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/10-3-3-overconstrained-should-respect-direction-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/10-3-3-overconstrained-should-respect-direction.html b/src/cobalt/layout_tests/testdata/css-2-1/10-3-3-overconstrained-should-respect-direction.html
new file mode 100644
index 0000000..9653530
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/10-3-3-overconstrained-should-respect-direction.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<!--
+ | When values are over-constrained:
+ | If the 'direction' property of the containing block has the value 'ltr',
+ | the specified value of 'margin-right' is ignored and the value is calculated
+ | so as to make the equality true. If the value of 'direction' is 'rtl', this
+ | happens to 'margin-left' instead.
+ |   https://www.w3.org/TR/CSS21/visudet.html#blockwidth
+ -->
+<html>
+<head>
+  <style>
+    .containing-block {
+      background-color: #000000;
+      width: 300px;
+      height: 90px;
+      margin: 5px;
+    }
+    .red {
+      width: 50px;
+      height: 30px;
+      background-color: #800000;
+    }
+    .green {
+      width: 50px;
+      height: 30px;
+      background-color: #008000;
+    }
+    .blue {
+      width: 50px;
+      height: 30px;
+      background-color: #000080;
+    }
+  </style>
+</head>
+<body>
+  <div class="containing-block">
+    <div class="red" dir="rtl"></div>
+    <div class="green" dir="ltr"></div>
+    <div class="blue"></div>
+  </div>
+  <div class="containing-block" dir="ltr">
+    <div class="red" dir="rtl"></div>
+    <div class="green" dir="ltr"></div>
+    <div class="blue"></div>
+  </div>
+  <div class="containing-block" dir="rtl">
+    <div class="red" dir="rtl"></div>
+    <div class="green" dir="ltr"></div>
+    <div class="blue"></div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-4-3-relative-positioned-elements-use-left-and-top-versus-right-and-bottom-rtl-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/9-4-3-relative-positioned-elements-use-left-and-top-versus-right-and-bottom-rtl-expected.png
new file mode 100644
index 0000000..70771d4
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/9-4-3-relative-positioned-elements-use-left-and-top-versus-right-and-bottom-rtl-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-4-3-relative-positioned-elements-use-left-and-top-versus-right-and-bottom-rtl.html b/src/cobalt/layout_tests/testdata/css-2-1/9-4-3-relative-positioned-elements-use-left-and-top-versus-right-and-bottom-rtl.html
new file mode 100644
index 0000000..284c0b5
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/9-4-3-relative-positioned-elements-use-left-and-top-versus-right-and-bottom-rtl.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<!--
+ | When a relatively positioned element is overconstrained because left and
+ | right are both specified, left wins (if the 'direction' property is set to
+ | 'ltr', if it is 'rtl' then right wins).  When top and bottom are specified,
+ | top wins.
+ |   https://www.w3.org/TR/CSS21/visuren.html#relative-positioning
+ -->
+<html>
+<head>
+  <style>
+    .box {
+      width: 50px;
+      height: 50px;
+
+      background-color: rgb(100, 100, 255);
+    }
+
+    .position-relative {
+      position: relative;
+      left: 25px;
+      top: 25px;
+      right: 10px;
+      bottom: 10px;
+      direction: ltr;
+      background-color: rgb(255, 100, 100);
+    }
+  </style>
+</head>
+
+<body dir="rtl">
+  <div class="box"></div>
+  <div class="box position-relative"></div>
+  <div class="box"></div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt b/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt
index 0ec39c3..4fbd942 100644
--- a/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt
+++ b/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt
@@ -18,6 +18,7 @@
 10-3-3-margin-auto-should-be-treated-as-zero-if-sum-is-greater-than-containing-block-width
 10-3-3-margin-right-should-be-ignored-if-overconstrained
 10-3-3-one-auto-should-follow-from-equality
+10-3-3-overconstrained-should-respect-direction
 10-3-4-block-level-replaced-box-margins-should-be-calculated-as-for-non-replaced-box
 10-3-4-block-level-replaced-box-width-should-be-calculated-as-for-inline-replaced-box
 10-3-7-absolute-element-children-should-shrink-to-fit
@@ -181,6 +182,7 @@
 9-4-3-relative-positioned-elements-are-offset-from-normal-flow-via-right-and-bottom
 9-4-3-relative-positioned-elements-participate-in-stacking-context
 9-4-3-relative-positioned-elements-use-left-and-top-versus-right-and-bottom
+9-4-3-relative-positioned-elements-use-left-and-top-versus-right-and-bottom-rtl
 9-4-3-relative-positioned-elements-with-inline-block-display-are-offset-from-normal-flow
 9-4-3-relative-positioned-elements-with-inline-display-are-offset-from-normal-flow
 9-7-display-should-resolve-to-block-if-position-is-absolute
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/layout_tests.txt b/src/cobalt/layout_tests/testdata/the-dir-attribute/layout_tests.txt
new file mode 100644
index 0000000..5a437dc
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/layout_tests.txt
@@ -0,0 +1,47 @@
+the-dir-attribute-001
+the-dir-attribute-002
+# <table> not supported in Cobalt.
+#the-dir-attribute-003
+# <table> not supported in Cobalt.
+#the-dir-attribute-004
+# <ul> not supported in Cobalt.
+#the-dir-attribute-005
+# <dl> not supported in Cobalt.
+#the-dir-attribute-006
+the-dir-attribute-007
+the-dir-attribute-008
+the-dir-attribute-009
+the-dir-attribute-010
+the-dir-attribute-011
+the-dir-attribute-012
+the-dir-attribute-046
+the-dir-attribute-047
+the-dir-attribute-048
+the-dir-attribute-049
+the-dir-attribute-050
+# <bdi> not supported in Cobalt.
+#the-dir-attribute-051
+# <bdi> not supported in Cobalt.
+#the-dir-attribute-052
+the-dir-attribute-053
+the-dir-attribute-054
+the-dir-attribute-055
+the-dir-attribute-056
+the-dir-attribute-057
+the-dir-attribute-058
+the-dir-attribute-059
+the-dir-attribute-060
+the-dir-attribute-061
+the-dir-attribute-062
+# <textarea> not supported in Cobalt.
+#the-dir-attribute-063
+# <textarea> not supported in Cobalt.
+#the-dir-attribute-064
+the-dir-attribute-065
+the-dir-attribute-066
+the-dir-attribute-067
+the-dir-attribute-068
+the-dir-attribute-069
+the-dir-attribute-070
+the-dir-attribute-071
+writing-modes-principal-flow-body-propagation
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-001-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-001-expected.png
new file mode 100644
index 0000000..4ca0552
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-001-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-002-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-002-expected.png
new file mode 100644
index 0000000..446c219
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-002-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-007-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-007-expected.png
new file mode 100644
index 0000000..a12a63c
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-007-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-008-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-008-expected.png
new file mode 100644
index 0000000..0a17acb
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-008-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-009-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-009-expected.png
new file mode 100644
index 0000000..1df3e6f
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-009-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-010-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-010-expected.png
new file mode 100644
index 0000000..e816eb7
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-010-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-011-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-011-expected.png
new file mode 100644
index 0000000..e816eb7
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-011-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-012-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-012-expected.png
new file mode 100644
index 0000000..e816eb7
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-012-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-046-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-046-expected.png
new file mode 100644
index 0000000..028c32a
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-046-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-047-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-047-expected.png
new file mode 100644
index 0000000..8590f0c
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-047-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-048-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-048-expected.png
new file mode 100644
index 0000000..4c74a9a
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-048-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-049-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-049-expected.png
new file mode 100644
index 0000000..bd90764
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-049-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-050-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-050-expected.png
new file mode 100644
index 0000000..febcb88
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-050-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-053-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-053-expected.png
new file mode 100644
index 0000000..814e8f1
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-053-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-054-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-054-expected.png
new file mode 100644
index 0000000..8bb2447
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-054-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-055-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-055-expected.png
new file mode 100644
index 0000000..814e8f1
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-055-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-056-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-056-expected.png
new file mode 100644
index 0000000..7445d28
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-056-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-057-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-057-expected.png
new file mode 100644
index 0000000..c00f305
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-057-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-058-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-058-expected.png
new file mode 100644
index 0000000..93bf38d
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-058-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-059-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-059-expected.png
new file mode 100644
index 0000000..4c74a9a
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-059-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-060-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-060-expected.png
new file mode 100644
index 0000000..3a2d4ba
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-060-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-061-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-061-expected.png
new file mode 100644
index 0000000..4c74a9a
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-061-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-062-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-062-expected.png
new file mode 100644
index 0000000..3a2d4ba
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-062-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-065-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-065-expected.png
new file mode 100644
index 0000000..d8481ad
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-065-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-066-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-066-expected.png
new file mode 100644
index 0000000..121824e
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-066-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-067-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-067-expected.png
new file mode 100644
index 0000000..5b64bf5
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-067-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-068-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-068-expected.png
new file mode 100644
index 0000000..69c01aa
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-068-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-069-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-069-expected.png
new file mode 100644
index 0000000..5436352
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-069-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-070-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-070-expected.png
new file mode 100644
index 0000000..b0ec05a
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-070-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-071-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-071-expected.png
new file mode 100644
index 0000000..5d42e36
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/the-dir-attribute-071-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/writing-modes-principal-flow-body-propagation-expected.png b/src/cobalt/layout_tests/testdata/the-dir-attribute/writing-modes-principal-flow-body-propagation-expected.png
new file mode 100644
index 0000000..ef7454f
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/writing-modes-principal-flow-body-propagation-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/the-dir-attribute/writing-modes-principal-flow-body-propagation.html b/src/cobalt/layout_tests/testdata/the-dir-attribute/writing-modes-principal-flow-body-propagation.html
new file mode 100644
index 0000000..093472a
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/the-dir-attribute/writing-modes-principal-flow-body-propagation.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!--
+ | The body element's direction should propagate to the root element.
+ |   https://www.w3.org/TR/css-writing-modes-3/#principal-flow
+ -->
+<html dir="ltr">
+<head>
+  <style>
+    body {
+      background-color: rgb(128, 128, 128);
+      width: 200px;
+      height: 200px;
+      border: 10px solid rgb(255, 255, 255);
+    }
+    .box {
+      background-color: rgb(0, 128, 0);
+      width: 100px;
+      height: 100px;
+    }
+  </style>
+</head>
+<body dir="rtl">
+  <div class="box"></div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/web_platform_tests.cc b/src/cobalt/layout_tests/web_platform_tests.cc
index 5d29f26..6b97890 100644
--- a/src/cobalt/layout_tests/web_platform_tests.cc
+++ b/src/cobalt/layout_tests/web_platform_tests.cc
@@ -202,6 +202,9 @@
   // ready for layout should be performed.  See cobalt/dom/test_runner.h.
   browser::WebModule::Options web_module_options;
   web_module_options.layout_trigger = layout::LayoutManager::kTestRunnerMode;
+  // We assume that we won't suspend/resume while running the tests, and so
+  // we take advantage of the convenience of inline script tags.
+  web_module_options.enable_inline_script_warnings = false;
 
   // Prepare a slot for our results to be placed when ready.
   base::Optional<browser::WebModule::LayoutResults> results;
diff --git a/src/cobalt/loader/image/image_decoder_test.cc b/src/cobalt/loader/image/image_decoder_test.cc
index 6e03611..bf3beca 100644
--- a/src/cobalt/loader/image/image_decoder_test.cc
+++ b/src/cobalt/loader/image/image_decoder_test.cc
@@ -785,7 +785,7 @@
 
 // Test that we can properly decode animated WEBP image.
 TEST(ImageDecoderTest, DecodeAnimatedWEBPImage) {
-  base::Thread thread("AnimatedWebP test");
+  base::Thread thread("AnimatedWebP");
   thread.Start();
 
   MockImageDecoder image_decoder;
@@ -814,7 +814,7 @@
 
 // Test that we can properly decode animated WEBP image in multiple chunks.
 TEST(ImageDecoderTest, DecodeAnimatedWEBPImageWithMultipleChunks) {
-  base::Thread thread("AnimatedWebP test");
+  base::Thread thread("AnimatedWebP");
   thread.Start();
 
   MockImageDecoder image_decoder;
diff --git a/src/cobalt/loader/image/png_image_decoder.cc b/src/cobalt/loader/image/png_image_decoder.cc
index 1dc0df8..395cd80 100644
--- a/src/cobalt/loader/image/png_image_decoder.cc
+++ b/src/cobalt/loader/image/png_image_decoder.cc
@@ -237,6 +237,8 @@
   if (interlace_type == PNG_INTERLACE_ADAM7) {
     // Notify libpng to send us rows for interlaced pngs.
     png_set_interlace_handling(png_);
+    DLOG(WARNING) << "Interlaced PNGs are not displayed properly in older "
+                     "versions of Cobalt";
   }
 
   // Updates |info_| to reflect any transformations that have been requested.
@@ -321,7 +323,7 @@
       decoded_image_data_->GetMemory() +
       decoded_image_data_->GetDescriptor().pitch_in_bytes * row_index;
 
-  png_bytep pixel = row_buffer;
+  png_bytep pixel = row;
 
   switch (pixel_format()) {
     case render_tree::kPixelFormatRGBA8: {
diff --git a/src/cobalt/loader/sync_loader.cc b/src/cobalt/loader/sync_loader.cc
index 28d6626..3cb4b44 100644
--- a/src/cobalt/loader/sync_loader.cc
+++ b/src/cobalt/loader/sync_loader.cc
@@ -35,10 +35,13 @@
 // decoder creators.
 class LoaderOnThread {
  public:
-  LoaderOnThread()
-      : start_waitable_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
-                              base::WaitableEvent::InitialState::NOT_SIGNALED),
-        end_waitable_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+  LoaderOnThread(base::Callback<void(const base::Optional<std::string>&)>
+                     load_complete_callback)
+      : load_complete_callback_(load_complete_callback),
+        load_complete_waitable_event_(
+            base::WaitableEvent::ResetPolicy::MANUAL,
+            base::WaitableEvent::InitialState::NOT_SIGNALED),
+        end_waitable_event_(base::WaitableEvent::ResetPolicy::MANUAL,
                             base::WaitableEvent::InitialState::NOT_SIGNALED) {}
 
   // Start() and End() should be called on the same thread, the sychronous load
@@ -46,16 +49,19 @@
   // on that same thread.
   void Start(base::Callback<std::unique_ptr<Fetcher>(Fetcher::Handler*)>
                  fetcher_creator,
-             base::Callback<std::unique_ptr<Decoder>()> decoder_creator,
-             base::Callback<void(const base::Optional<std::string>&)>
-                 load_complete_callback);
+             base::Callback<std::unique_ptr<Decoder>()> decoder_creator);
   void End();
 
-  void SignalStartDone() { start_waitable_event_.Signal(); }
+  void SignalLoadComplete(const base::Optional<std::string>& error) {
+    load_complete_callback_.Run(error);
+    load_complete_waitable_event_.Signal();
+  }
   void SignalEndDone() { end_waitable_event_.Signal(); }
 
-  void WaitForStart(base::WaitableEvent* interrupt_event) {
-    base::WaitableEvent* event_array[] = {&start_waitable_event_,
+  // Returns true if start completed normally, returns false if the wait was
+  // interrupted by |interrupt_event|.
+  void WaitForLoad(base::WaitableEvent* interrupt_event) {
+    base::WaitableEvent* event_array[] = {&load_complete_waitable_event_,
                                           interrupt_event};
     size_t effective_size = arraysize(event_array);
     if (!interrupt_event) {
@@ -67,11 +73,14 @@
   void WaitForEnd() { end_waitable_event_.Wait(); }
 
  private:
+  base::Callback<void(const base::Optional<std::string>&)>
+      load_complete_callback_;
+
   std::unique_ptr<Decoder> decoder_;
   std::unique_ptr<FetcherToDecoderAdapter> fetcher_to_decoder_adaptor_;
   std::unique_ptr<Fetcher> fetcher_;
 
-  base::WaitableEvent start_waitable_event_;
+  base::WaitableEvent load_complete_waitable_event_;
   base::WaitableEvent end_waitable_event_;
 };
 
@@ -83,9 +92,7 @@
       LoaderOnThread* loader_on_thread, Decoder* decoder,
       base::Callback<void(const base::Optional<std::string>&)>
           load_complete_callback)
-      : loader_on_thread_(loader_on_thread),
-        decoder_(decoder),
-        load_complete_callback_(load_complete_callback) {}
+      : loader_on_thread_(loader_on_thread), decoder_(decoder) {}
 
   // From Fetcher::Handler.
   void OnReceived(Fetcher* fetcher, const char* data, size_t size) override {
@@ -96,28 +103,23 @@
     DCHECK(fetcher);
     decoder_->SetLastURLOrigin(fetcher->last_url_origin());
     decoder_->Finish();
-    loader_on_thread_->SignalStartDone();
+    loader_on_thread_->SignalLoadComplete(base::nullopt);
   }
   void OnError(Fetcher* /*fetcher*/, const std::string& error) override {
-    load_complete_callback_.Run(error);
-    loader_on_thread_->SignalStartDone();
+    loader_on_thread_->SignalLoadComplete(error);
   }
 
  private:
   LoaderOnThread* loader_on_thread_;
   Decoder* decoder_;
-  base::Callback<void(const base::Optional<std::string>&)>
-      load_complete_callback_;
 };
 
 void LoaderOnThread::Start(
     base::Callback<std::unique_ptr<Fetcher>(Fetcher::Handler*)> fetcher_creator,
-    base::Callback<std::unique_ptr<Decoder>()> decoder_creator,
-    base::Callback<void(const base::Optional<std::string>&)>
-        load_complete_callback) {
+    base::Callback<std::unique_ptr<Decoder>()> decoder_creator) {
   decoder_ = decoder_creator.Run();
   fetcher_to_decoder_adaptor_.reset(new FetcherToDecoderAdapter(
-      this, decoder_.get(), load_complete_callback));
+      this, decoder_.get(), load_complete_callback_));
   fetcher_ = fetcher_creator.Run(fetcher_to_decoder_adaptor_.get());
 }
 
@@ -125,6 +127,18 @@
   fetcher_.reset();
   fetcher_to_decoder_adaptor_.reset();
   decoder_.reset();
+
+  if (!load_complete_waitable_event_.IsSignaled()) {
+    // We were interrupted, and we did not complete.  Call the
+    // |load_complete_callback| "manually" to indicate this.
+    load_complete_callback_.Run(
+        "Synchronous load was interrupted (probably due to the process "
+        "suspending).  This probably implies that there is a suspend/resume "
+        "bug (are you using inlined <script> tags?  If so, switch to loading "
+        "scripts via JavaScript e.g. by using "
+        "document.createElement('script')).");
+  }
+
   SignalEndDone();
 }
 
@@ -144,13 +158,13 @@
   DCHECK(message_loop);
   DCHECK(!load_complete_callback.is_null());
 
-  LoaderOnThread loader_on_thread;
+  LoaderOnThread loader_on_thread(load_complete_callback);
 
   message_loop->task_runner()->PostTask(
       FROM_HERE,
       base::Bind(&LoaderOnThread::Start, base::Unretained(&loader_on_thread),
-                 fetcher_creator, decoder_creator, load_complete_callback));
-  loader_on_thread.WaitForStart(interrupt_trigger);
+                 fetcher_creator, decoder_creator));
+  loader_on_thread.WaitForLoad(interrupt_trigger);
 
   message_loop->task_runner()->PostTask(
       FROM_HERE,
diff --git a/src/cobalt/media/base/decrypt_config.cc b/src/cobalt/media/base/decrypt_config.cc
index 6aa5655..e5f438b 100644
--- a/src/cobalt/media/base/decrypt_config.cc
+++ b/src/cobalt/media/base/decrypt_config.cc
@@ -5,25 +5,81 @@
 #include "cobalt/media/base/decrypt_config.h"
 
 #include "base/logging.h"
+#include "base/memory/ptr_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "starboard/types.h"
 
 namespace cobalt {
 namespace media {
 
-DecryptConfig::DecryptConfig(const std::string& key_id, const std::string& iv,
-                             const std::vector<SubsampleEntry>& subsamples)
-    : key_id_(key_id), iv_(iv), subsamples_(subsamples) {
-  CHECK_GT(key_id.size(), 0u);
-  CHECK(iv.size() == static_cast<size_t>(DecryptConfig::kDecryptionKeySize) ||
-        iv.empty());
+namespace {
+
+const char* EncryptionModeAsString(EncryptionMode mode) {
+  switch (mode) {
+    case EncryptionMode::kUnencrypted:
+      return "Unencrypted";
+    case EncryptionMode::kCenc:
+      return "CENC";
+    case EncryptionMode::kCbcs:
+      return "CBCS";
+    default:
+      return "Unknown";
+  }
+}
+
+}  // namespace
+
+// static
+std::unique_ptr<DecryptConfig> DecryptConfig::CreateCencConfig(
+    const std::string& key_id,
+    const std::string& iv,
+    const std::vector<SubsampleEntry>& subsamples) {
+  return std::make_unique<DecryptConfig>(EncryptionMode::kCenc, key_id, iv,
+                                         subsamples, base::nullopt);
+}
+
+// static
+std::unique_ptr<DecryptConfig> DecryptConfig::CreateCbcsConfig(
+    const std::string& key_id,
+    const std::string& iv,
+    const std::vector<SubsampleEntry>& subsamples,
+    base::Optional<EncryptionPattern> encryption_pattern) {
+  return std::make_unique<DecryptConfig>(EncryptionMode::kCbcs, key_id, iv,
+                                         subsamples,
+                                         std::move(encryption_pattern));
+}
+
+DecryptConfig::DecryptConfig(
+    const EncryptionMode& encryption_mode,
+    const std::string& key_id,
+    const std::string& iv,
+    const std::vector<SubsampleEntry>& subsamples,
+    base::Optional<EncryptionPattern> encryption_pattern)
+    : encryption_mode_(encryption_mode),
+      key_id_(key_id),
+      iv_(iv),
+      subsamples_(subsamples),
+      encryption_pattern_(std::move(encryption_pattern)) {
+  // Unencrypted blocks should not have a DecryptConfig.
+  DCHECK_NE(encryption_mode_, EncryptionMode::kUnencrypted);
+  CHECK_GT(key_id_.size(), 0u);
+  CHECK_EQ(iv_.size(), static_cast<size_t>(DecryptConfig::kDecryptionKeySize));
+
+  // Pattern not allowed for non-'cbcs' modes.
+  DCHECK(encryption_mode_ == EncryptionMode::kCbcs || !encryption_pattern_);
 }
 
 DecryptConfig::~DecryptConfig() {}
 
+bool DecryptConfig::HasPattern() const {
+  return encryption_pattern_.has_value();
+}
+
 bool DecryptConfig::Matches(const DecryptConfig& config) const {
   if (key_id() != config.key_id() || iv() != config.iv() ||
-      subsamples().size() != config.subsamples().size()) {
+      subsamples().size() != config.subsamples().size() ||
+      encryption_mode_ != config.encryption_mode_ ||
+      encryption_pattern_ != config.encryption_pattern_) {
     return false;
   }
 
@@ -39,7 +95,13 @@
 
 std::ostream& DecryptConfig::Print(std::ostream& os) const {
   os << "key_id:'" << base::HexEncode(key_id_.data(), key_id_.size()) << "'"
-     << " iv:'" << base::HexEncode(iv_.data(), iv_.size()) << "'";
+     << " iv:'" << base::HexEncode(iv_.data(), iv_.size()) << "'"
+     << " mode:" << EncryptionModeAsString(encryption_mode_);
+
+  if (encryption_pattern_) {
+    os << " pattern:" << encryption_pattern_->crypt_byte_block() << ":"
+       << encryption_pattern_->skip_byte_block();
+  }
 
   os << " subsamples:[";
   for (size_t i = 0; i < subsamples_.size(); ++i) {
diff --git a/src/cobalt/media/base/decrypt_config.h b/src/cobalt/media/base/decrypt_config.h
index a92969f..4c43834 100644
--- a/src/cobalt/media/base/decrypt_config.h
+++ b/src/cobalt/media/base/decrypt_config.h
@@ -6,10 +6,13 @@
 #define COBALT_MEDIA_BASE_DECRYPT_CONFIG_H_
 
 #include <iosfwd>
+#include <memory>
 #include <string>
 #include <vector>
 
 #include "base/basictypes.h"
+#include "base/optional.h"
+#include "cobalt/media/base/encryption_pattern.h"
 #include "cobalt/media/base/media_export.h"
 #include "cobalt/media/base/subsample_entry.h"
 #include "starboard/types.h"
@@ -17,6 +20,15 @@
 namespace cobalt {
 namespace media {
 
+// The encryption mode. The definitions are from ISO/IEC 23001-7:2016.
+// TODO(crbug.com/825041): Merge this with existing media::EncryptionScheme.
+enum class EncryptionMode {
+  kUnencrypted = 0,
+  kCenc,  // 'cenc' subsample encryption using AES-CTR mode.
+  kCbcs,  // 'cbcs' pattern encryption using AES-CBC mode.
+  kMaxValue = kCbcs
+};
+
 // Contains all information that a decryptor needs to decrypt a media sample.
 class MEDIA_EXPORT DecryptConfig {
  public:
@@ -25,18 +37,42 @@
 
   // |key_id| is the ID that references the decryption key for this sample.
   // |iv| is the initialization vector defined by the encrypted format.
-  //   Currently |iv| must be 16 bytes as defined by WebM and ISO. Or must be
-  //   empty which signals an unencrypted frame.
+  //   Currently |iv| must be 16 bytes as defined by WebM and ISO. It must
+  //   be provided.
   // |subsamples| defines the clear and encrypted portions of the sample as
   //   described above. A decrypted buffer will be equal in size to the sum
   //   of the subsample sizes.
-  DecryptConfig(const std::string& key_id, const std::string& iv,
-                const std::vector<SubsampleEntry>& subsamples);
+  // |encryption_pattern| is the pattern used ('cbcs' only). It is optional
+  //   as Common encryption of MPEG-2 transport streams v1 (23009-1:2014)
+  //   does not specify patterns for cbcs encryption mode. The pattern is
+  //   assumed to be 1:9 for video and 1:0 for audio.
+  static std::unique_ptr<DecryptConfig> CreateCencConfig(
+      const std::string& key_id,
+      const std::string& iv,
+      const std::vector<SubsampleEntry>& subsamples);
+  static std::unique_ptr<DecryptConfig> CreateCbcsConfig(
+      const std::string& key_id,
+      const std::string& iv,
+      const std::vector<SubsampleEntry>& subsamples,
+      base::Optional<EncryptionPattern> encryption_pattern);
+
+  DecryptConfig(const EncryptionMode& encryption_mode,
+                const std::string& key_id,
+                const std::string& iv,
+                const std::vector<SubsampleEntry>& subsamples,
+                base::Optional<EncryptionPattern> encryption_pattern);
   ~DecryptConfig();
 
   const std::string& key_id() const { return key_id_; }
   const std::string& iv() const { return iv_; }
   const std::vector<SubsampleEntry>& subsamples() const { return subsamples_; }
+  const EncryptionMode& encryption_mode() const { return encryption_mode_; }
+  const base::Optional<EncryptionPattern>& encryption_pattern() const {
+    return encryption_pattern_;
+  }
+
+  // Returns whether this config has EncryptionPattern set or not.
+  bool HasPattern() const;
 
   // Returns true if the corresponding decoder buffer requires decryption and
   // false if that buffer is clear despite the presense of DecryptConfig.
@@ -49,6 +85,7 @@
   std::ostream& Print(std::ostream& os) const;
 
  private:
+  const EncryptionMode encryption_mode_;
   const std::string key_id_;
 
   // Initialization vector.
@@ -58,6 +95,9 @@
   // (less data ignored by data_offset_) is encrypted.
   const std::vector<SubsampleEntry> subsamples_;
 
+  // Only specified if |encryption_mode_| requires a pattern.
+  base::Optional<EncryptionPattern> encryption_pattern_;
+
   DISALLOW_COPY_AND_ASSIGN(DecryptConfig);
 };
 
diff --git a/src/cobalt/media/base/encryption_pattern.cc b/src/cobalt/media/base/encryption_pattern.cc
new file mode 100644
index 0000000..20608ca
--- /dev/null
+++ b/src/cobalt/media/base/encryption_pattern.cc
@@ -0,0 +1,46 @@
+// Copyright 2018 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 "media/base/encryption_pattern.h"
+
+namespace cobalt {
+namespace media {
+
+EncryptionPattern::EncryptionPattern() = default;
+
+EncryptionPattern::EncryptionPattern(uint32_t crypt_byte_block,
+                                     uint32_t skip_byte_block)
+    : crypt_byte_block_(crypt_byte_block), skip_byte_block_(skip_byte_block) {}
+
+EncryptionPattern::EncryptionPattern(const EncryptionPattern& rhs) = default;
+
+EncryptionPattern& EncryptionPattern::operator=(const EncryptionPattern& rhs) =
+    default;
+
+EncryptionPattern::~EncryptionPattern() = default;
+
+bool EncryptionPattern::Matches(const EncryptionPattern& other) const {
+  return crypt_byte_block_ == other.crypt_byte_block() &&
+         skip_byte_block_ == other.skip_byte_block();
+}
+
+bool EncryptionPattern::IsInEffect() const {
+  // ISO/IEC 23001-7(2016), section 10.3, discussing 'cens' pattern encryption
+  // scheme, states "Tracks other than video are protected using whole-block
+  // full-sample encryption as specified in 9.7 and hence skip_byte_block
+  // SHALL be 0." So pattern is in effect as long as |crypt_byte_block_| is set.
+  return crypt_byte_block_ != 0;
+}
+
+bool EncryptionPattern::operator==(const EncryptionPattern& other) const {
+  return crypt_byte_block_ == other.crypt_byte_block_ &&
+         skip_byte_block_ == other.skip_byte_block_;
+}
+
+bool EncryptionPattern::operator!=(const EncryptionPattern& other) const {
+  return !operator==(other);
+}
+
+}  // namespace media
+}  // namespace cobalt
diff --git a/src/cobalt/media/base/encryption_pattern.h b/src/cobalt/media/base/encryption_pattern.h
new file mode 100644
index 0000000..ee830a3
--- /dev/null
+++ b/src/cobalt/media/base/encryption_pattern.h
@@ -0,0 +1,52 @@
+// Copyright 2018 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 COBALT_MEDIA_BASE_ENCRYPTION_PATTERN_H_
+#define COBALT_MEDIA_BASE_ENCRYPTION_PATTERN_H_
+
+#include "cobalt/media/base/media_export.h"
+#include "starboard/types.h"
+
+namespace cobalt {
+namespace media {
+
+// CENC 3rd Edition adds pattern encryption, through two new protection
+// schemes: 'cens' (with AES-CTR) and 'cbcs' (with AES-CBC).
+// The pattern applies independently to each 'encrypted' part of the frame (as
+// defined by the relevant subsample entries), and reduces further the
+// actual encryption applied through a repeating pattern of (encrypt:skip)
+// 16 byte blocks. For example, in a (1:9) pattern, the first block is
+// encrypted, and the next nine are skipped. This pattern is applied
+// repeatedly until the end of the last 16-byte block in the subsample.
+// Any remaining bytes are left clear.
+// If crypt_byte_block is 0, pattern encryption is disabled.
+// TODO(jrummell): Use base::Optional<EncryptionPattern> everywhere, and remove
+// IsInEffect().
+class MEDIA_EXPORT EncryptionPattern {
+ public:
+  EncryptionPattern();
+  EncryptionPattern(uint32_t crypt_byte_block, uint32_t skip_byte_block);
+  EncryptionPattern(const EncryptionPattern& rhs);
+  EncryptionPattern& operator=(const EncryptionPattern& rhs);
+  ~EncryptionPattern();
+
+  bool Matches(const EncryptionPattern& other) const;
+
+  uint32_t crypt_byte_block() const { return crypt_byte_block_; }
+  uint32_t skip_byte_block() const { return skip_byte_block_; }
+
+  bool IsInEffect() const;
+
+  bool operator==(const EncryptionPattern& other) const;
+  bool operator!=(const EncryptionPattern& other) const;
+
+ private:
+  uint32_t crypt_byte_block_ = 0;  // Count of the encrypted blocks.
+  uint32_t skip_byte_block_ = 0;   // Count of the unencrypted blocks.
+};
+
+}  // namespace media
+}  // namespace cobalt
+
+#endif  // COBALT_MEDIA_BASE_ENCRYPTION_PATTERN_H_
diff --git a/src/cobalt/media/base/encryption_scheme.cc b/src/cobalt/media/base/encryption_scheme.cc
index 1f13261..e29bb6d 100644
--- a/src/cobalt/media/base/encryption_scheme.cc
+++ b/src/cobalt/media/base/encryption_scheme.cc
@@ -7,32 +7,16 @@
 namespace cobalt {
 namespace media {
 
-EncryptionScheme::Pattern::Pattern() : encrypt_blocks_(0), skip_blocks_(0) {}
-
-EncryptionScheme::Pattern::Pattern(uint32_t encrypt_blocks,
-                                   uint32_t skip_blocks)
-    : encrypt_blocks_(encrypt_blocks), skip_blocks_(skip_blocks) {}
-
-EncryptionScheme::Pattern::~Pattern() {}
-
-bool EncryptionScheme::Pattern::Matches(const Pattern& other) const {
-  return encrypt_blocks_ == other.encrypt_blocks() &&
-         skip_blocks_ == other.skip_blocks();
-}
-
-bool EncryptionScheme::Pattern::IsInEffect() const {
-  return encrypt_blocks_ != 0 && skip_blocks_ != 0;
-}
-
 EncryptionScheme::EncryptionScheme() : mode_(CIPHER_MODE_UNENCRYPTED) {}
 
-EncryptionScheme::EncryptionScheme(CipherMode mode, const Pattern& pattern)
+EncryptionScheme::EncryptionScheme(CipherMode mode,
+                                   const EncryptionPattern& pattern)
     : mode_(mode), pattern_(pattern) {}
 
 EncryptionScheme::~EncryptionScheme() {}
 
 bool EncryptionScheme::Matches(const EncryptionScheme& other) const {
-  return mode_ == other.mode_ && pattern_.Matches(other.pattern_);
+  return mode_ == other.mode_ && pattern_ == other.pattern_;
 }
 
 }  // namespace media
diff --git a/src/cobalt/media/base/encryption_scheme.h b/src/cobalt/media/base/encryption_scheme.h
index fa01054..200d8d5 100644
--- a/src/cobalt/media/base/encryption_scheme.h
+++ b/src/cobalt/media/base/encryption_scheme.h
@@ -5,6 +5,7 @@
 #ifndef COBALT_MEDIA_BASE_ENCRYPTION_SCHEME_H_
 #define COBALT_MEDIA_BASE_ENCRYPTION_SCHEME_H_
 
+#include "cobalt/media/base/encryption_pattern.h"
 #include "cobalt/media/base/media_export.h"
 #include "starboard/types.h"
 
@@ -23,53 +24,22 @@
     CIPHER_MODE_MAX = CIPHER_MODE_AES_CBC
   };
 
-  // CENC 3rd Edition adds pattern encryption, through two new protection
-  // schemes: 'cens' (with AES-CTR) and 'cbcs' (with AES-CBC).
-  // The pattern applies independently to each 'encrypted' part of the frame (as
-  // defined by the relevant subsample entries), and reduces further the
-  // actual encryption applied through a repeating pattern of (encrypt:skip)
-  // 16 byte blocks. For example, in a (1:9) pattern, the first block is
-  // encrypted, and the next nine are skipped. This pattern is applied
-  // repeatedly until the end of the last 16-byte block in the subsample.
-  // Any remaining bytes are left clear.
-  // If either of encrypt_blocks or skip_blocks is 0, pattern encryption is
-  // disabled.
-  class MEDIA_EXPORT Pattern {
-   public:
-    Pattern();
-    Pattern(uint32_t encrypt_blocks, uint32_t skip_blocks);
-    ~Pattern();
-
-    bool Matches(const Pattern& other) const;
-
-    uint32_t encrypt_blocks() const { return encrypt_blocks_; }
-    uint32_t skip_blocks() const { return skip_blocks_; }
-
-    bool IsInEffect() const;
-
-   private:
-    uint32_t encrypt_blocks_;
-    uint32_t skip_blocks_;
-
-    // Allow copy and assignment.
-  };
-
   // The default constructor makes an instance that indicates no encryption.
   EncryptionScheme();
 
   // This constructor allows specification of the cipher mode and the pattern.
-  EncryptionScheme(CipherMode mode, const Pattern& pattern);
+  EncryptionScheme(CipherMode mode, const EncryptionPattern& pattern);
   ~EncryptionScheme();
 
   bool Matches(const EncryptionScheme& other) const;
 
   bool is_encrypted() const { return mode_ != CIPHER_MODE_UNENCRYPTED; }
   CipherMode mode() const { return mode_; }
-  const Pattern& pattern() const { return pattern_; }
+  const EncryptionPattern& pattern() const { return pattern_; }
 
  private:
   CipherMode mode_;
-  Pattern pattern_;
+  EncryptionPattern pattern_;
 
   // Allow copy and assignment.
 };
diff --git a/src/cobalt/media/base/fake_demuxer_stream.cc b/src/cobalt/media/base/fake_demuxer_stream.cc
index 3214850..5a13d0f 100644
--- a/src/cobalt/media/base/fake_demuxer_stream.cc
+++ b/src/cobalt/media/base/fake_demuxer_stream.cc
@@ -17,6 +17,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/media/base/bind_to_current_loop.h"
 #include "cobalt/media/base/decoder_buffer.h"
+#include "cobalt/media/base/decrypt_config.h"
 #include "cobalt/media/base/media_util.h"
 #include "cobalt/media/base/test_helpers.h"
 #include "cobalt/media/base/timestamp_constants.h"
@@ -193,7 +194,7 @@
 
   // TODO(xhwang): Output out-of-order buffers if needed.
   if (is_encrypted_) {
-    buffer->set_decrypt_config(base::MakeUnique<DecryptConfig>(
+    buffer->set_decrypt_config(DecryptConfig::CreateCencConfig(
         std::string(kKeyId, kKeyId + arraysize(kKeyId)),
         std::string(kIv, kIv + arraysize(kIv)), std::vector<SubsampleEntry>()));
   }
diff --git a/src/cobalt/media/base/media_util.cc b/src/cobalt/media/base/media_util.cc
index 2dda739..95375c7 100644
--- a/src/cobalt/media/base/media_util.cc
+++ b/src/cobalt/media/base/media_util.cc
@@ -4,6 +4,8 @@
 
 #include "cobalt/media/base/media_util.h"
 
+#include "cobalt/media/base/encryption_pattern.h"
+
 namespace cobalt {
 namespace media {
 
@@ -13,7 +15,7 @@
 
 EncryptionScheme AesCtrEncryptionScheme() {
   return EncryptionScheme(EncryptionScheme::CIPHER_MODE_AES_CTR,
-                          EncryptionScheme::Pattern());
+                          EncryptionPattern());
 }
 
 }  // namespace media
diff --git a/src/cobalt/media/base/mime_util_internal.cc b/src/cobalt/media/base/mime_util_internal.cc
index 38e0cb8..b873eb9 100644
--- a/src/cobalt/media/base/mime_util_internal.cc
+++ b/src/cobalt/media/base/mime_util_internal.cc
@@ -133,6 +133,20 @@
   return codec_id;
 }
 
+static bool ParseVp9CodecID(const std::string& mime_type_lower_case,
+                            const std::string& codec_id,
+                            VideoCodecProfile* out_profile,
+                            uint8_t* out_level) {
+  if (mime_type_lower_case == "video/mp4") {
+    return ParseNewStyleVp9CodecID(codec_id, out_profile, out_level);
+  } else if (mime_type_lower_case == "video/webm") {
+    // Only legacy codec strings are supported in WebM.
+    // TODO(kqyang): Should we support new codec string in WebM?
+    return ParseLegacyVp9CodecID(codec_id, out_profile, out_level);
+  }
+  return false;
+}
+
 static bool IsValidH264Level(uint8_t level_idc) {
   // Valid levels taken from Table A-1 in ISO/IEC 14496-10.
   // Level_idc represents the standard level represented as decimal number
@@ -144,81 +158,6 @@
           (level_idc >= 50 && level_idc <= 51));
 }
 
-// Handle parsing of vp9 codec IDs.
-static bool ParseVp9CodecID(const std::string& mime_type_lower_case,
-                            const std::string& codec_id,
-                            VideoCodecProfile* profile) {
-  if (mime_type_lower_case == "video/webm") {
-    if (codec_id == "vp9" || codec_id == "vp9.0") {
-      // Profile is not included in the codec string. Assuming profile 0 to be
-      // backward compatible.
-      *profile = VP9PROFILE_PROFILE0;
-      return true;
-    }
-    // TODO(kqyang): Should we support new codec string in WebM?
-    return false;
-  } else if (mime_type_lower_case == "audio/webm") {
-    return false;
-  }
-
-  std::vector<std::string> fields = base::SplitString(
-      codec_id, std::string("."), base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
-  if (fields.size() < 1) return false;
-
-  if (fields[0] != "vp09") return false;
-
-  if (fields.size() > 8) return false;
-
-  std::vector<int> values;
-  for (size_t i = 1; i < fields.size(); ++i) {
-    // Missing value is not allowed.
-    if (fields[i] == "") return false;
-    int value;
-    if (!base::StringToInt(fields[i], &value)) return false;
-    if (value < 0) return false;
-    values.push_back(value);
-  }
-
-  // The spec specifies 8 fields (7 values excluding the first codec field).
-  // We do not allow missing fields.
-  if (values.size() < 7) return false;
-
-  const int profile_idc = values[0];
-  switch (profile_idc) {
-    case 0:
-      *profile = VP9PROFILE_PROFILE0;
-      break;
-    case 1:
-      *profile = VP9PROFILE_PROFILE1;
-      break;
-    case 2:
-      *profile = VP9PROFILE_PROFILE2;
-      break;
-    case 3:
-      *profile = VP9PROFILE_PROFILE3;
-      break;
-    default:
-      return false;
-  }
-
-  const int bit_depth = values[2];
-  if (bit_depth != 8 && bit_depth != 10 && bit_depth != 12) return false;
-
-  const int color_space = values[3];
-  if (color_space > 7) return false;
-
-  const int chroma_subsampling = values[4];
-  if (chroma_subsampling > 3) return false;
-
-  const int transfer_function = values[5];
-  if (transfer_function > 1) return false;
-
-  const int video_full_range_flag = values[6];
-  if (video_full_range_flag > 1) return false;
-
-  return true;
-}
-
 MimeUtil::MimeUtil() : allow_proprietary_codecs_(false) {
 #if defined(OS_ANDROID)
   platform_info_.is_unified_media_pipeline_enabled =
@@ -617,8 +556,26 @@
   }
 
   // If |codec_id| is not in |string_to_codec_map_|, then we assume that it is
-  // either H.264 or HEVC/H.265 codec ID because currently those are the only
-  // ones that are not added to the |string_to_codec_map_| and require parsing.
+  // either VP9, H.264 or HEVC/H.265 codec ID because currently those are the
+  // only ones that are not added to the |string_to_codec_map_| and require
+  // parsing.
+  if (ParseVp9CodecID(mime_type_lower_case, codec_id, out_profile, out_level)) {
+    *codec = MimeUtil::VP9;
+    switch (*out_profile) {
+      case VP9PROFILE_PROFILE0:
+        // Profile 0 should always be supported if VP9 is supported.
+        *is_ambiguous = false;
+        break;
+      default:
+        // We don't know if the underlying platform supports these profiles.
+        // Need to add platform level querying to get supported profiles
+        // (crbug/604566).
+        *is_ambiguous = true;
+        break;
+    }
+    return true;
+  }
+
   if (ParseAVCCodecId(codec_id, out_profile, out_level)) {
     *codec = MimeUtil::H264;
     switch (*out_profile) {
@@ -646,23 +603,6 @@
     return true;
   }
 
-  if (ParseVp9CodecID(mime_type_lower_case, codec_id, out_profile)) {
-    *codec = MimeUtil::VP9;
-    *out_level = 1;
-    switch (*out_profile) {
-      case VP9PROFILE_PROFILE0:
-        // Profile 0 should always be supported if VP9 is supported.
-        *is_ambiguous = false;
-        break;
-      default:
-        // We don't know if the underlying platform supports these profiles.
-        // Need to add platform level querying to get supported profiles
-        // (crbug/604566).
-        *is_ambiguous = true;
-    }
-    return true;
-  }
-
   if (ParseHEVCCodecId(codec_id, out_profile, out_level)) {
     *codec = MimeUtil::HEVC;
     *is_ambiguous = false;
diff --git a/src/cobalt/media/base/sbplayer_pipeline.cc b/src/cobalt/media/base/sbplayer_pipeline.cc
index ac9a15b..32bad7e 100644
--- a/src/cobalt/media/base/sbplayer_pipeline.cc
+++ b/src/cobalt/media/base/sbplayer_pipeline.cc
@@ -40,6 +40,7 @@
 #include "cobalt/media/base/sbplayer_set_bounds_helper.h"
 #include "cobalt/media/base/starboard_player.h"
 #include "cobalt/media/base/video_decoder_config.h"
+#include "starboard/configuration_constants.h"
 #include "ui/gfx/size.h"
 
 namespace cobalt {
@@ -968,14 +969,22 @@
   DemuxerStream* audio_stream = demuxer_->GetStream(DemuxerStream::AUDIO);
   DemuxerStream* video_stream = demuxer_->GetStream(DemuxerStream::VIDEO);
 
-#if !SB_HAS(AUDIOLESS_VIDEO)
-  if (audio_stream == NULL) {
-    LOG(INFO) << "The video has to contain an audio track.";
-    ResetAndRunIfNotNull(&error_cb_, DEMUXER_ERROR_NO_SUPPORTED_STREAMS,
-                         "The video has to contain an audio track.");
-    return;
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION || \
+    defined(SB_HAS_AUDIOLESS_VIDEO)
+  if (!kSbHasAudiolessVideo) {
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION ||
+        // defined(SB_HAS_AUDIOLESS_VIDEO)
+    if (audio_stream == NULL) {
+      LOG(INFO) << "The video has to contain an audio track.";
+      ResetAndRunIfNotNull(&error_cb_, DEMUXER_ERROR_NO_SUPPORTED_STREAMS,
+                           "The video has to contain an audio track.");
+      return;
+    }
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION || \
+    defined(SB_HAS_AUDIOLESS_VIDEO)
   }
-#endif  // !SB_HAS(AUDIOLESS_VIDEO)
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION ||
+        // defined(SB_HAS_AUDIOLESS_VIDEO)
 
 #if SB_API_VERSION < 10
   if (video_stream == NULL) {
diff --git a/src/cobalt/media/base/starboard_player.cc b/src/cobalt/media/base/starboard_player.cc
index d0c7e92..209e3ff 100644
--- a/src/cobalt/media/base/starboard_player.cc
+++ b/src/cobalt/media/base/starboard_player.cc
@@ -150,6 +150,11 @@
   DCHECK(set_bounds_helper_);
   DCHECK(video_frame_provider_);
 
+#if SB_API_VERSION >= 11
+  audio_sample_info_.codec = kSbMediaAudioCodecNone;
+  video_sample_info_.codec = kSbMediaVideoCodecNone;
+#endif  // SB_API_VERSION >= 11
+
   if (audio_config.IsValidConfig()) {
     UpdateAudioConfig(audio_config);
   }
@@ -157,9 +162,7 @@
     UpdateVideoConfig(video_config);
   }
 
-  output_mode_ = ComputeSbPlayerOutputMode(
-      MediaVideoCodecToSbMediaVideoCodec(video_config.codec()), drm_system,
-      prefer_decode_to_texture);
+  output_mode_ = ComputeSbPlayerOutputMode(prefer_decode_to_texture);
 
   CreatePlayer();
 
@@ -935,24 +938,39 @@
 
 // static
 SbPlayerOutputMode StarboardPlayer::ComputeSbPlayerOutputMode(
-    SbMediaVideoCodec codec, SbDrmSystem drm_system,
-    bool prefer_decode_to_texture) {
-  // Try to choose the output mode according to the passed in value of
-  // |prefer_decode_to_texture|.  If the preferred output mode is unavailable
-  // though, fallback to an output mode that is available.
-  SbPlayerOutputMode output_mode = kSbPlayerOutputModeInvalid;
-  if (SbPlayerOutputModeSupported(kSbPlayerOutputModePunchOut, codec,
-                                  drm_system)) {
-    output_mode = kSbPlayerOutputModePunchOut;
-  }
-  if ((prefer_decode_to_texture || output_mode == kSbPlayerOutputModeInvalid) &&
-      SbPlayerOutputModeSupported(kSbPlayerOutputModeDecodeToTexture, codec,
-                                  drm_system)) {
-    output_mode = kSbPlayerOutputModeDecodeToTexture;
-  }
-  CHECK_NE(kSbPlayerOutputModeInvalid, output_mode);
+    bool prefer_decode_to_texture) const {
+  SbMediaVideoCodec video_codec = kSbMediaVideoCodecNone;
 
+#if SB_API_VERSION >= 11
+  video_codec = video_sample_info_.codec;
+#else   // SB_API_VERSION >= 11
+  video_codec = MediaVideoCodecToSbMediaVideoCodec(video_config_.codec());
+#endif  // SB_API_VERSION >= 11
+
+  // Try to choose |kSbPlayerOutputModeDecodeToTexture| when
+  // |prefer_decode_to_texture| is true.
+  if (prefer_decode_to_texture) {
+    if (SbPlayerOutputModeSupported(kSbPlayerOutputModeDecodeToTexture,
+                                    video_codec, drm_system_)) {
+      return kSbPlayerOutputModeDecodeToTexture;
+    }
+  }
+
+#if SB_HAS(PLAYER_GET_PREFERRED_OUTPUT_MODE)
+  auto output_mode = SbPlayerGetPreferredOutputMode(
+      &audio_sample_info_, &video_sample_info_, drm_system_,
+      max_video_capabilities_.c_str());
+  CHECK_NE(kSbPlayerOutputModeInvalid, output_mode);
   return output_mode;
+#else   // SB_HAS(PLAYER_GET_PREFERRED_OUTPUT_MODE)
+  if (SbPlayerOutputModeSupported(kSbPlayerOutputModePunchOut, video_codec,
+                                  drm_system_)) {
+    return kSbPlayerOutputModePunchOut;
+  }
+  CHECK(SbPlayerOutputModeSupported(kSbPlayerOutputModeDecodeToTexture,
+                                    video_codec, drm_system_));
+  return kSbPlayerOutputModeDecodeToTexture;
+#endif  // SB_HAS(PLAYER_GET_PREFERRED_OUTPUT_MODE)
 }
 
 }  // namespace media
diff --git a/src/cobalt/media/base/starboard_player.h b/src/cobalt/media/base/starboard_player.h
index 13ef299..2074cde 100644
--- a/src/cobalt/media/base/starboard_player.h
+++ b/src/cobalt/media/base/starboard_player.h
@@ -210,9 +210,8 @@
 #endif  // SB_HAS(PLAYER_WITH_URL)
   // Returns the output mode that should be used for a video with the given
   // specifications.
-  static SbPlayerOutputMode ComputeSbPlayerOutputMode(
-      SbMediaVideoCodec codec, SbDrmSystem drm_system,
-      bool prefer_decode_to_texture);
+  SbPlayerOutputMode ComputeSbPlayerOutputMode(
+      bool prefer_decode_to_texture) const;
 
   // The following variables are initialized in the ctor and never changed.
 #if SB_HAS(PLAYER_WITH_URL)
diff --git a/src/cobalt/media/base/starboard_utils.cc b/src/cobalt/media/base/starboard_utils.cc
index 9926a4e..9c07944 100644
--- a/src/cobalt/media/base/starboard_utils.cc
+++ b/src/cobalt/media/base/starboard_utils.cc
@@ -17,6 +17,7 @@
 #include <algorithm>
 
 #include "base/logging.h"
+#include "cobalt/media/base/decrypt_config.h"
 #include "starboard/configuration.h"
 #include "starboard/memory.h"
 
@@ -30,12 +31,23 @@
   switch (codec) {
     case kCodecAAC:
       return kSbMediaAudioCodecAac;
-#if SB_HAS(AC3_AUDIO)
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION || SB_HAS(AC3_AUDIO)
     case kCodecAC3:
+      if (!kSbHasAc3Audio) {
+        DLOG(ERROR) << "Audio codec AC3 not enabled on this platform. To "
+                    << "enable it, set kSbHasAc3Audio to |true|.";
+        return kSbMediaAudioCodecNone;
+      }
       return kSbMediaAudioCodecAc3;
     case kCodecEAC3:
+      if (!kSbHasAc3Audio) {
+        DLOG(ERROR) << "Audio codec AC3 not enabled on this platform. To "
+                    << "enable it, set kSbHasAc3Audio to |true|.";
+        return kSbMediaAudioCodecNone;
+      }
       return kSbMediaAudioCodecEac3;
-#endif  // SB_HAS(AC3_AUDIO)
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION ||
+        // SB_HAS(AC3_AUDIO)
     case kCodecVorbis:
       return kSbMediaAudioCodecVorbis;
     case kCodecOpus:
@@ -135,11 +147,29 @@
   DCHECK(subsample_mapping);
 
   const DecryptConfig* config = buffer->decrypt_config();
+
+#if SB_API_VERSION >= SB_DRM_CBCS_SUPPORT_VERSION
+  if (config->encryption_mode() == EncryptionMode::kCenc) {
+    drm_info->encryption_scheme = kSbDrmEncryptionSchemeAesCtr;
+  } else {
+    DCHECK_EQ(config->encryption_mode(), EncryptionMode::kCbcs);
+    drm_info->encryption_scheme = kSbDrmEncryptionSchemeAesCbc;
+  }
+#else   // SB_API_VERSION >= SB_DRM_CBCS_SUPPORT_VERSION
+  DCHECK_EQ(config->encryption_mode(), EncryptionMode::kCenc);
+#endif  // SB_API_VERSION >= SB_DRM_CBCS_SUPPORT_VERSION
+
+// Set content of |drm_info| to default or invalid values.
+#if SB_API_VERSION >= SB_DRM_CBCS_SUPPORT_VERSION
+  drm_info->encryption_pattern.crypt_byte_block = 0;
+  drm_info->encryption_pattern.skip_byte_block = 0;
+#endif  // SB_API_VERSION >= SB_DRM_CBCS_SUPPORT_VERSION
+  drm_info->initialization_vector_size = 0;
+  drm_info->identifier_size = 0;
+  drm_info->subsample_count = 0;
+  drm_info->subsample_mapping = NULL;
+
   if (!config || config->iv().empty() || config->key_id().empty()) {
-    drm_info->initialization_vector_size = 0;
-    drm_info->identifier_size = 0;
-    drm_info->subsample_count = 0;
-    drm_info->subsample_mapping = NULL;
     return;
   }
 
@@ -148,10 +178,6 @@
 
   if (config->iv().size() > sizeof(drm_info->initialization_vector) ||
       config->key_id().size() > sizeof(drm_info->identifier)) {
-    drm_info->initialization_vector_size = 0;
-    drm_info->identifier_size = 0;
-    drm_info->subsample_count = 0;
-    drm_info->subsample_mapping = NULL;
     return;
   }
 
@@ -175,6 +201,20 @@
     subsample_mapping->clear_byte_count = 0;
     subsample_mapping->encrypted_byte_count = buffer->data_size();
   }
+
+#if SB_API_VERSION >= SB_DRM_CBCS_SUPPORT_VERSION
+  if (buffer->decrypt_config()->HasPattern()) {
+    drm_info->encryption_pattern.crypt_byte_block =
+        config->encryption_pattern()->crypt_byte_block();
+    drm_info->encryption_pattern.skip_byte_block =
+        config->encryption_pattern()->skip_byte_block();
+  }
+#else   // SB_API_VERSION >= SB_DRM_CBCS_SUPPORT_VERSION
+  if (buffer->decrypt_config()->HasPattern()) {
+    DCHECK_EQ(config->encryption_pattern()->crypt_byte_block(), 0);
+    DCHECK_EQ(config->encryption_pattern()->skip_byte_block(), 0);
+  }
+#endif  // SB_API_VERSION >= SB_DRM_CBCS_SUPPORT_VERSION
 }
 
 // Ensure that the enums in starboard/media.h match enums in gfx::ColorSpace.
diff --git a/src/cobalt/media/base/stream_parser_buffer.cc b/src/cobalt/media/base/stream_parser_buffer.cc
index 58f6ba7..47e2300 100644
--- a/src/cobalt/media/base/stream_parser_buffer.cc
+++ b/src/cobalt/media/base/stream_parser_buffer.cc
@@ -226,9 +226,12 @@
   clone->set_splice_timestamp(splice_timestamp());
   const DecryptConfig* decrypt_config = this->decrypt_config();
   if (decrypt_config) {
-    clone->set_decrypt_config(std::unique_ptr<DecryptConfig>(
-        new DecryptConfig(decrypt_config->key_id(), decrypt_config->iv(),
-                          decrypt_config->subsamples())));
+    clone->set_decrypt_config(
+        std::make_unique<DecryptConfig>(
+          decrypt_config->encryption_mode(),
+                          decrypt_config->key_id(), decrypt_config->iv(),
+                          decrypt_config->subsamples(),
+                          decrypt_config->encryption_pattern()));
   }
 
   return clone;
diff --git a/src/cobalt/media/base/video_codecs.cc b/src/cobalt/media/base/video_codecs.cc
index 273a31e..230c302 100644
--- a/src/cobalt/media/base/video_codecs.cc
+++ b/src/cobalt/media/base/video_codecs.cc
@@ -109,6 +109,91 @@
   return "";
 }
 
+bool ParseNewStyleVp9CodecID(const std::string& codec_id,
+                             VideoCodecProfile* profile,
+                             uint8_t* level_idc) {
+  std::vector<std::string> fields = base::SplitString(
+      codec_id, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+
+  // TODO(kqyang): The spec specifies 8 fields. We do not allow missing or extra
+  // fields. See crbug.com/667834.
+  if (fields.size() != 8)
+    return false;
+
+  if (fields[0] != "vp09")
+    return false;
+
+  std::vector<int> values;
+  for (size_t i = 1; i < fields.size(); ++i) {
+    // Missing value is not allowed.
+    if (fields[i] == "")
+      return false;
+    int value;
+    if (!base::StringToInt(fields[i], &value))
+      return false;
+    if (value < 0)
+      return false;
+    values.push_back(value);
+  }
+
+  const int profile_idc = values[0];
+  switch (profile_idc) {
+    case 0:
+      *profile = VP9PROFILE_PROFILE0;
+      break;
+    case 1:
+      *profile = VP9PROFILE_PROFILE1;
+      break;
+    case 2:
+      *profile = VP9PROFILE_PROFILE2;
+      break;
+    case 3:
+      *profile = VP9PROFILE_PROFILE3;
+      break;
+    default:
+      return false;
+  }
+
+  *level_idc = values[1];
+  // TODO(kqyang): Check if |level_idc| is valid. See crbug.com/667834.
+
+  const int bit_depth = values[2];
+  if (bit_depth != 8 && bit_depth != 10 && bit_depth != 12)
+    return false;
+
+  const int color_space = values[3];
+  if (color_space > 7)
+    return false;
+
+  const int chroma_subsampling = values[4];
+  if (chroma_subsampling > 3)
+    return false;
+
+  const int transfer_function = values[5];
+  if (transfer_function > 1)
+    return false;
+
+  const int video_full_range_flag = values[6];
+  if (video_full_range_flag > 1)
+    return false;
+
+  return true;
+}
+
+bool ParseLegacyVp9CodecID(const std::string& codec_id,
+                           VideoCodecProfile* profile,
+                           uint8_t* level_idc) {
+  if (codec_id == "vp9" || codec_id == "vp9.0") {
+    // Profile is not included in the codec string. Assuming profile 0 to be
+    // backward compatible.
+    *profile = VP9PROFILE_PROFILE0;
+    // Use 0 to indicate unknown level.
+    *level_idc = 0;
+    return true;
+  }
+  return false;
+}
+
 bool ParseAv1CodecId(const std::string& codec_id, VideoCodecProfile* profile,
                      uint8_t* level_idc, gfx::ColorSpace* color_space) {
   // The codecs parameter string for the AOM AV1 codec is as follows:
@@ -510,12 +595,15 @@
   if (elem.empty()) return kUnknownVideoCodec;
   VideoCodecProfile profile = VIDEO_CODEC_PROFILE_UNKNOWN;
   uint8_t level = 0;
-  if (ParseAVCCodecId(codec_id, &profile, &level)) return kCodecH264;
-  if (codec_id == "vp8" || codec_id == "vp8.0") return kCodecVP8;
-  if (codec_id == "vp9" || codec_id == "vp9.0" || codec_id == "vp9.1" ||
-      codec_id == "vp9.2") {
+  if (codec_id == "vp8" || codec_id == "vp8.0")
+    return kCodecVP8;
+  if (ParseNewStyleVp9CodecID(codec_id, &profile, &level) ||
+      ParseLegacyVp9CodecID(codec_id, &profile, &level)) {
     return kCodecVP9;
   }
+  if (codec_id == "theora")
+    return kCodecTheora;
+  if (ParseAVCCodecId(codec_id, &profile, &level)) return kCodecH264;
   // We don't parse the full codec string as it must have been checked by
   // isTypeSupported() before passing into this function.
   if (codec_id.substr(0, 5) == "vp09.") {
@@ -524,7 +612,6 @@
   gfx::ColorSpace color_space;
   if (ParseAv1CodecId(codec_id, &profile, &level, &color_space))
     return kCodecAV1;
-  if (codec_id == "theora") return kCodecTheora;
   if (ParseHEVCCodecId(codec_id, &profile, &level)) return kCodecHEVC;
 
   return kUnknownVideoCodec;
diff --git a/src/cobalt/media/base/video_codecs.h b/src/cobalt/media/base/video_codecs.h
index f941d83..5294245 100644
--- a/src/cobalt/media/base/video_codecs.h
+++ b/src/cobalt/media/base/video_codecs.h
@@ -96,6 +96,19 @@
 std::string MEDIA_EXPORT GetCodecName(VideoCodec codec);
 std::string MEDIA_EXPORT GetProfileName(VideoCodecProfile profile);
 
+// ParseNewStyleVp9CodecID handles parsing of new style vp9 codec IDs per
+// proposed VP Codec ISO Media File Format Binding specification:
+// https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp-codec-iso-media-file-format-binding-20160516-draft.pdf
+// ParseLegacyVp9CodecID handles parsing of legacy VP9 codec strings defined
+// for WebM.
+// TODO(kqyang): Consolidate the two functions once we address crbug.com/667834
+MEDIA_EXPORT bool ParseNewStyleVp9CodecID(const std::string& codec_id,
+                                          VideoCodecProfile* profile,
+                                          uint8_t* level_idc);
+MEDIA_EXPORT bool ParseLegacyVp9CodecID(const std::string& codec_id,
+                                        VideoCodecProfile* profile,
+                                        uint8_t* level_idc);
+
 MEDIA_EXPORT bool ParseAv1CodecId(const std::string& codec_id,
                                   VideoCodecProfile* profile,
                                   uint8_t* level_idc,
diff --git a/src/cobalt/media/filters/shell_demuxer.cc b/src/cobalt/media/filters/shell_demuxer.cc
index a68a82a..60cb9ed 100644
--- a/src/cobalt/media/filters/shell_demuxer.cc
+++ b/src/cobalt/media/filters/shell_demuxer.cc
@@ -190,7 +190,7 @@
     : message_loop_(message_loop),
       buffer_allocator_(buffer_allocator),
       host_(NULL),
-      blocking_thread_("ShellDemuxerBlockingThread"),
+      blocking_thread_("ShellDemuxerBlk"),
       data_source_(data_source),
       media_log_(media_log),
       stopped_(false),
diff --git a/src/cobalt/media/formats/mp4/box_definitions.cc b/src/cobalt/media/formats/mp4/box_definitions.cc
index 01cb776..5d30892 100644
--- a/src/cobalt/media/formats/mp4/box_definitions.cc
+++ b/src/cobalt/media/formats/mp4/box_definitions.cc
@@ -23,6 +23,33 @@
 namespace media {
 namespace mp4 {
 
+namespace {
+
+const size_t kKeyIdSize = 16;
+
+// Read color coordinate value as defined in the MasteringDisplayColorVolume
+// ('mdcv') box.  Each coordinate is a float encoded in uint16_t, with upper
+// bound set to 50000.
+bool ReadMdcvColorCoordinate(BoxReader* reader,
+                             float* normalized_value_in_float) {
+  const float kColorCoordinateUpperBound = 50000.;
+
+  uint16_t value_in_uint16;
+  RCHECK(reader->Read2(&value_in_uint16));
+
+  float value_in_float = static_cast<float>(value_in_uint16);
+
+  if (value_in_float >= kColorCoordinateUpperBound) {
+    *normalized_value_in_float = 1.f;
+    return true;
+  }
+
+  *normalized_value_in_float = value_in_float / kColorCoordinateUpperBound;
+  return true;
+}
+
+}  // namespace
+
 FileType::FileType() {}
 FileType::~FileType() {}
 FourCC FileType::BoxType() const { return FOURCC_FTYP; }
@@ -142,8 +169,10 @@
 bool SampleEncryptionEntry::Parse(BufferReader* reader, uint8_t iv_size,
                                   bool has_subsamples) {
   // According to ISO/IEC FDIS 23001-7: CENC spec, IV should be either
-  // 64-bit (8-byte) or 128-bit (16-byte).
-  RCHECK(iv_size == 8 || iv_size == 16);
+  // 64-bit (8-byte) or 128-bit (16-byte). The 3rd Edition allows |iv_size|
+  // to be 0, for the case of a "constant IV". In this case, the existence of
+  // the constant IV must be ensured by the caller.
+  RCHECK(iv_size == 0 || iv_size == 8 || iv_size == 16);
 
   SbMemorySet(initialization_vector, 0, sizeof(initialization_vector));
   for (uint8_t i = 0; i < iv_size; i++)
@@ -210,18 +239,39 @@
   return true;
 }
 
-TrackEncryption::TrackEncryption() : is_encrypted(false), default_iv_size(0) {}
+TrackEncryption::TrackEncryption()
+    : is_encrypted(false),
+      default_iv_size(0),
+      default_crypt_byte_block(0),
+      default_skip_byte_block(0),
+      default_constant_iv_size(0)
+{}
 TrackEncryption::~TrackEncryption() {}
 FourCC TrackEncryption::BoxType() const { return FOURCC_TENC; }
 
 bool TrackEncryption::Parse(BoxReader* reader) {
   uint8_t flag;
-  RCHECK(reader->ReadFullBoxHeader() && reader->SkipBytes(2) &&
-         reader->Read1(&flag) && reader->Read1(&default_iv_size) &&
-         reader->ReadVec(&default_kid, 16));
+  uint8_t possible_pattern_info;
+  RCHECK(reader->ReadFullBoxHeader() &&
+         reader->SkipBytes(1) &&  // skip reserved byte
+         reader->Read1(&possible_pattern_info) && reader->Read1(&flag) &&
+         reader->Read1(&default_iv_size) &&
+         reader->ReadVec(&default_kid, kKeyIdSize));
   is_encrypted = (flag != 0);
   if (is_encrypted) {
-    RCHECK(default_iv_size == 8 || default_iv_size == 16);
+     if (reader->version() > 0) {
+       default_crypt_byte_block = (possible_pattern_info >> 4) & 0x0f;
+       default_skip_byte_block = possible_pattern_info & 0x0f;
+     }
+     if (default_iv_size == 0) {
+       RCHECK(reader->Read1(&default_constant_iv_size));
+       RCHECK(default_constant_iv_size == 8 || default_constant_iv_size == 16);
+       SbMemorySet(default_constant_iv, 0, sizeof(default_constant_iv));
+       for (uint8_t i = 0; i < default_constant_iv_size; i++)
+         RCHECK(reader->Read1(default_constant_iv + i));
+     } else {
+       RCHECK(default_iv_size == 8 || default_iv_size == 16);
+     }
   } else {
     RCHECK(default_iv_size == 0);
   }
@@ -243,14 +293,28 @@
 bool ProtectionSchemeInfo::Parse(BoxReader* reader) {
   RCHECK(reader->ScanChildren() && reader->ReadChild(&format) &&
          reader->ReadChild(&type));
-  if (type.type == FOURCC_CENC) RCHECK(reader->ReadChild(&info));
+  if (HasSupportedScheme()) RCHECK(reader->ReadChild(&info));
   // Other protection schemes are silently ignored. Since the protection scheme
   // type can't be determined until this box is opened, we return 'true' for
-  // non-CENC protection scheme types. It is the parent box's responsibility to
+  // unsupported protection schemes. It is the parent box's responsibility to
   // ensure that this scheme type is a supported one.
   return true;
 }
 
+bool ProtectionSchemeInfo::HasSupportedScheme() const {
+  FourCC four_cc = type.type;
+  if (four_cc == FOURCC_CENC)
+    return true;
+  if (four_cc == FOURCC_CBCS)
+    return true;
+  return false;
+}
+
+bool ProtectionSchemeInfo::IsCbcsEncryptionScheme() const {
+  FourCC four_cc = type.type;
+  return (four_cc == FOURCC_CBCS);
+}
+
 MovieHeader::MovieHeader()
     : version(0),
       creation_time(0),
@@ -620,6 +684,60 @@
   return true;
 }
 
+ColorParameterInformation::ColorParameterInformation() {}
+ColorParameterInformation::~ColorParameterInformation() {}
+FourCC ColorParameterInformation::BoxType() const { return FOURCC_COLR; }
+
+bool ColorParameterInformation::Parse(BoxReader* reader) {
+  FourCC type;
+  RCHECK(reader->ReadFourCC(&type));
+  // TODO: Support 'nclc', 'rICC', and 'prof'.
+  RCHECK(type == FOURCC_NCLX);
+
+  uint8_t full_range_byte;
+  RCHECK(reader->Read2(&colour_primaries) &&
+         reader->Read2(&transfer_characteristics) &&
+         reader->Read2(&matrix_coefficients) &&
+         reader->Read1(&full_range_byte));
+  full_range = full_range_byte & 0x80;
+
+  return true;
+}
+
+MasteringDisplayColorVolume::MasteringDisplayColorVolume() {}
+MasteringDisplayColorVolume::~MasteringDisplayColorVolume() {}
+FourCC MasteringDisplayColorVolume::BoxType() const { return FOURCC_MDCV; }
+
+bool MasteringDisplayColorVolume::Parse(BoxReader* reader) {
+  // Technically the color coordinates may be in any order.  The spec recommends
+  // GBR and it is assumed that the color coordinates are in such order.
+  RCHECK(ReadMdcvColorCoordinate(reader, &display_primaries_gx) &&
+         ReadMdcvColorCoordinate(reader, &display_primaries_gy) &&
+         ReadMdcvColorCoordinate(reader, &display_primaries_bx) &&
+         ReadMdcvColorCoordinate(reader, &display_primaries_by) &&
+         ReadMdcvColorCoordinate(reader, &display_primaries_rx) &&
+         ReadMdcvColorCoordinate(reader, &display_primaries_ry) &&
+         ReadMdcvColorCoordinate(reader, &white_point_x) &&
+         ReadMdcvColorCoordinate(reader, &white_point_y) &&
+         reader->Read4(&max_display_mastering_luminance) &&
+         reader->Read4(&min_display_mastering_luminance));
+
+  const uint32_t kUnitOfMasteringLuminance = 10000;
+  max_display_mastering_luminance /= kUnitOfMasteringLuminance;
+  min_display_mastering_luminance /= kUnitOfMasteringLuminance;
+
+  return true;
+}
+
+ContentLightLevelInformation::ContentLightLevelInformation() {}
+ContentLightLevelInformation::~ContentLightLevelInformation() {}
+FourCC ContentLightLevelInformation::BoxType() const { return FOURCC_CLLI; }
+
+bool ContentLightLevelInformation::Parse(BoxReader* reader) {
+  return reader->Read2(&max_content_light_level) &&
+         reader->Read2(&max_pic_average_light_level);
+}
+
 VideoSampleEntry::VideoSampleEntry()
     : format(FOURCC_NULL),
       data_reference_index(0),
@@ -646,7 +764,7 @@
   if (format == FOURCC_ENCV) {
     // Continue scanning until a recognized protection scheme is found, or until
     // we run out of protection schemes.
-    while (sinf.type.type != FOURCC_CENC) {
+    while (!sinf.HasSupportedScheme()) {
       if (!reader->ReadChild(&sinf)) return false;
     }
   }
@@ -681,6 +799,16 @@
           new HEVCBitstreamConverter(std::move(hevcConfig)));
       break;
     }
+    case FOURCC_VP09: {
+      DVLOG(2) << __func__ << " parsing VPCodecConfigurationRecord (vpcC)";
+      std::unique_ptr<VPCodecConfigurationRecord> vp_config(
+          new VPCodecConfigurationRecord());
+      RCHECK(reader->ReadChild(vp_config.get()));
+      frame_bitstream_converter = nullptr;
+      video_codec = kCodecVP9;
+      video_codec_profile = vp_config->profile;
+      break;
+    }
     case FOURCC_AV01: {
       DVLOG(2) << __func__ << " reading AV1 configuration.";
       AV1CodecConfigurationRecord av1_config;
@@ -688,6 +816,24 @@
       frame_bitstream_converter = nullptr;
       video_codec = kCodecAV1;
       video_codec_profile = av1_config.profile;
+
+      ColorParameterInformation color_parameter_information;
+      if (reader->HasChild(&color_parameter_information)) {
+        RCHECK(reader->ReadChild(&color_parameter_information));
+        this->color_parameter_information = color_parameter_information;
+      }
+
+      MasteringDisplayColorVolume mastering_display_color_volume;
+      if (reader->HasChild(&mastering_display_color_volume)) {
+        RCHECK(reader->ReadChild(&mastering_display_color_volume));
+        this->mastering_display_color_volume = mastering_display_color_volume;
+      }
+
+      ContentLightLevelInformation content_light_level_information;
+      if (reader->HasChild(&content_light_level_information)) {
+        RCHECK(reader->ReadChild(&content_light_level_information));
+        this->content_light_level_information = content_light_level_information;
+      }
       break;
     }
     default:
@@ -710,6 +856,8 @@
     case FOURCC_HEV1:
     case FOURCC_HVC1:
       return true;
+    case FOURCC_VP09:
+      return true;
     case FOURCC_AV01:
       return true;
     default:
@@ -773,7 +921,7 @@
   if (format == FOURCC_ENCA) {
     // Continue scanning until a recognized protection scheme is found, or until
     // we run out of protection schemes.
-    while (sinf.type.type != FOURCC_CENC) {
+    while (!sinf.HasSupportedScheme()) {
       if (!reader->ReadChild(&sinf)) return false;
     }
   }
@@ -1078,9 +1226,40 @@
 }
 
 CencSampleEncryptionInfoEntry::CencSampleEncryptionInfoEntry()
-    : is_encrypted(false), iv_size(0) {}
+    : is_encrypted(false),
+      iv_size(0),
+      crypt_byte_block(0),
+      skip_byte_block(0),
+      constant_iv_size(0)
+{}
 CencSampleEncryptionInfoEntry::~CencSampleEncryptionInfoEntry() {}
 
+bool CencSampleEncryptionInfoEntry::Parse(BoxReader* reader) {
+  uint8_t flag;
+  uint8_t possible_pattern_info;
+  RCHECK(reader->SkipBytes(1) &&  // reserved.
+         reader->Read1(&possible_pattern_info) && reader->Read1(&flag) &&
+         reader->Read1(&iv_size) && reader->ReadVec(&key_id, kKeyIdSize));
+
+  is_encrypted = (flag != 0);
+  if (is_encrypted) {
+    crypt_byte_block = (possible_pattern_info >> 4) & 0x0f;
+    skip_byte_block = possible_pattern_info & 0x0f;
+    if (iv_size == 0) {
+      RCHECK(reader->Read1(&constant_iv_size));
+      RCHECK(constant_iv_size == 8 || constant_iv_size == 16);
+      SbMemorySet(constant_iv, 0, sizeof(constant_iv));
+      for (uint8_t i = 0; i < constant_iv_size; i++)
+        RCHECK(reader->Read1(constant_iv + i));
+    } else {
+      RCHECK(iv_size == 8 || iv_size == 16);
+    }
+  } else {
+    RCHECK(iv_size == 0);
+  }
+  return true;
+}
+
 SampleGroupDescription::SampleGroupDescription() : grouping_type(0) {}
 SampleGroupDescription::~SampleGroupDescription() {}
 FourCC SampleGroupDescription::BoxType() const { return FOURCC_SGPD; }
@@ -1096,7 +1275,6 @@
 
   const uint8_t version = reader->version();
 
-  const size_t kKeyIdSize = 16;
   const size_t kEntrySize = sizeof(uint32_t) + kKeyIdSize;
   uint32_t default_length = 0;
   if (version == 1) {
@@ -1115,18 +1293,7 @@
         RCHECK(description_length >= kEntrySize);
       }
     }
-
-    uint8_t flag;
-    RCHECK(reader->SkipBytes(2) &&  // reserved.
-           reader->Read1(&flag) && reader->Read1(&entries[i].iv_size) &&
-           reader->ReadVec(&entries[i].key_id, kKeyIdSize));
-
-    entries[i].is_encrypted = (flag != 0);
-    if (entries[i].is_encrypted) {
-      RCHECK(entries[i].iv_size == 8 || entries[i].iv_size == 16);
-    } else {
-      RCHECK(entries[i].iv_size == 0);
-    }
+    RCHECK(entries[i].Parse(reader));
   }
   return true;
 }
diff --git a/src/cobalt/media/formats/mp4/box_definitions.h b/src/cobalt/media/formats/mp4/box_definitions.h
index 90415d9..0dd974f 100644
--- a/src/cobalt/media/formats/mp4/box_definitions.h
+++ b/src/cobalt/media/formats/mp4/box_definitions.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/compiler_specific.h"
+#include "base/optional.h"
 #include "cobalt/media/base/decrypt_config.h"
 #include "cobalt/media/base/media_export.h"
 #include "cobalt/media/base/media_log.h"
@@ -23,6 +24,9 @@
 namespace media {
 namespace mp4 {
 
+// Size in bytes needed to store largest IV.
+const int kInitializationVectorSize = 16;
+
 enum TrackType { kInvalid = 0, kVideo, kAudio, kText, kHint };
 
 enum SampleFlags { kSampleIsNonSyncSample = 0x10000 };
@@ -86,7 +90,7 @@
   // anywhere.
   bool GetTotalSizeOfSubsamples(size_t* total_size) const;
 
-  uint8_t initialization_vector[16];
+  uint8_t initialization_vector[kInitializationVectorSize];
   std::vector<SubsampleEntry> subsamples;
 };
 
@@ -124,6 +128,10 @@
   bool is_encrypted;
   uint8_t default_iv_size;
   std::vector<uint8_t> default_kid;
+  uint8_t default_crypt_byte_block;
+  uint8_t default_skip_byte_block;
+  uint8_t default_constant_iv_size;
+  uint8_t default_constant_iv[kInitializationVectorSize];
 };
 
 struct MEDIA_EXPORT SchemeInfo : Box {
@@ -138,6 +146,9 @@
   OriginalFormat format;
   SchemeType type;
   SchemeInfo info;
+
+  bool HasSupportedScheme() const;
+  bool IsCbcsEncryptionScheme() const;
 };
 
 struct MEDIA_EXPORT MovieHeader : Box {
@@ -239,6 +250,37 @@
   uint32_t v_spacing;
 };
 
+struct MEDIA_EXPORT ColorParameterInformation : Box {
+  DECLARE_BOX_METHODS(ColorParameterInformation);
+
+  uint16 colour_primaries;
+  uint16 transfer_characteristics;
+  uint16 matrix_coefficients;
+  bool full_range;
+};
+
+struct MEDIA_EXPORT MasteringDisplayColorVolume : Box {
+  DECLARE_BOX_METHODS(MasteringDisplayColorVolume);
+
+  float display_primaries_gx;
+  float display_primaries_gy;
+  float display_primaries_bx;
+  float display_primaries_by;
+  float display_primaries_rx;
+  float display_primaries_ry;
+  float white_point_x;
+  float white_point_y;
+  uint32 max_display_mastering_luminance;
+  uint32 min_display_mastering_luminance;
+};
+
+struct MEDIA_EXPORT ContentLightLevelInformation : Box {
+  DECLARE_BOX_METHODS(ContentLightLevelInformation);
+
+  uint16 max_content_light_level;
+  uint16 max_pic_average_light_level;
+};
+
 struct MEDIA_EXPORT VideoSampleEntry : Box {
   DECLARE_BOX_METHODS(VideoSampleEntry);
 
@@ -253,6 +295,10 @@
   VideoCodec video_codec;
   VideoCodecProfile video_codec_profile;
 
+  base::Optional<ColorParameterInformation> color_parameter_information;
+  base::Optional<MasteringDisplayColorVolume> mastering_display_color_volume;
+  base::Optional<ContentLightLevelInformation> content_light_level_information;
+
   bool IsFormatValid() const;
 
   scoped_refptr<BitstreamConverter> frame_bitstream_converter;
@@ -289,10 +335,15 @@
 struct MEDIA_EXPORT CencSampleEncryptionInfoEntry {
   CencSampleEncryptionInfoEntry();
   ~CencSampleEncryptionInfoEntry();
+  bool Parse(BoxReader* reader);
 
   bool is_encrypted;
   uint8_t iv_size;
   std::vector<uint8_t> key_id;
+  uint8_t crypt_byte_block;
+  uint8_t skip_byte_block;
+  uint8_t constant_iv_size;
+  uint8_t constant_iv[kInitializationVectorSize];
 };
 
 struct MEDIA_EXPORT SampleGroupDescription : Box {  // 'sgpd'.
diff --git a/src/cobalt/media/formats/mp4/fourccs.h b/src/cobalt/media/formats/mp4/fourccs.h
index e79b003..b933f91 100644
--- a/src/cobalt/media/formats/mp4/fourccs.h
+++ b/src/cobalt/media/formats/mp4/fourccs.h
@@ -21,8 +21,11 @@
   FOURCC_AVC3 = 0x61766333,
   FOURCC_AVCC = 0x61766343,
   FOURCC_BLOC = 0x626C6F63,
+  FOURCC_CBCS = 0x63626373,
   FOURCC_CENC = 0x63656e63,
+  FOURCC_CLLI = 0x636c6c69,
   FOURCC_CO64 = 0x636f3634,
+  FOURCC_COLR = 0x636f6c72,
   FOURCC_CTTS = 0x63747473,
   FOURCC_DINF = 0x64696e66,
   FOURCC_EDTS = 0x65647473,
@@ -41,6 +44,7 @@
   FOURCC_HVCC = 0x68766343,
   FOURCC_IODS = 0x696f6473,
   FOURCC_MDAT = 0x6d646174,
+  FOURCC_MDCV = 0x6d646376,
   FOURCC_MDHD = 0x6d646864,
   FOURCC_MDIA = 0x6d646961,
   FOURCC_MECO = 0x6d65636f,
@@ -55,6 +59,7 @@
   FOURCC_MP4V = 0x6d703476,
   FOURCC_MVEX = 0x6d766578,
   FOURCC_MVHD = 0x6d766864,
+  FOURCC_NCLX = 0x6e636c78,
   FOURCC_PASP = 0x70617370,
   FOURCC_PDIN = 0x7064696e,
   FOURCC_PRFT = 0x70726674,
diff --git a/src/cobalt/media/formats/mp4/mp4_stream_parser.cc b/src/cobalt/media/formats/mp4/mp4_stream_parser.cc
index 391318b..a184c8a 100644
--- a/src/cobalt/media/formats/mp4/mp4_stream_parser.cc
+++ b/src/cobalt/media/formats/mp4/mp4_stream_parser.cc
@@ -16,6 +16,9 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "cobalt/media/base/audio_decoder_config.h"
+#include "cobalt/media/base/color_space.h"
+#include "cobalt/media/base/encryption_scheme.h"
+#include "cobalt/media/base/hdr_metadata.h"
 #include "cobalt/media/base/media_tracks.h"
 #include "cobalt/media/base/media_util.h"
 #include "cobalt/media/base/stream_parser_buffer.h"
@@ -28,6 +31,7 @@
 #include "cobalt/media/formats/mp4/es_descriptor.h"
 #include "cobalt/media/formats/mp4/rcheck.h"
 #include "cobalt/media/formats/mpeg/adts_constants.h"
+#include "cobalt/media/formats/webm/webm_colour_parser.h"
 #include "starboard/memory.h"
 #include "starboard/types.h"
 
@@ -36,7 +40,73 @@
 namespace mp4 {
 
 namespace {
+
+using gfx::ColorSpace;
+
 const int kMaxEmptySampleLogs = 20;
+
+// Caller should be prepared to handle return of Unencrypted() in case of
+// unsupported scheme.
+EncryptionScheme GetEncryptionScheme(const ProtectionSchemeInfo& sinf) {
+  if (!sinf.HasSupportedScheme())
+    return Unencrypted();
+  FourCC fourcc = sinf.type.type;
+  EncryptionScheme::CipherMode mode = EncryptionScheme::CIPHER_MODE_UNENCRYPTED;
+  EncryptionPattern pattern;
+  bool uses_pattern_encryption = false;
+  switch (fourcc) {
+    case FOURCC_CENC:
+      mode = EncryptionScheme::CIPHER_MODE_AES_CTR;
+      break;
+    case FOURCC_CBCS:
+      mode = EncryptionScheme::CIPHER_MODE_AES_CBC;
+      uses_pattern_encryption = true;
+      break;
+    default:
+      NOTREACHED();
+      break;
+  }
+  if (uses_pattern_encryption) {
+    pattern = {sinf.info.track_encryption.default_crypt_byte_block,
+               sinf.info.track_encryption.default_skip_byte_block};
+  }
+  return EncryptionScheme(mode, pattern);
+}
+
+gfx::ColorSpace ConvertColorParameterInformationToColorSpace(
+    const ColorParameterInformation& info) {
+  auto primary_id = static_cast<ColorSpace::PrimaryID>(info.colour_primaries);
+  auto transfer_id =
+      static_cast<ColorSpace::TransferID>(info.transfer_characteristics);
+  auto matrix_id = static_cast<ColorSpace::MatrixID>(info.matrix_coefficients);
+
+  // Note that we don't check whether the embedded ids are valid.  We rely on
+  // the underlying video decoder to reject any ids that it doesn't support.
+  return gfx::ColorSpace(
+      primary_id, transfer_id, matrix_id,
+      info.full_range ? ColorSpace::kRangeIdFull : ColorSpace::kRangeIdLimited);
+}
+
+MasteringMetadata ConvertMdcvToMasteringMetadata(
+    const MasteringDisplayColorVolume& mdcv) {
+  MasteringMetadata mastering_metadata;
+
+  mastering_metadata.primary_r_chromaticity_x = mdcv.display_primaries_rx;
+  mastering_metadata.primary_r_chromaticity_y = mdcv.display_primaries_ry;
+  mastering_metadata.primary_g_chromaticity_x = mdcv.display_primaries_gx;
+  mastering_metadata.primary_g_chromaticity_y = mdcv.display_primaries_gy;
+  mastering_metadata.primary_b_chromaticity_x = mdcv.display_primaries_bx;
+  mastering_metadata.primary_b_chromaticity_y = mdcv.display_primaries_by;
+  mastering_metadata.white_point_chromaticity_x = mdcv.white_point_x;
+  mastering_metadata.white_point_chromaticity_y = mdcv.white_point_y;
+  mastering_metadata.luminance_max =
+      static_cast<float>(mdcv.max_display_mastering_luminance);
+  mastering_metadata.luminance_min =
+      static_cast<float>(mdcv.min_display_mastering_luminance);
+
+  return mastering_metadata;
+}
+
 }  // namespace
 
 MP4StreamParser::MP4StreamParser(DecoderBuffer::Allocator* buffer_allocator,
@@ -305,10 +375,15 @@
       }
       bool is_track_encrypted = entry.sinf.info.track_encryption.is_encrypted;
       is_track_encrypted_[audio_track_id] = is_track_encrypted;
-      audio_config.Initialize(
-          codec, sample_format, channel_layout, sample_per_second, extra_data,
-          is_track_encrypted ? AesCtrEncryptionScheme() : Unencrypted(),
-          base::TimeDelta(), 0);
+      EncryptionScheme scheme = Unencrypted();
+      if (is_track_encrypted) {
+        scheme = GetEncryptionScheme(entry.sinf);
+        if (!scheme.is_encrypted())
+          return false;
+      }
+      audio_config.Initialize(codec, sample_format, channel_layout,
+                              sample_per_second, extra_data, scheme,
+                              base::TimeDelta(), 0);
       DVLOG(1) << "audio_track_id=" << audio_track_id
                << " config=" << audio_config.AsHumanReadableString();
       if (!audio_config.IsValidConfig()) {
@@ -364,13 +439,42 @@
       }
       bool is_track_encrypted = entry.sinf.info.track_encryption.is_encrypted;
       is_track_encrypted_[video_track_id] = is_track_encrypted;
-      video_config.Initialize(
-          entry.video_codec, entry.video_codec_profile, PIXEL_FORMAT_YV12,
-          COLOR_SPACE_HD_REC709, coded_size, visible_rect, natural_size,
-          // No decoder-specific buffer needed for AVC;
-          // SPS/PPS are embedded in the video stream
-          EmptyExtraData(),
-          is_track_encrypted ? AesCtrEncryptionScheme() : Unencrypted());
+      EncryptionScheme scheme = Unencrypted();
+      if (is_track_encrypted) {
+        scheme = GetEncryptionScheme(entry.sinf);
+        if (!scheme.is_encrypted())
+          return false;
+      }
+      video_config.Initialize(entry.video_codec, entry.video_codec_profile,
+                              PIXEL_FORMAT_YV12, COLOR_SPACE_HD_REC709,
+                              coded_size, visible_rect, natural_size,
+                              // No decoder-specific buffer needed for AVC;
+                              // SPS/PPS are embedded in the video stream
+                              EmptyExtraData(), scheme);
+      if (entry.color_parameter_information) {
+        WebMColorMetadata color_metadata = {};
+
+        color_metadata.color_space =
+            ConvertColorParameterInformationToColorSpace(
+                *entry.color_parameter_information);
+
+        if (entry.mastering_display_color_volume) {
+          color_metadata.hdr_metadata.mastering_metadata =
+              ConvertMdcvToMasteringMetadata(
+                  *entry.mastering_display_color_volume);
+        }
+
+        if (entry.content_light_level_information) {
+          color_metadata.hdr_metadata.max_cll =
+              entry.content_light_level_information->max_content_light_level;
+          color_metadata.hdr_metadata.max_fall =
+              entry.content_light_level_information
+                  ->max_pic_average_light_level;
+        }
+
+        video_config.set_webm_color_metadata(color_metadata);
+      }
+
       DVLOG(1) << "video_track_id=" << video_track_id
                << " config=" << video_config.AsHumanReadableString();
       if (!video_config.IsValidConfig()) {
@@ -602,15 +706,12 @@
   if (decrypt_config) {
     if (!subsamples.empty()) {
       // Create a new config with the updated subsamples.
-      decrypt_config.reset(new DecryptConfig(decrypt_config->key_id(),
-                                             decrypt_config->iv(), subsamples));
+      decrypt_config.reset(
+          new DecryptConfig(decrypt_config->encryption_mode(),
+                            decrypt_config->key_id(), decrypt_config->iv(),
+                            subsamples, decrypt_config->encryption_pattern()));
     }
     // else, use the existing config.
-  } else if (is_track_encrypted_[runs_->track_id()]) {
-    // The media pipeline requires a DecryptConfig with an empty |iv|.
-    // TODO(ddorwin): Refactor so we do not need a fake key ID ("1");
-    decrypt_config.reset(
-        new DecryptConfig("1", "", std::vector<SubsampleEntry>()));
   }
 
   StreamParserBuffer::Type buffer_type =
diff --git a/src/cobalt/media/formats/mp4/track_run_iterator.cc b/src/cobalt/media/formats/mp4/track_run_iterator.cc
index 9dd1b4a..8a787cf 100644
--- a/src/cobalt/media/formats/mp4/track_run_iterator.cc
+++ b/src/cobalt/media/formats/mp4/track_run_iterator.cc
@@ -11,6 +11,8 @@
 #include <string>
 
 #include "base/basictypes.h"
+#include "cobalt/media/base/encryption_scheme.h"
+#include "cobalt/media/base/media_util.h"
 #include "cobalt/media/formats/mp4/rcheck.h"
 #include "cobalt/media/formats/mp4/sample_to_group_iterator.h"
 
@@ -49,6 +51,8 @@
   std::vector<uint8_t> aux_info_sizes;  // Populated if default_size == 0.
   int aux_info_total_size;
 
+  EncryptionScheme encryption_scheme;
+
   std::vector<CencSampleEncryptionInfoEntry> fragment_sample_encryption_info;
 
   TrackRunInfo();
@@ -321,20 +325,32 @@
       tri.fragment_sample_encryption_info =
           traf.sample_group_description.entries;
 
-      uint8_t default_iv_size = 0;
+      const TrackEncryption* track_encryption;
+      const ProtectionSchemeInfo* sinf;
       tri.is_audio = (stsd.type == kAudio);
       if (tri.is_audio) {
         RCHECK(!stsd.audio_entries.empty());
         if (desc_idx > stsd.audio_entries.size()) desc_idx = 0;
         tri.audio_description = &stsd.audio_entries[desc_idx];
-        default_iv_size =
-            tri.audio_description->sinf.info.track_encryption.default_iv_size;
+        sinf = &tri.audio_description->sinf;
+        track_encryption = &tri.audio_description->sinf.info.track_encryption;
       } else {
         RCHECK(!stsd.video_entries.empty());
         if (desc_idx > stsd.video_entries.size()) desc_idx = 0;
         tri.video_description = &stsd.video_entries[desc_idx];
-        default_iv_size =
-            tri.video_description->sinf.info.track_encryption.default_iv_size;
+        sinf = &tri.video_description->sinf;
+        track_encryption = &tri.video_description->sinf.info.track_encryption;
+      }
+
+      if (!sinf->HasSupportedScheme()) {
+        tri.encryption_scheme = Unencrypted();
+      } else {
+        tri.encryption_scheme = EncryptionScheme(
+            sinf->IsCbcsEncryptionScheme()
+                ? EncryptionScheme::CIPHER_MODE_AES_CBC
+                : EncryptionScheme::CIPHER_MODE_AES_CTR,
+            EncryptionPattern(track_encryption->default_crypt_byte_block,
+                              track_encryption->default_skip_byte_block));
       }
 
       // Initialize aux_info variables only if no sample encryption entries.
@@ -403,12 +419,45 @@
         tri.sample_encryption_entries.resize(trun.sample_count);
         for (size_t k = 0; k < trun.sample_count; k++) {
           uint32_t index = tri.samples[k].cenc_group_description_index;
-          const uint8_t iv_size =
-              index == 0 ? default_iv_size
-                         : GetSampleEncryptionInfoEntry(tri, index)->iv_size;
-          RCHECK(tri.sample_encryption_entries[k].Parse(
-              sample_encryption_reader.get(), iv_size,
-              traf.sample_encryption.use_subsample_encryption));
+          const CencSampleEncryptionInfoEntry* info_entry =
+              index == 0 ? nullptr : GetSampleEncryptionInfoEntry(tri, index);
+          const uint8_t iv_size = index == 0 ? track_encryption->default_iv_size
+                                             : info_entry->iv_size;
+          SampleEncryptionEntry& entry = tri.sample_encryption_entries[k];
+          RCHECK(entry.Parse(sample_encryption_reader.get(), iv_size,
+                             traf.sample_encryption.use_subsample_encryption));
+          // If we don't have a per-sample IV, get the constant IV.
+          bool is_encrypted = index == 0 ? track_encryption->is_encrypted
+                                         : info_entry->is_encrypted;
+          // We only support setting the pattern values in the 'tenc' box for
+          // the track (not varying on per sample group basis).
+          // Thus we need to verify that the settings in the sample group match
+          // those in the 'tenc'.
+          if (is_encrypted && index != 0) {
+            RCHECK_MEDIA_LOGGED(info_entry->crypt_byte_block ==
+                                    track_encryption->default_crypt_byte_block,
+                                media_log_,
+                                "Pattern value (crypt byte block) for the "
+                                "sample group does not match that in the tenc "
+                                "box . This is not currently supported.");
+            RCHECK_MEDIA_LOGGED(info_entry->skip_byte_block ==
+                                    track_encryption->default_skip_byte_block,
+                                media_log_,
+                                "Pattern value (skip byte block) for the "
+                                "sample group does not match that in the tenc "
+                                "box . This is not currently supported.");
+          }
+          if (is_encrypted && !iv_size) {
+            const uint8_t constant_iv_size =
+                index == 0 ? track_encryption->default_constant_iv_size
+                           : info_entry->constant_iv_size;
+            RCHECK(constant_iv_size != 0);
+            const uint8_t* constant_iv =
+                index == 0 ? track_encryption->default_constant_iv
+                           : info_entry->constant_iv;
+            SbMemoryCopy(entry.initialization_vector, constant_iv,
+                         constant_iv_size);
+          }
         }
       }
       runs_.push_back(tri);
@@ -468,8 +517,12 @@
       BufferReader reader(buf + pos, info_size);
       const uint8_t iv_size = GetIvSize(i);
       const bool has_subsamples = info_size > iv_size;
-      RCHECK(
-          sample_encryption_entries[i].Parse(&reader, iv_size, has_subsamples));
+      SampleEncryptionEntry& entry = sample_encryption_entries[i];
+      RCHECK(entry.Parse(&reader, iv_size, has_subsamples));
+      // if we don't have a per-sample IV, get the constant IV.
+      if (!iv_size) {
+        RCHECK(ApplyConstantIv(i, &entry));
+      }
     }
     pos += info_size;
   }
@@ -574,40 +627,79 @@
   return sample_itr_->is_keyframe;
 }
 
+const ProtectionSchemeInfo& TrackRunIterator::protection_scheme_info() const {
+  if (is_audio())
+    return audio_description().sinf;
+  return video_description().sinf;
+}
+
 const TrackEncryption& TrackRunIterator::track_encryption() const {
-  if (is_audio()) return audio_description().sinf.info.track_encryption;
-  return video_description().sinf.info.track_encryption;
+  return protection_scheme_info().info.track_encryption;
 }
 
 std::unique_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() {
   DCHECK(is_encrypted());
+  size_t sample_idx = sample_itr_ - run_itr_->samples.begin();
+  const std::vector<uint8_t>& kid = GetKeyId(sample_idx);
+  std::string key_id(kid.begin(), kid.end());
 
   if (run_itr_->sample_encryption_entries.empty()) {
     DCHECK_EQ(0, aux_info_size());
+    // The 'cbcs' scheme allows empty aux info when a constant IV is in use
+    // with full sample encryption. That case will fall through to here.
+    SampleEncryptionEntry sample_encryption_entry;
+    if (ApplyConstantIv(sample_idx, &sample_encryption_entry)) {
+      std::string iv(reinterpret_cast<const char*>(
+                         sample_encryption_entry.initialization_vector),
+                     arraysize(sample_encryption_entry.initialization_vector));
+      switch (run_itr_->encryption_scheme.mode()) {
+        case EncryptionScheme::CIPHER_MODE_UNENCRYPTED:
+          return nullptr;
+        case EncryptionScheme::CIPHER_MODE_AES_CTR:
+          return DecryptConfig::CreateCencConfig(
+              key_id, iv, sample_encryption_entry.subsamples);
+        case EncryptionScheme::CIPHER_MODE_AES_CBC:
+          return DecryptConfig::CreateCbcsConfig(
+              key_id, iv, sample_encryption_entry.subsamples,
+              run_itr_->encryption_scheme.pattern());
+      }
+    }
     MEDIA_LOG(ERROR, media_log_) << "Sample encryption info is not available.";
-    return std::unique_ptr<DecryptConfig>();
+    return nullptr;
   }
 
-  size_t sample_idx = sample_itr_ - run_itr_->samples.begin();
   DCHECK_LT(sample_idx, run_itr_->sample_encryption_entries.size());
   const SampleEncryptionEntry& sample_encryption_entry =
       run_itr_->sample_encryption_entries[sample_idx];
+  std::string iv(reinterpret_cast<const char*>(
+                     sample_encryption_entry.initialization_vector),
+                 arraysize(sample_encryption_entry.initialization_vector));
 
   size_t total_size = 0;
   if (!sample_encryption_entry.subsamples.empty() &&
       (!sample_encryption_entry.GetTotalSizeOfSubsamples(&total_size) ||
        total_size != static_cast<size_t>(sample_size()))) {
     MEDIA_LOG(ERROR, media_log_) << "Incorrect CENC subsample size.";
-    return std::unique_ptr<DecryptConfig>();
+    return nullptr;
   }
 
-  const std::vector<uint8_t>& kid = GetKeyId(sample_idx);
-  return std::unique_ptr<DecryptConfig>(new DecryptConfig(
-      std::string(reinterpret_cast<const char*>(&kid[0]), kid.size()),
-      std::string(reinterpret_cast<const char*>(
-                      sample_encryption_entry.initialization_vector),
-                  arraysize(sample_encryption_entry.initialization_vector)),
-      sample_encryption_entry.subsamples));
+  if (protection_scheme_info().IsCbcsEncryptionScheme()) {
+    uint32_t index = GetGroupDescriptionIndex(sample_idx);
+    uint32_t encrypt_blocks =
+        (index == 0)
+            ? track_encryption().default_crypt_byte_block
+            : GetSampleEncryptionInfoEntry(*run_itr_, index)->crypt_byte_block;
+    uint32_t skip_blocks =
+        (index == 0)
+            ? track_encryption().default_skip_byte_block
+            : GetSampleEncryptionInfoEntry(*run_itr_, index)->skip_byte_block;
+    return DecryptConfig::CreateCbcsConfig(
+        key_id, iv, sample_encryption_entry.subsamples,
+        EncryptionPattern(encrypt_blocks, skip_blocks));
+  }
+
+  return DecryptConfig::CreateCencConfig(key_id, iv,
+                                         sample_encryption_entry.subsamples);
 }
 
 uint32_t TrackRunIterator::GetGroupDescriptionIndex(
@@ -637,6 +729,24 @@
                       : GetSampleEncryptionInfoEntry(*run_itr_, index)->iv_size;
 }
 
+bool TrackRunIterator::ApplyConstantIv(size_t sample_index,
+                                       SampleEncryptionEntry* entry) const {
+  DCHECK(IsSampleEncrypted(sample_index));
+  uint32_t index = GetGroupDescriptionIndex(sample_index);
+  const uint8_t constant_iv_size =
+      index == 0
+          ? track_encryption().default_constant_iv_size
+          : GetSampleEncryptionInfoEntry(*run_itr_, index)->constant_iv_size;
+  RCHECK(constant_iv_size != 0);
+  const uint8_t* constant_iv =
+      index == 0 ? track_encryption().default_constant_iv
+                 : GetSampleEncryptionInfoEntry(*run_itr_, index)->constant_iv;
+  RCHECK(constant_iv != nullptr);
+  SbMemoryCopy(entry->initialization_vector, constant_iv,
+               kInitializationVectorSize);
+  return true;
+}
+
 }  // namespace mp4
 }  // namespace media
 }  // namespace cobalt
diff --git a/src/cobalt/media/formats/mp4/track_run_iterator.h b/src/cobalt/media/formats/mp4/track_run_iterator.h
index f78d6b8..d916ca4 100644
--- a/src/cobalt/media/formats/mp4/track_run_iterator.h
+++ b/src/cobalt/media/formats/mp4/track_run_iterator.h
@@ -90,6 +90,7 @@
 
  private:
   void ResetRun();
+  const ProtectionSchemeInfo& protection_scheme_info() const;
   const TrackEncryption& track_encryption() const;
 
   uint32_t GetGroupDescriptionIndex(uint32_t sample_index) const;
@@ -98,6 +99,7 @@
   bool IsSampleEncrypted(size_t sample_index) const;
   uint8_t GetIvSize(size_t sample_index) const;
   const std::vector<uint8_t>& GetKeyId(size_t sample_index) const;
+  bool ApplyConstantIv(size_t sample_index, SampleEncryptionEntry* entry) const;
 
   const Movie* moov_;
   scoped_refptr<MediaLog> media_log_;
diff --git a/src/cobalt/media/formats/mp4/track_run_iterator_unittest.cc b/src/cobalt/media/formats/mp4/track_run_iterator_unittest.cc
index 7ebbce9..2858a7f 100644
--- a/src/cobalt/media/formats/mp4/track_run_iterator_unittest.cc
+++ b/src/cobalt/media/formats/mp4/track_run_iterator_unittest.cc
@@ -97,6 +97,46 @@
     0x74, 0x43, 0x65, 0x6e, 0x63, 0x53, 0x61, 0x6d,
 };
 
+// Sample encryption data for two samples, using constant IV (defined by 'tenc'
+// or sample group entry).
+const uint8_t kSampleEncryptionDataWithSubsamplesAndConstantIv[] = {
+    // Sample count.
+    0x00, 0x00, 0x00, 0x05,
+    // Sample 1: Subsample count.
+    0x00, 0x01,
+    // Sample 1: Subsample 1.
+    0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
+    // Sample 2: Subsample count.
+    0x00, 0x02,
+    // Sample 2: Subsample 1.
+    0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
+    // Sample 2: Subsample 2.
+    0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
+    // Sample 3: Subsample count.
+    0x00, 0x01,
+    // Sample 3: Subsample 1.
+    0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
+    // Sample 4: Subsample count.
+    0x00, 0x01,
+    // Sample 4: Subsample 1.
+    0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
+    // Sample 5: Subsample count.
+    0x00, 0x01,
+    // Sample 5: Subsample 1.
+    0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
+};
+
+// Size of these IVs are 16 bytes.
+const char kIv4[] = {
+    0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x34,
+    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
+};
+
+const char kIv5[] = {
+    0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x35,
+    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
+};
+
 }  // namespace
 
 namespace cobalt {
@@ -257,8 +297,7 @@
     return moof;
   }
 
-  // Update the first sample description of a Track to indicate encryption
-  void AddEncryption(Track* track) {
+  ProtectionSchemeInfo* GetProtectionSchemeInfoForTrack(Track* track) {
     SampleDescription* stsd =
         &track->media.information.sample_table.description;
     ProtectionSchemeInfo* sinf;
@@ -267,7 +306,12 @@
     } else {
       sinf = &stsd->audio_entries[0].sinf;
     }
+    return sinf;
+  }
 
+  // Update the first sample description of a Track to indicate CENC encryption
+  void AddEncryption(Track* track) {
+    ProtectionSchemeInfo* sinf = GetProtectionSchemeInfoForTrack(track);
     sinf->type.type = FOURCC_CENC;
     sinf->info.track_encryption.is_encrypted = true;
     sinf->info.track_encryption.default_iv_size = 8;
@@ -352,6 +396,70 @@
     }
   }
 
+  // Update the first sample description of a Track to indicate CBCS encryption
+  // with a constant IV and pattern.
+  void AddEncryptionCbcs(Track* track) {
+    ProtectionSchemeInfo* sinf = GetProtectionSchemeInfoForTrack(track);
+    sinf->type.type = FOURCC_CBCS;
+    sinf->info.track_encryption.is_encrypted = true;
+    sinf->info.track_encryption.default_iv_size = 0;
+    sinf->info.track_encryption.default_crypt_byte_block = 1;
+    sinf->info.track_encryption.default_skip_byte_block = 9;
+    sinf->info.track_encryption.default_constant_iv_size = 16;
+    SbMemoryCopy(sinf->info.track_encryption.default_constant_iv, kIv3, 16);
+    sinf->info.track_encryption.default_kid.assign(kKeyId,
+                                                   kKeyId + arraysize(kKeyId));
+  }
+
+  void AddConstantIvsToCencSampleGroup(Track* track, TrackFragment* frag) {
+    auto& track_cenc_group =
+        track->media.information.sample_table.sample_group_description;
+    track_cenc_group.entries[0].iv_size = 0;
+    track_cenc_group.entries[0].crypt_byte_block = 1;
+    track_cenc_group.entries[0].skip_byte_block = 9;
+    track_cenc_group.entries[0].constant_iv_size = 16;
+    SbMemoryCopy(track_cenc_group.entries[0].constant_iv, kIv4, 16);
+
+    frag->sample_group_description.entries[1].iv_size = 0;
+    frag->sample_group_description.entries[1].crypt_byte_block = 1;
+    frag->sample_group_description.entries[1].skip_byte_block = 9;
+    frag->sample_group_description.entries[1].constant_iv_size = 16;
+    SbMemoryCopy(frag->sample_group_description.entries[1].constant_iv, kIv5,
+                 16);
+    frag->sample_group_description.entries[2].iv_size = 0;
+    frag->sample_group_description.entries[2].crypt_byte_block = 1;
+    frag->sample_group_description.entries[2].skip_byte_block = 9;
+    frag->sample_group_description.entries[2].constant_iv_size = 16;
+    SbMemoryCopy(frag->sample_group_description.entries[2].constant_iv, kIv5,
+                 16);
+  }
+
+  void AddSampleEncryptionCbcs(TrackFragment* frag) {
+    frag->sample_encryption.use_subsample_encryption = true;
+    frag->sample_encryption.sample_encryption_data.assign(
+        kSampleEncryptionDataWithSubsamplesAndConstantIv,
+        kSampleEncryptionDataWithSubsamplesAndConstantIv +
+            arraysize(kSampleEncryptionDataWithSubsamplesAndConstantIv));
+
+    // Update sample sizes and aux info header.
+    frag->runs.resize(1);
+    frag->runs[0].sample_count = 5;
+    frag->auxiliary_offset.offsets.push_back(0);
+    frag->auxiliary_size.sample_count = 5;
+    // Update sample sizes to match with subsample entries above.
+    frag->runs[0].sample_sizes[0] = 3;
+    frag->runs[0].sample_sizes[1] = 10;
+    frag->runs[0].sample_sizes[2] = 3;
+    frag->runs[0].sample_sizes[3] = 3;
+    frag->runs[0].sample_sizes[4] = 3;
+    // Set aux info header.
+    frag->auxiliary_size.sample_info_sizes.push_back(16);
+    frag->auxiliary_size.sample_info_sizes.push_back(30);
+    frag->auxiliary_size.sample_info_sizes.push_back(16);
+    frag->auxiliary_size.sample_info_sizes.push_back(16);
+    frag->auxiliary_size.sample_info_sizes.push_back(16);
+  }
+
   bool InitMoofWithArbitraryAuxInfo(MovieFragment* moof) {
     // Add aux info header (equal sized aux info for every sample).
     for (uint32_t i = 0; i < moof->tracks.size(); ++i) {
@@ -876,6 +984,95 @@
   EXPECT_EQ("2 K P P P K P", KeyframeAndRAPInfo(iter_.get()));
 }
 
+TEST_F(TrackRunIteratorTest, DecryptConfigTestWithConstantIvNoAuxInfo) {
+  AddEncryptionCbcs(&moov_.tracks[1]);
+  iter_.reset(new TrackRunIterator(&moov_, media_log_));
+
+  MovieFragment moof = CreateFragment();
+
+  ASSERT_TRUE(iter_->Init(moof));
+
+  // The run for track 2 will be the second.
+  iter_->AdvanceRun();
+  EXPECT_EQ(iter_->track_id(), 2u);
+  EXPECT_TRUE(iter_->is_encrypted());
+  ASSERT_FALSE(iter_->AuxInfoNeedsToBeCached());
+  EXPECT_EQ(iter_->sample_offset(), 200);
+  std::unique_ptr<DecryptConfig> config = iter_->GetDecryptConfig();
+  EXPECT_EQ(
+      std::string(reinterpret_cast<const char*>(kKeyId), arraysize(kKeyId)),
+      config->key_id());
+  EXPECT_EQ(std::string(reinterpret_cast<const char*>(kIv3), arraysize(kIv3)),
+            config->iv());
+  EXPECT_TRUE(config->subsamples().empty());
+  iter_->AdvanceSample();
+  config = iter_->GetDecryptConfig();
+  EXPECT_EQ(
+      std::string(reinterpret_cast<const char*>(kKeyId), arraysize(kKeyId)),
+      config->key_id());
+  EXPECT_EQ(std::string(reinterpret_cast<const char*>(kIv3), arraysize(kIv3)),
+            config->iv());
+  EXPECT_TRUE(config->subsamples().empty());
+}
+
+TEST_F(TrackRunIteratorTest, DecryptConfigTestWithSampleGroupsAndConstantIv) {
+  // Add TrackEncryption Box.
+  AddEncryptionCbcs(&moov_.tracks[1]);
+
+  MovieFragment moof = CreateFragment();
+  AddSampleEncryptionCbcs(&moof.tracks[1]);
+
+  const SampleToGroupEntry kSampleToGroupTable[] = {
+      // Associated with the 2nd entry in fragment SampleGroupDescription Box.
+      {1, SampleToGroupEntry::kFragmentGroupDescriptionIndexBase + 2},
+      // Associated with the default values specified in TrackEncryption Box.
+      {1, 0},
+      // Associated with the 1st entry in fragment SampleGroupDescription Box.
+      {1, SampleToGroupEntry::kFragmentGroupDescriptionIndexBase + 1},
+      // Associated with the 1st entry in track SampleGroupDescription Box.
+      {1, 1}};
+  AddCencSampleGroup(&moov_.tracks[1], &moof.tracks[1], kSampleToGroupTable,
+                     arraysize(kSampleToGroupTable));
+  AddConstantIvsToCencSampleGroup(&moov_.tracks[1], &moof.tracks[1]);
+  iter_.reset(new TrackRunIterator(&moov_, media_log_));
+  ASSERT_TRUE(iter_->Init(moof));
+
+  // The run for track 2 will be the second.
+  iter_->AdvanceRun();
+
+  std::string track_encryption_iv(kIv3, kIv3 + arraysize(kIv3));
+  std::string track_cenc_sample_group_iv(kIv4, kIv4 + arraysize(kIv4));
+  std::string fragment_cenc_sample_group_iv(kIv5, kIv5 + arraysize(kIv5));
+
+  for (size_t i = 0; i < kSampleToGroupTable[0].sample_count; ++i) {
+    EXPECT_TRUE(iter_->is_encrypted());
+    EXPECT_EQ(fragment_cenc_sample_group_iv, iter_->GetDecryptConfig()->iv());
+    iter_->AdvanceSample();
+  }
+
+  for (size_t i = 0; i < kSampleToGroupTable[1].sample_count; ++i) {
+    EXPECT_TRUE(iter_->is_encrypted());
+    EXPECT_EQ(track_encryption_iv, iter_->GetDecryptConfig()->iv());
+    iter_->AdvanceSample();
+  }
+
+  for (size_t i = 0; i < kSampleToGroupTable[2].sample_count; ++i) {
+    EXPECT_FALSE(iter_->is_encrypted());
+    iter_->AdvanceSample();
+  }
+
+  for (size_t i = 0; i < kSampleToGroupTable[3].sample_count; ++i) {
+    EXPECT_TRUE(iter_->is_encrypted());
+    EXPECT_EQ(track_cenc_sample_group_iv, iter_->GetDecryptConfig()->iv());
+    iter_->AdvanceSample();
+  }
+
+  // The remaining samples should be associated with the default values
+  // specified in TrackEncryption Box.
+  EXPECT_TRUE(iter_->is_encrypted());
+  EXPECT_EQ(track_encryption_iv, iter_->GetDecryptConfig()->iv());
+}
+
 }  // namespace mp4
 }  // namespace media
 }  // namespace cobalt
diff --git a/src/cobalt/media/formats/webm/webm_crypto_helpers.cc b/src/cobalt/media/formats/webm/webm_crypto_helpers.cc
index 2aac502..485aec5 100644
--- a/src/cobalt/media/formats/webm/webm_crypto_helpers.cc
+++ b/src/cobalt/media/formats/webm/webm_crypto_helpers.cc
@@ -106,9 +106,6 @@
   const uint8_t signal_byte = data[0];
   int frame_offset = sizeof(signal_byte);
 
-  // Setting the DecryptConfig object of the buffer while leaving the
-  // initialization vector empty will tell the decryptor that the frame is
-  // unencrypted.
   std::string counter_block;
   std::vector<SubsampleEntry> subsample_entries;
 
@@ -148,9 +145,14 @@
     }
   }
 
-  decrypt_config->reset(new DecryptConfig(
-      std::string(reinterpret_cast<const char*>(key_id), key_id_size),
-      counter_block, subsample_entries));
+  if (counter_block.empty()) {
+    // If the frame is unencrypted the DecryptConfig object should be NULL.
+    decrypt_config->reset();
+  } else {
+    *decrypt_config = DecryptConfig::CreateCencConfig(
+        std::string(reinterpret_cast<const char*>(key_id), key_id_size),
+        counter_block, subsample_entries);
+  }
   *data_offset = frame_offset;
 
   return true;
diff --git a/src/cobalt/media/formats/webm/webm_crypto_helpers.h b/src/cobalt/media/formats/webm/webm_crypto_helpers.h
index e837baf..b6b4ee3 100644
--- a/src/cobalt/media/formats/webm/webm_crypto_helpers.h
+++ b/src/cobalt/media/formats/webm/webm_crypto_helpers.h
@@ -14,11 +14,11 @@
 namespace cobalt {
 namespace media {
 
-// Fills an initialized DecryptConfig, which can be sent to the Decryptor if
-// the stream has potentially encrypted frames. Also sets |data_offset| which
-// indicates where the encrypted data starts. Leaving the IV empty will tell
-// the decryptor that the frame is unencrypted. Returns true if |data| is valid,
-// false otherwise, in which case |decrypt_config| and |data_offset| will not be
+// Fills |decrypt_config|, which can be sent to the Decryptor if the stream
+// has potentially encrypted frames. Also sets |data_offset| which indicates
+// where the encrypted data starts. If the frame is unencrypted
+// |*decrypt_config| will be null. Returns true if |data| is valid, false
+// otherwise, in which case |decrypt_config| and |data_offset| will not be
 // changed. Current encrypted WebM request for comments specification is here
 // http://wiki.webmproject.org/encryption/webm-encryption-rfc
 bool MEDIA_EXPORT WebMCreateDecryptConfig(
diff --git a/src/cobalt/media/media.gyp b/src/cobalt/media/media.gyp
index 38b157f..7a710c3 100644
--- a/src/cobalt/media/media.gyp
+++ b/src/cobalt/media/media.gyp
@@ -61,6 +61,8 @@
         'base/demuxer_stream_provider.h',
         'base/drm_system.cc',
         'base/drm_system.h',
+        "base/encryption_pattern.cc",
+        "base/encryption_pattern.h",
         'base/encryption_scheme.cc',
         'base/encryption_scheme.h',
         'base/hdr_metadata.cc',
diff --git a/src/cobalt/media/sandbox/fuzzer_app.cc b/src/cobalt/media/sandbox/fuzzer_app.cc
index 18f58ae..97a5f14 100644
--- a/src/cobalt/media/sandbox/fuzzer_app.cc
+++ b/src/cobalt/media/sandbox/fuzzer_app.cc
@@ -17,6 +17,7 @@
 #include "base/files/file_util.h"
 #include "base/logging.h"
 #include "starboard/common/string.h"
+#include "starboard/configuration_constants.h"
 #include "starboard/directory.h"
 
 namespace cobalt {
@@ -114,17 +115,17 @@
   }
 
 #if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
-  std::vector<char> entry(SB_FILE_MAX_NAME);
+  std::vector<char> entry(kSbFileMaxName);
 
   while (SbDirectoryGetNext(directory, entry.data(), entry.size())) {
-    std::string file_name = path_name + SB_FILE_SEP_STRING + entry.data();
+    std::string file_name = path_name + kSbFileSepString + entry.data();
     AddFile(file_name, min_ratio, max_ratio, initial_seed);
   }
 #else   // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
   SbDirectoryEntry entry;
 
   while (SbDirectoryGetNext(directory, &entry)) {
-    std::string file_name = path_name + SB_FILE_SEP_STRING + entry.name;
+    std::string file_name = path_name + kSbFileSepString + entry.name;
     AddFile(file_name, min_ratio, max_ratio, initial_seed);
   }
 #endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
diff --git a/src/cobalt/media_capture/encoders/flac_audio_encoder.cc b/src/cobalt/media_capture/encoders/flac_audio_encoder.cc
index 32bf59f..0e45933 100644
--- a/src/cobalt/media_capture/encoders/flac_audio_encoder.cc
+++ b/src/cobalt/media_capture/encoders/flac_audio_encoder.cc
@@ -49,7 +49,7 @@
   return match_iterator == mime_type_container.begin();
 }
 
-FlacAudioEncoder::FlacAudioEncoder() : thread_("flac_encoding_thread") {
+FlacAudioEncoder::FlacAudioEncoder() : thread_("FlacEncodingThd") {
   thread_.Start();
 }
 
diff --git a/src/cobalt/media_capture/media_recorder_test.cc b/src/cobalt/media_capture/media_recorder_test.cc
index 6317bfa..cfc5e3c 100644
--- a/src/cobalt/media_capture/media_recorder_test.cc
+++ b/src/cobalt/media_capture/media_recorder_test.cc
@@ -217,7 +217,7 @@
 
   media_recorder_->Start(&exception_state_);
 
-  base::Thread t("MediaStreamAudioSource thread");
+  base::Thread t("MediaStrmAudio");
   t.Start();
   // media_recorder_ is a ref-counted object, binding it to PushData that will
   // later be executed on another thread violates the thread-unsafe assumption
diff --git a/src/cobalt/renderer/backend/backend.gyp b/src/cobalt/renderer/backend/backend.gyp
index a3b5e85..edf8da0 100644
--- a/src/cobalt/renderer/backend/backend.gyp
+++ b/src/cobalt/renderer/backend/backend.gyp
@@ -29,5 +29,31 @@
         '<(DEPTH)/cobalt/renderer/backend/starboard/platform_backend.gyp:renderer_platform_backend',
       ],
     },
+    {
+      'target_name': 'graphics_system_test',
+      'type': '<(gtest_target_type)',
+      'sources': [
+        'graphics_system_test.cc',
+      ],
+      'dependencies': [
+        '<(DEPTH)/cobalt/base/base.gyp:base',
+        '<(DEPTH)/testing/gmock.gyp:gmock',
+        '<(DEPTH)/testing/gtest.gyp:gtest',
+        '<(DEPTH)/cobalt/renderer/backend/backend.gyp:renderer_backend',
+        '<(DEPTH)/cobalt/system_window/system_window.gyp:system_window',
+      ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
+    },
+    {
+      'target_name': 'graphics_system_test_deploy',
+      'type': 'none',
+      'dependencies': [
+        'graphics_system_test',
+      ],
+      'variables': {
+        'executable_name': 'graphics_system_test',
+      },
+      'includes': [ '<(DEPTH)/starboard/build/deploy.gypi' ],
+    },
   ],
 }
diff --git a/src/cobalt/renderer/backend/egl/resource_context.cc b/src/cobalt/renderer/backend/egl/resource_context.cc
index b8286fd..a6c8365 100644
--- a/src/cobalt/renderer/backend/egl/resource_context.cc
+++ b/src/cobalt/renderer/backend/egl/resource_context.cc
@@ -27,7 +27,7 @@
 namespace backend {
 
 ResourceContext::ResourceContext(EGLDisplay display, EGLConfig config)
-    : display_(display), config_(config), thread_("GL Texture Resource") {
+    : display_(display), config_(config), thread_("GLTextureResrc") {
   context_ = CreateGLES3Context(display, config, EGL_NO_CONTEXT);
 
   // Create a dummy EGLSurface object to be assigned as the target surface
diff --git a/src/cobalt/renderer/backend/graphics_system_test.cc b/src/cobalt/renderer/backend/graphics_system_test.cc
new file mode 100644
index 0000000..f6ad30c
--- /dev/null
+++ b/src/cobalt/renderer/backend/graphics_system_test.cc
@@ -0,0 +1,123 @@
+// Copyright 2020 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <algorithm>
+#include <memory>
+
+#include "base/optional.h"
+#include "cobalt/renderer/backend/default_graphics_system.h"
+#include "cobalt/renderer/backend/graphics_context.h"
+#include "cobalt/renderer/backend/graphics_system.h"
+#include "starboard/log.h"
+#include "starboard/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace renderer {
+namespace backend {
+
+namespace {
+
+// Number of initializations to use for measuring the reference creation time.
+const int kReferenceCount = 5;
+}  // namespace
+
+TEST(GraphicsSystemTest, GraphicsSystemCanBeInitializedOften) {
+  // Test whether the graphics system can be initialized often without slowing
+  // down.
+  std::unique_ptr<GraphicsSystem> graphics_system;
+
+  // Treat the first initialization as a 'warm up'.
+  graphics_system = CreateDefaultGraphicsSystem();
+  graphics_system.reset();
+
+  SbTimeMonotonic start = SbTimeGetMonotonicNow();
+  for (int i = 0; i < kReferenceCount; ++i) {
+    graphics_system = CreateDefaultGraphicsSystem();
+    graphics_system.reset();
+  }
+  SbTimeMonotonic time_per_initialization =
+      (SbTimeGetMonotonicNow() - start) / kReferenceCount;
+  SB_LOG(INFO) << "Measured duration "
+               << time_per_initialization / kSbTimeMillisecond
+               << "ms per initialization.";
+
+  // Graphics system initializations should not take more than the maximum of
+  // 200ms or three times as long as the time we just measured.
+  SbTimeMonotonic maximum_time_per_initialization =
+      std::max(3 * time_per_initialization, 200 * kSbTimeMillisecond);
+
+  SbTimeMonotonic last = SbTimeGetMonotonicNow();
+  for (int i = 0; i < 20; ++i) {
+    graphics_system = CreateDefaultGraphicsSystem();
+    graphics_system.reset();
+    SbTimeMonotonic now = SbTimeGetMonotonicNow();
+    SB_LOG(INFO) << "Test duration " << (now - last) / kSbTimeMillisecond
+                 << "ms.";
+    ASSERT_LT(now - last, maximum_time_per_initialization);
+    last = now;
+  }
+}
+
+TEST(GraphicsSystemTest, GraphicsContextCanBeInitializedOften) {
+  // Test whether the graphics system and graphics context can be initialized
+  // often without slowing down.
+  std::unique_ptr<GraphicsSystem> graphics_system;
+  std::unique_ptr<GraphicsContext> graphics_context;
+
+  // Treat the first initialization as a 'warm up'.
+  graphics_system = CreateDefaultGraphicsSystem();
+  graphics_context = graphics_system->CreateGraphicsContext();
+
+  graphics_context.reset();
+  graphics_system.reset();
+
+  SbTimeMonotonic start = SbTimeGetMonotonicNow();
+  for (int i = 0; i < kReferenceCount; ++i) {
+    graphics_system = CreateDefaultGraphicsSystem();
+    graphics_context = graphics_system->CreateGraphicsContext();
+
+    graphics_context.reset();
+    graphics_system.reset();
+  }
+  SbTimeMonotonic time_per_initialization = kSbTimeMillisecond +
+      (SbTimeGetMonotonicNow() - start) / kReferenceCount;
+  SB_LOG(INFO) << "Measured duration "
+               << time_per_initialization / kSbTimeMillisecond
+               << "ms per initialization.";
+
+  // Graphics system and context initializations should not take more than the
+  // maximum of 200ms or three times as long as the time we just measured.
+  SbTimeMonotonic maximum_time_per_initialization =
+      std::max(3 * time_per_initialization, 200 * kSbTimeMillisecond);
+
+  SbTimeMonotonic last = SbTimeGetMonotonicNow();
+  for (int i = 0; i < 20; ++i) {
+    graphics_system = CreateDefaultGraphicsSystem();
+    graphics_context = graphics_system->CreateGraphicsContext();
+
+    graphics_context.reset();
+    graphics_system.reset();
+
+    SbTimeMonotonic now = SbTimeGetMonotonicNow();
+    SB_LOG(INFO) << "Test duration " << (now - last) / kSbTimeMillisecond
+                 << "ms.";
+    ASSERT_LT(now - last, maximum_time_per_initialization);
+    last = now;
+  }
+}
+
+}  // namespace backend
+}  // namespace renderer
+}  // namespace cobalt
diff --git a/src/cobalt/renderer/pipeline.cc b/src/cobalt/renderer/pipeline.cc
index e2dad23..50e58cf 100644
--- a/src/cobalt/renderer/pipeline.cc
+++ b/src/cobalt/renderer/pipeline.cc
@@ -38,6 +38,12 @@
 namespace renderer {
 
 namespace {
+#if !defined(COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS)
+// This default value has been moved from cobalt/build/cobalt_configuration.gypi
+// in favor of the usage of
+// CobaltExtensionGraphicsApi::GetMinimumFrameIntervalInMilliseconds API.
+const float kCobaltMinimumFrameTimeInMilliseconds = 16.0f;
+#endif
 // How quickly the renderer time adjusts to changing submission times.
 // 500ms is chosen as a default because it is fast enough that the user will not
 // usually notice input lag from a slow timeline renderer, but slow enough that
@@ -84,7 +90,7 @@
       render_target_(render_target),
       graphics_context_(graphics_context),
       rasterizer_thread_("Rasterizer"),
-      submission_disposal_thread_("Rasterizer Submission Disposal"),
+      submission_disposal_thread_("RasterzrSubDisp"),
       submit_even_if_render_tree_is_unchanged_(
           submit_even_if_render_tree_is_unchanged),
       last_did_rasterize_(false),
@@ -116,6 +122,10 @@
           "The most recent time animations started playing."),
       animations_end_time_("Time.Renderer.Rasterize.Animations.End", 0,
                            "The most recent time animations ended playing."),
+      fallback_rasterize_count_(
+          "Count.Renderer.Rasterize.FallbackRasterize", 0,
+          "Total number of times Skia was used to render a "
+          "non-text render tree node."),
 #if defined(ENABLE_DEBUGGER)
       ALLOW_THIS_IN_INITIALIZER_LIST(dump_current_render_tree_command_handler_(
           "dump_render_tree",
@@ -302,12 +312,33 @@
         graphics_context_
             ? graphics_context_->GetMinimumFrameIntervalInMilliseconds()
             : -1.0f;
+#if SB_API_VERSION >= SB_COBALT_MINIMUM_FRAME_TIME_DEPRECATED_VERSION && \
+    defined(COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS)
+#error \
+    "'cobalt_minimum_frame_time_in_milliseconds' was replaced by" \
+    "CobaltExtensionGraphicsApi::GetMinimumFrameIntervalInMilliseconds."
+#elif SB_API_VERSION < SB_COBALT_MINIMUM_FRAME_TIME_DEPRECATED_VERSION && \
+    defined(COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS)
     COMPILE_ASSERT(COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS > 0,
                    frame_time_must_be_positive);
     if (minimum_frame_interval_milliseconds < 0.0f) {
       minimum_frame_interval_milliseconds =
           COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS;
+    } else {
+      DLOG(ERROR) <<
+          "COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS and "
+          "CobaltExtensionGraphicsApi::GetMinimumFrameIntervalInMilliseconds"
+          "are both defined."
+          "Remove the 'cobalt_minimum_frame_time_in_milliseconds' ";
+          "from ../gyp_configuration.gypi in favor of the usage of "
+          "CobaltExtensionGraphicsApi::GetMinimumFrameIntervalInMilliseconds."
     }
+#else
+    if (minimum_frame_interval_milliseconds < 0.0f) {
+      minimum_frame_interval_milliseconds =
+          kCobaltMinimumFrameTimeInMilliseconds;
+    }
+#endif
     DCHECK(minimum_frame_interval_milliseconds > 0.0f);
     rasterize_timer_.emplace(
         FROM_HERE,
@@ -452,6 +483,8 @@
     ++new_render_tree_rasterize_count_;
     new_render_tree_rasterize_time_ = end_time.ToInternalValue();
   }
+
+  fallback_rasterize_count_ = rasterizer_->GetFallbackRasterizeCount();
 }
 
 bool Pipeline::RasterizeSubmissionToRenderTarget(
diff --git a/src/cobalt/renderer/pipeline.h b/src/cobalt/renderer/pipeline.h
index edd931d..4fc3bf0 100644
--- a/src/cobalt/renderer/pipeline.h
+++ b/src/cobalt/renderer/pipeline.h
@@ -272,6 +272,10 @@
   base::CValCollectionTimerStats<base::CValPublic>
       rasterize_animations_interval_timer_;
 
+  // The total number of times Skia was used to render
+  // a non-text render tree node.
+  base::CVal<int64, base::CValPublic> fallback_rasterize_count_;
+
   // The total number of new render trees that have been rasterized.
   base::CVal<int, base::CValPublic> new_render_tree_rasterize_count_;
   // The last time that a newly encountered render tree was first rasterized.
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_border.cc b/src/cobalt/renderer/rasterizer/egl/draw_rect_border.cc
index aac1ea6..4e01a57 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_border.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_border.cc
@@ -22,6 +22,7 @@
 #include "cobalt/math/transform_2d.h"
 #include "cobalt/renderer/backend/egl/utils.h"
 #include "cobalt/renderer/egl_and_gles.h"
+#include "cobalt/renderer/rasterizer/common/utils.h"
 #include "egl/generated_shader_impl.h"
 #include "starboard/memory.h"
 
@@ -184,13 +185,15 @@
   math::RectF outer_rect(border_rect);
   math::RectF inner_rect(content_rect);
   outer_rect.Inset(insets.Scale(0.5f * pixel_size_x, 0.5f * pixel_size_y));
-  if (draw_content_rect_) {
+  const bool content_is_visible =
+      !common::utils::IsTransparent(content_color.a());
+  if (draw_content_rect_ && content_is_visible) {
     inner_rect.Inset(insets.Scale(-0.5f * pixel_size_x, -0.5f * pixel_size_y));
   }
   math::RectF outer_outer(outer_rect);
   math::RectF inner_inner(inner_rect);
   outer_outer.Inset(insets.Scale(-pixel_size_x, -pixel_size_y));
-  if (draw_content_rect_) {
+  if (draw_content_rect_ && content_is_visible) {
     inner_inner.Inset(insets.Scale(pixel_size_x, pixel_size_y));
   }
 
@@ -202,7 +205,7 @@
   uint16_t inner_rect_verts = static_cast<uint16_t>(attributes_.size());
   AddRectVertices(inner_rect, GetGLRGBA(border_color));
   uint16_t inner_inner_verts = inner_rect_verts;
-  if (draw_content_rect_) {
+  if (draw_content_rect_ && content_is_visible) {
     inner_inner_verts = static_cast<uint16_t>(attributes_.size());
     AddRectVertices(inner_inner, GetGLRGBA(content_color));
   }
@@ -210,12 +213,12 @@
   // Add indices to draw the borders using the vertex attributes added.
   AddBorders(border, outer_outer_verts, outer_rect_verts);
   AddBorders(border, outer_rect_verts, inner_rect_verts);
-  if (draw_content_rect_) {
+  if (draw_content_rect_ && content_is_visible) {
     AddBorders(border, inner_rect_verts, inner_inner_verts);
   }
 
   // Draw the content rect as appropriate.
-  if (draw_content_rect_ && content_color.a() > 0.0f) {
+  if (draw_content_rect_ && content_is_visible) {
     AddRectIndices(inner_inner_verts, inner_inner_verts + 1,
                    inner_inner_verts + 2, inner_inner_verts + 3);
   }
diff --git a/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc
index 4d4828a..b86cedf 100644
--- a/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc
@@ -68,6 +68,8 @@
     return fallback_rasterizer_->GetResourceProvider();
   }
 
+  int64_t GetFallbackRasterizeCount() { return fallback_rasterize_count_; }
+
   void MakeCurrent() { graphics_context_->MakeCurrent(); }
   void ReleaseContext() { graphics_context_->ReleaseCurrentContext(); }
 
@@ -92,6 +94,8 @@
   std::unique_ptr<ShaderProgramManager> shader_program_manager_;
   std::unique_ptr<OffscreenTargetManager> offscreen_target_manager_;
 
+  int64_t fallback_rasterize_count_;
+
   backend::GraphicsContextEGL* graphics_context_;
   THREAD_CHECKER(thread_checker_);
 };
@@ -108,6 +112,7 @@
           skia_cache_size_in_bytes, scratch_surface_cache_size_in_bytes,
           purge_skia_font_caches_on_destruction,
           force_deterministic_rendering)),
+      fallback_rasterize_count_(0),
       graphics_context_(
           base::polymorphic_downcast<backend::GraphicsContextEGL*>(
               graphics_context)) {
@@ -254,6 +259,8 @@
     render_tree->Accept(&visitor);
   }
 
+  fallback_rasterize_count_ += visitor.GetFallbackRasterizeCount();
+
   graphics_state_->BeginFrame();
 
   // Rasterize to offscreen targets using skia.
@@ -341,6 +348,10 @@
   return impl_->GetResourceProvider();
 }
 
+int64_t HardwareRasterizer::GetFallbackRasterizeCount() {
+  return impl_->GetFallbackRasterizeCount();
+}
+
 void HardwareRasterizer::MakeCurrent() { return impl_->MakeCurrent(); }
 
 void HardwareRasterizer::ReleaseContext() { return impl_->ReleaseContext(); }
diff --git a/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.h b/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.h
index 131e452..749282b 100644
--- a/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.h
+++ b/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.h
@@ -65,6 +65,8 @@
 
   render_tree::ResourceProvider* GetResourceProvider() override;
 
+  int64_t GetFallbackRasterizeCount() override;
+
   void MakeCurrent() override;
   void ReleaseContext() override;
 
diff --git a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
index e830635..d6bbdae 100644
--- a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
+++ b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
@@ -319,7 +319,8 @@
       fallback_render_target_(fallback_render_target),
       render_target_(render_target),
       onscreen_render_target_(render_target),
-      last_draw_id_(0) {
+      last_draw_id_(0),
+      fallback_rasterize_count_(0) {
   draw_state_.scissor.Intersect(content_rect);
 }
 
@@ -825,6 +826,10 @@
   FallbackRasterize(text_node);
 }
 
+int64_t RenderTreeNodeVisitor::GetFallbackRasterizeCount() {
+  return fallback_rasterize_count_;
+}
+
 // Get a scratch texture row region for use in rendering |node|.
 void RenderTreeNodeVisitor::GetScratchTexture(
     scoped_refptr<render_tree::Node> node, float size,
@@ -909,6 +914,10 @@
   OffscreenTargetManager::TargetInfo target_info;
   math::RectF content_rect;
 
+  if (node->GetTypeId() != base::GetTypeId<render_tree::TextNode>()) {
+      ++fallback_rasterize_count_;
+  }
+
   // Retrieve the previously cached contents or try to allocate a cached
   // render target for the node.
   bool content_is_cached = false;
diff --git a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.h b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.h
index 4a33257..69fd81e 100644
--- a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.h
+++ b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.h
@@ -82,6 +82,8 @@
   void Visit(render_tree::RectShadowNode* shadow_node) override;
   void Visit(render_tree::TextNode* text_node) override;
 
+  int64_t GetFallbackRasterizeCount();
+
  private:
   void GetScratchTexture(scoped_refptr<render_tree::Node> node, float size,
                          DrawObject::TextureInfo* out_texture_info);
@@ -121,6 +123,8 @@
   backend::RenderTarget* render_target_;
   backend::RenderTarget* onscreen_render_target_;
 
+  int64_t fallback_rasterize_count_;
+
   uint32_t last_draw_id_;
 };
 
diff --git a/src/cobalt/renderer/rasterizer/pixel_test_fixture.cc b/src/cobalt/renderer/rasterizer/pixel_test_fixture.cc
index 17519cf..e7e38ca 100644
--- a/src/cobalt/renderer/rasterizer/pixel_test_fixture.cc
+++ b/src/cobalt/renderer/rasterizer/pixel_test_fixture.cc
@@ -21,6 +21,7 @@
 #include "base/files/file_util.h"
 #include "base/path_service.h"
 #include "cobalt/base/cobalt_paths.h"
+#include "cobalt/renderer/backend/default_graphics_system.h"
 
 namespace cobalt {
 namespace renderer {
@@ -57,7 +58,7 @@
 // kOutputAllTestDetails switches are set).
 base::FilePath GetTestOutputDirectory() {
   base::FilePath out_file_dir;
-  base::PathService::Get(cobalt::paths::DIR_COBALT_TEST_OUT, &out_file_dir);
+  base::PathService::Get(paths::DIR_COBALT_TEST_OUT, &out_file_dir);
   out_file_dir = out_file_dir.Append(FILE_PATH_LITERAL("cobalt"))
                      .Append(FILE_PATH_LITERAL("renderer"))
                      .Append(FILE_PATH_LITERAL("rasterizer"))
@@ -93,15 +94,33 @@
 
   pixel_tester_.emplace(math::Size(kTestSurfaceWidth, kTestSurfaceHeight),
                         GetTestInputDirectory(), GetTestOutputDirectory(),
-                        pixel_tester_options);
+                        graphics_context_, pixel_tester_options);
 
   output_surface_size_ = math::Size(kTestSurfaceWidth, kTestSurfaceHeight);
 }
 
 PixelTest::~PixelTest() {}
 
-void PixelTest::TestTree(
-    const scoped_refptr<cobalt::render_tree::Node>& test_tree) {
+// static
+backend::GraphicsSystem* PixelTest::graphics_system_ = nullptr;
+// static
+backend::GraphicsContext* PixelTest::graphics_context_ = nullptr;
+
+// static
+void PixelTest::SetUpTestCase() {
+  graphics_system_ = backend::CreateDefaultGraphicsSystem().release();
+  graphics_context_ = graphics_system_->CreateGraphicsContext().release();
+}
+
+// static
+void PixelTest::TearDownTestCase() {
+  delete graphics_context_;
+  graphics_context_ = nullptr;
+  delete graphics_system_;
+  graphics_system_ = nullptr;
+}
+
+void PixelTest::TestTree(const scoped_refptr<render_tree::Node>& test_tree) {
   // First extract the current test's name into a std::string so that we
   // can use it to load and save image files associated with this test.
   std::string current_test_name(
diff --git a/src/cobalt/renderer/rasterizer/pixel_test_fixture.h b/src/cobalt/renderer/rasterizer/pixel_test_fixture.h
index 690f550..279bfcf 100644
--- a/src/cobalt/renderer/rasterizer/pixel_test_fixture.h
+++ b/src/cobalt/renderer/rasterizer/pixel_test_fixture.h
@@ -19,6 +19,8 @@
 #include "base/optional.h"
 #include "cobalt/math/size_f.h"
 #include "cobalt/render_tree/node.h"
+#include "cobalt/renderer/backend/graphics_context.h"
+#include "cobalt/renderer/backend/graphics_system.h"
 #include "cobalt/renderer/render_tree_pixel_tester.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -33,13 +35,16 @@
   PixelTest();
   ~PixelTest();
 
+  static void SetUpTestCase();
+  static void TearDownTestCase();
+
  protected:
   // This function will be executed by individual tests.  The current test's
   // name will be used to identify and load the relevant test data expected
   // output image from disk.  Additionally the current test's name will be used
   // to determine the names of output files when a rebase is requested, or when
   // an error mask image is requested.
-  void TestTree(const scoped_refptr<cobalt::render_tree::Node>& test_tree);
+  void TestTree(const scoped_refptr<render_tree::Node>& test_tree);
 
   const math::Size& output_surface_size() const { return output_surface_size_; }
 
@@ -47,6 +52,9 @@
     return pixel_tester_->GetResourceProvider();
   }
 
+  static backend::GraphicsSystem* graphics_system_;
+  static backend::GraphicsContext* graphics_context_;
+
  private:
   base::Optional<RenderTreePixelTester> pixel_tester_;
   math::Size output_surface_size_;
diff --git a/src/cobalt/renderer/rasterizer/rasterizer.h b/src/cobalt/renderer/rasterizer/rasterizer.h
index a935789..e176bcc 100644
--- a/src/cobalt/renderer/rasterizer/rasterizer.h
+++ b/src/cobalt/renderer/rasterizer/rasterizer.h
@@ -80,6 +80,11 @@
   // subsequently submitted to this pipeline.  This call must be thread-safe.
   virtual render_tree::ResourceProvider* GetResourceProvider() = 0;
 
+  // Returns the total number of times Skia was used to render a non-text
+  // render tree node. For Blitter and Skia rasterizer, this value should always
+  // be 0.
+  virtual int64_t GetFallbackRasterizeCount() { return 0; }
+
   // Helper class to allow one to create a RAII object that will acquire the
   // current context upon construction and release it upon destruction.
   class ScopedMakeCurrent {
diff --git a/src/cobalt/renderer/rasterizer/stress_test.cc b/src/cobalt/renderer/rasterizer/stress_test.cc
index f06322f..d5ab9da 100644
--- a/src/cobalt/renderer/rasterizer/stress_test.cc
+++ b/src/cobalt/renderer/rasterizer/stress_test.cc
@@ -59,25 +59,46 @@
  public:
   StressTest();
 
+  static void SetUpTestCase();
+  static void TearDownTestCase();
+
   void TestTree(const Size& output_size, scoped_refptr<Node> tree);
   render_tree::ResourceProvider* GetResourceProvider() const {
     return rasterizer_->GetResourceProvider();
   }
 
  protected:
-  std::unique_ptr<backend::GraphicsSystem> graphics_system_;
-  std::unique_ptr<backend::GraphicsContext> graphics_context_;
   std::unique_ptr<rasterizer::Rasterizer> rasterizer_;
   scoped_refptr<backend::RenderTarget> render_target_;
+
+  static backend::GraphicsSystem* graphics_system_;
+  static backend::GraphicsContext* graphics_context_;
 };
 
 StressTest::StressTest() {
-  graphics_system_ = backend::CreateDefaultGraphicsSystem();
-  graphics_context_ = graphics_system_->CreateGraphicsContext();
   // Create the rasterizer using the platform default RenderModule options.
   RendererModule::Options render_module_options;
   rasterizer_ = render_module_options.create_rasterizer_function.Run(
-      graphics_context_.get(), render_module_options);
+      graphics_context_, render_module_options);
+}
+
+// static
+backend::GraphicsSystem* StressTest::graphics_system_ = nullptr;
+// static
+backend::GraphicsContext* StressTest::graphics_context_ = nullptr;
+
+// static
+void StressTest::SetUpTestCase() {
+  graphics_system_ = backend::CreateDefaultGraphicsSystem().release();
+  graphics_context_ = graphics_system_->CreateGraphicsContext().release();
+}
+
+// static
+void StressTest::TearDownTestCase() {
+  delete graphics_context_;
+  graphics_context_ = nullptr;
+  delete graphics_system_;
+  graphics_system_ = nullptr;
 }
 
 void StressTest::TestTree(const Size& output_size, scoped_refptr<Node> tree) {
diff --git a/src/cobalt/renderer/render_tree_pixel_tester.cc b/src/cobalt/renderer/render_tree_pixel_tester.cc
index f019294..ecd2b7f 100644
--- a/src/cobalt/renderer/render_tree_pixel_tester.cc
+++ b/src/cobalt/renderer/render_tree_pixel_tester.cc
@@ -20,9 +20,6 @@
 
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
-#include "cobalt/renderer/backend/default_graphics_system.h"
-#include "cobalt/renderer/backend/graphics_context.h"
-#include "cobalt/renderer/backend/graphics_system.h"
 #include "cobalt/renderer/backend/render_target.h"
 #include "cobalt/renderer/rasterizer/rasterizer.h"
 #include "cobalt/renderer/renderer_module.h"
@@ -37,9 +34,6 @@
 #undef CreateDirectory
 
 using cobalt::render_tree::ResourceProvider;
-using cobalt::renderer::backend::GraphicsContext;
-using cobalt::renderer::backend::GraphicsSystem;
-using cobalt::renderer::backend::RenderTarget;
 using cobalt::renderer::test::png_utils::DecodePNGToRGBA;
 using cobalt::renderer::test::png_utils::EncodeRGBAToPNG;
 
@@ -55,13 +49,12 @@
 RenderTreePixelTester::RenderTreePixelTester(
     const math::Size& test_surface_dimensions,
     const base::FilePath& expected_results_directory,
-    const base::FilePath& output_directory, const Options& options)
-    : expected_results_directory_(expected_results_directory),
+    const base::FilePath& output_directory,
+    backend::GraphicsContext* graphics_context, const Options& options)
+    : graphics_context_(graphics_context),
+      expected_results_directory_(expected_results_directory),
       output_directory_(output_directory),
       options_(options) {
-  graphics_system_ = cobalt::renderer::backend::CreateDefaultGraphicsSystem();
-  graphics_context_ = graphics_system_->CreateGraphicsContext();
-
   // Create our offscreen surface that will be the target of our test
   // rasterizations.
   test_surface_ = graphics_context_->CreateDownloadableOffscreenRenderTarget(
@@ -75,7 +68,7 @@
   render_module_options.purge_skia_font_caches_on_destruction = false;
 
   rasterizer_ = render_module_options.create_rasterizer_function.Run(
-      graphics_context_.get(), render_module_options);
+      graphics_context_, render_module_options);
 }
 
 RenderTreePixelTester::~RenderTreePixelTester() {}
diff --git a/src/cobalt/renderer/render_tree_pixel_tester.h b/src/cobalt/renderer/render_tree_pixel_tester.h
index d2b2510..976a4ca 100644
--- a/src/cobalt/renderer/render_tree_pixel_tester.h
+++ b/src/cobalt/renderer/render_tree_pixel_tester.h
@@ -22,7 +22,6 @@
 #include "cobalt/math/size.h"
 #include "cobalt/render_tree/node.h"
 #include "cobalt/renderer/backend/graphics_context.h"
-#include "cobalt/renderer/backend/graphics_system.h"
 #include "cobalt/renderer/backend/render_target.h"
 #include "cobalt/renderer/rasterizer/rasterizer.h"
 
@@ -68,6 +67,7 @@
   RenderTreePixelTester(const math::Size& test_surface_dimensions,
                         const base::FilePath& expected_results_directory,
                         const base::FilePath& output_directory,
+                        backend::GraphicsContext* graphics_context,
                         const Options& options);
   ~RenderTreePixelTester();
 
@@ -94,10 +94,9 @@
   const math::Size& GetTargetSize() const { return test_surface_->GetSize(); }
 
  private:
-  std::unique_ptr<cobalt::renderer::backend::GraphicsSystem> graphics_system_;
-  std::unique_ptr<cobalt::renderer::backend::GraphicsContext> graphics_context_;
-  std::unique_ptr<cobalt::renderer::rasterizer::Rasterizer> rasterizer_;
-  scoped_refptr<cobalt::renderer::backend::RenderTarget> test_surface_;
+  backend::GraphicsContext* graphics_context_;
+  std::unique_ptr<rasterizer::Rasterizer> rasterizer_;
+  scoped_refptr<backend::RenderTarget> test_surface_;
 
   base::FilePath expected_results_directory_;
   base::FilePath output_directory_;
diff --git a/src/cobalt/renderer/renderer.gyp b/src/cobalt/renderer/renderer.gyp
index b9b59e9..8447cd5 100644
--- a/src/cobalt/renderer/renderer.gyp
+++ b/src/cobalt/renderer/renderer.gyp
@@ -40,9 +40,7 @@
         'submission_queue.cc',
         'submission_queue.h',
       ],
-      'defines': [
-        'COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS=<(cobalt_minimum_frame_time_in_milliseconds)',
-      ],
+
       'includes': [
         'renderer_parameters_setup.gypi',
       ],
@@ -64,6 +62,11 @@
             '<(default_renderer_options_dependency)',
           ],
         }],
+        ['cobalt_minimum_frame_time_in_milliseconds != -1', {
+          'defines': [
+            'COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS=<(cobalt_minimum_frame_time_in_milliseconds)',
+          ],
+        }],
       ],
     },
 
@@ -135,6 +138,5 @@
       },
       'includes': [ '<(DEPTH)/starboard/build/deploy.gypi' ],
     },
-
   ],
 }
diff --git a/src/cobalt/renderer/resource_provider_test.cc b/src/cobalt/renderer/resource_provider_test.cc
index 1f2786c..1f73889 100644
--- a/src/cobalt/renderer/resource_provider_test.cc
+++ b/src/cobalt/renderer/resource_provider_test.cc
@@ -136,11 +136,13 @@
   ResourceProviderTest();
   ~ResourceProviderTest();
 
+  static void SetUpTestCase();
+  static void TearDownTestCase();
+
   // Lets the fixture know that it will run the run loop manually, and the
   // ResourceProviderTest destructor does not need to run it.
   void SetWillRunRunLoopManually() { run_run_loop_manually_ = true; }
 
-
   void Quit() {
     message_loop_.task_runner()->PostTask(FROM_HERE, run_loop_.QuitClosure());
   }
@@ -148,23 +150,21 @@
  protected:
   base::MessageLoop message_loop_;
   base::RunLoop run_loop_;
-  std::unique_ptr<backend::GraphicsSystem> graphics_system_;
-  std::unique_ptr<backend::GraphicsContext> graphics_context_;
   std::unique_ptr<rasterizer::Rasterizer> rasterizer_;
 
   bool run_run_loop_manually_;
+
+  static backend::GraphicsSystem* graphics_system_;
+  static backend::GraphicsContext* graphics_context_;
 };
 
 ResourceProviderTest::ResourceProviderTest()
     : message_loop_(base::MessageLoop::TYPE_DEFAULT),
       run_run_loop_manually_(false) {
-  graphics_system_ = backend::CreateDefaultGraphicsSystem();
-  graphics_context_ = graphics_system_->CreateGraphicsContext();
-
   // Create the rasterizer using the platform default RenderModule options.
   RendererModule::Options render_module_options;
   rasterizer_ = render_module_options.create_rasterizer_function.Run(
-      graphics_context_.get(), render_module_options);
+      graphics_context_, render_module_options);
 }
 
 ResourceProviderTest::~ResourceProviderTest() {
@@ -174,6 +174,25 @@
   }
 }
 
+// static
+backend::GraphicsSystem* ResourceProviderTest::graphics_system_ = nullptr;
+// static
+backend::GraphicsContext* ResourceProviderTest::graphics_context_ = nullptr;
+
+// static
+void ResourceProviderTest::SetUpTestCase() {
+  graphics_system_ = backend::CreateDefaultGraphicsSystem().release();
+  graphics_context_ = graphics_system_->CreateGraphicsContext().release();
+}
+
+// static
+void ResourceProviderTest::TearDownTestCase() {
+  delete graphics_context_;
+  graphics_context_ = nullptr;
+  delete graphics_system_;
+  graphics_system_ = nullptr;
+}
+
 // The following test ensures that any thread can successfully create render
 // tree images both at the same time and also while a graphics frame is started.
 // This might be a problem in an OpenGL implementation for instance if we
diff --git a/src/cobalt/script/mozjs-45/mozjs.cc b/src/cobalt/script/mozjs-45/mozjs.cc
index 9a12c85..d9c6fba 100644
--- a/src/cobalt/script/mozjs-45/mozjs.cc
+++ b/src/cobalt/script/mozjs-45/mozjs.cc
@@ -98,7 +98,8 @@
       }
     }
   } else {
-    standalone_runner.RunInteractive();
+    while (standalone_runner.RunInteractive()) {
+    }
   }
 
   return 0;
diff --git a/src/cobalt/script/mozjs-45/union_type_conversion_impl.h b/src/cobalt/script/mozjs-45/union_type_conversion_impl.h
index 6585ff8..52d1281 100644
--- a/src/cobalt/script/mozjs-45/union_type_conversion_impl.h
+++ b/src/cobalt/script/mozjs-45/union_type_conversion_impl.h
@@ -54,10 +54,10 @@
                  script::UnionType2<T1, T2>* out_union) {
   DCHECK_EQ(0, conversion_flags);
   // JS -> IDL type conversion procedure described here:
-  // http://heycam.github.io/webidl/#es-union
+  // https://www.w3.org/TR/WebIDL-1/#es-union
+  // ( Draft: http://heycam.github.io/webidl/#es-union )
 
-  // TODO: Support Date, RegExp, DOMException, Error, ArrayBuffer, DataView,
-  //       TypedArrayName, callback functions, dictionary.
+  // TODO: Support Date, RegExp, DOMException, Error, callback functions.
 
   // 1. If the union type includes a nullable type and |V| is null or undefined,
   // then return the IDL value null.
@@ -79,7 +79,7 @@
   T1 t1;
   T2 t2;
 
-  // 4. If |V| is a platform object, then:
+  // 3. If |V| is a platform object, then:
   //   1. If |types| includes an interface type that V implements, then return
   //      the IDL value that is a reference to the object |V|.
   //   2. If |types| includes object, then return the IDL value that is a
@@ -118,7 +118,92 @@
     }
   }
 
-  // 11. If |Type(V)| is Object, then:
+  // 4. If |V| object, then
+  // 1. If |types| includes object, then return the IDL value that is a
+  // reference to
+  //      the object V.
+  // Not implemented
+
+  // 5. If V is a DOMException platform object, then:
+  // 1. If types includes DOMException or Error, then return the result of
+  // converting
+  //      V to that type.
+  // Not implemented
+
+  // 6. If V is a native Error object (that is, it has an ErrorData internal
+  // slot),
+  //    then:
+  // 1. If types includes Error, then return the result of converting V to
+  // Error.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  // Not implemented
+
+  // 7. If V is an object with an ArrayBufferData internal slot, then:
+  // 1. If types includes ArrayBuffer, then return the result of converting V
+  // to
+  //      ArrayBuffer.
+  // 2. If types includes object, then return the IDL value that is a reference
+  //      to the object V.
+  // Not implemented
+
+  // 8. If V is an object with a DataView internal slot, then:
+  // 1. If types includes DataView, then return the result of converting V to
+  // DataView.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  //   Note: Only ArrayBufferView is implemented, other DataViews are not.
+  if (value.isObject() && JS_IsArrayBufferViewObject(&value.toObject())) {
+    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType2<T1, T2>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType2<T1, T2>(t2);
+      return;
+    }
+  }
+
+  // 9. If V is an object with a TypedArrayName internal slot, then:
+  // 1. If types includes a typed array type whose name is the value of V’s
+  // TypedArrayName  //      internal slot, then return the result of
+  // converting V to that type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 10. If IsCallable(V) is true, then
+  // 1. If types includes a callback function type, then return the result of
+  // converting V to
+  //      that callback function type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 11. If |V| is null or undefined object, then:
+  // 1. If types includes a dictionary type, then return the result of
+  // converting V to
+  //      that dictionary type.
+  if (value.isObject()) {
+    if (UnionTypeTraitsT1::is_dictionary_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType2<T1, T2>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_dictionary_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType2<T1, T2>(t2);
+      return;
+    }
+  }
+
+  // 12. If |Type(V)| is Object, then:
   //   1. If |types| includes a sequence type, then
   //     1. Let |method| be the result of GetMethod(V, @@iterator)
   //     2. ReturnIfAbrupt(method)
@@ -138,7 +223,7 @@
     }
   }
 
-  // 12. If |Type(V)| is Boolean, then:
+  // 13. If |Type(V)| is Boolean, then:
   // 1. If |types| includes a boolean, then return the result of converting |V|
   //      to boolean.
   if (value.isBoolean()) {
@@ -155,7 +240,7 @@
     }
   }
 
-  // 13. If |Type(V)| is a Number, then:
+  // 14. If |Type(V)| is a Number, then:
   //   1. If |types| includes a numeric type, then return the result of
   //      converting |V| to that numeric type.
   if (value.isNumber()) {
@@ -172,7 +257,7 @@
     }
   }
 
-  // 14. If |types| includes a string type, then return the result of converting
+  // 15. If |types| includes a string type, then return the result of converting
   //     |V| to that type.
   if (value.isString()) {
     if (UnionTypeTraitsT1::is_string_type) {
@@ -188,7 +273,7 @@
     }
   }
 
-  // 15. If |types| includes a numeric type, then return the result of
+  // 16. If |types| includes a numeric type, then return the result of
   //     converting |V| to that numeric type.
   if (UnionTypeTraitsT1::is_numeric_type) {
     FromJSValue(context, value, conversion_flags, exception_state, &t1);
@@ -202,7 +287,7 @@
     return;
   }
 
-  // 16. If |types| includes a boolean, then return the result of converting |V|
+  // 17. If |types| includes a boolean, then return the result of converting |V|
   //     to boolean.
   if (UnionTypeTraitsT1::is_boolean_type) {
     FromJSValue(context, value, conversion_flags, exception_state, &t1);
@@ -216,24 +301,8 @@
     return;
   }
 
-  // 17. If |types| includes an ArrayBufferView type, then return the result of
-  //     converting |V| to ArrayBufferView type.
-  //     This step has to be before 18 to catch array_buffer_view types.
-  if (value.isObject() && JS_IsArrayBufferViewObject(&value.toObject())) {
-    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
-      FromJSValue(context, value, conversion_flags, exception_state, &t1);
-      *out_union = script::UnionType2<T1, T2>(t1);
-      return;
-    }
-
-    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
-      FromJSValue(context, value, conversion_flags, exception_state, &t2);
-      *out_union = script::UnionType2<T1, T2>(t2);
-      return;
-    }
-  }
-
-  // 18. If |types| includes any ScriptValue type, then return the result of
+  // Note: non-spec fallback
+  //     If |types| includes any ScriptValue type, then return the result of
   //     converting |V| to that ScriptValue type.
   if (value.isObject() && JS_IsArrayBufferObject(&value.toObject())) {
     if (UnionTypeTraitsT1::is_script_value_type) {
@@ -249,7 +318,7 @@
     }
   }
 
-  // 19. Throw a TypeError.
+  // 18. Throw a TypeError.
   exception_state->SetSimpleException(kNotUnionType);
 }
 
@@ -279,10 +348,10 @@
                  script::UnionType3<T1, T2, T3>* out_union) {
   DCHECK_EQ(0, conversion_flags);
   // JS -> IDL type conversion procedure described here:
-  // http://heycam.github.io/webidl/#es-union
+  // https://www.w3.org/TR/WebIDL-1/#es-union
+  // ( Draft: http://heycam.github.io/webidl/#es-union )
 
-  // TODO: Support Date, RegExp, DOMException, Error, ArrayBuffer, DataView,
-  //       TypedArrayName, callback functions, dictionary.
+  // TODO: Support Date, RegExp, DOMException, Error, callback functions.
 
   // 1. If the union type includes a nullable type and |V| is null or undefined,
   // then return the IDL value null.
@@ -306,7 +375,7 @@
   T2 t2;
   T3 t3;
 
-  // 4. If |V| is a platform object, then:
+  // 3. If |V| is a platform object, then:
   //   1. If |types| includes an interface type that V implements, then return
   //      the IDL value that is a reference to the object |V|.
   //   2. If |types| includes object, then return the IDL value that is a
@@ -353,7 +422,104 @@
     }
   }
 
-  // 11. If |Type(V)| is Object, then:
+  // 4. If |V| object, then
+  // 1. If |types| includes object, then return the IDL value that is a
+  // reference to
+  //      the object V.
+  // Not implemented
+
+  // 5. If V is a DOMException platform object, then:
+  // 1. If types includes DOMException or Error, then return the result of
+  // converting
+  //      V to that type.
+  // Not implemented
+
+  // 6. If V is a native Error object (that is, it has an ErrorData internal
+  // slot),
+  //    then:
+  // 1. If types includes Error, then return the result of converting V to
+  // Error.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  // Not implemented
+
+  // 7. If V is an object with an ArrayBufferData internal slot, then:
+  // 1. If types includes ArrayBuffer, then return the result of converting V
+  // to
+  //      ArrayBuffer.
+  // 2. If types includes object, then return the IDL value that is a reference
+  //      to the object V.
+  // Not implemented
+
+  // 8. If V is an object with a DataView internal slot, then:
+  // 1. If types includes DataView, then return the result of converting V to
+  // DataView.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  //   Note: Only ArrayBufferView is implemented, other DataViews are not.
+  if (value.isObject() && JS_IsArrayBufferViewObject(&value.toObject())) {
+    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType3<T1, T2, T3>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType3<T1, T2, T3>(t2);
+      return;
+    }
+
+    if (UnionTypeTraitsT3::is_array_buffer_view_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t3);
+      *out_union = script::UnionType3<T1, T2, T3>(t3);
+      return;
+    }
+  }
+
+  // 9. If V is an object with a TypedArrayName internal slot, then:
+  // 1. If types includes a typed array type whose name is the value of V’s
+  // TypedArrayName  //      internal slot, then return the result of
+  // converting V to that type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 10. If IsCallable(V) is true, then
+  // 1. If types includes a callback function type, then return the result of
+  // converting V to
+  //      that callback function type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 11. If |V| is null or undefined object, then:
+  // 1. If types includes a dictionary type, then return the result of
+  // converting V to
+  //      that dictionary type.
+  if (value.isObject()) {
+    if (UnionTypeTraitsT1::is_dictionary_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType3<T1, T2, T3>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_dictionary_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType3<T1, T2, T3>(t2);
+      return;
+    }
+
+    if (UnionTypeTraitsT3::is_dictionary_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t3);
+      *out_union = script::UnionType3<T1, T2, T3>(t3);
+      return;
+    }
+  }
+
+  // 12. If |Type(V)| is Object, then:
   //   1. If |types| includes a sequence type, then
   //     1. Let |method| be the result of GetMethod(V, @@iterator)
   //     2. ReturnIfAbrupt(method)
@@ -379,7 +545,7 @@
     }
   }
 
-  // 12. If |Type(V)| is Boolean, then:
+  // 13. If |Type(V)| is Boolean, then:
   // 1. If |types| includes a boolean, then return the result of converting |V|
   //      to boolean.
   if (value.isBoolean()) {
@@ -402,7 +568,7 @@
     }
   }
 
-  // 13. If |Type(V)| is a Number, then:
+  // 14. If |Type(V)| is a Number, then:
   //   1. If |types| includes a numeric type, then return the result of
   //      converting |V| to that numeric type.
   if (value.isNumber()) {
@@ -425,7 +591,7 @@
     }
   }
 
-  // 14. If |types| includes a string type, then return the result of converting
+  // 15. If |types| includes a string type, then return the result of converting
   //     |V| to that type.
   if (value.isString()) {
     if (UnionTypeTraitsT1::is_string_type) {
@@ -447,7 +613,7 @@
     }
   }
 
-  // 15. If |types| includes a numeric type, then return the result of
+  // 16. If |types| includes a numeric type, then return the result of
   //     converting |V| to that numeric type.
   if (UnionTypeTraitsT1::is_numeric_type) {
     FromJSValue(context, value, conversion_flags, exception_state, &t1);
@@ -467,7 +633,7 @@
     return;
   }
 
-  // 16. If |types| includes a boolean, then return the result of converting |V|
+  // 17. If |types| includes a boolean, then return the result of converting |V|
   //     to boolean.
   if (UnionTypeTraitsT1::is_boolean_type) {
     FromJSValue(context, value, conversion_flags, exception_state, &t1);
@@ -487,30 +653,8 @@
     return;
   }
 
-  // 17. If |types| includes an ArrayBufferView type, then return the result of
-  //     converting |V| to ArrayBufferView type.
-  //     This step has to be before 18 to catch array_buffer_view types.
-  if (value.isObject() && JS_IsArrayBufferViewObject(&value.toObject())) {
-    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
-      FromJSValue(context, value, conversion_flags, exception_state, &t1);
-      *out_union = script::UnionType3<T1, T2, T3>(t1);
-      return;
-    }
-
-    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
-      FromJSValue(context, value, conversion_flags, exception_state, &t2);
-      *out_union = script::UnionType3<T1, T2, T3>(t2);
-      return;
-    }
-
-    if (UnionTypeTraitsT3::is_array_buffer_view_type) {
-      FromJSValue(context, value, conversion_flags, exception_state, &t3);
-      *out_union = script::UnionType3<T1, T2, T3>(t3);
-      return;
-    }
-  }
-
-  // 18. If |types| includes any ScriptValue type, then return the result of
+  // Note: non-spec fallback
+  //     If |types| includes any ScriptValue type, then return the result of
   //     converting |V| to that ScriptValue type.
   if (value.isObject() && JS_IsArrayBufferObject(&value.toObject())) {
     if (UnionTypeTraitsT1::is_script_value_type) {
@@ -532,7 +676,7 @@
     }
   }
 
-  // 19. Throw a TypeError.
+  // 18. Throw a TypeError.
   exception_state->SetSimpleException(kNotUnionType);
 }
 
@@ -566,10 +710,10 @@
                  script::UnionType4<T1, T2, T3, T4>* out_union) {
   DCHECK_EQ(0, conversion_flags);
   // JS -> IDL type conversion procedure described here:
-  // http://heycam.github.io/webidl/#es-union
+  // https://www.w3.org/TR/WebIDL-1/#es-union
+  // ( Draft: http://heycam.github.io/webidl/#es-union )
 
-  // TODO: Support Date, RegExp, DOMException, Error, ArrayBuffer, DataView,
-  //       TypedArrayName, callback functions, dictionary.
+  // TODO: Support Date, RegExp, DOMException, Error, callback functions.
 
   // 1. If the union type includes a nullable type and |V| is null or undefined,
   // then return the IDL value null.
@@ -595,7 +739,7 @@
   T3 t3;
   T4 t4;
 
-  // 4. If |V| is a platform object, then:
+  // 3. If |V| is a platform object, then:
   //   1. If |types| includes an interface type that V implements, then return
   //      the IDL value that is a reference to the object |V|.
   //   2. If |types| includes object, then return the IDL value that is a
@@ -650,7 +794,116 @@
     }
   }
 
-  // 11. If |Type(V)| is Object, then:
+  // 4. If |V| object, then
+  // 1. If |types| includes object, then return the IDL value that is a
+  // reference to
+  //      the object V.
+  // Not implemented
+
+  // 5. If V is a DOMException platform object, then:
+  // 1. If types includes DOMException or Error, then return the result of
+  // converting
+  //      V to that type.
+  // Not implemented
+
+  // 6. If V is a native Error object (that is, it has an ErrorData internal
+  // slot),
+  //    then:
+  // 1. If types includes Error, then return the result of converting V to
+  // Error.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  // Not implemented
+
+  // 7. If V is an object with an ArrayBufferData internal slot, then:
+  // 1. If types includes ArrayBuffer, then return the result of converting V
+  // to
+  //      ArrayBuffer.
+  // 2. If types includes object, then return the IDL value that is a reference
+  //      to the object V.
+  // Not implemented
+
+  // 8. If V is an object with a DataView internal slot, then:
+  // 1. If types includes DataView, then return the result of converting V to
+  // DataView.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  //   Note: Only ArrayBufferView is implemented, other DataViews are not.
+  if (value.isObject() && JS_IsArrayBufferViewObject(&value.toObject())) {
+    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t2);
+      return;
+    }
+
+    if (UnionTypeTraitsT3::is_array_buffer_view_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t3);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t3);
+      return;
+    }
+
+    if (UnionTypeTraitsT4::is_array_buffer_view_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t4);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t4);
+      return;
+    }
+  }
+
+  // 9. If V is an object with a TypedArrayName internal slot, then:
+  // 1. If types includes a typed array type whose name is the value of V’s
+  // TypedArrayName  //      internal slot, then return the result of
+  // converting V to that type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 10. If IsCallable(V) is true, then
+  // 1. If types includes a callback function type, then return the result of
+  // converting V to
+  //      that callback function type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 11. If |V| is null or undefined object, then:
+  // 1. If types includes a dictionary type, then return the result of
+  // converting V to
+  //      that dictionary type.
+  if (value.isObject()) {
+    if (UnionTypeTraitsT1::is_dictionary_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_dictionary_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t2);
+      return;
+    }
+
+    if (UnionTypeTraitsT3::is_dictionary_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t3);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t3);
+      return;
+    }
+
+    if (UnionTypeTraitsT4::is_dictionary_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t4);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t4);
+      return;
+    }
+  }
+
+  // 12. If |Type(V)| is Object, then:
   //   1. If |types| includes a sequence type, then
   //     1. Let |method| be the result of GetMethod(V, @@iterator)
   //     2. ReturnIfAbrupt(method)
@@ -682,7 +935,7 @@
     }
   }
 
-  // 12. If |Type(V)| is Boolean, then:
+  // 13. If |Type(V)| is Boolean, then:
   // 1. If |types| includes a boolean, then return the result of converting |V|
   //      to boolean.
   if (value.isBoolean()) {
@@ -711,7 +964,7 @@
     }
   }
 
-  // 13. If |Type(V)| is a Number, then:
+  // 14. If |Type(V)| is a Number, then:
   //   1. If |types| includes a numeric type, then return the result of
   //      converting |V| to that numeric type.
   if (value.isNumber()) {
@@ -740,7 +993,7 @@
     }
   }
 
-  // 14. If |types| includes a string type, then return the result of converting
+  // 15. If |types| includes a string type, then return the result of converting
   //     |V| to that type.
   if (value.isString()) {
     if (UnionTypeTraitsT1::is_string_type) {
@@ -768,7 +1021,7 @@
     }
   }
 
-  // 15. If |types| includes a numeric type, then return the result of
+  // 16. If |types| includes a numeric type, then return the result of
   //     converting |V| to that numeric type.
   if (UnionTypeTraitsT1::is_numeric_type) {
     FromJSValue(context, value, conversion_flags, exception_state, &t1);
@@ -794,7 +1047,7 @@
     return;
   }
 
-  // 16. If |types| includes a boolean, then return the result of converting |V|
+  // 17. If |types| includes a boolean, then return the result of converting |V|
   //     to boolean.
   if (UnionTypeTraitsT1::is_boolean_type) {
     FromJSValue(context, value, conversion_flags, exception_state, &t1);
@@ -820,36 +1073,8 @@
     return;
   }
 
-  // 17. If |types| includes an ArrayBufferView type, then return the result of
-  //     converting |V| to ArrayBufferView type.
-  //     This step has to be before 18 to catch array_buffer_view types.
-  if (value.isObject() && JS_IsArrayBufferViewObject(&value.toObject())) {
-    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
-      FromJSValue(context, value, conversion_flags, exception_state, &t1);
-      *out_union = script::UnionType4<T1, T2, T3, T4>(t1);
-      return;
-    }
-
-    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
-      FromJSValue(context, value, conversion_flags, exception_state, &t2);
-      *out_union = script::UnionType4<T1, T2, T3, T4>(t2);
-      return;
-    }
-
-    if (UnionTypeTraitsT3::is_array_buffer_view_type) {
-      FromJSValue(context, value, conversion_flags, exception_state, &t3);
-      *out_union = script::UnionType4<T1, T2, T3, T4>(t3);
-      return;
-    }
-
-    if (UnionTypeTraitsT4::is_array_buffer_view_type) {
-      FromJSValue(context, value, conversion_flags, exception_state, &t4);
-      *out_union = script::UnionType4<T1, T2, T3, T4>(t4);
-      return;
-    }
-  }
-
-  // 18. If |types| includes any ScriptValue type, then return the result of
+  // Note: non-spec fallback
+  //     If |types| includes any ScriptValue type, then return the result of
   //     converting |V| to that ScriptValue type.
   if (value.isObject() && JS_IsArrayBufferObject(&value.toObject())) {
     if (UnionTypeTraitsT1::is_script_value_type) {
@@ -877,7 +1102,7 @@
     }
   }
 
-  // 19. Throw a TypeError.
+  // 18. Throw a TypeError.
   exception_state->SetSimpleException(kNotUnionType);
 }
 
@@ -915,10 +1140,10 @@
                  script::UnionType5<T1, T2, T3, T4, T5>* out_union) {
   DCHECK_EQ(0, conversion_flags);
   // JS -> IDL type conversion procedure described here:
-  // http://heycam.github.io/webidl/#es-union
+  // https://www.w3.org/TR/WebIDL-1/#es-union
+  // ( Draft: http://heycam.github.io/webidl/#es-union )
 
-  // TODO: Support Date, RegExp, DOMException, Error, ArrayBuffer, DataView,
-  //       TypedArrayName, callback functions, dictionary.
+  // TODO: Support Date, RegExp, DOMException, Error, callback functions.
 
   // 1. If the union type includes a nullable type and |V| is null or undefined,
   // then return the IDL value null.
@@ -946,7 +1171,7 @@
   T4 t4;
   T5 t5;
 
-  // 4. If |V| is a platform object, then:
+  // 3. If |V| is a platform object, then:
   //   1. If |types| includes an interface type that V implements, then return
   //      the IDL value that is a reference to the object |V|.
   //   2. If |types| includes object, then return the IDL value that is a
@@ -1009,7 +1234,128 @@
     }
   }
 
-  // 11. If |Type(V)| is Object, then:
+  // 4. If |V| object, then
+  // 1. If |types| includes object, then return the IDL value that is a
+  // reference to
+  //      the object V.
+  // Not implemented
+
+  // 5. If V is a DOMException platform object, then:
+  // 1. If types includes DOMException or Error, then return the result of
+  // converting
+  //      V to that type.
+  // Not implemented
+
+  // 6. If V is a native Error object (that is, it has an ErrorData internal
+  // slot),
+  //    then:
+  // 1. If types includes Error, then return the result of converting V to
+  // Error.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  // Not implemented
+
+  // 7. If V is an object with an ArrayBufferData internal slot, then:
+  // 1. If types includes ArrayBuffer, then return the result of converting V
+  // to
+  //      ArrayBuffer.
+  // 2. If types includes object, then return the IDL value that is a reference
+  //      to the object V.
+  // Not implemented
+
+  // 8. If V is an object with a DataView internal slot, then:
+  // 1. If types includes DataView, then return the result of converting V to
+  // DataView.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  //   Note: Only ArrayBufferView is implemented, other DataViews are not.
+  if (value.isObject() && JS_IsArrayBufferViewObject(&value.toObject())) {
+    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t2);
+      return;
+    }
+
+    if (UnionTypeTraitsT3::is_array_buffer_view_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t3);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t3);
+      return;
+    }
+
+    if (UnionTypeTraitsT4::is_array_buffer_view_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t4);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t4);
+      return;
+    }
+
+    if (UnionTypeTraitsT5::is_array_buffer_view_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t5);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t5);
+      return;
+    }
+  }
+
+  // 9. If V is an object with a TypedArrayName internal slot, then:
+  // 1. If types includes a typed array type whose name is the value of V’s
+  // TypedArrayName  //      internal slot, then return the result of
+  // converting V to that type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 10. If IsCallable(V) is true, then
+  // 1. If types includes a callback function type, then return the result of
+  // converting V to
+  //      that callback function type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 11. If |V| is null or undefined object, then:
+  // 1. If types includes a dictionary type, then return the result of
+  // converting V to
+  //      that dictionary type.
+  if (value.isObject()) {
+    if (UnionTypeTraitsT1::is_dictionary_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_dictionary_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t2);
+      return;
+    }
+
+    if (UnionTypeTraitsT3::is_dictionary_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t3);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t3);
+      return;
+    }
+
+    if (UnionTypeTraitsT4::is_dictionary_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t4);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t4);
+      return;
+    }
+
+    if (UnionTypeTraitsT5::is_dictionary_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t5);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t5);
+      return;
+    }
+  }
+
+  // 12. If |Type(V)| is Object, then:
   //   1. If |types| includes a sequence type, then
   //     1. Let |method| be the result of GetMethod(V, @@iterator)
   //     2. ReturnIfAbrupt(method)
@@ -1047,7 +1393,7 @@
     }
   }
 
-  // 12. If |Type(V)| is Boolean, then:
+  // 13. If |Type(V)| is Boolean, then:
   // 1. If |types| includes a boolean, then return the result of converting |V|
   //      to boolean.
   if (value.isBoolean()) {
@@ -1082,7 +1428,7 @@
     }
   }
 
-  // 13. If |Type(V)| is a Number, then:
+  // 14. If |Type(V)| is a Number, then:
   //   1. If |types| includes a numeric type, then return the result of
   //      converting |V| to that numeric type.
   if (value.isNumber()) {
@@ -1117,7 +1463,7 @@
     }
   }
 
-  // 14. If |types| includes a string type, then return the result of converting
+  // 15. If |types| includes a string type, then return the result of converting
   //     |V| to that type.
   if (value.isString()) {
     if (UnionTypeTraitsT1::is_string_type) {
@@ -1151,7 +1497,7 @@
     }
   }
 
-  // 15. If |types| includes a numeric type, then return the result of
+  // 16. If |types| includes a numeric type, then return the result of
   //     converting |V| to that numeric type.
   if (UnionTypeTraitsT1::is_numeric_type) {
     FromJSValue(context, value, conversion_flags, exception_state, &t1);
@@ -1183,7 +1529,7 @@
     return;
   }
 
-  // 16. If |types| includes a boolean, then return the result of converting |V|
+  // 17. If |types| includes a boolean, then return the result of converting |V|
   //     to boolean.
   if (UnionTypeTraitsT1::is_boolean_type) {
     FromJSValue(context, value, conversion_flags, exception_state, &t1);
@@ -1215,42 +1561,8 @@
     return;
   }
 
-  // 17. If |types| includes an ArrayBufferView type, then return the result of
-  //     converting |V| to ArrayBufferView type.
-  //     This step has to be before 18 to catch array_buffer_view types.
-  if (value.isObject() && JS_IsArrayBufferViewObject(&value.toObject())) {
-    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
-      FromJSValue(context, value, conversion_flags, exception_state, &t1);
-      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t1);
-      return;
-    }
-
-    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
-      FromJSValue(context, value, conversion_flags, exception_state, &t2);
-      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t2);
-      return;
-    }
-
-    if (UnionTypeTraitsT3::is_array_buffer_view_type) {
-      FromJSValue(context, value, conversion_flags, exception_state, &t3);
-      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t3);
-      return;
-    }
-
-    if (UnionTypeTraitsT4::is_array_buffer_view_type) {
-      FromJSValue(context, value, conversion_flags, exception_state, &t4);
-      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t4);
-      return;
-    }
-
-    if (UnionTypeTraitsT5::is_array_buffer_view_type) {
-      FromJSValue(context, value, conversion_flags, exception_state, &t5);
-      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t5);
-      return;
-    }
-  }
-
-  // 18. If |types| includes any ScriptValue type, then return the result of
+  // Note: non-spec fallback
+  //     If |types| includes any ScriptValue type, then return the result of
   //     converting |V| to that ScriptValue type.
   if (value.isObject() && JS_IsArrayBufferObject(&value.toObject())) {
     if (UnionTypeTraitsT1::is_script_value_type) {
@@ -1284,7 +1596,7 @@
     }
   }
 
-  // 19. Throw a TypeError.
+  // 18. Throw a TypeError.
   exception_state->SetSimpleException(kNotUnionType);
 }
 
diff --git a/src/cobalt/script/mozjs-45/union_type_conversion_impl.h.pump b/src/cobalt/script/mozjs-45/union_type_conversion_impl.h.pump
index 8208625..885c55e 100644
--- a/src/cobalt/script/mozjs-45/union_type_conversion_impl.h.pump
+++ b/src/cobalt/script/mozjs-45/union_type_conversion_impl.h.pump
@@ -66,10 +66,10 @@
                  script::UnionType$(NUM_MEMBERS)<$for TYPE , [[T$(TYPE)]]>* out_union) {
   DCHECK_EQ(0, conversion_flags);
   // JS -> IDL type conversion procedure described here:
-  // http://heycam.github.io/webidl/#es-union
+  // https://www.w3.org/TR/WebIDL-1/#es-union
+  // ( Draft: http://heycam.github.io/webidl/#es-union )
 
-  // TODO: Support Date, RegExp, DOMException, Error, ArrayBuffer, DataView,
-  //       TypedArrayName, callback functions, dictionary.
+  // TODO: Support Date, RegExp, DOMException, Error, callback functions.
 
   // 1. If the union type includes a nullable type and |V| is null or undefined,
   // then return the IDL value null.
@@ -97,7 +97,7 @@
 
 ]]
 
-  // 4. If |V| is a platform object, then:
+  // 3. If |V| is a platform object, then:
   //   1. If |types| includes an interface type that V implements, then return
   //      the IDL value that is a reference to the object |V|.
   //   2. If |types| includes object, then return the IDL value that is a
@@ -132,7 +132,75 @@
 ]]
   }
 
-  // 11. If |Type(V)| is Object, then:
+  // 4. If |V| object, then
+  //   1. If |types| includes object, then return the IDL value that is a reference to
+  //      the object V.
+  // Not implemented
+
+  // 5. If V is a DOMException platform object, then:
+  //   1. If types includes DOMException or Error, then return the result of converting
+  //      V to that type.
+  // Not implemented
+
+  // 6. If V is a native Error object (that is, it has an [[ErrorData]] internal slot),
+  //    then:
+  //   1. If types includes Error, then return the result of converting V to Error.
+  //   2. If types includes object, then return the IDL value that is a reference to
+  //      the object V.
+  // Not implemented
+
+  // 7. If V is an object with an [[ArrayBufferData]] internal slot, then:
+  //   1. If types includes ArrayBuffer, then return the result of converting V to
+  //      ArrayBuffer.
+  //   2. If types includes object, then return the IDL value that is a reference
+  //      to the object V.
+  // Not implemented
+
+  // 8. If V is an object with a [[DataView]] internal slot, then:
+  //   1. If types includes DataView, then return the result of converting V to DataView.
+  //   2. If types includes object, then return the IDL value that is a reference to
+  //      the object V.
+  //   Note: Only ArrayBufferView is implemented, other DataViews are not.
+  if (value.isObject() && JS_IsArrayBufferViewObject(&value.toObject())) {
+$for TYPE [[
+
+    if (UnionTypeTraitsT$(TYPE)::is_array_buffer_view_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t$(TYPE));
+      *out_union = script::UnionType$(NUM_MEMBERS)<$for TYPE , [[T$(TYPE)]]>(t$(TYPE));
+      return;
+    }
+
+]]
+  }
+
+  // 9. If V is an object with a [[TypedArrayName]] internal slot, then:
+  //   1. If types includes a typed array type whose name is the value of V’s [[TypedArrayName]]
+  //      internal slot, then return the result of converting V to that type.
+  //   2. If types includes object, then return the IDL value that is a reference to the object V.
+  // Not implemented
+
+  // 10. If IsCallable(V) is true, then
+  //   1. If types includes a callback function type, then return the result of converting V to
+  //      that callback function type.
+  //   2. If types includes object, then return the IDL value that is a reference to the object V.
+  // Not implemented
+
+  // 11. If |V| is null or undefined object, then:
+  //   1. If types includes a dictionary type, then return the result of converting V to
+  //      that dictionary type.
+  if (value.isObject()) {
+$for TYPE [[
+
+    if (UnionTypeTraitsT$(TYPE)::is_dictionary_type) {
+      FromJSValue(context, value, conversion_flags, exception_state, &t$(TYPE));
+      *out_union = script::UnionType$(NUM_MEMBERS)<$for TYPE , [[T$(TYPE)]]>(t$(TYPE));
+      return;
+    }
+
+]]
+  }
+
+  // 12. If |Type(V)| is Object, then:
   //   1. If |types| includes a sequence type, then
   //     1. Let |method| be the result of GetMethod(V, @@iterator)
   //     2. ReturnIfAbrupt(method)
@@ -150,7 +218,7 @@
 ]]
   }
 
-  // 12. If |Type(V)| is Boolean, then:
+  // 13. If |Type(V)| is Boolean, then:
   //   1. If |types| includes a boolean, then return the result of converting |V|
   //      to boolean.
   if (value.isBoolean()) {
@@ -165,7 +233,7 @@
 ]]
   }
 
-  // 13. If |Type(V)| is a Number, then:
+  // 14. If |Type(V)| is a Number, then:
   //   1. If |types| includes a numeric type, then return the result of
   //      converting |V| to that numeric type.
   if (value.isNumber()) {
@@ -180,7 +248,7 @@
 ]]
   }
 
-  // 14. If |types| includes a string type, then return the result of converting
+  // 15. If |types| includes a string type, then return the result of converting
   //     |V| to that type.
   if (value.isString()) {
 $for TYPE [[
@@ -194,7 +262,7 @@
 ]]
   }
 
-  // 15. If |types| includes a numeric type, then return the result of
+  // 16. If |types| includes a numeric type, then return the result of
   //     converting |V| to that numeric type.
 $for TYPE [[
 
@@ -206,7 +274,7 @@
 
 ]]
 
-  // 16. If |types| includes a boolean, then return the result of converting |V|
+  // 17. If |types| includes a boolean, then return the result of converting |V|
   //     to boolean.
 $for TYPE [[
 
@@ -218,22 +286,8 @@
 
 ]]
 
-  // 17. If |types| includes an ArrayBufferView type, then return the result of
-  //     converting |V| to ArrayBufferView type.
-  //     This step has to be before 18 to catch array_buffer_view types.
-  if (value.isObject() && JS_IsArrayBufferViewObject(&value.toObject())) {
-$for TYPE [[
-
-    if (UnionTypeTraitsT$(TYPE)::is_array_buffer_view_type) {
-      FromJSValue(context, value, conversion_flags, exception_state, &t$(TYPE));
-      *out_union = script::UnionType$(NUM_MEMBERS)<$for TYPE , [[T$(TYPE)]]>(t$(TYPE));
-      return;
-    }
-
-]]
-  }
-
-  // 18. If |types| includes any ScriptValue type, then return the result of
+  // Note: non-spec fallback
+  //     If |types| includes any ScriptValue type, then return the result of
   //     converting |V| to that ScriptValue type.
   if (value.isObject() && JS_IsArrayBufferObject(&value.toObject())) {
 $for TYPE [[
@@ -247,7 +301,7 @@
 ]]
   }
 
-  // 19. Throw a TypeError.
+  // 18. Throw a TypeError.
   exception_state->SetSimpleException(kNotUnionType);
 }
 
diff --git a/src/cobalt/script/standalone_javascript_runner.cc b/src/cobalt/script/standalone_javascript_runner.cc
index f833ee7..8f26b36 100644
--- a/src/cobalt/script/standalone_javascript_runner.cc
+++ b/src/cobalt/script/standalone_javascript_runner.cc
@@ -24,14 +24,16 @@
 namespace script {
 
 StandaloneJavascriptRunner::StandaloneJavascriptRunner(
-    const JavaScriptEngine::Options& javascript_engine_options) {
+    scoped_refptr<base::TaskRunner> task_runner,
+    const JavaScriptEngine::Options& javascript_engine_options)
+    : task_runner_(task_runner) {
   CommonInitialization(javascript_engine_options);
   global_environment_->CreateGlobalObject();
 }
 
-void StandaloneJavascriptRunner::RunInteractive() {
+bool StandaloneJavascriptRunner::RunInteractive() {
 #if defined(COBALT_LINUX)
-  while (!std::cin.eof() && std::cin.good()) {
+  if (!std::cin.eof() && std::cin.good()) {
     // Interactive prompt.
     std::cout << "> ";
 
@@ -41,11 +43,29 @@
     if (!line.empty()) {
       ExecuteAndPrintResult(base::SourceLocation("[stdin]", 1, 1), line);
     }
+    return true;
   }
-  std::cout << std::endl;
 #else
   NOTIMPLEMENTED();
 #endif
+  return false;
+}
+
+void StandaloneJavascriptRunner::RunUntilDone(
+    const base::Closure& quit_closure) {
+  DCHECK(task_runner_);
+  if (RunInteractive()) {
+    task_runner_->PostTask(FROM_HERE,
+                           BindOnce(&StandaloneJavascriptRunner::RunUntilDone,
+                                    base::Unretained(this), quit_closure));
+  } else {
+    Quit(quit_closure);
+  }
+}
+
+void StandaloneJavascriptRunner::Quit(const base::Closure& quit_closure) {
+  DCHECK(task_runner_);
+  task_runner_->PostTask(FROM_HERE, quit_closure);
 }
 
 void StandaloneJavascriptRunner::ExecuteFile(const base::FilePath& path) {
diff --git a/src/cobalt/script/standalone_javascript_runner.h b/src/cobalt/script/standalone_javascript_runner.h
index 65aa07e..b837f66 100644
--- a/src/cobalt/script/standalone_javascript_runner.h
+++ b/src/cobalt/script/standalone_javascript_runner.h
@@ -19,6 +19,7 @@
 #include <string>
 
 #include "base/files/file_path.h"
+#include "base/task_runner.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
@@ -32,21 +33,30 @@
 class StandaloneJavascriptRunner {
  public:
   StandaloneJavascriptRunner(
+      scoped_refptr<base::TaskRunner> task_runner = nullptr,
       const JavaScriptEngine::Options& javascript_engine_options =
           JavaScriptEngine::Options());
 
   template <typename GlobalInterface>
   StandaloneJavascriptRunner(
+      scoped_refptr<base::TaskRunner> task_runner,
       const JavaScriptEngine::Options& javascript_engine_options,
       const scoped_refptr<GlobalInterface>& global_object) {
+    task_runner_ = task_runner;
     CommonInitialization(javascript_engine_options);
     global_environment_->CreateGlobalObject(global_object,
                                             environment_settings_.get());
   }
 
-  // Executes input from stdin and echoes the result to stdout. Loops until EOF
+  // Executes input from stdin and echoes the result to stdout. True until EOF
   // is encountered (CTRL-D).
-  void RunInteractive();
+  bool RunInteractive();
+
+  // Run interactively in a message loop
+  void RunUntilDone(const base::Closure& quit_closure);
+
+  // Quit, when run in a message loop
+  void Quit(const base::Closure& quit_closure);
 
   // Read the file from disk and execute the script. Echos the result to stdout.
   void ExecuteFile(const base::FilePath& file);
@@ -64,6 +74,7 @@
   std::unique_ptr<JavaScriptEngine> engine_;
   std::unique_ptr<EnvironmentSettings> environment_settings_;
   scoped_refptr<GlobalEnvironment> global_environment_;
+  scoped_refptr<base::TaskRunner> task_runner_;
 };
 }  // namespace script
 }  // namespace cobalt
diff --git a/src/cobalt/script/union_type_internal.h b/src/cobalt/script/union_type_internal.h
index 9ce7041..54f6f17 100644
--- a/src/cobalt/script/union_type_internal.h
+++ b/src/cobalt/script/union_type_internal.h
@@ -46,6 +46,7 @@
   // ScriptValue<ArrayBufferView>* manually.
   static const bool is_array_buffer_view_type = false;
   static const bool is_script_value_type = false;
+  static const bool is_dictionary_type = false;
 };
 
 // Default traits for types with no specialization
@@ -62,6 +63,13 @@
   static const bool is_interface_type = true;
 };
 
+template <typename T>
+struct UnionTypeTraits<
+    T, typename std::enable_if<T::is_a_generated_dict::value>::type>
+    : UnionTypeDefaultTraits<T> {
+  static const bool is_dictionary_type = true;
+};
+
 template <>
 struct UnionTypeTraits<Handle<ArrayBufferView>>
     : UnionTypeDefaultTraits<Handle<ArrayBufferView>> {
diff --git a/src/cobalt/script/v8c/cobalt_platform.cc b/src/cobalt/script/v8c/cobalt_platform.cc
index 1060596..d7d7085 100644
--- a/src/cobalt/script/v8c/cobalt_platform.cc
+++ b/src/cobalt/script/v8c/cobalt_platform.cc
@@ -88,6 +88,7 @@
 
 void CobaltPlatform::RegisterIsolateOnThread(v8::Isolate* isolate,
                                              base::MessageLoop* message_loop) {
+  DCHECK(message_loop);
   auto task_runner = GetForegroundTaskRunner(isolate);
   CobaltPlatform::CobaltV8TaskRunner* cobalt_v8_task_runner =
       base::polymorphic_downcast<CobaltPlatform::CobaltV8TaskRunner*>(
diff --git a/src/cobalt/script/v8c/isolate_fellowship.cc b/src/cobalt/script/v8c/isolate_fellowship.cc
index 9e402e9..4906dc4 100644
--- a/src/cobalt/script/v8c/isolate_fellowship.cc
+++ b/src/cobalt/script/v8c/isolate_fellowship.cc
@@ -17,10 +17,12 @@
 #include <algorithm>
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/logging.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/script/v8c/v8c_tracing_controller.h"
+#include "starboard/configuration_constants.h"
 #include "starboard/file.h"
 
 namespace cobalt {
@@ -101,9 +103,9 @@
   TRACE_EVENT0("cobalt::script", "IsolateFellowship::InitializeStartupData");
   DCHECK(startup_data.data == nullptr);
 
-  char cache_path[SB_FILE_MAX_PATH] = {};
-  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, cache_path,
-                       SB_ARRAY_SIZE_INT(cache_path))) {
+  std::vector<char> cache_path(kSbFileMaxPath);
+  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, cache_path.data(),
+                       cache_path.size())) {
     // If there is no cache directory, then just save the startup data in
     // memory.
     LOG(WARNING) << "Unable to read/write V8 startup snapshot data to file.";
@@ -118,7 +120,7 @@
 
   // Attempt to read the cache file.
   std::string snapshot_file_full_path =
-      std::string(cache_path) + SB_FILE_SEP_STRING +
+      std::string(cache_path.data()) + kSbFileSepString +
       V8C_INTERNAL_STARTUP_DATA_CACHE_FILE_NAME;
   bool read_file = ([&]() {
     starboard::ScopedFile scoped_file(snapshot_file_full_path.c_str(),
diff --git a/src/cobalt/script/v8c/union_type_conversion_impl.h b/src/cobalt/script/v8c/union_type_conversion_impl.h
index afcb775..32602fa 100644
--- a/src/cobalt/script/v8c/union_type_conversion_impl.h
+++ b/src/cobalt/script/v8c/union_type_conversion_impl.h
@@ -54,10 +54,10 @@
                  script::UnionType2<T1, T2>* out_union) {
   DCHECK_EQ(0, conversion_flags);
   // JS -> IDL type conversion procedure described here:
-  // http://heycam.github.io/webidl/#es-union
+  // https://www.w3.org/TR/WebIDL-1/#es-union
+  // ( Draft: http://heycam.github.io/webidl/#es-union )
 
-  // TODO: Support Date, RegExp, DOMException, Error, callback functions,
-  // dictionary.
+  // TODO: Support Date, RegExp, DOMException, Error, callback functions.
 
   // 1. If the union type includes a nullable type and |V| is null or undefined,
   // then return the IDL value null.
@@ -79,7 +79,7 @@
   T1 t1;
   T2 t2;
 
-  // 4. If |V| is a platform object, then:
+  // 3. If |V| is a platform object, then:
   //   1. If |types| includes an interface type that V implements, then return
   //      the IDL value that is a reference to the object |V|.
   //   2. If |types| includes object, then return the IDL value that is a
@@ -116,7 +116,93 @@
     }
   }
 
-  // 11. If |Type(V)| is Object, then:
+  // 4. If |V| object, then
+  // 1. If |types| includes object, then return the IDL value that is a
+  // reference to
+  //      the object V.
+  // Not implemented
+
+  // 5. If V is a DOMException platform object, then:
+  // 1. If types includes DOMException or Error, then return the result of
+  // converting
+  //      V to that type.
+  // Not implemented
+
+  // 6. If V is a native Error object (that is, it has an ErrorData internal
+  // slot),
+  //    then:
+  // 1. If types includes Error, then return the result of converting V to
+  // Error.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  // Not implemented
+
+  // 7. If V is an object with an ArrayBufferData internal slot, then:
+  // 1. If types includes ArrayBuffer, then return the result of converting V
+  // to
+  //      ArrayBuffer.
+  // 2. If types includes object, then return the IDL value that is a reference
+  //      to the object V.
+  // Not implemented
+
+  // 8. If V is an object with a DataView internal slot, then:
+  // 1. If types includes DataView, then return the result of converting V to
+  // DataView.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  //   Note: Only ArrayBufferView is implemented, other DataViews are not.
+  // https://www.w3.org/TR/WebIDL-1/#idl-DataView
+  if (value->IsArrayBufferView()) {
+    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType2<T1, T2>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType2<T1, T2>(t2);
+      return;
+    }
+  }
+
+  // 9. If V is an object with a TypedArrayName internal slot, then:
+  // 1. If types includes a typed array type whose name is the value of V’s
+  // TypedArrayName  //      internal slot, then return the result of
+  // converting V to that type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 10. If IsCallable(V) is true, then
+  // 1. If types includes a callback function type, then return the result of
+  // converting V to
+  //      that callback function type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 11. If |V| is null or undefined object, then:
+  // 1. If types includes a dictionary type, then return the result of
+  // converting V to
+  //      that dictionary type.
+  if (value->IsObject()) {
+    if (UnionTypeTraitsT1::is_dictionary_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType2<T1, T2>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_dictionary_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType2<T1, T2>(t2);
+      return;
+    }
+  }
+
+  // 12. If |Type(V)| is Object, then:
   //   1. If |types| includes a sequence type, then
   //     1. Let |method| be the result of GetMethod(V, @@iterator)
   //     2. ReturnIfAbrupt(method)
@@ -136,7 +222,7 @@
     }
   }
 
-  // 12. If |Type(V)| is Boolean, then:
+  // 13. If |Type(V)| is Boolean, then:
   // 1. If |types| includes a boolean, then return the result of converting |V|
   //      to boolean.
   if (value->IsBoolean()) {
@@ -153,7 +239,7 @@
     }
   }
 
-  // 13. If |Type(V)| is a Number, then:
+  // 14. If |Type(V)| is a Number, then:
   //   1. If |types| includes a numeric type, then return the result of
   //      converting |V| to that numeric type.
   if (value->IsNumber()) {
@@ -170,7 +256,7 @@
     }
   }
 
-  // 14. If |types| includes a string type, then return the result of converting
+  // 15. If |types| includes a string type, then return the result of converting
   //     |V| to that type.
   if (value->IsString()) {
     if (UnionTypeTraitsT1::is_string_type) {
@@ -186,7 +272,7 @@
     }
   }
 
-  // 15. If |types| includes a numeric type, then return the result of
+  // 16. If |types| includes a numeric type, then return the result of
   //     converting |V| to that numeric type.
   if (UnionTypeTraitsT1::is_numeric_type) {
     FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
@@ -200,7 +286,7 @@
     return;
   }
 
-  // 16. If |types| includes a boolean, then return the result of converting |V|
+  // 17. If |types| includes a boolean, then return the result of converting |V|
   //     to boolean.
   if (UnionTypeTraitsT1::is_boolean_type) {
     FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
@@ -214,24 +300,8 @@
     return;
   }
 
-  // 17. If |types| includes an ArrayBufferView type, then return the result of
-  //     converting |V| to ArrayBufferView type.
-  //     This step has to be before 18 to catch array_buffer_view types.
-  if (value->IsArrayBufferView()) {
-    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
-      FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
-      *out_union = script::UnionType2<T1, T2>(t1);
-      return;
-    }
-
-    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
-      FromJSValue(isolate, value, conversion_flags, exception_state, &t2);
-      *out_union = script::UnionType2<T1, T2>(t2);
-      return;
-    }
-  }
-
-  // 18. If |types| includes any ScriptValue type, then return the result of
+  // Note: non-spec fallback
+  //     If |types| includes any ScriptValue type, then return the result of
   //     converting |V| to that ScriptValue type.
   if (value->IsArrayBuffer()) {
     if (UnionTypeTraitsT1::is_script_value_type) {
@@ -247,7 +317,7 @@
     }
   }
 
-  // 19. Throw a TypeError.
+  // 18. Throw a TypeError.
   exception_state->SetSimpleException(kNotUnionType);
 }
 
@@ -277,10 +347,10 @@
                  script::UnionType3<T1, T2, T3>* out_union) {
   DCHECK_EQ(0, conversion_flags);
   // JS -> IDL type conversion procedure described here:
-  // http://heycam.github.io/webidl/#es-union
+  // https://www.w3.org/TR/WebIDL-1/#es-union
+  // ( Draft: http://heycam.github.io/webidl/#es-union )
 
-  // TODO: Support Date, RegExp, DOMException, Error, callback functions,
-  // dictionary.
+  // TODO: Support Date, RegExp, DOMException, Error, callback functions.
 
   // 1. If the union type includes a nullable type and |V| is null or undefined,
   // then return the IDL value null.
@@ -304,7 +374,7 @@
   T2 t2;
   T3 t3;
 
-  // 4. If |V| is a platform object, then:
+  // 3. If |V| is a platform object, then:
   //   1. If |types| includes an interface type that V implements, then return
   //      the IDL value that is a reference to the object |V|.
   //   2. If |types| includes object, then return the IDL value that is a
@@ -349,7 +419,105 @@
     }
   }
 
-  // 11. If |Type(V)| is Object, then:
+  // 4. If |V| object, then
+  // 1. If |types| includes object, then return the IDL value that is a
+  // reference to
+  //      the object V.
+  // Not implemented
+
+  // 5. If V is a DOMException platform object, then:
+  // 1. If types includes DOMException or Error, then return the result of
+  // converting
+  //      V to that type.
+  // Not implemented
+
+  // 6. If V is a native Error object (that is, it has an ErrorData internal
+  // slot),
+  //    then:
+  // 1. If types includes Error, then return the result of converting V to
+  // Error.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  // Not implemented
+
+  // 7. If V is an object with an ArrayBufferData internal slot, then:
+  // 1. If types includes ArrayBuffer, then return the result of converting V
+  // to
+  //      ArrayBuffer.
+  // 2. If types includes object, then return the IDL value that is a reference
+  //      to the object V.
+  // Not implemented
+
+  // 8. If V is an object with a DataView internal slot, then:
+  // 1. If types includes DataView, then return the result of converting V to
+  // DataView.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  //   Note: Only ArrayBufferView is implemented, other DataViews are not.
+  // https://www.w3.org/TR/WebIDL-1/#idl-DataView
+  if (value->IsArrayBufferView()) {
+    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType3<T1, T2, T3>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType3<T1, T2, T3>(t2);
+      return;
+    }
+
+    if (UnionTypeTraitsT3::is_array_buffer_view_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t3);
+      *out_union = script::UnionType3<T1, T2, T3>(t3);
+      return;
+    }
+  }
+
+  // 9. If V is an object with a TypedArrayName internal slot, then:
+  // 1. If types includes a typed array type whose name is the value of V’s
+  // TypedArrayName  //      internal slot, then return the result of
+  // converting V to that type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 10. If IsCallable(V) is true, then
+  // 1. If types includes a callback function type, then return the result of
+  // converting V to
+  //      that callback function type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 11. If |V| is null or undefined object, then:
+  // 1. If types includes a dictionary type, then return the result of
+  // converting V to
+  //      that dictionary type.
+  if (value->IsObject()) {
+    if (UnionTypeTraitsT1::is_dictionary_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType3<T1, T2, T3>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_dictionary_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType3<T1, T2, T3>(t2);
+      return;
+    }
+
+    if (UnionTypeTraitsT3::is_dictionary_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t3);
+      *out_union = script::UnionType3<T1, T2, T3>(t3);
+      return;
+    }
+  }
+
+  // 12. If |Type(V)| is Object, then:
   //   1. If |types| includes a sequence type, then
   //     1. Let |method| be the result of GetMethod(V, @@iterator)
   //     2. ReturnIfAbrupt(method)
@@ -375,7 +543,7 @@
     }
   }
 
-  // 12. If |Type(V)| is Boolean, then:
+  // 13. If |Type(V)| is Boolean, then:
   // 1. If |types| includes a boolean, then return the result of converting |V|
   //      to boolean.
   if (value->IsBoolean()) {
@@ -398,7 +566,7 @@
     }
   }
 
-  // 13. If |Type(V)| is a Number, then:
+  // 14. If |Type(V)| is a Number, then:
   //   1. If |types| includes a numeric type, then return the result of
   //      converting |V| to that numeric type.
   if (value->IsNumber()) {
@@ -421,7 +589,7 @@
     }
   }
 
-  // 14. If |types| includes a string type, then return the result of converting
+  // 15. If |types| includes a string type, then return the result of converting
   //     |V| to that type.
   if (value->IsString()) {
     if (UnionTypeTraitsT1::is_string_type) {
@@ -443,7 +611,7 @@
     }
   }
 
-  // 15. If |types| includes a numeric type, then return the result of
+  // 16. If |types| includes a numeric type, then return the result of
   //     converting |V| to that numeric type.
   if (UnionTypeTraitsT1::is_numeric_type) {
     FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
@@ -463,7 +631,7 @@
     return;
   }
 
-  // 16. If |types| includes a boolean, then return the result of converting |V|
+  // 17. If |types| includes a boolean, then return the result of converting |V|
   //     to boolean.
   if (UnionTypeTraitsT1::is_boolean_type) {
     FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
@@ -483,30 +651,8 @@
     return;
   }
 
-  // 17. If |types| includes an ArrayBufferView type, then return the result of
-  //     converting |V| to ArrayBufferView type.
-  //     This step has to be before 18 to catch array_buffer_view types.
-  if (value->IsArrayBufferView()) {
-    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
-      FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
-      *out_union = script::UnionType3<T1, T2, T3>(t1);
-      return;
-    }
-
-    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
-      FromJSValue(isolate, value, conversion_flags, exception_state, &t2);
-      *out_union = script::UnionType3<T1, T2, T3>(t2);
-      return;
-    }
-
-    if (UnionTypeTraitsT3::is_array_buffer_view_type) {
-      FromJSValue(isolate, value, conversion_flags, exception_state, &t3);
-      *out_union = script::UnionType3<T1, T2, T3>(t3);
-      return;
-    }
-  }
-
-  // 18. If |types| includes any ScriptValue type, then return the result of
+  // Note: non-spec fallback
+  //     If |types| includes any ScriptValue type, then return the result of
   //     converting |V| to that ScriptValue type.
   if (value->IsArrayBuffer()) {
     if (UnionTypeTraitsT1::is_script_value_type) {
@@ -528,7 +674,7 @@
     }
   }
 
-  // 19. Throw a TypeError.
+  // 18. Throw a TypeError.
   exception_state->SetSimpleException(kNotUnionType);
 }
 
@@ -562,10 +708,10 @@
                  script::UnionType4<T1, T2, T3, T4>* out_union) {
   DCHECK_EQ(0, conversion_flags);
   // JS -> IDL type conversion procedure described here:
-  // http://heycam.github.io/webidl/#es-union
+  // https://www.w3.org/TR/WebIDL-1/#es-union
+  // ( Draft: http://heycam.github.io/webidl/#es-union )
 
-  // TODO: Support Date, RegExp, DOMException, Error, callback functions,
-  // dictionary.
+  // TODO: Support Date, RegExp, DOMException, Error, callback functions.
 
   // 1. If the union type includes a nullable type and |V| is null or undefined,
   // then return the IDL value null.
@@ -591,7 +737,7 @@
   T3 t3;
   T4 t4;
 
-  // 4. If |V| is a platform object, then:
+  // 3. If |V| is a platform object, then:
   //   1. If |types| includes an interface type that V implements, then return
   //      the IDL value that is a reference to the object |V|.
   //   2. If |types| includes object, then return the IDL value that is a
@@ -644,7 +790,117 @@
     }
   }
 
-  // 11. If |Type(V)| is Object, then:
+  // 4. If |V| object, then
+  // 1. If |types| includes object, then return the IDL value that is a
+  // reference to
+  //      the object V.
+  // Not implemented
+
+  // 5. If V is a DOMException platform object, then:
+  // 1. If types includes DOMException or Error, then return the result of
+  // converting
+  //      V to that type.
+  // Not implemented
+
+  // 6. If V is a native Error object (that is, it has an ErrorData internal
+  // slot),
+  //    then:
+  // 1. If types includes Error, then return the result of converting V to
+  // Error.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  // Not implemented
+
+  // 7. If V is an object with an ArrayBufferData internal slot, then:
+  // 1. If types includes ArrayBuffer, then return the result of converting V
+  // to
+  //      ArrayBuffer.
+  // 2. If types includes object, then return the IDL value that is a reference
+  //      to the object V.
+  // Not implemented
+
+  // 8. If V is an object with a DataView internal slot, then:
+  // 1. If types includes DataView, then return the result of converting V to
+  // DataView.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  //   Note: Only ArrayBufferView is implemented, other DataViews are not.
+  // https://www.w3.org/TR/WebIDL-1/#idl-DataView
+  if (value->IsArrayBufferView()) {
+    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t2);
+      return;
+    }
+
+    if (UnionTypeTraitsT3::is_array_buffer_view_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t3);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t3);
+      return;
+    }
+
+    if (UnionTypeTraitsT4::is_array_buffer_view_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t4);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t4);
+      return;
+    }
+  }
+
+  // 9. If V is an object with a TypedArrayName internal slot, then:
+  // 1. If types includes a typed array type whose name is the value of V’s
+  // TypedArrayName  //      internal slot, then return the result of
+  // converting V to that type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 10. If IsCallable(V) is true, then
+  // 1. If types includes a callback function type, then return the result of
+  // converting V to
+  //      that callback function type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 11. If |V| is null or undefined object, then:
+  // 1. If types includes a dictionary type, then return the result of
+  // converting V to
+  //      that dictionary type.
+  if (value->IsObject()) {
+    if (UnionTypeTraitsT1::is_dictionary_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_dictionary_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t2);
+      return;
+    }
+
+    if (UnionTypeTraitsT3::is_dictionary_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t3);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t3);
+      return;
+    }
+
+    if (UnionTypeTraitsT4::is_dictionary_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t4);
+      *out_union = script::UnionType4<T1, T2, T3, T4>(t4);
+      return;
+    }
+  }
+
+  // 12. If |Type(V)| is Object, then:
   //   1. If |types| includes a sequence type, then
   //     1. Let |method| be the result of GetMethod(V, @@iterator)
   //     2. ReturnIfAbrupt(method)
@@ -676,7 +932,7 @@
     }
   }
 
-  // 12. If |Type(V)| is Boolean, then:
+  // 13. If |Type(V)| is Boolean, then:
   // 1. If |types| includes a boolean, then return the result of converting |V|
   //      to boolean.
   if (value->IsBoolean()) {
@@ -705,7 +961,7 @@
     }
   }
 
-  // 13. If |Type(V)| is a Number, then:
+  // 14. If |Type(V)| is a Number, then:
   //   1. If |types| includes a numeric type, then return the result of
   //      converting |V| to that numeric type.
   if (value->IsNumber()) {
@@ -734,7 +990,7 @@
     }
   }
 
-  // 14. If |types| includes a string type, then return the result of converting
+  // 15. If |types| includes a string type, then return the result of converting
   //     |V| to that type.
   if (value->IsString()) {
     if (UnionTypeTraitsT1::is_string_type) {
@@ -762,7 +1018,7 @@
     }
   }
 
-  // 15. If |types| includes a numeric type, then return the result of
+  // 16. If |types| includes a numeric type, then return the result of
   //     converting |V| to that numeric type.
   if (UnionTypeTraitsT1::is_numeric_type) {
     FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
@@ -788,7 +1044,7 @@
     return;
   }
 
-  // 16. If |types| includes a boolean, then return the result of converting |V|
+  // 17. If |types| includes a boolean, then return the result of converting |V|
   //     to boolean.
   if (UnionTypeTraitsT1::is_boolean_type) {
     FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
@@ -814,36 +1070,8 @@
     return;
   }
 
-  // 17. If |types| includes an ArrayBufferView type, then return the result of
-  //     converting |V| to ArrayBufferView type.
-  //     This step has to be before 18 to catch array_buffer_view types.
-  if (value->IsArrayBufferView()) {
-    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
-      FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
-      *out_union = script::UnionType4<T1, T2, T3, T4>(t1);
-      return;
-    }
-
-    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
-      FromJSValue(isolate, value, conversion_flags, exception_state, &t2);
-      *out_union = script::UnionType4<T1, T2, T3, T4>(t2);
-      return;
-    }
-
-    if (UnionTypeTraitsT3::is_array_buffer_view_type) {
-      FromJSValue(isolate, value, conversion_flags, exception_state, &t3);
-      *out_union = script::UnionType4<T1, T2, T3, T4>(t3);
-      return;
-    }
-
-    if (UnionTypeTraitsT4::is_array_buffer_view_type) {
-      FromJSValue(isolate, value, conversion_flags, exception_state, &t4);
-      *out_union = script::UnionType4<T1, T2, T3, T4>(t4);
-      return;
-    }
-  }
-
-  // 18. If |types| includes any ScriptValue type, then return the result of
+  // Note: non-spec fallback
+  //     If |types| includes any ScriptValue type, then return the result of
   //     converting |V| to that ScriptValue type.
   if (value->IsArrayBuffer()) {
     if (UnionTypeTraitsT1::is_script_value_type) {
@@ -871,7 +1099,7 @@
     }
   }
 
-  // 19. Throw a TypeError.
+  // 18. Throw a TypeError.
   exception_state->SetSimpleException(kNotUnionType);
 }
 
@@ -909,10 +1137,10 @@
                  script::UnionType5<T1, T2, T3, T4, T5>* out_union) {
   DCHECK_EQ(0, conversion_flags);
   // JS -> IDL type conversion procedure described here:
-  // http://heycam.github.io/webidl/#es-union
+  // https://www.w3.org/TR/WebIDL-1/#es-union
+  // ( Draft: http://heycam.github.io/webidl/#es-union )
 
-  // TODO: Support Date, RegExp, DOMException, Error, callback functions,
-  // dictionary.
+  // TODO: Support Date, RegExp, DOMException, Error, callback functions.
 
   // 1. If the union type includes a nullable type and |V| is null or undefined,
   // then return the IDL value null.
@@ -940,7 +1168,7 @@
   T4 t4;
   T5 t5;
 
-  // 4. If |V| is a platform object, then:
+  // 3. If |V| is a platform object, then:
   //   1. If |types| includes an interface type that V implements, then return
   //      the IDL value that is a reference to the object |V|.
   //   2. If |types| includes object, then return the IDL value that is a
@@ -1001,7 +1229,129 @@
     }
   }
 
-  // 11. If |Type(V)| is Object, then:
+  // 4. If |V| object, then
+  // 1. If |types| includes object, then return the IDL value that is a
+  // reference to
+  //      the object V.
+  // Not implemented
+
+  // 5. If V is a DOMException platform object, then:
+  // 1. If types includes DOMException or Error, then return the result of
+  // converting
+  //      V to that type.
+  // Not implemented
+
+  // 6. If V is a native Error object (that is, it has an ErrorData internal
+  // slot),
+  //    then:
+  // 1. If types includes Error, then return the result of converting V to
+  // Error.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  // Not implemented
+
+  // 7. If V is an object with an ArrayBufferData internal slot, then:
+  // 1. If types includes ArrayBuffer, then return the result of converting V
+  // to
+  //      ArrayBuffer.
+  // 2. If types includes object, then return the IDL value that is a reference
+  //      to the object V.
+  // Not implemented
+
+  // 8. If V is an object with a DataView internal slot, then:
+  // 1. If types includes DataView, then return the result of converting V to
+  // DataView.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to
+  //      the object V.
+  //   Note: Only ArrayBufferView is implemented, other DataViews are not.
+  // https://www.w3.org/TR/WebIDL-1/#idl-DataView
+  if (value->IsArrayBufferView()) {
+    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t2);
+      return;
+    }
+
+    if (UnionTypeTraitsT3::is_array_buffer_view_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t3);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t3);
+      return;
+    }
+
+    if (UnionTypeTraitsT4::is_array_buffer_view_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t4);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t4);
+      return;
+    }
+
+    if (UnionTypeTraitsT5::is_array_buffer_view_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t5);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t5);
+      return;
+    }
+  }
+
+  // 9. If V is an object with a TypedArrayName internal slot, then:
+  // 1. If types includes a typed array type whose name is the value of V’s
+  // TypedArrayName  //      internal slot, then return the result of
+  // converting V to that type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 10. If IsCallable(V) is true, then
+  // 1. If types includes a callback function type, then return the result of
+  // converting V to
+  //      that callback function type.
+  // 2. If types includes object, then return the IDL value that is a reference
+  // to the object V.
+  // Not implemented
+
+  // 11. If |V| is null or undefined object, then:
+  // 1. If types includes a dictionary type, then return the result of
+  // converting V to
+  //      that dictionary type.
+  if (value->IsObject()) {
+    if (UnionTypeTraitsT1::is_dictionary_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t1);
+      return;
+    }
+
+    if (UnionTypeTraitsT2::is_dictionary_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t2);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t2);
+      return;
+    }
+
+    if (UnionTypeTraitsT3::is_dictionary_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t3);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t3);
+      return;
+    }
+
+    if (UnionTypeTraitsT4::is_dictionary_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t4);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t4);
+      return;
+    }
+
+    if (UnionTypeTraitsT5::is_dictionary_type) {
+      FromJSValue(isolate, value, conversion_flags, exception_state, &t5);
+      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t5);
+      return;
+    }
+  }
+
+  // 12. If |Type(V)| is Object, then:
   //   1. If |types| includes a sequence type, then
   //     1. Let |method| be the result of GetMethod(V, @@iterator)
   //     2. ReturnIfAbrupt(method)
@@ -1039,7 +1389,7 @@
     }
   }
 
-  // 12. If |Type(V)| is Boolean, then:
+  // 13. If |Type(V)| is Boolean, then:
   // 1. If |types| includes a boolean, then return the result of converting |V|
   //      to boolean.
   if (value->IsBoolean()) {
@@ -1074,7 +1424,7 @@
     }
   }
 
-  // 13. If |Type(V)| is a Number, then:
+  // 14. If |Type(V)| is a Number, then:
   //   1. If |types| includes a numeric type, then return the result of
   //      converting |V| to that numeric type.
   if (value->IsNumber()) {
@@ -1109,7 +1459,7 @@
     }
   }
 
-  // 14. If |types| includes a string type, then return the result of converting
+  // 15. If |types| includes a string type, then return the result of converting
   //     |V| to that type.
   if (value->IsString()) {
     if (UnionTypeTraitsT1::is_string_type) {
@@ -1143,7 +1493,7 @@
     }
   }
 
-  // 15. If |types| includes a numeric type, then return the result of
+  // 16. If |types| includes a numeric type, then return the result of
   //     converting |V| to that numeric type.
   if (UnionTypeTraitsT1::is_numeric_type) {
     FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
@@ -1175,7 +1525,7 @@
     return;
   }
 
-  // 16. If |types| includes a boolean, then return the result of converting |V|
+  // 17. If |types| includes a boolean, then return the result of converting |V|
   //     to boolean.
   if (UnionTypeTraitsT1::is_boolean_type) {
     FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
@@ -1207,42 +1557,8 @@
     return;
   }
 
-  // 17. If |types| includes an ArrayBufferView type, then return the result of
-  //     converting |V| to ArrayBufferView type.
-  //     This step has to be before 18 to catch array_buffer_view types.
-  if (value->IsArrayBufferView()) {
-    if (UnionTypeTraitsT1::is_array_buffer_view_type) {
-      FromJSValue(isolate, value, conversion_flags, exception_state, &t1);
-      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t1);
-      return;
-    }
-
-    if (UnionTypeTraitsT2::is_array_buffer_view_type) {
-      FromJSValue(isolate, value, conversion_flags, exception_state, &t2);
-      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t2);
-      return;
-    }
-
-    if (UnionTypeTraitsT3::is_array_buffer_view_type) {
-      FromJSValue(isolate, value, conversion_flags, exception_state, &t3);
-      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t3);
-      return;
-    }
-
-    if (UnionTypeTraitsT4::is_array_buffer_view_type) {
-      FromJSValue(isolate, value, conversion_flags, exception_state, &t4);
-      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t4);
-      return;
-    }
-
-    if (UnionTypeTraitsT5::is_array_buffer_view_type) {
-      FromJSValue(isolate, value, conversion_flags, exception_state, &t5);
-      *out_union = script::UnionType5<T1, T2, T3, T4, T5>(t5);
-      return;
-    }
-  }
-
-  // 18. If |types| includes any ScriptValue type, then return the result of
+  // Note: non-spec fallback
+  //     If |types| includes any ScriptValue type, then return the result of
   //     converting |V| to that ScriptValue type.
   if (value->IsArrayBuffer()) {
     if (UnionTypeTraitsT1::is_script_value_type) {
@@ -1276,7 +1592,7 @@
     }
   }
 
-  // 19. Throw a TypeError.
+  // 18. Throw a TypeError.
   exception_state->SetSimpleException(kNotUnionType);
 }<