Import Cobalt 23.lts.5.1031871
diff --git a/cobalt/build/build.id b/cobalt/build/build.id
index 0a3476e..3d15dd7 100644
--- a/cobalt/build/build.id
+++ b/cobalt/build/build.id
@@ -1 +1 @@
-311259
\ No newline at end of file
+1031871
\ No newline at end of file
diff --git a/cobalt/build/get_build_id.py b/cobalt/build/get_build_id.py
index 14f4f0d..9c19e4c 100755
--- a/cobalt/build/get_build_id.py
+++ b/cobalt/build/get_build_id.py
@@ -15,20 +15,65 @@
 """Prints out the Cobalt Build ID."""
 
 import os
+import re
+import subprocess
+
 from cobalt.build.build_number import GetOrGenerateNewBuildNumber
 
+_FILE_DIR = os.path.dirname(__file__)
+COMMIT_COUNT_BUILD_NUMBER_OFFSET = 1000000
+
+# Matches numbers > 1000000. The pattern is basic so git log --grep is able to
+# interpret it.
+GIT_BUILD_NUMBER_PATTERN = r'[1-9]' + r'[0-9]' * 6 + r'[0-9]*'
+BUILD_NUMBER_TAG_PATTERN = r'^BUILD_NUMBER={}$'
+
+# git log --grep can't handle capture groups.
+BUILD_NUBER_PATTERN_WITH_CAPTURE = f'({GIT_BUILD_NUMBER_PATTERN})'
+
+
+def get_build_number_from_commits():
+  full_pattern = BUILD_NUMBER_TAG_PATTERN.format(GIT_BUILD_NUMBER_PATTERN)
+  output = subprocess.check_output(
+      ['git', 'log', '--grep', full_pattern, '-1', '--pretty=%b'],
+      cwd=_FILE_DIR).decode()
+
+  full_pattern_with_capture = re.compile(
+      BUILD_NUMBER_TAG_PATTERN.format(BUILD_NUBER_PATTERN_WITH_CAPTURE),
+      flags=re.MULTILINE)
+  match = full_pattern_with_capture.search(output)
+  return match.group(1) if match else None
+
+
+def get_build_number_from_server():
+  # Note $BUILD_ID_SERVER_URL will always be set in CI.
+  build_id_server_url = os.environ.get('BUILD_ID_SERVER_URL')
+  if not build_id_server_url:
+    return None
+
+  build_num = GetOrGenerateNewBuildNumber(version_server=build_id_server_url)
+  if build_num == 0:
+    raise ValueError('The build number received was zero.')
+  return build_num
+
+
+def get_build_number_from_commit_count():
+  output = subprocess.check_output(['git', 'rev-list', '--count', 'HEAD'],
+                                   cwd=_FILE_DIR)
+  build_number = int(output.strip().decode('utf-8'))
+  return build_number + COMMIT_COUNT_BUILD_NUMBER_OFFSET
+
 
 def main():
-  # Note $BUILD_ID_SERVER_URL will always be set in CI.
-  build_id_server_url = os.environ.get('BUILD_ID_SERVER_URL')
-  if build_id_server_url:
-    build_num = GetOrGenerateNewBuildNumber(version_server=build_id_server_url)
-    if build_num == 0:
-      raise ValueError('The build number received was zero.')
-    print(build_num)
-  else:
-    # No need to generate a build id for local builds.
-    print('0')
+  build_number = get_build_number_from_commits()
+
+  if not build_number:
+    build_number = get_build_number_from_server()
+
+  if not build_number:
+    build_number = get_build_number_from_commit_count()
+
+  print(build_number)
 
 
 if __name__ == '__main__':
diff --git a/cobalt/content/fonts/config/common/fonts.xml b/cobalt/content/fonts/config/common/fonts.xml
index abba022..2ddeaec 100644
--- a/cobalt/content/fonts/config/common/fonts.xml
+++ b/cobalt/content/fonts/config/common/fonts.xml
@@ -184,7 +184,7 @@
             <font weight="400" style="normal">NotoSansArmenian-Regular.woff2</font>
             <font weight="700" style="normal">NotoSansArmenian-Bold.woff2</font>
         </family>
-        <family lang="und-Geor,und-Geok" pages="0,5,16,45,254">
+        <family lang="und-Geor,und-Geok" pages="0,5,16,28,45,254">
             <font weight="400" style="normal">NotoSansGeorgian-Regular.woff2</font>
             <font weight="700" style="normal">NotoSansGeorgian-Bold.woff2</font>
         </family>
diff --git a/cobalt/content/fonts/font_files/NotoSansGeorgian-Bold.woff2 b/cobalt/content/fonts/font_files/NotoSansGeorgian-Bold.woff2
index a7f9336..03a41c8 100644
--- a/cobalt/content/fonts/font_files/NotoSansGeorgian-Bold.woff2
+++ b/cobalt/content/fonts/font_files/NotoSansGeorgian-Bold.woff2
Binary files differ
diff --git a/cobalt/content/fonts/font_files/NotoSansGeorgian-Regular.woff2 b/cobalt/content/fonts/font_files/NotoSansGeorgian-Regular.woff2
index fb2fe49..68e26b4 100644
--- a/cobalt/content/fonts/font_files/NotoSansGeorgian-Regular.woff2
+++ b/cobalt/content/fonts/font_files/NotoSansGeorgian-Regular.woff2
Binary files differ
diff --git a/cobalt/dom/serialized_algorithm_runner.h b/cobalt/dom/serialized_algorithm_runner.h
index db625ad..358f7ef 100644
--- a/cobalt/dom/serialized_algorithm_runner.h
+++ b/cobalt/dom/serialized_algorithm_runner.h
@@ -15,6 +15,7 @@
 #ifndef COBALT_DOM_SERIALIZED_ALGORITHM_RUNNER_H_
 #define COBALT_DOM_SERIALIZED_ALGORITHM_RUNNER_H_
 
+#include <algorithm>
 #include <memory>
 #include <utility>
 
@@ -26,6 +27,7 @@
 #include "base/single_thread_task_runner.h"
 #include "base/trace_event/trace_event.h"
 #include "starboard/common/mutex.h"
+#include "starboard/time.h"
 
 namespace cobalt {
 namespace dom {
@@ -84,11 +86,31 @@
                              const starboard::Mutex& mutex)
           : synchronization_required_(synchronization_required), mutex_(mutex) {
         if (synchronization_required_) {
-          mutex_.Acquire();
+          // Crash if we are trying to re-acquire again on the same thread.
+          CHECK_NE(acquired_thread_id_, SbThreadGetId());
+
+          SbTime start = SbTimeGetMonotonicNow();
+          SbTime wait_interval = kSbTimeMillisecond;
+          constexpr SbTime kMaxWaitInterval = kSbTimeMillisecond * 16;
+
+          for (;;) {
+            if (mutex_.AcquireTry()) {
+              break;
+            }
+            SbThreadSleep(wait_interval);
+            // Double the wait interval upon every failure, but cap it at
+            // kMaxWaitInterval.
+            wait_interval = std::min(wait_interval * 2, kMaxWaitInterval);
+            // Crash if we've been waiting for too long.
+            CHECK_LT(SbTimeGetMonotonicNow() - start, kSbTimeSecond);
+          }
+          acquired_thread_id_ = SbThreadGetId();
         }
       }
       ~ScopedLockWhenRequired() {
         if (synchronization_required_) {
+          CHECK_EQ(acquired_thread_id_, SbThreadGetId());
+          acquired_thread_id_ = kSbThreadInvalidId;
           mutex_.Release();
         }
       }
@@ -96,6 +118,7 @@
      private:
       const bool synchronization_required_;
       const starboard::Mutex& mutex_;
+      SbThreadId acquired_thread_id_ = kSbThreadInvalidId;
     };
 
     Handle(bool synchronization_required,
diff --git a/cobalt/h5vcc/h5vcc_runtime.cc b/cobalt/h5vcc/h5vcc_runtime.cc
index faf56ce..15f682b 100644
--- a/cobalt/h5vcc/h5vcc_runtime.cc
+++ b/cobalt/h5vcc/h5vcc_runtime.cc
@@ -91,7 +91,7 @@
     message_loop_->task_runner()->PostTask(
         FROM_HERE,
         base::Bind(&H5vccRuntime::OnDeepLinkEvent, base::Unretained(this),
-                   base::Passed(&deep_link_event)));
+                   base::Passed(std::move(deep_link_event))));
     return;
   }
   OnDeepLinkEvent(std::move(deep_link_event));
diff --git a/cobalt/layout/topmost_event_target.cc b/cobalt/layout/topmost_event_target.cc
index d7ce92e..3981de4 100644
--- a/cobalt/layout/topmost_event_target.cc
+++ b/cobalt/layout/topmost_event_target.cc
@@ -57,7 +57,8 @@
   document->DoSynchronousLayout();
 
   html_element_ = document->html();
-  ConsiderElement(html_element_, coordinate);
+  bool consider_only_fixed_elements = false;
+  ConsiderElement(html_element_, coordinate, consider_only_fixed_elements);
   box_ = NULL;
   render_sequence_.clear();
   scoped_refptr<dom::HTMLElement> topmost_element;
@@ -178,8 +179,8 @@
   }
 };
 
-bool ShouldConsiderElementAndChildren(dom::Element* element,
-                                      math::Vector2dF* coordinate) {
+bool CanTargetElementAndChildren(dom::Element* element,
+                                 math::Vector2dF* coordinate) {
   LayoutBoxes* layout_boxes = GetLayoutBoxesIfNotEmpty(element);
   const Boxes boxes = layout_boxes->boxes();
   const Box* box = boxes.front();
@@ -434,17 +435,43 @@
   return complete_matrix;
 }
 
+bool LayoutBoxesAreFixed(LayoutBoxes* layout_boxes) {
+  const Boxes boxes = layout_boxes->boxes();
+  const Box* box = boxes.front();
+  if (!box->computed_style()) {
+    return false;
+  }
+
+  return box->computed_style()->position() == cssom::KeywordValue::GetFixed();
+}
+
+bool ShouldConsiderElementAndChildren(dom::Element* element,
+                                      math::Vector2dF* coordinate,
+                                      bool consider_only_fixed_elements) {
+  LayoutBoxes* layout_boxes = GetLayoutBoxesIfNotEmpty(element);
+  if (!layout_boxes) {
+    return false;
+  }
+
+  bool is_fixed_element = LayoutBoxesAreFixed(layout_boxes);
+  if (consider_only_fixed_elements && !is_fixed_element) {
+    return false;
+  }
+  return CanTargetElementAndChildren(element, coordinate);
+}
+
 }  // namespace
 
 void TopmostEventTarget::ConsiderElement(dom::Element* element,
-                                         const math::Vector2dF& coordinate) {
+                                         const math::Vector2dF& coordinate,
+                                         bool consider_only_fixed_elements) {
   if (!element) return;
   math::Vector2dF element_coordinate(coordinate);
   LayoutBoxes* layout_boxes = GetLayoutBoxesIfNotEmpty(element);
-  if (layout_boxes) {
-    if (!ShouldConsiderElementAndChildren(element, &element_coordinate)) {
-      return;
-    }
+  bool consider_element_and_children = ShouldConsiderElementAndChildren(
+      element, &element_coordinate, consider_only_fixed_elements);
+
+  if (consider_element_and_children) {
     scoped_refptr<dom::HTMLElement> html_element = element->AsHTMLElement();
     if (html_element && html_element->CanBeDesignatedByPointerIfDisplayed()) {
       ConsiderBoxes(html_element, layout_boxes, element_coordinate);
@@ -453,7 +480,8 @@
 
   for (dom::Element* child_element = element->first_element_child();
        child_element; child_element = child_element->next_element_sibling()) {
-    ConsiderElement(child_element, element_coordinate);
+    ConsiderElement(child_element, element_coordinate,
+                    !consider_element_and_children);
   }
 }
 
diff --git a/cobalt/layout/topmost_event_target.h b/cobalt/layout/topmost_event_target.h
index 1d1dd9b..2a8148a 100644
--- a/cobalt/layout/topmost_event_target.h
+++ b/cobalt/layout/topmost_event_target.h
@@ -51,8 +51,8 @@
   void CancelScrollsInParentNavItems(
       scoped_refptr<dom::HTMLElement> target_element);
 
-  void ConsiderElement(dom::Element* element,
-                       const math::Vector2dF& coordinate);
+  void ConsiderElement(dom::Element* element, const math::Vector2dF& coordinate,
+                       bool consider_only_fixed_elements);
   void ConsiderBoxes(const scoped_refptr<dom::HTMLElement>& html_element,
                      LayoutBoxes* layout_boxes,
                      const math::Vector2dF& coordinate);
diff --git a/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt b/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt
index 686eabe..95fe188 100644
--- a/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt
+++ b/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt
@@ -1,34 +1,44 @@
 # Service Worker API tests
 
+service-worker/activation-after-registration.https.html, PASS
+service-worker/activate-event-after-install-state-change.https.html, PASS
 service-worker/clients-matchall-on-evaluation.https.html, PASS
 service-worker/fetch-event-add-async.https.html, PASS
+service-worker/import-scripts-cross-origin.https.html, PASS
+service-worker/import-scripts-resource-map.https.html, PASS
+service-worker/import-scripts-updated-flag.https.html, PASS
+service-worker/multiple-update.https.html, PASS
 service-worker/register-default-scope.https.html, PASS
 service-worker/registration-basic.https.html, PASS
 service-worker/registration-security-error.https.html, PASS
+service-worker/registration-service-worker-attributes.https.html, PASS
 service-worker/registration-script-url.https.html, PASS
 service-worker/rejections.https.html, PASS
 service-worker/serviceworkerobject-scripturl.https.html, PASS
 service-worker/service-worker-csp-default.https.html, PASS
 service-worker/service-worker-csp-connect.https.html, PASS
-service-worker/service-worker-header.https.html, PASS
 service-worker/service-worker-csp-script.https.html, PASS
+service-worker/service-worker-header.https.html, PASS
 service-worker/Service-Worker-Allowed-header.https.html, PASS
 service-worker/skip-waiting-without-client.https.html, PASS
+service-worker/state.https.html, PASS
+service-worker/synced-state.https.html, PASS
 service-worker/uncontrolled-page.https.html, PASS
 service-worker/unregister.https.html, PASS
-service-worker/update-missing-import-scripts.https.html, PASS
-service-worker/update-result.https.html, PASS
+service-worker/update-no-cache-request-headers.https.html, PASS
 
-# Tests pass with memory leakage issue.
-service-worker/update-no-cache-request-headers.https.html, DISABLE
+# b/274011216 flaky test
+service-worker/update-result.https.html, DISABLE
 
+# TODO(b/275914032): Flaky test.
+service-worker/update-missing-import-scripts.https.html, DISABLE
+
+# b/275643772 MIME type check is flaky
+service-worker/import-scripts-mime-types.https.html, DISABLE
 
 # b/234788479 Implement waiting for update worker state tasks in Install algorithm.
-service-worker/activation-after-registration.https.html, DISABLE
-service-worker/activate-event-after-install-state-change.https.html, DISABLE
-service-worker/multiple-update.https.html, DISABLE
+service-worker/import-scripts-redirect.https.html, DISABLE
 service-worker/register-wait-forever-in-install-worker.https.html, DISABLE
-service-worker/state.https.html, DISABLE
 
 # "Module" type of dedicated worker is supported in Cobalt
 service-worker/dedicated-worker-service-worker-interception.https.html, DISABLE
@@ -53,11 +63,6 @@
 service-worker/fetch-event-is-reload-navigation-manual.https.html, DISABLE
 
 # b/265841607 Unhandled rejection with value: object "TypeError"
-service-worker/import-scripts-cross-origin.https.html, DISABLE
-service-worker/import-scripts-mime-types.https.html, DISABLE
-service-worker/import-scripts-redirect.https.html, DISABLE
-service-worker/import-scripts-resource-map.https.html, DISABLE
-service-worker/import-scripts-updated-flag.https.html, DISABLE
 service-worker/same-site-cookies.https.html, DISABLE
 
 # b/265844662
@@ -87,12 +92,6 @@
 service-worker/unregister-immediately-before-installed.https.html, DISABLE
 service-worker/update-not-allowed.https.html, DISABLE
 
-# b/265981629
-service-worker/registration-service-worker-attributes.https.html, DISABLE
-
-# b/265983449
-service-worker/synced-state.https.html, DISABLE
-
 # websocket is not supported in Cobalt
 service-worker/websocket-in-service-worker.https.html, DISABLE
 
diff --git a/cobalt/layout_tests/web_platform_tests.cc b/cobalt/layout_tests/web_platform_tests.cc
index ac6e3fd..acca2ad 100644
--- a/cobalt/layout_tests/web_platform_tests.cc
+++ b/cobalt/layout_tests/web_platform_tests.cc
@@ -213,33 +213,36 @@
 
   web::CspDelegateFactory::GetInstance()->OverrideCreator(
       web::kCspEnforcementEnable, CspDelegatePermissive::Create);
+
+  std::unique_ptr<browser::UserAgentPlatformInfo> platform_info(
+      new browser::UserAgentPlatformInfo());
+  std::unique_ptr<browser::ServiceWorkerRegistry> service_worker_registry(
+      new browser::ServiceWorkerRegistry(&web_settings, &network_module,
+                                         platform_info.get(), url));
+
+  browser::WebModule::Options web_module_options;
   // Use test runner mode to allow the content itself to dictate when it is
   // ready for layout should be performed.  See cobalt/dom/test_runner.h.
-  browser::WebModule::Options web_module_options;
   web_module_options.layout_trigger = layout::LayoutManager::kTestRunnerMode;
+
   // We assume that we won't suspend/resume while running the tests, and so
   // we take advantage of the convenience of inline script tags.
   web_module_options.enable_inline_script_warnings = false;
 
   web_module_options.web_options.web_settings = &web_settings;
   web_module_options.web_options.network_module = &network_module;
+  web_module_options.web_options.service_worker_jobs =
+      service_worker_registry->service_worker_jobs();
+  web_module_options.web_options.platform_info = platform_info.get();
 
   // Prepare a slot for our results to be placed when ready.
   base::Optional<browser::WebModule::LayoutResults> results;
   base::RunLoop run_loop;
 
-  // Create the WebModule and wait for a layout to occur.
-  browser::WebModule web_module("RunWebPlatformTest");
-
-  // Create Service Worker Registry
-  browser::ServiceWorkerRegistry* service_worker_registry =
-      new browser::ServiceWorkerRegistry(&web_settings, &network_module,
-                                         new browser::UserAgentPlatformInfo(),
-                                         url);
-  web_module_options.web_options.service_worker_jobs =
-      service_worker_registry->service_worker_jobs();
-
-  web_module.Run(
+  // Run the WebModule and wait for a layout to occur.
+  std::unique_ptr<browser::WebModule> web_module(
+      new browser::WebModule("RunWebPlatformTest"));
+  web_module->Run(
       url, base::kApplicationStateStarted, nullptr /* scroll_engine */,
       base::Bind(&WebModuleOnRenderTreeProducedCallback, &results),
       base::Bind(&WebModuleErrorCallback, &run_loop,
@@ -249,12 +252,16 @@
       can_play_type_handler.get(), media_module.get(), kDefaultViewportSize,
       &resource_provider, 60.0f, web_module_options);
   run_loop.Run();
+
   const std::string extract_results =
       "document.getElementById(\"__testharness__results__\").textContent;";
   std::string output;
-  web_module.ExecuteJavascript(extract_results,
-                               base::SourceLocation(__FILE__, __LINE__, 1),
-                               &output, got_results);
+  web_module->ExecuteJavascript(extract_results,
+                                base::SourceLocation(__FILE__, __LINE__, 1),
+                                &output, got_results);
+  // Ensure that the WebModule stops before stopping the ServiceWorkerRegistry.
+  web_module.reset();
+  service_worker_registry.reset();
   return output;
 }
 
diff --git a/cobalt/loader/fetcher_cache.cc b/cobalt/loader/fetcher_cache.cc
index 8d6b707..1e6adb5 100644
--- a/cobalt/loader/fetcher_cache.cc
+++ b/cobalt/loader/fetcher_cache.cc
@@ -33,7 +33,7 @@
       const std::string& url,
       const scoped_refptr<net::HttpResponseHeaders>& headers,
       const Origin& last_url_origin, bool did_fail_from_transient_error,
-      std::string* data)>
+      std::string data)>
       SuccessCallback;
 
   CachedFetcherHandler(const std::string& url, Fetcher::Handler* handler,
@@ -52,6 +52,7 @@
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(!wrapping_fetcher_);
     DCHECK(wrapping_fetcher);
+
     wrapping_fetcher_ = wrapping_fetcher;
   }
 
@@ -63,6 +64,7 @@
     // TODO: Respect HttpResponseHeaders::GetMaxAgeValue().
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(wrapping_fetcher_);
+
     auto response = handler_->OnResponseStarted(wrapping_fetcher_, headers);
     if (response == kLoadResponseContinue && headers) {
       headers_ = headers;
@@ -77,6 +79,7 @@
   void OnReceived(Fetcher*, const char* data, size_t size) override {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(wrapping_fetcher_);
+
     data_.insert(data_.end(), data, data + size);
     handler_->OnReceived(wrapping_fetcher_, data, size);
   }
@@ -84,6 +87,7 @@
   void OnReceivedPassed(Fetcher*, std::unique_ptr<std::string> data) override {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(wrapping_fetcher_);
+
     data_.insert(data_.end(), data->begin(), data->end());
     handler_->OnReceivedPassed(wrapping_fetcher_, std::move(data));
   }
@@ -91,15 +95,17 @@
   void OnDone(Fetcher*) override {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(wrapping_fetcher_);
+
     handler_->OnDone(wrapping_fetcher_);
     on_success_callback_.Run(
         url_, headers_, wrapping_fetcher_->last_url_origin(),
-        wrapping_fetcher_->did_fail_from_transient_error(), &data_);
+        wrapping_fetcher_->did_fail_from_transient_error(), std::move(data_));
   }
 
   void OnError(Fetcher*, const std::string& error) override {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(wrapping_fetcher_);
+
     handler_->OnError(wrapping_fetcher_, error);
   }
 
@@ -184,13 +190,12 @@
  public:
   CacheEntry(const scoped_refptr<net::HttpResponseHeaders>& headers,
              const Origin& last_url_origin, bool did_fail_from_transient_error,
-             std::string* data)
+             std::string data)
       : headers_(headers),
         last_url_origin_(last_url_origin),
-        did_fail_from_transient_error_(did_fail_from_transient_error) {
-    DCHECK(data);
-    data_.swap(*data);
-  }
+        did_fail_from_transient_error_(did_fail_from_transient_error),
+        data_(std::move(data)),
+        capacity_(data_.capacity()) {}
 
   const scoped_refptr<net::HttpResponseHeaders>& headers() const {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -213,16 +218,24 @@
   }
   size_t capacity() const {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-    return data_.capacity();
+    CHECK_EQ(capacity_, data_.capacity());
+    return capacity_;
   }
 
  private:
+  CacheEntry(const CacheEntry&) = delete;
+  CacheEntry& operator=(const CacheEntry&) = delete;
+
   THREAD_CHECKER(thread_checker_);
 
   scoped_refptr<net::HttpResponseHeaders> headers_;
-  Origin last_url_origin_;
-  bool did_fail_from_transient_error_;
-  std::string data_;
+  const Origin last_url_origin_;
+  const bool did_fail_from_transient_error_;
+  const std::string data_;
+
+  // TODO(b/270993319): For debugging cache integrity issues in production only,
+  //                    remove after identifying the root cause.
+  const size_t capacity_;
 };
 
 FetcherCache::FetcherCache(const char* name, size_t capacity)
