Import Cobalt 24.lts.5.1032616
diff --git a/.github/config/linux-modular.json b/.github/config/linux-modular.json
index ee3f9b1..c4e25d4 100644
--- a/.github/config/linux-modular.json
+++ b/.github/config/linux-modular.json
@@ -10,7 +10,7 @@
       "name":"modular",
       "platform":"linux-x64x11-modular",
       "target_platform":"linux-x64x11",
-      "extra_gn_arguments":"build_with_separate_cobalt_toolchain=true"
+      "extra_gn_arguments":"build_with_separate_cobalt_toolchain=true use_contrib_cast=true"
     }
   ]
 }
diff --git a/base/i18n/base_i18n_export.h b/base/i18n/base_i18n_export.h
index e8a2add..ef498e9 100644
--- a/base/i18n/base_i18n_export.h
+++ b/base/i18n/base_i18n_export.h
@@ -5,7 +5,11 @@
 #ifndef BASE_I18N_BASE_I18N_EXPORT_H_
 #define BASE_I18N_BASE_I18N_EXPORT_H_
 
-#if defined(COMPONENT_BUILD)
+#ifdef USE_COBALT_CUSTOMIZATIONS
+#include "starboard/configuration.h"
+#endif // USE_COBALT_CUSTOMIZATIONS
+
+#if defined(COMPONENT_BUILD) || SB_IS(MODULAR) && !SB_IS(EVERGREEN)
 #if defined(WIN32)
 
 #if defined(BASE_I18N_IMPLEMENTATION)
@@ -22,7 +26,7 @@
 #endif
 #endif
 
-#else  // defined(COMPONENT_BUILD)
+#else  // defined(COMPONENT_BUILD) || SB_IS(MODULAR) && !SB_IS(EVERGREEN)
 #define BASE_I18N_EXPORT
 #endif
 
