Import Cobalt 22.master.0.303689
diff --git a/src/.pre-commit-config.yaml b/src/.pre-commit-config.yaml
index 0150efe..6240f63 100644
--- a/src/.pre-commit-config.yaml
+++ b/src/.pre-commit-config.yaml
@@ -123,7 +123,7 @@
         entry: python precommit_hooks/run_python2_unittests.py
         language: python
         language_version: python2
-        additional_dependencies: ['mock', 'jsonschema']
+        additional_dependencies: ['mock']
         types: [python]
     -   id: osslint
         name: osslint
diff --git a/src/cobalt/bindings/IDLExtendedAttributes.txt b/src/cobalt/bindings/IDLExtendedAttributes.txt
index ed7490c..8f4ad98 100644
--- a/src/cobalt/bindings/IDLExtendedAttributes.txt
+++ b/src/cobalt/bindings/IDLExtendedAttributes.txt
@@ -47,7 +47,6 @@
 # used by the Window interface, but is also used for TestWindow in unit tests.
 # http://heycam.github.io/webidl/#Global
 Global
-PrimaryGlobal
 
 # This interface is exposed on the specified global interface, rather than
 # the default of Window.
diff --git a/src/cobalt/bindings/testing/window.idl b/src/cobalt/bindings/testing/window.idl
index 3109cf5..3c15832 100644
--- a/src/cobalt/bindings/testing/window.idl
+++ b/src/cobalt/bindings/testing/window.idl
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-[PrimaryGlobal]
+[Global]
 interface Window : GlobalInterfaceParent {
   void windowOperation();
   attribute DOMString windowProperty;
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index ba3c984..c0995fe 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -1093,14 +1093,20 @@
 #endif  // SB_API_VERSION >= 12 ||
         // SB_HAS(ON_SCREEN_KEYBOARD)
     case kSbEventTypeLink: {
-      DispatchDeepLink(static_cast<const char*>(starboard_event->data));
+#if SB_API_VERSION >= 13
+      DispatchDeepLink(static_cast<const char*>(starboard_event->data),
+                                                starboard_event->timestamp);
+#else  // SB_API_VERSION >= 13
+      DispatchDeepLink(static_cast<const char*>(starboard_event->data),
+                                                SbTimeGetMonotonicNow());
+#endif  // SB_API_VERSION >= 13
       break;
     }
 #if SB_API_VERSION >= 13
     case kSbEventTypeAccessibilitySettingsChanged:
 #else
     case kSbEventTypeAccessiblitySettingsChanged:
-#endif  // B_API_VERSION >= 13
+#endif  // SB_API_VERSION >= 13
       DispatchEventInternal(new base::AccessibilitySettingsChangedEvent());
 #if SB_API_VERSION < 12
       // Also dispatch the newer text-to-speech settings changed event since
@@ -1473,7 +1479,8 @@
   }
 }
 
-void Application::DispatchDeepLink(const char* link) {
+void Application::DispatchDeepLink(const char* link,
+                                   SbTimeMonotonic timestamp) {
   if (!link || *link == 0) {
     return;
   }
@@ -1489,10 +1496,17 @@
     deep_link = unconsumed_deep_link_;
   }
 
+  deep_link_timestamp_ = timestamp;
+
   LOG(INFO) << "Dispatching deep link: " << deep_link;
   DispatchEventInternal(new base::DeepLinkEvent(
       deep_link, base::Bind(&Application::OnDeepLinkConsumedCallback,
                             base::Unretained(this), deep_link)));
+#if SB_API_VERSION >= 13
+  if (browser_module_) {
+    browser_module_->SetDeepLinkTimestamp(timestamp);
+  }
+#endif  // SB_API_VERSION >= 13
 }
 
 void Application::DispatchDeepLinkIfNotConsumed() {
@@ -1510,6 +1524,11 @@
         deep_link, base::Bind(&Application::OnDeepLinkConsumedCallback,
                               base::Unretained(this), deep_link)));
   }
+#if SB_API_VERSION >= 13
+  if (browser_module_) {
+    browser_module_->SetDeepLinkTimestamp(deep_link_timestamp_);
+  }
+#endif  // SB_API_VERSION >= 13
 }
 
 }  // namespace browser
diff --git a/src/cobalt/browser/application.h b/src/cobalt/browser/application.h
index 1fa07da..c08330c 100644
--- a/src/cobalt/browser/application.h
+++ b/src/cobalt/browser/application.h
@@ -224,11 +224,14 @@
   // Lock for access to unconsumed_deep_link_ from different threads.
   base::Lock unconsumed_deep_link_lock_;
 
+  SbTimeMonotonic deep_link_timestamp_ = 0;
+
   // Called when deep links are consumed.
   void OnDeepLinkConsumedCallback(const std::string& link);
 
   // Dispatch events for deep links.
-  void DispatchDeepLink(const char* link);
+  void DispatchDeepLink(const char* link,
+                        SbTimeMonotonic timestamp);
   void DispatchDeepLinkIfNotConsumed();
 
   DISALLOW_COPY_AND_ASSIGN(Application);
diff --git a/src/cobalt/browser/browser_bindings_gen.gyp b/src/cobalt/browser/browser_bindings_gen.gyp
index 9941d47..bce6391 100644
--- a/src/cobalt/browser/browser_bindings_gen.gyp
+++ b/src/cobalt/browser/browser_bindings_gen.gyp
@@ -334,12 +334,12 @@
 
         '../dom/buffer_source.idl',
         '../dom/captions/navigator_system_caption_settings.idl',
-        '../dom/document__web_animations_api.idl',
         '../dom/document_cobalt.idl',
         '../dom/document_cssom.idl',
         '../dom/document_html5.idl',
         '../dom/document_page_lifecycle.idl',
         '../dom/document_page_visibility.idl',
+        '../dom/document_web_animations_api.idl',
         '../dom/element_css_inline_style.idl',
         '../dom/element_cssom_view.idl',
         '../dom/element_dom_parsing_and_serialization.idl',
@@ -365,13 +365,13 @@
         '../dom/speech_synthesis_getter.idl',
         '../dom/url_mse.idl',
         '../dom/url_utils.idl',
-        '../dom/window__animation_timing.idl',
-        '../dom/window__performance.idl',
+        '../dom/window_animation_timing.idl',
         '../dom/window_cssom.idl',
         '../dom/window_cssom_view.idl',
         '../dom/window_event_handlers.idl',
         '../dom/window_local_storage.idl',
         '../dom/window_on_screen_keyboard.idl',
+        '../dom/window_performance.idl',
         '../dom/window_session_storage.idl',
         '../dom/window_timers.idl',
         '../media_capture/navigator.idl',
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index d724d9e..bb922b0 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -2101,5 +2101,11 @@
       is_preload, timestamp);
 }
 
+void BrowserModule::SetDeepLinkTimestamp(
+    SbTimeMonotonic timestamp) {
+  DCHECK(web_module_);
+  web_module_->SetDeepLinkTimestamp(timestamp);
+}
+
 }  // namespace browser
 }  // namespace cobalt
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index ef8113f..cec410e 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -232,6 +232,8 @@
   // Pass the application preload or start timestamps from Starboard.
   void SetApplicationStartOrPreloadTimestamp(bool is_preload,
                                              SbTimeMonotonic timestamp);
+  // Pass the deeplink timestamp from Starboard.
+  void SetDeepLinkTimestamp(SbTimeMonotonic timestamp);
  private:
 #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
   static void CoreDumpHandler(void* browser_module_as_void);
diff --git a/src/cobalt/browser/debug_console.h b/src/cobalt/browser/debug_console.h
index 798154d..ebb8d03 100644
--- a/src/cobalt/browser/debug_console.h
+++ b/src/cobalt/browser/debug_console.h
@@ -96,25 +96,25 @@
 
   // LifecycleObserver implementation.
   void Blur(SbTimeMonotonic timestamp) override {
-    web_module_->Blur(timestamp);
+    web_module_->Blur(0);
   }
   void Conceal(render_tree::ResourceProvider* resource_provider,
                SbTimeMonotonic timestamp) override {
-    web_module_->Conceal(resource_provider, timestamp);
+    web_module_->Conceal(resource_provider, 0);
   }
   void Freeze(SbTimeMonotonic timestamp) override {
-    web_module_->Freeze(timestamp);
+    web_module_->Freeze(0);
   }
   void Unfreeze(render_tree::ResourceProvider* resource_provider,
                 SbTimeMonotonic timestamp) override {
-    web_module_->Unfreeze(resource_provider, timestamp);
+    web_module_->Unfreeze(resource_provider, 0);
   }
   void Reveal(render_tree::ResourceProvider* resource_provider,
               SbTimeMonotonic timestamp) override {
-    web_module_->Reveal(resource_provider, timestamp);
+    web_module_->Reveal(resource_provider, 0);
   }
   void Focus(SbTimeMonotonic timestamp) override {
-     web_module_->Focus(timestamp);
+     web_module_->Focus(0);
   }
 
   void ReduceMemory() { web_module_->ReduceMemory(); }
diff --git a/src/cobalt/browser/main.cc b/src/cobalt/browser/main.cc
index 98ee605..371613c 100644
--- a/src/cobalt/browser/main.cc
+++ b/src/cobalt/browser/main.cc
@@ -63,9 +63,9 @@
   }
   LOG(INFO) << "Concealing application.";
   DCHECK(!g_application);
-  g_application =
-      new cobalt::browser::Application(quit_closure, true /*should_preload*/,
-                                       timestamp);
+  g_application = new cobalt::browser::Application(quit_closure,
+                                                   true /*should_preload*/,
+                                                   timestamp);
   DCHECK(g_application);
 }
 
@@ -79,9 +79,9 @@
   LOG(INFO) << "Starting application.";
 #if SB_API_VERSION >= 13
   DCHECK(!g_application);
-  g_application =
-      new cobalt::browser::Application(quit_closure, false /*not_preload*/,
-                                       timestamp);
+  g_application = new cobalt::browser::Application(quit_closure,
+                                                   false /*not_preload*/,
+                                                   timestamp);
   DCHECK(g_application);
 #else
   if (!g_application) {
diff --git a/src/cobalt/browser/splash_screen.h b/src/cobalt/browser/splash_screen.h
index 3f0595d..1be5f33 100644
--- a/src/cobalt/browser/splash_screen.h
+++ b/src/cobalt/browser/splash_screen.h
@@ -53,26 +53,27 @@
   }
 
   // LifecycleObserver implementation.
+  // LifecycleObserver implementation.
   void Blur(SbTimeMonotonic timestamp) override {
-    web_module_->Blur(timestamp);
+    web_module_->Blur(0);
   }
   void Conceal(render_tree::ResourceProvider* resource_provider,
                SbTimeMonotonic timestamp) override {
-    web_module_->Conceal(resource_provider, timestamp);
+    web_module_->Conceal(resource_provider, 0);
   }
   void Freeze(SbTimeMonotonic timestamp) override {
-    web_module_->Freeze(timestamp);
+    web_module_->Freeze(0);
   }
   void Unfreeze(render_tree::ResourceProvider* resource_provider,
                 SbTimeMonotonic timestamp) override {
-    web_module_->Unfreeze(resource_provider, timestamp);
+    web_module_->Unfreeze(resource_provider, 0);
   }
   void Reveal(render_tree::ResourceProvider* resource_provider,
               SbTimeMonotonic timestamp) override {
-    web_module_->Reveal(resource_provider, timestamp);
+    web_module_->Reveal(resource_provider, 0);
   }
   void Focus(SbTimeMonotonic timestamp) override {
-    web_module_->Focus(timestamp);
+    web_module_->Focus(0);
   }
 
   void ReduceMemory() { web_module_->ReduceMemory(); }
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc
index 1411c37..785e879 100644
--- a/src/cobalt/browser/switches.cc
+++ b/src/cobalt/browser/switches.cc
@@ -397,6 +397,11 @@
     "the URL used to launch Cobalt, then the value of "
     "'fallback_splash_screen_url' will be used.";
 
+const char kUseQAUpdateServer[] = "use_qa_update_server";
+const char kUseQAUpdateServerHelp[] =
+    "Uses the QA update server to test the changes to the configuration of the "
+    "PROD update server.";
+
 const char kVersion[] = "version";
 const char kVersionHelp[] = "Prints the current version of Cobalt";
 
@@ -480,7 +485,8 @@
         {kSoftwareSurfaceCacheSizeInBytes,
          kSoftwareSurfaceCacheSizeInBytesHelp},
         {kFallbackSplashScreenURL, kFallbackSplashScreenURLHelp},
-        {kVersion, kVersionHelp}, {kViewport, kViewportHelp},
+        {kUseQAUpdateServer, kUseQAUpdateServerHelp}, {kVersion, kVersionHelp},
+        {kViewport, kViewportHelp},
         {kVideoPlaybackRateMultiplier, kVideoPlaybackRateMultiplierHelp},
   };
 
diff --git a/src/cobalt/browser/switches.h b/src/cobalt/browser/switches.h
index 53fe622..9b3a0c2 100644
--- a/src/cobalt/browser/switches.h
+++ b/src/cobalt/browser/switches.h
@@ -151,6 +151,8 @@
 extern const char kFallbackSplashScreenURLHelp[];
 extern const char kFallbackSplashScreenTopics[];
 extern const char kFallbackSplashScreenTopicsHelp[];
+extern const char kUseQAUpdateServer[];
+extern const char kUseQAUpdateServerHelp[];
 extern const char kVersion[];
 extern const char kVersionHelp[];
 extern const char kViewport[];
diff --git a/src/cobalt/browser/user_agent_platform_info.cc b/src/cobalt/browser/user_agent_platform_info.cc
index 50a703f..8c47fee 100644
--- a/src/cobalt/browser/user_agent_platform_info.cc
+++ b/src/cobalt/browser/user_agent_platform_info.cc
@@ -20,10 +20,13 @@
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "cobalt/browser/switches.h"
+#if SB_IS(EVERGREEN)
+#include "cobalt/extension/installation_manager.h"
+#endif  // SB_IS(EVERGREEN)
 #include "cobalt/renderer/get_default_rasterizer_for_platform.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/version.h"
-#include "cobalt_build_id.h"  // NOLINT(build/include)
+#include "cobalt_build_id.h"  // NOLINT(build/include_subdir)
 #include "starboard/common/log.h"
 #include "starboard/common/string.h"
 #include "starboard/system.h"
@@ -56,10 +59,12 @@
       return "UNKNOWN";
     default:
       NOTREACHED();
-      return "";
+      return "UNKNOWN";
   }
 }
 
+const char kUnspecifiedConnectionTypeName[] = "UnspecifiedConnectionType";
+
 std::string CreateConnectionTypeString(
     const base::Optional<SbSystemConnectionType>& connection_type) {
   if (connection_type) {
@@ -68,23 +73,268 @@
         return "Wired";
       case kSbSystemConnectionTypeWireless:
         return "Wireless";
-      default:
-        NOTREACHED();
+      case kSbSystemConnectionTypeUnknown:
+        return kUnspecifiedConnectionTypeName;
+    }
+  }
+  return kUnspecifiedConnectionTypeName;
+}
+
+static bool isAsciiAlphaDigit(int c) {
+  return base::IsAsciiAlpha(c) || base::IsAsciiDigit(c);
+}
+
+// https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1
+static bool isVCHARorSpace(int c) { return c >= 0x20 && c <= 0x7E; }
+
+// https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
+static bool isTCHAR(int c) {
+  if (isAsciiAlphaDigit(c)) return true;
+  switch (c) {
+    case '!':
+    case '#':
+    case '$':
+    case '%':
+    case '&':
+    case '\'':
+    case '*':
+    case '+':
+    case '-':
+    case '.':
+    case '^':
+    case '_':
+    case '`':
+    case '|':
+    case '~':
+      return true;
+    default:
+      return false;
+  }
+}
+
+static bool isTCHARorForwardSlash(int c) { return isTCHAR(c) || c == '/'; }
+
+const char kStripParentheses[] = "()";
+const char kStripParenthesesAndComma[] = "(),";
+
+// Replace reserved characters with Unicode homoglyphs
+std::string Sanitize(const std::string& str, bool (*allowed)(int),
+                     const char* strip = nullptr) {
+  std::string clean;
+  for (auto c : str) {
+    if (allowed(c) && (!strip || !strchr(strip, c))) {
+      clean.push_back(c);
+    }
+  }
+  return clean;
+}
+
+base::Optional<std::string> Sanitize(base::Optional<std::string> str,
+                                     bool (*allowed)(int),
+                                     const char* strip = nullptr) {
+  std::string clean;
+  if (str) {
+    clean = Sanitize(str.value(), allowed, strip);
+  }
+  if (clean.empty()) {
+    return base::Optional<std::string>();
+  }
+  return base::Optional<std::string>(clean);
+}
+
+
+// Function that will query Starboard and populate a UserAgentPlatformInfo
+// object based on those results.  This is de-coupled from
+// CreateUserAgentString() so that the common logic in CreateUserAgentString()
+// can be easily unit tested.
+void InitializeUserAgentPlatformInfoFields(UserAgentPlatformInfo& info) {
+  info.set_starboard_version(
+      base::StringPrintf("Starboard/%d", SB_API_VERSION));
+
+  const size_t kSystemPropertyMaxLength = 1024;
+  char value[kSystemPropertyMaxLength];
+  bool result;
+
+  result = SbSystemGetProperty(kSbSystemPropertyPlatformName, value,
+                               kSystemPropertyMaxLength);
+  SB_DCHECK(result);
+  info.set_os_name_and_version(value);
+
+#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
+  // Because we add Cobalt's user agent string to Crashpad before we actually
+  // start Cobalt, the command line won't be initialized when we first try to
+  // get the user agent string.
+  if (base::CommandLine::InitializedForCurrentProcess()) {
+    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+    if (command_line->HasSwitch(switches::kUserAgentOsNameVersion)) {
+      info.set_os_name_and_version(
+          command_line->GetSwitchValueASCII(switches::kUserAgentOsNameVersion));
+    }
+  }
+#endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
+
+#if SB_API_VERSION >= 12
+  // System Integrator
+  result = SbSystemGetProperty(kSbSystemPropertySystemIntegratorName, value,
+                               kSystemPropertyMaxLength);
+#else
+  // Original Design Manufacturer (ODM)
+  result = SbSystemGetProperty(kSbSystemPropertyOriginalDesignManufacturerName,
+                               value, kSystemPropertyMaxLength);
+#endif
+  if (result) {
+    info.set_original_design_manufacturer(value);
+  }
+
+  info.set_javascript_engine_version(
+      script::GetJavaScriptEngineNameAndVersion());
+  info.set_rasterizer_type(
+      renderer::GetDefaultRasterizerForPlatform().rasterizer_name);
+
+// Evergreen version
+#if SB_IS(EVERGREEN)
+  info.set_evergreen_version(updater::GetCurrentEvergreenVersion());
+  if (!SbSystemGetExtension(kCobaltExtensionInstallationManagerName)) {
+    // If the installation manager is not initialized, the "evergreen_lite"
+    // command line parameter is specified and the system image is loaded.
+    info.set_evergreen_type("Lite");
+  } else {
+    info.set_evergreen_type("Full");
+  }
+#endif
+
+  info.set_cobalt_version(COBALT_VERSION);
+  info.set_cobalt_build_version_number(COBALT_BUILD_VERSION_NUMBER);
+
+#if defined(COBALT_BUILD_TYPE_DEBUG)
+  info.set_build_configuration("debug");
+#elif defined(COBALT_BUILD_TYPE_DEVEL)
+  info.set_build_configuration("devel");
+#elif defined(COBALT_BUILD_TYPE_QA)
+  info.set_build_configuration("qa");
+#elif defined(COBALT_BUILD_TYPE_GOLD)
+  info.set_build_configuration("gold");
+#else
+#error Unknown build configuration.
+#endif
+
+  result = SbSystemGetProperty(kSbSystemPropertyUserAgentAuxField, value,
+                               kSystemPropertyMaxLength);
+  if (result) {
+    info.set_aux_field(value);
+  }
+
+  // Fill platform info if it is a hardware TV device.
+  info.set_device_type(SbSystemGetDeviceType());
+
+  // Chipset model number
+  result = SbSystemGetProperty(kSbSystemPropertyChipsetModelNumber, value,
+                               kSystemPropertyMaxLength);
+  if (result) {
+    info.set_chipset_model_number(value);
+  }
+
+  // Model year
+  result = SbSystemGetProperty(kSbSystemPropertyModelYear, value,
+                               kSystemPropertyMaxLength);
+  if (result) {
+    info.set_model_year(value);
+  }
+
+  // Firmware version
+  result = SbSystemGetProperty(kSbSystemPropertyFirmwareVersion, value,
+                               kSystemPropertyMaxLength);
+  if (result) {
+    info.set_firmware_version(value);
+  }
+
+  // Brand
+  result = SbSystemGetProperty(kSbSystemPropertyBrandName, value,
+                               kSystemPropertyMaxLength);
+  if (result) {
+    info.set_brand(value);
+    // If we didn't get a value for the original design manufacturer, use the
+    // value for the brand if one is available.
+    if (!info.original_design_manufacturer() && info.brand()) {
+      info.set_original_design_manufacturer(info.brand());
     }
   }
 
-  return "";
+  // Model name
+  result = SbSystemGetProperty(kSbSystemPropertyModelName, value,
+                               kSystemPropertyMaxLength);
+  if (result) {
+    info.set_model(value);
+  }
+
+  // Connection type
+  info.set_connection_type(SbSystemGetConnectionType());
 }
 
 }  // namespace
 
-UserAgentPlatformInfo::UserAgentPlatformInfo() { InitializeFields(); }
+UserAgentPlatformInfo::UserAgentPlatformInfo() {
+  InitializeUserAgentPlatformInfoFields(*this);
+}
 
+void UserAgentPlatformInfo::set_starboard_version(
+    const std::string& starboard_version) {
+  starboard_version_ = Sanitize(starboard_version, isTCHARorForwardSlash);
+}
+void UserAgentPlatformInfo::set_os_name_and_version(
+    const std::string& os_name_and_version) {
+  os_name_and_version_ =
+      Sanitize(os_name_and_version, isVCHARorSpace, kStripParentheses);
+}
+void UserAgentPlatformInfo::set_original_design_manufacturer(
+    base::Optional<std::string> original_design_manufacturer) {
+  if (original_design_manufacturer) {
+    original_design_manufacturer_ =
+        Sanitize(original_design_manufacturer, isAsciiAlphaDigit);
+  }
+}
 void UserAgentPlatformInfo::set_device_type(SbSystemDeviceType device_type) {
   device_type_ = device_type;
   device_type_string_ = CreateDeviceTypeString(device_type_);
 }
 
+void UserAgentPlatformInfo::set_chipset_model_number(
+    base::Optional<std::string> chipset_model_number) {
+  if (chipset_model_number) {
+    chipset_model_number_ = Sanitize(chipset_model_number, isAsciiAlphaDigit);
+  }
+}
+
+void UserAgentPlatformInfo::set_model_year(
+    base::Optional<std::string> model_year) {
+  if (model_year) {
+    model_year_ = Sanitize(model_year, base::IsAsciiDigit);
+  }
+}
+
+void UserAgentPlatformInfo::set_firmware_version(
+    base::Optional<std::string> firmware_version) {
+  if (firmware_version) {
+    firmware_version_ = Sanitize(firmware_version, isTCHAR);
+  }
+}
+
+void UserAgentPlatformInfo::set_brand(base::Optional<std::string> brand) {
+  if (brand) {
+    brand_ = Sanitize(brand, isVCHARorSpace, kStripParenthesesAndComma);
+  }
+}
+
+void UserAgentPlatformInfo::set_model(base::Optional<std::string> model) {
+  if (model) {
+    model_ = Sanitize(model, isVCHARorSpace, kStripParenthesesAndComma);
+  }
+}
+
+void UserAgentPlatformInfo::set_aux_field(const std::string& aux_field) {
+  aux_field_ = Sanitize(aux_field, isTCHARorForwardSlash);
+}
+
 void UserAgentPlatformInfo::set_connection_type(
     base::Optional<SbSystemConnectionType> connection_type) {
   if (connection_type) {
@@ -95,110 +345,40 @@
   }
 }
 
-void UserAgentPlatformInfo::InitializeFields() {
-  starboard_version_ = base::StringPrintf("Starboard/%d", SB_API_VERSION);
+void UserAgentPlatformInfo::set_javascript_engine_version(
+    const std::string& javascript_engine_version) {
+  javascript_engine_version_ =
+      Sanitize(javascript_engine_version, isTCHARorForwardSlash);
+}
 
-  const size_t kSystemPropertyMaxLength = 1024;
-  char value[kSystemPropertyMaxLength];
-  bool result;
+void UserAgentPlatformInfo::set_rasterizer_type(
+    const std::string& rasterizer_type) {
+  rasterizer_type_ = Sanitize(rasterizer_type, isTCHARorForwardSlash);
+}
 
-  result = SbSystemGetProperty(kSbSystemPropertyPlatformName, value,
-                               kSystemPropertyMaxLength);
-  SB_DCHECK(result);
-  os_name_and_version_ = value;
+void UserAgentPlatformInfo::set_evergreen_type(
+    const std::string& evergreen_type) {
+  evergreen_type_ = Sanitize(evergreen_type, isTCHARorForwardSlash);
+}
 
-  // Fill platform info if it is a hardware TV device.
-  SbSystemDeviceType device_type = SbSystemGetDeviceType();
+void UserAgentPlatformInfo::set_evergreen_version(
+    const std::string& evergreen_version) {
+  evergreen_version_ = Sanitize(evergreen_version, isTCHAR);
+}
 
-#if SB_API_VERSION >= 12
-  // System Integrator
-  result = SbSystemGetProperty(kSbSystemPropertySystemIntegratorName, value,
-                               kSystemPropertyMaxLength);
-#else
-  // Original Design Manufacturer (ODM)
-  result = SbSystemGetProperty(kSbSystemPropertyOriginalDesignManufacturerName,
-                               value, kSystemPropertyMaxLength);
-#endif
-  if (result) {
-    original_design_manufacturer_ = value;
-  }
+void UserAgentPlatformInfo::set_cobalt_version(
+    const std::string& cobalt_version) {
+  cobalt_version_ = Sanitize(cobalt_version, isTCHAR);
+}
 
-  javascript_engine_version_ = script::GetJavaScriptEngineNameAndVersion();
+void UserAgentPlatformInfo::set_cobalt_build_version_number(
+    const std::string& cobalt_build_version_number) {
+  cobalt_build_version_number_ = Sanitize(cobalt_build_version_number, isTCHAR);
+}
 
-  rasterizer_type_ =
-      renderer::GetDefaultRasterizerForPlatform().rasterizer_name;
-
-// Evergreen version
-#if SB_IS(EVERGREEN)
-  evergreen_version_ = updater::GetCurrentEvergreenVersion();
-#endif
-
-  cobalt_version_ = COBALT_VERSION;
-  cobalt_build_version_number_ = COBALT_BUILD_VERSION_NUMBER;
-
-#if defined(COBALT_BUILD_TYPE_DEBUG)
-  build_configuration_ = "debug";
-#elif defined(COBALT_BUILD_TYPE_DEVEL)
-  build_configuration_ = "devel";
-#elif defined(COBALT_BUILD_TYPE_QA)
-  build_configuration_ = "qa";
-#elif defined(COBALT_BUILD_TYPE_GOLD)
-  build_configuration_ = "gold";
-#else
-#error Unknown build configuration.
-#endif
-
-  result = SbSystemGetProperty(kSbSystemPropertyUserAgentAuxField, value,
-                               kSystemPropertyMaxLength);
-  if (result) {
-    aux_field_ = value;
-  }
-
-  // Device Type
-  device_type_ = device_type;
-  device_type_string_ = CreateDeviceTypeString(device_type_);
-
-  // Chipset model number
-  result = SbSystemGetProperty(kSbSystemPropertyChipsetModelNumber, value,
-                               kSystemPropertyMaxLength);
-  if (result) {
-    chipset_model_number_ = value;
-  }
-
-  // Model year
-  result = SbSystemGetProperty(kSbSystemPropertyModelYear, value,
-                               kSystemPropertyMaxLength);
-  if (result) {
-    model_year_ = value;
-  }
-
-  // Firmware version
-  result = SbSystemGetProperty(kSbSystemPropertyFirmwareVersion, value,
-                               kSystemPropertyMaxLength);
-  if (result) {
-    firmware_version_ = value;
-  }
-
-  // Brand
-  result = SbSystemGetProperty(kSbSystemPropertyBrandName, value,
-                               kSystemPropertyMaxLength);
-  if (result) {
-    brand_ = value;
-  }
-
-  // Model name
-  result = SbSystemGetProperty(kSbSystemPropertyModelName, value,
-                               kSystemPropertyMaxLength);
-  if (result) {
-    model_ = value;
-  }
-
-  // Connection type
-  SbSystemConnectionType connection_type = SbSystemGetConnectionType();
-  if (connection_type != kSbSystemConnectionTypeUnknown) {
-    connection_type_ = connection_type;
-  }
-  connection_type_string_ = CreateConnectionTypeString(connection_type_);
+void UserAgentPlatformInfo::set_build_configuration(
+    const std::string& build_configuration) {
+  build_configuration_ = Sanitize(build_configuration, isTCHAR);
 }
 
 }  // namespace browser
diff --git a/src/cobalt/browser/user_agent_platform_info.h b/src/cobalt/browser/user_agent_platform_info.h
index 80d9798..db6c94d 100644
--- a/src/cobalt/browser/user_agent_platform_info.h
+++ b/src/cobalt/browser/user_agent_platform_info.h
@@ -66,6 +66,7 @@
   const std::string& rasterizer_type() const override {
     return rasterizer_type_;
   }
+  const std::string& evergreen_type() const override { return evergreen_type_; }
   const std::string& evergreen_version() const override {
     return evergreen_version_;
   }
@@ -77,78 +78,33 @@
     return build_configuration_;
   }
 
-  // Other: For unit testing cobalt::browser::CreateUserAgentString()
+  // Other: Setters that sanitize the strings where needed.
   //
-  void set_starboard_version(const std::string& starboard_version) {
-    starboard_version_ = starboard_version;
-  }
-  void set_os_name_and_version(const std::string& os_name_and_version) {
-    os_name_and_version_ = os_name_and_version;
-  }
+  void set_starboard_version(const std::string& starboard_version);
+  void set_os_name_and_version(const std::string& os_name_and_version);
   void set_original_design_manufacturer(
-      base::Optional<std::string> original_design_manufacturer) {
-    if (original_design_manufacturer) {
-      original_design_manufacturer_ = original_design_manufacturer;
-    }
-  }
+      base::Optional<std::string> original_design_manufacturer);
   void set_device_type(SbSystemDeviceType device_type);
   void set_chipset_model_number(
-      base::Optional<std::string> chipset_model_number) {
-    if (chipset_model_number) {
-      chipset_model_number_ = chipset_model_number;
-    }
-  }
-  void set_model_year(base::Optional<std::string> model_year) {
-    if (model_year) {
-      model_year_ = model_year;
-    }
-  }
-  void set_firmware_version(base::Optional<std::string> firmware_version) {
-    if (firmware_version) {
-      firmware_version_ = firmware_version;
-    }
-  }
-  void set_brand(base::Optional<std::string> brand) {
-    if (brand) {
-      brand_ = brand;
-    }
-  }
-  void set_model(base::Optional<std::string> model) {
-    if (model) {
-      model_ = model;
-    }
-  }
-  void set_aux_field(const std::string& aux_field) { aux_field_ = aux_field; }
+      base::Optional<std::string> chipset_model_number);
+  void set_model_year(base::Optional<std::string> model_year);
+  void set_firmware_version(base::Optional<std::string> firmware_version);
+  void set_brand(base::Optional<std::string> brand);
+  void set_model(base::Optional<std::string> model);
+  void set_aux_field(const std::string& aux_field);
   void set_connection_type(
       base::Optional<SbSystemConnectionType> connection_type);
   void set_javascript_engine_version(
-      const std::string& javascript_engine_version) {
-    javascript_engine_version_ = javascript_engine_version;
-  }
-  void set_rasterizer_type(const std::string& rasterizer_type) {
-    rasterizer_type_ = rasterizer_type;
-  }
-  void set_evergreen_version(const std::string& evergreen_version) {
-    evergreen_version_ = evergreen_version;
-  }
-  void set_cobalt_version(const std::string& cobalt_version) {
-    cobalt_version_ = cobalt_version;
-  }
+      const std::string& javascript_engine_version);
+  void set_rasterizer_type(const std::string& rasterizer_type);
+  void set_evergreen_type(const std::string& evergreen_type);
+  void set_evergreen_version(const std::string& evergreen_version);
+  void set_cobalt_version(const std::string& cobalt_version);
   void set_cobalt_build_version_number(
-      const std::string& cobalt_build_version_number) {
-    cobalt_build_version_number_ = cobalt_build_version_number;
-  }
-  void set_build_configuration(const std::string& build_configuration) {
-    build_configuration_ = build_configuration;
-  }
+      const std::string& cobalt_build_version_number);
+  void set_build_configuration(const std::string& build_configuration);
 
  private:
-  // Function that will query Starboard and populate a UserAgentPlatformInfo
-  // object based on those results.  This is de-coupled from
-  // CreateUserAgentString() so that the common logic in CreateUserAgentString()
-  // can be easily unit tested.
-  void InitializeFields();
-
   std::string starboard_version_;
   std::string os_name_and_version_;
   base::Optional<std::string> original_design_manufacturer_;
@@ -164,6 +120,7 @@
   std::string connection_type_string_;
   std::string javascript_engine_version_;
   std::string rasterizer_type_;
+  std::string evergreen_type_;
   std::string evergreen_version_;
 
   std::string cobalt_version_;
diff --git a/src/cobalt/browser/user_agent_string.cc b/src/cobalt/browser/user_agent_string.cc
index fde8edb..c048b18 100644
--- a/src/cobalt/browser/user_agent_string.cc
+++ b/src/cobalt/browser/user_agent_string.cc
@@ -17,42 +17,17 @@
 #include <vector>
 
 #include "base/command_line.h"
-#include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "cobalt/browser/switches.h"
-#if SB_IS(EVERGREEN)
-#include "cobalt/extension/installation_manager.h"
-#endif  // SB_IS(EVERGREEN)
 #include "starboard/common/log.h"
 #include "starboard/common/string.h"
 #include "starboard/system.h"
 
 namespace cobalt {
 namespace browser {
-
 namespace {
 
-struct SanitizeReplacements {
-  const char* replace_chars;
-  const char* replace_with;
-} kSanitizeReplacements[] = {
-    {",", u8"\uFF0C"},  // fullwidth comma
-    {"_", u8"\u2E0F"},  // paragraphos
-    {"/", u8"\u2215"},  // division slash
-    {"(", u8"\uFF08"},  // fullwidth left paren
-    {")", u8"\uFF09"},  // fullwidth right paren
-};
-
-// Replace reserved characters with Unicode homoglyphs
-std::string Sanitize(const std::string& str) {
-  std::string clean(str);
-  for (size_t i = 0; i < arraysize(kSanitizeReplacements); i++) {
-    const SanitizeReplacements* replacement = kSanitizeReplacements + i;
-    base::ReplaceChars(clean, replacement->replace_chars,
-                       replacement->replace_with, &clean);
-  }
-  return clean;
-}
+const char kUnknownFieldName[] = "Unknown";
 
 }  // namespace
 
@@ -72,24 +47,9 @@
   //   Starboard/APIVersion,
   //   Device/FirmwareVersion (Brand, Model, ConnectionType)
 
-  std::string os_name_and_version = platform_info.os_name_and_version();
-
-#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
-  // Because we add Cobalt's user agent string to Crashpad before we actually
-  // start Cobalt, the command line won't be initialized when we first try to
-  // get the user agent string.
-  if (base::CommandLine::InitializedForCurrentProcess()) {
-    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
-    if (command_line->HasSwitch(switches::kUserAgentOsNameVersion)) {
-      os_name_and_version =
-          command_line->GetSwitchValueASCII(switches::kUserAgentOsNameVersion);
-    }
-  }
-#endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
-
   //   Mozilla/5.0 (ChromiumStylePlatform)
-  std::string user_agent =
-      base::StringPrintf("Mozilla/5.0 (%s)", os_name_and_version.c_str());
+  std::string user_agent = base::StringPrintf(
+      "Mozilla/5.0 (%s)", platform_info.os_name_and_version().c_str());
 
   //   Cobalt/Version.BuildNumber-BuildConfiguration (unlike Gecko)
   base::StringAppendF(&user_agent, " Cobalt/%s.%s-%s (unlike Gecko)",
@@ -113,15 +73,11 @@
   if (!platform_info.evergreen_version().empty()) {
     base::StringAppendF(&user_agent, " Evergreen/%s",
                         platform_info.evergreen_version().c_str());
-#if SB_IS(EVERGREEN)
-    if (!SbSystemGetExtension(kCobaltExtensionInstallationManagerName)) {
-      // If the installation manager is not initialized, the "evergreen_lite"
-      // command line parameter is specified and the system image is loaded.
-      base::StringAppendF(&user_agent, " Evergreen-Lite");
-    } else {
-      base::StringAppendF(&user_agent, " Evergreen-Full");
-    }
-#endif  // SB_IS(EVERGREEN)
+  }
+  // Evergreen type
+  if (!platform_info.evergreen_type().empty()) {
+    base::StringAppendF(&user_agent, " Evergreen-%s",
+                        platform_info.evergreen_type().c_str());
   }
 
   // Starboard/APIVersion,
@@ -133,19 +89,19 @@
   // Device/FirmwareVersion (Brand, Model, ConnectionType)
   base::StringAppendF(
       &user_agent, ", %s_%s_%s_%s/%s (%s, %s, %s)",
-      Sanitize(platform_info.original_design_manufacturer().value_or(""))
+      platform_info.original_design_manufacturer()
+          .value_or(kUnknownFieldName)
           .c_str(),
       platform_info.device_type_string().c_str(),
-      Sanitize(platform_info.chipset_model_number().value_or("")).c_str(),
-      Sanitize(platform_info.model_year().value_or("")).c_str(),
-      Sanitize(platform_info.firmware_version().value_or("")).c_str(),
-      Sanitize(platform_info.brand().value_or("")).c_str(),
-      Sanitize(platform_info.model().value_or("")).c_str(),
+      platform_info.chipset_model_number().value_or(kUnknownFieldName).c_str(),
+      platform_info.model_year().value_or("0").c_str(),
+      platform_info.firmware_version().value_or(kUnknownFieldName).c_str(),
+      platform_info.brand().value_or(kUnknownFieldName).c_str(),
+      platform_info.model().value_or(kUnknownFieldName).c_str(),
       platform_info.connection_type_string().c_str());
 
   if (!platform_info.aux_field().empty()) {
-    user_agent.append(" ");
-    user_agent.append(platform_info.aux_field());
+    base::StringAppendF(&user_agent, " %s", platform_info.aux_field().c_str());
   }
   return user_agent;
 }
diff --git a/src/cobalt/browser/user_agent_string_test.cc b/src/cobalt/browser/user_agent_string_test.cc
index 042d180..d2537e7 100644
--- a/src/cobalt/browser/user_agent_string_test.cc
+++ b/src/cobalt/browser/user_agent_string_test.cc
@@ -79,13 +79,204 @@
   EXPECT_NE(std::string::npos, user_agent_string.find(tv_info_str));
 }
 
-// Look-alike replacements expected from sanitizing fields
+// Look-alike replacements previously used for sanitizing fields
 #define COMMA u8"\uFF0C"   // fullwidth comma
 #define UNDER u8"\u2E0F"   // paragraphos
 #define SLASH u8"\u2215"   // division slash
 #define LPAREN u8"\uFF08"  // fullwidth left paren
 #define RPAREN u8"\uFF09"  // fullwidth right paren
 
+#define ALPHALOWER "abcdefghijklmnopqrstuvwxyz"
+#define ALPHAUPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+#define ALPHA ALPHAUPPER ALPHALOWER
+#define DIGIT "0123456789"
+#define DIGITREVERSED "9876543210"
+#define ALPHADIGIT ALPHA DIGIT
+#define TCHAR ALPHADIGIT "!#$%&\'*+-.^_`|~"
+#define TCHARORSLASH TCHAR "/"
+#define VCHAR_EXCEPTALPHADIGIT "!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~"
+#define VCHAR_EXCEPTPARENTHESES "!\"#$%&\'*+,-./:;<=>?@[\\]^_`{|}~" ALPHADIGIT
+#define VCHAR_EXCEPTPARENTHESESANDCOMMA \
+  "!\"#$%&\'*+-./:;<=>?@[\\]^_`{|}~" ALPHADIGIT
+#define VCHAR VCHAR_EXCEPTALPHADIGIT ALPHADIGIT
+#define VCHARORSPACE " " VCHAR
+#define VCHARORSPACE_EXCEPTPARENTHESES " " VCHAR_EXCEPTPARENTHESES
+#define VCHARORSPACE_EXCEPTPARENTHESESANDCOMMA \
+  " " VCHAR_EXCEPTPARENTHESESANDCOMMA
+
+#define CONTROL                                                  \
+  "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" \
+  "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
+#define DEL "\x7F"
+#define HIGH_ASCII                                                   \
+  "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F" \
+  "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F" \
+  "\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF" \
+  "\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF" \
+  "\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF" \
+  "\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF" \
+  "\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF" \
+  "\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF"
+
+#define NOT_DIGIT CONTROL VCHAR_EXCEPTALPHADIGIT ALPHA DEL HIGH_ASCII
+#define NOT_ALPHADIGIT CONTROL VCHAR_EXCEPTALPHADIGIT DEL HIGH_ASCII
+#define NOT_TCHAR CONTROL "\"(),/:;<=>?@[\\]{}" DEL HIGH_ASCII
+#define NOT_TCHARORSLASH CONTROL "\"(),:;<=>?@[\\]{}" DEL HIGH_ASCII
+
+#define NOT_VCHARORSPACE CONTROL DEL HIGH_ASCII
+
+TEST(UserAgentStringFactoryTest, SanitizedStarboardVersion) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_starboard_version("Foo" NOT_TCHARORSLASH "Bar" TCHARORSLASH
+                                      "Baz" NOT_TCHARORSLASH "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos,
+            user_agent_string.find("FooBar" TCHARORSLASH "BazQux"));
+}
+
+TEST(UserAgentStringFactoryTest, SanitizedOsNameAndVersion) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_os_name_and_version("Foo()" NOT_VCHARORSPACE
+                                        "Bar" VCHARORSPACE_EXCEPTPARENTHESES
+                                        "Baz()" NOT_VCHARORSPACE "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(
+      std::string::npos,
+      user_agent_string.find("FooBar" VCHARORSPACE_EXCEPTPARENTHESES "BazQux"));
+}
+
+TEST(UserAgentStringFactoryTest, SanitizedOriginalDesignManufacturer) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_original_design_manufacturer(
+      "Foo" NOT_ALPHADIGIT "Bar" ALPHADIGIT "Baz" NOT_ALPHADIGIT "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos,
+            user_agent_string.find("FooBar" ALPHADIGIT "BazQux"));
+}
+
+TEST(UserAgentStringFactoryTest, SanitizedChipsetModelNumber) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_chipset_model_number("Foo" NOT_ALPHADIGIT "Bar" ALPHADIGIT
+                                         "Baz" NOT_ALPHADIGIT "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos,
+            user_agent_string.find("FooBar" ALPHADIGIT "BazQux"));
+}
+
+TEST(UserAgentStringFactoryTest, SanitizedModelYear) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_chipset_model_number("FooBar");
+  platform_info.set_model_year(NOT_DIGIT DIGIT NOT_DIGIT DIGITREVERSED);
+  platform_info.set_firmware_version("BazQux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos,
+            user_agent_string.find("FooBar_" DIGIT DIGITREVERSED "/BazQux"));
+}
+
+TEST(UserAgentStringFactoryTest, SanitizedFirmwareVersion) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_firmware_version("Foo" NOT_TCHAR "Bar" TCHAR "Baz" NOT_TCHAR
+                                     "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos, user_agent_string.find("FooBar" TCHAR "BazQux"));
+}
+
+TEST(UserAgentStringFactoryTest, SanitizedBrand) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_brand("Foo()," NOT_VCHARORSPACE
+                          "Bar" VCHARORSPACE_EXCEPTPARENTHESESANDCOMMA
+                          "Baz()," NOT_VCHARORSPACE "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos,
+            user_agent_string.find(
+                "FooBar" VCHARORSPACE_EXCEPTPARENTHESESANDCOMMA "BazQux"));
+}
+
+TEST(UserAgentStringFactoryTest, SanitizedModel) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_model("Foo()," NOT_VCHARORSPACE
+                          "Bar" VCHARORSPACE_EXCEPTPARENTHESESANDCOMMA
+                          "Baz()," NOT_VCHARORSPACE "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos,
+            user_agent_string.find(
+                "FooBar" VCHARORSPACE_EXCEPTPARENTHESESANDCOMMA "BazQux"));
+}
+
+TEST(UserAgentStringFactoryTest, SanitizedAuxField) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_aux_field("Foo" NOT_TCHARORSLASH "Bar" TCHARORSLASH
+                              "Baz" NOT_TCHARORSLASH "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos,
+            user_agent_string.find("FooBar" TCHARORSLASH "BazQux"));
+}
+
+TEST(UserAgentStringFactoryTest, SanitizedJavascriptEngineVersion) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_javascript_engine_version(
+      "Foo" NOT_TCHARORSLASH "Bar" TCHARORSLASH "Baz" NOT_TCHARORSLASH "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos,
+            user_agent_string.find("FooBar" TCHARORSLASH "BazQux"));
+}
+
+TEST(UserAgentStringFactoryTest, SanitizedRasterizerType) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_rasterizer_type("Foo" NOT_TCHARORSLASH "Bar" TCHARORSLASH
+                                    "Baz" NOT_TCHARORSLASH "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos,
+            user_agent_string.find("FooBar" TCHARORSLASH "BazQux"));
+}
+
+TEST(UserAgentStringFactoryTest, SanitizedEvergreenType) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_evergreen_version("Foo" NOT_TCHAR "Bar" TCHAR
+                                      "Baz" NOT_TCHAR "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos, user_agent_string.find("FooBar" TCHAR "BazQux"));
+}
+
+TEST(UserAgentStringFactoryTest, SanitizedCobaltVersion) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_cobalt_version("Foo" NOT_TCHAR "Bar" TCHAR "Baz" NOT_TCHAR
+                                   "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos, user_agent_string.find("FooBar" TCHAR "BazQux"));
+}
+
+TEST(UserAgentStringFactoryTest, SanitizedCobaltBuildVersionNumber) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_cobalt_build_version_number("Foo" NOT_TCHAR "Bar" TCHAR
+                                                "Baz" NOT_TCHAR "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos, user_agent_string.find("FooBar" TCHAR "BazQux"));
+}
+
+TEST(UserAgentStringFactoryTest, SanitizedCobaltBuildConfiguration) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_build_configuration("Foo" NOT_TCHAR "Bar" TCHAR
+                                        "Baz" NOT_TCHAR "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos, user_agent_string.find("FooBar" TCHAR "BazQux"));
+}
+
 TEST(UserAgentStringFactoryTest, WithPlatformInfo) {
   // There are deliberately a variety of underscores, commas, slashes, and
   // parentheses in the strings below to ensure they get sanitized.
@@ -101,13 +292,8 @@
   std::string user_agent_string = CreateUserAgentString(platform_info);
 
   const char* tv_info_str =
-      "Aperture" UNDER "Science" UNDER
-      "Innovators"
-      "_OTT_"
-      "P-body" SLASH "Orange" UNDER "Atlas" SLASH
-      "Blue"
-      "_2013"
-      "/0" COMMA "01 (Aperture Science " LPAREN "Labs" RPAREN ", GLaDOS, )";
+      "ApertureScienceInnovators_OTT_PbodyOrangeAtlasBlue_2013/001 "
+      "(Aperture Science Labs, GLaDOS, )";
   EXPECT_NE(std::string::npos, user_agent_string.find(tv_info_str));
 }
 
@@ -118,7 +304,7 @@
   platform_info.set_device_type(kSbSystemDeviceTypeOverTheTopBox);
   std::string user_agent_string = CreateUserAgentString(platform_info);
 
-  EXPECT_NE(std::string::npos, user_agent_string.find("Wired"));
+  EXPECT_NE(std::string::npos, user_agent_string.find(", Wired)"));
 }
 
 TEST(UserAgentStringFactoryTest, WithWirelessConnection) {
@@ -128,7 +314,7 @@
   platform_info.set_device_type(kSbSystemDeviceTypeOverTheTopBox);
   std::string user_agent_string = CreateUserAgentString(platform_info);
 
-  EXPECT_NE(std::string::npos, user_agent_string.find("Wireless"));
+  EXPECT_NE(std::string::npos, user_agent_string.find(", Wireless)"));
 }
 
 TEST(UserAgentStringFactoryTest, WithOnlyBrandModelAndDeviceType) {
@@ -139,7 +325,8 @@
   platform_info.set_model("GLaDOS");
   std::string user_agent_string = CreateUserAgentString(platform_info);
 
-  const char* tv_info_str = ", _OTT__/ (Aperture Science, GLaDOS, )";
+  const char* tv_info_str =
+      ", Unknown_OTT_Unknown_0/Unknown (Aperture Science, GLaDOS, )";
   EXPECT_NE(std::string::npos, user_agent_string.find(tv_info_str));
 }
 
@@ -149,7 +336,8 @@
   platform_info.set_device_type(kSbSystemDeviceTypeOverTheTopBox);
   std::string user_agent_string = CreateUserAgentString(platform_info);
 
-  const char* tv_info_str = "Starboard/6, _OTT__/ (, , )";
+  const char* tv_info_str =
+      "Starboard/6, Unknown_OTT_Unknown_0/Unknown (Unknown, Unknown, )";
   EXPECT_NE(std::string::npos, user_agent_string.find(tv_info_str));
 }
 
@@ -158,7 +346,7 @@
   platform_info.set_javascript_engine_version("V8/6.5.254.28");
   std::string user_agent_string = CreateUserAgentString(platform_info);
 
-  EXPECT_NE(std::string::npos, user_agent_string.find("V8/6.5.254.28"));
+  EXPECT_NE(std::string::npos, user_agent_string.find(" V8/6.5.254.28"));
 }
 
 }  // namespace
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index f4c2294..606938d 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -263,6 +263,7 @@
 
   void SetApplicationStartOrPreloadTimestamp(
       bool is_preload, SbTimeMonotonic timestamp);
+  void SetDeepLinkTimestamp(SbTimeMonotonic timestamp);
 
  private:
   class DocumentLoadedObserver;
@@ -1034,6 +1035,11 @@
       is_preload, timestamp);
 }
 
+void WebModule::Impl::SetDeepLinkTimestamp(SbTimeMonotonic timestamp) {
+  DCHECK(window_);
+  window_->performance()->SetDeepLinkTimestamp(timestamp);
+}
+
 void WebModule::Impl::OnCspPolicyChanged() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(is_running_);
@@ -1836,5 +1842,20 @@
   }
 }
 
+void WebModule::SetDeepLinkTimestamp(SbTimeMonotonic timestamp) {
+  TRACE_EVENT0("cobalt::browser",
+               "WebModule::SetDeepLinkTimestamp()");
+  DCHECK(message_loop());
+  DCHECK(impl_);
+  if (base::MessageLoop::current() != message_loop()) {
+    message_loop()->task_runner()->PostBlockingTask(
+      FROM_HERE,
+      base::Bind(&WebModule::Impl::SetDeepLinkTimestamp,
+                 base::Unretained(impl_.get()), timestamp));
+  } else {
+    impl_->SetDeepLinkTimestamp(timestamp);
+  }
+}
+
 }  // namespace browser
 }  // namespace cobalt
diff --git a/src/cobalt/browser/web_module.h b/src/cobalt/browser/web_module.h
index d3350b3..16c830d 100644
--- a/src/cobalt/browser/web_module.h
+++ b/src/cobalt/browser/web_module.h
@@ -411,6 +411,7 @@
 
   void SetApplicationStartOrPreloadTimestamp(bool is_preload,
                                              SbTimeMonotonic timestamp);
+  void SetDeepLinkTimestamp(SbTimeMonotonic timestamp);
  private:
   // Data required to construct a WebModule, initialized in the constructor and
   // passed to |Initialize|.
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 912bb2b..0c55a1f 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-303120
\ No newline at end of file
+303689
\ No newline at end of file
diff --git a/src/cobalt/content/fonts/BUILD.gn b/src/cobalt/content/fonts/BUILD.gn
new file mode 100644
index 0000000..87b11ff
--- /dev/null
+++ b/src/cobalt/content/fonts/BUILD.gn
@@ -0,0 +1,64 @@
+# Copyright 2021 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.
+
+import("//cobalt/content/fonts/variables.gni")
+
+if (cobalt_font_package == "empty") {
+  copy("copy_font_data") {
+    sources = [ "$source_font_config_dir/fonts.xml" ]
+    outputs =
+        [ "$sb_static_contents_output_data_dir/fonts/{{source_file_part}}" ]
+  }
+} else {
+  action("fonts_xml") {
+    script = "scripts/filter_fonts.py"
+    font_xml = "$source_font_config_dir/fonts.xml"
+    sources = [ font_xml ]
+    outputs = [ "$sb_static_contents_output_data_dir/fonts/fonts.xml" ]
+    args = [
+             "-i",
+             rebase_path(font_xml, root_build_dir),
+             "-o",
+             outputs[0],
+           ] + package_categories
+  }
+
+  copy("copy_fonts") {
+    if (copy_font_files) {
+      fonts = exec_script("scripts/filter_fonts.py",
+                          [
+                                "-i",
+                                rebase_path("$source_font_config_dir/fonts.xml",
+                                            root_build_dir),
+                                "-f",
+                                source_font_files_dir,
+                              ] + package_categories,
+                          "trim list lines",
+                          [ "$source_font_config_dir/fonts.xml" ])
+    } else {
+      # Copy at least the fallback Roboto Subsetted font.
+      fonts = [ "$source_font_files_dir/Roboto-Regular-Subsetted.woff2" ]
+    }
+    sources = fonts
+    outputs =
+        [ "$sb_static_contents_output_data_dir/fonts/{{source_file_part}}" ]
+  }
+
+  group("copy_font_data") {
+    data_deps = [
+      ":copy_fonts",
+      ":fonts_xml",
+    ]
+  }
+}
diff --git a/src/cobalt/content/fonts/scripts/filter_fonts.py b/src/cobalt/content/fonts/scripts/filter_fonts.py
index bf332da..74486da 100755
--- a/src/cobalt/content/fonts/scripts/filter_fonts.py
+++ b/src/cobalt/content/fonts/scripts/filter_fonts.py
@@ -12,7 +12,6 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
 """Filters fonts.xml to include only desired font packages.
 
 This is meant to be used in a GYP file to generate the list of fonts as the
@@ -26,7 +25,6 @@
 from xml.dom import Node
 from xml.dom.minidom import parse as parse_xml
 
-
 NORMAL_WEIGHT = 400
 BOLD_WEIGHT = 700
 
@@ -77,8 +75,8 @@
   elif category == '2':
     return weight in (NORMAL_WEIGHT, BOLD_WEIGHT) and style == NORMAL_STYLE
   elif category == '3':
-    return (weight in (NORMAL_WEIGHT, BOLD_WEIGHT)
-            and style in (NORMAL_STYLE, ITALIC_STYLE))
+    return (weight in (NORMAL_WEIGHT, BOLD_WEIGHT) and
+            style in (NORMAL_STYLE, ITALIC_STYLE))
   elif category == '4':
     return True
   else:
@@ -161,13 +159,18 @@
     If --fonts_dir is specified, a newline-separated list of font files.
   """
   parser = argparse.ArgumentParser()
-  parser.add_argument('-i', '--input_xml', required=True,
-                      help='path to fonts.xml to be filtered')
-  parser.add_argument('-o', '--output_xml',
-                      help='path to write a filtered XML file')
-  parser.add_argument('-f', '--fonts_dir',
-                      help='prints a list of font files prefixed by the '
-                           'specified FONTS_DIR (for GYP inputs)')
+  parser.add_argument(
+      '-i',
+      '--input_xml',
+      required=True,
+      help='path to fonts.xml to be filtered')
+  parser.add_argument(
+      '-o', '--output_xml', help='path to write a filtered XML file')
+  parser.add_argument(
+      '-f',
+      '--fonts_dir',
+      help='prints a list of font files prefixed by the '
+      'specified FONTS_DIR (for GYP inputs)')
   parser.add_argument('package_categories', nargs=argparse.REMAINDER)
   options = parser.parse_args(argv)
 
@@ -176,8 +179,8 @@
 
   # Make a dictionary mapping package name to category.
   # E.g. ['sans-serif=1', 'serif=2'] becomes {'sans-serif':'1', 'serif':'2'}
-  package_categories = dict(pkg.split('=')
-                            for pkg in options.package_categories)
+  package_categories = dict(
+      pkg.split('=') for pkg in options.package_categories)
 
   fonts_doc = parse_xml(options.input_xml)
 
@@ -194,15 +197,16 @@
     # Join with '/' rather than os.path.join() because this is for GYP, which
     # even on Windows wants slashes rather than backslashes.
     # Make a set for unique fonts since .ttc files may be listed more than once.
-    result = ['/'.join((options.fonts_dir, font))
-              for font in sorted(set(kept_fonts))]
+    result = [
+        '/'.join((options.fonts_dir, font)) for font in sorted(set(kept_fonts))
+    ]
     return '\n'.join(result)
 
 
 def main(argv):
   result = DoMain(argv[1:])
   if result:
-    print result
+    print(result)
   return 0
 
 
diff --git a/src/cobalt/content/fonts/variables.gni b/src/cobalt/content/fonts/variables.gni
new file mode 100644
index 0000000..8e16bd8
--- /dev/null
+++ b/src/cobalt/content/fonts/variables.gni
@@ -0,0 +1,158 @@
+# Copyright 2021 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.
+
+declare_args() {
+  # See content/fonts/README.md for details on the specific values used for the
+  # variables below.
+  cobalt_font_package = "standard"
+  cobalt_font_package_override_named_sans_serif = -1
+  cobalt_font_package_override_named_serif = -1
+  cobalt_font_package_override_named_fcc_fonts = -1
+  cobalt_font_package_override_fallback_lang_non_cjk = -1
+  cobalt_font_package_override_fallback_lang_cjk = -1
+  cobalt_font_package_override_fallback_lang_cjk_low_quality = -1
+  cobalt_font_package_override_fallback_historic = -1
+  cobalt_font_package_override_fallback_color_emoji = -1
+  cobalt_font_package_override_fallback_emoji = -1
+  cobalt_font_package_override_fallback_symbols = -1
+
+  source_font_files_dir = "font_files"
+  copy_font_files = true
+}
+
+if (cobalt_font_package == "standard") {
+  declare_args() {
+    source_font_config_dir = "config/common"
+    package_named_sans_serif = 4
+    package_named_serif = 3
+    package_named_fcc_fonts = 2
+    package_fallback_lang_non_cjk = 2
+    package_fallback_lang_cjk = 1
+    package_fallback_lang_cjk_low_quality = 0
+    package_fallback_historic = 1
+    package_fallback_color_emoji = 1
+    package_fallback_emoji = 0
+    package_fallback_symbols = 1
+  }
+} else if (cobalt_font_package == "limited") {
+  declare_args() {
+    source_font_config_dir = "config/common"
+    package_named_sans_serif = 2
+    package_named_serif = 0
+    package_named_fcc_fonts = 0
+    package_fallback_lang_non_cjk = 1
+    package_fallback_lang_cjk = 0
+    package_fallback_lang_cjk_low_quality = 1
+    package_fallback_historic = 0
+    package_fallback_color_emoji = 0
+    package_fallback_emoji = 1
+    package_fallback_symbols = 1
+  }
+} else if (cobalt_font_package == "minimal") {
+  declare_args() {
+    source_font_config_dir = "config/common"
+    package_named_sans_serif = 0
+    package_named_serif = 0
+    package_named_fcc_fonts = 0
+    package_fallback_lang_non_cjk = 0
+    package_fallback_lang_cjk = 0
+    package_fallback_lang_cjk_low_quality = 0
+    package_fallback_historic = 0
+    package_fallback_color_emoji = 0
+    package_fallback_emoji = 0
+    package_fallback_symbols = 0
+  }
+} else if (cobalt_font_package == "empty") {
+  declare_args() {
+    source_font_config_dir = "config/empty"
+    package_named_sans_serif = 0
+    package_named_serif = 0
+    package_named_fcc_fonts = 0
+    package_fallback_lang_non_cjk = 0
+    package_fallback_lang_cjk = 0
+    package_fallback_lang_cjk_low_quality = 0
+    package_fallback_historic = 0
+    package_fallback_color_emoji = 0
+    package_fallback_emoji = 0
+    package_fallback_symbols = 0
+  }
+} else if (cobalt_font_package == "android_system") {
+  # fonts.xml contains a superset of what we expect to find on Android
+  # devices. The Android SbFile implementation falls back to system font
+  # files for those not in cobalt content.
+  declare_args() {
+    source_font_config_dir = "config/android"
+    package_named_sans_serif = 0
+    package_named_serif = 0
+    package_named_fcc_fonts = 0
+    package_fallback_lang_non_cjk = 0
+    package_fallback_lang_cjk = 0
+    package_fallback_lang_cjk_low_quality = 0
+    package_fallback_historic = 0
+    package_fallback_color_emoji = 0
+    package_fallback_emoji = 0
+    package_fallback_symbols = 0
+  }
+
+  # Don't copy font files for Android since it falls back to system
+  # font files for everything listed in fonts.xml.
+  copy_font_files = false
+}
+
+if (cobalt_font_package_override_named_sans_serif >= 0) {
+  package_named_sans_serif = cobalt_font_package_override_named_sans_serif
+}
+if (cobalt_font_package_override_named_serif >= 0) {
+  package_named_serif = cobalt_font_package_override_named_serif
+}
+if (cobalt_font_package_override_named_fcc_fonts >= 0) {
+  package_named_fcc_fonts = cobalt_font_package_override_named_fcc_fonts
+}
+if (cobalt_font_package_override_fallback_lang_non_cjk >= 0) {
+  package_fallback_lang_non_cjk =
+      cobalt_font_package_override_fallback_lang_non_cjk
+}
+if (cobalt_font_package_override_fallback_lang_cjk >= 0) {
+  package_fallback_lang_cjk = cobalt_font_package_override_fallback_lang_cjk
+}
+if (cobalt_font_package_override_fallback_lang_cjk_low_quality >= 0) {
+  package_fallback_lang_cjk_low_quality =
+      cobalt_font_package_override_fallback_lang_cjk_low_quality
+}
+if (cobalt_font_package_override_fallback_historic >= 0) {
+  package_fallback_historic = cobalt_font_package_override_fallback_historic
+}
+if (cobalt_font_package_override_fallback_color_emoji >= 0) {
+  package_fallback_color_emoji =
+      cobalt_font_package_override_fallback_color_emoji
+}
+if (cobalt_font_package_override_fallback_emoji >= 0) {
+  package_fallback_emoji = cobalt_font_package_override_fallback_emoji
+}
+if (cobalt_font_package_override_fallback_symbols >= 0) {
+  package_fallback_symbols = cobalt_font_package_override_fallback_symbols
+}
+
+package_categories = [
+  "sans-serif=${package_named_sans_serif}",
+  "serif=${package_named_serif}",
+  "fcc-captions=${package_named_fcc_fonts}",
+  "fallback-lang-non-cjk=${package_fallback_lang_non_cjk}",
+  "fallback-lang-cjk=${package_fallback_lang_cjk}",
+  "fallback-lang-cjk-low-quality=${package_fallback_lang_cjk_low_quality}",
+  "fallback-historic=${package_fallback_historic}",
+  "fallback-color-emoji=${package_fallback_color_emoji}",
+  "fallback-emoji=${package_fallback_emoji}",
+  "fallback-symbols=${package_fallback_symbols}",
+]
diff --git a/src/cobalt/debug/remote/devtools/copy_devtools_modules.rsp b/src/cobalt/debug/remote/devtools/copy_devtools_modules.rsp
new file mode 100644
index 0000000..39ad8b2
--- /dev/null
+++ b/src/cobalt/debug/remote/devtools/copy_devtools_modules.rsp
@@ -0,0 +1,476 @@
+front_end/network/network.js
+front_end/network/SignedExchangeInfoView.js
+front_end/network/ResourceWebSocketFrameView.js
+front_end/network/RequestTimingView.js
+front_end/network/RequestResponseView.js
+front_end/network/RequestPreviewView.js
+front_end/network/RequestInitiatorView.js
+front_end/network/RequestHeadersView.js
+front_end/network/RequestHTMLView.js
+front_end/network/RequestCookiesView.js
+front_end/network/NetworkWaterfallColumn.js
+front_end/network/NetworkTimeCalculator.js
+front_end/network/NetworkSearchScope.js
+front_end/network/NetworkPanel.js
+front_end/network/NetworkOverview.js
+front_end/network/NetworkManageCustomHeadersView.js
+front_end/network/NetworkLogViewColumns.js
+front_end/network/NetworkLogView.js
+front_end/network/NetworkItemView.js
+front_end/network/NetworkFrameGrouper.js
+front_end/network/NetworkDataGridNode.js
+front_end/network/NetworkConfigView.js
+front_end/network/HARWriter.js
+front_end/network/EventSourceMessagesView.js
+front_end/network/BlockedURLsPane.js
+front_end/network/BinaryResourceView.js
+front_end/test_runner/test_runner.js
+front_end/test_runner/TestRunner.js
+front_end/emulation/emulation.js
+front_end/emulation/SensorsView.js
+front_end/emulation/MediaQueryInspector.js
+front_end/emulation/InspectedPagePlaceholder.js
+front_end/emulation/GeolocationsSettingsTab.js
+front_end/emulation/EmulatedDevices.js
+front_end/emulation/DevicesSettingsTab.js
+front_end/emulation/DeviceModeWrapper.js
+front_end/emulation/DeviceModeView.js
+front_end/emulation/DeviceModeToolbar.js
+front_end/emulation/DeviceModeModel.js
+front_end/emulation/AdvancedApp.js
+front_end/inspector_main/inspector_main.js
+front_end/inspector_main/RenderingOptions.js
+front_end/inspector_main/InspectorMain.js
+front_end/js_main/js_main.js
+front_end/js_main/JsMain.js
+front_end/search/search.js
+front_end/search/SearchView.js
+front_end/search/SearchResultsPane.js
+front_end/search/SearchConfig.js
+front_end/screencast/screencast.js
+front_end/screencast/ScreencastView.js
+front_end/screencast/ScreencastApp.js
+front_end/screencast/InputModel.js
+front_end/performance_monitor/performance_monitor.js
+front_end/performance_monitor/PerformanceMonitor.js
+front_end/main/main.js
+front_end/main/SimpleApp.js
+front_end/main/MainImpl.js
+front_end/main/ExecutionContextSelector.js
+front_end/snippets/snippets.js
+front_end/snippets/SnippetsQuickOpen.js
+front_end/snippets/ScriptSnippetFileSystem.js
+front_end/settings/settings.js
+front_end/settings/SettingsScreen.js
+front_end/settings/FrameworkBlackboxSettingsTab.js
+front_end/security/security.js
+front_end/security/SecurityPanel.js
+front_end/security/SecurityModel.js
+front_end/javascript_metadata/javascript_metadata.js
+front_end/javascript_metadata/NativeFunctions.js
+front_end/javascript_metadata/JavaScriptMetadata.js
+front_end/har_importer/har_importer.js
+front_end/har_importer/HARImporter.js
+front_end/har_importer/HARFormat.js
+front_end/browser_debugger/browser_debugger.js
+front_end/browser_debugger/XHRBreakpointsSidebarPane.js
+front_end/browser_debugger/ObjectEventListenersSidebarPane.js
+front_end/browser_debugger/EventListenerBreakpointsSidebarPane.js
+front_end/browser_debugger/DOMBreakpointsSidebarPane.js
+front_end/layer_viewer/layer_viewer.js
+front_end/layer_viewer/TransformController.js
+front_end/layer_viewer/PaintProfilerView.js
+front_end/layer_viewer/Layers3DView.js
+front_end/layer_viewer/LayerViewHost.js
+front_end/layer_viewer/LayerTreeOutline.js
+front_end/layer_viewer/LayerDetailsView.js
+front_end/cm_web_modes/cm_web_modes.js
+front_end/cm_web_modes/cm_web_modes_cm.js
+front_end/cm_web_modes/cm_web_modes_headless.js
+front_end/cm_web_modes/css.js
+front_end/cm_web_modes/javascript.js
+front_end/cm_web_modes/xml.js
+front_end/cm_web_modes/htmlmixed.js
+front_end/cm_web_modes/htmlembedded.js
+front_end/text_editor/text_editor.js
+front_end/text_editor/TextEditorAutocompleteController.js
+front_end/text_editor/CodeMirrorUtils.js
+front_end/text_editor/CodeMirrorTextEditor.js
+front_end/quick_open/quick_open.js
+front_end/quick_open/QuickOpen.js
+front_end/quick_open/HelpQuickOpen.js
+front_end/quick_open/FilteredListWidget.js
+front_end/quick_open/CommandMenu.js
+front_end/elements/elements.js
+front_end/elements/elements-legacy.js
+front_end/elements/StylesSidebarPane.js
+front_end/elements/StylePropertyTreeElement.js
+front_end/elements/StylePropertyHighlighter.js
+front_end/elements/PropertiesWidget.js
+front_end/elements/PlatformFontsWidget.js
+front_end/elements/NodeStackTraceWidget.js
+front_end/elements/MetricsSidebarPane.js
+front_end/elements/MarkerDecorator.js
+front_end/elements/InspectElementModeController.js
+front_end/elements/EventListenersWidget.js
+front_end/elements/ElementsTreeOutline.js
+front_end/elements/ElementsTreeElement.js
+front_end/elements/ElementsTreeElementHighlighter.js
+front_end/elements/ElementStatePaneWidget.js
+front_end/elements/ElementsSidebarPane.js
+front_end/elements/ElementsPanel.js
+front_end/elements/ElementsBreadcrumbs.js
+front_end/elements/DOMPath.js
+front_end/elements/DOMLinkifier.js
+front_end/elements/ComputedStyleWidget.js
+front_end/elements/ComputedStyleModel.js
+front_end/elements/ColorSwatchPopoverIcon.js
+front_end/elements/ClassesPaneWidget.js
+front_end/timeline_model/timeline_model.js
+front_end/timeline_model/TracingLayerTree.js
+front_end/timeline_model/TimelineProfileTree.js
+front_end/timeline_model/TimelineModel.js
+front_end/timeline_model/TimelineModelFilter.js
+front_end/timeline_model/TimelineJSProfile.js
+front_end/timeline_model/TimelineIRModel.js
+front_end/timeline_model/TimelineFrameModel.js
+front_end/help/help.js
+front_end/help/ReleaseNoteView.js
+front_end/help/ReleaseNoteText.js
+front_end/help/HelpImpl.js
+front_end/workspace_diff/workspace_diff.js
+front_end/workspace_diff/WorkspaceDiff.js
+front_end/mobile_throttling/mobile_throttling.js
+front_end/mobile_throttling/ThrottlingSettingsTab.js
+front_end/mobile_throttling/ThrottlingPresets.js
+front_end/mobile_throttling/ThrottlingManager.js
+front_end/mobile_throttling/NetworkThrottlingSelector.js
+front_end/mobile_throttling/NetworkPanelIndicator.js
+front_end/mobile_throttling/MobileThrottlingSelector.js
+front_end/event_listeners/event_listeners.js
+front_end/event_listeners/EventListenersView.js
+front_end/event_listeners/EventListenersUtils.js
+front_end/object_ui/object_ui.js
+front_end/object_ui/RemoteObjectPreviewFormatter.js
+front_end/object_ui/ObjectPropertiesSection.js
+front_end/object_ui/ObjectPopoverHelper.js
+front_end/object_ui/JavaScriptREPL.js
+front_end/object_ui/JavaScriptAutocomplete.js
+front_end/object_ui/CustomPreviewComponent.js
+front_end/cookie_table/cookie_table.js
+front_end/cookie_table/CookiesTable.js
+front_end/cm_modes/cm_modes.js
+front_end/cm_modes/DefaultCodeMirrorMimeMode.js
+front_end/cm_modes/clike.js
+front_end/cm_modes/coffeescript.js
+front_end/cm_modes/php.js
+front_end/cm_modes/python.js
+front_end/cm_modes/shell.js
+front_end/cm_modes/livescript.js
+front_end/cm_modes/markdown.js
+front_end/cm_modes/clojure.js
+front_end/cm_modes/jsx.js
+front_end/css_overview/css_overview.js
+front_end/css_overview/CSSOverviewUnusedDeclarations.js
+front_end/css_overview/CSSOverviewStartView.js
+front_end/css_overview/CSSOverviewSidebarPanel.js
+front_end/css_overview/CSSOverviewProcessingView.js
+front_end/css_overview/CSSOverviewPanel.js
+front_end/css_overview/CSSOverviewModel.js
+front_end/css_overview/CSSOverviewController.js
+front_end/css_overview/CSSOverviewCompletedView.js
+front_end/console/console.js
+front_end/console/ConsoleContextSelector.js
+front_end/console/ConsoleFilter.js
+front_end/console/ConsoleSidebar.js
+front_end/console/ConsolePanel.js
+front_end/console/ConsolePinPane.js
+front_end/console/ConsolePrompt.js
+front_end/console/ConsoleView.js
+front_end/console/ConsoleViewMessage.js
+front_end/console/ConsoleViewport.js
+front_end/source_frame/source_frame.js
+front_end/source_frame/XMLView.js
+front_end/source_frame/SourcesTextEditor.js
+front_end/source_frame/SourceFrame.js
+front_end/source_frame/source_frame.js
+front_end/source_frame/SourceCodeDiff.js
+front_end/source_frame/ResourceSourceFrame.js
+front_end/source_frame/PreviewFactory.js
+front_end/source_frame/JSONView.js
+front_end/source_frame/ImageView.js
+front_end/source_frame/FontView.js
+front_end/source_frame/BinaryResourceViewFactory.js
+front_end/inline_editor/inline_editor.js
+front_end/inline_editor/SwatchPopoverHelper.js
+front_end/inline_editor/CSSShadowModel.js
+front_end/inline_editor/CSSShadowEditor.js
+front_end/inline_editor/ColorSwatch.js
+front_end/inline_editor/BezierUI.js
+front_end/inline_editor/BezierEditor.js
+front_end/diff/diff.js
+front_end/diff/diff_match_patch.js
+front_end/diff/DiffWrapper.js
+front_end/formatter/formatter.js
+front_end/formatter/ScriptFormatter.js
+front_end/formatter/FormatterWorkerPool.js
+front_end/color_picker/color_picker.js
+front_end/color_picker/Spectrum.js
+front_end/color_picker/ContrastOverlay.js
+front_end/color_picker/ContrastInfo.js
+front_end/color_picker/ContrastDetails.js
+front_end/cm/cm.js
+front_end/cm/active-line.js
+front_end/cm/brace-fold.js
+front_end/cm/closebrackets.js
+front_end/cm/codemirror.js
+front_end/cm/comment.js
+front_end/cm/foldcode.js
+front_end/cm/foldgutter.js
+front_end/cm/mark-selection.js
+front_end/cm/matchbrackets.js
+front_end/cm/multiplex.js
+front_end/cm/overlay.js
+front_end/formatter_worker.unbundled.js
+front_end/heap_snapshot_worker.unbundled.js
+front_end/heap_snapshot_model/heap_snapshot_model.js
+front_end/heap_snapshot_model/HeapSnapshotModel.js
+front_end/heap_snapshot_worker/heap_snapshot_worker.js
+front_end/heap_snapshot_worker/AllocationProfile.js
+front_end/heap_snapshot_worker/HeapSnapshot.js
+front_end/heap_snapshot_worker/HeapSnapshotLoader.js
+front_end/heap_snapshot_worker/HeapSnapshotWorker.js
+front_end/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.js
+front_end/text_utils/text_utils.js
+front_end/text_utils/TextUtils.js
+front_end/text_utils/TextRange.js
+front_end/text_utils/Text.js
+front_end/formatter_worker/formatter_worker.js
+front_end/formatter_worker/RelaxedJSONParser.js
+front_end/formatter_worker/JavaScriptOutline.js
+front_end/formatter_worker/JavaScriptFormatter.js
+front_end/formatter_worker/IdentityFormatter.js
+front_end/formatter_worker/HTMLFormatter.js
+front_end/formatter_worker/FormatterWorker.js
+front_end/formatter_worker/FormattedContentBuilder.js
+front_end/formatter_worker/ESTreeWalker.js
+front_end/formatter_worker/CSSRuleParser.js
+front_end/formatter_worker/CSSFormatter.js
+front_end/formatter_worker/AcornTokenizer.js
+front_end/cm_headless/cm_headless.js
+front_end/cm_headless/headlesscodemirror.js
+front_end/data_grid/data_grid.js
+front_end/data_grid/ViewportDataGrid.js
+front_end/data_grid/SortableDataGrid.js
+front_end/data_grid/ShowMoreDataGridNode.js
+front_end/data_grid/DataGrid.js
+front_end/protocol_monitor/protocol_monitor.js
+front_end/protocol_monitor/ProtocolMonitor.js
+front_end/console_counters/console_counters.js
+front_end/console_counters/WarningErrorCounter.js
+front_end/extensions/extensions.js
+front_end/extensions/ExtensionAPI.js
+front_end/extensions/ExtensionPanel.js
+front_end/extensions/ExtensionServer.js
+front_end/extensions/ExtensionTraceProvider.js
+front_end/extensions/ExtensionView.js
+front_end/browser_sdk/browser_sdk.js
+front_end/browser_sdk/LogManager.js
+front_end/persistence/persistence.js
+front_end/persistence/WorkspaceSettingsTab.js
+front_end/persistence/PlatformFileSystem.js
+front_end/persistence/PersistenceUtils.js
+front_end/persistence/PersistenceImpl.js
+front_end/persistence/PersistenceActions.js
+front_end/persistence/NetworkPersistenceManager.js
+front_end/persistence/IsolatedFileSystemManager.js
+front_end/persistence/IsolatedFileSystem.js
+front_end/persistence/FileSystemWorkspaceBinding.js
+front_end/persistence/EditFileSystemView.js
+front_end/persistence/Automapping.js
+front_end/components/components.js
+front_end/components/TargetDetachedDialog.js
+front_end/components/Reload.js
+front_end/components/Linkifier.js
+front_end/components/JSPresentationUtils.js
+front_end/components/ImagePreview.js
+front_end/components/DockController.js
+front_end/bindings/bindings.js
+front_end/bindings/TempFile.js
+front_end/bindings/StylesSourceMapping.js
+front_end/bindings/SASSSourceMapping.js
+front_end/bindings/ResourceUtils.js
+front_end/bindings/ResourceScriptMapping.js
+front_end/bindings/ResourceMapping.js
+front_end/bindings/PresentationConsoleMessageHelper.js
+front_end/bindings/NetworkProject.js
+front_end/bindings/LiveLocation.js
+front_end/bindings/FileUtils.js
+front_end/bindings/DefaultScriptMapping.js
+front_end/bindings/DebuggerWorkspaceBinding.js
+front_end/bindings/CSSWorkspaceBinding.js
+front_end/bindings/ContentProviderBasedProject.js
+front_end/bindings/CompilerScriptMapping.js
+front_end/bindings/BreakpointManager.js
+front_end/bindings/BlackboxManager.js
+front_end/workspace/workspace.js
+front_end/workspace/WorkspaceImpl.js
+front_end/workspace/UISourceCode.js
+front_end/workspace/FileManager.js
+front_end/services/services.js
+front_end/services/ServiceManager.js
+front_end/sdk/sdk.js
+front_end/sdk/TracingModel.js
+front_end/sdk/TracingManager.js
+front_end/sdk/TargetManager.js
+front_end/sdk/Target.js
+front_end/sdk/SourceMapManager.js
+front_end/sdk/SourceMap.js
+front_end/sdk/ServiceWorkerManager.js
+front_end/sdk/ServiceWorkerCacheModel.js
+front_end/sdk/ServerTiming.js
+front_end/sdk/SecurityOriginManager.js
+front_end/sdk/SDKModel.js
+front_end/sdk/Script.js
+front_end/sdk/ScreenCaptureModel.js
+front_end/sdk/RuntimeModel.js
+front_end/sdk/ResourceTreeModel.js
+front_end/sdk/Resource.js
+front_end/sdk/RemoteObject.js
+front_end/sdk/ProfileTreeModel.js
+front_end/sdk/IssuesModel.js
+front_end/sdk/PerformanceMetricsModel.js
+front_end/sdk/PaintProfiler.js
+front_end/sdk/OverlayModel.js
+front_end/sdk/NetworkRequest.js
+front_end/sdk/NetworkManager.js
+front_end/sdk/NetworkLog.js
+front_end/sdk/LogModel.js
+front_end/sdk/LayerTreeBase.js
+front_end/sdk/IsolateManager.js
+front_end/sdk/HeapProfilerModel.js
+front_end/sdk/HARLog.js
+front_end/sdk/FilmStripModel.js
+front_end/sdk/EmulationModel.js
+front_end/sdk/DOMModel.js
+front_end/sdk/DOMDebuggerModel.js
+front_end/sdk/DebuggerModel.js
+front_end/sdk/CSSStyleSheetHeader.js
+front_end/sdk/CSSStyleDeclaration.js
+front_end/sdk/CSSRule.js
+front_end/sdk/CSSProperty.js
+front_end/sdk/CSSModel.js
+front_end/sdk/CSSMetadata.js
+front_end/sdk/CSSMedia.js
+front_end/sdk/CSSMatchedStyles.js
+front_end/sdk/CPUProfilerModel.js
+front_end/sdk/CPUProfileDataModel.js
+front_end/sdk/CookieParser.js
+front_end/sdk/CookieModel.js
+front_end/sdk/CompilerSourceMappingContentProvider.js
+front_end/sdk/ConsoleModel.js
+front_end/sdk/Connections.js
+front_end/sdk/ChildTargetManager.js
+front_end/protocol/protocol.js
+front_end/protocol/NodeURL.js
+front_end/protocol/InspectorBackend.js
+front_end/host/host.js
+front_end/host/UserMetrics.js
+front_end/host/ResourceLoader.js
+front_end/host/Platform.js
+front_end/host/InspectorFrontendHost.js
+front_end/host/InspectorFrontendHostAPI.js
+front_end/dom_extension/DOMExtension.js
+front_end/dom_extension/dom_extension.js
+front_end/root.js
+front_end/Runtime.js
+front_end/platform/utilities.js
+front_end/platform/platform.js
+front_end/ui/ARIAUtils.js
+front_end/ui/ZoomManager.js
+front_end/ui/XWidget.js
+front_end/ui/XLink.js
+front_end/ui/XElement.js
+front_end/ui/Widget.js
+front_end/ui/View.js
+front_end/ui/ViewManager.js
+front_end/ui/UIUtils.js
+front_end/ui/ui.js
+front_end/ui/Treeoutline.js
+front_end/ui/Tooltip.js
+front_end/ui/Toolbar.js
+front_end/ui/ThrottledWidget.js
+front_end/ui/TextPrompt.js
+front_end/ui/TextEditor.js
+front_end/ui/TargetCrashedScreen.js
+front_end/ui/TabbedPane.js
+front_end/ui/SyntaxHighlighter.js
+front_end/ui/SuggestBox.js
+front_end/ui/SplitWidget.js
+front_end/ui/SoftDropDown.js
+front_end/ui/SoftContextMenu.js
+front_end/ui/ShortcutsScreen.js
+front_end/ui/ShortcutRegistry.js
+front_end/ui/SettingsUI.js
+front_end/ui/SegmentedButton.js
+front_end/ui/SearchableView.js
+front_end/ui/RootView.js
+front_end/ui/ResizerWidget.js
+front_end/ui/ReportView.js
+front_end/ui/RemoteDebuggingTerminatedScreen.js
+front_end/ui/ProgressIndicator.js
+front_end/ui/PopoverHelper.js
+front_end/ui/Panel.js
+front_end/ui/ListWidget.js
+front_end/ui/ListModel.js
+front_end/ui/ListControl.js
+front_end/ui/KeyboardShortcut.js
+front_end/ui/InspectorView.js
+front_end/ui/InplaceEditor.js
+front_end/ui/Infobar.js
+front_end/ui/Icon.js
+front_end/ui/HistoryInput.js
+front_end/ui/GlassPane.js
+front_end/ui/Geometry.js
+front_end/ui/Fragment.js
+front_end/ui/ForwardedInputEventHandler.js
+front_end/ui/FilterSuggestionBuilder.js
+front_end/ui/FilterBar.js
+front_end/ui/EmptyWidget.js
+front_end/ui/DropTarget.js
+front_end/ui/Dialog.js
+front_end/ui/ContextMenu.js
+front_end/ui/Context.js
+front_end/ui/ARIAUtils.js
+front_end/ui/ActionRegistry.js
+front_end/ui/Action.js
+front_end/ui/ActionDelegate.js
+front_end/ui/ContextFlavorListener.js
+front_end/root.js
+front_end/common/common.js
+front_end/common/common-legacy.js
+front_end/common/App.js
+front_end/common/AppProvider.js
+front_end/common/CharacterIdMap.js
+front_end/common/Color.js
+front_end/common/ContentProvider.js
+front_end/common/EventTarget.js
+front_end/common/JavaScriptMetaData.js
+front_end/common/Linkifier.js
+front_end/common/Object.js
+front_end/common/Console.js
+front_end/common/ParsedURL.js
+front_end/common/Progress.js
+front_end/common/QueryParamHandler.js
+front_end/common/ResourceType.js
+front_end/common/Revealer.js
+front_end/common/Runnable.js
+front_end/common/SegmentedRange.js
+front_end/common/Settings.js
+front_end/common/StaticContentProvider.js
+front_end/common/StringOutputStream.js
+front_end/common/TextDictionary.js
+front_end/common/Throttler.js
+front_end/common/Trie.js
+front_end/common/UIString.js
+front_end/common/Worker.js
diff --git a/src/cobalt/dom/cobalt_ua_data_values.idl b/src/cobalt/dom/cobalt_ua_data_values.idl
index 000b238..2dea119 100644
--- a/src/cobalt/dom/cobalt_ua_data_values.idl
+++ b/src/cobalt/dom/cobalt_ua_data_values.idl
@@ -19,6 +19,7 @@
   DOMString cobaltBuildConfiguration;
   DOMString jsEngineVersion;
   DOMString rasterizer;
+  DOMString evergreenType;
   DOMString evergreenVersion;
   DOMString starboardVersion;
   DOMString originalDesignManufacturer;
diff --git a/src/cobalt/dom/cobalt_ua_data_values_interface.cc b/src/cobalt/dom/cobalt_ua_data_values_interface.cc
index a688cf8..586e086 100644
--- a/src/cobalt/dom/cobalt_ua_data_values_interface.cc
+++ b/src/cobalt/dom/cobalt_ua_data_values_interface.cc
@@ -58,6 +58,9 @@
   if (init_dict.has_rasterizer()) {
     rasterizer_ = init_dict.rasterizer();
   }
+  if (init_dict.has_evergreen_type()) {
+    evergreen_type_ = init_dict.evergreen_type();
+  }
   if (init_dict.has_evergreen_version()) {
     evergreen_version_ = init_dict.evergreen_version();
   }
diff --git a/src/cobalt/dom/cobalt_ua_data_values_interface.h b/src/cobalt/dom/cobalt_ua_data_values_interface.h
index e3ef570..cf0093b 100644
--- a/src/cobalt/dom/cobalt_ua_data_values_interface.h
+++ b/src/cobalt/dom/cobalt_ua_data_values_interface.h
@@ -43,6 +43,7 @@
   }
   const std::string& js_engine_version() const { return js_engine_version_; }
   const std::string& rasterizer() const { return rasterizer_; }
+  const std::string& evergreen_type() const { return evergreen_type_; }
   const std::string& evergreen_version() const { return evergreen_version_; }
   const std::string& starboard_version() const { return starboard_version_; }
   const std::string& original_design_manufacturer() const {
@@ -72,6 +73,7 @@
   std::string cobalt_build_configuration_;
   std::string js_engine_version_;
   std::string rasterizer_;
+  std::string evergreen_type_;
   std::string evergreen_version_;
   std::string starboard_version_;
   std::string original_design_manufacturer_;
diff --git a/src/cobalt/dom/cobalt_ua_data_values_interface.idl b/src/cobalt/dom/cobalt_ua_data_values_interface.idl
index 179ac91..ff1b119 100644
--- a/src/cobalt/dom/cobalt_ua_data_values_interface.idl
+++ b/src/cobalt/dom/cobalt_ua_data_values_interface.idl
@@ -29,6 +29,7 @@
   readonly attribute DOMString cobaltBuildConfiguration;
   readonly attribute DOMString jsEngineVersion;
   readonly attribute DOMString rasterizer;
+  readonly attribute DOMString evergreenType;
   readonly attribute DOMString evergreenVersion;
   readonly attribute DOMString starboardVersion;
   readonly attribute DOMString originalDesignManufacturer;
diff --git a/src/cobalt/dom/document__web_animations_api.idl b/src/cobalt/dom/document_web_animations_api.idl
similarity index 100%
rename from src/cobalt/dom/document__web_animations_api.idl
rename to src/cobalt/dom/document_web_animations_api.idl
diff --git a/src/cobalt/dom/html_link_element.cc b/src/cobalt/dom/html_link_element.cc
index 3e398c7..8e94b1b 100644
--- a/src/cobalt/dom/html_link_element.cc
+++ b/src/cobalt/dom/html_link_element.cc
@@ -254,6 +254,9 @@
 
 void HTMLLinkElement::OnLoadingComplete(
     const base::Optional<std::string>& error) {
+  // GetLoadTimingInfo and create resource timing before loader released.
+  GetLoadTimingInfoAndCreateResourceTiming();
+
   base::MessageLoop::current()->task_runner()->PostTask(
       FROM_HERE, base::Bind(&HTMLLinkElement::ReleaseLoader, this));
 
@@ -279,9 +282,6 @@
     // complete.
     node_document()->DecreaseLoadingCounterAndMaybeDispatchLoadEvent();
   }
-
-  // GetLoadTimingInfo and create resource timing before loader released.
-  GetLoadTimingInfoAndCreateResourceTiming();
 }
 
 void HTMLLinkElement::OnSplashscreenLoaded(Document* document,
diff --git a/src/cobalt/dom/html_script_element.cc b/src/cobalt/dom/html_script_element.cc
index ad9386e..fe37061 100644
--- a/src/cobalt/dom/html_script_element.cc
+++ b/src/cobalt/dom/html_script_element.cc
@@ -555,6 +555,9 @@
 //   https://www.w3.org/TR/html50/scripting-1.html#prepare-a-script
 void HTMLScriptElement::OnLoadingComplete(
     const base::Optional<std::string>& error) {
+  // GetLoadTimingInfo and create resource timing before loader released.
+  GetLoadTimingInfoAndCreateResourceTiming();
+
   if (!error) return;
 
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -596,9 +599,6 @@
   // once the resource has been fetched (defined above) has been run.
   document_->DecreaseLoadingCounterAndMaybeDispatchLoadEvent();
 
-  // GetLoadTimingInfo and create resource timing before loader released.
-  GetLoadTimingInfoAndCreateResourceTiming();
-
   // Post a task to release the loader.
   base::MessageLoop::current()->task_runner()->PostTask(
       FROM_HERE, base::Bind(&HTMLScriptElement::ReleaseLoader, this));
diff --git a/src/cobalt/dom/navigator_ua_data.cc b/src/cobalt/dom/navigator_ua_data.cc
index 2d19869..73d9aed 100644
--- a/src/cobalt/dom/navigator_ua_data.cc
+++ b/src/cobalt/dom/navigator_ua_data.cc
@@ -57,6 +57,7 @@
   all_high_entropy_values_.set_js_engine_version(
       platform_info->javascript_engine_version());
   all_high_entropy_values_.set_rasterizer(platform_info->rasterizer_type());
+  all_high_entropy_values_.set_evergreen_type(platform_info->evergreen_type());
   all_high_entropy_values_.set_evergreen_version(
       platform_info->evergreen_version());
   all_high_entropy_values_.set_starboard_version(
@@ -118,6 +119,9 @@
     } else if ((*it).compare("rasterizer") == 0) {
       select_high_entropy_values_.set_rasterizer(
           all_high_entropy_values_.rasterizer());
+    } else if ((*it).compare("evergreenType") == 0) {
+      select_high_entropy_values_.set_evergreen_type(
+          all_high_entropy_values_.evergreen_type());
     } else if ((*it).compare("evergreenVersion") == 0) {
       select_high_entropy_values_.set_evergreen_version(
           all_high_entropy_values_.evergreen_version());
diff --git a/src/cobalt/dom/performance.cc b/src/cobalt/dom/performance.cc
index a90c2e8..7554005 100644
--- a/src/cobalt/dom/performance.cc
+++ b/src/cobalt/dom/performance.cc
@@ -31,7 +31,7 @@
 namespace {
 
 base::TimeDelta GetUnixAtZeroMonotonic(const base::Clock* clock,
-                                         const base::TickClock* tick_clock) {
+                                       const base::TickClock* tick_clock) {
   base::TimeDelta unix_time_now = clock->Now() - base::Time::UnixEpoch();
   base::TimeDelta time_since_origin = tick_clock->NowTicks().since_origin();
   return unix_time_now - time_since_origin;
@@ -622,5 +622,9 @@
       is_preload, timestamp);
 }
 
+void Performance::SetDeepLinkTimestamp(SbTimeMonotonic timestamp) {
+  lifecycle_timing_->SetDeepLinkTimestamp(timestamp);
+}
+
 }  // namespace dom
 }  // namespace cobalt
diff --git a/src/cobalt/dom/performance.h b/src/cobalt/dom/performance.h
index 753123d..d48c7ea 100644
--- a/src/cobalt/dom/performance.h
+++ b/src/cobalt/dom/performance.h
@@ -138,6 +138,8 @@
   void SetApplicationStartOrPreloadTimestamp(
       bool is_preload, SbTimeMonotonic timestamp);
 
+  void SetDeepLinkTimestamp(SbTimeMonotonic timestamp);
+
   void TraceMembers(script::Tracer* tracer) override;
   DEFINE_WRAPPABLE_TYPE(Performance);
 
diff --git a/src/cobalt/dom/performance_lifecycle_timing.cc b/src/cobalt/dom/performance_lifecycle_timing.cc
index bb2e21b..08f47d4 100644
--- a/src/cobalt/dom/performance_lifecycle_timing.cc
+++ b/src/cobalt/dom/performance_lifecycle_timing.cc
@@ -40,11 +40,13 @@
     return "INVALID_APPLICATION_STATE";
   }
 
-  DOMHighResTimeStamp ConvertSbTimeMonotonicToDOMHiResTimeStamp(
+    DOMHighResTimeStamp ConvertSbTimeMonotonicToDOMHiResTimeStamp(
       base::TimeTicks time_origin, SbTimeMonotonic monotonic_time) {
     SbTimeMonotonic time_delta = SbTimeGetNow() - SbTimeGetMonotonicNow();
+    base::Time base_time = base::Time::FromSbTime(time_delta + monotonic_time);
     base::TimeTicks time_ticks =
-        base::TimeTicks::FromInternalValue(time_delta + monotonic_time);
+        base::TimeTicks::FromInternalValue(static_cast<int64_t>(
+            base_time.ToJsTime()));
     return Performance::MonotonicTimeToDOMHighResTimeStamp(
         time_origin, time_ticks);
   }
@@ -88,6 +90,10 @@
   return ReportDOMHighResTimeStamp(lifecycle_timing_info_.app_unfreeze);
 }
 
+DOMHighResTimeStamp PerformanceLifecycleTiming::app_deeplink() const {
+  return ReportDOMHighResTimeStamp(lifecycle_timing_info_.app_deeplink);
+}
+
 std::string PerformanceLifecycleTiming::current_state() const {
   return TranslateApplicationStateToString(
       lifecycle_timing_info_.current_state);
@@ -102,24 +108,40 @@
     base::ApplicationState state, SbTimeMonotonic timestamp) {
   switch (state) {
     case base::kApplicationStateBlurred:
-      if (GetLastState() == base::kApplicationStateStarted) {
+      if (GetCurrentState() == base::kApplicationStateStarted ||
+          // TODO: Figure out why the current state is not set
+          // by SetApplicationStartOrPreloadTimestamp.
+          GetCurrentState() == base::kApplicationStateStopped) {
         lifecycle_timing_info_.app_blur = timestamp;
-      } else if (GetLastState() == base::kApplicationStateConcealed) {
+      } else if (GetCurrentState() == base::kApplicationStateConcealed) {
         lifecycle_timing_info_.app_reveal = timestamp;
+      } else {
+        DLOG(INFO) << "Current State: " <<
+            TranslateApplicationStateToString(GetCurrentState());
+        DLOG(INFO) << "Next State: " <<
+            TranslateApplicationStateToString(state);
+        NOTREACHED() << "Invalid application state transition.";
       }
       break;
     case base::kApplicationStateConcealed:
-      if (GetLastState() == base::kApplicationStateBlurred) {
+      if (GetCurrentState() == base::kApplicationStateBlurred ||
+          GetCurrentState() == base::kApplicationStateStopped) {
         lifecycle_timing_info_.app_conceal = timestamp;
-      } else if (GetLastState() == base::kApplicationStateFrozen) {
+      } else if (GetCurrentState() == base::kApplicationStateFrozen) {
         lifecycle_timing_info_.app_unfreeze = timestamp;
+      } else {
+        DLOG(INFO) << "Current State: " <<
+            TranslateApplicationStateToString(GetCurrentState());
+        DLOG(INFO) << "Next State: " <<
+            TranslateApplicationStateToString(state);
+        NOTREACHED() << "Invalid application state transition.";
       }
       break;
     case base::kApplicationStateFrozen:
       lifecycle_timing_info_.app_freeze = timestamp;
       break;
     case base::kApplicationStateStarted:
-      if (GetLastState() == base::kApplicationStateBlurred) {
+      if (GetCurrentState() == base::kApplicationStateBlurred) {
         if (lifecycle_timing_info_.app_preload != 0) {
           lifecycle_timing_info_.app_start = timestamp;
         }
@@ -137,7 +159,7 @@
 }
 
 void PerformanceLifecycleTiming::SetApplicationStartOrPreloadTimestamp(
-      bool is_preload, SbTimeMonotonic timestamp) {
+    bool is_preload, SbTimeMonotonic timestamp) {
   if (is_preload) {
     lifecycle_timing_info_.app_preload = timestamp;
     SetLifecycleTimingInfoState(base::kApplicationStateConcealed);
@@ -147,8 +169,13 @@
   }
 }
 
-base::ApplicationState PerformanceLifecycleTiming::GetLastState() const {
-  return lifecycle_timing_info_.last_state;
+void PerformanceLifecycleTiming::SetDeepLinkTimestamp(
+    SbTimeMonotonic timestamp) {
+  lifecycle_timing_info_.app_deeplink = timestamp;
+}
+
+base::ApplicationState PerformanceLifecycleTiming::GetCurrentState() const {
+  return lifecycle_timing_info_.current_state;
 }
 
 void PerformanceLifecycleTiming::SetLifecycleTimingInfoState(
@@ -158,12 +185,11 @@
   lifecycle_timing_info_.current_state = state;
 }
 
-DOMHighResTimeStamp
-    PerformanceLifecycleTiming::ReportDOMHighResTimeStamp(
-        SbTimeMonotonic timestamp) const {
+DOMHighResTimeStamp PerformanceLifecycleTiming::ReportDOMHighResTimeStamp(
+    SbTimeMonotonic timestamp) const {
   if (timestamp != 0) {
-    return ConvertSbTimeMonotonicToDOMHiResTimeStamp(
-        time_origin_, timestamp);
+    return ConvertSbTimeMonotonicToDOMHiResTimeStamp(time_origin_,
+                                                     timestamp);
   }
   return PerformanceEntry::start_time();
 }
diff --git a/src/cobalt/dom/performance_lifecycle_timing.h b/src/cobalt/dom/performance_lifecycle_timing.h
index 42fc320..9baa10c 100644
--- a/src/cobalt/dom/performance_lifecycle_timing.h
+++ b/src/cobalt/dom/performance_lifecycle_timing.h
@@ -42,6 +42,7 @@
   DOMHighResTimeStamp app_reveal() const;
   DOMHighResTimeStamp app_freeze() const;
   DOMHighResTimeStamp app_unfreeze() const;
+  DOMHighResTimeStamp app_deeplink() const;
   std::string current_state() const;
   std::string last_state() const;
 
@@ -54,6 +55,7 @@
                            SbTimeMonotonic timestamp);
   void SetApplicationStartOrPreloadTimestamp(
       bool is_preload, SbTimeMonotonic timestamp);
+  void SetDeepLinkTimestamp(SbTimeMonotonic timestamp);
 
   DEFINE_WRAPPABLE_TYPE(PerformanceLifecycleTiming);
 
@@ -61,7 +63,7 @@
   void SetLifecycleTimingInfoState(base::ApplicationState state);
   DOMHighResTimeStamp ReportDOMHighResTimeStamp(
       SbTimeMonotonic timestamp) const;
-  base::ApplicationState GetLastState() const;
+  base::ApplicationState GetCurrentState() const;
  struct LifecycleTimingInfo {
   SbTimeMonotonic app_preload = 0;
   SbTimeMonotonic app_start = 0;
@@ -71,6 +73,7 @@
   SbTimeMonotonic app_reveal = 0;
   SbTimeMonotonic app_freeze = 0;
   SbTimeMonotonic app_unfreeze = 0;
+  SbTimeMonotonic app_deeplink = 0;
 
   base::ApplicationState current_state =
       base::kApplicationStateStopped;
diff --git a/src/cobalt/dom/performance_lifecycle_timing.idl b/src/cobalt/dom/performance_lifecycle_timing.idl
index 7d14eb8..8dd56fb 100644
--- a/src/cobalt/dom/performance_lifecycle_timing.idl
+++ b/src/cobalt/dom/performance_lifecycle_timing.idl
@@ -22,6 +22,7 @@
   readonly  attribute DOMHighResTimeStamp appReveal;
   readonly  attribute DOMHighResTimeStamp appFreeze;
   readonly  attribute DOMHighResTimeStamp appUnfreeze;
+  readonly  attribute DOMHighResTimeStamp appDeeplink;
   readonly  attribute DOMString  currentState;
   readonly  attribute DOMString  lastState;
 };
diff --git a/src/cobalt/dom/user_agent_platform_info.h b/src/cobalt/dom/user_agent_platform_info.h
index a63c8a5..2b288b5 100644
--- a/src/cobalt/dom/user_agent_platform_info.h
+++ b/src/cobalt/dom/user_agent_platform_info.h
@@ -44,6 +44,7 @@
   virtual const std::string& connection_type_string() const = 0;
   virtual const std::string& javascript_engine_version() const = 0;
   virtual const std::string& rasterizer_type() const = 0;
+  virtual const std::string& evergreen_type() const = 0;
   virtual const std::string& evergreen_version() const = 0;
 
   virtual const std::string& cobalt_version() const = 0;
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc
index a3bee80..52fe0bb 100644
--- a/src/cobalt/dom/window.cc
+++ b/src/cobalt/dom/window.cc
@@ -538,6 +538,7 @@
                                  SbTimeMonotonic timestamp) {
   html_element_context_->application_lifecycle_state()->SetApplicationState(
       state);
+  if (timestamp == 0) return;
   performance_->SetApplicationState(state, timestamp);
 }
 
diff --git a/src/cobalt/dom/window.idl b/src/cobalt/dom/window.idl
index 2173dd9..711559d 100644
--- a/src/cobalt/dom/window.idl
+++ b/src/cobalt/dom/window.idl
@@ -15,7 +15,7 @@
 // https://www.w3.org/TR/html50/browsers.html#the-window-object
 
 // Note: Cobalt only supports one browsing context and one Window object.
-[PrimaryGlobal]
+[Global]
 /*sealed*/ interface Window : EventTarget {
   // the current browsing context
   [Unforgeable] readonly attribute Window window;
@@ -52,4 +52,3 @@
 };
 Window implements GlobalEventHandlers;
 Window implements WindowEventHandlers;
-
diff --git a/src/cobalt/dom/window__animation_timing.idl b/src/cobalt/dom/window_animation_timing.idl
similarity index 100%
rename from src/cobalt/dom/window__animation_timing.idl
rename to src/cobalt/dom/window_animation_timing.idl
diff --git a/src/cobalt/dom/window__performance.idl b/src/cobalt/dom/window_performance.idl
similarity index 90%
rename from src/cobalt/dom/window__performance.idl
rename to src/cobalt/dom/window_performance.idl
index 0e6b71e..0eda289 100644
--- a/src/cobalt/dom/window__performance.idl
+++ b/src/cobalt/dom/window_performance.idl
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// https://www.w3.org/TR/navigation-timing/#performance
+// https://www.w3.org/TR/2012/REC-navigation-timing-20121217/#performance
 
 partial interface Window {
   readonly attribute Performance performance;
diff --git a/src/cobalt/loader/image/image_decoder.cc b/src/cobalt/loader/image/image_decoder.cc
index a499c63..68782a3 100644
--- a/src/cobalt/loader/image/image_decoder.cc
+++ b/src/cobalt/loader/image/image_decoder.cc
@@ -54,25 +54,6 @@
   result->append(message);
 }
 
-// Determine the ImageType of an image from its signature.
-ImageDecoder::ImageType DetermineImageType(const uint8* header) {
-  if (!memcmp(header, "\xFF\xD8\xFF", 3)) {
-    return ImageDecoder::kImageTypeJPEG;
-  } else if (!memcmp(header, "GIF87a", 6) || !memcmp(header, "GIF89a", 6)) {
-    return ImageDecoder::kImageTypeGIF;
-  } else if (!memcmp(header, "{", 1)) {
-    // TODO: Improve heuristics for determining whether the file contains valid
-    // Lottie JSON.
-    return ImageDecoder::kImageTypeJSON;
-  } else if (!memcmp(header, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8)) {
-    return ImageDecoder::kImageTypePNG;
-  } else if (!memcmp(header, "RIFF", 4) && !memcmp(header + 8, "WEBPVP", 6)) {
-    return ImageDecoder::kImageTypeWebP;
-  } else {
-    return ImageDecoder::kImageTypeInvalid;
-  }
-}
-
 // Returns true if the ResourceProvider is ResourceProviderStub.
 bool IsResourceProviderStub(render_tree::ResourceProvider* resource_provider) {
   return resource_provider->GetTypeId() ==
@@ -81,6 +62,39 @@
 
 }  // namespace
 
+// Determine the ImageType of an image from its signature.
+ImageDecoder::ImageType DetermineImageType(const uint8* header) {
+  if (!memcmp(header, "\xFF\xD8\xFF", 3)) {
+    return ImageDecoder::kImageTypeJPEG;
+  } else if (!memcmp(header, "GIF87a", 6) || !memcmp(header, "GIF89a", 6)) {
+    return ImageDecoder::kImageTypeGIF;
+  } else if (!memcmp(header, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8)) {
+    return ImageDecoder::kImageTypePNG;
+  } else if (!memcmp(header, "RIFF", 4) && !memcmp(header + 8, "WEBPVP", 6)) {
+    return ImageDecoder::kImageTypeWebP;
+  } else {
+    const std::string header_str = reinterpret_cast<const char*>(header);
+    std::string::size_type first_non_white_space_pos =
+        header_str.find_first_not_of(" \t\r\n");
+    if (first_non_white_space_pos + 1 < header_str.size()) {
+      if (header_str[first_non_white_space_pos] == '{' ||
+          header_str[first_non_white_space_pos] ==
+              '[') {  // json can start with either object hash or an array
+        std::string::size_type second_non_white_space_pos =
+            header_str.find_first_not_of(" \t\r\n",
+                                         first_non_white_space_pos + 1);
+        if (second_non_white_space_pos + 1 < header_str.size()) {
+          if (header_str[second_non_white_space_pos] == '"' &&
+              isalnum(header_str[second_non_white_space_pos + 1])) {
+            return ImageDecoder::kImageTypeJSON;
+          }
+        }
+      }
+    }
+    return ImageDecoder::kImageTypeInvalid;
+  }
+}
+
 ImageDecoder::ImageDecoder(
     render_tree::ResourceProvider* resource_provider,
     const base::DebuggerHooks& debugger_hooks,
diff --git a/src/cobalt/loader/image/image_decoder.h b/src/cobalt/loader/image/image_decoder.h
index cb80ce6..89b4d6b 100644
--- a/src/cobalt/loader/image/image_decoder.h
+++ b/src/cobalt/loader/image/image_decoder.h
@@ -122,6 +122,8 @@
   bool use_failure_image_decoder_ = false;
 };
 
+ImageDecoder::ImageType DetermineImageType(const uint8* header);
+
 }  // namespace image
 }  // namespace loader
 }  // namespace cobalt
diff --git a/src/cobalt/loader/image/image_decoder_unit_test.cc b/src/cobalt/loader/image/image_decoder_unit_test.cc
new file mode 100644
index 0000000..a6dfc5d
--- /dev/null
+++ b/src/cobalt/loader/image/image_decoder_unit_test.cc
@@ -0,0 +1,68 @@
+// Copyright 2021 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/loader/image/image_decoder.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace loader {
+namespace image {
+
+TEST(ImageDecoderUnitTest, ValidImageType) {
+  EXPECT_EQ(DetermineImageType(reinterpret_cast<const uint8*>("\xFF\xD8\xFF")),
+            ImageDecoder::kImageTypeJPEG);
+  EXPECT_EQ(DetermineImageType(reinterpret_cast<const uint8*>("GIF87a")),
+            ImageDecoder::kImageTypeGIF);
+  EXPECT_EQ(DetermineImageType(reinterpret_cast<const uint8*>("GIF89a")),
+            ImageDecoder::kImageTypeGIF);
+  EXPECT_EQ(DetermineImageType(reinterpret_cast<const uint8*>(
+                "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A")),
+            ImageDecoder::kImageTypePNG);
+  EXPECT_EQ(
+      DetermineImageType(reinterpret_cast<const uint8*>("RIFF    WEBPVP")),
+      ImageDecoder::kImageTypeWebP);
+  EXPECT_EQ(
+      DetermineImageType(reinterpret_cast<const uint8*>("{\"v\":\"4.6.8\"")),
+      ImageDecoder::kImageTypeJSON);
+  EXPECT_EQ(DetermineImageType(
+                reinterpret_cast<const uint8*>("  {\t\"v\":\"4.6.8\"")),
+            ImageDecoder::kImageTypeJSON);
+  EXPECT_EQ(DetermineImageType(
+                reinterpret_cast<const uint8*>("\r [ \"v\":\"4.6.8\"")),
+            ImageDecoder::kImageTypeJSON);
+}
+
+TEST(ImageDecoderUnitTest, InvalidImageType) {
+  EXPECT_EQ(DetermineImageType(reinterpret_cast<const uint8*>(
+                "\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01"
+                "\x00")),
+            ImageDecoder::kImageTypeInvalid);
+  EXPECT_EQ(
+      DetermineImageType(reinterpret_cast<const uint8*>("{}\0\0\0\0\0\0")),
+      ImageDecoder::kImageTypeInvalid);
+  EXPECT_EQ(
+      DetermineImageType(reinterpret_cast<const uint8*>("{a\0\0\0\0\0\0")),
+      ImageDecoder::kImageTypeInvalid);
+  EXPECT_EQ(
+      DetermineImageType(reinterpret_cast<const uint8*>("{\"\"\0\0\0\0\0")),
+      ImageDecoder::kImageTypeInvalid);
+  EXPECT_EQ(
+      DetermineImageType(reinterpret_cast<const uint8*>("{\"{\0\0\0\0\0")),
+      ImageDecoder::kImageTypeInvalid);
+}
+
+}  // namespace image
+}  // namespace loader
+}  // namespace cobalt
diff --git a/src/cobalt/loader/loader.gyp b/src/cobalt/loader/loader.gyp
index 3a213f3..b4f2551 100644
--- a/src/cobalt/loader/loader.gyp
+++ b/src/cobalt/loader/loader.gyp
@@ -114,7 +114,7 @@
         '<(DEPTH)/third_party/libjpeg/libjpeg.gyp:libjpeg',
         '<(DEPTH)/third_party/libpng/libpng.gyp:libpng',
         '<(DEPTH)/third_party/libwebp/libwebp.gyp:libwebp',
-        'embed_resources_as_header_files',
+        'embed_resources_as_header_files_loader',
       ],
       'conditions': [
         ['cobalt_config != "gold"', {
@@ -142,6 +142,7 @@
         'file_fetcher_test.cc',
         'font/typeface_decoder_test.cc',
         'image/image_decoder_test.cc',
+        'image/image_decoder_unit_test.cc',
         'mesh/mesh_decoder_test.cc',
         'loader_test.cc',
         'text_decoder_test.cc',
@@ -187,7 +188,7 @@
       # This target takes all files in the embedded_resources directory (e.g.
       # the splash screen) and embeds them as header files for
       # inclusion into the binary.
-      'target_name': 'embed_resources_as_header_files',
+      'target_name': 'embed_resources_as_header_files_loader',
       'type': 'none',
       # Because we generate a header, we must set the hard_dependency flag.
       'hard_dependency': 1,
@@ -211,7 +212,7 @@
       ],
       'actions': [
         {
-          'action_name': 'embed_resources_as_header_files',
+          'action_name': 'embed_resources_as_header_files_loader',
           'inputs': [
             '<(script_path)',
             '<@(_sources)',
diff --git a/src/cobalt/media/base/playback_statistics.cc b/src/cobalt/media/base/playback_statistics.cc
index 2c797c8..2aca335 100644
--- a/src/cobalt/media/base/playback_statistics.cc
+++ b/src/cobalt/media/base/playback_statistics.cc
@@ -14,6 +14,9 @@
 
 #include "cobalt/media/base/playback_statistics.h"
 
+#include <math.h>
+#include <stdint.h>
+
 #include "base/strings/stringprintf.h"
 #include "starboard/atomic.h"
 #include "starboard/common/string.h"
@@ -34,14 +37,12 @@
 volatile SbAtomic32 s_max_video_height = 0;
 volatile SbAtomic32 s_last_working_codec = kUnknownVideoCodec;
 
-int RoundValues(SbAtomic32 value) {
+int64_t RoundValues(int64_t value) {
   if (value < 10) {
     return value;
   }
-  if (value < 100) {
-    return value / 10 * 10;
-  }
-  return value / 100 * 100;
+  int64_t closest_power = pow(10, floor(log10(value)));
+  return floor(value / closest_power) * closest_power;
 }
 
 void UpdateMaxValue(SbAtomic32 new_value, volatile SbAtomic32* max) {
@@ -210,15 +211,13 @@
 std::string PlaybackStatistics::GetStatistics(
     const VideoDecoderConfig& current_video_config) const {
   return starboard::FormatString(
-      "current_codec: %s, drm: %s, width: %d, height: %d,"
-      " active_players (max): %d (%d), av1: ~%d, h264: ~%d, hevc: ~%d,"
-      " vp9: ~%d, min_width: %d, min_height: %d, max_width: %d, max_height: %d,"
-      " last_working_codec: %s,"
-      " seek_time: %s,"
-      " first_audio_time: %s,"
-      " first_video_time: %s,"
-      " last_audio_time: %s,"
-      " last_video_time: %s",
+      "current_codec: %s, drm: %s, width: %d, height: %d"
+      ", active_players (max): %d (%d), av1: ~%" PRId64 ", h264: ~%" PRId64
+      ", hevc: ~%" PRId64 ", vp9: ~%" PRId64
+      ", min_width: %d, min_height: %d, max_width: %d, max_height: %d"
+      ", last_working_codec: %s, seek_time: %s"
+      ", first_audio_time: ~%" PRId64 ", first_video_time: ~%" PRId64
+      ", last_audio_time: ~%" PRId64 ", last_video_time: ~%" PRId64,
       GetCodecName(current_video_config.codec()).c_str(),
       (current_video_config.is_encrypted() ? "Y" : "N"), video_width_.value(),
       video_height_.value(), SbAtomicNoBarrier_Load(&s_active_instances),
@@ -236,17 +235,17 @@
           .c_str(),
       ValToString(seek_time_).c_str(),
       is_first_audio_buffer_written_
-          ? ValToString(last_written_audio_timestamp_).c_str()
-          : "n/a",
+          ? RoundValues(first_written_audio_timestamp_.value().InSeconds())
+          : -1,
       is_first_video_buffer_written_
-          ? ValToString(first_written_audio_timestamp_).c_str()
-          : "n/a",
+          ? RoundValues(first_written_video_timestamp_.value().InSeconds())
+          : -1,
       is_first_audio_buffer_written_
-          ? ValToString(last_written_audio_timestamp_).c_str()
-          : "n/a",
+          ? RoundValues(last_written_audio_timestamp_.value().InSeconds())
+          : -1,
       is_first_video_buffer_written_
-          ? ValToString(last_written_video_timestamp_).c_str()
-          : "n/a");
+          ? RoundValues(last_written_video_timestamp_.value().InSeconds())
+          : -1);
 }
 
 }  // namespace media
diff --git a/src/cobalt/media/base/sbplayer_pipeline.cc b/src/cobalt/media/base/sbplayer_pipeline.cc
index aefa84b..baebdee 100644
--- a/src/cobalt/media/base/sbplayer_pipeline.cc
+++ b/src/cobalt/media/base/sbplayer_pipeline.cc
@@ -57,6 +57,8 @@
 
 static const int kRetryDelayAtSuspendInMilliseconds = 100;
 
+unsigned int g_pipeline_identifier_counter = 0;
+
 // Used to post parameters to SbPlayerPipeline::StartTask() as the number of
 // parameters exceed what base::Bind() can support.
 struct StartTaskParameters {
@@ -325,7 +327,8 @@
         get_decode_target_graphics_context_provider_func,
     bool allow_resume_after_suspend, MediaLog* media_log,
     VideoFrameProvider* video_frame_provider)
-    : pipeline_identifier_(base::StringPrintf("%p", this)),
+    : pipeline_identifier_(base::StringPrintf("%X",
+                           g_pipeline_identifier_counter++)),
       task_runner_(task_runner),
       allow_resume_after_suspend_(allow_resume_after_suspend),
       window_(window),
@@ -909,11 +912,15 @@
         task_runner_, source_url, window_, this, set_bounds_helper_.get(),
         allow_resume_after_suspend_, *decode_to_texture_output_mode_,
         on_encrypted_media_init_data_encountered_cb_, video_frame_provider_));
-    SetPlaybackRateTask(playback_rate_);
-    SetVolumeTask(volume_);
+    if (player_->IsValid()) {
+      SetPlaybackRateTask(playback_rate_);
+      SetVolumeTask(volume_);
+    } else {
+      player_.reset();
+    }
   }
 
-  if (player_->IsValid()) {
+  if (player_ && player_->IsValid()) {
     base::Closure output_mode_change_cb;
     {
       base::AutoLock auto_lock(lock_);
@@ -924,7 +931,6 @@
     return;
   }
 
-  player_.reset();
   CallSeekCB(DECODER_ERROR_NOT_SUPPORTED,
              "SbPlayerPipeline::CreateUrlPlayer failed: "
              "player_->IsValid() is false.");
@@ -997,33 +1003,35 @@
         set_bounds_helper_.get(), allow_resume_after_suspend_,
         *decode_to_texture_output_mode_, video_frame_provider_,
         max_video_capabilities_));
-
-    if (!player_->IsValid()) {
+    if (player_->IsValid()) {
+      SetPlaybackRateTask(playback_rate_);
+      SetVolumeTask(volume_);
+    } else {
       player_.reset();
-      CallErrorCB(DECODER_ERROR_NOT_SUPPORTED,
-                  "SbPlayerPipeline::CreatePlayer failed: "
-                  "player_->IsValid() is false.");
-      return;
     }
-
-    SetPlaybackRateTask(playback_rate_);
-    SetVolumeTask(volume_);
   }
 
-  base::Closure output_mode_change_cb;
-  {
-    base::AutoLock auto_lock(lock_);
-    DCHECK(!output_mode_change_cb_.is_null());
-    output_mode_change_cb = std::move(output_mode_change_cb_);
-  }
-  output_mode_change_cb.Run();
+  if (player_ && player_->IsValid()) {
+    base::Closure output_mode_change_cb;
+    {
+      base::AutoLock auto_lock(lock_);
+      DCHECK(!output_mode_change_cb_.is_null());
+      output_mode_change_cb = std::move(output_mode_change_cb_);
+    }
+    output_mode_change_cb.Run();
 
-  if (audio_stream_) {
-    UpdateDecoderConfig(audio_stream_);
+    if (audio_stream_) {
+      UpdateDecoderConfig(audio_stream_);
+    }
+    if (video_stream_) {
+      UpdateDecoderConfig(video_stream_);
+    }
+    return;
   }
-  if (video_stream_) {
-    UpdateDecoderConfig(video_stream_);
-  }
+
+  CallSeekCB(DECODER_ERROR_NOT_SUPPORTED,
+             "SbPlayerPipeline::CreatePlayer failed: "
+             "player_->IsValid() is false.");
 }
 
 void SbPlayerPipeline::OnDemuxerInitialized(PipelineStatus status) {
@@ -1426,7 +1434,12 @@
   if (player_) {
     player_->Resume(window);
     if (!player_->IsValid()) {
-      player_.reset();
+      {
+        base::AutoLock auto_lock(lock_);
+        player_.reset();
+      }
+      // TODO: Determine if CallSeekCB() may be used here, as |seek_cb_| may be
+      // available if the app is suspended before a seek is completed.
       CallErrorCB(DECODER_ERROR_NOT_SUPPORTED,
                   "SbPlayerPipeline::ResumeTask failed: "
                   "player_->IsValid() is false.");
diff --git a/src/cobalt/media/player/web_media_player_impl.cc b/src/cobalt/media/player/web_media_player_impl.cc
index 480d7e8..050af74 100644
--- a/src/cobalt/media/player/web_media_player_impl.cc
+++ b/src/cobalt/media/player/web_media_player_impl.cc
@@ -231,7 +231,7 @@
   UMA_HISTOGRAM_ENUMERATION("Media.URLScheme", URLScheme(url), kMaxURLScheme);
   DLOG(INFO) << "Start URL playback";
 
-  // Handle any volume changes that occured before load().
+  // Handle any volume changes that occurred before load().
   SetVolume(GetClient()->Volume());
 
   // TODO: Set networkState to WebMediaPlayer::kNetworkStateIdle on stop.
@@ -251,7 +251,7 @@
 
   DLOG(INFO) << "Start MEDIASOURCE playback";
 
-  // Handle any volume changes that occured before load().
+  // Handle any volume changes that occurred before load().
   SetVolume(GetClient()->Volume());
 
   SetNetworkState(WebMediaPlayer::kNetworkStateLoading);
@@ -278,7 +278,7 @@
   UMA_HISTOGRAM_ENUMERATION("Media.URLScheme", URLScheme(url), kMaxURLScheme);
   DLOG(INFO) << "Start PROGRESSIVE playback";
 
-  // Handle any volume changes that occured before load().
+  // Handle any volume changes that occurred before load().
   SetVolume(GetClient()->Volume());
 
   SetNetworkState(WebMediaPlayer::kNetworkStateLoading);
@@ -307,9 +307,6 @@
   TRACE_EVENT0("cobalt::media", "WebMediaPlayerImpl::Play");
 
   DCHECK_EQ(main_loop_, base::MessageLoop::current());
-#if defined(__LB_ANDROID__)
-  audio_focus_bridge_.RequestAudioFocus();
-#endif  // defined(__LB_ANDROID__)
 
   state_.paused = false;
   pipeline_->SetPlaybackRate(state_.playback_rate);
@@ -319,9 +316,6 @@
 
 void WebMediaPlayerImpl::Pause() {
   DCHECK_EQ(main_loop_, base::MessageLoop::current());
-#if defined(__LB_ANDROID__)
-  audio_focus_bridge_.AbandonAudioFocus();
-#endif  // defined(__LB_ANDROID__)
 
   state_.paused = true;
   pipeline_->SetPlaybackRate(0.0f);
diff --git a/src/cobalt/media/player/web_media_player_impl.h b/src/cobalt/media/player/web_media_player_impl.h
index 9056524..8707d64 100644
--- a/src/cobalt/media/player/web_media_player_impl.h
+++ b/src/cobalt/media/player/web_media_player_impl.h
@@ -316,10 +316,6 @@
   std::unique_ptr<Demuxer> progressive_demuxer_;
   std::unique_ptr<ChunkDemuxer> chunk_demuxer_;
 
-#if defined(__LB_ANDROID__)
-  AudioFocusBridge audio_focus_bridge_;
-#endif  // defined(__LB_ANDROID__)
-
   // Suppresses calls to OnPipelineError() after destruction / shutdown has been
   // started; prevents us from spuriously logging errors that are transient or
   // unimportant.
diff --git a/src/cobalt/media_integration_tests/endurance/endurance_test.py b/src/cobalt/media_integration_tests/endurance/endurance_test.py
new file mode 100644
index 0000000..302519d
--- /dev/null
+++ b/src/cobalt/media_integration_tests/endurance/endurance_test.py
@@ -0,0 +1,264 @@
+# Copyright 2021 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.
+""" Endurance test of playbacks."""
+
+import argparse
+import logging
+import random
+import time
+
+import _env  # pylint: disable=unused-import
+from cobalt.media_integration_tests.test_app import Features, MediaSessionPlaybackState
+from cobalt.media_integration_tests.test_case import TestCase
+from cobalt.media_integration_tests.test_util import PlaybackUrls
+
+# The variables below are all in seconds.
+STATUS_CHECK_INTERVAL = 0.5
+SINGLE_PLAYBACK_WATCH_TIME_MAXIMUM = 2.0 * 60 * 60
+PLAYER_INITIALIZATION_WAITING_TIMEOUT = 5.0 * 60
+MEDIA_TIME_UPDATE_WAITING_TIMEOUT = 10.0
+WRITTEN_INPUT_WAITING_TIMEOUT = 30.0
+PLAYBACK_END_WAITING_TIMEOUT = 30.0
+# Needs to interact with the app regularly to keep it active. Otherwise, the
+# playback may be paused.
+IDLE_TIME_MAXIMUM = 60 * 60
+
+
+class EnduranceTest(TestCase):
+  """
+    Test case for playback endurance test.
+  """
+
+  def __init__(self, *args, **kwargs):
+    super(EnduranceTest, self).__init__(*args, **kwargs)
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        '--startup_url', default=PlaybackUrls.PLAYLIST, type=str)
+    # Stop the test after |max_running_time| (in hours). If |max_running_time|
+    # is 0 or negative, the test will not stop until it gets an error.
+    parser.add_argument('--max_running_time', default=20, type=float)
+    # Insert random actions if |random_action_interval| is greater than 0.
+    parser.add_argument('--random_action_interval', default=0.0, type=float)
+    args, _ = parser.parse_known_args()
+
+    self.startup_url = args.startup_url
+    # Convert |max_running_time| to seconds.
+    self.max_running_time = args.max_running_time * 60 * 60
+    self.needs_random_action = args.random_action_interval > 0
+    self.random_action_interval = args.random_action_interval
+
+  def ResetTimestamps(self):
+    self.last_media_time = -1
+    self.last_media_time_update_time = -1
+    self.last_written_audio_timestamp = -1
+    self.last_written_audio_update_time = -1
+    self.last_written_video_timestamp = -1
+    self.last_written_video_update_time = -1
+    self.audio_eos_written_time = -1
+    self.video_eos_written_time = -1
+    self.playback_end_time = -1
+
+  def OnPlayerStateChanged(self, player_state_handler, player_state):
+    current_running_time = time.time() - self.start_time
+    element_state = player_state.video_element_state
+    pipeline_state = player_state.pipeline_state
+    media_session_state = player_state.media_session_state
+
+    if not player_state_handler.IsPlayerPlaying():
+      return
+
+    # Reset timestamps after player identifier is changed.
+    if self.player_identifier != pipeline_state.identifier:
+      self.player_identifier = pipeline_state.identifier
+      self.playback_start_time = current_running_time
+      self.ResetTimestamps()
+
+    # Reset timestamps after resume. It could happen after pause, fastforward,
+    # rewind and playback switch.
+    if media_session_state.playback_state != MediaSessionPlaybackState.PLAYING:
+      self.playback_is_playing = False
+    elif not self.playback_is_playing:
+      self.playback_is_playing = True
+      self.ResetTimestamps()
+
+    # Update media time.
+    if self.last_media_time != element_state.current_time:
+      self.last_media_time = element_state.current_time
+      self.last_media_time_update_time = current_running_time
+
+    # Update written audio timestamp.
+    if (self.last_written_audio_timestamp !=
+        pipeline_state.last_written_audio_timestamp):
+      self.last_written_audio_timestamp = (
+          pipeline_state.last_written_audio_timestamp)
+      self.last_written_audio_update_time = current_running_time
+
+    # Update written video timestamp.
+    if (self.last_written_video_timestamp !=
+        pipeline_state.last_written_video_timestamp):
+      self.last_written_video_timestamp = (
+          pipeline_state.last_written_video_timestamp)
+      self.last_written_video_update_time = current_running_time
+
+    # Update audio eos timestamp.
+    if (self.audio_eos_written_time == -1 and
+        pipeline_state.is_audio_eos_written):
+      self.audio_eos_written_time = current_running_time
+
+    # Update video eos timestamp.
+    if (self.video_eos_written_time == -1 and
+        pipeline_state.is_video_eos_written):
+      self.video_eos_written_time = current_running_time
+
+    # Update playback end time.
+    if (self.audio_eos_written_time != -1 and
+        self.video_eos_written_time != -1 and self.playback_end_time == -1 and
+        element_state.current_time >= element_state.duration):
+      self.playback_end_time = current_running_time
+
+  def GenerateErrorString(self, err_msg):
+    return (
+        '%s (running time: %f, player identifier: "%s", is playing: %r, '
+        'playback start time: %f, last media time: %f (updated at %f), '
+        'last written audio timestamp: %d (updated at %f), '
+        'last written video timestamp: %d (updated at %f), '
+        'audio eos written at %f, video eos written at %f, '
+        'playback ended at %f).' %
+        (err_msg, time.time() - self.start_time, self.player_identifier,
+         self.playback_is_playing, self.playback_start_time,
+         self.last_media_time, self.last_media_time_update_time,
+         self.last_written_audio_timestamp, self.last_written_audio_update_time,
+         self.last_written_video_timestamp, self.last_written_video_update_time,
+         self.audio_eos_written_time, self.video_eos_written_time,
+         self.playback_end_time))
+
+  def SendRandomAction(self, app):
+
+    def SuspendAndResume(app):
+      app.Suspend()
+      app.WaitUntilReachState(
+          lambda _app: not _app.ApplicationState().is_visible)
+      # Wait for 1 second before resume.
+      time.sleep(1)
+      app.Resume()
+
+    actions = {
+      'PlayPause': lambda _app: _app.PlayOrPause(),
+      'Fastforward': lambda _app: _app.Fastforward(),
+      'Rewind': lambda _app: _app.Rewind(),
+      'PlayPrevious': lambda _app: _app.PlayPrevious(),
+      'PlayNext': lambda _app: _app.PlayNext(),
+    }
+    if TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME):
+      actions['SuspendAndResume'] = SuspendAndResume
+
+    action_name = random.choice(list(actions.keys()))
+    logging.info('Send random action (%s).', action_name)
+    actions[action_name](app)
+
+  def test_playback_endurance(self):
+    self.start_time = time.time()
+    self.player_identifier = ''
+    self.playback_is_playing = False
+    self.playback_start_time = -1
+    self.last_state_check_time = 0
+    self.last_action_time = 0
+    self.ResetTimestamps()
+
+    app = self.CreateCobaltApp(self.startup_url)
+    app.AddPlayerStateChangeHandler(self.OnPlayerStateChanged)
+    with app:
+      while True:
+        # Put the sleep at the top of the loop.
+        time.sleep(STATUS_CHECK_INTERVAL)
+
+        current_running_time = time.time() - self.start_time
+        # Stop the test after reaching max running time.
+        if (self.max_running_time > 0 and
+            current_running_time > self.max_running_time):
+          break
+        # Skip if there's no running player.
+        if not app.player_state_handler.IsPlayerPlaying():
+          # TODO: identify network problem
+          self.assertTrue(
+              current_running_time - self.last_state_check_time <
+              PLAYER_INITIALIZATION_WAITING_TIMEOUT,
+              self.GenerateErrorString(
+                  'Timed out waiting for player initialization (waited: %f).' %
+                  (current_running_time - self.last_state_check_time)))
+          continue
+        self.last_state_check_time = current_running_time
+        # Skip to next playback if it has been played for long time.
+        if (self.playback_start_time != -1 and
+            current_running_time - self.playback_start_time >
+            SINGLE_PLAYBACK_WATCH_TIME_MAXIMUM):
+          app.PlayNext()
+          # Set start time to -1 here to avoid send same command in next loop.
+          self.playback_start_time = -1
+          continue
+        if self.playback_is_playing:
+          # Check media time.
+          if self.last_media_time_update_time > 0:
+            # TODO: identify network problem
+            self.assertTrue(
+                current_running_time - self.last_media_time_update_time <
+                MEDIA_TIME_UPDATE_WAITING_TIMEOUT,
+                self.GenerateErrorString(
+                    'Timed out waiting for media time update (waited: %f).' %
+                    (current_running_time - self.last_media_time_update_time)))
+          # Check written audio timestamp.
+          if (self.last_written_audio_update_time > 0 and
+              self.audio_eos_written_time == -1):
+            self.assertTrue(
+                current_running_time - self.last_written_audio_update_time <
+                WRITTEN_INPUT_WAITING_TIMEOUT,
+                self.GenerateErrorString(
+                    'Timed out waiting for new audio input (waited: %f).' %
+                    (current_running_time -
+                     self.last_written_audio_update_time)))
+          # Check written video timestamp.
+          if (self.last_written_video_update_time > 0 and
+              self.video_eos_written_time == -1):
+            self.assertTrue(
+                current_running_time - self.last_written_video_update_time <
+                WRITTEN_INPUT_WAITING_TIMEOUT,
+                self.GenerateErrorString(
+                    'Timed out waiting for new video input (waited: %f).' %
+                    (current_running_time -
+                     self.last_written_video_update_time)))
+          # Check if the playback ends properly.
+          if (self.audio_eos_written_time > 0 and
+              self.video_eos_written_time > 0 and self.playback_end_time > 0):
+            self.assertTrue(
+                current_running_time - self.playback_end_time <
+                PLAYBACK_END_WAITING_TIMEOUT,
+                self.GenerateErrorString(
+                    'Timed out waiting for playback to end (waited: %f).' %
+                    (current_running_time - self.playback_end_time,)))
+
+        # Send random actions.
+        if (self.needs_random_action and
+            current_running_time - self.last_action_time >
+            self.random_action_interval):
+          self.SendRandomAction(app)
+          self.last_action_time = current_running_time
+
+        # Interact with the app to keep it active.
+        if current_running_time - self.last_action_time > IDLE_TIME_MAXIMUM:
+          # Play the previous playback again.
+          app.PlayPrevious()
+          self.playback_start_time = -1
+
+      app.RemovePlayerStateChangeHandler(self.OnPlayerStateChanged)
diff --git a/src/cobalt/media_integration_tests/functionality/general_playback.py b/src/cobalt/media_integration_tests/functionality/general_playback.py
new file mode 100644
index 0000000..d90eca4
--- /dev/null
+++ b/src/cobalt/media_integration_tests/functionality/general_playback.py
@@ -0,0 +1,58 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+""" Tests for general playbacks with different formats."""
+
+import logging
+
+import _env  # pylint: disable=unused-import
+from cobalt.media_integration_tests.test_case import TestCase
+from cobalt.media_integration_tests.test_util import MimeStrings, PlaybackUrls
+
+
+class GeneralPlaybackTest(TestCase):
+  """
+    Test cases for general playbacks.
+  """
+
+  def run_test(self, url, mime=None):
+    app = self.CreateCobaltApp(url)
+    with app:
+      # Skip the test if the mime is not supported.
+      if mime and not app.IsMediaTypeSupported(mime):
+        logging.info('Mime type (%s) is not supported. Skip the test.', mime)
+        return
+      # Wait until the playback starts.
+      app.WaitUntilPlayerStart()
+      app.WaitUntilAdsEnd()
+      # Let the playback play for 10 seconds.
+      app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 10)
+
+TEST_PARAMETERS = [
+    ('H264', PlaybackUrls.H264_ONLY, None),
+    ('ENCRYPTED', PlaybackUrls.ENCRYPTED, None),
+    ('VR', PlaybackUrls.VR, None),
+    ('VP9', PlaybackUrls.VP9, MimeStrings.VP9),
+    ('VP9_HFR', PlaybackUrls.VP9_HFR, MimeStrings.VP9_HFR),
+    ('AV1', PlaybackUrls.AV1, MimeStrings.AV1),
+    ('AV1_HFR', PlaybackUrls.AV1_HFR, MimeStrings.AV1_HFR),
+    ('VERTICAL', PlaybackUrls.VERTICAL, None),
+    ('SHORT', PlaybackUrls.SHORT, None),
+    ('VP9_HDR_HLG', PlaybackUrls.VP9_HDR_HLG, MimeStrings.VP9_HDR_HLG),
+    ('VP9_HDR_PQ', PlaybackUrls.VP9_HDR_PQ, MimeStrings.VP9_HDR_PQ),
+    ('HDR_PQ_HFR', PlaybackUrls.HDR_PQ_HFR, MimeStrings.VP9_HDR_PQ_HFR),
+]
+
+for name, playback_url, mime_str in TEST_PARAMETERS:
+  TestCase.CreateTest(GeneralPlaybackTest, name, GeneralPlaybackTest.run_test,
+                      playback_url, mime_str)
diff --git a/src/cobalt/media_integration_tests/functionality/live_playback.py b/src/cobalt/media_integration_tests/functionality/live_playback.py
new file mode 100644
index 0000000..7390116
--- /dev/null
+++ b/src/cobalt/media_integration_tests/functionality/live_playback.py
@@ -0,0 +1,57 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+""" Tests for live playbacks."""
+
+import time
+
+import _env  # pylint: disable=unused-import
+from cobalt.media_integration_tests.test_case import TestCase
+from cobalt.media_integration_tests.test_util import PlaybackUrls
+
+
+class LivePlaybackTest(TestCase):
+  """
+    Test cases for live playbacks.
+  """
+
+  def run_test(self, url):
+    app = self.CreateCobaltApp(url)
+    with app:
+      # Wait until the playback starts.
+      app.WaitUntilPlayerStart()
+      app.WaitUntilAdsEnd()
+      # Let the playback play for 5 seconds.
+      app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 5)
+      # Pause the playback and wait for some time.
+      app.PlayOrPause()
+      app.WaitUntilReachState(
+          lambda _app: _app.PlayerState().video_element_state.paused)
+      time.sleep(2)
+      # Resume the playback.
+      app.PlayOrPause()
+      app.WaitUntilReachState(
+          lambda _app: not _app.PlayerState().video_element_state.paused)
+      # Let the playback play for another 5 seconds.
+      app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 5)
+
+
+TEST_PARAMETERS = [
+    ('LIVE', PlaybackUrls.LIVE),
+    ('LIVE_ULL', PlaybackUrls.LIVE_ULL),
+    ('LIVE_VR', PlaybackUrls.LIVE_VR),
+]
+
+for name, playback_url in TEST_PARAMETERS:
+  TestCase.CreateTest(LivePlaybackTest, name, LivePlaybackTest.run_test,
+                      playback_url)
diff --git a/src/cobalt/media_integration_tests/functionality/playback_controls.py b/src/cobalt/media_integration_tests/functionality/playback_controls.py
new file mode 100644
index 0000000..13b1ec8
--- /dev/null
+++ b/src/cobalt/media_integration_tests/functionality/playback_controls.py
@@ -0,0 +1,101 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+""" Tests for playback control shortcuts."""
+
+import logging
+
+import _env  # pylint: disable=unused-import
+from cobalt.media_integration_tests.test_case import TestCase
+from cobalt.media_integration_tests.test_util import PlaybackUrls
+
+
+class PlaybackControlsTest(TestCase):
+  """
+    Test cases for playback control shortcuts.
+  """
+
+  def test_play_pause(self):
+    app = self.CreateCobaltApp(PlaybackUrls.H264_ONLY)
+    with app:
+      # Wait until the playback starts.
+      app.WaitUntilPlayerStart()
+      app.WaitUntilAdsEnd()
+      # Let the playback play for 2 seconds.
+      app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2)
+      # Pause the playback.
+      app.PlayOrPause()
+      app.WaitUntilReachState(
+          lambda _app: _app.PlayerState().video_element_state.paused)
+      # Resume the playback.
+      app.PlayOrPause()
+      app.WaitUntilReachState(
+          lambda _app: not _app.PlayerState().video_element_state.paused)
+      # Let the playback play for another 2 seconds.
+      app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2)
+
+  def test_rewind_fastforward(self):
+    app = self.CreateCobaltApp(PlaybackUrls.H264_ONLY)
+    with app:
+      # Wait until the playback starts.
+      app.WaitUntilPlayerStart()
+      app.WaitUntilAdsEnd()
+      # Let the playback play for 2 seconds.
+      app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2)
+
+      logging.info('Fast forward the playback.')
+      old_media_time = app.CurrentMediaTime()
+      # Fastforward the playback by 10 seconds.
+      app.Fastforward()
+      app.WaitUntilReachState(
+          lambda _app: _app.CurrentMediaTime() > old_media_time + 10)
+      # Let the playback play for another 2 seconds.
+      app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2)
+
+      logging.info('Rewind the playback.')
+      old_media_time = app.CurrentMediaTime()
+      # Rewind the playback by 10 seconds.
+      app.Rewind()
+      app.WaitUntilReachState(
+          lambda _app: _app.CurrentMediaTime() < old_media_time)
+      # Let the playback play for another 2 seconds.
+      app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2)
+
+  def test_play_next_and_previous(self):
+    app = self.CreateCobaltApp(PlaybackUrls.PLAYLIST)
+    with app:
+      # Wait until the playback starts.
+      app.WaitUntilPlayerStart()
+      app.WaitUntilAdsEnd()
+      # Let the playback play for 2 seconds.
+      app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2)
+      # Play next track.
+      logging.info('Play next playback.')
+      app.PlayNext()
+      # Wait until the player is destroyed.
+      app.WaitUntilPlayerDestroyed()
+      # Wait until the playback starts.
+      app.WaitUntilPlayerStart()
+      app.WaitUntilAdsEnd()
+      # Let the playback play for another 2 seconds.
+      app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2)
+      # Play previous track.
+      logging.info('Play previous playback.')
+      app.PlayPrevious()
+      # Wait until the player is destroyed.
+      app.WaitUntilPlayerDestroyed()
+      # Wait until the playback starts.
+      app.WaitUntilPlayerStart()
+      app.WaitUntilAdsEnd()
+      # Let the playback play for another 2 seconds.
+      app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2)
diff --git a/src/cobalt/media_integration_tests/functionality/suspend_resume.py b/src/cobalt/media_integration_tests/functionality/suspend_resume.py
index e51215b..cc26c33 100644
--- a/src/cobalt/media_integration_tests/functionality/suspend_resume.py
+++ b/src/cobalt/media_integration_tests/functionality/suspend_resume.py
@@ -11,14 +11,15 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-""" Tests of suspend and resume during playing."""
+""" Tests for suspend and resume during playing."""
 
+import logging
 import time
-import unittest
 
 import _env  # pylint: disable=unused-import
 from cobalt.media_integration_tests.test_app import Features
 from cobalt.media_integration_tests.test_case import TestCase
+from cobalt.media_integration_tests.test_util import MimeStrings, PlaybackUrls
 
 
 class SuspendResumeTest(TestCase):
@@ -26,56 +27,46 @@
     Test cases for suspend and resume.
   """
 
-  def run_test_with_url(self, url):
+  def run_test(self, url, mime=None):
     app = self.CreateCobaltApp(url)
     with app:
+      # Skip the test if the mime is not supported.
+      if mime and not app.IsMediaTypeSupported(mime):
+        logging.info('Mime type (%s) is not supported. Skip the test.', mime)
+        return
       # Wait until the playback starts.
       app.WaitUntilPlayerStart()
       app.WaitUntilAdsEnd()
       # Let the playback play for 2 seconds.
       app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2)
       # Suspend the app and wait the app enters background.
+      logging.info('Suspend the application.')
       app.Suspend()
       app.WaitUntilReachState(
           lambda _app: not _app.ApplicationState().is_visible)
       # Wait for 1 second before resume.
       time.sleep(1)
       # Resume the app and let it play for a few time.
+      logging.info('Resume the application.')
       app.Resume()
       app.WaitUntilPlayerStart()
       app.WaitUntilAdsEnd()
       # Let the playback play for 2 seconds.
       app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2)
 
-  @unittest.skipIf(not TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME),
-                   'Suspend and resume is not supported on this platform.')
-  def test_playback_h264(self):
-    self.run_test_with_url('https://www.youtube.com/tv#/watch?v=RACW52qnJMI')
+TEST_PARAMETERS = [
+    ('H264', PlaybackUrls.H264_ONLY, None),
+    ('ENCRYPTED', PlaybackUrls.ENCRYPTED, None),
+    ('LIVE', PlaybackUrls.LIVE, None),
+    ('VP9', PlaybackUrls.VP9, MimeStrings.VP9),
+    ('AV1', PlaybackUrls.AV1, MimeStrings.AV1),
+    ('VERTICAL', PlaybackUrls.VERTICAL, None),
+    ('VR', PlaybackUrls.VR, None),
+]
 
-  @unittest.skipIf(not TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME),
-                   'Suspend and resume is not supported on this platform.')
-  def test_encrypted_playback(self):
-    self.run_test_with_url('https://www.youtube.com/tv#/watch?v=Vx5lkGS4w30')
-
-  @unittest.skipIf(not TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME),
-                   'Suspend and resume is not supported on this platform.')
-  def test_live_stream(self):
-    self.run_test_with_url('https://www.youtube.com/tv#/watch?v=KI1XlTQrsa0')
-
-  # Test for vp9 playback if supported.
-  @unittest.skipIf(not TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME),
-                   'Suspend and resume is not supported on this platform.')
-  def test_playback_vp9(self):
-    self.run_test_with_url('https://www.youtube.com/tv#/watch?v=1La4QzGeaaQ')
-
-  # Test for av1 playback if supported.
-  @unittest.skipIf(not TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME),
-                   'Suspend and resume is not supported on this platform.')
-  def test_playback_av1(self):
-    self.run_test_with_url('https://www.youtube.com/tv#/watch?v=iXvy8ZeCs5M')
-
-  # Test for vertical playback
-  @unittest.skipIf(not TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME),
-                   'Suspend and resume is not supported on this platform.')
-  def test_vertical_playback(self):
-    self.run_test_with_url('https://www.youtube.com/tv#/watch?v=jNQXAC9IVRw')
+if not TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME):
+  logging.info('Suspend and resume is not supported on this platform.')
+else:
+  for name, playback_url, mime_str in TEST_PARAMETERS:
+    TestCase.CreateTest(SuspendResumeTest, name, SuspendResumeTest.run_test,
+                        playback_url, mime_str)
diff --git a/src/cobalt/media_integration_tests/performance/codec_capability.py b/src/cobalt/media_integration_tests/performance/codec_capability.py
new file mode 100644
index 0000000..c117fd2
--- /dev/null
+++ b/src/cobalt/media_integration_tests/performance/codec_capability.py
@@ -0,0 +1,78 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+""" Tests of platform codec capabilities."""
+
+import logging
+
+import _env  # pylint: disable=unused-import
+from cobalt.media_integration_tests.test_case import TestCase
+from cobalt.media_integration_tests.test_util import MimeStrings, PlaybackUrls
+
+
+class CodecCapabilityTest(TestCase):
+  """
+    Test cases for platform codec capabilities.
+  """
+
+  # Returns a string which shows the max supported resolution, or "n/a" if the
+  # codec is not supported.
+  @staticmethod
+  def run_video_codec_test(app, codec_name, codec_mime):
+    if app.IsMediaTypeSupported(codec_mime):
+      for res_name, _ in reversed(MimeStrings.RESOLUTIONS.items()):
+        if app.IsMediaTypeSupported(
+            MimeStrings.create_video_mime_string(codec_mime, res_name)):
+          return '[%s, %s]' % (codec_name, res_name)
+    return '[%s, n/a]' % (codec_name)
+
+  # Returns a string which shows the max supported channels, or "n/a" if the
+  # codec is not supported.
+  @staticmethod
+  def run_audio_codec_test(app, codec_name, codec_mime):
+    if app.IsMediaTypeSupported(codec_mime):
+      for channels in [6, 4, 2]:
+        if app.IsMediaTypeSupported(
+            MimeStrings.create_audio_mime_string(codec_mime, channels)):
+          return '[%s, %s]' % (codec_name, channels)
+    return '[%s, n/a]' % (codec_name)
+
+  def test_video_codec_capability(self):
+    app = self.CreateCobaltApp(PlaybackUrls.DEFAULT)
+    mimes = [
+        ('H264', MimeStrings.H264),
+        ('VP9', MimeStrings.VP9),
+        ('VP9_HFR', MimeStrings.VP9_HFR),
+        ('AV1', MimeStrings.AV1),
+        ('AV1_HFR', MimeStrings.AV1_HFR),
+        ('VP9_HDR_HLG', MimeStrings.VP9_HDR_HLG),
+        ('VP9_HDR_PQ', MimeStrings.VP9_HDR_PQ),
+        ('VP9_HDR_PQ_HFR', MimeStrings.VP9_HDR_PQ_HFR),
+    ]
+    result = [' ***** Video codec capability test results: ']
+    with app:
+      for mime_name, mime_str in mimes:
+        result.append(self.run_video_codec_test(app, mime_name, mime_str))
+    logging.info(', '.join(result))
+
+  def test_audio_codec_capability(self):
+    app = self.CreateCobaltApp(PlaybackUrls.DEFAULT)
+    mimes = [
+        ('AAC', MimeStrings.AAC),
+        ('OPUS', MimeStrings.OPUS),
+    ]
+    result = [' ***** Audio codec capability test results: ']
+    with app:
+      for mime_name, mime_str in mimes:
+        result.append(self.run_audio_codec_test(app, mime_name, mime_str))
+    logging.info(', '.join(result))
diff --git a/src/cobalt/media_integration_tests/performance/dropped_frames.py b/src/cobalt/media_integration_tests/performance/dropped_frames.py
new file mode 100644
index 0000000..89f426c
--- /dev/null
+++ b/src/cobalt/media_integration_tests/performance/dropped_frames.py
@@ -0,0 +1,75 @@
+# Copyright 2021 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.
+""" Performance tests of dropped frames."""
+
+import argparse
+import logging
+
+import _env  # pylint: disable=unused-import
+from cobalt.media_integration_tests.test_case import TestCase
+from cobalt.media_integration_tests.test_util import MimeStrings, PlaybackUrls
+
+
+class DroppedFrameTest(TestCase):
+  """
+    Test cases for playback dropped frames.
+  """
+
+  def __init__(self, *args, **kwargs):
+    super(DroppedFrameTest, self).__init__(*args, **kwargs)
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--test_times', default=5, type=int)
+    args, _ = parser.parse_known_args()
+    self.test_times = args.test_times
+
+  def run_test_with_url(self, test_name, url, mime=None):
+    test_results = []
+    for _ in range(self.test_times):
+      app = self.CreateCobaltApp(url)
+      with app:
+        # Skip the test if the mime is not supported.
+        if mime and not app.IsMediaTypeSupported(mime):
+          logging.info(' ***** Dropped frame test result: [%s, n/a]', test_name)
+          return
+        # Wait until the playback starts.
+        app.WaitUntilPlayerStart()
+        app.WaitUntilAdsEnd()
+        # Let the playback play for 10 seconds.
+        app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 10)
+
+        total_frames = app.PlayerState().video_element_state.total_video_frames
+        dropped_frames = app.PlayerState(
+        ).video_element_state.dropped_video_frames
+        dropped_ratio = dropped_frames * 1.0 / total_frames
+        test_results.append([dropped_ratio, dropped_frames, total_frames])
+    logging.info(' ***** Dropped frame test result of %s : %r.', test_name,
+                 test_results)
+
+
+TEST_PARAMETERS = [
+    ('H264', PlaybackUrls.H264_ONLY, MimeStrings.H264),
+    ('VP9', PlaybackUrls.VP9, MimeStrings.VP9),
+    ('VP9_HFR', PlaybackUrls.VP9_HFR, MimeStrings.VP9_HFR),
+    ('AV1', PlaybackUrls.AV1, MimeStrings.AV1),
+    ('AV1_HFR', PlaybackUrls.AV1_HFR, MimeStrings.AV1_HFR),
+    ('VP9_HDR_HLG', PlaybackUrls.VP9_HDR_HLG, MimeStrings.VP9_HDR_HLG),
+    ('VP9_HDR_PQ', PlaybackUrls.VP9_HDR_PQ, MimeStrings.VP9_HDR_PQ),
+    ('VP9_HDR_PQ_HFR', PlaybackUrls.HDR_PQ_HFR, MimeStrings.VP9_HDR_PQ_HFR),
+]
+
+for name, playback_url, mime_str in TEST_PARAMETERS:
+  TestCase.CreateTest(DroppedFrameTest, name,
+                      DroppedFrameTest.run_test_with_url, name, playback_url,
+                      mime_str)
diff --git a/src/cobalt/media_integration_tests/performance/start_latency.py b/src/cobalt/media_integration_tests/performance/start_latency.py
new file mode 100644
index 0000000..62b32f1
--- /dev/null
+++ b/src/cobalt/media_integration_tests/performance/start_latency.py
@@ -0,0 +1,157 @@
+# Copyright 2021 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.
+""" Performance tests of playback operation latency."""
+
+import argparse
+import logging
+import time
+
+import _env  # pylint: disable=unused-import
+from cobalt.media_integration_tests.test_app import AdsState
+from cobalt.media_integration_tests.test_case import TestCase
+from cobalt.media_integration_tests.test_util import MimeStrings, PlaybackUrls
+
+
+class StartLatencyTest(TestCase):
+  """
+    Test cases for playback operation latency.
+  """
+
+  def __init__(self, *args, **kwargs):
+    super(StartLatencyTest, self).__init__(*args, **kwargs)
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--test_times', default=5, type=int)
+    args, _ = parser.parse_known_args()
+    self.test_times = args.test_times
+
+  def run_first_start_latency_test(self, test_name, url, mime=None):
+    test_results = []
+    while len(test_results) < self.test_times:
+      app = self.CreateCobaltApp(url)
+      with app:
+        # Skip the test if the mime is not supported.
+        if mime and not app.IsMediaTypeSupported(mime):
+          logging.info(' ***** First start latency test result of %s : [n/a].',
+                       test_name)
+          return
+        app.WaitUntilPlayerStart()
+        start_time = time.time()
+        start_media_time = app.CurrentMediaTime()
+        app.WaitUntilReachState(lambda _app: _app.PlayerState(
+        ).pipeline_state.first_written_video_timestamp != _app.PlayerState().
+                                pipeline_state.last_written_video_timestamp)
+        # Record inputs received time to know the preliminary impact of network.
+        inputs_received_time = time.time()
+        inputs_received_latency = inputs_received_time - start_time
+        # Let the playback play for 1 second.
+        app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 1)
+
+        # If it's playing ads, wait until ads end and run the test again.
+        if app.GetAdsState() != AdsState.NONE:
+          app.WaitUntilAdsEnd()
+          continue
+        media_time = app.CurrentMediaTime() - start_media_time
+        end_time = time.time()
+        start_latency = end_time - start_time - media_time
+        test_results.append([
+            start_latency, inputs_received_latency, start_time,
+            inputs_received_time, media_time, end_time
+        ])
+    logging.info(' ***** First start latency test result of %s : %r.',
+                 test_name, test_results)
+
+  def run_play_pause_latency_test(self, test_name, url, mime=None):
+    app = self.CreateCobaltApp(url)
+    with app:
+      # Skip the test if the mime is not supported.
+      if mime and not app.IsMediaTypeSupported(mime):
+        logging.info(' ***** Play/pause latency test result of %s : [n/a].',
+                     test_name)
+        return
+      test_results = []
+      # Wait until the playback starts.
+      app.WaitUntilPlayerStart()
+      app.WaitUntilAdsEnd()
+      while len(test_results) < self.test_times:
+        # Let the playback play for 1 second.
+        app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 1)
+        start_time = time.time()
+        # Pause the playback.
+        app.PlayOrPause()
+        app.WaitUntilReachState(
+            lambda _app: _app.PlayerState().video_element_state.paused)
+        pause_latency = time.time() - start_time
+
+        start_time = time.time()
+        # Resume the playback.
+        app.PlayOrPause()
+        app.WaitUntilReachState(
+            lambda _app: not _app.PlayerState().video_element_state.paused)
+
+        play_latency = time.time() - start_time
+        test_results.append([play_latency, pause_latency])
+      logging.info(' ***** Play/pause latency test result of %s : %r.',
+                   test_name, test_results)
+
+  def run_fastforward_latency_test(self, test_name, url, mime=None):
+    app = self.CreateCobaltApp(url)
+    with app:
+      # Skip the test if the mime is not supported.
+      if mime and not app.IsMediaTypeSupported(mime):
+        logging.info(' ***** Fastforward latency test result of %s : [n/a].',
+                     test_name)
+        return
+      test_results = []
+      # Wait until the playback starts.
+      app.WaitUntilPlayerStart()
+      app.WaitUntilAdsEnd()
+      # Let the playback play for 1 second.
+      app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 1)
+      while len(test_results) < self.test_times:
+        old_media_time = app.CurrentMediaTime()
+        start_time = time.time()
+        app.Fastforward()
+        app.WaitUntilReachState(lambda _app: _app.PlayerState(
+        ).pipeline_state.first_written_video_timestamp != _app.PlayerState().
+                                pipeline_state.last_written_video_timestamp)
+        # Record inputs received time to know the preliminary impact of network.
+        inputs_received_latency = time.time() - start_time
+        app.WaitUntilReachState(
+            lambda _app: _app.CurrentMediaTime() > old_media_time + 10)
+        fastforward_latency = time.time() - start_time
+        test_results.append([fastforward_latency, inputs_received_latency])
+
+      logging.info(' ***** Fastforward latency test result of %s : %r.',
+                   test_name, test_results)
+
+
+TEST_PARAMETERS = [
+    ('H264', PlaybackUrls.H264_ONLY, MimeStrings.H264),
+    ('VP9', PlaybackUrls.VP9, MimeStrings.VP9),
+    ('AV1', PlaybackUrls.AV1, MimeStrings.AV1),
+    ('VP9_HDR_HLG', PlaybackUrls.VP9_HDR_HLG, MimeStrings.VP9_HDR_HLG),
+    ('VP9_HDR_PQ', PlaybackUrls.VP9_HDR_PQ, MimeStrings.VP9_HDR_PQ),
+]
+
+for name, playback_url, mime_str in TEST_PARAMETERS:
+  TestCase.CreateTest(StartLatencyTest, 'first_start_latency_%s' % (name),
+                      StartLatencyTest.run_first_start_latency_test, name,
+                      playback_url, mime_str)
+  TestCase.CreateTest(StartLatencyTest, 'play_pause_latency_%s' % (name),
+                      StartLatencyTest.run_play_pause_latency_test, name,
+                      playback_url, mime_str)
+  TestCase.CreateTest(StartLatencyTest, 'fastforward_latency_%s' % (name),
+                      StartLatencyTest.run_fastforward_latency_test, name,
+                      playback_url, mime_str)
diff --git a/src/cobalt/media_integration_tests/test_app.py b/src/cobalt/media_integration_tests/test_app.py
index d0f62dd..cea1406 100644
--- a/src/cobalt/media_integration_tests/test_app.py
+++ b/src/cobalt/media_integration_tests/test_app.py
@@ -32,6 +32,8 @@
 WAIT_UNTIL_ADS_END_DEFAULT_TIMEOUT_SECONDS = 120
 WAIT_UNTIL_MEDIA_TIME_REACHED_DEFAULT_TIMEOUT_SECONDS = 30
 
+ACCOUNT_SELECTOR_ADD_ACCOUNT_TEXT = u'Add account'
+
 
 def GetValueFromQueryResult(query_result, key, default):
   if query_result is None:
@@ -83,7 +85,9 @@
   MEDIA_NEXT_TRACK = u'\uf000'
   MEDIA_PREV_TRACK = u'\uf001'
   MEDIA_STOP = u'\uf002'
-  MEDIA_PLAYPAUSE = u'\uf003'
+  MEDIA_PLAY_PAUSE = u'\uf003'
+  MEDIA_REWIND = u'\uf004'
+  MEDIA_FAST_FORWARD = u'\uf005'
 
 
 class Features(enum.Enum):
@@ -250,11 +254,12 @@
       const primary_pipeline_keys = h5vcc.cVal.keys().filter(key =>
         key.startsWith("Media.Pipeline.") &&
         key.endsWith("MaxVideoCapabilities") &&
-        h5vcc.cVal.getValue(key).length === 0)
+        h5vcc.cVal.getValue(key).length === 0);
       if (primary_pipeline_keys.length == 0) {
-        return "null"
+        return "null";
       }
-      const key_prefix = primary_pipeline_keys[0].slice(0, -".MaxVideoCapabilities".length)
+      const key_prefix = primary_pipeline_keys[0].slice(0,
+                          -".MaxVideoCapabilities".length);
       return {
         identifier: key_prefix.slice("Media.Pipeline.".length),
         is_started: h5vcc.cVal.getValue(key_prefix + '.Started'),
@@ -266,7 +271,8 @@
         playback_rate: h5vcc.cVal.getValue(key_prefix + '.PlaybackRate'),
         duration: h5vcc.cVal.getValue(key_prefix + '.Duration'),
         last_media_time: h5vcc.cVal.getValue(key_prefix + '.LastMediaTime'),
-        max_video_capabilities: h5vcc.cVal.getValue(key_prefix + '.MaxVideoCapabilities'),
+        max_video_capabilities: h5vcc.cVal.getValue(
+                                key_prefix + '.MaxVideoCapabilities'),
         seek_time: h5vcc.cVal.getValue(key_prefix + '.SeekTime'),
         first_written_audio_timestamp:
             h5vcc.cVal.getValue(key_prefix + '.FirstWrittenAudioTimestamp'),
@@ -278,11 +284,13 @@
             h5vcc.cVal.getValue(key_prefix + '.LastWrittenVideoTimestamp'),
         video_width: h5vcc.cVal.getValue(key_prefix + '.VideoWidth'),
         video_height: h5vcc.cVal.getValue(key_prefix + '.VideoHeight'),
-        is_audio_eos_written: h5vcc.cVal.getValue(key_prefix + '.IsAudioEOSWritten'),
-        is_video_eos_written: h5vcc.cVal.getValue(key_prefix + '.IsVideoEOSWritten'),
+        is_audio_eos_written: h5vcc.cVal.getValue(key_prefix +
+                              '.IsAudioEOSWritten'),
+        is_video_eos_written: h5vcc.cVal.getValue(key_prefix +
+                              '.IsVideoEOSWritten'),
         pipeline_status: h5vcc.cVal.getValue(key_prefix + '.PipelineStatus'),
         error_message: h5vcc.cVal.getValue(key_prefix + '.ErrorMessage'),
-      }
+      };
     """
 
   def IsPlaying(self):
@@ -340,7 +348,7 @@
       return {
           metadata: navigator.mediaSession.metadata,
           playbackState: navigator.mediaSession.playbackState,
-        }
+        };
     """
 
 
@@ -391,8 +399,8 @@
       const players = document.querySelectorAll("video");
       if (players && players.length > 0) {
         for (let i = 0; i < players.length; i++) {
-          const player = players[i]
-          const rect = player.getBoundingClientRect()
+          const player = players[i];
+          const rect = player.getBoundingClientRect();
           if (rect.width === window.innerWidth ||
               rect.height === window.innerHeight) {
             const quality = player.getVideoPlaybackQuality();
@@ -404,11 +412,11 @@
               volume: player.volume,
               dropped_video_frames: quality.droppedVideoFrames,
               total_video_frames: quality.totalVideoFrames,
-            }
+            };
           }
         }
       }
-      return "null"
+      return "null";
     """
 
 
@@ -522,9 +530,17 @@
 
   def __exit__(self, *args):
     self.should_exit.set()
-    self.periodic_query_thread.join(THREAD_EXIT_TIMEOUT_SECONDS)
+    if self.periodic_query_thread.is_alive():
+      self.periodic_query_thread.join(THREAD_EXIT_TIMEOUT_SECONDS)
     return self.runner.__exit__(*args)
 
+  def ExecuteScript(self, script):
+    try:
+      result = self.runner.webdriver.execute_script(script)
+    except Exception as e:
+      raise RuntimeError('Fail to excute script with error (%s).' % (str(e)))
+    return result
+
   def _OnNewLogLine(self, line):
     # Note that the function is called on cobalt runner reader thread.
     # pass
@@ -584,7 +600,7 @@
     self.player_state_handler.AddPlayerStateChangeHandler(handler)
 
   def RemovePlayerStateChangeHandler(self, handler):
-    self.player_state_handler.RemoveAppStateChangeHandler(handler)
+    self.player_state_handler.RemovePlayerStateChangeHandler(handler)
 
   # The handler will receive parameters (TestApp, String, Dictionary).
   def RegisterPeriodicQuery(self, query_name, query_js_code, result_handler):
@@ -620,12 +636,7 @@
         # Generate javascript code and execute it.
         js_code = self._GeneratePeriodicQueryJsCode(local_is_queries_changed,
                                                     local_periodic_queries)
-        try:
-          result = self.runner.webdriver.execute_script(js_code)
-        except Exception as e:
-          raise RuntimeError('Periodic queries failed with error (%s)' %
-                             (str(e)))
-
+        result = self.ExecuteScript(js_code)
         for query_name in local_periodic_queries.keys():
           if not result.get(query_name):
             raise RuntimeError(
@@ -636,11 +647,11 @@
       time.sleep(PERIODIC_QUERIES_INTERVAL_SECONDS)
 
   _PERIODIC_QUERIES_JS_CODE = """
-    var ret = {}
-    for(var key in _media_integration_testing_queries) {
-      ret[key] = _media_integration_testing_queries[key]()
+    let ret = {};
+    for(let key in _media_integration_testing_queries) {
+      ret[key] = _media_integration_testing_queries[key]();
     }
-    return ret
+    return ret;
   """
 
   def _GeneratePeriodicQueryJsCode(self, is_queries_changed, periodic_queries):
@@ -658,7 +669,10 @@
     return js_code
 
   # The first input of |state_lambda| is an instance of TestApp.
-  def WaitUntilReachState(self, state_lambda, timeout=WAIT_UNTIL_REACH_STATE_DEFAULT_TIMEOUT_SECONDS):
+  def WaitUntilReachState(
+      self,
+      state_lambda,
+      timeout=WAIT_UNTIL_REACH_STATE_DEFAULT_TIMEOUT_SECONDS):
     start_time = time.time()
     while not state_lambda(self) and time.time() - start_time < timeout:
       time.sleep(WAIT_INTERVAL_SECONDS)
@@ -667,36 +681,94 @@
       raise RuntimeError('WaitUntilReachState timed out after (%f) seconds.' %
                          (execute_interval))
 
-  def WaitUntilPlayerStart(self,
-                           timeout=WAIT_UNTIL_REACH_STATE_DEFAULT_TIMEOUT_SECONDS
-                          ):
-    self.WaitUntilReachState(
-        lambda _app: _app.player_state_handler.IsPlayerPlaying(), timeout)
+  # The result is an array of overlay header text contents.
+  _OVERLAY_QUERY_JS_CODE = """
+    let result = [];
+    if (document.getElementsByTagName(
+         "YTLR-OVERLAY-PANEL-HEADER-RENDERER").length > 0) {
+      let childNodes = document.getElementsByTagName(
+         "YTLR-OVERLAY-PANEL-HEADER-RENDERER")[0].childNodes;
+      for (let i = 0; i < childNodes.length; i++) {
+        result.push(childNodes[i].textContent);
+      }
+    }
+    return result;
+  """
 
-  # TODO: Need to verify if it works without corp network.
-  # TODO: Needs to recognize and skip survery.
+  def WaitUntilPlayerStart(
+      self, timeout=WAIT_UNTIL_REACH_STATE_DEFAULT_TIMEOUT_SECONDS):
+
+    def PlayerStateCheckCallback(app):
+      # Check if it's showing account selector page.
+      is_showing_account_selector = self.ExecuteScript(
+          'return document.getElementsByTagName("YTLR-ACCOUNT-SELECTOR")'
+          '.length > 0;')
+      if is_showing_account_selector:
+        active_element_label_attr = self.ExecuteScript(
+            'return document.activeElement.getAttribute("aria-label");')
+        if active_element_label_attr != ACCOUNT_SELECTOR_ADD_ACCOUNT_TEXT:
+          logging.info('Select an account (%s) to continue the test.',
+                       active_element_label_attr)
+          self.SendKeys(webdriver_keys.Keys.ENTER)
+        else:
+          logging.info('Current selected item is "Add acount", move to next'
+                       ' item.')
+          self.SendKeys(webdriver_keys.Keys.ARROW_RIGHT)
+        return False
+      # Check if it's showing a playback survey.
+      is_showing_skip_button = self.ExecuteScript(
+          'return document.getElementsByTagName("YTLR-SKIP-BUTTON-RENDERER")'
+          '.length > 0;')
+      # When there's a skip button and no running player, it's showing a
+      # survey.
+      if (is_showing_skip_button and
+          not app.player_state_handler.IsPlayerPlaying()):
+        self.SendKeys(webdriver_keys.Keys.ENTER)
+        logging.info('Send enter key event to skip the survey.')
+        return False
+      # Check if it's showing an overlay.
+      overlay_query_result = self.ExecuteScript(self._OVERLAY_QUERY_JS_CODE)
+      if len(overlay_query_result) > 0:
+        # Note that if there're playback errors, after close the overlay,
+        # the test will end with timeout error.
+        self.SendKeys(webdriver_keys.Keys.ENTER)
+        logging.info(
+            'Send enter key event to close the overlay. Overlay '
+            'headers : %r', overlay_query_result)
+        return False
+
+      return app.player_state_handler.IsPlayerPlaying()
+
+    self.WaitUntilReachState(PlayerStateCheckCallback, timeout)
+
+  def WaitUntilPlayerDestroyed(
+      self, timeout=WAIT_UNTIL_REACH_STATE_DEFAULT_TIMEOUT_SECONDS):
+    current_identifier = self.PlayerState().pipeline_state.identifier
+    if not current_identifier or len(current_identifier) == 0:
+      raise RuntimeError('No existing player when calling'
+                         'WaitUntilPlayerDestroyed.')
+    self.WaitUntilReachState(
+        lambda _app: _app.PlayerState().pipeline_state.identifier !=
+        current_identifier, timeout)
+
   """
     The return values of the query, exact mapping of AdsState defined earlier
     in this file.
   """
   _GET_ADS_STATE_QUERY_JS_CODE = """
-    if( document.getElementsByTagName("YTLR-AD-PREVIEW-RENDERER").length > 0 ) {
-      return 1
+    if (document.getElementsByTagName("YTLR-AD-PREVIEW-RENDERER").length > 0) {
+      return 1;
     }
-    else if( document.getElementsByTagName("YTLR-SKIP-BUTTON-RENDERER").length > 0 ) {
-      return 2
+    if (document.getElementsByTagName("YTLR-SKIP-BUTTON-RENDERER").length > 0) {
+      return 2;
     }
-    return 0
+    return 0;
   """
 
   def GetAdsState(self):
     if not self.runner.test_script_started.is_set():
       raise RuntimeError('Webdriver is not ready yet')
-    try:
-      result = self.runner.webdriver.execute_script(
-          self._GET_ADS_STATE_QUERY_JS_CODE)
-    except Exception as e:
-      raise RuntimeError('Ads query failed with error (%s)' % (str(e)))
+    result = self.ExecuteScript(self._GET_ADS_STATE_QUERY_JS_CODE)
     return AdsState(result)
 
   def WaitUntilAdsEnd(self, timeout=WAIT_UNTIL_ADS_END_DEFAULT_TIMEOUT_SECONDS):
@@ -730,15 +802,49 @@
     if start_media_time > media_time:
       return
 
-    # Wait until playback starts, otherwise playback rate could be 0.
+    # Wait until playback starts.
     self.WaitUntilReachState(
-        lambda _app: _app.CurrentMediaTime() > start_media_time, timeout)
+        lambda _app: _app.PlayerState().pipeline_state.playback_rate > 0,
+        timeout)
 
+    duration = self.PlayerState().video_element_state.duration
+    if media_time > duration:
+      logging.info(
+          'Requested media time (%f) is greater than the duration (%f)',
+          media_time, duration)
+      media_time = duration
+
+    time_to_play = max(media_time - self.CurrentMediaTime(), 0)
     playback_rate = self.PlayerState().pipeline_state.playback_rate
-    if playback_rate == 0:
-      raise NotImplementedError
-
-    adjusted_timeout = (media_time -
-                        self.CurrentMediaTime()) / playback_rate + timeout
+    adjusted_timeout = time_to_play / playback_rate + timeout
     self.WaitUntilReachState(lambda _app: _app.CurrentMediaTime() > media_time,
                              adjusted_timeout)
+
+  def IsMediaTypeSupported(self, mime):
+    return self.ExecuteScript('return MediaSource.isTypeSupported("%s");' %
+                              (mime))
+
+  def PlayOrPause(self):
+    self.SendKeys(AdditionalKeys.MEDIA_PLAY_PAUSE)
+
+  def Fastforward(self):
+    # The first fastforward will only bring up the progress bar.
+    self.SendKeys(AdditionalKeys.MEDIA_FAST_FORWARD)
+    # The second fastforward will forward the playback by 10 seconds.
+    self.SendKeys(AdditionalKeys.MEDIA_FAST_FORWARD)
+    # Press play button to start the playback.
+    self.SendKeys(AdditionalKeys.MEDIA_PLAY_PAUSE)
+
+  def Rewind(self):
+    # The first rewind will only bring up the progress bar.
+    self.SendKeys(AdditionalKeys.MEDIA_REWIND)
+    # The second rewind will rewind the playback by 10 seconds.
+    self.SendKeys(AdditionalKeys.MEDIA_REWIND)
+    # It needs to press play button to start the playback.
+    self.SendKeys(AdditionalKeys.MEDIA_PLAY_PAUSE)
+
+  def PlayPrevious(self):
+    self.SendKeys(AdditionalKeys.MEDIA_PREV_TRACK)
+
+  def PlayNext(self):
+    self.SendKeys(AdditionalKeys.MEDIA_NEXT_TRACK)
diff --git a/src/cobalt/media_integration_tests/test_case.py b/src/cobalt/media_integration_tests/test_case.py
index 09e2ec2..04b942a 100644
--- a/src/cobalt/media_integration_tests/test_case.py
+++ b/src/cobalt/media_integration_tests/test_case.py
@@ -59,3 +59,9 @@
   def IsFeatureSupported(feature):
     global _supported_features
     return _supported_features[feature]
+
+  @staticmethod
+  def CreateTest(test_class, test_name, test_function, *args):
+    test_method = lambda self: test_function(self, *args)
+    test_method.__name__ = 'test_%s' % test_name
+    setattr(test_class, test_method.__name__, test_method)
diff --git a/src/cobalt/media_integration_tests/test_util.py b/src/cobalt/media_integration_tests/test_util.py
new file mode 100644
index 0000000..c2153327
--- /dev/null
+++ b/src/cobalt/media_integration_tests/test_util.py
@@ -0,0 +1,87 @@
+# Copyright 2021 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 module of base media integration test case."""
+
+from collections import OrderedDict
+
+import _env  # pylint: disable=unused-import
+
+
+class MimeStrings():
+  """
+    Set of playback mime strings.
+  """
+
+  H264 = 'video/mp4; codecs=\\"avc1.4d4015\\"'
+  VP9 = 'video/webm; codecs=\\"vp9\\"'
+  VP9_HFR = 'video/webm; codecs=\\"vp9\\"; framerate=60'
+  AV1 = 'video/mp4; codecs=\\"av01.0.08M.08\\"'
+  AV1_HFR = 'video/mp4; codecs=\\"av01.0.08M.08\\"; framerate=60'
+  VP9_HDR_HLG = 'video/webm; codecs=\\"vp09.02.51.10.01.09.18.09.00\\"'
+  VP9_HDR_PQ = 'video/webm; codecs=\\"vp09.02.51.10.01.09.16.09.00\\"'
+  VP9_HDR_PQ_HFR = ('video/webm; codecs=\\"vp09.02.51.10.01.09.16.09.00\\";'
+                    'framerate=60')
+
+  AAC = 'audio/mp4; codecs=\\"mp4a.40.2\\"'
+  OPUS = 'audio/webm; codecs=\\"opus\\"'
+
+  RESOLUTIONS = OrderedDict([('140P', 'width=256; height=144'),
+                             ('240P', 'width=352; height=240'),
+                             ('360P', 'width=480; height=360'),
+                             ('480P', 'width=640; height=480'),
+                             ('720P', 'width=1280; height=720'),
+                             ('1080P', 'width=1920; height=1080'),
+                             ('2K', 'width=2560; height=1440'),
+                             ('4K', 'width=3840; height=2160'),
+                             ('8K', 'width=7680; height=4320')])
+
+  @staticmethod
+  def create_video_mime_string(codec, resolution):
+    return '%s; %s' % (codec, MimeStrings.RESOLUTIONS[resolution])
+
+  @staticmethod
+  def create_audio_mime_string(codec, channels):
+    return '%s; channels=%d' % (codec, channels)
+
+
+class PlaybackUrls():
+  """
+    Set of testing video urls.
+  """
+
+  DEFAULT = 'https://www.youtube.com/tv'
+  H264_ONLY = 'https://www.youtube.com/tv#/watch?v=RACW52qnJMI'
+  ENCRYPTED = 'https://www.youtube.com/tv#/watch?v=iNvUS1dnwfw'
+
+  VP9 = 'https://www.youtube.com/tv#/watch?v=x7GkebUe6XQ'
+  VP9_HFR = 'https://www.youtube.com/tv#/watch?v=Jsjtt5dWDYU'
+  AV1 = 'https://www.youtube.com/tv#/watch?v=iXvy8ZeCs5M'
+  AV1_HFR = 'https://www.youtube.com/tv#/watch?v=9jZ01i92JI8'
+  VERTICAL = 'https://www.youtube.com/tv#/watch?v=jNQXAC9IVRw'
+  SHORT = 'https://www.youtube.com/tv#/watch?v=NEf8Ug49FEw'
+  VP9_HDR_HLG = 'https://www.youtube.com/tv#/watch?v=ebhEiRWGvZM'
+  VP9_HDR_PQ = 'https://www.youtube.com/tv#/watch?v=Rw-qEKR5uv8'
+  HDR_PQ_HFR = 'https://www.youtube.com/tv#/watch?v=LXb3EKWsInQ'
+  VR = 'https://www.youtube.com/tv#/watch?v=Ei0fgLfJ6Tk'
+
+  LIVE = 'https://www.youtube.com/tv#/watch?v=o7UP6i4PAbk'
+  LIVE_ULL = 'https://www.youtube.com/tv#/watch?v=KI1XlTQrsa0'
+  LIVE_VR = 'https://www.youtube.com/tv#/watch?v=soeo5OPv7CA'
+
+  PLAYLIST = ('https://www.youtube.com/tv#/watch?list='
+              'PLynQTqo4blSSCMPUGcH2YnbSrV84AnsRD')
+  # The link refers to a video in the middle of the playlist, so that
+  # it can be used to test both play previous and next.
+  PLAYLIST_MID = ('https://www.youtube.com/tv#/watch?'
+                  'v=9WrgRpOJr0I&list=PLynQTqo4blSSCMPUGcH2YnbSrV84AnsRD')
diff --git a/src/cobalt/renderer/egl_and_gles.h b/src/cobalt/renderer/egl_and_gles.h
index 59d0dc8..fdf1839 100644
--- a/src/cobalt/renderer/egl_and_gles.h
+++ b/src/cobalt/renderer/egl_and_gles.h
@@ -18,40 +18,29 @@
 #include "base/logging.h"
 #include "starboard/common/log.h"
 #include "starboard/configuration.h"
-
-// Defining COBALT_EGL_AND_GLES_LOGGING enables a greater amount of logging and
-// error reporting with EGL and GLES calls throughout Cobalt. Each invoked
-// function will be logged, and when checks are failed the EGL or GLES error
-// code will be outputted.
-#undef COBALT_EGL_AND_GLES_LOGGING
-
 #include "starboard/egl.h"
 #include "starboard/gles.h"
+
 #define EGL_CALL_PREFIX ::cobalt::renderer::CobaltGetEglInterface().
 #define GL_CALL_PREFIX ::cobalt::renderer::CobaltGetGlesInterface().
 
-#if defined(COBALT_EGL_AND_GLES_LOGGING)
-#define EGL_DCHECK_MAYBE_LOG(x) SB_LOG(INFO) << #x;
-#define GL_DCHECK_MAYBE_LOG(x) SB_LOG(INFO) << #x;
-#else  // !defined(COBALT_EGL_AND_GLES_LOGGING)
-#define EGL_DCHECK_MAYBE_LOG(x)
-#define GL_DCHECK_MAYBE_LOG(x)
-#endif  // defined(COBALT_EGL_AND_GLES_LOGGING)
-
+#if SB_DCHECK_ENABLED
 #define EGL_DCHECK(x)                                                 \
   do {                                                                \
-    EGL_DCHECK_MAYBE_LOG(x);                                          \
     const int32_t COBALT_EGL_ERRNO = (EGL_CALL_PREFIX eglGetError()); \
     SB_DCHECK(COBALT_EGL_ERRNO == EGL_SUCCESS)                        \
         << #x << " exited with code: " << COBALT_EGL_ERRNO;           \
   } while (false)
 #define GL_DCHECK(x)                                               \
   do {                                                             \
-    GL_DCHECK_MAYBE_LOG(x);                                        \
     const int32_t COBALT_GL_ERRNO = (GL_CALL_PREFIX glGetError()); \
     SB_DCHECK(COBALT_GL_ERRNO == GL_NO_ERROR)                      \
         << #x << " exited with code: " << COBALT_GL_ERRNO;         \
   } while (false)
+#else
+#define EGL_DCHECK(x)
+#define GL_DCHECK(x)
+#endif  // SB_DCHECK_ENABLED
 
 namespace cobalt {
 namespace renderer {
@@ -92,21 +81,8 @@
     GL_DCHECK(x);     \
   } while (false)
 
-#if defined(COBALT_EGL_AND_GLES_LOGGING)
-#define EGL_CALL_SIMPLE(x)    \
-  ([&]() {                    \
-    SB_LOG(INFO) << #x;       \
-    return EGL_CALL_PREFIX x; \
-  }())
-#define GL_CALL_SIMPLE(x)    \
-  ([&]() {                   \
-    SB_LOG(INFO) << #x;      \
-    return GL_CALL_PREFIX x; \
-  }())
-#else  // !defined(COBALT_EGL_AND_GLES_LOGGING)
 #define EGL_CALL_SIMPLE(x) (EGL_CALL_PREFIX x)
 #define GL_CALL_SIMPLE(x) (GL_CALL_PREFIX x)
-#endif  // defined(COBALT_EGL_AND_GLES_LOGGING)
 
 // EGL TYPES
 #define EGLint SbEglInt32
diff --git a/src/cobalt/updater/configurator.cc b/src/cobalt/updater/configurator.cc
index b14e2d1..ec7b09d 100644
--- a/src/cobalt/updater/configurator.cc
+++ b/src/cobalt/updater/configurator.cc
@@ -7,7 +7,9 @@
 #include <set>
 #include <utility>
 
+#include "base/command_line.h"
 #include "base/version.h"
+#include "cobalt/browser/switches.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/updater/network_fetcher.h"
 #include "cobalt/updater/patcher.h"
@@ -86,7 +88,12 @@
 int Configurator::UpdateDelay() const { return 0; }
 
 std::vector<GURL> Configurator::UpdateUrl() const {
-  return std::vector<GURL>{GURL(kUpdaterJSONDefaultUrl)};
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          browser::switches::kUseQAUpdateServer)) {
+    return std::vector<GURL>{GURL(kUpdaterJSONDefaultUrlQA)};
+  } else {
+    return std::vector<GURL>{GURL(kUpdaterJSONDefaultUrl)};
+  }
 }
 
 std::vector<GURL> Configurator::PingUrl() const { return UpdateUrl(); }
diff --git a/src/cobalt/updater/network_fetcher.cc b/src/cobalt/updater/network_fetcher.cc
index fb7ea98..1dfbc7c 100644
--- a/src/cobalt/updater/network_fetcher.cc
+++ b/src/cobalt/updater/network_fetcher.cc
@@ -129,6 +129,13 @@
   url_fetcher_->Start();
 }
 
+void NetworkFetcher::CancelDownloadToFile() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  SB_LOG(INFO) << "Canceling DownloadToFile";
+  url_fetcher_.reset();
+}
+
 void NetworkFetcher::OnURLFetchResponseStarted(const net::URLFetcher* source) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   std::move(response_started_callback_)
diff --git a/src/cobalt/updater/network_fetcher.h b/src/cobalt/updater/network_fetcher.h
index 1ff2675..17b8909 100644
--- a/src/cobalt/updater/network_fetcher.h
+++ b/src/cobalt/updater/network_fetcher.h
@@ -69,6 +69,7 @@
                       ProgressCallback progress_callback,
                       DownloadToFileCompleteCallback
                           download_to_file_complete_callback) override;
+  void CancelDownloadToFile() override;
 
   // net::URLFetcherDelegate interface.
   void OnURLFetchResponseStarted(const net::URLFetcher* source) override;
diff --git a/src/cobalt/updater/updater_constants.h b/src/cobalt/updater/updater_constants.h
index aaaf48c..99f5759 100644
--- a/src/cobalt/updater/updater_constants.h
+++ b/src/cobalt/updater/updater_constants.h
@@ -47,8 +47,10 @@
 
 // URLs.
 //
-// Omaha server end point.
+// Update server end point (prod).
 extern const char kUpdaterJSONDefaultUrl[];
+// Update server end point (qa).
+extern const char kUpdaterJSONDefaultUrlQA[];
 
 // The URL where crash reports are uploaded.
 extern const char kCrashUploadURL[];
diff --git a/src/cobalt/updater/updater_module.cc b/src/cobalt/updater/updater_module.cc
index 8f5663f..3941cb6 100644
--- a/src/cobalt/updater/updater_module.cc
+++ b/src/cobalt/updater/updater_module.cc
@@ -174,6 +174,7 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   update_client_->RemoveObserver(updater_observer_.get());
   updater_observer_.reset();
+  update_client_->Stop();
   update_client_ = nullptr;
 
   if (updater_configurator_ != nullptr) {
diff --git a/src/cobalt/webdriver/keyboard.cc b/src/cobalt/webdriver/keyboard.cc
index b65672a..59331ac 100644
--- a/src/cobalt/webdriver/keyboard.cc
+++ b/src/cobalt/webdriver/keyboard.cc
@@ -171,17 +171,21 @@
   kSpecialKey_MediaPrevTrack,
   kSpecialKey_MediaStop,
   kSpecialKey_MediaPlayPause,
-  kLastAdditionalSpecialKey = kSpecialKey_MediaPlayPause,
+  kSpecialKey_MediaRewind,
+  kSpecialKey_MediaFastForward,
+  kLastAdditionalSpecialKey = kSpecialKey_MediaFastForward,
 };
 
 // Mapping from an additional special keycode to virtual keycode. Subtract
 // kFirstAdditionalSpecialKey from the integer value of the WebDriver keycode
 // and index into this table.
 const int32 additional_special_keycode_mapping[] = {
-    dom::keycode::kMediaNextTrack,  // kSpecialKey_MediaNextTrack,
-    dom::keycode::kMediaPrevTrack,  // kSpecialKey_MediaPrevTrack,
-    dom::keycode::kMediaStop,       // kSpecialKey_MediaStop,
-    dom::keycode::kMediaPlayPause,  // kSpecialKey_MediaPlayPause,
+    dom::keycode::kMediaNextTrack,    // kMediaNextTrack,
+    dom::keycode::kMediaPrevTrack,    // kMediaPrevTrack,
+    dom::keycode::kMediaStop,         // kMediaStop,
+    dom::keycode::kMediaPlayPause,    // kMediaPlayPause,
+    dom::keycode::kMediaRewind,       // kMediaRewind,
+    dom::keycode::kMediaFastForward,  // kMediaFastForward,
 };
 
 // Check that the mapping is the expected size.
diff --git a/src/components/update_client/component.cc b/src/components/update_client/component.cc
index 787ec2e..421dc40 100644
--- a/src/components/update_client/component.cc
+++ b/src/components/update_client/component.cc
@@ -300,6 +300,13 @@
       base::BindOnce(&Component::ChangeState, base::Unretained(this)));
 }
 
+#if defined(STARBOARD)
+void Component::Cancel() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  state_->Cancel();
+}
+#endif
+
 void Component::ChangeState(std::unique_ptr<State> next_state) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
@@ -532,6 +539,14 @@
   DoHandle();
 }
 
+#if defined(STARBOARD)
+void Component::State::Cancel() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  // Further work may be needed to ensure cancelation during any state results
+  // in a clear result and no memory leaks.
+}
+#endif
+
 void Component::State::TransitionState(std::unique_ptr<State> next_state) {
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(next_state);
@@ -726,6 +741,13 @@
   DCHECK(thread_checker_.CalledOnValidThread());
 }
 
+#if defined(STARBOARD)
+void Component::StateDownloadingDiff::Cancel() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  crx_downloader_->CancelDownload();
+}
+#endif
+
 void Component::StateDownloadingDiff::DoHandle() {
   DCHECK(thread_checker_.CalledOnValidThread());
 
@@ -800,6 +822,13 @@
   DCHECK(thread_checker_.CalledOnValidThread());
 }
 
+#if defined(STARBOARD)
+void Component::StateDownloading::Cancel() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  crx_downloader_->CancelDownload();
+}
+#endif
+
 void Component::StateDownloading::DoHandle() {
   DCHECK(thread_checker_.CalledOnValidThread());
 
diff --git a/src/components/update_client/component.h b/src/components/update_client/component.h
index 4058985..f6cc035 100644
--- a/src/components/update_client/component.h
+++ b/src/components/update_client/component.h
@@ -55,6 +55,12 @@
   // to the next component state before |callback_handle_complete_| is invoked.
   void Handle(CallbackHandleComplete callback_handle_complete);
 
+#if defined(STARBOARD)
+  // Stops update progress for the component and may clean resources used in its
+  // current state.
+  void Cancel();
+#endif
+
   CrxUpdateItem GetCrxUpdateItem() const;
 
   // Sets the uninstall state for this component.
@@ -156,6 +162,11 @@
     // by the outer component, after the current state is fully handled.
     void Handle(CallbackNextState callback);
 
+#if defined(STARBOARD)
+    // Stops update progress and may clean resources used in the current state.
+    virtual void Cancel();
+#endif
+
     ComponentState state() const { return state_; }
 
    protected:
@@ -247,6 +258,9 @@
    public:
     explicit StateDownloadingDiff(Component* component);
     ~StateDownloadingDiff() override;
+#if defined(STARBOARD)
+    void Cancel() override;
+#endif
 
    private:
     // State overrides.
@@ -270,6 +284,9 @@
    public:
     explicit StateDownloading(Component* component);
     ~StateDownloading() override;
+#if defined(STARBOARD)
+    void Cancel() override;
+#endif
 
    private:
     // State overrides.
diff --git a/src/components/update_client/crx_downloader.cc b/src/components/update_client/crx_downloader.cc
index fe83b57..8f2bffa 100644
--- a/src/components/update_client/crx_downloader.cc
+++ b/src/components/update_client/crx_downloader.cc
@@ -120,6 +120,12 @@
   DoStartDownload(*current_url_);
 }
 
+#if defined(STARBOARD)
+void CrxDownloader::CancelDownload() {
+  DoCancelDownload();
+}
+#endif
+
 void CrxDownloader::OnDownloadComplete(
     bool is_handled,
     const Result& result,
diff --git a/src/components/update_client/crx_downloader.h b/src/components/update_client/crx_downloader.h
index 6378d03..ae19a4a 100644
--- a/src/components/update_client/crx_downloader.h
+++ b/src/components/update_client/crx_downloader.h
@@ -122,6 +122,10 @@
                      const std::string& expected_hash,
                      DownloadCallback download_callback);
 
+#if defined(STARBOARD)
+  void CancelDownload();
+#endif
+
   const std::vector<DownloadMetrics> download_metrics() const;
 
  protected:
@@ -151,6 +155,9 @@
 
  private:
   virtual void DoStartDownload(const GURL& url) = 0;
+#if defined(STARBOARD)
+  virtual void DoCancelDownload() = 0;
+#endif
 
   void VerifyResponse(bool is_handled,
                       Result result,
diff --git a/src/components/update_client/network.h b/src/components/update_client/network.h
index daed162..dec108f 100644
--- a/src/components/update_client/network.h
+++ b/src/components/update_client/network.h
@@ -63,6 +63,9 @@
       ResponseStartedCallback response_started_callback,
       ProgressCallback progress_callback,
       DownloadToFileCompleteCallback download_to_file_complete_callback) = 0;
+#if defined(STARBOARD)
+  virtual void CancelDownloadToFile() = 0;
+#endif
 
  protected:
   NetworkFetcher() = default;
diff --git a/src/components/update_client/task_update.cc b/src/components/update_client/task_update.cc
index ab445b5..0129e4c 100644
--- a/src/components/update_client/task_update.cc
+++ b/src/components/update_client/task_update.cc
@@ -36,13 +36,25 @@
     return;
   }
 
+#if defined(STARBOARD)
+  update_engine_->Update(is_foreground_, ids_, std::move(crx_data_callback_),
+                         base::BindOnce(&TaskUpdate::TaskComplete, this),
+                         cancelation_closure_);
+#else
   update_engine_->Update(is_foreground_, ids_, std::move(crx_data_callback_),
                          base::BindOnce(&TaskUpdate::TaskComplete, this));
+#endif
 }
 
 void TaskUpdate::Cancel() {
   DCHECK(thread_checker_.CalledOnValidThread());
 
+#if defined(STARBOARD)
+  if (cancelation_closure_) {  // The engine's picked up the task.
+    std::move(cancelation_closure_).Run();
+  }
+#endif
+
   TaskComplete(Error::UPDATE_CANCELED);
 }
 
diff --git a/src/components/update_client/task_update.h b/src/components/update_client/task_update.h
index 4df796b..74c0a6a 100644
--- a/src/components/update_client/task_update.h
+++ b/src/components/update_client/task_update.h
@@ -57,6 +57,9 @@
   const std::vector<std::string> ids_;
   UpdateClient::CrxDataCallback crx_data_callback_;
   Callback callback_;
+#if defined(STARBOARD)
+  base::OnceClosure cancelation_closure_;
+#endif
 
   DISALLOW_COPY_AND_ASSIGN(TaskUpdate);
 };
diff --git a/src/components/update_client/update_client.cc b/src/components/update_client/update_client.cc
index 369c6d3..6ce65bf 100644
--- a/src/components/update_client/update_client.cc
+++ b/src/components/update_client/update_client.cc
@@ -201,6 +201,16 @@
 
   is_stopped_ = true;
 
+  // Cancel the pending tasks. These tasks are safe to cancel and delete since
+  // they have not picked up by the update engine, and not shared with any
+  // task runner yet.
+  while (!task_queue_.empty()) {
+    auto task = task_queue_.front();
+    task_queue_.pop_front();
+    task->Cancel();
+  }
+
+#if !defined(STARBOARD)
   // In the current implementation it is sufficient to cancel the pending
   // tasks only. The tasks that are run by the update engine will stop
   // making progress naturally, as the main task runner stops running task
@@ -210,15 +220,15 @@
   // area, to cancel the running tasks by canceling the current action update.
   // This behavior would be expected, correct, and result in no resource leaks
   // in all cases, in shutdown or not.
-  //
-  // Cancel the pending tasks. These tasks are safe to cancel and delete since
-  // they have not picked up by the update engine, and not shared with any
-  // task runner yet.
-  while (!task_queue_.empty()) {
-    auto task = task_queue_.front();
-    task_queue_.pop_front();
+#else
+  // For Cobalt it's not sufficient to just let the tasks already picked up by
+  // the update engine stop naturally, as this can result in resource leaks and
+  // crashes. These tasks are also canceled so that any necessary cleanup can be
+  // done.
+  for (auto task : tasks_) {
     task->Cancel();
   }
+#endif
 }
 
 void UpdateClientImpl::SendUninstallPing(const std::string& id,
diff --git a/src/components/update_client/update_engine.cc b/src/components/update_client/update_engine.cc
index a6d6920..29c260b 100644
--- a/src/components/update_client/update_engine.cc
+++ b/src/components/update_client/update_engine.cc
@@ -72,10 +72,19 @@
   DCHECK(thread_checker_.CalledOnValidThread());
 }
 
+#if !defined(STARBOARD)
 void UpdateEngine::Update(bool is_foreground,
                           const std::vector<std::string>& ids,
                           UpdateClient::CrxDataCallback crx_data_callback,
                           Callback callback) {
+#else
+void UpdateEngine::Update(bool is_foreground,
+                          const std::vector<std::string>& ids,
+                          UpdateClient::CrxDataCallback crx_data_callback,
+                          Callback callback,
+                          base::OnceClosure& cancelation_closure) {
+
+#endif
   DCHECK(thread_checker_.CalledOnValidThread());
 
   if (ids.empty()) {
@@ -100,6 +109,10 @@
       config_, is_foreground, ids, std::move(crx_data_callback),
       notify_observers_callback_, std::move(callback), crx_downloader_factory_);
   DCHECK(!update_context->session_id.empty());
+#if defined(STARBOARD)
+  cancelation_closure = base::BindOnce(&UpdateEngine::Cancel, this,
+                                       update_context->session_id, ids);
+#endif
 
   const auto result = update_contexts_.insert(
       std::make_pair(update_context->session_id, update_context));
@@ -405,6 +418,17 @@
          now < throttle_updates_until_;
 }
 
+#if defined(STARBOARD)
+void UpdateEngine::Cancel(const std::string& update_context_session_id,
+                          const std::vector<std::string>& crx_component_ids) {
+  const auto& context = update_contexts_.at(update_context_session_id);
+  for (const auto& crx_component_id : crx_component_ids) {
+    auto& component = context->components.at(crx_component_id);
+    component->Cancel();
+  }
+}
+#endif
+
 void UpdateEngine::SendUninstallPing(const std::string& id,
                                      const base::Version& version,
                                      int reason,
diff --git a/src/components/update_client/update_engine.h b/src/components/update_client/update_engine.h
index 9601873..4384fa8 100644
--- a/src/components/update_client/update_engine.h
+++ b/src/components/update_client/update_engine.h
@@ -56,10 +56,20 @@
   // is not found.
   bool GetUpdateState(const std::string& id, CrxUpdateItem* update_state);
 
+#if !defined(STARBOARD)
   void Update(bool is_foreground,
               const std::vector<std::string>& ids,
               UpdateClient::CrxDataCallback crx_data_callback,
               Callback update_callback);
+#else
+  // |cancelation_closure| is populated with a closure that can be run to cancel
+  // the update requested by the caller.
+  void Update(bool is_foreground,
+              const std::vector<std::string>& ids,
+              UpdateClient::CrxDataCallback crx_data_callback,
+              Callback update_callback,
+              base::OnceClosure& cancelation_closure);
+#endif
 
   void SendUninstallPing(const std::string& id,
                          const base::Version& version,
@@ -96,6 +106,14 @@
   // occurs too soon.
   bool IsThrottled(bool is_foreground) const;
 
+#if defined(STARBOARD)
+  // Cancels updates currently handled by the engine for each component
+  // identified by one of |crx_component_ids| for the update context identified
+  // by the |update_context_session_id|.
+  void Cancel(const std::string& update_context_session_id,
+              const std::vector<std::string>& crx_component_ids);
+#endif
+
   base::ThreadChecker thread_checker_;
   scoped_refptr<Configurator> config_;
   UpdateChecker::Factory update_checker_factory_;
diff --git a/src/components/update_client/url_fetcher_downloader.cc b/src/components/update_client/url_fetcher_downloader.cc
index 83c5727..078c275 100644
--- a/src/components/update_client/url_fetcher_downloader.cc
+++ b/src/components/update_client/url_fetcher_downloader.cc
@@ -144,6 +144,13 @@
 #endif
 }
 
+#if defined(STARBOARD)
+void UrlFetcherDownloader::DoCancelDownload() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  network_fetcher_->CancelDownloadToFile();
+}
+#endif
+
 void UrlFetcherDownloader::CreateDownloadDir() {
   base::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_url_fetcher_"),
                                &download_dir_);
diff --git a/src/components/update_client/url_fetcher_downloader.h b/src/components/update_client/url_fetcher_downloader.h
index e5c240d..3880efc 100644
--- a/src/components/update_client/url_fetcher_downloader.h
+++ b/src/components/update_client/url_fetcher_downloader.h
@@ -42,6 +42,9 @@
  private:
   // Overrides for CrxDownloader.
   void DoStartDownload(const GURL& url) override;
+#if defined(STARBOARD)
+  void DoCancelDownload() override;
+#endif
 
   void CreateDownloadDir();
   void StartURLFetch(const GURL& url);
diff --git a/src/docker-compose.yml b/src/docker-compose.yml
index 743a169..19374ed 100644
--- a/src/docker-compose.yml
+++ b/src/docker-compose.yml
@@ -167,19 +167,6 @@
       USE_CCACHE: ${USE_CCACHE:-1}
       NINJA_STATUS: ${NINJA_STATUS}
 
-  linux-x64x11-sbversion12-evergreen:
-    <<: *build-common-definitions
-    build:
-      context: ./docker/linux
-      dockerfile: linux-x64x11/Dockerfile
-      args:
-        - FROM_IMAGE=cobalt-build-evergreen
-    image: cobalt-build-linux-x64x11-evergreen
-    environment:
-      <<: *shared-build-env
-      PLATFORM: linux-x64x11-sbversion-12
-      CONFIG: ${CONFIG:-debug}
-
   # Define common build container for Android
   build-android:
     <<: *build-common-definitions
@@ -362,6 +349,20 @@
       <<: *shared-build-env
       PLATFORM: evergreen-arm-softfp-sbversion-12
 
+  linux-x64x11-sbversion12-evergreen:
+    <<: *build-common-definitions
+    build:
+      context: ./docker/linux
+      dockerfile: linux-x64x11/Dockerfile
+      args:
+        - FROM_IMAGE=cobalt-build-evergreen
+    image: cobalt-build-linux-x64x11-evergreen
+    depends_on: [ build-evergreen ]
+    environment:
+      <<: *shared-build-env
+      PLATFORM: linux-x64x11-sbversion-12
+      CONFIG: ${CONFIG:-debug}
+
   unittest:
     <<: *common-definitions
     build:
diff --git a/src/docker/linux/base/build/Dockerfile b/src/docker/linux/base/build/Dockerfile
index 7b39d75..0de107d 100644
--- a/src/docker/linux/base/build/Dockerfile
+++ b/src/docker/linux/base/build/Dockerfile
@@ -33,10 +33,15 @@
     && echo "Done"
 
 # === Get Nodejs pinned LTS version via NVM
+ARG NVM_SHA256SUM="f068e17dacb88f73302790cc076956c7a0d459ce9b01df842ff3e75744f9e2fe /tmp/install.sh"
+ARG NVM_URL="https://cobalt.googlesource.com/third_party/nvm/+/refs/tags/v0.35.3/install.sh?format=TEXT"
 ENV NVM_DIR /root/.nvm
 ENV NODE_VERSION 12.17.0
 
-RUN curl --silent -o- https://raw.githubusercontent.com/creationix/nvm/v0.35.3/install.sh | bash
+RUN curl --silent -o- ${NVM_URL} \
+  | base64 -d > /tmp/install.sh \
+   && echo ${NVM_SHA256SUM} | sha256sum --check \
+   && . /tmp/install.sh
 
 RUN . $NVM_DIR/nvm.sh \
    && nvm install --lts \
diff --git a/src/nb/nb_test.gyp b/src/nb/nb_test.gyp
index 5520709..2d5b937 100644
--- a/src/nb/nb_test.gyp
+++ b/src/nb/nb_test.gyp
@@ -80,6 +80,7 @@
       'variables': {
         'executable_name': 'reuse_allocator_benchmark',
       },
+      'includes': [ '<(DEPTH)/starboard/build/deploy.gypi' ],
     },
 
     {
diff --git a/src/starboard/android/apk/app/CMakeLists.txt b/src/starboard/android/apk/app/CMakeLists.txt
index 2ac5b87..b3f36b7 100644
--- a/src/starboard/android/apk/app/CMakeLists.txt
+++ b/src/starboard/android/apk/app/CMakeLists.txt
@@ -47,9 +47,9 @@
 endif()
 
 # If COBALT_LIBRARY_DIR isn't set for a particular deploy target use the
-# base product directory.
+# toplevel lib/ subdirectory.
 if(NOT COBALT_LIBRARY_DIR)
-  set(COBALT_LIBRARY_DIR COBALT_PRODUCT_DIR)
+  set(COBALT_LIBRARY_DIR ${COBALT_PRODUCT_DIR}/lib)
 endif()
 
 # For platform deploy builds, use the -n parameter to skip Cobalt ninja and
diff --git a/src/starboard/android/apk/app/build.gradle b/src/starboard/android/apk/app/build.gradle
index 7cdb4f1..944dcbd 100644
--- a/src/starboard/android/apk/app/build.gradle
+++ b/src/starboard/android/apk/app/build.gradle
@@ -30,7 +30,8 @@
             project.hasProperty('cobaltProductDir') ? new File(cobaltProductDir).canonicalPath : ''
     cobaltContentDir =
             project.hasProperty('cobaltContentDir') ? new File(cobaltContentDir).canonicalPath : ''
-    cobaltLibraryDir = project.hasProperty('cobaltLibraryDir') ? cobaltLibraryDir : cobaltProductDir
+    cobaltLibraryDir =
+            project.hasProperty('cobaltLibraryDir') ? new File(cobaltLibraryDir).canonicalPath : ''
     enableVulkan =
             project.hasProperty('enableVulkan') ? enableVulkan : 0
 
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 22f7e05..d9082da 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
@@ -33,6 +33,7 @@
 import dev.cobalt.util.DisplayUtil;
 import dev.cobalt.util.Log;
 import dev.cobalt.util.UsedByNative;
+import java.security.Timestamp;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -65,10 +66,15 @@
 
   private boolean forceCreateNewVideoSurfaceView = false;
 
+  private long timeInNanoseconds;
+
   private static native void nativeLowMemoryEvent();
 
   @Override
   protected void onCreate(Bundle savedInstanceState) {
+    // Record the application start timestamp.
+    timeInNanoseconds = System.nanoTime();
+
     // To ensure that volume controls adjust the correct stream, make this call
     // early in the app's lifecycle. This connects the volume controls to
     // STREAM_MUSIC whenever the target activity or fragment is visible.
@@ -341,4 +347,8 @@
     super.onLowMemory();
     nativeLowMemoryEvent();
   }
+
+  public long getAppStartTimestamp() {
+    return timeInNanoseconds;
+  }
 }
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 43227de..16518da 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
@@ -32,10 +32,9 @@
 
 public class MediaPlaybackService extends Service {
 
-  private static final int NOTIFICATION_ID = 1234;
-  private static final String NOTIFICATION_CHANNEL_ID = "default";
-  private static final String NOTIFICATION_CHANNEL_NAME = "Default channel";
-  private Context context;
+  private static final int NOTIFICATION_ID = 193266736; // CL number for uniqueness.
+  private static final String NOTIFICATION_CHANNEL_ID = "dev.cobalt.coat media playback service";
+  private static final String NOTIFICATION_CHANNEL_NAME = "Media playback service";
 
   @Override
   public void onCreate() {
@@ -46,7 +45,6 @@
       return;
     }
     getStarboardBridge().onServiceStart(this);
-    context = getApplicationContext();
   }
 
   @Override
@@ -70,7 +68,6 @@
       return;
     }
     getStarboardBridge().onServiceDestroy(this);
-    context = null;
     super.onDestroy();
     Log.i(TAG, "Destroying the Media playback service.");
   }
@@ -81,16 +78,18 @@
   }
 
   public void stopService() {
+    // Do not remove notification here.
+    stopForeground(false);
+    stopSelf();
+    // Delete notification after foreground stopped.
     deleteChannel();
     hideNotification();
-    stopForeground(true);
-    stopSelf();
   }
 
   private void hideNotification() {
     Log.i(TAG, "Hiding notification after stopped the service");
     NotificationManager notificationManager =
-        (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
     notificationManager.cancel(NOTIFICATION_ID);
   }
 
@@ -103,7 +102,7 @@
   @RequiresApi(26)
   private void createChannelInternalV26() {
     NotificationManager notificationManager =
-        (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
     NotificationChannel channel =
         new NotificationChannel(
             NOTIFICATION_CHANNEL_ID,
@@ -113,7 +112,7 @@
     try {
       notificationManager.createNotificationChannel(channel);
     } catch (IllegalArgumentException e) {
-
+      // intentional empty.
     }
   }
 
@@ -126,13 +125,13 @@
   @RequiresApi(26)
   private void deleteChannelInternalV26() {
     NotificationManager notificationManager =
-        (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
     notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
   }
 
   Notification buildNotification() {
     NotificationCompat.Builder builder =
-        new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
+        new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
             .setShowWhen(false)
             .setPriority(NotificationCompat.PRIORITY_MIN)
             .setSmallIcon(android.R.drawable.stat_sys_warning)
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
index 495b761..455f47b 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
@@ -94,6 +94,8 @@
   private final HashMap<String, CobaltService.Factory> cobaltServiceFactories = new HashMap<>();
   private final HashMap<String, CobaltService> cobaltServices = new HashMap<>();
 
+  private final long timeNanosecondsPerMicrosecond = 1000;
+
   public StarboardBridge(
       Context appContext,
       Holder<Activity> activityHolder,
@@ -123,6 +125,8 @@
 
   private native boolean nativeInitialize();
 
+  private native long nativeSbTimeGetMonotonicNow();
+
   protected void onActivityStart(Activity activity, KeyboardEditor keyboardEditor) {
     activityHolder.set(activity);
     this.keyboardEditor = keyboardEditor;
@@ -167,6 +171,10 @@
   protected void startMediaPlaybackService() {
     Service service = serviceHolder.get();
     if (service == null) {
+      if (appContext == null) {
+        Log.w(TAG, "Activiy already destoryed.");
+        return;
+      }
       Log.i(TAG, "Cold start - Instantiating a MediaPlaybackService.");
       Intent intent = new Intent(appContext, MediaPlaybackService.class);
       appContext.startService(intent);
@@ -673,4 +681,21 @@
   void closeCobaltService(String serviceName) {
     cobaltServices.remove(serviceName);
   }
+
+  /**
+   * Returns the application start timestamp.
+   */
+  @SuppressWarnings("unused")
+  @UsedByNative
+  protected long getAppStartTimestamp() {
+    Activity activity = activityHolder.get();
+    if (activity instanceof CobaltActivity) {
+      long javaStartTimestamp = ((CobaltActivity) activity).getAppStartTimestamp();
+      long cppTimestamp = nativeSbTimeGetMonotonicNow();
+      long javaStopTimestamp = System.nanoTime();
+      return cppTimestamp -
+          (javaStartTimestamp - javaStopTimestamp) / timeNanosecondsPerMicrosecond;
+    }
+    return 0;
+  }
 }
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 71009ed..0e1d66b 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
@@ -17,6 +17,7 @@
 import static dev.cobalt.media.Log.TAG;
 
 import android.content.Context;
+import android.media.AudioAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 import android.media.AudioManager;
@@ -122,7 +123,7 @@
 
   /** Convert AudioDeviceInfo.TYPE_* to name in String */
   @RequiresApi(23)
-  private static String getDeviceTypeName(int device_type) {
+  private static String getDeviceTypeNameV23(int device_type) {
     switch (device_type) {
       case AudioDeviceInfo.TYPE_AUX_LINE:
         return "TYPE_AUX_LINE";
@@ -240,7 +241,7 @@
           TAG,
           String.format(
               "  Audio Device: %s, channels: %s, sample rates: %s, encodings: %s",
-              getDeviceTypeName(info.getType()),
+              getDeviceTypeNameV23(info.getType()),
               Arrays.toString(info.getChannelCounts()),
               Arrays.toString(info.getSampleRates()),
               getEncodingNames(info.getEncodings())));
@@ -292,4 +293,140 @@
     AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
     return audioManager.generateAudioSessionId();
   }
+
+  /** Returns whether passthrough on `encoding` is supported. */
+  @SuppressWarnings("unused")
+  @UsedByNative
+  boolean hasPassthroughSupportFor(int encoding) {
+    if (Build.VERSION.SDK_INT < 23) {
+      Log.i(
+          TAG,
+          String.format(
+              "Passthrough on encoding %d is rejected on api %d, as passthrough is only"
+                  + " supported on api 23 or later.",
+              encoding, Build.VERSION.SDK_INT));
+      return false;
+    }
+    if (hasPassthroughSupportForV23(encoding)) {
+      Log.i(
+          TAG,
+          String.format(
+              "Passthrough on encoding %d is supported, as hasPassthroughSupportForV23() returns"
+                  + " true.",
+              encoding));
+      return true;
+    }
+    if (Build.VERSION.SDK_INT < 29) {
+      Log.i(
+          TAG,
+          String.format(
+              "Passthrough on encoding %d is rejected, as"
+                  + " hasDirectSurroundingPlaybackSupportForV29() is not called for api %d.",
+              encoding, Build.VERSION.SDK_INT));
+      return false;
+    }
+    if (hasDirectSurroundingPlaybackSupportForV29(encoding)) {
+      Log.i(
+          TAG,
+          String.format(
+              "Passthrough on encoding %d is supported, as"
+                  + " hasDirectSurroundingPlaybackSupportForV29() returns true.",
+              encoding));
+      return true;
+    }
+
+    Log.i(
+        TAG,
+        String.format(
+            "Passthrough on encoding %d is not supported, as"
+                + " hasDirectSurroundingPlaybackSupportForV29() returns false.",
+            encoding));
+    return false;
+  }
+
+  /** Returns whether passthrough on `encoding` is supported for API 23 and above. */
+  @RequiresApi(23)
+  private boolean hasPassthroughSupportForV23(int encoding) {
+    AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+    AudioDeviceInfo[] deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+
+    // TODO: Verify the follow code returns false if non-surrounding BT device is routed.
+    for (AudioDeviceInfo info : deviceInfos) {
+      final int type = info.getType();
+      if (type != AudioDeviceInfo.TYPE_HDMI && type != AudioDeviceInfo.TYPE_HDMI_ARC) {
+        continue;
+      }
+      // TODO: ExoPlayer uses ACTION_HDMI_AUDIO_PLUG to detect the encodings supported via
+      //       passthrough, we should consider using it, and maybe other actions like
+      //       ACTION_HEADSET_PLUG for general audio device switch/encoding detection.
+      final int[] encodings = info.getEncodings();
+      if (encodings.length == 0) {
+        // Per https://developer.android.com/reference/android/media/AudioDeviceInfo#getEncodings()
+        // an empty array indicates that the device supports arbitrary encodings.
+        Log.i(
+            TAG,
+            String.format(
+                "Passthrough on encoding %d is supported on %s, because getEncodings() returns"
+                    + " an empty array.",
+                encoding, getDeviceTypeNameV23(type)));
+        return true;
+      }
+      for (int i = 0; i < encodings.length; ++i) {
+        if (encodings[i] == encoding) {
+          Log.i(
+              TAG,
+              String.format(
+                  "Passthrough on encoding %d is supported on %s.",
+                  encoding, getDeviceTypeNameV23(type)));
+          return true;
+        }
+      }
+      Log.i(
+          TAG,
+          String.format(
+              "Passthrough on encoding %d is not supported on %s.",
+              encoding, getDeviceTypeNameV23(type)));
+    }
+    Log.i(
+        TAG,
+        String.format("Passthrough on encoding %d is not supported on any devices.", encoding));
+    return false;
+  }
+
+  @RequiresApi(29)
+  /**
+   * Returns whether direct playback on surrounding `encoding` is supported for API 29 and above.
+   */
+  private boolean hasDirectSurroundingPlaybackSupportForV29(int encoding) {
+    if (encoding != AudioFormat.ENCODING_AC3
+        && encoding != AudioFormat.ENCODING_E_AC3
+        && encoding != AudioFormat.ENCODING_E_AC3_JOC) {
+      Log.w(
+          TAG,
+          String.format(
+              "hasDirectSurroundingPlaybackSupportForV29() encountered unsupported encoding %d.",
+              encoding));
+      return false;
+    }
+
+    // Sample rate is not provided when the function is called, assume it is 48000.
+    final int DEFAULT_SURROUNDING_SAMPLE_RATE = 48000;
+    AudioFormat format =
+        new AudioFormat.Builder()
+            .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
+            .setEncoding(encoding)
+            .setSampleRate(DEFAULT_SURROUNDING_SAMPLE_RATE)
+            .build();
+    AudioAttributes attributes =
+        new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_MEDIA)
+            .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
+            .build();
+    final boolean supported = AudioTrack.isDirectPlaybackSupported(format, attributes);
+    Log.i(
+        TAG,
+        String.format(
+            "isDirectPlaybackSupported() for encoding %d returned %b.", encoding, supported));
+    return supported;
+  }
 }
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 ac387f5..7441984 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
@@ -63,10 +63,11 @@
         return 4;
       case AudioFormat.ENCODING_INVALID:
       default:
-        throw new RuntimeException("Unsupport audio format " + audioFormat);
+        throw new RuntimeException("Unsupported audio format " + audioFormat);
     }
   }
 
+  // TODO: Pass error details to caller.
   public AudioTrackBridge(
       int sampleType,
       int sampleRate,
@@ -119,11 +120,18 @@
               .setUsage(AudioAttributes.USAGE_MEDIA)
               .build();
     } else {
-      // TODO: Investigate if we can use |CONTENT_TYPE_MOVIE| for AudioTrack
-      //       used by video playback.
+      // TODO: Support ENCODING_E_AC3_JOC for api level 28 or later.
+      final boolean is_surrounding =
+          sampleType == AudioFormat.ENCODING_AC3 || sampleType == AudioFormat.ENCODING_E_AC3;
+      // TODO: We start to enforce |CONTENT_TYPE_MOVIE| for surrounding playback, investigate if we
+      //       can use |CONTENT_TYPE_MOVIE| for all non-surrounding AudioTrack used by video
+      //       playback.
       attributes =
           new AudioAttributes.Builder()
-              .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+              .setContentType(
+                  is_surrounding
+                      ? AudioAttributes.CONTENT_TYPE_MOVIE
+                      : AudioAttributes.CONTENT_TYPE_MUSIC)
               .setUsage(AudioAttributes.USAGE_MEDIA)
               .build();
     }
@@ -243,14 +251,29 @@
 
   @SuppressWarnings("unused")
   @UsedByNative
+  private void stop() {
+    if (audioTrack == null) {
+      Log.e(TAG, "Unable to stop with NULL audio track.");
+      return;
+    }
+    audioTrack.stop();
+  }
+
+  @SuppressWarnings("unused")
+  @UsedByNative
   private void flush() {
     if (audioTrack == null) {
       Log.e(TAG, "Unable to flush with NULL audio track.");
       return;
     }
     audioTrack.flush();
+    // Reset the states to allow reuse of |audioTrack| after flush() is called.  This can reduce
+    // switch latency for passthrough playbacks.
     avSyncHeader = null;
     avSyncPacketBytesRemaining = 0;
+    synchronized (this) {
+      maxFramePositionSoFar = 0;
+    }
   }
 
   @SuppressWarnings("unused")
@@ -355,25 +378,30 @@
       Log.e(TAG, "Unable to getAudioTimestamp with NULL audio track.");
       return audioTimestamp;
     }
-    if (audioTrack.getTimestamp(audioTimestamp)) {
-      // This conversion is safe, as only the lower bits will be set, since we
-      // called |getTimestamp| without a timebase.
-      // https://developer.android.com/reference/android/media/AudioTimestamp.html#framePosition
-      audioTimestamp.framePosition &= 0x7FFFFFFF;
-    } else {
-      // Time stamps haven't been updated yet, assume playback hasn't started.
-      audioTimestamp.framePosition = 0;
-      audioTimestamp.nanoTime = System.nanoTime();
-    }
+    // The `synchronized` is required as `maxFramePositionSoFar` can also be modified in flush().
+    // TODO: Consider refactor the code to remove the dependency on `synchronized`.
+    synchronized (this) {
+      if (audioTrack.getTimestamp(audioTimestamp)) {
+        // This conversion is safe, as only the lower bits will be set, since we
+        // called |getTimestamp| without a timebase.
+        // https://developer.android.com/reference/android/media/AudioTimestamp.html#framePosition
+        audioTimestamp.framePosition &= 0x7FFFFFFF;
+      } else {
+        // Time stamps haven't been updated yet, assume playback hasn't started.
+        audioTimestamp.framePosition = 0;
+        audioTimestamp.nanoTime = System.nanoTime();
+      }
 
-    // TODO: This is required for correctness of the audio sink, because
-    // otherwise we would be going back in time. Investigate the impact it has
-    // on playback.  All empirical measurements so far suggest that it should
-    // be negligible.
-    if (audioTimestamp.framePosition < maxFramePositionSoFar) {
-      audioTimestamp.framePosition = maxFramePositionSoFar;
+      if (audioTimestamp.framePosition > maxFramePositionSoFar) {
+        maxFramePositionSoFar = audioTimestamp.framePosition;
+      } else {
+        // The returned |audioTimestamp.framePosition| is not monotonically
+        // increasing, and a monotonically increastion frame position is
+        // required to calculate the playback time correctly, because otherwise
+        // we would be going back in time.
+        audioTimestamp.framePosition = maxFramePositionSoFar;
+      }
     }
-    maxFramePositionSoFar = audioTimestamp.framePosition;
 
     return audioTimestamp;
   }
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
index 4bf638b..0ca198c 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
@@ -33,6 +33,7 @@
 import dev.cobalt.util.Log;
 import dev.cobalt.util.UsedByNative;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 
 /** A wrapper of the MediaCodec class. */
 @SuppressWarnings("unused")
@@ -364,9 +365,7 @@
 
       // This logic is inspired by
       // https://github.com/google/ExoPlayer/blob/deb9b301b2c7ef66fdd7d8a3e58298a79ba9c619/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java#L1803.
-      byte[] hdrStaticInfoData = new byte[25];
-      ByteBuffer hdrStaticInfo = ByteBuffer.wrap(hdrStaticInfoData);
-
+      ByteBuffer hdrStaticInfo = ByteBuffer.allocateDirect(25).order(ByteOrder.LITTLE_ENDIAN);
       hdrStaticInfo.put((byte) 0);
       hdrStaticInfo.putShort((short) ((primaryRChromaticityX * MAX_CHROMATICITY) + 0.5f));
       hdrStaticInfo.putShort((short) ((primaryRChromaticityY * MAX_CHROMATICITY) + 0.5f));
diff --git a/src/starboard/android/shared/BUILD.gn b/src/starboard/android/shared/BUILD.gn
index 76af15f..66628a2 100644
--- a/src/starboard/android/shared/BUILD.gn
+++ b/src/starboard/android/shared/BUILD.gn
@@ -14,12 +14,6 @@
 
 import("//starboard/shared/starboard/player/buildfiles.gni")
 
-declare_args() {
-  has_input_events_filter = false
-
-  has_drm_system_extension = false
-}
-
 config("starboard_platform_config") {
   include_dirs = [ "bionic" ]
 }
@@ -431,16 +425,6 @@
     "//starboard/shared/starboard/player/player_set_playback_rate.cc",
   ]
 
-  if (has_drm_system_extension) {
-    # TODO(andrewsavage)
-  } else {
-    sources += [
-      "drm_create_system.cc",
-      "media_is_supported.cc",
-      "player_components_factory.cc",
-    ]
-  }
-
   configs += [
     ":starboard_platform_config",
     "//starboard/build/config:starboard_implementation",
@@ -458,6 +442,21 @@
     "//third_party/libevent",
     "//third_party/opus",
   ]
+
+  if (is_internal_build) {
+    sources += [
+      "input_events_filter.cc",
+      "input_events_filter.h",
+    ]
+    defines = [ "STARBOARD_INPUT_EVENTS_FILTER" ]
+    deps += [ "//starboard/android/shared/drm_system_extension" ]
+  } else {
+    sources += [
+      "drm_create_system.cc",
+      "media_is_supported.cc",
+      "player_components_factory.cc",
+    ]
+  }
 }
 
 static_library("starboard_base_symbolize") {
diff --git a/src/starboard/android/shared/application_android.cc b/src/starboard/android/shared/application_android.cc
index 2c4cda0..598bed7 100644
--- a/src/starboard/android/shared/application_android.cc
+++ b/src/starboard/android/shared/application_android.cc
@@ -259,9 +259,18 @@
         // This is the initial launch, so we have to start Cobalt now that we
         // have a window.
         env->CallStarboardVoidMethodOrAbort("beforeStartOrResume", "()V");
+#if SB_API_VERSION >= 13
+        DispatchStart(GetAppStartTimestamp());
+#else  // SB_API_VERSION >= 13
         DispatchStart();
+#endif  // SB_API_VERSION >= 13
       } else if (state() == kStateConcealed || state() == kStateFrozen) {
+#if SB_API_VERSION >= 13
+        DispatchAndDelete(new Event(kSbEventTypeReveal, SbTimeGetMonotonicNow(),
+                                    NULL, NULL));
+#else  // SB_API_VERSION >= 13
         DispatchAndDelete(new Event(kSbEventTypeReveal, NULL, NULL));
+#endif  // SB_API_VERSION >= 13
       } else {
         // Now that we got a window back, change the command for the switch
         // below to sync up with the current activity lifecycle.
@@ -280,7 +289,12 @@
         // Cobalt can't keep running without a window, even if the Activity
         // hasn't stopped yet. DispatchAndDelete() will inject events as needed
         // if we're not already paused.
+#if SB_API_VERSION >= 13
+        DispatchAndDelete(new Event(kSbEventTypeConceal, SbTimeGetMonotonicNow(),
+                                    NULL, NULL));
+#else  // SB_API_VERSION >= 13
         DispatchAndDelete(new Event(kSbEventTypeConceal, NULL, NULL));
+#endif  // SB_API_VERSION >= 13
         if (window_) {
           window_->native_window = NULL;
         }
@@ -334,7 +348,12 @@
           SbMemoryDeallocate(static_cast<void*>(deep_link));
         } else {
           SB_LOG(INFO) << "ApplicationAndroid Inject: kSbEventTypeLink";
+#if SB_API_VERSION >= 13
+          Inject(new Event(kSbEventTypeLink, SbTimeGetMonotonicNow(),
+              deep_link, SbMemoryDeallocate));
+#else  // SB_API_VERSION >= 13
           Inject(new Event(kSbEventTypeLink, deep_link, SbMemoryDeallocate));
+#endif  // SB_API_VERSION >= 13
         }
       }
       break;
@@ -342,6 +361,35 @@
 
   // If there's a window, sync the app state to the Activity lifecycle, letting
   // DispatchAndDelete() inject events as needed if we missed a state.
+#if SB_API_VERSION >= 13
+if (native_window_) {
+    switch (sync_state) {
+      case AndroidCommand::kStart:
+        DispatchAndDelete(new Event(kSbEventTypeReveal, SbTimeGetMonotonicNow(),
+                                    NULL, NULL));
+        break;
+      case AndroidCommand::kResume:
+        DispatchAndDelete(new Event(kSbEventTypeFocus, SbTimeGetMonotonicNow(),
+                                    NULL, NULL));
+        break;
+      case AndroidCommand::kPause:
+        DispatchAndDelete(new Event(kSbEventTypeBlur, SbTimeGetMonotonicNow(),
+                                    NULL, NULL));
+        break;
+      case AndroidCommand::kStop:
+        if (state() != kStateConcealed && state() != kStateFrozen) {
+          // We usually conceal when losing the window above, but if the window
+          // wasn't destroyed (e.g. when Daydream starts) then we still have to
+          // conceal when the Activity is stopped.
+          DispatchAndDelete(new Event(kSbEventTypeConceal, SbTimeGetMonotonicNow(),
+                                      NULL, NULL));
+        }
+        break;
+      default:
+        break;
+    }
+  }
+#else  // SB_API_VERSION >= 13
   if (native_window_) {
     switch (sync_state) {
       case AndroidCommand::kStart:
@@ -365,6 +413,7 @@
         break;
     }
   }
+#endif  // SB_API_VERSION >= 13
 }
 
 void ApplicationAndroid::SendAndroidCommand(AndroidCommand::CommandType type,
@@ -619,6 +668,22 @@
   }
 }
 
+SbTimeMonotonic ApplicationAndroid::GetAppStartTimestamp() {
+  JniEnvExt* env = JniEnvExt::Get();
+  jlong app_start_timestamp =
+      env->CallStarboardLongMethodOrAbort("getAppStartTimestamp",
+                                          "()J");
+  return app_start_timestamp;
+}
+
+extern "C" SB_EXPORT_PLATFORM jlong
+Java_dev_cobalt_coat_StarboardBridge_nativeSbTimeGetMonotonicNow(
+    JNIEnv* env,
+    jobject jcaller,
+    jboolean online) {
+  return SbTimeGetMonotonicNow();
+}
+
 }  // namespace shared
 }  // namespace android
 }  // namespace starboard
diff --git a/src/starboard/android/shared/application_android.h b/src/starboard/android/shared/application_android.h
index 90ffecd..1c8dde4 100644
--- a/src/starboard/android/shared/application_android.h
+++ b/src/starboard/android/shared/application_android.h
@@ -92,6 +92,7 @@
   void SbWindowSendInputEvent(const char* input_text, bool is_composing);
   void SendLowMemoryEvent();
   void OsNetworkStatusChange(bool became_online);
+  SbTimeMonotonic GetAppStartTimestamp();
 
  protected:
   // --- Application overrides ---
diff --git a/src/starboard/android/shared/audio_decoder.h b/src/starboard/android/shared/audio_decoder.h
index 45460f5..8a909fe 100644
--- a/src/starboard/android/shared/audio_decoder.h
+++ b/src/starboard/android/shared/audio_decoder.h
@@ -57,8 +57,8 @@
   bool is_valid() const { return media_decoder_ != NULL; }
 
  private:
-  // The maximum amount of work that can exist in the union of |EventQueue|,
-  // |pending_work| and |decoded_audios_|.
+  // The maximum amount of work that can exist in the union of |decoded_audios_|
+  // and |media_decoder_->GetNumberOfPendingTasks()|.
   static const int kMaxPendingWorkSize = 64;
 
   bool InitializeCodec();
diff --git a/src/starboard/android/shared/audio_decoder_passthrough.h b/src/starboard/android/shared/audio_decoder_passthrough.h
new file mode 100644
index 0000000..59b2473
--- /dev/null
+++ b/src/starboard/android/shared/audio_decoder_passthrough.h
@@ -0,0 +1,118 @@
+// Copyright 2021 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_AUDIO_DECODER_PASSTHROUGH_H_
+#define STARBOARD_ANDROID_SHARED_AUDIO_DECODER_PASSTHROUGH_H_
+
+#include <queue>
+
+#include "starboard/common/log.h"
+#include "starboard/common/ref_counted.h"
+#include "starboard/media.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/decoded_audio_internal.h"
+#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
+#include "starboard/shared/starboard/player/filter/common.h"
+#include "starboard/shared/starboard/player/input_buffer_internal.h"
+#include "starboard/shared/starboard/thread_checker.h"
+#include "starboard/types.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+// This class simply creates a DecodedAudio object from the InputBuffer passed
+// in, without actually decoding the input audio.  It can be used in situations
+// (like passthrough playbacks) where an AudioDecoder has to be used, but is
+// expected to not alter the input and pass it to the renderer as is.
+class AudioDecoderPassthrough
+    : public ::starboard::shared::starboard::player::filter::AudioDecoder {
+ public:
+  explicit AudioDecoderPassthrough(int samples_per_second)
+      : samples_per_second_(samples_per_second) {}
+
+  // AudioDecoder methods.
+  void Initialize(const OutputCB& output_cb, const ErrorCB& error_cb) override {
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
+    SB_DCHECK(!output_cb_);
+    SB_DCHECK(output_cb);
+
+    output_cb_ = output_cb;
+  }
+
+  void Decode(const scoped_refptr<InputBuffer>& input_buffer,
+              const ConsumedCB& consumed_cb) override {
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
+    SB_DCHECK(input_buffer);
+    SB_DCHECK(consumed_cb);
+    SB_DCHECK(output_cb_);
+
+    // TODO: |decoded_audio| is used as a buffer to store raw, encoded audio
+    //       here, which isn't aligned to its intended usage.  The code won't
+    //       break as its channel is explicitly set to 1, and the ctor of
+    //       DecodedAudio doesn't check whether the buffer size is a multiple of
+    //       the sample size.
+    //       We should revisit this once |DecodedAudio| is used by passthrough
+    //       mode on more platforms.
+    const int kChannels = 1;
+    scoped_refptr<DecodedAudio> decoded_audio =
+        new DecodedAudio(kChannels, kSbMediaAudioSampleTypeInt16Deprecated,
+                         kSbMediaAudioFrameStorageTypePlanar,
+                         input_buffer->timestamp(), input_buffer->size());
+    memcpy(decoded_audio->buffer(), input_buffer->data(), input_buffer->size());
+    decoded_audios_.push(decoded_audio);
+
+    consumed_cb();
+    output_cb_();
+  }
+
+  void WriteEndOfStream() override {
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
+    SB_DCHECK(output_cb_);
+
+    decoded_audios_.push(new DecodedAudio);
+    output_cb_();
+  }
+
+  scoped_refptr<DecodedAudio> Read(int* samples_per_second) override {
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
+    SB_DCHECK(samples_per_second);
+    SB_DCHECK(!decoded_audios_.empty());
+
+    *samples_per_second = samples_per_second_;
+
+    auto decoded_audio = decoded_audios_.front();
+    decoded_audios_.pop();
+    return decoded_audio;
+  }
+
+  void Reset() override {
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+    decoded_audios_ = std::queue<scoped_refptr<DecodedAudio>>();  // Clear
+  }
+
+ private:
+  ::starboard::shared::starboard::ThreadChecker thread_checker_;
+
+  const int samples_per_second_;
+  OutputCB output_cb_;
+  std::queue<scoped_refptr<DecodedAudio>> decoded_audios_;
+};
+
+}  // namespace shared
+}  // namespace android
+}  // namespace starboard
+
+#endif  // STARBOARD_ANDROID_SHARED_AUDIO_DECODER_PASSTHROUGH_H_
diff --git a/src/starboard/android/shared/audio_renderer_passthrough.cc b/src/starboard/android/shared/audio_renderer_passthrough.cc
new file mode 100644
index 0000000..4f2820d
--- /dev/null
+++ b/src/starboard/android/shared/audio_renderer_passthrough.cc
@@ -0,0 +1,568 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/audio_renderer_passthrough.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "starboard/android/shared/audio_decoder_passthrough.h"
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/memory.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+namespace {
+
+// Soft limit to ensure that the user of AudioRendererPassthrough won't keep
+// pushing data when there are enough decoded audio buffers.
+constexpr int kMaxDecodedAudios = 64;
+
+constexpr SbTime kAudioTrackUpdateInternal = kSbTimeMillisecond * 5;
+
+constexpr int kPreferredBufferSizeInBytes = 16 * 1024;
+// TODO: Enable audio routing and link it to client side experiment.
+constexpr bool kEnableAudioRouting = false;
+// TODO: Enable passthrough with tunnel mode.
+constexpr int kTunnelModeAudioSessionId = -1;
+
+// C++ rewrite of ExoPlayer function parseAc3SyncframeAudioSampleCount(), it
+// works for AC-3, E-AC-3, and E-AC-3-JOC.
+// The ExoPlayer implementation is based on
+// https://www.etsi.org/deliver/etsi_ts/102300_102399/102366/01.04.01_60/ts_102366v010401p.pdf.
+int ParseAc3SyncframeAudioSampleCount(const uint8_t* buffer, int size) {
+  SB_DCHECK(buffer);
+
+  constexpr int kAudioSamplesPerAudioBlock = 256;
+  // Each syncframe has 6 blocks that provide 256 new audio samples. See
+  // subsection 4.1.
+  constexpr int kAc3SyncFrameAudioSampleCount = 6 * kAudioSamplesPerAudioBlock;
+  // Number of audio blocks per E-AC-3 syncframe, indexed by numblkscod.
+  constexpr int kBlocksPerSyncFrameByNumblkscod[] = {1, 2, 3, 6};
+
+  if (size < 6) {
+    SB_LOG(WARNING) << "Invalid e/ac3 input buffer size " << size;
+    return kAc3SyncFrameAudioSampleCount;
+  }
+
+  // Parse the bitstream ID for AC-3 and E-AC-3 (see subsections 4.3, E.1.2 and
+  // E.1.3.1.6).
+  const bool is_eac3 = ((buffer[5] & 0xF8) >> 3) > 10;
+  if (is_eac3) {
+    int fscod = (buffer[4] & 0xC0) >> 6;
+    int numblkscod = fscod == 0x03 ? 3 : (buffer[4] & 0x30) >> 4;
+    return kBlocksPerSyncFrameByNumblkscod[numblkscod] *
+           kAudioSamplesPerAudioBlock;
+  } else {
+    return kAc3SyncFrameAudioSampleCount;
+  }
+}
+
+}  // namespace
+
+AudioRendererPassthrough::AudioRendererPassthrough(
+    const SbMediaAudioSampleInfo& audio_sample_info,
+    SbDrmSystem drm_system)
+    : audio_sample_info_(audio_sample_info) {
+  SB_DCHECK(audio_sample_info_.codec == kSbMediaAudioCodecAc3 ||
+            audio_sample_info_.codec == kSbMediaAudioCodecEac3);
+  if (SbDrmSystemIsValid(drm_system)) {
+    SB_LOG(INFO) << "Creating AudioDecoder as decryptor.";
+    scoped_ptr<AudioDecoder> audio_decoder(new AudioDecoder(
+        audio_sample_info_.codec, audio_sample_info, drm_system));
+    if (audio_decoder->is_valid()) {
+      decoder_.reset(audio_decoder.release());
+    }
+  } else {
+    SB_LOG(INFO) << "Creating AudioDecoderPassthrough.";
+    decoder_.reset(
+        new AudioDecoderPassthrough(audio_sample_info_.samples_per_second));
+  }
+}
+
+AudioRendererPassthrough::~AudioRendererPassthrough() {
+  SB_DCHECK(BelongsToCurrentThread());
+
+  if (is_valid()) {
+    SB_LOG(INFO) << "Force a seek to 0 to reset all states before destructing.";
+    Seek(0);
+  }
+}
+
+void AudioRendererPassthrough::Initialize(const ErrorCB& error_cb,
+                                          const PrerolledCB& prerolled_cb,
+                                          const EndedCB& ended_cb) {
+  SB_DCHECK(BelongsToCurrentThread());
+  SB_DCHECK(error_cb);
+  SB_DCHECK(prerolled_cb);
+  SB_DCHECK(ended_cb);
+  SB_DCHECK(!error_cb_);
+  SB_DCHECK(!prerolled_cb_);
+  SB_DCHECK(!ended_cb_);
+  SB_DCHECK(decoder_);
+
+  error_cb_ = error_cb;
+  prerolled_cb_ = prerolled_cb;
+  ended_cb_ = ended_cb;
+
+  decoder_->Initialize(
+      std::bind(&AudioRendererPassthrough::OnDecoderOutput, this), error_cb);
+}
+
+void AudioRendererPassthrough::WriteSample(
+    const scoped_refptr<InputBuffer>& input_buffer) {
+  SB_DCHECK(BelongsToCurrentThread());
+  SB_DCHECK(input_buffer);
+  SB_DCHECK(can_accept_more_data_.load());
+
+  if (!audio_track_thread_) {
+    audio_track_thread_.reset(
+        new JobThread("AudioPassthrough", 0, kSbThreadPriorityHigh));
+    audio_track_thread_->Schedule(std::bind(
+        &AudioRendererPassthrough::CreateAudioTrackAndStartProcessing, this));
+  }
+
+  if (frames_per_input_buffer_ == 0) {
+    frames_per_input_buffer_ = ParseAc3SyncframeAudioSampleCount(
+        input_buffer->data(), input_buffer->size());
+    SB_LOG(INFO) << "Got frames per input buffer " << frames_per_input_buffer_;
+  }
+
+  can_accept_more_data_.store(false);
+
+  decoder_->Decode(
+      input_buffer,
+      std::bind(&AudioRendererPassthrough::OnDecoderConsumed, this));
+}
+
+void AudioRendererPassthrough::WriteEndOfStream() {
+  SB_DCHECK(BelongsToCurrentThread());
+
+  if (end_of_stream_written_) {
+    SB_LOG(INFO) << "WriteEndOfStream() ignored as |end_of_stream_written_| is"
+                 << " true.";
+    return;
+  }
+
+  SB_LOG(INFO) << "WriteEndOfStream() called.";
+
+  end_of_stream_written_ = true;
+
+  if (audio_track_thread_) {
+    decoder_->WriteEndOfStream();
+    return;
+  }
+
+  SB_LOG(INFO) << "Audio eos reached without any samples written.";
+  end_of_stream_played_.store(true);
+  ended_cb_();
+}
+
+void AudioRendererPassthrough::SetVolume(double volume) {
+  SB_DCHECK(BelongsToCurrentThread());
+
+  if (volume_ == volume) {
+    SB_LOG(INFO) << "Volume already at " << volume;
+    return;
+  }
+
+  SB_LOG(INFO) << "Set volume to " << volume;
+
+  ScopedLock scoped_lock(mutex_);
+  volume_ = volume;
+}
+
+bool AudioRendererPassthrough::IsEndOfStreamWritten() const {
+  SB_DCHECK(BelongsToCurrentThread());
+
+  return end_of_stream_written_;
+}
+
+bool AudioRendererPassthrough::IsEndOfStreamPlayed() const {
+  SB_DCHECK(BelongsToCurrentThread());
+
+  return end_of_stream_played_.load();
+}
+
+bool AudioRendererPassthrough::CanAcceptMoreData() const {
+  SB_DCHECK(BelongsToCurrentThread());
+
+  ScopedLock scoped_lock(mutex_);
+  return can_accept_more_data_.load() &&
+         decoded_audios_.size() < kMaxDecodedAudios;
+}
+
+void AudioRendererPassthrough::Play() {
+  SB_DCHECK(BelongsToCurrentThread());
+
+  if (!paused_) {
+    SB_LOG(INFO) << "Already playing.";
+    return;
+  }
+
+  SB_LOG(INFO) << "Play.";
+
+  ScopedLock scoped_lock(mutex_);
+  paused_ = false;
+}
+
+void AudioRendererPassthrough::Pause() {
+  SB_DCHECK(BelongsToCurrentThread());
+
+  if (paused_) {
+    SB_LOG(INFO) << "Already paused.";
+    return;
+  }
+
+  SB_LOG(INFO) << "Pause.";
+
+  ScopedLock scoped_lock(mutex_);
+  paused_ = true;
+}
+
+void AudioRendererPassthrough::SetPlaybackRate(double playback_rate) {
+  SB_DCHECK(BelongsToCurrentThread());
+
+  if (playback_rate > 0.0 && playback_rate != 1.0) {
+    // TODO: Report unsupported playback rate as an error.
+    SB_LOG(WARNING) << "Playback rate " << playback_rate << " is not supported"
+                    << " and is set to 1.0.";
+    playback_rate = 1.0;
+  }
+
+  if (playback_rate_ == playback_rate) {
+    SB_LOG(INFO) << "Playback rate already at " << playback_rate;
+    return;
+  }
+
+  SB_LOG(INFO) << "Change playback rate from " << playback_rate_ << " to "
+               << playback_rate << ".";
+
+  ScopedLock scoped_lock(mutex_);
+  playback_rate_ = playback_rate;
+}
+
+void AudioRendererPassthrough::Seek(SbTime seek_to_time) {
+  SB_DCHECK(BelongsToCurrentThread());
+
+  SB_LOG(INFO) << "Seek to " << seek_to_time;
+
+  decoder_->Reset();
+
+  bool seek_to_time_set = false;
+  if (audio_track_thread_) {
+    audio_track_thread_->ScheduleAndWait(
+        std::bind(&AudioRendererPassthrough::FlushAudioTrackAndStopProcessing,
+                  this, seek_to_time));
+    // |seek_to_time_| is updated inside FlushAudioTrackAndStopProcessing(),
+    // update the flag so we needn't set it again below.
+    seek_to_time_set = true;
+    // Destroy the audio track thread, it will be re-created during preroll.
+    audio_track_thread_.reset();
+  }
+
+  CancelPendingJobs();
+
+  ScopedLock scoped_lock(mutex_);
+
+  can_accept_more_data_.store(true);
+  prerolled_.store(false);
+  end_of_stream_played_.store(false);
+  total_frames_written_ = 0;
+
+  end_of_stream_written_ = false;
+
+  stop_called_ = false;
+  playback_head_position_when_stopped_ = 0;
+  stopped_at_ = 0;
+  if (!seek_to_time_set) {
+    seek_to_time_ = seek_to_time;
+  }
+  paused_ = true;
+  decoded_audios_ = std::queue<scoped_refptr<DecodedAudio>>();  // clear it
+  decoded_audio_writing_in_progress_ = nullptr;
+  decoded_audio_writing_offset_ = 0;
+  total_frames_written_on_audio_track_thread_ = 0;
+}
+
+// This function can be called from *any* threads.
+SbTime AudioRendererPassthrough::GetCurrentMediaTime(bool* is_playing,
+                                                     bool* is_eos_played,
+                                                     bool* is_underflow,
+                                                     double* playback_rate) {
+  SB_DCHECK(is_playing);
+  SB_DCHECK(is_eos_played);
+  SB_DCHECK(is_underflow);
+  SB_DCHECK(playback_rate);
+
+  ScopedLock scoped_lock(mutex_);
+  *is_playing = !paused_;
+  *is_eos_played = end_of_stream_played_.load();
+  *is_underflow = false;  // TODO: Support underflow
+  *playback_rate = playback_rate_;
+
+  if (!audio_track_bridge_) {
+    return seek_to_time_;
+  }
+
+  if (stop_called_) {
+    // When AudioTrackBridge::Stop() is called, the playback will continue until
+    // all the frames written are played, as the AudioTrack in created in
+    // MODE_STREAM.
+    auto now = SbTimeGetMonotonicNow();
+    SB_DCHECK(now >= stopped_at_);
+    auto time_elapsed = now - stopped_at_;
+    int64_t frames_played =
+        time_elapsed * audio_sample_info_.samples_per_second / kSbTimeSecond;
+    int64_t total_frames_played =
+        frames_played + playback_head_position_when_stopped_;
+    total_frames_played = std::min(total_frames_played, total_frames_written_);
+    return seek_to_time_ + total_frames_played * kSbTimeSecond /
+                               audio_sample_info_.samples_per_second;
+  }
+
+  SbTime updated_at;
+  auto playback_head_position =
+      audio_track_bridge_->GetPlaybackHeadPosition(&updated_at);
+  if (playback_head_position <= 0) {
+    // The playback is warming up, don't adjust the media time by the monotonic
+    // system time.
+    return seek_to_time_;
+  }
+
+  // TODO: This may cause time regression, because the unadjusted time will be
+  //       returned on pause, after an adjusted time has been returned.
+  SbTime playback_time =
+      seek_to_time_ + playback_head_position * kSbTimeSecond /
+                          audio_sample_info_.samples_per_second;
+  if (paused_ || playback_rate_ == 0.0) {
+    return playback_time;
+  }
+
+  // TODO: Cap this to the maximum frames written to the AudioTrack.
+  auto now = SbTimeGetMonotonicNow();
+  SB_LOG_IF(WARNING, now < updated_at)
+      << "now (" << now << ") is not greater than updated_at (" << updated_at
+      << ").";
+  playback_time += std::max<SbTime>(now - updated_at, 0);
+
+  return playback_time;
+}
+
+void AudioRendererPassthrough::CreateAudioTrackAndStartProcessing() {
+  SB_DCHECK(audio_track_thread_);
+  SB_DCHECK(audio_track_thread_->BelongsToCurrentThread());
+  SB_DCHECK(error_cb_);
+
+  if (audio_track_bridge_) {
+    SB_DCHECK(!update_status_and_write_data_token_.is_valid());
+    AudioTrackState initial_state;
+    update_status_and_write_data_token_ = audio_track_thread_->Schedule(
+        std::bind(&AudioRendererPassthrough::UpdateStatusAndWriteData, this,
+                  initial_state));
+    SB_LOG(INFO) << "|audio_track_bridge_| already created, start processing.";
+    return;
+  }
+
+  std::unique_ptr<AudioTrackBridge> audio_track_bridge(new AudioTrackBridge(
+      audio_sample_info_.codec == kSbMediaAudioCodecAc3
+          ? kSbMediaAudioCodingTypeAc3
+          : kSbMediaAudioCodingTypeDolbyDigitalPlus,
+      optional<SbMediaAudioSampleType>(),  // Not required in passthrough mode
+      audio_sample_info_.number_of_channels,
+      audio_sample_info_.samples_per_second, kPreferredBufferSizeInBytes,
+      kEnableAudioRouting, kTunnelModeAudioSessionId));
+
+  if (!audio_track_bridge->is_valid()) {
+    error_cb_(kSbPlayerErrorDecode, "Error creating AudioTrackBridge");
+    return;
+  }
+
+  {
+    ScopedLock scoped_lock(mutex_);
+    audio_track_bridge_ = std::move(audio_track_bridge);
+  }
+
+  AudioTrackState initial_state;
+  update_status_and_write_data_token_ = audio_track_thread_->Schedule(
+      std::bind(&AudioRendererPassthrough::UpdateStatusAndWriteData, this,
+                initial_state));
+  SB_LOG(INFO) << "|audio_track_bridge_| created, start processing.";
+}
+
+void AudioRendererPassthrough::FlushAudioTrackAndStopProcessing(
+    SbTime seek_to_time) {
+  SB_DCHECK(audio_track_thread_);
+  SB_DCHECK(audio_track_thread_->BelongsToCurrentThread());
+
+  SB_LOG(INFO) << "Pause audio track and stop processing.";
+
+  // Flushing of |audio_track_bridge_| and updating of |seek_to_time_| have to
+  // be done together under lock to avoid |seek_to_time_| being added to a stale
+  // playback head or vice versa in GetCurrentMediaTime().
+  ScopedLock scoped_lock(mutex_);
+
+  // We have to reuse |audio_track_bridge_| instead of creating a new one, to
+  // reduce output mode switching between PCM and e/ac3.  Otherwise a noticeable
+  // silence can be observed after seeking on some audio receivers.
+  // TODO: Consider reusing audio sink for non-passthrough playbacks, to see if
+  //       it reduces latency after seeking.
+  audio_track_bridge_->PauseAndFlush();
+  seek_to_time_ = seek_to_time;
+  paused_ = true;
+  if (update_status_and_write_data_token_.is_valid()) {
+    audio_track_thread_->RemoveJobByToken(update_status_and_write_data_token_);
+    update_status_and_write_data_token_.ResetToInvalid();
+  }
+}
+
+void AudioRendererPassthrough::UpdateStatusAndWriteData(
+    const AudioTrackState previous_state) {
+  SB_DCHECK(audio_track_thread_);
+  SB_DCHECK(audio_track_thread_->BelongsToCurrentThread());
+  SB_DCHECK(error_cb_);
+  SB_DCHECK(audio_track_bridge_);
+
+  AudioTrackState current_state;
+
+  {
+    ScopedLock scoped_lock(mutex_);
+    current_state.volume = volume_;
+    current_state.paused = paused_;
+    current_state.playback_rate = playback_rate_;
+
+    if (!decoded_audio_writing_in_progress_ && !decoded_audios_.empty()) {
+      decoded_audio_writing_in_progress_ = decoded_audios_.front();
+      decoded_audios_.pop();
+      decoded_audio_writing_offset_ = 0;
+    }
+  }
+
+  if (previous_state.volume != current_state.volume) {
+    audio_track_bridge_->SetVolume(current_state.volume);
+  }
+  if (previous_state.playing() != current_state.playing()) {
+    if (current_state.playing()) {
+      audio_track_bridge_->Play();
+      SB_LOG(INFO) << "Played on AudioTrack thread.";
+      ScopedLock scoped_lock(mutex_);
+      stop_called_ = false;
+    } else {
+      audio_track_bridge_->Pause();
+      SB_LOG(INFO) << "Paused on AudioTrack thread.";
+    }
+  }
+
+  bool fully_written = false;
+  if (decoded_audio_writing_in_progress_) {
+    if (decoded_audio_writing_in_progress_->is_end_of_stream()) {
+      if (!prerolled_.exchange(true)) {
+        SB_LOG(INFO) << "Prerolled due to end of stream.";
+        prerolled_cb_();
+      }
+      ScopedLock scoped_lock(mutex_);
+      if (current_state.playing() && !stop_called_) {
+        // TODO: Check if we can apply the same stop logic to non-passthrough.
+        audio_track_bridge_->Stop();
+        stop_called_ = true;
+        playback_head_position_when_stopped_ =
+            audio_track_bridge_->GetPlaybackHeadPosition(&stopped_at_);
+        total_frames_written_ = total_frames_written_on_audio_track_thread_;
+        decoded_audio_writing_in_progress_ = nullptr;
+        SB_LOG(INFO) << "Audio track stopped at " << stopped_at_
+                     << ", playback head: "
+                     << playback_head_position_when_stopped_;
+      }
+    } else {
+      auto sample_buffer = decoded_audio_writing_in_progress_->buffer() +
+                           decoded_audio_writing_offset_;
+      auto samples_to_write = (decoded_audio_writing_in_progress_->size() -
+                               decoded_audio_writing_offset_);
+      // TODO: |sync_time| currently doesn't take partial writes into account.
+      //       It is not used in non-tunneled mode so it doesn't matter, but we
+      //       should revisit this.
+      auto sync_time = decoded_audio_writing_in_progress_->timestamp();
+      int samples_written = audio_track_bridge_->WriteSample(
+          sample_buffer, samples_to_write, sync_time);
+      // Error code returned as negative value, like kAudioTrackErrorDeadObject.
+      if (samples_written < 0) {
+        // `kSbPlayerErrorDecode` is used for general SbPlayer error, there is
+        // no error code corresponding to audio sink.
+        auto error = kSbPlayerErrorDecode;
+        if (samples_written == AudioTrackBridge::kAudioTrackErrorDeadObject) {
+          // Inform the audio end point change.
+          error = kSbPlayerErrorCapabilityChanged;
+        }
+        error_cb_(error, FormatString("Error while writing frames: %d",
+                                      samples_written));
+      }
+      decoded_audio_writing_offset_ += samples_written;
+
+      if (decoded_audio_writing_offset_ ==
+          decoded_audio_writing_in_progress_->size()) {
+        total_frames_written_on_audio_track_thread_ += frames_per_input_buffer_;
+        decoded_audio_writing_in_progress_ = nullptr;
+        decoded_audio_writing_offset_ = 0;
+        fully_written = true;
+      } else if (!prerolled_.exchange(true)) {
+        // The audio sink no longer takes all the samples written to it.  Assume
+        // that it has enough samples and preroll is finished.
+        SB_LOG(INFO) << "Prerolled.";
+        prerolled_cb_();
+      }
+    }
+  }
+
+  // EOS is handled on this thread instead of in GetCurrentMediaTime(), because
+  // GetCurrentMediaTime() is not guaranteed to be called.
+  if (stop_called_ && !end_of_stream_played_.load()) {
+    auto time_elapsed = SbTimeGetMonotonicNow() - stopped_at_;
+    auto frames_played =
+        time_elapsed * audio_sample_info_.samples_per_second / kSbTimeSecond;
+    if (frames_played + playback_head_position_when_stopped_ >=
+        total_frames_written_on_audio_track_thread_) {
+      end_of_stream_played_.store(true);
+      ended_cb_();
+      SB_LOG(INFO) << "Audio playback ended, UpdateStatusAndWriteData stopped.";
+      return;
+    }
+  }
+
+  update_status_and_write_data_token_ = audio_track_thread_->Schedule(
+      std::bind(&AudioRendererPassthrough::UpdateStatusAndWriteData, this,
+                current_state),
+      fully_written ? 0 : kAudioTrackUpdateInternal);
+}
+
+// This function can be called from *any* threads.
+void AudioRendererPassthrough::OnDecoderConsumed() {
+  auto old_value = can_accept_more_data_.exchange(true);
+  SB_DCHECK(!old_value);
+}
+
+// This function can be called from *any* threads.
+void AudioRendererPassthrough::OnDecoderOutput() {
+  int decoded_audio_sample_rate;
+  auto decoded_audio = decoder_->Read(&decoded_audio_sample_rate);
+  SB_DCHECK(decoded_audio);
+
+  ScopedLock scoped_lock(mutex_);
+  decoded_audios_.push(decoded_audio);
+}
+
+}  // namespace shared
+}  // namespace android
+}  // namespace starboard
diff --git a/src/starboard/android/shared/audio_renderer_passthrough.h b/src/starboard/android/shared/audio_renderer_passthrough.h
new file mode 100644
index 0000000..6d9a050
--- /dev/null
+++ b/src/starboard/android/shared/audio_renderer_passthrough.h
@@ -0,0 +1,146 @@
+// Copyright 2021 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_AUDIO_RENDERER_PASSTHROUGH_H_
+#define STARBOARD_ANDROID_SHARED_AUDIO_RENDERER_PASSTHROUGH_H_
+
+#include <memory>
+#include <queue>
+
+#include "starboard/android/shared/audio_decoder.h"
+#include "starboard/android/shared/audio_track_bridge.h"
+#include "starboard/android/shared/drm_system.h"
+#include "starboard/atomic.h"
+#include "starboard/common/mutex.h"
+#include "starboard/common/ref_counted.h"
+#include "starboard/drm.h"
+#include "starboard/media.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/decoded_audio_internal.h"
+#include "starboard/shared/starboard/player/filter/audio_renderer_internal.h"
+#include "starboard/shared/starboard/player/filter/common.h"
+#include "starboard/shared/starboard/player/filter/media_time_provider.h"
+#include "starboard/shared/starboard/player/input_buffer_internal.h"
+#include "starboard/shared/starboard/player/job_queue.h"
+#include "starboard/shared/starboard/player/job_thread.h"
+#include "starboard/time.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+// TODO: The audio receiver often requires some warm up time to switch the
+//       output to eac3.  Consider pushing some silence at the very beginning so
+//       the sound at the very beginning won't get lost during the switching.
+class AudioRendererPassthrough
+    : public ::starboard::shared::starboard::player::filter::AudioRenderer,
+      public ::starboard::shared::starboard::player::filter::MediaTimeProvider,
+      private ::starboard::shared::starboard::player::JobQueue::JobOwner {
+ public:
+  AudioRendererPassthrough(const SbMediaAudioSampleInfo& audio_sample_info,
+                           SbDrmSystem drm_system);
+  ~AudioRendererPassthrough() override;
+
+  bool is_valid() const { return decoder_ != nullptr; }
+
+  // AudioRenderer methods
+  void Initialize(const ErrorCB& error_cb,
+                  const PrerolledCB& prerolled_cb,
+                  const EndedCB& ended_cb) override;
+  void WriteSample(const scoped_refptr<InputBuffer>& input_buffer) override;
+  void WriteEndOfStream() override;
+
+  void SetVolume(double volume) override;
+
+  bool IsEndOfStreamWritten() const override;
+  bool IsEndOfStreamPlayed() const override;
+  bool CanAcceptMoreData() const override;
+
+  // MediaTimeProvider methods
+  void Play() override;
+  void Pause() override;
+  void SetPlaybackRate(double playback_rate) override;
+  void Seek(SbTime seek_to_time) override;
+  SbTime GetCurrentMediaTime(bool* is_playing,
+                             bool* is_eos_played,
+                             bool* is_underflow,
+                             double* playback_rate) override;
+
+ private:
+  typedef ::starboard::shared::starboard::player::DecodedAudio DecodedAudio;
+  typedef ::starboard::shared::starboard::player::JobThread JobThread;
+  typedef ::starboard::shared::starboard::player::JobQueue::JobToken JobToken;
+
+  struct AudioTrackState {
+    double volume = 1.0;
+    bool paused = true;
+    double playback_rate = 1.0;
+
+    bool playing() const { return !paused && playback_rate > 0.0; }
+  };
+
+  void CreateAudioTrackAndStartProcessing();
+  void FlushAudioTrackAndStopProcessing(SbTime seek_to_time);
+  void UpdateStatusAndWriteData(const AudioTrackState previous_state);
+  void OnDecoderConsumed();
+  void OnDecoderOutput();
+
+  // The following two variables are set in the ctor.
+  const SbMediaAudioSampleInfo audio_sample_info_;
+  // The AudioDecoder is used as a decryptor when the stream is encrypted.
+  // TODO: Revisit to encapsulate the AudioDecoder as a SbDrmSystemPrivate
+  //       instead.  This would need to turn SbDrmSystemPrivate::Decrypt() into
+  //       asynchronous, which comes with extra risks.
+  std::unique_ptr<::starboard::shared::starboard::player::filter::AudioDecoder>
+      decoder_;
+
+  // The following three variables are set in Initialize().
+  ErrorCB error_cb_;
+  PrerolledCB prerolled_cb_;
+  EndedCB ended_cb_;
+
+  int frames_per_input_buffer_ = 0;  // Set once before all uses.
+  atomic_bool can_accept_more_data_{true};
+  atomic_bool prerolled_;
+  atomic_bool end_of_stream_played_;
+
+  bool end_of_stream_written_ = false;  // Only accessed on PlayerWorker thread.
+
+  Mutex mutex_;
+  bool stop_called_ = false;
+  int64_t total_frames_written_ = 0;
+  int64_t playback_head_position_when_stopped_ = 0;
+  SbTimeMonotonic stopped_at_ = 0;
+  SbTime seek_to_time_ = 0;
+  double volume_ = 1.0;
+  bool paused_ = true;
+  double playback_rate_ = 1.0;
+  std::queue<scoped_refptr<DecodedAudio>> decoded_audios_;
+
+  // The following variable group is only accessed on |audio_track_thread_|, or
+  // after |audio_track_thread_| is destroyed (in Seek()).
+  scoped_refptr<DecodedAudio> decoded_audio_writing_in_progress_;
+  int decoded_audio_writing_offset_ = 0;
+  JobToken update_status_and_write_data_token_;
+  int64_t total_frames_written_on_audio_track_thread_ = 0;
+
+  std::unique_ptr<JobThread> audio_track_thread_;
+  std::unique_ptr<AudioTrackBridge> audio_track_bridge_;
+};
+
+}  // namespace shared
+}  // namespace android
+}  // namespace starboard
+
+#endif  // STARBOARD_ANDROID_SHARED_AUDIO_RENDERER_PASSTHROUGH_H_
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 189cea7..4c46710 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.cc
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "starboard/common/string.h"
+#include "starboard/shared/starboard/media/media_util.h"
 #include "starboard/shared/starboard/player/filter/common.h"
 
 namespace {
@@ -31,8 +32,7 @@
 namespace shared {
 namespace {
 
-// The same as AudioTrack.ERROR_DEAD_OBJECT.
-const int kAudioTrackErrorDeadObject = -6;
+using ::starboard::shared::starboard::media::GetBytesPerSample;
 
 // 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,
@@ -48,7 +48,6 @@
 // large (non zero) and results in dropped video frames.
 const SbTime kMaxDurationPerRequestInTunnelMode = 16 * kSbTimeMillisecond;
 
-const jint kNoOffset = 0;
 const size_t kSilenceFramesPerAppend = 1024;
 
 const int kMaxRequiredFrames = 16 * 1024;
@@ -58,32 +57,6 @@
 const int kSampleFrequency22050 = 22050;
 const int kSampleFrequency48000 = 48000;
 
-// Helper function to compute the size of the two valid starboard audio sample
-// types.
-size_t GetSampleSize(SbMediaAudioSampleType sample_type) {
-  switch (sample_type) {
-    case kSbMediaAudioSampleTypeFloat32:
-      return sizeof(float);
-    case kSbMediaAudioSampleTypeInt16Deprecated:
-      return sizeof(int16_t);
-  }
-  SB_NOTREACHED();
-  return 0u;
-}
-
-int GetAudioFormatSampleType(SbMediaAudioSampleType sample_type) {
-  switch (sample_type) {
-    case kSbMediaAudioSampleTypeFloat32:
-      // Android AudioFormat.ENCODING_PCM_FLOAT.
-      return 4;
-    case kSbMediaAudioSampleTypeInt16Deprecated:
-      // Android AudioFormat.ENCODING_PCM_16BIT.
-      return 2;
-  }
-  SB_NOTREACHED();
-  return 0u;
-}
-
 void* IncrementPointerByBytes(void* pointer, size_t offset) {
   return static_cast<uint8_t*>(pointer) + offset;
 }
@@ -126,30 +99,21 @@
           tunnel_mode_audio_session_id_ == -1
               ? kMaxFramesPerRequest
               : GetMaxFramesPerRequestForTunnelMode(sampling_frequency_hz_)),
-      context_(context) {
+      context_(context),
+      bridge_(kSbMediaAudioCodingTypePcm,
+              sample_type,
+              channels,
+              sampling_frequency_hz,
+              preferred_buffer_size_in_bytes,
+              enable_audio_routing,
+              tunnel_mode_audio_session_id) {
   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",
-      "(IIIIZI)Ldev/cobalt/media/AudioTrackBridge;",
-      GetAudioFormatSampleType(sample_type_), sampling_frequency_hz_, channels_,
-      preferred_buffer_size_in_bytes, enable_audio_routing,
-      tunnel_mode_audio_session_id_);
-  if (!j_audio_track_bridge) {
+  if (!bridge_.is_valid()) {
     // 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
@@ -159,18 +123,6 @@
     //       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_ * max_frames_per_request_);
-  } else if (sample_type_ == kSbMediaAudioSampleTypeInt16Deprecated) {
-    j_audio_data_ = env->NewByteArray(channels_ * GetSampleSize(sample_type_) *
-                                      max_frames_per_request_);
-  } else {
-    SB_NOTREACHED();
-  }
-  SB_DCHECK(j_audio_data_) << "Failed to allocate |j_audio_data_|";
-
-  j_audio_data_ = env->ConvertLocalRefToGlobalRef(j_audio_data_);
 
   audio_out_thread_ = SbThreadCreate(
       0, kSbThreadPriorityRealTime, kSbThreadNoAffinity, true,
@@ -184,24 +136,6 @@
   if (SbThreadIsValid(audio_out_thread_)) {
     SbThreadJoin(audio_out_thread_, NULL);
   }
-
-  JniEnvExt* env = JniEnvExt::Get();
-  if (j_audio_track_bridge_) {
-    ScopedLocalJavaRef<jobject> j_audio_output_manager(
-        env->CallStarboardObjectMethodOrAbort(
-            "getAudioOutputManager",
-            "()Ldev/cobalt/media/AudioOutputManager;"));
-    env->CallVoidMethodOrAbort(
-        j_audio_output_manager.Get(), "destroyAudioTrackBridge",
-        "(Ldev/cobalt/media/AudioTrackBridge;)V", j_audio_track_bridge_);
-    env->DeleteGlobalRef(j_audio_track_bridge_);
-    j_audio_track_bridge_ = NULL;
-  }
-
-  if (j_audio_data_) {
-    env->DeleteGlobalRef(j_audio_data_);
-    j_audio_data_ = NULL;
-  }
 }
 
 void AudioTrackAudioSink::SetPlaybackRate(double playback_rate) {
@@ -244,9 +178,7 @@
   while (!quit_) {
     int playback_head_position = 0;
     SbTime frames_consumed_at = 0;
-    bool new_audio_device_added = env->CallBooleanMethodOrAbort(
-        j_audio_track_bridge_, "getAndResetHasNewAudioDeviceAdded", "()Z");
-    if (new_audio_device_added) {
+    if (bridge_.GetAndResetHasNewAudioDeviceAdded(env)) {
       SB_LOG(INFO) << "New audio device added.";
       error_func_(kSbPlayerErrorCapabilityChanged, "New audio device added.",
                   context_);
@@ -254,16 +186,8 @@
     }
 
     if (was_playing) {
-      ScopedLocalJavaRef<jobject> j_audio_timestamp(
-          env->CallObjectMethodOrAbort(j_audio_track_bridge_,
-                                       "getAudioTimestamp",
-                                       "()Landroid/media/AudioTimestamp;"));
-      playback_head_position = env->GetLongFieldOrAbort(j_audio_timestamp.Get(),
-                                                        "framePosition", "J");
-      frames_consumed_at =
-          env->GetLongFieldOrAbort(j_audio_timestamp.Get(), "nanoTime", "J") /
-          1000;
-
+      playback_head_position =
+          bridge_.GetPlaybackHeadPosition(&frames_consumed_at, env);
       SB_DCHECK(playback_head_position >= last_playback_head_position_);
 
       playback_head_position =
@@ -315,15 +239,13 @@
 
     if (was_playing && !is_playing) {
       was_playing = false;
-      env->CallVoidMethodOrAbort(j_audio_track_bridge_, "pause", "()V");
-      SB_LOG(INFO) << "AudioTrackAudioSink paused.";
+      bridge_.Pause();
     } 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.";
+      bridge_.Play();
     }
 
     if (!is_playing || frames_in_buffer == 0) {
@@ -358,7 +280,7 @@
         const int silence_frames_per_append =
             std::min<int>(kSilenceFramesPerAppend, max_frames_per_request_);
         std::vector<uint8_t> silence_buffer(channels_ *
-                                            GetSampleSize(sample_type_) *
+                                            GetBytesPerSample(sample_type_) *
                                             silence_frames_per_append);
         auto sync_time = start_time_ + accumulated_written_frames *
                                            kSbTimeSecond /
@@ -387,11 +309,12 @@
         << ", frames_in_audio_track: " << frames_in_audio_track
         << ", offset_in_frames: " << offset_in_frames;
 
-    int written_frames = WriteData(
-        env,
-        IncrementPointerByBytes(frame_buffer_, start_position * channels_ *
-                                                   GetSampleSize(sample_type_)),
-        expected_written_frames, sync_time);
+    int written_frames =
+        WriteData(env,
+                  IncrementPointerByBytes(frame_buffer_,
+                                          start_position * channels_ *
+                                              GetBytesPerSample(sample_type_)),
+                  expected_written_frames, sync_time);
     SbTime now = SbTimeGetMonotonicNow();
 
     if (written_frames < 0) {
@@ -399,7 +322,8 @@
       // dead.
       consume_frames_func_(frames_in_audio_track, now, context_);
 
-      bool capabilities_changed = written_frames == kAudioTrackErrorDeadObject;
+      bool capabilities_changed =
+          written_frames == AudioTrackBridge::kAudioTrackErrorDeadObject;
       error_func_(
           capabilities_changed,
           FormatString("Error while writing frames: %d", written_frames),
@@ -428,68 +352,39 @@
     }
   }
 
-  // For an immediate stop, use pause(), followed by flush() to discard audio
-  // data that hasn't been played back yet.
-  env->CallVoidMethodOrAbort(j_audio_track_bridge_, "pause", "()V");
-  // Flushes the audio data currently queued for playback. Any data that has
-  // been written but not yet presented will be discarded.
-  env->CallVoidMethodOrAbort(j_audio_track_bridge_, "flush", "()V");
+  bridge_.PauseAndFlush();
 }
 
 int AudioTrackAudioSink::WriteData(JniEnvExt* env,
-                                   void* buffer,
+                                   const void* buffer,
                                    int expected_written_frames,
                                    SbTime sync_time) {
+  int samples_written = 0;
   if (sample_type_ == kSbMediaAudioSampleTypeFloat32) {
-    int expected_written_size = expected_written_frames * channels_;
-    env->SetFloatArrayRegion(static_cast<jfloatArray>(j_audio_data_), kNoOffset,
-                             expected_written_size,
-                             static_cast<const float*>(buffer));
-    int written =
-        env->CallIntMethodOrAbort(j_audio_track_bridge_, "write", "([FI)I",
-                                  j_audio_data_, expected_written_size);
-    if (written < 0) {
-      // Error code returned as negative value, like kAudioTrackErrorDeadObject.
-      return written;
-    }
-    SB_DCHECK(written % channels_ == 0);
-    return written / channels_;
+    samples_written =
+        bridge_.WriteSample(static_cast<const float*>(buffer),
+                            expected_written_frames * channels_, env);
+  } else if (sample_type_ == kSbMediaAudioSampleTypeInt16Deprecated) {
+    samples_written = bridge_.WriteSample(static_cast<const uint16_t*>(buffer),
+                                          expected_written_frames * channels_,
+                                          sync_time, env);
+  } else {
+    SB_NOTREACHED();
   }
-  if (sample_type_ == kSbMediaAudioSampleTypeInt16Deprecated) {
-    int expected_written_size =
-        expected_written_frames * channels_ * GetSampleSize(sample_type_);
-    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",
-                                            "([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_));
+  if (samples_written < 0) {
+    // Error code returned as negative value, like kAudioTrackErrorDeadObject.
+    return samples_written;
   }
-  SB_NOTREACHED();
-  return 0;
+  SB_DCHECK(samples_written % channels_ == 0);
+  return samples_written / channels_;
 }
 
 void AudioTrackAudioSink::SetVolume(double volume) {
-  auto* env = JniEnvExt::Get();
-  jint status = env->CallIntMethodOrAbort(j_audio_track_bridge_, "setVolume",
-                                          "(F)I", static_cast<float>(volume));
-  if (status != 0) {
-    SB_LOG(ERROR) << "Failed to set volume";
-  }
+  bridge_.SetVolume(volume);
 }
 
 int AudioTrackAudioSink::GetUnderrunCount() {
-  auto* env = JniEnvExt::Get();
-  jint underrun_count = env->CallIntMethodOrAbort(j_audio_track_bridge_,
-                                                  "getUnderrunCount", "()I");
-  return underrun_count;
+  return bridge_.GetUnderrunCount();
 }
 
 // static
@@ -499,17 +394,9 @@
     int sampling_frequency_hz) {
   SB_DCHECK(audio_track_audio_sink_type_);
 
-  JniEnvExt* env = JniEnvExt::Get();
-  ScopedLocalJavaRef<jobject> j_audio_output_manager(
-      env->CallStarboardObjectMethodOrAbort(
-          "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
-  int audio_track_min_buffer_size = static_cast<int>(env->CallIntMethodOrAbort(
-      j_audio_output_manager.Get(), "getMinBufferSize", "(III)I",
-      GetAudioFormatSampleType(sample_type), sampling_frequency_hz, channels));
-  int audio_track_min_buffer_size_in_frames =
-      audio_track_min_buffer_size / channels / GetSampleSize(sample_type);
   return std::max(
-      audio_track_min_buffer_size_in_frames,
+      AudioTrackBridge::GetMinBufferSizeInFrames(sample_type, channels,
+                                                 sampling_frequency_hz),
       audio_track_audio_sink_type_->GetMinBufferSizeInFramesInternal(
           channels, sample_type, sampling_frequency_hz));
 }
@@ -558,7 +445,7 @@
       channels, audio_sample_type, sampling_frequency_hz);
   SB_DCHECK(frames_per_channel >= min_required_frames);
   int preferred_buffer_size_in_bytes =
-      min_required_frames * channels * GetSampleSize(audio_sample_type);
+      min_required_frames * channels * GetBytesPerSample(audio_sample_type);
   AudioTrackAudioSink* audio_sink = new AudioTrackAudioSink(
       this, channels, sampling_frequency_hz, audio_sample_type, frame_buffers,
       frames_per_channel, preferred_buffer_size_in_bytes,
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 88793c3..5ae9f7b 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.h
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.h
@@ -21,6 +21,7 @@
 #include <vector>
 
 #include "starboard/android/shared/audio_sink_min_required_frames_tester.h"
+#include "starboard/android/shared/audio_track_bridge.h"
 #include "starboard/android/shared/jni_env_ext.h"
 #include "starboard/android/shared/jni_utils.h"
 #include "starboard/audio_sink.h"
@@ -114,7 +115,7 @@
       void* context);
   ~AudioTrackAudioSink() override;
 
-  bool IsAudioTrackValid() const { return j_audio_track_bridge_; }
+  bool IsAudioTrackValid() const { return bridge_.is_valid(); }
   bool IsType(Type* type) override { return type_ == type; }
   void SetPlaybackRate(double playback_rate) override;
 
@@ -125,7 +126,7 @@
   static void* ThreadEntryPoint(void* context);
   void AudioThreadFunc();
 
-  int WriteData(JniEnvExt* env, void* buffer, int size, SbTime sync_time);
+  int WriteData(JniEnvExt* env, const void* buffer, int size, SbTime sync_time);
 
   Type* const type_;
   const int channels_;
@@ -139,11 +140,11 @@
   const SbTime start_time_;
   const int tunnel_mode_audio_session_id_;
   const int max_frames_per_request_;
-
   void* const context_;
+
+  AudioTrackBridge bridge_;
+
   int last_playback_head_position_ = 0;
-  jobject j_audio_track_bridge_ = nullptr;
-  jobject j_audio_data_ = nullptr;
 
   volatile bool quit_ = false;
   SbThread audio_out_thread_ = kSbThreadInvalid;
diff --git a/src/starboard/android/shared/audio_track_bridge.cc b/src/starboard/android/shared/audio_track_bridge.cc
new file mode 100644
index 0000000..572e759
--- /dev/null
+++ b/src/starboard/android/shared/audio_track_bridge.cc
@@ -0,0 +1,294 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/audio_track_bridge.h"
+
+#include <algorithm>
+
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/media_common.h"
+#include "starboard/audio_sink.h"
+#include "starboard/common/log.h"
+#include "starboard/shared/starboard/media/media_util.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+namespace {
+
+using ::starboard::shared::starboard::media::GetBytesPerSample;
+
+const jint kNoOffset = 0;
+
+}  // namespace
+
+AudioTrackBridge::AudioTrackBridge(SbMediaAudioCodingType coding_type,
+                                   optional<SbMediaAudioSampleType> sample_type,
+                                   int channels,
+                                   int sampling_frequency_hz,
+                                   int preferred_buffer_size_in_bytes,
+                                   bool enable_audio_routing,
+                                   int tunnel_mode_audio_session_id) {
+  if (coding_type == kSbMediaAudioCodingTypePcm) {
+    SB_DCHECK(SbAudioSinkIsAudioSampleTypeSupported(sample_type.value()));
+
+    // TODO: Support query if platform supports float type for tunnel mode.
+    if (tunnel_mode_audio_session_id != -1) {
+      SB_DCHECK(sample_type.value() == kSbMediaAudioSampleTypeInt16Deprecated);
+    }
+  } else {
+    SB_DCHECK(coding_type == kSbMediaAudioCodingTypeAc3 ||
+              coding_type == kSbMediaAudioCodingTypeDolbyDigitalPlus);
+    // TODO: Support passthrough under tunnel mode.
+    SB_DCHECK(tunnel_mode_audio_session_id == -1);
+    // TODO: |sample_type| is not used in passthrough mode, we should make this
+    // explicit.
+  }
+
+  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",
+      "(IIIIZI)Ldev/cobalt/media/AudioTrackBridge;",
+      GetAudioFormatSampleType(coding_type, sample_type), sampling_frequency_hz,
+      channels, preferred_buffer_size_in_bytes, enable_audio_routing,
+      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.
+    SB_LOG(WARNING) << "Failed to create |j_audio_track_bridge|.";
+    return;
+  }
+  j_audio_track_bridge_ = env->ConvertLocalRefToGlobalRef(j_audio_track_bridge);
+  if (coding_type != kSbMediaAudioCodingTypePcm) {
+    // This must be passthrough.
+    SB_DCHECK(!sample_type);
+    max_samples_per_write_ = kMaxFramesPerRequest;
+    j_audio_data_ = env->NewByteArray(max_samples_per_write_);
+  } else if (sample_type == kSbMediaAudioSampleTypeFloat32) {
+    max_samples_per_write_ = channels * kMaxFramesPerRequest;
+    j_audio_data_ = env->NewFloatArray(channels * kMaxFramesPerRequest);
+  } else if (sample_type == kSbMediaAudioSampleTypeInt16Deprecated) {
+    max_samples_per_write_ = channels * kMaxFramesPerRequest;
+    j_audio_data_ =
+        env->NewByteArray(channels * GetBytesPerSample(sample_type.value()) *
+                          kMaxFramesPerRequest);
+  } else {
+    SB_NOTREACHED();
+  }
+  SB_DCHECK(j_audio_data_) << "Failed to allocate |j_audio_data_|";
+
+  j_audio_data_ = env->ConvertLocalRefToGlobalRef(j_audio_data_);
+}
+
+AudioTrackBridge::~AudioTrackBridge() {
+  JniEnvExt* env = JniEnvExt::Get();
+  if (j_audio_track_bridge_) {
+    ScopedLocalJavaRef<jobject> j_audio_output_manager(
+        env->CallStarboardObjectMethodOrAbort(
+            "getAudioOutputManager",
+            "()Ldev/cobalt/media/AudioOutputManager;"));
+    env->CallVoidMethodOrAbort(
+        j_audio_output_manager.Get(), "destroyAudioTrackBridge",
+        "(Ldev/cobalt/media/AudioTrackBridge;)V", j_audio_track_bridge_);
+    env->DeleteGlobalRef(j_audio_track_bridge_);
+    j_audio_track_bridge_ = nullptr;
+  }
+
+  if (j_audio_data_) {
+    env->DeleteGlobalRef(j_audio_data_);
+    j_audio_data_ = nullptr;
+  }
+}
+
+// static
+int AudioTrackBridge::GetMinBufferSizeInFrames(
+    SbMediaAudioSampleType sample_type,
+    int channels,
+    int sampling_frequency_hz,
+    JniEnvExt* env /*= JniEnvExt::Get()*/) {
+  SB_DCHECK(env);
+
+  ScopedLocalJavaRef<jobject> j_audio_output_manager(
+      env->CallStarboardObjectMethodOrAbort(
+          "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
+  int audio_track_min_buffer_size = static_cast<int>(env->CallIntMethodOrAbort(
+      j_audio_output_manager.Get(), "getMinBufferSize", "(III)I",
+      GetAudioFormatSampleType(kSbMediaAudioCodingTypePcm, sample_type),
+      sampling_frequency_hz, channels));
+  return audio_track_min_buffer_size / channels /
+         GetBytesPerSample(sample_type);
+}
+
+void AudioTrackBridge::Play(JniEnvExt* env /*= JniEnvExt::Get()*/) {
+  SB_DCHECK(env);
+  SB_DCHECK(is_valid());
+
+  env->CallVoidMethodOrAbort(j_audio_track_bridge_, "play", "()V");
+  SB_LOG(INFO) << "AudioTrackBridge playing.";
+}
+
+void AudioTrackBridge::Pause(JniEnvExt* env /*= JniEnvExt::Get()*/) {
+  SB_DCHECK(env);
+  SB_DCHECK(is_valid());
+
+  env->CallVoidMethodOrAbort(j_audio_track_bridge_, "pause", "()V");
+  SB_LOG(INFO) << "AudioTrackBridge paused.";
+}
+
+void AudioTrackBridge::Stop(JniEnvExt* env /*= JniEnvExt::Get()*/) {
+  SB_DCHECK(env);
+  SB_DCHECK(is_valid());
+
+  env->CallVoidMethodOrAbort(j_audio_track_bridge_, "stop", "()V");
+  SB_LOG(INFO) << "AudioTrackBridge stopped.";
+}
+
+void AudioTrackBridge::PauseAndFlush(JniEnvExt* env /*= JniEnvExt::Get()*/) {
+  SB_DCHECK(env);
+  SB_DCHECK(is_valid());
+
+  // For an immediate stop, use pause(), followed by flush() to discard audio
+  // data that hasn't been played back yet.
+  env->CallVoidMethodOrAbort(j_audio_track_bridge_, "pause", "()V");
+  // Flushes the audio data currently queued for playback. Any data that has
+  // been written but not yet presented will be discarded.
+  env->CallVoidMethodOrAbort(j_audio_track_bridge_, "flush", "()V");
+}
+
+int AudioTrackBridge::WriteSample(const float* samples,
+                                  int num_of_samples,
+                                  JniEnvExt* env /*= JniEnvExt::Get()*/) {
+  SB_DCHECK(env);
+  SB_DCHECK(is_valid());
+  SB_DCHECK(num_of_samples <= max_samples_per_write_);
+
+  num_of_samples = std::min(num_of_samples, max_samples_per_write_);
+
+  // TODO: Test this code path
+  env->SetFloatArrayRegion(static_cast<jfloatArray>(j_audio_data_), kNoOffset,
+                           num_of_samples, samples);
+  int samples_written = env->CallIntMethodOrAbort(
+      j_audio_track_bridge_, "write", "([FI)I", j_audio_data_, num_of_samples);
+  return samples_written;
+}
+
+int AudioTrackBridge::WriteSample(const uint16_t* samples,
+                                  int num_of_samples,
+                                  SbTime sync_time,
+                                  JniEnvExt* env /*= JniEnvExt::Get()*/) {
+  SB_DCHECK(env);
+  SB_DCHECK(is_valid());
+  SB_DCHECK(num_of_samples <= max_samples_per_write_);
+
+  num_of_samples = std::min(num_of_samples, max_samples_per_write_);
+
+  // TODO: Test this code path
+  env->SetByteArrayRegion(static_cast<jbyteArray>(j_audio_data_), kNoOffset,
+                          num_of_samples * sizeof(uint16_t),
+                          reinterpret_cast<const jbyte*>(samples));
+
+  int bytes_written = env->CallIntMethodOrAbort(
+      j_audio_track_bridge_, "write", "([BIJ)I", j_audio_data_,
+      num_of_samples * sizeof(uint16_t), sync_time);
+  if (bytes_written < 0) {
+    // Error code returned as negative value, like AudioTrack.ERROR_DEAD_OBJECT.
+    return bytes_written;
+  }
+  SB_DCHECK(bytes_written % sizeof(uint16_t) == 0);
+  return bytes_written / sizeof(uint16_t);
+}
+
+int AudioTrackBridge::WriteSample(const uint8_t* samples,
+                                  int num_of_samples,
+                                  SbTime sync_time,
+                                  JniEnvExt* env /*= JniEnvExt::Get()*/) {
+  SB_DCHECK(env);
+  SB_DCHECK(is_valid());
+  SB_DCHECK(num_of_samples <= max_samples_per_write_);
+
+  num_of_samples = std::min(num_of_samples, max_samples_per_write_);
+
+  env->SetByteArrayRegion(static_cast<jbyteArray>(j_audio_data_), kNoOffset,
+                          num_of_samples,
+                          reinterpret_cast<const jbyte*>(samples));
+
+  int bytes_written =
+      env->CallIntMethodOrAbort(j_audio_track_bridge_, "write", "([BIJ)I",
+                                j_audio_data_, num_of_samples, sync_time);
+  if (bytes_written < 0) {
+    // Error code returned as negative value, like AudioTrack.ERROR_DEAD_OBJECT.
+    return bytes_written;
+  }
+  return bytes_written;
+}
+
+void AudioTrackBridge::SetVolume(double volume,
+                                 JniEnvExt* env /*= JniEnvExt::Get()*/) {
+  SB_DCHECK(env);
+  SB_DCHECK(is_valid());
+
+  jint status = env->CallIntMethodOrAbort(j_audio_track_bridge_, "setVolume",
+                                          "(F)I", static_cast<float>(volume));
+  if (status != 0) {
+    SB_LOG(ERROR) << "Failed to set volume to " << volume;
+  }
+}
+
+int64_t AudioTrackBridge::GetPlaybackHeadPosition(
+    SbTime* updated_at,
+    JniEnvExt* env /*= JniEnvExt::Get()*/) {
+  SB_DCHECK(env);
+  SB_DCHECK(is_valid());
+
+  ScopedLocalJavaRef<jobject> j_audio_timestamp(
+      env->CallObjectMethodOrAbort(j_audio_track_bridge_, "getAudioTimestamp",
+                                   "()Landroid/media/AudioTimestamp;"));
+  if (updated_at) {
+    *updated_at =
+        env->GetLongFieldOrAbort(j_audio_timestamp.Get(), "nanoTime", "J") /
+        kSbTimeNanosecondsPerMicrosecond;
+  }
+  return env->GetLongFieldOrAbort(j_audio_timestamp.Get(), "framePosition",
+                                  "J");
+}
+
+bool AudioTrackBridge::GetAndResetHasNewAudioDeviceAdded(
+    JniEnvExt* env /*= JniEnvExt::Get()*/) {
+  SB_DCHECK(env);
+  SB_DCHECK(is_valid());
+
+  return env->CallBooleanMethodOrAbort(
+      j_audio_track_bridge_, "getAndResetHasNewAudioDeviceAdded", "()Z");
+}
+
+int AudioTrackBridge::GetUnderrunCount(JniEnvExt* env /*= JniEnvExt::Get()*/) {
+  SB_DCHECK(env);
+  SB_DCHECK(is_valid());
+
+  return env->CallIntMethodOrAbort(j_audio_track_bridge_, "getUnderrunCount",
+                                   "()I");
+}
+
+}  // namespace shared
+}  // namespace android
+}  // namespace starboard
diff --git a/src/starboard/android/shared/audio_track_bridge.h b/src/starboard/android/shared/audio_track_bridge.h
new file mode 100644
index 0000000..e18e498
--- /dev/null
+++ b/src/starboard/android/shared/audio_track_bridge.h
@@ -0,0 +1,99 @@
+// Copyright 2021 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_AUDIO_TRACK_BRIDGE_H_
+#define STARBOARD_ANDROID_SHARED_AUDIO_TRACK_BRIDGE_H_
+
+#include <jni.h>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/common/optional.h"
+#include "starboard/media.h"
+#include "starboard/time.h"
+#include "starboard/types.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+// The C++ encapsulation of the Java class AudioTrackBridge.
+class AudioTrackBridge {
+ public:
+  // The maximum number of frames that can be written to android audio track per
+  // write request.  It is used to pre-allocate |j_audio_data_|.
+  static constexpr int kMaxFramesPerRequest = 65536;
+  // The same as Android AudioTrack.ERROR_DEAD_OBJECT.
+  static constexpr int kAudioTrackErrorDeadObject = -6;
+
+  AudioTrackBridge(SbMediaAudioCodingType coding_type,
+                   optional<SbMediaAudioSampleType> sample_type,
+                   int channels,
+                   int sampling_frequency_hz,
+                   int preferred_buffer_size_in_bytes,
+                   bool enable_audio_routing,
+                   int tunnel_mode_audio_session_id);
+  ~AudioTrackBridge();
+
+  static int GetMinBufferSizeInFrames(SbMediaAudioSampleType sample_type,
+                                      int channels,
+                                      int sampling_frequency_hz,
+                                      JniEnvExt* env = JniEnvExt::Get());
+
+  bool is_valid() const {
+    return j_audio_track_bridge_ != nullptr && j_audio_data_ != nullptr;
+  }
+
+  void Play(JniEnvExt* env = JniEnvExt::Get());
+  void Pause(JniEnvExt* env = JniEnvExt::Get());
+  void Stop(JniEnvExt* env = JniEnvExt::Get());
+  void PauseAndFlush(JniEnvExt* env = JniEnvExt::Get());
+
+  int WriteSample(const float* samples,
+                  int num_of_samples,
+                  JniEnvExt* env = JniEnvExt::Get());
+  // Returns samples written.
+  int WriteSample(const uint16_t* samples,
+                  int num_of_samples,
+                  SbTime sync_time,
+                  JniEnvExt* env = JniEnvExt::Get());
+  // This is used by passthrough, it treats samples as if they are in bytes.
+  int WriteSample(const uint8_t* buffer,
+                  int num_of_samples,
+                  SbTime sync_time,
+                  JniEnvExt* env = JniEnvExt::Get());
+
+  void SetVolume(double volume, JniEnvExt* env = JniEnvExt::Get());
+
+  // |updated_at| contains the timestamp when the playback head position is
+  // updated on return.  It can be nullptr.
+  int64_t GetPlaybackHeadPosition(SbTime* updated_at,
+                                  JniEnvExt* env = JniEnvExt::Get());
+  bool GetAndResetHasNewAudioDeviceAdded(JniEnvExt* env = JniEnvExt::Get());
+  int GetUnderrunCount(JniEnvExt* env = JniEnvExt::Get());
+
+ private:
+  int max_samples_per_write_;
+
+  jobject j_audio_track_bridge_ = nullptr;
+  // The audio data has to be copied into a Java Array before writing into the
+  // audio track.  Allocating a large array and saves as a member variable
+  // avoids an array being allocated repeatedly.
+  jobject j_audio_data_ = nullptr;
+};
+
+}  // namespace shared
+}  // namespace android
+}  // namespace starboard
+
+#endif  // STARBOARD_ANDROID_SHARED_AUDIO_TRACK_BRIDGE_H_
diff --git a/src/starboard/android/shared/gyp_configuration.py b/src/starboard/android/shared/gyp_configuration.py
index 4a8fd94..e38efdf 100644
--- a/src/starboard/android/shared/gyp_configuration.py
+++ b/src/starboard/android/shared/gyp_configuration.py
@@ -277,6 +277,51 @@
   # A map of failing or crashing tests per target.
   __FILTERED_TESTS = {  # pylint: disable=invalid-name
       'player_filter_tests': [
+          # All e/ac3 related decoder tests are disabled.  They will be
+          # re-enabled soon once we can filter out audio decoder tests for
+          # passthrough decoders.
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/12',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/26',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/40',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/54',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/68',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/82',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/84',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/86',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/88',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/90',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/92',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/94',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/96',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/98',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/100',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/102',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/104',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/12',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/26',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/40',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/54',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/68',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/82',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/84',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/86',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/88',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/90',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/92',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/94',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/96',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/98',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.' +
+              'MultipleInput/100',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.' +
+              'MultipleInput/102',
+          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.' +
+              'MultipleInput/104',
+          'AudioDecoderTests/AudioDecoderTest.SingleInput/12',
+          'AudioDecoderTests/AudioDecoderTest.ResetBeforeInput/12',
+          'AudioDecoderTests/AudioDecoderTest.MultipleInputs/12',
+          'AudioDecoderTests/AudioDecoderTest.LimitedInput/12',
+          'AudioDecoderTests/AudioDecoderTest.ContinuedLimitedInput/12',
 
           # GetMaxNumberOfCachedFrames() on Android is device dependent,
           # and Android doesn't provide an API to get it. So, this function
@@ -296,6 +341,9 @@
 
           # The video pipeline will hang if it doesn't receive any input.
           'PlayerComponentsTests/PlayerComponentsTest.EOSWithoutInput/*',
+
+          # The e/eac3 audio time reporting during pause will be revisitied.
+          'PlayerComponentsTests/PlayerComponentsTest.Pause/15',
       ],
       'nplb': [
           # This test is failing because localhost is not defined for IPv6 in
@@ -316,6 +364,17 @@
           # These tests are disabled due to not receiving the kEndOfStream
           # player state update within the specified timeout.
           'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.NoInput/*',
+
+          # Android does not use SbDrmSessionClosedFunc, which these tests
+          # depend on.
+          'SbDrmSessionTest.SunnyDay',
+          'SbDrmSessionTest.CloseDrmSessionBeforeUpdateSession',
+
+          # This test is failing because Android calls the
+          # SbDrmSessionUpdateRequestFunc with SbDrmStatus::kSbDrmStatusSuccess
+          # when invalid initialization data is passed to
+          # SbDrmGenerateSessionUpdateRequest().
+          'SbDrmSessionTest.InvalidSessionUpdateRequestParams',
       ],
   }
 
diff --git a/src/starboard/android/shared/launcher.py b/src/starboard/android/shared/launcher.py
index 5f591dc..abd2787 100644
--- a/src/starboard/android/shared/launcher.py
+++ b/src/starboard/android/shared/launcher.py
@@ -279,7 +279,11 @@
     # Setup for running executable
     self._CheckCallAdb('wait-for-device')
     self._Shutdown()
-
+    # TODO: Need to wait until cobalt fully shutdown. Otherwise, it may get
+    # dirty logs from previous test, and logs like "***Application Stopped***"
+    # will cause unexpected errors.
+    # Simply wait 2s as a temperary solution.
+    time.sleep(2)
     # Clear logcat
     self._CheckCallAdb('logcat', '-c')
 
diff --git a/src/starboard/android/shared/media_codec_bridge.cc b/src/starboard/android/shared/media_codec_bridge.cc
index 188f90f..0415adc 100644
--- a/src/starboard/android/shared/media_codec_bridge.cc
+++ b/src/starboard/android/shared/media_codec_bridge.cc
@@ -159,7 +159,9 @@
     const SbMediaAudioSampleInfo& audio_sample_info,
     Handler* handler,
     jobject j_media_crypto) {
-  const char* mime = SupportedAudioCodecToMimeType(audio_codec);
+  bool is_passthrough = false;
+  const char* mime =
+      SupportedAudioCodecToMimeType(audio_codec, &is_passthrough);
   if (!mime) {
     return scoped_ptr<MediaCodecBridge>(NULL);
   }
diff --git a/src/starboard/android/shared/media_common.h b/src/starboard/android/shared/media_common.h
index 02feffb..5a8d57c 100644
--- a/src/starboard/android/shared/media_common.h
+++ b/src/starboard/android/shared/media_common.h
@@ -21,6 +21,7 @@
 #include "starboard/android/shared/jni_env_ext.h"
 #include "starboard/common/log.h"
 #include "starboard/common/mutex.h"
+#include "starboard/common/optional.h"
 #include "starboard/common/string.h"
 #include "starboard/configuration.h"
 #include "starboard/media.h"
@@ -30,8 +31,6 @@
 namespace android {
 namespace shared {
 
-const int64_t kSecondInMicroseconds = 1000 * 1000;
-
 inline bool IsWidevineL1(const char* key_system) {
   return strcmp(key_system, "com.widevine") == 0 ||
          strcmp(key_system, "com.widevine.alpha") == 0;
@@ -42,17 +41,30 @@
 }
 
 // Map a supported |SbMediaAudioCodec| into its corresponding mime type
-// string.  Returns |NULL| if |audio_codec| is not supported.
+// string.  Returns |nullptr| if |audio_codec| is not supported.
+// On return, |is_passthrough| will be set to true if the codec should be played
+// in passthrough mode, i.e. the AudioDecoder shouldn't decode the input to pcm,
+// and should rely on the audio output device to decode and play the input.
 inline const char* SupportedAudioCodecToMimeType(
-    const SbMediaAudioCodec audio_codec) {
+    const SbMediaAudioCodec audio_codec,
+    bool* is_passthrough) {
+  SB_DCHECK(is_passthrough);
+
+  *is_passthrough = false;
+
+  if (audio_codec == kSbMediaAudioCodecAc3 ||
+      audio_codec == kSbMediaAudioCodecEac3) {
+    *is_passthrough = true;
+    return "audio/raw";
+  }
   if (audio_codec == kSbMediaAudioCodecAac) {
     return "audio/mp4a-latm";
   }
-  return NULL;
+  return nullptr;
 }
 
 // Map a supported |SbMediaVideoCodec| into its corresponding mime type
-// string.  Returns |NULL| if |video_codec| is not supported.
+// string.  Returns |nullptr| if |video_codec| is not supported.
 inline const char* SupportedVideoCodecToMimeType(
     const SbMediaVideoCodec video_codec) {
   if (video_codec == kSbMediaVideoCodecVp9) {
@@ -64,39 +76,35 @@
   } else if (video_codec == kSbMediaVideoCodecAv1) {
     return "video/av01";
   }
-  return NULL;
+  return nullptr;
 }
 
-// A simple thread-safe queue for events of type |E|, that supports polling
-// based access only.
-template <typename E>
-class EventQueue {
- public:
-  E PollFront() {
-    ScopedLock lock(mutex_);
-    if (!deque_.empty()) {
-      E event = deque_.front();
-      deque_.pop_front();
-      return event;
-    }
-
-    return E();
+inline int GetAudioFormatSampleType(
+    SbMediaAudioCodingType coding_type,
+    const optional<SbMediaAudioSampleType>& sample_type =
+        optional<SbMediaAudioSampleType>()) {
+  if (coding_type == kSbMediaAudioCodingTypeAc3) {
+    SB_DCHECK(!sample_type);
+    return 5;  // Android AudioFormat.ENCODING_AC3.
+  }
+  if (coding_type == kSbMediaAudioCodingTypeDolbyDigitalPlus) {
+    SB_DCHECK(!sample_type);
+    return 6;  // Android AudioFormat.ENCODING_E_AC3.
+    // TODO: Consider using 18 (AudioFormat.ENCODING_E_AC3_JOC) when supported.
   }
 
-  void PushBack(const E& event) {
-    ScopedLock lock(mutex_);
-    deque_.push_back(event);
-  }
+  SB_DCHECK(coding_type == kSbMediaAudioCodingTypePcm);
+  SB_DCHECK(sample_type);
 
-  void Clear() {
-    ScopedLock lock(mutex_);
-    deque_.clear();
+  switch (sample_type.value()) {
+    case kSbMediaAudioSampleTypeFloat32:
+      return 4;  // Android AudioFormat.ENCODING_PCM_FLOAT.
+    case kSbMediaAudioSampleTypeInt16Deprecated:
+      return 2;  // Android AudioFormat.ENCODING_PCM_16BIT.
   }
-
- private:
-  ::starboard::Mutex mutex_;
-  std::deque<E> deque_;
-};
+  SB_NOTREACHED();
+  return 0u;
+}
 
 }  // namespace shared
 }  // namespace android
diff --git a/src/starboard/android/shared/media_decoder.h b/src/starboard/android/shared/media_decoder.h
index cb4d63f..6550cdd 100644
--- a/src/starboard/android/shared/media_decoder.h
+++ b/src/starboard/android/shared/media_decoder.h
@@ -49,6 +49,7 @@
   // This class should be implemented by the users of MediaDecoder to receive
   // various notifications.  Note that all such functions are called on the
   // decoder thread.
+  // TODO: Replace this with std::function<> based callbacks.
   class Host {
    public:
     virtual void ProcessOutputBuffer(MediaCodecBridge* media_codec_bridge,
diff --git a/src/starboard/android/shared/media_is_audio_supported.cc b/src/starboard/android/shared/media_is_audio_supported.cc
index fe8b4ec..334c7dd 100644
--- a/src/starboard/android/shared/media_is_audio_supported.cc
+++ b/src/starboard/android/shared/media_is_audio_supported.cc
@@ -30,12 +30,18 @@
 bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec,
                              const char* content_type,
                              int64_t bitrate) {
+  if (bitrate >= kSbMediaMaxAudioBitrateInBitsPerSecond) {
+    return false;
+  }
+
   // Android now uses libopus based opus decoder.
-  if (audio_codec == kSbMediaAudioCodecOpus &&
-      bitrate < kSbMediaMaxAudioBitrateInBitsPerSecond) {
+  if (audio_codec == kSbMediaAudioCodecOpus) {
     return true;
   }
-  const char* mime = SupportedAudioCodecToMimeType(audio_codec);
+
+  bool is_passthrough = false;
+  const char* mime =
+      SupportedAudioCodecToMimeType(audio_codec, &is_passthrough);
   if (!mime) {
     return false;
   }
@@ -76,8 +82,34 @@
   ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime));
   const bool must_support_tunnel_mode =
       enable_tunnel_mode_parameter_value == "true";
-  return env->CallStaticBooleanMethodOrAbort(
-             "dev/cobalt/media/MediaCodecUtil", "hasAudioDecoderFor",
-             "(Ljava/lang/String;IZ)Z", j_mime.Get(),
-             static_cast<jint>(bitrate), must_support_tunnel_mode) == JNI_TRUE;
+  auto media_codec_supported =
+      env->CallStaticBooleanMethodOrAbort(
+          "dev/cobalt/media/MediaCodecUtil", "hasAudioDecoderFor",
+          "(Ljava/lang/String;IZ)Z", j_mime.Get(), static_cast<jint>(bitrate),
+          must_support_tunnel_mode) == JNI_TRUE;
+  if (!media_codec_supported) {
+    return false;
+  }
+  if (!is_passthrough) {
+    return true;
+  }
+  SbMediaAudioCodingType coding_type;
+  switch (audio_codec) {
+    case kSbMediaAudioCodecAc3:
+      coding_type = kSbMediaAudioCodingTypeAc3;
+      break;
+    case kSbMediaAudioCodecEac3:
+      coding_type = kSbMediaAudioCodingTypeDolbyDigitalPlus;
+      break;
+    default:
+      return false;
+  }
+  int encoding =
+      ::starboard::android::shared::GetAudioFormatSampleType(coding_type);
+  ScopedLocalJavaRef<jobject> j_audio_output_manager(
+      env->CallStarboardObjectMethodOrAbort(
+          "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
+  return env->CallBooleanMethodOrAbort(j_audio_output_manager.Get(),
+                                       "hasPassthroughSupportFor", "(I)Z",
+                                       encoding) == JNI_TRUE;
 }
diff --git a/src/starboard/android/shared/media_is_supported.cc b/src/starboard/android/shared/media_is_supported.cc
index 14db015..bd1f79e 100644
--- a/src/starboard/android/shared/media_is_supported.cc
+++ b/src/starboard/android/shared/media_is_supported.cc
@@ -30,11 +30,12 @@
     return false;
   }
 
-  // Filter anything other then aac as we only support paid content on aac.
-  // TODO: Add support of Opus if we are going to support software based drm
-  // systems.
+  // We support all codecs except Opus in L1.  Use allow list to avoid
+  // accidentally introducing the support of a codec brought in in future.
   if (audio_codec != kSbMediaAudioCodecNone &&
-      audio_codec != kSbMediaAudioCodecAac) {
+      audio_codec != kSbMediaAudioCodecAac &&
+      audio_codec != kSbMediaAudioCodecAc3 &&
+      audio_codec != kSbMediaAudioCodecEac3) {
     return false;
   }
   if (!IsWidevineL1(key_system)) {
diff --git a/src/starboard/android/shared/platform_configuration/configuration.gni b/src/starboard/android/shared/platform_configuration/configuration.gni
index f0e73b2..19850b6 100644
--- a/src/starboard/android/shared/platform_configuration/configuration.gni
+++ b/src/starboard/android/shared/platform_configuration/configuration.gni
@@ -47,3 +47,5 @@
     [ "//starboard/android/shared/platform_configuration:library_config" ]
 
 install_target_path = "//starboard/android/shared/install_target.gni"
+
+sb_widevine_platform = "android"
diff --git a/src/starboard/android/shared/player_components_factory.h b/src/starboard/android/shared/player_components_factory.h
index 7caf57b..fa64270 100644
--- a/src/starboard/android/shared/player_components_factory.h
+++ b/src/starboard/android/shared/player_components_factory.h
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "starboard/android/shared/audio_decoder.h"
+#include "starboard/android/shared/audio_renderer_passthrough.h"
 #include "starboard/android/shared/audio_track_audio_sink_type.h"
 #include "starboard/android/shared/drm_system.h"
 #include "starboard/android/shared/jni_env_ext.h"
@@ -41,14 +42,13 @@
 #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_internal_impl.h"
 #include "starboard/shared/starboard/player/filter/video_renderer_sink.h"
 
 namespace starboard {
 namespace android {
 namespace shared {
 
-using starboard::shared::starboard::media::MimeType;
-
 // Tunnel mode has to be enabled explicitly by the web app via mime attributes
 // "tunnelmode", set the following variable to true to force enabling tunnel
 // mode on all playbacks.
@@ -126,6 +126,29 @@
   atomic_bool error_occurred_;
 };
 
+class PlayerComponentsPassthrough
+    : public starboard::shared::starboard::player::filter::PlayerComponents {
+ public:
+  PlayerComponentsPassthrough(
+      scoped_ptr<AudioRendererPassthrough> audio_renderer,
+      scoped_ptr<VideoRenderer> video_renderer)
+      : audio_renderer_(audio_renderer.Pass()),
+        video_renderer_(video_renderer.Pass()) {}
+
+ private:
+  // PlayerComponents methods
+  MediaTimeProvider* GetMediaTimeProvider() override {
+    return audio_renderer_.get();
+  }
+  AudioRenderer* GetAudioRenderer() override { return audio_renderer_.get(); }
+  VideoRenderer* GetVideoRenderer() override { return video_renderer_.get(); }
+
+  scoped_ptr<AudioRendererPassthrough> audio_renderer_;
+  scoped_ptr<VideoRenderer> video_renderer_;
+};
+
+// TODO: Invesigate if the implementation of member functions should be moved
+//       into .cc file.
 class PlayerComponentsFactory : public starboard::shared::starboard::player::
                                     filter::PlayerComponents::Factory {
   typedef starboard::shared::opus::OpusAudioDecoder OpusAudioDecoder;
@@ -137,6 +160,8 @@
       AudioRendererSink;
   typedef starboard::shared::starboard::player::filter::AudioRendererSinkImpl
       AudioRendererSinkImpl;
+  typedef starboard::shared::starboard::player::filter::PlayerComponents
+      PlayerComponents;
   typedef starboard::shared::starboard::player::filter::VideoDecoder
       VideoDecoderBase;
   typedef starboard::shared::starboard::player::filter::VideoRenderAlgorithm
@@ -157,6 +182,56 @@
     return (value + alignment - 1) / alignment * alignment;
   }
 
+  scoped_ptr<PlayerComponents> CreateComponents(
+      const CreationParameters& creation_parameters,
+      std::string* error_message) override {
+    SB_DCHECK(error_message);
+
+    if (creation_parameters.audio_codec() != kSbMediaAudioCodecAc3 &&
+        creation_parameters.audio_codec() != kSbMediaAudioCodecEac3) {
+      SB_LOG(INFO) << "Creating non-passthrough components.";
+      return PlayerComponents::Factory::CreateComponents(creation_parameters,
+                                                         error_message);
+    }
+
+    SB_LOG(INFO) << "Creating passthrough components.";
+    // TODO: Enable tunnel mode for passthrough
+    scoped_ptr<AudioRendererPassthrough> audio_renderer;
+    audio_renderer.reset(new AudioRendererPassthrough(
+        creation_parameters.audio_sample_info(),
+        GetExtendedDrmSystem(creation_parameters.drm_system())));
+    if (!audio_renderer->is_valid()) {
+      return scoped_ptr<PlayerComponents>();
+    }
+
+    scoped_ptr<::starboard::shared::starboard::player::filter::VideoRenderer>
+        video_renderer;
+    if (creation_parameters.video_codec() != kSbMediaVideoCodecNone) {
+      constexpr int kTunnelModeAudioSessionId = -1;
+      constexpr bool kForceSecurePipelineUnderTunnelMode = false;
+
+      scoped_ptr<VideoDecoder> video_decoder = CreateVideoDecoder(
+          creation_parameters, kTunnelModeAudioSessionId,
+          kForceSecurePipelineUnderTunnelMode, error_message);
+      if (video_decoder) {
+        using starboard::shared::starboard::player::filter::VideoRendererImpl;
+
+        auto video_render_algorithm = video_decoder->GetRenderAlgorithm();
+        auto video_renderer_sink = video_decoder->GetSink();
+        auto media_time_provider = audio_renderer.get();
+
+        video_renderer.reset(new VideoRendererImpl(
+            scoped_ptr<VideoDecoderBase>(video_decoder.Pass()),
+            media_time_provider, video_render_algorithm.Pass(),
+            video_renderer_sink));
+      } else {
+        return scoped_ptr<PlayerComponents>();
+      }
+    }
+    return scoped_ptr<PlayerComponents>(new PlayerComponentsPassthrough(
+        audio_renderer.Pass(), video_renderer.Pass()));
+  }
+
   bool CreateSubComponents(
       const CreationParameters& creation_parameters,
       scoped_ptr<AudioDecoderBase>* audio_decoder,
@@ -165,6 +240,7 @@
       scoped_ptr<VideoRenderAlgorithmBase>* video_render_algorithm,
       scoped_refptr<VideoRendererSink>* video_renderer_sink,
       std::string* error_message) override {
+    using starboard::shared::starboard::media::MimeType;
     SB_DCHECK(error_message);
 
     int tunnel_mode_audio_session_id = -1;
@@ -242,7 +318,8 @@
             return audio_decoder_impl.PassAs<AudioDecoderBase>();
           }
         } else {
-          SB_NOTREACHED();
+          SB_LOG(ERROR) << "Unsupported audio codec "
+                        << audio_sample_info.codec;
         }
         return scoped_ptr<AudioDecoderBase>();
       };
@@ -288,24 +365,16 @@
         force_secure_pipeline_under_tunnel_mode = false;
       }
 
-      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(),
-          tunnel_mode_audio_session_id, force_secure_pipeline_under_tunnel_mode,
-          error_message));
-      if (creation_parameters.video_codec() == kSbMediaVideoCodecAv1 ||
-          video_decoder_impl->is_decoder_created()) {
+      scoped_ptr<VideoDecoder> video_decoder_impl = CreateVideoDecoder(
+          creation_parameters, tunnel_mode_audio_session_id,
+          force_secure_pipeline_under_tunnel_mode, error_message);
+      if (video_decoder_impl) {
         *video_render_algorithm = video_decoder_impl->GetRenderAlgorithm();
         *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;
       }
     }
@@ -338,6 +407,28 @@
     *max_cached_frames = AlignUp(*max_cached_frames, kAudioSinkFramesAlignment);
   }
 
+  scoped_ptr<VideoDecoder> CreateVideoDecoder(
+      const CreationParameters& creation_parameters,
+      int tunnel_mode_audio_session_id,
+      bool force_secure_pipeline_under_tunnel_mode,
+      std::string* error_message) {
+    scoped_ptr<VideoDecoder> video_decoder(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(),
+        tunnel_mode_audio_session_id, force_secure_pipeline_under_tunnel_mode,
+        error_message));
+    if (creation_parameters.video_codec() == kSbMediaVideoCodecAv1 ||
+        video_decoder->is_decoder_created()) {
+      return video_decoder.Pass();
+    }
+    *error_message =
+        "Failed to create video decoder with error: " + *error_message;
+    return scoped_ptr<VideoDecoder>();
+  }
+
   bool IsTunnelModeSupported(const CreationParameters& creation_parameters,
                              bool* force_secure_pipeline_under_tunnel_mode) {
     SB_DCHECK(force_secure_pipeline_under_tunnel_mode);
diff --git a/src/starboard/android/shared/player_create.cc b/src/starboard/android/shared/player_create.cc
index 3cb64f9..f4c4083 100644
--- a/src/starboard/android/shared/player_create.cc
+++ b/src/starboard/android/shared/player_create.cc
@@ -83,6 +83,8 @@
 
   if (audio_codec != kSbMediaAudioCodecNone &&
       audio_codec != kSbMediaAudioCodecAac &&
+      audio_codec != kSbMediaAudioCodecAc3 &&
+      audio_codec != kSbMediaAudioCodecEac3 &&
       audio_codec != kSbMediaAudioCodecOpus) {
     SB_LOG(ERROR) << "Unsupported audio codec " << audio_codec;
     return kSbPlayerInvalid;
diff --git a/src/starboard/android/shared/starboard_platform.gypi b/src/starboard/android/shared/starboard_platform.gypi
index 87bad6c..8338cbc 100644
--- a/src/starboard/android/shared/starboard_platform.gypi
+++ b/src/starboard/android/shared/starboard_platform.gypi
@@ -65,7 +65,9 @@
         'atomic_public.h',
         'audio_decoder.cc',
         'audio_decoder.h',
-        'audio_renderer.h',
+        'audio_decoder_passthrough.h',
+        'audio_renderer_passthrough.cc',
+        'audio_renderer_passthrough.h',
         'audio_sink_get_max_channels.cc',
         'audio_sink_get_min_buffer_size_in_frames.cc',
         'audio_sink_min_required_frames_tester.cc',
@@ -75,6 +77,8 @@
         'audio_sink_is_audio_sample_type_supported.cc',
         'audio_track_audio_sink_type.cc',
         'audio_track_audio_sink_type.h',
+        'audio_track_bridge.cc',
+        'audio_track_bridge.h',
         'bionic/bionic_netlink.cpp',
         'bionic/bionic_netlink.h',
         'bionic/ifaddrs.cpp',
diff --git a/src/starboard/build/collect_deploy_content.py b/src/starboard/build/collect_deploy_content.py
index 7009367..3f03b9d 100755
--- a/src/starboard/build/collect_deploy_content.py
+++ b/src/starboard/build/collect_deploy_content.py
@@ -60,6 +60,19 @@
     raise RuntimeError('Content is %d levels deep (max allowed is %d): %s' %
                        (depth, max_depth, deepest_file))
 
+def _CopyTree(src_path, dst_path):
+  """Copy tree with a safeguard for windows long path (>260).
+
+  On Windows Python is facing long path limitation, for more details see
+  https://bugs.python.org/issue27730
+  """
+  if os.sep == '\\':
+    prefix = '\\\\?\\'
+    if prefix not in src_path:
+      src_path = prefix + src_path
+    if prefix not in dst_path:
+      dst_path = prefix + dst_path
+  shutil.copytree(src_path, dst_path)
 
 def main(argv):
   parser = argparse.ArgumentParser()
@@ -141,7 +154,7 @@
         logging.error(msg)
 
     if options.copy_override:
-      shutil.copytree(src_path, dst_path)
+      _CopyTree(src_path, dst_path)
     elif options.use_absolute_symlinks:
       port_symlink.MakeSymLink(
           target_path=os.path.abspath(src_path),
diff --git a/src/starboard/build/config/BUILDCONFIG.gn b/src/starboard/build/config/BUILDCONFIG.gn
index 4b455b3..31a8fd5 100644
--- a/src/starboard/build/config/BUILDCONFIG.gn
+++ b/src/starboard/build/config/BUILDCONFIG.gn
@@ -24,6 +24,8 @@
   is_starboard = true
 
   cobalt_fastbuild = getenv("IS_CI") == 1
+
+  is_internal_build = false
 }
 
 is_debug = build_type == "debug"
diff --git a/src/starboard/build/config/sabi/BUILD.gn b/src/starboard/build/config/sabi/BUILD.gn
index 8b03959..081e595 100644
--- a/src/starboard/build/config/sabi/BUILD.gn
+++ b/src/starboard/build/config/sabi/BUILD.gn
@@ -38,10 +38,15 @@
                   ],
                   "trim string")
 
-  mock_sabi_id = "\"MOCK_SABI_ID\""
+  sabi_id = exec_script("//starboard/sabi/generate_sabi_id.py",
+                        [
+                          "-f",
+                          rebase_path(sabi_path, root_build_dir),
+                        ],
+                        "trim string")
 
   defines = [
-    "SB_SABI_JSON_ID=$mock_sabi_id",
+    "SB_SABI_JSON_ID=R\"($sabi_id)\"",
     "SB_API_VERSION=$sb_api_version",
 
     "SB_IS_ARCH_${arch_uppercase}=1",
diff --git a/src/starboard/build/doc/migration_changes.md b/src/starboard/build/doc/migration_changes.md
index 6ff5712..145ed7b 100644
--- a/src/starboard/build/doc/migration_changes.md
+++ b/src/starboard/build/doc/migration_changes.md
@@ -11,6 +11,10 @@
 :---------------------------------------- | :--------------------------------------------------- | :----------
 `OS` ("starboard"/other)                  | `is_starboard` (true/false)                          | (global)
 `clang` (0/1)                             | `is_clang` (true/false)                              | (global)
+`has_input_events_filter`                 | `is_internal_build` (true/false)                     | (global)
+`has_drm_system_extension`                | `is_internal_build` (true/false)                     | (global)
+`has_cdm`                                 | `is_internal_build` (true/false)                     | (global)
+`has_private_system_properties`           | `is_internal_build` (true/false)                     | (global)
 `sb_deploy_output_dir`                    | `sb_install_output_dir`                              | `//starboard/build/config/base_configuration.gni`
 `sb_evergreen` (0/1)                      | `sb_is_evergreen` (true/false)                       | `//starboard/build/config/base_configuration.gni`
 `sb_evergreen_compatible` (0/1)           | `sb_is_evergreen_compatible` (true/false)            | `//starboard/build/config/base_configuration.gni`
diff --git a/src/starboard/common/log.h b/src/starboard/common/log.h
index 61a6ebf..9438a17 100644
--- a/src/starboard/common/log.h
+++ b/src/starboard/common/log.h
@@ -168,8 +168,10 @@
 #if SB_LOGGING_IS_OFFICIAL_BUILD || \
     (defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON))
 #define SB_DCHECK(condition) SB_EAT_STREAM_PARAMETERS
+#define SB_DCHECK_ENABLED 0
 #else
 #define SB_DCHECK(condition) SB_CHECK(condition)
+#define SB_DCHECK_ENABLED 1
 #endif
 
 #define SB_DLOG(severity) SB_DLOG_IF(severity, SB_DLOG_IS_ON(severity))
@@ -224,8 +226,10 @@
 #if SB_LOGGING_IS_OFFICIAL_BUILD
 #define SB_NOTIMPLEMENTED()
 #define SB_DCHECK(condition)
+#define SB_DCHECK_ENABLED 0
 #else
 #define SB_DCHECK(condition) SB_CHECK(condition)
+#define SB_DCHECK_ENABLED 1
 #define SB_NOTIMPLEMENTED()                              \
   do {                                                   \
     static int count = 0;                                \
diff --git a/src/starboard/doc/evergreen/cobalt_evergreen_overview.md b/src/starboard/doc/evergreen/cobalt_evergreen_overview.md
index 93bcde0..75b6940 100644
--- a/src/starboard/doc/evergreen/cobalt_evergreen_overview.md
+++ b/src/starboard/doc/evergreen/cobalt_evergreen_overview.md
@@ -111,12 +111,12 @@
 The gyp variable `sb_evergreen` is set to 1 when building `libcobalt.so`.
 
 The partner port of Starboard is built with the partner’s toolchain and is
-linked into the **`loader_app` which knows how to dynamically load
+linked into the `loader_app` which knows how to dynamically load
 `libcobalt.so`, and the `crashpad_handler` which handles crashes.
 
 ```
-cobalt/build/gyp_cobalt <partner_port_name>
-ninja -C out/<partner_port_name>_qa loader_app crashpad_handler
+$ cobalt/build/gyp_cobalt <partner_port_name>
+$ ninja -C out/<partner_port_name>_qa loader_app crashpad_handler
 ```
 
 Partners should set `sb_evergreen_compatible` to 1 in their gyp platform config.
@@ -254,7 +254,7 @@
 ## Verifying Platform Requirements
 
 In order to verify the platform requirements you should run the
-‘nplb\_evergreen\_compat\_tests’. These tests ensure that the platform is
+`nplb_evergreen_compat_tests`. These tests ensure that the platform is
 configured appropriately for Evergreen.
 
 To enable the test, set the `sb_evergreen_compatible gyp` variable to 1 in the
@@ -264,6 +264,44 @@
 There is a reference implementation available for Raspberry Pi 2 with
 instructions available [here](cobalt_evergreen_reference_port_raspi2.md).
 
+### Verifying Crashpad Uploads
+
+1. Build the `crashpad_database_util` target and deploy it onto the device.
+```
+$ cobalt/build/gyp_cobalt <partner_port_name>
+$ ninja -C out/<partner_port_name>_qa crashpad_database_util
+```
+2. Remove the existing state for crashpad as it throttles uploads to 1 per hour:
+```
+$ rm -rf <kSbSystemPathCacheDirectory>/crashpad_database/
+
+```
+3. Launch Cobalt.
+4. Trigger crash by sending `abort` signal to the `loader_app` process:
+```
+$ kill -6 <pid>
+```
+5. Verify the crash was uploaded through running `crashpad_database_util` on the device
+pointing it to the cache directory, where the crash data is stored.
+
+```
+$ crashpad_database_util -d <kSbSystemPathCacheDirectory>/crashpad_database/ --show-completed-reports --show-all-report-info
+```
+
+```
+8c3af145-30a0-43c7-a3a5-0952dea230e4:
+  Path: cobalt/cache/crashpad_database/completed/8c3af145-30a0-43c7-a3a5-0952dea230e4.dmp
+  Remote ID: c9b14b489a895093
+  Creation time: 2021-06-01 17:01:19 HDT
+  Uploaded: true
+  Last upload attempt time: 2021-06-01 17:01:19 HDT
+  Upload attempts: 1
+```
+
+In this example the minidump was successfully uploaded because we see `Uploaded: true`.
+
+Reference for [crashpad_database_util](https://chromium.googlesource.com/crashpad/crashpad/+/refs/heads/main/tools/crashpad_database_util.md)
+
 ## System Design
 
 ![Cobalt Evergreen
diff --git a/src/starboard/elf_loader/BUILD.gn b/src/starboard/elf_loader/BUILD.gn
index e150d2f..9ce393b 100644
--- a/src/starboard/elf_loader/BUILD.gn
+++ b/src/starboard/elf_loader/BUILD.gn
@@ -12,6 +12,153 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+_elf_loader_sources = [
+  "dynamic_section.cc",
+  "dynamic_section.h",
+  "elf_hash_table.cc",
+  "elf_hash_table.h",
+  "elf_header.cc",
+  "elf_header.h",
+  "elf_loader.cc",
+  "elf_loader.h",
+  "elf_loader_constants.cc",
+  "elf_loader_constants.h",
+  "exported_symbols.cc",
+  "file.h",
+  "file_impl.cc",
+  "file_impl.h",
+  "gnu_hash_table.cc",
+  "gnu_hash_table.h",
+  "lz4_file_impl.cc",
+  "lz4_file_impl.h",
+  "program_table.cc",
+  "program_table.h",
+  "relocations.cc",
+  "relocations.h",
+]
+
+config("elf_loader_config") {
+  include_dirs = [
+    "src/include",
+    "src/src",
+  ]
+}
+
+static_library("elf_loader") {
+  sources = _elf_loader_sources + [
+              "elf_loader_impl.cc",
+              "elf_loader_impl.h",
+            ]
+
+  configs += [ ":elf_loader_config" ]
+
+  deps = [
+    ":evergreen_config",
+    ":evergreen_info",
+    "//starboard",
+    "//third_party/lz4_lib:lz4",
+  ]
+}
+
+static_library("elf_loader_sys") {
+  # System loader based on dlopen/dlsym.
+  # Should be used only for debugging/troubleshooting.
+  sources = _elf_loader_sources + [
+              "elf_loader_impl.h",
+              "elf_loader_sys_impl.cc",
+              "elf_loader_sys_impl.h",
+            ]
+
+  configs += [ ":elf_loader_config" ]
+
+  deps = [
+    ":evergreen_config",
+    ":evergreen_info",
+    "//starboard",
+  ]
+
+  if (sb_is_evergreen_compatible) {
+    deps += [ "//third_party/crashpad/wrapper" ]
+  } else {
+    deps += [ "//third_party/crashpad/wrapper:wrapper_stub" ]
+  }
+}
+
+target(final_executable_type, "elf_loader_sandbox") {
+  sources = [ "sandbox.cc" ]
+  configs += [ ":elf_loader_config" ]
+
+  deps = [
+    ":elf_loader",
+    ":evergreen_info",
+    ":sabi_string",
+    "//cobalt/content/fonts:copy_font_data",
+    "//starboard",
+  ]
+}
+
+target(final_executable_type, "elf_loader_sys_sandbox") {
+  # To properly function the system loader requires the starboard
+  # symbols to be exported from the binary.
+  # To allow symbols to be exported remove the '-fvisibility=hidden' flag
+  # from your compiler_flags.gypi. For Linux this would be:
+  #   starboard/linux/shared/compiler_flags.gypi
+  # Example run:
+  # export LD_LIBRARY_PATH=.
+  # ./elf_loader_sys_sandbox --evergreen_library=app/cobalt/lib/libcobalt.so --evergreen_content=app/cobalt/content
+  sources = [ "sandbox.cc" ]
+  configs += [ ":elf_loader_config" ]
+
+  starboard_syms_path =
+      rebase_path("//starboard/starboard.syms", root_build_dir)
+  ldflags = [
+    "-Wl,--dynamic-list=$starboard_syms_path",
+    "-ldl",
+  ]
+
+  deps = [
+    ":elf_loader_sys",
+    ":evergreen_info",
+    ":sabi_string",
+    "//starboard",
+  ]
+}
+
+target(gtest_target_type, "elf_loader_test") {
+  testonly = true
+  sources = [ "//starboard/common/test_main.cc" ]
+  deps = [
+    "//starboard",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+
+  if (target_cpu == "x86" || target_cpu == "x64" || target_cpu == "arm" ||
+      target_cpu == "arm64") {
+    sources += [
+      "dynamic_section_test.cc",
+      "elf_header_test.cc",
+      "elf_loader_test.cc",
+      "lz4_file_impl_test.cc",
+      "program_table_test.cc",
+      "relocations_test.cc",
+    ]
+    deps += [
+      ":copy_elf_loader_testdata",
+      ":elf_loader",
+    ]
+  }
+}
+
+copy("copy_elf_loader_testdata") {
+  sources = [
+    "testdata/compressed.lz4",
+    "testdata/uncompressed",
+  ]
+  subdir = "starboard/elf_loader"
+  outputs = [ "$sb_static_contents_output_data_dir/test/$subdir/{{source_target_relative}}" ]
+}
+
 static_library("evergreen_info") {
   sources = [
     "evergreen_info.cc",
@@ -29,3 +176,12 @@
 
   public_deps = [ "//starboard/common" ]
 }
+
+static_library("sabi_string") {
+  sources = [
+    "sabi_string.cc",
+    "sabi_string.h",
+  ]
+
+  deps = [ "//starboard/common" ]
+}
diff --git a/src/starboard/elf_loader/exported_symbols.cc b/src/starboard/elf_loader/exported_symbols.cc
index c6b56f6..791b129 100644
--- a/src/starboard/elf_loader/exported_symbols.cc
+++ b/src/starboard/elf_loader/exported_symbols.cc
@@ -231,7 +231,7 @@
   REGISTER_SYMBOL(SbStringConcatWide);
   REGISTER_SYMBOL(SbStringCopy);
   REGISTER_SYMBOL(SbStringCopyWide);
-#endif  // SB_API_VERSION >= 13
+#endif  // SB_API_VERSION < 13
   REGISTER_SYMBOL(SbStringDuplicate);
 #if SB_API_VERSION < 13
   REGISTER_SYMBOL(SbStringFindCharacter);
diff --git a/src/starboard/evergreen/testing/linux/start_cobalt.sh b/src/starboard/evergreen/testing/linux/start_cobalt.sh
index b060bbc..70dc749 100755
--- a/src/starboard/evergreen/testing/linux/start_cobalt.sh
+++ b/src/starboard/evergreen/testing/linux/start_cobalt.sh
@@ -30,7 +30,7 @@
 
   URL="${1}"
   LOG="${2}"
-  __LOADER="${3}"
+  declare -n loader_pid_ref=$3
   ARGS="${4}"
 
   stop_cobalt
@@ -44,9 +44,9 @@
 
   log "info" " Logs will be output to '${LOG_PATH}/${LOG}'"
 
-  eval "${OUT}/loader_app --url=\"\"${URL}\"\" ${ARGS} 2>&1 | tee \"${LOG_PATH}/${LOG}\"" &
+  eval "${OUT}/loader_app --url=\"\"${URL}\"\" ${ARGS} &> >(tee \"${LOG_PATH}/${LOG}\") &"
 
-  eval $__LOADER=$!
+  loader_pid_ref=$!
 
-  log "info" " Cobalt process ID is ${__LOADER}"
+  log "info" " Cobalt process ID is ${loader_pid_ref}"
 }
diff --git a/src/starboard/linux/shared/BUILD.gn b/src/starboard/linux/shared/BUILD.gn
index c45fdac..522b781 100644
--- a/src/starboard/linux/shared/BUILD.gn
+++ b/src/starboard/linux/shared/BUILD.gn
@@ -14,10 +14,6 @@
 
 import("//starboard/shared/starboard/player/buildfiles.gni")
 
-declare_args() {
-  has_cdm = false
-}
-
 group("starboard_platform") {
   public_deps = [
     ":starboard_base_symbolize",
@@ -68,7 +64,11 @@
     "//starboard/linux/shared/decode_target_release.cc",
     "//starboard/linux/shared/media_is_audio_supported.cc",
     "//starboard/linux/shared/media_is_video_supported.cc",
+    "//starboard/linux/shared/netlink.cc",
+    "//starboard/linux/shared/netlink.h",
     "//starboard/linux/shared/player_components_factory.cc",
+    "//starboard/linux/shared/routes.cc",
+    "//starboard/linux/shared/routes.h",
     "//starboard/linux/shared/system_get_connection_type.cc",
     "//starboard/linux/shared/system_get_device_type.cc",
     "//starboard/linux/shared/system_get_extensions.cc",
@@ -386,8 +386,42 @@
 
   sources += common_player_sources
 
-  if (has_cdm) {
-    # TODO(andrewsavage)
+  configs += [
+    "//starboard/build/config:starboard_implementation",
+    "//third_party/de265_includes",
+    "//third_party/pulseaudio_includes",
+  ]
+
+  public_deps = [
+    "//starboard:starboard_headers_only",
+    "//starboard/shared/starboard/player/filter:filter_based_player_sources",
+  ]
+  deps = [ "//third_party/boringssl:crypto" ]
+
+  if (is_internal_build) {
+    sources += [
+      "//starboard/linux/shared/drm_create_system.cc",
+      "//starboard/linux/shared/oemcrypto_engine_device_properties_linux.cc",
+      "//starboard/shared/starboard/drm/drm_close_session.cc",
+      "//starboard/shared/starboard/drm/drm_destroy_system.cc",
+      "//starboard/shared/starboard/drm/drm_generate_session_update_request.cc",
+      "//starboard/shared/starboard/drm/drm_get_metrics.cc",
+      "//starboard/shared/starboard/drm/drm_is_server_certificate_updatable.cc",
+      "//starboard/shared/starboard/drm/drm_system_internal.h",
+      "//starboard/shared/starboard/drm/drm_update_server_certificate.cc",
+      "//starboard/shared/starboard/drm/drm_update_session.cc",
+      "//starboard/shared/widevine/drm_system_widevine.cc",
+      "//starboard/shared/widevine/drm_system_widevine.h",
+      "//starboard/shared/widevine/media_is_supported.cc",
+      "//starboard/shared/widevine/widevine_storage.cc",
+      "//starboard/shared/widevine/widevine_storage.h",
+      "//starboard/shared/widevine/widevine_timer.cc",
+      "//starboard/shared/widevine/widevine_timer.h",
+    ]
+    deps += [
+      "//starboard/shared/widevine:oemcrypto",
+      "//third_party/ce_cdm/cdm:widevine_ce_cdm_static",
+    ]
   } else {
     sources += [
       "//starboard/shared/stub/drm_close_session.cc",
@@ -401,15 +435,4 @@
       "//starboard/shared/stub/media_is_supported.cc",
     ]
   }
-
-  public_deps = [
-    "//starboard:starboard_headers_only",
-    "//starboard/shared/starboard/player/filter:filter_based_player_sources",
-  ]
-  deps = [ "//third_party/boringssl:crypto" ]
-  configs += [
-    "//starboard/build/config:starboard_implementation",
-    "//third_party/de265_includes",
-    "//third_party/pulseaudio_includes",
-  ]
 }
diff --git a/src/starboard/linux/shared/netlink.cc b/src/starboard/linux/shared/netlink.cc
new file mode 100644
index 0000000..69378a2
--- /dev/null
+++ b/src/starboard/linux/shared/netlink.cc
@@ -0,0 +1,169 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/linux/shared/netlink.h"
+
+#include <linux/netlink.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "starboard/common/log.h"
+#include "starboard/types.h"
+
+// Omit namespace linux due to symbol name conflict.
+namespace starboard {
+namespace shared {
+
+namespace {
+void LogLastError(const char* msg) {
+  const int kErrorMessageBufferSize = 256;
+  char msgbuf[kErrorMessageBufferSize];
+  SbSystemError error_code = SbSystemGetLastError();
+  if (SbSystemGetErrorString(error_code, msgbuf, kErrorMessageBufferSize) > 0) {
+    SB_LOG(ERROR) << msg << ": " << msgbuf;
+  }
+}
+
+// Buffer size for receiving netlink messages. Should be large enough to hold
+// the full routing table dump.
+const size_t kNetlinkMessageBufferSize = 32768;
+}  // namespace
+
+// Open the netlink socket.
+bool NetLink::Open(int type, int protocol) {
+  //  https://man7.org/linux/man-pages/man7/netlink.7.html
+  //  https://datatracker.ietf.org/doc/html/rfc3549#section-2
+  socket_fd_ = socket(AF_NETLINK, type, protocol);
+  if (socket_fd_ == -1) {
+    LogLastError("Netlink Request socket could not be opened");
+    return false;
+  }
+
+#if !defined(MSG_NOSIGNAL) && defined(SO_NOSIGPIPE)
+  {
+    // Use SO_NOSIGPIPE to mute SIGPIPE on darwin systems.
+    int optval_set = 1;
+    setsockopt(socket_fd_, SOL_SOCKET, SO_NOSIGPIPE,
+               reinterpret_cast<void*>(&optval_set), sizeof(int));
+  }
+#endif
+
+#if defined(NETLINK_DUMP_STRICT_CHK)
+  {
+    int optval_set = 1;
+    setsockopt(socket_fd_, SOL_NETLINK, NETLINK_DUMP_STRICT_CHK,
+               reinterpret_cast<void*>(&optval_set), sizeof(int));
+  }
+#endif
+  return true;
+}
+
+bool NetLink::IsOpened() {
+  return socket_fd_ != -1;
+}
+
+void NetLink::Close() {
+  if (socket_fd_ != -1) {
+    shutdown(socket_fd_, SHUT_RDWR);
+    close(socket_fd_);
+    socket_fd_ = -1;
+  }
+  request_sent_ = false;
+}
+
+// Send a netlink request.
+bool NetLink::Request(uint16_t type,
+                      uint16_t message_flags,
+                      void* payload,
+                      int payload_length) {
+  if (!IsOpened())
+    return false;
+  std::vector<char> netlink_buffer(NLMSG_LENGTH(payload_length));
+  auto header = reinterpret_cast<struct nlmsghdr*>(netlink_buffer.data());
+  memcpy(NLMSG_DATA(header), payload, payload_length);
+
+  header->nlmsg_len = netlink_buffer.size();
+  header->nlmsg_type = type;
+  header->nlmsg_flags = NLM_F_REQUEST | message_flags;
+  header->nlmsg_seq = ++request_sequence_;
+  request_sent_ =
+      send(socket_fd_, netlink_buffer.data(), netlink_buffer.size(), 0) != -1;
+  return request_sent_;
+}
+
+// Return the next netlink message. Returns nullptr if there are no more
+// messages.
+struct nlmsghdr* NetLink::GetNextMessage() {
+  // If we already have a message header, return the next header in the
+  // message if there is one.
+  if (header_) {
+    if ((header_->nlmsg_type == NLMSG_DONE) ||
+        ((header_->nlmsg_flags & NLM_F_MULTI) == 0)) {
+      // This is not a multipart response, or the last header.
+      message_length_ = 0;
+      header_ = nullptr;
+    } else {
+      // Get the next header.
+      header_ = NLMSG_NEXT(header_, message_length_);
+      if ((NLMSG_OK(header_, message_length_) == 0) ||
+          (header_->nlmsg_type == NLMSG_ERROR)) {
+        header_ = nullptr;
+      }
+    }
+  }
+
+  if (header_)
+    return header_;
+
+  // Receive the next message with netlink headers.
+  if (!request_sent_)
+    return nullptr;
+  if (message_buffer_.size() == 0) {
+    message_buffer_.resize(kNetlinkMessageBufferSize);
+  }
+  message_length_ =
+      recv(socket_fd_, message_buffer_.data(), message_buffer_.size(), 0);
+  if (message_length_ == -1) {
+    Close();
+    return nullptr;
+  }
+
+  header_ = reinterpret_cast<struct nlmsghdr*>(message_buffer_.data());
+  if ((NLMSG_OK(header_, message_length_) == 0) ||
+      (header_->nlmsg_type == NLMSG_ERROR)) {
+    Close();
+    return nullptr;
+  }
+
+  if ((header_->nlmsg_type == NLMSG_DONE) ||
+      ((header_->nlmsg_flags & NLM_F_MULTI) == 0) ||
+      (header_->nlmsg_pid == 0)) {
+    request_sent_ = false;
+  }
+
+  if ((header_->nlmsg_seq != request_sequence_)) {
+    SB_LOG(WARNING) << "Unexpected different sequence " << header_->nlmsg_seq
+                    << "!= " << request_sequence_ << ".";
+    Close();
+    return nullptr;
+  }
+
+  return header_;
+}
+
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/linux/shared/netlink.h b/src/starboard/linux/shared/netlink.h
new file mode 100644
index 0000000..2b7f2ca
--- /dev/null
+++ b/src/starboard/linux/shared/netlink.h
@@ -0,0 +1,70 @@
+// Copyright 2021 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_LINUX_SHARED_NETLINK_H_
+#define STARBOARD_LINUX_SHARED_NETLINK_H_
+
+#include <linux/netlink.h>
+
+#include <vector>
+
+#include "starboard/types.h"
+
+// Omit namespace linux due to symbol name conflict.
+namespace starboard {
+namespace shared {
+
+// Support class for netlink sockets.
+//  https://man7.org/linux/man-pages/man7/netlink.7.html
+
+// Helper class for the netlink socket API.
+class NetLink {
+ public:
+  NetLink() {}
+  virtual ~NetLink() { Close(); }
+
+  // Open the netlink socket.
+  bool Open(int type, int protocol);
+
+  bool IsOpened();
+
+  void Close();
+
+  // Send a netlink request.
+  bool Request(uint16_t type,
+               uint16_t message_flags,
+               void* payload = nullptr,
+               int payload_length = 0);
+
+  // Return the next netlink message. Returns nullptr if there are no more
+  // messages.
+  struct nlmsghdr* GetNextMessage();
+
+ private:
+  NetLink(const NetLink&) = delete;
+  NetLink& operator=(NetLink&&) = delete;
+  void operator=(const NetLink&) = delete;
+
+  int socket_fd_ = -1;
+  int request_sequence_ = 0;
+  bool request_sent_ = false;
+  std::vector<char> message_buffer_;
+  int message_length_ = 0;
+  struct nlmsghdr* header_ = nullptr;
+};
+
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_LINUX_SHARED_NETLINK_H_
diff --git a/src/starboard/linux/shared/platform_configuration/configuration.gni b/src/starboard/linux/shared/platform_configuration/configuration.gni
index 437b035..3618b00 100644
--- a/src/starboard/linux/shared/platform_configuration/configuration.gni
+++ b/src/starboard/linux/shared/platform_configuration/configuration.gni
@@ -26,3 +26,5 @@
 
 speed_config_path = "//starboard/linux/shared/platform_configuration:speed"
 size_config_path = "//starboard/linux/shared/platform_configuration:size"
+
+sb_widevine_platform = "linux"
diff --git a/src/starboard/linux/shared/routes.cc b/src/starboard/linux/shared/routes.cc
new file mode 100644
index 0000000..0c3c613
--- /dev/null
+++ b/src/starboard/linux/shared/routes.cc
@@ -0,0 +1,153 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/linux/shared/routes.h"
+
+#include <arpa/inet.h>
+#include <linux/rtnetlink.h>
+#include <net/if.h>
+// This include has to be loaded after net/if.h.
+#include <linux/wireless.h>  // NOLINT(build/include_order)
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "starboard/common/log.h"
+#include "starboard/linux/shared/netlink.h"
+
+// Omit namespace linux due to symbol name conflict.
+namespace starboard {
+namespace shared {
+
+namespace {
+void LogLastError(const char* msg) {
+  const int kErrorMessageBufferSize = 256;
+  char msgbuf[kErrorMessageBufferSize];
+  SbSystemError error_code = SbSystemGetLastError();
+  if (SbSystemGetErrorString(error_code, msgbuf, kErrorMessageBufferSize) > 0) {
+    SB_LOG(ERROR) << msg << ": " << msgbuf;
+  }
+}
+}  // namespace
+
+// Return true if the route has the default destination address.
+// static
+bool Routes::IsDefaultRoute(const Route& route) {
+  switch (route.family) {
+    case AF_INET6:
+      return memcmp(&route.dst_addr6, &in6addr_any, sizeof(in6addr_any)) == 0;
+    case AF_INET:
+      return route.dst_addr == INADDR_ANY;
+    default:
+      return false;
+  }
+}
+
+// Return true if the route uses a wireless interface.
+// static
+bool Routes::IsWirelessInterface(const Route& route) {
+  int socket_fd = -1;
+
+  if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+    LogLastError("socket");
+    return false;
+  }
+
+  struct iwreq request;
+  memset(&request, 0, sizeof(request));
+  strncpy(request.ifr_name, route.name, IFNAMSIZ);
+  if (ioctl(socket_fd, SIOCGIWNAME, &request) != -1) {
+    close(socket_fd);
+    return true;
+  }
+
+  close(socket_fd);
+  return false;
+}
+
+// Open the netlink socket for routing table information.
+bool Routes::Open() {
+  //  https://datatracker.ietf.org/doc/html/rfc3549#section-3.1
+
+  //  https://man7.org/linux/man-pages/man7/rtnetlink.7.html
+  return NetLink::Open(SOCK_DGRAM, NETLINK_ROUTE);
+}
+
+// Request a dump of the routing tables from the netlink socket.
+bool Routes::RequestDump() {
+  if (!IsOpened())
+    Open();
+  struct rtmsg header;
+  memset(&header, 0, sizeof(header));
+  // header.rtm_family=AF_INET6;
+  header.rtm_table = RT_TABLE_UNSPEC;
+  header.rtm_protocol = RTPROT_UNSPEC;
+  header.rtm_scope = RT_SCOPE_UNIVERSE;
+  header.rtm_type = RTN_UNSPEC;
+  return Request(RTM_GETROUTE, NLM_F_DUMP, &header, sizeof(header));
+}
+
+// static
+void Routes::GetRouteFromNetlinkMessage(struct nlmsghdr* message,
+                                        Route& route) {
+  auto route_message = reinterpret_cast<struct rtmsg*>(NLMSG_DATA(message));
+  memset(&route, 0, sizeof(route));
+  route.family = route_message->rtm_family;
+  int payload = RTM_PAYLOAD(message);
+  for (struct rtattr* attribute = RTM_RTA(route_message);
+       RTA_OK(attribute, payload); attribute = RTA_NEXT(attribute, payload)) {
+    void* data = RTA_DATA(attribute);
+    const int& value(*reinterpret_cast<int*>(data));
+    switch (attribute->rta_type) {
+      case RTA_OIF:
+        if (!if_indextoname(value, route.name)) {
+          continue;
+        }
+        break;
+      case RTA_DST:
+        if (route.family == AF_INET6) {
+          memcpy(&route.dst_addr6, data, sizeof(route.dst_addr6));
+        } else {
+          SB_DCHECK(route.family == AF_INET);
+          memcpy(&route.dst_addr, data, sizeof(route.dst_addr));
+        }
+        break;
+      case RTA_PRIORITY:
+        route.priority = value;
+    }
+  }
+}
+
+// Return the next route from the netlink interface message. Returns nullptr
+// when no more routes are available.
+Routes::Route* Routes::GetNextRoute() {
+  struct nlmsghdr* message = nullptr;
+  while ((message = GetNextMessage())) {
+    auto route = reinterpret_cast<struct rtmsg*>(NLMSG_DATA(message));
+    if (route->rtm_table != RT_TABLE_MAIN) {
+      continue;
+    }
+    if (route->rtm_family == AF_INET || route->rtm_family == AF_INET6) {
+      GetRouteFromNetlinkMessage(message, last_route_);
+      return &last_route_;
+    }
+  }
+  return nullptr;
+}
+
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/linux/shared/routes.h b/src/starboard/linux/shared/routes.h
new file mode 100644
index 0000000..4f7073f
--- /dev/null
+++ b/src/starboard/linux/shared/routes.h
@@ -0,0 +1,71 @@
+// Copyright 2021 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_LINUX_SHARED_ROUTES_H_
+#define STARBOARD_LINUX_SHARED_ROUTES_H_
+
+#include <arpa/inet.h>
+#include <net/if.h>
+
+#include "starboard/common/log.h"
+#include "starboard/linux/shared/netlink.h"
+#include "starboard/system.h"
+
+// Omit namespace linux due to symbol name conflict.
+namespace starboard {
+namespace shared {
+
+// Helper class for examining device routes from the netlink socket API.
+class Routes : public NetLink {
+ public:
+  struct Route {
+    sa_family_t family;
+    union {
+      in_addr_t dst_addr;
+      struct in6_addr dst_addr6;
+    };
+    int priority;
+    char name[IF_NAMESIZE];
+  };
+
+  // Return true if the route has the default destination address.
+  static bool IsDefaultRoute(const Route& route);
+
+  // Return true if the route uses a wireless interface.
+  static bool IsWirelessInterface(const Route& route);
+
+  Routes() {}
+  ~Routes() {}
+
+  // Open the netlink socket for routing table information.
+  bool Open();
+
+  // Request a dump of the routing tables from the netlink socket.
+  bool RequestDump();
+
+  // Return the next route from the netlink interface message. Returns nullptr
+  // when no more routes are available.
+  Route* GetNextRoute();
+
+ private:
+  static void GetRouteFromNetlinkMessage(struct nlmsghdr* message,
+                                         Route& route);
+
+  Route last_route_;
+};
+
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_LINUX_SHARED_ROUTES_H_
diff --git a/src/starboard/linux/shared/starboard_platform.gypi b/src/starboard/linux/shared/starboard_platform.gypi
index 76f7cc1..5387624 100644
--- a/src/starboard/linux/shared/starboard_platform.gypi
+++ b/src/starboard/linux/shared/starboard_platform.gypi
@@ -35,7 +35,11 @@
       '<(DEPTH)/starboard/linux/shared/decode_target_release.cc',
       '<(DEPTH)/starboard/linux/shared/media_is_audio_supported.cc',
       '<(DEPTH)/starboard/linux/shared/media_is_video_supported.cc',
+      '<(DEPTH)/starboard/linux/shared/netlink.cc',
+      '<(DEPTH)/starboard/linux/shared/netlink.h',
       '<(DEPTH)/starboard/linux/shared/player_components_factory.cc',
+      '<(DEPTH)/starboard/linux/shared/routes.cc',
+      '<(DEPTH)/starboard/linux/shared/routes.h',
       '<(DEPTH)/starboard/linux/shared/system_get_connection_type.cc',
       '<(DEPTH)/starboard/linux/shared/system_get_device_type.cc',
       '<(DEPTH)/starboard/linux/shared/system_get_extensions.cc',
diff --git a/src/starboard/linux/shared/system_get_connection_type.cc b/src/starboard/linux/shared/system_get_connection_type.cc
index 92a96a8..7795c20 100644
--- a/src/starboard/linux/shared/system_get_connection_type.cc
+++ b/src/starboard/linux/shared/system_get_connection_type.cc
@@ -14,9 +14,31 @@
 
 #include "starboard/system.h"
 
-#include "starboard/common/log.h"
+#include "starboard/linux/shared/routes.h"
+
+using starboard::shared::Routes;
 
 SbSystemConnectionType SbSystemGetConnectionType() {
-  SB_NOTIMPLEMENTED();
+  Routes routes;
+
+  if (!routes.RequestDump()) {
+    return kSbSystemConnectionTypeUnknown;
+  }
+
+  // Find the top priority route and return if the corresponding route uses a
+  // wireless interface.
+  int priority = INT_MAX;
+  bool is_wireless = false;
+  while (auto route = routes.GetNextRoute()) {
+    if (Routes::IsDefaultRoute(*route)) {
+      if (route->priority < priority) {
+        priority = route->priority;
+        is_wireless = Routes::IsWirelessInterface(*route);
+      }
+    }
+  }
+  if (priority != INT_MAX)
+    return is_wireless ? kSbSystemConnectionTypeWireless
+                       : kSbSystemConnectionTypeWired;
   return kSbSystemConnectionTypeUnknown;
 }
diff --git a/src/starboard/linux/x64x11/shared/BUILD.gn b/src/starboard/linux/x64x11/shared/BUILD.gn
index c433086..3238904a 100644
--- a/src/starboard/linux/x64x11/shared/BUILD.gn
+++ b/src/starboard/linux/x64x11/shared/BUILD.gn
@@ -12,10 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-declare_args() {
-  has_private_system_properties = false
-}
-
 group("starboard_platform") {
   public_deps = [
     ":starboard_platform_sources",
@@ -47,7 +43,7 @@
     "//starboard/shared/x11/window_internal.cc",
   ]
 
-  if (has_private_system_properties) {
+  if (is_internal_build) {
     sources += [ "//starboard/keyboxes/linux/system_properties.cc" ]
   } else {
     sources += [ "//starboard/linux/x64x11/public_system_properties.cc" ]
diff --git a/src/starboard/linux/x64x11/system_get_property_impl.cc b/src/starboard/linux/x64x11/system_get_property_impl.cc
index 37de482..f08fa2c 100644
--- a/src/starboard/linux/x64x11/system_get_property_impl.cc
+++ b/src/starboard/linux/x64x11/system_get_property_impl.cc
@@ -14,19 +14,40 @@
 
 #include "starboard/linux/x64x11/system_get_property_impl.h"
 
-#include <netdb.h>
-#include <linux/if.h>  // NOLINT(build/include_alpha)
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-
 #include "starboard/common/log.h"
 #include "starboard/common/string.h"
 #include "starboard/linux/x64x11/system_properties.h"
 
 namespace {
 
-const char* kFriendlyName = "Linux Desktop";
-const char* kPlatformName = "X11; Linux x86_64";
+const char kBrandName[] = "BrandName";
+const char kChipsetModelNumber[] = "ChipsetModelNumber";
+const char kFirmwareVersion[] = "FirmwareVersion";
+const char kFriendlyName[] = "Linux Desktop";
+const char kModelName[] = "ModelName";
+const char kPlatformName[] = "X11; Linux x86_64";
+
+#if SB_API_VERSION >= 12
+const char kSystemIntegratorName[] = "SystemIntegratorName";
+#else
+const char kOriginalDesignManufacturerName[] = "OriginalDesignManufacturerName";
+#endif
+
+#if SB_API_VERSION >= 13
+const char kModelYear[] = "2022";
+#elif SB_API_VERSION >= 12
+const char kModelYear[] = "2021";
+#elif SB_API_VERSION >= 11
+const char kModelYear[] = "2020";
+#elif SB_API_VERSION >= 10
+const char kModelYear[] = "2019";
+#elif SB_API_VERSION >= 6
+const char kModelYear[] = "2018";
+#elif SB_API_VERSION >= 2
+const char kModelYear[] = "2017";
+#else
+const char kModelYear[] = "2016";
+#endif  // SB_API_VERSION
 
 }  // namespace
 
@@ -52,32 +73,38 @@
 
   switch (property_id) {
     case kSbSystemPropertyBrandName:
-    case kSbSystemPropertyChipsetModelNumber:
-    case kSbSystemPropertyFirmwareVersion:
-    case kSbSystemPropertyModelName:
-    case kSbSystemPropertyModelYear:
-#if SB_API_VERSION >= 12
-    case kSbSystemPropertySystemIntegratorName:
-#else
-    case kSbSystemPropertyOriginalDesignManufacturerName:
-#endif
-    case kSbSystemPropertySpeechApiKey:
-      return false;
-    case kSbSystemPropertyUserAgentAuxField:
-      return false;
-    case kSbSystemPropertyFriendlyName:
-      return CopyStringAndTestIfSuccess(out_value, value_length, kFriendlyName);
-
-    case kSbSystemPropertyPlatformName:
-      return CopyStringAndTestIfSuccess(out_value, value_length, kPlatformName);
-
+      return CopyStringAndTestIfSuccess(out_value, value_length, kBrandName);
     case kSbSystemPropertyCertificationScope:
       if (kCertificationScope[0] == '\0')
         return false;
       return CopyStringAndTestIfSuccess(out_value, value_length,
                                         kCertificationScope);
-
-
+    case kSbSystemPropertyChipsetModelNumber:
+      return CopyStringAndTestIfSuccess(out_value, value_length,
+                                        kChipsetModelNumber);
+    case kSbSystemPropertyFirmwareVersion:
+      return CopyStringAndTestIfSuccess(out_value, value_length,
+                                        kFirmwareVersion);
+    case kSbSystemPropertyFriendlyName:
+      return CopyStringAndTestIfSuccess(out_value, value_length, kFriendlyName);
+    case kSbSystemPropertyModelName:
+      return CopyStringAndTestIfSuccess(out_value, value_length, kModelName);
+    case kSbSystemPropertyModelYear:
+      return CopyStringAndTestIfSuccess(out_value, value_length, kModelYear);
+    case kSbSystemPropertyPlatformName:
+      return CopyStringAndTestIfSuccess(out_value, value_length, kPlatformName);
+#if SB_API_VERSION >= 12
+    case kSbSystemPropertySystemIntegratorName:
+      return CopyStringAndTestIfSuccess(out_value, value_length,
+                                        kSystemIntegratorName);
+#else
+    case kSbSystemPropertyOriginalDesignManufacturerName:
+      return CopyStringAndTestIfSuccess(out_value, value_length,
+                                        kOriginalDesignManufacturerName);
+#endif
+    case kSbSystemPropertySpeechApiKey:
+    case kSbSystemPropertyUserAgentAuxField:
+      return false;
     default:
       SB_DLOG(WARNING) << __FUNCTION__
                        << ": Unrecognized property: " << property_id;
diff --git a/src/starboard/loader_app/BUILD.gn b/src/starboard/loader_app/BUILD.gn
index dba9360..481ccc8 100644
--- a/src/starboard/loader_app/BUILD.gn
+++ b/src/starboard/loader_app/BUILD.gn
@@ -12,6 +12,204 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+_common_loader_app_sources = [
+  "loader_app.cc",
+  "loader_app_switches.cc",
+  "loader_app_switches.h",
+  "system_get_extension_shim.cc",
+  "system_get_extension_shim.h",
+]
+
+group("common_loader_app_dependencies") {
+  public_deps = [
+    ":app_key",
+    ":installation_manager",
+    ":slot_management",
+    "//starboard",
+    "//starboard/elf_loader:sabi_string",
+  ]
+}
+
+target(final_executable_type, "loader_app") {
+  if (target_cpu == "x86" || target_cpu == "x64" || target_cpu == "arm" ||
+      target_cpu == "arm64") {
+    sources = _common_loader_app_sources
+    deps = [
+      ":common_loader_app_dependencies",
+      "//cobalt/content/fonts:copy_font_data",
+      "//starboard/elf_loader",
+    ]
+  }
+}
+
+target(final_executable_type, "loader_app_sys") {
+  if (target_cpu == "x86" || target_cpu == "x64" || target_cpu == "arm" ||
+      target_cpu == "arm64") {
+    sources = _common_loader_app_sources
+
+    starboard_syms_path =
+        rebase_path("//starboard/starboard.syms", root_build_dir)
+    ldflags = [
+      "-Wl,--dynamic-list=$starboard_syms_path",
+      "-ldl",
+    ]
+    deps = [
+      ":common_loader_app_dependencies",
+      "//cobalt/content/fonts:copy_font_data",
+      "//starboard/elf_loader:elf_loader_sys",
+    ]
+  }
+}
+
+static_library("app_key_files") {
+  sources = [
+    "app_key_files.cc",
+    "app_key_files.h",
+  ]
+  deps = [ "//starboard" ]
+}
+
+target(gtest_target_type, "app_key_files_test") {
+  testonly = true
+  sources = [
+    "//starboard/common/test_main.cc",
+    "app_key_files_test.cc",
+  ]
+  deps = [
+    ":app_key_files",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
+
+static_library("app_key") {
+  sources = [
+    "app_key.cc",
+    "app_key.h",
+    "app_key_internal.cc",
+    "app_key_internal.h",
+  ]
+  deps = [
+    "//starboard",
+    "//third_party/modp_b64",
+  ]
+}
+
+target(gtest_target_type, "app_key_test") {
+  testonly = true
+  sources = [
+    "//starboard/common/test_main.cc",
+    "app_key_test.cc",
+  ]
+  deps = [
+    ":app_key",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
+
+static_library("drain_file") {
+  sources = [
+    "drain_file.cc",
+    "drain_file.h",
+  ]
+  deps = [ "//starboard/common" ]
+}
+
+target(gtest_target_type, "drain_file_test") {
+  testonly = true
+  sources = [
+    "//starboard/common/test_main.cc",
+    "drain_file_helper.cc",
+    "drain_file_helper.h",
+    "drain_file_test.cc",
+  ]
+  deps = [
+    ":drain_file",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
+
+static_library("installation_store_proto") {
+  sources = [
+    "installation_store.pb.cc",
+    "installation_store.pb.h",
+  ]
+  public_deps = [ "//third_party/protobuf:protobuf_lite" ]
+}
+
+static_library("installation_manager") {
+  sources = [
+    "installation_manager.cc",
+    "installation_manager.h",
+  ]
+
+  include_dirs = [
+    # Get protobuf headers from the chromium tree.
+    "//third_party/protobuf/src",
+  ]
+
+  deps = [
+    ":installation_store_proto",
+    "//starboard",
+  ]
+
+  if (sb_evergreen_compatible_enable_lite) {
+    deps += [ ":pending_restart" ]
+  }
+}
+
+target(gtest_target_type, "installation_manager_test") {
+  testonly = true
+  sources = [
+    "//starboard/common/test_main.cc",
+    "installation_manager_test.cc",
+  ]
+  if (sb_evergreen_compatible_enable_lite) {
+    sources += [ "pending_restart_test.cc" ]
+  }
+  deps = [
+    ":installation_manager",
+    ":installation_store_proto",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
+
+static_library("slot_management") {
+  sources = [
+    "slot_management.cc",
+    "slot_management.h",
+  ]
+  deps = [
+    ":app_key_files",
+    ":drain_file",
+    ":installation_manager",
+    "//starboard",
+    "//starboard/elf_loader",
+    "//starboard/elf_loader:sabi_string",
+  ]
+}
+
+target(gtest_target_type, "slot_management_test") {
+  testonly = true
+  sources = [
+    "//starboard/common/test_main.cc",
+    "slot_management_test.cc",
+  ]
+  deps = [
+    ":app_key_files",
+    ":drain_file",
+    ":installation_manager",
+    ":installation_store_proto",
+    ":slot_management",
+    "//starboard/elf_loader:sabi_string",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
+
 static_library("pending_restart") {
   sources = [
     "pending_restart.cc",
diff --git a/src/starboard/nplb/BUILD.gn b/src/starboard/nplb/BUILD.gn
index 52e56bd..a53df6a 100644
--- a/src/starboard/nplb/BUILD.gn
+++ b/src/starboard/nplb/BUILD.gn
@@ -103,7 +103,6 @@
     "double_floor_test.cc",
     "double_is_finite_test.cc",
     "double_is_nan_test.cc",
-    "drm_create_system_test.cc",
     "drm_get_metrics_test.cc",
     "drm_helpers.cc",
     "drm_helpers.h",
@@ -142,7 +141,6 @@
     # TODO: Separate functions tested by media buffer test into multiple
     # files.
     "media_buffer_test.cc",
-    "media_can_play_mime_and_key_system_test.cc",
     "media_configuration_test.cc",
     "memory_align_to_page_size_test.cc",
     "memory_allocate_aligned_test.cc",
@@ -241,6 +239,7 @@
     "string_scan_test.cc",
     "system_binary_search_test.cc",
     "system_clear_last_error_test.cc",
+    "system_get_connection_type_test.cc",
     "system_get_error_string_test.cc",
     "system_get_extension_test.cc",
     "system_get_last_error_test.cc",
@@ -295,6 +294,13 @@
     "window_get_size_test.cc",
   ]
 
+  if (is_internal_build) {
+    sources += [
+      "drm_create_system_test.cc",
+      "media_can_play_mime_and_key_system_test.cc",
+    ]
+  }
+
   deps = [ "//starboard/nplb/testdata/file_tests:nplb_file_tests_data" ]
 
   public_deps = [
diff --git a/src/starboard/nplb/drm_helpers.h b/src/starboard/nplb/drm_helpers.h
index e6b0972..be29b72 100644
--- a/src/starboard/nplb/drm_helpers.h
+++ b/src/starboard/nplb/drm_helpers.h
@@ -62,9 +62,7 @@
 SbDrmSystem CreateDummyDrmSystem(const char* key_system);
 
 static const char* kKeySystems[] = {
-    "com.widevine",
-    "com.widevine.alpha",
-    "com.youtube.playready",
+    "com.widevine", "com.widevine.alpha", "com.youtube.playready",
     "com.youtube.fairplay",
 };
 
@@ -72,6 +70,138 @@
     "cenc", "cbcs", "cbcs-1-9",
 };
 
+static constexpr uint8_t kWidevineCertificate[] = {
+    0x0a, 0xc1, 0x02, 0x08, 0x03, 0x12, 0x10, 0x17, 0x05, 0xb9, 0x17, 0xcc,
+    0x12, 0x04, 0x86, 0x8b, 0x06, 0x33, 0x3a, 0x2f, 0x77, 0x2a, 0x8c, 0x18,
+    0x82, 0xb4, 0x82, 0x92, 0x05, 0x22, 0x8e, 0x02, 0x30, 0x82, 0x01, 0x0a,
+    0x02, 0x82, 0x01, 0x01, 0x00, 0x99, 0xed, 0x5b, 0x3b, 0x32, 0x7d, 0xab,
+    0x5e, 0x24, 0xef, 0xc3, 0xb6, 0x2a, 0x95, 0xb5, 0x98, 0x52, 0x0a, 0xd5,
+    0xbc, 0xcb, 0x37, 0x50, 0x3e, 0x06, 0x45, 0xb8, 0x14, 0xd8, 0x76, 0xb8,
+    0xdf, 0x40, 0x51, 0x04, 0x41, 0xad, 0x8c, 0xe3, 0xad, 0xb1, 0x1b, 0xb8,
+    0x8c, 0x4e, 0x72, 0x5a, 0x5e, 0x4a, 0x9e, 0x07, 0x95, 0x29, 0x1d, 0x58,
+    0x58, 0x40, 0x23, 0xa7, 0xe1, 0xaf, 0x0e, 0x38, 0xa9, 0x12, 0x79, 0x39,
+    0x30, 0x08, 0x61, 0x0b, 0x6f, 0x15, 0x8c, 0x87, 0x8c, 0x7e, 0x21, 0xbf,
+    0xfb, 0xfe, 0xea, 0x77, 0xe1, 0x01, 0x9e, 0x1e, 0x57, 0x81, 0xe8, 0xa4,
+    0x5f, 0x46, 0x26, 0x3d, 0x14, 0xe6, 0x0e, 0x80, 0x58, 0xa8, 0x60, 0x7a,
+    0xdc, 0xe0, 0x4f, 0xac, 0x84, 0x57, 0xb1, 0x37, 0xa8, 0xd6, 0x7c, 0xcd,
+    0xeb, 0x33, 0x70, 0x5d, 0x98, 0x3a, 0x21, 0xfb, 0x4e, 0xec, 0xbd, 0x4a,
+    0x10, 0xca, 0x47, 0x49, 0x0c, 0xa4, 0x7e, 0xaa, 0x5d, 0x43, 0x82, 0x18,
+    0xdd, 0xba, 0xf1, 0xca, 0xde, 0x33, 0x92, 0xf1, 0x3d, 0x6f, 0xfb, 0x64,
+    0x42, 0xfd, 0x31, 0xe1, 0xbf, 0x40, 0xb0, 0xc6, 0x04, 0xd1, 0xc4, 0xba,
+    0x4c, 0x95, 0x20, 0xa4, 0xbf, 0x97, 0xee, 0xbd, 0x60, 0x92, 0x9a, 0xfc,
+    0xee, 0xf5, 0x5b, 0xba, 0xf5, 0x64, 0xe2, 0xd0, 0xe7, 0x6c, 0xd7, 0xc5,
+    0x5c, 0x73, 0xa0, 0x82, 0xb9, 0x96, 0x12, 0x0b, 0x83, 0x59, 0xed, 0xce,
+    0x24, 0x70, 0x70, 0x82, 0x68, 0x0d, 0x6f, 0x67, 0xc6, 0xd8, 0x2c, 0x4a,
+    0xc5, 0xf3, 0x13, 0x44, 0x90, 0xa7, 0x4e, 0xec, 0x37, 0xaf, 0x4b, 0x2f,
+    0x01, 0x0c, 0x59, 0xe8, 0x28, 0x43, 0xe2, 0x58, 0x2f, 0x0b, 0x6b, 0x9f,
+    0x5d, 0xb0, 0xfc, 0x5e, 0x6e, 0xdf, 0x64, 0xfb, 0xd3, 0x08, 0xb4, 0x71,
+    0x1b, 0xcf, 0x12, 0x50, 0x01, 0x9c, 0x9f, 0x5a, 0x09, 0x02, 0x03, 0x01,
+    0x00, 0x01, 0x3a, 0x14, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2e,
+    0x77, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
+    0x12, 0x80, 0x03, 0xae, 0x34, 0x73, 0x14, 0xb5, 0xa8, 0x35, 0x29, 0x7f,
+    0x27, 0x13, 0x88, 0xfb, 0x7b, 0xb8, 0xcb, 0x52, 0x77, 0xd2, 0x49, 0x82,
+    0x3c, 0xdd, 0xd1, 0xda, 0x30, 0xb9, 0x33, 0x39, 0x51, 0x1e, 0xb3, 0xcc,
+    0xbd, 0xea, 0x04, 0xb9, 0x44, 0xb9, 0x27, 0xc1, 0x21, 0x34, 0x6e, 0xfd,
+    0xbd, 0xea, 0xc9, 0xd4, 0x13, 0x91, 0x7e, 0x6e, 0xc1, 0x76, 0xa1, 0x04,
+    0x38, 0x46, 0x0a, 0x50, 0x3b, 0xc1, 0x95, 0x2b, 0x9b, 0xa4, 0xe4, 0xce,
+    0x0f, 0xc4, 0xbf, 0xc2, 0x0a, 0x98, 0x08, 0xaa, 0xaf, 0x4b, 0xfc, 0xd1,
+    0x9c, 0x1d, 0xcf, 0xcd, 0xf5, 0x74, 0xcc, 0xac, 0x28, 0xd1, 0xb4, 0x10,
+    0x41, 0x6c, 0xf9, 0xde, 0x88, 0x04, 0x30, 0x1c, 0xbd, 0xb3, 0x34, 0xca,
+    0xfc, 0xd0, 0xd4, 0x09, 0x78, 0x42, 0x3a, 0x64, 0x2e, 0x54, 0x61, 0x3d,
+    0xf0, 0xaf, 0xcf, 0x96, 0xca, 0x4a, 0x92, 0x49, 0xd8, 0x55, 0xe4, 0x2b,
+    0x3a, 0x70, 0x3e, 0xf1, 0x76, 0x7f, 0x6a, 0x9b, 0xd3, 0x6d, 0x6b, 0xf8,
+    0x2b, 0xe7, 0x6b, 0xbf, 0x0c, 0xba, 0x4f, 0xde, 0x59, 0xd2, 0xab, 0xcc,
+    0x76, 0xfe, 0xb6, 0x42, 0x47, 0xb8, 0x5c, 0x43, 0x1f, 0xbc, 0xa5, 0x22,
+    0x66, 0xb6, 0x19, 0xfc, 0x36, 0x97, 0x95, 0x43, 0xfc, 0xa9, 0xcb, 0xbd,
+    0xbb, 0xfa, 0xfa, 0x0e, 0x1a, 0x55, 0xe7, 0x55, 0xa3, 0xc7, 0xbc, 0xe6,
+    0x55, 0xf9, 0x64, 0x6f, 0x58, 0x2a, 0xb9, 0xcf, 0x70, 0xaa, 0x08, 0xb9,
+    0x79, 0xf8, 0x67, 0xf6, 0x3a, 0x0b, 0x2b, 0x7f, 0xdb, 0x36, 0x2c, 0x5b,
+    0xc4, 0xec, 0xd5, 0x55, 0xd8, 0x5b, 0xca, 0xa9, 0xc5, 0x93, 0xc3, 0x83,
+    0xc8, 0x57, 0xd4, 0x9d, 0xaa, 0xb7, 0x7e, 0x40, 0xb7, 0x85, 0x1d, 0xdf,
+    0xd2, 0x49, 0x98, 0x80, 0x8e, 0x35, 0xb2, 0x58, 0xe7, 0x5d, 0x78, 0xea,
+    0xc0, 0xca, 0x16, 0xf7, 0x04, 0x73, 0x04, 0xc2, 0x0d, 0x93, 0xed, 0xe4,
+    0xe8, 0xff, 0x1c, 0x6f, 0x17, 0xe6, 0x24, 0x3e, 0x3f, 0x3d, 0xa8, 0xfc,
+    0x17, 0x09, 0x87, 0x0e, 0xc4, 0x5f, 0xba, 0x82, 0x3a, 0x26, 0x3f, 0x0c,
+    0xef, 0xa1, 0xf7, 0x09, 0x3b, 0x19, 0x09, 0x92, 0x83, 0x26, 0x33, 0x37,
+    0x05, 0x04, 0x3a, 0x29, 0xbd, 0xa6, 0xf9, 0xb4, 0x34, 0x2c, 0xc8, 0xdf,
+    0x54, 0x3c, 0xb1, 0xa1, 0x18, 0x2f, 0x7c, 0x5f, 0xff, 0x33, 0xf1, 0x04,
+    0x90, 0xfa, 0xca, 0x5b, 0x25, 0x36, 0x0b, 0x76, 0x01, 0x5e, 0x9c, 0x5a,
+    0x06, 0xab, 0x8e, 0xe0, 0x2f, 0x00, 0xd2, 0xe8, 0xd5, 0x98, 0x61, 0x04,
+    0xaa, 0xcc, 0x4d, 0xd4, 0x75, 0xfd, 0x96, 0xee, 0x9c, 0xe4, 0xe3, 0x26,
+    0xf2, 0x1b, 0x83, 0xc7, 0x05, 0x85, 0x77, 0xb3, 0x87, 0x32, 0xcd, 0xda,
+    0xbc, 0x6a, 0x6b, 0xed, 0x13, 0xfb, 0x0d, 0x49, 0xd3, 0x8a, 0x45, 0xeb,
+    0x87, 0xa5, 0xf4};
+
+static constexpr uint8_t kCencInitData[] = {
+    0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68, 0x00, 0x00, 0x00, 0x00,
+    0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc,
+    0xd5, 0x1d, 0x21, 0xed, 0x00, 0x00, 0x00, 0x14, 0x08, 0x01, 0x12, 0x10,
+    0x31, 0xfd, 0x5b, 0x66, 0x19, 0xfc, 0x5e, 0xad, 0x86, 0x7c, 0xff, 0xb5,
+    0x84, 0xed, 0x4c, 0x19, 0x00, 0x00, 0x02, 0xf4, 0x70, 0x73, 0x73, 0x68,
+    0x00, 0x00, 0x00, 0x00, 0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86,
+    0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95, 0x00, 0x00, 0x02, 0xd4,
+    0xd4, 0x02, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0xca, 0x02, 0x3c, 0x00,
+    0x57, 0x00, 0x52, 0x00, 0x4d, 0x00, 0x48, 0x00, 0x45, 0x00, 0x41, 0x00,
+    0x44, 0x00, 0x45, 0x00, 0x52, 0x00, 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00,
+    0x6c, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x68, 0x00,
+    0x74, 0x00, 0x74, 0x00, 0x70, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x2f, 0x00,
+    0x73, 0x00, 0x63, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x61, 0x00,
+    0x73, 0x00, 0x2e, 0x00, 0x6d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00,
+    0x6f, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00,
+    0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2f, 0x00, 0x44, 0x00, 0x52, 0x00,
+    0x4d, 0x00, 0x2f, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x37, 0x00,
+    0x2f, 0x00, 0x30, 0x00, 0x33, 0x00, 0x2f, 0x00, 0x50, 0x00, 0x6c, 0x00,
+    0x61, 0x00, 0x79, 0x00, 0x52, 0x00, 0x65, 0x00, 0x61, 0x00, 0x64, 0x00,
+    0x79, 0x00, 0x48, 0x00, 0x65, 0x00, 0x61, 0x00, 0x64, 0x00, 0x65, 0x00,
+    0x72, 0x00, 0x22, 0x00, 0x20, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00,
+    0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x3d, 0x00, 0x22, 0x00,
+    0x34, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x2e, 0x00,
+    0x30, 0x00, 0x22, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x44, 0x00, 0x41, 0x00,
+    0x54, 0x00, 0x41, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x50, 0x00, 0x52, 0x00,
+    0x4f, 0x00, 0x54, 0x00, 0x45, 0x00, 0x43, 0x00, 0x54, 0x00, 0x49, 0x00,
+    0x4e, 0x00, 0x46, 0x00, 0x4f, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x4b, 0x00,
+    0x45, 0x00, 0x59, 0x00, 0x4c, 0x00, 0x45, 0x00, 0x4e, 0x00, 0x3e, 0x00,
+    0x31, 0x00, 0x36, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x4b, 0x00, 0x45, 0x00,
+    0x59, 0x00, 0x4c, 0x00, 0x45, 0x00, 0x4e, 0x00, 0x3e, 0x00, 0x3c, 0x00,
+    0x41, 0x00, 0x4c, 0x00, 0x47, 0x00, 0x49, 0x00, 0x44, 0x00, 0x3e, 0x00,
+    0x41, 0x00, 0x45, 0x00, 0x53, 0x00, 0x43, 0x00, 0x54, 0x00, 0x52, 0x00,
+    0x3c, 0x00, 0x2f, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x47, 0x00, 0x49, 0x00,
+    0x44, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x50, 0x00, 0x52, 0x00,
+    0x4f, 0x00, 0x54, 0x00, 0x45, 0x00, 0x43, 0x00, 0x54, 0x00, 0x49, 0x00,
+    0x4e, 0x00, 0x46, 0x00, 0x4f, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x4b, 0x00,
+    0x49, 0x00, 0x44, 0x00, 0x3e, 0x00, 0x5a, 0x00, 0x6c, 0x00, 0x76, 0x00,
+    0x39, 0x00, 0x4d, 0x00, 0x66, 0x00, 0x77, 0x00, 0x5a, 0x00, 0x72, 0x00,
+    0x56, 0x00, 0x36, 0x00, 0x47, 0x00, 0x66, 0x00, 0x50, 0x00, 0x2b, 0x00,
+    0x31, 0x00, 0x68, 0x00, 0x4f, 0x00, 0x31, 0x00, 0x4d, 0x00, 0x47, 0x00,
+    0x51, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x4b, 0x00,
+    0x49, 0x00, 0x44, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x43, 0x00, 0x48, 0x00,
+    0x45, 0x00, 0x43, 0x00, 0x4b, 0x00, 0x53, 0x00, 0x55, 0x00, 0x4d, 0x00,
+    0x3e, 0x00, 0x4a, 0x00, 0x58, 0x00, 0x46, 0x00, 0x36, 0x00, 0x57, 0x00,
+    0x38, 0x00, 0x41, 0x00, 0x64, 0x00, 0x51, 0x00, 0x2b, 0x00, 0x49, 0x00,
+    0x3d, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x43, 0x00, 0x48, 0x00, 0x45, 0x00,
+    0x43, 0x00, 0x4b, 0x00, 0x53, 0x00, 0x55, 0x00, 0x4d, 0x00, 0x3e, 0x00,
+    0x3c, 0x00, 0x4c, 0x00, 0x41, 0x00, 0x5f, 0x00, 0x55, 0x00, 0x52, 0x00,
+    0x4c, 0x00, 0x3e, 0x00, 0x68, 0x00, 0x74, 0x00, 0x74, 0x00, 0x70, 0x00,
+    0x73, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x77, 0x00, 0x77, 0x00,
+    0x77, 0x00, 0x2e, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00,
+    0x75, 0x00, 0x62, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00,
+    0x6d, 0x00, 0x2f, 0x00, 0x61, 0x00, 0x70, 0x00, 0x69, 0x00, 0x2f, 0x00,
+    0x64, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x2f, 0x00, 0x70, 0x00, 0x6c, 0x00,
+    0x61, 0x00, 0x79, 0x00, 0x72, 0x00, 0x65, 0x00, 0x61, 0x00, 0x64, 0x00,
+    0x79, 0x00, 0x3f, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00,
+    0x63, 0x00, 0x65, 0x00, 0x3d, 0x00, 0x59, 0x00, 0x4f, 0x00, 0x55, 0x00,
+    0x54, 0x00, 0x55, 0x00, 0x42, 0x00, 0x45, 0x00, 0x26, 0x00, 0x61, 0x00,
+    0x6d, 0x00, 0x70, 0x00, 0x3b, 0x00, 0x76, 0x00, 0x69, 0x00, 0x64, 0x00,
+    0x65, 0x00, 0x6f, 0x00, 0x5f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x3d, 0x00,
+    0x39, 0x00, 0x33, 0x00, 0x39, 0x00, 0x35, 0x00, 0x62, 0x00, 0x34, 0x00,
+    0x36, 0x00, 0x64, 0x00, 0x37, 0x00, 0x64, 0x00, 0x63, 0x00, 0x64, 0x00,
+    0x37, 0x00, 0x38, 0x00, 0x38, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x2f, 0x00,
+    0x4c, 0x00, 0x41, 0x00, 0x5f, 0x00, 0x55, 0x00, 0x52, 0x00, 0x4c, 0x00,
+    0x3e, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x44, 0x00, 0x41, 0x00, 0x54, 0x00,
+    0x41, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x57, 0x00, 0x52, 0x00,
+    0x4d, 0x00, 0x48, 0x00, 0x45, 0x00, 0x41, 0x00, 0x44, 0x00, 0x45, 0x00,
+    0x52, 0x00, 0x3e, 0x00, 0x00};
+
 }  // namespace nplb
 }  // namespace starboard
 
diff --git a/src/starboard/nplb/drm_session_test.cc b/src/starboard/nplb/drm_session_test.cc
new file mode 100644
index 0000000..0e911a3
--- /dev/null
+++ b/src/starboard/nplb/drm_session_test.cc
@@ -0,0 +1,413 @@
+// Copyright 2021 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 <string>
+#include <vector>
+
+#include "starboard/drm.h"
+
+#include "starboard/common/log.h"
+#include "starboard/common/queue.h"
+#include "starboard/nplb/drm_helpers.h"
+#include "starboard/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+namespace {
+
+constexpr SbTimeMonotonic kDefaultWaitForCallbackTimeout = kSbTimeSecond;
+constexpr char kWidevineKeySystem[] = "com.widevine.alpha";
+constexpr char kCencInitDataType[] = "cenc";
+constexpr int kInitialTicket = kSbDrmTicketInvalid + 1;
+
+class SbDrmSessionTest : public ::testing::Test {
+ protected:
+  struct SessionUpdateRequestGeneratedCallbackEvent {
+    SessionUpdateRequestGeneratedCallbackEvent() {}
+
+    SessionUpdateRequestGeneratedCallbackEvent(
+        SbDrmSystem drm_system,
+        const int ticket,
+        const SbDrmStatus status,
+        const SbDrmSessionRequestType type,
+        const std::string& error_message,
+        const std::string& session_id,
+        const std::vector<uint8_t>& content)
+        : drm_system(drm_system),
+          ticket(ticket),
+          drm_status(status),
+          type(type),
+          error_message(error_message),
+          session_id(session_id),
+          content(content),
+          is_valid(true) {}
+
+    SbDrmSystem drm_system = kSbDrmSystemInvalid;
+    int ticket = kSbDrmTicketInvalid;
+    SbDrmStatus drm_status = kSbDrmStatusUnknownError;
+    SbDrmSessionRequestType type = kSbDrmSessionRequestTypeLicenseRequest;
+    std::string error_message;
+    std::string session_id;
+    std::vector<uint8_t> content;
+    bool is_valid = false;
+  };
+
+  struct ServerCertificateUpdatedCallbackEvent {
+    ServerCertificateUpdatedCallbackEvent() {}
+
+    ServerCertificateUpdatedCallbackEvent(SbDrmSystem drm_system,
+                                          const int ticket,
+                                          const SbDrmStatus status,
+                                          const std::string& error_message)
+        : drm_system(drm_system),
+          ticket(ticket),
+          drm_status(status),
+          error_message(error_message),
+          is_valid(true) {}
+
+    SbDrmSystem drm_system = kSbDrmSystemInvalid;
+    int ticket = kSbDrmTicketInvalid;
+    SbDrmStatus drm_status = kSbDrmStatusUnknownError;
+    std::string error_message;
+    bool is_valid = false;
+  };
+
+  struct SessionClosedCallbackEvent {
+    SessionClosedCallbackEvent() {}
+
+    SessionClosedCallbackEvent(SbDrmSystem drm_system,
+                               const std::string& session_id)
+        : drm_system(drm_system), session_id(session_id), is_valid(true) {}
+
+    SbDrmSystem drm_system = kSbDrmSystemInvalid;
+    std::string session_id;
+    bool is_valid = false;
+  };
+
+  SbDrmSessionTest() {}
+
+  void SetUp() override;
+  void TearDown() override;
+
+  // Checks callback parameters to determine if their respective SbDrm*
+  // functions executed successfully.
+  void CheckSessionUpdateRequestGeneratedCallback(
+      const SessionUpdateRequestGeneratedCallbackEvent& event,
+      const int ticket) const;
+  void CheckServerCertificateUpdatedCallback(
+      const ServerCertificateUpdatedCallbackEvent& event,
+      const int ticket) const;
+  void CheckSessionClosedCallback(const SessionClosedCallbackEvent& event,
+                                  const std::string& session_id) const;
+
+  void OnSessionUpdateRequestGenerated(SbDrmSystem drm_system,
+                                       const int ticket,
+                                       const SbDrmStatus status,
+                                       const SbDrmSessionRequestType type,
+                                       const std::string& error_message,
+                                       const std::string& session_id,
+                                       const std::vector<uint8_t>& content);
+  void OnServerCertificateUpdated(SbDrmSystem drm_system,
+                                  const int ticket,
+                                  const SbDrmStatus status,
+                                  const std::string& error_message);
+  void OnSessionClosed(SbDrmSystem drm_system, const std::string& session_id);
+
+  static void OnSessionUpdateRequestGeneratedFunc(SbDrmSystem drm_system,
+                                                  void* context,
+                                                  int ticket,
+                                                  SbDrmStatus status,
+                                                  SbDrmSessionRequestType type,
+                                                  const char* error_message,
+                                                  const void* session_id,
+                                                  int session_id_size,
+                                                  const void* content,
+                                                  int content_size,
+                                                  const char* url);
+  static void OnServerCertificateUpdatedFunc(SbDrmSystem drm_system,
+                                             void* context,
+                                             int ticket,
+                                             SbDrmStatus status,
+                                             const char* error_message);
+  static void OnSessionClosedFunc(SbDrmSystem drm_system,
+                                  void* context,
+                                  const void* session_id,
+                                  int session_id_size);
+
+  // Queue containing callback events for OnSessionUpdateRequestGeneratedFunc().
+  Queue<SessionUpdateRequestGeneratedCallbackEvent>
+      session_update_request_callback_event_queue_;
+  // Queue containing callback events for OnServerCertificateUpdatedFunc().
+  Queue<ServerCertificateUpdatedCallbackEvent>
+      server_certificate_updated_callback_event_queue_;
+  // Queue containing callback events for OnSessionClosedFunc().
+  Queue<SessionClosedCallbackEvent> session_closed_callback_event_queue_;
+
+  SbDrmSystem drm_system_ = kSbDrmSystemInvalid;
+};
+
+void SbDrmSessionTest::SetUp() {
+  drm_system_ = SbDrmCreateSystem(
+      kWidevineKeySystem, this, OnSessionUpdateRequestGeneratedFunc,
+      DummySessionUpdatedFunc, DummySessionKeyStatusesChangedFunc,
+      OnServerCertificateUpdatedFunc, OnSessionClosedFunc);
+}
+
+void SbDrmSessionTest::TearDown() {
+  SbDrmDestroySystem(drm_system_);
+}
+
+void SbDrmSessionTest::CheckSessionUpdateRequestGeneratedCallback(
+    const SessionUpdateRequestGeneratedCallbackEvent& event,
+    const int ticket) const {
+  ASSERT_TRUE(event.is_valid);
+  ASSERT_EQ(event.drm_system, drm_system_);
+  ASSERT_EQ(event.ticket, ticket);
+  ASSERT_EQ(event.drm_status, kSbDrmStatusSuccess);
+  ASSERT_TRUE(event.error_message.empty());
+  ASSERT_FALSE(event.session_id.empty());
+  ASSERT_FALSE(event.content.empty());
+}
+
+void SbDrmSessionTest::CheckServerCertificateUpdatedCallback(
+    const ServerCertificateUpdatedCallbackEvent& event,
+    const int ticket) const {
+  ASSERT_TRUE(event.is_valid);
+  ASSERT_EQ(event.drm_system, drm_system_);
+  ASSERT_EQ(event.ticket, ticket);
+  ASSERT_EQ(event.drm_status, kSbDrmStatusSuccess);
+  ASSERT_TRUE(event.error_message.empty());
+}
+
+void SbDrmSessionTest::CheckSessionClosedCallback(
+    const SessionClosedCallbackEvent& event,
+    const std::string& session_id) const {
+  ASSERT_TRUE(event.is_valid);
+  ASSERT_EQ(event.drm_system, drm_system_);
+  ASSERT_EQ(event.session_id, session_id);
+}
+
+void SbDrmSessionTest::OnSessionUpdateRequestGenerated(
+    SbDrmSystem drm_system,
+    const int ticket,
+    const SbDrmStatus status,
+    const SbDrmSessionRequestType type,
+    const std::string& error_message,
+    const std::string& session_id,
+    const std::vector<uint8_t>& content) {
+  session_update_request_callback_event_queue_.Put(
+      SessionUpdateRequestGeneratedCallbackEvent(drm_system, ticket, status,
+                                                 type, error_message,
+                                                 session_id, content));
+}
+
+void SbDrmSessionTest::OnServerCertificateUpdated(
+    SbDrmSystem drm_system,
+    const int ticket,
+    const SbDrmStatus status,
+    const std::string& error_message) {
+  server_certificate_updated_callback_event_queue_.Put(
+      ServerCertificateUpdatedCallbackEvent(drm_system, ticket, status,
+                                            error_message));
+}
+
+void SbDrmSessionTest::OnSessionClosed(SbDrmSystem drm_system,
+                                       const std::string& session_id) {
+  session_closed_callback_event_queue_.Put(
+      SessionClosedCallbackEvent(drm_system, session_id));
+}
+
+// static.
+void SbDrmSessionTest::OnSessionUpdateRequestGeneratedFunc(
+    SbDrmSystem drm_system,
+    void* context,
+    int ticket,
+    SbDrmStatus status,
+    SbDrmSessionRequestType type,
+    const char* error_message,
+    const void* session_id,
+    int session_id_size,
+    const void* content,
+    int content_size,
+    const char* url) {
+  SB_DCHECK(context);
+  static_cast<SbDrmSessionTest*>(context)->OnSessionUpdateRequestGenerated(
+      drm_system, ticket, status, type,
+      error_message ? std::string(error_message) : "",
+      session_id
+          ? std::string(static_cast<const char*>(session_id),
+                        static_cast<const char*>(session_id) + session_id_size)
+          : "",
+      content ? std::vector<uint8_t>(
+                    static_cast<const uint8_t*>(content),
+                    static_cast<const uint8_t*>(content) + content_size)
+              : std::vector<uint8_t>());
+}
+
+// static.
+void SbDrmSessionTest::OnServerCertificateUpdatedFunc(
+    SbDrmSystem drm_system,
+    void* context,
+    int ticket,
+    SbDrmStatus status,
+    const char* error_message) {
+  SB_DCHECK(context);
+  static_cast<SbDrmSessionTest*>(context)->OnServerCertificateUpdated(
+      drm_system, ticket, status,
+      error_message ? std::string(error_message) : "");
+}
+
+// static.
+void SbDrmSessionTest::OnSessionClosedFunc(SbDrmSystem drm_system,
+                                           void* context,
+                                           const void* session_id,
+                                           int session_id_size) {
+  SB_DCHECK(context);
+  static_cast<SbDrmSessionTest*>(context)->OnSessionClosed(
+      drm_system,
+      session_id
+          ? std::string(static_cast<const char*>(session_id),
+                        static_cast<const char*>(session_id) + session_id_size)
+          : "");
+}
+
+TEST_F(SbDrmSessionTest, SunnyDay) {
+  if (!SbDrmSystemIsValid(drm_system_)) {
+    SB_LOG(INFO) << "Skipping test, DRM system Widevine is not supported on "
+                    "this platform.";
+    return;
+  }
+
+  if (SbDrmIsServerCertificateUpdatable(drm_system_)) {
+    SbDrmUpdateServerCertificate(drm_system_, kInitialTicket,
+                                 kWidevineCertificate,
+                                 SB_ARRAY_SIZE_INT(kWidevineCertificate));
+    ASSERT_NO_FATAL_FAILURE(CheckServerCertificateUpdatedCallback(
+        server_certificate_updated_callback_event_queue_.GetTimed(
+            kDefaultWaitForCallbackTimeout),
+        kInitialTicket));
+  }
+
+  SbDrmGenerateSessionUpdateRequest(drm_system_, kInitialTicket,
+                                    kCencInitDataType, kCencInitData,
+                                    SB_ARRAY_SIZE_INT(kCencInitData));
+  auto session_update_request_generated_event =
+      session_update_request_callback_event_queue_.GetTimed(
+          kDefaultWaitForCallbackTimeout);
+  ASSERT_NO_FATAL_FAILURE(CheckSessionUpdateRequestGeneratedCallback(
+      session_update_request_generated_event, kInitialTicket));
+
+  SbDrmCloseSession(drm_system_,
+                    session_update_request_generated_event.session_id.c_str(),
+                    session_update_request_generated_event.session_id.size());
+  ASSERT_NO_FATAL_FAILURE(CheckSessionClosedCallback(
+      session_closed_callback_event_queue_.GetTimed(
+          kDefaultWaitForCallbackTimeout),
+      session_update_request_generated_event.session_id));
+}
+
+TEST_F(SbDrmSessionTest, CloseDrmSessionBeforeUpdateSession) {
+  if (!SbDrmSystemIsValid(drm_system_)) {
+    SB_LOG(INFO) << "Skipping test, DRM system Widevine is not supported on "
+                    "this platform.";
+    return;
+  }
+
+  SbDrmGenerateSessionUpdateRequest(drm_system_, kInitialTicket,
+                                    kCencInitDataType, kCencInitData,
+                                    SB_ARRAY_SIZE_INT(kCencInitData));
+  auto session_update_request_generated_event =
+      session_update_request_callback_event_queue_.GetTimed(
+          kDefaultWaitForCallbackTimeout);
+  ASSERT_NO_FATAL_FAILURE(CheckSessionUpdateRequestGeneratedCallback(
+      session_update_request_generated_event, kInitialTicket));
+
+  // Some implementations may send a server certificate request with a fake
+  // session ID before the underlying CDM session is established. This test
+  // ensures that the fake session ID can be used to close the session, and that
+  // the session closed callback is still called.
+  SbDrmCloseSession(drm_system_,
+                    session_update_request_generated_event.session_id.c_str(),
+                    session_update_request_generated_event.session_id.size());
+  ASSERT_NO_FATAL_FAILURE(CheckSessionClosedCallback(
+      session_closed_callback_event_queue_.GetTimed(
+          kDefaultWaitForCallbackTimeout),
+      session_update_request_generated_event.session_id));
+}
+
+TEST_F(SbDrmSessionTest, InvalidSessionUpdateRequestParams) {
+  if (!SbDrmSystemIsValid(drm_system_)) {
+    SB_LOG(INFO) << "Skipping test, DRM system Widevine is not supported on "
+                    "this platform.";
+    return;
+  }
+
+  SbDrmGenerateSessionUpdateRequest(drm_system_, kSbDrmTicketInvalid,
+                                    kCencInitDataType, kCencInitData,
+                                    SB_ARRAY_SIZE_INT(kCencInitData));
+  auto session_update_request_generated_event =
+      session_update_request_callback_event_queue_.GetTimed(
+          kDefaultWaitForCallbackTimeout);
+  // Check that the session update request callback is not called when the
+  // ticket is invalid.
+  ASSERT_FALSE(session_update_request_generated_event.is_valid);
+
+  SbDrmGenerateSessionUpdateRequest(kSbDrmSystemInvalid, kInitialTicket,
+                                    kCencInitDataType, kCencInitData,
+                                    SB_ARRAY_SIZE_INT(kCencInitData));
+  session_update_request_generated_event =
+      session_update_request_callback_event_queue_.GetTimed(
+          kDefaultWaitForCallbackTimeout);
+  // Check that the session update request callback is not called when the
+  // DRM system is invalid.
+  ASSERT_FALSE(session_update_request_generated_event.is_valid);
+
+  constexpr int ticket = kInitialTicket + 1;
+
+  if (SbDrmIsServerCertificateUpdatable(drm_system_)) {
+    SbDrmUpdateServerCertificate(drm_system_, ticket, kWidevineCertificate,
+                                 SB_ARRAY_SIZE_INT(kWidevineCertificate));
+    ASSERT_NO_FATAL_FAILURE(CheckServerCertificateUpdatedCallback(
+        server_certificate_updated_callback_event_queue_.GetTimed(
+            kDefaultWaitForCallbackTimeout),
+        ticket));
+  }
+
+  // Check that the session update request callback is still called when the
+  // CENC initialization data is invalid.
+  constexpr char kInvalidCencInitData[] = "invalidcencinitializationdata";
+  SbDrmGenerateSessionUpdateRequest(
+      drm_system_, ticket, kCencInitDataType, kInvalidCencInitData,
+      SB_ARRAY_SIZE_INT(kInvalidCencInitData) - 1);
+  session_update_request_generated_event =
+      session_update_request_callback_event_queue_.GetTimed(
+          kDefaultWaitForCallbackTimeout);
+  ASSERT_TRUE(session_update_request_generated_event.is_valid);
+  ASSERT_EQ(session_update_request_generated_event.ticket, ticket);
+  if (session_update_request_generated_event.type ==
+      kSbDrmSessionRequestTypeIndividualizationRequest) {
+    SB_DLOG(ERROR) << "The session update request generated callback type is "
+                      "|SbDrmSessionRequestType::"
+                      "kSbDrmSessionRequestTypeIndividualizationRequest|. "
+                      "The CDM is unprovisioned.";
+  } else {
+    ASSERT_NE(session_update_request_generated_event.drm_status,
+              kSbDrmStatusSuccess);
+  }
+}
+
+}  // namespace
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/nplb/nplb.gyp b/src/starboard/nplb/nplb.gyp
index 5d44daa..b0f9f91 100644
--- a/src/starboard/nplb/nplb.gyp
+++ b/src/starboard/nplb/nplb.gyp
@@ -122,6 +122,7 @@
         'drm_helpers.cc',
         'drm_helpers.h',
         'drm_is_server_certificate_updatable_test.cc',
+        'drm_session_test.cc',
         'drm_update_server_certificate_test.cc',
         'egl_test.cc',
         'extern_c_test.cc',
@@ -255,6 +256,7 @@
         'system_binary_search_test.cc',
         'system_clear_last_error_test.cc',
         'system_get_error_string_test.cc',
+        'system_get_connection_type_test.cc',
         'system_get_extension_test.cc',
         'system_get_last_error_test.cc',
         'system_get_locale_id_test.cc',
diff --git a/src/starboard/nplb/system_get_connection_type_test.cc b/src/starboard/nplb/system_get_connection_type_test.cc
new file mode 100644
index 0000000..4c21b30
--- /dev/null
+++ b/src/starboard/nplb/system_get_connection_type_test.cc
@@ -0,0 +1,44 @@
+// Copyright 2021 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.
+
+// Here we are not trying to do anything fancy, just to really sanity check that
+// this is hooked up to something.
+
+#include "starboard/common/log.h"
+#include "starboard/common/string.h"
+#include "starboard/system.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+namespace {
+
+TEST(SbSystemGetConnectionTypeTest, SunnyDay) {
+  SbSystemConnectionType type = SbSystemGetConnectionType();
+}
+
+TEST(SbSystemGetConnectionTypeTest, ConnectionIsKnown) {
+  SbSystemConnectionType type = SbSystemGetConnectionType();
+  EXPECT_NE(type, kSbSystemConnectionTypeUnknown);
+}
+
+TEST(SbSystemGetConnectionTypeTest, ConnectionIsWiredOrWireless) {
+  SbSystemConnectionType type = SbSystemGetConnectionType();
+  EXPECT_TRUE(type == kSbSystemConnectionTypeWired ||
+              type == kSbSystemConnectionTypeWireless);
+}
+
+}  // namespace
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/raspi/shared/starboard_platform.gypi b/src/starboard/raspi/shared/starboard_platform.gypi
index 0cdabb0..46c8a8f 100644
--- a/src/starboard/raspi/shared/starboard_platform.gypi
+++ b/src/starboard/raspi/shared/starboard_platform.gypi
@@ -44,6 +44,10 @@
         '<(DEPTH)/starboard/linux/shared/atomic_public.h',
         '<(DEPTH)/starboard/linux/shared/configuration_constants.cc',
         '<(DEPTH)/starboard/linux/shared/configuration_public.h',
+        '<(DEPTH)/starboard/linux/shared/netlink.cc',
+        '<(DEPTH)/starboard/linux/shared/netlink.h',
+        '<(DEPTH)/starboard/linux/shared/routes.cc',
+        '<(DEPTH)/starboard/linux/shared/routes.h',
         '<(DEPTH)/starboard/linux/shared/system_get_connection_type.cc',
         '<(DEPTH)/starboard/linux/shared/system_get_path.cc',
         '<(DEPTH)/starboard/linux/shared/system_has_capability.cc',
diff --git a/src/starboard/sabi/generate_sabi_id.py b/src/starboard/sabi/generate_sabi_id.py
index 2be9c29..497e409 100644
--- a/src/starboard/sabi/generate_sabi_id.py
+++ b/src/starboard/sabi/generate_sabi_id.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Copyright 2020 The Cobalt Authors. All Rights Reserved.
 #
@@ -15,14 +15,14 @@
 # limitations under the License.
 """Generates the ID of a specified Starboard ABI JSON file."""
 
-import _env  # pylint: disable=unused-import
-
 import argparse
 import json
 import os
 import sys
 
-from starboard.sabi import sabi_utils
+sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
+
+from starboard.sabi import sabi_utils  # pylint:disable=wrong-import-position
 
 
 def _GenerateSabiId(sabi_json, omaha):
@@ -62,7 +62,7 @@
   )
   sabi_utils.AddSabiArguments(arg_parser)
   args, _ = arg_parser.parse_known_args(argv)
-  sabi_json = sabi_utils.LoadSabi(args.filename, args.platform)
+  sabi_json = sabi_utils.LoadSabi(args.filename)
   return _GenerateSabiId(sabi_json, args.omaha)
 
 
diff --git a/src/starboard/sabi/generate_sabi_id_test.py b/src/starboard/sabi/generate_sabi_id_test.py
index 32c63e3..9eb9893 100755
--- a/src/starboard/sabi/generate_sabi_id_test.py
+++ b/src/starboard/sabi/generate_sabi_id_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Copyright 2019 The Cobalt Authors. All Rights Reserved.
 #
@@ -16,12 +16,15 @@
 #
 """Tests the generate_sabi_id module."""
 
-import _env  # pylint: disable=unused-import
-
+import json
 import os
+import sys
 import tempfile
 import unittest
 
+sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
+
+# pylint: disable=wrong-import-position
 from starboard.sabi import generate_sabi_id
 from starboard.tools import paths
 
@@ -33,7 +36,7 @@
 class GenerateSabiIdTest(unittest.TestCase):
 
   def testRainyDayNoFileNoPlatform(self):
-    with self.assertRaises(SystemExit):
+    with self.assertRaises(TypeError):
       generate_sabi_id.DoMain([])
 
   def testRainyDayNonexistentFile(self):
@@ -41,15 +44,11 @@
       generate_sabi_id.DoMain(['-f', 'invalid_filename'])
 
   def testRainyDayBadFile(self):
-    bad_sabi_json = tempfile.NamedTemporaryFile()
+    bad_sabi_json = tempfile.NamedTemporaryFile(mode='w')
     bad_sabi_json.write('{}')
-    with self.assertRaises(ValueError):
+    with self.assertRaises(json.decoder.JSONDecodeError):
       generate_sabi_id.DoMain(['-f', bad_sabi_json.name])
 
-  def testRainyDayBadPlatform(self):
-    with self.assertRaises(ValueError):
-      generate_sabi_id.DoMain(['-p', 'ms-dos'])
-
   def testSunnyDayNotOmaha(self):
     sabi_id = generate_sabi_id.DoMain(['-f', _TEST_SABI])
     self.assertEqual(_TEST_SABI_ID, sabi_id)
diff --git a/src/starboard/sabi/sabi_utils.py b/src/starboard/sabi/sabi_utils.py
index 4a279af..7762e36 100644
--- a/src/starboard/sabi/sabi_utils.py
+++ b/src/starboard/sabi/sabi_utils.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Copyright 2020 The Cobalt Authors. All Rights Reserved.
 #
@@ -15,113 +15,54 @@
 # limitations under the License.
 """Utility functions for interacting with Starboard ABI JSON files."""
 
-import _env  # pylint: disable=unused-import
-
 import json
 import os
 import re
+import sys
 
-from starboard.sabi import sabi
-from starboard.tools import build
-from starboard.tools import paths
+sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
+
+from starboard.tools import paths  # pylint:disable=wrong-import-position
 
 SABI_SCHEMA_PATH = os.path.join(paths.STARBOARD_ROOT, 'sabi', 'schema')
 SB_API_VERSION_FROM_SABI_RE = re.compile('sabi-v([0-9]+).json')
 
 
-def _PlatformToSabiFile(platform):
-  """Returns the Starboard ABI file for the given platform.
-
-  Args:
-    platform: The platform of the desired Starboard ABI file.
-
-  Raises:
-    ValueError: When |platform| is not provided, or when |platform| is provided
-      and it fails to load the platform configuration.
-
-  Returns:
-    The path to the Starboard ABI file associated with the provided platform.
-  """
-  if not platform:
-    raise ValueError('A platform must be specified.')
-  platform_configuration = build.GetPlatformConfig(platform)
-  if not platform_configuration:
-    raise ValueError('Failed to get platform configuration.')
-  filename = platform_configuration.GetPathToSabiJsonFile().format(
-      sb_api_version=sabi.SB_API_VERSION)
-  return os.path.join(paths.REPOSITORY_ROOT, filename)
-
-
 def AddSabiArguments(arg_parser):
-  group = arg_parser.add_mutually_exclusive_group(required=True)
-  group.add_argument(
+  arg_parser.add_argument(
       '-f',
       '--filename',
       default=None,
       help='The Starboard ABI JSON file that should be used.',
   )
-  group.add_argument(
-      '-p',
-      '--platform',
-      default=None,
-      help='The platform whose Starboard ABI JSON should be used.',
-  )
 
 
-def LoadSabi(filename=None, platform=None):
+def LoadSabi(filename):
   """Returns the contents of the desired Starboard ABI file.
 
-  This function will use either the provided |filename| or the provided
-  |platform| to locate the desired Starboard ABI file.
+  This function will use the provided |filename| to locate the desired
+  Starboard ABI file.
 
   Args:
     filename: The path, can be relative or absolute, to the desired Starboard
       ABI file.
-    platform: The platform of the desired Starboard ABI file.
-
-  Raises:
-    ValueError: When both |filename| and |platform| are provided.
 
   Returns:
     The contents of the desired Starboard ABI file.
   """
-  if (filename and platform) or (not filename and not platform):
-    raise ValueError('Either |filename| or |platform| must be provided.')
-  if platform:
-    filename = _PlatformToSabiFile(platform)
   with open(filename) as f:
     return json.load(f)['variables']
 
 
-def LoadSabiSchema(filename=None, platform=None):
+def LoadSabiSchema(filename):
   """Returns the contents of the schema associated with the Starboard ABI file.
 
   Args:
     filename: The path, can be relative or absolute, to the desired Starboard
       ABI schema file.
-    platform: A platform whose Starboard ABI file can be validated with the
-      desired Starboard ABI schema file.
-
-  Raises:
-    ValueError: When both |filename| and |platform| are provided, or when
-      |platform| is provided and Starboard API version could not be parsed
-      from the associated Starboard ABI filename.
 
   Returns:
     The contents of the schema associated with the provided Starboard ABI file.
   """
-  if (filename and platform) or (not filename and not platform):
-    raise ValueError('Either |filename| or |platform| must be provided.')
-  if platform:
-    filename = _PlatformToSabiFile(platform)
-    filename = os.path.basename(filename)
-    match = SB_API_VERSION_FROM_SABI_RE.search(filename)
-    if not match:
-      raise ValueError(
-          'The Starboard API version could not be parsed from the filename: {}'
-          .format(filename))
-    filename = os.path.join(
-        SABI_SCHEMA_PATH, 'sabi-v{sb_api_version}.schema.json'.format(
-            sb_api_version=match.group(1)))
   with open(filename) as f:
     return json.load(f)
diff --git a/src/starboard/sabi/validate_sabi.py b/src/starboard/sabi/validate_sabi.py
index 88e3e61..853df34 100644
--- a/src/starboard/sabi/validate_sabi.py
+++ b/src/starboard/sabi/validate_sabi.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Copyright 2019 The Cobalt Authors. All Rights Reserved.
 #
@@ -15,12 +15,13 @@
 # limitations under the License.
 """Validates a specified Starboard ABI JSON file."""
 
-import _env  # pylint: disable=unused-import
-
 import argparse
+import os
 import sys
 
-from starboard.sabi import sabi_utils
+sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
+
+from starboard.sabi import sabi_utils  # pylint:disable=wrong-import-position
 
 # Starboard ABI files are considered invalid unless 'jsonschema' is installed
 # and used to verify them.
@@ -56,8 +57,8 @@
   sabi_utils.AddSabiArguments(arg_parser)
   args, _ = arg_parser.parse_known_args()
   return ValidateSabi(
-      sabi_utils.LoadSabi(args.filename, args.platform),
-      sabi_utils.LoadSabiSchema(args.filename, args.platform))
+      sabi_utils.LoadSabi(args.filename),
+      sabi_utils.LoadSabiSchema(args.filename))
 
 
 if __name__ == '__main__':
diff --git a/src/starboard/sabi/validate_sabi_test.py b/src/starboard/sabi/validate_sabi_test.py
index 4610184..3b54b0d 100644
--- a/src/starboard/sabi/validate_sabi_test.py
+++ b/src/starboard/sabi/validate_sabi_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Copyright 2019 The Cobalt Authors. All Rights Reserved.
 #
@@ -16,21 +16,22 @@
 #
 """Tests the validate_sabi module."""
 
-import _env  # pylint: disable=unused-import
-
 import copy
 import os
+import sys
 import unittest
 
+sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
+
+# pylint: disable=wrong-import-position
 from starboard.sabi import sabi_utils
 from starboard.sabi import validate_sabi
 from starboard.tools import paths
 
 _TEST_SABI_JSON = sabi_utils.LoadSabi(
-    os.path.join(paths.STARBOARD_ROOT, 'sabi', 'test', 'sabi.json'), None)
+    os.path.join(paths.STARBOARD_ROOT, 'sabi', 'test', 'sabi.json'))
 _TEST_SABI_SCHEMA = sabi_utils.LoadSabiSchema(
-    os.path.join(paths.STARBOARD_ROOT, 'sabi', 'test', 'sabi.schema.json'),
-    None)
+    os.path.join(paths.STARBOARD_ROOT, 'sabi', 'test', 'sabi.schema.json'))
 
 
 class ValidateSabiTest(unittest.TestCase):
@@ -49,8 +50,8 @@
           sabi_schema_path = os.path.join(
               sabi_utils.SABI_SCHEMA_PATH,
               'sabi-v{}.schema.json'.format(match.group(1)))
-          sabi_json = sabi_utils.LoadSabi(os.path.join(root, f), None)
-          sabi_schema = sabi_utils.LoadSabiSchema(sabi_schema_path, None)
+          sabi_json = sabi_utils.LoadSabi(os.path.join(root, f))
+          sabi_schema = sabi_utils.LoadSabiSchema(sabi_schema_path)
           self.assertTrue(validate_sabi.ValidateSabi(sabi_json, sabi_schema))
 
   def testRainyDayValidateSabiSabiJsonWithMissingEntry(self):
@@ -65,7 +66,7 @@
 
   def testRainyDayValidateSabiSabiJsonWithInvalidEntry(self):
     sabi_json = copy.deepcopy(_TEST_SABI_JSON)
-    sabi_json[sabi_json.keys()[0]] = 'invalid_value'
+    sabi_json[list(sabi_json.keys())[0]] = 'invalid_value'
     self.assertFalse(validate_sabi.ValidateSabi(sabi_json, _TEST_SABI_SCHEMA))
 
 
diff --git a/src/starboard/shared/posix/socket_create.cc b/src/starboard/shared/posix/socket_create.cc
index 86815f7..5c0922c 100644
--- a/src/starboard/shared/posix/socket_create.cc
+++ b/src/starboard/shared/posix/socket_create.cc
@@ -76,9 +76,9 @@
 
 #if !defined(MSG_NOSIGNAL) && defined(SO_NOSIGPIPE)
   // Use SO_NOSIGPIPE to mute SIGPIPE on darwin systems.
-  int optval_set=1;
-  setsockopt(socket_fd, SOL_SOCKET, SO_NOSIGPIPE, (void*)&optval_set,
-    sizeof(int));
+  int optval_set = 1;
+  setsockopt(socket_fd, SOL_SOCKET, SO_NOSIGPIPE,
+             reinterpret_cast<void*>(&optval_set), sizeof(int));
 #endif
 
   return new SbSocketPrivate(address_type, protocol, socket_fd);
diff --git a/src/starboard/shared/starboard/application.cc b/src/starboard/shared/starboard/application.cc
index 1500f42..c54fa31 100644
--- a/src/starboard/shared/starboard/application.cc
+++ b/src/starboard/shared/starboard/application.cc
@@ -233,21 +233,33 @@
   }
 }
 
+#if SB_API_VERSION >= 13
+void Application::DispatchStart(SbTimeMonotonic timestamp) {
+  SB_DCHECK(IsCurrentThread());
+  SB_DCHECK(state_ == kStateUnstarted);
+  DispatchAndDelete(CreateInitialEvent(kSbEventTypeStart, timestamp));
+}
+#else  // SB_API_VERSION >= 13
 void Application::DispatchStart() {
   SB_DCHECK(IsCurrentThread());
-#if SB_API_VERSION >= 13
-  SB_DCHECK(state_ == kStateUnstarted);
-#else
   SB_DCHECK(state_ == kStateUnstarted || state_ == kStatePreloading);
-#endif  // SB_API_VERSION >= 13
   DispatchAndDelete(CreateInitialEvent(kSbEventTypeStart));
 }
+#endif  // SB_API_VERSION >= 13
 
+#if SB_API_VERSION >= 13
+void Application::DispatchPreload(SbTimeMonotonic timestamp) {
+  SB_DCHECK(IsCurrentThread());
+  SB_DCHECK(state_ == kStateUnstarted);
+  DispatchAndDelete(CreateInitialEvent(kSbEventTypePreload, timestamp));
+}
+#else  // SB_API_VERSION >= 13
 void Application::DispatchPreload() {
   SB_DCHECK(IsCurrentThread());
   SB_DCHECK(state_ == kStateUnstarted);
   DispatchAndDelete(CreateInitialEvent(kSbEventTypePreload));
 }
+#endif  // SB_API_VERSION >= 13
 
 bool Application::HasPreloadSwitch() {
   return command_line_->HasSwitch(kPreloadSwitch);
@@ -570,7 +582,12 @@
   }
 }
 
+#if SB_API_VERSION >= 13
+Application::Event* Application::CreateInitialEvent(SbEventType type,
+                                                    SbTimeMonotonic timestamp) {
+#else  // SB_API_VERSION >= 13
 Application::Event* Application::CreateInitialEvent(SbEventType type) {
+#endif  // SB_API_VERSION >= 13
   SB_DCHECK(type == kSbEventTypePreload || type == kSbEventTypeStart);
   SbEventStartData* start_data = new SbEventStartData();
   memset(start_data, 0, sizeof(SbEventStartData));
@@ -583,16 +600,28 @@
     start_data->argument_values[i] = const_cast<char*>(args[i].c_str());
   }
   start_data->link = start_link_;
+#if SB_API_VERSION >= 13
+  return new Event(type, timestamp, start_data, &DeleteStartData);
+#else  // SB_API_VERSION >= 13
   return new Event(type, start_data, &DeleteStartData);
+#endif  // SB_API_VERSION >= 13
 }
 
 int Application::RunLoop() {
   SB_DCHECK(command_line_);
+#if SB_API_VERSION >= 13
   if (IsPreloadImmediate()) {
+    DispatchPreload(SbTimeGetMonotonicNow());
+  } else if (IsStartImmediate()) {
+    DispatchStart(SbTimeGetMonotonicNow());
+  }
+#else  // SB_API_VERSION >= 13
+ if (IsPreloadImmediate()) {
     DispatchPreload();
   } else if (IsStartImmediate()) {
     DispatchStart();
   }
+#endif  // SB_API_VERSION >= 13
 
   for (;;) {
     if (!DispatchNextEvent()) {
diff --git a/src/starboard/shared/starboard/application.h b/src/starboard/shared/starboard/application.h
index 4e332b4..6281f00 100644
--- a/src/starboard/shared/starboard/application.h
+++ b/src/starboard/shared/starboard/application.h
@@ -475,7 +475,11 @@
 
   // Synchronously dispatches a Start event to the system event handler. Must be
   // called on the main dispatch thread.
+#if SB_API_VERSION >= 13
+  void DispatchStart(SbTimeMonotonic timestamp);
+#else  // SB_API_VERSION >= 13
   void DispatchStart();
+#endif  // SB_API_VERSION >= 13
 
   // Returns whether the Preload event should be sent in |Run| before entering
   // the event loop. Derived classes that return true must call |Unpause| or
@@ -486,7 +490,11 @@
 
   // Synchronously dispatches a Preload event to the system event handler. Must
   // be called on the main dispatch thread.
+#if SB_API_VERSION >= 13
+  void DispatchPreload(SbTimeMonotonic timestamp);
+#else  // SB_API_VERSION >= 13
   void DispatchPreload();
+#endif  // SB_API_VERSION >= 13
 
   // Returns whether the '--preload' command-line argument is specified.
   bool HasPreloadSwitch();
@@ -504,7 +512,11 @@
  private:
   // Creates an initial event type of either Start or Preload with the original
   // command line and deep link.
+#if SB_API_VERSION >= 13
+  Event* CreateInitialEvent(SbEventType type, SbTimeMonotonic timestamp);
+#else  // SB_API_VERSION >= 13
   Event* CreateInitialEvent(SbEventType type);
+#endif  // SB_API_VERSION >= 13
 
   // Internal workhorse of the main run loop.
   int RunLoop();
diff --git a/src/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc b/src/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc
index c6e9f7f..dbb1d9e 100644
--- a/src/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc
+++ b/src/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc
@@ -362,7 +362,8 @@
     return test_params;
   }
 
-  vector<const char*> supported_files = GetSupportedAudioTestFiles(false, true);
+  vector<const char*> supported_files =
+      GetSupportedAudioTestFiles(kExcludeHeaac, 6);
 
   // Generate test cases. For example, we have |supported_files| [A, B, C].
   // Add tests A->A, A->B, A->C, B->A, B->B, B->C, C->A, C->B and C->C.
diff --git a/src/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc b/src/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
index 39fb11d..74b8adf 100644
--- a/src/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
+++ b/src/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
@@ -454,9 +454,8 @@
        i < original_audio_sample_info.audio_specific_config_size; ++i) {
     std::vector<uint8_t> config(
         original_audio_sample_info.audio_specific_config_size);
-    memcpy(config.data(),
-                 original_audio_sample_info.audio_specific_config,
-                 original_audio_sample_info.audio_specific_config_size);
+    memcpy(config.data(), original_audio_sample_info.audio_specific_config,
+           original_audio_sample_info.audio_specific_config_size);
     auto audio_sample_info = original_audio_sample_info;
     config[i] = ~config[i];
     audio_sample_info.audio_specific_config = config.data();
@@ -478,8 +477,7 @@
   for (uint16_t i = 0;
        i < original_audio_sample_info.audio_specific_config_size; ++i) {
     std::vector<uint8_t> config(i);
-    memcpy(config.data(),
-                 original_audio_sample_info.audio_specific_config, i);
+    memcpy(config.data(), original_audio_sample_info.audio_specific_config, i);
     auto audio_sample_info = original_audio_sample_info;
     audio_sample_info.audio_specific_config = config.data();
     audio_sample_info.audio_specific_config_size = i;
@@ -637,7 +635,7 @@
 INSTANTIATE_TEST_CASE_P(
     AudioDecoderTests,
     AudioDecoderTest,
-    Combine(ValuesIn(GetSupportedAudioTestFiles(true, true)), Bool()));
+    Combine(ValuesIn(GetSupportedAudioTestFiles(kIncludeHeaac, 6)), Bool()));
 
 }  // namespace
 }  // namespace testing
diff --git a/src/starboard/shared/starboard/player/filter/testing/player_components_test.cc b/src/starboard/shared/starboard/player/filter/testing/player_components_test.cc
index 0b49905..fe7932f 100644
--- a/src/starboard/shared/starboard/player/filter/testing/player_components_test.cc
+++ b/src/starboard/shared/starboard/player/filter/testing/player_components_test.cc
@@ -264,10 +264,14 @@
 
   void WriteDataUntilPrerolled(SbTime timeout = kDefaultPrerollTimeOut) {
     SbTimeMonotonic start_time = SbTimeGetMonotonicNow();
+    SbTime max_timestamp = GetMediaTime() + kMaxWriteAheadDuration;
     while (!IsPlaybackPrerolled()) {
-      ASSERT_LE(SbTimeGetMonotonicNow() - start_time, timeout);
-      bool written =
-          TryToWriteOneInputBuffer(GetMediaTime() + kMaxWriteAheadDuration);
+      ASSERT_LE(SbTimeGetMonotonicNow() - start_time, timeout)
+          << "WriteDataUntilPrerolled() timed out, buffered audio ("
+          << GetCurrentAudioBufferTimestamp() << "), buffered video ("
+          << GetCurrentVideoBufferTimestamp() << "), max timestamp ("
+          << max_timestamp << ").";
+      bool written = TryToWriteOneInputBuffer(max_timestamp);
       if (!written) {
         ASSERT_NO_FATAL_FAILURE(RenderAndProcessPendingJobs());
         SbThreadSleep(5 * kSbTimeMillisecond);
@@ -275,6 +279,9 @@
     }
   }
 
+  // The function will exit after all buffers before |eos_timestamp| are written
+  // into the player. Note that, to avoid audio or video underflow, the function
+  // allow to write buffers of timestamp greater than |timestamp|.
   void WriteDataUntil(SbTime timestamp, SbTime timeout = kDefaultWriteTimeOut) {
     SB_CHECK(playback_rate_ != 0);
 
@@ -283,9 +290,14 @@
         (GetAudioRenderer() && GetCurrentAudioBufferTimestamp() < timestamp) ||
         (GetVideoRenderer() && GetCurrentVideoBufferTimestamp() < timestamp)) {
       if (last_input_filled_time != -1) {
-        ASSERT_LE(SbTimeGetMonotonicNow() - last_input_filled_time, timeout);
+        ASSERT_LE(SbTimeGetMonotonicNow() - last_input_filled_time, timeout)
+            << "WriteDataUntil() timed out, buffered audio ("
+            << GetCurrentAudioBufferTimestamp() << "), buffered video ("
+            << GetCurrentVideoBufferTimestamp() << "), timestamp (" << timestamp
+            << ").";
       }
-      bool written = TryToWriteOneInputBuffer(timestamp);
+      bool written =
+          TryToWriteOneInputBuffer(timestamp + kMaxWriteAheadDuration);
       if (written) {
         last_input_filled_time = SbTimeGetMonotonicNow();
       } else {
@@ -295,7 +307,44 @@
     }
   }
 
-  void WriteEOS() {
+  // The function will write EOS immediately after all buffers before
+  // |eos_timestamp| are written into the player.
+  void WriteDataAndEOS(SbTime eos_timestamp,
+                       SbTime timeout = kDefaultWriteTimeOut) {
+    SB_CHECK(playback_rate_ != 0);
+    bool audio_eos_written = !GetAudioRenderer();
+    bool video_eos_written = !GetVideoRenderer();
+
+    SbTimeMonotonic last_input_filled_time = SbTimeGetMonotonicNow();
+    while (!audio_eos_written || !video_eos_written) {
+      if (!audio_eos_written &&
+          GetCurrentAudioBufferTimestamp() >= eos_timestamp) {
+        GetAudioRenderer()->WriteEndOfStream();
+        audio_eos_written = true;
+      }
+      if (!video_eos_written &&
+          GetCurrentVideoBufferTimestamp() >= eos_timestamp) {
+        GetVideoRenderer()->WriteEndOfStream();
+        video_eos_written = true;
+      }
+      if (last_input_filled_time != -1) {
+        ASSERT_LE(SbTimeGetMonotonicNow() - last_input_filled_time, timeout)
+            << "WriteDataAndEOS() timed out, buffered audio ("
+            << GetCurrentAudioBufferTimestamp() << "), buffered video ("
+            << GetCurrentVideoBufferTimestamp() << "), eos_timestamp ("
+            << eos_timestamp << ").";
+      }
+      bool written = TryToWriteOneInputBuffer(eos_timestamp);
+      if (written) {
+        last_input_filled_time = SbTimeGetMonotonicNow();
+      } else {
+        ASSERT_NO_FATAL_FAILURE(RenderAndProcessPendingJobs());
+        SbThreadSleep(5 * kSbTimeMillisecond);
+      }
+    }
+  }
+
+  void WriteEOSDirectly() {
     if (GetAudioRenderer()) {
       GetAudioRenderer()->WriteEndOfStream();
     }
@@ -314,9 +363,14 @@
         SbTimeGetMonotonicNow() +
         static_cast<SbTime>((duration - current_time) / playback_rate_) +
         kDefaultEndTimeOut;
+
     while (!IsPlaybackEnded()) {
       // If this fails, timeout must have been reached.
-      ASSERT_LE(SbTimeGetMonotonicNow(), expected_end_time);
+      ASSERT_LE(SbTimeGetMonotonicNow(), expected_end_time)
+          << "WaitUntilPlaybackEnded() timed out, buffered audio ("
+          << GetCurrentAudioBufferTimestamp() << "), buffered video ("
+          << GetCurrentVideoBufferTimestamp() << "), current media time is "
+          << GetMediaTime() << ".";
       ASSERT_NO_FATAL_FAILURE(RenderAndProcessPendingJobs());
       SbThreadSleep(5 * kSbTimeMillisecond);
     }
@@ -358,9 +412,11 @@
   }
   void OnEnded(SbMediaType media_type) {
     if (media_type == kSbMediaTypeAudio) {
+      SB_DLOG(INFO) << "Audio OnEnded is called.";
       audio_ended_ = true;
     } else {
       EXPECT_EQ(media_type, kSbMediaTypeVideo);
+      SB_DLOG(INFO) << "Video OnEnded is called.";
       video_ended_ = true;
     }
   }
@@ -454,8 +510,7 @@
                                    GetCurrentAudioBufferTimestamp());
   media_duration = std::max(kSbTimeSecond, media_duration);
 
-  ASSERT_NO_FATAL_FAILURE(WriteDataUntil(media_duration));
-  ASSERT_NO_FATAL_FAILURE(WriteEOS());
+  ASSERT_NO_FATAL_FAILURE(WriteDataAndEOS(media_duration));
   ASSERT_NO_FATAL_FAILURE(WaitUntilPlaybackEnded());
 
   // TODO: investigate and reduce the tolerance.
@@ -466,15 +521,14 @@
 
 TEST_P(PlayerComponentsTest, ShortPlayback) {
   Seek(0);
-  ASSERT_NO_FATAL_FAILURE(WriteDataUntil(50 * kSbTimeMillisecond));
-  ASSERT_NO_FATAL_FAILURE(WriteEOS());
+  ASSERT_NO_FATAL_FAILURE(WriteDataAndEOS(50 * kSbTimeMillisecond));
   Play();
   ASSERT_NO_FATAL_FAILURE(WaitUntilPlaybackEnded());
 }
 
 TEST_P(PlayerComponentsTest, EOSWithoutInput) {
   Seek(0);
-  ASSERT_NO_FATAL_FAILURE(WriteEOS());
+  ASSERT_NO_FATAL_FAILURE(WriteEOSDirectly());
   Play();
   ASSERT_NO_FATAL_FAILURE(WaitUntilPlaybackEnded());
   // TODO: investigate and reduce the tolerance.
@@ -503,9 +557,9 @@
   ASSERT_EQ(media_time, GetMediaTime());
 
   Play();
-  ASSERT_NO_FATAL_FAILURE(WriteDataUntil(std::max(
-      GetCurrentAudioBufferTimestamp(), GetCurrentVideoBufferTimestamp())));
-  ASSERT_NO_FATAL_FAILURE(WriteEOS());
+  SbTime duration = std::max(GetCurrentAudioBufferTimestamp(),
+                             GetCurrentVideoBufferTimestamp());
+  ASSERT_NO_FATAL_FAILURE(WriteDataAndEOS(duration));
   ASSERT_NO_FATAL_FAILURE(WaitUntilPlaybackEnded());
 }
 
@@ -528,8 +582,7 @@
 //   SbTimeMonotonic play_requested_at = SbTimeGetMonotonicNow();
 //   Play();
 
-//   ASSERT_NO_FATAL_FAILURE(WriteDataUntil(media_duration_to_write));
-//   ASSERT_NO_FATAL_FAILURE(WriteEOS());
+//   ASSERT_NO_FATAL_FAILURE(WriteDataAndEOS(media_duration_to_write));
 //   ASSERT_NO_FATAL_FAILURE(WaitUntilPlaybackEnded());
 
 //   TODO: Enable the below check after we improve the accuracy of varied
@@ -558,8 +611,7 @@
 //   SbTimeMonotonic play_requested_at = SbTimeGetMonotonicNow();
 //   Play();
 
-//   ASSERT_NO_FATAL_FAILURE(WriteDataUntil(media_duration_to_write));
-//   ASSERT_NO_FATAL_FAILURE(WriteEOS());
+//   ASSERT_NO_FATAL_FAILURE(WriteDataAndEOS(media_duration_to_write));
 //   ASSERT_NO_FATAL_FAILURE(WaitUntilPlaybackEnded());
 
 //   playback rate time reporting.
@@ -586,8 +638,7 @@
   ASSERT_FALSE(IsPlaying());
 
   Play();
-  ASSERT_NO_FATAL_FAILURE(WriteDataUntil(seek_to_time + kSbTimeSecond));
-  ASSERT_NO_FATAL_FAILURE(WriteEOS());
+  ASSERT_NO_FATAL_FAILURE(WriteDataAndEOS(seek_to_time + kSbTimeSecond));
   ASSERT_NO_FATAL_FAILURE(WaitUntilPlaybackEnded());
 }
 
@@ -599,7 +650,7 @@
   ASSERT_FALSE(IsPlaying());
 
   Play();
-  ASSERT_NO_FATAL_FAILURE(WriteDataUntil(seek_to_time + kSbTimeSecond));
+  ASSERT_NO_FATAL_FAILURE(WriteDataAndEOS(seek_to_time + kSbTimeSecond));
 
   Pause();
   seek_to_time = 1 * kSbTimeSecond;
@@ -609,8 +660,7 @@
   ASSERT_FALSE(IsPlaying());
 
   Play();
-  ASSERT_NO_FATAL_FAILURE(WriteDataUntil(seek_to_time + kSbTimeSecond));
-  ASSERT_NO_FATAL_FAILURE(WriteEOS());
+  ASSERT_NO_FATAL_FAILURE(WriteDataAndEOS(seek_to_time + kSbTimeSecond));
   ASSERT_NO_FATAL_FAILURE(WaitUntilPlaybackEnded());
 }
 
@@ -624,7 +674,8 @@
   vector<PlayerComponentsTestParam> supported_parameters;
 
   // TODO: Enable tests of "heaac.dmp".
-  vector<const char*> audio_files = GetSupportedAudioTestFiles(false, false);
+  vector<const char*> audio_files =
+      GetSupportedAudioTestFiles(kExcludeHeaac, SbAudioSinkGetMaxChannels());
   vector<VideoTestParam> video_params = GetSupportedVideoTests();
 
   // Filter too short dmp files, as the tests need at least 4s of data.
diff --git a/src/starboard/shared/starboard/player/filter/testing/test_util.cc b/src/starboard/shared/starboard/player/filter/testing/test_util.cc
index 3c71cd3..759fc83 100644
--- a/src/starboard/shared/starboard/player/filter/testing/test_util.cc
+++ b/src/starboard/shared/starboard/player/filter/testing/test_util.cc
@@ -65,8 +65,35 @@
   return GetTestInputDirectory() + kSbFileSepChar + filename;
 }
 
-std::vector<const char*> GetSupportedAudioTestFiles(bool include_heaac,
-                                                    bool ignore_channels) {
+std::string GetContentTypeFromAudioCodec(SbMediaAudioCodec audio_codec,
+                                         const char* mime_attributes) {
+  SB_DCHECK(audio_codec != kSbMediaAudioCodecNone);
+
+  std::string content_type;
+  switch (audio_codec) {
+    case kSbMediaAudioCodecAac:
+      content_type = "audio/mp4; codecs=\"mp4a.40.2\"";
+      break;
+    case kSbMediaAudioCodecOpus:
+      content_type = "audio/webm; codecs=\"opus\"";
+      break;
+    case kSbMediaAudioCodecAc3:
+      content_type = "audio/mp4; codecs=\"ac-3\"";
+      break;
+    case kSbMediaAudioCodecEac3:
+      content_type = "audio/mp4; codecs=\"ec-3\"";
+      break;
+    default:
+      SB_NOTREACHED();
+  }
+  return strlen(mime_attributes) > 0 ? content_type + "; " + mime_attributes
+                                     : content_type;
+}
+
+std::vector<const char*> GetSupportedAudioTestFiles(
+    HeaacOption heaac_option,
+    int max_channels,
+    const char* extra_mime_attributes) {
   // beneath_the_canopy_aac_stereo.dmp
   //   codec: kSbMediaAudioCodecAac
   //   sampling rate: 44.1k
@@ -81,6 +108,14 @@
   // filters for every platform, just in case a particular test case should be
   // disabled.
 
+  struct AudioFileInfo {
+    const char* filename;
+    SbMediaAudioCodec audio_codec;
+    int num_channels;
+    int64_t bitrate;
+    bool is_heaac;
+  };
+
   const char* kFilenames[] = {"beneath_the_canopy_aac_stereo.dmp",
                               "beneath_the_canopy_aac_5_1.dmp",
                               "beneath_the_canopy_aac_mono.dmp",
@@ -91,51 +126,50 @@
                               "sintel_381_ac3.dmp",
                               "heaac.dmp"};
 
-  static std::vector<const char*> supported_filenames_channels_unchecked;
-  static std::vector<const char*> supported_filenames_channels_checked;
+  static std::vector<AudioFileInfo> audio_file_info_cache;
+  std::vector<const char*> filenames;
 
-  if (supported_filenames_channels_unchecked.empty()) {
-    int supported_channels = SbAudioSinkGetMaxChannels();
+  if (audio_file_info_cache.empty()) {
+    audio_file_info_cache.reserve(SB_ARRAY_SIZE_INT(kFilenames));
     for (auto filename : kFilenames) {
       VideoDmpReader dmp_reader(ResolveTestFileName(filename).c_str(),
                                 VideoDmpReader::kEnableReadOnDemand);
       SB_DCHECK(dmp_reader.number_of_audio_buffers() > 0);
 
-      // Filter files of unsupported codec.
-      if (!SbMediaIsAudioSupported(dmp_reader.audio_codec(),
-#if SB_API_VERSION >= 12
-                                   "",  // content_type
-#endif                                  // SB_API_VERSION >= 12
-                                   dmp_reader.audio_bitrate())) {
-        continue;
-      }
-      supported_filenames_channels_unchecked.push_back(filename);
-
-      // Filter files of unsupported channels.
-      if (dmp_reader.audio_sample_info().number_of_channels >
-          supported_channels) {
-        continue;
-      }
-      supported_filenames_channels_checked.push_back(filename);
+      audio_file_info_cache.push_back(
+          {filename, dmp_reader.audio_codec(),
+           dmp_reader.audio_sample_info().number_of_channels,
+           dmp_reader.audio_bitrate(), strstr(filename, "heaac") != nullptr});
     }
   }
 
-  if (include_heaac) {
-    return ignore_channels ? supported_filenames_channels_unchecked
-                           : supported_filenames_channels_checked;
-  }
-
-  // Filter heaac file.
-  std::vector<const char*> test_params;
-  for (auto filename :
-       (ignore_channels ? supported_filenames_channels_unchecked
-                        : supported_filenames_channels_checked)) {
-    if (strstr(filename, "heaac") != nullptr) {
+  for (auto& audio_file_info : audio_file_info_cache) {
+    // Filter files with channels exceeding |max_channels|.
+    if (audio_file_info.num_channels > max_channels) {
       continue;
     }
-    test_params.push_back(filename);
+
+    // Filter heaac files when |heaac_option| == kExcludeHeaac.
+    if (heaac_option == kExcludeHeaac && audio_file_info.is_heaac) {
+      continue;
+    }
+
+    // Filter files of unsupported codec.
+    if (!SbMediaIsAudioSupported(
+            audio_file_info.audio_codec,
+#if SB_API_VERSION >= 12
+            GetContentTypeFromAudioCodec(audio_file_info.audio_codec,
+                                         extra_mime_attributes)
+                .c_str(),
+#endif  // SB_API_VERSION >= 12
+            audio_file_info.bitrate)) {
+      continue;
+    }
+
+    filenames.push_back(audio_file_info.filename);
   }
-  return test_params;
+
+  return filenames;
 }
 
 std::vector<VideoTestParam> GetSupportedVideoTests() {
diff --git a/src/starboard/shared/starboard/player/filter/testing/test_util.h b/src/starboard/shared/starboard/player/filter/testing/test_util.h
index 9923a96..20808ec 100644
--- a/src/starboard/shared/starboard/player/filter/testing/test_util.h
+++ b/src/starboard/shared/starboard/player/filter/testing/test_util.h
@@ -35,6 +35,11 @@
 
 typedef std::tuple<const char*, SbPlayerOutputMode> VideoTestParam;
 
+enum HeaacOption {
+  kIncludeHeaac,
+  kExcludeHeaac,
+};
+
 // The function doesn't free the buffer, it assumes that the lifetime of the
 // buffer is actually managed by other code.  It can be used in the places where
 // SbPlayerDeallocateSampleFunc is expected.
@@ -43,8 +48,10 @@
                               const void* sample_buffer);
 
 std::string ResolveTestFileName(const char* filename);
-std::vector<const char*> GetSupportedAudioTestFiles(bool include_heaac,
-                                                    bool ignore_channels);
+std::vector<const char*> GetSupportedAudioTestFiles(
+    HeaacOption heaac_option,
+    int max_channels,
+    const char* extra_mime_attributes = "");
 std::vector<VideoTestParam> GetSupportedVideoTests();
 
 bool CreateAudioComponents(bool using_stub_decoder,
diff --git a/src/starboard/shared/starboard/player/player.gyp b/src/starboard/shared/starboard/player/player.gyp
index 264d2b8..c38e82e 100644
--- a/src/starboard/shared/starboard/player/player.gyp
+++ b/src/starboard/shared/starboard/player/player.gyp
@@ -65,7 +65,7 @@
           },
           'action_name': 'player_download_test_data',
           'action': [
-            'python',
+            'python2',
             '<(DEPTH)/tools/download_from_gcs.py',
             '--bucket', 'cobalt-static-storage',
             '--sha1', '<(sha_dir)',
diff --git a/src/starboard/shared/widevine/BUILD.gn b/src/starboard/shared/widevine/BUILD.gn
new file mode 100644
index 0000000..8ab037d
--- /dev/null
+++ b/src/starboard/shared/widevine/BUILD.gn
@@ -0,0 +1,51 @@
+# Copyright 2021 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.
+
+config("oemcrypto_external") {
+  include_dirs = [
+    "//third_party/ce_cdm/core/include",
+    "//third_party/ce_cdm/oemcrypto/include",
+  ]
+}
+
+config("oemcrypto_internal") {
+  defines = [
+    "COBALT_WIDEVINE_KEYBOX_TRANSFORM_FUNCTION=${sb_widevine_platform}_client",
+    "COBALT_WIDEVINE_KEYBOX_TRANSFORM_INCLUDE=\"starboard/keyboxes/${sb_widevine_platform}/${sb_widevine_platform}.h\"",
+    "COBALT_WIDEVINE_KEYBOX_INCLUDE=\"starboard/keyboxes/${sb_widevine_platform}_widevine_keybox.h\"",
+  ]
+}
+
+static_library("oemcrypto") {
+  sources = [
+    "//starboard/keyboxes/${sb_widevine_platform}/${sb_widevine_platform}.h",
+    "//starboard/keyboxes/${sb_widevine_platform}/${sb_widevine_platform}_client.c",
+    "//starboard/shared/widevine/widevine_keybox_hash.cc",
+    "//starboard/shared/widevine/wv_keybox.cc",
+  ]
+
+  public_configs = [ ":oemcrypto_external" ]
+  configs -= [ "//starboard/build/config:size" ]
+  configs += [
+    ":oemcrypto_internal",
+    "//starboard/build/config:speed",
+    "//third_party/ce_cdm/cdm:shared_config",
+  ]
+
+  deps = [
+    "//starboard/common",
+    "//third_party/boringssl:crypto",
+    "//third_party/ce_cdm/oemcrypto/mock:oec_mock",
+  ]
+}
diff --git a/src/starboard/stub/BUILD.gn b/src/starboard/stub/BUILD.gn
index 643a0a9..fa39e19 100644
--- a/src/starboard/stub/BUILD.gn
+++ b/src/starboard/stub/BUILD.gn
@@ -13,8 +13,6 @@
 # limitations under the License.
 
 static_library("starboard_platform") {
-  deps = [ ":stub_sources" ]
-
   sources = [
     "application_stub.cc",
     "application_stub.h",
@@ -30,14 +28,11 @@
     "system_get_extensions.cc",
     "thread_types_public.h",
   ]
+
+  public_deps = [ ":stub_sources" ]
 }
 
 static_library("stub_sources") {
-  public_deps = [
-    "//starboard:starboard_headers_only",
-    "//starboard/common",
-  ]
-
   sources = [
     "//starboard/shared/starboard/application.cc",
     "//starboard/shared/starboard/command_line.cc",
@@ -338,4 +333,13 @@
   ]
 
   public_configs = [ "//starboard/build/config:starboard_implementation" ]
+
+  public_deps = [
+    "//starboard:starboard_headers_only",
+    "//starboard/common",
+  ]
+
+  if (is_internal_build) {
+    deps = [ "//starboard/private/shared/stub:private_sources" ]
+  }
 }
diff --git a/src/third_party/crashpad/handler/handler.gyp b/src/third_party/crashpad/handler/handler.gyp
index effe9e1..1275a4d 100644
--- a/src/third_party/crashpad/handler/handler.gyp
+++ b/src/third_party/crashpad/handler/handler.gyp
@@ -89,8 +89,21 @@
       'sources': [
         'main.cc',
       ],
-
       'conditions': [
+        # Help platforms cross compiled on linux to reduce
+        # the memory footprint of crashpad_handler by eliminating
+        # unused code and unused shared libraries.
+        # The flags assume gcc/clang toolchain.
+        ['host_os=="linux"',  {
+          'cflags': [
+            '-ffunction-sections',
+            '-fdata-sections',
+          ],
+          'ldflags': [
+            '-Wl,--as-needed',
+            '-Wl,-gc-sections',
+          ],
+        }],
         ['OS=="win"',  {
           'msvs_settings': {
             'VCLinkerTool': {
diff --git a/src/third_party/protobuf/BUILD.gn b/src/third_party/protobuf/BUILD.gn
index 84c916a..46161c7 100644
--- a/src/third_party/protobuf/BUILD.gn
+++ b/src/third_party/protobuf/BUILD.gn
@@ -11,6 +11,21 @@
   if (!is_win) {
     defines += [ "HAVE_PTHREAD" ]
   }
+
+  if (is_starboard) {
+    if (is_clang) {
+      cflags = [
+        # protobuf-3 contains a few functions that are unused.
+        "-Wno-unused-function",
+        # protobuf-3 mixes generated enum types.
+        "-Wno-enum-compare-switch",
+        # Used by older version of clang.
+        "-Wno-enum-compare",
+        # Older version of clang don't have -Wno-enum-compare-switch
+        "-Wno-unknown-warning-option",
+      ]
+    }
+  }
 }
 
 config("protobuf_warnings") {
@@ -139,7 +154,11 @@
 component("protobuf_lite") {
   sources = protobuf_lite_sources
 
-  configs -= [ "//build/config/compiler:chromium_code" ]
+  if (is_starboard) {
+    deps = [ "//starboard/common" ]
+  } else {
+    configs -= [ "//build/config/compiler:chromium_code" ]
+  }
   configs += [
     "//build/config/compiler:no_chromium_code",
 
@@ -159,9 +178,11 @@
     "//build/config/compiler:no_size_t_to_int_warning",
   ]
 
-  deps = [
-    "//build/config/sanitizers:deps",
-  ]
+  if (!is_starboard) {
+    deps = [
+      "//build/config/sanitizers:deps",
+    ]
+  }
 
   cflags = protobuf_lite_cflags
 
@@ -308,11 +329,14 @@
     "src/google/protobuf/wrappers.pb.h",
   ]
 
-  deps = [
-    "//build/config/sanitizers:deps",
-  ]
-
-  configs -= [ "//build/config/compiler:chromium_code" ]
+  if (is_starboard) {
+    deps = [ "//starboard/common" ]
+  } else {
+    deps = [
+      "//build/config/sanitizers:deps",
+    ]
+    configs -= [ "//build/config/compiler:chromium_code" ]
+  }
   configs += [
     "//build/config/compiler:no_chromium_code",
 
@@ -526,7 +550,9 @@
       "src/google/protobuf/compiler/zip_writer.h",
     ]
 
-    configs -= [ "//build/config/compiler:chromium_code" ]
+    if (!is_starboard) {
+      configs -= [ "//build/config/compiler:chromium_code" ]
+    }
     configs += [
       "//build/config/compiler:no_chromium_code",
 
@@ -553,7 +579,9 @@
       "src/google/protobuf/compiler/main.cc",
     ]
 
-    configs -= [ "//build/config/compiler:chromium_code" ]
+    if (!is_starboard) {
+      configs -= [ "//build/config/compiler:chromium_code" ]
+    }
     configs += [ "//build/config/compiler:no_chromium_code" ]
 
     cflags = protobuf_lite_cflags
@@ -567,86 +595,88 @@
   }
 }
 
-google_python_dir = "$root_out_dir/pyproto/google"
+if (!is_starboard) {
+  google_python_dir = "$root_out_dir/pyproto/google"
 
-copy("copy_google") {
-  sources = [
-    "__init__.py",
-  ]
-  outputs = [
-    "$google_python_dir/{{source_file_part}}",
-  ]
-}
+  copy("copy_google") {
+    sources = [
+      "__init__.py",
+    ]
+    outputs = [
+      "$google_python_dir/{{source_file_part}}",
+    ]
+  }
 
-copy("copy_six") {
-  sources = [
-    "third_party/six/six.py",
-  ]
-  outputs = [
-    "$google_python_dir/third_party/six/{{source_file_part}}",
-  ]
-}
+  copy("copy_six") {
+    sources = [
+      "third_party/six/six.py",
+    ]
+    outputs = [
+      "$google_python_dir/third_party/six/{{source_file_part}}",
+    ]
+  }
 
-copy("copy_google_protobuf") {
-  sources = [
-    "python/google/protobuf/__init__.py",
-    "python/google/protobuf/descriptor.py",
-    "python/google/protobuf/descriptor_database.py",
-    "python/google/protobuf/descriptor_pool.py",
-    "python/google/protobuf/json_format.py",
-    "python/google/protobuf/message.py",
-    "python/google/protobuf/message_factory.py",
-    "python/google/protobuf/proto_builder.py",
-    "python/google/protobuf/reflection.py",
-    "python/google/protobuf/service.py",
-    "python/google/protobuf/service_reflection.py",
-    "python/google/protobuf/symbol_database.py",
-    "python/google/protobuf/text_encoding.py",
-    "python/google/protobuf/text_format.py",
+  copy("copy_google_protobuf") {
+    sources = [
+      "python/google/protobuf/__init__.py",
+      "python/google/protobuf/descriptor.py",
+      "python/google/protobuf/descriptor_database.py",
+      "python/google/protobuf/descriptor_pool.py",
+      "python/google/protobuf/json_format.py",
+      "python/google/protobuf/message.py",
+      "python/google/protobuf/message_factory.py",
+      "python/google/protobuf/proto_builder.py",
+      "python/google/protobuf/reflection.py",
+      "python/google/protobuf/service.py",
+      "python/google/protobuf/service_reflection.py",
+      "python/google/protobuf/symbol_database.py",
+      "python/google/protobuf/text_encoding.py",
+      "python/google/protobuf/text_format.py",
 
-    # TODO(ncarter): protoc's python generator treats descriptor.proto
-    # specially, but only when the input path is exactly
-    # "google/protobuf/descriptor.proto".  I'm not sure how to execute a rule
-    # from a different directory.  For now, use a manually-generated copy of
-    # descriptor_pb2.py.
-    "python/google/protobuf/descriptor_pb2.py",
-  ]
-  outputs = [
-    "$google_python_dir/protobuf/{{source_file_part}}",
-  ]
-}
+      # TODO(ncarter): protoc's python generator treats descriptor.proto
+      # specially, but only when the input path is exactly
+      # "google/protobuf/descriptor.proto".  I'm not sure how to execute a rule
+      # from a different directory.  For now, use a manually-generated copy of
+      # descriptor_pb2.py.
+      "python/google/protobuf/descriptor_pb2.py",
+    ]
+    outputs = [
+      "$google_python_dir/protobuf/{{source_file_part}}",
+    ]
+  }
 
-copy("copy_google_protobuf_internal") {
-  sources = [
-    "python/google/protobuf/internal/__init__.py",
-    "python/google/protobuf/internal/_parameterized.py",
-    "python/google/protobuf/internal/api_implementation.py",
-    "python/google/protobuf/internal/containers.py",
-    "python/google/protobuf/internal/decoder.py",
-    "python/google/protobuf/internal/encoder.py",
-    "python/google/protobuf/internal/enum_type_wrapper.py",
-    "python/google/protobuf/internal/message_listener.py",
-    "python/google/protobuf/internal/python_message.py",
-    "python/google/protobuf/internal/type_checkers.py",
-    "python/google/protobuf/internal/well_known_types.py",
-    "python/google/protobuf/internal/wire_format.py",
-  ]
-  outputs = [
-    "$google_python_dir/protobuf/internal/{{source_file_part}}",
-  ]
-}
+  copy("copy_google_protobuf_internal") {
+    sources = [
+      "python/google/protobuf/internal/__init__.py",
+      "python/google/protobuf/internal/_parameterized.py",
+      "python/google/protobuf/internal/api_implementation.py",
+      "python/google/protobuf/internal/containers.py",
+      "python/google/protobuf/internal/decoder.py",
+      "python/google/protobuf/internal/encoder.py",
+      "python/google/protobuf/internal/enum_type_wrapper.py",
+      "python/google/protobuf/internal/message_listener.py",
+      "python/google/protobuf/internal/python_message.py",
+      "python/google/protobuf/internal/type_checkers.py",
+      "python/google/protobuf/internal/well_known_types.py",
+      "python/google/protobuf/internal/wire_format.py",
+    ]
+    outputs = [
+      "$google_python_dir/protobuf/internal/{{source_file_part}}",
+    ]
+  }
 
-group("py_proto") {
-  public_deps = [
-    ":copy_google",
-    ":copy_google_protobuf",
-    ":copy_google_protobuf_internal",
-    ":copy_six",
-  ]
+  group("py_proto") {
+    public_deps = [
+      ":copy_google",
+      ":copy_google_protobuf",
+      ":copy_google_protobuf_internal",
+      ":copy_six",
+    ]
 
-  # Targets that depend on this should depend on the copied data files.
-  data = get_target_outputs(":copy_google")
-  data += get_target_outputs(":copy_six")
-  data += get_target_outputs(":copy_google_protobuf")
-  data += get_target_outputs(":copy_google_protobuf_internal")
+    # Targets that depend on this should depend on the copied data files.
+    data = get_target_outputs(":copy_google")
+    data += get_target_outputs(":copy_six")
+    data += get_target_outputs(":copy_google_protobuf")
+    data += get_target_outputs(":copy_google_protobuf_internal")
+  }
 }
diff --git a/src/third_party/protobuf/proto_library.gni b/src/third_party/protobuf/proto_library.gni
index 84373f3..6dd4c13 100644
--- a/src/third_party/protobuf/proto_library.gni
+++ b/src/third_party/protobuf/proto_library.gni
@@ -101,7 +101,9 @@
   # Platform files should have gotten filtered out in the sources assignment
   # when this template was invoked. If they weren't, it was on purpose and
   # this template shouldn't re-apply the filter.
-  set_sources_assignment_filter([])
+  if (!is_starboard) {
+    set_sources_assignment_filter([])
+  }
 
   if (host_os == "win") {
     host_executable_suffix = ".exe"
@@ -224,20 +226,34 @@
   # Generate protobuf stubs.
   action(action_name) {
     visibility = [ ":$source_set_name" ]
-    script = "//tools/protoc_wrapper/protoc_wrapper.py"
+    if (is_starboard) {
+      script = "//tools/protoc_wrapper/gn_protoc_wrapper.py"
+    } else {
+      script = "//tools/protoc_wrapper/protoc_wrapper.py"
+    }
     sources = proto_sources
     outputs = get_path_info(protogens, "abspath")
     args = protos
 
-    protoc_label = "//third_party/protobuf:protoc($host_toolchain)"
-    protoc_path = get_label_info(protoc_label, "root_out_dir") + "/protoc" +
-                  host_executable_suffix
+    if (is_starboard) {
+      args += [
+        "--protoc",
+        rebase_path("//tools/protoc", root_build_dir) + host_executable_suffix,
+      ]
+    } else {
+      protoc_label = "//third_party/protobuf:protoc($host_toolchain)"
+      protoc_path = get_label_info(protoc_label, "root_out_dir") + "/protoc" +
+                    host_executable_suffix
+      args += [
+        # Wrapper should never pick a system protoc.
+        # Path should be rebased because |root_build_dir| for current toolchain
+        # may be different from |root_out_dir| of protoc built on host toolchain.
+        "--protoc",
+        "./" + rebase_path(protoc_path, root_build_dir),
+      ]
+    }
+
     args += [
-      # Wrapper should never pick a system protoc.
-      # Path should be rebased because |root_build_dir| for current toolchain
-      # may be different from |root_out_dir| of protoc built on host toolchain.
-      "--protoc",
-      "./" + rebase_path(protoc_path, root_build_dir),
       "--proto-in-dir",
       rebase_path(proto_in_dir, root_build_dir),
     ]
@@ -289,13 +305,18 @@
       }
     }
 
-    # System protoc is not used so it's necessary to build a chromium one.
-    inputs = [
-      protoc_path,
-    ]
-    deps = [
-      protoc_label,
-    ]
+    if (is_starboard) {
+      inputs = []
+      deps = []
+    } else {
+      # System protoc is not used so it's necessary to build a chromium one.
+      inputs = [
+        protoc_path,
+      ]
+      deps = [
+        protoc_label,
+      ]
+    }
 
     if (generate_with_plugin) {
       inputs += [ plugin_path ]
@@ -345,6 +366,10 @@
 
     sources = get_target_outputs(":$action_name")
 
+    if (generate_python) {
+      sources -= [ get_path_info("$py_out_dir/${proto_path}_pb2.py", "abspath") ]
+    }
+
     if (defined(invoker.extra_configs)) {
       configs += invoker.extra_configs
     }
diff --git a/src/third_party/v8/BUILD.gn b/src/third_party/v8/BUILD.gn
index 04346a6..dfc2fe4 100644
--- a/src/third_party/v8/BUILD.gn
+++ b/src/third_party/v8/BUILD.gn
@@ -1983,12 +1983,6 @@
   deps = [ ":v8_version" ]
 }
 
-v8_source_set("v8_wrappers") {
-  configs = [ ":internal_config" ]
-
-  sources = [ "src/base/platform/wrappers.h" ]
-}
-
 # This is split out to share basic headers with Torque.
 v8_header_set("v8_shared_internal_headers") {
   visibility = [ ":*" ]  # Only targets in this file can depend on this.
@@ -3835,7 +3829,6 @@
     ":v8_shared_internal_headers",
     ":v8_tracing",
     ":v8_version",
-    ":v8_wrappers",
     "src/inspector:inspector",
   ]
 
@@ -4002,8 +3995,8 @@
 
   public_deps = [
     ":v8_libbase",
-    ":v8_wrappers",
   ]
+  public_deps = [ ":v8_libbase" ]
 
   # The use of exceptions for Torque in violation of the Chromium style-guide
   # is justified by the fact that it is only used from the non-essential
@@ -4151,8 +4144,6 @@
 
   deps = [ ":v8_headers" ]
 
-  public_deps = [ ":v8_wrappers" ]
-
   data = []
 
   data_deps = []
@@ -4312,7 +4303,6 @@
     ":v8_headers",
     ":v8_libbase",
     ":v8_tracing",
-    ":v8_wrappers",
   ]
 
   if (v8_use_perfetto) {
@@ -4643,7 +4633,6 @@
       ":v8_libplatform",
       ":v8_maybe_icu",
       ":v8_tracing",
-      ":v8_wrappers",
       "//build/win:default_exe_manifest",
     ]
   }
@@ -4984,7 +4973,6 @@
     ":v8_libbase",
     ":v8_libplatform",
     ":v8_tracing",
-    ":v8_wrappers",
     "//build/win:default_exe_manifest",
   ]
 
@@ -5123,10 +5111,7 @@
 v8_source_set("multi_return_fuzzer") {
   sources = [ "test/fuzzer/multi-return.cc" ]
 
-  deps = [
-    ":fuzzer_support",
-    ":v8_wrappers",
-  ]
+  deps = [ ":fuzzer_support" ]
 
   configs = [
     ":external_config",
@@ -5140,10 +5125,7 @@
 v8_source_set("parser_fuzzer") {
   sources = [ "test/fuzzer/parser.cc" ]
 
-  deps = [
-    ":fuzzer_support",
-    ":v8_wrappers",
-  ]
+  deps = [ ":fuzzer_support" ]
 
   configs = [
     ":external_config",
@@ -5160,10 +5142,7 @@
     "test/fuzzer/regexp_builtins/mjsunit.js.h",
   ]
 
-  deps = [
-    ":fuzzer_support",
-    ":v8_wrappers",
-  ]
+  deps = [ ":fuzzer_support" ]
 
   configs = [
     ":external_config",
@@ -5177,10 +5156,7 @@
 v8_source_set("regexp_fuzzer") {
   sources = [ "test/fuzzer/regexp.cc" ]
 
-  deps = [
-    ":fuzzer_support",
-    ":v8_wrappers",
-  ]
+  deps = [ ":fuzzer_support" ]
 
   configs = [
     ":external_config",
@@ -5219,7 +5195,6 @@
   deps = [
     ":fuzzer_support",
     ":lib_wasm_fuzzer_common",
-    ":v8_wrappers",
     ":wasm_test_common",
   ]
 
@@ -5238,7 +5213,6 @@
   deps = [
     ":fuzzer_support",
     ":lib_wasm_fuzzer_common",
-    ":v8_wrappers",
     ":wasm_test_common",
   ]
 
@@ -5260,7 +5234,6 @@
   deps = [
     ":fuzzer_support",
     ":lib_wasm_fuzzer_common",
-    ":v8_wrappers",
     ":wasm_test_common",
   ]
 
@@ -5302,7 +5275,6 @@
   deps = [
     ":fuzzer_support",
     ":lib_wasm_fuzzer_common",
-    ":v8_wrappers",
     ":wasm_test_common",
   ]
 
@@ -5320,7 +5292,6 @@
 
   deps = [
     ":fuzzer_support",
-    ":v8_wrappers",
     "test/inspector:inspector_test",
   ]
 
diff --git a/src/third_party/v8/src/api/api.cc b/src/third_party/v8/src/api/api.cc
index b9ccdc0..5997bff 100644
--- a/src/third_party/v8/src/api/api.cc
+++ b/src/third_party/v8/src/api/api.cc
@@ -136,10 +136,6 @@
 #endif  // V8_OS_WIN64
 #endif  // V8_OS_WIN
 
-#if defined(V8_OS_STARBOARD)
-#include "src/poems.h"
-#endif
-
 #define TRACE_BS(...)                                     \
   do {                                                    \
     if (i::FLAG_trace_backing_store) PrintF(__VA_ARGS__); \
diff --git a/src/third_party/v8/src/ast/ast-value-factory.cc b/src/third_party/v8/src/ast/ast-value-factory.cc
index 90d1c70..b5a39b2 100644
--- a/src/third_party/v8/src/ast/ast-value-factory.cc
+++ b/src/third_party/v8/src/ast/ast-value-factory.cc
@@ -29,7 +29,6 @@
 
 #include "src/base/hashmap-entry.h"
 #include "src/base/logging.h"
-#include "src/base/platform/wrappers.h"
 #include "src/common/globals.h"
 #include "src/heap/factory-inl.h"
 #include "src/heap/local-factory-inl.h"
@@ -40,10 +39,6 @@
 #include "src/strings/string-hasher.h"
 #include "src/utils/utils-inl.h"
 
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 
diff --git a/src/third_party/v8/src/base/file-utils.cc b/src/third_party/v8/src/base/file-utils.cc
index edfa9c1..6e1c492 100644
--- a/src/third_party/v8/src/base/file-utils.cc
+++ b/src/third_party/v8/src/base/file-utils.cc
@@ -8,11 +8,6 @@
 #include <string.h>
 
 #include "src/base/platform/platform.h"
-#include "src/base/platform/wrappers.h"
-
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif  // V8_OS_STARBOARD
 
 namespace v8 {
 namespace base {
diff --git a/src/third_party/v8/src/base/hashmap.h b/src/third_party/v8/src/base/hashmap.h
index 3fd9bcc..777dba3 100644
--- a/src/third_party/v8/src/base/hashmap.h
+++ b/src/third_party/v8/src/base/hashmap.h
@@ -16,10 +16,6 @@
 #include "src/base/logging.h"
 #include "src/base/platform/wrappers.h"
 
-#if defined(V8_OS_STARBOARD)
-#include "starboard/memory.h"
-#endif
-
 namespace v8 {
 namespace base {
 
diff --git a/src/third_party/v8/src/base/logging.cc b/src/third_party/v8/src/base/logging.cc
index 59c6aba..9e1cf59 100644
--- a/src/third_party/v8/src/base/logging.cc
+++ b/src/third_party/v8/src/base/logging.cc
@@ -12,10 +12,6 @@
 #include "src/base/debug/stack_trace.h"
 #include "src/base/platform/platform.h"
 
-#if defined(V8_OS_STARBOARD)
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace base {
 
diff --git a/src/third_party/v8/src/base/macros.h b/src/third_party/v8/src/base/macros.h
index 1fa3227..515a9e3 100644
--- a/src/third_party/v8/src/base/macros.h
+++ b/src/third_party/v8/src/base/macros.h
@@ -10,7 +10,6 @@
 
 #include "src/base/compiler-specific.h"
 #include "src/base/logging.h"
-#include "src/base/platform/wrappers.h"
 
 // No-op macro which is used to work around MSVC's funky VA_ARGS support.
 #define EXPAND(x) x
diff --git a/src/third_party/v8/src/base/memory.h b/src/third_party/v8/src/base/memory.h
index db6a9ea..e2676a8 100644
--- a/src/third_party/v8/src/base/memory.h
+++ b/src/third_party/v8/src/base/memory.h
@@ -6,7 +6,6 @@
 #define V8_BASE_MEMORY_H_
 
 #include "src/base/macros.h"
-#include "src/base/platform/wrappers.h"
 
 namespace v8 {
 namespace base {
diff --git a/src/third_party/v8/src/base/page-allocator.cc b/src/third_party/v8/src/base/page-allocator.cc
index 2defe619..9f48ee7 100644
--- a/src/third_party/v8/src/base/page-allocator.cc
+++ b/src/third_party/v8/src/base/page-allocator.cc
@@ -5,7 +5,6 @@
 #include "src/base/page-allocator.h"
 
 #include "src/base/platform/platform.h"
-#include "src/base/platform/wrappers.h"
 
 #if V8_OS_MACOSX
 #include <sys/mman.h>  // For MAP_JIT.
diff --git a/src/third_party/v8/src/base/platform/wrappers.h b/src/third_party/v8/src/base/platform/wrappers.h
index a67f2bf..5de31b4 100644
--- a/src/third_party/v8/src/base/platform/wrappers.h
+++ b/src/third_party/v8/src/base/platform/wrappers.h
@@ -14,6 +14,7 @@
 
 #if defined(V8_OS_STARBOARD)
 #include "starboard/memory.h"
+#include "starboard/string.h"
 #endif
 
 namespace v8 {
@@ -33,9 +34,7 @@
 
 inline void* Calloc(size_t count, size_t size) { return calloc(count, size); }
 
-inline void* Memcpy(void* dest, const void* source, size_t count) {
-  return memcpy(dest, source, count);
-}
+inline char* Strdup(const char* source) { return strdup(source); }
 
 inline FILE* Fopen(const char* filename, const char* mode) {
   return fopen(filename, mode);
@@ -57,9 +56,7 @@
   return SbMemoryCalloc(count, size);
 }
 
-inline void* Memcpy(void* dest, const void* source, size_t count) {
-  return memcpy(dest, source, count);
-}
+inline char* Strdup(const char* source) { return SbStringDuplicate(source); }
 
 inline FILE* Fopen(const char* filename, const char* mode) { return NULL; }
 
diff --git a/src/third_party/v8/src/builtins/builtins-array-gen.cc b/src/third_party/v8/src/builtins/builtins-array-gen.cc
index 2d9cd5a..04fb751 100644
--- a/src/third_party/v8/src/builtins/builtins-array-gen.cc
+++ b/src/third_party/v8/src/builtins/builtins-array-gen.cc
@@ -3,8 +3,6 @@
 // found in the LICENSE file.
 
 #include "src/builtins/builtins-array-gen.h"
-#include "src/objects/torque-defined-classes.h"
-#include "src/objects/torque-defined-classes-inl.h"
 
 #include "src/builtins/builtins-iterator-gen.h"
 #include "src/builtins/builtins-string-gen.h"
@@ -17,7 +15,6 @@
 #include "src/objects/allocation-site-inl.h"
 #include "src/objects/arguments-inl.h"
 #include "src/objects/property-cell.h"
-
 #include "src/objects/torque-defined-classes.h"
 #include "src/objects/torque-defined-classes-inl.h"
 
diff --git a/src/third_party/v8/src/builtins/builtins-trace.cc b/src/third_party/v8/src/builtins/builtins-trace.cc
index 96a60e0..e98b38d 100644
--- a/src/third_party/v8/src/builtins/builtins-trace.cc
+++ b/src/third_party/v8/src/builtins/builtins-trace.cc
@@ -12,7 +12,6 @@
 
 #if defined(V8_USE_PERFETTO)
 #include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
-#include "src/base/platform/wrappers.h"
 #endif
 
 namespace v8 {
@@ -41,8 +40,8 @@
         // strings, the bytes we get from SeqOneByteString are not. buf_ is
         // guaranteed to be null terminated.
         DisallowHeapAllocation no_gc;
-        memcpy(
-            buf_, Handle<SeqOneByteString>::cast(string)->GetChars(no_gc), len);
+        memcpy(buf_, Handle<SeqOneByteString>::cast(string)->GetChars(no_gc),
+               len);
       }
     } else {
       Local<v8::String> local = Utils::ToLocal(string);
diff --git a/src/third_party/v8/src/codegen/arm64/assembler-arm64.h b/src/third_party/v8/src/codegen/arm64/assembler-arm64.h
index e685f88..f787bad 100644
--- a/src/third_party/v8/src/codegen/arm64/assembler-arm64.h
+++ b/src/third_party/v8/src/codegen/arm64/assembler-arm64.h
@@ -27,7 +27,6 @@
 #endif
 
 #if defined(V8_OS_WIN)
-#include "src/base/platform/wrappers.h"
 #include "src/diagnostics/unwinding-info-win64.h"
 #endif  // V8_OS_WIN
 
diff --git a/src/third_party/v8/src/codegen/arm64/macro-assembler-arm64.cc b/src/third_party/v8/src/codegen/arm64/macro-assembler-arm64.cc
index 3aac779..6924248 100644
--- a/src/third_party/v8/src/codegen/arm64/macro-assembler-arm64.cc
+++ b/src/third_party/v8/src/codegen/arm64/macro-assembler-arm64.cc
@@ -27,7 +27,6 @@
 // Satisfy cpplint check, but don't include platform-specific header. It is
 // included recursively via macro-assembler.h.
 #if 0
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/arm64/macro-assembler-arm64.h"
 #endif
 
diff --git a/src/third_party/v8/src/codegen/external-reference-table.cc b/src/third_party/v8/src/codegen/external-reference-table.cc
index 2741bd8..fb7d503 100644
--- a/src/third_party/v8/src/codegen/external-reference-table.cc
+++ b/src/third_party/v8/src/codegen/external-reference-table.cc
@@ -12,7 +12,6 @@
 #if defined(DEBUG) && defined(V8_OS_LINUX) && !defined(V8_OS_ANDROID)
 #define SYMBOLIZE_FUNCTION
 #include <execinfo.h>
-
 #include <vector>
 
 #include "src/base/platform/wrappers.h"
diff --git a/src/third_party/v8/src/codegen/external-reference.cc b/src/third_party/v8/src/codegen/external-reference.cc
index ecb5dbc..499e5c5 100644
--- a/src/third_party/v8/src/codegen/external-reference.cc
+++ b/src/third_party/v8/src/codegen/external-reference.cc
@@ -33,7 +33,6 @@
 #include "src/wasm/wasm-external-refs.h"
 
 #ifdef V8_INTL_SUPPORT
-#include "src/base/platform/wrappers.h"
 #include "src/objects/intl-objects.h"
 #endif  // V8_INTL_SUPPORT
 
diff --git a/src/third_party/v8/src/codegen/optimized-compilation-info.cc b/src/third_party/v8/src/codegen/optimized-compilation-info.cc
index 98bfcd5..bf45a5f 100644
--- a/src/third_party/v8/src/codegen/optimized-compilation-info.cc
+++ b/src/third_party/v8/src/codegen/optimized-compilation-info.cc
@@ -5,7 +5,6 @@
 #include "src/codegen/optimized-compilation-info.h"
 
 #include "src/api/api.h"
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/source-position.h"
 #include "src/debug/debug.h"
 #include "src/execution/isolate.h"
diff --git a/src/third_party/v8/src/codegen/x64/assembler-x64.cc b/src/third_party/v8/src/codegen/x64/assembler-x64.cc
index ac4b5e3..014a2d5 100644
--- a/src/third_party/v8/src/codegen/x64/assembler-x64.cc
+++ b/src/third_party/v8/src/codegen/x64/assembler-x64.cc
@@ -17,7 +17,6 @@
 
 #include "src/base/bits.h"
 #include "src/base/cpu.h"
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/assembler-inl.h"
 #include "src/codegen/macro-assembler.h"
 #include "src/codegen/string-constants.h"
@@ -408,7 +407,6 @@
   DCHECK(options().collect_win64_unwind_info);
   DCHECK_NOT_NULL(xdata_encoder_);
   return win64_unwindinfo::BuiltinUnwindInfo();
-  // return xdata_encoder_->unwinding_info();
 }
 #endif
 
diff --git a/src/third_party/v8/src/common/globals.h b/src/third_party/v8/src/common/globals.h
index b2d1df5..988ab10 100644
--- a/src/third_party/v8/src/common/globals.h
+++ b/src/third_party/v8/src/common/globals.h
@@ -1765,10 +1765,10 @@
 struct RelaxedLoadTag {};
 struct ReleaseStoreTag {};
 struct RelaxedStoreTag {};
-static constexpr AcquireLoadTag kAcquireLoad{};
-static constexpr RelaxedLoadTag kRelaxedLoad{};
-static constexpr ReleaseStoreTag kReleaseStore{};
-static constexpr RelaxedStoreTag kRelaxedStore{};
+static constexpr AcquireLoadTag kAcquireLoad;
+static constexpr RelaxedLoadTag kRelaxedLoad;
+static constexpr ReleaseStoreTag kReleaseStore;
+static constexpr RelaxedStoreTag kRelaxedStore;
 
 }  // namespace v8
 
diff --git a/src/third_party/v8/src/compiler/backend/arm/instruction-selector-arm.cc b/src/third_party/v8/src/compiler/backend/arm/instruction-selector-arm.cc
index f310349..248f765 100644
--- a/src/third_party/v8/src/compiler/backend/arm/instruction-selector-arm.cc
+++ b/src/third_party/v8/src/compiler/backend/arm/instruction-selector-arm.cc
@@ -5,7 +5,6 @@
 #include "src/base/bits.h"
 #include "src/base/enum-set.h"
 #include "src/base/iterator.h"
-#include "src/base/platform/wrappers.h"
 #include "src/compiler/backend/instruction-selector-impl.h"
 #include "src/compiler/node-matchers.h"
 #include "src/compiler/node-properties.h"
diff --git a/src/third_party/v8/src/compiler/backend/arm64/instruction-selector-arm64.cc b/src/third_party/v8/src/compiler/backend/arm64/instruction-selector-arm64.cc
index e11b9fd..584cfb6 100644
--- a/src/third_party/v8/src/compiler/backend/arm64/instruction-selector-arm64.cc
+++ b/src/third_party/v8/src/compiler/backend/arm64/instruction-selector-arm64.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include "src/base/bits.h"
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/assembler-inl.h"
 #include "src/compiler/backend/instruction-selector-impl.h"
 #include "src/compiler/node-matchers.h"
diff --git a/src/third_party/v8/src/compiler/backend/ia32/instruction-selector-ia32.cc b/src/third_party/v8/src/compiler/backend/ia32/instruction-selector-ia32.cc
index d8f2fa6..c16584a 100644
--- a/src/third_party/v8/src/compiler/backend/ia32/instruction-selector-ia32.cc
+++ b/src/third_party/v8/src/compiler/backend/ia32/instruction-selector-ia32.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include "src/base/iterator.h"
-#include "src/base/platform/wrappers.h"
 #include "src/compiler/backend/instruction-selector-impl.h"
 #include "src/compiler/node-matchers.h"
 #include "src/compiler/node-properties.h"
diff --git a/src/third_party/v8/src/compiler/backend/instruction-selector.cc b/src/third_party/v8/src/compiler/backend/instruction-selector.cc
index 706a9b2..b62cc83 100644
--- a/src/third_party/v8/src/compiler/backend/instruction-selector.cc
+++ b/src/third_party/v8/src/compiler/backend/instruction-selector.cc
@@ -7,7 +7,6 @@
 #include <limits>
 
 #include "src/base/iterator.h"
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/assembler-inl.h"
 #include "src/codegen/tick-counter.h"
 #include "src/compiler/backend/instruction-selector-impl.h"
@@ -3317,8 +3316,7 @@
 void InstructionSelector::CanonicalizeShuffle(Node* node, uint8_t* shuffle,
                                               bool* is_swizzle) {
   // Get raw shuffle indices.
-  memcpy(shuffle, S128ImmediateParameterOf(node->op()).data(),
-               kSimd128Size);
+  memcpy(shuffle, S128ImmediateParameterOf(node->op()).data(), kSimd128Size);
   bool needs_swap;
   bool inputs_equal = GetVirtualRegister(node->InputAt(0)) ==
                       GetVirtualRegister(node->InputAt(1));
diff --git a/src/third_party/v8/src/compiler/backend/mips64/instruction-selector-mips64.cc b/src/third_party/v8/src/compiler/backend/mips64/instruction-selector-mips64.cc
index a4e8b1f..216b83c 100644
--- a/src/third_party/v8/src/compiler/backend/mips64/instruction-selector-mips64.cc
+++ b/src/third_party/v8/src/compiler/backend/mips64/instruction-selector-mips64.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include "src/base/bits.h"
-#include "src/base/platform/wrappers.h"
 #include "src/compiler/backend/instruction-selector-impl.h"
 #include "src/compiler/node-matchers.h"
 #include "src/compiler/node-properties.h"
diff --git a/src/third_party/v8/src/compiler/backend/s390/instruction-selector-s390.cc b/src/third_party/v8/src/compiler/backend/s390/instruction-selector-s390.cc
index c3c0cff..124193f 100644
--- a/src/third_party/v8/src/compiler/backend/s390/instruction-selector-s390.cc
+++ b/src/third_party/v8/src/compiler/backend/s390/instruction-selector-s390.cc
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "src/base/platform/wrappers.h"
 #include "src/compiler/backend/instruction-selector-impl.h"
 #include "src/compiler/node-matchers.h"
 #include "src/compiler/node-properties.h"
diff --git a/src/third_party/v8/src/compiler/backend/x64/instruction-selector-x64.cc b/src/third_party/v8/src/compiler/backend/x64/instruction-selector-x64.cc
index 95461247..7a8a2b4 100644
--- a/src/third_party/v8/src/compiler/backend/x64/instruction-selector-x64.cc
+++ b/src/third_party/v8/src/compiler/backend/x64/instruction-selector-x64.cc
@@ -7,7 +7,6 @@
 #include "src/base/iterator.h"
 #include "src/base/logging.h"
 #include "src/base/overflowing-math.h"
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/machine-type.h"
 #include "src/compiler/backend/instruction-selector-impl.h"
 #include "src/compiler/machine-operator.h"
diff --git a/src/third_party/v8/src/compiler/bytecode-graph-builder.cc b/src/third_party/v8/src/compiler/bytecode-graph-builder.cc
index 12abe01..14d014b 100644
--- a/src/third_party/v8/src/compiler/bytecode-graph-builder.cc
+++ b/src/third_party/v8/src/compiler/bytecode-graph-builder.cc
@@ -5,7 +5,6 @@
 #include "src/compiler/bytecode-graph-builder.h"
 
 #include "src/ast/ast.h"
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/source-position-table.h"
 #include "src/codegen/tick-counter.h"
 #include "src/compiler/access-builder.h"
@@ -4366,8 +4365,7 @@
     if (has_effect) ++input_count_with_deps;
     Node** buffer = EnsureInputBufferSize(input_count_with_deps);
     if (value_input_count > 0) {
-      memcpy(buffer, value_inputs,
-                   kSystemPointerSize * value_input_count);
+      memcpy(buffer, value_inputs, kSystemPointerSize * value_input_count);
     }
     Node** current_input = buffer + value_input_count;
     if (has_context) {
diff --git a/src/third_party/v8/src/compiler/code-assembler.cc b/src/third_party/v8/src/compiler/code-assembler.cc
index e6b29f9..87edc79 100644
--- a/src/third_party/v8/src/compiler/code-assembler.cc
+++ b/src/third_party/v8/src/compiler/code-assembler.cc
@@ -26,10 +26,6 @@
 #include "src/utils/memcopy.h"
 #include "src/zone/zone.h"
 
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 
diff --git a/src/third_party/v8/src/compiler/node.cc b/src/third_party/v8/src/compiler/node.cc
index a107bb7..8525fa0 100644
--- a/src/third_party/v8/src/compiler/node.cc
+++ b/src/third_party/v8/src/compiler/node.cc
@@ -4,10 +4,6 @@
 
 #include "src/compiler/node.h"
 
-#if defined(V8_OS_STARBOARD)
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 namespace compiler {
diff --git a/src/third_party/v8/src/compiler/pipeline.cc b/src/third_party/v8/src/compiler/pipeline.cc
index e0b59c9..3caa6a6 100644
--- a/src/third_party/v8/src/compiler/pipeline.cc
+++ b/src/third_party/v8/src/compiler/pipeline.cc
@@ -96,10 +96,6 @@
 #include "src/wasm/function-compiler.h"
 #include "src/wasm/wasm-engine.h"
 
-#if V8_OS_STARBOARD
-#include "starboard/common/log.h"
-#endif  // V8_OS_STARBOARD
-
 namespace v8 {
 namespace internal {
 namespace compiler {
diff --git a/src/third_party/v8/src/compiler/simd-scalar-lowering.cc b/src/third_party/v8/src/compiler/simd-scalar-lowering.cc
index a1d1128e..06673c3 100644
--- a/src/third_party/v8/src/compiler/simd-scalar-lowering.cc
+++ b/src/third_party/v8/src/compiler/simd-scalar-lowering.cc
@@ -4,7 +4,6 @@
 
 #include "src/compiler/simd-scalar-lowering.h"
 
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/machine-type.h"
 #include "src/common/globals.h"
 #include "src/compiler/diamond.h"
diff --git a/src/third_party/v8/src/compiler/wasm-compiler.cc b/src/third_party/v8/src/compiler/wasm-compiler.cc
index c476d15..fa08578 100644
--- a/src/third_party/v8/src/compiler/wasm-compiler.cc
+++ b/src/third_party/v8/src/compiler/wasm-compiler.cc
@@ -9,7 +9,6 @@
 #include "src/base/optional.h"
 #include "src/base/platform/elapsed-timer.h"
 #include "src/base/platform/platform.h"
-#include "src/base/platform/wrappers.h"
 #include "src/base/small-vector.h"
 #include "src/base/v8-fallthrough.h"
 #include "src/codegen/assembler-inl.h"
diff --git a/src/third_party/v8/src/d8.isolate b/src/third_party/v8/src/d8.isolate
deleted file mode 100644
index 1c9bd9e..0000000
--- a/src/third_party/v8/src/d8.isolate
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2015 the V8 project 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': {
-    'command': [
-      '<(PRODUCT_DIR)/d8<(EXECUTABLE_SUFFIX)',
-    ],
-    'files': [
-      '<(PRODUCT_DIR)/d8<(EXECUTABLE_SUFFIX)',
-    ],
-  },
-  'includes': [
-    'base.isolate',
-  ],
-}
\ No newline at end of file
diff --git a/src/third_party/v8/src/d8.js b/src/third_party/v8/src/d8.js
deleted file mode 100644
index 287571b..0000000
--- a/src/third_party/v8/src/d8.js
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2008 the V8 project authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-(function() {
-"use strict";
-
-// A more universal stringify that supports more types than JSON.
-// Used by the d8 shell to output results.
-var stringifyDepthLimit = 4;  // To avoid crashing on cyclic objects
-
-// Hacky solution to circumvent forcing --allow-natives-syntax for d8
-function isProxy(o) { return false };
-function JSProxyGetTarget(proxy) { };
-function JSProxyGetHandler(proxy) { };
-
-try {
-  isProxy = Function(['object'], 'return %_IsJSProxy(object)');
-  JSProxyGetTarget = Function(['proxy'],
-    'return %JSProxyGetTarget(proxy)');
-  JSProxyGetHandler = Function(['proxy'],
-    'return %JSProxyGetHandler(proxy)');
-} catch(e) {};
-
-
-function Stringify(x, depth) {
-  if (depth === undefined)
-    depth = stringifyDepthLimit;
-  else if (depth === 0)
-    return "...";
-  if (isProxy(x)) {
-    return StringifyProxy(x, depth);
-  }
-  switch (typeof x) {
-    case "undefined":
-      return "undefined";
-    case "boolean":
-    case "number":
-    case "function":
-    case "symbol":
-      return x.toString();
-    case "string":
-      return "\"" + x.toString() + "\"";
-    case "bigint":
-      // TODO(neis): Use x.toString() once we have it.
-      return String(x) + "n";
-    case "object":
-      if (IS_NULL(x)) return "null";
-      if (x.constructor && x.constructor.name === "Array") {
-        var elems = [];
-        for (var i = 0; i < x.length; ++i) {
-          elems.push(
-            {}.hasOwnProperty.call(x, i) ? Stringify(x[i], depth - 1) : "");
-        }
-        return "[" + elems.join(", ") + "]";
-      }
-      try {
-        var string = String(x);
-        if (string && string !== "[object Object]") return string;
-      } catch(e) {}
-      var props = [];
-      var names = Object.getOwnPropertyNames(x);
-      names = names.concat(Object.getOwnPropertySymbols(x));
-      for (var i in names) {
-        var name = names[i];
-        var desc = Object.getOwnPropertyDescriptor(x, name);
-        if (IS_UNDEFINED(desc)) continue;
-        if (IS_SYMBOL(name)) name = "[" + Stringify(name) + "]";
-        if ("value" in desc) {
-          props.push(name + ": " + Stringify(desc.value, depth - 1));
-        }
-        if (desc.get) {
-          var getter = Stringify(desc.get);
-          props.push("get " + name + getter.slice(getter.indexOf('(')));
-        }
-        if (desc.set) {
-          var setter = Stringify(desc.set);
-          props.push("set " + name + setter.slice(setter.indexOf('(')));
-        }
-      }
-      return "{" + props.join(", ") + "}";
-    default:
-      return "[crazy non-standard value]";
-  }
-}
-
-function StringifyProxy(proxy, depth) {
-  var proxy_type = typeof proxy;
-  var info_object = {
-    target: JSProxyGetTarget(proxy),
-    handler: JSProxyGetHandler(proxy)
-  }
-  return '[' + proxy_type + ' Proxy ' + Stringify(info_object, depth-1) + ']';
-}
-
-return Stringify;
-})();
diff --git a/src/third_party/v8/src/d8/d8-posix.cc b/src/third_party/v8/src/d8/d8-posix.cc
index a9a9fcd..c7313b4 100644
--- a/src/third_party/v8/src/d8/d8-posix.cc
+++ b/src/third_party/v8/src/d8/d8-posix.cc
@@ -16,7 +16,6 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
-#include "src/base/platform/wrappers.h"
 #include "src/d8/d8.h"
 
 namespace v8 {
diff --git a/src/third_party/v8/src/debug/debug-coverage.cc b/src/third_party/v8/src/debug/debug-coverage.cc
index 8bc964a..bee4122 100644
--- a/src/third_party/v8/src/debug/debug-coverage.cc
+++ b/src/third_party/v8/src/debug/debug-coverage.cc
@@ -14,10 +14,6 @@
 #include "src/objects/debug-objects-inl.h"
 #include "src/objects/objects.h"
 
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 
diff --git a/src/third_party/v8/src/debug/wasm/gdb-server/transport.cc b/src/third_party/v8/src/debug/wasm/gdb-server/transport.cc
index 9806a72..f1aed96 100644
--- a/src/third_party/v8/src/debug/wasm/gdb-server/transport.cc
+++ b/src/third_party/v8/src/debug/wasm/gdb-server/transport.cc
@@ -3,11 +3,8 @@
 // found in the LICENSE file.
 
 #include "src/debug/wasm/gdb-server/transport.h"
-
 #include <fcntl.h>
 
-#include "src/base/platform/wrappers.h"
-
 #ifndef SD_BOTH
 #define SD_BOTH 2
 #endif
diff --git a/src/third_party/v8/src/debug/wasm/gdb-server/wasm-module-debug.cc b/src/third_party/v8/src/debug/wasm/gdb-server/wasm-module-debug.cc
index 50b9d8d..f0b77bc 100644
--- a/src/third_party/v8/src/debug/wasm/gdb-server/wasm-module-debug.cc
+++ b/src/third_party/v8/src/debug/wasm/gdb-server/wasm-module-debug.cc
@@ -6,7 +6,6 @@
 
 #include "src/api/api-inl.h"
 #include "src/api/api.h"
-#include "src/base/platform/wrappers.h"
 #include "src/execution/frames-inl.h"
 #include "src/execution/frames.h"
 #include "src/objects/script.h"
diff --git a/src/third_party/v8/src/deoptimizer/deoptimizer.cc b/src/third_party/v8/src/deoptimizer/deoptimizer.cc
index 5057029..4ae51bf 100644
--- a/src/third_party/v8/src/deoptimizer/deoptimizer.cc
+++ b/src/third_party/v8/src/deoptimizer/deoptimizer.cc
@@ -33,10 +33,6 @@
 // Has to be the last include (doesn't have include guards)
 #include "src/objects/object-macros.h"
 
-#if defined(V8_OS_STARBOARD)
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 
diff --git a/src/third_party/v8/src/diagnostics/objects-printer.cc b/src/third_party/v8/src/diagnostics/objects-printer.cc
index 5b5413c..8ee9685 100644
--- a/src/third_party/v8/src/diagnostics/objects-printer.cc
+++ b/src/third_party/v8/src/diagnostics/objects-printer.cc
@@ -22,11 +22,6 @@
 #include "src/wasm/wasm-engine.h"
 #include "src/wasm/wasm-objects-inl.h"
 
-#if defined(STARBOARD)
-#include "starboard/common/log.h"
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 
@@ -2683,7 +2678,7 @@
     void* object) {
   i::Object o(GetObjectFromRaw(object));
   if (!o.IsLayoutDescriptor()) {
-    printf("Please provide a layout descriptor\n");
+    i::PrintF("Please provide a layout descriptor\n");
   } else {
     i::LayoutDescriptor::cast(o).Print();
   }
@@ -2697,7 +2692,7 @@
 V8_EXPORT_PRIVATE extern void _v8_internal_Print_TransitionTree(void* object) {
   i::Object o(GetObjectFromRaw(object));
   if (!o.IsMap()) {
-    printf("Please provide a valid Map\n");
+    i::PrintF("Please provide a valid Map\n");
   } else {
 #if defined(DEBUG) || defined(OBJECT_PRINT)
     i::DisallowHeapAllocation no_gc;
diff --git a/src/third_party/v8/src/diagnostics/unwinding-info-win64.cc b/src/third_party/v8/src/diagnostics/unwinding-info-win64.cc
index 695e5ef..361935b 100644
--- a/src/third_party/v8/src/diagnostics/unwinding-info-win64.cc
+++ b/src/third_party/v8/src/diagnostics/unwinding-info-win64.cc
@@ -10,7 +10,6 @@
 #if defined(V8_OS_WIN_X64)
 #include "src/codegen/x64/assembler-x64.h"
 #elif defined(V8_OS_WIN_ARM64)
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/arm64/assembler-arm64-inl.h"
 #include "src/codegen/arm64/macro-assembler-arm64-inl.h"
 #else
@@ -171,8 +170,7 @@
   masm.movq(rax, reinterpret_cast<uint64_t>(&CRASH_HANDLER_FUNCTION_NAME));
   masm.jmp(rax);
   DCHECK_LE(masm.instruction_size(), sizeof(record->exception_thunk));
-  memcpy(&record->exception_thunk[0], masm.buffer_start(),
-               masm.instruction_size());
+  memcpy(&record->exception_thunk[0], masm.buffer_start(), masm.instruction_size());
 }
 
 #elif defined(V8_OS_WIN_ARM64)
@@ -449,8 +447,7 @@
            Operand(reinterpret_cast<uint64_t>(&CRASH_HANDLER_FUNCTION_NAME)));
   masm.Br(x16);
   DCHECK_LE(masm.instruction_size(), sizeof(record->exception_thunk));
-  memcpy(&record->exception_thunk[0], masm.buffer_start(),
-               masm.instruction_size());
+  memcpy(&record->exception_thunk[0], masm.buffer_start(), masm.instruction_size());
 }
 
 #endif  // V8_OS_WIN_X64
diff --git a/src/third_party/v8/src/execution/arm64/simulator-arm64.h b/src/third_party/v8/src/execution/arm64/simulator-arm64.h
index 93fb59a..ee6d634 100644
--- a/src/third_party/v8/src/execution/arm64/simulator-arm64.h
+++ b/src/third_party/v8/src/execution/arm64/simulator-arm64.h
@@ -11,11 +11,9 @@
 #if defined(USE_SIMULATOR)
 
 #include <stdarg.h>
-
 #include <vector>
 
 #include "src/base/compiler-specific.h"
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/arm64/assembler-arm64.h"
 #include "src/codegen/arm64/decoder-arm64.h"
 #include "src/codegen/assembler.h"
@@ -340,8 +338,7 @@
     DCHECK_GE(lane, 0);
     DCHECK_LE(sizeof(new_value) + (lane * sizeof(new_value)),
               static_cast<unsigned>(kSizeInBytes));
-    memcpy(&value_[lane * sizeof(new_value)], &new_value,
-                 sizeof(new_value));
+    memcpy(&value_[lane * sizeof(new_value)], &new_value, sizeof(new_value));
     NotifyRegisterWrite();
   }
 
diff --git a/src/third_party/v8/src/execution/frames.cc b/src/third_party/v8/src/execution/frames.cc
index ea964e4..3288f53 100644
--- a/src/third_party/v8/src/execution/frames.cc
+++ b/src/third_party/v8/src/execution/frames.cc
@@ -8,7 +8,6 @@
 #include <sstream>
 
 #include "src/base/bits.h"
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/interface-descriptors.h"
 #include "src/codegen/macro-assembler.h"
 #include "src/codegen/register-configuration.h"
diff --git a/src/third_party/v8/src/execution/isolate.cc b/src/third_party/v8/src/execution/isolate.cc
index fd12f76..ff3886f 100644
--- a/src/third_party/v8/src/execution/isolate.cc
+++ b/src/third_party/v8/src/execution/isolate.cc
@@ -98,10 +98,6 @@
 #include "unicode/uobject.h"
 #endif  // V8_INTL_SUPPORT
 
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif
-
 #if defined(V8_OS_WIN64)
 #include "src/diagnostics/unwinding-info-win64.h"
 #endif  // V8_OS_WIN64
diff --git a/src/third_party/v8/src/handles/maybe-handles.h b/src/third_party/v8/src/handles/maybe-handles.h
index f18a7a9..15397ef 100644
--- a/src/third_party/v8/src/handles/maybe-handles.h
+++ b/src/third_party/v8/src/handles/maybe-handles.h
@@ -14,7 +14,7 @@
 
 struct NullMaybeHandleType {};
 
-constexpr NullMaybeHandleType kNullMaybeHandle{};
+constexpr NullMaybeHandleType kNullMaybeHandle;
 
 // ----------------------------------------------------------------------------
 // A Handle can be converted into a MaybeHandle. Converting a MaybeHandle
diff --git a/src/third_party/v8/src/init/v8.cc b/src/third_party/v8/src/init/v8.cc
index 688ca23..1bc54c3 100644
--- a/src/third_party/v8/src/init/v8.cc
+++ b/src/third_party/v8/src/init/v8.cc
@@ -27,10 +27,6 @@
 #include "src/tracing/tracing-category-observer.h"
 #include "src/wasm/wasm-engine.h"
 
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 
diff --git a/src/third_party/v8/src/inspector/v8-string-conversions.h b/src/third_party/v8/src/inspector/v8-string-conversions.h
index 285667e..c1d69c1 100644
--- a/src/third_party/v8/src/inspector/v8-string-conversions.h
+++ b/src/third_party/v8/src/inspector/v8-string-conversions.h
@@ -7,11 +7,6 @@
 
 #include <string>
 
-// The v8config.h above is needed to turn on V8_OS_STARBOARD
-#if defined(V8_OS_STARBOARD)
-#include "starboard/types.h"
-#endif
-
 // Conversion routines between UT8 and UTF16, used by string-16.{h,cc}. You may
 // want to use string-16.h directly rather than these.
 namespace v8_inspector {
diff --git a/src/third_party/v8/src/libplatform/tracing/tracing-controller.cc b/src/third_party/v8/src/libplatform/tracing/tracing-controller.cc
index 923ee68..115232e 100644
--- a/src/third_party/v8/src/libplatform/tracing/tracing-controller.cc
+++ b/src/third_party/v8/src/libplatform/tracing/tracing-controller.cc
@@ -11,6 +11,7 @@
 #include "src/base/atomicops.h"
 #include "src/base/platform/mutex.h"
 #include "src/base/platform/time.h"
+#include "src/base/platform/wrappers.h"
 
 #if V8_OS_STARBOARD
 #include "src/poems.h"
@@ -329,7 +330,7 @@
     // Don't hold on to the category_group pointer, so that we can create
     // category groups with strings not known at compile time (this is
     // required by SetWatchEvent).
-    const char* new_group = strdup(category_group);
+    const char* new_group = base::Strdup(category_group);
     g_category_groups[category_index] = new_group;
     DCHECK(!g_category_group_enabled[category_index]);
     // Note that if both included and excluded patterns in the
diff --git a/src/third_party/v8/src/logging/log-utils.cc b/src/third_party/v8/src/logging/log-utils.cc
index 7a26e4b..e017fe8 100644
--- a/src/third_party/v8/src/logging/log-utils.cc
+++ b/src/third_party/v8/src/logging/log-utils.cc
@@ -16,10 +16,6 @@
 #include "src/utils/vector.h"
 #include "src/utils/version.h"
 
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 
diff --git a/src/third_party/v8/src/numbers/conversions-inl.h b/src/third_party/v8/src/numbers/conversions-inl.h
index 43421e3..08502d4 100644
--- a/src/third_party/v8/src/numbers/conversions-inl.h
+++ b/src/third_party/v8/src/numbers/conversions-inl.h
@@ -16,7 +16,6 @@
 
 #include "src/base/bits.h"
 #include "src/base/platform/platform.h"
-#include "src/base/platform/wrappers.h"
 #include "src/numbers/conversions.h"
 #include "src/numbers/double.h"
 #include "src/objects/heap-number-inl.h"
diff --git a/src/third_party/v8/src/numbers/conversions.cc b/src/third_party/v8/src/numbers/conversions.cc
index f83f19e..1423bd6 100644
--- a/src/third_party/v8/src/numbers/conversions.cc
+++ b/src/third_party/v8/src/numbers/conversions.cc
@@ -9,7 +9,6 @@
 
 #include <cmath>
 
-#include "src/base/platform/wrappers.h"
 #include "src/common/assert-scope.h"
 #include "src/handles/handles.h"
 #include "src/heap/factory.h"
@@ -1348,8 +1347,7 @@
   DCHECK_LE(0, integer_cursor);
   // Allocate new string as return value.
   char* result = NewArray<char>(fraction_cursor - integer_cursor);
-  memcpy(result, buffer + integer_cursor,
-               fraction_cursor - integer_cursor);
+  memcpy(result, buffer + integer_cursor, fraction_cursor - integer_cursor);
   return result;
 }
 
diff --git a/src/third_party/v8/src/objects/backing-store.cc b/src/third_party/v8/src/objects/backing-store.cc
index 53e3837..9f755f5 100644
--- a/src/third_party/v8/src/objects/backing-store.cc
+++ b/src/third_party/v8/src/objects/backing-store.cc
@@ -6,7 +6,6 @@
 
 #include <cstring>
 
-#include "src/base/platform/wrappers.h"
 #include "src/execution/isolate.h"
 #include "src/handles/global-handles.h"
 #include "src/logging/counters.h"
@@ -465,8 +464,7 @@
     // If the allocation was successful, then the new buffer must be at least
     // as big as the old one.
     DCHECK_GE(new_pages * wasm::kWasmPageSize, byte_length_);
-    memcpy(new_backing_store->buffer_start(), buffer_start_,
-                 byte_length_);
+    memcpy(new_backing_store->buffer_start(), buffer_start_, byte_length_);
   }
 
   return new_backing_store;
diff --git a/src/third_party/v8/src/objects/bigint.cc b/src/third_party/v8/src/objects/bigint.cc
index f2cd964..129bb14 100644
--- a/src/third_party/v8/src/objects/bigint.cc
+++ b/src/third_party/v8/src/objects/bigint.cc
@@ -240,7 +240,6 @@
 OBJECT_CONSTRUCTORS_IMPL(MutableBigInt, FreshlyAllocatedBigInt)
 NEVER_READ_ONLY_SPACE_IMPL(MutableBigInt)
 
-#include "src/base/platform/wrappers.h"
 #include "src/objects/object-macros-undef.h"
 
 template <typename T, typename Isolate>
@@ -362,10 +361,9 @@
   int length = source->length();
   // Allocating a BigInt of the same length as an existing BigInt cannot throw.
   Handle<MutableBigInt> result = New(isolate, length).ToHandleChecked();
-  memcpy(
-      reinterpret_cast<void*>(result->address() + BigIntBase::kHeaderSize),
-      reinterpret_cast<void*>(source->address() + BigIntBase::kHeaderSize),
-      BigInt::SizeFor(length) - BigIntBase::kHeaderSize);
+  memcpy(reinterpret_cast<void*>(result->address() + BigIntBase::kHeaderSize),
+         reinterpret_cast<void*>(source->address() + BigIntBase::kHeaderSize),
+         BigInt::SizeFor(length) - BigIntBase::kHeaderSize);
   return result;
 }
 
diff --git a/src/third_party/v8/src/objects/fixed-array-inl.h b/src/third_party/v8/src/objects/fixed-array-inl.h
index c4eafe1..547e4dc 100644
--- a/src/third_party/v8/src/objects/fixed-array-inl.h
+++ b/src/third_party/v8/src/objects/fixed-array-inl.h
@@ -632,7 +632,6 @@
 }  // namespace internal
 }  // namespace v8
 
-#include "src/base/platform/wrappers.h"
 #include "src/objects/object-macros-undef.h"
 
 #endif  // V8_OBJECTS_FIXED_ARRAY_INL_H_
diff --git a/src/third_party/v8/src/objects/js-array-buffer.cc b/src/third_party/v8/src/objects/js-array-buffer.cc
index 15477ac..728791c 100644
--- a/src/third_party/v8/src/objects/js-array-buffer.cc
+++ b/src/third_party/v8/src/objects/js-array-buffer.cc
@@ -4,7 +4,6 @@
 
 #include "src/objects/js-array-buffer.h"
 
-#include "src/base/platform/wrappers.h"
 #include "src/execution/protectors-inl.h"
 #include "src/logging/counters.h"
 #include "src/objects/js-array-buffer-inl.h"
diff --git a/src/third_party/v8/src/objects/layout-descriptor.cc b/src/third_party/v8/src/objects/layout-descriptor.cc
index 680f8d3..034680e 100644
--- a/src/third_party/v8/src/objects/layout-descriptor.cc
+++ b/src/third_party/v8/src/objects/layout-descriptor.cc
@@ -7,7 +7,6 @@
 #include <sstream>
 
 #include "src/base/bits.h"
-#include "src/base/platform/wrappers.h"
 #include "src/handles/handles-inl.h"
 #include "src/objects/objects-inl.h"
 
@@ -103,8 +102,8 @@
 
   if (layout_descriptor->IsSlowLayout()) {
     memcpy(new_layout_descriptor->GetDataStartAddress(),
-                 layout_descriptor->GetDataStartAddress(),
-                 layout_descriptor->DataSize());
+           layout_descriptor->GetDataStartAddress(),
+           layout_descriptor->DataSize());
     return new_layout_descriptor;
   } else {
     // Fast layout.
diff --git a/src/third_party/v8/src/objects/shared-function-info-inl.h b/src/third_party/v8/src/objects/shared-function-info-inl.h
index 70ef90d..caf14e8 100644
--- a/src/third_party/v8/src/objects/shared-function-info-inl.h
+++ b/src/third_party/v8/src/objects/shared-function-info-inl.h
@@ -791,7 +791,6 @@
 }  // namespace internal
 }  // namespace v8
 
-#include "src/base/platform/wrappers.h"
 #include "src/objects/object-macros-undef.h"
 
 #endif  // V8_OBJECTS_SHARED_FUNCTION_INFO_INL_H_
diff --git a/src/third_party/v8/src/parsing/parse-info.cc b/src/third_party/v8/src/parsing/parse-info.cc
index ff94d6c..1c2b1b9 100644
--- a/src/third_party/v8/src/parsing/parse-info.cc
+++ b/src/third_party/v8/src/parsing/parse-info.cc
@@ -17,10 +17,6 @@
 #include "src/objects/scope-info.h"
 #include "src/zone/zone.h"
 
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 
diff --git a/src/third_party/v8/src/parsing/preparse-data.cc b/src/third_party/v8/src/parsing/preparse-data.cc
index 0ce29fa..b18788b 100644
--- a/src/third_party/v8/src/parsing/preparse-data.cc
+++ b/src/third_party/v8/src/parsing/preparse-data.cc
@@ -8,7 +8,6 @@
 
 #include "src/ast/scopes.h"
 #include "src/ast/variables.h"
-#include "src/base/platform/wrappers.h"
 #include "src/handles/handles.h"
 #include "src/objects/objects-inl.h"
 #include "src/objects/shared-function-info.h"
diff --git a/src/third_party/v8/src/parsing/scanner.cc b/src/third_party/v8/src/parsing/scanner.cc
index 7c0838f..9a04bdc 100644
--- a/src/third_party/v8/src/parsing/scanner.cc
+++ b/src/third_party/v8/src/parsing/scanner.cc
@@ -11,7 +11,6 @@
 #include <cmath>
 
 #include "src/ast/ast-value-factory.h"
-#include "src/base/platform/wrappers.h"
 #include "src/numbers/conversions-inl.h"
 #include "src/objects/bigint.h"
 #include "src/parsing/parse-info.h"
diff --git a/src/third_party/v8/src/poems.h b/src/third_party/v8/src/poems.h
index 4ac3cae..b8ac836 100644
--- a/src/third_party/v8/src/poems.h
+++ b/src/third_party/v8/src/poems.h
@@ -1,14 +1,3 @@
-// Copyright 2018 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
@@ -23,6 +12,11 @@
 #error "Including V8 poems without V8_OS_STARBOARD defined."
 #endif
 
+// This file is deprecated, stop using poems and use wrapper functions in
+// base/platform/wrappers.h instead.
+// Currently only tracing-controller.cc has trouble removing poems.h
+// dependencies.
+
 #include "starboard/memory.h"
 #include "starboard/string.h"
 #include "starboard/common/log.h"
@@ -56,3 +50,4 @@
 #define fflush(x)
 
 #endif  // V8_SRC_POEMS_H_
+
diff --git a/src/third_party/v8/src/profiler/allocation-tracker.cc b/src/third_party/v8/src/profiler/allocation-tracker.cc
index cb40e6e..5900438 100644
--- a/src/third_party/v8/src/profiler/allocation-tracker.cc
+++ b/src/third_party/v8/src/profiler/allocation-tracker.cc
@@ -9,10 +9,6 @@
 #include "src/objects/objects-inl.h"
 #include "src/profiler/heap-snapshot-generator-inl.h"
 
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 
diff --git a/src/third_party/v8/src/profiler/heap-snapshot-generator.cc b/src/third_party/v8/src/profiler/heap-snapshot-generator.cc
index 4c057eb..2907a21 100644
--- a/src/third_party/v8/src/profiler/heap-snapshot-generator.cc
+++ b/src/third_party/v8/src/profiler/heap-snapshot-generator.cc
@@ -40,10 +40,6 @@
 #include "src/profiler/heap-snapshot-generator-inl.h"
 #include "src/utils/vector.h"
 
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 
diff --git a/src/third_party/v8/src/profiler/profile-generator.cc b/src/third_party/v8/src/profiler/profile-generator.cc
index 2b7030e..f3344c5 100644
--- a/src/third_party/v8/src/profiler/profile-generator.cc
+++ b/src/third_party/v8/src/profiler/profile-generator.cc
@@ -14,10 +14,6 @@
 #include "src/tracing/trace-event.h"
 #include "src/tracing/traced-value.h"
 
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 
diff --git a/src/third_party/v8/src/regexp/regexp-interpreter.cc b/src/third_party/v8/src/regexp/regexp-interpreter.cc
index 8607f34..a73a9d3 100644
--- a/src/third_party/v8/src/regexp/regexp-interpreter.cc
+++ b/src/third_party/v8/src/regexp/regexp-interpreter.cc
@@ -22,10 +22,6 @@
 #include "unicode/uchar.h"
 #endif  // V8_INTL_SUPPORT
 
-#if defined(V8_OS_STARBOARD)
-#include "src/poems.h"
-#endif
-
 // Use token threaded dispatch iff the compiler supports computed gotos and the
 // build argument v8_enable_regexp_interpreter_threaded_dispatch was set.
 #if V8_HAS_COMPUTED_GOTO && \
diff --git a/src/third_party/v8/src/runtime/runtime-test.cc b/src/third_party/v8/src/runtime/runtime-test.cc
index f85cddb..c6df042 100644
--- a/src/third_party/v8/src/runtime/runtime-test.cc
+++ b/src/third_party/v8/src/runtime/runtime-test.cc
@@ -40,10 +40,6 @@
 #include "src/wasm/wasm-objects-inl.h"
 #include "src/wasm/wasm-serialization.h"
 
-#if defined(V8_OS_STARBOARD)
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 
diff --git a/src/third_party/v8/src/runtime/runtime.cc b/src/third_party/v8/src/runtime/runtime.cc
index e2e7ab4..4c5360f 100644
--- a/src/third_party/v8/src/runtime/runtime.cc
+++ b/src/third_party/v8/src/runtime/runtime.cc
@@ -5,7 +5,6 @@
 #include "src/runtime/runtime.h"
 
 #include "src/base/hashmap.h"
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/reloc-info.h"
 #include "src/execution/isolate.h"
 #include "src/handles/handles-inl.h"
diff --git a/src/third_party/v8/src/snapshot/deserializer.cc b/src/third_party/v8/src/snapshot/deserializer.cc
index 8f4b8a8..5a729b3 100644
--- a/src/third_party/v8/src/snapshot/deserializer.cc
+++ b/src/third_party/v8/src/snapshot/deserializer.cc
@@ -5,7 +5,6 @@
 #include "src/snapshot/deserializer.h"
 
 #include "src/base/logging.h"
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/assembler-inl.h"
 #include "src/common/assert-scope.h"
 #include "src/common/external-pointer.h"
diff --git a/src/third_party/v8/src/snapshot/snapshot-source-sink.h b/src/third_party/v8/src/snapshot/snapshot-source-sink.h
index 964b049..f0686af 100644
--- a/src/third_party/v8/src/snapshot/snapshot-source-sink.h
+++ b/src/third_party/v8/src/snapshot/snapshot-source-sink.h
@@ -9,7 +9,6 @@
 
 #include "src/base/atomicops.h"
 #include "src/base/logging.h"
-#include "src/base/platform/wrappers.h"
 #include "src/common/globals.h"
 #include "src/snapshot/snapshot-utils.h"
 #include "src/utils/utils.h"
diff --git a/src/third_party/v8/src/trap-handler/handler-outside.cc b/src/third_party/v8/src/trap-handler/handler-outside.cc
index 65e8f75..22d94ea 100644
--- a/src/third_party/v8/src/trap-handler/handler-outside.cc
+++ b/src/third_party/v8/src/trap-handler/handler-outside.cc
@@ -30,10 +30,6 @@
 #include "src/trap-handler/trap-handler-internal.h"
 #include "src/trap-handler/trap-handler.h"
 
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif
-
 namespace {
 size_t gNextCodeObject = 0;
 
diff --git a/src/third_party/v8/src/utils/allocation.cc b/src/third_party/v8/src/utils/allocation.cc
index e7aa682..79c2790 100644
--- a/src/third_party/v8/src/utils/allocation.cc
+++ b/src/third_party/v8/src/utils/allocation.cc
@@ -10,6 +10,7 @@
 #include "src/base/logging.h"
 #include "src/base/page-allocator.h"
 #include "src/base/platform/platform.h"
+#include "src/base/platform/wrappers.h"
 #include "src/flags/flags.h"
 #include "src/init/v8.h"
 #include "src/sanitizer/lsan-page-allocator.h"
@@ -18,12 +19,6 @@
 
 #if V8_LIBC_BIONIC
 #include <malloc.h>  // NOLINT
-
-#include "src/base/platform/wrappers.h"
-#endif
-
-#if defined(V8_OS_STARBOARD)
-#include "src/poems.h"
 #endif
 
 namespace v8 {
diff --git a/src/third_party/v8/src/utils/detachable-vector.h b/src/third_party/v8/src/utils/detachable-vector.h
index e921ea7..232e89f 100644
--- a/src/third_party/v8/src/utils/detachable-vector.h
+++ b/src/third_party/v8/src/utils/detachable-vector.h
@@ -11,7 +11,6 @@
 
 #include "src/base/logging.h"
 #include "src/base/macros.h"
-#include "src/base/platform/wrappers.h"
 
 namespace v8 {
 namespace internal {
diff --git a/src/third_party/v8/src/utils/memcopy.h b/src/third_party/v8/src/utils/memcopy.h
index b3648ed..b940a4a 100644
--- a/src/third_party/v8/src/utils/memcopy.h
+++ b/src/third_party/v8/src/utils/memcopy.h
@@ -8,12 +8,10 @@
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
-
 #include <algorithm>
 
 #include "src/base/logging.h"
 #include "src/base/macros.h"
-#include "src/base/platform/wrappers.h"
 
 namespace v8 {
 namespace internal {
diff --git a/src/third_party/v8/src/utils/ostreams.cc b/src/third_party/v8/src/utils/ostreams.cc
index 61a2c7c..d58357c 100644
--- a/src/third_party/v8/src/utils/ostreams.cc
+++ b/src/third_party/v8/src/utils/ostreams.cc
@@ -17,10 +17,6 @@
 #endif
 #endif
 
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif
-
 #if defined(ANDROID) && !defined(V8_ANDROID_LOG_STDOUT)
 #define LOG_TAG "v8"
 #include <android/log.h>  // NOLINT
diff --git a/src/third_party/v8/src/utils/ostreams.h b/src/third_party/v8/src/utils/ostreams.h
index 5a91d1e..82c2923 100644
--- a/src/third_party/v8/src/utils/ostreams.h
+++ b/src/third_party/v8/src/utils/ostreams.h
@@ -166,9 +166,6 @@
 // Print any collection which can be iterated via std::begin and std::end.
 // {Iterator} is the common type of {std::begin} and {std::end} called on a
 // {const T&}. This function is only instantiable if that type exists.
-// template <typename T, typename Iterator = typename std::common_type<
-//                           decltype(std::begin(std::declval<const T&>())),
-//                           decltype(std::end(std::declval<const T&>()))>::type>
 template <typename T, typename Iterator = decltype(std::begin(std::declval<const T&>()))>
 PrintIteratorRange<Iterator> PrintCollection(const T& collection) {
   return {std::begin(collection), std::end(collection)};
diff --git a/src/third_party/v8/src/utils/utils.cc b/src/third_party/v8/src/utils/utils.cc
index 1c2b8cc..4957f9e 100644
--- a/src/third_party/v8/src/utils/utils.cc
+++ b/src/third_party/v8/src/utils/utils.cc
@@ -15,10 +15,6 @@
 #include "src/base/platform/wrappers.h"
 #include "src/utils/memcopy.h"
 
-#if defined(V8_OS_STARBOARD)
-#include "v8/src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 
diff --git a/src/third_party/v8/src/wasm/baseline/arm/liftoff-assembler-arm.h b/src/third_party/v8/src/wasm/baseline/arm/liftoff-assembler-arm.h
index 2f450f7..af969f3 100644
--- a/src/third_party/v8/src/wasm/baseline/arm/liftoff-assembler-arm.h
+++ b/src/third_party/v8/src/wasm/baseline/arm/liftoff-assembler-arm.h
@@ -5,7 +5,6 @@
 #ifndef V8_WASM_BASELINE_ARM_LIFTOFF_ASSEMBLER_ARM_H_
 #define V8_WASM_BASELINE_ARM_LIFTOFF_ASSEMBLER_ARM_H_
 
-#include "src/base/platform/wrappers.h"
 #include "src/heap/memory-chunk.h"
 #include "src/wasm/baseline/liftoff-assembler.h"
 #include "src/wasm/baseline/liftoff-register.h"
diff --git a/src/third_party/v8/src/wasm/baseline/ia32/liftoff-assembler-ia32.h b/src/third_party/v8/src/wasm/baseline/ia32/liftoff-assembler-ia32.h
index 86ded4d..5e64009 100644
--- a/src/third_party/v8/src/wasm/baseline/ia32/liftoff-assembler-ia32.h
+++ b/src/third_party/v8/src/wasm/baseline/ia32/liftoff-assembler-ia32.h
@@ -5,7 +5,6 @@
 #ifndef V8_WASM_BASELINE_IA32_LIFTOFF_ASSEMBLER_IA32_H_
 #define V8_WASM_BASELINE_IA32_LIFTOFF_ASSEMBLER_IA32_H_
 
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/assembler.h"
 #include "src/heap/memory-chunk.h"
 #include "src/wasm/baseline/liftoff-assembler.h"
diff --git a/src/third_party/v8/src/wasm/baseline/liftoff-assembler.cc b/src/third_party/v8/src/wasm/baseline/liftoff-assembler.cc
index 28f0458..956291f 100644
--- a/src/third_party/v8/src/wasm/baseline/liftoff-assembler.cc
+++ b/src/third_party/v8/src/wasm/baseline/liftoff-assembler.cc
@@ -18,10 +18,6 @@
 #include "src/wasm/wasm-linkage.h"
 #include "src/wasm/wasm-opcodes.h"
 
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 namespace wasm {
diff --git a/src/third_party/v8/src/wasm/baseline/liftoff-assembler.h b/src/third_party/v8/src/wasm/baseline/liftoff-assembler.h
index 20e972e..895abbb 100644
--- a/src/third_party/v8/src/wasm/baseline/liftoff-assembler.h
+++ b/src/third_party/v8/src/wasm/baseline/liftoff-assembler.h
@@ -20,10 +20,6 @@
 #include "src/wasm/wasm-opcodes.h"
 #include "src/wasm/wasm-value.h"
 
-#if defined(V8_OS_STARBOARD)
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 
diff --git a/src/third_party/v8/src/wasm/baseline/liftoff-compiler.cc b/src/third_party/v8/src/wasm/baseline/liftoff-compiler.cc
index 39b2956..5066468 100644
--- a/src/third_party/v8/src/wasm/baseline/liftoff-compiler.cc
+++ b/src/third_party/v8/src/wasm/baseline/liftoff-compiler.cc
@@ -5,7 +5,6 @@
 #include "src/wasm/baseline/liftoff-compiler.h"
 
 #include "src/base/optional.h"
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/assembler-inl.h"
 // TODO(clemensb): Remove dependences on compiler stuff.
 #include "src/codegen/external-reference.h"
diff --git a/src/third_party/v8/src/wasm/baseline/mips64/liftoff-assembler-mips64.h b/src/third_party/v8/src/wasm/baseline/mips64/liftoff-assembler-mips64.h
index 3eb2d28..b97c494 100644
--- a/src/third_party/v8/src/wasm/baseline/mips64/liftoff-assembler-mips64.h
+++ b/src/third_party/v8/src/wasm/baseline/mips64/liftoff-assembler-mips64.h
@@ -5,7 +5,6 @@
 #ifndef V8_WASM_BASELINE_MIPS64_LIFTOFF_ASSEMBLER_MIPS64_H_
 #define V8_WASM_BASELINE_MIPS64_LIFTOFF_ASSEMBLER_MIPS64_H_
 
-#include "src/base/platform/wrappers.h"
 #include "src/heap/memory-chunk.h"
 #include "src/wasm/baseline/liftoff-assembler.h"
 
diff --git a/src/third_party/v8/src/wasm/function-body-decoder-impl.h b/src/third_party/v8/src/wasm/function-body-decoder-impl.h
index d67f1e3..b319965 100644
--- a/src/third_party/v8/src/wasm/function-body-decoder-impl.h
+++ b/src/third_party/v8/src/wasm/function-body-decoder-impl.h
@@ -11,7 +11,6 @@
 #include <inttypes.h>
 
 #include "src/base/platform/elapsed-timer.h"
-#include "src/base/platform/wrappers.h"
 #include "src/base/small-vector.h"
 #include "src/utils/bit-vector.h"
 #include "src/wasm/decoder.h"
@@ -3157,8 +3156,13 @@
 #undef DECODE_IMPL2
 
   OpcodeHandler GetOpcodeHandler(uint8_t opcode) {
+#ifndef V8_OS_STARBOARD
+    static constexpr std::array<OpcodeHandler, 256> kOpcodeHandlers =
+        base::make_array<256>(GetOpcodeHandlerTableEntry);
+#else
     DCHECK(false);
     static constexpr std::array<OpcodeHandler, 256> kOpcodeHandlers{};
+#endif
     return kOpcodeHandlers[opcode];
   }
 
diff --git a/src/third_party/v8/src/wasm/local-decl-encoder.cc b/src/third_party/v8/src/wasm/local-decl-encoder.cc
index a396194..5a2681c 100644
--- a/src/third_party/v8/src/wasm/local-decl-encoder.cc
+++ b/src/third_party/v8/src/wasm/local-decl-encoder.cc
@@ -4,7 +4,6 @@
 
 #include "src/wasm/local-decl-encoder.h"
 
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/signature.h"
 #include "src/wasm/leb-helper.h"
 
diff --git a/src/third_party/v8/src/wasm/memory-tracing.cc b/src/third_party/v8/src/wasm/memory-tracing.cc
index 58ed547..0d88c4b 100644
--- a/src/third_party/v8/src/wasm/memory-tracing.cc
+++ b/src/third_party/v8/src/wasm/memory-tracing.cc
@@ -10,10 +10,6 @@
 #include "src/utils/utils.h"
 #include "src/utils/vector.h"
 
-#if V8_OS_STARBOARD
-#include "starboard/common/log.h"
-#endif  // V8_OS_STARBOARD
-
 namespace v8 {
 namespace internal {
 namespace wasm {
diff --git a/src/third_party/v8/src/wasm/module-compiler.cc b/src/third_party/v8/src/wasm/module-compiler.cc
index a7d69a8..e5cd5eb 100644
--- a/src/third_party/v8/src/wasm/module-compiler.cc
+++ b/src/third_party/v8/src/wasm/module-compiler.cc
@@ -36,10 +36,6 @@
 #include "src/wasm/wasm-result.h"
 #include "src/wasm/wasm-serialization.h"
 
-#if defined(V8_OS_STARBOARD)
-#include "src/poems.h"
-#endif
-
 #define TRACE_COMPILE(...)                             \
   do {                                                 \
     if (FLAG_trace_wasm_compiler) PrintF(__VA_ARGS__); \
diff --git a/src/third_party/v8/src/wasm/module-instantiate.cc b/src/third_party/v8/src/wasm/module-instantiate.cc
index 361d481..9169a31 100644
--- a/src/third_party/v8/src/wasm/module-instantiate.cc
+++ b/src/third_party/v8/src/wasm/module-instantiate.cc
@@ -6,7 +6,6 @@
 
 #include "src/api/api.h"
 #include "src/asmjs/asm-js.h"
-#include "src/base/platform/wrappers.h"
 #include "src/logging/counters.h"
 #include "src/logging/metrics.h"
 #include "src/numbers/conversions-inl.h"
diff --git a/src/third_party/v8/src/wasm/streaming-decoder.cc b/src/third_party/v8/src/wasm/streaming-decoder.cc
index f25dd0e..e9b987f 100644
--- a/src/third_party/v8/src/wasm/streaming-decoder.cc
+++ b/src/third_party/v8/src/wasm/streaming-decoder.cc
@@ -4,7 +4,6 @@
 
 #include "src/wasm/streaming-decoder.h"
 
-#include "src/base/platform/wrappers.h"
 #include "src/handles/handles.h"
 #include "src/objects/descriptor-array.h"
 #include "src/objects/dictionary.h"
diff --git a/src/third_party/v8/src/wasm/wasm-code-manager.cc b/src/third_party/v8/src/wasm/wasm-code-manager.cc
index 83be91d..0e1b953 100644
--- a/src/third_party/v8/src/wasm/wasm-code-manager.cc
+++ b/src/third_party/v8/src/wasm/wasm-code-manager.cc
@@ -35,14 +35,9 @@
 #include "src/wasm/wasm-objects.h"
 
 #if defined(V8_OS_WIN64)
-#include "src/base/platform/wrappers.h"
 #include "src/diagnostics/unwinding-info-win64.h"
 #endif  // V8_OS_WIN64
 
-#if defined(V8_OS_STARBOARD)
-#include "src/poems.h"
-#endif
-
 #define TRACE_HEAP(...)                                   \
   do {                                                    \
     if (FLAG_trace_wasm_native_heap) PrintF(__VA_ARGS__); \
diff --git a/src/third_party/v8/src/wasm/wasm-debug-evaluate.cc b/src/third_party/v8/src/wasm/wasm-debug-evaluate.cc
index f4a4deb..bbd75f6 100644
--- a/src/third_party/v8/src/wasm/wasm-debug-evaluate.cc
+++ b/src/third_party/v8/src/wasm/wasm-debug-evaluate.cc
@@ -8,7 +8,6 @@
 #include <limits>
 
 #include "src/api/api-inl.h"
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/machine-type.h"
 #include "src/compiler/wasm-compiler.h"
 #include "src/execution/frames-inl.h"
diff --git a/src/third_party/v8/src/wasm/wasm-debug.cc b/src/third_party/v8/src/wasm/wasm-debug.cc
index 36ced71..5da5525 100644
--- a/src/third_party/v8/src/wasm/wasm-debug.cc
+++ b/src/third_party/v8/src/wasm/wasm-debug.cc
@@ -8,7 +8,6 @@
 #include <unordered_map>
 
 #include "src/base/optional.h"
-#include "src/base/platform/wrappers.h"
 #include "src/codegen/assembler-inl.h"
 #include "src/common/assert-scope.h"
 #include "src/compiler/wasm-compiler.h"
diff --git a/src/third_party/v8/src/wasm/wasm-engine.cc b/src/third_party/v8/src/wasm/wasm-engine.cc
index 9649789..9f962f7 100644
--- a/src/third_party/v8/src/wasm/wasm-engine.cc
+++ b/src/third_party/v8/src/wasm/wasm-engine.cc
@@ -27,7 +27,6 @@
 #include "src/wasm/wasm-objects-inl.h"
 
 #ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
-#include "src/base/platform/wrappers.h"
 #include "src/debug/wasm/gdb-server/gdb-server.h"
 #endif  // V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
 
diff --git a/src/third_party/v8/src/wasm/wasm-js.cc b/src/third_party/v8/src/wasm/wasm-js.cc
index 91a49ee..8bc82f2 100644
--- a/src/third_party/v8/src/wasm/wasm-js.cc
+++ b/src/third_party/v8/src/wasm/wasm-js.cc
@@ -12,7 +12,6 @@
 #include "src/ast/ast.h"
 #include "src/base/logging.h"
 #include "src/base/overflowing-math.h"
-#include "src/base/platform/wrappers.h"
 #include "src/common/assert-scope.h"
 #include "src/execution/execution.h"
 #include "src/execution/frames-inl.h"
diff --git a/src/third_party/v8/src/wasm/wasm-module.h b/src/third_party/v8/src/wasm/wasm-module.h
index e86b3de..9c54f17 100644
--- a/src/third_party/v8/src/wasm/wasm-module.h
+++ b/src/third_party/v8/src/wasm/wasm-module.h
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "src/base/optional.h"
-#include "src/base/platform/wrappers.h"
 #include "src/common/globals.h"
 #include "src/handles/handles.h"
 #include "src/utils/vector.h"
diff --git a/src/third_party/v8/src/wasm/wasm-objects.cc b/src/third_party/v8/src/wasm/wasm-objects.cc
index 15a70cc..e0579d1 100644
--- a/src/third_party/v8/src/wasm/wasm-objects.cc
+++ b/src/third_party/v8/src/wasm/wasm-objects.cc
@@ -30,10 +30,6 @@
 #include "src/wasm/wasm-subtyping.h"
 #include "src/wasm/wasm-value.h"
 
-#if defined(V8_OS_STARBOARD)
-#include "src/poems.h"
-#endif
-
 #define TRACE_IFT(...)              \
   do {                              \
     if (false) PrintF(__VA_ARGS__); \
diff --git a/src/third_party/v8/src/wasm/wasm-text.cc b/src/third_party/v8/src/wasm/wasm-text.cc
deleted file mode 100644
index 5690295..0000000
--- a/src/third_party/v8/src/wasm/wasm-text.cc
+++ /dev/null
@@ -1,400 +0,0 @@
-// Copyright 2016 the V8 project authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "src/wasm/wasm-text.h"
-
-#include "src/debug/interface-types.h"
-#include "src/utils/ostreams.h"
-#include "src/utils/vector.h"
-#include "src/objects/objects-inl.h"
-#include "src/wasm/function-body-decoder-impl.h"
-#include "src/wasm/function-body-decoder.h"
-#include "src/wasm/wasm-module.h"
-#include "src/wasm/wasm-opcodes.h"
-#include "src/zone/zone.h"
-
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif
-
-namespace v8 {
-namespace internal {
-namespace wasm {
-
-namespace {
-bool IsValidFunctionName(const Vector<const char> &name) {
-  if (name.empty()) return false;
-  const char *special_chars = "_.+-*/\\^~=<>!?@#$%&|:'`";
-  for (char c : name) {
-    bool valid_char = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
-                      (c >= 'A' && c <= 'Z') || strchr(special_chars, c);
-    if (!valid_char) return false;
-  }
-  return true;
-}
-
-}  // namespace
-
-void PrintWasmText(const WasmModule* module, const ModuleWireBytes& wire_bytes,
-                   uint32_t func_index, std::ostream& os,
-                   debug::WasmDisassembly::OffsetTable* offset_table) {
-  DCHECK_NOT_NULL(module);
-  DCHECK_GT(module->functions.size(), func_index);
-  const WasmFunction *fun = &module->functions[func_index];
-
-  AccountingAllocator allocator;
-  Zone zone(&allocator, ZONE_NAME);
-  int line_nr = 0;
-  int control_depth = 1;
-
-  // Print the function signature.
-  os << "func";
-  WasmName fun_name = wire_bytes.GetNameOrNull(fun, module);
-  if (IsValidFunctionName(fun_name)) {
-    os << " $";
-    os.write(fun_name.begin(), fun_name.length());
-  }
-  if (fun->sig->parameter_count()) {
-    os << " (param";
-    for (auto param : fun->sig->parameters())
-      os << ' ' << ValueTypes::TypeName(param);
-    os << ')';
-  }
-  if (fun->sig->return_count()) {
-    os << " (result";
-    for (auto ret : fun->sig->returns()) os << ' ' << ValueTypes::TypeName(ret);
-    os << ')';
-  }
-  os << "\n";
-  ++line_nr;
-
-  // Print the local declarations.
-  BodyLocalDecls decls(&zone);
-  Vector<const byte> func_bytes = wire_bytes.GetFunctionBytes(fun);
-  BytecodeIterator i(func_bytes.begin(), func_bytes.end(), &decls);
-  DCHECK_LT(func_bytes.begin(), i.pc());
-  if (!decls.type_list.empty()) {
-    os << "(local";
-    for (const ValueType &v : decls.type_list) {
-      os << ' ' << ValueTypes::TypeName(v);
-    }
-    os << ")\n";
-    ++line_nr;
-  }
-
-  for (; i.has_next(); i.next()) {
-    WasmOpcode opcode = i.current();
-    if (opcode == kExprElse || opcode == kExprCatch || opcode == kExprEnd) {
-      --control_depth;
-    }
-
-    DCHECK_LE(0, control_depth);
-    const int kMaxIndentation = 64;
-    int indentation = std::min(kMaxIndentation, 2 * control_depth);
-    if (offset_table) {
-      offset_table->emplace_back(i.pc_offset(), line_nr, indentation);
-    }
-
-    // 64 whitespaces
-    const char padding[kMaxIndentation + 1] =
-        "                                                                ";
-    os.write(padding, indentation);
-
-    switch (opcode) {
-      case kExprLoop:
-      case kExprIf:
-      case kExprBlock:
-      case kExprTry: {
-        BlockTypeImmediate<Decoder::kNoValidate> imm(kAllWasmFeatures, &i,
-                                                     i.pc());
-        os << WasmOpcodes::OpcodeName(opcode);
-        if (imm.type == kWasmBottom) {
-          os << " (type " << imm.sig_index << ")";
-        } else if (imm.out_arity() > 0) {
-          os << " " << ValueTypes::TypeName(imm.out_type(0));
-        }
-        control_depth++;
-        break;
-      }
-      case kExprBr:
-      case kExprBrIf: {
-        BranchDepthImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-        os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.depth;
-        break;
-      }
-      case kExprBrOnExn: {
-        BranchOnExceptionImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-        os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.depth.depth << ' '
-           << imm.index.index;
-        break;
-      }
-      case kExprElse:
-      case kExprCatch:
-        os << WasmOpcodes::OpcodeName(opcode);
-        control_depth++;
-        break;
-      case kExprEnd:
-        os << "end";
-        break;
-      case kExprBrTable: {
-        BranchTableImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-        BranchTableIterator<Decoder::kNoValidate> iterator(&i, imm);
-        os << "br_table";
-        while (iterator.has_next()) os << ' ' << iterator.next();
-        break;
-      }
-      case kExprCallIndirect:
-      case kExprReturnCallIndirect: {
-        CallIndirectImmediate<Decoder::kNoValidate> imm(kAllWasmFeatures, &i,
-                                                        i.pc());
-        DCHECK_EQ(0, imm.table_index);
-        os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.sig_index;
-        break;
-      }
-      case kExprCallFunction:
-      case kExprReturnCall: {
-        CallFunctionImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-        os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
-        break;
-      }
-      case kExprGetLocal:
-      case kExprSetLocal:
-      case kExprTeeLocal: {
-        LocalIndexImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-        os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
-        break;
-      }
-      case kExprThrow: {
-        ExceptionIndexImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-        os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
-        break;
-      }
-      case kExprGetGlobal:
-      case kExprSetGlobal: {
-        GlobalIndexImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-        os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
-        break;
-      }
-      case kExprTableGet:
-      case kExprTableSet: {
-        TableIndexImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-        os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
-        break;
-      }
-      case kExprSelectWithType: {
-        SelectTypeImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-        os << WasmOpcodes::OpcodeName(opcode) << ' '
-           << ValueTypes::TypeName(imm.type);
-        break;
-      }
-#define CASE_CONST(type, str, cast_type)                        \
-  case kExpr##type##Const: {                                    \
-    Imm##type##Immediate<Decoder::kNoValidate> imm(&i, i.pc()); \
-    os << #str ".const " << static_cast<cast_type>(imm.value);  \
-    break;                                                      \
-  }
-        CASE_CONST(I32, i32, int32_t)
-        CASE_CONST(I64, i64, int64_t)
-        CASE_CONST(F32, f32, float)
-        CASE_CONST(F64, f64, double)
-#undef CASE_CONST
-
-      case kExprRefFunc: {
-        FunctionIndexImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-        os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
-        break;
-      }
-
-#define CASE_OPCODE(opcode, _, __) case kExpr##opcode:
-        FOREACH_LOAD_MEM_OPCODE(CASE_OPCODE)
-        FOREACH_STORE_MEM_OPCODE(CASE_OPCODE) {
-          MemoryAccessImmediate<Decoder::kNoValidate> imm(&i, i.pc(),
-                                                          kMaxUInt32);
-          os << WasmOpcodes::OpcodeName(opcode) << " offset=" << imm.offset
-             << " align=" << (1ULL << imm.alignment);
-          break;
-        }
-
-        FOREACH_SIMPLE_OPCODE(CASE_OPCODE)
-        FOREACH_SIMPLE_PROTOTYPE_OPCODE(CASE_OPCODE)
-      case kExprUnreachable:
-      case kExprNop:
-      case kExprReturn:
-      case kExprMemorySize:
-      case kExprMemoryGrow:
-      case kExprDrop:
-      case kExprSelect:
-      case kExprRethrow:
-      case kExprRefNull:
-        os << WasmOpcodes::OpcodeName(opcode);
-        break;
-
-      case kNumericPrefix: {
-        WasmOpcode numeric_opcode = i.prefixed_opcode();
-        switch (numeric_opcode) {
-          case kExprI32SConvertSatF32:
-          case kExprI32UConvertSatF32:
-          case kExprI32SConvertSatF64:
-          case kExprI32UConvertSatF64:
-          case kExprI64SConvertSatF32:
-          case kExprI64UConvertSatF32:
-          case kExprI64SConvertSatF64:
-          case kExprI64UConvertSatF64:
-          case kExprMemoryCopy:
-          case kExprMemoryFill:
-            os << WasmOpcodes::OpcodeName(opcode);
-            break;
-          case kExprMemoryInit: {
-            MemoryInitImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-            os << WasmOpcodes::OpcodeName(opcode) << ' '
-               << imm.data_segment_index;
-            break;
-          }
-          case kExprDataDrop: {
-            DataDropImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-            os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
-            break;
-          }
-          case kExprTableInit: {
-            TableInitImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-            os << WasmOpcodes::OpcodeName(opcode) << ' '
-               << imm.elem_segment_index << ' ' << imm.table.index;
-            break;
-          }
-          case kExprElemDrop: {
-            ElemDropImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-            os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
-            break;
-          }
-          case kExprTableCopy: {
-            TableCopyImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-            os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.table_src.index
-               << ' ' << imm.table_dst.index;
-            break;
-          }
-          case kExprTableGrow:
-          case kExprTableSize:
-          case kExprTableFill: {
-            TableIndexImmediate<Decoder::kNoValidate> imm(&i, i.pc() + 1);
-            os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
-            break;
-          }
-          default:
-            UNREACHABLE();
-            break;
-        }
-        break;
-      }
-
-      case kSimdPrefix: {
-        WasmOpcode simd_opcode = i.prefixed_opcode();
-        switch (simd_opcode) {
-          case kExprS128LoadMem:
-          case kExprS128StoreMem: {
-            MemoryAccessImmediate<Decoder::kNoValidate> imm(&i, i.pc(),
-                                                            kMaxUInt32);
-            os << WasmOpcodes::OpcodeName(opcode) << " offset=" << imm.offset
-               << " align=" << (1ULL << imm.alignment);
-            break;
-          }
-
-          case kExprS8x16Shuffle: {
-            Simd8x16ShuffleImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-            os << WasmOpcodes::OpcodeName(opcode);
-            for (uint8_t v : imm.shuffle) {
-              os << ' ' << v;
-            }
-            break;
-          }
-
-          case kExprI8x16ExtractLane:
-          case kExprI16x8ExtractLane:
-          case kExprI32x4ExtractLane:
-          case kExprI64x2ExtractLane:
-          case kExprF32x4ExtractLane:
-          case kExprF64x2ExtractLane:
-          case kExprI8x16ReplaceLane:
-          case kExprI16x8ReplaceLane:
-          case kExprI32x4ReplaceLane:
-          case kExprI64x2ReplaceLane:
-          case kExprF32x4ReplaceLane:
-          case kExprF64x2ReplaceLane: {
-            SimdLaneImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-            os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.lane;
-            break;
-          }
-
-          case kExprI8x16Shl:
-          case kExprI8x16ShrS:
-          case kExprI8x16ShrU:
-          case kExprI16x8Shl:
-          case kExprI16x8ShrS:
-          case kExprI16x8ShrU:
-          case kExprI32x4Shl:
-          case kExprI32x4ShrS:
-          case kExprI32x4ShrU:
-          case kExprI64x2Shl:
-          case kExprI64x2ShrS:
-          case kExprI64x2ShrU: {
-            SimdShiftImmediate<Decoder::kNoValidate> imm(&i, i.pc());
-            os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.shift;
-            break;
-          }
-
-            FOREACH_SIMD_0_OPERAND_OPCODE(CASE_OPCODE) {
-              os << WasmOpcodes::OpcodeName(opcode);
-              break;
-            }
-
-          default:
-            UNREACHABLE();
-            break;
-        }
-        break;
-      }
-
-      case kAtomicPrefix: {
-        WasmOpcode atomic_opcode = i.prefixed_opcode();
-        switch (atomic_opcode) {
-          FOREACH_ATOMIC_OPCODE(CASE_OPCODE) {
-            MemoryAccessImmediate<Decoder::kNoValidate> imm(&i, i.pc() + 1,
-                                                            kMaxUInt32);
-            os << WasmOpcodes::OpcodeName(atomic_opcode)
-               << " offset=" << imm.offset
-               << " align=" << (1ULL << imm.alignment);
-            break;
-          }
-          FOREACH_ATOMIC_0_OPERAND_OPCODE(CASE_OPCODE) {
-            os << WasmOpcodes::OpcodeName(atomic_opcode);
-            break;
-          }
-          default:
-            UNREACHABLE();
-            break;
-        }
-        break;
-      }
-
-        // This group is just printed by their internal opcode name, as they
-        // should never be shown to end-users.
-        FOREACH_ASMJS_COMPAT_OPCODE(CASE_OPCODE) {
-          os << WasmOpcodes::OpcodeName(opcode);
-        }
-        break;
-#undef CASE_OPCODE
-
-      default:
-        UNREACHABLE();
-        break;
-    }
-    os << '\n';
-    ++line_nr;
-  }
-  DCHECK_EQ(0, control_depth);
-  DCHECK(i.ok());
-}
-
-}  // namespace wasm
-}  // namespace internal
-}  // namespace v8
diff --git a/src/third_party/v8/src/zone/accounting-allocator.cc b/src/third_party/v8/src/zone/accounting-allocator.cc
index 8808a67..baa7016 100644
--- a/src/third_party/v8/src/zone/accounting-allocator.cc
+++ b/src/third_party/v8/src/zone/accounting-allocator.cc
@@ -14,10 +14,6 @@
 #include "src/zone/zone-compression.h"
 #include "src/zone/zone-segment.h"
 
-#if V8_OS_STARBOARD
-#include "src/poems.h"
-#endif
-
 namespace v8 {
 namespace internal {
 
diff --git a/src/third_party/web_platform_tests/FileAPI/idlharness.html b/src/third_party/web_platform_tests/FileAPI/idlharness.html
index 0ef3700..1795112 100644
--- a/src/third_party/web_platform_tests/FileAPI/idlharness.html
+++ b/src/third_party/web_platform_tests/FileAPI/idlharness.html
@@ -30,7 +30,7 @@
     request.onload = function() {
         var idls = request.responseText;
 
-        idl_array.add_untested_idls("[PrimaryGlobal] interface Window { };");
+        idl_array.add_untested_idls("[Global] interface Window { };");
 
         idl_array.add_untested_idls("interface ArrayBuffer {};");
         idl_array.add_untested_idls("interface ArrayBufferView {};");
diff --git a/src/third_party/web_platform_tests/IndexedDB/interfaces.html b/src/third_party/web_platform_tests/IndexedDB/interfaces.html
index cc4e7f2..447e4da 100644
--- a/src/third_party/web_platform_tests/IndexedDB/interfaces.html
+++ b/src/third_party/web_platform_tests/IndexedDB/interfaces.html
@@ -20,7 +20,7 @@
   request.onload = function() {
     var idls = request.responseText;
 
-    idlArray.add_untested_idls("[PrimaryGlobal] interface Window { };");
+    idlArray.add_untested_idls("[Global] interface Window { };");
     idlArray.add_untested_idls("interface Event { };");
     idlArray.add_untested_idls("interface EventTarget { };");
 
diff --git a/src/third_party/web_platform_tests/ambient-light/idlharness.html b/src/third_party/web_platform_tests/ambient-light/idlharness.html
index bcf2228..bf79159 100644
--- a/src/third_party/web_platform_tests/ambient-light/idlharness.html
+++ b/src/third_party/web_platform_tests/ambient-light/idlharness.html
@@ -15,7 +15,7 @@
 <div id="log"></div>
 
 <pre id="untested_idl">
-[PrimaryGlobal]
+[Global]
 interface Window {
 };
 
diff --git a/src/third_party/web_platform_tests/html/dom/interfaces.html b/src/third_party/web_platform_tests/html/dom/interfaces.html
index 69d81cc..bf9650e 100644
--- a/src/third_party/web_platform_tests/html/dom/interfaces.html
+++ b/src/third_party/web_platform_tests/html/dom/interfaces.html
@@ -2252,7 +2252,7 @@
 // Window.
 typedef Window WindowProxy;
 
-[PrimaryGlobal]
+[Global]
 /*sealed*/ interface Window : EventTarget {
   // the current browsing context
   [Unforgeable] readonly attribute WindowProxy window;
diff --git a/src/third_party/web_platform_tests/html/webappapis/animation-frames/idlharness.html b/src/third_party/web_platform_tests/html/webappapis/animation-frames/idlharness.html
index acc6657..fb16e95 100644
--- a/src/third_party/web_platform_tests/html/webappapis/animation-frames/idlharness.html
+++ b/src/third_party/web_platform_tests/html/webappapis/animation-frames/idlharness.html
@@ -15,7 +15,7 @@
 <p>This test validates the WebIDL included in the Timing control for script-based animations specification.</p>
 
 <pre id='untested_idl' style='display:none'>
-[PrimaryGlobal]
+[Global]
 interface Window {
 };
 </pre>
diff --git a/src/third_party/web_platform_tests/mediasession/idlharness.html b/src/third_party/web_platform_tests/mediasession/idlharness.html
index a90c83e..58f7556 100644
--- a/src/third_party/web_platform_tests/mediasession/idlharness.html
+++ b/src/third_party/web_platform_tests/mediasession/idlharness.html
@@ -13,7 +13,7 @@
 <h1>Media Session IDL tests</h1>
 
 <pre id='untested_idl' style='display:none'>
-[PrimaryGlobal]
+[Global]
 interface Window {
 };
 
diff --git a/src/third_party/web_platform_tests/navigation-timing/idlharness.html b/src/third_party/web_platform_tests/navigation-timing/idlharness.html
index 65a8778..c3b8735 100644
--- a/src/third_party/web_platform_tests/navigation-timing/idlharness.html
+++ b/src/third_party/web_platform_tests/navigation-timing/idlharness.html
@@ -18,7 +18,7 @@
 
 <pre id='untested_idl' style='display:none'>
 
-[PrimaryGlobal]
+[Global]
 interface Window {
 };
 
diff --git a/src/third_party/web_platform_tests/proximity/idlharness.html b/src/third_party/web_platform_tests/proximity/idlharness.html
index 817aa5c..607251c 100644
--- a/src/third_party/web_platform_tests/proximity/idlharness.html
+++ b/src/third_party/web_platform_tests/proximity/idlharness.html
@@ -15,7 +15,7 @@
 <div id="log"></div>
 
 <pre id="untested_idl">
-[PrimaryGlobal]
+[Global]
 interface Window {
 };
 
diff --git a/src/third_party/web_platform_tests/webstorage/idlharness.html b/src/third_party/web_platform_tests/webstorage/idlharness.html
index 454e441..bd6b746 100644
--- a/src/third_party/web_platform_tests/webstorage/idlharness.html
+++ b/src/third_party/web_platform_tests/webstorage/idlharness.html
@@ -15,7 +15,7 @@
 <div id="log"></div>
 
 <pre id='untested_idl' style='display:none'>
-[PrimaryGlobal]
+[Global]
 interface Window {
 };
 
diff --git a/src/tools/format_ninja.py b/src/tools/format_ninja.py
index b0d1ba0..acff046 100644
--- a/src/tools/format_ninja.py
+++ b/src/tools/format_ninja.py
@@ -33,7 +33,7 @@
 from typing import List, Tuple
 
 _ACTION_COMPONENTS = ['directory', 'command', 'file', 'output']
-
+STRIP = 0
 
 def make_path_absolute(path: str, directory: str) -> str:
   if os.path.isabs(path):
@@ -43,6 +43,8 @@
 
 
 def remove_directory_path(path: str, directory: str) -> str:
+  dirsplit = directory.split(os.path.sep)
+  directory = os.path.sep.join(dirsplit[:-STRIP])
   if os.path.commonpath([path, directory]) != directory:
     return path
 
@@ -113,7 +115,10 @@
   parser = argparse.ArgumentParser()
   parser.add_argument('json_filename', type=str)
   parser.add_argument('-o', '--output', type=str)
+  parser.add_argument('-p', '--strip', type=int)
   args = parser.parse_args()
+  if args.strip:
+    STRIP = int(args.strip)
   output = args.output if args.output else 'normalized_' + os.path.basename(
       args.json_filename)
   main(args.json_filename, output)
diff --git a/src/tools/gyp/pylib/gyp/MSVSVersion.py b/src/tools/gyp/pylib/gyp/MSVSVersion.py
index f51c3a9..6478510 100644
--- a/src/tools/gyp/pylib/gyp/MSVSVersion.py
+++ b/src/tools/gyp/pylib/gyp/MSVSVersion.py
@@ -324,10 +324,13 @@
         if not os.path.exists(path):
           path = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE'
       path = _ConvertToCygpath(path)
-      full_path = os.path.join(path, 'devenv.exe')
-      if os.path.exists(full_path) and version in version_to_year:
+      devenv_path = os.path.join(path, 'devenv.exe')
+      devenv_ini_path = os.path.join(path, 'devenv.isolation.ini')
+      if (os.path.exists(devenv_path) or os.path.exists(devenv_ini_path)) and version in version_to_year:
         versions.append(_CreateVersion(version_to_year[version],
             os.path.join(path, '..', '..')))
+      else:
+          print('_DetectVisualStudioVersion() did not find Visual Studio 2017 (v15.0)')
       continue
     # Old method of searching for which VS version is installed
     # We don't use the 2010-encouraged-way because we also want to get the
diff --git a/src/tools/protoc_wrapper/gn_protoc_wrapper.py b/src/tools/protoc_wrapper/gn_protoc_wrapper.py
new file mode 100644
index 0000000..dc8a3f1
--- /dev/null
+++ b/src/tools/protoc_wrapper/gn_protoc_wrapper.py
@@ -0,0 +1,212 @@
+#!/usr/bin/env python3
+# 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.
+
+"""
+A simple wrapper for protoc.
+Script for //third_party/protobuf/proto_library.gni .
+Features:
+- Inserts #include for extra header automatically.
+- Prevents bad proto names.
+- Works around protoc's bad descriptor file generation.
+  Ninja expects the format:
+  target: deps
+  But protoc just outputs:
+  deps
+  This script adds the "target:" part.
+"""
+
+from __future__ import print_function
+import argparse
+import os.path
+import subprocess
+import sys
+import tempfile
+
+PROTOC_INCLUDE_POINT = "// @@protoc_insertion_point(includes)"
+
+
+def FormatGeneratorOptions(options):
+  if not options:
+    return ""
+  if options.endswith(":"):
+    return options
+  return options + ":"
+
+
+def VerifyProtoNames(protos):
+  for filename in protos:
+    if "-" in filename:
+      raise RuntimeError("Proto file names must not contain hyphens "
+                         "(see http://crbug.com/386125 for more information).")
+
+
+def StripProtoExtension(filename):
+  if not filename.endswith(".proto"):
+    raise RuntimeError("Invalid proto filename extension: "
+                       "{0} .".format(filename))
+  return filename.rsplit(".", 1)[0]
+
+
+def WriteIncludes(headers, include):
+  for filename in headers:
+    include_point_found = False
+    contents = []
+    with open(filename) as f:
+      for line in f:
+        stripped_line = line.strip()
+        contents.append(stripped_line)
+        if stripped_line == PROTOC_INCLUDE_POINT:
+          if include_point_found:
+            raise RuntimeError("Multiple include points found.")
+          include_point_found = True
+          extra_statement = "#include \"{0}\"".format(include)
+          contents.append(extra_statement)
+
+      if not include_point_found:
+        raise RuntimeError("Include point not found in header: "
+                           "{0} .".format(filename))
+
+    with open(filename, "w") as f:
+      for line in contents:
+        print(line, file=f)
+
+
+def main(argv):
+  parser = argparse.ArgumentParser()
+  parser.add_argument("--protoc", required=True,
+                      help="Relative path to compiler.")
+
+  parser.add_argument("--proto-in-dir", required=True,
+                      help="Base directory with source protos.")
+  parser.add_argument("--cc-out-dir",
+                      help="Output directory for standard C++ generator.")
+  parser.add_argument("--py-out-dir",
+                      help="Output directory for standard Python generator.")
+  parser.add_argument("--js-out-dir",
+                      help="Output directory for standard JS generator.")
+  parser.add_argument("--plugin-out-dir",
+                      help="Output directory for custom generator plugin.")
+
+  parser.add_argument('--enable-kythe-annotations', action='store_true',
+                      help='Enable generation of Kythe kzip, used for '
+                      'codesearch.')
+  parser.add_argument("--plugin",
+                      help="Relative path to custom generator plugin.")
+  parser.add_argument("--plugin-options",
+                      help="Custom generator plugin options.")
+  parser.add_argument("--cc-options",
+                      help="Standard C++ generator options.")
+  parser.add_argument("--include",
+                      help="Name of include to insert into generated headers.")
+  parser.add_argument("--import-dir", action="append", default=[],
+                      help="Extra import directory for protos, can be repeated."
+  )
+  parser.add_argument("--descriptor-set-out",
+                      help="Path to write a descriptor.")
+  parser.add_argument(
+      "--descriptor-set-dependency-file",
+      help="Path to write the dependency file for descriptor set.")
+  # The meaning of this flag is flipped compared to the corresponding protoc
+  # flag due to this script previously passing --include_imports. Removing the
+  # --include_imports is likely to have unintended consequences.
+  parser.add_argument(
+      "--exclude-imports",
+      help="Do not include imported files into generated descriptor.",
+      action="store_true",
+      default=False)
+  parser.add_argument("protos", nargs="+",
+                      help="Input protobuf definition file(s).")
+
+  options = parser.parse_args(argv)
+
+  proto_dir = os.path.relpath(options.proto_in_dir)
+  protoc_cmd = [os.path.realpath(options.protoc)]
+
+  protos = options.protos
+  headers = []
+  VerifyProtoNames(protos)
+
+  if options.py_out_dir:
+    protoc_cmd += ["--python_out", options.py_out_dir]
+
+  if options.js_out_dir:
+    protoc_cmd += [
+        "--js_out",
+        "one_output_file_per_input_file,binary:" + options.js_out_dir
+    ]
+
+  if options.cc_out_dir:
+    cc_out_dir = options.cc_out_dir
+    cc_options_list = []
+    if options.enable_kythe_annotations:
+      cc_options_list.extend([
+          'annotate_headers', 'annotation_pragma_name=kythe_metadata',
+          'annotation_guard_name=KYTHE_IS_RUNNING'
+      ])
+
+    # cc_options will likely have trailing colon so needs to be inserted at the
+    # end.
+    if options.cc_options:
+      cc_options_list.append(options.cc_options)
+
+    cc_options = FormatGeneratorOptions(','.join(cc_options_list))
+    protoc_cmd += ["--cpp_out", cc_options + cc_out_dir]
+    for filename in protos:
+      stripped_name = StripProtoExtension(filename)
+      headers.append(os.path.join(cc_out_dir, stripped_name + ".pb.h"))
+
+  if options.plugin_out_dir:
+    plugin_options = FormatGeneratorOptions(options.plugin_options)
+    protoc_cmd += [
+      "--plugin", "protoc-gen-plugin=" + os.path.relpath(options.plugin),
+      "--plugin_out", plugin_options + options.plugin_out_dir
+    ]
+
+  protoc_cmd += ["--proto_path", proto_dir]
+  for path in options.import_dir:
+    protoc_cmd += ["--proto_path", path]
+
+  protoc_cmd += [os.path.join(proto_dir, name) for name in protos]
+
+  if options.descriptor_set_out:
+    protoc_cmd += ["--descriptor_set_out", options.descriptor_set_out]
+    if not options.exclude_imports:
+      protoc_cmd += ["--include_imports"]
+
+  dependency_file_data = None
+  if options.descriptor_set_out and options.descriptor_set_dependency_file:
+    protoc_cmd += ['--dependency_out', options.descriptor_set_dependency_file]
+    ret = subprocess.call(protoc_cmd)
+
+    with open(options.descriptor_set_dependency_file, 'rb') as f:
+      dependency_file_data = f.read().decode('utf-8')
+
+  ret = subprocess.call(protoc_cmd)
+  if ret != 0:
+    if ret <= -100:
+      # Windows error codes such as 0xC0000005 and 0xC0000409 are much easier to
+      # recognize and differentiate in hex. In order to print them as unsigned
+      # hex we need to add 4 Gig to them.
+      error_number = "0x%08X" % (ret + (1 << 32))
+    else:
+      error_number = "%d" % ret
+    raise RuntimeError("Protoc has returned non-zero status: "
+                       "{0}".format(error_number))
+
+  if dependency_file_data:
+    with open(options.descriptor_set_dependency_file, 'w') as f:
+      f.write(options.descriptor_set_out + ":")
+      f.write(dependency_file_data)
+
+  if options.include:
+    WriteIncludes(headers, options.include)
+
+
+if __name__ == "__main__":
+  try:
+    main(sys.argv[1:])
+  except RuntimeError as e:
+    print(e, file=sys.stderr)
+    sys.exit(1)