Import Cobalt 22.lts.1.303143
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index ba3c984..6f0d01a 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -591,6 +591,9 @@
 ssize_t Application::available_memory_ = 0;
 int64 Application::lifetime_in_ms_ = 0;
 
+Application::AppStatus Application::app_status_ =
+    Application::kUninitializedAppStatus;
+
 Application::Application(const base::Closure& quit_closure, bool should_preload,
                          SbTimeMonotonic timestamp)
     : message_loop_(base::MessageLoop::current()),
@@ -857,6 +860,8 @@
                                                          timestamp);
   UpdateUserAgent();
 
+  app_status_ = (should_preload ? kConcealedAppStatus : kRunningAppStatus);
+
   // Register event callbacks.
   window_size_change_event_callback_ = base::Bind(
       &Application::OnWindowSizeChangedEvent, base::Unretained(this));
@@ -1004,6 +1009,8 @@
       base::DateTimeConfigurationChangedEvent::TypeId(),
       on_date_time_configuration_changed_event_callback_);
 #endif
+
+  app_status_ = kShutDownAppStatus;
 }
 
 void Application::Start(SbTimeMonotonic timestamp) {
@@ -1027,6 +1034,7 @@
   }
 
   quit_closure_.Run();
+  app_status_ = kQuitAppStatus;
 }
 
 void Application::HandleStarboardEvent(const SbEvent* starboard_event) {
diff --git a/src/cobalt/browser/application.h b/src/cobalt/browser/application.h
index 1fa07da..52e2a94 100644
--- a/src/cobalt/browser/application.h
+++ b/src/cobalt/browser/application.h
@@ -203,6 +203,8 @@
   static ssize_t available_memory_;
   static int64 lifetime_in_ms_;
 
+  static AppStatus app_status_;
+
   CValStats c_val_stats_;
 
   base::RepeatingTimer stats_update_timer_;
diff --git a/src/cobalt/browser/browser_bindings_gen.gyp b/src/cobalt/browser/browser_bindings_gen.gyp
index 9941d47..f9540eb 100644
--- a/src/cobalt/browser/browser_bindings_gen.gyp
+++ b/src/cobalt/browser/browser_bindings_gen.gyp
@@ -135,8 +135,6 @@
         '../dom/performance.idl',
         '../dom/performance_entry.idl',
         '../dom/performance_lifecycle_timing.idl',
-        '../dom/performance_mark.idl',
-        '../dom/performance_measure.idl',
         '../dom/performance_observer.idl',
         '../dom/performance_observer_entry_list.idl',
         '../dom/performance_resource_timing.idl',
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 912bb2b..9dfa8f2 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-303120
\ No newline at end of file
+303143
\ No newline at end of file
diff --git a/src/cobalt/dom/dom.gyp b/src/cobalt/dom/dom.gyp
index 4039e2c..7cb5cb8 100644
--- a/src/cobalt/dom/dom.gyp
+++ b/src/cobalt/dom/dom.gyp
@@ -264,10 +264,6 @@
         'performance_entry_list_impl.h',
         'performance_lifecycle_timing.cc',
         'performance_lifecycle_timing.h',
-        'performance_mark.cc',
-        'performance_mark.h',
-        'performance_measure.cc',
-        'performance_measure.h',
         'performance_observer.cc',
         'performance_observer.h',
         'performance_observer_entry_list.cc',
diff --git a/src/cobalt/dom/performance.cc b/src/cobalt/dom/performance.cc
index a90c2e8..f7f79b9 100644
--- a/src/cobalt/dom/performance.cc
+++ b/src/cobalt/dom/performance.cc
@@ -14,61 +14,26 @@
 
 #include "cobalt/dom/performance.h"
 
-#include <string>
-
-#include "base/time/time.h"
 #include "base/time/default_clock.h"
+#include "base/time/time.h"
 #include "cobalt/browser/stack_size_constants.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/memory_info.h"
 #include "cobalt/dom/performance_entry.h"
-#include "cobalt/dom/performance_mark.h"
-#include "cobalt/dom/performance_measure.h"
 
 namespace cobalt {
 namespace dom {
 
 namespace {
 
-base::TimeDelta GetUnixAtZeroMonotonic(const base::Clock* clock,
+  base::TimeDelta GetUnixAtZeroMonotonic(const base::Clock* clock,
                                          const base::TickClock* tick_clock) {
   base::TimeDelta unix_time_now = clock->Now() - base::Time::UnixEpoch();
   base::TimeDelta time_since_origin = tick_clock->NowTicks().since_origin();
   return unix_time_now - time_since_origin;
 }
 
-bool IsNamePerformanceTimingAttribute(const std::string& name) {
-  return name == "navigationStart";
 }
 
-DOMHighResTimeStamp ConvertNameToTimestamp(
-    const std::string& name, script::ExceptionState* exception_state) {
-  // The algorithm of ConvertNameToTimestamp() follows these steps:
-  //   https://www.w3.org/TR/user-timing/#convert-a-name-to-a-timestamp
-  // 1. If the global object is not a Window object, throw a SyntaxError.
-  // 2. If name is navigationStart, return 0.
-  if (name == "navigationStart") {
-    return 0.0;
-  }
-
-  // 3. Let startTime be the value of navigationStart in the PerformanceTiming
-  // interface.
-  // 4. Let endTime be the value of name in the PerformanceTiming interface.
-  // 5. If endTime is 0, throw an InvalidAccessError.
-  // 6. Return result of subtracting startTime from endTime.
-
-  // Note that we only support navigationStart in the PerformanceTiming
-  // interface. We return 0.0 instead of the result of subtracting
-  // startTime from endTime.
-  dom::DOMException::Raise(dom::DOMException::kSyntaxErr,
-                           "Cannot convert a name that is not a public "
-                           "attribute of PerformanceTiming to a timestamp",
-                           exception_state);
-  return 0.0;
-}
-
-}  //namespace
-
 Performance::Performance(script::EnvironmentSettings* settings,
                          const scoped_refptr<base::BasicClock>& clock)
     : EventTarget(settings),
@@ -141,163 +106,6 @@
       Performance::kPerformanceTimerMinResolutionInMicroseconds);
 }
 
-void Performance::Mark(const std::string& mark_name,
-                       script::ExceptionState* exception_state) {
-  // The algorithm for mark() follows these steps:
-  //   https://www.w3.org/TR/2019/REC-user-timing-2-20190226/#mark-method
-  // 1. If the global object is a Window object and markName uses the same name
-  // as a read only attribute in the PerformanceTiming interface, throw a
-  // SyntaxError.
-  if (IsNamePerformanceTimingAttribute(mark_name)) {
-    dom::DOMException::Raise(
-        dom::DOMException::kSyntaxErr,
-        "Cannot create a mark with the same name as a read-only attribute in "
-        "the PerformanceTiming interface",
-        exception_state);
-  }
-
-  // 2. Create a new PerformanceMark object (entry).
-  // 3. Set entry's name attribute to markName.
-  // 4. Set entry's entryType attribute to DOMString "mark".
-  // 5. Set entry's startTime attribute to the value that would be returned by
-  // the Performance object's now() method.
-  // 6. Set entry's duration attribute to 0.
-  scoped_refptr<PerformanceMark> entry =
-      base::MakeRefCounted<PerformanceMark>(mark_name, Now());
-
-  // 7. Queue entry.
-  QueuePerformanceEntry(entry);
-
-  // 8. Add entry to the performance entry buffer.
-  performance_entry_buffer_.push_back(entry);
-
-  // 9. Return undefined
-}
-
-void Performance::ClearMarks(const std::string& mark_name) {
-  // The algorithm for clearMarks follows these steps:
-  //   https://www.w3.org/TR/2019/REC-user-timing-2-20190226/#clearmarks-method
-  // 1. If markName is omitted, remove all PerformanceMark objects from the
-  // performance entry buffer.
-  // 2. Otherwise, remove all PerformanceMark objects listed in the performance
-  // entry buffer whose name matches markName.
-  PerformanceEntryList retained_performance_entry_buffer;
-  for (const auto& entry : performance_entry_buffer_) {
-    bool should_remove_entry =
-        PerformanceEntry::ToEntryTypeEnum(entry->entry_type()) ==
-            PerformanceEntry::kMark &&
-        (mark_name.empty() || entry->name() == mark_name);
-    if (!should_remove_entry) {
-      retained_performance_entry_buffer.push_back(entry);
-    }
-  }
-  performance_entry_buffer_.swap(retained_performance_entry_buffer);
-
-  // 3. Return undefined.
-}
-
-void Performance::Measure(const std::string& measure_name,
-                          const std::string& start_mark,
-                          const std::string& end_mark,
-                          script::ExceptionState* exception_state) {
-  // The algorithm for measure() follows these steps:
-  //   https://www.w3.org/TR/2019/REC-user-timing-2-20190226/#measure-method
-  // 1. Let end time be 0.
-  DOMHighResTimeStamp end_time = 0.0;
-
-  // 2. If endMark is omitted, let end time be the value that would be returned
-  // by the Performance object's now() method.
-  if (end_mark.empty()) {
-    end_time = Now();
-  } else if (IsNamePerformanceTimingAttribute(end_mark)) {
-    // 2.1. Otherwise, if endMark has the same name as a read only attribute in
-    // the PerformanceTiming interface, let end time be the value returned by
-    // running the convert a name to a timestamp algorithm with name set to the
-    // value of endMark.
-    end_time = ConvertNameToTimestamp(end_mark, exception_state);
-  } else {
-    // 2.2. Otherwise let end time be the value of the startTime attribute from
-    // the most recent occurrence of a PerformanceMark object in the performance
-    // entry buffer whose name matches the value of endMark. If no matching
-    // entry is found, throw a SyntaxError.
-    PerformanceEntryList list = GetEntriesByName(end_mark, "mark");
-    if (list.empty()) {
-      dom::DOMException::Raise(
-          dom::DOMException::kSyntaxErr,
-          "Cannot create measure; no mark found with name: " + end_mark + ".",
-          exception_state);
-      return;
-    }
-    end_time = list.at(list.size() - 1)->start_time();
-  }
-
-  DOMHighResTimeStamp start_time;
-  // 3. If startMark is omitted, let start time be 0.
-  if (start_mark.empty()) {
-    start_time = 0.0;
-  } else if (IsNamePerformanceTimingAttribute(start_mark)) {
-    // 3.1. If startMark has the same name as a read only attribute in the
-    // PerformanceTiming interface, let start time be the value returned by
-    // running the convert a name to a timestamp algorithm with name set to
-    // startMark.
-    start_time = ConvertNameToTimestamp(start_mark, exception_state);
-  } else {
-    // 3.2. Otherwise let start time be the value of the startTime attribute
-    // from the most recent occurrence of a PerformanceMark object in the
-    // performance entry buffer whose name matches the value of startMark. If no
-    // matching entry is found, throw a SyntaxError.
-    PerformanceEntryList list = GetEntriesByName(start_mark, "mark");
-    if (list.empty()) {
-      dom::DOMException::Raise(
-          dom::DOMException::kSyntaxErr,
-          "Cannot create measure; no mark found with name: " + start_mark + ".",
-          exception_state);
-      return;
-    }
-    start_time = list.at(list.size() - 1)->start_time();
-  }
-
-  // 4. Create a new PerformanceMeasure object (entry).
-  // 5. Set entry's name attribute to measureName.
-  // 6. Set entry's entryType attribute to DOMString "measure".
-  // 7. Set entry's startTime attribute to start time.
-  // 8. Set entry's duration attribute to the duration from start time to end
-  // time. The resulting duration value MAY be negative.
-  scoped_refptr<PerformanceMeasure> entry =
-      base::MakeRefCounted<PerformanceMeasure>(measure_name, start_time,
-                                               end_time);
-
-  // 9. Queue entry.
-  QueuePerformanceEntry(entry);
-
-  // 10. Add entry to the performance entry buffer.
-  performance_entry_buffer_.push_back(entry);
-
-  // 11. Return undefined.
-}
-
-void Performance::ClearMeasures(const std::string& measure_name) {
-  // The algorithm for clearMeasures follows these steps:
-  //   https://www.w3.org/TR/2019/REC-user-timing-2-20190226/#clearmeasures-method
-  // 1. If measureName is omitted, remove all PerformanceMeasure objects in the
-  // performance entry buffer.
-  // 2. Otherwise remove all PerformanceMeasure objects listed in the
-  // performance entry buffer whose name matches measureName.
-  PerformanceEntryList performance_entry_buffer;
-  for (const auto& entry : performance_entry_buffer_) {
-    bool shouldRemoveEntry =
-        PerformanceEntry::ToEntryTypeEnum(entry->entry_type()) ==
-            PerformanceEntry::kMeasure &&
-        (measure_name.empty() || entry->name() == measure_name);
-    if (!shouldRemoveEntry) {
-      performance_entry_buffer.push_back(entry);
-    }
-  }
-  performance_entry_buffer_.swap(performance_entry_buffer);
-
-  // 3. Return undefined.
-}
-
 void Performance::UnregisterPerformanceObserver(
     const scoped_refptr<PerformanceObserver>& old_observer) {
   auto iter = registered_performance_observers_.begin();
@@ -315,7 +123,8 @@
     const PerformanceObserverInit& options) {
   std::list<PerformanceObserverInit> options_list;
   options_list.push_back(options);
-  registered_performance_observers_.emplace_back(observer, options_list);
+  registered_performance_observers_.emplace_back(observer,
+                                                 options_list);
 }
 
 void Performance::ReplaceRegisteredPerformanceObserverOptionsList(
@@ -391,8 +200,7 @@
   resource_timing_buffer_current_size_ = 0;
 }
 
-void Performance::SetResourceTimingBufferSize(
-    unsigned long max_size) {  // NOLINT(runtime/int)
+void Performance::SetResourceTimingBufferSize(unsigned long max_size) {
   // The method runs the following steps:
   //   https://www.w3.org/TR/2021/WD-resource-timing-2-20210414/#dom-performance-setresourcetimingbuffersize
   // 1. Set resource timing buffer size limit to the maxSize parameter.
@@ -453,7 +261,7 @@
   while (!resource_timing_secondary_buffer_.empty()) {
     // 1.1 Let number of excess entries before be resource timing secondary
     // buffer current size.
-    unsigned long excess_entries_before =  // NOLINT(runtime/int)
+    unsigned long excess_entries_before =
         resource_timing_secondary_buffer_current_size_;
     // 1.2 If can add resource timing entry returns false, then fire an event
     // named resourcetimingbufferfull at the Performance object.
@@ -464,7 +272,7 @@
     CopySecondaryBuffer();
     // 1.4 Let number of excess entries after be resource timing secondary
     // buffer current size.
-    unsigned long excess_entries_after =  // NOLINT(runtime/int)
+    unsigned long excess_entries_after =
         resource_timing_secondary_buffer_current_size_;
     // 1.5 If number of excess entries before is lower than or equals number of
     // excess entries after, then remove all entries from resource timing
diff --git a/src/cobalt/dom/performance.h b/src/cobalt/dom/performance.h
index 753123d..38c5600 100644
--- a/src/cobalt/dom/performance.h
+++ b/src/cobalt/dom/performance.h
@@ -21,8 +21,6 @@
 #include <string>
 
 #include "base/time/default_tick_clock.h"
-#include "base/threading/thread.h"
-#include "base/threading/thread_checker.h"
 #include "cobalt/base/application_state.h"
 #include "cobalt/base/clock.h"
 #include "cobalt/dom/event_target.h"
@@ -35,7 +33,7 @@
 #include "cobalt/dom/performance_resource_timing.h"
 #include "cobalt/dom/performance_timing.h"
 #include "cobalt/script/environment_settings.h"
-#include "cobalt/script/exception_state.h"
+
 #include "net/base/load_timing_info.h"
 
 namespace cobalt {
@@ -52,11 +50,9 @@
   // order to mitigate potential Spectre-related attacks.  This is following
   // Mozilla's lead as described here:
   //   https://www.mozilla.org/en-US/security/advisories/mfsa2018-01/
-  // NOLINT(runtime/int)
   static constexpr int64_t kPerformanceTimerMinResolutionInMicroseconds = 20;
   //   https://www.w3.org/TR/2021/WD-resource-timing-2-20210414/#sec-extensions-performance-interface
-  static constexpr unsigned long  // NOLINT(runtime/int)
-      kMaxResourceTimingBufferSize = 250;
+  static constexpr unsigned long kMaxResourceTimingBufferSize = 250;
 
   Performance(script::EnvironmentSettings* settings,
               const scoped_refptr<base::BasicClock>& clock);
@@ -81,22 +77,10 @@
   PerformanceEntryList GetEntriesByName(const std::string& name,
                                         const base::StringPiece& type);
 
-  // Web API: User Timing extensions to Performance
-  //   https://www.w3.org/TR/2019/REC-user-timing-2-20190226/#extensions-performance-interface
-  void Mark(const std::string& mark_name,
-            script::ExceptionState* exception_state);
-  void ClearMarks(const std::string& mark_name);
-  void Measure(const std::string& measure_name, const std::string& start_mark,
-               const std::string& end_mark,
-               script::ExceptionState* exception_state);
-  void ClearMeasures(const std::string& measure_name);
-
   // Web API: Performance Resource Timing extensions to Performance.
   //   https://w3c.github.io/resource-timing/#sec-extensions-performance-interface
   void ClearResourceTimings();
-  // NOLINT(runtime/int)
-  void SetResourceTimingBufferSize(
-      unsigned long max_size);  // NOLINT(runtime/int)
+  void SetResourceTimingBufferSize(unsigned long max_size);
   bool CanAddResourceTimingEntry();
   void CopySecondaryBuffer();
   void set_onresourcetimingbufferfull(
@@ -173,8 +157,7 @@
   unsigned long resource_timing_buffer_size_limit_;
   unsigned long resource_timing_buffer_current_size_;
   bool resource_timing_buffer_full_event_pending_flag_;
-  unsigned long
-      resource_timing_secondary_buffer_current_size_;
+  unsigned long resource_timing_secondary_buffer_current_size_;
   std::deque<scoped_refptr<PerformanceResourceTiming>>
       resource_timing_secondary_buffer_;
 
diff --git a/src/cobalt/dom/performance.idl b/src/cobalt/dom/performance.idl
index 91d2e8e..fd42dd3 100644
--- a/src/cobalt/dom/performance.idl
+++ b/src/cobalt/dom/performance.idl
@@ -25,14 +25,4 @@
   void clearResourceTimings ();
   void setResourceTimingBufferSize (unsigned long maxSize);
   attribute EventHandler onresourcetimingbufferfull;
-
-  // Web API: User Timing extensions to Performance
-  //   https://www.w3.org/TR/2019/REC-user-timing-2-20190226/#extensions-performance-interface
-  [RaisesException] void mark(DOMString name);
-  [RaisesException] void measure(
-      DOMString measureName,
-      optional DOMString startMark = "",
-      optional DOMString endMark = "");
-  void clearMarks(optional DOMString name = "");
-  void clearMeasures(optional DOMString measureName = "");
 };
diff --git a/src/cobalt/dom/performance_entry.h b/src/cobalt/dom/performance_entry.h
index 7617b36..5643bb1 100644
--- a/src/cobalt/dom/performance_entry.h
+++ b/src/cobalt/dom/performance_entry.h
@@ -29,20 +29,19 @@
 //   https://w3c.github.io/performance-timeline/#the-performanceentry-interface
 class PerformanceEntry : public script::Wrappable {
  public:
-  PerformanceEntry(const std::string& name, DOMHighResTimeStamp start_time,
+  PerformanceEntry(const std::string& name,
+                   DOMHighResTimeStamp start_time,
                    DOMHighResTimeStamp finish_time);
 
   enum EntryType : PerformanceEntryType {
     kResource = 0,
     kNavigation = 1,
     kLifecycle = 2,
-    kMark = 3,
-    kMeasure = 4,
-    kInvalid = 5,
+    kInvalid = 3,
   };
 
-  static constexpr const char* kEntryTypeString[] = {
-      "resource", "navigation", "lifecycle", "mark", "measure", "invalid"};
+  static constexpr const char* kEntryTypeString[] =
+      {"resource", "navigation", "lifecycle", "invalid"};
 
   std::string name() const { return name_; }
   DOMHighResTimeStamp start_time() const;
@@ -55,9 +54,8 @@
   static PerformanceEntry::EntryType ToEntryTypeEnum(
       const std::string& entry_type);
 
-  static bool StartTimeCompareLessThan(
-      const scoped_refptr<PerformanceEntry>& a,
-      const scoped_refptr<PerformanceEntry>& b);
+  static bool StartTimeCompareLessThan(const scoped_refptr<PerformanceEntry>& a,
+                                       const scoped_refptr<PerformanceEntry>& b);
 
   DEFINE_WRAPPABLE_TYPE(PerformanceEntry);
 
diff --git a/src/cobalt/dom/performance_mark.cc b/src/cobalt/dom/performance_mark.cc
deleted file mode 100644
index 0efdf5b..0000000
--- a/src/cobalt/dom/performance_mark.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2021 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "cobalt/dom/performance_mark.h"
-
-#include "cobalt/dom/performance.h"
-#include "cobalt/dom/performance_entry.h"
-
-namespace cobalt {
-namespace dom {
-
-PerformanceMark::PerformanceMark(const std::string& name,
-                                 DOMHighResTimeStamp start_time)
-    : PerformanceEntry(name, start_time, start_time) {}
-
-}  // namespace dom
-}  // namespace cobalt
diff --git a/src/cobalt/dom/performance_mark.h b/src/cobalt/dom/performance_mark.h
deleted file mode 100644
index 7d477b6..0000000
--- a/src/cobalt/dom/performance_mark.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2021 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef COBALT_DOM_PERFORMANCE_MARK_H_
-#define COBALT_DOM_PERFORMANCE_MARK_H_
-
-#include <string>
-
-#include "cobalt/dom/performance_entry.h"
-#include "cobalt/dom/performance_high_resolution_time.h"
-#include "cobalt/script/wrappable.h"
-#include "net/base/load_timing_info.h"
-
-namespace cobalt {
-namespace dom {
-class Performance;
-
-// Implements the PerformanceMart interface.
-//   https://www.w3.org/TR/2019/REC-user-timing-2-20190226/#performancemark
-class PerformanceMark : public PerformanceEntry {
- public:
-  PerformanceMark(const std::string& name, DOMHighResTimeStamp start_time);
-
-  std::string entry_type() const override { return "mark"; }
-  PerformanceEntryType EntryTypeEnum() const override {
-    return PerformanceEntry::kMark;
-  }
-
-  DEFINE_WRAPPABLE_TYPE(PerformanceMark);
-};
-}  // namespace dom
-}  // namespace cobalt
-
-#endif  // COBALT_DOM_PERFORMANCE_MARK_H_
diff --git a/src/cobalt/dom/performance_mark.idl b/src/cobalt/dom/performance_mark.idl
deleted file mode 100644
index 834479b..0000000
--- a/src/cobalt/dom/performance_mark.idl
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-[Exposed=Window]
-interface PerformanceMark : PerformanceEntry {};
diff --git a/src/cobalt/dom/performance_measure.cc b/src/cobalt/dom/performance_measure.cc
deleted file mode 100644
index 1a7edfb..0000000
--- a/src/cobalt/dom/performance_measure.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2021 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "cobalt/dom/performance_measure.h"
-
-#include "cobalt/dom/performance.h"
-#include "cobalt/dom/performance_entry.h"
-
-namespace cobalt {
-namespace dom {
-
-PerformanceMeasure::PerformanceMeasure(const std::string& name,
-                                       DOMHighResTimeStamp start_time,
-                                       DOMHighResTimeStamp end_time)
-    : PerformanceEntry(name, start_time, end_time) {}
-
-}  // namespace dom
-}  // namespace cobalt
diff --git a/src/cobalt/dom/performance_measure.h b/src/cobalt/dom/performance_measure.h
deleted file mode 100644
index 41107c3..0000000
--- a/src/cobalt/dom/performance_measure.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2021 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef COBALT_DOM_PERFORMANCE_MEASURE_H_
-#define COBALT_DOM_PERFORMANCE_MEASURE_H_
-
-#include <string>
-
-#include "cobalt/dom/performance_entry.h"
-#include "cobalt/dom/performance_high_resolution_time.h"
-#include "cobalt/script/wrappable.h"
-#include "net/base/load_timing_info.h"
-
-namespace cobalt {
-namespace dom {
-class Performance;
-
-// Implements the PerformanceMeasure interface.
-//   https://www.w3.org/TR/2019/REC-user-timing-2-20190226/#performancemeasure
-class PerformanceMeasure : public PerformanceEntry {
- public:
-  PerformanceMeasure(const std::string& name, DOMHighResTimeStamp start_time,
-                     DOMHighResTimeStamp end_time);
-
-  std::string entry_type() const override { return "measure"; }
-  PerformanceEntryType EntryTypeEnum() const override {
-    return PerformanceEntry::kMeasure;
-  }
-
-  DEFINE_WRAPPABLE_TYPE(PerformanceMeasure);
-};
-}  // namespace dom
-}  // namespace cobalt
-
-#endif  // COBALT_DOM_PERFORMANCE_MEASURE_H_
diff --git a/src/cobalt/dom/performance_measure.idl b/src/cobalt/dom/performance_measure.idl
deleted file mode 100644
index b6cb447..0000000
--- a/src/cobalt/dom/performance_measure.idl
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-[Exposed=Window]
-interface PerformanceMeasure : PerformanceEntry {};
diff --git a/src/cobalt/dom/performance_timing.h b/src/cobalt/dom/performance_timing.h
index 184d92d..a31efb6 100644
--- a/src/cobalt/dom/performance_timing.h
+++ b/src/cobalt/dom/performance_timing.h
@@ -15,9 +15,10 @@
 #ifndef COBALT_DOM_PERFORMANCE_TIMING_H_
 #define COBALT_DOM_PERFORMANCE_TIMING_H_
 
+#include "cobalt/script/wrappable.h"
+
 #include "base/time/time.h"
 #include "cobalt/base/clock.h"
-#include "cobalt/script/wrappable.h"
 
 namespace cobalt {
 namespace dom {
@@ -25,8 +26,6 @@
 // Implements the PerformanceTiming IDL interface, as described here:
 //   https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html#sec-navigation-timing-interface
 class PerformanceTiming : public script::Wrappable {
-  // If any new public fields are added here, handling logic must be added to
-  // Performance::Mark and Performance::Measure.
  public:
   explicit PerformanceTiming(const scoped_refptr<base::BasicClock>& clock,
                              base::TimeTicks time_origin);
diff --git a/src/cobalt/media/base/playback_statistics.cc b/src/cobalt/media/base/playback_statistics.cc
index 2c797c8..c35a451 100644
--- a/src/cobalt/media/base/playback_statistics.cc
+++ b/src/cobalt/media/base/playback_statistics.cc
@@ -14,7 +14,6 @@
 
 #include "cobalt/media/base/playback_statistics.h"
 
-#include "base/strings/stringprintf.h"
 #include "starboard/atomic.h"
 #include "starboard/common/string.h"
 
@@ -74,56 +73,11 @@
   }
 }
 
-}  // namespace
+std::string ToString(const base::Optional<base::TimeDelta>& t) {
+  return (t) ? starboard::FormatString("%" PRId64, t->InMicroseconds()) : "n/a";
+}
 
-PlaybackStatistics::PlaybackStatistics(const std::string& pipeline_identifier)
-    : seek_time_(base::StringPrintf("Media.Pipeline.%s.SeekTime",
-                                    pipeline_identifier.c_str()),
-                 base::TimeDelta(), "The seek-to time of the media pipeline."),
-      first_written_audio_timestamp_(
-          base::StringPrintf("Media.Pipeline.%s.FirstWrittenAudioTimestamp",
-                             pipeline_identifier.c_str()),
-          base::TimeDelta(),
-          "The timestamp of the first written audio buffer in the media "
-          "pipeline."),
-      first_written_video_timestamp_(
-          base::StringPrintf("Media.Pipeline.%s.FirstWrittenVideoTimestamp",
-                             pipeline_identifier.c_str()),
-          base::TimeDelta(),
-          "The timestamp of the first written audio buffer in the media "
-          "pipeline."),
-      last_written_audio_timestamp_(
-          base::StringPrintf("Media.Pipeline.%s.LastWrittenAudioTimestamp",
-                             pipeline_identifier.c_str()),
-          base::TimeDelta(),
-          "The timestamp of the last written audio buffer in the media "
-          "pipeline."),
-      last_written_video_timestamp_(
-          base::StringPrintf("Media.Pipeline.%s.LastWrittenVideoTimestamp",
-                             pipeline_identifier.c_str()),
-          base::TimeDelta(),
-          "The timestamp of the last written video buffer in the media "
-          "pipeline."),
-      video_width_(base::StringPrintf("Media.Pipeline.%s.VideoWidth",
-                                      pipeline_identifier.c_str()),
-                   0, "The frame width of the video."),
-      video_height_(base::StringPrintf("Media.Pipeline.%s.VideoHeight",
-                                       pipeline_identifier.c_str()),
-                    0, "The frame height of the video."),
-      is_audio_eos_written_(
-          base::StringPrintf("Media.Pipeline.%s.IsAudioEOSWritten",
-                             pipeline_identifier.c_str()),
-          false, "Indicator of if the audio eos is written."),
-      is_video_eos_written_(
-          base::StringPrintf("Media.Pipeline.%s.IsVideoEOSWritten",
-                             pipeline_identifier.c_str()),
-          false, "Indicator of if the video eos is written."),
-      pipeline_status_(base::StringPrintf("Media.Pipeline.%s.PipelineStatus",
-                                          pipeline_identifier.c_str()),
-                       PIPELINE_OK, "The status of the media pipeline."),
-      error_message_(base::StringPrintf("Media.Pipeline.%s.ErrorMessage",
-                                        pipeline_identifier.c_str()),
-                     "", "The error message of the media pipeline error.") {}
+}  // namespace
 
 PlaybackStatistics::~PlaybackStatistics() {
   SbAtomicNoBarrier_Increment(&s_active_instances, -1);
@@ -151,11 +105,10 @@
     }
   }
 
-  video_width_ = video_config.natural_size().width();
-  video_height_ = video_config.natural_size().height();
-
-  const auto width = static_cast<SbAtomic32>(video_width_);
-  const auto height = static_cast<SbAtomic32>(video_height_);
+  const auto width =
+      static_cast<SbAtomic32>(video_config.natural_size().width());
+  const auto height =
+      static_cast<SbAtomic32>(video_config.natural_size().height());
 
   UpdateMinValue(width, &s_min_video_width);
   UpdateMaxValue(width, &s_max_video_width);
@@ -173,38 +126,22 @@
 
 void PlaybackStatistics::OnSeek(const base::TimeDelta& seek_time) {
   seek_time_ = seek_time;
-  is_first_audio_buffer_written_ = false;
-  is_first_video_buffer_written_ = false;
+  first_written_audio_timestamp_.reset();
+  first_written_video_timestamp_.reset();
 }
 
-void PlaybackStatistics::OnAudioAU(const scoped_refptr<DecoderBuffer>& buffer) {
-  if (buffer->end_of_stream()) {
-    is_audio_eos_written_ = true;
-    return;
-  }
-  last_written_audio_timestamp_ = buffer->timestamp();
-  if (!is_first_audio_buffer_written_) {
-    is_first_audio_buffer_written_ = true;
-    first_written_audio_timestamp_ = buffer->timestamp();
+void PlaybackStatistics::OnAudioAU(const base::TimeDelta& timestamp) {
+  last_written_audio_timestamp_ = timestamp;
+  if (!first_written_audio_timestamp_) {
+    first_written_audio_timestamp_.emplace(timestamp);
   }
 }
 
-void PlaybackStatistics::OnVideoAU(const scoped_refptr<DecoderBuffer>& buffer) {
-  if (buffer->end_of_stream()) {
-    is_video_eos_written_ = true;
-    return;
+void PlaybackStatistics::OnVideoAU(const base::TimeDelta& timestamp) {
+  last_written_video_timestamp_ = timestamp;
+  if (!first_written_video_timestamp_) {
+    first_written_video_timestamp_.emplace(timestamp);
   }
-  last_written_video_timestamp_ = buffer->timestamp();
-  if (!is_first_video_buffer_written_) {
-    is_first_video_buffer_written_ = true;
-    first_written_video_timestamp_ = buffer->timestamp();
-  }
-}
-
-void PlaybackStatistics::OnError(PipelineStatus status,
-                                 const std::string& error_message) {
-  pipeline_status_ = status;
-  error_message_ = error_message;
 }
 
 std::string PlaybackStatistics::GetStatistics(
@@ -214,14 +151,18 @@
       " active_players (max): %d (%d), av1: ~%d, h264: ~%d, hevc: ~%d,"
       " vp9: ~%d, min_width: %d, min_height: %d, max_width: %d, max_height: %d,"
       " last_working_codec: %s,"
-      " seek_time: %s,"
+      " seek_time: %" PRId64
+      ","
       " first_audio_time: %s,"
       " first_video_time: %s,"
-      " last_audio_time: %s,"
-      " last_video_time: %s",
+      " last_audio_time: %" PRId64
+      ","
+      " last_video_time: %" PRId64,
       GetCodecName(current_video_config.codec()).c_str(),
-      (current_video_config.is_encrypted() ? "Y" : "N"), video_width_.value(),
-      video_height_.value(), SbAtomicNoBarrier_Load(&s_active_instances),
+      (current_video_config.is_encrypted() ? "Y" : "N"),
+      static_cast<int>(current_video_config.natural_size().width()),
+      static_cast<int>(current_video_config.natural_size().height()),
+      SbAtomicNoBarrier_Load(&s_active_instances),
       SbAtomicNoBarrier_Load(&s_max_active_instances),
       RoundValues(SbAtomicNoBarrier_Load(&s_av1_played)),
       RoundValues(SbAtomicNoBarrier_Load(&s_h264_played)),
@@ -234,19 +175,11 @@
       GetCodecName(static_cast<VideoCodec>(
                        SbAtomicNoBarrier_Load(&s_last_working_codec)))
           .c_str(),
-      ValToString(seek_time_).c_str(),
-      is_first_audio_buffer_written_
-          ? ValToString(last_written_audio_timestamp_).c_str()
-          : "n/a",
-      is_first_video_buffer_written_
-          ? ValToString(first_written_audio_timestamp_).c_str()
-          : "n/a",
-      is_first_audio_buffer_written_
-          ? ValToString(last_written_audio_timestamp_).c_str()
-          : "n/a",
-      is_first_video_buffer_written_
-          ? ValToString(last_written_video_timestamp_).c_str()
-          : "n/a");
+      seek_time_.InMicroseconds(),
+      ToString(first_written_audio_timestamp_).c_str(),
+      ToString(first_written_audio_timestamp_).c_str(),
+      last_written_audio_timestamp_.InMicroseconds(),
+      last_written_video_timestamp_.InMicroseconds());
 }
 
 }  // namespace media
diff --git a/src/cobalt/media/base/playback_statistics.h b/src/cobalt/media/base/playback_statistics.h
index 78808b3..9cbb562 100644
--- a/src/cobalt/media/base/playback_statistics.h
+++ b/src/cobalt/media/base/playback_statistics.h
@@ -19,9 +19,6 @@
 
 #include "base/optional.h"
 #include "base/time/time.h"
-#include "cobalt/base/c_val.h"
-#include "cobalt/media/base/decoder_buffer.h"
-#include "cobalt/media/base/pipeline_status.h"
 #include "cobalt/media/base/video_decoder_config.h"
 
 namespace cobalt {
@@ -29,34 +26,24 @@
 
 class PlaybackStatistics {
  public:
-  explicit PlaybackStatistics(const std::string& pipeline_identifier);
   ~PlaybackStatistics();
 
   void UpdateVideoConfig(const VideoDecoderConfig& video_config);
   void OnPresenting(const VideoDecoderConfig& video_config);
   void OnSeek(const base::TimeDelta& seek_time);
-  void OnAudioAU(const scoped_refptr<DecoderBuffer>& buffer);
-  void OnVideoAU(const scoped_refptr<DecoderBuffer>& buffer);
-  void OnError(PipelineStatus status, const std::string& error_message);
+  void OnAudioAU(const base::TimeDelta& timestamp);
+  void OnVideoAU(const base::TimeDelta& timestamp);
 
   std::string GetStatistics(
       const VideoDecoderConfig& current_video_config) const;
 
  private:
-  base::CVal<base::TimeDelta> seek_time_;
-  base::CVal<base::TimeDelta> first_written_audio_timestamp_;
-  base::CVal<base::TimeDelta> first_written_video_timestamp_;
-  base::CVal<base::TimeDelta> last_written_audio_timestamp_;
-  base::CVal<base::TimeDelta> last_written_video_timestamp_;
-  base::CVal<int> video_width_;
-  base::CVal<int> video_height_;
-  base::CVal<bool> is_audio_eos_written_;
-  base::CVal<bool> is_video_eos_written_;
-  base::CVal<PipelineStatus> pipeline_status_;
-  base::CVal<std::string> error_message_;
+  base::TimeDelta seek_time_;
+  base::Optional<base::TimeDelta> first_written_audio_timestamp_;
+  base::Optional<base::TimeDelta> first_written_video_timestamp_;
+  base::TimeDelta last_written_audio_timestamp_;
+  base::TimeDelta last_written_video_timestamp_;
   bool is_initial_config_ = true;
-  bool is_first_audio_buffer_written_ = false;
-  bool is_first_video_buffer_written_ = false;
 };
 
 }  // namespace media
diff --git a/src/cobalt/media/base/sbplayer_pipeline.cc b/src/cobalt/media/base/sbplayer_pipeline.cc
index aefa84b..2429ac4 100644
--- a/src/cobalt/media/base/sbplayer_pipeline.cc
+++ b/src/cobalt/media/base/sbplayer_pipeline.cc
@@ -21,13 +21,11 @@
 #include "base/callback_helpers.h"
 #include "base/logging.h"
 #include "base/optional.h"
-#include "base/strings/stringprintf.h"
 #include "base/synchronization/lock.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task_runner.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
-#include "cobalt/base/c_val.h"
 #include "cobalt/math/size.h"
 #include "cobalt/media/base/audio_decoder_config.h"
 #include "cobalt/media/base/bind_to_current_loop.h"
@@ -191,10 +189,6 @@
   // Retrieve the statistics as a string and append to message.
   std::string AppendStatisticsString(const std::string& message) const;
 
-  // An identifier string for the pipeline, used in CVal to identify multiple
-  // pipelines.
-  const std::string pipeline_identifier_;
-
   // Message loop used to execute pipeline tasks.  It is thread-safe.
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 
@@ -225,12 +219,12 @@
   // Current volume level (from 0.0f to 1.0f).  This value is set immediately
   // via SetVolume() and a task is dispatched on the message loop to notify the
   // filters.
-  base::CVal<float> volume_;
+  float volume_ = 1.f;
 
   // Current playback rate (>= 0.0f).  This value is set immediately via
   // SetPlaybackRate() and a task is dispatched on the message loop to notify
   // the filters.
-  base::CVal<float> playback_rate_;
+  float playback_rate_ = 0.f;
 
   // The saved audio and video demuxer streams.  Note that it is safe to store
   // raw pointers of the demuxer streams, as the Demuxer guarantees that its
@@ -265,7 +259,7 @@
   bool audio_read_in_progress_ = false;
   bool audio_read_delayed_ = false;
   bool video_read_in_progress_ = false;
-  base::CVal<TimeDelta> duration_;
+  TimeDelta duration_;
 
 #if SB_HAS(PLAYER_WITH_URL)
   TimeDelta start_date_;
@@ -282,14 +276,12 @@
 
   // Temporary callback used for Start() and Seek().
   SeekCB seek_cb_;
-  TimeDelta seek_time_;
+  base::TimeDelta seek_time_;
   std::unique_ptr<StarboardPlayer> player_;
   bool is_initial_preroll_ = true;
-  base::CVal<bool> started_;
-  base::CVal<bool> suspended_;
-  base::CVal<bool> stopped_;
-  base::CVal<bool> ended_;
-  base::CVal<SbPlayerState> player_state_;
+  bool suspended_ = false;
+  bool stopped_ = false;
+  bool ended_ = false;
 
   VideoFrameProvider* video_frame_provider_;
 
@@ -307,11 +299,11 @@
   // Timestamp for the last written audio.
   SbTime timestamp_of_last_written_audio_ = 0;
   // Last media time reported by GetMediaTime().
-  base::CVal<SbTime> last_media_time_;
+  SbTime last_media_time_ = 0;
   // Time when we last checked the media time.
   SbTime last_time_media_time_retrieved_ = 0;
   // The maximum video playback capabilities required for the playback.
-  base::CVal<std::string> max_video_capabilities_;
+  std::string max_video_capabilities_;
 
   PlaybackStatistics playback_statistics_;
 
@@ -325,51 +317,14 @@
         get_decode_target_graphics_context_provider_func,
     bool allow_resume_after_suspend, MediaLog* media_log,
     VideoFrameProvider* video_frame_provider)
-    : pipeline_identifier_(base::StringPrintf("%p", this)),
-      task_runner_(task_runner),
+    : task_runner_(task_runner),
       allow_resume_after_suspend_(allow_resume_after_suspend),
       window_(window),
       get_decode_target_graphics_context_provider_func_(
           get_decode_target_graphics_context_provider_func),
       natural_size_(0, 0),
-      volume_(base::StringPrintf("Media.Pipeline.%s.Volume",
-                                 pipeline_identifier_.c_str()),
-              1.0f, "Volume of the media pipeline."),
-      playback_rate_(base::StringPrintf("Media.Pipeline.%s.PlaybackRate",
-                                        pipeline_identifier_.c_str()),
-                     0.0f, "Playback rate of the media pipeline."),
-      duration_(base::StringPrintf("Media.Pipeline.%s.Duration",
-                                   pipeline_identifier_.c_str()),
-                base::TimeDelta(), "Playback duration of the media pipeline."),
       set_bounds_helper_(new SbPlayerSetBoundsHelper),
-      started_(base::StringPrintf("Media.Pipeline.%s.Started",
-                                  pipeline_identifier_.c_str()),
-               false, "Whether the media pipeline has started."),
-      suspended_(base::StringPrintf("Media.Pipeline.%s.Suspended",
-                                    pipeline_identifier_.c_str()),
-                 false,
-                 "Whether the media pipeline has been suspended. The "
-                 "pipeline is usually suspended after the app enters "
-                 "background mode."),
-      stopped_(base::StringPrintf("Media.Pipeline.%s.Stopped",
-                                  pipeline_identifier_.c_str()),
-               false, "Whether the media pipeline has stopped."),
-      ended_(base::StringPrintf("Media.Pipeline.%s.Ended",
-                                pipeline_identifier_.c_str()),
-             false, "Whether the media pipeline has ended."),
-      player_state_(base::StringPrintf("Media.Pipeline.%s.PlayerState",
-                                       pipeline_identifier_.c_str()),
-                    kSbPlayerStateInitialized,
-                    "The underlying SbPlayer state of the media pipeline."),
-      video_frame_provider_(video_frame_provider),
-      last_media_time_(base::StringPrintf("Media.Pipeline.%s.LastMediaTime",
-                                          pipeline_identifier_.c_str()),
-                       0, "Last media time reported by the underlying player."),
-      max_video_capabilities_(
-          base::StringPrintf("Media.Pipeline.%s.MaxVideoCapabilities",
-                             pipeline_identifier_.c_str()),
-          "", "The max video capabilities required for the media pipeline."),
-      playback_statistics_(pipeline_identifier_) {
+      video_frame_provider_(video_frame_provider) {
   SbMediaSetAudioWriteDuration(kAudioLimit);
 }
 
@@ -818,8 +773,6 @@
                        BindToCurrentLoop(base::Bind(
                            &SbPlayerPipeline::OnDemuxerInitialized, this)),
                        kEnableTextTracks);
-
-  started_ = true;
 }
 
 void SbPlayerPipeline::SetVolumeTask(float volume) {
@@ -1160,13 +1113,15 @@
 
   if (type == DemuxerStream::AUDIO) {
     audio_read_in_progress_ = false;
-    playback_statistics_.OnAudioAU(buffer);
     if (!buffer->end_of_stream()) {
+      playback_statistics_.OnAudioAU(buffer->timestamp());
       timestamp_of_last_written_audio_ = buffer->timestamp().ToSbTime();
     }
   } else {
     video_read_in_progress_ = false;
-    playback_statistics_.OnVideoAU(buffer);
+    if (!buffer->end_of_stream()) {
+      playback_statistics_.OnVideoAU(buffer->timestamp());
+    }
   }
 
   player_->WriteBuffer(type, buffer);
@@ -1210,7 +1165,7 @@
           timestamp_of_last_written_audio_ - last_media_time_;
       if (time_ahead_of_playback > (kAudioLimit + kMediaTimeCheckInterval)) {
         SbTime delay_time = (time_ahead_of_playback - kAudioLimit) /
-                            std::max(playback_rate_.value(), 1.0f);
+                            std::max(playback_rate_, 1.0f);
         task_runner_->PostDelayedTask(
             FROM_HERE, base::Bind(&SbPlayerPipeline::DelayedNeedData, this),
             base::TimeDelta::FromMicroseconds(delay_time));
@@ -1241,7 +1196,6 @@
   if (!player_) {
     return;
   }
-  player_state_ = state;
   switch (state) {
     case kSbPlayerStateInitialized:
       NOTREACHED();
@@ -1379,11 +1333,6 @@
 void SbPlayerPipeline::CallErrorCB(PipelineStatus status,
                                    const std::string& error_message) {
   DCHECK_NE(status, PIPELINE_OK);
-  // Only to record the first error.
-  if (error_cb_.is_null()) {
-    return;
-  }
-  playback_statistics_.OnError(status, error_message);
   ResetAndRunIfNotNull(&error_cb_, status,
                        AppendStatisticsString(error_message));
 }
diff --git a/src/cobalt/media_integration_tests/__init__.py b/src/cobalt/media_integration_tests/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/src/cobalt/media_integration_tests/__init__.py
+++ /dev/null
diff --git a/src/cobalt/media_integration_tests/_env.py b/src/cobalt/media_integration_tests/_env.py
deleted file mode 100644
index a9d83bf..0000000
--- a/src/cobalt/media_integration_tests/_env.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Copyright 2021 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-"""Ask the parent directory to load the project environment."""
-
-from imp import load_source
-from os import path
-import sys
-
-_ENV = path.abspath(path.join(path.dirname(__file__), path.pardir, '_env.py'))
-if not path.exists(_ENV):
-  print '%s: Can\'t find repo root.\nMissing parent: %s' % (__file__, _ENV)
-  sys.exit(1)
-load_source('', _ENV)
diff --git a/src/cobalt/media_integration_tests/endurance/__init__.py b/src/cobalt/media_integration_tests/endurance/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/src/cobalt/media_integration_tests/endurance/__init__.py
+++ /dev/null
diff --git a/src/cobalt/media_integration_tests/endurance/_env.py b/src/cobalt/media_integration_tests/endurance/_env.py
deleted file mode 100644
index a9d83bf..0000000
--- a/src/cobalt/media_integration_tests/endurance/_env.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Copyright 2021 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-"""Ask the parent directory to load the project environment."""
-
-from imp import load_source
-from os import path
-import sys
-
-_ENV = path.abspath(path.join(path.dirname(__file__), path.pardir, '_env.py'))
-if not path.exists(_ENV):
-  print '%s: Can\'t find repo root.\nMissing parent: %s' % (__file__, _ENV)
-  sys.exit(1)
-load_source('', _ENV)
diff --git a/src/cobalt/media_integration_tests/functionality/__init__.py b/src/cobalt/media_integration_tests/functionality/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/src/cobalt/media_integration_tests/functionality/__init__.py
+++ /dev/null
diff --git a/src/cobalt/media_integration_tests/functionality/_env.py b/src/cobalt/media_integration_tests/functionality/_env.py
deleted file mode 100644
index a9d83bf..0000000
--- a/src/cobalt/media_integration_tests/functionality/_env.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Copyright 2021 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-"""Ask the parent directory to load the project environment."""
-
-from imp import load_source
-from os import path
-import sys
-
-_ENV = path.abspath(path.join(path.dirname(__file__), path.pardir, '_env.py'))
-if not path.exists(_ENV):
-  print '%s: Can\'t find repo root.\nMissing parent: %s' % (__file__, _ENV)
-  sys.exit(1)
-load_source('', _ENV)
diff --git a/src/cobalt/media_integration_tests/functionality/suspend_resume.py b/src/cobalt/media_integration_tests/functionality/suspend_resume.py
deleted file mode 100644
index e51215b..0000000
--- a/src/cobalt/media_integration_tests/functionality/suspend_resume.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# Copyright 2021 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-""" Tests of suspend and resume during playing."""
-
-import time
-import unittest
-
-import _env  # pylint: disable=unused-import
-from cobalt.media_integration_tests.test_app import Features
-from cobalt.media_integration_tests.test_case import TestCase
-
-
-class SuspendResumeTest(TestCase):
-  """
-    Test cases for suspend and resume.
-  """
-
-  def run_test_with_url(self, url):
-    app = self.CreateCobaltApp(url)
-    with app:
-      # Wait until the playback starts.
-      app.WaitUntilPlayerStart()
-      app.WaitUntilAdsEnd()
-      # Let the playback play for 2 seconds.
-      app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2)
-      # Suspend the app and wait the app enters background.
-      app.Suspend()
-      app.WaitUntilReachState(
-          lambda _app: not _app.ApplicationState().is_visible)
-      # Wait for 1 second before resume.
-      time.sleep(1)
-      # Resume the app and let it play for a few time.
-      app.Resume()
-      app.WaitUntilPlayerStart()
-      app.WaitUntilAdsEnd()
-      # Let the playback play for 2 seconds.
-      app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2)
-
-  @unittest.skipIf(not TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME),
-                   'Suspend and resume is not supported on this platform.')
-  def test_playback_h264(self):
-    self.run_test_with_url('https://www.youtube.com/tv#/watch?v=RACW52qnJMI')
-
-  @unittest.skipIf(not TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME),
-                   'Suspend and resume is not supported on this platform.')
-  def test_encrypted_playback(self):
-    self.run_test_with_url('https://www.youtube.com/tv#/watch?v=Vx5lkGS4w30')
-
-  @unittest.skipIf(not TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME),
-                   'Suspend and resume is not supported on this platform.')
-  def test_live_stream(self):
-    self.run_test_with_url('https://www.youtube.com/tv#/watch?v=KI1XlTQrsa0')
-
-  # Test for vp9 playback if supported.
-  @unittest.skipIf(not TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME),
-                   'Suspend and resume is not supported on this platform.')
-  def test_playback_vp9(self):
-    self.run_test_with_url('https://www.youtube.com/tv#/watch?v=1La4QzGeaaQ')
-
-  # Test for av1 playback if supported.
-  @unittest.skipIf(not TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME),
-                   'Suspend and resume is not supported on this platform.')
-  def test_playback_av1(self):
-    self.run_test_with_url('https://www.youtube.com/tv#/watch?v=iXvy8ZeCs5M')
-
-  # Test for vertical playback
-  @unittest.skipIf(not TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME),
-                   'Suspend and resume is not supported on this platform.')
-  def test_vertical_playback(self):
-    self.run_test_with_url('https://www.youtube.com/tv#/watch?v=jNQXAC9IVRw')
diff --git a/src/cobalt/media_integration_tests/media_integration_tests_runner.py b/src/cobalt/media_integration_tests/media_integration_tests_runner.py
deleted file mode 100644
index 7db2ab2..0000000
--- a/src/cobalt/media_integration_tests/media_integration_tests_runner.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# Copyright 2021 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""The media integration test runner.
-
-Usage:
-  To run all tests of a specific type:
-      "python media_integration_tests_runner.py
-      -p android-arm64 -c devel -w noinstall -log-level info
-      --type functionality"
-  To run a specific test (suspend_resume):
-      python media_integration_tests_runner.py
-      -p android-arm64 -c devel -w noinstall -log-level info
-      --type functionality --test_name suspend_resume"
-
-"""
-
-import argparse
-import importlib
-import logging
-import pkgutil
-import sys
-import unittest
-
-import _env  # pylint: disable=unused-import
-from cobalt.media_integration_tests.test_app import Features
-from cobalt.media_integration_tests.test_case import SetLauncherParams, SetSupportedFeatures
-from starboard.tools import abstract_launcher
-from starboard.tools import command_line
-from starboard.tools import log_level
-
-# Location of test files.
-_TESTS_PATH = {
-    'functionality': 'cobalt.media_integration_tests.functionality',
-    'endurance': 'cobalt.media_integration_tests.endurance',
-    'performance': 'cobalt.media_integration_tests.performance'
-}
-
-
-def GetSupportedFeatures(launcher_params):
-  launcher = abstract_launcher.LauncherFactory(
-      launcher_params.platform,
-      'cobalt',
-      launcher_params.config,
-      device_id=launcher_params.device_id,
-      target_params=None,
-      output_file=None,
-      out_directory=launcher_params.out_directory,
-      loader_platform=launcher_params.loader_platform,
-      loader_config=launcher_params.loader_config,
-      loader_out_directory=launcher_params.loader_out_directory)
-  return {
-      Features.SUSPEND_AND_RESUME: launcher.SupportsSystemSuspendResume(),
-      Features.SEND_KEYS: True,
-  }
-
-
-def GetTestPackagePath(test_type):
-  if _TESTS_PATH.has_key(test_type):
-    return _TESTS_PATH[test_type]
-  return None
-
-
-def GetAllTestNamesInPackage(package_path):
-  test_names = []
-  loader = pkgutil.get_loader(package_path)
-  for sub_module in pkgutil.walk_packages([loader.filename]):
-    _, sub_module_name, _ = sub_module
-    # Filter '_env' and '__init__'.
-    if sub_module_name[0] == '_':
-      continue
-    if not sub_module_name in test_names:
-      test_names.append(sub_module_name)
-  return test_names
-
-
-def LoadAllTests(package_path, all_test_names):
-  test_suite = unittest.TestSuite()
-  for test_name in all_test_names:
-    module = importlib.import_module(package_path + '.' + test_name)
-    test_suite.addTest(unittest.TestLoader().loadTestsFromModule(module))
-  return test_suite
-
-
-def main():
-  # TODO: Support filters.
-  parser = argparse.ArgumentParser()
-  parser.add_argument(
-      '--type',
-      required=True,
-      type=str,
-      help=('Type of the tests to run. It must be functionality, endurance or'
-            ' performance.'))
-  parser.add_argument(
-      '--test_name',
-      default=None,
-      type=str,
-      help=('Name of test to run. If not specified, it will run all tests in'
-            'the directory.'))
-  args, _ = parser.parse_known_args()
-
-  package_path = GetTestPackagePath(args.type)
-  if package_path is None:
-    logging.error('{%s} is not a valid test type.', args.type)
-    return 2
-
-  all_test_names = GetAllTestNamesInPackage(package_path)
-  specified_test_name = args.test_name
-  if specified_test_name is not None:
-    if not specified_test_name in all_test_names:
-      logging.error('{%s} is not a valid test name.', specified_test_name)
-      return 2
-    else:
-      all_test_names = [specified_test_name]
-
-  log_level.InitializeLogging(args)
-  launcher_params = command_line.CreateLauncherParams()
-  supported_features = GetSupportedFeatures(launcher_params)
-
-  # Update global variables in test case.
-  SetLauncherParams(launcher_params)
-  SetSupportedFeatures(supported_features)
-
-  unittest.installHandler()
-
-  test_suite = LoadAllTests(package_path, all_test_names)
-
-  return not unittest.TextTestRunner(
-      verbosity=0, stream=sys.stdout).run(test_suite).wasSuccessful()
-
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/src/cobalt/media_integration_tests/performance/__init__.py b/src/cobalt/media_integration_tests/performance/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/src/cobalt/media_integration_tests/performance/__init__.py
+++ /dev/null
diff --git a/src/cobalt/media_integration_tests/performance/_env.py b/src/cobalt/media_integration_tests/performance/_env.py
deleted file mode 100644
index a9d83bf..0000000
--- a/src/cobalt/media_integration_tests/performance/_env.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Copyright 2021 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-"""Ask the parent directory to load the project environment."""
-
-from imp import load_source
-from os import path
-import sys
-
-_ENV = path.abspath(path.join(path.dirname(__file__), path.pardir, '_env.py'))
-if not path.exists(_ENV):
-  print '%s: Can\'t find repo root.\nMissing parent: %s' % (__file__, _ENV)
-  sys.exit(1)
-load_source('', _ENV)
diff --git a/src/cobalt/media_integration_tests/test_app.py b/src/cobalt/media_integration_tests/test_app.py
deleted file mode 100644
index d0f62dd..0000000
--- a/src/cobalt/media_integration_tests/test_app.py
+++ /dev/null
@@ -1,744 +0,0 @@
-# Copyright 2021 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""A module to support fundamental communications with Cobalt application."""
-
-import enum
-import json
-import logging
-import threading
-import time
-
-import _env  # pylint: disable=unused-import
-from cobalt.tools.automated_testing import cobalt_runner
-from cobalt.tools.automated_testing import webdriver_utils
-
-webdriver_keys = webdriver_utils.import_selenium_module('webdriver.common.keys')
-
-PERIODIC_QUERIES_INTERVAL_SECONDS = 0.1
-THREAD_EXIT_TIMEOUT_SECONDS = 10
-WAIT_INTERVAL_SECONDS = 0.5
-WAIT_UNTIL_REACH_STATE_DEFAULT_TIMEOUT_SECONDS = 30
-WAIT_UNTIL_ADS_END_DEFAULT_TIMEOUT_SECONDS = 120
-WAIT_UNTIL_MEDIA_TIME_REACHED_DEFAULT_TIMEOUT_SECONDS = 30
-
-
-def GetValueFromQueryResult(query_result, key, default):
-  if query_result is None:
-    return default
-
-  if not isinstance(query_result, dict):
-    return default
-
-  if not query_result.has_key(key):
-    return default
-
-  value = query_result[key]
-  default_value_type = type(default)
-  value_type = type(value)
-
-  if value is None:
-    return default
-
-  if value_type == default_value_type:
-    return value
-
-  if value_type in (int, float) and default_value_type in (int, float):
-    return value
-
-  # CVal returns unicode, so we need to decode them before using.
-  if default_value_type in (bool, int, float):
-    if value_type in (str, unicode):
-      try:
-        # Try to parse numbers and booleans.
-        parsed_value = json.loads(value)
-      except ValueError:
-        raise RuntimeError('Failed to parse query result.')
-
-      return parsed_value
-
-  elif default_value_type is str:
-    if value_type == unicode:
-      return value.encode('utf-8')
-
-  raise NotImplementedError('Convertion from (%s) to (%s) is not suppoted.' %
-                            (value_type, default_value_type))
-
-
-class AdditionalKeys():
-  """
-    Set of special keys codes for media control, corresponding to cobalt
-    webdriver AdditionalSpecialKey.
-  """
-  MEDIA_NEXT_TRACK = u'\uf000'
-  MEDIA_PREV_TRACK = u'\uf001'
-  MEDIA_STOP = u'\uf002'
-  MEDIA_PLAYPAUSE = u'\uf003'
-
-
-class Features(enum.Enum):
-  """Set of platform features."""
-  SUSPEND_AND_RESUME = 1
-  SEND_KEYS = 2
-
-
-class ApplicationState():
-  """Set of platform features."""
-
-  def __init__(self, query_result=None):
-    if not query_result is None and not isinstance(query_result, dict):
-      raise NotImplementedError
-
-    self.is_visible = GetValueFromQueryResult(query_result, 'is_visible', False)
-
-  def __eq__(self, other):
-    if not isinstance(other, self.__class__):
-      raise NotImplementedError
-    return self.is_visible == other.is_visible
-
-  def __ne__(self, other):
-    return not self.__eq__(other)
-
-  @staticmethod
-  def QueryJsCode():
-    return """
-      return {
-        is_visible: document.visibilityState === "visible",
-      }
-    """
-
-
-class ApplicationStateHandler():
-  """The handler of applicatoin state."""
-
-  def __init__(self, app):
-    self.app = app
-    self.state_change_handlers = []
-    self.app_state = ApplicationState()
-
-    self.app.RegisterPeriodicQuery('Application',
-                                   ApplicationState.QueryJsCode(),
-                                   self._ApplicatinStateQueryHandler)
-
-  def AddAppStateChangeHandler(self, handler):
-    self.state_change_handlers.append(handler)
-
-  def RemoveAppStateChangeHandler(self, handler):
-    self.state_change_handlers.remove(handler)
-
-  def _NotifyHandlersOnStateChanged(self):
-    for handler in self.state_change_handlers:
-      handler(self, self.app, self.app_state)
-
-  def _ApplicatinStateQueryHandler(self, app, query_name, query_result):
-    del app, query_name  # Unused here.
-    # Note that the callback is invoked on a different thread.
-    new_state = ApplicationState(query_result)
-    if self.app_state != new_state:
-      self.app_state = new_state
-      self._NotifyHandlersOnStateChanged()
-
-
-class PipelinePlayerState(enum.IntEnum):
-  """Set of pipeline states, equals to SbPlayerState."""
-  INITIALIZED = 0  # kSbPlayerStateInitialized
-  PREROLLING = 1  # kSbPlayerStatePrerolling
-  PRESENTING = 2  # kSbPlayerStatePresenting
-  ENDOFSTREAM = 3  # kSbPlayerStateEndOfStream
-  DESTROYED = 4  # kSbPlayerStateDestroyed
-
-
-class PipelineState():
-  """
-    The states of SbPlayerPipeline. We update the states periodically via
-    webdriver and logs. Note that the duration and timestamps are in
-    milliseconds.
-  """
-
-  def __init__(self, query_result=None):
-    # If there's no player existing, the query return unicode code "null".
-    if (not query_result is None and not query_result == u'null' and
-        not isinstance(query_result, dict)):
-      raise NotImplementedError
-
-    self.identifier = GetValueFromQueryResult(query_result, 'identifier', '')
-    self.is_started = GetValueFromQueryResult(query_result, 'is_started', False)
-    self.is_suspended = GetValueFromQueryResult(query_result, 'is_suspended',
-                                                False)
-    self.is_stopped = GetValueFromQueryResult(query_result, 'is_stopped', False)
-    self.is_ended = GetValueFromQueryResult(query_result, 'is_ended', False)
-    self.player_state = PipelinePlayerState(
-        GetValueFromQueryResult(query_result, 'player_state', 0))
-    self.volume = GetValueFromQueryResult(query_result, 'volume', 1.0)
-    self.playback_rate = GetValueFromQueryResult(query_result, 'playback_rate',
-                                                 1.0)
-    self.duration = GetValueFromQueryResult(query_result, 'duration', 0)
-    self.last_media_time = GetValueFromQueryResult(query_result,
-                                                   'last_media_time', 0)
-    self.max_video_capabilities = GetValueFromQueryResult(
-        query_result, 'max_video_capabilities', '')
-    self.seek_time = GetValueFromQueryResult(query_result, 'seek_time', 0)
-    self.first_written_audio_timestamp = GetValueFromQueryResult(
-        query_result, 'first_written_audio_timestamp', 0)
-    self.first_written_video_timestamp = GetValueFromQueryResult(
-        query_result, 'first_written_video_timestamp', 0)
-    self.last_written_audio_timestamp = GetValueFromQueryResult(
-        query_result, 'last_written_audio_timestamp', 0)
-    self.last_written_video_timestamp = GetValueFromQueryResult(
-        query_result, 'last_written_video_timestamp', 0)
-    self.video_width = GetValueFromQueryResult(query_result, 'video_width', 0)
-    self.video_height = GetValueFromQueryResult(query_result, 'video_height', 0)
-    self.is_audio_eos_written = GetValueFromQueryResult(query_result,
-                                                        'is_audio_eos_written',
-                                                        False)
-    self.is_video_eos_written = GetValueFromQueryResult(query_result,
-                                                        'is_video_eos_written',
-                                                        False)
-    # If |pipeline_status| is not 0, it indicates there's an error
-    # in the pipeline.
-    self.pipeline_status = GetValueFromQueryResult(query_result,
-                                                   'pipeline_status', 0)
-    self.error_message = GetValueFromQueryResult(query_result, 'error_message',
-                                                 '')
-
-  def __eq__(self, other):
-    if not isinstance(other, self.__class__):
-      raise NotImplementedError
-    return (self.identifier == other.identifier and
-            self.is_started == other.is_started and
-            self.is_suspended == other.identifier and
-            self.is_stopped == other.identifier and
-            self.is_ended == other.is_ended and
-            self.player_state == other.player_state and
-            self.volume == other.volume and
-            self.playback_rate == other.playback_rate and
-            self.duration == other.duration and
-            self.last_media_time == other.last_media_time and
-            self.max_video_capabilities == other.max_video_capabilities and
-            self.seek_time == other.seek_time and
-            self.first_written_audio_timestamp
-            == other.first_written_audio_timestamp and
-            self.first_written_video_timestamp
-            == other.first_written_video_timestamp and
-            self.last_written_audio_timestamp
-            == other.last_written_audio_timestamp and
-            self.last_written_video_timestamp
-            == other.last_written_video_timestamp and
-            self.video_width == other.video_width and
-            self.video_height == other.video_height and
-            self.is_audio_eos_written == other.is_audio_eos_written and
-            self.is_video_eos_written == other.is_video_eos_written and
-            self.pipeline_status == other.pipeline_status and
-            self.error_message == other.error_message)
-
-  def __ne__(self, other):
-    return not self.__eq__(other)
-
-  @staticmethod
-  def QueryJsCode():
-    return """
-      const primary_pipeline_keys = h5vcc.cVal.keys().filter(key =>
-        key.startsWith("Media.Pipeline.") &&
-        key.endsWith("MaxVideoCapabilities") &&
-        h5vcc.cVal.getValue(key).length === 0)
-      if (primary_pipeline_keys.length == 0) {
-        return "null"
-      }
-      const key_prefix = primary_pipeline_keys[0].slice(0, -".MaxVideoCapabilities".length)
-      return {
-        identifier: key_prefix.slice("Media.Pipeline.".length),
-        is_started: h5vcc.cVal.getValue(key_prefix + '.Started'),
-        is_suspended: h5vcc.cVal.getValue(key_prefix + '.Suspended'),
-        is_stopped: h5vcc.cVal.getValue(key_prefix + '.Stopped'),
-        is_ended: h5vcc.cVal.getValue(key_prefix + '.Ended'),
-        player_state: h5vcc.cVal.getValue(key_prefix + '.PlayerState'),
-        volume: h5vcc.cVal.getValue(key_prefix + '.Volume'),
-        playback_rate: h5vcc.cVal.getValue(key_prefix + '.PlaybackRate'),
-        duration: h5vcc.cVal.getValue(key_prefix + '.Duration'),
-        last_media_time: h5vcc.cVal.getValue(key_prefix + '.LastMediaTime'),
-        max_video_capabilities: h5vcc.cVal.getValue(key_prefix + '.MaxVideoCapabilities'),
-        seek_time: h5vcc.cVal.getValue(key_prefix + '.SeekTime'),
-        first_written_audio_timestamp:
-            h5vcc.cVal.getValue(key_prefix + '.FirstWrittenAudioTimestamp'),
-        first_written_video_timestamp:
-            h5vcc.cVal.getValue(key_prefix + '.FirstWrittenVideoTimestamp'),
-        last_written_audio_timestamp:
-            h5vcc.cVal.getValue(key_prefix + '.LastWrittenAudioTimestamp'),
-        last_written_video_timestamp:
-            h5vcc.cVal.getValue(key_prefix + '.LastWrittenVideoTimestamp'),
-        video_width: h5vcc.cVal.getValue(key_prefix + '.VideoWidth'),
-        video_height: h5vcc.cVal.getValue(key_prefix + '.VideoHeight'),
-        is_audio_eos_written: h5vcc.cVal.getValue(key_prefix + '.IsAudioEOSWritten'),
-        is_video_eos_written: h5vcc.cVal.getValue(key_prefix + '.IsVideoEOSWritten'),
-        pipeline_status: h5vcc.cVal.getValue(key_prefix + '.PipelineStatus'),
-        error_message: h5vcc.cVal.getValue(key_prefix + '.ErrorMessage'),
-      }
-    """
-
-  def IsPlaying(self):
-    return (len(self.identifier) > 0 and self.is_started and
-            not self.is_ended and not self.is_suspended and not self.is_stopped)
-
-
-class MediaSessionPlaybackState(enum.Enum):
-  """Set of media session playback states."""
-  NONE = 0
-  PAUSED = 1
-  PLAYING = 2
-
-  # Aliases to convert string to the enum.
-  none = 0
-  paused = 1
-  playing = 2
-
-
-class MediaSessionState():
-  """
-    The info we get from HTML MediaSession. We update the states periodically
-    via webdriver.
-  """
-
-  def __init__(self, query_result=None):
-    if not query_result is None and not isinstance(query_result, dict):
-      raise NotImplementedError
-
-    metadata = None
-    if isinstance(query_result, dict) and query_result.has_key('metadata'):
-      metadata = query_result['metadata']
-
-    self.title = GetValueFromQueryResult(metadata, 'title', '')
-    self.artist = GetValueFromQueryResult(metadata, 'artist', '')
-    self.album = GetValueFromQueryResult(metadata, 'album', '')
-    self.playback_state = MediaSessionPlaybackState[GetValueFromQueryResult(
-        query_result, 'playbackState', 'none')]
-
-  def __eq__(self, other):
-    if not isinstance(other, self.__class__):
-      raise NotImplementedError
-    return (self.title == other.title and self.artist == other.artist and
-            self.album == other.album and
-            self.playback_state == other.playback_state)
-
-  def __ne__(self, other):
-    if self.__eq__(other):
-      return False
-    return True
-
-  @staticmethod
-  def QueryJsCode():
-    return """
-      return {
-          metadata: navigator.mediaSession.metadata,
-          playbackState: navigator.mediaSession.playbackState,
-        }
-    """
-
-
-class VideoElementState():
-  """
-    The info we get from HTML video element. We update the states periodically
-    via webdriver.
-  """
-
-  def __init__(self, query_result=None):
-    # If there's no player existing, the query return unicode code "null".
-    if (not query_result is None and not query_result == u'null' and
-        not isinstance(query_result, dict)):
-      raise NotImplementedError
-
-    self.has_player = isinstance(query_result, dict)
-    self.current_time = GetValueFromQueryResult(query_result, 'current_time',
-                                                0.0)
-    self.duration = GetValueFromQueryResult(query_result, 'duration', 0.0)
-    self.paused = GetValueFromQueryResult(query_result, 'paused', False)
-    self.playback_rate = GetValueFromQueryResult(query_result, 'playback_rate',
-                                                 0.0)
-    self.volume = GetValueFromQueryResult(query_result, 'volume', 0.0)
-    self.dropped_video_frames = GetValueFromQueryResult(query_result,
-                                                        'dropped_video_frames',
-                                                        0)
-    self.total_video_frames = GetValueFromQueryResult(query_result,
-                                                      'total_video_frames', 0)
-
-  def __eq__(self, other):
-    if not isinstance(other, self.__class__):
-      raise NotImplementedError
-    return (self.has_player == other.has_player and
-            self.current_time == other.current_time and
-            self.duration == other.duration and self.paused == other.paused and
-            self.playback_rate == other.playback_rate and
-            self.volume == other.volume and
-            self.dropped_video_frames == other.dropped_video_frames and
-            self.total_video_frames == other.total_video_frames)
-
-  def __ne__(self, other):
-    return not self.__eq__(other)
-
-  @staticmethod
-  def QueryJsCode():
-    # Looking for a full screen video tag and return that as the primary player.
-    return """
-      const players = document.querySelectorAll("video");
-      if (players && players.length > 0) {
-        for (let i = 0; i < players.length; i++) {
-          const player = players[i]
-          const rect = player.getBoundingClientRect()
-          if (rect.width === window.innerWidth ||
-              rect.height === window.innerHeight) {
-            const quality = player.getVideoPlaybackQuality();
-            return {
-              current_time: player.currentTime,
-              duration: player.duration,
-              paused: player.paused,
-              playback_rate: player.playbackRate,
-              volume: player.volume,
-              dropped_video_frames: quality.droppedVideoFrames,
-              total_video_frames: quality.totalVideoFrames,
-            }
-          }
-        }
-      }
-      return "null"
-    """
-
-
-class PlayerStateHandler():
-  """The handler of player state."""
-
-  def __init__(self, app):
-    self.app = app
-    self.state_change_handlers = []
-    self.player_state = type('', (), {})()
-    self.player_state.pipeline_state = PipelineState()
-    self.player_state.media_session_state = MediaSessionState()
-    self.player_state.video_element_state = VideoElementState()
-
-    self.app.RegisterPeriodicQuery('MediaPipeline', PipelineState.QueryJsCode(),
-                                   self._PipelineStateQueryHandler)
-    self.app.RegisterPeriodicQuery('MediaSession',
-                                   MediaSessionState.QueryJsCode(),
-                                   self._MediaSessionStateQueryHandler)
-    self.app.RegisterPeriodicQuery('VideoElement',
-                                   VideoElementState.QueryJsCode(),
-                                   self._VideoElementStateQueryHandler)
-
-  def AddPlayerStateChangeHandler(self, handler):
-    self.state_change_handlers.append(handler)
-
-  def RemovePlayerStateChangeHandler(self, handler):
-    self.state_change_handlers.remove(handler)
-
-  def IsPlayerPlaying(self):
-    return self.player_state.pipeline_state.IsPlaying()
-
-  def _NotifyHandlersOnStateChanged(self):
-    for handler in self.state_change_handlers:
-      handler(self, self.player_state)
-
-  def _PipelineStateQueryHandler(self, app, query_name, query_result):
-    # Note that the callback is invoked on a different thread.
-    del app, query_name  # Unused here.
-    new_state = PipelineState(query_result)
-    if self.player_state.pipeline_state != new_state:
-      self.player_state.pipeline_state = new_state
-      self._NotifyHandlersOnStateChanged()
-
-  def _MediaSessionStateQueryHandler(self, app, query_name, query_result):
-    # Note that the callback is invoked on a different thread.
-    del app, query_name  # Unused here.
-    new_state = MediaSessionState(query_result)
-    if self.player_state.media_session_state != new_state:
-      self.player_state.media_session_state = new_state
-      self._NotifyHandlersOnStateChanged()
-
-  def _VideoElementStateQueryHandler(self, app, query_name, query_result):
-    # Note that the callback is invoked on a different thread.
-    del app, query_name  # Unused here.
-    new_state = VideoElementState(query_result)
-    if self.player_state.video_element_state != new_state:
-      self.player_state.video_element_state = new_state
-      self._NotifyHandlersOnStateChanged()
-
-
-class AdsState(enum.IntEnum):
-  """Set of ads states. The numeric values are used in ads state query."""
-  NONE = 0
-  PLAYING = 1
-  PLAYING_AND_SKIPPABLE = 2
-
-
-class TestApp():
-  """
-    A class encapsulating fundamental functions to control and query data
-    from Cobalt application.
-  """
-
-  def __init__(self,
-               launcher_params,
-               url,
-               supported_features,
-               log_file=None,
-               target_params=None,
-               success_message=None):
-    self.supported_features = supported_features
-    self.runner = cobalt_runner.CobaltRunner(
-        launcher_params=launcher_params,
-        url=url,
-        log_handler=self._OnNewLogLine,
-        log_file=log_file,
-        target_params=target_params,
-        success_message=success_message)
-    self.log_handlers = []
-    self.is_queries_changed = False
-    self.periodic_queries = {}
-    self.periodic_queries_lock = threading.Lock()
-    self.should_exit = threading.Event()
-    self.periodic_query_thread = threading.Thread(
-        target=self._RunPeriodicQueries)
-
-    self.app_state_handler = ApplicationStateHandler(self)
-    self.player_state_handler = PlayerStateHandler(self)
-
-  def __enter__(self):
-    try:
-      self.runner.__enter__()
-    except Exception:
-      # Potentially from thread.interrupt_main(). No need to start
-      # query thread.
-      return self
-
-    self.periodic_query_thread.start()
-    return self
-
-  def __exit__(self, *args):
-    self.should_exit.set()
-    self.periodic_query_thread.join(THREAD_EXIT_TIMEOUT_SECONDS)
-    return self.runner.__exit__(*args)
-
-  def _OnNewLogLine(self, line):
-    # Note that the function is called on cobalt runner reader thread.
-    # pass
-    self._NotifyHandlersOnNewLogLine(line)
-
-  def _NotifyHandlersOnNewLogLine(self, line):
-    for handler in self.log_handlers:
-      handler(self, line)
-
-  def ApplicationState(self):
-    return self.app_state_handler.app_state
-
-  def PlayerState(self):
-    return self.player_state_handler.player_state
-
-  def CurrentMediaTime(self):
-    # Convert timestamp from millisecond to second.
-    return float(self.PlayerState().pipeline_state.last_media_time) / 1000000.0
-
-  def Suspend(self):
-    if not self.supported_features[Features.SUSPEND_AND_RESUME]:
-      raise RuntimeError('Suspend is not supported.')
-    if not self.runner.launcher_is_running:
-      raise RuntimeError('Runner is not running.')
-    self.runner.SendSystemSuspend()
-
-  def Resume(self):
-    if not self.supported_features[Features.SUSPEND_AND_RESUME]:
-      raise RuntimeError('Resume is not supported.')
-    if not self.runner.launcher_is_running:
-      raise RuntimeError('Runner is not running.')
-    self.runner.SendSystemResume()
-
-  def SendKeys(self, keys):
-    if not self.supported_features[Features.SEND_KEYS]:
-      raise RuntimeError('SendKeys is not supported.')
-    if not self.runner.launcher_is_running:
-      raise RuntimeError('Runner is not running.')
-    self.runner.SendKeys(keys)
-
-  # The handler will receive parameters (TestApp, String).
-  def AddLogHandler(self, handler):
-    self.log_handlers.append(handler)
-
-  def RemoveLogHandler(self, handler):
-    self.log_handlers.remove(handler)
-
-  # The handler will receive parameters (TestApp, ApplicationState).
-  def AddAppStateChangeHandler(self, handler):
-    self.app_state_handler.AddAppStateChangeHandler(handler)
-
-  def RemoveAppStateChangeHandler(self, handler):
-    self.app_state_handler.RemoveAppStateChangeHandler(handler)
-
-  # The handler will receive parameters (TestApp, PlayerState).
-  def AddPlayerStateChangeHandler(self, handler):
-    self.player_state_handler.AddPlayerStateChangeHandler(handler)
-
-  def RemovePlayerStateChangeHandler(self, handler):
-    self.player_state_handler.RemoveAppStateChangeHandler(handler)
-
-  # The handler will receive parameters (TestApp, String, Dictionary).
-  def RegisterPeriodicQuery(self, query_name, query_js_code, result_handler):
-    if not isinstance(query_name, str):
-      raise RuntimeError('Query name must be a string.')
-    if self.periodic_queries.get(query_name):
-      raise RuntimeError('Duplicate query name is not allowed.')
-    # TODO: Validate js code.
-    with self.periodic_queries_lock:
-      self.is_queries_changed = True
-      self.periodic_queries[query_name] = (query_js_code, result_handler)
-
-  def UnregisterPeriodicQuery(self, query_name):
-    if not isinstance(query_name, str):
-      raise RuntimeError('Query name must be a string.')
-    with self.periodic_queries_lock:
-      self.is_queries_changed = True
-      self.periodic_queries.pop(query_name)
-
-  def _RunPeriodicQueries(self):
-    while True:
-      if self.should_exit.is_set():
-        break
-      # Wait until webdriver client attached to Cobalt.
-      if self.runner.test_script_started.is_set():
-        with self.periodic_queries_lock:
-          local_is_queries_changed = self.is_queries_changed
-          local_periodic_queries = self.periodic_queries
-          self.is_queries_changed = False
-        # Skip if there's no registered query.
-        if len(local_periodic_queries) == 0:
-          continue
-        # Generate javascript code and execute it.
-        js_code = self._GeneratePeriodicQueryJsCode(local_is_queries_changed,
-                                                    local_periodic_queries)
-        try:
-          result = self.runner.webdriver.execute_script(js_code)
-        except Exception as e:
-          raise RuntimeError('Periodic queries failed with error (%s)' %
-                             (str(e)))
-
-        for query_name in local_periodic_queries.keys():
-          if not result.get(query_name):
-            raise RuntimeError(
-                'There\'s something wrong in the queries result.')
-          local_periodic_queries[query_name][1](self, query_name,
-                                                result[query_name])
-
-      time.sleep(PERIODIC_QUERIES_INTERVAL_SECONDS)
-
-  _PERIODIC_QUERIES_JS_CODE = """
-    var ret = {}
-    for(var key in _media_integration_testing_queries) {
-      ret[key] = _media_integration_testing_queries[key]()
-    }
-    return ret
-  """
-
-  def _GeneratePeriodicQueryJsCode(self, is_queries_changed, periodic_queries):
-    js_code = ''
-    # Update registered queries at host side.
-    if is_queries_changed:
-      js_code += '_media_integration_testing_queries = {'
-      for query_name in periodic_queries:
-        js_code += query_name + ': function() {'
-        js_code += periodic_queries[query_name][0]
-        js_code += '},'
-      js_code += '}'
-    # Run queries and collect results.
-    js_code += self._PERIODIC_QUERIES_JS_CODE
-    return js_code
-
-  # The first input of |state_lambda| is an instance of TestApp.
-  def WaitUntilReachState(self, state_lambda, timeout=WAIT_UNTIL_REACH_STATE_DEFAULT_TIMEOUT_SECONDS):
-    start_time = time.time()
-    while not state_lambda(self) and time.time() - start_time < timeout:
-      time.sleep(WAIT_INTERVAL_SECONDS)
-    execute_interval = time.time() - start_time
-    if execute_interval > timeout:
-      raise RuntimeError('WaitUntilReachState timed out after (%f) seconds.' %
-                         (execute_interval))
-
-  def WaitUntilPlayerStart(self,
-                           timeout=WAIT_UNTIL_REACH_STATE_DEFAULT_TIMEOUT_SECONDS
-                          ):
-    self.WaitUntilReachState(
-        lambda _app: _app.player_state_handler.IsPlayerPlaying(), timeout)
-
-  # TODO: Need to verify if it works without corp network.
-  # TODO: Needs to recognize and skip survery.
-  """
-    The return values of the query, exact mapping of AdsState defined earlier
-    in this file.
-  """
-  _GET_ADS_STATE_QUERY_JS_CODE = """
-    if( document.getElementsByTagName("YTLR-AD-PREVIEW-RENDERER").length > 0 ) {
-      return 1
-    }
-    else if( document.getElementsByTagName("YTLR-SKIP-BUTTON-RENDERER").length > 0 ) {
-      return 2
-    }
-    return 0
-  """
-
-  def GetAdsState(self):
-    if not self.runner.test_script_started.is_set():
-      raise RuntimeError('Webdriver is not ready yet')
-    try:
-      result = self.runner.webdriver.execute_script(
-          self._GET_ADS_STATE_QUERY_JS_CODE)
-    except Exception as e:
-      raise RuntimeError('Ads query failed with error (%s)' % (str(e)))
-    return AdsState(result)
-
-  def WaitUntilAdsEnd(self, timeout=WAIT_UNTIL_ADS_END_DEFAULT_TIMEOUT_SECONDS):
-    start_time = time.time()
-    # There may be multiple ads and we need to wait all of them finished.
-    while time.time() - start_time < timeout:
-      # Wait until the content starts, otherwise the ads state may not be right.
-      self.WaitUntilReachState(lambda _app: _app.CurrentMediaTime() > 0,
-                               timeout)
-      ads_state = self.GetAdsState()
-      if ads_state == AdsState.NONE:
-        break
-      while ads_state != AdsState.NONE and time.time() - start_time < timeout:
-        ads_state = self.GetAdsState()
-        if ads_state == AdsState.PLAYING_AND_SKIPPABLE:
-          self.SendKeys(webdriver_keys.Keys.ENTER)
-          logging.info('Send enter key event to skip the ad.')
-        time.sleep(WAIT_INTERVAL_SECONDS)
-
-    execute_interval = time.time() - start_time
-    if execute_interval > timeout:
-      raise RuntimeError(
-          'WaitUntilReachState timed out after (%f) seconds, ads_state: (%d).' %
-          (execute_interval, ads_state))
-
-  def WaitUntilMediaTimeReached(
-      self,
-      media_time,
-      timeout=WAIT_UNTIL_MEDIA_TIME_REACHED_DEFAULT_TIMEOUT_SECONDS):
-    start_media_time = self.CurrentMediaTime()
-    if start_media_time > media_time:
-      return
-
-    # Wait until playback starts, otherwise playback rate could be 0.
-    self.WaitUntilReachState(
-        lambda _app: _app.CurrentMediaTime() > start_media_time, timeout)
-
-    playback_rate = self.PlayerState().pipeline_state.playback_rate
-    if playback_rate == 0:
-      raise NotImplementedError
-
-    adjusted_timeout = (media_time -
-                        self.CurrentMediaTime()) / playback_rate + timeout
-    self.WaitUntilReachState(lambda _app: _app.CurrentMediaTime() > media_time,
-                             adjusted_timeout)
diff --git a/src/cobalt/media_integration_tests/test_case.py b/src/cobalt/media_integration_tests/test_case.py
deleted file mode 100644
index 09e2ec2..0000000
--- a/src/cobalt/media_integration_tests/test_case.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# Copyright 2021 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""The module of base media integration test case."""
-
-import logging
-import unittest
-
-import _env  # pylint: disable=unused-import
-from cobalt.media_integration_tests.test_app import TestApp
-
-# Global variables
-_launcher_params = None
-_supported_features = None
-
-
-def SetLauncherParams(launcher_params):
-  global _launcher_params
-  _launcher_params = launcher_params
-
-
-def SetSupportedFeatures(supported_features):
-  global _supported_features
-  _supported_features = supported_features
-
-
-class TestCase(unittest.TestCase):
-  """The base class for media integration test cases."""
-
-  def __init__(self, *args, **kwargs):
-    super(TestCase, self).__init__(*args, **kwargs)
-    self.launcher_params = _launcher_params
-    self.supported_features = _supported_features
-
-  def setUp(self):
-    logging.info('Running %s', str(self.id()))
-
-  def tearDown(self):
-    logging.info('Done %s', str(self.id()))
-
-  def CreateCobaltApp(self, url):
-    app = TestApp(
-        launcher_params=self.launcher_params,
-        supported_features=self.supported_features,
-        url=url)
-    return app
-
-  @staticmethod
-  def IsFeatureSupported(feature):
-    global _supported_features
-    return _supported_features[feature]
diff --git a/src/cobalt/tools/automated_testing/cobalt_runner.py b/src/cobalt/tools/automated_testing/cobalt_runner.py
index 1c26ae3..d1c878b 100644
--- a/src/cobalt/tools/automated_testing/cobalt_runner.py
+++ b/src/cobalt/tools/automated_testing/cobalt_runner.py
@@ -85,8 +85,7 @@
                url,
                log_file=None,
                target_params=None,