diff --git a/build/config/win/visual_studio_version.gni b/build/config/win/visual_studio_version.gni
index 63b9a97..22ec3af 100644
--- a/build/config/win/visual_studio_version.gni
+++ b/build/config/win/visual_studio_version.gni
@@ -39,6 +39,8 @@
 
   declare_args() {
     msvc_path = "$visual_studio_path/VC/Tools/MSVC/$visual_studio_version"
+
+    llvm_clang_path = "$visual_studio_path/VC/Tools/Llvm/x64/bin"
   }
 } else {
   declare_args() {
diff --git a/build/toolchain/gcc_toolchain.gni b/build/toolchain/gcc_toolchain.gni
index 89b26d3..2a2cc3c 100644
--- a/build/toolchain/gcc_toolchain.gni
+++ b/build/toolchain/gcc_toolchain.gni
@@ -117,7 +117,7 @@
 template("gcc_toolchain") {
   toolchain(target_name) {
     is_starboard_toolchain = target_name == "starboard"
-    if (!build_with_separate_cobalt_toolchain) {
+    if (!sb_is_modular || sb_is_evergreen) {
       not_needed(["is_starboard_toolchain"])
     }
     assert(defined(invoker.ar), "gcc_toolchain() must specify a \"ar\" value")
@@ -391,7 +391,7 @@
       # TODO(b/206642994): see if we can remove this condition. It's needed for
       # now to add cflags for evergreen platforms but we haven't yet decided
       # whether cflags should be added here for all platforms.
-      if (is_starboard && sb_is_evergreen && !is_starboard_toolchain) {
+      if (!is_starboard_toolchain && is_starboard && sb_is_modular) {
         command = "$asm -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{cflags}} {{asmflags}}${extra_asmflags} -c {{source}} -o {{output}}"
       } else {
         command = "$asm -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{asmflags}}${extra_asmflags} -c {{source}} -o {{output}}"
diff --git a/cobalt/BUILD.gn b/cobalt/BUILD.gn
index 53c4bc3..92fe9dc 100644
--- a/cobalt/BUILD.gn
+++ b/cobalt/BUILD.gn
@@ -81,8 +81,5 @@
         "//components/update_client",
       ]
     }
-    if (!build_with_separate_cobalt_toolchain) {
-      deps += [ "//third_party/llvm-project/libunwind:unwind_evergreen" ]
-    }
   }
 }
diff --git a/cobalt/browser/BUILD.gn b/cobalt/browser/BUILD.gn
index 3228260..25e387a 100644
--- a/cobalt/browser/BUILD.gn
+++ b/cobalt/browser/BUILD.gn
@@ -55,6 +55,7 @@
     ":browser",
     ":browser_switches",
     "//cobalt/base",
+    "//cobalt/css_parser",
     "//net",
   ]
   data_deps = [
@@ -414,3 +415,10 @@
 cache_templates("cached_jinja_templates") {
   output_dir = _bindings_scripts_output_dir
 }
+
+group("test_dependencies_on_browser") {
+  testonly = true
+
+  # TODO: 297087147 - Depend on smaller targets than browser.
+  deps = [ "//cobalt/browser" ]
+}
diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc
index d83200f..c6f7f18 100644
--- a/cobalt/browser/browser_module.cc
+++ b/cobalt/browser/browser_module.cc
@@ -492,33 +492,51 @@
 
   // First try any registered handlers. If one of these handles the URL, we
   // don't use the web module.
-  if (TryURLHandlers(url)) {
+  if (NavigateTryURLHandlers(url)) {
     return;
   }
 
   // Clear error handling once we're told to navigate, either because it's the
   // retry from the error or something decided we should navigate elsewhere.
-  on_error_retry_timer_.Stop();
-  waiting_for_error_retry_ = false;
+  NavigateResetErrorHandling();
 
   // Navigations aren't allowed if the app is frozen. If this is the case,
   // simply set the pending navigate url, which will cause the navigation to
   // occur when Cobalt resumes, and return.
-  if (application_state_ == base::kApplicationStateFrozen) {
-    pending_navigate_url_ = url;
+  if (NavigateHandleStateFrozen(url)) {
     return;
   }
 
-  // Now that we know the navigation is occurring, clear out
-  // |pending_navigate_url_|.
-  pending_navigate_url_ = GURL::EmptyGURL();
+  // Destroys the old WebModule, increments the navigation generation
+  // number, and resets the main WebModule layer
+  NavigateResetWebModule();
 
+  // checks whether a service worker should be started for the given URL
+  auto service_worker_started_event = std::make_unique<base::WaitableEvent>();
+  bool can_start_service_worker =
+      NavigateServiceWorkerSetups(url, service_worker_started_event.get());
+
+  // Wait until after the old WebModule is destroyed before setting the navigate
+  // time so that it won't be included in the time taken to load the URL.
+  navigate_time_ = base::TimeTicks::Now().ToInternalValue();
+
+  const ViewportSize viewport_size = GetViewportSize();
+
+  // Show a splash screen while we're waiting for the web page to load.
+  NavigateSetupSplashScreen(url, viewport_size);
+
+  NavigateSetupScrollEngine();
+
+  NavigateCreateWebModule(url, can_start_service_worker,
+                          service_worker_started_event.get(), viewport_size);
+}
+
+void BrowserModule::NavigateResetWebModule() {
 #if defined(ENABLE_DEBUGGER)
   if (web_module_) {
     web_module_->FreezeDebugger(&debugger_state_);
   }
 #endif  // defined(ENABLE_DEBUGGER)
-
   // Destroy old WebModule first, so we don't get a memory high-watermark after
   // the second WebModule's constructor runs, but before
   // std::unique_ptr::reset() is run.
@@ -534,23 +552,56 @@
   current_main_web_module_timeline_id_ = next_timeline_id_++;
 
   main_web_module_layer_->Reset();
+}
 
+void BrowserModule::NavigateResetErrorHandling() {
+  on_error_retry_timer_.Stop();
+  waiting_for_error_retry_ = false;
+}
+
+bool BrowserModule::NavigateHandleStateFrozen(const GURL& url) {
+  if (application_state_ == base::kApplicationStateFrozen) {
+    pending_navigate_url_ = url;
+    return true;
+  }
+
+  // Now that we know the navigation is occurring, clear out
+  // |pending_navigate_url_|.
+  pending_navigate_url_ = GURL::EmptyGURL();
+  return false;
+}
+
+bool BrowserModule::NavigateServiceWorkerSetups(
+    const GURL& url, base::WaitableEvent* service_worker_started_event) {
   // Service worker should only start for HTTP or HTTPS fetches.
   // https://fetch.spec.whatwg.org/commit-snapshots/8f8ab504da6ca9681db5c7f8aa3d1f4b6bf8840c/#http-fetch
   bool can_start_service_worker = url.SchemeIsHTTPOrHTTPS();
-  auto service_worker_started_event = std::make_unique<base::WaitableEvent>();
+  watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
+  if (watchdog) {
+    std::vector<std::string> service_worker_clients = {
+        worker::WorkerConsts::kServiceWorkerRegistryName,
+        worker::WorkerConsts::kServiceWorkerName};
+    std::string violation_json =
+        watchdog->GetWatchdogViolations(service_worker_clients);
+    {
+      if (violation_json != "") {
+        LOG(WARNING) << "Service Worker watchdog violation detected: "
+                     << violation_json;
+        LOG(WARNING) << "Erase Service Worker registration map.";
+        can_start_service_worker = false;
+        service_worker_registry_->EraseRegistrationMap();
+      }
+    }
+  }
   if (can_start_service_worker) {
     service_worker_registry_->EnsureServiceWorkerStarted(
-        url::Origin::Create(url), url, service_worker_started_event.get());
+        url::Origin::Create(url), url, service_worker_started_event);
   }
+  return can_start_service_worker;
+}
 
-  // Wait until after the old WebModule is destroyed before setting the navigate
-  // time so that it won't be included in the time taken to load the URL.
-  navigate_time_ = base::TimeTicks::Now().ToInternalValue();
-
-  // Show a splash screen while we're waiting for the web page to load.
-  const ViewportSize viewport_size = GetViewportSize();
-
+void BrowserModule::NavigateSetupSplashScreen(
+    const GURL& url, const ViewportSize viewport_size) {
   DestroySplashScreen();
   if (options_.enable_splash_screen_on_reloads ||
       main_web_module_generation_ == 1) {
@@ -571,10 +622,17 @@
       lifecycle_observers_.AddObserver(splash_screen_.get());
     }
   }
+}
 
+void BrowserModule::NavigateSetupScrollEngine() {
   scroll_engine_.reset(new ui_navigation::scroll_engine::ScrollEngine());
   scroll_engine_->thread()->Start();
+}
 
+void BrowserModule::NavigateCreateWebModule(
+    const GURL& url, bool can_start_service_worker,
+    base::WaitableEvent* service_worker_started_event,
+    const ViewportSize viewport_size) {
 // Create new WebModule.
 #if !defined(COBALT_FORCE_CSP)
   options_.web_module_options.csp_insecure_allowed_token =
@@ -1345,7 +1403,7 @@
   }
 }
 
-bool BrowserModule::TryURLHandlers(const GURL& url) {
+bool BrowserModule::NavigateTryURLHandlers(const GURL& url) {
   for (URLHandlerCollection::const_iterator iter = url_handlers_.begin();
        iter != url_handlers_.end(); ++iter) {
     if (iter->Run(url)) {
diff --git a/cobalt/browser/browser_module.h b/cobalt/browser/browser_module.h
index 00fd5ce..2011577 100644
--- a/cobalt/browser/browser_module.h
+++ b/cobalt/browser/browser_module.h
@@ -139,6 +139,7 @@
   // |pending_navigate_url_| to the specified url, which will trigger a
   // navigation when Cobalt resumes.
   void Navigate(const GURL& url_reference);
+
   // Reloads web module.
   void Reload();
 
@@ -334,9 +335,28 @@
   bool FilterKeyEventForHotkeys(base::Token type,
                                 const dom::KeyboardEventInit& event);
 
+  void NavigateResetErrorHandling();
+
+  bool NavigateHandleStateFrozen(const GURL& url);
+
+  void NavigateResetWebModule();
+
+  bool NavigateServiceWorkerSetups(
+      const GURL& url, base::WaitableEvent* service_worker_started_event);
+
+  void NavigateSetupSplashScreen(const GURL& url,
+                                 const cssom::ViewportSize viewport_size);
+
+  void NavigateSetupScrollEngine();
+
+  void NavigateCreateWebModule(
+      const GURL& url, bool can_start_service_worker,
+      base::WaitableEvent* service_worker_started_event,
+      const cssom::ViewportSize viewport_size);
+
   // Tries all registered URL handlers for a URL. Returns true if one of the
   // handlers handled the URL, false if otherwise.
-  bool TryURLHandlers(const GURL& url);
+  bool NavigateTryURLHandlers(const GURL& url);
 
   // Destroys the splash screen, if currently displayed.
   void DestroySplashScreen(base::TimeDelta close_time = base::TimeDelta());
diff --git a/cobalt/browser/main.cc b/cobalt/browser/main.cc
index 7d0d0a0..ff13b37 100644
--- a/cobalt/browser/main.cc
+++ b/cobalt/browser/main.cc
@@ -18,6 +18,7 @@
 #include "cobalt/base/wrap_main.h"
 #include "cobalt/browser/application.h"
 #include "cobalt/browser/switches.h"
+#include "cobalt/css_parser/switches.h"
 #include "cobalt/version.h"
 
 namespace {
@@ -41,6 +42,7 @@
           cobalt::browser::switches::kHelp)) {
     SbLogRaw("Options: \n");
     SbLogRaw(cobalt::browser::switches::HelpMessage().c_str());
+    SbLogRaw(cobalt::css_parser::switches::HelpMessage().c_str());
     return true;
   }
   return false;
diff --git a/cobalt/browser/metrics/BUILD.gn b/cobalt/browser/metrics/BUILD.gn
index 3f25f1a..62c85fa 100644
--- a/cobalt/browser/metrics/BUILD.gn
+++ b/cobalt/browser/metrics/BUILD.gn
@@ -51,6 +51,7 @@
   deps = [
     ":metrics",
     "//base",
+    "//cobalt//browser:test_dependencies_on_browser",
     "//cobalt/browser:generated_types",
     "//cobalt/h5vcc",
     "//cobalt/h5vcc:metric_event_handler_wrapper",
diff --git a/cobalt/browser/service_worker_registry.cc b/cobalt/browser/service_worker_registry.cc
index bbfdf0f..89b9395 100644
--- a/cobalt/browser/service_worker_registry.cc
+++ b/cobalt/browser/service_worker_registry.cc
@@ -31,8 +31,6 @@
 // Signals the given WaitableEvent.
 void SignalWaitableEvent(base::WaitableEvent* event) { event->Signal(); }
 
-// The watchdog client name used to represent service worker registry thread.
-const char kWatchdogName[] = "service worker registry";
 // The watchdog time interval in microseconds allowed between pings before
 // triggering violations.
 const int64_t kWatchdogTimeInterval = 15000000;
@@ -61,7 +59,8 @@
   // Registers service worker thread as a watchdog client.
   if (watchdog) {
     watchdog_registered_ = true;
-    watchdog->Register(kWatchdogName, kWatchdogName,
+    watchdog->Register(worker::WorkerConsts::kServiceWorkerRegistryName,
+                       worker::WorkerConsts::kServiceWorkerRegistryName,
                        base::kApplicationStateStarted, kWatchdogTimeInterval,
                        kWatchdogTimeWait, watchdog::PING);
     message_loop()->task_runner()->PostDelayedTask(
@@ -101,7 +100,7 @@
   watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
   if (watchdog) {
     watchdog_registered_ = false;
-    watchdog->Unregister(kWatchdogName);
+    watchdog->Unregister(worker::WorkerConsts::kServiceWorkerRegistryName);
   }
 
   // Ensure that the destruction observer got added before stopping the thread.
@@ -120,7 +119,7 @@
   // If watchdog is already unregistered or shut down, stop ping watchdog.
   if (!watchdog_registered_ || !watchdog) return;
 
-  watchdog->Ping(kWatchdogName);
+  watchdog->Ping(worker::WorkerConsts::kServiceWorkerRegistryName);
   message_loop()->task_runner()->PostDelayedTask(
       FROM_HERE,
       base::Bind(&ServiceWorkerRegistry::PingWatchdog, base::Unretained(this)),
@@ -134,6 +133,10 @@
                                                        done_event);
 }
 
+void ServiceWorkerRegistry::EraseRegistrationMap() {
+  service_worker_context()->EraseRegistrationMap();
+}
+
 worker::ServiceWorkerContext* ServiceWorkerRegistry::service_worker_context() {
   // Ensure that the thread had a chance to allocate the object.
   destruction_observer_added_.Wait();
diff --git a/cobalt/browser/service_worker_registry.h b/cobalt/browser/service_worker_registry.h
index 4880d6a..f0e934e 100644
--- a/cobalt/browser/service_worker_registry.h
+++ b/cobalt/browser/service_worker_registry.h
@@ -49,6 +49,8 @@
                                   const GURL& client_url,
                                   base::WaitableEvent* done_event);
 
+  void EraseRegistrationMap();
+
   worker::ServiceWorkerContext* service_worker_context();
 
  private:
diff --git a/cobalt/build/build_info.py b/cobalt/build/build_info.py
index aed2646..52f4494 100755
--- a/cobalt/build/build_info.py
+++ b/cobalt/build/build_info.py
@@ -57,7 +57,7 @@
   output = subprocess.check_output(['git', 'rev-list', '--count', 'HEAD'],
                                    cwd=cwd)
   build_id = int(output.strip().decode()) + COMMIT_COUNT_BUILD_ID_OFFSET
-  return build_id
+  return str(build_id)
 
 
 def _get_last_commit_with_format(placeholder, cwd):
diff --git a/cobalt/build/get_build_id_test.py b/cobalt/build/get_build_id_test.py
index 562179f..26e7b2b 100644
--- a/cobalt/build/get_build_id_test.py
+++ b/cobalt/build/get_build_id_test.py
@@ -87,8 +87,9 @@
       self.make_commit()
     build_number = build_info.get_build_id_from_commit_count(
         cwd=self.test_dir.name)
-    self.assertEqual(build_number,
-                     num_commits + build_info.COMMIT_COUNT_BUILD_ID_OFFSET)
+    self.assertEqual(
+        int(build_number),
+        num_commits + build_info.COMMIT_COUNT_BUILD_ID_OFFSET)
 
   def testCommitsOutrankCommitCount(self):
     self.make_commit()
@@ -102,8 +103,9 @@
     for _ in range(num_commits):
       self.make_commit()
     build_number = get_build_id.main(cwd=self.test_dir.name)
-    self.assertEqual(build_number,
-                     num_commits + build_info.COMMIT_COUNT_BUILD_ID_OFFSET)
+    self.assertEqual(
+        int(build_number),
+        num_commits + build_info.COMMIT_COUNT_BUILD_ID_OFFSET)
 
 
 if __name__ == '__main__':
diff --git a/cobalt/build/gn.py b/cobalt/build/gn.py
index 7ff3bc4..e8c4472 100755
--- a/cobalt/build/gn.py
+++ b/cobalt/build/gn.py
@@ -68,7 +68,7 @@
   parser.add_argument(
       '-p',
       '--platform',
-      required=True,
+      default='linux-x64x11',
       choices=list(PLATFORMS),
       help='The platform to build.')
   parser.add_argument(
diff --git a/cobalt/css_parser/BUILD.gn b/cobalt/css_parser/BUILD.gn
index d6a668c..8cbc194 100644
--- a/cobalt/css_parser/BUILD.gn
+++ b/cobalt/css_parser/BUILD.gn
@@ -76,6 +76,8 @@
     "shadow_property_parse_structures.cc",
     "shadow_property_parse_structures.h",
     "string_pool.h",
+    "switches.cc",
+    "switches.h",
     "text_decoration_shorthand_property_parse_structures.cc",
     "text_decoration_shorthand_property_parse_structures.h",
     "transition_shorthand_property_parse_structures.cc",
@@ -92,6 +94,7 @@
 
   deps = [
     ":css_grammar",
+    "//base",
     "//cobalt/base",
     "//cobalt/cssom",
     "//nb",
@@ -119,7 +122,9 @@
     ":css_grammar",
     ":css_parser",
     "//base",
+    "//cobalt/browser:test_dependencies_on_browser",
     "//cobalt/cssom",
+    "//cobalt/dom",
     "//cobalt/test:run_all_unittests",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/cobalt/css_parser/parser.cc b/cobalt/css_parser/parser.cc
index ccc9b42..aaaf474 100644
--- a/cobalt/css_parser/parser.cc
+++ b/cobalt/css_parser/parser.cc
@@ -23,6 +23,7 @@
 #include <string>
 
 #include "base/bind.h"
+#include "base/command_line.h"
 #include "base/containers/hash_tables.h"
 #include "base/lazy_instance.h"
 #include "base/optional.h"
@@ -39,6 +40,7 @@
 #include "cobalt/css_parser/ref_counted_util.h"
 #include "cobalt/css_parser/scanner.h"
 #include "cobalt/css_parser/string_pool.h"
+#include "cobalt/css_parser/switches.h"
 #include "cobalt/css_parser/trivial_string_piece.h"
 #include "cobalt/css_parser/trivial_type_pairs.h"
 #include "cobalt/cssom/active_pseudo_class.h"
@@ -539,11 +541,19 @@
 void LogWarningCallback(const ::base::DebuggerHooks* debugger_hooks,
                         const std::string& message) {
   CLOG(WARNING, *debugger_hooks) << message;
+  ::base::CommandLine* command_line = ::base::CommandLine::ForCurrentProcess();
+  if (command_line->GetSwitchValueASCII(switches::kOnCssWarning) == "crash") {
+    IMMEDIATE_CRASH() << message;
+  }
 }
 
 void LogErrorCallback(const ::base::DebuggerHooks* debugger_hooks,
                       const std::string& message) {
   CLOG(ERROR, *debugger_hooks) << message;
+  ::base::CommandLine* command_line = ::base::CommandLine::ForCurrentProcess();
+  if (command_line->GetSwitchValueASCII(switches::kOnCssError) == "crash") {
+    IMMEDIATE_CRASH() << message;
+  }
 }
 
 }  // namespace
diff --git a/cobalt/css_parser/switches.cc b/cobalt/css_parser/switches.cc
new file mode 100644
index 0000000..bb4e085
--- /dev/null
+++ b/cobalt/css_parser/switches.cc
@@ -0,0 +1,50 @@
+// Copyright 2023 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/css_parser/switches.h"
+
+#include <map>
+
+namespace cobalt {
+namespace css_parser {
+namespace switches {
+
+const char kOnCssError[] = "on_css_error";
+const char kOnCssErrorHelp[] =
+    "If set to \"crash\", crashes on CSS error even when recoverable.";
+
+const char kOnCssWarning[] = "on_css_warning";
+const char kOnCssWarningHelp[] = "If set to \"crash\", crashes on CSS warning.";
+
+std::string HelpMessage() {
+  std::string help_message;
+  std::map<std::string, const char*> help_map{
+      {kOnCssError, kOnCssErrorHelp},
+      {kOnCssWarning, kOnCssWarningHelp},
+  };
+
+  for (const auto& switch_message : help_map) {
+    help_message.append("  --")
+        .append(switch_message.first)
+        .append("\n")
+        .append("  ")
+        .append(switch_message.second)
+        .append("\n\n");
+  }
+  return help_message;
+}
+
+}  // namespace switches
+}  // namespace css_parser
+}  // namespace cobalt
diff --git a/cobalt/css_parser/switches.h b/cobalt/css_parser/switches.h
new file mode 100644
index 0000000..cd3943f
--- /dev/null
+++ b/cobalt/css_parser/switches.h
@@ -0,0 +1,35 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_CSS_PARSER_SWITCHES_H_
+#define COBALT_CSS_PARSER_SWITCHES_H_
+
+#include <string>
+
+namespace cobalt {
+namespace css_parser {
+namespace switches {
+
+extern const char kOnCssError[];
+extern const char kOnCssErrorHelp[];
+extern const char kOnCssWarning[];
+extern const char kOnCssWarningHelp[];
+
+std::string HelpMessage();
+
+}  // namespace switches
+}  // namespace css_parser
+}  // namespace cobalt
+
+#endif  // COBALT_CSS_PARSER_SWITCHES_H_
diff --git a/cobalt/cssom/BUILD.gn b/cobalt/cssom/BUILD.gn
index b313c59..04dae86 100644
--- a/cobalt/cssom/BUILD.gn
+++ b/cobalt/cssom/BUILD.gn
@@ -319,8 +319,10 @@
 
   deps = [
     "//cobalt/base",
+    "//cobalt/browser:test_dependencies_on_browser",
     "//cobalt/css_parser",
     "//cobalt/cssom",
+    "//cobalt/dom",
     "//cobalt/math",
     "//cobalt/test:run_all_unittests",
     "//testing/gmock",
diff --git a/cobalt/h5vcc/BUILD.gn b/cobalt/h5vcc/BUILD.gn
index f081ce4..901f668 100644
--- a/cobalt/h5vcc/BUILD.gn
+++ b/cobalt/h5vcc/BUILD.gn
@@ -28,12 +28,6 @@
   has_pedantic_warnings = true
 
   sources = [
-    "dial/dial_http_request.cc",
-    "dial/dial_http_request.h",
-    "dial/dial_http_response.cc",
-    "dial/dial_http_response.h",
-    "dial/dial_server.cc",
-    "dial/dial_server.h",
     "h5vcc.cc",
     "h5vcc.h",
     "h5vcc_accessibility.cc",
@@ -91,10 +85,23 @@
     "//cobalt/web:dom_exception",
     "//cobalt/worker",
     "//net",
-    "//net:http_server",
     "//starboard",
     "//third_party/protobuf:protobuf_lite",
   ]
+  if (enable_in_app_dial) {
+    sources += [
+      "dial/dial_http_request.cc",
+      "dial/dial_http_request.h",
+      "dial/dial_http_response.cc",
+      "dial/dial_http_response.h",
+      "dial/dial_server.cc",
+      "dial/dial_server.h",
+    ]
+    deps += [
+      "//net:cobalt_dial_server",
+      "//net:http_server",
+    ]
+  }
 
   if (enable_account_manager) {
     sources += [
diff --git a/cobalt/h5vcc/h5vcc_crash_log.cc b/cobalt/h5vcc/h5vcc_crash_log.cc
index b3e76e7..39c545c 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.cc
+++ b/cobalt/h5vcc/h5vcc_crash_log.cc
@@ -191,6 +191,19 @@
   return "";
 }
 
+script::Sequence<std::string> H5vccCrashLog::GetWatchdogViolationClients() {
+  watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
+  script::Sequence<std::string> client_names;
+  if (watchdog) {
+    std::vector<std::string> client_string_names =
+        watchdog->GetWatchdogViolationClientNames();
+    for (std::size_t i = 0; i < client_string_names.size(); ++i) {
+      client_names.push_back(client_string_names[i]);
+    }
+  }
+  return client_names;
+}
+
 bool H5vccCrashLog::GetPersistentSettingWatchdogEnable() {
   watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
   if (watchdog) return watchdog->GetPersistentSettingWatchdogEnable();
diff --git a/cobalt/h5vcc/h5vcc_crash_log.h b/cobalt/h5vcc/h5vcc_crash_log.h
index ea06c88..e01d23e 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.h
+++ b/cobalt/h5vcc/h5vcc_crash_log.h
@@ -81,6 +81,8 @@
   std::string GetWatchdogViolations(
       const script::Sequence<std::string>& clients = {});
 
+  script::Sequence<std::string> GetWatchdogViolationClients();
+
   bool GetPersistentSettingWatchdogEnable();
 
   void SetPersistentSettingWatchdogEnable(bool enable_watchdog);
diff --git a/cobalt/h5vcc/h5vcc_crash_log.idl b/cobalt/h5vcc/h5vcc_crash_log.idl
index 8d4a3ba..e60e0ab 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.idl
+++ b/cobalt/h5vcc/h5vcc_crash_log.idl
@@ -86,6 +86,9 @@
   // }
   DOMString getWatchdogViolations(optional sequence<DOMString> clients);
 
+  // Returns a sequence of the client names that have watchdog violations.
+  sequence<DOMString> getWatchdogViolationClients();
+
   // Gets a persistent Watchdog setting that determines whether or not Watchdog
   // is enabled. When disabled, Watchdog behaves like a stub except that
   // persistent settings can still be get/set. Requires a restart to take
diff --git a/cobalt/h5vcc/h5vcc_storage.cc b/cobalt/h5vcc/h5vcc_storage.cc
index f059e8e..dac5b5c 100644
--- a/cobalt/h5vcc/h5vcc_storage.cc
+++ b/cobalt/h5vcc/h5vcc_storage.cc
@@ -25,7 +25,7 @@
 #include "cobalt/cache/cache.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
 #include "cobalt/storage/storage_manager.h"
-#include "cobalt/worker/service_worker_consts.h"
+#include "cobalt/worker/worker_consts.h"
 #include "net/base/completion_once_callback.h"
 #include "net/disk_cache/cobalt/cobalt_backend_impl.h"
 #include "net/disk_cache/cobalt/resource_type.h"
@@ -419,7 +419,7 @@
                   kSbFileMaxPath);
   base::FilePath service_worker_file_path =
       base::FilePath(storage_dir.data())
-          .Append(worker::ServiceWorkerConsts::kSettingsJson);
+          .Append(worker::WorkerConsts::kSettingsJson);
   base::DeleteFile(service_worker_file_path, /*recursive=*/false);
 }
 
diff --git a/cobalt/layout/BUILD.gn b/cobalt/layout/BUILD.gn
index 7966365..36c0181 100644
--- a/cobalt/layout/BUILD.gn
+++ b/cobalt/layout/BUILD.gn
@@ -140,6 +140,7 @@
   deps = [
     ":layout",
     "//base",
+    "//cobalt//browser:test_dependencies_on_browser",
     "//cobalt/base",
     "//cobalt/css_parser",
     "//cobalt/cssom",
diff --git a/cobalt/loader/BUILD.gn b/cobalt/loader/BUILD.gn
index 2db902d..9b808f9 100644
--- a/cobalt/loader/BUILD.gn
+++ b/cobalt/loader/BUILD.gn
@@ -195,6 +195,7 @@
   deps = [
     ":loader",
     "//cobalt/base:base",
+    "//cobalt/browser:test_dependencies_on_browser",
     "//cobalt/dom",
     "//cobalt/dom_parser",
     "//cobalt/math:math",
diff --git a/cobalt/loader/image/sandbox/BUILD.gn b/cobalt/loader/image/sandbox/BUILD.gn
index adf6ab1..dbfc6a2 100644
--- a/cobalt/loader/image/sandbox/BUILD.gn
+++ b/cobalt/loader/image/sandbox/BUILD.gn
@@ -18,10 +18,12 @@
 # This target will build a sandbox application that allows for easy
 # experimentation with the ImageDecoder on any platform.
 target(final_executable_type, "image_decoder_sandbox") {
+  testonly = true
   sources = [ "image_decoder_sandbox.cc" ]
 
   deps = [
     "//cobalt/base",
+    "//cobalt/browser:test_dependencies_on_browser",
     "//cobalt/loader",
     "//cobalt/loader:copy_loader_test_data",
     "//cobalt/math",
diff --git a/cobalt/media/BUILD.gn b/cobalt/media/BUILD.gn
index 813baa2..0e7c06d 100644
--- a/cobalt/media/BUILD.gn
+++ b/cobalt/media/BUILD.gn
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import("//starboard/build/config/os_definitions.gni")
+
 config("media_config") {
   if (!is_win) {
     cflags_cc = [
@@ -129,6 +131,7 @@
   deps = [
     ":media",
     "//base/test:test_support",
+    "//cobalt//browser:test_dependencies_on_browser",
     "//cobalt/base",
     "//cobalt/test:run_all_unittests",
     "//testing/gmock",
@@ -137,4 +140,9 @@
   ]
 
   data_deps = [ "//cobalt/media/testing:cobalt_media_download_test_data" ]
+
+  # TODO: b/296715826 - Fix symbol resolution in cval_stats_test.cc.
+  if (sb_is_modular && is_host_win) {
+    sources -= [ "base/cval_stats_test.cc" ]
+  }
 }
diff --git a/cobalt/media/sandbox/BUILD.gn b/cobalt/media/sandbox/BUILD.gn
index 19be835..84d2620 100644
--- a/cobalt/media/sandbox/BUILD.gn
+++ b/cobalt/media/sandbox/BUILD.gn
@@ -16,9 +16,11 @@
 # media/renderer interface.
 
 target(final_executable_type, "media_sandbox") {
+  testonly = true
   sources = [ "media2_sandbox.cc" ]
 
   deps = [
+    "//cobalt//browser:test_dependencies_on_browser",
     "//cobalt/base",
     "//cobalt/math",
     "//cobalt/media",
@@ -28,6 +30,7 @@
 }
 
 target(final_executable_type, "web_media_player_sandbox") {
+  testonly = true
   sources = [
     "format_guesstimator.cc",
     "format_guesstimator.h",
@@ -42,6 +45,7 @@
     "//cobalt/base",
 
     # Use test data from demos to avoid keeping two copies of video files.
+    "//cobalt/browser:test_dependencies_on_browser",
     "//cobalt/demos/content:demos_testdata",
     "//cobalt/loader",
     "//cobalt/math",
diff --git a/cobalt/network/BUILD.gn b/cobalt/network/BUILD.gn
index 1a4fd75..73e33ca 100644
--- a/cobalt/network/BUILD.gn
+++ b/cobalt/network/BUILD.gn
@@ -63,10 +63,7 @@
   }
 
   if (enable_in_app_dial) {
-    deps += [
-      # DialService depends on http server.
-      "//net:http_server",
-    ]
+    deps += [ "//net:cobalt_dial_server" ]
   }
 
   # Enable network logging on all but gold builds.
diff --git a/cobalt/renderer/BUILD.gn b/cobalt/renderer/BUILD.gn
index 2b4ac9d..54d2187 100644
--- a/cobalt/renderer/BUILD.gn
+++ b/cobalt/renderer/BUILD.gn
@@ -128,6 +128,7 @@
     ":renderer",
     ":renderer_headers_only",
     "//base:i18n",
+    "//cobalt//browser:test_dependencies_on_browser",
     "//cobalt/base",
     "//cobalt/loader",
     "//cobalt/math",
diff --git a/cobalt/renderer/sandbox/BUILD.gn b/cobalt/renderer/sandbox/BUILD.gn
index 30d7def..bf1b00b 100644
--- a/cobalt/renderer/sandbox/BUILD.gn
+++ b/cobalt/renderer/sandbox/BUILD.gn
@@ -20,10 +20,12 @@
 # also be useful for visually inspecting the output that the Cobalt
 # renderer is producing.
 target(final_executable_type, "renderer_sandbox") {
+  testonly = true
   sources = [ "renderer_sandbox_main.cc" ]
 
   deps = [
     "//cobalt/base",
+    "//cobalt/browser:test_dependencies_on_browser",
     "//cobalt/math",
     "//cobalt/renderer",
     "//cobalt/renderer/test/scenes",
@@ -39,10 +41,12 @@
 # is constantly animating, which for many implementations can be a
 # performance problem.
 target(final_executable_type, "scaling_text_sandbox") {
+  testonly = true
   sources = [ "scaling_text_sandbox_main.cc" ]
 
   deps = [
     "//cobalt/base",
+    "//cobalt/browser:test_dependencies_on_browser",
     "//cobalt/math",
     "//cobalt/renderer",
     "//cobalt/renderer/test/scenes",
diff --git a/cobalt/script/v8c/v8c_exception_state.cc b/cobalt/script/v8c/v8c_exception_state.cc
index ee9fbe0..ee47099 100644
--- a/cobalt/script/v8c/v8c_exception_state.cc
+++ b/cobalt/script/v8c/v8c_exception_state.cc
@@ -70,6 +70,7 @@
 
   V8cGlobalEnvironment* global_environment =
       V8cGlobalEnvironment::GetFromIsolate(isolate_);
+  if (!global_environment) return;
   v8::Local<v8::Object> wrapper =
       global_environment->wrapper_factory()->GetWrapper(exception);
 
diff --git a/cobalt/script/v8c/v8c_global_environment.cc b/cobalt/script/v8c/v8c_global_environment.cc
index 9ec5c07..04e94d1 100644
--- a/cobalt/script/v8c/v8c_global_environment.cc
+++ b/cobalt/script/v8c/v8c_global_environment.cc
@@ -378,7 +378,7 @@
   V8cGlobalEnvironment* global_environment =
       V8cGlobalEnvironment::GetFromIsolate(context->GetIsolate());
   DCHECK(global_environment);
-  if (!global_environment->report_eval_.is_null()) {
+  if (global_environment && !global_environment->report_eval_.is_null()) {
     global_environment->report_eval_.Run();
   }
   // This callback should only be called while code generation from strings is
@@ -396,7 +396,8 @@
   v8::Isolate* isolate = v8::Isolate::GetCurrent();
   V8cGlobalEnvironment* global_environment =
       V8cGlobalEnvironment::GetFromIsolate(isolate);
-  if (isolate->GetEnteredOrMicrotaskContext().IsEmpty()) {
+  if (!global_environment ||
+      isolate->GetEnteredOrMicrotaskContext().IsEmpty()) {
     return;
   }
   if (message->ErrorLevel() != v8::Isolate::kMessageError) {
diff --git a/cobalt/site/docs/development/setup-android.md b/cobalt/site/docs/development/setup-android.md
index 2a5f0b1..82e26aa 100644
--- a/cobalt/site/docs/development/setup-android.md
+++ b/cobalt/site/docs/development/setup-android.md
@@ -26,12 +26,16 @@
     Where 4 is the number of parallel threads. You can adjust the number of
     parallel threads according to how your workstation performs.
 
-1.  Run `cobalt/build/gn.py -p android-x86` to configure the Cobalt build,
-    which also installs the SDK and NDK. (This step will have to be repeated
-    with 'android-arm' or 'android-arm64' to target those architectures.) The
-    SDK and NDK will be downloaded and installed into a `starboard-toolchains`
-    directory as needed. If prompted, read and accept the license agreement to
-    proceed forward.
+1.  Run `starboard/android/shared/download_sdk.sh` to download the SDK and NDK.
+    The SDK and NDK will be downloaded and installed into
+    `~/starboard-toolchains`. If you wish to customize the download location
+    you must set the relevant environment variables accordingly.
+
+    If prompted, read and accept the license agreement.
+
+1.  Run `cobalt/build/gn.py -p android-x86` to configure the Cobalt build.
+    (This step will have to be repeated with 'android-arm' or 'android-arm64'
+    to target those architectures.)
 
     **Note:** If you have trouble building with an error referencing the
     `debug.keystore` you may need to set one up on your system:
diff --git a/cobalt/site/docs/development/setup-windows.md b/cobalt/site/docs/development/setup-windows.md
index 65b9496..47a98cc 100644
--- a/cobalt/site/docs/development/setup-windows.md
+++ b/cobalt/site/docs/development/setup-windows.md
@@ -27,10 +27,10 @@
 

     <aside class="note">

       <b>Note:</b> By default, Cobalt's build system will check

-      C:\Program Files (x86)\ for the Visual Studio install directory. If you

-      installed it elsewhere, you can set the `VSINSTALLDIR` environment

+      <code>C:\Program Files (x86)\</code> for the Visual Studio install directory. If you

+      installed it elsewhere, you can set the <code>VSINSTALLDIR</code> environment

       variable to point to the correct location. For example

-      `C:/Program Files/Microsoft Visual Studio/2022/Professional`

+      <code>C:/Program Files/Microsoft Visual Studio/2022/Professional</code>

     </aside>

 

 1.  Install GN, which we use for our build system code. There are a few ways to

@@ -63,12 +63,12 @@
     ```

 

     <aside class="note">

-      If you plan to contribute to the Cobalt codebase it is recommended that

-      you create your own

-      [fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks)

-      of the [Cobalt repository](https://github.com/youtube/cobalt), apply

-      changes to the fork, and then

-      [create a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork)

+      <b>Note:</b> If you plan to contribute to the Cobalt codebase it is

+      recommended that you create your own

+      <a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks">fork</a>

+      of the <a href="https://github.com/youtube/cobalt">Cobalt repository</a>,

+      apply changes to the fork, and then

+      <a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork">create a pull request</a>

       to merge those changes into the Cobalt repository.

     </aside>

 

@@ -141,7 +141,8 @@
         configuration](/starboard/porting.html#1-enumerate-and-name-your-platform-configurations)

         that identifies the platform. As described in the Starboard porting

         guide, it contains a `family name` (like `linux`) and a

-        `binary variant` (like `x64x11`), separated by a hyphen.

+        `binary variant` (like `x64x11`), separated by a hyphen. For Windows

+        builds use win-win32.

     1.  `<build_type>` is the build you are compiling. Possible values are

         `debug`, `devel`, `qa`, and `gold`.

     1.  `<target_name>` is the name assigned to the compiled code and it is

@@ -222,10 +223,10 @@
 but they should work for local testing.

 

 <aside class="note">

-  <b>Note:</b> if you change the value of `PUBLISHER` in

-  `appx_product_settings.py` you <b>must</b> regenerate a pfx file in order for

-  the packaging step below to work correctly. Follow the instructions in

-  `starboard/xb1/cert/README.md` to generate your own pfx.

+  <b>Note:</b> if you change the value of <code>PUBLISHER</code> in

+  <code>appx_product_settings.py</code> you <b>must</b> regenerate a pfx file in

+  order for the packaging step below to work correctly. Follow the instructions

+  in <code>starboard/xb1/cert/README.md</code> to generate your own pfx.

 </aside>

 

 ### Build Cobalt

diff --git a/cobalt/storage/BUILD.gn b/cobalt/storage/BUILD.gn
index 9c575bd..0bab5d9 100644
--- a/cobalt/storage/BUILD.gn
+++ b/cobalt/storage/BUILD.gn
@@ -55,7 +55,6 @@
     ":storage",
     "//cobalt/base",
     "//cobalt/test:run_all_unittests",
-    "//net",
     "//testing/gmock",
     "//testing/gtest",
     "//url",
diff --git a/cobalt/ui_navigation/scroll_engine/BUILD.gn b/cobalt/ui_navigation/scroll_engine/BUILD.gn
index 980b3fd..25dc91b 100644
--- a/cobalt/ui_navigation/scroll_engine/BUILD.gn
+++ b/cobalt/ui_navigation/scroll_engine/BUILD.gn
@@ -34,6 +34,7 @@
   sources = [ "free_scrolling_nav_item_test.cc" ]
   deps = [
     ":scroll_engine",
+    "//cobalt//browser:test_dependencies_on_browser",
     "//cobalt/base",
     "//cobalt/test:run_all_unittests",
     "//testing/gmock",
diff --git a/cobalt/version.h b/cobalt/version.h
index 9d152df..517e7d5 100644
--- a/cobalt/version.h
+++ b/cobalt/version.h
@@ -35,6 +35,6 @@
 //                  release is cut.
 //.
 
-#define COBALT_VERSION "24.lts.4"
+#define COBALT_VERSION "24.lts.5"
 
 #endif  // COBALT_VERSION_H_
diff --git a/cobalt/watchdog/watchdog.cc b/cobalt/watchdog/watchdog.cc
index 5e2a599..1e43688 100644
--- a/cobalt/watchdog/watchdog.cc
+++ b/cobalt/watchdog/watchdog.cc
@@ -109,6 +109,7 @@
 
   // Starts monitor thread.
   is_monitoring_.store(true);
+  InitializeViolationsMap(this);
   SB_DCHECK(!SbThreadIsValid(watchdog_thread_));
   watchdog_thread_ = SbThreadCreate(0, kSbThreadNoPriority, kSbThreadNoAffinity,
                                     true, "Watchdog", &Watchdog::Monitor, this);
@@ -146,10 +147,17 @@
   return watchdog_file_path_;
 }
 
-std::vector<std::string> Watchdog::GetWatchdogClientNames() {
+std::vector<std::string> Watchdog::GetWatchdogViolationClientNames() {
+  if (pending_write_) WriteWatchdogViolations();
+
+  std::string watchdog_json = ReadViolationFile(GetWatchdogFilePath().c_str());
   std::vector<std::string> names;
-  for (auto& it : client_map_) {
-    names.push_back(it.first);
+  if (watchdog_json != "") {
+    std::unique_ptr<base::Value> violations_map =
+        base::JSONReader::Read(watchdog_json);
+    for (const auto& it : violations_map->DictItems()) {
+      names.push_back(it.first);
+    }
   }
   return names;
 }
@@ -228,8 +236,6 @@
 void Watchdog::UpdateViolationsMap(void* context, Client* client,
                                    SbTimeMonotonic time_delta) {
   // Gets violation dictionary with key client name from violations_map_.
-  if (static_cast<Watchdog*>(context)->violations_map_ == nullptr)
-    InitializeViolationsMap(context);
   base::Value* violation_dict =
       (static_cast<Watchdog*>(context)->violations_map_)->FindKey(client->name);
 
@@ -317,19 +323,26 @@
   static_cast<Watchdog*>(context)->pending_write_ = true;
 }
 
+std::string Watchdog::ReadViolationFile(const char* file_path) {
+  starboard::ScopedFile read_file(file_path, kSbFileOpenOnly | kSbFileRead);
+  if (read_file.IsValid()) {
+    int64_t kFileSize = read_file.GetSize();
+    std::vector<char> buffer(kFileSize + 1, 0);
+    read_file.ReadAll(buffer.data(), kFileSize);
+    return std::string(buffer.data());
+  }
+  return "";
+}
+
 void Watchdog::InitializeViolationsMap(void* context) {
   // Loads the previous Watchdog violations file containing violations before
   // app start, if it exists, to populate violations_map_.
   static_cast<Watchdog*>(context)->violations_count_ = 0;
 
-  starboard::ScopedFile read_file(
-      (static_cast<Watchdog*>(context)->GetWatchdogFilePath()).c_str(),
-      kSbFileOpenOnly | kSbFileRead);
-  if (read_file.IsValid()) {
-    int64_t kFileSize = read_file.GetSize();
-    std::vector<char> buffer(kFileSize + 1, 0);
-    read_file.ReadAll(buffer.data(), kFileSize);
-    std::string watchdog_json = std::string(buffer.data());
+  std::string watchdog_json =
+      static_cast<Watchdog*>(context)->ReadViolationFile(
+          (static_cast<Watchdog*>(context)->GetWatchdogFilePath()).c_str());
+  if (watchdog_json != "") {
     static_cast<Watchdog*>(context)->violations_map_ =
         base::JSONReader::Read(watchdog_json);
   }
@@ -538,48 +551,37 @@
 
   if (pending_write_) WriteWatchdogViolations();
 
-  starboard::ScopedFile file(GetWatchdogFilePath().c_str(),
-                             kSbFileOpenOnly | kSbFileRead | kSbFileWrite);
-  if (file.IsValid()) {
-    int64_t kFileSize = file.GetSize();
-    std::vector<char> buffer(kFileSize + 1, 0);
-    file.ReadAll(buffer.data(), kFileSize);
-    watchdog_json = std::string(buffer.data());
-
-    // If clients is empty we will fetch all clients.
+  if (!static_cast<base::DictionaryValue*>(violations_map_.get())->empty()) {
+    // Get all Watchdog violations if clients is given.
     if (clients.empty()) {
+      // Removes all Watchdog violations.
+      base::JSONWriter::Write(*violations_map_, &watchdog_json_fetched);
       if (clear) {
-        if (violations_map_) {
-          static_cast<base::DictionaryValue*>(violations_map_.get())->Clear();
-          violations_count_ = 0;
-        }
+        static_cast<base::DictionaryValue*>(violations_map_.get())->Clear();
+        violations_count_ = 0;
         starboard::SbFileDeleteRecursive(GetWatchdogFilePath().c_str(), true);
       }
-      watchdog_json_fetched = watchdog_json;
     } else {
-      std::string watchdog_json_not_read = "";
-      std::unique_ptr<base::Value> violations_map =
-          base::JSONReader::Read(watchdog_json);
       base::Value filtered_client_data(base::Value::Type::DICTIONARY);
       for (int i = 0; i < clients.size(); i++) {
-        base::Value* violation_dict = violations_map->FindKey(clients[i]);
+        base::Value* violation_dict =
+            static_cast<base::DictionaryValue*>(violations_map_.get())
+                ->FindKey(clients[i]);
         if (violation_dict != nullptr) {
           filtered_client_data.SetKey(clients[i], (*violation_dict).Clone());
           if (clear) {
             base::Value* violations = violation_dict->FindKey("violations");
             int violations_count = violations->GetList().size();
-            violations_map->RemoveKey(clients[i]);
-            if (violations_map_) {
-              bool result =
-                  static_cast<base::DictionaryValue*>(violations_map_.get())
-                      ->RemoveKey(clients[i]);
-              if (result) {
-                violations_count_ -= violations_count;
-              }
-              if (violations_count_ == 0) {
-                static_cast<base::DictionaryValue*>(violations_map_.get())
-                    ->Clear();
-              }
+
+            static_cast<base::DictionaryValue*>(violations_map_.get())
+                ->RemoveKey(clients[i]);
+            violations_count_ -= violations_count;
+            if (!static_cast<base::DictionaryValue*>(violations_map_.get())
+                     ->empty()) {
+              WriteWatchdogViolations();
+            } else {
+              starboard::SbFileDeleteRecursive(GetWatchdogFilePath().c_str(),
+                                               true);
             }
           }
         }
@@ -587,19 +589,6 @@
       if (!filtered_client_data.DictEmpty()) {
         base::JSONWriter::Write(filtered_client_data, &watchdog_json_fetched);
       }
-      if (clear) {
-        // If all data is fetched, delete the violation file.
-        if (violations_map->DictEmpty()) {
-          starboard::SbFileDeleteRecursive(GetWatchdogFilePath().c_str(), true);
-        } else {
-          base::JSONWriter::Write(*violations_map, &watchdog_json_not_read);
-          file.Seek(kSbFileFromBegin, 0);
-          file.WriteAll(watchdog_json_not_read.c_str(),
-                        static_cast<int>(watchdog_json_not_read.size()));
-          file.Truncate(static_cast<int>(watchdog_json_not_read.size()));
-          time_last_written_microseconds_ = SbTimeGetMonotonicNow();
-        }
-      }
     }
     SB_LOG(INFO) << "[Watchdog] Reading violations:\n" << watchdog_json_fetched;
   } else {
diff --git a/cobalt/watchdog/watchdog.h b/cobalt/watchdog/watchdog.h
index fde3b4b..8aa9a7c 100644
--- a/cobalt/watchdog/watchdog.h
+++ b/cobalt/watchdog/watchdog.h
@@ -82,7 +82,7 @@
   bool InitializeStub();
   void Uninitialize();
   std::string GetWatchdogFilePath();
-  std::vector<std::string> GetWatchdogClientNames();
+  std::vector<std::string> GetWatchdogViolationClientNames();
   void UpdateState(base::ApplicationState state);
   bool Register(std::string name, std::string description,
                 base::ApplicationState monitor_state,
@@ -105,6 +105,7 @@
 
  private:
   void WriteWatchdogViolations();
+  std::string ReadViolationFile(const char* file_path);
   static void* Monitor(void* context);
   static void UpdateViolationsMap(void* context, Client* client,
                                   SbTimeMonotonic time_delta);
diff --git a/cobalt/watchdog/watchdog_test.cc b/cobalt/watchdog/watchdog_test.cc
index 0681ce0..1cafc43 100644
--- a/cobalt/watchdog/watchdog_test.cc
+++ b/cobalt/watchdog/watchdog_test.cc
@@ -325,7 +325,10 @@
   starboard::ScopedFile file(watchdog_->GetWatchdogFilePath().c_str(),
                              kSbFileCreateAlways | kSbFileWrite);
   file.WriteAll(json.c_str(), static_cast<int>(json.size()));
-
+  TearDown();
+  watchdog_ = new watchdog::Watchdog();
+  watchdog_->InitializeCustom(nullptr, std::string(kWatchdogViolationsJson),
+                              kWatchdogMonitorFrequency);
   ASSERT_TRUE(watchdog_->Register("test-name-3", "test-desc-3",
                                   base::kApplicationStateStarted,
                                   kWatchdogMonitorFrequency));
@@ -489,23 +492,26 @@
   ASSERT_NE(write_json, json);
 }
 
-TEST_F(WatchdogTest, GetRegisteredClientNames) {
+TEST_F(WatchdogTest, GetViolationClientNames) {
   ASSERT_TRUE(watchdog_->Register("test-name-1", "test-desc-1",
                                   base::kApplicationStateStarted,
                                   kWatchdogMonitorFrequency));
   ASSERT_TRUE(watchdog_->Register("test-name-2", "test-desc-2",
                                   base::kApplicationStateStarted,
                                   kWatchdogMonitorFrequency));
-  std::vector<std::string> names = watchdog_->GetWatchdogClientNames();
+  SbThreadSleep(kWatchdogSleepDuration);
+  ASSERT_TRUE(watchdog_->Unregister("test-name-1"));
+  ASSERT_TRUE(watchdog_->Unregister("test-name-2"));
+  std::vector<std::string> names = watchdog_->GetWatchdogViolationClientNames();
+  ASSERT_EQ(names.size(), 2);
   std::set<std::string> expected_names = {"test-name-1", "test-name-2"};
   for (std::vector<std::string>::const_iterator it = names.begin();
        it != names.end(); ++it) {
     const std::string name = *it;
     ASSERT_TRUE((expected_names.find(name) != expected_names.end()));
   }
-  ASSERT_TRUE(watchdog_->Unregister("test-name-1"));
-  ASSERT_TRUE(watchdog_->Unregister("test-name-2"));
-  names = watchdog_->GetWatchdogClientNames();
+  watchdog_->GetWatchdogViolations();
+  names = watchdog_->GetWatchdogViolationClientNames();
   ASSERT_EQ(names.size(), 0);
 }
 
diff --git a/cobalt/web/agent.cc b/cobalt/web/agent.cc
index a2f8901..e9f51e4 100644
--- a/cobalt/web/agent.cc
+++ b/cobalt/web/agent.cc
@@ -43,6 +43,7 @@
 #include "cobalt/worker/service_worker_object.h"
 #include "cobalt/worker/service_worker_registration.h"
 #include "cobalt/worker/service_worker_registration_object.h"
+#include "cobalt/worker/worker_consts.h"
 
 namespace cobalt {
 namespace web {
@@ -547,7 +548,7 @@
   }
 
   watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
-  if (watchdog) {
+  if (watchdog && watchdog_registered_) {
     watchdog_registered_ = false;
     watchdog->Unregister(watchdog_name_);
   }
@@ -577,12 +578,15 @@
   if (!thread_.StartWithOptions(thread_options)) return;
   DCHECK(message_loop());
 
+  // Registers service worker thread as a watchdog client.
   watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
 
-  // Registers service worker thread as a watchdog client.
   if (watchdog) {
-    watchdog_name_ =
-        thread_.thread_name() + std::to_string(thread_.GetThreadId());
+    watchdog_name_ = thread_.thread_name();
+    if (watchdog_name_ == worker::WorkerConsts::kServiceWorkerName ||
+        watchdog_name_ == worker::WorkerConsts::kDedicatedWorkerName) {
+      watchdog_name_ += std::to_string(thread_.GetThreadId());
+    }
     watchdog_registered_ = true;
     watchdog->Register(watchdog_name_, watchdog_name_,
                        base::kApplicationStateStarted, kWatchdogTimeInterval,
diff --git a/cobalt/web/cache.cc b/cobalt/web/cache.cc
index a47a3c6..29e3372 100644
--- a/cobalt/web/cache.cc
+++ b/cobalt/web/cache.cc
@@ -198,13 +198,13 @@
     script::EnvironmentSettings* environment_settings,
     std::unique_ptr<script::ValueHandleHolder::Reference> request_reference,
     std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference) {
+  base::AutoLock auto_lock(fetcher_lock_);
   auto* global_environment = get_global_environment(environment_settings);
   auto* isolate = global_environment->isolate();
   script::v8c::EntryScope entry_scope(isolate);
   uint32_t key = cache_utils::GetKey(environment_settings->base_url(),
                                      request_reference->referenced_value());
   if (fetchers_.find(key) != fetchers_.end()) {
-    base::AutoLock auto_lock(*(fetchers_[key]->lock()));
     auto* promises = &(fetch_contexts_[key].first);
     promises->push_back(std::move(promise_reference));
     return;
@@ -402,6 +402,7 @@
 }
 
 void Cache::OnFetchCompleted(uint32_t key, bool success) {
+  base::AutoLock auto_lock(fetcher_lock_);
   auto* environment_settings = fetch_contexts_[key].second;
   auto* context = get_context(environment_settings);
   context->message_loop()->task_runner()->PostTask(
@@ -410,17 +411,15 @@
 }
 
 void Cache::OnFetchCompletedMainThread(uint32_t key, bool success) {
+  base::AutoLock auto_lock(fetcher_lock_);
   auto* fetcher = fetchers_[key].get();
   auto* promises = &(fetch_contexts_[key].first);
   int status = fetcher->response_code();
   // |status| of 200-299 excluding 206 "Partial Content" should be cached.
   if (!success || status == 206 || status < 200 || status > 299) {
-    {
-      base::AutoLock auto_lock(*fetcher->lock());
-      while (promises->size() > 0) {
-        promises->back()->value().Reject();
-        promises->pop_back();
-      }
+    while (promises->size() > 0) {
+      promises->back()->value().Reject();
+      promises->pop_back();
     }
     fetchers_.erase(key);
     fetch_contexts_.erase(key);
@@ -447,12 +446,9 @@
     global_environment->Compile(script::SourceCode::CreateSourceCode(
         fetcher->BufferToString(), base::SourceLocation(__FILE__, 1, 1)));
   }
-  {
-    base::AutoLock auto_lock(*fetcher->lock());
-    while (promises->size() > 0) {
-      promises->back()->value().Resolve();
-      promises->pop_back();
-    }
+  while (promises->size() > 0) {
+    promises->back()->value().Resolve();
+    promises->pop_back();
   }
   fetchers_.erase(key);
   fetch_contexts_.erase(key);
diff --git a/cobalt/web/cache.h b/cobalt/web/cache.h
index 02e5c88..88e0855 100644
--- a/cobalt/web/cache.h
+++ b/cobalt/web/cache.h
@@ -75,7 +75,6 @@
     int response_code() const { return response_code_; }
     const std::string& status_text() const { return status_text_; }
     base::ListValue headers() { return std::move(headers_); }
-    base::Lock* lock() const { return &lock_; }
     std::vector<uint8_t> BufferToVector() const;
     std::string BufferToString() const;
 
@@ -95,7 +94,6 @@
     int response_code_;
     base::ListValue headers_;
     std::string status_text_;
-    mutable base::Lock lock_;
   };
 
   void PerformAdd(
@@ -106,6 +104,7 @@
   void OnFetchCompletedMainThread(uint32_t key, bool success);
 
   std::map<uint32_t, std::unique_ptr<Fetcher>> fetchers_;
+  mutable base::Lock fetcher_lock_;
   std::map<uint32_t, std::pair<std::vector<std::unique_ptr<
                                    script::ValuePromiseVoid::Reference>>,
                                script::EnvironmentSettings*>>
diff --git a/cobalt/web_animations/BUILD.gn b/cobalt/web_animations/BUILD.gn
index 81ee087..6d4f09f 100644
--- a/cobalt/web_animations/BUILD.gn
+++ b/cobalt/web_animations/BUILD.gn
@@ -53,8 +53,10 @@
 
   deps = [
     ":web_animations",
+    "//cobalt/browser:test_dependencies_on_browser",
     "//cobalt/css_parser",
     "//cobalt/cssom",
+    "//cobalt/dom",
     "//cobalt/test:run_all_unittests",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/cobalt/worker/BUILD.gn b/cobalt/worker/BUILD.gn
index 23aeaa1..4b07e54 100644
--- a/cobalt/worker/BUILD.gn
+++ b/cobalt/worker/BUILD.gn
@@ -36,8 +36,6 @@
     "navigation_preload_manager.h",
     "service_worker.cc",
     "service_worker.h",
-    "service_worker_consts.cc",
-    "service_worker_consts.h",
     "service_worker_container.cc",
     "service_worker_container.h",
     "service_worker_context.cc",
@@ -60,6 +58,7 @@
     "window_client.h",
     "worker.cc",
     "worker.h",
+    "worker_consts.h",
     "worker_global_scope.cc",
     "worker_global_scope.h",
     "worker_location.h",
@@ -105,6 +104,7 @@
     "//cobalt/browser:browser",
     "//cobalt/browser:generated_bindings",
     "//cobalt/browser:generated_types",
+    "//cobalt/browser:test_dependencies_on_browser",
     "//cobalt/css_parser",
     "//cobalt/cssom",
     "//cobalt/dom/testing:dom_testing",
diff --git a/cobalt/worker/dedicated_worker.cc b/cobalt/worker/dedicated_worker.cc
index 489a987..783cead 100644
--- a/cobalt/worker/dedicated_worker.cc
+++ b/cobalt/worker/dedicated_worker.cc
@@ -28,10 +28,6 @@
 namespace cobalt {
 namespace worker {
 
-namespace {
-const char kDedicatedWorkerName[] = "DedicatedWorker";
-}  // namespace
-
 DedicatedWorker::DedicatedWorker(script::EnvironmentSettings* settings,
                                  const std::string& scriptURL,
                                  script::ExceptionState* exception_state)
@@ -111,7 +107,7 @@
     options.construction_location.file_path =
         environment_settings()->creation_url().spec();
   }
-  worker_.reset(new Worker(kDedicatedWorkerName, options));
+  worker_.reset(new Worker(WorkerConsts::kDedicatedWorkerName, options));
   // 10. Return worker.
 }
 
diff --git a/cobalt/worker/service_worker_consts.cc b/cobalt/worker/service_worker_consts.cc
deleted file mode 100644
index 4254b85..0000000
--- a/cobalt/worker/service_worker_consts.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2023 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/worker/service_worker_consts.h"
-
-namespace cobalt {
-namespace worker {
-const char ServiceWorkerConsts::kServiceWorkerRegisterBadMIMEError[] =
-    "Service Worker Register failed: The script has an unsupported MIME type "
-    "('%s').";
-
-const char ServiceWorkerConsts::kServiceWorkerRegisterNoMIMEError[] =
-    "Service Worker Register failed: The script does not have a MIME type.";
-
-const char
-    ServiceWorkerConsts::kServiceWorkerRegisterScriptOriginNotSameError[] =
-        "Service Worker Register failed: Script URL ('%s') and referrer ('%s') "
-        "origin are not the same.";
-
-const char
-    ServiceWorkerConsts::kServiceWorkerRegisterScopeOriginNotSameError[] =
-        "Service Worker Register failed: Scope URL ('%s') and referrer ('%s') "
-        "origin are not the same.";
-
-const char ServiceWorkerConsts::kServiceWorkerRegisterBadScopeError[] =
-    "Service Worker Register failed: Scope ('%s') is not allowed.";
-
-const char
-    ServiceWorkerConsts::kServiceWorkerUnregisterScopeOriginNotSameError[] =
-        "Service Worker Unregister failed: Scope origin does not match.";
-
-const char ServiceWorkerConsts::kServiceWorkerAllowed[] =
-    "Service-Worker-Allowed";
-
-const char ServiceWorkerConsts::kSettingsJson[] =
-    "service_worker_settings.json";
-
-const char* const ServiceWorkerConsts::kJavaScriptMimeTypes[16] = {
-    "application/ecmascript",
-    "application/javascript",
-    "application/x-ecmascript",
-    "application/x-javascript",
-    "text/ecmascript",
-    "text/javascript",
-    "text/javascript1.0",
-    "text/javascript1.1",
-    "text/javascript1.2",
-    "text/javascript1.3",
-    "text/javascript1.4",
-    "text/javascript1.5",
-    "text/jscript",
-    "text/livescript",
-    "text/x-ecmascript",
-    "text/x-javascript"};
-}  // namespace worker
-}  // namespace cobalt
diff --git a/cobalt/worker/service_worker_consts.h b/cobalt/worker/service_worker_consts.h
deleted file mode 100644
index 5bb8dd1..0000000
--- a/cobalt/worker/service_worker_consts.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2023 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef COBALT_WORKER_SERVICE_WORKER_CONSTS_H_
-#define COBALT_WORKER_SERVICE_WORKER_CONSTS_H_
-
-namespace cobalt {
-namespace worker {
-
-struct ServiceWorkerConsts {
-  // Constants for error messages.
-  static const char kServiceWorkerRegisterBadMIMEError[];
-  static const char kServiceWorkerRegisterNoMIMEError[];
-  static const char kServiceWorkerRegisterScriptOriginNotSameError[];
-  static const char kServiceWorkerRegisterScopeOriginNotSameError[];
-  static const char kServiceWorkerRegisterBadScopeError[];
-  static const char kServiceWorkerUnregisterScopeOriginNotSameError[];
-  static const char kServiceWorkerAllowed[];
-
-  // Constants for ServiceWorkerPersistentSettings
-  static const char kSettingsJson[];
-
-  // Array of JavaScript mime types, according to the MIME Sniffinc spec:
-  //   https://mimesniff.spec.whatwg.org/#javascript-mime-type
-  static const char* const kJavaScriptMimeTypes[16];
-};
-
-}  // namespace worker
-}  // namespace cobalt
-
-#endif  // COBALT_WORKER_SERVICE_WORKER_CONSTS_H_
diff --git a/cobalt/worker/service_worker_context.cc b/cobalt/worker/service_worker_context.cc
index 5c39206..b4b4bac 100644
--- a/cobalt/worker/service_worker_context.cc
+++ b/cobalt/worker/service_worker_context.cc
@@ -376,6 +376,16 @@
   service_worker_object->ObtainWebAgentAndWaitUntilDone();
 }
 
+void ServiceWorkerContext::EraseRegistrationMap() {
+  if (message_loop() != base::MessageLoop::current()) {
+    message_loop()->task_runner()->PostTask(
+        FROM_HERE, base::BindOnce(&ServiceWorkerContext::EraseRegistrationMap,
+                                  base::Unretained(this)));
+    return;
+  }
+  scope_to_registration_map_->DeletePersistentSettings();
+}
+
 std::string* ServiceWorkerContext::RunServiceWorker(ServiceWorkerObject* worker,
                                                     bool force_bypass_cache) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::RunServiceWorker()");
diff --git a/cobalt/worker/service_worker_context.h b/cobalt/worker/service_worker_context.h
index 1446fe8..e51882d 100644
--- a/cobalt/worker/service_worker_context.h
+++ b/cobalt/worker/service_worker_context.h
@@ -150,6 +150,8 @@
                                   const GURL& client_url,
                                   base::WaitableEvent* done_event);
 
+  void EraseRegistrationMap();
+
   ServiceWorkerJobs* jobs() { return jobs_.get(); }
   ServiceWorkerRegistrationMap* registration_map() {
     return scope_to_registration_map_.get();
diff --git a/cobalt/worker/service_worker_jobs.cc b/cobalt/worker/service_worker_jobs.cc
index 5c93575..fa0451b 100644
--- a/cobalt/worker/service_worker_jobs.cc
+++ b/cobalt/worker/service_worker_jobs.cc
@@ -325,8 +325,7 @@
         PromiseErrorData(
             web::DOMException::kSecurityErr,
             base::StringPrintf(
-                ServiceWorkerConsts::
-                    kServiceWorkerRegisterScriptOriginNotSameError,
+                WorkerConsts::kServiceWorkerRegisterScriptOriginNotSameError,
                 job->script_url.spec().c_str(), job->referrer.spec().c_str())));
     // 2.2. Invoke Finish Job with job and abort these steps.
     FinishJob(job);
@@ -343,8 +342,7 @@
         PromiseErrorData(
             web::DOMException::kSecurityErr,
             base::StringPrintf(
-                ServiceWorkerConsts::
-                    kServiceWorkerRegisterScopeOriginNotSameError,
+                WorkerConsts::kServiceWorkerRegisterScopeOriginNotSameError,
                 job->scope_url.spec().c_str(), job->referrer.spec().c_str())));
 
     // 3.2. Invoke Finish Job with job and abort these steps.
@@ -491,12 +489,11 @@
     if (content_type.empty()) {
       RejectJobPromise(
           state->job,
-          PromiseErrorData(
-              web::DOMException::kSecurityErr,
-              ServiceWorkerConsts::kServiceWorkerRegisterNoMIMEError));
+          PromiseErrorData(web::DOMException::kSecurityErr,
+                           WorkerConsts::kServiceWorkerRegisterNoMIMEError));
       return true;
     }
-    for (auto mime_type : ServiceWorkerConsts::kJavaScriptMimeTypes) {
+    for (auto mime_type : WorkerConsts::kJavaScriptMimeTypes) {
       if (net::MatchesMimeType(mime_type, content_type)) {
         mime_type_is_javascript = true;
         break;
@@ -511,16 +508,15 @@
         state->job,
         PromiseErrorData(
             web::DOMException::kSecurityErr,
-            base::StringPrintf(
-                ServiceWorkerConsts::kServiceWorkerRegisterBadMIMEError,
-                content_type.c_str())));
+            base::StringPrintf(WorkerConsts::kServiceWorkerRegisterBadMIMEError,
+                               content_type.c_str())));
     return true;
   }
   //   8.8.  Let serviceWorkerAllowed be the result of extracting header list
   //         values given `Service-Worker-Allowed` and response’s header list.
   std::string service_worker_allowed;
   bool service_worker_allowed_exists = headers->GetNormalizedHeader(
-      ServiceWorkerConsts::kServiceWorkerAllowed, &service_worker_allowed);
+      WorkerConsts::kServiceWorkerAllowed, &service_worker_allowed);
   //   8.9.  Set policyContainer to the result of creating a policy container
   //         from a fetch response given response.
   state->script_headers = headers;
@@ -564,11 +560,10 @@
     //   8.16.2. Asynchronously complete these steps with a network error.
     RejectJobPromise(
         state->job,
-        PromiseErrorData(
-            web::DOMException::kSecurityErr,
-            base::StringPrintf(
-                ServiceWorkerConsts::kServiceWorkerRegisterBadScopeError,
-                scope_string.c_str())));
+        PromiseErrorData(web::DOMException::kSecurityErr,
+                         base::StringPrintf(
+                             WorkerConsts::kServiceWorkerRegisterBadScopeError,
+                             scope_string.c_str())));
     return true;
   }
   return true;
@@ -722,7 +717,7 @@
 
   // 11. Let worker be a new service worker.
   ServiceWorkerObject::Options options(
-      "ServiceWorker", state->job->client->web_settings(),
+      WorkerConsts::kServiceWorkerName, state->job->client->web_settings(),
       state->job->client->network_module(), state->registration);
   options.web_options.platform_info = state->job->client->platform_info();
   options.web_options.service_worker_context =
@@ -1014,9 +1009,9 @@
     // 1.1. Invoke Reject Job Promise with job and "SecurityError" DOMException.
     RejectJobPromise(
         job,
-        PromiseErrorData(web::DOMException::kSecurityErr,
-                         ServiceWorkerConsts::
-                             kServiceWorkerUnregisterScopeOriginNotSameError));
+        PromiseErrorData(
+            web::DOMException::kSecurityErr,
+            WorkerConsts::kServiceWorkerUnregisterScopeOriginNotSameError));
 
     // 1.2. Invoke Finish Job with job and abort these steps.
     FinishJob(job);
diff --git a/cobalt/worker/service_worker_persistent_settings.cc b/cobalt/worker/service_worker_persistent_settings.cc
index f74d63f..fc81821 100644
--- a/cobalt/worker/service_worker_persistent_settings.cc
+++ b/cobalt/worker/service_worker_persistent_settings.cc
@@ -30,11 +30,11 @@
 #include "cobalt/script/promise.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/web/cache_utils.h"
-#include "cobalt/worker/service_worker_consts.h"
 #include "cobalt/worker/service_worker_context.h"
 #include "cobalt/worker/service_worker_jobs.h"
 #include "cobalt/worker/service_worker_registration_object.h"
 #include "cobalt/worker/service_worker_update_via_cache.h"
+#include "cobalt/worker/worker_consts.h"
 #include "cobalt/worker/worker_global_scope.h"
 #include "net/base/completion_once_callback.h"
 #include "net/disk_cache/cobalt/resource_type.h"
@@ -88,7 +88,7 @@
     const Options& options)
     : options_(options) {
   persistent_settings_.reset(new cobalt::persistent_storage::PersistentSettings(
-      ServiceWorkerConsts::kSettingsJson));
+      WorkerConsts::kSettingsJson));
   persistent_settings_->ValidatePersistentSettings();
   DCHECK(persistent_settings_);
 }
@@ -110,7 +110,7 @@
         persistent_settings_->GetPersistentSettingAsDictionary(key_string);
     if (dict.empty()) {
       DLOG(INFO) << "Key: " << key_string << " does not exist in "
-                 << ServiceWorkerConsts::kSettingsJson;
+                 << WorkerConsts::kSettingsJson;
       continue;
     }
     if (!CheckPersistentValue(key_string, kSettingsStorageKeyKey, dict,
@@ -461,5 +461,9 @@
   }
 }
 
+void ServiceWorkerPersistentSettings::DeleteAll(base::OnceClosure closure) {
+  persistent_settings_->DeletePersistentSettings(std::move(closure));
+}
+
 }  // namespace worker
 }  // namespace cobalt
diff --git a/cobalt/worker/service_worker_persistent_settings.h b/cobalt/worker/service_worker_persistent_settings.h
index e595acb..f855895 100644
--- a/cobalt/worker/service_worker_persistent_settings.h
+++ b/cobalt/worker/service_worker_persistent_settings.h
@@ -87,6 +87,8 @@
 
   void RemoveAll();
 
+  void DeleteAll(base::OnceClosure closure);
+
  private:
   Options options_;
 
diff --git a/cobalt/worker/service_worker_registration_map.cc b/cobalt/worker/service_worker_registration_map.cc
index 3532094..24bfd5d 100644
--- a/cobalt/worker/service_worker_registration_map.cc
+++ b/cobalt/worker/service_worker_registration_map.cc
@@ -61,8 +61,6 @@
       new ServiceWorkerPersistentSettings(options));
   DCHECK(service_worker_persistent_settings_);
 
-  // TODO(b/259731731) For now do not read from persisted settings until
-  // activation of persisted registrations works.
   ReadPersistentSettings();
 }
 
@@ -71,6 +69,15 @@
       registration_map_);
 }
 
+void ServiceWorkerRegistrationMap::DeletePersistentSettings() {
+  base::OnceClosure closure = base::BindOnce(
+      [](std::map<RegistrationMapKey,
+                  scoped_refptr<ServiceWorkerRegistrationObject>>*
+             registration_map) { (*registration_map).clear(); },
+      &registration_map_);
+  service_worker_persistent_settings_->DeleteAll(std::move(closure));
+}
+
 scoped_refptr<ServiceWorkerRegistrationObject>
 ServiceWorkerRegistrationMap::MatchServiceWorkerRegistration(
     const url::Origin& storage_key, const GURL& client_url) {
diff --git a/cobalt/worker/service_worker_registration_map.h b/cobalt/worker/service_worker_registration_map.h
index 591f58f..421e6f8 100644
--- a/cobalt/worker/service_worker_registration_map.h
+++ b/cobalt/worker/service_worker_registration_map.h
@@ -81,6 +81,8 @@
 
   void ReadPersistentSettings();
 
+  void DeletePersistentSettings();
+
  private:
   // ThreadChecker for use by the methods operating on the registration map.
   THREAD_CHECKER(thread_checker_);
diff --git a/cobalt/worker/worker.cc b/cobalt/worker/worker.cc
index d01d252..28ee68e 100644
--- a/cobalt/worker/worker.cc
+++ b/cobalt/worker/worker.cc
@@ -174,9 +174,19 @@
   //     1. Set request's reserved client to inside settings.
   //     2. Fetch request, and asynchronously wait to run the remaining steps as
   //        part of fetch's process response for the response response.
+  DCHECK(web_context_);
+  DCHECK(web_context_->environment_settings());
   const GURL& url = web_context_->environment_settings()->creation_url();
+  DCHECK(!url.is_empty());
   loader::Origin origin = loader::Origin(url.GetOrigin());
 
+  DCHECK(options_.outside_context);
+
+  // Window thread may remove its global object before destroying worker.
+  if (!options_.outside_context->GetWindowOrWorkerGlobalScope()) {
+    return;
+  }
+
   csp::SecurityCallback csp_callback = base::Bind(
       &web::CspDelegate::CanLoad,
       base::Unretained(options_.outside_context->GetWindowOrWorkerGlobalScope()
diff --git a/cobalt/worker/worker.h b/cobalt/worker/worker.h
index d37d462..364837a 100644
--- a/cobalt/worker/worker.h
+++ b/cobalt/worker/worker.h
@@ -63,13 +63,13 @@
     base::SourceLocation construction_location;
 
     // True if worker is a SharedWorker object, and false otherwise.
-    bool is_shared;
+    bool is_shared = false;
 
     // Parameters from 'Run a worker' step 9.1 in the spec.
     //   https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#dom-worker
     GURL url;
     web::Context* outside_context = nullptr;
-    web::EventTarget* outside_event_target;
+    web::EventTarget* outside_event_target = nullptr;
     web::MessagePort* outside_port = nullptr;
     WorkerOptions options;
   };
diff --git a/cobalt/worker/worker_consts.h b/cobalt/worker/worker_consts.h
new file mode 100644
index 0000000..9ee5c52
--- /dev/null
+++ b/cobalt/worker/worker_consts.h
@@ -0,0 +1,80 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_WORKER_WORKER_CONSTS_H_
+#define COBALT_WORKER_WORKER_CONSTS_H_
+
+namespace cobalt {
+namespace worker {
+
+namespace WorkerConsts {
+
+// Constants for error messages.
+constexpr const char kServiceWorkerRegisterBadMIMEError[] =
+    "Service Worker Register failed: The script has an unsupported MIME type "
+    "('%s').";
+constexpr const char kServiceWorkerRegisterNoMIMEError[] =
+    "Service Worker Register failed: The script does not have a MIME type.";
+constexpr const char kServiceWorkerRegisterScriptOriginNotSameError[] =
+    "Service Worker Register failed: Script URL ('%s') and referrer ('%s') "
+    "origin are not the same.";
+constexpr const char kServiceWorkerRegisterScopeOriginNotSameError[] =
+    "Service Worker Register failed: Scope URL ('%s') and referrer ('%s') "
+    "origin are not the same.";
+constexpr const char kServiceWorkerRegisterBadScopeError[] =
+    "Service Worker Register failed: Scope ('%s') is not allowed.";
+constexpr const char kServiceWorkerUnregisterScopeOriginNotSameError[] =
+    "Service Worker Unregister failed: Scope origin does not match.";
+
+constexpr const char kServiceWorkerAllowed[] = "Service-Worker-Allowed";
+
+// Constants for ServiceWorkerPersistentSettings
+constexpr const char kSettingsJson[] = "service_worker_settings.json";
+
+// Array of JavaScript mime types, according to the MIME Sniffinc spec:
+//   https://mimesniff.spec.whatwg.org/#javascript-mime-type
+constexpr const char* const kJavaScriptMimeTypes[16] = {
+    "application/ecmascript",
+    "application/javascript",
+    "application/x-ecmascript",
+    "application/x-javascript",
+    "text/ecmascript",
+    "text/javascript",
+    "text/javascript1.0",
+    "text/javascript1.1",
+    "text/javascript1.2",
+    "text/javascript1.3",
+    "text/javascript1.4",
+    "text/javascript1.5",
+    "text/jscript",
+    "text/livescript",
+    "text/x-ecmascript",
+    "text/x-javascript"};
+
+// The name of a Service Worker thread.
+constexpr const char kServiceWorkerName[] = "ServiceWorker";
+
+// The name of a Service Worker Registry thread.
+constexpr const char kServiceWorkerRegistryName[] = "ServiceWorkerRegistry";
+
+// The name of a Dedicated Worker thread.
+constexpr const char kDedicatedWorkerName[] = "DedicatedWorker";
+
+};  // namespace WorkerConsts
+
+
+}  // namespace worker
+}  // namespace cobalt
+
+#endif  // COBALT_WORKER_WORKER_CONSTS_H_
diff --git a/cobalt/worker/worker_global_scope.cc b/cobalt/worker/worker_global_scope.cc
index 0f2372b..7bf72c0 100644
--- a/cobalt/worker/worker_global_scope.cc
+++ b/cobalt/worker/worker_global_scope.cc
@@ -31,8 +31,8 @@
 #include "cobalt/web/user_agent_platform_info.h"
 #include "cobalt/web/window_or_worker_global_scope.h"
 #include "cobalt/web/window_timers.h"
-#include "cobalt/worker/service_worker_consts.h"
 #include "cobalt/worker/service_worker_object.h"
+#include "cobalt/worker/worker_consts.h"
 #include "cobalt/worker/worker_location.h"
 #include "cobalt/worker/worker_navigator.h"
 #include "net/base/mime_util.h"
@@ -169,7 +169,7 @@
     std::string content_type;
     bool mime_type_is_javascript = false;
     if (headers->GetNormalizedHeader("Content-type", &content_type)) {
-      for (auto mime_type : ServiceWorkerConsts::kJavaScriptMimeTypes) {
+      for (auto mime_type : WorkerConsts::kJavaScriptMimeTypes) {
         if (net::MatchesMimeType(mime_type, content_type)) {
           mime_type_is_javascript = true;
           break;
@@ -177,13 +177,12 @@
       }
     }
     if (content_type.empty()) {
-      error->reset(new std::string(base::StringPrintf(
-          ServiceWorkerConsts::kServiceWorkerRegisterNoMIMEError,
-          content_type.c_str())));
+      error->reset(new std::string(
+          base::StringPrintf(WorkerConsts::kServiceWorkerRegisterNoMIMEError)));
     } else if (!mime_type_is_javascript) {
-      error->reset(new std::string(base::StringPrintf(
-          ServiceWorkerConsts::kServiceWorkerRegisterBadMIMEError,
-          content_type.c_str())));
+      error->reset(new std::string(
+          base::StringPrintf(WorkerConsts::kServiceWorkerRegisterBadMIMEError,
+                             content_type.c_str())));
     }
     return true;
   }
diff --git a/cobalt/worker/worker_global_scope.h b/cobalt/worker/worker_global_scope.h
index 9c3468f..49ce6d7 100644
--- a/cobalt/worker/worker_global_scope.h
+++ b/cobalt/worker/worker_global_scope.h
@@ -35,7 +35,7 @@
 #include "cobalt/web/user_agent_platform_info.h"
 #include "cobalt/web/window_or_worker_global_scope.h"
 #include "cobalt/web/window_timers.h"
-#include "cobalt/worker/service_worker_consts.h"
+#include "cobalt/worker/worker_consts.h"
 #include "cobalt/worker/worker_location.h"
 #include "cobalt/worker/worker_navigator.h"
 #include "net/http/http_response_headers.h"
diff --git a/components/update_client/BUILD.gn b/components/update_client/BUILD.gn
index d83b2e6..08c7c4d 100644
--- a/components/update_client/BUILD.gn
+++ b/components/update_client/BUILD.gn
@@ -246,10 +246,7 @@
     "//starboard/loader_app:installation_manager",
     "//testing/gmock",
     "//testing/gtest",
+    # TODO: b/296919440 - Make this a data dep 
+    "//starboard/loader_app($starboard_toolchain)",
   ]
-  if (build_with_separate_cobalt_toolchain) {
-    data_deps = [ "//starboard/loader_app($starboard_toolchain)" ]
-  } else {
-    deps += [ "//starboard/loader_app" ]
-  }
 }
diff --git a/content/browser/speech/BUILD.gn b/content/browser/speech/BUILD.gn
index 6e5ae30..21ba4c7 100644
--- a/content/browser/speech/BUILD.gn
+++ b/content/browser/speech/BUILD.gn
@@ -39,6 +39,7 @@
   ]
   deps = [
     ":speech",
+    "//cobalt//browser:test_dependencies_on_browser",
     "//cobalt/media",
     "//cobalt/test:run_all_unittests",
     "//starboard",
diff --git a/docker/docsite/Dockerfile b/docker/docsite/Dockerfile
index 6b1bd9d..2e164c8 100644
--- a/docker/docsite/Dockerfile
+++ b/docker/docsite/Dockerfile
@@ -46,7 +46,10 @@
 RUN mkdir /project_out_dir \
     && chown ${USER:-defaultuser}:defaultgroup /project_out_dir
 
+<<<<<<< HEAD
 RUN git config --global --add safe.directory /code
+=======
+>>>>>>> 77f6a7c80d7 (Fix git config for /code in Linux containers (#611))
 
 COPY Gemfile /app/Gemfile
 # Note: This file was generated by running a working version of this Docker
diff --git a/docker/linux/base/build/Dockerfile b/docker/linux/base/build/Dockerfile
index 38fa8ce..4d0306d 100644
--- a/docker/linux/base/build/Dockerfile
+++ b/docker/linux/base/build/Dockerfile
@@ -110,5 +110,7 @@
     && echo ${CLANG_16_VER} >> ${CLANG_16_TC_HOME}/cr_build_revision \
     && rm clang-llvmorg-${CLANG_16_VER}.tgz
 
+RUN git config --global --add safe.directory /code
+
 WORKDIR /code
 CMD ["/usr/bin/python","--version"]
diff --git a/docker/windows/base/visualstudio2022/Dockerfile b/docker/windows/base/visualstudio2022/Dockerfile
index 780d9bf..a2722ff 100644
--- a/docker/windows/base/visualstudio2022/Dockerfile
+++ b/docker/windows/base/visualstudio2022/Dockerfile
@@ -31,13 +31,16 @@
         -ArgumentList '--quiet --wait --norestart --nocache`
         --installPath C:\BuildTools `
         --add Microsoft.VisualStudio.Component.VC.CoreIde `
-        --add Microsoft.VisualStudio.Component.VC.14.34.17.4.x86.x64 `
-        --add Microsoft.VisualStudio.Component.VC.Llvm.Clang `
-        --add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset `
-        --add Microsoft.VisualStudio.Component.Windows10SDK.18362';`
+        --add Microsoft.VisualStudio.Component.VC.14.34.17.4.x86.x64  `
+        --add Microsoft.VisualStudio.Component.VC.Llvm.Clang          `
+        --add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset   `
+        --add Microsoft.VisualStudio.Component.Windows10SDK.18362';   `
     Write-Host ('Cleaning up vs_buildtools.exe');`
     Remove-Item -Force -Recurse ${env:ProgramFiles(x86)}\'Microsoft Visual Studio'\Installer;`
     Remove-Item -Force -Recurse $env:TEMP\*;`
+    Remove-Item -Force -Recurse $env:ProgramData\'Package Cache'\;`
+    Remove-Item -Force -Recurse C:\BuildTools\VC\Tools\Llvm\ARM64;`
+    Remove-Item -Force -Recurse C:\BuildTools\Common7\IDE;`
     Remove-Item C:\TEMP\vs_buildtools.exe
 
 ENV VSINSTALLDIR "C:\BuildTools"
diff --git a/nb/BUILD.gn b/nb/BUILD.gn
index eb947a2..ff056c9 100644
--- a/nb/BUILD.gn
+++ b/nb/BUILD.gn
@@ -63,7 +63,9 @@
       "//starboard/common",
     ]
 
-    if (defined(has_nb_platform) && has_nb_platform) {
+    # TODO: b/295615064 Remove nb platform specific dependency.
+    if (defined(has_nb_platform) && has_nb_platform &&
+        current_toolchain == starboard_toolchain) {
       deps += [ "//$starboard_path/nb:nb_platform" ]
     }
   }
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 0412e4d..e694523 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -198,7 +198,6 @@
     "der/tag.cc",
     "der/tag.h",
 
-    # dial is a legacy Chromium component with heavy Cobalt modifications.
     "base/arena.cc",
     "base/arena.h",
     "base/backoff_entry.cc",
@@ -344,18 +343,6 @@
     "cookies/cookie_util.h",
     "cookies/parsed_cookie.cc",
     "cookies/parsed_cookie.h",
-    "dial/dial_http_server.cc",
-    "dial/dial_http_server.h",
-    "dial/dial_service.cc",
-    "dial/dial_service.h",
-    "dial/dial_service_handler.h",
-    "dial/dial_system_config.cc",
-    "dial/dial_system_config.h",
-    "dial/dial_system_config_starboard.cc",
-    "dial/dial_udp_server.cc",
-    "dial/dial_udp_server.h",
-    "dial/dial_udp_socket_factory.cc",
-    "dial/dial_udp_socket_factory.h",
     "dns/address_sorter.h",
     "dns/address_sorter.h",
     "dns/address_sorter_starboard.cc",
@@ -3630,12 +3617,6 @@
     "dns/record_rdata_unittest.cc",
     "dns/serial_worker_unittest.cc",
 
-    # dial is a legacy component we kept from old Chromium net.
-    "dial/dial_http_server_unittest.cc",
-    "dial/dial_service_unittest.cc",
-    "dial/dial_test_helpers.h",
-    "dial/dial_udp_server_unittests.cc",
-
     # disk_cache component is disabled because only http cache depends on
     # it and Cobalt does not support http cache yet.
     # "disk_cache/backend_cleanup_tracker_unittest.cc",
@@ -4194,6 +4175,17 @@
     ]
   }
 
+  if (enable_in_app_dial) {
+    sources += [
+    # DIAL server is Cobalt custom implementation. This is the wrong place for
+    # it and it should be moved to cobalt/network in the future.
+    "dial/dial_http_server_unittest.cc",
+    "dial/dial_service_unittest.cc",
+    "dial/dial_test_helpers.h",
+    "dial/dial_udp_server_unittests.cc",
+    ]
+  }
+
   # Avoid compiler errors due to conversion from "0x00" to char.
   if (is_win) {
     if (!is_starboard) {
@@ -4234,6 +4226,9 @@
     "//url",
     "//url:url_features",
   ]
+  if (enable_in_app_dial) {
+    deps += [ ":cobalt_dial_server" ]
+  }
 
   if (is_starboard) {
     data_deps = [
@@ -4243,6 +4238,33 @@
       "//third_party/icu:icudata",
     ]
   }
+
+  if(use_cobalt_customizations) {
+    # TODO: b/296715826 - Fix unused symbols which cause linker errors for windows platform modular builds.
+    sources -= [
+      "http/http_stream_factory_job_controller_unittest.cc",
+      "http/http_stream_factory_unittest.cc",
+      "http/http_stream_parser_unittest.cc",
+      "quic/bidirectional_stream_quic_impl_unittest.cc",
+      "quic/crypto/proof_verifier_chromium_test.cc",
+      "quic/network_connection_unittest.cc",
+      "quic/properties_based_quic_server_info_test.cc",
+      "quic/quic_address_mismatch_test.cc",
+      "quic/quic_chromium_alarm_factory_test.cc",
+      "quic/quic_chromium_client_session_test.cc",
+      "quic/quic_chromium_client_stream_test.cc",
+      "quic/quic_chromium_connection_helper_test.cc",
+      "quic/quic_clock_skew_detector_test.cc",
+      "quic/quic_connectivity_probing_manager_test.cc",
+      "quic/quic_end_to_end_unittest.cc",
+      "quic/quic_http_stream_test.cc",
+      "quic/quic_network_transaction_unittest.cc",
+      "quic/quic_proxy_client_socket_unittest.cc",
+      "quic/quic_stream_factory_test.cc",
+      "quic/quic_utils_chromium_test.cc",
+    ]
+
+  }
 }
 
 static_library("test_support") {
@@ -4255,6 +4277,7 @@
     # "disk_cache/disk_cache_test_util.cc",
     # "disk_cache/disk_cache_test_util.h",
 
+    "base/directory_lister.h",
     "base/test_data_stream.cc",
     "base/test_data_stream.h",
     "quic/quic_test_packet_maker.cc",
@@ -4421,6 +4444,16 @@
     "//third_party/zlib",
     "//url",
   ]
+
+  if(use_cobalt_customizations) {
+    # TODO: b/296715826 - Fix unused symbols which cause linker errors for windows platform modular builds.
+    sources -= [
+      "test/quic_simple_test_server.cc",
+      "test/quic_simple_test_server.h",
+      "quic/quic_test_packet_maker.cc",
+      "quic/quic_test_packet_maker.h",
+    ]
+  }
 }
 
 static_library("quic_test_tools") {
@@ -4659,3 +4692,28 @@
     "//starboard/common",
   ]
 }
+
+if (enable_in_app_dial) {
+static_library("cobalt_dial_server") {
+  sources = [
+    "dial/dial_http_server.cc",
+    "dial/dial_http_server.h",
+    "dial/dial_service.cc",
+    "dial/dial_service.h",
+    "dial/dial_service_handler.h",
+    "dial/dial_system_config.cc",
+    "dial/dial_system_config.h",
+    "dial/dial_system_config_starboard.cc",
+    "dial/dial_udp_server.cc",
+    "dial/dial_udp_server.h",
+    "dial/dial_udp_socket_factory.cc",
+    "dial/dial_udp_socket_factory.h",
+  ]
+  deps = [
+      ":http_server",
+      ":net",
+      "//base:base",
+      "//starboard/common",
+    ]
+  }
+}
diff --git a/net/base/directory_lister.h b/net/base/directory_lister.h
index 3f8b541..e5677e4 100644
--- a/net/base/directory_lister.h
+++ b/net/base/directory_lister.h
@@ -21,6 +21,7 @@
 
 namespace net {
 
+#if !defined(USE_COBALT_CUSTOMIZATIONS)
 // This class provides an API for asynchronously listing the contents of a
 // directory on the filesystem.  It runs a task on a background thread, and
 // enumerates all files in the specified directory on that thread.  Destroying
@@ -135,6 +136,37 @@
   DISALLOW_COPY_AND_ASSIGN(DirectoryLister);
 };
 
+#else
+
+// TODO b/296715826 Fix stubbed out class.
+class NET_EXPORT DirectoryLister  {
+ public:
+  struct DirectoryListerData {
+    base::FileEnumerator::FileInfo info;
+    base::FilePath path;
+    base::FilePath absolute_path;
+  };
+  class DirectoryListerDelegate {
+   public:
+    virtual void OnListFile(const DirectoryListerData& data) = 0;
+
+    virtual void OnListDone(int error) = 0;
+
+   protected:
+    virtual ~DirectoryListerDelegate() {}
+  };
+  enum ListingType {};
+  DirectoryLister(const base::FilePath& dir,
+                  DirectoryListerDelegate* delegate){};
+  DirectoryLister(const base::FilePath& dir,
+                  ListingType type,
+                  DirectoryListerDelegate* delegate){};
+  ~DirectoryLister(){};
+  void Start() {};
+  void Cancel() {};
+};
+
+#endif // !defined(USE_COBALT_CUSTOMIZATIONS)
 }  // namespace net
 
 #endif  // NET_BASE_DIRECTORY_LISTER_H_
diff --git a/net/base/net_export.h b/net/base/net_export.h
index 55cf986..29891cf 100644
--- a/net/base/net_export.h
+++ b/net/base/net_export.h
@@ -4,12 +4,16 @@
 
 #ifndef NET_BASE_NET_EXPORT_H_
 #define NET_BASE_NET_EXPORT_H_
-
 // Defines NET_EXPORT so that functionality implemented by the net module can
 // be exported to consumers, and NET_EXPORT_PRIVATE that allows unit tests to
 // access features not intended to be used directly by real consumers.
 
-#if defined(COMPONENT_BUILD)
+#ifdef USE_COBALT_CUSTOMIZATIONS
+#include "starboard/configuration.h"
+#endif // USE_COBALT_CUSTOMIZATIONS
+
+#if defined(COMPONENT_BUILD) || SB_IS(MODULAR) && !SB_IS(EVERGREEN)
+
 #if defined(WIN32)
 
 #if defined(NET_IMPLEMENTATION)
@@ -30,7 +34,7 @@
 #endif
 #endif
 
-#else  /// defined(COMPONENT_BUILD)
+#else  // defined(COMPONENT_BUILD) || SB_IS(MODULAR) && !SB_IS(EVERGREEN)
 #define NET_EXPORT
 #define NET_EXPORT_PRIVATE
 #endif
diff --git a/starboard/BUILD.gn b/starboard/BUILD.gn
index 630ec47..3ab468c 100644
--- a/starboard/BUILD.gn
+++ b/starboard/BUILD.gn
@@ -12,7 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import("//starboard/build/config/os_definitions.gni")
 import("//starboard/build/config/starboard_target_type.gni")
+import("//starboard/contrib/cast/cast.gni")
 
 group("gn_all") {
   testonly = true
@@ -57,9 +59,8 @@
     deps += [ "//starboard/benchmark" ]
   }
 
-  if (build_with_separate_cobalt_toolchain && is_cobalt_toolchain) {
-    assert(sb_is_modular,
-           "sb_is_modular should be set when building with cobalt_toolchain")
+  if (build_with_separate_cobalt_toolchain && use_contrib_cast) {
+    deps += [ "//starboard/contrib/cast/cast_starboard_api/samples:cast" ]
   }
 
   if (!sb_is_evergreen) {
@@ -68,7 +69,7 @@
     if (sb_is_evergreen_compatible) {
       deps += [ "//third_party/crashpad/client" ]
       data_deps = [
-        "//starboard/loader_app",
+        "//starboard/loader_app($starboard_toolchain)",
         "//third_party/crashpad/handler:crashpad_handler(//$starboard_path/toolchain:native_target)",
       ]
     }
@@ -89,7 +90,7 @@
     "//starboard/client_porting/eztime",
   ]
 
-  if (sb_is_modular) {
+  if (sb_is_modular && current_toolchain == cobalt_toolchain) {
     public_deps += [
       "//third_party/llvm-project/compiler-rt:compiler_rt",
       "//third_party/llvm-project/libcxx:cxx",
@@ -98,11 +99,13 @@
     ]
     if (sb_is_evergreen) {
       public_deps += [ "//starboard/elf_loader:sabi_string" ]
-    }
-    if (build_with_separate_cobalt_toolchain) {
-      data_deps = [ ":starboard_platform_group($starboard_toolchain)" ]
     } else {
-      public_deps += [ "//third_party/llvm-project/libunwind:unwind_evergreen" ]
+      data_deps = [ ":starboard_platform_group($starboard_toolchain)" ]
+    }
+
+    # TODO: b/295702296 Fix libunwind for modular builds.
+    if (sb_is_evergreen || is_host_win) {
+      public_deps += [ "//third_party/llvm-project/libunwind:unwind" ]
     }
   } else {
     public_deps += [
@@ -110,7 +113,7 @@
       "//starboard/common",
     ]
 
-    if (!build_with_separate_cobalt_toolchain) {
+    if (!sb_is_modular || sb_is_evergreen) {
       if (sb_is_evergreen_compatible) {
         public_deps += [ "//third_party/crashpad/wrapper" ]
       } else {
@@ -177,24 +180,8 @@
 }
 
 if (current_toolchain == starboard_toolchain) {
-  target(starboard_target_type, "starboard_platform_group") {
-    if (starboard_target_type == "shared_library") {
-      build_loader = false
-    }
-    public_deps = [
-      "//starboard/client_porting/cwrappers",
-      "//starboard/client_porting/eztime",
-      "//starboard/common",
-      "//starboard/egl_and_gles",
-    ]
-    if (sb_is_evergreen_compatible) {
-      public_deps += [ "//third_party/crashpad/wrapper" ]
-    } else {
-      public_deps += [ "//third_party/crashpad/wrapper:wrapper_stub" ]
-    }
-    if (!sb_is_modular) {
-      public_deps += [ "//$starboard_path:starboard_platform" ]
-    }
+  # This is the default 'starboard_platform_target'; use default properties
+  starboard_platform_target("starboard_platform_group") {
   }
 
   if (platform_tests_path == "") {
@@ -206,10 +193,17 @@
       sources = [ "//starboard/common/test_main.cc" ]
 
       public_deps = [
-        ":starboard",
+        ":starboard_with_main",
         "//testing/gmock",
         "//testing/gtest",
       ]
     }
   }
+
+  group("starboard_with_main") {
+    public_deps = [ ":starboard" ]
+    if (sb_is_modular && !sb_is_evergreen) {
+      public_deps += [ "//$starboard_path:starboard_platform_with_main" ]
+    }
+  }
 }
diff --git a/starboard/android/apk/app/CMakeLists.txt b/starboard/android/apk/app/CMakeLists.txt
index 8c7d129..d10920b 100644
--- a/starboard/android/apk/app/CMakeLists.txt
+++ b/starboard/android/apk/app/CMakeLists.txt
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Since libcoat.so is an "IMPORTED" library in CMake, in order to debug in
+# Since libcobalt.so is an "IMPORTED" library in CMake, in order to debug in
 # Android Studio you have to manually set the debugger type so that LLDB will
 # be started:
 # Run -> Edit Configurations -> "app" -> Debugger -> Debug Type = Dual
@@ -65,18 +65,18 @@
   set(skip_ninja_arg -n)
 endif()
 
-# Run Cobalt ninja, and copy the result as our "IMPORTED" libcoat.so.
+# Run Cobalt ninja, and copy the result as our "IMPORTED" libcobalt.so.
 # ("coat_lib" never gets created, so this runs every time.)
 add_custom_command(OUTPUT coat_lib
     COMMAND ${CMAKE_CURRENT_LIST_DIR}/cobalt-ninja.sh
             ${skip_ninja_arg} -C ${COBALT_PRODUCT_DIR} ${COBALT_TARGET}
     COMMAND ${CMAKE_COMMAND} -E copy
             ${COBALT_LIBRARY_DIR}/lib${COBALT_TARGET}.so
-            ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libcoat.so
+            ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libcobalt.so
 )
 
 if(EVERGREEN_COMPATIBLE)
-  # Follow the pattern used to import libcoat.so, above.
+  # Follow the pattern used to import libcobalt.so, above.
   add_custom_command(OUTPUT crashpad_handler_lib
       COMMAND ${CMAKE_CURRENT_LIST_DIR}/cobalt-ninja.sh
               ${skip_ninja_arg} -C ${COBALT_PRODUCT_DIR} crashpad_handler
@@ -109,15 +109,15 @@
 # We need a target (not a file) for the phony native dependency below.
 add_custom_target(external_cobalt_build DEPENDS coat_lib cobalt_content)
 
-# Declare libcoat.so as a shared library that needs to be included in the APK.
+# Declare libcobalt.so as a shared library that needs to be included in the APK.
 # However, Android Studio will build it as an "IMPORTED" library.
 add_library(coat SHARED IMPORTED)
 set_target_properties(coat PROPERTIES
-    IMPORTED_LOCATION ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libcoat.so
+    IMPORTED_LOCATION ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libcobalt.so
 )
 
 if(EVERGREEN_COMPATIBLE)
-  # Follow the pattern used to include libcoat.so, above.
+  # Follow the pattern used to include libcobalt.so, above.
   add_library(crashpadhandler SHARED IMPORTED)
   set_target_properties(crashpadhandler PROPERTIES
       IMPORTED_LOCATION ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libcrashpad_handler.so
diff --git a/starboard/android/apk/app/src/app/AndroidManifest.xml b/starboard/android/apk/app/src/app/AndroidManifest.xml
index 0bece9b..6b2602d 100644
--- a/starboard/android/apk/app/src/app/AndroidManifest.xml
+++ b/starboard/android/apk/app/src/app/AndroidManifest.xml
@@ -50,7 +50,7 @@
       <meta-data android:name="cobalt.APP_URL" android:value="https://www.youtube.com/tv"/>
       <meta-data android:name="cobalt.SPLASH_URL" android:value="h5vcc-embedded://cobalt_splash_screen.html"/>
       <meta-data android:name="cobalt.EVERGREEN_LITE" android:value="false"/>
-      <meta-data android:name="android.app.lib_name" android:value="coat"/>
+      <meta-data android:name="android.app.lib_name" android:value="cobalt"/>
       <intent-filter>
         <action android:name="android.intent.action.MAIN"/>
         <category android:name="android.intent.category.LAUNCHER"/>
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltHttpHelper.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltHttpHelper.java
index 56a2ffd..e5f5df8 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltHttpHelper.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltHttpHelper.java
@@ -25,6 +25,7 @@
 
 /** Helper class that implements an HTTP POST function used by DRM one-time provisioning. */
 public class CobaltHttpHelper {
+  private static final int HTTP_TIMEOUT_MILLIS = 10000;
   private static final int RETRY_SLEEP_MILLIS = 250;
   private static final int MAX_ATTEMPTS = 3;
 
@@ -72,6 +73,8 @@
   private byte[] internalPerformHttpPost(String url) throws IOException, TransientFailure {
     HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
     try {
+      conn.setConnectTimeout(HTTP_TIMEOUT_MILLIS);
+      conn.setReadTimeout(HTTP_TIMEOUT_MILLIS);
       conn.setRequestMethod("POST");
       conn.setDoOutput(false);
       conn.setDoInput(true);
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
index 2d719ad..61f4bca 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
@@ -85,7 +85,11 @@
   static {
     // Even though NativeActivity already loads our library from C++,
     // we still have to load it from Java to make JNI calls into it.
-    System.loadLibrary("coat");
+
+    // GameActivity has code to load the libcobalt.so as well.
+    // It reads the library name from the meta data field "android.app.lib_name" in the
+    // AndroidManifest.xml
+    System.loadLibrary("cobalt");
   }
 
   private final Context appContext;
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
index 23ab5f3..ffcea69 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
@@ -535,41 +535,10 @@
     if (mMediaDrm == null) {
       throw new IllegalStateException("Cannot create media crypto with null mMediaDrm.");
     }
-    if (mMediaCryptoSession != null) {
-      throw new IllegalStateException(
-          "Cannot create media crypto with non-null mMediaCryptoSession.");
-    }
-    // TODO: Cannot do this during provisioning pending.
-
-    // Open media crypto session.
-    try {
-      mMediaCryptoSession = openSession();
-    } catch (NotProvisionedException e) {
-      Log.d(TAG, "Device not provisioned", e);
-      if (!attemptProvisioning()) {
-        Log.e(TAG, "Failed to provision device during MediaCrypto creation.");
-        return false;
-      }
-      try {
-        mMediaCryptoSession = openSession();
-      } catch (NotProvisionedException e2) {
-        Log.e(TAG, "Device still not provisioned after supposedly successful provisioning", e2);
-        return false;
-      }
-    }
-
-    if (mMediaCryptoSession == null) {
-      Log.e(TAG, "Cannot create MediaCrypto Session.");
-      return false;
-    }
-    Log.d(
-        TAG,
-        String.format("MediaCrypto Session created: %s", bytesToHexString(mMediaCryptoSession)));
-
     // Create MediaCrypto object.
     try {
       if (MediaCrypto.isCryptoSchemeSupported(mSchemeUUID)) {
-        MediaCrypto mediaCrypto = new MediaCrypto(mSchemeUUID, mMediaCryptoSession);
+        MediaCrypto mediaCrypto = new MediaCrypto(mSchemeUUID, new byte[0]);
         Log.d(TAG, "MediaCrypto successfully created!");
         mMediaCrypto = mediaCrypto;
         return true;
@@ -580,14 +549,6 @@
       Log.e(TAG, "Cannot create MediaCrypto", e);
     }
 
-    try {
-      // Some implementations let this method throw exceptions.
-      mMediaDrm.closeSession(mMediaCryptoSession);
-    } catch (Exception e) {
-      Log.e(TAG, "closeSession failed: ", e);
-    }
-    mMediaCryptoSession = null;
-
     return false;
   }
 
@@ -621,6 +582,59 @@
     }
   }
 
+  @UsedByNative
+  boolean createMediaCryptoSession() {
+    if (mMediaCryptoSession != null) {
+      return true;
+    }
+    Log.w(TAG, "MediaDrmBridge createMediaCryptoSession");
+    if (mMediaCrypto == null) {
+      throw new IllegalStateException("Cannot create media crypto session with null mMediaCrypto.");
+    }
+
+    // Open media crypto session.
+    try {
+      mMediaCryptoSession = openSession();
+    } catch (NotProvisionedException e) {
+      Log.w(TAG, "Device not provisioned", e);
+      if (!attemptProvisioning()) {
+        Log.e(TAG, "Failed to provision device during MediaCrypto creation.");
+        return false;
+      }
+      try {
+        mMediaCryptoSession = openSession();
+      } catch (NotProvisionedException e2) {
+        Log.e(TAG, "Device still not provisioned after supposedly successful provisioning", e2);
+        return false;
+      }
+    }
+
+    if (mMediaCryptoSession == null) {
+      Log.e(TAG, "Cannot create MediaCrypto Session.");
+      return false;
+    }
+
+    try {
+      mMediaCrypto.setMediaDrmSession(mMediaCryptoSession);
+    } catch (MediaCryptoException e3) {
+      Log.e(TAG, "Unable to set media drm session", e3);
+      try {
+        // Some implementations let this method throw exceptions.
+        mMediaDrm.closeSession(mMediaCryptoSession);
+      } catch (Exception e) {
+        Log.e(TAG, "closeSession failed: ", e);
+      }
+      mMediaCryptoSession = null;
+      return false;
+    }
+
+    Log.d(
+        TAG,
+        String.format("MediaCrypto Session created: %s", bytesToHexString(mMediaCryptoSession)));
+
+    return true;
+  }
+
   /**
    * Attempt to get the device that we are currently running on provisioned.
    *
diff --git a/starboard/android/arm/toolchain/BUILD.gn b/starboard/android/arm/toolchain/BUILD.gn
index b49df51..6b167d1 100644
--- a/starboard/android/arm/toolchain/BUILD.gn
+++ b/starboard/android/arm/toolchain/BUILD.gn
@@ -21,7 +21,6 @@
   cc = "$prefix/armv7a-linux-androideabi${android_ndk_api_level}-clang"
   cxx = "$prefix/armv7a-linux-androideabi${android_ndk_api_level}-clang++"
   ld = cxx
-  ar = "$prefix/arm-linux-androideabi-readelf"
   ar = "ar"
   nm = "nm"
 
@@ -35,7 +34,6 @@
   cc = "$prefix/armv7a-linux-androideabi${android_ndk_api_level}-clang"
   cxx = "$prefix/armv7a-linux-androideabi${android_ndk_api_level}-clang++"
   ld = cxx
-  ar = "$prefix/arm-linux-androideabi-readelf"
   ar = "ar"
   nm = "nm"
 
diff --git a/starboard/android/shared/application_android.cc b/starboard/android/shared/application_android.cc
index 0334d1f..33b26ad 100644
--- a/starboard/android/shared/application_android.cc
+++ b/starboard/android/shared/application_android.cc
@@ -143,6 +143,7 @@
 }
 
 ApplicationAndroid::~ApplicationAndroid() {
+  // The application is exiting.
   // Release the global reference.
   if (resource_overlay_) {
     JniEnvExt* env = JniEnvExt::Get();
@@ -156,6 +157,12 @@
   ALooper_removeFd(looper_, keyboard_inject_readfd_);
   close(keyboard_inject_readfd_);
   close(keyboard_inject_writefd_);
+
+  {
+    // Signal for any potentially waiting window creation or destroy commands.
+    ScopedLock lock(android_command_mutex_);
+    android_command_condition_.Signal();
+  }
 }
 
 void ApplicationAndroid::Initialize() {
@@ -251,7 +258,10 @@
   JniEnvExt* env = JniEnvExt::Get();
   AndroidCommand cmd;
   int err = read(android_command_readfd_, &cmd, sizeof(cmd));
-  SB_DCHECK(err >= 0) << "Command read failed. errno=" << errno;
+  if (err < 0) {
+    SB_DCHECK(err >= 0) << "Command read failed. errno=" << errno;
+    return;
+  }
 
   SB_LOG(INFO) << "Android command: " << AndroidCommandName(cmd.type);
 
@@ -394,7 +404,10 @@
   SB_LOG(INFO) << "Send Android command: " << AndroidCommandName(type);
   AndroidCommand cmd{type, data};
   ScopedLock lock(android_command_mutex_);
-  write(android_command_writefd_, &cmd, sizeof(cmd));
+  if (write(android_command_writefd_, &cmd, sizeof(cmd)) == -1) {
+    SB_LOG(ERROR) << "Writing Android command failed";
+    return;
+  }
   // Synchronization only necessary when managing resources.
   switch (type) {
     case AndroidCommand::kNativeWindowCreated:
diff --git a/starboard/android/shared/application_android.h b/starboard/android/shared/application_android.h
index d023e74..e8917c1 100644
--- a/starboard/android/shared/application_android.h
+++ b/starboard/android/shared/application_android.h
@@ -56,8 +56,8 @@
       kDeepLink,
     } CommandType;
 
-    CommandType type;
-    void* data;
+    CommandType type = kUndefined;
+    void* data = nullptr;
   };
 
 #if SB_API_VERSION >= 15
diff --git a/starboard/android/shared/drm_system.cc b/starboard/android/shared/drm_system.cc
index 3b195e5..7897d3b 100644
--- a/starboard/android/shared/drm_system.cc
+++ b/starboard/android/shared/drm_system.cc
@@ -14,10 +14,15 @@
 
 #include "starboard/android/shared/drm_system.h"
 
+#include <memory>
+#include <utility>
+
 #include "starboard/android/shared/jni_env_ext.h"
 #include "starboard/android/shared/jni_utils.h"
 #include "starboard/android/shared/media_common.h"
+#include "starboard/common/atomic.h"
 #include "starboard/common/instance_counter.h"
+#include "starboard/common/thread.h"
 
 namespace {
 
@@ -177,7 +182,8 @@
     SbDrmSessionUpdateRequestFunc update_request_callback,
     SbDrmSessionUpdatedFunc session_updated_callback,
     SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback)
-    : key_system_(key_system),
+    : Thread("DrmSystemThread"),
+      key_system_(key_system),
       context_(context),
       update_request_callback_(update_request_callback),
       session_updated_callback_(session_updated_callback),
@@ -206,10 +212,35 @@
     return;
   }
   j_media_crypto_ = env->ConvertLocalRefToGlobalRef(j_media_crypto_);
+
+  Start();
+}
+
+void DrmSystem::Run() {
+  JniEnvExt* env = JniEnvExt::Get();
+  bool result = env->CallBooleanMethodOrAbort(
+      j_media_drm_bridge_, "createMediaCryptoSession", "()Z");
+  if (result) {
+    created_media_crypto_session_.store(true);
+  }
+  if (!result && j_media_crypto_) {
+    env->DeleteGlobalRef(j_media_crypto_);
+    j_media_crypto_ = NULL;
+    return;
+  }
+
+  ScopedLock scoped_lock(mutex_);
+  if (!deferred_session_update_requests_.empty()) {
+    for (const auto& update_request : deferred_session_update_requests_) {
+      update_request->Generate(j_media_drm_bridge_);
+    }
+    deferred_session_update_requests_.clear();
+  }
 }
 
 DrmSystem::~DrmSystem() {
   ON_INSTANCE_RELEASED(AndroidDrmSystem);
+  Join();
 
   JniEnvExt* env = JniEnvExt::Get();
   if (j_media_crypto_) {
@@ -223,17 +254,64 @@
   }
 }
 
+DrmSystem::SessionUpdateRequest::SessionUpdateRequest(
+    int ticket,
+    const char* type,
+    const void* initialization_data,
+    int initialization_data_size) {
+  JniEnvExt* env = JniEnvExt::Get();
+  j_ticket_ = static_cast<jint>(ticket);
+  j_init_data_ =
+      ByteArrayFromRaw(initialization_data, initialization_data_size);
+  j_mime_ = env->NewStringStandardUTFOrAbort(type);
+}
+
+void DrmSystem::SessionUpdateRequest::ConvertLocalRefToGlobalRef() {
+  if (!references_are_global_) {
+    JniEnvExt* env = JniEnvExt::Get();
+    j_init_data_ = env->ConvertLocalRefToGlobalRef(j_init_data_);
+    j_mime_ = env->ConvertLocalRefToGlobalRef(j_mime_);
+    references_are_global_ = true;
+  }
+}
+
+DrmSystem::SessionUpdateRequest::~SessionUpdateRequest() {
+  JniEnvExt* env = JniEnvExt::Get();
+  if (references_are_global_) {
+    env->DeleteGlobalRef(j_init_data_);
+    env->DeleteGlobalRef(j_mime_);
+  } else {
+    env->DeleteLocalRef(j_init_data_);
+    env->DeleteLocalRef(j_mime_);
+  }
+  j_init_data_ = nullptr;
+  j_mime_ = nullptr;
+}
+
+void DrmSystem::SessionUpdateRequest::Generate(
+    jobject j_media_drm_bridge) const {
+  JniEnvExt* env = JniEnvExt::Get();
+  env->CallVoidMethodOrAbort(j_media_drm_bridge, "createSession",
+                             "(I[BLjava/lang/String;)V", j_ticket_,
+                             j_init_data_, j_mime_);
+}
+
 void DrmSystem::GenerateSessionUpdateRequest(int ticket,
                                              const char* type,
                                              const void* initialization_data,
                                              int initialization_data_size) {
-  ScopedLocalJavaRef<jbyteArray> j_init_data(
-      ByteArrayFromRaw(initialization_data, initialization_data_size));
-  JniEnvExt* env = JniEnvExt::Get();
-  ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(type));
-  env->CallVoidMethodOrAbort(
-      j_media_drm_bridge_, "createSession", "(I[BLjava/lang/String;)V",
-      static_cast<jint>(ticket), j_init_data.Get(), j_mime.Get());
+  std::unique_ptr<SessionUpdateRequest> session_update_request(
+      new SessionUpdateRequest(ticket, type, initialization_data,
+                               initialization_data_size));
+  if (created_media_crypto_session_.load()) {
+    session_update_request->Generate(j_media_drm_bridge_);
+  } else {
+    // Defer generating the update request.
+    session_update_request->ConvertLocalRefToGlobalRef();
+    ScopedLock scoped_lock(mutex_);
+    deferred_session_update_requests_.push_back(
+        std::move(session_update_request));
+  }
   // |update_request_callback_| will be called by Java calling into
   // |onSessionMessage|.
 }
diff --git a/starboard/android/shared/drm_system.h b/starboard/android/shared/drm_system.h
index 48051c5..f60d25b 100644
--- a/starboard/android/shared/drm_system.h
+++ b/starboard/android/shared/drm_system.h
@@ -18,21 +18,25 @@
 #include "starboard/shared/starboard/drm/drm_system_internal.h"
 
 #include <jni.h>
+#include <memory>
 
 #include <string>
 #include <unordered_map>
 #include <vector>
 
+#include "starboard/android/shared/jni_utils.h"
 #include "starboard/android/shared/media_common.h"
+#include "starboard/common/atomic.h"
 #include "starboard/common/log.h"
 #include "starboard/common/mutex.h"
+#include "starboard/common/thread.h"
 #include "starboard/types.h"
 
 namespace starboard {
 namespace android {
 namespace shared {
 
-class DrmSystem : public ::SbDrmSystemPrivate {
+class DrmSystem : public ::SbDrmSystemPrivate, private Thread {
  public:
   DrmSystem(const char* key_system,
             void* context,
@@ -81,9 +85,33 @@
     return IsWidevineL1(key_system_.c_str());
   }
 
+  // Return true when the drm system is ready for secure input buffers.
+  bool IsReady() { return created_media_crypto_session_.load(); }
+
  private:
+  class SessionUpdateRequest {
+   public:
+    SessionUpdateRequest(int ticket,
+                         const char* type,
+                         const void* initialization_data,
+                         int initialization_data_size);
+    ~SessionUpdateRequest();
+
+    void ConvertLocalRefToGlobalRef();
+    void Generate(jobject j_media_drm_bridge) const;
+
+   private:
+    bool references_are_global_ = false;
+    jint j_ticket_;
+    jobject j_init_data_;
+    jobject j_mime_;
+  };
+
   void CallKeyStatusesChangedCallbackWithKeyStatusRestricted_Locked();
 
+  // From Thread.
+  void Run() override;
+
   const std::string key_system_;
   void* context_;
   SbDrmSessionUpdateRequestFunc update_request_callback_;
@@ -94,9 +122,13 @@
   jobject j_media_drm_bridge_;
   jobject j_media_crypto_;
 
+  std::vector<std::unique_ptr<SessionUpdateRequest>>
+      deferred_session_update_requests_;
+
   Mutex mutex_;
-  std::unordered_map<std::string, std::vector<SbDrmKeyId> > cached_drm_key_ids_;
+  std::unordered_map<std::string, std::vector<SbDrmKeyId>> cached_drm_key_ids_;
   bool hdcp_lost_;
+  atomic_bool created_media_crypto_session_;
 
   std::vector<uint8_t> metrics_;
 };
diff --git a/starboard/android/shared/media_decoder.cc b/starboard/android/shared/media_decoder.cc
index 721680e..78425b7 100644
--- a/starboard/android/shared/media_decoder.cc
+++ b/starboard/android/shared/media_decoder.cc
@@ -35,6 +35,9 @@
 const jint kNoSize = 0;
 const jint kNoBufferFlags = 0;
 
+// Delay to use after a retryable error has been encountered.
+const SbTime kErrorRetryDelay = 50 * kSbTimeMillisecond;
+
 const char* GetNameForMediaCodecStatus(jint status) {
   switch (status) {
     case MEDIA_CODEC_OK:
@@ -461,15 +464,23 @@
 
   jint status;
   if (event.type == Event::kWriteCodecConfig) {
-    status = media_codec_bridge_->QueueInputBuffer(dequeue_input_result.index,
-                                                   kNoOffset, size, kNoPts,
-                                                   BUFFER_FLAG_CODEC_CONFIG);
+    if (!drm_system_ || (drm_system_ && drm_system_->IsReady())) {
+      status = media_codec_bridge_->QueueInputBuffer(dequeue_input_result.index,
+                                                     kNoOffset, size, kNoPts,
+                                                     BUFFER_FLAG_CODEC_CONFIG);
+    } else {
+      status = MEDIA_CODEC_NO_KEY;
+    }
   } else if (event.type == Event::kWriteInputBuffer) {
     jlong pts_us = input_buffer->timestamp();
     if (drm_system_ && input_buffer->drm_info()) {
-      status = media_codec_bridge_->QueueSecureInputBuffer(
-          dequeue_input_result.index, kNoOffset, *input_buffer->drm_info(),
-          pts_us);
+      if (drm_system_->IsReady()) {
+        status = media_codec_bridge_->QueueSecureInputBuffer(
+            dequeue_input_result.index, kNoOffset, *input_buffer->drm_info(),
+            pts_us);
+      } else {
+        status = MEDIA_CODEC_NO_KEY;
+      }
     } else {
       status = media_codec_bridge_->QueueInputBuffer(
           dequeue_input_result.index, kNoOffset, size, pts_us, kNoBufferFlags);
@@ -534,6 +545,7 @@
     SB_LOG(INFO) << "|" << action_name << "| failed with status: "
                  << GetNameForMediaCodecStatus(status)
                  << ", will try again after a delay.";
+    SbThreadYield();
   } else {
     SB_LOG(ERROR) << "|" << action_name << "| failed with status: "
                   << GetNameForMediaCodecStatus(status) << ".";
diff --git a/starboard/android/shared/media_get_audio_configuration.cc b/starboard/android/shared/media_get_audio_configuration.cc
index dbd658f..6b645b6 100644
--- a/starboard/android/shared/media_get_audio_configuration.cc
+++ b/starboard/android/shared/media_get_audio_configuration.cc
@@ -17,6 +17,7 @@
 #include "starboard/android/shared/jni_env_ext.h"
 #include "starboard/android/shared/jni_utils.h"
 #include "starboard/android/shared/media_capabilities_cache.h"
+#include "starboard/common/media.h"
 
 // Constants for output types from
 // https://developer.android.com/reference/android/media/AudioDeviceInfo.
@@ -129,11 +130,19 @@
 bool SbMediaGetAudioConfiguration(
     int output_index,
     SbMediaAudioConfiguration* out_configuration) {
+  using starboard::GetMediaAudioConnectorName;
   using starboard::android::shared::JniEnvExt;
   using starboard::android::shared::MediaCapabilitiesCache;
   using starboard::android::shared::ScopedLocalJavaRef;
 
-  if (output_index < 0 || out_configuration == NULL) {
+  if (output_index < 0) {
+    SB_LOG(WARNING) << "output_index is " << output_index
+                    << ", which cannot be negative.";
+    return false;
+  }
+
+  if (out_configuration == nullptr) {
+    SB_LOG(WARNING) << "out_configuration cannot be nullptr.";
     return false;
   }
 
@@ -152,6 +161,8 @@
       output_index, j_output_device_info.Get());
 
   if (!succeeded) {
+    SB_LOG(WARNING)
+        << "Call to AudioOutputManager.getOutputDeviceInfo() failed.";
     return false;
   }
 
@@ -171,9 +182,8 @@
     int channels =
         MediaCapabilitiesCache::GetInstance()->GetMaxAudioOutputChannels();
     if (channels < 2) {
-      SB_LOG(WARNING)
-          << "The supported channels from output device is smaller than 2. "
-             "Fallback to 2 channels";
+      SB_LOG(WARNING) << "The supported channels from output device is "
+                      << channels << ", set to 2 channels instead.";
       out_configuration->number_of_channels = 2;
     } else {
       out_configuration->number_of_channels = channels;
@@ -182,5 +192,10 @@
     out_configuration->number_of_channels = 2;
   }
 
+  SB_LOG(INFO) << "Audio connector type for index " << output_index << " is "
+               << GetMediaAudioConnectorName(out_configuration->connector)
+               << " and it has " << out_configuration->number_of_channels
+               << " channels.";
+
   return true;
 }
diff --git a/starboard/android/shared/media_get_audio_output_count.cc b/starboard/android/shared/media_get_audio_output_count.cc
index befefd3..0c4d473 100644
--- a/starboard/android/shared/media_get_audio_output_count.cc
+++ b/starboard/android/shared/media_get_audio_output_count.cc
@@ -15,6 +15,9 @@
 #include "starboard/media.h"
 
 int SbMediaGetAudioOutputCount() {
-  // Only supports one HDMI output.
-  return 1;
+  // TODO(b/284140486, b/297426689): Tentatively returns 16 to ensure that all
+  // audio output devices are checked in `IsAudioOutputSupported()`.  We should
+  // revisit this, and probably remove `SbMediaGetAudioOutputCount()` completely
+  // from Starboard.
+  return 16;
 }
diff --git a/starboard/build/config/BUILD.gn b/starboard/build/config/BUILD.gn
index 0c816e8..6a0290a 100644
--- a/starboard/build/config/BUILD.gn
+++ b/starboard/build/config/BUILD.gn
@@ -92,7 +92,7 @@
 config("target") {
   if (current_toolchain != host_toolchain) {
     if (final_executable_type == "shared_library") {
-      if (!build_with_separate_cobalt_toolchain) {
+      if (!sb_is_modular || sb_is_evergreen) {
         # Rewrite main() functions into StarboardMain. TODO: This is a
         # hack, it would be better to be more surgical, here.
         defines = [ "main=StarboardMain" ]
@@ -229,7 +229,8 @@
 # override flags specified in a platform's "platform_configuration" config,
 # which is where these particular flags would otherwise naturally fit.
 config("default_compiler_flags") {
-  if (is_starboardized_toolchain && sb_is_modular && target_cpu == "arm") {
+  if (is_starboardized_toolchain && sb_is_modular &&
+      current_toolchain == cobalt_toolchain && target_cpu == "arm") {
     cflags = [ "-mfpu=vfpv3" ]
     asmflags = cflags
   }
diff --git a/starboard/build/config/BUILDCONFIG.gn b/starboard/build/config/BUILDCONFIG.gn
index 6c7d409..4075ed3 100644
--- a/starboard/build/config/BUILDCONFIG.gn
+++ b/starboard/build/config/BUILDCONFIG.gn
@@ -99,13 +99,7 @@
 host_toolchain = "//starboard/build/toolchain/$host_os:$_host_toolchain_cpu"
 
 if (build_with_separate_cobalt_toolchain) {
-  # TODO(b/294450490): decide a way to set cobalt_toolchain for modular builds for all platforms.
-  # we'll need more conditionals for other platforms.
-  if (host_os == "win") {
-    cobalt_toolchain = "//$starboard_path/toolchain:cobalt"
-  } else {
-    cobalt_toolchain = "//starboard/build/toolchain:clang"
-  }
+  cobalt_toolchain = "//starboard/build/toolchain:clang"
   starboard_toolchain = "//$starboard_path/toolchain:starboard"
 } else {
   cobalt_toolchain = "//$starboard_path/toolchain:target"
@@ -117,6 +111,9 @@
 import("//$starboard_path/platform_configuration/configuration.gni")
 import("//starboard/build/config/build_assertions.gni")
 
+# TODO(b/295399926) Clean up flag by moving to modular/helper_var.gni
+sb_is_modular = sb_is_evergreen || build_with_separate_cobalt_toolchain
+
 declare_args() {
   use_tsan = getenv("USE_TSAN") == 1
 }
diff --git a/starboard/build/config/os_definitions.gni b/starboard/build/config/os_definitions.gni
index 6a5747b..16c48ef 100644
--- a/starboard/build/config/os_definitions.gni
+++ b/starboard/build/config/os_definitions.gni
@@ -31,3 +31,5 @@
 
 is_apple = is_ios || is_mac
 is_posix = !is_win && !is_fuchsia
+
+is_host_win = host_os == "win" || host_os == "winuwp"
diff --git a/starboard/build/config/starboard_target_type.gni b/starboard/build/config/starboard_target_type.gni
index 0c3983a..c1ebc17 100644
--- a/starboard/build/config/starboard_target_type.gni
+++ b/starboard/build/config/starboard_target_type.gni
@@ -17,9 +17,36 @@
 }
 
 if (starboard_target_type == "") {
-  if (build_with_separate_cobalt_toolchain) {
+  if (sb_is_modular && !sb_is_evergreen) {
     starboard_target_type = "shared_library"
   } else {
     starboard_target_type = "group"
   }
 }
+
+template("starboard_platform_target") {
+  target(starboard_target_type, target_name) {
+    forward_variables_from(invoker, [ "extra_configs" ])
+
+    if (defined(invoker.extra_configs)) {
+      configs += extra_configs
+    }
+    if (starboard_target_type == "shared_library") {
+      build_loader = false
+    }
+    public_deps = [
+      "//starboard/client_porting/cwrappers",
+      "//starboard/client_porting/eztime",
+      "//starboard/common",
+      "//starboard/egl_and_gles",
+    ]
+    if (sb_is_evergreen_compatible) {
+      public_deps += [ "//third_party/crashpad/wrapper" ]
+    } else {
+      public_deps += [ "//third_party/crashpad/wrapper:wrapper_stub" ]
+    }
+    if (!sb_is_evergreen) {
+      public_deps += [ "//$starboard_path:starboard_platform" ]
+    }
+  }
+}
diff --git a/starboard/build/toolchain/BUILD.gn b/starboard/build/toolchain/BUILD.gn
index 2bfec6e..21a6a3c 100644
--- a/starboard/build/toolchain/BUILD.gn
+++ b/starboard/build/toolchain/BUILD.gn
@@ -13,8 +13,59 @@
 # limitations under the License.
 
 import("//build/config/clang/clang.gni")
+import("//build/config/win/visual_studio_version.gni")
 import("//build/toolchain/gcc_toolchain.gni")
+import("//starboard/build/config/os_definitions.gni")
 
-clang_toolchain("clang") {
-  clang_base_path = clang_base_path
+# Use this toolchain for linux-x64 platforms.
+if (is_linux && !using_old_compiler && target_cpu == "x64") {
+  clang_toolchain("clang") {
+    clang_base_path = clang_base_path
+  }
+} else {
+  import("//$starboard_path/toolchain/variables.gni")
+  assert(
+      defined(native_linker_path),
+      "native_linker_path should be defined in $starboard_path/toolchain/variables.gni")
+}
+
+# Use this toolchain for raspi platforms, old linux compiler platforms.
+if ((is_linux && target_cpu == "arm") || (using_old_compiler && !is_host_win)) {
+  gcc_toolchain("clang") {
+    prefix = rebase_path("$clang_base_path/bin", root_build_dir)
+    cc = "$prefix/clang"
+    cxx = "$prefix/clang++"
+    ld = native_linker_path
+    readelf = "readelf"
+    ar = "${prefix}/llvm-ar"
+    nm = "nm"
+    toolchain_args = {
+      is_clang = true
+    }
+  }
+}
+
+# Use this toolchain for Windows based platforms.
+if (is_host_win) {
+  gcc_toolchain("clang") {
+    prefix = llvm_clang_path
+    cc = "$prefix/clang.exe"
+    cxx = "$prefix/clang++.exe"
+    ld = native_linker_path
+    readelf = "$prefix/llvm-readobj.exe"
+    ar = "${prefix}/llvm-ar.exe"
+    nm = "${prefix}/llvm-nm.exe"
+    toolchain_args = {
+      is_clang = true
+    }
+
+    executable_extension = native_executable_extension
+    shlib_extension = native_shlib_extension
+    if (defined(native_snarl_linker)) {
+      using_snarl_linker = true
+    }
+    toolchain_args = {
+      is_clang = true
+    }
+  }
 }
diff --git a/starboard/common/optional.h b/starboard/common/optional.h
index 6c65763..f67609c 100644
--- a/starboard/common/optional.h
+++ b/starboard/common/optional.h
@@ -21,7 +21,6 @@
 
 #include "starboard/common/log.h"
 #include "starboard/configuration.h"
-#include "starboard/export.h"
 #include "starboard/memory.h"
 
 namespace starboard {
@@ -89,7 +88,7 @@
 extern const in_place_t in_place;
 
 template <typename T>
-class SB_EXPORT optional {
+class optional {
  public:
   // Construction via the default constructor results in an optional that is
   // not engaged.
diff --git a/starboard/contrib/cast/README.md b/starboard/contrib/cast/README.md
new file mode 100644
index 0000000..479ab3c
--- /dev/null
+++ b/starboard/contrib/cast/README.md
@@ -0,0 +1,53 @@
+## Cast Starboard API
+
+The Cast Starboard API is a shared library which contains the portion of
+Starboard required to run Cast.
+
+### Customizations
+
+As of Starboard 15, there are public methods required for Cast that are not
+already exposed by `libstarboard_platform_group.so`. As a result, the dedicated
+header `cast_starboard_api.h` is omitted from this release, though it may
+return in the future.
+
+Cast still requires additional behavior be implemented behind the existing
+Starboard APIs. Reference the `Cast TV Integration Guide` for details.
+
+### Reference Implementation
+
+The `cast_starboard_api/samples/` directory contains the reference target
+`cast_starboard_api` which can be built when both
+`build_with_separate_cobalt_toolchain=true` and `use_contrib_cast=true` are
+specified. To generate the target:
+
+```
+gn gen out/linux-x64x11_devel --args="target_platform=\"linux-x64x11\" build_with_separate_cobalt_toolchain=true use_contrib_cast=true build_type=\"devel\""
+```
+
+To build the target:
+
+```
+ninja -C out/linux-x64x11_devel/ cast_starboard_api
+```
+
+### Test Suite
+
+Tests for Cast-specific behaviors are not currently included in NPLB or YTS.
+
+A limited test suite, `cast_starboard_api_test`, is provided to ensure the
+standalone library can be initialized and a window surface can be created in the
+format required by Cast. To build the test suite:
+
+```
+ninja -C out/linux-x64x11_devel/ cast_starboard_api_test
+```
+
+To run the test suite:
+
+```
+./out/linux-x64x11_devel/cast_starboard_api_test_loader
+```
+
+### Known Issues
+
+- When `build_type=\"devel\"`, some systems may SB_DCHECK in `NetworkNotifier`.
diff --git a/starboard/contrib/cast/cast.gni b/starboard/contrib/cast/cast.gni
new file mode 100644
index 0000000..4a0cd3f
--- /dev/null
+++ b/starboard/contrib/cast/cast.gni
@@ -0,0 +1,5 @@
+declare_args() {
+  # Includes `//starboard/contrib/cast/cast_starboard_api/samples:cast` into
+  # `//starboard:gn_all`.
+  use_contrib_cast = false
+}
diff --git a/starboard/contrib/cast/cast_starboard_api/samples/BUILD.gn b/starboard/contrib/cast/cast_starboard_api/samples/BUILD.gn
new file mode 100644
index 0000000..a26bfa4
--- /dev/null
+++ b/starboard/contrib/cast/cast_starboard_api/samples/BUILD.gn
@@ -0,0 +1,57 @@
+# Copyright 2023 The Cobalt Authors. All rights reserved.
+import("//starboard/build/config/starboard_target_type.gni")
+import("//starboard/contrib/cast/cast.gni")
+
+assert(build_with_separate_cobalt_toolchain && use_contrib_cast)
+
+group("cast") {
+  public_deps = [ ":cast_starboard_api($starboard_toolchain)" ]
+}
+
+config("default") {
+  ldflags = [
+    # Hide unwanted symbols, which also shrinks the resulting binary.
+    "-Wl,--version-script=" +
+        rebase_path("./cast_starboard_api.lds", root_build_dir),
+  ]
+
+  if (!use_asan) {
+    ldflags += [
+      # Prevent unresolved symbols, which would require the consumer of
+      # `cast_starboard_api` to be aware of transitive dependences at runtime.
+      "-Wl,-z,defs",
+    ]
+  }
+}
+
+if (current_toolchain == starboard_toolchain) {
+  starboard_platform_target("cast_starboard_api") {
+    extra_configs = [ ":default" ]
+  }
+
+  copy("cast_starboard_api_test_data") {
+    install_content = true
+    sources = [ "$root_out_dir/libcast_starboard_api.so" ]
+
+    # This artifact is consumed by a test built outside of the
+    # starboard_toolchain; move from `starboard/content/` to `content/`.
+    outputs = [
+      "$sb_static_contents_output_data_dir/../../content/{{source_file_part}}",
+    ]
+    deps = [ ":cast_starboard_api" ]
+  }
+}
+
+target(gtest_target_type, "cast_starboard_api_test") {
+  testonly = true
+  sources = [
+    "//starboard/common/test_main.cc",
+    "cast_starboard_api_test.cc",
+  ]
+  data_deps = [ ":cast_starboard_api_test_data($starboard_toolchain)" ]
+  deps = [
+    "//starboard",
+    "//starboard/nplb/testdata/file_tests:nplb_file_tests_data",
+    "//testing/gtest",
+  ]
+}
diff --git a/starboard/contrib/cast/cast_starboard_api/samples/cast_starboard_api.lds b/starboard/contrib/cast/cast_starboard_api/samples/cast_starboard_api.lds
new file mode 100644
index 0000000..b686799
--- /dev/null
+++ b/starboard/contrib/cast/cast_starboard_api/samples/cast_starboard_api.lds
@@ -0,0 +1,9 @@
+# Copyright 2023 The Cobalt Authors. All rights reserved.
+LIBCAST_STARBOARD_API {
+  global:
+    CastStarboardApi*;
+    kSb*;
+    Sb*;
+  local:
+    *;
+};
diff --git a/starboard/contrib/cast/cast_starboard_api/samples/cast_starboard_api_test.cc b/starboard/contrib/cast/cast_starboard_api/samples/cast_starboard_api_test.cc
new file mode 100644
index 0000000..cc6ff4d
--- /dev/null
+++ b/starboard/contrib/cast/cast_starboard_api/samples/cast_starboard_api_test.cc
@@ -0,0 +1,271 @@
+// Copyright 2023 The Cobalt Authors. All rights reserved.
+
+#include <dlfcn.h>
+
+#include "starboard/common/condition_variable.h"
+#include "starboard/common/log.h"
+#include "starboard/common/mutex.h"
+#include "starboard/common/thread.h"
+#include "starboard/egl.h"
+#include "starboard/event.h"
+#include "starboard/file.h"
+#include "starboard/gles.h"
+#include "starboard/system.h"
+#include "starboard/window.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class CastStarboardApiTest : public ::testing::Test {
+ public:
+  // This acts as a proxy to the Starboard implementation provided by
+  // `libcast_starboard_api.so`.
+  struct CastStarboardApi {
+    decltype(SbRunStarboardMain)* SbRunStarboardMain;
+    decltype(SbEventSchedule)* SbEventSchedule;
+    decltype(SbGetEglInterface)* SbGetEglInterface;
+    decltype(SbGetGlesInterface)* SbGetGlesInterface;
+    decltype(SbWindowCreate)* SbWindowCreate;
+    decltype(SbWindowDestroy)* SbWindowDestroy;
+    decltype(SbWindowGetPlatformHandle)* SbWindowGetPlatformHandle;
+    decltype(SbSystemRequestStop)* SbSystemRequestStop;
+  };
+
+  CastStarboardApiTest();
+  ~CastStarboardApiTest();
+
+  // Receives events from the static |EventHandle|. If |event| is non-null,
+  // add its type to |received_| so tests can see it. If |event| is null,
+  // then it was a manually scheduled event and we signal |received_cond_|.
+  void EventHandleInternal(const SbEvent* event);
+
+  // Waits until |received_cond_| has been signalled; used to ensure the event
+  // loop is running.
+  void WaitForEventCallback();
+
+  // Signals |received_cond_|.
+  void EventCallbackInternal();
+
+  CastStarboardApi& api() { return api_; }
+  const std::vector<SbEventType>& events() { return received_; }
+
+ private:
+  class CastStarboardApiThread : public starboard::Thread {
+   public:
+    explicit CastStarboardApiThread(CastStarboardApi* api)
+        : starboard::Thread("cast_thread"), api_(api) {}
+
+    void Run() override;
+
+   private:
+    CastStarboardApi* api_;
+  };
+
+  template <class FuncType>
+  void DlSym(void* lib, const char* func_name, FuncType* func) {
+    *func = (FuncType)(dlsym(lib, func_name));
+    EXPECT_NE(func, nullptr) << func_name << " could not be loaded";
+  }
+
+  CastStarboardApi api_;
+
+  // These properties are used to initialize the main Starboard thread.
+  std::unique_ptr<CastStarboardApiThread> sb_thread_;
+  std::unique_ptr<starboard::ConditionVariable> started_cond_;
+  starboard::Mutex started_mutex_;
+
+  // These properties are used to track event dispatch during tests.
+  std::vector<SbEventType> received_;
+  starboard::Mutex received_mutex_;
+  std::unique_ptr<starboard::ConditionVariable> received_cond_;
+};
+
+// A behavior in the default implementation prevents dlclose from being used on
+// this library, so we must only open it once.
+void* g_lib = nullptr;
+
+// |EventHandleStatic| must be able to operate on the |g_test_instance|, so it's
+// tracked here.
+CastStarboardApiTest* g_test_instance = nullptr;
+
+// Static callback for SbEvent(s); forwards to |EventHandleInternal|.
+void EventHandleStatic(const SbEvent* event) {
+  g_test_instance->EventHandleInternal(event);
+}
+
+// Static callback for manually scheduled events; forwards to
+// |EventHandleInternal|.
+void EventCallbackStatic(void* context) {
+  static_cast<CastStarboardApiTest*>(context)->EventCallbackInternal();
+}
+
+CastStarboardApiTest::CastStarboardApiTest() {
+  g_test_instance = this;
+  started_cond_ =
+      std::make_unique<starboard::ConditionVariable>(started_mutex_);
+  received_cond_ =
+      std::make_unique<starboard::ConditionVariable>(received_mutex_);
+
+  // Ensure libcast_starboard_api.so has been opened.
+  if (!g_lib) {
+    std::vector<char> content_path(kSbFileMaxPath + 1);
+    EXPECT_TRUE(SbSystemGetPath(kSbSystemPathContentDirectory,
+                                content_path.data(), kSbFileMaxPath + 1));
+    const std::string lib_path = std::string(content_path.data()) +
+                                 kSbFileSepChar + "libcast_starboard_api.so";
+    g_lib = dlopen(lib_path.c_str(), RTLD_LAZY);
+    EXPECT_NE(g_lib, nullptr)
+        << lib_path << " could not be loaded: " << dlerror();
+  }
+
+  // Assign the |api_| methods.
+  DlSym(g_lib, "SbRunStarboardMain", &api_.SbRunStarboardMain);
+  DlSym(g_lib, "SbEventSchedule", &api_.SbEventSchedule);
+  DlSym(g_lib, "SbGetEglInterface", &api_.SbGetEglInterface);
+  DlSym(g_lib, "SbGetGlesInterface", &api_.SbGetGlesInterface);
+  DlSym(g_lib, "SbWindowCreate", &api_.SbWindowCreate);
+  DlSym(g_lib, "SbWindowDestroy", &api_.SbWindowDestroy);
+  DlSym(g_lib, "SbWindowGetPlatformHandle", &api_.SbWindowGetPlatformHandle);
+  DlSym(g_lib, "SbSystemRequestStop", &api_.SbSystemRequestStop);
+
+  // Start the Starboard thread and wait for kSbEventTypeStart to propagate.
+  sb_thread_ = std::make_unique<CastStarboardApiThread>(&api_);
+  sb_thread_->Start();
+
+  // Watch event for initialation completion.
+  started_mutex_.Acquire();
+  started_cond_->Wait();
+  started_mutex_.Release();
+}
+
+CastStarboardApiTest::~CastStarboardApiTest() {
+  api().SbSystemRequestStop(0);
+  sb_thread_->Join();
+  sb_thread_.reset();
+  started_cond_.reset();
+  g_test_instance = nullptr;
+}
+
+void CastStarboardApiTest::EventHandleInternal(const SbEvent* event) {
+  switch (event->type) {
+    case kSbEventTypeStart:
+      started_cond_->Signal();
+      break;
+    default:
+      break;
+  }
+
+  received_.push_back(event->type);
+}
+
+void CastStarboardApiTest::EventCallbackInternal() {
+  received_cond_->Signal();
+}
+
+void CastStarboardApiTest::WaitForEventCallback() {
+  received_mutex_.Acquire();
+  received_cond_->Wait();
+  received_mutex_.Release();
+}
+
+void CastStarboardApiTest::CastStarboardApiThread::Run() {
+  api_->SbRunStarboardMain(0, nullptr, &EventHandleStatic);
+}
+
+// The default Application only be started once in the lifetime of the
+// executable. To simplify and avoid race conditions in the test framework, both
+// test scenarios are implemented in a single test case.
+TEST_F(CastStarboardApiTest, EventLoop_CastWindowSurface) {
+  // Test 1:
+  // Ensure the event loop is running after initialization, and that SbEvent(s)
+  // are being received. Effectively tests |SbRunStarboardMain|.
+  api().SbEventSchedule(&EventCallbackStatic, this, 0);
+  WaitForEventCallback();
+  EXPECT_FALSE(events().empty());
+  EXPECT_EQ(events().front(), kSbEventTypeStart);
+
+  // Test 2:
+  // Ensure that a window surface can be created which supports the config
+  // required by Cast.
+  const SbEglInterface* egl = api().SbGetEglInterface();
+
+  // Cast expects `SB_EGL_DEFAULT_DISPLAY` to refer to the correct display.
+  SbEglDisplay display = egl->eglGetDisplay(SB_EGL_DEFAULT_DISPLAY);
+  EXPECT_TRUE(egl->eglInitialize(display, nullptr, nullptr))
+      << "Failed eglInitialize for display: " << egl->eglGetError();
+
+  SbWindow window = api().SbWindowCreate(nullptr);
+  SbEglNativeWindowType window_type = reinterpret_cast<SbEglNativeWindowType>(
+      api().SbWindowGetPlatformHandle(window));
+
+  SbEglInt32 config_attribs[] = {SB_EGL_BUFFER_SIZE,
+                                 32,
+                                 SB_EGL_ALPHA_SIZE,
+                                 8,
+                                 SB_EGL_BLUE_SIZE,
+                                 8,
+                                 SB_EGL_GREEN_SIZE,
+                                 8,
+                                 SB_EGL_RED_SIZE,
+                                 8,
+                                 SB_EGL_RENDERABLE_TYPE,
+                                 SB_EGL_OPENGL_ES2_BIT,
+                                 SB_EGL_SURFACE_TYPE,
+                                 SB_EGL_WINDOW_BIT,
+                                 SB_EGL_NONE};
+
+  int32_t num_configs;
+  EXPECT_TRUE(
+      egl->eglChooseConfig(display, config_attribs, nullptr, 0, &num_configs))
+      << "Failed eglChooseConfig for the specified config_attribs: "
+      << egl->eglGetError();
+  EXPECT_NE(num_configs, 0) << "No suitable EGL configs found.";
+
+  std::unique_ptr<SbEglConfig[]> configs(new SbEglConfig[num_configs]);
+  EXPECT_TRUE(egl->eglChooseConfig(display, config_attribs, configs.get(),
+                                   num_configs, &num_configs))
+      << "Failed eglChooseConfig: " << egl->eglGetError();
+
+  SbEglSurface surface = nullptr;
+  SbEglConfig config;
+  for (int i = 0; i < num_configs; i++) {
+    surface =
+        egl->eglCreateWindowSurface(display, configs[i], window_type, NULL);
+    if (surface) {
+      config = configs[i];
+      egl->eglDestroySurface(display, surface);
+      break;
+    }
+  }
+
+  EXPECT_NE(surface, nullptr)
+      << "Failed eglCreateWindowSurface for all configs: "
+      << egl->eglGetError();
+  const SbGlesInterface* gles = api().SbGetGlesInterface();
+
+  const SbEglInt32 context_attribs[] = {SB_EGL_CONTEXT_CLIENT_VERSION, 2,
+                                        SB_EGL_NONE};
+
+  SbEglContext context =
+      egl->eglCreateContext(display, config, NULL, context_attribs);
+  egl->eglMakeCurrent(display, SB_EGL_NO_SURFACE, SB_EGL_NO_SURFACE, context);
+  std::string version(
+      reinterpret_cast<const char*>(gles->glGetString(SB_GL_VERSION)));
+  const std::string prefix = "OpenGL ES ";
+  EXPECT_TRUE(version.find(prefix, 0) == 0);
+  EXPECT_GT(version.length(), prefix.length() + 1);
+
+  char actual_version = version.at(prefix.length());
+  if (!(actual_version == '2' || actual_version == '3')) {
+    // Normally we could check whether gles->glGetStringi (or other OpenGL 3+
+    // functions) are non-null, but various Starboard implementations actually
+    // report the underlying version even if OpenGL 3+ functions are not
+    // exposed. Cast will automatically treat any valid OpenGL 2+ version as
+    // OpenGL ES 2.0.
+    FAIL() << "Expected OpenGL ES 2 or 3.";
+  }
+
+  api().SbWindowDestroy(window);
+}
+
+}  // namespace
diff --git a/starboard/evergreen/shared/platform_configuration/BUILD.gn b/starboard/evergreen/shared/platform_configuration/BUILD.gn
index 01ac96f..2340c86 100644
--- a/starboard/evergreen/shared/platform_configuration/BUILD.gn
+++ b/starboard/evergreen/shared/platform_configuration/BUILD.gn
@@ -28,7 +28,7 @@
     "-Wl,-u GetEvergreenSabiString",
   ]
 
-  if (!build_with_separate_cobalt_toolchain) {
+  if (sb_is_evergreen) {
     ldflags += [ "-nostdlib" ]
   }
   cflags = [
diff --git a/starboard/evergreen/shared/platform_configuration/configuration.gni b/starboard/evergreen/shared/platform_configuration/configuration.gni
index c76baea..4327e1d 100644
--- a/starboard/evergreen/shared/platform_configuration/configuration.gni
+++ b/starboard/evergreen/shared/platform_configuration/configuration.gni
@@ -14,9 +14,7 @@
 
 import("//starboard/build/config/base_configuration.gni")
 
-sb_is_modular = true
-
-sb_is_evergreen = true
+sb_is_evergreen = !build_with_separate_cobalt_toolchain
 
 cobalt_font_package = "empty"
 
diff --git a/starboard/extension/BUILD.gn b/starboard/extension/BUILD.gn
index 5212664..57b95b1 100644
--- a/starboard/extension/BUILD.gn
+++ b/starboard/extension/BUILD.gn
@@ -26,7 +26,7 @@
     "//testing/gmock",
     "//testing/gtest",
   ]
-  if (sb_is_modular) {
+  if (sb_is_modular && current_toolchain == cobalt_toolchain) {
     deps += cobalt_platform_dependencies
   }
 }
diff --git a/starboard/linux/shared/BUILD.gn b/starboard/linux/shared/BUILD.gn
index 065f2c4..57b7908 100644
--- a/starboard/linux/shared/BUILD.gn
+++ b/starboard/linux/shared/BUILD.gn
@@ -428,13 +428,15 @@
     build_loader = false
     testonly = true
 
-    sources = media_tests_sources + player_tests_sources +
-              [ "//starboard/common/test_main.cc" ]
+    sources = media_tests_sources + player_tests_sources + [
+                "//starboard/common/test_main.cc",
+                "//starboard/shared/ffmpeg/ffmpeg_audio_decoder_test.cc",
+              ]
 
     configs += [ "//starboard/build/config:starboard_implementation" ]
 
     deps = [
-      "//starboard",
+      "//starboard:starboard_with_main",
       "//starboard/shared/starboard/player/filter/testing:test_util",
       "//testing/gmock",
       "//testing/gtest",
diff --git a/starboard/linux/shared/launcher.py b/starboard/linux/shared/launcher.py
index 022073a..10cdbde 100644
--- a/starboard/linux/shared/launcher.py
+++ b/starboard/linux/shared/launcher.py
@@ -31,8 +31,6 @@
 # This file is still executed with Python2 in CI.
 # pylint:disable=consider-using-f-string,super-with-arguments
 
-IS_MODULAR_BUILD = os.getenv("MODULAR_BUILD", "0") == "1"
-
 
 def GetProcessStatus(pid):
   """Returns process running status given its pid, or empty string if not found.
@@ -63,7 +61,7 @@
       self.device_ip = socket.gethostbyname(socket.gethostname())
 
     self.executable = self.GetTargetPath()
-    if IS_MODULAR_BUILD:
+    if abstract_launcher.IS_MODULAR_BUILD:
       self.executable += "_loader"
       if not os.path.exists(self.executable):
         self.executable = os.path.abspath(
diff --git a/starboard/linux/shared/media_is_audio_supported.cc b/starboard/linux/shared/media_is_audio_supported.cc
index 8d2e637..d857bd5 100644
--- a/starboard/linux/shared/media_is_audio_supported.cc
+++ b/starboard/linux/shared/media_is_audio_supported.cc
@@ -49,6 +49,12 @@
   if (audio_codec == kSbMediaAudioCodecMp3) {
     return bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond;
   }
+  if (audio_codec == kSbMediaAudioCodecPcm) {
+    return bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond;
+  }
+  if (audio_codec == kSbMediaAudioCodecFlac) {
+    return bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond;
+  }
 #endif  // SB_API_VERSION >= 14
 
   return false;
diff --git a/starboard/linux/shared/platform_configuration/BUILD.gn b/starboard/linux/shared/platform_configuration/BUILD.gn
index 87ea329..4e7334a 100644
--- a/starboard/linux/shared/platform_configuration/BUILD.gn
+++ b/starboard/linux/shared/platform_configuration/BUILD.gn
@@ -63,8 +63,8 @@
       # Do not warn about unused function params.
       "-Wno-unused-parameter",
     ]
-    if (build_with_separate_cobalt_toolchain) {
-      # If we're building modularly, we need visibility.
+    if (sb_is_modular && !sb_is_evergreen) {
+      # If we're building with cobalt toolchain and native linker, we need visibility.
       cflags += [ "-fvisibility=default" ]
     } else {
       # Default visibility to hidden, to enable dead stripping.
@@ -143,7 +143,7 @@
     "rt",
   ]
 
-  if (!sb_is_modular) {
+  if (!sb_is_modular || current_toolchain != cobalt_toolchain) {
     ldflags += [ "-static-libstdc++" ]
   }
 
diff --git a/starboard/linux/x64x11/BUILD.gn b/starboard/linux/x64x11/BUILD.gn
index ad6b13a..3666f36 100644
--- a/starboard/linux/x64x11/BUILD.gn
+++ b/starboard/linux/x64x11/BUILD.gn
@@ -15,7 +15,10 @@
 static_library("starboard_platform") {
   check_includes = false
 
-  sources = [ "//starboard/linux/x64x11/main.cc" ]
+  sources = [ "//starboard/linux/x64x11/run_starboard_main.cc" ]
+  if (!sb_is_modular || sb_is_evergreen) {
+    sources += [ "//starboard/linux/x64x11/main.cc" ]
+  }
 
   public_deps = [
     "//starboard/linux/x64x11/shared:starboard_platform",
@@ -24,3 +27,12 @@
 
   configs += [ "//starboard/build/config:starboard_implementation" ]
 }
+
+if (sb_is_modular && !sb_is_evergreen) {
+  static_library("starboard_platform_with_main") {
+    check_includes = false
+    sources = [ "//starboard/linux/x64x11/main.cc" ]
+    configs += [ "//starboard/build/config:starboard_implementation" ]
+    public_deps = [ ":starboard_platform" ]
+  }
+}
diff --git a/starboard/linux/x64x11/clang/3.9/BUILD.gn b/starboard/linux/x64x11/clang/3.9/BUILD.gn
index ba56f59..f947b50 100644
--- a/starboard/linux/x64x11/clang/3.9/BUILD.gn
+++ b/starboard/linux/x64x11/clang/3.9/BUILD.gn
@@ -15,7 +15,10 @@
 static_library("starboard_platform") {
   check_includes = false
 
-  sources = [ "//starboard/linux/x64x11/main.cc" ]
+  sources = [ "//starboard/linux/x64x11/run_starboard_main.cc" ]
+  if (!sb_is_modular || sb_is_evergreen) {
+    sources += [ "//starboard/linux/x64x11/main.cc" ]
+  }
 
   public_deps = [
     "//starboard/linux/x64x11/shared:starboard_platform",
diff --git a/starboard/linux/x64x11/egl/BUILD.gn b/starboard/linux/x64x11/egl/BUILD.gn
index ad6b13a..91ed007 100644
--- a/starboard/linux/x64x11/egl/BUILD.gn
+++ b/starboard/linux/x64x11/egl/BUILD.gn
@@ -15,7 +15,10 @@
 static_library("starboard_platform") {
   check_includes = false
 
-  sources = [ "//starboard/linux/x64x11/main.cc" ]
+  sources = [ "//starboard/linux/x64x11/run_starboard_main.cc" ]
+  if (!sb_is_modular || sb_is_evergreen) {
+    sources += [ "//starboard/linux/x64x11/main.cc" ]
+  }
 
   public_deps = [
     "//starboard/linux/x64x11/shared:starboard_platform",
diff --git a/starboard/linux/x64x11/gcc/6.3/BUILD.gn b/starboard/linux/x64x11/gcc/6.3/BUILD.gn
index ba56f59..f947b50 100644
--- a/starboard/linux/x64x11/gcc/6.3/BUILD.gn
+++ b/starboard/linux/x64x11/gcc/6.3/BUILD.gn
@@ -15,7 +15,10 @@
 static_library("starboard_platform") {
   check_includes = false
 
-  sources = [ "//starboard/linux/x64x11/main.cc" ]
+  sources = [ "//starboard/linux/x64x11/run_starboard_main.cc" ]
+  if (!sb_is_modular || sb_is_evergreen) {
+    sources += [ "//starboard/linux/x64x11/main.cc" ]
+  }
 
   public_deps = [
     "//starboard/linux/x64x11/shared:starboard_platform",
diff --git a/starboard/linux/x64x11/main.cc b/starboard/linux/x64x11/main.cc
index 82046a0..5cfd197 100644
--- a/starboard/linux/x64x11/main.cc
+++ b/starboard/linux/x64x11/main.cc
@@ -53,6 +53,7 @@
     return 1;
   }
 
+#if !SB_IS(MODULAR)
   bool start_handler_at_crash =
       command_line.HasSwitch(
           starboard::shared::starboard::kStartHandlerAtCrash) ||
@@ -60,6 +61,7 @@
           starboard::shared::starboard::kStartHandlerAtLaunch);
   third_party::crashpad::wrapper::InstallCrashpadHandler(start_handler_at_crash,
                                                          ca_certificates_path);
+#endif  // !SB_IS(MODULAR)
 #endif
 
 #if SB_HAS_QUIRK(BACKTRACE_DLOPEN_BUG)
@@ -83,15 +85,3 @@
   starboard::shared::signal::UninstallCrashSignalHandlers();
   return result;
 }
-
-#if SB_API_VERSION >= 15
-int SbRunStarboardMain(int argc, char** argv, SbEventHandleCallback callback) {
-  starboard::shared::x11::ApplicationX11 application(callback);
-  int result = 0;
-  {
-    starboard::shared::starboard::LinkReceiver receiver(&application);
-    result = application.Run(argc, argv);
-  }
-  return result;
-}
-#endif  // SB_API_VERSION >= 15
diff --git a/starboard/linux/x64x11/run_starboard_main.cc b/starboard/linux/x64x11/run_starboard_main.cc
new file mode 100644
index 0000000..813e1df
--- /dev/null
+++ b/starboard/linux/x64x11/run_starboard_main.cc
@@ -0,0 +1,30 @@
+// Copyright 2023 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/event.h"
+
+#include "starboard/shared/starboard/link_receiver.h"
+#include "starboard/shared/x11/application_x11.h"
+
+#if SB_API_VERSION >= 15
+int SbRunStarboardMain(int argc, char** argv, SbEventHandleCallback callback) {
+  starboard::shared::x11::ApplicationX11 application(callback);
+  int result = 0;
+  {
+    starboard::shared::starboard::LinkReceiver receiver(&application);
+    result = application.Run(argc, argv);
+  }
+  return result;
+}
+#endif  // SB_API_VERSION >= 15
diff --git a/starboard/linux/x64x11/shared/BUILD.gn b/starboard/linux/x64x11/shared/BUILD.gn
index d58d764..2da6b40 100644
--- a/starboard/linux/x64x11/shared/BUILD.gn
+++ b/starboard/linux/x64x11/shared/BUILD.gn
@@ -45,7 +45,7 @@
     "//starboard/shared/x11/window_internal.cc",
   ]
 
-  if (!build_with_separate_cobalt_toolchain) {
+  if (!sb_is_modular || sb_is_evergreen) {
     sources += [ "//starboard/linux/x64x11/sanitizer_options.cc" ]
   }
 
diff --git a/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn b/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn
index 32c24f6..ab9cf36 100644
--- a/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn
+++ b/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn
@@ -13,8 +13,8 @@
 # limitations under the License.
 
 config("platform_configuration") {
-  if (current_toolchain == default_toolchain &&
-      build_with_separate_cobalt_toolchain) {
+  if (current_toolchain == default_toolchain && sb_is_modular &&
+      !sb_is_evergreen) {
     configs = [ "//starboard/evergreen/x64/platform_configuration" ]
   } else {
     configs = [
@@ -28,7 +28,7 @@
 
 config("libraries") {
   configs = [ "//starboard/linux/shared/platform_configuration:libraries" ]
-  if (build_with_separate_cobalt_toolchain) {
+  if (sb_is_modular && !sb_is_evergreen) {
     libs = [ "//third_party/libvpx/platforms/linux-x64/libvpx.so.6" ]
     ldflags = [ "-Wl,-rpath=" + rebase_path("//") +
                 "/third_party/libvpx/platforms/linux-x64/" ]
diff --git a/starboard/linux/x64x11/skia/BUILD.gn b/starboard/linux/x64x11/skia/BUILD.gn
index 024e560..66917ea 100644
--- a/starboard/linux/x64x11/skia/BUILD.gn
+++ b/starboard/linux/x64x11/skia/BUILD.gn
@@ -16,11 +16,14 @@
   check_includes = false
 
   sources = [
-    "//starboard/linux/x64x11/main.cc",
+    "//starboard/linux/x64x11/run_starboard_main.cc",
     "//starboard/linux/x64x11/skia/configuration.cc",
     "//starboard/linux/x64x11/skia/configuration.h",
     "//starboard/linux/x64x11/skia/system_get_extensions.cc",
   ]
+  if (!sb_is_modular || sb_is_evergreen) {
+    sources += [ "//starboard/linux/x64x11/main.cc" ]
+  }
 
   public_deps = [
     "//starboard/linux/x64x11/shared:starboard_platform",
diff --git a/starboard/loader_app/BUILD.gn b/starboard/loader_app/BUILD.gn
index 4d38cce..e0384a1 100644
--- a/starboard/loader_app/BUILD.gn
+++ b/starboard/loader_app/BUILD.gn
@@ -106,6 +106,7 @@
       deps = [
         ":common_loader_app_dependencies",
         "//cobalt/content/fonts:copy_font_data",
+        "//starboard:starboard_with_main",
         "//starboard/elf_loader",
       ]
       if (sb_is_evergreen_compatible && sb_evergreen_compatible_package) {
diff --git a/starboard/nplb/BUILD.gn b/starboard/nplb/BUILD.gn
index 08e415a..664751b 100644
--- a/starboard/nplb/BUILD.gn
+++ b/starboard/nplb/BUILD.gn
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import("//starboard/build/config/os_definitions.gni")
+
 target(gtest_target_type, "nplb") {
   testonly = true
 
@@ -268,4 +270,31 @@
     # signedness_and_size_of_enum_test.cc casts -1 to enum value
     cflags = [ "-Wno-enum-constexpr-conversion" ]
   }
+
+  #  TODO b/296238576 Add these tests for windows based platform modular builds
+  if (sb_is_modular && !sb_is_evergreen && is_host_win) {
+    sources -= [
+      "maximum_player_configuration_explorer.cc",
+      "maximum_player_configuration_explorer.h",
+      "maximum_player_configuration_explorer_test.cc",
+      "media_buffer_test.cc",
+      "media_set_audio_write_duration_test.cc",
+      "player_create_test.cc",
+      "player_creation_param_helpers.cc",
+      "player_creation_param_helpers.h",
+      "player_get_audio_configuration_test.cc",
+      "player_get_preferred_output_mode_test.cc",
+      "player_test_fixture.cc",
+      "player_test_fixture.h",
+      "player_test_util.cc",
+      "player_test_util.h",
+      "player_write_sample_test.cc",
+      "vertical_video_test.cc",
+    ]
+
+    deps -= [
+      "//starboard/shared/starboard/media:media_util",
+      "//starboard/shared/starboard/player:video_dmp",
+    ]
+  }
 }
diff --git a/starboard/nplb/media_can_play_mime_and_key_system_test.cc b/starboard/nplb/media_can_play_mime_and_key_system_test.cc
index 2a5594e..a77a4e9 100644
--- a/starboard/nplb/media_can_play_mime_and_key_system_test.cc
+++ b/starboard/nplb/media_can_play_mime_and_key_system_test.cc
@@ -72,6 +72,21 @@
   return kDeviceTypeFHD;
 }
 
+testing::AssertionResult IsMimeAndKeySystemSupported(std::string param,
+                                                     std::string key_system) {
+  SbMediaSupportType support_type =
+      SbMediaCanPlayMimeAndKeySystem(param.c_str(), key_system.c_str());
+  if (support_type == kSbMediaSupportTypeProbably) {
+    return testing::AssertionSuccess();
+  }
+
+  return testing::AssertionFailure()
+         << "SbMediaCanPlayMimeAndKeySystem(\"" << param << "\", \""
+         << key_system << "\") returns " << support_type
+         << ". It should return kSbMediaSupportTypeProbably ("
+         << kSbMediaSupportTypeProbably << ").";
+}
+
 TEST(SbMediaCanPlayMimeAndKeySystem, SunnyDay) {
   // Vp9
   SbMediaCanPlayMimeAndKeySystem(
@@ -292,20 +307,20 @@
       mime_params = params_fhd;
   }
 
+  const char* key_system = "";
   for (auto& param : mime_params) {
-    SbMediaSupportType support = SbMediaCanPlayMimeAndKeySystem(param, "");
-    EXPECT_TRUE(support == kSbMediaSupportTypeProbably);
+    EXPECT_TRUE(IsMimeAndKeySystemSupported(param, key_system));
   }
 
   // AAC-LC
-  SbMediaSupportType result = SbMediaCanPlayMimeAndKeySystem(
-      "audio/mp4; codecs=\"mp4a.40.2\"; channels=2; bitrate=256000;", "");
-  ASSERT_EQ(result, kSbMediaSupportTypeProbably);
+  ASSERT_TRUE(IsMimeAndKeySystemSupported(
+      "audio/mp4; codecs=\"mp4a.40.2\"; channels=2; bitrate=256000",
+      key_system));
 
   // HE-AAC
-  result = SbMediaCanPlayMimeAndKeySystem(
-      "audio/mp4; codecs=\"mp4a.40.5\"; channels=2; bitrate=48000;", "");
-  ASSERT_EQ(result, kSbMediaSupportTypeProbably);
+  ASSERT_TRUE(IsMimeAndKeySystemSupported(
+      "audio/mp4; codecs=\"mp4a.40.5\"; channels=2; bitrate=48000",
+      key_system));
 }
 
 TEST(SbMediaCanPlayMimeAndKeySystem, AnySupportedKeySystems) {
diff --git a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
index 17937fa..4cf444b 100644
--- a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
@@ -20,6 +20,7 @@
 #include "starboard/audio_sink.h"
 #include "starboard/common/log.h"
 #include "starboard/common/string.h"
+#include "starboard/media.h"
 #include "starboard/memory.h"
 #include "starboard/shared/starboard/media/media_util.h"
 
@@ -36,7 +37,10 @@
   return kSbMediaAudioSampleTypeInt16Deprecated;
 }
 
-AVCodecID GetFfmpegCodecIdByMediaCodec(SbMediaAudioCodec audio_codec) {
+AVCodecID GetFfmpegCodecIdByMediaCodec(
+    starboard::media::AudioStreamInfo stream_info) {
+  SbMediaAudioCodec audio_codec = stream_info.codec;
+
   switch (audio_codec) {
     case kSbMediaAudioCodecAac:
       return AV_CODEC_ID_AAC;
@@ -57,6 +61,24 @@
 #if SB_API_VERSION >= 14
     case kSbMediaAudioCodecMp3:
       return AV_CODEC_ID_MP3;
+    case kSbMediaAudioCodecPcm:
+      if (stream_info.bits_per_sample == 16) {
+        return AV_CODEC_ID_PCM_S16LE;
+      } else {
+        SB_LOG(ERROR) << "PCM is only supported for 16-bit audio ("
+                      << stream_info.bits_per_sample
+                      << " bits per sample was requested)";
+        return AV_CODEC_ID_NONE;
+      }
+    case kSbMediaAudioCodecFlac:
+      if (stream_info.bits_per_sample == 16) {
+        return AV_CODEC_ID_FLAC;
+      } else {
+        SB_LOG(ERROR) << "FLAC is only supported for 16-bit audio ("
+                      << stream_info.bits_per_sample
+                      << " bits per sample was requested)";
+        return AV_CODEC_ID_NONE;
+      }
 #endif  // SB_API_VERSION >= 14
     default:
       return AV_CODEC_ID_NONE;
@@ -78,7 +100,7 @@
       stream_ended_(false),
       audio_stream_info_(audio_stream_info) {
   SB_DCHECK(g_registered) << "Decoder Specialization registration failed.";
-  SB_DCHECK(GetFfmpegCodecIdByMediaCodec(audio_stream_info_.codec) !=
+  SB_DCHECK(GetFfmpegCodecIdByMediaCodec(audio_stream_info_) !=
             AV_CODEC_ID_NONE)
       << "Unsupported audio codec " << audio_stream_info_.codec;
   ffmpeg_ = FFMPEGDispatch::GetInstance();
@@ -277,10 +299,16 @@
   }
 
   codec_context_->codec_type = AVMEDIA_TYPE_AUDIO;
-  codec_context_->codec_id =
-      GetFfmpegCodecIdByMediaCodec(audio_stream_info_.codec);
+  codec_context_->codec_id = GetFfmpegCodecIdByMediaCodec(audio_stream_info_);
   // Request_sample_fmt is set by us, but sample_fmt is set by the decoder.
-  if (GetSupportedSampleType() == kSbMediaAudioSampleTypeInt16Deprecated) {
+  if (GetSupportedSampleType() == kSbMediaAudioSampleTypeInt16Deprecated
+#if SB_API_VERSION >= 14
+      // If we request FLT for 16-bit FLAC, FFmpeg will pick S32 as the closest
+      // option. Since the rest of this pipeline doesn't support S32, we should
+      // use S16 as the desired format.
+      || audio_stream_info_.codec == kSbMediaAudioCodecFlac
+#endif  // SB_API_VERSION >= 14
+  ) {
     codec_context_->request_sample_fmt = AV_SAMPLE_FMT_S16;
   } else {
     codec_context_->request_sample_fmt = AV_SAMPLE_FMT_FLT;
diff --git a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_test.cc b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_test.cc
new file mode 100644
index 0000000..c009bb4
--- /dev/null
+++ b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_test.cc
@@ -0,0 +1,84 @@
+// Copyright 2023 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/shared/ffmpeg/ffmpeg_audio_decoder.h"
+
+#include <memory>
+
+#include "starboard/media.h"
+#include "starboard/shared/starboard/media/media_util.h"
+#include "starboard/shared/starboard/player/job_queue.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace shared {
+namespace ffmpeg {
+namespace {
+
+// The codecs tested by these tests were introduced in SB_API_VERSION 14.
+#if SB_API_VERSION >= 14
+using ::starboard::shared::starboard::media::AudioStreamInfo;
+using ::testing::NotNull;
+
+AudioStreamInfo CreateStreamInfoForCodec(SbMediaAudioCodec codec) {
+  AudioStreamInfo stream_info;
+  stream_info.codec = codec;
+  stream_info.number_of_channels = 2;
+  stream_info.samples_per_second = 44100;
+  stream_info.bits_per_sample = 8;
+  return stream_info;
+}
+
+class FFmpegAudioDecoderTest
+    : public ::testing::Test,
+      public ::starboard::shared::starboard::player::JobQueue::JobOwner {
+ protected:
+  FFmpegAudioDecoderTest() : JobOwner(kDetached) { AttachToCurrentThread(); }
+
+  ~FFmpegAudioDecoderTest() override = default;
+
+  // Create a JobQueue for use on the current thread.
+  ::starboard::shared::starboard::player::JobQueue job_queue_;
+};
+
+TEST_F(FFmpegAudioDecoderTest, SupportsMp3Codec) {
+  AudioStreamInfo stream_info = CreateStreamInfoForCodec(kSbMediaAudioCodecMp3);
+  std::unique_ptr<AudioDecoder> decoder(AudioDecoder::Create(stream_info));
+  ASSERT_THAT(decoder, NotNull());
+  EXPECT_TRUE(decoder->is_valid());
+}
+
+TEST_F(FFmpegAudioDecoderTest, SupportsFlacCodecFor16BitAudio) {
+  AudioStreamInfo stream_info =
+      CreateStreamInfoForCodec(kSbMediaAudioCodecFlac);
+  stream_info.bits_per_sample = 16;
+  std::unique_ptr<AudioDecoder> decoder(AudioDecoder::Create(stream_info));
+  ASSERT_THAT(decoder, NotNull());
+  EXPECT_TRUE(decoder->is_valid());
+}
+
+TEST_F(FFmpegAudioDecoderTest, SupportsPcmCodecFor16BitAudio) {
+  AudioStreamInfo stream_info = CreateStreamInfoForCodec(kSbMediaAudioCodecPcm);
+  stream_info.bits_per_sample = 16;
+  std::unique_ptr<AudioDecoder> decoder(AudioDecoder::Create(stream_info));
+  ASSERT_THAT(decoder, NotNull());
+  EXPECT_TRUE(decoder->is_valid());
+}
+#endif  // SB_API_VERSION >= 14
+
+}  // namespace
+}  // namespace ffmpeg
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/starboard/crash_handler.cc b/starboard/shared/starboard/crash_handler.cc
index a9c998f..e1fec16 100644
--- a/starboard/shared/starboard/crash_handler.cc
+++ b/starboard/shared/starboard/crash_handler.cc
@@ -29,7 +29,11 @@
 }
 
 bool SetString(const char* key, const char* value) {
+#if SB_IS(MODULAR)
+  return false;
+#else
   return third_party::crashpad::wrapper::InsertCrashpadAnnotation(key, value);
+#endif  // SB_IS(MODULAR)
 }
 
 const CobaltExtensionCrashHandlerApi kCrashHandlerApi = {
diff --git a/starboard/shared/starboard/media/codec_util.cc b/starboard/shared/starboard/media/codec_util.cc
index 291bee1..1c2c1b3 100644
--- a/starboard/shared/starboard/media/codec_util.cc
+++ b/starboard/shared/starboard/media/codec_util.cc
@@ -103,6 +103,11 @@
   if (strcmp(codec, "flac") == 0) {
     return kSbMediaAudioCodecFlac;
   }
+  // For WAV, the "codecs" parameter of a MIME type refers to the WAVE format
+  // ID, where 1 represents PCM: https://datatracker.ietf.org/doc/html/rfc2361
+  if (strcmp(codec, "1") == 0) {
+    return kSbMediaAudioCodecPcm;
+  }
 #endif  // SB_API_VERSION >= 14
 #if SB_API_VERSION >= 15
   if (strncmp(codec, "iamf.", 5) == 0) {
diff --git a/starboard/shared/starboard/media/media_tests.gni b/starboard/shared/starboard/media/media_tests.gni
index a720a48..4777863 100644
--- a/starboard/shared/starboard/media/media_tests.gni
+++ b/starboard/shared/starboard/media/media_tests.gni
@@ -17,6 +17,7 @@
   "//starboard/shared/starboard/media/codec_util_test.cc",
   "//starboard/shared/starboard/media/media_util_test.cc",
   "//starboard/shared/starboard/media/mime_type_test.cc",
+  "//starboard/shared/starboard/media/mime_util_test.cc",
   "//starboard/shared/starboard/media/video_capabilities_test.cc",
   "//starboard/shared/starboard/media/vp9_util_test.cc",
 ]
diff --git a/starboard/shared/starboard/media/mime_util.cc b/starboard/shared/starboard/media/mime_util.cc
index fe49830..0ef4b2d 100644
--- a/starboard/shared/starboard/media/mime_util.cc
+++ b/starboard/shared/starboard/media/mime_util.cc
@@ -37,12 +37,15 @@
 // Use SbMediaGetAudioConfiguration() to check if the platform can support
 // |channels|.
 bool IsAudioOutputSupported(SbMediaAudioCodingType coding_type, int channels) {
+  // TODO(b/284140486, b/297426689): Consider removing the call to
+  // `SbMediaGetAudioOutputCount()` completely as the loop will be terminated
+  // once `SbMediaGetAudioConfiguration()` returns false.
   int count = SbMediaGetAudioOutputCount();
 
   for (int output_index = 0; output_index < count; ++output_index) {
     SbMediaAudioConfiguration configuration;
     if (!SbMediaGetAudioConfiguration(output_index, &configuration)) {
-      continue;
+      break;
     }
 
     if (configuration.coding_type == coding_type &&
@@ -99,9 +102,20 @@
       break;
 #if SB_API_VERSION >= 14
     case kSbMediaAudioCodecMp3:
+      if (mime_type.subtype() != "mpeg" && mime_type.subtype() != "mp3") {
+        return false;
+      }
+      break;
     case kSbMediaAudioCodecFlac:
+      if (mime_type.subtype() != "ogg" && mime_type.subtype() != "flac") {
+        return false;
+      }
+      break;
     case kSbMediaAudioCodecPcm:
-      return false;
+      if (mime_type.subtype() != "wav") {
+        return false;
+      }
+      break;
 #endif  // SB_API_VERSION >= 14
 #if SB_API_VERSION >= 15
     case kSbMediaAudioCodecIamf:
diff --git a/starboard/shared/starboard/media/mime_util_test.cc b/starboard/shared/starboard/media/mime_util_test.cc
new file mode 100644
index 0000000..14784e6
--- /dev/null
+++ b/starboard/shared/starboard/media/mime_util_test.cc
@@ -0,0 +1,122 @@
+// Copyright 2023 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/shared/starboard/media/mime_util.h"
+
+#include "starboard/media.h"
+#include "starboard/shared/starboard/media/media_support_internal.h"
+#include "starboard/shared/starboard/media/mime_type.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+namespace {
+
+// The codecs tested by these tests were introduced in SB_API_VERSION 14.
+#if SB_API_VERSION >= 14
+constexpr char kEmptyKeySystem[] = "";
+constexpr int64_t kBitrate = 44100;
+
+TEST(MimeUtilTest, ChecksSupportedMp3Containers) {
+  const std::string valid_mp3_mime_str_1 =
+      "audio/mpeg; codecs=\"mp3\"; bitrate=44100";
+  const MimeType valid_mp3_mime_1(valid_mp3_mime_str_1);
+  EXPECT_EQ(
+      CanPlayMimeAndKeySystem(valid_mp3_mime_str_1.c_str(), kEmptyKeySystem),
+      SbMediaIsAudioSupported(kSbMediaAudioCodecMp3, &valid_mp3_mime_1,
+                              kBitrate)
+          ? kSbMediaSupportTypeProbably
+          : kSbMediaSupportTypeNotSupported);
+
+  const std::string valid_mp3_mime_str_2 =
+      "audio/mp3; codecs=\"mp3\"; bitrate=44100";
+  const MimeType valid_mp3_mime_2(valid_mp3_mime_str_2);
+  EXPECT_EQ(
+      CanPlayMimeAndKeySystem(valid_mp3_mime_str_2.c_str(), kEmptyKeySystem),
+      SbMediaIsAudioSupported(kSbMediaAudioCodecMp3, &valid_mp3_mime_2,
+                              kBitrate)
+          ? kSbMediaSupportTypeProbably
+          : kSbMediaSupportTypeNotSupported);
+}
+
+TEST(MimeUtilTest, ChecksUnsupportedMp3Containers) {
+  // Invalid container for MP3 codec.
+  const std::string invalid_mp3_mime_str =
+      "audio/mp4; codecs=\"mp3\"; bitrate=44100";
+  const MimeType invalid_mp3_mime(invalid_mp3_mime_str);
+  EXPECT_EQ(
+      CanPlayMimeAndKeySystem(invalid_mp3_mime_str.c_str(), kEmptyKeySystem),
+      kSbMediaSupportTypeNotSupported);
+}
+
+TEST(MimeUtilTest, ChecksSupportedFlacContainers) {
+  const std::string valid_flac_mime_str_1 =
+      "audio/ogg; codecs=\"flac\"; bitrate=44100";
+  const MimeType valid_flac_mime_1(valid_flac_mime_str_1);
+  EXPECT_EQ(
+      CanPlayMimeAndKeySystem(valid_flac_mime_str_1.c_str(), kEmptyKeySystem),
+      SbMediaIsAudioSupported(kSbMediaAudioCodecMp3, &valid_flac_mime_1,
+                              kBitrate)
+          ? kSbMediaSupportTypeProbably
+          : kSbMediaSupportTypeNotSupported);
+
+  const std::string valid_flac_mime_str_2 =
+      "audio/flac; codecs=\"flac\"; bitrate=44100";
+  const MimeType valid_flac_mime_2(valid_flac_mime_str_2);
+  EXPECT_EQ(
+      CanPlayMimeAndKeySystem(valid_flac_mime_str_2.c_str(), kEmptyKeySystem),
+      SbMediaIsAudioSupported(kSbMediaAudioCodecMp3, &valid_flac_mime_2,
+                              kBitrate)
+          ? kSbMediaSupportTypeProbably
+          : kSbMediaSupportTypeNotSupported);
+}
+
+TEST(MimeUtilTest, ChecksUnsupportedFlacContainers) {
+  // Invalid container for FLAC codec.
+  const std::string invalid_flac_mime_str =
+      "audio/mp4; codecs=\"flac\"; bitrate=44100";
+  const MimeType invalid_flac_mime(invalid_flac_mime_str);
+  EXPECT_EQ(
+      CanPlayMimeAndKeySystem(invalid_flac_mime_str.c_str(), kEmptyKeySystem),
+      kSbMediaSupportTypeNotSupported);
+}
+
+TEST(MimeUtilTest, ChecksSupportedPcmContainers) {
+  const std::string valid_pcm_mime_str =
+      "audio/wav; codecs=\"1\"; bitrate=44100";
+  const MimeType valid_pcm_mime(valid_pcm_mime_str);
+  EXPECT_EQ(
+      CanPlayMimeAndKeySystem(valid_pcm_mime_str.c_str(), kEmptyKeySystem),
+      SbMediaIsAudioSupported(kSbMediaAudioCodecPcm, &valid_pcm_mime, kBitrate)
+          ? kSbMediaSupportTypeProbably
+          : kSbMediaSupportTypeNotSupported);
+}
+
+TEST(MimeUtilTest, ChecksUnsupportedWavCodecs) {
+  const std::string invalid_wav_mime_str =
+      "audio/wav; codecs=\"aac\"; bitrate=44100";
+  const MimeType invalid_wav_mime(invalid_wav_mime_str);
+  EXPECT_EQ(
+      CanPlayMimeAndKeySystem(invalid_wav_mime_str.c_str(), kEmptyKeySystem),
+      kSbMediaSupportTypeNotSupported);
+}
+#endif  // SB_API_VERSION >= 14
+
+}  // namespace
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/starboard/player/filter/audio_resampler_impl.cc b/starboard/shared/starboard/player/filter/audio_resampler_impl.cc
index c07f29e..b0fa874 100644
--- a/starboard/shared/starboard/player/filter/audio_resampler_impl.cc
+++ b/starboard/shared/starboard/player/filter/audio_resampler_impl.cc
@@ -12,9 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "starboard/shared/starboard/player/filter/audio_resampler.h"
+
+#include <deque>
+
 #include "starboard/common/log.h"
 #include "starboard/configuration.h"
-#include "starboard/shared/starboard/player/filter/audio_resampler.h"
 #include "starboard/shared/starboard/player/filter/interleaved_sinc_resampler.h"
 
 namespace starboard {
@@ -58,8 +61,11 @@
   SbMediaAudioSampleType destination_sample_type_;
   SbMediaAudioFrameStorageType destination_storage_type_;
 
+  std::deque<scoped_refptr<DecodedAudio>> audio_inputs_;
+
   size_t frames_to_resample_ = 0;
   size_t frames_resampled_ = 0;
+  size_t frames_outputted_ = 0;
 };
 
 }  // namespace
@@ -82,69 +88,84 @@
 scoped_refptr<DecodedAudio> AudioResamplerImpl::WriteEndOfStream() {
   double sample_rate_ratio = interleaved_resampler_.GetSampleRateRatio();
   int out_num_of_frames =
-      round(frames_to_resample_ / sample_rate_ratio) - frames_resampled_;
+      round(frames_to_resample_ / sample_rate_ratio) - frames_outputted_;
   if (out_num_of_frames > 0) {
-    interleaved_resampler_.QueueBuffer(nullptr, 0);
-    int channels = interleaved_resampler_.channels();
-    int resampled_audio_size = out_num_of_frames * channels * sizeof(float);
-    scoped_refptr<DecodedAudio> resampled_audio = new DecodedAudio(
-        channels, kSbMediaAudioSampleTypeFloat32,
-        kSbMediaAudioFrameStorageTypeInterleaved, 0, resampled_audio_size);
+    SB_DCHECK(!audio_inputs_.empty());
+    // If there're frames left in |interleaved_resampler_|, |audio_inputs_|
+    // should not be empty. To be cautious in production, we add a check here.
+    if (!audio_inputs_.empty()) {
+      interleaved_resampler_.QueueBuffer(nullptr, 0);
+      int channels = interleaved_resampler_.channels();
+      int resampled_audio_size = out_num_of_frames * channels * sizeof(float);
+      scoped_refptr<DecodedAudio> resampled_audio = new DecodedAudio(
+          channels, kSbMediaAudioSampleTypeFloat32,
+          kSbMediaAudioFrameStorageTypeInterleaved,
+          audio_inputs_.front()->timestamp(), resampled_audio_size);
 
-    float* dst = reinterpret_cast<float*>(resampled_audio->data());
-    interleaved_resampler_.Resample(dst, out_num_of_frames);
+      float* dst = reinterpret_cast<float*>(resampled_audio->data());
+      interleaved_resampler_.Resample(dst, out_num_of_frames);
 
-    if (!resampled_audio->IsFormat(destination_sample_type_,
-                                   destination_storage_type_)) {
-      resampled_audio = resampled_audio->SwitchFormatTo(
-          destination_sample_type_, destination_storage_type_);
+      if (!resampled_audio->IsFormat(destination_sample_type_,
+                                     destination_storage_type_)) {
+        resampled_audio = resampled_audio->SwitchFormatTo(
+            destination_sample_type_, destination_storage_type_);
+      }
+      return resampled_audio;
     }
-    return resampled_audio;
   }
 
   return new DecodedAudio;
 }
 
 scoped_refptr<DecodedAudio> AudioResamplerImpl::Resample(
-    scoped_refptr<DecodedAudio> audio_data) {
-  SB_DCHECK(audio_data->channels() == interleaved_resampler_.channels());
+    scoped_refptr<DecodedAudio> audio_input) {
+  SB_DCHECK(audio_input->channels() == interleaved_resampler_.channels());
+
+  audio_inputs_.push_back(audio_input);
 
   // It does nothing if source sample type is float and source storage type is
   // interleaved.
-  if (!audio_data->IsFormat(kSbMediaAudioSampleTypeFloat32,
-                            kSbMediaAudioFrameStorageTypeInterleaved)) {
-    audio_data =
-        audio_data->SwitchFormatTo(kSbMediaAudioSampleTypeFloat32,
-                                   kSbMediaAudioFrameStorageTypeInterleaved);
+  if (!audio_input->IsFormat(kSbMediaAudioSampleTypeFloat32,
+                             kSbMediaAudioFrameStorageTypeInterleaved)) {
+    audio_input =
+        audio_input->SwitchFormatTo(kSbMediaAudioSampleTypeFloat32,
+                                    kSbMediaAudioFrameStorageTypeInterleaved);
   }
 
-  int num_of_frames = audio_data->frames();
-  frames_to_resample_ += num_of_frames;
-  int channels = audio_data->channels();
-  int out_num_of_frames = static_cast<int>(
-      ceil(num_of_frames / interleaved_resampler_.GetSampleRateRatio()));
+  // Enqueue the input.
+  int num_of_input_frames = audio_input->frames();
+  frames_to_resample_ += num_of_input_frames;
+  float* input_samples = reinterpret_cast<float*>(audio_input->data());
+  interleaved_resampler_.QueueBuffer(input_samples, num_of_input_frames);
 
-  int resampled_audio_size = out_num_of_frames * channels * sizeof(float);
+  // Check if we have enough frames to output.
+  scoped_refptr<DecodedAudio>& next_audio_to_output = audio_inputs_.front();
+  int num_of_output_frames =
+      static_cast<int>(
+          ceil((frames_resampled_ + next_audio_to_output->frames()) /
+               interleaved_resampler_.GetSampleRateRatio())) -
+      frames_outputted_;
+  int channels = next_audio_to_output->channels();
 
   scoped_refptr<DecodedAudio> resampled_audio = nullptr;
-
-  float* samples = reinterpret_cast<float*>(audio_data->data());
-  interleaved_resampler_.QueueBuffer(samples, num_of_frames);
-
-  if (interleaved_resampler_.HasEnoughData(out_num_of_frames)) {
+  if (interleaved_resampler_.HasEnoughData(num_of_output_frames)) {
+    int output_audio_size = num_of_output_frames * channels * sizeof(float);
     resampled_audio =
         new DecodedAudio(channels, kSbMediaAudioSampleTypeFloat32,
                          kSbMediaAudioFrameStorageTypeInterleaved,
-                         audio_data->timestamp(), resampled_audio_size);
+                         next_audio_to_output->timestamp(), output_audio_size);
     float* dst = reinterpret_cast<float*>(resampled_audio->data());
-    interleaved_resampler_.Resample(dst, out_num_of_frames);
-    frames_resampled_ += out_num_of_frames;
+    interleaved_resampler_.Resample(dst, num_of_output_frames);
+    frames_resampled_ += next_audio_to_output->frames();
+    frames_outputted_ += num_of_output_frames;
 
     if (!resampled_audio->IsFormat(destination_sample_type_,
                                    destination_storage_type_)) {
       resampled_audio = resampled_audio->SwitchFormatTo(
           destination_sample_type_, destination_storage_type_);
     }
+
+    audio_inputs_.pop_front();
   }
 
   return resampled_audio;
diff --git a/starboard/shared/starboard/player/filter/testing/BUILD.gn b/starboard/shared/starboard/player/filter/testing/BUILD.gn
index 80f2765..9d6f6b9 100644
--- a/starboard/shared/starboard/player/filter/testing/BUILD.gn
+++ b/starboard/shared/starboard/player/filter/testing/BUILD.gn
@@ -26,6 +26,7 @@
       "audio_decoder_test.cc",
       "audio_frame_discarder_test.cc",
       "audio_renderer_internal_test.cc",
+      "audio_resampler_test.cc",
       "file_cache_reader_test.cc",
       "media_time_provider_impl_test.cc",
       "player_components_test.cc",
@@ -38,6 +39,7 @@
 
     public_deps = [
       ":test_util",
+      "//starboard:starboard_with_main",
       "//starboard/shared/starboard/media:media_util",
       "//testing/gmock",
     ]
diff --git a/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc b/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc
index 7a2b8fe..5d6f6e0 100644
--- a/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc
+++ b/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc
@@ -43,6 +43,7 @@
 namespace testing {
 namespace {
 
+using std::deque;
 using std::string;
 using std::vector;
 using ::testing::Bool;
@@ -112,9 +113,11 @@
     ASSERT_LT(buffer_index, dmp_reader->number_of_audio_buffers());
 
     can_accept_more_input_ = false;
+    scoped_refptr<InputBuffer> input =
+        GetAudioInputBuffer(dmp_reader, buffer_index);
+    written_inputs_.push_back(input);
     audio_decoder_->Decode(
-        {GetAudioInputBuffer(dmp_reader, buffer_index)},
-        std::bind(&AdaptiveAudioDecoderTest::OnConsumed, this));
+        {input}, std::bind(&AdaptiveAudioDecoderTest::OnConsumed, this));
   }
 
   void WriteMultipleInputs(VideoDmpReader* dmp_reader,
@@ -191,6 +194,7 @@
 
   vector<std::unique_ptr<VideoDmpReader>> dmp_readers_;
   scoped_refptr<DecodedAudio> last_decoded_audio_;
+  deque<scoped_refptr<InputBuffer>> written_inputs_;
   int num_of_output_frames_ = 0;
   int output_sample_rate_;
   bool first_output_received_ = false;
@@ -244,11 +248,23 @@
       last_decoded_audio_ = decoded_audio;
       return;
     }
-    // TODO: fix resampler timestamp issue.
-    // if (last_decoded_audio_) {
-    //   ASSERT_LT(last_decoded_audio_->timestamp(),
-    //             decoded_audio->timestamp());
-    // }
+
+    // Stub decoder doesn't produce outputs with right timestamp.
+    if (!using_stub_decoder_) {
+      ASSERT_NEAR(decoded_audio->timestamp(),
+                  written_inputs_.front()->timestamp(), 5);
+      written_inputs_.pop_front();
+
+      // TODO: The timestamps of inputs are not monotonic increasing, so
+      // that we can't get monotonic increasing output timestamps here. We
+      // can enable the check below once we make the input timestamps monotonic
+      // increasing.
+      if (last_decoded_audio_) {
+        // ASSERT_LE(last_decoded_audio_->timestamp(),
+        // decoded_audio->timestamp());
+      }
+    }
+
     last_decoded_audio_ = decoded_audio;
     num_of_output_frames_ += last_decoded_audio_->frames();
   }
diff --git a/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc b/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
index 43fbff5..b9500df 100644
--- a/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
+++ b/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
@@ -224,10 +224,7 @@
     ASSERT_EQ(decoded_audio_sample_type_, local_decoded_audio->sample_type());
     ASSERT_EQ(decoded_audio_sample_rate_, decoded_sample_rate);
 
-    // TODO: Adaptive audio decoder may set output timestamp to 0, so we don't
-    //       verify audio timestamp if it's 0.  Consider enabling it after we
-    //       fix timestamp issues.
-    if (local_decoded_audio->timestamp() != 0 && !decoded_audios_.empty()) {
+    if (!decoded_audios_.empty()) {
       ASSERT_LT(decoded_audios_.back()->timestamp(),
                 local_decoded_audio->timestamp());
     }
diff --git a/starboard/shared/starboard/player/filter/testing/audio_resampler_test.cc b/starboard/shared/starboard/player/filter/testing/audio_resampler_test.cc
new file mode 100644
index 0000000..ad88fa6
--- /dev/null
+++ b/starboard/shared/starboard/player/filter/testing/audio_resampler_test.cc
@@ -0,0 +1,195 @@
+// Copyright 2023 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 <tuple>
+#include <vector>
+
+#include "starboard/common/string.h"
+#include "starboard/shared/starboard/player/filter/audio_resampler.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+namespace testing {
+namespace {
+
+using ::testing::Combine;
+using ::testing::ValuesIn;
+
+typedef ::testing::tuple<SbMediaAudioSampleType,
+                         SbMediaAudioFrameStorageType,
+                         int,
+                         SbMediaAudioSampleType,
+                         SbMediaAudioFrameStorageType,
+                         int,
+                         int>
+    AudioResamplerTestParam;
+
+SbMediaAudioSampleType kSampleTypesToTest[] = {
+    kSbMediaAudioSampleTypeInt16Deprecated,
+    kSbMediaAudioSampleTypeFloat32,
+};
+SbMediaAudioFrameStorageType kStorageTypesToTest[] = {
+    kSbMediaAudioFrameStorageTypeInterleaved,
+    kSbMediaAudioFrameStorageTypePlanar,
+};
+int kSampleRatesToTest[] = {22050, 44100, 48000};
+int kChannelsToTest[] = {1, 2, 6};
+
+const char* ConvertSampleTypeToString(SbMediaAudioSampleType sample_type) {
+  if (sample_type == kSbMediaAudioSampleTypeInt16Deprecated) {
+    return "int16";
+  } else if (sample_type == kSbMediaAudioSampleTypeFloat32) {
+    return "float32";
+  }
+  SB_NOTREACHED();
+  return "";
+}
+
+const char* ConvertStorageTypeToString(
+    SbMediaAudioFrameStorageType storage_type) {
+  if (storage_type == kSbMediaAudioFrameStorageTypeInterleaved) {
+    return "interleaved";
+  } else if (storage_type == kSbMediaAudioFrameStorageTypePlanar) {
+    return "planar";
+  }
+  SB_NOTREACHED();
+  return "";
+}
+
+class AudioResamplerTest
+    : public ::testing::TestWithParam<AudioResamplerTestParam> {
+ protected:
+  AudioResamplerTest() {
+    const AudioResamplerTestParam& param = GetParam();
+    source_sample_type_ = std::get<0>(param);
+    source_storage_type_ = std::get<1>(param);
+    source_sample_rate_ = std::get<2>(param);
+    destination_sample_type_ = std::get<3>(param);
+    destination_storage_type_ = std::get<4>(param);
+    destination_sample_rate_ = std::get<5>(param);
+    channels_ = std::get<6>(param);
+
+    GenerateAudioInputs();
+  }
+
+  void GenerateAudioInputs() {
+    const int kNumberOfInputs = 40;
+    const int kSamplesPerInput = 1024;
+
+    int total_frames = 0;
+    for (int i = 0; i < kNumberOfInputs; i++) {
+      int sample_size =
+          source_sample_type_ == kSbMediaAudioSampleTypeInt16Deprecated
+              ? sizeof(int16_t)
+              : sizeof(float);
+      int audio_size = kSamplesPerInput * channels_ * sample_size;
+      scoped_refptr<DecodedAudio> input = new DecodedAudio(
+          channels_, source_sample_type_, source_storage_type_,
+          kSbTimeSecond * total_frames / source_sample_rate_, audio_size);
+      total_frames += kSamplesPerInput;
+      inputs_.push_back(input);
+    }
+  }
+
+  SbMediaAudioSampleType source_sample_type_;
+  SbMediaAudioFrameStorageType source_storage_type_;
+  int source_sample_rate_;
+  SbMediaAudioSampleType destination_sample_type_;
+  SbMediaAudioFrameStorageType destination_storage_type_;
+  int destination_sample_rate_;
+  int channels_;
+
+  std::vector<scoped_refptr<DecodedAudio>> inputs_;
+};
+
+TEST_P(AudioResamplerTest, SunnyDay) {
+  scoped_ptr<AudioResampler> resampler = AudioResampler::Create(
+      source_sample_type_, source_storage_type_, source_sample_rate_,
+      destination_sample_type_, destination_storage_type_,
+      destination_sample_rate_, channels_);
+
+  int total_input_frames = 0;
+  std::vector<scoped_refptr<DecodedAudio>> outputs;
+  for (auto input : inputs_) {
+    scoped_refptr<DecodedAudio> output = resampler->Resample(input);
+    total_input_frames += input->frames();
+    if (output) {
+      outputs.push_back(output);
+    }
+  }
+  scoped_refptr<DecodedAudio> output = resampler->WriteEndOfStream();
+  if (output) {
+    outputs.push_back(output);
+  }
+
+  // Theoretically, if the input is too small, the last output could consist
+  // of multiple inputs. But as our audio unit is always larger than resampler
+  // block size. The amount of outputs should be always same as the inputs, and
+  // they should have same timestamp.
+  EXPECT_EQ(inputs_.size(), outputs.size());
+  int total_output_frames = 0;
+  for (int i = 0; i < outputs.size(); i++) {
+    EXPECT_EQ(inputs_[i]->timestamp(), outputs[i]->timestamp());
+    total_output_frames += outputs[i]->frames();
+    EXPECT_NEAR(
+        inputs_[i]->frames() * destination_sample_rate_ / source_sample_rate_,
+        outputs[i]->frames(), 5);
+  }
+  EXPECT_NEAR(
+      total_input_frames * destination_sample_rate_ / source_sample_rate_,
+      total_output_frames, 5);
+}
+
+std::string GetTestConfigName(
+    ::testing::TestParamInfo<AudioResamplerTestParam> info) {
+  const AudioResamplerTestParam& param = info.param;
+  SbMediaAudioSampleType source_sample_type = std::get<0>(param);
+  SbMediaAudioFrameStorageType source_storage_type = std::get<1>(param);
+  int source_sample_rate = std::get<2>(param);
+  SbMediaAudioSampleType destination_sample_type = std::get<3>(param);
+  SbMediaAudioFrameStorageType destination_storage_type = std::get<4>(param);
+  int destination_sample_rate = std::get<5>(param);
+  int channels = std::get<6>(param);
+  std::string name = FormatString(
+      "%s_%s_%d_to_%s_%s_%d_channels_%d",
+      ConvertSampleTypeToString(source_sample_type),
+      ConvertStorageTypeToString(source_storage_type), source_sample_rate,
+      ConvertSampleTypeToString(destination_sample_type),
+      ConvertStorageTypeToString(destination_storage_type),
+      destination_sample_rate, channels);
+  return name;
+}
+
+INSTANTIATE_TEST_CASE_P(AudioResamplerTests,
+                        AudioResamplerTest,
+                        Combine(ValuesIn(kSampleTypesToTest),
+                                ValuesIn(kStorageTypesToTest),
+                                ValuesIn(kSampleRatesToTest),
+                                ValuesIn(kSampleTypesToTest),
+                                ValuesIn(kStorageTypesToTest),
+                                ValuesIn(kSampleRatesToTest),
+                                ValuesIn(kChannelsToTest)),
+                        GetTestConfigName);
+
+}  // namespace
+}  // namespace testing
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/starboard/player/filter/tools/BUILD.gn b/starboard/shared/starboard/player/filter/tools/BUILD.gn
index b2d0a78..f1842ff 100644
--- a/starboard/shared/starboard/player/filter/tools/BUILD.gn
+++ b/starboard/shared/starboard/player/filter/tools/BUILD.gn
@@ -18,7 +18,7 @@
     sources = [ "audio_dmp_player.cc" ]
     configs += [ "//starboard/build/config:starboard_implementation" ]
     public_deps = [
-      "//starboard",
+      "//starboard:starboard_with_main",
       "//starboard/shared/starboard/media:media_util",
       "//starboard/shared/starboard/player:player_download_test_data",
       "//starboard/shared/starboard/player:video_dmp",
diff --git a/starboard/shared/uwp/media_is_video_supported.cc b/starboard/shared/uwp/media_is_video_supported.cc
index 5316a98..eab4c87 100644
--- a/starboard/shared/uwp/media_is_video_supported.cc
+++ b/starboard/shared/uwp/media_is_video_supported.cc
@@ -112,11 +112,7 @@
       case starboard::shared::uwp::kXboxOneX:
         // Horizontal video resolutions
         hw_decoder_capabilities_.AddSdrRule(kSbMediaVideoCodecVp9, 3840, 2160,
-                                            30);
-        hw_decoder_capabilities_.AddSdrRule(kSbMediaVideoCodecVp9, 2560, 1440,
                                             60);
-        // Until we can resolve b/170881040, we should cap Xbox One X to 2K for
-        // HDR 60 FPS.
         hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecVp9, 3840, 2160,
                                             30);
         hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecVp9, 2560, 1440,
@@ -124,7 +120,7 @@
         // Vertical video resolutions
         hw_decoder_capabilities_.AddSdrRule(kSbMediaVideoCodecVp9, 2160, 3840,
                                             30);
-        hw_decoder_capabilities_.AddSdrRule(kSbMediaVideoCodecVp9, 1440, 2560,
+        hw_decoder_capabilities_.AddSdrRule(kSbMediaVideoCodecVp9, 2160, 3840,
                                             60);
         hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecVp9, 2160, 3840,
                                             30);
diff --git a/starboard/shared/x11/application_x11.cc b/starboard/shared/x11/application_x11.cc
index d2b4f12..7efa103 100644
--- a/starboard/shared/x11/application_x11.cc
+++ b/starboard/shared/x11/application_x11.cc
@@ -919,7 +919,12 @@
     return pending_event;
   }
 
-  SB_DCHECK(dev_input_);
+  // In modular builds, |CreateWindow| is not always called before the event
+  // loop is running.
+  if (!dev_input_) {
+    return nullptr;
+  }
+
   shared::starboard::Application::Event* evdev_event =
       dev_input_->WaitForSystemEventWithTimeout(time);
 
diff --git a/starboard/tools/abstract_launcher.py b/starboard/tools/abstract_launcher.py
index ff0ad61..9928039 100644
--- a/starboard/tools/abstract_launcher.py
+++ b/starboard/tools/abstract_launcher.py
@@ -26,6 +26,8 @@
 ARG_SYSTOOLS = "systools"
 ARG_DRYRUN = "dryrun"
 
+IS_MODULAR_BUILD = os.getenv("MODULAR_BUILD", "0") == "1"
+
 
 def _GetLauncherForPlatform(platform_name):
   """Gets the module containing a platform's concrete launcher implementation.
diff --git a/starboard/xb1/args.gn b/starboard/xb1/args.gn
index 5101b33..2bf8065 100644
--- a/starboard/xb1/args.gn
+++ b/starboard/xb1/args.gn
@@ -16,4 +16,3 @@
 target_os = "winuwp"
 target_cpu = "x64"
 is_clang = false
-is_internal_build = false
diff --git a/starboard/xb1/platform_configuration/BUILD.gn b/starboard/xb1/platform_configuration/BUILD.gn
index 5c493ae..68b7862 100644
--- a/starboard/xb1/platform_configuration/BUILD.gn
+++ b/starboard/xb1/platform_configuration/BUILD.gn
@@ -57,9 +57,10 @@
       "vcruntime.lib",
       "ucrt.lib",
     ]
-    ldflags += [ "/DEBUG:FASTLINK" ]
   }
 
+  ldflags += [ "/DEBUG:FASTLINK" ]
+
   ldflags += [ "/NODEFAULTLIB" ]
   arflags += [ "/NODEFAULTLIB" ]
   libs += [
diff --git a/third_party/boringssl/BUILD.gn b/third_party/boringssl/BUILD.gn
index f90940c..51f71e4 100644
--- a/third_party/boringssl/BUILD.gn
+++ b/third_party/boringssl/BUILD.gn
@@ -176,26 +176,3 @@
     configs += [ "//starboard/build/config:speed" ]
   }
 }
-
-target(final_executable_type, "crypto_tool") {
-  sources = [
-    "src/tool/args.cc",
-    "src/tool/ciphers.cc",
-    "src/tool/const.cc",
-    "src/tool/digest.cc",
-    "src/tool/file.cc",
-    "src/tool/generate_ed25519.cc",
-    "src/tool/genrsa.cc",
-    "src/tool/pkcs12.cc",
-    "src/tool/rand.cc",
-    "src/tool/sign.cc",
-    "src/tool/speed.cc",
-    "src/tool/tool.cc",
-  ]
-  include_dirs = [ "src/include" ]
-  defines = [ "OPENSSL_NO_SOCK" ]
-  deps = [ ":crypto" ]
-  if (is_starboard) {
-    deps += [ "//starboard" ]
-  }
-}
diff --git a/third_party/inspector_protocol/crdtp/json_platform.cc b/third_party/inspector_protocol/crdtp/json_platform.cc
index facc7c8..968df8e 100644
--- a/third_party/inspector_protocol/crdtp/json_platform.cc
+++ b/third_party/inspector_protocol/crdtp/json_platform.cc
@@ -26,7 +26,6 @@
 }
 
 std::string DToStr(double value) {
-  //Ask Kaido/Yavor if this is needed.
 #if SB_IS(MODULAR)
 #error "The std::locale::classic() is not supported for Evergreen. Please use base::NumberToString()."
 #endif
diff --git a/third_party/llvm-project/libcxx/BUILD.gn b/third_party/llvm-project/libcxx/BUILD.gn
index 1d47c23..0c80cac 100644
--- a/third_party/llvm-project/libcxx/BUILD.gn
+++ b/third_party/llvm-project/libcxx/BUILD.gn
@@ -119,7 +119,4 @@
     "//third_party/llvm-project/libcxxabi:cxxabi",
     "//third_party/musl:c",
   ]
-  if (!build_with_separate_cobalt_toolchain) {
-    deps += [ "//third_party/llvm-project/libunwind:unwind_evergreen" ]
-  }
 }
diff --git a/third_party/llvm-project/libcxxabi/BUILD.gn b/third_party/llvm-project/libcxxabi/BUILD.gn
index 0ed1b42..9142fa4 100644
--- a/third_party/llvm-project/libcxxabi/BUILD.gn
+++ b/third_party/llvm-project/libcxxabi/BUILD.gn
@@ -90,7 +90,4 @@
   all_dependent_configs = [ ":cxxabi_dependents_config" ]
 
   deps = [ "//third_party/musl:c" ]
-  if (!build_with_separate_cobalt_toolchain) {
-    deps += [ "//third_party/llvm-project/libunwind:unwind_evergreen" ]
-  }
 }
diff --git a/third_party/llvm-project/libunwind/BUILD.gn b/third_party/llvm-project/libunwind/BUILD.gn
index 44d7882..cedc13b 100644
--- a/third_party/llvm-project/libunwind/BUILD.gn
+++ b/third_party/llvm-project/libunwind/BUILD.gn
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import("//starboard/build/config/os_definitions.gni")
+
 common_sources = [
   "src/AddressSpace.hpp",
   "src/CompactUnwinder.hpp",
@@ -55,8 +57,9 @@
 # dependencies from spilling over to gn check when run for non-evergreen
 # platforms (e.g., raspi-2_gn_devel). It can and should be removed once the gn
 # check errors have been resolved for evergreen.
-if (sb_is_evergreen) {
-  config("unwind_evergreen_config") {
+# TODO: b/295702296 Fix libunwind for modular  builds.
+if (sb_is_evergreen || (sb_is_modular && is_host_win)) {
+  config("unwind_config") {
     configs = [ ":common_unwind_dependents_config" ]
 
     cflags = [
@@ -90,10 +93,10 @@
     ]
   }
 
-  static_library("unwind_evergreen") {
+  static_library("unwind") {
     sources = common_sources
 
-    configs += [ ":unwind_evergreen_config" ]
+    configs += [ ":unwind_config" ]
     all_dependent_configs = [ ":common_unwind_dependents_config" ]
 
     deps = [
diff --git a/third_party/musl/BUILD.gn b/third_party/musl/BUILD.gn
index 740216a..8d00e30 100644
--- a/third_party/musl/BUILD.gn
+++ b/third_party/musl/BUILD.gn
@@ -520,9 +520,7 @@
   ]
 }
 
-target(gtest_target_type, "musl_unittests") {
-  build_loader = false
-  testonly = true
+static_library("musl_unittests") {
   sources = [ "test/type_size_test.cc" ]
   deps = [
     ":c",
diff --git a/third_party/musl/test/type_size_test.cc b/third_party/musl/test/type_size_test.cc
index f85a0ce..ae2e93f 100644
--- a/third_party/musl/test/type_size_test.cc
+++ b/third_party/musl/test/type_size_test.cc
@@ -190,8 +190,13 @@
 // TODO: Decide if we should, and how, verify that __WCHAR_TYPE__ is the
 //       expected size.
 #else   // !SB_IS(ARCH_X86)
+#if SB_IS(WCHAR_T_UTF16)
+SB_COMPILE_ASSERT(sizeof(wchar_t) == 2,
+                  SB_IS_WCHAR_T_UTF16_is_inconsistent_with_sizeof_wchar_t);
+#else
 SB_COMPILE_ASSERT(sizeof(wchar_t) == SB_SIZE_OF_INT,
                   SB_SIZE_OF_INT_is_inconsistent_with_sizeof_wchar_t);
+#endif // SB_IS(WCHAR_T_UTF16)
 #endif  // SB_IS(ARCH_X86)
 
 #endif  // SB_IS(MODULAR)
diff --git a/third_party/opus/starboard/config.h b/third_party/opus/starboard/config.h
index 7c67f29..4a9f5e1 100644
--- a/third_party/opus/starboard/config.h
+++ b/third_party/opus/starboard/config.h
@@ -35,6 +35,9 @@
 
 #define OPUS_BUILD            1
 
+/* Don't let EXPORT to be redefined by later config code. */
+#define OPUS_EXPORT
+
 #if defined(_M_IX86) || defined(_M_X64)
 /* Can always compile SSE intrinsics (no special compiler flags necessary) */
 #define OPUS_X86_MAY_HAVE_SSE