Import Cobalt 22.lts.4.307648
diff --git a/src/.codespellignorelines b/src/.codespellignorelines
index 4e787e0..e2b2905 100644
--- a/src/.codespellignorelines
+++ b/src/.codespellignorelines
@@ -1 +1,3 @@
-    vp9WhiteList.get("Technicolor").add("STING");
+    vp9AllowList.get("Technicolor").add("STING");
+  return SkSurface::MakeRenderTarget(gr_context_.get(), SkBudgeted::kNo,
+            texture_infos.push_back(TextureInfo("uv", "ba"));
diff --git a/src/base/strings/utf_string_conversions.cc b/src/base/strings/utf_string_conversions.cc
index a83d106..b14410c 100644
--- a/src/base/strings/utf_string_conversions.cc
+++ b/src/base/strings/utf_string_conversions.cc
@@ -200,7 +200,10 @@
   bool res = DoUTFConversion(src_str.data(), src_len32, dest, &dest_len32);
 
   dest_str->resize(dest_len32);
-  dest_str->shrink_to_fit();
+  // dest_str->shrink_to_fit();
+  // Workaround for missing symbols linking issue for Xcode 13.2.1
+  // Mac builds.
+  dest_str->reserve(0);
 
   return res;
 }
diff --git a/src/base/test/scoped_task_environment.cc b/src/base/test/scoped_task_environment.cc
index 50a4632..92daedf 100644
--- a/src/base/test/scoped_task_environment.cc
+++ b/src/base/test/scoped_task_environment.cc
@@ -267,6 +267,11 @@
   mock_time_task_runner_->FastForwardBy(delta);
 }
 
+void ScopedTaskEnvironment::AdvanceMockTickClock(TimeDelta delta) {
+  DCHECK(mock_time_task_runner_);
+  mock_time_task_runner_->AdvanceMockTickClock(delta);
+}
+
 void ScopedTaskEnvironment::FastForwardUntilNoTasksRemain() {
   DCHECK(mock_time_task_runner_);
   mock_time_task_runner_->FastForwardUntilNoTasksRemain();
diff --git a/src/base/test/scoped_task_environment.h b/src/base/test/scoped_task_environment.h
index 2e46073..0f5aa59 100644
--- a/src/base/test/scoped_task_environment.h
+++ b/src/base/test/scoped_task_environment.h
@@ -116,6 +116,8 @@
   // (currently only main thread time is mocked).
   void FastForwardBy(TimeDelta delta);
 
+  void AdvanceMockTickClock(TimeDelta delta);
+
   // Only valid for instances with a MOCK_TIME MainThreadType.
   // Short for FastForwardBy(TimeDelta::Max()).
   void FastForwardUntilNoTasksRemain();
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index 303a70b..36b5eff 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -605,6 +605,11 @@
 #endif  // defined(ENABLE_DEBUGGER) && defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
 {
   DCHECK(!quit_closure_.is_null());
+  if (should_preload) {
+    preload_timestamp_ = timestamp;
+  } else {
+    start_timestamp_ = timestamp;
+  }
   // Check to see if a timed_trace has been set, indicating that we should
   // begin a timed trace upon startup.
   base::TimeDelta trace_duration = GetTimedTraceDuration();
@@ -823,6 +828,12 @@
   options.web_module_options.csp_enforcement_mode = dom::kCspEnforcementEnable;
 
   options.requested_viewport_size = requested_viewport_size;
+
+  // Set callback to collect unload event time before firing document's unload
+  // event.
+  options.web_module_options.collect_unload_event_time_callback = base::Bind(
+      &Application::CollectUnloadEventTimingInfo, base::Unretained(this));
+
   account_manager_.reset(new account::AccountManager());
 
   storage_manager_.reset(new storage::StorageManager(
@@ -866,9 +877,6 @@
 #endif
       options));
 
-  DCHECK(browser_module_);
-  browser_module_->SetApplicationStartOrPreloadTimestamp(should_preload,
-                                                         timestamp);
   UpdateUserAgent();
 
   // Register event callbacks.
@@ -1027,10 +1035,12 @@
         base::Bind(&Application::Start, base::Unretained(this), timestamp));
     return;
   }
+  DCHECK_CALLED_ON_VALID_THREAD(application_event_thread_checker_);
 
+  if (!start_timestamp_.has_value()) {
+    start_timestamp_ = timestamp;
+  }
   OnApplicationEvent(kSbEventTypeStart, timestamp);
-  browser_module_->SetApplicationStartOrPreloadTimestamp(false /*is_preload*/,
-                                                         timestamp);
 }
 
 void Application::Quit() {
@@ -1039,6 +1049,7 @@
         FROM_HERE, base::Bind(&Application::Quit, base::Unretained(this)));
     return;
   }
+  DCHECK_CALLED_ON_VALID_THREAD(application_event_thread_checker_);
 
   quit_closure_.Run();
 }
@@ -1399,14 +1410,34 @@
 }
 #endif
 
-void Application::WebModuleCreated() {
+void Application::WebModuleCreated(WebModule* web_module) {
   TRACE_EVENT0("cobalt::browser", "Application::WebModuleCreated()");
+  DCHECK(web_module);
+  if (preload_timestamp_.has_value()) {
+    web_module->SetApplicationStartOrPreloadTimestamp(
+        true, preload_timestamp_.value());
+  }
+  if (start_timestamp_.has_value()) {
+    web_module->SetApplicationStartOrPreloadTimestamp(false,
+                                                      start_timestamp_.value());
+  }
   DispatchDeepLinkIfNotConsumed();
 #if defined(ENABLE_WEBDRIVER)
   if (web_driver_module_) {
     web_driver_module_->OnWindowRecreated();
   }
 #endif
+  if (!unload_event_start_time_.is_null() ||
+      !unload_event_end_time_.is_null()) {
+    web_module->SetUnloadEventTimingInfo(unload_event_start_time_,
+                                         unload_event_end_time_);
+  }
+}
+
+void Application::CollectUnloadEventTimingInfo(base::TimeTicks start_time,
+                                               base::TimeTicks end_time) {
+  unload_event_start_time_ = start_time;
+  unload_event_end_time_ = end_time;
 }
 
 Application::CValStats::CValStats()
diff --git a/src/cobalt/browser/application.h b/src/cobalt/browser/application.h
index c08330c..229ba18 100644
--- a/src/cobalt/browser/application.h
+++ b/src/cobalt/browser/application.h
@@ -29,6 +29,7 @@
 #include "cobalt/browser/browser_module.h"
 #include "cobalt/browser/memory_tracker/tool.h"
 #include "cobalt/system_window/system_window.h"
+#include "starboard/time.h"
 #if SB_IS(EVERGREEN)
 #include "cobalt/updater/updater_module.h"
 #endif
@@ -68,8 +69,7 @@
   void OnNetworkEvent(const base::Event* event);
 
   // Called to handle an application event.
-  void OnApplicationEvent(SbEventType event_type,
-                          SbTimeMonotonic timestamp);
+  void OnApplicationEvent(SbEventType event_type, SbTimeMonotonic timestamp);
 
   // Called to handle a window size change event.
   void OnWindowSizeChangedEvent(const base::Event* event);
@@ -95,7 +95,10 @@
 #endif
 
   // Called when a navigation occurs in the BrowserModule.
-  void WebModuleCreated();
+  void WebModuleCreated(WebModule* web_module);
+
+  void CollectUnloadEventTimingInfo(base::TimeTicks start_time,
+                                    base::TimeTicks end_time);
 
   // A conduit for system events.
   base::EventDispatcher event_dispatcher_;
@@ -195,6 +198,14 @@
   void UpdatePeriodicStats();
   void DispatchEventInternal(base::Event* event);
 
+  base::Optional<SbTimeMonotonic> preload_timestamp_;
+  base::Optional<SbTimeMonotonic> start_timestamp_;
+
+  // These represent the 'document unload timing info' from the spec to be
+  // passed to the next document.
+  base::TimeTicks unload_event_start_time_;
+  base::TimeTicks unload_event_end_time_;
+
   // The message loop that will handle UI events.
   base::MessageLoop* message_loop_;
 
@@ -230,8 +241,7 @@
   void OnDeepLinkConsumedCallback(const std::string& link);
 
   // Dispatch events for deep links.
-  void DispatchDeepLink(const char* link,
-                        SbTimeMonotonic timestamp);
+  void DispatchDeepLink(const char* link, SbTimeMonotonic timestamp);
   void DispatchDeepLinkIfNotConsumed();
 
   DISALLOW_COPY_AND_ASSIGN(Application);
diff --git a/src/cobalt/browser/browser_bindings_gen.gyp b/src/cobalt/browser/browser_bindings_gen.gyp
index bce6391..f7f97fe 100644
--- a/src/cobalt/browser/browser_bindings_gen.gyp
+++ b/src/cobalt/browser/browser_bindings_gen.gyp
@@ -137,6 +137,7 @@
         '../dom/performance_lifecycle_timing.idl',
         '../dom/performance_mark.idl',
         '../dom/performance_measure.idl',
+        '../dom/performance_navigation_timing.idl',
         '../dom/performance_observer.idl',
         '../dom/performance_observer_entry_list.idl',
         '../dom/performance_resource_timing.idl',
@@ -284,6 +285,7 @@
         '../dom/media_source_ready_state.idl',
         '../dom/mouse_event_init.idl',
         '../dom/mutation_observer_init.idl',
+        '../dom/navigation_type.idl',
         '../dom/navigator_ua_brand_version.idl',
         '../dom/performance_observer_callback_options.idl',
         '../dom/performance_observer_init.idl',
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index 5bea38e..7cf9008 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -71,55 +71,9 @@
 
 namespace cobalt {
 
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
-namespace timestamp {
-// This is a temporary workaround.
-extern SbAtomic64 g_last_render_timestamp;
-}  // namespace timestamp
-
-namespace {
-struct NonTrivialGlobalVariables {
-  NonTrivialGlobalVariables();
-
-  SbAtomic64* last_render_timestamp;
-};
-
-NonTrivialGlobalVariables::NonTrivialGlobalVariables() {
-  last_render_timestamp = &cobalt::timestamp::g_last_render_timestamp;
-  SbAtomicNoBarrier_Exchange64(last_render_timestamp,
-                               static_cast<SbAtomic64>(SbTimeGetNow()));
-}
-
-base::LazyInstance<NonTrivialGlobalVariables>::DestructorAtExit
-    non_trivial_global_variables = LAZY_INSTANCE_INITIALIZER;
-}  // namespace
-#endif  // defined(COBALT_CHECK_RENDER_TIMEOUT)
-
 namespace browser {
 namespace {
 
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
-// Timeout for last render.
-const int kLastRenderTimeoutSeconds = 15;
-
-// Polling interval for timeout_polling_thread_.
-const int kRenderTimeOutPollingDelaySeconds = 1;
-
-// Minimum number of continuous times the timeout expirations. This is used to
-// prevent unintended behavior in situations such as when returning from
-// suspended state. Note that the timeout response trigger will be delayed
-// after the actual timeout expiration by this value times the polling delay.
-const int kMinimumContinuousRenderTimeoutExpirations = 2;
-
-// Name for timeout_polling_thread_.
-const char* kTimeoutPollingThreadName = "TimeoutPolling";
-
-// This specifies the percentage of calls to OnRenderTimeout() that result in a
-// call to OnError().
-const int kRenderTimeoutErrorPercentage = 99;
-
-#endif
-
 // This constant defines the maximum rate at which the layout engine will
 // refresh over time.  Since there is little benefit in performing a layout
 // faster than the display's refresh rate, we set this to 60Hz.
@@ -295,10 +249,6 @@
 #endif  // defined(ENABLE_DEBUGGER)
       has_resumed_(base::WaitableEvent::ResetPolicy::MANUAL,
                    base::WaitableEvent::InitialState::NOT_SIGNALED),
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
-      timeout_polling_thread_(kTimeoutPollingThreadName),
-      render_timeout_count_(0),
-#endif
       on_error_retry_count_(0),
       waiting_for_error_retry_(false),
       will_quit_(false),
@@ -315,21 +265,8 @@
 #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
   SbCoreDumpRegisterHandler(BrowserModule::CoreDumpHandler, this);
   on_error_triggered_count_ = 0;
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
-  recovery_mechanism_triggered_count_ = 0;
-  timeout_response_trigger_count_ = 0;
-#endif  // defined(COBALT_CHECK_RENDER_TIMEOUT)
 #endif  // SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
 
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
-  timeout_polling_thread_.Start();
-  timeout_polling_thread_.message_loop()->task_runner()->PostDelayedTask(
-      FROM_HERE,
-      base::Bind(&BrowserModule::OnPollForRenderTimeout, base::Unretained(this),
-                 url),
-      base::TimeDelta::FromSeconds(kRenderTimeOutPollingDelaySeconds));
-#endif
-
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
 
   // Create the main web module layer.
@@ -651,7 +588,7 @@
       options));
   lifecycle_observers_.AddObserver(web_module_.get());
   if (!web_module_created_callback_.is_null()) {
-    web_module_created_callback_.Run();
+    web_module_created_callback_.Run(web_module_.get());
   }
 
   if (system_window_) {
@@ -677,12 +614,6 @@
       static_cast<BrowserModule*>(browser_module_as_void);
   SbCoreDumpLogInteger("BrowserModule.on_error_triggered_count_",
                        browser_module->on_error_triggered_count_);
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
-  SbCoreDumpLogInteger("BrowserModule.recovery_mechanism_triggered_count_",
-                       browser_module->recovery_mechanism_triggered_count_);
-  SbCoreDumpLogInteger("BrowserModule.timeout_response_trigger_count_",
-                       browser_module->timeout_response_trigger_count_);
-#endif  // defined(COBALT_CHECK_RENDER_TIMEOUT)
 }
 #endif  // SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
 
@@ -1558,52 +1489,6 @@
   }
 }
 
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
-void BrowserModule::OnPollForRenderTimeout(const GURL& url) {
-  SbTime last_render_timestamp = static_cast<SbTime>(SbAtomicAcquire_Load64(
-      non_trivial_global_variables.Get().last_render_timestamp));
-  base::Time last_render = base::Time::FromSbTime(last_render_timestamp);
-  bool timeout_expiration = base::Time::Now() - base::TimeDelta::FromSeconds(
-                                                    kLastRenderTimeoutSeconds) >
-                            last_render;
-  bool timeout_response_trigger = false;
-  if (timeout_expiration) {
-    // The timeout only triggers if the timeout expiration has been detected
-    // without interruption at least kMinimumContinuousRenderTimeoutExpirations
-    // times.
-    ++render_timeout_count_;
-    timeout_response_trigger =
-        render_timeout_count_ >= kMinimumContinuousRenderTimeoutExpirations;
-  } else {
-    render_timeout_count_ = 0;
-  }
-
-  if (timeout_response_trigger) {
-#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
-    timeout_response_trigger_count_++;
-#endif
-    SbAtomicNoBarrier_Exchange64(
-        non_trivial_global_variables.Get().last_render_timestamp,
-        static_cast<SbAtomic64>(kSbTimeMax));
-    if (SbSystemGetRandomUInt64() <
-        kRenderTimeoutErrorPercentage * (UINT64_MAX / 100)) {
-      OnError(url, std::string("Rendering Timeout"));
-#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
-      recovery_mechanism_triggered_count_++;
-#endif
-    } else {
-      SB_DLOG(INFO) << "Received OnRenderTimeout, ignoring by random chance.";
-    }
-  } else {
-    timeout_polling_thread_.message_loop()->task_runner()->PostDelayedTask(
-        FROM_HERE,
-        base::Bind(&BrowserModule::OnPollForRenderTimeout,
-                   base::Unretained(this), url),
-        base::TimeDelta::FromSeconds(kRenderTimeOutPollingDelaySeconds));
-  }
-}
-#endif
-
 render_tree::ResourceProvider* BrowserModule::GetResourceProvider() {
   if (application_state_ == base::kApplicationStateConcealed) {
     DCHECK(resource_provider_stub_);
@@ -2109,12 +1994,6 @@
   return scoped_refptr<script::Wrappable>(new h5vcc::H5vcc(h5vcc_settings));
 }
 
-void BrowserModule::SetApplicationStartOrPreloadTimestamp(
-    bool is_preload, SbTimeMonotonic timestamp) {
-  DCHECK(web_module_);
-  web_module_->SetApplicationStartOrPreloadTimestamp(is_preload, timestamp);
-}
-
 void BrowserModule::SetDeepLinkTimestamp(SbTimeMonotonic timestamp) {
   DCHECK(web_module_);
   web_module_->SetDeepLinkTimestamp(timestamp);
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index cec410e..891a95e 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -21,6 +21,7 @@
 #include <vector>
 
 #include "base/observer_list.h"
+#include "base/optional.h"
 #include "base/synchronization/lock.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/thread.h"
@@ -91,6 +92,7 @@
 // different subsystems together.
 class BrowserModule {
  public:
+  typedef base::Callback<void(WebModule*)> WebModuleCreatedCallback;
   // All browser subcomponent options should have default constructors that
   // setup reasonable default options.
   struct Options {
@@ -103,7 +105,7 @@
     renderer::RendererModule::Options renderer_module_options;
     WebModule::Options web_module_options;
     media::MediaModule::Options media_module_options;
-    base::Closure web_module_created_callback;
+    WebModuleCreatedCallback web_module_created_callback;
     memory_settings::AutoMemSettings command_line_auto_mem_settings;
     memory_settings::AutoMemSettings build_auto_mem_settings;
     base::Optional<GURL> fallback_splash_screen_url;
@@ -229,19 +231,13 @@
   static void GetParamMap(const std::string& url,
                           std::map<std::string, std::string>& map);
 
-  // Pass the application preload or start timestamps from Starboard.
-  void SetApplicationStartOrPreloadTimestamp(bool is_preload,
-                                             SbTimeMonotonic timestamp);
   // Pass the deeplink timestamp from Starboard.
   void SetDeepLinkTimestamp(SbTimeMonotonic timestamp);
+
  private:
 #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
   static void CoreDumpHandler(void* browser_module_as_void);
   int on_error_triggered_count_;
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
-  int recovery_mechanism_triggered_count_;
-  int timeout_response_trigger_count_;
-#endif  // defined(COBALT_CHECK_RENDER_TIMEOUT)
 #endif  // SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
 
   // Called when the WebModule's Window.onload event is fired.
@@ -394,11 +390,6 @@
   // Process all messages queued into the |render_tree_submission_queue_|.
   void ProcessRenderTreeSubmissionQueue();
 
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
-  // Poll for render timeout. Called from timeout_polling_thread_.
-  void OnPollForRenderTimeout(const GURL& url);
-#endif
-
   // Gets the current resource provider.
   render_tree::ResourceProvider* GetResourceProvider();
 
@@ -587,7 +578,7 @@
 
   // This will be called after a WebModule has been recreated, which could occur
   // on navigation.
-  base::Closure web_module_created_callback_;
+  WebModuleCreatedCallback web_module_created_callback_;
 
   // The time when a URL navigation starts. This is recorded after the previous
   // WebModule is destroyed.
@@ -648,14 +639,6 @@
   // Reset when the browser is paused, signalled to resume.
   base::WaitableEvent has_resumed_;
 
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
-  base::Thread timeout_polling_thread_;
-
-  // Counts the number of continuous render timeout expirations. This value is
-  // updated and used from OnPollForRenderTimeout.
-  int render_timeout_count_;
-#endif
-
   // The URL that Cobalt will attempt to navigate to during an OnErrorRetry()
   // and also when starting from a concealed state or unfreezing from a
   // frozen state. This url is set within OnError() and also when a
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc
index 01ff75d..1ae119c 100644
--- a/src/cobalt/browser/switches.cc
+++ b/src/cobalt/browser/switches.cc
@@ -232,6 +232,10 @@
 
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 
+const char kCompressUpdate[] = "compress_update";
+const char kCompressUpdateHelp[] =
+    "The updater should compress the update package";
+
 const char kMinLogLevel[] = "min_log_level";
 const char kMinLogLevelHelp[] =
     "Set the minimum logging level: info|warning|error|fatal.";
@@ -478,6 +482,7 @@
 #endif  // SB_API_VERSION >= 12 ||
         // SB_HAS(ON_SCREEN_KEYBOARD)
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
+        {kCompressUpdate, kCompressUpdateHelp},
         {kDisableJavaScriptJit, kDisableJavaScriptJitHelp},
         {kDisableMapToMesh, kDisableMapToMeshHelp},
         {kDisableTimerResolutionLimit, kDisableTimerResolutionLimitHelp},
diff --git a/src/cobalt/browser/switches.h b/src/cobalt/browser/switches.h
index fa7f6a4..b2aa24e 100644
--- a/src/cobalt/browser/switches.h
+++ b/src/cobalt/browser/switches.h
@@ -98,6 +98,8 @@
         // SB_HAS(ON_SCREEN_KEYBOARD)
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 
+extern const char kCompressUpdate[];
+extern const char kCompressUpdateHelp[];
 extern const char kDisableJavaScriptJit[];
 extern const char kDisableJavaScriptJitHelp[];
 extern const char kDisableMapToMesh[];
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index 06600b8..73e2e02 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -52,6 +52,7 @@
 #include "cobalt/dom/keyboard_event_init.h"
 #include "cobalt/dom/local_storage_database.h"
 #include "cobalt/dom/mutation_observer_task_manager.h"
+#include "cobalt/dom/navigation_type.h"
 #include "cobalt/dom/navigator.h"
 #include "cobalt/dom/pointer_event.h"
 #include "cobalt/dom/storage.h"
@@ -265,6 +266,9 @@
                                              SbTimeMonotonic timestamp);
   void SetDeepLinkTimestamp(SbTimeMonotonic timestamp);
 
+  void SetUnloadEventTimingInfo(base::TimeTicks start_time,
+                                base::TimeTicks end_time);
+
  private:
   class DocumentLoadedObserver;
 
@@ -309,6 +313,22 @@
 
   void OnLoadComplete(const base::Optional<std::string>& error) {
     if (error) error_callback_.Run(window_->location()->url(), *error);
+
+    // Create Performance navigation timing info after document loading
+    // completed.
+    DCHECK(window_);
+    DCHECK(window_->performance());
+    if (window_->GetDocumentLoader()) {
+      net::LoadTimingInfo load_timing_info =
+          window_->GetDocumentLoader()->get_load_timing_info();
+      // Check if the load happens through net mdoule.
+      bool is_load_timing_info_valid =
+          !load_timing_info.request_start.is_null();
+      if (is_load_timing_info_valid) {
+        window_->document()->CreatePerformanceNavigationTiming(
+            window_->performance(), load_timing_info);
+      }
+    }
   }
 
   // Report an error encountered while running JS.
@@ -476,6 +496,9 @@
   base::WaitableEvent synchronous_loader_interrupt_ = {
       base::WaitableEvent::ResetPolicy::MANUAL,
       base::WaitableEvent::InitialState::NOT_SIGNALED};
+
+  base::Callback<void(base::TimeTicks, base::TimeTicks)>
+      report_unload_timing_info_callback_;
 };
 
 class WebModule::Impl::DocumentLoadedObserver : public dom::DocumentObserver {
@@ -772,6 +795,9 @@
       resource_provider_, window_, data.options.debugger_state));
 #endif  // ENABLE_DEBUGGER
 
+  report_unload_timing_info_callback_ =
+      data.options.collect_unload_event_time_callback;
+
   is_running_ = true;
 }
 
@@ -782,7 +808,20 @@
   global_environment_->SetReportEvalCallback(base::Closure());
   global_environment_->SetReportErrorCallback(
       script::GlobalEnvironment::ReportErrorCallback());
+  // Collect document's unload event start time.
+  base::TimeTicks unload_event_start_time = base::TimeTicks::Now();
+
   window_->DispatchEvent(new dom::Event(base::Tokens::unload()));
+
+  // Collect document's unload event end time.
+  base::TimeTicks unload_event_end_time = base::TimeTicks::Now();
+
+  // Send the unload event start/end time back to application.
+  if (!report_unload_timing_info_callback_.is_null()) {
+    report_unload_timing_info_callback_.Run(unload_event_start_time,
+                                            unload_event_end_time);
+  }
+
   document_load_observer_.reset();
 
 #if defined(ENABLE_DEBUGGER)
@@ -824,7 +863,7 @@
 void WebModule::Impl::InjectInputEvent(scoped_refptr<dom::Element> element,
                                        const scoped_refptr<dom::Event>& event) {
   TRACE_EVENT1("cobalt::browser", "WebModule::Impl::InjectInputEvent()",
-               "event", event->type().c_str());
+               "event", TRACE_STR_COPY(event->type().c_str()));
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(is_running_);
   DCHECK(window_);
@@ -1031,6 +1070,7 @@
 void WebModule::Impl::SetApplicationStartOrPreloadTimestamp(
     bool is_preload, SbTimeMonotonic timestamp) {
   DCHECK(window_);
+  DCHECK(window_->performance());
   window_->performance()->SetApplicationStartOrPreloadTimestamp(is_preload,
                                                                 timestamp);
 }
@@ -1040,6 +1080,14 @@
   window_->performance()->SetDeepLinkTimestamp(timestamp);
 }
 
+void WebModule::Impl::SetUnloadEventTimingInfo(base::TimeTicks start_time,
+                                               base::TimeTicks end_time) {
+  DCHECK(window_);
+  if (window_->document()) {
+    window_->document()->SetUnloadEventTimingInfo(start_time, end_time);
+  }
+}
+
 void WebModule::Impl::OnCspPolicyChanged() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(is_running_);
@@ -1446,7 +1494,7 @@
     base::Token type, const dom::InputEventInit& event) {
   TRACE_EVENT1("cobalt::browser",
                "WebModule::InjectOnScreenKeyboardInputEvent()", "type",
-               type.c_str());
+               TRACE_STR_COPY(type.c_str()));
   DCHECK(message_loop());
   DCHECK(impl_);
   message_loop()->task_runner()->PostTask(
@@ -1519,7 +1567,7 @@
 void WebModule::InjectKeyboardEvent(base::Token type,
                                     const dom::KeyboardEventInit& event) {
   TRACE_EVENT1("cobalt::browser", "WebModule::InjectKeyboardEvent()", "type",
-               type.c_str());
+               TRACE_STR_COPY(type.c_str()));
   DCHECK(message_loop());
   DCHECK(impl_);
   message_loop()->task_runner()->PostTask(
@@ -1531,7 +1579,7 @@
 void WebModule::InjectPointerEvent(base::Token type,
                                    const dom::PointerEventInit& event) {
   TRACE_EVENT1("cobalt::browser", "WebModule::InjectPointerEvent()", "type",
-               type.c_str());
+               TRACE_STR_COPY(type.c_str()));
   DCHECK(message_loop());
   DCHECK(impl_);
   message_loop()->task_runner()->PostTask(
@@ -1543,7 +1591,7 @@
 void WebModule::InjectWheelEvent(base::Token type,
                                  const dom::WheelEventInit& event) {
   TRACE_EVENT1("cobalt::browser", "WebModule::InjectWheelEvent()", "type",
-               type.c_str());
+               TRACE_STR_COPY(type.c_str()));
   DCHECK(message_loop());
   DCHECK(impl_);
   message_loop()->task_runner()->PostTask(
@@ -1832,13 +1880,12 @@
   TRACE_EVENT0("cobalt::browser",
                "WebModule::SetApplicationStartOrPreloadTimestamp()");
   DCHECK(message_loop());
-  DCHECK(impl_);
   if (base::MessageLoop::current() != message_loop()) {
-    message_loop()->task_runner()->PostBlockingTask(
-        FROM_HERE,
-        base::Bind(&WebModule::Impl::SetApplicationStartOrPreloadTimestamp,
-                   base::Unretained(impl_.get()), is_preload, timestamp));
+    message_loop()->task_runner()->PostTask(
+        FROM_HERE, base::Bind(&WebModule::SetApplicationStartOrPreloadTimestamp,
+                              base::Unretained(this), is_preload, timestamp));
   } else {
+    DCHECK(impl_);
     impl_->SetApplicationStartOrPreloadTimestamp(is_preload, timestamp);
   }
 }
@@ -1856,5 +1903,20 @@
   }
 }
 
+void WebModule::SetUnloadEventTimingInfo(base::TimeTicks start_time,
+                                         base::TimeTicks end_time) {
+  TRACE_EVENT0("cobalt::browser", "WebModule::SetUnloadEventTimingInfo()");
+  DCHECK(message_loop());
+  DCHECK(impl_);
+  if (base::MessageLoop::current() != message_loop()) {
+    message_loop()->task_runner()->PostTask(
+        FROM_HERE,
+        base::Bind(&WebModule::Impl::SetUnloadEventTimingInfo,
+                   base::Unretained(impl_.get()), start_time, end_time));
+  } else {
+    impl_->SetUnloadEventTimingInfo(start_time, end_time);
+  }
+}
+
 }  // namespace browser
 }  // namespace cobalt
diff --git a/src/cobalt/browser/web_module.h b/src/cobalt/browser/web_module.h
index 20b39bb..9e1690e 100644
--- a/src/cobalt/browser/web_module.h
+++ b/src/cobalt/browser/web_module.h
@@ -271,6 +271,11 @@
     // there is no playback during Concealed state, we should provide a chance
     // for Cobalt to freeze.
     base::Closure maybe_freeze_callback;
+
+    // This callback is for collecting previous document unload event start/end
+    // time.
+    base::Callback<void(base::TimeTicks, base::TimeTicks)>
+        collect_unload_event_time_callback;
   };
 
   typedef layout::LayoutManager::LayoutResults LayoutResults;
@@ -409,10 +414,16 @@
 
   scoped_refptr<render_tree::Node> DoSynchronousLayoutAndGetRenderTree();
 
+  // Pass the application preload or start timestamps from Starboard.
   void SetApplicationStartOrPreloadTimestamp(bool is_preload,
                                              SbTimeMonotonic timestamp);
   void SetDeepLinkTimestamp(SbTimeMonotonic timestamp);
 
+  // Set document's load timing info's unload event start/end time.
+  void SetUnloadEventTimingInfo(base::TimeTicks start_time,
+                                base::TimeTicks end_time);
+
+
  private:
   // Data required to construct a WebModule, initialized in the constructor and
   // passed to |Initialize|.
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 74c5406..bc012d6 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-306369
\ No newline at end of file
+307648
\ No newline at end of file
diff --git a/src/cobalt/content/fonts/config/android/fonts.xml b/src/cobalt/content/fonts/config/android/fonts.xml
index d26ab07..1c6c6df 100644
--- a/src/cobalt/content/fonts/config/android/fonts.xml
+++ b/src/cobalt/content/fonts/config/android/fonts.xml
@@ -165,6 +165,8 @@
     <family>
         <font weight="400" style="normal">NotoSansEthiopic-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansEthiopic-Bold.ttf</font>
+        <font weight="400" style="normal">NotoSansEthiopic-VF.ttf</font>
+        <font weight="700" style="normal">NotoSansEthiopic-VF.ttf</font>
     </family>
     <family>
         <font weight="400" style="normal">NotoSansHebrew-Regular.ttf</font>
@@ -179,6 +181,8 @@
         <font weight="400" style="normal">NotoSansArmenian-Regular.otf</font>
         <font weight="700" style="normal">NotoSansArmenian-Bold.ttf</font>
         <font weight="700" style="normal">NotoSansArmenian-Bold.otf</font>
+        <font weight="400" style="normal">NotoSansArmenian-VF.ttf</font>
+        <font weight="700" style="normal">NotoSansArmenian-VF.ttf</font>
     </family>
     <family>
         <font weight="400" style="normal">NotoSansGeorgian-Regular.ttf</font>
@@ -191,6 +195,8 @@
         <font weight="400" style="normal">NotoSansDevanagariUI-Regular.otf</font>
         <font weight="700" style="normal">NotoSansDevanagariUI-Bold.ttf</font>
         <font weight="700" style="normal">NotoSansDevanagariUI-Bold.otf</font>
+        <font weight="400" style="normal">NotoSansDevanagariUI-VF.ttf</font>
+        <font weight="700" style="normal">NotoSansDevanagariUI-VF.ttf</font>
     </family>
     <!-- Gujarati should come after Devanagari -->
     <family>
@@ -201,32 +207,44 @@
     <family>
         <font weight="400" style="normal">NotoSansGurmukhiUI-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansGurmukhiUI-Bold.ttf</font>
+        <font weight="400" style="normal">NotoSansGurmukhiUI-VF.ttf</font>
+        <font weight="700" style="normal">NotoSansGurmukhiUI-VF.ttf</font>
     </family>
     <family>
         <font weight="400" style="normal">NotoSansTamilUI-Regular.ttf</font>
         <font weight="400" style="normal">NotoSansTamilUI-Regular.otf</font>
         <font weight="700" style="normal">NotoSansTamilUI-Bold.ttf</font>
         <font weight="700" style="normal">NotoSansTamilUI-Bold.otf</font>
+        <font weight="400" style="normal">NotoSansTamilUI-VF.ttf</font>
+        <font weight="700" style="normal">NotoSansTamilUI-VF.ttf</font>
     </family>
     <family>
         <font weight="400" style="normal">NotoSansMalayalamUI-Regular.ttf</font>
         <font weight="400" style="normal">NotoSansMalayalamUI-Regular.otf</font>
         <font weight="700" style="normal">NotoSansMalayalamUI-Bold.ttf</font>
         <font weight="700" style="normal">NotoSansMalayalamUI-Bold.otf</font>
+        <font weight="400" style="normal">NotoSansMalayalamUI-VF.ttf</font>
+        <font weight="700" style="normal">NotoSansMalayalamUI-VF.ttf</font>
     </family>
     <family>
         <font weight="400" style="normal">NotoSansBengaliUI-Regular.ttf</font>
         <font weight="400" style="normal">NotoSansBengaliUI-Regular.otf</font>
         <font weight="700" style="normal">NotoSansBengaliUI-Bold.ttf</font>
         <font weight="700" style="normal">NotoSansBengaliUI-Bold.otf</font>
+        <font weight="400" style="normal">NotoSansBengaliUI-VF.ttf</font>
+        <font weight="700" style="normal">NotoSansBengaliUI-VF.ttf</font>
     </family>
     <family>
         <font weight="400" style="normal">NotoSansTeluguUI-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansTeluguUI-Bold.ttf</font>
+        <font weight="400" style="normal">NotoSansTeluguUI-VF.ttf</font>
+        <font weight="700" style="normal">NotoSansTeluguUI-VF.ttf</font>
     </family>
     <family>
         <font weight="400" style="normal">NotoSansKannadaUI-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansKannadaUI-Bold.ttf</font>
+        <font weight="400" style="normal">NotoSansKannadaUI-VF.ttf</font>
+        <font weight="700" style="normal">NotoSansKannadaUI-VF.ttf</font>
     </family>
     <family>
         <font weight="400" style="normal">NotoSansOriyaUI-Regular.ttf</font>
@@ -237,6 +255,8 @@
         <font weight="400" style="normal">NotoSansSinhala-Regular.otf</font>
         <font weight="700" style="normal">NotoSansSinhala-Bold.ttf</font>
         <font weight="700" style="normal">NotoSansSinhala-Bold.otf</font>
+        <font weight="400" style="normal">NotoSansSinhala-VF.ttf</font>
+        <font weight="700" style="normal">NotoSansSinhala-VF.ttf</font>
     </family>
     <family>
         <font weight="400" style="normal">NotoSansKhmerUI-Regular.ttf</font>
diff --git a/src/cobalt/demos/content/hybrid-navigation/hybrid-navigation-grid-rtl.html b/src/cobalt/demos/content/hybrid-navigation/hybrid-navigation-grid-rtl.html
new file mode 100644
index 0000000..672141c
--- /dev/null
+++ b/src/cobalt/demos/content/hybrid-navigation/hybrid-navigation-grid-rtl.html
@@ -0,0 +1,180 @@
+<!DOCTYPE html>
+<!--
+ | This demo demonstrates how scroll container should behave in RTL mode.
+ -->
+<html>
+  <head>
+    <style>
+      body {
+        background-color: rgb(25,25,25);
+        color: rgb(200,200,200);
+        width: 1920px;
+        height: 720px;
+      }
+
+      .vscroll {
+        overflow: auto;
+        position: absolute;
+        transform: translate(100px,100px);
+        background-color: rgb(50,50,50);
+        width: 1080px;
+        height: 520px;
+      }
+
+      .hscroll {
+        overflow: auto;
+        position: relative;
+        background-color: rgb(75, 75, 75);
+        width: 1080px;
+        height: 320px;
+      }
+
+      /*
+       * focusItem is used as the actual focusable item. Its dimensions are
+       * static so that the underlying SbUiNavigation system knows how much to
+       * scroll to ensure it is fully visible. The scroll animation is usually
+       * calculated to bring the to-be-focused item into view, but if that item
+       * is resized after the animation starts, then the animation may no
+       * longer end with the larger item in full view.
+       */
+      .focusItem {
+        overflow: hidden;
+        position: absolute;
+        width: 300px;
+        height: 300px;
+        outline: none;
+      }
+
+      /*
+       * focusContent will react to focus changes. Its size will grow when
+       * its parent receives focus.
+       */
+      .focusContent {
+        overflow: hidden;
+        margin: 20px;
+        border: 5px solid rgb(0,0,0);
+        width: 250px;
+        height: 250px;
+        line-height: 250px;
+        text-align: center;
+        vertical-align: middle;
+        font-size: 100px;
+
+        /*
+         * The user may be scrolling through items quickly. Use a transition
+         * delay so that the transition is shown once the user settles on a
+         * focus item.
+         */
+        transition: transform 0.5s;
+        transition-delay: 0.1s;
+      }
+
+      /*
+       * Update focusContent when the parent is focused.
+       */
+      .focusItem:focus .focusContent {
+        background-color: rgb(240,240,240);
+        color: rgb(0,0,0);
+        transform: -cobalt-ui-nav-focus-transform() scale(1.1);
+      }
+
+      /*
+       * Add a spotlight to focusContent when the parent is focused.
+       */
+      .focusItem:focus .focusContent::after {
+        content: '';
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        background: radial-gradient(rgba(255,0,0,0.5) 0%, rgba(255,0,0,0) 30%);
+        transform: -cobalt-ui-nav-spotlight-transform();
+      }
+    </style>
+  </head>
+
+  <body class="body">
+    <div class="vscroll">
+      <div class="hscroll" dir="rtl">
+        <!--
+         | translateX() should be honored when computing items' position in rtl mode.
+         | The right edge of A0 should be 50px from the right edge of the container when the
+         | page is rendered.
+         | User should be able to scroll items using the entire width of the container,
+         | meaning A0 should be visibly translated from the initial position to the right edge
+         | of the container.
+         | A0 should stop 50px from the right edge of the container when scrolling back to
+         | A0.
+         -->
+        <div id="start" class="focusItem" tabindex="-2" style="transform: translateX(-50px)">
+          <div class="focusContent">A0</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-350px)">
+          <div class="focusContent">A1</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-650px)">
+          <div class="focusContent">A2</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-950px)">
+          <div class="focusContent">A3</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-1250px)">
+          <div class="focusContent">A4</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-1550px)">
+          <div class="focusContent">A5</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-1850px)">
+          <div class="focusContent">A6</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-2150px)">
+          <div class="focusContent">A7</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-2450px)">
+          <div class="focusContent">A8</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-2750px)">
+          <div class="focusContent">A9</div>
+        </div>
+      </div>
+      <div class="hscroll" dir="rtl">
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-50px)">
+          <div class="focusContent">B0</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-350px)">
+          <div class="focusContent">B1</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-650px)">
+          <div class="focusContent">B2</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-950px)">
+          <div class="focusContent">B3</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-1250px)">
+          <div class="focusContent">B4</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-1550px)">
+          <div class="focusContent">B5</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-1850px)">
+          <div class="focusContent">B6</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-2150px)">
+          <div class="focusContent">B7</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-2450px)">
+          <div class="focusContent">B8</div>
+        </div>
+        <div class="focusItem" tabindex="-2" style="transform: translateX(-2750px)">
+          <div class="focusContent">B9</div>
+        </div>
+      </div>
+    </div>
+  </body>
+  <script>
+    window.onload = function() {
+      document.getElementById("start").focus();
+    }
+  </script>
+</html>
diff --git a/src/cobalt/demos/content/network-status-demo/index.html b/src/cobalt/demos/content/network-status-demo/index.html
new file mode 100644
index 0000000..944a2d2
--- /dev/null
+++ b/src/cobalt/demos/content/network-status-demo/index.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+
+<head>
+    <title>Test network status</title>
+</head>
+
+<body style="color: white;">
+    <h1>
+        <span>Network Status Value:</span>
+        <span id="net_stat">n/a</span>
+    </h1>
+
+    <script>
+        window.addEventListener('offline', function (e) {
+            console.log('offline');
+            document.getElementById('net_stat').innerHTML = "False";
+        });
+        window.addEventListener('online', function (e) {
+            console.log('online');
+            document.getElementById('net_stat').innerHTML = "True";
+        });
+    </script>
+</body>
diff --git a/src/cobalt/dom/document.cc b/src/cobalt/dom/document.cc
index 949ff8d..082d661 100644
--- a/src/cobalt/dom/document.cc
+++ b/src/cobalt/dom/document.cc
@@ -59,6 +59,7 @@
 #include "cobalt/dom/mouse_event.h"
 #include "cobalt/dom/named_node_map.h"
 #include "cobalt/dom/node_descendants_iterator.h"
+#include "cobalt/dom/performance.h"
 #include "cobalt/dom/text.h"
 #include "cobalt/dom/ui_event.h"
 #include "cobalt/dom/wheel_event.h"
@@ -105,7 +106,8 @@
       render_postponed_(false),
       frozenness_(false),
       ALLOW_THIS_IN_INITIALIZER_LIST(intersection_observer_task_manager_(
-          new IntersectionObserverTaskManager())) {
+          new IntersectionObserverTaskManager())),
+      navigation_type_(kNavigationTypeNavigate) {
   DCHECK(html_element_context_);
   DCHECK(options.url.is_empty() || options.url.is_valid());
   application_lifecycle_state_->AddObserver(this);
@@ -126,7 +128,8 @@
   location_ = new Location(
       options.url, options.hashchange_callback, options.navigation_callback,
       base::Bind(&CspDelegate::CanLoad, base::Unretained(csp_delegate_.get()),
-                 CspDelegate::kLocation));
+                 CspDelegate::kLocation),
+      base::Bind(&Document::SetNavigationType, base::Unretained(this)));
 
   font_cache_.reset(new FontCache(
       html_element_context_->resource_provider(),
@@ -1148,6 +1151,45 @@
   tracer->Trace(initial_computed_style_declaration_);
 }
 
+void Document::CreatePerformanceNavigationTiming(
+    Performance* performance, const net::LoadTimingInfo& timing_info) {
+  // To create the navigation timing entry for document, given a loadTiminginfo,
+  // a navigationType and a null service worker timing info, do the following:
+  //   https://www.w3.org/TR/2022/WD-navigation-timing-2-20220224/#marking-navigation-timing
+  // 1. Let global be document's relevant global object.
+  // 2. Let navigationTimingEntry be a new PerformanceNavigationTiming object
+  // in global's realm.
+  // 3. Setup the resource timing entry for navigationTimingEntry given
+  // "navigation", document's address, and fetchTiming.
+  // 4. Set navigationTimingEntry's document load timing to document's
+  // load timing info.
+  // 5. Set navigationTimingEntry's previous document unload timing to
+  // document's previous document unload timing.
+  // 6. Set navigationTimingEntry's redirect count to redirectCount.
+  // 7. Set navigationTimingEntry's navigation type to navigationType.
+  // 8. Set navigationTimingEntry's service worker timing to
+  // serviceWorkerTiming.
+  scoped_refptr<PerformanceNavigationTiming> navigation_timing(
+      new PerformanceNavigationTiming(timing_info, location_->url().spec(),
+                                      performance, this,
+                                      performance->GetTimeOrigin()));
+
+  // 9. Set document's navigation timing entry to
+  // navigationTimingEntry.
+  navigation_timing_entry_ = navigation_timing;
+  // 10. add navigationTimingEntry to global's performance entry buffer.
+  performance->AddEntryToPerformanceEntryBuffer(navigation_timing_entry_);
+  // 11. To queue the navigation timing entry for Document document, queue
+  // document's navigation timing entry.
+  performance->QueuePerformanceEntry(navigation_timing_entry_);
+}
+
+void Document::SetUnloadEventTimingInfo(base::TimeTicks start_time,
+                                        base::TimeTicks end_time) {
+  document_load_timing_info_.unload_event_start = start_time;
+  document_load_timing_info_.unload_event_end = end_time;
+}
+
 void Document::set_render_postponed(bool render_postponed) {
   bool unpostponed = render_postponed_ && !render_postponed;
   render_postponed_ = render_postponed;
@@ -1156,6 +1198,28 @@
   }
 }
 
+void Document::CollectTimingInfoAndDispatchEvent() {
+  DCHECK(html_element_context_);
+  Performance* performance = html_element_context_->performance();
+  bool is_performance_valid = performance != nullptr;
+
+  // Set document load timing info's dom content loaded event start time
+  // before user agent dispatches the DOMConentLoaded event.
+  if (is_performance_valid) {
+    document_load_timing_info_.dom_content_loaded_event_start =
+        performance->Now();
+  }
+
+  PostToDispatchEventName(FROM_HERE, base::Tokens::domcontentloaded());
+
+  // Set document load timing info's dom content loaded event end time
+  // after user agent completes handling the DOMConentLoaded event.
+  if (is_performance_valid) {
+    document_load_timing_info_.dom_content_loaded_event_end =
+        performance->Now();
+  }
+}
+
 void Document::OnRootElementUnableToProvideOffsetDimensions() {
   window_->OnDocumentRootElementUnableToProvideOffsetDimensions();
 }
@@ -1173,6 +1237,16 @@
     UpdateComputedStyles();
   }
 
+  DCHECK(html_element_context_);
+  Performance* performance = html_element_context_->performance();
+  bool is_performance_valid = performance != nullptr;
+
+  // Set document load timing info's dom complete time before user agent set the
+  // current document readiness to "complete".
+  if (is_performance_valid) {
+    document_load_timing_info_.dom_complete = performance->Now();
+  }
+
   // Adjust the document ready state to reflect the fact that the document has
   // finished loading.  Performing this update and firing the readystatechange
   // event before the load event matches Chromium's behavior.
@@ -1182,9 +1256,21 @@
   // have changed the document ready state.
   DispatchEvent(new Event(base::Tokens::readystatechange()));
 
+  // Set document load timing info's load event start time before user agent
+  // dispatch the load event for the document.
+  if (is_performance_valid) {
+    document_load_timing_info_.load_event_start = performance->Now();
+  }
+
   // Dispatch the document's onload event.
   DispatchEvent(new Event(base::Tokens::load()));
 
+  // Set document load timing info's load event end time after user agent
+  // completes handling the load event for the document.
+  if (is_performance_valid) {
+    document_load_timing_info_.load_event_end = performance->Now();
+  }
+
   // After all JavaScript OnLoad event handlers have executed, signal to let
   // any Document observers know that a load event has occurred.
   SignalOnLoadToObservers();
diff --git a/src/cobalt/dom/document.h b/src/cobalt/dom/document.h
index 32b4fbc..297fc92 100644
--- a/src/cobalt/dom/document.h
+++ b/src/cobalt/dom/document.h
@@ -41,6 +41,7 @@
 #include "cobalt/cssom/viewport_size.h"
 #include "cobalt/dom/application_lifecycle_state.h"
 #include "cobalt/dom/csp_delegate_type.h"
+#include "cobalt/dom/document_load_timing_info.h"
 #include "cobalt/dom/document_ready_state.h"
 #include "cobalt/dom/document_timeline.h"
 #include "cobalt/dom/event.h"
@@ -48,6 +49,7 @@
 #include "cobalt/dom/intersection_observer_task_manager.h"
 #include "cobalt/dom/location.h"
 #include "cobalt/dom/node.h"
+#include "cobalt/dom/performance_navigation_timing.h"
 #include "cobalt/dom/pointer_state.h"
 #include "cobalt/dom/visibility_state.h"
 #include "cobalt/math/size.h"
@@ -75,6 +77,7 @@
 class HTMLMediaElement;
 class HTMLScriptElement;
 class Location;
+class Performance;
 class Text;
 class Window;
 
@@ -481,6 +484,43 @@
   DEFINE_WRAPPABLE_TYPE(Document);
   void TraceMembers(script::Tracer* tracer) override;
 
+  // PerformanceNavigationTiming related API.
+  void CreatePerformanceNavigationTiming(
+      Performance* performance, const net::LoadTimingInfo& timing_info);
+  base::TimeTicks GetDocumentUnloadEventStartTime() const {
+    return document_load_timing_info_.unload_event_start;
+  }
+  base::TimeTicks GetDocumentUnloadEventEndTime() const {
+    return document_load_timing_info_.unload_event_end;
+  }
+  DOMHighResTimeStamp GetDocumentContentLoadedEventStartTime() const {
+    return document_load_timing_info_.dom_content_loaded_event_start;
+  }
+  DOMHighResTimeStamp GetDocumentContentLoadedEventEndTime() const {
+    return document_load_timing_info_.dom_content_loaded_event_end;
+  }
+  DOMHighResTimeStamp GetDocumentDomCompleteTime() const {
+    return document_load_timing_info_.dom_complete;
+  }
+  DOMHighResTimeStamp GetDocumentLoadEventStartTime() const {
+    return document_load_timing_info_.load_event_start;
+  }
+  DOMHighResTimeStamp GetDocumentLoadEventEndTime() const {
+    return document_load_timing_info_.load_event_end;
+  }
+  NavigationType GetNavigationType() const { return navigation_type_; }
+
+  // Collect dom content loaded timing info and dispatch dom content loaded
+  // event.
+  void CollectTimingInfoAndDispatchEvent();
+
+  void SetNavigationType(NavigationType navigation_type) {
+    navigation_type_ = navigation_type;
+  }
+
+  void SetUnloadEventTimingInfo(base::TimeTicks start_time,
+                                base::TimeTicks end_time);
+
  protected:
   ~Document() override;
 
@@ -644,6 +684,11 @@
 
   scoped_refptr<IntersectionObserverTaskManager>
       intersection_observer_task_manager_;
+
+  scoped_refptr<PerformanceNavigationTiming> navigation_timing_entry_;
+
+  DocumentLoadTimingInfo document_load_timing_info_;
+  NavigationType navigation_type_;
 };
 
 }  // namespace dom
diff --git a/src/cobalt/dom/document_load_timing_info.h b/src/cobalt/dom/document_load_timing_info.h
new file mode 100644
index 0000000..322d6d8
--- /dev/null
+++ b/src/cobalt/dom/document_load_timing_info.h
@@ -0,0 +1,64 @@
+// 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.
+
+#ifndef COBALT_DOM_DOCUMENT_LOAD_TIMING_INFO_H_
+#define COBALT_DOM_DOCUMENT_LOAD_TIMING_INFO_H_
+
+#include "cobalt/dom/performance_high_resolution_time.h"
+
+namespace cobalt {
+namespace dom {
+
+// Implements the DocumentLoadTimingInfo struct for saving document loading
+// related timing info.
+//   https://html.spec.whatwg.org/multipage/dom.html#document-load-timing-info
+struct DocumentLoadTimingInfo {
+  DocumentLoadTimingInfo() = default;
+  DocumentLoadTimingInfo(const DocumentLoadTimingInfo& other) = default;
+  ~DocumentLoadTimingInfo() = default;
+
+  // If the previous document and the current document have the same origin,
+  // these timestamps are measured immediately after the user agent handles the
+  // unload event of the previous document. If there is no previous document
+  // or the previous document has a different origin than the current document,
+  // these attributes will be zero.
+  base::TimeTicks unload_event_start;
+  base::TimeTicks unload_event_end;
+
+  // This timestamp is measured before the user agent dispatches the
+  // DOMContentLoaded event.
+  DOMHighResTimeStamp dom_content_loaded_event_start = 0.0;
+
+  // This timestamp is measured after the user agent completes handling of
+  // the DOMContentLoaded event.
+  DOMHighResTimeStamp dom_content_loaded_event_end = 0.0;
+
+  // This timestamp is measured before the user agent sets the current
+  // document readiness to "complete". See document readiness for a precise
+  // definition.
+  DOMHighResTimeStamp dom_complete = 0.0;
+
+  // This timestamp is measured before the user agent dispatches the load
+  // event for the document.
+  DOMHighResTimeStamp load_event_start = 0.0;
+
+  // This timestamp is measured after the user agent completes handling
+  // the load event for the document.
+  DOMHighResTimeStamp load_event_end = 0.0;
+};
+
+}  // namespace dom
+}  // namespace cobalt
+
+#endif  // COBALT_DOM_DOCUMENT_LOAD_TIMING_INFO_H_
diff --git a/src/cobalt/dom/dom.gyp b/src/cobalt/dom/dom.gyp
index 4039e2c..0163c9d 100644
--- a/src/cobalt/dom/dom.gyp
+++ b/src/cobalt/dom/dom.gyp
@@ -77,6 +77,7 @@
         'directionality.h',
         'document.cc',
         'document.h',
+        'document_load_timing_info.h',
         'document_timeline.cc',
         'document_timeline.h',
         'document_type.cc',
@@ -268,6 +269,8 @@
         'performance_mark.h',
         'performance_measure.cc',
         'performance_measure.h',
+        'performance_navigation_timing.cc',
+        'performance_navigation_timing.h',
         'performance_observer.cc',
         'performance_observer.h',
         'performance_observer_entry_list.cc',
diff --git a/src/cobalt/dom/dom_stat_tracker.cc b/src/cobalt/dom/dom_stat_tracker.cc
index 544a218..586f8a3 100644
--- a/src/cobalt/dom/dom_stat_tracker.cc
+++ b/src/cobalt/dom/dom_stat_tracker.cc
@@ -26,10 +26,21 @@
       count_html_element_document_(
           base::StringPrintf("Count.%s.DOM.HtmlElement.Document", name.c_str()),
           0, "Number of HTML elements in the document."),
+      count_window_timers_interval_(
+          base::StringPrintf("Count.%s.DOM.WindowTimers.Interval",
+                             name.c_str()),
+          0, "Number of active WindowTimer Intervals."),
+      count_window_timers_timeout_(
+          base::StringPrintf("Count.%s.DOM.WindowTimers.Timeout", name.c_str()),
+          0, "Number of active WindowTimer Timeouts."),
       count_html_element_created_(0),
       count_html_element_destroyed_(0),
       count_html_element_document_added_(0),
       count_html_element_document_removed_(0),
+      count_window_timers_interval_created_(0),
+      count_window_timers_interval_destroyed_(0),
+      count_window_timers_timeout_created_(0),
+      count_window_timers_timeout_destroyed_(0),
       script_element_execute_count_(
           base::StringPrintf("Count.%s.DOM.HtmlScriptElement.Execute",
                              name.c_str()),
@@ -69,6 +80,8 @@
   // destroyed.
   DCHECK_EQ(count_html_element_, 0);
   DCHECK_EQ(count_html_element_document_, 0);
+  DCHECK_EQ(count_window_timers_interval_, 0);
+  DCHECK_EQ(count_window_timers_timeout_, 0);
 
   event_video_start_delay_stop_watch_.Stop();
 }
@@ -146,11 +159,20 @@
   count_html_element_document_ +=
       count_html_element_document_added_ - count_html_element_document_removed_;
 
+  count_window_timers_interval_ += count_window_timers_interval_created_ -
+                                   count_window_timers_interval_destroyed_;
+  count_window_timers_timeout_ += count_window_timers_timeout_created_ -
+                                  count_window_timers_timeout_destroyed_;
   // Now clear the values.
   count_html_element_created_ = 0;
   count_html_element_destroyed_ = 0;
   count_html_element_document_added_ = 0;
   count_html_element_document_removed_ = 0;
+
+  count_window_timers_interval_created_ = 0;
+  count_window_timers_interval_destroyed_ = 0;
+  count_window_timers_timeout_created_ = 0;
+  count_window_timers_timeout_destroyed_ = 0;
 }
 
 void DomStatTracker::StartTrackingEvent() {
diff --git a/src/cobalt/dom/dom_stat_tracker.h b/src/cobalt/dom/dom_stat_tracker.h
index 381b9f0..288444e 100644
--- a/src/cobalt/dom/dom_stat_tracker.h
+++ b/src/cobalt/dom/dom_stat_tracker.h
@@ -37,6 +37,19 @@
   explicit DomStatTracker(const std::string& name);
   ~DomStatTracker();
 
+  void OnWindowTimersIntervalCreated() {
+    ++count_window_timers_interval_created_;
+  }
+  void OnWindowTimersIntervalDestroyed() {
+    ++count_window_timers_interval_destroyed_;
+  }
+  void OnWindowTimersTimeoutCreated() {
+    ++count_window_timers_timeout_created_;
+  }
+  void OnWindowTimersTimeoutDestroyed() {
+    ++count_window_timers_timeout_destroyed_;
+  }
+
   void OnHtmlElementCreated();
   void OnHtmlElementDestroyed();
   void OnHtmlElementInsertedIntoDocument();
@@ -94,12 +107,19 @@
   base::CVal<int, base::CValPublic> count_html_element_;
   base::CVal<int, base::CValPublic> count_html_element_document_;
 
+  base::CVal<int, base::CValPublic> count_window_timers_interval_;
+  base::CVal<int, base::CValPublic> count_window_timers_timeout_;
+
   // Periodic counts. The counts are cleared after the CVals are updated in
   // |FlushPeriodicTracking|.
   int count_html_element_created_;
   int count_html_element_destroyed_;
   int count_html_element_document_added_;
   int count_html_element_document_removed_;
+  int count_window_timers_interval_created_;
+  int count_window_timers_interval_destroyed_;
+  int count_window_timers_timeout_created_;
+  int count_window_timers_timeout_destroyed_;
 
   // Count of HtmlScriptElement::Execute() calls, their total size in bytes, and
   // the time of last call.
diff --git a/src/cobalt/dom/dom_test.gyp b/src/cobalt/dom/dom_test.gyp
index 456965d..84aed7e 100644
--- a/src/cobalt/dom/dom_test.gyp
+++ b/src/cobalt/dom/dom_test.gyp
@@ -70,6 +70,7 @@
         'url_utils_test.cc',
         'user_agent_data_test.cc',
         'window_test.cc',
+        'window_timers_test.cc',
         'xml_document_test.cc',
       ],
       'dependencies': [
diff --git a/src/cobalt/dom/event_target.cc b/src/cobalt/dom/event_target.cc
index e663bb1..c125766 100644
--- a/src/cobalt/dom/event_target.cc
+++ b/src/cobalt/dom/event_target.cc
@@ -15,6 +15,7 @@
 #include "cobalt/dom/event_target.h"
 
 #include <memory>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
@@ -90,8 +91,9 @@
   DCHECK(event);
   DCHECK(!event->IsBeingDispatched());
   DCHECK(event->initialized_flag());
-  TRACE_EVENT1("cobalt::dom", "EventTarget::DispatchEvent", "event",
-               event->type().c_str());
+  TRACE_EVENT2("cobalt::dom", "EventTarget::DispatchEvent", "name",
+               TRACE_STR_COPY(GetDebugName().c_str()), "event", TRACE_STR_COPY(event->type().c_str()));
+
   if (!event || event->IsBeingDispatched() || !event->initialized_flag()) {
     return false;
   }
diff --git a/src/cobalt/dom/html_element_context.h b/src/cobalt/dom/html_element_context.h
index aa4de99..cb4183d 100644
--- a/src/cobalt/dom/html_element_context.h
+++ b/src/cobalt/dom/html_element_context.h
@@ -25,8 +25,8 @@
 #include "cobalt/dom/application_lifecycle_state.h"
 #include "cobalt/dom/dom_stat_tracker.h"
 #include "cobalt/dom/parser.h"
-#include "cobalt/dom/url_registry.h"
 #include "cobalt/dom/performance.h"
+#include "cobalt/dom/url_registry.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/font/remote_typeface_cache.h"
 #include "cobalt/loader/image/animated_image_tracker.h"
@@ -75,8 +75,7 @@
       const std::string& font_language_script,
       base::ApplicationState initial_application_state,
       base::WaitableEvent* synchronous_loader_interrupt,
-      Performance* performance,
-      bool enable_inline_script_warnings = false,
+      Performance* performance, bool enable_inline_script_warnings = false,
       float video_playback_rate_multiplier = 1.0);
   ~HTMLElementContext();
 
diff --git a/src/cobalt/dom/html_media_element.cc b/src/cobalt/dom/html_media_element.cc
index 84edb94..745faf2 100644
--- a/src/cobalt/dom/html_media_element.cc
+++ b/src/cobalt/dom/html_media_element.cc
@@ -271,8 +271,6 @@
       NOTREACHED();
   }
   MLOG() << "(" << mime_type << ", " << key_system << ") => " << result;
-  LOG(INFO) << "HTMLMediaElement::canPlayType(" << mime_type << ", "
-            << key_system << ") -> " << result;
   return result;
 }
 
diff --git a/src/cobalt/dom/location.cc b/src/cobalt/dom/location.cc
index dfcbea6..e34db7c 100644
--- a/src/cobalt/dom/location.cc
+++ b/src/cobalt/dom/location.cc
@@ -23,12 +23,15 @@
 
 Location::Location(const GURL& url, const base::Closure& hashchange_callback,
                    const base::Callback<void(const GURL&)>& navigation_callback,
-                   const csp::SecurityCallback& security_callback)
+                   const csp::SecurityCallback& security_callback,
+                   const base::Callback<void(NavigationType type)>&
+                       set_navigation_type_callback)
     : ALLOW_THIS_IN_INITIALIZER_LIST(url_utils_(
           url, base::Bind(&Location::Replace, base::Unretained(this)))),
       hashchange_callback_(hashchange_callback),
       navigation_callback_(navigation_callback),
-      security_callback_(security_callback) {}
+      security_callback_(security_callback),
+      set_navigation_type_callback_(set_navigation_type_callback) {}
 
 // Algorithm for Replace:
 //   https://www.w3.org/TR/html50/browsers.html#dom-location-replace
@@ -78,6 +81,9 @@
     if (!navigation_callback_.is_null()) {
       navigation_callback_.Run(new_url);
     }
+    if (!set_navigation_type_callback_.is_null()) {
+      set_navigation_type_callback_.Run(kNavigationTypeNavigate);
+    }
   }
 }
 
@@ -86,6 +92,9 @@
     LOG(INFO) << "Reloading URL: " << url();
     navigation_callback_.Run(url());
   }
+  if (!set_navigation_type_callback_.is_null()) {
+    set_navigation_type_callback_.Run(kNavigationTypeReload);
+  }
 }
 
 }  // namespace dom
diff --git a/src/cobalt/dom/location.h b/src/cobalt/dom/location.h
index d73dde0..b103683 100644
--- a/src/cobalt/dom/location.h
+++ b/src/cobalt/dom/location.h
@@ -19,6 +19,7 @@
 
 #include "base/callback.h"
 #include "cobalt/csp/content_security_policy.h"
+#include "cobalt/dom/navigation_type.h"
 #include "cobalt/dom/url_utils.h"
 #include "cobalt/script/wrappable.h"
 #include "url/gurl.h"
@@ -37,7 +38,9 @@
   // otherwise they can be empty.
   Location(const GURL& url, const base::Closure& hashchange_callback,
            const base::Callback<void(const GURL&)>& navigation_callback,
-           const csp::SecurityCallback& security_callback);
+           const csp::SecurityCallback& security_callback,
+           const base::Callback<void(NavigationType type)>&
+               set_navigation_type_callback);
 
   // Web API: Location
   //
@@ -102,6 +105,7 @@
   base::Closure hashchange_callback_;
   base::Callback<void(const GURL&)> navigation_callback_;
   csp::SecurityCallback security_callback_;
+  const base::Callback<void(NavigationType)> set_navigation_type_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(Location);
 };
diff --git a/src/cobalt/dom/location_test.cc b/src/cobalt/dom/location_test.cc
index 70385a4..a63cab6 100644
--- a/src/cobalt/dom/location_test.cc
+++ b/src/cobalt/dom/location_test.cc
@@ -62,8 +62,10 @@
   void Init(const GURL& url, const base::Closure& hashchange_callback,
             const base::Callback<void(const GURL&)>& navigation_callback,
             const csp::SecurityCallback& security_callback) {
+    base::Callback<void(NavigationType)> null_cb;
     location_ = new Location(url, hashchange_callback, navigation_callback,
-                             security_callback);
+                             security_callback,
+                             null_cb /*set_navigation_type_callback*/);
   }
 
   scoped_refptr<Location> location_;
diff --git a/src/cobalt/dom/media_source.cc b/src/cobalt/dom/media_source.cc
index a970978..c070968 100644
--- a/src/cobalt/dom/media_source.cc
+++ b/src/cobalt/dom/media_source.cc
@@ -52,6 +52,7 @@
 #include "base/compiler_specific.h"
 #include "base/guid.h"
 #include "base/logging.h"
+#include "base/trace_event/trace_event.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/dom_settings.h"
@@ -149,6 +150,7 @@
 scoped_refptr<SourceBuffer> MediaSource::AddSourceBuffer(
     script::EnvironmentSettings* settings, const std::string& type,
     script::ExceptionState* exception_state) {
+  TRACE_EVENT1("cobalt::dom", "MediaSource::AddSourceBuffer()", "type", type);
   DLOG(INFO) << "add SourceBuffer with type " << type;
 
   if (type.empty()) {
@@ -191,6 +193,7 @@
 void MediaSource::RemoveSourceBuffer(
     const scoped_refptr<SourceBuffer>& source_buffer,
     script::ExceptionState* exception_state) {
+  TRACE_EVENT0("cobalt::dom", "MediaSource::RemoveSourceBuffer()");
   if (source_buffer.get() == NULL) {
     DOMException::Raise(DOMException::kInvalidAccessErr, exception_state);
     return;
@@ -222,12 +225,14 @@
 }
 
 void MediaSource::EndOfStream(script::ExceptionState* exception_state) {
+  TRACE_EVENT0("cobalt::dom", "MediaSource::EndOfStream()");
   // If there is no error string provided, treat it as empty.
   EndOfStream(kMediaSourceEndOfStreamErrorNoError, exception_state);
 }
 
 void MediaSource::EndOfStream(MediaSourceEndOfStreamError error,
                               script::ExceptionState* exception_state) {
+  TRACE_EVENT1("cobalt::dom", "MediaSource::EndOfStream()", "error", error);
   if (!IsOpen() || IsUpdating()) {
     DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
     return;
@@ -265,28 +270,24 @@
 // static
 bool MediaSource::IsTypeSupported(script::EnvironmentSettings* settings,
                                   const std::string& type) {
+  TRACE_EVENT1("cobalt::dom", "MediaSource::IsTypeSupported()", "type", type);
   DCHECK(settings);
   DOMSettings* dom_settings =
       base::polymorphic_downcast<DOMSettings*>(settings);
   DCHECK(dom_settings->can_play_type_handler());
   SbMediaSupportType support_type =
       dom_settings->can_play_type_handler()->CanPlayAdaptive(type.c_str(), "");
-  if (support_type == kSbMediaSupportTypeNotSupported) {
-    LOG(INFO) << "MediaSource::IsTypeSupported(" << type
-              << ") -> not supported/false";
-    return false;
+  switch (support_type) {
+    case kSbMediaSupportTypeNotSupported:
+      return false;
+    case kSbMediaSupportTypeMaybe:
+      return true;
+    case kSbMediaSupportTypeProbably:
+      return true;
+    default:
+      NOTREACHED();
+      return false;
   }
-  if (support_type == kSbMediaSupportTypeMaybe) {
-    LOG(INFO) << "MediaSource::IsTypeSupported(" << type << ") -> maybe/true";
-    return true;
-  }
-  if (support_type == kSbMediaSupportTypeProbably) {
-    LOG(INFO) << "MediaSource::IsTypeSupported(" << type
-              << ") -> probably/true";
-    return true;
-  }
-  NOTREACHED();
-  return false;
 }
 
 bool MediaSource::AttachToElement(HTMLMediaElement* media_element) {
diff --git a/src/cobalt/dom/navigation_type.idl b/src/cobalt/dom/navigation_type.idl
new file mode 100644
index 0000000..4326021
--- /dev/null
+++ b/src/cobalt/dom/navigation_type.idl
@@ -0,0 +1,23 @@
+// 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.
+
+// https://www.w3.org/TR/2022/WD-navigation-timing-2-20220224/#sec-performance-navigation-types
+
+// Cobalt only supports "navigate" and "reload".
+enum NavigationType {
+    "navigate",
+    "reload",
+    "back_forward",
+    "prerender"
+};
diff --git a/src/cobalt/dom/node.cc b/src/cobalt/dom/node.cc
index 7df763a..0b39848 100644
--- a/src/cobalt/dom/node.cc
+++ b/src/cobalt/dom/node.cc
@@ -15,6 +15,7 @@
 #include "cobalt/dom/node.h"
 
 #include <memory>
+#include <utility>
 #include <vector>
 
 #include "base/lazy_instance.h"
@@ -58,7 +59,7 @@
   DCHECK(event->initialized_flag());
 
   TRACE_EVENT1("cobalt::dom", "Node::DispatchEvent", "event",
-               event->type().c_str());
+               TRACE_STR_COPY(event->type().c_str()));
 
   if (!event || event->IsBeingDispatched() || !event->initialized_flag()) {
     return false;
diff --git a/src/cobalt/dom/performance.h b/src/cobalt/dom/performance.h
index 5193232..f6c3391 100644
--- a/src/cobalt/dom/performance.h
+++ b/src/cobalt/dom/performance.h
@@ -69,8 +69,7 @@
       base::TimeTicks monotonic_time) const;
 
   static DOMHighResTimeStamp MonotonicTimeToDOMHighResTimeStamp(
-      base::TimeTicks time_origin,
-      base::TimeTicks monotonic_time);
+      base::TimeTicks time_origin, base::TimeTicks monotonic_time);
 
   // Web API: Performance Timeline extensions to the Performance.
   //   https://www.w3.org/TR/performance-timeline-2/#extensions-to-the-performance-interface
@@ -109,12 +108,13 @@
   void CreatePerformanceResourceTiming(const net::LoadTimingInfo& timing_info,
                                        const std::string& initiator_type,
                                        const std::string& requested_url);
-  void CreatePerformanceLifecycleTiming();
+  void AddEntryToPerformanceEntryBuffer(
+      const scoped_refptr<PerformanceEntry>& entry) {
+    performance_entry_buffer_.push_back(entry);
+  }
   // Custom, not in any spec.
   // Internal getter method for the time origin value.
-  base::TimeTicks GetTimeOrigin() const {
-      return time_origin_;
-  }
+  base::TimeTicks GetTimeOrigin() const { return time_origin_; }
   // Register and unregisterthe performance observer.
   void UnregisterPerformanceObserver(
       const scoped_refptr<PerformanceObserver>& observer);
@@ -133,8 +133,8 @@
   void SetApplicationState(base::ApplicationState state,
                            SbTimeMonotonic timestamp);
 
-  void SetApplicationStartOrPreloadTimestamp(
-      bool is_preload, SbTimeMonotonic timestamp);
+  void SetApplicationStartOrPreloadTimestamp(bool is_preload,
+                                             SbTimeMonotonic timestamp);
 
   void SetDeepLinkTimestamp(SbTimeMonotonic timestamp);
 
@@ -142,7 +142,8 @@
   DEFINE_WRAPPABLE_TYPE(Performance);
 
  private:
-  unsigned long GetDroppedEntriesCount(const std::string& entry_type);
+  unsigned long GetDroppedEntriesCount(  // NOLINT(runtime/int)
+      const std::string& entry_type);
 
   base::TimeTicks time_origin_;
   const base::TickClock* tick_clock_;
@@ -170,10 +171,10 @@
       RegisteredPerformanceObserverList;
   RegisteredPerformanceObserverList registered_performance_observers_;
 
-  unsigned long resource_timing_buffer_size_limit_;
-  unsigned long resource_timing_buffer_current_size_;
+  unsigned long resource_timing_buffer_size_limit_;    // NOLINT(runtime/int)
+  unsigned long resource_timing_buffer_current_size_;  // NOLINT(runtime/int)
   bool resource_timing_buffer_full_event_pending_flag_;
-  unsigned long
+  unsigned long  // NOLINT(runtime/int)
       resource_timing_secondary_buffer_current_size_;
   std::deque<scoped_refptr<PerformanceResourceTiming>>
       resource_timing_secondary_buffer_;
diff --git a/src/cobalt/dom/performance_lifecycle_timing.cc b/src/cobalt/dom/performance_lifecycle_timing.cc
index d81ebb0..e06763f 100644
--- a/src/cobalt/dom/performance_lifecycle_timing.cc
+++ b/src/cobalt/dom/performance_lifecycle_timing.cc
@@ -21,32 +21,32 @@
 
 namespace {
 
-  std::string TranslateApplicationStateToString(
-      base::ApplicationState state) {
-    switch (state) {
-      case base::kApplicationStateBlurred:
-        return "ApplicationStateBlurred";
-      case base::kApplicationStateConcealed:
-        return "ApplicationStateConcealed";
-      case base::kApplicationStateFrozen:
-        return "ApplicationStateFrozen";
-      case base::kApplicationStateStarted:
-        return "ApplicationStateStarted";
-      case base::kApplicationStateStopped:
-        return "ApplicationStateStopped";
-    }
-
-    NOTREACHED() << "state = " << state;
-    return "INVALID_APPLICATION_STATE";
+std::string TranslateApplicationStateToString(base::ApplicationState state) {
+  switch (state) {
+    case base::kApplicationStateBlurred:
+      return "ApplicationStateBlurred";
+    case base::kApplicationStateConcealed:
+      return "ApplicationStateConcealed";
+    case base::kApplicationStateFrozen:
+      return "ApplicationStateFrozen";
+    case base::kApplicationStateStarted:
+      return "ApplicationStateStarted";
+    case base::kApplicationStateStopped:
+      return "ApplicationStateStopped";
   }
 
-    DOMHighResTimeStamp ConvertSbTimeMonotonicToDOMHiResTimeStamp(
-      const DOMHighResTimeStamp time_origin, SbTimeMonotonic monotonic_time) {
-    SbTimeMonotonic time_delta = SbTimeGetNow() - SbTimeGetMonotonicNow();
-    base::Time base_time = base::Time::FromSbTime(time_delta + monotonic_time);
-    return ClampTimeStampMinimumResolution(base_time.ToJsTime() - time_origin,
-        Performance::kPerformanceTimerMinResolutionInMicroseconds);
-  }
+  NOTREACHED() << "state = " << state;
+  return "INVALID_APPLICATION_STATE";
+}
+
+DOMHighResTimeStamp ConvertSbTimeMonotonicToDOMHiResTimeStamp(
+    const DOMHighResTimeStamp time_origin, SbTimeMonotonic monotonic_time) {
+  SbTimeMonotonic time_delta = SbTimeGetNow() - SbTimeGetMonotonicNow();
+  base::Time base_time = base::Time::FromSbTime(time_delta + monotonic_time);
+  return ClampTimeStampMinimumResolution(
+      base_time.ToJsTime() - time_origin,
+      Performance::kPerformanceTimerMinResolutionInMicroseconds);
+}
 
 }  // namespace
 
@@ -96,8 +96,7 @@
 }
 
 std::string PerformanceLifecycleTiming::last_state() const {
-  return TranslateApplicationStateToString(
-      lifecycle_timing_info_.last_state);
+  return TranslateApplicationStateToString(lifecycle_timing_info_.last_state);
 }
 
 void PerformanceLifecycleTiming::SetApplicationState(
@@ -112,10 +111,10 @@
       } else if (GetCurrentState() == base::kApplicationStateConcealed) {
         lifecycle_timing_info_.app_reveal = timestamp;
       } else {
-        DLOG(INFO) << "Current State: " <<
-            TranslateApplicationStateToString(GetCurrentState());
-        DLOG(INFO) << "Next State: " <<
-            TranslateApplicationStateToString(state);
+        DLOG(INFO) << "Current State: "
+                   << TranslateApplicationStateToString(GetCurrentState());
+        DLOG(INFO) << "Next State: "
+                   << TranslateApplicationStateToString(state);
         NOTREACHED() << "Invalid application state transition.";
       }
       break;
@@ -126,10 +125,10 @@
       } else if (GetCurrentState() == base::kApplicationStateFrozen) {
         lifecycle_timing_info_.app_unfreeze = timestamp;
       } else {
-        DLOG(INFO) << "Current State: " <<
-            TranslateApplicationStateToString(GetCurrentState());
-        DLOG(INFO) << "Next State: " <<
-            TranslateApplicationStateToString(state);
+        DLOG(INFO) << "Current State: "
+                   << TranslateApplicationStateToString(GetCurrentState());
+        DLOG(INFO) << "Next State: "
+                   << TranslateApplicationStateToString(state);
         NOTREACHED() << "Invalid application state transition.";
       }
       break;
@@ -138,7 +137,7 @@
       break;
     case base::kApplicationStateStarted:
       if (GetCurrentState() == base::kApplicationStateBlurred) {
-        if (lifecycle_timing_info_.app_preload != 0) {
+        if (lifecycle_timing_info_.app_start == 0) {
           lifecycle_timing_info_.app_start = timestamp;
         }
         lifecycle_timing_info_.app_focus = timestamp;
@@ -176,19 +175,17 @@
 
 void PerformanceLifecycleTiming::SetLifecycleTimingInfoState(
     base::ApplicationState state) {
-  lifecycle_timing_info_.last_state =
-      lifecycle_timing_info_.current_state;
+  lifecycle_timing_info_.last_state = lifecycle_timing_info_.current_state;
   lifecycle_timing_info_.current_state = state;
 }
 
 DOMHighResTimeStamp PerformanceLifecycleTiming::ReportDOMHighResTimeStamp(
     SbTimeMonotonic timestamp) const {
   if (timestamp != 0) {
-    return ConvertSbTimeMonotonicToDOMHiResTimeStamp(time_origin_,
-                                                     timestamp);
+    return ConvertSbTimeMonotonicToDOMHiResTimeStamp(time_origin_, timestamp);
   }
   return PerformanceEntry::start_time();
 }
 
 }  // namespace dom
-}  // namespace cobalt
\ No newline at end of file
+}  // namespace cobalt
diff --git a/src/cobalt/dom/performance_navigation_timing.cc b/src/cobalt/dom/performance_navigation_timing.cc
new file mode 100644
index 0000000..ca6c3eb
--- /dev/null
+++ b/src/cobalt/dom/performance_navigation_timing.cc
@@ -0,0 +1,92 @@
+// 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/performance_navigation_timing.h"
+#include "cobalt/dom/performance.h"
+
+#include "cobalt/dom/document.h"
+
+namespace cobalt {
+namespace dom {
+
+namespace {
+//  https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype
+const char kPerformanceNavigationInitiatorType[] = "navigation";
+}  // namespace
+
+PerformanceNavigationTiming::PerformanceNavigationTiming(
+    const net::LoadTimingInfo& timing_info, const std::string& requested_url,
+    Performance* performance, Document* document, base::TimeTicks time_origin)
+    : PerformanceResourceTiming(timing_info,
+                                kPerformanceNavigationInitiatorType,
+                                requested_url, performance, time_origin),
+      document_(base::AsWeakPtr(document)),
+      time_origin_(time_origin) {}
+
+std::string PerformanceNavigationTiming::initiator_type() const {
+  return kPerformanceNavigationInitiatorType;
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::unload_event_start() const {
+  base::TimeTicks start_time = document_->GetDocumentUnloadEventStartTime();
+  if (start_time.is_null()) {
+    return 0.0;
+  }
+
+  return Performance::MonotonicTimeToDOMHighResTimeStamp(time_origin_,
+                                                         start_time);
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::unload_event_end() const {
+  base::TimeTicks end_time = document_->GetDocumentUnloadEventEndTime();
+  if (end_time.is_null()) {
+    return 0.0;
+  }
+
+  return Performance::MonotonicTimeToDOMHighResTimeStamp(time_origin_,
+                                                         end_time);
+}
+
+DOMHighResTimeStamp
+PerformanceNavigationTiming::dom_content_loaded_event_start() const {
+  return document_->GetDocumentContentLoadedEventStartTime();
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::dom_content_loaded_event_end()
+    const {
+  return document_->GetDocumentContentLoadedEventEndTime();
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::dom_complete() const {
+  return document_->GetDocumentDomCompleteTime();
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::load_event_start() const {
+  return document_->GetDocumentLoadEventStartTime();
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::load_event_end() const {
+  return document_->GetDocumentLoadEventEndTime();
+}
+
+NavigationType PerformanceNavigationTiming::type() const {
+  return document_->GetNavigationType();
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::duration() const {
+  return load_event_end() - start_time();
+}
+
+}  // namespace dom
+}  // namespace cobalt
diff --git a/src/cobalt/dom/performance_navigation_timing.h b/src/cobalt/dom/performance_navigation_timing.h
new file mode 100644
index 0000000..49b1a4b
--- /dev/null
+++ b/src/cobalt/dom/performance_navigation_timing.h
@@ -0,0 +1,78 @@
+// 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.
+
+#ifndef COBALT_DOM_PERFORMANCE_NAVIGATION_TIMING_H_
+#define COBALT_DOM_PERFORMANCE_NAVIGATION_TIMING_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "cobalt/dom/navigation_type.h"
+#include "cobalt/dom/performance_entry.h"
+#include "cobalt/dom/performance_high_resolution_time.h"
+#include "cobalt/dom/performance_resource_timing.h"
+
+#include "cobalt/script/wrappable.h"
+#include "net/base/load_timing_info.h"
+
+namespace cobalt {
+namespace dom {
+
+class Document;
+class Performance;
+
+// Implements the Performance Navigation Timing IDL interface, as described
+// here:
+//   https://www.w3.org/TR/navigation-timing-2/
+class PerformanceNavigationTiming : public PerformanceResourceTiming {
+ public:
+  PerformanceNavigationTiming(const net::LoadTimingInfo& timing_info,
+                              const std::string& requested_url,
+                              Performance* performance, Document* document,
+                              base::TimeTicks time_origin);
+
+  // Web API.
+  DOMHighResTimeStamp unload_event_start() const;
+  DOMHighResTimeStamp unload_event_end() const;
+  DOMHighResTimeStamp dom_content_loaded_event_start() const;
+  DOMHighResTimeStamp dom_content_loaded_event_end() const;
+  DOMHighResTimeStamp dom_complete() const;
+  DOMHighResTimeStamp load_event_start() const;
+  DOMHighResTimeStamp load_event_end() const;
+  NavigationType type() const;
+
+  // PerformanceResourceTiming overrides.
+  std::string initiator_type() const override;
+
+  // PerformanceEntry overrides. Return a timestamp equal to the difference
+  // between loadEventEnd and its startTime.
+  DOMHighResTimeStamp duration() const override;
+
+  std::string entry_type() const override { return "navigation"; }
+  PerformanceEntryType EntryTypeEnum() const override {
+    return PerformanceEntry::kNavigation;
+  }
+
+  DEFINE_WRAPPABLE_TYPE(PerformanceNavigationTiming);
+
+ private:
+  base::WeakPtr<Document> document_;
+  base::TimeTicks time_origin_;
+
+  DISALLOW_COPY_AND_ASSIGN(PerformanceNavigationTiming);
+};
+}  // namespace dom
+}  // namespace cobalt
+
+#endif  // COBALT_DOM_PERFORMANCE_NAVIGATION_TIMING_H_
diff --git a/src/cobalt/dom/performance_navigation_timing.idl b/src/cobalt/dom/performance_navigation_timing.idl
new file mode 100644
index 0000000..4883cc3
--- /dev/null
+++ b/src/cobalt/dom/performance_navigation_timing.idl
@@ -0,0 +1,27 @@
+// 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.
+
+// https://www.w3.org/TR/2022/WD-navigation-timing-2-20220224/
+
+[Exposed=Window]
+interface PerformanceNavigationTiming : PerformanceResourceTiming {
+  readonly attribute DOMHighResTimeStamp unloadEventStart;
+  readonly attribute DOMHighResTimeStamp unloadEventEnd;
+  readonly attribute DOMHighResTimeStamp domContentLoadedEventStart;
+  readonly attribute DOMHighResTimeStamp domContentLoadedEventEnd;
+  readonly attribute DOMHighResTimeStamp domComplete;
+  readonly attribute DOMHighResTimeStamp loadEventStart;
+  readonly attribute DOMHighResTimeStamp loadEventEnd;
+  readonly attribute NavigationType      type;
+};
diff --git a/src/cobalt/dom/performance_observer.cc b/src/cobalt/dom/performance_observer.cc
index 6e0855a..eebf4b8 100644
--- a/src/cobalt/dom/performance_observer.cc
+++ b/src/cobalt/dom/performance_observer.cc
@@ -35,6 +35,9 @@
       : callback_(observer, callback) {}
   bool RunCallback(const scoped_refptr<PerformanceObserverEntryList>& entries,
                    const scoped_refptr<PerformanceObserver>& observer) override {
+    // ScriptCallback's lifetime is bined with JS PerformanceObserver object. We
+    // should not run callback, When JS object has been destroyed.
+    if (callback_.referenced_value().IsNull()) return false;
     script::CallbackResult<void> result = callback_.value().Run(entries, observer);
     return !result.exception;
   }
diff --git a/src/cobalt/dom/performance_resource_timing.cc b/src/cobalt/dom/performance_resource_timing.cc
index 11dac39..4a0f314 100644
--- a/src/cobalt/dom/performance_resource_timing.cc
+++ b/src/cobalt/dom/performance_resource_timing.cc
@@ -41,7 +41,8 @@
       cache_mode_(kPerformanceResourceTimingCacheMode),
       transfer_size_(0),
       timing_info_(timing_info),
-      time_origin_(time_origin) {}
+      time_origin_(time_origin),
+      timing_info_response_end_(performance->Now()) {}
 
 std::string PerformanceResourceTiming::initiator_type() const {
   return initiator_type_;
@@ -112,7 +113,7 @@
 }
 
 DOMHighResTimeStamp PerformanceResourceTiming::response_end() const {
-  return response_start();
+  return timing_info_response_end_;
 }
 
 unsigned long long PerformanceResourceTiming::transfer_size() const {
diff --git a/src/cobalt/dom/performance_resource_timing.h b/src/cobalt/dom/performance_resource_timing.h
index cab3c48..1809b4d 100644
--- a/src/cobalt/dom/performance_resource_timing.h
+++ b/src/cobalt/dom/performance_resource_timing.h
@@ -43,7 +43,7 @@
                             base::TimeTicks time_origin);
 
   // Web API.
-  std::string initiator_type() const;
+  virtual std::string initiator_type() const;
   DOMHighResTimeStamp fetch_start() const;
   DOMHighResTimeStamp domain_lookup_start() const;
   DOMHighResTimeStamp domain_lookup_end() const;
@@ -52,9 +52,8 @@
   DOMHighResTimeStamp secure_connection_start() const;
   DOMHighResTimeStamp request_start() const;
   DOMHighResTimeStamp response_start() const;
-  // As we don't have response start in LoadTimingInfo, we use
-  // response start instead.
-  // TODO: Add response_end into LoadTimingInfo.
+  // As we don't have response end in LoadTimingInfo, we collect the timestamp
+  // of loading completes instead.
   DOMHighResTimeStamp response_end() const;
   unsigned long long transfer_size() const;
 
@@ -77,6 +76,7 @@
   std::string requested_url_;
   net::LoadTimingInfo timing_info_;
   base::TimeTicks time_origin_;
+  DOMHighResTimeStamp timing_info_response_end_;
 
   DISALLOW_COPY_AND_ASSIGN(PerformanceResourceTiming);
 };
diff --git a/src/cobalt/dom/pointer_state.cc b/src/cobalt/dom/pointer_state.cc
index 7b2b2b4..49ae842 100644
--- a/src/cobalt/dom/pointer_state.cc
+++ b/src/cobalt/dom/pointer_state.cc
@@ -15,6 +15,7 @@
 #include "cobalt/dom/pointer_state.h"
 
 #include <algorithm>
+#include <utility>
 
 #include "base/trace_event/trace_event.h"
 #include "cobalt/dom/mouse_event.h"
@@ -27,7 +28,7 @@
 
 void PointerState::QueuePointerEvent(const scoped_refptr<Event>& event) {
   TRACE_EVENT1("cobalt::dom", "PointerState::QueuePointerEvent()", "event",
-               event->type().c_str());
+               TRACE_STR_COPY(event->type().c_str()));
 
   // Only accept this for event types that are MouseEvents or known derivatives.
   SB_DCHECK(CanQueueEvent(event));
diff --git a/src/cobalt/dom/source_buffer.cc b/src/cobalt/dom/source_buffer.cc
index de6fd76..7fae5df 100644
--- a/src/cobalt/dom/source_buffer.cc
+++ b/src/cobalt/dom/source_buffer.cc
@@ -49,6 +49,7 @@
 #include "base/compiler_specific.h"
 #include "base/logging.h"
 #include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/media_source.h"
@@ -215,6 +216,8 @@
 
 void SourceBuffer::AppendBuffer(const script::Handle<script::ArrayBuffer>& data,
                                 script::ExceptionState* exception_state) {
+  TRACE_EVENT1("cobalt::dom", "SourceBuffer::AppendBuffer()", "size",
+               data->ByteLength());
   AppendBufferInternal(static_cast<const unsigned char*>(data->Data()),
                        data->ByteLength(), exception_state);
 }
@@ -222,11 +225,15 @@
 void SourceBuffer::AppendBuffer(
     const script::Handle<script::ArrayBufferView>& data,
     script::ExceptionState* exception_state) {
+  TRACE_EVENT1("cobalt::dom", "SourceBuffer::AppendBuffer()", "size",
+               data->ByteLength());
   AppendBufferInternal(static_cast<const unsigned char*>(data->RawData()),
                        data->ByteLength(), exception_state);
 }
 
 void SourceBuffer::Abort(script::ExceptionState* exception_state) {
+  TRACE_EVENT0("cobalt::dom", "SourceBuffer::Abort()");
+
   if (media_source_ == NULL) {
     DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
     return;
@@ -427,6 +434,7 @@
 }
 
 void SourceBuffer::OnAppendTimer() {
+  TRACE_EVENT0("cobalt::dom", "SourceBuffer::OnAppendTimer()");
   const size_t kMaxAppendSize = 128 * 1024;
 
   DCHECK(updating_);
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc
index 6fde6ea..9005f3c 100644
--- a/src/cobalt/dom/window.cc
+++ b/src/cobalt/dom/window.cc
@@ -165,7 +165,8 @@
       ALLOW_THIS_IN_INITIALIZER_LIST(
           relay_on_load_event_(new RelayLoadEvent(this))),
       ALLOW_THIS_IN_INITIALIZER_LIST(window_timers_(
-          new WindowTimers(this, debugger_hooks(), initial_application_state))),
+          new WindowTimers(this, dom_stat_tracker, debugger_hooks(),
+                           initial_application_state))),
       ALLOW_THIS_IN_INITIALIZER_LIST(animation_frame_request_callback_list_(
           new AnimationFrameRequestCallbackList(this, debugger_hooks()))),
       crypto_(new Crypto()),
@@ -418,7 +419,7 @@
 int Window::SetInterval(const WindowTimers::TimerCallbackArg& handler,
                         int timeout) {
   LOG_IF(WARNING, timeout < 0)
-      << "Window::SetInterval received negative timeout: " << timeout;
+      << "Window::SetInterval received negative interval: " << timeout;
   timeout = std::max(timeout, 0);
 
   int return_value = 0;
@@ -510,7 +511,7 @@
 
 void Window::InjectEvent(const scoped_refptr<Event>& event) {
   TRACE_EVENT1("cobalt::dom", "Window::InjectEvent()", "event",
-               event->type().c_str());
+               TRACE_STR_COPY(event->type().c_str()));
 
   // Forward the event on to the correct object in DOM.
   if (event->GetWrappableType() == base::GetTypeId<KeyboardEvent>()) {
diff --git a/src/cobalt/dom/window.h b/src/cobalt/dom/window.h
index d6bf1ab..70b11f7 100644
--- a/src/cobalt/dom/window.h
+++ b/src/cobalt/dom/window.h
@@ -409,6 +409,8 @@
 
   const scoped_refptr<media_session::MediaSession> media_session() const;
 
+  loader::Loader* GetDocumentLoader() const { return document_loader_.get(); }
+
   DEFINE_WRAPPABLE_TYPE(Window);
 
  private:
diff --git a/src/cobalt/dom/window_timers.cc b/src/cobalt/dom/window_timers.cc
index 3b5c079..951f7ee 100644
--- a/src/cobalt/dom/window_timers.cc
+++ b/src/cobalt/dom/window_timers.cc
@@ -40,16 +40,13 @@
   }
 
   if (callbacks_active_) {
-    auto* timer = new Timer(type, owner_, handler, timeout, handle, this);
+    scoped_refptr<Timer> timer =
+        new Timer(type, owner_, dom_stat_tracker_, debugger_hooks_, handler,
+                  timeout, handle, this);
     if (application_state_ != base::kApplicationStateFrozen) {
       timer->StartOrResume();
     }
     timers_[handle] = timer;
-    debugger_hooks_.AsyncTaskScheduled(
-        timers_[handle], type == Timer::kOneShot ? "SetTimeout" : "SetInterval",
-        type == Timer::kOneShot
-            ? base::DebuggerHooks::AsyncTaskFrequency::kOneshot
-            : base::DebuggerHooks::AsyncTaskFrequency::kRecurring);
   } else {
     timers_[handle] = nullptr;
   }
@@ -62,33 +59,31 @@
   return TryAddNewTimer(Timer::kOneShot, handler, timeout);
 }
 
-void WindowTimers::ClearTimeout(int handle) { timers_.erase(handle); }
+void WindowTimers::ClearTimeout(int handle) {
+  Timers::iterator timer = timers_.find(handle);
+  if (timer == timers_.end()) {
+    return;
+  }
+  if (timer->second) {
+    timer->second->Disable();
+  }
+  timers_.erase(timer);
+}
 
 int WindowTimers::SetInterval(const TimerCallbackArg& handler, int timeout) {
   TRACK_MEMORY_SCOPE("DOM");
   return TryAddNewTimer(Timer::kRepeating, handler, timeout);
 }
 
-void WindowTimers::ClearInterval(int handle) {
-  Timers::iterator timer = timers_.find(handle);
-  if (timer != timers_.end()) {
-    debugger_hooks_.AsyncTaskCanceled(timer->second);
-    timers_.erase(timer);
-  }
-}
-
-void WindowTimers::ClearAllIntervalsAndTimeouts() {
-  for (auto& timer_entry : timers_) {
-    debugger_hooks_.AsyncTaskCanceled(timer_entry.second);
-  }
-  timers_.clear();
-}
+void WindowTimers::ClearInterval(int handle) { ClearTimeout(handle); }
 
 void WindowTimers::DisableCallbacks() {
   callbacks_active_ = false;
   // Immediately cancel any pending timers.
   for (auto& timer_entry : timers_) {
-    debugger_hooks_.AsyncTaskCanceled(timer_entry.second);
+    if (timer_entry.second) {
+      timer_entry.second->Disable();
+    }
     timer_entry.second = nullptr;
   }
 }
@@ -113,38 +108,6 @@
   return 0;
 }
 
-void WindowTimers::RunTimerCallback(int handle) {
-  TRACE_EVENT0("cobalt::dom", "WindowTimers::RunTimerCallback");
-  DCHECK(callbacks_active_)
-      << "All timer callbacks should have already been cancelled.";
-  Timers::iterator timer = timers_.find(handle);
-  DCHECK(timer != timers_.end());
-
-  // The callback is now being run. Track it in the global stats.
-  GlobalStats::GetInstance()->StartJavaScriptEvent();
-
-  {
-    // Keep a |TimerInfo| reference, so it won't be released when running the
-    // callback.
-    scoped_refptr<Timer> timer_info = timer->second;
-    base::ScopedAsyncTask async_task(debugger_hooks_, timer_info);
-    timer_info->callback_reference().value().Run();
-  }
-
-  // After running the callback, double check whether the timer is still there
-  // since it might be deleted inside the callback.
-  timer = timers_.find(handle);
-  // If the timer is not deleted and is not running, it means it is an oneshot
-  // timer and has just fired the shot, and it should be deleted now.
-  if (timer != timers_.end() && !timer->second->timer()->IsRunning()) {
-    debugger_hooks_.AsyncTaskCanceled(timer->second);
-    timers_.erase(timer);
-  }
-
-  // The callback has finished running. Stop tracking it in the global stats.
-  GlobalStats::GetInstance()->StopJavaScriptEvent();
-}
-
 void WindowTimers::SetApplicationState(base::ApplicationState state) {
   switch (state) {
     case base::kApplicationStateFrozen:
@@ -169,13 +132,66 @@
 }
 
 WindowTimers::Timer::Timer(TimerType type, script::Wrappable* const owner,
+                           DomStatTracker* dom_stat_tracker,
+                           const base::DebuggerHooks& debugger_hooks,
                            const TimerCallbackArg& callback, int timeout,
                            int handle, WindowTimers* window_timers)
     : type_(type),
       callback_(owner, callback),
+      dom_stat_tracker_(dom_stat_tracker),
+      debugger_hooks_(debugger_hooks),
       timeout_(timeout),
       handle_(handle),
-      window_timers_(window_timers) {}
+      active_(false),
+      window_timers_(window_timers) {
+  debugger_hooks_.AsyncTaskScheduled(
+      this, type == Timer::kOneShot ? "SetTimeout" : "SetInterval",
+      type == Timer::kOneShot
+          ? base::DebuggerHooks::AsyncTaskFrequency::kOneshot
+          : base::DebuggerHooks::AsyncTaskFrequency::kRecurring);
+  switch (type) {
+    case Timer::kOneShot:
+      dom_stat_tracker_->OnWindowTimersTimeoutCreated();
+      break;
+    case Timer::kRepeating:
+      dom_stat_tracker_->OnWindowTimersIntervalCreated();
+      break;
+  }
+}
+
+WindowTimers::Timer::~Timer() {
+  switch (type_) {
+    case Timer::kOneShot:
+      dom_stat_tracker_->OnWindowTimersTimeoutDestroyed();
+      break;
+    case Timer::kRepeating:
+      dom_stat_tracker_->OnWindowTimersIntervalDestroyed();
+      break;
+  }
+  debugger_hooks_.AsyncTaskCanceled(this);
+}
+
+void WindowTimers::Timer::Run() {
+  if (!active_) {
+    return;
+  }
+
+  // The callback is now being run. Track it in the global stats.
+  GlobalStats::GetInstance()->StartJavaScriptEvent();
+
+  {
+    base::ScopedAsyncTask async_task(debugger_hooks_, this);
+    callback_.value().Run();
+  }
+
+  // Remove one-shot timers from the timers list.
+  if (active_ && !timer_->IsRunning()) {
+    window_timers_->ClearTimeout(handle_);
+  }
+
+  // The callback has finished running. Stop tracking it in the global stats.
+  GlobalStats::GetInstance()->StopJavaScriptEvent();
+}
 
 void WindowTimers::Timer::Pause() {
   if (timer_) {
@@ -188,6 +204,7 @@
 
 void WindowTimers::Timer::StartOrResume() {
   if (timer_ != nullptr) return;
+  active_ = true;
   switch (type_) {
     case kOneShot:
       if (desired_run_time_) {
@@ -215,20 +232,27 @@
         // The timer was paused and the desired run time is in the past.
         // Call the callback once before continuing the repeating timer.
         base::SequencedTaskRunnerHandle::Get()->PostTask(
-            FROM_HERE, base::Bind(&WindowTimers::RunTimerCallback,
-                                  base::Unretained(window_timers_), handle_));
+            FROM_HERE, base::Bind(&WindowTimers::Timer::Run, this));
       }
       break;
   }
 }
 
+void WindowTimers::Timer::Disable() {
+  // Prevent the timer callback from doing anything.
+  active_ = false;
+
+  // Clear the TimerBase object to release the reference to |this| in the
+  // user callback.
+  timer_.reset();
+}
+
 template <class TimerClass>
 std::unique_ptr<base::internal::TimerBase>
 WindowTimers::Timer::CreateAndStart() {
   auto* timer = new TimerClass();
   timer->Start(FROM_HERE, base::TimeDelta::FromMilliseconds(timeout_),
-               base::Bind(&WindowTimers::RunTimerCallback,
-                          base::Unretained(window_timers_), handle_));
+               base::Bind(&WindowTimers::Timer::Run, this));
   return std::unique_ptr<base::internal::TimerBase>(timer);
 }
 
diff --git a/src/cobalt/dom/window_timers.h b/src/cobalt/dom/window_timers.h
index 42dcf56..fc81784 100644
--- a/src/cobalt/dom/window_timers.h
+++ b/src/cobalt/dom/window_timers.h
@@ -26,6 +26,7 @@
 #include "base/timer/timer.h"
 #include "cobalt/base/application_state.h"
 #include "cobalt/base/debugger_hooks.h"
+#include "cobalt/dom/dom_stat_tracker.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/script/wrappable.h"
@@ -38,12 +39,14 @@
   typedef script::CallbackFunction<void()> TimerCallback;
   typedef script::ScriptValue<TimerCallback> TimerCallbackArg;
   explicit WindowTimers(script::Wrappable* const owner,
+                        DomStatTracker* dom_stat_tracker,
                         const base::DebuggerHooks& debugger_hooks,
                         base::ApplicationState application_state)
       : owner_(owner),
+        dom_stat_tracker_(dom_stat_tracker),
         debugger_hooks_(debugger_hooks),
         application_state_(application_state) {}
-  ~WindowTimers() {}
+  ~WindowTimers() { DisableCallbacks(); }
 
   int SetTimeout(const TimerCallbackArg& handler, int timeout);
 
@@ -53,8 +56,6 @@
 
   void ClearInterval(int handle);
 
-  void ClearAllIntervalsAndTimeouts();
-
   // When called, it will irreversibly put the WindowTimers object in an
   // inactive state where timer callbacks are ignored.  This is useful when
   // we're in the process of shutting down and wish to drain the JavaScript
@@ -69,11 +70,12 @@
     enum TimerType { kOneShot, kRepeating };
 
     Timer(TimerType type, script::Wrappable* const owner,
+          DomStatTracker* dom_stat_tracker,
+          const base::DebuggerHooks& debugger_hooks,
           const TimerCallbackArg& callback, int timeout, int handle,
           WindowTimers* window_timers);
 
-    base::internal::TimerBase* timer() { return timer_.get(); }
-    TimerCallbackArg::Reference& callback_reference() { return callback_; }
+    void Run();
 
     // Pause this timer. The timer will not fire when paused.
     void Pause();
@@ -81,8 +83,11 @@
     // time is in the past, it will fire immediately.
     void StartOrResume();
 
+    // Disable the timer callback.
+    void Disable();
+
    private:
-    ~Timer() {}
+    ~Timer();
 
     // Create and start a timer of the specified TimerClass type.
     template <class TimerClass>
@@ -91,8 +96,11 @@
     TimerType type_;
     std::unique_ptr<base::internal::TimerBase> timer_;
     TimerCallbackArg::Reference callback_;
+    DomStatTracker* const dom_stat_tracker_;
+    const base::DebuggerHooks& debugger_hooks_;
     int timeout_;
     int handle_;
+    bool active_;
     WindowTimers* window_timers_;
 
     // Store the desired run tim of a paused timer.
@@ -111,13 +119,10 @@
   // if none can be found.
   int GetFreeTimerHandle();
 
-  // This callback, when called by Timer, runs the callback in TimerInfo
-  // and removes the handle if necessary.
-  void RunTimerCallback(int handle);
-
   Timers timers_;
   int current_timer_index_ = 0;
   script::Wrappable* const owner_;
+  DomStatTracker* const dom_stat_tracker_;
   const base::DebuggerHooks& debugger_hooks_;
 
   // Set to false when we're about to shutdown, to ensure that no new JavaScript
diff --git a/src/cobalt/dom/window_timers_test.cc b/src/cobalt/dom/window_timers_test.cc
new file mode 100644
index 0000000..57b233e
--- /dev/null
+++ b/src/cobalt/dom/window_timers_test.cc
@@ -0,0 +1,468 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/dom/window_timers.h"
+
+#include <memory>
+#include <string>
+
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "cobalt/dom/dom_stat_tracker.h"
+#include "cobalt/script/callback_function.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/javascript_engine.h"
+#include "cobalt/script/testing/fake_script_value.h"
+#include "net/test/test_with_scoped_task_environment.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace dom {
+
+namespace testing {
+
+using ::testing::_;
+using ::testing::Return;
+
+class MockTimerCallback : public WindowTimers::TimerCallback {
+ public:
+  MOCK_CONST_METHOD0(Run, script::CallbackResult<void>());
+  void ExpectRunCall(int times) {
+    EXPECT_CALL(*this, Run())
+        .Times(times)
+        .WillRepeatedly(Return(script::CallbackResult<void>()));
+  }
+};
+
+class MockDebuggerHooks : public base::DebuggerHooks {
+ public:
+  MOCK_CONST_METHOD2(ConsoleLog, void(::logging::LogSeverity, std::string));
+  MOCK_CONST_METHOD3(AsyncTaskScheduled,
+                     void(const void*, const std::string&, AsyncTaskFrequency));
+  MOCK_CONST_METHOD1(AsyncTaskStarted, void(const void*));
+  MOCK_CONST_METHOD1(AsyncTaskFinished, void(const void*));
+  MOCK_CONST_METHOD1(AsyncTaskCanceled, void(const void*));
+
+  void ExpectAsyncTaskScheduled(int times) {
+    EXPECT_CALL(*this, AsyncTaskScheduled(_, _, _)).Times(times);
+  }
+  void ExpectAsyncTaskStarted(int times) {
+    EXPECT_CALL(*this, AsyncTaskStarted(_)).Times(times);
+  }
+  void ExpectAsyncTaskFinished(int times) {
+    EXPECT_CALL(*this, AsyncTaskFinished(_)).Times(times);
+  }
+  void ExpectAsyncTaskCanceled(int times) {
+    EXPECT_CALL(*this, AsyncTaskCanceled(_)).Times(times);
+  }
+};
+
+}  // namespace testing
+
+namespace {
+const int kTimerDelayInMilliseconds = 100;
+}  // namespace
+
+using ::cobalt::script::testing::FakeScriptValue;
+
+class WindowTimersTest : public ::testing::Test,
+                         public net::WithScopedTaskEnvironment {
+ protected:
+  WindowTimersTest()
+      : WithScopedTaskEnvironment(
+            base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME),
+        dom_stat_tracker_("WindowTimersTest"),
+        callback_(&mock_timer_callback_) {
+    script::Wrappable* foo = nullptr;
+    timers_.reset(
+        new WindowTimers(foo, &dom_stat_tracker_, hooks_,
+                         base::ApplicationState::kApplicationStateStarted));
+  }
+
+  ~WindowTimersTest() override {}
+
+  testing::MockDebuggerHooks hooks_;
+  DomStatTracker dom_stat_tracker_;
+  std::unique_ptr<WindowTimers> timers_;
+  testing::MockTimerCallback mock_timer_callback_;
+  FakeScriptValue<WindowTimers::TimerCallback> callback_;
+};
+
+TEST_F(WindowTimersTest, TimeoutIsNotCalledDirectly) {
+  hooks_.ExpectAsyncTaskScheduled(1);
+  hooks_.ExpectAsyncTaskCanceled(1);
+
+  mock_timer_callback_.ExpectRunCall(0);
+  timers_->SetTimeout(callback_, 0);
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(0));
+}
+
+TEST_F(WindowTimersTest, TimeoutZeroIsCalledImmediatelyFromTask) {
+  hooks_.ExpectAsyncTaskScheduled(1);
+  hooks_.ExpectAsyncTaskCanceled(1);
+  hooks_.ExpectAsyncTaskStarted(1);
+  hooks_.ExpectAsyncTaskFinished(1);
+
+  mock_timer_callback_.ExpectRunCall(1);
+  timers_->SetTimeout(callback_, 0);
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(0));
+
+  RunUntilIdle();
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 0);
+}
+
+TEST_F(WindowTimersTest, TimeoutIsNotCalledBeforeDelay) {
+  hooks_.ExpectAsyncTaskScheduled(1);
+  hooks_.ExpectAsyncTaskCanceled(1);
+
+  hooks_.ExpectAsyncTaskStarted(0);
+  hooks_.ExpectAsyncTaskFinished(0);
+  mock_timer_callback_.ExpectRunCall(0);
+  timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+  FastForwardBy(
+      base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds - 1));
+  RunUntilIdle();
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+}
+
+TEST_F(WindowTimersTest, TimeoutIsCalledAfterDelay) {
+  hooks_.ExpectAsyncTaskScheduled(1);
+  hooks_.ExpectAsyncTaskCanceled(1);
+  hooks_.ExpectAsyncTaskStarted(1);
+  hooks_.ExpectAsyncTaskFinished(1);
+
+  mock_timer_callback_.ExpectRunCall(1);
+  timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+  FastForwardBy(base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+  RunUntilIdle();
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 0);
+}
+
+TEST_F(WindowTimersTest, TimeoutIsNotCalledRepeatedly) {
+  hooks_.ExpectAsyncTaskScheduled(1);
+  hooks_.ExpectAsyncTaskCanceled(1);
+  hooks_.ExpectAsyncTaskStarted(1);
+  hooks_.ExpectAsyncTaskFinished(1);
+
+  mock_timer_callback_.ExpectRunCall(1);
+  timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+  FastForwardBy(
+      base::TimeDelta::FromMilliseconds(10 * kTimerDelayInMilliseconds));
+  FastForwardUntilNoTasksRemain();
+  RunUntilIdle();
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 0);
+}
+
+TEST_F(WindowTimersTest, TimeoutIsCalledWhenDelayed) {
+  hooks_.ExpectAsyncTaskScheduled(1);
+  hooks_.ExpectAsyncTaskCanceled(1);
+  hooks_.ExpectAsyncTaskStarted(1);
+  hooks_.ExpectAsyncTaskFinished(1);
+
+  mock_timer_callback_.ExpectRunCall(1);
+  timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+  AdvanceMockTickClock(
+      base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds + 1000));
+  RunUntilIdle();
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 0);
+}
+
+TEST_F(WindowTimersTest, MultipleTimeouts) {
+  hooks_.ExpectAsyncTaskScheduled(2);
+  hooks_.ExpectAsyncTaskCanceled(2);
+  hooks_.ExpectAsyncTaskStarted(2);
+  hooks_.ExpectAsyncTaskFinished(2);
+
+  mock_timer_callback_.ExpectRunCall(2);
+  timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
+  timers_->SetTimeout(callback_, kTimerDelayInMilliseconds * 3);
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+  FastForwardBy(
+      base::TimeDelta::FromMilliseconds(3 * kTimerDelayInMilliseconds));
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 0);
+}
+
+TEST_F(WindowTimersTest, ActiveTimeoutsAreCounted) {
+  hooks_.ExpectAsyncTaskScheduled(2);
+  hooks_.ExpectAsyncTaskCanceled(2);
+  hooks_.ExpectAsyncTaskStarted(2);
+  hooks_.ExpectAsyncTaskFinished(2);
+
+  mock_timer_callback_.ExpectRunCall(2);
+  timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
+  timers_->SetTimeout(callback_, kTimerDelayInMilliseconds * 3);
+
+  dom_stat_tracker_.FlushPeriodicTracking();
+  EXPECT_EQ("0", base::CValManager::GetInstance()
+                     ->GetValueAsString(
+                         "Count.WindowTimersTest.DOM.WindowTimers.Interval")
+                     .value_or("Foo"));
+  EXPECT_EQ("2", base::CValManager::GetInstance()
+                     ->GetValueAsString(
+                         "Count.WindowTimersTest.DOM.WindowTimers.Timeout")
+                     .value_or("Foo"));
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+  FastForwardBy(
+      base::TimeDelta::FromMilliseconds(3 * kTimerDelayInMilliseconds));
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 0);
+
+  dom_stat_tracker_.FlushPeriodicTracking();
+  EXPECT_EQ("0", base::CValManager::GetInstance()
+                     ->GetValueAsString(
+                         "Count.WindowTimersTest.DOM.WindowTimers.Interval")
+                     .value_or("Foo"));
+  EXPECT_EQ("0", base::CValManager::GetInstance()
+                     ->GetValueAsString(
+                         "Count.WindowTimersTest.DOM.WindowTimers.Timeout")
+                     .value_or("Foo"));
+}
+
+TEST_F(WindowTimersTest, IntervalZeroTaskIsScheduledImmediately) {
+  hooks_.ExpectAsyncTaskScheduled(1);
+  hooks_.ExpectAsyncTaskCanceled(1);
+
+  timers_->SetInterval(callback_, 0);
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(0));
+
+  // Note: We can't use RunUntilIdle() or FastForwardBy() in this case,
+  // because the task queue never gets idle.
+}
+
+TEST_F(WindowTimersTest, IntervalIsNotCalledBeforeDelay) {
+  hooks_.ExpectAsyncTaskScheduled(1);
+  hooks_.ExpectAsyncTaskCanceled(1);
+
+  hooks_.ExpectAsyncTaskStarted(0);
+  hooks_.ExpectAsyncTaskFinished(0);
+  mock_timer_callback_.ExpectRunCall(0);
+  timers_->SetInterval(callback_, kTimerDelayInMilliseconds);
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+  FastForwardBy(
+      base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds - 1));
+  RunUntilIdle();
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+}
+
+TEST_F(WindowTimersTest, IntervalIsCalledAfterDelay) {
+  hooks_.ExpectAsyncTaskScheduled(1);
+  hooks_.ExpectAsyncTaskCanceled(1);
+  hooks_.ExpectAsyncTaskStarted(1);
+  hooks_.ExpectAsyncTaskFinished(1);
+
+  mock_timer_callback_.ExpectRunCall(1);
+  timers_->SetInterval(callback_, kTimerDelayInMilliseconds);
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+  FastForwardBy(base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+  RunUntilIdle();
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+}
+
+TEST_F(WindowTimersTest, IntervalIsCalledRepeatedly) {
+  int interval_count = 10;
+
+  hooks_.ExpectAsyncTaskScheduled(1);
+  hooks_.ExpectAsyncTaskCanceled(1);
+  hooks_.ExpectAsyncTaskStarted(interval_count);
+  hooks_.ExpectAsyncTaskFinished(interval_count);
+
+  mock_timer_callback_.ExpectRunCall(interval_count);
+  timers_->SetInterval(callback_, kTimerDelayInMilliseconds);
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+  FastForwardBy(base::TimeDelta::FromMilliseconds(interval_count *
+                                                  kTimerDelayInMilliseconds));
+  RunUntilIdle();
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+}
+
+TEST_F(WindowTimersTest, IntervalDrifts) {
+  int interval_count = 10;
+
+  hooks_.ExpectAsyncTaskScheduled(1);
+  hooks_.ExpectAsyncTaskCanceled(1);
+  hooks_.ExpectAsyncTaskStarted(interval_count);
+  hooks_.ExpectAsyncTaskFinished(interval_count);
+
+  mock_timer_callback_.ExpectRunCall(interval_count);
+  timers_->SetInterval(callback_, kTimerDelayInMilliseconds);
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+  while (interval_count--) {
+    AdvanceMockTickClock(
+        base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds + 1000));
+    RunUntilIdle();
+  }
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+}
+
+TEST_F(WindowTimersTest, MultipleIntervals) {
+  hooks_.ExpectAsyncTaskScheduled(2);
+  hooks_.ExpectAsyncTaskCanceled(2);
+  hooks_.ExpectAsyncTaskStarted(4);
+  hooks_.ExpectAsyncTaskFinished(4);
+
+  mock_timer_callback_.ExpectRunCall(4);
+  timers_->SetInterval(callback_, kTimerDelayInMilliseconds);
+  timers_->SetInterval(callback_, kTimerDelayInMilliseconds * 3);
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+  FastForwardBy(
+      base::TimeDelta::FromMilliseconds(3 * kTimerDelayInMilliseconds));
+  RunUntilIdle();
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
+}
+
+TEST_F(WindowTimersTest, ActiveIntervalsAreCounted) {
+  hooks_.ExpectAsyncTaskScheduled(2);
+  hooks_.ExpectAsyncTaskCanceled(2);
+  hooks_.ExpectAsyncTaskStarted(4);
+  hooks_.ExpectAsyncTaskFinished(4);
+
+  mock_timer_callback_.ExpectRunCall(4);
+  timers_->SetInterval(callback_, kTimerDelayInMilliseconds);
+  timers_->SetInterval(callback_, kTimerDelayInMilliseconds * 3);
+
+  dom_stat_tracker_.FlushPeriodicTracking();
+  EXPECT_EQ("2", base::CValManager::GetInstance()
+                     ->GetValueAsString(
+                         "Count.WindowTimersTest.DOM.WindowTimers.Interval")
+                     .value_or("Foo"));
+  EXPECT_EQ("0", base::CValManager::GetInstance()
+                     ->GetValueAsString(
+                         "Count.WindowTimersTest.DOM.WindowTimers.Timeout")
+                     .value_or("Foo"));
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+  FastForwardBy(
+      base::TimeDelta::FromMilliseconds(3 * kTimerDelayInMilliseconds));
+  RunUntilIdle();
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
+
+  dom_stat_tracker_.FlushPeriodicTracking();
+  EXPECT_EQ("2", base::CValManager::GetInstance()
+                     ->GetValueAsString(
+                         "Count.WindowTimersTest.DOM.WindowTimers.Interval")
+                     .value_or("Foo"));
+  EXPECT_EQ("0", base::CValManager::GetInstance()
+                     ->GetValueAsString(
+                         "Count.WindowTimersTest.DOM.WindowTimers.Timeout")
+                     .value_or("Foo"));
+}
+
+TEST_F(WindowTimersTest, ActiveIntervalsAndTimeoutsAreCounted) {
+  hooks_.ExpectAsyncTaskScheduled(4);
+  hooks_.ExpectAsyncTaskCanceled(4);
+  hooks_.ExpectAsyncTaskStarted(6);
+  hooks_.ExpectAsyncTaskFinished(6);
+
+  mock_timer_callback_.ExpectRunCall(6);
+  timers_->SetInterval(callback_, kTimerDelayInMilliseconds);
+  timers_->SetInterval(callback_, kTimerDelayInMilliseconds * 3);
+  timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
+  timers_->SetTimeout(callback_, kTimerDelayInMilliseconds * 3);
+
+  dom_stat_tracker_.FlushPeriodicTracking();
+  EXPECT_EQ("2", base::CValManager::GetInstance()
+                     ->GetValueAsString(
+                         "Count.WindowTimersTest.DOM.WindowTimers.Interval")
+                     .value_or("Foo"));
+  EXPECT_EQ("2", base::CValManager::GetInstance()
+                     ->GetValueAsString(
+                         "Count.WindowTimersTest.DOM.WindowTimers.Timeout")
+                     .value_or("Foo"));
+
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 4);
+  EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+            base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+  FastForwardBy(
+      base::TimeDelta::FromMilliseconds(3 * kTimerDelayInMilliseconds));
+  RunUntilIdle();
+  EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
+
+  dom_stat_tracker_.FlushPeriodicTracking();
+  EXPECT_EQ("2", base::CValManager::GetInstance()
+                     ->GetValueAsString(
+                         "Count.WindowTimersTest.DOM.WindowTimers.Interval")
+                     .value_or("Foo"));
+  EXPECT_EQ("0", base::CValManager::GetInstance()
+                     ->GetValueAsString(
+                         "Count.WindowTimersTest.DOM.WindowTimers.Timeout")
+                     .value_or("Foo"));
+}
+
+
+}  // namespace dom
+}  // namespace cobalt
diff --git a/src/cobalt/dom_parser/libxml_parser_wrapper.cc b/src/cobalt/dom_parser/libxml_parser_wrapper.cc
index 4642ab1..d3bad2d 100644
--- a/src/cobalt/dom_parser/libxml_parser_wrapper.cc
+++ b/src/cobalt/dom_parser/libxml_parser_wrapper.cc
@@ -167,8 +167,8 @@
 }
 
 void Characters(void* context, const xmlChar* ch, int len) {
-  ToLibxmlParserWrapper(context)
-      ->OnCharacters(std::string(ToCString(ch), static_cast<size_t>(len)));
+  ToLibxmlParserWrapper(context)->OnCharacters(
+      std::string(ToCString(ch), static_cast<size_t>(len)));
 }
 
 void Comment(void* context, const xmlChar* value) {
@@ -200,8 +200,8 @@
 }
 
 void CDATABlock(void* context, const xmlChar* value, int len) {
-  ToLibxmlParserWrapper(context)
-      ->OnCDATABlock(std::string(ToCString(value), static_cast<size_t>(len)));
+  ToLibxmlParserWrapper(context)->OnCDATABlock(
+      std::string(ToCString(value), static_cast<size_t>(len)));
 }
 
 //////////////////////////////////////////////////////////////////
@@ -230,8 +230,9 @@
   }
 
   if (IsFullDocument()) {
-    document_->PostToDispatchEventName(FROM_HERE,
-                                       base::Tokens::domcontentloaded());
+    // Collect dom content loaded timing info and dispatch dom content
+    // loaded event.
+    document_->CollectTimingInfoAndDispatchEvent();
   }
 }
 
diff --git a/src/cobalt/extension/extension_test.cc b/src/cobalt/extension/extension_test.cc
index 43b4925..b7c0602 100644
--- a/src/cobalt/extension/extension_test.cc
+++ b/src/cobalt/extension/extension_test.cc
@@ -18,6 +18,7 @@
 #include "cobalt/extension/crash_handler.h"
 #include "cobalt/extension/cwrappers.h"
 #include "cobalt/extension/font.h"
+#include "cobalt/extension/free_space.h"
 #include "cobalt/extension/graphics.h"
 #include "cobalt/extension/installation_manager.h"
 #include "cobalt/extension/javascript_cache.h"
@@ -349,5 +350,24 @@
       << "Extension struct should be a singleton";
 }
 
+TEST(ExtensionTest, FreeSpace) {
+  typedef CobaltExtensionFreeSpaceApi ExtensionApi;
+  const char* kExtensionName = kCobaltExtensionFreeSpaceName;
+
+  const ExtensionApi* extension_api =
+      static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+  if (!extension_api) {
+    return;
+  }
+
+  EXPECT_STREQ(extension_api->name, kExtensionName);
+  EXPECT_EQ(extension_api->version, 1u);
+  EXPECT_NE(extension_api->MeasureFreeSpace, nullptr);
+
+  const ExtensionApi* second_extension_api =
+      static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+  EXPECT_EQ(second_extension_api, extension_api)
+      << "Extension struct should be a singleton";
+}
 }  // namespace extension
 }  // namespace cobalt
diff --git a/src/cobalt/extension/free_space.h b/src/cobalt/extension/free_space.h
new file mode 100644
index 0000000..53d466b
--- /dev/null
+++ b/src/cobalt/extension/free_space.h
@@ -0,0 +1,50 @@
+// 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.
+
+
+#ifndef COBALT_EXTENSION_FREE_SPACE_H_
+#define COBALT_EXTENSION_FREE_SPACE_H_
+
+#include <stdint.h>
+
+#include "starboard/system.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define kCobaltExtensionFreeSpaceName "dev.cobalt.extension.FreeSpace"
+
+typedef struct CobaltExtensionFreeSpaceApi {
+  // Name should be the string |kCobaltExtensionFreeSpaceName|.
+  // This helps to validate that the extension API is correct.
+  const char* name;
+
+  // This specifies the version of the API that is implemented.
+  uint32_t version;
+
+  // The fields below this point were added in version 1 or later.
+
+  // Returns the free space in bytes for the provided |system_path_id|.
+  // If there is no implementation for the that |system_path_id| or
+  // if there was an error -1 is returned.
+  int64_t (*MeasureFreeSpace)(SbSystemPathId system_path_id);
+} CobaltExtensionFreeSpaceApi;
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // COBALT_EXTENSION_FREE_SPACE_H_
diff --git a/src/cobalt/h5vcc/h5vcc.cc b/src/cobalt/h5vcc/h5vcc.cc
index 196c708..e1e9e04 100644
--- a/src/cobalt/h5vcc/h5vcc.cc
+++ b/src/cobalt/h5vcc/h5vcc.cc
@@ -28,6 +28,9 @@
   runtime_ = new H5vccRuntime(settings.event_dispatcher);
   settings_ =
       new H5vccSettings(settings.media_module, settings.network_module,
+#if SB_IS(EVERGREEN)
+                        settings.updater_module,
+#endif
                         settings.user_agent_data, settings.global_environment);
 #if defined(COBALT_ENABLE_SSO)
   sso_ = new H5vccSso();
diff --git a/src/cobalt/h5vcc/h5vcc_settings.cc b/src/cobalt/h5vcc/h5vcc_settings.cc
index 2607bed..83bf08b 100644
--- a/src/cobalt/h5vcc/h5vcc_settings.cc
+++ b/src/cobalt/h5vcc/h5vcc_settings.cc
@@ -21,18 +21,29 @@
 
 H5vccSettings::H5vccSettings(media::MediaModule* media_module,
                              cobalt::network::NetworkModule* network_module,
+#if SB_IS(EVERGREEN)
+                             cobalt::updater::UpdaterModule* updater_module,
+#endif
                              dom::NavigatorUAData* user_agent_data,
                              script::GlobalEnvironment* global_environment)
     : media_module_(media_module),
       network_module_(network_module),
+#if SB_IS(EVERGREEN)
+      updater_module_(updater_module),
+#endif
       user_agent_data_(user_agent_data),
-      global_environment_(global_environment) {}
+      global_environment_(global_environment) {
+}
 
 bool H5vccSettings::Set(const std::string& name, int32 value) const {
   const char kMediaPrefix[] = "Media.";
   const char kNavigatorUAData[] = "NavigatorUAData";
   const char kQUIC[] = "QUIC";
 
+#if SB_IS(EVERGREEN)
+  const char kUpdaterMinFreeSpaceBytes[] = "Updater.MinFreeSpaceBytes";
+#endif
+
   if (name.compare(kMediaPrefix) == 0) {
     return media_module_ ? media_module_->SetConfiguration(name, value) : false;
   }
@@ -51,6 +62,12 @@
     }
   }
 
+#if SB_IS(EVERGREEN)
+  if (name.compare(kUpdaterMinFreeSpaceBytes) == 0) {
+    updater_module_->SetMinFreeSpaceBytes(value);
+    return true;
+  }
+#endif
   return false;
 }
 
diff --git a/src/cobalt/h5vcc/h5vcc_settings.h b/src/cobalt/h5vcc/h5vcc_settings.h
index ec4d4c1..086a524 100644
--- a/src/cobalt/h5vcc/h5vcc_settings.h
+++ b/src/cobalt/h5vcc/h5vcc_settings.h
@@ -23,6 +23,10 @@
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/wrappable.h"
 
+#if SB_IS(EVERGREEN)
+#include "cobalt/updater/updater_module.h"
+#endif
+
 namespace cobalt {
 namespace h5vcc {
 
@@ -33,6 +37,9 @@
  public:
   explicit H5vccSettings(media::MediaModule* media_module,
                          cobalt::network::NetworkModule* network_module,
+#if SB_IS(EVERGREEN)
+                         cobalt::updater::UpdaterModule* updater_module,
+#endif
                          dom::NavigatorUAData* user_agent_data,
                          script::GlobalEnvironment* global_environment);
 
@@ -46,6 +53,9 @@
  private:
   media::MediaModule* media_module_;
   cobalt::network::NetworkModule* network_module_ = nullptr;
+#if SB_IS(EVERGREEN)
+  cobalt::updater::UpdaterModule* updater_module_ = nullptr;
+#endif
   dom::NavigatorUAData* user_agent_data_;
   script::GlobalEnvironment* global_environment_;
 
diff --git a/src/cobalt/h5vcc/h5vcc_trace_event.cc b/src/cobalt/h5vcc/h5vcc_trace_event.cc
index 5e27555..2a06dee 100644
--- a/src/cobalt/h5vcc/h5vcc_trace_event.cc
+++ b/src/cobalt/h5vcc/h5vcc_trace_event.cc
@@ -14,6 +14,8 @@
 
 #include "cobalt/h5vcc/h5vcc_trace_event.h"
 
+#include "base/files/file_util.h"
+
 namespace cobalt {
 namespace h5vcc {
 
@@ -29,17 +31,35 @@
   } else {
     base::FilePath output_filepath(
         output_filename.empty() ? kOutputTraceFilename : output_filename);
+    last_absolute_path_.clear();
     trace_to_file_.reset(new trace_event::ScopedTraceToFile(output_filepath));
   }
 }
 
 void H5vccTraceEvent::Stop() {
   if (trace_to_file_) {
+    last_absolute_path_ = trace_to_file_->absolute_output_path();
     trace_to_file_.reset();
   } else {
     DLOG(WARNING) << "H5vccTraceEvent is already stopped.";
   }
 }
 
+std::string H5vccTraceEvent::Read(const std::string& read_filename) {
+  if (trace_to_file_) {
+    Stop();
+  }
+  std::string trace;
+  if (!read_filename.empty()) {
+    auto path = trace_event::ScopedTraceToFile::filepath_to_absolute(
+        base::FilePath(read_filename));
+    ReadFileToString(path, &trace);
+  } else if (!last_absolute_path_.empty()) {
+    ReadFileToString(last_absolute_path_, &trace);
+  }
+  return trace;
+}
+
+
 }  // namespace h5vcc
 }  // namespace cobalt
diff --git a/src/cobalt/h5vcc/h5vcc_trace_event.h b/src/cobalt/h5vcc/h5vcc_trace_event.h
index c1f2f8f..5239af7 100644
--- a/src/cobalt/h5vcc/h5vcc_trace_event.h
+++ b/src/cobalt/h5vcc/h5vcc_trace_event.h
@@ -51,6 +51,7 @@
 
   void Start(const std::string& output_filename);
   void Stop();
+  std::string Read(const std::string& output_filename);
 
   TRACE_EVENT0_FOR_EACH(DEFINE_H5VCC_TRACE_EVENT0)
   TRACE_EVENT1_FOR_EACH(DEFINE_H5VCC_TRACE_EVENT1)
@@ -62,6 +63,8 @@
   // While initialized, it means that a trace is on-going.
   std::unique_ptr<trace_event::ScopedTraceToFile> trace_to_file_;
 
+  base::FilePath last_absolute_path_;
+
   DISALLOW_COPY_AND_ASSIGN(H5vccTraceEvent);
 };
 
diff --git a/src/cobalt/h5vcc/h5vcc_trace_event.idl b/src/cobalt/h5vcc/h5vcc_trace_event.idl
index 9fca122..0b2bccf 100644
--- a/src/cobalt/h5vcc/h5vcc_trace_event.idl
+++ b/src/cobalt/h5vcc/h5vcc_trace_event.idl
@@ -19,6 +19,7 @@
   // will be used.
   void start(optional DOMString output_filename = "");
   void stop();
+  DOMString read(optional DOMString read_filename = "");
 
   void traceBegin(DOMString category, DOMString name);
   void traceEnd(DOMString category, DOMString name);
diff --git a/src/cobalt/media/base/format_support_query_metrics.cc b/src/cobalt/media/base/format_support_query_metrics.cc
new file mode 100644
index 0000000..5d43621
--- /dev/null
+++ b/src/cobalt/media/base/format_support_query_metrics.cc
@@ -0,0 +1,113 @@
+// 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/media/base/format_support_query_metrics.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "starboard/common/string.h"
+
+namespace cobalt {
+namespace media {
+
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+
+// static
+SbTimeMonotonic FormatSupportQueryMetrics::cached_query_durations_[] = {0};
+char FormatSupportQueryMetrics::max_query_description_[] = {};
+SbTimeMonotonic FormatSupportQueryMetrics::max_query_duration_ = 0;
+SbTimeMonotonic FormatSupportQueryMetrics::total_query_duration_ = 0;
+int FormatSupportQueryMetrics::total_num_queries_ = 0;
+
+FormatSupportQueryMetrics::FormatSupportQueryMetrics() {
+  query_start_time_ = SbTimeGetMonotonicNow();
+}
+
+
+void FormatSupportQueryMetrics::RecordQuery(const char* query_name,
+                                            const std::string& mime_type,
+                                            const std::string& key_system,
+                                            SbMediaSupportType support_type) {
+  auto get_support_type_str = [](SbMediaSupportType support_type) {
+    switch (support_type) {
+      case kSbMediaSupportTypeNotSupported:
+        return " -> not supported/false";
+      case kSbMediaSupportTypeMaybe:
+        return " -> maybe/true";
+      case kSbMediaSupportTypeProbably:
+        return " -> probably/true";
+      default:
+        NOTREACHED();
+        return "";
+    }
+  };
+
+  SbTimeMonotonic duration = SbTimeGetMonotonicNow() - query_start_time_;
+  total_query_duration_ += duration;
+
+  std::string query_description = starboard::FormatString(
+      "%s(%s%s%s, %" PRId64 " μs", query_name, mime_type.c_str(),
+      (key_system.empty() ? ")" : ", " + key_system + ")").c_str(),
+      get_support_type_str(support_type), duration);
+  LOG(INFO) << query_description;
+
+  if (total_num_queries_ < SB_ARRAY_SIZE_INT(cached_query_durations_)) {
+    cached_query_durations_[total_num_queries_] = duration;
+  }
+  ++total_num_queries_;
+  if (duration > max_query_duration_) {
+    max_query_duration_ = duration;
+    base::strlcpy(max_query_description_, query_description.c_str(),
+                  SB_ARRAY_SIZE_INT(max_query_description_));
+  }
+}
+
+// static
+void FormatSupportQueryMetrics::PrintAndResetFormatSupportQueryMetrics() {
+  if (total_num_queries_ == 0) {
+    LOG(INFO) << "Format support query metrics:\n\tNumber of queries: 0";
+    return;
+  }
+
+  auto get_median = []() {
+    int num_elements = std::min(total_num_queries_,
+                                SB_ARRAY_SIZE_INT(cached_query_durations_));
+    int middle_index = num_elements / 2;
+    std::nth_element(cached_query_durations_,
+                     cached_query_durations_ + middle_index,
+                     cached_query_durations_ + num_elements);
+    auto middle_element = cached_query_durations_[middle_index];
+    return middle_element;
+  };
+
+  LOG(INFO) << "Format support query metrics:\n\tNumber of queries: "
+            << total_num_queries_
+            << "\n\tTotal query time: " << total_query_duration_
+            << " μs\n\tAverage query time: "
+            << total_query_duration_ / total_num_queries_
+            << " μs\n\tMedian query time: ~" << get_median()
+            << " μs\n\tLongest query: " << max_query_description_;
+
+  max_query_description_[0] = 0;
+  max_query_duration_ = 0;
+  total_query_duration_ = 0;
+  total_num_queries_ = 0;
+}
+
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
+
+}  // namespace media
+}  // namespace cobalt
diff --git a/src/cobalt/media/base/format_support_query_metrics.h b/src/cobalt/media/base/format_support_query_metrics.h
new file mode 100644
index 0000000..4678d6e
--- /dev/null
+++ b/src/cobalt/media/base/format_support_query_metrics.h
@@ -0,0 +1,69 @@
+// 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.
+
+#ifndef COBALT_MEDIA_BASE_FORMAT_SUPPORT_QUERY_METRICS_H_
+#define COBALT_MEDIA_BASE_FORMAT_SUPPORT_QUERY_METRICS_H_
+
+#include <string>
+
+#include "starboard/media.h"
+#include "starboard/time.h"
+
+namespace cobalt {
+namespace media {
+
+#if defined(COBALT_BUILD_TYPE_GOLD)
+
+class FormatSupportQueryMetrics {
+ public:
+  FormatSupportQueryMetrics() = default;
+  ~FormatSupportQueryMetrics() = default;
+  void RecordQuery(const char* query_name, const std::string& mime_type,
+                   const std::string& key_system,
+                   SbMediaSupportType support_type) {}
+  static void PrintAndResetFormatSupportQueryMetrics() {}
+};
+
+#else  // defined(COBALT_BUILD_TYPE_GOLD)
+
+class FormatSupportQueryMetrics {
+ public:
+  FormatSupportQueryMetrics();
+  ~FormatSupportQueryMetrics() {}
+
+  void RecordQuery(const char* query_name, const std::string& mime_type,
+                   const std::string& key_system,
+                   SbMediaSupportType support_type);
+
+  static void PrintAndResetFormatSupportQueryMetrics();
+
+ private:
+  static constexpr int kMaxCachedQueryDurations = 150;
+  static constexpr int kMaxQueryDescriptionLength = 350;
+
+  static SbTimeMonotonic cached_query_durations_[kMaxCachedQueryDurations];
+  static char max_query_description_[kMaxQueryDescriptionLength];
+  static SbTimeMonotonic max_query_duration_;
+  static SbTimeMonotonic total_query_duration_;
+  static int total_num_queries_;
+
+  SbTimeMonotonic query_start_time_ = 0;
+};
+
+#endif  // defined(COBALT_BUILD_TYPE_GOLD)
+
+}  // namespace media
+}  // namespace cobalt
+
+#endif  // COBALT_MEDIA_BASE_FORMAT_SUPPORT_QUERY_METRICS_H_
diff --git a/src/cobalt/media/base/sbplayer_pipeline.cc b/src/cobalt/media/base/sbplayer_pipeline.cc
index 52e44a4..9530bfd 100644
--- a/src/cobalt/media/base/sbplayer_pipeline.cc
+++ b/src/cobalt/media/base/sbplayer_pipeline.cc
@@ -14,6 +14,7 @@
 
 #include <algorithm>
 #include <memory>
+#include <string>
 #include <vector>
 
 #include "base/basictypes.h"  // For COMPILE_ASSERT
@@ -28,6 +29,7 @@
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/c_val.h"
+#include "cobalt/base/startup_timer.h"
 #include "cobalt/math/size.h"
 #include "cobalt/media/base/audio_decoder_config.h"
 #include "cobalt/media/base/bind_to_current_loop.h"
@@ -46,6 +48,7 @@
 #include "cobalt/media/base/video_decoder_config.h"
 #include "starboard/common/string.h"
 #include "starboard/configuration_constants.h"
+#include "starboard/time.h"
 
 namespace cobalt {
 namespace media {
@@ -193,6 +196,12 @@
   // Retrieve the statistics as a string and append to message.
   std::string AppendStatisticsString(const std::string& message) const;
 
+  // Get information on the time since app start and the time since the last
+  // playback resume.
+  std::string GetTimeInformation() const;
+
+  void RunSetDrmSystemReadyCB();
+
   // An identifier string for the pipeline, used in CVal to identify multiple
   // pipelines.
   const std::string pipeline_identifier_;
@@ -317,6 +326,10 @@
 
   PlaybackStatistics playback_statistics_;
 
+  SbTimeMonotonic last_resume_time_ = -1;
+
+  SbTimeMonotonic set_drm_system_ready_cb_time_ = -1;
+
   DISALLOW_COPY_AND_ASSIGN(SbPlayerPipeline);
 };
 
@@ -506,8 +519,7 @@
                  on_encrypted_media_init_data_encountered_cb);
   set_drm_system_ready_cb_ = parameters.set_drm_system_ready_cb;
   DCHECK(!set_drm_system_ready_cb_.is_null());
-  set_drm_system_ready_cb_.Run(
-      base::Bind(&SbPlayerPipeline::SetDrmSystem, this));
+  RunSetDrmSystemReadyCB();
 
   task_runner_->PostTask(
       FROM_HERE, base::Bind(&SbPlayerPipeline::StartTask, this, parameters));
@@ -933,10 +945,15 @@
     return;
   }
 
+  std::string time_information = GetTimeInformation();
+  LOG(INFO) << "SbPlayerPipeline::CreateUrlPlayer failed to create a valid "
+               "StarboardPlayer - "
+            << time_information << " \'" << error_message << "\'";
+
   CallSeekCB(DECODER_ERROR_NOT_SUPPORTED,
              "SbPlayerPipeline::CreateUrlPlayer failed to create a valid "
-             "StarboardPlayer: \'" +
-                 error_message + "\'");
+             "StarboardPlayer - " +
+                 time_information + " \'" + error_message + "\'");
 }
 
 void SbPlayerPipeline::SetDrmSystem(SbDrmSystem drm_system) {
@@ -949,6 +966,7 @@
   }
 
   if (player_->IsValid()) {
+    player_->RecordSetDrmSystemReadyTime(set_drm_system_ready_cb_time_);
     player_->SetDrmSystem(drm_system);
   }
 }
@@ -1018,6 +1036,7 @@
   }
 
   if (player_ && player_->IsValid()) {
+    player_->RecordSetDrmSystemReadyTime(set_drm_system_ready_cb_time_);
     base::Closure output_mode_change_cb;
     {
       base::AutoLock auto_lock(lock_);
@@ -1035,10 +1054,15 @@
     return;
   }
 
+  std::string time_information = GetTimeInformation();
+  LOG(INFO) << "SbPlayerPipeline::CreatePlayer failed to create a valid "
+               "StarboardPlayer - "
+            << time_information << " \'" << error_message << "\'";
+
   CallSeekCB(DECODER_ERROR_NOT_SUPPORTED,
              "SbPlayerPipeline::CreatePlayer failed to create a valid "
-             "StarboardPlayer: \'" +
-                 error_message + "\'");
+             "StarboardPlayer - " +
+                 time_information + " \'" + error_message + "\'");
 }
 
 void SbPlayerPipeline::OnDemuxerInitialized(PipelineStatus status) {
@@ -1098,8 +1122,7 @@
       content_size_change_cb_.Run();
     }
     if (is_encrypted) {
-      set_drm_system_ready_cb_.Run(
-          BindToCurrentLoop(base::Bind(&SbPlayerPipeline::CreatePlayer, this)));
+      RunSetDrmSystemReadyCB();
       return;
     }
   }
@@ -1432,6 +1455,7 @@
   DCHECK(suspended_);
 
   if (!suspended_) {
+    last_resume_time_ = SbTimeGetMonotonicNow();
     done_event->Signal();
     return;
   }
@@ -1447,17 +1471,22 @@
         error_message = player_->GetPlayerCreationErrorMessage();
         player_.reset();
       }
+      std::string time_information = GetTimeInformation();
+      LOG(INFO) << "SbPlayerPipeline::ResumeTask failed to create a valid "
+                   "StarboardPlayer - "
+                << time_information << " \'" << error_message << "\'";
       // TODO: Determine if CallSeekCB() may be used here, as |seek_cb_| may be
       // available if the app is suspended before a seek is completed.
-      CallErrorCB(DECODER_ERROR_NOT_SUPPORTED,
-                  "SbPlayerPipeline::ResumeTask failed to create a valid "
-                  "StarboardPlayer: \'" +
-                      error_message + "\'");
+      CallSeekCB(DECODER_ERROR_NOT_SUPPORTED,
+                 "SbPlayerPipeline::ResumeTask failed to create a valid "
+                 "StarboardPlayer - " +
+                     time_information + " \'" + error_message + "\'");
       return;
     }
   }
 
   suspended_ = false;
+  last_resume_time_ = SbTimeGetMonotonicNow();
 
   done_event->Signal();
 }
@@ -1474,6 +1503,46 @@
   }
 }
 
+std::string SbPlayerPipeline::GetTimeInformation() const {
+  auto round_time_in_seconds = [](const SbTime time) {
+    const int64_t seconds = time / kSbTimeSecond;
+    if (seconds < 15) {
+      return seconds / 5 * 5;
+    }
+    if (seconds < 60) {
+      return seconds / 15 * 15;
+    }
+    if (seconds < 3600) {
+      return std::max(static_cast<SbTime>(60), seconds / 600 * 600);
+    }
+    return std::max(static_cast<SbTime>(3600), seconds / 18000 * 18000);
+  };
+  std::string time_since_start =
+      std::to_string(
+          round_time_in_seconds(base::StartupTimer::TimeElapsed().ToSbTime())) +
+      "s";
+  std::string time_since_resume =
+      last_resume_time_ != -1
+          ? std::to_string(round_time_in_seconds(SbTimeGetMonotonicNow() -
+                                                 last_resume_time_)) +
+                "s"
+          : "null";
+  return "time since app start: " + time_since_start +
+         ", time since last resume: " + time_since_resume;
+}
+
+void SbPlayerPipeline::RunSetDrmSystemReadyCB() {
+  TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::RunSetDrmSystemReadyCB");
+  set_drm_system_ready_cb_time_ = SbTimeGetMonotonicNow();
+#if SB_HAS(PLAYER_WITH_URL)
+  set_drm_system_ready_cb_.Run(
+      base::Bind(&SbPlayerPipeline::SetDrmSystem, this));
+#else   // SB_HAS(PLAYER_WITH_URL)
+  set_drm_system_ready_cb_.Run(
+      BindToCurrentLoop(base::Bind(&SbPlayerPipeline::CreatePlayer, this)));
+#endif  // SB_HAS(PLAYER_WITH_URL)
+}
+
 }  // namespace
 
 // static
diff --git a/src/cobalt/media/base/starboard_player.cc b/src/cobalt/media/base/starboard_player.cc
index 47ad52b..30f3a4c 100644
--- a/src/cobalt/media/base/starboard_player.cc
+++ b/src/cobalt/media/base/starboard_player.cc
@@ -14,6 +14,8 @@
 
 #include "cobalt/media/base/starboard_player.h"
 
+#include <algorithm>
+#include <iomanip>
 #include <string>
 #include <vector>
 
@@ -22,8 +24,10 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/trace_event/trace_event.h"
+#include "cobalt/media/base/format_support_query_metrics.h"
 #include "cobalt/media/base/starboard_utils.h"
 #include "starboard/common/media.h"
+#include "starboard/common/string.h"
 #include "starboard/configuration.h"
 #include "starboard/memory.h"
 
@@ -518,6 +522,13 @@
 
   DCHECK(!on_encrypted_media_init_data_encountered_cb_.is_null());
   LOG(INFO) << "CreateUrlPlayer passed url " << url;
+
+  if (max_video_capabilities_.empty()) {
+    FormatSupportQueryMetrics::PrintAndResetFormatSupportQueryMetrics();
+  }
+
+  player_creation_time_ = SbTimeGetMonotonicNow();
+
   player_ =
       SbUrlPlayerCreate(url.c_str(), window_, &StarboardPlayer::PlayerStatusCB,
                         &StarboardPlayer::EncryptedMediaInitDataEncounteredCB,
@@ -556,6 +567,12 @@
 
   is_creating_player_ = true;
 
+  if (max_video_capabilities_.empty()) {
+    FormatSupportQueryMetrics::PrintAndResetFormatSupportQueryMetrics();
+  }
+
+  player_creation_time_ = SbTimeGetMonotonicNow();
+
 #if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
 
   SbPlayerCreationParam creation_param = {};
@@ -653,6 +670,14 @@
   }
 
   auto sample_type = DemuxerStreamTypeToSbMediaType(type);
+
+  if (sample_type == kSbMediaTypeAudio && first_audio_sample_time_ == 0) {
+    first_audio_sample_time_ = SbTimeGetMonotonicNow();
+  } else if (sample_type == kSbMediaTypeVideo &&
+             first_video_sample_time_ == 0) {
+    first_video_sample_time_ = SbTimeGetMonotonicNow();
+  }
+
   SbDrmSampleInfo drm_info;
   SbDrmSubSampleMapping subsample_mapping;
   drm_info.subsample_count = 0;
@@ -807,6 +832,8 @@
 
 void StarboardPlayer::OnPlayerStatus(SbPlayer player, SbPlayerState state,
                                      int ticket) {
+  TRACE_EVENT1("cobalt::media", "StarboardPlayer::OnPlayerStatus", "state",
+               state);
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   if (player_ != player) {
@@ -823,11 +850,24 @@
     if (ticket_ == SB_PLAYER_INITIAL_TICKET) {
       ++ticket_;
     }
+    if (sb_player_state_initialized_time_ == 0) {
+      sb_player_state_initialized_time_ = SbTimeGetMonotonicNow();
+    }
     SbPlayerSeek2(player_, preroll_timestamp_.InMicroseconds(), ticket_);
     SetVolume(volume_);
     SbPlayerSetPlaybackRate(player_, playback_rate_);
     return;
   }
+  if (state == kSbPlayerStatePrerolling &&
+      sb_player_state_prerolling_time_ == 0) {
+    sb_player_state_prerolling_time_ = SbTimeGetMonotonicNow();
+  } else if (state == kSbPlayerStatePresenting &&
+             sb_player_state_presenting_time_ == 0) {
+    sb_player_state_presenting_time_ = SbTimeGetMonotonicNow();
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+    LogStartupLatency();
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
+  }
   host_->OnPlayerStatus(state);
 }
 
@@ -984,5 +1024,48 @@
 #endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
 }
 
+void StarboardPlayer::LogStartupLatency() const {
+  std::string first_events_str;
+  if (set_drm_system_ready_cb_time_ == -1) {
+    first_events_str =
+        starboard::FormatString("%-50s0 us", "SbPlayerCreate() called");
+
+  } else if (set_drm_system_ready_cb_time_ < player_creation_time_) {
+    first_events_str = starboard::FormatString(
+        "%-50s0 us\n%-50s%" PRId64 " us", "set_drm_system_ready_cb called",
+        "SbPlayerCreate() called",
+        player_creation_time_ - set_drm_system_ready_cb_time_);
+  } else {
+    first_events_str = starboard::FormatString(
+        "%-50s0 us\n%-50s%" PRId64 " us", "SbPlayerCreate() called",
+        "set_drm_system_ready_cb called",
+        set_drm_system_ready_cb_time_ - player_creation_time_);
+  }
+
+  SbTime player_initialization_time_delta =
+      sb_player_state_initialized_time_ -
+      std::max(player_creation_time_, set_drm_system_ready_cb_time_);
+  SbTime player_preroll_time_delta =
+      sb_player_state_prerolling_time_ - sb_player_state_initialized_time_;
+  SbTime first_audio_sample_time_delta = std::max(
+      first_audio_sample_time_ - sb_player_state_prerolling_time_, SbTime(0));
+  SbTime first_video_sample_time_delta = std::max(
+      first_video_sample_time_ - sb_player_state_prerolling_time_, SbTime(0));
+  SbTime player_presenting_time_delta =
+      sb_player_state_presenting_time_ -
+      std::max(first_audio_sample_time_, first_video_sample_time_);
+
+  LOG(INFO) << starboard::FormatString(
+      "SbPlayer startup latencies\n%-50s%s\n%s\n%-50s%" PRId64
+      " us\n%-50s%" PRId64 " us\n%-50s%" PRId64 "/%" PRId64 " us\n%-50s%" PRId64
+      " us",
+      "Event name", "time since last event", first_events_str.c_str(),
+      "kSbPlayerStateInitialized received", player_initialization_time_delta,
+      "kSbPlayerStatePrerolling received", player_preroll_time_delta,
+      "First media sample(s) written [audio/video]",
+      first_audio_sample_time_delta, first_video_sample_time_delta,
+      "kSbPlayerStatePresenting received", player_presenting_time_delta);
+}
+
 }  // namespace media
 }  // namespace cobalt
diff --git a/src/cobalt/media/base/starboard_player.h b/src/cobalt/media/base/starboard_player.h
index 6e40ac4..7e3ffd8 100644
--- a/src/cobalt/media/base/starboard_player.h
+++ b/src/cobalt/media/base/starboard_player.h
@@ -130,6 +130,10 @@
   SbDecodeTarget GetCurrentSbDecodeTarget();
   SbPlayerOutputMode GetSbPlayerOutputMode();
 
+  void RecordSetDrmSystemReadyTime(SbTimeMonotonic timestamp) {
+    set_drm_system_ready_cb_time_ = timestamp;
+  }
+
  private:
   enum State {
     kPlaying,
@@ -216,6 +220,8 @@
   SbPlayerOutputMode ComputeSbPlayerOutputMode(
       bool prefer_decode_to_texture) const;
 
+  void LogStartupLatency() const;
+
 // The following variables are initialized in the ctor and never changed.
 #if SB_HAS(PLAYER_WITH_URL)
   std::string url_;
@@ -269,6 +275,15 @@
   bool is_creating_player_ = false;
   std::string player_creation_error_message_;
 
+  // Variables related to tracking player startup latencies.
+  SbTimeMonotonic set_drm_system_ready_cb_time_ = -1;
+  SbTimeMonotonic player_creation_time_ = 0;
+  SbTimeMonotonic sb_player_state_initialized_time_ = 0;
+  SbTimeMonotonic sb_player_state_prerolling_time_ = 0;
+  SbTimeMonotonic first_audio_sample_time_ = 0;
+  SbTimeMonotonic first_video_sample_time_ = 0;
+  SbTimeMonotonic sb_player_state_presenting_time_ = 0;
+
 #if SB_HAS(PLAYER_WITH_URL)
   const bool is_url_based_;
 #endif  // SB_HAS(PLAYER_WITH_URL)
diff --git a/src/cobalt/media/media.gyp b/src/cobalt/media/media.gyp
index 42567fa..4757b3e 100644
--- a/src/cobalt/media/media.gyp
+++ b/src/cobalt/media/media.gyp
@@ -68,6 +68,8 @@
         "base/encryption_pattern.h",
         'base/encryption_scheme.cc',
         'base/encryption_scheme.h',
+        'base/format_support_query_metrics.cc',
+        'base/format_support_query_metrics.h',
         'base/hdr_metadata.cc',
         'base/hdr_metadata.h',
         'base/interleaved_sinc_resampler.cc',
diff --git a/src/cobalt/media/media_module.cc b/src/cobalt/media/media_module.cc
index 8d4e195..43ad61b 100644
--- a/src/cobalt/media/media_module.cc
+++ b/src/cobalt/media/media_module.cc
@@ -24,6 +24,7 @@
 #include "base/logging.h"
 #include "base/strings/string_split.h"
 #include "base/synchronization/waitable_event.h"
+#include "cobalt/media/base/format_support_query_metrics.h"
 #include "cobalt/media/base/media_log.h"
 #include "cobalt/media/base/mime_util.h"
 #include "nb/memory_scope.h"
@@ -120,18 +121,27 @@
     //   video/webm; codecs="vp9"
     // We do a rough pre-filter to ensure that only video/mp4 is supported as
     // progressive.
+    SbMediaSupportType support_type;
+    media::FormatSupportQueryMetrics metrics;
     if (strstr(mime_type.c_str(), "video/mp4") == 0 &&
         strstr(mime_type.c_str(), "application/x-mpegURL") == 0) {
-      return kSbMediaSupportTypeNotSupported;
+      support_type = kSbMediaSupportTypeNotSupported;
+    } else {
+      support_type = CanPlayType(mime_type, "");
     }
-
-    return CanPlayType(mime_type, "");
+    metrics.RecordQuery("HTMLMediaElement::canPlayType", mime_type, "",
+                        support_type);
+    return support_type;
   }
 
   SbMediaSupportType CanPlayAdaptive(
       const std::string& mime_type,
       const std::string& key_system) const override {
-    return CanPlayType(mime_type, key_system);
+    media::FormatSupportQueryMetrics metrics;
+    SbMediaSupportType support_type = CanPlayType(mime_type, key_system);
+    metrics.RecordQuery("MediaSource::IsTypeSupported", mime_type, key_system,
+                        support_type);
+    return support_type;
   }
 
  private:
diff --git a/src/cobalt/render_tree/image.h b/src/cobalt/render_tree/image.h
index 357052f..668f4f0 100644
--- a/src/cobalt/render_tree/image.h
+++ b/src/cobalt/render_tree/image.h
@@ -137,6 +137,9 @@
   // A YUV image where each channel, Y, U and V, is stored as a separate
   // single-channel 10bit unnormalized image plane.
   kMultiPlaneImageFormatYUV3Plane10BitBT2020,
+  // A compacted YUV image where Y, U and V, are stored in 32-bit and each
+  // 32-bit represents 3 10-bit pixels with 2 bits of gap.
+  kMultiPlaneImageFormatYUV3Plane10BitCompactedBT2020,
 };
 
 // Like the ImageDataDescriptor object, a MultiPlaneImageDataDescriptor
diff --git a/src/cobalt/renderer/backend/egl/graphics_context.cc b/src/cobalt/renderer/backend/egl/graphics_context.cc
index ebc2d9d..480ac6d 100644
--- a/src/cobalt/renderer/backend/egl/graphics_context.cc
+++ b/src/cobalt/renderer/backend/egl/graphics_context.cc
@@ -17,6 +17,7 @@
 
 #include <algorithm>
 #include <memory>
+#include <utility>
 
 #include "cobalt/renderer/backend/egl/graphics_context.h"
 
@@ -96,6 +97,10 @@
   return base::polymorphic_downcast<GraphicsSystemEGL*>(system());
 }
 
+const GraphicsSystemEGL* GraphicsContextEGL::system_egl() const {
+  return base::polymorphic_downcast<GraphicsSystemEGL*>(system());
+}
+
 bool GraphicsContextEGL::ComputeReadPixelsNeedVerticalFlip() {
   // This computation is expensive, so it is cached the first time that it is
   // computed. Simply return the value if it is already cached.
@@ -204,10 +209,10 @@
   GL_CALL(glCompileShader(blit_fragment_shader_));
   GL_CALL(glAttachShader(blit_program_, blit_fragment_shader_));
 
-  GL_CALL(glBindAttribLocation(
-      blit_program_, kBlitPositionAttribute, "a_position"));
-  GL_CALL(glBindAttribLocation(
-      blit_program_, kBlitTexcoordAttribute, "a_tex_coord"));
+  GL_CALL(glBindAttribLocation(blit_program_, kBlitPositionAttribute,
+                               "a_position"));
+  GL_CALL(glBindAttribLocation(blit_program_, kBlitTexcoordAttribute,
+                               "a_tex_coord"));
 
   GL_CALL(glLinkProgram(blit_program_));
 
@@ -286,8 +291,7 @@
 }
 
 void GraphicsContextEGL::MakeCurrentWithSurface(RenderTargetEGL* surface) {
-  DCHECK_NE(EGL_NO_SURFACE, surface) <<
-      "Use ReleaseCurrentContext().";
+  DCHECK_NE(EGL_NO_SURFACE, surface) << "Use ReleaseCurrentContext().";
 
   // In some EGL implementations, like Angle, the first time we make current on
   // a thread can result in global allocations being made that are never freed.
@@ -353,8 +357,8 @@
 
 void GraphicsContextEGL::ReleaseCurrentContext() {
   GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
-  EGL_CALL(eglMakeCurrent(
-      display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+  EGL_CALL(
+      eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
 
   current_surface_ = NULL;
   is_current_ = false;
@@ -380,8 +384,8 @@
 
 scoped_refptr<RenderTarget> GraphicsContextEGL::CreateOffscreenRenderTarget(
     const math::Size& dimensions) {
-  scoped_refptr<RenderTarget> render_target(new PBufferRenderTargetEGL(
-      display_, config_, dimensions));
+  scoped_refptr<RenderTarget> render_target(
+      new PBufferRenderTargetEGL(display_, config_, dimensions));
 
   if (render_target->CreationError()) {
     return scoped_refptr<RenderTarget>();
@@ -412,8 +416,7 @@
   int half_height = height / 2;
   for (int row = 0; row < half_height; ++row) {
     uint8_t* top_row = pixels + row * pitch_in_bytes;
-    uint8_t* bottom_row =
-        pixels + (height - 1 - row) * pitch_in_bytes;
+    uint8_t* bottom_row = pixels + (height - 1 - row) * pitch_in_bytes;
     for (int i = 0; i < pitch_in_bytes; ++i) {
       std::swap(top_row[i], bottom_row[i]);
     }
@@ -496,6 +499,10 @@
   GL_CALL(glFinish());
 }
 
+math::Size GraphicsContextEGL::GetWindowSize() const {
+  return system_egl()->GetWindowSize();
+}
+
 void GraphicsContextEGL::Blit(GLuint texture, int x, int y, int width,
                               int height) {
   // Render a texture to the specified output rectangle on the render target.
diff --git a/src/cobalt/renderer/backend/egl/graphics_context.h b/src/cobalt/renderer/backend/egl/graphics_context.h
index b19bb91..232c9ba 100644
--- a/src/cobalt/renderer/backend/egl/graphics_context.h
+++ b/src/cobalt/renderer/backend/egl/graphics_context.h
@@ -42,6 +42,7 @@
   ~GraphicsContextEGL() override;
 
   GraphicsSystemEGL* system_egl();
+  const GraphicsSystemEGL* system_egl() const;
 
   EGLContext GetContext() { return context_; }
 
@@ -66,6 +67,8 @@
 
   void Finish() override;
 
+  math::Size GetWindowSize() const;
+
   // Helper class to allow one to create a RAII object that will acquire the
   // current context upon construction and release it upon destruction.
   class ScopedMakeCurrent {
diff --git a/src/cobalt/renderer/backend/egl/graphics_system.cc b/src/cobalt/renderer/backend/egl/graphics_system.cc
index 8418244..d0148b9 100644
--- a/src/cobalt/renderer/backend/egl/graphics_system.cc
+++ b/src/cobalt/renderer/backend/egl/graphics_system.cc
@@ -283,6 +283,10 @@
 #endif
 }
 
+math::Size GraphicsSystemEGL::GetWindowSize() const {
+  return system_window_ ? system_window_->GetWindowSize() : math::Size();
+}
+
 }  // namespace backend
 }  // namespace renderer
 }  // namespace cobalt
diff --git a/src/cobalt/renderer/backend/egl/graphics_system.h b/src/cobalt/renderer/backend/egl/graphics_system.h
index 3ed462d..4920900 100644
--- a/src/cobalt/renderer/backend/egl/graphics_system.h
+++ b/src/cobalt/renderer/backend/egl/graphics_system.h
@@ -15,6 +15,8 @@
 #ifndef COBALT_RENDERER_BACKEND_EGL_GRAPHICS_SYSTEM_H_
 #define COBALT_RENDERER_BACKEND_EGL_GRAPHICS_SYSTEM_H_
 
+#include <memory>
+
 #include "base/optional.h"
 #include "cobalt/renderer/backend/egl/resource_context.h"
 #include "cobalt/renderer/backend/egl/texture_data.h"
@@ -45,6 +47,8 @@
   std::unique_ptr<RawTextureMemoryEGL> AllocateRawTextureMemory(
       size_t size_in_bytes, size_t alignment);
 
+  math::Size GetWindowSize() const;
+
  private:
   EGLDisplay display_;
   EGLConfig config_;
diff --git a/src/cobalt/renderer/backend/graphics_context.cc b/src/cobalt/renderer/backend/graphics_context.cc
index 1d3d0f6..3455980 100644
--- a/src/cobalt/renderer/backend/graphics_context.cc
+++ b/src/cobalt/renderer/backend/graphics_context.cc
@@ -63,11 +63,10 @@
       graphics_context ? graphics_context->graphics_extension_ : nullptr;
 #if SB_API_VERSION >= 12
 #if defined(ENABLE_MAP_TO_MESH)
-#error \
-    "ENABLE_MAP_TO_MESH is deprecated after Starboard version 12, use \
-the Cobalt graphics extension function IsMapToMeshEnabled() instead."
+#error "ENABLE_MAP_TO_MESH is deprecated after Starboard version 12, use "
+  "the Cobalt graphics extension function IsMapToMeshEnabled() instead."
 #endif  // defined(ENABLE_MAP_TO_MESH)
-  if (graphics_ext && graphics_ext->version >= 3) {
+      if (graphics_ext && graphics_ext->version >= 3) {
     return graphics_ext->IsMapToMeshEnabled();
   }
 
diff --git a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
index 076aaf8..a1d3f9b 100644
--- a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
+++ b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
@@ -100,6 +100,10 @@
     64 * 1.1678f, 64 * 2.1479f,  0.0f,          -1.1469f,
     0.0f,         0.0f,          0.0f,          1.0f};
 
+const float k10BitBT2020ColorMatrixCompacted[16] = {
+    1.1678f, 0.0f,    1.6835f, -0.9147f, 1.1678f, -0.1878f, -0.6522f, 0.347f,
+    1.1678f, 2.1479f, 0.0f,    -1.1469f, 0.0f,    0.0f,     0.0f,     1.0f};
+
 const float* GetColorMatrixForImageType(
     TexturedMeshRenderer::Image::Type type) {
   switch (type) {
@@ -115,6 +119,9 @@
     case TexturedMeshRenderer::Image::YUV_UYVY_422_BT709: {
       return kBT709ColorMatrix;
     } break;
+    case TexturedMeshRenderer::Image::YUV_3PLANE_10BIT_COMPACT_BT2020: {
+      return k10BitBT2020ColorMatrixCompacted;
+    } break;
     case TexturedMeshRenderer::Image::YUV_3PLANE_10BIT_BT2020: {
       return k10BitBT2020ColorMatrix;
     } break;
@@ -177,6 +184,25 @@
                          scale_translate_vector));
   }
 
+  if (image.type == Image::YUV_3PLANE_10BIT_COMPACT_BT2020) {
+    math::Size viewport_size = graphics_context_->GetWindowSize();
+    GL_CALL(glUniform2f(blit_program.viewport_size_uniform,
+                        viewport_size.width(), viewport_size.height()));
+
+    SB_DCHECK(image.num_textures() >= 1);
+    math::Size texture_size = image.textures[0].texture->GetSize();
+    GL_CALL(glUniform2f(
+        blit_program.viewport_to_texture_size_ratio_uniform,
+        viewport_size.width() / static_cast<float>(texture_size.width()),
+        viewport_size.height() / static_cast<float>(texture_size.height())));
+
+    // The pixel shader requires the actual frame size of the first plane
+    // for calculations.
+    size_t subtexture_width = (texture_size.width() + 2) / 3;
+    GL_CALL(glUniform2f(blit_program.subtexture_size_uniform, subtexture_width,
+                        texture_size.height()));
+  }
+
   GL_CALL(glDrawArrays(mode, 0, num_vertices));
 
   for (int i = 0; i < image.num_textures(); ++i) {
@@ -472,6 +498,71 @@
   return CompileShader(blit_fragment_shader_source);
 }
 
+uint32 TexturedMeshRenderer::CreateYUVCompactedTexturesFragmentShader(
+    uint32 texture_target) {
+  SamplerInfo sampler_info = GetSamplerInfo(texture_target);
+
+  std::string blit_fragment_shader_source = sampler_info.preamble;
+
+  // The fragment shader below performs YUV->RGB transform for
+  // compacted 10-bit yuv420 textures
+  blit_fragment_shader_source +=
+      "precision mediump float;"
+      "varying vec2 v_tex_coord_y;"
+      "varying vec2 v_tex_coord_u;"
+      "varying vec2 v_tex_coord_v;"
+      "uniform sampler2D texture_y;"
+      "uniform sampler2D texture_u;"
+      "uniform sampler2D texture_v;"
+      "uniform vec2 viewport_size;"
+      "uniform vec2 viewport_to_texture_ratio;"
+      "uniform vec2 texture_size;"
+      "uniform mat4 to_rgb_color_matrix;"
+      // In a compacted YUV image each channel, Y, U and V, is stored as a
+      // separate single-channel image plane. Its pixels are stored in 32-bit
+      // and each represents 3 10-bit pixels with 2 bits of gap.
+      // In order to extract compacted pixels, the horizontal position at the
+      // compacted texture is calculated as:
+      // index = compacted_position % 3;
+      // decompacted_position = compacted_position / 3;
+      // pixel = CompactedTexture.Sample(Sampler, decompacted_position)[index];
+      "void main() {"
+      // Take into account display size to texture size ratio for Y component.
+      "vec2 DTid = vec2(floor(v_tex_coord_y * viewport_size));"
+      "vec2 compact_pos_Y = vec2((DTid + 0.5) / viewport_to_texture_ratio);"
+      // Calculate the position of 10-bit pixel for Y.
+      "vec2 decompact_pos_Y;"
+      "decompact_pos_Y.x = (floor(compact_pos_Y.x / 3.0) + 0.5) / "
+      "texture_size.x;"
+      "decompact_pos_Y.y = compact_pos_Y.y / texture_size.y;"
+      // Calculate the index of 10-bit pixel for Y.
+      "int index_Y = int(mod(floor(compact_pos_Y.x), 3.0));"
+      // Extract Y component.
+      "float Y_component = texture2D(texture_y, decompact_pos_Y)[index_Y];"
+      // For yuv420 U and V textures have dimensions twice less than Y.
+      "DTid = vec2(DTid / 2.0);"
+      "vec2 texture_size_UV = vec2(texture_size / 2.0);"
+      "vec2 compact_pos_UV = vec2((DTid + 0.5) / viewport_to_texture_ratio);"
+      // Calculate the position of 10-bit pixels for U and V.
+      "vec2 decompact_pos_UV;"
+      "decompact_pos_UV.x = (floor(compact_pos_UV.x / 3.0) + 0.5) / "
+      "texture_size_UV.x;"
+      "decompact_pos_UV.y = (floor(compact_pos_UV.y) + 0.5) / "
+      "texture_size_UV.y;"
+      // Calculate the index of 10-bit pixels for U and V.
+      "int index_UV = int(mod(floor(compact_pos_UV), 3.0));"
+      // Extract U and V components.
+      "float U_component = texture2D(texture_u, decompact_pos_UV)[index_UV];"
+      "float V_component =  texture2D(texture_v, decompact_pos_UV)[index_UV];"
+      // Perform the YUV->RGB transform and output the color value.
+      "vec4 untransformed_color = vec4(Y_component, U_component, V_component, "
+      "1.0);"
+      "gl_FragColor = untransformed_color * to_rgb_color_matrix;"
+      "}";
+
+  return CompileShader(blit_fragment_shader_source);
+}
+
 // static
 TexturedMeshRenderer::ProgramInfo TexturedMeshRenderer::MakeBlitProgram(
     const float* color_matrix, const std::vector<TextureInfo>& textures,
@@ -530,7 +621,20 @@
         glGetUniformLocation(result.gl_program_id, "to_rgb_color_matrix"));
     GL_CALL(glUniformMatrix4fv(to_rgb_color_matrix_uniform, 1, GL_FALSE,
                                color_matrix));
+    if (color_matrix == k10BitBT2020ColorMatrixCompacted) {
+      result.viewport_size_uniform = GL_CALL_SIMPLE(
+          glGetUniformLocation(result.gl_program_id, "viewport_size"));
+      DCHECK_EQ(GL_NO_ERROR, GL_CALL_SIMPLE(glGetError()));
+      result.viewport_to_texture_size_ratio_uniform =
+          GL_CALL_SIMPLE(glGetUniformLocation(result.gl_program_id,
+                                              "viewport_to_texture_ratio"));
+      DCHECK_EQ(GL_NO_ERROR, GL_CALL_SIMPLE(glGetError()));
+      result.subtexture_size_uniform = GL_CALL_SIMPLE(
+          glGetUniformLocation(result.gl_program_id, "texture_size"));
+      DCHECK_EQ(GL_NO_ERROR, GL_CALL_SIMPLE(glGetError()));
+    }
   }
+
   GL_CALL(glUseProgram(0));
 
   GL_CALL(glDeleteShader(blit_fragment_shader));
@@ -599,7 +703,8 @@
       } break;
       case Image::YUV_3PLANE_BT601_FULL_RANGE:
       case Image::YUV_3PLANE_BT709:
-      case Image::YUV_3PLANE_10BIT_BT2020: {
+      case Image::YUV_3PLANE_10BIT_BT2020:
+      case Image::YUV_3PLANE_10BIT_COMPACT_BT2020: {
         std::vector<TextureInfo> texture_infos;
 #if defined(GL_RED_EXT)
         if (image.textures[0].texture->GetFormat() == GL_RED_EXT) {
@@ -622,9 +727,15 @@
         texture_infos.push_back(TextureInfo("u", "a"));
         texture_infos.push_back(TextureInfo("v", "a"));
 #endif  // defined(GL_RED_EXT)
-        result = MakeBlitProgram(
-            color_matrix, texture_infos,
-            CreateFragmentShader(texture_target, texture_infos, color_matrix));
+        uint32 shader_program;
+        if (type == Image::YUV_3PLANE_10BIT_COMPACT_BT2020) {
+          shader_program =
+              CreateYUVCompactedTexturesFragmentShader(texture_target);
+        } else {
+          shader_program =
+              CreateFragmentShader(texture_target, texture_infos, color_matrix);
+        }
+        result = MakeBlitProgram(color_matrix, texture_infos, shader_program);
       } break;
       case Image::YUV_UYVY_422_BT709: {
         std::vector<TextureInfo> texture_infos;
diff --git a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
index e607bd7..2faac78 100644
--- a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
+++ b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
@@ -59,6 +59,10 @@
       // YUV BT2020 image where the Y, U and V components are all on different
       // 10bit unnormalized textures.
       YUV_3PLANE_10BIT_BT2020,
+      // YUV BT2020 image where the Y, U and V components are all on different
+      // textures. Its pixels are stored in 32 bits, and each pixel represents
+      // three 10-bit pixels with two bits of gap.
+      YUV_3PLANE_10BIT_COMPACT_BT2020,
       // 1 texture is used that contains RGBA pixels.
       RGBA,
       // 1 texture plane is used where Y is sampled twice for each UV sample
@@ -79,6 +83,7 @@
         case YUV_3PLANE_BT601_FULL_RANGE:
         case YUV_3PLANE_BT709:
         case YUV_3PLANE_10BIT_BT2020:
+        case YUV_3PLANE_10BIT_COMPACT_BT2020:
           return 3;
         case RGBA:
         case YUV_UYVY_422_BT709:
@@ -121,6 +126,9 @@
     int32 texture_uniforms[3];
     int32 texture_size_uniforms[3];
     uint32 gl_program_id;
+    int32 viewport_size_uniform;
+    int32 viewport_to_texture_size_ratio_uniform;
+    int32 subtexture_size_uniform;
   };
   // We key each program off of their GL texture type and image type.
   typedef std::tuple<uint32, Image::Type, base::Optional<int32> > CacheKey;
@@ -142,6 +150,9 @@
   // of applying bilinear filtering within a texel between the two Y values.
   static uint32 CreateUYVYFragmentShader(uint32 texture_target,
                                          int32 texture_wrap_s);
+  // YUV compacted textures need a special fragment shader to handle compacted
+  // pixels. Compacted textures support YUV420 only.
+  static uint32 CreateYUVCompactedTexturesFragmentShader(uint32 texture_target);
 
   backend::GraphicsContextEGL* graphics_context_;
 
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
index 3ec33fe..7070c83 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
@@ -328,6 +328,18 @@
   return result;
 }
 
+inline void SetImageTextures(egl::TexturedMeshRenderer::Image& result,
+                             unsigned int textures_num,
+                             const math::Matrix3F& local_transform,
+                             HardwareMultiPlaneImage* hardware_image,
+                             render_tree::StereoMode stereo_mode) {
+  for (auto i = 0; i < textures_num; i++) {
+    result.textures[i] = GetTextureFromHardwareFrontendImage(
+        local_transform, hardware_image->GetHardwareFrontendImage(i).get(),
+        stereo_mode);
+  }
+}
+
 egl::TexturedMeshRenderer::Image SkiaImageToTexturedMeshRendererImage(
     const math::Matrix3F& local_transform, Image* image,
     render_tree::StereoMode stereo_mode) {
@@ -357,48 +369,25 @@
         render_tree::kMultiPlaneImageFormatYUV3PlaneBT601FullRange) {
       result.type =
           egl::TexturedMeshRenderer::Image::YUV_3PLANE_BT601_FULL_RANGE;
-      result.textures[0] = GetTextureFromHardwareFrontendImage(
-          local_transform, hardware_image->GetHardwareFrontendImage(0).get(),
-          stereo_mode);
-      result.textures[1] = GetTextureFromHardwareFrontendImage(
-          local_transform, hardware_image->GetHardwareFrontendImage(1).get(),
-          stereo_mode);
-      result.textures[2] = GetTextureFromHardwareFrontendImage(
-          local_transform, hardware_image->GetHardwareFrontendImage(2).get(),
-          stereo_mode);
+      SetImageTextures(result, 3, local_transform, hardware_image, stereo_mode);
     } else if (hardware_image->GetFormat() ==
                render_tree::kMultiPlaneImageFormatYUV2PlaneBT709) {
       result.type = egl::TexturedMeshRenderer::Image::YUV_2PLANE_BT709;
-      result.textures[0] = GetTextureFromHardwareFrontendImage(
-          local_transform, hardware_image->GetHardwareFrontendImage(0).get(),
-          stereo_mode);
-      result.textures[1] = GetTextureFromHardwareFrontendImage(
-          local_transform, hardware_image->GetHardwareFrontendImage(1).get(),
-          stereo_mode);
+      SetImageTextures(result, 2, local_transform, hardware_image, stereo_mode);
     } else if (hardware_image->GetFormat() ==
                render_tree::kMultiPlaneImageFormatYUV3PlaneBT709) {
       result.type = egl::TexturedMeshRenderer::Image::YUV_3PLANE_BT709;
-      result.textures[0] = GetTextureFromHardwareFrontendImage(
-          local_transform, hardware_image->GetHardwareFrontendImage(0).get(),
-          stereo_mode);
-      result.textures[1] = GetTextureFromHardwareFrontendImage(
-          local_transform, hardware_image->GetHardwareFrontendImage(1).get(),
-          stereo_mode);
-      result.textures[2] = GetTextureFromHardwareFrontendImage(
-          local_transform, hardware_image->GetHardwareFrontendImage(2).get(),
-          stereo_mode);
+      SetImageTextures(result, 3, local_transform, hardware_image, stereo_mode);
     } else if (hardware_image->GetFormat() ==
                render_tree::kMultiPlaneImageFormatYUV3Plane10BitBT2020) {
       result.type = egl::TexturedMeshRenderer::Image::YUV_3PLANE_10BIT_BT2020;
-      result.textures[0] = GetTextureFromHardwareFrontendImage(
-          local_transform, hardware_image->GetHardwareFrontendImage(0).get(),
-          stereo_mode);
-      result.textures[1] = GetTextureFromHardwareFrontendImage(
-          local_transform, hardware_image->GetHardwareFrontendImage(1).get(),
-          stereo_mode);
-      result.textures[2] = GetTextureFromHardwareFrontendImage(
-          local_transform, hardware_image->GetHardwareFrontendImage(2).get(),
-          stereo_mode);
+      SetImageTextures(result, 3, local_transform, hardware_image, stereo_mode);
+    } else if (hardware_image->GetFormat() ==
+               render_tree::
+                   kMultiPlaneImageFormatYUV3Plane10BitCompactedBT2020) {
+      result.type =
+          egl::TexturedMeshRenderer::Image::YUV_3PLANE_10BIT_COMPACT_BT2020;
+      SetImageTextures(result, 3, local_transform, hardware_image, stereo_mode);
     }
   } else {
     NOTREACHED();
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
index f3295a7..b12b510 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
@@ -19,6 +19,7 @@
 #include "cobalt/renderer/rasterizer/skia/hardware_resource_provider.h"
 
 #include <memory>
+#include <utility>
 
 #include "base/synchronization/waitable_event.h"
 #include "base/trace_event/trace_event.h"
@@ -205,6 +206,9 @@
       }
     } break;
     case kSbDecodeTargetFormat3Plane10BitYUVI420:
+#if SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+    case kSbDecodeTargetFormat3Plane10BitYUVI420Compact:
+#endif  // SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
     case kSbDecodeTargetFormat3PlaneYUVI420: {
       DCHECK_LT(plane, 3);
 #if defined(GL_RED_EXT)
@@ -233,6 +237,11 @@
     case kSbDecodeTargetFormat3Plane10BitYUVI420: {
       return render_tree::kMultiPlaneImageFormatYUV3Plane10BitBT2020;
     } break;
+#if SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+    case kSbDecodeTargetFormat3Plane10BitYUVI420Compact: {
+      return render_tree::kMultiPlaneImageFormatYUV3Plane10BitCompactedBT2020;
+    }
+#endif  // SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
     default: { NOTREACHED(); }
   }
   return render_tree::kMultiPlaneImageFormatYUV2PlaneBT709;
diff --git a/src/cobalt/site/docs/development/setup-android.md b/src/cobalt/site/docs/development/setup-android.md
index b2ddb55..621f5f3 100644
--- a/src/cobalt/site/docs/development/setup-android.md
+++ b/src/cobalt/site/docs/development/setup-android.md
@@ -9,7 +9,7 @@
 ## Preliminary Setup
 
 <aside class="note">
-<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section **Set up your workstation**, then return and complete the following steps.
+<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section <b>Set up your workstation</b>, then return and complete the following steps.
 </aside>
 
 1.  Additional build dependencies may need to be installed:
diff --git a/src/cobalt/site/docs/development/setup-docker.md b/src/cobalt/site/docs/development/setup-docker.md
new file mode 100644
index 0000000..afc7065
--- /dev/null
+++ b/src/cobalt/site/docs/development/setup-docker.md
@@ -0,0 +1,93 @@
+---
+layout: doc
+title: "Set up your environment - Docker"
+---
+
+We provide <a
+href="https://cobalt.googlesource.com/cobalt/+/refs/heads/22.lts.stable/src/docker/linux/">Docker image definitions</a> to simplify managing build environments.
+
+The instructions below assume Docker is installed and is able to run basic
+hello-world verification. `docker-compose` command is expected to be available as well.
+
+## Set up your workstation
+
+Clone the Cobalt code repository. The following `git` command creates a
+`cobalt` directory that contains the repository:
+
+```
+$ git clone https://cobalt.googlesource.com/cobalt
+$ cd cobalt
+```
+
+### Usage
+
+The simplest usage is:
+
+```
+docker-compose run <platform>
+```
+
+By default, a `debug` build will be built, with `cobalt` as a target.
+You can override this with an environment variable, e.g.
+
+```
+docker-compose run -e CONFIG=devel -e TARGET=nplb <platform>
+```
+
+where config is one of the four optimization levels, `debug`, `devel`,
+`qa` and `gold`, and target is the build target passed to ninja
+
+See <a
+href="https://cobalt.googlesource.com/cobalt/+/refs/heads/22.lts.stable/src/README.md#build-types">Cobalt README</a> for full details.
+
+Builds will be available in your `${COBALT_SRC}/out` directory.
+
+<aside class="note">
+Note that Docker runs processes as root user by default, hence
+output files in `src/out/<platform>` directory have `root` as file owner.
+</aside>
+
+### Customization
+
+To parametrize base operating system images used for the build, pass
+`BASE_OS` as an argument to `docker-compose` as follows:
+
+```
+docker-compose build --build-arg BASE_OS="ubuntu:bionic" base
+```
+
+This parameter is defined in `docker/linux/base/Dockerfile` and is passed
+to Docker `FROM ...` statement.
+
+Available parameters for customizing container execution are:
+
+**BASE_OS**: passed to `base` image at build time to select a Debian-based
+   base os image and version. Defaults to Debian 10. `ubuntu:bionic` and
+   `ubuntu:xenial` are other tested examples.
+
+**PLATFORM**: Cobalt build platform, passed to GYP
+
+**CONFIG**: Cobalt build config, passed to GYP. Defaults to `debug`
+
+**TARGET**: Build target, passed to `ninja`
+
+The `docker-compose.yml` contains the currently defined experimental build
+configurations. Edit or add new `service` entries as needed, to build custom
+configurations.
+
+
+### Pre-built images
+
+Note: Pre-built images from a public container registry are not yet available.
+
+### Troubleshooting
+
+To debug build issues, enter the shell of the corresponding build container
+by launching the bash shell, i.e.
+
+```
+docker-compose run linux-x64x11 /bin/bash
+```
+
+and try to build Cobalt with the <a
+href="https://cobalt.googlesource.com/cobalt/+/refs/heads/22.lts.stable/src/README.md#building-and-running-the-code">usual gyp / ninja flow.</a>
diff --git a/src/cobalt/site/docs/development/setup-linux.md b/src/cobalt/site/docs/development/setup-linux.md
index 288711b..21d07e5 100644
--- a/src/cobalt/site/docs/development/setup-linux.md
+++ b/src/cobalt/site/docs/development/setup-linux.md
@@ -9,16 +9,20 @@
 on the machine that you are using to view the client. For example, you cannot
 SSH into another machine and run the binary on that machine.
 
+These instructions were tested on a fresh ubuntu:20.04 Docker image. (1/12/22)
+Required libraries can differ depending on your Linux distribution and version.
+
 ## Set up your workstation
 
 1.  Run the following command to install packages needed to build and run
     Cobalt on Linux:
 
     ```
-    $ sudo apt install -qqy --no-install-recommends pkgconf ninja-build \
-        bison yasm binutils clang libgles2-mesa-dev mesa-common-dev \
-        libpulse-dev libavresample-dev libasound2-dev libxrender-dev \
-        libxcomposite-dev
+    $ sudo apt update && sudo apt install -qqy --no-install-recommends \
+        pkgconf ninja-build bison yasm binutils clang libgles2-mesa-dev \
+        mesa-common-dev libpulse-dev libavresample-dev libasound2-dev \
+        libxrender-dev libxcomposite-dev libxml2-dev curl git \
+        python3.8-venv python2
     ```
 
 1.  Install Node.js via `nvm`:
@@ -48,6 +52,14 @@
     $ ccache --max-size=20G
     ```
 
+1.  Install necessary python2 packages for GYP. Until Cobalt 23, when we have
+    migrated our build system to GN, we still require some python2 packages:
+
+    ```
+    $ curl https://bootstrap.pypa.io/pip/2.7/get-pip.py | python2
+    $ python2 -m pip install --user requests selenium six
+    ```
+
 1.  Clone the Cobalt code repository. The following `git` command creates a
     `cobalt` directory that contains the repository:
 
@@ -63,17 +75,17 @@
     $ cd cobalt
     ```
 
-<aside class="note">
-<b>Note:</b> Pre-commit is only available on branches later than 22.lts.1+,
-including trunk. The below commands will fail on 22.lts.1+ and earlier branches.
-For earlier branches, run `cd src` and move on to the next section.
-</aside>
+    <aside class="note">
+    <b>Note:</b> Pre-commit is only available on branches later than 22.lts.1+,
+    including trunk. The below commands will fail on 22.lts.1+ and earlier branches.
+    For earlier branches, run `cd src` and move on to the next section.
+    </aside>
 
 1.  Create a Python 3 virtual environment for working on Cobalt (feel free to use `virtualenvwrapper` instead):
 
     ```
-    $ python -m venv ~/.virtualenvs/cobalt_dev
-    $ source ~/.virtualenvs/cobalt_dev
+    $ python3 -m venv ~/.virtualenvs/cobalt_dev
+    $ source ~/.virtualenvs/cobalt_dev/bin/activate
     $ pip install -r requirements.txt
     ```
 
diff --git a/src/cobalt/site/docs/development/setup-raspi.md b/src/cobalt/site/docs/development/setup-raspi.md
index 48ca4cc..8badc0a 100644
--- a/src/cobalt/site/docs/development/setup-raspi.md
+++ b/src/cobalt/site/docs/development/setup-raspi.md
@@ -4,57 +4,42 @@
 ---
 
 These instructions explain how to set up Cobalt for your workstation and your
-Raspberry Pi device.
+Raspberry Pi device. They have been tested with Ubuntu:20.04 and a Raspberry Pi
+3 Model B.
 
 ## Set up your device
 
-<aside class="note">
-<b>Note:</b> Raspberry Pi <em>cannot</em> have MesaGL installed and will return
-an error, like `DRI2 not supported` or `DRI2 failed to authenticate` if MesaGL
-is installed.
-</aside>
+Download the latest Cobalt customized Raspbian image from <a
+href="https://storage.googleapis.com/cobalt-static-storage/2020-02-13-raspbian-buster-lite_shrunk_20210427.img">GCS bucket</a>
+(this is built via <a
+href="https://github.com/youtube/cobalt/tree/master/tools/raspi_image#readme">this
+customization tool</a>)
 
-<aside class="note">
-<b>Note:</b> The current builds of Cobalt currently are verified to work only
-with a fairly old version of Raspbian Lite from 2017-07-05 (
-<a href="http://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2017-07-05/2017-07-05-raspbian-jessie-lite.zip">download link</a>
-).  If you have a newer version, you may encounter linker errors when building
-Cobalt as the sysroot system libraries will differ in the latest version of
-Raspbian.
-</aside>
+On MacOS, use an image flashing tool like <a href="https://www.balena.io/etcher/">balenaEtcher</a> to write the image to a 32GB SD-card.
 
-Configure the Raspberry Pi memory split.
+On Linux, follow the steps below.
 
-1.  `sudo raspi-config`
-1.  Go to Advanced
-1.  Memory Split: 256 for RasPi-0, 512 for all others.
-
-Cobalt assumes the Raspberry Pi is configured to use non-default thread
-schedulers and priorities. Ensure that **/etc/security/limits.conf** sets
-**rtprio** and **nice** limits for the user. For example, if the user is **pi**,
-then limits.conf should have the following lines:
+Check the location of your SD card (/dev/sdX or /dev/mmcblkX)
 
 ```
-@pi hard rtprio 99
-@pi soft rtprio 99
-@pi hard nice -20
-@pi soft nice -20
+$ sudo fdisk -l
 ```
+Make sure the card isn't mounted ( `umount /dev/sdX` ).
 
-The following commands update the package configuration on your Raspberry Pi
-so that Cobalt can run properly:
+Copy the downloaded image to your SD card (the disk, not the partition. E.g. /dev/sdX or /dev/mmcblkX):
 
 ```
-$ apt-get remove -y --purge --auto-remove libgl1-mesa-dev \
-          libegl1-mesa-dev libgles2-mesa libgles2-mesa-dev
-$ apt-get install -y libpulse-dev libasound2-dev libavformat-dev \
-          libavresample-dev rsync
+$ sudo dd bs=4M if=2020-02-13-raspbian-buster-lite_shrunk_20210427.img of=/dev/sdX
 ```
+After flashing your device, you'll still need to setup your wifi. Login with the
+default pi login, and run `sudo raspi-config`. You'll find wifi settings under
+`1. System Options`, then `S1 Wireless LAN`.
+
 
 ## Set up your workstation
 
 <aside class="note">
-<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section **Set up your workstation**, then return and complete the following steps.
+<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section <b>Set up your workstation</b>, then return and complete the following steps.
 </aside>
 
 The following steps install the cross-compiling toolchain on your workstation.
@@ -66,7 +51,8 @@
 
     ```
     $ sudo apt install -qqy --no-install-recommends g++-multilib \
-          python-requests wget xz-utils
+        wget xz-utils libxml2  binutils-aarch64-linux-gnu \
+        binutils-arm-linux-gnueabi  libglib2.0-dev
     ```
 
 1.  Choose a location for the installed toolchain &ndash; e.g. `raspi-tools`
@@ -77,32 +63,19 @@
 1.  Create the directory for the installed toolchain and go to it:
 
     ```
-    mkdir -p $RASPI_HOME
-    cd $RASPI_HOME
+    $ mkdir -p $RASPI_HOME
+    $ cd $RASPI_HOME
     ```
 
-1.  Clone the GitHub repository for Raspberry Pi tools:
+1.  Download the pre-packaged toolchain and extract it in `$RASPI_HOME`.
 
     ```
-    git clone git://github.com/raspberrypi/tools.git
+    $ curl -O https://storage.googleapis.com/cobalt-static-storage/cobalt_raspi_tools.tar.bz2
+    $ tar xvpf cobalt_raspi_tools.tar.bz2
     ```
 
-1.  Sync your sysroot by completing the following steps:
-
-    1.  Boot up your RasPi, and set `$RASPI_ADDR` to the device's IP address.
-    1.  Run `mkdir -p $RASPI_HOME/sysroot`
-    1.  Run:
-
-        ```
-        rsync -avzh --safe-links \
-              --delete-after pi@$RASPI_ADDR:/{opt,lib,usr} \
-              --exclude="lib/firmware" --exclude="lib/modules" \
-              --include="usr/lib" --include="usr/include" \
-              --include="usr/local/include" --include="usr/local/lib" \
-              --exclude="usr/*" --include="opt/vc" --exclude="opt/*" \
-              $RASPI_HOME/sysroot
-        password: raspberry
-        ```
+    (This is a combination of old raspi tools and a newer one from linaro
+     to support older Raspbian Jessie and newer Raspbian Buster)
 
 ## Build, install, and run Cobalt for Raspberry Pi
 
@@ -123,7 +96,7 @@
     on the device:
 
     ```
-    rsync -avzLPh --exclude="obj*" --exclude="gen/" \
+    $ rsync -avzLPh --exclude="obj*" --exclude="gen/" \
           $COBALT_SRC/out/raspi-2_debug pi@$RASPI_ADDR:~/
     ```
 
@@ -135,9 +108,9 @@
     to quit or restart Cobalt.
 
     ```
-    ssh pi@$RASPI_ADDR
-    cd raspi-2_debug
-    ./cobalt
+    $ ssh pi@$RASPI_ADDR
+    $ cd raspi-2_debug
+    $ ./cobalt
     ```
 
     With this approach, you can just hit `[CTRL-C]` to close Cobalt. If you
@@ -147,21 +120,3 @@
     Note that you can also exit YouTube on Cobalt by hitting the `[Esc]` key
     enough times to bring up the "Do you want to quit YouTube?" dialog and
     selecting "yes".
-
-### Improving Cobalt performance on Raspberry Pi
-
-1.  You will find that there are some processes installed by default that run on the
-    Raspberry Pi and can take away CPU time from Cobalt. You may wish to consider
-    disabling these processes for maximum (and more consistent) performance, as they
-    have been found to occasionally take >10% of the CPU according to `top`.
-    You can do this by typing:
-
-    ```
-    apt-get remove -y --auto-remove [PACKAGE_NAME, ...]
-    ```
-
-    For example:
-
-    ```
-    apt-get remove -y --auto-remove avahi-daemon
-    ```
diff --git a/src/cobalt/system_window/system_window.cc b/src/cobalt/system_window/system_window.cc
index 85dbe0c..7dc64ce 100644
--- a/src/cobalt/system_window/system_window.cc
+++ b/src/cobalt/system_window/system_window.cc
@@ -108,6 +108,14 @@
                                       InputEvent::Type type, bool is_repeat) {
   DCHECK(event);
   DCHECK(event->data);
+  if (event == nullptr) {
+    LOG(ERROR) << "SystemWindow::DispatchInputEvent: missing event";
+    return;
+  }
+  if (event->data == nullptr) {
+    LOG(ERROR) << "SystemWindow::DispatchInputEvent: missing event data";
+    return;
+  }
   SbInputData* input_data = static_cast<SbInputData*>(event->data);
   const SbInputData& data = *input_data;
 
@@ -115,7 +123,7 @@
   SbTimeMonotonic timestamp = 0;
 #if SB_API_VERSION >= 13
   timestamp = event->timestamp;
-#else  // SB_API_VERSION >= 13
+#else   // SB_API_VERSION >= 13
   bool use_input_timestamp =
       SbSystemHasCapability(kSbSystemCapabilitySetsInputTimestamp);
   if (use_input_timestamp) {
diff --git a/src/cobalt/trace_event/scoped_trace_to_file.cc b/src/cobalt/trace_event/scoped_trace_to_file.cc
index d5d3d9d..ffbe948 100644
--- a/src/cobalt/trace_event/scoped_trace_to_file.cc
+++ b/src/cobalt/trace_event/scoped_trace_to_file.cc
@@ -28,12 +28,17 @@
 namespace cobalt {
 namespace trace_event {
 
+// static
+base::FilePath ScopedTraceToFile::filepath_to_absolute(
+    const base::FilePath& output_path_relative_to_logs) {
+  base::FilePath result;
+  base::PathService::Get(cobalt::paths::DIR_COBALT_DEBUG_OUT, &result);
+  return result.Append(output_path_relative_to_logs);
+}
+
 ScopedTraceToFile::ScopedTraceToFile(
     const base::FilePath& output_path_relative_to_logs) {
-  base::PathService::Get(cobalt::paths::DIR_COBALT_DEBUG_OUT,
-                         &absolute_output_path_);
-  absolute_output_path_ =
-      absolute_output_path_.Append(output_path_relative_to_logs);
+  absolute_output_path_ = filepath_to_absolute(output_path_relative_to_logs);
 
   DCHECK(!base::trace_event::TraceLog::GetInstance()->IsEnabled());
   base::trace_event::TraceLog::GetInstance()->SetEnabled(
diff --git a/src/cobalt/trace_event/scoped_trace_to_file.h b/src/cobalt/trace_event/scoped_trace_to_file.h
index 49c6413..d03216f 100644
--- a/src/cobalt/trace_event/scoped_trace_to_file.h
+++ b/src/cobalt/trace_event/scoped_trace_to_file.h
@@ -38,6 +38,10 @@
     return absolute_output_path_;
   }
 
+  // Returns absolute output path from relative filename
+  static base::FilePath filepath_to_absolute(
+      const base::FilePath& output_path_relative_to_logs);
+
  private:
   base::FilePath absolute_output_path_;
 };
diff --git a/src/cobalt/updater/configurator.cc b/src/cobalt/updater/configurator.cc
index 4c3b6cf..ee8b15b 100644
--- a/src/cobalt/updater/configurator.cc
+++ b/src/cobalt/updater/configurator.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/version.h"
+#include "cobalt/browser/switches.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/updater/network_fetcher.h"
 #include "cobalt/updater/patcher.h"
@@ -43,6 +44,11 @@
   return prop;
 }
 
+bool CompressUpdate() {
+  return base::CommandLine::ForCurrentProcess()->HasSwitch(
+      cobalt::browser::switches::kCompressUpdate);
+}
+
 }  // namespace
 
 namespace cobalt {
@@ -53,7 +59,7 @@
       persisted_data_(std::make_unique<update_client::PersistedData>(
           pref_service_.get(), nullptr)),
       is_channel_changed_(0),
-      unzip_factory_(base::MakeRefCounted<UnzipperFactory>()),
+      unzip_factory_(base::MakeRefCounted<UnzipperFactory>(CompressUpdate())),
       network_fetcher_factory_(
           base::MakeRefCounted<NetworkFetcherFactoryCobalt>(network_module)),
       patch_factory_(base::MakeRefCounted<PatcherFactory>()) {
@@ -243,6 +249,16 @@
   updater_status_ = status;
 }
 
+void Configurator::SetMinFreeSpaceBytes(uint64_t bytes) {
+  base::AutoLock auto_lock(const_cast<base::Lock&>(min_free_space_bytes_lock_));
+  min_free_space_bytes_ = bytes;
+}
+
+uint64_t Configurator::GetMinFreeSpaceBytes() {
+  base::AutoLock auto_lock(const_cast<base::Lock&>(min_free_space_bytes_lock_));
+  return min_free_space_bytes_;
+}
+
 std::string Configurator::GetPreviousUpdaterStatus() const {
   base::AutoLock auto_lock(
       const_cast<base::Lock&>(previous_updater_status_lock_));
diff --git a/src/cobalt/updater/configurator.h b/src/cobalt/updater/configurator.h
index 5314984..54bb6c0 100644
--- a/src/cobalt/updater/configurator.h
+++ b/src/cobalt/updater/configurator.h
@@ -1,6 +1,16 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 #ifndef COBALT_UPDATER_CONFIGURATOR_H_
 #define COBALT_UPDATER_CONFIGURATOR_H_
@@ -85,6 +95,9 @@
   std::string GetPreviousUpdaterStatus() const override;
   void SetPreviousUpdaterStatus(const std::string& status) override;
 
+  void SetMinFreeSpaceBytes(uint64_t bytes) override;
+  uint64_t GetMinFreeSpaceBytes() override;
+
  private:
   friend class base::RefCountedThreadSafe<Configurator>;
   ~Configurator() override;
@@ -102,6 +115,8 @@
   std::string previous_updater_status_;
   base::Lock previous_updater_status_lock_;
   std::string user_agent_string_;
+  uint64_t min_free_space_bytes_ = 48 * 1024 * 1024;
+  base::Lock min_free_space_bytes_lock_;
 
   DISALLOW_COPY_AND_ASSIGN(Configurator);
 };
diff --git a/src/cobalt/updater/unzipper.cc b/src/cobalt/updater/unzipper.cc
index 1855b3a..5b1e2ba 100644
--- a/src/cobalt/updater/unzipper.cc
+++ b/src/cobalt/updater/unzipper.cc
@@ -15,22 +15,223 @@
 #include "cobalt/updater/unzipper.h"
 
 #include <utility>
+#include <vector>
+
+#include "base/bind.h"
 #include "base/callback.h"
+#include "base/files/file.h"
 #include "base/files/file_path.h"
+#include "starboard/event.h"
+#include "starboard/memory.h"
+#include "starboard/system.h"
 #include "starboard/time.h"
+#include "third_party/lz4_lib/lz4frame.h"
 #include "third_party/zlib/google/zip.h"
+#include "third_party/zlib/google/zip_reader.h"
 
 namespace cobalt {
 namespace updater {
 
 namespace {
 
+constexpr size_t kChunkSize = 8 * 1024;
+constexpr LZ4F_preferences_t kPrefs = {
+    {LZ4F_max256KB, LZ4F_blockLinked, LZ4F_noContentChecksum, LZ4F_frame,
+     0 /* unknown content size */, 0 /* no dictID */, LZ4F_noBlockChecksum},
+    0,
+    /* compression level; 0 == default */ 0,
+    /* autoflush */ 0,
+    /* favor decompression speed */ {0, 0, 0},
+    /* reserved, must be set to 0 */};
+
+class LZ4WriterDelegate : public zip::WriterDelegate {
+ public:
+  explicit LZ4WriterDelegate(const base::FilePath& output_file_path)
+      : output_file_path_(output_file_path) {}
+
+  bool PrepareOutput() override {
+    if (!base::CreateDirectory(output_file_path_.DirName())) {
+      return false;
+    }
+
+    output_file_path_ = output_file_path_.ReplaceExtension("lz4");
+
+    LOG(INFO) << "LZ4WriterDelegate::PrepareOutput: output_file_path_="
+              << output_file_path_.value();
+    file_.Initialize(output_file_path_,
+                     base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+    if (!file_.IsValid()) {
+      LOG(ERROR) << "LZ4WriterDelegate::PrepareOutput: invalid file"
+                 << output_file_path_.value();
+      return false;
+    }
+
+    LZ4F_errorCode_t res = LZ4F_createCompressionContext(&ctx_, LZ4F_VERSION);
+    if (LZ4F_isError(res)) {
+      LOG(ERROR) << "LZ4WriterDelegate::PrepareOutput: lz4 error code= " << res;
+      return false;
+    }
+
+    out_capacity_ = LZ4F_compressBound(kChunkSize, &kPrefs);
+    LOG(INFO) << "out_capacity=" << out_capacity_;
+    out_buffer_.resize(out_capacity_);
+    if (!WriteHeader()) {
+      LOG(ERROR) << "LZ4WriterDelegate::PrepareOutput: Failed to write header";
+      return false;
+    }
+    return true;
+  }
+
+  bool WriteBytes(const char* data, int num_bytes) override {
+    LOG(INFO) << "LZ4WriterDelegate::WriteBytes: num_bytes=" << num_bytes
+              << " out_capacity=" << out_capacity_;
+    size_t compressed_size = LZ4F_compressUpdate(
+        ctx_, out_buffer_.data(), out_buffer_.size(), data, num_bytes, nullptr);
+    if (LZ4F_isError(compressed_size)) {
+      LOG(ERROR) << "LZ4WriterDelegate::WriteBytes: Compression failed: error "
+                 << (unsigned)compressed_size;
+      return false;
+    }
+
+    LOG(INFO) << "LZ4WriterDelegate::WriteBytes: Writing "
+              << (unsigned)compressed_size << " bytes";
+    if (compressed_size !=
+        file_.WriteAtCurrentPos(out_buffer_.data(), compressed_size)) {
+      LOG(ERROR) << "LZ4WriterDelegate::WriteBytes: failed to write "
+                 << compressed_size << " bytes";
+      return false;
+    }
+    return true;
+  }
+
+  void SetTimeModified(const base::Time& time) override {
+    size_t compressed_size =
+        LZ4F_compressEnd(ctx_, out_buffer_.data(), out_buffer_.size(), nullptr);
+    if (LZ4F_isError(compressed_size)) {
+      LOG(ERROR) << "LZ4WriterDelegate::SetTimeModified: Failed to end "
+                    "compression: error "
+                 << (unsigned)compressed_size;
+      return;
+    }
+
+    LOG(INFO) << "Writing " << (unsigned)compressed_size << " bytes";
+
+    if (compressed_size !=
+        file_.WriteAtCurrentPos(out_buffer_.data(), compressed_size)) {
+      LOG(ERROR) << "LZ4WriterDelegate::SetTimeModified: failed to write "
+                 << compressed_size << " bytes";
+      return;
+    }
+
+    success_ = true;
+  }
+
+  bool IsSuccessful() override { return success_; }
+
+  ~LZ4WriterDelegate() {
+    if (ctx_) {
+      LZ4F_freeCompressionContext(ctx_);
+    }
+    file_.Close();
+  }
+
+ private:
+  bool WriteHeader() {
+    size_t header_size = LZ4F_compressBegin(ctx_, out_buffer_.data(),
+                                            out_buffer_.size(), &kPrefs);
+    if (LZ4F_isError(header_size)) {
+      LOG(ERROR) << "Failed to start compression: error "
+                 << (unsigned)header_size;
+      return false;
+    }
+    LOG(INFO) << "Header size " << (unsigned)header_size;
+    return header_size ==
+           file_.WriteAtCurrentPos(out_buffer_.data(), header_size);
+  }
+
+  base::FilePath output_file_path_;
+  base::File file_;
+  LZ4F_compressionContext_t ctx_ = nullptr;
+  std::vector<char> out_buffer_;
+  size_t out_capacity_ = 64 * 1024;
+  bool success_ = false;
+};
+
 class UnzipperImpl : public update_client::Unzipper {
  public:
-  UnzipperImpl() = default;
+  explicit UnzipperImpl(bool updater_compression)
+      : compress_update_(updater_compression) {}
 
   void Unzip(const base::FilePath& zip_path, const base::FilePath& output_path,
              UnzipCompleteCallback callback) override {
+    if (compress_update_) {
+      UnzipAndLZ4Compress(zip_path, output_path, std::move(callback));
+    } else {
+      UnzipToPath(zip_path, output_path, std::move(callback));
+    }
+  }
+
+  void UnzipAndLZ4Compress(const base::FilePath& zip_path,
+                           const base::FilePath& output_path,
+                           UnzipCompleteCallback callback) {
+    SbTimeMonotonic time_before_unzip = SbTimeGetMonotonicNow();
+
+    // Filter to unzip just libcobalt.so
+    auto filter = base::BindRepeating([](const base::FilePath& path) {
+      return (path.BaseName().value() == "libcobalt.so");
+    });
+    auto directory_creator = base::BindRepeating(
+        [](const base::FilePath& extract_dir,
+           const base::FilePath& entry_path) {
+          return base::CreateDirectory(extract_dir.Append(entry_path));
+        },
+        output_path);
+    auto writer_factory = base::BindRepeating(
+        [](const base::FilePath& extract_dir, const base::FilePath& entry_path)
+            -> std::unique_ptr<zip::WriterDelegate> {
+          return std::make_unique<LZ4WriterDelegate>(
+              extract_dir.Append(entry_path));
+        },
+        output_path);
+
+    if (!zip::UnzipWithFilterAndWriters(zip_path, writer_factory,
+                                        directory_creator, filter, true)) {
+      LOG(ERROR) << "Failed to unzip libcobalt.so";
+      std::move(callback).Run(false);
+      return;
+    } else {
+      LOG(INFO) << "Successfully unzipped and lz4 compressed libcobalt.so";
+    }
+
+    // Filter to unzip the rest of the CRX package.
+    filter = base::BindRepeating([](const base::FilePath& path) {
+      if (path.BaseName().value() == "libcobalt.so") {
+        return false;
+      }
+      return true;
+    });
+
+    if (!zip::UnzipWithFilterCallback(zip_path, output_path, filter, true)) {
+      LOG(ERROR) << "Failed to unzip the CRX package";
+      std::move(callback).Run(false);
+      return;
+    } else {
+      LOG(INFO) << "Successfully unzipped the CRX package";
+    }
+
+    std::move(callback).Run(true);
+
+    SbTimeMonotonic time_unzip_took =
+        SbTimeGetMonotonicNow() - time_before_unzip;
+    LOG(INFO) << "Unzip file path = " << zip_path;
+    LOG(INFO) << "output_path = " << output_path;
+    LOG(INFO) << "Unzip took " << time_unzip_took / kSbTimeMillisecond
+              << " milliseconds.";
+  }
+
+  void UnzipToPath(const base::FilePath& zip_path,
+                   const base::FilePath& output_path,
+                   UnzipCompleteCallback callback) {
     SbTimeMonotonic time_before_unzip = SbTimeGetMonotonicNow();
     std::move(callback).Run(zip::Unzip(zip_path, output_path));
     SbTimeMonotonic time_unzip_took =
@@ -40,14 +241,18 @@
     LOG(INFO) << "Unzip took " << time_unzip_took / kSbTimeMillisecond
               << " milliseconds.";
   }
+
+ private:
+  bool compress_update_ = false;
 };
 
 }  // namespace
 
-UnzipperFactory::UnzipperFactory() = default;
+UnzipperFactory::UnzipperFactory(bool compress_update)
+    : compress_update_(compress_update) {}
 
 std::unique_ptr<update_client::Unzipper> UnzipperFactory::Create() const {
-  return std::make_unique<UnzipperImpl>();
+  return std::make_unique<UnzipperImpl>(compress_update_);
 }
 
 UnzipperFactory::~UnzipperFactory() = default;
diff --git a/src/cobalt/updater/unzipper.h b/src/cobalt/updater/unzipper.h
index 46dfb7e..df9ecf5 100644
--- a/src/cobalt/updater/unzipper.h
+++ b/src/cobalt/updater/unzipper.h
@@ -1,6 +1,16 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 
 #ifndef COBALT_UPDATER_UNZIPPER_H_
 #define COBALT_UPDATER_UNZIPPER_H_
@@ -16,7 +26,7 @@
 
 class UnzipperFactory : public update_client::UnzipperFactory {
  public:
-  UnzipperFactory();
+  explicit UnzipperFactory(bool compress_update);
 
   std::unique_ptr<update_client::Unzipper> Create() const override;
 
@@ -24,6 +34,7 @@
   ~UnzipperFactory() override;
 
  private:
+  bool compress_update_;
   DISALLOW_COPY_AND_ASSIGN(UnzipperFactory);
 };
 
diff --git a/src/cobalt/updater/updater.gyp b/src/cobalt/updater/updater.gyp
index e603703..efbef55 100644
--- a/src/cobalt/updater/updater.gyp
+++ b/src/cobalt/updater/updater.gyp
@@ -46,6 +46,7 @@
         '<(DEPTH)/components/update_client/update_client.gyp:update_client',
         '<(DEPTH)/starboard/loader_app/app_key_files.gyp:app_key_files',
         '<(DEPTH)/starboard/loader_app/drain_file.gyp:drain_file',
+        '<(DEPTH)/third_party/lz4_lib/lz4.gyp:lz4',
         '<(DEPTH)/third_party/zlib/zlib.gyp:zip',
         '<(DEPTH)/url/url.gyp:url',
       ],
diff --git a/src/cobalt/updater/updater_module.cc b/src/cobalt/updater/updater_module.cc
index e2e4001..dfbb14b 100644
--- a/src/cobalt/updater/updater_module.cc
+++ b/src/cobalt/updater/updater_module.cc
@@ -37,6 +37,7 @@
 #include "cobalt/updater/crash_reporter.h"
 #include "cobalt/updater/utils.h"
 #include "components/crx_file/crx_verifier.h"
+#include "components/update_client/cobalt_slot_management.h"
 #include "components/update_client/utils.h"
 #include "starboard/common/file.h"
 #include "starboard/configuration_constants.h"
@@ -44,6 +45,7 @@
 namespace {
 
 using update_client::ComponentState;
+using update_client::CobaltSlotManagement;
 
 // The SHA256 hash of the "cobalt_evergreen_public" key.
 constexpr uint8_t kCobaltPublicKeyHash[] = {
@@ -92,7 +94,8 @@
 namespace cobalt {
 namespace updater {
 
-const uint64_t kDefaultUpdateCheckDelaySeconds = 60;
+// The delay in seconds before the first update check.
+const uint64_t kDefaultUpdateCheckDelaySeconds = 15;
 
 void Observer::OnEvent(Events event, const std::string& id) {
   LOG(INFO) << "Observer::OnEvent id=" << id;
@@ -220,6 +223,17 @@
   }
 
   updater_configurator_ = nullptr;
+
+  // Cleanup drain files
+  const auto installation_manager =
+      static_cast<const CobaltExtensionInstallationManagerApi*>(
+          SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
+  if (installation_manager) {
+    CobaltSlotManagement cobalt_slot_management;
+    if (cobalt_slot_management.Init(installation_manager)) {
+      cobalt_slot_management.CleanupAllDrainFiles();
+    }
+  }
   LOG(INFO) << "UpdaterModule::Finalize end";
 }
 
@@ -388,5 +402,12 @@
   return index;
 }
 
+void UpdaterModule::SetMinFreeSpaceBytes(uint64_t bytes) {
+  LOG(INFO) << "UpdaterModule::SetMinFreeSpaceBytes bytes=" << bytes;
+  if (updater_configurator_) {
+    updater_configurator_->SetMinFreeSpaceBytes(bytes);
+  }
+}
+
 }  // namespace updater
 }  // namespace cobalt
diff --git a/src/cobalt/updater/updater_module.h b/src/cobalt/updater/updater_module.h
index 353215e..0d6f793 100644
--- a/src/cobalt/updater/updater_module.h
+++ b/src/cobalt/updater/updater_module.h
@@ -151,6 +151,8 @@
 
   int GetInstallationIndex() const;
 
+  void SetMinFreeSpaceBytes(uint64_t bytes);
+
  private:
   std::unique_ptr<base::Thread> updater_thread_;
   scoped_refptr<update_client::UpdateClient> update_client_;
diff --git a/src/cobalt/version.h b/src/cobalt/version.h
index 67c2448..4d35aae 100644
--- a/src/cobalt/version.h
+++ b/src/cobalt/version.h
@@ -35,6 +35,6 @@
 //                  release is cut.
 //.
 
-#define COBALT_VERSION "22.lts.3"
+#define COBALT_VERSION "22.lts.4"
 
 #endif  // COBALT_VERSION_H_
diff --git a/src/components/update_client/cobalt_slot_management.cc b/src/components/update_client/cobalt_slot_management.cc
index 0c601d7..6abee58 100644
--- a/src/components/update_client/cobalt_slot_management.cc
+++ b/src/components/update_client/cobalt_slot_management.cc
@@ -14,8 +14,10 @@
 
 #include "components/update_client/cobalt_slot_management.h"
 
+#include <algorithm>
 #include <vector>
 
+#include "base/files/file_util.h"
 #include "base/values.h"
 #include "cobalt/updater/utils.h"
 #include "components/update_client/utils.h"
@@ -37,6 +39,32 @@
   return !bad_app_key_file_path.empty() &&
          SbFileExists(bad_app_key_file_path.c_str());
 }
+
+uint64_t ComputeSlotSize(
+    const CobaltExtensionInstallationManagerApi* installation_api,
+    int index) {
+  if (!installation_api) {
+    LOG(WARNING) << "ComputeSlotSize: "
+                 << "Missing installation manager extension.";
+    return 0;
+  }
+  std::vector<char> installation_path(kSbFileMaxPath);
+  if (installation_api->GetInstallationPath(index, installation_path.data(),
+                                            kSbFileMaxPath) == IM_EXT_ERROR) {
+    LOG(WARNING) << "ComputeSlotSize: "
+                 << "Failed to get installation path for slot " << index;
+    return 0;
+  }
+  int64_t slot_size =
+      base::ComputeDirectorySize(base::FilePath(installation_path.data()));
+  LOG(INFO) << "ComputeSlotSize: slot_size=" << slot_size;
+  if (slot_size <= 0) {
+    LOG(WARNING) << "ComputeSlotSize: "
+                 << "Failed to compute slot " << index << " size";
+    return 0;
+  }
+  return slot_size;
+}
 }  // namespace
 
 CobaltSlotManagement::CobaltSlotManagement() : installation_api_(nullptr) {}
@@ -61,11 +89,15 @@
     return false;
   }
   app_key_ = app_key;
+  initialized_ = true;
   return true;
 }
 
 bool CobaltSlotManagement::SelectSlot(base::FilePath* dir) {
-  SB_DCHECK(installation_api_);
+  SB_DCHECK(initialized_);
+  if (!initialized_) {
+    return false;
+  }
   LOG(INFO) << "CobaltSlotManagement::SelectSlot";
   int max_slots = installation_api_->GetMaxNumberInstallations();
   if (max_slots == IM_EXT_ERROR) {
@@ -97,16 +129,18 @@
     base::FilePath installation_dir(
         std::string(installation_path.begin(), installation_path.end()));
 
-    // Cleanup expired drain files.
-    DrainFileClear(installation_dir.value().c_str(), app_key_.c_str(), true);
+    // Cleanup all expired files from all apps.
+    DrainFileClearExpired(installation_dir.value().c_str());
 
     // Cleanup all drain files from the current app.
-    DrainFileRemove(installation_dir.value().c_str(), app_key_.c_str());
+    DrainFileClearForApp(installation_dir.value().c_str(), app_key_.c_str());
+
     base::Version version =
         cobalt::updater::ReadEvergreenVersion(installation_dir);
     if (!version.IsValid()) {
       LOG(INFO) << "CobaltSlotManagement::SelectSlot installed version invalid";
-      if (!DrainFileDraining(installation_dir.value().c_str(), "")) {
+      if (!DrainFileIsAnotherAppDraining(installation_dir.value().c_str(),
+                                         app_key_.c_str())) {
         LOG(INFO) << "CobaltSlotManagement::SelectSlot not draining";
         // Found empty slot.
         slot_candidate = i;
@@ -119,7 +153,8 @@
       }
     } else if ((!slot_candidate_version.IsValid() ||
                 slot_candidate_version > version)) {
-      if (!DrainFileDraining(installation_dir.value().c_str(), "")) {
+      if (!DrainFileIsAnotherAppDraining(installation_dir.value().c_str(),
+                                         app_key_.c_str())) {
         // Found a slot with older version that's not draining.
         LOG(INFO) << "CobaltSlotManagement::SelectSlot slot candidate: " << i;
         slot_candidate_version = version;
@@ -147,7 +182,10 @@
 }
 
 bool CobaltSlotManagement::ConfirmSlot(const base::FilePath& dir) {
-  SB_DCHECK(installation_api_);
+  SB_DCHECK(initialized_);
+  if (!initialized_) {
+    return false;
+  }
   LOG(INFO) << "CobaltSlotManagement::ConfirmSlot ";
   if (!DrainFileRankAndCheck(dir.value().c_str(), app_key_.c_str())) {
     LOG(INFO) << "CobaltSlotManagement::ConfirmSlot: failed to lock slot ";
@@ -170,13 +208,49 @@
   return true;
 }
 
-void CobaltSlotManagement::CleanupAllDrainFiles(const base::FilePath& dir) {
-  if (!dir.empty() && !app_key_.empty()) {
-    DrainFileRemove(dir.value().c_str(), app_key_.c_str());
+void CobaltSlotManagement::CleanupAllDrainFiles() {
+  SB_DCHECK(initialized_);
+  if (!initialized_) {
+    return;
+  }
+
+  int max_slots = installation_api_->GetMaxNumberInstallations();
+  if (max_slots == IM_EXT_ERROR) {
+    LOG(ERROR) << "CobaltSlotManagement::CleanupAllDrainFile: Failed to get "
+                  "max number of slots";
+    return;
+  }
+
+  // Iterate over all writeable installation slots.
+  for (int i = 1; i < max_slots; i++) {
+    LOG(INFO) << "CobaltSlotManagement::CleanupAllDrainFile iterating slot="
+              << i;
+    std::vector<char> installation_path(kSbFileMaxPath);
+    if (installation_api_->GetInstallationPath(i, installation_path.data(),
+                                               installation_path.size()) ==
+        IM_EXT_ERROR) {
+      LOG(ERROR) << "CobaltSlotManagement::CleanupAllDrainFile: Failed to get "
+                    "installation path for slot="
+                 << i;
+      continue;
+    }
+
+    LOG(INFO)
+        << "CobaltSlotManagement::CleanupAllDrainFile: installation_path = "
+        << installation_path.data();
+
+    base::FilePath installation_dir(
+        std::string(installation_path.begin(), installation_path.end()));
+
+    DrainFileClearForApp(installation_dir.value().c_str(), app_key_.c_str());
   }
 }
 
 int CobaltSlotManagement::GetInstallationIndex() {
+  SB_DCHECK(initialized_);
+  if (!initialized_) {
+    return false;
+  }
   return installation_index_;
 }
 
@@ -191,7 +265,7 @@
   if (!starboard::loader_app::CreateAppKeyFile(good_app_key_file_path)) {
     LOG(WARNING) << "Failed to create good app key file";
   }
-  DrainFileRemove(dir.c_str(), app_key.c_str());
+  DrainFileClearForApp(dir.c_str(), app_key.c_str());
   int ret =
       installation_api->RequestRollForwardToInstallation(installation_index);
   if (ret == IM_EXT_ERROR) {
@@ -247,18 +321,24 @@
     base::Version installed_version =
         cobalt::updater::ReadEvergreenVersion(installation_dir);
 
+    std::string good_app_key_file_path =
+        starboard::loader_app::GetGoodAppKeyFilePath(
+            installation_dir.value().c_str(), app_key);
     if (!installed_version.IsValid()) {
       LOG(WARNING) << "CobaltQuickInstallation: invalid version ";
       continue;
     } else if (slot_candidate_version < installed_version &&
                current_version < installed_version &&
-               !DrainFileDraining(installation_dir.value().c_str(), "") &&
+               !DrainFileIsAnotherAppDraining(installation_dir.value().c_str(),
+                                              app_key) &&
                !CheckBadFileExists(installation_dir.value().c_str(), app_key) &&
+               !SbFileExists(good_app_key_file_path.c_str()) &&
                starboard::loader_app::AnyGoodAppKeyFile(
                    installation_dir.value().c_str())) {
       // Found a slot with newer version than the current version that's not
       // draining, and no bad file of current app exists, and a good file
-      // exists. The final candidate is the newest version of the valid
+      // exists from a another app, but not from the current app.
+      // The final candidate is the newest version of the valid
       // candidates.
       LOG(INFO) << "CobaltQuickInstallation: slot candidate: " << i;
       slot_candidate_version = installed_version;
@@ -277,4 +357,52 @@
   return false;
 }
 
+bool CobaltSkipUpdate(
+    const CobaltExtensionInstallationManagerApi* installation_api,
+    uint64_t min_free_space_bytes,
+    int64_t free_space_bytes,
+    uint64_t installation_cleanup_size) {
+  LOG(INFO) << "CobaltSkipUpdate: "
+            << " min_free_space_bytes=" << min_free_space_bytes
+            << " free_space_bytes=" << free_space_bytes
+            << " installation_cleanup_size=" << installation_cleanup_size;
+
+  if (free_space_bytes < 0) {
+    LOG(WARNING) << "CobaltSkipUpdate: "
+                 << "Unable to determine free space";
+    return false;
+  }
+
+  if (free_space_bytes + installation_cleanup_size < min_free_space_bytes) {
+    LOG(WARNING) << "CobaltSkipUpdate: Not enough free space";
+    return true;
+  }
+
+  return false;
+}
+
+uint64_t CobaltInstallationCleanupSize(
+    const CobaltExtensionInstallationManagerApi* installation_api) {
+  if (!installation_api) {
+    LOG(WARNING) << "CobaltInstallationCleanupSize: "
+                 << "Missing installation manager extension.";
+    return 0;
+  }
+  int max_slots = installation_api->GetMaxNumberInstallations();
+  if (max_slots == IM_EXT_ERROR) {
+    LOG(ERROR)
+        << "CobaltInstallationCleanupSize: Failed to get max number of slots.";
+    return 0;
+  }
+  // Ignore the system slot 0 and start with slot 1.
+  uint64_t min_slot_size = ComputeSlotSize(installation_api, 1);
+  for (int i = 2; i < max_slots; i++) {
+    uint64_t slot_size = ComputeSlotSize(installation_api, i);
+    if (slot_size < min_slot_size) {
+      min_slot_size = slot_size;
+    }
+  }
+
+  return min_slot_size;
+}
 }  // namespace update_client
diff --git a/src/components/update_client/cobalt_slot_management.h b/src/components/update_client/cobalt_slot_management.h
index e5d185c..8eb40c9 100644
--- a/src/components/update_client/cobalt_slot_management.h
+++ b/src/components/update_client/cobalt_slot_management.h
@@ -52,12 +52,13 @@
   int GetInstallationIndex();
 
   // Cleanup all drain files of the current app.
-  void CleanupAllDrainFiles(const base::FilePath& dir);
+  void CleanupAllDrainFiles();
 
  private:
   int installation_index_ = IM_EXT_INVALID_INDEX;
   const CobaltExtensionInstallationManagerApi* installation_api_;
   std::string app_key_;
+  bool initialized_ = false;
 };
 
 // Creates a good file and rolls forward to the installation in
@@ -76,6 +77,22 @@
     const CobaltExtensionInstallationManagerApi* installation_api,
     const base::Version& current_version);
 
+// Computes whether Cobalt should skip the update based on the
+// |min_free_space_bytes|, the |free_space_bytes| and the amount of
+// space that can be recovered from |installation_cleanup_size|.
+// The default behavior is to NOT skip unless the measurements
+// show that there isn't enough space to perform the update.
+bool CobaltSkipUpdate(
+    const CobaltExtensionInstallationManagerApi* installation_api,
+    uint64_t min_free_space_bytes,
+    int64_t free_space_bytes,
+    uint64_t installation_cleanup_size);
+
+// Computes the installation cleanup size by taking the min space
+// from all the installation slots excluding the system slot 0.
+uint64_t CobaltInstallationCleanupSize(
+    const CobaltExtensionInstallationManagerApi* installation_api);
+
 }  // namespace update_client
 
 #endif  // COMPONENTS_UPDATE_CLIENT_COBALT_SLOT_MANAGEMENT_H_
diff --git a/src/components/update_client/cobalt_slot_management_test.cc b/src/components/update_client/cobalt_slot_management_test.cc
index 55256de..44463bd 100644
--- a/src/components/update_client/cobalt_slot_management_test.cc
+++ b/src/components/update_client/cobalt_slot_management_test.cc
@@ -14,7 +14,11 @@
 
 #include "components/update_client/cobalt_slot_management.h"
 
+#include <algorithm>
+#include <vector>
+
 #include "base/strings/string_util.h"
+#include "cobalt/extension/free_space.h"
 #include "starboard/common/file.h"
 #include "starboard/loader_app/app_key_files.h"
 #include "starboard/loader_app/drain_file.h"
@@ -109,7 +113,7 @@
   ASSERT_TRUE(cobalt_slot_management.Init(api_));
   base::FilePath dir;
   ASSERT_TRUE(cobalt_slot_management.SelectSlot(&dir));
-  ASSERT_TRUE(DrainFileDraining(dir.value().c_str(), kTestAppKey1));
+  ASSERT_TRUE(DrainFileIsAppDraining(dir.value().c_str(), kTestAppKey1));
   ASSERT_TRUE(base::EndsWith(dir.value(), "installation_1",
                              base::CompareCase::SENSITIVE));
 }
@@ -148,7 +152,7 @@
   ASSERT_TRUE(cobalt_slot_management.Init(api_));
   base::FilePath dir;
   cobalt_slot_management.SelectSlot(&dir);
-  ASSERT_TRUE(DrainFileDraining(dir.value().c_str(), kTestAppKey1));
+  ASSERT_TRUE(DrainFileIsAppDraining(dir.value().c_str(), kTestAppKey1));
   LOG(INFO) << "dir=" << dir;
 
   ASSERT_TRUE(base::EndsWith(dir.value(), "installation_2",
@@ -174,7 +178,7 @@
   ASSERT_TRUE(base::EndsWith(dir.value(), "installation_1",
                              base::CompareCase::SENSITIVE));
 
-  ASSERT_TRUE(DrainFileDraining(dir.value().c_str(), kTestAppKey1));
+  ASSERT_TRUE(DrainFileIsAppDraining(dir.value().c_str(), kTestAppKey1));
 
   ASSERT_TRUE(cobalt_slot_management.ConfirmSlot(dir));
 
@@ -182,6 +186,19 @@
   ASSERT_EQ(IM_MAX_NUM_TRIES, ImGetInstallationNumTriesLeft(1));
 }
 
+TEST_F(CobaltSlotManagementTest, CleanupAllDrainFiles) {
+  if (!storage_path_implemented_) {
+    return;
+  }
+  CobaltSlotManagement cobalt_slot_management;
+  ASSERT_TRUE(cobalt_slot_management.Init(api_));
+  base::FilePath dir;
+  ASSERT_TRUE(cobalt_slot_management.SelectSlot(&dir));
+  ASSERT_TRUE(DrainFileIsAppDraining(dir.value().c_str(), kTestAppKey1));
+  cobalt_slot_management.CleanupAllDrainFiles();
+  ASSERT_FALSE(DrainFileIsAppDraining(dir.value().c_str(), kTestAppKey1));
+}
+
 TEST_F(CobaltSlotManagementTest, CobaltFinishInstallation) {
   std::string slot_path = storage_path_;
   slot_path += kSbFileSepString;
@@ -229,6 +246,55 @@
   base::Version version("1.0.0");
   ASSERT_FALSE(CobaltQuickUpdate(api_, version));
 }
+
+TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateNoInstallationMoreSpace) {
+  ASSERT_FALSE(
+      CobaltSkipUpdate(api_, 1024 /* min */, 1025 /* free */, 0 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateNoInstallationExactSpace) {
+  ASSERT_FALSE(
+      CobaltSkipUpdate(api_, 1024 /* min */, 1024 /* free */, 0 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateNoInstallationNotEnoughSpace) {
+  ASSERT_TRUE(
+      CobaltSkipUpdate(api_, 1024 /* min */, 1023 /* free */, 0 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateWithInstallationMoreSpace) {
+  ASSERT_FALSE(
+      CobaltSkipUpdate(api_, 1024 /* min */, 1024 /* free */, 1 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateWithInstallationExactSpace) {
+  ASSERT_FALSE(
+      CobaltSkipUpdate(api_, 1024 /* min */, 1023 /* free */, 1 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest,
+       CobaltSkipUpdateWithInstallationNotEnoughSpace) {
+  ASSERT_TRUE(
+      CobaltSkipUpdate(api_, 1024 /* min */, 1022 /* free */, 1 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest, CobaltInstallationCleanupSizeNoInstallation) {
+  uint64_t size = CobaltInstallationCleanupSize(api_);
+  ASSERT_EQ(size, 0);
+}
+
+TEST_F(CobaltSlotManagementTest,
+       CobaltInstallationCleanupSizeTwoInstallations) {
+  int len1 = strlen(kManifestV1);
+  int len2 = strlen(kManifestV2);
+  ASSERT_NE(len1, len2);
+
+  CreateManifest("installation_1", kManifestV2, len1);
+  CreateManifest("installation_2", kManifestV1, len2);
+
+  uint64_t size = CobaltInstallationCleanupSize(api_);
+  ASSERT_EQ(size, std::min(len1, len2));
+}
 }  // namespace
 
 }  // namespace update_client
diff --git a/src/components/update_client/component.cc b/src/components/update_client/component.cc
index 8b4fa5d..59c0637 100644
--- a/src/components/update_client/component.cc
+++ b/src/components/update_client/component.cc
@@ -121,6 +121,9 @@
     const std::string& public_key,
 #if defined(STARBOARD)
     const int installation_index,
+    PersistedData* metadata,
+    const std::string& id,
+    const std::string& version,
 #endif
     const std::string& fingerprint,
     scoped_refptr<CrxInstaller> installer,
@@ -170,8 +173,16 @@
       // TODO: add correct error code.
       install_error = InstallError::GENERIC_ERROR;
     } else {
-      if (!CobaltFinishInstallation(installation_api, installation_index,
+      if (CobaltFinishInstallation(installation_api, installation_index,
                                     unpack_path.value(), app_key)) {
+        // Write the version of the unpacked update package to the persisted data.
+        if (metadata != nullptr) {
+          main_task_runner->PostTask(
+              FROM_HERE, base::BindOnce(&PersistedData::SetLastInstalledVersion,
+                                        base::Unretained(metadata), id, version));
+        }
+      } else {
+
         // TODO: add correct error code.
         install_error = InstallError::GENERIC_ERROR;
       }
@@ -215,8 +226,15 @@
     // When there is an error unpacking the downloaded CRX, such as a failure to
     // verify the package, we should remember to clear out any drain files.
     if (base::DirectoryExists(crx_path.DirName())) {
-      CobaltSlotManagement cobalt_slot_management;
-      cobalt_slot_management.CleanupAllDrainFiles(crx_path.DirName());
+      const auto installation_api =
+        static_cast<const CobaltExtensionInstallationManagerApi*>(
+          SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
+      if (installation_api) {
+        CobaltSlotManagement cobalt_slot_management;
+        if (cobalt_slot_management.Init(installation_api)) {
+          cobalt_slot_management.CleanupAllDrainFiles();
+        }
+      }
     }
 #endif
     main_task_runner->PostTask(
@@ -226,21 +244,15 @@
     return;
   }
 
-#if defined(STARBOARD)
-  // Write the version of the unpacked update package to the persisted data.
-  if (metadata != nullptr) {
-    main_task_runner->PostTask(
-        FROM_HERE, base::BindOnce(&PersistedData::SetLastUnpackedVersion,
-                                  base::Unretained(metadata), id, version));
-  }
-#endif
-
   base::PostTaskWithTraits(
       FROM_HERE, kTaskTraits,
       base::BindOnce(&InstallOnBlockingTaskRunner, main_task_runner,
                      result.unpack_path, result.public_key,
 #if defined(STARBOARD)
                      installation_index,
+                     metadata,
+                     id,
+                     version,
 #endif
                      fingerprint, installer, std::move(callback)));
 }
@@ -717,8 +729,15 @@
   DCHECK_NE(ErrorCategory::kNone, component.error_category_);
   DCHECK_NE(0, component.error_code_);
 
+#if defined(STARBOARD)
+  // Create an event when the server response included an update, or when it's
+  // an update check error, like quick roll-forward or out of space
+  if (component.IsUpdateAvailable() ||
+      component.error_category_ == ErrorCategory::kUpdateCheck)
+#else
   // Create an event only when the server response included an update.
   if (component.IsUpdateAvailable())
+#endif
     component.AppendEvent(component.MakeEventUpdateComplete());
 
   EndState();
diff --git a/src/components/update_client/configurator.h b/src/components/update_client/configurator.h
index 16fd982..e4619e9 100644
--- a/src/components/update_client/configurator.h
+++ b/src/components/update_client/configurator.h
@@ -180,6 +180,8 @@
   // Compare and swap the is_channel_changed flag.
   virtual void CompareAndSwapChannelChanged(int old_value, int new_value) = 0;
 
+  virtual void SetMinFreeSpaceBytes(uint64_t bytes) = 0;
+  virtual uint64_t GetMinFreeSpaceBytes() = 0;
 #endif
 
  protected:
diff --git a/src/components/update_client/persisted_data.cc b/src/components/update_client/persisted_data.cc
index 2928b2e..0e80750 100644
--- a/src/components/update_client/persisted_data.cc
+++ b/src/components/update_client/persisted_data.cc
@@ -81,7 +81,7 @@
 }
 
 #if defined(STARBOARD)
-std::string PersistedData::GetLastUnpackedVersion(const std::string& id) const {
+std::string PersistedData::GetLastInstalledVersion(const std::string& id) const {
   return GetString(id, "version");
 }
 std::string PersistedData::GetUpdaterChannel(const std::string& id) const {
@@ -145,7 +145,7 @@
 }
 
 #if defined(STARBOARD)
-void PersistedData::SetLastUnpackedVersion(const std::string& id,
+void PersistedData::SetLastInstalledVersion(const std::string& id,
                                            const std::string& version) {
   SetString(id, "version", version);
 }
diff --git a/src/components/update_client/persisted_data.h b/src/components/update_client/persisted_data.h
index 84efa3e..a59b92c 100644
--- a/src/components/update_client/persisted_data.h
+++ b/src/components/update_client/persisted_data.h
@@ -51,10 +51,10 @@
   int GetDateLastActive(const std::string& id) const;
 
 #if defined(STARBOARD)
-  // Returns the version of the update that was last successfully unpacked for
+  // Returns the version of the update that was last successfully installed for
   // the specified |id|. "" indicates that there is no recorded version value
   // for the |id|.
-  std::string GetLastUnpackedVersion(const std::string& id) const;
+  std::string GetLastInstalledVersion(const std::string& id) const;
 
   // Returns the updater channel that is set for the specified |id|. ""
   // indicates that there is no recorded updater channel value for the |id|.
@@ -82,9 +82,9 @@
   void SetDateLastActive(const std::vector<std::string>& ids, int datenum);
 
 #if defined(STARBOARD)
-  // Records the version of the update that is successfully unpacked for
+  // Records the version of the update that is successfully installed for
   // the specified |id|.
-  void SetLastUnpackedVersion(const std::string& id,
+  void SetLastInstalledVersion(const std::string& id,
                               const std::string& version);
 
   // Records the updater channel that is set for the specified |id|.
diff --git a/src/components/update_client/task_update.cc b/src/components/update_client/task_update.cc
index b4894b7..ea331b4 100644
--- a/src/components/update_client/task_update.cc
+++ b/src/components/update_client/task_update.cc
@@ -43,6 +43,10 @@
   DCHECK(thread_checker_.CalledOnValidThread());
 #if defined(STARBOARD)
   LOG(INFO) << "TaskUpdate::Run begin";
+  if(is_completed_) {
+    LOG(WARNING) << "TaskUpdate::Run already completed";
+    return;
+  }
 #endif
 
   if (ids_.empty()) {
diff --git a/src/components/update_client/test_configurator.h b/src/components/update_client/test_configurator.h
index 6a9e5eb..9b37687 100644
--- a/src/components/update_client/test_configurator.h
+++ b/src/components/update_client/test_configurator.h
@@ -135,6 +135,10 @@
 
   std::string GetPreviousUpdaterStatus() const override { return ""; }
   void SetPreviousUpdaterStatus(const std::string& status) override {}
+
+  void SetMinFreeSpaceBytes(uint64_t bytes) override {}
+
+  uint64_t GetMinFreeSpaceBytes() override { return 0; }
 #else
   network::TestURLLoaderFactory* test_url_loader_factory() {
     return &test_url_loader_factory_;
diff --git a/src/components/update_client/update_checker.cc b/src/components/update_client/update_checker.cc
index 4951abf..124fe58 100644
--- a/src/components/update_client/update_checker.cc
+++ b/src/components/update_client/update_checker.cc
@@ -22,11 +22,11 @@
 #include "base/threading/thread_checker.h"
 #if defined(STARBOARD)
 #include "base/threading/thread_id_name_manager.h"
+#include "cobalt/extension/free_space.h"
 #endif
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #if defined(STARBOARD)
-#include "cobalt/extension/installation_manager.h"
 #include "cobalt/updater/utils.h"
 #include "components/update_client/cobalt_slot_management.h"
 #endif
@@ -102,7 +102,8 @@
       const base::flat_map<std::string, std::string>& additional_attributes,
       bool enabled_component_updates);
 #if defined(STARBOARD)
-  void Cancel();
+  void Cancel() ;
+  virtual bool SkipUpdate(const CobaltExtensionInstallationManagerApi* installation_api);
 #endif
   void OnRequestSenderComplete(int error,
                                const std::string& response,
@@ -228,22 +229,20 @@
 
     base::Version current_version = crx_component->version;
 #if defined(STARBOARD)
-    std::string unpacked_version =
-        GetPersistedData()->GetLastUnpackedVersion(app_id);
-    // If the version of the last unpacked update package is higher than the
-    // version of the running binary, use the former to indicate the current
-    // update version in the update check request.
-    if (!unpacked_version.empty() &&
-        base::Version(unpacked_version).CompareTo(current_version) > 0) {
-      current_version = base::Version(unpacked_version);
-    }
-
     // Check if there is an available update already for quick roll-forward
     auto installation_api =
         static_cast<const CobaltExtensionInstallationManagerApi*>(
             SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
     if (!installation_api) {
-      LOG(ERROR) << "Failed to get installation manager extension.";
+      LOG(ERROR) << "UpdaterChecker: "
+                 << "Failed to get installation manager extension.";
+      return;
+    }
+
+    if (SkipUpdate(installation_api)) {
+      LOG(WARNING) << "UpdaterChecker is skipping";
+      UpdateCheckFailed(ErrorCategory::kUpdateCheck,
+        static_cast<int>(UpdateCheckError::OUT_OF_SPACE), -1);
       return;
     }
 
@@ -259,6 +258,15 @@
       return;
     }
 
+    std::string last_installed_version =
+        GetPersistedData()->GetLastInstalledVersion(app_id);
+    // If the version of the last installed update package is higher than the
+    // version of the running binary, use the former to indicate the current
+    // update version in the update check request.
+    if (!last_installed_version.empty() &&
+        base::Version(last_installed_version).CompareTo(current_version) > 0) {
+      current_version = base::Version(last_installed_version);
+    }
 // If the quick roll forward update slot candidate doesn't exist, continue
 // with update check.
 #endif
@@ -305,6 +313,24 @@
     request_sender_->Cancel();
   }
 }
+
+bool UpdateCheckerImpl::SkipUpdate(
+  const CobaltExtensionInstallationManagerApi* installation_api) {
+  auto free_space_ext = static_cast<const CobaltExtensionFreeSpaceApi*>(
+      SbSystemGetExtension(kCobaltExtensionFreeSpaceName));
+  if (!installation_api) {
+    LOG(WARNING) << "UpdaterChecker::SkipUpdate: missing installation api";
+     return false;
+  }
+  if (!free_space_ext) {
+    LOG(WARNING) << "UpdaterChecker::SkipUpdate: No FreeSpace Cobalt extension";
+    return false;
+  }
+
+  return CobaltSkipUpdate(installation_api, config_->GetMinFreeSpaceBytes(),
+     free_space_ext->MeasureFreeSpace(kSbSystemPathStorageDirectory),
+     CobaltInstallationCleanupSize(installation_api)) ;
+}
 #endif
 
 void UpdateCheckerImpl::OnRequestSenderComplete(int error,
diff --git a/src/components/update_client/update_checker.h b/src/components/update_client/update_checker.h
index 78eb968..7c7c062 100644
--- a/src/components/update_client/update_checker.h
+++ b/src/components/update_client/update_checker.h
@@ -18,6 +18,10 @@
 #include "components/update_client/protocol_parser.h"
 #include "url/gurl.h"
 
+#if defined(STARBOARD)
+#include "cobalt/extension/installation_manager.h"
+#endif
+
 namespace update_client {
 
 class Configurator;
@@ -53,6 +57,7 @@
 
 #if defined(STARBOARD)
   virtual void Cancel() = 0;
+  virtual bool SkipUpdate(const CobaltExtensionInstallationManagerApi* installation_api) = 0;
 #endif
 
   static std::unique_ptr<UpdateChecker> Create(
diff --git a/src/components/update_client/update_client.cc b/src/components/update_client/update_client.cc
index 0ba76cd..9800348 100644
--- a/src/components/update_client/update_client.cc
+++ b/src/components/update_client/update_client.cc
@@ -79,7 +79,8 @@
   DCHECK(tasks_.empty());
 
 #if defined(STARBOARD)
-  LOG(INFO) << "UpdateClientImpl::~UpdateClientImpl: task_queue_.size=" << task_queue_.size() << " tasks.size=" << tasks_.size();
+  LOG(INFO) << "UpdateClientImpl::~UpdateClientImpl: task_queue_.size="
+            << task_queue_.size() << " tasks.size=" << tasks_.size();
 #endif
 
   config_ = nullptr;
@@ -175,6 +176,12 @@
 void UpdateClientImpl::NotifyObservers(Observer::Events event,
                                        const std::string& id) {
   DCHECK(thread_checker_.CalledOnValidThread());
+#if defined(STARBOARD)
+  if (is_stopped_) {
+    LOG(WARNING) << "UpdateClientImpl::NotifyObservers: already stopped";
+    return;
+  }
+#endif
   for (auto& observer : observer_list_)
     observer.OnEvent(event, id);
 }
diff --git a/src/components/update_client/update_client_errors.h b/src/components/update_client/update_client_errors.h
index db08a24..4193ace 100644
--- a/src/components/update_client/update_client_errors.h
+++ b/src/components/update_client/update_client_errors.h
@@ -105,6 +105,7 @@
   // Using 21 that doesn't conflict with the exsiting error codes and stays away
   // from the other codes below 20.
   QUICK_ROLL_FORWARD = 21,
+  OUT_OF_SPACE = 22,
 };
 #endif
 
diff --git a/src/components/update_client/url_fetcher_downloader.cc b/src/components/update_client/url_fetcher_downloader.cc
index 4536da6..1ae692c 100644
--- a/src/components/update_client/url_fetcher_downloader.cc
+++ b/src/components/update_client/url_fetcher_downloader.cc
@@ -194,7 +194,7 @@
 #endif
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 #if defined(STARBOARD)
-  cobalt_slot_management_.CleanupAllDrainFiles(download_dir_);
+  cobalt_slot_management_.CleanupAllDrainFiles();
 #endif
   Result result;
 #if defined(STARBOARD)
diff --git a/src/glimp/egl/display.cc b/src/glimp/egl/display.cc
index f338b51..10b9fad 100644
--- a/src/glimp/egl/display.cc
+++ b/src/glimp/egl/display.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Google Inc. All Rights Reserved.
+ * Copyright 2015 The Cobalt Authors. All Rights Reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,10 +22,38 @@
 #include "glimp/egl/config.h"
 #include "glimp/egl/error.h"
 #include "starboard/common/log.h"
+#include "starboard/event.h"
 
 namespace glimp {
 namespace egl {
 
+const SbTime kSubmitDoneDelay = kSbTimeSecond / 60;
+
+// Don't repeat the submitDone callback during suspension
+// until specified by eglTerminate.
+bool Display::repeat_submit_done_during_suspend = false;
+
+namespace {
+void ScheduleSubmitDoneCallback(void* context) {
+  if (Display::repeat_submit_done_during_suspend) {
+    DisplayImpl::CallSubmitDone();
+    Display::RepeatSubmitDoneDuringSuspend();
+  }
+}
+}  // namespace
+
+void Display::RepeatSubmitDoneDuringSuspend() {
+  static SbEventId submit_done_repeating_callback_event = kSbEventIdInvalid;
+  if (Display::repeat_submit_done_during_suspend) {
+    submit_done_repeating_callback_event =
+        SbEventSchedule(&ScheduleSubmitDoneCallback, NULL, kSubmitDoneDelay);
+  } else {
+    if (submit_done_repeating_callback_event != kSbEventIdInvalid) {
+      SbEventCancel(submit_done_repeating_callback_event);
+    }
+  }
+}
+
 Display::Display(nb::scoped_ptr<DisplayImpl> display_impl)
     : impl_(display_impl.Pass()) {}
 
diff --git a/src/glimp/egl/display.h b/src/glimp/egl/display.h
index 8fc432d..b9333d6 100644
--- a/src/glimp/egl/display.h
+++ b/src/glimp/egl/display.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Google Inc. All Rights Reserved.
+ * Copyright 2015 The Cobalt Authors. All Rights Reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -35,6 +35,9 @@
 // upon construction.
 class Display {
  public:
+  static void RepeatSubmitDoneDuringSuspend();
+  static bool repeat_submit_done_during_suspend;
+
   // In order to create a display, it must have a platform-specific
   // implementation injected into it, where many methods will forward to.
   explicit Display(nb::scoped_ptr<DisplayImpl> display_impl);
diff --git a/src/glimp/egl/display_impl.h b/src/glimp/egl/display_impl.h
index d98efb9..711f024 100644
--- a/src/glimp/egl/display_impl.h
+++ b/src/glimp/egl/display_impl.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Google Inc. All Rights Reserved.
+ * Copyright 2015 The Cobalt Authors. All Rights Reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -54,6 +54,9 @@
   // To be implemented by each implementing platform.
   static nb::scoped_ptr<DisplayImpl> Create(EGLNativeDisplayType display_id);
 
+  // Submit done call
+  static void CallSubmitDone();
+
   // Returns the EGL major and minor versions, if they are not NULL.
   // Called by eglInitialize():
   //   https://www.khronos.org/registry/egl/sdk/docs/man/html/eglInitialize.xhtml
diff --git a/src/glimp/entry_points/egl.cc b/src/glimp/entry_points/egl.cc
index 4b6d649..71ad8be 100644
--- a/src/glimp/entry_points/egl.cc
+++ b/src/glimp/entry_points/egl.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Google Inc. All Rights Reserved.
+ * Copyright 2015 The Cobalt Authors. All Rights Reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -197,6 +197,8 @@
   display->GetVersionInfo(major, minor);
 
   egl::SetError(EGL_SUCCESS);
+  egl::Display::repeat_submit_done_during_suspend = false;
+  egl::Display::RepeatSubmitDoneDuringSuspend();
   return true;
 }
 
@@ -264,6 +266,8 @@
   egl::DisplayRegistry::TerminateDisplay(dpy);
 
   egl::SetError(EGL_SUCCESS);
+  egl::Display::repeat_submit_done_during_suspend = true;
+  egl::Display::RepeatSubmitDoneDuringSuspend();
   return true;
 }
 
diff --git a/src/net/cookies/cookie_monster_unittest.cc b/src/net/cookies/cookie_monster_unittest.cc
index c13af89..b137fb5 100644
--- a/src/net/cookies/cookie_monster_unittest.cc
+++ b/src/net/cookies/cookie_monster_unittest.cc
@@ -510,8 +510,8 @@
     EXPECT_EQ(expected_secure, num_secure);
 
     // Validate each priority.
-    size_t expected_count[3] = {
-        expected_low_count, expected_medium_count, expected_high_count};
+    size_t expected_count[3] = {expected_low_count, expected_medium_count,
+                                expected_high_count};
     for (int i = 0; i < 3; ++i) {
       size_t num_for_priority =
           surviving_id_list[0][i].size() + surviving_id_list[1][i].size();
@@ -1049,7 +1049,7 @@
 
 TEST_F(DeferredCookieTaskTest, DeferredGetCookieList) {
   DeclareLoadedCookie(http_www_foo_.url(),
-                      "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+                      "X=1; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
                       Time::Now() + TimeDelta::FromDays(3));
 
   MockGetCookieListCallback get_cookie_list_callback;
@@ -1141,7 +1141,7 @@
 
 TEST_F(DeferredCookieTaskTest, DeferredGetAllCookies) {
   DeclareLoadedCookie(http_www_foo_.url(),
-                      "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+                      "X=1; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
                       Time::Now() + TimeDelta::FromDays(3));
 
   MockGetCookieListCallback get_cookie_list_callback;
@@ -1163,7 +1163,7 @@
 
 TEST_F(DeferredCookieTaskTest, DeferredGetAllForUrlCookies) {
   DeclareLoadedCookie(http_www_foo_.url(),
-                      "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+                      "X=1; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
                       Time::Now() + TimeDelta::FromDays(3));
 
   MockGetCookieListCallback get_cookie_list_callback;
@@ -1188,7 +1188,7 @@
 
 TEST_F(DeferredCookieTaskTest, DeferredGetAllForUrlWithOptionsCookies) {
   DeclareLoadedCookie(http_www_foo_.url(),
-                      "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+                      "X=1; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
                       Time::Now() + TimeDelta::FromDays(3));
 
   MockGetCookieListCallback get_cookie_list_callback;
@@ -1312,7 +1312,7 @@
 // being dispatched go to the end of the queue.
 TEST_F(DeferredCookieTaskTest, DeferredTaskOrder) {
   DeclareLoadedCookie(http_www_foo_.url(),
-                      "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+                      "X=1; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
                       Time::Now() + TimeDelta::FromDays(3));
 
   MockGetCookieListCallback get_cookie_list_callback;
@@ -1367,7 +1367,7 @@
   // Create a persistent cookie.
   EXPECT_TRUE(SetCookie(
       cm.get(), http_www_foo_.url(),
-      std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+      std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-62 22:50:13 GMT"));
   ASSERT_EQ(1u, store->commands().size());
   EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[0].type);
 
@@ -1762,20 +1762,20 @@
   // the import.
 
   AddCookieToList(GURL("http://www.foo.com"),
-                  "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+                  "X=1; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
                   Time::Now() + TimeDelta::FromDays(3), &initial_cookies);
 
   AddCookieToList(GURL("http://www.foo.com"),
-                  "X=2; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+                  "X=2; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
                   Time::Now() + TimeDelta::FromDays(1), &initial_cookies);
 
   // ===> This one is the WINNER (biggest creation time).  <====
   AddCookieToList(GURL("http://www.foo.com"),
-                  "X=3; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+                  "X=3; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
                   Time::Now() + TimeDelta::FromDays(4), &initial_cookies);
 
   AddCookieToList(GURL("http://www.foo.com"),
-                  "X=4; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+                  "X=4; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
                   Time::Now(), &initial_cookies);
 
   // Insert 2 cookies with name "X" on path "/2", with varying creation
@@ -1783,16 +1783,16 @@
 
   // ===> This one is the WINNER (biggest creation time).  <====
   AddCookieToList(GURL("http://www.foo.com"),
-                  "X=a1; path=/2; expires=Mon, 18-Apr-22 22:50:14 GMT",
+                  "X=a1; path=/2; expires=Mon, 18-Apr-62 22:50:14 GMT",
                   Time::Now() + TimeDelta::FromDays(9), &initial_cookies);
 
   AddCookieToList(GURL("http://www.foo.com"),
-                  "X=a2; path=/2; expires=Mon, 18-Apr-22 22:50:14 GMT",
+                  "X=a2; path=/2; expires=Mon, 18-Apr-62 22:50:14 GMT",
                   Time::Now() + TimeDelta::FromDays(2), &initial_cookies);
 
   // Insert 1 cookie with name "Y" on path "/".
   AddCookieToList(GURL("http://www.foo.com"),
-                  "Y=a; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+                  "Y=a; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
                   Time::Now() + TimeDelta::FromDays(10), &initial_cookies);
 
   // Inject our initial cookies into the mock PersistentCookieStore.
@@ -2654,7 +2654,7 @@
 
   // Add a cookie.
   EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(),
-                        "A=B; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+                        "A=B; expires=Mon, 18-Apr-62 22:50:13 GMT"));
   this->MatchCookieLines("A=B", GetCookies(cm.get(), http_www_foo_.url()));
   ASSERT_EQ(1u, store->commands().size());
   EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[0].type);
@@ -2667,13 +2667,13 @@
 
   // Add a cookie.
   EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(),
-                        "A=B; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+                        "A=B; expires=Mon, 18-Apr-62 22:50:13 GMT"));
   this->MatchCookieLines("A=B", GetCookies(cm.get(), http_www_foo_.url()));
   ASSERT_EQ(3u, store->commands().size());
   EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[2].type);
   // Overwrite it.
   EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(),
-                        "A=Foo; expires=Mon, 18-Apr-22 22:50:14 GMT"));
+                        "A=Foo; expires=Mon, 18-Apr-62 22:50:14 GMT"));
   this->MatchCookieLines("A=Foo", GetCookies(cm.get(), http_www_foo_.url()));
   ASSERT_EQ(5u, store->commands().size());
   EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[3].type);
@@ -3283,12 +3283,12 @@
   // Set up a set of cookies with a duplicate.
   std::vector<std::unique_ptr<CanonicalCookie>> initial_cookies;
   AddCookieToList(GURL("http://www.foo.com"),
-                  "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+                  "X=1; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
                   base::Time::Now() + base::TimeDelta::FromDays(3),
                   &initial_cookies);
 
   AddCookieToList(GURL("http://www.foo.com"),
-                  "X=2; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+                  "X=2; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
                   base::Time::Now() + base::TimeDelta::FromDays(1),
                   &initial_cookies);
 
diff --git a/src/net/cookies/cookie_store_unittest.h b/src/net/cookies/cookie_store_unittest.h
index 6d24c79..ed997f4 100644
--- a/src/net/cookies/cookie_store_unittest.h
+++ b/src/net/cookies/cookie_store_unittest.h
@@ -1207,7 +1207,7 @@
   // Create a persistent cookie.
   EXPECT_TRUE(this->SetCookie(
       cs, this->http_www_foo_.url(),
-      std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+      std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-62 22:50:13 GMT"));
 
   this->MatchCookieLines("A=B",
                          this->GetCookies(cs, this->http_www_foo_.url()));
@@ -1220,7 +1220,7 @@
   // Create a persistent cookie.
   EXPECT_TRUE(this->SetCookie(
       cs, this->http_www_foo_.url(),
-      std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+      std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-62 22:50:13 GMT"));
   this->MatchCookieLines("A=B",
                          this->GetCookies(cs, this->http_www_foo_.url()));
   // Delete it via Expires.
@@ -1233,13 +1233,13 @@
   // Create a persistent cookie.
   EXPECT_TRUE(this->SetCookie(
       cs, this->http_www_foo_.url(),
-      std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+      std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-62 22:50:13 GMT"));
   this->MatchCookieLines("A=B",
                          this->GetCookies(cs, this->http_www_foo_.url()));
   // Check that it is not deleted with significant enough clock skew.
   base::Time server_time;
-  EXPECT_TRUE(base::Time::FromString("Sun, 17-Apr-1977 22:50:13 GMT",
-                                     &server_time));
+  EXPECT_TRUE(
+      base::Time::FromString("Sun, 17-Apr-1977 22:50:13 GMT", &server_time));
   EXPECT_TRUE(this->SetCookieWithServerTime(
       cs, this->http_www_foo_.url(),
       std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-1977 22:50:13 GMT",
@@ -1250,7 +1250,7 @@
   // Create a persistent cookie.
   EXPECT_TRUE(this->SetCookie(
       cs, this->http_www_foo_.url(),
-      std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+      std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-62 22:50:13 GMT"));
   this->MatchCookieLines("A=B",
                          this->GetCookies(cs, this->http_www_foo_.url()));
   // Delete it via Expires, with a unix epoch of 0.
@@ -1270,7 +1270,7 @@
 
   // Set a persistent cookie.
   EXPECT_TRUE(this->SetCookie(cs, this->http_www_foo_.url(),
-                              "C=D; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+                              "C=D; expires=Mon, 18-Apr-62 22:50:13 GMT"));
 
   EXPECT_EQ(2u, this->GetAllCookies(cs).size());
 
@@ -1420,12 +1420,12 @@
   // Insert a cookie "a" for path "/path1"
   EXPECT_TRUE(this->SetCookie(cs, url_foo,
                               "a=val1; path=/path1; "
-                              "expires=Mon, 18-Apr-22 22:50:13 GMT"));
+                              "expires=Mon, 18-Apr-62 22:50:13 GMT"));
 
   // Insert a cookie "b" for path "/path1"
   EXPECT_TRUE(this->SetCookie(cs, url_foo,
                               "b=val1; path=/path1; "
-                              "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+                              "expires=Mon, 18-Apr-62 22:50:14 GMT"));
 
   // Insert a cookie "b" for path "/path1", that is httponly. This should
   // overwrite the non-http-only version.
@@ -1433,26 +1433,26 @@
   allow_httponly.set_include_httponly();
   EXPECT_TRUE(this->SetCookieWithOptions(cs, url_foo,
                                          "b=val2; path=/path1; httponly; "
-                                         "expires=Mon, 18-Apr-22 22:50:14 GMT",
+                                         "expires=Mon, 18-Apr-62 22:50:14 GMT",
                                          allow_httponly));
 
   // Insert a cookie "a" for path "/path1". This should overwrite.
   EXPECT_TRUE(this->SetCookie(cs, url_foo,
                               "a=val33; path=/path1; "
-                              "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+                              "expires=Mon, 18-Apr-62 22:50:14 GMT"));
 
   // Insert a cookie "a" for path "/path2". This should NOT overwrite
   // cookie "a", since the path is different.
   EXPECT_TRUE(this->SetCookie(cs, url_foo,
                               "a=val9; path=/path2; "
-                              "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+                              "expires=Mon, 18-Apr-62 22:50:14 GMT"));
 
   // Insert a cookie "a" for path "/path1", but this time for "chromium.org".
   // Although the name and path match, the hostnames do not, so shouldn't
   // overwrite.
   EXPECT_TRUE(this->SetCookie(cs, url_chromium,
                               "a=val99; path=/path1; "
-                              "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+                              "expires=Mon, 18-Apr-62 22:50:14 GMT"));
 
   if (TypeParam::supports_http_only) {
     this->MatchCookieLines(
@@ -1654,7 +1654,7 @@
   EXPECT_TRUE(this->SetCookie(
       cs, this->http_www_foo_.url(),
       this->http_www_foo_.Format("C=D; path=/; domain=%D;"
-                                 "expires=Mon, 18-Apr-22 22:50:13 GMT")));
+                                 "expires=Mon, 18-Apr-62 22:50:13 GMT")));
   this->MatchCookieLines("A=B; C=D",
                          this->GetCookies(cs, this->http_www_foo_.url()));
   // Delete the session cookie.
diff --git a/src/net/test/test_with_scoped_task_environment.h b/src/net/test/test_with_scoped_task_environment.h
index 4ec5586..7abb7ea 100644
--- a/src/net/test/test_with_scoped_task_environment.h
+++ b/src/net/test/test_with_scoped_task_environment.h
@@ -18,7 +18,7 @@
 namespace net {
 
 // Inherit from this class if a ScopedTaskEnvironment is needed in a test.
-// Use in class hierachies where inheritance from ::testing::Test at the same
+// Use in class hierarchies where inheritance from ::testing::Test at the same
 // time is not desirable or possible (for example, when inheriting from
 // PlatformTest at the same time).
 class WithScopedTaskEnvironment {
@@ -34,16 +34,30 @@
     return scoped_task_environment_.MainThreadHasPendingTask();
   }
 
+  // Executes all tasks that have no remaining delay. Tasks with a remaining
+  // delay greater than zero will remain enqueued, and no virtual time will
+  // elapse.
   void RunUntilIdle() { scoped_task_environment_.RunUntilIdle(); }
 
+  // Fast-forwards virtual time by |delta|, causing all tasks with a remaining
+  // delay less than or equal to |delta| to be executed. |delta| must be
+  // non-negative.
   void FastForwardBy(base::TimeDelta delta) {
     scoped_task_environment_.FastForwardBy(delta);
   }
 
+  // Fast-forwards virtual time by |delta| but not causing any task execution.
+  void AdvanceMockTickClock(base::TimeDelta delta) {
+    scoped_task_environment_.AdvanceMockTickClock(delta);
+  }
+
+  // Fast-forwards virtual time just until all tasks are executed.
   void FastForwardUntilNoTasksRemain() {
     scoped_task_environment_.FastForwardUntilNoTasksRemain();
   }
 
+  // Returns a TickClock that uses the virtual time ticks of |this| as its tick
+  // source. The returned TickClock will hold a reference to |this|.
   const base::TickClock* GetMockTickClock() WARN_UNUSED_RESULT {
     return scoped_task_environment_.GetMockTickClock();
   }
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
index d9082da..a858ea3 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
@@ -25,6 +25,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.util.Pair;
+import android.view.View;
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewParent;
 import android.widget.FrameLayout;
@@ -33,7 +34,6 @@
 import dev.cobalt.util.DisplayUtil;
 import dev.cobalt.util.Log;
 import dev.cobalt.util.UsedByNative;
-import java.security.Timestamp;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -143,6 +143,10 @@
     if (VideoSurfaceView.getCurrentSurface() != null) {
       forceCreateNewVideoSurfaceView = true;
     }
+
+    // Set the SurfaceView to fullscreen.
+    View rootView = getWindow().getDecorView();
+    setVideoSurfaceBounds(0, 0, rootView.getWidth(), rootView.getHeight());
   }
 
   @Override
@@ -298,6 +302,10 @@
   }
 
   public void setVideoSurfaceBounds(final int x, final int y, final int width, final int height) {
+    if (width == 0 || height == 0) {
+      // The SurfaceView should be covered by our UI layer in this case.
+      return;
+    }
     runOnUiThread(
         new Runnable() {
           @Override
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ResourceOverlay.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ResourceOverlay.java
new file mode 100644
index 0000000..b80c6c8
--- /dev/null
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ResourceOverlay.java
@@ -0,0 +1,36 @@
+// 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.
+
+package dev.cobalt.coat;
+
+import android.content.Context;
+import dev.cobalt.util.UsedByNative;
+
+/**
+ * A class for managing the resource overlays of Cobalt. Client can turn on certain feature by
+ * setting the resource overlay.
+ */
+public class ResourceOverlay {
+  // To facilitate maintenance, these member names should match what is in the
+  // resource XML file.
+  @SuppressWarnings("MemberName")
+  @UsedByNative
+  public final boolean supports_spherical_videos;
+
+  public ResourceOverlay(Context context) {
+    // Load the values for all Overlay variables.
+    this.supports_spherical_videos =
+        context.getResources().getBoolean(R.bool.supports_spherical_videos);
+  }
+}
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
index 408ada0..95b4457 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
@@ -51,8 +51,10 @@
 import dev.cobalt.util.Log;
 import dev.cobalt.util.UsedByNative;
 import java.lang.reflect.Method;
+import java.util.Calendar;
 import java.util.HashMap;
 import java.util.Locale;
+import java.util.TimeZone;
 
 /** Implementation of the required JNI methods called by the Starboard C++ code. */
 public class StarboardBridge {
@@ -72,6 +74,7 @@
   private AudioPermissionRequester audioPermissionRequester;
   private KeyboardEditor keyboardEditor;
   private NetworkStatus networkStatus;
+  private ResourceOverlay resourceOverlay;
 
   static {
     // Even though NativeActivity already loads our library from C++,
@@ -97,6 +100,7 @@
   private final HashMap<String, CobaltService.Factory> cobaltServiceFactories = new HashMap<>();
   private final HashMap<String, CobaltService> cobaltServices = new HashMap<>();
 
+  private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone("America/Los_Angeles");
   private final long timeNanosecondsPerMicrosecond = 1000;
 
   public StarboardBridge(
@@ -124,6 +128,7 @@
         new CobaltMediaSession(appContext, activityHolder, audioOutputManager);
     this.audioPermissionRequester = new AudioPermissionRequester(appContext, activityHolder);
     this.networkStatus = new NetworkStatus(appContext);
+    this.resourceOverlay = new ResourceOverlay(appContext);
   }
 
   private native boolean nativeInitialize();
@@ -363,7 +368,9 @@
     return ttsHelper;
   }
 
-  /** @return A new CaptionSettings object with the current system caption settings. */
+  /**
+   * @return A new CaptionSettings object with the current system caption settings.
+   */
   @SuppressWarnings("unused")
   @UsedByNative
   CaptionSettings getCaptionSettings() {
@@ -381,6 +388,18 @@
 
   @SuppressWarnings("unused")
   @UsedByNative
+  String getTimeZoneId() {
+    Locale locale = Locale.getDefault();
+    Calendar calendar = Calendar.getInstance(locale);
+    TimeZone timeZone = DEFAULT_TIME_ZONE;
+    if (calendar != null) {
+      timeZone = calendar.getTimeZone();
+    }
+    return timeZone.getID();
+  }
+
+  @SuppressWarnings("unused")
+  @UsedByNative
   SizeF getDisplayDpi() {
     return DisplayUtil.getDisplayDpi();
   }
@@ -391,6 +410,12 @@
     return DisplayUtil.getSystemDisplaySize();
   }
 
+  @SuppressWarnings("unused")
+  @UsedByNative
+  public ResourceOverlay getResourceOverlay() {
+    return resourceOverlay;
+  }
+
   @Nullable
   private static String getSystemProperty(String name) {
     try {
@@ -486,7 +511,9 @@
     return audioManager.isMicrophoneMute();
   }
 
-  /** @return true if we have an active network connection and it's on an wireless network. */
+  /**
+   * @return true if we have an active network connection and it's on an wireless network.
+   */
   @SuppressWarnings("unused")
   @UsedByNative
   boolean isCurrentNetworkWireless() {
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
index f265e98..5a13b01 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
@@ -60,14 +60,18 @@
       int channelCount,
       int preferredBufferSizeInBytes,
       boolean enableAudioDeviceCallback,
-      int tunnelModeAudioSessionId) {
+      boolean enablePcmContentTypeMovie,
+      int tunnelModeAudioSessionId,
+      boolean isWebAudio) {
     AudioTrackBridge audioTrackBridge =
         new AudioTrackBridge(
             sampleType,
             sampleRate,
             channelCount,
             preferredBufferSizeInBytes,
-            tunnelModeAudioSessionId);
+            enablePcmContentTypeMovie,
+            tunnelModeAudioSessionId,
+            isWebAudio);
     if (!audioTrackBridge.isAudioTrackValid()) {
       Log.e(TAG, "AudioTrackBridge has invalid audio track");
       return null;
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
index 0001fde..0a7270e 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
@@ -64,7 +64,9 @@
       int sampleRate,
       int channelCount,
       int preferredBufferSizeInBytes,
-      int tunnelModeAudioSessionId) {
+      boolean enablePcmContentTypeMovie,
+      int tunnelModeAudioSessionId,
+      boolean isWebAudio) {
 
     tunnelModeEnabled = tunnelModeAudioSessionId != -1;
     int channelConfig;
@@ -109,15 +111,13 @@
               .build();
     } else {
       // TODO: Support ENCODING_E_AC3_JOC for api level 28 or later.
-      final boolean is_surround =
+      final boolean isSurround =
           sampleType == AudioFormat.ENCODING_AC3 || sampleType == AudioFormat.ENCODING_E_AC3;
-      // TODO: We start to enforce |CONTENT_TYPE_MOVIE| for surround playback, investigate if we
-      //       can use |CONTENT_TYPE_MOVIE| for all non-surround AudioTrack used by video
-      //       playback.
+      final boolean useContentTypeMovie = isSurround || (!isWebAudio && enablePcmContentTypeMovie);
       attributes =
           new AudioAttributes.Builder()
               .setContentType(
-                  is_surround
+                  useContentTypeMovie
                       ? AudioAttributes.CONTENT_TYPE_MOVIE
                       : AudioAttributes.CONTENT_TYPE_MUSIC)
               .setUsage(AudioAttributes.USAGE_MEDIA)
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
index e47ce31..41a11e4 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
@@ -588,6 +588,7 @@
       String mime,
       boolean mustSupportSecure,
       boolean mustSupportSoftwareCodec,
+      boolean forceImprovedSupportCheck,
       int width,
       int height,
       int fps,
@@ -609,6 +610,7 @@
             mustSupportHdr,
             mustSupportSoftwareCodec,
             mustSupportTunneled,
+            forceImprovedSupportCheck,
             0,
             0,
             0,
@@ -622,6 +624,7 @@
               false /* mustSupportHdr */,
               mustSupportSoftwareCodec,
               mustSupportTunneled,
+              forceImprovedSupportCheck,
               0,
               0,
               0,
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
index e2632dd..c71644d 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
@@ -397,6 +397,7 @@
       boolean mustSupportSecure,
       boolean mustSupportHdr,
       boolean mustSupportTunnelMode,
+      boolean forceImprovedSupportCheck,
       int frameWidth,
       int frameHeight,
       int bitrate,
@@ -408,6 +409,7 @@
             mustSupportHdr,
             false /* mustSupportSoftwareCodec */,
             mustSupportTunnelMode,
+            forceImprovedSupportCheck,
             frameWidth,
             frameHeight,
             bitrate,
@@ -433,7 +435,8 @@
    */
   @SuppressWarnings("unused")
   @UsedByNative
-  public static boolean hasHdrCapableVideoDecoder(String mimeType) {
+  public static boolean hasHdrCapableVideoDecoder(
+      String mimeType, boolean forceImprovedSupportCheck) {
     // VP9Profile* values were not added until API level 24.  See
     // https://developer.android.com/reference/android/media/MediaCodecInfo.CodecProfileLevel.html.
     if (Build.VERSION.SDK_INT < 24) {
@@ -446,7 +449,8 @@
     }
 
     FindVideoDecoderResult findVideoDecoderResult =
-        findVideoDecoder(mimeType, false, true, false, false, 0, 0, 0, 0);
+        findVideoDecoder(
+            mimeType, false, true, false, false, forceImprovedSupportCheck, 0, 0, 0, 0);
     return isHdrCapableVideoDecoder(mimeType, findVideoDecoderResult);
   }
 
@@ -487,6 +491,7 @@
       boolean mustSupportHdr,
       boolean mustSupportSoftwareCodec,
       boolean mustSupportTunnelMode,
+      boolean forceImprovedSupportCheck,
       int frameWidth,
       int frameHeight,
       int bitrate,
@@ -496,7 +501,8 @@
         String.format(
             "Searching for video decoder with parameters mimeType: %s, secure: %b, frameWidth: %d,"
                 + " frameHeight: %d, bitrate: %d, fps: %d, mustSupportHdr: %b,"
-                + " mustSupportSoftwareCodec: %b, mustSupportTunnelMode: %b",
+                + " mustSupportSoftwareCodec: %b, mustSupportTunnelMode: %b,"
+                + " forceImprovedSupportCheck: %b",
             mimeType,
             mustSupportSecure,
             frameWidth,
@@ -505,7 +511,8 @@
             fps,
             mustSupportHdr,
             mustSupportSoftwareCodec,
-            mustSupportTunnelMode));
+            mustSupportTunnelMode,
+            forceImprovedSupportCheck));
     Log.v(
         TAG,
         String.format(
@@ -609,8 +616,9 @@
         // Enable the improved support check based on more specific APIs, like isSizeSupported() or
         // areSizeAndRateSupported(), for 8k content. These APIs are theoretically more accurate,
         // but we are unsure about their level of support on various Android TV platforms.
-        final boolean enableImprovedCheck = frameWidth > 3840 || frameHeight > 2160;
-        if (enableImprovedCheck) {
+        final boolean enableImprovedSupportCheck =
+            forceImprovedSupportCheck || (frameWidth > 3840 || frameHeight > 2160);
+        if (enableImprovedSupportCheck) {
           if (frameWidth != 0 && frameHeight != 0) {
             if (!videoCapabilities.isSizeSupported(frameWidth, frameHeight)) {
               String format = "Rejecting %s, reason: width %s is not compatible with height %d";
@@ -651,7 +659,7 @@
         }
 
         Range<Integer> supportedFrameRates = videoCapabilities.getSupportedFrameRates();
-        if (enableImprovedCheck) {
+        if (enableImprovedSupportCheck) {
           if (fps != 0) {
             if (frameHeight != 0 && frameWidth != 0) {
               if (!videoCapabilities.areSizeAndRateSupported(frameWidth, frameHeight, fps)) {
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
index abb56c8..c6db123 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
@@ -76,6 +76,14 @@
   @SuppressWarnings("deprecation")
   private static final int MEDIA_DRM_EVENT_KEY_EXPIRED = MediaDrm.EVENT_KEY_EXPIRED;
 
+  // Deprecated in API 23, but we still log it on earlier devices.
+  @SuppressWarnings("deprecation")
+  private static final int MEDIA_DRM_EVENT_PROVISION_REQUIRED = MediaDrm.EVENT_PROVISION_REQUIRED;
+
+  // Added in API 23.
+  private static final int MEDIA_DRM_EVENT_SESSION_RECLAIMED =
+      Build.VERSION.SDK_INT >= 23 ? MediaDrm.EVENT_SESSION_RECLAIMED : 5;
+
   private MediaDrm mMediaDrm;
   private long mNativeMediaDrmBridge;
   private UUID mSchemeUUID;
@@ -400,47 +408,48 @@
                   String.format("EventListener: Invalid session %s", bytesToHexString(sessionId)));
               return;
             }
-            switch (event) {
-              case MediaDrm.EVENT_KEY_REQUIRED:
-                Log.d(TAG, "MediaDrm.EVENT_KEY_REQUIRED");
-                String mime = mSessionIds.get(ByteBuffer.wrap(sessionId));
-                MediaDrm.KeyRequest request = null;
-                try {
-                  request = getKeyRequest(sessionId, data, mime);
-                } catch (NotProvisionedException e) {
-                  Log.e(TAG, "Device not provisioned", e);
-                  if (!attemptProvisioning()) {
-                    Log.e(TAG, "Failed to provision device when responding to EVENT_KEY_REQUIRED");
-                    return;
-                  }
-                  // If we supposedly successfully provisioned ourselves, then try to create a
-                  // request again.
-                  try {
-                    request = getKeyRequest(sessionId, data, mime);
-                  } catch (NotProvisionedException e2) {
-                    Log.e(
-                        TAG,
-                        "Device still not provisioned after supposedly successful provisioning",
-                        e2);
-                    return;
-                  }
-                }
-                if (request != null) {
-                  onSessionMessage(SB_DRM_TICKET_INVALID, sessionId, request);
-                } else {
-                  Log.e(TAG, "EventListener: getKeyRequest failed.");
+
+            if (event == MediaDrm.EVENT_KEY_REQUIRED) {
+              Log.d(TAG, "MediaDrm.EVENT_KEY_REQUIRED");
+              String mime = mSessionIds.get(ByteBuffer.wrap(sessionId));
+              MediaDrm.KeyRequest request = null;
+              try {
+                request = getKeyRequest(sessionId, data, mime);
+              } catch (NotProvisionedException e) {
+                Log.e(TAG, "Device not provisioned", e);
+                if (!attemptProvisioning()) {
+                  Log.e(TAG, "Failed to provision device when responding to EVENT_KEY_REQUIRED");
                   return;
                 }
-                break;
-              case MEDIA_DRM_EVENT_KEY_EXPIRED:
-                Log.d(TAG, "MediaDrm.EVENT_KEY_EXPIRED");
-                break;
-              case MediaDrm.EVENT_VENDOR_DEFINED:
-                Log.d(TAG, "MediaDrm.EVENT_VENDOR_DEFINED");
-                break;
-              default:
-                Log.e(TAG, "Invalid DRM event " + event);
+                // If we supposedly successfully provisioned ourselves, then try to create a
+                // request again.
+                try {
+                  request = getKeyRequest(sessionId, data, mime);
+                } catch (NotProvisionedException e2) {
+                  Log.e(
+                      TAG,
+                      "Device still not provisioned after supposedly successful provisioning",
+                      e2);
+                  return;
+                }
+              }
+              if (request != null) {
+                onSessionMessage(SB_DRM_TICKET_INVALID, sessionId, request);
+              } else {
+                Log.e(TAG, "EventListener: getKeyRequest failed.");
                 return;
+              }
+            } else if (event == MEDIA_DRM_EVENT_KEY_EXPIRED) {
+              Log.d(TAG, "MediaDrm.EVENT_KEY_EXPIRED");
+            } else if (event == MediaDrm.EVENT_VENDOR_DEFINED) {
+              Log.d(TAG, "MediaDrm.EVENT_VENDOR_DEFINED");
+            } else if (event == MEDIA_DRM_EVENT_PROVISION_REQUIRED) {
+              Log.d(TAG, "MediaDrm.EVENT_PROVISION_REQUIRED");
+            } else if (event == MEDIA_DRM_EVENT_SESSION_RECLAIMED) {
+              Log.d(TAG, "MediaDrm.EVENT_SESSION_RECLAIMED");
+            } else {
+              Log.e(TAG, "Invalid DRM event " + event);
+              return;
             }
           }
         });
diff --git a/src/starboard/android/apk/app/src/main/res/values/overlayable.xml b/src/starboard/android/apk/app/src/main/res/values/overlayable.xml
new file mode 100644
index 0000000..e892f68
--- /dev/null
+++ b/src/starboard/android/apk/app/src/main/res/values/overlayable.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+-->
+
+<resources xmlns:tools="http://schemas.android.com/tools"
+           tools:ignore="DuplicateDefinition">
+  <overlayable name="CobaltOverlay" >
+    <policy type="vendor|product|system">
+      <!-- This flag indicates whether 360 videos can be played. -->
+      <item type="bool" name="supports_spherical_videos" />
+    </policy>
+  </overlayable>
+</resources>
diff --git a/src/starboard/android/apk/app/src/main/res/values/rro_variables.xml b/src/starboard/android/apk/app/src/main/res/values/rro_variables.xml
new file mode 100644
index 0000000..874c137
--- /dev/null
+++ b/src/starboard/android/apk/app/src/main/res/values/rro_variables.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+-->
+
+<resources xmlns:tools="http://schemas.android.com/tools">
+  <!-- This flag indicates whether 360 videos can be played. -->
+  <bool name="supports_spherical_videos" tools:ignore="DuplicateDefinition">true</bool>
+</resources>
diff --git a/src/starboard/android/apk/build.gradle b/src/starboard/android/apk/build.gradle
index 27edbf5..302652a 100644
--- a/src/starboard/android/apk/build.gradle
+++ b/src/starboard/android/apk/build.gradle
@@ -20,7 +20,7 @@
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.6.3'
+        classpath 'com.android.tools.build:gradle:4.1.0'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
@@ -49,7 +49,7 @@
 // Move the 'buildDir' for all projects into sub-directories of a shared top-level build directory,
 // which is either the root's original 'buildDir' or a custom location when building for platform
 // deploy.  Note that the platform deploy action sets a custom 'cobaltGradleDir' property rather
-// than setting 'buildDir' directly on the command line since Gradle trys to get smart about
+// than setting 'buildDir' directly on the command line since Gradle tries to get smart about
 // 'buildDir' which can end up putting it at the wrong depth in the file system.
 def rootBuildDir = hasProperty('cobaltGradleDir') ? new File(cobaltGradleDir, 'build') : buildDir
 allprojects { buildDir = new File(rootBuildDir, project.name).canonicalFile }
diff --git a/src/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties b/src/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties
index 0ebb310..186b715 100644
--- a/src/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties
+++ b/src/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/src/starboard/android/examples/overlays/AndroidManifest.xml b/src/starboard/android/examples/overlays/AndroidManifest.xml
new file mode 100644
index 0000000..a88fe39
--- /dev/null
+++ b/src/starboard/android/examples/overlays/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="dev.cobalt.coat.rro">
+
+  <application android:hasCode="false" />
+  <overlay android:targetPackage="dev.cobalt.coat"
+           android:targetName="CobaltOverlay"
+           android:resourcesMap="@xml/overlays"/>
+</manifest>
diff --git a/src/starboard/android/examples/overlays/res/xml/overlays.xml b/src/starboard/android/examples/overlays/res/xml/overlays.xml
new file mode 100644
index 0000000..7a3f011
--- /dev/null
+++ b/src/starboard/android/examples/overlays/res/xml/overlays.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+-->
+
+<overlay>
+  <!-- This flag indicates whether 360 videos can be played. -->
+  <item target="bool/supports_spherical_videos" value="true" />
+</overlay>
diff --git a/src/starboard/android/shared/BUILD.gn b/src/starboard/android/shared/BUILD.gn
index dd4f76a..91baf57 100644
--- a/src/starboard/android/shared/BUILD.gn
+++ b/src/starboard/android/shared/BUILD.gn
@@ -336,6 +336,8 @@
     "file_truncate.cc",
     "file_write.cc",
     "get_home_directory.cc",
+    "graphics.cc",
+    "graphics.h",
     "input_events_generator.cc",
     "input_events_generator.h",
     "jni_env_ext.cc",
diff --git a/src/starboard/android/shared/application_android.cc b/src/starboard/android/shared/application_android.cc
index f2986e7..f0348d5 100644
--- a/src/starboard/android/shared/application_android.cc
+++ b/src/starboard/android/shared/application_android.cc
@@ -116,9 +116,20 @@
   keyboard_inject_writefd_ = pipefd[1];
   ALooper_addFd(looper_, keyboard_inject_readfd_, kLooperIdKeyboardInject,
                 ALOOPER_EVENT_INPUT, NULL, NULL);
+
+  JniEnvExt* env = JniEnvExt::Get();
+  jobject local_ref = env->CallStarboardObjectMethodOrAbort(
+      "getResourceOverlay", "()Ldev/cobalt/coat/ResourceOverlay;");
+  resource_overlay_ = env->ConvertLocalRefToGlobalRef(local_ref);
 }
 
 ApplicationAndroid::~ApplicationAndroid() {
+  // Release the global reference.
+  if (resource_overlay_) {
+    JniEnvExt* env = JniEnvExt::Get();
+    env->DeleteGlobalRef(resource_overlay_);
+    resource_overlay_ = nullptr;
+  }
   ALooper_removeFd(looper_, android_command_readfd_);
   close(android_command_readfd_);
   close(android_command_writefd_);
@@ -681,6 +692,44 @@
   ApplicationAndroid::Get()->SendDateTimeConfigurationChangedEvent();
 }
 
+int ApplicationAndroid::GetOverlayedIntValue(const char* var_name) {
+  ScopedLock lock(overlay_mutex_);
+  if (overlayed_int_variables_.find(var_name) !=
+      overlayed_int_variables_.end()) {
+    return overlayed_int_variables_[var_name];
+  }
+  JniEnvExt* env = JniEnvExt::Get();
+  jint value = env->GetIntFieldOrAbort(resource_overlay_, var_name, "I");
+  overlayed_int_variables_[var_name] = value;
+  return value;
+}
+
+std::string ApplicationAndroid::GetOverlayedStringValue(const char* var_name) {
+  ScopedLock lock(overlay_mutex_);
+  if (overlayed_string_variables_.find(var_name) !=
+      overlayed_string_variables_.end()) {
+    return overlayed_string_variables_[var_name];
+  }
+  JniEnvExt* env = JniEnvExt::Get();
+  std::string value = env->GetStringStandardUTFOrAbort(
+      env->GetStringFieldOrAbort(resource_overlay_, var_name));
+  overlayed_string_variables_[var_name] = value;
+  return value;
+}
+
+bool ApplicationAndroid::GetOverlayedBoolValue(const char* var_name) {
+  ScopedLock lock(overlay_mutex_);
+  if (overlayed_bool_variables_.find(var_name) !=
+      overlayed_bool_variables_.end()) {
+    return overlayed_bool_variables_[var_name];
+  }
+  JniEnvExt* env = JniEnvExt::Get();
+  jboolean value =
+      env->GetBooleanFieldOrAbort(resource_overlay_, var_name, "Z");
+  overlayed_bool_variables_[var_name] = value;
+  return value;
+}
+
 }  // namespace shared
 }  // namespace android
 }  // namespace starboard
diff --git a/src/starboard/android/shared/application_android.h b/src/starboard/android/shared/application_android.h
index a1c92a6..2d147eb 100644
--- a/src/starboard/android/shared/application_android.h
+++ b/src/starboard/android/shared/application_android.h
@@ -18,6 +18,7 @@
 #include <android/looper.h>
 #include <android/native_window.h>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #include "starboard/android/shared/input_events_generator.h"
@@ -96,6 +97,14 @@
 
   void SendDateTimeConfigurationChangedEvent();
 
+  // Methods to get the Runtime Resource Overlay variables.
+  // All RRO variables which can be retrieved here must be defined
+  // in res/values/rro_variables.xml and be loaded in
+  // dev/cobalt/coat/ResourceOverlay.java.
+  int GetOverlayedIntValue(const char* var_name);
+  std::string GetOverlayedStringValue(const char* var_name);
+  bool GetOverlayedBoolValue(const char* var_name);
+
   // Methods to start/stop Media playback service.
   void StartMediaPlaybackService();
   void StopMediaPlaybackService();
@@ -139,6 +148,13 @@
 
   bool last_is_accessibility_high_contrast_text_enabled_;
 
+  jobject resource_overlay_;
+
+  Mutex overlay_mutex_;
+  std::unordered_map<std::string, bool> overlayed_bool_variables_;
+  std::unordered_map<std::string, int> overlayed_int_variables_;
+  std::unordered_map<std::string, std::string> overlayed_string_variables_;
+
   // Methods to process pipes attached to the Looper.
   void ProcessAndroidCommand();
   void ProcessAndroidInput();
diff --git a/src/starboard/android/shared/audio_renderer_passthrough.cc b/src/starboard/android/shared/audio_renderer_passthrough.cc
index 1f0849c..f80c44e 100644
--- a/src/starboard/android/shared/audio_renderer_passthrough.cc
+++ b/src/starboard/android/shared/audio_renderer_passthrough.cc
@@ -20,6 +20,7 @@
 #include "starboard/android/shared/audio_decoder_passthrough.h"
 #include "starboard/android/shared/jni_env_ext.h"
 #include "starboard/android/shared/jni_utils.h"
+#include "starboard/common/string.h"
 #include "starboard/memory.h"
 
 namespace starboard {
@@ -384,7 +385,8 @@
       optional<SbMediaAudioSampleType>(),  // Not required in passthrough mode
       audio_sample_info_.number_of_channels,
       audio_sample_info_.samples_per_second, kPreferredBufferSizeInBytes,
-      enable_audio_device_callback_, kTunnelModeAudioSessionId));
+      enable_audio_device_callback_, false /* enable_pcm_content_type_movie */,
+      kTunnelModeAudioSessionId, false /* is_web_audio */));
 
   if (!audio_track_bridge->is_valid()) {
     error_cb_(kSbPlayerErrorDecode, "Error creating AudioTrackBridge");
diff --git a/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc b/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
index 1c7d413..80656e2 100644
--- a/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
+++ b/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
@@ -125,7 +125,7 @@
             GetSampleSize(task.sample_type),
         &MinRequiredFramesTester::UpdateSourceStatusFunc,
         &MinRequiredFramesTester::ConsumeFramesFunc,
-        &MinRequiredFramesTester::ErrorFunc, 0, -1, false, this);
+        &MinRequiredFramesTester::ErrorFunc, 0, -1, false, false, false, this);
     {
       ScopedLock scoped_lock(mutex_);
       wait_timeout = !condition_variable_.WaitTimed(kSbTimeSecond * 5);
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.cc b/src/starboard/android/shared/audio_track_audio_sink_type.cc
index 6af4aa6..0e9d29e 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.cc
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -83,6 +83,8 @@
     SbTime start_time,
     int tunnel_mode_audio_session_id,
     bool enable_audio_device_callback,
+    bool enable_pcm_content_type_movie,
+    bool is_web_audio,
     void* context)
     : type_(type),
       channels_(channels),
@@ -106,7 +108,9 @@
               sampling_frequency_hz,
               preferred_buffer_size_in_bytes,
               enable_audio_device_callback,
-              tunnel_mode_audio_session_id) {
+              enable_pcm_content_type_movie,
+              tunnel_mode_audio_session_id,
+              is_web_audio) {
   SB_DCHECK(update_source_status_func_);
   SB_DCHECK(consume_frames_func_);
   SB_DCHECK(frame_buffer_);
@@ -420,11 +424,15 @@
   const int kTunnelModeAudioSessionId = -1;
   // Disable AudioDeviceCallback for WebAudio.
   const bool kEnableAudioDeviceCallback = false;
+  const bool kIsWebAudio = true;
+  // Disable AudioAttributes::CONTENT_TYPE_MOVIE for WebAudio.
+  const bool kEnablePcmContentTypeMovie = false;
   return Create(channels, sampling_frequency_hz, audio_sample_type,
                 audio_frame_storage_type, frame_buffers, frames_per_channel,
                 update_source_status_func, consume_frames_func, error_func,
                 kStartTime, kTunnelModeAudioSessionId,
-                kEnableAudioDeviceCallback, context);
+                kEnableAudioDeviceCallback, kEnablePcmContentTypeMovie,
+                kIsWebAudio, context);
 }
 
 SbAudioSink AudioTrackAudioSinkType::Create(
@@ -440,6 +448,8 @@
     SbTime start_media_time,
     int tunnel_mode_audio_session_id,
     bool enable_audio_device_callback,
+    bool enable_pcm_content_type_movie,
+    bool is_web_audio,
     void* context) {
   int min_required_frames = SbAudioSinkGetMinBufferSizeInFrames(
       channels, audio_sample_type, sampling_frequency_hz);
@@ -451,7 +461,8 @@
       frames_per_channel, preferred_buffer_size_in_bytes,
       update_source_status_func, consume_frames_func, error_func,
       start_media_time, tunnel_mode_audio_session_id,
-      enable_audio_device_callback, context);
+      enable_audio_device_callback, enable_pcm_content_type_movie, is_web_audio,
+      context);
   if (!audio_sink->IsAudioTrackValid()) {
     SB_DLOG(ERROR)
         << "AudioTrackAudioSinkType::Create failed to create audio track";
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.h b/src/starboard/android/shared/audio_track_audio_sink_type.h
index adf050d..035b093 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.h
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.h
@@ -69,6 +69,8 @@
       SbTime start_time,
       int tunnel_mode_audio_session_id,
       bool enable_audio_device_callback,
+      bool enable_pcm_content_type_movie,
+      bool is_web_audio,
       void* context);
 
   bool IsValid(SbAudioSink audio_sink) override {
@@ -112,6 +114,8 @@
       SbTime start_media_time,
       int tunnel_mode_audio_session_id,
       bool enable_audio_device_callback,
+      bool enable_pcm_content_type_movie,
+      bool is_web_audio,
       void* context);
   ~AudioTrackAudioSink() override;
 
diff --git a/src/starboard/android/shared/audio_track_bridge.cc b/src/starboard/android/shared/audio_track_bridge.cc
index 79fe83f..05b9c9b 100644
--- a/src/starboard/android/shared/audio_track_bridge.cc
+++ b/src/starboard/android/shared/audio_track_bridge.cc
@@ -40,7 +40,9 @@
                                    int sampling_frequency_hz,
                                    int preferred_buffer_size_in_bytes,
                                    bool enable_audio_device_callback,
-                                   int tunnel_mode_audio_session_id) {
+                                   bool enable_pcm_content_type_movie,
+                                   int tunnel_mode_audio_session_id,
+                                   bool is_web_audio) {
   if (coding_type == kSbMediaAudioCodingTypePcm) {
     SB_DCHECK(SbAudioSinkIsAudioSampleTypeSupported(sample_type.value()));
 
@@ -63,10 +65,11 @@
           "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
   jobject j_audio_track_bridge = env->CallObjectMethodOrAbort(
       j_audio_output_manager.Get(), "createAudioTrackBridge",
-      "(IIIIZI)Ldev/cobalt/media/AudioTrackBridge;",
+      "(IIIIZZIZ)Ldev/cobalt/media/AudioTrackBridge;",
       GetAudioFormatSampleType(coding_type, sample_type), sampling_frequency_hz,
       channels, preferred_buffer_size_in_bytes, enable_audio_device_callback,
-      tunnel_mode_audio_session_id);
+      enable_pcm_content_type_movie, tunnel_mode_audio_session_id,
+      is_web_audio);
   if (!j_audio_track_bridge) {
     // One of the cases that this may hit is when output happened to be switched
     // to a device that doesn't support tunnel mode.
diff --git a/src/starboard/android/shared/audio_track_bridge.h b/src/starboard/android/shared/audio_track_bridge.h
index 607837d..3804b6a 100644
--- a/src/starboard/android/shared/audio_track_bridge.h
+++ b/src/starboard/android/shared/audio_track_bridge.h
@@ -42,7 +42,9 @@
                    int sampling_frequency_hz,
                    int preferred_buffer_size_in_bytes,
                    bool enable_audio_device_callback,
-                   int tunnel_mode_audio_session_id);
+                   bool enable_pcm_content_type_movie,
+                   int tunnel_mode_audio_session_id,
+                   bool is_web_audio);
   ~AudioTrackBridge();
 
   static int GetMinBufferSizeInFrames(SbMediaAudioSampleType sample_type,
diff --git a/src/starboard/android/shared/graphics.cc b/src/starboard/android/shared/graphics.cc
new file mode 100644
index 0000000..5ceed59
--- /dev/null
+++ b/src/starboard/android/shared/graphics.cc
@@ -0,0 +1,59 @@
+// 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 "starboard/android/shared/graphics.h"
+
+#include "starboard/common/log.h"
+
+#include "cobalt/extension/graphics.h"
+#include "starboard/android/shared/application_android.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+namespace {
+
+float GetMaximumFrameIntervalInMilliseconds() {
+  return -1.0f;
+}
+
+float GetMinimumFrameIntervalInMilliseconds() {
+  return 16.0f;
+}
+
+bool IsMapToMeshEnabled() {
+  bool supports_spherical_videos =
+      starboard::android::shared::ApplicationAndroid::Get()
+          ->GetOverlayedBoolValue("supports_spherical_videos");
+  return supports_spherical_videos;
+}
+
+const CobaltExtensionGraphicsApi kGraphicsApi = {
+    kCobaltExtensionGraphicsName,
+    3,
+    &GetMaximumFrameIntervalInMilliseconds,
+    &GetMinimumFrameIntervalInMilliseconds,
+    &IsMapToMeshEnabled,
+};
+
+}  // namespace
+
+const void* GetGraphicsApi() {
+  return &kGraphicsApi;
+}
+
+}  // namespace shared
+}  // namespace android
+}  // namespace starboard
diff --git a/src/starboard/android/shared/graphics.h b/src/starboard/android/shared/graphics.h
new file mode 100644
index 0000000..34b3447
--- /dev/null
+++ b/src/starboard/android/shared/graphics.h
@@ -0,0 +1,28 @@
+// 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.
+
+#ifndef STARBOARD_ANDROID_SHARED_GRAPHICS_H_
+#define STARBOARD_ANDROID_SHARED_GRAPHICS_H_
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+const void* GetGraphicsApi();
+
+}  // namespace shared
+}  // namespace android
+}  // namespace starboard
+
+#endif  // STARBOARD_ANDROID_SHARED_GRAPHICS_H_
diff --git a/src/starboard/android/shared/media_codec_bridge.cc b/src/starboard/android/shared/media_codec_bridge.cc
index 57acdad..d663f08 100644
--- a/src/starboard/android/shared/media_codec_bridge.cc
+++ b/src/starboard/android/shared/media_codec_bridge.cc
@@ -207,6 +207,7 @@
     bool require_software_codec,
     int tunnel_mode_audio_session_id,
     bool force_big_endian_hdr_metadata,
+    bool force_improved_support_check,
     std::string* error_message) {
   SB_DCHECK(error_message);
 
@@ -256,16 +257,16 @@
       new MediaCodecBridge(handler));
   env->CallStaticVoidMethodOrAbort(
       "dev/cobalt/media/MediaCodecBridge", "createVideoMediaCodecBridge",
-      "(JLjava/lang/String;ZZIIILandroid/view/Surface;"
+      "(JLjava/lang/String;ZZZIIILandroid/view/Surface;"
       "Landroid/media/MediaCrypto;"
       "Ldev/cobalt/media/MediaCodecBridge$ColorInfo;"
       "I"
       "Ldev/cobalt/media/MediaCodecBridge$CreateMediaCodecBridgeResult;)"
       "V",
       reinterpret_cast<jlong>(native_media_codec_bridge.get()), j_mime.Get(),
-      !!j_media_crypto, require_software_codec, width, height, fps, j_surface,
-      j_media_crypto, j_color_info.Get(), tunnel_mode_audio_session_id,
-      j_create_media_codec_bridge_result.Get());
+      !!j_media_crypto, require_software_codec, force_improved_support_check,
+      width, height, fps, j_surface, j_media_crypto, j_color_info.Get(),
+      tunnel_mode_audio_session_id, j_create_media_codec_bridge_result.Get());
 
   jobject j_media_codec_bridge = env->CallObjectMethodOrAbort(
       j_create_media_codec_bridge_result.Get(), "mediaCodecBridge",
diff --git a/src/starboard/android/shared/media_codec_bridge.h b/src/starboard/android/shared/media_codec_bridge.h
index 60a6487..77b9c7a 100644
--- a/src/starboard/android/shared/media_codec_bridge.h
+++ b/src/starboard/android/shared/media_codec_bridge.h
@@ -113,6 +113,7 @@
       bool require_software_codec,
       int tunnel_mode_audio_session_id,
       bool force_big_endian_hdr_metadata,
+      bool force_improved_support_check,
       std::string* error_message);
 
   ~MediaCodecBridge();
diff --git a/src/starboard/android/shared/media_common.h b/src/starboard/android/shared/media_common.h
index aa17a47..f9e5a56 100644
--- a/src/starboard/android/shared/media_common.h
+++ b/src/starboard/android/shared/media_common.h
@@ -15,14 +15,12 @@
 #ifndef STARBOARD_ANDROID_SHARED_MEDIA_COMMON_H_
 #define STARBOARD_ANDROID_SHARED_MEDIA_COMMON_H_
 
-#include <deque>
-#include <queue>
+#include <cstring>
 
 #include "starboard/android/shared/jni_env_ext.h"
 #include "starboard/common/log.h"
 #include "starboard/common/mutex.h"
 #include "starboard/common/optional.h"
-#include "starboard/common/string.h"
 #include "starboard/configuration.h"
 #include "starboard/media.h"
 #include "starboard/shared/starboard/player/filter/audio_frame_tracker.h"
@@ -36,10 +34,6 @@
          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/src/starboard/android/shared/media_decoder.cc b/src/starboard/android/shared/media_decoder.cc
index 44095f7..66bd437 100644
--- a/src/starboard/android/shared/media_decoder.cc
+++ b/src/starboard/android/shared/media_decoder.cc
@@ -117,6 +117,7 @@
                            const FrameRenderedCB& frame_rendered_cb,
                            int tunnel_mode_audio_session_id,
                            bool force_big_endian_hdr_metadata,
+                           bool force_improved_support_check,
                            std::string* error_message)
     : media_type_(kSbMediaTypeVideo),
       host_(host),
@@ -131,7 +132,8 @@
   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, error_message);
+      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/src/starboard/android/shared/media_decoder.h b/src/starboard/android/shared/media_decoder.h
index 423d6ac..dbe73fc 100644
--- a/src/starboard/android/shared/media_decoder.h
+++ b/src/starboard/android/shared/media_decoder.h
@@ -89,6 +89,7 @@
                const FrameRenderedCB& frame_rendered_cb,
                int tunnel_mode_audio_session_id,
                bool force_big_endian_hdr_metadata,
+               bool force_improved_support_check,
                std::string* error_message);
   ~MediaDecoder();
 
diff --git a/src/starboard/android/shared/media_is_audio_supported.cc b/src/starboard/android/shared/media_is_audio_supported.cc
index 419de2a..d9efe42 100644
--- a/src/starboard/android/shared/media_is_audio_supported.cc
+++ b/src/starboard/android/shared/media_is_audio_supported.cc
@@ -52,6 +52,10 @@
     mime_type.RegisterBoolParameter("tunnelmode");
     // Enables audio passthrough if the codec supports it.
     mime_type.RegisterBoolParameter("audiopassthrough");
+    // Allows for disabling the CONTENT_TYPE_MOVIE AudioAttribute for
+    // non-tunneled playbacks with PCM audio. Enabled by default.
+    // (https://developer.android.com/reference/android/media/AudioAttributes#CONTENT_TYPE_MOVIE)
+    mime_type.RegisterBoolParameter("enablepcmcontenttypemovie");
 
     if (!mime_type.is_valid()) {
       return false;
diff --git a/src/starboard/android/shared/media_is_video_supported.cc b/src/starboard/android/shared/media_is_video_supported.cc
index d4e8a48..b3d048e 100644
--- a/src/starboard/android/shared/media_is_video_supported.cc
+++ b/src/starboard/android/shared/media_is_video_supported.cc
@@ -48,7 +48,7 @@
   bool has_hdr_capable_decoder =
       JniEnvExt::Get()->CallStaticBooleanMethodOrAbort(
           "dev/cobalt/media/MediaCodecUtil", "hasHdrCapableVideoDecoder",
-          "(Ljava/lang/String;)Z", j_mime.Get()) == JNI_TRUE;
+          "(Ljava/lang/String;Z)Z", j_mime.Get(), false) == JNI_TRUE;
   if (!has_hdr_capable_decoder) {
     return false;
   }
@@ -104,6 +104,9 @@
     mime_type.RegisterBoolParameter("tunnelmode");
     // Override endianness on HDR Info header. Defaults to little.
     mime_type.RegisterStringParameter("hdrinfoendianness", "big|little");
+    // Forces the use of specific Android APIs (isSizeSupported() and
+    // areSizeAndRateSupported()) to determine format support.
+    mime_type.RegisterBoolParameter("forceimprovedsupportcheck");
 
     if (!mime_type.is_valid()) {
       return false;
@@ -128,10 +131,13 @@
   // tunneled playback to be encrypted, so we must align the tunnel mode
   // requirement with the secure playback requirement.
   const bool require_secure_playback = must_support_tunnel_mode;
+  const bool force_improved_support_check =
+      mime_type.GetParamBoolValue("forceimprovedsupportcheck", true);
   return env->CallStaticBooleanMethodOrAbort(
              "dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor",
-             "(Ljava/lang/String;ZZZIIII)Z", j_mime.Get(),
+             "(Ljava/lang/String;ZZZZIIII)Z", j_mime.Get(),
              require_secure_playback, must_support_hdr,
-             must_support_tunnel_mode, frame_width, frame_height,
-             static_cast<jint>(bitrate), fps) == JNI_TRUE;
+             must_support_tunnel_mode, force_improved_support_check,
+             frame_width, frame_height, static_cast<jint>(bitrate),
+             fps) == JNI_TRUE;
 }
diff --git a/src/starboard/android/shared/player_components_factory.h b/src/starboard/android/shared/player_components_factory.h
index a4041e2..35deb31 100644
--- a/src/starboard/android/shared/player_components_factory.h
+++ b/src/starboard/android/shared/player_components_factory.h
@@ -64,11 +64,16 @@
 // TODO: Allow this to be configured per playback at run time from the web app.
 constexpr bool kForceSecurePipelineInTunnelModeWhenRequired = true;
 
+// Forces video surface to reset after tunnel mode playbacks. This prevents
+// video distortion on some platforms.
+constexpr bool kForceResetSurfaceUnderTunnelMode = true;
+
 // This class allows us to force int16 sample type when tunnel mode is enabled.
 class AudioRendererSinkAndroid : public ::starboard::shared::starboard::player::
                                      filter::AudioRendererSinkImpl {
  public:
   explicit AudioRendererSinkAndroid(bool enable_audio_device_callback,
+                                    bool enable_pcm_content_type_movie,
                                     int tunnel_mode_audio_session_id = -1)
       : AudioRendererSinkImpl(
             [=](SbTime start_media_time,
@@ -92,6 +97,7 @@
                   frame_buffers_size_in_frames, update_source_status_func,
                   consume_frames_func, error_func, start_media_time,
                   tunnel_mode_audio_session_id, enable_audio_device_callback,
+                  enable_pcm_content_type_movie, false, /* is_web_audio */
                   context);
             }) {}
 
@@ -156,6 +162,7 @@
 //       into .cc file.
 class PlayerComponentsFactory : public starboard::shared::starboard::player::
                                     filter::PlayerComponents::Factory {
+  typedef starboard::shared::starboard::media::MimeType MimeType;
   typedef starboard::shared::opus::OpusAudioDecoder OpusAudioDecoder;
   typedef starboard::shared::starboard::player::filter::AdaptiveAudioDecoder
       AdaptiveAudioDecoder;
@@ -190,7 +197,6 @@
   scoped_ptr<PlayerComponents> CreateComponents(
       const CreationParameters& creation_parameters,
       std::string* error_message) override {
-    using starboard::shared::starboard::media::MimeType;
     SB_DCHECK(error_message);
 
     if (creation_parameters.audio_codec() != kSbMediaAudioCodecAc3 &&
@@ -238,9 +244,20 @@
       constexpr int kTunnelModeAudioSessionId = -1;
       constexpr bool kForceSecurePipelineUnderTunnelMode = false;
 
-      scoped_ptr<VideoDecoder> video_decoder = CreateVideoDecoder(
-          creation_parameters, kTunnelModeAudioSessionId,
-          kForceSecurePipelineUnderTunnelMode, error_message);
+      MimeType video_mime_type(creation_parameters.video_mime());
+      video_mime_type.RegisterBoolParameter("forceimprovedsupportcheck");
+      if (!video_mime_type.is_valid()) {
+        return scoped_ptr<PlayerComponents>();
+      }
+      const bool force_improved_support_check =
+          video_mime_type.GetParamBoolValue("forceimprovedsupportcheck", true);
+      SB_LOG_IF(INFO, !force_improved_support_check)
+          << "Improved support check is disabled for queries under 4K.";
+
+      scoped_ptr<VideoDecoder> video_decoder =
+          CreateVideoDecoder(creation_parameters, kTunnelModeAudioSessionId,
+                             kForceSecurePipelineUnderTunnelMode,
+                             force_improved_support_check, error_message);
       if (video_decoder) {
         using starboard::shared::starboard::player::filter::VideoRendererImpl;
 
@@ -268,7 +285,6 @@
       scoped_ptr<VideoRenderAlgorithmBase>* video_render_algorithm,
       scoped_refptr<VideoRendererSink>* video_renderer_sink,
       std::string* error_message) override {
-    using starboard::shared::starboard::media::MimeType;
     SB_DCHECK(error_message);
 
     const char* audio_mime =
@@ -280,6 +296,7 @@
         strlen(creation_parameters.audio_mime()) > 0) {
       audio_mime_type.RegisterBoolParameter("tunnelmode");
       audio_mime_type.RegisterBoolParameter("enableaudiodevicecallback");
+      audio_mime_type.RegisterBoolParameter("enablepcmcontenttypemovie");
 
       if (!audio_mime_type.is_valid()) {
         return false;
@@ -294,6 +311,7 @@
     if (creation_parameters.video_codec() != kSbMediaVideoCodecNone &&
         strlen(creation_parameters.video_mime()) > 0) {
       video_mime_type.RegisterBoolParameter("tunnelmode");
+      video_mime_type.RegisterBoolParameter("forceimprovedsupportcheck");
 
       if (!video_mime_type.is_valid()) {
         return false;
@@ -333,17 +351,26 @@
       enable_tunnel_mode = true;
     }
 
+    const bool force_improved_support_check =
+        video_mime_type.GetParamBoolValue("forceimprovedsupportcheck", true);
+    SB_LOG_IF(INFO, !force_improved_support_check)
+        << "Improved support check is disabled for queries under 4K.";
     bool force_secure_pipeline_under_tunnel_mode = false;
     if (enable_tunnel_mode &&
         IsTunnelModeSupported(creation_parameters,
-                              &force_secure_pipeline_under_tunnel_mode)) {
-      tunnel_mode_audio_session_id =
-          GenerateAudioSessionId(creation_parameters);
+                              &force_secure_pipeline_under_tunnel_mode,
+                              force_improved_support_check)) {
+      tunnel_mode_audio_session_id = GenerateAudioSessionId(
+          creation_parameters, force_improved_support_check);
     }
 
     if (tunnel_mode_audio_session_id == -1) {
       SB_LOG(INFO) << "Create non-tunnel mode pipeline.";
     } else {
+      SB_LOG_IF(INFO, !kForceResetSurfaceUnderTunnelMode)
+          << "`kForceResetSurfaceUnderTunnelMode` is set to false, the video "
+             "surface will not be forced to reset after "
+             "tunneled playback.";
       SB_LOG(INFO) << "Create tunnel mode pipeline with audio session id "
                    << tunnel_mode_audio_session_id << '.';
     }
@@ -386,6 +413,11 @@
           audio_mime_type.GetParamBoolValue("enableaudiodevicecallback", true);
       SB_LOG(INFO) << "AudioDeviceCallback is "
                    << (enable_audio_device_callback ? "enabled." : "disabled.");
+      bool enable_pcm_content_type_movie =
+          audio_mime_type.GetParamBoolValue("enablepcmcontenttypemovie", true);
+      SB_LOG(INFO) << "AudioAttributes::CONTENT_TYPE_MOVIE is "
+                   << (enable_pcm_content_type_movie ? "enabled" : "disabled")
+                   << " for non-tunneled PCM audio playback.";
 
       if (tunnel_mode_audio_session_id != -1) {
         *audio_renderer_sink = TryToCreateTunnelModeAudioRendererSink(
@@ -396,8 +428,8 @@
         }
       }
       if (!*audio_renderer_sink) {
-        audio_renderer_sink->reset(
-            new AudioRendererSinkAndroid(enable_audio_device_callback));
+        audio_renderer_sink->reset(new AudioRendererSinkAndroid(
+            enable_audio_device_callback, enable_pcm_content_type_movie));
       }
     }
 
@@ -411,9 +443,10 @@
         force_secure_pipeline_under_tunnel_mode = false;
       }
 
-      scoped_ptr<VideoDecoder> video_decoder_impl = CreateVideoDecoder(
-          creation_parameters, tunnel_mode_audio_session_id,
-          force_secure_pipeline_under_tunnel_mode, error_message);
+      scoped_ptr<VideoDecoder> video_decoder_impl =
+          CreateVideoDecoder(creation_parameters, tunnel_mode_audio_session_id,
+                             force_secure_pipeline_under_tunnel_mode,
+                             force_improved_support_check, error_message);
       if (video_decoder_impl) {
         *video_render_algorithm = video_decoder_impl->GetRenderAlgorithm();
         *video_renderer_sink = video_decoder_impl->GetSink();
@@ -457,8 +490,8 @@
       const CreationParameters& creation_parameters,
       int tunnel_mode_audio_session_id,
       bool force_secure_pipeline_under_tunnel_mode,
+      bool force_improved_support_check,
       std::string* error_message) {
-    using starboard::shared::starboard::media::MimeType;
     // Use mime param to determine endianness of HDR metadata. If param is
     // missing or invalid it defaults to Little Endian.
     MimeType video_mime_type(creation_parameters.video_mime());
@@ -479,7 +512,8 @@
         creation_parameters.decode_target_graphics_context_provider(),
         creation_parameters.max_video_capabilities(),
         tunnel_mode_audio_session_id, force_secure_pipeline_under_tunnel_mode,
-        force_big_endian_hdr_metadata, error_message));
+        kForceResetSurfaceUnderTunnelMode, force_big_endian_hdr_metadata,
+        force_improved_support_check, error_message));
     if (creation_parameters.video_codec() == kSbMediaVideoCodecAv1 ||
         video_decoder->is_decoder_created()) {
       return video_decoder.Pass();
@@ -490,7 +524,8 @@
   }
 
   bool IsTunnelModeSupported(const CreationParameters& creation_parameters,
-                             bool* force_secure_pipeline_under_tunnel_mode) {
+                             bool* force_secure_pipeline_under_tunnel_mode,
+                             const bool force_improved_support_check) {
     SB_DCHECK(force_secure_pipeline_under_tunnel_mode);
     *force_secure_pipeline_under_tunnel_mode = false;
 
@@ -534,8 +569,8 @@
     bool is_encrypted = !!j_media_crypto;
     if (env->CallStaticBooleanMethodOrAbort(
             "dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor",
-            "(Ljava/lang/String;ZZZIIII)Z", j_mime.Get(), is_encrypted, false,
-            true, 0, 0, 0, 0) == JNI_TRUE) {
+            "(Ljava/lang/String;ZZZZIIII)Z", j_mime.Get(), is_encrypted, false,
+            true, force_improved_support_check, 0, 0, 0, 0) == JNI_TRUE) {
       return true;
     }
 
@@ -544,8 +579,9 @@
       auto support_tunnel_mode_under_secure_pipeline =
           env->CallStaticBooleanMethodOrAbort(
               "dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor",
-              "(Ljava/lang/String;ZZZIIII)Z", j_mime.Get(), kIsEncrypted, false,
-              true, 0, 0, 0, 0) == JNI_TRUE;
+              "(Ljava/lang/String;ZZZZIIII)Z", j_mime.Get(), kIsEncrypted,
+              false, true, force_improved_support_check, 0, 0, 0,
+              0) == JNI_TRUE;
       if (support_tunnel_mode_under_secure_pipeline) {
         *force_secure_pipeline_under_tunnel_mode = true;
         return true;
@@ -557,10 +593,12 @@
     return false;
   }
 
-  int GenerateAudioSessionId(const CreationParameters& creation_parameters) {
+  int GenerateAudioSessionId(const CreationParameters& creation_parameters,
+                             const bool force_improved_support_check) {
     bool force_secure_pipeline_under_tunnel_mode = false;
     SB_DCHECK(IsTunnelModeSupported(creation_parameters,
-                                    &force_secure_pipeline_under_tunnel_mode));
+                                    &force_secure_pipeline_under_tunnel_mode,
+                                    force_improved_support_check));
 
     JniEnvExt* env = JniEnvExt::Get();
     ScopedLocalJavaRef<jobject> j_audio_output_manager(
@@ -585,7 +623,7 @@
       const CreationParameters& creation_parameters,
       bool enable_audio_device_callback) {
     scoped_ptr<AudioRendererSink> audio_sink(new AudioRendererSinkAndroid(
-        enable_audio_device_callback, tunnel_mode_audio_session_id));
+        enable_audio_device_callback, true, tunnel_mode_audio_session_id));
     // We need to double check if the audio sink can actually be created.
     int max_cached_frames, min_frames_per_append;
     GetAudioRendererParams(creation_parameters, &max_cached_frames,
diff --git a/src/starboard/android/shared/starboard_platform.gypi b/src/starboard/android/shared/starboard_platform.gypi
index 95f0ba0..176d978 100644
--- a/src/starboard/android/shared/starboard_platform.gypi
+++ b/src/starboard/android/shared/starboard_platform.gypi
@@ -118,6 +118,8 @@
         'file_truncate.cc',
         'file_write.cc',
         'get_home_directory.cc',
+        'graphics.cc',
+        'graphics.h',
         'input_events_generator.cc',
         'input_events_generator.h',
         'jni_env_ext.cc',
diff --git a/src/starboard/android/shared/system_get_extensions.cc b/src/starboard/android/shared/system_get_extensions.cc
index 7e661ed..9d817f1 100644
--- a/src/starboard/android/shared/system_get_extensions.cc
+++ b/src/starboard/android/shared/system_get_extensions.cc
@@ -15,10 +15,12 @@
 #include "starboard/system.h"
 
 #include "cobalt/extension/configuration.h"
+#include "cobalt/extension/graphics.h"
 #include "cobalt/extension/media_session.h"
 #include "cobalt/extension/platform_service.h"
 #include "starboard/android/shared/android_media_session_client.h"
 #include "starboard/android/shared/configuration.h"
+#include "starboard/android/shared/graphics.h"
 #include "starboard/android/shared/platform_service.h"
 #include "starboard/common/log.h"
 #include "starboard/common/string.h"
@@ -33,5 +35,8 @@
   if (strcmp(name, kCobaltExtensionMediaSessionName) == 0) {
     return starboard::android::shared::GetMediaSessionApi();
   }
+  if (strcmp(name, kCobaltExtensionGraphicsName) == 0) {
+    return starboard::android::shared::GetGraphicsApi();
+  }
   return NULL;
 }
diff --git a/src/starboard/android/shared/time_zone_get_name.cc b/src/starboard/android/shared/time_zone_get_name.cc
index b2b6c41..f058c09 100644
--- a/src/starboard/android/shared/time_zone_get_name.cc
+++ b/src/starboard/android/shared/time_zone_get_name.cc
@@ -16,8 +16,21 @@
 
 #include <time.h>
 
-const char* SbTimeZoneGetName() {
-  // Note tzset() is called in ApplicationAndroid::Initialize()
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
 
-  return tzname[0];
+using starboard::android::shared::JniEnvExt;
+using starboard::android::shared::ScopedLocalJavaRef;
+
+const char* SbTimeZoneGetName() {
+  static char s_time_zone_id[64];
+  // Note tzset() is called in ApplicationAndroid::Initialize()
+  JniEnvExt* env = JniEnvExt::Get();
+  ScopedLocalJavaRef<jstring> result(env->CallStarboardObjectMethodOrAbort(
+      "getTimeZoneId", "()Ljava/lang/String;"));
+  std::string time_zone_id = env->GetStringStandardUTFOrAbort(result.Get());
+  time_zone_id.push_back('\0');
+  strncpy(s_time_zone_id, time_zone_id.c_str(), sizeof(s_time_zone_id));
+  s_time_zone_id[sizeof(s_time_zone_id) - 1] = 0;
+  return s_time_zone_id;
 }
diff --git a/src/starboard/android/shared/video_decoder.cc b/src/starboard/android/shared/video_decoder.cc
index 775496c..5078989 100644
--- a/src/starboard/android/shared/video_decoder.cc
+++ b/src/starboard/android/shared/video_decoder.cc
@@ -218,7 +218,9 @@
                            const char* max_video_capabilities,
                            int tunnel_mode_audio_session_id,
                            bool force_secure_pipeline_under_tunnel_mode,
+                           bool force_reset_surface_under_tunnel_mode,
                            bool force_big_endian_hdr_metadata,
+                           bool force_improved_support_check,
                            std::string* error_message)
     : video_codec_(video_codec),
       drm_system_(static_cast<DrmSystem*>(drm_system)),
@@ -226,11 +228,14 @@
       decode_target_graphics_context_provider_(
           decode_target_graphics_context_provider),
       tunnel_mode_audio_session_id_(tunnel_mode_audio_session_id),
+      force_reset_surface_under_tunnel_mode_(
+          force_reset_surface_under_tunnel_mode),
       has_new_texture_available_(false),
       surface_condition_variable_(surface_destroy_mutex_),
       require_software_codec_(max_video_capabilities &&
                               strlen(max_video_capabilities) > 0),
-      force_big_endian_hdr_metadata_(force_big_endian_hdr_metadata) {
+      force_big_endian_hdr_metadata_(force_big_endian_hdr_metadata),
+      force_improved_support_check_(force_improved_support_check) {
   SB_DCHECK(error_message);
 
   if (tunnel_mode_audio_session_id != -1) {
@@ -264,7 +269,11 @@
 
 VideoDecoder::~VideoDecoder() {
   TeardownCodec();
-  ClearVideoWindow();
+  if (tunnel_mode_audio_session_id_ != -1) {
+    ClearVideoWindow(force_reset_surface_under_tunnel_mode_);
+  } else {
+    ClearVideoWindow(false);
+  }
 
   if (!require_software_codec_) {
     number_of_hardware_decoders_--;
@@ -559,7 +568,7 @@
       require_software_codec_,
       std::bind(&VideoDecoder::OnTunnelModeFrameRendered, this, _1),
       tunnel_mode_audio_session_id_, force_big_endian_hdr_metadata_,
-      error_message));
+      force_improved_support_check_, error_message));
   if (media_decoder_->is_valid()) {
     if (error_cb_) {
       media_decoder_->Initialize(
diff --git a/src/starboard/android/shared/video_decoder.h b/src/starboard/android/shared/video_decoder.h
index cc5dc1e..7859aa2 100644
--- a/src/starboard/android/shared/video_decoder.h
+++ b/src/starboard/android/shared/video_decoder.h
@@ -67,7 +67,9 @@
                const char* max_video_capabilities,
                int tunnel_mode_audio_session_id,
                bool force_secure_pipeline_under_tunnel_mode,
+               bool force_reset_surface_under_tunnel_mode,
                bool force_big_endian_hdr_metadata,
+               bool force_improved_support_check,
                std::string* error_message);
   ~VideoDecoder() override;
 
@@ -136,10 +138,18 @@
   // the main player and SW decoder for sub players.
   const bool require_software_codec_;
 
+  // Forces the use of specific Android APIs (isSizeSupported() and
+  // areSizeAndRateSupported()) to determine format support.
+  const bool force_improved_support_check_;
+
   // Force endianness of HDR Metadata.
   const bool force_big_endian_hdr_metadata_;
 
   const int tunnel_mode_audio_session_id_ = -1;
+
+  // Force resetting the video surface after tunnel mode playback, which
+  // prevents video distortion on some devices.
+  const bool force_reset_surface_under_tunnel_mode_;
   // On some platforms tunnel mode is only supported in the secure pipeline.  So
   // we create a dummy drm system to force the video playing in secure pipeline
   // to enable tunnel mode.
diff --git a/src/starboard/android/shared/video_window.cc b/src/starboard/android/shared/video_window.cc
index 88b7037..81d898d 100644
--- a/src/starboard/android/shared/video_window.cc
+++ b/src/starboard/android/shared/video_window.cc
@@ -14,10 +14,10 @@
 
 #include "starboard/android/shared/video_window.h"
 
-#include <android/native_window.h>
-#include <android/native_window_jni.h>
 #include <EGL/egl.h>
 #include <GLES2/gl2.h>
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
 #include <jni.h>
 
 #include "starboard/android/shared/jni_env_ext.h"
@@ -45,6 +45,87 @@
 // vertical video.
 bool g_reset_surface_on_clear_window = false;
 
+void ClearNativeWindow(ANativeWindow* native_window) {
+  EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+  eglInitialize(display, NULL, NULL);
+  if (display == EGL_NO_DISPLAY) {
+    SB_DLOG(ERROR) << "Found no EGL display in ClearVideoWindow";
+    return;
+  }
+
+  const EGLint kAttributeList[] = {
+      EGL_RED_SIZE,
+      8,
+      EGL_GREEN_SIZE,
+      8,
+      EGL_BLUE_SIZE,
+      8,
+      EGL_ALPHA_SIZE,
+      8,
+      EGL_RENDERABLE_TYPE,
+      EGL_OPENGL_ES2_BIT,
+      EGL_NONE,
+      0,
+      EGL_NONE,
+  };
+
+  // First, query how many configs match the given attribute list.
+  EGLint num_configs = 0;
+  EGL_CALL(eglChooseConfig(display, kAttributeList, NULL, 0, &num_configs));
+  SB_DCHECK(num_configs != 0);
+
+  // Allocate space to receive the matching configs and retrieve them.
+  EGLConfig* configs = new EGLConfig[num_configs];
+  EGL_CALL(eglChooseConfig(display, kAttributeList, configs, num_configs,
+                           &num_configs));
+
+  EGLNativeWindowType egl_native_window =
+      static_cast<EGLNativeWindowType>(native_window);
+  EGLConfig config;
+
+  // Find the first config that successfully allow a window surface to be
+  // created.
+  EGLSurface surface;
+  for (int config_number = 0; config_number < num_configs; ++config_number) {
+    config = configs[config_number];
+    surface = eglCreateWindowSurface(display, config, egl_native_window, NULL);
+    if (eglGetError() == EGL_SUCCESS)
+      break;
+  }
+  if (surface == EGL_NO_SURFACE) {
+    SB_DLOG(ERROR) << "Found no EGL surface in ClearVideoWindow";
+    return;
+  }
+  SB_DCHECK(surface != EGL_NO_SURFACE);
+
+  delete[] configs;
+
+  // Create an OpenGL ES 2.0 context.
+  EGLContext context = EGL_NO_CONTEXT;
+  EGLint context_attrib_list[] = {
+      EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE,
+  };
+  context =
+      eglCreateContext(display, config, EGL_NO_CONTEXT, context_attrib_list);
+  SB_DCHECK(eglGetError() == EGL_SUCCESS);
+  SB_DCHECK(context != EGL_NO_CONTEXT);
+
+  /* connect the context to the surface */
+  EGL_CALL(eglMakeCurrent(display, surface, surface, context));
+
+  GL_CALL(glClearColor(0, 0, 0, 1));
+  GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
+  GL_CALL(glFlush());
+  EGL_CALL(eglSwapBuffers(display, surface));
+
+  // Cleanup all used resources.
+  EGL_CALL(
+      eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+  EGL_CALL(eglDestroyContext(display, context));
+  EGL_CALL(eglDestroySurface(display, surface));
+  EGL_CALL(eglTerminate(display));
+}
+
 }  // namespace
 
 extern "C" SB_EXPORT_PLATFORM void
@@ -68,6 +149,7 @@
   if (surface) {
     g_j_video_surface = env->NewGlobalRef(surface);
     g_native_video_window = ANativeWindow_fromSurface(env, surface);
+    ClearNativeWindow(g_native_video_window);
   }
 }
 
@@ -118,7 +200,7 @@
   }
 }
 
-void VideoSurfaceHolder::ClearVideoWindow() {
+void VideoSurfaceHolder::ClearVideoWindow(bool force_reset_surface) {
   // Lock *GetViewSurfaceMutex() here, to avoid releasing g_native_video_window
   // during painting.
   ScopedLock lock(*GetViewSurfaceMutex());
@@ -128,7 +210,11 @@
     return;
   }
 
-  if (g_reset_surface_on_clear_window) {
+  if (force_reset_surface) {
+    JniEnvExt::Get()->CallStarboardVoidMethodOrAbort("resetVideoSurface",
+                                                     "()V");
+    return;
+  } else if (g_reset_surface_on_clear_window) {
     int width = ANativeWindow_getWidth(g_native_video_window);
     int height = ANativeWindow_getHeight(g_native_video_window);
     if (width <= height) {
@@ -138,84 +224,7 @@
     }
   }
 
-  EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-  eglInitialize(display, NULL, NULL);
-  if (display == EGL_NO_DISPLAY) {
-    SB_DLOG(ERROR) << "Found no EGL display in ClearVideoWindow";
-    return;
-  }
-
-  const EGLint kAttributeList[] = {
-      EGL_RED_SIZE,
-      8,
-      EGL_GREEN_SIZE,
-      8,
-      EGL_BLUE_SIZE,
-      8,
-      EGL_ALPHA_SIZE,
-      8,
-      EGL_RENDERABLE_TYPE,
-      EGL_OPENGL_ES2_BIT,
-      EGL_NONE,
-      0,
-      EGL_NONE,
-  };
-
-  // First, query how many configs match the given attribute list.
-  EGLint num_configs = 0;
-  EGL_CALL(eglChooseConfig(display, kAttributeList, NULL, 0, &num_configs));
-  SB_DCHECK(num_configs != 0);
-
-  // Allocate space to receive the matching configs and retrieve them.
-  EGLConfig* configs = new EGLConfig[num_configs];
-  EGL_CALL(eglChooseConfig(display, kAttributeList, configs, num_configs,
-                           &num_configs));
-
-  EGLNativeWindowType native_window =
-      static_cast<EGLNativeWindowType>(g_native_video_window);
-  EGLConfig config;
-
-  // Find the first config that successfully allow a window surface to be
-  // created.
-  EGLSurface surface;
-  for (int config_number = 0; config_number < num_configs; ++config_number) {
-    config = configs[config_number];
-    surface = eglCreateWindowSurface(display, config, native_window, NULL);
-    if (eglGetError() == EGL_SUCCESS)
-      break;
-  }
-  if (surface == EGL_NO_SURFACE) {
-    SB_DLOG(ERROR) << "Found no EGL surface in ClearVideoWindow";
-    return;
-  }
-  SB_DCHECK(surface != EGL_NO_SURFACE);
-
-  delete[] configs;
-
-  // Create an OpenGL ES 2.0 context.
-  EGLContext context = EGL_NO_CONTEXT;
-  EGLint context_attrib_list[] = {
-      EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE,
-  };
-  context =
-      eglCreateContext(display, config, EGL_NO_CONTEXT, context_attrib_list);
-  SB_DCHECK(eglGetError() == EGL_SUCCESS);
-  SB_DCHECK(context != EGL_NO_CONTEXT);
-
-  /* connect the context to the surface */
-  EGL_CALL(eglMakeCurrent(display, surface, surface, context));
-
-  GL_CALL(glClearColor(0, 0, 0, 1));
-  GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
-  GL_CALL(glFlush());
-  EGL_CALL(eglSwapBuffers(display, surface));
-
-  // Cleanup all used resources.
-  EGL_CALL(
-      eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
-  EGL_CALL(eglDestroyContext(display, context));
-  EGL_CALL(eglDestroySurface(display, surface));
-  EGL_CALL(eglTerminate(display));
+  ClearNativeWindow(g_native_video_window);
 }
 
 }  // namespace shared
diff --git a/src/starboard/android/shared/video_window.h b/src/starboard/android/shared/video_window.h
index 17db407..4196cb9 100644
--- a/src/starboard/android/shared/video_window.h
+++ b/src/starboard/android/shared/video_window.h
@@ -48,7 +48,7 @@
   bool GetVideoWindowSize(int* width, int* height);
 
   // Clear the video window by painting it Black.
-  void ClearVideoWindow();
+  void ClearVideoWindow(bool force_reset_surface);
 };
 
 }  // namespace shared
diff --git a/src/starboard/configuration.h b/src/starboard/configuration.h
index fafa6e8..0aa01c1 100644
--- a/src/starboard/configuration.h
+++ b/src/starboard/configuration.h
@@ -65,6 +65,11 @@
 //   //   exposes functionality for my new feature.
 //   #define SB_MY_EXPERIMENTAL_FEATURE_VERSION SB_EXPERIMENTAL_API_VERSION
 
+// Introduce a new format kSbDecodeTargetFormat3Plane10BitYUVI420Compact.
+//   A decoder target format consisting of 10bit Y, U, and V planes.
+#define SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION \
+  SB_EXPERIMENTAL_API_VERSION
+
 // --- Release Candidate Feature Defines -------------------------------------
 
 // --- Common Detected Features ----------------------------------------------
diff --git a/src/starboard/decode_target.h b/src/starboard/decode_target.h
index ce00b88..9c18f79 100644
--- a/src/starboard/decode_target.h
+++ b/src/starboard/decode_target.h
@@ -136,9 +136,16 @@
   kSbDecodeTargetFormat3PlaneYUVI420,
 
   // A decoder target format consisting of 10bit Y, U, and V planes, in that
-  // order.
+  // order. Each pixel is stored in 16 bits.
   kSbDecodeTargetFormat3Plane10BitYUVI420,
 
+#if SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+  // A decoder target format consisting of 10bit Y, U, and V planes, in that
+  // order. The plane data is stored in a compact format. Every three 10-bit
+  // pixels are packed into 32 bits.
+  kSbDecodeTargetFormat3Plane10BitYUVI420Compact,
+#endif  // SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+
   // A decoder target format consisting of a single plane with pixels laid out
   // in the format UYVY.  Since there are two Y values per sample, but only one
   // U value and only one V value, horizontally the Y resolution is twice the
@@ -341,6 +348,9 @@
     case kSbDecodeTargetFormat2PlaneYUVNV12:
       return 2;
     case kSbDecodeTargetFormat3Plane10BitYUVI420:
+#if SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+    case kSbDecodeTargetFormat3Plane10BitYUVI420Compact:
+#endif  // SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
     case kSbDecodeTargetFormat3PlaneYUVI420:
       return 3;
     default:
diff --git a/src/starboard/elf_loader/elf_loader.cc b/src/starboard/elf_loader/elf_loader.cc
index f75f0f7..ba37533 100644
--- a/src/starboard/elf_loader/elf_loader.cc
+++ b/src/starboard/elf_loader/elf_loader.cc
@@ -59,7 +59,9 @@
 bool ElfLoader::Load(const std::string& library_path,
                      const std::string& content_path,
                      bool is_relative_path,
-                     const void* (*custom_get_extension)(const char* name)) {
+                     const void* (*custom_get_extension)(const char* name),
+                     bool use_compression,
+                     bool use_memory_mapped_file) {
   if (is_relative_path) {
     library_path_ = MakeRelativeToContentPath(library_path);
     content_path_ = MakeRelativeToContentPath(content_path);
@@ -76,7 +78,8 @@
                           custom_get_extension);
   SB_LOG(INFO) << "evergreen_config: content_path=" << content_path_;
   SbTime start_time = SbTimeGetMonotonicNow();
-  bool res = impl_->Load(library_path_.c_str(), custom_get_extension);
+  bool res = impl_->Load(library_path_.c_str(), custom_get_extension,
+                         use_compression, use_memory_mapped_file);
   SbTime end_time = SbTimeGetMonotonicNow();
   SB_LOG(INFO) << "Loading took: "
                << (end_time - start_time) / kSbTimeMillisecond << " ms";
diff --git a/src/starboard/elf_loader/elf_loader.h b/src/starboard/elf_loader/elf_loader.h
index 587c7a6..f8f9e26 100644
--- a/src/starboard/elf_loader/elf_loader.h
+++ b/src/starboard/elf_loader/elf_loader.h
@@ -38,11 +38,15 @@
   // Loads the shared library. Returns false if |library_path| or |content_path|
   // is empty, or if the library could not be loaded.
   // An optional |custom_get_extension| function pointer can be passed in order
-  // to override the |SbSystemGetExtension| function.
+  // to override the |SbSystemGetExtension| function. The flags
+  // |use_compression| and |use_memory_mapped_file| are not compatible so only
+  // one of those can be turned on.
   bool Load(const std::string& library_path,
             const std::string& content_path,
             bool is_relative_path,
-            const void* (*custom_get_extension)(const char* name) = NULL);
+            const void* (*custom_get_extension)(const char* name) = NULL,
+            bool use_compression = false,
+            bool use_memory_mapped_file = false);
 
   // Looks up the symbol address in the
   // shared library.
diff --git a/src/starboard/elf_loader/elf_loader_impl.cc b/src/starboard/elf_loader/elf_loader_impl.cc
index cf392a9..4446e07 100644
--- a/src/starboard/elf_loader/elf_loader_impl.cc
+++ b/src/starboard/elf_loader/elf_loader_impl.cc
@@ -47,10 +47,16 @@
 #endif
 }
 
-bool ElfLoaderImpl::Load(
-    const char* name,
-    const void* (*custom_get_extension)(const char* name)) {
-  if (EndsWith(name, kCompressionSuffix)) {
+bool ElfLoaderImpl::Load(const char* name,
+                         const void* (*custom_get_extension)(const char* name),
+                         bool use_compression,
+                         bool use_memory_mapped_files) {
+  if (use_compression && use_memory_mapped_files) {
+    SB_LOG(ERROR) << "Loading " << name
+                  << " Compression is not supported with memory mapped files.";
+    return false;
+  }
+  if (use_compression && EndsWith(name, kCompressionSuffix)) {
     elf_file_.reset(new LZ4FileImpl());
     SB_LOG(INFO) << "Loading " << name << " using compression";
   } else {
@@ -67,13 +73,18 @@
 
   SB_DLOG(INFO) << "Loaded ELF header";
 
-  const auto* memory_mapped_file_extension =
-      reinterpret_cast<const CobaltExtensionMemoryMappedFileApi*>(
-          SbSystemGetExtension(kCobaltExtensionMemoryMappedFileName));
-  if (memory_mapped_file_extension &&
-      strcmp(memory_mapped_file_extension->name,
-             kCobaltExtensionMemoryMappedFileName) == 0 &&
-      memory_mapped_file_extension->version >= 1) {
+  if (use_memory_mapped_files) {
+    const auto* memory_mapped_file_extension =
+        reinterpret_cast<const CobaltExtensionMemoryMappedFileApi*>(
+            SbSystemGetExtension(kCobaltExtensionMemoryMappedFileName));
+
+    if (!memory_mapped_file_extension ||
+        strcmp(memory_mapped_file_extension->name,
+               kCobaltExtensionMemoryMappedFileName) != 0 ||
+        memory_mapped_file_extension->version < 1) {
+      SB_LOG(ERROR) << "CobaltExtensionMemoryMappedFileApi not implemented";
+      return false;
+    }
     program_table_.reset(new ProgramTable(memory_mapped_file_extension));
   } else {
     program_table_.reset(new ProgramTable(nullptr));
diff --git a/src/starboard/elf_loader/elf_loader_impl.h b/src/starboard/elf_loader/elf_loader_impl.h
index 84c43c7..aad97b1 100644
--- a/src/starboard/elf_loader/elf_loader_impl.h
+++ b/src/starboard/elf_loader/elf_loader_impl.h
@@ -34,7 +34,9 @@
  public:
   ElfLoaderImpl();
   bool Load(const char* file_name,
-            const void* (*custom_get_extension)(const char* name));
+            const void* (*custom_get_extension)(const char* name),
+            bool use_compression,
+            bool use_memory_mapped_file);
   void* LookupSymbol(const char* symbol);
   ~ElfLoaderImpl();
 
diff --git a/src/starboard/elf_loader/elf_loader_sys_impl.cc b/src/starboard/elf_loader/elf_loader_sys_impl.cc
index fd1c0a2..18fefb9 100644
--- a/src/starboard/elf_loader/elf_loader_sys_impl.cc
+++ b/src/starboard/elf_loader/elf_loader_sys_impl.cc
@@ -24,9 +24,10 @@
 
 ElfLoaderImpl::ElfLoaderImpl() {}
 
-bool ElfLoaderImpl::Load(
-    const char* name,
-    const void* (*custom_get_extension)(const char* name)) {
+bool ElfLoaderImpl::Load(const char* name,
+                         const void* (*custom_get_extension)(const char* name),
+                         bool use_compression,
+                         bool use_memory_mapped_file) {
   SB_LOG(INFO) << "Loading: " << name;
 
   // Creating the instance forces the binary to keep all the symbols.
diff --git a/src/starboard/elf_loader/elf_loader_sys_impl.h b/src/starboard/elf_loader/elf_loader_sys_impl.h
index 7f148a5..5913c0d 100644
--- a/src/starboard/elf_loader/elf_loader_sys_impl.h
+++ b/src/starboard/elf_loader/elf_loader_sys_impl.h
@@ -25,7 +25,9 @@
  public:
   ElfLoaderImpl();
   bool Load(const char* file_name,
-            const void* (*custom_get_extension)(const char* name) = NULL);
+            const void* (*custom_get_extension)(const char* name) = NULL,
+            bool use_compression = false,
+            bool use_memory_mapped_file = false);
   void* LookupSymbol(const char* symbol);
   ~ElfLoaderImpl();
 
diff --git a/src/starboard/elf_loader/program_table.cc b/src/starboard/elf_loader/program_table.cc
index 61d823c..4bafa4a 100644
--- a/src/starboard/elf_loader/program_table.cc
+++ b/src/starboard/elf_loader/program_table.cc
@@ -80,7 +80,7 @@
   SB_DLOG(INFO) << "page_max - page_min=" << page_max - page_min;
 
   if (memory_mapped_file_extension_) {
-    SB_DLOG(INFO) << "Memory mapped file for the program header";
+    SB_LOG(INFO) << "Using memory mapped file for the program header";
     phdr_mmap_ = memory_mapped_file_extension_->MemoryMapFile(
         NULL, elf_file->GetName().c_str(), kSbMemoryMapProtectRead, page_min,
         phdr_size_);
diff --git a/src/starboard/evergreen/testing/tests/clean_drain_file_on_suspend_test.sh b/src/starboard/evergreen/testing/tests/clean_drain_file_on_suspend_test.sh
new file mode 100755
index 0000000..be0e547
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/clean_drain_file_on_suspend_test.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="CleanDrainFilesOnSuspend"
+TEST_FILE="test.html"
+
+function run_test() {
+  clear_storage
+
+  LOG="${TEST_NAME}.0.log"
+  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${LOG}" LOADER --update_check_delay_seconds=1
+
+  VAR=1
+
+  for i in {1..5}
+  do
+    log "info" "${TEST_NAME} #${VAR}"
+    sleep ${VAR}
+
+    # suspend
+    kill -s USR1 "${LOADER}"
+    if [[ $? -ne 0 ]]; then
+      log "warning" "Failed to suspend"
+      break
+    fi
+
+    sleep 5
+
+    if ls ${STORAGE_DIR}/installation_?/d_* 1> /dev/null 2>&1; then
+      echo "drain file wasn't cleaned up ."
+      return 1
+    fi
+
+    # resume
+    kill -s CONT "${LOADER}"
+    if [[ $? -ne 0 ]]; then
+      log "warning" "Failed to resume"
+      break
+    fi
+
+    let "VAR=${VAR}+1"
+  done
+  return 0
+
+}
diff --git a/src/starboard/evergreen/testing/tests/evergreen_lite_test.sh b/src/starboard/evergreen/testing/tests/evergreen_lite_test.sh
index 42d37a3..106b145 100755
--- a/src/starboard/evergreen/testing/tests/evergreen_lite_test.sh
+++ b/src/starboard/evergreen/testing/tests/evergreen_lite_test.sh
@@ -46,5 +46,11 @@
     return 1
   fi
 
+  cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.2.log" "Loaded WebModule" "--evergreen_lite"
+
+  if [[ $? -ne 0 ]]; then
+    log "error" "Failed to run system installation"
+    return 1
+  fi
   return 0
 }
diff --git a/src/starboard/evergreen/testing/tests/out_of_storage_test.sh b/src/starboard/evergreen/testing/tests/out_of_storage_test.sh
index 3e13e55..03c0feb 100755
--- a/src/starboard/evergreen/testing/tests/out_of_storage_test.sh
+++ b/src/starboard/evergreen/testing/tests/out_of_storage_test.sh
@@ -45,9 +45,21 @@
 
   OLD_TIMEOUT="${TIMEOUT}"
   TIMEOUT=300
+  RESULT=0
 
-  cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "Failed to update, log \"error\" code is 12"
+  cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "Failed to update, error code is 22"
+  if [[ $? -ne 0 ]]; then
+    log "error" "Failed to run out of storage"
+    RESULT=1
+  fi
 
+  if [[ $RESULT -ne 1 ]]; then
+    cycle_cobalt "file:///tests/${TEST_FILE}?channel=test\&min_storage=5" "${TEST_NAME}.0.log" "Failed to update, error code is 200"
+    if [[ $? -ne 0 ]]; then
+      log "error" "Failed to run out of storage"
+      RESULT=1
+    fi
+  fi
   # Remove the symbolic link.
   run_command "rm -f ${STORAGE_DIR}" 1> /dev/null
 
@@ -56,12 +68,7 @@
     run_command "mv \"${STORAGE_DIR}.tmp\" \"${STORAGE_DIR}\"" 1> /dev/null
   fi
 
-  if [[ $? -ne 0 ]]; then
-    log "error" "Failed to run out of storage"
-    return 1
-  fi
-
   TIMEOUT="${OLD_TIMEOUT}"
 
-  return 0
+  return $RESULT
 }
diff --git a/src/starboard/evergreen/testing/tests/suspend_resume_test.sh b/src/starboard/evergreen/testing/tests/suspend_resume_test.sh
index 35b549f..6d2dcfa 100755
--- a/src/starboard/evergreen/testing/tests/suspend_resume_test.sh
+++ b/src/starboard/evergreen/testing/tests/suspend_resume_test.sh
@@ -39,7 +39,7 @@
   clear_storage
 
   LOG="${TEST_NAME}.0.log"
-  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${LOG}" LOADER --update_check_delay=1
+  start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${LOG}" LOADER --update_check_delay_seconds=1
 
   VAR=1
 
diff --git a/src/starboard/evergreen/testing/tests/test.html b/src/starboard/evergreen/testing/tests/test.html
index e53f706..97f205a 100644
--- a/src/starboard/evergreen/testing/tests/test.html
+++ b/src/starboard/evergreen/testing/tests/test.html
@@ -53,6 +53,9 @@
   }
 
   query.forEach(part => {
+    if (part.startsWith("min_storage=")) {
+      window.h5vcc.settings.set("Updater.MinFreeSpaceBytes", part.split("=")[1]);
+    }
     if (changeChannel) {
       return;
     }
@@ -61,6 +64,7 @@
       targetChannel = part.split("=")[1];
       changeChannel = setInterval(tryChangeChannel, 500);
     }
+
   });
 </script>
 </html>
diff --git a/src/starboard/evergreen/testing/tests/use_mmap_file_test.sh b/src/starboard/evergreen/testing/tests/use_mmap_file_test.sh
new file mode 100644
index 0000000..f82cd76
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/use_mmap_file_test.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="UseMemoryMappedFile"
+TEST_FILE="test.html"
+
+function run_test() {
+  clear_storage
+
+  cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.2.log" "Using memory mapped file for the program header" "--loader_use_mmap_file"
+
+  if [[ $? -ne 0 ]]; then
+    log "error" "Failed to run system installation"
+    return 1
+  fi
+
+  cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.2.log" "Using memory mapped file for the program header" "--loader_use_mmap_file --evergreen_lite"
+
+  if [[ $? -ne 0 ]]; then
+    log "error" "Failed to run system installation"
+    return 1
+  fi
+  return 0
+}
diff --git a/src/starboard/linux/shared/BUILD.gn b/src/starboard/linux/shared/BUILD.gn
index c720b16..0dde49f 100644
--- a/src/starboard/linux/shared/BUILD.gn
+++ b/src/starboard/linux/shared/BUILD.gn
@@ -176,6 +176,8 @@
     "//starboard/shared/posix/file_seek.cc",
     "//starboard/shared/posix/file_truncate.cc",
     "//starboard/shared/posix/file_write.cc",
+    "//starboard/shared/posix/free_space.cc",
+    "//starboard/shared/posix/free_space.h",
     "//starboard/shared/posix/log.cc",
     "//starboard/shared/posix/log_flush.cc",
     "//starboard/shared/posix/log_format.cc",
diff --git a/src/starboard/linux/shared/starboard_platform.gypi b/src/starboard/linux/shared/starboard_platform.gypi
index fef07e1..584b1fd 100644
--- a/src/starboard/linux/shared/starboard_platform.gypi
+++ b/src/starboard/linux/shared/starboard_platform.gypi
@@ -147,6 +147,8 @@
       '<(DEPTH)/starboard/shared/posix/file_seek.cc',
       '<(DEPTH)/starboard/shared/posix/file_truncate.cc',
       '<(DEPTH)/starboard/shared/posix/file_write.cc',
+      '<(DEPTH)/starboard/shared/posix/free_space.cc',
+      '<(DEPTH)/starboard/shared/posix/free_space.h',
       '<(DEPTH)/starboard/shared/posix/log_flush.cc',
       '<(DEPTH)/starboard/shared/posix/log_format.cc',
       '<(DEPTH)/starboard/shared/posix/log_is_tty.cc',
diff --git a/src/starboard/linux/shared/system_get_extensions.cc b/src/starboard/linux/shared/system_get_extensions.cc
index 3411d93..5a8479d 100644
--- a/src/starboard/linux/shared/system_get_extensions.cc
+++ b/src/starboard/linux/shared/system_get_extensions.cc
@@ -16,8 +16,10 @@
 
 #include "cobalt/extension/configuration.h"
 #include "cobalt/extension/crash_handler.h"
+#include "cobalt/extension/free_space.h"
 #include "cobalt/extension/memory_mapped_file.h"
 #include "starboard/common/string.h"
+#include "starboard/shared/posix/free_space.h"
 #include "starboard/shared/posix/memory_mapped_file.h"
 #include "starboard/shared/starboard/crash_handler.h"
 #if SB_IS(EVERGREEN_COMPATIBLE)
@@ -46,5 +48,8 @@
   if (strcmp(name, kCobaltExtensionMemoryMappedFileName) == 0) {
     return starboard::shared::posix::GetMemoryMappedFileApi();
   }
+  if (strcmp(name, kCobaltExtensionFreeSpaceName) == 0) {
+    return starboard::shared::posix::GetFreeSpaceApi();
+  }
   return NULL;
 }
diff --git a/src/starboard/loader_app/drain_file.cc b/src/starboard/loader_app/drain_file.cc
index b2a5e50..7efb6b5 100644
--- a/src/starboard/loader_app/drain_file.cc
+++ b/src/starboard/loader_app/drain_file.cc
@@ -195,7 +195,23 @@
   return !strcmp(ranking_app_key.data(), app_key);
 }
 
-bool Remove(const char* dir, const char* app_key) {
+void ClearExpired(const char* dir) {
+  SB_DCHECK(dir);
+  std::vector<std::string> filenames = FindAllWithPrefix(dir, kDrainFilePrefix);
+
+  for (const auto& filename : filenames) {
+    if (!IsExpired(filename)) {
+      continue;
+    }
+    const std::string path = dir + std::string(kSbFileSepString) + filename;
+    if (!SbFileDelete(path.c_str())) {
+      SB_LOG(ERROR) << "Failed to remove expired drain file at '" << path
+                    << "'";
+    }
+  }
+}
+
+void ClearForApp(const char* dir, const char* app_key) {
   SB_DCHECK(dir);
   SB_DCHECK(app_key);
 
@@ -205,29 +221,9 @@
 
   for (const auto& filename : filenames) {
     const std::string path = dir + std::string(kSbFileSepString) + filename;
-
-    if (!SbFileDelete(path.c_str()))
-      return false;
-  }
-  return true;
-}
-
-void Clear(const char* dir, const char* app_key, bool expired) {
-  SB_DCHECK(dir);
-
-  std::vector<std::string> filenames = FindAllWithPrefix(dir, kDrainFilePrefix);
-
-  for (const auto& filename : filenames) {
-    if (expired && !IsExpired(filename))
-      continue;
-    if (app_key && (filename.find(app_key) != std::string::npos))
-      continue;
-
-    const std::string path = dir + std::string(kSbFileSepString) + filename;
-
-    if (!SbFileDelete(path.c_str()))
-      SB_LOG(ERROR) << "Failed to remove expired drain file at '" << filename
-                    << "'";
+    if (!SbFileDelete(path.c_str())) {
+      SB_LOG(ERROR) << "Failed to remove drain file at '" << path << "'";
+    }
   }
 }
 
@@ -250,13 +246,13 @@
   }
 }
 
-bool Draining(const char* dir, const char* app_key) {
+bool IsAppDraining(const char* dir, const char* app_key) {
   SB_DCHECK(dir);
+  SB_DCHECK(app_key);
+  SB_DCHECK(strlen(app_key));
 
   std::string prefix(kDrainFilePrefix);
-
-  if (app_key)
-    prefix.append(app_key);
+  prefix.append(app_key);
 
   const std::vector<std::string> filenames =
       FindAllWithPrefix(dir, prefix.c_str());
@@ -268,6 +264,23 @@
   return false;
 }
 
+bool IsAnotherAppDraining(const char* dir, const char* app_key) {
+  SB_DCHECK(dir);
+  SB_DCHECK(app_key);
+  SB_DCHECK(strlen(app_key));
+  std::vector<std::string> filenames = FindAllWithPrefix(dir, kDrainFilePrefix);
+
+  for (const auto& filename : filenames) {
+    if ((filename.find(app_key) != std::string::npos)) {
+      continue;
+    }
+    if (!IsExpired(filename)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 }  // namespace drain_file
 }  // namespace loader_app
 }  // namespace starboard
@@ -284,20 +297,24 @@
   return starboard::loader_app::drain_file::RankAndCheck(dir, app_key);
 }
 
-bool DrainFileRemove(const char* dir, const char* app_key) {
-  return starboard::loader_app::drain_file::Remove(dir, app_key);
+void DrainFileClearExpired(const char* dir) {
+  starboard::loader_app::drain_file::ClearExpired(dir);
 }
 
-void DrainFileClear(const char* dir, const char* app_key, bool expired) {
-  starboard::loader_app::drain_file::Clear(dir, app_key, expired);
+void DrainFileClearForApp(const char* dir, const char* app_key) {
+  starboard::loader_app::drain_file::ClearForApp(dir, app_key);
 }
 
 void DrainFilePrepareDirectory(const char* dir, const char* app_key) {
   starboard::loader_app::drain_file::PrepareDirectory(dir, app_key);
 }
 
-bool DrainFileDraining(const char* dir, const char* app_key) {
-  return starboard::loader_app::drain_file::Draining(dir, app_key);
+bool DrainFileIsAppDraining(const char* dir, const char* app_key) {
+  return starboard::loader_app::drain_file::IsAppDraining(dir, app_key);
+}
+
+bool DrainFileIsAnotherAppDraining(const char* dir, const char* app_key) {
+  return starboard::loader_app::drain_file::IsAnotherAppDraining(dir, app_key);
 }
 
 #ifdef __cplusplus
diff --git a/src/starboard/loader_app/drain_file.h b/src/starboard/loader_app/drain_file.h
index 92a9dba..11d72a5 100644
--- a/src/starboard/loader_app/drain_file.h
+++ b/src/starboard/loader_app/drain_file.h
@@ -42,24 +42,23 @@
 // match, otherwise |false|.
 bool DrainFileRankAndCheck(const char* dir, const char* app_key);
 
-// Removed the drain files in |dir| whose app key matches |app_key|. Returns
-// |true| if no files were found, or they were removed, otherwise returns
-// |false|.
-bool DrainFileRemove(const char* dir, const char* app_key);
+// Clears all expired drain files in |dir| for all apps.
+void DrainFileClearExpired(const char* dir);
 
-// Clears the drain files in |dir|. If |app_key| is provided, all drain files
-// with matching app keys are ignored. If |expired| is |true|, all non-expired
-// drain files are ignored.
-void DrainFileClear(const char* dir, const char* app_key, bool expired);
+// Clears the drain files in |dir| with an app key matching |app_key|.
+void DrainFileClearForApp(const char* dir, const char* app_key);
 
 // Clears all files and directories in |dir| except for the drain file with an
 // app key matching |app_key|.
 void DrainFilePrepareDirectory(const char* dir, const char* app_key);
 
-// Checks whether a non-expired drain file exists in |dir|. If |app_key| is
-// provided, only drain files with a matching |app_key| are considered. Returns
-// |true| if there is, otherwise |false|.
-bool DrainFileDraining(const char* dir, const char* app_key);
+// Checks whether a non-expired drain file exists in |dir| for an app
+// with key |app_key|.
+bool DrainFileIsAppDraining(const char* dir, const char* app_key);
+
+// Checks whether a non-expired drain file exists in |dir| for an app
+// with key different from |app_key|.
+bool DrainFileIsAnotherAppDraining(const char* dir, const char* app_key);
 
 #ifdef __cplusplus
 }  // extern "C"
diff --git a/src/starboard/loader_app/drain_file_test.cc b/src/starboard/loader_app/drain_file_test.cc
index 35ae7cd..bf0863c 100644
--- a/src/starboard/loader_app/drain_file_test.cc
+++ b/src/starboard/loader_app/drain_file_test.cc
@@ -36,29 +36,20 @@
 
 class DrainFileTest : public ::testing::Test {
  protected:
-  // This function is used to set the temporary directory used for testing once,
-  // and to create a subdir in this test directory, once for all test cases.
-  static void SetUpTestCase() {
-    std::vector<char> path(kSbFileMaxPath, 0);
-
-    ASSERT_TRUE(
-        SbSystemGetPath(kSbSystemPathTempDirectory, path.data(), path.size()));
-
-    dir_.assign(path.data());
+  void SetUp() override {
+    temp_dir_.resize(kSbFileMaxPath);
+    ASSERT_TRUE(SbSystemGetPath(kSbSystemPathTempDirectory, temp_dir_.data(),
+                                temp_dir_.size()));
   }
 
-  void TearDown() override {
-    DrainFileClear(GetTempDir(), NULL, false);
-  }
+  void TearDown() override { DrainFileClearForApp(GetTempDir(), ""); }
 
-  const char* GetTempDir() const { return dir_.c_str(); }
+  const char* GetTempDir() const { return temp_dir_.data(); }
 
  private:
-  static std::string dir_;
+  std::vector<char> temp_dir_;
 };
 
-std::string DrainFileTest::dir_ = "";
-
 // Typical drain file usage.
 TEST_F(DrainFileTest, SunnyDay) {
   EXPECT_TRUE(DrainFileTryDrain(GetTempDir(), kAppKeyOne));
@@ -71,9 +62,9 @@
   ScopedDrainFile stale(GetTempDir(), kAppKeyOne,
                         SbTimeGetNow() - kDrainFileMaximumAge);
 
-  EXPECT_FALSE(DrainFileDraining(GetTempDir(), kAppKeyOne));
+  EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
   EXPECT_TRUE(DrainFileTryDrain(GetTempDir(), kAppKeyOne));
-  EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyOne));
+  EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
 }
 
 // Previously created drain file should be reused if it has not expired.
@@ -85,56 +76,74 @@
 // Draining status should return whether or not the file exists and has not yet
 // expired.
 TEST_F(DrainFileTest, SunnyDayDraining) {
-  EXPECT_FALSE(DrainFileDraining(GetTempDir(), kAppKeyOne));
+  EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
   EXPECT_TRUE(DrainFileTryDrain(GetTempDir(), kAppKeyOne));
-  EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyOne));
+  EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
 
-  DrainFileClear(GetTempDir(), NULL, false);
+  DrainFileClearForApp(GetTempDir(), kAppKeyOne);
 
-  EXPECT_FALSE(DrainFileDraining(GetTempDir(), kAppKeyOne));
+  EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+}
+
+// Check if another app is draining.
+TEST_F(DrainFileTest, SunnyDayAnotherAppDraining) {
+  EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+  EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyTwo));
+  EXPECT_FALSE(DrainFileIsAnotherAppDraining(GetTempDir(), kAppKeyOne));
+  EXPECT_FALSE(DrainFileIsAnotherAppDraining(GetTempDir(), kAppKeyTwo));
+
+  EXPECT_TRUE(DrainFileTryDrain(GetTempDir(), kAppKeyOne));
+  EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+  EXPECT_TRUE(DrainFileIsAnotherAppDraining(GetTempDir(), kAppKeyTwo));
+
+  DrainFileClearForApp(GetTempDir(), kAppKeyOne);
+  EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+  EXPECT_FALSE(DrainFileIsAnotherAppDraining(GetTempDir(), kAppKeyTwo));
 }
 
 // Remove an existing drain file.
 TEST_F(DrainFileTest, SunnyDayRemove) {
   EXPECT_TRUE(DrainFileTryDrain(GetTempDir(), kAppKeyOne));
-  EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyOne));
-  EXPECT_TRUE(DrainFileRemove(GetTempDir(), kAppKeyOne));
-  EXPECT_FALSE(DrainFileDraining(GetTempDir(), kAppKeyOne));
+  EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+  DrainFileClearForApp(GetTempDir(), kAppKeyOne);
+  EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
 }
 
-// Try to remove a drain file that does not exist.
-TEST_F(DrainFileTest, SunnyDayRemoveNonexistentDrainFile) {
-  EXPECT_FALSE(DrainFileDraining(GetTempDir(), kAppKeyOne));
-  EXPECT_TRUE(DrainFileRemove(GetTempDir(), kAppKeyOne));
-}
-
-// When clearing drain files it should be possible to provide a file to ignore,
-// and it should be possible to only clear expire files if desired.
-TEST_F(DrainFileTest, SunnyDayClear) {
+// Clear only expired drain files.
+TEST_F(DrainFileTest, SunnyDayClearExpired) {
   EXPECT_TRUE(DrainFileTryDrain(GetTempDir(), kAppKeyOne));
 
   ScopedDrainFile valid_file(GetTempDir(), kAppKeyTwo, SbTimeGetNow());
   ScopedDrainFile stale_file(GetTempDir(), kAppKeyThree,
                              SbTimeGetNow() - kDrainFileMaximumAge);
 
-  EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyOne));
-  EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyTwo));
+  EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+  EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyTwo));
   EXPECT_TRUE(SbFileExists(stale_file.path().c_str()));
 
-  DrainFileClear(GetTempDir(), kAppKeyOne, true);
+  DrainFileClearExpired(GetTempDir());
 
-  EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyOne));
-  EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyTwo));
   EXPECT_FALSE(SbFileExists(stale_file.path().c_str()));
+}
 
-  DrainFileClear(GetTempDir(), kAppKeyOne, false);
+// Clearing drain files for an app.
+TEST_F(DrainFileTest, SunnyDayClearForApp) {
+  EXPECT_TRUE(DrainFileTryDrain(GetTempDir(), kAppKeyOne));
 
-  EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyOne));
-  EXPECT_FALSE(DrainFileDraining(GetTempDir(), kAppKeyTwo));
+  ScopedDrainFile valid_file(GetTempDir(), kAppKeyTwo, SbTimeGetNow());
+  ScopedDrainFile stale_file(GetTempDir(), kAppKeyThree,
+                             SbTimeGetNow() - kDrainFileMaximumAge);
 
-  DrainFileClear(GetTempDir(), NULL, false);
+  EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+  EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyTwo));
+  EXPECT_TRUE(SbFileExists(stale_file.path().c_str()));
 
-  EXPECT_FALSE(DrainFileDraining(GetTempDir(), kAppKeyOne));
+  // clean all drain files for an app
+  DrainFileClearForApp(GetTempDir(), kAppKeyOne);
+
+  EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+  EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyTwo));
+  EXPECT_TRUE(SbFileExists(stale_file.path().c_str()));
 }
 
 // Ranking drain files should first be done by timestamp, with the app key being
diff --git a/src/starboard/loader_app/loader_app.cc b/src/starboard/loader_app/loader_app.cc
index 2fdce8f..b60720d 100644
--- a/src/starboard/loader_app/loader_app.cc
+++ b/src/starboard/loader_app/loader_app.cc
@@ -45,6 +45,9 @@
 // Relative path to the Cobalt's system image library.
 const char kSystemImageLibraryPath[] = "app/cobalt/lib/libcobalt.so";
 
+// Relative path to the compressed Cobalt's system image library.
+const char kSystemImageCompressedLibraryPath[] = "app/cobalt/lib/libcobalt.lz4";
+
 // Cobalt default URL.
 const char kCobaltDefaultUrl[] = "https://www.youtube.com/tv";
 
@@ -58,9 +61,12 @@
 class CobaltLibraryLoader : public starboard::loader_app::LibraryLoader {
  public:
   virtual bool Load(const std::string& library_path,
-                    const std::string& content_path) {
+                    const std::string& content_path,
+                    bool use_compression,
+                    bool use_memory_mapped_file) {
     return g_elf_loader.Load(library_path, content_path, false,
-                             &starboard::loader_app::SbSystemGetExtensionShim);
+                             &starboard::loader_app::SbSystemGetExtensionShim,
+                             use_compression, use_memory_mapped_file);
   }
   virtual void* Resolve(const std::string& symbol) {
     return g_elf_loader.LookupSymbol(symbol.c_str());
@@ -79,7 +85,13 @@
   return true;
 }
 
-void LoadLibraryAndInitialize(const std::string& alternative_content_path) {
+void LoadLibraryAndInitialize(const std::string& alternative_content_path,
+                              bool use_compression,
+                              bool use_memory_mapped_file) {
+  if (use_compression && use_memory_mapped_file) {
+    SB_LOG(ERROR) << "Using both compression and mmap files is not supported";
+    return;
+  }
   std::string content_dir;
   if (!GetContentDir(&content_dir)) {
     SB_LOG(ERROR) << "Failed to get the content dir";
@@ -95,13 +107,15 @@
   }
   std::string library_path = content_dir;
   library_path += kSbFileSepString;
-  library_path += kSystemImageLibraryPath;
-  if (!SbFileExists(library_path.c_str())) {
-    // Try the compressed path if the binary doesn't exits.
-    library_path += starboard::elf_loader::kCompressionSuffix;
+
+  if (use_compression) {
+    library_path += kSystemImageCompressedLibraryPath;
+  } else {
+    library_path += kSystemImageLibraryPath;
   }
 
-  if (!g_elf_loader.Load(library_path, content_path, false)) {
+  if (!g_elf_loader.Load(library_path, content_path, false, nullptr,
+                         use_compression, use_memory_mapped_file)) {
     SB_NOTREACHED() << "Failed to load library at '"
                     << g_elf_loader.GetLibraryPath() << "'.";
     return;
@@ -189,8 +203,16 @@
         command_line.GetSwitchValue(starboard::loader_app::kContent);
     SB_LOG(INFO) << "alternative_content=" << alternative_content;
 
+    bool use_compression =
+        command_line.HasSwitch(starboard::loader_app::kLoaderUseCompression);
+
+    bool use_memory_mapped_file = command_line.HasSwitch(
+        starboard::loader_app::kLoaderUseMemoryMappedFile);
+    SB_LOG(INFO) << "loader_app: use_compression=" << use_compression
+                 << " use_memory_mapped_file=" << use_memory_mapped_file;
     if (is_evergreen_lite) {
-      LoadLibraryAndInitialize(alternative_content);
+      LoadLibraryAndInitialize(alternative_content, use_compression,
+                               use_memory_mapped_file);
     } else {
       std::string url =
           command_line.GetSwitchValue(starboard::loader_app::kURL);
@@ -202,7 +224,8 @@
 
       g_sb_event_func = reinterpret_cast<void (*)(const SbEvent*)>(
           starboard::loader_app::LoadSlotManagedLibrary(
-              app_key, alternative_content, &g_cobalt_library_loader));
+              app_key, alternative_content, &g_cobalt_library_loader,
+              use_compression, use_memory_mapped_file));
     }
     SB_CHECK(g_sb_event_func);
   }
diff --git a/src/starboard/loader_app/loader_app_switches.cc b/src/starboard/loader_app/loader_app_switches.cc
index 7758dea..b40574c 100644
--- a/src/starboard/loader_app/loader_app_switches.cc
+++ b/src/starboard/loader_app/loader_app_switches.cc
@@ -22,6 +22,8 @@
 const char kEvergreenLite[] = "evergreen_lite";
 const char kLoaderAppVersion[] = "loader_app_version";
 const char kShowSABI[] = "show_sabi";
+const char kLoaderUseCompression[] = "loader_use_compression";
+const char kLoaderUseMemoryMappedFile[] = "loader_use_mmap_file";
 
 }  // namespace loader_app
 }  // namespace starboard
diff --git a/src/starboard/loader_app/loader_app_switches.h b/src/starboard/loader_app/loader_app_switches.h
index 22030c5..bf62ffb 100644
--- a/src/starboard/loader_app/loader_app_switches.h
+++ b/src/starboard/loader_app/loader_app_switches.h
@@ -38,6 +38,11 @@
 // Print the loader_app Starboard ABI string on the command line.
 extern const char kShowSABI[];
 
+// The elf loader should use compression.
+extern const char kLoaderUseCompression[];
+
+// The elf laoder should use a Memory Mapped file.
+extern const char kLoaderUseMemoryMappedFile[];
 }  // namespace loader_app
 }  // namespace starboard
 
diff --git a/src/starboard/loader_app/slot_management.cc b/src/starboard/loader_app/slot_management.cc
index 0d8cf3f..b834f07 100644
--- a/src/starboard/loader_app/slot_management.cc
+++ b/src/starboard/loader_app/slot_management.cc
@@ -43,33 +43,41 @@
 // Filename for the Cobalt binary.
 const char kCobaltLibraryName[] = "libcobalt.so";
 
+// Filename for the compressed Cobalt binary.
+const char kCompressedCobaltLibraryName[] = "libcobalt.lz4";
+
 // Relative path for the content directory of
 // the Cobalt installation.
 const char kCobaltContentPath[] = "content";
 
 }  // namespace
 
-int RevertBack(int current_installation, const std::string& app_key) {
+int RevertBack(int current_installation,
+               const std::string& app_key,
+               bool mark_bad) {
   SB_LOG(INFO) << "RevertBack current_installation=" << current_installation;
   SB_DCHECK(current_installation != 0);
-  std::vector<char> installation_path(kSbFileMaxPath);
-  if (ImGetInstallationPath(current_installation, installation_path.data(),
-                            kSbFileMaxPath) != IM_ERROR) {
-    std::string bad_app_key_file_path =
-        starboard::loader_app::GetBadAppKeyFilePath(installation_path.data(),
-                                                    app_key);
-    if (bad_app_key_file_path.empty()) {
-      SB_LOG(WARNING) << "Failed to get bad app key file path for path="
-                      << installation_path.data() << " and app_key=" << app_key;
-    } else {
-      if (!starboard::loader_app::CreateAppKeyFile(bad_app_key_file_path)) {
-        SB_LOG(WARNING) << "Failed to create bad app key file: "
-                        << bad_app_key_file_path;
+  if (mark_bad) {
+    std::vector<char> installation_path(kSbFileMaxPath);
+    if (ImGetInstallationPath(current_installation, installation_path.data(),
+                              kSbFileMaxPath) != IM_ERROR) {
+      std::string bad_app_key_file_path =
+          starboard::loader_app::GetBadAppKeyFilePath(installation_path.data(),
+                                                      app_key);
+      if (bad_app_key_file_path.empty()) {
+        SB_LOG(WARNING) << "Failed to get bad app key file path for path="
+                        << installation_path.data()
+                        << " and app_key=" << app_key;
+      } else {
+        if (!starboard::loader_app::CreateAppKeyFile(bad_app_key_file_path)) {
+          SB_LOG(WARNING) << "Failed to create bad app key file: "
+                          << bad_app_key_file_path;
+        }
       }
+    } else {
+      SB_LOG(WARNING) << "Failed to get installation path for index: "
+                      << current_installation;
     }
-  } else {
-    SB_LOG(WARNING) << "Failed to get installation path for index: "
-                    << current_installation;
   }
   current_installation = ImRevertToSuccessfulInstallation();
   return current_installation;
@@ -124,7 +132,13 @@
 
 void* LoadSlotManagedLibrary(const std::string& app_key,
                              const std::string& alternative_content_path,
-                             LibraryLoader* library_loader) {
+                             LibraryLoader* library_loader,
+                             bool use_compression,
+                             bool use_memory_mapped_file) {
+  if (use_compression && use_memory_mapped_file) {
+    SB_LOG(ERROR) << "Using both compression and mmap files is not supported";
+    return NULL;
+  }
   // Initialize the Installation Manager.
   SB_CHECK(ImInitialize(kMaxNumInstallations, app_key.c_str()) == IM_SUCCESS)
       << "Abort. Failed to initialize Installation Manager";
@@ -150,7 +164,8 @@
         // discard the image and auto rollback, but only if
         // the current image is not the system image.
         if (current_installation != 0) {
-          current_installation = RevertBack(current_installation, app_key);
+          current_installation =
+              RevertBack(current_installation, app_key, true /* mark_bad */);
         }
       }
     }
@@ -167,7 +182,8 @@
       // Hard failure. Discard the image and auto rollback, but only if
       // the current image is not the system image.
       if (current_installation != 0) {
-        current_installation = RevertBack(current_installation, app_key);
+        current_installation =
+            RevertBack(current_installation, app_key, true /* mark_bad */);
         continue;
       } else {
         // The system image at index 0 failed.
@@ -178,41 +194,49 @@
     SB_DLOG(INFO) << "installation_path=" << installation_path.data();
 
     if (current_installation != 0) {
-      // Cleanup expired drain files
-      DrainFileClear(installation_path.data(), app_key.c_str(), true);
+      // Cleanup all expired files from all apps.
+      DrainFileClearExpired(installation_path.data());
+
+      // Cleanup all drain files from the current app.
+      DrainFileClearForApp(installation_path.data(), app_key.c_str());
 
       // Check for bad file.
       if (CheckBadFileExists(installation_path.data(), app_key.c_str())) {
         SB_LOG(INFO) << "Bad app key file";
-        current_installation = RevertBack(current_installation, app_key);
+        current_installation =
+            RevertBack(current_installation, app_key, true /* mark_bad */);
         continue;
       }
       // If the current installation is in use by an updater roll back.
-      if (DrainFileDraining(installation_path.data(), "")) {
+      if (DrainFileIsAnotherAppDraining(installation_path.data(),
+                                        app_key.c_str())) {
         SB_LOG(INFO) << "Active slot draining";
-        current_installation = RevertBack(current_installation, app_key);
+        current_installation =
+            RevertBack(current_installation, app_key, false /* mark_bad */);
         continue;
       }
       // Adopt installation performed from different app.
       if (!AdoptInstallation(current_installation, installation_path.data(),
                              app_key.c_str())) {
         SB_LOG(INFO) << "Unable to adopt installation";
-        current_installation = RevertBack(current_installation, app_key);
+        current_installation =
+            RevertBack(current_installation, app_key, true /* mark_bad */);
         continue;
       }
     }
 
     // installation_n/lib/libcobalt.so
     std::vector<char> lib_path(kSbFileMaxPath);
+    std::string library_name;
+    if (use_compression) {
+      library_name = kCompressedCobaltLibraryName;
+    } else {
+      library_name = kCobaltLibraryName;
+    }
     SbStringFormatF(lib_path.data(), kSbFileMaxPath, "%s%s%s%s%s",
                     installation_path.data(), kSbFileSepString,
-                    kCobaltLibraryPath, kSbFileSepString, kCobaltLibraryName);
-    if (!SbFileExists(lib_path.data())) {
-      // Try the compressed path if the binary doesn't exits.
-      starboard::strlcat(lib_path.data(),
-                         starboard::elf_loader::kCompressionSuffix,
-                         kSbFileMaxPath);
-    }
+                    kCobaltLibraryPath, kSbFileSepString, library_name.c_str());
+
     SB_LOG(INFO) << "lib_path=" << lib_path.data();
 
     std::string content;
@@ -229,13 +253,15 @@
 
     SB_LOG(INFO) << "content=" << content;
 
-    if (!library_loader->Load(lib_path.data(), content.c_str())) {
+    if (!library_loader->Load(lib_path.data(), content.c_str(), use_compression,
+                              use_memory_mapped_file)) {
       SB_LOG(WARNING) << "Failed to load Cobalt!";
 
       // Hard failure. Discard the image and auto rollback, but only if
       // the current image is not the system image.
       if (current_installation != 0) {
-        current_installation = RevertBack(current_installation, app_key);
+        current_installation =
+            RevertBack(current_installation, app_key, true /* mark_bad */);
         continue;
       } else {
         // The system image at index 0 failed.
@@ -261,7 +287,8 @@
       // Hard failure. Discard the image and auto rollback, but only if
       // the current image is not the system image.
       if (current_installation != 0) {
-        current_installation = RevertBack(current_installation, app_key);
+        current_installation =
+            RevertBack(current_installation, app_key, true /* mark_bad */);
         continue;
       } else {
         // The system image at index 0 failed.
@@ -294,7 +321,8 @@
       // Hard failure. Discard the image and auto rollback, but only if
       // the current image is not the system image.
       if (current_installation != 0) {
-        current_installation = RevertBack(current_installation, app_key);
+        current_installation =
+            RevertBack(current_installation, app_key, true /* mark_bad */);
         continue;
       } else {
         // The system image at index 0 failed.
diff --git a/src/starboard/loader_app/slot_management.h b/src/starboard/loader_app/slot_management.h
index 319cec9..b5a2237 100644
--- a/src/starboard/loader_app/slot_management.h
+++ b/src/starboard/loader_app/slot_management.h
@@ -26,9 +26,14 @@
   virtual ~LibraryLoader() {}
 
   // Load the library with the provided full path to |library_path| and
-  // |content_path|.
+  // |content_path|. If |use_compression| is true the library is assumed
+  // to be compressed. If |use_memory_mapped_file| is true the library
+  // would be loaded as a memory mapped file. The |use_compression| and
+  // |use_memory_mapped_file|  are not compatible and can't be both enabled.
   virtual bool Load(const std::string& library_path,
-                    const std::string& content_path) = 0;
+                    const std::string& content_path,
+                    bool use_compression,
+                    bool use_memory_mapped_file) = 0;
 
   // Resolve a symbol by name.
   virtual void* Resolve(const std::string& symbol) = 0;
@@ -39,10 +44,16 @@
 // The actual loading from the slot is performed by the |library_loader|.
 // An alternative content can be used by specifying non-empty
 // |alternative_content_path| with the full path to the content.
+// If |use_compression| is true the library is assumed
+// to be compressed. If |use_memory_mapped_file| is true the library
+// would be loaded as a memory mapped file. The |use_compression| and
+// |use_memory_mapped_file|  are not compatible and can't be both enabled.
 // Returns a pointer to the |SbEventHandle| symbol in the library.
 void* LoadSlotManagedLibrary(const std::string& app_key,
                              const std::string& alternative_content_path,
-                             LibraryLoader* library_loader);
+                             LibraryLoader* library_loader,
+                             bool use_compression,
+                             bool use_memory_mapped_file);
 
 }  // namespace loader_app
 }  // namespace starboard
diff --git a/src/starboard/loader_app/slot_management_test.cc b/src/starboard/loader_app/slot_management_test.cc
index 9f63b1a..1ed55d2 100644
--- a/src/starboard/loader_app/slot_management_test.cc
+++ b/src/starboard/loader_app/slot_management_test.cc
@@ -48,25 +48,23 @@
 
 class MockLibraryLoader : public LibraryLoader {
  public:
-  MOCK_METHOD2(Load,
+  MOCK_METHOD4(Load,
                bool(const std::string& library_path,
-                    const std::string& content_path));
+                    const std::string& content_path,
+                    bool use_compression,
+                    bool use_memory_mapped_file));
   MOCK_METHOD1(Resolve, void*(const std::string& symbol));
 };
 
-class SlotManagementTest : public testing::Test {
+class SlotManagementTest : public testing::TestWithParam<bool> {
  protected:
   virtual void SetUp() {
     slot_0_libcobalt_path_ =
-        CreatePath({"content", "app", "cobalt", "lib", "libcobalt.so.lz4"});
+        CreatePath({"content", "app", "cobalt", "lib", "libcobalt"});
     slot_0_content_path_ = CreatePath({"content", "app", "cobalt", "content"});
-
-    slot_1_libcobalt_path_ =
-        CreatePath({"installation_1", "lib", "libcobalt.so.lz4"});
+    slot_1_libcobalt_path_ = CreatePath({"installation_1", "lib", "libcobalt"});
     slot_1_content_path_ = CreatePath({"installation_1", "content"});
-
-    slot_2_libcobalt_path_ =
-        CreatePath({"installation_2", "lib", "libcobalt.so.lz4"});
+    slot_2_libcobalt_path_ = CreatePath({"installation_2", "lib", "libcobalt"});
     slot_2_content_path_ = CreatePath({"installation_2", "content"});
 
     std::vector<char> buf(kSbFileMaxPath);
@@ -74,6 +72,14 @@
                                                 buf.data(), kSbFileMaxPath);
   }
 
+  void AddFileExtension(std::string& path) {
+    if (GetParam()) {
+      path += ".lz4";
+    } else {
+      path += ".so";
+    }
+  }
+
   std::string CreatePath(std::initializer_list<std::string> path_elements) {
     std::string result;
     for (const std::string& path : path_elements) {
@@ -135,10 +141,14 @@
   }
 
   void VerifyLoad(const std::string& lib, const std::string& content) {
+    bool use_compression = GetParam();
     MockLibraryLoader library_loader;
 
+    std::string full_lib_path = lib;
+    AddFileExtension(full_lib_path);
     EXPECT_CALL(library_loader,
-                Load(testing::EndsWith(lib), testing::EndsWith(content)))
+                Load(testing::EndsWith(full_lib_path),
+                     testing::EndsWith(content), use_compression, false))
         .Times(1)
         .WillOnce(testing::Return(true));
     EXPECT_CALL(library_loader, Resolve("GetEvergreenSabiString"))
@@ -153,7 +163,8 @@
         .Times(1)
         .WillOnce(testing::Return(reinterpret_cast<void*>(&SbEventFake)));
     ASSERT_EQ(&SbEventFake,
-              LoadSlotManagedLibrary(kTestAppKey, "", &library_loader));
+              LoadSlotManagedLibrary(kTestAppKey, "", &library_loader,
+                                     use_compression, false));
   }
 
  protected:
@@ -166,7 +177,7 @@
   bool storage_path_implemented_;
 };
 
-TEST_F(SlotManagementTest, SystemSlot) {
+TEST_P(SlotManagementTest, SystemSlot) {
   if (!storage_path_implemented_) {
     return;
   }
@@ -178,7 +189,7 @@
   VerifyBadFile(0, kTestAppKey, false);
 }
 
-TEST_F(SlotManagementTest, AdoptSlot) {
+TEST_P(SlotManagementTest, AdoptSlot) {
   if (!storage_path_implemented_) {
     return;
   }
@@ -196,7 +207,7 @@
   VerifyBadFile(1, kTestAppKey, false);
 }
 
-TEST_F(SlotManagementTest, GoodSlot) {
+TEST_P(SlotManagementTest, GoodSlot) {
   if (!storage_path_implemented_) {
     return;
   }
@@ -213,7 +224,7 @@
   VerifyBadFile(2, kTestAppKey, false);
 }
 
-TEST_F(SlotManagementTest, NotAdoptSlot) {
+TEST_P(SlotManagementTest, NotAdoptSlot) {
   if (!storage_path_implemented_) {
     return;
   }
@@ -230,7 +241,7 @@
   VerifyBadFile(2, kTestAppKey, true);
 }
 
-TEST_F(SlotManagementTest, BadSlot) {
+TEST_P(SlotManagementTest, BadSlot) {
   if (!storage_path_implemented_) {
     return;
   }
@@ -245,7 +256,7 @@
   VerifyGoodFile(1, kTestAppKey, false);
 }
 
-TEST_F(SlotManagementTest, DrainingSlot) {
+TEST_P(SlotManagementTest, DrainingSlot) {
   if (!storage_path_implemented_) {
     return;
   }
@@ -258,10 +269,10 @@
   ImUninitialize();
   VerifyLoad(slot_0_libcobalt_path_, slot_0_content_path_);
   VerifyGoodFile(1, kTestAppKey, false);
-  VerifyBadFile(1, kTestAppKey, true);
+  VerifyBadFile(1, kTestAppKey, false);
 }
 
-TEST_F(SlotManagementTest, AlternativeContent) {
+TEST_P(SlotManagementTest, AlternativeContent) {
   if (!storage_path_implemented_) {
     return;
   }
@@ -274,8 +285,11 @@
 
   MockLibraryLoader library_loader;
 
-  EXPECT_CALL(library_loader, Load(testing::EndsWith(slot_0_libcobalt_path_),
-                                   testing::EndsWith("/foo")))
+  std::string full_lib_path = slot_0_libcobalt_path_;
+  AddFileExtension(full_lib_path);
+  EXPECT_CALL(library_loader,
+              Load(testing::EndsWith(full_lib_path), testing::EndsWith("/foo"),
+                   GetParam(), false))
       .Times(1)
       .WillOnce(testing::Return(true));
   EXPECT_CALL(library_loader, Resolve("GetEvergreenSabiString"))
@@ -291,10 +305,11 @@
       .Times(1)
       .WillOnce(testing::Return(reinterpret_cast<void*>(&SbEventFake)));
   ASSERT_EQ(&SbEventFake,
-            LoadSlotManagedLibrary(kTestAppKey, "/foo", &library_loader));
+            LoadSlotManagedLibrary(kTestAppKey, "/foo", &library_loader,
+                                   GetParam(), false));
 }
 
-TEST_F(SlotManagementTest, BadSabi) {
+TEST_P(SlotManagementTest, BadSabi) {
   if (!storage_path_implemented_) {
     return;
   }
@@ -314,8 +329,11 @@
 
   MockLibraryLoader library_loader;
 
-  EXPECT_CALL(library_loader, Load(testing::EndsWith(slot_2_libcobalt_path_),
-                                   testing::EndsWith(slot_2_content_path_)))
+  std::string slot2_libcobalt_full = slot_2_libcobalt_path_;
+  AddFileExtension(slot2_libcobalt_full);
+  EXPECT_CALL(library_loader,
+              Load(testing::EndsWith(slot2_libcobalt_full),
+                   testing::EndsWith(slot_2_content_path_), GetParam(), false))
       .Times(1)
       .WillOnce(testing::Return(true));
 
@@ -326,8 +344,11 @@
       .WillOnce(
           testing::Return(reinterpret_cast<void*>(&GetEvergreenSabiString)));
 
-  EXPECT_CALL(library_loader, Load(testing::EndsWith(slot_1_libcobalt_path_),
-                                   testing::EndsWith(slot_1_content_path_)))
+  std::string slot1_libcobalt_full = slot_1_libcobalt_path_;
+  AddFileExtension(slot1_libcobalt_full);
+  EXPECT_CALL(library_loader,
+              Load(testing::EndsWith(slot1_libcobalt_full),
+                   testing::EndsWith(slot_1_content_path_), GetParam(), false))
       .Times(1)
       .WillOnce(testing::Return(true));
 
@@ -341,9 +362,14 @@
       .WillOnce(testing::Return(reinterpret_cast<void*>(&SbEventFake)));
 
   ASSERT_EQ(&SbEventFake,
-            LoadSlotManagedLibrary(kTestAppKey, "", &library_loader));
+            LoadSlotManagedLibrary(kTestAppKey, "", &library_loader, GetParam(),
+                                   false));
 }
 
+INSTANTIATE_TEST_CASE_P(SlotManagementTests,
+                        SlotManagementTest,
+                        ::testing::Bool());
+
 }  // namespace
 }  // namespace loader_app
 }  // namespace starboard
diff --git a/src/starboard/nplb/media_can_play_mime_and_key_system_test.cc b/src/starboard/nplb/media_can_play_mime_and_key_system_test.cc
index 3d5317c..81f85e2 100644
--- a/src/starboard/nplb/media_can_play_mime_and_key_system_test.cc
+++ b/src/starboard/nplb/media_can_play_mime_and_key_system_test.cc
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <map>
+
 #include "starboard/common/string.h"
 #include "starboard/media.h"
 #include "starboard/nplb/drm_helpers.h"
@@ -290,280 +292,190 @@
   }
 }
 
-// TODO: Create an abstraction to shorten the length of this test.
 TEST(SbMediaCanPlayMimeAndKeySystem, PrintMaximumSupport) {
-  // AVC
-  std::string avc_resolution = "Unsupported";
-  // 1080p
-  SbMediaSupportType result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"avc1.4d402a\"; width=1920; height=1080; "
-      "framerate=30",
-      "");
-  if (result == kSbMediaSupportTypeProbably) {
-    avc_resolution = "1080p";
-    // 2K
-    result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"avc1.64002a\"; width=2560; height=1440; "
-        "framerate=30",
-        "");
-    if (result == kSbMediaSupportTypeProbably) {
-      avc_resolution = "2K";
-      // 4K
-      result = SbMediaCanPlayMimeAndKeySystem(
-          "video/mp4; codecs=\"avc1.64002a\"; width=3840; height=2160; "
-          "framerate=30",
-          "");
-      if (result == kSbMediaSupportTypeProbably) {
-        avc_resolution = "4K";
+  const char* kResolution1080p = "1080p";
+  const char* kResolution2k = "2K";
+  const char* kResolution4k = "4K";
+  const char* kResolution8k = "8K";
+  auto get_max_video_codec_resolution =
+      [=](std::string video_type,
+          const std::map<const char*, std::string> codec_params,
+          std::string framerate) {
+        std::map<const char*, std::string> resolutions = {
+            {kResolution1080p, "width=1920; height=1080;"},
+            {kResolution2k, "width=2560; height=1440;"},
+            {kResolution4k, "width=3840; height=2160;"},
+            {kResolution8k, "width=7680; height=4320;"}};
+
+        std::string max_supported_resolution;
+        for (auto&& it : resolutions) {
+          if (codec_params.find(it.first) == codec_params.end()) {
+            break;
+          }
+          std::string content_type = video_type + "; codecs=\"" +
+                                     codec_params.at(it.first) + "\"; " +
+                                     it.second + " framerate=" + framerate;
+          if (SbMediaCanPlayMimeAndKeySystem(content_type.c_str(), "") !=
+              kSbMediaSupportTypeProbably) {
+            break;
+          }
+          max_supported_resolution = it.first;
+        }
+        return max_supported_resolution.empty() ? "Unsupported"
+                                                : max_supported_resolution;
+      };
+
+  auto get_drm_system_support = [](std::string key_system) {
+    std::map<const char*, std::string> content_types = {
+        {"video/mp4; codecs=\"avc1.4d402a\"", "AVC"},
+        {"video/webm; codecs=\"vp9\"", "VP9"},
+        {"video/mp4; codecs=\"av01.0.08M.08\"", "AV1"},
+        {"audio/mp4; codecs=\"mp4a.40.2\"", "AAC"},
+        {"audio/webm; codecs=\"opus\"", "Opus"},
+        {"audio/mp4; codecs=\"ac-3\"", "AC-3"},
+        {"audio/mp4; codecs=\"ec-3\"", "E-AC-3"}};
+    std::string supported_codecs;
+    for (auto&& it : content_types) {
+      if (SbMediaCanPlayMimeAndKeySystem(it.first, key_system.c_str()) ==
+          kSbMediaSupportTypeProbably) {
+        supported_codecs += it.second + " ";
       }
     }
+    return supported_codecs.empty() ? "Unsupported" : supported_codecs;
+  };
+
+  std::string avc_support =
+      get_max_video_codec_resolution("video/mp4",
+                                     {{kResolution1080p, "avc1.4d402a"},
+                                      {kResolution2k, "avc1.64002a"},
+                                      {kResolution4k, "avc1.64002a"}},
+                                     "30");
+  if (avc_support != "Unsupported") {
+    avc_support += "\n\tAVC HFR: " + get_max_video_codec_resolution(
+                                         "video/mp4",
+                                         {{kResolution1080p, "avc1.4d402a"},
+                                          {kResolution2k, "avc1.64402a"},
+                                          {kResolution4k, "avc1.64402a"}},
+                                         "60");
   }
 
-  // AVC HFR
-  std::string avc_hfr_resolution = "Unsupported";
-  // 1080p
-  result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"avc1.4d402a\"; width=1920; height=1080; "
-      "framerate=60",
-      "");
-  if (result == kSbMediaSupportTypeProbably) {
-    avc_hfr_resolution = "1080p";
-    // 2K
-    result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"avc1.64402a\"; width=2560; height=1440; "
-        "framerate=60",
-        "");
-    if (result == kSbMediaSupportTypeProbably) {
-      avc_hfr_resolution = "2K";
-      // 4K
-      result = SbMediaCanPlayMimeAndKeySystem(
-          "video/mp4; codecs=\"avc1.64402a\"; width=3840; height=2160; "
-          "framerate=60",
-          "");
-      if (result == kSbMediaSupportTypeProbably) {
-        avc_hfr_resolution = "4K";
-      }
+  std::string vp9_support =
+      get_max_video_codec_resolution("video/webm",
+                                     {{kResolution1080p, "vp9"},
+                                      {kResolution2k, "vp9"},
+                                      {kResolution4k, "vp9"}},
+                                     "30");
+  if (vp9_support != "Unsupported") {
+    vp9_support += "\n\tVP9 HFR: " +
+                   get_max_video_codec_resolution("video/webm",
+                                                  {{kResolution1080p, "vp9"},
+                                                   {kResolution2k, "vp9"},
+                                                   {kResolution4k, "vp9"}},
+                                                  "60");
+    std::string vp9_hdr_support = get_max_video_codec_resolution(
+        "video/webm",
+        {{kResolution1080p, "vp09.02.41.10.01.09.16.09.00"},
+         {kResolution2k, "vp09.02.51.10.01.09.16.09.00"},
+         {kResolution4k, "vp09.02.51.10.01.09.16.09.00"}},
+        "30");
+    if (vp9_hdr_support != "Unsupported") {
+      vp9_hdr_support +=
+          "\n\tVP9 HDR HFR: " +
+          get_max_video_codec_resolution(
+              "video/webm",
+              {{kResolution1080p, "vp09.02.41.10.01.09.16.09.00"},
+               {kResolution2k, "vp09.02.51.10.01.09.16.09.00"},
+               {kResolution4k, "vp09.02.51.10.01.09.16.09.00"}},
+              "60");
     }
+    vp9_support += "\n\tVP9 HDR: " + vp9_hdr_support;
   }
-
-  // VP9
-  std::string vp9_resolution = "Unsupported";
-  // 1080p
-  result = SbMediaCanPlayMimeAndKeySystem(
-      "video/webm; codecs=\"vp9\"; width=1920; height=1080; framerate=30", "");
-  if (result == kSbMediaSupportTypeProbably) {
-    vp9_resolution = "1080p";
-    // 2K
-    result = SbMediaCanPlayMimeAndKeySystem(
-        "video/webm; codecs=\"vp9\"; width=2560; height=1440; framerate=30",
-        "");
-    if (result == kSbMediaSupportTypeProbably) {
-      vp9_resolution = "2K";
-      // 4K
-      result = SbMediaCanPlayMimeAndKeySystem(
-          "video/webm; codecs=\"vp9\"; width=3840; height=2160; framerate=30",
-          "");
-      if (result == kSbMediaSupportTypeProbably) {
-        vp9_resolution = "4K";
-      }
+  std::string av1_support =
+      get_max_video_codec_resolution("video/mp4",
+                                     {{kResolution1080p, "av01.0.08M.08"},
+                                      {kResolution2k, "av01.0.12M.08"},
+                                      {kResolution4k, "av01.0.12M.08"},
+                                      {kResolution8k, "av01.0.16M.08"}},
+                                     "30");
+  if (av1_support != "Unsupported") {
+    av1_support += "\n\tAV1 HFR: " + get_max_video_codec_resolution(
+                                         "video/mp4",
+                                         {{kResolution1080p, "av01.0.09M.08"},
+                                          {kResolution2k, "av01.0.12M.08"},
+                                          {kResolution4k, "av01.0.13M.08"},
+                                          {kResolution8k, "av01.0.17M.08"}},
+                                         "60");
+    std::string av1_hdr_support = get_max_video_codec_resolution(
+        "video/mp4",
+        {{kResolution1080p, "av01.0.09M.10.0.110.09.16.09.0"},
+         {kResolution2k, "av01.0.12M.10.0.110.09.16.09.0"},
+         {kResolution4k, "av01.0.13M.10.0.110.09.16.09.0"},
+         {kResolution8k, "av01.0.17M.10.0.110.09.16.09.0"}},
+        "30");
+    if (av1_hdr_support != "Unsupported") {
+      av1_hdr_support +=
+          "\n\tAV1 HDR HFR: " +
+          get_max_video_codec_resolution(
+              "video/mp4",
+              {{kResolution1080p, "av01.0.09M.10.0.110.09.16.09.0"},
+               {kResolution2k, "av01.0.12M.10.0.110.09.16.09.0"},
+               {kResolution4k, "av01.0.13M.10.0.110.09.16.09.0"},
+               {kResolution8k, "av01.0.17M.10.0.110.09.16.09.0"}},
+              "60");
     }
+    av1_support += "\n\tAV1 HDR: " + av1_hdr_support;
   }
 
-  // VP9 HFR
-  std::string vp9_hfr_resolution = "Unsupported";
-  // 1080p
-  result = SbMediaCanPlayMimeAndKeySystem(
-      "video/webm; codecs=\"vp9\"; width=1920; height=1080; framerate=60", "");
-  if (result == kSbMediaSupportTypeProbably) {
-    vp9_hfr_resolution = "1080p";
-    // 2K
-    result = SbMediaCanPlayMimeAndKeySystem(
-        "video/webm; codecs=\"vp9\"; width=2560; height=1440; framerate=60",
-        "");
-    if (result == kSbMediaSupportTypeProbably) {
-      vp9_hfr_resolution = "2K";
-      // 4K
-      result = SbMediaCanPlayMimeAndKeySystem(
-          "video/webm; codecs=\"vp9\"; width=3840; height=2160; framerate=60",
-          "");
-      if (result == kSbMediaSupportTypeProbably) {
-        vp9_hfr_resolution = "4K";
-      }
-    }
-  }
+  std::string aac_support = SbMediaCanPlayMimeAndKeySystem(
+                                "audio/mp4; codecs=\"mp4a.40.2\"; channels=2",
+                                "") == kSbMediaSupportTypeProbably
+                                ? "Supported"
+                                : "Unsupported";
 
-  // VP9 HDR
-  std::string vp9_hdr_resolution = "Unsupported";
-  // 1080p
-  result = SbMediaCanPlayMimeAndKeySystem(
-      "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=1920; "
-      "height=1080; framerate=30",
-      "");
-  if (result == kSbMediaSupportTypeProbably) {
-    vp9_hdr_resolution = "1080p";
-    // 2K
-    result = SbMediaCanPlayMimeAndKeySystem(
-        "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=2560; "
-        "height=1440; framerate=30",
-        "");
-    if (result == kSbMediaSupportTypeProbably) {
-      vp9_hdr_resolution = "2K";
-      // 4K
-      result = SbMediaCanPlayMimeAndKeySystem(
-          "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=3840; "
-          "height=2160; framerate=30",
-          "");
-      if (result == kSbMediaSupportTypeProbably) {
-        vp9_hdr_resolution = "4K";
-      }
-    }
-  }
+  std::string aac51_support = SbMediaCanPlayMimeAndKeySystem(
+                                  "audio/mp4; codecs=\"mp4a.40.2\"; channels=6",
+                                  "") == kSbMediaSupportTypeProbably
+                                  ? "Supported"
+                                  : "Unsupported";
 
-  // AV1
-  std::string av1_resolution = "Unsupported";
-  // 1080p
-  result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"av01.0.09M.08\"; width=1920; "
-      "height=1080; framerate=30",
-      "");
-  if (result == kSbMediaSupportTypeProbably) {
-    av1_resolution = "1080p";
-    // 2K
-    result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"av01.0.12M.08\"; width=2560; "
-        "height=1440; framerate=30",
-        "");
-    if (result == kSbMediaSupportTypeProbably) {
-      av1_resolution = "2K";
-      // 4K
-      result = SbMediaCanPlayMimeAndKeySystem(
-          "video/mp4; codecs=\"av01.0.12M.08\"; width=3840; "
-          "height=2160; framerate=30",
-          "");
-      if (result == kSbMediaSupportTypeProbably) {
-        av1_resolution = "4K";
-      }
-    }
-  }
-  // AV1 HFR
-  std::string av1_hfr_resolution = "Unsupported";
-  // 1080p
-  result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"av01.0.09M.08\"; width=1920; height=1080; "
-      "framerate=60",
-      "");
-  if (result == kSbMediaSupportTypeProbably) {
-    av1_hfr_resolution = "1080p";
-    // 2K
-    result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"av01.0.12M.08\"; width=2560; height=1440; "
-        "framerate=60",
-        "");
-    if (result == kSbMediaSupportTypeProbably) {
-      av1_hfr_resolution = "2K";
-      // 4K
-      result = SbMediaCanPlayMimeAndKeySystem(
-          "video/mp4; codecs=\"av01.0.13M.08\"; width=3840; height=2160; "
-          "framerate=60",
-          "");
-      if (result == kSbMediaSupportTypeProbably) {
-        av1_hfr_resolution = "4K";
-      }
-    }
-  }
+  std::string opus_support = SbMediaCanPlayMimeAndKeySystem(
+                                 "audio/webm; codecs=\"opus\"; "
+                                 "channels=2; bitrate=128000;",
+                                 "") == kSbMediaSupportTypeProbably
+                                 ? "Supported"
+                                 : "Unsupported";
 
-  // AV1 HDR
-  std::string av1_hdr_resolution = "Unsupported";
-  // 1080p
-  result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"av01.0.09M.10.0.110.09.16.09.0\"; width=1920; "
-      "height=1080",
-      "");
-  if (result == kSbMediaSupportTypeProbably) {
-    av1_hdr_resolution = "1080p";
-    // 2K
-    result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"av01.0.12M.10.0.110.09.16.09.0\"; width=2560; "
-        "height=1440",
-        "");
-    if (result == kSbMediaSupportTypeProbably) {
-      av1_hdr_resolution = "2K";
-      // 4K
-      result = SbMediaCanPlayMimeAndKeySystem(
-          "video/mp4; codecs=\"av01.0.13M.10.0.110.09.16.09.0\"; width=3840; "
-          "height=2160",
-          "");
-      if (result == kSbMediaSupportTypeProbably) {
-        av1_hdr_resolution = "4K";
-      }
-    }
-  }
+  std::string opus51_support = SbMediaCanPlayMimeAndKeySystem(
+                                   "audio/webm; codecs=\"opus\"; "
+                                   "channels=6; bitrate=576000;",
+                                   "") == kSbMediaSupportTypeProbably
+                                   ? "Supported"
+                                   : "Unsupported";
 
-  // AAC
-  std::string aac_support = "Unsupported";
-  result = SbMediaCanPlayMimeAndKeySystem(
-      "audio/mp4; codecs=\"mp4a.40.2\"; channels=2", "");
-  if (result == kSbMediaSupportTypeProbably) {
-    aac_support = "Supported";
-  }
+  std::string ac3_support =
+      SbMediaCanPlayMimeAndKeySystem(
+          "audio/mp4; codecs=\"ac-3\"; channels=6; bitrate=512000", "") ==
+              kSbMediaSupportTypeProbably
+          ? "Supported"
+          : "Unsupported";
 
-  // AAC51
-  std::string aac51_support = "Unsupported";
-  result = SbMediaCanPlayMimeAndKeySystem(
-      "audio/mp4; codecs=\"mp4a.40.2\"; channels=6", "");
-  if (result == kSbMediaSupportTypeProbably) {
-    aac51_support = "Supported";
-  }
+  std::string eac3_support =
+      SbMediaCanPlayMimeAndKeySystem(
+          "audio/mp4; codecs=\"ec-3\"; channels=6; bitrate=512000", "") ==
+              kSbMediaSupportTypeProbably
+          ? "Supported"
+          : "Unsupported";
 
-  // Opus
-  std::string opus_support = "Unsupported";
-  result = SbMediaCanPlayMimeAndKeySystem(
-      "audio/webm; codecs=\"opus\"; "
-      "channels=2; bitrate=128000;",
-      "");
-  if (result == kSbMediaSupportTypeProbably) {
-    opus_support = "Supported";
-  }
-
-  // Opus51
-  std::string opus51_support = "Unsupported";
-  result = SbMediaCanPlayMimeAndKeySystem(
-      "audio/webm; codecs=\"opus\"; "
-      "channels=6; bitrate=576000;",
-      "");
-  if (result == kSbMediaSupportTypeProbably) {
-    opus51_support = "Supported";
-  }
-
-  // AC-3
-  std::string ac3_support = "Unsupported";
-  result = SbMediaCanPlayMimeAndKeySystem(
-      "audio/mp4; codecs=\"ac-3\"; channels=6; bitrate=512000", "");
-  if (result == kSbMediaSupportTypeProbably) {
-    ac3_support = "Supported";
-  }
-
-  // E-AC-3
-  std::string eac3_support = "Unsupported";
-  result = SbMediaCanPlayMimeAndKeySystem(
-      "audio/mp4; codecs=\"ec-3\"; channels=6; bitrate=512000", "");
-  if (result == kSbMediaSupportTypeProbably) {
-    eac3_support = "Supported";
-  }
-
-  SB_LOG(INFO) << "\nVideo Codec Capability:\n\tAVC: " << avc_resolution
-               << "\n\tAVC HFR: " << avc_hfr_resolution
-               << "\n\tVP9: " << vp9_resolution
-               << "\n\tVP9 HFR: " << vp9_hfr_resolution
-               << "\n\tVP9 HDR: " << vp9_hdr_resolution
-               << "\n\tAV1: " << av1_resolution
-               << "\n\tAV1 HFR:" << av1_hfr_resolution
-               << "\n\tAV1 HDR:" << av1_hdr_resolution
+  SB_LOG(INFO) << "\nVideo Codec Capability:\n\tAVC: " << avc_support
+               << "\n\tVP9: " << vp9_support << "\n\tAV1: " << av1_support
                << "\n\nAudio Codec Capability:\n\tAAC: " << aac_support
                << "\n\tAAC 51: " << aac51_support
                << "\n\tOpus: " << opus_support
                << "\n\tOpus 51: " << opus51_support
-               << "\n\tAC-3: " << ac3_support << "\n\tE-AC-3: " << eac3_support;
+               << "\n\tAC-3: " << ac3_support << "\n\tE-AC-3: " << eac3_support
+               << "\n\nDRM Capability:\n\tWidevine: "
+               << get_drm_system_support("com.widevine") << "\n\tPlayReady: "
+               << get_drm_system_support("com.youtube.playready");
 }
 
 // TODO: Create an abstraction to shorten the length of this test.
diff --git a/src/starboard/shared/libjpeg/jpeg_image_decoder.cc b/src/starboard/shared/libjpeg/jpeg_image_decoder.cc
index cbd6adb..65aba00 100644
--- a/src/starboard/shared/libjpeg/jpeg_image_decoder.cc
+++ b/src/starboard/shared/libjpeg/jpeg_image_decoder.cc
@@ -158,9 +158,13 @@
         FillRow<2, 1, 0, 3>(static_cast<int>(info->image_width), pixel_data,
                             sample_buffer);
         break;
+      // All the other decode formats are not supported.
       case kSbDecodeTargetFormat2PlaneYUVNV12:
       case kSbDecodeTargetFormat3PlaneYUVI420:
       case kSbDecodeTargetFormat3Plane10BitYUVI420:
+#if SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+      case kSbDecodeTargetFormat3Plane10BitYUVI420Compact:
+#endif  // SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
       case kSbDecodeTargetFormat1PlaneUYVY:
       case kSbDecodeTargetFormatInvalid:
         SB_NOTREACHED();
diff --git a/src/starboard/shared/posix/free_space.cc b/src/starboard/shared/posix/free_space.cc
new file mode 100644
index 0000000..f66de59
--- /dev/null
+++ b/src/starboard/shared/posix/free_space.cc
@@ -0,0 +1,55 @@
+// 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 "starboard/shared/posix/free_space.h"
+
+#include <sys/statvfs.h>
+#include <vector>
+
+#include "cobalt/extension/free_space.h"
+#include "starboard/common/log.h"
+#include "starboard/configuration_constants.h"
+
+namespace starboard {
+namespace shared {
+namespace posix {
+
+namespace {
+
+int64_t MeasureFreeSpace(SbSystemPathId system_path_id) {
+  std::vector<char> path(kSbFileMaxPath + 1);
+  if (!SbSystemGetPath(system_path_id, path.data(), path.size())) {
+    return -1;
+  }
+  struct statvfs s;
+  if (statvfs(path.data(), &s) != 0) {
+    return -1;
+  }
+
+  return s.f_bsize * s.f_bfree;
+}
+
+const CobaltExtensionFreeSpaceApi kFreeSpaceApi = {
+    kCobaltExtensionFreeSpaceName, 1, &MeasureFreeSpace,
+};
+
+}  // namespace
+
+const void* GetFreeSpaceApi() {
+  return &kFreeSpaceApi;
+}
+
+}  // namespace posix
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/posix/free_space.h b/src/starboard/shared/posix/free_space.h
new file mode 100644
index 0000000..1d44447
--- /dev/null
+++ b/src/starboard/shared/posix/free_space.h
@@ -0,0 +1,28 @@
+// 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.
+
+#ifndef STARBOARD_SHARED_POSIX_FREE_SPACE_H_
+#define STARBOARD_SHARED_POSIX_FREE_SPACE_H_
+
+namespace starboard {
+namespace shared {
+namespace posix {
+
+const void* GetFreeSpaceApi();
+
+}  // namespace posix
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_POSIX_FREE_SPACE_H_
diff --git a/src/starboard/shared/posix/socket_bind.cc b/src/starboard/shared/posix/socket_bind.cc
index fcb5869..42f8335 100644
--- a/src/starboard/shared/posix/socket_bind.cc
+++ b/src/starboard/shared/posix/socket_bind.cc
@@ -28,21 +28,21 @@
                            const SbSocketAddress* local_address) {
   if (!SbSocketIsValid(socket)) {
     errno = EBADF;
-    SB_DLOG(ERROR) << __FUNCTION__ << ": Invalid socket";
+    SB_LOG(ERROR) << __FUNCTION__ << ": Invalid socket";
     return kSbSocketErrorFailed;
   }
 
   sbposix::SockAddr sock_addr;
   if (!sock_addr.FromSbSocketAddress(local_address)) {
-    SB_DLOG(ERROR) << __FUNCTION__ << ": Invalid address";
+    SB_LOG(ERROR) << __FUNCTION__ << ": Invalid address";
     return (socket->error = sbposix::TranslateSocketErrno(EINVAL));
   }
 
   SB_DCHECK(socket->socket_fd >= 0);
   if (local_address->type != socket->address_type) {
-    SB_DLOG(ERROR) << __FUNCTION__ << ": Incompatible addresses: "
-                   << "socket type = " << socket->address_type
-                   << ", argument type = " << local_address->type;
+    SB_LOG(ERROR) << __FUNCTION__ << ": Incompatible addresses: "
+                  << "socket type = " << socket->address_type
+                  << ", argument type = " << local_address->type;
     return (socket->error = sbposix::TranslateSocketErrno(EAFNOSUPPORT));
   }
 
@@ -63,7 +63,7 @@
   int result = HANDLE_EINTR(
       bind(socket->socket_fd, sock_addr.sockaddr(), sock_addr.length));
   if (result != 0) {
-    SB_DLOG(ERROR) << __FUNCTION__ << ": Bind failed. errno=" << errno;
+    SB_LOG(ERROR) << __FUNCTION__ << ": Bind failed. errno=" << errno;
     return (socket->error = sbposix::TranslateSocketErrno(errno));
   }
 
diff --git a/src/starboard/shared/posix/socket_connect.cc b/src/starboard/shared/posix/socket_connect.cc
index 0172da5..aa47dea 100644
--- a/src/starboard/shared/posix/socket_connect.cc
+++ b/src/starboard/shared/posix/socket_connect.cc
@@ -30,15 +30,15 @@
 
   sbposix::SockAddr sock_addr;
   if (!sock_addr.FromSbSocketAddress(address)) {
-    SB_DLOG(ERROR) << __FUNCTION__ << ": Invalid address";
+    SB_LOG(ERROR) << __FUNCTION__ << ": Invalid address";
     return (socket->error = kSbSocketErrorFailed);
   }
 
   SB_DCHECK(socket->socket_fd >= 0);
   if (address->type != socket->address_type) {
-    SB_DLOG(ERROR) << __FUNCTION__ << ": Incompatible addresses: "
-                   << "socket type = " << socket->address_type
-                   << ", argument type = " << address->type;
+    SB_LOG(ERROR) << __FUNCTION__ << ": Incompatible addresses: "
+                  << "socket type = " << socket->address_type
+                  << ", argument type = " << address->type;
     return (socket->error = kSbSocketErrorFailed);
   }
 
@@ -49,7 +49,7 @@
   }
 
   if (result != 0) {
-    SB_DLOG(ERROR) << __FUNCTION__ << ": connect failed: " << errno;
+    SB_LOG(ERROR) << __FUNCTION__ << ": connect failed: " << errno;
     return (socket->error = kSbSocketErrorFailed);
   }
 
diff --git a/src/starboard/shared/posix/socket_internal.cc b/src/starboard/shared/posix/socket_internal.cc
index 1b18007..3e60e5b 100644
--- a/src/starboard/shared/posix/socket_internal.cc
+++ b/src/starboard/shared/posix/socket_internal.cc
@@ -34,7 +34,7 @@
 const socklen_t kAddressStructLengthIpv6 =
     static_cast<socklen_t>(sizeof(struct sockaddr_in6));
 #endif
-}
+}  // namespace
 
 SbSocketError TranslateSocketErrno(int error) {
   switch (error) {
@@ -73,8 +73,8 @@
   int result =
       setsockopt(socket->socket_fd, level, option_code, &on, sizeof(on));
   if (result != 0) {
-    SB_DLOG(ERROR) << "Failed to set " << option_name << " on socket "
-                   << socket->socket_fd << ", errno = " << errno;
+    SB_LOG(ERROR) << "Failed to set " << option_name << " on socket "
+                  << socket->socket_fd << ", errno = " << errno;
     socket->error = TranslateSocketErrno(errno);
     return false;
   }
@@ -97,8 +97,8 @@
   int result =
       setsockopt(socket->socket_fd, level, option_code, &value, sizeof(value));
   if (result != 0) {
-    SB_DLOG(ERROR) << "Failed to set " << option_name << " on socket "
-                   << socket->socket_fd << ", errno = " << errno;
+    SB_LOG(ERROR) << "Failed to set " << option_name << " on socket "
+                  << socket->socket_fd << ", errno = " << errno;
     socket->error = TranslateSocketErrno(errno);
     return false;
   }
@@ -147,7 +147,7 @@
     return false;
   }
 
-  // Check that we have been properly initialized.
+// Check that we have been properly initialized.
 #if SB_HAS(IPV6)
   SB_DCHECK(length == kAddressStructLengthIpv4 ||
             length == kAddressStructLengthIpv6);
@@ -209,7 +209,7 @@
 #endif
   }
 
-  SB_DLOG(WARNING) << "Unrecognized address family: " << family;
+  SB_LOG(WARNING) << "Unrecognized address family: " << family;
   return false;
 }
 
diff --git a/src/starboard/shared/posix/socket_join_multicast_group.cc b/src/starboard/shared/posix/socket_join_multicast_group.cc
index 72d3593..0dde43f 100644
--- a/src/starboard/shared/posix/socket_join_multicast_group.cc
+++ b/src/starboard/shared/posix/socket_join_multicast_group.cc
@@ -45,8 +45,8 @@
   int result = setsockopt(socket->socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
                           &imreq, sizeof(imreq));
   if (result != 0) {
-    SB_DLOG(ERROR) << "Failed to IP_ADD_MEMBERSHIP on socket "
-                   << socket->socket_fd << ", errno = " << errno;
+    SB_LOG(ERROR) << "Failed to IP_ADD_MEMBERSHIP on socket "
+                  << socket->socket_fd << ", errno = " << errno;
     socket->error = sbposix::TranslateSocketErrno(errno);
     return false;
   }
diff --git a/src/starboard/shared/posix/socket_receive_from.cc b/src/starboard/shared/posix/socket_receive_from.cc
index de1e2b8..8c0b779 100644
--- a/src/starboard/shared/posix/socket_receive_from.cc
+++ b/src/starboard/shared/posix/socket_receive_from.cc
@@ -49,14 +49,14 @@
       int result = getpeername(socket->socket_fd, sock_addr.sockaddr(),
                                &sock_addr.length);
       if (result < 0) {
-        SB_DLOG(ERROR) << __FUNCTION__
-                       << ": getpeername failed, errno = " << errno;
+        SB_LOG(ERROR) << __FUNCTION__
+                      << ": getpeername failed, errno = " << errno;
         socket->error = sbposix::TranslateSocketErrno(errno);
         return -1;
       }
 
       if (!sock_addr.ToSbSocketAddress(out_source)) {
-        SB_DLOG(FATAL) << __FUNCTION__ << ": Bad TCP source address.";
+        SB_LOG(FATAL) << __FUNCTION__ << ": Bad TCP source address.";
         socket->error = kSbSocketErrorFailed;
         return -1;
       }
@@ -71,7 +71,7 @@
 
     if (IsReportableErrno(errno) &&
         socket->error != sbposix::TranslateSocketErrno(errno)) {
-      SB_DLOG(ERROR) << "recv failed, errno = " << errno;
+      SB_LOG(ERROR) << "recv failed, errno = " << errno;
     }
     socket->error = sbposix::TranslateSocketErrno(errno);
     return -1;
@@ -84,7 +84,7 @@
     if (bytes_read >= 0) {
       if (out_source) {
         if (!sock_addr.ToSbSocketAddress(out_source)) {
-          SB_DLOG(FATAL) << __FUNCTION__ << ": Bad UDP source address.";
+          SB_LOG(FATAL) << __FUNCTION__ << ": Bad UDP source address.";
           socket->error = kSbSocketErrorFailed;
           return -1;
         }
@@ -96,7 +96,7 @@
 
     if (errno != EAGAIN && errno != EWOULDBLOCK &&
         socket->error != sbposix::TranslateSocketErrno(errno)) {
-      SB_DLOG(ERROR) << "recvfrom failed, errno = " << errno;
+      SB_LOG(ERROR) << "recvfrom failed, errno = " << errno;
     }
     socket->error = sbposix::TranslateSocketErrno(errno);
     return -1;
diff --git a/src/starboard/shared/posix/socket_send_to.cc b/src/starboard/shared/posix/socket_send_to.cc
index cdee458..3e81ae0 100644
--- a/src/starboard/shared/posix/socket_send_to.cc
+++ b/src/starboard/shared/posix/socket_send_to.cc
@@ -39,7 +39,7 @@
   SB_DCHECK(socket->socket_fd >= 0);
   if (socket->protocol == kSbSocketProtocolTcp) {
     if (destination) {
-      SB_DLOG(FATAL) << "Destination passed to TCP send.";
+      SB_LOG(FATAL) << "Destination passed to TCP send.";
       socket->error = kSbSocketErrorFailed;
       return -1;
     }
@@ -52,13 +52,13 @@
     }
 
     if (errno != EAGAIN && errno != EWOULDBLOCK) {
-      SB_DLOG(ERROR) << "send failed, errno = " << errno;
+      SB_LOG(ERROR) << "send failed, errno = " << errno;
     }
     socket->error = sbposix::TranslateSocketErrno(errno);
     return -1;
   } else if (socket->protocol == kSbSocketProtocolUdp) {
     if (!destination) {
-      SB_DLOG(FATAL) << "No destination passed to UDP send.";
+      SB_LOG(FATAL) << "No destination passed to UDP send.";
       socket->error = kSbSocketErrorFailed;
       return -1;
     }
@@ -68,7 +68,7 @@
     socklen_t sockaddr_length = 0;
     if (destination) {
       if (!sock_addr.FromSbSocketAddress(destination)) {
-        SB_DLOG(FATAL) << "Invalid destination passed to UDP send.";
+        SB_LOG(FATAL) << "Invalid destination passed to UDP send.";
         socket->error = kSbSocketErrorFailed;
         return -1;
       }
@@ -84,7 +84,7 @@
     }
 
     if (errno != EAGAIN && errno != EWOULDBLOCK) {
-      SB_DLOG(ERROR) << "sendto failed, errno = " << errno;
+      SB_LOG(ERROR) << "sendto failed, errno = " << errno;
     }
     socket->error = sbposix::TranslateSocketErrno(errno);
     return -1;
diff --git a/src/third_party/devtools/devtools.gyp b/src/third_party/devtools/devtools.gyp
index d8a1f2c..17c7ce6 100644
--- a/src/third_party/devtools/devtools.gyp
+++ b/src/third_party/devtools/devtools.gyp
@@ -77,6 +77,7 @@
       "front_end/cm_headless/module.json",
       "front_end/cm_modes/module.json",
       "front_end/cm_web_modes/module.json",
+      "front_end/cobalt/module.json",
       "front_end/color_picker/module.json",
       "front_end/color_picker/spectrum.css",
       "front_end/common/module.json",
@@ -548,6 +549,7 @@
     # (Not important since Cobalt doesn't need lighthouse)
 
     'all_devtools_modules': [
+      "front_end/cobalt/cobalt.js",
       "front_end/network/network.js",
       "front_end/network/SignedExchangeInfoView.js",
       "front_end/network/ResourceWebSocketFrameView.js",
@@ -1273,6 +1275,7 @@
     ],
 
     'copied_devtools_modules': [
+      "<(resources_out_dir)/cobalt/cobalt.js",
       "<(resources_out_dir)/network/network.js",
       "<(resources_out_dir)/network/SignedExchangeInfoView.js",
       "<(resources_out_dir)/network/ResourceWebSocketFrameView.js",
diff --git a/src/third_party/devtools/front_end/cobalt/cobalt.js b/src/third_party/devtools/front_end/cobalt/cobalt.js
new file mode 100644
index 0000000..a085c62
--- /dev/null
+++ b/src/third_party/devtools/front_end/cobalt/cobalt.js
@@ -0,0 +1,112 @@
+
+/**
+ * @implements {SDK.TargetManager.Observer}
+ * @unrestricted
+ */
+export default class CobaltPanel extends UI.VBox {
+    constructor() {
+        const trace_files = [
+            ['Trace', 'console_trace.json'],
+            ['Timed Trace', 'timed_trace.json']
+        ];
+        super(true, false);
+        SDK.targetManager.observeTargets(this);
+
+        this._target = UI.context.flavor(SDK.Target);
+        this._runtimeAgent = this._target.runtimeAgent();
+
+        this.element = this._shadowRoot.createChild('div');
+        this.element.textContent = 'Cobalt Console';
+        let download_element = this._shadowRoot.createChild('a', 'download');
+        download_element.style.display = 'none';
+
+        const traceContainer = this.element.createChild('div', 'trace-container');
+        traceContainer.appendChild(UI.createTextButton(Common.UIString('Start Trace'), event => {
+            console.log("Start Trace");
+            const filename = trace_files[0][1];
+            this.run(`(function() { window.h5vcc.traceEvent.start('${filename}');})()`);
+            console.log("Started Trace");
+        }));
+        traceContainer.appendChild(UI.createTextButton(Common.UIString('Stop Trace'), event => {
+            console.log("Stop Trace");
+            this.run(`(function() { window.h5vcc.traceEvent.stop();})()`);
+            console.log("Stopped Trace");
+        }));
+        trace_files.forEach( (file) => {
+            traceContainer.appendChild(UI.createTextButton(Common.UIString('Download ' + file[0]), event => {
+                console.log("Download Trace");
+                const filename = file[1];
+                this.run(`(function() { return window.h5vcc.traceEvent.read('${filename}');})()`).then(function (result) {
+                    download_element.setAttribute('href', 'data:text/plain;charset=utf-8,' +
+                        encodeURIComponent(result.result.value));
+                    download_element.setAttribute('download', filename);
+                    console.log("Downloaded Trace");
+                    download_element.click();
+                    download_element.setAttribute('href', undefined);
+                });
+            }));
+        });
+    }
+
+    async run(expression) {
+        return await this._runtimeAgent.invoke_evaluate({ expression, returnByValue: true });
+    }
+
+
+    /**
+     * @override
+     */
+    focus() {
+    }
+
+    /**
+     * @override
+     */
+    wasShown() {
+        super.wasShown();
+        if (this._model) {
+            this._model.enable();
+        }
+    }
+
+    /**
+     * @override
+     */
+    willHide() {
+        if (this._model) {
+            this._model.disable();
+        }
+        super.willHide();
+    }
+
+    /**
+     * @override
+     * @param {!SDK.Target} target
+     */
+    targetAdded(target) {
+        if (this._model) {
+            return;
+        }
+        if (!this._model) {
+            return;
+        }
+    }
+
+    /**
+     * @override
+     * @param {!SDK.Target} target
+     */
+    targetRemoved(target) {
+        if (!this._model || this._model.target() !== target) {
+            return;
+        }
+    }
+}
+
+/* Legacy exported object */
+self.Cobalt = self.Cobalt || {};
+
+/**
+ * @constructor
+ */
+self.Cobalt.CobaltPanel = CobaltPanel;
diff --git a/src/third_party/devtools/front_end/cobalt/module.json b/src/third_party/devtools/front_end/cobalt/module.json
new file mode 100644
index 0000000..7bc7faa
--- /dev/null
+++ b/src/third_party/devtools/front_end/cobalt/module.json
@@ -0,0 +1,19 @@
+{
+    "extensions": [
+        {
+            "type": "view",
+            "location": "panel",
+            "id": "cobalt",
+            "title": "Cobalt",
+            "order": 100,
+            "persistence": "permanent",
+            "className": "Cobalt.CobaltPanel"
+        }
+    ],
+    "dependencies": [],
+    "scripts": [],
+    "modules": [
+        "cobalt.js",
+    ],
+    "resources": []
+}
diff --git a/src/third_party/devtools/front_end/devtools_app.json b/src/third_party/devtools/front_end/devtools_app.json
index b29c5bb..c662566 100644
--- a/src/third_party/devtools/front_end/devtools_app.json
+++ b/src/third_party/devtools/front_end/devtools_app.json
@@ -8,6 +8,8 @@
     // { "name": "animation" },
     // { "name": "audits" },
     { "name": "browser_debugger" },
+    { "name": "cobalt" },
+
     { "name": "css_overview" },
     { "name": "cookie_table" },
     // { "name": "dagre_layout", "type": "remote" },
diff --git a/src/third_party/zlib/google/zip_reader.cc b/src/third_party/zlib/google/zip_reader.cc
index f3e9d0f..90b4145 100644
--- a/src/third_party/zlib/google/zip_reader.cc
+++ b/src/third_party/zlib/google/zip_reader.cc
@@ -50,6 +50,10 @@
 
   void SetTimeModified(const base::Time& time) override;
 
+#if defined(STARBOARD)
+  bool IsSuccessful();
+#endif
+
  private:
   size_t max_read_bytes_;
   std::string* output_;
@@ -70,6 +74,12 @@
   return true;
 }
 
+#if defined(STARBOARD)
+bool StringWriterDelegate::IsSuccessful() {
+  return true;
+}
+#endif
+
 bool StringWriterDelegate::WriteBytes(const char* data, int num_bytes) {
   if (output_->size() + num_bytes > max_read_bytes_)
     return false;
@@ -289,7 +299,7 @@
     delegate->SetTimeModified(current_entry_info()->last_modified());
   }
 
-  return entire_file_extracted;
+  return entire_file_extracted && delegate->IsSuccessful();
 }
 
 void ZipReader::ExtractCurrentEntryToFilePathAsync(
@@ -467,6 +477,12 @@
   return file_->Seek(base::File::FROM_BEGIN, 0) >= 0;
 }
 
+#if defined(STARBOARD)
+bool FileWriterDelegate::IsSuccessful() {
+  return true;
+}
+#endif
+
 bool FileWriterDelegate::WriteBytes(const char* data, int num_bytes) {
   int bytes_written = file_->WriteAtCurrentPos(data, num_bytes);
   if (bytes_written > 0)
@@ -497,6 +513,12 @@
   return file_.IsValid();
 }
 
+#ifdef STARBOARD
+bool FilePathWriterDelegate::IsSuccessful() {
+  return true;
+}
+#endif
+
 bool FilePathWriterDelegate::WriteBytes(const char* data, int num_bytes) {
   return num_bytes == file_.WriteAtCurrentPos(data, num_bytes);
 }
diff --git a/src/third_party/zlib/google/zip_reader.h b/src/third_party/zlib/google/zip_reader.h
index d442d42..ca0414b 100644
--- a/src/third_party/zlib/google/zip_reader.h
+++ b/src/third_party/zlib/google/zip_reader.h
@@ -42,6 +42,11 @@
 
   // Sets the last-modified time of the data.
   virtual void SetTimeModified(const base::Time& time) = 0;
+
+#ifdef STARBOARD
+  // Returns true if the writer finished successfully.
+  virtual bool IsSuccessful() = 0;
+#endif
 };
 
 // This class is used for reading zip files. A typical use case of this
@@ -255,6 +260,10 @@
   // Sets the last-modified time of the data.
   void SetTimeModified(const base::Time& time) override;
 
+#ifdef STARBOARD
+  bool IsSuccessful() override;
+#endif
+
   // Return the actual size of the file.
   int64_t file_length() { return file_length_; }
 
@@ -289,6 +298,9 @@
   // Sets the last-modified time of the data.
   void SetTimeModified(const base::Time& time) override;
 
+#ifdef STARBOARD
+  bool IsSuccessful() override;
+#endif
  private:
   base::FilePath output_file_path_;
   base::File file_;
diff --git a/src/third_party/zlib/google/zip_reader_unittest.cc b/src/third_party/zlib/google/zip_reader_unittest.cc
index a595102..cd4f1ae 100644
--- a/src/third_party/zlib/google/zip_reader_unittest.cc
+++ b/src/third_party/zlib/google/zip_reader_unittest.cc
@@ -115,6 +115,9 @@
 class MockWriterDelegate : public zip::WriterDelegate {
  public:
   MOCK_METHOD0(PrepareOutput, bool());
+#if defined(STARBOARD)
+  MOCK_METHOD0(IsSuccessful, bool());
+#endif
   MOCK_METHOD2(WriteBytes, bool(const char*, int));
   MOCK_METHOD1(SetTimeModified, void(const base::Time&));
 };
@@ -644,6 +647,10 @@
   EXPECT_CALL(mock_writer, WriteBytes(_, _))
       .WillRepeatedly(Return(true));
   EXPECT_CALL(mock_writer, SetTimeModified(_));
+#if defined(STARBOARD)
+  EXPECT_CALL(mock_writer, IsSuccessful())
+      .WillOnce(Return(true));
+#endif
 
   base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
   ZipReader reader;