Import Cobalt 23.lts.1.309190
diff --git a/cobalt/account/user_authorizer.h b/cobalt/account/user_authorizer.h
index 6bd8ad5..784984a 100644
--- a/cobalt/account/user_authorizer.h
+++ b/cobalt/account/user_authorizer.h
@@ -77,7 +77,8 @@
   // request. Calling other methods after |Shutdown| may have no effect.
   virtual void Shutdown() {}
 
-  // Instantiates an instance of the platform-specific implementation.
+  // Instantiates an instance of the platform-specific implementation. This may
+  // return nullptr if UserAuthorizer functions are not supported.
   static UserAuthorizer* Create();
 
  private:
diff --git a/cobalt/browser/idl_files.gni b/cobalt/browser/idl_files.gni
index 258a1ce..db2d7c5 100644
--- a/cobalt/browser/idl_files.gni
+++ b/cobalt/browser/idl_files.gni
@@ -157,7 +157,7 @@
   "//cobalt/h5vcc/h5vcc.idl",
   "//cobalt/h5vcc/h5vcc_accessibility.idl",
   "//cobalt/h5vcc/h5vcc_account_info.idl",
-  "//cobalt/h5vcc/h5vcc_account_manager.idl",
+  "//cobalt/h5vcc/h5vcc_account_manager_internal.idl",
   "//cobalt/h5vcc/h5vcc_audio_config.idl",
   "//cobalt/h5vcc/h5vcc_audio_config_array.idl",
   "//cobalt/h5vcc/h5vcc_crash_log.idl",
diff --git a/cobalt/dom/BUILD.gn b/cobalt/dom/BUILD.gn
index e16397b..f014522 100644
--- a/cobalt/dom/BUILD.gn
+++ b/cobalt/dom/BUILD.gn
@@ -261,7 +261,6 @@
     "screenshot.h",
     "screenshot_manager.cc",
     "screenshot_manager.h",
-    "serialized_algorithm_runner.cc",
     "serialized_algorithm_runner.h",
     "serializer.cc",
     "serializer.h",
diff --git a/cobalt/dom/document.cc b/cobalt/dom/document.cc
index 4e665a5..41bfa41a 100644
--- a/cobalt/dom/document.cc
+++ b/cobalt/dom/document.cc
@@ -448,7 +448,7 @@
     return;
   }
   if (cookie_jar_) {
-    cookie_jar_->SetCookie(url_as_gurl(), cookie);
+    cookie_jar_->SetCookie(location()->url(), cookie);
   }
 }
 
@@ -467,7 +467,7 @@
   }
   if (cookie_jar_) {
     return net::CanonicalCookie::BuildCookieLine(
-        cookie_jar_->GetCookies(url_as_gurl()));
+        cookie_jar_->GetCookies(location()->url()));
   } else {
     DLOG(WARNING) << "Document has no cookie jar";
     return "";
@@ -485,7 +485,7 @@
     return;
   }
   if (cookie_jar_) {
-    cookie_jar_->SetCookie(url_as_gurl(), cookie);
+    cookie_jar_->SetCookie(location()->url(), cookie);
   }
 }
 
@@ -501,7 +501,7 @@
   }
   if (cookie_jar_) {
     return net::CanonicalCookie::BuildCookieLine(
-        cookie_jar_->GetCookies(url_as_gurl()));
+        cookie_jar_->GetCookies(location()->url()));
   } else {
     DLOG(WARNING) << "Document has no cookie jar";
     return "";
@@ -517,7 +517,8 @@
   // limited quirks mode, or no-quirks mode), and its type (XML document or HTML
   // document).
   //   https://www.w3.org/TR/dom/#concept-node-clone
-  return new Document(html_element_context_, Document::Options(url_as_gurl()));
+  return new Document(html_element_context_,
+                      Document::Options(location()->url()));
 }
 
 scoped_refptr<HTMLHtmlElement> Document::html() const {
diff --git a/cobalt/dom/document.h b/cobalt/dom/document.h
index 2fc0246..89c65c0 100644
--- a/cobalt/dom/document.h
+++ b/cobalt/dom/document.h
@@ -247,8 +247,6 @@
 
   FontCache* font_cache() const { return font_cache_.get(); }
 
-  const GURL& url_as_gurl() const { return location_->url(); }
-
   scoped_refptr<HTMLHtmlElement> html() const;
 
   // List of scripts that will execute in order as soon as possible.
diff --git a/cobalt/dom/document_test.cc b/cobalt/dom/document_test.cc
index 8909802..b693173 100644
--- a/cobalt/dom/document_test.cc
+++ b/cobalt/dom/document_test.cc
@@ -103,7 +103,7 @@
       new testing::FakeDocument(&html_element_context_, Document::Options(url));
   EXPECT_EQ(url.spec(), document->url());
   EXPECT_EQ(url.spec(), document->document_uri());
-  EXPECT_EQ(url, document->url_as_gurl());
+  EXPECT_EQ(url, document->location()->url());
 }
 
 TEST_F(DocumentTest, IsNotXMLDocument) {
diff --git a/cobalt/dom/dom_settings.cc b/cobalt/dom/dom_settings.cc
index d07474b..8390913 100644
--- a/cobalt/dom/dom_settings.cc
+++ b/cobalt/dom/dom_settings.cc
@@ -44,6 +44,11 @@
 
 DOMSettings::~DOMSettings() {}
 
+Window* DOMSettings::window() const {
+  DCHECK(context()->GetWindowOrWorkerGlobalScope()->IsWindow());
+  return context()->GetWindowOrWorkerGlobalScope()->AsWindow();
+}
+
 const GURL& DOMSettings::base_url() const {
   // From algorithm for to setup up a window environment settings object:
   //   https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#set-up-a-window-environment-settings-object
@@ -51,15 +56,10 @@
   //    algorithms are defined as follows:
   //    The API base URL
   //    Return the current base URL of window's associated Document.
-  return window()->document()->url_as_gurl();
+  return window()->document()->location()->url();
 }
 
-scoped_refptr<Window> DOMSettings::window() const {
-  DCHECK(context()->GetWindowOrWorkerGlobalScope()->IsWindow());
-  return context()->GetWindowOrWorkerGlobalScope()->AsWindow();
-}
-
-loader::Origin DOMSettings::document_origin() const {
+loader::Origin DOMSettings::GetOrigin() const {
   return window()->document()->location()->GetOriginAsObject();
 }
 
diff --git a/cobalt/dom/dom_settings.h b/cobalt/dom/dom_settings.h
index 33a5c0a..58e221f 100644
--- a/cobalt/dom/dom_settings.h
+++ b/cobalt/dom/dom_settings.h
@@ -69,7 +69,7 @@
     return microphone_options_;
   }
 
-  scoped_refptr<Window> window() const;
+  Window* window() const;
 
   MediaSourceRegistry* media_source_registry() const {
     return media_source_registry_;
@@ -91,13 +91,14 @@
     return mutation_observer_task_manager_;
   }
 
-  // Return's document's origin.
-  loader::Origin document_origin() const;
-
   // From: script::EnvironmentSettings
   //
   const GURL& base_url() const override;
 
+  // Return the origin of window's associated Document.
+  //   https://html.spec.whatwg.org/#set-up-a-window-environment-settings-object
+  loader::Origin GetOrigin() const override;
+
  private:
   const int max_dom_element_depth_;
   const speech::Microphone::Options microphone_options_;
diff --git a/cobalt/dom/html_anchor_element.cc b/cobalt/dom/html_anchor_element.cc
index c262595..0c15a83 100644
--- a/cobalt/dom/html_anchor_element.cc
+++ b/cobalt/dom/html_anchor_element.cc
@@ -54,7 +54,7 @@
   }
 
   // Resolve the URL given by the href attribute, relative to the element.
-  const GURL& base_url = document->url_as_gurl();
+  const GURL& base_url = document->location()->url();
   GURL absolute_url = base_url.Resolve(value);
 
   // If the previous step fails, then abort these steps.
diff --git a/cobalt/dom/html_element.cc b/cobalt/dom/html_element.cc
index aa38f18..970c56e 100644
--- a/cobalt/dom/html_element.cc
+++ b/cobalt/dom/html_element.cc
@@ -2025,7 +2025,7 @@
   // came from.
   cssom::GURLMap property_key_to_base_url_map;
   property_key_to_base_url_map[cssom::kBackgroundImageProperty] =
-      document->url_as_gurl();
+      document->location()->url();
 
   // Flags tracking which cached values must be invalidated.
   UpdateComputedStyleInvalidationFlags invalidation_flags;