@@ -236,12 +249,15 @@
 
 FetcherCache::~FetcherCache() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  CHECK_EQ(thread_id_, SbThreadGetId());
+  CHECK(destroy_soon_called_);
 
   while (!cache_entries_.empty()) {
     delete cache_entries_.begin()->second;
-    cache_entries_.erase(cache_entries_.begin());
+    cache_entries_.pop_front();
   }
 
+  total_size_ = 0;
   memory_size_in_bytes_ = 0;
   count_resources_cached_ = 0;
 }
@@ -251,12 +267,13 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(!real_fetcher_creator.is_null());
 
-  return base::Bind(&FetcherCache::CreateCachedFetcher, base::Unretained(this),
-                    url, real_fetcher_creator);
+  return base::Bind(&FetcherCache::CreateCachedFetcher, this, url,
+                    real_fetcher_creator);
 }
 
 void FetcherCache::NotifyResourceRequested(const std::string& url) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  CHECK_EQ(thread_id_, SbThreadGetId());
 
   auto iter = cache_entries_.find(url);
   if (iter != cache_entries_.end()) {
@@ -266,6 +283,14 @@
   }
 }
 
+void FetcherCache::DestroySoon() {
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+  CHECK(HasOneRef());
+#endif  //! defined(COBALT_BUILD_TYPE_GOLD)
+
+  destroy_soon_called_ = true;
+}
+
 std::unique_ptr<Fetcher> FetcherCache::CreateCachedFetcher(
     const GURL& url, const Loader::FetcherCreator& real_fetcher_creator,
     Fetcher::Handler* handler) {
@@ -273,6 +298,11 @@
   DCHECK(!real_fetcher_creator.is_null());
   DCHECK(handler);
 
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+  CHECK(!destroy_soon_called_);
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
+  CHECK_EQ(thread_id_, SbThreadGetId());
+
   auto iterator = cache_entries_.find(url.spec());
   if (iterator != cache_entries_.end()) {
     auto entry = iterator->second;
@@ -285,8 +315,7 @@
   }
 
   std::unique_ptr<CachedFetcherHandler> cached_handler(new CachedFetcherHandler(
-      url.spec(), handler,
-      base::Bind(&FetcherCache::OnFetchSuccess, base::Unretained(this))));
+      url.spec(), handler, base::Bind(&FetcherCache::OnFetchSuccess, this)));
   return std::unique_ptr<Fetcher>(
       new OngoingFetcher(std::move(cached_handler), real_fetcher_creator));
 }
@@ -295,32 +324,44 @@
     const std::string& url,
     const scoped_refptr<net::HttpResponseHeaders>& headers,
     const Origin& last_url_origin, bool did_fail_from_transient_error,
-    std::string* data) {
+    std::string data) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  DCHECK(data);
 
-  if (data->size() <= capacity_) {
-    auto entry = new CacheEntry(headers, last_url_origin,
-                                did_fail_from_transient_error, data);
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+  CHECK(!destroy_soon_called_);
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
+  CHECK_EQ(thread_id_, SbThreadGetId());
 
-    bool inserted = cache_entries_.insert(std::make_pair(url, entry)).second;
-    if (!inserted) {
-      // The resource is already cached.
-      delete entry;
-      return;
-    }
-
-    total_size_ += entry->capacity();
-    while (total_size_ > capacity_) {
-      DCHECK(!cache_entries_.empty());
-      total_size_ -= cache_entries_.begin()->second->capacity();
-      delete cache_entries_.begin()->second;
-      cache_entries_.erase(cache_entries_.begin());
-      --count_resources_cached_;
-    }
-    ++count_resources_cached_;
-    memory_size_in_bytes_ = total_size_;
+  if (data.capacity() > capacity_) {
+    return;
   }
+
+  auto entry = new CacheEntry(headers, last_url_origin,
+                              did_fail_from_transient_error, std::move(data));
+
+  bool inserted = cache_entries_.insert(std::make_pair(url, entry)).second;
+  if (!inserted) {
+    // The resource is already cached.
+    delete entry;
+    return;
+  }
+
+  total_size_ += entry->capacity();
+  ++count_resources_cached_;
+
+  while (total_size_ > capacity_) {
+    // TODO(b/270993319): For debugging cache integrity issues in production
+    //                    only, remove after identifying the root cause.
+    CHECK(!cache_entries_.empty());
+    CHECK_GE(total_size_, cache_entries_.begin()->second->capacity());
+
+    total_size_ -= cache_entries_.begin()->second->capacity();
+    delete cache_entries_.begin()->second;
+    cache_entries_.pop_front();
+    --count_resources_cached_;
+  }
+
+  memory_size_in_bytes_ = total_size_;
 }
 
 }  // namespace loader
diff --git a/cobalt/loader/fetcher_cache.h b/cobalt/loader/fetcher_cache.h
index 32a4466..8f6e2b0 100644
--- a/cobalt/loader/fetcher_cache.h
+++ b/cobalt/loader/fetcher_cache.h
@@ -15,6 +15,7 @@
 #ifndef COBALT_LOADER_FETCHER_CACHE_H_
 #define COBALT_LOADER_FETCHER_CACHE_H_
 
+#include <atomic>
 #include <memory>
 #include <string>
 
@@ -25,6 +26,7 @@
 #include "cobalt/loader/loader.h"
 #include "net/base/linked_hash_map.h"
 #include "net/http/http_response_headers.h"
+#include "starboard/thread.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -32,7 +34,7 @@
 namespace loader {
 
 // Manages a cache for data fetched by Fetchers.
-class FetcherCache {
+class FetcherCache : public base::RefCountedThreadSafe<FetcherCache> {
  public:
   FetcherCache(const char* name, size_t capacity);
   ~FetcherCache();
@@ -41,6 +43,13 @@
       const GURL& url, const Loader::FetcherCreator& real_fetcher_creator);
   void NotifyResourceRequested(const std::string& url);
 
+  // To signal the imminent destruction of this object.  If everything is
+  // working as expected, there shouldn't be any other reference of this object,
+  // and all usages of this object should be completed.
+  // TODO(b/270993319): For debugging cache integrity issues in production only,
+  //                    remove after identifying the root cause.
+  void DestroySoon();
+
  private:
   class CacheEntry;
 
@@ -50,10 +59,15 @@
   void OnFetchSuccess(const std::string& url,
                       const scoped_refptr<net::HttpResponseHeaders>& headers,
                       const Origin& last_url_origin,
-                      bool did_fail_from_transient_error, std::string* data);
+                      bool did_fail_from_transient_error, std::string data);
 
   THREAD_CHECKER(thread_checker_);
 
+  // TODO(b/270993319): For debugging cache integrity issues in production only,
+  //                    remove after identifying the root cause.
+  const SbThreadId thread_id_ = SbThreadGetId();
+  std::atomic_bool destroy_soon_called_{false};
+
   const size_t capacity_;
   size_t total_size_ = 0;
 
diff --git a/cobalt/loader/loader_factory.cc b/cobalt/loader/loader_factory.cc
index 0cb575e..fcbe2d2 100644
--- a/cobalt/loader/loader_factory.cc
+++ b/cobalt/loader/loader_factory.cc
@@ -31,7 +31,13 @@
       debugger_hooks_(debugger_hooks),
       resource_provider_(resource_provider) {
   if (encoded_image_cache_capacity > 0) {
-    fetcher_cache_.reset(new FetcherCache(name, encoded_image_cache_capacity));
+    fetcher_cache_ = new FetcherCache(name, encoded_image_cache_capacity);
+  }
+}
+
+LoaderFactory::~LoaderFactory() {
+  if (fetcher_cache_) {
+    fetcher_cache_->DestroySoon();
   }
 }
 
diff --git a/cobalt/loader/loader_factory.h b/cobalt/loader/loader_factory.h
index e3f80ff..7602602 100644
--- a/cobalt/loader/loader_factory.h
+++ b/cobalt/loader/loader_factory.h
@@ -47,6 +47,7 @@
                 const base::DebuggerHooks& debugger_hooks,
                 size_t encoded_image_cache_capacity,
                 base::ThreadPriority loader_thread_priority);
+  ~LoaderFactory();
 
   // Creates a loader that fetches and decodes an image.
   std::unique_ptr<Loader> CreateImageLoader(
@@ -108,7 +109,7 @@
   // Used to cache the fetched raw data.  Note that currently the cache is only
   // used to cache Image data.  We may introduce more caches once we want to
   // cache fetched data for other resource types.
-  std::unique_ptr<FetcherCache> fetcher_cache_;
+  scoped_refptr<FetcherCache> fetcher_cache_;
 
   // Used with CLOG to report errors with the image source.
   const base::DebuggerHooks& debugger_hooks_;
diff --git a/cobalt/version.h b/cobalt/version.h
index a191328..3182bc9 100644
--- a/cobalt/version.h
+++ b/cobalt/version.h
@@ -35,6 +35,6 @@
 //                  release is cut.
 //.
 
-#define COBALT_VERSION "23.lts.4"
+#define COBALT_VERSION "23.lts.5"
 
 #endif  // COBALT_VERSION_H_
diff --git a/cobalt/web/csp_delegate.cc b/cobalt/web/csp_delegate.cc
index 3fe54b5..92d0520 100644
--- a/cobalt/web/csp_delegate.cc
+++ b/cobalt/web/csp_delegate.cc
@@ -80,8 +80,8 @@
     if (csp_header_policy_ == csp::kCSPRequired || should_allow) {
       return should_allow;
     } else {
-      DLOG(WARNING) << "Page must include Content-Security-Policy header, it "
-                       "will fail to load in production builds of Cobalt!";
+      LOG(WARNING) << "Page must include Content-Security-Policy header, it "
+                      "will fail to load in production builds of Cobalt!";
     }
   }
 
@@ -185,7 +185,7 @@
   } else {
     // Didn't find Content-Security-Policy header.
     if (!headers.content_security_policy_report_only().empty()) {
-      DLOG(INFO)
+      LOG(INFO)
           << "Content-Security-Policy-Report-Only headers were "
              "received, but Content-Security-Policy headers are required.";
     }
diff --git a/cobalt/webdriver/web_driver_module.cc b/cobalt/webdriver/web_driver_module.cc
index 3a4d360..64eaa39 100644
--- a/cobalt/webdriver/web_driver_module.cc
+++ b/cobalt/webdriver/web_driver_module.cc
@@ -294,11 +294,18 @@
       base::StringPrintf("/session/%s/execute", kSessionIdVariable),
       current_window_command_factory->GetCommandHandler(
           base::Bind(&WindowDriver::Execute)));
+  // https://www.w3.org/TR/2015/WD-webdriver-20150808/#execute-async-script
   webdriver_dispatcher_->RegisterCommand(
       WebDriverServer::kPost,
       base::StringPrintf("/session/%s/execute_async", kSessionIdVariable),
       current_window_command_factory->GetCommandHandler(
           base::Bind(&WindowDriver::ExecuteAsync)));
+  // https://www.w3.org/TR/2015/WD-webdriver-20150827/#execute-async-script
+  webdriver_dispatcher_->RegisterCommand(
+      WebDriverServer::kPost,
+      base::StringPrintf("/session/%s/execute/async", kSessionIdVariable),
+      current_window_command_factory->GetCommandHandler(
+          base::Bind(&WindowDriver::ExecuteAsync)));
   webdriver_dispatcher_->RegisterCommand(
       WebDriverServer::kPost,
       base::StringPrintf("/session/%s/element", kSessionIdVariable),
diff --git a/cobalt/worker/service_worker_consts.cc b/cobalt/worker/service_worker_consts.cc
index a168caf..4254b85 100644
--- a/cobalt/worker/service_worker_consts.cc
+++ b/cobalt/worker/service_worker_consts.cc
@@ -45,5 +45,23 @@
 
 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
index adb4d31..5bb8dd1 100644
--- a/cobalt/worker/service_worker_consts.h
+++ b/cobalt/worker/service_worker_consts.h
@@ -30,6 +30,10 @@
 
   // 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
diff --git a/cobalt/worker/service_worker_global_scope.cc b/cobalt/worker/service_worker_global_scope.cc
index e71735f..1598b81 100644
--- a/cobalt/worker/service_worker_global_scope.cc
+++ b/cobalt/worker/service_worker_global_scope.cc
@@ -122,6 +122,7 @@
             //     last update check time to the current time.
             // 11. If response’s unsafe response is a bad import script
             //     response, then return a network error.
+            // This is checked in WorkerGlobalScope.
 
             // 12. Set map[url] to response.
             service_worker->SetScriptResource(url, content);
diff --git a/cobalt/worker/service_worker_jobs.cc b/cobalt/worker/service_worker_jobs.cc
index 3052d63..bf19a94 100644
--- a/cobalt/worker/service_worker_jobs.cc
+++ b/cobalt/worker/service_worker_jobs.cc
@@ -733,28 +733,6 @@
       /*skip_fetch_intercept=*/true);
 }
 
-namespace {
-// Array of JavaScript mime types, according to the MIME Sniffinc spec:
-//   https://mimesniff.spec.whatwg.org/#javascript-mime-type
-static const char* const kJavaScriptMimeTypes[] = {"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
-
 bool ServiceWorkerJobs::UpdateOnResponseStarted(
     scoped_refptr<UpdateJobState> state, loader::Fetcher* fetcher,
     const scoped_refptr<net::HttpResponseHeaders>& headers) {
@@ -771,7 +749,7 @@
               ServiceWorkerConsts::kServiceWorkerRegisterNoMIMEError));
       return true;
     }
