Import Cobalt 23.master.0.308777
diff --git a/cobalt/BUILD.gn b/cobalt/BUILD.gn
index d025533..e9c9fcb 100644
--- a/cobalt/BUILD.gn
+++ b/cobalt/BUILD.gn
@@ -47,6 +47,7 @@
 
   if (sb_is_evergreen) {
     deps += [
+      "//cobalt/updater:updater_test",
       "//components/update_client:cobalt_slot_management_test",
       "//components/update_client:update_client_test",
     ]
diff --git a/cobalt/base/BUILD.gn b/cobalt/base/BUILD.gn
index 1ba068f..898475d 100644
--- a/cobalt/base/BUILD.gn
+++ b/cobalt/base/BUILD.gn
@@ -72,6 +72,7 @@
     "source_location.h",
     "startup_timer.cc",
     "startup_timer.h",
+    "statistics.h",
     "stop_watch.cc",
     "stop_watch.h",
     "token.cc",
@@ -113,6 +114,7 @@
     "c_val_time_interval_timer_stats_test.cc",
     "circular_buffer_shell_unittest.cc",
     "fixed_size_lru_cache_test.cc",
+    "statistics_test.cc",
     "token_test.cc",
   ]
   deps = [
diff --git a/cobalt/base/statistics.h b/cobalt/base/statistics.h
new file mode 100644
index 0000000..90dbe3e
--- /dev/null
+++ b/cobalt/base/statistics.h
@@ -0,0 +1,183 @@
+// 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_BASE_STATISTICS_H_
+#define COBALT_BASE_STATISTICS_H_
+
+#include <algorithm>
+#include <functional>
+#include <vector>
+
+#include "starboard/types.h"
+
+namespace base {
+
+namespace internal {
+
+// The default function to convert a sample to its value by dividing.
+inline int64_t DefaultSampleToValueFunc(int64_t dividend, int64_t divisor) {
+  if (divisor == 0) {
+    divisor = 1;
+  }
+  return dividend / divisor;
+}
+
+}  // namespace internal
+
+// Track the statistics of a series of generic samples reprensented as
+// dividends and divisors in integer types.  The value of a sample is calculated
+// by `SampleToValueFunc` using its dividend and divisor.
+//
+// Example usages:
+//   1. Set dividends to bytes in int and divisors to SbTime, to track the
+//      statistics of bandwidth.
+//   2. Set the divisor to always be 1, to track the statistics of a count or a
+//      duration.
+//
+// Currently the class can produce the following statistics:
+//   1. Average, tracked across all samples.
+//   2. Median, tracked across the most recent |MaxSamples| samples.
+//   3. Min, tracked across all samples.
+//   4. Max, tracked across all samples.
+//
+// Notes:
+//   1. The accumulated values are stored in `int64_t`, and it is the user's
+//      responsibility to avoid overflow.
+//   2. The class isn't multi-thread safe, and it is the user's responsibility
+//      to synchronize the usage when necessary.
+//   3. Both the dividend and divisor of the samples should ideally be positive,
+//      which is NOT enforced by the class.
+
+#if defined(COBALT_BUILD_TYPE_GOLD)
+
+template <typename DividendType, typename DivisorType, int MaxSamples,
+          int64_t (*SampleToValueFunc)(int64_t, int64_t) =
+              internal::DefaultSampleToValueFunc>
+class Statistics {
+ public:
+  constexpr Statistics() = default;
+
+  void AddSample(DividendType dividend, DivisorType divisor) {}
+  int64_t accumulated_dividend() const { return 0; }
+  int64_t accumulated_divisor() const { return 1; }
+  int64_t average() const { return 0; }
+  int64_t min() const { return 0; }
+  int64_t max() const { return 0; }
+
+  int64_t GetMedian() const { return 0; }
+};
+
+#else  // defined(COBALT_BUILD_TYPE_GOLD)
+
+template <typename DividendType, typename DivisorType, size_t MaxSamples,
+          int64_t (*SampleToValueFunc)(int64_t, int64_t) =
+              internal::DefaultSampleToValueFunc>
+class Statistics {
+ public:
+  constexpr Statistics() = default;
+
+  void AddSample(DividendType dividend, DivisorType divisor) {
+    static_assert(MaxSamples > 0, "MaxSamples has to be greater than 0.");
+
+    auto& current =
+        samples_[(first_sample_index_ + number_of_samples_) % MaxSamples];
+    if (number_of_samples_ == MaxSamples) {
+      first_sample_index_ = (first_sample_index_ + 1) % MaxSamples;
+    } else {
+      ++number_of_samples_;
+    }
+
+    current.dividend = dividend;
+    current.divisor = divisor;
+    accumulated_dividend_ += dividend;
+    accumulated_divisor_ += divisor;
+
+    auto value = GetSampleValue(current);
+    if (first_sample_added_) {
+      min_ = std::min(min_, value);
+      max_ = std::max(max_, value);
+    } else {
+      min_ = max_ = value;
+      first_sample_added_ = true;
+    }
+  }
+
+  int64_t accumulated_dividend() const { return accumulated_dividend_; }
+  int64_t accumulated_divisor() const { return accumulated_divisor_; }
+
+  int64_t average() const {
+    return SampleToValueFunc(accumulated_dividend_, accumulated_divisor_);
+  }
+  int64_t min() const { return min_; }
+  int64_t max() const { return max_; }
+
+  // When there are even number of samples in the object, it is implementation
+  // specific to pick any number close to the middle, or the median of the
+  // numbers close to the middle.  Use {1, 2, 3, 4} as an example, the median
+  // can be 3, or 4, or 3.5.
+  int64_t GetMedian() const {
+    if (number_of_samples_ == 0) {
+      return 0;
+    }
+
+    std::vector<Sample> copy;
+    copy.reserve(number_of_samples_);
+    if (first_sample_index_ + number_of_samples_ <= MaxSamples) {
+      copy.assign(samples_ + first_sample_index_,
+                  samples_ + first_sample_index_ + number_of_samples_);
+    } else {
+      auto samples_to_copy = number_of_samples_;
+      copy.assign(samples_ + first_sample_index_, samples_ + MaxSamples);
+      samples_to_copy -= MaxSamples - first_sample_index_;
+      copy.insert(copy.end(), samples_, samples_ + samples_to_copy);
+    }
+
+    std::nth_element(copy.begin(), copy.begin() + number_of_samples_ / 2,
+                     copy.end(), [](const Sample& left, const Sample& right) {
+                       return GetSampleValue(left) < GetSampleValue(right);
+                     });
+    return GetSampleValue(copy[number_of_samples_ / 2]);
+  }
+
+ private:
+  Statistics(const Statistics&) = delete;
+  Statistics& operator=(const Statistics&) = delete;
+
+  struct Sample {
+    DividendType dividend = 0;
+    DivisorType divisor = 0;
+  };
+
+  static int64_t GetSampleValue(const Sample& sample) {
+    return SampleToValueFunc(static_cast<int64_t>(sample.dividend),
+                             static_cast<int64_t>(sample.divisor));
+  }
+
+  bool first_sample_added_ = false;
+
+  int64_t accumulated_dividend_ = 0;
+  int64_t accumulated_divisor_ = 0;
+  int64_t min_ = 0;
+  int64_t max_ = 0;
+
+  Sample samples_[MaxSamples] = {};  // Ring buffer for samples.
+  size_t number_of_samples_ = 0;     // Number of samples in |samples_|.
+  size_t first_sample_index_ = 0;    // Index of the first sample in |samples_|.
+};
+
+#endif  // defined(COBALT_BUILD_TYPE_GOLD)
+
+}  // namespace base
+
+#endif  // COBALT_BASE_STATISTICS_H_
diff --git a/cobalt/base/statistics_test.cc b/cobalt/base/statistics_test.cc
new file mode 100644
index 0000000..beefdb9
--- /dev/null
+++ b/cobalt/base/statistics_test.cc
@@ -0,0 +1,165 @@
+// 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/base/statistics.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+TEST(StatisticsTest, ZeroSamples) {
+  Statistics<int, int, 1> statistics;
+
+  EXPECT_EQ(statistics.accumulated_dividend(), 0);
+  EXPECT_EQ(statistics.accumulated_divisor(), 0);
+  EXPECT_EQ(statistics.average(), 0);
+  EXPECT_EQ(statistics.min(), 0);
+  EXPECT_EQ(statistics.max(), 0);
+  EXPECT_EQ(statistics.GetMedian(), 0);
+}
+
+TEST(StatisticsTest, SingleSample) {
+  Statistics<int, int, 1> statistics;
+
+  statistics.AddSample(100, 10);
+
+  EXPECT_EQ(statistics.accumulated_dividend(), 100);
+  EXPECT_EQ(statistics.accumulated_divisor(), 10);
+  EXPECT_EQ(statistics.average(), 10);
+  EXPECT_EQ(statistics.min(), 10);
+  EXPECT_EQ(statistics.max(), 10);
+  EXPECT_EQ(statistics.GetMedian(), 10);
+}
+
+TEST(StatisticsTest, NotCrashOnZeroDivisor) {
+  Statistics<int, int, 4> statistics;
+
+  statistics.AddSample(100, 0);
+
+  EXPECT_EQ(statistics.accumulated_dividend(), 100);
+  EXPECT_EQ(statistics.accumulated_divisor(), 0);
+  // The only expectation for the following calls is that they don't crash.
+  statistics.average();
+  statistics.min();
+  statistics.max();
+  statistics.GetMedian();
+}
+
+TEST(StatisticsTest, MultipleSamples) {
+  Statistics<int, int, 4> statistics;
+
+  statistics.AddSample(10, 2);
+  statistics.AddSample(20, 2);
+  statistics.AddSample(30, 2);
+  EXPECT_EQ(statistics.accumulated_dividend(), 10 + 20 + 30);
+  EXPECT_EQ(statistics.accumulated_divisor(), 6);
+  EXPECT_EQ(statistics.average(), (10 + 20 + 30) / 6);
+  EXPECT_EQ(statistics.min(), 10 / 2);
+  EXPECT_EQ(statistics.max(), 30 / 2);
+  EXPECT_EQ(statistics.GetMedian(), 20 / 2);
+
+  statistics.AddSample(40, 2);
+  EXPECT_EQ(statistics.accumulated_dividend(), 10 + 20 + 30 + 40);
+  EXPECT_EQ(statistics.accumulated_divisor(), 8);
+  EXPECT_EQ(statistics.average(), (10 + 20 + 30 + 40) / 8);
+  EXPECT_EQ(statistics.min(), 10 / 2);
+  EXPECT_EQ(statistics.max(), 40 / 2);
+  // The median can be in the range of either number close to the middle, and is
+  // implementation specific.
+  EXPECT_GE(statistics.GetMedian(), 30 / 2);
+  EXPECT_LE(statistics.GetMedian(), 40 / 2);
+}
+
+TEST(StatisticsTest, MultipleSamplesDifferentOrders) {
+  Statistics<int, int, 10> statistics;
+  Statistics<int, int, 10> statistics_reversed;
+
+  for (int i = 0; i < 10; ++i) {
+    statistics.AddSample(i * i, i * 3);
+  }
+
+  for (int i = 9; i >= 0; --i) {
+    statistics_reversed.AddSample(i * i, i * 3);
+  }
+
+  EXPECT_EQ(statistics.accumulated_dividend(),
+            statistics_reversed.accumulated_dividend());
+  EXPECT_EQ(statistics.accumulated_divisor(),
+            statistics_reversed.accumulated_divisor());
+  EXPECT_EQ(statistics.average(), statistics_reversed.average());
+  EXPECT_EQ(statistics.min(), statistics_reversed.min());
+  EXPECT_EQ(statistics.max(), statistics_reversed.max());
+  EXPECT_EQ(statistics.GetMedian(), statistics_reversed.GetMedian());
+}
+
+TEST(StatisticsTest, MoreSamplesThanCapacity) {
+  Statistics<int, int, 1> statistics_1;
+  Statistics<int, int, 10> statistics_10;
+
+  for (int i = 0; i < 5; ++i) {
+    statistics_1.AddSample(i * i, i * 3);
+    statistics_10.AddSample(i * i, i * 3);
+  }
+
+  Statistics<int, int, 1> statistics_1_median_reference;
+  Statistics<int, int, 10> statistics_10_median_reference;
+
+  for (int i = 0; i < 10; ++i) {
+    statistics_1.AddSample(i * i * i, i * 5);
+    statistics_10.AddSample(i * i * i, i * 5);
+    statistics_1_median_reference.AddSample(i * i * i, i * 5);
+    statistics_10_median_reference.AddSample(i * i * i, i * 5);
+  }
+
+  // All statistics except median are tracked over all samples added, median is
+  // tracked on the samples cached, so:
+  // 1. All statistics except median are the same over `statistics_1` and
+  // `statistics_10`.
+  // 2. Medians of `statistics_1` and `statistics_1_median_reference` are the
+  //    same, and the same to `statistics_10` and
+  //    `statistics_10_median_reference`.
+  EXPECT_EQ(statistics_1.accumulated_dividend(),
+            statistics_10.accumulated_dividend());
+  EXPECT_EQ(statistics_1.accumulated_divisor(),
+            statistics_10.accumulated_divisor());
+  EXPECT_EQ(statistics_1.average(), statistics_10.average());
+  EXPECT_EQ(statistics_1.min(), statistics_10.min());
+  EXPECT_EQ(statistics_1.max(), statistics_10.max());
+
+  EXPECT_EQ(statistics_1.GetMedian(),
+            statistics_1_median_reference.GetMedian());
+  EXPECT_EQ(statistics_10.GetMedian(),
+            statistics_10_median_reference.GetMedian());
+}
+
+TEST(StatisticsTest, MedianWithOverflow) {
+  Statistics<int, int, 3> statistics;
+
+  statistics.AddSample(1, 1);
+  statistics.AddSample(11, 1);
+  statistics.AddSample(21, 1);
+  EXPECT_EQ(statistics.GetMedian(), 11);
+
+  // Removed 1, with {11, 21, 16} cached.
+  statistics.AddSample(16, 1);
+  EXPECT_EQ(statistics.GetMedian(), 16);
+
+  // Removed 11, with {21, 16, 26} cached.
+  statistics.AddSample(26, 1);
+  EXPECT_EQ(statistics.GetMedian(), 21);
+}
+
+}  // namespace
+}  // namespace base
diff --git a/cobalt/black_box_tests/testdata/service_worker_test.html b/cobalt/black_box_tests/testdata/service_worker_test.html
index 49ef9ad..7450faf 100644
--- a/cobalt/black_box_tests/testdata/service_worker_test.html
+++ b/cobalt/black_box_tests/testdata/service_worker_test.html
@@ -39,6 +39,36 @@
         }
 
         function test_claimable_worker() {
+            console.log('Adding ready promise.');
+            navigator.serviceWorker.ready.then(function (
+                registration) {
+                assertNotEqual(null, registration);
+                console.log('(Expected) Registration ready promise',
+                    registration, ' with active worker ',
+                    registration.active);
+                assertNotEqual(null, registration.active);
+                registration.active.postMessage(
+                    'Registration ready received for claimable worker.');
+                count_event();
+                registration.unregister()
+                    .then(function (success) {
+                        // Even a claimed registration will successfully
+                        // unregister because unregistration takes effect after
+                        // the page is unloaded, so it's not blocked by being
+                        // the active service worker.
+                        console.log('(Expected) unregister success :',
+                            success);
+                        count_event(41);
+                    }, function (error) {
+                        console.log('(Unexpected) unregister ' +
+                            `${error}`, error);
+                        assertIncludes('SecurityError: ', `${error}`);
+                        notReached();
+                    });
+                console.log('unregister started.');
+
+            });
+
             navigator.serviceWorker.register('service_worker_test_claimable.js', {
                 scope: './',
             }).then(function (registration) {
@@ -63,25 +93,6 @@
                         'Registration successful, current worker state is ' +
                         'active.');
                 }
-
-                registration.unregister()
-                    .then(function (success) {
-                        // Even a claimed registration will successfully
-                        // unregister because unregistration takes effect after
-                        // the page is unloaded, so it's not blocked by being
-                        // the active service worker.
-                        console.log('(Expected) unregister success :',
-                            success);
-                        count_event(41);
-                    }, function (error) {
-                        console.log('(Unexpected) unregister ' +
-                            `${error}`, error);
-                        assertIncludes('SecurityError: ', `${error}`);
-                        notReached();
-                    });
-                console.log('unregister started.');
-
-
              }, function (error) {
                 console.log('(Unexpected) :', error);
                 notReached();
@@ -253,22 +264,11 @@
                     }
 
                     // Check that the registration has an activated worker after
-                    // some time.
+                    // some time. The delay used here should be long enough for
+                    // the service worker to complete activating and have the
+                    // state 'activated'. It has to be longer than the combined
+                    // delays in the install or activate event handlers.
                     window.setTimeout(function () {
-                        // TODO(b/234659851): Investigate whether this should
-                        // resolve or not, in this case where there already is
-                        // an active worker.
-                        navigator.serviceWorker.ready.then(function (
-                            registration) {
-                            assertNotEqual(null, registration);
-                            console.log('(Expected) Registration ready promise',
-                                registration, ' with active worker ',
-                                registration.active);
-                            assertNotEqual(null, registration.active);
-                            registration.active.postMessage(
-                                'Registration ready received after waiting.');
-                            count_event();
-                        });
                         // Since these events are asynchronous, the service
                         // worker can be either of these states.
                         console.log(
@@ -319,6 +319,7 @@
                                         console.log('(Unexpected) :', error);
                                         notReached();
                                     });
+
                                 test_claimable_worker();
                             }, function (error) {
                                 console.log('(Unexpected) unregister ' +
@@ -326,7 +327,7 @@
                                 assertIncludes('SecurityError: ', `${error}`);
                                 notReached();
                             });
-                    }, 500);
+                    }, 1000);
 
                     // Test getRegistration for a non-registered scope.
                     navigator.serviceWorker.getRegistration('/bo/gus')
@@ -411,12 +412,14 @@
 
         console.log('Done starting tests');
         setupFinished();
+        // This delay has to be long enough to guarantee that the test has
+        // finished.
         window.setTimeout(
             () => {
                 console.log('Events:', expected_event_count)
                 assertEqual(41, expected_event_count);
                 onEndTest();
-            }, 3000);
+            }, 7000);
 
     </script>
 </body>
diff --git a/cobalt/black_box_tests/testdata/service_worker_test.js b/cobalt/black_box_tests/testdata/service_worker_test.js
index 5b9e117..4b985a2 100644
--- a/cobalt/black_box_tests/testdata/service_worker_test.js
+++ b/cobalt/black_box_tests/testdata/service_worker_test.js
@@ -31,11 +31,11 @@
   console.log('oninstall event received', e);
 
   console.log('self.clients.claim()');
-  self.clients.claim().then(function (clients) {
+  e.waitUntil(self.clients.claim().then(function (clients) {
     console.log('(Unexpected) self.clients.claim():', clients);
   }, function (error) {
     console.log(`(Expected) self.clients.claim() not yet activated: ${error}`, error);
-  });
+  }));
 
 }
 self.onactivate = function (e) {
@@ -43,14 +43,14 @@
 
   // Claim should pass here, since the state is activating.
   console.log('self.clients.claim()');
-  self.clients.claim().then(function (clients) {
+  e.waitUntil(self.clients.claim().then(function (clients) {
     console.log('(Expected) self.clients.claim():', clients);
 
     var options = {
       includeUncontrolled: false, type: 'window'
     };
     console.log('self.clients.matchAll(options)');
-    self.clients.matchAll(options).then(function (clients) {
+    e.waitUntil(self.clients.matchAll(options).then(function (clients) {
       console.log('(Expected) self.clients.matchAll():', clients.length, clients);
       for (var i = 0; i < clients.length; i++) {
         console.log('Client with url', clients[i].url,
@@ -60,11 +60,11 @@
       }
     }, function (error) {
       console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error);
-    });
+    }));
 
   }, function (error) {
     console.log(`(Unexpected) self.clients.claim(): ${error}`, error);
-  });
+  }));
 
 }
 console.log('self.registration', self.registration);