diff --git a/cobalt/dom/html_image_element.cc b/cobalt/dom/html_image_element.cc
index 2cbc162..02300ed 100644
--- a/cobalt/dom/html_image_element.cc
+++ b/cobalt/dom/html_image_element.cc
@@ -119,7 +119,7 @@
   if (!src.empty()) {
     // 7.1. Resolve selected source, relative to the element. If that is not
     // successful, abort these steps.
-    const GURL& base_url = node_document()->url_as_gurl();
+    const GURL& base_url = node_document()->location()->url();
     const GURL selected_source = base_url.Resolve(src);
     if (!selected_source.is_valid()) {
       LOG(WARNING) << src << " cannot be resolved based on " << base_url << ".";
@@ -235,7 +235,7 @@
   // Resolve selected source, relative to the element.
   const auto src_attr = GetAttribute("src");
   const std::string src = src_attr.value_or("");
-  const GURL& base_url = node_document()->url_as_gurl();
+  const GURL& base_url = node_document()->location()->url();
   const GURL selected_source = base_url.Resolve(src);
 
   html_element_context()->performance()->CreatePerformanceResourceTiming(
diff --git a/cobalt/dom/html_link_element.cc b/cobalt/dom/html_link_element.cc
index b51d697..17a6de1 100644
--- a/cobalt/dom/html_link_element.cc
+++ b/cobalt/dom/html_link_element.cc
@@ -147,7 +147,7 @@
 
 void HTMLLinkElement::ResolveAndSetAbsoluteURL() {
   // Resolve the URL given by the href attribute, relative to the element.
-  const GURL& base_url = node_document()->url_as_gurl();
+  const GURL& base_url = node_document()->location()->url();
   absolute_url_ = base_url.Resolve(href());
 
   LOG_IF(WARNING, !absolute_url_.is_valid())
@@ -316,7 +316,7 @@
   // If not loading from network-fetched resources or fetched resource is same
   // origin as the document, set origin-clean flag to true.
   if (request_mode_ != loader::kNoCORSMode || !loader_ ||
-      document->url_as_gurl().SchemeIsFile() ||
+      document->location()->url().SchemeIsFile() ||
       (fetched_last_url_origin_ == document->location()->GetOriginAsObject())) {
     css_style_sheet->SetOriginClean(true);
   }
diff --git a/cobalt/dom/html_media_element.cc b/cobalt/dom/html_media_element.cc
index 099559b..d496b01 100644
--- a/cobalt/dom/html_media_element.cc
+++ b/cobalt/dom/html_media_element.cc
@@ -795,7 +795,7 @@
     GURL media_url(src);
     if (media_url.is_empty()) {
       // Try to resolve it as a relative url.
-      media_url = node_document()->url_as_gurl().Resolve(src);
+      media_url = node_document()->location()->url().Resolve(src);
     }
     if (media_url.is_empty()) {
       MediaLoadingFailed(WebMediaPlayer::kNetworkStateFormatError,
@@ -1690,7 +1690,7 @@
   std::string src = this->src();
   GURL current_url = GURL(src);
   if (current_url.is_empty()) {
-    current_url = node_document()->url_as_gurl().Resolve(src);
+    current_url = node_document()->location()->url().Resolve(src);
   }
   if (!current_url.SchemeIs("http") &&
       OriginIsSafe(request_mode_, current_url,
diff --git a/cobalt/dom/html_script_element.cc b/cobalt/dom/html_script_element.cc
index a036d7c..42f7997 100644
--- a/cobalt/dom/html_script_element.cc
+++ b/cobalt/dom/html_script_element.cc
@@ -78,6 +78,18 @@
   DCHECK(document->html_element_context()->script_runner());
 }
 
+std::string HTMLScriptElement::src() const {
+  auto src = GetAttribute("src");
+  if (!src.has_value()) {
+    return "";
+  }
+  if (!node_document()) {
+    return src.value();
+  }
+  const GURL& base_url = node_document()->location()->url();
+  return base_url.Resolve(src.value()).spec();
+}
+
 base::Optional<std::string> HTMLScriptElement::cross_origin() const {
   base::Optional<std::string> cross_origin_attribute =
       GetAttribute("crossOrigin");
@@ -217,7 +229,11 @@
   //   1. Let src be the value of the element's src attribute.
   //   2. If src is the empty string, queue a task to fire a simple event
   // named error at the element, and abort these steps.
-  if (HasAttribute("src") && src() == "") {
+  //
+  // Need to use the "src" attribute. The |src| property is fully resolved. See
+  // header file for details.
+  auto src = GetAttribute("src").value_or("");
+  if (HasAttribute("src") && src == "") {
     LOG(ERROR) << "src attribute of script element is empty.";
 
     PreventGarbageCollectionAndPostToDispatchEvent(
@@ -229,10 +245,10 @@
   //   3. Resolve src relative to the element.
   //   4. If the previous step failed, queue a task to fire a simple event named
   // error at the element, and abort these steps.
-  const GURL& base_url = document_->url_as_gurl();
-  url_ = base_url.Resolve(src());
+  const GURL& base_url = document_->location()->url();
+  url_ = base_url.Resolve(src);
   if (!url_.is_valid()) {
-    LOG(ERROR) << src() << " cannot be resolved based on " << base_url << ".";
+    LOG(ERROR) << src << " cannot be resolved based on " << base_url << ".";
 
     PreventGarbageCollectionAndPostToDispatchEvent(
         FROM_HERE, base::Tokens::error(),
@@ -267,7 +283,7 @@
              "bugs, it is recommended to use JavaScript to create a "
              "script element and load it async. The <script> reference "
              "appears at: \""
-          << inline_script_location_ << "\" and its src is \"" << src() << "\"";
+          << inline_script_location_ << "\" and its src is \"" << src << "\"";
     }
 
     load_option_ = 2;
diff --git a/cobalt/dom/html_script_element.h b/cobalt/dom/html_script_element.h
index 3c9740e..f5537fd 100644
--- a/cobalt/dom/html_script_element.h
+++ b/cobalt/dom/html_script_element.h
@@ -42,7 +42,13 @@
 
   // Web API: HTMLScriptElement
   //
-  std::string src() const { return GetAttribute("src").value_or(""); }
+  // If the "src" attribute is a relative URL, the |src| property returns a
+  // fully resolved URL used the |document.location.href| as the base URL. This
+  // matches the Chrome implementation of the |src| property.
+  // See:
+  //   https://source.chromium.org/chromium/chromium/src/+/main:out/Debug/gen/third_party/blink/renderer/bindings/core/v8/v8_html_script_element.cc;l=93;drc=4f7206c7352efba617f4561cd8c95844d9e9814f
+  //   https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/dom/element.cc;l=7360;drc=5f6702f8b8f9da8510d2b3ec809e177e6db09e53
+  std::string src() const;
   void set_src(const std::string& value) { SetAttribute("src", value); }
 
   std::string type() const { return GetAttribute("type").value_or(""); }
diff --git a/cobalt/dom/lottie_player.cc b/cobalt/dom/lottie_player.cc
index 93baab8..4adb4a8 100644
--- a/cobalt/dom/lottie_player.cc
+++ b/cobalt/dom/lottie_player.cc
@@ -298,7 +298,7 @@
   const std::string src = GetAttribute("src").value_or("");
 
   if (!src.empty()) {
-    const GURL& base_url = node_document()->url_as_gurl();
+    const GURL& base_url = node_document()->location()->url();
     const GURL selected_source = base_url.Resolve(src);
     if (!selected_source.is_valid()) {
       LOG(WARNING) << src << " cannot be resolved based on " << base_url << ".";
diff --git a/cobalt/dom/media_source.cc b/cobalt/dom/media_source.cc
index f9f2c05..1dcc7d3 100644
--- a/cobalt/dom/media_source.cc
+++ b/cobalt/dom/media_source.cc
@@ -83,13 +83,14 @@
 // If the system has more processors than the specified value, SourceBuffer
 // append and remove algorithm will be offloaded to a non-web thread to reduce
 // the load on the web thread.
-// The default value is 2.  Set to a reasonably high value (say 1024) will
-// disable algorithm offloading completely.
+// The default value is 1024, which effectively disable offloading by default.
+// Setting to a reasonably low value (say 0 or 2) will enable algorithm
+// offloading.
 bool IsAlgorithmOffloadEnabled(script::EnvironmentSettings* settings) {
   int min_process_count_to_offload =
       GetMediaSettings(settings)
           ->GetMinimumProcessorCountToOffloadAlgorithm()
-          .value_or(2);
+          .value_or(1024);
   DCHECK_GE(min_process_count_to_offload, 0);
   return SbSystemGetNumberOfProcessors() >= min_process_count_to_offload;
 }
@@ -97,10 +98,10 @@
 // If this function returns true, SourceBuffer will reduce asynchronous
 // behaviors.  For example, queued events will be dispatached immediately when
 // possible.
-// The default value is true.
+// The default value is false.
 bool IsAsynchronousReductionEnabled(script::EnvironmentSettings* settings) {
   return GetMediaSettings(settings)->IsAsynchronousReductionEnabled().value_or(
-      true);
+      false);
 }
 
 // If the size of a job that is part of an algorithm is less than or equal to
@@ -390,9 +391,10 @@
 
   if (algorithm_process_thread_) {
     LOG(INFO) << "Algorithm offloading enabled.";
-    offload_algorithm_runner_.reset(new OffloadAlgorithmRunner(
-        algorithm_process_thread_->message_loop()->task_runner(),
-        base::MessageLoop::current()->task_runner()));
+    offload_algorithm_runner_.reset(
+        new OffloadAlgorithmRunner<SourceBufferAlgorithm>(
+            algorithm_process_thread_->message_loop()->task_runner(),
+            base::MessageLoop::current()->task_runner()));
   } else {
     LOG(INFO) << "Algorithm offloading disabled.";
   }
@@ -574,7 +576,8 @@
   return has_max_video_capabilities_;
 }
 
-SerializedAlgorithmRunner* MediaSource::GetAlgorithmRunner(int job_size) {
+SerializedAlgorithmRunner<SourceBufferAlgorithm>*
+MediaSource::GetAlgorithmRunner(int job_size) {
   if (!offload_algorithm_runner_) {
     return &default_algorithm_runner_;
   }
diff --git a/cobalt/dom/media_source.h b/cobalt/dom/media_source.h
index bfe0774..65ab2a8 100644
--- a/cobalt/dom/media_source.h
+++ b/cobalt/dom/media_source.h
@@ -59,6 +59,7 @@
 #include "cobalt/dom/media_source_ready_state.h"
 #include "cobalt/dom/serialized_algorithm_runner.h"
 #include "cobalt/dom/source_buffer.h"
+#include "cobalt/dom/source_buffer_algorithm.h"
 #include "cobalt/dom/source_buffer_list.h"
 #include "cobalt/dom/time_ranges.h"
 #include "cobalt/dom/video_track.h"
@@ -126,7 +127,8 @@
   void SetSourceBufferActive(SourceBuffer* source_buffer, bool is_active);
   HTMLMediaElement* GetMediaElement() const;
   bool MediaElementHasMaxVideoCapabilities() const;
-  SerializedAlgorithmRunner* GetAlgorithmRunner(int job_size);
+  SerializedAlgorithmRunner<SourceBufferAlgorithm>* GetAlgorithmRunner(
+      int job_size);
 
   DEFINE_WRAPPABLE_TYPE(MediaSource);
   void TraceMembers(script::Tracer* tracer) override;
@@ -148,9 +150,10 @@
   const int min_size_for_immediate_job_;
 
   // The default algorithm runner runs all steps on the web thread.
-  DefaultAlgorithmRunner default_algorithm_runner_;
+  DefaultAlgorithmRunner<SourceBufferAlgorithm> default_algorithm_runner_;
   // The offload algorithm runner offloads some steps to a non-web thread.
-  std::unique_ptr<OffloadAlgorithmRunner> offload_algorithm_runner_;
+  std::unique_ptr<OffloadAlgorithmRunner<SourceBufferAlgorithm>>
+      offload_algorithm_runner_;
   std::unique_ptr<base::Thread> algorithm_process_thread_;
 
   ChunkDemuxer* chunk_demuxer_;
diff --git a/cobalt/dom/serialized_algorithm_runner.cc b/cobalt/dom/serialized_algorithm_runner.cc
deleted file mode 100644
index 07dece0..0000000
--- a/cobalt/dom/serialized_algorithm_runner.cc
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2022 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "cobalt/dom/serialized_algorithm_runner.h"
-
-#include "base/message_loop/message_loop.h"
-
-namespace cobalt {
-namespace dom {
-
-void DefaultAlgorithmRunner::Start(const scoped_refptr<HandleBase>& handle) {
-  DCHECK(handle);
-  TRACE_EVENT0("cobalt::dom", "DefaultAlgorithmRunner::Start()");
-
-  if (asynchronous_reduction_enabled_) {
-    Process(handle);
-    return;
-  }
-
-  auto task_runner = base::MessageLoop::current()->task_runner();
-  task_runner->PostTask(FROM_HERE,
-                        base::BindOnce(&DefaultAlgorithmRunner::Process,
-                                       base::Unretained(this), handle));
-}
-
-void DefaultAlgorithmRunner::Process(const scoped_refptr<HandleBase>& handle) {
-  DCHECK(handle);
-  TRACE_EVENT0("cobalt::dom", "DefaultAlgorithmRunner::Process()");
-
-  auto task_runner = base::MessageLoop::current()->task_runner();
-
-  bool finished = false;
-  handle->Process(&finished);
-
-  if (finished) {
-    handle->Finalize();
-    return;
-  }
-  task_runner->PostTask(FROM_HERE,
-                        base::BindOnce(&DefaultAlgorithmRunner::Process,
-                                       base::Unretained(this), handle));
-}
-
-OffloadAlgorithmRunner::OffloadAlgorithmRunner(
-    const scoped_refptr<TaskRunner>& process_task_runner,
-    const scoped_refptr<TaskRunner>& finalize_task_runner)
-    : process_task_runner_(process_task_runner),
-      finalize_task_runner_(finalize_task_runner) {
-  DCHECK(process_task_runner_);
-  DCHECK(finalize_task_runner_);
-  DCHECK_NE(process_task_runner_, finalize_task_runner_);
-}
-
-void OffloadAlgorithmRunner::Start(const scoped_refptr<HandleBase>& handle) {
-  DCHECK(handle);
-
-  TRACE_EVENT0("cobalt::dom", "OffloadAlgorithmRunner::Start()");
-
-  process_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&OffloadAlgorithmRunner::Process,
-                                base::Unretained(this), handle));
-}
-
-void OffloadAlgorithmRunner::Process(const scoped_refptr<HandleBase>& handle) {
-  DCHECK(handle);
-  DCHECK(process_task_runner_->BelongsToCurrentThread());
-
-  TRACE_EVENT0("cobalt::dom", "OffloadAlgorithmRunner::Process()");
-
-  bool finished = false;
-  handle->Process(&finished);
-
-  if (finished) {
-    finalize_task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(&HandleBase::Finalize, handle));
-    return;
-  }
-  process_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&OffloadAlgorithmRunner::Process,
-                                base::Unretained(this), handle));
-}
-
-}  // namespace dom
-}  // namespace cobalt
diff --git a/cobalt/dom/serialized_algorithm_runner.h b/cobalt/dom/serialized_algorithm_runner.h
index d4c2b39..db625ad 100644
--- a/cobalt/dom/serialized_algorithm_runner.h
+++ b/cobalt/dom/serialized_algorithm_runner.h
@@ -22,6 +22,7 @@
 #include "base/callback.h"
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/trace_event/trace_event.h"
 #include "starboard/common/mutex.h"
@@ -53,37 +54,56 @@
 // This class will keep calling Process() until |finished| becomes false, and
 // then call Finalize().  It guarantees that all calls won't be overlapped and
 // the member functions of the algorithm don't have to synchronize between them.
+template <typename SerializedAlgorithm>
 class SerializedAlgorithmRunner {
  public:
-  // Abstract the non-template part of the Handle class, and shouldn't be used
-  // directly.
-  class HandleBase : public base::RefCountedThreadSafe<HandleBase> {
-   public:
-    virtual ~HandleBase() {}
-
-    virtual void Process(bool* finished) = 0;
-    virtual void Finalize() = 0;
-  };
-
   // A handle object for a running algorithm instance, to allow for aborting and
   // access the algorithm.
-  template <typename SerializedAlgorithm>
-  class Handle : public HandleBase {
+  class Handle : public base::RefCountedThreadSafe<Handle> {
    public:
-    explicit Handle(std::unique_ptr<SerializedAlgorithm> algorithm);
-
     // Abort the algorithm and no more processing will happen on return.  It is
     // possible that Process() has already finished asynchronously, in which
     // case this function will call Finalize() instead (if it hasn't been called
     // yet).
     void Abort();
+    void Process(bool* finished);
+    void FinalizeIfNotAborted();
+
     SerializedAlgorithm* algorithm() const { return algorithm_.get(); }
 
    private:
-    void Process(bool* finished) override;
-    void Finalize() override;
+    friend class SerializedAlgorithmRunner;
 
-    // The |mutex_| is necessary as `Abort()` can be called from any thread.
+    // Provide synchronization only when |synchronization_required| is true.
+    // This allows bypassing of synchronization for algorithm runners that
+    // operate on a single thread, where a mutex could be reentrant if acquired
+    // due to nested calls.
+    class ScopedLockWhenRequired {
+     public:
+      ScopedLockWhenRequired(bool synchronization_required,
+                             const starboard::Mutex& mutex)
+          : synchronization_required_(synchronization_required), mutex_(mutex) {
+        if (synchronization_required_) {
+          mutex_.Acquire();
+        }
+      }
+      ~ScopedLockWhenRequired() {
+        if (synchronization_required_) {
+          mutex_.Release();
+        }
+      }
+
+     private:
+      const bool synchronization_required_;
+      const starboard::Mutex& mutex_;
+    };
+
+    Handle(bool synchronization_required,
+           std::unique_ptr<SerializedAlgorithm> algorithm);
+
+    // The |mutex_| is necessary for algorithm runners operate on multiple
+    // threads as `Abort()` can be called from any thread.
+    const bool synchronization_required_;
     starboard::Mutex mutex_;
     std::unique_ptr<SerializedAlgorithm> algorithm_;
     bool aborted_ = false;
@@ -93,21 +113,32 @@
 
   virtual ~SerializedAlgorithmRunner() {}
 
-  virtual void Start(const scoped_refptr<HandleBase>& handle) = 0;
+  virtual scoped_refptr<Handle> CreateHandle(
+      std::unique_ptr<SerializedAlgorithm> algorithm) = 0;
+  virtual void Start(scoped_refptr<Handle> handle) = 0;
+
+ protected:
+  scoped_refptr<Handle> CreateHandle(
+      bool synchronization_required,
+      std::unique_ptr<SerializedAlgorithm> algorithm) {
+    return new Handle(synchronization_required, std::move(algorithm));
+  }
 };
 
 template <typename SerializedAlgorithm>
-SerializedAlgorithmRunner::Handle<SerializedAlgorithm>::Handle(
+SerializedAlgorithmRunner<SerializedAlgorithm>::Handle::Handle(
+    bool synchronization_required,
     std::unique_ptr<SerializedAlgorithm> algorithm)
-    : algorithm_(std::move(algorithm)) {
+    : synchronization_required_(synchronization_required),
+      algorithm_(std::move(algorithm)) {
   DCHECK(algorithm_);
 }
 
 template <typename SerializedAlgorithm>
-void SerializedAlgorithmRunner::Handle<SerializedAlgorithm>::Abort() {
+void SerializedAlgorithmRunner<SerializedAlgorithm>::Handle::Abort() {
   TRACE_EVENT0("cobalt::dom", "SerializedAlgorithmRunner::Handle::Abort()");
 
-  starboard::ScopedLock scoped_lock(mutex_);
+  ScopedLockWhenRequired scoped_lock(synchronization_required_, mutex_);
 
   DCHECK(!aborted_);  // Abort() cannot be called twice.
 
@@ -126,18 +157,19 @@
 }
 
 template <typename SerializedAlgorithm>
-void SerializedAlgorithmRunner::Handle<SerializedAlgorithm>::Process(
+void SerializedAlgorithmRunner<SerializedAlgorithm>::Handle::Process(
     bool* finished) {
   TRACE_EVENT0("cobalt::dom", "SerializedAlgorithmRunner::Handle::Process()");
 
   DCHECK(finished);
 
-  starboard::ScopedLock scoped_lock(mutex_);
+  ScopedLockWhenRequired scoped_lock(synchronization_required_, mutex_);
 
   DCHECK(!finished_);
   DCHECK(!finalized_);
 
   if (aborted_) {
+    *finished = true;
     return;
   }
 
@@ -147,17 +179,19 @@
 }
 
 template <typename SerializedAlgorithm>
-void SerializedAlgorithmRunner::Handle<SerializedAlgorithm>::Finalize() {
+void SerializedAlgorithmRunner<
+    SerializedAlgorithm>::Handle::FinalizeIfNotAborted() {
   TRACE_EVENT0("cobalt::dom", "SerializedAlgorithmRunner::Handle::Finalize()");
-  starboard::ScopedLock scoped_lock(mutex_);
 
-  DCHECK(finished_);
+  ScopedLockWhenRequired scoped_lock(synchronization_required_, mutex_);
+
   DCHECK(!finalized_);
 
   if (aborted_) {
     return;
   }
 
+  DCHECK(finished_);
   DCHECK(algorithm_);
 
   algorithm_->Finalize();
@@ -167,14 +201,21 @@
 
 // This class runs algorithm on the task runner associated with the thread where
 // Start() is called.
-class DefaultAlgorithmRunner : public SerializedAlgorithmRunner {
+template <typename SerializedAlgorithm>
+class DefaultAlgorithmRunner
+    : public SerializedAlgorithmRunner<SerializedAlgorithm> {
  public:
   explicit DefaultAlgorithmRunner(bool asynchronous_reduction_enabled)
       : asynchronous_reduction_enabled_(asynchronous_reduction_enabled) {}
 
  private:
-  void Start(const scoped_refptr<HandleBase>& handle) override;
-  void Process(const scoped_refptr<HandleBase>& handle);
+  typedef
+      typename SerializedAlgorithmRunner<SerializedAlgorithm>::Handle Handle;
+
+  scoped_refptr<Handle> CreateHandle(
+      std::unique_ptr<SerializedAlgorithm> algorithm) override;
+  void Start(scoped_refptr<Handle> handle) override;
+  void Process(scoped_refptr<Handle> handle);
 
   const bool asynchronous_reduction_enabled_;
 };
@@ -185,7 +226,9 @@
 // This class will keep calling the Process() member function of the algorithm
 // on the process task runner, and then call Finalize() on the finalize task
 // runner when |finished| becomes true.
-class OffloadAlgorithmRunner : public SerializedAlgorithmRunner {
+template <typename SerializedAlgorithm>
+class OffloadAlgorithmRunner
+    : public SerializedAlgorithmRunner<SerializedAlgorithm> {
  public:
   typedef base::SingleThreadTaskRunner TaskRunner;
 
@@ -193,13 +236,121 @@
                          const scoped_refptr<TaskRunner>& finalize_task_runner);
 
  private:
-  void Start(const scoped_refptr<HandleBase>& handle) override;
-  void Process(const scoped_refptr<HandleBase>& handle);
+  typedef
+      typename SerializedAlgorithmRunner<SerializedAlgorithm>::Handle Handle;
+
+  scoped_refptr<Handle> CreateHandle(
+      std::unique_ptr<SerializedAlgorithm> algorithm) override;
+  void Start(scoped_refptr<Handle> handle) override;
+  void Process(scoped_refptr<Handle> handle);
 
   scoped_refptr<TaskRunner> process_task_runner_;
   scoped_refptr<TaskRunner> finalize_task_runner_;
 };
 
+template <typename SerializedAlgorithm>
+scoped_refptr<typename SerializedAlgorithmRunner<SerializedAlgorithm>::Handle>
+DefaultAlgorithmRunner<SerializedAlgorithm>::CreateHandle(
+    std::unique_ptr<SerializedAlgorithm> algorithm) {
+  TRACE_EVENT0("cobalt::dom", "DefaultAlgorithmRunner::CreateHandle()");
+
+  const bool kSynchronizationRequired = false;
+  return SerializedAlgorithmRunner<SerializedAlgorithm>::CreateHandle(
+      kSynchronizationRequired, std::move(algorithm));
+}
+
+template <typename SerializedAlgorithm>
+void DefaultAlgorithmRunner<SerializedAlgorithm>::Start(
+    scoped_refptr<Handle> handle) {
+  DCHECK(handle);
+  TRACE_EVENT0("cobalt::dom", "DefaultAlgorithmRunner::Start()");
+
+  if (asynchronous_reduction_enabled_) {
+    Process(handle);
+    return;
+  }
+
+  auto task_runner = base::MessageLoop::current()->task_runner();
+  task_runner->PostTask(FROM_HERE,
+                        base::BindOnce(&DefaultAlgorithmRunner::Process,
+                                       base::Unretained(this), handle));
+}
+
+template <typename SerializedAlgorithm>
+void DefaultAlgorithmRunner<SerializedAlgorithm>::Process(
+    scoped_refptr<Handle> handle) {
+  DCHECK(handle);
+  TRACE_EVENT0("cobalt::dom", "DefaultAlgorithmRunner::Process()");
+
+  auto task_runner = base::MessageLoop::current()->task_runner();
+
+  bool finished = false;
+  handle->Process(&finished);
+
+  if (finished) {
+    handle->FinalizeIfNotAborted();
+    return;
+  }
+  task_runner->PostTask(FROM_HERE,
+                        base::BindOnce(&DefaultAlgorithmRunner::Process,
+                                       base::Unretained(this), handle));
+}
+
+template <typename SerializedAlgorithm>
+OffloadAlgorithmRunner<SerializedAlgorithm>::OffloadAlgorithmRunner(
+    const scoped_refptr<TaskRunner>& process_task_runner,
+    const scoped_refptr<TaskRunner>& finalize_task_runner)
+    : process_task_runner_(process_task_runner),
+      finalize_task_runner_(finalize_task_runner) {
+  DCHECK(process_task_runner_);
+  DCHECK(finalize_task_runner_);
+  DCHECK_NE(process_task_runner_, finalize_task_runner_);
+}
+
+template <typename SerializedAlgorithm>
+scoped_refptr<typename SerializedAlgorithmRunner<SerializedAlgorithm>::Handle>
+OffloadAlgorithmRunner<SerializedAlgorithm>::CreateHandle(
+    std::unique_ptr<SerializedAlgorithm> algorithm) {
+  TRACE_EVENT0("cobalt::dom", "OffloadAlgorithmRunner::CreateHandle()");
+
+  const bool kSynchronizationRequired = true;
+  return SerializedAlgorithmRunner<SerializedAlgorithm>::CreateHandle(
+      kSynchronizationRequired, std::move(algorithm));
+}
+
+template <typename SerializedAlgorithm>
+void OffloadAlgorithmRunner<SerializedAlgorithm>::Start(
+    scoped_refptr<Handle> handle) {
+  DCHECK(handle);
+
+  TRACE_EVENT0("cobalt::dom", "OffloadAlgorithmRunner::Start()");
+
+  process_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&OffloadAlgorithmRunner::Process,
+                                base::Unretained(this), handle));
+}
+
+template <typename SerializedAlgorithm>
+void OffloadAlgorithmRunner<SerializedAlgorithm>::Process(
+    scoped_refptr<Handle> handle) {
+  DCHECK(handle);
+  DCHECK(process_task_runner_->BelongsToCurrentThread());
+
+  TRACE_EVENT0("cobalt::dom", "OffloadAlgorithmRunner::Process()");
+
+  bool finished = false;
+  handle->Process(&finished);
+
+  if (finished) {
+    finalize_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(&Handle::FinalizeIfNotAborted, handle));
+    return;
+  }
+  process_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&OffloadAlgorithmRunner::Process,
+                                base::Unretained(this), handle));
+}
+
 }  // namespace dom
 }  // namespace cobalt
 
diff --git a/cobalt/dom/source_buffer.cc b/cobalt/dom/source_buffer.cc
index ffaee80..1f6278b 100644
--- a/cobalt/dom/source_buffer.cc
+++ b/cobalt/dom/source_buffer.cc
@@ -387,11 +387,11 @@
                      base::Unretained(this)),
           base::Bind(&SourceBuffer::OnAlgorithmFinalized,
                      base::Unretained(this))));
+  auto algorithm_runner =
+      media_source_->GetAlgorithmRunner(std::numeric_limits<int>::max());
   active_algorithm_handle_ =
-      new SerializedAlgorithmRunner::Handle<SourceBufferAlgorithm>(
-          std::move(algorithm));
-  media_source_->GetAlgorithmRunner(std::numeric_limits<int>::max())
-      ->Start(active_algorithm_handle_);
+      algorithm_runner->CreateHandle(std::move(algorithm));
+  algorithm_runner->Start(active_algorithm_handle_);
 }
 
 void SourceBuffer::set_track_defaults(
@@ -482,9 +482,13 @@
 }
 
 void SourceBuffer::ScheduleAndMaybeDispatchImmediately(base::Token event_name) {
+  ScheduleEvent(event_name);
+  // TODO(b/244773734): Re-enable direct event dispatching
+  /*
   scoped_refptr<web::Event> event = new web::Event(event_name);
   event->set_target(this);
   event_queue_->EnqueueAndMaybeDispatchImmediately(event);
+  */
 }
 
 bool SourceBuffer::PrepareAppend(size_t new_data_size,
@@ -509,6 +513,7 @@
     return false;
   }
 
+  metrics_.StartTracking();
   media_source_->OpenIfInEndedState();
 
   double current_time = media_source_->GetMediaElement()->current_time(NULL);
@@ -517,9 +522,11 @@
           new_data_size + evict_extra_in_bytes_)) {
     web::DOMException::Raise(web::DOMException::kQuotaExceededErr,
                              exception_state);
+    metrics_.EndTracking(0);
     return false;
   }
 
+  metrics_.EndTracking(0);
   return true;
 }
 
@@ -528,11 +535,9 @@
     script::ExceptionState* exception_state) {
   TRACE_EVENT1("cobalt::dom", "SourceBuffer::AppendBufferInternal()", "size",
                size);
-  metrics_.StartTracking();
   if (!PrepareAppend(size, exception_state)) {
     return;
   }
-  metrics_.EndTracking(0);
 
   DCHECK(data || size == 0);
 
@@ -562,10 +567,10 @@
           base::Bind(&SourceBuffer::OnAlgorithmFinalized,
                      base::Unretained(this)),
           &metrics_));
+  auto algorithm_runner = media_source_->GetAlgorithmRunner(size);
   active_algorithm_handle_ =
-      new SerializedAlgorithmRunner::Handle<SourceBufferAlgorithm>(
-          std::move(algorithm));
-  media_source_->GetAlgorithmRunner(size)->Start(active_algorithm_handle_);
+      algorithm_runner->CreateHandle(std::move(algorithm));
+  algorithm_runner->Start(active_algorithm_handle_);
 }
 
 void SourceBuffer::OnAlgorithmFinalized() {
diff --git a/cobalt/dom/source_buffer.h b/cobalt/dom/source_buffer.h
index 0cf7f28..c7336e6 100644
--- a/cobalt/dom/source_buffer.h
+++ b/cobalt/dom/source_buffer.h
@@ -204,7 +204,7 @@
   starboard::Mutex timestamp_offset_mutex_;
   double timestamp_offset_ = 0;
 
-  scoped_refptr<SerializedAlgorithmRunner::Handle<SourceBufferAlgorithm>>
+  scoped_refptr<SerializedAlgorithmRunner<SourceBufferAlgorithm>::Handle>
       active_algorithm_handle_;
   double append_window_start_ = 0;
   double append_window_end_ = std::numeric_limits<double>::infinity();
diff --git a/cobalt/dom/storage.cc b/cobalt/dom/storage.cc
index 010890f..5706528 100644
--- a/cobalt/dom/storage.cc
+++ b/cobalt/dom/storage.cc
@@ -57,7 +57,7 @@
       key, old_value, new_value, window_->document()->url(), this));
 }
 