-    for (auto mime_type : kJavaScriptMimeTypes) {
+    for (auto mime_type : ServiceWorkerConsts::kJavaScriptMimeTypes) {
       if (net::MatchesMimeType(mime_type, content_type)) {
         mime_type_is_javascript = true;
         break;
@@ -1148,9 +1126,9 @@
     if (context->environment_settings()->GetOrigin() == registration_origin) {
       // 9. ... queue a task on settingsObject’s responsible event loop in the
       //    DOM manipulation task source to run the following steps:
-      context->message_loop()->task_runner()->PostTask(
+      context->message_loop()->task_runner()->PostBlockingTask(
           FROM_HERE,
-          base::BindOnce(
+          base::Bind(
               [](web::Context* context,
                  scoped_refptr<ServiceWorkerRegistrationObject> registration) {
                 // 9.1. Let registrationObjects be every
@@ -1699,9 +1677,9 @@
       // 2.2. For each registrationObject in registrationObjects:
       for (auto& context : web_context_registrations_) {
         // 2.2.1. Queue a task to...
-        context->message_loop()->task_runner()->PostTask(
+        context->message_loop()->task_runner()->PostBlockingTask(
             FROM_HERE,
-            base::BindOnce(
+            base::Bind(
                 [](web::Context* context,
                    ServiceWorkerRegistrationObject* registration) {
                   // 2.2.1. ... set the installing attribute of
@@ -1729,9 +1707,9 @@
       // 3.2. For each registrationObject in registrationObjects:
       for (auto& context : web_context_registrations_) {
         // 3.2.1. Queue a task to...
-        context->message_loop()->task_runner()->PostTask(
+        context->message_loop()->task_runner()->PostBlockingTask(
             FROM_HERE,
-            base::BindOnce(
+            base::Bind(
                 [](web::Context* context,
                    ServiceWorkerRegistrationObject* registration) {
                   // 3.2.1. ... set the waiting attribute of registrationObject
@@ -1757,9 +1735,9 @@
       // 4.2. For each registrationObject in registrationObjects:
       for (auto& context : web_context_registrations_) {
         // 4.2.1. Queue a task to...
-        context->message_loop()->task_runner()->PostTask(
+        context->message_loop()->task_runner()->PostBlockingTask(
             FROM_HERE,
-            base::BindOnce(
+            base::Bind(
                 [](web::Context* context,
                    ServiceWorkerRegistrationObject* registration) {
                   // 4.2.1. ... set the active attribute of registrationObject
@@ -1807,34 +1785,31 @@
       // 4. ... queue a task on
       //    settingsObject's responsible event loop in the DOM manipulation task
       //    source to run the following steps:
-      context->message_loop()->task_runner()->PostTask(
-          FROM_HERE,
-          base::BindOnce(
-              [](web::Context* context, ServiceWorkerObject* worker,
-                 ServiceWorkerState state) {
-                DCHECK_EQ(context->message_loop(),
-                          base::MessageLoop::current());
-                // 4.1. Let objectMap be settingsObject's service worker object
-                //      map.
-                // 4.2. If objectMap[worker] does not exist, then abort these
-                //      steps.
-                // 4.3. Let  workerObj be objectMap[worker].
-                auto worker_obj = context->LookupServiceWorker(worker);
-                if (worker_obj) {
-                  // 4.4. Set workerObj's state to state.
-                  worker_obj->set_state(state);
-                  // 4.5. Fire an event named statechange at workerObj.
-                  context->message_loop()->task_runner()->PostTask(
-                      FROM_HERE,
-                      base::BindOnce(
-                          [](scoped_refptr<ServiceWorker> worker_obj) {
-                            worker_obj->DispatchEvent(
-                                new web::Event(base::Tokens::statechange()));
-                          },
-                          worker_obj));
-                }
-              },
-              context, base::Unretained(worker), state));
+      context->message_loop()->task_runner()->PostBlockingTask(
+          FROM_HERE, base::Bind(
+                         [](web::Context* context, ServiceWorkerObject* worker,
+                            ServiceWorkerState state) {
+                           DCHECK_EQ(context->message_loop(),
+                                     base::MessageLoop::current());
+                           // 4.1. Let objectMap be settingsObject's service
+                           // worker object
+                           //      map.
+                           // 4.2. If objectMap[worker] does not exist, then
+                           // abort these
+                           //      steps.
+                           // 4.3. Let  workerObj be objectMap[worker].
+                           auto worker_obj =
+                               context->LookupServiceWorker(worker);
+                           if (worker_obj) {
+                             // 4.4. Set workerObj's state to state.
+                             worker_obj->set_state(state);
+                             // 4.5. Fire an event named statechange at
+                             // workerObj.
+                             worker_obj->DispatchEvent(
+                                 new web::Event(base::Tokens::statechange()));
+                           }
+                         },
+                         context, base::Unretained(worker), state));
     }
   }
 }
@@ -1916,6 +1891,12 @@
     context->message_loop()->task_runner()->PostBlockingTask(
         FROM_HERE, base::Bind(
                        [](web::Context* context, ServiceWorkerObject* worker) {
+                         auto worker_obj = context->LookupServiceWorker(worker);
+                         if (worker_obj) {
+                           worker_obj->set_state(kServiceWorkerStateRedundant);
+                           worker_obj->DispatchEvent(
+                               new web::Event(base::Tokens::statechange()));
+                         }
                          context->RemoveServiceWorker(worker);
                        },
                        context, base::Unretained(worker)));
diff --git a/cobalt/worker/service_worker_jobs.h b/cobalt/worker/service_worker_jobs.h
index 1b18ff4..d121554 100644
--- a/cobalt/worker/service_worker_jobs.h
+++ b/cobalt/worker/service_worker_jobs.h
@@ -42,6 +42,7 @@
 #include "cobalt/worker/client_query_options.h"
 #include "cobalt/worker/frame_type.h"
 #include "cobalt/worker/service_worker.h"
+#include "cobalt/worker/service_worker_consts.h"
 #include "cobalt/worker/service_worker_object.h"
 #include "cobalt/worker/service_worker_registration.h"
 #include "cobalt/worker/service_worker_registration_map.h"
diff --git a/cobalt/worker/service_worker_object.cc b/cobalt/worker/service_worker_object.cc
index b16feb6..9fcbbd1 100644
--- a/cobalt/worker/service_worker_object.cc
+++ b/cobalt/worker/service_worker_object.cc
@@ -233,8 +233,8 @@
   if (!service_worker_global_scope->csp_delegate()->OnReceiveHeaders(
           csp_headers)) {
     // https://www.w3.org/TR/service-workers/#content-security-policy
-    DLOG(WARNING) << "Warning: No Content Security Header received for the "
-                     "service worker.";
+    LOG(WARNING) << "Warning: No Content Security Header received for the "
+                    "service worker.";
   }
   web_context_->SetupFinished();
   // 8.11. If serviceWorker is an active worker, and there are any tasks queued
@@ -298,8 +298,8 @@
       // set of event types to handle remains an empty set. The user agents are
       // encouraged to show a warning that the event listeners must be added on
       // the very first evaluation of the worker script.
-      DLOG(WARNING) << "ServiceWorkerGlobalScope's event listeners must be "
-                       "added on the first evaluation of the worker script.";
+      LOG(WARNING) << "ServiceWorkerGlobalScope's event listeners must be "
+                      "added on the first evaluation of the worker script.";
     }
     event_types.clear();
   }
diff --git a/cobalt/worker/service_worker_persistent_settings.cc b/cobalt/worker/service_worker_persistent_settings.cc
index ba47191..41a689d 100644
--- a/cobalt/worker/service_worker_persistent_settings.cc
+++ b/cobalt/worker/service_worker_persistent_settings.cc
@@ -90,9 +90,6 @@
       ServiceWorkerConsts::kSettingsJson));
   persistent_settings_->ValidatePersistentSettings();
   DCHECK(persistent_settings_);
-
-  cache_.reset(cobalt::cache::Cache::GetInstance());
-  DCHECK(cache_);
 }
 
 void ServiceWorkerPersistentSettings::ReadServiceWorkerRegistrationMapSettings(
@@ -243,9 +240,10 @@
     if (script_url_value.is_string()) {
       auto script_url_string = script_url_value.GetString();
       auto script_url = GURL(script_url_string);
-      std::unique_ptr<std::vector<uint8_t>> data = cache_->Retrieve(
-          disk_cache::ResourceType::kServiceWorkerScript,
-          web::cache_utils::GetKey(key_string + script_url_string));
+      std::unique_ptr<std::vector<uint8_t>> data =
+          cobalt::cache::Cache::GetInstance()->Retrieve(
+              disk_cache::ResourceType::kServiceWorkerScript,
+              web::cache_utils::GetKey(key_string + script_url_string));
       if (data == nullptr) {
         return false;
       }
@@ -382,7 +380,7 @@
     // Use Cache::Store to persist the script resource.
     std::string resource = *(script_resource.second.content.get());
     std::vector<uint8_t> data(resource.begin(), resource.end());
-    cache_->Store(
+    cobalt::cache::Cache::GetInstance()->Store(
         disk_cache::ResourceType::kServiceWorkerScript,
         web::cache_utils::GetKey(registration_key_string + script_url_string),
         data,
@@ -448,7 +446,7 @@
       auto script_url_value = std::move(script_urls_list[i]);
       if (script_url_value.is_string()) {
         auto script_url_string = script_url_value.GetString();
-        cache_->Delete(
+        cobalt::cache::Cache::GetInstance()->Delete(
             disk_cache::ResourceType::kServiceWorkerScript,
             web::cache_utils::GetKey(key_string + script_url_string));
       }
diff --git a/cobalt/worker/service_worker_persistent_settings.h b/cobalt/worker/service_worker_persistent_settings.h
index efda66f..1d4fd2e 100644
--- a/cobalt/worker/service_worker_persistent_settings.h
+++ b/cobalt/worker/service_worker_persistent_settings.h
@@ -94,8 +94,6 @@
       persistent_settings_;
 
   std::set<std::string> key_set_;
-
-  std::unique_ptr<cobalt::cache::Cache> cache_;
 };
 
 }  // namespace worker
diff --git a/cobalt/worker/worker_global_scope.cc b/cobalt/worker/worker_global_scope.cc
index 150313e..33c7b86 100644
--- a/cobalt/worker/worker_global_scope.cc
+++ b/cobalt/worker/worker_global_scope.cc
@@ -31,9 +31,11 @@
 #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_location.h"
 #include "cobalt/worker/worker_navigator.h"
+#include "net/base/mime_util.h"
 #include "starboard/atomic.h"
 #include "url/gurl.h"
 
@@ -135,9 +137,11 @@
               *output_content = std::move(content);
             },
             content),
+        base::Bind(&ScriptLoader::UpdateOnResponseStarted,
+                   base::Unretained(this), error),
         base::Bind(&ScriptLoader::LoadingCompleteCallback,
                    base::Unretained(this), loader, error),
-        skip_fetch_intercept);
+        net::HttpRequestHeaders(), skip_fetch_intercept);
   }
 
   void LoadingCompleteCallback(std::unique_ptr<loader::Loader>* loader,
@@ -159,6 +163,31 @@
     }
   }
 
+  bool UpdateOnResponseStarted(
+      std::unique_ptr<std::string>* error, loader::Fetcher* fetcher,
+      const scoped_refptr<net::HttpResponseHeaders>& headers) {
+    std::string content_type;
+    bool mime_type_is_javascript = false;
+    if (headers->GetNormalizedHeader("Content-type", &content_type)) {
+      for (auto mime_type : ServiceWorkerConsts::kJavaScriptMimeTypes) {
+        if (net::MatchesMimeType(mime_type, content_type)) {
+          mime_type_is_javascript = true;
+          break;
+        }
+      }
+    }
+    if (content_type.empty()) {
+      error->reset(new std::string(base::StringPrintf(
+          ServiceWorkerConsts::kServiceWorkerRegisterNoMIMEError,
+          content_type.c_str())));
+    } else if (!mime_type_is_javascript) {
+      error->reset(new std::string(base::StringPrintf(
+          ServiceWorkerConsts::kServiceWorkerRegisterBadMIMEError,
+          content_type.c_str())));
+    }
+    return true;
+  }
+
   std::unique_ptr<std::string>& GetContents(int index) {
     return contents_[index];
   }
diff --git a/cobalt/worker/worker_global_scope.h b/cobalt/worker/worker_global_scope.h
index d3780d9..9c3468f 100644
--- a/cobalt/worker/worker_global_scope.h
+++ b/cobalt/worker/worker_global_scope.h
@@ -35,6 +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_location.h"
 #include "cobalt/worker/worker_navigator.h"
 #include "net/http/http_response_headers.h"
diff --git a/components/prefs/json_pref_store.cc b/components/prefs/json_pref_store.cc
index 7d8d189..d0d7481 100644
--- a/components/prefs/json_pref_store.cc
+++ b/components/prefs/json_pref_store.cc
@@ -206,9 +206,13 @@
   base::Value* old_value = nullptr;
   prefs_->Get(key, &old_value);
   if (!old_value || !value->Equals(old_value)) {
+#if defined(STARBOARD)
     // Value::DictionaryValue::Set creates a nested dictionary treating a URL
     // key as a path, SetKey avoids this.
-    prefs_->SetKey(key, std::move(*value.get()));
+    prefs_->SetKey(key, base::Value::FromUniquePtrValue(std::move(value)));
+#else
+    prefs_->Set(key, std::move(value));
+#endif
     ReportValueChanged(key, flags);
   }
 }
@@ -222,7 +226,11 @@
   base::Value* old_value = nullptr;
   prefs_->Get(key, &old_value);
   if (!old_value || !value->Equals(old_value)) {
-    prefs_->SetPath({key}, base::Value::FromUniquePtrValue(std::move(value)));
+#if defined(STARBOARD)
+    prefs_->SetKey(key, base::Value::FromUniquePtrValue(std::move(value)));
+#else
+    prefs_->Set(key, std::move(value));
+#endif
     ScheduleWrite(flags);
   }
 }
diff --git a/docker/linux/base/build/Dockerfile b/docker/linux/base/build/Dockerfile
index a3f0a3b..60533d6 100644
--- a/docker/linux/base/build/Dockerfile
+++ b/docker/linux/base/build/Dockerfile
@@ -22,6 +22,7 @@
     && apt install -qqy --no-install-recommends \
         binutils \
         bison \
+        nasm \
         ninja-build \
         pkgconf \
         unzip \
diff --git a/docker/linux/clang-3-9/Dockerfile b/docker/linux/clang-3-9/Dockerfile
index d79ccd3..abbb506 100644
--- a/docker/linux/clang-3-9/Dockerfile
+++ b/docker/linux/clang-3-9/Dockerfile
@@ -17,7 +17,11 @@
 ARG DEBIAN_FRONTEND=noninteractive
 
 RUN apt update -qqy \
-    && apt install -qqy --no-install-recommends clang-3.9 \
+    && apt install -qqy --no-install-recommends \
+        clang-3.9 software-properties-common \
+    && add-apt-repository -y ppa:git-core/ppa \
+    && apt update -qqy \
+    && apt install -qqy --no-install-recommends git \
     && /opt/clean-after-apt.sh
 
 CMD gn gen ${OUTDIR}/${PLATFORM}_${CONFIG} --args="target_platform=\"${PLATFORM}\" build_type=\"${CONFIG}\" using_old_compiler=true" && \
diff --git a/docker/linux/gcc-6-3/Dockerfile b/docker/linux/gcc-6-3/Dockerfile
index 2ead07d..18e431a 100644
--- a/docker/linux/gcc-6-3/Dockerfile
+++ b/docker/linux/gcc-6-3/Dockerfile
@@ -17,7 +17,11 @@
 ARG DEBIAN_FRONTEND=noninteractive
 
 RUN apt update -qqy \
-    && apt install -qqy gcc-6 g++-6 \
+    && apt install -qqy --no-install-recommends \
+        gcc-6 g++-6 software-properties-common \
+    && add-apt-repository -y ppa:git-core/ppa \
+    && apt update -qqy \
+    && apt install -qqy --no-install-recommends git \
     && /opt/clean-after-apt.sh
 
 CMD gn gen ${OUTDIR}/${PLATFORM}_${CONFIG} --args="target_platform=\"${PLATFORM}\" build_type=\"${CONFIG}\" is_clang=false using_old_compiler=true" && \
diff --git a/net/cookies/cookie_monster_unittest.cc b/net/cookies/cookie_monster_unittest.cc
index b137fb5..c55ddd0 100644
--- a/net/cookies/cookie_monster_unittest.cc
+++ b/net/cookies/cookie_monster_unittest.cc
@@ -1482,6 +1482,7 @@
 static const base::TimeDelta kAccessDelay =
     kLastAccessThreshold + base::TimeDelta::FromMilliseconds(20);
 
+#if !defined(STARBOARD)
 TEST_F(CookieMonsterTest, TestLastAccess) {
   std::unique_ptr<CookieMonster> cm(
       new CookieMonster(nullptr, kLastAccessThreshold, &net_log_));
@@ -1520,6 +1521,7 @@
             GetCookiesWithOptions(cm.get(), http_www_foo_.url(), options));
   EXPECT_FALSE(last_access_date == GetFirstCookieAccessDate(cm.get()));
 }
+#endif
 
 TEST_F(CookieMonsterTest, TestHostGarbageCollection) {
   TestHostGarbageCollectHelper();
diff --git a/net/cookies/cookie_options.cc b/net/cookies/cookie_options.cc
index 1b97f85..fe87f26 100644
--- a/net/cookies/cookie_options.cc
+++ b/net/cookies/cookie_options.cc
@@ -12,7 +12,11 @@
 CookieOptions::CookieOptions()
     : exclude_httponly_(true),
       same_site_cookie_mode_(SameSiteCookieMode::DO_NOT_INCLUDE),
+#if defined(STARBOARD)
+      update_access_time_(false),
+#else
       update_access_time_(true),
+#endif
       server_time_() {}
 
 }  // namespace net
diff --git a/net/cookies/cookie_options.h b/net/cookies/cookie_options.h
index 0e312b0..8cc6658 100644
--- a/net/cookies/cookie_options.h
+++ b/net/cookies/cookie_options.h
@@ -59,7 +59,9 @@
   bool has_server_time() const { return !server_time_.is_null(); }
   base::Time server_time() const { return server_time_; }
 
+#if !defined(STARBOARD)
   void set_update_access_time() { update_access_time_ = true; }
+#endif
   void set_do_not_update_access_time() { update_access_time_ = false; }
   bool update_access_time() const { return update_access_time_; }
 
diff --git a/starboard/android/apk/apk_sources.gni b/starboard/android/apk/apk_sources.gni
index 07912f6..d1f668c 100644
--- a/starboard/android/apk/apk_sources.gni
+++ b/starboard/android/apk/apk_sources.gni
@@ -28,11 +28,13 @@
   "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltSystemConfigChangeReceiver.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltTextToSpeechHelper.java",
+  "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/CrashContextUpdateHandler.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/ErrorDialog.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/KeyboardEditor.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/KeyboardInputConnection.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/NetworkStatus.java",
+  "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/NullCobaltFactory.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/PlatformError.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/ResourceOverlay.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java",
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
index 0592441..d942102 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
@@ -159,6 +159,8 @@
 
     getStarboardBridge().onActivityStart(this, keyboardEditor);
     super.onStart();
+
+    nativeInitializeMediaCapabilitiesInBackground();
   }
 
   @Override
@@ -394,4 +396,6 @@
   public long getAppStartTimestamp() {
     return timeInNanoseconds;
   }
+
+  private static native void nativeInitializeMediaCapabilitiesInBackground();
 }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CrashContextUpdateHandler.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CrashContextUpdateHandler.java
new file mode 100644
index 0000000..3dca27b
--- /dev/null
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CrashContextUpdateHandler.java
@@ -0,0 +1,5 @@
+package dev.cobalt.coat;
+
+public interface CrashContextUpdateHandler {
+  public void onCrashContextUpdate();
+}
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/NullCobaltFactory.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/NullCobaltFactory.java
new file mode 100644
index 0000000..e239e40
--- /dev/null
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/NullCobaltFactory.java
@@ -0,0 +1,16 @@
+package dev.cobalt.coat;
+
+// Provide a Null factory as some platform service might need it for legacy
+// apk versions.
+public class NullCobaltFactory implements CobaltService.Factory {
+
+  @Override
+  public CobaltService createCobaltService(long nativeService) {
+    return null;
+  }
+
+  @Override
+  public String getServiceName() {
+    return null;
+  }
+}
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 e1cf4f7..2e25fc9 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
@@ -81,6 +81,7 @@
   private ResourceOverlay resourceOverlay;
   private AdvertisingId advertisingId;
   private VolumeStateReceiver volumeStateReceiver;
+  private CrashContextUpdateHandler crashContextUpdateHandler;
 
   static {
     // Even though NativeActivity already loads our library from C++,
@@ -827,9 +828,16 @@
   public void setCrashContext(String key, String value) {
     Log.i(TAG, "setCrashContext Called: " + key + ", " + value);
     crashContext.put(key, value);
+    if (this.crashContextUpdateHandler != null) {
+      this.crashContextUpdateHandler.onCrashContextUpdate();
+    }
   }
 
   public HashMap<String, String> getCrashContext() {
     return this.crashContext;
   }
+
+  public void registerCrashContextUpdateHandler(CrashContextUpdateHandler handler) {
+    this.crashContextUpdateHandler = handler;
+  }
 }
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 1d3428e..9722d93 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
@@ -136,7 +136,7 @@
    * @param nativeMediaDrmBridge The native owner of this class.
    */
   @UsedByNative
-  static MediaDrmBridge create(long nativeMediaDrmBridge) {
+  static MediaDrmBridge create(String keySystem, long nativeMediaDrmBridge) {
     UUID cryptoScheme = WIDEVINE_UUID;
     if (!MediaDrm.isCryptoSchemeSupported(cryptoScheme)) {
       return null;
@@ -144,7 +144,7 @@
 
     MediaDrmBridge mediaDrmBridge = null;
     try {
-      mediaDrmBridge = new MediaDrmBridge(cryptoScheme, nativeMediaDrmBridge);
+      mediaDrmBridge = new MediaDrmBridge(keySystem, cryptoScheme, nativeMediaDrmBridge);
       Log.d(TAG, "MediaDrmBridge successfully created.");
     } catch (UnsupportedSchemeException e) {
       Log.e(TAG, "Unsupported DRM scheme", e);
@@ -373,7 +373,7 @@
     return mMediaCrypto;
   }
 
-  private MediaDrmBridge(UUID schemeUUID, long nativeMediaDrmBridge)
+  private MediaDrmBridge(String keySystem, UUID schemeUUID, long nativeMediaDrmBridge)
       throws android.media.UnsupportedSchemeException {
     mSchemeUUID = schemeUUID;
     mMediaDrm = new MediaDrm(schemeUUID);
@@ -450,6 +450,10 @@
 
     mMediaDrm.setPropertyString("privacyMode", "enable");
     mMediaDrm.setPropertyString("sessionSharing", "enable");
+    if (keySystem.equals("com.youtube.widevine.l3")
+        && mMediaDrm.getPropertyString("securityLevel") != "L3") {
+      mMediaDrm.setPropertyString("securityLevel", "L3");
+    }
   }
 
   @RequiresApi(23)
diff --git a/starboard/android/shared/BUILD.gn b/starboard/android/shared/BUILD.gn
index ca4c2e2..cf01d39 100644
--- a/starboard/android/shared/BUILD.gn
+++ b/starboard/android/shared/BUILD.gn
@@ -336,6 +336,7 @@
     "directory_get_next.cc",
     "directory_internal.h",
     "directory_open.cc",
+    "drm_create_system.cc",
     "drm_system.cc",
     "drm_system.h",
     "egl_swap_buffers.cc",
@@ -385,10 +386,12 @@
     "media_get_video_buffer_budget.cc",
     "media_is_audio_supported.cc",
     "media_is_buffer_pool_allocate_on_demand.cc",
+    "media_is_supported.cc",
     "media_is_video_supported.cc",
     "microphone_impl.cc",
     "network_status_impl.cc",
     "platform_service.cc",
+    "player_components_factory.cc",
     "player_components_factory.h",
     "player_create.cc",
     "player_destroy.cc",
@@ -481,13 +484,6 @@
       "internal/input_events_filter.h",
     ]
     defines = [ "STARBOARD_INPUT_EVENTS_FILTER" ]
-    deps += [ "//starboard/android/shared/drm_system_extension" ]
-  } else {
-    sources += [
-      "drm_create_system.cc",
-      "media_is_supported.cc",
-      "player_components_factory.cc",
-    ]
   }
 
   if (sb_is_evergreen_compatible) {
diff --git a/starboard/android/shared/android_main.cc b/starboard/android/shared/android_main.cc
index 755210d..40fa072 100644
--- a/starboard/android/shared/android_main.cc
+++ b/starboard/android/shared/android_main.cc
@@ -224,6 +224,25 @@
   JniEnvExt::Initialize(env, starboard_bridge);
 }
 
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_coat_VolumeStateReceiver_nativeVolumeChanged(JNIEnv* env,
+                                                             jobject jcaller,
+                                                             jint volumeDelta) {
+  if (g_app_running) {
+    SbKey key =
+        volumeDelta > 0 ? SbKey::kSbKeyVolumeUp : SbKey::kSbKeyVolumeDown;
+    ApplicationAndroid::Get()->SendKeyboardInject(key);
+  }
+}
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_coat_VolumeStateReceiver_nativeMuteChanged(JNIEnv* env,
+                                                           jobject jcaller) {
+  if (g_app_running) {
+    ApplicationAndroid::Get()->SendKeyboardInject(SbKey::kSbKeyVolumeMute);
+  }
+}
+
 }  // namespace
 }  // namespace shared
 }  // namespace android
diff --git a/starboard/android/shared/application_android.cc b/starboard/android/shared/application_android.cc
index 18bf1fc..deeed10 100644
--- a/starboard/android/shared/application_android.cc
+++ b/starboard/android/shared/application_android.cc
@@ -736,21 +736,6 @@
   overlayed_bool_variables_[var_name] = value;
   return value;
 }
-
-extern "C" SB_EXPORT_PLATFORM void
-Java_dev_cobalt_coat_VolumeStateReceiver_nativeVolumeChanged(JNIEnv* env,
-                                                             jobject jcaller,
-                                                             jint volumeDelta) {
-  SbKey key = volumeDelta > 0 ? SbKey::kSbKeyVolumeUp : SbKey::kSbKeyVolumeDown;
-  ApplicationAndroid::Get()->SendKeyboardInject(key);
-}
-
-extern "C" SB_EXPORT_PLATFORM void
-Java_dev_cobalt_coat_VolumeStateReceiver_nativeMuteChanged(JNIEnv* env,
-                                                           jobject jcaller) {
-  ApplicationAndroid::Get()->SendKeyboardInject(SbKey::kSbKeyVolumeMute);
-}
-
 }  // namespace shared
 }  // namespace android
 }  // namespace starboard
diff --git a/starboard/android/shared/audio_renderer_passthrough.cc b/starboard/android/shared/audio_renderer_passthrough.cc
index 34f5bed..3f94eb0 100644
--- a/starboard/android/shared/audio_renderer_passthrough.cc
+++ b/starboard/android/shared/audio_renderer_passthrough.cc
@@ -135,12 +135,6 @@
         &AudioRendererPassthrough::CreateAudioTrackAndStartProcessing, this));
   }
 
-  if (frames_per_input_buffer_ == 0) {
-    frames_per_input_buffer_ = ParseAc3SyncframeAudioSampleCount(
-        input_buffers.front()->data(), input_buffers.front()->size());
-    SB_LOG(INFO) << "Got frames per input buffer " << frames_per_input_buffer_;
-  }
-
   can_accept_more_data_.store(false);
 
   decoder_->Decode(
@@ -597,6 +591,25 @@
   auto decoded_audio = decoder_->Read(&decoded_audio_sample_rate);
   SB_DCHECK(decoded_audio);
 
+  if (!decoded_audio->is_end_of_stream()) {
+    SB_DCHECK(decoded_audio->size() > 0);
+    // We set |frames_per_input_buffer_| before adding first |decoded_audio|
+    // into |decoded_audios_|. The usage of |frames_per_input_buffer_| in
+    // UpdateStatusAndWriteData() from another thread only happens when there is
+    // audio decoded, so it's thread-safe even if the code is not synchronized
+    // using a lock.
+    if (frames_per_input_buffer_ == 0) {
+      frames_per_input_buffer_ = ParseAc3SyncframeAudioSampleCount(
+          decoded_audio->buffer(), decoded_audio->size());
+      SB_LOG(INFO) << "Got frames per input buffer "
+                   << frames_per_input_buffer_;
+    } else {
+      SB_DCHECK(frames_per_input_buffer_ ==
+                ParseAc3SyncframeAudioSampleCount(decoded_audio->buffer(),
+                                                  decoded_audio->size()));
+    }
+  }
+
   ScopedLock scoped_lock(mutex_);
   decoded_audios_.push(decoded_audio);
 }
diff --git a/starboard/android/shared/audio_track_audio_sink_type.cc b/starboard/android/shared/audio_track_audio_sink_type.cc
index d055a29..ada20e9 100644
--- a/starboard/android/shared/audio_track_audio_sink_type.cc
+++ b/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -181,8 +181,8 @@
     if (bridge_.GetAndResetHasAudioDeviceChanged(env)) {
       SB_LOG(INFO) << "Audio device changed, raising a capability changed "
                       "error to restart playback.";
-      error_func_(kSbPlayerErrorCapabilityChanged,
-                  "Audio device capability changed", context_);
+      ReportError(kSbPlayerErrorCapabilityChanged,
+                  "Audio device capability changed");
       break;
     }
 
@@ -325,10 +325,9 @@
 
       bool capabilities_changed =
           written_frames == AudioTrackBridge::kAudioTrackErrorDeadObject;
-      error_func_(
+      ReportError(
           capabilities_changed,
-          FormatString("Error while writing frames: %d", written_frames),
-          context_);
+          FormatString("Error while writing frames: %d", written_frames));
       SB_LOG(INFO) << "Restarting playback.";
       break;
     } else if (written_frames > 0) {
@@ -381,6 +380,14 @@
   return samples_written / channels_;
 }
 
+void AudioTrackAudioSink::ReportError(bool capability_changed,
+                                      const std::string& error_message) {
+  SB_LOG(INFO) << "AudioTrackAudioSink error: " << error_message;
+  if (error_func_) {
+    error_func_(capability_changed, error_message, context_);
+  }
+}
+
 void AudioTrackAudioSink::SetVolume(double volume) {
   bridge_.SetVolume(volume);
 }
diff --git a/starboard/android/shared/audio_track_audio_sink_type.h b/starboard/android/shared/audio_track_audio_sink_type.h
index 38c7014..88974ff 100644
--- a/starboard/android/shared/audio_track_audio_sink_type.h
+++ b/starboard/android/shared/audio_track_audio_sink_type.h
@@ -18,6 +18,7 @@
 #include <atomic>
 #include <functional>
 #include <map>
+#include <string>
 #include <vector>
 
 #include "starboard/android/shared/audio_sink_min_required_frames_tester.h"
@@ -133,6 +134,8 @@
 
   int WriteData(JniEnvExt* env, const void* buffer, int size, SbTime sync_time);
 
+  void ReportError(bool capability_changed, const std::string& error_message);
+
   Type* const type_;
   const int channels_;
   const int sampling_frequency_hz_;
diff --git a/starboard/android/shared/drm_create_system.cc b/starboard/android/shared/drm_create_system.cc
index 1e97b89..6e9bc88 100644
--- a/starboard/android/shared/drm_create_system.cc
+++ b/starboard/android/shared/drm_create_system.cc
@@ -26,6 +26,7 @@
     SbDrmSessionClosedFunc session_closed_callback) {
   using starboard::android::shared::DrmSystem;
   using starboard::android::shared::IsWidevineL1;
+  using starboard::android::shared::IsWidevineL3;
 
   if (!update_request_callback || !session_updated_callback ||
       !key_statuses_changed_callback || !server_certificate_updated_callback ||
@@ -33,13 +34,13 @@
     return kSbDrmSystemInvalid;
   }
 
-  if (!IsWidevineL1(key_system)) {
+  if (!IsWidevineL1(key_system) && !IsWidevineL3(key_system)) {
     return kSbDrmSystemInvalid;
   }
 
   DrmSystem* drm_system =
-      new DrmSystem(context, update_request_callback, session_updated_callback,
-                    key_statuses_changed_callback);
+      new DrmSystem(key_system, context, update_request_callback,
+                    session_updated_callback, key_statuses_changed_callback);
   if (!drm_system->is_valid()) {
     delete drm_system;
     return kSbDrmSystemInvalid;
diff --git a/starboard/android/shared/drm_system.cc b/starboard/android/shared/drm_system.cc
index a2edadd..3b195e5 100644
--- a/starboard/android/shared/drm_system.cc
+++ b/starboard/android/shared/drm_system.cc
@@ -67,8 +67,7 @@
   if (left.identifier_size != right.identifier_size) {
     return false;
   }
-  return memcmp(left.identifier, right.identifier,
-                left.identifier_size) == 0;
+  return memcmp(left.identifier, right.identifier, left.identifier_size) == 0;
 }
 
 extern "C" SB_EXPORT_PLATFORM void
@@ -112,8 +111,9 @@
   SB_DCHECK(session_id_elements);
 
   // NULL array indicates key status isn't supported (i.e. Android API < 23)
-  jsize length = (j_key_status_array == NULL) ? 0
-      : env->GetArrayLength(j_key_status_array);
+  jsize length = (j_key_status_array == NULL)
+                     ? 0
+                     : env->GetArrayLength(j_key_status_array);
   std::vector<SbDrmKeyId> drm_key_ids(length);
   std::vector<SbDrmKeyStatus> drm_key_statuses(length);
 
@@ -172,11 +172,13 @@
 }  // namespace
 
 DrmSystem::DrmSystem(
+    const char* key_system,
     void* context,
     SbDrmSessionUpdateRequestFunc update_request_callback,
     SbDrmSessionUpdatedFunc session_updated_callback,
     SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback)
-    : context_(context),
+    : key_system_(key_system),
+      context_(context),
       update_request_callback_(update_request_callback),
       session_updated_callback_(session_updated_callback),
       key_statuses_changed_callback_(key_statuses_changed_callback),
@@ -186,9 +188,12 @@
   ON_INSTANCE_CREATED(AndroidDrmSystem);
 
   JniEnvExt* env = JniEnvExt::Get();
+  ScopedLocalJavaRef<jstring> j_key_system(
+      env->NewStringStandardUTFOrAbort(key_system));
   j_media_drm_bridge_ = env->CallStaticObjectMethodOrAbort(
       "dev/cobalt/media/MediaDrmBridge", "create",
-      "(J)Ldev/cobalt/media/MediaDrmBridge;", reinterpret_cast<jlong>(this));
+      "(Ljava/lang/String;J)Ldev/cobalt/media/MediaDrmBridge;",
+      j_key_system.Get(), reinterpret_cast<jlong>(this));
   if (!j_media_drm_bridge_) {
     SB_LOG(ERROR) << "Failed to create MediaDrmBridge.";
     return;
diff --git a/starboard/android/shared/drm_system.h b/starboard/android/shared/drm_system.h
index 19b4bfe..48051c5 100644
--- a/starboard/android/shared/drm_system.h
+++ b/starboard/android/shared/drm_system.h
@@ -23,6 +23,7 @@
 #include <unordered_map>
 #include <vector>
 
+#include "starboard/android/shared/media_common.h"
 #include "starboard/common/log.h"
 #include "starboard/common/mutex.h"
 #include "starboard/types.h"
@@ -33,7 +34,8 @@
 
 class DrmSystem : public ::SbDrmSystemPrivate {
  public:
-  DrmSystem(void* context,
+  DrmSystem(const char* key_system,
+            void* context,
             SbDrmSessionUpdateRequestFunc update_request_callback,
             SbDrmSessionUpdatedFunc session_updated_callback,
             SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback);
@@ -54,8 +56,7 @@
   bool IsServerCertificateUpdatable() override { return false; }
   void UpdateServerCertificate(int ticket,
                                const void* certificate,
-                               int certificate_size) override {
-  }
+                               int certificate_size) override {}
   const void* GetMetrics(int* size) override;
 
   jobject GetMediaCrypto() const { return j_media_crypto_; }
@@ -76,10 +77,14 @@
   bool is_valid() const {
     return j_media_drm_bridge_ != NULL && j_media_crypto_ != NULL;
   }
+  bool require_secured_decoder() const {
+    return IsWidevineL1(key_system_.c_str());
+  }
 
  private:
   void CallKeyStatusesChangedCallbackWithKeyStatusRestricted_Locked();
 
+  const std::string key_system_;
   void* context_;
   SbDrmSessionUpdateRequestFunc update_request_callback_;
   SbDrmSessionUpdatedFunc session_updated_callback_;
diff --git a/starboard/android/shared/media_capabilities_cache.cc b/starboard/android/shared/media_capabilities_cache.cc
index 2a5fc0e..7b8997b 100644
--- a/starboard/android/shared/media_capabilities_cache.cc
+++ b/starboard/android/shared/media_capabilities_cache.cc
@@ -22,6 +22,7 @@
 #include "starboard/once.h"
 #include "starboard/shared/starboard/media/key_system_supportability_cache.h"
 #include "starboard/shared/starboard/media/mime_supportability_cache.h"
+#include "starboard/thread.h"
 
 namespace starboard {
 namespace android {
@@ -268,7 +269,7 @@
     return GetIsWidevineSupported();
   }
   ScopedLock scoped_lock(mutex_);
-  LazyInitialize_Locked();
+  UpdateMediaCapabilities_Locked();
   return is_widevine_supported_;
 }
 
@@ -277,7 +278,7 @@
     return GetIsCbcsSupported();
   }
   ScopedLock scoped_lock(mutex_);
-  LazyInitialize_Locked();
+  UpdateMediaCapabilities_Locked();
   return is_cbcs_supported_;
 }
 