-               success_message=None,
-               log_handler=None):
+               success_message=None):
     """CobaltRunner constructor.
 
     Args:
@@ -113,8 +112,6 @@
         'webdriver')
 
     self.launcher_params = launcher_params
-    self.log_handler = log_handler
-
     if log_file:
       self.log_file = open(log_file)
       logging.basicConfig(stream=self.log_file, level=logging.INFO)
@@ -155,14 +152,6 @@
     """Sends a system signal to put Cobalt into stopped state."""
     self.launcher.SendStop()
 
-  def SendSystemSuspend(self):
-    """Ask the system to suspend Cobalt."""
-    self.launcher.SendSystemSuspend()
-
-  def SendSystemResume(self):
-    """Ask the system to resume Cobalt."""
-    self.launcher.SendSystemResume()
-
   def SendDeepLink(self, link):
     """Sends a deep link to Cobalt."""
     return self.launcher.SendDeepLink(link)
@@ -186,21 +175,17 @@
     while True:
       line = self.launcher_read_pipe.readline()
       if line:
-        if self.log_handler is not None:
-          self.log_handler(line)
         self.log_file.write(line)
         # Calling flush() to ensure the logs are delivered timely.
         self.log_file.flush()
       else:
         break
 
-      if not self.windowdriver_created.set() and \
-          RE_WINDOWDRIVER_CREATED.search(line):
+      if RE_WINDOWDRIVER_CREATED.search(line):
         self.windowdriver_created.set()
         continue
 
-      if not self.webmodule_loaded.set() and \
-          RE_WEBMODULE_LOADED.search(line):
+      if RE_WEBMODULE_LOADED.search(line):
         self.webmodule_loaded.set()
         continue
 
diff --git a/src/cobalt/updater/configurator.cc b/src/cobalt/updater/configurator.cc
index b14e2d1..b8df78b 100644
--- a/src/cobalt/updater/configurator.cc
+++ b/src/cobalt/updater/configurator.cc
@@ -207,8 +207,7 @@
 std::vector<uint8_t> Configurator::GetRunActionKeyHash() const { return {}; }
 
 std::string Configurator::GetAppGuid() const {
-  // Omaha console app id for trunk
-  return "{A9557415-DDCD-4948-8113-C643EFCF710C}";
+  return "{6D4E53F3-CC64-4CB8-B6BD-AB0B8F300E1C}";
 }
 
 std::unique_ptr<update_client::ProtocolHandlerFactory>
diff --git a/src/cobalt/version.h b/src/cobalt/version.h
index 85eae78..94b0f68 100644
--- a/src/cobalt/version.h
+++ b/src/cobalt/version.h
@@ -35,6 +35,6 @@
 //                  release is cut.
 //.
 
-#define COBALT_VERSION "22.master.0"
+#define COBALT_VERSION "22.lts.1"
 
 #endif  // COBALT_VERSION_H_
diff --git a/src/cobalt/webdriver/keyboard.cc b/src/cobalt/webdriver/keyboard.cc
index b65672a..285dc9e 100644
--- a/src/cobalt/webdriver/keyboard.cc
+++ b/src/cobalt/webdriver/keyboard.cc
@@ -162,28 +162,6 @@
     dom::keycode::kLwin,       // kSpecialKey_Meta
 };
 
-// Besides of selenium defined special keys, we need additional special keys
-// for media control. The following utf-8 code could be provided as "keys"
-// sent to WebDriver, and should be mapped to the corresponding keyboard code.
-enum AdditionalSpecialKey {
-  kFirstAdditionalSpecialKey = 0xF000,
-  kSpecialKey_MediaNextTrack = kFirstAdditionalSpecialKey,
-  kSpecialKey_MediaPrevTrack,
-  kSpecialKey_MediaStop,
-  kSpecialKey_MediaPlayPause,
-  kLastAdditionalSpecialKey = kSpecialKey_MediaPlayPause,
-};
-
-// Mapping from an additional special keycode to virtual keycode. Subtract
-// kFirstAdditionalSpecialKey from the integer value of the WebDriver keycode
-// and index into this table.
-const int32 additional_special_keycode_mapping[] = {
-    dom::keycode::kMediaNextTrack,  // kSpecialKey_MediaNextTrack,
-    dom::keycode::kMediaPrevTrack,  // kSpecialKey_MediaPrevTrack,
-    dom::keycode::kMediaStop,       // kSpecialKey_MediaStop,
-    dom::keycode::kMediaPlayPause,  // kSpecialKey_MediaPlayPause,
-};
-
 // Check that the mapping is the expected size.
 const int kLargestMappingIndex = kLastSpecialKey - kFirstSpecialKey;
 COMPILE_ASSERT(arraysize(special_keycode_mapping) == kLargestMappingIndex + 1,
@@ -271,11 +249,6 @@
   return webdriver_key >= kFirstSpecialKey && webdriver_key < kLastSpecialKey;
 }
 
-bool IsAdditionalSpecialKey(int webdriver_key) {
-  return webdriver_key >= kFirstAdditionalSpecialKey &&
-         webdriver_key <= kLastAdditionalSpecialKey;
-}
-
 bool IsModifierKey(int webdriver_key) {
   return webdriver_key == kSpecialKey_Alt ||
          webdriver_key == kSpecialKey_Shift ||
@@ -321,15 +294,6 @@
   return KeyboardEvent::kDomKeyLocationStandard;
 }
 
-// Returns the keycode that corresponds to this additional special key.
-int32 GetAdditionalSpecialKeycode(int32 webdriver_key) {
-  DCHECK(IsAdditionalSpecialKey(webdriver_key));
-  int index = webdriver_key - kFirstAdditionalSpecialKey;
-  DCHECK_GE(index, 0);
-  DCHECK_LT(index, arraysize(additional_special_keycode_mapping));
-  return additional_special_keycode_mapping[index];
-}
-
 class KeyTranslator {
  public:
   explicit KeyTranslator(Keyboard::KeyboardEventVector* event_vector)
@@ -363,14 +327,6 @@
         KeyLocationCode location = GetSpecialKeyLocation(webdriver_key);
         AddKeyDownEvent(key_code, char_code, location);
         AddKeyUpEvent(key_code, char_code, location);
-      } else if (IsAdditionalSpecialKey(webdriver_key)) {
-        // Else if it's an additional special key, translate to key_code and
-        // send key events.
-        int32 key_code = GetAdditionalSpecialKeycode(webdriver_key);
-        int32 char_code = 0;
-        KeyLocationCode location = KeyboardEvent::kDomKeyLocationStandard;
-        AddKeyDownEvent(key_code, char_code, location);
-        AddKeyUpEvent(key_code, char_code, location);
       } else {
         DCHECK_GE(webdriver_key, 0);
         DCHECK_LT(webdriver_key, std::numeric_limits<char>::max());
diff --git a/src/cobalt/webdriver/window_driver.cc b/src/cobalt/webdriver/window_driver.cc
index 6a33518..1cb4432 100644
--- a/src/cobalt/webdriver/window_driver.cc
+++ b/src/cobalt/webdriver/window_driver.cc
@@ -479,6 +479,9 @@
     script_executor_ = base::AsWeakPtr(script_executor.get());
   }
 
+  DLOG(INFO) << "Executing: " << script.function_body();
+  DLOG(INFO) << "Arguments: " << script.argument_array();
+
   auto gc_prevented_params =
       ScriptExecutorParams::Create(global_environment, script.function_body(),
                                    script.argument_array(), async_timeout);
diff --git a/src/cobalt/websocket/web_socket_impl.cc b/src/cobalt/websocket/web_socket_impl.cc
index 39b54d2..e577c68 100644
--- a/src/cobalt/websocket/web_socket_impl.cc
+++ b/src/cobalt/websocket/web_socket_impl.cc
@@ -275,7 +275,6 @@
     SendQueueMessage message = send_queue_.front();
     size_t current_message_length = message.length - sent_size_of_top_message_;
     bool final = false;
-    bool continuation = sent_size_of_top_message_ > 0 ? true : false;
     if (current_quota_ < static_cast<int64_t>(current_message_length)) {
       // quota is not enough to send the top message.
       scoped_refptr<net::IOBuffer> new_io_buffer(
@@ -295,10 +294,7 @@
       current_quota_ -= current_message_length;
     }
     auto channel_state = websocket_channel_->SendFrame(
-        final,
-        continuation ? net::WebSocketFrameHeader::kOpCodeContinuation
-                     : message.op_code,
-        message.io_buffer, current_message_length);
+        final, message.op_code, message.io_buffer, current_message_length);
     if (channel_state == net::WebSocketChannel::CHANNEL_DELETED) {
       websocket_channel_.reset();
     }
diff --git a/src/cobalt/websocket/web_socket_impl_test.cc b/src/cobalt/websocket/web_socket_impl_test.cc
index 802ab70..8db8285 100644
--- a/src/cobalt/websocket/web_socket_impl_test.cc
+++ b/src/cobalt/websocket/web_socket_impl_test.cc
@@ -172,19 +172,12 @@
     EXPECT_CALL(*mock_channel_,
                 MockSendFrame(false, net::WebSocketFrameHeader::kOpCodeText, _,
                               kDefaultSendQuotaHighWaterMark))
-        .Times(1)
+        .Times(2)
         .WillRepeatedly(Return(net::WebSocketChannel::CHANNEL_ALIVE));
 
     EXPECT_CALL(
         *mock_channel_,
-        MockSendFrame(false, net::WebSocketFrameHeader::kOpCodeContinuation, _,
-                      kDefaultSendQuotaHighWaterMark))
-        .Times(1)
-        .WillRepeatedly(Return(net::WebSocketChannel::CHANNEL_ALIVE));
-
-    EXPECT_CALL(*mock_channel_,
-                MockSendFrame(
-                    true, net::WebSocketFrameHeader::kOpCodeContinuation, _, 1))
+        MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeText, _, 1))
         .Times(1)
         .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
   }
@@ -211,7 +204,7 @@
         .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
     EXPECT_CALL(
         *mock_channel_,
-        MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeContinuation, _, 1))
+        MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeBinary, _, 1))
         .Times(1)
         .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
     EXPECT_CALL(*mock_channel_,
@@ -220,7 +213,7 @@
         .Times(1)
         .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
     EXPECT_CALL(*mock_channel_,
-                MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeContinuation, _,
+                MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeText, _,
                               kTooMuch - (k512KB - 1)))
         .Times(1)
         .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
diff --git a/src/docker-compose.yml b/src/docker-compose.yml
index 743a169..7cb3f94 100644
--- a/src/docker-compose.yml
+++ b/src/docker-compose.yml
@@ -167,19 +167,6 @@
       USE_CCACHE: ${USE_CCACHE:-1}
       NINJA_STATUS: ${NINJA_STATUS}
 
-  linux-x64x11-sbversion12-evergreen:
-    <<: *build-common-definitions
-    build:
-      context: ./docker/linux
-      dockerfile: linux-x64x11/Dockerfile
-      args:
-        - FROM_IMAGE=cobalt-build-evergreen
-    image: cobalt-build-linux-x64x11-evergreen
-    environment:
-      <<: *shared-build-env
-      PLATFORM: linux-x64x11-sbversion-12
-      CONFIG: ${CONFIG:-debug}
-
   # Define common build container for Android
   build-android:
     <<: *build-common-definitions
diff --git a/src/docker/linux/raspi/Dockerfile b/src/docker/linux/raspi/Dockerfile
index e4f7f01..5483068 100644
--- a/src/docker/linux/raspi/Dockerfile
+++ b/src/docker/linux/raspi/Dockerfile
@@ -20,14 +20,10 @@
 # Required by the gyp build system.
 ENV RASPI_HOME=${raspi_home}
 
-# libxml2 and binutils* needed for evergreen
 RUN apt update -qqy \
     && apt install -qqy --no-install-recommends \
         g++-multilib \
         bzip2 \
-        libxml2 \
-        binutils-aarch64-linux-gnu \
-        binutils-arm-linux-gnueabi \
     && apt-get clean autoclean \
     && apt-get autoremove -y --purge \
     && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
diff --git a/src/starboard/CHANGELOG.md b/src/starboard/CHANGELOG.md
index 450d514..51a66bd 100644
--- a/src/starboard/CHANGELOG.md
+++ b/src/starboard/CHANGELOG.md
@@ -117,6 +117,7 @@
 * `SbStringFindString`
 * `SbStringGetLength`
 * `SbStringGetLengthWide`
+* `SbStringFindCharacter`
 * `SbStringParseDouble`
 * `SbStringParseSignedInteger`
 * `SbStringParseUInt64`
diff --git a/src/starboard/android/shared/video_render_algorithm.cc b/src/starboard/android/shared/video_render_algorithm.cc
index 74ed628..1b9fcc4 100644
--- a/src/starboard/android/shared/video_render_algorithm.cc
+++ b/src/starboard/android/shared/video_render_algorithm.cc
@@ -73,23 +73,6 @@
       video_decoder_->SetPlaybackRate(playback_rate);
     }
 
-    if (is_audio_eos_played) {
-      // If the audio stream has reached end of stream before the video stream,
-      // we should end the video stream immediately by only keeping one frame in
-      // the backlog.
-      bool popped = false;
-      while (frames->size() > 1) {
-        frames->pop_front();
-        popped = true;
-      }
-      if (popped) {
-        // Let the loop process the end of stream frame, if there is one.
-        continue;
-      } else {
-        break;
-      }
-    }
-
     jlong early_us = frames->front()->timestamp() - playback_time;
 
     auto system_time_ns = GetSystemNanoTime();
diff --git a/src/starboard/android/x86/gyp_configuration.py b/src/starboard/android/x86/gyp_configuration.py
index 9af8e0c..2caad96 100644
--- a/src/starboard/android/x86/gyp_configuration.py
+++ b/src/starboard/android/x86/gyp_configuration.py
@@ -43,20 +43,13 @@
           'SbMicrophoneOpenTest.*',
           'SbMicrophoneReadTest.*',
           'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.*',
+          'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest'
+          '.SunnyDaySourceForDestination/*',
           'SbMediaSetAudioWriteDurationTests/SbMediaSetAudioWriteDurationTest'
           '.WriteContinuedLimitedInput/*',
       ],
       'player_filter_tests': [
-          'AudioDecoderTests/*',
           'VideoDecoderTests/*',
-
-          'PlayerComponentsTests/PlayerComponentsTest.*/2',
-          'PlayerComponentsTests/PlayerComponentsTest.*/4',
-          'PlayerComponentsTests/PlayerComponentsTest.*/9',
-          'PlayerComponentsTests/PlayerComponentsTest.*/11',
-          'PlayerComponentsTests/PlayerComponentsTest.*/16',
-          'PlayerComponentsTests/PlayerComponentsTest.*/17',
-          'PlayerComponentsTests/PlayerComponentsTest.*/20',
-          'PlayerComponentsTests/PlayerComponentsTest.*/21',
+          'AudioDecoderTests/*',
       ],
   }
diff --git a/src/starboard/build/config/base_configuration.gni b/src/starboard/build/config/base_configuration.gni
index 8a80d04..3b798ea 100644
--- a/src/starboard/build/config/base_configuration.gni
+++ b/src/starboard/build/config/base_configuration.gni
@@ -21,7 +21,7 @@
 
   # The Starboard API version of the current build configuration. The default
   # value is meant to be overridden by a Starboard ABI file.
-  sb_api_version = 14
+  sb_api_version = 13
 
   # Enabling this variable enables pedantic levels of warnings for the current
   # toolchain.
diff --git a/src/starboard/linux/shared/gyp_configuration.py b/src/starboard/linux/shared/gyp_configuration.py
index e8a7a9f..b8f0949 100644
--- a/src/starboard/linux/shared/gyp_configuration.py
+++ b/src/starboard/linux/shared/gyp_configuration.py
@@ -121,10 +121,3 @@
           'VideoDecoderTests/VideoDecoderTest.*Invalid*',
       ],
   }
-  # Conditionally disables tests that require ipv6
-  if os.getenv('IPV6_NOT_AVAILABLE', False):
-    __FILTERED_TESTS['nplb'] = [
-        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDayDestination/1',
-        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceForDestination/1',
-        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceNotLoopback/1',
-    ]
diff --git a/src/starboard/raspi/shared/gyp_configuration.py b/src/starboard/raspi/shared/gyp_configuration.py
index 158f126..9644b4c 100644
--- a/src/starboard/raspi/shared/gyp_configuration.py
+++ b/src/starboard/raspi/shared/gyp_configuration.py
@@ -172,7 +172,36 @@
           'VideoDecoderTests/VideoDecoderTest.EndOfStreamWithoutAnyInput/0',
           'VideoDecoderTests/VideoDecoderTest.MultipleResets/0',
           # Filter failed tests.
-          'PlayerComponentsTests/PlayerComponentsTest.*',
+          'PlayerComponentsTests/PlayerComponentsTest.Preroll/0',
+          'PlayerComponentsTests/PlayerComponentsTest.Preroll/1',
+          'PlayerComponentsTests/PlayerComponentsTest.Preroll/2',
+          'PlayerComponentsTests/PlayerComponentsTest.Preroll/3',
+          'PlayerComponentsTests/PlayerComponentsTest.Sunnyday/0',
+          'PlayerComponentsTests/PlayerComponentsTest.Sunnyday/1',
+          'PlayerComponentsTests/PlayerComponentsTest.Sunnyday/2',
+          'PlayerComponentsTests/PlayerComponentsTest.Sunnyday/3',
+          'PlayerComponentsTests/PlayerComponentsTest.Pause/0',
+          'PlayerComponentsTests/PlayerComponentsTest.Pause/1',
+          'PlayerComponentsTests/PlayerComponentsTest.Pause/2',
+          'PlayerComponentsTests/PlayerComponentsTest.Pause/3',
+          'PlayerComponentsTests/PlayerComponentsTest.PlaybackRateHalf/0',
+          'PlayerComponentsTests/PlayerComponentsTest.PlaybackRateHalf/1',
+          'PlayerComponentsTests/PlayerComponentsTest.PlaybackRateHalf/2',
+          'PlayerComponentsTests/PlayerComponentsTest.PlaybackRateHalf/3',
+          'PlayerComponentsTests/PlayerComponentsTest.PlaybackRateDouble/0',
+          'PlayerComponentsTests/PlayerComponentsTest.PlaybackRateDouble/1',
+          'PlayerComponentsTests/PlayerComponentsTest.PlaybackRateDouble/2',
+          'PlayerComponentsTests/PlayerComponentsTest.PlaybackRateDouble/3',
+          'PlayerComponentsTests/PlayerComponentsTest.PlaybackRateDouble/4',
+          'PlayerComponentsTests/PlayerComponentsTest.PlaybackRateDouble/5',
+          'PlayerComponentsTests/PlayerComponentsTest.SeekForward/0',
+          'PlayerComponentsTests/PlayerComponentsTest.SeekForward/1',
+          'PlayerComponentsTests/PlayerComponentsTest.SeekForward/2',
+          'PlayerComponentsTests/PlayerComponentsTest.SeekForward/3',
+          'PlayerComponentsTests/PlayerComponentsTest.SeekBackward/0',
+          'PlayerComponentsTests/PlayerComponentsTest.SeekBackward/1',
+          'PlayerComponentsTests/PlayerComponentsTest.SeekBackward/2',
+          'PlayerComponentsTests/PlayerComponentsTest.SeekBackward/3',
 
       ],
   }