-GURL Storage::origin() const { return window_->document()->url_as_gurl(); }
+GURL Storage::origin() const { return window_->document()->location()->url(); }
 
 }  // namespace dom
 }  // namespace cobalt
diff --git a/cobalt/dom/xml_document.h b/cobalt/dom/xml_document.h
index 422b6e4..8e3f684 100644
--- a/cobalt/dom/xml_document.h
+++ b/cobalt/dom/xml_document.h
@@ -29,7 +29,7 @@
   // Custom, not in any spec: Node.
   scoped_refptr<Node> Duplicate() const override {
     return new XMLDocument(html_element_context(),
-                           Document::Options(url_as_gurl()));
+                           Document::Options(location()->url()));
   }
 
   // Custom, not in any spec: Document.
diff --git a/cobalt/h5vcc/BUILD.gn b/cobalt/h5vcc/BUILD.gn
index fd7216f..188c6ed 100644
--- a/cobalt/h5vcc/BUILD.gn
+++ b/cobalt/h5vcc/BUILD.gn
@@ -96,8 +96,8 @@
 
   if (enable_account_manager) {
     sources += [
-      "h5vcc_account_manager.cc",
-      "h5vcc_account_manager.h",
+      "h5vcc_account_manager_internal.cc",
+      "h5vcc_account_manager_internal.h",
     ]
   }
 