@@ -289,7 +290,7 @@
            supported_transfer_ids.end();
   }
   ScopedLock scoped_lock(mutex_);
-  LazyInitialize_Locked();
+  UpdateMediaCapabilities_Locked();
   return supported_transfer_ids_.find(transfer_id) !=
          supported_transfer_ids_.end();
 }
@@ -316,7 +317,7 @@
   }
 
   ScopedLock scoped_lock(mutex_);
-  LazyInitialize_Locked();
+  UpdateMediaCapabilities_Locked();
   return max_audio_output_channels_;
 }
 
@@ -361,7 +362,7 @@
   }
 
   ScopedLock scoped_lock(mutex_);
-  LazyInitialize_Locked();
+  UpdateMediaCapabilities_Locked();
 
   for (auto& audio_capability : audio_codec_capabilities_map_[mime_type]) {
     // Reject if tunnel mode is required but codec doesn't support it.
@@ -407,7 +408,7 @@
   }
 
   ScopedLock scoped_lock(mutex_);
-  LazyInitialize_Locked();
+  UpdateMediaCapabilities_Locked();
 
   for (auto& video_capability : video_codec_capabilities_map_[mime_type]) {
     // Reject if secure decoder is required but codec doesn't support it.
@@ -465,6 +466,8 @@
 void MediaCapabilitiesCache::ClearCache() {
   ScopedLock scoped_lock(mutex_);
   is_initialized_ = false;
+  supported_hdr_types_is_dirty_ = true;
+  audio_output_channels_is_dirty_ = true;
   is_widevine_supported_ = false;
   is_cbcs_supported_ = false;
   supported_transfer_ids_.clear();
@@ -474,23 +477,9 @@
   max_audio_output_channels_ = -1;
 }
 
-void MediaCapabilitiesCache::ReloadSupportedHdrTypes() {
+void MediaCapabilitiesCache::Initialize() {
   ScopedLock scoped_lock(mutex_);
-  if (!is_initialized_) {
-    LazyInitialize_Locked();
-    return;
-  }
-  supported_transfer_ids_ = GetSupportedHdrTypes();
-}
-
-void MediaCapabilitiesCache::ReloadAudioOutputChannels() {
-  ScopedLock scoped_lock(mutex_);
-  if (!is_initialized_) {
-    LazyInitialize_Locked();
-    return;
-  }
-  max_audio_output_channels_ =
-      ::starboard::android::shared::GetMaxAudioOutputChannels();
+  UpdateMediaCapabilities_Locked();
 }
 
 MediaCapabilitiesCache::MediaCapabilitiesCache() {
@@ -499,20 +488,23 @@
   KeySystemSupportabilityCache::GetInstance()->SetCacheEnabled(true);
 }
 
-void MediaCapabilitiesCache::LazyInitialize_Locked() {
+void MediaCapabilitiesCache::UpdateMediaCapabilities_Locked() {
   mutex_.DCheckAcquired();
 
+  if (supported_hdr_types_is_dirty_.exchange(false)) {
+    supported_transfer_ids_ = GetSupportedHdrTypes();
+  }
+  if (audio_output_channels_is_dirty_.exchange(false)) {
+    max_audio_output_channels_ =
+        ::starboard::android::shared::GetMaxAudioOutputChannels();
+  }
+
   if (is_initialized_) {
     return;
   }
   is_widevine_supported_ = GetIsWidevineSupported();
   is_cbcs_supported_ = GetIsCbcsSupported();
-  supported_transfer_ids_ = GetSupportedHdrTypes();
-  max_audio_output_channels_ =
-      ::starboard::android::shared::GetMaxAudioOutputChannels();
-
   LoadCodecInfos_Locked();
-
   is_initialized_ = true;
 }
 
@@ -569,17 +561,30 @@
 extern "C" SB_EXPORT_PLATFORM void
 Java_dev_cobalt_util_DisplayUtil_nativeOnDisplayChanged() {
   SB_DLOG(INFO) << "Display device has changed.";
-  MediaCapabilitiesCache::GetInstance()->ReloadSupportedHdrTypes();
+  MediaCapabilitiesCache::GetInstance()->ClearSupportedHdrTypes();
   MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities();
 }
 
 extern "C" SB_EXPORT_PLATFORM void
 Java_dev_cobalt_media_AudioOutputManager_nativeOnAudioDeviceChanged() {
   SB_DLOG(INFO) << "Audio device has changed.";
-  MediaCapabilitiesCache::GetInstance()->ReloadAudioOutputChannels();
+  MediaCapabilitiesCache::GetInstance()->ClearAudioOutputChannels();
   MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities();
 }
 
+void* MediaCapabilitiesCacheInitializationThreadEntry(void* context) {
+  SB_LOG(INFO) << "Initialize MediaCapabilitiesCache in background.";
+  MediaCapabilitiesCache::GetInstance()->Initialize();
+  return 0;
+}
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_coat_CobaltActivity_nativeInitializeMediaCapabilitiesInBackground() {
+  SbThreadCreate(0, kSbThreadPriorityNormal, kSbThreadNoAffinity, false,
+                 "media_capabilities_cache_thread",
+                 MediaCapabilitiesCacheInitializationThreadEntry, nullptr);
+}
+
 }  // namespace shared
 }  // namespace android
 }  // namespace starboard
diff --git a/starboard/android/shared/media_capabilities_cache.h b/starboard/android/shared/media_capabilities_cache.h
index 21d735b..db3879e 100644
--- a/starboard/android/shared/media_capabilities_cache.h
+++ b/starboard/android/shared/media_capabilities_cache.h
@@ -160,8 +160,9 @@
   void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; }
   void ClearCache();
 
