Import Cobalt 21.master.0.289410
diff --git a/src/build/json_schema_bundle_compile.gypi b/src/build/json_schema_bundle_compile.gypi
deleted file mode 100644
index ecefe41..0000000
--- a/src/build/json_schema_bundle_compile.gypi
+++ /dev/null
@@ -1,62 +0,0 @@
-# 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.
-
-{
-  'variables': {
-    # When including this gypi, the following variables must be set:
-    #   idl_schema_files: an array of idl files that comprise the api model.
-    #   cc_dir: path to generated files
-    #   root_namespace: the C++ namespace that all generated files go under
-    # Functions and namespaces can be excluded by setting "nocompile" to true.
-    'api_gen_dir': '<(DEPTH)/tools/json_schema_compiler',
-    'api_gen': '<(api_gen_dir)/compiler.py',
-  },
-  'actions': [
-    {
-      'action_name': 'genapi_bundle',
-      'inputs': [
-        '<(api_gen_dir)/cc_generator.py',
-        '<(api_gen_dir)/code.py',
-        '<(api_gen_dir)/compiler.py',
-        '<(api_gen_dir)/cpp_type_generator.py',
-        '<(api_gen_dir)/cpp_util.py',
-        '<(api_gen_dir)/h_generator.py',
-        '<(api_gen_dir)/idl_schema.py',
-        '<(api_gen_dir)/json_schema.py',
-        '<(api_gen_dir)/model.py',
-        '<(api_gen_dir)/schema_bundle_generator.py',
-        '<(api_gen_dir)/util_cc_helper.py',
-        '<@(idl_schema_files)',
-      ],
-      'outputs': [
-        '<(SHARED_INTERMEDIATE_DIR)/<(cc_dir)/generated_api.h',
-        '<(SHARED_INTERMEDIATE_DIR)/<(cc_dir)/generated_schemas.h',
-        '<(SHARED_INTERMEDIATE_DIR)/<(cc_dir)/generated_schemas.cc',
-      ],
-      'action': [
-        'python',
-        '<(api_gen)',
-        '--root=<(DEPTH)',
-        '--destdir=<(SHARED_INTERMEDIATE_DIR)',
-        '--namespace=<(root_namespace)',
-        '--bundle',
-        '<@(idl_schema_files)',
-      ],
-      'message': 'Generating C++ API bundle code',
-      'process_outputs_as_sources': 1,
-    }
-  ],
-  'include_dirs': [
-    '<(SHARED_INTERMEDIATE_DIR)',
-    '<(DEPTH)',
-  ],
-  'direct_dependent_settings': {
-    'include_dirs': [
-      '<(SHARED_INTERMEDIATE_DIR)',
-    ]
-  },
-  # This target exports a hard dependency because it generates header
-  # files.
-  'hard_dependency': 1,
-}
diff --git a/src/build/json_schema_compile.gypi b/src/build/json_schema_compile.gypi
deleted file mode 100644
index 6c8f69c..0000000
--- a/src/build/json_schema_compile.gypi
+++ /dev/null
@@ -1,110 +0,0 @@
-# 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.
-
-{
-  'variables': {
-    # When including this gypi, the following variables must be set:
-    #   json_schema_files: a list of json files that comprise the api model.
-    #   idl_schema_files: a list of IDL files that comprise the api model.
-    #   cc_dir: path to generated files
-    #   root_namespace: the C++ namespace that all generated files go under
-    # Functions and namespaces can be excluded by setting "nocompile" to true.
-    'api_gen_dir': '<(DEPTH)/tools/json_schema_compiler',
-    'api_gen': '<(api_gen_dir)/compiler.py',
-  },
-  'rules': [
-    {
-      'rule_name': 'genapi',
-      'extension': 'json',
-      'inputs': [
-        '<(api_gen_dir)/any.cc',
-        '<(api_gen_dir)/any.h',
-        '<(api_gen_dir)/any_helper.py',
-        '<(api_gen_dir)/cc_generator.py',
-        '<(api_gen_dir)/code.py',
-        '<(api_gen_dir)/compiler.py',
-        '<(api_gen_dir)/cpp_type_generator.py',
-        '<(api_gen_dir)/cpp_util.py',
-        '<(api_gen_dir)/h_generator.py',
-        '<(api_gen_dir)/json_schema.py',
-        '<(api_gen_dir)/model.py',
-        '<(api_gen_dir)/util.cc',
-        '<(api_gen_dir)/util.h',
-        '<(api_gen_dir)/util_cc_helper.py',
-        # TODO(calamity): uncomment this when gyp on windows behaves like other
-        # platforms. List expansions of filepaths in inputs expand to different
-        # things.
-        # '<@(json_schema_files)',
-      ],
-      'outputs': [
-        '<(SHARED_INTERMEDIATE_DIR)/<(cc_dir)/<(RULE_INPUT_ROOT).cc',
-        '<(SHARED_INTERMEDIATE_DIR)/<(cc_dir)/<(RULE_INPUT_ROOT).h',
-      ],
-      'action': [
-        'python',
-        '<(api_gen)',
-        '<(RULE_INPUT_PATH)',
-        '--root=<(DEPTH)',
-        '--destdir=<(SHARED_INTERMEDIATE_DIR)',
-        '--namespace=<(root_namespace)',
-      ],
-      'message': 'Generating C++ code from <(RULE_INPUT_PATH) json files',
-      'process_outputs_as_sources': 1,
-    },
-    {
-      'rule_name': 'genapi_idl',
-      'msvs_external_rule': 1,
-      'extension': 'idl',
-      'inputs': [
-        '<(api_gen_dir)/any.cc',
-        '<(api_gen_dir)/any.h',
-        '<(api_gen_dir)/any_helper.py',
-        '<(api_gen_dir)/cc_generator.py',
-        '<(api_gen_dir)/code.py',
-        '<(api_gen_dir)/compiler.py',
-        '<(api_gen_dir)/cpp_type_generator.py',
-        '<(api_gen_dir)/cpp_util.py',
-        '<(api_gen_dir)/h_generator.py',
-        '<(api_gen_dir)/idl_schema.py',
-        '<(api_gen_dir)/model.py',
-        '<(api_gen_dir)/util.cc',
-        '<(api_gen_dir)/util.h',
-        '<(api_gen_dir)/util_cc_helper.py',
-        # TODO(calamity): uncomment this when gyp on windows behaves like other
-        # platforms. List expansions of filepaths in inputs expand to different
-        # things.
-        # '<@(idl_schema_files)',
-      ],
-      'outputs': [
-        '<(SHARED_INTERMEDIATE_DIR)/<(cc_dir)/<(RULE_INPUT_ROOT).cc',
-        '<(SHARED_INTERMEDIATE_DIR)/<(cc_dir)/<(RULE_INPUT_ROOT).h',
-      ],
-      'action': [
-        'python',
-        '<(api_gen)',
-        '<(RULE_INPUT_PATH)',
-        '--root=<(DEPTH)',
-        '--destdir=<(SHARED_INTERMEDIATE_DIR)',
-        '--namespace=<(root_namespace)',
-      ],
-      'message': 'Generating C++ code from <(RULE_INPUT_PATH) IDL files',
-      'process_outputs_as_sources': 1,
-    },
-  ],
-  'include_dirs': [
-    '<(SHARED_INTERMEDIATE_DIR)',
-    '<(DEPTH)',
-  ],
-  'dependencies':[
-    '<(DEPTH)/tools/json_schema_compiler/api_gen_util.gyp:api_gen_util',
-  ],
-  'direct_dependent_settings': {
-    'include_dirs': [
-      '<(SHARED_INTERMEDIATE_DIR)',
-    ]
-  },
-  # This target exports a hard dependency because it generates header
-  # files.
-  'hard_dependency': 1,
-}
diff --git a/src/build/json_to_struct.gypi b/src/build/json_to_struct.gypi
deleted file mode 100644
index 130f6d1..0000000
--- a/src/build/json_to_struct.gypi
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright 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.
-
-{
-  'variables': {
-    # When including this gypi, the following variables must be set:
-    #   schema_file: a json file that comprise the structure model.
-    #   namespace: the C++ namespace that all generated files go under
-    #   cc_dir: path to generated files
-    # Functions and namespaces can be excluded by setting "nocompile" to true.
-    'struct_gen_dir': '<(DEPTH)/tools/json_to_struct',
-    'struct_gen': '<(struct_gen_dir)/json_to_struct.py',
-  },
-  'rules': [
-    {
-      'rule_name': 'genstaticinit',
-      'extension': 'json',
-      'inputs': [
-        '<(struct_gen_dir)/element_generator.py',
-        '<(struct_gen_dir)/json_to_struct.py',
-        '<(struct_gen_dir)/struct_generator.py',
-        '<(schema_file)',
-      ],
-      'outputs': [
-        '<(SHARED_INTERMEDIATE_DIR)/<(cc_dir)/<(RULE_INPUT_ROOT).cc',
-        '<(SHARED_INTERMEDIATE_DIR)/<(cc_dir)/<(RULE_INPUT_ROOT).h',
-      ],
-      'action': [
-        'python',
-        '<(struct_gen)',
-        '<(RULE_INPUT_PATH)',
-        '--destbase=<(SHARED_INTERMEDIATE_DIR)',
-        '--destdir=<(cc_dir)',
-        '--namespace=<(namespace)',
-        '--schema=<(schema_file)',
-      ],
-      'message': 'Generating C++ static initializers from <(RULE_INPUT_PATH)',
-      'process_outputs_as_sources': 1,
-    },
-  ],
-  'include_dirs': [
-    '<(SHARED_INTERMEDIATE_DIR)',
-    '<(DEPTH)',
-  ],
-  # This target exports a hard dependency because it generates header
-  # files.
-  'hard_dependency': 1,
-}
diff --git a/src/build/shim_headers.gypi b/src/build/shim_headers.gypi
deleted file mode 100644
index cf0914d..0000000
--- a/src/build/shim_headers.gypi
+++ /dev/null
@@ -1,45 +0,0 @@
-# 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.
-
-# This file is meant to be included into a target to handle shim headers
-# in a consistent manner. To use this the following variables need to be
-# defined:
-#   headers_root_path: string: path to directory containing headers
-#   header_filenames: list: list of header file names
-
-{
-  'variables': {
-    'shim_headers_path': '<(INTERMEDIATE_DIR)/shim_headers',
-  },
-  'direct_dependent_settings': {
-    'include_dirs+': [
-      '<(shim_headers_path)',
-    ],
-  },
-  'actions': [
-    {
-      'variables': {
-        'generator_path': '<(DEPTH)/tools/generate_shim_headers/generate_shim_headers.py',
-        'generator_args': [
-          '--headers-root', '<(headers_root_path)',
-          '--output-directory', '<(shim_headers_path)',
-          '<@(header_filenames)',
-        ],
-      },
-      'action_name': 'generate_<(_target_name)_shim_headers',
-      'inputs': [
-        '<(generator_path)',
-      ],
-      'outputs': [
-        '<!@pymod_do_main(generate_shim_headers <@(generator_args) --outputs)',
-      ],
-      'action': ['python',
-                 '<(generator_path)',
-                 '<@(generator_args)',
-                 '--generate',
-      ],
-      'message': 'Generating <(_target_name) shim headers.',
-    },
-  ],
-}
diff --git a/src/cobalt/CHANGELOG.md b/src/cobalt/CHANGELOG.md
index f2872b4..644ecae 100644
--- a/src/cobalt/CHANGELOG.md
+++ b/src/cobalt/CHANGELOG.md
@@ -119,6 +119,12 @@
    the comment of `SbMediaCanPlayMimeAndKeySystem()` in `media.h` for more
    details.
 
+ - **Added support for controlling shutdown behavior of graphics system.**
+
+   Cobalt normally clears the framebuffer to opaque black on suspend or exit.
+   This behavior can now be overridden by implementing the cobalt extension
+   function `CobaltExtensionGraphicsApi::ShouldClearFrameOnShutdown`.
+
 ## Version 20
 
  - **Support for QUIC and SPDY is now enabled.**
diff --git a/src/cobalt/audio/audio_test.gyp b/src/cobalt/audio/audio_test.gyp
index c4e3979..97bd3f9 100644
--- a/src/cobalt/audio/audio_test.gyp
+++ b/src/cobalt/audio/audio_test.gyp
@@ -24,6 +24,7 @@
         'audio_node_input_output_test.cc',
       ],
       'dependencies': [
+        '<@(cobalt_platform_dependencies)',
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
         '<(DEPTH)/cobalt/media/media.gyp:media',
         '<(DEPTH)/testing/gmock.gyp:gmock',
diff --git a/src/cobalt/bindings/run_cobalt_bindings_tests.py b/src/cobalt/bindings/run_cobalt_bindings_tests.py
index eb0fe24..33f7494 100755
--- a/src/cobalt/bindings/run_cobalt_bindings_tests.py
+++ b/src/cobalt/bindings/run_cobalt_bindings_tests.py
@@ -25,12 +25,12 @@
 import argparse
 import os
 import sys
-from webkitpy.bindings.bindings_tests import run_bindings_tests
-
 import _env  # pylint: disable=unused-import
+
 from cobalt.bindings.idl_compiler_cobalt import IdlCompilerCobalt
 from cobalt.bindings.mozjs45.code_generator_mozjs45 import CodeGeneratorMozjs45
 from cobalt.bindings.v8c.code_generator_v8c import CodeGeneratorV8c
+from webkitpy.bindings.bindings_tests import run_bindings_tests
 
 
 def main(argv):
diff --git a/src/cobalt/bindings/testing/date_bindings_test.cc b/src/cobalt/bindings/testing/date_bindings_test.cc
index 602f2ee..bb58362 100644
--- a/src/cobalt/bindings/testing/date_bindings_test.cc
+++ b/src/cobalt/bindings/testing/date_bindings_test.cc
@@ -14,8 +14,11 @@
 
 #include "base/logging.h"
 
+#include "base/time/time.h"
 #include "cobalt/bindings/testing/bindings_test_base.h"
 #include "cobalt/bindings/testing/interface_with_date.h"
+#include "starboard/client_porting/eztime/eztime.h"
+#include "starboard/time_zone.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -79,6 +82,45 @@
   EXPECT_LT(std::abs(posix_now_ms - js_now_ms), 1000);
 }
 
+TEST_F(DateBindingsTest, StarboardTimeZone) {
+  const char* month_table[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+                               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+  std::string result;
+
+  EvaluateScript("new Date().toString();", &result);
+  base::Time now = base::Time::Now();
+
+  SB_LOG(INFO) << "JavaScript Date now is : " << result;
+  SB_LOG(INFO) << "and base::Time is: " << now;
+
+  base::Time::Exploded exploded;
+  now.LocalExplode(&exploded);
+  EXPECT_NE(result.find(std::to_string(exploded.year)), std::string::npos);
+  EXPECT_NE(result.find(month_table[exploded.month - 1]), std::string::npos);
+  EXPECT_NE(result.find(std::to_string(exploded.day_of_month)),
+            std::string::npos);
+  EXPECT_NE(result.find(std::to_string(exploded.hour)), std::string::npos);
+  EXPECT_NE(result.find(std::to_string(exploded.minute)), std::string::npos);
+  EXPECT_NE(result.find(std::to_string(exploded.second)), std::string::npos);
+}
+
+TEST_F(DateBindingsTest, TimezoneOffset) {
+  EzTimeExploded ez_exploded;
+
+  auto eztt = EzTimeTFromSbTime(SbTimeGetNow());
+  EzTimeTExplodeLocal(&eztt, &ez_exploded);
+  // ez_exploded is already local time, use UTC method to convert to
+  // EzTimeT.
+  EzTimeT local_time_minutes = EzTimeTImplodeUTC(&ez_exploded) / 60;
+  EzTimeT utc_minutes = EzTimeTFromSbTime(SbTimeGetNow()) / 60;
+  EzTimeT timezone_offset = utc_minutes - local_time_minutes;
+
+  std::string result;
+  EvaluateScript("new Date().getTimezoneOffset();", &result);
+
+  EXPECT_EQ(result, std::to_string(utc_minutes - local_time_minutes));
+}
+
 }  // namespace
 }  // namespace testing
 }  // namespace bindings
diff --git a/src/cobalt/black_box_tests/black_box_tests.py b/src/cobalt/black_box_tests/black_box_tests.py
index 60f5596..271b2cc 100644
--- a/src/cobalt/black_box_tests/black_box_tests.py
+++ b/src/cobalt/black_box_tests/black_box_tests.py
@@ -35,6 +35,7 @@
 _DISABLED_BLACKBOXTEST_CONFIGS = [
     'android-arm/devel',
     'android-arm64/devel',
+    'android-x86/devel',
     'raspi-0/devel',
 ]
 
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index ace1e98..d709ff7 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -637,7 +637,7 @@
 #endif  // ENABLE_DEBUGGER
 
   // Pass down this callback from to Web module.
-  options_.web_module_options.maybe_freeze_callback =
+  options.maybe_freeze_callback =
       base::Bind(&BrowserModule::OnMaybeFreeze, base::Unretained(this));
 
   web_module_.reset(new WebModule(
@@ -1830,7 +1830,8 @@
 #if defined(ENABLE_DEBUGGER)
       debug_console_ready_to_freeze &&
 #endif  // defined(ENABLE_DEBUGGER)
-      web_module_ready_to_freeze) {
+      web_module_ready_to_freeze &&
+      application_state_ == base::kApplicationStateConcealed) {
 #if SB_API_VERSION >= SB_ADD_CONCEALED_STATE_SUPPORT_VERSION || \
     SB_HAS(CONCEALED_STATE)
     SbSystemRequestFreeze();
diff --git a/src/cobalt/browser/memory_settings/memory_settings.h b/src/cobalt/browser/memory_settings/memory_settings.h
index 3aee9b5..afaeef6 100644
--- a/src/cobalt/browser/memory_settings/memory_settings.h
+++ b/src/cobalt/browser/memory_settings/memory_settings.h
@@ -107,7 +107,8 @@
  private:
   // Default constructor for MemorySetting is forbidden. Do not use it.
   MemorySetting();
-  SB_DISALLOW_COPY_AND_ASSIGN(MemorySetting);
+  MemorySetting(const MemorySetting&) = delete;
+  void operator=(const MemorySetting&) = delete;
 };
 
 // A memory setting for integer values.
@@ -135,7 +136,8 @@
  private:
   int64_t value_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(IntSetting);
+  IntSetting(const IntSetting&) = delete;
+  void operator=(const IntSetting&) = delete;
 };
 
 // A memory setting for dimensions type values like "2048x4096x2" where
@@ -166,7 +168,8 @@
  private:
   TextureDimensions value_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(DimensionSetting);
+  DimensionSetting(const DimensionSetting&) = delete;
+  void operator=(const DimensionSetting&) = delete;
 };
 
 class SkiaGlyphAtlasTextureSetting : public DimensionSetting {
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index 9748ca1..10124f4 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -52,6 +52,7 @@
 #include "cobalt/dom/keyboard_event_init.h"
 #include "cobalt/dom/local_storage_database.h"
 #include "cobalt/dom/mutation_observer_task_manager.h"
+#include "cobalt/dom/navigator.h"
 #include "cobalt/dom/pointer_event.h"
 #include "cobalt/dom/storage.h"
 #include "cobalt/dom/ui_event.h"
@@ -240,7 +241,13 @@
   void CancelSynchronousLoads();
 
   void IsReadyToFreeze(volatile bool* is_ready_to_freeze) {
-    *is_ready_to_freeze = !media_session_client_->is_active();
+    if (window_->media_session()->media_session_client() == NULL) {
+      *is_ready_to_freeze = true;
+      return;
+    }
+
+    *is_ready_to_freeze =
+        !window_->media_session()->media_session_client()->is_active();
   }
 
  private:
@@ -432,8 +439,6 @@
   // DocumentObserver that observes the loading document.
   std::unique_ptr<DocumentLoadedObserver> document_load_observer_;
 
-  std::unique_ptr<media_session::MediaSessionClient> media_session_client_;
-
   std::unique_ptr<layout::TopmostEventTarget> topmost_event_target_;
 
   base::Closure on_before_unload_fired_but_not_handled_;
@@ -592,11 +597,6 @@
       &mutation_observer_task_manager_, data.options.dom_settings_options));
   DCHECK(environment_settings_);
 
-  media_session_client_ = media_session::MediaSessionClient::Create();
-  media_session_client_->SetMediaPlayerFactory(data.web_media_player_factory);
-  media_session_client_->SetMaybeFreezeCallback(
-      data.options.maybe_freeze_callback);
-
   system_caption_settings_ = new cobalt::dom::captions::SystemCaptionSettings(
       environment_settings_.get());
 
@@ -653,7 +653,6 @@
                  base::Unretained(this)),
       data.window_close_callback, data.window_minimize_callback,
       data.options.on_screen_keyboard_bridge, data.options.camera_3d,
-      media_session_client_->GetMediaSession(),
       base::Bind(&WebModule::Impl::OnStartDispatchEvent,
                  base::Unretained(this)),
       base::Bind(&WebModule::Impl::OnStopDispatchEvent, base::Unretained(this)),
@@ -686,6 +685,11 @@
   error_callback_ = data.error_callback;
   DCHECK(!error_callback_.is_null());
 
+  window_->navigator()->set_maybefreeze_callback(
+      data.options.maybe_freeze_callback);
+  window_->navigator()->set_media_player_factory(
+      data.web_media_player_factory);
+
   bool is_concealed =
       (data.initial_application_state == base::kApplicationStateConcealed);
   layout_manager_.reset(new layout::LayoutManager(
@@ -749,7 +753,6 @@
       script::GlobalEnvironment::ReportErrorCallback());
   window_->DispatchEvent(new dom::Event(base::Tokens::unload()));
   document_load_observer_.reset();
-  media_session_client_.reset();
 
 #if defined(ENABLE_DEBUGGER)
   debug_module_.reset();
@@ -1069,7 +1072,6 @@
 void WebModule::Impl::SetWebMediaPlayerFactory(
     media::WebMediaPlayerFactory* web_media_player_factory) {
   window_->set_web_media_player_factory(web_media_player_factory);
-  media_session_client_->SetMediaPlayerFactory(web_media_player_factory);
 }
 
 void WebModule::Impl::SetApplicationState(base::ApplicationState state) {
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 396f316..3fcc752 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-286167
\ No newline at end of file
+289410
\ No newline at end of file
diff --git a/src/cobalt/build/cobalt_configuration.gypi b/src/cobalt/build/cobalt_configuration.gypi
index 7f4f3c6..7880f93 100644
--- a/src/cobalt/build/cobalt_configuration.gypi
+++ b/src/cobalt/build/cobalt_configuration.gypi
@@ -523,10 +523,6 @@
     # further reduced on systems with extremely low memory.
     'cobalt_media_source_garbage_collection_duration_threshold_in_seconds%': -1,
 
-    'compiler_flags_host': [
-      '-D__LB_HOST__',  # TODO: Is this still needed?
-    ],
-
     'defines_debug': [
       'ALLOCATOR_STATS_TRACKING',
       'COBALT_BOX_DUMP_ENABLED',
diff --git a/src/cobalt/build/cobalt_configuration.py b/src/cobalt/build/cobalt_configuration.py
index 2cd04f6..b178ed5 100644
--- a/src/cobalt/build/cobalt_configuration.py
+++ b/src/cobalt/build/cobalt_configuration.py
@@ -38,8 +38,17 @@
                          application_directory)
 
   def GetVariables(self, config_name):
+
+    # Use env var to optimize build speed on CI
+    try:
+      # Force to int, so it's easy to pass in an override.
+      use_fastbuild = int(os.environ.get('IS_CI', 0))
+    except (ValueError, TypeError):
+      use_fastbuild = 0
+
     variables = {
-        'cobalt_fastbuild': os.environ.get('LB_FASTBUILD', 0),
+        # This is used to omit large debuginfo in files on CI environment
+        'cobalt_fastbuild': use_fastbuild,
 
         # This is here rather than cobalt_configuration.gypi so that it's
         # available for browser_bindings_gen.gyp.
@@ -119,7 +128,7 @@
         # Disabled because of: Flaky on buildbot across multiple buildconfigs.
         # Non-reproducible with local runs.
         ('xhr/WebPlatformTest.Run/'
-        'XMLHttpRequest_send_entity_body_get_head_async_htm'),
+         'XMLHttpRequest_send_entity_body_get_head_async_htm'),
         'xhr/WebPlatformTest.Run/XMLHttpRequest_status_error_htm',
         'xhr/WebPlatformTest.Run/XMLHttpRequest_response_json_htm',
         'xhr/WebPlatformTest.Run/XMLHttpRequest_send_redirect_to_non_cors_htm',
diff --git a/src/cobalt/css_parser/css_parser.gyp b/src/cobalt/css_parser/css_parser.gyp
index d07f4c8..aa4ae4a 100644
--- a/src/cobalt/css_parser/css_parser.gyp
+++ b/src/cobalt/css_parser/css_parser.gyp
@@ -33,6 +33,8 @@
     {
       'target_name': 'css_grammar',
       'type': 'none',
+      # This target generates header files which may be used by other targets.
+      'hard_dependency': 1,
       'sources': [
         'grammar.h',
         'grammar.y'
diff --git a/src/cobalt/css_parser/grammar.y b/src/cobalt/css_parser/grammar.y
index 920cce8..6909469 100644
--- a/src/cobalt/css_parser/grammar.y
+++ b/src/cobalt/css_parser/grammar.y
@@ -4882,7 +4882,17 @@
   | kCobaltUiNavFocusTransformFunctionToken maybe_whitespace ')'
       maybe_whitespace {
     $<transform_functions>0->emplace_back(
-        new cssom::CobaltUiNavFocusTransformFunction);
+        new cssom::CobaltUiNavFocusTransformFunction(1.0f, 1.0f));
+  }
+  | kCobaltUiNavFocusTransformFunctionToken maybe_whitespace number ')'
+      maybe_whitespace {
+    $<transform_functions>0->emplace_back(
+        new cssom::CobaltUiNavFocusTransformFunction($3, $3));
+  }
+  | kCobaltUiNavFocusTransformFunctionToken maybe_whitespace number comma
+      number ')' maybe_whitespace {
+    $<transform_functions>0->emplace_back(
+        new cssom::CobaltUiNavFocusTransformFunction($3, $5));
   }
   // This Cobalt-specific transform function for hybrid navigation tracks
   // the direction in which focus is moving. This can be used to provide
diff --git a/src/cobalt/css_parser/parser_test.cc b/src/cobalt/css_parser/parser_test.cc
index 040c178..d510f2c 100644
--- a/src/cobalt/css_parser/parser_test.cc
+++ b/src/cobalt/css_parser/parser_test.cc
@@ -7223,7 +7223,55 @@
 TEST_F(ParserTest, ParsesCobaltUiNavFocusTransform) {
   scoped_refptr<cssom::CSSDeclaredStyleData> style =
       parser_.ParseStyleDeclarationList(
-          "transform: -cobalt-ui-nav-focus-transform();",
+          "transform: -cobalt-ui-nav-focus-transform();", source_location_);
+
+  ASSERT_TRUE(style->IsDeclared(cssom::kTransformProperty));
+  scoped_refptr<cssom::TransformFunctionListValue> transform_list =
+      dynamic_cast<cssom::TransformFunctionListValue*>(
+          style->GetPropertyValue(cssom::kTransformProperty).get());
+  ASSERT_TRUE(transform_list);
+  EXPECT_TRUE(transform_list->value().HasTrait(kTraitIsDynamic));
+  EXPECT_FALSE(transform_list->value().HasTrait(kTraitUsesRelativeUnits));
+  EXPECT_TRUE(transform_list->value().HasTrait(kTraitUsesUiNavFocus));
+  ASSERT_EQ(1, transform_list->value().size());
+
+  const cssom::CobaltUiNavFocusTransformFunction* focus_function =
+      dynamic_cast<const cssom::CobaltUiNavFocusTransformFunction*>(
+          transform_list->value()[0].get());
+  ASSERT_TRUE(focus_function);
+
+  EXPECT_FLOAT_EQ(1.0f, focus_function->x_translation_scale());
+  EXPECT_FLOAT_EQ(1.0f, focus_function->y_translation_scale());
+}
+
+TEST_F(ParserTest, ParsesCobaltUiNavFocusTransformOneArgument) {
+  scoped_refptr<cssom::CSSDeclaredStyleData> style =
+      parser_.ParseStyleDeclarationList(
+          "transform: -cobalt-ui-nav-focus-transform(2);", source_location_);
+
+  ASSERT_TRUE(style->IsDeclared(cssom::kTransformProperty));
+  scoped_refptr<cssom::TransformFunctionListValue> transform_list =
+      dynamic_cast<cssom::TransformFunctionListValue*>(
+          style->GetPropertyValue(cssom::kTransformProperty).get());
+  ASSERT_TRUE(transform_list);
+  EXPECT_TRUE(transform_list->value().HasTrait(kTraitIsDynamic));
+  EXPECT_FALSE(transform_list->value().HasTrait(kTraitUsesRelativeUnits));
+  EXPECT_TRUE(transform_list->value().HasTrait(kTraitUsesUiNavFocus));
+  ASSERT_EQ(1, transform_list->value().size());
+
+  const cssom::CobaltUiNavFocusTransformFunction* focus_function =
+      dynamic_cast<const cssom::CobaltUiNavFocusTransformFunction*>(
+          transform_list->value()[0].get());
+  ASSERT_TRUE(focus_function);
+
+  EXPECT_FLOAT_EQ(2.0f, focus_function->x_translation_scale());
+  EXPECT_FLOAT_EQ(2.0f, focus_function->y_translation_scale());
+}
+
+TEST_F(ParserTest, ParsesCobaltUiNavFocusTransformTwoArguments) {
+  scoped_refptr<cssom::CSSDeclaredStyleData> style =
+      parser_.ParseStyleDeclarationList(
+          "transform: -cobalt-ui-nav-focus-transform(0.5, 2);",
           source_location_);
 
   ASSERT_TRUE(style->IsDeclared(cssom::kTransformProperty));
@@ -7240,13 +7288,15 @@
       dynamic_cast<const cssom::CobaltUiNavFocusTransformFunction*>(
           transform_list->value()[0].get());
   ASSERT_TRUE(focus_function);
+
+  EXPECT_FLOAT_EQ(0.5f, focus_function->x_translation_scale());
+  EXPECT_FLOAT_EQ(2.0f, focus_function->y_translation_scale());
 }
 
 TEST_F(ParserTest, ParsesCobaltUiNavSpotlightTransform) {
   scoped_refptr<cssom::CSSDeclaredStyleData> style =
       parser_.ParseStyleDeclarationList(
-          "transform: -cobalt-ui-nav-spotlight-transform();",
-          source_location_);
+          "transform: -cobalt-ui-nav-spotlight-transform();", source_location_);
 
   ASSERT_TRUE(style->IsDeclared(cssom::kTransformProperty));
   scoped_refptr<cssom::TransformFunctionListValue> transform_list =
@@ -8532,8 +8582,9 @@
 TEST_F(ParserTest, ParsesAnimatablePropertyNameListWithSingleValue) {
   scoped_refptr<cssom::PropertyKeyListValue> property_name_list =
       dynamic_cast<cssom::PropertyKeyListValue*>(
-          parser_.ParsePropertyValue("transition-property", "color",
-                                     source_location_)
+          parser_
+              .ParsePropertyValue("transition-property", "color",
+                                  source_location_)
               .get());
   ASSERT_TRUE(property_name_list);
 
@@ -8544,9 +8595,10 @@
 TEST_F(ParserTest, ParsesAnimatablePropertyNameListWithMultipleValues) {
   scoped_refptr<cssom::PropertyKeyListValue> property_name_list =
       dynamic_cast<cssom::PropertyKeyListValue*>(
-          parser_.ParsePropertyValue("transition-property",
-                                     "color, transform , background-color",
-                                     source_location_)
+          parser_
+              .ParsePropertyValue("transition-property",
+                                  "color, transform , background-color",
+                                  source_location_)
               .get());
   ASSERT_TRUE(property_name_list);
 
@@ -8559,8 +8611,9 @@
 TEST_F(ParserTest, ParsesTransitionPropertyWithAllValue) {
   scoped_refptr<cssom::PropertyKeyListValue> property_name_list =
       dynamic_cast<cssom::PropertyKeyListValue*>(
-          parser_.ParsePropertyValue("transition-property", "all",
-                                     source_location_)
+          parser_
+              .ParsePropertyValue("transition-property", "all",
+                                  source_location_)
               .get());
   ASSERT_TRUE(property_name_list.get());
 
@@ -8593,8 +8646,9 @@
 TEST_F(ParserTest, ParsesTimeListWithSingleValue) {
   scoped_refptr<cssom::TimeListValue> time_list_value =
       dynamic_cast<cssom::TimeListValue*>(
-          parser_.ParsePropertyValue("transition-duration", "1s",
-                                     source_location_).get());
+          parser_
+              .ParsePropertyValue("transition-duration", "1s", source_location_)
+              .get());
   ASSERT_TRUE(time_list_value.get());
 
   ASSERT_EQ(1, time_list_value->value().size());
@@ -8604,9 +8658,10 @@
 TEST_F(ParserTest, ParsesTimeListWithMultipleValues) {
   scoped_refptr<cssom::TimeListValue> time_list_value =
       dynamic_cast<cssom::TimeListValue*>(
-          parser_.ParsePropertyValue("transition-duration",
-                                     "2s, 1ms, 0, 2ms, 3s, 3ms",
-                                     source_location_).get());
+          parser_
+              .ParsePropertyValue("transition-duration",
+                                  "2s, 1ms, 0, 2ms, 3s, 3ms", source_location_)
+              .get());
   ASSERT_TRUE(time_list_value.get());
 
   ASSERT_EQ(6, time_list_value->value().size());
@@ -8621,8 +8676,10 @@
 TEST_F(ParserTest, ParsesNegativeTimeList) {
   scoped_refptr<cssom::TimeListValue> time_list_value =
       dynamic_cast<cssom::TimeListValue*>(
-          parser_.ParsePropertyValue("transition-duration", "-4s",
-                                     source_location_).get());
+          parser_
+              .ParsePropertyValue("transition-duration", "-4s",
+                                  source_location_)
+              .get());
   ASSERT_TRUE(time_list_value.get());
 
   ASSERT_EQ(1, time_list_value->value().size());
@@ -8780,52 +8837,52 @@
 }
 
 TEST_F(ParserTest, AboveRangeCubicBezierP1XParameterProduceError) {
-  EXPECT_CALL(parser_observer_,
-              OnError(
-                  "[object ParserTest]:1:1: error: cubic-bezier control point "
-                  "x values must be in the range [0, 1]."));
+  EXPECT_CALL(
+      parser_observer_,
+      OnError("[object ParserTest]:1:1: error: cubic-bezier control point "
+              "x values must be in the range [0, 1]."));
   scoped_refptr<cssom::PropertyValue> error_value = parser_.ParsePropertyValue(
       "transition-timing-function", "cubic-bezier(2, 0, 0.5, 0)",
       source_location_);
   // Test that the ease function was returned in place of the error function.
   EXPECT_TRUE(error_value->Equals(*CreateSingleTimingFunctionValue(
-                                      cssom::TimingFunction::GetEase().get())));
+      cssom::TimingFunction::GetEase().get())));
 }
 TEST_F(ParserTest, BelowRangeCubicBezierP1XParameterProduceError) {
-  EXPECT_CALL(parser_observer_,
-              OnError(
-                  "[object ParserTest]:1:1: error: cubic-bezier control point "
-                  "x values must be in the range [0, 1]."));
+  EXPECT_CALL(
+      parser_observer_,
+      OnError("[object ParserTest]:1:1: error: cubic-bezier control point "
+              "x values must be in the range [0, 1]."));
   scoped_refptr<cssom::PropertyValue> error_value = parser_.ParsePropertyValue(
       "transition-timing-function", "cubic-bezier(-1, 0, 0.5, 0)",
       source_location_);
   // Test that the ease function was returned in place of the error function.
   EXPECT_TRUE(error_value->Equals(*CreateSingleTimingFunctionValue(
-                                      cssom::TimingFunction::GetEase().get())));
+      cssom::TimingFunction::GetEase().get())));
 }
 TEST_F(ParserTest, AboveRangeCubicBezierP2XParameterProduceError) {
-  EXPECT_CALL(parser_observer_,
-              OnError(
-                  "[object ParserTest]:1:1: error: cubic-bezier control point "
-                  "x values must be in the range [0, 1]."));
+  EXPECT_CALL(
+      parser_observer_,
+      OnError("[object ParserTest]:1:1: error: cubic-bezier control point "
+              "x values must be in the range [0, 1]."));
   scoped_refptr<cssom::PropertyValue> error_value = parser_.ParsePropertyValue(
       "transition-timing-function", "cubic-bezier(0.5, 0, 2, 0)",
       source_location_);
   // Test that the ease function was returned in place of the error function.
   EXPECT_TRUE(error_value->Equals(*CreateSingleTimingFunctionValue(
-                                      cssom::TimingFunction::GetEase().get())));
+      cssom::TimingFunction::GetEase().get())));
 }
 TEST_F(ParserTest, BelowRangeCubicBezierP2XParameterProduceError) {
-  EXPECT_CALL(parser_observer_,
-              OnError(
-                  "[object ParserTest]:1:1: error: cubic-bezier control point "
-                  "x values must be in the range [0, 1]."));
+  EXPECT_CALL(
+      parser_observer_,
+      OnError("[object ParserTest]:1:1: error: cubic-bezier control point "
+              "x values must be in the range [0, 1]."));
   scoped_refptr<cssom::PropertyValue> error_value = parser_.ParsePropertyValue(
       "transition-timing-function", "cubic-bezier(0.5, 0, -1, 0)",
       source_location_);
   // Test that the ease function was returned in place of the error function.
   EXPECT_TRUE(error_value->Equals(*CreateSingleTimingFunctionValue(
-                                      cssom::TimingFunction::GetEase().get())));
+      cssom::TimingFunction::GetEase().get())));
 }
 
 TEST_F(ParserTest, ParsesTransitionShorthandOfMultipleItemsWithNoDefaults) {
@@ -9156,9 +9213,9 @@
 }
 
 TEST_F(ParserTest, ParsesTransitionShorthandWithErrorBeforeSemicolon) {
-  EXPECT_CALL(parser_observer_, OnError(
-                                    "[object ParserTest]:1:16: error: "
-                                    "unsupported property value for animation"))
+  EXPECT_CALL(parser_observer_,
+              OnError("[object ParserTest]:1:16: error: "
+                      "unsupported property value for animation"))
       .Times(AtLeast(1));
 
   scoped_refptr<cssom::CSSDeclaredStyleData> style =
@@ -9187,9 +9244,9 @@
 }
 
 TEST_F(ParserTest, ParsesTransitionShorthandWithErrorBeforeSpace) {
-  EXPECT_CALL(parser_observer_, OnError(
-                                    "[object ParserTest]:1:16: error: "
-                                    "unsupported property value for animation"))
+  EXPECT_CALL(parser_observer_,
+              OnError("[object ParserTest]:1:16: error: "
+                      "unsupported property value for animation"))
       .Times(AtLeast(1));
 
   scoped_refptr<cssom::CSSDeclaredStyleData> style =
@@ -9215,9 +9272,9 @@
 
 TEST_F(ParserTest,
        ParsesTransitionShorthandIgnoringErrorButProceedingWithNonError) {
-  EXPECT_CALL(parser_observer_, OnError(
-                                    "[object ParserTest]:1:16: error: "
-                                    "unsupported property value for animation"))
+  EXPECT_CALL(parser_observer_,
+              OnError("[object ParserTest]:1:16: error: "
+                      "unsupported property value for animation"))
       .Times(AtLeast(1));
 
   scoped_refptr<cssom::CSSDeclaredStyleData> style =
@@ -9737,8 +9794,9 @@
 
 TEST_F(ParserTest, ParsesValidMediaQuery) {
   scoped_refptr<cssom::MediaQuery> media_query =
-      parser_.ParseMediaQuery("(max-width: 1024px) and (max-height: 512px)",
-                              source_location_)
+      parser_
+          .ParseMediaQuery("(max-width: 1024px) and (max-height: 512px)",
+                           source_location_)
           .get();
   ASSERT_TRUE(media_query.get());
   // TODO: Update when media query serialization is implemented.
@@ -9754,8 +9812,9 @@
 
 TEST_F(ParserTest, ParsesValidMediaList) {
   scoped_refptr<cssom::MediaList> media_list =
-      parser_.ParseMediaList("(max-width: 1024px), (max-height: 512px)",
-                             source_location_)
+      parser_
+          .ParseMediaList("(max-width: 1024px), (max-height: 512px)",
+                          source_location_)
           .get();
   ASSERT_TRUE(media_list.get());
   ASSERT_EQ(media_list->length(), 2);
@@ -9766,9 +9825,10 @@
 
 TEST_F(ParserTest, ParsesValidMediaQueryWithIntegers) {
   scoped_refptr<cssom::MediaQuery> media_query =
-      parser_.ParseMediaQuery(
-                 "(color: 8) and (grid:0) and (color) and (scan: progressive)",
-                 source_location_)
+      parser_
+          .ParseMediaQuery(
+              "(color: 8) and (grid:0) and (color) and (scan: progressive)",
+              source_location_)
           .get();
   ASSERT_TRUE(media_query.get());
 
diff --git a/src/cobalt/cssom/cobalt_ui_nav_focus_transform_function.cc b/src/cobalt/cssom/cobalt_ui_nav_focus_transform_function.cc
index c250141..b29ec28 100644
--- a/src/cobalt/cssom/cobalt_ui_nav_focus_transform_function.cc
+++ b/src/cobalt/cssom/cobalt_ui_nav_focus_transform_function.cc
@@ -14,6 +14,7 @@
 
 #include "cobalt/cssom/cobalt_ui_nav_focus_transform_function.h"
 
+#include "base/strings/stringprintf.h"
 #include "cobalt/cssom/transform_function_visitor.h"
 #include "cobalt/math/matrix_interpolation.h"
 
@@ -21,8 +22,11 @@
 namespace cssom {
 
 CobaltUiNavFocusTransformFunction::CobaltUiNavFocusTransformFunction(
+    float x_translation_scale, float y_translation_scale,
     float progress_to_identity)
-    : progress_to_identity_(progress_to_identity) {
+    : x_translation_scale_(x_translation_scale),
+      y_translation_scale_(y_translation_scale),
+      progress_to_identity_(progress_to_identity) {
   traits_ = kTraitIsDynamic | kTraitUsesUiNavFocus;
 }
 
@@ -32,22 +36,22 @@
 }
 
 std::string CobaltUiNavFocusTransformFunction::ToString() const {
-  return "-cobalt-ui-nav-focus-transform()";
+  return base::StringPrintf("-cobalt-ui-nav-focus-transform(%.7g, %.7g)",
+                            x_translation_scale_, y_translation_scale_);
 }
 
 math::Matrix3F CobaltUiNavFocusTransformFunction::ToMatrix(
     const math::SizeF& used_size,
     const scoped_refptr<ui_navigation::NavItem>& used_ui_nav_focus) const {
   ui_navigation::NativeMatrix4 matrix;
-  if (used_ui_nav_focus &&
-      used_ui_nav_focus->GetFocusTransform(&matrix)) {
+  if (used_ui_nav_focus && used_ui_nav_focus->GetFocusTransform(&matrix)) {
     return math::InterpolateMatrices(
         math::Matrix3F::FromValues(
-            matrix.m[ 0], matrix.m[ 1], matrix.m[ 3],
-            matrix.m[ 4], matrix.m[ 5], matrix.m[ 7],
-            matrix.m[12], matrix.m[13], matrix.m[15]),
-        math::Matrix3F::Identity(),
-        progress_to_identity_);
+            // Since the UI is only rendered in 2D, ignore any scaling or
+            // shearing that might be part of a 3D transform.
+            1.0f, 0.0f, matrix.m[3] * x_translation_scale_, 0.0f, 1.0f,
+            matrix.m[7] * y_translation_scale_, 0.0f, 0.0f, 1.0f),
+        math::Matrix3F::Identity(), progress_to_identity_);
   }
   return math::Matrix3F::Identity();
 }
diff --git a/src/cobalt/cssom/cobalt_ui_nav_focus_transform_function.h b/src/cobalt/cssom/cobalt_ui_nav_focus_transform_function.h
index 210786b..ed31665 100644
--- a/src/cobalt/cssom/cobalt_ui_nav_focus_transform_function.h
+++ b/src/cobalt/cssom/cobalt_ui_nav_focus_transform_function.h
@@ -39,26 +39,33 @@
   // resulting in this transform returning the identity matrix. DOM elements
   // will only ever use progress_to_identity == 0, but intermediate animation
   // frames may use other values.
-  explicit CobaltUiNavFocusTransformFunction(
-      float progress_to_identity = 0.0f);
+  CobaltUiNavFocusTransformFunction(float x_translation_scale,
+                                    float y_translation_scale,
+                                    float progress_to_identity = 0.0f);
 
   void Accept(TransformFunctionVisitor* visitor) const override;
 
+  float x_translation_scale() const { return x_translation_scale_; }
+  float y_translation_scale() const { return y_translation_scale_; }
   float progress_to_identity() const { return progress_to_identity_; }
 
   std::string ToString() const override;
 
   math::Matrix3F ToMatrix(const math::SizeF& used_size,
-      const scoped_refptr<ui_navigation::NavItem>& used_ui_nav_focus)
-      const override;
+                          const scoped_refptr<ui_navigation::NavItem>&
+                              used_ui_nav_focus) const override;
 
   bool operator==(const CobaltUiNavFocusTransformFunction& other) const {
-    return progress_to_identity_ == other.progress_to_identity_;
+    return x_translation_scale_ == other.x_translation_scale_ &&
+           y_translation_scale_ == other.y_translation_scale_ &&
+           progress_to_identity_ == other.progress_to_identity_;
   }
 
   DEFINE_POLYMORPHIC_EQUATABLE_TYPE(CobaltUiNavFocusTransformFunction);
 
  private:
+  const float x_translation_scale_;
+  const float y_translation_scale_;
   const float progress_to_identity_;
 };
 
diff --git a/src/cobalt/cssom/interpolate_property_value.cc b/src/cobalt/cssom/interpolate_property_value.cc
index b781e1a..79f0d1f 100644
--- a/src/cobalt/cssom/interpolate_property_value.cc
+++ b/src/cobalt/cssom/interpolate_property_value.cc
@@ -17,6 +17,7 @@
 #include <algorithm>
 #include <limits>
 #include <memory>
+#include <utility>
 
 #include "base/memory/ptr_util.h"
 #include "cobalt/base/enable_if.h"
@@ -269,11 +270,23 @@
   // identity matrix when transitioning to transform none. Instead, the
   // focus transform needs to know how close to the identity transform it
   // needs to interpolate its value when evaluated.
-  float progress_to_identity_end =
-      focus_end ? focus_end->progress_to_identity() : 1.0f;
+  float progress_to_identity_end = 1.0f;
+  // Maintain the translation scale values if interpolating to identity since
+  // the interpolation to identity is also phasing out the translation scales.
+  float x_translation_scale_end = focus_function->x_translation_scale();
+  float y_translation_scale_end = focus_function->y_translation_scale();
+  if (focus_end) {
+    progress_to_identity_end = focus_end->progress_to_identity();
+    x_translation_scale_end = focus_end->x_translation_scale();
+    y_translation_scale_end = focus_end->y_translation_scale();
+  }
   animated_.reset(new CobaltUiNavFocusTransformFunction(
-      Lerp(focus_function->progress_to_identity(),
-           progress_to_identity_end, progress_)));
+      Lerp(focus_function->x_translation_scale(), x_translation_scale_end,
+           progress_),
+      Lerp(focus_function->y_translation_scale(), y_translation_scale_end,
+           progress_),
+      Lerp(focus_function->progress_to_identity(), progress_to_identity_end,
+           progress_)));
 }
 
 void AnimateTransformFunction::VisitCobaltUiNavSpotlightTransform(
@@ -289,8 +302,8 @@
   float progress_to_identity_end =
       spotlight_end ? spotlight_end->progress_to_identity() : 1.0f;
   animated_.reset(new CobaltUiNavSpotlightTransformFunction(
-      Lerp(spotlight_function->progress_to_identity(),
-           progress_to_identity_end, progress_)));
+      Lerp(spotlight_function->progress_to_identity(), progress_to_identity_end,
+           progress_)));
 }
 
 // Returns true if two given transform function lists have the same number of
@@ -339,13 +352,11 @@
   }
 
   TransformFunctionListValue* start_transform =
-      base::polymorphic_downcast<TransformFunctionListValue*>(
-          start_value);
+      base::polymorphic_downcast<TransformFunctionListValue*>(start_value);
   TransformFunctionListValue* end_transform =
       end_value->Equals(*KeywordValue::GetNone())
           ? NULL
-          : base::polymorphic_downcast<TransformFunctionListValue*>(
-                end_value);
+          : base::polymorphic_downcast<TransformFunctionListValue*>(end_value);
 
   const TransformFunctionListValue::Builder* start_functions =
       &start_transform->value();
@@ -377,8 +388,8 @@
     // into a matrix and animate the matrix using the algorithm described here:
     //   https://www.w3.org/TR/2012/WD-css3-transforms-20120228/#matrix-decomposition
     DCHECK(end_transform);
-    return new InterpolatedTransformPropertyValue(
-        start_transform, end_transform, progress);
+    return new InterpolatedTransformPropertyValue(start_transform,
+                                                  end_transform, progress);
   }
 }
 }  // namespace
@@ -545,7 +556,7 @@
       // Try to interpolate each transform function in the transform function
       // list. This can only be done if the function types match.
       interpolated_value_ = AnimateTransform(start_transform_property_value,
-          end_value_, progress_);
+                                             end_value_, progress_);
     }
   } else if (end_value_->Equals(*KeywordValue::GetNone())) {
     // Interpolate to identity matrix.
@@ -553,8 +564,7 @@
     builder.emplace_back(new MatrixFunction(math::Matrix3F::Identity()));
     interpolated_value_ = new InterpolatedTransformPropertyValue(
         start_transform_property_value,
-        new TransformFunctionListValue(std::move(builder)),
-        progress_);
+        new TransformFunctionListValue(std::move(builder)), progress_);
   } else {
     interpolated_value_ = new InterpolatedTransformPropertyValue(
         start_transform_property_value,
diff --git a/src/cobalt/cssom/interpolate_property_value_test.cc b/src/cobalt/cssom/interpolate_property_value_test.cc
index 4c49ff1..1550392 100644
--- a/src/cobalt/cssom/interpolate_property_value_test.cc
+++ b/src/cobalt/cssom/interpolate_property_value_test.cc
@@ -133,7 +133,7 @@
     }
     static scoped_refptr<PropertyValue> End() {
       TransformFunctionListValue::Builder functions;
-      functions.emplace_back(new CobaltUiNavFocusTransformFunction);
+      functions.emplace_back(new CobaltUiNavFocusTransformFunction(1.0f, 1.0f));
       return new TransformFunctionListValue(std::move(functions));
     }
   };
@@ -149,6 +149,8 @@
       dynamic_cast<const CobaltUiNavFocusTransformFunction*>(
           interpolated->value()[0].get());
   ASSERT_TRUE(focus_function);
+  EXPECT_NEAR(focus_function->x_translation_scale(), 1.0f, kErrorEpsilon);
+  EXPECT_NEAR(focus_function->y_translation_scale(), 1.0f, kErrorEpsilon);
   EXPECT_NEAR(focus_function->progress_to_identity(), 0.25f, kErrorEpsilon);
 
   math::Matrix3F value = focus_function->ToMatrix(math::SizeF(), nullptr);
@@ -163,12 +165,14 @@
   struct MakeSingleFocusTransform {
     static scoped_refptr<PropertyValue> Start() {
       TransformFunctionListValue::Builder functions;
-      functions.emplace_back(new CobaltUiNavFocusTransformFunction(0.2f));
+      functions.emplace_back(
+          new CobaltUiNavFocusTransformFunction(1.0f, 2.0f, 0.2f));
       return new TransformFunctionListValue(std::move(functions));
     }
     static scoped_refptr<PropertyValue> End() {
       TransformFunctionListValue::Builder functions;
-      functions.emplace_back(new CobaltUiNavFocusTransformFunction(0.6f));
+      functions.emplace_back(
+          new CobaltUiNavFocusTransformFunction(2.0f, 4.0f, 0.6f));
       return new TransformFunctionListValue(std::move(functions));
     }
   };
@@ -184,6 +188,8 @@
       dynamic_cast<const CobaltUiNavFocusTransformFunction*>(
           interpolated->value()[0].get());
   ASSERT_TRUE(focus_function);
+  EXPECT_NEAR(focus_function->x_translation_scale(), 1.5f, kErrorEpsilon);
+  EXPECT_NEAR(focus_function->y_translation_scale(), 3.0f, kErrorEpsilon);
   EXPECT_NEAR(focus_function->progress_to_identity(), 0.4f, kErrorEpsilon);
 
   math::Matrix3F value = focus_function->ToMatrix(math::SizeF(), nullptr);
@@ -720,8 +726,7 @@
           0.75f, MakeMultipleMismatchedTransform::Start(),
           MakeMultipleMismatchedTransform::End());
   EXPECT_TRUE(
-      interpolated
-          ->ToMatrix(math::SizeF(), nullptr)
+      interpolated->ToMatrix(math::SizeF(), nullptr)
           .IsNear(math::TranslateMatrix(3.5f, 0.0f) *
                       math::RotateMatrix(-static_cast<float>(M_PI * 3 / 8)) *
                       math::ScaleMatrix(3.5f, 1.75f),
@@ -759,8 +764,7 @@
       InterpolatePropertyTyped<InterpolatedTransformPropertyValue>(
           0.5f, interpolated, MakeMultipleMismatchedTransform::Start());
 
-  EXPECT_TRUE(next_interpolated
-                  ->ToMatrix(math::SizeF(), nullptr)
+  EXPECT_TRUE(next_interpolated->ToMatrix(math::SizeF(), nullptr)
                   .IsNear(math::RotateMatrix(-static_cast<float>(M_PI / 8)),
                           kErrorEpsilon));
 }
@@ -832,8 +836,8 @@
           0.5f, MakeMultipleMismatchedTransform::Start(),
           MakeMultipleMismatchedTransform::End());
 
-  math::Matrix3F value = interpolated->ToMatrix(
-      math::SizeF(100.0f, 200.0f), nullptr);
+  math::Matrix3F value =
+      interpolated->ToMatrix(math::SizeF(100.0f, 200.0f), nullptr);
 
   EXPECT_NEAR(cos(M_PI / 4), value(0, 0), kErrorEpsilon);
   EXPECT_NEAR(sin(M_PI / 4), value(1, 0), kErrorEpsilon);
diff --git a/src/cobalt/cssom/property_value_is_equal_test.cc b/src/cobalt/cssom/property_value_is_equal_test.cc
index ec073bb..5d64235 100644
--- a/src/cobalt/cssom/property_value_is_equal_test.cc
+++ b/src/cobalt/cssom/property_value_is_equal_test.cc
@@ -81,17 +81,24 @@
 }
 
 TEST(PropertyValueIsEqualTest, CobaltUiNavFocusTransformFunctionsAreEqual) {
-  CobaltUiNavFocusTransformFunction function_a;
-  CobaltUiNavFocusTransformFunction function_b;
+  CobaltUiNavFocusTransformFunction function_a(1.0f, 1.0f);
+  CobaltUiNavFocusTransformFunction function_b(1.0f, 1.0f);
 
   EXPECT_TRUE(function_a.Equals(function_b));
 }
 
 TEST(PropertyValueIsEqualTest, CobaltUiNavFocusTransformFunctionsAreNotEqual) {
-  CobaltUiNavFocusTransformFunction function_a(0.0f);
-  CobaltUiNavFocusTransformFunction function_b(1.0f);
+  CobaltUiNavFocusTransformFunction function_a(1.0f, 1.0f, 1.0f);
+  CobaltUiNavFocusTransformFunction function_b(1.0f, 1.0f, 0.0f);
+  CobaltUiNavFocusTransformFunction function_c(1.0f, 2.0f, 1.0f);
+  CobaltUiNavFocusTransformFunction function_d(2.0f, 1.0f, 1.0f);
 
   EXPECT_FALSE(function_a.Equals(function_b));
+  EXPECT_FALSE(function_a.Equals(function_c));
+  EXPECT_FALSE(function_a.Equals(function_d));
+  EXPECT_FALSE(function_b.Equals(function_c));
+  EXPECT_FALSE(function_b.Equals(function_d));
+  EXPECT_FALSE(function_c.Equals(function_d));
 }
 
 TEST(PropertyValueIsEqualTest, CobaltUiNavSpotlightTransformFunctionsAreEqual) {
@@ -166,17 +173,14 @@
 }
 
 TEST(PropertyValueIsEqualTest, LengthsAreEqual) {
-  scoped_refptr<LengthValue> value_a(
-      new LengthValue(1.5f, kPixelsUnit));
-  scoped_refptr<LengthValue> value_b(
-      new LengthValue(1.5f, kPixelsUnit));
+  scoped_refptr<LengthValue> value_a(new LengthValue(1.5f, kPixelsUnit));
+  scoped_refptr<LengthValue> value_b(new LengthValue(1.5f, kPixelsUnit));
 
   EXPECT_TRUE(value_a->Equals(*value_b));
 }
 
 TEST(PropertyValueIsEqualTest, LengthsAreNotEqual) {
-  scoped_refptr<LengthValue> value_a(
-      new LengthValue(1.5f, kPixelsUnit));
+  scoped_refptr<LengthValue> value_a(new LengthValue(1.5f, kPixelsUnit));
   scoped_refptr<LengthValue> value_b(
       new LengthValue(1.5f, kFontSizesAkaEmUnit));
 
diff --git a/src/cobalt/cssom/property_value_to_string_test.cc b/src/cobalt/cssom/property_value_to_string_test.cc
index 7dc1476..609e2b2 100644
--- a/src/cobalt/cssom/property_value_to_string_test.cc
+++ b/src/cobalt/cssom/property_value_to_string_test.cc
@@ -343,8 +343,8 @@
 }
 
 TEST(PropertyValueToStringTest, CobaltUiNavFocusTransformFunction) {
-  CobaltUiNavFocusTransformFunction function;
-  EXPECT_EQ(function.ToString(), "-cobalt-ui-nav-focus-transform()");
+  CobaltUiNavFocusTransformFunction function(0.5f, 1.0f);
+  EXPECT_EQ(function.ToString(), "-cobalt-ui-nav-focus-transform(0.5, 1)");
 }
 
 TEST(PropertyValueToStringTest, CobaltUiNavSpotlightTransformFunction) {
@@ -362,8 +362,9 @@
   scoped_refptr<TransformFunctionListValue> property(
       new TransformFunctionListValue(std::move(transform_list)));
 
-  EXPECT_EQ(property->ToString(), "translateX(1px) scale(2, 2) rotate(1rad) "
-      "-cobalt-ui-nav-spotlight-transform()");
+  EXPECT_EQ(property->ToString(),
+            "translateX(1px) scale(2, 2) rotate(1rad) "
+            "-cobalt-ui-nav-spotlight-transform()");
 }
 
 TEST(PropertyValueToStringTest, URLValue) {
diff --git a/src/cobalt/cssom/transform_function_visitor_test.cc b/src/cobalt/cssom/transform_function_visitor_test.cc
index 7ca695f..2fd05a5 100644
--- a/src/cobalt/cssom/transform_function_visitor_test.cc
+++ b/src/cobalt/cssom/transform_function_visitor_test.cc
@@ -63,26 +63,25 @@
 }
 
 TEST(TransformFunctionVisitorTest, VisitsTranslateXFunction) {
-  TranslateFunction translate_function(
-      TranslateFunction::kXAxis, new LengthValue(0, kPixelsUnit));
+  TranslateFunction translate_function(TranslateFunction::kXAxis,
+                                       new LengthValue(0, kPixelsUnit));
   MockTransformFunctionVisitor mock_visitor;
   EXPECT_CALL(mock_visitor, VisitTranslate(&translate_function));
   translate_function.Accept(&mock_visitor);
 }
 
 TEST(TransformFunctionVisitorTest, VisitsCobaltUiNavFocusTransform) {
-  CobaltUiNavFocusTransformFunction focus_function;
+  CobaltUiNavFocusTransformFunction focus_function(1.0f, 1.0f);
   MockTransformFunctionVisitor mock_visitor;
-  EXPECT_CALL(mock_visitor, VisitCobaltUiNavFocusTransform(
-      &focus_function));
+  EXPECT_CALL(mock_visitor, VisitCobaltUiNavFocusTransform(&focus_function));
   focus_function.Accept(&mock_visitor);
 }
 
 TEST(TransformFunctionVisitorTest, VisitsCobaltUiNavSpotlightTransform) {
   CobaltUiNavSpotlightTransformFunction spotlight_function;
   MockTransformFunctionVisitor mock_visitor;
-  EXPECT_CALL(mock_visitor, VisitCobaltUiNavSpotlightTransform(
-      &spotlight_function));
+  EXPECT_CALL(mock_visitor,
+              VisitCobaltUiNavSpotlightTransform(&spotlight_function));
   spotlight_function.Accept(&mock_visitor);
 }
 
diff --git a/src/cobalt/demos/content/media-element-demo/README.md b/src/cobalt/demos/content/media-element-demo/README.md
new file mode 100644
index 0000000..ee731e2
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/README.md
@@ -0,0 +1,27 @@
+# Cobalt Media Demo
+
+## Installation
+
+```bash
+# Install node modules
+npm install
+```
+Only needed for the first time.
+
+## Development
+
+```bash
+# Start an http server and build the package
+npm start
+```
+
+It watches script changes and recompiles the script bundle.
+
+
+## Deploy
+```bash
+# Compiles the script bundle.
+npm run build
+```
+
+Copy all files under `dist/` folder to the server.
diff --git a/src/cobalt/demos/content/media-element-demo/README.txt b/src/cobalt/demos/content/media-element-demo/README.txt
deleted file mode 100644
index 6b09933..0000000
--- a/src/cobalt/demos/content/media-element-demo/README.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-The content of this folder no longer works as local files as the media files
-have to be served in a web server in order to make it work with XMLHttpRequest.
diff --git a/src/cobalt/demos/content/media-element-demo/key-systems.html b/src/cobalt/demos/content/media-element-demo/legacy/key-systems.html
similarity index 100%
rename from src/cobalt/demos/content/media-element-demo/key-systems.html
rename to src/cobalt/demos/content/media-element-demo/legacy/key-systems.html
diff --git a/src/cobalt/demos/content/media-element-demo/key-systems.js b/src/cobalt/demos/content/media-element-demo/legacy/key-systems.js
similarity index 100%
rename from src/cobalt/demos/content/media-element-demo/key-systems.js
rename to src/cobalt/demos/content/media-element-demo/legacy/key-systems.js
diff --git a/src/cobalt/demos/content/media-element-demo/loop-playback.html b/src/cobalt/demos/content/media-element-demo/loop-playback.html
deleted file mode 100644
index 91b04c0..0000000
--- a/src/cobalt/demos/content/media-element-demo/loop-playback.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <title>Loop Playback</title>
-  <style>
-    body {
-      background-color: rgb(255, 255, 255);
-      color: #0047ab;
-      font-size: 100px;
-    }
-    video {
-      transform: translateX(100px) rotate(3deg);
-    }
-  </style>
-</head>
-<body>
-  Loop Playback
-  <script type="text/javascript" src="loop-playback.js"></script>
-</body>
-</html>
diff --git a/src/cobalt/demos/content/media-element-demo/loop-playback.js b/src/cobalt/demos/content/media-element-demo/loop-playback.js
deleted file mode 100644
index b100d12..0000000
--- a/src/cobalt/demos/content/media-element-demo/loop-playback.js
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2018 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.
-
-// The page simply plays an audio or a video stream in a loop, it can be used
-// in the following forms:
-//   loop-playback.html?url=video.mp4&type=progressive
-//   loop-playback.html?url=video.webm&type=video
-//   loop-playback.html?url=audio.mp4&type=audio
-// If the stream is adaptive, it has to be fit in memory as this demo will
-// download the whole stream at once.
-
-var video = null;
-var url = null;
-var type = null;
-
-function downloadAndAppend(source_buffer, callback) {
-  var xhr = new XMLHttpRequest;
-  xhr.open('GET', url, true);
-  xhr.responseType = 'arraybuffer';
-  xhr.addEventListener('load', function(e) {
-    var onupdateend = function() {
-      source_buffer.removeEventListener('updateend', onupdateend);
-      callback();
-    };
-    source_buffer.addEventListener('updateend', onupdateend);
-    source_buffer.appendBuffer(new Uint8Array(e.target.response));
-  });
-  xhr.send();
-}
-
-function createVideoElement() {
-  var video = document.createElement('video');
-  video.autoplay = true;
-  video.style.width = '1280px';
-  video.style.height = '720px';
-  document.body.appendChild(video);
-
-  return video;
-}
-
-function onVideoEnded() {
-  console.log('playback ended');
-  startNextVideo();
-}
-
-function startProgressiveVideo() {
-  video.src = '';
-  video.load();
-  video.src = url;
-  video.addEventListener('ended', onVideoEnded.bind());
-}
-
-function startAdaptiveVideo() {
-  video.src = '';
-  video.load();
-  var mediasource = new MediaSource;
-  mediasource.addEventListener('sourceopen', function () {
-    var source_buffer;
-    if (type == "audio") {
-      if (url.indexOf(".mp4") != -1) {
-        source_buffer = mediasource.addSourceBuffer('audio/mp4; codecs="mp4a.40.2"');
-      } else if (url.indexOf(".webm") != -1) {
-        source_buffer = mediasource.addSourceBuffer('audio/webm; codecs="opus"');
-      } else {
-        throw "unknown audio format " + url;
-      }
-    } else {
-      if (url.indexOf(".mp4") != -1) {
-        source_buffer = mediasource.addSourceBuffer('video/mp4; codecs="avc1.640028"');
-      } else if (url.indexOf(".webm") != -1) {
-        source_buffer = mediasource.addSourceBuffer('video/webm; codecs="vp9"');
-      } else {
-        throw "unknown video format " + url;
-      }
-    }
-    downloadAndAppend(source_buffer, function () {
-      mediasource.endOfStream();
-    });
-  })
-
-  video.src = window.URL.createObjectURL(mediasource);
-  video.addEventListener('ended', onVideoEnded);
-}
-
-function startNextVideo() {
-  if (type == "progressive") {
-    startProgressiveVideo();
-  } else {
-    startAdaptiveVideo();
-  }
-}
-
-function main() {
-  var get_parameters = window.location.search.substr(1).split('&');
-  for (var param of get_parameters) {
-    splitted = param.split('=');
-    if (splitted[0] == 'url') {
-      url = splitted[1];
-    } else if (splitted[0] == 'type') {
-      type = splitted[1];
-    }
-  }
-
-  if (!url) {
-    throw "url is not set.";
-  }
-
-  if (type != 'progressive' && type != 'audio' && type != 'video') {
-    throw "invalid type " + type;
-  }
-
-  video = createVideoElement();
-  startNextVideo();
-}
-
-main();
diff --git a/src/cobalt/demos/content/media-element-demo/media-element-demo.html b/src/cobalt/demos/content/media-element-demo/media-element-demo.html
deleted file mode 100644
index 665d953..0000000
--- a/src/cobalt/demos/content/media-element-demo/media-element-demo.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <title>Media Element Demo</title>
-  <style>
-    body {
-      background-color: rgb(255, 255, 255);
-      color: #0047ab;
-      font-size: 100px;
-    }
-    video {
-      transform: translateX(100px) rotate(3deg);
-    }
-  </style>
-</head>
-<body>
-  Media Element Demo
-  <script type="text/javascript" src="media-element-demo.js"></script>
-</body>
-</html>
diff --git a/src/cobalt/demos/content/media-element-demo/media-element-demo.js b/src/cobalt/demos/content/media-element-demo/media-element-demo.js
deleted file mode 100644
index 73cea11..0000000
--- a/src/cobalt/demos/content/media-element-demo/media-element-demo.js
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2015 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.
-
-// The demo does the following steps in a loop:
-//  1. Playback a progressive video.
-//  2. Playback a section of a 240p video in adaptive (DASH).
-//  3. Playback the above 240p video in 1080p.
-// All above videos have audio accompanied.
-
-// Description of the streams being played.
-var kProgressiveVideoUrl = 'progressive.mp4';
-
-var kAdaptiveVideoUrl_240p = 'dash-video-240p.mp4';
-var kAdaptiveVideoSize_240p = 2051611;
-// The size of the video that can be played for 10s.
-var kAdaptiveVideo10sChunkSize_240p = 306689;
-
-var kAdaptiveVideoUrl_1080p = 'dash-video-1080p.mp4';
-var kAdaptiveVideoSize_1080p = 22461669;
-
-var kAdaptiveAudioUrl = 'dash-audio.mp4';
-var kAdaptiveAudioSize = 1048531;
-var kAdaptiveAudioChunkSize = 720 * 1024;
-
-var is_progressive = false;
-var video = null;
-
-// Send a range request of [`begin`, `end`] (inclusive) to download content from
-// the `url` and append the downloaded content into the `source_buffer` before
-// calling the `callback` function.
-function downloadAndAppend(url, begin, end, source_buffer, callback) {
-  var xhr = new XMLHttpRequest;
-  xhr.open('GET', url, true);
-  xhr.responseType = 'arraybuffer';
-  xhr.addEventListener('load', function(e) {
-    var onupdateend = function() {
-      source_buffer.removeEventListener('updateend', onupdateend);
-      callback();
-    };
-    source_buffer.addEventListener('updateend', onupdateend);
-    source_buffer.appendBuffer(new Uint8Array(e.target.response));
-  });
-  xhr.setRequestHeader('Range', ('bytes=' + begin +'-' + end));
-  xhr.send();
-}
-
-function createVideoElement() {
-  var video = document.createElement('video');
-  video.autoplay = true;
-  video.style.width = '1280px';
-  video.style.height = '720px';
-  document.body.appendChild(video);
-
-  return video;
-}
-
-function createStatusElement(video) {
-  var status = document.createElement('div');
-  document.body.appendChild(status);
-  video.addEventListener('timeupdate', function () {
-    status.textContent = is_progressive ? 'Progressive' : 'Adaptive';
-    status.textContent += ' / time: ' + video.currentTime.toFixed(2);
-  });
-
-  return status;
-}
-
-function onVideoEnded() {
-  console.log('ended');
-  is_progressive = !is_progressive;
-  startNextVideo();
-}
-
-function startProgressiveVideo(video) {
-  video.src = '';
-  video.load();
-  video.src = kProgressiveVideoUrl;
-  video.addEventListener('ended', onVideoEnded);
-}
-
-function startAdaptiveVideo(video) {
-  video.src = '';
-  video.load();
-  var mediasource = new MediaSource;
-  mediasource.addEventListener('sourceopen', function () {
-    var video_source_buffer = mediasource.addSourceBuffer('video/mp4; codecs="avc1.640028"');
-    var audio_source_buffer = mediasource.addSourceBuffer('audio/mp4; codecs="mp4a.40.2"');
-    downloadAndAppend('dash-video-1080p.mp4', 0, 15 * 1024 * 1024, video_source_buffer, function () {
-      video_source_buffer.abort();
-      // Append the first two segments of the 240p video so we can see the transition.
-      downloadAndAppend('dash-video-240p.mp4', 0, kAdaptiveVideo10sChunkSize_240p, video_source_buffer, function () {
-        video_source_buffer.abort();
-        downloadAndAppend('dash-audio.mp4', 0, kAdaptiveAudioChunkSize, audio_source_buffer, function () {
-          mediasource.endOfStream();
-        });
-      });
-    });
-  })
-
-  video.src = window.URL.createObjectURL(mediasource);
-  video.addEventListener('ended', onVideoEnded);
-}
-
-function startNextVideo() {
-  if (is_progressive) {
-    startProgressiveVideo(video);
-  } else {
-    startAdaptiveVideo(video);
-  }
-}
-
-video = createVideoElement();
-createStatusElement(video);
-startNextVideo(video);
diff --git a/src/cobalt/demos/content/media-element-demo/multi-video-demo.html b/src/cobalt/demos/content/media-element-demo/multi-video-demo.html
deleted file mode 100644
index e79f36b..0000000
--- a/src/cobalt/demos/content/media-element-demo/multi-video-demo.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <title>Multi Video Demo</title>
-  <style>
-    body {
-      background-color: rgb(255, 255, 255);
-      color: #0047ab;
-      font-size: 10px;
-    }
-    video {
-      transform: translateX(100px) rotate(3deg);
-    }
-  </style>
-</head>
-<body>
-  <script type="text/javascript" src="multi-video-demo.js"></script>
-</body>
-</html>
diff --git a/src/cobalt/demos/content/media-element-demo/multi-video-demo.js b/src/cobalt/demos/content/media-element-demo/multi-video-demo.js
deleted file mode 100644
index cab4d58..0000000
--- a/src/cobalt/demos/content/media-element-demo/multi-video-demo.js
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2018 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.
-
-// The demo simply plays a video using get parameters in the form of:
-//   .../multi-video-demo.html?audio=filename_in_same_folder&video=filename_in_same_folder&instances=2
-
-var kAudioChunkSize = 512 * 1024;
-var kVideoChunkSize = 2 * 1024 * 1024;
-
-var kEndOfStreamOffset = -1;
-
-class Player {
-  constructor(audio_url, video_url) {
-    this.audio_offset = 0;
-    this.audio_url = audio_url;
-    this.video_offset = 0;
-    this.video_url = video_url;
-    this.video_tag = this.createVideoElement();
-    this.status = this.createStatusElement(this.video_tag);
-
-    this.video_tag.src = '';
-    this.video_tag.load();
-    this.mediasource = new MediaSource;
-    this.mediasource.addEventListener('sourceopen', this.onsourceopen.bind(this));
-    this.video_tag.src = window.URL.createObjectURL(this.mediasource);
-  }
-
-  onsourceopen() {
-    if (this.video_url.endsWith('.mp4')) {
-        this.video_source_buffer = this.mediasource.addSourceBuffer(
-                                       'video/mp4; codecs="avc1.640028"');
-      } else {
-        this.video_source_buffer = this.mediasource.addSourceBuffer(
-                                       'video/webm; codecs="vp9"');
-      }
-
-      this.audio_source_buffer = this.mediasource.addSourceBuffer(
-                                     'audio/mp4; codecs="mp4a.40.2"');
-      this.tryToDownloadAudioData();
-  }
-
-  downloadAndAppend(url, begin, end, source_buffer, callback) {
-    var xhr = new XMLHttpRequest;
-    xhr.open('GET', url, true);
-    xhr.responseType = 'arraybuffer';
-    xhr.addEventListener('load', function(e) {
-      var data = new Uint8Array(e.target.response);
-      var onupdateend = function() {
-        source_buffer.removeEventListener('updateend', onupdateend);
-        callback(data.length);
-      };
-      source_buffer.addEventListener('updateend', onupdateend);
-      source_buffer.appendBuffer(data);
-      console.log('append ' + data.length + ' bytes from '
-                  + url);
-    });
-    xhr.setRequestHeader('Range', ('bytes=' + begin +'-' + end));
-    xhr.send();
-  }
-
-  isTrackNeedAudioData() {
-    if (this.audio_offset == kEndOfStreamOffset) {
-      return false;
-    }
-    var buffer_range_size = this.audio_source_buffer.buffered.length;
-    if (buffer_range_size == 0) {
-      return true;
-    }
-    var start = this.audio_source_buffer.buffered.start(buffer_range_size - 1);
-    var end = this.audio_source_buffer.buffered.end(buffer_range_size - 1);
-    console.log('audio ' + start + '/' + end + ' ' + this.video_tag.currentTime)
-    return end - this.video_tag.currentTime <= 20;
-  }
-
-  isTrackNeedVideoData() {
-    if (this.video_offset == kEndOfStreamOffset) {
-      return false;
-    }
-    var buffer_range_size = this.video_source_buffer.buffered.length;
-    if (buffer_range_size == 0) {
-      return true;
-    }
-    var start = this.video_source_buffer.buffered.start(buffer_range_size - 1);
-    var end = this.video_source_buffer.buffered.end(buffer_range_size - 1);
-    console.log('video ' + start + '/' + end + ' ' + this.video_tag.currentTime)
-    return end - this.video_tag.currentTime <= 20;
-  }
-
-  tryToDownloadAudioData() {
-    if (!this.isTrackNeedAudioData()) {
-      if (this.isTrackNeedVideoData()) {
-        this.tryToDownloadVideoData();
-      } else {
-        window.setTimeout(this.tryToDownloadAudioData.bind(this), 1000);
-      }
-      return;
-    }
-
-    this.downloadAndAppend(
-        this.audio_url, this.audio_offset,
-        this.audio_offset + kAudioChunkSize - 1, this.audio_source_buffer,
-        this.onAudioDataDownloaded.bind(this));
-  }
-
-  onAudioDataDownloaded(length) {
-    if (length != kAudioChunkSize) {
-      this.audio_offset = kEndOfStreamOffset;
-    }
-    if (this.audio_offset != kEndOfStreamOffset) {
-      this.audio_offset += kAudioChunkSize;
-    }
-    this.tryToDownloadVideoData();
-  }
-
-  tryToDownloadVideoData() {
-    if (!this.isTrackNeedVideoData()) {
-      if (this.isTrackNeedAudioData()) {
-        this.tryToDownloadAudioData();
-      } else {
-        window.setTimeout(this.tryToDownloadVideoData.bind(this), 1000);
-      }
-      return;
-    }
-
-    this.downloadAndAppend(
-        this.video_url, this.video_offset,
-        this.video_offset + kVideoChunkSize - 1, this.video_source_buffer,
-        this.onVideoDataDownloaded.bind(this));
-  }
-
-  onVideoDataDownloaded(length) {
-    if (length != kVideoChunkSize) {
-      this.video_offset = kEndOfStreamOffset;
-    }
-    if (this.video_offset != kEndOfStreamOffset) {
-      this.video_offset += kVideoChunkSize;
-    }
-    this.tryToDownloadAudioData();
-  }
-
-  createVideoElement() {
-    var video = document.createElement('video');
-    video.autoplay = true;
-    video.style.width = '320px';
-    video.style.height = '240px';
-    document.body.appendChild(video);
-
-    return video;
-  }
-
-  createStatusElement(video) {
-    var status = document.createElement('div');
-    document.body.appendChild(status);
-    video.addEventListener('timeupdate', function () {
-      status.textContent = 'time: ' + video.currentTime.toFixed(2);
-    });
-
-    return status;
-  }
-}
-
-function main() {
-  var get_parameters = window.location.search.substr(1).split('&');
-  var audio_url, video_url, instances = 2;
-  for (var param of get_parameters) {
-    splitted = param.split('=');
-    if (splitted[0] == 'audio') {
-      audio_url = splitted[1];
-    } else if (splitted[0] == 'video') {
-      video_url = splitted[1];
-    } else if (splitted[0] == 'instances') {
-      instances = splitted[1];
-    }
-  }
-
-  if (audio_url && video_url) {
-    for (var i = 0; i < instances; ++i) {
-      new Player(audio_url, video_url);
-    }
-  } else {
-    status.textContent = "invalid get parameters " +
-                         window.location.search.substr(1);
-  }
-}
-
-main();
diff --git a/src/cobalt/demos/content/media-element-demo/package-lock.json b/src/cobalt/demos/content/media-element-demo/package-lock.json
new file mode 100644
index 0000000..698b6f8
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/package-lock.json
@@ -0,0 +1,4395 @@
+{
+  "name": "media-element-demo",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "@nodelib/fs.scandir": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz",
+      "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==",
+      "dev": true,
+      "requires": {
+        "@nodelib/fs.stat": "2.0.3",
+        "run-parallel": "^1.1.9"
+      }
+    },
+    "@nodelib/fs.stat": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz",
+      "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==",
+      "dev": true
+    },
+    "@nodelib/fs.walk": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz",
+      "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==",
+      "dev": true,
+      "requires": {
+        "@nodelib/fs.scandir": "2.1.3",
+        "fastq": "^1.6.0"
+      }
+    },
+    "@npmcli/move-file": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz",
+      "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==",
+      "dev": true,
+      "requires": {
+        "mkdirp": "^1.0.4"
+      },
+      "dependencies": {
+        "mkdirp": {
+          "version": "1.0.4",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+          "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+          "dev": true
+        }
+      }
+    },
+    "@types/json-schema": {
+      "version": "7.0.6",
+      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
+      "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
+      "dev": true
+    },
+    "@webassemblyjs/ast": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
+      "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/helper-module-context": "1.9.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+        "@webassemblyjs/wast-parser": "1.9.0"
+      }
+    },
+    "@webassemblyjs/floating-point-hex-parser": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz",
+      "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==",
+      "dev": true
+    },
+    "@webassemblyjs/helper-api-error": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz",
+      "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==",
+      "dev": true
+    },
+    "@webassemblyjs/helper-buffer": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz",
+      "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==",
+      "dev": true
+    },
+    "@webassemblyjs/helper-code-frame": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz",
+      "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/wast-printer": "1.9.0"
+      }
+    },
+    "@webassemblyjs/helper-fsm": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz",
+      "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==",
+      "dev": true
+    },
+    "@webassemblyjs/helper-module-context": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz",
+      "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0"
+      }
+    },
+    "@webassemblyjs/helper-wasm-bytecode": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz",
+      "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==",
+      "dev": true
+    },
+    "@webassemblyjs/helper-wasm-section": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz",
+      "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/helper-buffer": "1.9.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+        "@webassemblyjs/wasm-gen": "1.9.0"
+      }
+    },
+    "@webassemblyjs/ieee754": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz",
+      "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==",
+      "dev": true,
+      "requires": {
+        "@xtuc/ieee754": "^1.2.0"
+      }
+    },
+    "@webassemblyjs/leb128": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz",
+      "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==",
+      "dev": true,
+      "requires": {
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "@webassemblyjs/utf8": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz",
+      "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==",
+      "dev": true
+    },
+    "@webassemblyjs/wasm-edit": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz",
+      "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/helper-buffer": "1.9.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+        "@webassemblyjs/helper-wasm-section": "1.9.0",
+        "@webassemblyjs/wasm-gen": "1.9.0",
+        "@webassemblyjs/wasm-opt": "1.9.0",
+        "@webassemblyjs/wasm-parser": "1.9.0",
+        "@webassemblyjs/wast-printer": "1.9.0"
+      }
+    },
+    "@webassemblyjs/wasm-gen": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz",
+      "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+        "@webassemblyjs/ieee754": "1.9.0",
+        "@webassemblyjs/leb128": "1.9.0",
+        "@webassemblyjs/utf8": "1.9.0"
+      }
+    },
+    "@webassemblyjs/wasm-opt": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz",
+      "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/helper-buffer": "1.9.0",
+        "@webassemblyjs/wasm-gen": "1.9.0",
+        "@webassemblyjs/wasm-parser": "1.9.0"
+      }
+    },
+    "@webassemblyjs/wasm-parser": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz",
+      "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/helper-api-error": "1.9.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+        "@webassemblyjs/ieee754": "1.9.0",
+        "@webassemblyjs/leb128": "1.9.0",
+        "@webassemblyjs/utf8": "1.9.0"
+      }
+    },
+    "@webassemblyjs/wast-parser": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz",
+      "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/floating-point-hex-parser": "1.9.0",
+        "@webassemblyjs/helper-api-error": "1.9.0",
+        "@webassemblyjs/helper-code-frame": "1.9.0",
+        "@webassemblyjs/helper-fsm": "1.9.0",
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "@webassemblyjs/wast-printer": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz",
+      "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/wast-parser": "1.9.0",
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "@xtuc/ieee754": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+      "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+      "dev": true
+    },
+    "@xtuc/long": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+      "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+      "dev": true
+    },
+    "acorn": {
+      "version": "6.4.2",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz",
+      "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==",
+      "dev": true
+    },
+    "aggregate-error": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+      "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+      "dev": true,
+      "requires": {
+        "clean-stack": "^2.0.0",
+        "indent-string": "^4.0.0"
+      }
+    },
+    "ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "requires": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "ajv-errors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
+      "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==",
+      "dev": true
+    },
+    "ajv-keywords": {
+      "version": "3.5.2",
+      "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+      "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+      "dev": true
+    },
+    "ansi-regex": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+      "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+      "dev": true
+    },
+    "ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "dev": true,
+      "requires": {
+        "color-convert": "^1.9.0"
+      }
+    },
+    "anymatch": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
+      "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      }
+    },
+    "aproba": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+      "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+      "dev": true
+    },
+    "arr-diff": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+      "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+      "dev": true
+    },
+    "arr-flatten": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+      "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+      "dev": true
+    },
+    "arr-union": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+      "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
+      "dev": true
+    },
+    "array-union": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+      "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+      "dev": true
+    },
+    "array-unique": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+      "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+      "dev": true
+    },
+    "asn1.js": {
+      "version": "5.4.1",
+      "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
+      "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0",
+        "safer-buffer": "^2.1.0"
+      },
+      "dependencies": {
+        "bn.js": {
+          "version": "4.11.9",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+          "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+          "dev": true
+        }
+      }
+    },
+    "assert": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
+      "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
+      "dev": true,
+      "requires": {
+        "object-assign": "^4.1.1",
+        "util": "0.10.3"
+      },
+      "dependencies": {
+        "inherits": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+          "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
+          "dev": true
+        },
+        "util": {
+          "version": "0.10.3",
+          "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+          "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+          "dev": true,
+          "requires": {
+            "inherits": "2.0.1"
+          }
+        }
+      }
+    },
+    "assign-symbols": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+      "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
+      "dev": true
+    },
+    "async": {
+      "version": "2.6.3",
+      "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
+      "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
+      "dev": true,
+      "requires": {
+        "lodash": "^4.17.14"
+      }
+    },
+    "async-each": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
+      "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
+      "dev": true,
+      "optional": true
+    },
+    "atob": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+      "dev": true
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
+    },
+    "base": {
+      "version": "0.11.2",
+      "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+      "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+      "dev": true,
+      "requires": {
+        "cache-base": "^1.0.1",
+        "class-utils": "^0.3.5",
+        "component-emitter": "^1.2.1",
+        "define-property": "^1.0.0",
+        "isobject": "^3.0.1",
+        "mixin-deep": "^1.2.0",
+        "pascalcase": "^0.1.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "base64-js": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
+      "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
+      "dev": true
+    },
+    "basic-auth": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz",
+      "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=",
+      "dev": true
+    },
+    "big.js": {
+      "version": "5.2.2",
+      "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+      "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+      "dev": true
+    },
+    "binary-extensions": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
+      "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
+      "dev": true,
+      "optional": true
+    },
+    "bluebird": {
+      "version": "3.7.2",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
+    },
+    "bn.js": {
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz",
+      "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==",
+      "dev": true
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "braces": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+      "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+      "dev": true,
+      "requires": {
+        "arr-flatten": "^1.1.0",
+        "array-unique": "^0.3.2",
+        "extend-shallow": "^2.0.1",
+        "fill-range": "^4.0.0",
+        "isobject": "^3.0.1",
+        "repeat-element": "^1.1.2",
+        "snapdragon": "^0.8.1",
+        "snapdragon-node": "^2.0.1",
+        "split-string": "^3.0.2",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "brorand": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+      "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+      "dev": true
+    },
+    "browserify-aes": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+      "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+      "dev": true,
+      "requires": {
+        "buffer-xor": "^1.0.3",
+        "cipher-base": "^1.0.0",
+        "create-hash": "^1.1.0",
+        "evp_bytestokey": "^1.0.3",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "browserify-cipher": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
+      "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
+      "dev": true,
+      "requires": {
+        "browserify-aes": "^1.0.4",
+        "browserify-des": "^1.0.0",
+        "evp_bytestokey": "^1.0.0"
+      }
+    },
+    "browserify-des": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
+      "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
+      "dev": true,
+      "requires": {
+        "cipher-base": "^1.0.1",
+        "des.js": "^1.0.0",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "browserify-rsa": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+      "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "randombytes": "^2.0.1"
+      },
+      "dependencies": {
+        "bn.js": {
+          "version": "4.11.9",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+          "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+          "dev": true
+        }
+      }
+    },
+    "browserify-sign": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
+      "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^5.1.1",
+        "browserify-rsa": "^4.0.1",
+        "create-hash": "^1.2.0",
+        "create-hmac": "^1.1.7",
+        "elliptic": "^6.5.3",
+        "inherits": "^2.0.4",
+        "parse-asn1": "^5.1.5",
+        "readable-stream": "^3.6.0",
+        "safe-buffer": "^5.2.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.6.0",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+          "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+          "dev": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.2.1",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+          "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+          "dev": true
+        }
+      }
+    },
+    "browserify-zlib": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+      "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+      "dev": true,
+      "requires": {
+        "pako": "~1.0.5"
+      }
+    },
+    "buffer": {
+      "version": "4.9.2",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
+      "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
+      "dev": true,
+      "requires": {
+        "base64-js": "^1.0.2",
+        "ieee754": "^1.1.4",
+        "isarray": "^1.0.0"
+      }
+    },
+    "buffer-from": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+      "dev": true
+    },
+    "buffer-xor": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+      "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+      "dev": true
+    },
+    "builtin-status-codes": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+      "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
+      "dev": true
+    },
+    "cacache": {
+      "version": "12.0.4",
+      "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
+      "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
+      "dev": true,
+      "requires": {
+        "bluebird": "^3.5.5",
+        "chownr": "^1.1.1",
+        "figgy-pudding": "^3.5.1",
+        "glob": "^7.1.4",
+        "graceful-fs": "^4.1.15",
+        "infer-owner": "^1.0.3",
+        "lru-cache": "^5.1.1",
+        "mississippi": "^3.0.0",
+        "mkdirp": "^0.5.1",
+        "move-concurrently": "^1.0.1",
+        "promise-inflight": "^1.0.1",
+        "rimraf": "^2.6.3",
+        "ssri": "^6.0.1",
+        "unique-filename": "^1.1.1",
+        "y18n": "^4.0.0"
+      }
+    },
+    "cache-base": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+      "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+      "dev": true,
+      "requires": {
+        "collection-visit": "^1.0.0",
+        "component-emitter": "^1.2.1",
+        "get-value": "^2.0.6",
+        "has-value": "^1.0.0",
+        "isobject": "^3.0.1",
+        "set-value": "^2.0.0",
+        "to-object-path": "^0.3.0",
+        "union-value": "^1.0.0",
+        "unset-value": "^1.0.0"
+      }
+    },
+    "camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+      "dev": true
+    },
+    "chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      },
+      "dependencies": {
+        "supports-color": {
+          "version": "5.5.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^3.0.0"
+          }
+        }
+      }
+    },
+    "chokidar": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz",
+      "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "anymatch": "~3.1.1",
+        "braces": "~3.0.2",
+        "fsevents": "~2.1.2",
+        "glob-parent": "~5.1.0",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.5.0"
+      },
+      "dependencies": {
+        "braces": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+          "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "fill-range": "^7.0.1"
+          }
+        },
+        "fill-range": {
+          "version": "7.0.1",
+          "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+          "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "to-regex-range": "^5.0.1"
+          }
+        },
+        "is-number": {
+          "version": "7.0.0",
+          "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+          "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+          "dev": true,
+          "optional": true
+        },
+        "to-regex-range": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+          "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "is-number": "^7.0.0"
+          }
+        }
+      }
+    },
+    "chownr": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+      "dev": true
+    },
+    "chrome-trace-event": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz",
+      "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "cipher-base": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+      "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "class-utils": {
+      "version": "0.3.6",
+      "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+      "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+      "dev": true,
+      "requires": {
+        "arr-union": "^3.1.0",
+        "define-property": "^0.2.5",
+        "isobject": "^3.0.0",
+        "static-extend": "^0.1.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        }
+      }
+    },
+    "clean-stack": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+      "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+      "dev": true
+    },
+    "cliui": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+      "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+      "dev": true,
+      "requires": {
+        "string-width": "^3.1.0",
+        "strip-ansi": "^5.2.0",
+        "wrap-ansi": "^5.1.0"
+      }
+    },
+    "collection-visit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+      "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+      "dev": true,
+      "requires": {
+        "map-visit": "^1.0.0",
+        "object-visit": "^1.0.0"
+      }
+    },
+    "color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dev": true,
+      "requires": {
+        "color-name": "1.1.3"
+      }
+    },
+    "color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+      "dev": true
+    },
+    "colors": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
+      "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
+      "dev": true
+    },
+    "commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "dev": true
+    },
+    "commondir": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+      "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+      "dev": true
+    },
+    "component-emitter": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
+      "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
+      "dev": true
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
+    },
+    "concat-stream": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+      "dev": true,
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.2.2",
+        "typedarray": "^0.0.6"
+      }
+    },
+    "console-browserify": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
+      "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==",
+      "dev": true
+    },
+    "constants-browserify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+      "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
+      "dev": true
+    },
+    "copy-concurrently": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
+      "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
+      "dev": true,
+      "requires": {
+        "aproba": "^1.1.1",
+        "fs-write-stream-atomic": "^1.0.8",
+        "iferr": "^0.1.5",
+        "mkdirp": "^0.5.1",
+        "rimraf": "^2.5.4",
+        "run-queue": "^1.0.0"
+      }
+    },
+    "copy-descriptor": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+      "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
+      "dev": true
+    },
+    "copy-webpack-plugin": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.2.1.tgz",
+      "integrity": "sha512-VH2ZTMIBsx4p++Lmpg77adZ0KUyM5gFR/9cuTrbneNnJlcQXUFvsNariPqq2dq2kV3F2skHiDGPQCyKWy1+U0Q==",
+      "dev": true,
+      "requires": {
+        "cacache": "^15.0.5",
+        "fast-glob": "^3.2.4",
+        "find-cache-dir": "^3.3.1",
+        "glob-parent": "^5.1.1",
+        "globby": "^11.0.1",
+        "loader-utils": "^2.0.0",
+        "normalize-path": "^3.0.0",
+        "p-limit": "^3.0.2",
+        "schema-utils": "^3.0.0",
+        "serialize-javascript": "^5.0.1",
+        "webpack-sources": "^1.4.3"
+      },
+      "dependencies": {
+        "cacache": {
+          "version": "15.0.5",
+          "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz",
+          "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==",
+          "dev": true,
+          "requires": {
+            "@npmcli/move-file": "^1.0.1",
+            "chownr": "^2.0.0",
+            "fs-minipass": "^2.0.0",
+            "glob": "^7.1.4",
+            "infer-owner": "^1.0.4",
+            "lru-cache": "^6.0.0",
+            "minipass": "^3.1.1",
+            "minipass-collect": "^1.0.2",
+            "minipass-flush": "^1.0.5",
+            "minipass-pipeline": "^1.2.2",
+            "mkdirp": "^1.0.3",
+            "p-map": "^4.0.0",
+            "promise-inflight": "^1.0.1",
+            "rimraf": "^3.0.2",
+            "ssri": "^8.0.0",
+            "tar": "^6.0.2",
+            "unique-filename": "^1.1.1"
+          }
+        },
+        "chownr": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
+          "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
+          "dev": true
+        },
+        "find-cache-dir": {
+          "version": "3.3.1",
+          "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz",
+          "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==",
+          "dev": true,
+          "requires": {
+            "commondir": "^1.0.1",
+            "make-dir": "^3.0.2",
+            "pkg-dir": "^4.1.0"
+          }
+        },
+        "find-up": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+          "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+          "dev": true,
+          "requires": {
+            "locate-path": "^5.0.0",
+            "path-exists": "^4.0.0"
+          }
+        },
+        "json5": {
+          "version": "2.1.3",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
+          "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
+          "dev": true,
+          "requires": {
+            "minimist": "^1.2.5"
+          }
+        },
+        "loader-utils": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
+          "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
+          "dev": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^2.1.2"
+          }
+        },
+        "locate-path": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+          "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+          "dev": true,
+          "requires": {
+            "p-locate": "^4.1.0"
+          }
+        },
+        "lru-cache": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+          "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+          "dev": true,
+          "requires": {
+            "yallist": "^4.0.0"
+          }
+        },
+        "make-dir": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+          "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+          "dev": true,
+          "requires": {
+            "semver": "^6.0.0"
+          }
+        },
+        "mkdirp": {
+          "version": "1.0.4",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+          "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+          "dev": true
+        },
+        "p-limit": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz",
+          "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==",
+          "dev": true,
+          "requires": {
+            "p-try": "^2.0.0"
+          }
+        },
+        "p-locate": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+          "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+          "dev": true,
+          "requires": {
+            "p-limit": "^2.2.0"
+          },
+          "dependencies": {
+            "p-limit": {
+              "version": "2.3.0",
+              "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+              "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+              "dev": true,
+              "requires": {
+                "p-try": "^2.0.0"
+              }
+            }
+          }
+        },
+        "path-exists": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+          "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+          "dev": true
+        },
+        "pkg-dir": {
+          "version": "4.2.0",
+          "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+          "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+          "dev": true,
+          "requires": {
+            "find-up": "^4.0.0"
+          }
+        },
+        "rimraf": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+          "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+          "dev": true,
+          "requires": {
+            "glob": "^7.1.3"
+          }
+        },
+        "schema-utils": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
+          "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
+          "dev": true,
+          "requires": {
+            "@types/json-schema": "^7.0.6",
+            "ajv": "^6.12.5",
+            "ajv-keywords": "^3.5.2"
+          }
+        },
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        },
+        "serialize-javascript": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz",
+          "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==",
+          "dev": true,
+          "requires": {
+            "randombytes": "^2.1.0"
+          }
+        },
+        "ssri": {
+          "version": "8.0.0",
+          "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz",
+          "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==",
+          "dev": true,
+          "requires": {
+            "minipass": "^3.1.1"
+          }
+        },
+        "yallist": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+          "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+          "dev": true
+        }
+      }
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+      "dev": true
+    },
+    "corser": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
+      "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=",
+      "dev": true
+    },
+    "create-ecdh": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz",
+      "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "elliptic": "^6.5.3"
+      },
+      "dependencies": {
+        "bn.js": {
+          "version": "4.11.9",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+          "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+          "dev": true
+        }
+      }
+    },
+    "create-hash": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+      "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+      "dev": true,
+      "requires": {
+        "cipher-base": "^1.0.1",
+        "inherits": "^2.0.1",
+        "md5.js": "^1.3.4",
+        "ripemd160": "^2.0.1",
+        "sha.js": "^2.4.0"
+      }
+    },
+    "create-hmac": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+      "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+      "dev": true,
+      "requires": {
+        "cipher-base": "^1.0.3",
+        "create-hash": "^1.1.0",
+        "inherits": "^2.0.1",
+        "ripemd160": "^2.0.0",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      }
+    },
+    "cross-spawn": {
+      "version": "6.0.5",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+      "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+      "dev": true,
+      "requires": {
+        "nice-try": "^1.0.4",
+        "path-key": "^2.0.1",
+        "semver": "^5.5.0",
+        "shebang-command": "^1.2.0",
+        "which": "^1.2.9"
+      }
+    },
+    "crypto-browserify": {
+      "version": "3.12.0",
+      "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
+      "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+      "dev": true,
+      "requires": {
+        "browserify-cipher": "^1.0.0",
+        "browserify-sign": "^4.0.0",
+        "create-ecdh": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "create-hmac": "^1.1.0",
+        "diffie-hellman": "^5.0.0",
+        "inherits": "^2.0.1",
+        "pbkdf2": "^3.0.3",
+        "public-encrypt": "^4.0.0",
+        "randombytes": "^2.0.0",
+        "randomfill": "^1.0.3"
+      }
+    },
+    "cyclist": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
+      "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
+      "dev": true
+    },
+    "debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dev": true,
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
+    "decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+      "dev": true
+    },
+    "decode-uri-component": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+      "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
+      "dev": true
+    },
+    "define-property": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+      "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+      "dev": true,
+      "requires": {
+        "is-descriptor": "^1.0.2",
+        "isobject": "^3.0.1"
+      },
+      "dependencies": {
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "des.js": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
+      "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0"
+      }
+    },
+    "detect-file": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
+      "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
+      "dev": true
+    },
+    "diffie-hellman": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+      "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "miller-rabin": "^4.0.0",
+        "randombytes": "^2.0.0"
+      },
+      "dependencies": {
+        "bn.js": {
+          "version": "4.11.9",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+          "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+          "dev": true
+        }
+      }
+    },
+    "dir-glob": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+      "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+      "dev": true,
+      "requires": {
+        "path-type": "^4.0.0"
+      }
+    },
+    "domain-browser": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+      "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
+      "dev": true
+    },
+    "duplexify": {
+      "version": "3.7.1",
+      "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
+      "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
+      "dev": true,
+      "requires": {
+        "end-of-stream": "^1.0.0",
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.0",
+        "stream-shift": "^1.0.0"
+      }
+    },
+    "ecstatic": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz",
+      "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==",
+      "dev": true,
+      "requires": {
+        "he": "^1.1.1",
+        "mime": "^1.6.0",
+        "minimist": "^1.1.0",
+        "url-join": "^2.0.5"
+      }
+    },
+    "elliptic": {
+      "version": "6.5.3",
+      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
+      "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.4.0",
+        "brorand": "^1.0.1",
+        "hash.js": "^1.0.0",
+        "hmac-drbg": "^1.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.0"
+      },
+      "dependencies": {
+        "bn.js": {
+          "version": "4.11.9",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+          "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+          "dev": true
+        }
+      }
+    },
+    "emoji-regex": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+      "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+      "dev": true
+    },
+    "emojis-list": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+      "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+      "dev": true
+    },
+    "end-of-stream": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+      "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+      "dev": true,
+      "requires": {
+        "once": "^1.4.0"
+      }
+    },
+    "enhanced-resolve": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz",
+      "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "memory-fs": "^0.5.0",
+        "tapable": "^1.0.0"
+      },
+      "dependencies": {
+        "memory-fs": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
+          "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==",
+          "dev": true,
+          "requires": {
+            "errno": "^0.1.3",
+            "readable-stream": "^2.0.1"
+          }
+        }
+      }
+    },
+    "errno": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
+      "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
+      "dev": true,
+      "requires": {
+        "prr": "~1.0.1"
+      }
+    },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+      "dev": true
+    },
+    "eslint-scope": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
+      "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
+      "dev": true,
+      "requires": {
+        "esrecurse": "^4.1.0",
+        "estraverse": "^4.1.1"
+      }
+    },
+    "esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^5.2.0"
+      },
+      "dependencies": {
+        "estraverse": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
+          "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+          "dev": true
+        }
+      }
+    },
+    "estraverse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+      "dev": true
+    },
+    "eventemitter3": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
+      "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==",
+      "dev": true
+    },
+    "events": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz",
+      "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==",
+      "dev": true
+    },
+    "evp_bytestokey": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+      "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+      "dev": true,
+      "requires": {
+        "md5.js": "^1.3.4",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "expand-brackets": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+      "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+      "dev": true,
+      "requires": {
+        "debug": "^2.3.3",
+        "define-property": "^0.2.5",
+        "extend-shallow": "^2.0.1",
+        "posix-character-classes": "^0.1.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "expand-tilde": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
+      "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=",
+      "dev": true,
+      "requires": {
+        "homedir-polyfill": "^1.0.1"
+      }
+    },
+    "extend-shallow": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+      "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+      "dev": true,
+      "requires": {
+        "assign-symbols": "^1.0.0",
+        "is-extendable": "^1.0.1"
+      },
+      "dependencies": {
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "dev": true,
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
+      }
+    },
+    "extglob": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+      "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+      "dev": true,
+      "requires": {
+        "array-unique": "^0.3.2",
+        "define-property": "^1.0.0",
+        "expand-brackets": "^2.1.4",
+        "extend-shallow": "^2.0.1",
+        "fragment-cache": "^0.2.1",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "dev": true
+    },
+    "fast-glob": {
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz",
+      "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==",
+      "dev": true,
+      "requires": {
+        "@nodelib/fs.stat": "^2.0.2",
+        "@nodelib/fs.walk": "^1.2.3",
+        "glob-parent": "^5.1.0",
+        "merge2": "^1.3.0",
+        "micromatch": "^4.0.2",
+        "picomatch": "^2.2.1"
+      },
+      "dependencies": {
+        "braces": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+          "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+          "dev": true,
+          "requires": {
+            "fill-range": "^7.0.1"
+          }
+        },
+        "fill-range": {
+          "version": "7.0.1",
+          "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+          "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+          "dev": true,
+          "requires": {
+            "to-regex-range": "^5.0.1"
+          }
+        },
+        "is-number": {
+          "version": "7.0.0",
+          "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+          "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+          "dev": true
+        },
+        "micromatch": {
+          "version": "4.0.2",
+          "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
+          "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
+          "dev": true,
+          "requires": {
+            "braces": "^3.0.1",
+            "picomatch": "^2.0.5"
+          }
+        },
+        "to-regex-range": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+          "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+          "dev": true,
+          "requires": {
+            "is-number": "^7.0.0"
+          }
+        }
+      }
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "fastq": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz",
+      "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==",
+      "dev": true,
+      "requires": {
+        "reusify": "^1.0.4"
+      }
+    },
+    "figgy-pudding": {
+      "version": "3.5.2",
+      "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
+      "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==",
+      "dev": true
+    },
+    "fill-range": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+      "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^2.0.1",
+        "is-number": "^3.0.0",
+        "repeat-string": "^1.6.1",
+        "to-regex-range": "^2.1.0"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "find-cache-dir": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
+      "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+      "dev": true,
+      "requires": {
+        "commondir": "^1.0.1",
+        "make-dir": "^2.0.0",
+        "pkg-dir": "^3.0.0"
+      }
+    },
+    "find-up": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+      "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+      "dev": true,
+      "requires": {
+        "locate-path": "^3.0.0"
+      }
+    },
+    "findup-sync": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz",
+      "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==",
+      "dev": true,
+      "requires": {
+        "detect-file": "^1.0.0",
+        "is-glob": "^4.0.0",
+        "micromatch": "^3.0.4",
+        "resolve-dir": "^1.0.1"
+      }
+    },
+    "flush-write-stream": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
+      "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.3.6"
+      }
+    },
+    "follow-redirects": {
+      "version": "1.12.1",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz",
+      "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==",
+      "dev": true
+    },
+    "for-in": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+      "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
+      "dev": true
+    },
+    "fragment-cache": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+      "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+      "dev": true,
+      "requires": {
+        "map-cache": "^0.2.2"
+      }
+    },
+    "from2": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+      "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.0"
+      }
+    },
+    "fs-minipass": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
+      "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
+      "dev": true,
+      "requires": {
+        "minipass": "^3.0.0"
+      }
+    },
+    "fs-write-stream-atomic": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
+      "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "iferr": "^0.1.5",
+        "imurmurhash": "^0.1.4",
+        "readable-stream": "1 || 2"
+      }
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+      "dev": true
+    },
+    "fsevents": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
+      "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+      "dev": true,
+      "optional": true
+    },
+    "get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+      "dev": true
+    },
+    "get-value": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+      "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
+      "dev": true
+    },
+    "glob": {
+      "version": "7.1.6",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+      "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+      "dev": true,
+      "requires": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.0.4",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      }
+    },
+    "glob-parent": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
+      "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+      "dev": true,
+      "requires": {
+        "is-glob": "^4.0.1"
+      }
+    },
+    "global-modules": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
+      "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==",
+      "dev": true,
+      "requires": {
+        "global-prefix": "^3.0.0"
+      },
+      "dependencies": {
+        "global-prefix": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
+          "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
+          "dev": true,
+          "requires": {
+            "ini": "^1.3.5",
+            "kind-of": "^6.0.2",
+            "which": "^1.3.1"
+          }
+        }
+      }
+    },
+    "global-prefix": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz",
+      "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=",
+      "dev": true,
+      "requires": {
+        "expand-tilde": "^2.0.2",
+        "homedir-polyfill": "^1.0.1",
+        "ini": "^1.3.4",
+        "is-windows": "^1.0.1",
+        "which": "^1.2.14"
+      }
+    },
+    "globby": {
+      "version": "11.0.1",
+      "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz",
+      "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==",
+      "dev": true,
+      "requires": {
+        "array-union": "^2.1.0",
+        "dir-glob": "^3.0.1",
+        "fast-glob": "^3.1.1",
+        "ignore": "^5.1.4",
+        "merge2": "^1.3.0",
+        "slash": "^3.0.0"
+      }
+    },
+    "graceful-fs": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
+      "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
+      "dev": true
+    },
+    "has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+      "dev": true
+    },
+    "has-value": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+      "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+      "dev": true,
+      "requires": {
+        "get-value": "^2.0.6",
+        "has-values": "^1.0.0",
+        "isobject": "^3.0.0"
+      }
+    },
+    "has-values": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+      "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+      "dev": true,
+      "requires": {
+        "is-number": "^3.0.0",
+        "kind-of": "^4.0.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+          "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "hash-base": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
+      "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.4",
+        "readable-stream": "^3.6.0",
+        "safe-buffer": "^5.2.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.6.0",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+          "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+          "dev": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.2.1",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+          "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+          "dev": true
+        }
+      }
+    },
+    "hash.js": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+      "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.3",
+        "minimalistic-assert": "^1.0.1"
+      }
+    },
+    "he": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+      "dev": true
+    },
+    "hmac-drbg": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+      "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+      "dev": true,
+      "requires": {
+        "hash.js": "^1.0.3",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.1"
+      }
+    },
+    "homedir-polyfill": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
+      "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
+      "dev": true,
+      "requires": {
+        "parse-passwd": "^1.0.0"
+      }
+    },
+    "http-proxy": {
+      "version": "1.18.1",
+      "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
+      "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
+      "dev": true,
+      "requires": {
+        "eventemitter3": "^4.0.0",
+        "follow-redirects": "^1.0.0",
+        "requires-port": "^1.0.0"
+      }
+    },
+    "http-server": {
+      "version": "0.12.3",
+      "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz",
+      "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==",
+      "dev": true,
+      "requires": {
+        "basic-auth": "^1.0.3",
+        "colors": "^1.4.0",
+        "corser": "^2.0.1",
+        "ecstatic": "^3.3.2",
+        "http-proxy": "^1.18.0",
+        "minimist": "^1.2.5",
+        "opener": "^1.5.1",
+        "portfinder": "^1.0.25",
+        "secure-compare": "3.0.1",
+        "union": "~0.5.0"
+      }
+    },
+    "https-browserify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+      "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
+      "dev": true
+    },
+    "ieee754": {
+      "version": "1.1.13",
+      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+      "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
+      "dev": true
+    },
+    "iferr": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
+      "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
+      "dev": true
+    },
+    "ignore": {
+      "version": "5.1.8",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
+      "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
+      "dev": true
+    },
+    "import-local": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz",
+      "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==",
+      "dev": true,
+      "requires": {
+        "pkg-dir": "^3.0.0",
+        "resolve-cwd": "^2.0.0"
+      }
+    },
+    "imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+      "dev": true
+    },
+    "indent-string": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+      "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+      "dev": true
+    },
+    "infer-owner": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
+      "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
+      "dev": true
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "dev": true,
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "dev": true
+    },
+    "ini": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+      "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
+      "dev": true
+    },
+    "interpret": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
+      "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
+      "dev": true
+    },
+    "is-accessor-descriptor": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+      "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "binary-extensions": "^2.0.0"
+      }
+    },
+    "is-buffer": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+      "dev": true
+    },
+    "is-data-descriptor": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+      "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-descriptor": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+      "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+      "dev": true,
+      "requires": {
+        "is-accessor-descriptor": "^0.1.6",
+        "is-data-descriptor": "^0.1.4",
+        "kind-of": "^5.0.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+          "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+          "dev": true
+        }
+      }
+    },
+    "is-extendable": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+      "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+      "dev": true
+    },
+    "is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+      "dev": true
+    },
+    "is-fullwidth-code-point": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+      "dev": true
+    },
+    "is-glob": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+      "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+      "dev": true,
+      "requires": {
+        "is-extglob": "^2.1.1"
+      }
+    },
+    "is-number": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+      "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-plain-object": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+      "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.1"
+      }
+    },
+    "is-windows": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+      "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+      "dev": true
+    },
+    "is-wsl": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
+      "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=",
+      "dev": true
+    },
+    "isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+      "dev": true
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
+    },
+    "isobject": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+      "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+      "dev": true
+    },
+    "json-parse-better-errors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+      "dev": true
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "json5": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+      "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.2.0"
+      }
+    },
+    "kind-of": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+      "dev": true
+    },
+    "loader-runner": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
+      "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==",
+      "dev": true
+    },
+    "loader-utils": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
+      "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+      "dev": true,
+      "requires": {
+        "big.js": "^5.2.2",
+        "emojis-list": "^3.0.0",
+        "json5": "^1.0.1"
+      }
+    },
+    "locate-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+      "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+      "dev": true,
+      "requires": {
+        "p-locate": "^3.0.0",
+        "path-exists": "^3.0.0"
+      }
+    },
+    "lodash": {
+      "version": "4.17.19",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
+      "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
+      "dev": true
+    },
+    "lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "dev": true,
+      "requires": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "make-dir": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+      "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+      "dev": true,
+      "requires": {
+        "pify": "^4.0.1",
+        "semver": "^5.6.0"
+      }
+    },
+    "map-cache": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+      "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
+      "dev": true
+    },
+    "map-visit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+      "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+      "dev": true,
+      "requires": {
+        "object-visit": "^1.0.0"
+      }
+    },
+    "md5.js": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+      "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+      "dev": true,
+      "requires": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "memory-fs": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
+      "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
+      "dev": true,
+      "requires": {
+        "errno": "^0.1.3",
+        "readable-stream": "^2.0.1"
+      }
+    },
+    "merge2": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+      "dev": true
+    },
+    "micromatch": {
+      "version": "3.1.10",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+      "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+      "dev": true,
+      "requires": {
+        "arr-diff": "^4.0.0",
+        "array-unique": "^0.3.2",
+        "braces": "^2.3.1",
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "extglob": "^2.0.4",
+        "fragment-cache": "^0.2.1",
+        "kind-of": "^6.0.2",
+        "nanomatch": "^1.2.9",
+        "object.pick": "^1.3.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.2"
+      }
+    },
+    "miller-rabin": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
+      "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.0.0",
+        "brorand": "^1.0.1"
+      },
+      "dependencies": {
+        "bn.js": {
+          "version": "4.11.9",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+          "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+          "dev": true
+        }
+      }
+    },
+    "mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "dev": true
+    },
+    "minimalistic-assert": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+      "dev": true
+    },
+    "minimalistic-crypto-utils": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+      "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+      "dev": true
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true,
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "minimist": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+      "dev": true
+    },
+    "minipass": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz",
+      "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==",
+      "dev": true,
+      "requires": {
+        "yallist": "^4.0.0"
+      },
+      "dependencies": {
+        "yallist": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+          "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+          "dev": true
+        }
+      }
+    },
+    "minipass-collect": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
+      "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
+      "dev": true,
+      "requires": {
+        "minipass": "^3.0.0"
+      }
+    },
+    "minipass-flush": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
+      "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
+      "dev": true,
+      "requires": {
+        "minipass": "^3.0.0"
+      }
+    },
+    "minipass-pipeline": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
+      "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
+      "dev": true,
+      "requires": {
+        "minipass": "^3.0.0"
+      }
+    },
+    "minizlib": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
+      "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
+      "dev": true,
+      "requires": {
+        "minipass": "^3.0.0",
+        "yallist": "^4.0.0"
+      },
+      "dependencies": {
+        "yallist": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+          "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+          "dev": true
+        }
+      }
+    },
+    "mississippi": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
+      "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
+      "dev": true,
+      "requires": {
+        "concat-stream": "^1.5.0",
+        "duplexify": "^3.4.2",
+        "end-of-stream": "^1.1.0",
+        "flush-write-stream": "^1.0.0",
+        "from2": "^2.1.0",
+        "parallel-transform": "^1.1.0",
+        "pump": "^3.0.0",
+        "pumpify": "^1.3.3",
+        "stream-each": "^1.1.0",
+        "through2": "^2.0.0"
+      }
+    },
+    "mixin-deep": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+      "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
+      "dev": true,
+      "requires": {
+        "for-in": "^1.0.2",
+        "is-extendable": "^1.0.1"
+      },
+      "dependencies": {
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "dev": true,
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
+      }
+    },
+    "mkdirp": {
+      "version": "0.5.5",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+      "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.2.5"
+      }
+    },
+    "move-concurrently": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
+      "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
+      "dev": true,
+      "requires": {
+        "aproba": "^1.1.1",
+        "copy-concurrently": "^1.0.0",
+        "fs-write-stream-atomic": "^1.0.8",
+        "mkdirp": "^0.5.1",
+        "rimraf": "^2.5.4",
+        "run-queue": "^1.0.3"
+      }
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+      "dev": true
+    },
+    "nanomatch": {
+      "version": "1.2.13",
+      "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+      "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+      "dev": true,
+      "requires": {
+        "arr-diff": "^4.0.0",
+        "array-unique": "^0.3.2",
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "fragment-cache": "^0.2.1",
+        "is-windows": "^1.0.2",
+        "kind-of": "^6.0.2",
+        "object.pick": "^1.3.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      }
+    },
+    "neo-async": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+      "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+      "dev": true
+    },
+    "nice-try": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+      "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+      "dev": true
+    },
+    "node-libs-browser": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
+      "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==",
+      "dev": true,
+      "requires": {
+        "assert": "^1.1.1",
+        "browserify-zlib": "^0.2.0",
+        "buffer": "^4.3.0",
+        "console-browserify": "^1.1.0",
+        "constants-browserify": "^1.0.0",
+        "crypto-browserify": "^3.11.0",
+        "domain-browser": "^1.1.1",
+        "events": "^3.0.0",
+        "https-browserify": "^1.0.0",
+        "os-browserify": "^0.3.0",
+        "path-browserify": "0.0.1",
+        "process": "^0.11.10",
+        "punycode": "^1.2.4",
+        "querystring-es3": "^0.2.0",
+        "readable-stream": "^2.3.3",
+        "stream-browserify": "^2.0.1",
+        "stream-http": "^2.7.2",
+        "string_decoder": "^1.0.0",
+        "timers-browserify": "^2.0.4",
+        "tty-browserify": "0.0.0",
+        "url": "^0.11.0",
+        "util": "^0.11.0",
+        "vm-browserify": "^1.0.1"
+      },
+      "dependencies": {
+        "punycode": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+          "dev": true
+        }
+      }
+    },
+    "normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true
+    },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+      "dev": true
+    },
+    "object-copy": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+      "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+      "dev": true,
+      "requires": {
+        "copy-descriptor": "^0.1.0",
+        "define-property": "^0.2.5",
+        "kind-of": "^3.0.3"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "object-visit": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+      "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.0"
+      }
+    },
+    "object.pick": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+      "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.1"
+      }
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "dev": true,
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "opener": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz",
+      "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==",
+      "dev": true
+    },
+    "os-browserify": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
+      "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
+      "dev": true
+    },
+    "p-limit": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+      "dev": true,
+      "requires": {
+        "p-try": "^2.0.0"
+      }
+    },
+    "p-locate": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+      "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+      "dev": true,
+      "requires": {
+        "p-limit": "^2.0.0"
+      }
+    },
+    "p-map": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+      "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+      "dev": true,
+      "requires": {
+        "aggregate-error": "^3.0.0"
+      }
+    },
+    "p-try": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+      "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+      "dev": true
+    },
+    "pako": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+      "dev": true
+    },
+    "parallel-transform": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
+      "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
+      "dev": true,
+      "requires": {
+        "cyclist": "^1.0.1",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.1.5"
+      }
+    },
+    "parse-asn1": {
+      "version": "5.1.6",
+      "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz",
+      "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==",
+      "dev": true,
+      "requires": {
+        "asn1.js": "^5.2.0",
+        "browserify-aes": "^1.0.0",
+        "evp_bytestokey": "^1.0.0",
+        "pbkdf2": "^3.0.3",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "parse-passwd": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
+      "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
+      "dev": true
+    },
+    "pascalcase": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+      "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
+      "dev": true
+    },
+    "path-browserify": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
+      "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==",
+      "dev": true
+    },
+    "path-dirname": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+      "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
+      "dev": true,
+      "optional": true
+    },
+    "path-exists": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+      "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+      "dev": true
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+      "dev": true
+    },
+    "path-key": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+      "dev": true
+    },
+    "path-type": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+      "dev": true
+    },
+    "pbkdf2": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
+      "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
+      "dev": true,
+      "requires": {
+        "create-hash": "^1.1.2",
+        "create-hmac": "^1.1.4",
+        "ripemd160": "^2.0.1",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      }
+    },
+    "picomatch": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
+      "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
+      "dev": true
+    },
+    "pify": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+      "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+      "dev": true
+    },
+    "pkg-dir": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+      "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+      "dev": true,
+      "requires": {
+        "find-up": "^3.0.0"
+      }
+    },
+    "portfinder": {
+      "version": "1.0.28",
+      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
+      "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==",
+      "dev": true,
+      "requires": {
+        "async": "^2.6.2",
+        "debug": "^3.1.1",
+        "mkdirp": "^0.5.5"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        }
+      }
+    },
+    "posix-character-classes": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+      "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
+      "dev": true
+    },
+    "process": {
+      "version": "0.11.10",
+      "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+      "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
+      "dev": true
+    },
+    "process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+      "dev": true
+    },
+    "promise-inflight": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
+      "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
+      "dev": true
+    },
+    "prr": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
+      "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
+      "dev": true
+    },
+    "public-encrypt": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
+      "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "browserify-rsa": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "parse-asn1": "^5.0.0",
+        "randombytes": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      },
+      "dependencies": {
+        "bn.js": {
+          "version": "4.11.9",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+          "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+          "dev": true
+        }
+      }
+    },
+    "pump": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+      "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+      "dev": true,
+      "requires": {
+        "end-of-stream": "^1.1.0",
+        "once": "^1.3.1"
+      }
+    },
+    "pumpify": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+      "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+      "dev": true,
+      "requires": {
+        "duplexify": "^3.6.0",
+        "inherits": "^2.0.3",
+        "pump": "^2.0.0"
+      },
+      "dependencies": {
+        "pump": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+          "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+          "dev": true,
+          "requires": {
+            "end-of-stream": "^1.1.0",
+            "once": "^1.3.1"
+          }
+        }
+      }
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+      "dev": true
+    },
+    "qs": {
+      "version": "6.9.4",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz",
+      "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==",
+      "dev": true
+    },
+    "querystring": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+      "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+      "dev": true
+    },
+    "querystring-es3": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+      "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
+      "dev": true
+    },
+    "randombytes": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "^5.1.0"
+      }
+    },
+    "randomfill": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
+      "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+      "dev": true,
+      "requires": {
+        "randombytes": "^2.0.5",
+        "safe-buffer": "^5.1.0"
+      }
+    },
+    "readable-stream": {
+      "version": "2.3.7",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+      "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+      "dev": true,
+      "requires": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "readdirp": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
+      "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "picomatch": "^2.2.1"
+      }
+    },
+    "regex-not": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+      "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^3.0.2",
+        "safe-regex": "^1.1.0"
+      }
+    },
+    "remove-trailing-separator": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+      "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
+      "dev": true,
+      "optional": true
+    },
+    "repeat-element": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+      "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==",
+      "dev": true
+    },
+    "repeat-string": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+      "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
+      "dev": true
+    },
+    "require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+      "dev": true
+    },
+    "require-main-filename": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+      "dev": true
+    },
+    "requires-port": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+      "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
+      "dev": true
+    },
+    "resolve-cwd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
+      "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
+      "dev": true,
+      "requires": {
+        "resolve-from": "^3.0.0"
+      }
+    },
+    "resolve-dir": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz",
+      "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=",
+      "dev": true,
+      "requires": {
+        "expand-tilde": "^2.0.0",
+        "global-modules": "^1.0.0"
+      },
+      "dependencies": {
+        "global-modules": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
+          "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
+          "dev": true,
+          "requires": {
+            "global-prefix": "^1.0.1",
+            "is-windows": "^1.0.1",
+            "resolve-dir": "^1.0.0"
+          }
+        }
+      }
+    },
+    "resolve-from": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+      "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+      "dev": true
+    },
+    "resolve-url": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+      "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+      "dev": true
+    },
+    "ret": {
+      "version": "0.1.15",
+      "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+      "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+      "dev": true
+    },
+    "reusify": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+      "dev": true
+    },
+    "rimraf": {
+      "version": "2.7.1",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+      "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+      "dev": true,
+      "requires": {
+        "glob": "^7.1.3"
+      }
+    },
+    "ripemd160": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+      "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+      "dev": true,
+      "requires": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1"
+      }
+    },
+    "run-parallel": {
+      "version": "1.1.9",
+      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz",
+      "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==",
+      "dev": true
+    },
+    "run-queue": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
+      "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
+      "dev": true,
+      "requires": {
+        "aproba": "^1.1.1"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "dev": true
+    },
+    "safe-regex": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+      "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+      "dev": true,
+      "requires": {
+        "ret": "~0.1.10"
+      }
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "dev": true
+    },
+    "schema-utils": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+      "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+      "dev": true,
+      "requires": {
+        "ajv": "^6.1.0",
+        "ajv-errors": "^1.0.0",
+        "ajv-keywords": "^3.1.0"
+      }
+    },
+    "secure-compare": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
+      "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=",
+      "dev": true
+    },
+    "semver": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+      "dev": true
+    },
+    "serialize-javascript": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
+      "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
+      "dev": true,
+      "requires": {
+        "randombytes": "^2.1.0"
+      }
+    },
+    "set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+      "dev": true
+    },
+    "set-value": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+      "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^2.0.1",
+        "is-extendable": "^0.1.1",
+        "is-plain-object": "^2.0.3",
+        "split-string": "^3.0.1"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "setimmediate": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+      "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+      "dev": true
+    },
+    "sha.js": {
+      "version": "2.4.11",
+      "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+      "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "shebang-command": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+      "dev": true,
+      "requires": {
+        "shebang-regex": "^1.0.0"
+      }
+    },
+    "shebang-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+      "dev": true
+    },
+    "slash": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+      "dev": true
+    },
+    "snapdragon": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+      "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+      "dev": true,
+      "requires": {
+        "base": "^0.11.1",
+        "debug": "^2.2.0",
+        "define-property": "^0.2.5",
+        "extend-shallow": "^2.0.1",
+        "map-cache": "^0.2.2",
+        "source-map": "^0.5.6",
+        "source-map-resolve": "^0.5.0",
+        "use": "^3.1.0"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "snapdragon-node": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+      "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+      "dev": true,
+      "requires": {
+        "define-property": "^1.0.0",
+        "isobject": "^3.0.0",
+        "snapdragon-util": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "snapdragon-util": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+      "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.2.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "source-list-map": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
+      "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
+      "dev": true
+    },
+    "source-map": {
+      "version": "0.5.7",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+      "dev": true
+    },
+    "source-map-resolve": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
+      "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
+      "dev": true,
+      "requires": {
+        "atob": "^2.1.2",
+        "decode-uri-component": "^0.2.0",
+        "resolve-url": "^0.2.1",
+        "source-map-url": "^0.4.0",
+        "urix": "^0.1.0"
+      }
+    },
+    "source-map-support": {
+      "version": "0.5.19",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
+      "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
+      "dev": true,
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "source-map-url": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+      "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
+      "dev": true
+    },
+    "split-string": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+      "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^3.0.0"
+      }
+    },
+    "ssri": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
+      "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
+      "dev": true,
+      "requires": {
+        "figgy-pudding": "^3.5.1"
+      }
+    },
+    "static-extend": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+      "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+      "dev": true,
+      "requires": {
+        "define-property": "^0.2.5",
+        "object-copy": "^0.1.0"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        }
+      }
+    },
+    "stream-browserify": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
+      "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
+      "dev": true,
+      "requires": {
+        "inherits": "~2.0.1",
+        "readable-stream": "^2.0.2"
+      }
+    },
+    "stream-each": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
+      "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
+      "dev": true,
+      "requires": {
+        "end-of-stream": "^1.1.0",
+        "stream-shift": "^1.0.0"
+      }
+    },
+    "stream-http": {
+      "version": "2.8.3",
+      "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
+      "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
+      "dev": true,
+      "requires": {
+        "builtin-status-codes": "^3.0.0",
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.3.6",
+        "to-arraybuffer": "^1.0.0",
+        "xtend": "^4.0.0"
+      }
+    },
+    "stream-shift": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
+      "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
+      "dev": true
+    },
+    "string-width": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+      "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+      "dev": true,
+      "requires": {
+        "emoji-regex": "^7.0.1",
+        "is-fullwidth-code-point": "^2.0.0",
+        "strip-ansi": "^5.1.0"
+      }
+    },
+    "string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "strip-ansi": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^4.1.0"
+      }
+    },
+    "supports-color": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+      "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+      "dev": true,
+      "requires": {
+        "has-flag": "^3.0.0"
+      }
+    },
+    "tapable": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
+      "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
+      "dev": true
+    },
+    "tar": {
+      "version": "6.0.5",
+      "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz",
+      "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==",
+      "dev": true,
+      "requires": {
+        "chownr": "^2.0.0",
+        "fs-minipass": "^2.0.0",
+        "minipass": "^3.0.0",
+        "minizlib": "^2.1.1",
+        "mkdirp": "^1.0.3",
+        "yallist": "^4.0.0"
+      },
+      "dependencies": {
+        "chownr": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
+          "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
+          "dev": true
+        },
+        "mkdirp": {
+          "version": "1.0.4",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+          "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+          "dev": true
+        },
+        "yallist": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+          "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+          "dev": true
+        }
+      }
+    },
+    "terser": {
+      "version": "4.8.0",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
+      "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
+      "dev": true,
+      "requires": {
+        "commander": "^2.20.0",
+        "source-map": "~0.6.1",
+        "source-map-support": "~0.5.12"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "terser-webpack-plugin": {
+      "version": "1.4.5",
+      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz",
+      "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==",
+      "dev": true,
+      "requires": {
+        "cacache": "^12.0.2",
+        "find-cache-dir": "^2.1.0",
+        "is-wsl": "^1.1.0",
+        "schema-utils": "^1.0.0",
+        "serialize-javascript": "^4.0.0",
+        "source-map": "^0.6.1",
+        "terser": "^4.1.2",
+        "webpack-sources": "^1.4.0",
+        "worker-farm": "^1.7.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "through2": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+      "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+      "dev": true,
+      "requires": {
+        "readable-stream": "~2.3.6",
+        "xtend": "~4.0.1"
+      }
+    },
+    "timers-browserify": {
+      "version": "2.0.11",
+      "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz",
+      "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==",
+      "dev": true,
+      "requires": {
+        "setimmediate": "^1.0.4"
+      }
+    },
+    "to-arraybuffer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
+      "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
+      "dev": true
+    },
+    "to-object-path": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+      "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "to-regex": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+      "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+      "dev": true,
+      "requires": {
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "regex-not": "^1.0.2",
+        "safe-regex": "^1.1.0"
+      }
+    },
+    "to-regex-range": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+      "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+      "dev": true,
+      "requires": {
+        "is-number": "^3.0.0",
+        "repeat-string": "^1.6.1"
+      }
+    },
+    "ts-loader": {
+      "version": "8.0.3",
+      "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.3.tgz",
+      "integrity": "sha512-wsqfnVdB7xQiqhqbz2ZPLGHLPZbHVV5Qn/MNFZkCFxRU1miDyxKORucDGxKtsQJ63Rfza0udiUxWF5nHY6bpdQ==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.3.0",
+        "enhanced-resolve": "^4.0.0",
+        "loader-utils": "^1.0.2",
+        "micromatch": "^4.0.0",
+        "semver": "^6.0.0"
+      },
+      "dependencies": {
+        "braces": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+          "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+          "dev": true,
+          "requires": {
+            "fill-range": "^7.0.1"
+          }
+        },
+        "fill-range": {
+          "version": "7.0.1",
+          "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+          "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+          "dev": true,
+          "requires": {
+            "to-regex-range": "^5.0.1"
+          }
+        },
+        "is-number": {
+          "version": "7.0.0",
+          "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+          "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+          "dev": true
+        },
+        "micromatch": {
+          "version": "4.0.2",
+          "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
+          "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
+          "dev": true,
+          "requires": {
+            "braces": "^3.0.1",
+            "picomatch": "^2.0.5"
+          }
+        },
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        },
+        "to-regex-range": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+          "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+          "dev": true,
+          "requires": {
+            "is-number": "^7.0.0"
+          }
+        }
+      }
+    },
+    "tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+      "dev": true
+    },
+    "tty-browserify": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+      "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
+      "dev": true
+    },
+    "typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+      "dev": true
+    },
+    "typescript": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz",
+      "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==",
+      "dev": true
+    },
+    "union": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
+      "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
+      "dev": true,
+      "requires": {
+        "qs": "^6.4.0"
+      }
+    },
+    "union-value": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+      "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
+      "dev": true,
+      "requires": {
+        "arr-union": "^3.1.0",
+        "get-value": "^2.0.6",
+        "is-extendable": "^0.1.1",
+        "set-value": "^2.0.1"
+      }
+    },
+    "unique-filename": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
+      "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
+      "dev": true,
+      "requires": {
+        "unique-slug": "^2.0.0"
+      }
+    },
+    "unique-slug": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
+      "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
+      "dev": true,
+      "requires": {
+        "imurmurhash": "^0.1.4"
+      }
+    },
+    "unset-value": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+      "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+      "dev": true,
+      "requires": {
+        "has-value": "^0.3.1",
+        "isobject": "^3.0.0"
+      },
+      "dependencies": {
+        "has-value": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+          "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+          "dev": true,
+          "requires": {
+            "get-value": "^2.0.3",
+            "has-values": "^0.1.4",
+            "isobject": "^2.0.0"
+          },
+          "dependencies": {
+            "isobject": {
+              "version": "2.1.0",
+              "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+              "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+              "dev": true,
+              "requires": {
+                "isarray": "1.0.0"
+              }
+            }
+          }
+        },
+        "has-values": {
+          "version": "0.1.4",
+          "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+          "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
+          "dev": true
+        }
+      }
+    },
+    "upath": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
+      "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
+      "dev": true,
+      "optional": true
+    },
+    "uri-js": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz",
+      "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==",
+      "dev": true,
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "urix": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+      "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
+      "dev": true
+    },
+    "url": {
+      "version": "0.11.0",
+      "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+      "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+      "dev": true,
+      "requires": {
+        "punycode": "1.3.2",
+        "querystring": "0.2.0"
+      },
+      "dependencies": {
+        "punycode": {
+          "version": "1.3.2",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+          "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+          "dev": true
+        }
+      }
+    },
+    "url-join": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz",
+      "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=",
+      "dev": true
+    },
+    "use": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+      "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
+      "dev": true
+    },
+    "util": {
+      "version": "0.11.1",
+      "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
+      "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==",
+      "dev": true,
+      "requires": {
+        "inherits": "2.0.3"
+      },
+      "dependencies": {
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "dev": true
+        }
+      }
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+      "dev": true
+    },
+    "v8-compile-cache": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
+      "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
+      "dev": true
+    },
+    "vm-browserify": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
+      "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
+      "dev": true
+    },
+    "watchpack": {
+      "version": "1.7.4",
+      "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz",
+      "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==",
+      "dev": true,
+      "requires": {
+        "chokidar": "^3.4.1",
+        "graceful-fs": "^4.1.2",
+        "neo-async": "^2.5.0",
+        "watchpack-chokidar2": "^2.0.0"
+      }
+    },
+    "watchpack-chokidar2": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz",
+      "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "chokidar": "^2.1.8"
+      },
+      "dependencies": {
+        "anymatch": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
+          "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "micromatch": "^3.1.4",
+            "normalize-path": "^2.1.1"
+          },
+          "dependencies": {
+            "normalize-path": {
+              "version": "2.1.1",
+              "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+              "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "remove-trailing-separator": "^1.0.1"
+              }
+            }
+          }
+        },
+        "binary-extensions": {
+          "version": "1.13.1",
+          "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
+          "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
+          "dev": true,
+          "optional": true
+        },
+        "chokidar": {
+          "version": "2.1.8",
+          "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
+          "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "anymatch": "^2.0.0",
+            "async-each": "^1.0.1",
+            "braces": "^2.3.2",
+            "fsevents": "^1.2.7",
+            "glob-parent": "^3.1.0",
+            "inherits": "^2.0.3",
+            "is-binary-path": "^1.0.0",
+            "is-glob": "^4.0.0",
+            "normalize-path": "^3.0.0",
+            "path-is-absolute": "^1.0.0",
+            "readdirp": "^2.2.1",
+            "upath": "^1.1.1"
+          }
+        },
+        "fsevents": {
+          "version": "1.2.13",
+          "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
+          "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
+          "dev": true,
+          "optional": true
+        },
+        "glob-parent": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+          "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "is-glob": "^3.1.0",
+            "path-dirname": "^1.0.0"
+          },
+          "dependencies": {
+            "is-glob": {
+              "version": "3.1.0",
+              "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+              "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "is-extglob": "^2.1.0"
+              }
+            }
+          }
+        },
+        "is-binary-path": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+          "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "binary-extensions": "^1.0.0"
+          }
+        },
+        "readdirp": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
+          "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "graceful-fs": "^4.1.11",
+            "micromatch": "^3.1.10",
+            "readable-stream": "^2.0.2"
+          }
+        }
+      }
+    },
+    "webpack": {
+      "version": "4.44.2",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz",
+      "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/helper-module-context": "1.9.0",
+        "@webassemblyjs/wasm-edit": "1.9.0",
+        "@webassemblyjs/wasm-parser": "1.9.0",
+        "acorn": "^6.4.1",
+        "ajv": "^6.10.2",
+        "ajv-keywords": "^3.4.1",
+        "chrome-trace-event": "^1.0.2",
+        "enhanced-resolve": "^4.3.0",
+        "eslint-scope": "^4.0.3",
+        "json-parse-better-errors": "^1.0.2",
+        "loader-runner": "^2.4.0",
+        "loader-utils": "^1.2.3",
+        "memory-fs": "^0.4.1",
+        "micromatch": "^3.1.10",
+        "mkdirp": "^0.5.3",
+        "neo-async": "^2.6.1",
+        "node-libs-browser": "^2.2.1",
+        "schema-utils": "^1.0.0",
+        "tapable": "^1.1.3",
+        "terser-webpack-plugin": "^1.4.3",
+        "watchpack": "^1.7.4",
+        "webpack-sources": "^1.4.1"
+      }
+    },
+    "webpack-cli": {
+      "version": "3.3.12",
+      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz",
+      "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.4.2",
+        "cross-spawn": "^6.0.5",
+        "enhanced-resolve": "^4.1.1",
+        "findup-sync": "^3.0.0",
+        "global-modules": "^2.0.0",
+        "import-local": "^2.0.0",
+        "interpret": "^1.4.0",
+        "loader-utils": "^1.4.0",
+        "supports-color": "^6.1.0",
+        "v8-compile-cache": "^2.1.1",
+        "yargs": "^13.3.2"
+      }
+    },
+    "webpack-sources": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
+      "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
+      "dev": true,
+      "requires": {
+        "source-list-map": "^2.0.0",
+        "source-map": "~0.6.1"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "dev": true,
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "which-module": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+      "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+      "dev": true
+    },
+    "worker-farm": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
+      "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==",
+      "dev": true,
+      "requires": {
+        "errno": "~0.1.7"
+      }
+    },
+    "wrap-ansi": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+      "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.0",
+        "string-width": "^3.0.0",
+        "strip-ansi": "^5.0.0"
+      }
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+      "dev": true
+    },
+    "xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "dev": true
+    },
+    "y18n": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+      "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+      "dev": true
+    },
+    "yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+      "dev": true
+    },
+    "yargs": {
+      "version": "13.3.2",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
+      "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
+      "dev": true,
+      "requires": {
+        "cliui": "^5.0.0",
+        "find-up": "^3.0.0",
+        "get-caller-file": "^2.0.1",
+        "require-directory": "^2.1.1",
+        "require-main-filename": "^2.0.0",
+        "set-blocking": "^2.0.0",
+        "string-width": "^3.0.0",
+        "which-module": "^2.0.0",
+        "y18n": "^4.0.0",
+        "yargs-parser": "^13.1.2"
+      }
+    },
+    "yargs-parser": {
+      "version": "13.1.2",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
+      "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
+      "dev": true,
+      "requires": {
+        "camelcase": "^5.0.0",
+        "decamelize": "^1.2.0"
+      }
+    }
+  }
+}
diff --git a/src/cobalt/demos/content/media-element-demo/package.json b/src/cobalt/demos/content/media-element-demo/package.json
new file mode 100644
index 0000000..bed93af
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/package.json
@@ -0,0 +1,35 @@
+{
+  "name": "media-element-demo",
+  "version": "1.0.0",
+  "main": "index.js",
+  "scripts": {
+    "start": "npm run watch & npm run server",
+    "build": "webpack",
+    "server": "http-server dist",
+    "watch": "webpack --watch"
+  },
+  "author": "",
+  "license": "ISC",
+  "devDependencies": {
+    "copy-webpack-plugin": "^6.2.1",
+    "http-server": "^0.12.3",
+    "ts-loader": "^8.0.3",
+    "typescript": "^4.0.2",
+    "webpack": "^4.44.2",
+    "webpack-cli": "^3.3.12"
+  },
+  "devDependencies-comments": {
+    "copy-webpack-plugin": "Allows webpack to copy files from public/ to dist/.",
+    "http-server": "An http server to host the site. Used as the dev server.",
+    "ts-loader": "Allows webpack to load ts files.",
+    "typescript": "The famous TypeScript.",
+    "webpack": "Bundles scripts into one file and generate dist/.",
+    "webpack-cli": "Allows us to run `webpack --watch`."
+  },
+  "dependencies": {
+    "bluebird": "^3.7.2"
+  },
+  "dependencies-comments": {
+    "bluebird": "Replaces native Promise in Cobalt because Cobalt doesn't support unhandledrejection natively."
+  }
+}
diff --git a/src/cobalt/demos/content/media-element-demo/progressive-demo.html b/src/cobalt/demos/content/media-element-demo/progressive-demo.html
deleted file mode 100644
index 2d10dde2..0000000
--- a/src/cobalt/demos/content/media-element-demo/progressive-demo.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <title>Progressive Demo</title>
-  <style>
-    body {
-      background-color: rgb(255, 255, 255);
-      color: #0047ab;
-      font-size: 100px;
-    }
-    .small {
-      margin: 100px;
-      border: 10px solid blue;
-      width: 960px;
-      height: 540px;
-    }
-    .big {
-      margin: 10px;
-      border: 10px solid blue;
-      width: 1280px;
-      height: 720px;
-    }
-  </style>
-</head>
-<body>
-  <div>Progressive Demo</div>
-  <video autoplay loop id="v" class="small" src="progressive.mp4"></video>
-  <script>
-    window.setInterval(function() {
-      if (document.getElementById('v').className === 'big')
-        document.getElementById('v').className = 'small';
-      else
-        document.getElementById('v').className = 'big';
-    }, 3000);
-  </script>
-</body>
-</html>
diff --git a/src/cobalt/demos/content/media-element-demo/vp9_720p.webm b/src/cobalt/demos/content/media-element-demo/public/assets/vp9_720p.webm
similarity index 100%
rename from src/cobalt/demos/content/media-element-demo/vp9_720p.webm
rename to src/cobalt/demos/content/media-element-demo/public/assets/vp9_720p.webm
Binary files differ
diff --git a/src/cobalt/demos/content/media-element-demo/public/index.html b/src/cobalt/demos/content/media-element-demo/public/index.html
new file mode 100644
index 0000000..b56dfe7
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/public/index.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <title>Media Element Demo</title>
+  <link rel="stylesheet" type="text/css" href="styles/app.css">
+</head>
+
+<body>
+  <div id="error-logger"></div>
+  <div id="router"></div>
+  <script async type="text/javascript" src="bundle.js"></script>
+</body>
+
+</html>
diff --git a/src/cobalt/demos/content/media-element-demo/public/styles/app.css b/src/cobalt/demos/content/media-element-demo/public/styles/app.css
new file mode 100644
index 0000000..0b425ab
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/public/styles/app.css
@@ -0,0 +1,14 @@
+body {
+  background: #000000;
+  color: #ffffff;
+  font-size: 20px;
+}
+
+.error {
+  background:#f8d7da;
+  border-radius: .25rem;
+  border: 1px transparent solid;
+  color: #721c24;
+  margin-bottom: .25rem;
+  padding: .75rem 1.25rem;
+}
diff --git a/src/cobalt/demos/content/media-element-demo/src/components/component.ts b/src/cobalt/demos/content/media-element-demo/src/components/component.ts
new file mode 100644
index 0000000..55f9474
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/components/component.ts
@@ -0,0 +1,60 @@
+type ComponentCtor<Props> = new (props: Props) => Component<Props>;
+type ComponentFunc<Props> = (props: Props) => string;
+
+/** Base component class. */
+export class Component<Props> {
+  el!: Element;
+
+  constructor(readonly props: Props) {
+    this.props = props;
+  }
+
+  /** Lifecycle method to generate static templates. */
+  render(props: Props): string {
+    throw new Error('Must be overridden.');
+  }
+
+  /**
+   * Lifecycle method called after `render()`. It's a good opportunity to add
+   * dynamic content and add event listeners.
+   */
+  afterRender() {}
+}
+
+/** Caches the component instances hooked to DOM elements. */
+const componentMap = new WeakMap<Element, Component<unknown>>();
+
+function isComponentCtor<Props>(
+    CtorOrFunc: ComponentCtor<Props>|
+    ComponentFunc<Props>): CtorOrFunc is ComponentCtor<Props> {
+  if (CtorOrFunc.prototype instanceof Component) {
+    return true;
+  }
+  return false;
+}
+
+/**
+ * Renders a component to the container element.
+ * @param CtorOrFunc Can be either a component contructor or a component
+ *     function. The component function is easier to write but does not keep
+ *     track of internal states.
+ * @param props Component props.
+ * @param container The container element of the component.
+ */
+export function renderComponent<Props>(
+    CtorOrFunc: ComponentCtor<Props>|ComponentFunc<Props>, props: Props,
+    container: Element) {
+  if (!isComponentCtor(CtorOrFunc)) {
+    // Function based component
+    container.innerHTML = CtorOrFunc(props);
+    return;
+  }
+  let component = componentMap.get(container) as Component<Props>| undefined;
+  if (!(component instanceof CtorOrFunc)) {
+    component = new CtorOrFunc(props);
+    componentMap.set(container, component);
+    component.el = container;
+  }
+  container.innerHTML = component.render(props);
+  component.afterRender();
+}
diff --git a/src/cobalt/demos/content/media-element-demo/src/components/download_buffer_info.ts b/src/cobalt/demos/content/media-element-demo/src/components/download_buffer_info.ts
new file mode 100644
index 0000000..ed059d1
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/components/download_buffer_info.ts
@@ -0,0 +1,23 @@
+import {MediaDownloaderStatus} from '../utils/download_buffer';
+import {Media} from '../utils/media';
+
+interface Props {
+  mediaList: Media[];
+  reportMap: Map<Media, MediaDownloaderStatus>;
+}
+
+/** A component that displays the download buffer info. */
+export function DownloadBufferInfo({mediaList, reportMap}: Props) {
+  const elements = mediaList.map((video) => {
+    const report = reportMap.get(video);
+    if (!report) {
+      throw new Error(`Download buffer info for ${video.url} not found.`);
+    }
+    return `
+      <div>
+        ${video.url}: downloaded ${report.downloadedBytes} bytes ${
+        report.finished ? '[Done]' : ''}, queued ${report.queuedChunks} chunks.
+      </div>`;
+  });
+  return elements.join('');
+}
diff --git a/src/cobalt/demos/content/media-element-demo/src/components/error_logger.ts b/src/cobalt/demos/content/media-element-demo/src/components/error_logger.ts
new file mode 100644
index 0000000..81750dc
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/components/error_logger.ts
@@ -0,0 +1,46 @@
+import {errors} from '../utils/shared_values';
+
+import {Component} from './component';
+
+/** A component that holds error logs. */
+export class ErrorLogger extends Component<{}> {
+  /** @override */
+  render() {
+    return '';
+  }
+
+  /** @override */
+  afterRender() {
+    errors.observe((messages: string[]) => {
+      this.renderError(messages);
+    });
+    window.addEventListener('error', (message) => {
+      if (typeof message === 'string') {
+        logError(message);
+        return;
+      }
+    });
+    window.addEventListener('unhandledrejection', (evt: any) => {
+      // In cobalt where bluebird is used, the reason is under evt.detail.reason
+      const reason = evt.reason ?? evt.detail.reason;
+      logError(reason);
+    });
+  }
+
+  private renderError(messages: string[]) {
+    const elements = messages.map(message => `
+      <div class="error">${message}</div>
+    `);
+    this.el.innerHTML = elements.join('');
+  }
+}
+
+/**
+ * Logs an error message. The logged error will be automatically picked up by
+ * the error logger.
+ * @param message The error message.
+ */
+export function logError(message: string) {
+  const current = errors.get();
+  errors.set([...current, message]);
+}
diff --git a/src/cobalt/demos/content/media-element-demo/src/components/player.ts b/src/cobalt/demos/content/media-element-demo/src/components/player.ts
new file mode 100644
index 0000000..7bd9c17
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/components/player.ts
@@ -0,0 +1,240 @@
+import {DownloadBuffer} from '../utils/download_buffer';
+import {PlayMode, SwitchMode} from '../utils/enums';
+import {LimitedSourceBuffer} from '../utils/limited_source_buffer';
+import {Media} from '../utils/media';
+
+import {Component, renderComponent} from './component';
+import {DownloadBufferInfo} from './download_buffer_info';
+import {SourceBufferInfo} from './source_buffer_info';
+import {VideoInfo} from './video_info';
+
+interface Props {
+  video?: string|string[];
+  audio?: string|string[];
+  switchMode?: SwitchMode;
+}
+
+/** A component that controls video playback. */
+export class Player extends Component<Props> {
+  /** The videos to play. */
+  private readonly videos: Media[];
+
+  /** The audios to play. */
+  private readonly audios: Media[];
+
+  /** The <video> element. */
+  private videoEl!: HTMLVideoElement;
+
+  /** The element displaying video download buffer info. */
+  private videoDownloadBufferInfo!: Element;
+
+  /** The element displaying audio download buffer info. */
+  private audioDownloadBufferInfo!: Element;
+
+  /** The element displaying video source buffer info. */
+  private videoSourceBufferInfo!: Element;
+
+  /** The element displaying audio source buffer info. */
+  private audioSourceBufferInfo!: Element;
+
+  /** The element displaying video info. */
+  private videoInfo!: Element;
+
+  /**
+   * Switch mode defines whether to create a new playback session or <video>
+   * element after a media finishes playing.
+   */
+  private switchMode?: SwitchMode;
+
+  /** Play mode defines whether to play progressively or adaptively. */
+  private playMode?: PlayMode;
+
+  constructor(props: Props) {
+    super(props);
+    this.videos = convertToMediaArray(props.video);
+    this.audios = convertToMediaArray(props.audio);
+  }
+
+  /** @override */
+  render() {
+    return `
+      <div class="video-container"></div>
+      <div class="video-info"></div>
+      <div class="video-source-buffer-info"></div>
+      <div class="audio-source-buffer-info"></div>
+      <div class="video-download-buffer-info"></div>
+      <div class="audio-download-buffer-info"></div>
+    `;
+  }
+
+  /** @override */
+  async afterRender() {
+    this.videoInfo = this.el.querySelector('.video-info') as HTMLVideoElement;
+    this.videoEl = document.createElement('video');
+    this.videoEl.style.width = '1280px';
+    this.videoEl.style.height = '720px';
+    this.videoEl.addEventListener('timeupdate', () => {
+      this.renderVideoInfo();
+    });
+    this.videoEl.addEventListener('durationchange', (evt) => {
+      this.renderVideoInfo();
+    });
+    this.el.querySelector('.video-container')!.appendChild(this.videoEl);
+
+    this.videoSourceBufferInfo =
+        this.el.querySelector('.video-source-buffer-info')!;
+    this.audioSourceBufferInfo =
+        this.el.querySelector('.audio-source-buffer-info')!;
+    this.videoDownloadBufferInfo =
+        this.el.querySelector('.video-download-buffer-info')!;
+    this.audioDownloadBufferInfo =
+        this.el.querySelector('.audio-download-buffer-info')!;
+    this.play();
+  }
+
+  private renderVideoInfo() {
+    renderComponent(
+        VideoInfo, {
+          duration: this.videoEl.duration,
+          currentTime: this.videoEl.currentTime,
+        },
+        this.videoInfo);
+  }
+
+  private async play() {
+    // Do not reorder methods.
+    await this.validateParams();
+    await this.initPlayMode();
+    await this.initSwitchMode(this.props.switchMode);
+    if (this.playMode === PlayMode.PROGRESSIVE) {
+      this.playProgressiveVideo();
+    } else {
+      this.playAdaptiveVideo();
+    }
+  }
+
+  private async initSwitchMode(override?: SwitchMode) {
+    // TODO: Add support for RELOAD and RECREATE_ELEMENT.
+    this.switchMode = SwitchMode.NORMAL;
+  }
+
+  private async initPlayMode() {
+    // Because `validateProgressive_` ensures progressive videos cannot be
+    // played together with adaptive audios/videos. We can decide whether to use
+    // progressive mode by checking the type of the first videos.
+    if (this.videos.length > 0 && await this.videos[0].isProgressive()) {
+      this.playMode = PlayMode.PROGRESSIVE;
+    } else {
+      this.playMode = PlayMode.ADAPTIVE;
+    }
+  }
+
+  /**
+   * Plays all videos as progressive videos, assuming only progressive mp4
+   * videos are provided.
+   */
+  private playProgressiveVideo(videoIndex = 0) {
+    const currentMedia = this.videos[videoIndex];
+    if (!currentMedia) {
+      return;
+    }
+    this.videoEl.src = currentMedia.url;
+    const handleVideoEnd = () => {
+      this.videoEl.removeEventListener('ended', handleVideoEnd);
+      this.playProgressiveVideo(videoIndex++);
+    };
+    this.videoEl.addEventListener('ended', handleVideoEnd);
+    this.videoEl.play();
+  }
+
+  /**
+   * Plays all videos as adaptive videos.
+   * TODO: dynmaically calculate the source buffer MIME.
+   */
+  private playAdaptiveVideo() {
+    const ms = new MediaSource();
+    this.videoEl.src = URL.createObjectURL(ms);
+    ms.addEventListener('sourceopen', async () => {
+      if (this.videos.length > 0) {
+        const videoSourceBuffer =
+            ms.addSourceBuffer('video/mp4; codecs="avc1.640028"');
+        videoSourceBuffer.addEventListener('updateend', () => {
+          renderComponent(
+              SourceBufferInfo,
+              {name: 'Video', sourceBuffer: videoSourceBuffer},
+              this.videoSourceBufferInfo);
+        });
+        const downloadBuffer = new DownloadBuffer(this.videos);
+        downloadBuffer.register((reportMap) => {
+          renderComponent(
+              DownloadBufferInfo, {mediaList: this.videos, reportMap},
+              this.videoDownloadBufferInfo);
+        });
+        new LimitedSourceBuffer(
+            this.videoEl, videoSourceBuffer, this.videos, downloadBuffer);
+      }
+      if (this.audios.length > 0) {
+        const audioSourceBuffer =
+            ms.addSourceBuffer('audio/mp4; codecs="mp4a.40.2"');
+        audioSourceBuffer.addEventListener('updateend', () => {
+          renderComponent(
+              SourceBufferInfo,
+              {name: 'Audio', sourceBuffer: audioSourceBuffer},
+              this.audioSourceBufferInfo);
+        });
+        const downloadBuffer = new DownloadBuffer(this.audios);
+        downloadBuffer.register(
+            (reportMap) => {renderComponent(
+                DownloadBufferInfo, {mediaList: this.audios, reportMap},
+                this.audioDownloadBufferInfo)});
+        new LimitedSourceBuffer(
+            this.videoEl, audioSourceBuffer, this.audios, downloadBuffer);
+      }
+    });
+    this.videoEl.play();
+  }
+
+  private async validateParams() {
+    this.validateMediaExists();
+    await this.validateProgressive_();
+  }
+
+  /** Validates at least one video or audio is provided. */
+  private validateMediaExists() {
+    if (this.videos.length === 0 && this.audios.length === 0) {
+      throw new Error(
+          `No audio or video is specified. Please pass values to 'audio=' or ` +
+          `'video=' params.`);
+    }
+  }
+
+  /**
+   * Validates progressive videos are not played together with adaptive audios.
+   */
+  async validateProgressive_() {
+    let progressiveVideosCount = 0;
+    for (const video of this.videos) {
+      if (await video.isProgressive()) {
+        progressiveVideosCount++;
+      }
+    }
+    if (progressiveVideosCount > 0 &&
+        (progressiveVideosCount !== this.videos.length ||
+         this.audios.length > 0)) {
+      throw new Error(
+          'Progressive video[s] cannot be played together with adaptive ' +
+          'video[s] and audio[s]');
+    }
+  }
+}
+
+/** Converts filenames to a list of media. */
+function convertToMediaArray(filenames?: string|string[]): Media[] {
+  if (!filenames) {
+    return [];
+  }
+  if (!Array.isArray(filenames)) {
+    return [new Media(filenames)];
+  }
+  return filenames.map(filename => new Media(filename));
+}
diff --git a/src/cobalt/demos/content/media-element-demo/src/components/router.ts b/src/cobalt/demos/content/media-element-demo/src/components/router.ts
new file mode 100644
index 0000000..11e3d75
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/components/router.ts
@@ -0,0 +1,41 @@
+import {Component, renderComponent} from './component';
+
+import {Watch} from './watch';
+
+/** A component that parses URL params and decide what page to load. */
+export class Router extends Component<{}> {
+  /** @override */
+  render() {
+    return `<div class="router-content"></div>`;
+  }
+
+  /** @override */
+  afterRender() {
+    const params = parseParams();
+    renderComponent(Watch, params, this.el.querySelector('.router-content')!);
+  }
+}
+
+/**
+ * Converts the GET params to an object.
+ * @return The parsed params object.
+ */
+function parseParams(): Record<string, string|string[]> {
+  const params: Record<string, string|string[]> = {};
+  if (!location.search) {
+    return params;
+  }
+  const terms = location.search.slice(1).split('&');
+  for (const term of terms) {
+    const [key, value] = term.split('=');
+    if (params[key] !== undefined) {
+      throw new Error(`Detected multiple values for ${key}`);
+    }
+    if (value.includes(',')) {
+      params[key] = value.split(',');
+    } else {
+      params[key] = value;
+    }
+  }
+  return params;
+}
diff --git a/src/cobalt/demos/content/media-element-demo/src/components/source_buffer_info.ts b/src/cobalt/demos/content/media-element-demo/src/components/source_buffer_info.ts
new file mode 100644
index 0000000..92343f3
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/components/source_buffer_info.ts
@@ -0,0 +1,9 @@
+interface Props {
+  sourceBuffer: SourceBuffer;
+  name: string;
+}
+
+/** A component that displays the source buffer info. */
+export function SourceBufferInfo({sourceBuffer, name}: Props) {
+  return `<div>${name} buffered: ${sourceBuffer.buffered.end(0)} sec</div>`;
+}
diff --git a/src/cobalt/demos/content/media-element-demo/src/components/video_info.ts b/src/cobalt/demos/content/media-element-demo/src/components/video_info.ts
new file mode 100644
index 0000000..3f8306f
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/components/video_info.ts
@@ -0,0 +1,9 @@
+interface Props {
+  duration: number;
+  currentTime: number;
+}
+
+/** A component that displays video info. */
+export function VideoInfo({duration, currentTime}: Props) {
+  return `<div>${currentTime} / ${duration}</div>`;
+}
diff --git a/src/cobalt/demos/content/media-element-demo/src/components/watch.ts b/src/cobalt/demos/content/media-element-demo/src/components/watch.ts
new file mode 100644
index 0000000..5503d44
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/components/watch.ts
@@ -0,0 +1,45 @@
+import {isCobalt} from '../utils/shared_values';
+import {Component, renderComponent} from './component';
+import {Player} from './player';
+
+/** TODO: Add correct watch props. */
+interface Props {}
+
+/** The watch page. */
+export class Watch extends Component<Props> {
+  /** @override */
+  render() {
+    return '<div class="player"></div>';
+  }
+
+  /** @override */
+  afterRender() {
+    // Mainstream browsers require users to interact with the document first
+    // before it can start play any video. This rule does not apply to Cobalt.
+    if (isCobalt) {
+      this.boostrapPage();
+    } else {
+      this.waitForInteraction();
+    }
+  }
+
+  /**
+   * Listens to any key event and displays a message. Bootstraps the page once
+   * user interacts with the docuemnt.
+   */
+  private waitForInteraction() {
+    const messageEl = document.createElement('h1');
+    messageEl.textContent = 'Press ANY KEY to start';
+    this.el.appendChild(messageEl);
+    const handler = () => {
+      window.removeEventListener('keyup', handler);
+      messageEl.parentElement!.removeChild(messageEl);
+      this.boostrapPage();
+    };
+    window.addEventListener('keyup', handler);
+  }
+
+  private boostrapPage() {
+    renderComponent(Player, this.props, this.el.querySelector('.player')!);
+  }
+}
diff --git a/src/cobalt/demos/content/media-element-demo/src/index.ts b/src/cobalt/demos/content/media-element-demo/src/index.ts
new file mode 100644
index 0000000..0554019
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/index.ts
@@ -0,0 +1,15 @@
+import * as Promise from 'bluebird';
+
+import {renderComponent} from './components/component';
+import {ErrorLogger} from './components/error_logger';
+import {Router} from './components/router';
+import {isCobalt} from './utils/shared_values';
+
+if (isCobalt) {
+  // Use bluebird as the default promise because Cobalt does not support
+  // unhandledrejection natively.
+  window.Promise = Promise as any;
+}
+
+renderComponent(ErrorLogger, {}, document.querySelector('#error-logger')!);
+renderComponent(Router, {}, document.querySelector('#router')!);
diff --git a/src/cobalt/demos/content/media-element-demo/src/utils/download_buffer.ts b/src/cobalt/demos/content/media-element-demo/src/utils/download_buffer.ts
new file mode 100644
index 0000000..8cbb218
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/utils/download_buffer.ts
@@ -0,0 +1,187 @@
+import {download} from './downloader';
+import {Media} from './media';
+
+const CHUNK_SIZE = 1024 * 1024;  // 1MB
+const DOWNLOAD_BUFFER_MAX_SIZE = 10;
+
+type DownloadBufferListener = (results: Map<Media, MediaDownloaderStatus>) =>
+    void;
+
+/**
+ * Download buffer fetches the passed in media list chunk by chunk. It
+ * automatically pauses the download when the buffer reaches the limit and
+ * resumes when a chunk is taken out from the buffer.
+ */
+export class DownloadBuffer {
+  private isDownloading = false;
+  private currentMediaIndex = 0;
+  private listeners: DownloadBufferListener[] = [];
+  private downloaderMap = new Map<Media, MediaDownloader>();
+
+  constructor(
+      private readonly mediaList: Media[],
+      private readonly limit = DOWNLOAD_BUFFER_MAX_SIZE) {
+    for (const media of mediaList) {
+      this.downloaderMap.set(media, new MediaDownloader(media));
+    }
+    this.download();
+  }
+
+  /** Registers callback to listen to download buffer changes. */
+  register(callback: DownloadBufferListener) {
+    this.listeners.push(callback);
+    return () => {
+      const index = this.listeners.indexOf(callback);
+      if (index !== -1) {
+        this.listeners.splice(index, 1);
+      }
+    }
+  }
+
+  /** Calls listeners with the current media downloaders' status. */
+  private reportChanges() {
+    const result = new Map<Media, MediaDownloaderStatus>();
+    this.downloaderMap.forEach((downloader) => {
+      result.set(downloader.media, downloader.status());
+    });
+    for (const listener of this.listeners) {
+      listener(result);
+    }
+  }
+
+  /** @return The total buffer sizes of all media. */
+  size() {
+    let res = 0;
+    this.downloaderMap.forEach((downloader) => {
+      res += downloader.size();
+    });
+    return res;
+  }
+
+  private async download() {
+    if (this.isDownloading) {
+      return;
+    }
+    const media = this.mediaList[this.currentMediaIndex];
+    if (!media) {
+      // All media are processed.
+      return;
+    }
+    const downloader = this.downloaderMap.get(media);
+    if (!downloader) {
+      throw new Error(
+          `${media.url} does not have a downloader. This should not happen.`);
+    }
+    if (downloader.downloadFinished()) {
+      // The current media has finished downloading. Move on to the next one.
+      this.currentMediaIndex++;
+      this.download();
+      return;
+    }
+    this.isDownloading = true;
+    await downloader.downloadAndEnqueueNextChunk();
+    this.isDownloading = false;
+    this.reportChanges();
+    if (this.size() < this.limit) {
+      this.download();
+    }
+  }
+
+  /** Shifts a chunk of the requested media. */
+  shift(media: Media) {
+    return new Promise<ArrayBuffer|undefined>((resolve, reject) => {
+      const downloader = this.downloaderMap.get(media);
+      if (!downloader) {
+        throw new Error(
+            `${media.url} does not have a downloader. This should not happen.`);
+      }
+      const chunk = downloader.shift();
+      if (chunk) {
+        this.reportChanges();
+        this.download();
+        resolve(chunk);
+        return;
+      }
+      if (downloader.downloadFinished()) {
+        resolve();
+        return;
+      }
+      // The request media has not been downloaded. Wait until it is ready.
+      downloader.registerOnce(() => {
+        const chunk = downloader.shift();
+        if (chunk) {
+          this.reportChanges();
+          this.download();
+          resolve(chunk);
+        } else {
+          reject(new Error('Something is wrong'));
+        }
+      });
+    });
+  }
+}
+
+export interface MediaDownloaderStatus {
+  downloadedBytes: number;
+  queuedChunks: number;
+  finished: boolean;
+}
+
+/** Controls how to download a media. */
+class MediaDownloader {
+  private finished = false;
+  private chunks: ArrayBuffer[] = [];
+  private startingByte = 0;
+  private listeners: Array<() => void> = [];
+
+  constructor(readonly media: Media) {}
+
+  status(): MediaDownloaderStatus {
+    return {
+      downloadedBytes: this.startingByte,
+      queuedChunks: this.size(),
+      finished: this.downloadFinished(),
+    };
+  }
+
+  size() {
+    return this.chunks.length;
+  }
+
+  /**
+   * Listens when a chunk is enqueued. The callback will only be triggered
+   * once.
+   */
+  registerOnce(callback: () => void) {
+    this.listeners.push(callback);
+  }
+
+  downloadFinished(): boolean {
+    return this.finished;
+  }
+
+  shift(): ArrayBuffer|undefined {
+    return this.chunks.shift();
+  }
+
+  /** Downloads next chunk and enqueues the chunk to the buffer. */
+  async downloadAndEnqueueNextChunk() {
+    if (this.finished) {
+      throw new Error(`Attempt to download ${
+          this.media.url} after it has finished downloading.`);
+    }
+    const data = await download(
+        this.media.url, this.startingByte, this.startingByte + CHUNK_SIZE - 1);
+    if (data.byteLength === 0) {
+      console.log(`${this.media.url} is fully downloaded.`);
+      this.finished = true;
+      return;
+    }
+    console.log(`Downloaded ${this.media.url} at ${this.startingByte}`);
+    this.startingByte += data.byteLength;
+    this.chunks.push(data);
+    while (this.listeners.length > 0) {
+      this.listeners.shift()!();
+    }
+  }
+}
diff --git a/src/cobalt/demos/content/media-element-demo/src/utils/downloader.ts b/src/cobalt/demos/content/media-element-demo/src/utils/downloader.ts
new file mode 100644
index 0000000..8d2cc96
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/utils/downloader.ts
@@ -0,0 +1,20 @@
+import {logError} from '../components/error_logger';
+
+/**
+ * Downloads an asset with the provided URL.
+ * @param url Asset path.
+ * @param begin Start byte.
+ * @param end End byte.
+ */
+export async function download(
+    url: string, begin: number, end: number): Promise<ArrayBuffer> {
+  const response = await fetch(url, {
+    headers: {
+      'Range': `bytes=${begin}-${end}`,
+    },
+  });
+  if (response.status === 404) {
+    logError(`${url} does not exist.`);
+  }
+  return response.arrayBuffer();
+}
diff --git a/src/cobalt/demos/content/media-element-demo/src/utils/enums.ts b/src/cobalt/demos/content/media-element-demo/src/utils/enums.ts
new file mode 100644
index 0000000..3877882
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/utils/enums.ts
@@ -0,0 +1,16 @@
+/** A player config defines how to switch between videos. */
+export enum SwitchMode {
+  // Play adaptive videos under the same playback session using one media
+  // source. Play under different playback sessions if adaptive playback is not
+  // possible.
+  NORMAL = 'normal',
+  // Play adaptive videos under different playback sessions.
+  RELOAD = 'reload',
+  // Use a new video element when switching to the next media file.
+  RECREATE_ELEMENT = 'recreate-element',
+}
+
+export enum PlayMode {
+  PROGRESSIVE = 'progressive',
+  ADAPTIVE = 'adaptive',
+}
diff --git a/src/cobalt/demos/content/media-element-demo/src/utils/limited_source_buffer.ts b/src/cobalt/demos/content/media-element-demo/src/utils/limited_source_buffer.ts
new file mode 100644
index 0000000..63f5aae
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/utils/limited_source_buffer.ts
@@ -0,0 +1,87 @@
+import {DownloadBuffer} from './download_buffer';
+import {Media} from './media';
+
+const UNPLAYED_BUFFER_MAX_SIZE = 10;  // Only buffer 10 seconds maximum.
+
+/**
+ * A source buffer wrapper that limits the array buffers that can be added to
+ * the source buffer. If the bufferred length exceeds the limit, it will
+ * automatically pauses and waits until the remaining playback time is below the
+ * limit.
+ */
+export class LimitedSourceBuffer {
+  private scheduleTimeoutId = -1;
+  private currentMediaIndex = 0;
+
+  constructor(
+      private readonly videoEl: HTMLVideoElement,
+      private readonly sourceBuffer: SourceBuffer,
+      private readonly mediaList: Media[],
+      private readonly downloadBuffer: DownloadBuffer) {
+    this.appendNext();
+  }
+
+  /** Appends the next array buffer to the source buffer. */
+  async appendNext() {
+    const unplayedBufferLength = this.unplayedBufferLength();
+    if (unplayedBufferLength > UNPLAYED_BUFFER_MAX_SIZE) {
+      // Buffered too many content. Wait until we need to add buffer again.
+      const timeout = unplayedBufferLength - UNPLAYED_BUFFER_MAX_SIZE;
+      clearTimeout(this.scheduleTimeoutId);
+      console.log(`Schedule appendNext in ${timeout} sec.`);
+      this.scheduleTimeoutId =
+          setTimeout(() => this.appendNext(), timeout * 1000);
+      return;
+    }
+    const media = this.mediaList[this.currentMediaIndex];
+    if (!media) {
+      // All media are appended.
+      console.log('All media are appended');
+      return;
+    }
+    const chunk = await this.downloadBuffer.shift(media);
+    if (!chunk) {
+      // No more buffer left to append in the current media. Move on to the next
+      // one.
+      console.log(`${media.url} finished appending. Move on to the next one.`);
+      this.currentMediaIndex++;
+      const bufferedLength = this.sourceBuffer.buffered.end(0);
+      this.sourceBuffer.timestampOffset = bufferedLength;
+      this.appendNext();
+      return;
+    }
+    console.log(`Appending new chunk from ${media.url}`);
+    await this.appendChunkToBuffer(chunk);
+    this.appendNext();
+  }
+
+  /**
+   * Appends a chunk to the source buffer. Waits until the update is completed.
+   */
+  private async appendChunkToBuffer(chunk: ArrayBuffer) {
+    return new Promise((resolve, reject) => {
+      this.sourceBuffer.appendBuffer(chunk);
+      const unsubscribe = () => {
+        this.sourceBuffer.removeEventListener('updateend', onupdateend);
+        this.sourceBuffer.removeEventListener('error', onerror);
+      };
+      const onerror = () => {
+        unsubscribe();
+        reject(new Error('Append to buffer failed.'));
+      };
+      const onupdateend = () => {
+        unsubscribe();
+        resolve();
+      };
+      this.sourceBuffer.addEventListener('updateend', onupdateend);
+      this.sourceBuffer.addEventListener('error', onerror);
+    });
+  }
+
+  private unplayedBufferLength() {
+    if (this.sourceBuffer.buffered.length === 0) {
+      return 0;
+    }
+    return this.sourceBuffer.buffered.end(0) - this.videoEl.currentTime;
+  }
+}
diff --git a/src/cobalt/demos/content/media-element-demo/src/utils/media.ts b/src/cobalt/demos/content/media-element-demo/src/utils/media.ts
new file mode 100644
index 0000000..5502b56
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/utils/media.ts
@@ -0,0 +1,84 @@
+import {logError} from '../components/error_logger';
+
+import {download} from './downloader';
+
+/** A class that holds media metadata. */
+export class Media {
+  readonly url: string;
+  private metadata?: MediaMetadata;
+
+  constructor(filename: string) {
+    /** @type {string} Asset url. */
+    this.url = `assets/${filename}`;
+  }
+
+  async getMetadata(): Promise<MediaMetadata> {
+    if (!this.metadata) {
+      this.metadata = await this.fetchMetadata();
+    }
+    return this.metadata;
+  }
+
+  /**
+   * @return Whether the video is a progressive video.
+   */
+  async isProgressive(): Promise<boolean> {
+    const metadata = await this.getMetadata();
+    return metadata.type === 'mp42';
+  }
+
+  /** Downloads the head and builds metadata. */
+  private async fetchMetadata(): Promise<MediaMetadata> {
+    if (this.url.substr(-3) !== 'mp4') {
+      // TODO: Add more media type support.
+      return {};
+    }
+    // Metadata should be included in the first 10K.
+    const buffer = await download(this.url, 0, 1024 * 10);
+    if (buffer.byteLength === 0) {
+      return {};
+    }
+    const headBuffer = buffer.slice(0, getSize(buffer));
+    const type = getFtypType(headBuffer);
+    return {
+      type,
+    };
+  }
+}
+
+interface MediaMetadata {
+  /** The ftyp type. */
+  type?: string;
+}
+
+/**
+ * Gets the buffer size from the first 4 bytes.
+ * @return Buffer size.
+ */
+function getSize(buffer: ArrayBuffer): number {
+  const dv = new DataView(buffer.slice(0, 4));
+  return dv.getUint32(0);
+}
+
+/**
+ * Turns the buffer into a string.
+ * @return The generated string.
+ */
+function stringifyBuffer(
+    buffer: ArrayBuffer, begin: number, end: number): string {
+  return String.fromCharCode.apply(
+      null, Array.from(new Uint8Array(buffer.slice(begin, end))));
+}
+
+/**
+ * Gets the ftyp type from the buffer.
+ * @return The ftyp type. Undefined if there is an error.
+ */
+function getFtypType(buffer: ArrayBuffer): string|undefined {
+  const code = stringifyBuffer(buffer, 4, 8);
+  if (code !== 'ftyp') {
+    logError(`unknown type code ${code}`);
+    return;
+  }
+  return stringifyBuffer(buffer, 8, 12);
+}
diff --git a/src/cobalt/demos/content/media-element-demo/src/utils/observable.ts b/src/cobalt/demos/content/media-element-demo/src/utils/observable.ts
new file mode 100644
index 0000000..63c451f
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/utils/observable.ts
@@ -0,0 +1,50 @@
+type ObservableCallback<T> = (next: T, prev: T) => void;
+
+/**
+ * An observable value. Observers can attach a callback that gets called when
+ * the value changes.
+ */
+export class Observable<T> {
+  /** The actual value. */
+  private value: T;
+
+  /** Callbacks to trigger when the value changes. */
+  private callbacks: Array<ObservableCallback<T>> = [];
+
+  constructor(initValue: T) {
+    this.value = initValue;
+  }
+
+  /**
+   * Sets a new value and triggers the observer callbacks if the value changes.
+   */
+  set(value: T) {
+    if (this.value === value) {
+      return;
+    }
+
+    const oldValue = value;
+    this.value = value;
+    for (const callback of this.callbacks) {
+      callback(this.value, oldValue);
+    }
+  }
+
+  get(): T {
+    return this.value;
+  }
+
+  /**
+   * Triggers the callback on value changes.
+   * @return The unsubscriber.
+   */
+  observe(callback: ObservableCallback<T>): () => void {
+    this.callbacks.push(callback);
+    return () => {
+      const index = this.callbacks.indexOf(callback);
+      if (index !== -1) {
+        this.callbacks.splice(index, 1);
+      }
+    }
+  }
+}
diff --git a/src/cobalt/demos/content/media-element-demo/src/utils/shared_values.ts b/src/cobalt/demos/content/media-element-demo/src/utils/shared_values.ts
new file mode 100644
index 0000000..031b96c
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/src/utils/shared_values.ts
@@ -0,0 +1,5 @@
+/** This file contains singleton values shared by the application. */
+import {Observable} from './observable';
+
+export const errors = new Observable<string[]>([]);
+export const isCobalt = navigator.userAgent.indexOf('Cobalt') >= 0;
diff --git a/src/cobalt/demos/content/media-element-demo/tsconfig.json b/src/cobalt/demos/content/media-element-demo/tsconfig.json
new file mode 100644
index 0000000..afc9053
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/tsconfig.json
@@ -0,0 +1,15 @@
+{
+  "compilerOptions": {
+    "outDir": "./dist/",
+    "sourceMap": true,
+    "noImplicitAny": true,
+    "module": "es6",
+    "target": "es5",
+    "allowJs": true,
+    "strict": true,
+    "lib": [
+      "esnext",
+      "dom"
+    ],
+  }
+}
diff --git a/src/cobalt/demos/content/media-element-demo/webpack.config.js b/src/cobalt/demos/content/media-element-demo/webpack.config.js
new file mode 100644
index 0000000..ab9687c
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/webpack.config.js
@@ -0,0 +1,29 @@
+const path = require('path');
+const CopyPlugin = require('copy-webpack-plugin');
+
+module.exports = {
+  mode: 'development',
+  devtool: 'source-map',
+  module: {
+    rules: [{
+      test: /\.ts$/,
+      use: 'ts-loader',
+      exclude: /node_modules/,
+    }]
+  },
+  resolve: {
+    extensions: ['.ts', '.js'],
+  },
+  entry: './src/index.ts',
+  output: {
+    path: path.resolve(__dirname, 'dist'),
+    filename: 'bundle.js',
+  },
+  plugins: [
+    new CopyPlugin({
+      patterns: [
+        {from: 'public'},
+      ]
+    }),
+  ]
+};
diff --git a/src/cobalt/doc/splash_screen.md b/src/cobalt/doc/splash_screen.md
index 9122555..fbb6b6d 100644
--- a/src/cobalt/doc/splash_screen.md
+++ b/src/cobalt/doc/splash_screen.md
@@ -49,15 +49,15 @@
      first time.
 
   3. **Build-time fallback splash screen:** If a web cached splash screen is
-     unavailable and command line parameters are not passed by the system, a
-     `gyp_configuration.gypi` fallback splash screen may be used. Porters should
-     set the gypi variable `fallback_splash_screen_url` to the splash screen
-     URL.
+     unavailable and command line parameters are not passed by the system,
+     a CobaltExtensionConfigurationApi fallback splash screen may be used.
+     Porters should set the `CobaltFallbackSplashScreenUrl` value in
+     `configuration.cc` to the splash screen URL.
 
-  4. **Default splash screen:** If no web cached splash screen is
-     available, and command line and `gyp_configuration.gypi` fallbacks are not
-     set, a default splash screen will be used. This is set in `base.gypi` via
-     `fallback_splash_screen_url%` to refer to a black splash screen.
+  4. **Default splash screen:** If no web cached splash screen is available, and
+     command line and CobaltExtensionConfigurationApi fallbacks are not set, a
+     default splash screen will be used. This is set in
+     `configuration_defaults.cc` to refer to a black splash screen.
 
 ## Web-updatability
 
@@ -85,6 +85,40 @@
 Cobalt will also need to read the cached splash screen from the cache directory
 when starting up.
 
+## Topic-specific splash screens
+
+It is possible to specify multiple splash screens for a given Cobalt-based
+application, using a start-up 'topic' to select between the available splash
+screens. This can be useful when an application has multiple entry points that
+require different splash screens. The topic may be specified in the start-up url
+or deeplink as a query parameter. For example,
+`https://www.example.com/path?topic=foo`. If a splash-screen has been specified
+for topic 'foo', it will be used. Otherwise, the topic is ignored. Topic values
+should be URL encoded and limited to alphanumeric characters, hyphens,
+underscores, and percent signs.
+
+There are three ways to specify topic-specific splash screens. These methods mirror
+the types of splash screens listed above, and unless specified, the rules here
+are the same as for non-topic-based splash screens.
+
+  1. **Web cached splash screen:** A custom `rel="<topic>_splashscreen"`
+     attribute on a link element is used to specify a topic-specific splash
+     screen. There can be any number of these elements with different topics, in
+     addition to the topic-neutral `rel="splashscreen"`.
+
+  2. **Command line fallback splash screen:** The command line argument
+     `--fallback_splash_screen_topics` can be used if the cache is unavailable.
+     The argument accepts a list of topic/file parameters. If a file is not a
+     valid URL path, then it will be used as a filename at the path specified by
+     `--fallback_splash_screen_url`. For example,
+     `foo_topic=file:///foo.html&bar=bar.html`.
+
+  3. **Build-time fallback splash screen:** If a web cached splash screen is
+     unavailable and command line parameters are not passed by the system, a
+     CobaltExtensionConfigurationApi fallback splash screen may be used. Porters
+     should set the `CobaltFallbackSplashScreenTopics` value in
+     `configuration.cc` and this value should look like the command line option.
+
 ## Application-specific splash screens
 
 On systems that plan to support multiple Cobalt-based applications, an
@@ -94,9 +128,8 @@
 the Cobalt binary must be handled by the system.
 
 Alternatively, an application developer may use the default black splash screen
-specified in base.gypi whenever a cached splash screen is not available and rely
-on the web application to specify an application-specific cached splash screen
-otherwise.
+whenever a cached splash screen is not available and rely on the web application
+to specify an application-specific cached splash screen otherwise.
 
 ## Provided embedded resource splash screens
 For convenience, we currently provide the following splash screens as embedded
diff --git a/src/cobalt/dom/custom_event_test.cc b/src/cobalt/dom/custom_event_test.cc
index ee0b08f..083c08f 100644
--- a/src/cobalt/dom/custom_event_test.cc
+++ b/src/cobalt/dom/custom_event_test.cc
@@ -31,7 +31,6 @@
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/loader_factory.h"
-#include "cobalt/media_session/media_session.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/script/source_code.h"
@@ -84,7 +83,7 @@
         kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
         base::Closure() /* ran_animation_frame_callbacks */,
         dom::Window::CloseCallback() /* window_close */,
-        base::Closure() /* window_minimize */, NULL, NULL, NULL,
+        base::Closure() /* window_minimize */, NULL, NULL,
         dom::Window::OnStartDispatchEventCallback(),
         dom::Window::OnStopDispatchEventCallback(),
         dom::ScreenshotManager::ProvideScreenshotFunctionCallback(), NULL);
diff --git a/src/cobalt/dom/error_event_test.cc b/src/cobalt/dom/error_event_test.cc
index 937cd45..7f431a7 100644
--- a/src/cobalt/dom/error_event_test.cc
+++ b/src/cobalt/dom/error_event_test.cc
@@ -31,7 +31,6 @@
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/loader_factory.h"
-#include "cobalt/media_session/media_session.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/script/source_code.h"
@@ -86,7 +85,7 @@
         kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
         base::Closure() /* ran_animation_frame_callbacks */,
         dom::Window::CloseCallback() /* window_close */,
-        base::Closure() /* window_minimize */, NULL, NULL, NULL,
+        base::Closure() /* window_minimize */, NULL, NULL,
         dom::Window::OnStartDispatchEventCallback(),
         dom::Window::OnStopDispatchEventCallback(),
         dom::ScreenshotManager::ProvideScreenshotFunctionCallback(), NULL);
diff --git a/src/cobalt/dom/html_element.cc b/src/cobalt/dom/html_element.cc
index 1db3c22..e3f92a6 100644
--- a/src/cobalt/dom/html_element.cc
+++ b/src/cobalt/dom/html_element.cc
@@ -17,6 +17,7 @@
 #include <algorithm>
 #include <map>
 #include <memory>
+#include <utility>
 
 #include "base/lazy_instance.h"
 #include "base/message_loop/message_loop_task_runner.h"
@@ -1032,7 +1033,6 @@
   // themselves still need to have their computed style updated, in case the
   // value of display is changed.
   if (computed_style()->display() == cssom::KeywordValue::GetNone()) {
-    ReleaseUiNavigationItem();
     return;
   }
 
@@ -1079,6 +1079,7 @@
     }
   }
 
+  ReleaseUiNavigationItem();
   MarkNotDisplayedOnDescendants();
 }
 
@@ -1521,8 +1522,7 @@
     if (property == U_LEFT_TO_RIGHT) {
       return HTMLElement::kDirLeftToRight;
     }
-    if (property == U_RIGHT_TO_LEFT ||
-        property == U_RIGHT_TO_LEFT_ARABIC) {
+    if (property == U_RIGHT_TO_LEFT || property == U_RIGHT_TO_LEFT_ARABIC) {
       return HTMLElement::kDirRightToLeft;
     }
   }
@@ -1615,11 +1615,11 @@
     return kDirLeftToRight;
   }
 
-  // Although the spec says to use the parent's directionality, the W3C test
-  // (the-dir-attribute-069.html) says to default to LTR. Chrome follows the
-  // W3C expectation, so follow Chrome. Additional discussion here:
-  //   https://github.com/w3c/i18n-drafts/issues/235
-  // The following code block which implements the spec is left for reference.
+// Although the spec says to use the parent's directionality, the W3C test
+// (the-dir-attribute-069.html) says to default to LTR. Chrome follows the
+// W3C expectation, so follow Chrome. Additional discussion here:
+//   https://github.com/w3c/i18n-drafts/issues/235
+// The following code block which implements the spec is left for reference.
 #if 0
   // Otherwise, the directionality of the element is the same as the element's
   //   parent element's directionality.
@@ -2106,8 +2106,7 @@
        element = element->next_element_sibling()) {
     HTMLElement* html_element = element->AsHTMLElement();
     if (html_element) {
-      HTMLMediaElement* media_html_element =
-          html_element->AsHTMLMediaElement();
+      HTMLMediaElement* media_html_element = html_element->AsHTMLMediaElement();
       if (media_html_element) {
         html_media_elements->push_back(media_html_element);
       }
@@ -2160,7 +2159,7 @@
     ui_nav_item_type = ui_navigation::kNativeItemTypeContainer;
   }
 
-  if (ui_nav_item_type) {
+  if (ui_nav_item_type && IsDisplayed()) {
     ui_navigation::NativeItemDir ui_nav_item_dir;
     ui_nav_item_dir.is_left_to_right =
         directionality() == kLeftToRightDirectionality;
@@ -2175,12 +2174,7 @@
       // The current navigation item isn't of the correct type. Disable it so
       // that callbacks won't be invoked for it. The object will be destroyed
       // when all references to it are released.
-      if (g_ui_nav_focus_ == this) {
-        g_ui_nav_focus_ = nullptr;
-        ui_nav_item_->UnfocusAll();
-      }
-      ui_nav_item_->SetEnabled(false);
-      ui_nav_item_ = nullptr;
+      ReleaseUiNavigationItem();
     }
 
     ui_nav_item_ = new ui_navigation::NavItem(
@@ -2204,12 +2198,7 @@
     return false;
   } else if (ui_nav_item_) {
     // This navigation item is no longer relevant.
-    if (g_ui_nav_focus_ == this) {
-      g_ui_nav_focus_ = nullptr;
-      ui_nav_item_->UnfocusAll();
-    }
-    ui_nav_item_->SetEnabled(false);
-    ui_nav_item_ = nullptr;
+    ReleaseUiNavigationItem();
     return false;
   }
 
@@ -2218,13 +2207,6 @@
 
 void HTMLElement::ReleaseUiNavigationItem() {
   if (ui_nav_item_) {
-    // Make sure layout updates this element.
-    InvalidateLayoutBoxesOfNodeAndAncestors();
-    if (ui_nav_item_->IsContainer()) {
-      // Make sure layout updates any focus items that may be in this container.
-      InvalidateLayoutBoxesOfDescendants();
-    }
-
     // Disable the UI navigation item so it won't receive anymore callbacks
     // while being released.
     if (g_ui_nav_focus_ == this) {
diff --git a/src/cobalt/dom/navigator.cc b/src/cobalt/dom/navigator.cc
index a6d3ce3..8b8d5f6 100644
--- a/src/cobalt/dom/navigator.cc
+++ b/src/cobalt/dom/navigator.cc
@@ -145,14 +145,13 @@
 
 Navigator::Navigator(
     script::EnvironmentSettings* settings, const std::string& user_agent,
-    const std::string& language, scoped_refptr<MediaSession> media_session,
+    const std::string& language,
     scoped_refptr<cobalt::dom::captions::SystemCaptionSettings> captions,
     script::ScriptValueFactory* script_value_factory)
     : user_agent_(user_agent),
       language_(language),
       mime_types_(new MimeTypeArray()),
       plugins_(new PluginArray()),
-      media_session_(media_session),
       media_devices_(
           new media_capture::MediaDevices(settings, script_value_factory)),
       system_caption_settings_(captions),
@@ -223,8 +222,20 @@
   return plugins_;
 }
 
-const scoped_refptr<media_session::MediaSession>& Navigator::media_session()
-    const {
+const scoped_refptr<media_session::MediaSession>& Navigator::media_session() {
+  if (media_session_ == nullptr) {
+    media_session_ =
+        scoped_refptr<media_session::MediaSession>(new MediaSession());
+
+    if (media_player_factory_ != nullptr) {
+      media_session_->EnsureMediaSessionClient();
+      DCHECK(media_session_->media_session_client());
+      media_session_->media_session_client()
+          ->SetMaybeFreezeCallback(maybe_freeze_callback_);
+      media_session_->media_session_client()
+          ->SetMediaPlayerFactory(media_player_factory_);
+    }
+  }
   return media_session_;
 }
 
diff --git a/src/cobalt/dom/navigator.h b/src/cobalt/dom/navigator.h
index 1489f78..348f624 100644
--- a/src/cobalt/dom/navigator.h
+++ b/src/cobalt/dom/navigator.h
@@ -23,6 +23,7 @@
 #include "cobalt/dom/eme/media_key_system_configuration.h"
 #include "cobalt/dom/mime_type_array.h"
 #include "cobalt/dom/plugin_array.h"
+#include "cobalt/media/web_media_player_factory.h"
 #include "cobalt/media_capture/media_devices.h"
 #include "cobalt/media_session/media_session.h"
 #include "cobalt/script/promise.h"
@@ -42,7 +43,6 @@
   Navigator(
       script::EnvironmentSettings* settings, const std::string& user_agent,
       const std::string& language,
-      scoped_refptr<cobalt::media_session::MediaSession> media_session,
       scoped_refptr<cobalt::dom::captions::SystemCaptionSettings> captions,
       script::ScriptValueFactory* script_value_factory);
 
@@ -67,8 +67,17 @@
   const scoped_refptr<MimeTypeArray>& mime_types() const;
   const scoped_refptr<PluginArray>& plugins() const;
 
-  const scoped_refptr<cobalt::media_session::MediaSession>& media_session()
-      const;
+  const scoped_refptr<media_session::MediaSession>& media_session();
+
+  // Set maybe freeze callback.
+  void set_maybefreeze_callback(const base::Closure& maybe_freeze_callback) {
+    maybe_freeze_callback_ = maybe_freeze_callback;
+  }
+
+  void set_media_player_factory(
+      const media::WebMediaPlayerFactory* factory) {
+    media_player_factory_ = factory;
+  }
 
   // Web API: extension defined in Encrypted Media Extensions (16 March 2017).
   using InterfacePromise = script::Promise<scoped_refptr<script::Wrappable>>;
@@ -124,6 +133,9 @@
   script::ScriptValueFactory* script_value_factory_;
   base::Optional<bool> key_system_with_attributes_supported_;
 
+  base::Closure maybe_freeze_callback_;
+  const media::WebMediaPlayerFactory* media_player_factory_ = nullptr;
+
   DISALLOW_COPY_AND_ASSIGN(Navigator);
 };
 
diff --git a/src/cobalt/dom/navigator_licenses_test.cc b/src/cobalt/dom/navigator_licenses_test.cc
index d7f2172..cc55b6b 100644
--- a/src/cobalt/dom/navigator_licenses_test.cc
+++ b/src/cobalt/dom/navigator_licenses_test.cc
@@ -24,7 +24,7 @@
   testing::StubEnvironmentSettings environment_settings;
   scoped_refptr<cobalt::dom::Navigator> navigator =
       new cobalt::dom::Navigator(&environment_settings, std::string(),
-                                 std::string(), nullptr, nullptr, nullptr);
+                                 std::string(), nullptr, nullptr);
 
   ASSERT_TRUE(navigator != nullptr);
   EXPECT_FALSE(navigator->licenses().empty());
diff --git a/src/cobalt/dom/on_screen_keyboard_test.cc b/src/cobalt/dom/on_screen_keyboard_test.cc
index 9493146..701c842 100644
--- a/src/cobalt/dom/on_screen_keyboard_test.cc
+++ b/src/cobalt/dom/on_screen_keyboard_test.cc
@@ -30,7 +30,6 @@
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/loader_factory.h"
-#include "cobalt/media_session/media_session.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/script/source_code.h"
@@ -222,7 +221,7 @@
             base::Closure() /* ran_animation_frame_callbacks */,
             dom::Window::CloseCallback() /* window_close */,
             base::Closure() /* window_minimize */,
-            on_screen_keyboard_bridge_.get(), NULL, NULL,
+            on_screen_keyboard_bridge_.get(), NULL,
             dom::Window::OnStartDispatchEventCallback(),
             dom::Window::OnStopDispatchEventCallback(),
             dom::ScreenshotManager::ProvideScreenshotFunctionCallback(),
diff --git a/src/cobalt/dom/testing/stub_window.h b/src/cobalt/dom/testing/stub_window.h
index 7377579..2631d15 100644
--- a/src/cobalt/dom/testing/stub_window.h
+++ b/src/cobalt/dom/testing/stub_window.h
@@ -32,7 +32,6 @@
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/loader_factory.h"
-#include "cobalt/media_session/media_session.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
 #include "starboard/window.h"
@@ -80,7 +79,7 @@
         dom::kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
         base::Closure() /* ran_animation_frame_callbacks */,
         dom::Window::CloseCallback() /* window_close */,
-        base::Closure() /* window_minimize */, NULL, NULL, NULL,
+        base::Closure() /* window_minimize */, NULL, NULL,
         dom::Window::OnStartDispatchEventCallback(),
         dom::Window::OnStopDispatchEventCallback(),
         dom::ScreenshotManager::ProvideScreenshotFunctionCallback(), NULL);
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc
index 4f89140..2ee9432 100644
--- a/src/cobalt/dom/window.cc
+++ b/src/cobalt/dom/window.cc
@@ -124,7 +124,6 @@
     const base::Closure& window_minimize_callback,
     OnScreenKeyboardBridge* on_screen_keyboard_bridge,
     const scoped_refptr<input::Camera3D>& camera_3d,
-    const scoped_refptr<MediaSession>& media_session,
     const OnStartDispatchEventCallback& on_start_dispatch_event_callback,
     const OnStopDispatchEventCallback& on_stop_dispatch_event_callback,
     const ScreenshotManager::ProvideScreenshotFunctionCallback&
@@ -168,7 +167,7 @@
               csp_insecure_allowed_token, dom_max_element_depth)))),
       document_loader_(nullptr),
       history_(new History()),
-      navigator_(new Navigator(settings, user_agent, language, media_session,
+      navigator_(new Navigator(settings, user_agent, language,
                                captions, script_value_factory)),
       ALLOW_THIS_IN_INITIALIZER_LIST(
           relay_on_load_event_(new RelayLoadEvent(this))),
@@ -705,6 +704,11 @@
   tracer->Trace(on_screen_keyboard_);
 }
 
+const scoped_refptr<media_session::MediaSession>
+    Window::media_session() const {
+  return navigator_->media_session();
+}
+
 void Window::CacheSplashScreen(const std::string& content,
                                const base::Optional<std::string>& topic) {
   if (splash_screen_cache_callback_.is_null()) {
diff --git a/src/cobalt/dom/window.h b/src/cobalt/dom/window.h
index de6fce2..c572407 100644
--- a/src/cobalt/dom/window.h
+++ b/src/cobalt/dom/window.h
@@ -167,7 +167,6 @@
       const base::Closure& window_minimize_callback,
       OnScreenKeyboardBridge* on_screen_keyboard_bridge,
       const scoped_refptr<input::Camera3D>& camera_3d,
-      const scoped_refptr<cobalt::media_session::MediaSession>& media_session,
       const OnStartDispatchEventCallback&
           start_tracking_dispatch_event_callback,
       const OnStopDispatchEventCallback& stop_tracking_dispatch_event_callback,
@@ -355,9 +354,9 @@
   void SetCamera3D(const scoped_refptr<input::Camera3D>& camera_3d);
 
   void set_web_media_player_factory(
-      media::WebMediaPlayerFactory* web_media_player_factory) {
-    html_element_context_->set_web_media_player_factory(
-        web_media_player_factory);
+    media::WebMediaPlayerFactory* web_media_player_factory) {
+  html_element_context_->set_web_media_player_factory(
+      web_media_player_factory);
   }
 
   // Sets the current application state, forwarding on to the
@@ -408,6 +407,9 @@
 
   bool enable_map_to_mesh() { return enable_map_to_mesh_; }
 
+  const scoped_refptr<media_session::MediaSession>
+      media_session() const;
+
   DEFINE_WRAPPABLE_TYPE(Window);
 
  private:
diff --git a/src/cobalt/dom/window_test.cc b/src/cobalt/dom/window_test.cc
index 8e44fb0..2b1b96f 100644
--- a/src/cobalt/dom/window_test.cc
+++ b/src/cobalt/dom/window_test.cc
@@ -29,7 +29,6 @@
 #include "cobalt/dom/testing/stub_environment_settings.h"
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
-#include "cobalt/media_session/media_session.h"
 #include "cobalt/network_bridge/net_poster.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
@@ -74,7 +73,7 @@
         kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
         base::Closure() /* ran_animation_frame_callbacks */,
         dom::Window::CloseCallback() /* window_close */,
-        base::Closure() /* window_minimize */, NULL, NULL, NULL,
+        base::Closure() /* window_minimize */, NULL, NULL,
         dom::Window::OnStartDispatchEventCallback(),
         dom::Window::OnStopDispatchEventCallback(),
         dom::ScreenshotManager::ProvideScreenshotFunctionCallback(), NULL);
diff --git a/src/cobalt/extension/extension_test.cc b/src/cobalt/extension/extension_test.cc
index 38b7c83..d92acd1 100644
--- a/src/cobalt/extension/extension_test.cc
+++ b/src/cobalt/extension/extension_test.cc
@@ -30,23 +30,22 @@
   typedef CobaltExtensionPlatformServiceApi ExtensionApi;
   const char* kExtensionName = kCobaltExtensionPlatformServiceName;
 
-  const ExtensionApi* extension_api = static_cast<const ExtensionApi*>(
-      SbSystemGetExtension(kExtensionName));
+  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 ||
-              extension_api->version == 3) << "Invalid version";
-  EXPECT_TRUE(extension_api->Has != NULL);
-  EXPECT_TRUE(extension_api->Open != NULL);
-  EXPECT_TRUE(extension_api->Close != NULL);
-  EXPECT_TRUE(extension_api->Send != NULL);
+  EXPECT_GE(extension_api->version, 1u);
+  EXPECT_LE(extension_api->version, 3u);
+  EXPECT_NE(extension_api->Has, nullptr);
+  EXPECT_NE(extension_api->Open, nullptr);
+  EXPECT_NE(extension_api->Close, nullptr);
+  EXPECT_NE(extension_api->Send, nullptr);
 
-  const ExtensionApi* second_extension_api = static_cast<const ExtensionApi*>(
-      SbSystemGetExtension(kExtensionName));
+  const ExtensionApi* second_extension_api =
+      static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
   EXPECT_EQ(second_extension_api, extension_api)
       << "Extension struct should be a singleton";
 }
@@ -55,35 +54,50 @@
   typedef CobaltExtensionGraphicsApi ExtensionApi;
   const char* kExtensionName = kCobaltExtensionGraphicsName;
 
-  const ExtensionApi* extension_api = static_cast<const ExtensionApi*>(
-      SbSystemGetExtension(kExtensionName));
+  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 ||
-              extension_api->version == 3) << "Invalid version";
-  EXPECT_TRUE(extension_api->GetMaximumFrameIntervalInMilliseconds != NULL);
-  if (extension_api->version >= 2) {
-    EXPECT_TRUE(extension_api->GetMinimumFrameIntervalInMilliseconds != NULL);
-  }
-  if (extension_api->version >= 3) {
-    EXPECT_TRUE(extension_api->IsMapToMeshEnabled != NULL);
-  }
+  EXPECT_GE(extension_api->version, 1u);
+  EXPECT_LE(extension_api->version, 4u);
 
+  EXPECT_NE(extension_api->GetMaximumFrameIntervalInMilliseconds, nullptr);
   float maximum_frame_interval =
       extension_api->GetMaximumFrameIntervalInMilliseconds();
   EXPECT_FALSE(std::isnan(maximum_frame_interval));
 
   if (extension_api->version >= 2) {
+    EXPECT_NE(extension_api->GetMinimumFrameIntervalInMilliseconds, nullptr);
     float minimum_frame_interval =
-      extension_api->GetMinimumFrameIntervalInMilliseconds();
+        extension_api->GetMinimumFrameIntervalInMilliseconds();
     EXPECT_GT(minimum_frame_interval, 0);
   }
-  const ExtensionApi* second_extension_api = static_cast<const ExtensionApi*>(
-      SbSystemGetExtension(kExtensionName));
+
+  if (extension_api->version >= 3) {
+    EXPECT_NE(extension_api->IsMapToMeshEnabled, nullptr);
+  }
+
+  if (extension_api->version >= 4) {
+    EXPECT_NE(extension_api->ShouldClearFrameOnShutdown, nullptr);
+    float clear_color_r, clear_color_g, clear_color_b, clear_color_a;
+    if (extension_api->ShouldClearFrameOnShutdown(
+            &clear_color_r, &clear_color_g, &clear_color_b, &clear_color_a)) {
+      EXPECT_GE(clear_color_r, 0.0f);
+      EXPECT_LE(clear_color_r, 1.0f);
+      EXPECT_GE(clear_color_g, 0.0f);
+      EXPECT_LE(clear_color_g, 1.0f);
+      EXPECT_GE(clear_color_b, 0.0f);
+      EXPECT_LE(clear_color_b, 1.0f);
+      EXPECT_GE(clear_color_a, 0.0f);
+      EXPECT_LE(clear_color_a, 1.0f);
+    }
+  }
+
+  const ExtensionApi* second_extension_api =
+      static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
   EXPECT_EQ(second_extension_api, extension_api)
       << "Extension struct should be a singleton";
 }
@@ -99,18 +113,17 @@
   }
 
   EXPECT_STREQ(extension_api->name, kExtensionName);
-  EXPECT_TRUE(extension_api->version == 1 ||
-              extension_api->version == 2 ||
-              extension_api->version == 3) << "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);
-  EXPECT_TRUE(extension_api->GetAppKey != NULL);
-  EXPECT_TRUE(extension_api->GetMaxNumberInstallations != NULL);
-  EXPECT_TRUE(extension_api->ResetInstallation != NULL);
-  EXPECT_TRUE(extension_api->Reset != NULL);
+  EXPECT_GE(extension_api->version, 1u);
+  EXPECT_LE(extension_api->version, 3u);
+  EXPECT_NE(extension_api->GetCurrentInstallationIndex, nullptr);
+  EXPECT_NE(extension_api->MarkInstallationSuccessful, nullptr);
+  EXPECT_NE(extension_api->RequestRollForwardToInstallation, nullptr);
+  EXPECT_NE(extension_api->GetInstallationPath, nullptr);
+  EXPECT_NE(extension_api->SelectNewInstallationIndex, nullptr);
+  EXPECT_NE(extension_api->GetAppKey, nullptr);
+  EXPECT_NE(extension_api->GetMaxNumberInstallations, nullptr);
+  EXPECT_NE(extension_api->ResetInstallation, nullptr);
+  EXPECT_NE(extension_api->Reset, nullptr);
   const ExtensionApi* second_extension_api =
       static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
   EXPECT_EQ(second_extension_api, extension_api)
@@ -128,29 +141,31 @@
   }
 
   EXPECT_STREQ(extension_api->name, kExtensionName);
-  EXPECT_TRUE(extension_api->version == 1 || extension_api->version == 2);
-  EXPECT_TRUE(extension_api->CobaltUserOnExitStrategy != NULL);
-  EXPECT_TRUE(extension_api->CobaltRenderDirtyRegionOnly != NULL);
-  EXPECT_TRUE(extension_api->CobaltEglSwapInterval != NULL);
-  EXPECT_TRUE(extension_api->CobaltFallbackSplashScreenUrl != NULL);
-  EXPECT_TRUE(extension_api->CobaltEnableQuic != NULL);
-  EXPECT_TRUE(extension_api->CobaltSkiaCacheSizeInBytes != NULL);
-  EXPECT_TRUE(extension_api->CobaltOffscreenTargetCacheSizeInBytes != NULL);
-  EXPECT_TRUE(extension_api->CobaltEncodedImageCacheSizeInBytes != NULL);
-  EXPECT_TRUE(extension_api->CobaltImageCacheSizeInBytes != NULL);
-  EXPECT_TRUE(extension_api->CobaltLocalTypefaceCacheSizeInBytes != NULL);
-  EXPECT_TRUE(extension_api->CobaltRemoteTypefaceCacheSizeInBytes != NULL);
-  EXPECT_TRUE(extension_api->CobaltMeshCacheSizeInBytes != NULL);
-  EXPECT_TRUE(extension_api->CobaltSoftwareSurfaceCacheSizeInBytes != NULL);
-  EXPECT_TRUE(extension_api->CobaltImageCacheCapacityMultiplierWhenPlayingVideo != NULL);
-  EXPECT_TRUE(extension_api->CobaltSkiaGlyphAtlasWidth != NULL);
-  EXPECT_TRUE(extension_api->CobaltSkiaGlyphAtlasHeight != NULL);
-  EXPECT_TRUE(extension_api->CobaltJsGarbageCollectionThresholdInBytes != NULL);
-  EXPECT_TRUE(extension_api->CobaltReduceCpuMemoryBy != NULL);
-  EXPECT_TRUE(extension_api->CobaltReduceGpuMemoryBy != NULL);
-  EXPECT_TRUE(extension_api->CobaltGcZeal != NULL);
+  EXPECT_GE(extension_api->version, 1u);
+  EXPECT_LE(extension_api->version, 2u);
+  EXPECT_NE(extension_api->CobaltUserOnExitStrategy, nullptr);
+  EXPECT_NE(extension_api->CobaltRenderDirtyRegionOnly, nullptr);
+  EXPECT_NE(extension_api->CobaltEglSwapInterval, nullptr);
+  EXPECT_NE(extension_api->CobaltFallbackSplashScreenUrl, nullptr);
+  EXPECT_NE(extension_api->CobaltEnableQuic, nullptr);
+  EXPECT_NE(extension_api->CobaltSkiaCacheSizeInBytes, nullptr);
+  EXPECT_NE(extension_api->CobaltOffscreenTargetCacheSizeInBytes, nullptr);
+  EXPECT_NE(extension_api->CobaltEncodedImageCacheSizeInBytes, nullptr);
+  EXPECT_NE(extension_api->CobaltImageCacheSizeInBytes, nullptr);
+  EXPECT_NE(extension_api->CobaltLocalTypefaceCacheSizeInBytes, nullptr);
+  EXPECT_NE(extension_api->CobaltRemoteTypefaceCacheSizeInBytes, nullptr);
+  EXPECT_NE(extension_api->CobaltMeshCacheSizeInBytes, nullptr);
+  EXPECT_NE(extension_api->CobaltSoftwareSurfaceCacheSizeInBytes, nullptr);
+  EXPECT_NE(extension_api->CobaltImageCacheCapacityMultiplierWhenPlayingVideo,
+            nullptr);
+  EXPECT_NE(extension_api->CobaltSkiaGlyphAtlasWidth, nullptr);
+  EXPECT_NE(extension_api->CobaltSkiaGlyphAtlasHeight, nullptr);
+  EXPECT_NE(extension_api->CobaltJsGarbageCollectionThresholdInBytes, nullptr);
+  EXPECT_NE(extension_api->CobaltReduceCpuMemoryBy, nullptr);
+  EXPECT_NE(extension_api->CobaltReduceGpuMemoryBy, nullptr);
+  EXPECT_NE(extension_api->CobaltGcZeal, nullptr);
   if (extension_api->version >= 2) {
-    EXPECT_TRUE(extension_api->CobaltFallbackSplashScreenTopics != NULL);
+    EXPECT_NE(extension_api->CobaltFallbackSplashScreenTopics, nullptr);
   }
 
   const ExtensionApi* second_extension_api =
@@ -170,8 +185,8 @@
   }
 
   EXPECT_STREQ(extension_api->name, kExtensionName);
-  EXPECT_TRUE(extension_api->version == 1);
-  EXPECT_TRUE(extension_api->OnMediaSessionStateChanged != NULL);
+  EXPECT_EQ(extension_api->version, 1u);
+  EXPECT_NE(extension_api->OnMediaSessionStateChanged, nullptr);
 
   const ExtensionApi* second_extension_api =
       static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
diff --git a/src/cobalt/extension/graphics.h b/src/cobalt/extension/graphics.h
index 9c993c5..a1a3b4e 100644
--- a/src/cobalt/extension/graphics.h
+++ b/src/cobalt/extension/graphics.h
@@ -23,8 +23,7 @@
 extern "C" {
 #endif
 
-#define kCobaltExtensionGraphicsName \
-  "dev.cobalt.extension.Graphics"
+#define kCobaltExtensionGraphicsName "dev.cobalt.extension.Graphics"
 
 typedef struct CobaltExtensionGraphicsApi {
   // Name should be the string kCobaltExtensionGraphicsName.
@@ -62,6 +61,20 @@
 
   // Get whether the renderer should support 360 degree video or not.
   bool (*IsMapToMeshEnabled)();
+
+  // The fields below this point were added in version 4 or later.
+
+  // Specify whether the framebuffer should be cleared when the graphics
+  // system is shutdown and color to use for clearing. The graphics system
+  // is shutdown on suspend or exit. The clear color values should be in the
+  // range of [0,1]; color values are only used if this function returns true.
+  //
+  // The default behavior is to clear to opaque black on shutdown unless this
+  // API specifies otherwise.
+  bool (*ShouldClearFrameOnShutdown)(float* clear_color_red,
+                                     float* clear_color_green,
+                                     float* clear_color_blue,
+                                     float* clear_color_alpha);
 } CobaltExtensionGraphicsApi;
 
 #ifdef __cplusplus
diff --git a/src/cobalt/layout/topmost_event_target.cc b/src/cobalt/layout/topmost_event_target.cc
index df2cd6a..8801fef 100644
--- a/src/cobalt/layout/topmost_event_target.cc
+++ b/src/cobalt/layout/topmost_event_target.cc
@@ -241,9 +241,11 @@
         for (scoped_refptr<dom::Element> element = target_element;
              element != nearest_common_ancestor;
              element = element->parent_element()) {
-          element->DispatchEvent(new dom::PointerEvent(
-              base::Tokens::pointerenter(), dom::Event::kNotBubbles,
-              dom::Event::kNotCancelable, view, *event_init));
+          if (element) {
+            element->DispatchEvent(new dom::PointerEvent(
+                base::Tokens::pointerenter(), dom::Event::kNotBubbles,
+                dom::Event::kNotCancelable, view, *event_init));
+          }
         }
       }
 
@@ -254,9 +256,11 @@
       for (scoped_refptr<dom::Element> element = target_element;
            element != nearest_common_ancestor;
            element = element->parent_element()) {
-        element->DispatchEvent(new dom::MouseEvent(
-            base::Tokens::mouseenter(), dom::Event::kNotBubbles,
-            dom::Event::kNotCancelable, view, *event_init));
+        if (element) {
+          element->DispatchEvent(new dom::MouseEvent(
+              base::Tokens::mouseenter(), dom::Event::kNotBubbles,
+              dom::Event::kNotCancelable, view, *event_init));
+        }
       }
     }
   }
diff --git a/src/cobalt/media/base/sbplayer_pipeline.cc b/src/cobalt/media/base/sbplayer_pipeline.cc
index 68abc49..143b238 100644
--- a/src/cobalt/media/base/sbplayer_pipeline.cc
+++ b/src/cobalt/media/base/sbplayer_pipeline.cc
@@ -280,8 +280,13 @@
 
   VideoFrameProvider* video_frame_provider_;
 
+  // Read audio from the stream if |timestamp_of_last_written_audio_| is less
+  // than |seek_time_| + |kAudioPrerollLimit|, this effectively allows 10
+  // seconds of audio to be written to the SbPlayer after playback startup or
+  // seek.
+  static const SbTime kAudioPrerollLimit = 10 * kSbTimeSecond;
   // Don't read audio from the stream more than |kAudioLimit| ahead of the
-  // current media time.
+  // current media time during playing.
   static const SbTime kAudioLimit = kSbTimeSecond;
   // Only call GetMediaTime() from OnNeedData if it has been
   // |kMediaTimeCheckInterval| since the last call to GetMediaTime().
@@ -1121,22 +1126,28 @@
         kMediaTimeCheckInterval) {
       GetMediaTime();
     }
-    // The estimated time ahead of playback may be negative if no audio has been
-    // written.
-    SbTime time_ahead_of_playback =
-        timestamp_of_last_written_audio_ - last_media_time_;
-    // Delay reading audio more than |kAudioLimit| ahead of playback, taking
-    // into account that our estimate of playback time might be behind by
+
+    // Delay reading audio more than |kAudioLimit| ahead of playback after the
+    // player has received enough audio for preroll, taking into account that
+    // our estimate of playback time might be behind by
     // |kMediaTimeCheckInterval|.
-    if (time_ahead_of_playback > (kAudioLimit + kMediaTimeCheckInterval)) {
-      SbTime delay_time = (time_ahead_of_playback - kAudioLimit) /
-                          std::max(playback_rate_, 1.0f);
-      task_runner_->PostDelayedTask(
-          FROM_HERE, base::Bind(&SbPlayerPipeline::DelayedNeedData, this),
-          base::TimeDelta::FromMicroseconds(delay_time));
-      audio_read_delayed_ = true;
-      return;
+    if (timestamp_of_last_written_audio_ - seek_time_.ToSbTime() >
+        kAudioPrerollLimit) {
+      // The estimated time ahead of playback may be negative if no audio has
+      // been written.
+      SbTime time_ahead_of_playback =
+          timestamp_of_last_written_audio_ - last_media_time_;
+      if (time_ahead_of_playback > (kAudioLimit + kMediaTimeCheckInterval)) {
+        SbTime delay_time = (time_ahead_of_playback - kAudioLimit) /
+                            std::max(playback_rate_, 1.0f);
+        task_runner_->PostDelayedTask(
+            FROM_HERE, base::Bind(&SbPlayerPipeline::DelayedNeedData, this),
+            base::TimeDelta::FromMicroseconds(delay_time));
+        audio_read_delayed_ = true;
+        return;
+      }
     }
+
     audio_read_delayed_ = false;
 #endif  // SB_API_VERSION >= 11
     audio_read_in_progress_ = true;
diff --git a/src/cobalt/media_session/default_media_session_client.cc b/src/cobalt/media_session/default_media_session_client.cc
index 0b4fbac..b1a7c47 100644
--- a/src/cobalt/media_session/default_media_session_client.cc
+++ b/src/cobalt/media_session/default_media_session_client.cc
@@ -36,4 +36,4 @@
 }  // namespace media_session
 }  // namespace cobalt
 
-#endif  // COBALT_MEDIA_SESSION_DEFAULT_MEDIA_SESSION_CLIENT_H_
\ No newline at end of file
+#endif  // COBALT_MEDIA_SESSION_DEFAULT_MEDIA_SESSION_CLIENT_H_
diff --git a/src/cobalt/media_session/media_session.cc b/src/cobalt/media_session/media_session.cc
index b9d04a0..acc4070 100644
--- a/src/cobalt/media_session/media_session.cc
+++ b/src/cobalt/media_session/media_session.cc
@@ -19,9 +19,8 @@
 namespace cobalt {
 namespace media_session {
 
-MediaSession::MediaSession(MediaSessionClient* client)
-    : media_session_client_(client),
-      playback_state_(kMediaSessionPlaybackStateNone),
+MediaSession::MediaSession()
+    : playback_state_(kMediaSessionPlaybackStateNone),
       task_runner_(base::MessageLoop::current()->task_runner()),
       is_change_task_queued_(false),
       last_position_updated_time_(0) {}
@@ -34,6 +33,13 @@
   action_map_.clear();
 }
 
+MediaSession::MediaSession(MediaSessionClient* client)
+    : media_session_client_(client),
+      playback_state_(kMediaSessionPlaybackStateNone),
+      task_runner_(base::MessageLoop::current()->task_runner()),
+      is_change_task_queued_(false),
+      last_position_updated_time_(0) {}
+
 void MediaSession::set_metadata(scoped_refptr<MediaMetadata> value) {
   metadata_ = value;
   MaybeQueueChangeTask(base::TimeDelta());
@@ -77,6 +83,14 @@
   return is_change_task_queued_;
 }
 
+void MediaSession::EnsureMediaSessionClient() {
+  if (media_session_client_ == nullptr) {
+    media_session_client_ = media_session::MediaSessionClient::Create();
+    DCHECK(media_session_client_);
+    media_session_client_->set_media_session(this);
+  }
+}
+
 void MediaSession::MaybeQueueChangeTask(base::TimeDelta delay) {
   DCHECK(task_runner_->BelongsToCurrentThread());
   if (is_change_task_queued_) {
diff --git a/src/cobalt/media_session/media_session.h b/src/cobalt/media_session/media_session.h
index 6c1db11..c83b1df 100644
--- a/src/cobalt/media_session/media_session.h
+++ b/src/cobalt/media_session/media_session.h
@@ -21,6 +21,7 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
+#include "cobalt/media/web_media_player_factory.h"
 #include "cobalt/media_session/media_metadata.h"
 #include "cobalt/media_session/media_position_state.h"
 #include "cobalt/media_session/media_session_action.h"
@@ -58,9 +59,11 @@
       ActionMap;
 
  public:
-  explicit MediaSession(MediaSessionClient* client);
+  MediaSession();
   ~MediaSession() override;
 
+  explicit MediaSession(MediaSessionClient* client);
+
   scoped_refptr<MediaMetadata> metadata() const { return metadata_; }
 
   void set_metadata(scoped_refptr<MediaMetadata> value);
@@ -81,6 +84,12 @@
   // unit tests.
   bool IsChangeTaskQueuedForTesting() const;
 
+  MediaSessionClient* media_session_client() {
+    return media_session_client_.get();
+  }
+
+  void EnsureMediaSessionClient();
+
  private:
   void MaybeQueueChangeTask(base::TimeDelta delay);
   void OnChanged();
@@ -91,7 +100,7 @@
   }
 
   ActionMap action_map_;
-  MediaSessionClient* media_session_client_;
+  std::unique_ptr<MediaSessionClient> media_session_client_;
   scoped_refptr<MediaMetadata> metadata_;
   MediaSessionPlaybackState playback_state_;
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
diff --git a/src/cobalt/media_session/media_session_client.cc b/src/cobalt/media_session/media_session_client.cc
index 9243556..dc767a3 100644
--- a/src/cobalt/media_session/media_session_client.cc
+++ b/src/cobalt/media_session/media_session_client.cc
@@ -67,7 +67,7 @@
 }  // namespace
 
 MediaSessionClient::MediaSessionClient(
-    scoped_refptr<MediaSession> media_session)
+    MediaSession* media_session)
     : media_session_(media_session),
       platform_playback_state_(kMediaSessionPlaybackStateNone) {
 #if SB_API_VERSION < 11
@@ -91,8 +91,6 @@
 
 MediaSessionClient::~MediaSessionClient() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  // Prevent any outstanding MediaSession::OnChanged tasks from calling this.
-  media_session_->media_session_client_ = nullptr;
 
   // Destroy the platform's MediaSessionClient, if it exists.
   if (extension_ != NULL &&
diff --git a/src/cobalt/media_session/media_session_client.h b/src/cobalt/media_session/media_session_client.h
index 76883d9..4961a91 100644
--- a/src/cobalt/media_session/media_session_client.h
+++ b/src/cobalt/media_session/media_session_client.h
@@ -35,10 +35,9 @@
   friend class MediaSession;
 
  public:
-  MediaSessionClient() : MediaSessionClient(new MediaSession(this)) {}
-
+  MediaSessionClient(): MediaSessionClient(nullptr) {}
   // Injectable MediaSession for tests.
-  explicit MediaSessionClient(scoped_refptr<MediaSession> media_session);
+  explicit MediaSessionClient(MediaSession* media_session);
 
   virtual ~MediaSessionClient();
 
@@ -46,7 +45,7 @@
   static std::unique_ptr<MediaSessionClient> Create();
 
   // Retrieves the singleton MediaSession associated with this client.
-  scoped_refptr<MediaSession>& GetMediaSession() { return media_session_; }
+  MediaSession* GetMediaSession() { return media_session_; }
 
   // The web app should set the MediaPositionState of the MediaSession object.
   // However, if that is not done, then query the web media player factory to
@@ -115,7 +114,8 @@
   // Indicate the media session client is active or not depending on the
   // media session playback state.
   bool is_active() {
-    return platform_playback_state_ != kMediaSessionPlaybackStateNone;
+    return session_state_.actual_playback_state() !=
+        kMediaSessionPlaybackStateNone;
   }
 
   // Set maybe freeze callback.
@@ -123,9 +123,13 @@
     maybe_freeze_callback_ = maybe_freeze_callback;
   }
 
+  void set_media_session(MediaSession* media_session) {
+    media_session_ = media_session;
+  }
+
  private:
   THREAD_CHECKER(thread_checker_);
-  scoped_refptr<MediaSession> media_session_;
+  MediaSession* media_session_;
   MediaSessionState session_state_;
   MediaSessionPlaybackState platform_playback_state_;
   const media::WebMediaPlayerFactory* media_player_factory_ = nullptr;
diff --git a/src/cobalt/media_session/media_session_test.cc b/src/cobalt/media_session/media_session_test.cc
index 36e6660..fcdd413 100644
--- a/src/cobalt/media_session/media_session_test.cc
+++ b/src/cobalt/media_session/media_session_test.cc
@@ -46,25 +46,20 @@
 namespace media_session {
 namespace {
 
+class MockMediaSessionClient;
+
 class MockCallbackFunction : public MediaSession::MediaSessionActionHandler {
  public:
   MOCK_CONST_METHOD1(
       Run, ReturnValue(const MediaSessionActionDetails& action_details));
 };
 
-class MockMediaSession : public MediaSession {
- public:
-  explicit MockMediaSession(MediaSessionClient* client)
-      : MediaSession(client) {}
-  MOCK_CONST_METHOD0(GetMonotonicNow, SbTimeMonotonic());
-};
-
 class MockMediaSessionClient : public MediaSessionClient {
  public:
-  MockMediaSessionClient() : MediaSessionClient(new MockMediaSession(this)) {}
-  MockMediaSession& mock_session() {
-    return static_cast<MockMediaSession&>(*GetMediaSession().get());
+  explicit MockMediaSessionClient(MediaSession* media_session) :
+      MediaSessionClient(media_session) {
   }
+
   void OnMediaSessionStateChanged(const MediaSessionState& session_state)
       override {
     session_state_ = session_state;
@@ -87,6 +82,18 @@
   size_t session_change_count_ = 0;
 };
 
+class MockMediaSession : public MediaSession {
+ public:
+  explicit MockMediaSession(MockMediaSessionClient* client)
+      : MediaSession(client) {}
+
+  MockMediaSessionClient* mock_session_client() {
+    return static_cast<MockMediaSessionClient*>(media_session_client());
+  }
+
+  MOCK_CONST_METHOD0(GetMonotonicNow, SbTimeMonotonic());
+};
+
 MATCHER_P(SeekTime, time, "") {
   return arg.action() == kMediaSessionActionSeekto && arg.seek_time() == time;
 }
@@ -102,85 +109,95 @@
 TEST(MediaSessionTest, MediaSessionTest) {
   base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
 
-  MockMediaSessionClient client;
-  scoped_refptr<MediaSession> session = client.GetMediaSession();
+  scoped_refptr<MockMediaSession> session =
+      scoped_refptr<MockMediaSession>(new MockMediaSession(
+          new MockMediaSessionClient(nullptr)));
+  session->media_session_client()->set_media_session(session);
 
   EXPECT_EQ(kMediaSessionPlaybackStateNone, session->playback_state());
 
   session->set_playback_state(kMediaSessionPlaybackStatePlaying);
 
-  client.WaitForSessionStateChange();
-  EXPECT_EQ(kMediaSessionPlaybackStatePlaying,
-            client.GetMediaSessionState().actual_playback_state());
+  session->mock_session_client()->WaitForSessionStateChange();
+  EXPECT_EQ(kMediaSessionPlaybackStatePlaying, session->mock_session_client()
+      ->GetMediaSessionState().actual_playback_state());
 
-  EXPECT_EQ(client.GetMediaSessionChangeCount(), 1);
+  EXPECT_EQ(session->mock_session_client()->GetMediaSessionChangeCount(), 1);
 }
 
 TEST(MediaSessionTest, ActualPlaybackState) {
   base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
 
-  MockMediaSessionClient client;
-  scoped_refptr<MediaSession> session = client.GetMediaSession();
+  scoped_refptr<MockMediaSession> session =
+      scoped_refptr<MockMediaSession>(new MockMediaSession(
+          new MockMediaSessionClient(nullptr)));
+  session->media_session_client()->set_media_session(session);
 
   // Trigger a session state change without impacting playback state.
   session->set_metadata(new MediaMetadata);
-  client.WaitForSessionStateChange();
-  EXPECT_EQ(client.GetMediaSessionChangeCount(), 1);
+  session->mock_session_client()->WaitForSessionStateChange();
+  EXPECT_EQ(session->mock_session_client()->GetMediaSessionChangeCount(), 1);
 
-  EXPECT_EQ(kMediaSessionPlaybackStateNone,
-            client.GetMediaSessionState().actual_playback_state());
+  EXPECT_EQ(kMediaSessionPlaybackStateNone, session->mock_session_client()
+      ->GetMediaSessionState().actual_playback_state());
 
-  client.UpdatePlatformPlaybackState(kMediaSessionPlaybackStatePlaying);
+  session->mock_session_client()->UpdatePlatformPlaybackState(
+      kMediaSessionPlaybackStatePlaying);
 
-  client.WaitForSessionStateChange();
-  EXPECT_EQ(kMediaSessionPlaybackStatePlaying,
-            client.GetMediaSessionState().actual_playback_state());
+  session->mock_session_client()->WaitForSessionStateChange();
+  EXPECT_EQ(kMediaSessionPlaybackStatePlaying, session->mock_session_client()
+      ->GetMediaSessionState().actual_playback_state());
 
   session->set_playback_state(kMediaSessionPlaybackStatePlaying);
 
-  client.WaitForSessionStateChange();
-  EXPECT_EQ(kMediaSessionPlaybackStatePlaying,
-            client.GetMediaSessionState().actual_playback_state());
+  session->mock_session_client()->WaitForSessionStateChange();
+  EXPECT_EQ(kMediaSessionPlaybackStatePlaying, session->mock_session_client()
+      ->GetMediaSessionState().actual_playback_state());
 
   session->set_playback_state(kMediaSessionPlaybackStatePaused);
 
-  client.WaitForSessionStateChange();
-  EXPECT_EQ(kMediaSessionPlaybackStatePlaying,
-            client.GetMediaSessionState().actual_playback_state());
+  session->mock_session_client()->WaitForSessionStateChange();
+  EXPECT_EQ(kMediaSessionPlaybackStatePlaying, session->mock_session_client()
+      ->GetMediaSessionState().actual_playback_state());
 
-  client.UpdatePlatformPlaybackState(kMediaSessionPlaybackStatePaused);
+  session->mock_session_client()->UpdatePlatformPlaybackState(
+      kMediaSessionPlaybackStatePaused);
 
-  client.WaitForSessionStateChange();
-  EXPECT_EQ(kMediaSessionPlaybackStatePaused,
-            client.GetMediaSessionState().actual_playback_state());
+  session->mock_session_client()->WaitForSessionStateChange();
+  EXPECT_EQ(kMediaSessionPlaybackStatePaused, session->mock_session_client()
+      ->GetMediaSessionState().actual_playback_state());
 
   session->set_playback_state(kMediaSessionPlaybackStateNone);
 
-  client.WaitForSessionStateChange();
-  EXPECT_EQ(kMediaSessionPlaybackStateNone,
-            client.GetMediaSessionState().actual_playback_state());
+  session->mock_session_client()->WaitForSessionStateChange();
+  EXPECT_EQ(kMediaSessionPlaybackStateNone, session->mock_session_client()
+      ->GetMediaSessionState().actual_playback_state());
 
-  client.UpdatePlatformPlaybackState(kMediaSessionPlaybackStateNone);
+  session->mock_session_client()->UpdatePlatformPlaybackState(
+      kMediaSessionPlaybackStateNone);
 
-  client.WaitForSessionStateChange();
-  EXPECT_EQ(kMediaSessionPlaybackStateNone,
-            client.GetMediaSessionState().actual_playback_state());
+  session->mock_session_client()->WaitForSessionStateChange();
+  EXPECT_EQ(kMediaSessionPlaybackStateNone, session->mock_session_client()
+      ->GetMediaSessionState().actual_playback_state());
 
-  EXPECT_GE(client.GetMediaSessionChangeCount(), 2);
+  EXPECT_GE(session->mock_session_client()->GetMediaSessionChangeCount(), 2);
 }
 
 TEST(MediaSessionTest, NullActionClears) {
   base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
 
-  MockMediaSessionClient client;
-  scoped_refptr<MediaSession> session = client.GetMediaSession();
+  scoped_refptr<MockMediaSession> session =
+      scoped_refptr<MockMediaSession>(new MockMediaSession(
+          new MockMediaSessionClient(nullptr)));
+  session->media_session_client()->set_media_session(session);
 
   // Trigger a session state change without impacting playback state.
   session->set_metadata(new MediaMetadata);
-  client.WaitForSessionStateChange();
-  EXPECT_EQ(client.GetMediaSessionChangeCount(), 1);
+  session->mock_session_client()->WaitForSessionStateChange();
+  EXPECT_EQ(session->mock_session_client()->GetMediaSessionChangeCount(), 1);
 
-  MediaSessionState state = client.GetMediaSessionState();
+  MediaSessionState state =
+      session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(kMediaSessionPlaybackStateNone, state.actual_playback_state());
   EXPECT_EQ(0, state.available_actions().to_ulong());
 
@@ -193,31 +210,38 @@
   FakeScriptValue<MediaSession::MediaSessionActionHandler> null_holder(NULL);
 
   session->SetActionHandler(kMediaSessionActionPlay, holder);
-  client.WaitForSessionStateChange();
-  EXPECT_EQ(1, client.GetMediaSessionState().available_actions().to_ulong());
-  client.InvokeAction(kCobaltExtensionMediaSessionActionPlay);
+  session->mock_session_client()->WaitForSessionStateChange();
+  EXPECT_EQ(1, session->mock_session_client()
+      ->GetMediaSessionState().available_actions().to_ulong());
+  session->mock_session_client()->InvokeAction(
+      kCobaltExtensionMediaSessionActionPlay);
 
   session->SetActionHandler(kMediaSessionActionPlay, null_holder);
-  client.WaitForSessionStateChange();
-  EXPECT_EQ(0, client.GetMediaSessionState().available_actions().to_ulong());
-  client.InvokeAction(kCobaltExtensionMediaSessionActionPlay);
+  session->mock_session_client()->WaitForSessionStateChange();
+  EXPECT_EQ(0, session->mock_session_client()
+      ->GetMediaSessionState().available_actions().to_ulong());
+  session->mock_session_client()->InvokeAction(
+      kCobaltExtensionMediaSessionActionPlay);
 
-  EXPECT_GE(client.GetMediaSessionChangeCount(), 3);
+  EXPECT_GE(session->mock_session_client()->GetMediaSessionChangeCount(), 3);
 }
 
 TEST(MediaSessionTest, AvailableActions) {
   base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
 
-  MockMediaSessionClient client;
   MediaSessionState state;
-  scoped_refptr<MediaSession> session = client.GetMediaSession();
+
+  scoped_refptr<MockMediaSession> session =
+      scoped_refptr<MockMediaSession>(new MockMediaSession(
+          new MockMediaSessionClient(nullptr)));
+  session->media_session_client()->set_media_session(session);
 
   // Trigger a session state change without impacting playback state.
   session->set_metadata(new MediaMetadata);
-  client.WaitForSessionStateChange();
-  EXPECT_EQ(client.GetMediaSessionChangeCount(), 1);
+  session->mock_session_client()->WaitForSessionStateChange();
+  EXPECT_EQ(session->mock_session_client()->GetMediaSessionChangeCount(), 1);
 
-  state = client.GetMediaSessionState();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(kMediaSessionPlaybackStateNone, state.actual_playback_state());
   EXPECT_EQ(0, state.available_actions().to_ulong());
 
@@ -227,83 +251,86 @@
 
   session->SetActionHandler(kMediaSessionActionPlay, holder);
 
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(1 << kMediaSessionActionPlay,
             state.available_actions().to_ulong());
 
   session->SetActionHandler(kMediaSessionActionPause, holder);
 
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(1 << kMediaSessionActionPlay,
             state.available_actions().to_ulong());
 
   session->SetActionHandler(kMediaSessionActionSeekto, holder);
 
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(1 << kMediaSessionActionPlay, state.available_actions().to_ulong());
 
-  client.UpdatePlatformPlaybackState(kMediaSessionPlaybackStatePlaying);
+  session->mock_session_client()->UpdatePlatformPlaybackState(
+      kMediaSessionPlaybackStatePlaying);
 
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(kMediaSessionPlaybackStatePlaying, state.actual_playback_state());
   EXPECT_EQ(1 << kMediaSessionActionPause | 1 << kMediaSessionActionSeekto,
             state.available_actions().to_ulong());
 
   session->set_playback_state(kMediaSessionPlaybackStatePlaying);
 
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(kMediaSessionPlaybackStatePlaying, state.actual_playback_state());
   EXPECT_EQ(1 << kMediaSessionActionPause | 1 << kMediaSessionActionSeekto,
             state.available_actions().to_ulong());
 
   session->set_playback_state(kMediaSessionPlaybackStatePaused);
 
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(kMediaSessionPlaybackStatePlaying, state.actual_playback_state());
   EXPECT_EQ(1 << kMediaSessionActionPause | 1 << kMediaSessionActionSeekto,
             state.available_actions().to_ulong());
 
   session->set_playback_state(kMediaSessionPlaybackStatePlaying);
 
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(kMediaSessionPlaybackStatePlaying, state.actual_playback_state());
   EXPECT_EQ(1 << kMediaSessionActionPause | 1 << kMediaSessionActionSeekto,
             state.available_actions().to_ulong());
 
-  client.UpdatePlatformPlaybackState(kMediaSessionPlaybackStatePaused);
+  session->mock_session_client()->UpdatePlatformPlaybackState(
+      kMediaSessionPlaybackStatePaused);
 
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(kMediaSessionPlaybackStatePlaying, state.actual_playback_state());
   EXPECT_EQ(1 << kMediaSessionActionPause | 1 << kMediaSessionActionSeekto,
             state.available_actions().to_ulong());
 
   session->set_playback_state(kMediaSessionPlaybackStateNone);
 
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(kMediaSessionPlaybackStateNone, state.actual_playback_state());
   EXPECT_EQ(1 << kMediaSessionActionPlay, state.available_actions().to_ulong());
 
   session->set_playback_state(kMediaSessionPlaybackStatePaused);
 
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(kMediaSessionPlaybackStatePaused, state.actual_playback_state());
   EXPECT_EQ(1 << kMediaSessionActionPlay | 1 << kMediaSessionActionSeekto,
             state.available_actions().to_ulong());
 
-  client.UpdatePlatformPlaybackState(kMediaSessionPlaybackStateNone);
+  session->mock_session_client()->UpdatePlatformPlaybackState(
+      kMediaSessionPlaybackStateNone);
 
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(kMediaSessionPlaybackStatePaused, state.actual_playback_state());
   EXPECT_EQ(1 << kMediaSessionActionPlay | 1 << kMediaSessionActionSeekto,
             state.available_actions().to_ulong());
@@ -312,8 +339,10 @@
 TEST(MediaSessionTest, InvokeAction) {
   base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
 
-  MockMediaSessionClient client;
-  scoped_refptr<MediaSession> session = client.GetMediaSession();
+  scoped_refptr<MockMediaSession> session =
+      scoped_refptr<MockMediaSession>(new MockMediaSession(
+          new MockMediaSessionClient(nullptr)));
+  session->media_session_client()->set_media_session(session);
 
   MockCallbackFunction cf;
   FakeScriptValue<MediaSession::MediaSessionActionHandler> holder(&cf);
@@ -325,14 +354,16 @@
 
   details->set_action(kMediaSessionActionSeekto);
   details->set_seek_time(1.2);
-  client.InvokeAction(std::move(details));
+  session->mock_session_client()->InvokeAction(std::move(details));
 }
 
 TEST(MediaSessionTest, SeekDetails) {
   base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
 
-  MockMediaSessionClient client;
-  scoped_refptr<MediaSession> session = client.GetMediaSession();
+  scoped_refptr<MockMediaSession> session =
+      scoped_refptr<MockMediaSession>(new MockMediaSession(
+          new MockMediaSessionClient(nullptr)));
+  session->media_session_client()->set_media_session(session);
 
   MockCallbackFunction cf;
   FakeScriptValue<MediaSession::MediaSessionActionHandler> holder(&cf);
@@ -344,41 +375,46 @@
 
   EXPECT_CALL(cf, Run(SeekNoOffset(kMediaSessionActionSeekforward)))
       .WillOnce(Return(CallbackResult<void>()));
-  client.InvokeAction(kCobaltExtensionMediaSessionActionSeekforward);
+  session->mock_session_client()->InvokeAction(
+      kCobaltExtensionMediaSessionActionSeekforward);
 
   EXPECT_CALL(cf, Run(SeekNoOffset(kMediaSessionActionSeekbackward)))
       .WillOnce(Return(CallbackResult<void>()));
-  client.InvokeAction(kCobaltExtensionMediaSessionActionSeekbackward);
+  session->mock_session_client()->InvokeAction(
+      kCobaltExtensionMediaSessionActionSeekbackward);
 
   EXPECT_CALL(cf, Run(SeekTime(1.2))).WillOnce(Return(CallbackResult<void>()));
   CobaltExtensionMediaSessionActionDetailsInit(
       &details, kCobaltExtensionMediaSessionActionSeekto);
   details.seek_time = 1.2;
-  client.InvokeCobaltExtensionAction(details);
+  session->mock_session_client()->InvokeCobaltExtensionAction(details);
 
   EXPECT_CALL(cf, Run(SeekOffset(kMediaSessionActionSeekforward, 3.4)))
       .WillOnce(Return(CallbackResult<void>()));
   CobaltExtensionMediaSessionActionDetailsInit(
       &details, kCobaltExtensionMediaSessionActionSeekforward);
   details.seek_offset = 3.4;
-  client.InvokeCobaltExtensionAction(details);
+  session->mock_session_client()->InvokeCobaltExtensionAction(details);
 
   EXPECT_CALL(cf, Run(SeekOffset(kMediaSessionActionSeekbackward, 5.6)))
       .WillOnce(Return(CallbackResult<void>()));
   CobaltExtensionMediaSessionActionDetailsInit(
       &details, kCobaltExtensionMediaSessionActionSeekbackward);
   details.seek_offset = 5.6;
-  client.InvokeCobaltExtensionAction(details);
+  session->mock_session_client()->InvokeCobaltExtensionAction(details);
 
-  client.WaitForSessionStateChange();
-  EXPECT_GE(client.GetMediaSessionChangeCount(), 0);
+  session->mock_session_client()->WaitForSessionStateChange();
+  EXPECT_GE(session->mock_session_client()->GetMediaSessionChangeCount(), 0);
 }
 
 TEST(MediaSessionTest, PositionState) {
   base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
 
-  MockMediaSessionClient client;
-  MockMediaSession& session = client.mock_session();
+  scoped_refptr<MockMediaSession> session =
+      scoped_refptr<MockMediaSession>(new MockMediaSession(
+          new MockMediaSessionClient(nullptr)));
+  session->media_session_client()->set_media_session(session);
+
   MediaSessionState state;
 
   SbTimeMonotonic start_time = 1111111111;
@@ -389,23 +425,23 @@
   position_state->set_position(10.0);
 
   // Trigger a session state change without impacting playback state.
-  session.set_metadata(new MediaMetadata);
-  client.WaitForSessionStateChange();
-  EXPECT_EQ(client.GetMediaSessionChangeCount(), 1);
+  session->set_metadata(new MediaMetadata);
+  session->mock_session_client()->WaitForSessionStateChange();
+  EXPECT_EQ(session->mock_session_client()->GetMediaSessionChangeCount(), 1);
 
   // Position state not yet reported
-  state = client.GetMediaSessionState();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(0,
             state.GetCurrentPlaybackPosition(start_time + 999 * kSbTimeSecond));
   EXPECT_EQ(0, state.duration());
   EXPECT_EQ(0.0, state.actual_playback_rate());
 
   // Forward playback
-  EXPECT_CALL(session, GetMonotonicNow()).WillOnce(Return(start_time));
+  EXPECT_CALL(*session, GetMonotonicNow()).WillOnce(Return(start_time));
   position_state->set_playback_rate(1.0);
-  session.SetPositionState(position_state);
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->SetPositionState(position_state);
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ((10 + 50) * kSbTimeSecond,
             state.GetCurrentPlaybackPosition(start_time + 50 * kSbTimeSecond));
   EXPECT_EQ(100 * kSbTimeSecond,
@@ -414,11 +450,11 @@
   EXPECT_EQ(1.0, state.actual_playback_rate());
 
   // Fast playback
-  EXPECT_CALL(session, GetMonotonicNow()).WillOnce(Return(start_time));
+  EXPECT_CALL(*session, GetMonotonicNow()).WillOnce(Return(start_time));
   position_state->set_playback_rate(2.0);
-  session.SetPositionState(position_state);
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->SetPositionState(position_state);
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ((10 + 2 * 20) * kSbTimeSecond,
             state.GetCurrentPlaybackPosition(start_time + 20 * kSbTimeSecond));
   EXPECT_EQ(100 * kSbTimeSecond,
@@ -427,11 +463,11 @@
   EXPECT_EQ(2.0, state.actual_playback_rate());
 
   // Reverse playback
-  EXPECT_CALL(session, GetMonotonicNow()).WillOnce(Return(start_time));
+  EXPECT_CALL(*session, GetMonotonicNow()).WillOnce(Return(start_time));
   position_state->set_playback_rate(-1.0);
-  session.SetPositionState(position_state);
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->SetPositionState(position_state);
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(0 * kSbTimeSecond,
             state.GetCurrentPlaybackPosition(start_time + 20 * kSbTimeSecond));
   EXPECT_EQ((10 - 3) * kSbTimeSecond,
@@ -440,12 +476,12 @@
   EXPECT_EQ(-1.0, state.actual_playback_rate());
 
   // Indefinite duration (live) playback
-  EXPECT_CALL(session, GetMonotonicNow()).WillOnce(Return(start_time));
+  EXPECT_CALL(*session, GetMonotonicNow()).WillOnce(Return(start_time));
   position_state->set_duration(std::numeric_limits<double>::infinity());
   position_state->set_playback_rate(1.0);
-  session.SetPositionState(position_state);
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->SetPositionState(position_state);
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(10 * kSbTimeSecond + 1 * kSbTimeDay,
             state.GetCurrentPlaybackPosition(start_time + 1 * kSbTimeDay));
   EXPECT_EQ(kSbTimeMax, state.duration());
@@ -454,45 +490,47 @@
   // Paused playback
   // (Actual playback rate is 0.0, so position is the last reported position.
   //  The web app should update position and playback states together.)
-  session.set_playback_state(kMediaSessionPlaybackStatePaused);
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->set_playback_state(kMediaSessionPlaybackStatePaused);
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(10 * kSbTimeSecond,
             state.GetCurrentPlaybackPosition(start_time + 999 * kSbTimeSecond));
   EXPECT_EQ(kSbTimeMax, state.duration());
   EXPECT_EQ(0.0, state.actual_playback_rate());
-  session.set_playback_state(kMediaSessionPlaybackStatePlaying);
+  session->set_playback_state(kMediaSessionPlaybackStatePlaying);
 
   // Position state cleared
-  EXPECT_CALL(session, GetMonotonicNow()).WillOnce(Return(start_time));
-  session.SetPositionState(base::nullopt);
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  EXPECT_CALL(*session, GetMonotonicNow()).WillOnce(Return(start_time));
+  session->SetPositionState(base::nullopt);
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   EXPECT_EQ(0,
             state.GetCurrentPlaybackPosition(start_time + 999 * kSbTimeSecond));
   EXPECT_EQ(0, state.duration());
   EXPECT_EQ(0.0, state.actual_playback_rate());
 
-  EXPECT_GE(client.GetMediaSessionChangeCount(), 3);
+  EXPECT_GE(session->mock_session_client()->GetMediaSessionChangeCount(), 3);
 }
 
 TEST(MediaSessionTest, Metadata) {
   base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
 
-  MockMediaSessionClient client;
-  MockMediaSession& session = client.mock_session();
+  scoped_refptr<MockMediaSession> session =
+      scoped_refptr<MockMediaSession>(new MockMediaSession(
+          new MockMediaSessionClient(nullptr)));
+  session->media_session_client()->set_media_session(session);
   MediaSessionState state;
 
   MediaMetadataInit init_metadata;
   base::Optional<MediaMetadataInit> state_metadata;
 
   // Trigger a session state change without impacting metadata.
-  session.set_playback_state(kMediaSessionPlaybackStateNone);
-  client.WaitForSessionStateChange();
-  EXPECT_EQ(client.GetMediaSessionChangeCount(), 1);
+  session->set_playback_state(kMediaSessionPlaybackStateNone);
+  session->mock_session_client()->WaitForSessionStateChange();
+  EXPECT_EQ(session->mock_session_client()->GetMediaSessionChangeCount(), 1);
 
   // Metadata not yet set
-  state = client.GetMediaSessionState();
+  state = session->mock_session_client()->GetMediaSessionState();
   state_metadata = state.metadata();
   EXPECT_FALSE(state.has_metadata());
   EXPECT_FALSE(state_metadata.has_value());
@@ -507,11 +545,11 @@
   script::Sequence<MediaImage> artwork;
   artwork.push_back(art_image);
   init_metadata.set_artwork(artwork);
-  session.set_metadata(
+  session->set_metadata(
       scoped_refptr<MediaMetadata>(new MediaMetadata(init_metadata)));
 
-  client.WaitForSessionStateChange();
-  state = client.GetMediaSessionState();
+  session->mock_session_client()->WaitForSessionStateChange();
+  state = session->mock_session_client()->GetMediaSessionState();
   state_metadata = state.metadata();
   EXPECT_TRUE(state.has_metadata());
   EXPECT_TRUE(state_metadata.has_value());
@@ -521,7 +559,7 @@
   EXPECT_EQ(1, state_metadata->artwork().size());
   EXPECT_EQ("http://art.image", state_metadata->artwork().at(0).src());
 
-  EXPECT_GE(client.GetMediaSessionChangeCount(), 2);
+  EXPECT_GE(session->mock_session_client()->GetMediaSessionChangeCount(), 2);
 }
 
 }  // namespace
diff --git a/src/cobalt/media_stream/media_stream_test.gyp b/src/cobalt/media_stream/media_stream_test.gyp
index a7cc5b1..aac127c 100644
--- a/src/cobalt/media_stream/media_stream_test.gyp
+++ b/src/cobalt/media_stream/media_stream_test.gyp
@@ -30,6 +30,7 @@
         'testing/mock_media_stream_audio_track.h',
       ],
       'dependencies': [
+        '<@(cobalt_platform_dependencies)',
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
         '<(DEPTH)/cobalt/media_stream/media_stream.gyp:media_stream',
         '<(DEPTH)/testing/gmock.gyp:gmock',
diff --git a/src/cobalt/renderer/pipeline.cc b/src/cobalt/renderer/pipeline.cc
index 97bef85..a78f0ed 100644
--- a/src/cobalt/renderer/pipeline.cc
+++ b/src/cobalt/renderer/pipeline.cc
@@ -24,12 +24,14 @@
 #include "cobalt/base/address_sanitizer.h"
 #include "cobalt/base/cobalt_paths.h"
 #include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/extension/graphics.h"
 #include "cobalt/math/rect_f.h"
 #include "cobalt/render_tree/brush.h"
 #include "cobalt/render_tree/composition_node.h"
 #include "cobalt/render_tree/dump_render_tree_to_string.h"
 #include "cobalt/render_tree/rect_node.h"
 #include "nb/memory_scope.h"
+#include "starboard/system.h"
 
 using cobalt::render_tree::Node;
 using cobalt::render_tree::animations::AnimateNode;
@@ -38,6 +40,7 @@
 namespace renderer {
 
 namespace {
+
 #if !defined(COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS)
 // This default value has been moved from cobalt/build/cobalt_configuration.gypi
 // in favor of the usage of
@@ -76,6 +79,34 @@
   }
 }
 
+bool ShouldClearFrameOnShutdown(render_tree::ColorRGBA* out_clear_color) {
+#if SB_API_VERSION >= 11
+  const CobaltExtensionGraphicsApi* graphics_extension =
+      static_cast<const CobaltExtensionGraphicsApi*>(
+          SbSystemGetExtension(kCobaltExtensionGraphicsName));
+  if (graphics_extension &&
+      strcmp(graphics_extension->name, kCobaltExtensionGraphicsName) == 0 &&
+      graphics_extension->version >= 4) {
+    float r, g, b, a;
+    if (graphics_extension->ShouldClearFrameOnShutdown(&r, &g, &b, &a)) {
+      out_clear_color->set_r(r);
+      out_clear_color->set_g(g);
+      out_clear_color->set_b(b);
+      out_clear_color->set_a(a);
+      return true;
+    }
+    return false;
+  }
+#endif
+
+  // Default is to clear to opaque black.
+  out_clear_color->set_r(0.0f);
+  out_clear_color->set_g(0.0f);
+  out_clear_color->set_b(0.0f);
+  out_clear_color->set_a(1.0f);
+  return true;
+}
+
 }  // namespace
 
 Pipeline::Pipeline(const CreateRasterizerFunction& create_rasterizer_function,
@@ -329,13 +360,13 @@
       minimum_frame_interval_milliseconds =
           COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS;
     } else {
-      DLOG(ERROR) <<
-          "COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS and "
-          "CobaltExtensionGraphicsApi::GetMinimumFrameIntervalInMilliseconds"
-          "are both defined."
-          "Remove the 'cobalt_minimum_frame_time_in_milliseconds' ";
-          "from ../gyp_configuration.gypi in favor of the usage of "
-          "CobaltExtensionGraphicsApi::GetMinimumFrameIntervalInMilliseconds."
+      DLOG(ERROR)
+          << "COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS and "
+             "CobaltExtensionGraphicsApi::GetMinimumFrameIntervalInMilliseconds"
+             "are both defined."
+             "Remove the 'cobalt_minimum_frame_time_in_milliseconds' ";
+      "from ../gyp_configuration.gypi in favor of the usage of "
+      "CobaltExtensionGraphicsApi::GetMinimumFrameIntervalInMilliseconds."
     }
 #else
     if (minimum_frame_interval_milliseconds < 0.0f) {
@@ -373,11 +404,13 @@
   bool is_new_render_tree = submission.render_tree != last_render_tree_;
   bool has_render_tree_changed =
       !last_animations_expired_ || is_new_render_tree;
-  bool force_rasterize = submit_even_if_render_tree_is_unchanged_ ||
-      fps_overlay_update_pending_;
+  bool force_rasterize =
+      submit_even_if_render_tree_is_unchanged_ || fps_overlay_update_pending_;
 
-  float maximum_frame_interval_milliseconds = graphics_context_ ?
-      graphics_context_->GetMaximumFrameIntervalInMilliseconds() : -1.0f;
+  float maximum_frame_interval_milliseconds =
+      graphics_context_
+          ? graphics_context_->GetMaximumFrameIntervalInMilliseconds()
+          : -1.0f;
   if (maximum_frame_interval_milliseconds >= 0.0f) {
     base::TimeDelta max_time_between_rasterize =
         base::TimeDelta::FromMillisecondsD(maximum_frame_interval_milliseconds);
@@ -617,18 +650,18 @@
   // Shutdown the FPS overlay which may reference render trees.
   fps_overlay_ = base::nullopt;
 
-  // Submit a black fullscreen rect node to clear the display before shutting
+  // Submit a fullscreen rect node to clear the display before shutting
   // down.  This can be helpful if we quit while playing a video via
   // punch-through, which may result in unexpected images/colors appearing for
   // a flicker behind the display.
-  if (render_target_ && (clear_on_shutdown_mode_ == kClearToBlack)) {
-    rasterizer_->Submit(
-        new render_tree::RectNode(
-            math::RectF(render_target_->GetSize()),
-            std::unique_ptr<render_tree::Brush>(
-                new render_tree::SolidColorBrush(
-                    render_tree::ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f)))),
-        render_target_);
+  render_tree::ColorRGBA clear_color;
+  if (render_target_ && clear_on_shutdown_mode_ == kClearAccordingToPlatform &&
+      ShouldClearFrameOnShutdown(&clear_color)) {
+    rasterizer_->Submit(new render_tree::RectNode(
+                            math::RectF(render_target_->GetSize()),
+                            std::unique_ptr<render_tree::Brush>(
+                                new render_tree::SolidColorBrush(clear_color))),
+                        render_target_);
   }
 
   // This potential reference to a render tree whose animations may have ended
diff --git a/src/cobalt/renderer/pipeline.h b/src/cobalt/renderer/pipeline.h
index bbb9658..5848d95 100644
--- a/src/cobalt/renderer/pipeline.h
+++ b/src/cobalt/renderer/pipeline.h
@@ -58,7 +58,12 @@
       RasterizationCompleteCallback;
 
   enum ShutdownClearMode {
-    kClearToBlack,
+    // Query CobaltExtensionGraphicsApi's ShouldClearFrameOnShutdown for
+    // shutdown behavior.
+    kClearAccordingToPlatform,
+
+    // Do not clear regardless of what CobaltExtensionGraphicsApi's
+    // ShouldClearFrameOnShutdown specifies.
     kNoClear,
   };
 
diff --git a/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc
index 801112c..1f04c7a 100644
--- a/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc
@@ -234,7 +234,7 @@
   uint32_t untouched_states =
       kMSAAEnable_GrGLBackendState | kStencil_GrGLBackendState |
       kPixelStore_GrGLBackendState | kFixedFunction_GrGLBackendState |
-      kPathRendering_GrGLBackendState | kMisc_GrGLBackendState;
+      kPathRendering_GrGLBackendState;
 
   GetFallbackContext()->resetContext(~untouched_states & kAll_GrBackendState);
 }
diff --git a/src/cobalt/renderer/renderer_module.cc b/src/cobalt/renderer/renderer_module.cc
index 6b52456..9783414 100644
--- a/src/cobalt/renderer/renderer_module.cc
+++ b/src/cobalt/renderer/renderer_module.cc
@@ -102,7 +102,7 @@
         // deprecate the submit_even_if_render_tree_is_unchanged.
         false,
 #endif
-        renderer::Pipeline::kClearToBlack, pipeline_options));
+        renderer::Pipeline::kClearAccordingToPlatform, pipeline_options));
   }
 }
 
diff --git a/src/cobalt/site/docs/reference/starboard/modules/configuration.md b/src/cobalt/site/docs/reference/starboard/modules/configuration.md
index fff4d9a..5e41790 100644
--- a/src/cobalt/site/docs/reference/starboard/modules/configuration.md
+++ b/src/cobalt/site/docs/reference/starboard/modules/configuration.md
@@ -53,11 +53,6 @@
 SB_DEPRECATED_EXTERNAL(...) annotates the function as deprecated for external
 clients, but not deprecated for starboard.
 
-### SB_DISALLOW_COPY_AND_ASSIGN(TypeName) ###
-
-A macro to disallow the copy constructor and operator= functions This should be
-used in the private: declarations for a class
-
 ### SB_EXPERIMENTAL_API_VERSION ###
 
 The API version that is currently open for changes, and therefore is not stable
diff --git a/src/cobalt/speech/sandbox/sandbox.gyp b/src/cobalt/speech/sandbox/sandbox.gyp
index 03be453..25aaed0 100644
--- a/src/cobalt/speech/sandbox/sandbox.gyp
+++ b/src/cobalt/speech/sandbox/sandbox.gyp
@@ -28,6 +28,7 @@
         'speech_sandbox_main.cc',
       ],
       'dependencies': [
+        '<@(cobalt_platform_dependencies)',
         '<(DEPTH)/cobalt/audio/audio.gyp:audio',
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/debug/debug.gyp:console_command_manager',
diff --git a/src/cobalt/tools/automated_testing/cobalt_runner.py b/src/cobalt/tools/automated_testing/cobalt_runner.py
index 1efd7dd..003edf5 100644
--- a/src/cobalt/tools/automated_testing/cobalt_runner.py
+++ b/src/cobalt/tools/automated_testing/cobalt_runner.py
@@ -9,10 +9,10 @@
 import os
 import re
 import sys
-import thread
 import threading
 import time
 import traceback
+import thread
 
 import _env  # pylint: disable=unused-import
 from cobalt.tools.automated_testing import c_val_names
@@ -26,7 +26,8 @@
 RE_WEBDRIVER_FAILED = re.compile(r'Could not start WebDriver server')
 # Pattern to match Cobalt log line for when a WindowDriver has been created.
 RE_WINDOWDRIVER_CREATED = re.compile(
-    r'^\[[\d:]+/[\d.]+:INFO:browser_module\.cc\(\d+\)\] Created WindowDriver: ID=\S+'
+    (r'^\[[\d:]+/[\d.]+:INFO:browser_module\.cc\(\d+\)\] Created WindowDriver: '
+     r'ID=\S+')
 )
 # Pattern to match Cobalt log line for when a WebModule is has been loaded.
 RE_WEBMODULE_LOADED = re.compile(
@@ -247,7 +248,7 @@
       self.WaitForStart()
     except KeyboardInterrupt:
       # potentially from thread.interrupt_main(). We will treat as
-      # a timeout regardless
+      # a timeout regardless.
 
       self.Exit(should_fail=True)
       raise TimeoutException
@@ -286,8 +287,8 @@
       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'
-      )
+          '***Runner thread still alive after sending graceful shutdown '
+          'command, try again by killing app***\n')
       self.launcher.Kill()
     # Once the write end of the pipe has been closed by the launcher, the reader
     # thread will get EOF and exit.
@@ -328,7 +329,7 @@
       logging.info('Cobalt terminated.')
       if not self.failed and self.success_message:
         print('{}\n'.format(self.success_message))
-        logging.info('{}\n'.format(self.success_message))
+        logging.info('%s\n', self.success_message)
     # pylint: disable=broad-except
     except Exception as ex:
       sys.stderr.write('Exception running Cobalt ' + str(ex))
@@ -370,15 +371,14 @@
     """
     javascript_code = 'return h5vcc.cVal.getValue(\'{}\')'.format(cval_name)
     cval_string = self.ExecuteJavaScript(javascript_code)
-    if cval_string is None:
-      return None
-    else:
+    if cval_string:
       try:
         # Try to parse numbers and booleans.
         return json.loads(cval_string)
       except ValueError:
         # If we can't parse a value, return the cval string as-is.
         return cval_string
+    return None
 
   def GetCvalBatch(self, cval_name_list):
     """Retrieves a batch of cvals.
@@ -465,6 +465,8 @@
     Returns:
       Array of selected elements
     """
+    elements = None
+
     # The retry part below is a temporary workaround to handle command
     # failures during a short window of stale Cobalt WindowDriver
     # after navigation. We only introduced it because of limited time budget
diff --git a/src/cobalt/updater/configurator.cc b/src/cobalt/updater/configurator.cc
index aacee6a..f48c66a 100644
--- a/src/cobalt/updater/configurator.cc
+++ b/src/cobalt/updater/configurator.cc
@@ -28,50 +28,35 @@
 // Default time constants.
 const int kDelayOneMinute = 60;
 const int kDelayOneHour = kDelayOneMinute * 60;
-
-#if defined(COBALT_BUILD_TYPE_DEBUG) || defined(COBALT_BUILD_TYPE_DEVEL)
-const std::set<std::string> valid_channels = {"dev"};
-const std::string kDefaultUpdaterChannel = "dev";
-#elif defined(COBALT_BUILD_TYPE_QA)
 const std::set<std::string> valid_channels = {
-    // Default channel for qa builds
-    "qa",
-    // Test an update with higher version than qa channel
-    "test",
-    // Test an update with mismatched sabi
-    "tmsabi",
-    // Test an update that does nothing
-    "tnoop",
-    // Test an update that crashes
-    "tcrash",
-    // Test an update that fails verification
-    "tfailv",
-    // Test a series of continuous updates with two channels
-    "tseries1", "tseries2",
-};
-const std::string kDefaultUpdaterChannel = "qa";
-#elif defined(COBALT_BUILD_TYPE_GOLD)
-const std::set<std::string> valid_channels = {
-    // Default channel for gold builds
-    "prod",
-    // Channel for dogfooders
+    // Default channel for debug/devel builds.
+    "dev",
+    // Channel for dogfooders.
     "dogfood",
+    // Default channel for gold builds.
+    "prod",
     // Default channel for qa builds. A gold build can switch to this channel to
     // get an official qa build.
     "qa",
-    // Test an update with higher version than prod channel
+    // Test an update with higher version than prod channel.
     "test",
-    // Test an update with mismatched sabi
+    // Test an update with mismatched sabi.
     "tmsabi",
-    // Test an update that does nothing
+    // Test an update that does nothing.
     "tnoop",
-    // Test an update that crashes
+    // Test an update that crashes.
     "tcrash",
-    // Test an update that fails verification
+    // Test an update that fails verification.
     "tfailv",
-    // Test a series of continuous updates with two channels
+    // Test a series of continuous updates with two channels.
     "tseries1", "tseries2",
 };
+
+#if defined(COBALT_BUILD_TYPE_DEBUG) || defined(COBALT_BUILD_TYPE_DEVEL)
+const std::string kDefaultUpdaterChannel = "dev";
+#elif defined(COBALT_BUILD_TYPE_QA)
+const std::string kDefaultUpdaterChannel = "qa";
+#elif defined(COBALT_BUILD_TYPE_GOLD)
 const std::string kDefaultUpdaterChannel = "prod";
 #endif
 
diff --git a/src/cobalt/updater/noop_sandbox.cc b/src/cobalt/updater/noop_sandbox.cc
index d9ce028..5f63965 100644
--- a/src/cobalt/updater/noop_sandbox.cc
+++ b/src/cobalt/updater/noop_sandbox.cc
@@ -15,7 +15,12 @@
 // This is a test app for Evergreen that does nothing.
 
 #include "starboard/event.h"
+#include "starboard/system.h"
+#include "starboard/thread.h"
+#include "starboard/time.h"
 
 void SbEventHandle(const SbEvent* event) {
-  // noop
+  // No-op app. Exit after 1s.
+  SbThreadSleep(kSbTimeSecond);
+  SbSystemRequestStop(0);
 }
diff --git a/src/cobalt/websocket/websocket.gyp b/src/cobalt/websocket/websocket.gyp
index c3bfb3a..4ba8d9a 100644
--- a/src/cobalt/websocket/websocket.gyp
+++ b/src/cobalt/websocket/websocket.gyp
@@ -33,6 +33,7 @@
         'web_socket_impl.h',
       ],
       'dependencies': [
+        '<@(cobalt_platform_dependencies)',
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/browser/browser_bindings_gen.gyp:generated_types',
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
diff --git a/src/cobalt/xhr/url_fetcher_buffer_writer.cc b/src/cobalt/xhr/url_fetcher_buffer_writer.cc
index a6eec44..fd67bb4 100644
--- a/src/cobalt/xhr/url_fetcher_buffer_writer.cc
+++ b/src/cobalt/xhr/url_fetcher_buffer_writer.cc
@@ -28,6 +28,7 @@
 const int64_t kDefaultPreAllocateSizeInBytes = 64 * 1024;
 // Set max allocate size to avoid erroneous size estimate.
 const int64_t kMaxPreAllocateSizeInBytes = 10 * 1024 * 1024;
+const uint8_t kResizingMultiplier = 2;
 
 void ReleaseMemory(std::string* str) {
   DCHECK(str);
@@ -167,6 +168,9 @@
   } else {
     capacity_known_ = true;
   }
+  // Record the desired_capacity_ to avoid reserving unused memory during
+  // resizing.
+  desired_capacity_ = static_cast<size_t>(capacity);
 
   if (capacity == 0) {
     return;
@@ -218,7 +222,16 @@
       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);
+    size_t new_size = std::max(
+        std::min(data_as_array_buffer_.byte_length() * kResizingMultiplier,
+                 desired_capacity_),
+        data_as_array_buffer_size_ + num_bytes);
+    if (new_size > desired_capacity_) {
+      // Content-length is wrong, response size is completely unknown.
+      // Double the capacity to avoid frequent resizing.
+      new_size *= kResizingMultiplier;
+    }
+    data_as_array_buffer_.Resize(new_size);
   }
 
   auto destination = static_cast<uint8_t*>(data_as_array_buffer_.data()) +
diff --git a/src/cobalt/xhr/url_fetcher_buffer_writer.h b/src/cobalt/xhr/url_fetcher_buffer_writer.h
index 8eb9736..35058ab 100644
--- a/src/cobalt/xhr/url_fetcher_buffer_writer.h
+++ b/src/cobalt/xhr/url_fetcher_buffer_writer.h
@@ -74,6 +74,7 @@
     Type type_;
     bool allow_preallocate_ = true;
     bool capacity_known_ = false;
+    size_t desired_capacity_ = 0;
 
     // This class can be accessed by both network and MainWebModule threads.
     mutable base::Lock lock_;
diff --git a/src/cobalt/xhr/xhr.gyp b/src/cobalt/xhr/xhr.gyp
index 051822e..c3ebc4c 100644
--- a/src/cobalt/xhr/xhr.gyp
+++ b/src/cobalt/xhr/xhr.gyp
@@ -23,8 +23,6 @@
       'sources': [
         'url_fetcher_buffer_writer.cc',
         'url_fetcher_buffer_writer.h',
-        'xhr_response_data.cc',
-        'xhr_response_data.h',
         'xml_http_request.cc',
         'xml_http_request.h',
         'xml_http_request_event_target.cc',
@@ -58,10 +56,10 @@
       'target_name': 'xhr_test',
       'type': '<(gtest_target_type)',
       'sources': [
-        'xhr_response_data_test.cc',
         'xml_http_request_test.cc',
       ],
       'dependencies': [
+        '<@(cobalt_platform_dependencies)',
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
         '<(DEPTH)/testing/gmock.gyp:gmock',
diff --git a/src/cobalt/xhr/xhr_response_data.cc b/src/cobalt/xhr/xhr_response_data.cc
deleted file mode 100644
index d2e5051..0000000
--- a/src/cobalt/xhr/xhr_response_data.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2015 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/xhr/xhr_response_data.h"
-
-#include <algorithm>
-
-#include "cobalt/dom/global_stats.h"
-
-namespace cobalt {
-namespace xhr {
-
-namespace {
-
-// When we don't have any data, we still want to return a non-null pointer to a
-// valid memory location.  Because even it will never be accessed, a null
-// pointer may trigger undefined behavior in functions like memcpy.  So we
-// create this dummy value here and return its address when we don't have any
-// data.
-uint8 s_dummy;
-
-// We are using std::string to store binary data so we want to ensure that char
-// occupies one byte.
-COMPILE_ASSERT(sizeof(char) == 1, char_should_occupy_one_byte);
-
-}  // namespace
-
-XhrResponseData::XhrResponseData() { IncreaseMemoryUsage(); }
-
-XhrResponseData::~XhrResponseData() { DecreaseMemoryUsage(); }
-
-void XhrResponseData::Clear() {
-  DecreaseMemoryUsage();
-  // Use swap to force free the memory allocated.
-  std::string dummy;
-  data_.swap(dummy);
-  IncreaseMemoryUsage();
-}
-
-void XhrResponseData::Reserve(size_t new_capacity_bytes) {
-  DecreaseMemoryUsage();
-  data_.reserve(new_capacity_bytes);
-  IncreaseMemoryUsage();
-}
-
-void XhrResponseData::Append(const uint8* source_data, size_t size_bytes) {
-  if (size_bytes == 0) {
-    return;
-  }
-  DecreaseMemoryUsage();
-  data_.resize(data_.size() + size_bytes);
-  memcpy(&data_[data_.size() - size_bytes], source_data, size_bytes);
-  IncreaseMemoryUsage();
-}
-
-const uint8* XhrResponseData::data() const {
-  return data_.empty() ? &s_dummy : reinterpret_cast<const uint8*>(&data_[0]);
-}
-
-uint8* XhrResponseData::data() {
-  return data_.empty() ? &s_dummy : reinterpret_cast<uint8*>(&data_[0]);
-}
-
-void XhrResponseData::IncreaseMemoryUsage() {
-  dom::GlobalStats::GetInstance()->IncreaseXHRMemoryUsage(capacity());
-}
-
-void XhrResponseData::DecreaseMemoryUsage() {
-  dom::GlobalStats::GetInstance()->DecreaseXHRMemoryUsage(capacity());
-}
-
-}  // namespace xhr
-}  // namespace cobalt
diff --git a/src/cobalt/xhr/xhr_response_data.h b/src/cobalt/xhr/xhr_response_data.h
deleted file mode 100644
index 894cd3f..0000000
--- a/src/cobalt/xhr/xhr_response_data.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2015 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_XHR_XHR_RESPONSE_DATA_H_
-#define COBALT_XHR_XHR_RESPONSE_DATA_H_
-
-#include <string>
-
-#include "base/basictypes.h"
-
-namespace cobalt {
-namespace xhr {
-
-// Simple wrapper for an array of data.
-// Used by XMLHttpRequest to construct the response body.
-class XhrResponseData {
- public:
-  XhrResponseData();
-  ~XhrResponseData();
-
-  // Destroy the data_ and reset the size and capacity to 0.
-  void Clear();
-  // Allocate storage for |new_capacity_bytes| of data.
-  void Reserve(size_t new_capacity_bytes);
-  // Append |source_data|, |size_bytes| in length, to the data array.
-  void Append(const uint8* source_data, size_t size_bytes);
-
-  const uint8* data() const;
-  uint8* data();
-
-  const std::string& string() const { return data_; }
-
-  size_t size() const { return data_.size(); }
-  size_t capacity() const { return data_.capacity(); }
-
- private:
-  void IncreaseMemoryUsage();
-  void DecreaseMemoryUsage();
-
-  std::string data_;
-};
-
-}  // namespace xhr
-}  // namespace cobalt
-
-#endif  // COBALT_XHR_XHR_RESPONSE_DATA_H_
diff --git a/src/cobalt/xhr/xhr_response_data_test.cc b/src/cobalt/xhr/xhr_response_data_test.cc
deleted file mode 100644
index f63d70e..0000000
--- a/src/cobalt/xhr/xhr_response_data_test.cc
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2015 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/xhr/xhr_response_data.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace cobalt {
-namespace xhr {
-
-namespace {
-
-TEST(XhrResponseData, InitialState) {
-  XhrResponseData empty;
-  EXPECT_EQ(0u, empty.size());
-  EXPECT_TRUE(empty.data() != NULL);
-}
-
-TEST(XhrResponseData, Append) {
-  XhrResponseData data;
-  uint8 raw_data[64];
-  for (int i = 0; i < 64; ++i) {
-    raw_data[i] = static_cast<uint8>(i);
-  }
-  data.Append(raw_data, 64);
-  EXPECT_EQ(64u, data.size());
-  EXPECT_LE(64u, data.capacity());
-
-  for (int i = 0; i < 64; ++i) {
-    EXPECT_EQ(raw_data[i], data.data()[i]);
-  }
-}
-
-TEST(XhrResponseData, Reserve) {
-  XhrResponseData data;
-  data.Reserve(1);
-  EXPECT_LE(1u, data.capacity());
-  EXPECT_EQ(0u, data.size());
-  EXPECT_TRUE(data.data() != NULL);
-}
-
-}  // namespace
-}  // namespace xhr
-}  // namespace cobalt
diff --git a/src/glimp/gles/context.cc b/src/glimp/gles/context.cc
index aa11665..21db890 100644
--- a/src/glimp/gles/context.cc
+++ b/src/glimp/gles/context.cc
@@ -1495,8 +1495,7 @@
 
   // The incoming pixel data should be aligned as the client has specified
   // that it will be.
-  SB_DCHECK(nb::IsAligned(nb::AsInteger(pixels),
-                          static_cast<uintptr_t>(unpack_alignment_)));
+  SB_DCHECK(nb::IsAligned(pixels, static_cast<size_t>(unpack_alignment_)));
 
   // Determine pitch taking into account glPixelStorei() settings.
   int pitch_in_bytes = GetPitchForTextureData(width, pixel_format);
@@ -1578,8 +1577,7 @@
 
   // The incoming pixel data should be aligned as the client has specified
   // that it will be.
-  SB_DCHECK(nb::IsAligned(nb::AsInteger(pixels),
-                          static_cast<uintptr_t>(unpack_alignment_)));
+  SB_DCHECK(nb::IsAligned(pixels, static_cast<size_t>(unpack_alignment_)));
 
   // Determine pitch taking into account glPixelStorei() settings.
   int pitch_in_bytes = GetPitchForTextureData(width, pixel_format);
diff --git a/src/nb/allocator.cc b/src/nb/allocator.cc
new file mode 100644
index 0000000..668c977
--- /dev/null
+++ b/src/nb/allocator.cc
@@ -0,0 +1,21 @@
+// Copyright 2020 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "nb/allocator.h"
+
+namespace nb {
+
+const size_t Allocator::kMinAlignment = 16;
+
+}  // namespace nb
diff --git a/src/nb/allocator.h b/src/nb/allocator.h
index 07ee8e0..028e25d 100644
--- a/src/nb/allocator.h
+++ b/src/nb/allocator.h
@@ -17,9 +17,7 @@
 #ifndef NB_ALLOCATOR_H_
 #define NB_ALLOCATOR_H_
 
-// TODO: Include "starboard/types.h" once legacy platforms are ported to
-// starboard.  Currently including <vector> is used as a platform independent
-// way to introduce std::size_t.
+#include <cstddef>
 #include <vector>
 
 namespace nb {
@@ -31,6 +29,11 @@
 // through derived classes.
 class Allocator {
  public:
+  // Using a minimum value for alignment keeps things rounded and aligned
+  // and help us avoid creating tiny and/or badly misaligned free blocks.  Also
+  // ensures even for a 0-byte request will get a unique block.
+  static const size_t kMinAlignment;
+
   virtual ~Allocator() {}
 
   // Allocates a range of memory of the given size, without any alignment
diff --git a/src/nb/analytics/memory_tracker.h b/src/nb/analytics/memory_tracker.h
index 182488a..cf6e0eb 100644
--- a/src/nb/analytics/memory_tracker.h
+++ b/src/nb/analytics/memory_tracker.h
@@ -32,8 +32,11 @@
 class AllocationGroup;
 
 struct MemoryStats {
-  MemoryStats() : total_cpu_memory(0), used_cpu_memory(0),
-                  total_gpu_memory(0), used_gpu_memory(0) {}
+  MemoryStats()
+      : total_cpu_memory(0),
+        used_cpu_memory(0),
+        total_gpu_memory(0),
+        used_gpu_memory(0) {}
   int64_t total_cpu_memory;
   int64_t used_cpu_memory;
   int64_t total_gpu_memory;
@@ -143,7 +146,8 @@
  protected:
   virtual ~MemoryTracker() {}
 
-  SB_DISALLOW_COPY_AND_ASSIGN(MemoryTracker);
+  MemoryTracker(const MemoryTracker&) = delete;
+  void operator=(const MemoryTracker&) = delete;
 };
 
 // A visitor class which is useful for inspecting data.
diff --git a/src/nb/analytics/memory_tracker_helpers.h b/src/nb/analytics/memory_tracker_helpers.h
index 448b9ca..124bfaa 100644
--- a/src/nb/analytics/memory_tracker_helpers.h
+++ b/src/nb/analytics/memory_tracker_helpers.h
@@ -42,9 +42,7 @@
  public:
   NoReportAllocator() {}
   NoReportAllocator(const NoReportAllocator&) {}
-  static void* Allocate(size_t n) {
-    return SbMemoryAllocateNoReport(n);
-  }
+  static void* Allocate(size_t n) { return SbMemoryAllocateNoReport(n); }
   // Second argument can be used for accounting, but is otherwise optional.
   static void Deallocate(void* ptr, size_t /* not used*/) {
     SbMemoryDeallocateNoReport(ptr);
@@ -73,7 +71,8 @@
   starboard::atomic_int64_t allocation_bytes_;
   starboard::atomic_int32_t num_allocations_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(AllocationGroup);
+  AllocationGroup(const AllocationGroup&) = delete;
+  void operator=(const AllocationGroup&) = delete;
 };
 
 // A self locking data structure that maps strings -> AllocationGroups. This is
@@ -96,14 +95,16 @@
       std::string,
       AllocationGroup*,
       std::less<std::string>,
-      nb::StdAllocator<
-          std::pair<const std::string, AllocationGroup*>,
-          NoReportAllocator> > Map;
+      nb::StdAllocator<std::pair<const std::string, AllocationGroup*>,
+                       NoReportAllocator> >
+      Map;
   Map group_map_;
   AllocationGroup* unaccounted_group_;
   mutable starboard::Mutex mutex_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(AtomicStringAllocationGroupMap);
+  AtomicStringAllocationGroupMap(const AtomicStringAllocationGroupMap&) =
+      delete;
+  void operator=(const AtomicStringAllocationGroupMap&) = delete;
 };
 
 class AllocationGroupStack {
@@ -127,7 +128,9 @@
   const AllocationGroupPtrVec& data() const { return alloc_group_stack_; }
 
  private:
-  SB_DISALLOW_COPY_AND_ASSIGN(AllocationGroupStack);
+  AllocationGroupStack(const AllocationGroupStack&) = delete;
+  void operator=(const AllocationGroupStack&) = delete;
+
   AllocationGroupPtrVec alloc_group_stack_, debug_stack_;
 };
 
@@ -161,14 +164,15 @@
       const void*,
       AllocationRecord,
       std::less<const void*>,  // required, when specifying allocator.
-      nb::StdAllocator<
-          std::pair<const void* const, AllocationRecord>,
-          NoReportAllocator> > PointerMap;
+      nb::StdAllocator<std::pair<const void* const, AllocationRecord>,
+                       NoReportAllocator> >
+      PointerMap;
 
   PointerMap pointer_map_;
   mutable starboard::Mutex mutex_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(AtomicAllocationMap);
+  AtomicAllocationMap(const AtomicAllocationMap&) = delete;
+  void operator=(const AtomicAllocationMap&) = delete;
 };
 
 // A per-pointer map of allocations to AllocRecords. This is a hybrid data
@@ -203,7 +207,9 @@
   const AtomicAllocationMap& GetMapForPointer(const void* ptr) const;
 
  private:
-  SB_DISALLOW_COPY_AND_ASSIGN(ConcurrentAllocationMap);
+  ConcurrentAllocationMap(const ConcurrentAllocationMap&) = delete;
+  void operator=(const ConcurrentAllocationMap&) = delete;
+
   // Takes a pointer and generates a hash.
   static uint32_t hash_ptr(const void* ptr);
 
diff --git a/src/nb/bidirectional_fit_reuse_allocator_test.cc b/src/nb/bidirectional_fit_reuse_allocator_test.cc
index 6c08b9b..21d841a 100644
--- a/src/nb/bidirectional_fit_reuse_allocator_test.cc
+++ b/src/nb/bidirectional_fit_reuse_allocator_test.cc
@@ -17,18 +17,15 @@
 #include "nb/bidirectional_fit_reuse_allocator.h"
 
 #include "nb/fixed_no_free_allocator.h"
+#include "nb/pointer_arithmetic.h"
 #include "nb/scoped_ptr.h"
+#include "nb/starboard_aligned_memory_deleter.h"
 #include "starboard/configuration.h"
 #include "starboard/types.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
 
-inline bool IsAligned(void* ptr, std::size_t boundary) {
-  uintptr_t ptr_as_int = reinterpret_cast<uintptr_t>(ptr);
-  return ptr_as_int % boundary == 0;
-}
-
 class BidirectionalFitReuseAllocatorTest : public ::testing::Test {
  public:
   static const int kBufferSize = 1 * 1024 * 1024;
@@ -39,18 +36,19 @@
   void ResetAllocator(std::size_t initial_capacity = 0,
                       std::size_t small_allocation_threshold = 0,
                       std::size_t allocation_increment = 0) {
-    nb::scoped_array<uint8_t> buffer(new uint8_t[kBufferSize]);
+    buffer_.reset(static_cast<uint8_t*>(
+        SbMemoryAllocateAligned(nb::Allocator::kMinAlignment, kBufferSize)));
+
     nb::scoped_ptr<nb::FixedNoFreeAllocator> fallback_allocator(
-        new nb::FixedNoFreeAllocator(buffer.get(), kBufferSize));
+        new nb::FixedNoFreeAllocator(buffer_.get(), kBufferSize));
     allocator_.reset(new nb::BidirectionalFitReuseAllocator(
         fallback_allocator.get(), initial_capacity, small_allocation_threshold,
         allocation_increment));
 
     fallback_allocator_.swap(fallback_allocator);
-    buffer_.swap(buffer);
   }
 
-  nb::scoped_array<uint8_t> buffer_;
+  std::unique_ptr<uint8_t, nb::AlignedMemoryDeleter> buffer_;
   nb::scoped_ptr<nb::FixedNoFreeAllocator> fallback_allocator_;
   nb::scoped_ptr<nb::BidirectionalFitReuseAllocator> allocator_;
 };
@@ -64,7 +62,7 @@
     for (int j = 0; j < SB_ARRAY_SIZE(kBlockSizes); ++j) {
       void* p = allocator_->Allocate(kBlockSizes[j], kAlignments[i]);
       EXPECT_TRUE(p != NULL);
-      EXPECT_EQ(IsAligned(p, kAlignments[i]), true);
+      EXPECT_EQ(nb::IsAligned(p, kAlignments[i]), true);
       allocator_->Free(p);
     }
   }
diff --git a/src/nb/concurrent_map.h b/src/nb/concurrent_map.h
index 39e2b4e..c066872 100644
--- a/src/nb/concurrent_map.h
+++ b/src/nb/concurrent_map.h
@@ -299,7 +299,8 @@
     typename InnerMap::iterator iterator_;
     bool iterator_valid_;
 
-    SB_DISALLOW_COPY_AND_ASSIGN(EntryHandle);
+    EntryHandle(const EntryHandle&) = delete;
+    void operator=(const EntryHandle&) = delete;
   };
 
   class ConstEntryHandle {
@@ -349,7 +350,8 @@
     typename InnerMap::const_iterator iterator_;
     bool iterator_valid_;
 
-    SB_DISALLOW_COPY_AND_ASSIGN(ConstEntryHandle);
+    ConstEntryHandle(const ConstEntryHandle&) = delete;
+    void operator=(const ConstEntryHandle&) = delete;
   };
 
   struct Bucket {
diff --git a/src/nb/first_fit_reuse_allocator_test.cc b/src/nb/first_fit_reuse_allocator_test.cc
index 2e51bd2..dd70c6d 100644
--- a/src/nb/first_fit_reuse_allocator_test.cc
+++ b/src/nb/first_fit_reuse_allocator_test.cc
@@ -17,18 +17,15 @@
 #include "nb/first_fit_reuse_allocator.h"
 
 #include "nb/fixed_no_free_allocator.h"
+#include "nb/pointer_arithmetic.h"
 #include "nb/scoped_ptr.h"
+#include "nb/starboard_aligned_memory_deleter.h"
 #include "starboard/configuration.h"
 #include "starboard/types.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
 
-inline bool IsAligned(void* ptr, std::size_t boundary) {
-  uintptr_t ptr_as_int = reinterpret_cast<uintptr_t>(ptr);
-  return ptr_as_int % boundary == 0;
-}
-
 class FirstFitReuseAllocatorTest : public ::testing::Test {
  public:
   static const int kBufferSize = 1 * 1024 * 1024;
@@ -38,17 +35,17 @@
  protected:
   void ResetAllocator(std::size_t initial_capacity = 0,
                       std::size_t allocation_increment = 0) {
-    nb::scoped_array<uint8_t> buffer(new uint8_t[kBufferSize]);
+    buffer_.reset(static_cast<uint8_t*>(
+        SbMemoryAllocateAligned(nb::Allocator::kMinAlignment, kBufferSize)));
     nb::scoped_ptr<nb::FixedNoFreeAllocator> fallback_allocator(
-        new nb::FixedNoFreeAllocator(buffer.get(), kBufferSize));
+        new nb::FixedNoFreeAllocator(buffer_.get(), kBufferSize));
     allocator_.reset(new nb::FirstFitReuseAllocator(
         fallback_allocator.get(), initial_capacity, allocation_increment));
 
     fallback_allocator_.swap(fallback_allocator);
-    buffer_.swap(buffer);
   }
 
-  nb::scoped_array<uint8_t> buffer_;
+  std::unique_ptr<uint8_t, nb::AlignedMemoryDeleter> buffer_;
   nb::scoped_ptr<nb::FixedNoFreeAllocator> fallback_allocator_;
   nb::scoped_ptr<nb::FirstFitReuseAllocator> allocator_;
 };
@@ -62,7 +59,7 @@
     for (int j = 0; j < SB_ARRAY_SIZE(kBlockSizes); ++j) {
       void* p = allocator_->Allocate(kBlockSizes[j], kAlignments[i]);
       EXPECT_TRUE(p != NULL);
-      EXPECT_EQ(IsAligned(p, kAlignments[i]), true);
+      EXPECT_EQ(nb::IsAligned(p, kAlignments[i]), true);
       allocator_->Free(p);
     }
   }
diff --git a/src/nb/fixed_no_free_allocator.cc b/src/nb/fixed_no_free_allocator.cc
index f4f8e1c..c7fa52a 100644
--- a/src/nb/fixed_no_free_allocator.cc
+++ b/src/nb/fixed_no_free_allocator.cc
@@ -25,6 +25,7 @@
                                            std::size_t memory_size)
     : memory_start_(memory_start),
       memory_end_(AsPointer(AsInteger(memory_start) + memory_size)) {
+  SB_CHECK(IsAligned(memory_start, kMinAlignment));
   next_memory_ = memory_start_;
 }
 
diff --git a/src/nb/fixed_no_free_allocator.h b/src/nb/fixed_no_free_allocator.h
index 31bfc60..b2c53f7 100644
--- a/src/nb/fixed_no_free_allocator.h
+++ b/src/nb/fixed_no_free_allocator.h
@@ -36,6 +36,7 @@
 // memory and we would like to wrap it in an allocator.
 class FixedNoFreeAllocator : public Allocator {
  public:
+  // Requires aligned memory to at least |nb::kMinAlignment|.
   FixedNoFreeAllocator(void* memory_start, std::size_t memory_size);
   void* Allocate(std::size_t size) { return Allocate(&size, 1, true); }
 
diff --git a/src/nb/fixed_no_free_allocator_test.cc b/src/nb/fixed_no_free_allocator_test.cc
index 89b4545..5789fb9 100644
--- a/src/nb/fixed_no_free_allocator_test.cc
+++ b/src/nb/fixed_no_free_allocator_test.cc
@@ -17,6 +17,7 @@
 #include "nb/fixed_no_free_allocator.h"
 
 #include "nb/pointer_arithmetic.h"
+#include "nb/starboard_aligned_memory_deleter.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 class FixedNoFreeAllocatorTest : public ::testing::Test {
@@ -29,30 +30,32 @@
   static const std::size_t kMaxAllocations = 64;
   static const std::size_t kBufferSize = kAllocationSize * kMaxAllocations;
 
-  char buffer_[kBufferSize];
+  std::unique_ptr<uint8_t, nb::AlignedMemoryDeleter> buffer_;
   nb::FixedNoFreeAllocator allocator_;
 };
 
 FixedNoFreeAllocatorTest::FixedNoFreeAllocatorTest()
-    : allocator_(buffer_, kBufferSize) {}
+    : buffer_(static_cast<uint8_t*>(
+          SbMemoryAllocateAligned(nb::Allocator::kMinAlignment, kBufferSize))),
+      allocator_(buffer_.get(), kBufferSize) {}
 
 TEST_F(FixedNoFreeAllocatorTest, CanDoSimpleAllocations) {
   void* allocation = allocator_.Allocate(kAllocationSize);
 
-  EXPECT_GE(allocation, buffer_);
-  EXPECT_LE(
-      reinterpret_cast<uintptr_t>(allocation),
-      reinterpret_cast<uintptr_t>(buffer_) + kBufferSize - kAllocationSize);
+  EXPECT_GE(allocation, buffer_.get());
+  EXPECT_LE(reinterpret_cast<uintptr_t>(allocation),
+            reinterpret_cast<uintptr_t>(buffer_.get()) + kBufferSize -
+                kAllocationSize);
 }
 
 TEST_F(FixedNoFreeAllocatorTest, CanDoMultipleAllocationsProperly) {
   void* buffers[kMaxAllocations];
   for (int i = 0; i < kMaxAllocations; ++i) {
     buffers[i] = allocator_.Allocate(kAllocationSize);
-    EXPECT_GE(buffers[i], buffer_);
-    EXPECT_LE(
-        reinterpret_cast<uintptr_t>(buffers[i]),
-        reinterpret_cast<uintptr_t>(buffer_) + kBufferSize - kAllocationSize);
+    EXPECT_GE(buffers[i], buffer_.get());
+    EXPECT_LE(reinterpret_cast<uintptr_t>(buffers[i]),
+              reinterpret_cast<uintptr_t>(buffer_.get()) + kBufferSize -
+                  kAllocationSize);
 
     // Make sure this allocation doesn't overlap with any previous ones.
     for (int j = 0; j < i; ++j) {
@@ -72,10 +75,10 @@
   for (int i = 0; i < kMaxAllocations; ++i) {
     void* current_allocation = allocator_.Allocate(kAllocationSize);
 
-    EXPECT_GE(current_allocation, buffer_);
-    EXPECT_LE(
-        reinterpret_cast<uintptr_t>(current_allocation),
-        reinterpret_cast<uintptr_t>(buffer_) + kBufferSize - kAllocationSize);
+    EXPECT_GE(current_allocation, buffer_.get());
+    EXPECT_LE(reinterpret_cast<uintptr_t>(current_allocation),
+              reinterpret_cast<uintptr_t>(buffer_.get()) + kBufferSize -
+                  kAllocationSize);
 
     allocator_.Free(current_allocation);
   }
@@ -85,10 +88,10 @@
   for (int i = 0; i < kMaxAllocations; ++i) {
     void* current_allocation = allocator_.Allocate(kAllocationSize);
 
-    EXPECT_GE(current_allocation, buffer_);
-    EXPECT_LE(
-        reinterpret_cast<uintptr_t>(current_allocation),
-        reinterpret_cast<uintptr_t>(buffer_) + kBufferSize - kAllocationSize);
+    EXPECT_GE(current_allocation, buffer_.get());
+    EXPECT_LE(reinterpret_cast<uintptr_t>(current_allocation),
+              reinterpret_cast<uintptr_t>(buffer_.get()) + kBufferSize -
+                  kAllocationSize);
 
     allocator_.Free(current_allocation);
   }
@@ -109,10 +112,10 @@
     EXPECT_EQ(0, reinterpret_cast<uintptr_t>(current_allocation) %
                      kAllocationAlignment);
 
-    EXPECT_GE(current_allocation, buffer_);
-    EXPECT_LE(
-        reinterpret_cast<uintptr_t>(current_allocation),
-        reinterpret_cast<uintptr_t>(buffer_) + kBufferSize - kAllocationSize);
+    EXPECT_GE(current_allocation, buffer_.get());
+    EXPECT_LE(reinterpret_cast<uintptr_t>(current_allocation),
+              reinterpret_cast<uintptr_t>(buffer_.get()) + kBufferSize -
+                  kAllocationSize);
 
     allocator_.Free(current_allocation);
   }
diff --git a/src/nb/nb.gyp b/src/nb/nb.gyp
index ce9e71d..6078f07 100644
--- a/src/nb/nb.gyp
+++ b/src/nb/nb.gyp
@@ -36,6 +36,7 @@
       'conditions': [
         ['OS=="starboard"', {
           'sources': [
+            'allocator.cc',
             'allocator.h',
             'analytics/memory_tracker.cc',
             'analytics/memory_tracker.h',
@@ -75,6 +76,7 @@
             'simple_thread.h',
             'simple_profiler.cc',
             'simple_profiler.h',
+            'starboard_aligned_memory_deleter.h',
             'std_allocator.h',
             'string_interner.cc',
             'string_interner.h',
diff --git a/src/nb/pointer_arithmetic.h b/src/nb/pointer_arithmetic.h
index cb1539a..d34cba0 100644
--- a/src/nb/pointer_arithmetic.h
+++ b/src/nb/pointer_arithmetic.h
@@ -61,8 +61,8 @@
 // Helper method for subclasses to determine if a given address or value
 // is aligned or not.
 template <typename T>
-static bool IsAligned(T value, T alignment) {
-  return value % alignment == 0;
+static bool IsAligned(T value, size_t alignment) {
+  return AsInteger(value) % alignment == 0;
 }
 
 }  // namespace nb
diff --git a/src/nb/reuse_allocator_base.cc b/src/nb/reuse_allocator_base.cc
index 95189ac..2d6943a 100644
--- a/src/nb/reuse_allocator_base.cc
+++ b/src/nb/reuse_allocator_base.cc
@@ -29,10 +29,6 @@
 // Minimum block size to avoid extremely small blocks inside the block list and
 // to ensure that a zero sized allocation will return a non-zero sized block.
 const std::size_t kMinBlockSizeBytes = 16;
-// Using a minimum value for size and alignment keeps things rounded and aligned
-// and help us avoid creating tiny and/or badly misaligned free blocks.  Also
-// ensures even for a 0-byte request will get a unique block.
-const std::size_t kMinAlignment = 16;
 // The max lines of allocation to print inside PrintAllocations().  Set to 0 to
 // print all allocations.
 const int kMaxAllocationLinesToPrint = 0;
diff --git a/src/nb/simple_thread.h b/src/nb/simple_thread.h
index b01a58c..1f9132d 100644
--- a/src/nb/simple_thread.h
+++ b/src/nb/simple_thread.h
@@ -56,7 +56,8 @@
   SbThread thread_;

   starboard::atomic_bool join_called_;

 

-  SB_DISALLOW_COPY_AND_ASSIGN(SimpleThread);

+  SimpleThread(const SimpleThread&) = delete;

+  void operator=(const SimpleThread&) = delete;

 };

 

 }  // namespace nb

diff --git a/src/nb/starboard_aligned_memory_deleter.h b/src/nb/starboard_aligned_memory_deleter.h
new file mode 100644
index 0000000..4f307c1
--- /dev/null
+++ b/src/nb/starboard_aligned_memory_deleter.h
@@ -0,0 +1,28 @@
+// Copyright 2020 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef NB_STARBOARD_ALIGNED_MEMORY_DELETER_H_
+#define NB_STARBOARD_ALIGNED_MEMORY_DELETER_H_
+
+#include "starboard/memory.h"
+
+namespace nb {
+
+struct AlignedMemoryDeleter {
+  void operator()(uint8_t* p) { SbMemoryDeallocateAligned(p); }
+};
+
+}  // namespace nb
+
+#endif  // NB_STARBOARD_ALIGNED_MEMORY_DELETER_H_
diff --git a/src/nb/string_interner.h b/src/nb/string_interner.h
index e739b69..f842449 100644
--- a/src/nb/string_interner.h
+++ b/src/nb/string_interner.h
@@ -65,13 +65,15 @@
   mutable starboard::Mutex mutex_;
   mutable std::string scratch_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(StringInterner);
+  StringInterner(const StringInterner&) = delete;
+  void operator=(const StringInterner&) = delete;
 };
 
 class ConcurrentStringInterner {
  public:
   explicit ConcurrentStringInterner(size_t table_size = 32);
-  ~ConcurrentStringInterner();  // All outstanding const std::string* are invalidated.
+  ~ConcurrentStringInterner();  // All outstanding const std::string* are
+                                // invalidated.
 
   // Returns an equivalent string to the input. If the input is missing from
   // the data store then a copy-by-value is made.
@@ -90,7 +92,8 @@
   const StringInterner& GetBucket(const char* string, size_t n) const;
   std::vector<StringInterner*> string_interner_table_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(ConcurrentStringInterner);
+  ConcurrentStringInterner(const ConcurrentStringInterner&) = delete;
+  void operator=(const ConcurrentStringInterner&) = delete;
 };
 
 }  // namespace nb
diff --git a/src/nb/test_thread.h b/src/nb/test_thread.h
index 0ea5768..4f2da5d 100644
--- a/src/nb/test_thread.h
+++ b/src/nb/test_thread.h
@@ -35,14 +35,11 @@
   void Start() {
     SbThreadEntryPoint entry_point = ThreadEntryPoint;
 
-    thread_ = SbThreadCreate(
-        0,                     // default stack_size.
-        kSbThreadNoPriority,   // default priority.
-        kSbThreadNoAffinity,   // default affinity.
-        true,                  // joinable.
-        "TestThread",
-        entry_point,
-        this);
+    thread_ = SbThreadCreate(0,                    // default stack_size.
+                             kSbThreadNoPriority,  // default priority.
+                             kSbThreadNoAffinity,  // default affinity.
+                             true,                 // joinable.
+                             "TestThread", entry_point, this);
 
     if (kSbThreadInvalid == thread_) {
       ADD_FAILURE_AT(__FILE__, __LINE__) << "Invalid thread.";
@@ -65,7 +62,8 @@
 
   SbThread thread_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(TestThread);
+  TestThread(const TestThread&) = delete;
+  void operator=(const TestThread&) = delete;
 };
 
 }  // namespace nb.
diff --git a/src/nb/thread_local_boolean.h b/src/nb/thread_local_boolean.h
index d91a744..c27baf6 100644
--- a/src/nb/thread_local_boolean.h
+++ b/src/nb/thread_local_boolean.h
@@ -44,7 +44,8 @@
   ThreadLocalPointer<void> tlp_;
   const bool default_value_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(ThreadLocalBoolean);
+  ThreadLocalBoolean(const ThreadLocalBoolean&) = delete;
+  void operator=(const ThreadLocalBoolean&) = delete;
 };
 
 }  // namespace nb.
diff --git a/src/nb/thread_local_object.h b/src/nb/thread_local_object.h
index 6299f51..741227f 100644
--- a/src/nb/thread_local_object.h
+++ b/src/nb/thread_local_object.h
@@ -77,11 +77,11 @@
     CheckCurrentThreadAllowedToDestruct();
     if (SB_DLOG_IS_ON(FATAL)) {
       SB_DCHECK(entry_set_.size() < 2)
-        << "Logic error: Some threads may still be accessing the objects that "
-        << "are about to be destroyed. Only one object is expected and that "
-        << "should be for the main thread. The caller should ensure that "
-        << "other threads that access this object are externally "
-        << "synchronized.";
+          << "Logic error: Some threads may still be accessing the objects "
+          << "that are about to be destroyed. Only one object is expected "
+          << "and that should be for the main thread. The caller should "
+          << "ensure that other threads that access this object are"
+          << " externally synchronized.";
     }
     // No locking is done because the entries should not be accessed by
     // different threads while this object is shutting down. If access is
@@ -101,20 +101,20 @@
   // Warns if there is a misuse of this object.
   void CheckCurrentThreadAllowedToDestruct() const {
     if (kSbThreadInvalidId == constructing_thread_id_) {
-      return;   // EnableDestructionByAnyThread() called.
+      return;  // EnableDestructionByAnyThread() called.
     }
     const SbThreadId curr_thread_id = SbThreadGetId();
     if (curr_thread_id == constructing_thread_id_) {
-      return;   // Same thread that constructed this.
+      return;  // Same thread that constructed this.
     }
 
     if (SB_DLOG_IS_ON(FATAL)) {
-      SB_DCHECK(false)
-          << "ThreadLocalObject<T> was created in thread "
-          << constructing_thread_id_ << "\nbut was destroyed by "
-          << curr_thread_id << ". If this is intentional then call "
-          << "EnableDestructionByAnyThread() to silence this "
-          << "warning.";
+      SB_DCHECK(false) << "ThreadLocalObject<T> was created in thread "
+                       << constructing_thread_id_ << "\nbut was destroyed by "
+                       << curr_thread_id
+                       << ". If this is intentional then call "
+                       << "EnableDestructionByAnyThread() to silence this "
+                       << "warning.";
     }
   }
 
@@ -158,7 +158,9 @@
   // Returns the pointer if it exists in the current thread, otherwise NULL.
   Type* GetIfExists() const {
     Entry* entry = GetEntryIfExists();
-    if (!entry) { return NULL; }
+    if (!entry) {
+      return NULL;
+    }
     return entry->ptr_;
   }
 
@@ -178,8 +180,7 @@
 
  private:
   struct Entry {
-    Entry(ThreadLocalObject* own, Type* ptr) : owner_(own), ptr_(ptr) {
-    }
+    Entry(ThreadLocalObject* own, Type* ptr) : owner_(own), ptr_(ptr) {}
     ~Entry() {
       ptr_ = NULL;
       owner_ = NULL;
@@ -228,7 +229,8 @@
   // thread that destroyed this object.
   SbThreadId constructing_thread_id_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(ThreadLocalObject<Type>);
+  ThreadLocalObject<Type>(const ThreadLocalObject<Type>&) = delete;
+  void operator=(const ThreadLocalObject<Type>&) = delete;
 };
 
 }  // namespace nb
diff --git a/src/nb/thread_local_pointer.h b/src/nb/thread_local_pointer.h
index d21f81f..5d0e7f3 100644
--- a/src/nb/thread_local_pointer.h
+++ b/src/nb/thread_local_pointer.h
@@ -46,7 +46,8 @@
 
  private:
   SbThreadLocalKey slot_;
-  SB_DISALLOW_COPY_AND_ASSIGN(ThreadLocalPointer<Type>);
+  ThreadLocalPointer<Type>(const ThreadLocalPointer<Type>&) = delete;
+  void operator=(const ThreadLocalPointer<Type>&) = delete;
 };
 
 }  // namespace nb.
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
index ac9be3c..89b4be8 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
@@ -27,6 +27,7 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewParent;
 import android.widget.FrameLayout;
+import dev.cobalt.media.MediaCodecUtil;
 import dev.cobalt.media.VideoSurfaceView;
 import dev.cobalt.util.Log;
 import dev.cobalt.util.UsedByNative;
@@ -45,7 +46,9 @@
   private static final java.lang.String META_DATA_APP_URL = "cobalt.APP_URL";
 
   private static final String SPLASH_URL_ARG = "--fallback_splash_screen_url=";
+  private static final String SPLASH_TOPICS_ARG = "--fallback_splash_screen_topics=";
   private static final java.lang.String META_DATA_SPLASH_URL = "cobalt.SPLASH_URL";
+  private static final java.lang.String META_DATA_SPLASH_TOPICS = "cobalt.SPLASH_TOPIC";
 
   private static final String FORCE_MIGRATION_FOR_STORAGE_PARTITIONING =
       "--force_migration_for_storage_partitioning";
@@ -107,6 +110,9 @@
 
   @Override
   protected void onStart() {
+    if (!isReleaseBuild()) {
+      MediaCodecUtil.dumpAllDecoders();
+    }
     if (forceCreateNewVideoSurfaceView) {
       Log.w(TAG, "Force to create a new video surface.");
       createNewSurfaceView();
@@ -174,7 +180,9 @@
     boolean hasUrlArg = hasArg(args, URL_ARG);
     // If the splash screen url arg isn't specified, get it from AndroidManifest.xml.
     boolean hasSplashUrlArg = hasArg(args, SPLASH_URL_ARG);
-    if (!hasUrlArg || !hasSplashUrlArg) {
+    // If the splash screen topics arg isn't specified, get it from AndroidManifest.xml.
+    boolean hasSplashTopicsArg = hasArg(args, SPLASH_TOPICS_ARG);
+    if (!hasUrlArg || !hasSplashUrlArg || !hasSplashTopicsArg) {
       try {
         ActivityInfo ai =
             getPackageManager()
@@ -192,6 +200,12 @@
               args.add(SPLASH_URL_ARG + splashUrl);
             }
           }
+          if (!hasSplashTopicsArg) {
+            String splashTopics = ai.metaData.getString(META_DATA_SPLASH_TOPICS);
+            if (splashTopics != null) {
+              args.add(SPLASH_TOPICS_ARG + splashTopics);
+            }
+          }
           if (ai.metaData.getBoolean(META_FORCE_MIGRATION_FOR_STORAGE_PARTITIONING)) {
             args.add(FORCE_MIGRATION_FOR_STORAGE_PARTITIONING);
           }
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java
index 2fb2c95..b6de9b0 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java
@@ -41,6 +41,10 @@
 public void onCreate() {
   super.onCreate();
   Log.i(TAG, "Creating a Media playback foreground service.");
+  if (getStarboardBridge() == null) {
+    Log.e(TAG, "StarboardBridge already destroyed.");
+    return;
+  }
   getStarboardBridge().onServiceStart(this);
   context = getApplicationContext();
 }
@@ -61,10 +65,14 @@
 
 @Override
 public void onDestroy() {
+  if (getStarboardBridge() == null) {
+    Log.e(TAG, "StarboardBridge already destroyed.");
+    return;
+  }
   getStarboardBridge().onServiceDestroy(this);
   context = null;
   super.onDestroy();
-  Log.i(TAG, "Destorying the Media playback service.");
+  Log.i(TAG, "Destroying the Media playback service.");
 }
 
 public void startService() {
@@ -139,6 +147,10 @@
 
 @UsedByNative
 protected StarboardBridge getStarboardBridge() {
+  if (getApplication() == null) {
+    Log.e(TAG, "Application already destroyed.");
+    return null;
+  }
   return ((StarboardBridge.HostApplication) getApplication()).getStarboardBridge();
 }
 
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/VoiceRecognizer.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/VoiceRecognizer.java
index 06cc02f..799aef9 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/VoiceRecognizer.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/VoiceRecognizer.java
@@ -122,7 +122,8 @@
     this.nativeSpeechRecognizerImpl = nativeSpeechRecognizer;
 
     if (this.audioPermissionRequester.requestRecordAudioPermission(
-        this.nativeSpeechRecognizerImpl)) {
+        this.nativeSpeechRecognizerImpl) &&
+        SpeechRecognizer.isRecognitionAvailable(context)) {
       startRecognitionInternal();
     } else {
       mainHandler.post(
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 554d8d5..b80fa03 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
@@ -48,9 +48,18 @@
   @SuppressWarnings("unused")
   @UsedByNative
   AudioTrackBridge createAudioTrackBridge(
-      int sampleType, int sampleRate, int channelCount, int preferredBufferSizeInBytes) {
+      int sampleType,
+      int sampleRate,
+      int channelCount,
+      int preferredBufferSizeInBytes,
+      int tunnelModeAudioSessionId) {
     AudioTrackBridge audioTrackBridge =
-        new AudioTrackBridge(sampleType, sampleRate, channelCount, preferredBufferSizeInBytes);
+        new AudioTrackBridge(
+            sampleType,
+            sampleRate,
+            channelCount,
+            preferredBufferSizeInBytes,
+            tunnelModeAudioSessionId);
     if (!audioTrackBridge.isAudioTrackValid()) {
       Log.e(TAG, "AudioTrackBridge has invalid audio track");
       return null;
@@ -128,4 +137,29 @@
     }
     return AudioTrack.getMinBufferSize(sampleRate, channelConfig, sampleType);
   }
+
+  /** Generate audio session id used by tunneled playback. */
+  @SuppressWarnings("unused")
+  @UsedByNative
+  int generateTunnelModeAudioSessionId(int numberOfChannels) {
+    // Android 9.0 (Build.VERSION.SDK_INT >= 28) support v2 sync header that
+    // aligns sync header with audio frame size. V1 sync header has alignment
+    // issues for multi-channel audio.
+    if (Build.VERSION.SDK_INT < 28) {
+      // Currently we only support int16 under tunnel mode.
+      final int sampleSizeInBytes = 2;
+      final int frameSizeInBytes = sampleSizeInBytes * numberOfChannels;
+      if (AudioTrackBridge.AV_SYNC_HEADER_V1_SIZE % frameSizeInBytes != 0) {
+        Log.w(
+            TAG,
+            String.format(
+                "Disable tunnel mode due to sampleSizeInBytes (%d) * numberOfChannels (%d) isn't"
+                    + " aligned to AV_SYNC_HEADER_V1_SIZE (%d).",
+                sampleSizeInBytes, numberOfChannels, AudioTrackBridge.AV_SYNC_HEADER_V1_SIZE));
+        return -1;
+      }
+    }
+    AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+    return audioManager.generateAudioSessionId();
+  }
 }
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
index 419f5d8..b8d5a53 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
@@ -26,18 +26,45 @@
 import dev.cobalt.util.Log;
 import dev.cobalt.util.UsedByNative;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 
-// A wrapper of the android AudioTrack class.
-// Android AudioTrack would not start playing until the buffer is fully
-// filled once.
+/**
+ * A wrapper of the android AudioTrack class. Android AudioTrack would not start playing until the
+ * buffer is fully filled once.
+ */
 @UsedByNative
 public class AudioTrackBridge {
+  // Also used by AudioOutputManager.
+  static final int AV_SYNC_HEADER_V1_SIZE = 16;
+
   private AudioTrack audioTrack;
   private AudioTimestamp audioTimestamp = new AudioTimestamp();
   private long maxFramePositionSoFar = 0;
 
+  private final boolean tunnelModeEnabled;
+  // The following variables are used only when |tunnelModeEnabled| is true.
+  private ByteBuffer avSyncHeader;
+  private int avSyncPacketBytesRemaining;
+
+  private static int getBytesPerSample(int audioFormat) {
+    switch (audioFormat) {
+      case AudioFormat.ENCODING_PCM_16BIT:
+        return 2;
+      case AudioFormat.ENCODING_PCM_FLOAT:
+        return 4;
+      case AudioFormat.ENCODING_INVALID:
+      default:
+        throw new RuntimeException("Unsupport audio format " + audioFormat);
+    }
+  }
+
   public AudioTrackBridge(
-      int sampleType, int sampleRate, int channelCount, int preferredBufferSizeInBytes) {
+      int sampleType,
+      int sampleRate,
+      int channelCount,
+      int preferredBufferSizeInBytes,
+      int tunnelModeAudioSessionId) {
+    tunnelModeEnabled = tunnelModeAudioSessionId != -1;
     int channelConfig;
     switch (channelCount) {
       case 1:
@@ -53,11 +80,40 @@
         throw new RuntimeException("Unsupported channel count: " + channelCount);
     }
 
-    AudioAttributes attributes =
-        new AudioAttributes.Builder()
-            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
-            .setUsage(AudioAttributes.USAGE_MEDIA)
-            .build();
+    AudioAttributes attributes;
+    if (tunnelModeEnabled) {
+      // Android 9.0 (Build.VERSION.SDK_INT >= 28) support v2 sync header that aligns sync header
+      // with audio frame size. V1 sync header has alignment issues for multi-channel audio.
+      if (Build.VERSION.SDK_INT < 28) {
+        int frameSize = getBytesPerSample(sampleType) * channelCount;
+        // This shouldn't happen as it should have been checked in
+        // AudioOutputManager.generateTunnelModeAudioSessionId().
+        if (AV_SYNC_HEADER_V1_SIZE % frameSize != 0) {
+          audioTrack = null;
+          String errorMessage =
+              String.format(
+                  "Enable tunnel mode when frame size is unaligned, "
+                      + "sampleType: %d, channel: %d, sync header size: %d.",
+                  sampleType, channelCount, AV_SYNC_HEADER_V1_SIZE);
+          Log.e(TAG, errorMessage);
+          throw new RuntimeException(errorMessage);
+        }
+      }
+      attributes =
+          new AudioAttributes.Builder()
+              .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
+              .setFlags(AudioAttributes.FLAG_HW_AV_SYNC)
+              .setUsage(AudioAttributes.USAGE_MEDIA)
+              .build();
+    } else {
+      // TODO: Investigate if we can use |CONTENT_TYPE_MOVIE| for AudioTrack
+      //       used by video playback.
+      attributes =
+          new AudioAttributes.Builder()
+              .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+              .setUsage(AudioAttributes.USAGE_MEDIA)
+              .build();
+    }
     AudioFormat format =
         new AudioFormat.Builder()
             .setEncoding(sampleType)
@@ -66,6 +122,10 @@
             .build();
 
     int audioTrackBufferSize = preferredBufferSizeInBytes;
+    // TODO: Investigate if this implementation could be refined.
+    // It is not necessary to loop until 0 since there is new implementation based on
+    // AudioTrack.getMinBufferSize(). Especially for tunnel mode, it would fail if audio HAL does
+    // not support tunnel mode and then it is not helpful to retry.
     while (audioTrackBufferSize > 0) {
       try {
         audioTrack =
@@ -74,7 +134,9 @@
                 format,
                 audioTrackBufferSize,
                 AudioTrack.MODE_STREAM,
-                AudioManager.AUDIO_SESSION_ID_GENERATE);
+                tunnelModeEnabled
+                    ? tunnelModeAudioSessionId
+                    : AudioManager.AUDIO_SESSION_ID_GENERATE);
       } catch (Exception e) {
         audioTrack = null;
       }
@@ -104,6 +166,8 @@
       audioTrack.release();
     }
     audioTrack = null;
+    avSyncHeader = null;
+    avSyncPacketBytesRemaining = 0;
   }
 
   @SuppressWarnings("unused")
@@ -144,15 +208,22 @@
       return;
     }
     audioTrack.flush();
+    avSyncHeader = null;
+    avSyncPacketBytesRemaining = 0;
   }
 
   @SuppressWarnings("unused")
   @UsedByNative
-  private int write(byte[] audioData, int sizeInBytes) {
+  private int write(byte[] audioData, int sizeInBytes, long presentationTimeInMicroseconds) {
     if (audioTrack == null) {
       Log.e(TAG, "Unable to write with NULL audio track.");
       return 0;
     }
+
+    if (tunnelModeEnabled) {
+      return writeWithAvSync(audioData, sizeInBytes, presentationTimeInMicroseconds);
+    }
+
     if (Build.VERSION.SDK_INT >= 23) {
       return audioTrack.write(audioData, 0, sizeInBytes, AudioTrack.WRITE_NON_BLOCKING);
     } else {
@@ -161,6 +232,66 @@
     }
   }
 
+  private int writeWithAvSync(
+      byte[] audioData, int sizeInBytes, long presentationTimeInMicroseconds) {
+    if (audioTrack == null) {
+      throw new RuntimeException("writeWithAvSync() is called when audioTrack is null.");
+    }
+
+    if (!tunnelModeEnabled) {
+      throw new RuntimeException("writeWithAvSync() is called when tunnelModeEnabled is false.");
+    }
+
+    long presentationTimeInNanoseconds = presentationTimeInMicroseconds * 1000;
+
+    // Android support tunnel mode from 5.0 (API level 21), but the app has to manually write the
+    // sync header before API 23, where the write() function with presentation timestamp is
+    // introduced.
+    // Set the following constant to |false| to test manual sync header writing in API level 23 or
+    // later.  Note that the code to write sync header manually only supports v1 sync header.
+    final boolean useAutoSyncHeaderWrite = false;
+    if (useAutoSyncHeaderWrite && Build.VERSION.SDK_INT >= 23) {
+      ByteBuffer byteBuffer = ByteBuffer.wrap(audioData);
+      return audioTrack.write(
+          byteBuffer, sizeInBytes, AudioTrack.WRITE_NON_BLOCKING, presentationTimeInNanoseconds);
+    }
+
+    if (avSyncHeader == null) {
+      avSyncHeader = ByteBuffer.allocate(AV_SYNC_HEADER_V1_SIZE);
+      avSyncHeader.order(ByteOrder.BIG_ENDIAN);
+      avSyncHeader.putInt(0x55550001);
+    }
+
+    if (avSyncPacketBytesRemaining == 0) {
+      avSyncHeader.putInt(4, sizeInBytes);
+      avSyncHeader.putLong(8, presentationTimeInNanoseconds);
+      avSyncHeader.position(0);
+      avSyncPacketBytesRemaining = sizeInBytes;
+    }
+
+    if (avSyncHeader.remaining() > 0) {
+      int ret =
+          audioTrack.write(avSyncHeader, avSyncHeader.remaining(), AudioTrack.WRITE_NON_BLOCKING);
+      if (ret < 0) {
+        avSyncPacketBytesRemaining = 0;
+        return ret;
+      }
+      if (avSyncHeader.remaining() > 0) {
+        return 0;
+      }
+    }
+
+    int sizeToWrite = Math.min(avSyncPacketBytesRemaining, sizeInBytes);
+    ByteBuffer byteBuffer = ByteBuffer.wrap(audioData);
+    int ret = audioTrack.write(byteBuffer, sizeToWrite, AudioTrack.WRITE_NON_BLOCKING);
+    if (ret < 0) {
+      avSyncPacketBytesRemaining = 0;
+      return ret;
+    }
+    avSyncPacketBytesRemaining -= ret;
+    return ret;
+  }
+
   @SuppressWarnings("unused")
   @UsedByNative
   private int write(float[] audioData, int sizeInFloats) {
@@ -168,6 +299,9 @@
       Log.e(TAG, "Unable to write with NULL audio track.");
       return 0;
     }
+    if (tunnelModeEnabled) {
+      throw new RuntimeException("Float sample is not supported under tunnel mode.");
+    }
     return audioTrack.write(audioData, 0, sizeInFloats, AudioTrack.WRITE_NON_BLOCKING);
   }
 
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
index 71e6857..03fb7cc 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
@@ -682,52 +682,69 @@
    * Debug utility function that can be locally added to dump information about all decoders on a
    * particular system.
    */
-  @SuppressWarnings("unused")
-  private static void dumpAllDecoders() {
+  public static void dumpAllDecoders() {
+    String decoderDumpString = "";
     for (MediaCodecInfo info : new MediaCodecList(MediaCodecList.ALL_CODECS).getCodecInfos()) {
       if (info.isEncoder()) {
         continue;
       }
       for (String supportedType : info.getSupportedTypes()) {
         String name = info.getName();
-        CodecCapabilities codecCapabilities = info.getCapabilitiesForType(supportedType);
-        Log.v(TAG, "==================================================");
-        Log.v(TAG, String.format("name: %s", name));
-        Log.v(TAG, String.format("supportedType: %s", supportedType));
-        Log.v(
-            TAG, String.format("codecBlackList.contains(name): %b", codecBlackList.contains(name)));
-        Log.v(
-            TAG,
+        decoderDumpString +=
             String.format(
-                "FEATURE_SecurePlayback: %b",
-                codecCapabilities.isFeatureSupported(
-                    MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback)));
+                "name: %s (%s, %s):",
+                name,
+                supportedType,
+                codecBlackList.contains(name) ? "blacklisted" : "not blacklisted");
+        CodecCapabilities codecCapabilities = info.getCapabilitiesForType(supportedType);
         VideoCapabilities videoCapabilities = codecCapabilities.getVideoCapabilities();
         if (videoCapabilities != null) {
-          Log.v(
-              TAG,
+          decoderDumpString +=
               String.format(
-                  "videoCapabilities.getSupportedWidths(): %s",
-                  videoCapabilities.getSupportedWidths().toString()));
-          Log.v(
-              TAG,
-              String.format(
-                  "videoCapabilities.getSupportedHeights(): %s",
-                  videoCapabilities.getSupportedHeights().toString()));
-          Log.v(
-              TAG,
-              String.format(
-                  "videoCapabilities.getBitrateRange(): %s",
-                  videoCapabilities.getBitrateRange().toString()));
-          Log.v(
-              TAG,
-              String.format(
-                  "videoCapabilities.getSupportedFrameRates(): %s",
-                  videoCapabilities.getSupportedFrameRates().toString()));
+                  "\n\t\t"
+                      + "widths: %s, "
+                      + "heights: %s, "
+                      + "bitrates: %s, "
+                      + "framerates: %s, ",
+                  videoCapabilities.getSupportedWidths().toString(),
+                  videoCapabilities.getSupportedHeights().toString(),
+                  videoCapabilities.getBitrateRange().toString(),
+                  videoCapabilities.getSupportedFrameRates().toString());
         }
-        Log.v(TAG, "==================================================");
-        Log.v(TAG, "");
+        boolean adaptivePlayback =
+            codecCapabilities.isFeatureSupported(
+                MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback);
+        boolean securePlayback =
+            codecCapabilities.isFeatureSupported(
+                MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback);
+        boolean tunneledPlayback =
+            codecCapabilities.isFeatureSupported(
+                MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback);
+        if (adaptivePlayback || securePlayback || tunneledPlayback) {
+          decoderDumpString +=
+              String.format(
+                  "(%s%s%s",
+                  adaptivePlayback ? "AdaptivePlayback, " : "",
+                  securePlayback ? "SecurePlayback, " : "",
+                  tunneledPlayback ? "TunneledPlayback, " : "");
+          // Remove trailing space and comma
+          decoderDumpString = decoderDumpString.substring(0, decoderDumpString.length() - 2);
+          decoderDumpString += ")";
+        } else {
+          decoderDumpString += " No extra features supported";
+        }
+        decoderDumpString += "\n";
       }
     }
+    Log.v(
+        TAG,
+        String.format(
+            " \n"
+                + "==================================================\n"
+                + "Full list of decoder features: [AdaptivePlayback, SecurePlayback,"
+                + " TunneledPlayback]\n"
+                + "Unsupported features for each codec are not listed\n"
+                + decoderDumpString
+                + "=================================================="));
   }
 }
diff --git a/src/starboard/android/apk/build.id b/src/starboard/android/apk/build.id
new file mode 100644
index 0000000..d3b971d
--- /dev/null
+++ b/src/starboard/android/apk/build.id
@@ -0,0 +1 @@
+289402
\ No newline at end of file
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 a5a77a7..88f3762 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
@@ -123,7 +123,8 @@
         min_required_frames_ * task.number_of_channels *
             GetSampleSize(task.sample_type),
         &MinRequiredFramesTester::UpdateSourceStatusFunc,
-        &MinRequiredFramesTester::ConsumeFramesFunc, this);
+        &MinRequiredFramesTester::ConsumeFramesFunc,
+        &MinRequiredFramesTester::ErrorFunc, 0, -1, this);
     {
       ScopedLock scoped_lock(mutex_);
       wait_timeout = !condition_variable_.WaitTimed(kSbTimeSecond * 5);
@@ -174,6 +175,14 @@
   tester->ConsumeFrames(frames_consumed);
 }
 
+// static
+void MinRequiredFramesTester::ErrorFunc(bool capability_changed,
+                                        void* context) {
+  // TODO: Handle errors during minimum frames test, maybe by terminating the
+  //       test earlier.
+  SB_NOTREACHED();
+}
+
 void MinRequiredFramesTester::UpdateSourceStatus(int* frames_in_buffer,
                                                  int* offset_in_frames,
                                                  bool* is_playing,
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 290011d..e689e96 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
@@ -85,6 +85,7 @@
   static void ConsumeFramesFunc(int frames_consumed,
                                 SbTime frames_consumed_at,
                                 void* context);
+  static void ErrorFunc(bool capability_changed, void* context);
   void UpdateSourceStatus(int* frames_in_buffer,
                           int* offset_in_frames,
                           bool* is_playing,
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 44dffc8..cf9d744 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.cc
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -29,11 +29,23 @@
 namespace shared {
 namespace {
 
+// The same as AudioTrack.ERROR_DEAD_OBJECT.
+const int kAudioTrackErrorDeadObject = -6;
+
 // The maximum number of frames that can be written to android audio track per
 // write request. If we don't set this cap for writing frames to audio track,
 // we will repeatedly allocate a large byte array which cannot be consumed by
 // audio track completely.
 const int kMaxFramesPerRequest = 65536;
+
+// Most Android audio HAL updates audio time for A/V synchronization on audio
+// sync frames. For example, audio HAL may try to render when it gets an entire
+// sync frame and then update audio time. Shorter duration of sync frame
+// improves the accuracy of audio time, especially at the beginning of a
+// playback, as otherwise the audio time during the initial update may be too
+// large (non zero) and results in dropped video frames.
+const SbTime kMaxDurationPerRequestInTunnelMode = 16 * kSbTimeMillisecond;
+
 const jint kNoOffset = 0;
 const size_t kSilenceFramesPerAppend = 1024;
 
@@ -74,6 +86,12 @@
   return static_cast<uint8_t*>(pointer) + offset;
 }
 
+int GetMaxFramesPerRequestForTunnelMode(int sampling_frequency_hz) {
+  auto max_frames = kMaxDurationPerRequestInTunnelMode * sampling_frequency_hz /
+                    kSbTimeSecond;
+  return (max_frames + 15) / 16 * 16;  // align to 16
+}
+
 }  // namespace
 
 AudioTrackAudioSink::AudioTrackAudioSink(
@@ -86,6 +104,9 @@
     int preferred_buffer_size_in_bytes,
     SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
     ConsumeFramesFunc consume_frames_func,
+    SbAudioSinkPrivate::ErrorFunc error_func,
+    SbTime start_time,
+    int tunnel_mode_audio_session_id,
     void* context)
     : type_(type),
       channels_(channels),
@@ -95,40 +116,56 @@
       frames_per_channel_(frames_per_channel),
       update_source_status_func_(update_source_status_func),
       consume_frames_func_(consume_frames_func),
-      context_(context),
-      last_playback_head_position_(0),
-      j_audio_track_bridge_(NULL),
-      j_audio_data_(NULL),
-      quit_(false),
-      audio_out_thread_(kSbThreadInvalid),
-      playback_rate_(1.0f),
-      written_frames_(0) {
+      error_func_(error_func),
+      start_time_(start_time),
+      tunnel_mode_audio_session_id_(tunnel_mode_audio_session_id),
+      max_frames_per_request_(
+          tunnel_mode_audio_session_id_ == -1
+              ? kMaxFramesPerRequest
+              : GetMaxFramesPerRequestForTunnelMode(sampling_frequency_hz_)),
+      context_(context) {
   SB_DCHECK(update_source_status_func_);
   SB_DCHECK(consume_frames_func_);
   SB_DCHECK(frame_buffer_);
   SB_DCHECK(SbAudioSinkIsAudioSampleTypeSupported(sample_type));
 
+  // TODO: Support query if platform supports float type for tunnel mode.
+  if (tunnel_mode_audio_session_id_ != -1) {
+    SB_DCHECK(sample_type_ == kSbMediaAudioSampleTypeInt16Deprecated);
+  }
+
+  SB_LOG(INFO) << "Creating audio sink starts at " << start_time_;
+
   JniEnvExt* env = JniEnvExt::Get();
   ScopedLocalJavaRef<jobject> j_audio_output_manager(
       env->CallStarboardObjectMethodOrAbort(
           "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
   jobject j_audio_track_bridge = env->CallObjectMethodOrAbort(
       j_audio_output_manager.Get(), "createAudioTrackBridge",
-      "(IIII)Ldev/cobalt/media/AudioTrackBridge;",
+      "(IIIII)Ldev/cobalt/media/AudioTrackBridge;",
       GetAudioFormatSampleType(sample_type_), sampling_frequency_hz_, channels_,
-      preferred_buffer_size_in_bytes);
+      preferred_buffer_size_in_bytes, tunnel_mode_audio_session_id_);
   if (!j_audio_track_bridge) {
+    // One of the cases that this may hit is when output happened to be switched
+    // to a device that doesn't support tunnel mode.
+    // TODO: Find a way to exclude the device from tunnel mode playback, to
+    //       avoid infinite loop in creating the audio sink on a device
+    //       claims to support tunnel mode but fails to create the audio sink.
+    // TODO: Currently this will be reported as a general decode error,
+    //       investigate if this can be reported as a capability changed error.
     return;
   }
   j_audio_track_bridge_ = env->ConvertLocalRefToGlobalRef(j_audio_track_bridge);
   if (sample_type_ == kSbMediaAudioSampleTypeFloat32) {
-    j_audio_data_ = env->NewFloatArray(channels_ * kMaxFramesPerRequest);
+    j_audio_data_ = env->NewFloatArray(channels_ * max_frames_per_request_);
   } else if (sample_type_ == kSbMediaAudioSampleTypeInt16Deprecated) {
     j_audio_data_ = env->NewByteArray(channels_ * GetSampleSize(sample_type_) *
-                                      kMaxFramesPerRequest);
+                                      max_frames_per_request_);
   } else {
     SB_NOTREACHED();
   }
+  SB_CHECK(j_audio_data_) << "Failed to allocate |j_audio_data_|";
+
   j_audio_data_ = env->ConvertLocalRefToGlobalRef(j_audio_data_);
 
   audio_out_thread_ = SbThreadCreate(
@@ -183,15 +220,21 @@
   return NULL;
 }
 
+// TODO: Break down the function into manageable pieces.
 void AudioTrackAudioSink::AudioThreadFunc() {
   JniEnvExt* env = JniEnvExt::Get();
   bool was_playing = false;
 
   SB_LOG(INFO) << "AudioTrackAudioSink thread started.";
 
-#if defined(SB_PLAYER_FILTER_ENABLE_STATE_CHECK)
+  int accumulated_written_frames = 0;
+  // TODO: |last_playback_head_changed_at| is also resetted when a warning is
+  //       logged after the playback head position hasn't been updated for a
+  //       while.  We should refine the name to make it better reflect its
+  //       usage.
   SbTime last_playback_head_changed_at = -1;
-#endif  // defined(SB_PLAYER_FILTER_ENABLE_STATE_CHECK)
+  SbTime playback_head_not_changed_duration = 0;
+  SbTime last_written_succeeded_at = -1;
 
   while (!quit_) {
     int playback_head_position = 0;
@@ -214,12 +257,15 @@
           std::max(playback_head_position, last_playback_head_position_);
       int frames_consumed =
           playback_head_position - last_playback_head_position_;
+      SbTime now = SbTimeGetMonotonicNow();
 
-#if defined(SB_PLAYER_FILTER_ENABLE_STATE_CHECK)
+      if (last_playback_head_changed_at == -1) {
+        last_playback_head_changed_at = now;
+      }
       if (last_playback_head_position_ == playback_head_position) {
-        auto now = SbTimeGetMonotonicNow();
         SbTime elapsed = now - last_playback_head_changed_at;
-        if (was_playing && elapsed > 5 * kSbTimeSecond) {
+        if (elapsed > 5 * kSbTimeSecond) {
+          playback_head_not_changed_duration += elapsed;
           last_playback_head_changed_at = now;
           SB_LOG(INFO) << "last playback head position is "
                        << last_playback_head_position_
@@ -227,9 +273,9 @@
                        << " microseconds.";
         }
       } else {
-        last_playback_head_changed_at = SbTimeGetMonotonicNow();
+        last_playback_head_changed_at = now;
+        playback_head_not_changed_duration = 0;
       }
-#endif  // defined(SB_PLAYER_FILTER_ENABLE_STATE_CHECK)
 
       last_playback_head_position_ = playback_head_position;
       frames_consumed = std::min(frames_consumed, written_frames_);
@@ -260,6 +306,9 @@
       SB_LOG(INFO) << "AudioTrackAudioSink paused.";
     } else if (!was_playing && is_playing) {
       was_playing = true;
+      last_playback_head_changed_at = -1;
+      playback_head_not_changed_duration = 0;
+      last_written_succeeded_at = -1;
       env->CallVoidMethodOrAbort(j_audio_track_bridge_, "play", "()V");
       SB_LOG(INFO) << "AudioTrackAudioSink playing.";
     }
@@ -281,7 +330,7 @@
     }
 
     expected_written_frames =
-        std::min(expected_written_frames, kMaxFramesPerRequest);
+        std::min(expected_written_frames, max_frames_per_request_);
     if (expected_written_frames == 0) {
       // It is possible that all the frames in buffer are written to the
       // soundcard, but those are not being consumed. If eos is reached,
@@ -292,24 +341,84 @@
         // Currently AudioDevice and AudioRenderer will write tail silence.
         // It should be reached only in tests. It's not ideal to allocate
         // a new silence buffer every time.
-        std::vector<uint8_t> silence_buffer(
-            channels_ * GetSampleSize(sample_type_) * kSilenceFramesPerAppend);
-        WriteData(env, silence_buffer.data(), kSilenceFramesPerAppend);
+        const int silence_frames_per_append =
+            std::min<int>(kSilenceFramesPerAppend, max_frames_per_request_);
+        std::vector<uint8_t> silence_buffer(channels_ *
+                                            GetSampleSize(sample_type_) *
+                                            silence_frames_per_append);
+        auto sync_time = start_time_ + accumulated_written_frames *
+                                           kSbTimeSecond /
+                                           sampling_frequency_hz_;
+        // Not necessary to handle error of WriteData(), even for
+        // kAudioTrackErrorDeadObject, as the audio has reached the end of
+        // stream.
+        // TODO: Ensure that the audio stream can still reach the end when an
+        //       error occurs.
+        WriteData(env, silence_buffer.data(), silence_frames_per_append,
+                  sync_time);
       }
+
+      // While WriteData() returns kAudioTrackErrorDeadObject on dead object,
+      // getAudioTimestamp() doesn't, it just no longer updates its return
+      // value.  If the dead object error occurs when there isn't any audio data
+      // to write, there is no way to detect it by checking the return value of
+      // getAudioTimestamp().  Instead, we have to check if its return value has
+      // been changed during a certain period of time, to detect the underlying
+      // dead object error.
+      // Note that dead object would occur while switching audio end points in
+      // tunnel mode.  Under non-tunnel mode, the Android native AudioTrack will
+      // handle audio track dead object automatically if the new end point can
+      // support current audio format.
+      // TODO: Investigate to handle this error in non-tunnel mode.
+      if (tunnel_mode_audio_session_id_ != -1 &&
+          last_written_succeeded_at != -1) {
+        const SbTime kAudioSinkBlockedDuration = kSbTimeSecond;
+        auto time_since_last_written_success =
+            SbTimeGetMonotonicNow() - last_written_succeeded_at;
+        if (time_since_last_written_success > kAudioSinkBlockedDuration &&
+            playback_head_not_changed_duration > kAudioSinkBlockedDuration &&
+            tunnel_mode_audio_session_id_ != -1) {
+          SB_LOG(INFO) << "Over one second without frames written and consumed";
+          consume_frames_func_(written_frames_, SbTimeGetMonotonicNow(),
+                               context_);
+          error_func_(kSbPlayerErrorCapabilityChanged, context_);
+          break;
+        }
+      }
+
       SbThreadSleep(10 * kSbTimeMillisecond);
       continue;
     }
     SB_DCHECK(expected_written_frames > 0);
+    auto sync_time = start_time_ + accumulated_written_frames * kSbTimeSecond /
+                                       sampling_frequency_hz_;
+
+    SB_CHECK(start_position + expected_written_frames <= frames_per_channel_);
     int written_frames = WriteData(
         env,
         IncrementPointerByBytes(frame_buffer_, start_position * channels_ *
                                                    GetSampleSize(sample_type_)),
-        expected_written_frames);
+        expected_written_frames, sync_time);
+    SbTime now = SbTimeGetMonotonicNow();
+
+    if (written_frames < 0) {
+      // Take all |written_frames_| as consumed since audio track could be dead.
+      consume_frames_func_(written_frames_, now, context_);
+      error_func_(written_frames == kAudioTrackErrorDeadObject
+                      ? kSbPlayerErrorCapabilityChanged
+                      : kSbPlayerErrorDecode,
+                  context_);
+      break;
+    } else if (written_frames > 0) {
+      last_written_succeeded_at = now;
+    }
     written_frames_ += written_frames;
+    accumulated_written_frames += written_frames;
+
     bool written_fully = (written_frames == expected_written_frames);
     auto unplayed_frames_in_time =
         written_frames_ * kSbTimeSecond / sampling_frequency_hz_ -
-        (SbTimeGetMonotonicNow() - frames_consumed_at);
+        (now - frames_consumed_at);
     // As long as there is enough data in the buffer, run the loop in lower
     // frequency to avoid taking too much CPU.  Note that the threshold should
     // be big enough to account for the unstable playback head reported at the
@@ -332,8 +441,9 @@
 }
 
 int AudioTrackAudioSink::WriteData(JniEnvExt* env,
-                                   const void* buffer,
-                                   int expected_written_frames) {
+                                   void* buffer,
+                                   int expected_written_frames,
+                                   SbTime sync_time) {
   if (sample_type_ == kSbMediaAudioSampleTypeFloat32) {
     int expected_written_size = expected_written_frames * channels_;
     env->SetFloatArrayRegion(static_cast<jfloatArray>(j_audio_data_), kNoOffset,
@@ -342,7 +452,10 @@
     int written =
         env->CallIntMethodOrAbort(j_audio_track_bridge_, "write", "([FI)I",
                                   j_audio_data_, expected_written_size);
-    SB_DCHECK(written >= 0);
+    if (written < 0) {
+      // Error code returned as negative value, like kAudioTrackErrorDeadObject.
+      return written;
+    }
     SB_DCHECK(written % channels_ == 0);
     return written / channels_;
   }
@@ -352,10 +465,14 @@
     env->SetByteArrayRegion(static_cast<jbyteArray>(j_audio_data_), kNoOffset,
                             expected_written_size,
                             static_cast<const jbyte*>(buffer));
-    int written =
-        env->CallIntMethodOrAbort(j_audio_track_bridge_, "write", "([BI)I",
-                                  j_audio_data_, expected_written_size);
-    SB_DCHECK(written >= 0);
+
+    int written = env->CallIntMethodOrAbort(j_audio_track_bridge_, "write",
+                                            "([BIJ)I", j_audio_data_,
+                                            expected_written_size, sync_time);
+    if (written < 0) {
+      // Error code returned as negative value, like kAudioTrackErrorDeadObject.
+      return written;
+    }
     SB_DCHECK(written % (channels_ * GetSampleSize(sample_type_)) == 0);
     return written / (channels_ * GetSampleSize(sample_type_));
   }
@@ -417,6 +534,27 @@
     SbAudioSinkPrivate::ConsumeFramesFunc consume_frames_func,
     SbAudioSinkPrivate::ErrorFunc error_func,
     void* context) {
+  const SbTime kStartTime = 0;
+  const int kTunnelModeAudioSessionId = -1;  // disable tunnel mode
+  return Create(channels, sampling_frequency_hz, audio_sample_type,
+                audio_frame_storage_type, frame_buffers, frames_per_channel,
+                update_source_status_func, consume_frames_func, error_func,
+                kStartTime, kTunnelModeAudioSessionId, context);
+}
+
+SbAudioSink AudioTrackAudioSinkType::Create(
+    int channels,
+    int sampling_frequency_hz,
+    SbMediaAudioSampleType audio_sample_type,
+    SbMediaAudioFrameStorageType audio_frame_storage_type,
+    SbAudioSinkFrameBuffers frame_buffers,
+    int frames_per_channel,
+    SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
+    SbAudioSinkPrivate::ConsumeFramesFunc consume_frames_func,
+    SbAudioSinkPrivate::ErrorFunc error_func,
+    SbTime start_media_time,
+    int tunnel_mode_audio_session_id,
+    void* context) {
   int min_required_frames = SbAudioSinkGetMinBufferSizeInFrames(
       channels, audio_sample_type, sampling_frequency_hz);
   SB_DCHECK(frames_per_channel >= min_required_frames);
@@ -425,7 +563,8 @@
   AudioTrackAudioSink* audio_sink = new AudioTrackAudioSink(
       this, channels, sampling_frequency_hz, audio_sample_type, frame_buffers,
       frames_per_channel, preferred_buffer_size_in_bytes,
-      update_source_status_func, consume_frames_func, context);
+      update_source_status_func, consume_frames_func, error_func,
+      start_media_time, tunnel_mode_audio_session_id, context);
   if (!audio_sink->IsAudioTrackValid()) {
     SB_DLOG(ERROR)
         << "AudioTrackAudioSinkType::Create failed to create audio track";
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 43b0c86..c5b1ba7 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.h
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.h
@@ -18,6 +18,7 @@
 #include <atomic>
 #include <functional>
 #include <map>
+#include <vector>
 
 #include "starboard/android/shared/audio_sink_min_required_frames_tester.h"
 #include "starboard/android/shared/jni_env_ext.h"
@@ -54,6 +55,19 @@
       SbAudioSinkPrivate::ConsumeFramesFunc consume_frames_func,
       SbAudioSinkPrivate::ErrorFunc error_func,
       void* context) override;
+  SbAudioSink Create(
+      int channels,
+      int sampling_frequency_hz,
+      SbMediaAudioSampleType audio_sample_type,
+      SbMediaAudioFrameStorageType audio_frame_storage_type,
+      SbAudioSinkFrameBuffers frame_buffers,
+      int frames_per_channel,
+      SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
+      SbAudioSinkPrivate::ConsumeFramesFunc consume_frames_func,
+      SbAudioSinkPrivate::ErrorFunc error_func,
+      SbTime start_time,
+      int tunnel_mode_audio_session_id,
+      void* context);
 
   bool IsValid(SbAudioSink audio_sink) override {
     return audio_sink != kSbAudioSinkInvalid && audio_sink->IsType(this);
@@ -92,6 +106,9 @@
       int preferred_buffer_size,
       SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
       ConsumeFramesFunc consume_frames_func,
+      SbAudioSinkPrivate::ErrorFunc error_func,
+      SbTime start_media_time,
+      int tunnel_mode_audio_session_id,
       void* context);
   ~AudioTrackAudioSink() override;
 
@@ -106,8 +123,9 @@
   static void* ThreadEntryPoint(void* context);
   void AudioThreadFunc();
 
-  int WriteData(JniEnvExt* env, const void* buffer, int size);
+  int WriteData(JniEnvExt* env, void* buffer, int size, SbTime sync_time);
 
+  // TODO: Add const to the following variables where appropriate.
   Type* type_;
   int channels_;
   int sampling_frequency_hz_;
@@ -116,18 +134,25 @@
   int frames_per_channel_;
   SbAudioSinkUpdateSourceStatusFunc update_source_status_func_;
   ConsumeFramesFunc consume_frames_func_;
+  SbAudioSinkPrivate::ErrorFunc error_func_;
+  const SbTime start_time_;
+  const int tunnel_mode_audio_session_id_;
+  const int max_frames_per_request_;
+
   void* context_;
-  int last_playback_head_position_;
-  jobject j_audio_track_bridge_;
-  jobject j_audio_data_;
+  int last_playback_head_position_ = 0;
+  jobject j_audio_track_bridge_ = nullptr;
+  jobject j_audio_data_ = nullptr;
 
-  volatile bool quit_;
-  SbThread audio_out_thread_;
+  volatile bool quit_ = false;
+  SbThread audio_out_thread_ = kSbThreadInvalid;
 
-  starboard::Mutex mutex_;
-  double playback_rate_;
+  Mutex mutex_;
+  double playback_rate_ = 1.0;
 
-  int written_frames_;
+  // TODO: Rename to |frames_in_audio_track| and move it into AudioThreadFunc()
+  //       as a local variable.
+  int written_frames_ = 0;
 };
 
 }  // namespace shared
diff --git a/src/starboard/android/shared/configuration_constants.cc b/src/starboard/android/shared/configuration_constants.cc
index bbf77e7..a7a09fd 100644
--- a/src/starboard/android/shared/configuration_constants.cc
+++ b/src/starboard/android/shared/configuration_constants.cc
@@ -83,19 +83,6 @@
 // video.
 const uint32_t kSbMediaMaxVideoBitrateInBitsPerSecond = 200 * 1024 * 1024;
 
-// Specify the number of video frames to be cached during playback.  A large
-// value leads to more stable fps but also causes the app to use more memory.
-const uint32_t kSbMediaMaximumVideoFrames = 12;
-
-// The encoded video frames are compressed in different ways, their decoding
-// time can vary a lot.  Occasionally a single frame can take longer time to
-// decode than the average time per frame.  The player has to cache some frames
-// to account for such inconsistency.  The number of frames being cached are
-// controlled by the following two macros.
-//
-// Specify the number of video frames to be cached before the playback starts.
-// Note that set this value too large may increase the playback start delay.
-const uint32_t kSbMediaMaximumVideoPrerollFrames = 4;
 
 // Specifies how video frame buffers must be aligned on this platform.
 const uint32_t kSbMediaVideoFrameAlignment = 256;
diff --git a/src/starboard/android/shared/configuration_public.h b/src/starboard/android/shared/configuration_public.h
index e428200..30374c7 100644
--- a/src/starboard/android/shared/configuration_public.h
+++ b/src/starboard/android/shared/configuration_public.h
@@ -31,10 +31,6 @@
 
 // --- Architecture Configuration --------------------------------------------
 
-// Indicates that there is no support for alignment at greater than 16 bytes for
-// items on the stack.
-#define SB_HAS_QUIRK_DOES_NOT_STACK_ALIGN_OVER_16_BYTES 1
-
 // --- System Header Configuration -------------------------------------------
 
 // Any system headers listed here that are not provided by the platform will be
@@ -163,13 +159,21 @@
 
 // --- User Configuration ----------------------------------------------------
 
+// --- Platform Specific Configuration ---------------------------------------
+
+// Enable SB_HAS_CONCEALED_STATE support.
+#define SB_HAS_CONCEALED_STATE 1
+
 // --- Platform Specific Audits ----------------------------------------------
 
 #if !defined(__GNUC__)
 #error "Android builds need a GCC-like compiler (for the moment)."
 #endif
 
-// Enable SB_HAS_CONCEALED_STATE support.
-#define SB_HAS_CONCEALED_STATE 1
+// --- Platform Specific Quirks ----------------------------------------------
+
+// Indicates that there is no support for alignment at greater than 16 bytes for
+// items on the stack.
+#define SB_HAS_QUIRK_DOES_NOT_STACK_ALIGN_OVER_16_BYTES 1
 
 #endif  // STARBOARD_ANDROID_SHARED_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/android/shared/gyp_configuration.py b/src/starboard/android/shared/gyp_configuration.py
index 3474905..7aecf6e 100644
--- a/src/starboard/android/shared/gyp_configuration.py
+++ b/src/starboard/android/shared/gyp_configuration.py
@@ -305,10 +305,7 @@
           'SbFileGetPathInfoTest.WorksOnStaticContentDirectories',
           # Android doesn't currently support specifying a bitrate under 8000
           'SbMediaCanPlayMimeAndKeySystem.MinimumSupport',
-          # There are issues with playback of heeac format files where the input
-          # |frames_per_channel| is 6912, instead of 12544 (as with other
-          # formats).
-          'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.NoInput/6',
+
           # These tests are disabled due to not receiving the kEndOfStream
           # player state update within the specified timeout.
           'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.NoInput/7',
diff --git a/src/starboard/android/shared/jni_utils.h b/src/starboard/android/shared/jni_utils.h
index 811a309..2dae0c1 100644
--- a/src/starboard/android/shared/jni_utils.h
+++ b/src/starboard/android/shared/jni_utils.h
@@ -53,7 +53,8 @@
  private:
   JT jt_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(ScopedLocalJavaRef);
+  ScopedLocalJavaRef(const ScopedLocalJavaRef&) = delete;
+  void operator=(const ScopedLocalJavaRef&) = delete;
 };
 
 // Convenience class to manage the lifetime of a local Java ByteBuffer
@@ -78,7 +79,8 @@
  private:
   ScopedLocalJavaRef<jobject> j_byte_buffer_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(ScopedJavaByteBuffer);
+  ScopedJavaByteBuffer(const ScopedJavaByteBuffer&) = delete;
+  void operator=(const ScopedJavaByteBuffer&) = delete;
 };
 
 }  // namespace shared
diff --git a/src/starboard/android/shared/launcher.py b/src/starboard/android/shared/launcher.py
index 566e595..106b2b9 100644
--- a/src/starboard/android/shared/launcher.py
+++ b/src/starboard/android/shared/launcher.py
@@ -117,6 +117,8 @@
       line = CleanLine(self.process.stdout.readline())
       if not line:
         return
+      # Show the crash lines reported by "am monitor".
+      sys.stderr.write(line)
       if re.search(_RE_ADB_AM_MONITOR_ERROR, line):
         self.done_queue.put(_QUEUE_CODE_CRASHED)
         # This log line will wake up the main thread
@@ -311,6 +313,7 @@
         'raw',
         '-s',
         '*:F',
+        '*:E',
         'DEBUG:*',
         'System.err:*',
         'starboard:*',
diff --git a/src/starboard/android/shared/media_codec_bridge.h b/src/starboard/android/shared/media_codec_bridge.h
index 77452c1..e68c69a 100644
--- a/src/starboard/android/shared/media_codec_bridge.h
+++ b/src/starboard/android/shared/media_codec_bridge.h
@@ -162,7 +162,8 @@
   // |GetOutputDimensions|.
   jobject j_reused_get_output_format_result_ = NULL;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(MediaCodecBridge);
+  MediaCodecBridge(const MediaCodecBridge&) = delete;
+  void operator=(const MediaCodecBridge&) = delete;
 };
 
 }  // namespace shared
diff --git a/src/starboard/android/shared/player_components_factory.cc b/src/starboard/android/shared/player_components_factory.cc
index 6ac984a..01dc7a6 100644
--- a/src/starboard/android/shared/player_components_factory.cc
+++ b/src/starboard/android/shared/player_components_factory.cc
@@ -12,23 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/android/shared/audio_decoder.h"
-#include "starboard/android/shared/video_decoder.h"
-#include "starboard/android/shared/video_render_algorithm.h"
-#include "starboard/common/log.h"
-#include "starboard/common/ref_counted.h"
-#include "starboard/common/scoped_ptr.h"
-#include "starboard/media.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"
-#include "starboard/shared/starboard/player/filter/audio_renderer_sink_impl.h"
-#include "starboard/shared/starboard/player/filter/player_components.h"
-#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
-#include "starboard/shared/starboard/player/filter/video_render_algorithm.h"
-#include "starboard/shared/starboard/player/filter/video_render_algorithm_impl.h"
-#include "starboard/shared/starboard/player/filter/video_renderer_sink.h"
+#include "starboard/android/shared/player_components_factory.h"
 
 namespace starboard {
 namespace shared {
@@ -36,121 +20,10 @@
 namespace player {
 namespace filter {
 
-namespace {
-
-const int kAudioSinkFramesAlignment = 256;
-const int kDefaultAudioSinkMinFramesPerAppend = 1024;
-const int kDefaultAudioSinkMaxCachedFrames =
-    8 * kDefaultAudioSinkMinFramesPerAppend;
-
-int AlignUp(int value, int alignment) {
-  return (value + alignment - 1) / alignment * alignment;
-}
-
-class PlayerComponentsFactory : public PlayerComponents::Factory {
-  bool CreateSubComponents(
-      const CreationParameters& creation_parameters,
-      scoped_ptr<AudioDecoder>* audio_decoder,
-      scoped_ptr<AudioRendererSink>* audio_renderer_sink,
-      scoped_ptr<VideoDecoder>* video_decoder,
-      scoped_ptr<VideoRenderAlgorithm>* video_render_algorithm,
-      scoped_refptr<VideoRendererSink>* video_renderer_sink,
-      std::string* error_message) override {
-    SB_DCHECK(error_message);
-
-    if (creation_parameters.audio_codec() != kSbMediaAudioCodecNone) {
-      SB_DCHECK(audio_decoder);
-      SB_DCHECK(audio_renderer_sink);
-
-      auto decoder_creator = [](const SbMediaAudioSampleInfo& audio_sample_info,
-                                SbDrmSystem drm_system) {
-        if (audio_sample_info.codec == kSbMediaAudioCodecAac) {
-          scoped_ptr<android::shared::AudioDecoder> audio_decoder_impl(
-              new android::shared::AudioDecoder(audio_sample_info.codec,
-                                                audio_sample_info, drm_system));
-          if (audio_decoder_impl->is_valid()) {
-            return audio_decoder_impl.PassAs<AudioDecoder>();
-          }
-        } else if (audio_sample_info.codec == kSbMediaAudioCodecOpus) {
-          scoped_ptr<opus::OpusAudioDecoder> audio_decoder_impl(
-              new opus::OpusAudioDecoder(audio_sample_info));
-          if (audio_decoder_impl->is_valid()) {
-            return audio_decoder_impl.PassAs<AudioDecoder>();
-          }
-        } else {
-          SB_NOTREACHED();
-        }
-        return scoped_ptr<AudioDecoder>();
-      };
-
-      audio_decoder->reset(new AdaptiveAudioDecoder(
-          creation_parameters.audio_sample_info(),
-          creation_parameters.drm_system(), decoder_creator));
-      audio_renderer_sink->reset(new AudioRendererSinkImpl);
-    }
-
-    if (creation_parameters.video_codec() != kSbMediaVideoCodecNone) {
-      SB_DCHECK(video_decoder);
-      SB_DCHECK(video_render_algorithm);
-      SB_DCHECK(video_renderer_sink);
-      SB_DCHECK(error_message);
-
-      scoped_ptr<android::shared::VideoDecoder> video_decoder_impl(
-          new android::shared::VideoDecoder(
-              creation_parameters.video_codec(),
-              creation_parameters.drm_system(),
-              creation_parameters.output_mode(),
-              creation_parameters.decode_target_graphics_context_provider(),
-              creation_parameters.max_video_capabilities(), error_message));
-      if (video_decoder_impl->is_valid()) {
-        video_render_algorithm->reset(new android::shared::VideoRenderAlgorithm(
-            video_decoder_impl.get()));
-        *video_renderer_sink = video_decoder_impl->GetSink();
-        video_decoder->reset(video_decoder_impl.release());
-      } else {
-        video_decoder->reset();
-        *video_renderer_sink = NULL;
-        *error_message =
-            "Failed to create video decoder with error: " + *error_message;
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-  void GetAudioRendererParams(const CreationParameters& creation_parameters,
-                              int* max_cached_frames,
-                              int* min_frames_per_append) const override {
-    SB_DCHECK(max_cached_frames);
-    SB_DCHECK(min_frames_per_append);
-    SB_DCHECK(kDefaultAudioSinkMinFramesPerAppend % kAudioSinkFramesAlignment ==
-              0);
-    *min_frames_per_append = kDefaultAudioSinkMinFramesPerAppend;
-
-    // AudioRenderer prefers to use kSbMediaAudioSampleTypeFloat32 and only uses
-    // kSbMediaAudioSampleTypeInt16Deprecated when float32 is not supported.
-    int min_frames_required = SbAudioSinkGetMinBufferSizeInFrames(
-        creation_parameters.audio_sample_info().number_of_channels,
-        SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeFloat32)
-            ? kSbMediaAudioSampleTypeFloat32
-            : kSbMediaAudioSampleTypeInt16Deprecated,
-        creation_parameters.audio_sample_info().samples_per_second);
-    // On Android 5.0, the size of audio renderer sink buffer need to be two
-    // times larger than AudioTrack minBufferSize. Otherwise, AudioTrack may
-    // stop working after pause.
-    *max_cached_frames =
-        min_frames_required * 2 + kDefaultAudioSinkMinFramesPerAppend;
-    *max_cached_frames = AlignUp(*max_cached_frames, kAudioSinkFramesAlignment);
-  }
-};
-
-}  // namespace
-
 // static
 scoped_ptr<PlayerComponents::Factory> PlayerComponents::Factory::Create() {
   return make_scoped_ptr<PlayerComponents::Factory>(
-      new PlayerComponentsFactory);
+      new android::shared::PlayerComponentsFactory);
 }
 
 // static
diff --git a/src/starboard/android/shared/player_components_factory.h b/src/starboard/android/shared/player_components_factory.h
new file mode 100644
index 0000000..350b00f
--- /dev/null
+++ b/src/starboard/android/shared/player_components_factory.h
@@ -0,0 +1,173 @@
+// 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.
+
+#ifndef STARBOARD_ANDROID_SHARED_PLAYER_COMPONENTS_FACTORY_H_
+#define STARBOARD_ANDROID_SHARED_PLAYER_COMPONENTS_FACTORY_H_
+
+#include <string>
+
+#include "starboard/android/shared/audio_decoder.h"
+#include "starboard/android/shared/video_decoder.h"
+#include "starboard/android/shared/video_render_algorithm.h"
+#include "starboard/common/log.h"
+#include "starboard/common/ref_counted.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/media.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"
+#include "starboard/shared/starboard/player/filter/audio_renderer_sink_impl.h"
+#include "starboard/shared/starboard/player/filter/player_components.h"
+#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
+#include "starboard/shared/starboard/player/filter/video_render_algorithm.h"
+#include "starboard/shared/starboard/player/filter/video_render_algorithm_impl.h"
+#include "starboard/shared/starboard/player/filter/video_renderer_sink.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+class PlayerComponentsFactory : public starboard::shared::starboard::player::
+                                    filter::PlayerComponents::Factory {
+  typedef starboard::shared::opus::OpusAudioDecoder OpusAudioDecoder;
+  typedef starboard::shared::starboard::player::filter::AdaptiveAudioDecoder
+      AdaptiveAudioDecoder;
+  typedef starboard::shared::starboard::player::filter::AudioDecoder
+      AudioDecoderBase;
+  typedef starboard::shared::starboard::player::filter::AudioRendererSink
+      AudioRendererSink;
+  typedef starboard::shared::starboard::player::filter::AudioRendererSinkImpl
+      AudioRendererSinkImpl;
+  typedef starboard::shared::starboard::player::filter::VideoDecoder
+      VideoDecoderBase;
+  typedef starboard::shared::starboard::player::filter::VideoRenderAlgorithm
+      VideoRenderAlgorithmBase;
+  typedef starboard::shared::starboard::player::filter::VideoRendererSink
+      VideoRendererSink;
+
+  const int kAudioSinkFramesAlignment = 256;
+  const int kDefaultAudioSinkMinFramesPerAppend = 1024;
+  const int kDefaultAudioSinkMaxCachedFrames =
+      8 * kDefaultAudioSinkMinFramesPerAppend;
+
+  virtual SbDrmSystem GetExtendedDrmSystem(SbDrmSystem drm_system) {
+    return drm_system;
+  }
+
+  static int AlignUp(int value, int alignment) {
+    return (value + alignment - 1) / alignment * alignment;
+  }
+
+  bool CreateSubComponents(
+      const CreationParameters& creation_parameters,
+      scoped_ptr<AudioDecoderBase>* audio_decoder,
+      scoped_ptr<AudioRendererSink>* audio_renderer_sink,
+      scoped_ptr<VideoDecoderBase>* video_decoder,
+      scoped_ptr<VideoRenderAlgorithmBase>* video_render_algorithm,
+      scoped_refptr<VideoRendererSink>* video_renderer_sink,
+      std::string* error_message) override {
+    SB_DCHECK(error_message);
+
+    if (creation_parameters.audio_codec() != kSbMediaAudioCodecNone) {
+      SB_DCHECK(audio_decoder);
+      SB_DCHECK(audio_renderer_sink);
+
+      auto decoder_creator = [](const SbMediaAudioSampleInfo& audio_sample_info,
+                                SbDrmSystem drm_system) {
+        if (audio_sample_info.codec == kSbMediaAudioCodecAac) {
+          scoped_ptr<AudioDecoder> audio_decoder_impl(new AudioDecoder(
+              audio_sample_info.codec, audio_sample_info, drm_system));
+          if (audio_decoder_impl->is_valid()) {
+            return audio_decoder_impl.PassAs<AudioDecoderBase>();
+          }
+        } else 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<AudioDecoderBase>();
+          }
+        } else {
+          SB_NOTREACHED();
+        }
+        return scoped_ptr<AudioDecoderBase>();
+      };
+
+      audio_decoder->reset(new AdaptiveAudioDecoder(
+          creation_parameters.audio_sample_info(),
+          GetExtendedDrmSystem(creation_parameters.drm_system()),
+          decoder_creator));
+      audio_renderer_sink->reset(new AudioRendererSinkImpl);
+    }
+
+    if (creation_parameters.video_codec() != kSbMediaVideoCodecNone) {
+      SB_DCHECK(video_decoder);
+      SB_DCHECK(video_render_algorithm);
+      SB_DCHECK(video_renderer_sink);
+      SB_DCHECK(error_message);
+
+      scoped_ptr<VideoDecoder> video_decoder_impl(new VideoDecoder(
+          creation_parameters.video_codec(),
+          GetExtendedDrmSystem(creation_parameters.drm_system()),
+          creation_parameters.output_mode(),
+          creation_parameters.decode_target_graphics_context_provider(),
+          creation_parameters.max_video_capabilities(), error_message));
+      if (video_decoder_impl->is_valid()) {
+        video_render_algorithm->reset(
+            new VideoRenderAlgorithm(video_decoder_impl.get()));
+        *video_renderer_sink = video_decoder_impl->GetSink();
+        video_decoder->reset(video_decoder_impl.release());
+      } else {
+        video_decoder->reset();
+        *video_renderer_sink = NULL;
+        *error_message =
+            "Failed to create video decoder with error: " + *error_message;
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  void GetAudioRendererParams(const CreationParameters& creation_parameters,
+                              int* max_cached_frames,
+                              int* min_frames_per_append) const override {
+    SB_DCHECK(max_cached_frames);
+    SB_DCHECK(min_frames_per_append);
+    SB_DCHECK(kDefaultAudioSinkMinFramesPerAppend % kAudioSinkFramesAlignment ==
+              0);
+    *min_frames_per_append = kDefaultAudioSinkMinFramesPerAppend;
+
+    // AudioRenderer prefers to use kSbMediaAudioSampleTypeFloat32 and only uses
+    // kSbMediaAudioSampleTypeInt16Deprecated when float32 is not supported.
+    int min_frames_required = SbAudioSinkGetMinBufferSizeInFrames(
+        creation_parameters.audio_sample_info().number_of_channels,
+        SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeFloat32)
+            ? kSbMediaAudioSampleTypeFloat32
+            : kSbMediaAudioSampleTypeInt16Deprecated,
+        creation_parameters.audio_sample_info().samples_per_second);
+    // On Android 5.0, the size of audio renderer sink buffer need to be two
+    // times larger than AudioTrack minBufferSize. Otherwise, AudioTrack may
+    // stop working after pause.
+    *max_cached_frames =
+        min_frames_required * 2 + kDefaultAudioSinkMinFramesPerAppend;
+    *max_cached_frames = AlignUp(*max_cached_frames, kAudioSinkFramesAlignment);
+  }
+};
+
+}  // namespace shared
+}  // namespace android
+}  // namespace starboard
+
+#endif  // STARBOARD_ANDROID_SHARED_PLAYER_COMPONENTS_FACTORY_H_
diff --git a/src/starboard/android/shared/player_create.cc b/src/starboard/android/shared/player_create.cc
index a3a9a2a..a3ff14b 100644
--- a/src/starboard/android/shared/player_create.cc
+++ b/src/starboard/android/shared/player_create.cc
@@ -14,7 +14,6 @@
 
 #include "starboard/player.h"
 
-#include "starboard/android/shared/android_media_session_client.h"
 #include "starboard/android/shared/video_decoder.h"
 #include "starboard/android/shared/video_window.h"
 #include "starboard/common/log.h"
@@ -28,8 +27,6 @@
 using starboard::shared::starboard::player::filter::
     FilterBasedPlayerWorkerHandler;
 using starboard::shared::starboard::player::PlayerWorker;
-using starboard::android::shared::kPlaying;
-using starboard::android::shared::UpdateActiveSessionPlatformPlaybackState;
 using starboard::android::shared::VideoDecoder;
 
 SbPlayer SbPlayerCreate(SbWindow window,
@@ -125,8 +122,6 @@
         kMaxNumberOfHardwareDecoders) {
       return kSbPlayerInvalid;
     }
-    // Only update session state for main player.
-    UpdateActiveSessionPlatformPlaybackState(kPlaying);
   }
 
   if (creation_param->output_mode != kSbPlayerOutputModeDecodeToTexture &&
diff --git a/src/starboard/android/shared/player_destroy.cc b/src/starboard/android/shared/player_destroy.cc
index bdfbba3..6a1a293 100644
--- a/src/starboard/android/shared/player_destroy.cc
+++ b/src/starboard/android/shared/player_destroy.cc
@@ -14,16 +14,12 @@
 
 #include "starboard/player.h"
 
-#include "starboard/android/shared/android_media_session_client.h"
 #include "starboard/shared/starboard/player/player_internal.h"
 
-using starboard::android::shared::kNone;
-using starboard::android::shared::UpdateActiveSessionPlatformPlaybackState;
-
 void SbPlayerDestroy(SbPlayer player) {
   if (!SbPlayerIsValid(player)) {
     return;
   }
-  UpdateActiveSessionPlatformPlaybackState(kNone);
+
   delete player;
 }
diff --git a/src/starboard/android/shared/player_set_playback_rate.cc b/src/starboard/android/shared/player_set_playback_rate.cc
index 233cd92..d18c197 100644
--- a/src/starboard/android/shared/player_set_playback_rate.cc
+++ b/src/starboard/android/shared/player_set_playback_rate.cc
@@ -14,14 +14,9 @@
 
 #include "starboard/player.h"
 
-#include "starboard/android/shared/android_media_session_client.h"
 #include "starboard/common/log.h"
 #include "starboard/shared/starboard/player/player_internal.h"
 
-using starboard::android::shared::kPaused;
-using starboard::android::shared::kPlaying;
-using starboard::android::shared::UpdateActiveSessionPlatformPlaybackState;
-
 bool SbPlayerSetPlaybackRate(SbPlayer player, double playback_rate) {
   if (!SbPlayerIsValid(player)) {
     SB_DLOG(WARNING) << "player is invalid.";
@@ -32,8 +27,6 @@
                      << playback_rate << '.';
     return false;
   }
-  bool paused = (playback_rate == 0.0);
-  UpdateActiveSessionPlatformPlaybackState(paused ? kPaused : kPlaying);
 
   player->SetPlaybackRate(playback_rate);
   return true;
diff --git a/src/starboard/android/shared/starboard_platform.gypi b/src/starboard/android/shared/starboard_platform.gypi
index 65c5044..d38907b 100644
--- a/src/starboard/android/shared/starboard_platform.gypi
+++ b/src/starboard/android/shared/starboard_platform.gypi
@@ -135,6 +135,7 @@
         'media_is_audio_supported.cc',
         'media_is_video_supported.cc',
         'microphone_impl.cc',
+        'player_components_factory.h',
         'player_create.cc',
         'player_destroy.cc',
         'player_get_preferred_output_mode.cc',
diff --git a/src/starboard/android/x86/cobalt/configuration.py b/src/starboard/android/x86/cobalt/configuration.py
index 5e11fd8..64509ee 100644
--- a/src/starboard/android/x86/cobalt/configuration.py
+++ b/src/starboard/android/x86/cobalt/configuration.py
@@ -43,14 +43,6 @@
           'CSS3FontsLayoutTests/Layout.Test'
           '/synthetic_bolding_should_occur_on_non_bold_font',
       ],
-      'nb_test': [
-          'BidirectionalFitReuseAllocatorTest.FallbackBlockMerge',
-          'BidirectionalFitReuseAllocatorTest.FreeBlockMergingLeft',
-          'BidirectionalFitReuseAllocatorTest.FreeBlockMergingRight',
-          'FirstFitReuseAllocatorTest.FallbackBlockMerge',
-          'FirstFitReuseAllocatorTest.FreeBlockMergingLeft',
-          'FirstFitReuseAllocatorTest.FreeBlockMergingRight',
-      ],
       'net_unittests': [  # Net tests are very unstable on Android L
           test_filter.FILTER_ALL
       ],
diff --git a/src/starboard/build/platform_configuration.py b/src/starboard/build/platform_configuration.py
index ebb054e..eaf4088 100644
--- a/src/starboard/build/platform_configuration.py
+++ b/src/starboard/build/platform_configuration.py
@@ -22,7 +22,7 @@
 from starboard.build.application_configuration import ApplicationConfiguration
 from starboard.optional import get_optional_tests
 from starboard.sabi import sabi
-from starboard.tools import ccache
+from starboard.tools import cache
 from starboard.tools import environment
 from starboard.tools import paths
 from starboard.tools import platform
@@ -70,15 +70,19 @@
       self._directory = os.path.realpath(os.path.dirname(__file__))
     self._application_configuration = None
     self._application_configuration_search_path = [self._directory]
+    # Default build accelerator is ccache.
+    self.build_accelerator = self.GetBuildAccelerator(cache.Accelerator.CCACHE)
 
-    # Specifies the build accelerator to be used. Default is ccache.
-    build_accelerator = ccache.Ccache()
+  def GetBuildAccelerator(self, accelerator):
+    """Returns the build accelerator name."""
+    build_accelerator = cache.Cache(accelerator)
+    name = build_accelerator.GetName()
     if build_accelerator.Use():
-      self.build_accelerator = build_accelerator.GetName()
-      logging.info('Using %sbuild accelerator.', self.build_accelerator)
+      logging.info('Using %s build accelerator.', name)
+      return name
     else:
-      self.build_accelerator = ''
-      logging.info('Not using a build accelerator.')
+      logging.info('Not using %s build accelerator.', name)
+      return ''
 
   def GetBuildFormat(self):
     """Returns the desired build format."""
@@ -354,7 +358,8 @@
     raise NotImplementedError()
 
   def GetPathToSabiJsonFile(self):
-    """Gets the path to the JSON file with Starboard ABI information for the build.
+    """Gets the path to the JSON file with Starboard ABI information for the
+       build.
 
     Examples:
         'starboard/sabi/arm64/sabi-v12.json'
diff --git a/src/starboard/common/configuration_defaults.cc b/src/starboard/common/configuration_defaults.cc
index d603e4f..6a933b9 100644
--- a/src/starboard/common/configuration_defaults.cc
+++ b/src/starboard/common/configuration_defaults.cc
@@ -30,7 +30,7 @@
 }
 
 const char* CobaltFallbackSplashScreenUrlDefault() {
-  return "none";
+  return "h5vcc-embedded://black_splash_screen.html";
 }
 
 const char* CobaltFallbackSplashScreenTopicsDefault() {
diff --git a/src/starboard/common/mutex.h b/src/starboard/common/mutex.h
index 67f1b68..6012c55 100644
--- a/src/starboard/common/mutex.h
+++ b/src/starboard/common/mutex.h
@@ -52,7 +52,8 @@
   SbMutex* mutex() const;
   mutable SbMutex mutex_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(Mutex);
+  Mutex(const Mutex&) = delete;
+  void operator=(const Mutex&) = delete;
 };
 
 // Scoped lock holder that works on starboard::Mutex.
@@ -63,7 +64,9 @@
 
  private:
   const Mutex& mutex_;
-  SB_DISALLOW_COPY_AND_ASSIGN(ScopedLock);
+
+  ScopedLock(const ScopedLock&) = delete;
+  void operator=(const ScopedLock&) = delete;
 };
 
 // Scoped lock holder that works on starboard::Mutex which uses AcquireTry()
@@ -78,7 +81,9 @@
  private:
   const Mutex& mutex_;
   bool is_locked_;
-  SB_DISALLOW_COPY_AND_ASSIGN(ScopedTryLock);
+
+  ScopedTryLock(const ScopedTryLock&) = delete;
+  void operator=(const ScopedTryLock&) = delete;
 };
 
 }  // namespace starboard
diff --git a/src/starboard/common/recursive_mutex.h b/src/starboard/common/recursive_mutex.h
index b949da9..6806858 100644
--- a/src/starboard/common/recursive_mutex.h
+++ b/src/starboard/common/recursive_mutex.h
@@ -48,7 +48,8 @@
   // Only the owner is able to modify recurse_count_.
   size_t recurse_count_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(RecursiveMutex);
+  RecursiveMutex(const RecursiveMutex&) = delete;
+  void operator=(const RecursiveMutex&) = delete;
 };
 
 class ScopedRecursiveLock {
@@ -58,7 +59,9 @@
 
  private:
   RecursiveMutex& mutex_;
-  SB_DISALLOW_COPY_AND_ASSIGN(ScopedRecursiveLock);
+
+  ScopedRecursiveLock(const ScopedRecursiveLock&) = delete;
+  void operator=(const ScopedRecursiveLock&) = delete;
 };
 
 }  // namespace starboard
diff --git a/src/starboard/common/rwlock.h b/src/starboard/common/rwlock.h
index 2ec8d05..73aafdd 100644
--- a/src/starboard/common/rwlock.h
+++ b/src/starboard/common/rwlock.h
@@ -68,7 +68,8 @@
   int32_t readers_;
   bool writing_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(RWLock);
+  RWLock(const RWLock&) = delete;
+  void operator=(const RWLock&) = delete;
 };
 
 class ScopedReadLock {
@@ -80,7 +81,9 @@
 
  private:
   RWLock* rw_lock_;
-  SB_DISALLOW_COPY_AND_ASSIGN(ScopedReadLock);
+
+  ScopedReadLock(const ScopedReadLock&) = delete;
+  void operator=(const ScopedReadLock&) = delete;
 };
 
 class ScopedWriteLock {
@@ -92,7 +95,9 @@
 
  private:
   RWLock* rw_lock_;
-  SB_DISALLOW_COPY_AND_ASSIGN(ScopedWriteLock);
+
+  ScopedWriteLock(const ScopedWriteLock&) = delete;
+  void operator=(const ScopedWriteLock&) = delete;
 };
 
 }  // namespace starboard
diff --git a/src/starboard/common/semaphore.h b/src/starboard/common/semaphore.h
index 35ad34a..ce0f800 100644
--- a/src/starboard/common/semaphore.h
+++ b/src/starboard/common/semaphore.h
@@ -52,7 +52,8 @@
   ConditionVariable condition_;
   int permits_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(Semaphore);
+  Semaphore(const Semaphore&) = delete;
+  void operator=(const Semaphore&) = delete;
 };
 
 }  // namespace starboard.
diff --git a/src/starboard/common/thread.h b/src/starboard/common/thread.h
index 1a878a0..c01b817 100644
--- a/src/starboard/common/thread.h
+++ b/src/starboard/common/thread.h
@@ -77,7 +77,8 @@
   struct Data;
   scoped_ptr<Data> d_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(Thread);
+  Thread(const Thread&) = delete;
+  void operator=(const Thread&) = delete;
 };
 
 }  // namespace starboard
diff --git a/src/starboard/configuration.h b/src/starboard/configuration.h
index e82f471..9a4477a 100644
--- a/src/starboard/configuration.h
+++ b/src/starboard/configuration.h
@@ -31,9 +31,6 @@
 #error "You must define STARBOARD in Starboard builds."
 #endif
 
-#define SB_TRUE 1
-#define SB_FALSE 0
-
 // --- Common Defines --------------------------------------------------------
 
 // The minimum API version allowed by this version of the Starboard headers,
@@ -77,6 +74,13 @@
 // Deprecated the SB_OVERRIDE macro.
 #define SB_OVERRIDE_DEPRECATED_VERSION SB_EXPERIMENTAL_API_VERSION
 
+// Deprecated the SB_DISALLOW_COPY_AND_ASSIGN macro.
+#define SB_DISALLOW_COPY_AND_ASSIGN_DEPRECATED_VERSION \
+  SB_EXPERIMENTAL_API_VERSION
+
+// Deprecated SB_TRUE and SB_FALSE.
+#define SB_TRUE_FALSE_DEPRECATED_VERSION SB_EXPERIMENTAL_API_VERSION
+
 // --- Release Candidate Feature Defines -------------------------------------
 
 // --- Common Detected Features ----------------------------------------------
@@ -89,6 +93,14 @@
 
 // --- Common Helper Macros --------------------------------------------------
 
+#if SB_API_VERSION < SB_TRUE_FALSE_DEPRECATED_VERSION
+#define SB_TRUE 1
+#define SB_FALSE 0
+#else
+#define SB_TRUE #error "The macro SB_TRUE is deprecated."
+#define SB_FALSE #error "The macro SB_FALSE is deprecated."
+#endif
+
 // Determines a compile-time capability of the system.
 #define SB_CAN(SB_FEATURE) \
   ((defined SB_CAN_##SB_FEATURE) && SB_CAN_##SB_FEATURE)
@@ -146,11 +158,16 @@
 #define SB_STRINGIFY(x) SB_STRINGIFY2(x)
 #define SB_STRINGIFY2(x) #x
 
+#if SB_API_VERSION < SB_DISALLOW_COPY_AND_ASSIGN_DEPRECATED_VERSION
 // A macro to disallow the copy constructor and operator= functions
 // This should be used in the private: declarations for a class
 #define SB_DISALLOW_COPY_AND_ASSIGN(TypeName) \
   TypeName(const TypeName&) = delete;         \
   void operator=(const TypeName&) = delete
+#else
+#define SB_DISALLOW_COPY_AND_ASSIGN \
+  #error "The SB_DISALLOW_COPY_AND_ASSIGN macro is deprecated."
+#endif  // SB_DISALLOW_COPY_AND_ASSIGN_DEPRECATED_VERSION < SB_API_VERSION
 
 // An enumeration of values for the kSbPreferredByteOrder configuration
 // variable.  Setting this up properly means avoiding slow color swizzles when
@@ -598,16 +615,13 @@
 #if defined(SB_MEDIA_MAXIMUM_VIDEO_FRAMES)
 #error \
     "SB_MEDIA_MAXIMUM_VIDEO_FRAMES should not be defined in Starboard " \
-"versions 12 and later. Instead, define kSbMediaMaximumVideoFrames in " \
-"starboard/<PLATFORM_PATH>/configuration_constants.cc."
+"versions 12 and later."
 #endif
 
 #if defined(SB_MEDIA_MAXIMUM_VIDEO_PREROLL_FRAMES)
 #error \
     "SB_MEDIA_MAXIMUM_VIDEO_PREROLL_FRAMES should not be defined in " \
-"Starboard versions 12 and later. Instead, define " \
-"kSbMediaMaximumVideoPrerollFrames in " \
-"starboard/<PLATFORM_PATH>/configuration_constants.cc."
+"Starboard versions 12 and later."
 #endif
 
 #if defined(SB_MEDIA_MAX_AUDIO_BITRATE_IN_BITS_PER_SECOND)
diff --git a/src/starboard/configuration_constants.h b/src/starboard/configuration_constants.h
index 961affe..1a1aac4 100644
--- a/src/starboard/configuration_constants.h
+++ b/src/starboard/configuration_constants.h
@@ -93,21 +93,6 @@
 // video.
 extern const uint32_t kSbMediaMaxVideoBitrateInBitsPerSecond;
 
-// Specify the number of video frames to be cached during playback.  A large
-// value leads to more stable fps but also causes the app to use more memory.
-extern const uint32_t kSbMediaMaximumVideoFrames;
-
-// The encoded video frames are compressed in different ways, so their decoding
-// time can vary a lot.  Occasionally a single frame can take longer time to
-// decode than the average time per frame.  The player has to cache some frames
-// to account for such inconsistency.  The number of frames being cached are
-// controlled by SB_MEDIA_MAXIMUM_VIDEO_PREROLL_FRAMES and
-// SB_MEDIA_MAXIMUM_VIDEO_FRAMES.
-//
-// Specify the number of video frames to be cached before the playback starts.
-// Note that setting this value too large may increase the playback start delay.
-extern const uint32_t kSbMediaMaximumVideoPrerollFrames;
-
 // Specifies how video frame buffers must be aligned on this platform.
 extern const uint32_t kSbMediaVideoFrameAlignment;
 
diff --git a/src/starboard/contrib/tizen/armv7l/configuration_public.h b/src/starboard/contrib/tizen/armv7l/configuration_public.h
index d3c0233..c4902db 100644
--- a/src/starboard/contrib/tizen/armv7l/configuration_public.h
+++ b/src/starboard/contrib/tizen/armv7l/configuration_public.h
@@ -126,14 +126,6 @@
 // decode than the average time per frame.  The player has to cache some frames
 // to account for such inconsistency.  The number of frames being cached are
 // controlled by the following two macros.
-//
-// Specify the number of video frames to be cached before the playback starts.
-// Note that set this value too large may increase the playback start delay.
-#define SB_MEDIA_MAXIMUM_VIDEO_PREROLL_FRAMES 4
-
-// Specify the number of video frames to be cached during playback.  A large
-// value leads to more stable fps but also causes the app to use more memory.
-#define SB_MEDIA_MAXIMUM_VIDEO_FRAMES 12
 
 // --- Timing API ------------------------------------------------------------
 
diff --git a/src/starboard/elf_loader/dynamic_section.h b/src/starboard/elf_loader/dynamic_section.h
index 8d22bf1..6619b90 100644
--- a/src/starboard/elf_loader/dynamic_section.h
+++ b/src/starboard/elf_loader/dynamic_section.h
@@ -90,7 +90,8 @@
   linker_function_t init_func_;
   linker_function_t fini_func_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(DynamicSection);
+  DynamicSection(const DynamicSection&) = delete;
+  void operator=(const DynamicSection&) = delete;
 };
 
 }  // namespace elf_loader
diff --git a/src/starboard/elf_loader/elf_hash_table.h b/src/starboard/elf_loader/elf_hash_table.h
index 14bb9ca..7a867c1 100644
--- a/src/starboard/elf_loader/elf_hash_table.h
+++ b/src/starboard/elf_loader/elf_hash_table.h
@@ -53,7 +53,8 @@
   const Word* hash_chain_;
   size_t hash_chain_size_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(ElfHashTable);
+  ElfHashTable(const ElfHashTable&) = delete;
+  void operator=(const ElfHashTable&) = delete;
 };
 
 }  // namespace elf_loader
diff --git a/src/starboard/elf_loader/elf_header.h b/src/starboard/elf_loader/elf_header.h
index a8b666c..647a1bf 100644
--- a/src/starboard/elf_loader/elf_header.h
+++ b/src/starboard/elf_loader/elf_header.h
@@ -36,7 +36,9 @@
 
  private:
   scoped_ptr<Ehdr> elf_header_;
-  SB_DISALLOW_COPY_AND_ASSIGN(ElfHeader);
+
+  ElfHeader(const ElfHeader&) = delete;
+  void operator=(const ElfHeader&) = delete;
 };
 
 }  // namespace elf_loader
diff --git a/src/starboard/elf_loader/elf_loader.h b/src/starboard/elf_loader/elf_loader.h
index 7119651..587c7a6 100644
--- a/src/starboard/elf_loader/elf_loader.h
+++ b/src/starboard/elf_loader/elf_loader.h
@@ -66,7 +66,8 @@
   // The single ELF Loader instance.
   static ElfLoader* g_instance;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(ElfLoader);
+  ElfLoader(const ElfLoader&) = delete;
+  void operator=(const ElfLoader&) = delete;
 };
 
 }  // namespace elf_loader
diff --git a/src/starboard/elf_loader/elf_loader_impl.h b/src/starboard/elf_loader/elf_loader_impl.h
index da9c5f3..84c43c7 100644
--- a/src/starboard/elf_loader/elf_loader_impl.h
+++ b/src/starboard/elf_loader/elf_loader_impl.h
@@ -46,7 +46,8 @@
   scoped_ptr<ExportedSymbols> exported_symbols_;
   scoped_ptr<Relocations> relocations_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(ElfLoaderImpl);
+  ElfLoaderImpl(const ElfLoaderImpl&) = delete;
+  void operator=(const ElfLoaderImpl&) = delete;
 };
 
 }  // namespace elf_loader
diff --git a/src/starboard/elf_loader/elf_loader_sys_impl.h b/src/starboard/elf_loader/elf_loader_sys_impl.h
index bcc7af0..7f148a5 100644
--- a/src/starboard/elf_loader/elf_loader_sys_impl.h
+++ b/src/starboard/elf_loader/elf_loader_sys_impl.h
@@ -32,7 +32,8 @@
  private:
   void* handle_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(ElfLoaderImpl);
+  ElfLoaderImpl(const ElfLoaderImpl&) = delete;
+  void operator=(const ElfLoaderImpl&) = delete;
 };
 
 }  // namespace elf_loader
diff --git a/src/starboard/elf_loader/evergreen_config.h b/src/starboard/elf_loader/evergreen_config.h
index a2ca212..6078b43 100644
--- a/src/starboard/elf_loader/evergreen_config.h
+++ b/src/starboard/elf_loader/evergreen_config.h
@@ -48,7 +48,8 @@
   EvergreenConfig(const char* library_path,
                   const char* content_path,
                   const void* (*custom_get_extension_)(const char* name));
-  SB_DISALLOW_COPY_AND_ASSIGN(EvergreenConfig);
+  EvergreenConfig(const EvergreenConfig&) = delete;
+  void operator=(const EvergreenConfig&) = delete;
 };
 
 }  // namespace elf_loader
diff --git a/src/starboard/elf_loader/exported_symbols.cc b/src/starboard/elf_loader/exported_symbols.cc
index b07c021..33e24f0 100644
--- a/src/starboard/elf_loader/exported_symbols.cc
+++ b/src/starboard/elf_loader/exported_symbols.cc
@@ -447,8 +447,6 @@
   REGISTER_SYMBOL(kSbMaxThreadNameLength);
   REGISTER_SYMBOL(kSbMediaMaxAudioBitrateInBitsPerSecond);
   REGISTER_SYMBOL(kSbMediaMaxVideoBitrateInBitsPerSecond);
-  REGISTER_SYMBOL(kSbMediaMaximumVideoFrames);
-  REGISTER_SYMBOL(kSbMediaMaximumVideoPrerollFrames);
   REGISTER_SYMBOL(kSbMediaVideoFrameAlignment);
   REGISTER_SYMBOL(kSbMemoryLogPath);
   REGISTER_SYMBOL(kSbMemoryPageSize);
diff --git a/src/starboard/elf_loader/exported_symbols.h b/src/starboard/elf_loader/exported_symbols.h
index 1ffc732..e246214 100644
--- a/src/starboard/elf_loader/exported_symbols.h
+++ b/src/starboard/elf_loader/exported_symbols.h
@@ -41,7 +41,8 @@
  private:
   std::map<std::string, const void*> map_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(ExportedSymbols);
+  ExportedSymbols(const ExportedSymbols&) = delete;
+  void operator=(const ExportedSymbols&) = delete;
 };
 
 }  // namespace elf_loader
diff --git a/src/starboard/elf_loader/file_impl.h b/src/starboard/elf_loader/file_impl.h
index 9407c95..8545195 100644
--- a/src/starboard/elf_loader/file_impl.h
+++ b/src/starboard/elf_loader/file_impl.h
@@ -35,7 +35,8 @@
  private:
   SbFile file_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(FileImpl);
+  FileImpl(const FileImpl&) = delete;
+  void operator=(const FileImpl&) = delete;
 };
 
 }  // namespace elf_loader
diff --git a/src/starboard/elf_loader/gnu_hash_table.h b/src/starboard/elf_loader/gnu_hash_table.h
index 5bdf199..bf08c17 100644
--- a/src/starboard/elf_loader/gnu_hash_table.h
+++ b/src/starboard/elf_loader/gnu_hash_table.h
@@ -57,7 +57,8 @@
   const uint32_t* buckets_;
   const uint32_t* chain_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(GnuHashTable);
+  GnuHashTable(const GnuHashTable&) = delete;
+  void operator=(const GnuHashTable&) = delete;
 };
 
 }  // namespace elf_loader
diff --git a/src/starboard/elf_loader/program_table.h b/src/starboard/elf_loader/program_table.h
index d08301c..3741002 100644
--- a/src/starboard/elf_loader/program_table.h
+++ b/src/starboard/elf_loader/program_table.h
@@ -90,7 +90,8 @@
   // from the ELF file are offsets from this address.
   Addr base_memory_address_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(ProgramTable);
+  ProgramTable(const ProgramTable&) = delete;
+  void operator=(const ProgramTable&) = delete;
 };
 
 }  // namespace elf_loader
diff --git a/src/starboard/elf_loader/relocations.h b/src/starboard/elf_loader/relocations.h
index fad8a4e..1058c19 100644
--- a/src/starboard/elf_loader/relocations.h
+++ b/src/starboard/elf_loader/relocations.h
@@ -84,7 +84,8 @@
 
   ExportedSymbols* exported_symbols_;
 
-  SB_DISALLOW_COPY_AND_ASSIGN(Relocations);
+  Relocations(const Relocations&) = delete;
+  void operator=(const Relocations&) = delete;
 };
 
 }  // namespace elf_loader
diff --git a/src/starboard/evergreen/arm64/configuration_public.h b/src/starboard/evergreen/arm64/configuration_public.h
index 211ccc1..243ed7c 100644
--- a/src/starboard/evergreen/arm64/configuration_public.h
+++ b/src/starboard/evergreen/arm64/configuration_public.h
@@ -23,10 +23,6 @@
 
 // --- Architecture Configuration --------------------------------------------
 
-// Indicates that there is no support for alignment at greater than 16 bytes for
-// items on the stack.
-#define SB_HAS_QUIRK_DOES_NOT_STACK_ALIGN_OVER_16_BYTES 1
-
 // --- System Header Configuration -------------------------------------------
 
 // Any system headers listed here that are not provided by the platform will be
@@ -141,16 +137,16 @@
 
 // --- Memory Configuration --------------------------------------------------
 
+// Whether this platform can map executable memory. Implies SB_HAS_MMAP. This is
+// required for platforms that want to JIT.
+#define SB_CAN_MAP_EXECUTABLE_MEMORY 1
+
 // Whether this platform has and should use an MMAP function to map physical
 // memory to the virtual address space.
 #if SB_API_VERSION < 12
 #define SB_HAS_MMAP 1
 #endif
 
-// Whether this platform can map executable memory. Implies SB_HAS_MMAP. This is
-// required for platforms that want to JIT.
-#define SB_CAN_MAP_EXECUTABLE_MEMORY 1
-
 // --- Network Configuration -------------------------------------------------
 
 // Specifies whether this platform supports IPV6.
@@ -177,4 +173,10 @@
 #error "Evergreen-arm64 builds need a GCC-like compiler (for the moment)."
 #endif
 
+// --- Platform Specific Quirks ----------------------------------------------
+
+// Indicates that there is no support for alignment at greater than 16 bytes for
+// items on the stack.
+#define SB_HAS_QUIRK_DOES_NOT_STACK_ALIGN_OVER_16_BYTES 1
+
 #endif  // STARBOARD_EVERGREEN_ARM64_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/evergreen/testing/tests/abort_update_if_already_updating_test.sh b/src/starboard/evergreen/testing/tests/abort_update_if_already_updating_test.sh
index 7610aba..3061fb6 100755
--- a/src/starboard/evergreen/testing/tests/abort_update_if_already_updating_test.sh
+++ b/src/starboard/evergreen/testing/tests/abort_update_if_already_updating_test.sh
@@ -25,7 +25,7 @@
 function run_test() {
   clear_storage
 
-  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "Created drain file"
+  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "Created drain file at"
 
   if [[ $? -ne 0 ]]; then
     error "Failed to create a drain file for the test package"
diff --git a/src/starboard/evergreen/testing/tests/alternative_content_test.sh b/src/starboard/evergreen/testing/tests/alternative_content_test.sh
index 51ac20b..c962b8e 100755
--- a/src/starboard/evergreen/testing/tests/alternative_content_test.sh
+++ b/src/starboard/evergreen/testing/tests/alternative_content_test.sh
@@ -25,7 +25,7 @@
 function run_test() {
   clear_storage
 
-  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "update from test channel installed"
+  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "update from test channel was installed"
 
   if [[ $? -ne 0 ]]; then
     error "Failed to download and install the test package"
diff --git a/src/starboard/evergreen/testing/tests/crashing_binary_test.sh b/src/starboard/evergreen/testing/tests/crashing_binary_test.sh
index 67a3f9a..c485690 100755
--- a/src/starboard/evergreen/testing/tests/crashing_binary_test.sh
+++ b/src/starboard/evergreen/testing/tests/crashing_binary_test.sh
@@ -25,7 +25,7 @@
 function run_test() {
   clear_storage
 
-  start_cobalt "file:///tests/${TEST_FILE}?channel=tcrash" "${TEST_NAME}.0.log" "update from tcrash channel installed"
+  start_cobalt "file:///tests/${TEST_FILE}?channel=tcrash" "${TEST_NAME}.0.log" "update from tcrash channel was installed"
 
   if [[ $? -ne 0 ]]; then
     error "Failed to download and install the tcrash package"
diff --git a/src/starboard/evergreen/testing/tests/disabled_updater_test.sh b/src/starboard/evergreen/testing/tests/disabled_updater_test.sh
index 93275c5..9ba2528 100755
--- a/src/starboard/evergreen/testing/tests/disabled_updater_test.sh
+++ b/src/starboard/evergreen/testing/tests/disabled_updater_test.sh
@@ -25,7 +25,7 @@
 function run_test() {
   clear_storage
 
-  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "update from test channel installed"
+  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "update from test channel was installed"
 
   if [[ $? -ne 0 ]]; then
     error "Failed to download and install the test package"
diff --git a/src/starboard/evergreen/testing/tests/load_slot_being_updated_test.sh b/src/starboard/evergreen/testing/tests/load_slot_being_updated_test.sh
index fdb73b0..d0b206f 100755
--- a/src/starboard/evergreen/testing/tests/load_slot_being_updated_test.sh
+++ b/src/starboard/evergreen/testing/tests/load_slot_being_updated_test.sh
@@ -25,7 +25,7 @@
 function run_test() {
   clear_storage
 
-  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "update from test channel installed"
+  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "update from test channel was installed"
 
   if [[ $? -ne 0 ]]; then
     error "Failed to download and install the test package"
diff --git a/src/starboard/evergreen/testing/tests/mismatched_architecture_test.sh b/src/starboard/evergreen/testing/tests/mismatched_architecture_test.sh
index 00cc6da..6a90b12 100755
--- a/src/starboard/evergreen/testing/tests/mismatched_architecture_test.sh
+++ b/src/starboard/evergreen/testing/tests/mismatched_architecture_test.sh
@@ -25,7 +25,7 @@
 function run_test() {
   clear_storage
 
-  start_cobalt "file:///tests/${TEST_FILE}?channel=tmsabi" "${TEST_NAME}.0.log" "update from tmsabi channel installed"
+  start_cobalt "file:///tests/${TEST_FILE}?channel=tmsabi" "${TEST_NAME}.0.log" "update from tmsabi channel was installed"
 
   if [[ $? -ne 0 ]]; then
     error "Failed to download and install the tmsabi package"
diff --git a/src/starboard/evergreen/testing/tests/noop_binary_test.sh b/src/starboard/evergreen/testing/tests/noop_binary_test.sh
index 0eb7cae..fd33059 100755
--- a/src/starboard/evergreen/testing/tests/noop_binary_test.sh
+++ b/src/starboard/evergreen/testing/tests/noop_binary_test.sh
@@ -25,7 +25,7 @@
 function run_test() {
   clear_storage
 
-  start_cobalt "file:///tests/${TEST_FILE}?channel=tnoop" "${TEST_NAME}.0.log" "update from tnoop channel installed"
+  start_cobalt "file:///tests/${TEST_FILE}?channel=tnoop" "${TEST_NAME}.0.log" "update from tnoop channel was installed"
 
   if [[ $? -ne 0 ]]; then
     error "Failed to download and install the tnoop package"
diff --git a/src/starboard/evergreen/testing/tests/quick_roll_forward_test.sh b/src/starboard/evergreen/testing/tests/quick_roll_forward_test.sh
index 7dc28fe..789cbb6 100755
--- a/src/starboard/evergreen/testing/tests/quick_roll_forward_test.sh
+++ b/src/starboard/evergreen/testing/tests/quick_roll_forward_test.sh
@@ -25,7 +25,7 @@
 function run_test() {
   clear_storage
 
-  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "update from test channel installed"
+  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "update from test channel was installed"
 
   if [[ $? -ne 0 ]]; then
     error "Failed to download and install the test package"
diff --git a/src/starboard/evergreen/testing/tests/racing_updaters_test.sh b/src/starboard/evergreen/testing/tests/racing_updaters_test.sh
index 2865095..6d3cde6 100755
--- a/src/starboard/evergreen/testing/tests/racing_updaters_test.sh
+++ b/src/starboard/evergreen/testing/tests/racing_updaters_test.sh
@@ -44,7 +44,7 @@
 function run_test() {
   clear_storage
 
-  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "Created drain file"
+  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "Created drain file at"
 
   if [[ $? -ne 0 ]]; then
     error "Failed to create a drain file for the test package"
@@ -60,7 +60,7 @@
 
   clear_storage
 
-  wait_and_force_race_condition "Created drain file" "${LOG_PATH}/${TEST_NAME}.1.log" "${FILENAME}" &
+  wait_and_force_race_condition "Created drain file at" "${LOG_PATH}/${TEST_NAME}.1.log" "${FILENAME}" &
 
   start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.1.log" "failed to lock slot"
 
diff --git a/src/starboard/evergreen/testing/tests/test.html b/src/starboard/evergreen/testing/tests/test.html
index 81a943c..d0b47f3 100644
--- a/src/starboard/evergreen/testing/tests/test.html
+++ b/src/starboard/evergreen/testing/tests/test.html
@@ -37,8 +37,10 @@
       return;
     }
 
-    if (currentChannel == targetChannel) {
-      console.log("update from " + targetChannel + " channel installed");
+    if (status == "Update installed, pending restart") {
+      clearInterval(changeChannel);
+      console.log("update from " + currentChannel + " channel was installed");
+      return;
     }
   }
 
diff --git a/src/starboard/evergreen/testing/tests/update_works_for_only_one_app_test.sh b/src/starboard/evergreen/testing/tests/update_works_for_only_one_app_test.sh
index 65e5ad0..7565d99 100755
--- a/src/starboard/evergreen/testing/tests/update_works_for_only_one_app_test.sh
+++ b/src/starboard/evergreen/testing/tests/update_works_for_only_one_app_test.sh
@@ -25,7 +25,7 @@
 function run_test() {
   clear_storage
 
-  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "update from test channel installed"
+  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "update from test channel was installed"
 
   if [[ $? -ne 0 ]]; then
     error "Failed to download and install the test package"
diff --git a/src/starboard/evergreen/testing/tests/valid_slot_overwritten_test.sh b/src/starboard/evergreen/testing/tests/valid_slot_overwritten_test.sh
index 5fe2228..57379c4 100755
--- a/src/starboard/evergreen/testing/tests/valid_slot_overwritten_test.sh
+++ b/src/starboard/evergreen/testing/tests/valid_slot_overwritten_test.sh
@@ -25,7 +25,7 @@
 function run_test() {
   clear_storage
 
-  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "update from test channel installed"
+  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "update from test channel was installed"
 
   if [[ $? -ne 0 ]]; then
     error "Failed to download and install the test package"
diff --git a/src/starboard/evergreen/testing/tests/verify_qa_channel_update_test.sh b/src/starboard/evergreen/testing/tests/verify_qa_channel_update_test.sh
index 1d7e1c9..0bf6f36 100755
--- a/src/starboard/evergreen/testing/tests/verify_qa_channel_update_test.sh
+++ b/src/starboard/evergreen/testing/tests/verify_qa_channel_update_test.sh
@@ -25,7 +25,7 @@
 function run_test() {
   clear_storage
 
-  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "update from test channel installed"
+  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "update from test channel was installed"
 
   if [[ $? -ne 0 ]]; then
     error "Failed to download and install the test package"
diff --git a/src/starboard/linux/shared/configuration_constants.cc b/src/starboard/linux/shared/configuration_constants.cc
index e3ad2c7..4311059 100644
--- a/src/starboard/linux/shared/configuration_constants.cc
+++ b/src/starboard/linux/shared/configuration_constants.cc
@@ -84,20 +84,6 @@
 // video.
 const uint32_t kSbMediaMaxVideoBitrateInBitsPerSecond = 200 * 1024 * 1024;
 
-// Specify the number of video frames to be cached during playback.  A large
-// value leads to more stable fps but also causes the app to use more memory.
-const uint32_t kSbMediaMaximumVideoFrames = 12;
-
-// The encoded video frames are compressed in different ways, their decoding
-// time can vary a lot.  Occasionally a single frame can take longer time to
-// decode than the average time per frame.  The player has to cache some frames
-// to account for such inconsistency.  The number of frames being cached are
-// controlled by the following two macros.
-//
-// Specify the number of video frames to be cached before the playback starts.
-// Note that set this value too large may increase the playback start delay.
-const uint32_t kSbMediaMaximumVideoPrerollFrames = 4;
-
 // Specifies how video frame buffers must be aligned on this platform.
 const uint32_t kSbMediaVideoFrameAlignment = 256;
 
diff --git a/src/starboard/linux/shared/configuration_public.h b/src/starboard/linux/shared/configuration_public.h
index 1990c44..0e847e4 100644
--- a/src/starboard/linux/shared/configuration_public.h
+++ b/src/starboard/linux/shared/configuration_public.h
@@ -199,6 +199,20 @@
 // --- Media Configuration ---------------------------------------------------
 
 #if SB_API_VERSION < 12
+// Allow ac3 and ec3 support
+#define SB_HAS_AC3_AUDIO 1
+#endif  // SB_API_VERSION < 12
+
+#if SB_API_VERSION < 12
+// Specifies whether this platform updates audio frames asynchronously.  In such
+// case an extra parameter will be added to |SbAudioSinkConsumeFramesFunc| to
+// indicate the absolute time that the consumed audio frames are reported.
+// Check document for |SbAudioSinkConsumeFramesFunc| in audio_sink.h for more
+// details.
+#define SB_HAS_ASYNC_AUDIO_FRAMES_REPORTING 0
+#endif  // SB_API_VERSION <  12
+
+#if SB_API_VERSION < 12
 // The maximum audio bitrate the platform can decode.  The following value
 // equals to 5M bytes per seconds which is more than enough for compressed
 // audio.
@@ -219,26 +233,12 @@
 #endif  // SB_API_VERSION < 12
 
 #if SB_API_VERSION < 12
-// Specifies whether this platform updates audio frames asynchronously.  In such
-// case an extra parameter will be added to |SbAudioSinkConsumeFramesFunc| to
-// indicate the absolute time that the consumed audio frames are reported.
-// Check document for |SbAudioSinkConsumeFramesFunc| in audio_sink.h for more
-// details.
-#define SB_HAS_ASYNC_AUDIO_FRAMES_REPORTING 0
-#endif  // SB_API_VERSION <  12
-
-#if SB_API_VERSION < 12
 // Specifies the stack size for threads created inside media stack.  Set to 0 to
 // use the default thread stack size.  Set to non-zero to explicitly set the
 // stack size for media stack threads.
 #define SB_MEDIA_THREAD_STACK_SIZE 0U
 #endif  // SB_API_VERSION < 12
 
-#if SB_API_VERSION < 12
-// Allow ac3 and ec3 support
-#define SB_HAS_AC3_AUDIO 1
-#endif  // SB_API_VERSION < 12
-
 // --- Decoder-only Params ---
 
 #if SB_API_VERSION < 12
@@ -253,24 +253,6 @@
 #define SB_MEDIA_VIDEO_FRAME_ALIGNMENT 256U
 #endif  // SB_API_VERSION < 12
 
-#if SB_API_VERSION < 12
-// The encoded video frames are compressed in different ways, their decoding
-// time can vary a lot.  Occasionally a single frame can take longer time to
-// decode than the average time per frame.  The player has to cache some frames
-// to account for such inconsistency.  The number of frames being cached are
-// controlled by the following two macros.
-//
-// Specify the number of video frames to be cached before the playback starts.
-// Note that set this value too large may increase the playback start delay.
-#define SB_MEDIA_MAXIMUM_VIDEO_PREROLL_FRAMES 4
-#endif  // SB_API_VERSION < 12
-
-#if SB_API_VERSION < 12
-// Specify the number of video frames to be cached during playback.  A large
-// value leads to more stable fps but also causes the app to use more memory.
-#define SB_MEDIA_MAXIMUM_VIDEO_FRAMES 12
-#endif  // SB_API_VERSION < 12
-
 // --- Memory Configuration --------------------------------------------------
 
 #if SB_API_VERSION < 12
diff --git a/src/starboard/linux/x64x11/blittergles/sbversion/10/configuration_public.h b/src/starboard/linux/x64x11/blittergles/sbversion/10/configuration_public.h
index 3356def..65a0411 100644
--- a/src/starboard/linux/x64x11/blittergles/sbversion/10/configuration_public.h
+++ b/src/starboard/linux/x64x11/blittergles/sbversion/10/configuration_public.h
@@ -88,11 +88,11 @@
 // microphone supported.
 #define SB_HAS_MICROPHONE 1
 
+// Whether the current platform implements the on screen keyboard interface.
+#define SB_HAS_ON_SCREEN_KEYBOARD 0
+
 // Whether the current platform has speech synthesis.
 #undef SB_HAS_SPEECH_SYNTHESIS
 #define SB_HAS_SPEECH_SYNTHESIS 0
 
-// Whether the current platform implements the on screen keyboard interface.
-#define SB_HAS_ON_SCREEN_KEYBOARD 0
-
 #endif  // STARBOARD_LINUX_X64X11_BLITTERGLES_SBVERSION_10_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/blittergles/sbversion/11/configuration_public.h b/src/starboard/linux/x64x11/blittergles/sbversion/11/configuration_public.h
index 6aa1686..733c9d8 100644
--- a/src/starboard/linux/x64x11/blittergles/sbversion/11/configuration_public.h
+++ b/src/starboard/linux/x64x11/blittergles/sbversion/11/configuration_public.h
@@ -84,11 +84,11 @@
 // microphone supported.
 #define SB_HAS_MICROPHONE 1
 
+// Whether the current platform implements the on screen keyboard interface.
+#define SB_HAS_ON_SCREEN_KEYBOARD 0
+
 // Whether the current platform has speech synthesis.
 #undef SB_HAS_SPEECH_SYNTHESIS
 #define SB_HAS_SPEECH_SYNTHESIS 0
 
-// Whether the current platform implements the on screen keyboard interface.
-#define SB_HAS_ON_SCREEN_KEYBOARD 0
-
 #endif  // STARBOARD_LINUX_X64X11_BLITTERGLES_SBVERSION_11_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/blittergles/shared/configuration_public.h b/src/starboard/linux/x64x11/blittergles/shared/configuration_public.h
index 7a312fd..5661880 100644
--- a/src/starboard/linux/x64x11/blittergles/shared/configuration_public.h
+++ b/src/starboard/linux/x64x11/blittergles/shared/configuration_public.h
@@ -18,6 +18,8 @@
 #ifndef STARBOARD_LINUX_X64X11_BLITTERGLES_SHARED_CONFIGURATION_PUBLIC_H_
 #define STARBOARD_LINUX_X64X11_BLITTERGLES_SHARED_CONFIGURATION_PUBLIC_H_
 
+// --- Graphics Configuration ------------------------------------------------
+
 // This configuration supports the blitter API (implemented via GLES).
 #undef SB_HAS_BLITTER
 #define SB_HAS_BLITTER 1
diff --git a/src/starboard/linux/x64x11/configuration_public.h b/src/starboard/linux/x64x11/configuration_public.h
index 38bcc2e..3802b49 100644
--- a/src/starboard/linux/x64x11/configuration_public.h
+++ b/src/starboard/linux/x64x11/configuration_public.h
@@ -57,14 +57,13 @@
 #define SB_HAS_MICROPHONE 1
 #endif  // SB_API_VERSION < 12