diff --git a/cobalt/black_box_tests/testdata/service_worker_test_claimable.js b/cobalt/black_box_tests/testdata/service_worker_test_claimable.js
index 21cb0d7..191c964 100644
--- a/cobalt/black_box_tests/testdata/service_worker_test_claimable.js
+++ b/cobalt/black_box_tests/testdata/service_worker_test_claimable.js
@@ -35,7 +35,9 @@
 
 self.oninstall = function (e) {
   console.log('oninstall event received', e);
-  e.waitUntil(delay_promise(500).then(() => console.log('Promised delay.'), () => console.log('\nPromised rejected.\n')));
+  // Using a delay long enough to make it clearly visible in the log that the
+  // event is extended, and is delaying the activate event and ready promise.
+  e.waitUntil(delay_promise(1000).then(() => console.log('Promised delay.'), () => console.log('\nPromised rejected.\n')));
 }
 
 self.onactivate = function (e) {
@@ -49,6 +51,8 @@
     var options = {
       includeUncontrolled: false, type: 'window'
     };
+  // Using a delay long enough to make it clearly visible in the log that the
+  // event is extended.
     e.waitUntil(delay_promise(1000).then(function () {
       console.log('self.clients.matchAll(options)');
       e.waitUntil(self.clients.matchAll(options).then(function (clients) {
diff --git a/cobalt/browser/application.cc b/cobalt/browser/application.cc
index 5fa7318..b6a691f 100644
--- a/cobalt/browser/application.cc
+++ b/cobalt/browser/application.cc
@@ -645,6 +645,7 @@
       std::make_unique<persistent_storage::PersistentSettings>(
           kPersistentSettingsJson, message_loop_->task_runner());
 
+  // Initializes Watchdog.
   watchdog::Watchdog* watchdog =
       watchdog::Watchdog::CreateInstance(persistent_settings_.get());
   DCHECK(watchdog);
diff --git a/cobalt/browser/user_agent_platform_info.cc b/cobalt/browser/user_agent_platform_info.cc
index 4c334e9..fe9f3e1 100644
--- a/cobalt/browser/user_agent_platform_info.cc
+++ b/cobalt/browser/user_agent_platform_info.cc
@@ -250,9 +250,12 @@
   info.set_rasterizer_type(
       renderer::GetDefaultRasterizerForPlatform().rasterizer_name);
 
-// Evergreen version
+// Evergreen info
 #if SB_IS(EVERGREEN)
-  info.set_evergreen_version(updater::GetCurrentEvergreenVersion());
+  updater::EvergreenLibraryMetadata evergreen_library_metadata =
+      updater::GetCurrentEvergreenLibraryMetadata();
+  info.set_evergreen_version(evergreen_library_metadata.version);
+  info.set_evergreen_file_type(evergreen_library_metadata.file_type);
   if (!SbSystemGetExtension(kCobaltExtensionInstallationManagerName)) {
     // If the installation manager is not initialized, the "evergreen_lite"
     // command line parameter is specified and the system image is loaded.
@@ -380,6 +383,9 @@
         } else if (!input.first.compare("evergreen_type")) {
           info.set_evergreen_type(input.second);
           LOG(INFO) << "Set evergreen type to " << input.second;
+        } else if (!input.first.compare("evergreen_file_type")) {
+          info.set_evergreen_file_type(input.second);
+          LOG(INFO) << "Set evergreen file type to " << input.second;
         } else if (!input.first.compare("evergreen_version")) {
           info.set_evergreen_version(input.second);
           LOG(INFO) << "Set evergreen version to " << input.second;
@@ -481,6 +487,11 @@
   evergreen_type_ = Sanitize(evergreen_type, isTCHARorForwardSlash);
 }
 
+void UserAgentPlatformInfo::set_evergreen_file_type(
+    const std::string& evergreen_file_type) {
+  evergreen_file_type_ = Sanitize(evergreen_file_type, isTCHARorForwardSlash);
+}
+
 void UserAgentPlatformInfo::set_evergreen_version(
     const std::string& evergreen_version) {
   evergreen_version_ = Sanitize(evergreen_version, isTCHAR);
diff --git a/cobalt/browser/user_agent_platform_info.h b/cobalt/browser/user_agent_platform_info.h
index 1f379f6..d71c7fe 100644
--- a/cobalt/browser/user_agent_platform_info.h
+++ b/cobalt/browser/user_agent_platform_info.h
@@ -66,6 +66,9 @@
     return rasterizer_type_;
   }
   const std::string& evergreen_type() const override { return evergreen_type_; }
+  const std::string& evergreen_file_type() const override {
+    return evergreen_file_type_;
+  }
   const std::string& evergreen_version() const override {
     return evergreen_version_;
   }
@@ -95,6 +98,7 @@
       const std::string& javascript_engine_version);
   void set_rasterizer_type(const std::string& rasterizer_type);
   void set_evergreen_type(const std::string& evergreen_type);
+  void set_evergreen_file_type(const std::string& evergreen_file_type);
   void set_evergreen_version(const std::string& evergreen_version);
   void set_cobalt_version(const std::string& cobalt_version);
   void set_cobalt_build_version_number(
@@ -116,6 +120,7 @@
   std::string javascript_engine_version_;
   std::string rasterizer_type_;
   std::string evergreen_type_;
+  std::string evergreen_file_type_;
   std::string evergreen_version_;
 
   std::string cobalt_version_;
diff --git a/cobalt/browser/user_agent_string.cc b/cobalt/browser/user_agent_string.cc
index 3b36311..bd41231 100644
--- a/cobalt/browser/user_agent_string.cc
+++ b/cobalt/browser/user_agent_string.cc
@@ -46,6 +46,11 @@
   //   JavaScript Engine Name/Version
   //   Starboard/APIVersion,
   //   Device/FirmwareVersion (Brand, Model, ConnectionType)
+  //
+  // In the case of Evergreen, it contains three additional sections:
+  //   Evergreen/Version
+  //   Evergreen-Type
+  //   Evergreen-FileType
 
   //   Mozilla/5.0 (ChromiumStylePlatform)
   std::string user_agent = base::StringPrintf(
@@ -74,12 +79,19 @@
     base::StringAppendF(&user_agent, " Evergreen/%s",
                         platform_info.evergreen_version().c_str());
   }
+
   // Evergreen type
   if (!platform_info.evergreen_type().empty()) {
     base::StringAppendF(&user_agent, " Evergreen-%s",
                         platform_info.evergreen_type().c_str());
   }
 
+  // Evergreen file type
+  if (!platform_info.evergreen_file_type().empty()) {
+    base::StringAppendF(&user_agent, " Evergreen-%s",
+                        platform_info.evergreen_file_type().c_str());
+  }
+
   // Starboard/APIVersion,
   if (!platform_info.starboard_version().empty()) {
     base::StringAppendF(&user_agent, " %s",
diff --git a/cobalt/browser/user_agent_string_test.cc b/cobalt/browser/user_agent_string_test.cc
index 9ce2eb4..27e232b 100644
--- a/cobalt/browser/user_agent_string_test.cc
+++ b/cobalt/browser/user_agent_string_test.cc
@@ -38,6 +38,8 @@
   platform_info.set_javascript_engine_version("");
   platform_info.set_rasterizer_type("");
   platform_info.set_evergreen_version("");
+  platform_info.set_evergreen_type("");
+  platform_info.set_evergreen_file_type("");
   platform_info.set_cobalt_version("");
   platform_info.set_cobalt_build_version_number("");
   platform_info.set_build_configuration("");
@@ -242,7 +244,7 @@
             user_agent_string.find("FooBar" TCHARORSLASH "BazQux"));
 }
 
-TEST(UserAgentStringFactoryTest, SanitizedEvergreenType) {
+TEST(UserAgentStringFactoryTest, SanitizedEvergreenVersion) {
   UserAgentPlatformInfo platform_info =
       CreateOnlyOSNameAndVersionPlatformInfo();
   platform_info.set_evergreen_version("Foo" NOT_TCHAR "Bar" TCHAR
@@ -251,6 +253,26 @@
   EXPECT_NE(std::string::npos, user_agent_string.find("FooBar" TCHAR "BazQux"));
 }
 
+TEST(UserAgentStringFactoryTest, SanitizedEvergreenType) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_evergreen_type("Foo" NOT_TCHARORSLASH "Bar" TCHARORSLASH
+                                   "Baz" NOT_TCHARORSLASH "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos,
+            user_agent_string.find("FooBar" TCHARORSLASH "BazQux"));
+}
+
+TEST(UserAgentStringFactoryTest, SanitizedEvergreenFileType) {
+  UserAgentPlatformInfo platform_info =
+      CreateOnlyOSNameAndVersionPlatformInfo();
+  platform_info.set_evergreen_file_type(
+      "Foo" NOT_TCHARORSLASH "Bar" TCHARORSLASH "Baz" NOT_TCHARORSLASH "Qux");
+  std::string user_agent_string = CreateUserAgentString(platform_info);
+  EXPECT_NE(std::string::npos,
+            user_agent_string.find("FooBar" TCHARORSLASH "BazQux"));
+}
+
 TEST(UserAgentStringFactoryTest, SanitizedCobaltVersion) {
   UserAgentPlatformInfo platform_info =
       CreateOnlyOSNameAndVersionPlatformInfo();
diff --git a/cobalt/demos/content/watchdog-demo/index.html b/cobalt/demos/content/watchdog-demo/index.html
index 4114648..0213470 100644
--- a/cobalt/demos/content/watchdog-demo/index.html
+++ b/cobalt/demos/content/watchdog-demo/index.html
@@ -23,6 +23,9 @@
       unregister: 'Unregister',
       ping: 'Ping',
       getWatchdogViolations: 'Get Watchdog Violations',
+      getPersistentSettingWatchdogEnable: 'Get Enable Watchdog',
+      setPersistentSettingWatchdogEnableTrue: 'Set Enable Watchdog True',
+      setPersistentSettingWatchdogEnableFalse: 'Set Enable Watchdog False',
       getPersistentSettingWatchdogCrash: 'Get Can Trigger Crash',
       setPersistentSettingWatchdogCrashTrue: 'Set Can Trigger Crash True',
       setPersistentSettingWatchdogCrashFalse: 'Set Can Trigger Crash False',
@@ -63,6 +66,12 @@
             ret = h5vcc.crashLog.ping('test-name', `test-ping`);
           } else if (watchdogFunction == 'getWatchdogViolations') {
             ret = h5vcc.crashLog.getWatchdogViolations();
+          } else if (watchdogFunction == 'getPersistentSettingWatchdogEnable') {
+            ret = h5vcc.crashLog.getPersistentSettingWatchdogEnable();
+          } else if (watchdogFunction == 'setPersistentSettingWatchdogEnableTrue') {
+            h5vcc.crashLog.setPersistentSettingWatchdogEnable(true);
+          } else if (watchdogFunction == 'setPersistentSettingWatchdogEnableFalse') {
+            h5vcc.crashLog.setPersistentSettingWatchdogEnable(false);
           } else if (watchdogFunction == 'getPersistentSettingWatchdogCrash') {
             ret = h5vcc.crashLog.getPersistentSettingWatchdogCrash();
           } else if (watchdogFunction == 'setPersistentSettingWatchdogCrashTrue') {
diff --git a/cobalt/dom/media_source.cc b/cobalt/dom/media_source.cc
index 255da43..290996f 100644
--- a/cobalt/dom/media_source.cc
+++ b/cobalt/dom/media_source.cc
@@ -486,6 +486,7 @@
 }
 
 bool MediaSource::MediaElementHasMaxVideoCapabilities() const {
+  SB_DCHECK(attached_element_);
   return has_max_video_capabilities_;
 }
 
diff --git a/cobalt/dom/source_buffer.cc b/cobalt/dom/source_buffer.cc
index 806a970..a8b78e9 100644
--- a/cobalt/dom/source_buffer.cc
+++ b/cobalt/dom/source_buffer.cc
@@ -108,7 +108,8 @@
       audio_tracks_(
           new AudioTrackList(settings, media_source->GetMediaElement())),
       video_tracks_(
-          new VideoTrackList(settings, media_source->GetMediaElement())) {
+          new VideoTrackList(settings, media_source->GetMediaElement())),
+      metrics_(!media_source_->MediaElementHasMaxVideoCapabilities()) {
   DCHECK(!id_.empty());
   DCHECK(media_source_);
   DCHECK(chunk_demuxer);
@@ -367,13 +368,16 @@
   //   RemoveMediaTracks();
   // }
 
-  if (!media_source_->MediaElementHasMaxVideoCapabilities()) {
-    // TODO: Determine if the source buffer contains an audio or video stream,
-    // and print the steam type along with the metrics.
-    metrics_.PrintMetrics();
-  }
+  // TODO: Determine if the source buffer contains an audio or video stream,
+  //       maybe by implementing track support and get from the type of the
+  //       track, and print the steam type along with the metrics.
+  metrics_.PrintCurrentMetricsAndUpdateAccumulatedMetrics();
 
   chunk_demuxer_->RemoveId(id_);
+  if (chunk_demuxer_->GetAllStreams().empty()) {
+    metrics_.PrintAccumulatedMetrics();
+  }
+
   chunk_demuxer_ = NULL;
   media_source_ = NULL;
   event_queue_ = NULL;
diff --git a/cobalt/dom/source_buffer_metrics.cc b/cobalt/dom/source_buffer_metrics.cc
index 1fbf9b6..f2ae570 100644
--- a/cobalt/dom/source_buffer_metrics.cc
+++ b/cobalt/dom/source_buffer_metrics.cc
@@ -17,7 +17,9 @@
 #include <algorithm>
 
 #include "base/logging.h"
+#include "cobalt/base/statistics.h"
 #include "starboard/common/string.h"
+#include "starboard/types.h"
 
 namespace cobalt {
 namespace dom {
@@ -30,9 +32,30 @@
   return duration == 0 ? 0 : size * kSbTimeSecond / duration;
 }
 
+int64_t GetBandwidthForStatistics(int64_t size, int64_t duration) {
+  return GetBandwidth(static_cast<std::size_t>(size), duration);
+}
+
+using BandwidthStatistics =
+    base::Statistics<int64_t, SbTimeMonotonic, 1024, GetBandwidthForStatistics>;
+
+BandwidthStatistics s_accumulated_wall_time_bandwidth_;
+BandwidthStatistics s_accumulated_thread_time_bandwidth_;
+
+double GetWallToThreadTimeRatio(int64_t wall_time, int64_t thread_time) {
+  if (thread_time == 0) {
+    thread_time = 1;
+  }
+  return static_cast<double>(wall_time) / thread_time;
+}
+
 }  // namespace
 
 void SourceBufferMetrics::StartTracking() {
+  if (!is_primary_video_) {
+    return;
+  }
+
   DCHECK(!is_tracking_);
   is_tracking_ = true;
   wall_start_time_ = SbTimeGetMonotonicNow();
@@ -41,6 +64,10 @@
 }
 
 void SourceBufferMetrics::EndTracking(std::size_t size_appended) {
+  if (!is_primary_video_) {
+    return;
+  }
+
   DCHECK(is_tracking_);
   is_tracking_ = false;
 
@@ -67,24 +94,62 @@
   }
 }
 
-void SourceBufferMetrics::PrintMetrics() {
+void SourceBufferMetrics::PrintCurrentMetricsAndUpdateAccumulatedMetrics() {
+  if (!is_primary_video_) {
+    return;
+  }
+
+  s_accumulated_wall_time_bandwidth_.AddSample(total_size_, total_wall_time_);
+  s_accumulated_thread_time_bandwidth_.AddSample(total_size_,
+                                                 total_thread_time_);
+
   LOG_IF(INFO, total_thread_time_ > total_wall_time_)
       << "Total thread time " << total_thread_time_
       << " should not be greater than total wall time " << total_wall_time_
       << ".";
+
+  // clang-format off
   LOG(INFO) << starboard::FormatString(
-      "AppendBuffer() metrics:\n\t%-30s%zu B\n\t%-30s%d "
-      "us (%d B/s)\n\t\t%-28s%d "
-      "B/s\n\t\t%-28s%d B/s\n\t%-30s%d us (%d B/s)\n\t\t%-28s%d "
-      "B/s\n\t\t%-28s%d B/s",
-      "Total size of appended data:", total_size_, "Total append wall time",
+      "AppendBuffer() metrics:\n"
+      "    Total size of appended data: %zu B, wall time / thread time = %02g\n"
+      "    Total append wall time:      %" PRId64 " us (%d B/s)\n"
+      "        Max wall bandwidth:      %d B/s\n"
+      "        Min wall bandwidth:      %d B/s\n"
+      "    Total append thread time:    %" PRId64 " us (%d B/s)\n"
+      "        Max thread bandwidth:    %d B/s\n"
+      "        Min thread bandwidth:    %d B/s\n",
+      total_size_,
+      GetWallToThreadTimeRatio(total_wall_time_, total_thread_time_),
       total_wall_time_, GetBandwidth(total_size_, total_wall_time_),
-      "Max wall bandwidth:", max_wall_bandwidth_,
-      "Min wall bandwidth:", min_wall_bandwidth_,
-      "Total append thread time:", total_thread_time_,
-      GetBandwidth(total_size_, total_thread_time_),
-      "Max thread bandwidth:", max_thread_bandwidth_,
-      "Min thread bandwidth:", min_thread_bandwidth_);
+      max_wall_bandwidth_, min_wall_bandwidth_, total_thread_time_,
+      GetBandwidth(total_size_, total_thread_time_), max_thread_bandwidth_,
+      min_thread_bandwidth_);
+  // clang-format on
+}
+
+void SourceBufferMetrics::PrintAccumulatedMetrics() {
+  if (!is_primary_video_) {
+    return;
+  }
+
+  LOG(INFO) << starboard::FormatString(
+      "Accumulated AppendBuffer() metrics:\n"
+      "    wall time / thread time = %02g\n"
+      "    wall bandwidth statistics (B/s):\n"
+      "        min %d, median %d, average %d, max %d\n"
+      "    thread bandwidth statistics (B/s):\n"
+      "        min %d, median %d, average %d, max %d",
+      GetWallToThreadTimeRatio(
+          s_accumulated_wall_time_bandwidth_.accumulated_divisor(),
+          s_accumulated_thread_time_bandwidth_.accumulated_divisor()),
+      static_cast<int>(s_accumulated_wall_time_bandwidth_.min()),
+      static_cast<int>(s_accumulated_wall_time_bandwidth_.GetMedian()),
+      static_cast<int>(s_accumulated_wall_time_bandwidth_.average()),
+      static_cast<int>(s_accumulated_wall_time_bandwidth_.max()),
+      static_cast<int>(s_accumulated_thread_time_bandwidth_.min()),
+      static_cast<int>(s_accumulated_thread_time_bandwidth_.GetMedian()),
+      static_cast<int>(s_accumulated_thread_time_bandwidth_.average()),
+      static_cast<int>(s_accumulated_thread_time_bandwidth_.max()));
 }
 
 #endif  // !defined(COBALT_BUILD_TYPE_GOLD)
diff --git a/cobalt/dom/source_buffer_metrics.h b/cobalt/dom/source_buffer_metrics.h
index 67001fe..766ebac 100644
--- a/cobalt/dom/source_buffer_metrics.h
+++ b/cobalt/dom/source_buffer_metrics.h
@@ -24,34 +24,41 @@
 
 class SourceBufferMetrics {
  public:
-  SourceBufferMetrics() = default;
+  explicit SourceBufferMetrics(bool is_primary_video) {}
   ~SourceBufferMetrics() = default;
 
   void StartTracking() {}
   void EndTracking(size_t size_appended) {}
-  void PrintMetrics() {}
+  void PrintCurrentMetricsAndUpdateAccumulatedMetrics() {}
+  void PrintAccumulatedMetrics() {}
 };
 
 #else  // defined(COBALT_BUILD_TYPE_GOLD)
 
 class SourceBufferMetrics {
  public:
-  SourceBufferMetrics() = default;
+  explicit SourceBufferMetrics(bool is_primary_video)
+      : is_primary_video_(is_primary_video) {}
   ~SourceBufferMetrics() = default;
 
   void StartTracking();
   void EndTracking(size_t size_appended);
-  void PrintMetrics();
+  void PrintCurrentMetricsAndUpdateAccumulatedMetrics();
+  void PrintAccumulatedMetrics();
 
  private:
+  SourceBufferMetrics(const SourceBufferMetrics&) = delete;
+  SourceBufferMetrics& operator=(const SourceBufferMetrics&) = delete;
+
   SbTimeMonotonic wall_start_time_ = 0;
   SbTimeMonotonic thread_start_time_ = 0;
 
+  const bool is_primary_video_;
   bool is_tracking_ = false;
 
   size_t total_size_ = 0;
-  int total_thread_time_ = 0;
-  int total_wall_time_ = 0;
+  SbTime total_thread_time_ = 0;
+  SbTime total_wall_time_ = 0;
   int max_thread_bandwidth_ = 0;
   int min_thread_bandwidth_ = INT_MAX;
   int max_wall_bandwidth_ = 0;
diff --git a/cobalt/h5vcc/h5vcc_crash_log.cc b/cobalt/h5vcc/h5vcc_crash_log.cc
index d61dca0..7a502b4 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.cc
+++ b/cobalt/h5vcc/h5vcc_crash_log.cc
@@ -186,6 +186,17 @@
   return "";
 }
 
+bool H5vccCrashLog::GetPersistentSettingWatchdogEnable() {
+  watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
+  if (watchdog) return watchdog->GetPersistentSettingWatchdogEnable();
+  return true;
+}
+
+void H5vccCrashLog::SetPersistentSettingWatchdogEnable(bool enable_watchdog) {
+  watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
+  if (watchdog) watchdog->SetPersistentSettingWatchdogEnable(enable_watchdog);
+}
+
 bool H5vccCrashLog::GetPersistentSettingWatchdogCrash() {
   watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
   if (watchdog) return watchdog->GetPersistentSettingWatchdogCrash();
diff --git a/cobalt/h5vcc/h5vcc_crash_log.h b/cobalt/h5vcc/h5vcc_crash_log.h
index 5b1557b..60a51b1 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.h
+++ b/cobalt/h5vcc/h5vcc_crash_log.h
@@ -44,6 +44,10 @@
 
   std::string GetWatchdogViolations();
 
+  bool GetPersistentSettingWatchdogEnable();
+
+  void SetPersistentSettingWatchdogEnable(bool enable_watchdog);
+
   bool GetPersistentSettingWatchdogCrash();
 
   void SetPersistentSettingWatchdogCrash(bool can_trigger_crash);
diff --git a/cobalt/h5vcc/h5vcc_crash_log.idl b/cobalt/h5vcc/h5vcc_crash_log.idl
index 5c91aa0..f551cd7 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.idl
+++ b/cobalt/h5vcc/h5vcc_crash_log.idl
@@ -32,7 +32,7 @@
   // Returns true if Watchdog client was registered.
   //   name, Watchdog client to register.
   //   description, information on the Watchdog client.
-  //   monitor_state, application state up to which the client is monitored.
+  //   monitor_state, application state to continue monitoring client up to.
   //     Inclusive.
   //   time_interval, maximum number of microseconds allowed between pings
   //     before triggering a Watchdog violation.
@@ -56,8 +56,47 @@
 
   // Returns a json string containing the Watchdog violations since the last
   // call. Clears internal cache of Watchdog violations to prevent duplicates.
+  // Timestamps are stored as strings due to int size constraints.
+  // Example json:
+  // {
+  //   "test-name":{
+  //     "description":"test-description",
+  //     "violations":[
+  //       {
+  //         "monitorState":"kApplicationStateStarted",
+  //         "pingInfos":[
+  //           {
+  //             "info":"test-ping",
+  //             "timestampMicroseconds":"1658972623547006"
+  //           }
+  //         ],
+  //         "registeredClients":[
+  //           "test-name"
+  //         ],
+  //         "timeIntervalMicroseconds":"5000000",
+  //         "timeWaitMicroseconds":"0",
+  //         "timestampLastPingedMicroseconds":"1658972623547006",
+  //         "timestampRegisteredMicroseconds":"1658972621890834",
+  //         "timestampViolationMicroseconds":"1658972629489771",
+  //         "violationDurationMicroseconds":"942764"
+  //       }
+  //     ]
+  //   }
+  // }
   DOMString getWatchdogViolations();
 
+  // Gets a persistent Watchdog setting that determines whether or not Watchdog
+  // is enabled. When disabled, Watchdog behaves like a stub except that
+  // persistent settings can still be get/set. Requires a restart to take
+  // effect.
+  boolean getPersistentSettingWatchdogEnable();
+
+  // Sets a persistent Watchdog setting that determines whether or not Watchdog
+  // is enabled. When disabled, Watchdog behaves like a stub except that
+  // persistent settings can still be get/set. Requires a restart to take
+  // effect.
+  void setPersistentSettingWatchdogEnable(boolean enable_watchdog);
+
   // Gets a persistent Watchdog setting that determines whether or not a
   // Watchdog violation will trigger a crash.
   boolean getPersistentSettingWatchdogCrash();
diff --git a/cobalt/media/base/starboard_player.cc b/cobalt/media/base/starboard_player.cc
index 8aeb271..b6e50b3 100644
--- a/cobalt/media/base/starboard_player.cc
+++ b/cobalt/media/base/starboard_player.cc
@@ -24,6 +24,7 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/trace_event/trace_event.h"
+#include "cobalt/base/statistics.h"
 #include "cobalt/media/base/format_support_query_metrics.h"
 #include "starboard/common/media.h"
 #include "starboard/common/string.h"
@@ -34,6 +35,12 @@
 namespace cobalt {
 namespace media {
 
+namespace {
+
+base::Statistics<SbTime, int, 1024> s_player_presenting_delays_;
+
+}  // namespace
+
 StarboardPlayer::CallbackHelper::CallbackHelper(StarboardPlayer* player)
     : player_(player) {}
 
@@ -977,16 +984,16 @@
   std::string first_events_str;
   if (set_drm_system_ready_cb_time_ == -1) {
     first_events_str =
-        starboard::FormatString("%-50s0 us", "SbPlayerCreate() called");
+        starboard::FormatString("%-40s0 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",
+        "%-40s0 us\n%-40s%" 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",
+        "%-40s0 us\n%-40s%" PRId64 " us", "SbPlayerCreate() called",
         "set_drm_system_ready_cb called",
         set_drm_system_ready_cb_time_ - player_creation_time_);
   }
@@ -1004,16 +1011,27 @@
       sb_player_state_presenting_time_ -
       std::max(first_audio_sample_time_, first_video_sample_time_);
 
+  s_player_presenting_delays_.AddSample(player_presenting_time_delta, 1);
+
+  // clang-format off
   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);
+      "\nSbPlayer startup latencies\n"
+      "  Event name                              time since last event\n"
+      "  %s\n"  // |first_events_str| populated above
+      "  kSbPlayerStateInitialized received      %" PRId64 " us\n"
+      "  kSbPlayerStatePrerolling received       %" PRId64 " us\n"
+      "  First media sample(s) written [a/v]     %" PRId64 "/%" PRId64 " us\n"
+      "  kSbPlayerStatePresenting received       %" PRId64 " us\n"
+      "  kSbPlayerStatePresenting delay statistics (us):\n"
+      "    min: %" PRId64 ", median: %" PRId64 ", average: %" PRId64
+      ", max: %" PRId64,
+      first_events_str.c_str(), player_initialization_time_delta,
+      player_preroll_time_delta, first_audio_sample_time_delta,
+      first_video_sample_time_delta, player_presenting_time_delta,
+      s_player_presenting_delays_.min(),
+      s_player_presenting_delays_.GetMedian(),
+      s_player_presenting_delays_.average(), s_player_presenting_delays_.max());
+  // clang-format on
 }
 
 }  // namespace media
diff --git a/cobalt/renderer/pipeline.cc b/cobalt/renderer/pipeline.cc
index 4815da9..6198549 100644
--- a/cobalt/renderer/pipeline.cc
+++ b/cobalt/renderer/pipeline.cc
@@ -71,7 +71,7 @@
 const char kWatchdogName[] = "renderer";
 // The watchdog time interval in microseconds allowed between pings before
 // triggering violations.
-const int64_t kWatchdogTimeInterval = 1000000;
+const int64_t kWatchdogTimeInterval = 2000000;
 // The watchdog time wait in microseconds to initially wait before triggering
 // violations.
 const int64_t kWatchdogTimeWait = 2000000;
diff --git a/cobalt/updater/BUILD.gn b/cobalt/updater/BUILD.gn
index 3dcfbc7..4026027 100644
--- a/cobalt/updater/BUILD.gn
+++ b/cobalt/updater/BUILD.gn
@@ -97,3 +97,19 @@
     "//starboard/loader_app:app_key",
   ]
 }
+
+target(gtest_target_type, "updater_test") {
+  testonly = true
+
+  sources = [
+    "//starboard/common/test_main.cc",
+    "utils_test.cc",
+  ]
+
+  deps = [
+    ":updater",
+    "//base",
+    "//components/update_client",
+    "//testing/gtest",
+  ]
+}
diff --git a/cobalt/updater/utils.cc b/cobalt/updater/utils.cc
index 6c9cb3e..d1678de 100644
--- a/cobalt/updater/utils.cc
+++ b/cobalt/updater/utils.cc
@@ -12,6 +12,7 @@
 #include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/path_service.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/values.h"
 #include "build/build_config.h"
@@ -20,6 +21,7 @@
 #include "crypto/secure_hash.h"
 #include "crypto/sha2.h"
 #include "starboard/configuration_constants.h"
+#include "starboard/file.h"
 #include "starboard/string.h"
 #include "starboard/system.h"
 
@@ -28,12 +30,16 @@
 namespace cobalt {
 namespace updater {
 namespace {
-// The default manifest version to assume when the actual manifest cannot be
-// parsed for any reason. This should not be used for installation manager
-// errors, or any other error unrelated to parsing the manifest.
-const char kDefaultManifestVersion[] = "1.0.0";
+// Path to compressed Cobalt library, relative to the installation directory.
+const char kCompressedLibraryPath[] = "lib/libcobalt.lz4";
+
+// Path to uncompressed Cobalt library, relative to the installation directory.
+const char kUncompressedLibraryPath[] = "lib/libcobalt.so";
+
 }  // namespace
 
+const char kDefaultManifestVersion[] = "1.0.0";
+
 bool CreateProductDirectory(base::FilePath* path) {
   if (!GetProductDirectoryPath(path)) {
     LOG(ERROR) << "Can't get product directory path";
@@ -89,19 +95,73 @@
   return base::Version();
 }
 
-const std::string GetLoadedInstallationEvergreenVersion() {
+const std::string GetEvergreenFileType(const std::string& installation_path) {
+  std::string compressed_library_path = base::StrCat(
+      {installation_path, kSbFileSepString, kCompressedLibraryPath});
+  std::string uncompressed_library_path = base::StrCat(
+      {installation_path, kSbFileSepString, kUncompressedLibraryPath});
+
+  if (SbFileExists(compressed_library_path.c_str())) {
+    return "Compressed";
+  } else if (SbFileExists(uncompressed_library_path.c_str())) {
+    return "Uncompressed";
+  } else {
+    LOG(ERROR) << "Failed to get Evergreen file type. Defaulting to "
+                  "FileTypeUnknown.";
+    return "FileTypeUnknown";
+  }
+}
+
+const base::FilePath GetLoadedInstallationPath() {
   std::vector<char> system_path_content_dir(kSbFileMaxPath);
   if (!SbSystemGetPath(kSbSystemPathContentDirectory,
                        system_path_content_dir.data(), kSbFileMaxPath)) {
     LOG(ERROR) << "Failed to get system path content directory";
-    return "";
+    return base::FilePath();
   }
-  // Get the parent directory of the system_path_content_dir, and read the
-  // manifest.json there
-  base::Version version = ReadEvergreenVersion(
-      base::FilePath(std::string(system_path_content_dir.begin(),
-                                 system_path_content_dir.end()))
-          .DirName());
+  // Since the Cobalt library has already been loaded,
+  // kSbSystemPathContentDirectory points to the content dir of the running
+  // library and the installation dir is therefore its parent.
+  return base::FilePath(std::string(system_path_content_dir.begin(),
+                                    system_path_content_dir.end()))
+      .DirName();
+}
+
+const base::FilePath FindInstallationPath() {
+  // TODO(b/233914266): consider using base::NoDestructor to give the
+  // installation path static duration once found.
+
+  auto installation_manager =
+      static_cast<const CobaltExtensionInstallationManagerApi*>(
+          SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
+  if (!installation_manager) {
+    LOG(ERROR) << "Failed to get installation manager extension, getting the "
+                  "installation path of the loaded library.";
+    return GetLoadedInstallationPath();
+  }
+  // Get the update version from the manifest file under the current
+  // installation path.
+  int index = installation_manager->GetCurrentInstallationIndex();
+  if (index == IM_EXT_ERROR) {
+    LOG(ERROR) << "Failed to get current installation index, getting the "
+                  "installation path of the loaded library.";
+    return GetLoadedInstallationPath();
+  }
+  std::vector<char> installation_path(kSbFileMaxPath);
+  if (installation_manager->GetInstallationPath(
+          index, installation_path.data(), kSbFileMaxPath) == IM_EXT_ERROR) {
+    LOG(ERROR) << "Failed to get installation path from the installation "
+                  "manager, getting the installation path of the loaded "
+                  "library.";
+    return GetLoadedInstallationPath();
+  }
+  return base::FilePath(
+      std::string(installation_path.begin(), installation_path.end()));
+}
+
+const std::string GetValidOrDefaultEvergreenVersion(
+    const base::FilePath installation_path) {
+  base::Version version = ReadEvergreenVersion(installation_path);
 
   if (!version.IsValid()) {
     LOG(ERROR) << "Failed to get the Evergreen version. Defaulting to "
@@ -112,39 +172,20 @@
 }
 
 const std::string GetCurrentEvergreenVersion() {
-  auto installation_manager =
-      static_cast<const CobaltExtensionInstallationManagerApi*>(
-          SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
-  if (!installation_manager) {
-    LOG(ERROR) << "Failed to get installation manager extension, getting "
-                  "the Evergreen version of the loaded installation.";
-    return GetLoadedInstallationEvergreenVersion();
-  }
-  // Get the update version from the manifest file under the current
-  // installation path.
-  int index = installation_manager->GetCurrentInstallationIndex();
-  if (index == IM_EXT_ERROR) {
-    LOG(ERROR) << "Failed to get current installation index, getting the "
-                  "Evergreen version of the currently loaded installation.";
-    return GetLoadedInstallationEvergreenVersion();
-  }
-  std::vector<char> installation_path(kSbFileMaxPath);
-  if (installation_manager->GetInstallationPath(
-          index, installation_path.data(), kSbFileMaxPath) == IM_EXT_ERROR) {
-    LOG(ERROR) << "Failed to get installation path, getting the Evergreen "
-                  "version of the currently loaded installation.";
-    return GetLoadedInstallationEvergreenVersion();
-  }
+  base::FilePath installation_path = FindInstallationPath();
+  return GetValidOrDefaultEvergreenVersion(installation_path);
+}
 
-  base::Version version = ReadEvergreenVersion(base::FilePath(
-      std::string(installation_path.begin(), installation_path.end())));
+EvergreenLibraryMetadata GetCurrentEvergreenLibraryMetadata() {
+  EvergreenLibraryMetadata evergreen_library_metadata;
+  base::FilePath installation_path = FindInstallationPath();
 
-  if (!version.IsValid()) {
-    LOG(ERROR) << "Failed to get the Evergreen version. Defaulting to "
-               << kDefaultManifestVersion << ".";
-    return std::string(kDefaultManifestVersion);
-  }
-  return version.GetString();
+  evergreen_library_metadata.version =
+      GetValidOrDefaultEvergreenVersion(installation_path);
+  evergreen_library_metadata.file_type =
+      GetEvergreenFileType(installation_path.value());
+
+  return evergreen_library_metadata;
 }
 
 std::string GetLibrarySha256(int index) {
diff --git a/cobalt/updater/utils.h b/cobalt/updater/utils.h
index a0b5008..2b7b1ba 100644
--- a/cobalt/updater/utils.h
+++ b/cobalt/updater/utils.h
@@ -16,6 +16,16 @@
 namespace cobalt {
 namespace updater {
 
+// The default manifest version to assume when the actual manifest cannot be
+// parsed for any reason. This should not be used for installation manager
+// errors, or any other error unrelated to parsing the manifest.
+extern const char kDefaultManifestVersion[];
+
+struct EvergreenLibraryMetadata {
+  std::string version;
+  std::string file_type;
+};
+
 // Create a directory where updater files or its data is stored.
 bool CreateProductDirectory(base::FilePath* path);
 
@@ -23,10 +33,13 @@
 // stored.
 bool GetProductDirectoryPath(base::FilePath* path);
 
+// Returns the Evergreen library metadata of the current installation.
+EvergreenLibraryMetadata GetCurrentEvergreenLibraryMetadata();
+
 // Returns the Evergreen version of the current installation.
 const std::string GetCurrentEvergreenVersion();
 
-// Read the Evergreen version of the installation dir.
+// Reads the Evergreen version of the installation dir.
 base::Version ReadEvergreenVersion(base::FilePath installation_dir);
 
 // Returns the hash of the libcobalt.so binary for the installation
diff --git a/cobalt/updater/utils_test.cc b/cobalt/updater/utils_test.cc
new file mode 100644
index 0000000..91fc329
--- /dev/null
+++ b/cobalt/updater/utils_test.cc
@@ -0,0 +1,229 @@
+// 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/updater/utils.h"
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/strings/strcat.h"
+#include "base/values.h"
+#include "starboard/common/file.h"
+#include "starboard/directory.h"
+#include "starboard/file.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace updater {
+namespace {
+
+const char kEvergreenManifestFilename[] = "manifest.json";
+const char kEvergreenLibDirname[] = "lib";
+
+class UtilsTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    temp_dir_path_.resize(kSbFileMaxPath);
+    ASSERT_TRUE(SbSystemGetPath(kSbSystemPathTempDirectory,
+                                temp_dir_path_.data(), temp_dir_path_.size()));
+  }
+
+  void TearDown() override {
+    ASSERT_TRUE(starboard::SbFileDeleteRecursive(temp_dir_path_.data(), true));
+  }
+
+  void CreateManifest(const char* content, const std::string& directory) {
+    std::string manifest_path =
+        base::StrCat({directory, kSbFileSepString, kEvergreenManifestFilename});
+    SbFile sb_file =
+        SbFileOpen(manifest_path.c_str(), kSbFileOpenAlways | kSbFileRead,
+                   nullptr, nullptr);
+    ASSERT_TRUE(SbFileIsValid(sb_file));
+    ASSERT_TRUE(SbFileClose(sb_file));
+
+    ASSERT_TRUE(
+        SbFileAtomicReplace(manifest_path.c_str(), content, strlen(content)));
+  }
+
+  void DeleteManifest(const std::string& directory) {
+    std::string manifest_path =
+        base::StrCat({directory, kSbFileSepString, kEvergreenManifestFilename});
+    ASSERT_TRUE(SbFileDelete(manifest_path.c_str()));
+  }
+
+  void CreateEmptyLibrary(const std::string& name,
+                          const std::string& installation_path) {
+    std::string lib_path = base::StrCat(
+        {installation_path, kSbFileSepString, kEvergreenLibDirname});
+    ASSERT_TRUE(SbDirectoryCreate(lib_path.c_str()));
+
+    lib_path = base::StrCat({lib_path, kSbFileSepString, name});
+    SbFile sb_file = SbFileOpen(
+        lib_path.c_str(), kSbFileOpenAlways | kSbFileRead, nullptr, nullptr);
+    ASSERT_TRUE(SbFileIsValid(sb_file));
+    ASSERT_TRUE(SbFileClose(sb_file));
+  }
+
+  void DeleteLibraryDirRecursively(const std::string& installation_path) {
+    std::string lib_path = base::StrCat(
+        {installation_path, kSbFileSepString, kEvergreenLibDirname});
+    ASSERT_TRUE(starboard::SbFileDeleteRecursive(lib_path.c_str(), false));
+  }
+
+  std::vector<char> temp_dir_path_;
+};
+
+
+TEST_F(UtilsTest, ReadEvergreenVersionReturnsVersionForValidManifest) {
+  std::string installation_path = base::StrCat(
+      {temp_dir_path_.data(), kSbFileSepString, "some_installation_path"});
+  ASSERT_TRUE(SbDirectoryCreate(installation_path.c_str()));
+  char manifest_content[] = R"json(
+  {
+    "manifest_version": 2,
+    "name": "Cobalt",
+    "description": "Cobalt",
+    "version": "1.2.0"
+  })json";
+  CreateManifest(manifest_content, installation_path);
+
+  base::Version version =
+      ReadEvergreenVersion(base::FilePath(installation_path));
+
+  ASSERT_EQ(version.GetString(), "1.2.0");
+
+  DeleteManifest(installation_path);
+}
+
+TEST_F(UtilsTest,
+       ReadEvergreenVersionReturnsInvalidVersionForVersionlessManifest) {
+  std::string installation_path = base::StrCat(
+      {temp_dir_path_.data(), kSbFileSepString, "some_installation_path"});
+  ASSERT_TRUE(SbDirectoryCreate(installation_path.c_str()));
+  char versionless_manifest_content[] = R"json(
+  {
+    "manifest_version": 2,
+    "name": "Cobalt",
+    "description": "Cobalt",
+  })json";
+  CreateManifest(versionless_manifest_content, installation_path);
+
+  base::Version version =
+      ReadEvergreenVersion(base::FilePath(installation_path));
+
+  ASSERT_FALSE(version.IsValid());
+
+  DeleteManifest(installation_path);
+}
+
+TEST_F(UtilsTest, ReadEvergreenVersionReturnsInvalidVersionForMissingManifest) {
+  base::Version version =
+      ReadEvergreenVersion(base::FilePath("nonexistent_manifest_path"));
+
+  ASSERT_FALSE(version.IsValid());
+}
+
+TEST_F(UtilsTest,
+       ReturnsValidCurrentEvergreenVersionForManifestInLoadedInstallation) {
+  std::vector<char> system_path_content_dir(kSbFileMaxPath);
+  SbSystemGetPath(kSbSystemPathContentDirectory, system_path_content_dir.data(),
+                  kSbFileMaxPath);
+  // Since the libupdater_test.so library has already been loaded,
+  // kSbSystemPathContentDirectory points to the content dir of the running
+  // library and the installation dir is therefore its parent.
+  std::string installation_path =
+      base::FilePath(std::string(system_path_content_dir.begin(),
+                                 system_path_content_dir.end()))
+          .DirName()
+          .value();
+  char manifest_content[] = R"json(
+  {
+    "manifest_version": 2,
+    "name": "Cobalt",
+    "description": "Cobalt",
+    "version": "1.2.0"
+  })json";
+  CreateManifest(manifest_content, installation_path);
+
+  std::string version = GetCurrentEvergreenVersion();
+
+  ASSERT_EQ(version, "1.2.0");
+
+  DeleteManifest(installation_path);
+}
+
+TEST_F(UtilsTest,
+       ReturnsDefaultEvergreenVersionForManifestMissingFromLoadedInstallation) {
+  std::string version = GetCurrentEvergreenVersion();
+
+  ASSERT_EQ(version, kDefaultManifestVersion);
+}
+
+TEST_F(UtilsTest,
+       ReturnsValidCurrentMetadataForValidFilesInLoadedInstallation) {
+  std::vector<char> system_path_content_dir(kSbFileMaxPath);
+  SbSystemGetPath(kSbSystemPathContentDirectory, system_path_content_dir.data(),
+                  kSbFileMaxPath);
+  // Since the libupdater_test.so library has already been loaded,
+  // kSbSystemPathContentDirectory points to the content dir of the running
+  // library and the installation dir is therefore its parent.
+  std::string installation_path =
+      base::FilePath(std::string(system_path_content_dir.begin(),
+                                 system_path_content_dir.end()))
+          .DirName()
+          .value();
+  char manifest_content[] = R"json(
+  {
+    "manifest_version": 2,
+    "name": "Cobalt",
+    "description": "Cobalt",
+    "version": "1.2.0"
+  })json";
+  CreateManifest(manifest_content, installation_path);
+  CreateEmptyLibrary("libcobalt.so", installation_path);
+
+  EvergreenLibraryMetadata metadata = GetCurrentEvergreenLibraryMetadata();
+
+  ASSERT_EQ(metadata.version, "1.2.0");
+  ASSERT_EQ(metadata.file_type, "Uncompressed");
+
+  DeleteManifest(installation_path);
+  DeleteLibraryDirRecursively(installation_path);
+}
+
+TEST_F(UtilsTest,
+       ReturnsUnknownFileTypeInMetadataForUnexpectedLibInLoadedInstallation) {
+  std::vector<char> system_path_content_dir(kSbFileMaxPath);
+  SbSystemGetPath(kSbSystemPathContentDirectory, system_path_content_dir.data(),
+                  kSbFileMaxPath);
+  // Since the libupdater_test.so library has already been loaded,
+  // kSbSystemPathContentDirectory points to the content dir of the running
+  // library and the installation dir is therefore its parent.
+  std::string installation_path =
+      base::FilePath(std::string(system_path_content_dir.begin(),
+                                 system_path_content_dir.end()))
+          .DirName()
+          .value();
+  CreateEmptyLibrary("libcobalt.unexpected", installation_path);
+
+  EvergreenLibraryMetadata metadata = GetCurrentEvergreenLibraryMetadata();
+
+  ASSERT_EQ(metadata.file_type, "FileTypeUnknown");
+
+  DeleteLibraryDirRecursively(installation_path);
+}
+
+}  // namespace
+}  // namespace updater
+}  // namespace cobalt
diff --git a/cobalt/watchdog/singleton.h b/cobalt/watchdog/singleton.h
index 9b85980..8ae4933 100644
--- a/cobalt/watchdog/singleton.h
+++ b/cobalt/watchdog/singleton.h
@@ -57,9 +57,11 @@
   static Type* GetInstance() { return s_singleton; }
 
   static void DeleteInstance() {
-    s_singleton->Type::Uninitialize();
-    delete s_singleton;
-    s_singleton = nullptr;
+    if (s_singleton) {
+      s_singleton->Type::Uninitialize();
+      delete s_singleton;
+      s_singleton = nullptr;
+    }
   }
 
   // Prevent copying and moving.
diff --git a/cobalt/watchdog/watchdog.cc b/cobalt/watchdog/watchdog.cc
index 3b6a888..544a47d 100644
--- a/cobalt/watchdog/watchdog.cc
+++ b/cobalt/watchdog/watchdog.cc
@@ -18,7 +18,8 @@
 #include <vector>
 
 #include "base/command_line.h"
-#include "base/values.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
 #include "cobalt/watchdog/watchdog.h"
 #include "starboard/common/file.h"
 #include "starboard/common/log.h"
@@ -33,19 +34,24 @@
 
 namespace {
 
-// The Watchdog violations json file names.
+// The Watchdog violations json filename.
 const char kWatchdogViolationsJson[] = "watchdog.json";
-const char kWatchdogPreviousViolationsJson[] = "watchdog_old.json";
 // The default number of microseconds between each monitor loop.
 const int64_t kWatchdogSmallestTimeInterval = 1000000;
-// The maximum number of repeated Watchdog violations. Prevents excessive
-// Watchdog violation updates.
+// The maximum number of most recent repeated Watchdog violations.
 const int64_t kWatchdogMaxViolations = 100;
-// The maximum number of most recent ping infos to store.
+// The maximum number of most recent ping infos.
 const int64_t kWatchdogMaxPingInfos = 100;
 
 // Persistent setting name and default setting for the boolean that controls
-// whether or not crashes can be triggered.
+// whether or not Watchdog is enabled. When disabled, Watchdog behaves like a
+// stub except that persistent settings can still be get/set. Requires a
+// restart to take effect.
+const char kPersistentSettingWatchdogEnable[] =
+    "kPersistentSettingWatchdogEnable";
+const bool kDefaultSettingWatchdogEnable = true;
+// Persistent setting name and default setting for the boolean that controls
+// whether or not a Watchdog violation will trigger a crash.
 const char kPersistentSettingWatchdogCrash[] =
     "kPersistentSettingWatchdogCrash";
 const bool kDefaultSettingWatchdogCrash = false;
@@ -54,9 +60,13 @@
 
 bool Watchdog::Initialize(
     persistent_storage::PersistentSettings* persistent_settings) {
+  persistent_settings_ = persistent_settings;
+  is_disabled_ = !GetPersistentSettingWatchdogEnable();
+
+  if (is_disabled_) return true;
+
   SB_CHECK(SbMutexCreate(&mutex_));
   smallest_time_interval_ = kWatchdogSmallestTimeInterval;
-  persistent_settings_ = persistent_settings;
 
 #if defined(_DEBUG)
   // Sets Watchdog delay settings from command line switch.
@@ -93,52 +103,36 @@
 }
 
 bool Watchdog::InitializeStub() {
-  is_stub_ = true;
+  is_disabled_ = true;
   return true;
 }
 
 void Watchdog::Uninitialize() {
+  if (is_disabled_) return;
+
   SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired);
   is_monitoring_ = false;
   SB_CHECK(SbMutexRelease(&mutex_));
   SbThreadJoin(watchdog_thread_, nullptr);
 }
 
-std::string Watchdog::GetWatchdogFilePath(bool current) {
-  // Gets the Watchdog violations file path or the previous Watchdog violations
-  // file path with lazy initialization.
+std::string Watchdog::GetWatchdogFilePath() {
+  // Gets the Watchdog violations file path with lazy initialization.
   if (watchdog_file_ == "") {
-    // Sets Watchdog violations file paths.
+    // Sets Watchdog violations file path.
     std::vector<char> cache_dir(kSbFileMaxPath + 1, 0);
     SbSystemGetPath(kSbSystemPathCacheDirectory, cache_dir.data(),
                     kSbFileMaxPath);
     watchdog_file_ = std::string(cache_dir.data()) + kSbFileSepString +
                      std::string(kWatchdogViolationsJson);
-    SB_LOG(INFO) << "Watchdog violations file path: " << watchdog_file_;
-    watchdog_old_file_ = std::string(cache_dir.data()) + kSbFileSepString +
-                         std::string(kWatchdogPreviousViolationsJson);
-    PreservePreviousWatchdogViolations();
+    SB_LOG(INFO) << "[Watchdog] Violations filepath: " << watchdog_file_;
   }
-  if (current) return watchdog_file_;
-  return watchdog_old_file_;
-}
-
-void Watchdog::PreservePreviousWatchdogViolations() {
-  // Copies the previous Watchdog violations file containing violations before
-  // app start, if it exists, to preserve it.
-  starboard::ScopedFile read_file(watchdog_file_.c_str(),
-                                  kSbFileOpenOnly | kSbFileRead);
-  if (read_file.IsValid()) {
-    int64_t kFileSize = read_file.GetSize();
-    std::string watchdog_content(kFileSize + 1, '\0');
-    read_file.ReadAll(&watchdog_content[0], kFileSize);
-    starboard::ScopedFile write_file(watchdog_old_file_.c_str(),
-                                     kSbFileCreateAlways | kSbFileWrite);
-    write_file.WriteAll(&watchdog_content[0], kFileSize);
-  }
+  return watchdog_file_;
 }
 
 void Watchdog::UpdateState(base::ApplicationState state) {
+  if (is_disabled_) return;
+
   SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired);
   state_ = state;
   SB_CHECK(SbMutexRelease(&mutex_));
@@ -156,23 +150,25 @@
 
     int64_t current_time = SbTimeToPosix(SbTimeGetNow());
     SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow();
-    std::string serialized_client_map = "";
+    base::Value registered_clients(base::Value::Type::LIST);
 
     // Iterates through client map to monitor all registered clients.
-    bool new_watchdog_violation = false;
+    bool watchdog_violation = false;
     for (auto& it : static_cast<Watchdog*>(context)->client_map_) {
       Client* client = it.second.get();
       // Ignores and resets clients in idle states, clients whose monitor_state
       // is below the current application state. Resets time_wait_microseconds
-      // and time_interval_microseconds deltas.
+      // and time_interval_microseconds start values.
       if (static_cast<Watchdog*>(context)->state_ > client->monitor_state) {
         client->time_registered_monotonic_microseconds = current_monotonic_time;
-        client->time_last_pinged_microseconds = current_monotonic_time;
+        client->time_last_updated_monotonic_microseconds =
+            current_monotonic_time;
         continue;
       }
 
       SbTimeMonotonic time_delta =
-          current_monotonic_time - client->time_last_pinged_microseconds;
+          current_monotonic_time -
+          client->time_last_updated_monotonic_microseconds;
       SbTimeMonotonic time_wait =
           current_monotonic_time -
           client->time_registered_monotonic_microseconds;
@@ -180,46 +176,102 @@
       // Watchdog violation
       if (time_delta > client->time_interval_microseconds &&
           time_wait > client->time_wait_microseconds) {
-        // Reset time last pinged.
-        client->time_last_pinged_microseconds = current_monotonic_time;
-        // Get serialized client map.
-        if (serialized_client_map == "") {
-          serialized_client_map =
-              static_cast<Watchdog*>(context)->GetSerializedClientMap();
-        }
+        watchdog_violation = true;
 
-        // Updates Watchdog violations.
-        auto iter = (static_cast<Watchdog*>(context)->watchdog_violations_)
-                        .find(client->name);
-        bool already_violated =
-            iter !=
-            (static_cast<Watchdog*>(context)->watchdog_violations_).end();
+        // Gets violation dictionary with key client name from violations_map_.
+        if (static_cast<Watchdog*>(context)->violations_map_ == nullptr)
+          InitializeViolationsMap(context);
+        base::Value* violation_dict =
+            (static_cast<Watchdog*>(context)->violations_map_)
+                ->FindKey(client->name);
 
-        if (already_violated) {
-          // Prevents excessive Watchdog violation updates.
-          Violation* violation = iter->second.get();
-          if (violation->violation_count <= kWatchdogMaxViolations)
-            new_watchdog_violation = true;
-          violation->ping_infos = client->ping_infos;
-          violation->violation_time_microseconds = current_time;
-          violation->violation_delta_microseconds = time_delta;
-          violation->violation_count++;
-          violation->serialized_client_map = serialized_client_map;
+        // Checks if new unique violation.
+        bool new_violation = false;
+        if (violation_dict == nullptr) {
+          new_violation = true;
         } else {
-          new_watchdog_violation = true;
-          std::unique_ptr<Violation> violation(new Violation);
-          *violation = *client;
-          violation->violation_time_microseconds = current_time;
-          violation->violation_delta_microseconds = time_delta;
-          violation->violation_count = 1;
-          violation->serialized_client_map = serialized_client_map;
-          (static_cast<Watchdog*>(context)->watchdog_violations_)
-              .emplace(violation->name, std::move(violation));
+          // Compares against last_pinged_timestamp_microsecond of last
+          // violation.
+          base::Value* violations = violation_dict->FindKey("violations");
+          int index = violations->GetList().size() - 1;
+          std::string timestamp_last_pinged_microseconds =
+              violations->GetList()[index]
+                  .FindKey("timestampLastPingedMicroseconds")
+                  ->GetString();
+          if (timestamp_last_pinged_microseconds !=
+              std::to_string(client->time_last_pinged_microseconds))
+            new_violation = true;
         }
+
+        // New unique violation.
+        if (new_violation) {
+          // Creates new violation.
+          base::Value violation(base::Value::Type::DICTIONARY);
+          violation.SetKey("pingInfos", client->ping_infos.Clone());
+          violation.SetKey("monitorState",
+                           base::Value(std::string(GetApplicationStateString(
+                               client->monitor_state))));
+          violation.SetKey(
+              "timeIntervalMicroseconds",
+              base::Value(std::to_string(client->time_interval_microseconds)));
+          violation.SetKey(
+              "timeWaitMicroseconds",
+              base::Value(std::to_string(client->time_wait_microseconds)));
+          violation.SetKey("timestampRegisteredMicroseconds",
+                           base::Value(std::to_string(
+                               client->time_registered_microseconds)));
+          violation.SetKey("timestampLastPingedMicroseconds",
+                           base::Value(std::to_string(
+                               client->time_last_pinged_microseconds)));
+          violation.SetKey("timestampViolationMicroseconds",
+                           base::Value(std::to_string(current_time)));
+          violation.SetKey(
+              "violationDurationMicroseconds",
+              base::Value(std::to_string(time_delta -
+                                         client->time_interval_microseconds)));
+          if (registered_clients.GetList().empty()) {
+            for (auto& it : static_cast<Watchdog*>(context)->client_map_) {
+              registered_clients.GetList().emplace_back(base::Value(it.first));
+            }
+          }
+          violation.SetKey("registeredClients", registered_clients.Clone());
+
+          // Adds new violation to violations_map_.
+          if (violation_dict == nullptr) {
+            base::Value dict(base::Value::Type::DICTIONARY);
+            dict.SetKey("description", base::Value(client->description));
+            base::Value list(base::Value::Type::LIST);
+            list.GetList().emplace_back(violation.Clone());
+            dict.SetKey("violations", list.Clone());
+            (static_cast<Watchdog*>(context)->violations_map_)
+                ->SetKey(client->name, dict.Clone());
+          } else {
+            base::Value* violations = violation_dict->FindKey("violations");
+            violations->GetList().emplace_back(violation.Clone());
+            if (violations->GetList().size() > kWatchdogMaxViolations)
+              violations->GetList().erase(violations->GetList().begin());
+          }
+          // Consecutive non-unique violation.
+        } else {
+          // Updates consecutive violation in violations_map_.
+          base::Value* violations = violation_dict->FindKey("violations");
+          int index = violations->GetList().size() - 1;
+          int64_t violation_duration =
+              std::stoll(violations->GetList()[index]
+                             .FindKey("violationDurationMicroseconds")
+                             ->GetString());
+          violations->GetList()[index].SetKey(
+              "violationDurationMicroseconds",
+              base::Value(std::to_string(violation_duration + time_delta)));
+        }
+
+        // Resets time last updated.
+        client->time_last_updated_monotonic_microseconds =
+            current_monotonic_time;
       }
     }
-    if (new_watchdog_violation) {
-      SerializeWatchdogViolations(context);
+    if (watchdog_violation) {
+      WriteWatchdogViolations(context);
       MaybeTriggerCrash(context);
     }
 
@@ -229,73 +281,34 @@
   return nullptr;
 }
 
-std::string Watchdog::GetSerializedClientMap() {
-  // Gets the current list of registered clients from the client map and
-  // returns it as a serialized json string.
-  std::string serialized_client_map = "[";
-  std::string comma = "";
-  for (auto& it : client_map_) {
-    serialized_client_map += (comma + "\"" + it.first + "\"");
-    comma = ", ";
-  }
-  serialized_client_map += "]";
-  return serialized_client_map;
-}
-
-void Watchdog::SerializeWatchdogViolations(void* context) {
-  // Writes Watchdog violations to persistent storage as a partial json file.
-  std::string watchdog_json = "";
-  std::string comma = "";
-  for (auto& it : static_cast<Watchdog*>(context)->watchdog_violations_) {
-    Violation* violation = it.second.get();
-    std::string ping_infos = "[";
-    std::string inner_comma = "";
-    while (violation->ping_infos.size() > 0) {
-      ping_infos += (inner_comma + "\"" + violation->ping_infos.front() + "\"");
-      violation->ping_infos.pop();
-      inner_comma = ", ";
-    }
-    ping_infos += "]";
-
-    std::ostringstream ss;
-    ss << comma << "    {\n"
-       << "      \"name\": \"" << violation->name << "\",\n"
-       << "      \"description\": \"" << violation->description << "\",\n"
-       << "      \"ping_infos\": " << ping_infos << ",\n"
-       << "      \"monitor_state\": \""
-       << std::string(GetApplicationStateString(violation->monitor_state))
-       << "\",\n"
-       << "      \"time_interval_microseconds\": "
-       << violation->time_interval_microseconds << ",\n"
-       << "      \"time_wait_microseconds\": "
-       << violation->time_wait_microseconds << ",\n"
-       << "      \"time_registered_microseconds\": "
-       << violation->time_registered_microseconds << ",\n"
-       << "      \"violation_time_microseconds\": "
-       << violation->violation_time_microseconds << ",\n"
-       << "      \"violation_delta_microseconds\": "
-       << violation->violation_delta_microseconds << ",\n"
-       << "      \"violation_count\": " << violation->violation_count << ",\n"
-       << "      \"client_map\": " << violation->serialized_client_map << "\n"
-       << "    }";
-    watchdog_json += ss.str();
-    comma = ",\n";
-  }
-
-  // Appends previous Watchdog violations.
+void Watchdog::InitializeViolationsMap(void* context) {
+  // Loads the previous Watchdog violations file containing violations before
+  // app start, if it exists, to populate violations_map_.
   starboard::ScopedFile read_file(
-      (static_cast<Watchdog*>(context)->GetWatchdogFilePath(false)).c_str(),
+      (static_cast<Watchdog*>(context)->GetWatchdogFilePath()).c_str(),
       kSbFileOpenOnly | kSbFileRead);
   if (read_file.IsValid()) {
     int64_t kFileSize = read_file.GetSize();
-    std::string prev_watchdog_json(kFileSize + 1, '\0');
-    read_file.ReadAll(&prev_watchdog_json[0], kFileSize);
-    prev_watchdog_json.erase(prev_watchdog_json.find('\0'));
-    watchdog_json += ",\n";
-    watchdog_json += prev_watchdog_json;
+    std::vector<char> buffer(kFileSize + 1, 0);
+    read_file.ReadAll(buffer.data(), kFileSize);
+    std::string watchdog_json = std::string(buffer.data());
+    static_cast<Watchdog*>(context)->violations_map_ =
+        base::JSONReader::Read(watchdog_json);
   }
+  if (static_cast<Watchdog*>(context)->violations_map_ == nullptr) {
+    SB_LOG(INFO) << "[Watchdog] No previous violations JSON.";
+    static_cast<Watchdog*>(context)->violations_map_ =
+        std::make_unique<base::Value>(base::Value::Type::DICTIONARY);
+  }
+}
 
-  SB_LOG(INFO) << "Writing Watchdog violations:\n" << watchdog_json;
+void Watchdog::WriteWatchdogViolations(void* context) {
+  // Writes Watchdog violations to persistent storage as a json file.
+  std::string watchdog_json;
+  base::JSONWriter::Write(*(static_cast<Watchdog*>(context)->violations_map_),
+                          &watchdog_json);
+
+  SB_LOG(INFO) << "[Watchdog] Writing violations to JSON:\n" << watchdog_json;
 
   starboard::ScopedFile watchdog_file(
       (static_cast<Watchdog*>(context)->GetWatchdogFilePath()).c_str(),
@@ -306,7 +319,7 @@
 
 void Watchdog::MaybeTriggerCrash(void* context) {
   if (static_cast<Watchdog*>(context)->GetPersistentSettingWatchdogCrash()) {
-    SB_LOG(ERROR) << "Triggering Watchdog Violation Crash!";
+    SB_LOG(ERROR) << "[Watchdog] Triggering violation Crash!";
     CHECK(false);
   }
 }
@@ -321,11 +334,13 @@
                         base::ApplicationState monitor_state,
                         int64_t time_interval, int64_t time_wait,
                         Replace replace) {
-  // Watchdog stub
-  if (is_stub_) return true;
+  if (is_disabled_) return true;
 
   SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired);
 
+  int64_t current_time = SbTimeToPosix(SbTimeGetNow());
+  SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow();
+
   // If replace is PING or ALL, handles already registered cases.
   if (replace != NONE) {
     auto it = client_map_.find(name);
@@ -333,7 +348,9 @@
 
     if (already_registered) {
       if (replace == PING) {
-        it->second->time_last_pinged_microseconds = SbTimeGetMonotonicNow();
+        it->second->time_last_pinged_microseconds = current_time;
+        it->second->time_last_updated_monotonic_microseconds =
+            current_monotonic_time;
         SB_CHECK(SbMutexRelease(&mutex_));
         return true;
       }
@@ -345,49 +362,50 @@
   std::unique_ptr<Client> client(new Client);
   client->name = name;
   client->description = description;
-  client->ping_infos = std::queue<std::string>();
+  client->ping_infos = base::Value(base::Value::Type::LIST);
   client->monitor_state = monitor_state;
   client->time_interval_microseconds = time_interval;
   client->time_wait_microseconds = time_wait;
-  client->time_registered_microseconds = SbTimeToPosix(SbTimeGetNow());
-  client->time_registered_monotonic_microseconds = SbTimeGetMonotonicNow();
-  client->time_last_pinged_microseconds =
-      client->time_registered_monotonic_microseconds;
+  client->time_registered_microseconds = current_time;
+  client->time_registered_monotonic_microseconds = current_monotonic_time;
+  client->time_last_pinged_microseconds = current_time;
+  client->time_last_updated_monotonic_microseconds = current_monotonic_time;
 
   // Registers.
   auto result = client_map_.emplace(name, std::move(client));
-  // Checks for new smallest_time_interval_.
+  // Sets new smallest_time_interval_.
   smallest_time_interval_ = std::min(smallest_time_interval_, time_interval);
 
   SB_CHECK(SbMutexRelease(&mutex_));
 
   if (result.second) {
-    SB_DLOG(INFO) << "Watchdog Registered: " << name;
+    SB_DLOG(INFO) << "[Watchdog] Registered: " << name;
   } else {
-    SB_DLOG(ERROR) << "Watchdog Unable to Register: " << name;
+    SB_DLOG(ERROR) << "[Watchdog] Unable to Register: " << name;
   }
   return result.second;
 }
 
 bool Watchdog::Unregister(const std::string& name, bool lock) {
-  // Watchdog stub
-  if (is_stub_) return true;
+  if (is_disabled_) return true;
 
   if (lock) SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired);
   // Unregisters.
   auto result = client_map_.erase(name);
   // Sets new smallest_time_interval_.
-  smallest_time_interval_ = kWatchdogSmallestTimeInterval;
-  for (auto& it : client_map_) {
-    smallest_time_interval_ = std::min(smallest_time_interval_,
-                                       it.second->time_interval_microseconds);
+  if (result) {
+    smallest_time_interval_ = kWatchdogSmallestTimeInterval;
+    for (auto& it : client_map_) {
+      smallest_time_interval_ = std::min(smallest_time_interval_,
+                                         it.second->time_interval_microseconds);
+    }
   }
   if (lock) SB_CHECK(SbMutexRelease(&mutex_));
 
   if (result) {
-    SB_DLOG(INFO) << "Watchdog Unregistered: " << name;
+    SB_DLOG(INFO) << "[Watchdog] Unregistered: " << name;
   } else {
-    SB_DLOG(ERROR) << "Watchdog Unable to Unregister: " << name;
+    SB_DLOG(ERROR) << "[Watchdog] Unable to Unregister: " << name;
   }
   return result;
 }
@@ -395,25 +413,35 @@
 bool Watchdog::Ping(const std::string& name) { return Ping(name, ""); }
 
 bool Watchdog::Ping(const std::string& name, const std::string& info) {
-  // Watchdog stub
-  if (is_stub_) return true;
+  if (is_disabled_) return true;
 
   SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired);
   auto it = client_map_.find(name);
   bool client_exists = it != client_map_.end();
 
   if (client_exists) {
+    int64_t current_time = SbTimeToPosix(SbTimeGetNow());
+    SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow();
+
+    Client* client = it->second.get();
     // Updates last ping.
-    it->second->time_last_pinged_microseconds = SbTimeGetMonotonicNow();
+    client->time_last_pinged_microseconds = current_time;
+    client->time_last_updated_monotonic_microseconds = current_monotonic_time;
+
     if (info != "") {
-      int64_t current_time = SbTimeToPosix(SbTimeGetNow());
-      it->second->ping_infos.push(std::to_string(current_time) + "\\n" + info +
-                                  "\\n");
-      if (it->second->ping_infos.size() > kWatchdogMaxPingInfos)
-        it->second->ping_infos.pop();
+      // Creates new ping_info.
+      base::Value ping_info(base::Value::Type::DICTIONARY);
+      ping_info.SetKey("timestampMicroseconds",
+                       base::Value(std::to_string(current_time)));
+      ping_info.SetKey("info", base::Value(info));
+
+      client->ping_infos.GetList().emplace_back(ping_info.Clone());
+      if (client->ping_infos.GetList().size() > kWatchdogMaxPingInfos)
+        client->ping_infos.GetList().erase(
+            client->ping_infos.GetList().begin());
     }
   } else {
-    SB_DLOG(ERROR) << "Watchdog Unable to Ping: " << name;
+    SB_DLOG(ERROR) << "[Watchdog] Unable to Ping: " << name;
   }
   SB_CHECK(SbMutexRelease(&mutex_));
   return client_exists;
@@ -421,10 +449,9 @@
 
 std::string Watchdog::GetWatchdogViolations() {
   // Gets a json string containing the Watchdog violations since the last
-  // call (up to a limit).
+  // call (up to the kWatchdogMaxViolations limit).
 
-  // Watchdog stub
-  if (is_stub_) return "";
+  if (is_disabled_) return "";
 
   std::string watchdog_json = "";
   SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired);
@@ -432,28 +459,47 @@
                                   kSbFileOpenOnly | kSbFileRead);
   if (read_file.IsValid()) {
     int64_t kFileSize = read_file.GetSize();
-    std::string watchdog_content(kFileSize + 1, '\0');
-    read_file.ReadAll(&watchdog_content[0], kFileSize);
-    watchdog_content.erase(watchdog_content.find('\0'));
-    watchdog_json = "{\n  \"watchdog_violations\": [\n";
-    watchdog_json += watchdog_content;
-    watchdog_json += "\n  ]\n}";
+    std::vector<char> buffer(kFileSize + 1, 0);
+    read_file.ReadAll(buffer.data(), kFileSize);
+    watchdog_json = std::string(buffer.data());
 
     // Removes all Watchdog violations.
-    watchdog_violations_.clear();
+    if (violations_map_) {
+      base::DictionaryValue** dict = nullptr;
+      violations_map_->GetAsDictionary(dict);
+      if (dict) (*dict)->Clear();
+    }
     starboard::SbFileDeleteRecursive(GetWatchdogFilePath().c_str(), true);
-    starboard::SbFileDeleteRecursive(GetWatchdogFilePath(false).c_str(), true);
-    SB_LOG(INFO) << "Reading Watchdog violations:\n" << watchdog_json;
+    SB_LOG(INFO) << "[Watchdog] Reading violations:\n" << watchdog_json;
   } else {
-    SB_LOG(INFO) << "No Watchdog Violations.";
+    SB_LOG(INFO) << "[Watchdog] No violations.";
   }
   SB_CHECK(SbMutexRelease(&mutex_));
   return watchdog_json;
 }
 
+bool Watchdog::GetPersistentSettingWatchdogEnable() {
+  // Watchdog stub
+  if (!persistent_settings_) return kDefaultSettingWatchdogEnable;
+
+  // Gets the boolean that controls whether or not Watchdog is enabled.
+  return persistent_settings_->GetPersistentSettingAsBool(
+      kPersistentSettingWatchdogEnable, kDefaultSettingWatchdogEnable);
+}
+
+void Watchdog::SetPersistentSettingWatchdogEnable(bool enable_watchdog) {
+  // Watchdog stub
+  if (!persistent_settings_) return;
+
+  // Sets the boolean that controls whether or not Watchdog is enabled.
+  persistent_settings_->SetPersistentSetting(
+      kPersistentSettingWatchdogEnable,
+      std::make_unique<base::Value>(enable_watchdog));
+}
+
 bool Watchdog::GetPersistentSettingWatchdogCrash() {
   // Watchdog stub
-  if (is_stub_) return kDefaultSettingWatchdogCrash;
+  if (!persistent_settings_) return kDefaultSettingWatchdogCrash;
 
   // Gets the boolean that controls whether or not crashes can be triggered.
   return persistent_settings_->GetPersistentSettingAsBool(
@@ -462,7 +508,7 @@
 
 void Watchdog::SetPersistentSettingWatchdogCrash(bool can_trigger_crash) {
   // Watchdog stub
-  if (is_stub_) return;
+  if (!persistent_settings_) return;
 
   // Sets the boolean that controls whether or not crashes can be triggered.
   persistent_settings_->SetPersistentSetting(
@@ -473,14 +519,14 @@
 #if defined(_DEBUG)
 // Sleeps threads based off of environment variables for Watchdog debugging.
 void Watchdog::MaybeInjectDebugDelay(const std::string& name) {
-  // Watchdog stub
-  if (is_stub_) return;
+  if (is_disabled_) return;
 
   starboard::ScopedLock scoped_lock(delay_lock_);
 
   if (name != delay_name_) return;
 
   SbTimeMonotonic current_time = SbTimeGetMonotonicNow();
+
   if (time_last_delayed_microseconds_ == 0)
     time_last_delayed_microseconds_ = current_time;
 
diff --git a/cobalt/watchdog/watchdog.h b/cobalt/watchdog/watchdog.h
index ed14db0..b4535f1 100644
--- a/cobalt/watchdog/watchdog.h
+++ b/cobalt/watchdog/watchdog.h
@@ -16,10 +16,10 @@
 #define COBALT_WATCHDOG_WATCHDOG_H_
 
 #include <memory>
-#include <queue>
 #include <string>
 #include <unordered_map>
 
+#include "base/values.h"
 #include "cobalt/base/application_state.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
 #include "cobalt/watchdog/singleton.h"
@@ -36,27 +36,7 @@
   std::string name;
   std::string description;
   // List of strings optionally provided with each Ping.
-  std::queue<std::string> ping_infos;
-  // Application state to continue monitoring client up to.
-  base::ApplicationState monitor_state;
-  // Maximum number of microseconds allowed between pings before triggering a
-  // Watchdog violation.
-  int64_t time_interval_microseconds;
-  // Number of microseconds to initially wait before Watchdog violations can be
-  // triggered. Reapplies after client resumes from idle state due to
-  // application state changes.
-  int64_t time_wait_microseconds;
-  int64_t time_registered_microseconds;                    // since epoch
-  SbTimeMonotonic time_registered_monotonic_microseconds;  // since (relative)
-  SbTimeMonotonic time_last_pinged_microseconds;           // since (relative)
-} Client;
-
-// Watchdog violation
-typedef struct Violation {
-  std::string name;
-  std::string description;
-  // List of strings optionally provided with each Ping.
-  std::queue<std::string> ping_infos;
+  base::Value ping_infos;
   // Application state to continue monitoring client up to. Inclusive.
   base::ApplicationState monitor_state;
   // Maximum number of microseconds allowed between pings before triggering a
@@ -66,23 +46,22 @@
   // triggered. Reapplies after client resumes from idle state due to
   // application state changes.
   int64_t time_wait_microseconds;
-  int64_t time_registered_microseconds;  // since epoch
-  int64_t violation_time_microseconds;   // since epoch
-  int64_t violation_delta_microseconds;  // over time_interval
-  int64_t violation_count;
-  // Client map as a serialized json string
-  std::string serialized_client_map;
-
-  void operator=(const Client& c) {
-    name = c.name;
-    description = c.description;
-    ping_infos = c.ping_infos;
-    monitor_state = c.monitor_state;
-    time_interval_microseconds = c.time_wait_microseconds;
-    time_wait_microseconds = c.time_wait_microseconds;
-    time_registered_microseconds = c.time_registered_microseconds;
-  }
-} Violation;
+  // Epoch time when client was registered.
+  int64_t time_registered_microseconds;
+  // Monotonically increasing timestamp when client was registered. Used as the
+  // start value for time wait calculations.
+  SbTimeMonotonic time_registered_monotonic_microseconds;
+  // Epoch time when client was last pinged. Set by Ping() and Register() when
+  // in PING replace mode or set initially by Register().
+  int64_t time_last_pinged_microseconds;
+  // Monotonically increasing timestamp when client was last updated. Set by
+  // Ping() and Register() when in PING replace mode or set initially by
+  // Register(). Also reset by Monitor() when in idle states or when a
+  // violation occurs. Prevents excessive violations as they must occur
+  // time_interval_microseconds apart rather than smallest_time_interval_
+  // apart. Used as the start value for time interval calculations.
+  SbTimeMonotonic time_last_updated_monotonic_microseconds;
+} Client;
 
 // Register behavior with previously registered clients of the same name.
 enum Replace {
@@ -107,6 +86,8 @@
   bool Ping(const std::string& name);
   bool Ping(const std::string& name, const std::string& info);
   std::string GetWatchdogViolations();
+  bool GetPersistentSettingWatchdogEnable();
+  void SetPersistentSettingWatchdogEnable(bool enable_watchdog);
   bool GetPersistentSettingWatchdogCrash();
   void SetPersistentSettingWatchdogCrash(bool can_trigger_crash);
 
@@ -116,39 +97,36 @@
 #endif  // defined(_DEBUG)
 
  private:
-  std::string GetWatchdogFilePath(bool current = true);
-  void PreservePreviousWatchdogViolations();
+  std::string GetWatchdogFilePath();
   static void* Monitor(void* context);
-  std::string GetSerializedClientMap();
-  static void SerializeWatchdogViolations(void* context);
+  static void InitializeViolationsMap(void* context);
+  static void WriteWatchdogViolations(void* context);
   static void MaybeTriggerCrash(void* context);
 
-  // Watchdog violations file paths.
+  // Watchdog violations file path.
   std::string watchdog_file_;
-  std::string watchdog_old_file_;
+  // Access to persistent settings.
+  persistent_storage::PersistentSettings* persistent_settings_;
+  // Flag to disable Watchdog. When disabled, Watchdog behaves like a stub
+  // except that persistent settings can still be get/set.
+  bool is_disabled_;
   // Creates a lock which ensures that each loop of monitor is atomic in that
   // modifications to is_monitoring_, state_, smallest_time_interval_, and most
   // importantly to the dictionaries containing Watchdog clients, client_map_
-  // and watchdog_violations_, only occur in between loops of monitor. API
-  // functions like Register(), Unregister(), Ping(), and
-  // GetWatchdogViolations() will be called by various threads and interact
-  // with these class variables.
+  // and violations_map_, only occur in between loops of monitor. API functions
+  // like Register(), Unregister(), Ping(), and GetWatchdogViolations() will be
+  // called by various threads and interact with these class variables.
   SbMutex mutex_;
-  // Time interval between monitor loops.
-  int64_t smallest_time_interval_;
-  // Access to persistent settings.
-  persistent_storage::PersistentSettings* persistent_settings_;
-  // Monitor thread.
-  SbThread watchdog_thread_;
   // Tracks application state.
   base::ApplicationState state_ = base::kApplicationStateStarted;
+  // Time interval between monitor loops.
+  int64_t smallest_time_interval_;
   // Dictionary of registered Watchdog clients.
   std::unordered_map<std::string, std::unique_ptr<Client>> client_map_;
-  // Dictionary of Watchdog violations.
-  std::unordered_map<std::string, std::unique_ptr<Violation>>
-      watchdog_violations_;
-  // Flag to stub out Watchdog.
-  bool is_stub_ = false;
+  // Dictionary of lists of Watchdog violations represented as dictionaries.
+  std::unique_ptr<base::Value> violations_map_;
+  // Monitor thread.
+  SbThread watchdog_thread_;
   // Flag to stop monitor thread.
   bool is_monitoring_;
 
diff --git a/cobalt/web/cobalt_ua_data_values.idl b/cobalt/web/cobalt_ua_data_values.idl
index 2dea119..d8832df 100644
--- a/cobalt/web/cobalt_ua_data_values.idl
+++ b/cobalt/web/cobalt_ua_data_values.idl
@@ -20,6 +20,7 @@
   DOMString jsEngineVersion;
   DOMString rasterizer;
   DOMString evergreenType;
+  DOMString evergreenFileType;
   DOMString evergreenVersion;
   DOMString starboardVersion;
   DOMString originalDesignManufacturer;
diff --git a/cobalt/web/cobalt_ua_data_values_interface.cc b/cobalt/web/cobalt_ua_data_values_interface.cc
index 35504a8..d1dd39a 100644
--- a/cobalt/web/cobalt_ua_data_values_interface.cc
+++ b/cobalt/web/cobalt_ua_data_values_interface.cc
@@ -61,6 +61,9 @@
   if (init_dict.has_evergreen_type()) {
     evergreen_type_ = init_dict.evergreen_type();
   }
+  if (init_dict.has_evergreen_file_type()) {
+    evergreen_file_type_ = init_dict.evergreen_file_type();
+  }
   if (init_dict.has_evergreen_version()) {
     evergreen_version_ = init_dict.evergreen_version();
   }
diff --git a/cobalt/web/cobalt_ua_data_values_interface.h b/cobalt/web/cobalt_ua_data_values_interface.h
index 91dff3a..15612a1 100644
--- a/cobalt/web/cobalt_ua_data_values_interface.h
+++ b/cobalt/web/cobalt_ua_data_values_interface.h
@@ -44,6 +44,9 @@
   const std::string& js_engine_version() const { return js_engine_version_; }
   const std::string& rasterizer() const { return rasterizer_; }
   const std::string& evergreen_type() const { return evergreen_type_; }
+  const std::string& evergreen_file_type() const {
+    return evergreen_file_type_;
+  }
   const std::string& evergreen_version() const { return evergreen_version_; }
   const std::string& starboard_version() const { return starboard_version_; }
   const std::string& original_design_manufacturer() const {
@@ -74,6 +77,7 @@
   std::string js_engine_version_;
   std::string rasterizer_;
   std::string evergreen_type_;
+  std::string evergreen_file_type_;
   std::string evergreen_version_;
   std::string starboard_version_;
   std::string original_design_manufacturer_;
diff --git a/cobalt/web/cobalt_ua_data_values_interface.idl b/cobalt/web/cobalt_ua_data_values_interface.idl
index ff1b119..6243b22 100644
--- a/cobalt/web/cobalt_ua_data_values_interface.idl
+++ b/cobalt/web/cobalt_ua_data_values_interface.idl
@@ -30,6 +30,7 @@
   readonly attribute DOMString jsEngineVersion;
   readonly attribute DOMString rasterizer;
   readonly attribute DOMString evergreenType;
+  readonly attribute DOMString evergreenFileType;
   readonly attribute DOMString evergreenVersion;
   readonly attribute DOMString starboardVersion;
   readonly attribute DOMString originalDesignManufacturer;
diff --git a/cobalt/web/navigator_ua_data.cc b/cobalt/web/navigator_ua_data.cc
index 93ec852..b2bae8b 100644
--- a/cobalt/web/navigator_ua_data.cc
+++ b/cobalt/web/navigator_ua_data.cc
@@ -56,6 +56,8 @@
       platform_info->javascript_engine_version());
   all_high_entropy_values_.set_rasterizer(platform_info->rasterizer_type());
   all_high_entropy_values_.set_evergreen_type(platform_info->evergreen_type());
+  all_high_entropy_values_.set_evergreen_file_type(
+      platform_info->evergreen_file_type());
   all_high_entropy_values_.set_evergreen_version(
       platform_info->evergreen_version());
   all_high_entropy_values_.set_starboard_version(
diff --git a/cobalt/web/user_agent_platform_info.h b/cobalt/web/user_agent_platform_info.h
index 995051d..4d7e51a 100644
--- a/cobalt/web/user_agent_platform_info.h
+++ b/cobalt/web/user_agent_platform_info.h
@@ -43,6 +43,7 @@
   virtual const std::string& javascript_engine_version() const = 0;
   virtual const std::string& rasterizer_type() const = 0;
   virtual const std::string& evergreen_type() const = 0;
+  virtual const std::string& evergreen_file_type() const = 0;
   virtual const std::string& evergreen_version() const = 0;
 
   virtual const std::string& cobalt_version() const = 0;
diff --git a/cobalt/worker/clients.cc b/cobalt/worker/clients.cc
index 5df114d..b1ce469 100644
--- a/cobalt/worker/clients.cc
+++ b/cobalt/worker/clients.cc
@@ -75,7 +75,8 @@
   jobs->message_loop()->task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(&ServiceWorkerJobs::ClientsGetSubSteps,
-                     base::Unretained(jobs), base::Unretained(settings_),
+                     base::Unretained(jobs),
+                     base::Unretained(settings_->context()),
                      base::Unretained(GetAssociatedServiceWorker(settings_)),
                      std::move(promise_reference), id));
 
@@ -103,7 +104,8 @@
   jobs->message_loop()->task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(&ServiceWorkerJobs::ClientsMatchAllSubSteps,
-                     base::Unretained(jobs), base::Unretained(settings_),
+                     base::Unretained(jobs),
+                     base::Unretained(settings_->context()),
                      base::Unretained(GetAssociatedServiceWorker(settings_)),
                      std::move(promise_reference),
                      options.include_uncontrolled(), options.type()));
@@ -156,7 +158,7 @@
   jobs->message_loop()->task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(&ServiceWorkerJobs::ClaimSubSteps, base::Unretained(jobs),
-                     base::Unretained(settings_),
+                     base::Unretained(settings_->context()),
                      base::Unretained(GetAssociatedServiceWorker(settings_)),
                      std::move(promise_reference)));
   // 4. Return promise.
diff --git a/cobalt/worker/extendable_event.h b/cobalt/worker/extendable_event.h
index 1765ad2..e7d7a6a 100644
--- a/cobalt/worker/extendable_event.h
+++ b/cobalt/worker/extendable_event.h
@@ -20,6 +20,8 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/callback.h"
+#include "base/synchronization/waitable_event.h"
 #include "cobalt/base/token.h"
 #include "cobalt/script/promise.h"
 #include "cobalt/script/v8c/native_promise.h"
@@ -42,7 +44,10 @@
 class ExtendableEvent : public web::Event {
  public:
   explicit ExtendableEvent(const std::string& type) : Event(type) {}
-  explicit ExtendableEvent(base::Token type) : Event(type) {}
+  explicit ExtendableEvent(base::Token type,
+                           base::OnceCallback<void(bool)> done_callback =
+                               base::OnceCallback<void(bool)>())
+      : Event(type), done_callback_(std::move(done_callback)) {}
   ExtendableEvent(const std::string& type, const ExtendableEventInit& init_dict)
       : Event(type, init_dict) {}
 
@@ -86,6 +91,9 @@
     --pending_promise_count_;
     // 5.2. If event’s pending promises count is 0, then:
     if (0 == pending_promise_count_) {
+      if (done_callback_) {
+        std::move(done_callback_).Run(has_rejected_promise_);
+      }
       web::Context* context =
           base::polymorphic_downcast<web::EnvironmentSettings*>(settings)
               ->context();
@@ -115,6 +123,8 @@
            ((pending_promise_count_ > 0) || IsBeingDispatched());
   }
 
+  bool has_rejected_promise() const { return has_rejected_promise_; }
+
   DEFINE_WRAPPABLE_TYPE(ExtendableEvent);
 
  protected:
@@ -127,6 +137,8 @@
   bool has_rejected_promise_ = false;
   // https://w3c.github.io/ServiceWorker/#extendableevent-timed-out-flag
   bool timed_out_flag_ = false;
+
+  base::OnceCallback<void(bool)> done_callback_;
 };
 
 }  // namespace worker
diff --git a/cobalt/worker/service_worker.h b/cobalt/worker/service_worker.h
index 2b68fc2..1e39dfe 100644
--- a/cobalt/worker/service_worker.h
+++ b/cobalt/worker/service_worker.h
@@ -45,7 +45,7 @@
   //
   void PostMessage(const std::string& message) {
     DCHECK(message_port_);
-    message_port_->PostMessage(message);
+    if (worker_->worker_global_scope()) message_port_->PostMessage(message);
   }
 
   // The scriptURL getter steps are to return the
@@ -82,7 +82,7 @@
     worker_ = nullptr;
   }
 
-  ServiceWorkerObject* worker_ = nullptr;
+  scoped_refptr<ServiceWorkerObject> worker_;
   scoped_refptr<web::MessagePort> message_port_;
   ServiceWorkerState state_;
 };
diff --git a/cobalt/worker/service_worker_global_scope.cc b/cobalt/worker/service_worker_global_scope.cc
index 9a7a6c9..cf7f3b6 100644
--- a/cobalt/worker/service_worker_global_scope.cc
+++ b/cobalt/worker/service_worker_global_scope.cc
@@ -169,7 +169,7 @@
       FROM_HERE,
       base::BindOnce(&ServiceWorkerJobs::SkipWaitingSubSteps,
                      base::Unretained(jobs),
-                     base::Unretained(environment_settings()),
+                     base::Unretained(environment_settings()->context()),
                      service_worker_object_, std::move(promise_reference)));
   // 3. Return promise.
   return promise;
diff --git a/cobalt/worker/service_worker_jobs.cc b/cobalt/worker/service_worker_jobs.cc
index 9ba0101..0c31df7 100644
--- a/cobalt/worker/service_worker_jobs.cc
+++ b/cobalt/worker/service_worker_jobs.cc
@@ -25,9 +25,11 @@
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_current.h"
 #include "base/single_thread_task_runner.h"
 #include "base/synchronization/lock.h"
 #include "base/task_runner.h"
+#include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/dom/visibility_state.h"
@@ -733,6 +735,19 @@
   // 16. Let runResult be the result of running the Run Service Worker
   //     algorithm with worker and forceBypassCache.
   auto* run_result = RunServiceWorker(worker.get(), force_bypass_cache);
+  bool run_result_is_success = run_result;
+
+  // Post a task for the remaining steps, to let tasks posted by
+  // RunServiceWorker, such as for registering the web context, execute first.
+  base::MessageLoop::current()->task_runner()->PostTask(
+      FROM_HERE, base::Bind(&ServiceWorkerJobs::UpdateOnRunServiceWorker,
+                            base::Unretained(this), state, std::move(worker),
+                            run_result_is_success));
+}
+
+void ServiceWorkerJobs::UpdateOnRunServiceWorker(
+    scoped_refptr<UpdateJobState> state,
+    scoped_refptr<ServiceWorkerObject> worker, bool run_result) {
   // 17. If runResult is failure or an abrupt completion, then:
   if (!run_result) {
     // 17.1. Invoke Reject Job Promise with job and TypeError.
@@ -799,7 +814,11 @@
   //   https://w3c.github.io/ServiceWorker/#installation-algorithm
 
   // 1. Let installFailed be false.
-  starboard::atomic_bool install_failed(false);
+  // Using a shared pointer because this flag is explicitly defined in the spec
+  // to be modified from the worker's event loop, at asynchronous promise
+  // completion that may occur after a timeout.
+  std::shared_ptr<starboard::atomic_bool> install_failed(
+      new starboard::atomic_bool(false));
 
   // 2. Let newestWorker be the result of running Get Newest Worker algorithm
   //    passing registration as its argument.
@@ -873,11 +892,16 @@
     auto* run_result = RunServiceWorker(installing_worker, force_bypass_cache);
     if (!run_result) {
       // 11.2.1. Set installFailed to true.
-      install_failed.store(true);
+      install_failed->store(true);
       // 11.3. Else:
     } else {
       // 11.3.1. Queue a task task on installingWorker’s event loop using the
       //         DOM manipulation task source to run the following steps:
+      // Using a shared pointer to ensure that it still exists when it is
+      // signaled from the callback after the timeout.
+      std::shared_ptr<base::WaitableEvent> done_event(new base::WaitableEvent(
+          base::WaitableEvent::ResetPolicy::MANUAL,
+          base::WaitableEvent::InitialState::NOT_SIGNALED));
       installing_worker->web_agent()
           ->context()
           ->message_loop()
@@ -885,14 +909,13 @@
           ->PostBlockingTask(
               FROM_HERE,
               base::Bind(
-                  [](ServiceWorkerObject* installing_worker) {
+                  [](ServiceWorkerObject* installing_worker,
+                     std::shared_ptr<base::WaitableEvent> done_event,
+                     std::shared_ptr<starboard::atomic_bool> install_failed) {
                     // 11.3.1.1. Let e be the result of creating an event with
                     //           ExtendableEvent.
-                    // TODO(b/228976500): implement this as ExtendableEvent.
                     // 11.3.1.2. Initialize e’s type attribute to install.
                     // 11.3.1.3. Dispatch e at installingWorker’s global object.
-                    installing_worker->worker_global_scope()->DispatchEvent(
-                        new ExtendableEvent(base::Tokens::install()));
                     // 11.3.1.4. WaitForAsynchronousExtensions: Run the
                     //           following substeps in parallel:
                     // 11.3.1.4.1. Wait until e is not active.
@@ -903,17 +926,43 @@
                     // 11.3.1.4.4. Upon rejection of p, set installFailed to
                     //             true.
                     //         If task is discarded, set installFailed to true.
+                    auto done_callback = base::BindOnce(
+                        [](std::shared_ptr<base::WaitableEvent> done_event,
+                           std::shared_ptr<starboard::atomic_bool>
+                               install_failed,
+                           bool was_rejected) {
+                          if (was_rejected) install_failed->store(true);
+                          done_event->Signal();
+                        },
+                        done_event, install_failed);
+                    scoped_refptr<ExtendableEvent> event(new ExtendableEvent(
+                        base::Tokens::install(), std::move(done_callback)));
+                    installing_worker->worker_global_scope()->DispatchEvent(
+                        event);
+                    if (!event->IsActive()) {
+                      // If the event handler doesn't use waitUntil(), it will
+                      // already no longer be active, and there will never be a
+                      // callback to signal the done event.
+                      done_event->Signal();
+                    }
                   },
-                  base::Unretained(installing_worker)));
+                  base::Unretained(installing_worker), done_event,
+                  install_failed));
       // 11.3.2. Wait for task to have executed or been discarded.
-      // Waiting is done inside PostBlockingTask above.
+      // This waiting is done inside PostBlockingTask above.
       // 11.3.3. Wait for the step labeled WaitForAsynchronousExtensions to
       //         complete.
-      NOTIMPLEMENTED();
+      // TODO(b/240164388): Investigate a better approach for combining waiting
+      // for the ExtendableEvent while also allowing use of algorithms that run
+      // on the same thread from the event handler.
+      while (!done_event->TimedWait(base::TimeDelta::FromMilliseconds(100))) {
+        base::MessageLoopCurrent::ScopedNestableTaskAllower allow;
+        base::RunLoop().RunUntilIdle();
+      }
     }
   }
   // 12. If installFailed is true, then:
-  if (install_failed.load()) {
+  if (install_failed->load()) {
     // 12.1. Run the Update Worker State algorithm passing registration’s
     //       installing worker and "redundant" as the arguments.
     UpdateWorkerState(registration->installing_worker(),
@@ -1124,6 +1173,9 @@
                 active_worker->worker_global_scope()
                     ->environment_settings()
                     ->context());
+      std::shared_ptr<base::WaitableEvent> done_event(new base::WaitableEvent(
+          base::WaitableEvent::ResetPolicy::MANUAL,
+          base::WaitableEvent::InitialState::NOT_SIGNALED));
       active_worker->web_agent()
           ->context()
           ->message_loop()
@@ -1131,23 +1183,40 @@
           ->PostBlockingTask(
               FROM_HERE,
               base::Bind(
-                  [](ServiceWorkerObject* active_worker) {
+                  [](ServiceWorkerObject* active_worker,
+                     std::shared_ptr<base::WaitableEvent> done_event) {
+                    auto done_callback = base::BindOnce(
+                        [](std::shared_ptr<base::WaitableEvent> done_event,
+                           bool) { done_event->Signal(); },
+                        done_event);
+                    scoped_refptr<ExtendableEvent> event(new ExtendableEvent(
+                        base::Tokens::activate(), std::move(done_callback)));
                     // 11.1.1.1. Let e be the result of creating an event with
                     //           ExtendableEvent.
-                    // TODO(b/228976500): implement this as ExtendableEvent.
                     // 11.1.1.2. Initialize e’s type attribute to activate.
                     // 11.1.1.3. Dispatch e at activeWorker’s global object.
-                    active_worker->worker_global_scope()->DispatchEvent(
-                        new ExtendableEvent(base::Tokens::activate()));
+                    active_worker->worker_global_scope()->DispatchEvent(event);
                     // 11.1.1.4. WaitForAsynchronousExtensions: Wait, in
                     //           parallel, until e is not active.
+                    if (!event->IsActive()) {
+                      // If the event handler doesn't use waitUntil(), it will
+                      // already no longer be active, and there will never be a
+                      // callback to signal the done event.
+                      done_event->Signal();
+                    }
                   },
-                  base::Unretained(active_worker)));
+                  base::Unretained(active_worker), done_event));
       // 11.1.2. Wait for task to have executed or been discarded.
-      // Waiting is done inside PostBlockingTask above.
+      // This waiting is done inside PostBlockingTask above.
       // 11.1.3. Wait for the step labeled WaitForAsynchronousExtensions to
       //         complete.
-      NOTIMPLEMENTED();
+      // TODO(b/240164388): Investigate a better approach for combining waiting
+      // for the ExtendableEvent while also allowing use of algorithms that run
+      // on the same thread from the event handler.
+      while (!done_event->TimedWait(base::TimeDelta::FromMilliseconds(100))) {
+        base::MessageLoopCurrent::ScopedNestableTaskAllower allow;
+        base::RunLoop().RunUntilIdle();
+      }
     }
   }
   // 12. Run the Update Worker State algorithm passing registration’s active
@@ -1183,7 +1252,7 @@
     ServiceWorkerObject* worker) {
   // Algorithm for Service Worker Has No Pending Events
   //   https://w3c.github.io/ServiceWorker/#service-worker-has-no-pending-events
-  // TODO(b/228976500): implement this from ExtendableEvent support.
+  // TODO(b/240174245): Implement this using the 'set of extended events'.
   NOTIMPLEMENTED();
 
   // 1. For each event of worker’s set of extended events:
@@ -1503,7 +1572,7 @@
   service_worker_global_scope->set_closing_flag(true);
 
   // 1.3. Remove all the items from serviceWorker’s set of extended events.
-  // TODO(b/228976500): implement this with ExtendableEvent support.
+  // TODO(b/240174245): Implement 'set of extended events'.
 
   // 1.4. If there are any tasks, whose task source is either the handle fetch
   //      task source or the handle functional event task source, queued in
@@ -1813,11 +1882,18 @@
 }
 
 void ServiceWorkerJobs::SkipWaitingSubSteps(
-    web::EnvironmentSettings* client,
+    web::Context* client_context,
     const base::WeakPtr<ServiceWorkerObject>& service_worker,
     std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::SkipWaitingSubSteps()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  // Check if the client web context is still active. This may trigger if
+  // skipWaiting() was called and service worker installation fails.
+  if (!IsWebContextRegistered(client_context)) {
+    promise_reference.release();
+    return;
+  }
+
   // Algorithm for Sub steps of ServiceWorkerGlobalScope.skipWaiting():
   //   https://w3c.github.io/ServiceWorker/#dom-serviceworkerglobalscope-skipwaiting
 
@@ -1829,7 +1905,7 @@
   TryActivate(service_worker->containing_service_worker_registration());
 
   // 2.3. Resolve promise with undefined.
-  client->context()->message_loop()->task_runner()->PostTask(
+  client_context->message_loop()->task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(
           [](std::unique_ptr<script::ValuePromiseVoid::Reference> promise) {
@@ -1855,13 +1931,20 @@
     TryActivate(registration);
   }
 }
+
 void ServiceWorkerJobs::ClientsGetSubSteps(
-    web::EnvironmentSettings* settings,
+    web::Context* promise_context,
     ServiceWorkerObject* associated_service_worker,
     std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference,
     const std::string& id) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::ClientsGetSubSteps()");
   DCHECK_EQ(message_loop_, base::MessageLoop::current());
+  // Check if the client web context is still active. This may trigger if
+  // Clients.get() was called and service worker installation fails.
+  if (!IsWebContextRegistered(promise_context)) {
+    promise_reference.release();
+    return;
+  }
   // Parallel sub steps (2) for algorithm for Clients.get(id):
   //   https://w3c.github.io/ServiceWorker/#clients-get
   // 2.1. For each service worker client client where the result of running
@@ -1884,27 +1967,26 @@
 
       // 2.1.3. If client’s execution ready flag is set, then invoke Resolve Get
       //        Client Promise with client and promise, and abort these steps.
-      ResolveGetClientPromise(client, settings, std::move(promise_reference));
+      ResolveGetClientPromise(client, promise_context,
+                              std::move(promise_reference));
       return;
     }
   }
   // 2.2. Resolve promise with undefined.
-  settings->context()->message_loop()->task_runner()->PostTask(
+  promise_context->message_loop()->task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(
           [](std::unique_ptr<script::ValuePromiseWrappable::Reference>
                  promise_reference) {
-            TRACE_EVENT0(
-                "cobalt::worker",
-                "ServiceWorkerJobs::ResolveGetClientPromise() Resolve");
+            TRACE_EVENT0("cobalt::worker",
+                         "ServiceWorkerJobs::ClientsGetSubSteps() Resolve");
             promise_reference->value().Resolve(scoped_refptr<Client>());
           },
           std::move(promise_reference)));
 }
 
 void ServiceWorkerJobs::ResolveGetClientPromise(
-    web::EnvironmentSettings* client,
-    web::EnvironmentSettings* promise_relevant_settings,
+    web::EnvironmentSettings* client, web::Context* promise_context,
     std::unique_ptr<script::ValuePromiseWrappable::Reference>
         promise_reference) {
   TRACE_EVENT0("cobalt::worker",
@@ -1935,21 +2017,18 @@
     // 3.2. Queue a task to resolve promise with clientObject, on promise’s
     //      relevant settings object's responsible event loop using the DOM
     //      manipulation task source, and abort these steps.
-    promise_relevant_settings->context()
-        ->message_loop()
-        ->task_runner()
-        ->PostTask(
-            FROM_HERE,
-            base::BindOnce(
-                [](std::unique_ptr<script::ValuePromiseWrappable::Reference>
-                       promise_reference,
-                   scoped_refptr<Client> client_object) {
-                  TRACE_EVENT0(
-                      "cobalt::worker",
-                      "ServiceWorkerJobs::ResolveGetClientPromise() Resolve");
-                  promise_reference->value().Resolve(client_object);
-                },
-                std::move(promise_reference), client_object));
+    promise_context->message_loop()->task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            [](std::unique_ptr<script::ValuePromiseWrappable::Reference>
+                   promise_reference,
+               scoped_refptr<Client> client_object) {
+              TRACE_EVENT0(
+                  "cobalt::worker",
+                  "ServiceWorkerJobs::ResolveGetClientPromise() Resolve");
+              promise_reference->value().Resolve(client_object);
+            },
+            std::move(promise_reference), client_object));
     return;
   }
   // 4. Else:
@@ -1968,8 +2047,7 @@
   client->context()->message_loop()->task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(
-          [](web::EnvironmentSettings* client,
-             web::EnvironmentSettings* promise_relevant_settings,
+          [](web::EnvironmentSettings* client, web::Context* promise_context,
              std::unique_ptr<script::ValuePromiseWrappable::Reference>
                  promise_reference) {
             std::unique_ptr<WindowData> window_data(new WindowData);
@@ -1999,36 +2077,32 @@
             // 4.4.6. Queue a task to run the following steps on promise’s
             //        relevant settings object's responsible event loop using
             //        the DOM manipulation task source:
-            promise_relevant_settings->context()
-                ->message_loop()
-                ->task_runner()
-                ->PostTask(
-                    FROM_HERE,
-                    base::BindOnce(
-                        [](std::unique_ptr<
-                               script::ValuePromiseWrappable::Reference>
-                               promise_reference,
-                           std::unique_ptr<WindowData> window_data) {
-                          // 4.4.6.1. If client’s discarded flag is set, resolve
-                          //          promise with undefined and abort these
-                          //          steps.
-                          // 4.4.6.2. Let windowClient be the result of running
-                          //          Create Window Client with client,
-                          //          frameType, visibilityState, focusState,
-                          //          and ancestorOriginsList.
-                          scoped_refptr<WindowClient> window_client =
-                              WindowClient::Create(*window_data);
-                          // 4.4.6.3. Resolve promise with windowClient.
-                          promise_reference->value().Resolve(window_client);
-                        },
-                        std::move(promise_reference), std::move(window_data)));
+            promise_context->message_loop()->task_runner()->PostTask(
+                FROM_HERE,
+                base::BindOnce(
+                    [](std::unique_ptr<script::ValuePromiseWrappable::Reference>
+                           promise_reference,
+                       std::unique_ptr<WindowData> window_data) {
+                      // 4.4.6.1. If client’s discarded flag is set, resolve
+                      //          promise with undefined and abort these
+                      //          steps.
+                      // 4.4.6.2. Let windowClient be the result of running
+                      //          Create Window Client with client,
+                      //          frameType, visibilityState, focusState,
+                      //          and ancestorOriginsList.
+                      scoped_refptr<WindowClient> window_client =
+                          WindowClient::Create(*window_data);
+                      // 4.4.6.3. Resolve promise with windowClient.
+                      promise_reference->value().Resolve(window_client);
+                    },
+                    std::move(promise_reference), std::move(window_data)));
           },
-          client, promise_relevant_settings, std::move(promise_reference)));
+          client, promise_context, std::move(promise_reference)));
   DCHECK_EQ(nullptr, promise_reference.get());
 }
 
 void ServiceWorkerJobs::ClientsMatchAllSubSteps(
-    web::EnvironmentSettings* settings,
+    web::Context* client_context,
     ServiceWorkerObject* associated_service_worker,
     std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
         promise_reference,
@@ -2036,6 +2110,13 @@
   TRACE_EVENT0("cobalt::worker",
                "ServiceWorkerJobs::ClientsMatchAllSubSteps()");
   DCHECK_EQ(message_loop_, base::MessageLoop::current());
+  // Check if the client web context is still active. This may trigger if
+  // Clients.matchAll() was called and service worker installation fails.
+  if (!IsWebContextRegistered(client_context)) {
+    promise_reference.release();
+    return;
+  }
+
   // Parallel sub steps (2) for algorithm for Clients.matchAll():
   //   https://w3c.github.io/ServiceWorker/#clients-matchall
   // 2.1. Let targetClients be a new list.
@@ -2106,7 +2187,7 @@
       //          browsingContext to client’s global object's browsing context.
 
       // 2.5.1.5. Else, set browsingContext to client’s target browsing context.
-      web::Context* browsing_context = settings->context();
+      web::Context* browsing_context = client_context;
 
       // 2.5.1.6. Queue a task task to run the following substeps on
       //          browsingContext’s event loop using the user interaction task
@@ -2183,7 +2264,7 @@
   // 2.6. Queue a task to run the following steps on promise’s relevant
   // settings object's responsible event loop using the DOM manipulation
   // task source:
-  settings->context()->message_loop()->task_runner()->PostTask(
+  client_context->message_loop()->task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(
           [](std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
@@ -2247,11 +2328,19 @@
 }
 
 void ServiceWorkerJobs::ClaimSubSteps(
-    web::EnvironmentSettings* settings,
+    web::Context* client_context,
     ServiceWorkerObject* associated_service_worker,
     std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::ClaimSubSteps()");
   DCHECK_EQ(message_loop_, base::MessageLoop::current());
+
+  // Check if the client web context is still active. This may trigger if
+  // Clients.claim() was called and service worker installation fails.
+  if (!IsWebContextRegistered(client_context)) {
+    promise_reference.release();
+    return;
+  }
+
   // Parallel sub steps (3) for algorithm for Clients.claim():
   //   https://w3c.github.io/ServiceWorker/#dom-clients-claim
   std::list<web::EnvironmentSettings*> target_clients;
@@ -2263,9 +2352,9 @@
       associated_service_worker->containing_service_worker_registration()
           ->storage_key();
   for (auto& context : web_context_registrations_) {
-    web::EnvironmentSettings* client = context->environment_settings();
     // Don't claim to be our own service worker.
-    if (client == settings) continue;
+    if (context == client_context) continue;
+    web::EnvironmentSettings* client = context->environment_settings();
     url::Origin client_storage_key = client->ObtainStorageKey();
     if (client_storage_key.IsSameOriginWith(storage_key)) {
       // 3.1.1. If client’s execution ready flag is unset or client’s discarded
@@ -2314,7 +2403,7 @@
     }
   }
   // 3.2. Resolve promise with undefined.
-  settings->context()->message_loop()->task_runner()->PostTask(
+  client_context->message_loop()->task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(
           [](std::unique_ptr<script::ValuePromiseVoid::Reference> promise) {
diff --git a/cobalt/worker/service_worker_jobs.h b/cobalt/worker/service_worker_jobs.h
index 8e09831..11b037b 100644
--- a/cobalt/worker/service_worker_jobs.h
+++ b/cobalt/worker/service_worker_jobs.h
@@ -200,7 +200,7 @@
   // Sub steps (2) of ServiceWorkerGlobalScope.skipWaiting().
   //   https://w3c.github.io/ServiceWorker/#dom-serviceworkerglobalscope-skipwaiting
   void SkipWaitingSubSteps(
-      web::EnvironmentSettings* client,
+      web::Context* client_context,
       const base::WeakPtr<ServiceWorkerObject>& service_worker,
       std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference);
 
@@ -211,7 +211,7 @@
   // Parallel sub steps (2) for algorithm for Clients.get(id):
   //   https://w3c.github.io/ServiceWorker/#clients-get
   void ClientsGetSubSteps(
-      web::EnvironmentSettings* settings,
+      web::Context* client_context,
       ServiceWorkerObject* associated_service_worker,
       std::unique_ptr<script::ValuePromiseWrappable::Reference>
           promise_reference,
@@ -220,15 +220,14 @@
   // Algorithm for Resolve Get Client Promise:
   //   https://w3c.github.io/ServiceWorker/#resolve-get-client-promise
   void ResolveGetClientPromise(
-      web::EnvironmentSettings* client,
-      web::EnvironmentSettings* promise_relevant_settings,
+      web::EnvironmentSettings* client, web::Context* promise_context,
       std::unique_ptr<script::ValuePromiseWrappable::Reference>
           promise_reference);
 
   // Parallel sub steps (2) for algorithm for Clients.matchAll():
   //   https://w3c.github.io/ServiceWorker/#clients-matchall
   void ClientsMatchAllSubSteps(
-      web::EnvironmentSettings* settings,
+      web::Context* client_context,
       ServiceWorkerObject* associated_service_worker,
       std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
           promise_reference,
@@ -237,13 +236,17 @@
   // Parallel sub steps (3) for algorithm for Clients.claim():
   //   https://w3c.github.io/ServiceWorker/#dom-clients-claim
   void ClaimSubSteps(
-      web::EnvironmentSettings* settings,
+      web::Context* client_context,
       ServiceWorkerObject* associated_service_worker,
       std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference);
 
   // Registration of web contexts that may have service workers.
   void RegisterWebContext(web::Context* context);
   void UnregisterWebContext(web::Context* context);
+  bool IsWebContextRegistered(web::Context* context) {
+    return web_context_registrations_.end() !=
+           web_context_registrations_.find(context);
+  }
 
   // https://w3c.github.io/ServiceWorker/#create-job
   std::unique_ptr<Job> CreateJob(
@@ -344,6 +347,11 @@
   void UpdateOnLoadingComplete(scoped_refptr<UpdateJobState> state,
                                const base::Optional<std::string>& error);
 
+  void UpdateOnRunServiceWorker(scoped_refptr<UpdateJobState> state,
+                                scoped_refptr<ServiceWorkerObject> worker,
+                                bool run_result);
+
+
   // https://w3c.github.io/ServiceWorker/#unregister-algorithm
   void Unregister(Job* job);
 
diff --git a/cobalt/worker/service_worker_registration.h b/cobalt/worker/service_worker_registration.h
index dd3107c..58a2e3d 100644
--- a/cobalt/worker/service_worker_registration.h
+++ b/cobalt/worker/service_worker_registration.h
@@ -83,7 +83,7 @@
   void UnregisterTask(
       std::unique_ptr<script::ValuePromiseBool::Reference> promise_reference);
 
-  worker::ServiceWorkerRegistrationObject* registration_;
+  scoped_refptr<worker::ServiceWorkerRegistrationObject> registration_;
   scoped_refptr<ServiceWorker> installing_;
   scoped_refptr<ServiceWorker> waiting_;
   scoped_refptr<ServiceWorker> active_;
diff --git a/docker/linux/base/build/Dockerfile b/docker/linux/base/build/Dockerfile
index 10098de..27050e3 100644
--- a/docker/linux/base/build/Dockerfile
+++ b/docker/linux/base/build/Dockerfile
@@ -63,10 +63,10 @@
     CCACHE_DIR=${HOME}/ccache \
     CCACHE_MAXSIZE=30G
 
-# == Set up  ccache
+# === Set up ccache
 RUN cd /tmp && mkdir ${HOME}/ccache
 
-#  === Install portable sccache binary
+# === Install portable sccache binary
 ARG SCCACHE=sccache-v0.3.0-x86_64-unknown-linux-musl.tar.gz
 ARG SCCACHE_SHA256=e6cd8485f93d683a49c83796b9986f090901765aa4feb40d191b03ea770311d8
 RUN cd /tmp \
diff --git a/precommit_hooks/gcheckstyle_wrapper.py b/precommit_hooks/gcheckstyle_wrapper.py
index 42f8675..bfadb84 100755
--- a/precommit_hooks/gcheckstyle_wrapper.py
+++ b/precommit_hooks/gcheckstyle_wrapper.py
@@ -24,6 +24,11 @@
 
 if __name__ == '__main__':
   gcheckstyle_args = sys.argv[1:]
+
+  if not checkstyle_path:
+    print('Checkstyle not available, skipping.')
+    sys.exit(0)
+
   try:
     sys.exit(subprocess.call([checkstyle_path] + gcheckstyle_args))
   except FileNotFoundError:
diff --git a/starboard/android/shared/file_open.cc b/starboard/android/shared/file_open.cc
index 0f398a4..3e0caa6 100644
--- a/starboard/android/shared/file_open.cc
+++ b/starboard/android/shared/file_open.cc
@@ -26,17 +26,6 @@
 
 namespace {
 
-// We don't package most font files in Cobalt content and fallback to the system
-// font file of the same name.
-const std::string kFontsXml("fonts.xml");
-const std::string kSystemFontsDir("/system/fonts/");
-
-#if SB_IS(EVERGREEN_COMPATIBLE)
-const std::string kCobaltFontsDir("/cobalt/assets/app/cobalt/content/fonts/");
-#else
-const std::string kCobaltFontsDir("/cobalt/assets/fonts/");
-#endif
-
 // Returns the fallback for the given asset path, or an empty string if none.
 // NOTE: While Cobalt now provides a mechanism for loading system fonts through
 //       SbSystemGetPath(), using the fallback logic within SbFileOpen() is
@@ -54,12 +43,24 @@
 //       straightforward mechanism for including vendor fonts via
 //       SbSystemGetPath().
 std::string FallbackPath(const std::string& path) {
+  // We don't package most font files in Cobalt content and fallback to the
+  // system font file of the same name.
+  const std::string fonts_xml("fonts.xml");
+  const std::string system_fonts_dir("/system/fonts/");
+
+#if SB_IS(EVERGREEN_COMPATIBLE)
+  const std::string cobalt_fonts_dir(
+      "/cobalt/assets/app/cobalt/content/fonts/");
+#else
+  const std::string cobalt_fonts_dir("/cobalt/assets/fonts/");
+#endif
+
   // Fonts fallback to the system fonts.
-  if (path.compare(0, kCobaltFontsDir.length(), kCobaltFontsDir) == 0) {
-    std::string file_name = path.substr(kCobaltFontsDir.length());
+  if (path.compare(0, cobalt_fonts_dir.length(), cobalt_fonts_dir) == 0) {
+    std::string file_name = path.substr(cobalt_fonts_dir.length());
     // fonts.xml doesn't fallback.
-    if (file_name != kFontsXml) {
-      return kSystemFontsDir + file_name;
+    if (file_name != fonts_xml) {
+      return system_fonts_dir + file_name;
     }
   }
   return std::string();
diff --git a/starboard/android/shared/test_filters.py b/starboard/android/shared/test_filters.py
index ff82758..a57ab71 100644
--- a/starboard/android/shared/test_filters.py
+++ b/starboard/android/shared/test_filters.py
@@ -45,11 +45,6 @@
         # /etc/hosts.
         'SbSocketAddressTypes/SbSocketResolveTest.Localhost/1',
 
-        # These tests are taking longer due to interop on android. Work is
-        # underway to investigate whether this is acceptable.
-        'SbMediaCanPlayMimeAndKeySystem.ValidatePerformance',
-        'SbMediaConfigurationTest.ValidatePerformance',
-
         # SbDirectory has problems with empty Asset dirs.
         'SbDirectoryCanOpenTest.SunnyDayStaticContent',
         'SbDirectoryGetNextTest.SunnyDayStaticContent',
diff --git a/starboard/android/x86/test_filters.py b/starboard/android/x86/test_filters.py
index c33e6cf..1dbace9 100644
--- a/starboard/android/x86/test_filters.py
+++ b/starboard/android/x86/test_filters.py
@@ -22,7 +22,6 @@
         'SbAccessibilityTest.CallSetCaptionsEnabled',
         'SbAccessibilityTest.GetCaptionSettingsReturnIsValid',
         'SbAudioSinkTest.*',
-        'SbMediaCanPlayMimeAndKeySystem.*',
         'SbMicrophoneCloseTest.*',
         'SbMicrophoneOpenTest.*',
         'SbMicrophoneReadTest.*',
diff --git a/starboard/evergreen/shared/gyp_configuration.py b/starboard/evergreen/shared/gyp_configuration.py
index 0cc51a6..f1c6e43 100644
--- a/starboard/evergreen/shared/gyp_configuration.py
+++ b/starboard/evergreen/shared/gyp_configuration.py
@@ -27,7 +27,7 @@
 
   def GetTestTargets(self):
     tests = super(EvergreenConfiguration, self).GetTestTargets()
-    tests.append('cobalt_slot_management_test')
+    tests.extend({'cobalt_slot_management_test', 'updater_test'})
     return [test for test in tests if test not in self.__FORBIDDEN_TESTS]
 
   __FORBIDDEN_TESTS = [  # pylint: disable=invalid-name
diff --git a/starboard/nplb/media_can_play_mime_and_key_system_test.cc b/starboard/nplb/media_can_play_mime_and_key_system_test.cc
index af93b99..223a749 100644
--- a/starboard/nplb/media_can_play_mime_and_key_system_test.cc
+++ b/starboard/nplb/media_can_play_mime_and_key_system_test.cc
@@ -704,13 +704,30 @@
   }
 }
 
+// Note: If the platform failed on this test, please improve the performance of
+// SbMediaCanPlayMimeAndKeySystem(). A few ideas:
+//    1. Cache codec and drm capabilities. Please make sure a codec or drm
+//       capability query can be done quickly without too many calculations.
+//    2. Cache audio output and display configurations. On some platforms, it
+//       takes time to get the audio/video configurations. Caching these
+//       configurations can significantly reduce the latency on acquiring
+//       configurations. Unlike codec and drm capabilities, audio/video
+//       configurations may change during app runtime, the platform need to
+//       update the cache if there's any change.
+//    3. Enable MimeSupportabilityCache and KeySystemSupportabilityCache. These
+//       supportability caches will cache the results of previous queries, to
+//       boost the queries of repeated mime and key system. Note that if there's
+//       any capability change, the platform need to explicitly clear the
+//       caches, otherwise they may return outdated results.
 TEST(SbMediaCanPlayMimeAndKeySystem, ValidatePerformance) {
   auto test_sequential_function_calls =
-      [](const char** mime_params, int num_function_calls,
-         SbTimeMonotonic max_time_delta, const char* query_type) {
+      [](const SbMediaCanPlayMimeAndKeySystemParam* mime_params,
+         int num_function_calls, SbTimeMonotonic max_time_delta,
+         const char* query_type) {
         const SbTimeMonotonic time_start = SbTimeGetMonotonicNow();
         for (int i = 0; i < num_function_calls; ++i) {
-          SbMediaCanPlayMimeAndKeySystem(mime_params[i], "");
+          SbMediaCanPlayMimeAndKeySystem(mime_params[i].mime,
+                                         mime_params[i].key_system);
         }
         const SbTimeMonotonic time_last = SbTimeGetMonotonicNow();
         const SbTimeMonotonic time_delta = time_last - time_start;
@@ -725,15 +742,30 @@
         EXPECT_LE(time_delta, max_time_delta);
       };
 
-  test_sequential_function_calls(kSdrQueryParams,
-                                 SB_ARRAY_SIZE_INT(kSdrQueryParams),
-                                 10 * kSbTimeMillisecond, "SDR queries");
-  test_sequential_function_calls(kHdrQueryParams,
-                                 SB_ARRAY_SIZE_INT(kHdrQueryParams),
-                                 20 * kSbTimeMillisecond, "HDR queries");
-  test_sequential_function_calls(kDrmQueryParams,
-                                 SB_ARRAY_SIZE_INT(kDrmQueryParams),
-                                 50 * kSbTimeMillisecond, "DRM queries");
+  // Warmup the cache.
+  test_sequential_function_calls(
+      kWarmupQueryParams, SB_ARRAY_SIZE_INT(kWarmupQueryParams),
+      5 * kSbTimeMillisecond /* 9 calls */, "Warmup queries");
+  // First round of the queires.
+  test_sequential_function_calls(
+      kSdrQueryParams, SB_ARRAY_SIZE_INT(kSdrQueryParams),
+      10 * kSbTimeMillisecond /* 38 calls */, "SDR queries");
+  test_sequential_function_calls(
+      kHdrQueryParams, SB_ARRAY_SIZE_INT(kHdrQueryParams),
+      10 * kSbTimeMillisecond /* 82 calls */, "HDR queries");
+  test_sequential_function_calls(
+      kDrmQueryParams, SB_ARRAY_SIZE_INT(kDrmQueryParams),
+      10 * kSbTimeMillisecond /* 81 calls */, "DRM queries");
+  // Second round of the queries.
+  test_sequential_function_calls(
+      kSdrQueryParams, SB_ARRAY_SIZE_INT(kSdrQueryParams),
+      5 * kSbTimeMillisecond /* 38 calls */, "Cached SDR queries");
+  test_sequential_function_calls(
+      kHdrQueryParams, SB_ARRAY_SIZE_INT(kHdrQueryParams),
+      5 * kSbTimeMillisecond /* 82 calls */, "Cached HDR queries");
+  test_sequential_function_calls(
+      kDrmQueryParams, SB_ARRAY_SIZE_INT(kDrmQueryParams),
+      5 * kSbTimeMillisecond /* 81 calls */, "Cached DRM queries");
 }
 
 }  // namespace
diff --git a/starboard/nplb/media_can_play_mime_and_key_system_test_helpers.h b/starboard/nplb/media_can_play_mime_and_key_system_test_helpers.h
index 544961b..01f555b 100644
--- a/starboard/nplb/media_can_play_mime_and_key_system_test_helpers.h
+++ b/starboard/nplb/media_can_play_mime_and_key_system_test_helpers.h
@@ -4,7 +4,7 @@
 // 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
+// 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,
@@ -18,344 +18,517 @@
 namespace starboard {
 namespace nplb {
 
+struct SbMediaCanPlayMimeAndKeySystemParam {
+  const char* mime;
+  const char* key_system;
+};
+
+static SbMediaCanPlayMimeAndKeySystemParam kWarmupQueryParams[] = {
+    {"audio/mp4; codecs=\"mp4a.40.2\"", ""},
+    {"audio/webm; codecs=\"opus\"", ""},
+    {"video/webm; codecs=\"avc1.64002a\"", ""},
+    {"video/webm; codecs=\"vp9\"", ""},
+    {"video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\";", ""},
+    {"video/webm; codecs=\"av01.0.09M.08\"", ""},
+    {"video/webm; codecs=\"av01.0.17M.10.0.110.09.16.09.0\"", ""},
+    {"audio/mp4; codecs=\"mp4a.40.2\"", "com.widevine.alpha"},
+    {"video/webm; codecs=\"avc1.64002a\"", "com.widevine.alpha"},
+};
+
 // Query params from https://youtu.be/iXvy8ZeCs5M.
-static const char* kSdrQueryParams[] = {
-    "video/mp4; codecs=\"avc1.42001E\"",
-    "audio/mp4; codecs=\"mp4a.40.2\"",
-    "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"",
-    "video/webm; codecs=\"vp09.02.51.10.01.09.99.99.00\"",
-    "audio/webm; codecs=\"opus\"",
-    "audio/webm; codecs=\"opus\"; channels=2",
-    "audio/webm; codecs=\"opus\"; channels=99",
-    "video/mp4; codecs=av01.0.05M.08",
-    "video/webm; codecs=\"vp9\"",
-    "video/webm; codecs=\"vp9\"; width=640",
-    "video/webm; codecs=\"vp9\"; width=99999",
-    "video/webm; codecs=\"vp9\"",
-    "video/webm; codecs=\"vp9\"; height=360",
-    "video/webm; codecs=\"vp9\"; height=99999",
-    "video/webm; codecs=\"vp9\"",
-    "video/webm; codecs=\"vp9\"; framerate=30",
-    "video/webm; codecs=\"vp9\"; framerate=9999",
-    "video/webm; codecs=\"vp9\"; width=3840; height=2160; bitrate=2000000",
-    "video/webm; codecs=\"vp9\"; width=3840; height=2160; bitrate=20000000",
-    "video/webm; codecs=\"vp9\"",
-    "video/webm; codecs=\"vp9\"; bitrate=300000",
-    "video/webm; codecs=\"vp9\"; bitrate=2000000000",
-    "video/mp4; codecs=\"avc1.4d4015\"; width=426; height=240; framerate=24; "
-    "bitrate=233713",
-    "video/mp4; codecs=\"avc1.4d401e\"; width=640; height=360; framerate=24; "
-    "bitrate=422012",
-    "audio/mp4; codecs=\"mp4a.40.2\"; channels=2",
-    "video/mp4; codecs=\"avc1.4d400c\"; width=256; height=144; framerate=24; "
-    "bitrate=110487",
-    "video/webm; codecs=\"vp9\"",
-    "video/webm; codecs=\"vp9\"; eotf=bt709",
-    "video/webm; codecs=\"vp9\"; eotf=catavision",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=426; "
-    "height=240; framerate=24; bitrate=191916; eotf=bt709",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=640; "
-    "height=360; framerate=24; bitrate=400973; eotf=bt709",
-    "audio/webm; codecs=\"opus\"; channels=2",
-    "audio/webm; codecs=\"opus\"; channels=2",
-    "video/mp4; codecs=\"av01.0.00M.08\"; width=256; height=144; framerate=24; "
-    "bitrate=76146; eotf=bt709",
-    "video/mp4; codecs=\"av01.0.00M.08\"; width=426; height=240; framerate=24; "
-    "bitrate=156234; eotf=bt709",
-    "video/mp4; codecs=\"av01.0.01M.08\"; width=640; height=360; framerate=24; "
-    "bitrate=302046; eotf=bt709",
-    "audio/webm; codecs=\"opus\"",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\""};
+static SbMediaCanPlayMimeAndKeySystemParam kSdrQueryParams[] = {
+    {"video/mp4; codecs=\"avc1.42001E\"", ""},
+    {"audio/mp4; codecs=\"mp4a.40.2\"", ""},
+    {"video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"", ""},
+    {"video/webm; codecs=\"vp09.02.51.10.01.09.99.99.00\"", ""},
+    {"audio/webm; codecs=\"opus\"", ""},
+    {"audio/webm; codecs=\"opus\"; channels=2", ""},
+    {"audio/webm; codecs=\"opus\"; channels=99", ""},
+    {"video/mp4; codecs=av01.0.05M.08", ""},
+    {"video/webm; codecs=\"vp9\"", ""},
+    {"video/webm; codecs=\"vp9\"; width=640", ""},
+    {"video/webm; codecs=\"vp9\"; width=99999", ""},
+    {"video/webm; codecs=\"vp9\"", ""},
+    {"video/webm; codecs=\"vp9\"; height=360", ""},
+    {"video/webm; codecs=\"vp9\"; height=99999", ""},
+    {"video/webm; codecs=\"vp9\"", ""},
+    {"video/webm; codecs=\"vp9\"; framerate=30", ""},
+    {"video/webm; codecs=\"vp9\"; framerate=9999", ""},
+    {"video/webm; codecs=\"vp9\"; width=3840; height=2160; bitrate=2000000",
+     ""},
+    {"video/webm; codecs=\"vp9\"; width=3840; height=2160; bitrate=20000000",
+     ""},
+    {"video/webm; codecs=\"vp9\"", ""},
+    {"video/webm; codecs=\"vp9\"; bitrate=300000", ""},
+    {"video/webm; codecs=\"vp9\"; bitrate=2000000000", ""},
+    {"video/mp4; codecs=\"avc1.4d4015\"; width=426; height=240; framerate=24; "
+     "bitrate=233713",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401e\"; width=640; height=360; framerate=24; "
+     "bitrate=422012",
+     ""},
+    {"audio/mp4; codecs=\"mp4a.40.2\"; channels=2", ""},
+    {"video/mp4; codecs=\"avc1.4d400c\"; width=256; height=144; framerate=24; "
+     "bitrate=110487",
+     ""},
+    {"video/webm; codecs=\"vp9\"", ""},
+    {"video/webm; codecs=\"vp9\"; eotf=bt709", ""},
+    {"video/webm; codecs=\"vp9\"; eotf=catavision", ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=426; "
+     "height=240; framerate=24; bitrate=191916; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=640; "
+     "height=360; framerate=24; bitrate=400973; eotf=bt709",
+     ""},
+    {"audio/webm; codecs=\"opus\"; channels=2", ""},
+    {"audio/webm; codecs=\"opus\"; channels=2", ""},
+    {"video/mp4; codecs=\"av01.0.00M.08\"; width=256; height=144; "
+     "framerate=24; "
+     "bitrate=76146; eotf=bt709",
+     ""},
+    {"video/mp4; codecs=\"av01.0.00M.08\"; width=426; height=240; "
+     "framerate=24; "
+     "bitrate=156234; eotf=bt709",
+     ""},
+    {"video/mp4; codecs=\"av01.0.01M.08\"; width=640; height=360; "
+     "framerate=24; "
+     "bitrate=302046; eotf=bt709",
+     ""},
+    {"audio/webm; codecs=\"opus\"", ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"", ""},
+};
 
 // Query params from https://youtu.be/1La4QzGeaaQ.
-static const char* kHdrQueryParams[] = {
-    "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"",
-    "video/webm; codecs=\"vp09.02.51.10.01.09.99.99.00\"",
-    "audio/webm; codecs=\"opus\"",
-    "audio/webm; codecs=\"opus\"; channels=2",
-    "audio/webm; codecs=\"opus\"; channels=99",
-    "video/mp4; codecs=av01.0.05M.08",
-    "video/mp4; codecs=av99.0.05M.08",
-    "video/webm; codecs=\"vp9\"",
-    "video/webm; codecs=\"vp9\"; height=360",
-    "video/webm; codecs=\"vp9\"; height=99999",
-    "video/webm; codecs=\"vp9\"; width=3840; height=2160; bitrate=2000000",
-    "video/webm; codecs=\"vp9\"",
-    "video/webm; codecs=\"vp9\"; bitrate=300000",
-    "video/webm; codecs=\"vp9\"; bitrate=2000000000",
-    "video/webm; codecs=\"vp9\"",
-    "video/webm; codecs=\"vp9\"; width=640",
-    "video/webm; codecs=\"vp9\"; width=99999",
-    "video/webm; codecs=\"vp9\"",
-    "video/webm; codecs=\"vp9\"; framerate=30",
-    "video/webm; codecs=\"vp9\"; framerate=9999",
-    "video/mp4; codecs=\"avc1.4d4015\"; width=426; height=240; framerate=30; "
-    "bitrate=296736",
-    "video/mp4; codecs=\"avc1.4d401e\"; width=640; height=360; framerate=30; "
-    "bitrate=700126",
-    "video/mp4; codecs=\"avc1.4d401f\"; width=854; height=480; framerate=30; "
-    "bitrate=1357113",
-    "video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=30; "
-    "bitrate=2723992",
-    "audio/mp4; codecs=\"mp4a.40.2\"; channels=2",
-    "video/mp4; codecs=\"avc1.4d400c\"; width=256; height=144; framerate=30; "
-    "bitrate=123753",
-    "video/webm; codecs=\"vp9\"",
-    "video/webm; codecs=\"vp9\"; eotf=bt709",
-    "video/webm; codecs=\"vp9\"; eotf=catavision",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=426; "
-    "height=240; framerate=30; bitrate=202710; eotf=bt709",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=640; "
-    "height=360; framerate=30; bitrate=427339; eotf=bt709",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
-    "height=480; framerate=30; bitrate=782821; eotf=bt709",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
-    "height=720; framerate=30; bitrate=1542503; eotf=bt709",
-    "audio/webm; codecs=\"opus\"; channels=2",
-    "audio/webm; codecs=\"opus\"; channels=2",
-    "video/mp4; codecs=\"avc1.4d4020\"; width=1280; height=720; framerate=60; "
-    "bitrate=3488936",
-    "video/mp4; codecs=\"avc1.64002a\"; width=1920; height=1080; framerate=60; "
-    "bitrate=5833750",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
-    "height=720; framerate=60; bitrate=2676194; eotf=bt709",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
-    "height=1080; framerate=60; bitrate=4461346; eotf=bt709",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=2560; "
-    "height=1440; framerate=60; bitrate=13384663; eotf=bt709",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=3840; "
-    "height=2160; framerate=60; bitrate=26752474; eotf=bt709",
-    "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=256; "
-    "height=144; framerate=60; bitrate=245561",
-    "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=426; "
-    "height=240; framerate=60; bitrate=500223",
-    "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=640; "
-    "height=360; framerate=60; bitrate=1064485",
-    "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=854; "
-    "height=480; framerate=60; bitrate=1998847",
-    "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=1280; "
-    "height=720; framerate=60; bitrate=4556353",
-    "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=1920; "
-    "height=1080; framerate=60; bitrate=6946958",
-    "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=2560; "
-    "height=1440; framerate=60; bitrate=16930005",
-    "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=3840; "
-    "height=2160; framerate=60; bitrate=30184402",
-    "video/mp4; codecs=\"av01.0.00M.10.0.110.09.16.09.0\"; width=256; "
-    "height=144; framerate=30; bitrate=89195; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.00M.10.0.110.09.16.09.0\"; width=426; "
-    "height=240; framerate=30; bitrate=172861; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.01M.10.0.110.09.16.09.0\"; width=640; "
-    "height=360; framerate=30; bitrate=369517; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.04M.10.0.110.09.16.09.0\"; width=854; "
-    "height=480; framerate=30; bitrate=695606; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.08M.10.0.110.09.16.09.0\"; width=1280; "
-    "height=720; framerate=60; bitrate=2017563; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.09M.10.0.110.09.16.09.0\"; width=1920; "
-    "height=1080; framerate=60; bitrate=3755257; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.12M.10.0.110.09.16.09.0\"; width=2560; "
-    "height=1440; framerate=60; bitrate=8546165; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.13M.10.0.110.09.16.09.0\"; width=3840; "
-    "height=2160; framerate=60; bitrate=17537773; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.17M.10.0.110.09.16.09.0\"; width=7680; "
-    "height=4320; framerate=60; bitrate=37270368; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.00M.10.0.110.09.16.09.0\"; width=256; "
-    "height=144; framerate=60; bitrate=193907; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.01M.10.0.110.09.16.09.0\"; width=426; "
-    "height=240; framerate=60; bitrate=400353; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.04M.10.0.110.09.16.09.0\"; width=640; "
-    "height=360; framerate=60; bitrate=817812; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.05M.10.0.110.09.16.09.0\"; width=854; "
-    "height=480; framerate=60; bitrate=1558025; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.08M.10.0.110.09.16.09.0\"; width=1280; "
-    "height=720; framerate=60; bitrate=4167668; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.09M.10.0.110.09.16.09.0\"; width=1920; "
-    "height=1080; framerate=60; bitrate=6870811; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.12M.10.0.110.09.16.09.0\"; width=2560; "
-    "height=1440; framerate=60; bitrate=17316706; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.13M.10.0.110.09.16.09.0\"; width=3840; "
-    "height=2160; framerate=60; bitrate=31942925; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.17M.10.0.110.09.16.09.0\"; width=7680; "
-    "height=4320; framerate=60; bitrate=66038840; eotf=smpte2084",
-    "video/mp4; codecs=\"av01.0.17M.10.0.110.09.16.09.0\"; width=7680; "
-    "height=4320; framerate=60; bitrate=45923436; eotf=smpte2084",
-    "video/mp4; codecs=\"avc1.4d4015\"; width=426; height=240; framerate=24; "
-    "bitrate=160590",
-    "video/mp4; codecs=\"avc1.4d401e\"; width=640; height=360; framerate=24; "
-    "bitrate=255156",
-    "video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
-    "bitrate=490890",
-    "video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
-    "bitrate=1000556",
-    "video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; framerate=24; "
-    "bitrate=1810004",
-    "audio/mp4; codecs=\"mp4a.40.2\"; channels=2",
-    "video/mp4; codecs=\"avc1.4d400c\"; width=256; height=144; framerate=24; "
-    "bitrate=82746",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=426; "
-    "height=240; framerate=24; bitrate=178701; eotf=bt709",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=640; "
-    "height=360; framerate=24; bitrate=371303; eotf=bt709",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
-    "height=480; framerate=24; bitrate=579918; eotf=bt709",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
-    "height=720; framerate=24; bitrate=999223; eotf=bt709",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
-    "height=1080; framerate=24; bitrate=1814623; eotf=bt709",
-    "audio/webm; codecs=\"opus\"; channels=2",
-    "audio/webm; codecs=\"opus\"; channels=2"};
+static SbMediaCanPlayMimeAndKeySystemParam kHdrQueryParams[] = {
+    {"video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"", ""},
+    {"video/webm; codecs=\"vp09.02.51.10.01.09.99.99.00\"", ""},
+    {"audio/webm; codecs=\"opus\"", ""},
+    {"audio/webm; codecs=\"opus\"; channels=2", ""},
+    {"audio/webm; codecs=\"opus\"; channels=99", ""},
+    {"video/mp4; codecs=av01.0.05M.08", ""},
+    {"video/mp4; codecs=av99.0.05M.08", ""},
+    {"video/webm; codecs=\"vp9\"", ""},
+    {"video/webm; codecs=\"vp9\"; height=360", ""},
+    {"video/webm; codecs=\"vp9\"; height=99999", ""},
+    {"video/webm; codecs=\"vp9\"; width=3840; height=2160; bitrate=2000000",
+     ""},
+    {"video/webm; codecs=\"vp9\"", ""},
+    {"video/webm; codecs=\"vp9\"; bitrate=300000", ""},
+    {"video/webm; codecs=\"vp9\"; bitrate=2000000000", ""},
+    {"video/webm; codecs=\"vp9\"", ""},
+    {"video/webm; codecs=\"vp9\"; width=640", ""},
+    {"video/webm; codecs=\"vp9\"; width=99999", ""},
+    {"video/webm; codecs=\"vp9\"", ""},
+    {"video/webm; codecs=\"vp9\"; framerate=30", ""},
+    {"video/webm; codecs=\"vp9\"; framerate=9999", ""},
+    {"video/mp4; codecs=\"avc1.4d4015\"; width=426; height=240; framerate=30; "
+     "bitrate=296736",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401e\"; width=640; height=360; framerate=30; "
+     "bitrate=700126",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401f\"; width=854; height=480; framerate=30; "
+     "bitrate=1357113",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=30; "
+     "bitrate=2723992",
+     ""},
+    {"audio/mp4; codecs=\"mp4a.40.2\"; channels=2", ""},
+    {"video/mp4; codecs=\"avc1.4d400c\"; width=256; height=144; framerate=30; "
+     "bitrate=123753",
+     ""},
+    {"video/webm; codecs=\"vp9\"", ""},
+    {"video/webm; codecs=\"vp9\"; eotf=bt709", ""},
+    {"video/webm; codecs=\"vp9\"; eotf=catavision", ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=426; "
+     "height=240; framerate=30; bitrate=202710; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=640; "
+     "height=360; framerate=30; bitrate=427339; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+     "height=480; framerate=30; bitrate=782821; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+     "height=720; framerate=30; bitrate=1542503; eotf=bt709",
+     ""},
+    {"audio/webm; codecs=\"opus\"; channels=2", ""},
+    {"audio/webm; codecs=\"opus\"; channels=2", ""},
+    {"video/mp4; codecs=\"avc1.4d4020\"; width=1280; height=720; framerate=60; "
+     "bitrate=3488936",
+     ""},
+    {"video/mp4; codecs=\"avc1.64002a\"; width=1920; height=1080; "
+     "framerate=60; "
+     "bitrate=5833750",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+     "height=720; framerate=60; bitrate=2676194; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+     "height=1080; framerate=60; bitrate=4461346; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=2560; "
+     "height=1440; framerate=60; bitrate=13384663; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=3840; "
+     "height=2160; framerate=60; bitrate=26752474; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=256; "
+     "height=144; framerate=60; bitrate=245561",
+     ""},
+    {"video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=426; "
+     "height=240; framerate=60; bitrate=500223",
+     ""},
+    {"video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=640; "
+     "height=360; framerate=60; bitrate=1064485",
+     ""},
+    {"video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=854; "
+     "height=480; framerate=60; bitrate=1998847",
+     ""},
+    {"video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=1280; "
+     "height=720; framerate=60; bitrate=4556353",
+     ""},
+    {"video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=1920; "
+     "height=1080; framerate=60; bitrate=6946958",
+     ""},
+    {"video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=2560; "
+     "height=1440; framerate=60; bitrate=16930005",
+     ""},
+    {"video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=3840; "
+     "height=2160; framerate=60; bitrate=30184402",
+     ""},
+    {"video/mp4; codecs=\"av01.0.00M.10.0.110.09.16.09.0\"; width=256; "
+     "height=144; framerate=30; bitrate=89195; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.00M.10.0.110.09.16.09.0\"; width=426; "
+     "height=240; framerate=30; bitrate=172861; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.01M.10.0.110.09.16.09.0\"; width=640; "
+     "height=360; framerate=30; bitrate=369517; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.04M.10.0.110.09.16.09.0\"; width=854; "
+     "height=480; framerate=30; bitrate=695606; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.08M.10.0.110.09.16.09.0\"; width=1280; "
+     "height=720; framerate=60; bitrate=2017563; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.09M.10.0.110.09.16.09.0\"; width=1920; "
+     "height=1080; framerate=60; bitrate=3755257; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.12M.10.0.110.09.16.09.0\"; width=2560; "
+     "height=1440; framerate=60; bitrate=8546165; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.13M.10.0.110.09.16.09.0\"; width=3840; "
+     "height=2160; framerate=60; bitrate=17537773; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.17M.10.0.110.09.16.09.0\"; width=7680; "
+     "height=4320; framerate=60; bitrate=37270368; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.00M.10.0.110.09.16.09.0\"; width=256; "
+     "height=144; framerate=60; bitrate=193907; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.01M.10.0.110.09.16.09.0\"; width=426; "
+     "height=240; framerate=60; bitrate=400353; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.04M.10.0.110.09.16.09.0\"; width=640; "
+     "height=360; framerate=60; bitrate=817812; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.05M.10.0.110.09.16.09.0\"; width=854; "
+     "height=480; framerate=60; bitrate=1558025; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.08M.10.0.110.09.16.09.0\"; width=1280; "
+     "height=720; framerate=60; bitrate=4167668; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.09M.10.0.110.09.16.09.0\"; width=1920; "
+     "height=1080; framerate=60; bitrate=6870811; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.12M.10.0.110.09.16.09.0\"; width=2560; "
+     "height=1440; framerate=60; bitrate=17316706; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.13M.10.0.110.09.16.09.0\"; width=3840; "
+     "height=2160; framerate=60; bitrate=31942925; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.17M.10.0.110.09.16.09.0\"; width=7680; "
+     "height=4320; framerate=60; bitrate=66038840; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"av01.0.17M.10.0.110.09.16.09.0\"; width=7680; "
+     "height=4320; framerate=60; bitrate=45923436; eotf=smpte2084",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d4015\"; width=426; height=240; framerate=24; "
+     "bitrate=160590",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401e\"; width=640; height=360; framerate=24; "
+     "bitrate=255156",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
+     "bitrate=490890",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
+     "bitrate=1000556",
+     ""},
+    {"video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; "
+     "framerate=24; "
+     "bitrate=1810004",
+     ""},
+    {"audio/mp4; codecs=\"mp4a.40.2\"; channels=2", ""},
+    {"video/mp4; codecs=\"avc1.4d400c\"; width=256; height=144; framerate=24; "
+     "bitrate=82746",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=426; "
+     "height=240; framerate=24; bitrate=178701; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=640; "
+     "height=360; framerate=24; bitrate=371303; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+     "height=480; framerate=24; bitrate=579918; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+     "height=720; framerate=24; bitrate=999223; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+     "height=1080; framerate=24; bitrate=1814623; eotf=bt709",
+     ""},
+    {"audio/webm; codecs=\"opus\"; channels=2", ""},
+    {"audio/webm; codecs=\"opus\"; channels=2", ""},
+};
 
 // Query params from https://youtu.be/1mSzHxMpji0.
-static const char* kDrmQueryParams[] = {
-    "video/mp4; codecs=\"avc1.4d4015\"; width=426; height=240; framerate=24; "
-    "bitrate=281854",
-    "video/mp4; codecs=\"avc1.4d401e\"; width=640; height=360; framerate=24; "
-    "bitrate=637760",
-    "video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
-    "bitrate=1164612",
-    "video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; framerate=24; "
-    "bitrate=4362827",
-    "audio/mp4; codecs=\"mp4a.40.2\"; channels=2",
-    "video/mp4; codecs=\"avc1.4d400c\"; width=256; height=144; framerate=24; "
-    "bitrate=138907",
-    "video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
-    "bitrate=1746306",
-    "video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
-    "bitrate=3473564",
-    "video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
-    "bitrate=3481130",
-    "video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
-    "bitrate=5789806",
-    "video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; framerate=24; "
-    "bitrate=5856175",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
-    "height=480; framerate=24; bitrate=2629046; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
-    "height=720; framerate=24; bitrate=1328071; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
-    "height=1080; framerate=24; bitrate=2375894; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=426; "
-    "height=240; framerate=24; bitrate=229634; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=640; "
-    "height=360; framerate=24; bitrate=324585; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
-    "height=480; framerate=24; bitrate=639196; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
-    "height=480; framerate=24; bitrate=1055128; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "audio/mp4; codecs=\"ec-3\"; channels=6",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
-    "height=720; framerate=24; bitrate=2111149; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
-    "height=720; framerate=24; bitrate=3709033; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
-    "height=1080; framerate=24; bitrate=3679792; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
-    "height=1080; framerate=24; bitrate=5524689; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "audio/mp4; codecs=\"ac-3\"; channels=6",
-    "video/mp4; codecs=\"avc1.4d4015\"; width=426; height=240; framerate=24; "
-    "bitrate=281854",
-    "video/mp4; codecs=\"avc1.4d401e\"; width=640; height=360; framerate=24; "
-    "bitrate=637760",
-    "video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
-    "bitrate=1164612",
-    "video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; framerate=24; "
-    "bitrate=4362827",
-    "audio/mp4; codecs=\"mp4a.40.2\"; channels=2",
-    "video/mp4; codecs=\"avc1.4d400c\"; width=256; height=144; framerate=24; "
-    "bitrate=138907",
-    "video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
-    "bitrate=1746306",
-    "video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
-    "bitrate=3473564",
-    "video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
-    "bitrate=3481130",
-    "video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
-    "bitrate=5789806",
-    "video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; framerate=24; "
-    "bitrate=5856175",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
-    "height=480; framerate=24; bitrate=2629046; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
-    "height=720; framerate=24; bitrate=1328071; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
-    "height=1080; framerate=24; bitrate=2375894; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=426; "
-    "height=240; framerate=24; bitrate=229634; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=640; "
-    "height=360; framerate=24; bitrate=324585; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
-    "height=480; framerate=24; bitrate=639196; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
-    "height=480; framerate=24; bitrate=1055128; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "audio/mp4; codecs=\"ec-3\"; channels=6",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
-    "height=720; framerate=24; bitrate=2111149; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
-    "height=720; framerate=24; bitrate=3709033; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
-    "height=1080; framerate=24; bitrate=3679792; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
-    "height=1080; framerate=24; bitrate=5524689; eotf=bt709; "
-    "cryptoblockformat=subsample",
-    "audio/mp4; codecs=\"ac-3\"; channels=6",
-    "video/mp4; codecs=\"avc1.4d4015\"; width=426; height=240; framerate=24; "
-    "bitrate=149590",
-    "video/mp4; codecs=\"avc1.4d401e\"; width=640; height=360; framerate=24; "
-    "bitrate=261202",
-    "video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
-    "bitrate=368187",
-    "video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
-    "bitrate=676316",
-    "video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; framerate=24; "
-    "bitrate=2691722",
-    "audio/mp4; codecs=\"mp4a.40.2\"; channels=2",
-    "video/mp4; codecs=\"avc1.4d400c\"; width=256; height=144; framerate=24; "
-    "bitrate=84646",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=426; "
-    "height=240; framerate=24; bitrate=192698; eotf=bt709",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=640; "
-    "height=360; framerate=24; bitrate=342403; eotf=bt709",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
-    "height=480; framerate=24; bitrate=514976; eotf=bt709",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
-    "height=720; framerate=24; bitrate=852689; eotf=bt709",
-    "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
-    "height=1080; framerate=24; bitrate=2389269; eotf=bt709",
-    "audio/webm; codecs=\"opus\"; channels=2",
-    "audio/webm; codecs=\"opus\"; channels=2",
-    "video/mp4; codecs=\"av01.0.00M.08\"; width=256; height=144; framerate=24; "
-    "bitrate=74957; eotf=bt709",
-    "video/mp4; codecs=\"av01.0.00M.08\"; width=426; height=240; framerate=24; "
-    "bitrate=148691; eotf=bt709",
-    "video/mp4; codecs=\"av01.0.01M.08\"; width=640; height=360; framerate=24; "
-    "bitrate=305616; eotf=bt709",
-    "video/mp4; codecs=\"av01.0.04M.08\"; width=854; height=480; framerate=24; "
-    "bitrate=577104; eotf=bt709",
-    "video/mp4; codecs=\"av01.0.05M.08\"; width=1280; height=720; "
-    "framerate=24; bitrate=989646; eotf=bt709",
-    "video/mp4; codecs=\"av01.0.08M.08\"; width=1920; height=1080; "
-    "framerate=24; bitrate=1766589; eotf=bt709"};
+static SbMediaCanPlayMimeAndKeySystemParam kDrmQueryParams[] = {
+    {"video/mp4; codecs=\"avc1.4d4015\"; width=426; height=240; framerate=24; "
+     "bitrate=281854",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401e\"; width=640; height=360; framerate=24; "
+     "bitrate=637760",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
+     "bitrate=1164612",
+     ""},
+    {"video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; "
+     "framerate=24; "
+     "bitrate=4362827",
+     ""},
+    {"audio/mp4; codecs=\"mp4a.40.2\"; channels=2", ""},
+    {"video/mp4; codecs=\"avc1.4d400c\"; width=256; height=144; framerate=24; "
+     "bitrate=138907",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
+     "bitrate=1746306",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
+     "bitrate=3473564",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
+     "bitrate=3481130",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
+     "bitrate=5789806",
+     ""},
+    {"video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; "
+     "framerate=24; "
+     "bitrate=5856175",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+     "height=480; framerate=24; bitrate=2629046; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+     "height=720; framerate=24; bitrate=1328071; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+     "height=1080; framerate=24; bitrate=2375894; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=426; "
+     "height=240; framerate=24; bitrate=229634; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=640; "
+     "height=360; framerate=24; bitrate=324585; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+     "height=480; framerate=24; bitrate=639196; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+     "height=480; framerate=24; bitrate=1055128; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"audio/mp4; codecs=\"ec-3\"; channels=6", ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+     "height=720; framerate=24; bitrate=2111149; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+     "height=720; framerate=24; bitrate=3709033; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+     "height=1080; framerate=24; bitrate=3679792; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+     "height=1080; framerate=24; bitrate=5524689; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"audio/mp4; codecs=\"ac-3\"; channels=6", ""},
+    {"video/mp4; codecs=\"avc1.4d4015\"; width=426; height=240; framerate=24; "
+     "bitrate=281854",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401e\"; width=640; height=360; framerate=24; "
+     "bitrate=637760",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
+     "bitrate=1164612",
+     ""},
+    {"video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; "
+     "framerate=24; "
+     "bitrate=4362827",
+     ""},
+    {"audio/mp4; codecs=\"mp4a.40.2\"; channels=2", ""},
+    {"video/mp4; codecs=\"avc1.4d400c\"; width=256; height=144; framerate=24; "
+     "bitrate=138907",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
+     "bitrate=1746306",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
+     "bitrate=3473564",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
+     "bitrate=3481130",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
+     "bitrate=5789806",
+     ""},
+    {"video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; "
+     "framerate=24; "
+     "bitrate=5856175",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+     "height=480; framerate=24; bitrate=2629046; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+     "height=720; framerate=24; bitrate=1328071; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+     "height=1080; framerate=24; bitrate=2375894; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=426; "
+     "height=240; framerate=24; bitrate=229634; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=640; "
+     "height=360; framerate=24; bitrate=324585; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+     "height=480; framerate=24; bitrate=639196; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+     "height=480; framerate=24; bitrate=1055128; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"audio/mp4; codecs=\"ec-3\"; channels=6", ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+     "height=720; framerate=24; bitrate=2111149; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+     "height=720; framerate=24; bitrate=3709033; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+     "height=1080; framerate=24; bitrate=3679792; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+     "height=1080; framerate=24; bitrate=5524689; eotf=bt709; "
+     "cryptoblockformat=subsample",
+     ""},
+    {"audio/mp4; codecs=\"ac-3\"; channels=6", ""},
+    {"video/mp4; codecs=\"avc1.4d4015\"; width=426; height=240; framerate=24; "
+     "bitrate=149590",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401e\"; width=640; height=360; framerate=24; "
+     "bitrate=261202",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
+     "bitrate=368187",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
+     "bitrate=676316",
+     ""},
+    {"video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; "
+     "framerate=24; "
+     "bitrate=2691722",
+     ""},
+    {"audio/mp4; codecs=\"mp4a.40.2\"; channels=2", ""},
+    {"video/mp4; codecs=\"avc1.4d400c\"; width=256; height=144; framerate=24; "
+     "bitrate=84646",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=426; "
+     "height=240; framerate=24; bitrate=192698; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=640; "
+     "height=360; framerate=24; bitrate=342403; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+     "height=480; framerate=24; bitrate=514976; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+     "height=720; framerate=24; bitrate=852689; eotf=bt709",
+     ""},
+    {"video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+     "height=1080; framerate=24; bitrate=2389269; eotf=bt709",
+     ""},
+    {"audio/webm; codecs=\"opus\"; channels=2", ""},
+    {"audio/webm; codecs=\"opus\"; channels=2", ""},
+    {"video/mp4; codecs=\"av01.0.00M.08\"; width=256; height=144; "
+     "framerate=24; "
+     "bitrate=74957; eotf=bt709",
+     ""},
+    {"video/mp4; codecs=\"av01.0.00M.08\"; width=426; height=240; "
+     "framerate=24; "
+     "bitrate=148691; eotf=bt709",
+     ""},
+    {"video/mp4; codecs=\"av01.0.01M.08\"; width=640; height=360; "
+     "framerate=24; "
+     "bitrate=305616; eotf=bt709",
+     ""},
+    {"video/mp4; codecs=\"av01.0.04M.08\"; width=854; height=480; "
+     "framerate=24; "
+     "bitrate=577104; eotf=bt709",
+     ""},
+    {"video/mp4; codecs=\"av01.0.05M.08\"; width=1280; height=720; "
+     "framerate=24; bitrate=989646; eotf=bt709",
+     ""},
+    {"video/mp4; codecs=\"av01.0.08M.08\"; width=1920; height=1080; "
+     "framerate=24; bitrate=1766589; eotf=bt709",
+     ""},
+    {"video/mp4; codecs=\"avc1.4d4015\"", "com.widevine.alpha"},
+    {"video/mp4; codecs=\"avc1.4d4015\"", "com.widevine.alpha"},
+    {"video/mp4; codecs=\"avc1.4d401e\"", "com.widevine.alpha"},
+    {"video/mp4; codecs=\"avc1.4d401e\"", "com.widevine.alpha"},
+    {"video/mp4; codecs=\"avc1.4d401f\"", "com.widevine.alpha"},
+    {"video/mp4; codecs=\"avc1.4d401f\"", "com.widevine.alpha"},
+    {"video/mp4; codecs=\"avc1.640028\"", "com.widevine.alpha"},
+    {"video/mp4; codecs=\"avc1.640028\"", "com.widevine.alpha"},
+    {"video/mp4; codecs=\"avc1.4d400c\"", "com.widevine.alpha"},
+    {"video/mp4; codecs=\"avc1.4d400c\"", "com.widevine.alpha"},
+    {"video/mp4; codecs=\"vp09.00.51.08.01.01.01.01.00\"",
+     "com.widevine.alpha"},
+    {"video/mp4; codecs=\"vp09.00.51.08.01.01.01.01.00\"",
+     "com.widevine.alpha"},
+    {"video/mp4; codecs=\"audio/mp4; codecs=\"mp4a.40.2\"",
+     "com.widevine.alpha"},
+};
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/starboard/shared/ffmpeg/BUILD.gn b/starboard/shared/ffmpeg/BUILD.gn
index b55636d..21316bb 100644
--- a/starboard/shared/ffmpeg/BUILD.gn
+++ b/starboard/shared/ffmpeg/BUILD.gn
@@ -12,6 +12,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+declare_args() {
+  # Whether or not to enable the ffmpeg_demuxer_test target. The target will
+  # only work if ffmpeg 58.35.100 is installed on the build machine.
+  #
+  # TODO(b/239961799): Rework the test to run regardless of the installed
+  # ffmpeg version.
+  enable_ffmpeg_demuxer_test = false
+}
+
 ffmpeg_specialization_sources = [
   "ffmpeg_audio_decoder_impl.cc",
   "ffmpeg_audio_decoder_impl.h",
@@ -97,27 +106,29 @@
   public_configs = [ "//starboard/build/config:starboard_implementation" ]
 }
 
-target(gtest_target_type, "ffmpeg_demuxer_test") {
-  testonly = true
-  configs += [ "//starboard/build/config:starboard_implementation" ]
-  sources = ffmpeg_specialization_sources + [
-              "ffmpeg_demuxer.h",
-              "ffmpeg_demuxer.cc",
-              "ffmpeg_demuxer_test.cc",
-            ]
+if (enable_ffmpeg_demuxer_test) {
+  target(gtest_target_type, "ffmpeg_demuxer_test") {
+    testonly = true
+    configs += [ "//starboard/build/config:starboard_implementation" ]
+    sources = ffmpeg_specialization_sources + [
+                "ffmpeg_demuxer.h",
+                "ffmpeg_demuxer.cc",
+                "ffmpeg_demuxer_test.cc",
+              ]
 
-  # Build only against one specified version of the ffmpeg includes. That means
-  # that this binary will only work well when run on a machine with the given
-  # version of ffmpeg installed. This test binary actually should have
-  # specializations for all supported ffmpeg versions, or it should only test
-  # the behavior of the abstraction layer without testing implementation
-  # details.
-  include_dirs = [ "//third_party/ffmpeg_includes/ffmpeg.58.35.100" ]
-  deps = [
-    "//cobalt/test:run_all_unittests",
-    "//starboard",
-    "//starboard/common",
-    "//testing/gmock",
-    "//testing/gtest",
-  ]
+    # Build only against one specified version of the ffmpeg includes. That means
+    # that this binary will only work well when run on a machine with the given
+    # version of ffmpeg installed. This test binary actually should have
+    # specializations for all supported ffmpeg versions, or it should only test
+    # the behavior of the abstraction layer without testing implementation
+    # details.
+    include_dirs = [ "//third_party/ffmpeg_includes/ffmpeg.58.35.100" ]
+    deps = [
+      "//cobalt/test:run_all_unittests",
+      "//starboard",
+      "//starboard/common",
+      "//testing/gmock",
+      "//testing/gtest",
+    ]
+  }
 }
diff --git a/starboard/shared/ffmpeg/ffmpeg_common.h b/starboard/shared/ffmpeg/ffmpeg_common.h
index c10bbbd..9d4b499 100644
--- a/starboard/shared/ffmpeg/ffmpeg_common.h
+++ b/starboard/shared/ffmpeg/ffmpeg_common.h
@@ -32,6 +32,14 @@
 #define LIBAVUTIL_VERSION_52_8 AV_VERSION_INT(52, 8, 0)
 #endif
 
+#ifndef LIBAVCODEC_VERSION_57_100
+#define LIBAVCODEC_VERSION_57_100 AV_VERSION_INT(57, 100, 0)
+#endif
+
+#ifndef LIBAVFORMAT_VERSION_57_83
+#define LIBAVFORMAT_VERSION_57_83 AV_VERSION_INT(57, 83, 0)
+#endif
+
 #if !defined(LIBAVUTIL_VERSION_MAJOR)
 #error "LIBAVUTIL_VERSION_MAJOR not defined"
 #endif  // !defined(LIBAVUTIL_VERSION_MAJOR)
diff --git a/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc
index 6b8455e..b95d777 100644
--- a/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc
@@ -18,6 +18,7 @@
 #include <cassert>
 #include <cmath>
 #include <cstdint>
+#include <cstring>
 #include <deque>
 #include <functional>
 #include <iostream>
@@ -305,10 +306,14 @@
 
 CobaltExtensionDemuxerEncryptionScheme GetEncryptionScheme(
     const AVStream& stream) {
+#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
   return FFmpegDemuxer::GetDispatch()->av_dict_get(
              stream.metadata, "enc_key_id", nullptr, 0) == nullptr
              ? kCobaltExtensionDemuxerEncryptionSchemeUnencrypted
              : kCobaltExtensionDemuxerEncryptionSchemeCenc;
+#else
+  return kCobaltExtensionDemuxerEncryptionSchemeUnencrypted;
+#endif  // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
 }
 
 int64_t ExtractStartTime(AVStream* stream) {
@@ -318,11 +323,11 @@
         ConvertFromTimeBaseToMicros(stream->time_base, stream->start_time);
   }
 
-#if LIBAVCODEC_LIBRARY_IS_FFMPEG
+#if LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83
   const int32_t codec_id = stream->codecpar->codec_id;
 #else
   const int32_t codec_id = stream->codec->codec_id;
-#endif
+#endif  // LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83
 
   if (stream->first_dts != kNoFFmpegTimestamp
 #if FFMPEG >= 560
@@ -363,6 +368,7 @@
       input_formats.cbegin(), input_formats.cend(),
       +[](const std::string& format) -> bool { return format == "webm"; });
 
+#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
   if (is_webm) {
     const AVDictionaryEntry* entry = FFmpegDemuxer::GetDispatch()->av_dict_get(
         format_context->metadata, "creation_time", nullptr, 0);
@@ -372,6 +378,7 @@
     // is harder than it sounds in pure C++.
     return 0;
   }
+#endif  // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
   return 0;
 }
 
@@ -406,7 +413,12 @@
     return;
   }
   auto* packet = static_cast<AVPacket*>(ptr);
+#if LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100
   GetDispatch()->av_packet_free(&packet);
+#else
+  GetDispatch()->av_free_packet(packet);
+  GetDispatch()->av_free(packet);
+#endif  // LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100
 }
 
 FFmpegDemuxerImpl<FFMPEG>::FFmpegDemuxerImpl(
@@ -467,7 +479,7 @@
   // stream present.
   for (int i = 0; i < format_context_->nb_streams; ++i) {
     AVStream* stream = format_context_->streams[i];
-#if LIBAVCODEC_LIBRARY_IS_FFMPEG
+#if LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83
     const AVCodecParameters* codec_parameters = stream->codecpar;
     const AVMediaType codec_type = codec_parameters->codec_type;
     const AVCodecID codec_id = codec_parameters->codec_id;
@@ -475,7 +487,7 @@
     const AVCodecContext* codec = stream->codec;
     const AVMediaType codec_type = codec->codec_type;
     const AVCodecID codec_id = codec->codec_id;
-#endif
+#endif  // LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83
     // Skip streams which are not properly detected.
     if (codec_id == AV_CODEC_ID_NONE) {
       stream->discard = AVDISCARD_ALL;
@@ -590,9 +602,8 @@
   buffer.data = packet->data;
   buffer.data_size = packet->size;
 
-// The supported libav libraries don't define
-// AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL.
-#if LIBAVCODEC_LIBRARY_IS_FFMPEG
+// Only newer versions support AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL.
+#if LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100
   std::vector<CobaltExtensionDemuxerSideData> side_data;
   for (int i = 0; i < packet->side_data_elems; ++i) {
     const AVPacketSideData& packet_side_data = packet->side_data[i];
@@ -613,7 +624,7 @@
     buffer.side_data = side_data.data();
     buffer.side_data_elements = side_data.size();
   }
-#endif  // LIBAVCODEC_LIBRARY_IS_FFMPEG
+#endif  // LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100
 
   read_cb(&buffer, read_cb_user_data);
 }
@@ -633,6 +644,23 @@
   return kCobaltExtensionDemuxerOk;
 }
 
+FFmpegDemuxerImpl<FFMPEG>::ScopedAVPacket
+FFmpegDemuxerImpl<FFMPEG>::CreateScopedAVPacket() {
+  ScopedAVPacket packet;
+
+#if LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100
+  packet.reset(GetDispatch()->av_packet_alloc());
+#else
+  // av_packet_alloc is not available.
+  packet.reset(
+      static_cast<AVPacket*>(GetDispatch()->av_malloc(sizeof(AVPacket))));
+  memset(packet.get(), 0, sizeof(AVPacket));
+  GetDispatch()->av_init_packet(packet.get());
+#endif  // LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100
+
+  return packet;
+}
+
 // Returns the next packet of type |type|, or nullptr if EoS has been reached
 // or an error was encountered.
 FFmpegDemuxerImpl<FFMPEG>::ScopedAVPacket FFmpegDemuxerImpl<
@@ -640,13 +668,14 @@
   // Handle the simple case: if we already have a packet buffered, just return
   // it.
   ScopedAVPacket packet = GetBufferedPacket(type);
-  if (packet)
+  if (packet) {
     return packet;
+  }
 
   // Read another packet from FFmpeg. We may have to discard a packet if it's
   // not from the right stream. Additionally, if we hit end-of-file or an
   // error, we need to return null.
-  packet.reset(GetDispatch()->av_packet_alloc());
+  packet = CreateScopedAVPacket();
   while (true) {
     int result = GetDispatch()->av_read_frame(format_context_, packet.get());
     if (result < 0) {
@@ -665,7 +694,7 @@
       // The caller doesn't need a video packet; just buffer it and allocate a
       // new packet.
       BufferPacket(std::move(packet), kCobaltExtensionDemuxerStreamTypeVideo);
-      packet.reset(GetDispatch()->av_packet_alloc());
+      packet = CreateScopedAVPacket();
       continue;
     } else if (audio_stream_ && packet->stream_index == audio_stream_->index) {
       if (type == kCobaltExtensionDemuxerStreamTypeAudio) {
@@ -676,13 +705,17 @@
       // The caller doesn't need an audio packet; just buffer it and allocate
       // a new packet.
       BufferPacket(std::move(packet), kCobaltExtensionDemuxerStreamTypeAudio);
-      packet.reset(GetDispatch()->av_packet_alloc());
+      packet = CreateScopedAVPacket();
       continue;
     }
 
-    // This is a packet for a stream we don't care about. Unref it and keep
-    // searching.
+// This is a packet for a stream we don't care about. Unref it (clear the
+// fields) and keep searching.
+#if LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100
     GetDispatch()->av_packet_unref(packet.get());
+#else
+    GetDispatch()->av_free_packet(packet.get());
+#endif  // LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100
   }
 
   SB_NOTREACHED();
@@ -730,7 +763,7 @@
 
   config->encryption_scheme = GetEncryptionScheme(*audio_stream);
 
-#if LIBAVCODEC_LIBRARY_IS_FFMPEG
+#if LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83
   std::unique_ptr<AVCodecContext, ScopedPtrAVFreeContext> codec_context(
       GetDispatch()->avcodec_alloc_context3(nullptr));
   if (!codec_context) {
@@ -741,9 +774,9 @@
           codec_context.get(), audio_stream->codecpar) < 0) {
     return false;
   }
-#else   // LIBAVCODEC_LIBRARY_IS_FFMPEG
+#else
   AVCodecContext* codec_context = audio_stream->codec;
-#endif  // LIBAVCODEC_LIBRARY_IS_FFMPEG
+#endif  // LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83
 
   config->codec = AvCodecIdToAudioCodec(codec_context->codec_id);
   config->sample_format =
@@ -788,7 +821,7 @@
 bool FFmpegDemuxerImpl<FFMPEG>::ParseVideoConfig(
     AVStream* video_stream,
     CobaltExtensionDemuxerVideoDecoderConfig* config) {
-#if LIBAVCODEC_LIBRARY_IS_FFMPEG
+#if LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83
   std::unique_ptr<AVCodecContext, ScopedPtrAVFreeContext> codec_context(
       GetDispatch()->avcodec_alloc_context3(nullptr));
 
@@ -802,7 +835,7 @@
   }
 #else
   AVCodecContext* codec_context = video_stream->codec;
-#endif
+#endif  // LIBAVFORMAT_VERSION_INT >= LIBAVFORMAT_VERSION_57_83
 
   config->visible_rect_x = 0;
   config->visible_rect_y = 0;
diff --git a/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.h b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.h
index 25f7308..cc609c5 100644
--- a/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.h
+++ b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.h
@@ -81,6 +81,12 @@
 
   explicit FFmpegDemuxerImpl(CobaltExtensionDemuxerDataSource* data_source);
 
+  // Creates an empty ScopedAVPacket. The returned ScopedAVPacket will not be
+  // null.
+  // Since different versions of FFmpeg require different functions to create an
+  // AVPacket, this function abstracts away those differences.
+  ScopedAVPacket CreateScopedAVPacket();
+
   // Returns the next packet of type |type|, or nullptr if EoS has been reached
   // or an error was encountered.
   ScopedAVPacket GetNextPacket(CobaltExtensionDemuxerStreamType type);
diff --git a/starboard/shared/ffmpeg/ffmpeg_dispatch.h b/starboard/shared/ffmpeg/ffmpeg_dispatch.h
index 1b46ff6..347566b 100644
--- a/starboard/shared/ffmpeg/ffmpeg_dispatch.h
+++ b/starboard/shared/ffmpeg/ffmpeg_dispatch.h
@@ -114,6 +114,7 @@
   void (*av_free)(void* ptr);
   AVPacket* (*av_packet_alloc)(void);
   void (*av_packet_free)(AVPacket** pkt);
+  void (*av_free_packet)(AVPacket* pkt);
   AVDictionaryEntry* (*av_dict_get)(const AVDictionary* m,
                                     const char* key,
                                     const AVDictionaryEntry* prev,
diff --git a/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc b/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc
index 748d897..6fc39d1 100644
--- a/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc
@@ -262,10 +262,10 @@
   INITSYMBOL(avutil_, av_freep);
   INITSYMBOL(avutil_, av_frame_alloc);
   INITSYMBOL(avutil_, av_free);
-  INITSYMBOL(avutil_, av_dict_get);
   INITSYMBOL(avutil_, av_rescale_rnd);
 #if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
   INITSYMBOL(avutil_, av_frame_free);
+  INITSYMBOL(avutil_, av_dict_get);
 #endif  // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
   INITSYMBOL(avutil_, av_frame_unref);
   INITSYMBOL(avutil_, av_samples_get_buffer_size);
@@ -294,6 +294,7 @@
   INITSYMBOL(avcodec_, av_packet_free);
   INITSYMBOL(avcodec_, av_packet_unref);
   INITSYMBOL(avcodec_, avcodec_parameters_to_context);
+  INITSYMBOL(avcodec_, av_free_packet);
 
   // Load symbols from the avformat shared library.
   INITSYMBOL(avformat_, avformat_version);
diff --git a/starboard/shared/ffmpeg/ffmpeg_linked_dispatch_impl.cc b/starboard/shared/ffmpeg/ffmpeg_linked_dispatch_impl.cc
index 20d7d1b..c3d2f52 100644
--- a/starboard/shared/ffmpeg/ffmpeg_linked_dispatch_impl.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_linked_dispatch_impl.cc
@@ -53,10 +53,13 @@
   SB_DCHECK(ffmpeg->avutil_version);
   INITSYMBOL(av_malloc);
   INITSYMBOL(av_freep);
+  INITSYMBOL(av_free);
+  INITSYMBOL(av_rescale_rnd);
 #if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
   INITSYMBOL(av_frame_alloc);
   INITSYMBOL(av_frame_free);
   INITSYMBOL(av_frame_unref);
+  INITSYMBOL(av_dict_get);
 #endif  // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
   INITSYMBOL(av_samples_get_buffer_size);
   INITSYMBOL(av_opt_set_int);
@@ -83,6 +86,22 @@
 #endif  // LIBAVUTIL_VERSION_INT > LIBAVUTIL_VERSION_52_8
   INITSYMBOL(avcodec_align_dimensions2);
 
+#if LIBAVCODEC_LIBRARY_IS_FFMPEG
+  INITSYMBOL(av_packet_alloc);
+  INITSYMBOL(av_packet_free);
+  INITSYMBOL(av_packet_unref);
+  INITSYMBOL(avcodec_parameters_to_context);
+#endif  // LIBAVCODEC_LIBRARY_IS_FFMPEG
+  INITSYMBOL(av_free_packet);
+
+  INITSYMBOL(av_read_frame);
+  INITSYMBOL(av_seek_frame);
+  INITSYMBOL(avformat_open_input);
+  INITSYMBOL(avformat_close_input);
+  INITSYMBOL(avformat_alloc_context);
+  INITSYMBOL(avformat_find_stream_info);
+  INITSYMBOL(avio_alloc_context);
+
   // Load symbols from the avformat shared library.
   INITSYMBOL(avformat_version);
   SB_DCHECK(ffmpeg->avformat_version);
diff --git a/starboard/shared/starboard/player/filter/stub_video_decoder.cc b/starboard/shared/starboard/player/filter/stub_video_decoder.cc
index 1deb809..359b48c 100644
--- a/starboard/shared/starboard/player/filter/stub_video_decoder.cc
+++ b/starboard/shared/starboard/player/filter/stub_video_decoder.cc
@@ -14,7 +14,10 @@
 
 #include "starboard/shared/starboard/player/filter/stub_video_decoder.h"
 
+#include <string>
+
 #include "starboard/common/media.h"
+#include "starboard/shared/starboard/player/filter/cpu_video_frame.h"
 
 namespace starboard {
 namespace shared {
@@ -102,7 +105,7 @@
 
   output_frame_timestamps_.insert(input_buffer->timestamp());
   if (output_frame_timestamps_.size() > kMaxFramesToDelay) {
-    output_frame = new VideoFrame(*output_frame_timestamps_.begin());
+    output_frame = CreateOutputFrame(*output_frame_timestamps_.begin());
     output_frame_timestamps_.erase(output_frame_timestamps_.begin());
   }
 
@@ -122,12 +125,32 @@
   // If there are any remaining frames we need to output, send them all out
   // before writing EOS.
   for (const auto time : output_frame_timestamps_) {
-    scoped_refptr<VideoFrame> output_frame = new VideoFrame(time);
+    scoped_refptr<VideoFrame> output_frame = CreateOutputFrame(time);
     decoder_status_cb_(kBufferFull, output_frame);
   }
   decoder_status_cb_(kBufferFull, VideoFrame::CreateEOSFrame());
 }
 
+scoped_refptr<VideoFrame> StubVideoDecoder::CreateOutputFrame(
+    SbTime timestamp) const {
+  int bits_per_channel = video_sample_info_.color_metadata.bits_per_channel;
+  if (bits_per_channel == 0) {
+    // Assume 8 bits when |bits_per_channel| is unknown (0).
+    bits_per_channel = 8;
+  }
+  int uv_stride = bits_per_channel > 8 ? video_sample_info_.frame_width
+                                       : video_sample_info_.frame_width / 2;
+  int y_stride = uv_stride * 2;
+  std::string data(y_stride * video_sample_info_.frame_height, 0);
+
+  return CpuVideoFrame::CreateYV12Frame(
+      bits_per_channel, video_sample_info_.frame_width,
+      video_sample_info_.frame_height, y_stride, uv_stride, timestamp,
+      reinterpret_cast<const uint8_t*>(data.data()),
+      reinterpret_cast<const uint8_t*>(data.data()),
+      reinterpret_cast<const uint8_t*>(data.data()));
+}
+
 }  // namespace filter
 }  // namespace player
 }  // namespace starboard
diff --git a/starboard/shared/starboard/player/filter/stub_video_decoder.h b/starboard/shared/starboard/player/filter/stub_video_decoder.h
index 670841c..0199274 100644
--- a/starboard/shared/starboard/player/filter/stub_video_decoder.h
+++ b/starboard/shared/starboard/player/filter/stub_video_decoder.h
@@ -52,6 +52,8 @@
   void DecodeOneBuffer(const scoped_refptr<InputBuffer>& input_buffer);
   void DecodeEndOfStream();
 
+  scoped_refptr<VideoFrame> CreateOutputFrame(SbTime timestamp) const;
+
   DecoderStatusCB decoder_status_cb_;
   media::VideoSampleInfo video_sample_info_;
 
diff --git a/starboard/shared/x11/application_x11.cc b/starboard/shared/x11/application_x11.cc
index a91bab6..a650884 100644
--- a/starboard/shared/x11/application_x11.cc
+++ b/starboard/shared/x11/application_x11.cc
@@ -18,12 +18,12 @@
 #include <stdlib.h>
 #include <unistd.h>
 #define XK_3270  // for XK_3270_BackTab
-#include <X11/keysym.h>
-#include <X11/Xatom.h>
 #include <X11/XF86keysym.h>
 #include <X11/XKBlib.h>
+#include <X11/Xatom.h>
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
+#include <X11/keysym.h>
 
 #include <algorithm>
 #include <iomanip>
@@ -789,8 +789,8 @@
             continue;
           }
           if (cpu_video_frame->format() != CpuVideoFrame::kBGRA32) {
-            cpu_video_frame = cpu_video_frame->ConvertTo(
-                CpuVideoFrame::kBGRA32);
+            cpu_video_frame =
+                cpu_video_frame->ConvertTo(CpuVideoFrame::kBGRA32);
           }
           current_video_frames_[player] = cpu_video_frame;
         }
@@ -852,7 +852,11 @@
 }
 
 void ApplicationX11::PlayerSetBounds(SbPlayer player,
-    int z_index, int x, int y, int width, int height) {
+                                     int z_index,
+                                     int x,
+                                     int y,
+                                     int width,
+                                     int height) {
   ScopedLock lock(frame_mutex_);
 
   bool player_exists =
diff --git a/third_party/web_platform_tests/tools/html5lib/html5lib/trie/_base.py b/third_party/web_platform_tests/tools/html5lib/html5lib/trie/_base.py
index 724486b..5f724b1 100644
--- a/third_party/web_platform_tests/tools/html5lib/html5lib/trie/_base.py
+++ b/third_party/web_platform_tests/tools/html5lib/html5lib/trie/_base.py
@@ -1,6 +1,6 @@
 from __future__ import absolute_import, division, unicode_literals
 
-from collections import Mapping
+from collections.abc import Mapping
 
 
 class Trie(Mapping):