-  void ReloadSupportedHdrTypes();
-  void ReloadAudioOutputChannels();
+  void Initialize();
+  void ClearSupportedHdrTypes() { supported_hdr_types_is_dirty_ = true; }
+  void ClearAudioOutputChannels() { audio_output_channels_is_dirty_ = true; }
 
  private:
   MediaCapabilitiesCache();
@@ -170,7 +171,7 @@
   MediaCapabilitiesCache(const MediaCapabilitiesCache&) = delete;
   MediaCapabilitiesCache& operator=(const MediaCapabilitiesCache&) = delete;
 
-  void LazyInitialize_Locked();
+  void UpdateMediaCapabilities_Locked();
   void LoadCodecInfos_Locked();
 
   Mutex mutex_;
@@ -187,6 +188,8 @@
   std::map<std::string, VideoCodecCapabilities> video_codec_capabilities_map_;
 
   std::atomic_bool is_enabled_{true};
+  std::atomic_bool supported_hdr_types_is_dirty_{true};
+  std::atomic_bool audio_output_channels_is_dirty_{true};
   bool is_initialized_ = false;
   bool is_widevine_supported_ = false;
   bool is_cbcs_supported_ = false;
diff --git a/starboard/android/shared/media_codec_bridge.cc b/starboard/android/shared/media_codec_bridge.cc
index 2e2cd1a..f09bd35 100644
--- a/starboard/android/shared/media_codec_bridge.cc
+++ b/starboard/android/shared/media_codec_bridge.cc
@@ -170,8 +170,8 @@
 
   std::string decoder_name =
       MediaCapabilitiesCache::GetInstance()->FindAudioDecoder(
-          mime, 0, /* bitrate */
-          false /* must_support_tunnel_mode */);
+          mime, /* bitrate = */ 0,
+          /* must_support_tunnel_mode = */ false);
 
   if (decoder_name.empty()) {
     SB_LOG(ERROR) << "Failed to find decoder for " << audio_codec << ".";
@@ -220,6 +220,7 @@
     jobject j_surface,
     jobject j_media_crypto,
     const SbMediaColorMetadata* color_metadata,
+    bool require_secured_decoder,
     bool require_software_codec,
     int tunnel_mode_audio_session_id,
     bool force_big_endian_hdr_metadata,
@@ -233,34 +234,28 @@
     return scoped_ptr<MediaCodecBridge>(NULL);
   }
 
-  const bool must_support_secure = !!j_media_crypto;
+  const bool must_support_secure = require_secured_decoder;
   const bool must_support_hdr = color_metadata;
   const bool must_support_tunnel_mode = tunnel_mode_audio_session_id != -1;
   // On first pass, try to find a decoder with HDR if the color info is
   // non-null.
   std::string decoder_name =
       MediaCapabilitiesCache::GetInstance()->FindVideoDecoder(
-          mime, must_support_secure,    /* must_support_secure */
-          must_support_hdr,             /* must_support_hdr */
-          require_software_codec,       /* is_software_codec */
-          must_support_tunnel_mode,     /* must_support_tunnel_mode */
-          force_improved_support_check, /* force_improved_support_check */
-          0,                            /* frame_width */
-          0,                            /* frame_height */
-          0,                            /* bitrate */
-          0 /* fps */);
+          mime, must_support_secure, must_support_hdr, require_software_codec,
+          must_support_tunnel_mode, force_improved_support_check,
+          /* frame_width = */ 0,
+          /* frame_height = */ 0,
+          /* bitrate = */ 0,
+          /* fps = */ 0);
   if (decoder_name.empty() && color_metadata) {
     // On second pass, forget HDR.
     decoder_name = MediaCapabilitiesCache::GetInstance()->FindVideoDecoder(
-        mime, must_support_secure,    /* must_support_secure */
-        false,                        /* must_support_hdr */
-        require_software_codec,       /* is_software_codec */
-        must_support_tunnel_mode,     /* must_support_tunnel_mode */
-        force_improved_support_check, /* force_improved_support_check */
-        0,                            /* frame_width */
-        0,                            /* frame_height */
-        0,                            /* bitrate */
-        0 /* fps */);
+        mime, must_support_secure, /* must_support_hdr = */ false,
+        require_software_codec, must_support_tunnel_mode,
+        force_improved_support_check, /* frame_width = */ 0,
+        /* frame_height = */ 0,
+        /* bitrate = */ 0,
+        /* fps = */ 0);
   }
 
   if (decoder_name.empty()) {
diff --git a/starboard/android/shared/media_codec_bridge.h b/starboard/android/shared/media_codec_bridge.h
index 77b9c7a..d3999af 100644
--- a/starboard/android/shared/media_codec_bridge.h
+++ b/starboard/android/shared/media_codec_bridge.h
@@ -110,6 +110,7 @@
       jobject j_surface,
       jobject j_media_crypto,
       const SbMediaColorMetadata* color_metadata,
+      bool require_secured_decoder,
       bool require_software_codec,
       int tunnel_mode_audio_session_id,
       bool force_big_endian_hdr_metadata,
diff --git a/starboard/android/shared/media_common.h b/starboard/android/shared/media_common.h
index f9e5a56..dcbd0b3 100644
--- a/starboard/android/shared/media_common.h
+++ b/starboard/android/shared/media_common.h
@@ -34,6 +34,10 @@
          strcmp(key_system, "com.widevine.alpha") == 0;
 }
 
+inline bool IsWidevineL3(const char* key_system) {
+  return strcmp(key_system, "com.youtube.widevine.l3") == 0;
+}
+
 // Map a supported |SbMediaAudioCodec| into its corresponding mime type
 // string.  Returns |nullptr| if |audio_codec| is not supported.
 // On return, |is_passthrough| will be set to true if the codec should be played
diff --git a/starboard/android/shared/media_decoder.cc b/starboard/android/shared/media_decoder.cc
index 90beda3..eb9079c 100644
--- a/starboard/android/shared/media_decoder.cc
+++ b/starboard/android/shared/media_decoder.cc
@@ -128,12 +128,14 @@
   SB_DCHECK(frame_rendered_cb_);
 
   jobject j_media_crypto = drm_system_ ? drm_system_->GetMediaCrypto() : NULL;
+  const bool require_secured_decoder =
+      drm_system_ && drm_system_->require_secured_decoder();
   SB_DCHECK(!drm_system_ || j_media_crypto);
   media_codec_bridge_ = MediaCodecBridge::CreateVideoMediaCodecBridge(
       video_codec, width, height, fps, this, j_output_surface, j_media_crypto,
-      color_metadata, require_software_codec, tunnel_mode_audio_session_id,
-      force_big_endian_hdr_metadata, force_improved_support_check,
-      error_message);
+      color_metadata, require_secured_decoder, require_software_codec,
+      tunnel_mode_audio_session_id, force_big_endian_hdr_metadata,
+      force_improved_support_check, error_message);
   if (!media_codec_bridge_) {
     SB_LOG(ERROR) << "Failed to create video media codec bridge with error: "
                   << *error_message;
diff --git a/starboard/android/shared/media_is_supported.cc b/starboard/android/shared/media_is_supported.cc
index fcd6806..c27e0d2 100644
--- a/starboard/android/shared/media_is_supported.cc
+++ b/starboard/android/shared/media_is_supported.cc
@@ -26,6 +26,7 @@
                         SbMediaAudioCodec audio_codec,
                         const char* key_system) {
   using starboard::android::shared::IsWidevineL1;
+  using starboard::android::shared::IsWidevineL3;
   using starboard::android::shared::MediaCapabilitiesCache;
   using starboard::shared::starboard::media::MimeType;
 
@@ -48,7 +49,7 @@
     return false;
   }
   const char* key_system_type = mime_type.subtype().c_str();
-  if (!IsWidevineL1(key_system_type)) {
+  if (!IsWidevineL1(key_system_type) && !IsWidevineL3(key_system_type)) {
     return false;
   }
 
diff --git a/starboard/android/shared/player_components_factory.cc b/starboard/android/shared/player_components_factory.cc
index 01dc7a6..c2761f0 100644
--- a/starboard/android/shared/player_components_factory.cc
+++ b/starboard/android/shared/player_components_factory.cc
@@ -14,6 +14,8 @@
 
 #include "starboard/android/shared/player_components_factory.h"
 
+#include "starboard/android/shared/drm_system.h"
+
 namespace starboard {
 namespace shared {
 namespace starboard {
@@ -30,12 +32,18 @@
 bool VideoDecoder::OutputModeSupported(SbPlayerOutputMode output_mode,
                                        SbMediaVideoCodec codec,
                                        SbDrmSystem drm_system) {
+  using ::starboard::android::shared::DrmSystem;
+
   if (output_mode == kSbPlayerOutputModePunchOut) {
     return true;
   }
 
   if (output_mode == kSbPlayerOutputModeDecodeToTexture) {
-    return !SbDrmSystemIsValid(drm_system);
+    if (!SbDrmSystemIsValid(drm_system)) {
+      return true;
+    }
+    DrmSystem* android_drm_system = static_cast<DrmSystem*>(drm_system);
+    return !android_drm_system->require_secured_decoder();
   }
 
   return false;
diff --git a/starboard/android/shared/player_components_factory.h b/starboard/android/shared/player_components_factory.h
index 92a2b78..5271243 100644
--- a/starboard/android/shared/player_components_factory.h
+++ b/starboard/android/shared/player_components_factory.h
@@ -194,10 +194,6 @@
   const int kDefaultAudioSinkMaxCachedFrames =
       8 * kDefaultAudioSinkMinFramesPerAppend;
 
-  virtual SbDrmSystem GetExtendedDrmSystem(SbDrmSystem drm_system) {
-    return drm_system;
-  }
-
   static int AlignUp(int value, int alignment) {
     return (value + alignment - 1) / alignment * alignment;
   }
@@ -241,8 +237,7 @@
     scoped_ptr<AudioRendererPassthrough> audio_renderer;
     audio_renderer.reset(new AudioRendererPassthrough(
         creation_parameters.audio_sample_info(),
-        GetExtendedDrmSystem(creation_parameters.drm_system()),
-        enable_audio_device_callback));
+        creation_parameters.drm_system(), enable_audio_device_callback));
     if (!audio_renderer->is_valid()) {
       return scoped_ptr<PlayerComponents>();
     }
@@ -420,8 +415,7 @@
 
       audio_decoder->reset(new AdaptiveAudioDecoder(
           creation_parameters.audio_sample_info(),
-          GetExtendedDrmSystem(creation_parameters.drm_system()),
-          decoder_creator));
+          creation_parameters.drm_system(), decoder_creator));
 
       bool enable_audio_device_callback =
           audio_mime_type.GetParamBoolValue("enableaudiodevicecallback", true);
@@ -520,8 +514,7 @@
     }
 
     scoped_ptr<VideoDecoder> video_decoder(new VideoDecoder(
-        creation_parameters.video_codec(),
-        GetExtendedDrmSystem(creation_parameters.drm_system()),
+        creation_parameters.video_codec(), creation_parameters.drm_system(),
         creation_parameters.output_mode(),
         creation_parameters.decode_target_graphics_context_provider(),
         creation_parameters.max_video_capabilities(),
diff --git a/starboard/android/shared/video_decoder.cc b/starboard/android/shared/video_decoder.cc
index d1ce65f..2faa26b 100644
--- a/starboard/android/shared/video_decoder.cc
+++ b/starboard/android/shared/video_decoder.cc
@@ -254,8 +254,8 @@
     SB_DCHECK(tunnel_mode_audio_session_id != -1);
     SB_DCHECK(!drm_system_);
     drm_system_to_enforce_tunnel_mode_.reset(new DrmSystem(
-        nullptr, StubDrmSessionUpdateRequestFunc, StubDrmSessionUpdatedFunc,
-        StubDrmSessionKeyStatusesChangedFunc));
+        "com.youtube.widevine.l3", nullptr, StubDrmSessionUpdateRequestFunc,
+        StubDrmSessionUpdatedFunc, StubDrmSessionKeyStatusesChangedFunc));
     drm_system_ = drm_system_to_enforce_tunnel_mode_.get();
   }
 
diff --git a/starboard/build/config/base_configuration.gni b/starboard/build/config/base_configuration.gni
index 33b3763..ae010ae 100644
--- a/starboard/build/config/base_configuration.gni
+++ b/starboard/build/config/base_configuration.gni
@@ -21,11 +21,11 @@
 # Please follow the formatting in this file when adding new ones.
 
 declare_args() {
-  # Enables the yasm compiler to be used to compile .asm files.
-  yasm_exists = false
+  # Enables the nasm compiler to be used to compile .asm files.
+  nasm_exists = false
 
   # Where yasm can be found on the host device.
-  path_to_yasm = "yasm"
+  path_to_nasm = "nasm"
 
   # The Starboard API version of the current build configuration. The default
   # value is meant to be overridden by a Starboard ABI file.
diff --git a/starboard/build/nasm_assemble.gni b/starboard/build/nasm_assemble.gni
index 9b23cd9..737db6c 100644
--- a/starboard/build/nasm_assemble.gni
+++ b/starboard/build/nasm_assemble.gni
@@ -69,7 +69,7 @@
                            ])
 
     # Flags.
-    args = [ "$path_to_yasm" ]
+    args = [ "$path_to_nasm" ]
     args += _nasm_flags
     if (defined(invoker.nasm_flags)) {
       args += invoker.nasm_flags
@@ -105,9 +105,7 @@
     }
 
     # Output file.
