Import Cobalt 20.master.0.239287

Includes the following patches:
  https://cobalt-review.googlesource.com/c/cobalt/+/5590
    by n1214.hwang@samsung.com
  https://cobalt-review.googlesource.com/c/cobalt/+/5530
    by errong.leng@samsung.com
diff --git a/src/base/base.gyp b/src/base/base.gyp
index 5eccf13..e442340 100644
--- a/src/base/base.gyp
+++ b/src/base/base.gyp
@@ -692,24 +692,6 @@
       'sources': [
       ],
     },
-
-
-    # Include this target for a main() function that simply instantiates
-    # and runs a base::TestSuite.
-    {
-      'target_name': 'run_all_unittests',
-      'type': 'static_library',
-      'dependencies': [
-        'test_support_base',
-      ],
-      'sources': [
-        'test/run_all_unittests.cc',
-      ],
-      'dependencies': [
-        '<(DEPTH)/testing/gmock.gyp:gmock',
-        '<(DEPTH)/testing/gtest.gyp:gtest',
-      ],
-    },
     {
       'target_name': 'base_unittests',
       'type': '<(gtest_target_type)',
@@ -948,7 +930,6 @@
         'base',
         'base_i18n',
         'base_static',
-        'run_all_unittests',
         'test_support_base',
         'third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
       ],
@@ -969,6 +950,7 @@
         # Destructor is explicitly deleted.
         4624,
       ],
+      'includes': ['<(DEPTH)/base/test/test.gypi'],
     },
     {
       'target_name': 'test_support_base',
diff --git a/src/base/files/file_enumerator_starboard.cc b/src/base/files/file_enumerator_starboard.cc
index 1c2b3f0..70e9590 100644
--- a/src/base/files/file_enumerator_starboard.cc
+++ b/src/base/files/file_enumerator_starboard.cc
@@ -107,10 +107,23 @@
   };
 
   std::vector<FileEnumerator::FileInfo> ret;
-  SbDirectoryEntry entry;
   // We test if SbDirectoryGetNext returns parent directory file descriptor(..)
   // because the definition of SbDirectoryGetNext does not guarantee that.
   bool found_dot_dot = false;
+
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+  std::vector<char> entry(SB_FILE_MAX_NAME);
+
+  while (SbDirectoryGetNext(dir, entry.data(), entry.size())) {
+    const char dot_dot_str[] = "..";
+    if (!SbStringCompare(entry.data(), dot_dot_str, sizeof(dot_dot_str))) {
+      found_dot_dot = true;
+    }
+    ret.push_back(GenerateEntry(entry.data()));
+  }
+#else   // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+  SbDirectoryEntry entry;
+
   while (SbDirectoryGetNext(dir, &entry)) {
     const char dot_dot_str[] = "..";
     if (!SbStringCompare(entry.name, dot_dot_str, sizeof(dot_dot_str))) {
@@ -118,6 +131,8 @@
     }
     ret.push_back(GenerateEntry(entry.name));
   }
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
   if (!found_dot_dot) {
     ret.push_back(GenerateEntry(".."));
   }
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 02454ed..da61163 100644
--- a/src/base/task/task_scheduler/scheduler_worker_pool_impl.cc
+++ b/src/base/task/task_scheduler/scheduler_worker_pool_impl.cc
@@ -50,7 +50,7 @@
     "TaskScheduler.NumTasksBetweenWaits.";
 constexpr char kNumThreadsHistogramPrefix[] = "TaskScheduler.NumWorkers.";
 #ifdef STARBOARD
-constexpr size_t kMaxNumberOfWorkers = SB_MAX_THREADS;
+const size_t kMaxNumberOfWorkers = SB_MAX_THREADS;
 #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 0fad634..816415f 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
@@ -1415,7 +1415,7 @@
 // leaves the pool in a valid state with regards to max tasks.
 TEST_F(TaskSchedulerWorkerPoolBlockingTest, MaximumWorkersTest) {
 #ifdef STARBOARD
-  constexpr size_t kMaxNumberOfWorkers = SB_MAX_THREADS;
+  const size_t kMaxNumberOfWorkers = SB_MAX_THREADS;
 #else
   constexpr size_t kMaxNumberOfWorkers = 256;
 #endif
@@ -1665,7 +1665,7 @@
 // test for https://crbug.com/810464.
 TEST_F(TaskSchedulerWorkerPoolImplStartInBodyTest, RacyCleanup) {
 #ifdef STARBOARD
-  constexpr size_t kLocalMaxTasks = SB_MAX_THREADS;
+  const size_t kLocalMaxTasks = SB_MAX_THREADS;
 #else
 #if defined(OS_FUCHSIA)
   // Fuchsia + QEMU doesn't deal well with *many* threads being
diff --git a/src/base/test/test.gypi b/src/base/test/test.gypi
new file mode 100644
index 0000000..c99d14d
--- /dev/null
+++ b/src/base/test/test.gypi
@@ -0,0 +1,16 @@
+# Copyright (c) 2012 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 this file for a main() function that simply instantiates and runs a
+# base::TestSuite.
+{
+  'dependencies': [
+    '<(DEPTH)/base/base.gyp:test_support_base',
+    '<(DEPTH)/testing/gmock.gyp:gmock',
+    '<(DEPTH)/testing/gtest.gyp:gtest',
+  ],
+  'sources': [
+    '<(DEPTH)/base/test/run_all_unittests.cc',
+  ],
+}
diff --git a/src/base/time/time_unittest.cc b/src/base/time/time_unittest.cc
index 87d4cc5..a8a4a58 100644
--- a/src/base/time/time_unittest.cc
+++ b/src/base/time/time_unittest.cc
@@ -1000,14 +1000,22 @@
 // static
 ThreadTicks ThreadTicksOverride::now_ticks_;
 
-#if SB_HAS(TIME_THREAD_NOW)
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION || \
+    SB_HAS(TIME_THREAD_NOW)
 // IOS doesn't support ThreadTicks::Now().
-#if defined(OS_IOS) || SB_HAS(TIME_THREAD_NOW)
+#if defined(OS_IOS)
 #define MAYBE_NowOverride DISABLED_NowOverride
 #else
 #define MAYBE_NowOverride NowOverride
 #endif
 TEST(ThreadTicks, MAYBE_NowOverride) {
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+  if (!SbTimeIsTimeThreadNowSupported()) {
+    SB_LOG(INFO) << "Time thread now not supported. Test skipped.";
+    return;
+  }
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
   ThreadTicksOverride::now_ticks_ = ThreadTicks::Min();
 
   // Override is not active. All Now() methods should return a sensible value.
@@ -1043,7 +1051,8 @@
   EXPECT_LE(initial_thread_ticks, subtle::ThreadTicksNowIgnoringOverride());
   EXPECT_GT(ThreadTicks::Max(), subtle::ThreadTicksNowIgnoringOverride());
 }
-#endif  // SB_HAS(TIME_THREAD_NOW)
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION ||
+        // SB_HAS(TIME_THREAD_NOW)
 
 TEST(ThreadTicks, ThreadNow) {
   if (ThreadTicks::IsSupported()) {
diff --git a/src/cobalt/CHANGELOG.md b/src/cobalt/CHANGELOG.md
index 06fe9c7..cce7b0f 100644
--- a/src/cobalt/CHANGELOG.md
+++ b/src/cobalt/CHANGELOG.md
@@ -6,16 +6,28 @@
 
  - **DevTools and WebDriver listen to ANY interface, except on Linux.**
 
-  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.
+   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.
 
  - **DevTools shows asynchronous stack traces.**
 
-  When stopped at a breakpoint within the handler function for an asynchronous
-  operation, the call stack in DevTools now shows both the current function as
-  well as the function where the asynchronous operation was initiated.
+   When stopped at a breakpoint within the handler function for an asynchronous
+   operation, the call stack in DevTools now shows both the current function as
+   well as the function where the asynchronous operation was initiated.
+
+ - **Optimized network buffer management and notification handling.**
+
+   Reduced unnecessary buffer copying during network downloading which results
+   in the reduction of CPU usage on both the NetworkModule thread and the
+   MainWebModule thread.  Peak memory usage during downloading is also reduced.
+   Also reduced redundant notifications from the NetworkModule thread to the
+   MainWebModule thread on downloading progresses.
+   CPU utilization of both threads is reduced by more than 10% with the above
+   optimizations on some less powerful platforms during high bitrate content
+   playback.  The lower CPU utilization of the MainWebModule thread allows it to
+   process other tasks (like Javascript execution) more responsively.
 
 ## Version 20
 
diff --git a/src/cobalt/audio/audio_test.gyp b/src/cobalt/audio/audio_test.gyp
index c354a85..c4e3979 100644
--- a/src/cobalt/audio/audio_test.gyp
+++ b/src/cobalt/audio/audio_test.gyp
@@ -26,7 +26,6 @@
       'dependencies': [
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
         '<(DEPTH)/cobalt/media/media.gyp:media',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
 
@@ -34,6 +33,7 @@
         #       ScriptValueFactory has non-virtual method CreatePromise().
         '<(DEPTH)/cobalt/script/engine.gyp:engine',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/base/base.gyp b/src/cobalt/base/base.gyp
index a34c59f..f849ff4 100644
--- a/src/cobalt/base/base.gyp
+++ b/src/cobalt/base/base.gyp
@@ -111,10 +111,10 @@
       ],
       'dependencies': [
         'base',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
     {
       'target_name': 'base_test_deploy',
diff --git a/src/cobalt/bindings/testing/testing.gyp b/src/cobalt/bindings/testing/testing.gyp
index cfa6c3d..2898f07 100644
--- a/src/cobalt/bindings/testing/testing.gyp
+++ b/src/cobalt/bindings/testing/testing.gyp
@@ -187,12 +187,12 @@
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/script/engine.gyp:engine',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'bindings',
         'bindings_test_implementation',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/black_box_tests/README.md b/src/cobalt/black_box_tests/README.md
index d830906..6a696ea 100644
--- a/src/cobalt/black_box_tests/README.md
+++ b/src/cobalt/black_box_tests/README.md
@@ -84,6 +84,6 @@
   1. Add a python test script in tests/.
   2. Add target web page(s) and associated resources(if any) to testdata/.
   3. Add the test name(name of the python test script) to black_box_tests.py
-     to automate new test. Add the name to either the list of tests requiring
+     to automate new test. Add the name to the list of tests requiring
      app launcher support for system signals(e.g. suspend/resume), or the list
-     of tests that don't.
+     of tests requiring deep link support, or the list of tests that don't.
diff --git a/src/cobalt/black_box_tests/black_box_tests.py b/src/cobalt/black_box_tests/black_box_tests.py
index c209f91..bfc8ce5 100644
--- a/src/cobalt/black_box_tests/black_box_tests.py
+++ b/src/cobalt/black_box_tests/black_box_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
@@ -54,6 +53,11 @@
     'web_debugger',
     'web_platform_tests',
 ]
+# These tests can only be run on platforms whose app launcher can send deep
+# links.
+_TESTS_NEEDING_DEEP_LINK = [
+    'fire_deep_link_before_load',
+]
 # Location of test files.
 _TEST_DIR_PATH = 'cobalt.black_box_tests.tests.'
 # Platform dependent device parameters.
@@ -108,10 +112,13 @@
       output_file=None,
       out_directory=out_directory)
 
+  test_targets = _TESTS_NO_SIGNAL
+
   if launcher.SupportsSuspendResume():
-    test_targets = _TESTS_NEEDING_SYSTEM_SIGNAL + _TESTS_NO_SIGNAL
-  else:
-    test_targets = _TESTS_NO_SIGNAL
+    test_targets += _TESTS_NEEDING_SYSTEM_SIGNAL
+
+  if launcher.SupportsDeepLink():
+    test_targets += _TESTS_NEEDING_DEEP_LINK
 
   test_suite = unittest.TestSuite()
   for test in test_targets:
@@ -123,8 +130,12 @@
 class BlackBoxTests(object):
   """Helper class to run all black box tests and return results."""
 
-  def __init__(self, server_binding_address, proxy_address=None,
-               proxy_port=None, test_name=None, wpt_http_port=None):
+  def __init__(self,
+               server_binding_address,
+               proxy_address=None,
+               proxy_port=None,
+               test_name=None,
+               wpt_http_port=None):
     logging.basicConfig(level=logging.DEBUG)
 
     # Setup global variables used by test cases
@@ -159,22 +170,21 @@
     # Test domains used in web platform tests to be resolved to the server
     # binding address.
     hosts = [
-        'web-platform.test',
-        'www.web-platform.test',
-        'www1.web-platform.test',
-        'www2.web-platform.test',
-        'xn--n8j6ds53lwwkrqhv28a.web-platform.test',
+        'web-platform.test', 'www.web-platform.test', 'www1.web-platform.test',
+        'www2.web-platform.test', 'xn--n8j6ds53lwwkrqhv28a.web-platform.test',
         'xn--lve-6lad.web-platform.test'
     ]
-    self.host_resolve_map = dict([(host, server_binding_address) for host in hosts])
+    self.host_resolve_map = dict([
+        (host, server_binding_address) for host in hosts
+    ])
 
   def Run(self):
     if self.proxy_port == '-1':
       return 1
     logging.info('Using proxy port: %s', self.proxy_port)
 
-    with ProxyServer(port=self.proxy_port,
-                     host_resolve_map=self.host_resolve_map):
+    with ProxyServer(
+        port=self.proxy_port, host_resolve_map=self.host_resolve_map):
       if self.test_name:
         suite = unittest.TestLoader().loadTestsFromModule(
             importlib.import_module(_TEST_DIR_PATH + self.test_name))
@@ -199,7 +209,8 @@
       socks.append((address, socket.socket(socket.AF_INET, socket.SOCK_STREAM)))
     try:
       for _ in range(_PORT_SELECTION_RETRY_LIMIT):
-        port = random.randint(_PORT_SELECTION_RANGE[0], _PORT_SELECTION_RANGE[1])
+        port = random.randint(_PORT_SELECTION_RANGE[0],
+                              _PORT_SELECTION_RANGE[1])
         unused = True
         for sock in socks:
           result = sock[1].connect_ex((sock[0], port))
@@ -208,9 +219,8 @@
             break
         if unused:
           return port
-      logging.error(
-          'Can not find unused port on addresses within %s attempts.' %
-          _PORT_SELECTION_RETRY_LIMIT)
+      logging.error('Can not find unused port on addresses within %s attempts.',
+                    _PORT_SELECTION_RETRY_LIMIT)
       return -1
     finally:
       for sock in socks:
@@ -219,28 +229,33 @@
 
 def main():
   parser = argparse.ArgumentParser()
-  parser.add_argument('--server_binding_address',
-                      default='127.0.0.1',
-                      help='Binding address used to create the test server.')
-  parser.add_argument('--proxy_address',
-                      default=None,
-                      help=('Address to the proxy server that all black box'
-                            'tests are run through. If not specified, the'
-                            'server binding address is used.'))
-  parser.add_argument('--proxy_port',
-                      default=None,
-                      help=('Port used to create the proxy server that all'
-                            'black box tests are run through. If not'
-                            'specified, a random free port is used.'))
-  parser.add_argument('--test_name',
-                      default=None,
-                      help=('Name of test to be run. If not specified, all '
-                            'tests are run.'))
-  parser.add_argument('--wpt_http_port',
-                      default=None,
-                       help=('Port used to create the web platform test http'
-                             'server. If not specified, a random free port is'
-                             'used.'))
+  parser.add_argument(
+      '--server_binding_address',
+      default='127.0.0.1',
+      help='Binding address used to create the test server.')
+  parser.add_argument(
+      '--proxy_address',
+      default=None,
+      help=('Address to the proxy server that all black box'
+            'tests are run through. If not specified, the'
+            'server binding address is used.'))
+  parser.add_argument(
+      '--proxy_port',
+      default=None,
+      help=('Port used to create the proxy server that all'
+            'black box tests are run through. If not'
+            'specified, a random free port is used.'))
+  parser.add_argument(
+      '--test_name',
+      default=None,
+      help=('Name of test to be run. If not specified, all '
+            'tests are run.'))
+  parser.add_argument(
+      '--wpt_http_port',
+      default=None,
+      help=('Port used to create the web platform test http'
+            'server. If not specified, a random free port is'
+            'used.'))
   args, _ = parser.parse_known_args()
 
   test_object = BlackBoxTests(args.server_binding_address, args.proxy_address,
diff --git a/src/cobalt/black_box_tests/proxy_server.py b/src/cobalt/black_box_tests/proxy_server.py
index ee29a39..df6c03b 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='0.0.0.0', port='8000', host_resolve_map=None):
+  def __init__(self, hostname='127.0.0.1', 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/fire_deep_link_before_load.html b/src/cobalt/black_box_tests/testdata/fire_deep_link_before_load.html
new file mode 100644
index 0000000..6be8649
--- /dev/null
+++ b/src/cobalt/black_box_tests/testdata/fire_deep_link_before_load.html
@@ -0,0 +1,7 @@
+<HTML>
+  <HEAD></HEAD>
+  <BODY>
+    <script src='black_box_js_test_utils.js'></script>
+    <script src='fire_deep_link_before_load.js'></script>
+  </BODY>
+</HTML>
diff --git a/src/cobalt/black_box_tests/testdata/fire_deep_link_before_load.js b/src/cobalt/black_box_tests/testdata/fire_deep_link_before_load.js
new file mode 100644
index 0000000..cc2128c
--- /dev/null
+++ b/src/cobalt/black_box_tests/testdata/fire_deep_link_before_load.js
@@ -0,0 +1,35 @@
+// 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.
+
+// Fail if the deep link is not received within 15 seconds.
+var kTimeout = 15 * 1000;
+var failTimer = setTimeout(fail, kTimeout);
+
+function fail() {
+    console.log("Failing due to timeout!");
+    assertTrue(false);
+}
+
+// The test sends "link 1", "link 2", "link 3" before load & so only "link 3"
+// should be handled.
+function listener(link) {
+    console.log("Received link: " + link.toString());
+    assertEqual("link 3", link);
+    console.log("Ending test");
+    onEndTest();
+    clearTimeout(failTimer);
+}
+
+h5vcc.runtime.onDeepLink.addListener(listener);
+console.log("Listener added");
\ No newline at end of file
diff --git a/src/cobalt/black_box_tests/tests/fire_deep_link_before_load.py b/src/cobalt/black_box_tests/tests/fire_deep_link_before_load.py
new file mode 100644
index 0000000..1a3daba
--- /dev/null
+++ b/src/cobalt/black_box_tests/tests/fire_deep_link_before_load.py
@@ -0,0 +1,119 @@
+# 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.
+"""Tests sending deep links before load."""
+
+# This test script works by splitting the work over 3 threads, so that they
+# can each make progress even if they come across blocking operations.
+# The three threads are:
+#   1. Main thread, runs BlackBoxTestCase, sends suspend/resume signals, etc.
+#   2. HTTP Server, responsible for slowly responding to a fetch of a javascript
+#      file.
+#   3. Webdriver thread, instructs Cobalt to navigate to a URL
+#
+# Steps in ~ chronological order:
+#   1. Create a TCP socket and listen on all interfaces.
+#   2. Start Cobalt, and point it to the socket created in Step 1.
+#   3. Send 3 deep links.
+#   4. Load & run the javascript resource.
+#   5. Check to see if JSTestsSucceeded().
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import _env  # pylint: disable=unused-import,g-bad-import-order
+
+import os
+import SimpleHTTPServer
+import threading
+import traceback
+import urlparse
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import MakeRequestHandlerClass
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+
+_FIRE_DEEP_LINK_BEFORE_LOAD_HTML = 'fire_deep_link_before_load.html'
+_FIRE_DEEP_LINK_BEFORE_LOAD_JS = 'fire_deep_link_before_load.js'
+_MAX_ALLOTTED_TIME_SECONDS = 60
+
+_links_fired = threading.Event()
+
+# The base path of the requested assets is the parent directory.
+_SERVER_ROOT_PATH = os.path.join(os.path.dirname(__file__), os.pardir)
+
+
+class JavascriptRequestDetector(MakeRequestHandlerClass(_SERVER_ROOT_PATH)):
+  """Proxies everything to SimpleHTTPRequestHandler, except some paths."""
+
+  def do_GET(self):  # pylint: disable=invalid-name
+    """Handles HTTP GET requests for resources."""
+
+    parsed_path = urlparse.urlparse(self.path)
+    if parsed_path.path == '/testdata/' + _FIRE_DEEP_LINK_BEFORE_LOAD_JS:
+      # It is important not to send any response back, so we block.
+      print('Waiting on links to be fired.')
+      _links_fired.wait()
+      print('Links have been fired. Getting JS.')
+
+    return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
+
+
+class FireDeepLinkBeforeLoad(black_box_tests.BlackBoxTestCase):
+  """Tests firing deep links before web module is loaded."""
+
+  def _LoadPage(self, webdriver, url):
+    """Instructs webdriver to navigate to url."""
+    try:
+      # Note: The following is a blocking request, and returns only when the
+      # page has fully loaded.  In this test, the page will not fully load
+      # so, this does not return until Cobalt exits.
+      webdriver.get(url)
+    except:  # pylint: disable=bare-except
+      traceback.print_exc()
+
+  def test_simple(self):
+
+    # Step 2. Start Cobalt, and point it to the socket created in Step 1.
+    try:
+      with ThreadedWebServer(JavascriptRequestDetector,
+                             self.GetBindingAddress()) as server:
+        with self.CreateCobaltRunner(url='about:blank') as runner:
+          target_url = server.GetURL(file_name='../testdata/' +
+                                     _FIRE_DEEP_LINK_BEFORE_LOAD_HTML)
+          cobalt_launcher_thread = threading.Thread(
+              target=FireDeepLinkBeforeLoad._LoadPage,
+              args=(self, runner.webdriver, target_url))
+          cobalt_launcher_thread.start()
+
+          # Step 3. Send 3 deep links
+          for i in range(1, 4):
+            link = 'link ' + str(i)
+            print('Sending link : ' + link)
+            self.assertTrue(runner.SendDeepLink(link) == 0)
+          print('Links fired.')
+          # Step 4. Load & run the javascript resource.
+          _links_fired.set()
+
+          # Step 5. Check to see if JSTestsSucceeded().
+          # Note that this call will check the DOM multiple times for a period
+          # of time (current default is 30 seconds).
+          self.assertTrue(runner.JSTestsSucceeded())
+    except:  # pylint: disable=bare-except
+      traceback.print_exc()
+      # Consider an exception being thrown as a test failure.
+      self.assertTrue(False)
+    finally:
+      print('Cleaning up.')
+      _links_fired.set()
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index 9b76975..2684cb9 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -678,12 +678,18 @@
   options.web_module_options.csp_enforcement_mode = dom::kCspEnforcementEnable;
 
   options.requested_viewport_size = requested_viewport_size;
+  options.web_module_loaded_callback =
+      base::Bind(&Application::DispatchEarlyDeepLink, base::Unretained(this));
   account_manager_.reset(new account::AccountManager());
   browser_module_.reset(
       new BrowserModule(initial_url,
                         (should_preload ? base::kApplicationStatePreloading
                                         : base::kApplicationStateStarted),
                         &event_dispatcher_, account_manager_.get(), options));
+#if SB_IS(EVERGREEN)
+  updater_module_.reset(new updater::UpdaterModule(
+      message_loop_, browser_module_->GetNetworkModule()));
+#endif
   UpdateUserAgent();
 
   app_status_ = (should_preload ? kPreloadingAppStatus : kRunningAppStatus);
@@ -785,6 +791,11 @@
         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() {
@@ -861,6 +872,7 @@
 
 void Application::HandleStarboardEvent(const SbEvent* starboard_event) {
   DCHECK(starboard_event);
+  DCHECK_EQ(base::MessageLoop::current(), message_loop_);
 
   // Forward input events to |SystemWindow|.
   if (starboard_event->type == kSbEventTypeInput) {
@@ -916,7 +928,13 @@
         // SB_HAS(ON_SCREEN_KEYBOARD)
     case kSbEventTypeLink: {
       const char* link = static_cast<const char*>(starboard_event->data);
-      DispatchEventInternal(new base::DeepLinkEvent(link));
+      if (browser_module_->IsWebModuleLoaded()) {
+        DLOG(INFO) << "Dispatching deep link " << link;
+        DispatchEventInternal(new base::DeepLinkEvent(link));
+      } else {
+        DLOG(INFO) << "Storing deep link " << link;
+        early_deep_link_ = link;
+      }
       break;
     }
     case kSbEventTypeAccessiblitySettingsChanged:
@@ -1201,5 +1219,14 @@
 }
 #endif  // defined(ENABLE_DEBUGGER) && defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
 
+void Application::DispatchEarlyDeepLink() {
+  if (early_deep_link_.empty()) {
+    return;
+  }
+  DLOG(INFO) << "Dispatching early deep link " << early_deep_link_;
+  DispatchEventInternal(new base::DeepLinkEvent(early_deep_link_.c_str()));
+  early_deep_link_ = "";
+}
+
 }  // namespace browser
 }  // namespace cobalt
diff --git a/src/cobalt/browser/application.h b/src/cobalt/browser/application.h
index db55875..731f42f 100644
--- a/src/cobalt/browser/application.h
+++ b/src/cobalt/browser/application.h
@@ -28,6 +28,9 @@
 #include "cobalt/browser/browser_module.h"
 #include "cobalt/browser/memory_tracker/tool.h"
 #include "cobalt/system_window/system_window.h"
+#if SB_IS(EVERGREEN)
+#include "cobalt/updater/updater_module.h"
+#endif
 #include "starboard/event.h"
 
 #if defined(ENABLE_WEBDRIVER)
@@ -101,6 +104,11 @@
   // Main components of the Cobalt browser application.
   std::unique_ptr<BrowserModule> browser_module_;
 
+#if SB_IS(EVERGREEN)
+  // Cobalt Updater.
+  std::unique_ptr<updater::UpdaterModule> updater_module_;
+#endif
+
   // Event callbacks.
   base::EventCallback network_event_callback_;
   base::EventCallback deep_link_event_callback_;
@@ -212,6 +220,13 @@
   void OnMemoryTrackerCommand(const std::string& message);
 #endif  // defined(ENABLE_DEBUGGER) && defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
 
+  // The latest link received before the Web Module is loaded is stored here.
+  std::string early_deep_link_;
+
+  // Dispach events for early deeplink. This should be called once the Web
+  // Module is loaded.
+  void DispatchEarlyDeepLink();
+
   DISALLOW_COPY_AND_ASSIGN(Application);
 };
 
diff --git a/src/cobalt/browser/browser.gyp b/src/cobalt/browser/browser.gyp
index 5ac69e0..666c6e9 100644
--- a/src/cobalt/browser/browser.gyp
+++ b/src/cobalt/browser/browser.gyp
@@ -15,6 +15,7 @@
 {
   'variables': {
     'sb_pedantic_warnings': 1,
+    'has_updater%' : '<!(python ../../build/file_exists.py <(DEPTH)/cobalt/updater/updater.gyp)',
   },
   'targets': [
     {
@@ -213,6 +214,11 @@
             'COBALT_MESH_CACHE_SIZE_IN_BYTES=<(mesh_cache_size_in_bytes)',
           ],
         }],
+        ['sb_evergreen == 1 and has_updater == "True"', {
+          'dependencies': [
+            '<(DEPTH)/cobalt/updater/updater.gyp:updater',
+          ],
+        }],
       ],
     },
 
@@ -241,11 +247,11 @@
         '<(DEPTH)/cobalt/speech/speech.gyp:speech',
         '<(DEPTH)/cobalt/storage/storage.gyp:storage',
         '<(DEPTH)/cobalt/storage/storage.gyp:storage_upgrade_copy_test_data',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'browser',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/browser/browser_bindings_gen.gyp b/src/cobalt/browser/browser_bindings_gen.gyp
index 29cbc9d..beb8e78 100644
--- a/src/cobalt/browser/browser_bindings_gen.gyp
+++ b/src/cobalt/browser/browser_bindings_gen.gyp
@@ -232,6 +232,7 @@
         '../audio/audio_node_channel_count_mode.idl',
         '../audio/audio_node_channel_interpretation.idl',
         '../debug/console/console_command.idl',
+        '../debug/console/debug_console_mode.idl',
         '../dom/blob_property_bag.idl',
         '../dom/captions/caption_character_edge_style.idl',
         '../dom/captions/caption_color.idl',
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index 6052b23..9b4b07b 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -310,7 +310,8 @@
       main_web_module_generation_(0),
       next_timeline_id_(1),
       current_splash_screen_timeline_id_(-1),
-      current_main_web_module_timeline_id_(-1) {
+      current_main_web_module_timeline_id_(-1),
+      web_module_loaded_callback_(options_.web_module_loaded_callback) {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::BrowserModule()");
 
   // Apply platform memory setting adjustments and defaults.
@@ -345,7 +346,18 @@
 #if defined(ENABLE_DEBUGGER)
   debug_console_layer_ = render_tree_combiner_.CreateLayer(kDebugConsoleZIndex);
 #endif
-  if (command_line->HasSwitch(browser::switches::kQrCodeOverlay)) {
+
+  int qr_code_overlay_slots = 4;
+  if (command_line->HasSwitch(switches::kQrCodeOverlay)) {
+    auto slots_in_string =
+        command_line->GetSwitchValueASCII(switches::kQrCodeOverlay);
+    if (!slots_in_string.empty()) {
+      auto result = base::StringToInt(slots_in_string, &qr_code_overlay_slots);
+      DCHECK(result) << "Failed to convert value of --"
+                     << switches::kQrCodeOverlay << ": "
+                     << qr_code_overlay_slots << " to int.";
+      DCHECK_GT(qr_code_overlay_slots, 0);
+    }
     qr_overlay_info_layer_ =
         render_tree_combiner_.CreateLayer(kOverlayInfoZIndex);
   } else {
@@ -430,7 +442,7 @@
   if (qr_overlay_info_layer_) {
     math::Size width_height = GetViewportSize().width_height();
     qr_code_overlay_.reset(new overlay_info::QrCodeOverlay(
-        width_height, GetResourceProvider(),
+        width_height, qr_code_overlay_slots, GetResourceProvider(),
         base::Bind(&BrowserModule::QueueOnQrCodeOverlayRenderTreeProduced,
                    base::Unretained(this))));
   }
@@ -680,7 +692,12 @@
   on_error_retry_count_ = 0;
 
   on_load_event_time_ = base::TimeTicks::Now().ToInternalValue();
+
   web_module_loaded_.Signal();
+
+  if (!web_module_loaded_callback_.is_null()) {
+    web_module_loaded_callback_.Run();
+  }
 }
 
 bool BrowserModule::WaitForLoad(const base::TimeDelta& timeout) {
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index b44418a..f4f9aa5 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -106,6 +106,7 @@
     base::Optional<cssom::ViewportSize> requested_viewport_size;
     bool enable_splash_screen_on_reloads;
     bool enable_on_screen_keyboard = true;
+    base::Closure web_module_loaded_callback;
   };
 
   // Type for a collection of URL handler callbacks that can potentially handle
@@ -119,6 +120,7 @@
                 const Options& options);
   ~BrowserModule();
 
+  network::NetworkModule* GetNetworkModule() { return &network_module_; }
   std::string GetUserAgent() { return network_module_.GetUserAgent(); }
 
   // Recreates web module with the given URL. In the case where Cobalt is
@@ -210,6 +212,8 @@
       const base::AccessibilityCaptionSettingsChangedEvent* event);
 #endif  // SB_API_VERSION >= SB_CAPTIONS_REQUIRED_VERSION || SB_HAS(CAPTIONS)
 
+  bool IsWebModuleLoaded() { return web_module_loaded_.IsSignaled(); }
+
  private:
 #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
   static void CoreDumpHandler(void* browser_module_as_void);
@@ -666,6 +670,9 @@
   // by automem.  We want this so that we can check that it never changes, since
   // we do not have the ability to modify it after startup.
   base::Optional<int64_t> javascript_gc_threshold_in_bytes_;
+
+  // Callback to run when the Web Module is loaded.
+  base::Closure web_module_loaded_callback_;
 };
 
 }  // namespace browser
diff --git a/src/cobalt/browser/cobalt.gyp b/src/cobalt/browser/cobalt.gyp
index 8320f9b..471e9ec 100644
--- a/src/cobalt/browser/cobalt.gyp
+++ b/src/cobalt/browser/cobalt.gyp
@@ -15,7 +15,6 @@
 {
   'variables': {
     'sb_pedantic_warnings': 1,
-    'has_updater%' : '<!(python ../../build/file_exists.py <(DEPTH)/cobalt/updater/updater.gyp)',
   },
   'targets': [
     {
@@ -34,11 +33,6 @@
             '<(DEPTH)/cobalt/browser/splash_screen/splash_screen.gyp:copy_splash_screen',
           ],
         }],
-        ['sb_evergreen == 1 and has_updater == "True"', {
-          'dependencies': [
-            '<(DEPTH)/cobalt/updater/updater.gyp:updater',
-          ],
-        }],
       ],
     },
     {
@@ -91,7 +85,7 @@
         },
       ]
     }],
-    ['final_executable_type == "shared_library"', {
+    ['final_executable_type == "shared_library" and sb_evergreen != 1', {
       'targets': [
         {
           'target_name': 'cobalt_bin',
diff --git a/src/cobalt/browser/debug_console.cc b/src/cobalt/browser/debug_console.cc
index 56223d9..36b5caa 100644
--- a/src/cobalt/browser/debug_console.cc
+++ b/src/cobalt/browser/debug_console.cc
@@ -33,9 +33,11 @@
 const char kInitialDebugConsoleUrl[] =
     "file:///cobalt/debug/console/debug_console.html";
 
-const char kDebugConsoleOffString[] = "off";
-const char kDebugConsoleOnString[] = "on";
-const char kDebugConsoleHudString[] = "hud";
+const char kDebugConsoleModeOffString[] = "off";
+const char kDebugConsoleModeHudString[] = "hud";
+const char kDebugConsoleModeDebugString[] = "debug";
+const char kDebugConsoleModeDebugStringAlias[] = "on";  // Legacy name of mode.
+const char kDebugConsoleModeMediaString[] = "media";
 
 // Convert from a debug console visibility setting string to an integer
 // value specified by a constant defined in debug::console::DebugHub.
@@ -44,12 +46,16 @@
   // Static casting is necessary in order to get around what appears to be a
   // compiler error on Linux when implicitly constructing a base::Optional<int>
   // from a static const int.
-  if (mode_string == kDebugConsoleOffString) {
-    return static_cast<int>(debug::console::DebugHub::kDebugConsoleOff);
-  } else if (mode_string == kDebugConsoleHudString) {
-    return static_cast<int>(debug::console::DebugHub::kDebugConsoleHud);
-  } else if (mode_string == kDebugConsoleOnString) {
-    return static_cast<int>(debug::console::DebugHub::kDebugConsoleOn);
+  if (mode_string == kDebugConsoleModeOffString) {
+    return static_cast<int>(debug::console::kDebugConsoleModeOff);
+  } else if (mode_string == kDebugConsoleModeHudString) {
+    return static_cast<int>(debug::console::kDebugConsoleModeHud);
+  } else if (mode_string == kDebugConsoleModeDebugString) {
+    return static_cast<int>(debug::console::kDebugConsoleModeDebug);
+  } else if (mode_string == kDebugConsoleModeDebugStringAlias) {
+    return static_cast<int>(debug::console::kDebugConsoleModeDebug);
+  } else if (mode_string == kDebugConsoleModeMediaString) {
+    return static_cast<int>(debug::console::kDebugConsoleModeMedia);
   } else {
     DLOG(WARNING) << "Debug console mode \"" << mode_string
                   << "\" not recognized.";
@@ -82,7 +88,7 @@
   }
 
   // By default the debug console is off.
-  return debug::console::DebugHub::kDebugConsoleOff;
+  return debug::console::kDebugConsoleModeOff;
 }
 
 // A function to create a DebugHub object, to be injected into WebModule.
@@ -147,8 +153,8 @@
 
 bool DebugConsole::ShouldInjectInputEvents() {
   switch (GetMode()) {
-    case debug::console::DebugHub::kDebugConsoleOff:
-    case debug::console::DebugHub::kDebugConsoleHud:
+    case debug::console::kDebugConsoleModeOff:
+    case debug::console::kDebugConsoleModeHud:
       return false;
     default:
       return true;
@@ -197,12 +203,12 @@
 
 void DebugConsole::CycleMode() {
   base::AutoLock lock(mode_mutex_);
-  mode_ = (mode_ + 1) % debug::console::DebugHub::kDebugConsoleNumModes;
+  mode_ = (mode_ + 1) % debug::console::kDebugConsoleModeCount;
 }
 
-int DebugConsole::GetMode() {
+debug::console::DebugConsoleMode DebugConsole::GetMode() {
   base::AutoLock lock(mode_mutex_);
-  return mode_;
+  return static_cast<debug::console::DebugConsoleMode>(mode_);
 }
 
 }  // namespace browser
diff --git a/src/cobalt/browser/debug_console.h b/src/cobalt/browser/debug_console.h
index bb1fe25..f368fc5 100644
--- a/src/cobalt/browser/debug_console.h
+++ b/src/cobalt/browser/debug_console.h
@@ -27,6 +27,7 @@
 #include "cobalt/base/token.h"
 #include "cobalt/browser/lifecycle_observer.h"
 #include "cobalt/browser/web_module.h"
+#include "cobalt/debug/console/debug_console_mode.h"
 #include "cobalt/debug/console/debug_hub.h"
 #include "cobalt/dom/input_event_init.h"
 #include "cobalt/dom/keyboard_event_init.h"
@@ -86,7 +87,7 @@
 
   // Returns true iff the console is in a mode that is visible.
   bool IsVisible() {
-    return (GetMode() != debug::console::DebugHub::kDebugConsoleOff);
+    return (GetMode() != debug::console::kDebugConsoleModeOff);
   }
 
   void SetSize(const cssom::ViewportSize& window_dimensions,
@@ -114,7 +115,7 @@
   }
 
   // Returns the currently set debug console visibility mode.
-  int GetMode();
+  debug::console::DebugConsoleMode GetMode();
 
   // Returns true iff the debug console is in a state where it should route
   // input events to its web module.
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc
index edb849b..127556b 100644
--- a/src/cobalt/browser/switches.cc
+++ b/src/cobalt/browser/switches.cc
@@ -315,7 +315,10 @@
 const char kQrCodeOverlay[] = "qr_code_overlay";
 const char kQrCodeOverlayHelp[] =
     "Display QrCode based overlay information. These information can be used"
-    " for performance tuning or playback quality check.";
+    " for performance tuning or playback quality check.  By default qr code"
+    " will be displayed in 4 different locations on the screen alternatively,"
+    " and the number of locations can be overwritten by specifying it as the "
+    " value of the command line parameter, like '--qr_code_overlay=6'.";
 
 const char kReduceCpuMemoryBy[] = "reduce_cpu_memory_by";
 const char kReduceCpuMemoryByHelp[] =
diff --git a/src/cobalt/build/all.gyp b/src/cobalt/build/all.gyp
index e0d3b20..e484055 100644
--- a/src/cobalt/build/all.gyp
+++ b/src/cobalt/build/all.gyp
@@ -99,7 +99,7 @@
             '<(DEPTH)/starboard/elf_loader/elf_loader.gyp:elf_loader_test_deploy',
           ],
         }],
-        ['has_loader_app == "True"', {
+        ['has_loader_app == "True" and sb_evergreen != 1', {
           'dependencies': [
             '<(DEPTH)/starboard/loader_app/loader_app.gyp:*',
           ],
@@ -114,6 +114,7 @@
         ['sb_evergreen==1', {
           'dependencies': [
             '<(DEPTH)/third_party/musl/musl.gyp:musl_unittests',
+            '<(DEPTH)/starboard/loader_app/installation_manager.gyp:*',
           ],
         }],
       ],
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index a86f8a5..e411972 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-234144
\ No newline at end of file
+239287
\ No newline at end of file
diff --git a/src/cobalt/csp/csp.gyp b/src/cobalt/csp/csp.gyp
index eaeaf4b..564888d 100644
--- a/src/cobalt/csp/csp.gyp
+++ b/src/cobalt/csp/csp.gyp
@@ -57,12 +57,12 @@
       ],
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'csp',
         'csp_copy_test_data',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/css_parser/css_parser.gyp b/src/cobalt/css_parser/css_parser.gyp
index 3c1f6c6..d498ca5 100644
--- a/src/cobalt/css_parser/css_parser.gyp
+++ b/src/cobalt/css_parser/css_parser.gyp
@@ -132,12 +132,12 @@
       ],
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'css_grammar',
         'css_parser',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/cssom/css_computed_style_data.h b/src/cobalt/cssom/css_computed_style_data.h
index f2c0b9f..557742d 100644
--- a/src/cobalt/cssom/css_computed_style_data.h
+++ b/src/cobalt/cssom/css_computed_style_data.h
@@ -23,6 +23,7 @@
 #include "base/containers/small_map.h"
 #include "base/memory/ref_counted.h"
 #include "cobalt/base/unused.h"
+#include "cobalt/cssom/keyword_value.h"
 #include "cobalt/cssom/property_definitions.h"
 #include "cobalt/cssom/property_value.h"
 
@@ -514,6 +515,18 @@
     return is_inline_before_blockification_;
   }
 
+  bool IsContainingBlockForPositionAbsoluteElements() const {
+    return IsPositioned() || IsTransformed();
+  }
+
+  bool IsPositioned() const {
+    return position() != cssom::KeywordValue::GetStatic();
+  }
+
+  bool IsTransformed() const {
+    return transform() != cssom::KeywordValue::GetNone();
+  }
+
  protected:
   void SetPropertyValue(const PropertyKey key,
                         const scoped_refptr<PropertyValue>& value);
diff --git a/src/cobalt/cssom/cssom_test.gyp b/src/cobalt/cssom/cssom_test.gyp
index d08f3d8..92fb5d6 100644
--- a/src/cobalt/cssom/cssom_test.gyp
+++ b/src/cobalt/cssom/cssom_test.gyp
@@ -58,10 +58,10 @@
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/css_parser/css_parser.gyp:css_parser',
         '<(DEPTH)/cobalt/cssom/cssom.gyp:cssom',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/debug/backend/content/dom_agent.js b/src/cobalt/debug/backend/content/dom_agent.js
index 2dcf369..f702565 100644
--- a/src/cobalt/debug/backend/content/dom_agent.js
+++ b/src/cobalt/debug/backend/content/dom_agent.js
@@ -82,13 +82,6 @@
   return JSON.stringify(result);
 }
 
-// Returns the bounding box of a node. This pseudo-command in the DOM domain is
-// a helper for the C++ |DOMAgent::HighlightNode|.
-commands._getBoundingClientRect = function(params) {
-  var node = commands._findNode(params);
-  return JSON.stringify(node.getBoundingClientRect());
-}
-
 // 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.
diff --git a/src/cobalt/debug/backend/content/overlay_agent.js b/src/cobalt/debug/backend/content/overlay_agent.js
new file mode 100644
index 0000000..ef0f82c
--- /dev/null
+++ b/src/cobalt/debug/backend/content/overlay_agent.js
@@ -0,0 +1,179 @@
+// 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.
+
+(function(debugBackend) {
+
+// Attach methods to handle commands in the 'Overlay' devtools domain.
+// https://chromedevtools.github.io/devtools-protocol/tot/Overlay
+let commands = debugBackend.Overlay = {};
+
+// Returns non-overlapping rectangles to highlight for the box model of a node.
+// This pseudo-command in the Overlay domain is a helper for the C++
+// |OverlayAgent::HighlightNode|.
+commands._highlightNodeRects = function(params) {
+  let node = debugBackend.DOM._findNode(params);
+  let config = params.highlightConfig || {};
+  let highlights = [];
+  if (node && node.getBoundingClientRect) {
+    let styles = window.getComputedStyle(node);
+    let content = node.getBoundingClientRect();
+    let color;
+    let box;
+
+    let transformed = isNodeTransformed(node);
+
+    if (!transformed) {
+      // Margin
+      color = config.marginColor;
+      box = styleBox(styles, 'margin');
+      if (color) {
+        boxRects(content, box).forEach(
+            rect => highlights.push(highlightParams(rect, color)));
+      }
+
+      // Border
+      color = config.borderColor;
+      box = styleBox(styles, 'border');
+      if (color) {
+        boxRects(content, box).forEach(
+            rect => highlights.push(highlightParams(rect, color)));
+      }
+      content = insetRect(content, box);
+
+      // Padding
+      color = config.paddingColor;
+      box = styleBox(styles, 'padding');
+      if (color) {
+        boxRects(content, box).forEach(
+            rect => highlights.push(highlightParams(rect, color)));
+      }
+      content = insetRect(content, box);
+    }
+
+    // Content
+    color = config.contentColor;
+    if (color) {
+      let highlight = highlightParams(content, color);
+      if (transformed) {
+        highlight.outlineColor = {r: 255, g: 0, b: 255, a: 1.0};
+      }
+      highlights.push(highlight);
+    }
+  }
+  return JSON.stringify({highlightRects: highlights});
+}
+
+// Returns the inset width of the 4 sides of a box in the computed style, with
+// margin being negative to place it outside the rect.
+function styleBox(styles, boxName) {
+  let suffix = boxName == 'border' ? '-width' : '';
+  let sign = boxName == 'margin' ? -1 : 1;
+  let box = {};
+  ['top', 'right', 'bottom', 'left'].forEach(side => {
+    let width = styles.getPropertyValue(`${boxName}-${side}${suffix}`);
+    box[side] = sign * parseFloat(width) || 0;
+  });
+  return box;
+}
+
+function isNodeTransformed(node) {
+  while (node) {
+    if (window.getComputedStyle(node).transform !== 'none') return true;
+    node = node.offsetParent;
+  }
+  return false;
+}
+
+// Returns an array of non-overlapping rectangles for the box around the inside
+// of |rect|, but the rectangle for any box side with negative width will be
+// on the outside of |rect|.
+function boxRects(rect, box) {
+  // Start out assuming box widths are all positive.
+  let outerT = rect.y;
+  let outerB = rect.y + rect.height;
+  let outerL = rect.x;
+  let outerR = rect.x + rect.width;
+  let innerT = outerT + box.top;
+  let innerB = outerB - box.bottom;
+  let innerL = outerL + box.left;
+  let innerR =  outerR - box.right;
+
+  // Swap any inner/outer "inverted" by a negative box side.
+  if (outerT > innerT) [outerT, innerT] = [innerT, outerT];
+  if (outerB < innerB) [outerB, innerB] = [innerB, outerB];
+  if (outerL > innerL) [outerL, innerL] = [innerL, outerL];
+  if (outerR < innerR) [outerR, innerR] = [innerR, outerR];
+
+  // +--------------+
+  // |              |
+  // +--+--------+--+
+  // |  |        |  |
+  // |  |        |  |
+  // +--+--------+--+
+  // |              |
+  // +--------------+
+  return [
+    // top
+    { x: outerL,
+      y: outerT,
+      width: outerR - outerL,
+      height: innerT - outerT },
+    // bottom
+    { x: outerL,
+      y: innerB,
+      width: outerR - outerL,
+      height: outerB - innerB },
+    // left
+    { x: outerL,
+      y: innerT,
+      width: innerL - outerL,
+      height: innerB - innerT },
+    // right
+    { x: innerR,
+      y: innerT,
+      width: outerR - innerR,
+      height: innerB - innerT },
+  ];
+}
+
+// Returns the rectangle with the box around the insides removed.
+function insetRect(rect, box) {
+  return {
+    x: rect.x + box.left,
+    y: rect.y + box.top,
+    width: rect.width - box.left - box.right,
+    height: rect.height - box.top - box.bottom };
+}
+
+// Returns parameters matching the DevTools protocol "Overlay.highlightRect"
+// parameters, as expected by the native overlay agent.
+function highlightParams(rect, color) {
+  // Copy each property rather than whole objects to ensure this can be
+  // converted with JSON.serialize().
+  return {
+    x: rect.x,
+    y: rect.y,
+    width: rect.width,
+    height: rect.height,
+    color: {
+      r: color.r,
+      g: color.g,
+      b: color.b,
+      a: color.a,
+    },
+  };
+}
+
+// TODO: Pass debugBackend from C++ instead of getting it from the window.
+})(window.debugBackend);
diff --git a/src/cobalt/debug/backend/css_agent.cc b/src/cobalt/debug/backend/css_agent.cc
index 38914e5..1de669c 100644
--- a/src/cobalt/debug/backend/css_agent.cc
+++ b/src/cobalt/debug/backend/css_agent.cc
@@ -14,6 +14,7 @@
 
 #include "cobalt/debug/backend/css_agent.h"
 
+#include "cobalt/dom/document.h"
 #include "cobalt/dom/html_element.h"
 
 namespace cobalt {
@@ -65,7 +66,8 @@
   CSSStyleRuleSequence css_rules;
   auto html_element = element->AsHTMLElement();
   if (html_element) {
-    html_element->UpdateMatchingRules();
+    html_element->node_document()->UpdateComputedStyleOnElementAndAncestor(
+        html_element.get());
     for (const auto& matching_rule : *html_element->matching_rules()) {
       css_rules.push_back(matching_rule.first);
     }
diff --git a/src/cobalt/debug/backend/debug_module.cc b/src/cobalt/debug/backend/debug_module.cc
index 0a854a2..5434789 100644
--- a/src/cobalt/debug/backend/debug_module.cc
+++ b/src/cobalt/debug/backend/debug_module.cc
@@ -29,6 +29,7 @@
 constexpr char kLogAgent[] = "LogAgent";
 constexpr char kDomAgent[] = "DomAgent";
 constexpr char kCssAgent[] = "CssAgent";
+constexpr char kOverlayAgent[] = "OverlayAgent";
 constexpr char kPageAgent[] = "PageAgent";
 constexpr char kTracingAgent[] = "TracingAgent";
 
@@ -152,7 +153,7 @@
   std::unique_ptr<RenderLayer> page_render_layer(new RenderLayer(base::Bind(
       &RenderOverlay::SetOverlay, base::Unretained(data.render_overlay))));
 
-  std::unique_ptr<RenderLayer> dom_render_layer(new RenderLayer(
+  std::unique_ptr<RenderLayer> overlay_render_layer(new RenderLayer(
       base::Bind(&RenderLayer::SetBackLayer, page_render_layer->AsWeakPtr())));
 
   // Create the agents that implement the various devtools protocol domains by
@@ -167,9 +168,10 @@
   }
   console_agent_.reset(new ConsoleAgent(debug_dispatcher_.get(), data.console));
   log_agent_.reset(new LogAgent(debug_dispatcher_.get()));
-  dom_agent_.reset(
-      new DOMAgent(debug_dispatcher_.get(), std::move(dom_render_layer)));
+  dom_agent_.reset(new DOMAgent(debug_dispatcher_.get()));
   css_agent_ = WrapRefCounted(new CSSAgent(debug_dispatcher_.get()));
+  overlay_agent_.reset(new OverlayAgent(debug_dispatcher_.get(),
+                                        std::move(overlay_render_layer)));
   page_agent_.reset(new PageAgent(debug_dispatcher_.get(), data.window,
                                   std::move(page_render_layer),
                                   data.resource_provider));
@@ -201,6 +203,7 @@
   log_agent_->Thaw(RemoveAgentState(kLogAgent, agents_state));
   dom_agent_->Thaw(RemoveAgentState(kDomAgent, agents_state));
   css_agent_->Thaw(RemoveAgentState(kCssAgent, agents_state));
+  overlay_agent_->Thaw(RemoveAgentState(kOverlayAgent, agents_state));
   page_agent_->Thaw(RemoveAgentState(kPageAgent, agents_state));
   tracing_agent_->Thaw(RemoveAgentState(kTracingAgent, agents_state));
 
@@ -228,6 +231,7 @@
   StoreAgentState(agents_state, kLogAgent, log_agent_->Freeze());
   StoreAgentState(agents_state, kDomAgent, dom_agent_->Freeze());
   StoreAgentState(agents_state, kCssAgent, css_agent_->Freeze());
+  StoreAgentState(agents_state, kOverlayAgent, overlay_agent_->Freeze());
   StoreAgentState(agents_state, kPageAgent, page_agent_->Freeze());
   StoreAgentState(agents_state, kTracingAgent, tracing_agent_->Freeze());
 
diff --git a/src/cobalt/debug/backend/debug_module.h b/src/cobalt/debug/backend/debug_module.h
index 07bb642..11f34bd 100644
--- a/src/cobalt/debug/backend/debug_module.h
+++ b/src/cobalt/debug/backend/debug_module.h
@@ -29,6 +29,7 @@
 #include "cobalt/debug/backend/debugger_state.h"
 #include "cobalt/debug/backend/dom_agent.h"
 #include "cobalt/debug/backend/log_agent.h"
+#include "cobalt/debug/backend/overlay_agent.h"
 #include "cobalt/debug/backend/page_agent.h"
 #include "cobalt/debug/backend/render_overlay.h"
 #include "cobalt/debug/backend/runtime_agent.h"
@@ -146,6 +147,7 @@
   std::unique_ptr<LogAgent> log_agent_;
   std::unique_ptr<DOMAgent> dom_agent_;
   scoped_refptr<CSSAgent> css_agent_;
+  std::unique_ptr<OverlayAgent> overlay_agent_;
   std::unique_ptr<PageAgent> page_agent_;
   std::unique_ptr<RuntimeAgent> runtime_agent_;
   std::unique_ptr<ScriptDebuggerAgent> script_debugger_agent_;
diff --git a/src/cobalt/debug/backend/dom_agent.cc b/src/cobalt/debug/backend/dom_agent.cc
index 268e26b..f3d5322 100644
--- a/src/cobalt/debug/backend/dom_agent.cc
+++ b/src/cobalt/debug/backend/dom_agent.cc
@@ -14,16 +14,6 @@
 
 #include "cobalt/debug/backend/dom_agent.h"
 
-#include <memory>
-#include <string>
-
-#include "base/bind.h"
-#include "cobalt/math/matrix3_f.h"
-#include "cobalt/math/transform_2d.h"
-#include "cobalt/render_tree/brush.h"
-#include "cobalt/render_tree/color_rgba.h"
-#include "cobalt/render_tree/rect_node.h"
-
 namespace cobalt {
 namespace debug {
 namespace backend {
@@ -37,17 +27,13 @@
 constexpr char kScriptFile[] = "dom_agent.js";
 }  // namespace
 
-DOMAgent::DOMAgent(DebugDispatcher* dispatcher,
-                   std::unique_ptr<RenderLayer> render_layer)
+DOMAgent::DOMAgent(DebugDispatcher* dispatcher)
     : dispatcher_(dispatcher),
-      render_layer_(std::move(render_layer)),
       ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this, kInspectorDomain)) {
   DCHECK(dispatcher_);
 
   commands_["disable"] = &DOMAgent::Disable;
   commands_["enable"] = &DOMAgent::Enable;
-  commands_["highlightNode"] = &DOMAgent::HighlightNode;
-  commands_["hideHighlight"] = &DOMAgent::HideHighlight;
 }
 
 void DOMAgent::Thaw(JSONObject agent_state) {
@@ -72,77 +58,6 @@
 
 void DOMAgent::Disable(const Command& command) { command.SendResponse(); }
 
-// Unlike most other DOM command handlers, this one is not fully implemented
-// in JavaScript. Instead, the JS object is used to look up the node from the
-// parameters and return its bounding client rect, then the highlight itself
-// is rendered by calling the C++ function |RenderHighlight| to set the render
-// overlay.
-void DOMAgent::HighlightNode(const Command& command) {
-  // Get the bounding rectangle of the specified node.
-  JSONObject json_dom_rect = dispatcher_->RunScriptCommand(
-      "dom._getBoundingClientRect", command.GetParams());
-  double x = 0.0;
-  double y = 0.0;
-  double width = 0.0;
-  double height = 0.0;
-  json_dom_rect->GetDouble("result.x", &x);
-  json_dom_rect->GetDouble("result.y", &y);
-  json_dom_rect->GetDouble("result.width", &width);
-  json_dom_rect->GetDouble("result.height", &height);
-
-  scoped_refptr<dom::DOMRect> dom_rect(
-      new dom::DOMRect(static_cast<float>(x), static_cast<float>(y),
-                       static_cast<float>(width), static_cast<float>(height)));
-
-  // |highlight_config_value| still owned by |params|.
-  JSONObject params = JSONParse(command.GetParams());
-  base::DictionaryValue* highlight_config_value = NULL;
-  bool got_highlight_config =
-      params->GetDictionary("highlightConfig", &highlight_config_value);
-  DCHECK(got_highlight_config);
-  DCHECK(highlight_config_value);
-
-  RenderHighlight(dom_rect, highlight_config_value);
-
-  command.SendResponse();
-}
-
-void DOMAgent::HideHighlight(const Command& command) {
-  render_layer_->SetFrontLayer(scoped_refptr<render_tree::Node>());
-  command.SendResponse();
-}
-
-void DOMAgent::RenderHighlight(
-    const scoped_refptr<dom::DOMRect>& bounding_rect,
-    const base::DictionaryValue* highlight_config_value) {
-  // TODO: Should also render borders, etc.
-
-  // Content color is optional in the parameters, so use a fallback.
-  int r = 112;
-  int g = 168;
-  int b = 219;
-  double a = 0.66;
-  const base::DictionaryValue* content_color = NULL;
-  bool got_content_color =
-      highlight_config_value->GetDictionary("contentColor", &content_color);
-  if (got_content_color && content_color) {
-    content_color->GetInteger("r", &r);
-    content_color->GetInteger("g", &g);
-    content_color->GetInteger("b", &b);
-    content_color->GetDouble("a", &a);
-  }
-  render_tree::ColorRGBA color(r / 255.0f, g / 255.0f, b / 255.0f,
-                               static_cast<float>(a));
-
-  std::unique_ptr<render_tree::Brush> background_brush(
-      new render_tree::SolidColorBrush(color));
-  scoped_refptr<render_tree::Node> rect = new render_tree::RectNode(
-      math::RectF(bounding_rect->x(), bounding_rect->y(),
-                  bounding_rect->width(), bounding_rect->height()),
-      std::move(background_brush));
-  render_layer_->SetFrontLayer(rect);
-}
-
 }  // namespace backend
 }  // namespace debug
 }  // namespace cobalt
diff --git a/src/cobalt/debug/backend/dom_agent.h b/src/cobalt/debug/backend/dom_agent.h
index b1d5339..6e59ffb 100644
--- a/src/cobalt/debug/backend/dom_agent.h
+++ b/src/cobalt/debug/backend/dom_agent.h
@@ -14,16 +14,10 @@
 #ifndef COBALT_DEBUG_BACKEND_DOM_AGENT_H_
 #define COBALT_DEBUG_BACKEND_DOM_AGENT_H_
 
-#include <memory>
-#include <string>
-
-#include "base/memory/weak_ptr.h"
 #include "cobalt/debug/backend/command_map.h"
 #include "cobalt/debug/backend/debug_dispatcher.h"
-#include "cobalt/debug/backend/render_layer.h"
 #include "cobalt/debug/command.h"
 #include "cobalt/debug/json_object.h"
-#include "cobalt/dom/dom_rect.h"
 
 namespace cobalt {
 namespace debug {
@@ -31,8 +25,7 @@
 
 class DOMAgent {
  public:
-  DOMAgent(DebugDispatcher* dispatcher,
-           std::unique_ptr<RenderLayer> render_layer);
+  explicit DOMAgent(DebugDispatcher* dispatcher);
 
   void Thaw(JSONObject agent_state);
   JSONObject Freeze();
@@ -41,22 +34,8 @@
   void Enable(const Command& command);
   void Disable(const Command& command);
 
-  // Highlights a specified node according to highlight parameters.
-  void HighlightNode(const Command& command);
-
-  // Hides the node highlighting.
-  void HideHighlight(const Command& command);
-
-  // Renders a highlight to the overlay.
-  void RenderHighlight(const scoped_refptr<dom::DOMRect>& bounding_rect,
-                       const base::DictionaryValue* highlight_config_value);
-
-  // Helper object to connect to the debug dispatcher, etc.
   DebugDispatcher* dispatcher_;
 
-  // Render layer owned by this object.
-  std::unique_ptr<RenderLayer> render_layer_;
-
   // Map of member functions implementing commands.
   CommandMap<DOMAgent> commands_;
 
diff --git a/src/cobalt/debug/backend/overlay_agent.cc b/src/cobalt/debug/backend/overlay_agent.cc
new file mode 100644
index 0000000..449675c
--- /dev/null
+++ b/src/cobalt/debug/backend/overlay_agent.cc
@@ -0,0 +1,162 @@
+// 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/debug/backend/overlay_agent.h"
+
+#include "cobalt/math/clamp.h"
+#include "cobalt/math/rect_f.h"
+#include "cobalt/render_tree/brush.h"
+#include "cobalt/render_tree/color_rgba.h"
+#include "cobalt/render_tree/composition_node.h"
+#include "cobalt/render_tree/rect_node.h"
+
+namespace cobalt {
+namespace debug {
+namespace backend {
+
+using base::Value;
+using math::Clamp;
+using render_tree::ColorRGBA;
+
+namespace {
+// Definitions from the set specified here:
+// https://chromedevtools.github.io/devtools-protocol/tot/Overlay
+constexpr char kInspectorDomain[] = "Overlay";
+
+// File to load JavaScript Overlay DevTools domain implementation from.
+constexpr char kScriptFile[] = "overlay_agent.js";
+
+// Returns the float value of a param, or 0.0 if undefined or non-numeric.
+float GetFloatParam(const Value* params, base::StringPiece key) {
+  if (!params || !params->is_dict()) return 0.0f;
+  const Value* v = params->FindKey(key);
+  if (!v || !(v->is_double() || v->is_int())) return 0.0f;
+  return static_cast<float>(v->GetDouble());
+}
+
+// Returns an RGBA color defined by a param, or transparent if undefined.
+ColorRGBA RenderColor(const Value* params) {
+  float r = GetFloatParam(params, "r") / 255.0f;
+  float g = GetFloatParam(params, "g") / 255.0f;
+  float b = GetFloatParam(params, "b") / 255.0f;
+  float a = GetFloatParam(params, "a");
+  return ColorRGBA(Clamp(r, 0.0f, 1.0f), Clamp(g, 0.0f, 1.0f),
+                   Clamp(b, 0.0f, 1.0f), Clamp(a, 0.0f, 1.0f));
+}
+
+// Returns a rectangle to render according to the params for the DevTools
+// "Overlay.highlightRect" command.
+// https://chromedevtools.github.io/devtools-protocol/tot/Overlay#method-highlightRect
+scoped_refptr<render_tree::RectNode> RenderHighlightRect(const Value* params) {
+  float x = GetFloatParam(params, "x");
+  float y = GetFloatParam(params, "y");
+  float width = GetFloatParam(params, "width");
+  float height = GetFloatParam(params, "height");
+  ColorRGBA color(RenderColor(params->FindKey("color")));
+  const Value* outline_param = params->FindKey("outlineColor");
+  ColorRGBA outline_color(RenderColor(outline_param));
+  float outline_width = outline_param ? 1.0f : 0.0f;
+  return base::MakeRefCounted<render_tree::RectNode>(
+      math::RectF(x, y, width, height),
+      std::make_unique<render_tree::SolidColorBrush>(color),
+      std::make_unique<render_tree::Border>(render_tree::BorderSide(
+          outline_width, render_tree::kBorderStyleSolid, outline_color)));
+}
+
+}  // namespace
+
+OverlayAgent::OverlayAgent(DebugDispatcher* dispatcher,
+                           std::unique_ptr<RenderLayer> render_layer)
+    : dispatcher_(dispatcher),
+      render_layer_(std::move(render_layer)),
+      ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this, kInspectorDomain)) {
+  DCHECK(dispatcher_);
+  DCHECK(render_layer_);
+
+  commands_["disable"] = &OverlayAgent::Disable;
+  commands_["enable"] = &OverlayAgent::Enable;
+  commands_["highlightNode"] = &OverlayAgent::HighlightNode;
+  commands_["highlightRect"] = &OverlayAgent::HighlightRect;
+  commands_["hideHighlight"] = &OverlayAgent::HideHighlight;
+}
+
+void OverlayAgent::Thaw(JSONObject agent_state) {
+  dispatcher_->AddDomain(kInspectorDomain, commands_.Bind());
+  script_loaded_ = dispatcher_->RunScriptFile(kScriptFile);
+  DLOG_IF(ERROR, !script_loaded_) << "Failed to load " << kScriptFile;
+}
+
+JSONObject OverlayAgent::Freeze() {
+  dispatcher_->RemoveDomain(kInspectorDomain);
+  return JSONObject();
+}
+
+void OverlayAgent::Enable(const Command& command) {
+  if (script_loaded_) {
+    enabled_ = true;
+    command.SendResponse();
+  } else {
+    command.SendErrorResponse(Command::kInternalError,
+                              "Cannot create Overlay inspector.");
+  }
+}
+
+void OverlayAgent::Disable(const Command& command) {
+  enabled_ = false;
+  command.SendResponse();
+}
+
+void OverlayAgent::HighlightNode(const Command& command) {
+  if (!enabled_) {
+    command.SendErrorResponse(Command::kInvalidRequest,
+                              "Overlay inspector not enabled.");
+    return;
+  }
+  // Use the injected JavaScript helper to get the rectangles to highlight for
+  // the specified node.
+  JSONObject rects_response = dispatcher_->RunScriptCommand(
+      "Overlay._highlightNodeRects", command.GetParams());
+  const Value* highlight_rects =
+      rects_response->FindPath({"result", "highlightRects"});
+  if (!highlight_rects) {
+    command.SendErrorResponse(Command::kInvalidParams,
+                              "Can't get node highlights.");
+    return;
+  }
+
+  // Render all the highlight rects as children of a CompositionNode.
+  render_tree::CompositionNode::Builder builder;
+  for (const Value& rect_params : highlight_rects->GetList()) {
+    builder.AddChild(RenderHighlightRect(&rect_params));
+  }
+  render_layer_->SetFrontLayer(
+      base::MakeRefCounted<render_tree::CompositionNode>(builder));
+
+  command.SendResponse();
+}
+
+void OverlayAgent::HighlightRect(const Command& command) {
+  JSONObject params = JSONParse(command.GetParams());
+  render_layer_->SetFrontLayer(RenderHighlightRect(params.get()));
+  command.SendResponse();
+}
+
+void OverlayAgent::HideHighlight(const Command& command) {
+  render_layer_->SetFrontLayer(scoped_refptr<render_tree::Node>());
+  command.SendResponse();
+}
+
+}  // namespace backend
+}  // namespace debug
+}  // namespace cobalt
diff --git a/src/cobalt/debug/backend/overlay_agent.h b/src/cobalt/debug/backend/overlay_agent.h
new file mode 100644
index 0000000..4b7425b
--- /dev/null
+++ b/src/cobalt/debug/backend/overlay_agent.h
@@ -0,0 +1,61 @@
+// 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_DEBUG_BACKEND_OVERLAY_AGENT_H_
+#define COBALT_DEBUG_BACKEND_OVERLAY_AGENT_H_
+
+#include "cobalt/debug/backend/command_map.h"
+#include "cobalt/debug/backend/debug_dispatcher.h"
+#include "cobalt/debug/backend/render_layer.h"
+#include "cobalt/debug/command.h"
+#include "cobalt/debug/json_object.h"
+
+namespace cobalt {
+namespace debug {
+namespace backend {
+
+class OverlayAgent {
+ public:
+  OverlayAgent(DebugDispatcher* dispatcher,
+               std::unique_ptr<RenderLayer> render_layer);
+
+  void Thaw(JSONObject agent_state);
+  JSONObject Freeze();
+
+ private:
+  void Enable(const Command& command);
+  void Disable(const Command& command);
+
+  void HighlightNode(const Command& command);
+  void HighlightRect(const Command& command);
+  void HideHighlight(const Command& command);
+
+  DebugDispatcher* dispatcher_;
+
+  // Render layer owned by this object.
+  std::unique_ptr<RenderLayer> render_layer_;
+
+  // Map of member functions implementing commands.
+  CommandMap<OverlayAgent> commands_;
+
+  // Whether we successfully loaded the agent's JavaScript implementation.
+  bool script_loaded_ = false;
+
+  bool enabled_ = false;
+};
+
+}  // namespace backend
+}  // namespace debug
+}  // namespace cobalt
+
+#endif  // COBALT_DEBUG_BACKEND_OVERLAY_AGENT_H_
diff --git a/src/cobalt/debug/console/content/console_manager.js b/src/cobalt/debug/console/content/console_manager.js
new file mode 100644
index 0000000..532e00e
--- /dev/null
+++ b/src/cobalt/debug/console/content/console_manager.js
@@ -0,0 +1,196 @@
+// 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.
+
+var consoleManager = null;
+
+function start() {
+  window.consoleManager = new ConsoleManager();
+}
+
+window.addEventListener('load', start);
+
+function ConsoleManager() {
+  // Handles communication with the debugger.
+  this.debuggerClient = new DebuggerClient();
+  // Number of animation frame samples since the last update.
+  this.animationFrameSamples = 0;
+  // A list of all the possible interactive consoles.
+  // Each item contains the mode and the reference to the actual console.
+  this.consoleRegistry = null;
+
+  this.initializeConsoles();
+
+  document.addEventListener('keydown', this.handleKeydown.bind(this));
+  document.addEventListener('keyup', this.handleKeyup.bind(this));
+  document.addEventListener('keypress', this.handleKeypress.bind(this));
+  document.addEventListener('wheel', this.handleWheel.bind(this));
+  document.addEventListener('input', this.handleInput.bind(this));
+  if (typeof window.onScreenKeyboard != 'undefined'
+      && window.onScreenKeyboard) {
+    window.onScreenKeyboard.onInput = this.handleInput.bind(this);
+  }
+  window.requestAnimationFrame(this.animate.bind(this));
+}
+
+ConsoleManager.prototype.initializeConsoles = function() {
+  this.consoleRegistry = [
+    {
+      console: new DebugConsole(this.debuggerClient),
+      mode: 'debug',
+      bodyClass: 'debugConsole hud',
+    },
+    {
+      console: new MediaConsole(this.debuggerClient),
+      mode: 'media',
+      bodyClass: 'mediaConsole',
+    },
+  ];
+
+  this.hudConsole = this.consoleRegistry[0].console;
+
+  this.consoleRegistry.forEach( entry => {
+    let ensureConsolesAreValid = function(method) {
+      if(typeof entry.console[method] != "function") {
+        console.warn(`Console "${entry.mode}" ${method}() is not implemented. \
+            Providing default empty implementation.`);
+        // Provide a default not-implemented warning message.
+        let consoleName = entry.mode;
+        let notImplementedMessage = function() {
+          console.log(
+              `Console "${consoleName}" ${method}() is not implemented.`);
+        };
+        entry.console[method] = notImplementedMessage;
+      }
+    };
+    ensureConsolesAreValid("update");
+    ensureConsolesAreValid("setVisible");
+    ensureConsolesAreValid("onKeydown");
+    ensureConsolesAreValid("onKeyup");
+    ensureConsolesAreValid("onKeypress");
+    ensureConsolesAreValid("onInput");
+    ensureConsolesAreValid("onWheel");
+  });
+}
+
+ConsoleManager.prototype.update = function() {
+  let mode = window.debugHub.getDebugConsoleMode();
+
+  if (mode !== 'off') {
+    this.debuggerClient.attach();
+  }
+
+  let activeConsole = this.getActiveConsole();
+  let bodyClass = '';
+  if (mode == 'hud') {
+    bodyClass = 'hud';
+    // The HUD is owned by the debug console, but since it has its own mode
+    // dedicated to it, it needs to be specifically updated when it is visible.
+    // TODO: Factor out hudConsole into its own console.
+    this.hudConsole.updateHud();
+  } else if (mode != 'off' && mode != 'hud') {
+    bodyClass = activeConsole.bodyClass;
+  }
+  document.body.className = bodyClass;
+
+  this.consoleRegistry.forEach((entry) => {
+    entry.console.setVisible(entry == activeConsole);
+  });
+
+  if (mode !== 'off') {
+    if (activeConsole) { activeConsole.console.update(); }
+  }
+}
+
+// Animation callback: updates state and animated nodes.
+ConsoleManager.prototype.animate = function(time) {
+  const subsample = 8;
+  this.animationFrameSamples = (this.animationFrameSamples + 1) % subsample;
+  if (this.animationFrameSamples == 0) {
+    this.update();
+  }
+  window.requestAnimationFrame(this.animate.bind(this));
+}
+
+ConsoleManager.prototype.getActiveConsole = function() {
+  let mode = window.debugHub.getDebugConsoleMode();
+  return this.consoleRegistry.find( entry => entry.mode === mode );
+}
+
+ConsoleManager.prototype.handleKeydown =  function(event) {
+  // Map of 'Unidentified' additional Cobalt keyCodes to equivalent keys.
+  const unidentifiedCobaltKeyMap = {
+    // kSbKeyGamepad1
+    0x8000: 'Enter',
+    // kSbKeyGamepad2
+    0x8001: 'Esc',
+    // kSbKeyGamepad3
+    0x8002: 'Home',
+    // kSbKeyGamepad5
+    0x8008: 'Enter',
+    // kSbKeyGamepad6
+    0x8009: 'Enter',
+    // kSbKeyGamepadDPadUp
+    0x800C: 'ArrowUp',
+    // kSbKeyGamepadDPadDown
+    0x800D: 'ArrowDown',
+    // kSbKeyGamepadDPadLeft
+    0x800E: 'ArrowLeft',
+    // kSbKeyGamepadDPadRight
+    0x800F: 'ArrowRight',
+    // kSbKeyGamepadLeftStickUp
+    0x8011: 'ArrowUp',
+    // kSbKeyGamepadLeftStickDown
+    0x8012: 'ArrowDown',
+    // kSbKeyGamepadLeftStickLeft
+    0x8013: 'ArrowLeft',
+    // kSbKeyGamepadLeftStickRight
+    0x8014: 'ArrowRight',
+    // kSbKeyGamepadRightStickUp
+    0x8015: 'ArrowUp',
+    // kSbKeyGamepadRightStickDown
+    0x8016: 'ArrowDown',
+    // kSbKeyGamepadRightStickLeft
+    0x8017: 'ArrowLeft',
+    // kSbKeyGamepadRightStickRight
+    0x8018: 'ArrowRight'
+  };
+
+  let key = event.key;
+  if (key == 'Unidentified') {
+    key = unidentifiedCobaltKeyMap[event.keyCode] || 'Unidentified';
+  }
+
+  let active = this.getActiveConsole();
+  if (active) { active.console.onKeydown(event); }
+}
+
+ConsoleManager.prototype.handleKeyup = function(event) {
+  let active = this.getActiveConsole();
+  if (active) { active.console.onKeyup(event); }
+}
+
+ConsoleManager.prototype.handleKeypress = function(event) {
+  let active = this.getActiveConsole();
+  if (active) { active.console.onKeypress(event); }
+}
+
+ConsoleManager.prototype.handleInput = function(event) {
+  let active = this.getActiveConsole();
+  if (active) { active.console.onInput(event); }
+}
+
+ConsoleManager.prototype.handleWheel = function(event) {
+  let active = this.getActiveConsole();
+  if (active) { active.console.onWheel(event); }
+}
diff --git a/src/cobalt/debug/console/content/debug_commands.js b/src/cobalt/debug/console/content/debug_commands.js
index e91754d..8c4456b 100644
--- a/src/cobalt/debug/console/content/debug_commands.js
+++ b/src/cobalt/debug/console/content/debug_commands.js
@@ -13,114 +13,117 @@
 // limitations under the License.
 
 function initDebugCommands() {
-  debug = new Object();
-  d = debug;
+  let debugCommands = {};
 
-  debug.cvalList = function() {
+  debugCommands.cvalList = function() {
     var result = consoleValues.listAll();
-    printToMessageLog(messageLog.INTERACTIVE, result);
+    printToMessageLog(MessageLog.INTERACTIVE, result);
   }
-  debug.cvalList.shortHelp = 'List all registered console values.';
-  debug.cvalList.longHelp =
+  debugCommands.cvalList.shortHelp = 'List all registered console values.';
+  debugCommands.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.';
 
-  debug.cvalAdd = function(substringToMatch) {
+  debugCommands.cvalAdd = function(substringToMatch) {
     var result = consoleValues.addActive(substringToMatch);
-    printToMessageLog(messageLog.INTERACTIVE, result);
+    printToMessageLog(MessageLog.INTERACTIVE, result);
     // After each change, save the active set with the default key.
     this.cvalSave();
   }
-  debug.cvalAdd.shortHelp = 'Adds one or more consoles value to the HUD.';
-  debug.cvalAdd.longHelp =
+  debugCommands.cvalAdd.shortHelp =
+      'Adds one or more consoles value to the HUD.';
+  debugCommands.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.';
 
-  debug.cvalRemove = function(substringToMatch) {
+  debugCommands.cvalRemove = function(substringToMatch) {
     var result = consoleValues.removeActive(substringToMatch);
-    printToMessageLog(messageLog.INTERACTIVE, result);
+    printToMessageLog(MessageLog.INTERACTIVE, result);
     // After each change, save the active set with the default key.
     this.cvalSave();
   }
-  debug.cvalRemove.shortHelp =
+  debugCommands.cvalRemove.shortHelp =
       'Removes one or more consoles value from the HUD.';
-  debug.cvalRemove.longHelp =
+  debugCommands.cvalRemove.longHelp =
       'Removes any of the consolve values displayed in the HUD ' +
       'whose name matches one of the specified space-separated prefixes.';
 
-  debug.cvalSave = function(key) {
+  debugCommands.cvalSave = function(key) {
     var result = consoleValues.saveActiveSet(key);
-    printToMessageLog(messageLog.INTERACTIVE, result);
+    printToMessageLog(MessageLog.INTERACTIVE, result);
   }
-  debug.cvalSave.shortHelp =
+  debugCommands.cvalSave.shortHelp =
       'Saves the current set of console values displayed in the HUD.';
-  debug.cvalSave.longHelp =
+  debugCommands.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.';
 
-  debug.cvalLoad = function(key) {
+  debugCommands.cvalLoad = function(key) {
     var result = consoleValues.loadActiveSet(key);
-    printToMessageLog(messageLog.INTERACTIVE, result);
+    printToMessageLog(MessageLog.INTERACTIVE, result);
   }
-  debug.cvalLoad.shortHelp =
+  debugCommands.cvalLoad.shortHelp =
       'Loads a previously stored set of console values displayed in the HUD.';
-  debug.cvalLoad.longHelp =
+  debugCommands.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.';
 
-  debug.history = history;
-  debug.history.shortHelp = 'Display command history.';
-  debug.history.longHelp =
+  debugCommands.history = history;
+  debugCommands.history.shortHelp = 'Display command history.';
+  debugCommands.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.'
 
-  debug.help = help;
-  debug.help.shortHelp = 'Display this message, or detail for a specific command.';
-  debug.help.longHelp =
+  debugCommands.help = help.bind(this, debugCommands);
+  debugCommands.help.shortHelp =
+      'Display this message, or detail for a specific command.';
+  debugCommands.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.';
 
-  debug.dir = dir;
-  debug.dir.shortHelp =
+  debugCommands.dir = dir;
+  debugCommands.dir.shortHelp =
       'Lists the properties of an object in the main web module.';
-  debug.dir.longHelp =
+  debugCommands.dir.longHelp =
       'Lists the properties of the specified object in the main web module. ' +
       'Remember to enclose the name of the object in quotes.';
 
-  debug.debugger = function() {
+  debugCommands.debugger = function() {
     return debuggerClient;
   }
-  debug.debugger.shortHelp =
+  debugCommands.debugger.shortHelp =
       'Get the debugger client';
-  debug.debugger.longHelp =
+  debugCommands.debugger.longHelp =
       'Get the debugger client. The debugger client can be used to issue ' +
       'JavaScript debugging commands to the main web module.';
 
-  addConsoleCommands();
+  addConsoleCommands(debugCommands);
+
+  return debugCommands;
 }
 
-function help(command) {
+function help(debugCommands, command) {
   var helpString = '';
   if (command) {
     // Detailed help on a specific command.
-    if (debug[command]) {
-      helpString = debug[command].longHelp;
+    if (debugCommands[command]) {
+      helpString = debugCommands[command].longHelp;
     } else {
       helpString = 'Command "' + command + '" not found.';
     }
   } else {
     // Summary help for all commands.
     helpString = 'Cobalt Debug Console commands:\n\n';
-    for (cmd in debug) {
+    for (cmd in debugCommands) {
       helpString += 'debug.' + cmd + '() - ';
-      helpString += debug[cmd].shortHelp + '\n';
+      helpString += debugCommands[cmd].shortHelp + '\n';
     }
     helpString +=
         '\nYou are entering JavaScript, so remember to use parentheses, ' +
@@ -129,13 +132,13 @@
         'All other text will be executed as JavaScript in the main web ' +
         'module.\n';
   }
-  printToMessageLog(messageLog.INTERACTIVE, helpString);
+  printToMessageLog(MessageLog.INTERACTIVE, helpString);
 }
 
 function history() {
   var history = commandInput.getHistory();
   for (var i = 0; i < history.length; i += 1) {
-    printToMessageLog(messageLog.INTERACTIVE, i + ' ' + history[i]);
+    printToMessageLog(MessageLog.INTERACTIVE, i + ' ' + history[i]);
   }
 }
 
@@ -148,18 +151,18 @@
   executeMain(js);
 }
 
-function addConsoleCommands() {
+function addConsoleCommands(debugCommands) {
   var consoleCommands = window.debugHub.consoleCommands;
   for (var i = 0; i < consoleCommands.length; i++) {
     var c = consoleCommands[i];
-    addOneConsoleCommand(c.command, c.shortHelp, c.longHelp);
+    addOneConsoleCommand(debugCommands, c.command, c.shortHelp, c.longHelp);
   }
 }
 
-function addOneConsoleCommand(command, shortHelp, longHelp) {
-  debug[command] = function(message) {
+function addOneConsoleCommand(debugCommands, command, shortHelp, longHelp) {
+  debugCommands[command] = function(message) {
     window.debugHub.sendConsoleCommand(command, message);
   }
-  debug[command].shortHelp = shortHelp;
-  debug[command].longHelp = longHelp;
+  debugCommands[command].shortHelp = shortHelp;
+  debugCommands[command].longHelp = longHelp;
 }
diff --git a/src/cobalt/debug/console/content/debug_console.css b/src/cobalt/debug/console/content/debug_console.css
index d0ccc4d..f087f24 100644
--- a/src/cobalt/debug/console/content/debug_console.css
+++ b/src/cobalt/debug/console/content/debug_console.css
@@ -12,6 +12,9 @@
   color: #FFFFFF;
   display: none;
 }
+body.hud #hudFrame {
+  display: block;
+}
 
 #hud {
   position: absolute;
@@ -27,7 +30,7 @@
   padding: 0.625em;
 }
 
-#consoleFrame {
+#debugConsoleFrame {
   position: absolute;
   top: 0;
   left: 0;
@@ -38,6 +41,9 @@
   overflow: hidden;
   display: none;
 }
+body.debugConsole #debugConsoleFrame {
+  display: block;
+}
 
 #messageContainerFrame {
   position: absolute;
@@ -122,3 +128,33 @@
   border-style: solid;
   padding: 0.625em;
 }
+
+#mediaConsoleFrame {
+  position: absolute;
+  top: 75%;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  width: 100%;
+  background-color: rgba(128, 128, 128, 0.6);
+  color: #FFFFFF;
+  display: none;
+}
+body.mediaConsole #mediaConsoleFrame {
+  display: block;
+}
+
+#mediaConsole {
+  position: absolute;
+  top: 0.625em;
+  left: 0.625em;
+  bottom: 0.625em;
+  right: 0.625em;
+  background-color: rgba(0, 0, 0, 0.6);
+  color: #FFFFFF;
+  border: 0.0625em;
+  border-color: #606060;
+  border-style: solid;
+  padding: 0.625em;
+  white-space: pre;
+}
diff --git a/src/cobalt/debug/console/content/debug_console.html b/src/cobalt/debug/console/content/debug_console.html
index a60c6e4..baed2fd 100644
--- a/src/cobalt/debug/console/content/debug_console.html
+++ b/src/cobalt/debug/console/content/debug_console.html
@@ -5,23 +5,28 @@
   <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>
 
 <body>
   <div id="hudFrame">
     <div id="hud"></div>
   </div>
-  <div id="consoleFrame">
+  <div id="debugConsoleFrame">
     <div id="messageContainerFrame">
-      <div id = "messageContainer"></div>
+      <div id="messageContainer"></div>
     </div>
     <div id="in">> _</div>
   </div>
+  <div id="mediaConsoleFrame">
+    <div id="mediaConsole"></div>
+  </div>
 </body>
 
 </html>
diff --git a/src/cobalt/debug/console/content/debug_console.js b/src/cobalt/debug/console/content/debug_console.js
index e1028d5..088b744 100644
--- a/src/cobalt/debug/console/content/debug_console.js
+++ b/src/cobalt/debug/console/content/debug_console.js
@@ -12,171 +12,78 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-
-// Text the user has typed since last hitting Enter
-var inputText = '';
-// The DOM node used as a container for message nodes.
-var messageLog = null;
-// Stores and manipulates the set of console values.
-var consoleValues = null;
-// Handles command input, editing, history traversal, etc.
-var commandInput = null;
-// Object to store methods to be executed in the debug console.
-var debug = null;
-// Shorthand reference for the debug object.
-var d = null;
-// Handles communication with the debugger.
-var debuggerClient = null;
-// Number of animation frame samples since the last update.
-var animationFrameSamples = 0;
-
-// Map of 'Unidentified' additional Cobalt keyCodes to equivalent keys.
-var unidentifiedCobaltKeyMap = {
-  // kSbKeyGamepad1
-  0x8000: 'Enter',
-  // kSbKeyGamepad2
-  0x8001: 'Esc',
-  // kSbKeyGamepad3
-  0x8002: 'Home',
-  // kSbKeyGamepad5
-  0x8008: 'Enter',
-  // kSbKeyGamepad6
-  0x8009: 'Enter',
-  // kSbKeyGamepadDPadUp
-  0x800C: 'ArrowUp',
-  // kSbKeyGamepadDPadDown
-  0x800D: 'ArrowDown',
-  // kSbKeyGamepadDPadLeft
-  0x800E: 'ArrowLeft',
-  // kSbKeyGamepadDPadRight
-  0x800F: 'ArrowRight',
-  // kSbKeyGamepadLeftStickUp
-  0x8011: 'ArrowUp',
-  // kSbKeyGamepadLeftStickDown
-  0x8012: 'ArrowDown',
-  // kSbKeyGamepadLeftStickLeft
-  0x8013: 'ArrowLeft',
-  // kSbKeyGamepadLeftStickRight
-  0x8014: 'ArrowRight',
-  // kSbKeyGamepadRightStickUp
-  0x8015: 'ArrowUp',
-  // kSbKeyGamepadRightStickDown
-  0x8016: 'ArrowDown',
-  // kSbKeyGamepadRightStickLeft
-  0x8017: 'ArrowLeft',
-  // kSbKeyGamepadRightStickRight
-  0x8018: 'ArrowRight'
-};
-
-function createMessageLog() {
-  var messageContainer = document.getElementById('messageContainer');
-  messageLog = new MessageLog(messageContainer);
-}
-
-function createCommandInput() {
-  var inputElem = document.getElementById('in');
-  this.commandInput = new CommandInput(inputElem);
-}
-
-function createConsoleValues() {
-  // Create the console values and attempt to load the active set using the
-  // default key. If this fails, it will leave the active set equal to the
-  // all registered CVals.
-  consoleValues = new ConsoleValues();
-  var loadResult = consoleValues.loadActiveSet();
-  printToMessageLog(messageLog.INTERACTIVE, loadResult);
-}
-
-function createDebuggerClient() {
-  debuggerClient = new DebuggerClient();
-}
-
-function showBlockElem(elem, doShow) {
-  if (elem) {
-    var display = doShow ? 'block' : 'none';
-    if (elem.style.display != display) {
-      elem.style.display = display;
-    }
+// Global for other modules to use.
+function printToMessageLog(severity, message) {
+  if (window.debugConsoleInstance) {
+    window.debugConsoleInstance.printToMessageLog(severity, message);
   }
 }
 
-function showConsole(doShow) {
-  showBlockElem(document.getElementById('consoleFrame'), doShow);
-  messageLog.setVisible(doShow);
+function DebugConsole(debuggerClient) {
+  window.debugConsoleInstance = this;
+
+  this.debuggerClient = debuggerClient;
+  // Text the user has typed since last hitting Enter
+  this.inputText = '';
+
+  this.commandInput = new CommandInput(document.getElementById('in'));
+  this.messageLog = new MessageLog(document.getElementById('messageContainer'));
+
+  this.consoleValues = new ConsoleValues();
+  let loadResult = this.consoleValues.loadActiveSet();
+  this.printToMessageLog(MessageLog.INTERACTIVE, loadResult);
+
+  this.debugCommands = initDebugCommands();
 }
 
-function isConsoleVisible() {
-  var mode = window.debugHub.getDebugConsoleMode();
-  return mode >= window.debugHub.DEBUG_CONSOLE_ON;
+DebugConsole.prototype.printToMessageLog = function(severity, message) {
+  this.messageLog.addMessage(severity, message);
 }
 
-function printToMessageLog(severity, message) {
-  messageLog.addMessage(severity, message);
-}
-
-function showHud(doShow) {
-  showBlockElem(document.getElementById('hudFrame'), doShow);
-}
-
-function printToHud(message) {
-  var elem = document.getElementById('hud');
+DebugConsole.prototype.printToHud = function(message) {
+  let elem = document.getElementById('hud');
   elem.textContent = message;
 }
 
-function updateHud(time) {
-  var mode = window.debugHub.getDebugConsoleMode();
-  if (mode >= window.debugHub.DEBUG_CONSOLE_HUD) {
-    consoleValues.update();
-    var cvalString = consoleValues.toString();
-    printToHud(cvalString);
-  }
+DebugConsole.prototype.updateHud = function() {
+  let mode = window.debugHub.getDebugConsoleMode();
+  this.consoleValues.update();
+  let cvalString = this.consoleValues.toString();
+  this.printToHud(cvalString);
 }
 
-function updateMode() {
-  var mode = window.debugHub.getDebugConsoleMode();
-  showConsole(mode >= window.debugHub.DEBUG_CONSOLE_ON);
-  showHud(mode >= window.debugHub.DEBUG_CONSOLE_HUD);
+DebugConsole.prototype.setVisible = function(visible) {
+  this.messageLog.setVisible(visible);
 }
 
-// Animation callback: updates state and animated nodes.
-function animate(time) {
-  var subsample = 8;
-  animationFrameSamples = (animationFrameSamples + 1) % subsample;
-  if (animationFrameSamples == 0) {
-    updateMode();
-    updateHud(time);
-    if (isConsoleVisible()) {
-      commandInput.animateBlink();
-      // This will do nothing if debugger is already attached.
-      debuggerClient.attach();
-    }
-  }
-  window.requestAnimationFrame(animate);
+DebugConsole.prototype.update = function() {
+  this.commandInput.animateBlink();
+  this.updateHud();
 }
 
 // Executes a command from the history buffer.
 // Index should be an integer (positive is an absolute index, negative is a
 // number of commands back from the current) or '!' to execute the last command.
-function executeCommandFromHistory(idx) {
+DebugConsole.prototype.executeCommandFromHistory = function(idx) {
   if (idx == '!') {
     idx = -1;
   }
   idx = parseInt(idx);
-  commandInput.setCurrentCommandFromHistory(idx);
-  executeCurrentCommand();
+  this.commandInput.setCurrentCommandFromHistory(idx);
+  this.executeCurrentCommand();
 }
 
 // Special commands that are executed immediately, not as JavaScript,
 // e.g. !N to execute the Nth command in the history buffer.
 // Returns true if the command is processed here, false otherwise.
-function executeImmediate(command) {
+DebugConsole.prototype.executeImmediate = function(command) {
   if (command[0] == '!') {
-    executeCommandFromHistory(command.substring(1));
+    this.executeCommandFromHistory(command.substring(1));
     return true;
   } else if (command.trim() == 'help') {
     // Treat 'help' as a special case for users not expecting JS execution.
     help();
-    commandInput.clearCurrentCommand();
+    this.commandInput.clearCurrentCommand();
     return true;
   }
   return false;
@@ -185,9 +92,11 @@
 // 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).
-function executeDebug(command) {
+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;
   }
@@ -197,8 +106,9 @@
 // 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.
-function executeMain(command) {
-  debuggerClient.evaluate(command);
+DebugConsole.prototype.executeMain = function(command) {
+  let callback = this.printToLogCallback.bind(this);
+  this.debuggerClient.evaluate(command, callback);
 }
 
 // Executes a command entered by the user.
@@ -208,118 +118,112 @@
 // 3. If no matching command is found, pass to the Cobalt DebugHub.
 //    DebugHub will execute any commands recognized on the C++ side,
 //    or pass to the main web module to be executed as JavaScript.
-function executeCommand(command) {
-  if (executeImmediate(command)) {
-    printToMessageLog(messageLog.INTERACTIVE, '');
+DebugConsole.prototype.executeCommand = function(command) {
+  if (this.executeImmediate(command)) {
+    this.printToMessageLog(MessageLog.INTERACTIVE, '');
     return;
   }
-  commandInput.storeAndClearCurrentCommand();
-  if (executeDebug(command)) {
-    printToMessageLog(messageLog.INTERACTIVE, '');
+  this.commandInput.storeAndClearCurrentCommand();
+  if (this.executeDebug(command)) {
+    this.printToMessageLog(MessageLog.INTERACTIVE, '');
     return;
   }
-  executeMain(command);
+  this.executeMain(command);
 }
 
 // Executes the current command in the CommandInput object.
 // Typically called when the user hits Enter.
-function executeCurrentCommand() {
-  var command = commandInput.getCurrentCommand();
-  printToMessageLog(messageLog.INTERACTIVE, '> ' + command);
-  executeCommand(command);
+DebugConsole.prototype.executeCurrentCommand = function() {
+  let command = this.commandInput.getCurrentCommand();
+  this.printToMessageLog(MessageLog.INTERACTIVE, '> ' + command);
+  this.executeCommand(command);
 }
 
-function onWheel(event) {
+DebugConsole.prototype.onWheel = function(event) {
   if (event.deltaY > 0) {
-    messageLog.scrollDown(event.deltaY);
+    this.messageLog.scrollDown(event.deltaY);
   } else if (event.deltaY < 0) {
-    messageLog.scrollUp(-event.deltaY);
+    this.messageLog.scrollUp(-event.deltaY);
   }
 }
 
-function onKeydown(event) {
-  var key = event.key;
-  if (key == 'Unidentified') {
-    key = unidentifiedCobaltKeyMap[event.keyCode] || 'Unidentified';
-  }
+DebugConsole.prototype.onKeydown = function(event) {
+  let key = event.key;
 
   if (key == 'ArrowLeft') {
-    commandInput.moveCursor(-1);
+    this.commandInput.moveCursor(-1);
   } else if (key == 'ArrowRight') {
-    commandInput.moveCursor(1);
+    this.commandInput.moveCursor(1);
   } else if (key == 'ArrowUp') {
-    commandInput.back();
+    this.commandInput.back();
   } else  if (key == 'ArrowDown') {
-    commandInput.forward();
+    this.commandInput.forward();
   } else if (key == 'Backspace') {
-    commandInput.deleteCharBehindCursor();
+    this.commandInput.deleteCharBehindCursor();
   } else if (key == 'Enter') {
-    executeCurrentCommand();
+    this.executeCurrentCommand();
   } else if (key == 'PageUp') {
-    messageLog.pageUp();
+    this.messageLog.pageUp();
   } else if (key == 'PageDown') {
-    messageLog.pageDown();
+    this.messageLog.pageDown();
   } else if (key == 'Delete') {
-    commandInput.deleteCharAtCursor();
+    this.commandInput.deleteCharAtCursor();
   } else if (key == 'Home') {
     if (event.ctrlKey) {
-      messageLog.toHead();
+      this.messageLog.toHead();
     } else {
-      commandInput.moveCursor(-1000);
+      this.commandInput.moveCursor(-1000);
     }
   } else if (key == 'End') {
     if (event.ctrlKey) {
-      messageLog.toTail();
+      this.messageLog.toTail();
     } else {
-      commandInput.moveCursor(1000);
+      this.commandInput.moveCursor(1000);
     }
   }
 }
 
-function onKeyup(event) {}
+DebugConsole.prototype.onKeyup = function(event) {}
 
-function onKeypress(event) {
-  var mode = window.debugHub.getDebugConsoleMode();
-  if (mode >= window.debugHub.DEBUG_CONSOLE_ON) {
-    event.preventDefault();
-    event.stopPropagation();
-    var c = event.charCode;
-    // If we have a printable character, insert it; otherwise ignore.
-    if (c >= 0x20 && c <= 0x7e) {
-      commandInput.insertStringBehindCursor(String.fromCharCode(c));
-    }
+DebugConsole.prototype.onKeypress = function(event) {
+  event.preventDefault();
+  event.stopPropagation();
+  let c = event.charCode;
+  // If we have a printable character, insert it; otherwise ignore.
+  if (c >= 0x20 && c <= 0x7e) {
+    this.commandInput.insertStringBehindCursor(String.fromCharCode(c));
   }
 }
 
-function onInput(event) {
+DebugConsole.prototype.onInput = function(event) {
   console.log('In DebugConsole onInput, event.data ' + event.data);
-  var mode = window.debugHub.getDebugConsoleMode();
-  if (mode >= window.debugHub.DEBUG_CONSOLE_ON && event.data) {
+  if (event.data) {
     event.preventDefault();
     event.stopPropagation();
-    commandInput.insertStringBehindCursor(event.data);
+    this.commandInput.insertStringBehindCursor(event.data);
   }
 }
 
-function start() {
-  createCommandInput();
-  createMessageLog();
-  createDebuggerClient();
-  showHud(false);
-  showConsole(false);
-  createConsoleValues();
-  initDebugCommands();
-  document.addEventListener('wheel', onWheel);
-  document.addEventListener('keypress', onKeypress);
-  document.addEventListener('keydown', onKeydown);
-  document.addEventListener('keyup', onKeyup);
-  if (typeof window.onScreenKeyboard != 'undefined'
-      && window.onScreenKeyboard) {
-    window.onScreenKeyboard.oninput = onInput;
+DebugConsole.prototype.printToLogCallback = function(result) {
+  if (result.wasThrown) {
+    this.printToMessageLog(MessageLog.ERROR,
+                      'Uncaught ' + result.result.description);
+  } else if (result.result.preview) {
+    this.printToMessageLog(MessageLog.INFO, result.result.preview.description);
+    if (result.result.preview.properties) {
+      for (let i = 0; i < result.result.preview.properties.length; ++i) {
+        let property = result.result.preview.properties[i];
+        this.printToMessageLog(MessageLog.INFO,
+                          '  ' + property.name + ': ' + property.value);
+      }
+    }
+    if (result.result.preview.overflow) {
+      this.printToMessageLog(MessageLog.INFO, '  ...');
+    }
+  } else if (result.result.description) {
+    this.printToMessageLog(MessageLog.INFO, result.result.description);
+  } else if (result.result.value) {
+    this.printToMessageLog(MessageLog.INFO, result.result.value.toString());
   }
-  curr = window.performance.now();
-  window.requestAnimationFrame(animate);
+  this.printToMessageLog(MessageLog.INFO, '');
 }
-
-window.addEventListener('load', start);
-
diff --git a/src/cobalt/debug/console/content/debugger_client.js b/src/cobalt/debug/console/content/debugger_client.js
index cc2b168..2ceb3e0 100644
--- a/src/cobalt/debug/console/content/debugger_client.js
+++ b/src/cobalt/debug/console/content/debugger_client.js
@@ -28,7 +28,7 @@
 DebuggerClient.prototype.attach = function() {
   if (this.attachState == this.DEBUGGER_DETACHED) {
     this.attachState = this.DEBUGGER_ATTACHING;
-    printToMessageLog(messageLog.INTERACTIVE,
+    printToMessageLog(MessageLog.INTERACTIVE,
                       'Attempting to attach to debugger...');
     this.scripts = [];
     debugHub.onEvent.addListener(this.onEventCallback);
@@ -37,7 +37,7 @@
     this.sendCommand('Log.enable');
     this.sendCommand('Runtime.enable');
   } else if (this.attachState == this.DEBUGGER_ATTACHING) {
-    printToMessageLog(messageLog.INTERACTIVE,
+    printToMessageLog(MessageLog.INTERACTIVE,
                       'Still attempting to attach to debugger...');
   }
 }
@@ -49,7 +49,7 @@
   for (var i in this.scripts) {
     var index = this.pad(i, 3);
     var scriptUrl = this.scripts[i].url;
-    printToMessageLog(messageLog.INTERACTIVE, index + ': ' + scriptUrl);
+    printToMessageLog(MessageLog.INTERACTIVE, index + ': ' + scriptUrl);
   }
 }
 
@@ -74,12 +74,11 @@
   var lines = scriptSource.split('\n');
   for (var i = 0; i < lines.length; i++) {
     var index = this.pad(i + 1, 4);
-    printToMessageLog(messageLog.INFO, index + ': ' + lines[i]);
+    printToMessageLog(MessageLog.INFO, index + ': ' + lines[i]);
   }
 }
 
-DebuggerClient.prototype.evaluate = function(expression) {
-  var callback = this.evaluateCallback.bind(this);
+DebuggerClient.prototype.evaluate = function(expression, callback) {
   var method = 'Runtime.evaluate';
   var params = {};
   params.contextId = this.executionContext;
@@ -91,30 +90,6 @@
   this.sendCommand(method, params, callback);
 }
 
-DebuggerClient.prototype.evaluateCallback = function(result) {
-  if (result.wasThrown) {
-    printToMessageLog(messageLog.ERROR,
-                      'Uncaught ' + result.result.description);
-  } else if (result.result.preview) {
-    printToMessageLog(messageLog.INFO, result.result.preview.description);
-    if (result.result.preview.properties) {
-      for (var i = 0; i < result.result.preview.properties.length; ++i) {
-        var property = result.result.preview.properties[i];
-        printToMessageLog(messageLog.INFO,
-                          '  ' + property.name + ': ' + property.value);
-      }
-    }
-    if (result.result.preview.overflow) {
-      printToMessageLog(messageLog.INFO, '  ...');
-    }
-  } else if (result.result.description) {
-    printToMessageLog(messageLog.INFO, result.result.description);
-  } else if (result.result.value) {
-    printToMessageLog(messageLog.INFO, result.result.value.toString());
-  }
-  printToMessageLog(messageLog.INFO, '');
-}
-
 // All debugger commands are routed through this method. Converts the command
 // parameters into a JSON string to pass to the debug dispatcher.
 DebuggerClient.prototype.sendCommand = function(method, commandParams,
@@ -133,7 +108,7 @@
 
   if (response && response.error) {
     printToMessageLog(
-        messageLog.ERROR,
+        MessageLog.ERROR,
         '[ERROR(' + response.error.code + '):' + method + '] ' +
             response.error.message);
   } else if (callback) {
@@ -149,10 +124,10 @@
 
 DebuggerClient.prototype.onAttach = function() {
   if (debugHub.lastError) {
-    printToMessageLog(messageLog.WARNING, 'Could not attach to debugger.');
+    printToMessageLog(MessageLog.WARNING, 'Could not attach to debugger.');
     this.attachState = this.DEBUGGER_DETACHED;
   } else {
-    printToMessageLog(messageLog.INTERACTIVE, 'Debugger attached.');
+    printToMessageLog(MessageLog.INTERACTIVE, 'Debugger attached.');
     this.attachState = this.DEBUGGER_ATTACHED;
   }
 }
@@ -178,13 +153,13 @@
 }
 
 DebuggerClient.prototype.onDetached = function() {
-  printToMessageLog(messageLog.INTERACTIVE, 'Debugger detached.');
+  printToMessageLog(MessageLog.INTERACTIVE, 'Debugger detached.');
   this.attachState = this.DEBUGGER_DETACHED;
 }
 
 DebuggerClient.prototype.onExecutionContextCreated = function(params) {
   this.executionContext = params.context.id;
-  printToMessageLog(messageLog.INFO,
+  printToMessageLog(MessageLog.INFO,
                     'Execution context created: ' + this.executionContext);
 }
 
@@ -205,9 +180,9 @@
 DebuggerClient.prototype.onConsoleApiCalled = function(params) {
   var severity = params.type;
   if (severity === "assert") {
-    severity = messageLog.ERROR;
+    severity = MessageLog.ERROR;
   } else if (severity === "log") {
-    severity = messageLog.INFO;
+    severity = MessageLog.INFO;
   }
 
   var message = '';
@@ -222,7 +197,7 @@
     }
   }
 
-  printToMessageLog(messageLog.CONSOLE + severity, message);
+  printToMessageLog(MessageLog.CONSOLE + severity, message);
 }
 
 DebuggerClient.prototype.onScriptParsed = function(params) {
diff --git a/src/cobalt/debug/console/content/media_console.js b/src/cobalt/debug/console/content/media_console.js
new file mode 100644
index 0000000..66b33d1
--- /dev/null
+++ b/src/cobalt/debug/console/content/media_console.js
@@ -0,0 +1,221 @@
+// 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.
+
+const kNewline = '\r\n';
+
+const kMediaConsoleFrame = 'mediaConsoleFrame';
+
+const videoCodecFamilies = ['av01', 'avc1', 'avc3', 'hev1', 'hvc1', 'vp09',
+      'vp8', 'vp9'];
+const audioCodecFamilies = ['ac-3', 'ec-3', 'mp4a', 'opus', 'vorbis'];
+
+const kAltModifier = 'A-';
+const kCtrlModifier = 'C-';
+
+function MediaConsole(debuggerClient) {
+  let mediaConsoleNode = document.getElementById('mediaConsole');
+
+  this.debuggerClient = debuggerClient;
+  this.getDisabledMediaCodecs = window.debugHub.cVal.getValue.bind(
+      window.debugHub.cVal, 'Media.DisabledMediaCodecs');
+  this.setDisabledMediaCodecs = window.debugHub.sendConsoleCommand.bind(
+      window.debugHub, 'disable_media_codecs');
+  this.lastUpdateTime = window.performance.now();
+  this.updatePeriod = 1000;
+
+  // Registry of all the hotkeys and their function handlers.
+  // NOTE: This is not the full list, since more will be added once the codec
+  // specific controls are registered.
+  this.hotkeyRegistry = new Map([
+      ['p', {handler: this.onPlayPause.bind(this), help: '(p)ause/(p)lay'}],
+      [']', {handler: this.onIncreasePlaybackRate.bind(this),
+        help: '(])Increase Playback Rate'}],
+      ['[', {handler: this.onDecreasePlaybackRate.bind(this),
+        help: '([)Decrease Playback Rate'}],
+  ]);
+
+  this.hotkeyHelpNode = document.createTextNode('');
+  mediaConsoleNode.appendChild(this.hotkeyHelpNode);
+
+  // Dynamically added div will be changed as we get state information.
+  let playerStateElem = document.createElement('div');
+  this.playerStateText = document.createTextNode('');
+  playerStateElem.appendChild(this.playerStateText);
+  mediaConsoleNode.appendChild(playerStateElem);
+
+  this.printToMediaConsoleCallback = this.printToMediaConsole.bind(this);
+  this.codecSupportMap = new Map();
+  this.isInitialized = false;
+}
+
+MediaConsole.prototype.setVisible = function(visible) {
+  if (visible) {
+    this.initialize();
+  }
+}
+
+MediaConsole.prototype.initialize = function() {
+  if (this.initialized) { return; }
+
+  this.debuggerClient.attach();
+  this.initializeMediaConsoleContext();
+  this.initializeSupportedCodecs();
+
+  // Since |initializeSupportedCodecs| is resolved asynchronously, we need to
+  // wait until |codecSupportMap| is fully populated before finishing the rest
+  // of the initialization.
+  if (this.codecSupportMap.size > 0) {
+    this.registerCodecHotkeys();
+    this.generateHotkeyHelpText();
+    this.isInitialized = true;
+  }
+}
+
+MediaConsole.prototype.initializeMediaConsoleContext = function() {
+  let js = debugHub.readDebugContentText('console/media_console_context.js');
+  this.debuggerClient.evaluate(js);
+}
+
+MediaConsole.prototype.initializeSupportedCodecs = function() {
+  if (this.codecSupportMap.size > 0) {
+    return;
+  }
+
+  function handleSupportQueryResponse(response) {
+    let results = JSON.parse(response.result.value);
+    results.forEach(record => {
+      let codecFamily = record[0];
+      let isSupported = record[1] || !!this.codecSupportMap.get(codecFamily);
+      this.codecSupportMap.set(codecFamily, isSupported);
+    });
+  };
+
+  this.debuggerClient.evaluate(`_mediaConsoleContext.getSupportedCodecs()`,
+      handleSupportQueryResponse.bind(this));
+}
+
+MediaConsole.prototype.registerCodecHotkeys = function() {
+  // Codec control hotkeys are of the form: Modifier + Number.
+  // Due to the large number of codecs, we split all the video codecs into Ctrl
+  // and all the audio codecs into Alt.
+  // "C-" prefix indicates a key with the ctrlKey modifier.
+  // "A-" prefix indicates a key with the altKey modifier.
+  // Additionally, we only register those hotkeys that we filter as supported.
+  function registerCodecHotkey(modifier, codec, index) {
+    if (!this.codecSupportMap.get(codec)) {
+      return;
+    }
+    let modAndNum = `${modifier}${index + 1}`;
+    let helpStr = `( ${modAndNum} ) ${codec}`;
+    this.hotkeyRegistry.set(modAndNum, {
+      handler: this.onToggleCodec.bind(this, codec),
+      help: helpStr
+    });
+  };
+  videoCodecFamilies.forEach(registerCodecHotkey.bind(this, kCtrlModifier));
+  audioCodecFamilies.forEach(registerCodecHotkey.bind(this, kAltModifier));
+}
+
+MediaConsole.prototype.generateHotkeyHelpText = function() {
+  // Generate the help text that will be displayed at the top of the console
+  // output.
+  let hotkeysHelp = `Hotkeys: ${kNewline}`;
+  hotkeysHelp += `Ctrl+Num toggles video codecs ${kNewline}`;
+  hotkeysHelp += `Alt+Num toggles audio codecs ${kNewline}`;
+  const generateHelpText = function(hotkeyInfo, hotkey, map) {
+    hotkeysHelp += hotkeyInfo.help + ', ';
+  };
+  this.hotkeyRegistry.forEach(generateHelpText);
+  hotkeysHelp = hotkeysHelp.substring(0, hotkeysHelp.length-2);
+  hotkeysHelp += kNewline;
+  hotkeysHelp += kNewline;
+  hotkeysHelp += kNewline;
+
+  this.hotkeyHelpNode.nodeValue = hotkeysHelp;
+}
+
+MediaConsole.prototype.parseStateFromResponse = function(response) {
+  // TODO: Handle the situation where the response contains exceptionDetails.
+  // https://chromedevtools.github.io/devtools-protocol/1-3/Runtime#method-evaluate
+  return JSON.parse(response.result.value);
+}
+
+MediaConsole.prototype.printToMediaConsole = function(response) {
+  const state = this.parseStateFromResponse(response);
+  let videoStatus = 'No primary video.';
+  if(state.hasPrimaryVideo) { videoStatus = ''; }
+  this.playerStateText.textContent =
+      `Primary Video: ${videoStatus} ${kNewline} \
+      Paused: ${state.paused} ${kNewline} \
+      Current Time: ${state.currentTime} ${kNewline} \
+      Duration: ${state.duration} ${kNewline} \
+      Playback Rate: ${state.playbackRate} \
+      (default: ${state.defaultPlaybackRate}) ${kNewline} \
+      Disabled Media Codecs: ${state.disabledMediaCodecs}`;
+}
+
+MediaConsole.prototype.update = function() {
+  const t = window.performance.now();
+  if (t > this.lastUpdateTime + this.updatePeriod) {
+    this.lastUpdateTime = t;
+    this.debuggerClient.evaluate('_mediaConsoleContext.getPlayerState()',
+        this.printToMediaConsoleCallback);
+  }
+}
+
+MediaConsole.prototype.onWheel = function(event) {}
+
+MediaConsole.prototype.onKeyup = function(event) {}
+
+MediaConsole.prototype.onKeypress = function(event) {}
+
+MediaConsole.prototype.onInput = function(event) {}
+
+MediaConsole.prototype.onKeydown = function(event) {
+  let matchedHotkeyEntry = null;
+  this.hotkeyRegistry.forEach(function(hotkeyInfo, hotkey, map) {
+    let eventKey = event.key;
+    if(event.altKey) { eventKey = kAltModifier + eventKey; }
+    if(event.ctrlKey) { eventKey = kCtrlModifier + eventKey; }
+    if(eventKey == hotkey) {
+      matchedHotkeyEntry = hotkeyInfo;
+    }
+  });
+  if (matchedHotkeyEntry) {
+    matchedHotkeyEntry.handler();
+  }
+}
+
+MediaConsole.prototype.onPlayPause = function() {
+  this.debuggerClient.evaluate('_mediaConsoleContext.togglePlayPause()',
+      this.printToMediaConsoleCallback);
+}
+
+MediaConsole.prototype.onIncreasePlaybackRate = function() {
+  this.debuggerClient.evaluate('_mediaConsoleContext.increasePlaybackRate()',
+      this.printToMediaConsoleCallback);
+}
+
+MediaConsole.prototype.onDecreasePlaybackRate = function() {
+  this.debuggerClient.evaluate('_mediaConsoleContext.decreasePlaybackRate()',
+      this.printToMediaConsoleCallback);
+}
+
+MediaConsole.prototype.onToggleCodec = function(codecToToggle) {
+  let codecs = this.getDisabledMediaCodecs().split(";");
+  codecs = codecs.filter(s => s.length > 0);
+  toggled = codecs.filter(c => c != codecToToggle);
+  if(codecs.length == toggled.length) { toggled.push(codecToToggle); }
+  this.setDisabledMediaCodecs(toggled.join(';'));
+}
diff --git a/src/cobalt/debug/console/content/media_console_context.js b/src/cobalt/debug/console/content/media_console_context.js
new file mode 100644
index 0000000..bd0df0f
--- /dev/null
+++ b/src/cobalt/debug/console/content/media_console_context.js
@@ -0,0 +1,162 @@
+// 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.
+
+(function() {
+
+let ctx = window._mediaConsoleContext = {};
+
+// NOTE: Place all "private" members and methods of |_mediaConsoleContext|
+// below. Private functions are not attached to the |_mediaConsoleContext|
+// directly. Rather they are referenced within the public functions, which
+// prevent it from being garbage collected.
+
+const kPlaybackRates = [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0];
+
+function getPrimaryVideo() {
+  const elem = document.querySelectorAll('video');
+  if (elem && elem.length > 0) {
+    let primary = null;
+    for (let i = 0; i < elem.length; i++) {
+      const rect = elem[i].getBoundingClientRect();
+      if (rect.width == window.innerWidth &&
+          rect.height == window.innerHeight) {
+        if (primary != null) {
+          console.warn('Two video elements found with the same\
+              dimensions as the main window.');
+        }
+        primary = elem[i];
+      }
+    }
+    return primary;
+  }
+  return null;
+};
+
+function extractState(video, disabledMediaCodecs) {
+  let state = {};
+  state.disabledMediaCodecs = disabledMediaCodecs;
+  state.hasPrimaryVideo = false;
+  if (video) {
+    state.hasPrimaryVideo = true;
+    state.paused = video.paused;
+    state.currentTime = video.currentTime;
+    state.duration = video.duration;
+    state.defaultPlaybackRate = video.defaultPlaybackRate;
+    state.playbackRate = video.playbackRate;
+  }
+  return JSON.stringify(state);
+};
+
+function getDisabledMediaCodecs() {
+  return window.h5vcc.cVal.getValue("Media.DisabledMediaCodecs");
+};
+
+// NOTE: Place all public members and methods of |_mediaConsoleContext|
+// below. They form closures with the above "private" members and methods
+// and hence can use them directly, without referencing |this|.
+
+ctx.getPlayerState = function() {
+  const video = getPrimaryVideo();
+  const disabledMediaCodecs = getDisabledMediaCodecs();
+  return extractState(video, disabledMediaCodecs);
+};
+
+ctx.togglePlayPause = function() {
+  let video = getPrimaryVideo();
+  if (video) {
+    if (video.paused) {
+      video.play();
+    } else {
+      video.pause();
+    }
+  }
+  return extractState(video, getDisabledMediaCodecs());
+};
+
+ctx.increasePlaybackRate = function() {
+  let video = getPrimaryVideo();
+  if (video) {
+    let i = kPlaybackRates.indexOf(video.playbackRate);
+    i = Math.min(i + 1, kPlaybackRates.length - 1);
+    video.playbackRate = kPlaybackRates[i];
+  }
+  return extractState(video, getDisabledMediaCodecs());
+};
+
+ctx.decreasePlaybackRate = function() {
+  let video = getPrimaryVideo();
+  if (video) {
+    let i = kPlaybackRates.indexOf(video.playbackRate);
+    i = Math.max(i - 1, 0);
+    video.playbackRate = kPlaybackRates[i];
+  }
+  return extractState(video, getDisabledMediaCodecs());
+};
+
+ctx.getSupportedCodecs = function() {
+  // By querying all the possible mime and codec pairs, we can determine
+  // which codecs are valid to control and toggle.  We use arbitrarily-
+  // chosen codec subformats to determine if the entire family is
+  // supported.
+  const kVideoCodecs = [
+      'av01.0.04M.10.0.110.09.16.09.0',
+      'avc1.640028',
+      'hvc1.1.2.L93.B0',
+      'vp09.02.10.10.01.09.16.09.01',
+      'vp8',
+      'vp9',
+  ];
+  const kVideoMIMEs = [
+      'video/mpeg',
+      'video/mp4',
+      'video/ogg',
+      'video/webm',
+  ];
+  const kAudioCodecs = [
+      'ac-3',
+      'ec-3',
+      'mp4a.40.2',
+      'opus',
+      'vorbis',
+  ];
+  const kAudioMIMEs = [
+      'audio/flac',
+      'audio/mpeg',
+      'audio/mp3',
+      'audio/mp4',
+      'audio/ogg',
+      'audio/wav',
+      'audio/webm',
+      'audio/x-m4a',
+  ];
+
+  let results = [];
+  kVideoMIMEs.forEach(mime => {
+    kVideoCodecs.forEach(codec => {
+      let family = codec.split('.')[0];
+      let mimeCodec = mime + '; codecs ="' + codec + '"';
+      results.push([family, MediaSource.isTypeSupported(mimeCodec)]);
+    })
+  });
+  kAudioMIMEs.forEach(mime => {
+    kAudioCodecs.forEach(codec => {
+      let family = codec.split('.')[0];
+      let mimeCodec = mime + '; codecs ="' + codec + '"';
+      results.push([family, MediaSource.isTypeSupported(mimeCodec)]);
+    })
+  });
+  return JSON.stringify(results);
+}
+
+})()
diff --git a/src/cobalt/debug/console/content/message_log.js b/src/cobalt/debug/console/content/message_log.js
index 03433bf..c22b510 100644
--- a/src/cobalt/debug/console/content/message_log.js
+++ b/src/cobalt/debug/console/content/message_log.js
@@ -77,16 +77,6 @@
 
 // Constructor for the message log object itself.
 function MessageLog(messageContainer) {
-  // Log levels defined by the 'level' property of LogEntry
-  // https://chromedevtools.github.io/devtools-protocol/1-3/Log#type-LogEntry
-  this.VERBOSE = "verbose";
-  this.INFO = "info";
-  this.WARNING = "warning";
-  this.ERROR = "error";
-  // Custom level used internally by the console.
-  this.INTERACTIVE = "interactive";
-  // Prefix on severity for messages from the JS console.
-  this.CONSOLE = '*';
   // Number of items to display on a single page.
   this.PAGE_SIZE = 50;
   // Number of items to scroll when the user pages up or down.
@@ -103,6 +93,17 @@
   this.visible = false;
 }
 
+// Log levels defined by the 'level' property of LogEntry
+// https://chromedevtools.github.io/devtools-protocol/1-3/Log#type-LogEntry
+MessageLog.VERBOSE = "verbose";
+MessageLog.INFO = "info";
+MessageLog.WARNING = "warning";
+MessageLog.ERROR = "error";
+// Custom level used internally by the console.
+MessageLog.INTERACTIVE = "interactive";
+// Prefix on severity for messages from the JS console.
+MessageLog.CONSOLE = '*';
+
 MessageLog.prototype.setVisible = function(visible) {
   var wasVisible = this.visible;
   this.visible = visible;
@@ -131,8 +132,8 @@
   elem.className = 'message';
   var text = document.createTextNode(message);
 
-  if (severity.startsWith(this.CONSOLE)) {
-    severity = severity.substr(this.CONSOLE.length);
+  if (severity.startsWith(MessageLog.CONSOLE)) {
+    severity = severity.substr(MessageLog.CONSOLE.length);
     elem.classList.add('console');
   }
 
diff --git a/src/cobalt/debug/console/debug_console_mode.idl b/src/cobalt/debug/console/debug_console_mode.idl
new file mode 100644
index 0000000..04163e2
--- /dev/null
+++ b/src/cobalt/debug/console/debug_console_mode.idl
@@ -0,0 +1,25 @@
+// 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.
+
+// Custom interface to communicate with the debugger, modeled after
+// chrome.debugger extension API.
+// https://developer.chrome.com/extensions/debugger
+
+enum DebugConsoleMode {
+  "off",
+  "hud",
+  "debug",
+  "media",
+  "count"
+};
diff --git a/src/cobalt/debug/console/debug_hub.cc b/src/cobalt/debug/console/debug_hub.cc
index e8760d5..2d9ecdc 100644
--- a/src/cobalt/debug/console/debug_hub.cc
+++ b/src/cobalt/debug/console/debug_hub.cc
@@ -17,9 +17,13 @@
 #include <memory>
 #include <set>
 
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
 #include "base/json/json_writer.h"
+#include "base/path_service.h"
 #include "base/values.h"
 #include "cobalt/base/c_val.h"
+#include "cobalt/base/cobalt_paths.h"
 #include "cobalt/debug/console/command_manager.h"
 #include "cobalt/debug/json_object.h"
 
@@ -27,6 +31,10 @@
 namespace debug {
 namespace console {
 
+namespace {
+constexpr char kContentDir[] = "cobalt/debug";
+}  // namespace
+
 DebugHub::DebugHub(
     const GetHudModeCallback& get_hud_mode_callback,
     const CreateDebugClientCallback& create_debug_client_callback)
@@ -37,7 +45,7 @@
 
 DebugHub::~DebugHub() {}
 
-int DebugHub::GetDebugConsoleMode() const {
+debug::console::DebugConsoleMode DebugHub::GetDebugConsoleMode() const {
   return get_hud_mode_callback_.Run();
 }
 
@@ -62,6 +70,21 @@
   callback_reference.value().Run();
 }
 
+std::string DebugHub::ReadDebugContentText(const std::string& filename) {
+  std::string result;
+
+  base::FilePath file_path;
+  base::PathService::Get(paths::DIR_COBALT_WEB_ROOT, &file_path);
+  file_path = file_path.AppendASCII(kContentDir);
+  file_path = file_path.AppendASCII(filename);
+
+  std::string text;
+  if (!base::ReadFileToString(file_path, &text)) {
+    DLOG(WARNING) << "Cannot read file: " << file_path.value();
+  }
+  return text;
+}
+
 void DebugHub::SendCommand(const std::string& method,
                            const std::string& json_params,
                            const ResponseCallbackArg& callback) {
diff --git a/src/cobalt/debug/console/debug_hub.h b/src/cobalt/debug/console/debug_hub.h
index cb02444..319e862 100644
--- a/src/cobalt/debug/console/debug_hub.h
+++ b/src/cobalt/debug/console/debug_hub.h
@@ -22,6 +22,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/optional.h"
 #include "cobalt/debug/console/console_command.h"
+#include "cobalt/debug/console/debug_console_mode.h"
 #include "cobalt/debug/console/debugger_event_target.h"
 #include "cobalt/debug/debug_client.h"
 #include "cobalt/dom/c_val_view.h"
@@ -43,7 +44,7 @@
  public:
   // Function signature to call when we need to query for the Hud visibility
   // mode.
-  typedef base::Callback<int()> GetHudModeCallback;
+  typedef base::Callback<debug::console::DebugConsoleMode()> GetHudModeCallback;
 
   // JavaScript callback to be run when debugger attaches/detaches.
   typedef script::CallbackFunction<void()> AttachCallback;
@@ -54,12 +55,6 @@
       ResponseCallback;
   typedef script::ScriptValue<ResponseCallback> ResponseCallbackArg;
 
-  // Debug console visibility modes.
-  static const int kDebugConsoleOff = 0;
-  static const int kDebugConsoleHud = 1;
-  static const int kDebugConsoleOn = 2;
-  static const int kDebugConsoleNumModes = kDebugConsoleOn + 1;
-
   // Thread-safe ref-counted struct used to pass asynchronously executed
   // response callbacks around. Stores the message loop the callback must be
   // executed on as well as the callback itself.
@@ -80,11 +75,15 @@
 
   const scoped_refptr<dom::CValView>& c_val() const { return c_val_; }
 
-  int GetDebugConsoleMode() const;
+  debug::console::DebugConsoleMode GetDebugConsoleMode() const;
 
   void Attach(const AttachCallbackArg& callback);
   void Detach(const AttachCallbackArg& callback);
 
+  // Read a text file from the content/web/cobalt/debug/ directory and return
+  // its contents.
+  std::string ReadDebugContentText(const std::string& filename);
+
   // Sends a devtools protocol command to be executed in the context of the main
   // WebModule that is being debugged.
   void SendCommand(const std::string& method, const std::string& json_params,
diff --git a/src/cobalt/debug/console/debug_hub.idl b/src/cobalt/debug/console/debug_hub.idl
index 21af7d6..a9afb03 100644
--- a/src/cobalt/debug/console/debug_hub.idl
+++ b/src/cobalt/debug/console/debug_hub.idl
@@ -19,17 +19,14 @@
 [
   Conditional=ENABLE_DEBUGGER
 ] interface DebugHub {
-  const long DEBUG_CONSOLE_OFF = 0;
-  const long DEBUG_CONSOLE_HUD = 1;
-  const long DEBUG_CONSOLE_ON = 2;
-
   readonly attribute CValView cVal;
 
-  long getDebugConsoleMode();
+  DebugConsoleMode getDebugConsoleMode();
 
   void attach(AttachCallback cb);
   void detach(AttachCallback cb);
 
+  DOMString readDebugContentText(DOMString filename);
   void sendCommand(DOMString method, DOMString jsonParams, ResponseCallback cb);
 
   readonly attribute DOMString? lastError;
diff --git a/src/cobalt/debug/debug.gyp b/src/cobalt/debug/debug.gyp
index 059acf9..35da044 100644
--- a/src/cobalt/debug/debug.gyp
+++ b/src/cobalt/debug/debug.gyp
@@ -40,6 +40,8 @@
         'backend/dom_agent.h',
         'backend/log_agent.cc',
         'backend/log_agent.h',
+        'backend/overlay_agent.cc',
+        'backend/overlay_agent.h',
         'backend/page_agent.cc',
         'backend/page_agent.h',
         'backend/render_layer.cc',
diff --git a/src/cobalt/doc/web_debugging.md b/src/cobalt/doc/web_debugging.md
index b43a59b..c4711db 100644
--- a/src/cobalt/doc/web_debugging.md
+++ b/src/cobalt/doc/web_debugging.md
@@ -158,9 +158,6 @@
 
 ## Tips
 
-*   Don't open Cobalt's console overlay before attaching the remote DevTools as
-    that can cause the latter not to work.
-
 *   You can make Cobalt reload the current page by pressing F5 in the Cobalt
     window, or ctrl-R in the remote DevTools. This may be useful for debugging
     startup code in the web app. It may also help in case some source file is
diff --git a/src/cobalt/dom/dom_test.gyp b/src/cobalt/dom/dom_test.gyp
index 7dd6d14..29a0a2f 100644
--- a/src/cobalt/dom/dom_test.gyp
+++ b/src/cobalt/dom/dom_test.gyp
@@ -77,10 +77,10 @@
         '<(DEPTH)/cobalt/loader/loader.gyp:loader',
         '<(DEPTH)/cobalt/renderer/rasterizer/skia/skia/skia.gyp:skia',
         '<(DEPTH)/cobalt/speech/speech.gyp:speech',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/dom/eme/media_key_session.cc b/src/cobalt/dom/eme/media_key_session.cc
index 3570c14..5a9c775 100644
--- a/src/cobalt/dom/eme/media_key_session.cc
+++ b/src/cobalt/dom/eme/media_key_session.cc
@@ -215,12 +215,10 @@
   // 5.2. Use CDM to close the key session associated with session.
   drm_system_session_->Close();
 
-  // Let |MediaKeys| know that the session should be removed from the list
-  // of open sessions.
-  closed_callback_.Run(this);
-
+#if !SB_HAS(DRM_SESSION_CLOSED)
   // 5.3.1. Run the Session Closed algorithm on the session.
   OnSessionClosed();
+#endif  // !SB_HAS(DRM_SESSION_CLOSED)
 
   // 5.3.2. Resolve promise.
   promise->Resolve();
@@ -416,6 +414,10 @@
   //    - TODO: Implement expiration.
   // 7. Resolve promise.
   closed_promise_reference_.value().Resolve();
+
+  // Let |MediaKeys| know that the session should be removed from the list
+  // of open sessions.
+  closed_callback_.Run(this);
 }
 
 }  // namespace eme
diff --git a/src/cobalt/dom/html_element.cc b/src/cobalt/dom/html_element.cc
index 1d3a522..7b7483c 100644
--- a/src/cobalt/dom/html_element.cc
+++ b/src/cobalt/dom/html_element.cc
@@ -535,7 +535,7 @@
   //     has no associated scrolling box, or the element has no overflow,
   //     terminate these steps.
   if (!layout_boxes_ ||
-      scroll_width() <= layout_boxes_->GetPaddingEdgeWidth() ) {
+      scroll_width() <= layout_boxes_->GetPaddingEdgeWidth()) {
     // Make sure the UI navigation container is set to the expected 0.
     x = 0.0f;
   }
@@ -597,7 +597,7 @@
 }
 
 // Algorithm for offsetParent:
-//   https://www.w3.org/TR/2013/WD-cssom-view-20131217/#dom-htmlelement-offsetparent
+//   https://drafts.csswg.org/date/2019-10-11/cssom-view/#extensions-to-the-htmlelement-interface
 Element* HTMLElement::offset_parent() {
   DCHECK(node_document());
   node_document()->DoSynchronousLayout();
@@ -617,7 +617,9 @@
   // 2. Return the nearest ancestor element of the element for which at least
   //    one of the following is true and terminate this algorithm if such an
   //    ancestor is found:
-  //    . The computed value of the 'position' property is not 'static'.
+  //    . The element is a containing block of absolutely-positioned descendants
+  //      (regardless of whether there are any absolutely-positioned
+  //       descendants).
   //    . It is the HTML body element.
   for (Node* ancestor_node = parent_node(); ancestor_node;
        ancestor_node = ancestor_node->parent_node()) {
@@ -631,8 +633,8 @@
     }
     DCHECK(ancestor_html_element->computed_style());
     if (ancestor_html_element->AsHTMLBodyElement() ||
-        ancestor_html_element->computed_style()->position() !=
-            cssom::KeywordValue::GetStatic()) {
+        ancestor_html_element->computed_style()
+            ->IsContainingBlockForPositionAbsoluteElements()) {
       return ancestor_element;
     }
   }
@@ -1144,24 +1146,23 @@
   directionality_ = base::nullopt;
 }
 
-void HTMLElement::OnUiNavBlur() {
-  Blur();
-}
+void HTMLElement::OnUiNavBlur() { Blur(); }
 
 void HTMLElement::OnUiNavFocus() {
   // Ensure the focusing steps do not trigger the UI navigation item to
   // force focus again.
-  scoped_refptr<ui_navigation::NavItem> temp_item = ui_nav_item_;
-  ui_nav_item_ = nullptr;
-  Focus();
-  ui_nav_item_ = temp_item;
+  if (!ui_nav_focusing_) {
+    ui_nav_focusing_ = true;
+    Focus();
+    ui_nav_focusing_ = false;
+  }
 }
 
 void HTMLElement::OnUiNavScroll() {
   Document* document = node_document();
   scoped_refptr<Window> window(document ? document->window() : nullptr);
-  DispatchEvent(new UIEvent(base::Tokens::scroll(),
-                Event::kBubbles, Event::kNotCancelable, window));
+  DispatchEvent(new UIEvent(base::Tokens::scroll(), Event::kBubbles,
+                            Event::kNotCancelable, window));
 }
 
 HTMLElement::HTMLElement(Document* document, base::Token local_name)
@@ -1342,7 +1343,7 @@
   ClearRuleMatchingState();
 
   // Set the focus item for the UI navigation system.
-  if (ui_nav_item_ && !ui_nav_item_->IsContainer()) {
+  if (ui_nav_item_ && !ui_nav_item_->IsContainer() && !ui_nav_focusing_) {
     // Only navigation items attached to the root container are interactable.
     // If the item is not registered with a container, then force a layout to
     // connect items to their containers and eventually to the root container.
diff --git a/src/cobalt/dom/html_element.h b/src/cobalt/dom/html_element.h
index 5166bf8..d1f548d 100644
--- a/src/cobalt/dom/html_element.h
+++ b/src/cobalt/dom/html_element.h
@@ -493,6 +493,12 @@
   // boxes without requiring a new layout.
   scoped_refptr<ui_navigation::NavItem> ui_nav_item_;
 
+  // This temporary flag is used to avoid a cycle on focus changes. When the
+  // HTML element receives focus, it must inform the UI navigation item. When
+  // the UI navigation item receives focus (either by calling SetFocus or by an
+  // update from the UI engine), it will tell the HTML element it was focused.
+  bool ui_nav_focusing_ = false;
+
   // HTMLElement is a friend of Animatable so that animatable can insert and
   // remove animations into HTMLElement's set of animations.
   friend class DOMAnimatable;
diff --git a/src/cobalt/dom/source_buffer.cc b/src/cobalt/dom/source_buffer.cc
index 08ea6e4..b99fde2 100644
--- a/src/cobalt/dom/source_buffer.cc
+++ b/src/cobalt/dom/source_buffer.cc
@@ -45,9 +45,6 @@
 #include "cobalt/dom/source_buffer.h"
 
 #include <algorithm>
-#include <limits>
-#include <memory>
-#include <vector>
 
 #include "base/compiler_specific.h"
 #include "base/logging.h"
@@ -94,21 +91,11 @@
       id_(id),
       chunk_demuxer_(chunk_demuxer),
       media_source_(media_source),
-      track_defaults_(new TrackDefaultList(NULL)),
       event_queue_(event_queue),
-      mode_(kSourceBufferAppendModeSegments),
-      updating_(false),
-      timestamp_offset_(0),
       audio_tracks_(
           new AudioTrackList(settings, media_source->GetMediaElement())),
       video_tracks_(
-          new VideoTrackList(settings, media_source->GetMediaElement())),
-      append_window_start_(0),
-      append_window_end_(std::numeric_limits<double>::infinity()),
-      first_initialization_segment_received_(false),
-      pending_append_data_offset_(0),
-      pending_remove_start_(-1),
-      pending_remove_end_(-1) {
+          new VideoTrackList(settings, media_source->GetMediaElement())) {
   DCHECK(!id_.empty());
   DCHECK(media_source_);
   DCHECK(chunk_demuxer);
@@ -340,6 +327,9 @@
   chunk_demuxer_ = NULL;
   media_source_ = NULL;
   event_queue_ = NULL;
+
+  pending_append_data_.reset();
+  pending_append_data_capacity_ = 0;
 }
 
 double SourceBuffer::GetHighestPresentationTimestamp() const {
@@ -419,8 +409,15 @@
 
   DCHECK(data || size == 0);
   if (data) {
-    pending_append_data_.insert(pending_append_data_.end(), data, data + size);
+    DCHECK_EQ(pending_append_data_offset_, 0u);
+    if (pending_append_data_capacity_ < size) {
+      pending_append_data_.reset();
+      pending_append_data_.reset(new uint8_t[size]);
+      pending_append_data_capacity_ = size;
+    }
+    SbMemoryCopy(pending_append_data_.get(), data, size);
   }
+  pending_append_data_size_ = size;
   pending_append_data_offset_ = 0;
 
   updating_ = true;
@@ -436,15 +433,14 @@
 
   DCHECK(updating_);
 
-  DCHECK_GE(pending_append_data_.size(), pending_append_data_offset_);
-  size_t append_size =
-      pending_append_data_.size() - pending_append_data_offset_;
+  DCHECK_GE(pending_append_data_size_, pending_append_data_offset_);
+  size_t append_size = pending_append_data_size_ - pending_append_data_offset_;
   append_size = std::min(append_size, kMaxAppendSize);
 
-  uint8 dummy[1];
+  uint8_t dummy;
   const uint8* data_to_append =
-      append_size > 0 ? &pending_append_data_[0] + pending_append_data_offset_
-                      : dummy;
+      append_size > 0 ? pending_append_data_.get() + pending_append_data_offset_
+                      : &dummy;
 
   base::TimeDelta timestamp_offset = DoubleToTimeDelta(timestamp_offset_);
   bool success = chunk_demuxer_->AppendData(
@@ -456,20 +452,20 @@
   }
 
   if (!success) {
-    pending_append_data_.clear();
+    pending_append_data_size_ = 0;
     pending_append_data_offset_ = 0;
     AppendError();
   } else {
     pending_append_data_offset_ += append_size;
 
-    if (pending_append_data_offset_ < pending_append_data_.size()) {
+    if (pending_append_data_offset_ < pending_append_data_size_) {
       append_timer_.Start(FROM_HERE, base::TimeDelta(), this,
                           &SourceBuffer::OnAppendTimer);
       return;
     }
 
     updating_ = false;
-    pending_append_data_.clear();
+    pending_append_data_size_ = 0;
     pending_append_data_offset_ = 0;
 
     ScheduleEvent(base::Tokens::update());
@@ -524,7 +520,7 @@
   DCHECK_EQ(pending_remove_start_, -1);
 
   append_timer_.Stop();
-  pending_append_data_.clear();
+  pending_append_data_size_ = 0;
   pending_append_data_offset_ = 0;
 
   updating_ = false;
diff --git a/src/cobalt/dom/source_buffer.h b/src/cobalt/dom/source_buffer.h
index efdd34b..2aa4a3f 100644
--- a/src/cobalt/dom/source_buffer.h
+++ b/src/cobalt/dom/source_buffer.h
@@ -45,6 +45,7 @@
 #ifndef COBALT_DOM_SOURCE_BUFFER_H_
 #define COBALT_DOM_SOURCE_BUFFER_H_
 
+#include <limits>
 #include <memory>
 #include <string>
 #include <vector>
@@ -170,25 +171,27 @@
   const std::string id_;
   media::ChunkDemuxer* chunk_demuxer_;
   MediaSource* media_source_;
-  scoped_refptr<TrackDefaultList> track_defaults_;
+  scoped_refptr<TrackDefaultList> track_defaults_ = new TrackDefaultList(NULL);
   EventQueue* event_queue_;
 
-  SourceBufferAppendMode mode_;
-  bool updating_;
-  double timestamp_offset_;
+  SourceBufferAppendMode mode_ = kSourceBufferAppendModeSegments;
+  bool updating_ = false;
+  double timestamp_offset_ = 0;
   scoped_refptr<AudioTrackList> audio_tracks_;
   scoped_refptr<VideoTrackList> video_tracks_;
-  double append_window_start_;
-  double append_window_end_;
+  double append_window_start_ = 0;
+  double append_window_end_ = std::numeric_limits<double>::infinity();
 
   base::OneShotTimer append_timer_;
-  bool first_initialization_segment_received_;
-  std::vector<uint8_t> pending_append_data_;
-  size_t pending_append_data_offset_;
+  bool first_initialization_segment_received_ = false;
+  std::unique_ptr<uint8_t[]> pending_append_data_;
+  size_t pending_append_data_capacity_ = 0;
+  size_t pending_append_data_size_ = 0;
+  size_t pending_append_data_offset_ = 0;
 
   base::OneShotTimer remove_timer_;
-  double pending_remove_start_;
-  double pending_remove_end_;
+  double pending_remove_start_ = -1;
+  double pending_remove_end_ = -1;
 };
 
 }  // namespace dom
diff --git a/src/cobalt/dom_parser/dom_parser_test.gyp b/src/cobalt/dom_parser/dom_parser_test.gyp
index 6730d25..aedb612 100644
--- a/src/cobalt/dom_parser/dom_parser_test.gyp
+++ b/src/cobalt/dom_parser/dom_parser_test.gyp
@@ -28,10 +28,10 @@
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
         '<(DEPTH)/cobalt/dom/testing/dom_testing.gyp:dom_testing',
         '<(DEPTH)/cobalt/dom_parser/dom_parser.gyp:dom_parser',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/dom_parser/html_decoder.cc b/src/cobalt/dom_parser/html_decoder.cc
index 0adbef0..b662528 100644
--- a/src/cobalt/dom_parser/html_decoder.cc
+++ b/src/cobalt/dom_parser/html_decoder.cc
@@ -64,7 +64,10 @@
       require_csp_ == csp::kCSPOptional) {
     return loader::kLoadResponseContinue;
   } else {
-    DLOG(ERROR) << "Failure receiving Content Security Policy headers";
+    LOG(ERROR) << "Failure receiving Content Security Policy headers "
+                  "for URL: " << url_fetcher->GetURL() << ".";
+    LOG(ERROR) << "The server *must* send CSP headers or Cobalt will not "
+                  "load the page.";
     return loader::kLoadResponseAbort;
   }
 }
diff --git a/src/cobalt/extension/extension.gyp b/src/cobalt/extension/extension.gyp
index 340fbc9..bf63c0d 100644
--- a/src/cobalt/extension/extension.gyp
+++ b/src/cobalt/extension/extension.gyp
@@ -26,11 +26,11 @@
       'dependencies': [
         '<@(cobalt_platform_dependencies)',
         '<(DEPTH)/cobalt/base/base.gyp:base',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/starboard/starboard.gyp:starboard',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
     {
       'target_name': 'extension_test_deploy',
diff --git a/src/cobalt/extension/extension_test.cc b/src/cobalt/extension/extension_test.cc
index 044bdde..2615ba2 100644
--- a/src/cobalt/extension/extension_test.cc
+++ b/src/cobalt/extension/extension_test.cc
@@ -15,6 +15,7 @@
 #include <cmath>
 
 #include "cobalt/extension/graphics.h"
+#include "cobalt/extension/installation_manager.h"
 #include "cobalt/extension/platform_service.h"
 #include "starboard/system.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -34,7 +35,8 @@
   }
 
   EXPECT_STREQ(extension_api->name, kExtensionName);
-  EXPECT_EQ(extension_api->version, 1) << "Invalid version";
+  EXPECT_TRUE(extension_api->version == 1 ||
+              extension_api->version == 2) << "Invalid version";
   EXPECT_TRUE(extension_api->Has != NULL);
   EXPECT_TRUE(extension_api->Open != NULL);
   EXPECT_TRUE(extension_api->Close != NULL);
@@ -57,19 +59,52 @@
   }
 
   EXPECT_STREQ(extension_api->name, kExtensionName);
-  EXPECT_EQ(extension_api->version, 1) << "Invalid version";
+  EXPECT_TRUE(extension_api->version == 1 ||
+              extension_api->version == 2) << "Invalid version";
   EXPECT_TRUE(extension_api->GetMaximumFrameIntervalInMilliseconds != NULL);
+  if (extension_api->version >= 2) {
+    EXPECT_TRUE(extension_api->GetMinimumFrameIntervalInMilliseconds != NULL);
+  }
 
   float maximum_frame_interval =
       extension_api->GetMaximumFrameIntervalInMilliseconds();
   EXPECT_FALSE(std::isnan(maximum_frame_interval));
 
+  if (extension_api->version >= 2) {
+    float minimum_frame_interval =
+      extension_api->GetMinimumFrameIntervalInMilliseconds();
+    EXPECT_GT(minimum_frame_interval, 0);
+  }
   const ExtensionApi* second_extension_api = static_cast<const ExtensionApi*>(
       SbSystemGetExtension(kExtensionName));
   EXPECT_EQ(second_extension_api, extension_api)
       << "Extension struct should be a singleton";
 }
 
+TEST(ExtensionTest, InstallationManager) {
+  typedef CobaltExtensionInstallationManagerApi ExtensionApi;
+  const char* kExtensionName = kCobaltExtensionInstallationManagerName;
+
+  const ExtensionApi* extension_api =
+      static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+  if (!extension_api) {
+    return;
+  }
+
+  EXPECT_STREQ(extension_api->name, kExtensionName);
+  EXPECT_TRUE(extension_api->version == 1 ||
+              extension_api->version == 2) << "Invalid version";
+  EXPECT_TRUE(extension_api->GetCurrentInstallationIndex != NULL);
+  EXPECT_TRUE(extension_api->MarkInstallationSuccessful != NULL);
+  EXPECT_TRUE(extension_api->RequestRollForwardToInstallation != NULL);
+  EXPECT_TRUE(extension_api->GetInstallationPath != NULL);
+  EXPECT_TRUE(extension_api->SelectNewInstallationIndex != NULL);
+
+  const ExtensionApi* second_extension_api =
+      static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+  EXPECT_EQ(second_extension_api, extension_api)
+      << "Extension struct should be a singleton";
+}
 }  // namespace extension
 }  // namespace cobalt
 #endif  // SB_API_VERSION >= 11
diff --git a/src/cobalt/extension/graphics.h b/src/cobalt/extension/graphics.h
index b68ac9f..b0e2bb8 100644
--- a/src/cobalt/extension/graphics.h
+++ b/src/cobalt/extension/graphics.h
@@ -48,6 +48,15 @@
   // precedence over this. For example, if the minimum frame time is 8ms and
   // the maximum frame interval is 0ms, then the renderer will target 125 fps.
   float (*GetMaximumFrameIntervalInMilliseconds)();
+
+  // The fields below this point were added in version 2 or later.
+
+  // Allow throttling of the frame rate. This is expressed in terms of
+  // 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.
+  float (*GetMinimumFrameIntervalInMilliseconds)();
 } CobaltExtensionGraphicsApi;
 
 #ifdef __cplusplus
diff --git a/src/cobalt/extension/installation_manager.h b/src/cobalt/extension/installation_manager.h
new file mode 100644
index 0000000..4a99cda
--- /dev/null
+++ b/src/cobalt/extension/installation_manager.h
@@ -0,0 +1,55 @@
+// 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_EXTENSION_INSTALLATION_MANAGER_H_
+#define COBALT_EXTENSION_INSTALLATION_MANAGER_H_
+
+#include <stdint.h>
+
+#include "starboard/configuration.h"
+
+#define IM_EXT_ERROR -1
+#define IM_EXT_SUCCESS 0
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define kCobaltExtensionInstallationManagerName \
+  "dev.cobalt.extension.InstallationManager"
+
+typedef struct CobaltExtensionInstallationManagerApi {
+  // Name should be the string kCobaltExtensionInstallationManagerName.
+  // This helps to validate that the extension API is correct.
+  const char* name;
+
+  // This specifies the version of the API that is implemented.
+  uint32_t version;
+
+  // Installation Manager API wrapper.
+  // For more details, check:
+  //  starboard/loader_app/installation_manager.h
+  int (*GetCurrentInstallationIndex)();
+  int (*MarkInstallationSuccessful)(int installation_index);
+  int (*RequestRollForwardToInstallation)(int installation_index);
+  int (*GetInstallationPath)(int installation_index, char* path,
+                             int path_length);
+  int (*SelectNewInstallationIndex)();
+} CobaltExtensionInstallationManagerApi;
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // COBALT_EXTENSION_INSTALLATION_MANAGER_H_
diff --git a/src/cobalt/input/input_device_manager_desktop.cc b/src/cobalt/input/input_device_manager_desktop.cc
index 85ccbb1..221b04f 100644
--- a/src/cobalt/input/input_device_manager_desktop.cc
+++ b/src/cobalt/input/input_device_manager_desktop.cc
@@ -241,8 +241,7 @@
   keypress_generator_filter_.HandleKeyboardEvent(type, keyboard_event);
 
   int32_t key_code_in_int32 = static_cast<int32_t>(key_code);
-  overlay_info::OverlayInfoRegistry::Register(
-      "input_manager:keydown", &key_code_in_int32, sizeof(key_code_in_int32));
+  overlay_info::OverlayInfoRegistry::Register("keydown", key_code_in_int32);
 }
 
 void InputDeviceManagerDesktop::HandlePointerEvent(
diff --git a/src/cobalt/layout/box.cc b/src/cobalt/layout/box.cc
index 4eff827..ac4ebed 100644
--- a/src/cobalt/layout/box.cc
+++ b/src/cobalt/layout/box.cc
@@ -132,11 +132,11 @@
 Box::~Box() { layout_stat_tracker_->OnBoxDestroyed(); }
 
 bool Box::IsPositioned() const {
-  return computed_style()->position() != cssom::KeywordValue::GetStatic();
+  return computed_style()->IsPositioned();
 }
 
 bool Box::IsTransformed() const {
-  return computed_style()->transform() != cssom::KeywordValue::GetNone();
+  return computed_style()->IsTransformed();
 }
 
 bool Box::IsAbsolutelyPositioned() const {
diff --git a/src/cobalt/layout/container_box.cc b/src/cobalt/layout/container_box.cc
index 3c9ef9e..acdddff 100644
--- a/src/cobalt/layout/container_box.cc
+++ b/src/cobalt/layout/container_box.cc
@@ -170,7 +170,8 @@
 }
 
 bool ContainerBox::IsContainingBlockForPositionAbsoluteElements() const {
-  return parent() == NULL || IsPositioned() || IsTransformed();
+  return parent() == NULL ||
+         computed_style()->IsContainingBlockForPositionAbsoluteElements();
 }
 
 bool ContainerBox::IsContainingBlockForPositionFixedElements() const {
diff --git a/src/cobalt/layout/layout.gyp b/src/cobalt/layout/layout.gyp
index bef1686..e608390 100644
--- a/src/cobalt/layout/layout.gyp
+++ b/src/cobalt/layout/layout.gyp
@@ -138,11 +138,11 @@
       'dependencies': [
         '<(DEPTH)/cobalt/css_parser/css_parser.gyp:css_parser',
         '<(DEPTH)/cobalt/layout/layout.gyp:layout_testing',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'layout',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/layout/line_box.cc b/src/cobalt/layout/line_box.cc
index a73a401..75b83c1 100644
--- a/src/cobalt/layout/line_box.cc
+++ b/src/cobalt/layout/line_box.cc
@@ -871,9 +871,8 @@
   // The start edge offset at which the ellipsis was eventually placed. This
   // will be set by TryPlaceEllipsisOrProcessPlacedEllipsis() within one of the
   // child boxes.
-  // NOTE: While this is is guaranteed to be set later, initializing it here
-  // keeps compilers from complaining about it being an uninitialized variable
-  // below.
+  // NOTE: While this is guaranteed to be set later, initializing it here keeps
+  // compilers from complaining about it being an uninitialized variable below.
   LayoutUnit placed_start_edge_offset;
 
   // Walk each box within the line in base direction order attempting to place
diff --git a/src/cobalt/layout_tests/layout_tests.gyp b/src/cobalt/layout_tests/layout_tests.gyp
index 35683b2..05b34a6 100644
--- a/src/cobalt/layout_tests/layout_tests.gyp
+++ b/src/cobalt/layout_tests/layout_tests.gyp
@@ -65,12 +65,12 @@
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/browser/browser.gyp:browser',
         '<(DEPTH)/cobalt/renderer/renderer.gyp:render_tree_pixel_tester',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/url/url.gyp:url',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'layout_test_utils',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
@@ -94,12 +94,12 @@
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/browser/browser.gyp:browser',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/url/url.gyp:url',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'layout_test_utils',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/layout_tests/testdata/csp/img-src.html b/src/cobalt/layout_tests/testdata/csp/img-src.html
index ad628af..20a59ea 100644
--- a/src/cobalt/layout_tests/testdata/csp/img-src.html
+++ b/src/cobalt/layout_tests/testdata/csp/img-src.html
@@ -24,6 +24,7 @@
     var div_ids = ["insecure", "secure"];
 
     var urls = ["http://" + image_base, "https://" + image_base];
+    var num_errors = 0;
     for (var i = 0; i < 2; i++) {
       var divname = div_ids[i];
       var url = urls[i];
@@ -36,8 +37,12 @@
         }
       }
       images[i].onerror = function() {
-        // NOTE: This won't be called due to an outstanding bug.
+        ++num_errors;
         console.log('Error loading: ' + this.src);
+        if (num_errors >= 2) {
+          console.log('More errors than expected!');
+          window.testRunner.notifyDone();
+        }
       }
       images[i].src = urls[i];
     }
diff --git a/src/cobalt/loader/loader.gyp b/src/cobalt/loader/loader.gyp
index 87cdade..d05d443 100644
--- a/src/cobalt/loader/loader.gyp
+++ b/src/cobalt/loader/loader.gyp
@@ -159,7 +159,6 @@
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/math/math.gyp:math',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         '<(DEPTH)/third_party/ots/ots.gyp:ots',
@@ -167,6 +166,7 @@
         'loader_copy_test_data',
         '<@(cobalt_platform_dependencies)',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/loader/net_fetcher.cc b/src/cobalt/loader/net_fetcher.cc
index 3302418..bf5a097 100644
--- a/src/cobalt/loader/net_fetcher.cc
+++ b/src/cobalt/loader/net_fetcher.cc
@@ -172,11 +172,13 @@
     auto* download_data_writer =
         base::polymorphic_downcast<URLFetcherStringWriter*>(
             source->GetResponseWriter());
-    std::unique_ptr<std::string> data = download_data_writer->data();
-    if (!data->empty()) {
+    std::string data;
+    download_data_writer->GetAndResetData(&data);
+    if (!data.empty()) {
       DLOG(INFO) << "in OnURLFetchComplete data still has bytes: "
-                 << data->size();
-      handler()->OnReceivedPassed(this, std::move(data));
+                 << data.size();
+      handler()->OnReceivedPassed(
+          this, std::unique_ptr<std::string>(new std::string(std::move(data))));
     }
     handler()->OnDone(this);
   } else {
@@ -209,15 +211,17 @@
     auto* download_data_writer =
         base::polymorphic_downcast<URLFetcherStringWriter*>(
             source->GetResponseWriter());
-    std::unique_ptr<std::string> data = download_data_writer->data();
-    if (data->empty()) {
+    std::string data;
+    download_data_writer->GetAndResetData(&data);
+    if (data.empty()) {
       return;
     }
 #if defined(HANDLE_CORE_DUMP)
     net_fetcher_log.Get().IncrementFetchedBytes(
-        static_cast<int>(data->length()));
+        static_cast<int>(data.length()));
 #endif
-    handler()->OnReceivedPassed(this, std::move(data));
+    handler()->OnReceivedPassed(
+        this, std::unique_ptr<std::string>(new std::string(std::move(data))));
   }
 }
 
diff --git a/src/cobalt/loader/url_fetcher_string_writer.cc b/src/cobalt/loader/url_fetcher_string_writer.cc
index b583799..54ae81a 100644
--- a/src/cobalt/loader/url_fetcher_string_writer.cc
+++ b/src/cobalt/loader/url_fetcher_string_writer.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include "cobalt/loader/url_fetcher_string_writer.h"
+
+#include "base/logging.h"
 #include "net/base/net_errors.h"
 
 namespace cobalt {
@@ -24,6 +26,10 @@
 //   DCHECK(consumer_task_runner);
 // }
 
+namespace {
+const int64_t kPreAllocateThreshold = 64 * 1024;
+}  // namespace
+
 URLFetcherStringWriter::URLFetcherStringWriter() = default;
 
 URLFetcherStringWriter::~URLFetcherStringWriter() = default;
@@ -33,22 +39,56 @@
   return net::OK;
 }
 
-std::unique_ptr<std::string> URLFetcherStringWriter::data() {
+void URLFetcherStringWriter::OnResponseStarted(int64_t content_length) {
   base::AutoLock auto_lock(lock_);
-  if (!data_) {
-    return std::make_unique<std::string>();
+
+  if (content_length >= 0) {
+    content_length_ = content_length;
   }
-  return std::move(data_);
+}
+
+bool URLFetcherStringWriter::HasData() const {
+  base::AutoLock auto_lock(lock_);
+  return !data_.empty();
+}
+
+void URLFetcherStringWriter::GetAndResetData(std::string* data) {
+  DCHECK(data);
+
+  std::string empty;
+  data->swap(empty);
+
+  base::AutoLock auto_lock(lock_);
+  data_.swap(*data);
 }
 
 int URLFetcherStringWriter::Write(net::IOBuffer* buffer, int num_bytes,
                                   net::CompletionOnceCallback /*callback*/) {
   base::AutoLock auto_lock(lock_);
-  if (!data_) {
-    data_ = std::make_unique<std::string>();
+
+  if (content_offset_ == 0 && num_bytes <= content_length_) {
+    // Pre-allocate the whole buffer for small downloads, hope that all data can
+    // be downloaded before GetAndResetData() is called.
+    if (content_length_ <= kPreAllocateThreshold) {
+      data_.reserve(content_length_);
+    } else {
+      data_.reserve(kPreAllocateThreshold);
+    }
   }
 
-  data_->append(buffer->data(), num_bytes);
+  if (content_length_ > 0 && content_length_ > content_offset_ &&
+      data_.size() + num_bytes > data_.capacity()) {
+    // There is not enough memory allocated, and std::string is going to double
+    // the allocation.  So a content in "1M + 1" bytes may end up allocating 2M
+    // bytes.  Try to reserve the proper size to avoid this.
+    auto content_remaining = content_length_ - content_offset_;
+    if (data_.size() + content_remaining < data_.capacity() * 2) {
+      data_.reserve(data_.size() + content_remaining);
+    }
+  }
+
+  data_.append(buffer->data(), num_bytes);
+  content_offset_ += num_bytes;
   // consumer_task_runner_->PostTask(FROM_HERE,
   // base::Bind((on_write_callback_.Run), std::move(data)));
   return num_bytes;
diff --git a/src/cobalt/loader/url_fetcher_string_writer.h b/src/cobalt/loader/url_fetcher_string_writer.h
index 3bab4a6..72d87e8 100644
--- a/src/cobalt/loader/url_fetcher_string_writer.h
+++ b/src/cobalt/loader/url_fetcher_string_writer.h
@@ -35,19 +35,22 @@
   URLFetcherStringWriter();
   ~URLFetcherStringWriter() override;
 
-  std::unique_ptr<std::string> data();
+  bool HasData() const;
+  void GetAndResetData(std::string* data);
 
   // URLFetcherResponseWriter overrides:
   int Initialize(net::CompletionOnceCallback callback) override;
+  void OnResponseStarted(int64_t content_length) override;
   int Write(net::IOBuffer* buffer, int num_bytes,
             net::CompletionOnceCallback callback) override;
   int Finish(int net_error, net::CompletionOnceCallback callback) override;
 
  private:
-  // This class can be accessed by both network thread and MainWebModule
-  // thread.
-  base::Lock lock_;
-  std::unique_ptr<std::string> data_;
+  // This class can be accessed by both network thread and MainWebModule thread.
+  mutable base::Lock lock_;
+  int64_t content_length_ = -1;
+  int64_t content_offset_ = 0;
+  std::string data_;
   // OnWriteCallback on_write_callback_;
   // base::TaskRunner* consumer_task_runner_;
 
diff --git a/src/cobalt/math/math.gyp b/src/cobalt/math/math.gyp
index 5fc0326..344ec57 100644
--- a/src/cobalt/math/math.gyp
+++ b/src/cobalt/math/math.gyp
@@ -90,10 +90,10 @@
       'dependencies': [
         '<(DEPTH)/base/base.gyp:base',
         '<(DEPTH)/cobalt/base/base.gyp:base',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'math',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
     {
       'target_name': 'math_test_deploy',
diff --git a/src/cobalt/media/base/drm_system.cc b/src/cobalt/media/base/drm_system.cc
index 8ad4118..b94c858 100644
--- a/src/cobalt/media/base/drm_system.cc
+++ b/src/cobalt/media/base/drm_system.cc
@@ -28,8 +28,7 @@
 DECLARE_INSTANCE_COUNTER(DrmSystem);
 
 DrmSystem::Session::Session(
-    DrmSystem* drm_system
-    ,
+    DrmSystem* drm_system,
     SessionUpdateKeyStatusesCallback update_key_statuses_callback
 #if SB_HAS(DRM_SESSION_CLOSED)
     ,
@@ -41,7 +40,8 @@
 #if SB_HAS(DRM_SESSION_CLOSED)
       session_closed_callback_(session_closed_callback),
 #endif  // SB_HAS(DRM_SESSION_CLOSED)
-      closed_(false) {
+      closed_(false),
+      weak_factory_(this) {
   DCHECK(!update_key_statuses_callback_.is_null());
 #if SB_HAS(DRM_SESSION_CLOSED)
   DCHECK(!session_closed_callback_.is_null());
@@ -69,7 +69,7 @@
   update_request_generated_callback_ =
       session_update_request_generated_callback;
   drm_system_->GenerateSessionUpdateRequest(
-      this, type, init_data, init_data_length,
+      weak_factory_.GetWeakPtr(), type, init_data, init_data_length,
       session_update_request_generated_callback,
       session_update_request_did_not_generate_callback);
 }
@@ -169,8 +169,8 @@
 #endif  // SB_API_VERSION >= 10
 
 void DrmSystem::GenerateSessionUpdateRequest(
-    Session* session, const std::string& type, const uint8_t* init_data,
-    int init_data_length,
+    const base::WeakPtr<Session>& session, const std::string& type,
+    const uint8_t* init_data, int init_data_length,
     const SessionUpdateRequestGeneratedCallback&
         session_update_request_generated_callback,
     const SessionUpdateRequestDidNotGenerateCallback&
@@ -221,6 +221,8 @@
     SessionTicketAndOptionalId ticket_and_optional_id, SbDrmStatus status,
     SbDrmSessionRequestType type, const std::string& error_message,
     std::unique_ptr<uint8[]> message, int message_size) {
+  DCHECK(message_loop_->BelongsToCurrentThread());
+
   int ticket = ticket_and_optional_id.ticket;
   const base::Optional<std::string>& session_id = ticket_and_optional_id.id;
   if (SbDrmTicketIsValid(ticket)) {
@@ -237,21 +239,25 @@
     const SessionUpdateRequest& session_update_request =
         session_update_request_iterator->second;
 
-    // Interpret the result.
-    if (session_id) {
-      // Successful request generation.
+    // As DrmSystem::Session may be released, need to check it before using it.
+    if (session_update_request.session &&
+        !session_update_request.session->is_closed()) {
+      // Interpret the result.
+      if (session_id) {
+        // Successful request generation.
 
-      // Enable session lookup by id which is used by spontaneous callbacks.
-      session_update_request.session->set_id(*session_id);
-      id_to_session_map_.insert(
-          std::make_pair(*session_id, session_update_request.session));
+        // Enable session lookup by id which is used by spontaneous callbacks.
+        session_update_request.session->set_id(*session_id);
+        id_to_session_map_.insert(
+            std::make_pair(*session_id, session_update_request.session));
 
-      session_update_request.generated_callback.Run(type, std::move(message),
-                                                    message_size);
-    } else {
-      // Failure during request generation.
-      session_update_request.did_not_generate_callback.Run(status,
-                                                           error_message);
+        session_update_request.generated_callback.Run(type, std::move(message),
+                                                      message_size);
+      } else {
+        // Failure during request generation.
+        session_update_request.did_not_generate_callback.Run(status,
+                                                             error_message);
+      }
     }
 
     // Sweep the context of |GenerateSessionUpdateRequest| once license updated.
@@ -274,15 +280,19 @@
       LOG(ERROR) << "Unknown session id: " << *session_id << ".";
       return;
     }
-    Session* session = session_iterator->second;
 
-    session->update_request_generated_callback().Run(type, std::move(message),
-                                                     message_size);
+    // As DrmSystem::Session may be released, need to check it before using it.
+    if (session_iterator->second) {
+      session_iterator->second->update_request_generated_callback().Run(
+          type, std::move(message), message_size);
+    }
   }
 }
 
 void DrmSystem::OnSessionUpdated(int ticket, SbDrmStatus status,
                                  const std::string& error_message) {
+  DCHECK(message_loop_->BelongsToCurrentThread());
+
   // Restore the context of |UpdateSession|.
   TicketToSessionUpdateMap::iterator session_update_iterator =
       ticket_to_session_update_map_.find(ticket);
@@ -306,6 +316,8 @@
 void DrmSystem::OnSessionKeyStatusChanged(
     const std::string& session_id, const std::vector<std::string>& key_ids,
     const std::vector<SbDrmKeyStatus>& key_statuses) {
+  DCHECK(message_loop_->BelongsToCurrentThread());
+
   // Find the session by ID.
   IdToSessionMap::iterator session_iterator =
       id_to_session_map_.find(session_id);
@@ -313,13 +325,18 @@
     LOG(ERROR) << "Unknown session id: " << session_id << ".";
     return;
   }
-  Session* session = session_iterator->second;
 
-  session->update_key_statuses_callback().Run(key_ids, key_statuses);
+  // As DrmSystem::Session may be released, need to check it before using it.
+  if (session_iterator->second) {
+    session_iterator->second->update_key_statuses_callback().Run(key_ids,
+                                                                 key_statuses);
+  }
 }
 
 #if SB_HAS(DRM_SESSION_CLOSED)
 void DrmSystem::OnSessionClosed(const std::string& session_id) {
+  DCHECK(message_loop_->BelongsToCurrentThread());
+
   // Find the session by ID.
   IdToSessionMap::iterator session_iterator =
       id_to_session_map_.find(session_id);
@@ -327,9 +344,11 @@
     LOG(ERROR) << "Unknown session id: " << session_id << ".";
     return;
   }
-  Session* session = session_iterator->second;
 
-  session->session_closed_callback().Run();
+  // As DrmSystem::Session may be released, need to check it before using it.
+  if (session_iterator->second) {
+    session_iterator->second->session_closed_callback().Run();
+  }
   id_to_session_map_.erase(session_iterator);
 }
 #endif  // SB_HAS(DRM_SESSION_CLOSED)
@@ -337,6 +356,8 @@
 #if SB_API_VERSION >= 10
 void DrmSystem::OnServerCertificateUpdated(int ticket, SbDrmStatus status,
                                            const std::string& error_message) {
+  DCHECK(message_loop_->BelongsToCurrentThread());
+
   auto iter = ticket_to_server_certificate_updated_map_.find(ticket);
   if (iter == ticket_to_server_certificate_updated_map_.end()) {
     LOG(ERROR) << "Unknown ticket: " << ticket << ".";
diff --git a/src/cobalt/media/base/drm_system.h b/src/cobalt/media/base/drm_system.h
index d4290f8..a80bf34 100644
--- a/src/cobalt/media/base/drm_system.h
+++ b/src/cobalt/media/base/drm_system.h
@@ -132,6 +132,8 @@
     // Supports spontaneous invocations of |SbDrmSessionUpdateRequestFunc|.
     SessionUpdateRequestGeneratedCallback update_request_generated_callback_;
 
+    base::WeakPtrFactory<Session> weak_factory_;
+
     friend class DrmSystem;
 
     DISALLOW_COPY_AND_ASSIGN(Session);
@@ -160,14 +162,14 @@
  private:
   // Stores context of |GenerateSessionUpdateRequest|.
   struct SessionUpdateRequest {
-    Session* session;
+    base::WeakPtr<Session> session;
     SessionUpdateRequestGeneratedCallback generated_callback;
     SessionUpdateRequestDidNotGenerateCallback did_not_generate_callback;
   };
   typedef base::hash_map<int, SessionUpdateRequest>
       TicketToSessionUpdateRequestMap;
 
-  typedef base::hash_map<std::string, Session*> IdToSessionMap;
+  typedef base::hash_map<std::string, base::WeakPtr<Session>> IdToSessionMap;
 
   typedef base::hash_map<int, ServerCertificateUpdatedCallback>
       TicketToServerCertificateUpdatedMap;
@@ -188,8 +190,8 @@
 
   // Private API for |Session|.
   void GenerateSessionUpdateRequest(
-      Session* session, const std::string& type, const uint8_t* init_data,
-      int init_data_length,
+      const base::WeakPtr<Session>& session, const std::string& type,
+      const uint8_t* init_data, int init_data_length,
       const SessionUpdateRequestGeneratedCallback&
           session_update_request_generated_callback,
       const SessionUpdateRequestDidNotGenerateCallback&
diff --git a/src/cobalt/media/base/starboard_player.cc b/src/cobalt/media/base/starboard_player.cc
index c41e9ac..d0c7e92 100644
--- a/src/cobalt/media/base/starboard_player.cc
+++ b/src/cobalt/media/base/starboard_player.cc
@@ -268,6 +268,7 @@
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   decoder_buffer_cache_.ClearAll();
+  seek_pending_ = false;
 
   if (state_ == kSuspended) {
     preroll_timestamp_ = time;
@@ -290,7 +291,6 @@
   SbPlayerSeek2(player_, time.InMicroseconds(), ticket_);
 #endif  // SB_API_VERSION < 10
 
-  seek_pending_ = false;
   SbPlayerSetPlaybackRate(player_, playback_rate_);
 }
 
@@ -434,13 +434,14 @@
 
   SbPlayerSetPlaybackRate(player_, 0.0);
 
+  set_bounds_helper_->SetPlayer(NULL);
+
   base::AutoLock auto_lock(lock_);
   GetInfo_Locked(&cached_video_frames_decoded_, &cached_video_frames_dropped_,
                  &preroll_timestamp_);
 
   state_ = kSuspended;
 
-  set_bounds_helper_->SetPlayer(NULL);
   video_frame_provider_->SetOutputMode(VideoFrameProvider::kOutputModeInvalid);
   video_frame_provider_->ResetGetCurrentSbDecodeTargetFunction();
 
diff --git a/src/cobalt/media/fetcher_buffered_data_source.cc b/src/cobalt/media/fetcher_buffered_data_source.cc
index cbf6f41..254f386 100644
--- a/src/cobalt/media/fetcher_buffered_data_source.cc
+++ b/src/cobalt/media/fetcher_buffered_data_source.cc
@@ -211,12 +211,13 @@
   auto* download_data_writer =
       base::polymorphic_downcast<loader::URLFetcherStringWriter*>(
           source->GetResponseWriter());
-  std::unique_ptr<std::string> download_data = download_data_writer->data();
-  size_t size = download_data->size();
+  std::string downloaded_data;
+  download_data_writer->GetAndResetData(&downloaded_data);
+  size_t size = downloaded_data.size();
   if (size == 0) {
     return;
   }
-  const uint8* data = reinterpret_cast<const uint8*>(download_data->data());
+  const uint8* data = reinterpret_cast<const uint8*>(downloaded_data.data());
   base::AutoLock auto_lock(lock_);
 
   if (fetcher_.get() != source || error_occured_) {
diff --git a/src/cobalt/media/fetcher_buffered_data_source.h b/src/cobalt/media/fetcher_buffered_data_source.h
index 6eca602..66fef53 100644
--- a/src/cobalt/media/fetcher_buffered_data_source.h
+++ b/src/cobalt/media/fetcher_buffered_data_source.h
@@ -28,6 +28,7 @@
 #include "cobalt/csp/content_security_policy.h"
 #include "cobalt/loader/fetcher.h"
 #include "cobalt/loader/origin.h"
+#include "cobalt/loader/url_fetcher_string_writer.h"
 #include "cobalt/media/player/buffered_data_source.h"
 #include "cobalt/network/network_module.h"
 #include "net/url_request/url_fetcher.h"
diff --git a/src/cobalt/media/media.gyp b/src/cobalt/media/media.gyp
index 7cd8793..38b157f 100644
--- a/src/cobalt/media/media.gyp
+++ b/src/cobalt/media/media.gyp
@@ -232,7 +232,6 @@
       'dependencies': [
         'media',
         '<(DEPTH)/cobalt/base/base.gyp:base',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
       ],
@@ -241,6 +240,7 @@
         'filters/shell_mp4_map_unittest.cc',
         'filters/shell_rbsp_stream_unittest.cc',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
     {
       'target_name': 'media_test_deploy',
diff --git a/src/cobalt/media/sandbox/fuzzer_app.cc b/src/cobalt/media/sandbox/fuzzer_app.cc
index 9a9d0c2..18f58ae 100644
--- a/src/cobalt/media/sandbox/fuzzer_app.cc
+++ b/src/cobalt/media/sandbox/fuzzer_app.cc
@@ -113,11 +113,21 @@
     return;
   }
 
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+  std::vector<char> entry(SB_FILE_MAX_NAME);
+
+  while (SbDirectoryGetNext(directory, entry.data(), entry.size())) {
+    std::string file_name = path_name + SB_FILE_SEP_STRING + 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;
     AddFile(file_name, min_ratio, max_ratio, initial_seed);
   }
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
 
   SbDirectoryClose(directory);
 }
diff --git a/src/cobalt/media_capture/media_capture_test.gyp b/src/cobalt/media_capture/media_capture_test.gyp
index a28069c..2d330ab 100644
--- a/src/cobalt/media_capture/media_capture_test.gyp
+++ b/src/cobalt/media_capture/media_capture_test.gyp
@@ -35,10 +35,10 @@
         # For Fake Microphone.
         '<(DEPTH)/cobalt/speech/speech.gyp:speech',
 
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/media_session/media_session_test.gyp b/src/cobalt/media_session/media_session_test.gyp
index aa135c1..9c2c8d7 100644
--- a/src/cobalt/media_session/media_session_test.gyp
+++ b/src/cobalt/media_session/media_session_test.gyp
@@ -24,10 +24,10 @@
         '<(DEPTH)/cobalt/media_session/media_session.gyp:media_session',
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/browser/browser.gyp:browser',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
     {
       'target_name': 'media_session_test_deploy',
diff --git a/src/cobalt/media_stream/media_stream_test.gyp b/src/cobalt/media_stream/media_stream_test.gyp
index ac57801..a7cc5b1 100644
--- a/src/cobalt/media_stream/media_stream_test.gyp
+++ b/src/cobalt/media_stream/media_stream_test.gyp
@@ -32,10 +32,10 @@
       'dependencies': [
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
         '<(DEPTH)/cobalt/media_stream/media_stream.gyp:media_stream',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/network/local_network.cc b/src/cobalt/network/local_network.cc
index 862eb19..07e5765 100644
--- a/src/cobalt/network/local_network.cc
+++ b/src/cobalt/network/local_network.cc
@@ -83,7 +83,7 @@
       return true;
     }
     if ((ip.address[0] == 172) &&
-        ((ip.address[1] >= 16) || (ip.address[1] <= 31))) {
+        ((ip.address[1] >= 16) && (ip.address[1] <= 31))) {
       // IP is in range 172.16.0.0 - 172.31.255.255 (172.16/12 prefix).
       return true;
     }
diff --git a/src/cobalt/network/network.gyp b/src/cobalt/network/network.gyp
index 08e7036..c06c9e9 100644
--- a/src/cobalt/network/network.gyp
+++ b/src/cobalt/network/network.gyp
@@ -116,11 +116,11 @@
       ],
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'network',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
     {
       'target_name': 'network_test_deploy',
diff --git a/src/cobalt/overlay_info/overlay_info.gyp b/src/cobalt/overlay_info/overlay_info.gyp
index 78c4b0f..9aab7f5 100644
--- a/src/cobalt/overlay_info/overlay_info.gyp
+++ b/src/cobalt/overlay_info/overlay_info.gyp
@@ -42,9 +42,9 @@
       ],
       'dependencies': [
         '<(DEPTH)/cobalt/overlay_info/overlay_info.gyp:overlay_info',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gtest.gyp:gtest',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/overlay_info/overlay_info_registry.cc b/src/cobalt/overlay_info/overlay_info_registry.cc
index 6b01448..1341374 100644
--- a/src/cobalt/overlay_info/overlay_info_registry.cc
+++ b/src/cobalt/overlay_info/overlay_info_registry.cc
@@ -32,8 +32,7 @@
   void Disable();
 
   void Register(const char* category, const char* str);
-  void Register(const char* category, const void* data, size_t data_size);
-  void RetrieveAndClear(std::vector<uint8_t>* infos);
+  void RetrieveAndClear(std::string* infos);
 
  private:
   // Reserve enough data for |infos_| to avoid extra allcations.
@@ -48,7 +47,7 @@
 
   bool enabled_ = true;
   starboard::Mutex mutex_;
-  std::vector<uint8_t> infos_;
+  std::string infos_;
 };
 
 // static
@@ -64,40 +63,25 @@
   infos_.clear();
 }
 
-void OverlayInfoRegistryImpl::Register(const char* category, const char* str) {
-  auto length = SbStringGetLength(str);
-  Register(category, reinterpret_cast<const uint8_t*>(str), length);
-}
-
-void OverlayInfoRegistryImpl::Register(const char* category, const void* data,
-                                       size_t data_size) {
+void OverlayInfoRegistryImpl::Register(const char* category, const char* data) {
   DCHECK(SbStringFindCharacter(
              category, static_cast<char>(OverlayInfoRegistry::kDelimiter)) ==
          NULL)
       << "Category " << category
       << " cannot contain the delimiter:" << OverlayInfoRegistry::kDelimiter;
-  auto category_size = SbStringGetLength(category);
-  auto total_size = category_size + 1 + data_size;
-
-  DCHECK_GT(category_size, 0u);
-  // Use |kMaxSizeOfData + 0| to avoid link error caused by DCHECK_LE.
-  DCHECK_LE(total_size, OverlayInfoRegistry::kMaxSizeOfData + 0);
-
-  starboard::ScopedLock scoped_lock(mutex_);
-  // Use |kMaxNumberOfPendingOverlayInfo + 0| to avoid link error caused by
-  // DCHECK_LE.
-  DCHECK_LE(infos_.size() + total_size,
-            OverlayInfoRegistry::kMaxNumberOfPendingOverlayInfo + 0);
-  if (enabled_) {
-    infos_.push_back(static_cast<uint8_t>(total_size));
-    infos_.insert(infos_.end(), category, category + category_size);
-    infos_.push_back(kDelimiter);
-    infos_.insert(infos_.end(), static_cast<const uint8_t*>(data),
-                  static_cast<const uint8_t*>(data) + data_size);
+  DCHECK(SbStringFindCharacter(
+             data, static_cast<char>(OverlayInfoRegistry::kDelimiter)) == NULL)
+      << "Data " << data
+      << " cannot contain the delimiter:" << OverlayInfoRegistry::kDelimiter;
+  if (!infos_.empty()) {
+    infos_ += kDelimiter;
   }
+  infos_ += category;
+  infos_ += kDelimiter;
+  infos_ += data;
 }
 
-void OverlayInfoRegistryImpl::RetrieveAndClear(std::vector<uint8_t>* infos) {
+void OverlayInfoRegistryImpl::RetrieveAndClear(std::string* infos) {
   DCHECK(infos);
 
   starboard::ScopedLock scoped_lock(mutex_);
@@ -114,16 +98,30 @@
   OverlayInfoRegistryImpl::GetInstance()->Disable();
 }
 
-void OverlayInfoRegistry::Register(const char* category, const char* str) {
-  OverlayInfoRegistryImpl::GetInstance()->Register(category, str);
+void OverlayInfoRegistry::Register(const char* category, const char* data) {
+  OverlayInfoRegistryImpl::GetInstance()->Register(category, data);
 }
 
 void OverlayInfoRegistry::Register(const char* category, const void* data,
                                    size_t data_size) {
-  OverlayInfoRegistryImpl::GetInstance()->Register(category, data, data_size);
+  const char kHex[] = "0123456789abcdef";
+
+  const uint8_t* data_as_bytes = static_cast<const uint8_t*>(data);
+  std::string data_in_hex;
+
+  data_in_hex.reserve(data_size * 2);
+
+  while (data_size > 0) {
+    data_in_hex += kHex[*data_as_bytes / 16];
+    data_in_hex += kHex[*data_as_bytes % 16];
+    ++data_as_bytes;
+    --data_size;
+  }
+  OverlayInfoRegistryImpl::GetInstance()->Register(category,
+                                                   data_in_hex.c_str());
 }
 
-void OverlayInfoRegistry::RetrieveAndClear(std::vector<uint8_t>* infos) {
+void OverlayInfoRegistry::RetrieveAndClear(std::string* infos) {
   OverlayInfoRegistryImpl::GetInstance()->RetrieveAndClear(infos);
 }
 
diff --git a/src/cobalt/overlay_info/overlay_info_registry.h b/src/cobalt/overlay_info/overlay_info_registry.h
index ac45190..7de2a28 100644
--- a/src/cobalt/overlay_info/overlay_info_registry.h
+++ b/src/cobalt/overlay_info/overlay_info_registry.h
@@ -15,34 +15,27 @@
 #ifndef COBALT_OVERLAY_INFO_OVERLAY_INFO_REGISTRY_H_
 #define COBALT_OVERLAY_INFO_OVERLAY_INFO_REGISTRY_H_
 
-#include <vector>
+#include <sstream>
+#include <string>
 
 #include "starboard/types.h"
 
 namespace cobalt {
 namespace overlay_info {
 
-// This class allows register of arbitrary overlay information in the form of
-// string or binary data from anywhere inside Cobalt.  It also allows a consumer
-// to retrieve and clear all registered info, such info will be displayed as
-// overlay.
+// This class allows register of arbitrary overlay information from anywhere
+// inside Cobalt.  It also allows a consumer to retrieve and clear all
+// registered info, such info will be displayed as overlay.  The data is stored
+// as std::string internally.
 // The class is thread safe and all its methods can be accessed from any thread.
 // On average it expects to have less than 10 such info registered per frame.
 //
-// The binary data or string are stored in the following format:
-// [<one byte size> <size bytes data>]*
-// Each data entry contains a string category and some binary data, separated by
-// the delimiter '<'.
-// For example, the overlay infos ("media", "pts"), ("renderer", "fps\x60"), and
-// ("dom", "keydown") will be stored as
-//   '\x09', 'm', 'e', 'd', 'i', 'a', '<', 'p', 't', 's',
-//   '\x0d', 'r', 'e', 'n', 'd', 'e', 'r', 'e', 'r', '<', 'f', 'p', 's', '\x60',
-//   '\x0b', 'd', 'o', 'm', '<', 'k', 'e', 'y', 'd', 'o', 'w', 'n',
-// and the size of the vector will be 36.  Note that C strings won't be NULL
-// terminated and their sizes are calculated by the size of the data.
+// The info is stored in the following format:
+//   name0,value0,name1,value1,...
+// Binary data will be converted into hex string before storing.
 class OverlayInfoRegistry {
  public:
-  static const uint8_t kDelimiter = '<';  // ':' doesn't work with some scanners
+  static const char kDelimiter = ',';  // ':' doesn't work with some scanners
   // The size of category and data combined should be less than or equal to
   // kMaxSizeOfData - 1.  The extra room of one byte is used by the delimiter.
   static const size_t kMaxSizeOfData = 255;
@@ -52,14 +45,20 @@
 
   static void Disable();
 
-  // |category| cannot contain ':'.  The sum of size of |category| and |string|
-  // cannot exceed 254.  It leaves room for the delimiter.
-  static void Register(const char* category, const char* str);
-  // |category| cannot contain ':'.  The sum of size of |category| and |data|
-  // cannot exceed 254.  It leaves room for the delimiter.
+  // Both |category| and |data| cannot contain the delimiter.
+  static void Register(const char* category, const char* data);
+  // Both |category| and |data| cannot contain the delimiter.
   static void Register(const char* category, const void* data,
                        size_t data_size);
-  static void RetrieveAndClear(std::vector<uint8_t>* infos);
+  // Both |category| and |data| cannot contain the delimiter.
+  template <typename T>
+  static void Register(const char* category, T data) {
+    std::stringstream ss;
+    ss << data;
+    Register(category, ss.str().c_str());
+  }
+
+  static void RetrieveAndClear(std::string* infos);
 };
 
 }  // namespace overlay_info
diff --git a/src/cobalt/overlay_info/overlay_info_registry_test.cc b/src/cobalt/overlay_info/overlay_info_registry_test.cc
index ce031ee..0b5d97e 100644
--- a/src/cobalt/overlay_info/overlay_info_registry_test.cc
+++ b/src/cobalt/overlay_info/overlay_info_registry_test.cc
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "base/logging.h"
+#include "base/strings/string_split.h"
 #include "starboard/memory.h"
 #include "starboard/types.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -29,56 +30,36 @@
 
 namespace {
 
-typedef std::pair<std::string, std::vector<uint8_t>> ValuePair;
+typedef std::pair<std::string, std::string> ValuePair;
 
 // See comment of OverlayInfoRegistry for format of |info|.
-std::vector<ValuePair> ParseOverlayInfo(std::vector<uint8_t> info) {
-  std::vector<ValuePair> parsed_infos;
+bool ParseOverlayInfo(std::string infos, std::vector<ValuePair>* values) {
+  CHECK(values);
 
-  while (!info.empty()) {
-    // Parse the size
-    size_t size = info[0];
-    info.erase(info.begin());
+  const char delimiter[] = {OverlayInfoRegistry::kDelimiter, 0};
 
-    CHECK_LE(size, info.size());
-
-    // Parse the category name
-    const auto kDelimiter = OverlayInfoRegistry::kDelimiter;
-    auto iter = std::find(info.begin(), info.end(), kDelimiter);
-    CHECK(iter != info.end());
-
-    const auto category_length = iter - info.begin();
-    CHECK_LE(static_cast<size_t>(category_length), size);
-
-    std::string category(
-        reinterpret_cast<char*>(info.data()),
-        reinterpret_cast<char*>(info.data()) + category_length);
-
-    // Parse the data
-    std::vector<uint8_t> data(info.begin() + category_length + 1,
-                              info.begin() + size);
-    info.erase(info.begin(), info.begin() + size);
-
-    parsed_infos.push_back(std::make_pair(category, data));
+  auto tokens = base::SplitString(infos, delimiter, base::KEEP_WHITESPACE,
+                                  base::SPLIT_WANT_ALL);
+  if (tokens.size() % 2 != 0) {
+    return false;
   }
 
-  CHECK(info.empty());
-  return parsed_infos;
-}
+  while (!tokens.empty()) {
+    values->push_back(std::make_pair(tokens[0], tokens[1]));
+    tokens.erase(tokens.begin(), tokens.begin() + 2);
+  }
 
-bool IsSame(const std::vector<uint8_t>& data, const std::string& str) {
-  return data.size() == str.size() &&
-         SbMemoryCompare(data.data(), str.c_str(), data.size()) == 0;
+  return true;
 }
 
 }  // namespace
 
 TEST(OverlayInfoRegistryTest, RetrieveOnEmptyData) {
-  std::vector<uint8_t> infos('a');
+  std::string infos("a");
   OverlayInfoRegistry::RetrieveAndClear(&infos);
   EXPECT_TRUE(infos.empty());
 
-  std::vector<uint8_t> infos1('a');
+  std::string infos1("b");
   OverlayInfoRegistry::RetrieveAndClear(&infos1);
   EXPECT_TRUE(infos1.empty());
 }
@@ -92,33 +73,13 @@
 
     OverlayInfoRegistry::Register(kCategory, value.c_str());
 
-    std::vector<uint8_t> infos('a');
+    std::string infos("a");
     OverlayInfoRegistry::RetrieveAndClear(&infos);
-    auto parsed_infos = ParseOverlayInfo(infos);
+    std::vector<ValuePair> parsed_infos;
+    ASSERT_TRUE(ParseOverlayInfo(infos, &parsed_infos));
     EXPECT_EQ(parsed_infos.size(), 1);
     EXPECT_EQ(parsed_infos[0].first, kCategory);
-    EXPECT_TRUE(IsSame(parsed_infos[0].second, value));
-
-    OverlayInfoRegistry::RetrieveAndClear(&infos);
-    EXPECT_TRUE(infos.empty());
-  }
-}
-
-TEST(OverlayInfoRegistryTest, RegisterSingleBinaryStringAndRetrieve) {
-  const char kCategory[] = "category";
-  const size_t kMaxDataSize = 20;
-
-  for (size_t i = 0; i < kMaxDataSize; ++i) {
-    std::vector<uint8_t> value(i, static_cast<uint8_t>(i % 2));
-
-    OverlayInfoRegistry::Register(kCategory, value.data(), value.size());
-
-    std::vector<uint8_t> infos('a');
-    OverlayInfoRegistry::RetrieveAndClear(&infos);
-    auto parsed_infos = ParseOverlayInfo(infos);
-    EXPECT_EQ(parsed_infos.size(), 1);
-    EXPECT_EQ(parsed_infos[0].first, kCategory);
-    EXPECT_TRUE(parsed_infos[0].second == value);
+    EXPECT_EQ(parsed_infos[0].second, value);
 
     OverlayInfoRegistry::RetrieveAndClear(&infos);
     EXPECT_TRUE(infos.empty());
@@ -137,9 +98,10 @@
     OverlayInfoRegistry::Register(kCategory, values.back().c_str());
   }
 
-  std::vector<uint8_t> infos('a');
+  std::string infos("a");
   OverlayInfoRegistry::RetrieveAndClear(&infos);
-  auto parsed_infos = ParseOverlayInfo(infos);
+  std::vector<ValuePair> parsed_infos;
+  ASSERT_TRUE(ParseOverlayInfo(infos, &parsed_infos));
   OverlayInfoRegistry::RetrieveAndClear(&infos);
   EXPECT_TRUE(infos.empty());
 
@@ -147,35 +109,19 @@
 
   for (size_t i = 0; i < kMaxStringLength; ++i) {
     EXPECT_EQ(parsed_infos[i].first, kCategory);
-    EXPECT_TRUE(IsSame(parsed_infos[i].second, values[i]));
+    EXPECT_EQ(parsed_infos[i].second, values[i]);
   }
 }
 
-TEST(OverlayInfoRegistryTest, RegisterMultipleBinaryDataAndRetrieve) {
-  const char kCategory[] = "c";
-  const size_t kMaxDataSize = 20;
+TEST(OverlayInfoRegistryTest, RegisterMultipleTypes) {
+  OverlayInfoRegistry::Register("string", "string_value");
+  OverlayInfoRegistry::Register("int", -12345);
+  OverlayInfoRegistry::Register("uint64", 123456789012u);
 
-  std::vector<std::vector<uint8_t>> values;
-
-  for (size_t i = 0; i < kMaxDataSize; ++i) {
-    values.push_back(std::vector<uint8_t>(i, static_cast<uint8_t>(i % 2)));
-
-    OverlayInfoRegistry::Register(kCategory, values.back().data(),
-                                  values.back().size());
-  }
-
-  std::vector<uint8_t> infos('a');
+  std::string infos("a");
   OverlayInfoRegistry::RetrieveAndClear(&infos);
-  auto parsed_infos = ParseOverlayInfo(infos);
-  OverlayInfoRegistry::RetrieveAndClear(&infos);
-  EXPECT_TRUE(infos.empty());
 
-  ASSERT_EQ(parsed_infos.size(), kMaxDataSize);
-
-  for (size_t i = 0; i < kMaxDataSize; ++i) {
-    EXPECT_EQ(parsed_infos[i].first, kCategory);
-    EXPECT_TRUE(parsed_infos[i].second == values[i]);
-  }
+  EXPECT_EQ(infos, "string,string_value,int,-12345,uint64,123456789012");
 }
 
 }  // namespace overlay_info
diff --git a/src/cobalt/overlay_info/qr_code_overlay.cc b/src/cobalt/overlay_info/qr_code_overlay.cc
index 6fee031..f4691d3 100644
--- a/src/cobalt/overlay_info/qr_code_overlay.cc
+++ b/src/cobalt/overlay_info/qr_code_overlay.cc
@@ -15,13 +15,14 @@
 #include "cobalt/overlay_info/qr_code_overlay.h"
 
 #include <algorithm>
-#include <vector>
+#include <string>
 
 #include "base/compiler_specific.h"
 #include "base/logging.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/overlay_info/overlay_info_registry.h"
 #include "cobalt/render_tree/animations/animate_node.h"
+#include "starboard/memory.h"
 #include "third_party/QR-Code-generator/cpp/QrCode.hpp"
 
 namespace cobalt {
@@ -32,16 +33,20 @@
 using qrcodegen::QrCode;
 using render_tree::Image;
 using render_tree::ImageNode;
+using render_tree::ResourceProvider;
 
-const int kModuleDimensionInPixels = 4;
+const int kMinimumQrCodeVersion = 3;
 const int kPixelSizeInBytes = 4;
-const uint32_t kBlack = 0x00000000;
+#if SB_IS_BIG_ENDIAN
+const uint32_t kBlack = 0x000000FF;
+#else   // SB_IS_BIG_ENDIAN
+const uint32_t kBlack = 0xFF000000;
+#endif  // SB_IS_BIG_ENDIAN
 const uint32_t kWhite = 0xFFFFFFFF;
 const uint32_t kBorderColor = kWhite;
-const int kCodeBorderInPixels = 16;
-const int kScreenMarginInPixels = 128;
+const int kScreenMarginInPixels = 64;
 
-int64_t s_frame_count_ = 0;
+uint32_t s_frame_index_ = 0;
 
 void DrawRect(int width, int height, int pitch_in_bytes, uint32_t color,
               uint8_t* target_buffer) {
@@ -55,43 +60,45 @@
   }
 }
 
-void DrawQrCode(const QrCode& qr_code, int pitch_in_bytes,
-                uint8_t* target_buffer) {
+void DrawQrCode(const QrCode& qr_code, int module_dimension_in_pixels,
+                int pitch_in_bytes, uint8_t* target_buffer) {
   uint8_t* row_data = target_buffer;
   for (int row = 0; row < qr_code.getSize(); ++row) {
     uint8_t* column_data = row_data;
     for (int column = 0; column < qr_code.getSize(); ++column) {
-      DrawRect(kModuleDimensionInPixels, kModuleDimensionInPixels,
+      DrawRect(module_dimension_in_pixels, module_dimension_in_pixels,
                pitch_in_bytes, qr_code.getModule(row, column) ? kBlack : kWhite,
                column_data);
-      column_data += kPixelSizeInBytes * kModuleDimensionInPixels;
+      column_data += kPixelSizeInBytes * module_dimension_in_pixels;
     }
 
-    row_data += pitch_in_bytes * kModuleDimensionInPixels;
+    row_data += pitch_in_bytes * module_dimension_in_pixels;
   }
 }
 
-scoped_refptr<Image> CreateImageForQrCodes(
-    const std::vector<QrCode>& qr_codes, const math::Size& screen_size,
-    render_tree::ResourceProvider* resource_provider) {
-  TRACE_EVENT0("cobalt::overlay_info", "CreateImageForQrCodes()");
+scoped_refptr<Image> CreateImageForQrCode(const QrCode& qr_code,
+                                          const math::Size& screen_size,
+                                          int slots,
+                                          ResourceProvider* resource_provider) {
+  TRACE_EVENT0("cobalt::overlay_info", "CreateImageForQrCode()");
 
-  int max_code_size = 0;
+  const int module_dimension_in_pixels = screen_size.height() > 1080 ? 16 : 8;
+  const int code_border_in_pixels = module_dimension_in_pixels * 2;
 
-  for (auto& qr_code : qr_codes) {
-    max_code_size = std::max(max_code_size, qr_code.getSize());
-  }
+  int qr_code_size_in_blocks = qr_code.getSize();
 
-  int column =
-      (screen_size.width() - kScreenMarginInPixels * 2 - kCodeBorderInPixels) /
-      (max_code_size * kModuleDimensionInPixels + kCodeBorderInPixels);
-  column = std::min(column, static_cast<int>(qr_codes.size()));
-  int row = (static_cast<int>(qr_codes.size()) + column - 1) / column;
+  int column = (screen_size.width() - kScreenMarginInPixels * 2 -
+                code_border_in_pixels) /
+               (qr_code_size_in_blocks * module_dimension_in_pixels +
+                code_border_in_pixels);
+  column = std::min(column, slots);
+  int row = (slots + column - 1) / column;
 
-  int image_width = column * max_code_size * kModuleDimensionInPixels +
-                    kCodeBorderInPixels * (column + 1);
-  int image_height = row * max_code_size * kModuleDimensionInPixels +
-                     kCodeBorderInPixels * (row + 1);
+  int image_width =
+      column * qr_code_size_in_blocks * module_dimension_in_pixels +
+      code_border_in_pixels * (column + 1);
+  int image_height = row * qr_code_size_in_blocks * module_dimension_in_pixels +
+                     code_border_in_pixels * (row + 1);
 
   auto image_data = resource_provider->AllocateImageData(
       math::Size(image_width, image_height), render_tree::kPixelFormatRGBA8,
@@ -99,59 +106,63 @@
   DCHECK(image_data);
   auto image_desc = image_data->GetDescriptor();
 
-  size_t qr_code_index = 0;
+  uint32_t slot_index = 0;
   auto row_data = image_data->GetMemory();
   for (int i = 0; i < row; ++i) {
-    // Draw the top border of all qr codes in the row.
-    DrawRect(image_width, kCodeBorderInPixels, image_desc.pitch_in_bytes,
+    // Draw the top border of all qr code blocks in the row.
+    DrawRect(image_width, code_border_in_pixels, image_desc.pitch_in_bytes,
              kBorderColor, row_data);
-    row_data += kCodeBorderInPixels * image_desc.pitch_in_bytes;
+    row_data += code_border_in_pixels * image_desc.pitch_in_bytes;
     auto column_data = row_data;
 
     for (int j = 0; j < column; ++j) {
       // Draw the left border.
-      DrawRect(kCodeBorderInPixels, max_code_size * kModuleDimensionInPixels,
+      DrawRect(code_border_in_pixels,
+               qr_code_size_in_blocks * module_dimension_in_pixels,
                image_desc.pitch_in_bytes, kBorderColor, column_data);
-      column_data += kCodeBorderInPixels * kPixelSizeInBytes;
-      if (qr_code_index < qr_codes.size()) {
+      column_data += code_border_in_pixels * kPixelSizeInBytes;
+      if (slot_index == s_frame_index_ % slots) {
         // Draw qr code.
-        DrawQrCode(qr_codes[qr_code_index], image_desc.pitch_in_bytes,
-                   column_data);
-        ++qr_code_index;
+        DrawQrCode(qr_code, module_dimension_in_pixels,
+                   image_desc.pitch_in_bytes, column_data);
+      } else {
+        DrawRect(qr_code_size_in_blocks * module_dimension_in_pixels,
+                 qr_code_size_in_blocks * module_dimension_in_pixels,
+                 image_desc.pitch_in_bytes, kBlack, column_data);
       }
-      column_data +=
-          max_code_size * kModuleDimensionInPixels * kPixelSizeInBytes;
+      ++slot_index;
+      column_data += qr_code_size_in_blocks * module_dimension_in_pixels *
+                     kPixelSizeInBytes;
     }
 
     // Draw the right border of the row.
-    DrawRect(kCodeBorderInPixels, max_code_size * kModuleDimensionInPixels,
+    DrawRect(code_border_in_pixels,
+             qr_code_size_in_blocks * module_dimension_in_pixels,
              image_desc.pitch_in_bytes, kBorderColor, column_data);
 
-    row_data +=
-        max_code_size * kModuleDimensionInPixels * image_desc.pitch_in_bytes;
+    row_data += qr_code_size_in_blocks * module_dimension_in_pixels *
+                image_desc.pitch_in_bytes;
   }
 
   // Draw the bottom border of all qr code.
-  DrawRect(image_width, kCodeBorderInPixels, image_desc.pitch_in_bytes,
+  DrawRect(image_width, code_border_in_pixels, image_desc.pitch_in_bytes,
            kBorderColor, row_data);
 
   return resource_provider->CreateImage(std::move(image_data));
 }
 
-void AnimateCB(math::Size screen_size,
-               render_tree::ResourceProvider* resource_provider,
-               render_tree::ImageNode::Builder* image_node,
-               base::TimeDelta time) {
+void AnimateCB(math::Size screen_size, int slots,
+               ResourceProvider* resource_provider,
+               ImageNode::Builder* image_node, base::TimeDelta time) {
   SB_UNREFERENCED_PARAMETER(time);
   DCHECK(image_node);
 
   TRACE_EVENT0("cobalt::overlay_info", "AnimateCB()");
 
-  OverlayInfoRegistry::Register("overlay_info:frame_count", &s_frame_count_,
-                                sizeof(s_frame_count_));
-  ++s_frame_count_;
+  OverlayInfoRegistry::Register("frame", s_frame_index_);
+  ++s_frame_index_;
 
-  std::vector<uint8_t> infos;
+  std::string infos;
   OverlayInfoRegistry::RetrieveAndClear(&infos);
 
   if (infos.empty()) {
@@ -159,26 +170,25 @@
     return;
   }
 
-  // Use a vector in case we decide to switch back to multiple qr codes.
-  std::vector<QrCode> qr_codes;
-  qr_codes.emplace_back(QrCode::encodeBinary(infos, QrCode::Ecc::LOW));
-
+  auto qrcode = QrCode::encodeText(infos.c_str(), QrCode::Ecc::LOW,
+                                   kMinimumQrCodeVersion);
   image_node->source =
-      CreateImageForQrCodes(qr_codes, screen_size, resource_provider);
+      CreateImageForQrCode(qrcode, screen_size, slots, resource_provider);
   auto image_size = image_node->source->GetSize();
-  // TODO: Move the QR code between draws to avoid tearing.
-  image_node->destination_rect =
-      math::RectF(kScreenMarginInPixels, kScreenMarginInPixels,
-                  image_size.width(), image_size.height());
+  image_node->destination_rect = math::RectF(
+      screen_size.width() - image_size.width() - kScreenMarginInPixels,
+      screen_size.height() - image_size.height() - kScreenMarginInPixels,
+      image_size.width(), image_size.height());
 }
 
 }  // namespace
 
 QrCodeOverlay::QrCodeOverlay(
-    const math::Size& screen_size,
-    render_tree::ResourceProvider* resource_provider,
+    const math::Size& screen_size, int slots,
+    ResourceProvider* resource_provider,
     const RenderTreeProducedCB& render_tree_produced_cb)
     : render_tree_produced_cb_(render_tree_produced_cb),
+      slots_(slots),
       screen_size_(screen_size),
       resource_provider_(resource_provider) {
   DCHECK_GT(screen_size.width(), 0);
@@ -196,8 +206,7 @@
   UpdateRenderTree();
 }
 
-void QrCodeOverlay::SetResourceProvider(
-    render_tree::ResourceProvider* resource_provider) {
+void QrCodeOverlay::SetResourceProvider(ResourceProvider* resource_provider) {
   resource_provider_ = resource_provider;
   UpdateRenderTree();
 }
@@ -212,8 +221,8 @@
   scoped_refptr<ImageNode> image_node = new ImageNode(nullptr);
   render_tree::animations::AnimateNode::Builder animate_node_builder;
 
-  animate_node_builder.Add(
-      image_node, base::Bind(AnimateCB, screen_size_, resource_provider_));
+  animate_node_builder.Add(image_node, base::Bind(AnimateCB, screen_size_,
+                                                  slots_, resource_provider_));
 
   render_tree_produced_cb_.Run(new render_tree::animations::AnimateNode(
       animate_node_builder, image_node));
diff --git a/src/cobalt/overlay_info/qr_code_overlay.h b/src/cobalt/overlay_info/qr_code_overlay.h
index 5d5aa15..02d630b 100644
--- a/src/cobalt/overlay_info/qr_code_overlay.h
+++ b/src/cobalt/overlay_info/qr_code_overlay.h
@@ -31,7 +31,7 @@
   typedef base::Callback<void(const scoped_refptr<render_tree::Node>&)>
       RenderTreeProducedCB;
 
-  QrCodeOverlay(const math::Size& screen_size,
+  QrCodeOverlay(const math::Size& screen_size, int slots,
                 render_tree::ResourceProvider* resource_provider,
                 const RenderTreeProducedCB& render_tree_produced_cb);
 
@@ -41,7 +41,11 @@
  private:
   void UpdateRenderTree();
 
-  RenderTreeProducedCB render_tree_produced_cb_;
+  const RenderTreeProducedCB render_tree_produced_cb_;
+  // Qr codes are displayed in rotating positions so it won't be blurred during
+  // capture.  The number of rotating positions are specified by |slots_|.
+  const int slots_;
+
   math::Size screen_size_;
   render_tree::ResourceProvider* resource_provider_;
 };
diff --git a/src/cobalt/page_visibility/page_visibility.gyp b/src/cobalt/page_visibility/page_visibility.gyp
index bef5f08..4d85443 100644
--- a/src/cobalt/page_visibility/page_visibility.gyp
+++ b/src/cobalt/page_visibility/page_visibility.gyp
@@ -47,11 +47,11 @@
         'page_visibility_state_test.cc',
       ],
       'dependencies': [
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'page_visibility',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
     {
       'target_name': 'page_visibility_test_deploy',
diff --git a/src/cobalt/render_tree/render_tree.gyp b/src/cobalt/render_tree/render_tree.gyp
index c7c0ec9..8f74058 100644
--- a/src/cobalt/render_tree/render_tree.gyp
+++ b/src/cobalt/render_tree/render_tree.gyp
@@ -104,12 +104,12 @@
         'node_visitor_test.cc',
       ],
       'dependencies': [
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'animations',
         'render_tree',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     # Deploys the render tree library test on a console.
diff --git a/src/cobalt/renderer/backend/backend.gyp b/src/cobalt/renderer/backend/backend.gyp
index 3ff2de4..a3b5e85 100644
--- a/src/cobalt/renderer/backend/backend.gyp
+++ b/src/cobalt/renderer/backend/backend.gyp
@@ -23,7 +23,6 @@
         'render_target.cc',
         'render_target.h',
       ],
-
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/math/math.gyp:math',
diff --git a/src/cobalt/renderer/backend/graphics_context.cc b/src/cobalt/renderer/backend/graphics_context.cc
index 12f0e3c..4008b32 100644
--- a/src/cobalt/renderer/backend/graphics_context.cc
+++ b/src/cobalt/renderer/backend/graphics_context.cc
@@ -52,6 +52,16 @@
   return -1.0f;
 }
 
+float GraphicsContext::GetMinimumFrameIntervalInMilliseconds() {
+  if (graphics_extension_ && graphics_extension_->version >= 2) {
+    return graphics_extension_->GetMinimumFrameIntervalInMilliseconds();
+  }
+
+  // Return negative value, if the GraphicsExtension is not implemented
+  // or the GraphicsExtension version is below 2.
+  return -1.0f;
+}
+
 }  // namespace backend
 }  // namespace renderer
 }  // namespace cobalt
diff --git a/src/cobalt/renderer/backend/graphics_context.h b/src/cobalt/renderer/backend/graphics_context.h
index 8621f5e..ead28c3 100644
--- a/src/cobalt/renderer/backend/graphics_context.h
+++ b/src/cobalt/renderer/backend/graphics_context.h
@@ -85,6 +85,15 @@
   // only be presented when something changes.
   virtual float GetMaximumFrameIntervalInMilliseconds();
 
+  // Allow throttling of the frame rate. This is expressed in terms of
+  // 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. If implemented, this takes precedence over the gyp
+  // variable 'cobalt_minimum_frame_time_in_milliseconds'.
+  // Note: Return a negative number if no value is specified by the platform.
+  virtual float GetMinimumFrameIntervalInMilliseconds();
+
  private:
   GraphicsSystem* system_;
 
diff --git a/src/cobalt/renderer/pipeline.cc b/src/cobalt/renderer/pipeline.cc
index ef54726..e2dad23 100644
--- a/src/cobalt/renderer/pipeline.cc
+++ b/src/cobalt/renderer/pipeline.cc
@@ -298,12 +298,20 @@
     // swaps. It is possible that a submission is not rendered (this can
     // happen if the render tree has not changed between submissions), so no
     // frame swap occurs, and the minimum frame time is the only throttle.
+    float minimum_frame_interval_milliseconds =
+        graphics_context_
+            ? graphics_context_->GetMinimumFrameIntervalInMilliseconds()
+            : -1.0f;
     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;
+    }
+    DCHECK(minimum_frame_interval_milliseconds > 0.0f);
     rasterize_timer_.emplace(
         FROM_HERE,
-        base::TimeDelta::FromMillisecondsD(
-            COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS),
+        base::TimeDelta::FromMillisecondsD(minimum_frame_interval_milliseconds),
         base::BindRepeating(&Pipeline::RasterizeCurrentTree,
                             base::Unretained(this)));
     rasterize_timer_->Reset();
diff --git a/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp b/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp
index f989a6d..af03b7b 100644
--- a/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp
+++ b/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp
@@ -103,10 +103,10 @@
 
       'dependencies': [
         'hardware_rasterizer',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest'
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
index b3e56b2..0fd2ab2 100644
--- a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
+++ b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
@@ -85,10 +85,17 @@
     1.164f, 2.112f, 0.0f,   -1.12875f, 0.0f,   0.0f,    0.0f,    1.0f};
 
 // Used for 10bit unnormalized YUV images.
+// Y is between 64 and 940 inclusive. U and V are between 64 and 960 inclusive.
+// it is 1023/(940-64) = 1.1678 for Y and 1023/(960-64) = 1.1417 for U and V.
+// 64 is the scale factor for 10 bit.
+// Input YUV must be subtracted by (0.0625, 0.5, 0.5), so
+// -1.1678 * 0.0625  - 0 * 0.5         - 1.6835 * 0.5    = -0.9147
+// -1.1678 * 0.0625  -(-0.1878 * 0.5)  - (-0.6522 * 0.5) = 0.347
+// -1.1678 * 0.0625  - (2.1479f * 0.5) - 0 * 0.5         = -1.1469
 const float k10BitBT2020ColorMatrix[16] = {
-    64 * 1.1678f, 0.0f,          64 * 1.6835f,  -0.96925f,
-    64 * 1.1678f, 64 * -0.1878f, 64 * -0.6522f, 0.30025f,
-    64 * 1.1678f, 64 * 2.1479f,  0.0f,          -1.12875f,
+    64 * 1.1678f, 0.0f,          64 * 1.6835f,  -0.9147f,
+    64 * 1.1678f, 64 * -0.1878f, 64 * -0.6522f, 0.347f,
+    64 * 1.1678f, 64 * 2.1479f,  0.0f,          -1.1469f,
     0.0f,         0.0f,          0.0f,          1.0f};
 
 const float* GetColorMatrixForImageType(
diff --git a/src/cobalt/renderer/renderer.gyp b/src/cobalt/renderer/renderer.gyp
index 89d6804..b9b59e9 100644
--- a/src/cobalt/renderer/renderer.gyp
+++ b/src/cobalt/renderer/renderer.gyp
@@ -101,7 +101,6 @@
       ],
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'render_tree_pixel_tester',
@@ -112,6 +111,7 @@
           'defines' : ['ENABLE_MAP_TO_MESH'],
         }],
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
     {
       'target_name': 'renderer_copy_test_data',
diff --git a/src/cobalt/renderer/test/png_utils/png_utils.gyp b/src/cobalt/renderer/test/png_utils/png_utils.gyp
index dbea190..f56ddc6 100644
--- a/src/cobalt/renderer/test/png_utils/png_utils.gyp
+++ b/src/cobalt/renderer/test/png_utils/png_utils.gyp
@@ -44,12 +44,12 @@
       ],
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'png_utils',
         'png_utils_copy_test_data',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
     {
       'target_name': 'png_utils_benchmark',
diff --git a/src/cobalt/samples/simple_example/simple_example.gyp b/src/cobalt/samples/simple_example/simple_example.gyp
index 5548fc2..7a6226c 100644
--- a/src/cobalt/samples/simple_example/simple_example.gyp
+++ b/src/cobalt/samples/simple_example/simple_example.gyp
@@ -91,11 +91,11 @@
       ],
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'simple_example_lib',
         'simple_example_copy_test_data',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     # This target is optional and is only needed if tests are using
diff --git a/src/cobalt/script/array_buffer.h b/src/cobalt/script/array_buffer.h
index 850fc52..eb5acce 100644
--- a/src/cobalt/script/array_buffer.h
+++ b/src/cobalt/script/array_buffer.h
@@ -15,6 +15,7 @@
 #ifndef COBALT_SCRIPT_ARRAY_BUFFER_H_
 #define COBALT_SCRIPT_ARRAY_BUFFER_H_
 
+#include <algorithm>
 #include <memory>
 
 #include "base/logging.h"
@@ -81,6 +82,7 @@
 //   DCHECK_EQ(data.data(), nullptr);
 class PreallocatedArrayBufferData {
  public:
+  PreallocatedArrayBufferData() = default;
   explicit PreallocatedArrayBufferData(size_t byte_length);
   ~PreallocatedArrayBufferData();
 
@@ -89,8 +91,17 @@
       default;
 
   void* data() { return data_; }
+  const void* data() const { return data_; }
   size_t byte_length() const { return byte_length_; }
 
+  void Swap(PreallocatedArrayBufferData* that) {
+    DCHECK(that);
+
+    std::swap(data_, that->data_);
+    std::swap(byte_length_, that->byte_length_);
+  }
+  void Resize(size_t new_byte_length);
+
  private:
   PreallocatedArrayBufferData(const PreallocatedArrayBufferData&) = delete;
   void operator=(const PreallocatedArrayBufferData&) = delete;
@@ -106,8 +117,8 @@
     byte_length_ = 0u;
   }
 
-  void* data_;
-  size_t byte_length_;
+  void* data_ = nullptr;
+  size_t byte_length_ = 0u;
 
   friend ArrayBuffer;
 };
diff --git a/src/cobalt/script/mozjs-45/mozjs_array_buffer.cc b/src/cobalt/script/mozjs-45/mozjs_array_buffer.cc
index 1fa7a43..714a582 100644
--- a/src/cobalt/script/mozjs-45/mozjs_array_buffer.cc
+++ b/src/cobalt/script/mozjs-45/mozjs_array_buffer.cc
@@ -12,11 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <algorithm>
 #include <memory>
 
 #include "cobalt/script/mozjs-45/mozjs_array_buffer.h"
 
 #include "cobalt/base/polymorphic_downcast.h"
+#include "starboard/memory.h"
 
 namespace cobalt {
 namespace script {
@@ -36,6 +38,22 @@
   }
 }
 
+void PreallocatedArrayBufferData::Resize(size_t new_byte_length) {
+  if (byte_length_ == new_byte_length) {
+    return;
+  }
+  auto new_data = js_malloc(new_byte_length);
+  DCHECK(new_data);
+  if (data_) {
+    if (new_data) {
+      SbMemoryCopy(new_data, data_, std::min(byte_length_, new_byte_length));
+    }
+    js_free(data_);
+  }
+  data_ = new_data;
+  byte_length_ = new_byte_length;
+}
+
 // static
 Handle<ArrayBuffer> ArrayBuffer::New(GlobalEnvironment* global_environment,
                                      size_t byte_length) {
diff --git a/src/cobalt/script/v8c/v8c_array_buffer.cc b/src/cobalt/script/v8c/v8c_array_buffer.cc
index 13087af..7fe8b3a 100644
--- a/src/cobalt/script/v8c/v8c_array_buffer.cc
+++ b/src/cobalt/script/v8c/v8c_array_buffer.cc
@@ -17,6 +17,7 @@
 #include "cobalt/script/v8c/v8c_array_buffer.h"
 
 #include "cobalt/base/polymorphic_downcast.h"
+#include "starboard/memory.h"
 
 namespace cobalt {
 namespace script {
@@ -34,6 +35,11 @@
   }
 }
 
+void PreallocatedArrayBufferData::Resize(size_t new_byte_length) {
+  data_ = SbMemoryReallocate(data_, new_byte_length);
+  byte_length_ = new_byte_length;
+}
+
 // static
 Handle<ArrayBuffer> ArrayBuffer::New(GlobalEnvironment* global_environment,
                                      size_t byte_length) {
diff --git a/src/cobalt/script/v8c/v8c_callback_function.h b/src/cobalt/script/v8c/v8c_callback_function.h
index b958457..c069726 100644
--- a/src/cobalt/script/v8c/v8c_callback_function.h
+++ b/src/cobalt/script/v8c/v8c_callback_function.h
@@ -22,6 +22,8 @@
 #ifndef COBALT_SCRIPT_V8C_V8C_CALLBACK_FUNCTION_H_
 #define COBALT_SCRIPT_V8C_V8C_CALLBACK_FUNCTION_H_
 
+#include <string>
+
 #include "base/logging.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/v8c/conversion_helpers.h"
@@ -90,6 +92,7 @@
 
     EntryScope entry_scope(isolate_);
     v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+    v8::TryCatch try_catch(isolate_);
 
     // https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
     // Callback 'this' is set to null, unless overridden by other specifications
@@ -110,7 +113,16 @@
         function_as_object->CallAsFunction(context, this_value, argc, argv);
     v8::Local<v8::Value> return_value;
     if (!maybe_return_value.ToLocal(&return_value)) {
-      NOTIMPLEMENTED();
+      std::string description;
+      v8::Local<v8::Value> stack;
+      if (try_catch.StackTrace(context).ToLocal(&stack)) {
+        description = *v8::String::Utf8Value(isolate_, stack);
+      } else {
+        description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+      }
+      if (description.empty()) description = "Unknown exception";
+      // TODO: Send the description to the console instead of logging it.
+      LOG(ERROR) << description;
       callback_result.exception = true;
     } else {
       callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
@@ -133,8 +145,7 @@
   V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
       : ScopedPersistent(isolate, handle), isolate_(isolate) {}
 
-  CallbackResult<R> Run(      typename
-      CallbackParamTraits<A1>::ForwardType a1)
+  CallbackResult<R> Run(typename CallbackParamTraits<A1>::ForwardType a1)
       const override {
     CallbackResult<R> callback_result;
     DCHECK(!this->IsEmpty());
@@ -146,6 +157,7 @@
 
     EntryScope entry_scope(isolate_);
     v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+    v8::TryCatch try_catch(isolate_);
 
     // https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
     // Callback 'this' is set to null, unless overridden by other specifications
@@ -168,7 +180,16 @@
         function_as_object->CallAsFunction(context, this_value, argc, argv);
     v8::Local<v8::Value> return_value;
     if (!maybe_return_value.ToLocal(&return_value)) {
-      NOTIMPLEMENTED();
+      std::string description;
+      v8::Local<v8::Value> stack;
+      if (try_catch.StackTrace(context).ToLocal(&stack)) {
+        description = *v8::String::Utf8Value(isolate_, stack);
+      } else {
+        description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+      }
+      if (description.empty()) description = "Unknown exception";
+      // TODO: Send the description to the console instead of logging it.
+      LOG(ERROR) << description;
       callback_result.exception = true;
     } else {
       callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
@@ -191,8 +212,7 @@
   V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
       : ScopedPersistent(isolate, handle), isolate_(isolate) {}
 
-  CallbackResult<R> Run(      typename
-      CallbackParamTraits<A1>::ForwardType a1,
+  CallbackResult<R> Run(typename CallbackParamTraits<A1>::ForwardType a1,
       typename CallbackParamTraits<A2>::ForwardType a2)
       const override {
     CallbackResult<R> callback_result;
@@ -205,6 +225,7 @@
 
     EntryScope entry_scope(isolate_);
     v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+    v8::TryCatch try_catch(isolate_);
 
     // https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
     // Callback 'this' is set to null, unless overridden by other specifications
@@ -228,7 +249,16 @@
         function_as_object->CallAsFunction(context, this_value, argc, argv);
     v8::Local<v8::Value> return_value;
     if (!maybe_return_value.ToLocal(&return_value)) {
-      NOTIMPLEMENTED();
+      std::string description;
+      v8::Local<v8::Value> stack;
+      if (try_catch.StackTrace(context).ToLocal(&stack)) {
+        description = *v8::String::Utf8Value(isolate_, stack);
+      } else {
+        description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+      }
+      if (description.empty()) description = "Unknown exception";
+      // TODO: Send the description to the console instead of logging it.
+      LOG(ERROR) << description;
       callback_result.exception = true;
     } else {
       callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
@@ -251,8 +281,7 @@
   V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
       : ScopedPersistent(isolate, handle), isolate_(isolate) {}
 
-  CallbackResult<R> Run(      typename
-      CallbackParamTraits<A1>::ForwardType a1,
+  CallbackResult<R> Run(typename CallbackParamTraits<A1>::ForwardType a1,
       typename CallbackParamTraits<A2>::ForwardType a2,
       typename CallbackParamTraits<A3>::ForwardType a3)
       const override {
@@ -266,6 +295,7 @@
 
     EntryScope entry_scope(isolate_);
     v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+    v8::TryCatch try_catch(isolate_);
 
     // https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
     // Callback 'this' is set to null, unless overridden by other specifications
@@ -290,7 +320,16 @@
         function_as_object->CallAsFunction(context, this_value, argc, argv);
     v8::Local<v8::Value> return_value;
     if (!maybe_return_value.ToLocal(&return_value)) {
-      NOTIMPLEMENTED();
+      std::string description;
+      v8::Local<v8::Value> stack;
+      if (try_catch.StackTrace(context).ToLocal(&stack)) {
+        description = *v8::String::Utf8Value(isolate_, stack);
+      } else {
+        description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+      }
+      if (description.empty()) description = "Unknown exception";
+      // TODO: Send the description to the console instead of logging it.
+      LOG(ERROR) << description;
       callback_result.exception = true;
     } else {
       callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
@@ -313,8 +352,7 @@
   V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
       : ScopedPersistent(isolate, handle), isolate_(isolate) {}
 
-  CallbackResult<R> Run(      typename
-      CallbackParamTraits<A1>::ForwardType a1,
+  CallbackResult<R> Run(typename CallbackParamTraits<A1>::ForwardType a1,
       typename CallbackParamTraits<A2>::ForwardType a2,
       typename CallbackParamTraits<A3>::ForwardType a3,
       typename CallbackParamTraits<A4>::ForwardType a4)
@@ -329,6 +367,7 @@
 
     EntryScope entry_scope(isolate_);
     v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+    v8::TryCatch try_catch(isolate_);
 
     // https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
     // Callback 'this' is set to null, unless overridden by other specifications
@@ -354,7 +393,16 @@
         function_as_object->CallAsFunction(context, this_value, argc, argv);
     v8::Local<v8::Value> return_value;
     if (!maybe_return_value.ToLocal(&return_value)) {
-      NOTIMPLEMENTED();
+      std::string description;
+      v8::Local<v8::Value> stack;
+      if (try_catch.StackTrace(context).ToLocal(&stack)) {
+        description = *v8::String::Utf8Value(isolate_, stack);
+      } else {
+        description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+      }
+      if (description.empty()) description = "Unknown exception";
+      // TODO: Send the description to the console instead of logging it.
+      LOG(ERROR) << description;
       callback_result.exception = true;
     } else {
       callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
@@ -378,8 +426,7 @@
   V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
       : ScopedPersistent(isolate, handle), isolate_(isolate) {}
 
-  CallbackResult<R> Run(      typename
-      CallbackParamTraits<A1>::ForwardType a1,
+  CallbackResult<R> Run(typename CallbackParamTraits<A1>::ForwardType a1,
       typename CallbackParamTraits<A2>::ForwardType a2,
       typename CallbackParamTraits<A3>::ForwardType a3,
       typename CallbackParamTraits<A4>::ForwardType a4,
@@ -395,6 +442,7 @@
 
     EntryScope entry_scope(isolate_);
     v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+    v8::TryCatch try_catch(isolate_);
 
     // https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
     // Callback 'this' is set to null, unless overridden by other specifications
@@ -421,7 +469,16 @@
         function_as_object->CallAsFunction(context, this_value, argc, argv);
     v8::Local<v8::Value> return_value;
     if (!maybe_return_value.ToLocal(&return_value)) {
-      NOTIMPLEMENTED();
+      std::string description;
+      v8::Local<v8::Value> stack;
+      if (try_catch.StackTrace(context).ToLocal(&stack)) {
+        description = *v8::String::Utf8Value(isolate_, stack);
+      } else {
+        description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+      }
+      if (description.empty()) description = "Unknown exception";
+      // TODO: Send the description to the console instead of logging it.
+      LOG(ERROR) << description;
       callback_result.exception = true;
     } else {
       callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
@@ -445,8 +502,7 @@
   V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
       : ScopedPersistent(isolate, handle), isolate_(isolate) {}
 
-  CallbackResult<R> Run(      typename
-      CallbackParamTraits<A1>::ForwardType a1,
+  CallbackResult<R> Run(typename CallbackParamTraits<A1>::ForwardType a1,
       typename CallbackParamTraits<A2>::ForwardType a2,
       typename CallbackParamTraits<A3>::ForwardType a3,
       typename CallbackParamTraits<A4>::ForwardType a4,
@@ -463,6 +519,7 @@
 
     EntryScope entry_scope(isolate_);
     v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+    v8::TryCatch try_catch(isolate_);
 
     // https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
     // Callback 'this' is set to null, unless overridden by other specifications
@@ -490,7 +547,16 @@
         function_as_object->CallAsFunction(context, this_value, argc, argv);
     v8::Local<v8::Value> return_value;
     if (!maybe_return_value.ToLocal(&return_value)) {
-      NOTIMPLEMENTED();
+      std::string description;
+      v8::Local<v8::Value> stack;
+      if (try_catch.StackTrace(context).ToLocal(&stack)) {
+        description = *v8::String::Utf8Value(isolate_, stack);
+      } else {
+        description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+      }
+      if (description.empty()) description = "Unknown exception";
+      // TODO: Send the description to the console instead of logging it.
+      LOG(ERROR) << description;
       callback_result.exception = true;
     } else {
       callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
@@ -514,8 +580,7 @@
   V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
       : ScopedPersistent(isolate, handle), isolate_(isolate) {}
 
-  CallbackResult<R> Run(      typename
-      CallbackParamTraits<A1>::ForwardType a1,
+  CallbackResult<R> Run(typename CallbackParamTraits<A1>::ForwardType a1,
       typename CallbackParamTraits<A2>::ForwardType a2,
       typename CallbackParamTraits<A3>::ForwardType a3,
       typename CallbackParamTraits<A4>::ForwardType a4,
@@ -533,6 +598,7 @@
 
     EntryScope entry_scope(isolate_);
     v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+    v8::TryCatch try_catch(isolate_);
 
     // https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
     // Callback 'this' is set to null, unless overridden by other specifications
@@ -561,7 +627,16 @@
         function_as_object->CallAsFunction(context, this_value, argc, argv);
     v8::Local<v8::Value> return_value;
     if (!maybe_return_value.ToLocal(&return_value)) {
-      NOTIMPLEMENTED();
+      std::string description;
+      v8::Local<v8::Value> stack;
+      if (try_catch.StackTrace(context).ToLocal(&stack)) {
+        description = *v8::String::Utf8Value(isolate_, stack);
+      } else {
+        description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+      }
+      if (description.empty()) description = "Unknown exception";
+      // TODO: Send the description to the console instead of logging it.
+      LOG(ERROR) << description;
       callback_result.exception = true;
     } else {
       callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
diff --git a/src/cobalt/script/v8c/v8c_callback_function.h.pump b/src/cobalt/script/v8c/v8c_callback_function.h.pump
index 42c9ffb..8bf8cd2 100644
--- a/src/cobalt/script/v8c/v8c_callback_function.h.pump
+++ b/src/cobalt/script/v8c/v8c_callback_function.h.pump
@@ -27,6 +27,8 @@
 #ifndef COBALT_SCRIPT_V8C_V8C_CALLBACK_FUNCTION_H_
 #define COBALT_SCRIPT_V8C_V8C_CALLBACK_FUNCTION_H_
 
+#include <string>
+
 #include "base/logging.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/v8c/conversion_helpers.h"
@@ -95,8 +97,8 @@
   V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
       : ScopedPersistent(isolate, handle), isolate_(isolate) {}
 
-  CallbackResult<R> Run($for ARG , [[
-      typename CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]])
+  CallbackResult<R> Run($for ARG ,
+      [[typename CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]])
       const override {
     CallbackResult<R> callback_result;
     DCHECK(!this->IsEmpty());
@@ -108,6 +110,7 @@
 
     EntryScope entry_scope(isolate_);
     v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+    v8::TryCatch try_catch(isolate_);
 
     // https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
     // Callback 'this' is set to null, unless overridden by other specifications
@@ -134,7 +137,16 @@
     v8::MaybeLocal<v8::Value> maybe_return_value = function_as_object->CallAsFunction(context, this_value, argc, argv);
     v8::Local<v8::Value> return_value;
     if (!maybe_return_value.ToLocal(&return_value)) {
-      NOTIMPLEMENTED();
+      std::string description;
+      v8::Local<v8::Value> stack;
+      if (try_catch.StackTrace(context).ToLocal(&stack)) {
+        description = *v8::String::Utf8Value(isolate_, stack);
+      } else {
+        description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+      }
+      if (description.empty()) description = "Unknown exception";
+      // TODO: Send the description to the console instead of logging it.
+      LOG(ERROR) << description;
       callback_result.exception = true;
     } else {
       callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
diff --git a/src/cobalt/speech/google_speech_service.cc b/src/cobalt/speech/google_speech_service.cc
index 231bf06..30d7463 100644
--- a/src/cobalt/speech/google_speech_service.cc
+++ b/src/cobalt/speech/google_speech_service.cc
@@ -229,18 +229,22 @@
                             is_last_chunk));
 }
 
+// TODO: Refactor OnURLFetchDownloadProgress() into a private function that is
+//       called by OnURLFetchDownloadProgress() and OnURLFetchComplete(), to
+//       explicitly remove the unreferenced parameters.
 void GoogleSpeechService::OnURLFetchDownloadProgress(
     const net::URLFetcher* source, int64_t /*current*/, int64_t /*total*/,
     int64_t /*current_network_bytes*/) {
   DCHECK_EQ(thread_.message_loop(), base::MessageLoop::current());
-  std::unique_ptr<std::string> data = download_data_writer_->data();
+  std::string data;
+  download_data_writer_->GetAndResetData(&data);
 
   const net::URLRequestStatus& status = source->GetStatus();
   const int response_code = source->GetResponseCode();
 
   if (source == downstream_fetcher_.get()) {
     if (status.is_success() && IsResponseCodeSuccess(response_code)) {
-      chunked_byte_buffer_.Append(*data);
+      chunked_byte_buffer_.Append(data);
       while (chunked_byte_buffer_.HasChunks()) {
         std::unique_ptr<std::vector<uint8_t> > chunk =
             chunked_byte_buffer_.PopChunk();
@@ -272,12 +276,11 @@
 
 void GoogleSpeechService::OnURLFetchComplete(const net::URLFetcher* source) {
   DCHECK_EQ(thread_.message_loop(), base::MessageLoop::current());
-  std::unique_ptr<std::string> remaining_data = download_data_writer_->data();
-  int64_t length = remaining_data->length();
-  if (remaining_data && length > 0) {
-    OnURLFetchDownloadProgress(source, length, length, length);
+  if (download_data_writer_->HasData()) {
+    // Explicitly pass '-1' for all sizes, as it is not used by
+    // OnURLFetchDownloadProgress();
+    OnURLFetchDownloadProgress(source, -1, -1, -1);
   }
-  // no-op.
 }
 
 // static
diff --git a/src/cobalt/speech/google_speech_service.h b/src/cobalt/speech/google_speech_service.h
index a69ff9d..3768636 100644
--- a/src/cobalt/speech/google_speech_service.h
+++ b/src/cobalt/speech/google_speech_service.h
@@ -68,7 +68,7 @@
 
   // net::URLFetcherDelegate interface
   void OnURLFetchDownloadProgress(const net::URLFetcher* source,
-                                  int64_t current, int64_t total,
+                                  int64_t /*current*/, int64_t /*total*/,
                                   int64_t /*current_network_bytes*/) override;
   void OnURLFetchComplete(const net::URLFetcher* source) override;
   void OnURLFetchUploadProgress(const net::URLFetcher* /*source*/,
diff --git a/src/cobalt/storage/storage.gyp b/src/cobalt/storage/storage.gyp
index 9b77578..96ea7bc 100644
--- a/src/cobalt/storage/storage.gyp
+++ b/src/cobalt/storage/storage.gyp
@@ -55,12 +55,12 @@
       ],
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'storage',
         'storage_upgrade_copy_test_data',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
     {
       'target_name': 'storage_test_deploy',
diff --git a/src/cobalt/storage/store/store.gyp b/src/cobalt/storage/store/store.gyp
index d80a876..1cf9fc4 100644
--- a/src/cobalt/storage/store/store.gyp
+++ b/src/cobalt/storage/store/store.gyp
@@ -39,11 +39,11 @@
       ],
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'memory_store',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
     {
       'target_name': 'memory_store_test_deploy',
diff --git a/src/cobalt/storage/store_upgrade/upgrade.gyp b/src/cobalt/storage/store_upgrade/upgrade.gyp
index 1066f1a..23a196e 100644
--- a/src/cobalt/storage/store_upgrade/upgrade.gyp
+++ b/src/cobalt/storage/store_upgrade/upgrade.gyp
@@ -48,11 +48,11 @@
         'storage_upgrade',
         'storage_upgrade_copy_test_files',
         '<(DEPTH)/cobalt/base/base.gyp:base',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         '<(DEPTH)/third_party/protobuf/protobuf.gyp:protobuf_lite',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
     {
       'target_name': 'storage_upgrade_test_deploy',
diff --git a/src/cobalt/test/run_all_unittests.cc b/src/cobalt/test/run_all_unittests.cc
index 90025ec..0d69f0c 100644
--- a/src/cobalt/test/run_all_unittests.cc
+++ b/src/cobalt/test/run_all_unittests.cc
@@ -22,7 +22,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
-
 int InitAndRunAllTests(int argc, char** argv) {
   base::CommandLine::Init(argc, argv);
   base::AtExitManager exit_manager;
diff --git a/src/cobalt/test/test.gyp b/src/cobalt/test/test.gyp
deleted file mode 100644
index 30af2ea..0000000
--- a/src/cobalt/test/test.gyp
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2017 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.
-
-{
-  'targets': [
-    {
-      'target_name': 'run_all_unittests',
-      'type': 'static_library',
-      'dependencies': [
-        '<(DEPTH)/base/base.gyp:test_support_base',
-        '<(DEPTH)/testing/gtest.gyp:gtest',
-      ],
-      'sources': [
-        'run_all_unittests.cc',
-      ],
-    },
-  ]
-}
diff --git a/src/cobalt/test/test.gypi b/src/cobalt/test/test.gypi
new file mode 100644
index 0000000..9df4e38
--- /dev/null
+++ b/src/cobalt/test/test.gypi
@@ -0,0 +1,23 @@
+# Copyright 2017 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.
+
+{
+  'dependencies': [
+    '<(DEPTH)/base/base.gyp:test_support_base',
+    '<(DEPTH)/testing/gtest.gyp:gtest',
+  ],
+  'sources': [
+    '<(DEPTH)/cobalt/test/run_all_unittests.cc',
+  ],
+}
diff --git a/src/cobalt/tools/automated_testing/cobalt_runner.py b/src/cobalt/tools/automated_testing/cobalt_runner.py
index 3f392c9..56a1b24 100644
--- a/src/cobalt/tools/automated_testing/cobalt_runner.py
+++ b/src/cobalt/tools/automated_testing/cobalt_runner.py
@@ -11,6 +11,7 @@
 import thread
 import threading
 import time
+import traceback
 
 import _env  # pylint: disable=unused-import
 from cobalt.tools.automated_testing import c_val_names
@@ -141,6 +142,10 @@
     """Sends a system signal to put Cobalt into suspend state."""
     self.launcher.SendSuspend()
 
+  def SendDeepLink(self, link):
+    """Sends a deep link to Cobalt."""
+    return self.launcher.SendDeepLink(link)
+
   def GetURL(self):
     return self.url
 
@@ -254,10 +259,18 @@
 
   def _KillLauncher(self):
     """Kills the launcher and its attached Cobalt instance."""
+    wait_for_runner_thread = True
     if self.CanExecuteJavaScript():
-      self.ExecuteJavaScript('window.close();')
+      try:
+        self.ExecuteJavaScript('window.close();')
+      except Exception:
+        wait_for_runner_thread = False
+        sys.stderr.write(
+            '***An exception was raised while trying to close the app:')
+        traceback.print_exc(file=sys.stderr)
 
-    self.runner_thread.join(COBALT_EXIT_TIMEOUT_SECONDS)
+    if wait_for_runner_thread:
+      self.runner_thread.join(COBALT_EXIT_TIMEOUT_SECONDS)
     if self.runner_thread.isAlive():
       sys.stderr.write(
           '***Runner thread still alive after sending graceful shutdown command, try again by killing app***\n'
@@ -503,7 +516,7 @@
   device_params.config = args.config
   device_params.device_id = args.device_id
   device_params.out_directory = args.out_directory
-  if args.target_params == None:
+  if args.target_params is None:
     device_params.target_params = []
   else:
     device_params.target_params = [args.target_params]
diff --git a/src/cobalt/web_animations/web_animations.gyp b/src/cobalt/web_animations/web_animations.gyp
index ff2eaf8..c350740 100644
--- a/src/cobalt/web_animations/web_animations.gyp
+++ b/src/cobalt/web_animations/web_animations.gyp
@@ -57,10 +57,10 @@
         'web_animations',
         '<(DEPTH)/cobalt/css_parser/css_parser.gyp:css_parser',
         '<(DEPTH)/cobalt/cssom/cssom.gyp:cssom',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/webdriver/screencast/screencast_module.cc b/src/cobalt/webdriver/screencast/screencast_module.cc
index ddbdbbd..9df5269 100644
--- a/src/cobalt/webdriver/screencast/screencast_module.cc
+++ b/src/cobalt/webdriver/screencast/screencast_module.cc
@@ -31,6 +31,8 @@
 
 namespace {
 const char kJpegContentType[] = "image/jpeg";
+// Add screencast frame rate as 30 fps.
+const int kScreencastFramesPerSecond = 30;
 }
 
 ScreencastModule::ScreencastModule(
@@ -88,7 +90,7 @@
       base::Bind(&ScreencastModule::TakeScreenshot, base::Unretained(this));
   screenshot_timer_->Start(FROM_HERE,
                            base::TimeDelta::FromMilliseconds(
-                               COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS),
+                               1000.0f / kScreencastFramesPerSecond),
                            screenshot_event);
 }
 
diff --git a/src/cobalt/webdriver/webdriver.gyp b/src/cobalt/webdriver/webdriver.gyp
index f344d75..491531d 100644
--- a/src/cobalt/webdriver/webdriver.gyp
+++ b/src/cobalt/webdriver/webdriver.gyp
@@ -94,7 +94,6 @@
           'dependencies': [ 'copy_webdriver_data', ],
           'defines': [
             'ENABLE_WEBDRIVER',
-            'COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS=<(cobalt_minimum_frame_time_in_milliseconds)',
           ],
           'all_dependent_settings': {
             'defines': [ 'ENABLE_WEBDRIVER', ],
diff --git a/src/cobalt/webdriver/webdriver_test.gyp b/src/cobalt/webdriver/webdriver_test.gyp
index 9342fdc..33c8ab8 100644
--- a/src/cobalt/webdriver/webdriver_test.gyp
+++ b/src/cobalt/webdriver/webdriver_test.gyp
@@ -15,11 +15,11 @@
       ],
       'dependencies': [
         '<(DEPTH)/cobalt/browser/browser.gyp:browser',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'webdriver_copy_test_data',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/websocket/cobalt_web_socket_event_handler.cc b/src/cobalt/websocket/cobalt_web_socket_event_handler.cc
index 1d3db2e..399c19c 100644
--- a/src/cobalt/websocket/cobalt_web_socket_event_handler.cc
+++ b/src/cobalt/websocket/cobalt_web_socket_event_handler.cc
@@ -132,5 +132,9 @@
   creator_->OnWriteDone(bytes_written);
 }
 
+void CobaltWebSocketEventHandler::OnFlowControl(int64_t quota) {
+  creator_->OnFlowControl(quota);
+}
+
 }  // namespace websocket
 }  // namespace cobalt
\ No newline at end of file
diff --git a/src/cobalt/websocket/cobalt_web_socket_event_handler.h b/src/cobalt/websocket/cobalt_web_socket_event_handler.h
index 6cb92fb..b974895 100644
--- a/src/cobalt/websocket/cobalt_web_socket_event_handler.h
+++ b/src/cobalt/websocket/cobalt_web_socket_event_handler.h
@@ -57,7 +57,7 @@
   // Called to provide more send quota for this channel to the renderer
   // process. Currently the quota units are always bytes of message body
   // data. In future it might depend on the type of multiplexing in use.
-  virtual void OnFlowControl(int64_t /*quota*/) override {}
+  virtual void OnFlowControl(int64_t quota) override;
 
   // Called when the remote server has Started the WebSocket Closing
   // Handshake. The client should not attempt to send any more messages after
diff --git a/src/cobalt/websocket/mock_websocket_channel.cc b/src/cobalt/websocket/mock_websocket_channel.cc
new file mode 100644
index 0000000..8fc17cf
--- /dev/null
+++ b/src/cobalt/websocket/mock_websocket_channel.cc
@@ -0,0 +1,23 @@
+// Copyright 2019 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 "cobalt/websocket/mock_websocket_channel.h"
+#include "cobalt/websocket/cobalt_web_socket_event_handler.h"
+
+// Generated constructors and destructors for GMock objects are very large. By
+// putting them in a separate file we can speed up compile times.
+
+namespace cobalt {
+namespace websocket {
+
+MockWebSocketChannel::MockWebSocketChannel(
+    WebSocketImpl* impl, network::NetworkModule* network_module)
+    : net::WebSocketChannel(std::unique_ptr<net::WebSocketEventInterface>(
+                                new CobaltWebSocketEventHandler(impl)),
+                            network_module->url_request_context()) {}
+
+MockWebSocketChannel::~MockWebSocketChannel() = default;
+
+}  // namespace websocket
+}  // namespace cobalt
\ No newline at end of file
diff --git a/src/cobalt/websocket/mock_websocket_channel.h b/src/cobalt/websocket/mock_websocket_channel.h
new file mode 100644
index 0000000..85bb534
--- /dev/null
+++ b/src/cobalt/websocket/mock_websocket_channel.h
@@ -0,0 +1,58 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_MODULES_WEBSOCKETS_MOCK_WEBSOCKET_CHANNEL_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBSOCKETS_MOCK_WEBSOCKET_CHANNEL_H_
+
+#include <memory>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/synchronization/lock.h"
+#include "cobalt/network/network_module.h"
+#include "cobalt/websocket/web_socket_impl.h"
+#include "net/websockets/websocket_channel.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace cobalt {
+namespace websocket {
+
+class SourceLocation;
+
+class MockWebSocketChannel : public net::WebSocketChannel {
+ public:
+  MockWebSocketChannel(WebSocketImpl* impl,
+                       network::NetworkModule* network_module);
+  ~MockWebSocketChannel();
+
+  MOCK_METHOD4(MockSendFrame,
+               net::WebSocketChannel::ChannelState(
+                   bool fin, net::WebSocketFrameHeader::OpCode op_code,
+                   scoped_refptr<net::IOBuffer> buffer, size_t buffer_size));
+  net::WebSocketChannel::ChannelState SendFrame(
+      bool fin, net::WebSocketFrameHeader::OpCode op_code,
+      scoped_refptr<net::IOBuffer> buffer, size_t buffer_size) override {
+    base::AutoLock scoped_lock(lock_);
+    return MockSendFrame(fin, op_code, buffer, buffer_size);
+  }
+
+  base::Lock& lock() { return lock_; }
+
+ private:
+  base::Lock lock_;
+};
+
+}  // namespace websocket
+}  // namespace cobalt
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WEBSOCKETS_MOCK_WEBSOCKET_CHANNEL_H_
\ No newline at end of file
diff --git a/src/cobalt/websocket/web_socket.h b/src/cobalt/websocket/web_socket.h
index 39ef2ec..1334aeb 100644
--- a/src/cobalt/websocket/web_socket.h
+++ b/src/cobalt/websocket/web_socket.h
@@ -242,6 +242,7 @@
   FRIEND_TEST_ALL_PREFIXES(WebSocketTest, FailInvalidSubProtocols);
   FRIEND_TEST_ALL_PREFIXES(WebSocketTest, SubProtocols);
   FRIEND_TEST_ALL_PREFIXES(WebSocketTest, DuplicatedSubProtocols);
+  friend class WebSocketImplTest;
 
   DISALLOW_COPY_AND_ASSIGN(WebSocket);
 };
diff --git a/src/cobalt/websocket/web_socket_impl.cc b/src/cobalt/websocket/web_socket_impl.cc
index 844d8f7..19e2062 100644
--- a/src/cobalt/websocket/web_socket_impl.cc
+++ b/src/cobalt/websocket/web_socket_impl.cc
@@ -27,9 +27,6 @@
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/websocket/web_socket.h"
 #include "net/http/http_util.h"
-#include "net/websockets/websocket_errors.h"
-#include "net/websockets/websocket_frame.h"
-#include "net/websockets/websocket_handshake_stream_create_helper.h"
 #include "starboard/memory.h"
 
 namespace cobalt {
@@ -72,6 +69,7 @@
   // priority thread might be required.  Investigation is needed.
   delegate_task_runner_ =
       network_module_->url_request_context_getter()->GetNetworkTaskRunner();
+  DCHECK(delegate_task_runner_);
   base::WaitableEvent channel_created_event(
       base::WaitableEvent::ResetPolicy::MANUAL,
       base::WaitableEvent::InitialState::NOT_SIGNALED);
@@ -163,6 +161,12 @@
                             selected_subprotocol));
 }
 
+void WebSocketImpl::OnFlowControl(int64_t quota) {
+  DCHECK(current_quota_ >= 0);
+  current_quota_ += quota;
+  ProcessSendQueue();
+}
+
 void WebSocketImpl::OnWebSocketConnected(
     const std::string &selected_subprotocol) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -260,13 +264,40 @@
     DLOG(WARNING) << "Attempt to send over a closed channel.";
     return;
   }
+  SendQueueMessage new_message = {io_buffer, length, op_code};
+  send_queue_.push(std::move(new_message));
+  ProcessSendQueue();
+}
 
-  // this behavior is not just an optimization, but required in case
-  // we are closing the connection
-  auto channel_state =
-      websocket_channel_->SendFrame(true /*fin*/, op_code, io_buffer, length);
-  if (channel_state == net::WebSocketChannel::CHANNEL_DELETED) {
-    websocket_channel_.reset();
+void WebSocketImpl::ProcessSendQueue() {
+  DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+  while (current_quota_ > 0 && !send_queue_.empty()) {
+    SendQueueMessage message = send_queue_.front();
+    size_t current_message_length = message.length - sent_size_of_top_message_;
+    bool final = false;
+    if (current_quota_ < static_cast<int64_t>(current_message_length)) {
+      // quota is not enough to send the top message.
+      scoped_refptr<net::IOBuffer> new_io_buffer(
+          new net::IOBuffer(static_cast<size_t>(current_quota_)));
+      SbMemoryCopy(new_io_buffer->data(),
+                   message.io_buffer->data() + sent_size_of_top_message_,
+                   current_quota_);
+      sent_size_of_top_message_ += current_quota_;
+      message.io_buffer = new_io_buffer;
+      current_message_length = current_quota_;
+      current_quota_ = 0;
+    } else {
+      // Sent all of the remaining top message.
+      final = true;
+      send_queue_.pop();
+      sent_size_of_top_message_ = 0;
+      current_quota_ -= current_message_length;
+    }
+    auto channel_state = websocket_channel_->SendFrame(
+        final, message.op_code, message.io_buffer, current_message_length);
+    if (channel_state == net::WebSocketChannel::CHANNEL_DELETED) {
+      websocket_channel_.reset();
+    }
   }
 }
 
diff --git a/src/cobalt/websocket/web_socket_impl.h b/src/cobalt/websocket/web_socket_impl.h
index 5e5977d..6c65620 100644
--- a/src/cobalt/websocket/web_socket_impl.h
+++ b/src/cobalt/websocket/web_socket_impl.h
@@ -16,6 +16,7 @@
 #define COBALT_WEBSOCKET_WEB_SOCKET_IMPL_H_
 
 #include <memory>
+#include <queue>
 #include <string>
 #include <vector>
 
@@ -32,7 +33,10 @@
 #include "cobalt/websocket/web_socket_message_container.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "net/websockets/websocket_channel.h"
+#include "net/websockets/websocket_errors.h"
+#include "net/websockets/websocket_frame.h"
 #include "net/websockets/websocket_frame_parser.h"
+#include "net/websockets/websocket_handshake_stream_create_helper.h"
 #include "url/gurl.h"
 
 namespace cobalt {
@@ -85,6 +89,8 @@
 
   void OnHandshakeComplete(const std::string& selected_subprotocol);
 
+  void OnFlowControl(int64_t quota);
+
   struct CloseInfo {
     CloseInfo(const net::WebSocketError code, const std::string& reason)
         : code(code), reason(reason) {}
@@ -108,6 +114,7 @@
   bool SendHelper(const net::WebSocketFrameHeader::OpCode op_code,
                   const char* data, std::size_t length,
                   std::string* error_message);
+  void ProcessSendQueue();
 
   void OnWebSocketConnected(const std::string& selected_subprotocol);
   void OnWebSocketDisconnected(bool was_clean, uint16 code,
@@ -125,11 +132,23 @@
   std::string origin_;
   GURL connect_url_;
 
+  // Data buffering and flow control.
+  // Should only be modified on delegate(network) thread.
+  int64_t current_quota_ = 0;
+  struct SendQueueMessage {
+    scoped_refptr<net::IOBuffer> io_buffer;
+    size_t length;
+    net::WebSocketFrameHeader::OpCode op_code;
+  };
+  std::queue<SendQueueMessage> send_queue_;
+  size_t sent_size_of_top_message_ = 0;
+
   scoped_refptr<base::SingleThreadTaskRunner> delegate_task_runner_;
   scoped_refptr<base::SingleThreadTaskRunner> owner_task_runner_;
 
   ~WebSocketImpl();
   friend class base::RefCountedThreadSafe<WebSocketImpl>;
+  friend class WebSocketImplTest;
 
   DISALLOW_COPY_AND_ASSIGN(WebSocketImpl);
 };
diff --git a/src/cobalt/websocket/web_socket_impl_test.cc b/src/cobalt/websocket/web_socket_impl_test.cc
new file mode 100644
index 0000000..1c2d0ac
--- /dev/null
+++ b/src/cobalt/websocket/web_socket_impl_test.cc
@@ -0,0 +1,230 @@
+// Copyright 2017 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/websocket/web_socket_impl.h"
+#include "cobalt/websocket/web_socket.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/test/scoped_task_environment.h"
+#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/dom/dom_exception.h"
+#include "cobalt/dom/dom_settings.h"
+#include "cobalt/dom/window.h"
+#include "cobalt/network/network_module.h"
+#include "cobalt/script/script_exception.h"
+#include "cobalt/script/testing/mock_exception_state.h"
+#include "cobalt/websocket/mock_websocket_channel.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::SaveArg;
+using ::testing::StrictMock;
+using ::testing::DefaultValue;
+using ::testing::Return;
+using cobalt::script::testing::MockExceptionState;
+
+namespace cobalt {
+namespace websocket {
+namespace {
+// These limits are copied from net::WebSocketChannel implementation.
+const int kDefaultSendQuotaHighWaterMark = 1 << 17;
+const int k800KB = 800;
+const int kTooMuch = kDefaultSendQuotaHighWaterMark + 1;
+const int kWayTooMuch = kDefaultSendQuotaHighWaterMark * 2 + 1;
+const int k512KB = 512;
+
+class FakeSettings : public dom::DOMSettings {
+ public:
+  FakeSettings()
+      : dom::DOMSettings(0, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+                         &null_debugger_hooks_, NULL),
+        base_("https://127.0.0.1:1234") {
+    network_module_.reset(new network::NetworkModule());
+    this->set_network_module(network_module_.get());
+  }
+  const GURL& base_url() const override { return base_; }
+
+  // public members, so that they're easier for testing.
+  base::NullDebuggerHooks null_debugger_hooks_;
+  GURL base_;
+  std::unique_ptr<network::NetworkModule> network_module_;
+};
+}  // namespace
+
+class WebSocketImplTest : public ::testing::Test {
+ public:
+  dom::DOMSettings* settings() const { return settings_.get(); }
+  void AddQuota(int quota) {
+    network_task_runner_->PostBlockingTask(
+        FROM_HERE,
+        base::Bind(&WebSocketImpl::OnFlowControl, websocket_impl_, quota));
+  }
+
+ protected:
+  WebSocketImplTest() : settings_(new FakeSettings()) {
+    std::vector<std::string> sub_protocols;
+    sub_protocols.push_back("chat");
+    // Use local URL so that WebSocket will not complain about URL format.
+    ws_ = new WebSocket(settings(), "wss://127.0.0.1:1234", sub_protocols,
+                        &exception_state_, false);
+
+    websocket_impl_ = ws_->impl_;
+    network_task_runner_ = settings_->network_module()
+                               ->url_request_context_getter()
+                               ->GetNetworkTaskRunner();
+    // The holder is only created to be base::Passed() on the next line, it will
+    // be empty so do not use it later.
+    network_task_runner_->PostBlockingTask(
+        FROM_HERE,
+        base::Bind(
+            [](scoped_refptr<WebSocketImpl> websocket_impl,
+               MockWebSocketChannel** mock_channel_slot,
+               dom::DOMSettings* settings) {
+              *mock_channel_slot = new MockWebSocketChannel(
+                  websocket_impl.get(), settings->network_module());
+              websocket_impl->websocket_channel_ =
+                  std::unique_ptr<net::WebSocketChannel>(*mock_channel_slot);
+            },
+            websocket_impl_, &mock_channel_, settings()));
+  }
+  ~WebSocketImplTest() {
+    network_task_runner_->PostBlockingTask(
+        FROM_HERE,
+        base::Bind(&WebSocketImpl::OnClose, websocket_impl_, true /*was_clan*/,
+                   net::kWebSocketNormalClosure /*error_code*/,
+                   "" /*close_reason*/));
+  }
+
+  base::test::ScopedTaskEnvironment env_;
+
+  std::unique_ptr<FakeSettings> settings_;
+  scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
+  scoped_refptr<WebSocket> ws_;
+  scoped_refptr<WebSocketImpl> websocket_impl_;
+  MockWebSocketChannel* mock_channel_;
+  StrictMock<MockExceptionState> exception_state_;
+};
+
+TEST_F(WebSocketImplTest, NormalSizeRequest) {
+  // Normally the high watermark quota is given at websocket connection success.
+  AddQuota(kDefaultSendQuotaHighWaterMark);
+
+  {
+    base::AutoLock scoped_lock(mock_channel_->lock());
+    // mock_channel_ is created and used on network thread.
+    EXPECT_CALL(
+        *mock_channel_,
+        MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeText, _, k800KB))
+        .Times(1)
+        .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+  }
+
+  char data[k800KB];
+  int32 buffered_amount = 0;
+  std::string error;
+  websocket_impl_->SendText(data, k800KB, &buffered_amount, &error);
+}
+
+TEST_F(WebSocketImplTest, LargeRequest) {
+  AddQuota(kDefaultSendQuotaHighWaterMark);
+
+  // mock_channel_ is created and used on network thread.
+  {
+    base::AutoLock scoped_lock(mock_channel_->lock());
+    EXPECT_CALL(*mock_channel_,
+                MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeText, _,
+                              kDefaultSendQuotaHighWaterMark))
+        .Times(1)
+        .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+  }
+
+  char data[kDefaultSendQuotaHighWaterMark];
+  int32 buffered_amount = 0;
+  std::string error;
+  websocket_impl_->SendText(data, kDefaultSendQuotaHighWaterMark,
+                            &buffered_amount, &error);
+}
+
+TEST_F(WebSocketImplTest, OverLimitRequest) {
+  AddQuota(kDefaultSendQuotaHighWaterMark);
+
+  // mock_channel_ is created and used on network thread.
+  {
+    base::AutoLock scoped_lock(mock_channel_->lock());
+    EXPECT_CALL(*mock_channel_,
+                MockSendFrame(false, net::WebSocketFrameHeader::kOpCodeText, _,
+                              kDefaultSendQuotaHighWaterMark))
+        .Times(2)
+        .WillRepeatedly(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+
+    EXPECT_CALL(
+        *mock_channel_,
+        MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeText, _, 1))
+        .Times(1)
+        .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+  }
+
+  char data[kWayTooMuch];
+  int32 buffered_amount = 0;
+  std::string error;
+  websocket_impl_->SendText(data, kWayTooMuch, &buffered_amount, &error);
+
+  AddQuota(kDefaultSendQuotaHighWaterMark);
+  AddQuota(kDefaultSendQuotaHighWaterMark);
+}
+
+
+TEST_F(WebSocketImplTest, ReuseSocketForLargeRequest) {
+  AddQuota(kDefaultSendQuotaHighWaterMark);
+
+  // mock_channel_ is created and used on network thread.
+  {
+    base::AutoLock scoped_lock(mock_channel_->lock());
+    EXPECT_CALL(*mock_channel_,
+                MockSendFrame(false, net::WebSocketFrameHeader::kOpCodeBinary,
+                              _, kDefaultSendQuotaHighWaterMark))
+        .Times(1)
+        .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+    EXPECT_CALL(
+        *mock_channel_,
+        MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeBinary, _, 1))
+        .Times(1)
+        .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+    EXPECT_CALL(*mock_channel_,
+                MockSendFrame(false, net::WebSocketFrameHeader::kOpCodeText, _,
+                              k512KB - 1))
+        .Times(1)
+        .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+    EXPECT_CALL(*mock_channel_,
+                MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeText, _,
+                              kTooMuch - (k512KB - 1)))
+        .Times(1)
+        .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+  }
+
+  char data[kTooMuch];
+  int32 buffered_amount = 0;
+  std::string error;
+  websocket_impl_->SendBinary(data, kTooMuch, &buffered_amount, &error);
+  websocket_impl_->SendText(data, kTooMuch, &buffered_amount, &error);
+
+  AddQuota(k512KB);
+  AddQuota(kDefaultSendQuotaHighWaterMark);
+}
+
+}  // namespace websocket
+}  // namespace cobalt
diff --git a/src/cobalt/websocket/web_socket_test.cc b/src/cobalt/websocket/web_socket_test.cc
index 575b46c..f85d4bb 100644
--- a/src/cobalt/websocket/web_socket_test.cc
+++ b/src/cobalt/websocket/web_socket_test.cc
@@ -36,6 +36,7 @@
 namespace cobalt {
 namespace websocket {
 
+namespace {
 class FakeSettings : public dom::testing::StubEnvironmentSettings {
  public:
   FakeSettings() : base_("https://example.com") {
@@ -48,6 +49,7 @@
   GURL base_;
   std::unique_ptr<network::NetworkModule> network_module_;
 };
+}  // namespace
 
 class WebSocketTest : public ::testing::Test {
  public:
diff --git a/src/cobalt/websocket/websocket.gyp b/src/cobalt/websocket/websocket.gyp
index 88e2bad..c3bfb3a 100644
--- a/src/cobalt/websocket/websocket.gyp
+++ b/src/cobalt/websocket/websocket.gyp
@@ -47,11 +47,12 @@
       'type': '<(gtest_target_type)',
       'sources': [
         'web_socket_test.cc',
+        'mock_websocket_channel.cc',
+        'web_socket_impl_test.cc',
       ],
       'dependencies': [
         'websocket',
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/url/url.gyp:url',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
@@ -68,6 +69,7 @@
           ],
         }],
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
 
     {
diff --git a/src/cobalt/xhr/url_fetcher_buffer_writer.cc b/src/cobalt/xhr/url_fetcher_buffer_writer.cc
new file mode 100644
index 0000000..f8c4378
--- /dev/null
+++ b/src/cobalt/xhr/url_fetcher_buffer_writer.cc
@@ -0,0 +1,303 @@
+// Copyright 2019 Google Inc. 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/xhr/url_fetcher_buffer_writer.h"
+
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+#include "starboard/memory.h"
+
+namespace cobalt {
+namespace xhr {
+
+namespace {
+
+// Allocate 64KB if the total size is unknown to avoid allocating small buffer
+// too many times.
+const int64_t kDefaultPreAllocateSizeInBytes = 64 * 1024;
+
+void ReleaseMemory(std::string* str) {
+  DCHECK(str);
+  std::string empty;
+  str->swap(empty);
+}
+
+void ReleaseMemory(script::PreallocatedArrayBufferData* data) {
+  DCHECK(data);
+  script::PreallocatedArrayBufferData empty;
+  data->Swap(&empty);
+}
+
+}  // namespace
+
+URLFetcherResponseWriter::Buffer::Buffer(Type type) : type_(type) {}
+
+void URLFetcherResponseWriter::Buffer::DisablePreallocate() {
+  base::AutoLock auto_lock(lock_);
+
+  DCHECK_EQ(GetSize_Locked(), 0u);
+  allow_preallocate_ = false;
+}
+
+void URLFetcherResponseWriter::Buffer::Clear() {
+  base::AutoLock auto_lock(lock_);
+
+  ReleaseMemory(&data_as_string_);
+  ReleaseMemory(&copy_of_data_as_string_);
+  ReleaseMemory(&data_as_array_buffer_);
+
+  download_progress_ = 0;
+  data_as_array_buffer_size_ = 0;
+}
+
+int64_t URLFetcherResponseWriter::Buffer::GetAndResetDownloadProgress() {
+  base::AutoLock auto_lock(lock_);
+  download_progress_ = GetSize_Locked();
+  return static_cast<int64_t>(download_progress_);
+}
+
+bool URLFetcherResponseWriter::Buffer::HasProgressSinceLastGetAndReset() const {
+  base::AutoLock auto_lock(lock_);
+  return GetSize_Locked() > download_progress_;
+}
+
+const std::string&
+URLFetcherResponseWriter::Buffer::GetReferenceOfStringAndSeal() {
+  base::AutoLock auto_lock(lock_);
+
+  UpdateType_Locked(kString);
+  allow_write_ = false;
+
+  return data_as_string_;
+}
+
+const std::string&
+URLFetcherResponseWriter::Buffer::GetTemporaryReferenceOfString() {
+  base::AutoLock auto_lock(lock_);
+
+  // This function can be further optimized by always return reference of
+  // |data_as_string_|, and only make a copy when |data_as_string_| is extended.
+  //  It is not done as GetTemporaryReferenceOfString() is currently not
+  // triggered.  It will only be called when JS app is retrieving responseText
+  // while the request is still in progress.
+
+  if (type_ == kString) {
+    copy_of_data_as_string_ = data_as_string_;
+  } else {
+    DCHECK_EQ(type_, kArrayBuffer);
+    const char* begin = static_cast<const char*>(data_as_array_buffer_.data());
+    copy_of_data_as_string_.assign(begin, begin + data_as_array_buffer_size_);
+  }
+
+  return copy_of_data_as_string_;
+}
+
+void URLFetcherResponseWriter::Buffer::GetAndReset(std::string* str) {
+  DCHECK(str);
+
+  ReleaseMemory(str);
+
+  base::AutoLock auto_lock(lock_);
+
+  UpdateType_Locked(kString);
+
+  if (capacity_known_ && data_as_string_.size() != data_as_string_.capacity()) {
+    DLOG(WARNING) << "String size " << data_as_string_.size()
+                  << " is different than its preset capacity "
+                  << data_as_string_.capacity();
+  }
+
+  data_as_string_.swap(*str);
+}
+
+void URLFetcherResponseWriter::Buffer::GetAndReset(
+    PreallocatedArrayBufferData* data) {
+  DCHECK(data);
+
+  ReleaseMemory(data);
+
+  base::AutoLock auto_lock(lock_);
+
+  UpdateType_Locked(kArrayBuffer);
+
+  if (data_as_array_buffer_.byte_length() != data_as_array_buffer_size_) {
+    DCHECK_LT(data_as_array_buffer_size_, data_as_array_buffer_.byte_length());
+    DLOG_IF(WARNING, capacity_known_)
+        << "ArrayBuffer size " << data_as_array_buffer_size_
+        << " is different than its preset capacity "
+        << data_as_array_buffer_.byte_length();
+    data_as_array_buffer_.Resize(data_as_array_buffer_size_);
+  }
+  data_as_array_buffer_.Swap(data);
+}
+
+void URLFetcherResponseWriter::Buffer::MaybePreallocate(int64_t capacity) {
+  base::AutoLock auto_lock(lock_);
+
+  if (!allow_preallocate_) {
+    return;
+  }
+
+  if (capacity < 0) {
+    capacity = kDefaultPreAllocateSizeInBytes;
+  } else {
+    capacity_known_ = true;
+  }
+
+  if (capacity == 0) {
+    return;
+  }
+
+  switch (type_) {
+    case kString:
+      DCHECK_EQ(data_as_string_.size(), 0u);
+      data_as_string_.reserve(capacity);
+      return;
+    case kArrayBuffer:
+      DCHECK_EQ(data_as_array_buffer_size_, 0u);
+      data_as_array_buffer_.Resize(capacity);
+      return;
+  }
+  NOTREACHED();
+}
+
+void URLFetcherResponseWriter::Buffer::Write(const void* buffer,
+                                             int num_bytes) {
+  DCHECK_GE(num_bytes, 0);
+
+  if (num_bytes <= 0) {
+    return;
+  }
+
+  base::AutoLock auto_lock(lock_);
+
+  DCHECK(allow_write_);
+
+  if (!allow_write_) {
+    return;
+  }
+
+  if (type_ == kString) {
+    if (capacity_known_ &&
+        num_bytes + data_as_string_.size() >= data_as_string_.capacity()) {
+      SB_LOG(WARNING) << "Data written is larger than the preset capacity "
+                      << data_as_string_.capacity();
+    }
+    data_as_string_.append(static_cast<const char*>(buffer), num_bytes);
+    return;
+  }
+
+  DCHECK_EQ(type_, kArrayBuffer);
+  if (data_as_array_buffer_size_ + num_bytes >
+      data_as_array_buffer_.byte_length()) {
+    if (capacity_known_) {
+      SB_LOG(WARNING) << "Data written is larger than the preset capacity "
+                      << data_as_array_buffer_.byte_length();
+    }
+    data_as_array_buffer_.Resize(data_as_array_buffer_size_ + num_bytes);
+  }
+
+  auto destination = static_cast<uint8_t*>(data_as_array_buffer_.data()) +
+                     data_as_array_buffer_size_;
+  SbMemoryCopy(destination, buffer, num_bytes);
+  data_as_array_buffer_size_ += num_bytes;
+}
+
+size_t URLFetcherResponseWriter::Buffer::GetSize_Locked() const {
+  lock_.AssertAcquired();
+
+  switch (type_) {
+    case kString:
+      return data_as_string_.size();
+    case kArrayBuffer:
+      return data_as_array_buffer_size_;
+  }
+  NOTREACHED();
+  return 0;
+}
+
+void URLFetcherResponseWriter::Buffer::UpdateType_Locked(Type type) {
+  lock_.AssertAcquired();
+
+  if (type_ == type) {
+    return;
+  }
+
+  DCHECK(allow_write_);
+
+  DLOG_IF(WARNING, GetSize_Locked() > 0)
+      << "Change response type from " << type_ << " to " << type
+      << " after response is started, which is less efficient.";
+
+  if (type_ == kString) {
+    DCHECK_EQ(type, kArrayBuffer);
+    DCHECK_EQ(data_as_array_buffer_size_, 0u);
+    DCHECK_EQ(data_as_array_buffer_.byte_length(), 0u);
+  } else {
+    DCHECK_EQ(type_, kArrayBuffer);
+    DCHECK_EQ(type, kString);
+    DCHECK_EQ(data_as_string_.size(), 0u);
+  }
+
+  type_ = type;
+
+  if (type == kArrayBuffer) {
+    data_as_array_buffer_.Resize(data_as_string_.capacity());
+    data_as_array_buffer_size_ = data_as_string_.size();
+    SbMemoryCopy(data_as_array_buffer_.data(), data_as_string_.data(),
+                 data_as_array_buffer_size_);
+
+    ReleaseMemory(&data_as_string_);
+    ReleaseMemory(&copy_of_data_as_string_);
+    return;
+  }
+
+  data_as_string_.reserve(data_as_array_buffer_.byte_length());
+  data_as_string_.append(static_cast<const char*>(data_as_array_buffer_.data()),
+                         data_as_array_buffer_size_);
+
+  ReleaseMemory(&data_as_array_buffer_);
+  data_as_array_buffer_size_ = 0;
+}
+
+URLFetcherResponseWriter::URLFetcherResponseWriter(
+    const scoped_refptr<Buffer>& buffer)
+    : buffer_(buffer) {
+  DCHECK(buffer_);
+}
+
+URLFetcherResponseWriter::~URLFetcherResponseWriter() = default;
+
+int URLFetcherResponseWriter::Initialize(
+    net::CompletionOnceCallback /*callback*/) {
+  return net::OK;
+}
+
+void URLFetcherResponseWriter::OnResponseStarted(int64_t content_length) {
+  buffer_->MaybePreallocate(content_length);
+}
+
+int URLFetcherResponseWriter::Write(net::IOBuffer* buffer, int num_bytes,
+                                    net::CompletionOnceCallback /*callback*/) {
+  buffer_->Write(buffer->data(), num_bytes);
+  return num_bytes;
+}
+
+int URLFetcherResponseWriter::Finish(int /*net_error*/,
+                                     net::CompletionOnceCallback /*callback*/) {
+  return net::OK;
+}
+
+}  // namespace xhr
+}  // namespace cobalt
diff --git a/src/cobalt/xhr/url_fetcher_buffer_writer.h b/src/cobalt/xhr/url_fetcher_buffer_writer.h
new file mode 100644
index 0000000..c82ce32
--- /dev/null
+++ b/src/cobalt/xhr/url_fetcher_buffer_writer.h
@@ -0,0 +1,112 @@
+// Copyright 2019 Google Inc. 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_XHR_URL_FETCHER_BUFFER_WRITER_H_
+#define COBALT_XHR_URL_FETCHER_BUFFER_WRITER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner.h"
+#include "cobalt/script/array_buffer.h"
+#include "net/base/io_buffer.h"
+#include "net/url_request/url_fetcher_response_writer.h"
+
+namespace cobalt {
+namespace xhr {
+
+class URLFetcherResponseWriter : public net::URLFetcherResponseWriter {
+ public:
+  class Buffer : public base::RefCountedThreadSafe<Buffer> {
+   public:
+    typedef script::PreallocatedArrayBufferData PreallocatedArrayBufferData;
+
+    enum Type {
+      kString,
+      kArrayBuffer,
+    };
+
+    explicit Buffer(Type type);
+
+    void DisablePreallocate();
+    void Clear();
+
+    int64_t GetAndResetDownloadProgress();
+    bool HasProgressSinceLastGetAndReset() const;
+
+    // When the following function is called, Write() can no longer be called to
+    // append more data.  It is the responsibility of the user of this class to
+    // ensure such behavior.
+    const std::string& GetReferenceOfStringAndSeal();
+    // Returns a reference to a std::string containing a copy of the data
+    // downloaded so far.  The reference is guaranteed to be valid until another
+    // public member function is called on this object.
+    const std::string& GetTemporaryReferenceOfString();
+
+    void GetAndReset(std::string* str);
+    void GetAndReset(PreallocatedArrayBufferData* data);
+
+    void MaybePreallocate(int64_t capacity);
+    void Write(const void* buffer, int num_bytes);
+
+   private:
+    size_t GetSize_Locked() const;
+
+    // It is possible (but extremely rare) that JS app changes response type
+    // after some data has been written on the network thread, in such case we
+    // allow to change the buffer type dynamically.
+    void UpdateType_Locked(Type type);
+
+    Type type_;
+    bool allow_preallocate_ = true;
+    bool capacity_known_ = false;
+
+    // This class can be accessed by both network and MainWebModule threads.
+    mutable base::Lock lock_;
+
+    bool allow_write_ = true;
+    size_t download_progress_ = 0;
+
+    // Data is stored in one of the following buffers, depends on the value of
+    // |type_|.
+    std::string data_as_string_;
+    // For use in GetReferenceOfString() so it can return a reference.
+    std::string copy_of_data_as_string_;
+    PreallocatedArrayBufferData data_as_array_buffer_;
+    size_t data_as_array_buffer_size_ = 0;
+  };
+
+  explicit URLFetcherResponseWriter(const scoped_refptr<Buffer>& buffer);
+  ~URLFetcherResponseWriter() override;
+
+  // URLFetcherResponseWriter overrides:
+  int Initialize(net::CompletionOnceCallback callback) override;
+  void OnResponseStarted(int64_t content_length) override;
+  int Write(net::IOBuffer* buffer, int num_bytes,
+            net::CompletionOnceCallback callback) override;
+  int Finish(int net_error, net::CompletionOnceCallback callback) override;
+
+ private:
+  scoped_refptr<Buffer> buffer_;
+
+  DISALLOW_COPY_AND_ASSIGN(URLFetcherResponseWriter);
+};
+
+}  // namespace xhr
+}  // namespace cobalt
+
+#endif  // COBALT_XHR_URL_FETCHER_BUFFER_WRITER_H_
diff --git a/src/cobalt/xhr/xhr.gyp b/src/cobalt/xhr/xhr.gyp
index eff72e7..86c029d 100644
--- a/src/cobalt/xhr/xhr.gyp
+++ b/src/cobalt/xhr/xhr.gyp
@@ -21,6 +21,8 @@
       'target_name': 'xhr',
       'type': 'static_library',
       'sources': [
+        'url_fetcher_buffer_writer.cc',
+        'url_fetcher_buffer_writer.h',
         'xhr_response_data.cc',
         'xhr_response_data.h',
         'xml_http_request.cc',
@@ -62,7 +64,6 @@
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
-        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         'xhr',
@@ -71,6 +72,7 @@
         #       ScriptValueFactory has non-virtual method CreatePromise().
         '<(DEPTH)/cobalt/script/engine.gyp:engine',
       ],
+      'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
     {
       'target_name': 'xhr_test_deploy',
diff --git a/src/cobalt/xhr/xml_http_request.cc b/src/cobalt/xhr/xml_http_request.cc
index de95cc4..0ae707f 100644
--- a/src/cobalt/xhr/xml_http_request.cc
+++ b/src/cobalt/xhr/xml_http_request.cc
@@ -51,10 +51,6 @@
 // How many milliseconds must elapse between each progress event notification.
 const int kProgressPeriodMs = 50;
 
-// Allocate 64KB on receiving the first chunk to avoid allocating small buffer
-// too many times.
-const size_t kInitialReceivingBufferSize = 64 * 1024;
-
 const char* kResponseTypes[] = {
     "",             // kDefault
     "text",         // kText
@@ -168,6 +164,8 @@
 
 XMLHttpRequest::XMLHttpRequest(script::EnvironmentSettings* settings)
     : XMLHttpRequestEventTarget(settings),
+      response_body_(new URLFetcherResponseWriter::Buffer(
+          URLFetcherResponseWriter::Buffer::kString)),
       settings_(base::polymorphic_downcast<dom::DOMSettings*>(settings)),
       state_(kUnsent),
       response_type_(kDefault),
@@ -203,7 +201,7 @@
   }
   ChangeState(kUnsent);
 
-  response_body_.Clear();
+  response_body_->Clear();
   response_array_buffer_reference_.reset();
 }
 
@@ -485,7 +483,14 @@
     return base::EmptyString();
   }
 
-  return response_body_.string();
+  // Note that the conversion from |response_body_| to std::string when |state_|
+  // isn't kDone isn't efficient for large responses.  Fortunately this feature
+  // is rarely used.
+  if (state_ == kLoading) {
+    LOG(WARNING) << "Retrieving responseText while loading can be inefficient.";
+    return response_body_->GetTemporaryReferenceOfString();
+  }
+  return response_body_->GetReferenceOfStringAndSeal();
 }
 
 // https://www.w3.org/TR/2014/WD-XMLHttpRequest-20140130/#the-responsexml-attribute
@@ -659,19 +664,6 @@
     fetch_mode_callback_->value().Run(is_cross_origin_);
   }
 
-  // Reserve space for the content in the case of a regular XHR request.
-  DCHECK_EQ(response_body_.size(), 0u);
-  if (!fetch_callback_) {
-    const int64 content_length = http_response_headers_->GetContentLength();
-
-    // If we know the eventual content length, allocate the total response body.
-    // Otherwise just reserve a reasonably large initial chunk.
-    size_t bytes_to_reserve = content_length > 0
-                                  ? static_cast<size_t>(content_length)
-                                  : kInitialReceivingBufferSize;
-    response_body_.Reserve(bytes_to_reserve);
-  }
-
   // Further filter response headers as XHR's mode is cors
   if (is_cross_origin_) {
     size_t iter = 0;
@@ -707,7 +699,7 @@
 
   ChangeState(kHeadersReceived);
 
-  UpdateProgress();
+  UpdateProgress(0);
 }
 
 void XMLHttpRequest::OnURLFetchDownloadProgress(
@@ -717,27 +709,19 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK_NE(state_, kDone);
 
-  auto* download_data_writer =
-      base::polymorphic_downcast<loader::URLFetcherStringWriter*>(
-          source->GetResponseWriter());
-  std::unique_ptr<std::string> download_data = download_data_writer->data();
-  if (!download_data.get() || download_data->empty()) {
+  if (response_body_->HasProgressSinceLastGetAndReset() == 0) {
     return;
   }
-  // Preserve the response body only for regular XHR requests. Fetch requests
-  // process the response in pieces, so do not need to keep the whole response.
-  if (!fetch_callback_) {
-    response_body_.Append(reinterpret_cast<const uint8*>(download_data->data()),
-                          download_data->size());
-  }
 
   // Signal to JavaScript that new data is now available.
   ChangeState(kLoading);
 
   if (fetch_callback_) {
+    std::string downloaded_data;
+    response_body_->GetAndReset(&downloaded_data);
     script::Handle<script::Uint8Array> data =
         script::Uint8Array::New(settings_->global_environment(),
-                                download_data->data(), download_data->size());
+                                downloaded_data.data(), downloaded_data.size());
     fetch_callback_->value().Run(data);
   }
 
@@ -746,7 +730,9 @@
   const base::TimeDelta elapsed(now - last_progress_time_);
   if (elapsed > base::TimeDelta::FromMilliseconds(kProgressPeriodMs)) {
     last_progress_time_ = now;
-    UpdateProgress();
+    // TODO: Investigate if we have to fire progress event with 0 loaded bytes
+    // when used as Fetch API.
+    UpdateProgress(response_body_->GetAndResetDownloadProgress());
   }
 }
 
@@ -787,7 +773,7 @@
       FireProgressEvent(upload_, base::Tokens::loadend());
     }
     ChangeState(kDone);
-    UpdateProgress();
+    UpdateProgress(response_body_->GetAndResetDownloadProgress());
     // Undo the ref we added in Send()
     DecrementActiveRequests();
   } else {
@@ -1038,13 +1024,14 @@
     // The request is done so it is safe to only keep the ArrayBuffer and clear
     // |response_body_|.  As |response_body_| will not be used unless the
     // request is re-opened.
-    auto array_buffer =
-        script::ArrayBuffer::New(settings_->global_environment(),
-                                 response_body_.data(), response_body_.size());
+    std::unique_ptr<script::PreallocatedArrayBufferData> downloaded_data(
+        new script::PreallocatedArrayBufferData());
+    response_body_->GetAndReset(downloaded_data.get());
+    auto array_buffer = script::ArrayBuffer::New(
+        settings_->global_environment(), std::move(downloaded_data));
     response_array_buffer_reference_.reset(
         new script::ScriptValue<script::ArrayBuffer>::Reference(this,
                                                                 array_buffer));
-    response_body_.Clear();
     return array_buffer;
   } else {
     return script::Handle<script::ArrayBuffer>(
@@ -1052,10 +1039,9 @@
   }
 }
 
-void XMLHttpRequest::UpdateProgress() {
+void XMLHttpRequest::UpdateProgress(int64_t received_length) {
   DCHECK(http_response_headers_);
   const int64 content_length = http_response_headers_->GetContentLength();
-  const int64 received_length = static_cast<int64>(response_body_.size());
   const bool length_computable =
       content_length > 0 && received_length <= content_length;
   const uint64 total =
@@ -1111,15 +1097,24 @@
 void XMLHttpRequest::StartRequest(const std::string& request_body) {
   TRACK_MEMORY_SCOPE("XHR");
 
-  response_body_.Clear();
   response_array_buffer_reference_.reset();
 
   network::NetworkModule* network_module =
       settings_->fetcher_factory()->network_module();
   url_fetcher_ = net::URLFetcher::Create(request_url_, method_, this);
   url_fetcher_->SetRequestContext(network_module->url_request_context_getter());
+  if (fetch_callback_) {
+    response_body_ = new URLFetcherResponseWriter::Buffer(
+        URLFetcherResponseWriter::Buffer::kString);
+    response_body_->DisablePreallocate();
+  } else {
+    response_body_ = new URLFetcherResponseWriter::Buffer(
+        response_type_ == kArrayBuffer
+            ? URLFetcherResponseWriter::Buffer::kArrayBuffer
+            : URLFetcherResponseWriter::Buffer::kString);
+  }
   std::unique_ptr<net::URLFetcherResponseWriter> download_data_writer(
-      new loader::URLFetcherStringWriter());
+      new URLFetcherResponseWriter(response_body_));
   url_fetcher_->SaveResponseWithWriter(std::move(download_data_writer));
   // Don't retry, let the caller deal with it.
   url_fetcher_->SetAutomaticallyRetryOn5xx(false);
@@ -1197,9 +1192,11 @@
       (xhr.response_type_ == XMLHttpRequest::kDefault ||
        xhr.response_type_ == XMLHttpRequest::kText)) {
     size_t kMaxSize = 4096;
-    response_text = base::StringPiece(
-        reinterpret_cast<const char*>(xhr.response_body_.data()),
-        std::min(kMaxSize, xhr.response_body_.size()));
+    const auto& response_body =
+        xhr.response_body_->GetTemporaryReferenceOfString();
+    response_text =
+        base::StringPiece(reinterpret_cast<const char*>(response_body.data()),
+                          std::min(kMaxSize, response_body.size()));
   }
 
   std::string xhr_out = base::StringPrintf(
@@ -1231,6 +1228,8 @@
 
 // https://www.w3.org/TR/2014/WD-XMLHttpRequest-20140130/#document-response-entity-body
 scoped_refptr<dom::Document> XMLHttpRequest::GetDocumentResponseEntityBody() {
+  DCHECK_EQ(state_, kDone);
+
   // Step 1..5
   const std::string final_mime_type =
       mime_type_override_.empty() ? response_mime_type_ : mime_type_override_;
@@ -1250,8 +1249,8 @@
       base::Bind(&XMLHttpRequest::XMLDecoderLoadCompleteCallback,
                  base::Unretained(this)));
   has_xml_decoder_error_ = false;
-  xml_decoder.DecodeChunk(response_body_.string().c_str(),
-                          response_body_.string().size());
+  xml_decoder.DecodeChunk(response_body_->GetReferenceOfStringAndSeal().c_str(),
+                          response_body_->GetReferenceOfStringAndSeal().size());
   xml_decoder.Finish();
   if (has_xml_decoder_error_) {
     return NULL;
diff --git a/src/cobalt/xhr/xml_http_request.h b/src/cobalt/xhr/xml_http_request.h
index 6ff0f76..4dd5375 100644
--- a/src/cobalt/xhr/xml_http_request.h
+++ b/src/cobalt/xhr/xml_http_request.h
@@ -34,7 +34,7 @@
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/typed_arrays.h"
 #include "cobalt/script/union_type.h"
-#include "cobalt/xhr/xhr_response_data.h"
+#include "cobalt/xhr/url_fetcher_buffer_writer.h"
 #include "cobalt/xhr/xml_http_request_event_target.h"
 #include "cobalt/xhr/xml_http_request_upload.h"
 #include "net/http/http_request_headers.h"
@@ -216,7 +216,7 @@
   // Return array buffer response body as an ArrayBuffer.
   script::Handle<script::ArrayBuffer> response_array_buffer();
 
-  void UpdateProgress();
+  void UpdateProgress(int64_t received_length);
 
   void StartRequest(const std::string& request_body);
 
@@ -253,7 +253,7 @@
 
   std::unique_ptr<net::URLFetcher> url_fetcher_;
   scoped_refptr<net::HttpResponseHeaders> http_response_headers_;
-  XhrResponseData response_body_;
+  scoped_refptr<URLFetcherResponseWriter::Buffer> response_body_;
   std::unique_ptr<script::ScriptValue<script::ArrayBuffer>::Reference>
       response_array_buffer_reference_;
   scoped_refptr<XMLHttpRequestUpload> upload_;
diff --git a/src/content/browser/speech/speech.gyp b/src/content/browser/speech/speech.gyp
index 397b372..e562177 100644
--- a/src/content/browser/speech/speech.gyp
+++ b/src/content/browser/speech/speech.gyp
@@ -39,12 +39,12 @@
       ],
       'dependencies': [
         'speech',
-        '<(DEPTH)/base/base.gyp:run_all_unittests',
         '<(DEPTH)/base/base.gyp:test_support_base',
         '<(DEPTH)/cobalt/media/media.gyp:media',
         '<(DEPTH)/testing/gtest.gyp:gtest',
         '<(DEPTH)/starboard/starboard.gyp:starboard',
       ],
+      'includes': ['<(DEPTH)/base/test/test.gypi'],
     },
 
     {
diff --git a/src/crypto/crypto.gyp b/src/crypto/crypto.gyp
index 354d689..48610c6 100644
--- a/src/crypto/crypto.gyp
+++ b/src/crypto/crypto.gyp
@@ -88,10 +88,10 @@
         'crypto',
         '<(DEPTH)/base/base.gyp:base',
         '<(DEPTH)/base/base.gyp:test_support_base',
-        '<(DEPTH)/base/base.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
       ],
+      'includes': ['<(DEPTH)/base/test/test.gypi'],
     },
   ],
   'conditions': [
diff --git a/src/nb/analytics/memory_tracker_impl.cc b/src/nb/analytics/memory_tracker_impl.cc
index 58eab36..6b08eea 100644
--- a/src/nb/analytics/memory_tracker_impl.cc
+++ b/src/nb/analytics/memory_tracker_impl.cc
@@ -231,7 +231,10 @@
                                  const void* memory,
                                  size_t size) {
   // We might do something more interesting with MapMemory calls later.
+  MemoryTrackerImpl* t = static_cast<MemoryTrackerImpl*>(context);
+  t->PushAllocationGroupByName("Mapped Memory");
   OnMalloc(context, memory, size);
+  t->PopAllocationGroup();
 }
 
 void MemoryTrackerImpl::OnUnMapMem(void* context,
diff --git a/src/nb/fixed_no_free_allocator.cc b/src/nb/fixed_no_free_allocator.cc
index 9c60617..f4f8e1c 100644
--- a/src/nb/fixed_no_free_allocator.cc
+++ b/src/nb/fixed_no_free_allocator.cc
@@ -16,8 +16,6 @@
 
 #include "nb/fixed_no_free_allocator.h"
 
-#include <algorithm>
-
 #include "nb/pointer_arithmetic.h"
 #include "starboard/common/log.h"
 
@@ -54,8 +52,6 @@
 void* FixedNoFreeAllocator::Allocate(std::size_t* size,
                                      std::size_t alignment,
                                      bool align_pointer) {
-  *size = std::max<std::size_t>(*size, 1);
-
   // Find the next aligned memory available.
   uint8_t* aligned_next_memory =
       AsPointer(AlignUp(AsInteger(next_memory_), alignment));
diff --git a/src/nb/reuse_allocator_base.cc b/src/nb/reuse_allocator_base.cc
index 03e9903..95189ac 100644
--- a/src/nb/reuse_allocator_base.cc
+++ b/src/nb/reuse_allocator_base.cc
@@ -358,18 +358,45 @@
   // allocate the difference between |size| and the size of the right most block
   // in the hope that they are continuous and can be connect to a block that is
   // large enough to fulfill |size|.
-  size_t size_difference = size - free_blocks_.rbegin()->size();
-  if (max_capacity_ && capacity_ + size_difference > max_capacity_) {
+  size_t free_address = AsInteger(free_blocks_.rbegin()->address());
+  size_t free_size = free_blocks_.rbegin()->size();
+  size_t aligned_address = AlignUp(free_address, alignment);
+  // In order to calculate |size_to_allocate|, we need to account for two
+  // possible scenarios: when |aligned_address| is within the free block region,
+  // or when it is after the free block region.
+  //
+  // Scenario 1:
+  //
+  // |free_address|      |free_address + free_size|
+  //   |                 |
+  //   | <- free_size -> | <- size_to_allocate -> |
+  //   --------------------------------------------
+  //               |<-          size           -> |
+  //               |
+  // |aligned_address|
+  //
+  // Scenario 2:
+  //
+  // |free_address|
+  //   |
+  //   | <- free_size -> | <- size_to_allocate -> |
+  //   --------------------------------------------
+  //                     |           | <- size -> |
+  //                     |           |
+  // |free_address + free_size|  |aligned_address|
+  size_t size_to_allocate = aligned_address + size - free_address - free_size;
+  if (max_capacity_ && capacity_ + size_to_allocate > max_capacity_) {
     return free_blocks_.end();
   }
-  ptr = fallback_allocator_->AllocateForAlignment(&size_difference, alignment);
+  SB_DCHECK(size_to_allocate > 0);
+  ptr = fallback_allocator_->AllocateForAlignment(&size_to_allocate, 1);
   if (ptr == NULL) {
     return free_blocks_.end();
   }
 
   fallback_allocations_.push_back(ptr);
-  capacity_ += size_difference;
-  AddFreeBlock(MemoryBlock(ptr, size_difference));
+  capacity_ += size_to_allocate;
+  AddFreeBlock(MemoryBlock(ptr, size_to_allocate));
   FreeBlockSet::iterator iter = free_blocks_.end();
   --iter;
   return iter->CanFullfill(size, alignment) ? iter : free_blocks_.end();
diff --git a/src/net/base/ip_endpoint.cc b/src/net/base/ip_endpoint.cc
index 1ed221f..076d49c 100644
--- a/src/net/base/ip_endpoint.cc
+++ b/src/net/base/ip_endpoint.cc
@@ -54,7 +54,7 @@
 
     default:
       NOTREACHED();
-      break;
+      return false;
   }
 
   return true;
diff --git a/src/net/cert/internal/trust_store_in_memory_starboard.cc b/src/net/cert/internal/trust_store_in_memory_starboard.cc
index 3d53b20..e3f0856 100644
--- a/src/net/cert/internal/trust_store_in_memory_starboard.cc
+++ b/src/net/cert/internal/trust_store_in_memory_starboard.cc
@@ -75,8 +75,20 @@
 #endif
     return std::unordered_set<std::string>();
   }
-  SbDirectoryEntry dir_entry;
+
   std::unordered_set<std::string> trusted_certs_on_disk;
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+  std::vector<char> dir_entry(SB_FILE_MAX_NAME);
+
+  while (SbDirectoryGetNext(sb_certs_directory, dir_entry.data(),
+                            dir_entry.size())) {
+    if (SbStringGetLength(dir_entry.data()) != kCertFileNameLength) {
+      continue;
+    }
+    trusted_certs_on_disk.emplace(dir_entry.data());
+  }
+#else   // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+  SbDirectoryEntry dir_entry;
 
   while (SbDirectoryGetNext(sb_certs_directory, &dir_entry)) {
     if (SbStringGetLength(dir_entry.name) != kCertFileNameLength) {
@@ -84,6 +96,8 @@
     }
     trusted_certs_on_disk.emplace(dir_entry.name);
   }
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
   SbDirectoryClose(sb_certs_directory);
   return std::move(trusted_certs_on_disk);
 }
diff --git a/src/net/dial/dial_udp_server.cc b/src/net/dial/dial_udp_server.cc
index affd934..bca1005 100644
--- a/src/net/dial/dial_udp_server.cc
+++ b/src/net/dial/dial_udp_server.cc
@@ -140,20 +140,23 @@
   // If M-Search request was valid, send response. Else, keep quiet.
   if (ParseSearchRequest(std::string(read_buf_->data()))) {
     auto response = std::make_unique<std::string>();
-    *response = ConstructSearchResponse();
+    *response = std::move(ConstructSearchResponse());
     // Using the fake IOBuffer to avoid another copy.
     scoped_refptr<WrappedIOBuffer> fake_buffer =
         new WrappedIOBuffer(response->data());
     // After optimization, some compiler will dereference and get response size
     // later than passing response.
     auto response_size = response->size();
-    auto result = socket_->SendTo(
+    int result = socket_->SendTo(
         fake_buffer.get(), response_size, client_address_,
-        base::Bind([](scoped_refptr<WrappedIOBuffer>,
-                      std::unique_ptr<std::string>, int /*rv*/) {},
+        base::Bind(&DialUdpServer::WriteComplete, base::Unretained(this),
                    fake_buffer, base::Passed(&response)));
-    if (result < 0) {
-      DLOG(WARNING) << "Socket SentTo error code: " << result;
+    if (result == ERR_IO_PENDING) {
+      // WriteComplete is responsible for posting the next callback to accept
+      // connection.
+      return;
+    } else if (result < 0) {
+      LOG(ERROR) << "UDPSocket SendTo error: " << result;
     }
   }
 
@@ -166,6 +169,17 @@
                             base::Unretained(this)));
 }
 
+void DialUdpServer::WriteComplete(scoped_refptr<WrappedIOBuffer>,
+                                  std::unique_ptr<std::string>,
+                                  int rv) {
+  if (rv < 0) {
+    LOG(ERROR) << "UDPSocket completion callback error: " << rv;
+  }
+  thread_.task_runner()->PostTask(
+      FROM_HERE, base::Bind(&DialUdpServer::AcceptAndProcessConnection,
+                            base::Unretained(this)));
+}
+
 // Parse a request to make sure it is a M-Search.
 bool DialUdpServer::ParseSearchRequest(const std::string& request) {
   HttpServerRequestInfo info;
@@ -215,7 +229,7 @@
 
 // Since we are constructing a response from user-generated string,
 // ensure all user-generated strings pass through StringPrintf.
-const std::string DialUdpServer::ConstructSearchResponse() const {
+std::string DialUdpServer::ConstructSearchResponse() const {
   DCHECK(!location_url_.empty());
 
   std::string ret("HTTP/1.1 200 OK\r\n");
@@ -237,7 +251,7 @@
                                 DialSystemConfig::GetInstance()->model_uuid(),
                                 kDialStRequest));
   ret.append("\r\n");
-  return ret;
+  return std::move(ret);
 }
 
 }  // namespace net
diff --git a/src/net/dial/dial_udp_server.h b/src/net/dial/dial_udp_server.h
index c3115c9..7af175a 100644
--- a/src/net/dial/dial_udp_server.h
+++ b/src/net/dial/dial_udp_server.h
@@ -30,6 +30,10 @@
 
   virtual void DidClose(UDPSocket* sock);
 
+  void WriteComplete(scoped_refptr<WrappedIOBuffer>,
+                     std::unique_ptr<std::string>,
+                     int rv);
+
  private:
   FRIEND_TEST_ALL_PREFIXES(DialUdpServerTest, ParseSearchRequest);
 
@@ -42,7 +46,7 @@
   void AcceptAndProcessConnection();
 
   // Construct the appropriate search response.
-  const std::string ConstructSearchResponse() const;
+  std::string ConstructSearchResponse() const;
 
   // Parse a request to make sure it is a M-Search.
   static bool ParseSearchRequest(const std::string& request);
diff --git a/src/net/disk_cache/simple/simple_index_file_starboard.cc b/src/net/disk_cache/simple/simple_index_file_starboard.cc
index 3f9634b..29d913b 100644
--- a/src/net/disk_cache/simple/simple_index_file_starboard.cc
+++ b/src/net/disk_cache/simple/simple_index_file_starboard.cc
@@ -33,7 +33,19 @@
     PLOG(ERROR) << "opendir " << cache_path.value() << ", erron: " << error;
     return false;
   }
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+  std::vector<char> entry(SB_FILE_MAX_NAME);
+
+  while (true) {
+    if (!SbDirectoryGetNext(dir, entry.data(), entry.size())) {
+      PLOG(ERROR) << "readdir " << cache_path.value();
+      return false;
+    }
+
+    const std::string file_name(entry.data());
+#else   // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
   SbDirectoryEntry entry;
+
   while (true) {
     if (!SbDirectoryGetNext(dir, &entry)) {
       PLOG(ERROR) << "readdir " << cache_path.value();
@@ -41,6 +53,7 @@
     }
 
     const std::string file_name(entry.name);
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
     if (file_name == "." || file_name == "..")
       continue;
     const base::FilePath file_path =
diff --git a/src/net/socket/transport_client_socket_pool.cc b/src/net/socket/transport_client_socket_pool.cc
index f5689ab..263e17c2 100644
--- a/src/net/socket/transport_client_socket_pool.cc
+++ b/src/net/socket/transport_client_socket_pool.cc
@@ -258,6 +258,15 @@
 int TransportConnectJob::DoResolveHostComplete(int result) {
   TRACE_EVENT0(kNetTracingCategory,
                "TransportConnectJob::DoResolveHostComplete");
+#ifdef STARBOARD
+    // Preferentially connect to an IPv4 address first, if available. Some
+    // hosts may have IPv6 addresses to which we can connect, but the read
+    // may still fail if the network is not properly configured. The existing
+    // code has a fallback mechanism to try different IPs in |addresses_|
+    // when connection fails. However, in this case, a connection can be made
+    // with the IPv6 address, but the read fails.
+    MakeAddressListStartWithIPv4(&addresses_);
+#endif
   connect_timing_.dns_end = base::TimeTicks::Now();
   // Overwrite connection start time, since for connections that do not go
   // through proxies, |connect_start| should not include dns lookup time.
diff --git a/src/net/socket/transport_client_socket_pool_unittest.cc b/src/net/socket/transport_client_socket_pool_unittest.cc
index 9d64c43..e8c7543 100644
--- a/src/net/socket/transport_client_socket_pool_unittest.cc
+++ b/src/net/socket/transport_client_socket_pool_unittest.cc
@@ -856,6 +856,10 @@
   handle.Reset();
 }
 
+// Disable this test since the TransportConnectJob::DoResolveHostComplete
+// customization causes the IPv4 address to be tried first, thus breaking
+// the assumptions of this test.
+#ifndef STARBOARD
 // Test the case of the IPv6 address stalling, and falling back to the IPv4
 // socket which finishes first.
 TEST_F(TransportClientSocketPoolTest, IPv6FallbackSocketIPv4FinishesFirst) {
@@ -903,7 +907,12 @@
 
   EXPECT_EQ(2, client_socket_factory_.allocation_count());
 }
+#endif
 
+// Disable this test since the TransportConnectJob::DoResolveHostComplete
+// customization causes the IPv4 address to be tried first, thus breaking
+// the assumptions of this test.
+#ifndef STARBOARD
 // Test the case of the IPv6 address being slow, thus falling back to trying to
 // connect to the IPv4 address, but having the connect to the IPv6 address
 // finish first.
@@ -955,6 +964,7 @@
 
   EXPECT_EQ(2, client_socket_factory_.allocation_count());
 }
+#endif
 
 TEST_F(TransportClientSocketPoolTest, IPv6NoIPv4AddressesToFallbackTo) {
   // Create a pool without backup jobs.
@@ -1072,6 +1082,10 @@
   EXPECT_TRUE(socket_data.IsUsingTCPFastOpen());
 }
 
+// Disable this test since the TransportConnectJob::DoResolveHostComplete
+// customization causes the IPv4 address to be tried first, thus breaking
+// the assumptions of this test.
+#ifndef STARBOARD
 // Test that if TCP FastOpen is enabled, it does not do anything when there
 // is a IPv6 address with fallback to an IPv4 address. This test tests the case
 // when the IPv6 connect fails and the IPv4 one succeeds.
@@ -1141,6 +1155,7 @@
   // Verify that TCP FastOpen was not turned on for the socket.
   EXPECT_FALSE(socket_data.IsUsingTCPFastOpen());
 }
+#endif
 
 // Test that SocketTag passed into TransportClientSocketPool is applied to
 // returned sockets.
diff --git a/src/net/socket/udp_socket_starboard.cc b/src/net/socket/udp_socket_starboard.cc
index 707fb74..369cf68 100644
--- a/src/net/socket/udp_socket_starboard.cc
+++ b/src/net/socket/udp_socket_starboard.cc
@@ -441,7 +441,7 @@
 
   if (result != ERR_IO_PENDING) {
     IPEndPoint log_address;
-    if (log_address.FromSbSocketAddress(&sb_address)) {
+    if (result < 0 || !log_address.FromSbSocketAddress(&sb_address)) {
       LogRead(result, buf->data(), NULL);
     } else {
       LogRead(result, buf->data(), &log_address);
diff --git a/src/net/url_request/url_fetcher_core.cc b/src/net/url_request/url_fetcher_core.cc
index 6d7b7f4..48c99c0 100644
--- a/src/net/url_request/url_fetcher_core.cc
+++ b/src/net/url_request/url_fetcher_core.cc
@@ -27,15 +27,45 @@
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "net/url_request/url_request_throttler_manager.h"
+#include "starboard/time.h"
 #include "starboard/types.h"
 #include "url/origin.h"
 
 namespace {
 
+#if defined(STARBOARD)
+const SbTime kInformDownloadProgressInterval = 50 * kSbTimeMillisecond;
+#else   // defined(STARBOARD)
 const int kBufferSize = 4096;
+#endif  // defined(STARBOARD)
+
 const int kUploadProgressTimerInterval = 100;
+
 bool g_ignore_certificate_requests = false;
 
+#if defined(STARBOARD)
+int GetIOBufferSizeByContentSize(int content_size) {
+  // If |content_size| is unknown, use 64k as buffer size.
+  if (content_size < 0) {
+    return 64 * 1024;
+  }
+  // If the content is really small, use 4k anyway.
+  if (content_size <= 4 * 1024) {
+    return 4 * 1024;
+  }
+  // If the content is medium sized, use the size as buffer size.
+  if (content_size < 64 * 1024) {
+    return content_size;
+  }
+  // If the content is fairly large, use a much larger buffer size.
+  if (content_size >= 512 * 1024) {
+    return 256 * 1024;
+  }
+  // Otherwise use 64k as buffer size.
+  return 64 * 1024;
+}
+#endif  // defined(STARBOARD)
+
 }  // namespace
 
 namespace net {
@@ -80,6 +110,9 @@
       load_flags_(LOAD_NORMAL),
       allow_credentials_(base::nullopt),
       response_code_(URLFetcher::RESPONSE_CODE_INVALID),
+#if defined(STARBOARD)
+      io_buffer_size_(GetIOBufferSizeByContentSize(-1)),
+#endif  // defined(STARBOARD)
       url_request_data_key_(NULL),
       was_fetched_via_proxy_(false),
       was_cached_(false),
@@ -442,15 +475,22 @@
   }
 
   DCHECK(!buffer_);
-  if (request_type_ != URLFetcher::HEAD)
-    buffer_ = base::MakeRefCounted<IOBuffer>(kBufferSize);
 #if defined(STARBOARD)
+  if (request_type_ != URLFetcher::HEAD) {
+    response_writer_->OnResponseStarted(total_response_bytes_);
+    io_buffer_size_ = GetIOBufferSizeByContentSize(total_response_bytes_);
+    buffer_ = base::MakeRefCounted<IOBuffer>(io_buffer_size_);
+  }
+
   // We update this earlier than OnReadCompleted(), so that the delegate
   // can know about it if they call GetURL() in any callback.
   if (!stopped_on_redirect_) {
     url_ = request_->url();
   }
   InformDelegateResponseStarted();
+#else   // defined(STARBOARD)
+  if (request_type_ != URLFetcher::HEAD)
+    buffer_ = base::MakeRefCounted<IOBuffer>(kBufferSize);
 #endif  // defined(STARBOARD)
   ReadResponse();
 }
@@ -480,6 +520,35 @@
   if (throttler_manager)
     url_throttler_entry_ = throttler_manager->RegisterRequestUrl(url_);
 
+#if defined(STARBOARD)
+  // Prime it to the current time so it is only called after the loop, or every
+  // time when the loop takes |kInformDownloadProgressInterval|.
+  SbTime download_progress_informed_at = SbTimeGetMonotonicNow();
+  bool did_read_after_inform_download_progress = false;
+
+  while (bytes_read > 0) {
+    current_response_bytes_ += bytes_read;
+    did_read_after_inform_download_progress = true;
+    auto now = SbTimeGetMonotonicNow();
+    if (now - download_progress_informed_at > kInformDownloadProgressInterval) {
+      InformDelegateDownloadProgress();
+      download_progress_informed_at = now;
+      did_read_after_inform_download_progress = false;
+    }
+
+    const int result = WriteBuffer(
+        base::MakeRefCounted<DrainableIOBuffer>(buffer_, bytes_read));
+    if (result < 0) {
+      // Write failed or waiting for write completion.
+      return;
+    }
+    bytes_read = request_->Read(buffer_.get(), io_buffer_size_);
+  }
+
+  if (did_read_after_inform_download_progress) {
+    InformDelegateDownloadProgress();
+  }
+#else   // defined(STARBOARD)
   while (bytes_read > 0) {
     current_response_bytes_ += bytes_read;
     InformDelegateDownloadProgress();
@@ -492,6 +561,7 @@
     }
     bytes_read = request_->Read(buffer_.get(), kBufferSize);
   }
+#endif  // defined(STARBOARD)
 
   // See comments re: HEAD requests in ReadResponse().
   if (bytes_read != ERR_IO_PENDING || request_type_ == URLFetcher::HEAD) {
@@ -933,8 +1003,13 @@
   // completed immediately, without trying to read any data back (all we care
   // about is the response code and headers, which we already have).
   int bytes_read = 0;
+#if defined(STARBOARD)
+  if (request_type_ != URLFetcher::HEAD)
+    bytes_read = request_->Read(buffer_.get(), io_buffer_size_);
+#else   // defined(STARBOARD)
   if (request_type_ != URLFetcher::HEAD)
     bytes_read = request_->Read(buffer_.get(), kBufferSize);
+#endif  // defined(STARBOARD)
 
   OnReadCompleted(request_.get(), bytes_read);
 }
diff --git a/src/net/url_request/url_fetcher_core.h b/src/net/url_request/url_fetcher_core.h
index 05ebd67..3a18e18 100644
--- a/src/net/url_request/url_fetcher_core.h
+++ b/src/net/url_request/url_fetcher_core.h
@@ -258,6 +258,10 @@
   // Whether credentials are sent along with the request.
   base::Optional<bool> allow_credentials_;
   int response_code_;                // HTTP status code for the request
+
+#if defined(STARBOARD)
+  int io_buffer_size_;
+#endif  // defined(STARBOARD)
   scoped_refptr<IOBuffer> buffer_;
                                      // Read buffer
   scoped_refptr<URLRequestContextGetter> request_context_getter_;
diff --git a/src/net/url_request/url_fetcher_response_writer.h b/src/net/url_request/url_fetcher_response_writer.h
index 1076a3e..bd4ca14 100644
--- a/src/net/url_request/url_fetcher_response_writer.h
+++ b/src/net/url_request/url_fetcher_response_writer.h
@@ -37,6 +37,13 @@
   // Initialize() success results in discarding already written data.
   virtual int Initialize(CompletionOnceCallback callback) = 0;
 
+#if defined(STARBOARD)
+  // The user of this class *may* call this function before any calls to Write()
+  // to prime the instance with response size, so it has a chance to do some
+  // preparation work, like pre-allocate the buffer.
+  virtual void OnResponseStarted(int64_t content_length) = 0;
+#endif  // defined(STARBOARD)
+
   // Writes |num_bytes| bytes in |buffer|, and returns the number of bytes
   // written or an error code. If ERR_IO_PENDING is returned, |callback| will be
   // run later with the result.
@@ -70,6 +77,9 @@
 
   // URLFetcherResponseWriter overrides:
   int Initialize(CompletionOnceCallback callback) override;
+#if defined(STARBOARD)
+  void OnResponseStarted(int64_t /*content_length*/) override {}
+#endif  // defined(STARBOARD)
   int Write(IOBuffer* buffer,
             int num_bytes,
             CompletionOnceCallback callback) override;
@@ -96,6 +106,9 @@
 
   // URLFetcherResponseWriter overrides:
   int Initialize(CompletionOnceCallback callback) override;
+#if defined(STARBOARD)
+  void OnResponseStarted(int64_t /*content_length*/) override {}
+#endif  // defined(STARBOARD)
   int Write(IOBuffer* buffer,
             int num_bytes,
             CompletionOnceCallback callback) override;
diff --git a/src/net/websockets/websocket_channel.h b/src/net/websockets/websocket_channel.h
index 2eae388..d914a08 100644
--- a/src/net/websockets/websocket_channel.h
+++ b/src/net/websockets/websocket_channel.h
@@ -90,10 +90,15 @@
   // character boundaries. Calling SendFrame may result in synchronous calls to
   // |event_interface_| which may result in this object being deleted. In that
   // case, the return value will be CHANNEL_DELETED.
+#if defined(STARBOARD)
+  // Make it virtual to enable mocking for unit tests.
+  virtual ChannelState SendFrame(bool fin,
+#else
   ChannelState SendFrame(bool fin,
-                         WebSocketFrameHeader::OpCode op_code,
-                         scoped_refptr<IOBuffer> buffer,
-                         size_t buffer_size);
+#endif
+                                 WebSocketFrameHeader::OpCode op_code,
+                                 scoped_refptr<IOBuffer> buffer,
+                                 size_t buffer_size);
 
   // Sends |quota| units of flow control to the remote side. If the underlying
   // transport has a concept of |quota|, then it permits the remote server to
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
index 2105e5b..4a091f8 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
@@ -19,7 +19,9 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
 import android.media.AudioManager;
+import android.media.AudioTrack;
 import android.os.Build;
 import dev.cobalt.util.Log;
 import dev.cobalt.util.UsedByNative;
@@ -105,4 +107,25 @@
     }
     return maxChannels;
   }
+
+  /** Returns the minimum buffer size of AudioTrack. */
+  @SuppressWarnings("unused")
+  @UsedByNative
+  int getMinBufferSize(int sampleType, int sampleRate, int channelCount) {
+    int channelConfig;
+    switch (channelCount) {
+      case 1:
+        channelConfig = AudioFormat.CHANNEL_OUT_MONO;
+        break;
+      case 2:
+        channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
+        break;
+      case 6:
+        channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
+        break;
+      default:
+        throw new RuntimeException("Unsupported channel count: " + channelCount);
+    }
+    return AudioTrack.getMinBufferSize(sampleRate, channelConfig, sampleType);
+  }
 }
diff --git a/src/starboard/android/shared/audio_sink_get_min_buffer_size_in_frames.cc b/src/starboard/android/shared/audio_sink_get_min_buffer_size_in_frames.cc
index 2bd7c3f..c6af790 100644
--- a/src/starboard/android/shared/audio_sink_get_min_buffer_size_in_frames.cc
+++ b/src/starboard/android/shared/audio_sink_get_min_buffer_size_in_frames.cc
@@ -20,10 +20,6 @@
 int SbAudioSinkGetMinBufferSizeInFrames(int channels,
                                         SbMediaAudioSampleType sample_type,
                                         int sampling_frequency_hz) {
-  // Currently, we only use |min_required_frames_| for web audio, which
-  // only supports 48k mono or stereo sound.
-  SB_DCHECK(sampling_frequency_hz == 48000);
-
   if (channels <= 0 || channels > SbAudioSinkGetMaxChannels()) {
     SB_LOG(ERROR) << "Not support channels count " << channels;
     return -1;
@@ -33,7 +29,7 @@
     SB_LOG(ERROR) << "Not support sample type " << sample_type;
     return -1;
   }
-  if (sampling_frequency_hz <= 0) {
+  if (sampling_frequency_hz <= 0 || sampling_frequency_hz >= 50000) {
     SB_LOG(ERROR) << "Not support sample frequency " << sampling_frequency_hz;
     return -1;
   }
diff --git a/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc b/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
index e9a87a3..6276641 100644
--- a/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
+++ b/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
@@ -39,22 +39,18 @@
 }
 }  // namespace
 
-MinRequiredFramesTester::MinRequiredFramesTester(int audio_sink_buffer_size,
-                                                 int max_required_frames,
-                                                 int default_required_frames,
+MinRequiredFramesTester::MinRequiredFramesTester(int max_required_frames,
                                                  int required_frames_increment,
                                                  int min_stable_played_frames)
-    : audio_sink_buffer_size_(audio_sink_buffer_size),
-      max_required_frames_(max_required_frames),
-      default_required_frames_(default_required_frames),
+    : max_required_frames_(max_required_frames),
       required_frames_increment_(required_frames_increment),
       min_stable_played_frames_(min_stable_played_frames),
       condition_variable_(mutex_),
-      destroyed_(false) {}
+      destroying_(false) {}
 
 MinRequiredFramesTester::~MinRequiredFramesTester() {
   SB_DCHECK(thread_checker_.CalledOnValidThread());
-  destroyed_.store(true);
+  destroying_.store(true);
   if (SbThreadIsValid(tester_thread_)) {
     {
       ScopedLock scoped_lock(mutex_);
@@ -65,19 +61,24 @@
   }
 }
 
-void MinRequiredFramesTester::StartTest(
+void MinRequiredFramesTester::AddTest(
     int number_of_channels,
     SbMediaAudioSampleType sample_type,
     int sample_rate,
-    OnMinRequiredFramesReceivedCallback received_cb) {
+    const OnMinRequiredFramesReceivedCallback& received_cb,
+    int default_required_frames) {
   SB_DCHECK(thread_checker_.CalledOnValidThread());
-  // MinRequiredFramesTester only supports to do test once now.
+  // MinRequiredFramesTester doesn't support to add test after starts.
   SB_DCHECK(!SbThreadIsValid(tester_thread_));
 
-  number_of_channels_ = number_of_channels;
-  sample_type_ = sample_type;
-  sample_rate_ = sample_rate;
-  received_cb_ = received_cb;
+  test_tasks_.emplace_back(number_of_channels, sample_type, sample_rate,
+                           received_cb, default_required_frames);
+}
+
+void MinRequiredFramesTester::Start() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  // MinRequiredFramesTester only supports to start once.
+  SB_DCHECK(!SbThreadIsValid(tester_thread_));
 
   tester_thread_ =
       SbThreadCreate(0, kSbThreadPriorityLowest, kSbThreadNoAffinity, true,
@@ -97,45 +98,51 @@
 
 void MinRequiredFramesTester::TesterThreadFunc() {
   bool wait_timeout = false;
-  // Currently, we only support test once. But we can put following codes in
-  // a for loop easily to support test multiple times.
-  std::vector<uint8_t> silence_buffer(
-      max_required_frames_ * number_of_channels_ * GetSampleSize(sample_type_),
-      0);
-  void* frame_buffers[1];
-  frame_buffers[0] = silence_buffer.data();
-  // Set default values.
-  min_required_frames_ = default_required_frames_;
-  total_consumed_frames_ = 0;
-  last_underrun_count_ = -1;
-  last_total_consumed_frames_ = 0;
-  {
-    ScopedLock scoped_lock(mutex_);
-    // Need to check |destroyed_| before start, as MinRequiredFramesTester may
+  for (const TestTask& task : test_tasks_) {
+    // Need to check |destroying_| before start, as MinRequiredFramesTester may
     // be destroyed immediately after tester thread started.
-    if (!destroyed_.load()) {
-      audio_sink_ = new AudioTrackAudioSink(
-          NULL, number_of_channels_, sample_rate_, sample_type_, frame_buffers,
-          max_required_frames_,
-          audio_sink_buffer_size_ * number_of_channels_ *
-              GetSampleSize(sample_type_),
-          &MinRequiredFramesTester::UpdateSourceStatusFunc,
-          &MinRequiredFramesTester::ConsumeFramesFunc, this);
-      wait_timeout = !condition_variable_.WaitTimed(kSbTimeSecond * 5);
-      if (wait_timeout) {
-        SB_LOG(ERROR) << "Audio sink min required frames tester timeout.";
-        SB_NOTREACHED();
-      }
+    if (destroying_.load()) {
+      break;
     }
-  }
-  delete audio_sink_;
-  audio_sink_ = nullptr;
-  // Call |received_cb_| after audio sink thread is ended.
-  // |number_of_channels_|, |sample_type_|, |sample_rate_| and
-  // |min_required_frames_| are shared between two threads.
-  if (!destroyed_.load() && !wait_timeout) {
-    received_cb_(number_of_channels_, sample_type_, sample_rate_,
-                 min_required_frames_);
+    std::vector<uint8_t> silence_buffer(max_required_frames_ *
+                                            task.number_of_channels *
+                                            GetSampleSize(task.sample_type),
+                                        0);
+    void* frame_buffers[1];
+    frame_buffers[0] = silence_buffer.data();
+
+    // Set default values.
+    min_required_frames_ = task.default_required_frames;
+    total_consumed_frames_ = 0;
+    last_underrun_count_ = -1;
+    last_total_consumed_frames_ = 0;
+
+    audio_sink_ = new AudioTrackAudioSink(
+        NULL, task.number_of_channels, task.sample_rate, task.sample_type,
+        frame_buffers, max_required_frames_,
+        min_required_frames_ * task.number_of_channels *
+            GetSampleSize(task.sample_type),
+        &MinRequiredFramesTester::UpdateSourceStatusFunc,
+        &MinRequiredFramesTester::ConsumeFramesFunc, this);
+    {
+      ScopedLock scoped_lock(mutex_);
+      wait_timeout = !condition_variable_.WaitTimed(kSbTimeSecond * 5);
+    }
+
+    if (wait_timeout) {
+      SB_LOG(ERROR) << "Audio sink min required frames tester timeout.";
+      SB_NOTREACHED();
+    }
+
+    delete audio_sink_;
+    audio_sink_ = nullptr;
+
+    // Call |received_cb_| after audio sink thread is ended.
+    // |min_required_frames_| is shared between two threads.
+    if (!destroying_.load() && !wait_timeout) {
+      task.received_cb(task.number_of_channels, task.sample_type,
+                       task.sample_rate, min_required_frames_);
+    }
   }
 }
 
diff --git a/src/starboard/android/shared/audio_sink_min_required_frames_tester.h b/src/starboard/android/shared/audio_sink_min_required_frames_tester.h
index 9e53134..290011d 100644
--- a/src/starboard/android/shared/audio_sink_min_required_frames_tester.h
+++ b/src/starboard/android/shared/audio_sink_min_required_frames_tester.h
@@ -17,6 +17,7 @@
 
 #include <atomic>
 #include <functional>
+#include <vector>
 
 #include "starboard/common/condition_variable.h"
 #include "starboard/common/mutex.h"
@@ -40,19 +41,39 @@
                              int min_required_frames)>
       OnMinRequiredFramesReceivedCallback;
 
-  MinRequiredFramesTester(int audio_sink_buffer_size,
-                          int max_required_frames,
-                          int default_required_frames,
+  MinRequiredFramesTester(int max_required_frames,
                           int required_frames_increment,
                           int min_stable_played_frames);
   ~MinRequiredFramesTester();
 
-  void StartTest(int number_of_channels,
-                 SbMediaAudioSampleType sample_type,
-                 int sample_rate,
-                 OnMinRequiredFramesReceivedCallback received_cb);
+  void AddTest(int number_of_channels,
+               SbMediaAudioSampleType sample_type,
+               int sample_rate,
+               const OnMinRequiredFramesReceivedCallback& received_cb,
+               int default_required_frames);
+
+  void Start();
 
  private:
+  struct TestTask {
+    TestTask(int number_of_channels,
+             SbMediaAudioSampleType sample_type,
+             int sample_rate,
+             OnMinRequiredFramesReceivedCallback received_cb,
+             int default_required_frames)
+        : number_of_channels(number_of_channels),
+          sample_type(sample_type),
+          sample_rate(sample_rate),
+          received_cb(received_cb),
+          default_required_frames(default_required_frames) {}
+
+    const int number_of_channels;
+    const SbMediaAudioSampleType sample_type;
+    const int sample_rate;
+    const OnMinRequiredFramesReceivedCallback received_cb;
+    const int default_required_frames;
+  };
+
   static void* TesterThreadEntryPoint(void* context);
   void TesterThreadFunc();
 
@@ -73,24 +94,16 @@
   MinRequiredFramesTester(const MinRequiredFramesTester&) = delete;
   MinRequiredFramesTester& operator=(const MinRequiredFramesTester&) = delete;
 
-  const int audio_sink_buffer_size_;
   const int max_required_frames_;
-  const int default_required_frames_;
   const int required_frames_increment_;
   const int min_stable_played_frames_;
 
   ::starboard::shared::starboard::ThreadChecker thread_checker_;
 
-  // Shared variables between tester thread and audio sink thread.
+  std::vector<const TestTask> test_tasks_;
   AudioTrackAudioSink* audio_sink_ = nullptr;
-  int number_of_channels_;
-  SbMediaAudioSampleType sample_type_;
-  int sample_rate_;
   int min_required_frames_;
 
-  // Used only by tester thread.
-  OnMinRequiredFramesReceivedCallback received_cb_;
-
   // Used only by audio sink thread.
   int total_consumed_frames_;
   int last_underrun_count_;
@@ -99,7 +112,7 @@
   Mutex mutex_;
   ConditionVariable condition_variable_;
   SbThread tester_thread_ = kSbThreadInvalid;
-  std::atomic_bool destroyed_;
+  std::atomic_bool destroying_;
 };
 
 }  // namespace shared
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.cc b/src/starboard/android/shared/audio_track_audio_sink_type.cc
index 994d904..363d4f9 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.cc
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -35,12 +35,13 @@
 const jint kNoOffset = 0;
 const size_t kSilenceFramesPerAppend = 1024;
 
-const int kAudioSinkBufferSize = 4 * 1024;
 const int kMaxRequiredFrames = 16 * 1024;
-const int kDefaultRequiredFrames = 8 * 1024;
 const int kRequiredFramesIncrement = 2 * 1024;
 const int kMinStablePlayedFrames = 12 * 1024;
 
+const int kSampleFrequency22k = 22050;
+const int kSampleFrequency48k = 48000;
+
 // Helper function to compute the size of the two valid starboard audio sample
 // types.
 size_t GetSampleSize(SbMediaAudioSampleType sample_type) {
@@ -80,7 +81,7 @@
     SbMediaAudioSampleType sample_type,
     SbAudioSinkFrameBuffers frame_buffers,
     int frames_per_channel,
-    int preferred_buffer_size,
+    int preferred_buffer_size_in_bytes,
     SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
     SbAudioSinkConsumeFramesFunc consume_frame_func,
     void* context)
@@ -113,7 +114,7 @@
       j_audio_output_manager.Get(), "createAudioTrackBridge",
       "(IIII)Ldev/cobalt/media/AudioTrackBridge;",
       GetAudioFormatSampleType(sample_type_), sampling_frequency_hz_, channels_,
-      preferred_buffer_size);
+      preferred_buffer_size_in_bytes);
   if (!j_audio_track_bridge) {
     return;
   }
@@ -358,16 +359,25 @@
     int sampling_frequency_hz) {
   SB_DCHECK(audio_track_audio_sink_type_);
 
-  return audio_track_audio_sink_type_->min_required_frames_.load();
+  JniEnvExt* env = JniEnvExt::Get();
+  ScopedLocalJavaRef<jobject> j_audio_output_manager(
+      env->CallStarboardObjectMethodOrAbort(
+          "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
+  int audio_track_min_buffer_size = static_cast<int>(env->CallIntMethodOrAbort(
+      j_audio_output_manager.Get(), "getMinBufferSize", "(III)I",
+      GetAudioFormatSampleType(sample_type), sampling_frequency_hz, channels));
+  int audio_track_min_buffer_size_in_frames =
+      audio_track_min_buffer_size / channels / GetSampleSize(sample_type);
+  return std::max(
+      audio_track_min_buffer_size_in_frames,
+      audio_track_audio_sink_type_->GetMinBufferSizeInFramesInternal(
+          channels, sample_type, sampling_frequency_hz));
 }
 
 AudioTrackAudioSinkType::AudioTrackAudioSinkType()
-    : min_required_frames_tester_(kAudioSinkBufferSize,
-                                  kMaxRequiredFrames,
-                                  kDefaultRequiredFrames,
+    : min_required_frames_tester_(kMaxRequiredFrames,
                                   kRequiredFramesIncrement,
-                                  kMinStablePlayedFrames),
-      min_required_frames_(kMaxRequiredFrames) {}
+                                  kMinStablePlayedFrames) {}
 
 SbAudioSink AudioTrackAudioSinkType::Create(
     int channels,
@@ -384,13 +394,13 @@
   // large buffer may cause AudioTrack not able to start. And Cobalt now write
   // no more than 1s of audio data and no more than 0.5 ahead to starboard
   // player, limit the buffer size to store at most 0.5s of audio data.
-  int preferred_buffer_size =
+  int preferred_buffer_size_in_bytes =
       std::min(frames_per_channel, sampling_frequency_hz / 2) * channels *
       GetSampleSize(audio_sample_type);
   AudioTrackAudioSink* audio_sink = new AudioTrackAudioSink(
       this, channels, sampling_frequency_hz, audio_sample_type, frame_buffers,
-      frames_per_channel, preferred_buffer_size, update_source_status_func,
-      consume_frames_func, context);
+      frames_per_channel, preferred_buffer_size_in_bytes,
+      update_source_status_func, consume_frames_func, context);
   if (!audio_sink->IsAudioTrackValid()) {
     SB_DLOG(ERROR)
         << "AudioTrackAudioSinkType::Create failed to create audio track";
@@ -407,7 +417,8 @@
         SB_LOG(INFO) << "Received min required frames " << min_required_frames
                      << " for " << number_of_channels << " channels, "
                      << sample_rate << "hz.";
-        min_required_frames_.store(min_required_frames);
+        ScopedLock lock(min_required_frames_map_mutex_);
+        min_required_frames_map_[sample_rate] = min_required_frames;
       };
 
   SbMediaAudioSampleType sample_type = kSbMediaAudioSampleTypeFloat32;
@@ -415,12 +426,33 @@
     sample_type = kSbMediaAudioSampleTypeInt16Deprecated;
     SB_DCHECK(SbAudioSinkIsAudioSampleTypeSupported(sample_type));
   }
+  min_required_frames_tester_.AddTest(2, sample_type, kSampleFrequency48k,
+                                      onMinRequiredFramesForWebAudioReceived,
+                                      8 * 1024);
+  min_required_frames_tester_.AddTest(2, sample_type, kSampleFrequency22k,
+                                      onMinRequiredFramesForWebAudioReceived,
+                                      4 * 1024);
+  min_required_frames_tester_.Start();
+}
 
-  // Currently, cobalt only use |min_required_frames_| for web audio, which
-  // only supports 48k mono or stereo sound. It should be fine now to only
-  // test 48k stereo sound.
-  min_required_frames_tester_.StartTest(2, sample_type, 48000,
-                                        onMinRequiredFramesForWebAudioReceived);
+int AudioTrackAudioSinkType::GetMinBufferSizeInFramesInternal(
+    int channels,
+    SbMediaAudioSampleType sample_type,
+    int sampling_frequency_hz) {
+  if (sampling_frequency_hz <= kSampleFrequency22k) {
+    ScopedLock lock(min_required_frames_map_mutex_);
+    if (min_required_frames_map_.find(kSampleFrequency22k) !=
+        min_required_frames_map_.end()) {
+      return min_required_frames_map_[kSampleFrequency22k];
+    }
+  } else if (sampling_frequency_hz <= kSampleFrequency48k) {
+    ScopedLock lock(min_required_frames_map_mutex_);
+    if (min_required_frames_map_.find(kSampleFrequency48k) !=
+        min_required_frames_map_.end()) {
+      return min_required_frames_map_[kSampleFrequency48k];
+    }
+  }
+  return kMaxRequiredFrames;
 }
 
 }  // namespace shared
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.h b/src/starboard/android/shared/audio_track_audio_sink_type.h
index 821a146..966fa27 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.h
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.h
@@ -17,6 +17,7 @@
 
 #include <atomic>
 #include <functional>
+#include <map>
 
 #include "starboard/android/shared/audio_sink_min_required_frames_tester.h"
 #include "starboard/android/shared/jni_env_ext.h"
@@ -68,8 +69,14 @@
   void TestMinRequiredFrames();
 
  private:
-  std::atomic_int min_required_frames_;
+  int GetMinBufferSizeInFramesInternal(int channels,
+                                       SbMediaAudioSampleType sample_type,
+                                       int sampling_frequency_hz);
+
   MinRequiredFramesTester min_required_frames_tester_;
+  Mutex min_required_frames_map_mutex_;
+  // The minimum frames required to avoid underruns of different frequencies.
+  std::map<int, int> min_required_frames_map_;
 };
 
 class AudioTrackAudioSink : public SbAudioSinkPrivate {
diff --git a/src/starboard/android/shared/configuration_constants.cc b/src/starboard/android/shared/configuration_constants.cc
new file mode 100644
index 0000000..5b3c886
--- /dev/null
+++ b/src/starboard/android/shared/configuration_constants.cc
@@ -0,0 +1,23 @@
+// 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.
+
+// This file defines all configuration constants for a platform.
+
+#include "starboard/configuration_constants.h"
+
+// Determines the alignment that allocations should have on this platform.
+const size_t kSbMallocAlignment = 16;
+
+// The maximum length of a name for a thread, including the NULL-terminator.
+const int32_t kSbMaxThreadNameLength = 16;
diff --git a/src/starboard/android/shared/configuration_public.h b/src/starboard/android/shared/configuration_public.h
index 7abc208..c5110f8 100644
--- a/src/starboard/android/shared/configuration_public.h
+++ b/src/starboard/android/shared/configuration_public.h
@@ -291,9 +291,6 @@
 // specify that.
 #define SB_NETWORK_IO_BUFFER_ALIGNMENT 16
 
-// Determines the alignment that allocations should have on this platform.
-#define SB_MALLOC_ALIGNMENT ((size_t)16U)
-
 // Determines the threshhold of allocation size that should be done with mmap
 // (if available), rather than allocated within the core heap.
 #define SB_DEFAULT_MMAP_THRESHOLD ((size_t)(256 * 1024U))
@@ -330,9 +327,6 @@
 // to not include here to decrease symbol pollution.
 #define SB_MAX_THREAD_LOCAL_KEYS 128
 
-// The maximum length of the name for a thread, including the NULL-terminator.
-#define SB_MAX_THREAD_NAME_LENGTH 16
-
 // --- Tuneable Parameters ---------------------------------------------------
 
 // Specifies the network receive buffer size in bytes, set via
diff --git a/src/starboard/android/shared/directory_get_next.cc b/src/starboard/android/shared/directory_get_next.cc
index f1e5b02..525fdf4 100644
--- a/src/starboard/android/shared/directory_get_next.cc
+++ b/src/starboard/android/shared/directory_get_next.cc
@@ -15,21 +15,24 @@
 #include "starboard/directory.h"
 
 #include <android/asset_manager.h>
+#include <string.h>
 
 #include "starboard/android/shared/directory_internal.h"
 #include "starboard/shared/iso/impl/directory_get_next.h"
 
-bool SbDirectoryGetNext(SbDirectory directory, SbDirectoryEntry* out_entry) {
-  if (directory && directory->asset_dir && out_entry) {
+bool SbDirectoryGetNext(SbDirectory directory,
+                        char* out_entry,
+                        size_t out_entry_size) {
+  if (directory && directory->asset_dir && out_entry &&
+      out_entry_size >= SB_FILE_MAX_NAME) {
     const char* file_name = AAssetDir_getNextFileName(directory->asset_dir);
     if (file_name == NULL) {
       return false;
     }
-    size_t size = SB_ARRAY_SIZE_INT(out_entry->name);
-    SbStringCopy(out_entry->name, file_name, size);
+    SbStringCopy(out_entry, file_name, out_entry_size);
     return true;
   }
 
-  return ::starboard::shared::iso::impl::SbDirectoryGetNext(directory,
-                                                            out_entry);
+  return ::starboard::shared::iso::impl::SbDirectoryGetNext(
+      directory, out_entry, out_entry_size);
 }
diff --git a/src/starboard/android/shared/gyp_configuration.gypi b/src/starboard/android/shared/gyp_configuration.gypi
index c06cc60..d5f23e0 100644
--- a/src/starboard/android/shared/gyp_configuration.gypi
+++ b/src/starboard/android/shared/gyp_configuration.gypi
@@ -20,7 +20,8 @@
     'target_os': 'android',
     'final_executable_type': 'shared_library',
     'gtest_target_type': 'shared_library',
-    'sb_widevine_platform' : 'android',
+    'sb_widevine_platform': 'android',
+    'sb_enable_benchmark': 1,
 
     'gl_type': 'system_gles2',
     'enable_remote_debugging': 0,
diff --git a/src/starboard/android/shared/media_decoder.cc b/src/starboard/android/shared/media_decoder.cc
index f1a3dfb..24ed8a4 100644
--- a/src/starboard/android/shared/media_decoder.cc
+++ b/src/starboard/android/shared/media_decoder.cc
@@ -343,7 +343,7 @@
     std::vector<int>* input_buffer_indices) {
   SB_DCHECK(media_codec_bridge_);
 
-  // During secure playback, and only secure playback, is is possible that our
+  // During secure playback, and only secure playback, it is possible that our
   // attempt to enqueue an input buffer will be rejected by MediaCodec because
   // we do not have a key yet.  In this case, we hold on to the input buffer
   // that we have already set up, and repeatedly attempt to enqueue it until
diff --git a/src/starboard/android/shared/player_components_impl.cc b/src/starboard/android/shared/player_components_impl.cc
index c2f52aa..117fd97 100644
--- a/src/starboard/android/shared/player_components_impl.cc
+++ b/src/starboard/android/shared/player_components_impl.cc
@@ -58,8 +58,11 @@
           return audio_decoder_impl.PassAs<AudioDecoder>();
         }
       } else if (audio_sample_info.codec == kSbMediaAudioCodecOpus) {
-        return scoped_ptr<AudioDecoder>(
+        scoped_ptr<OpusAudioDecoder> audio_decoder_impl(
             new OpusAudioDecoder(audio_sample_info));
+        if (audio_decoder_impl->is_valid()) {
+          return audio_decoder_impl.PassAs<AudioDecoder>();
+        }
       } else {
         SB_NOTREACHED();
       }
diff --git a/src/starboard/android/shared/starboard_platform.gypi b/src/starboard/android/shared/starboard_platform.gypi
index ad89435..d8fb10e 100644
--- a/src/starboard/android/shared/starboard_platform.gypi
+++ b/src/starboard/android/shared/starboard_platform.gypi
@@ -80,6 +80,7 @@
         'bionic/private/bionic_macros.h',
         'bionic/private/ErrnoRestorer.h',
         'configuration_public.h',
+        'configuration_constants.cc',
         'decode_target_create.cc',
         'decode_target_create.h',
         'decode_target_get_info.cc',
diff --git a/src/starboard/android/shared/video_decoder.cc b/src/starboard/android/shared/video_decoder.cc
index 227b54c..b3d542b 100644
--- a/src/starboard/android/shared/video_decoder.cc
+++ b/src/starboard/android/shared/video_decoder.cc
@@ -524,21 +524,16 @@
     if (has_new_texture) {
       updateTexImage(decode_target_->data->surface_texture);
 
+      decode_target_->data->info.planes[0].width = frame_width_;
+      decode_target_->data->info.planes[0].height = frame_height_;
+      decode_target_->data->info.width = frame_width_;
+      decode_target_->data->info.height = frame_height_;
+
       float matrix4x4[16];
       getTransformMatrix(decode_target_->data->surface_texture, matrix4x4);
       SetDecodeTargetContentRegionFromMatrix(
-          &decode_target_->data->info.planes[0].content_region, 1, 1,
-          matrix4x4);
-
-      // Mark the decode target's width and height as 1, so that the
-      // |content_region|'s coordinates will be interpreted as normalized
-      // coordinates.  This is nice because on Android we're never explicitly
-      // told the texture width/height, and we are only provided the content
-      // region via normalized coordinates.
-      decode_target_->data->info.planes[0].width = 1;
-      decode_target_->data->info.planes[0].height = 1;
-      decode_target_->data->info.width = 1;
-      decode_target_->data->info.height = 1;
+          &decode_target_->data->info.planes[0].content_region, frame_width_,
+          frame_height_, matrix4x4);
 
       if (!first_texture_received_) {
         first_texture_received_ = true;
diff --git a/src/starboard/benchmark/benchmark.gyp b/src/starboard/benchmark/benchmark.gyp
new file mode 100644
index 0000000..3e626a2
--- /dev/null
+++ b/src/starboard/benchmark/benchmark.gyp
@@ -0,0 +1,47 @@
+# 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.
+
+{
+  'targets': [
+    {
+      'target_name': 'benchmark',
+      'type': '<(final_executable_type)',
+      'defines': [
+        # This allows the benchmarks to include internal only header files.
+        'STARBOARD_IMPLEMENTATION',
+      ],
+      'sources': [
+        '<(DEPTH)/starboard/benchmark/memory_benchmark.cc',
+        '<(DEPTH)/starboard/benchmark/thread_benchmark.cc',
+        '<(DEPTH)/starboard/common/benchmark_main.cc',
+      ],
+      'dependencies': [
+        '<@(cobalt_platform_dependencies)',
+        '<(DEPTH)/starboard/starboard.gyp:starboard',
+        '<(DEPTH)/third_party/google_benchmark/google_benchmark.gyp:google_benchmark',
+      ],
+    },
+    {
+      'target_name': 'benchmark_deploy',
+      'type': 'none',
+      'dependencies': [
+        'benchmark',
+      ],
+      'variables': {
+        'executable_name': 'benchmark',
+      },
+      'includes': [ '<(DEPTH)/starboard/build/deploy.gypi' ],
+    },
+  ],
+}
diff --git a/src/starboard/benchmark/memory_benchmark.cc b/src/starboard/benchmark/memory_benchmark.cc
new file mode 100644
index 0000000..24e3cf1
--- /dev/null
+++ b/src/starboard/benchmark/memory_benchmark.cc
@@ -0,0 +1,62 @@
+// 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 "starboard/memory.h"
+
+#include "third_party/google_benchmark/include/benchmark/benchmark.h"
+
+namespace starboard {
+namespace benchmark {
+namespace {
+
+void BM_MemoryCopy(::benchmark::State& state) {
+  void* memory1 = SbMemoryAllocate(state.range(0));
+  void* memory2 = SbMemoryAllocate(state.range(0));
+
+  for (auto _ : state) {
+    SbMemoryCopy(memory1, memory2, state.range(0));
+    ::benchmark::ClobberMemory();
+  }
+  state.SetBytesProcessed(int64_t(state.iterations()) *
+                          int64_t(state.range(0)));
+
+  SbMemoryDeallocate(memory1);
+  SbMemoryDeallocate(memory2);
+}
+
+void BM_MemoryMove(::benchmark::State& state) {
+  void* memory1 = SbMemoryAllocate(state.range(0));
+  void* memory2 = SbMemoryAllocate(state.range(0));
+
+  for (auto _ : state) {
+    SbMemoryMove(memory1, memory2, state.range(0));
+    ::benchmark::ClobberMemory();
+  }
+  state.SetBytesProcessed(int64_t(state.iterations()) *
+                          int64_t(state.range(0)));
+
+  SbMemoryDeallocate(memory1);
+  SbMemoryDeallocate(memory2);
+}
+
+BENCHMARK(BM_MemoryCopy)->RangeMultiplier(4)->Range(16, 1024 * 1024);
+BENCHMARK(BM_MemoryCopy)
+    ->Arg(1024 * 1024)
+    ->DenseThreadRange(1, 4)
+    ->UseRealTime();
+BENCHMARK(BM_MemoryMove)->Arg(1024 * 1024);
+
+}  // namespace
+}  // namespace benchmark
+}  // namespace starboard
diff --git a/src/starboard/benchmark/thread_benchmark.cc b/src/starboard/benchmark/thread_benchmark.cc
new file mode 100644
index 0000000..d112ef2
--- /dev/null
+++ b/src/starboard/benchmark/thread_benchmark.cc
@@ -0,0 +1,36 @@
+// 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 "third_party/google_benchmark/include/benchmark/benchmark.h"
+
+namespace starboard {
+namespace benchmark {
+namespace {
+
+void BM_IntegerAccumulation(::benchmark::State& state) {
+  const int kIterations = 1024 * 1024;
+  for (auto _ : state) {
+    int x = 0;
+    for (int i = 0; i < kIterations; ++i) {
+      ::benchmark::DoNotOptimize(x += i);
+    }
+  }
+  state.SetItemsProcessed(kIterations);
+}
+
+BENCHMARK(BM_IntegerAccumulation)->DenseThreadRange(1, 8)->UseRealTime();
+
+}  // namespace
+}  // namespace benchmark
+}  // namespace starboard
diff --git a/src/starboard/build/base_configuration.gypi b/src/starboard/build/base_configuration.gypi
index 38b074f..c0dfb8b 100644
--- a/src/starboard/build/base_configuration.gypi
+++ b/src/starboard/build/base_configuration.gypi
@@ -121,6 +121,9 @@
     # Used to indicate that the player is filter based.
     'sb_filter_based_player%': 1,
 
+    # Used to enable benchmarks.
+    'sb_enable_benchmark%': 0,
+
     # This variable dictates whether a given gyp target should be compiled with
     # optimization flags for size vs. speed.
     'optimize_target_for_speed%': 0,
diff --git a/src/starboard/common/benchmark_main.cc b/src/starboard/common/benchmark_main.cc
new file mode 100644
index 0000000..d100a56
--- /dev/null
+++ b/src/starboard/common/benchmark_main.cc
@@ -0,0 +1,33 @@
+// 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 "starboard/client_porting/wrap_main/wrap_main.h"
+#include "starboard/event.h"
+#include "starboard/system.h"
+#include "third_party/google_benchmark/include/benchmark/benchmark.h"
+
+namespace {
+int RunAllBenchmarks(int argc, char** argv) {
+  ::benchmark::Initialize(&argc, argv);
+  ::benchmark::RunSpecifiedBenchmarks();
+  return 0;
+}
+}  // namespace
+
+// When we are building Evergreen we need to export SbEventHandle so that the
+// ELF loader can find and invoke it.
+#if SB_IS(EVERGREEN)
+SB_EXPORT
+#endif  // SB_IS(EVERGREEN)
+STARBOARD_WRAP_SIMPLE_MAIN(RunAllBenchmarks);
diff --git a/src/starboard/configuration.h b/src/starboard/configuration.h
index d650414..607a24f 100644
--- a/src/starboard/configuration.h
+++ b/src/starboard/configuration.h
@@ -180,6 +180,36 @@
 //  values are consumed, take a look at //starboard/sabi.
 #define SB_SABI_FILE_VERSION SB_EXPERIMENTAL_API_VERSION
 
+// Updates the API guarantees of SbMutexAcquireTry.
+// SbMutexAcquireTry now has undefined behavior when it is invoked on a mutex
+// that has already been locked by the calling thread. In addition, since
+// SbMutexAcquireTry was used in SbMutexDestroy, SbMutexDestroy now has
+// undefined behavior when invoked on a locked mutex.
+#define SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION SB_EXPERIMENTAL_API_VERSION
+
+// Migrate the Starboard configuration variables from macros to extern consts.
+//
+// The migration allows Cobalt to make platform level decisions at runtime
+// instead of compile time which lets us create a more comprehensive Cobalt
+// binary.
+//
+// This means Cobalt must remove all references to these macros that would not
+// translate well to constants, i.e. in compile time references or initializing
+// arrays. Therefore, we needed to change the functionality of the function
+// `SbDirectoryGetNext` in "starboard/directory.h". Because we do not want to
+// use variable length arrays, we pass in a c-string and length to the function
+// to achieve the same result as before when passing in a `SbDirectoryEntry`.
+//
+// A platform will define the extern constants declared in
+// "starboard/configuration_constants.h". The definitions are done in
+// "starboard/<PLATFORM_PATH>/configuration_constants.cc".
+//
+// The exact mapping between macros and extern variables can be found in
+// "starboard/shared/starboard/configuration_constants_compatibility_defines.h"
+// though the naming scheme is very nearly the same: the old SB_FOO macro will
+// always become the constant kSbFoo.
+#define SB_FEATURE_RUNTIME_CONFIGS_VERSION SB_EXPERIMENTAL_API_VERSION
+
 // --- Release Candidate Feature Defines -------------------------------------
 
 // --- Common Detected Features ----------------------------------------------
@@ -265,6 +295,14 @@
 // and all configurations.
 #include STARBOARD_CONFIGURATION_INCLUDE
 
+#if SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
+// After SB_FEATURE_RUNTIME_CONFIGS_VERSION, we start to use runtime constants
+// instead of macros for certain platform dependent configurations. This file
+// substitutes configuration macros for the corresponding runtime constants so
+// we don't reference these constants when they aren't defined.
+#include "starboard/shared/starboard/configuration_constants_compatibility_defines.h"
+#endif  // SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
 // --- Overridable Helper Macros ---------------------------------------------
 
 // The following macros can be overridden in STARBOARD_CONFIGURATION_INCLUDE
@@ -586,10 +624,30 @@
 #error "Your platform must define SB_MAX_THREAD_LOCAL_KEYS."
 #endif
 
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
+#if defined(SB_MALLOC_ALIGNMENT)
+#error \
+    "SB_MALLOC_ALIGNMENT should not be defined in Starboard " \
+"versions 12 and later. Instead, define kSbMallocAlignment in " \
+"starboard/<PLATFORM_PATH>/configuration_constants.cc."
+#endif
+
+#if defined(SB_MAX_THREAD_NAME_LENGTH)
+#error \
+    "SB_MAX_THREAD_NAME_LENGTH should not be defined in Starboard " \
+"versions 12 and later. Instead, define kSbMaxThreadNameLength in " \
+"starboard/<PLATFORM_PATH>/configuration_constants.cc."
+#endif
+
+#else  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
 #if !defined(SB_MAX_THREAD_NAME_LENGTH)
 #error "Your platform must define SB_MAX_THREAD_NAME_LENGTH."
 #endif
 
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
 #if (SB_API_VERSION < 12 && !defined(SB_HAS_MICROPHONE))
 #error \
     "Your platform must define SB_HAS_MICROPHONE in API versions 11 or earlier."
diff --git a/src/starboard/configuration_constants.h b/src/starboard/configuration_constants.h
new file mode 100644
index 0000000..1d772da
--- /dev/null
+++ b/src/starboard/configuration_constants.h
@@ -0,0 +1,36 @@
+// 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.
+
+// Module Overview: Starboard Configuration Variables module
+//
+// Declares all configuration variables we will need to use at runtime.
+// These variables describe the current platform in detail to allow cobalt to
+// make runtime decisions based on per platform configurations.
+
+#ifndef STARBOARD_CONFIGURATION_CONSTANTS_H_
+#define STARBOARD_CONFIGURATION_CONSTANTS_H_
+
+#include "starboard/types.h"
+
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
+// Determines the alignment that allocations should have on this platform.
+extern const size_t kSbMallocAlignment;
+
+// The maximum length of the name for a thread, including the NULL-terminator.
+extern const int32_t kSbMaxThreadNameLength;
+
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
+#endif  // STARBOARD_CONFIGURATION_CONSTANTS_H_
diff --git a/src/starboard/directory.h b/src/starboard/directory.h
index c9032b2..97ef448 100644
--- a/src/starboard/directory.h
+++ b/src/starboard/directory.h
@@ -34,11 +34,13 @@
 // A handle to an open directory stream.
 typedef struct SbDirectoryPrivate* SbDirectory;
 
+#if SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
 // Represents a directory entry.
 typedef struct SbDirectoryEntry {
   // The name of this directory entry.
   char name[SB_FILE_MAX_NAME];
 } SbDirectoryEntry;
+#endif  // SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
 
 // Well-defined value for an invalid directory stream handle.
 #define kSbDirectoryInvalid ((SbDirectory)NULL)
@@ -64,6 +66,24 @@
 // |directory|: The directory stream handle to close.
 SB_EXPORT bool SbDirectoryClose(SbDirectory directory);
 
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+// Populates |out_entry| with the next entry in the specified directory stream,
+// and moves the stream forward by one entry.
+//
+// This function returns |true| if there was a next directory, and |false|
+// at the end of the directory stream or if |out_entry_size| is smaller than
+// SB_FILE_MAX_NAME.
+//
+// |directory|: The directory stream from which to retrieve the next directory.
+// |out_entry|: The null terminated string to be populated with the next
+//              directory entry. The space allocated for this string should be
+//              equal to |out_entry_size|.
+// |out_entry_size|: The size of the space allocated for |out_entry|. This
+//                   should be at least equal to SB_FILE_MAX_NAME.
+SB_EXPORT bool SbDirectoryGetNext(SbDirectory directory,
+                                  char* out_entry,
+                                  size_t out_entry_size);
+#else   // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
 // Populates |out_entry| with the next entry in the specified directory stream,
 // and moves the stream forward by one entry.
 //
@@ -74,6 +94,7 @@
 // |out_entry|: The variable to be populated with the next directory entry.
 SB_EXPORT bool SbDirectoryGetNext(SbDirectory directory,
                                   SbDirectoryEntry* out_entry);
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
 
 // Indicates whether SbDirectoryOpen is allowed for the given |path|.
 //
diff --git a/src/starboard/linux/shared/configuration_constants.cc b/src/starboard/linux/shared/configuration_constants.cc
new file mode 100644
index 0000000..973063a
--- /dev/null
+++ b/src/starboard/linux/shared/configuration_constants.cc
@@ -0,0 +1,27 @@
+// 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.
+
+// This file defines all configuration constants for a platform.
+
+#include "starboard/configuration_constants.h"
+
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
+// Determines the alignment that allocations should have on this platform.
+const size_t kSbMallocAlignment = 16;
+
+// The maximum length of a name for a thread, including the NULL-terminator.
+const int32_t kSbMaxThreadNameLength = 16;
+
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
diff --git a/src/starboard/linux/shared/configuration_public.h b/src/starboard/linux/shared/configuration_public.h
index b3aeff7..485753c 100644
--- a/src/starboard/linux/shared/configuration_public.h
+++ b/src/starboard/linux/shared/configuration_public.h
@@ -287,8 +287,10 @@
 // specify that.
 #define SB_NETWORK_IO_BUFFER_ALIGNMENT 16
 
+#if SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
 // Determines the alignment that allocations should have on this platform.
 #define SB_MALLOC_ALIGNMENT ((size_t)16U)
+#endif  // SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
 
 // Determines the threshhold of allocation size that should be done with mmap
 // (if available), rather than allocated within the core heap.
@@ -319,8 +321,10 @@
 // The maximum number of thread local storage keys supported by this platform.
 #define SB_MAX_THREAD_LOCAL_KEYS 512
 
+#if SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
 // The maximum length of the name for a thread, including the NULL-terminator.
 #define SB_MAX_THREAD_NAME_LENGTH 16
+#endif  // SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
 
 // --- Tuneable Parameters ---------------------------------------------------
 
diff --git a/src/starboard/linux/shared/gyp_configuration.gypi b/src/starboard/linux/shared/gyp_configuration.gypi
index b224935..b565543 100644
--- a/src/starboard/linux/shared/gyp_configuration.gypi
+++ b/src/starboard/linux/shared/gyp_configuration.gypi
@@ -25,6 +25,8 @@
     'target_os': 'linux',
     'yasm_exists': 1,
     'sb_widevine_platform' : 'linux',
+    'sb_disable_opus_sse': 1,
+    'sb_enable_benchmark': 1,
 
     'platform_libraries': [
       '-lasound',
@@ -45,14 +47,14 @@
       'use_dlmalloc_allocator%': 0,
     },
     'conditions': [
-        ['sb_evergreen != 1', {
-          # TODO: allow starboard_platform to use system libc/libc++ in the
-          # future. For now, if this flags is enabled, a warning emerge saying
-          # it's unused anyway.
-          'linker_flags': [
-            '-static-libstdc++',
-          ],
-        }],
+      ['sb_evergreen != 1', {
+        # TODO: allow starboard_platform to use system libc/libc++ in the
+        # future. For now, if this flags is enabled, a warning emerge saying
+        # it's unused anyway.
+        'linker_flags': [
+          '-static-libstdc++',
+        ],
+      }],
     ],
   },
 
diff --git a/src/starboard/linux/shared/launcher.py b/src/starboard/linux/shared/launcher.py
index 662e68d..e5ba8f1 100644
--- a/src/starboard/linux/shared/launcher.py
+++ b/src/starboard/linux/shared/launcher.py
@@ -25,6 +25,7 @@
 
 import _env  # pylint: disable=unused-import
 from starboard.tools import abstract_launcher
+from starboard.tools import send_link
 
 STATUS_CHANGE_TIMEOUT = 15
 
@@ -124,6 +125,15 @@
     else:
       sys.stderr.write("Cannot send suspend to executable; it is closed.\n")
 
+  def SupportsDeepLink(self):
+    return True
+
+  def SendDeepLink(self, link):
+    # The connect call in SendLink occassionally fails. Retry a few times if this happens.
+    connection_attempts = 3
+    return send_link.SendLink(
+        os.path.basename(self.executable), link, connection_attempts)
+
   def WaitForProcessStatus(self, target_status, timeout):
     """Wait for Cobalt to turn to target status within specified timeout limit.
 
diff --git a/src/starboard/linux/shared/player_components_impl.cc b/src/starboard/linux/shared/player_components_impl.cc
index 53652ef..20baaaf 100644
--- a/src/starboard/linux/shared/player_components_impl.cc
+++ b/src/starboard/linux/shared/player_components_impl.cc
@@ -25,6 +25,7 @@
 #include "starboard/shared/libaom/aom_video_decoder.h"
 #include "starboard/shared/libde265/de265_video_decoder.h"
 #include "starboard/shared/libvpx/vpx_video_decoder.h"
+#include "starboard/shared/opus/opus_audio_decoder.h"
 #include "starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.h"
 #include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
 #include "starboard/shared/starboard/player/filter/audio_renderer_sink.h"
@@ -52,15 +53,25 @@
     SB_DCHECK(audio_decoder);
     SB_DCHECK(audio_renderer_sink);
 
+    typedef ::starboard::shared::ffmpeg::AudioDecoder FfmpegAudioDecoder;
+    typedef ::starboard::shared::opus::OpusAudioDecoder OpusAudioDecoder;
+
 #if SB_API_VERSION >= 11
     auto decoder_creator = [](const SbMediaAudioSampleInfo& audio_sample_info,
                               SbDrmSystem drm_system) {
-      typedef ::starboard::shared::ffmpeg::AudioDecoder AudioDecoderImpl;
-
-      scoped_ptr<AudioDecoderImpl> audio_decoder_impl(
-          AudioDecoderImpl::Create(audio_sample_info.codec, audio_sample_info));
-      if (audio_decoder_impl && audio_decoder_impl->is_valid()) {
-        return audio_decoder_impl.PassAs<AudioDecoder>();
+      if (audio_sample_info.codec == kSbMediaAudioCodecOpus) {
+        scoped_ptr<OpusAudioDecoder> audio_decoder_impl(
+            new OpusAudioDecoder(audio_sample_info));
+        if (audio_decoder_impl->is_valid()) {
+          return audio_decoder_impl.PassAs<AudioDecoder>();
+        }
+      } else {
+        scoped_ptr<FfmpegAudioDecoder> audio_decoder_impl(
+            FfmpegAudioDecoder::Create(audio_sample_info.codec,
+                                       audio_sample_info));
+        if (audio_decoder_impl && audio_decoder_impl->is_valid()) {
+          return audio_decoder_impl.PassAs<AudioDecoder>();
+        }
       }
       return scoped_ptr<AudioDecoder>();
     };
@@ -69,14 +80,23 @@
         new AdaptiveAudioDecoder(audio_parameters.audio_sample_info,
                                  audio_parameters.drm_system, decoder_creator));
 #else   // SB_API_VERSION >= 11
-    typedef ::starboard::shared::ffmpeg::AudioDecoder AudioDecoderImpl;
-
-    scoped_ptr<AudioDecoderImpl> audio_decoder_impl(AudioDecoderImpl::Create(
-        audio_parameters.audio_codec, audio_parameters.audio_sample_info));
-    if (audio_decoder_impl && audio_decoder_impl->is_valid()) {
-      audio_decoder->reset(audio_decoder_impl.release());
+    if (audio_parameters.audio_codec == kSbMediaAudioCodecOpus) {
+      scoped_ptr<OpusAudioDecoder> audio_decoder_impl(
+          new OpusAudioDecoder(audio_parameters.audio_sample_info));
+      if (audio_decoder_impl && audio_decoder_impl->is_valid()) {
+        audio_decoder->reset(audio_decoder_impl.release());
+      } else {
+        audio_decoder->reset();
+      }
     } else {
-      audio_decoder->reset();
+      scoped_ptr<FfmpegAudioDecoder> audio_decoder_impl(
+          FfmpegAudioDecoder::Create(audio_parameters.audio_codec,
+                                     audio_parameters.audio_sample_info));
+      if (audio_decoder_impl && audio_decoder_impl->is_valid()) {
+        audio_decoder->reset(audio_decoder_impl.release());
+      } else {
+        audio_decoder->reset();
+      }
     }
 #endif  // SB_API_VERSION >= 11
     audio_renderer_sink->reset(new AudioRendererSinkImpl);
diff --git a/src/starboard/linux/shared/starboard_platform.gypi b/src/starboard/linux/shared/starboard_platform.gypi
index d335946..b9286b3 100644
--- a/src/starboard/linux/shared/starboard_platform.gypi
+++ b/src/starboard/linux/shared/starboard_platform.gypi
@@ -31,6 +31,7 @@
       '<(DEPTH)/starboard/linux/shared/command_line_defaults.cc',
       '<(DEPTH)/starboard/linux/shared/command_line_defaults.h',
       '<(DEPTH)/starboard/linux/shared/configuration_public.h',
+      '<(DEPTH)/starboard/linux/shared/configuration_constants.cc',
       '<(DEPTH)/starboard/linux/shared/decode_target_get_info.cc',
       '<(DEPTH)/starboard/linux/shared/decode_target_internal.cc',
       '<(DEPTH)/starboard/linux/shared/decode_target_internal.h',
@@ -125,6 +126,8 @@
       '<(DEPTH)/starboard/shared/nouser/user_get_property.cc',
       '<(DEPTH)/starboard/shared/nouser/user_get_signed_in.cc',
       '<(DEPTH)/starboard/shared/nouser/user_internal.cc',
+      '<(DEPTH)/starboard/shared/opus/opus_audio_decoder.cc',
+      '<(DEPTH)/starboard/shared/opus/opus_audio_decoder.h',
       '<(DEPTH)/starboard/shared/posix/directory_create.cc',
       '<(DEPTH)/starboard/shared/posix/file_atomic_replace.cc',
       '<(DEPTH)/starboard/shared/posix/file_can_open.cc',
@@ -382,6 +385,7 @@
       '<(DEPTH)/third_party/de265_includes/de265_includes.gyp:de265',
       '<(DEPTH)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc',
       '<(DEPTH)/third_party/libevent/libevent.gyp:libevent',
+      '<(DEPTH)/third_party/opus/opus.gyp:opus',
       '<(DEPTH)/third_party/pulseaudio_includes/pulseaudio_includes.gyp:pulseaudio',
     ],
     'conditions': [
diff --git a/src/starboard/mutex.h b/src/starboard/mutex.h
index 0f6226e..afd8927 100644
--- a/src/starboard/mutex.h
+++ b/src/starboard/mutex.h
@@ -54,10 +54,17 @@
 // |out_mutex|: The handle to the newly created mutex.
 SB_EXPORT bool SbMutexCreate(SbMutex* out_mutex);
 
+#if SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+// Destroys a mutex. The return value indicates whether the destruction was
+// successful. Destroying a locked mutex results in undefined behavior.
+//
+// |mutex|: The mutex to be invalidated.
+#else   // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
 // Destroys a mutex. The return value indicates whether the destruction was
 // successful.
 //
 // |mutex|: The mutex to be invalidated.
+#endif  // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
 SB_EXPORT bool SbMutexDestroy(SbMutex* mutex);
 
 // Acquires |mutex|, blocking indefinitely. The return value identifies
@@ -67,11 +74,19 @@
 // |mutex|: The mutex to be acquired.
 SB_EXPORT SbMutexResult SbMutexAcquire(SbMutex* mutex);
 
+#if SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+// Acquires |mutex|, without blocking. The return value identifies
+// the acquisition result. SbMutexes are not reentrant, so a recursive
+// acquisition has undefined behavior.
+//
+// |mutex|: The mutex to be acquired.
+#else   // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
 // Acquires |mutex|, without blocking. The return value identifies
 // the acquisition result. SbMutexes are not reentrant, so a recursive
 // acquisition always fails.
 //
 // |mutex|: The mutex to be acquired.
+#endif  // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
 SB_EXPORT SbMutexResult SbMutexAcquireTry(SbMutex* mutex);
 
 // Releases |mutex| held by the current thread. The return value indicates
diff --git a/src/starboard/nplb/audio_sink_helpers.cc b/src/starboard/nplb/audio_sink_helpers.cc
index 082ce62..b0a0d9c 100644
--- a/src/starboard/nplb/audio_sink_helpers.cc
+++ b/src/starboard/nplb/audio_sink_helpers.cc
@@ -14,6 +14,8 @@
 
 #include "starboard/nplb/audio_sink_helpers.h"
 
+#include <algorithm>
+
 #include "starboard/common/log.h"
 
 namespace starboard {
@@ -107,13 +109,12 @@
 
 void AudioSinkTestEnvironment::AppendFrame(int frames_to_append) {
   ScopedLock lock(mutex_);
-  frames_appended_ += frames_to_append;
+  AppendFrame_Locked(frames_to_append);
 }
 
-int AudioSinkTestEnvironment::GetFrameBufferFreeSpaceAmount() const {
+int AudioSinkTestEnvironment::GetFrameBufferFreeSpaceInFrames() const {
   ScopedLock lock(mutex_);
-  int frames_in_buffer = frames_appended_ - frames_consumed_;
-  return frame_buffers_.frames_per_channel() - frames_in_buffer;
+  return GetFrameBufferFreeSpaceInFrames_Locked();
 }
 
 bool AudioSinkTestEnvironment::WaitUntilUpdateStatusCalled() {
@@ -147,20 +148,48 @@
 }
 
 bool AudioSinkTestEnvironment::WaitUntilAllFramesAreConsumed() {
+  const int kMaximumFramesPerAppend = 1024;
+
   ScopedLock lock(mutex_);
   is_eos_reached_ = true;
+  int frames_appended_before_eos = frames_appended_;
   SbTimeMonotonic start = SbTimeGetMonotonicNow();
-  while (frames_appended_ != frames_consumed_) {
+  int silence_frames_appended = 0;
+
+  while (frames_consumed_ < frames_appended_before_eos) {
     SbTime time_elapsed = SbTimeGetMonotonicNow() - start;
     if (time_elapsed >= kTimeToTry) {
       return false;
     }
     SbTime time_to_wait = kTimeToTry - time_elapsed;
+
+    // Append silence as some audio sink implementations won't be able to finish
+    // playback to the last frames filled.
+    int silence_frames_to_append =
+        std::min({GetFrameBufferFreeSpaceInFrames_Locked(),
+                  frame_buffers_.frames_per_channel() - silence_frames_appended,
+                  kMaximumFramesPerAppend});
+    AppendFrame_Locked(silence_frames_to_append);
+    silence_frames_appended += silence_frames_to_append;
+
     condition_variable_.WaitTimed(time_to_wait);
   }
   return true;
 }
 
+void AudioSinkTestEnvironment::AppendFrame_Locked(int frames_to_append) {
+  mutex_.DCheckAcquired();
+
+  frames_appended_ += frames_to_append;
+}
+
+int AudioSinkTestEnvironment::GetFrameBufferFreeSpaceInFrames_Locked() const {
+  mutex_.DCheckAcquired();
+
+  int frames_in_buffer = frames_appended_ - frames_consumed_;
+  return frame_buffers_.frames_per_channel() - frames_in_buffer;
+}
+
 void AudioSinkTestEnvironment::OnUpdateSourceStatus(int* frames_in_buffer,
                                                     int* offset_in_frames,
                                                     bool* is_playing,
diff --git a/src/starboard/nplb/audio_sink_helpers.h b/src/starboard/nplb/audio_sink_helpers.h
index 215ce94..72921e0 100644
--- a/src/starboard/nplb/audio_sink_helpers.h
+++ b/src/starboard/nplb/audio_sink_helpers.h
@@ -81,7 +81,7 @@
   }
   void SetIsPlaying(bool is_playing);
   void AppendFrame(int frames_to_append);
-  int GetFrameBufferFreeSpaceAmount() const;
+  int GetFrameBufferFreeSpaceInFrames() const;
 
   // The following functions return true when the expected condition are met.
   // Return false on timeout.
@@ -90,6 +90,8 @@
   bool WaitUntilAllFramesAreConsumed();
 
  private:
+  void AppendFrame_Locked(int frames_to_append);
+  int GetFrameBufferFreeSpaceInFrames_Locked() const;
   void OnUpdateSourceStatus(int* frames_in_buffer,
                             int* offset_in_frames,
                             bool* is_playing,
diff --git a/src/starboard/nplb/audio_sink_test.cc b/src/starboard/nplb/audio_sink_test.cc
index caa7870..e6c059b 100644
--- a/src/starboard/nplb/audio_sink_test.cc
+++ b/src/starboard/nplb/audio_sink_test.cc
@@ -66,8 +66,8 @@
   environment.AppendFrame(frames_to_append);
 
   EXPECT_TRUE(environment.WaitUntilSomeFramesAreConsumed());
-  ASSERT_GT(environment.GetFrameBufferFreeSpaceAmount(), 0);
-  environment.AppendFrame(environment.GetFrameBufferFreeSpaceAmount());
+  ASSERT_GT(environment.GetFrameBufferFreeSpaceInFrames(), 0);
+  environment.AppendFrame(environment.GetFrameBufferFreeSpaceInFrames());
   EXPECT_TRUE(environment.WaitUntilAllFramesAreConsumed());
 }
 
@@ -82,10 +82,10 @@
   int frames_to_append = frame_buffers.frames_per_channel();
   environment.AppendFrame(frames_to_append);
 
-  int free_space = environment.GetFrameBufferFreeSpaceAmount();
+  int free_space = environment.GetFrameBufferFreeSpaceInFrames();
   EXPECT_TRUE(environment.WaitUntilUpdateStatusCalled());
   EXPECT_TRUE(environment.WaitUntilUpdateStatusCalled());
-  EXPECT_EQ(free_space, environment.GetFrameBufferFreeSpaceAmount());
+  EXPECT_EQ(free_space, environment.GetFrameBufferFreeSpaceInFrames());
   environment.SetIsPlaying(true);
   EXPECT_TRUE(environment.WaitUntilSomeFramesAreConsumed());
 }
@@ -101,8 +101,8 @@
 
   EXPECT_TRUE(environment.WaitUntilSomeFramesAreConsumed());
   SbThreadSleep(250 * kSbTimeMillisecond);
-  ASSERT_GT(environment.GetFrameBufferFreeSpaceAmount(), 0);
-  environment.AppendFrame(environment.GetFrameBufferFreeSpaceAmount());
+  ASSERT_GT(environment.GetFrameBufferFreeSpaceInFrames(), 0);
+  environment.AppendFrame(environment.GetFrameBufferFreeSpaceInFrames());
   EXPECT_TRUE(environment.WaitUntilAllFramesAreConsumed());
 }
 
@@ -117,7 +117,7 @@
   int frames_to_append = sample_rate / 4;
 
   while (frames_to_append > 0) {
-    int free_space = environment.GetFrameBufferFreeSpaceAmount();
+    int free_space = environment.GetFrameBufferFreeSpaceInFrames();
     environment.AppendFrame(std::min(free_space, frames_to_append));
     frames_to_append -= std::min(free_space, frames_to_append);
     ASSERT_TRUE(environment.WaitUntilSomeFramesAreConsumed());
diff --git a/src/starboard/nplb/condition_variable_wait_test.cc b/src/starboard/nplb/condition_variable_wait_test.cc
index 3e42109..6586d6c 100644
--- a/src/starboard/nplb/condition_variable_wait_test.cc
+++ b/src/starboard/nplb/condition_variable_wait_test.cc
@@ -52,7 +52,7 @@
   const int kMany = SB_MAX_THREADS > 64 ? 64 : SB_MAX_THREADS;
   WaiterContext context;
 
-  SbThread threads[kMany];
+  std::vector<SbThread> threads(kMany);
   for (int i = 0; i < kMany; ++i) {
     threads[i] = SbThreadCreate(0, kSbThreadNoPriority, kSbThreadNoAffinity,
                                 true, NULL, WaiterEntryPoint, &context);
diff --git a/src/starboard/nplb/directory_get_next_test.cc b/src/starboard/nplb/directory_get_next_test.cc
index 24c05ba..80a61ea 100644
--- a/src/starboard/nplb/directory_get_next_test.cc
+++ b/src/starboard/nplb/directory_get_next_test.cc
@@ -50,17 +50,26 @@
   StringSet names_to_find(names);
   int count = 0;
   while (true) {
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+    std::vector<char> entry(SB_FILE_MAX_NAME, 0);
+    if (!SbDirectoryGetNext(directory, entry.data(), entry.size())) {
+      break;
+    }
+    const char* entry_name = entry.data();
+#else   // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
     SbDirectoryEntry entry = {0};
     if (!SbDirectoryGetNext(directory, &entry)) {
       break;
     }
+    const char* entry_name = entry.name;
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
 
     // SbDirectoryEntry just contains the last component of the absolute path to
     // the file, but ScopedRandomFile::filename() returns the full path.
     std::string filename;
     filename += directory_name;
     filename += SB_FILE_SEP_CHAR;
-    filename += entry.name;
+    filename += entry_name;
 
     StringSet::iterator iterator = names_to_find.find(filename);
     if (iterator != names_to_find.end()) {
@@ -68,7 +77,7 @@
     } else {
       // If it isn't in |names_to_find|, make sure it's some external entry and
       // not one of ours. Otherwise, an entry must have shown up twice.
-      EXPECT_TRUE(names.find(entry.name) == names.end());
+      EXPECT_TRUE(names.find(entry_name) == names.end());
     }
   }
 
@@ -79,8 +88,14 @@
 }
 
 TEST(SbDirectoryGetNextTest, FailureInvalidSbDirectory) {
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+  std::vector<char> entry(SB_FILE_MAX_NAME, 0);
+  EXPECT_FALSE(SbDirectoryGetNext(kSbDirectoryInvalid, entry.data(),
+                                  entry.size()));
+#else  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
   SbDirectoryEntry entry = {0};
   EXPECT_FALSE(SbDirectoryGetNext(kSbDirectoryInvalid, &entry));
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
 }
 
 TEST(SbDirectoryGetNextTest, FailureNullEntry) {
@@ -95,12 +110,20 @@
   SbDirectory directory = SbDirectoryOpen(path.c_str(), &error);
   EXPECT_TRUE(SbDirectoryIsValid(directory));
   EXPECT_EQ(kSbFileOk, error);
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+  EXPECT_FALSE(SbDirectoryGetNext(directory, NULL, SB_FILE_MAX_NAME));
+#else   // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
   EXPECT_FALSE(SbDirectoryGetNext(directory, NULL));
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
   EXPECT_TRUE(SbDirectoryClose(directory));
 }
 
 TEST(SbDirectoryGetNextTest, FailureInvalidAndNull) {
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+  EXPECT_FALSE(SbDirectoryGetNext(kSbDirectoryInvalid, NULL, SB_FILE_MAX_NAME));
+#else   // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
   EXPECT_FALSE(SbDirectoryGetNext(kSbDirectoryInvalid, NULL));
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
 }
 
 TEST(SbDirectoryGetNextTest, FailureOnEmptyDirectory) {
@@ -112,11 +135,42 @@
   ASSERT_TRUE(SbDirectoryIsValid(directory));
   ASSERT_EQ(kSbFileOk, error);
 
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+  std::vector<char> entry(SB_FILE_MAX_NAME, 0);
+  EXPECT_FALSE(SbDirectoryGetNext(directory, entry.data(), entry.size()));
+#else   // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
   SbDirectoryEntry entry = {0};
   EXPECT_FALSE(SbDirectoryGetNext(directory, &entry));
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
   ASSERT_TRUE(SbDirectoryClose(directory));
 }
 
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+TEST(SbDirectoryGetNextTest, FailureOnInsufficientSize) {
+  ScopedRandomFile file;
+  std::string directory_name = file.filename();
+  directory_name.resize(directory_name.find_last_of(SB_FILE_SEP_CHAR));
+  EXPECT_TRUE(SbFileExists(directory_name.c_str()))
+      << "Directory_name is " << directory_name;
+
+  SbFileError error = kSbFileErrorMax;
+  SbDirectory directory = SbDirectoryOpen(directory_name.c_str(), &error);
+  EXPECT_TRUE(SbDirectoryIsValid(directory));
+  EXPECT_EQ(kSbFileOk, error);
+
+  std::vector<char> entry(SB_FILE_MAX_NAME);
+  for (int i = 0; i < SB_FILE_MAX_NAME; i++)
+    entry[i] = i;
+  std::vector<char> entry_copy = entry;
+  EXPECT_EQ(SbDirectoryGetNext(directory, entry.data(), 0), false);
+  EXPECT_EQ(entry.size(), SB_FILE_MAX_NAME);
+  for (int i = 0; i < SB_FILE_MAX_NAME; i++)
+    EXPECT_EQ(entry[i], entry_copy[i]);
+
+  EXPECT_TRUE(SbDirectoryClose(directory));
+}
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
 }  // namespace
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/directory_open_test.cc b/src/starboard/nplb/directory_open_test.cc
index 5b43367..f9d3155 100644
--- a/src/starboard/nplb/directory_open_test.cc
+++ b/src/starboard/nplb/directory_open_test.cc
@@ -55,16 +55,16 @@
   EXPECT_FILE_EXISTS(path);
 
   const int kMany = SB_FILE_MAX_OPEN;
-  SbDirectory directories[kMany] = {0};
+  std::vector<SbDirectory> directories(kMany, 0);
 
-  for (int i = 0; i < SB_ARRAY_SIZE_INT(directories); ++i) {
+  for (int i = 0; i < directories.size(); ++i) {
     SbFileError error = kSbFileErrorMax;
     directories[i] = SbDirectoryOpen(path.c_str(), &error);
     EXPECT_TRUE(SbDirectoryIsValid(directories[i]));
     EXPECT_EQ(kSbFileOk, error);
   }
 
-  for (int i = 0; i < SB_ARRAY_SIZE_INT(directories); ++i) {
+  for (int i = 0; i < directories.size(); ++i) {
     EXPECT_TRUE(SbDirectoryClose(directories[i]));
   }
 }
diff --git a/src/starboard/nplb/flat_map_test.cc b/src/starboard/nplb/flat_map_test.cc
index dde8625..4564290 100644
--- a/src/starboard/nplb/flat_map_test.cc
+++ b/src/starboard/nplb/flat_map_test.cc
@@ -90,11 +90,14 @@
 }
 
 SbTimeMonotonic GetThreadTimeMonotonicNow() {
-#if SB_HAS(TIME_THREAD_NOW)
-  return SbTimeGetMonotonicThreadNow();
-#else
-  return SbTimeGetMonotonicNow();
+#if SB_API_VERSION >= SB_TIME_THREAD_NOW_REQUIRED_VERSION || \
+    SB_HAS(TIME_THREAD_NOW)
+#if SB_API_VERSION >= SB_TIME_THREAD_NOW_REQUIRED_VERSION
+  if (SbTimeIsTimeThreadNowSupported())
 #endif
+    return SbTimeGetMonotonicThreadNow();
+#endif
+  return SbTimeGetMonotonicNow();
 }
 
 // Generic stringification of the input map type. This allows good error
diff --git a/src/starboard/nplb/mutex_acquire_try_test.cc b/src/starboard/nplb/mutex_acquire_try_test.cc
index d424f1c..806f5f8 100644
--- a/src/starboard/nplb/mutex_acquire_try_test.cc
+++ b/src/starboard/nplb/mutex_acquire_try_test.cc
@@ -16,10 +16,30 @@
 #include "starboard/configuration.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+#include "starboard/nplb/thread_helpers.h"
+#include "starboard/thread.h"
+#endif  // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+
 namespace starboard {
 namespace nplb {
 namespace {
 
+#if SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+struct TestContext {
+  explicit TestContext(SbMutex* mutex) : was_locked_(false), mutex_(mutex) {}
+  bool was_locked_;
+  SbMutex* mutex_;
+};
+
+void* EntryPoint(void* parameter) {
+  TestContext* context = static_cast<TestContext*>(parameter);
+  context->was_locked_ =
+      (SbMutexAcquireTry(context->mutex_) == kSbMutexAcquired);
+  return NULL;
+}
+#endif  // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+
 TEST(SbMutexAcquireTryTest, SunnyDayUncontended) {
   SbMutex mutex;
   EXPECT_TRUE(SbMutexCreate(&mutex));
@@ -46,9 +66,20 @@
   EXPECT_EQ(result, kSbMutexAcquired);
   EXPECT_TRUE(SbMutexIsSuccess(result));
 
+#if SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+  TestContext context(&mutex);
+  SbThread thread =
+      SbThreadCreate(0, kSbThreadNoPriority, kSbThreadNoAffinity, true,
+                     nplb::kThreadName, &EntryPoint, &context);
+
+  EXPECT_TRUE(SbThreadIsValid(thread));
+  EXPECT_TRUE(SbThreadJoin(thread, NULL));
+  EXPECT_FALSE(context.was_locked_);
+#else   // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
   result = SbMutexAcquireTry(&mutex);
   EXPECT_EQ(result, kSbMutexBusy);
   EXPECT_FALSE(SbMutexIsSuccess(result));
+#endif  // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
 
   EXPECT_TRUE(SbMutexRelease(&mutex));
   EXPECT_TRUE(SbMutexDestroy(&mutex));
diff --git a/src/starboard/nplb/mutex_destroy_test.cc b/src/starboard/nplb/mutex_destroy_test.cc
index 92395b6..02e072d 100644
--- a/src/starboard/nplb/mutex_destroy_test.cc
+++ b/src/starboard/nplb/mutex_destroy_test.cc
@@ -27,6 +27,10 @@
   EXPECT_TRUE(SbMutexDestroy(&mutex));
 }
 
+#if SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+// Destroying a mutex that has already been destroyed is undefined behavior
+// and cannot be tested.
+#else   // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
 TEST(SbMutexDestroyTest, RainyDayDestroyHeld) {
   SbMutex mutex;
   EXPECT_TRUE(SbMutexCreate(&mutex));
@@ -39,6 +43,7 @@
   EXPECT_TRUE(SbMutexRelease(&mutex));
   EXPECT_TRUE(SbMutexDestroy(&mutex));
 }
+#endif  // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
 
 TEST(SbMutexDestroyTest, RainyDayNull) {
   EXPECT_FALSE(SbMutexDestroy(NULL));
diff --git a/src/starboard/nplb/nplb.gyp b/src/starboard/nplb/nplb.gyp
index cd280b6..6739ebe 100644
--- a/src/starboard/nplb/nplb.gyp
+++ b/src/starboard/nplb/nplb.gyp
@@ -165,6 +165,7 @@
         'memory_map_test.cc',
         'memory_move_test.cc',
         'memory_reallocate_test.cc',
+        'memory_reporter_test.cc',
         'memory_set_test.cc',
         'microphone_close_test.cc',
         'microphone_create_test.cc',
@@ -314,8 +315,6 @@
       'conditions': [
         ['sb_evergreen != 1', {
           'sources': [
-            # Segfaults for Cobalt Evergreen.
-            'memory_reporter_test.cc',
             # Segfaults or causes unresolved symbols for Cobalt Evergreen.
             'media_set_audio_write_duration_test.cc',
           ],
diff --git a/src/starboard/nplb/once_test.cc b/src/starboard/nplb/once_test.cc
index 0885860..b2743b8 100644
--- a/src/starboard/nplb/once_test.cc
+++ b/src/starboard/nplb/once_test.cc
@@ -98,7 +98,7 @@
 // routine got called exactly one time.
 TEST(SbOnceTest, SunnyDayMultipleThreadsInit) {
   const int kMany = SB_MAX_THREADS;
-  SbThread threads[kMany];
+  std::vector<SbThread> threads(kMany);
 
   const int kIterationCount = 10;
   for (int i = 0; i < kIterationCount; ++i) {
diff --git a/src/starboard/nplb/sabi/sabi.gypi b/src/starboard/nplb/sabi/sabi.gypi
index 3d76231..19c69ed 100644
--- a/src/starboard/nplb/sabi/sabi.gypi
+++ b/src/starboard/nplb/sabi/sabi.gypi
@@ -17,6 +17,7 @@
     'sabi_sources': [
       '<(DEPTH)/starboard/nplb/sabi/alignment_test.cc',
       '<(DEPTH)/starboard/nplb/sabi/endianness_test.cc',
+      '<(DEPTH)/starboard/nplb/sabi/signedness_and_size_of_enum_test.cc',
       '<(DEPTH)/starboard/nplb/sabi/signedness_of_char_test.cc',
       '<(DEPTH)/starboard/nplb/sabi/size_test.cc',
       '<(DEPTH)/starboard/nplb/sabi/struct_alignment_test.cc',
diff --git a/src/starboard/nplb/sabi/signedness_and_size_of_enum_test.cc b/src/starboard/nplb/sabi/signedness_and_size_of_enum_test.cc
new file mode 100644
index 0000000..bf66cd8
--- /dev/null
+++ b/src/starboard/nplb/sabi/signedness_and_size_of_enum_test.cc
@@ -0,0 +1,37 @@
+// 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 "starboard/configuration.h"
+
+#if SB_API_VERSION >= SB_SABI_FILE_VERSION
+
+namespace starboard {
+namespace sabi {
+namespace {
+
+typedef enum GenericEnumType {
+  kOnlyTag,
+} GenericEnumType;
+
+SB_COMPILE_ASSERT((static_cast<GenericEnumType>(-1) < 0) == SB_HAS_SIGNED_ENUM,
+                  SB_HAS_SIGNED_ENUM_is_inconsistent_with_sign_of_enum);
+
+SB_COMPILE_ASSERT(sizeof(GenericEnumType) == SB_SIZE_OF_ENUM,
+                  SB_SIZE_OF_ENUM_is_inconsistent_with_sizeof_enum);
+
+}  // namespace
+}  // namespace sabi
+}  // namespace starboard
+
+#endif  // SB_API_VERSION >= SB_SABI_FILE_VERSION
diff --git a/src/starboard/nplb/socket_waiter_add_test.cc b/src/starboard/nplb/socket_waiter_add_test.cc
index b56395c..a942c11 100644
--- a/src/starboard/nplb/socket_waiter_add_test.cc
+++ b/src/starboard/nplb/socket_waiter_add_test.cc
@@ -67,7 +67,7 @@
   EXPECT_TRUE(SbSocketWaiterIsValid(waiter));
 
   const int kMany = SB_FILE_MAX_OPEN;
-  SbSocket sockets[kMany] = {0};
+  std::vector<SbSocket> sockets(kMany, 0);
   for (int i = 0; i < kMany; ++i) {
     sockets[i] = SbSocketCreate(GetAddressType(), kSbSocketProtocolTcp);
     ASSERT_TRUE(SbSocketIsValid(sockets[i]));
diff --git a/src/starboard/nplb/speech_recognizer_cancel_test.cc b/src/starboard/nplb/speech_recognizer_cancel_test.cc
index 0743dc9..630e45a 100644
--- a/src/starboard/nplb/speech_recognizer_cancel_test.cc
+++ b/src/starboard/nplb/speech_recognizer_cancel_test.cc
@@ -20,9 +20,12 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#if SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION || \
+    SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 TEST_F(SpeechRecognizerTest, CancelTestSunnyDay) {
+  if (SkipLocale())
+    return;
   SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
   EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
   SbSpeechConfiguration configuration = {true, true, 1};
@@ -38,6 +41,8 @@
 }
 
 TEST_F(SpeechRecognizerTest, CancelIsCalledMultipleTimes) {
+  if (SkipLocale())
+    return;
   SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
   EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
   SbSpeechConfiguration configuration = {true, false, 1};
@@ -55,6 +60,8 @@
 }
 
 TEST_F(SpeechRecognizerTest, CancelTestStartIsNotCalled) {
+  if (SkipLocale())
+    return;
   SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
   EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
   SbSpeechRecognizerCancel(recognizer);
@@ -62,10 +69,13 @@
 }
 
 TEST_F(SpeechRecognizerTest, CancelWithInvalidSpeechRecognizer) {
+  if (SkipLocale())
+    return;
   SbSpeechRecognizerCancel(kSbSpeechRecognizerInvalid);
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#endif  // SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION ||
+        // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/speech_recognizer_create_test.cc b/src/starboard/nplb/speech_recognizer_create_test.cc
index adfbc20..52da0c7 100644
--- a/src/starboard/nplb/speech_recognizer_create_test.cc
+++ b/src/starboard/nplb/speech_recognizer_create_test.cc
@@ -19,15 +19,19 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#if SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION || \
+    SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 TEST_F(SpeechRecognizerTest, CreateTestSunnyDay) {
+  if (SkipLocale())
+    return;
   SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
   EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
   SbSpeechRecognizerDestroy(recognizer);
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#endif  // SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION ||
+        // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/speech_recognizer_destroy_test.cc b/src/starboard/nplb/speech_recognizer_destroy_test.cc
index 857fc96..f2f5fe1 100644
--- a/src/starboard/nplb/speech_recognizer_destroy_test.cc
+++ b/src/starboard/nplb/speech_recognizer_destroy_test.cc
@@ -19,13 +19,18 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#if SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION || \
+    SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 TEST_F(SpeechRecognizerTest, DestroyInvalidSpeechRecognizer) {
+  if (SkipLocale())
+    return;
   SbSpeechRecognizerDestroy(kSbSpeechRecognizerInvalid);
 }
 
 TEST_F(SpeechRecognizerTest, DestroyRecognizerWithoutStopping) {
+  if (SkipLocale())
+    return;
   SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
   EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
   SbSpeechConfiguration configuration = {true, true, 1};
@@ -33,7 +38,8 @@
   SbSpeechRecognizerDestroy(recognizer);
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#endif  // SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION ||
+        // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/speech_recognizer_helper.h b/src/starboard/nplb/speech_recognizer_helper.h
index cba631c..5ab23b0 100644
--- a/src/starboard/nplb/speech_recognizer_helper.h
+++ b/src/starboard/nplb/speech_recognizer_helper.h
@@ -25,7 +25,8 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#if SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION || \
+    SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 class SpeechRecognizerTest : public ::testing::Test {
  public:
@@ -48,6 +49,26 @@
   }
 
  protected:
+  bool isTestFixtureSupported;
+  virtual void SetUp() {
+    // We include all API tests at compile time after Starboard version 12, so
+    // we must do a runtime check to determine whether or not that API (and
+    // thus the test fixture) is supported.
+#if SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION
+    isTestFixtureSupported = SbSpeechRecognizerIsSupported();
+#else
+    isTestFixtureSupported = true;
+#endif
+  }
+
+  // TODO: Use GTEST_SKIP in |SetUp| when we have a newer version of gtest.
+  bool SkipLocale() {
+    if (!isTestFixtureSupported) {
+      SB_LOG(INFO) << "Speech recognizer not supported. Test skipped.";
+    }
+    return !isTestFixtureSupported;
+  }
+
   // Per test teardown.
   virtual void TearDown() {
     // Wait for the speech recognizer server to tear down in order to start
@@ -62,7 +83,8 @@
   SbSpeechRecognizerHandler handler_;
 };
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#endif  // SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION ||
+        // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/speech_recognizer_start_test.cc b/src/starboard/nplb/speech_recognizer_start_test.cc
index 15922f6..85acebc 100644
--- a/src/starboard/nplb/speech_recognizer_start_test.cc
+++ b/src/starboard/nplb/speech_recognizer_start_test.cc
@@ -19,9 +19,12 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#if SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION || \
+    SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 TEST_F(SpeechRecognizerTest, StartTestSunnyDay) {
+  if (SkipLocale())
+    return;
   SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
   EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
   SbSpeechConfiguration configuration = {false, false, 1};
@@ -36,6 +39,8 @@
 }
 
 TEST_F(SpeechRecognizerTest, StartRecognizerWithContinuousRecognition) {
+  if (SkipLocale())
+    return;
   SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
   EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
   SbSpeechConfiguration configuration = {true, false, 1};
@@ -50,6 +55,8 @@
 }
 
 TEST_F(SpeechRecognizerTest, StartRecognizerWithInterimResults) {
+  if (SkipLocale())
+    return;
   SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
   EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
   SbSpeechConfiguration configuration = {false, true, 1};
@@ -64,6 +71,8 @@
 }
 
 TEST_F(SpeechRecognizerTest, StartRecognizerWith10MaxAlternatives) {
+  if (SkipLocale())
+    return;
   SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
   EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
   SbSpeechConfiguration configuration = {true, true, 10};
@@ -78,6 +87,8 @@
 }
 
 TEST_F(SpeechRecognizerTest, StartIsCalledMultipleTimes) {
+  if (SkipLocale())
+    return;
   SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
   EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
   SbSpeechConfiguration configuration = {true, true, 1};
@@ -95,13 +106,16 @@
 }
 
 TEST_F(SpeechRecognizerTest, StartWithInvalidSpeechRecognizer) {
+  if (SkipLocale())
+    return;
   SbSpeechConfiguration configuration = {true, true, 1};
   bool success =
       SbSpeechRecognizerStart(kSbSpeechRecognizerInvalid, &configuration);
   EXPECT_FALSE(success);
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#endif  // SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION ||
+        // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/speech_recognizer_stop_test.cc b/src/starboard/nplb/speech_recognizer_stop_test.cc
index bbad125..d8808be 100644
--- a/src/starboard/nplb/speech_recognizer_stop_test.cc
+++ b/src/starboard/nplb/speech_recognizer_stop_test.cc
@@ -19,9 +19,12 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#if SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION || \
+    SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 TEST_F(SpeechRecognizerTest, StopIsCalledMultipleTimes) {
+  if (SkipLocale())
+    return;
   SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
   EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
   SbSpeechConfiguration configuration = {true, true, 1};
@@ -37,7 +40,8 @@
   SbSpeechRecognizerDestroy(recognizer);
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#endif  // SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION ||
+        // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/speech_synthesis_basic_test.cc b/src/starboard/nplb/speech_synthesis_basic_test.cc
index e6b09e3..65a2f1d 100644
--- a/src/starboard/nplb/speech_synthesis_basic_test.cc
+++ b/src/starboard/nplb/speech_synthesis_basic_test.cc
@@ -19,14 +19,22 @@
 namespace nplb {
 namespace {
 
-#if SB_HAS(SPEECH_SYNTHESIS)
+#if SB_API_VERSION >= SB_SPEECH_SYNTHESIS_REQUIRED_VERSION || \
+    SB_HAS(SPEECH_SYNTHESIS)
 
 TEST(SbSpeechSynthesisBasicTest, Basic) {
+#if SB_API_VERSION >= SB_SPEECH_SYNTHESIS_REQUIRED_VERSION
+  if (!SbSpeechSynthesisIsSupported()) {
+    SB_LOG(INFO) << "Speech synthesis not supported. Test skipped.";
+    return;
+  }
+#endif
   SbSpeechSynthesisSpeak("Hello");
   SbSpeechSynthesisCancel();
 }
 
-#endif  // SB_HAS(SPEECH_SYNTHESIS)
+#endif  // SB_API_VERSION >= SB_SPEECH_SYNTHESIS_REQUIRED_VERSION ||
+        // SB_HAS(SPEECH_SYNTHESIS)
 
 }  // namespace
 }  // namespace nplb
diff --git a/src/starboard/nplb/thread_create_test.cc b/src/starboard/nplb/thread_create_test.cc
index 716e001..74d4bef 100644
--- a/src/starboard/nplb/thread_create_test.cc
+++ b/src/starboard/nplb/thread_create_test.cc
@@ -142,7 +142,7 @@
 
 TEST(SbThreadCreateTest, Summertime) {
   const int kMany = SB_MAX_THREADS;
-  SbThread threads[kMany];
+  std::vector<SbThread> threads(kMany);
   for (int i = 0; i < kMany; ++i) {
     threads[i] = SbThreadCreate(0, kSbThreadNoPriority, kSbThreadNoAffinity,
                                 true, nplb::kThreadName, nplb::AddOneEntryPoint,
diff --git a/src/starboard/nplb/thread_local_value_test.cc b/src/starboard/nplb/thread_local_value_test.cc
index dcede6c..4c94f0f 100644
--- a/src/starboard/nplb/thread_local_value_test.cc
+++ b/src/starboard/nplb/thread_local_value_test.cc
@@ -177,7 +177,7 @@
 
 TEST(SbThreadLocalValueTest, SunnyDayMany) {
   const int kMany = (2 * SB_MAX_THREAD_LOCAL_KEYS) / 3;
-  SbThreadLocalKey keys[kMany];
+  std::vector<SbThreadLocalKey> keys(kMany);
 
   for (int i = 0; i < kMany; ++i) {
     keys[i] = SbThreadCreateLocalKey(NULL);
diff --git a/src/starboard/raspi/shared/configuration_constants.cc b/src/starboard/raspi/shared/configuration_constants.cc
new file mode 100644
index 0000000..5b3c886
--- /dev/null
+++ b/src/starboard/raspi/shared/configuration_constants.cc
@@ -0,0 +1,23 @@
+// 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.
+
+// This file defines all configuration constants for a platform.
+
+#include "starboard/configuration_constants.h"
+
+// Determines the alignment that allocations should have on this platform.
+const size_t kSbMallocAlignment = 16;
+
+// The maximum length of a name for a thread, including the NULL-terminator.
+const int32_t kSbMaxThreadNameLength = 16;
diff --git a/src/starboard/raspi/shared/configuration_public.h b/src/starboard/raspi/shared/configuration_public.h
index bbeb085..13cd368 100644
--- a/src/starboard/raspi/shared/configuration_public.h
+++ b/src/starboard/raspi/shared/configuration_public.h
@@ -288,9 +288,6 @@
 // specify that.
 #define SB_NETWORK_IO_BUFFER_ALIGNMENT 16
 
-// Determines the alignment that allocations should have on this platform.
-#define SB_MALLOC_ALIGNMENT ((size_t)16U)
-
 // Determines the threshhold of allocation size that should be done with mmap
 // (if available), rather than allocated within the core heap.
 #define SB_DEFAULT_MMAP_THRESHOLD ((size_t)(256 * 1024U))
@@ -329,9 +326,6 @@
 // The maximum number of thread local storage keys supported by this platform.
 #define SB_MAX_THREAD_LOCAL_KEYS 512
 
-// The maximum length of the name for a thread, including the NULL-terminator.
-#define SB_MAX_THREAD_NAME_LENGTH 16
-
 // --- Timing API ------------------------------------------------------------
 
 // Whether this platform has an API to retrieve how long the current thread
diff --git a/src/starboard/raspi/shared/starboard_platform.gypi b/src/starboard/raspi/shared/starboard_platform.gypi
index 17498ec..d43a9aa 100644
--- a/src/starboard/raspi/shared/starboard_platform.gypi
+++ b/src/starboard/raspi/shared/starboard_platform.gypi
@@ -43,6 +43,7 @@
         '<@(filter_based_player_sources)',
         '<(DEPTH)/starboard/linux/shared/atomic_public.h',
         '<(DEPTH)/starboard/linux/shared/configuration_public.h',
+        '<(DEPTH)/starboard/linux/shared/configuration_constants.cc',
         '<(DEPTH)/starboard/linux/shared/system_get_connection_type.cc',
         '<(DEPTH)/starboard/linux/shared/system_get_device_type.cc',
         '<(DEPTH)/starboard/linux/shared/system_get_path.cc',
diff --git a/src/starboard/shared/iso/directory_get_next.cc b/src/starboard/shared/iso/directory_get_next.cc
index e478a39..101ef08 100644
--- a/src/starboard/shared/iso/directory_get_next.cc
+++ b/src/starboard/shared/iso/directory_get_next.cc
@@ -16,7 +16,16 @@
 
 #include "starboard/shared/iso/impl/directory_get_next.h"
 
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+bool SbDirectoryGetNext(SbDirectory directory,
+                        char* out_entry,
+                        size_t out_entry_size) {
+  return ::starboard::shared::iso::impl::SbDirectoryGetNext(
+      directory, out_entry, out_entry_size);
+}
+#else   // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
 bool SbDirectoryGetNext(SbDirectory directory, SbDirectoryEntry* out_entry) {
   return ::starboard::shared::iso::impl::SbDirectoryGetNext(directory,
                                                             out_entry);
 }
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
diff --git a/src/starboard/shared/iso/impl/directory_get_next.h b/src/starboard/shared/iso/impl/directory_get_next.h
index 1bb4a1d..74d3c39 100644
--- a/src/starboard/shared/iso/impl/directory_get_next.h
+++ b/src/starboard/shared/iso/impl/directory_get_next.h
@@ -31,7 +31,16 @@
 namespace iso {
 namespace impl {
 
-bool SbDirectoryGetNext(SbDirectory directory, SbDirectoryEntry* out_entry) {
+bool SbDirectoryGetNext(SbDirectory directory,
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+                        char* out_entry,
+                        size_t out_entry_size) {
+  if (out_entry_size < SB_FILE_MAX_NAME) {
+    return false;
+  }
+#else   // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+                        SbDirectoryEntry* out_entry) {
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
   if (!directory || !directory->directory || !out_entry) {
     return false;
   }
@@ -54,8 +63,13 @@
     }
   } while (true);
 
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+  SbStringCopy(out_entry, dirent->d_name, out_entry_size);
+#else   // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
   SbStringCopy(out_entry->name, dirent->d_name,
                SB_ARRAY_SIZE_INT(out_entry->name));
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
   return true;
 }
 
diff --git a/src/starboard/shared/linux/dev_input/dev_input.cc b/src/starboard/shared/linux/dev_input/dev_input.cc
index 4e76bb4..2acca58 100644
--- a/src/starboard/shared/linux/dev_input/dev_input.cc
+++ b/src/starboard/shared/linux/dev_input/dev_input.cc
@@ -769,7 +769,19 @@
   }
 
   while (true) {
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+    std::vector<char> entry(SB_FILE_MAX_NAME);
+
+    if (!SbDirectoryGetNext(directory, entry.data(), SB_FILE_MAX_NAME)) {
+      break;
+    }
+
+    std::string path = kDevicePath;
+    path += "/";
+    path += entry.data();
+#else   // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
     SbDirectoryEntry entry;
+
     if (!SbDirectoryGetNext(directory, &entry)) {
       break;
     }
@@ -777,6 +789,7 @@
     std::string path = kDevicePath;
     path += "/";
     path += entry.name;
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
 
     if (SbDirectoryCanOpen(path.c_str())) {
       // This is a subdirectory. Skip.
diff --git a/src/starboard/shared/linux/thread_set_name.cc b/src/starboard/shared/linux/thread_set_name.cc
index f6066ec..be97f15 100644
--- a/src/starboard/shared/linux/thread_set_name.cc
+++ b/src/starboard/shared/linux/thread_set_name.cc
@@ -22,13 +22,15 @@
 #include "starboard/common/log.h"
 #include "starboard/common/string.h"
 
+#include "starboard/configuration_constants.h"
+
 void SbThreadSetName(const char* name) {
   // We don't want to rename the main thread.
   if (SbThreadGetId() == getpid()) {
     return;
   }
 
-  char buffer[SB_MAX_THREAD_NAME_LENGTH] = {};
+  char buffer[kSbMaxThreadNameLength];
 
   if (SbStringGetLength(name) >= SB_ARRAY_SIZE_INT(buffer)) {
     SbStringCopy(buffer, name, SB_ARRAY_SIZE_INT(buffer));
diff --git a/src/starboard/shared/opus/opus_audio_decoder.cc b/src/starboard/shared/opus/opus_audio_decoder.cc
index 04dd0a6..5ca3d94 100644
--- a/src/starboard/shared/opus/opus_audio_decoder.cc
+++ b/src/starboard/shared/opus/opus_audio_decoder.cc
@@ -27,7 +27,6 @@
 namespace opus {
 
 namespace {
-const int kMaxOpusFramesPerAU = 9600;
 
 typedef struct {
   int nb_streams;
@@ -52,15 +51,6 @@
 OpusAudioDecoder::OpusAudioDecoder(
     const SbMediaAudioSampleInfo& audio_sample_info)
     : audio_sample_info_(audio_sample_info) {
-#if SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES)
-  working_buffer_.resize(kMaxOpusFramesPerAU *
-                         audio_sample_info_.number_of_channels *
-                         sizeof(opus_int16));
-#else   // SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES)
-  working_buffer_.resize(kMaxOpusFramesPerAU *
-                         audio_sample_info_.number_of_channels * sizeof(float));
-#endif  // SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES)
-
   int error;
   int channels = audio_sample_info_.number_of_channels;
   if (channels > 8 || channels < 1) {
@@ -106,28 +96,44 @@
   SB_DCHECK(input_buffer);
   SB_DCHECK(output_cb_);
 
-  Schedule(consumed_cb);
-
   if (stream_ended_) {
     SB_LOG(ERROR) << "Decode() is called after WriteEndOfStream() is called.";
     return;
   }
 
+  scoped_refptr<DecodedAudio> decoded_audio = new DecodedAudio(
+      audio_sample_info_.number_of_channels, GetSampleType(),
+      kSbMediaAudioFrameStorageTypeInterleaved, input_buffer->timestamp(),
+      audio_sample_info_.number_of_channels * frames_per_au_ *
+          starboard::media::GetBytesPerSample(GetSampleType()));
+
 #if SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES)
   const char kDecodeFunctionName[] = "opus_multistream_decode";
   int decoded_frames = opus_multistream_decode(
       decoder_, static_cast<const unsigned char*>(input_buffer->data()),
       input_buffer->size(),
-      reinterpret_cast<opus_int16*>(working_buffer_.data()),
-      kMaxOpusFramesPerAU, 0);
+      reinterpret_cast<opus_int16*>(decoded_audio->buffer()), frames_per_au_,
+      0);
 #else   // SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES)
   const char kDecodeFunctionName[] = "opus_multistream_decode_float";
   int decoded_frames = opus_multistream_decode_float(
       decoder_, static_cast<const unsigned char*>(input_buffer->data()),
-      input_buffer->size(), reinterpret_cast<float*>(working_buffer_.data()),
-      kMaxOpusFramesPerAU, 0);
+      input_buffer->size(), reinterpret_cast<float*>(decoded_audio->buffer()),
+      frames_per_au_, 0);
 #endif  // SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES)
+  if (decoded_frames == OPUS_BUFFER_TOO_SMALL &&
+      frames_per_au_ < kMaxOpusFramesPerAU) {
+    frames_per_au_ = kMaxOpusFramesPerAU;
+    // Send to decode again with the new |frames_per_au_|.
+    Decode(input_buffer, consumed_cb);
+    return;
+  }
   if (decoded_frames <= 0) {
+    // When the following check fails, it indicates that |frames_per_au_| is
+    // greater than or equal to |kMaxOpusFramesPerAU|, which should never happen
+    // for Opus.
+    SB_DCHECK(decoded_frames != OPUS_BUFFER_TOO_SMALL);
+
     // TODO: Consider fill it with silence.
     SB_LOG(ERROR) << kDecodeFunctionName
                   << "() failed with error code: " << decoded_frames;
@@ -141,14 +147,13 @@
     return;
   }
 
-  scoped_refptr<DecodedAudio> decoded_audio = new DecodedAudio(
-      audio_sample_info_.number_of_channels, GetSampleType(),
-      kSbMediaAudioFrameStorageTypeInterleaved, input_buffer->timestamp(),
-      audio_sample_info_.number_of_channels * decoded_frames *
-          starboard::media::GetBytesPerSample(GetSampleType()));
-  SbMemoryCopy(decoded_audio->buffer(), working_buffer_.data(),
-               decoded_audio->size());
+  frames_per_au_ = decoded_frames;
+  decoded_audio->ShrinkTo(audio_sample_info_.number_of_channels *
+                          frames_per_au_ *
+                          starboard::media::GetBytesPerSample(GetSampleType()));
+
   decoded_audios_.push(decoded_audio);
+  Schedule(consumed_cb);
   Schedule(output_cb_);
 }
 
diff --git a/src/starboard/shared/opus/opus_audio_decoder.h b/src/starboard/shared/opus/opus_audio_decoder.h
index 689c33d..2c0f2d8 100644
--- a/src/starboard/shared/opus/opus_audio_decoder.h
+++ b/src/starboard/shared/opus/opus_audio_decoder.h
@@ -48,6 +48,8 @@
   void Reset() override;
 
  private:
+  static const int kMaxOpusFramesPerAU = 9600;
+
   SbMediaAudioSampleType GetSampleType() const;
 
   OutputCB output_cb_;
@@ -57,7 +59,7 @@
   bool stream_ended_ = false;
   std::queue<scoped_refptr<DecodedAudio> > decoded_audios_;
   SbMediaAudioSampleInfo audio_sample_info_;
-  std::vector<uint8_t> working_buffer_;
+  int frames_per_au_ = kMaxOpusFramesPerAU;
 };
 
 }  // namespace opus
diff --git a/src/starboard/shared/pthread/mutex_destroy.cc b/src/starboard/shared/pthread/mutex_destroy.cc
index 799c83d..aa85d9c 100644
--- a/src/starboard/shared/pthread/mutex_destroy.cc
+++ b/src/starboard/shared/pthread/mutex_destroy.cc
@@ -17,6 +17,7 @@
 #include <pthread.h>
 
 #include "starboard/common/log.h"
+#include "starboard/configuration.h"
 #include "starboard/shared/pthread/is_success.h"
 
 bool SbMutexDestroy(SbMutex* mutex) {
@@ -24,6 +25,11 @@
     return false;
   }
 
+#if SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+  // Both trying to recursively acquire a mutex that is locked by the calling
+  // thread, as well as deleting a locked mutex, result in undefined behavior.
+  return IsSuccess(pthread_mutex_destroy(mutex));
+#else   // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
   // Destroying a locked mutex is undefined, so fail if the mutex is
   // already locked,
   if (!IsSuccess(pthread_mutex_trylock(mutex))) {
@@ -33,4 +39,5 @@
 
   return IsSuccess(pthread_mutex_unlock(mutex)) &&
          IsSuccess(pthread_mutex_destroy(mutex));
+#endif  // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
 }
diff --git a/src/starboard/shared/pulse/pulse_audio_sink_type.cc b/src/starboard/shared/pulse/pulse_audio_sink_type.cc
index 3bfa883..ff87de2 100644
--- a/src/starboard/shared/pulse/pulse_audio_sink_type.cc
+++ b/src/starboard/shared/pulse/pulse_audio_sink_type.cc
@@ -30,6 +30,19 @@
 #include "starboard/thread.h"
 #include "starboard/time.h"
 
+#if defined(ADDRESS_SANITIZER)
+// By default, Leak Sanitizer and Address Sanitizer is expected to exist
+// together. However, this is not true for all platforms.
+// HAS_LEAK_SANTIZIER=0 explicitly removes the Leak Sanitizer from code.
+#ifndef HAS_LEAK_SANITIZER
+#define HAS_LEAK_SANITIZER 1
+#endif  // HAS_LEAK_SANITIZER
+#endif  // defined(ADDRESS_SANITIZER)
+
+#if HAS_LEAK_SANITIZER
+#include <sanitizer/lsan_interface.h>
+#endif  // HAS_LEAK_SANITIZER
+
 namespace starboard {
 namespace shared {
 namespace pulse {
@@ -424,7 +437,13 @@
     return false;
   }
   // Create pulse context.
+#if HAS_LEAK_SANITIZER
+  __lsan_disable();
+#endif
   context_ = pa_context_new(pa_mainloop_get_api(mainloop_), "cobalt_audio");
+#if HAS_LEAK_SANITIZER
+  __lsan_enable();
+#endif
   if (!context_) {
     SB_LOG(WARNING) << "Pulse audio error: cannot create context.";
     return false;
diff --git a/src/starboard/shared/starboard/configuration_constants_compatibility_defines.h b/src/starboard/shared/starboard/configuration_constants_compatibility_defines.h
new file mode 100644
index 0000000..3485e21
--- /dev/null
+++ b/src/starboard/shared/starboard/configuration_constants_compatibility_defines.h
@@ -0,0 +1,36 @@
+// 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.
+
+// This file defines macros to provide convenience for backwards compatibility
+// after the change that migrated certain configuration macros to extern
+// variables.
+
+#ifndef STARBOARD_SHARED_STARBOARD_CONFIGURATION_CONSTANTS_COMPATIBILITY_DEFINES_H_
+#define STARBOARD_SHARED_STARBOARD_CONFIGURATION_CONSTANTS_COMPATIBILITY_DEFINES_H_
+
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
+#error \
+    "This file is only relevant for Starboard versions before 12. Please do " \
+"not include this file otherwise."
+
+#else  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
+#define kSbMallocAlignment SB_MALLOC_ALIGNMENT
+
+#define kSbMaxThreadNameLength SB_MAX_THREAD_NAME_LENGTH
+
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
+#endif  // STARBOARD_SHARED_STARBOARD_CONFIGURATION_CONSTANTS_COMPATIBILITY_DEFINES_H_
diff --git a/src/starboard/shared/starboard/player/job_queue.cc b/src/starboard/shared/starboard/player/job_queue.cc
index de6f160..5f87bc3 100644
--- a/src/starboard/shared/starboard/player/job_queue.cc
+++ b/src/starboard/shared/starboard/player/job_queue.cc
@@ -153,8 +153,10 @@
   JobToken job_token(current_job_token_);
   JobRecord job_record = {job_token, job, owner};
 #if ENABLE_JOB_QUEUE_PROFILING
-  job_record.stack_size =
-      SbSystemGetStack(job_record.stack, kProfileStackDepth);
+  if (kProfileStackDepth > 0) {
+    job_record.stack_size =
+        SbSystemGetStack(job_record.stack, kProfileStackDepth);
+  }
 #endif  // ENABLE_JOB_QUEUE_PROFILING
 
   SbTimeMonotonic time_to_run_job = SbTimeGetMonotonicNow() + delay;
@@ -202,6 +204,9 @@
     if (time_to_job_record_map_.empty() && wait_for_next_job) {
       // |kSbTimeMax| makes more sense here, but |kSbTimeDay| is much safer.
       condition_.WaitTimed(kSbTimeDay);
+#if ENABLE_JOB_QUEUE_PROFILING
+      ++wait_times_;
+#endif  // ENABLE_JOB_QUEUE_PROFILING
     }
     if (time_to_job_record_map_.empty()) {
       return false;
@@ -212,6 +217,9 @@
     if (delay > 0) {
       if (wait_for_next_job) {
         condition_.WaitTimed(delay);
+#if ENABLE_JOB_QUEUE_PROFILING
+        ++wait_times_;
+#endif  // ENABLE_JOB_QUEUE_PROFILING
         if (time_to_job_record_map_.empty()) {
           return false;
         }
@@ -239,6 +247,8 @@
   job_record.job();
 
 #if ENABLE_JOB_QUEUE_PROFILING
+  ++jobs_processed_;
+
   auto now = SbTimeGetMonotonicNow();
   auto elapsed = now - start;
   if (elapsed > max_job_interval_) {
@@ -246,7 +256,10 @@
     max_job_interval_ = elapsed;
   }
   if (now - last_reset_time_ > kProfileResetInterval) {
-    SB_LOG(INFO) << "================ Max job takes " << max_job_interval_;
+    SB_LOG(INFO) << "================ " << jobs_processed_
+                 << " jobs processed, and waited for " << wait_times_
+                 << " times since last reset on 0x" << this
+                 << ", max job takes " << max_job_interval_;
     for (int i = 0; i < job_record.stack_size; ++i) {
       char function_name[1024];
       if (SbSystemSymbolize(job_record.stack[i], function_name,
@@ -258,6 +271,8 @@
     }
     last_reset_time_ = now;
     max_job_interval_ = 0;
+    jobs_processed_ = 0;
+    wait_times_ = 0;
   }
 #endif  // ENABLE_JOB_QUEUE_PROFILING
   return true;
diff --git a/src/starboard/shared/starboard/player/job_queue.h b/src/starboard/shared/starboard/player/job_queue.h
index 231c1d4..b236fcd 100644
--- a/src/starboard/shared/starboard/player/job_queue.h
+++ b/src/starboard/shared/starboard/player/job_queue.h
@@ -163,6 +163,8 @@
   SbTimeMonotonic last_reset_time_ = SbTimeGetMonotonicNow();
   JobRecord job_record_with_max_interval_;
   SbTimeMonotonic max_job_interval_ = 0;
+  int jobs_processed_ = 0;
+  int wait_times_ = 0;
 #endif  // ENABLE_JOB_QUEUE_PROFILING
 };
 
diff --git a/src/starboard/shared/starboard/thread_local_storage_internal.cc b/src/starboard/shared/starboard/thread_local_storage_internal.cc
index 0e28828..f98994b 100644
--- a/src/starboard/shared/starboard/thread_local_storage_internal.cc
+++ b/src/starboard/shared/starboard/thread_local_storage_internal.cc
@@ -229,7 +229,12 @@
   KeyRecord* record = &data_->key_table_[key->index];
 
   record->destructor = destructor;
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+  SbMemorySet(record->values.data(), 0,
+              record->values.size() * sizeof(record->values[0]));
+#else   // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
   SbMemorySet(record->values, 0, sizeof(record->values));
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
   record->valid = true;
 
   return key;
diff --git a/src/starboard/shared/starboard/thread_local_storage_internal.h b/src/starboard/shared/starboard/thread_local_storage_internal.h
index 9438fa9..312b98b 100644
--- a/src/starboard/shared/starboard/thread_local_storage_internal.h
+++ b/src/starboard/shared/starboard/thread_local_storage_internal.h
@@ -15,6 +15,8 @@
 #ifndef STARBOARD_SHARED_STARBOARD_THREAD_LOCAL_STORAGE_INTERNAL_H_
 #define STARBOARD_SHARED_STARBOARD_THREAD_LOCAL_STORAGE_INTERNAL_H_
 
+#include <vector>
+
 #include "starboard/common/mutex.h"
 #include "starboard/common/scoped_ptr.h"
 #include "starboard/shared/internal_only.h"
@@ -55,7 +57,11 @@
   struct KeyRecord {
     bool valid;
     SbThreadLocalDestructor destructor;
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+    std::vector<void*> values(kMaxThreads);
+#else   // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
     void* values[kMaxThreads];
+#endif  // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
   };
 
   // Sets up the specified key.
diff --git a/src/starboard/shared/stub/directory_get_next.cc b/src/starboard/shared/stub/directory_get_next.cc
index 5173d4e..8987598 100644
--- a/src/starboard/shared/stub/directory_get_next.cc
+++ b/src/starboard/shared/stub/directory_get_next.cc
@@ -15,6 +15,7 @@
 #include "starboard/directory.h"
 
 bool SbDirectoryGetNext(SbDirectory /*directory*/,
-                        SbDirectoryEntry* /*out_entry*/) {
+                        char* /*out_entry*/,
+                        size_t /* out_entry_size */) {
   return false;
 }
diff --git a/src/starboard/starboard_all.gyp b/src/starboard/starboard_all.gyp
index 76bb448..bfc58b3 100644
--- a/src/starboard/starboard_all.gyp
+++ b/src/starboard/starboard_all.gyp
@@ -89,6 +89,11 @@
             '<(DEPTH)/starboard/shared/starboard/player/filter/tools/tools.gyp:*',
           ],
         }],
+        ['sb_enable_benchmark==1', {
+          'dependencies': [
+            '<(DEPTH)/starboard/benchmark/benchmark.gyp:*',
+          ],
+        }],
       ],
     },
   ],
diff --git a/src/starboard/starboard_headers_only.gyp b/src/starboard/starboard_headers_only.gyp
index 6546f76..db954f5 100644
--- a/src/starboard/starboard_headers_only.gyp
+++ b/src/starboard/starboard_headers_only.gyp
@@ -33,6 +33,7 @@
         'character.h',
         'condition_variable.h',
         'configuration.h',
+        'configuration_constants.h',
         'cpu_features.h',
         'decode_target.h',
         'directory.h',
diff --git a/src/starboard/stub/configuration_constants.cc b/src/starboard/stub/configuration_constants.cc
new file mode 100644
index 0000000..5b3c886
--- /dev/null
+++ b/src/starboard/stub/configuration_constants.cc
@@ -0,0 +1,23 @@
+// 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.
+
+// This file defines all configuration constants for a platform.
+
+#include "starboard/configuration_constants.h"
+
+// Determines the alignment that allocations should have on this platform.
+const size_t kSbMallocAlignment = 16;
+
+// The maximum length of a name for a thread, including the NULL-terminator.
+const int32_t kSbMaxThreadNameLength = 16;
diff --git a/src/starboard/stub/configuration_public.h b/src/starboard/stub/configuration_public.h
index 265be2a..95eaf1f 100644
--- a/src/starboard/stub/configuration_public.h
+++ b/src/starboard/stub/configuration_public.h
@@ -367,9 +367,6 @@
 // specify that.
 #define SB_NETWORK_IO_BUFFER_ALIGNMENT 16
 
-// Determines the alignment that allocations should have on this platform.
-#define SB_MALLOC_ALIGNMENT ((size_t)16U)
-
 // Determines the threshhold of allocation size that should be done with mmap
 // (if available), rather than allocated within the core heap.
 #define SB_DEFAULT_MMAP_THRESHOLD ((size_t)(256 * 1024U))
@@ -398,9 +395,6 @@
 // The maximum number of thread local storage keys supported by this platform.
 #define SB_MAX_THREAD_LOCAL_KEYS 512
 
-// The maximum length of the name for a thread, including the NULL-terminator.
-#define SB_MAX_THREAD_NAME_LENGTH 16
-
 // --- Timing API ------------------------------------------------------------
 
 // Whether this platform has an API to retrieve how long the current thread
diff --git a/src/starboard/stub/starboard_platform.gyp b/src/starboard/stub/starboard_platform.gyp
index 5971d03..5baf0ca 100644
--- a/src/starboard/stub/starboard_platform.gyp
+++ b/src/starboard/stub/starboard_platform.gyp
@@ -24,6 +24,7 @@
         'application_stub.cc',
         'application_stub.h',
         'atomic_public.h',
+        'configuration_constants.cc',
         'main.cc',
         'thread_types_public.h',
         # Include private stubs, if present.
diff --git a/src/starboard/tools/abstract_launcher.py b/src/starboard/tools/abstract_launcher.py
index 90dc288..a9b5063 100644
--- a/src/starboard/tools/abstract_launcher.py
+++ b/src/starboard/tools/abstract_launcher.py
@@ -172,6 +172,23 @@
 
     raise RuntimeError("Suspend not supported for this platform.")
 
+  def SupportsDeepLink(self):
+    return False
+
+  def SendDeepLink(self, link):
+    """Sends deep link to the launcher's executable.
+
+    Args:
+      link:  Link to send to the executable.
+
+    Raises:
+      RuntimeError: Deep link not supported on platform.
+    """
+
+    raise RuntimeError(
+        "Deep link not supported for this platform (link {} sent).".format(
+            link))
+
   def GetStartupTimeout(self):
     """Gets the number of seconds to wait before assuming a launcher timeout."""
 
diff --git a/src/starboard/tools/package.py b/src/starboard/tools/package.py
index 8e3245c..716596b 100644
--- a/src/starboard/tools/package.py
+++ b/src/starboard/tools/package.py
@@ -33,7 +33,8 @@
     path: Path to the platform
     root_module: An already-loaded module
     module_name: Name of a python module to load. If None, load the platform
-        directory as a python module.
+      directory as a python module.
+
   Returns:
     A module loaded with importlib.import_module
   Throws:
@@ -95,8 +96,9 @@
     except Exception as e:  # pylint: disable=broad-except
       # Catch all exceptions to avoid an error in one platform's Packager
       # halting the script for other platforms' packagers.
-      logging.warning('Exception iterating supported platform for platform '
-                      '%s: %s.', platform_info.name, e)
+      logging.warning(
+          'Exception iterating supported platform for platform '
+          '%s: %s.', platform_info.name, e)
 
   return packager_modules
 
@@ -121,10 +123,9 @@
 
     Args:
       targets: A list of targets to install the package to, or None on platforms
-          that support installing to a default target.
-
-    This method can be overridden to implement platform-specific steps to
-    install the package for that platform.
+        that support installing to a default target.  This method can be
+        overridden to implement platform-specific steps to install the package
+        for that platform.
     """
     del targets
 
@@ -148,6 +149,7 @@
     constructor.
     Args:
       options: A namespace object returned from ArgumentParser.parse_args
+
     Returns:
       A dict of kwargs to be passed to the Package constructor.
     """
@@ -168,12 +170,12 @@
   def GetPlatformInfo(self, platform_name):
     return self.platform_infos.get(platform_name, None)
 
-  def GetApplicationPackageInfo(self, platform_name, applciation_name):
+  def GetApplicationPackageInfo(self, platform_name, application_name):
     """Get application-specific packaging information."""
     platform_info = self.GetPlatformInfo(platform_name)
     try:
       return _ImportModule(platform_info.path, starboard,
-                           '%s.package' % applciation_name)
+                           '%s.package' % application_name)
     except ImportError as e:
       # No package parameters specified for this platform.
       logging.debug('Failed to import cobalt.package: %s', e)
@@ -187,6 +189,7 @@
       source_dir: The directory containing the application to be packaged.
       output_dir: The directory into which the package files should be placed.
       **kwargs: Platform-specific arguments.
+
     Returns:
       A PackageBase instance.
     """
diff --git a/src/starboard/tools/port_symlink.py b/src/starboard/tools/port_symlink.py
index afd1202..bd7a7a1 100644
--- a/src/starboard/tools/port_symlink.py
+++ b/src/starboard/tools/port_symlink.py
@@ -136,6 +136,11 @@
   formatter_class = argparse.RawDescriptionHelpFormatter
   parser = MyParser(epilog=help_msg, formatter_class=formatter_class)
   parser.add_argument(
+      '-a',
+      '--use_absolute_symlinks',
+      action='store_true',
+      help='Generated symlinks are stored as absolute paths.')
+  parser.add_argument(
       '-f',
       '--force',
       action='store_true',
@@ -155,6 +160,9 @@
   args = parser.parse_args()
 
   folder_path, link_path = args.link
+  if args.use_absolute_symlinks:
+    folder_path = os.path.abspath(folder_path)
+    link_path = os.path.abspath(link_path)
   if '.' in folder_path:
     d1 = os.path.abspath(folder_path)
   else:
diff --git a/src/starboard/tools/send_link.py b/src/starboard/tools/send_link.py
index 74d47f4..10a5a5d 100755
--- a/src/starboard/tools/send_link.py
+++ b/src/starboard/tools/send_link.py
@@ -34,6 +34,7 @@
 import sys
 import tempfile
 import textwrap
+import time
 
 
 def _Uncase(text):
@@ -76,7 +77,20 @@
   return None
 
 
-def _SendLink(executable, link):
+def _ConnectWithRetry(s, port, num_attempts):
+  for attempt in range(num_attempts):
+    if attempt > 0:
+      time.sleep(1)
+    try:
+      s.connect(('localhost', port))
+      return True
+    except (RuntimeError, IOError):
+      logging.error('Could not connect to port %d, attempt %d / %d', port,
+                    attempt, num_attempts)
+  return False
+
+
+def SendLink(executable, link, connection_attempts=1):
   """Sends a link to the process starting with the given executable name."""
 
   pids = _GetPids(executable)
@@ -109,7 +123,9 @@
 
   try:
     with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
-      s.connect(('localhost', port))
+      if not _ConnectWithRetry(s, port, connection_attempts):
+        logging.exception('Could not connect to port: %d', port)
+        return 1
       terminated_link = link + '\x00'
       bytes_sent = 0
       while bytes_sent < len(terminated_link):
@@ -138,7 +154,7 @@
   parser.add_argument(
       'link', type=str, help='The link content to send to the executable.')
   arguments = parser.parse_args()
-  return _SendLink(arguments.executable, arguments.link)
+  return SendLink(arguments.executable, arguments.link)
 
 
 if __name__ == '__main__':
diff --git a/src/starboard/tools/symbolize/_env.py b/src/starboard/tools/symbolize/_env.py
new file mode 100644
index 0000000..021908e
--- /dev/null
+++ b/src/starboard/tools/symbolize/_env.py
@@ -0,0 +1,26 @@
+#
+# Copyright 2017 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.
+#
+"""Ask the parent directory to load the project environment."""
+
+from imp import load_source
+from os import path
+import sys
+
+_ENV = path.abspath(path.join(path.dirname(__file__), path.pardir, '_env.py'))
+if not path.exists(_ENV):
+  print '%s: Can\'t find repo root.\nMissing parent: %s' % (__file__, _ENV)
+  sys.exit(1)
+load_source('', _ENV)
diff --git a/src/starboard/tools/symbolize/symbolize.py b/src/starboard/tools/symbolize/symbolize.py
new file mode 100644
index 0000000..cd135a6
--- /dev/null
+++ b/src/starboard/tools/symbolize/symbolize.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python
+
+# 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.
+"""Lightweight utility to simplify resolving stack traces and crashes.
+
+This tool supports three different formats for crashes and stack traces, but can
+easily be expanded for addition cases. Examples of current formats are as
+follows:
+
+  Address Sanitizer
+    #1 0x7fdc59bbaa6b  (<unknown module>)
+
+  Cobalt
+    <unknown> [0x7efcdf1fd52b]
+
+  Raw
+    0x7efcdf1fd52b
+
+The results of the symbolizer will only be included if it was able to find the
+name of the symbol, and it does not appear to be malformed. The only exception
+is when the line was matched with the |_RAW| regular expression in which case it
+will always output the results of the symbolizer.
+"""
+
+import _env  # pylint: disable=unused-import
+
+import argparse
+import os
+import re
+import subprocess
+import sys
+
+from starboard.build import clang
+from starboard.tools import build
+
+_SYMBOLIZER = os.path.join(
+    build.GetToolchainsDir(), 'x86_64-linux-gnu-clang-chromium-{}'.format(
+        clang.GetClangSpecification().revision), 'bin', 'llvm-symbolizer')
+
+_RE_ASAN = re.compile(
+    r'\s*(#[0-9]{1,3})\s*(0x[a-z0-9]*)\s*\(<unknown\smodule>\)')
+_RE_COBALT = re.compile(r'\s*<unknown> \[(0x[a-z0-9]*)\]\s*')
+_RE_RAW = re.compile(r'^(0x[a-z0-9]*)$')
+
+
+def _Symbolize(filename, library, base_address):
+  """Attempts to resolve memory addresses within the file specified.
+
+  This function iterates through the file specified line by line. When a line is
+  found that matches one of our regular expressions it will stop and invoke
+  llvm-symbolizer with the offset of the symbol and the library specified. The
+  results are verified and the output formatted to match whichever crash-style
+  is being used.
+
+  Args:
+    filename:     The path to the file containing the stack trace.
+    library:      The path to the library that is believed to have the symbol.
+    base_address: The base address of the library when it was loaded and
+      crashed, typically found in the logs.
+  """
+  if not os.path.exists(filename):
+    raise ValueError('File not found: {}.'.format(filename))
+  if not os.path.exists(library):
+    raise ValueError('Library not found: {}.'.format(library))
+  with open(filename) as f:
+    for line in f:
+      # Address Sanitizer
+      match = _RE_ASAN.match(line)
+      if match:
+        offset = int(match.group(2), 0) - int(base_address, 0)
+        results = _RunSymbolizer(library, str(offset))
+        if results and '?' not in results[0] and '?' not in results[1]:
+          sys.stdout.write('    {} {} in {} {}\n'.format(
+              match.group(1), hex(offset), results[0], results[1]))
+          continue
+      # Cobalt
+      match = _RE_COBALT.match(line)
+      if match:
+        offset = int(match.group(1), 0) - int(base_address, 0)
+        results = _RunSymbolizer(library, str(offset))
+        if results and '?' not in results[0]:
+          sys.stdout.write('        {} [{}]\n'.format(hex(offset), results[0]))
+          continue
+      # Raw
+      match = _RE_RAW.match(line)
+      if match:
+        offset = int(match.group(1), 0) - int(base_address, 0)
+        results = _RunSymbolizer(library, str(offset))
+        if results:
+          sys.stdout.write('{} {} in {}\n'.format(
+              hex(offset), results[0], results[1]))
+          continue
+      sys.stdout.write(line)
+
+
+def _RunSymbolizer(library, offset):
+  """Uses a external symbolizer tool to resolve symbol names.
+
+  Args:
+    library: The path to the library that is believed to have the symbol.
+    offset:  The offset into the library of the symbol we are looking for.
+  """
+  if int(offset) >= 0:
+    command = subprocess.Popen([_SYMBOLIZER, '-e', library, offset, '-f'],
+                               stdout=subprocess.PIPE)
+    results = command.communicate()
+    if command.returncode == 0:
+      return results[0].split(os.linesep)
+  return None
+
+
+def main():
+  arg_parser = argparse.ArgumentParser()
+  arg_parser.add_argument(
+      '-f',
+      '--filename',
+      required=True,
+      help='The path to the file that contains the stack traces, crashes, or raw addresses.'
+  )
+  arg_parser.add_argument(
+      '-l',
+      '--library',
+      required=True,
+      help='The path to the library that is believed to contain the addresses.')
+  arg_parser.add_argument(
+      'base_address',
+      type=str,
+      nargs=1,
+      help='The base address of the library.')
+  args, _ = arg_parser.parse_known_args()
+
+  if not os.path.exists(_SYMBOLIZER):
+    raise ValueError(
+        'Please update {} with a valid llvm-symbolizer path.'.format(__file__))
+
+  return _Symbolize(args.filename, args.library, args.base_address[0])
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/src/starboard/tools/testing/test_runner.py b/src/starboard/tools/testing/test_runner.py
index ba8f2b6..ac03457 100755
--- a/src/starboard/tools/testing/test_runner.py
+++ b/src/starboard/tools/testing/test_runner.py
@@ -416,8 +416,7 @@
         coverage_directory=self.coverage_directory,
         env_variables=env,
         loader_platform=self.loader_platform,
-        loader_config=self.loader_config,
-        loader_out_directory=self.loader_out_directory)
+        loader_config=self.loader_config)
 
     test_reader = TestLineReader(read_pipe)
     test_launcher = TestLauncher(launcher)
diff --git a/src/third_party/QR-Code-generator/cpp/QrCode.cpp b/src/third_party/QR-Code-generator/cpp/QrCode.cpp
index 0f78920..0dbc086 100644
--- a/src/third_party/QR-Code-generator/cpp/QrCode.cpp
+++ b/src/third_party/QR-Code-generator/cpp/QrCode.cpp
@@ -54,9 +54,9 @@
 }
 
 
-QrCode QrCode::encodeText(const char *text, Ecc ecl) {
+QrCode QrCode::encodeText(const char *text, Ecc ecl, int minVersion) {
 	vector<QrSegment> segs = QrSegment::makeSegments(text);
-	return encodeSegments(segs, ecl);
+	return encodeSegments(segs, ecl, minVersion);
 }
 
 
diff --git a/src/third_party/QR-Code-generator/cpp/QrCode.hpp b/src/third_party/QR-Code-generator/cpp/QrCode.hpp
index 14a3f61..31dd1d7 100644
--- a/src/third_party/QR-Code-generator/cpp/QrCode.hpp
+++ b/src/third_party/QR-Code-generator/cpp/QrCode.hpp
@@ -64,7 +64,7 @@
 	 * QR Code version is automatically chosen for the output. The ECC level of the result may be higher than
 	 * the ecl argument if it can be done without increasing the version.
 	 */
-	public: static QrCode encodeText(const char *text, Ecc ecl);
+	public: static QrCode encodeText(const char *text, Ecc ecl, int minVersion);
 	
 	
 	/* 
diff --git a/src/third_party/angle/include/angle_hdr.h b/src/third_party/angle/include/angle_hdr.h
new file mode 100644
index 0000000..8a9dd6c
--- /dev/null
+++ b/src/third_party/angle/include/angle_hdr.h
@@ -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.
+
+#ifndef ANGLE_HDR_H_
+#define ANGLE_HDR_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void SetHdrAngleModeEnabled(bool flag);
+bool IsHdrAngleModeEnabled();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ANGLE_HDR_H_
diff --git a/src/third_party/angle/src/common/angle_hdr.cpp b/src/third_party/angle/src/common/angle_hdr.cpp
new file mode 100644
index 0000000..f975cb5
--- /dev/null
+++ b/src/third_party/angle/src/common/angle_hdr.cpp
@@ -0,0 +1,42 @@
+// 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.
+
+#if defined(STARBOARD)
+#include "angle_hdr.h"
+
+#include "starboard/atomic.h"
+#include "starboard/common/log.h"
+
+namespace angle
+{
+
+starboard::atomic_int32_t hdr_angle_mode_enable(0);
+
+}
+
+void SetHdrAngleModeEnabled(bool flag)
+{
+    if (!flag && angle::hdr_angle_mode_enable.load() == 0)
+    {
+        return;
+    }
+    angle::hdr_angle_mode_enable.fetch_add(flag ? 1 : -1);
+    SB_DCHECK(angle::hdr_angle_mode_enable.load() >= 0);
+}
+
+bool IsHdrAngleModeEnabled()
+{
+    return angle::hdr_angle_mode_enable.load() > 0;
+}
+#endif  // STARBOARD
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
index b4366e7..b669e8c 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
@@ -26,6 +26,63 @@
 namespace
 {
 
+#if defined(STARBOARD)
+// The following numbers are received from Recommendation ITU - R BT .2100 - 2(07 / 2018),
+// table 4 - PQ system reference non linear transfer functions
+// c1 = 0.8359375;
+// c2 = 18.8515625;
+// c3 = 18.6875;
+// m1 = 0.159301758125;
+// m2 = 78.84375;
+const std::string BT709_TO_BT2020_SHADER =
+    "struct PS_OUTPUT\n"
+    "{\n"
+    "    float4 gl_Color0 : SV_TARGET0;\n"
+    "};\n"
+    "#define kRefWhiteLevelSRGB 290.0f\n"
+    "#define kRefWhiteLevelPQ 10000.0f\n"
+    "static const float3x3 BT709_TO_BT2020 = { // ref: ARIB STD-B62 and BT.2087\n"
+    "  0.6274,    0.3293,    0.0433,\n"
+    "  0.0691,    0.9195,    0.0114,\n"
+    "  0.0164,    0.0880,    0.8956\n"
+    "};\n"
+    "float3 SRGB_EOTF(float3 E)\n"
+    "{\n"
+    "  float3 dark = E/12.92;\n"
+    "  float3 light = pow(abs((E+0.055)/(1+0.055)), 2.4);\n"
+    "  bool3 cri = E <= 0.04045;\n"
+    "  float3 cri_float = (float3)cri;\n"
+    "  float3 r = lerp(light, dark, cri_float);\n"
+    "  r = r * kRefWhiteLevelSRGB;\n"
+    "  return r;\n"
+    "}\n"
+    "//input: normalized L in units of RefWhite (1.0=100nits), output: normalized E\n"
+    "float3 PQ_OETF(float3 L)\n"
+    "{\n"
+    "  const float c1 = 0.8359375;\n"
+    "  const float c2 = 18.8515625;\n"
+    "  const float c3 = 18.6875;\n"
+    "  const float m1 = 0.159301758125;\n"
+    "  const float m2 = 78.84375;\n"
+    "  L = L / kRefWhiteLevelPQ;\n"
+    "  float3 Lm1 = pow(abs(L), m1);\n"
+    "  float3 X = (c1 + c2 * Lm1) / (1 + c3 * Lm1);\n"
+    "  float3 res = pow(abs(X), m2);\n"
+    "  return res;\n"
+    "}\n"
+    "PS_OUTPUT generateOutput()\n"
+    "{\n"
+    "    PS_OUTPUT output;\n"
+    "   \n"
+    "    float3 input_colors = gl_Color[0].rgb;\n"
+    "    float3 lin_osd_graphics = SRGB_EOTF(input_colors);\n"
+    "    lin_osd_graphics =  mul(BT709_TO_BT2020, lin_osd_graphics);\n"
+    "    output.gl_Color0.rgb = PQ_OETF(lin_osd_graphics);\n"
+    "    output.gl_Color0.a = gl_Color[0].a;\n"
+    "    return output;\n"
+    "}\n";
+#endif  // STARBOARD
+
 std::string HLSLComponentTypeString(GLenum componentType)
 {
     switch (componentType)
@@ -322,6 +379,21 @@
     return pixelHLSL;
 }
 
+#if defined(STARBOARD)
+std::string DynamicHLSL::generatePixelShaderForHdrOutputSignature(
+    const std::string &sourceShader,
+    const std::vector<PixelShaderOutputVariable> &outputVariables,
+    bool usesFragDepth,
+    const std::vector<GLenum> &outputLayout) const
+{
+    std::string pixelHLSL(sourceShader);
+    size_t outputInsertionPos = pixelHLSL.find(PIXEL_OUTPUT_STUB_STRING);
+    pixelHLSL.replace(outputInsertionPos, strlen(PIXEL_OUTPUT_STUB_STRING), BT709_TO_BT2020_SHADER);
+
+    return pixelHLSL;
+}
+#endif  // STARBOARD
+
 void DynamicHLSL::generateVaryingLinkHLSL(const VaryingPacking &varyingPacking,
                                           const BuiltinInfo &builtins,
                                           bool programUsesPointSize,
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.h b/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.h
index 0972a62..c0bfc05 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.h
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.h
@@ -112,6 +112,13 @@
         const std::string &sourceShader,
         const gl::InputLayout &inputLayout,
         const std::vector<sh::Attribute> &shaderAttributes) const;
+#if defined(STARBOARD)
+    std::string generatePixelShaderForHdrOutputSignature(
+        const std::string &sourceShader,
+        const std::vector<PixelShaderOutputVariable> &outputVariables,
+        bool usesFragDepth,
+        const std::vector<GLenum> &outputLayout) const;
+#endif  // STARBOARD
     std::string generatePixelShaderForOutputSignature(
         const std::string &sourceShader,
         const std::vector<PixelShaderOutputVariable> &outputVariables,
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.cpp
index db531ee..789bced 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.cpp
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.cpp
@@ -1148,6 +1148,76 @@
 {
 }
 
+#if defined(STARBOARD)
+gl::Error ProgramD3D::getPixelExecutableForHdrFramebuffer(const gl::Framebuffer *fbo,
+                                                          ShaderExecutableD3D **outExecutable)
+{
+    mPixelShaderOutputFormatCache.clear();
+
+    const FramebufferD3D *fboD3D = GetImplAs<FramebufferD3D>(fbo);
+    const gl::AttachmentList &colorbuffers = fboD3D->getColorAttachmentsForRender();
+
+    for (size_t colorAttachment = 0; colorAttachment < colorbuffers.size(); ++colorAttachment)
+    {
+        const gl::FramebufferAttachment *colorbuffer = colorbuffers[colorAttachment];
+
+        if (colorbuffer)
+        {
+            mPixelShaderOutputFormatCache.push_back(colorbuffer->getBinding() == GL_BACK
+                                                        ? GL_COLOR_ATTACHMENT0
+                                                        : colorbuffer->getBinding());
+        }
+        else
+        {
+            mPixelShaderOutputFormatCache.push_back(GL_NONE);
+        }
+    }
+
+    return getPixelExecutableForHdrOutputLayout(mPixelShaderOutputFormatCache, outExecutable,
+                                                nullptr);
+}
+
+gl::Error ProgramD3D::getPixelExecutableForHdrOutputLayout(
+    const std::vector<GLenum> &outputSignature,
+    ShaderExecutableD3D **outExecutable,
+    gl::InfoLog *infoLog)
+{
+    if (mPixelHdrExecutable)
+    {
+        *outExecutable = mPixelHdrExecutable->shaderExecutable();
+        return gl::NoError();
+    }
+
+    std::string finalPixelHLSL = mDynamicHLSL->generatePixelShaderForHdrOutputSignature(
+        mPixelHLSL, mPixelShaderKey, mUsesFragDepth, outputSignature);
+
+    // Generate new pixel executable
+    ShaderExecutableD3D *pixelExecutable = nullptr;
+
+    gl::InfoLog tempInfoLog;
+    gl::InfoLog *currentInfoLog = infoLog ? infoLog : &tempInfoLog;
+
+    ANGLE_TRY(mRenderer->compileToExecutable(
+        *currentInfoLog, finalPixelHLSL, SHADER_PIXEL, mStreamOutVaryings,
+        (mState.getTransformFeedbackBufferMode() == GL_SEPARATE_ATTRIBS), mPixelWorkarounds,
+        &pixelExecutable));
+
+    if (pixelExecutable)
+    {
+        mPixelHdrExecutable =
+            std::unique_ptr<PixelExecutable>(new PixelExecutable(outputSignature, pixelExecutable));
+    }
+    else if (!infoLog)
+    {
+        ERR() << "Error compiling BT709 to BT2020 pixel executable:" << std::endl
+              << tempInfoLog.str() << std::endl;
+    }
+
+    *outExecutable = pixelExecutable;
+    return gl::NoError();
+}
+#endif  // STARBOARD
+
 gl::Error ProgramD3D::getPixelExecutableForFramebuffer(const gl::Framebuffer *fbo,
                                                        ShaderExecutableD3D **outExecutable)
 {
@@ -2328,6 +2398,9 @@
 {
     mVertexExecutables.clear();
     mPixelExecutables.clear();
+#if defined(STARBOARD)
+    mPixelHdrExecutable.reset();
+#endif  // STARBOARD
 
     for (auto &geometryExecutable : mGeometryExecutables)
     {
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.h b/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.h
index 19b7dbf..d6015fb 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.h
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.h
@@ -170,6 +170,13 @@
     gl::Error getPixelExecutableForOutputLayout(const std::vector<GLenum> &outputLayout,
                                                 ShaderExecutableD3D **outExectuable,
                                                 gl::InfoLog *infoLog);
+#if defined(STARBOARD)
+    gl::Error getPixelExecutableForHdrFramebuffer(const gl::Framebuffer *fbo,
+                                                  ShaderExecutableD3D **outExectuable);
+    gl::Error getPixelExecutableForHdrOutputLayout(const std::vector<GLenum> &outputLayout,
+                                                   ShaderExecutableD3D **outExectuable,
+                                                   gl::InfoLog *infoLog);
+#endif  // STARBOARD
     gl::Error getVertexExecutableForInputLayout(const gl::InputLayout &inputLayout,
                                                 ShaderExecutableD3D **outExectuable,
                                                 gl::InfoLog *infoLog);
@@ -396,7 +403,9 @@
     std::vector<std::unique_ptr<PixelExecutable>> mPixelExecutables;
     std::vector<std::unique_ptr<ShaderExecutableD3D>> mGeometryExecutables;
     std::unique_ptr<ShaderExecutableD3D> mComputeExecutable;
-
+#if defined(STARBOARD)
+    std::unique_ptr<PixelExecutable> mPixelHdrExecutable;
+#endif  // STARBOARD
     std::string mVertexHLSL;
     angle::CompilerWorkaroundsD3D mVertexWorkarounds;
 
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
index d9b0b27..1a8601d 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
@@ -13,6 +13,9 @@
 #include <versionhelpers.h>
 #include <sstream>
 
+#if defined(STARBOARD)
+#include "angle_hdr.h"
+#endif  // STARBOARD
 #include "common/tls.h"
 #include "common/utilities.h"
 #include "libANGLE/Buffer.h"
@@ -378,6 +381,35 @@
 
 const uint32_t ScratchMemoryBufferLifetime = 1000;
 
+#if defined(STARBOARD)
+angle::Format::ID GetTextureFormatId(const gl::ContextState &data)
+{
+    const auto &glState    = data.getState();
+    ProgramD3D *programD3D = GetImplAs<ProgramD3D>(glState.getProgram());
+
+    gl::SamplerType type      = gl::SAMPLER_PIXEL;
+    unsigned int samplerRange = programD3D->getUsedSamplerRange(type);
+    for (unsigned int i = 0; i < samplerRange; i++)
+    {
+        GLint textureUnit = programD3D->getSamplerMapping(type, i, data.getCaps());
+        if (textureUnit != -1)
+        {
+            gl::Texture *texture = data.getState().getSamplerTexture(
+                textureUnit, programD3D->getSamplerTextureType(type, i));
+            ASSERT(texture);
+            rx::TextureD3D *textureD3D = GetImplAs<TextureD3D>(texture);
+            TextureStorage *texStorage = nullptr;
+            textureD3D->getNativeTexture(&texStorage);
+            if (texStorage)
+            {
+                return GetAs<TextureStorage11_2D>(texStorage)->getFormat().id;
+            }
+        }
+    }
+    return angle::Format::ID::NONE;
+}
+#endif  // STARBOARD
+
 }  // anonymous namespace
 
 Renderer11::Renderer11(egl::Display *display)
@@ -2455,7 +2487,30 @@
 
     const gl::Framebuffer *drawFramebuffer = glState.getDrawFramebuffer();
     ShaderExecutableD3D *pixelExe          = nullptr;
+#if defined(STARBOARD)
+    // While 10-bit HDR video is playing we run the pixel shader to apply color space for all UI
+    // elements conversion from 8-bit to 10-bit for all draw calls that do not involve the HDR video
+    // texture (look at spec ITU - R BT .2100 - 2(07 / 2018) for BT709 to BT2020 transform). This
+    // conversion  is applicable only once when we draw to the display - drawFramebuffer->id() is 0.
+    if (IsHdrAngleModeEnabled() && drawFramebuffer->id() == 0)
+    {
+        if (GetTextureFormatId(data) == angle::Format::ID::R10G10B10A2_UNORM ||
+            GetTextureFormatId(data) == angle::Format::ID::R16_UNORM)
+        {
+            ANGLE_TRY(programD3D->getPixelExecutableForFramebuffer(drawFramebuffer, &pixelExe));
+        }
+        else
+        {
+            ANGLE_TRY(programD3D->getPixelExecutableForHdrFramebuffer(drawFramebuffer, &pixelExe));
+        }
+    }
+    else
+    {
+        ANGLE_TRY(programD3D->getPixelExecutableForFramebuffer(drawFramebuffer, &pixelExe));
+    }
+#else
     ANGLE_TRY(programD3D->getPixelExecutableForFramebuffer(drawFramebuffer, &pixelExe));
+#endif  // STARBOARD
 
     ShaderExecutableD3D *geometryExe = nullptr;
     ANGLE_TRY(
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
index a8f4627..4d693f0 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
@@ -10,6 +10,9 @@
 
 #include <EGL/eglext.h>
 
+#if defined(STARBOARD)
+#include "angle_hdr.h"
+#endif  // STARBOARD
 #include "libANGLE/features.h"
 #include "libANGLE/renderer/d3d/d3d11/formatutils11.h"
 #include "libANGLE/renderer/d3d/d3d11/NativeWindow11.h"
@@ -23,6 +26,12 @@
 #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthroughrgba2d11ps.h"
 #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthroughrgba2dms11ps.h"
 
+#if defined(STARBOARD)
+#include <initguid.h>
+#include <dxgi1_4.h>
+#include <dxgi1_6.h>
+#endif  // STARBOARD
+
 #ifdef ANGLE_ENABLE_KEYEDMUTEX
 #define ANGLE_RESOURCE_SHARE_TYPE D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX
 #else
@@ -753,6 +762,37 @@
         return result;
     }
 
+#if defined(STARBOARD)
+    if (IsHdrAngleModeEnabled())
+    {
+        if (mCurrentColorSpace != DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020)
+        {
+            IDXGISwapChain3 *swapChain3 = static_cast<IDXGISwapChain3 *>(mSwapChain);
+            result = swapChain3->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
+            if (FAILED(result))
+            {
+                ERR() << "Color space DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 setup failed.";
+                return EGL_BAD_CONFIG;
+            }
+            mCurrentColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
+        }
+    }
+    else
+    {
+        if (mCurrentColorSpace != DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709)
+        {
+            IDXGISwapChain3 *swapChain3 = static_cast<IDXGISwapChain3 *>(mSwapChain);
+            result = swapChain3->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
+            if (FAILED(result))
+            {
+                ERR() << "Color space DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709 setup failed.";
+                return EGL_BAD_CONFIG;
+            }
+            mCurrentColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
+        }
+    }
+#endif  // STARBOARD
+
     mRenderer->onSwap();
 
     return EGL_SUCCESS;
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
index 85bd35d..71b9ad4 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
@@ -114,6 +114,9 @@
 
     EGLint mEGLSamples;
     LONGLONG mQPCFrequency;
+#if defined(STARBOARD)
+    DXGI_COLOR_SPACE_TYPE mCurrentColorSpace = DXGI_COLOR_SPACE_CUSTOM;
+#endif // STARBOARD
 };
 
 }  // namespace rx
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
index 5edc65d..449e811 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
@@ -264,6 +264,15 @@
     return gl::NoError();
 }
 
+#if defined(STARBOARD)
+const angle::Format &TextureStorage11_2D::getFormat()
+{
+    D3D11_TEXTURE2D_DESC desc = {0};
+    mTexture->GetDesc(&desc);
+    return d3d11_angle::GetFormat(desc.Format);
+}
+#endif  // STARBOARD
+
 gl::Error TextureStorage11::getCachedOrCreateSRV(const SRVKey &key,
                                                  ID3D11ShaderResourceView **outSRV)
 {
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h
index 366c7ea3..9712194 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h
@@ -173,6 +173,9 @@
     gl::Error releaseAssociatedImage(const gl::ImageIndex &index, Image11 *incomingImage) override;
 
     gl::Error useLevelZeroWorkaroundTexture(bool useLevelZeroTexture) override;
+#if defined(STARBOARD)
+    const angle::Format &getFormat();
+#endif // STARBOARD
 
   protected:
     gl::Error getSwizzleTexture(ID3D11Resource **outTexture) override;
diff --git a/src/third_party/angle/src/libGLESv2.gypi b/src/third_party/angle/src/libGLESv2.gypi
index aa05e4c..b691558 100644
--- a/src/third_party/angle/src/libGLESv2.gypi
+++ b/src/third_party/angle/src/libGLESv2.gypi
@@ -14,6 +14,7 @@
             '<(DEPTH)/third_party/angle/src/common/MemoryBuffer.cpp',
             '<(DEPTH)/third_party/angle/src/common/MemoryBuffer.h',
             '<(DEPTH)/third_party/angle/src/common/Optional.h',
+            '<(DEPTH)/third_party/angle/src/common/angle_hdr.cpp',
             '<(DEPTH)/third_party/angle/src/common/angleutils.cpp',
             '<(DEPTH)/third_party/angle/src/common/angleutils.h',
             '<(DEPTH)/third_party/angle/src/common/bitset_utils.h',
@@ -92,6 +93,7 @@
         'libangle_includes':
         [
             '<(DEPTH)/third_party/angle/include/angle_gl.h',
+            '<(DEPTH)/third_party/angle/include/angle_hdr.h',
             '<(DEPTH)/third_party/angle/include/export.h',
             '<(DEPTH)/third_party/angle/include/EGL/egl.h',
             '<(DEPTH)/third_party/angle/include/EGL/eglext.h',
diff --git a/src/third_party/dlmalloc/dlmalloc_config.h b/src/third_party/dlmalloc/dlmalloc_config.h
index 8b8543f..9335f50 100644
--- a/src/third_party/dlmalloc/dlmalloc_config.h
+++ b/src/third_party/dlmalloc/dlmalloc_config.h
@@ -19,6 +19,7 @@
 #if defined(STARBOARD)
 #include <sys/types.h>  // for ssize_t, maybe should add to starboard/types.h
 #include "starboard/configuration.h"
+#include "starboard/configuration_constants.h"
 #include "starboard/mutex.h"
 // Define STARBOARD_IMPLEMENTATION to allow inclusion of an internal Starboard
 // header. This is "okay" because dlmalloc is essentially an implementation
@@ -132,7 +133,7 @@
 #define DEFAULT_MMAP_THRESHOLD SB_DEFAULT_MMAP_THRESHOLD
 #endif
 
-#define MALLOC_ALIGNMENT SB_MALLOC_ALIGNMENT
+#define MALLOC_ALIGNMENT kSbMallocAlignment
 #define FORCEINLINE SB_C_FORCE_INLINE
 #define NOINLINE SB_C_NOINLINE
 #define LACKS_UNISTD_H 1
diff --git a/src/third_party/google_benchmark/.clang-format b/src/third_party/google_benchmark/.clang-format
new file mode 100644
index 0000000..e7d00fe
--- /dev/null
+++ b/src/third_party/google_benchmark/.clang-format
@@ -0,0 +1,5 @@
+---
+Language:        Cpp
+BasedOnStyle:  Google
+PointerAlignment: Left
+...
diff --git a/src/third_party/google_benchmark/.travis-libcxx-setup.sh b/src/third_party/google_benchmark/.travis-libcxx-setup.sh
new file mode 100644
index 0000000..a591743
--- /dev/null
+++ b/src/third_party/google_benchmark/.travis-libcxx-setup.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+# Install a newer CMake version
+curl -sSL https://cmake.org/files/v3.6/cmake-3.6.1-Linux-x86_64.sh -o install-cmake.sh
+chmod +x install-cmake.sh
+sudo ./install-cmake.sh --prefix=/usr/local --skip-license
+
+# Checkout LLVM sources
+git clone --depth=1 https://github.com/llvm-mirror/llvm.git llvm-source
+git clone --depth=1 https://github.com/llvm-mirror/libcxx.git llvm-source/projects/libcxx
+git clone --depth=1 https://github.com/llvm-mirror/libcxxabi.git llvm-source/projects/libcxxabi
+
+# Setup libc++ options
+if [ -z "$BUILD_32_BITS" ]; then
+  export BUILD_32_BITS=OFF && echo disabling 32 bit build
+fi
+
+# Build and install libc++ (Use unstable ABI for better sanitizer coverage)
+mkdir llvm-build && cd llvm-build
+cmake -DCMAKE_C_COMPILER=${C_COMPILER} -DCMAKE_CXX_COMPILER=${COMPILER} \
+      -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=/usr \
+      -DLIBCXX_ABI_UNSTABLE=ON \
+      -DLLVM_USE_SANITIZER=${LIBCXX_SANITIZER} \
+      -DLLVM_BUILD_32_BITS=${BUILD_32_BITS} \
+      ../llvm-source
+make cxx -j2
+sudo make install-cxxabi install-cxx
+cd ../
diff --git a/src/third_party/google_benchmark/.travis.yml b/src/third_party/google_benchmark/.travis.yml
new file mode 100644
index 0000000..6b6cfc7
--- /dev/null
+++ b/src/third_party/google_benchmark/.travis.yml
@@ -0,0 +1,235 @@
+sudo: required
+dist: trusty
+language: cpp
+
+env:
+  global:
+    - /usr/local/bin:$PATH
+
+matrix:
+  include:
+    - compiler: gcc
+      addons:
+        apt:
+          packages:
+            - lcov
+      env: COMPILER=g++ C_COMPILER=gcc BUILD_TYPE=Coverage
+    - compiler: gcc
+      env: COMPILER=g++ C_COMPILER=gcc BUILD_TYPE=Debug
+    - compiler: gcc
+      env: COMPILER=g++ C_COMPILER=gcc BUILD_TYPE=Release
+    - compiler: gcc
+      addons:
+        apt:
+          packages:
+            - g++-multilib
+            - libc6:i386
+      env:
+        - COMPILER=g++
+        - C_COMPILER=gcc
+        - BUILD_TYPE=Debug
+        - BUILD_32_BITS=ON
+        - EXTRA_FLAGS="-m32"
+    - compiler: gcc
+      addons:
+        apt:
+          packages:
+            - g++-multilib
+            - libc6:i386
+      env:
+        - COMPILER=g++
+        - C_COMPILER=gcc
+        - BUILD_TYPE=Release
+        - BUILD_32_BITS=ON
+        - EXTRA_FLAGS="-m32"
+    - compiler: gcc
+      env:
+        - INSTALL_GCC6_FROM_PPA=1
+        - COMPILER=g++-6 C_COMPILER=gcc-6  BUILD_TYPE=Debug
+        - ENABLE_SANITIZER=1
+        - EXTRA_FLAGS="-fno-omit-frame-pointer -g -O2 -fsanitize=undefined,address -fuse-ld=gold"
+    - compiler: clang
+      env: COMPILER=clang++ C_COMPILER=clang BUILD_TYPE=Debug
+    - compiler: clang
+      env: COMPILER=clang++ C_COMPILER=clang BUILD_TYPE=Release
+    # Clang w/ libc++
+    - compiler: clang
+      dist: xenial
+      addons:
+        apt:
+          packages:
+            clang-3.8
+      env:
+        - INSTALL_GCC6_FROM_PPA=1
+        - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Debug
+        - LIBCXX_BUILD=1
+        - EXTRA_CXX_FLAGS="-stdlib=libc++"
+    - compiler: clang
+      dist: xenial
+      addons:
+        apt:
+          packages:
+            clang-3.8
+      env:
+        - INSTALL_GCC6_FROM_PPA=1
+        - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Release
+        - LIBCXX_BUILD=1
+        - EXTRA_CXX_FLAGS="-stdlib=libc++"
+    # Clang w/ 32bit libc++
+    - compiler: clang
+      dist: xenial
+      addons:
+        apt:
+          packages:
+            - clang-3.8
+            - g++-multilib
+            - libc6:i386
+      env:
+        - INSTALL_GCC6_FROM_PPA=1
+        - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Debug
+        - LIBCXX_BUILD=1
+        - BUILD_32_BITS=ON
+        - EXTRA_FLAGS="-m32"
+        - EXTRA_CXX_FLAGS="-stdlib=libc++"
+    # Clang w/ 32bit libc++
+    - compiler: clang
+      dist: xenial
+      addons:
+        apt:
+          packages:
+            - clang-3.8
+            - g++-multilib
+            - libc6:i386
+      env:
+        - INSTALL_GCC6_FROM_PPA=1
+        - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Release
+        - LIBCXX_BUILD=1
+        - BUILD_32_BITS=ON
+        - EXTRA_FLAGS="-m32"
+        - EXTRA_CXX_FLAGS="-stdlib=libc++"
+    # Clang w/ libc++, ASAN, UBSAN
+    - compiler: clang
+      dist: xenial
+      addons:
+        apt:
+          packages:
+            clang-3.8
+      env:
+        - INSTALL_GCC6_FROM_PPA=1
+        - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Debug
+        - LIBCXX_BUILD=1 LIBCXX_SANITIZER="Undefined;Address"
+        - ENABLE_SANITIZER=1
+        - EXTRA_FLAGS="-g -O2 -fno-omit-frame-pointer -fsanitize=undefined,address -fno-sanitize-recover=all"
+        - EXTRA_CXX_FLAGS="-stdlib=libc++"
+        - UBSAN_OPTIONS=print_stacktrace=1
+    # Clang w/ libc++ and MSAN
+    - compiler: clang
+      dist: xenial
+      addons:
+        apt:
+          packages:
+            clang-3.8
+      env:
+        - INSTALL_GCC6_FROM_PPA=1
+        - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Debug
+        - LIBCXX_BUILD=1 LIBCXX_SANITIZER=MemoryWithOrigins
+        - ENABLE_SANITIZER=1
+        - EXTRA_FLAGS="-g -O2 -fno-omit-frame-pointer -fsanitize=memory -fsanitize-memory-track-origins"
+        - EXTRA_CXX_FLAGS="-stdlib=libc++"
+    # Clang w/ libc++ and MSAN
+    - compiler: clang
+      dist: xenial
+      addons:
+        apt:
+          packages:
+            clang-3.8
+      env:
+        - INSTALL_GCC6_FROM_PPA=1
+        - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=RelWithDebInfo
+        - LIBCXX_BUILD=1 LIBCXX_SANITIZER=Thread
+        - ENABLE_SANITIZER=1
+        - EXTRA_FLAGS="-g -O2 -fno-omit-frame-pointer -fsanitize=thread -fno-sanitize-recover=all"
+        - EXTRA_CXX_FLAGS="-stdlib=libc++"
+    - os: osx
+      osx_image: xcode8.3
+      compiler: clang
+      env:
+        - COMPILER=clang++ BUILD_TYPE=Debug
+    - os: osx
+      osx_image: xcode8.3
+      compiler: clang
+      env:
+        - COMPILER=clang++ BUILD_TYPE=Release
+    - os: osx
+      osx_image: xcode8.3
+      compiler: clang
+      env:
+        - COMPILER=clang++
+        - BUILD_TYPE=Release
+        - BUILD_32_BITS=ON
+        - EXTRA_FLAGS="-m32"
+    - os: osx
+      osx_image: xcode8.3
+      compiler: gcc
+      env:
+        - COMPILER=g++-7 C_COMPILER=gcc-7  BUILD_TYPE=Debug
+
+before_script:
+  - if [ -n "${LIBCXX_BUILD}" ]; then
+      source .travis-libcxx-setup.sh;
+    fi
+  - if [ -n "${ENABLE_SANITIZER}" ]; then
+      export EXTRA_OPTIONS="-DBENCHMARK_ENABLE_ASSEMBLY_TESTS=OFF";
+    else
+      export EXTRA_OPTIONS="";
+    fi
+  - mkdir -p build && cd build
+
+before_install:
+  - if [ -z "$BUILD_32_BITS" ]; then
+      export BUILD_32_BITS=OFF && echo disabling 32 bit build;
+    fi
+  - if [ -n "${INSTALL_GCC6_FROM_PPA}" ]; then
+      sudo add-apt-repository -y "ppa:ubuntu-toolchain-r/test";
+      sudo apt-get update --option Acquire::Retries=100 --option Acquire::http::Timeout="60";
+    fi
+
+install:
+  - if [ -n "${INSTALL_GCC6_FROM_PPA}" ]; then
+      travis_wait sudo -E apt-get -yq --no-install-suggests --no-install-recommends install g++-6;
+    fi
+  - if [ "${TRAVIS_OS_NAME}" == "linux" -a "${BUILD_32_BITS}" == "OFF" ]; then
+      travis_wait sudo -E apt-get -y --no-install-suggests --no-install-recommends install llvm-3.9-tools;
+      sudo cp /usr/lib/llvm-3.9/bin/FileCheck /usr/local/bin/;
+    fi
+  - if [ "${BUILD_TYPE}" == "Coverage" -a "${TRAVIS_OS_NAME}" == "linux" ]; then
+      PATH=~/.local/bin:${PATH};
+      pip install --user --upgrade pip;
+      travis_wait pip install --user cpp-coveralls;
+    fi
+  - if [ "${C_COMPILER}" == "gcc-7" -a "${TRAVIS_OS_NAME}" == "osx" ]; then
+      rm -f /usr/local/include/c++;
+      brew update;
+      travis_wait brew install gcc@7;
+    fi
+  - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
+      sudo apt-get update -qq;
+      sudo apt-get install -qq unzip cmake3;
+      wget https://github.com/bazelbuild/bazel/releases/download/0.10.1/bazel-0.10.1-installer-linux-x86_64.sh --output-document bazel-installer.sh;
+      travis_wait sudo bash bazel-installer.sh;
+    fi
+  - if [ "${TRAVIS_OS_NAME}" == "osx" ]; then
+      curl -L -o bazel-installer.sh https://github.com/bazelbuild/bazel/releases/download/0.10.1/bazel-0.10.1-installer-darwin-x86_64.sh;
+      travis_wait sudo bash bazel-installer.sh;
+    fi
+
+script:
+  - cmake -DCMAKE_C_COMPILER=${C_COMPILER} -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DCMAKE_C_FLAGS="${EXTRA_FLAGS}" -DCMAKE_CXX_FLAGS="${EXTRA_FLAGS} ${EXTRA_CXX_FLAGS}" -DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON -DBENCHMARK_BUILD_32_BITS=${BUILD_32_BITS} ${EXTRA_OPTIONS} ..
+  - make
+  - ctest -C ${BUILD_TYPE} --output-on-failure
+  - bazel test -c dbg --define google_benchmark.have_regex=posix --announce_rc --verbose_failures --test_output=errors --keep_going //test/...
+
+after_success:
+  - if [ "${BUILD_TYPE}" == "Coverage" -a "${TRAVIS_OS_NAME}" == "linux" ]; then
+      coveralls --include src --include include --gcov-options '\-lp' --root .. --build-root .;
+    fi
diff --git a/src/third_party/google_benchmark/.ycm_extra_conf.py b/src/third_party/google_benchmark/.ycm_extra_conf.py
new file mode 100644
index 0000000..5649ddc
--- /dev/null
+++ b/src/third_party/google_benchmark/.ycm_extra_conf.py
@@ -0,0 +1,115 @@
+import os
+import ycm_core
+
+# These are the compilation flags that will be used in case there's no
+# compilation database set (by default, one is not set).
+# CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR.
+flags = [
+'-Wall',
+'-Werror',
+'-pedantic-errors',
+'-std=c++0x',
+'-fno-strict-aliasing',
+'-O3',
+'-DNDEBUG',
+# ...and the same thing goes for the magic -x option which specifies the
+# language that the files to be compiled are written in. This is mostly
+# relevant for c++ headers.
+# For a C project, you would set this to 'c' instead of 'c++'.
+'-x', 'c++',
+'-I', 'include',
+'-isystem', '/usr/include',
+'-isystem', '/usr/local/include',
+]
+
+
+# Set this to the absolute path to the folder (NOT the file!) containing the
+# compile_commands.json file to use that instead of 'flags'. See here for
+# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html
+#
+# Most projects will NOT need to set this to anything; you can just change the
+# 'flags' list of compilation flags. Notice that YCM itself uses that approach.
+compilation_database_folder = ''
+
+if os.path.exists( compilation_database_folder ):
+  database = ycm_core.CompilationDatabase( compilation_database_folder )
+else:
+  database = None
+
+SOURCE_EXTENSIONS = [ '.cc' ]
+
+def DirectoryOfThisScript():
+  return os.path.dirname( os.path.abspath( __file__ ) )
+
+
+def MakeRelativePathsInFlagsAbsolute( flags, working_directory ):
+  if not working_directory:
+    return list( flags )
+  new_flags = []
+  make_next_absolute = False
+  path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ]
+  for flag in flags:
+    new_flag = flag
+
+    if make_next_absolute:
+      make_next_absolute = False
+      if not flag.startswith( '/' ):
+        new_flag = os.path.join( working_directory, flag )
+
+    for path_flag in path_flags:
+      if flag == path_flag:
+        make_next_absolute = True
+        break
+
+      if flag.startswith( path_flag ):
+        path = flag[ len( path_flag ): ]
+        new_flag = path_flag + os.path.join( working_directory, path )
+        break
+
+    if new_flag:
+      new_flags.append( new_flag )
+  return new_flags
+
+
+def IsHeaderFile( filename ):
+  extension = os.path.splitext( filename )[ 1 ]
+  return extension in [ '.h', '.hxx', '.hpp', '.hh' ]
+
+
+def GetCompilationInfoForFile( filename ):
+  # The compilation_commands.json file generated by CMake does not have entries
+  # for header files. So we do our best by asking the db for flags for a
+  # corresponding source file, if any. If one exists, the flags for that file
+  # should be good enough.
+  if IsHeaderFile( filename ):
+    basename = os.path.splitext( filename )[ 0 ]
+    for extension in SOURCE_EXTENSIONS:
+      replacement_file = basename + extension
+      if os.path.exists( replacement_file ):
+        compilation_info = database.GetCompilationInfoForFile(
+          replacement_file )
+        if compilation_info.compiler_flags_:
+          return compilation_info
+    return None
+  return database.GetCompilationInfoForFile( filename )
+
+
+def FlagsForFile( filename, **kwargs ):
+  if database:
+    # Bear in mind that compilation_info.compiler_flags_ does NOT return a
+    # python list, but a "list-like" StringVec object
+    compilation_info = GetCompilationInfoForFile( filename )
+    if not compilation_info:
+      return None
+
+    final_flags = MakeRelativePathsInFlagsAbsolute(
+      compilation_info.compiler_flags_,
+      compilation_info.compiler_working_dir_ )
+  else:
+    relative_to = DirectoryOfThisScript()
+    final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to )
+
+  return {
+    'flags': final_flags,
+    'do_cache': True
+  }
diff --git a/src/third_party/google_benchmark/AUTHORS b/src/third_party/google_benchmark/AUTHORS
new file mode 100644
index 0000000..35c4c8c
--- /dev/null
+++ b/src/third_party/google_benchmark/AUTHORS
@@ -0,0 +1,54 @@
+# This is the official list of benchmark authors for copyright purposes.
+# This file is distinct from the CONTRIBUTORS files.
+# See the latter for an explanation.
+#
+# Names should be added to this file as:
+#	Name or Organization <email address>
+# The email address is not required for organizations.
+#
+# Please keep the list sorted.
+
+Albert Pretorius <pretoalb@gmail.com>
+Alex Steele <steeleal123@gmail.com>
+Andriy Berestovskyy <berestovskyy@gmail.com>
+Arne Beer <arne@twobeer.de>
+Carto
+Christopher Seymour <chris.j.seymour@hotmail.com>
+Colin Braley <braley.colin@gmail.com>
+Daniel Harvey <danielharvey458@gmail.com>
+David Coeurjolly <david.coeurjolly@liris.cnrs.fr>
+Deniz Evrenci <denizevrenci@gmail.com>
+Dirac Research 
+Dominik Czarnota <dominik.b.czarnota@gmail.com>
+Eric Backus <eric_backus@alum.mit.edu>
+Eric Fiselier <eric@efcs.ca>
+Eugene Zhuk <eugene.zhuk@gmail.com>
+Evgeny Safronov <division494@gmail.com>
+Federico Ficarelli <federico.ficarelli@gmail.com>
+Felix Homann <linuxaudio@showlabor.de>
+Google Inc.
+International Business Machines Corporation
+Ismael Jimenez Martinez <ismael.jimenez.martinez@gmail.com>
+Jern-Kuan Leong <jernkuan@gmail.com>
+JianXiong Zhou <zhoujianxiong2@gmail.com>
+Joao Paulo Magalhaes <joaoppmagalhaes@gmail.com>
+Jussi Knuuttila <jussi.knuuttila@gmail.com>
+Kaito Udagawa <umireon@gmail.com>
+Kishan Kumar <kumar.kishan@outlook.com>
+Lei Xu <eddyxu@gmail.com>
+Matt Clarkson <mattyclarkson@gmail.com>
+Maxim Vafin <maxvafin@gmail.com>
+MongoDB Inc.
+Nick Hutchinson <nshutchinson@gmail.com>
+Oleksandr Sochka <sasha.sochka@gmail.com>
+Ori Livneh <ori.livneh@gmail.com>
+Paul Redmond <paul.redmond@gmail.com>
+Radoslav Yovchev <radoslav.tm@gmail.com>
+Roman Lebedev <lebedev.ri@gmail.com>
+Sayan Bhattacharjee <aero.sayan@gmail.com>
+Shuo Chen <chenshuo@chenshuo.com>
+Steinar H. Gunderson <sgunderson@bigfoot.com>
+Stripe, Inc.
+Yixuan Qiu <yixuanq@gmail.com>
+Yusuke Suzuki <utatane.tea@gmail.com>
+Zbigniew Skowron <zbychs@gmail.com>
diff --git a/src/third_party/google_benchmark/BUILD.bazel b/src/third_party/google_benchmark/BUILD.bazel
new file mode 100644
index 0000000..d97a019
--- /dev/null
+++ b/src/third_party/google_benchmark/BUILD.bazel
@@ -0,0 +1,44 @@
+licenses(["notice"])
+
+config_setting(
+    name = "windows",
+    values = {
+        "cpu": "x64_windows",
+    },
+    visibility = [":__subpackages__"],
+)
+
+load("@rules_cc//cc:defs.bzl", "cc_library")
+
+cc_library(
+    name = "benchmark",
+    srcs = glob(
+        [
+            "src/*.cc",
+            "src/*.h",
+        ],
+        exclude = ["src/benchmark_main.cc"],
+    ),
+    hdrs = ["include/benchmark/benchmark.h"],
+    linkopts = select({
+        ":windows": ["-DEFAULTLIB:shlwapi.lib"],
+        "//conditions:default": ["-pthread"],
+    }),
+    strip_include_prefix = "include",
+    visibility = ["//visibility:public"],
+)
+
+cc_library(
+    name = "benchmark_main",
+    srcs = ["src/benchmark_main.cc"],
+    hdrs = ["include/benchmark/benchmark.h"],
+    strip_include_prefix = "include",
+    visibility = ["//visibility:public"],
+    deps = [":benchmark"],
+)
+
+cc_library(
+    name = "benchmark_internal_headers",
+    hdrs = glob(["src/*.h"]),
+    visibility = ["//test:__pkg__"],
+)
diff --git a/src/third_party/google_benchmark/CMakeLists.txt b/src/third_party/google_benchmark/CMakeLists.txt
new file mode 100644
index 0000000..8cfe125
--- /dev/null
+++ b/src/third_party/google_benchmark/CMakeLists.txt
@@ -0,0 +1,277 @@
+cmake_minimum_required (VERSION 3.5.1)
+
+foreach(p
+    CMP0048 # OK to clear PROJECT_VERSION on project()
+    CMP0054 # CMake 3.1
+    CMP0056 # export EXE_LINKER_FLAGS to try_run
+    CMP0057 # Support no if() IN_LIST operator
+    CMP0063 # Honor visibility properties for all targets
+    )
+  if(POLICY ${p})
+    cmake_policy(SET ${p} NEW)
+  endif()
+endforeach()
+
+project (benchmark CXX)
+
+option(BENCHMARK_ENABLE_TESTING "Enable testing of the benchmark library." ON)
+option(BENCHMARK_ENABLE_EXCEPTIONS "Enable the use of exceptions in the benchmark library." ON)
+option(BENCHMARK_ENABLE_LTO "Enable link time optimisation of the benchmark library." OFF)
+option(BENCHMARK_USE_LIBCXX "Build and test using libc++ as the standard library." OFF)
+if(NOT MSVC)
+  option(BENCHMARK_BUILD_32_BITS "Build a 32 bit version of the library." OFF)
+else()
+  set(BENCHMARK_BUILD_32_BITS OFF CACHE BOOL "Build a 32 bit version of the library - unsupported when using MSVC)" FORCE)
+endif()
+option(BENCHMARK_ENABLE_INSTALL "Enable installation of benchmark. (Projects embedding benchmark may want to turn this OFF.)" ON)
+
+# Allow unmet dependencies to be met using CMake's ExternalProject mechanics, which
+# may require downloading the source code.
+option(BENCHMARK_DOWNLOAD_DEPENDENCIES "Allow the downloading and in-tree building of unmet dependencies" OFF)
+
+# This option can be used to disable building and running unit tests which depend on gtest
+# in cases where it is not possible to build or find a valid version of gtest.
+option(BENCHMARK_ENABLE_GTEST_TESTS "Enable building the unit tests which depend on gtest" ON)
+
+set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
+set(ENABLE_ASSEMBLY_TESTS_DEFAULT OFF)
+function(should_enable_assembly_tests)
+  if(CMAKE_BUILD_TYPE)
+    string(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_LOWER)
+    if (${CMAKE_BUILD_TYPE_LOWER} MATCHES "coverage")
+      # FIXME: The --coverage flag needs to be removed when building assembly
+      # tests for this to work.
+      return()
+    endif()
+  endif()
+  if (MSVC)
+    return()
+  elseif(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
+    return()
+  elseif(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
+    # FIXME: Make these work on 32 bit builds
+    return()
+  elseif(BENCHMARK_BUILD_32_BITS)
+     # FIXME: Make these work on 32 bit builds
+    return()
+  endif()
+  find_program(LLVM_FILECHECK_EXE FileCheck)
+  if (LLVM_FILECHECK_EXE)
+    set(LLVM_FILECHECK_EXE "${LLVM_FILECHECK_EXE}" CACHE PATH "llvm filecheck" FORCE)
+    message(STATUS "LLVM FileCheck Found: ${LLVM_FILECHECK_EXE}")
+  else()
+    message(STATUS "Failed to find LLVM FileCheck")
+    return()
+  endif()
+  set(ENABLE_ASSEMBLY_TESTS_DEFAULT ON PARENT_SCOPE)
+endfunction()
+should_enable_assembly_tests()
+
+# This option disables the building and running of the assembly verification tests
+option(BENCHMARK_ENABLE_ASSEMBLY_TESTS "Enable building and running the assembly tests"
+    ${ENABLE_ASSEMBLY_TESTS_DEFAULT})
+
+# Make sure we can import out CMake functions
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules")
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+
+
+# Read the git tags to determine the project version
+include(GetGitVersion)
+get_git_version(GIT_VERSION)
+
+# Tell the user what versions we are using
+string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" VERSION ${GIT_VERSION})
+message(STATUS "Version: ${VERSION}")
+
+# The version of the libraries
+set(GENERIC_LIB_VERSION ${VERSION})
+string(SUBSTRING ${VERSION} 0 1 GENERIC_LIB_SOVERSION)
+
+# Import our CMake modules
+include(CheckCXXCompilerFlag)
+include(AddCXXCompilerFlag)
+include(CXXFeatureCheck)
+
+if (BENCHMARK_BUILD_32_BITS)
+  add_required_cxx_compiler_flag(-m32)
+endif()
+
+if (MSVC)
+  # Turn compiler warnings up to 11
+  string(REGEX REPLACE "[-/]W[1-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
+  add_definitions(-D_CRT_SECURE_NO_WARNINGS)
+
+  if (NOT BENCHMARK_ENABLE_EXCEPTIONS)
+    add_cxx_compiler_flag(-EHs-)
+    add_cxx_compiler_flag(-EHa-)
+    add_definitions(-D_HAS_EXCEPTIONS=0)
+  endif()
+  # Link time optimisation
+  if (BENCHMARK_ENABLE_LTO)
+    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL")
+    set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /LTCG")
+    set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG")
+    set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG")
+
+    set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /GL")
+    string(REGEX REPLACE "[-/]INCREMENTAL" "/INCREMENTAL:NO" CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO}")
+    set(CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO} /LTCG")
+    string(REGEX REPLACE "[-/]INCREMENTAL" "/INCREMENTAL:NO" CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO}")
+    set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} /LTCG")
+    string(REGEX REPLACE "[-/]INCREMENTAL" "/INCREMENTAL:NO" CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}")
+    set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /LTCG")
+
+    set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} /GL")
+    set(CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL "${CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL} /LTCG")
+    set(CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL "${CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL} /LTCG")
+    set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL} /LTCG")
+  endif()
+else()
+  # Try and enable C++11. Don't use C++14 because it doesn't work in some
+  # configurations.
+  add_cxx_compiler_flag(-std=c++11)
+  if (NOT HAVE_CXX_FLAG_STD_CXX11)
+    add_cxx_compiler_flag(-std=c++0x)
+  endif()
+
+  # Turn compiler warnings up to 11
+  add_cxx_compiler_flag(-Wall)
+  add_cxx_compiler_flag(-Wextra)
+  add_cxx_compiler_flag(-Wshadow)
+  add_cxx_compiler_flag(-Werror RELEASE)
+  add_cxx_compiler_flag(-Werror RELWITHDEBINFO)
+  add_cxx_compiler_flag(-Werror MINSIZEREL)
+  add_cxx_compiler_flag(-pedantic)
+  add_cxx_compiler_flag(-pedantic-errors)
+  add_cxx_compiler_flag(-Wshorten-64-to-32)
+  add_cxx_compiler_flag(-fstrict-aliasing)
+  # Disable warnings regarding deprecated parts of the library while building
+  # and testing those parts of the library.
+  add_cxx_compiler_flag(-Wno-deprecated-declarations)
+  if (CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
+    # Intel silently ignores '-Wno-deprecated-declarations',
+    # warning no. 1786 must be explicitly disabled.
+    # See #631 for rationale.
+    add_cxx_compiler_flag(-wd1786)
+  endif()
+  # Disable deprecation warnings for release builds (when -Werror is enabled).
+  add_cxx_compiler_flag(-Wno-deprecated RELEASE)
+  add_cxx_compiler_flag(-Wno-deprecated RELWITHDEBINFO)
+  add_cxx_compiler_flag(-Wno-deprecated MINSIZEREL)
+  if (NOT BENCHMARK_ENABLE_EXCEPTIONS)
+    add_cxx_compiler_flag(-fno-exceptions)
+  endif()
+
+  if (HAVE_CXX_FLAG_FSTRICT_ALIASING)
+    if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Intel") #ICC17u2: Many false positives for Wstrict-aliasing
+      add_cxx_compiler_flag(-Wstrict-aliasing)
+    endif()
+  endif()
+  # ICC17u2: overloaded virtual function "benchmark::Fixture::SetUp" is only partially overridden
+  # (because of deprecated overload)
+  add_cxx_compiler_flag(-wd654)
+  add_cxx_compiler_flag(-Wthread-safety)
+  if (HAVE_CXX_FLAG_WTHREAD_SAFETY)
+    cxx_feature_check(THREAD_SAFETY_ATTRIBUTES)
+  endif()
+
+  # On most UNIX like platforms g++ and clang++ define _GNU_SOURCE as a
+  # predefined macro, which turns on all of the wonderful libc extensions.
+  # However g++ doesn't do this in Cygwin so we have to define it ourselfs
+  # since we depend on GNU/POSIX/BSD extensions.
+  if (CYGWIN)
+    add_definitions(-D_GNU_SOURCE=1)
+  endif()
+
+  if (QNXNTO)
+    add_definitions(-D_QNX_SOURCE)
+  endif()
+
+  # Link time optimisation
+  if (BENCHMARK_ENABLE_LTO)
+    add_cxx_compiler_flag(-flto)
+    if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
+      find_program(GCC_AR gcc-ar)
+      if (GCC_AR)
+        set(CMAKE_AR ${GCC_AR})
+      endif()
+      find_program(GCC_RANLIB gcc-ranlib)
+      if (GCC_RANLIB)
+        set(CMAKE_RANLIB ${GCC_RANLIB})
+      endif()
+    elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
+      include(llvm-toolchain)
+    endif()
+  endif()
+
+  # Coverage build type
+  set(BENCHMARK_CXX_FLAGS_COVERAGE "${CMAKE_CXX_FLAGS_DEBUG}"
+    CACHE STRING "Flags used by the C++ compiler during coverage builds."
+    FORCE)
+  set(BENCHMARK_EXE_LINKER_FLAGS_COVERAGE "${CMAKE_EXE_LINKER_FLAGS_DEBUG}"
+    CACHE STRING "Flags used for linking binaries during coverage builds."
+    FORCE)
+  set(BENCHMARK_SHARED_LINKER_FLAGS_COVERAGE "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}"
+    CACHE STRING "Flags used by the shared libraries linker during coverage builds."
+    FORCE)
+  mark_as_advanced(
+    BENCHMARK_CXX_FLAGS_COVERAGE
+    BENCHMARK_EXE_LINKER_FLAGS_COVERAGE
+    BENCHMARK_SHARED_LINKER_FLAGS_COVERAGE)
+  set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING
+    "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel Coverage.")
+  add_cxx_compiler_flag(--coverage COVERAGE)
+endif()
+
+if (BENCHMARK_USE_LIBCXX)
+  if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
+    add_cxx_compiler_flag(-stdlib=libc++)
+  elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR
+          "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")
+    add_cxx_compiler_flag(-nostdinc++)
+    message(WARNING "libc++ header path must be manually specified using CMAKE_CXX_FLAGS")
+    # Adding -nodefaultlibs directly to CMAKE_<TYPE>_LINKER_FLAGS will break
+    # configuration checks such as 'find_package(Threads)'
+    list(APPEND BENCHMARK_CXX_LINKER_FLAGS -nodefaultlibs)
+    # -lc++ cannot be added directly to CMAKE_<TYPE>_LINKER_FLAGS because
+    # linker flags appear before all linker inputs and -lc++ must appear after.
+    list(APPEND BENCHMARK_CXX_LIBRARIES c++)
+  else()
+    message(FATAL_ERROR "-DBENCHMARK_USE_LIBCXX:BOOL=ON is not supported for compiler")
+  endif()
+endif(BENCHMARK_USE_LIBCXX)
+
+# C++ feature checks
+# Determine the correct regular expression engine to use
+cxx_feature_check(STD_REGEX)
+cxx_feature_check(GNU_POSIX_REGEX)
+cxx_feature_check(POSIX_REGEX)
+if(NOT HAVE_STD_REGEX AND NOT HAVE_GNU_POSIX_REGEX AND NOT HAVE_POSIX_REGEX)
+  message(FATAL_ERROR "Failed to determine the source files for the regular expression backend")
+endif()
+if (NOT BENCHMARK_ENABLE_EXCEPTIONS AND HAVE_STD_REGEX
+        AND NOT HAVE_GNU_POSIX_REGEX AND NOT HAVE_POSIX_REGEX)
+  message(WARNING "Using std::regex with exceptions disabled is not fully supported")
+endif()
+cxx_feature_check(STEADY_CLOCK)
+# Ensure we have pthreads
+set(THREADS_PREFER_PTHREAD_FLAG ON)
+find_package(Threads REQUIRED)
+
+# Set up directories
+include_directories(${PROJECT_SOURCE_DIR}/include)
+
+# Build the targets
+add_subdirectory(src)
+
+if (BENCHMARK_ENABLE_TESTING)
+  enable_testing()
+  if (BENCHMARK_ENABLE_GTEST_TESTS AND
+      NOT (TARGET gtest AND TARGET gtest_main AND
+           TARGET gmock AND TARGET gmock_main))
+    include(GoogleTest)
+  endif()
+  add_subdirectory(test)
+endif()
diff --git a/src/third_party/google_benchmark/CONTRIBUTING.md b/src/third_party/google_benchmark/CONTRIBUTING.md
new file mode 100644
index 0000000..43de4c9
--- /dev/null
+++ b/src/third_party/google_benchmark/CONTRIBUTING.md
@@ -0,0 +1,58 @@
+# How to contribute #
+
+We'd love to accept your patches and contributions to this project.  There are
+a just a few small guidelines you need to follow.
+
+
+## Contributor License Agreement ##
+
+Contributions to any Google project must be accompanied by a Contributor
+License Agreement.  This is not a copyright **assignment**, it simply gives
+Google permission to use and redistribute your contributions as part of the
+project.
+
+  * If you are an individual writing original source code and you're sure you
+    own the intellectual property, then you'll need to sign an [individual
+    CLA][].
+
+  * If you work for a company that wants to allow you to contribute your work,
+    then you'll need to sign a [corporate CLA][].
+
+You generally only need to submit a CLA once, so if you've already submitted
+one (even if it was for a different project), you probably don't need to do it
+again.
+
+[individual CLA]: https://developers.google.com/open-source/cla/individual
+[corporate CLA]: https://developers.google.com/open-source/cla/corporate
+
+Once your CLA is submitted (or if you already submitted one for
+another Google project), make a commit adding yourself to the
+[AUTHORS][] and [CONTRIBUTORS][] files. This commit can be part
+of your first [pull request][].
+
+[AUTHORS]: AUTHORS
+[CONTRIBUTORS]: CONTRIBUTORS
+
+
+## Submitting a patch ##
+
+  1. It's generally best to start by opening a new issue describing the bug or
+     feature you're intending to fix.  Even if you think it's relatively minor,
+     it's helpful to know what people are working on.  Mention in the initial
+     issue that you are planning to work on that bug or feature so that it can
+     be assigned to you.
+
+  1. Follow the normal process of [forking][] the project, and setup a new
+     branch to work in.  It's important that each group of changes be done in
+     separate branches in order to ensure that a pull request only includes the
+     commits related to that bug or feature.
+
+  1. Do your best to have [well-formed commit messages][] for each change.
+     This provides consistency throughout the project, and ensures that commit
+     messages are able to be formatted properly by various git tools.
+
+  1. Finally, push the commits to your fork and submit a [pull request][].
+
+[forking]: https://help.github.com/articles/fork-a-repo
+[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
+[pull request]: https://help.github.com/articles/creating-a-pull-request
diff --git a/src/third_party/google_benchmark/CONTRIBUTORS b/src/third_party/google_benchmark/CONTRIBUTORS
new file mode 100644
index 0000000..6b64a00
--- /dev/null
+++ b/src/third_party/google_benchmark/CONTRIBUTORS
@@ -0,0 +1,76 @@
+# People who have agreed to one of the CLAs and can contribute patches.
+# The AUTHORS file lists the copyright holders; this file
+# lists people.  For example, Google employees are listed here
+# but not in AUTHORS, because Google holds the copyright.
+#
+# Names should be added to this file only after verifying that
+# the individual or the individual's organization has agreed to
+# the appropriate Contributor License Agreement, found here:
+#
+# https://developers.google.com/open-source/cla/individual
+# https://developers.google.com/open-source/cla/corporate
+#
+# The agreement for individuals can be filled out on the web.
+#
+# When adding J Random Contributor's name to this file,
+# either J's name or J's organization's name should be
+# added to the AUTHORS file, depending on whether the
+# individual or corporate CLA was used.
+#
+# Names should be added to this file as:
+#     Name <email address>
+#
+# Please keep the list sorted.
+
+Albert Pretorius <pretoalb@gmail.com>
+Alex Steele <steelal123@gmail.com>
+Andriy Berestovskyy <berestovskyy@gmail.com>
+Arne Beer <arne@twobeer.de>
+Billy Robert O'Neal III <billy.oneal@gmail.com> <bion@microsoft.com>
+Chris Kennelly <ckennelly@google.com> <ckennelly@ckennelly.com>
+Christopher Seymour <chris.j.seymour@hotmail.com>
+Colin Braley <braley.colin@gmail.com>
+Cyrille Faucheux <cyrille.faucheux@gmail.com>
+Daniel Harvey <danielharvey458@gmail.com>
+David Coeurjolly <david.coeurjolly@liris.cnrs.fr>
+Deniz Evrenci <denizevrenci@gmail.com>
+Dominic Hamon <dma@stripysock.com> <dominic@google.com>
+Dominik Czarnota <dominik.b.czarnota@gmail.com>
+Eric Backus <eric_backus@alum.mit.edu>
+Eric Fiselier <eric@efcs.ca>
+Eugene Zhuk <eugene.zhuk@gmail.com>
+Evgeny Safronov <division494@gmail.com>
+Federico Ficarelli <federico.ficarelli@gmail.com>
+Felix Homann <linuxaudio@showlabor.de>
+Geoffrey Martin-Noble <gcmn@google.com> <gmngeoffrey@gmail.com>
+Hannes Hauswedell <h2@fsfe.org>
+Ismael Jimenez Martinez <ismael.jimenez.martinez@gmail.com>
+Jern-Kuan Leong <jernkuan@gmail.com>
+JianXiong Zhou <zhoujianxiong2@gmail.com>
+Joao Paulo Magalhaes <joaoppmagalhaes@gmail.com>
+John Millikin <jmillikin@stripe.com>
+Jussi Knuuttila <jussi.knuuttila@gmail.com>
+Kai Wolf <kai.wolf@gmail.com>
+Kaito Udagawa <umireon@gmail.com>
+Kishan Kumar <kumar.kishan@outlook.com>
+Lei Xu <eddyxu@gmail.com>
+Matt Clarkson <mattyclarkson@gmail.com>
+Maxim Vafin <maxvafin@gmail.com>
+Nick Hutchinson <nshutchinson@gmail.com>
+Oleksandr Sochka <sasha.sochka@gmail.com>
+Ori Livneh <ori.livneh@gmail.com>
+Pascal Leroy <phl@google.com>
+Paul Redmond <paul.redmond@gmail.com>
+Pierre Phaneuf <pphaneuf@google.com>
+Radoslav Yovchev <radoslav.tm@gmail.com>
+Raul Marin <rmrodriguez@cartodb.com>
+Ray Glover <ray.glover@uk.ibm.com>
+Robert Guo <robert.guo@mongodb.com>
+Roman Lebedev <lebedev.ri@gmail.com>
+Sayan Bhattacharjee <aero.sayan@gmail.com>
+Shuo Chen <chenshuo@chenshuo.com>
+Tobias Ulvgård <tobias.ulvgard@dirac.se>
+Tom Madams <tom.ej.madams@gmail.com> <tmadams@google.com>
+Yixuan Qiu <yixuanq@gmail.com>
+Yusuke Suzuki <utatane.tea@gmail.com>
+Zbigniew Skowron <zbychs@gmail.com>
diff --git a/src/third_party/google_benchmark/LICENSE b/src/third_party/google_benchmark/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/src/third_party/google_benchmark/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/src/third_party/google_benchmark/README.md b/src/third_party/google_benchmark/README.md
new file mode 100644
index 0000000..d972ab0
--- /dev/null
+++ b/src/third_party/google_benchmark/README.md
@@ -0,0 +1,1270 @@
+# Benchmark
+
+[![Build Status](https://travis-ci.org/google/benchmark.svg?branch=master)](https://travis-ci.org/google/benchmark)
+[![Build status](https://ci.appveyor.com/api/projects/status/u0qsyp7t1tk7cpxs/branch/master?svg=true)](https://ci.appveyor.com/project/google/benchmark/branch/master)
+[![Coverage Status](https://coveralls.io/repos/google/benchmark/badge.svg)](https://coveralls.io/r/google/benchmark)
+[![slackin](https://slackin-iqtfqnpzxd.now.sh/badge.svg)](https://slackin-iqtfqnpzxd.now.sh/)
+
+A library to benchmark code snippets, similar to unit tests. Example:
+
+```c++
+#include <benchmark/benchmark.h>
+
+static void BM_SomeFunction(benchmark::State& state) {
+  // Perform setup here
+  for (auto _ : state) {
+    // This code gets timed
+    SomeFunction();
+  }
+}
+// Register the function as a benchmark
+BENCHMARK(BM_SomeFunction);
+// Run the benchmark
+BENCHMARK_MAIN();
+```
+
+To get started, see [Requirements](#requirements) and
+[Installation](#installation). See [Usage](#usage) for a full example and the
+[User Guide](#user-guide) for a more comprehensive feature overview.
+
+It may also help to read the [Google Test documentation](https://github.com/google/googletest/blob/master/googletest/docs/primer.md)
+as some of the structural aspects of the APIs are similar.
+
+### Resources
+
+[Discussion group](https://groups.google.com/d/forum/benchmark-discuss)
+
+IRC channel: [freenode](https://freenode.net) #googlebenchmark
+
+[Additional Tooling Documentation](docs/tools.md)
+
+[Assembly Testing Documentation](docs/AssemblyTests.md)
+
+## Requirements
+
+The library can be used with C++03. However, it requires C++11 to build,
+including compiler and standard library support.
+
+The following minimum versions are required to build the library:
+
+* GCC 4.8
+* Clang 3.4
+* Visual Studio 14 2015
+* Intel 2015 Update 1
+
+See [Platform-Specific Build Instructions](#platform-specific-build-instructions).
+
+## Installation
+
+This describes the installation process using cmake. As pre-requisites, you'll
+need git and cmake installed.
+
+_See [dependencies.md](dependencies.md) for more details regarding supported
+versions of build tools._
+
+```bash
+# Check out the library.
+$ git clone https://github.com/google/benchmark.git
+# Benchmark requires Google Test as a dependency. Add the source tree as a subdirectory.
+$ git clone https://github.com/google/googletest.git benchmark/googletest
+# Go to the library root directory
+$ cd benchmark
+# Make a build directory to place the build output.
+$ mkdir build && cd build
+# Generate a Makefile with cmake.
+# Use cmake -G <generator> to generate a different file type.
+$ cmake ../
+# Build the library.
+# Use make -j<number_of_parallel_jobs> to speed up the build process, e.g. make -j8 .
+$ make
+```
+This builds the `benchmark` and `benchmark_main` libraries and tests.
+On a unix system, the build directory should now look something like this:
+
+```
+/benchmark
+  /build
+    /src
+      /libbenchmark.a
+      /libbenchmark_main.a
+    /test
+      ...
+```
+
+Next, you can run the tests to check the build.
+
+```bash
+$ make test
+```
+
+If you want to install the library globally, also run:
+
+```
+sudo make install
+```
+
+Note that Google Benchmark requires Google Test to build and run the tests. This
+dependency can be provided two ways:
+
+* Checkout the Google Test sources into `benchmark/googletest` as above.
+* Otherwise, if `-DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON` is specified during
+  configuration, the library will automatically download and build any required
+  dependencies.
+
+If you do not wish to build and run the tests, add `-DBENCHMARK_ENABLE_GTEST_TESTS=OFF`
+to `CMAKE_ARGS`.
+
+### Debug vs Release
+
+By default, benchmark builds as a debug library. You will see a warning in the
+output when this is the case. To build it as a release library instead, use:
+
+```
+cmake -DCMAKE_BUILD_TYPE=Release
+```
+
+To enable link-time optimisation, use
+
+```
+cmake -DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_LTO=true
+```
+
+If you are using gcc, you might need to set `GCC_AR` and `GCC_RANLIB` cmake
+cache variables, if autodetection fails.
+
+If you are using clang, you may need to set `LLVMAR_EXECUTABLE`,
+`LLVMNM_EXECUTABLE` and `LLVMRANLIB_EXECUTABLE` cmake cache variables.
+
+
+### Stable and Experimental Library Versions
+
+The main branch contains the latest stable version of the benchmarking library;
+the API of which can be considered largely stable, with source breaking changes
+being made only upon the release of a new major version.
+
+Newer, experimental, features are implemented and tested on the
+[`v2` branch](https://github.com/google/benchmark/tree/v2). Users who wish
+to use, test, and provide feedback on the new features are encouraged to try
+this branch. However, this branch provides no stability guarantees and reserves
+the right to change and break the API at any time.
+
+## Usage
+
+### Basic usage
+
+Define a function that executes the code to measure, register it as a benchmark
+function using the `BENCHMARK` macro, and ensure an appropriate `main` function
+is available:
+
+```c++
+#include <benchmark/benchmark.h>
+
+static void BM_StringCreation(benchmark::State& state) {
+  for (auto _ : state)
+    std::string empty_string;
+}
+// Register the function as a benchmark
+BENCHMARK(BM_StringCreation);
+
+// Define another benchmark
+static void BM_StringCopy(benchmark::State& state) {
+  std::string x = "hello";
+  for (auto _ : state)
+    std::string copy(x);
+}
+BENCHMARK(BM_StringCopy);
+
+BENCHMARK_MAIN();
+```
+
+To run the benchmark, compile and link against the `benchmark` library
+(libbenchmark.a/.so). If you followed the build steps above, this
+library will be under the build directory you created.
+
+```bash
+# Example on linux after running the build steps above. Assumes the
+# `benchmark` and `build` directories are under the current directory.
+$ g++ mybenchmark.cc -std=c++11 -isystem benchmark/include \
+  -Lbenchmark/build/src -lbenchmark -lpthread -o mybenchmark
+```
+
+Alternatively, link against the `benchmark_main` library and remove
+`BENCHMARK_MAIN();` above to get the same behavior.
+
+The compiled executable will run all benchmarks by default. Pass the `--help`
+flag for option information or see the guide below.
+
+## Platform Specific Build Instructions
+
+### Building with GCC
+
+When the library is built using GCC it is necessary to link with the pthread
+library due to how GCC implements `std::thread`. Failing to link to pthread will
+lead to runtime exceptions (unless you're using libc++), not linker errors. See
+[issue #67](https://github.com/google/benchmark/issues/67) for more details. You
+can link to pthread by adding `-pthread` to your linker command. Note, you can
+also use `-lpthread`, but there are potential issues with ordering of command
+line parameters if you use that.
+
+### Building with Visual Studio 2015 or 2017
+
+The `shlwapi` library (`-lshlwapi`) is required to support a call to `CPUInfo` which reads the registry. Either add `shlwapi.lib` under `[ Configuration Properties > Linker > Input ]`, or use the following:
+
+```
+// Alternatively, can add libraries using linker options.
+#ifdef _WIN32
+#pragma comment ( lib, "Shlwapi.lib" )
+#ifdef _DEBUG
+#pragma comment ( lib, "benchmarkd.lib" )
+#else
+#pragma comment ( lib, "benchmark.lib" )
+#endif
+#endif
+```
+
+Can also use the graphical version of CMake:
+* Open `CMake GUI`.
+* Under `Where to build the binaries`, same path as source plus `build`.
+* Under `CMAKE_INSTALL_PREFIX`, same path as source plus `install`.
+* Click `Configure`, `Generate`, `Open Project`.
+* If build fails, try deleting entire directory and starting again, or unticking options to build less.
+
+### Building with Intel 2015 Update 1 or Intel System Studio Update 4
+
+See instructions for building with Visual Studio. Once built, right click on the solution and change the build to Intel.
+
+### Building on Solaris
+
+If you're running benchmarks on solaris, you'll want the kstat library linked in
+too (`-lkstat`).
+
+## User Guide
+
+### Command Line
+
+[Output Formats](#output-formats)
+
+[Output Files](#output-files)
+
+[Running Benchmarks](#running-benchmarks)
+
+[Running a Subset of Benchmarks](#running-a-subset-of-benchmarks)
+
+[Result Comparison](#result-comparison)
+
+### Library
+
+[Runtime and Reporting Considerations](#runtime-and-reporting-considerations)
+
+[Passing Arguments](#passing-arguments)
+
+[Calculating Asymptotic Complexity](#asymptotic-complexity)
+
+[Templated Benchmarks](#templated-benchmarks)
+
+[Fixtures](#fixtures)
+
+[Custom Counters](#custom-counters)
+
+[Multithreaded Benchmarks](#multithreaded-benchmarks)
+
+[CPU Timers](#cpu-timers)
+
+[Manual Timing](#manual-timing)
+
+[Setting the Time Unit](#setting-the-time-unit)
+
+[Preventing Optimization](#preventing-optimization)
+
+[Reporting Statistics](#reporting-statistics)
+
+[Custom Statistics](#custom-statistics)
+
+[Using RegisterBenchmark](#using-register-benchmark)
+
+[Exiting with an Error](#exiting-with-an-error)
+
+[A Faster KeepRunning Loop](#a-faster-keep-running-loop)
+
+[Disabling CPU Frequency Scaling](#disabling-cpu-frequency-scaling)
+
+
+<a name="output-formats" />
+
+### Output Formats
+
+The library supports multiple output formats. Use the
+`--benchmark_format=<console|json|csv>` flag (or set the
+`BENCHMARK_FORMAT=<console|json|csv>` environment variable) to set
+the format type. `console` is the default format.
+
+The Console format is intended to be a human readable format. By default
+the format generates color output. Context is output on stderr and the
+tabular data on stdout. Example tabular output looks like:
+
+```
+Benchmark                               Time(ns)    CPU(ns) Iterations
+----------------------------------------------------------------------
+BM_SetInsert/1024/1                        28928      29349      23853  133.097kB/s   33.2742k items/s
+BM_SetInsert/1024/8                        32065      32913      21375  949.487kB/s   237.372k items/s
+BM_SetInsert/1024/10                       33157      33648      21431  1.13369MB/s   290.225k items/s
+```
+
+The JSON format outputs human readable json split into two top level attributes.
+The `context` attribute contains information about the run in general, including
+information about the CPU and the date.
+The `benchmarks` attribute contains a list of every benchmark run. Example json
+output looks like:
+
+```json
+{
+  "context": {
+    "date": "2015/03/17-18:40:25",
+    "num_cpus": 40,
+    "mhz_per_cpu": 2801,
+    "cpu_scaling_enabled": false,
+    "build_type": "debug"
+  },
+  "benchmarks": [
+    {
+      "name": "BM_SetInsert/1024/1",
+      "iterations": 94877,
+      "real_time": 29275,
+      "cpu_time": 29836,
+      "bytes_per_second": 134066,
+      "items_per_second": 33516
+    },
+    {
+      "name": "BM_SetInsert/1024/8",
+      "iterations": 21609,
+      "real_time": 32317,
+      "cpu_time": 32429,
+      "bytes_per_second": 986770,
+      "items_per_second": 246693
+    },
+    {
+      "name": "BM_SetInsert/1024/10",
+      "iterations": 21393,
+      "real_time": 32724,
+      "cpu_time": 33355,
+      "bytes_per_second": 1199226,
+      "items_per_second": 299807
+    }
+  ]
+}
+```
+
+The CSV format outputs comma-separated values. The `context` is output on stderr
+and the CSV itself on stdout. Example CSV output looks like:
+
+```
+name,iterations,real_time,cpu_time,bytes_per_second,items_per_second,label
+"BM_SetInsert/1024/1",65465,17890.7,8407.45,475768,118942,
+"BM_SetInsert/1024/8",116606,18810.1,9766.64,3.27646e+06,819115,
+"BM_SetInsert/1024/10",106365,17238.4,8421.53,4.74973e+06,1.18743e+06,
+```
+
+<a name="output-files" />
+
+### Output Files
+
+Write benchmark results to a file with the `--benchmark_out=<filename>` option
+(or set `BENCHMARK_OUT`). Specify the output format with
+`--benchmark_out_format={json|console|csv}` (or set
+`BENCHMARK_OUT_FORMAT={json|console|csv}`). Note that specifying
+`--benchmark_out` does not suppress the console output.
+
+<a name="running-benchmarks" />
+
+### Running Benchmarks
+
+Benchmarks are executed by running the produced binaries. Benchmarks binaries,
+by default, accept options that may be specified either through their command
+line interface or by setting environment variables before execution. For every
+`--option_flag=<value>` CLI switch, a corresponding environment variable
+`OPTION_FLAG=<value>` exist and is used as default if set (CLI switches always
+ prevails). A complete list of CLI options is available running benchmarks
+ with the `--help` switch.
+
+<a name="running-a-subset-of-benchmarks" />
+
+### Running a Subset of Benchmarks
+
+The `--benchmark_filter=<regex>` option (or `BENCHMARK_FILTER=<regex>`
+environment variable) can be used to only run the benchmarks that match
+the specified `<regex>`. For example:
+
+```bash
+$ ./run_benchmarks.x --benchmark_filter=BM_memcpy/32
+Run on (1 X 2300 MHz CPU )
+2016-06-25 19:34:24
+Benchmark              Time           CPU Iterations
+----------------------------------------------------
+BM_memcpy/32          11 ns         11 ns   79545455
+BM_memcpy/32k       2181 ns       2185 ns     324074
+BM_memcpy/32          12 ns         12 ns   54687500
+BM_memcpy/32k       1834 ns       1837 ns     357143
+```
+
+<a name="result-comparison" />
+
+### Result comparison
+
+It is possible to compare the benchmarking results.
+See [Additional Tooling Documentation](docs/tools.md)
+
+<a name="runtime-and-reporting-considerations" />
+
+### Runtime and Reporting Considerations
+
+When the benchmark binary is executed, each benchmark function is run serially.
+The number of iterations to run is determined dynamically by running the
+benchmark a few times and measuring the time taken and ensuring that the
+ultimate result will be statistically stable. As such, faster benchmark
+functions will be run for more iterations than slower benchmark functions, and
+the number of iterations is thus reported.
+
+In all cases, the number of iterations for which the benchmark is run is
+governed by the amount of time the benchmark takes. Concretely, the number of
+iterations is at least one, not more than 1e9, until CPU time is greater than
+the minimum time, or the wallclock time is 5x minimum time. The minimum time is
+set per benchmark by calling `MinTime` on the registered benchmark object.
+
+Average timings are then reported over the iterations run. If multiple
+repetitions are requested using the `--benchmark_repetitions` command-line
+option, or at registration time, the benchmark function will be run several
+times and statistical results across these repetitions will also be reported.
+
+As well as the per-benchmark entries, a preamble in the report will include
+information about the machine on which the benchmarks are run.
+
+<a name="passing-arguments" />
+
+### Passing Arguments
+
+Sometimes a family of benchmarks can be implemented with just one routine that
+takes an extra argument to specify which one of the family of benchmarks to
+run. For example, the following code defines a family of benchmarks for
+measuring the speed of `memcpy()` calls of different lengths:
+
+```c++
+static void BM_memcpy(benchmark::State& state) {
+  char* src = new char[state.range(0)];
+  char* dst = new char[state.range(0)];
+  memset(src, 'x', state.range(0));
+  for (auto _ : state)
+    memcpy(dst, src, state.range(0));
+  state.SetBytesProcessed(int64_t(state.iterations()) *
+                          int64_t(state.range(0)));
+  delete[] src;
+  delete[] dst;
+}
+BENCHMARK(BM_memcpy)->Arg(8)->Arg(64)->Arg(512)->Arg(1<<10)->Arg(8<<10);
+```
+
+The preceding code is quite repetitive, and can be replaced with the following
+short-hand. The following invocation will pick a few appropriate arguments in
+the specified range and will generate a benchmark for each such argument.
+
+```c++
+BENCHMARK(BM_memcpy)->Range(8, 8<<10);
+```
+
+By default the arguments in the range are generated in multiples of eight and
+the command above selects [ 8, 64, 512, 4k, 8k ]. In the following code the
+range multiplier is changed to multiples of two.
+
+```c++
+BENCHMARK(BM_memcpy)->RangeMultiplier(2)->Range(8, 8<<10);
+```
+
+Now arguments generated are [ 8, 16, 32, 64, 128, 256, 512, 1024, 2k, 4k, 8k ].
+
+The preceding code shows a method of defining a sparse range.  The following
+example shows a method of defining a dense range. It is then used to benchmark
+the performance of `std::vector` initialization for uniformly increasing sizes.
+
+```c++
+static void BM_DenseRange(benchmark::State& state) {
+  for(auto _ : state) {
+    std::vector<int> v(state.range(0), state.range(0));
+    benchmark::DoNotOptimize(v.data());
+    benchmark::ClobberMemory();
+  }
+}
+BENCHMARK(BM_DenseRange)->DenseRange(0, 1024, 128);
+```
+
+Now arguments generated are [ 0, 128, 256, 384, 512, 640