diff --git a/cobalt/h5vcc/h5vcc.cc b/cobalt/h5vcc/h5vcc.cc
index 806f7a1..e88b901 100644
--- a/cobalt/h5vcc/h5vcc.cc
+++ b/cobalt/h5vcc/h5vcc.cc
@@ -14,6 +14,11 @@
 
 #include "cobalt/h5vcc/h5vcc.h"
 
+#if defined(COBALT_ENABLE_ACCOUNT_MANAGER)
+#include "cobalt/h5vcc/h5vcc_account_manager_internal.h"
+#include "cobalt/script/source_code.h"
+#endif
+
 #include "cobalt/persistent_storage/persistent_settings.h"
 #include "cobalt/sso/sso_interface.h"
 
@@ -45,6 +50,23 @@
 #else
   system_ = new H5vccSystem();
 #endif
+
+#if defined(COBALT_ENABLE_ACCOUNT_MANAGER)
+  // Bind "H5vccAccountManager" if it is supported. (This is not to be confused
+  // with settings.account_manager.)
+  if (H5vccAccountManagerInternal::IsSupported()) {
+    // Since we don't want to bind an instance of a wrappable, we cannot use
+    // Bind() nor BindTo(). Instead, just evaluate a script to alias the type.
+    scoped_refptr<script::SourceCode> source =
+        script::SourceCode::CreateSourceCode(
+            "H5vccAccountManager = H5vccAccountManagerInternal;"
+            "window.H5vccAccountManager = window.H5vccAccountManagerInternal;",
+            base::SourceLocation("h5vcc.cc", __LINE__, 1));
+    std::string result;
+    bool success = settings.global_environment->EvaluateScript(source, &result);
+    CHECK(success);
+  }
+#endif
 }
 
 void H5vcc::TraceMembers(script::Tracer* tracer) {
diff --git a/cobalt/h5vcc/h5vcc_account_manager.cc b/cobalt/h5vcc/h5vcc_account_manager_internal.cc
similarity index 77%
rename from cobalt/h5vcc/h5vcc_account_manager.cc
rename to cobalt/h5vcc/h5vcc_account_manager_internal.cc
index 8705df7..1c0a3c2 100644
--- a/cobalt/h5vcc/h5vcc_account_manager.cc
+++ b/cobalt/h5vcc/h5vcc_account_manager_internal.cc
@@ -14,7 +14,7 @@
 
 #include <memory>
 
-#include "cobalt/h5vcc/h5vcc_account_manager.h"
+#include "cobalt/h5vcc/h5vcc_account_manager_internal.h"
 
 #include "base/command_line.h"
 #include "base/memory/weak_ptr.h"
@@ -24,32 +24,42 @@
 namespace cobalt {
 namespace h5vcc {
 
-H5vccAccountManager::H5vccAccountManager()
+H5vccAccountManagerInternal::H5vccAccountManagerInternal()
     : user_authorizer_(account::UserAuthorizer::Create()),
       owning_message_loop_(base::MessageLoop::current()),
       thread_("AccountManager") {
   thread_.Start();
 }
 
-void H5vccAccountManager::GetAuthToken(
+// static
+bool H5vccAccountManagerInternal::IsSupported() {
+  auto account_manager = account::UserAuthorizer::Create();
+  if (account_manager) {
+    delete account_manager;
+    return true;
+  }
+  return false;
+}
+
+void H5vccAccountManagerInternal::GetAuthToken(
     const AccessTokenCallbackHolder& callback) {
   DLOG(INFO) << "Get authorization token.";
   PostOperation(kGetToken, callback);
 }
 
-void H5vccAccountManager::RequestPairing(
+void H5vccAccountManagerInternal::RequestPairing(
     const AccessTokenCallbackHolder& callback) {
   DLOG(INFO) << "Request application linking.";
   PostOperation(kPairing, callback);
 }
 
-void H5vccAccountManager::RequestUnpairing(
+void H5vccAccountManagerInternal::RequestUnpairing(
     const AccessTokenCallbackHolder& callback) {
   DLOG(INFO) << "Request application unlinking.";
   PostOperation(kUnpairing, callback);
 }
 
-void H5vccAccountManager::PostOperation(
+void H5vccAccountManagerInternal::PostOperation(
     OperationType operation_type, const AccessTokenCallbackHolder& callback) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   AccessTokenCallbackReference* token_callback =
@@ -57,31 +67,38 @@
   pending_callbacks_.push_back(
       std::unique_ptr<AccessTokenCallbackReference>(token_callback));
   thread_.message_loop()->task_runner()->PostTask(
-      FROM_HERE, base::Bind(&H5vccAccountManager::RequestOperationInternal,
-                            user_authorizer_.get(), operation_type,
-                            base::Bind(&H5vccAccountManager::PostResult,
-                                       owning_message_loop_,
-                                       base::AsWeakPtr(this), token_callback)));
+      FROM_HERE,
+      base::Bind(&H5vccAccountManagerInternal::RequestOperationInternal,
+                 user_authorizer_.get(), operation_type,
+                 base::Bind(&H5vccAccountManagerInternal::PostResult,
+                            owning_message_loop_, base::AsWeakPtr(this),
+                            token_callback)));
 }
 
-H5vccAccountManager::~H5vccAccountManager() {
+H5vccAccountManagerInternal::~H5vccAccountManagerInternal() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   // Give the UserAuthorizer a chance to abort any long running pending requests
   // before the message loop gets shut down.
-  user_authorizer_->Shutdown();
+  if (user_authorizer_) {
+    user_authorizer_->Shutdown();
+  }
 }
 
 // static
-void H5vccAccountManager::RequestOperationInternal(
+void H5vccAccountManagerInternal::RequestOperationInternal(
     account::UserAuthorizer* user_authorizer, OperationType operation,
     const base::Callback<void(const std::string&, uint64_t)>& post_result) {
+  bool enabled = user_authorizer != nullptr;
 #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
   if (command_line->HasSwitch(browser::switches::kDisableSignIn)) {
+    enabled = false;
+  }
+#endif  // defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
+  if (!enabled) {
     post_result.Run(std::string(), 0);
     return;
   }
-#endif  // defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
 
   SbUser current_user = SbUserGetCurrent();
   DCHECK(SbUserIsValid(current_user));
@@ -128,18 +145,18 @@
 }
 
 // static
-void H5vccAccountManager::PostResult(
+void H5vccAccountManagerInternal::PostResult(
     base::MessageLoop* message_loop,
-    base::WeakPtr<H5vccAccountManager> h5vcc_account_manager,
+    base::WeakPtr<H5vccAccountManagerInternal> h5vcc_account_manager,
     AccessTokenCallbackReference* token_callback, const std::string& token,
     uint64_t expiration_in_seconds) {
   message_loop->task_runner()->PostTask(
-      FROM_HERE,
-      base::Bind(&H5vccAccountManager::SendResult, h5vcc_account_manager,
-                 token_callback, token, expiration_in_seconds));
+      FROM_HERE, base::Bind(&H5vccAccountManagerInternal::SendResult,
+                            h5vcc_account_manager, token_callback, token,
+                            expiration_in_seconds));
 }
 
-void H5vccAccountManager::SendResult(
+void H5vccAccountManagerInternal::SendResult(
     AccessTokenCallbackReference* token_callback, const std::string& token,
     uint64_t expiration_in_seconds) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
diff --git a/cobalt/h5vcc/h5vcc_account_manager.h b/cobalt/h5vcc/h5vcc_account_manager_internal.h
similarity index 68%
rename from cobalt/h5vcc/h5vcc_account_manager.h
rename to cobalt/h5vcc/h5vcc_account_manager_internal.h
index 9057f3c..94585a0 100644
--- a/cobalt/h5vcc/h5vcc_account_manager.h
+++ b/cobalt/h5vcc/h5vcc_account_manager_internal.h
@@ -12,12 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_H5VCC_H5VCC_ACCOUNT_MANAGER_H_
-#define COBALT_H5VCC_H5VCC_ACCOUNT_MANAGER_H_
+#ifndef COBALT_H5VCC_H5VCC_ACCOUNT_MANAGER_INTERNAL_H_
+#define COBALT_H5VCC_H5VCC_ACCOUNT_MANAGER_INTERNAL_H_
 
 #include <memory>
 #include <queue>
 #include <string>
+#include <vector>
 
 #include "base/memory/weak_ptr.h"
 #include "base/message_loop/message_loop.h"
@@ -31,24 +32,32 @@
 namespace cobalt {
 namespace h5vcc {
 
-// Implementation of the H5vccAccountManager interface. Requests will be handled
-// one-at-time on another thread in FIFO order. When a request is complete, the
-// AccessTokenCallback will be fired on the thread that the H5vccAccountManager
-// was created on.
-class H5vccAccountManager : public script::Wrappable,
-                            public base::SupportsWeakPtr<H5vccAccountManager> {
+// Implementation of the H5vccAccountManagerInternal interface. Requests will
+// be handled one-at-time on another thread in FIFO order. When a request is
+// complete, the AccessTokenCallback will be fired on the thread that the
+// H5vccAccountManagerInternal was created on.
+class H5vccAccountManagerInternal
+    : public script::Wrappable,
+      public base::SupportsWeakPtr<H5vccAccountManagerInternal> {
  public:
   typedef script::CallbackFunction<bool(const std::string&, uint64_t)>
       AccessTokenCallback;
   typedef script::ScriptValue<AccessTokenCallback> AccessTokenCallbackHolder;
 
-  H5vccAccountManager();
-  // H5vccAccountManager interface.
+  // Some platforms may enable the account manager interface at compile-time
+  // but need a runtime check to determine if it should be presented to the
+  // web app. If so, H5vccAccountManagerInternal will be bound to
+  // "H5vccAccountManager".
+  static bool IsSupported();
+
+  H5vccAccountManagerInternal();
+
+  // H5vccAccountManagerInternal interface.
   void GetAuthToken(const AccessTokenCallbackHolder& callback);
   void RequestPairing(const AccessTokenCallbackHolder& callback);
   void RequestUnpairing(const AccessTokenCallbackHolder& callback);
 
-  DEFINE_WRAPPABLE_TYPE(H5vccAccountManager);
+  DEFINE_WRAPPABLE_TYPE(H5vccAccountManagerInternal);
 
  private:
   typedef script::ScriptValue<AccessTokenCallback>::Reference
@@ -59,24 +68,24 @@
     kGetToken,
   };
 
-  ~H5vccAccountManager();
+  ~H5vccAccountManagerInternal();
 
   // Posts an operation to the account manager thread.
   void PostOperation(OperationType operation_type,
                      const AccessTokenCallbackHolder& callback);
 
   // Processes an operation on the account manager thread. Static because
-  // H5vccAccountManager may have been destructed before this runs.
+  // H5vccAccountManagerInternal may have been destructed before this runs.
   static void RequestOperationInternal(
       account::UserAuthorizer* user_authorizer, OperationType operation,
       const base::Callback<void(const std::string&, uint64_t)>& post_result);
 
   // Posts the result of an operation from the account manager thread back to
-  // the owning thread. Static because H5vccAccountManager may have been
+  // the owning thread. Static because H5vccAccountManagerInternal may have been
   // destructed before this runs.
   static void PostResult(
       base::MessageLoop* message_loop,
-      base::WeakPtr<H5vccAccountManager> h5vcc_account_manager,
+      base::WeakPtr<H5vccAccountManagerInternal> h5vcc_account_manager,
       AccessTokenCallbackReference* token_callback, const std::string& token,
       uint64_t expiration_in_seconds);
 
@@ -93,9 +102,9 @@
   // Thread checker for the thread that creates this instance.
   THREAD_CHECKER(thread_checker_);
 
-  // The message loop that the H5vccAccountManager was created on. The public
-  // interface must be called from this message loop, and callbacks will be
-  // fired on this loop as well.
+  // The message loop that the H5vccAccountManagerInternal was created on. The
+  // public interface must be called from this message loop, and callbacks will
+  // be fired on this loop as well.
   base::MessageLoop* owning_message_loop_;
 
   // Each incoming request will have a corresponding task posted to this
@@ -105,11 +114,11 @@
   // message loop gets flushed.
   base::Thread thread_;
 
-  friend class scoped_refptr<H5vccAccountManager>;
-  DISALLOW_COPY_AND_ASSIGN(H5vccAccountManager);
+  friend class scoped_refptr<H5vccAccountManagerInternal>;
+  DISALLOW_COPY_AND_ASSIGN(H5vccAccountManagerInternal);
 };
 
 }  // namespace h5vcc
 }  // namespace cobalt
 
-#endif  // COBALT_H5VCC_H5VCC_ACCOUNT_MANAGER_H_
+#endif  // COBALT_H5VCC_H5VCC_ACCOUNT_MANAGER_INTERNAL_H_
diff --git a/cobalt/h5vcc/h5vcc_account_manager.idl b/cobalt/h5vcc/h5vcc_account_manager_internal.idl
similarity index 95%
rename from cobalt/h5vcc/h5vcc_account_manager.idl
rename to cobalt/h5vcc/h5vcc_account_manager_internal.idl
index a62f2d5..0c648a2 100644
--- a/cobalt/h5vcc/h5vcc_account_manager.idl
+++ b/cobalt/h5vcc/h5vcc_account_manager_internal.idl
@@ -15,7 +15,7 @@
   Conditional=COBALT_ENABLE_ACCOUNT_MANAGER,
   Constructor
 ]
-interface H5vccAccountManager {
+interface H5vccAccountManagerInternal {
   void getAuthToken(AccessTokenCallback callback);
   void requestPairing(AccessTokenCallback callback);
   void requestUnpairing(AccessTokenCallback callback);
diff --git a/cobalt/media_session/media_session_client.cc b/cobalt/media_session/media_session_client.cc
index e668cae..c8290cf 100644
--- a/cobalt/media_session/media_session_client.cc
+++ b/cobalt/media_session/media_session_client.cc
@@ -331,7 +331,7 @@
           ext_image.src = media_image.src().c_str();
           if (ext_image.src == nullptr) {
             // src() is required, but Cobalt IDL parser doesn't enforce it.
-            // http://cs/cobalt/cobalt/media_session/media_image.idl?l=19
+            // See cobalt/media_session/media_image.idl for more info.
             // https://wicg.github.io/mediasession/#dictdef-mediaimage
             LOG(ERROR) << "Required src string for MediaImage is missing.";
           }
diff --git a/cobalt/script/BUILD.gn b/cobalt/script/BUILD.gn
index fb5b577..1582517 100644
--- a/cobalt/script/BUILD.gn
+++ b/cobalt/script/BUILD.gn
@@ -58,6 +58,7 @@
 
   deps = [
     "//cobalt/base",
+    "//cobalt/loader:origin",
     "//nb",
     "//starboard:starboard_headers_only",
     "//third_party/v8",
diff --git a/cobalt/script/environment_settings.h b/cobalt/script/environment_settings.h
index d1761da..1980d9b 100644
--- a/cobalt/script/environment_settings.h
+++ b/cobalt/script/environment_settings.h
@@ -20,6 +20,7 @@
 
 #include "base/memory/ref_counted.h"
 #include "cobalt/base/debugger_hooks.h"
+#include "cobalt/loader/origin.h"
 #include "url/gurl.h"
 
 namespace cobalt {
@@ -47,7 +48,10 @@
   const GURL& creation_url() const { return creation_url_; }
 
   // https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-origin
-  const GURL GetOrigin() const { return creation_url().GetOrigin(); }
+  // TODO(b/244368134): Replace with url::Origin.
+  virtual loader::Origin GetOrigin() const {
+    return loader::Origin(base_url().GetOrigin());
+  }
 
   const base::DebuggerHooks& debugger_hooks() const { return debugger_hooks_; }
 
diff --git a/cobalt/web/message_port.cc b/cobalt/web/message_port.cc
index 519e682..e0eb126 100644
--- a/cobalt/web/message_port.cc
+++ b/cobalt/web/message_port.cc
@@ -66,7 +66,7 @@
 }
 
 void MessagePort::PostMessage(const script::ValueHandleHolder& message) {
-  PostMessageSerialized(std::move(SerializeScriptValue(message)));
+  PostMessageSerialized(std::move(script::SerializeScriptValue(message)));
 }
 
 void MessagePort::PostMessageSerialized(
diff --git a/cobalt/worker/service_worker_jobs.cc b/cobalt/worker/service_worker_jobs.cc
index 0c31df7..e30f364 100644
--- a/cobalt/worker/service_worker_jobs.cc
+++ b/cobalt/worker/service_worker_jobs.cc
@@ -841,7 +841,7 @@
   ResolveJobPromise(job, registration);
   // 8. Let settingsObjects be all environment settings objects whose origin is
   //    registration’s scope url's origin.
-  auto registration_origin = registration->scope_url().GetOrigin();
+  auto registration_origin = loader::Origin(registration->scope_url());
   // 9. For each settingsObject of settingsObjects...
   for (auto& context : web_context_registrations_) {
     if (context->environment_settings()->GetOrigin() == registration_origin) {
@@ -1469,7 +1469,7 @@
   DCHECK_NE(kServiceWorkerStateParsed, state);
   // 2. Set worker's state to state.
   worker->set_state(state);
-  auto worker_origin = worker->script_url().GetOrigin();
+  auto worker_origin = loader::Origin(worker->script_url());
   // 3. Let settingsObjects be all environment settings objects whose origin is
   //    worker's script url's origin.
   // 4. For each settingsObject of settingsObjects...
@@ -1615,7 +1615,7 @@
   // Algorithm for Unregister:
   //   https://w3c.github.io/ServiceWorker/#unregister-algorithm
   // 1. If the origin of job’s scope url is not job’s client's origin, then:
-  if (!url::Origin::Create(job->client->GetOrigin())
+  if (!url::Origin::Create(GURL(job->client->GetOrigin().SerializedOrigin()))
            .IsSameOriginWith(url::Origin::Create(job->scope_url))) {
     // 1.1. Invoke Reject Job Promise with job and "SecurityError" DOMException.
     RejectJobPromise(
diff --git a/cobalt/worker/service_worker_object.cc b/cobalt/worker/service_worker_object.cc
index 193638b..436f256 100644
--- a/cobalt/worker/service_worker_object.cc
+++ b/cobalt/worker/service_worker_object.cc
@@ -153,6 +153,9 @@
   //        Return serviceWorker’s script url.
   //      The origin
   //        Return its registering service worker client's origin.
+  WorkerSettings* worker_settings = new WorkerSettings();
+  worker_settings->set_origin(
+      loader::Origin(containing_service_worker_registration()->scope_url()));
   //      The policy container
   //        Return workerGlobalScope’s policy container.
   //      The time origin
@@ -162,7 +165,8 @@
   //      serviceWorker’s script url, top-level creation URL to null, top-level
   //      origin to an implementation-defined value, target browsing context to
   //      null, and active service worker to null.
-  web_context_->setup_environment_settings(new WorkerSettings());
+
+  web_context_->setup_environment_settings(worker_settings);
   web_context_->environment_settings()->set_creation_url(script_url_);
   scoped_refptr<ServiceWorkerGlobalScope> service_worker_global_scope =
       new ServiceWorkerGlobalScope(web_context_->environment_settings(), this);
diff --git a/cobalt/worker/worker.cc b/cobalt/worker/worker.cc
index aeed129..a820c8f 100644
--- a/cobalt/worker/worker.cc
+++ b/cobalt/worker/worker.cc
@@ -85,8 +85,14 @@
   //    . For the global object, if is shared is true, create a new
   //      SharedWorkerGlobalScope object. Otherwise, create a new
   //      DedicatedWorkerGlobalScope object.
-  web_context_->setup_environment_settings(
-      new WorkerSettings(options_.outside_port));
+  WorkerSettings* worker_settings = new WorkerSettings(options_.outside_port);
+  // From algorithm to set up a worker environment settings object
+  // Let inherited origin be outside settings's origin.
+  // The origin return a unique opaque origin if worker global scope's url's
+  // scheme is "data", and inherited origin otherwise.
+  //   https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#set-up-a-worker-environment-settings-object
+  worker_settings->set_origin(options_.outside_settings->GetOrigin());
+  web_context_->setup_environment_settings(worker_settings);
   // From algorithm for to setup up a worker environment settings object:
   //   https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#set-up-a-worker-environment-settings-object
   // 5. Set settings object's creation URL to worker global scope's url.
diff --git a/cobalt/worker/worker_settings.cc b/cobalt/worker/worker_settings.cc
index 8e53e10..df42733 100644
--- a/cobalt/worker/worker_settings.cc
+++ b/cobalt/worker/worker_settings.cc
@@ -43,6 +43,23 @@
   return context()->GetWindowOrWorkerGlobalScope()->AsWorker()->Url();
 }
 
+loader::Origin WorkerSettings::GetOrigin() const {
+  // From algorithm for to setup up a worker environment settings object:
+  //   https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#set-up-a-worker-environment-settings-object
+  // 3. Let settings object be a new environment settings object whose
+  //    algorithms are defined as follows:
+  //    The origin
+  //    Return a unique opaque origin if worker global scope's url's scheme is
+  //    "data", and inherited origin otherwise.
+  DCHECK(context()->GetWindowOrWorkerGlobalScope()->IsWorker());
+  const GURL& url =
+      context()->GetWindowOrWorkerGlobalScope()->AsWorker()->Url();
+  // TODO(b/244368134): Replace with url::Origin::CreateUniqueOpaque().
+  // Note: This does not have to be specialized for service workers, since
+  // their URL can not be a data URL.
+  if (url.SchemeIs("data")) return loader::Origin();
+  return origin_;
+}
 
 }  // namespace worker
 }  // namespace cobalt
diff --git a/cobalt/worker/worker_settings.h b/cobalt/worker/worker_settings.h
index b2d7097..3550b19 100644
--- a/cobalt/worker/worker_settings.h
+++ b/cobalt/worker/worker_settings.h
@@ -37,9 +37,16 @@
   //
   const GURL& base_url() const override;
 
+  // Return the origin of window's associated Document.
+  //   https://html.spec.whatwg.org/#set-up-a-window-environment-settings-object
+  void set_origin(const loader::Origin& origin) { origin_ = origin; }
+  loader::Origin GetOrigin() const override;
+
  private:
   // Outer message port.
   web::MessagePort* message_port_ = nullptr;
+
+  loader::Origin origin_;
 };
 
 }  // namespace worker
diff --git a/cobalt/xhr/xml_http_request.cc b/cobalt/xhr/xml_http_request.cc
index 56de1ee..d943154 100644
--- a/cobalt/xhr/xml_http_request.cc
+++ b/cobalt/xhr/xml_http_request.cc
@@ -570,7 +570,7 @@
   if (upload_) {
     upload_listener_ = upload_->HasOneOrMoreAttributeEventListener();
   }
-  origin_ = loader::Origin(settings_->GetOrigin());
+  origin_ = settings_->GetOrigin();
   // Step 9
   sent_ = true;
   // Now that a send is happening, prevent this object
diff --git a/cobalt/xhr/xml_http_request.h b/cobalt/xhr/xml_http_request.h
index 046e90f..01979a6 100644
--- a/cobalt/xhr/xml_http_request.h
+++ b/cobalt/xhr/xml_http_request.h
@@ -26,6 +26,7 @@
 #include "cobalt/dom/document.h"
 #include "cobalt/loader/cors_preflight.h"
 #include "cobalt/loader/net_fetcher.h"
+#include "cobalt/loader/origin.h"
 #include "cobalt/script/array_buffer.h"
 #include "cobalt/script/array_buffer_view.h"
 #include "cobalt/script/environment_settings.h"
diff --git a/starboard/CHANGELOG.md b/starboard/CHANGELOG.md
index 69ca601..fddc2c3 100644
--- a/starboard/CHANGELOG.md
+++ b/starboard/CHANGELOG.md
@@ -15,6 +15,9 @@
 [configuration.h](configuration.h).
 
 ## Version 14
+### Require kSbSystemPathStorageDirectory on all platforms.
+Path to directory for permanent persistent storage.
+
 ### Add MP3, FLAC, and PCM values to SbMediaAudioCodec.
 This makes it possible to support these codecs in the future.
 
diff --git a/starboard/android/apk/apk_sources.gni b/starboard/android/apk/apk_sources.gni
index e0378ea..07912f6 100644
--- a/starboard/android/apk/apk_sources.gni
+++ b/starboard/android/apk/apk_sources.gni
@@ -21,7 +21,6 @@
   "//starboard/android/apk/app/src/main/java/dev/cobalt/account/AccessToken.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/account/NoopUserAuthorizer.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/account/UserAuthorizer.java",
-  "//starboard/android/apk/app/src/main/java/dev/cobalt/account/UserAuthorizerImpl.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/AudioPermissionRequester.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltA11yHelper.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java",
diff --git a/starboard/android/apk/app/src/app/AndroidManifest.xml b/starboard/android/apk/app/src/app/AndroidManifest.xml
index 42d8bc0..9c4e11a 100644
--- a/starboard/android/apk/app/src/app/AndroidManifest.xml
+++ b/starboard/android/apk/app/src/app/AndroidManifest.xml
@@ -32,7 +32,7 @@
   <!-- This is needed when targeting API 28+ to use foreground services -->
   <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
 
-  <!-- Used for go/cobalt-ifa and AdvertisingIdClient.Info.getId() -->
+  <!-- https://iabtechlab.com/OTT-IFA, AdvertisingIdClient.Info.getId() -->
   <uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
 
   <application
diff --git a/starboard/android/apk/app/src/app/java/dev/cobalt/app/MainActivity.java b/starboard/android/apk/app/src/app/java/dev/cobalt/app/MainActivity.java
index dc26a8a..ee4cdee 100644
--- a/starboard/android/apk/app/src/app/java/dev/cobalt/app/MainActivity.java
+++ b/starboard/android/apk/app/src/app/java/dev/cobalt/app/MainActivity.java
@@ -16,7 +16,7 @@
 
 import android.app.Activity;
 import android.app.Service;
-import dev.cobalt.account.UserAuthorizerImpl;
+import dev.cobalt.account.NoopUserAuthorizer;
 import dev.cobalt.coat.CobaltActivity;
 import dev.cobalt.coat.CobaltService;
 import dev.cobalt.coat.StarboardBridge;
@@ -42,8 +42,7 @@
             getStarboardBridge().requestStop(0);
           }
         };
-    UserAuthorizerImpl userAuthorizer =
-        new UserAuthorizerImpl(getApplicationContext(), activityHolder, stopRequester);
+    NoopUserAuthorizer userAuthorizer = new NoopUserAuthorizer();
     StarboardBridge bridge =
         new StarboardBridge(
             getApplicationContext(),
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/account/NoopUserAuthorizer.java b/starboard/android/apk/app/src/main/java/dev/cobalt/account/NoopUserAuthorizer.java
index e995dd2..cf5b7f9 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/account/NoopUserAuthorizer.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/account/NoopUserAuthorizer.java
@@ -18,6 +18,7 @@
 import dev.cobalt.util.UsedByNative;
 
 /** UserAuthorizer implementation that doesn't try to sign in. */
+@UsedByNative
 public class NoopUserAuthorizer implements UserAuthorizer {
 
   @Override
@@ -55,5 +56,4 @@
   @Override
   public void onRequestPermissionsResult(
       int requestCode, String[] permissions, int[] grantResults) {}
-
 }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/account/UserAuthorizerImpl.java b/starboard/android/apk/app/src/main/java/dev/cobalt/account/UserAuthorizerImpl.java
deleted file mode 100644
index 1d96237..0000000
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/account/UserAuthorizerImpl.java
+++ /dev/null
@@ -1,537 +0,0 @@
-// Copyright 2017 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package dev.cobalt.account;
-
-import static android.Manifest.permission.GET_ACCOUNTS;
-import static dev.cobalt.util.Log.TAG;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.OnAccountsUpdateListener;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.text.TextUtils;
-import android.widget.Toast;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
-import com.google.android.gms.auth.GoogleAuthException;
-import com.google.android.gms.auth.GoogleAuthUtil;
-import com.google.android.gms.auth.UserRecoverableAuthException;
-import com.google.android.gms.common.AccountPicker;
-import com.google.android.gms.common.AccountPicker.AccountChooserOptions;
-import dev.cobalt.coat.R;
-import dev.cobalt.util.Holder;
-import dev.cobalt.util.Log;
-import dev.cobalt.util.UsedByNative;
-import java.io.IOException;
-import java.util.Arrays;
-
-/**
- * Java side implementation for starboard::android::shared::cobalt::AndroidUserAuthorizer.
- *
- * This implements the following business logic:
- * First run...
- * - if there are no accounts, just be be signed-out
- * - if there is one account, sign-in without any UI
- * - if there are more than one accounts, prompt to choose account
- * Subsequent runs...
- * - sign-in to the same account last used to sign-in
- * - if previously signed-out stay signed-out
- * When user clicks 'sign-in' in the UI...
- * - if there are no accounts, allow user to add an account
- * - if there is one account, sign-in without any UI
- * - if there are more than one accounts, prompt to choose account
- * If the last signed-in account is deleted...
- * - kill the app if stopped in the background to prompt next time it starts
- * - at the next app start, show a toast that the account isn't available
- * - if there are no accounts left, just be be signed-out
- * - if there are one or more accounts left, prompt to choose account
- */
-public class UserAuthorizerImpl implements OnAccountsUpdateListener, UserAuthorizer {
-
-  /** Pseudo account indicating the user chose to be signed-out. */
-  public static final Account SIGNED_OUT_ACCOUNT = new Account("-", "-");
-
-  /** Pseudo account indicating a saved account no longer exists. */
-  private static final Account MISSING_ACCOUNT = new Account("!", "!");
-
-  /** Foreshortened expiry of Google OAuth token, which typically lasts 1 hour. */
-  private static final long DEFAULT_EXPIRY_SECONDS = 5 * 60;
-
-  private static final String GOOGLE_ACCOUNT_TYPE = "com.google";
-  private static final String[] OAUTH_SCOPES = {
-      "https://www.googleapis.com/auth/youtube"
-  };
-
-  private static final String SHARED_PREFS_NAME = "user_auth";
-  private static final String ACCOUNT_NAME_PREF_KEY = "signed_in_account";
-
-  /** The thread on which the current request is running, or null if none. */
-  private volatile Thread requestThread;
-
-  private final Context appContext;
-  private final Holder<Activity> activityHolder;
-  private final Runnable stopRequester;
-  private final Handler mainHandler;
-
-  private Account currentAccount = null;
-  private AccessToken currentToken = null;
-
-  // Result from the account picker UI lands here.
-  private String chosenAccountName;
-
-  private volatile boolean waitingForPermission;
-  private volatile boolean permissionGranted;
-
-  public UserAuthorizerImpl(
-      Context appContext, Holder<Activity> activityHolder, Runnable stopRequester) {
-    this.appContext = appContext;
-    this.activityHolder = activityHolder;
-    this.stopRequester = stopRequester;
-    this.mainHandler = new Handler(Looper.getMainLooper());
-    addOnAccountsUpdatedListener(this);
-  }
-
-  @Override
-  public void shutdown() {
-    removeOnAccountsUpdatedListener(this);
-  }
-
-  @Override
-  @SuppressWarnings("unused")
-  @UsedByNative
-  public void interrupt() {
-    Thread t = requestThread;
-    if (t != null) {
-      t.interrupt();
-    }
-  }
-
-  @Override
-  @SuppressWarnings("unused")
-  @UsedByNative
-  public AccessToken authorizeUser() {
-    ensureBackgroundThread();
-    requestThread = Thread.currentThread();
-    // Let the user choose an account, or add one if there are none to choose.
-    // However, if there's only one account just choose it without any prompt.
-    currentAccount = autoSelectOrAddAccount();
-    writeAccountPref(currentAccount);
-    AccessToken accessToken = refreshCurrentToken();
-    requestThread = null;
-    return accessToken;
-  }
-
-  @Override
-  @SuppressWarnings("unused")
-  @UsedByNative
-  public boolean deauthorizeUser() {
-    ensureBackgroundThread();
-    requestThread = Thread.currentThread();
-    currentAccount = SIGNED_OUT_ACCOUNT;
-    writeAccountPref(currentAccount);
-    clearCurrentToken();
-    requestThread = null;
-    return true;
-  }
-
-  @Override
-  @SuppressWarnings("unused")
-  @UsedByNative
-  public AccessToken refreshAuthorization() {
-    ensureBackgroundThread();
-    requestThread = Thread.currentThread();
-
-    // If we haven't yet determined which account to use, check preferences for a saved account.
-    if (currentAccount == null) {
-      Account savedAccount = readAccountPref();
-      if (savedAccount == null) {
-        // No saved account, so this is the first ever run of the app.
-        currentAccount = autoSelectAccount();
-      } else if (savedAccount.equals(MISSING_ACCOUNT)) {
-        // The saved account got deleted.
-        currentAccount = forceSelectAccount();
-      } else {
-        // Use the saved account.
-        currentAccount = savedAccount;
-      }
-      writeAccountPref(currentAccount);
-    }
-
-    AccessToken accessToken = refreshCurrentToken();
-    requestThread = null;
-    return accessToken;
-  }
-
-  private static void ensureBackgroundThread() {
-    if (Looper.myLooper() == Looper.getMainLooper()) {
-      throw new UnsupportedOperationException("UserAuthorizer can't be called on main thread");
-    }
-  }
-
-  private void showToast(int resId, Object... formatArgs) {
-    final String msg = appContext.getResources().getString(resId, formatArgs);
-    mainHandler.post(new Runnable() {
-      @Override
-      public void run() {
-        Toast.makeText(appContext, msg, Toast.LENGTH_LONG).show();
-      }
-    });
-  }
-
-  private Account readAccountPref() {
-    String savedAccountName = loadSignedInAccountName();
-    if (TextUtils.isEmpty(savedAccountName)) {
-      return null;
-    } else if (savedAccountName.equals(SIGNED_OUT_ACCOUNT.name)) {
-      // Don't request permissions or look for a device account if we were signed-out.
-      return SIGNED_OUT_ACCOUNT;
-    } else if (!checkPermission()) {
-      // We won't be able to get the account without permission, so warn the user and be signed-out.
-      showToast(R.string.starboard_missing_account, savedAccountName);
-      return SIGNED_OUT_ACCOUNT;
-    } else {
-      // Find the saved account name among all accounts on the device.
-      for (Account account : getAccounts()) {
-        if (account.name.equals(savedAccountName)) {
-          return account;
-        }
-      }
-      showToast(R.string.starboard_missing_account, savedAccountName);
-      return MISSING_ACCOUNT;
-    }
-  }
-
-  private void writeAccountPref(Account account) {
-    if (account == null) {
-      return;
-    }
-    // Always write the account name, even if it's the signed-out pseudo account.
-    saveSignedInAccountName(account.name);
-  }
-
-  private void clearCurrentToken() {
-    if (currentToken != null) {
-      clearToken(currentToken.getTokenValue());
-      currentToken = null;
-    }
-  }
-
-  private AccessToken refreshCurrentToken() {
-    clearCurrentToken();
-    if (currentAccount == null || SIGNED_OUT_ACCOUNT.equals(currentAccount)) {
-      return null;
-    }
-    String tokenValue = getToken(currentAccount);
-    if (tokenValue == null) {
-      showToast(R.string.starboard_account_auth_error);
-      tokenValue = "";
-    }
-    // TODO: Get the token details and use the actual expiry.
-    long expiry = System.currentTimeMillis() / 1000 + DEFAULT_EXPIRY_SECONDS;
-    currentToken = new AccessToken(tokenValue, expiry);
-    return currentToken;
-  }
-
-  /**
-   * Prompts the user to select an account, or to add an account if there are none. The prompt is
-   * skipped if there is exactly one account to choose from.
-   */
-  private Account autoSelectOrAddAccount() {
-    if (!checkPermission()) {
-      return SIGNED_OUT_ACCOUNT;
-    }
-    Account[] accounts = getAccounts();
-    if (accounts.length == 1) {
-      return accounts[0];
-    }
-    return selectOrAddAccount();
-  }
-
-  /**
-   * Prompts the user to select an account. The prompt is skipped if there are zero or one accounts
-   * to choose from.
-   */
-  private Account autoSelectAccount() {
-    if (!checkPermission()) {
-      return SIGNED_OUT_ACCOUNT;
-    }
-    Account[] accounts = getAccounts();
-    if (accounts.length == 0) {
-      return SIGNED_OUT_ACCOUNT;
-    } else if (accounts.length == 1) {
-      return accounts[0];
-    }
-    return selectOrAddAccount();
-  }
-
-  /**
-   * Prompts the user to select an account, even if there's only one to choose from. The prompt is
-   * skipped if there are zero accounts to choose from.
-   */
-  private Account forceSelectAccount() {
-    // We don't check permissions before calling selectOrAddAccount() because if the account is
-    // missing, readAccountPref() must have just checked, and we don't want to show permission
-    // prompt or rationale to the user twice.
-    Account[] accounts = getAccounts();
-    if (accounts.length == 0) {
-      return SIGNED_OUT_ACCOUNT;
-    }
-    return selectOrAddAccount();
-  }
-
-  /**
-   * Prompts the user to select an account, even if there's only one to choose from. If there are
-   * zero accounts to choose from, the user is prompted to add one.
-   *
-   * The caller should ensure permissions are granted before calling this method to avoid showing
-   * a picker with accounts that we can't access.
-   */
-  private Account selectOrAddAccount() {
-    String accountName = showAccountPicker();
-    // If user cancelled the picker stay signed-out.
-    if (TextUtils.isEmpty(accountName)) {
-      return SIGNED_OUT_ACCOUNT;
-    }
-    // Get the accounts after the picker in case one was added in the account picker.
-    for (Account account : getAccounts()) {
-      if (account.name.equals(accountName)) {
-        return account;
-      }
-    }
-    // This shouldn't happen, but if it does let the user know we're still signed-out.
-    Log.e(TAG, "Selected account is missing");
-    showToast(R.string.starboard_missing_account, accountName);
-    return SIGNED_OUT_ACCOUNT;
-  }
-
-  private synchronized String showAccountPicker() {
-    Activity activity = activityHolder.get();
-    Intent chooseAccountIntent = newChooseAccountIntent(currentAccount);
-    if (activity == null || chooseAccountIntent == null) {
-      return "";
-    }
-    chosenAccountName = null;
-    activity.startActivityForResult(chooseAccountIntent, R.id.rc_choose_account);
-
-    // Block until the account picker activity returns its result.
-    while (chosenAccountName == null) {
-      try {
-        wait();
-      } catch (InterruptedException e) {
-        Log.e(TAG, "Account picker interrupted");
-        // Return empty string, as if the picker was cancelled.
-        return "";
-      }
-    }
-    return chosenAccountName;
-  }
-
-  @Override
-  public void onActivityResult(int requestCode, int resultCode, Intent data) {
-    if (requestCode == R.id.rc_choose_account) {
-      String accountName = null;
-      if (resultCode == Activity.RESULT_OK) {
-        accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
-      } else if (resultCode != Activity.RESULT_CANCELED) {
-        Log.e(TAG, "Account picker error " + resultCode);
-        showToast(R.string.starboard_account_picker_error);
-      }
-
-      // Notify showAccountPicker() which account was chosen.
-      synchronized (this) {
-        // Return empty string if the picker is cancelled or there's an unexpected result.
-        chosenAccountName = (accountName == null) ? "" : accountName;
-        notifyAll();
-      }
-    }
-  }
-
-  @Override
-  public void onAccountsUpdated(Account[] unused) {
-    if (currentAccount == null || SIGNED_OUT_ACCOUNT.equals(currentAccount)) {
-      // We're not signed-in; the update doesn't affect us.
-      return;
-    }
-    // Call getAccounts() since the param may not match the accounts we can access.
-    for (Account account : getAccounts()) {
-      if (account.name.equals(currentAccount.name)) {
-        // The current account is still there; the update doesn't affect us.
-        return;
-      }
-    }
-    // The current account is gone; leave the app so we prompt for sign-in next time.
-    // This should only happen while stopped in the background since we don't delete accounts.
-    stopRequester.run();
-  }
-
-  /**
-   * Calls framework AccountManager.addOnAccountsUpdatedListener().
-   */
-  private void addOnAccountsUpdatedListener(OnAccountsUpdateListener listener) {
-    AccountManager.get(appContext).addOnAccountsUpdatedListener(listener, null, false);
-  }
-
-  /**
-   * Calls framework AccountManager.removeOnAccountsUpdatedListener().
-   */
-  private void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) {
-    AccountManager.get(appContext).removeOnAccountsUpdatedListener(listener);
-  }
-
-  /**
-   * Calls framework AccountManager.getAccountsByType() for Google accounts.
-   */
-  private Account[] getAccounts() {
-    return AccountManager.get(appContext).getAccountsByType(GOOGLE_ACCOUNT_TYPE);
-  }
-
-  /**
-   * Calls GMS AccountPicker.newChooseAccountIntent().
-   *
-   * Returns an Intent that when started will always show the account picker even if there's just
-   * one account on the device. If there are no accounts on the device it shows the UI to add one.
-   */
-  private Intent newChooseAccountIntent(Account defaultAccount) {
-    AccountChooserOptions chooserOptions = new AccountChooserOptions.Builder()
-        .setSelectedAccount(defaultAccount)
-        .setAllowableAccountsTypes(Arrays.asList(GOOGLE_ACCOUNT_TYPE))
-        .setAlwaysShowAccountPicker(true)
-        .build();
-    return AccountPicker.newChooseAccountIntent(chooserOptions);
-  }
-
-  /**
-   * Calls GMS GoogleAuthUtil.getToken(), without throwing any exceptions.
-   *
-   * Returns an empty string if no token is available for the account.
-   * Returns null if there was an error getting the token.
-   */
-  private String getToken(Account account) {
-    String joinedScopes = "oauth2:" + TextUtils.join(" ", OAUTH_SCOPES);
-    try {
-      return GoogleAuthUtil.getToken(appContext, account, joinedScopes);
-    } catch (UserRecoverableAuthException e) {
-        Log.w(TAG, "Recoverable error getting OAuth token", e);
-        Intent intent = e.getIntent();
-        Activity activity = activityHolder.get();
-        if (intent != null && activity != null) {
-          activity.startActivity(intent);
-        } else {
-          Log.e(TAG, "Failed to recover OAuth token", e);
-        }
-        return null;
-
-    } catch (IOException | GoogleAuthException e) {
-      Log.e(TAG, "Error getting auth token", e);
-      return null;
-    }
-  }
-
-  /**
-   * Calls GMS GoogleAuthUtil.clearToken(), without throwing any exceptions.
-   */
-  private void clearToken(String tokenValue) {
-    try {
-      GoogleAuthUtil.clearToken(appContext, tokenValue);
-    } catch (GoogleAuthException | IOException e) {
-      Log.e(TAG, "Error clearing auth token", e);
-    }
-  }
-
-  /**
-   * Checks whether the app has necessary permissions, asking for them if needed.
-   *
-   * This blocks until permissions are granted/declined, and should not be called on the UI thread.
-   *
-   * Returns true if permissions are granted.
-   */
-  private synchronized boolean checkPermission() {
-    if (ContextCompat.checkSelfPermission(appContext, GET_ACCOUNTS)
-        == PackageManager.PERMISSION_GRANTED) {
-      return true;
-    }
-
-    final Activity activity = activityHolder.get();
-    if (activity == null) {
-      return false;
-    }
-
-    // Check if we have previously been denied permission.
-    if (ActivityCompat.shouldShowRequestPermissionRationale(activity, GET_ACCOUNTS)) {
-      activity.runOnUiThread(new Runnable() {
-        @Override
-        public void run() {
-          Toast.makeText(activity, R.string.starboard_accounts_permission, Toast.LENGTH_LONG)
-              .show();
-        }
-      });
-      return false;
-    }
-
-    // Request permission.
-    waitingForPermission = true;
-    permissionGranted = false;
-    ActivityCompat.requestPermissions(
-        activity, new String[]{GET_ACCOUNTS}, R.id.rc_get_accounts_permission);
-    try {
-      while (waitingForPermission) {
-        wait();
-      }
-    } catch (InterruptedException e) {
-      return false;
-    }
-    return permissionGranted;
-  }
-
-  /**
-   * Callback pass-thru from the Activity with the result from requesting permissions.
-   */
-  @Override
-  public void onRequestPermissionsResult(
-      int requestCode, String[] permissions, int[] grantResults) {
-    if (requestCode == R.id.rc_get_accounts_permission) {
-      synchronized (this) {
-        permissionGranted = grantResults.length > 0
-            && grantResults[0] == PackageManager.PERMISSION_GRANTED;
-        waitingForPermission = false;
-        notifyAll();
-      }
-    }
-  }
-
-  /**
-   * Remember the name of the signed-in account.
-   */
-  private void saveSignedInAccountName(String accountName) {
-    getPreferences().edit().putString(ACCOUNT_NAME_PREF_KEY, accountName).commit();
-  }
-
-  /**
-   * Returns the remembered name of the signed-in account.
-   */
-  private String loadSignedInAccountName() {
-    return getPreferences().getString(ACCOUNT_NAME_PREF_KEY, "");
-  }
-
-  private SharedPreferences getPreferences() {
-    return appContext.getSharedPreferences(SHARED_PREFS_NAME, 0);
-  }
-}
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/ClientLogInfo.java b/starboard/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/ClientLogInfo.java
index 57c39ad..7628f93 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/ClientLogInfo.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/ClientLogInfo.java
@@ -6,10 +6,7 @@
 import dev.cobalt.coat.CobaltService;
 import dev.cobalt.util.Log;
 
-/**
- * ClientLogInfo to report Android API support on android devices, read go/coat-api-support for more
- * info.
- */
+/** ClientLogInfo to report Android API support on android devices. */
 public class ClientLogInfo extends CobaltService {
   public static final String TAG = "ClientLogInfo";
 
diff --git a/starboard/android/shared/cobalt/android_user_authorizer.cc b/starboard/android/shared/cobalt/android_user_authorizer.cc
index d8eb6c9..9b19e75 100644
--- a/starboard/android/shared/cobalt/android_user_authorizer.cc
+++ b/starboard/android/shared/cobalt/android_user_authorizer.cc
@@ -14,6 +14,8 @@
 
 #include "starboard/android/shared/cobalt/android_user_authorizer.h"
 
+#include <memory>
+
 #include "base/time/time.h"
 #include "starboard/android/shared/jni_env_ext.h"
 #include "starboard/android/shared/jni_utils.h"
@@ -29,6 +31,17 @@
 namespace shared {
 namespace cobalt {
 
+bool UserAuthorizerIsSupported() {
+  // If using the NoopUserAuthorizer, then user authorizer functionally is not
+  // supported.
+  JniEnvExt* env = JniEnvExt::Get();
+  jobject local_ref = env->CallStarboardObjectMethodOrAbort(
+      "getUserAuthorizer", "()Ldev/cobalt/account/UserAuthorizer;");
+  return !env->IsInstanceOf(
+      local_ref,
+      env->FindClassExtOrAbort("dev/cobalt/account/NoopUserAuthorizer"));
+}
+
 AndroidUserAuthorizer::AndroidUserAuthorizer() : shutdown_(false) {
   JniEnvExt* env = JniEnvExt::Get();
   jobject local_ref = env->CallStarboardObjectMethodOrAbort(
@@ -71,8 +84,8 @@
                                        "()Z");
 }
 
-std::unique_ptr<AccessToken>
-AndroidUserAuthorizer::RefreshAuthorization(SbUser user) {
+std::unique_ptr<AccessToken> AndroidUserAuthorizer::RefreshAuthorization(
+    SbUser user) {
   SB_DCHECK(user == &::starboard::shared::nouser::g_user);
   if (shutdown_) {
     DLOG(WARNING) << "No-op RefreshAuthorization after shutdown";
@@ -85,8 +98,8 @@
   return CreateAccessToken(j_token.Get());
 }
 
-std::unique_ptr<AccessToken>
-AndroidUserAuthorizer::CreateAccessToken(jobject j_token) {
+std::unique_ptr<AccessToken> AndroidUserAuthorizer::CreateAccessToken(
+    jobject j_token) {
   if (!j_token) {
     return std::unique_ptr<AccessToken>();
   }
@@ -101,8 +114,8 @@
         env->GetStringStandardUTFOrAbort(j_token_string.Get());
   }
 
-  jlong j_expiry = env->CallLongMethodOrAbort(
-      j_token, "getExpirySeconds", "()J");
+  jlong j_expiry =
+      env->CallLongMethodOrAbort(j_token, "getExpirySeconds", "()J");
   if (j_expiry > 0) {
     access_token->expiry =
         base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(j_expiry);
@@ -120,7 +133,10 @@
 namespace account {
 
 UserAuthorizer* UserAuthorizer::Create() {
-  return new ::starboard::android::shared::cobalt::AndroidUserAuthorizer();
+  if (::starboard::android::shared::cobalt::UserAuthorizerIsSupported()) {
+    return new ::starboard::android::shared::cobalt::AndroidUserAuthorizer();
+  }
+  return nullptr;
 }
 
 }  // namespace account
diff --git a/starboard/nplb/system_get_path_test.cc b/starboard/nplb/system_get_path_test.cc
index e320737..44f4e00 100644
--- a/starboard/nplb/system_get_path_test.cc
+++ b/starboard/nplb/system_get_path_test.cc
@@ -69,6 +69,9 @@
 TEST(SbSystemGetPathTest, ReturnsRequiredPaths) {
   BasicTest(kSbSystemPathContentDirectory, true, true, __LINE__);
   BasicTest(kSbSystemPathCacheDirectory, true, true, __LINE__);
+#if SB_API_VERSION >= 14
+  BasicTest(kSbSystemPathStorageDirectory, true, true, __LINE__);
+#endif  // SB_API_VERSION >= 14
 }
 
 TEST(SbSystemGetPathTest, FailsGracefullyZeroBufferLength) {