-    outputs = [
-      "$root_out_dir/obj/third_party/libjpeg-turbo/{{source_name_part}}.asm.o",
-    ]
+    outputs = [ "$target_out_dir/$source_set_name/{{source_name_part}}.asm.o" ]
     args += [
       "-o",
       rebase_path(outputs[0], root_build_dir),
diff --git a/starboard/evergreen/shared/platform_configuration/configuration.gni b/starboard/evergreen/shared/platform_configuration/configuration.gni
index 5b331dc..a2b90ac 100644
--- a/starboard/evergreen/shared/platform_configuration/configuration.gni
+++ b/starboard/evergreen/shared/platform_configuration/configuration.gni
@@ -18,7 +18,7 @@
 
 cobalt_font_package = "empty"
 
-yasm_exists = true
+nasm_exists = true
 
 # Override that omits the "data" subdirectory.
 # TODO: Remove when omitted for all platforms in base_configuration.gni.
diff --git a/starboard/linux/shared/cobalt/configuration.py b/starboard/linux/shared/cobalt/configuration.py
index 62d27f5..f415970 100644
--- a/starboard/linux/shared/cobalt/configuration.py
+++ b/starboard/linux/shared/cobalt/configuration.py
@@ -17,6 +17,8 @@
 from starboard.tools.testing import test_filter
 
 _FILTERED_TESTS = {
+    # Tracked by b/185820828
+    'net_unittests': ['SpdyNetworkTransactionTest.SpdyBasicAuth',],
     'base_unittests': [
         # Fails when run in a sharded configuration: b/233108722, b/216774170
         'TaskQueueSelectorTest.TestHighestPriority',
diff --git a/starboard/linux/shared/platform_configuration/configuration.gni b/starboard/linux/shared/platform_configuration/configuration.gni
index ef6eba3..e98ebe7 100644
--- a/starboard/linux/shared/platform_configuration/configuration.gni
+++ b/starboard/linux/shared/platform_configuration/configuration.gni
@@ -14,7 +14,7 @@
 
 import("//starboard/build/config/base_configuration.gni")
 
-yasm_exists = true
+nasm_exists = true
 
 sb_static_contents_output_data_dir = "$root_out_dir/content"
 
diff --git a/starboard/linux/x64x11/clang/3.9/platform_configuration/configuration.gni b/starboard/linux/x64x11/clang/3.9/platform_configuration/configuration.gni
index e3b7857..188d57d 100644
--- a/starboard/linux/x64x11/clang/3.9/platform_configuration/configuration.gni
+++ b/starboard/linux/x64x11/clang/3.9/platform_configuration/configuration.gni
@@ -14,7 +14,7 @@
 
 import("//starboard/build/config/base_configuration.gni")
 
-yasm_exists = true
+nasm_exists = true
 
 sb_static_contents_output_data_dir = "$root_out_dir/content"
 
diff --git a/starboard/raspi/shared/launcher.py b/starboard/raspi/shared/launcher.py
index ba6605b..844e9f9 100644
--- a/starboard/raspi/shared/launcher.py
+++ b/starboard/raspi/shared/launcher.py
@@ -23,13 +23,15 @@
 import sys
 import threading
 import time
+import contextlib
 
 import pexpect
 from starboard.tools import abstract_launcher
+from starboard.raspi.shared import retry
 
 
 # pylint: disable=unused-argument
-def _SigIntOrSigTermHandler(signum, frame):
+def _sigint_or_sigterm_handler(signum, frame):
   """Clean up and exit with status |signum|.
 
   Args:
@@ -42,7 +44,7 @@
 
 
 # First call returns True, otherwise return false.
-def FirstRun():
+def first_run():
   v = globals()
   if 'first_run' not in v:
     v['first_run'] = False
@@ -64,16 +66,35 @@
   # pexpect times out each second to allow Kill to quickly stop a test run
   _PEXPECT_TIMEOUT = 1
 
-  # Wait up to 30 seconds for the password prompt from the raspi
-  _PEXPECT_PASSWORD_TIMEOUT_MAX_RETRIES = 30
+  # SSH shell command retries
+  _PEXPECT_SPAWN_RETRIES = 20
+
+  # pexpect.sendline retries
+  _PEXPECT_SENDLINE_RETRIES = 3
+
+  # Old process kill retries
+  _KILL_RETRIES = 3
+
+  _PEXPECT_SHUTDOWN_SLEEP_TIME = 3
+  # Time to wait after processes were killed
+  _PROCESS_KILL_SLEEP_TIME = 10
+
+  # Retrys for getting a clean prompt
+  _PROMPT_WAIT_MAX_RETRIES = 5
+  # Wait up to 10 seconds for the password prompt from the raspi
+  _PEXPECT_PASSWORD_TIMEOUT_MAX_RETRIES = 10
   # Wait up to 900 seconds for new output from the raspi
   _PEXPECT_READLINE_TIMEOUT_MAX_RETRIES = 900
   # Delay between subsequent SSH commands
-  _INTER_COMMAND_DELAY_SECONDS = 0.5
+  _INTER_COMMAND_DELAY_SECONDS = 1.5
 
   # This is used to strip ansi color codes from pexpect output.
   _PEXPECT_SANITIZE_LINE_RE = re.compile(r'\x1b[^m]*m')
 
+  # Exceptions to retry
+  _RETRY_EXCEPTIONS = (pexpect.TIMEOUT, pexpect.ExceptionPexpect,
+                       pexpect.exceptions.EOF, OSError)
+
   def __init__(self, platform, target_name, config, device_id, **kwargs):
     # pylint: disable=super-with-arguments
     super(Launcher, self).__init__(platform, target_name, config, device_id,
@@ -101,14 +122,17 @@
 
     self.log_targets = kwargs.get('log_targets', True)
 
-    signal.signal(signal.SIGINT, functools.partial(_SigIntOrSigTermHandler))
-    signal.signal(signal.SIGTERM, functools.partial(_SigIntOrSigTermHandler))
+    signal.signal(signal.SIGINT, functools.partial(_sigint_or_sigterm_handler))
+    signal.signal(signal.SIGTERM, functools.partial(_sigint_or_sigterm_handler))
 
     self.last_run_pexpect_cmd = ''
 
   def _InitPexpectCommands(self):
     """Initializes all of the pexpect commands needed for running the test."""
 
+    # Ensure no trailing slashes
+    self.out_directory = self.out_directory.rstrip('/')
+
     # TODO(b/218889313): This should reference the bin/ subdir when that's
     # used.
     test_dir = os.path.join(self.out_directory, 'install', self.target_name)
@@ -155,6 +179,18 @@
     self.test_command = (f'{test_base_command} {test_success_output}'
                          f'{test_failure_output}')
 
+  # pylint: disable=no-method-argument
+  def _CommandBackoff():
+    time.sleep(Launcher._INTER_COMMAND_DELAY_SECONDS)
+
+  def _ShutdownBackoff(self):
+    Launcher._CommandBackoff()
+    return self.shutdown_initiated.is_set()
+
+  @retry.retry(
+      exceptions=_RETRY_EXCEPTIONS,
+      retries=_PEXPECT_SPAWN_RETRIES,
+      backoff=_CommandBackoff)
   def _PexpectSpawnAndConnect(self, command):
     """Spawns a process with pexpect and connect to the raspi.
 
@@ -168,70 +204,67 @@
         command, timeout=Launcher._PEXPECT_TIMEOUT, **kwargs)
     # Let pexpect output directly to our output stream
     self.pexpect_process.logfile_read = self.output_file
-    retry_count = 0
     expected_prompts = [
         r'.*Are\syou\ssure.*',  # Fingerprint verification
         r'.* password:',  # Password prompt
         '.*[a-zA-Z]+.*',  # Any other text input
     ]
-    while True:
-      try:
-        i = self.pexpect_process.expect(expected_prompts)
-        if i == 0:
-          self._PexpectSendLine('yes')
-        elif i == 1:
-          self._PexpectSendLine(Launcher._RASPI_PASSWORD)
-          break
-        else:
-          # If any other input comes in, maybe we've logged in with rsa key or
-          # raspi does not have password. Check if we've logged in by echoing
-          # a special sentence and expect it back.
-          self._PexpectSendLine('echo ' + Launcher._SSH_LOGIN_SIGNAL)
-          i = self.pexpect_process.expect([Launcher._SSH_LOGIN_SIGNAL])
-          break
-      except pexpect.TIMEOUT:
-        if self.shutdown_initiated.is_set():
-          return
-        retry_count += 1
-        # Check if the max retry count has been exceeded. If it has, then
-        # re-raise the timeout exception.
-        if retry_count > Launcher._PEXPECT_PASSWORD_TIMEOUT_MAX_RETRIES:
-          raise
 
+    # pylint: disable=unnecessary-lambda
+    @retry.retry(
+        exceptions=Launcher._RETRY_EXCEPTIONS,
+        retries=Launcher._PEXPECT_PASSWORD_TIMEOUT_MAX_RETRIES,
+        backoff=lambda: self._ShutdownBackoff(),
+        wrap_exceptions=False)
+    def _inner():
+      i = self.pexpect_process.expect(expected_prompts)
+      if i == 0:
+        self._PexpectSendLine('yes')
+      elif i == 1:
+        self._PexpectSendLine(Launcher._RASPI_PASSWORD)
+      else:
+        # If any other input comes in, maybe we've logged in with rsa key or
+        # raspi does not have password. Check if we've logged in by echoing
+        # a special sentence and expect it back.
+        self._PexpectSendLine('echo ' + Launcher._SSH_LOGIN_SIGNAL)
+        i = self.pexpect_process.expect([Launcher._SSH_LOGIN_SIGNAL])
+
+    _inner()
+
+  @retry.retry(
+      exceptions=_RETRY_EXCEPTIONS,
+      retries=_PEXPECT_SENDLINE_RETRIES,
+      wrap_exceptions=False)
   def _PexpectSendLine(self, cmd):
     """Send lines to Pexpect and record the last command for logging purposes"""
+    logging.info('sending >> : %s ', cmd)
     self.last_run_pexpect_cmd = cmd
     self.pexpect_process.sendline(cmd)
 
   def _PexpectReadLines(self):
     """Reads all lines from the pexpect process."""
-
-    retry_count = 0
-    while True:
-      try:
+    # pylint: disable=unnecessary-lambda
+    @retry.retry(
+        exceptions=Launcher._RETRY_EXCEPTIONS,
+        retries=Launcher._PEXPECT_READLINE_TIMEOUT_MAX_RETRIES,
+        backoff=lambda: self.shutdown_initiated.is_set(),
+        wrap_exceptions=False)
+    def _readloop():
+      while True:
         # Sanitize the line to remove ansi color codes.
         line = Launcher._PEXPECT_SANITIZE_LINE_RE.sub(
             '', self.pexpect_process.readline())
         self.output_file.flush()
         if not line:
-          break
+          return
         # Check for the test complete tag. It will be followed by either a
         # success or failure tag.
         if line.startswith(self.test_complete_tag):
           if line.find(self.test_success_tag) != -1:
             self.return_value = 0
-          break
-        # A line was successfully read without timing out; reset the retry
-        # count before attempting to read the next line.
-        retry_count = 0
-      except pexpect.TIMEOUT:
-        if self.shutdown_initiated.is_set():
           return
-        retry_count += 1
-        # Check if the max retry count has been exceeded. If it has, then
-        # re-raise the timeout exception.
-        if retry_count > Launcher._PEXPECT_READLINE_TIMEOUT_MAX_RETRIES:
-          raise
+
+    _readloop()
 
   def _Sleep(self, val):
     self._PexpectSendLine(f'sleep {val};echo {Launcher._SSH_SLEEP_SIGNAL}')
@@ -244,39 +277,38 @@
       # Check if kernel logged OOM kill or any other system failure message
       if self.return_value:
         logging.info('Sending dmesg')
-        self._PexpectSendLine('dmesg -P --color=never | tail -n 100')
-        time.sleep(3)
-        try:
+        with contextlib.suppress(Launcher._RETRY_EXCEPTIONS):
+          self._PexpectSendLine('dmesg -P --color=never | tail -n 100')
+        time.sleep(self._PEXPECT_SHUTDOWN_SLEEP_TIME)
+        with contextlib.suppress(Launcher._RETRY_EXCEPTIONS):
           self.pexpect_process.readlines()
-        except pexpect.TIMEOUT:
-          logging.info('Timeout exception during cleanup command: %s',
-                       self.last_run_pexpect_cmd)
-          pass
         logging.info('Done sending dmesg')
 
       # Send ctrl-c to the raspi and close the process.
-      self._PexpectSendLine(chr(3))
-      time.sleep(1)  # Allow a second for normal shutdown
-      self.pexpect_process.close()
+      with contextlib.suppress(Launcher._RETRY_EXCEPTIONS):
+        self._PexpectSendLine(chr(3))
+      time.sleep(self._PEXPECT_TIMEOUT)  # Allow time for normal shutdown
+      with contextlib.suppress(Launcher._RETRY_EXCEPTIONS):
+        self.pexpect_process.close()
 
   def _WaitForPrompt(self):
     """Sends empty commands, until a bash prompt is returned"""
-    retry_count = 5
-    while True:
-      try:
-        self.pexpect_process.expect(self._RASPI_PROMPT)
-        break
-      except pexpect.TIMEOUT:
-        logging.info('Timeout exception during WaitForPrompt command: %s',
-                     self.last_run_pexpect_cmd)
-        if self.shutdown_initiated.is_set():
-          return
-        retry_count -= 1
-        if not retry_count:
-          raise
-        self._PexpectSendLine('echo ' + Launcher._SSH_SLEEP_SIGNAL)
-        time.sleep(self._INTER_COMMAND_DELAY_SECONDS)
 
+    def backoff():
+      self._PexpectSendLine('echo ' + Launcher._SSH_SLEEP_SIGNAL)
+      return self._ShutdownBackoff()
+
+    retry.with_retry(
+        lambda: self.pexpect_process.expect(self._RASPI_PROMPT),
+        exceptions=Launcher._RETRY_EXCEPTIONS,
+        retries=Launcher._PROMPT_WAIT_MAX_RETRIES,
+        backoff=backoff,
+        wrap_exceptions=False)
+
+  @retry.retry(
+      exceptions=_RETRY_EXCEPTIONS,
+      retries=_KILL_RETRIES,
+      backoff=_CommandBackoff)
   def _KillExistingCobaltProcesses(self):
     """If there are leftover Cobalt processes, kill them.
 
@@ -295,7 +327,7 @@
       logging.warning('Forced to pkill existing instance(s) of cobalt. '
                       'Pausing to ensure no further operations are run '
                       'before processes shut down.')
-      time.sleep(10)
+      time.sleep(Launcher._PROCESS_KILL_SLEEP_TIME)
     logging.info('Done killing existing processes')
 
   def Run(self):
@@ -331,12 +363,19 @@
       if self.test_result_xml_path:
         first_run_commands.append(f'touch {self.test_result_xml_path}')
       first_run_commands.extend(['free -mh', 'ps -ux', 'df -h'])
-      if FirstRun():
+      if first_run():
         for cmd in first_run_commands:
           if not self.shutdown_initiated.is_set():
             self._PexpectSendLine(cmd)
-            line = self.pexpect_process.readline()
-            self.output_file.write(line)
+
+            def _readline():
+              line = self.pexpect_process.readline()
+              self.output_file.write(line)
+
+            retry.with_retry(
+                _readline,
+                exceptions=Launcher._RETRY_EXCEPTIONS,
+                retries=Launcher._PROMPT_WAIT_MAX_RETRIES)
         self._WaitForPrompt()
         self.output_file.flush()
         self._Sleep(self._INTER_COMMAND_DELAY_SECONDS)
@@ -347,6 +386,9 @@
         self._PexpectSendLine(self.test_command)
         self._PexpectReadLines()
 
+    except retry.RetriesExceeded:
+      logging.exception('Command retry exceeded (cmd: %s)',
+                        self.last_run_pexpect_cmd)
     except pexpect.EOF:
       logging.exception('pexpect encountered EOF while reading line. (cmd: %s)',
                         self.last_run_pexpect_cmd)
@@ -378,7 +420,7 @@
     # Initiate the shutdown. This causes the run to abort within one second.
     self.shutdown_initiated.set()
     # Wait up to three seconds for the run to be set to inactive.
-    self.run_inactive.wait(3)
+    self.run_inactive.wait(Launcher._PEXPECT_SHUTDOWN_SLEEP_TIME)
 
   def GetDeviceIp(self):
     """Gets the device IP."""
diff --git a/starboard/raspi/shared/launcher_test.py b/starboard/raspi/shared/launcher_test.py
new file mode 100644
index 0000000..5fe6794
--- /dev/null
+++ b/starboard/raspi/shared/launcher_test.py
@@ -0,0 +1,208 @@
+#
+# 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.
+"""Tests for Raspi launcher"""
+
+import logging
+from starboard.raspi.shared import launcher
+import sys
+import argparse
+import unittest
+import os
+from unittest.mock import patch, ANY, call, Mock
+import tempfile
+from pathlib import Path
+import pexpect
+
+# pylint: disable=missing-class-docstring
+
+
+class LauncherTest(unittest.TestCase):
+
+  def setUp(self):
+    self.target = 'baz'
+    self.device_id = '198.51.100.1'  # Reserved address
+    # Current launcher requires real files, so we generate one
+    # pylint: disable=consider-using-with
+    self.tmpdir = tempfile.TemporaryDirectory()
+    target_path = os.path.join(self.tmpdir.name, 'install', self.target)
+    os.makedirs(target_path)
+    Path(os.path.join(target_path, self.target)).touch()
+    # Minimal set of params required to crete one
+    self.params = {
+        'device_id': self.device_id,
+        'platform': 'raspi-2',
+        'target_name': self.target,
+        'config': 'test',
+        'out_directory': self.tmpdir.name
+    }
+    self.fake_timeout = 0.11
+
+  # pylint: disable=protected-access
+  def _make_launcher(self):
+    launcher.Launcher._PEXPECT_TIMEOUT = self.fake_timeout
+    launcher.Launcher._PEXPECT_PASSWORD_TIMEOUT_MAX_RETRIES = 0
+    launcher.Launcher._PEXPECT_SHUTDOWN_SLEEP_TIME = 0.12
+    launcher.Launcher._INTER_COMMAND_DELAY_SECONDS = 0.013
+    launcher.Launcher._PEXPECT_READLINE_TIMEOUT_MAX_RETRIES = 2
+    launch = launcher.Launcher(**self.params)
+    return launch
+
+
+class LauncherAPITest(LauncherTest):
+
+  def test_construct(self):
+    launch = self._make_launcher()
+    self.assertIsNotNone(launch)
+    self.assertEqual(launch.device_id, self.device_id)
+    self.assertEqual(launch.platform_name, 'raspi-2')
+    self.assertEqual(launch.target_name, self.target)
+    self.assertEqual(launch.config, 'test')
+    self.assertEqual(launch.out_directory, self.tmpdir.name)
+
+  def test_run(self):
+    result = self._make_launcher().Run()
+    # Expect test failure
+    self.assertEqual(result, 1)
+
+  def test_ip(self):
+    self.assertEqual(self._make_launcher().GetDeviceIp(), self.device_id)
+
+  def test_output(self):
+    # The path is hardcoded in the launcher
+    self.assertEqual(self._make_launcher().GetDeviceOutputPath(), '/tmp')
+
+  def test_kill(self):
+    self.assertIsNone(self._make_launcher().Kill())
+
+
+class StringContains(str):
+
+  def __eq__(self, value):
+    return self in value
+
+
+# Tests here test implementation details, rather than behavior.
+# pylint: disable=protected-access
+class LauncherInternalsTest(LauncherTest):
+
+  def setUp(self):
+    super().setUp()
+    self.launch = self._make_launcher()
+    self.launch.pexpect_process = Mock(
+        spec_set=['expect', 'sendline', 'readline'])
+
+  @patch('starboard.raspi.shared.launcher.pexpect.spawn')
+  def test_spawn(self, spawn):
+    mock_pexpect = spawn.return_value
+    self.launch._PexpectSpawnAndConnect('echo test')
+    spawn.assert_called_once_with('echo test', timeout=ANY, encoding=ANY)
+    mock_pexpect.sendline.assert_called_once_with(
+        'echo cobalt-launcher-login-success')
+    mock_pexpect.expect.assert_any_call(['cobalt-launcher-login-success'])
+
+  def test_sleep(self):
+    self.launch._Sleep(42)
+    self.launch.pexpect_process.sendline.assert_called_once_with(
+        'sleep 42;echo cobalt-launcher-done-sleeping')
+    self.launch.pexpect_process.expect.assert_called_once_with(
+        ['cobalt-launcher-done-sleeping'])
+
+  def test_waitforconnect(self):
+    self.launch._WaitForPrompt()
+    self.launch.pexpect_process.expect.assert_called_once_with(
+        'pi@raspberrypi:')
+
+    # trigger one timeout
+    self.launch.pexpect_process.expect = Mock(
+        side_effect=[pexpect.TIMEOUT(1), None])
+    self.launch._WaitForPrompt()
+    self.launch.pexpect_process.expect.assert_has_calls([
+        call('pi@raspberrypi:'),
+        call('pi@raspberrypi:'),
+    ])
+
+    # infinite timeout
+    self.launch.pexpect_process.expect = Mock(side_effect=pexpect.TIMEOUT(1))
+    with self.assertRaises(pexpect.TIMEOUT):
+      self.launch._WaitForPrompt()
+
+  def test_readlines(self):
+    # Return empty string
+    self.launch.pexpect_process.readline = Mock(return_value='')
+    self.launch._PexpectReadLines()
+    self.launch.pexpect_process.readline.assert_called_once()
+    self.assertIsNone(getattr(self.launch, 'return_value', None))
+
+    # Return default success tag
+    self.launch.pexpect_process.readline = Mock(
+        return_value=self.launch.test_complete_tag)
+    self.launch._PexpectReadLines()
+    self.launch.pexpect_process.readline.assert_called_once()
+    # This is a bug
+    self.assertIsNone(getattr(self.launch, 'return_value', None))
+
+    line = self.launch.test_complete_tag + self.launch.test_success_tag
+    self.launch.pexpect_process.readline = Mock(return_value=line)
+    self.launch._PexpectReadLines()
+    self.assertEqual(self.launch.return_value, 0)
+
+    self.launch.pexpect_process.readline = Mock(side_effect=pexpect.TIMEOUT(1))
+    with self.assertRaises(pexpect.TIMEOUT):
+      self.launch._PexpectReadLines()
+
+  def test_readlines_multiple(self):
+    self.launch.pexpect_process.readline = Mock(side_effect=['abc', 'bbc', ''])
+    self.launch._PexpectReadLines()
+    self.assertEqual(3, self.launch.pexpect_process.readline.call_count)
+
+    self.launch.pexpect_process.readline = Mock(
+        side_effect=['abc', 'bbc', '', 'none'])
+    self.launch._PexpectReadLines()
+    self.assertEqual(3, self.launch.pexpect_process.readline.call_count)
+
+  def test_kill_processes(self):
+    self.launch._KillExistingCobaltProcesses()
+    self.launch.pexpect_process.sendline.assert_any_call(
+        StringContains('pkill'))
+
+  @patch('starboard.raspi.shared.launcher.pexpect.spawn')
+  def test_run_with_mock(self, spawn):
+    pexpect_ = Mock()
+    pexpect_.readline = Mock(return_value='')
+    spawn.return_value = pexpect_
+    self.launch.Run()
+    self.assertEqual(self.launch.return_value, 1)
+
+
+if __name__ == '__main__':
+  parser = argparse.ArgumentParser()
+  parser.add_argument('device_id')
+  parser.add_argument('--target', default='eztime_test')
+  parser.add_argument('--out_directory')
+  parser.add_argument('--config', default='devel')
+  parser.add_argument('--verbose', '-v', action='store_true')
+  args = parser.parse_args()
+  logging.basicConfig(
+      stream=sys.stdout, level=logging.DEBUG if args.verbose else logging.INFO)
+  path = os.path.join(
+      os.path.dirname(launcher.__file__), f'../../../out/raspi-2_{args.config}')
+  logging.info('path: %s', path)
+  launch_test = launcher.Launcher(
+      platform='raspi-2',
+      target_name=args.target,
+      config=args.config,
+      device_id=args.device_id,
+      out_directory=path)
+  launch_test.Run()
diff --git a/starboard/raspi/shared/retry.py b/starboard/raspi/shared/retry.py
new file mode 100644
index 0000000..1e8fd0e
--- /dev/null
+++ b/starboard/raspi/shared/retry.py
@@ -0,0 +1,121 @@
+#
+# 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.
+"""General retry wrapper module
+
+Allows retrying a function call either with a decorator or inline call.
+This is a substitute for more comprehensive Python retry wrapper packages like
+`tenacity`, `retry`, `backoff` and others.
+The only reason this exists is that Python package deployment for on-device
+tests cannot currently dynamically include dependencies.
+TODO(b/279249837): Remove this and use an off the shelf package.
+"""
+
+from typing import Sequence, Callable
+import functools
+import logging
+
+
+class RetriesExceeded(RuntimeError):
+  """Exception recording retry failure conditions"""
+
+  def __init__(self, retries: int, function: Callable, *args, **kwargs) -> None:
+    super().__init__(*args, **kwargs)
+    self.retries = retries
+    self.function = function
+
+  def __str__(self) -> str:
+    callable_str = getattr(self.function, '__name__', repr(self.function))
+    return (f'Retries exceeded while calling {callable_str}'
+            f' with max {self.retries}') + super().__str__()
+
+
+def _retry_function(function: Callable, exceptions: Sequence, retries: int,
+                    backoff: Callable, wrap_exceptions: bool):
+  current_retry = 0
+  while current_retry <= retries:
+    try:
+      return function()
+    except exceptions as inner:
+      current_retry += 1
+      logging.debug('Exception running %s, retry %d/%d', function, retry,
+                    retries)
+      if current_retry > retries:
+        # If 0 retries were attempted, pass up original exception
+        if not retries or not wrap_exceptions:
+          raise
+        raise RetriesExceeded(retries, function) from inner
+      if backoff:
+        if backoff():
+          raise StopIteration() from inner
+
+  raise RuntimeError('Bug: we should never get here')
+
+
+def with_retry(function: Callable,
+               args: tuple = (),
+               kwargs: dict = None,
+               exceptions: Sequence = (Exception,),
+               retries: int = 0,
+               backoff: Callable = None,
+               wrap_exceptions: bool = True):
+  """Call a function with retry on exception
+
+    :param args: Called function positional args.
+    :param kwargs: Called function named args.
+    :param exceptions: Sequence of exception types that will be retried.
+    :param retries: Max retries attempted.
+    :param backoff: Optional backoff callable. Truthy return from callable
+        terminates the loop.
+    :param wrap_exceptions: If true ( default ) wrap underlying exceptions in
+        RetriesExceeded exception type
+    :
+  """
+  return _retry_function(
+      functools.partial(function, *args, **(kwargs if kwargs else {})),
+      exceptions=exceptions,
+      retries=retries,
+      backoff=backoff,
+      wrap_exceptions=wrap_exceptions,
+  )
+
+
+def retry(exceptions: Sequence = (Exception,),
+          retries: int = 0,
+          backoff: Callable = None,
+          wrap_exceptions: bool = True):
+  """Decorator for self-retrying function on thrown exception
+
+    :param exceptions: Sequence of exception types that will be retried.
+    :param retries: Max retries attempted.
+    :param backoff: Optional backoff callable. Truthy return from callable
+        terminates the loop.
+    :param wrap_exceptions: If true ( default ) wrap underlying exceptions in
+        RetriesExceeded exception type
+    """
+
+  def decorator(function):
+
+    @functools.wraps(function)
+    def wrapper(*args, **kwargs):
+      return _retry_function(
+          functools.partial(function, *args, **kwargs),
+          exceptions=exceptions,
+          retries=retries,
+          backoff=backoff,
+          wrap_exceptions=wrap_exceptions)
+
+    return wrapper
+
+  return decorator
diff --git a/starboard/raspi/shared/retry_test.py b/starboard/raspi/shared/retry_test.py
new file mode 100644
index 0000000..465edda
--- /dev/null
+++ b/starboard/raspi/shared/retry_test.py
@@ -0,0 +1,257 @@
+#
+# 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.
+"""Tests for `retry` module"""
+
+import unittest
+from starboard.raspi.shared import retry
+import argparse
+import logging
+import sys
+import time
+from enum import IntEnum
+
+
+class Behavior(IntEnum):
+  OK = 0
+  OS_ERROR = 1
+  RUNTIME_ERROR = 2
+  OTHER_ERROR = 3
+
+
+def _problem(param: int, caller=str):
+  logging.info('%s: param=%d', caller, param)
+  if param == Behavior.OS_ERROR:
+    raise OSError('OS made an oops')
+  if param == Behavior.RUNTIME_ERROR:
+    raise RuntimeError('Runtime oops')
+  if param == Behavior.OTHER_ERROR:
+    raise MemoryError('Download more RAM')
+  return 100 + param * 3
+
+
+def problem(param: int):
+  return _problem(param, 'undecorated problem')
+
+
+@retry.retry(exceptions=(RuntimeError,), retries=1)
+def decorated_runtimeerror(param: int):
+  return _problem(param, 'decorated with runtimeerror')
+
+
+@retry.retry(exceptions=(OSError,), retries=1)
+def decorated_oserror(param: int):
+  return _problem(param, 'decorated with oserror')
+
+
+@retry.retry(exceptions=(OSError, RuntimeError), retries=1)
+def decorated_both(param: int):
+  return _problem(param, 'decorated with oserror+runtimeerror')
+
+
+@retry.retry(
+    exceptions=(OSError,),
+    retries=2,
+    backoff=lambda: (logging.info('sleeping 0.2'), time.sleep(0.2)))
+def decorated_oserror_backoff_2(param: int):
+  return _problem(param, 'decorated with oserror, 2 retries and sleep backoff')
+
+
+class RetryTest(unittest.TestCase):
+
+  def setUp(self) -> None:
+    self.actual_calls = 0
+    self.call_counter = 0
+    return super().setUp()
+
+  def problem(self, param):
+    self.actual_calls += 1
+    return _problem(param, 'undecorated problem method')
+
+  @retry.retry(exceptions=(OSError,), retries=1)
+  def decorated_os_problem(self, param):
+    self.actual_calls += 1
+    return _problem(param, 'decorated problem method')
+
+  @retry.retry(exceptions=(OSError,), retries=1, wrap_exceptions=False)
+  def decorated_os_problem_nowrap(self, param):
+    self.actual_calls += 1
+    return _problem(param, 'decorated problem method, pass-through exceptions')
+
+  @retry.retry(exceptions=(OSError,), retries=5)
+  def decorated_os_problem_3(self, param):
+    self.actual_calls += 1
+    self.call_counter += 1
+    if self.call_counter == 3:
+      return 200
+    return _problem(param, 'decorated problem that succeeds on 3rd try')
+
+  def test_ok_call_undecorated(self):
+    self.assertEqual(100, retry.with_retry(problem, (Behavior.OK,)))
+    self.assertEqual(100, retry.with_retry(self.problem, (Behavior.OK,)))
+    self.assertEqual(self.actual_calls, 1)
+
+  def test_ok_call_decorated(self):
+    self.assertEqual(100, decorated_both(Behavior.OK))
+    self.assertEqual(100, self.decorated_os_problem(Behavior.OK))
+    self.assertEqual(self.actual_calls, 1)
+
+  def test_retry_exceeds(self):
+    with self.assertRaises(OSError):
+      retry.with_retry(problem, (Behavior.OS_ERROR,), retries=0)
+    with self.assertRaises(retry.RetriesExceeded):
+      retry.with_retry(problem, (Behavior.OS_ERROR,), retries=1)
+    with self.assertRaises(retry.RetriesExceeded):
+      retry.with_retry(problem, (Behavior.OS_ERROR,), retries=50)
+    with self.assertRaises(retry.RetriesExceeded):
+      retry.with_retry(self.problem, (Behavior.OS_ERROR,), retries=1)
+    self.assertEqual(self.actual_calls, 2)
+
+  def test_retry_exceeds_decorated(self):
+    with self.assertRaises(retry.RetriesExceeded):
+      decorated_oserror(Behavior.OS_ERROR)
+    with self.assertRaises(retry.RetriesExceeded):
+      self.decorated_os_problem(Behavior.OS_ERROR)
+    self.assertEqual(self.actual_calls, 2)
+    with self.assertRaises(retry.RetriesExceeded):
+      decorated_runtimeerror(Behavior.RUNTIME_ERROR)
+
+  def test_other_exceptions_propagate(self):
+    with self.assertRaises(RuntimeError):
+      retry.with_retry(
+          problem, (Behavior.RUNTIME_ERROR,),
+          exceptions=(OSError, MemoryError),
+          retries=0)
+    with self.assertRaises(RuntimeError):
+      retry.with_retry(
+          problem, (Behavior.RUNTIME_ERROR,),
+          exceptions=(OSError, MemoryError),
+          retries=4)
+    with self.assertRaises(RuntimeError):
+      retry.with_retry(
+          self.problem, (Behavior.RUNTIME_ERROR,),
+          exceptions=(OSError, MemoryError),
+          retries=0)
+    self.assertEqual(self.actual_calls, 1)
+    with self.assertRaises(RuntimeError):
+      retry.with_retry(
+          self.problem, (Behavior.RUNTIME_ERROR,),
+          exceptions=(OSError, MemoryError),
+          retries=50)
+    self.assertEqual(self.actual_calls, 2)
+
+  def test_original_exceptions(self):
+    with self.assertRaises(OSError):
+      retry.with_retry(
+          problem, (Behavior.OS_ERROR,), retries=0, wrap_exceptions=False)
+    with self.assertRaises(OSError):
+      retry.with_retry(
+          problem, (Behavior.OS_ERROR,), retries=1, wrap_exceptions=False)
+    with self.assertRaises(OSError):
+      retry.with_retry(
+          problem, (Behavior.OS_ERROR,), retries=50, wrap_exceptions=False)
+    with self.assertRaises(OSError):
+      self.decorated_os_problem_nowrap(Behavior.OS_ERROR)
+
+  def test_call_can_succeed_1(self):
+    self.assertEqual(100, self.decorated_os_problem_3(Behavior.OK))
+    self.assertEqual(self.actual_calls, 1)
+
+  def test_call_can_succeed_2(self):
+    self.assertEqual(200, self.decorated_os_problem_3(Behavior.OS_ERROR))
+    self.assertEqual(self.actual_calls, 3)
+    with self.assertRaises(RuntimeError):  # ensure other errors still throw
+      self.decorated_os_problem_3(Behavior.RUNTIME_ERROR)
+
+  def test_backoff_gets_called(self):
+    backoff_calls = 0
+
+    def inrcement():
+      nonlocal backoff_calls
+      backoff_calls += 1
+
+    with self.assertRaises(RuntimeError):
+      retry.with_retry(
+          self.problem, (Behavior.RUNTIME_ERROR,),
+          exceptions=(OSError),
+          retries=2,
+          backoff=inrcement)
+    self.assertEqual(backoff_calls, 0)
+    with self.assertRaises(RuntimeError):
+      retry.with_retry(
+          self.problem, (Behavior.RUNTIME_ERROR,),
+          exceptions=(RuntimeError),
+          retries=2,
+          backoff=inrcement)
+    self.assertEqual(backoff_calls, 2)
+
+  def test_backoff_terminates_loop(self):
+    with self.assertRaises(StopIteration):
+      retry.with_retry(
+          self.problem, (Behavior.RUNTIME_ERROR,),
+          exceptions=(RuntimeError),
+          retries=2,
+          backoff=lambda: True)
+
+  def test_exception_has_details(self):
+    with self.assertRaises(retry.RetriesExceeded) as context:
+      retry.with_retry(problem, (Behavior.OS_ERROR,), retries=50)
+    self.assertIn('50', str(context.exception))
+    self.assertIn('problem', str(context.exception))
+
+
+if __name__ == '__main__':
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--verbose', '-v', action='store_true')
+  parser.add_argument('func_behavior', type=int)
+  parser.add_argument('--oserror', action='store_true')
+  parser.add_argument('--runtimeerror', action='store_true')
+  parser.add_argument('--retries', type=int, default=1)
+  parser.add_argument('--backoff', action='store_true')
+  parser.add_argument('--decorated', action='store_true')
+  args = parser.parse_args()
+  logging.basicConfig(
+      stream=sys.stdout, level=logging.DEBUG if args.verbose else logging.INFO)
+  exceptions = []
+  if args.oserror:
+    exceptions.append(OSError)
+  if args.runtimeerror:
+    exceptions.append(RuntimeError)
+  backoff = None if not args.backoff else lambda: (print('Backoff'),
+                                                   time.sleep(1))
+  if not args.decorated:
+    if exceptions:
+      print(
+          retry.with_retry(
+              problem, (args.func_behavior,),
+              retries=args.retries,
+              exceptions=tuple(exceptions),
+              backoff=backoff))
+    else:  # default, accept all exceptions
+      print(
+          retry.with_retry(
+              problem, (args.func_behavior,),
+              retries=args.retries,
+              backoff=backoff))
+  else:
+    if args.backoff:
+      print(decorated_oserror_backoff_2(args.func_behavior))
+    elif args.oserror and args.runtimeerror:
+      print(decorated_both(args.func_behavior))
+    elif args.oserror:
+      print(decorated_oserror(args.func_behavior))
+    elif args.runtimeerror:
+      print(decorated_runtimeerror(args.func_behavior))
+    else:
+      raise NotImplementedError('No test implemented for these args')
diff --git a/starboard/shared/crash/BUILD.gn b/starboard/shared/crash/BUILD.gn
new file mode 100644
index 0000000..fe4e454
--- /dev/null
+++ b/starboard/shared/crash/BUILD.gn
@@ -0,0 +1,20 @@
+# 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.
+
+import("//third_party/protobuf/proto_library.gni")
+
+proto_library("crash_annotations") {
+  sources = [ "crash_annotations.proto" ]
+  generate_python = false
+}
diff --git a/starboard/shared/crash/crash_annotations.proto b/starboard/shared/crash/crash_annotations.proto
new file mode 100644
index 0000000..6b4c2c6
--- /dev/null
+++ b/starboard/shared/crash/crash_annotations.proto
@@ -0,0 +1,24 @@
+// 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.
+
+syntax = "proto3";
+
+option optimize_for = LITE_RUNTIME;
+
+package starboard.shared.crash;
+
+message CrashAnnotations {
+  // Key/value pairs that should be included in a crash report.
+  map<string, string> runtime_annotations = 1;
+}
diff --git a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
index 4b21032..359a065 100644
--- a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
@@ -312,11 +312,11 @@
     return;
   }
 
-#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
-  av_frame_ = ffmpeg_->av_frame_alloc();
-#else   // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
-  av_frame_ = ffmpeg_->avcodec_alloc_frame();
-#endif  // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
+  if (ffmpeg_->avcodec_version() > kAVCodecSupportsAvFrameAlloc) {
+    av_frame_ = ffmpeg_->av_frame_alloc();
+  } else {
+    av_frame_ = ffmpeg_->avcodec_alloc_frame();
+  }
   if (av_frame_ == NULL) {
     SB_LOG(ERROR) << "Unable to allocate audio frame";
     TeardownCodec();
diff --git a/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc
index ef15ed2..0c78c4c 100644
--- a/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc
@@ -479,12 +479,13 @@
     return;
   }
   auto* packet = static_cast<AVPacket*>(ptr);
-#if LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100
-  GetDispatch()->av_packet_free(&packet);
-#else
-  GetDispatch()->av_free_packet(packet);
-  GetDispatch()->av_free(packet);
-#endif  // LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100
+
+  if (GetDispatch()->avcodec_version() > kAVCodecSupportsAvPacketAlloc) {
+    GetDispatch()->av_packet_free(&packet);
+  } else {
+    GetDispatch()->av_free_packet(packet);
+    GetDispatch()->av_free(packet);
+  }
 }
 
 FFmpegDemuxerImpl<FFMPEG>::FFmpegDemuxerImpl(
@@ -714,15 +715,15 @@
 FFmpegDemuxerImpl<FFMPEG>::CreateScopedAVPacket() {
   ScopedAVPacket packet;
 
-#if LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100
-  packet.reset(GetDispatch()->av_packet_alloc());
-#else
-  // av_packet_alloc is not available.
-  packet.reset(
-      static_cast<AVPacket*>(GetDispatch()->av_malloc(sizeof(AVPacket))));
-  memset(packet.get(), 0, sizeof(AVPacket));
-  GetDispatch()->av_init_packet(packet.get());
-#endif  // LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100
+  if (GetDispatch()->avcodec_version() > kAVCodecSupportsAvPacketAlloc) {
+    packet.reset(GetDispatch()->av_packet_alloc());
+  } else {
+    // av_packet_alloc is not available.
+    packet.reset(
+        static_cast<AVPacket*>(GetDispatch()->av_malloc(sizeof(AVPacket))));
+    memset(packet.get(), 0, sizeof(AVPacket));
+    GetDispatch()->av_init_packet(packet.get());
+  }
 
   return packet;
 }
@@ -775,13 +776,13 @@
       continue;
     }
 
-// This is a packet for a stream we don't care about. Unref it (clear the
-// fields) and keep searching.
-#if LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100
-    GetDispatch()->av_packet_unref(packet.get());
-#else
-    GetDispatch()->av_free_packet(packet.get());
-#endif  // LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100
+    // This is a packet for a stream we don't care about. Unref it (clear the
+    // fields) and keep searching.
+    if (GetDispatch()->avcodec_version() > kAVCodecSupportsAvPacketAlloc) {
+      GetDispatch()->av_packet_unref(packet.get());
+    } else {
+      GetDispatch()->av_free_packet(packet.get());
+    }
   }
 
   SB_NOTREACHED();
@@ -919,9 +920,9 @@
   const double aspect_ratio =
       video_stream->sample_aspect_ratio.num
           ? get_aspect_ratio(video_stream->sample_aspect_ratio)
-          : codec_context->sample_aspect_ratio.num
-                ? get_aspect_ratio(codec_context->sample_aspect_ratio)
-                : 0.0;
+      : codec_context->sample_aspect_ratio.num
+          ? get_aspect_ratio(codec_context->sample_aspect_ratio)
+          : 0.0;
   {
     double width = config->visible_rect_width;
     double height = config->visible_rect_height;
@@ -976,7 +977,7 @@
            codec_context->profile >
 #ifdef FF_PROFILE_HEVC_REXT
                FF_PROFILE_HEVC_REXT
-#else  // FF_PROFILE_HEVC_REXT
+#else   // FF_PROFILE_HEVC_REXT
                FF_PROFILE_HEVC_MAIN_STILL_PICTURE
 #endif  // FF_PROFILE_HEVC_REXT
            ) &&
diff --git a/starboard/shared/ffmpeg/ffmpeg_dispatch.cc b/starboard/shared/ffmpeg/ffmpeg_dispatch.cc
index 6344706..b858a1f 100644
--- a/starboard/shared/ffmpeg/ffmpeg_dispatch.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_dispatch.cc
@@ -17,6 +17,7 @@
 
 #include "starboard/shared/ffmpeg/ffmpeg_dispatch.h"
 
+#include "starboard/common/log.h"
 #include "starboard/common/mutex.h"
 #include "starboard/once.h"
 
@@ -43,22 +44,19 @@
 }
 
 void FFMPEGDispatch::FreeFrame(AVFrame** frame) {
-#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
-  av_frame_free(frame);
-#else   // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
-  av_freep(frame);
-#endif  // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
+  if (avcodec_version() > kAVCodecSupportsAvFrameAlloc) {
+    av_frame_free(frame);
+  } else {
+    av_freep(frame);
+  }
 }
 
 void FFMPEGDispatch::FreeContext(AVCodecContext** avctx) {
-#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
-  avcodec_free_context(avctx);
-#else   // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
-  if (avctx->extradata_size) {
-    av_freep(&((*avctx)->extradata));
+  if (avcodec_version() > kAVCodecSupportsAvcodecFreeContext) {
+    avcodec_free_context(avctx);
+  } else {
+    av_freep(avctx);
   }
-  av_freep(avctx);
-#endif  // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
 }
 
 }  // namespace ffmpeg
diff --git a/starboard/shared/ffmpeg/ffmpeg_dispatch.h b/starboard/shared/ffmpeg/ffmpeg_dispatch.h
index 347566b..e26f6d1 100644
--- a/starboard/shared/ffmpeg/ffmpeg_dispatch.h
+++ b/starboard/shared/ffmpeg/ffmpeg_dispatch.h
@@ -36,6 +36,15 @@
 namespace shared {
 namespace ffmpeg {
 
+//  derived from AV_VERSION_INT(a, b, c)   ((a)<<16 | (b)<<8 | (c))
+//  https://github.com/FFmpeg/FFmpeg/blob/master/doc/APIchanges#L1981
+constexpr int kAVCodecSupportsAvFrameAlloc = 3616101;
+constexpr int kAVCodecSupportsAvcodecFreeContext = 3620708;
+constexpr int kAVCodecSupportsAvPacketAlloc = 3738724;
+// https://github.com/libav/libav/blob/8e401dbe90cc77b1f3067a917d9fa48cefa3fcdb/libavutil/version.h
+// AV_VERSION_INT(52, 8, 0)
+constexpr int kAVUtilSupportsBufferCreate = 3409920;
+
 class FFMPEGDispatch {
  public:
   FFMPEGDispatch();
@@ -58,9 +67,7 @@
   void* (*av_malloc)(size_t size);
   void (*av_freep)(void* ptr);
   AVFrame* (*av_frame_alloc)(void);
-#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
   void (*av_frame_free)(AVFrame** frame);
-#endif  // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
   void (*av_frame_unref)(AVFrame* frame);
   int (*av_samples_get_buffer_size)(int* linesize,
                                     int nb_channels,
@@ -83,9 +90,7 @@
 
   unsigned (*avcodec_version)(void);
   AVCodecContext* (*avcodec_alloc_context3)(const AVCodec* codec);
-#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
   void (*avcodec_free_context)(AVCodecContext** avctx);
-#endif  // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
   AVCodec* (*avcodec_find_decoder)(int id);
   int (*avcodec_close)(AVCodecContext* avctx);
   int (*avcodec_open2)(AVCodecContext* avctx,
diff --git a/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc b/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc
index 6fc39d1..987d42d 100644
--- a/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc
@@ -247,39 +247,59 @@
 
 void FFMPEGDispatchImpl::LoadSymbols() {
   SB_DCHECK(is_valid());
-// Load the desired symbols from the shared libraries. Note: If a symbol is
-// listed as a '.text' entry in the output of 'objdump -T' on the shared
-// library file, then it is directly available from it.
+  // Load the desired symbols from the shared libraries. Note: If a symbol is
+  // listed as a '.text' entry in the output of 'objdump -T' on the shared
+  // library file, then it is directly available from it.
+  char* errstr;
+  errstr = dlerror();
+  if (errstr != NULL) {
+    SB_LOG(INFO) << "LoadSymbols - checking dlerror shows:" << errstr;
+  }
 
 #define INITSYMBOL(library, symbol)                                     \
   ffmpeg_->symbol = reinterpret_cast<decltype(FFMPEGDispatch::symbol)>( \
-      dlsym(library, #symbol));
+      dlsym(library, #symbol));                                         \
+  errstr = dlerror();                                                   \
+  if (errstr != NULL) {                                                 \
+    SB_LOG(INFO) << "Load Symbols ran into error:" << errstr;           \
+  }
 
   // Load symbols from the avutil shared library.
   INITSYMBOL(avutil_, avutil_version);
   SB_DCHECK(ffmpeg_->avutil_version);
+  SB_LOG(INFO) << "Opened avutil  - version is:" << ffmpeg_->avutil_version()
+               << std::endl;
   INITSYMBOL(avutil_, av_malloc);
   INITSYMBOL(avutil_, av_freep);
-  INITSYMBOL(avutil_, av_frame_alloc);
   INITSYMBOL(avutil_, av_free);
   INITSYMBOL(avutil_, av_rescale_rnd);
-#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
-  INITSYMBOL(avutil_, av_frame_free);
-  INITSYMBOL(avutil_, av_dict_get);
-#endif  // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
-  INITSYMBOL(avutil_, av_frame_unref);
   INITSYMBOL(avutil_, av_samples_get_buffer_size);
   INITSYMBOL(avutil_, av_opt_set_int);
   INITSYMBOL(avutil_, av_image_check_size);
-  INITSYMBOL(avutil_, av_buffer_create);
+  if (ffmpeg_->avutil_version() > kAVUtilSupportsBufferCreate) {
+    INITSYMBOL(avutil_, av_buffer_create);
+  }
 
   // Load symbols from the avcodec shared library.
   INITSYMBOL(avcodec_, avcodec_version);
   SB_DCHECK(ffmpeg_->avcodec_version);
+  SB_LOG(INFO) << "Opened libavcodec - version is:"
+               << ffmpeg_->avcodec_version() << std::endl;
+
+  if (ffmpeg_->avcodec_version() > kAVCodecSupportsAvFrameAlloc) {
+    INITSYMBOL(avcodec_, av_frame_alloc);
+    INITSYMBOL(avcodec_, av_frame_unref);
+    INITSYMBOL(avcodec_, av_frame_free);
+    INITSYMBOL(avcodec_, av_dict_get);
+  } else {
+    INITSYMBOL(avcodec_, avcodec_alloc_frame);
+    INITSYMBOL(avcodec_, avcodec_get_frame_defaults);
+  }
+
   INITSYMBOL(avcodec_, avcodec_alloc_context3);
-#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
-  INITSYMBOL(avcodec_, avcodec_free_context);
-#endif  // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
+  if (ffmpeg_->avcodec_version() > kAVCodecSupportsAvcodecFreeContext) {
+    INITSYMBOL(avcodec_, avcodec_free_context);
+  }
   INITSYMBOL(avcodec_, avcodec_find_decoder);
   INITSYMBOL(avcodec_, avcodec_close);
   INITSYMBOL(avcodec_, avcodec_open2);
@@ -287,18 +307,22 @@
   INITSYMBOL(avcodec_, avcodec_decode_audio4);
   INITSYMBOL(avcodec_, avcodec_decode_video2);
   INITSYMBOL(avcodec_, avcodec_flush_buffers);
-  INITSYMBOL(avcodec_, avcodec_alloc_frame);
-  INITSYMBOL(avcodec_, avcodec_get_frame_defaults);
   INITSYMBOL(avcodec_, avcodec_align_dimensions2);
-  INITSYMBOL(avcodec_, av_packet_alloc);
-  INITSYMBOL(avcodec_, av_packet_free);
-  INITSYMBOL(avcodec_, av_packet_unref);
-  INITSYMBOL(avcodec_, avcodec_parameters_to_context);
-  INITSYMBOL(avcodec_, av_free_packet);
+
+  if (ffmpeg_->avcodec_version() > kAVCodecSupportsAvPacketAlloc) {
+    INITSYMBOL(avcodec_, av_packet_alloc);
+    INITSYMBOL(avcodec_, av_packet_free);
+    INITSYMBOL(avcodec_, av_packet_unref);
+    INITSYMBOL(avcodec_, avcodec_parameters_to_context);
+  } else {
+    INITSYMBOL(avcodec_, av_free_packet);
+  }
 
   // Load symbols from the avformat shared library.
   INITSYMBOL(avformat_, avformat_version);
   SB_DCHECK(ffmpeg_->avformat_version);
+  SB_LOG(INFO) << "Opened libavformat - version is:"
+               << ffmpeg_->avformat_version() << std::endl;
   INITSYMBOL(avformat_, av_register_all);
   SB_DCHECK(ffmpeg_->av_register_all);
   INITSYMBOL(avformat_, av_read_frame);
diff --git a/starboard/shared/ffmpeg/ffmpeg_linked_dispatch_impl.cc b/starboard/shared/ffmpeg/ffmpeg_linked_dispatch_impl.cc
index c3d2f52..b2fb431 100644
--- a/starboard/shared/ffmpeg/ffmpeg_linked_dispatch_impl.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_linked_dispatch_impl.cc
@@ -41,9 +41,9 @@
 
 void LoadSymbols(FFMPEGDispatch* ffmpeg) {
   SB_DCHECK(ffmpeg->is_valid());
-// Load the desired symbols from the shared libraries. Note: If a symbol is
-// listed as a '.text' entry in the output of 'objdump -T' on the shared
-// library file, then it is directly available from it.
+  // Load the desired symbols from the shared libraries. Note: If a symbol is
+  // listed as a '.text' entry in the output of 'objdump -T' on the shared
+  // library file, then it is directly available from it.
 
 #define INITSYMBOL(symbol) \
   ffmpeg->symbol = reinterpret_cast<decltype(FFMPEGDispatch::symbol)>(symbol);
diff --git a/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc b/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc
index 3b6a9bb..8a7c898 100644
--- a/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc
@@ -263,9 +263,11 @@
 bool VideoDecoderImpl<FFMPEG>::DecodePacket(AVPacket* packet) {
   SB_DCHECK(packet != NULL);
 
-#if LIBAVUTIL_VERSION_INT < LIBAVUTIL_VERSION_52_8
-  ffmpeg_->avcodec_get_frame_defaults(av_frame_);
-#endif  // LIBAVUTIL_VERSION_INT < LIBAVUTIL_VERSION_52_8
+  if (ffmpeg_->avcodec_version() > kAVCodecSupportsAvFrameAlloc) {
+    ffmpeg_->av_frame_unref(av_frame_);
+  } else {
+    ffmpeg_->avcodec_get_frame_defaults(av_frame_);
+  }
   int frame_decoded = 0;
   int decode_result = ffmpeg_->avcodec_decode_video2(codec_context_, av_frame_,
                                                      &frame_decoded, packet);
diff --git a/starboard/shared/starboard/player/testdata/heaac.dmp.sha1 b/starboard/shared/starboard/player/testdata/heaac.dmp.sha1
index f8b1691..b29e1f2 100644
--- a/starboard/shared/starboard/player/testdata/heaac.dmp.sha1
+++ b/starboard/shared/starboard/player/testdata/heaac.dmp.sha1
@@ -1 +1 @@
-27ef8edb9991163ef5c391c60f959ef2cc334762
\ No newline at end of file
+fd4777e4d51ec22837d642771f38406b02af7e7d
diff --git a/starboard/shared/starboard/player/testdata/sintel_329_ec3.dmp.sha1 b/starboard/shared/starboard/player/testdata/sintel_329_ec3.dmp.sha1
index 1afa457..16c6718 100644
--- a/starboard/shared/starboard/player/testdata/sintel_329_ec3.dmp.sha1
+++ b/starboard/shared/starboard/player/testdata/sintel_329_ec3.dmp.sha1
@@ -1 +1 @@
-e69024d74c0d49e75e0447ce10b45ac18abbd1cc
+d9e8510d2457731e70884075b47af2fd35715172
diff --git a/starboard/shared/starboard/player/testdata/sintel_381_ac3.dmp.sha1 b/starboard/shared/starboard/player/testdata/sintel_381_ac3.dmp.sha1
index f7d8625..805d2c0 100644
--- a/starboard/shared/starboard/player/testdata/sintel_381_ac3.dmp.sha1
+++ b/starboard/shared/starboard/player/testdata/sintel_381_ac3.dmp.sha1
@@ -1 +1 @@
-3ab684539ae21c22b983e167a07d354eed545bc0
+90dac969ea01a51e3e90cd482ae76d909d123d98
diff --git a/starboard/win/win32/test_filters.py b/starboard/win/win32/test_filters.py
index a14fd92..8219947 100644
--- a/starboard/win/win32/test_filters.py
+++ b/starboard/win/win32/test_filters.py
@@ -41,6 +41,10 @@
         'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceForDestination/type_ipv6',
         'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceNotLoopback/type_ipv6',
         'SbSocketAddressTypes/SbSocketResolveTest.SunnyDayFiltered/filter_ipv6_type_ipv6',
+        'SbSocketAddressTypes/SbSocketSetOptionsTest.RainyDayInvalidSocket/type_ipv4',
+        'SbSocketAddressTypes/SbSocketSetOptionsTest.RainyDayInvalidSocket/type_ipv6',
+        # Flakiness is tracked in b/278276779.
+        'Semaphore.ThreadTakesWait_TimeExpires',
     ],
     'player_filter_tests': [
         # These tests fail on our VMs for win-win32 builds due to missing
diff --git a/third_party/jinja2/bccache.py b/third_party/jinja2/bccache.py
index 1e89b46..da6c477 100644
--- a/third_party/jinja2/bccache.py
+++ b/third_party/jinja2/bccache.py
@@ -14,7 +14,7 @@
     :copyright: (c) 2010 by the Jinja Team.
     :license: BSD.
 """
-from os import path, listdir, pardir
+from os import path, listdir, pardir, sep
 import sys
 import marshal
 import tempfile
@@ -175,9 +175,13 @@
         """Return a cache bucket for the given template.  All arguments are
         mandatory but filename may be `None`.
         """
+        # TODO(b/263289873): To get correct GN outputs the file list of cached
+        # templates should be printed to a depfile.
         # Use the path to the template relative to the project root to get a
-        # consistent hash.
-        key = self.get_cache_key(name, path.relpath(filename, _REPOSITORY_ROOT))
+        # consistent hash. To have hashes consistent across platforms we have to
+        # replace the path separator.
+        rel_path = path.relpath(filename, _REPOSITORY_ROOT)
+        key = self.get_cache_key(name, rel_path.replace(sep, '/'))
         checksum = self.get_source_checksum(source)
         bucket = Bucket(environment, key, checksum)
         self.load_bytecode(bucket)
diff --git a/third_party/libjpeg-turbo/BUILD.gn b/third_party/libjpeg-turbo/BUILD.gn
index f1ff3f3..0630c11 100644
--- a/third_party/libjpeg-turbo/BUILD.gn
+++ b/third_party/libjpeg-turbo/BUILD.gn
@@ -29,7 +29,7 @@
   }
 }
 
-if (yasm_exists && (current_cpu == "x86" || current_cpu == "x64")) {
+if (nasm_exists && (current_cpu == "x86" || current_cpu == "x64")) {
   if (is_starboard) {
     import("//starboard/build/nasm_assemble.gni")
   } else {
@@ -162,10 +162,10 @@
     defines = []
   }
 
-  if (current_cpu == "x86" && yasm_exists) {
+  if (current_cpu == "x86" && nasm_exists) {
     deps += [ ":simd_asm" ]
     sources = [ "simd/i386/jsimd.c" ]
-  } else if (current_cpu == "x64" && yasm_exists) {
+  } else if (current_cpu == "x64" && nasm_exists) {
     deps += [ ":simd_asm" ]
     sources = [ "simd/x86_64/jsimd.c" ]
   } else if ((current_cpu == "arm" || current_cpu == "arm64") && arm_use_neon) {
diff --git a/third_party/web_platform_tests/service-workers/service-worker/import-scripts-mime-types.https.html b/third_party/web_platform_tests/service-workers/service-worker/import-scripts-mime-types.https.html
index 1679831..86a7e1f 100644
--- a/third_party/web_platform_tests/service-workers/service-worker/import-scripts-mime-types.https.html
+++ b/third_party/web_platform_tests/service-workers/service-worker/import-scripts-mime-types.https.html
@@ -20,7 +20,15 @@
 
   await wait_for_state(t, registration.installing, 'activated');
 
-  serviceWorker = registration.active;
+  // TODO(b/234788479) Implement waiting for update worker state tasks in
+  // Install algorithm, otherwise the worker is activated too early
+  if (registration.active) {
+    serviceWorker = registration.active;
+  } else if (registration.waiting) {
+    serviceWorker = registration.waiting;
+  } else {
+    serviceWorker = registration.installing;
+  }
 }, 'Global setup');
 
 promise_test(async t => {
diff --git a/third_party/web_platform_tests/service-workers/service-worker/import-scripts-updated-flag.https.html b/third_party/web_platform_tests/service-workers/service-worker/import-scripts-updated-flag.https.html
index 09b4496..a658d07 100644
--- a/third_party/web_platform_tests/service-workers/service-worker/import-scripts-updated-flag.https.html
+++ b/third_party/web_platform_tests/service-workers/service-worker/import-scripts-updated-flag.https.html
@@ -38,10 +38,18 @@
           // other sub-tests in this file are declared synchronously, this test
           // will be the final test executed.
           promise_test(function(t) {
-              return registration.unregister();
-            });
-
-          return registration.active;
+            return registration.unregister();
+          });
+          // TODO(b/234788479) Implement waiting for update worker state tasks in
+          // Install algorithm, otherwise the worker is activated too early
+          if (registration.active) {
+            serviceWorker = registration.active;
+          } else if (registration.waiting) {
+            serviceWorker = registration.waiting;
+          } else {
+            serviceWorker = registration.installing;
+          }
+          return serviceWorker;
         });
 
     return register;
diff --git a/third_party/web_platform_tests/service-workers/service-worker/registration-service-worker-attributes.https.html b/third_party/web_platform_tests/service-workers/service-worker/registration-service-worker-attributes.https.html
index f7b52d5..0891b10 100644
--- a/third_party/web_platform_tests/service-workers/service-worker/registration-service-worker-attributes.https.html
+++ b/third_party/web_platform_tests/service-workers/service-worker/registration-service-worker-attributes.https.html
@@ -65,8 +65,10 @@
           // immediately followed by setting active to null, which means by the
           // time the event loop turns and the Promise for statechange is
           // resolved, this will be gone.
-          assert_equals(registration.active, null,
-                        'active should be null after redundant');
+          // For Cobalt, this is not the case. Setting active to null can happen
+          // a little bit later after the Update State change.
+          // assert_equals(registration.active, null,
+          //               'active should be null after redundant');
         });
   }, 'installing/waiting/active after registration');
 </script>
diff --git a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-echo.py b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-echo.py
index d38d660..7d92794 100644
--- a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-echo.py
+++ b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-echo.py
@@ -1,6 +1,6 @@
 def main(req, res):
     return ([
-        (b'Cache-Control', b'no-cache, must-revalidate'),
-        (b'Pragma', b'no-cache'),
-        (b'Content-Type', b'application/javascript')],
-      b'echo_output = "%s";\n' % req.GET[b'msg'])
+        ('Cache-Control', 'no-cache, must-revalidate'),
+        ('Pragma', 'no-cache'),
+        ('Content-Type', 'application/javascript')],
+      'echo_output = "%s";\n' % req.GET['msg'])
diff --git a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-get.py b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-get.py
index ab7b84e..9e376bc 100644
--- a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-get.py
+++ b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-get.py
@@ -1,6 +1,6 @@
 def main(req, res):
     return ([
-        (b'Cache-Control', b'no-cache, must-revalidate'),
-        (b'Pragma', b'no-cache'),
-        (b'Content-Type', b'application/javascript')],
-        b'%s = "%s";\n' % (req.GET[b'output'], req.GET[b'msg']))
+        ('Cache-Control', 'no-cache, must-revalidate'),
+        ('Pragma', 'no-cache'),
+        ('Content-Type', 'application/javascript')],
+        '%s = "%s";\n' % (req.GET['output'], req.GET['msg']))
diff --git a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-mime-types-worker.js b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-mime-types-worker.js
index d4f1f3e..2c585ac 100644
--- a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-mime-types-worker.js
+++ b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-mime-types-worker.js
@@ -30,7 +30,7 @@
 
 for (const mimeType of badMimeTypes) {
   test(() => {
-    assert_throws_dom(
+    assert_throws(
       'NetworkError',
       () => { importScriptsWithMimeType(mimeType); },
       `importScripts with ${mimeType ? 'bad' : 'no'} MIME type ${mimeType || ''} throws NetworkError`,
diff --git a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-version.py b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-version.py
index cde2854..fcac3b0 100644
--- a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-version.py
+++ b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-version.py
@@ -11,7 +11,7 @@
     now = (datetime.datetime.now() - epoch).total_seconds()
 
     return ([
-        (b'Cache-Control', b'no-cache, must-revalidate'),
-        (b'Pragma', b'no-cache'),
-        (b'Content-Type', b'application/javascript')],
-       u'version = "%s";\n' % now)
+        ('Cache-Control', 'no-cache, must-revalidate'),
+        ('Pragma', 'no-cache'),
+        ('Content-Type', 'application/javascript')],
+       'version = "%s";\n' % now)
diff --git a/third_party/web_platform_tests/service-workers/service-worker/resources/mime-type-worker.py b/third_party/web_platform_tests/service-workers/service-worker/resources/mime-type-worker.py
index 92a602e..a16684d 100644
--- a/third_party/web_platform_tests/service-workers/service-worker/resources/mime-type-worker.py
+++ b/third_party/web_platform_tests/service-workers/service-worker/resources/mime-type-worker.py
@@ -1,4 +1,4 @@
 def main(request, response):
-    if b'mime' in request.GET:
-        return [(b'Content-Type', request.GET[b'mime'])], b""
-    return [], b""
+    if 'mime' in request.GET:
+        return [('Content-Type', request.GET['mime'])], ""
+    return [], ""
diff --git a/third_party/web_platform_tests/service-workers/service-worker/resources/test-helpers.sub.js b/third_party/web_platform_tests/service-workers/service-worker/resources/test-helpers.sub.js
index 28b657a..7430152 100644
--- a/third_party/web_platform_tests/service-workers/service-worker/resources/test-helpers.sub.js
+++ b/third_party/web_platform_tests/service-workers/service-worker/resources/test-helpers.sub.js
@@ -86,15 +86,7 @@
   return new Promise(test.step_func(function(resolve) {
       var handler = test.step_func(function() {
         registration.removeEventListener('updatefound', handler);
-        // b/234788479 Implement waiting for update worker state tasks in
-        // Install algorithm, otherwise the worker is activated too early
-        if (registration.installing) {
-          resolve(registration.installing);
-        } else if (registration.waiting) {
-          resolve(registration.waiting);
-        } else {
-          resolve(registration.active);
-        }
+        resolve(registration.installing);
       });
       registration.addEventListener('updatefound', handler);
     }));
diff --git a/third_party/web_platform_tests/service-workers/service-worker/update-no-cache-request-headers.https.html b/third_party/web_platform_tests/service-workers/service-worker/update-no-cache-request-headers.https.html
index 6ebad4b..e2677bf 100644
--- a/third_party/web_platform_tests/service-workers/service-worker/update-no-cache-request-headers.https.html
+++ b/third_party/web_platform_tests/service-workers/service-worker/update-no-cache-request-headers.https.html
@@ -25,12 +25,13 @@
   await service_worker_unregister(t, scope);
   const registration = await navigator.serviceWorker.register(
       script, {scope, updateViaCache: 'none'});
-  await wait_for_state(t, registration.installing, 'activated');
+  // b/234788479 Implement waiting for update worker state tasks in
+  // Install algorithm, otherwise the worker is activated too early
+  await wait_for_state(t, get_newest_worker(registration), 'activated');
 
   // Do an update.
   await registration.update();
 
-  // Ask the new worker what the request headers were.
   const newWorker = registration.installing;
   const sawMessage = new Promise((resolve) => {
     navigator.serviceWorker.onmessage = (event) => {