Import Cobalt 9.70858
diff --git a/src/cobalt/base/c_val.h b/src/cobalt/base/c_val.h
index 48227bf..92f8f6c 100644
--- a/src/cobalt/base/c_val.h
+++ b/src/cobalt/base/c_val.h
@@ -567,7 +567,7 @@
   std::string description_;
   CValType type_;
 
-  friend CValManager;
+  friend class base::CValManager;
 };
 
 // This is a wrapper class that marks that we wish to track a value through
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index cacaa86..934cdfb 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -51,22 +51,15 @@
 #endif  // defined(__LB_SHELL__FOR_RELEASE__)
 #include "lbshell/src/lb_memory_pages.h"
 #endif  // defined(__LB_SHELL__)
-#if defined(OS_STARBOARD)
 #include "nb/lexical_cast.h"
 #include "starboard/configuration.h"
 #include "starboard/log.h"
-#endif  // defined(OS_STARBOARD)
 
 namespace cobalt {
 namespace browser {
 
 namespace {
 const int kStatUpdatePeriodMs = 1000;
-#if defined(COBALT_BUILD_TYPE_GOLD)
-const int kLiteStatUpdatePeriodMs = 1000;
-#else
-const int kLiteStatUpdatePeriodMs = 16;
-#endif
 
 const char kDefaultURL[] = "https://www.youtube.com/tv";
 
@@ -380,8 +373,8 @@
     : message_loop_(MessageLoop::current()),
       quit_closure_(quit_closure),
       start_time_(base::TimeTicks::Now()),
-      stats_update_timer_(true, true),
-      lite_stats_update_timer_(true, true) {
+      c_val_stats_(start_time_),
+      stats_update_timer_(true, true) {
   // Check to see if a timed_trace has been set, indicating that we should
   // begin a timed trace upon startup.
   base::TimeDelta trace_duration = GetTimedTraceDuration();
@@ -406,10 +399,6 @@
   stats_update_timer_.Start(
       FROM_HERE, base::TimeDelta::FromMilliseconds(kStatUpdatePeriodMs),
       base::Bind(&Application::UpdatePeriodicStats, base::Unretained(this)));
-  lite_stats_update_timer_.Start(
-      FROM_HERE, base::TimeDelta::FromMilliseconds(kLiteStatUpdatePeriodMs),
-      base::Bind(&Application::UpdatePeriodicLiteStats,
-                 base::Unretained(this)));
 
   // Get the initial URL.
   GURL initial_url = GetInitialURL();
@@ -695,7 +684,7 @@
 #endif
 }
 
-Application::CValStats::CValStats()
+Application::CValStats::CValStats(base::TimeTicks start_time)
     : free_cpu_memory("Memory.CPU.Free", 0,
                       "Total free application CPU memory remaining."),
       used_cpu_memory("Memory.CPU.Used", 0,
@@ -704,16 +693,16 @@
       exe_memory("Memory.CPU.Exe", 0,
                  "Total memory occupied by the size of the executable."),
 #endif
+      app_start_time("Time.Cobalt.Start", start_time.ToInternalValue(),
+                     "Start time of the application in microseconds."),
       app_lifetime("Cobalt.Lifetime", base::TimeDelta(),
-                   "Application lifetime.") {
-#if defined(OS_STARBOARD)
+                   "Application lifetime in microseconds.") {
   if (SbSystemHasCapability(kSbSystemCapabilityCanQueryGPUMemoryStats)) {
     free_gpu_memory.emplace("Memory.GPU.Free", 0,
                             "Total free application GPU memory remaining.");
     used_gpu_memory.emplace("Memory.GPU.Used", 0,
                             "Total GPU memory allocated by the application.");
   }
-#endif  // defined(OS_STARBOARD)
 }
 
 void Application::RegisterUserLogs() {
@@ -764,10 +753,6 @@
   }
 }
 
-void Application::UpdatePeriodicLiteStats() {
-  c_val_stats_.app_lifetime = base::TimeTicks::Now() - start_time_;
-}
-
 math::Size Application::InitSystemWindow(CommandLine* command_line) {
   base::optional<math::Size> viewport_size;
   if (command_line->HasSwitch(browser::switches::kViewport)) {
@@ -821,6 +806,8 @@
 
 void Application::UpdatePeriodicStats() {
   TRACE_EVENT0("cobalt::browser", "Application::UpdatePeriodicStats()");
+  c_val_stats_.app_lifetime = base::TimeTicks::Now() - start_time_;
+
 #if defined(__LB_SHELL__)
   bool memory_stats_updated = false;
 #if !defined(__LB_SHELL__FOR_RELEASE__)
diff --git a/src/cobalt/browser/application.h b/src/cobalt/browser/application.h
index 2c10118..e700d83 100644
--- a/src/cobalt/browser/application.h
+++ b/src/cobalt/browser/application.h
@@ -126,7 +126,7 @@
   // Stats related
 
   struct CValStats {
-    CValStats();
+    explicit CValStats(base::TimeTicks start_time);
 
     base::CVal<base::cval::SizeInBytes, base::CValPublic> free_cpu_memory;
     base::CVal<base::cval::SizeInBytes, base::CValPublic> used_cpu_memory;
@@ -143,6 +143,7 @@
     base::CVal<base::cval::SizeInBytes, base::CValPublic> exe_memory;
 #endif
 
+    base::CVal<int64> app_start_time;
     base::CVal<base::TimeDelta, base::CValPublic> app_lifetime;
   };
 
@@ -150,7 +151,6 @@
   void UpdateAndMaybeRegisterUserAgent();
 
   void UpdatePeriodicStats();
-  void UpdatePeriodicLiteStats();
 
   math::Size InitSystemWindow(CommandLine* command_line);
 
@@ -170,7 +170,6 @@
   CValStats c_val_stats_;
 
   base::Timer stats_update_timer_;
-  base::Timer lite_stats_update_timer_;
 
   scoped_ptr<memory_tracker::MemoryTrackerTool> memory_tracker_tool_;
 };
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index 6ed091f..e0334fb 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -231,6 +231,10 @@
       web_module_loaded_(true /* manually_reset */,
                          false /* initially_signalled */),
       web_module_recreated_callback_(options.web_module_recreated_callback),
+      navigate_time_("Time.Browser.Navigate", 0,
+                     "The last time a navigation occurred."),
+      on_load_event_time_("Time.Browser.OnLoadEvent", 0,
+                          "The last time the window.OnLoad event fired."),
 #if defined(ENABLE_DEBUG_CONSOLE)
       ALLOW_THIS_IN_INITIALIZER_LIST(fuzzer_toggle_command_handler_(
           kFuzzerToggleCommand,
@@ -397,6 +401,10 @@
   // run.
   web_module_.reset(NULL);
 
+  // Wait until after the old WebModule is destroyed before setting the navigate
+  // time so that it won't be included in the time taken to load the URL.
+  navigate_time_ = base::TimeTicks::Now().ToInternalValue();
+
   // Show a splash screen while we're waiting for the web page to load.
   const math::Size& viewport_size = renderer_module_.render_target()->GetSize();
   DestroySplashScreen();
@@ -463,6 +471,7 @@
   // changed unless the corresponding benchmark logic is changed as well.
   LOG(INFO) << "Loaded WebModule";
 
+  on_load_event_time_ = base::TimeTicks::Now().ToInternalValue();
   web_module_loaded_.Signal();
 }
 
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index fcb24ad..5712937 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -310,6 +310,13 @@
   // which could occur on navigation.
   base::Closure web_module_recreated_callback_;
 
+  // The time when a URL navigation starts. This is recorded after the previous
+  // WebModule is destroyed.
+  base::CVal<int64> navigate_time_;
+
+  // The time when the WebModule's Window.onload event is fired.
+  base::CVal<int64> on_load_event_time_;
+
 #if defined(ENABLE_DEBUG_CONSOLE)
   // Possibly null, but if not, will contain a reference to an instance of
   // a debug fuzzer input device manager.
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index a3d807a..79d56ea 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -35,6 +35,7 @@
 #include "cobalt/dom/blob.h"
 #include "cobalt/dom/csp_delegate_factory.h"
 #include "cobalt/dom/element.h"
+#include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/local_storage_database.h"
 #include "cobalt/dom/mutation_observer_task_manager.h"
 #include "cobalt/dom/storage.h"
@@ -631,7 +632,15 @@
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(is_running_);
   DCHECK(script_runner_);
+
+  // JavaScript is being run. Track it in the global stats.
+  dom::GlobalStats::GetInstance()->StartJavaScriptEvent();
+
   *result = script_runner_->Execute(script_utf8, script_location);
+
+  // JavaScript is done running. Stop tracking it in the global stats.
+  dom::GlobalStats::GetInstance()->StopJavaScriptEvent();
+
   got_result->Signal();
 }
 
diff --git a/src/cobalt/browser/web_module_stat_tracker.cc b/src/cobalt/browser/web_module_stat_tracker.cc
index bb5f370..4151969 100644
--- a/src/cobalt/browser/web_module_stat_tracker.cc
+++ b/src/cobalt/browser/web_module_stat_tracker.cc
@@ -80,6 +80,7 @@
   // If this is a valid event type, then start tracking it.
   if (current_event_type_ != kEventTypeInvalid) {
     event_is_processing_ = true;
+    event_start_time_ = base::TimeTicks::Now();
 
     dom_stat_tracker_->OnStartEvent();
     layout_stat_tracker_->OnStartEvent();
@@ -128,6 +129,12 @@
           StringPrintf("Event.Count.%s.DOM.HtmlElement.Destroyed",
                        name.c_str()),
           0, "Number of HTML elements destroyed."),
+      count_dom_html_elements_added(
+          StringPrintf("Event.Count.%s.DOM.HtmlElement.Added", name.c_str()), 0,
+          "Number of HTML elements added to document."),
+      count_dom_html_elements_removed(
+          StringPrintf("Event.Count.%s.DOM.HtmlElement.Removed", name.c_str()),
+          0, "Number of HTML elements removed from document."),
       count_dom_update_matching_rules(
           StringPrintf("Event.Count.%s.DOM.HtmlElement.UpdateMatchingRules",
                        name.c_str()),
@@ -225,6 +232,8 @@
 
 void WebModuleStatTracker::EndCurrentEvent(bool was_render_tree_produced) {
   if (current_event_type_ == kEventTypeInvalid) {
+    dom_stat_tracker_->OnEndEvent();
+    layout_stat_tracker_->OnEndEvent();
     return;
   }
 
@@ -240,6 +249,10 @@
       dom_stat_tracker_->html_elements_created_count();
   event_stats->count_dom_html_elements_destroyed =
       dom_stat_tracker_->html_elements_destroyed_count();
+  event_stats->count_dom_html_elements_added =
+      dom_stat_tracker_->html_elements_added_to_document_count();
+  event_stats->count_dom_html_elements_removed =
+      dom_stat_tracker_->html_elements_removed_from_document_count();
   event_stats->count_dom_update_matching_rules =
       dom_stat_tracker_->update_matching_rules_count();
   event_stats->count_dom_update_computed_style =
@@ -283,17 +296,31 @@
           layout::LayoutStatTracker::kStopWatchTypeRenderAndAnimate);
 
 #if defined(ENABLE_WEBDRIVER)
+  // Include the event's numbers in the total counts.
+  int html_elements_count = dom_stat_tracker_->html_elements_count() +
+                            dom_stat_tracker_->html_elements_created_count() -
+                            dom_stat_tracker_->html_elements_destroyed_count();
+  int document_html_elements_count =
+      dom_stat_tracker_->document_html_elements_count() +
+      dom_stat_tracker_->html_elements_added_to_document_count() -
+      dom_stat_tracker_->html_elements_removed_from_document_count();
+  int layout_boxes_count = layout_stat_tracker_->total_boxes() +
+                           layout_stat_tracker_->boxes_created_count() -
+                           layout_stat_tracker_->boxes_destroyed_count();
+
   // When the Webdriver is enabled, all of the event's values are stored within
   // a single string representing a dictionary of key-value pairs. This allows
   // the Webdriver to query a single CVal to retrieve all of the event's values.
   std::ostringstream oss;
   oss << "{"
+      << "\"StartTime\":" << event_start_time_.ToInternalValue() << ", "
       << "\"ProducedRenderTree\":" << was_render_tree_produced << ", "
       << "\"CntDomEventListeners\":"
       << dom::GlobalStats::GetInstance()->GetNumEventListeners() << ", "
       << "\"CntDomNodes\":" << dom::GlobalStats::GetInstance()->GetNumNodes()
       << ", "
-      << "\"CntDomHtmlElements\":" << dom_stat_tracker_->total_html_elements()
+      << "\"CntDomHtmlElements\":" << html_elements_count << ", "
+      << "\"CntDomDocumentHtmlElements\":" << document_html_elements_count
       << ", "
       << "\"CntDomHtmlElementsCreated\":"
       << dom_stat_tracker_->html_elements_created_count() << ", "
@@ -306,7 +333,7 @@
       << "\"CntDomGeneratePseudoComputedStyle\":"
       << dom_stat_tracker_->generate_pseudo_element_computed_style_count()
       << ", "
-      << "\"CntLayoutBoxes\":" << layout_stat_tracker_->total_boxes() << ", "
+      << "\"CntLayoutBoxes\":" << layout_boxes_count << ", "
       << "\"CntLayoutBoxesCreated\":"
       << layout_stat_tracker_->boxes_created_count() << ", "
       << "\"CntLayoutUpdateSize\":" << layout_stat_tracker_->update_size_count()
diff --git a/src/cobalt/browser/web_module_stat_tracker.h b/src/cobalt/browser/web_module_stat_tracker.h
index 3956a52..08ae658 100644
--- a/src/cobalt/browser/web_module_stat_tracker.h
+++ b/src/cobalt/browser/web_module_stat_tracker.h
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "base/memory/scoped_vector.h"
+#include "base/time.h"
 #include "cobalt/base/c_val.h"
 #include "cobalt/base/stop_watch.h"
 #include "cobalt/dom/dom_stat_tracker.h"
@@ -83,6 +84,8 @@
     // Count-related
     base::CVal<int, base::CValPublic> count_dom_html_elements_created;
     base::CVal<int, base::CValPublic> count_dom_html_elements_destroyed;
+    base::CVal<int, base::CValPublic> count_dom_html_elements_added;
+    base::CVal<int, base::CValPublic> count_dom_html_elements_removed;
     base::CVal<int, base::CValPublic> count_dom_update_matching_rules;
     base::CVal<int, base::CValPublic> count_dom_update_computed_style;
     base::CVal<int, base::CValPublic>
@@ -144,7 +147,8 @@
 
   std::string name_;
 
-  base::CVal<bool, base::CValPublic> event_is_processing_;
+  base::CVal<bool> event_is_processing_;
+  base::TimeTicks event_start_time_;
 };
 
 }  // namespace browser
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 210cbad..b145a60 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-62469
\ No newline at end of file
+70858
\ No newline at end of file
diff --git a/src/cobalt/dom/animation_frame_request_callback_list.cc b/src/cobalt/dom/animation_frame_request_callback_list.cc
index 6108afc..e20138e 100644
--- a/src/cobalt/dom/animation_frame_request_callback_list.cc
+++ b/src/cobalt/dom/animation_frame_request_callback_list.cc
@@ -15,6 +15,7 @@
 #include "cobalt/dom/animation_frame_request_callback_list.h"
 
 #include "base/debug/trace_event.h"
+#include "cobalt/dom/global_stats.h"
 
 namespace cobalt {
 namespace dom {
@@ -44,12 +45,18 @@
   TRACE_EVENT1("cobalt::dom", "Window::RunAnimationFrameCallbacks()",
                "number_of_callbacks", frame_request_callbacks_.size());
 
+  // The callbacks are now being run. Track it in the global stats.
+  GlobalStats::GetInstance()->StartJavaScriptEvent();
+
   for (InternalList::const_iterator iter = frame_request_callbacks_.begin();
        iter != frame_request_callbacks_.end(); ++iter) {
     if (!(*iter)->cancelled) {
       (*iter)->callback.value().Run(animation_time);
     }
   }
+
+  // The callbacks are done running. Stop tracking it in the global stats.
+  GlobalStats::GetInstance()->StopJavaScriptEvent();
 }
 
 bool AnimationFrameRequestCallbackList::HasPendingCallbacks() const {
diff --git a/src/cobalt/dom/dom_stat_tracker.cc b/src/cobalt/dom/dom_stat_tracker.cc
index cadf09e..e2e1219 100644
--- a/src/cobalt/dom/dom_stat_tracker.cc
+++ b/src/cobalt/dom/dom_stat_tracker.cc
@@ -20,17 +20,28 @@
 namespace dom {
 
 DomStatTracker::DomStatTracker(const std::string& name)
-    : total_html_elements_(
-          StringPrintf("Count.%s.DOM.HtmlElement", name.c_str()), 0,
+    : html_elements_count_(
+          StringPrintf("Count.%s.DOM.HtmlElement.Total", name.c_str()), 0,
           "Total number of HTML elements."),
+      document_html_elements_count_(
+          StringPrintf("Count.%s.DOM.HtmlElement.Document", name.c_str()), 0,
+          "Number of HTML elements in the document."),
       is_event_active_(false),
       event_video_start_delay_stop_watch_(kStopWatchTypeEventVideoStartDelay,
                                           base::StopWatch::kAutoStartOff, this),
       event_video_start_delay_(
           StringPrintf("Event.Duration.%s.DOM.VideoStartDelay", name.c_str()),
           base::TimeDelta(), "Total delay between event and video starting."),
+      script_element_execute_count_(
+          StringPrintf("Count.%s.DOM.HtmlScriptElement.Execute", name.c_str()),
+          0, "Count of HTML script element execute calls."),
+      script_element_execute_time_(
+          StringPrintf("Time.%s.DOM.HtmlScriptElement.Execute", name.c_str()),
+          0, "Time of the last HTML script element execute."),
       html_elements_created_count_(0),
       html_elements_destroyed_count_(0),
+      html_elements_inserted_into_document_count_(0),
+      html_elements_removed_from_document_count_(0),
       update_matching_rules_count_(0),
       update_computed_style_count_(0),
       generate_html_element_computed_style_count_(0),
@@ -41,8 +52,10 @@
 DomStatTracker::~DomStatTracker() {
   FlushPeriodicTracking();
 
-  // Verify that all of the elements were destroyed.
-  DCHECK_EQ(total_html_elements_, 0);
+  // Verify that all of the elements were removed from the document and
+  // destroyed.
+  DCHECK_EQ(html_elements_count_, 0);
+  DCHECK_EQ(document_html_elements_count_, 0);
 
   event_video_start_delay_stop_watch_.Stop();
 }
@@ -84,12 +97,25 @@
   }
 }
 
+void DomStatTracker::OnHtmlScriptElementExecuted() {
+  ++script_element_execute_count_;
+  script_element_execute_time_ = base::TimeTicks::Now().ToInternalValue();
+}
+
 void DomStatTracker::OnHtmlElementCreated() { ++html_elements_created_count_; }
 
 void DomStatTracker::OnHtmlElementDestroyed() {
   ++html_elements_destroyed_count_;
 }
 
+void DomStatTracker::OnHtmlElementInsertedIntoDocument() {
+  ++html_elements_inserted_into_document_count_;
+}
+
+void DomStatTracker::OnHtmlElementRemovedFromDocument() {
+  ++html_elements_removed_from_document_count_;
+}
+
 void DomStatTracker::OnUpdateMatchingRules() { ++update_matching_rules_count_; }
 
 void DomStatTracker::OnUpdateComputedStyle() { ++update_computed_style_count_; }
@@ -117,12 +143,16 @@
 
 void DomStatTracker::FlushPeriodicTracking() {
   // Update the CVals before clearing the periodic values.
-  total_html_elements_ +=
+  html_elements_count_ +=
       html_elements_created_count_ - html_elements_destroyed_count_;
+  document_html_elements_count_ += html_elements_inserted_into_document_count_ -
+                                   html_elements_removed_from_document_count_;
 
   // Now clear the values.
   html_elements_created_count_ = 0;
   html_elements_destroyed_count_ = 0;
+  html_elements_inserted_into_document_count_ = 0;
+  html_elements_removed_from_document_count_ = 0;
   update_matching_rules_count_ = 0;
   update_computed_style_count_ = 0;
   generate_html_element_computed_style_count_ = 0;
diff --git a/src/cobalt/dom/dom_stat_tracker.h b/src/cobalt/dom/dom_stat_tracker.h
index 6e34ab2..35cedb7 100644
--- a/src/cobalt/dom/dom_stat_tracker.h
+++ b/src/cobalt/dom/dom_stat_tracker.h
@@ -42,16 +42,22 @@
   void OnEndEvent();
 
   void OnHtmlVideoElementPlaying();
+  void OnHtmlScriptElementExecuted();
 
   // Periodic count-related
   void OnHtmlElementCreated();
   void OnHtmlElementDestroyed();
+  void OnHtmlElementInsertedIntoDocument();
+  void OnHtmlElementRemovedFromDocument();
   void OnUpdateMatchingRules();
   void OnUpdateComputedStyle();
   void OnGenerateHtmlElementComputedStyle();
   void OnGeneratePseudoElementComputedStyle();
 
-  int total_html_elements() const { return total_html_elements_; }
+  int html_elements_count() const { return html_elements_count_; }
+  int document_html_elements_count() const {
+    return document_html_elements_count_;
+  }
 
   int html_elements_created_count() const {
     return html_elements_created_count_;
@@ -59,6 +65,12 @@
   int html_elements_destroyed_count() const {
     return html_elements_destroyed_count_;
   }
+  int html_elements_added_to_document_count() const {
+    return html_elements_inserted_into_document_count_;
+  }
+  int html_elements_removed_from_document_count() const {
+    return html_elements_removed_from_document_count_;
+  }
   int update_matching_rules_count() const {
     return update_matching_rules_count_;
   }
@@ -84,7 +96,8 @@
   void FlushPeriodicTracking();
 
   // Count cvals that are updated when the periodic tracking is flushed.
-  base::CVal<int, base::CValPublic> total_html_elements_;
+  base::CVal<int, base::CValPublic> html_elements_count_;
+  base::CVal<int, base::CValPublic> document_html_elements_count_;
 
   // Event-related
   bool is_event_active_;
@@ -93,10 +106,16 @@
   base::StopWatch event_video_start_delay_stop_watch_;
   base::CVal<base::TimeDelta> event_video_start_delay_;
 
+  // Count of HtmlScriptElement::Execute() calls and time of last call.
+  base::CVal<int> script_element_execute_count_;
+  base::CVal<int64> script_element_execute_time_;
+
   // Periodic counts. The counts are cleared after the CVals are updated in
   // |FlushPeriodicTracking|.
   int html_elements_created_count_;
   int html_elements_destroyed_count_;
+  int html_elements_inserted_into_document_count_;
+  int html_elements_removed_from_document_count_;
   int update_matching_rules_count_;
   int update_computed_style_count_;
   int generate_html_element_computed_style_count_;
diff --git a/src/cobalt/dom/event_target.cc b/src/cobalt/dom/event_target.cc
index b923be1..ca6593c 100644
--- a/src/cobalt/dom/event_target.cc
+++ b/src/cobalt/dom/event_target.cc
@@ -83,10 +83,18 @@
     return false;
   }
 
+  // The event is now being dispatched. Track it in the global stats.
+  GlobalStats::GetInstance()->StartJavaScriptEvent();
+
   event->set_target(this);
   event->set_event_phase(Event::kAtTarget);
   FireEventOnListeners(event);
   event->set_event_phase(Event::kNone);
+
+  // The event has completed being dispatched. Stop tracking it in the global
+  // stats.
+  GlobalStats::GetInstance()->StopJavaScriptEvent();
+
   return !event->default_prevented();
 }
 
diff --git a/src/cobalt/dom/global_stats.cc b/src/cobalt/dom/global_stats.cc
index 8dc8f94..b2de28e 100644
--- a/src/cobalt/dom/global_stats.cc
+++ b/src/cobalt/dom/global_stats.cc
@@ -42,9 +42,9 @@
                  "Total number of currently active nodes."),
       num_node_lists_("Count.DOM.NodeLists", 0,
                       "Total number of currently active node lists."),
-      num_active_dispatch_events_(
-          "Count.DOM.ActiveDispatchEvents", 0,
-          "Total number of currently active dispatch events."),
+      num_active_java_script_events_(
+          "Count.DOM.ActiveJavaScriptEvents", 0,
+          "Total number of currently active JavaScript events."),
       num_xhrs_("Count.XHR", 0, "Total number of currently active XHRs."),
       xhr_memory_("Memory.XHR", 0, "Memory allocated by XHRs in bytes.") {}
 
@@ -55,7 +55,8 @@
          num_dom_token_lists_ == 0 && num_event_listeners_ == 0 &&
          num_html_collections_ == 0 && num_named_node_maps_ == 0 &&
          num_nodes_ == 0 && num_node_lists_ == 0 &&
-         num_active_dispatch_events_ == 0 && num_xhrs_ == 0 && xhr_memory_ == 0;
+         num_active_java_script_events_ == 0 && num_xhrs_ == 0 &&
+         xhr_memory_ == 0;
 }
 
 void GlobalStats::Add(Attr* /*object*/) { ++num_attrs_; }
@@ -92,9 +93,9 @@
 
 void GlobalStats::RemoveEventListener() { --num_event_listeners_; }
 
-void GlobalStats::StartDispatchEvent() { ++num_active_dispatch_events_; }
+void GlobalStats::StartJavaScriptEvent() { ++num_active_java_script_events_; }
 
-void GlobalStats::StopDispatchEvent() { --num_active_dispatch_events_; }
+void GlobalStats::StopJavaScriptEvent() { --num_active_java_script_events_; }
 
 void GlobalStats::Add(xhr::XMLHttpRequest* /* object */) { ++num_xhrs_; }
 
diff --git a/src/cobalt/dom/global_stats.h b/src/cobalt/dom/global_stats.h
index 75d4feb..413fc90 100644
--- a/src/cobalt/dom/global_stats.h
+++ b/src/cobalt/dom/global_stats.h
@@ -62,8 +62,8 @@
   int GetNumEventListeners() const { return num_event_listeners_; }
   int GetNumNodes() const { return num_nodes_; }
 
-  void StartDispatchEvent();
-  void StopDispatchEvent();
+  void StartJavaScriptEvent();
+  void StopJavaScriptEvent();
 
   void Add(xhr::XMLHttpRequest* object);
   void Remove(xhr::XMLHttpRequest* object);
@@ -84,7 +84,7 @@
   base::CVal<int, base::CValPublic> num_nodes_;
   base::CVal<int> num_node_lists_;
 
-  base::CVal<int> num_active_dispatch_events_;
+  base::CVal<int> num_active_java_script_events_;
 
   // XHR-related tracking
   base::CVal<int> num_xhrs_;
diff --git a/src/cobalt/dom/html_element.cc b/src/cobalt/dom/html_element.cc
index d9830c1..5f7e200 100644
--- a/src/cobalt/dom/html_element.cc
+++ b/src/cobalt/dom/html_element.cc
@@ -750,7 +750,11 @@
 
 HTMLElement::~HTMLElement() {
   --(non_trivial_static_fields.Get().html_element_count_log.count);
+  if (IsInDocument()) {
+    dom_stat_tracker_->OnHtmlElementRemovedFromDocument();
+  }
   dom_stat_tracker_->OnHtmlElementDestroyed();
+
   style_->set_mutation_observer(NULL);
 }
 
@@ -758,10 +762,14 @@
   directionality_ = other.directionality_;
 }
 
-void HTMLElement::OnMutation() { InvalidateMatchingRulesRecursively(); }
+void HTMLElement::OnInsertedIntoDocument() {
+  Node::OnInsertedIntoDocument();
+  dom_stat_tracker_->OnHtmlElementInsertedIntoDocument();
+}
 
 void HTMLElement::OnRemovedFromDocument() {
   Node::OnRemovedFromDocument();
+  dom_stat_tracker_->OnHtmlElementRemovedFromDocument();
 
   // When an element that is focused stops being a focusable element, or stops
   // being focused without another element being explicitly focused in its
@@ -779,6 +787,8 @@
   }
 }
 
+void HTMLElement::OnMutation() { InvalidateMatchingRulesRecursively(); }
+
 void HTMLElement::OnSetAttribute(const std::string& name,
                                  const std::string& value) {
   if (name == "class" || name == "id") {
diff --git a/src/cobalt/dom/html_element.h b/src/cobalt/dom/html_element.h
index bae437d..608b865 100644
--- a/src/cobalt/dom/html_element.h
+++ b/src/cobalt/dom/html_element.h
@@ -269,6 +269,9 @@
   HTMLElement(Document* document, base::Token tag_name);
   ~HTMLElement() OVERRIDE;
 
+  void OnInsertedIntoDocument() OVERRIDE;
+  void OnRemovedFromDocument() OVERRIDE;
+
   void CopyDirectionality(const HTMLElement& other);
 
   // HTMLElement keeps a pointer to the dom stat tracker to ensure that it can
@@ -279,7 +282,6 @@
  private:
   // From Node.
   void OnMutation() OVERRIDE;
-  void OnRemovedFromDocument() OVERRIDE;
 
   // From Element.
   void OnSetAttribute(const std::string& name,
diff --git a/src/cobalt/dom/html_media_element.cc b/src/cobalt/dom/html_media_element.cc
index 384816c..0dbebf1 100644
--- a/src/cobalt/dom/html_media_element.cc
+++ b/src/cobalt/dom/html_media_element.cc
@@ -1166,7 +1166,6 @@
 
   if (ready_state_ >= WebMediaPlayer::kReadyStateHaveMetadata &&
       old_state < WebMediaPlayer::kReadyStateHaveMetadata) {
-    PlayerOutputModeUpdated();
     duration_ = player_->GetDuration();
     ScheduleOwnEvent(base::Tokens::durationchange());
     ScheduleOwnEvent(base::Tokens::loadedmetadata());
@@ -1583,6 +1582,15 @@
   EndProcessingMediaPlayerCallback();
 }
 
+void HTMLMediaElement::OutputModeChanged() {
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::OutputModeChanged");
+  // If the player mode is updated, trigger a re-layout so that we can setup
+  // the video render tree differently depending on whether we are in punch-out
+  // or decode-to-texture.
+  node_document()->OnDOMMutation();
+  InvalidateLayoutBoxesOfNodeAndAncestors();
+}
+
 void HTMLMediaElement::PlaybackStateChanged() {
   if (!player_) {
     return;
@@ -1784,13 +1792,5 @@
 }
 #endif  // !defined(COBALT_MEDIA_SOURCE_2016)
 
-void HTMLMediaElement::PlayerOutputModeUpdated() {
-  // If the player mode is updated, trigger a re-layout so that we can setup
-  // the video render tree differently depending on whether we are in punch-out
-  // or decode-to-texture.
-  node_document()->OnDOMMutation();
-  InvalidateLayoutBoxesOfNodeAndAncestors();
-}
-
 }  // namespace dom
 }  // namespace cobalt
diff --git a/src/cobalt/dom/html_media_element.h b/src/cobalt/dom/html_media_element.h
index a45eb4e..b3d1f0d 100644
--- a/src/cobalt/dom/html_media_element.h
+++ b/src/cobalt/dom/html_media_element.h
@@ -233,6 +233,7 @@
   void ReadyStateChanged() OVERRIDE;
   void TimeChanged() OVERRIDE;
   void DurationChanged() OVERRIDE;
+  void OutputModeChanged() OVERRIDE;
   void PlaybackStateChanged() OVERRIDE;
   void SawUnsupportedTracks() OVERRIDE;
   float Volume() const OVERRIDE;
@@ -264,10 +265,6 @@
   void SetSourceState(MediaSourceReadyState ready_state);
 #endif  // !defined(COBALT_MEDIA_SOURCE_2016)
 
-  // Called whenever the player's output mode (e.g. punch-out,
-  // decode-to-texture) is updated.
-  void PlayerOutputModeUpdated();
-
   scoped_ptr<WebMediaPlayer> player_;
 
   std::string current_src_;
diff --git a/src/cobalt/dom/html_script_element.cc b/src/cobalt/dom/html_script_element.cc
index 840458d..9af2bcc 100644
--- a/src/cobalt/dom/html_script_element.cc
+++ b/src/cobalt/dom/html_script_element.cc
@@ -23,6 +23,7 @@
 #include "cobalt/base/tokens.h"
 #include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/document.h"
+#include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/html_element_context.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/sync_loader.h"
@@ -506,6 +507,9 @@
     return;
   }
 
+  // The script is now being run. Track it in the global stats.
+  GlobalStats::GetInstance()->StartJavaScriptEvent();
+
   TRACE_EVENT2("cobalt::dom", "HTMLScriptElement::Execute()", "file_path",
                script_location.file_path, "line_number",
                script_location.line_number);
@@ -536,6 +540,12 @@
     PreventGarbageCollectionAndPostToDispatchEvent(
         FROM_HERE, base::Tokens::readystatechange());
   }
+
+  // The script is done running. Stop tracking it in the global stats.
+  GlobalStats::GetInstance()->StopJavaScriptEvent();
+
+  // Notify the DomStatTracker of the execution.
+  dom_stat_tracker_->OnHtmlScriptElementExecuted();
 }
 
 void HTMLScriptElement::PreventGarbageCollectionAndPostToDispatchEvent(
diff --git a/src/cobalt/dom/node.cc b/src/cobalt/dom/node.cc
index b1f0569..6b15337 100644
--- a/src/cobalt/dom/node.cc
+++ b/src/cobalt/dom/node.cc
@@ -100,7 +100,7 @@
   }
 
   // The event is now being dispatched. Track it in the global stats.
-  GlobalStats::GetInstance()->StartDispatchEvent();
+  GlobalStats::GetInstance()->StartJavaScriptEvent();
 
   typedef std::vector<scoped_refptr<Node> > Ancestors;
   Ancestors ancestors;
@@ -139,7 +139,7 @@
 
   // The event has completed being dispatched. Stop tracking it in the global
   // stats.
-  GlobalStats::GetInstance()->StopDispatchEvent();
+  GlobalStats::GetInstance()->StopJavaScriptEvent();
 
   return !event->default_prevented();
 }
diff --git a/src/cobalt/dom/node.h b/src/cobalt/dom/node.h
index e1fbecb..c91e304 100644
--- a/src/cobalt/dom/node.h
+++ b/src/cobalt/dom/node.h
@@ -248,6 +248,8 @@
   // removed from to its owner document.
   virtual void OnRemovedFromDocument();
 
+  virtual bool IsInDocument() const { return inserted_into_document_; }
+
   virtual void PurgeCachedBackgroundImagesOfNodeAndDescendants();
   virtual void InvalidateComputedStylesOfNodeAndDescendants();
   virtual void InvalidateLayoutBoxesOfNodeAndAncestors();
diff --git a/src/cobalt/dom/window_timers.cc b/src/cobalt/dom/window_timers.cc
index 792ae47..265d0db 100644
--- a/src/cobalt/dom/window_timers.cc
+++ b/src/cobalt/dom/window_timers.cc
@@ -18,6 +18,8 @@
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
+#include "base/debug/trace_event.h"
+#include "cobalt/dom/global_stats.h"
 #include "nb/memory_scope.h"
 
 namespace cobalt {
@@ -85,8 +87,13 @@
 }
 
 void WindowTimers::RunTimerCallback(int handle) {
+  TRACE_EVENT0("cobalt::dom", "WindowTimers::RunTimerCallback");
   Timers::iterator timer = timers_.find(handle);
   DCHECK(timer != timers_.end());
+
+  // The callback is now being run. Track it in the global stats.
+  GlobalStats::GetInstance()->StartJavaScriptEvent();
+
   // Keep a |TimerInfo| reference, so it won't be released when running the
   // callback.
   scoped_refptr<TimerInfo> timer_info = timer->second;
@@ -99,6 +106,9 @@
   if (timer != timers_.end() && !timer->second->timer()->IsRunning()) {
     timers_.erase(timer);
   }
+
+  // The callback has finished running. Stop tracking it in the global stats.
+  GlobalStats::GetInstance()->StopJavaScriptEvent();
 }
 
 }  // namespace dom
diff --git a/src/cobalt/loader/resource_cache.h b/src/cobalt/loader/resource_cache.h
index 2134423..336d137 100644
--- a/src/cobalt/loader/resource_cache.h
+++ b/src/cobalt/loader/resource_cache.h
@@ -512,6 +512,7 @@
 
   base::CVal<base::cval::SizeInBytes, base::CValPublic> size_in_bytes_;
   base::CVal<base::cval::SizeInBytes, base::CValPublic> capacity_in_bytes_;
+  base::CVal<int> count_requested_resources_;
   base::CVal<int> count_loading_resources_;
   base::CVal<int> count_pending_callbacks_;
 
@@ -539,6 +540,9 @@
           "The capacity, in bytes, of the resource cache.  "
           "Exceeding this results in *unused* resources being "
           "purged."),
+      count_requested_resources_(
+          base::StringPrintf("Count.%s.RequestedResources", name_.c_str()), 0,
+          "The total number of resources that have been requested."),
       count_loading_resources_(
           base::StringPrintf("Count.%s.LoadingResources", name_.c_str()), 0,
           "The number of loading resources that are still outstanding."),
@@ -576,6 +580,7 @@
   }
 
   // If we reach this point, then the resource doesn't exist yet.
+  ++count_requested_resources_;
 
   // Add the resource to a loading set. If no current resources have pending
   // callbacks, then this resource will block callbacks until it is decoded.
diff --git a/src/cobalt/media/base/pipeline.h b/src/cobalt/media/base/pipeline.h
index 82863ce..8516cac 100644
--- a/src/cobalt/media/base/pipeline.h
+++ b/src/cobalt/media/base/pipeline.h
@@ -108,7 +108,8 @@
                      const PipelineStatusCB& error_cb,
                      const PipelineStatusCB& seek_cb,
                      const BufferingStateCB& buffering_state_cb,
-                     const base::Closure& duration_change_cb) = 0;
+                     const base::Closure& duration_change_cb,
+                     const base::Closure& output_mode_change_cb) = 0;
 
   // Asynchronously stops the pipeline, executing |stop_cb| when the pipeline
   // teardown has completed.
diff --git a/src/cobalt/media/base/sbplayer_pipeline.cc b/src/cobalt/media/base/sbplayer_pipeline.cc
index 452b4cd..e783005 100644
--- a/src/cobalt/media/base/sbplayer_pipeline.cc
+++ b/src/cobalt/media/base/sbplayer_pipeline.cc
@@ -61,6 +61,7 @@
   PipelineStatusCB seek_cb;
   Pipeline::BufferingStateCB buffering_state_cb;
   base::Closure duration_change_cb;
+  base::Closure output_mode_change_cb;
 };
 
 // SbPlayerPipeline is a PipelineBase implementation that uses the SbPlayer
@@ -82,7 +83,8 @@
              const PipelineStatusCB& ended_cb, const PipelineStatusCB& error_cb,
              const PipelineStatusCB& seek_cb,
              const BufferingStateCB& buffering_state_cb,
-             const base::Closure& duration_change_cb) OVERRIDE;
+             const base::Closure& duration_change_cb,
+             const base::Closure& output_mode_change_cb) OVERRIDE;
 
   void Stop(const base::Closure& stop_cb) OVERRIDE;
   void Seek(TimeDelta time, const PipelineStatusCB& seek_cb);
@@ -187,6 +189,7 @@
   PipelineStatusCB error_cb_;
   BufferingStateCB buffering_state_cb_;
   base::Closure duration_change_cb_;
+  base::Closure output_mode_change_cb_;
   base::optional<bool> decode_to_texture_output_mode_;
 
   // Demuxer reference used for setting the preload value.
@@ -259,7 +262,8 @@
                              const PipelineStatusCB& error_cb,
                              const PipelineStatusCB& seek_cb,
                              const BufferingStateCB& buffering_state_cb,
-                             const base::Closure& duration_change_cb) {
+                             const base::Closure& duration_change_cb,
+                             const base::Closure& output_mode_change_cb) {
   TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::Start");
 
   DCHECK(demuxer);
@@ -268,6 +272,7 @@
   DCHECK(!seek_cb.is_null());
   DCHECK(!buffering_state_cb.is_null());
   DCHECK(!duration_change_cb.is_null());
+  DCHECK(!output_mode_change_cb.is_null());
 
   StartTaskParameters parameters;
   parameters.demuxer = demuxer;
@@ -277,6 +282,7 @@
   parameters.seek_cb = seek_cb;
   parameters.buffering_state_cb = buffering_state_cb;
   parameters.duration_change_cb = duration_change_cb;
+  parameters.output_mode_change_cb = output_mode_change_cb;
 
   message_loop_->PostTask(
       FROM_HERE, base::Bind(&SbPlayerPipeline::StartTask, this, parameters));
@@ -485,6 +491,7 @@
   }
   buffering_state_cb_ = parameters.buffering_state_cb;
   duration_change_cb_ = parameters.duration_change_cb;
+  output_mode_change_cb_ = parameters.output_mode_change_cb;
 
   const bool kEnableTextTracks = false;
   demuxer_->Initialize(this,
@@ -584,6 +591,14 @@
   }
 
   if (player_->IsValid()) {
+    base::Closure output_mode_change_cb;
+    {
+      base::AutoLock auto_lock(lock_);
+      DCHECK(!output_mode_change_cb_.is_null());
+      output_mode_change_cb = base::ResetAndReturn(&output_mode_change_cb_);
+    }
+    output_mode_change_cb.Run();
+
     return;
   }
 
diff --git a/src/cobalt/media/blink/webcontentdecryptionmodule_impl.h b/src/cobalt/media/blink/webcontentdecryptionmodule_impl.h
index 4d09ac3..30a4fec 100644
--- a/src/cobalt/media/blink/webcontentdecryptionmodule_impl.h
+++ b/src/cobalt/media/blink/webcontentdecryptionmodule_impl.h
@@ -53,7 +53,7 @@
   scoped_refptr<MediaKeys> GetCdm();
 
  private:
-  friend CdmSessionAdapter;
+  friend class CdmSessionAdapter;
 
   // Takes reference to |adapter|.
   explicit WebContentDecryptionModuleImpl(
diff --git a/src/cobalt/media/player/web_media_player.h b/src/cobalt/media/player/web_media_player.h
index 94e372d..b3b5cb6 100644
--- a/src/cobalt/media/player/web_media_player.h
+++ b/src/cobalt/media/player/web_media_player.h
@@ -196,6 +196,7 @@
   virtual void ReadyStateChanged() = 0;
   virtual void TimeChanged() = 0;
   virtual void DurationChanged() = 0;
+  virtual void OutputModeChanged() = 0;
   virtual void PlaybackStateChanged() = 0;
   // TODO: Revisit the necessity of the following function.
   virtual void SetOpaque(bool /* opaque */) {}
diff --git a/src/cobalt/media/player/web_media_player_impl.cc b/src/cobalt/media/player/web_media_player_impl.cc
index 623c892..1d9ada5 100644
--- a/src/cobalt/media/player/web_media_player_impl.cc
+++ b/src/cobalt/media/player/web_media_player_impl.cc
@@ -695,7 +695,8 @@
       BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineError),
       BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineSeek),
       BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineBufferingState),
-      BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDurationChanged));
+      BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDurationChanged),
+      BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnOutputModeChanged));
 }
 
 void WebMediaPlayerImpl::SetNetworkState(WebMediaPlayer::NetworkState state) {
@@ -791,5 +792,9 @@
   GetClient()->DurationChanged();
 }
 
+void WebMediaPlayerImpl::OnOutputModeChanged() {
+  GetClient()->OutputModeChanged();
+}
+
 }  // namespace media
 }  // namespace cobalt
diff --git a/src/cobalt/media/player/web_media_player_impl.h b/src/cobalt/media/player/web_media_player_impl.h
index 5f3477b..95fcbbd 100644
--- a/src/cobalt/media/player/web_media_player_impl.h
+++ b/src/cobalt/media/player/web_media_player_impl.h
@@ -218,6 +218,7 @@
  private:
   // Callbacks that forward duration change from |pipeline_| to |client_|.
   void OnDurationChanged();
+  void OnOutputModeChanged();
 
   base::Thread pipeline_thread_;
 
diff --git a/src/cobalt/media/sandbox/web_media_player_helper.cc b/src/cobalt/media/sandbox/web_media_player_helper.cc
index adb3afe..58618a2 100644
--- a/src/cobalt/media/sandbox/web_media_player_helper.cc
+++ b/src/cobalt/media/sandbox/web_media_player_helper.cc
@@ -37,6 +37,7 @@
   void ReadyStateChanged() OVERRIDE {}
   void TimeChanged() OVERRIDE {}
   void DurationChanged() OVERRIDE {}
+  void OutputModeChanged() OVERRIDE {}
   void PlaybackStateChanged() OVERRIDE {}
   void SawUnsupportedTracks() OVERRIDE {}
   float Volume() const OVERRIDE { return 1.f; }
diff --git a/src/cobalt/renderer/pipeline.cc b/src/cobalt/renderer/pipeline.cc
index 40a6db2..534f38c 100644
--- a/src/cobalt/renderer/pipeline.cc
+++ b/src/cobalt/renderer/pipeline.cc
@@ -88,9 +88,20 @@
       rasterize_animations_timer_("Renderer.Rasterize.Animations",
                                   kRasterizeAnimationsTimerMaxEntries,
                                   true /*enable_entry_list_c_val*/),
+      new_render_tree_rasterize_count_(
+          "Count.Renderer.Rasterize.NewRenderTree", 0,
+          "Total number of new render trees rasterized."),
+      new_render_tree_rasterize_time_(
+          "Time.Renderer.Rasterize.NewRenderTree", 0,
+          "The last time a new render tree was rasterized."),
       has_active_animations_c_val_(
           "Renderer.HasActiveAnimations", false,
-          "Is non-zero if the current render tree has active animations.")
+          "Is non-zero if the current render tree has active animations."),
+      animations_start_time_(
+          "Time.Renderer.Rasterize.Animations.Start", 0,
+          "The most recent time animations started playing."),
+      animations_end_time_("Time.Renderer.Rasterize.Animations.End", 0,
+                           "The most recent time animations ended playing.")
 #if defined(ENABLE_DEBUG_CONSOLE)
       ,
       ALLOW_THIS_IN_INITIALIZER_LIST(dump_current_render_tree_command_handler_(
@@ -244,8 +255,9 @@
   base::TimeTicks now = base::TimeTicks::Now();
   Submission submission = submission_queue_->GetCurrentSubmission(now);
 
-  bool has_render_tree_changed = last_render_animations_active_ ||
-                                 submission.render_tree != last_render_tree_;
+  bool is_new_render_tree = submission.render_tree != last_render_tree_;
+  bool has_render_tree_changed =
+      last_render_animations_active_ || is_new_render_tree;
 
   // If our render tree hasn't changed from the one that was previously
   // rendered and it's okay on this system to not flip the display buffer
@@ -298,10 +310,16 @@
     rasterize_animations_timer_.Stop();
   }
 
-  // If animations are going from being active to expired, then set the c_val
-  // after rasterizing the final state of the tree. Now that we've finished
-  // tracking the animations, it's time to flush the timer.
-  if (last_render_animations_active_ && !are_animations_active) {
+  if (is_new_render_tree) {
+    ++new_render_tree_rasterize_count_;
+    new_render_tree_rasterize_time_ = base::TimeTicks::Now().ToInternalValue();
+  }
+
+  // Check for if the animations are starting or ending.
+  if (!last_render_animations_active_ && are_animations_active) {
+    animations_start_time_ = base::TimeTicks::Now().ToInternalValue();
+  } else if (last_render_animations_active_ && !are_animations_active) {
+    animations_end_time_ = base::TimeTicks::Now().ToInternalValue();
     has_active_animations_c_val_ = false;
     rasterize_animations_timer_.Flush();
   }
diff --git a/src/cobalt/renderer/pipeline.h b/src/cobalt/renderer/pipeline.h
index 8fab055..9df0d8a 100644
--- a/src/cobalt/renderer/pipeline.h
+++ b/src/cobalt/renderer/pipeline.h
@@ -199,8 +199,17 @@
   // tracking is flushed when the animations expire.
   base::CValCollectionTimerStats<base::CValDebug> rasterize_animations_timer_;
 
-  // Tracks whether or not animations are currently playing.
+  // The total number of new render trees that have been rasterized.
+  base::CVal<int> new_render_tree_rasterize_count_;
+  // The last time that a newly encountered render tree was first rasterized.
+  base::CVal<int64> new_render_tree_rasterize_time_;
+
+  // Whether or not animations are currently playing.
   base::CVal<bool> has_active_animations_c_val_;
+  // The most recent time animations started playing.
+  base::CVal<int64> animations_start_time_;
+  // The most recent time animations ended playing.
+  base::CVal<int64> animations_end_time_;
 
 #if defined(ENABLE_DEBUG_CONSOLE)
   // Dumps the current render tree to the console.
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkStream_cobalt.h b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkStream_cobalt.h
index 77266cc..82f2057 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkStream_cobalt.h
+++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkStream_cobalt.h
@@ -78,7 +78,7 @@
   void PurgeUnusedMemoryChunks();
 
  private:
-  friend SkFileMemoryChunkStreamProvider;
+  friend class SkFileMemoryChunkStreamProvider;
 
   // Attempts to reserve an available memory chunk and returns true if
   // successful. On success, |available_chunk_count_| is decremented by one.
@@ -124,8 +124,8 @@
   void PurgeUnusedMemoryChunks();
 
  private:
-  friend SkFileMemoryChunkStream;
-  friend SkFileMemoryChunkStreamManager;
+  friend class SkFileMemoryChunkStream;
+  friend class SkFileMemoryChunkStreamManager;
 
   SkFileMemoryChunkStreamProvider(const std::string& file_path,
                                   SkFileMemoryChunkStreamManager* manager);
@@ -183,7 +183,7 @@
   virtual size_t getLength() const SK_OVERRIDE;
 
  private:
-  friend SkFileMemoryChunkStreamProvider;
+  friend class SkFileMemoryChunkStreamProvider;
 
   explicit SkFileMemoryChunkStream(
       SkFileMemoryChunkStreamProvider* stream_provider);
diff --git a/src/cobalt/script/mozjs-45/wrapper_private.h b/src/cobalt/script/mozjs-45/wrapper_private.h
index 7e4b917..3ef8306 100644
--- a/src/cobalt/script/mozjs-45/wrapper_private.h
+++ b/src/cobalt/script/mozjs-45/wrapper_private.h
@@ -131,7 +131,7 @@
   GetOpaqueRootFunction get_opaque_root_function_;
   GetReachableWrappablesFunction get_reachable_wrappables_function_;
 
-  friend Tracer;
+  friend class Tracer;
 };
 
 }  // namespace mozjs
diff --git a/src/cobalt/script/mozjs/wrapper_private.h b/src/cobalt/script/mozjs/wrapper_private.h
index 1ac464f..c384eda 100644
--- a/src/cobalt/script/mozjs/wrapper_private.h
+++ b/src/cobalt/script/mozjs/wrapper_private.h
@@ -131,7 +131,7 @@
   GetOpaqueRootFunction get_opaque_root_function_;
   GetReachableWrappablesFunction get_reachable_wrappables_function_;
 
-  friend Tracer;
+  friend class Tracer;
 };
 
 }  // namespace mozjs
diff --git a/src/cobalt/webdriver_benchmarks/c_val_names.py b/src/cobalt/webdriver_benchmarks/c_val_names.py
index 3256e48..6ce4f4d 100644
--- a/src/cobalt/webdriver_benchmarks/c_val_names.py
+++ b/src/cobalt/webdriver_benchmarks/c_val_names.py
@@ -5,18 +5,46 @@
 from __future__ import print_function
 
 
-def count_dom_active_dispatch_events():
-  return "Count.DOM.ActiveDispatchEvents"
+def count_dom_active_java_script_events():
+  return "Count.DOM.ActiveJavaScriptEvents"
+
+
+def count_dom_html_elements_document():
+  return "Count.MainWebModule.DOM.HtmlElement.Document"
+
+
+def count_dom_html_elements_total():
+  return "Count.MainWebModule.DOM.HtmlElement.Total"
+
+
+def count_dom_html_script_element_execute():
+  return "Count.MainWebModule.DOM.HtmlScriptElement.Execute"
+
+
+def count_layout_boxes():
+  return "Count.MainWebModule.Layout.Box"
 
 
 def count_image_cache_loading_resources():
   return "Count.MainWebModule.ImageCache.LoadingResources"
 
 
+def count_image_cache_requested_resources():
+  return "Count.MainWebModule.ImageCache.RequestedResources"
+
+
+def count_rasterize_new_render_tree():
+  return "Count.Renderer.Rasterize.NewRenderTree"
+
+
 def event_duration_dom_video_start_delay():
   return "Event.Duration.MainWebModule.DOM.VideoStartDelay"
 
 
+def event_is_processing():
+  return "Event.MainWebModule.IsProcessing"
+
+
 def event_value_dictionary(event_type):
   return "Event.MainWebModule.{}.ValueDictionary".format(event_type)
 
@@ -31,3 +59,31 @@
 
 def renderer_has_active_animations():
   return "Renderer.HasActiveAnimations"
+
+
+def time_browser_navigate():
+  return "Time.Browser.Navigate"
+
+
+def time_browser_on_load_event():
+  return "Time.Browser.OnLoadEvent"
+
+
+def time_cobalt_start():
+  return "Time.Cobalt.Start"
+
+
+def time_dom_html_script_element_execute():
+  return "Time.MainWebModule.DOM.HtmlScriptElement.Execute"
+
+
+def time_rasterize_animations_start():
+  return "Time.Renderer.Rasterize.Animations.Start"
+
+
+def time_rasterize_animations_end():
+  return "Time.Renderer.Rasterize.Animations.End"
+
+
+def time_rasterize_new_render_tree():
+  return "Time.Renderer.Rasterize.NewRenderTree"
diff --git a/src/cobalt/webdriver_benchmarks/container_util.py b/src/cobalt/webdriver_benchmarks/container_util.py
index cab9451..56e948a 100644
--- a/src/cobalt/webdriver_benchmarks/container_util.py
+++ b/src/cobalt/webdriver_benchmarks/container_util.py
@@ -50,38 +50,5 @@
   if len(sorted_values) == index + 1:
     return sorted_values[index]
 
-  return sorted_values[index] * (1 - fractional
-                                ) + sorted_values[index + 1] * fractional
-
-
-def merge_dict(merge_into, merge_from):
-  """Merges the second dict into the first dict.
-
-  Merge into differs from update in that it will not override values.  If the
-  values already exist, the resulting value will be a list with a union of
-  existing and new items.
-
-  Args:
-    merge_into: An output dict to merge values into.
-    merge_from: An input dict to iterate over and insert values from.
-
-  Returns:
-    None
-  """
-  if not merge_from:
-    return
-  for k, v in merge_from.items():
-    try:
-      existing_value = merge_into[k]
-    except KeyError:
-      merge_into[k] = v
-      continue
-
-    if not isinstance(v, list):
-      v = [v]
-    if isinstance(existing_value, list):
-      existing_value.extend(v)
-    else:
-      new_value = [existing_value]
-      new_value.extend(v)
-      merge_into[k] = new_value
+  return sorted_values[index] * (
+      1 - fractional) + sorted_values[index + 1] * fractional
diff --git a/src/cobalt/webdriver_benchmarks/container_util_test.py b/src/cobalt/webdriver_benchmarks/container_util_test.py
index 371a61f..8c29bb4 100755
--- a/src/cobalt/webdriver_benchmarks/container_util_test.py
+++ b/src/cobalt/webdriver_benchmarks/container_util_test.py
@@ -85,50 +85,5 @@
     self.assertEqual(container_util.percentile([2, 1, 3, 4, 5], 100), 5)
 
 
-class MergeDictTest(unittest.TestCase):
-
-  def test_empty_merge(self):
-    a = {'x': 4}
-    b = {}
-    container_util.merge_dict(a, b)
-    self.assertEqual(a, {'x': 4})
-
-  def test_merge_into_empty(self):
-    a = {}
-    b = {'x': 4}
-    container_util.merge_dict(a, b)
-    self.assertEqual(a, {'x': 4})
-
-  def test_merge_non_overlapping_item(self):
-    a = {'x': 4}
-    b = {'y': 5}
-    container_util.merge_dict(a, b)
-    self.assertEqual(a, {'x': 4, 'y': 5})
-
-  def test_overlapping_item_with_item(self):
-    a = {'x': 4}
-    b = {'x': 5}
-    container_util.merge_dict(a, b)
-    self.assertEqual(a, {'x': [4, 5]})
-
-  def test_overlapping_list_with_item(self):
-    a = {'x': [4]}
-    b = {'x': 5}
-    container_util.merge_dict(a, b)
-    self.assertEqual(a, {'x': [4, 5]})
-
-  def test_overlapping_list_with_list(self):
-    a = {'x': [4]}
-    b = {'x': [5]}
-    container_util.merge_dict(a, b)
-    self.assertEqual(a, {'x': [4, 5]})
-
-  def test_overlapping_item_with_list(self):
-    a = {'x': 4}
-    b = {'x': [5]}
-    container_util.merge_dict(a, b)
-    self.assertEqual(a, {'x': [4, 5]})
-
-
 if __name__ == '__main__':
   sys.exit(unittest.main())
diff --git a/src/cobalt/webdriver_benchmarks/default_query_param_constants.py b/src/cobalt/webdriver_benchmarks/default_query_param_constants.py
new file mode 100644
index 0000000..c2347f2
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/default_query_param_constants.py
@@ -0,0 +1,10 @@
+"""Default query params to use when loading URLs."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+BASE_QUERY_PARAMS = {}
+
+INIT_QUERY_PARAMS = {}
+INIT_QUERY_PARAMS_TRIGGER_RELOAD = False
diff --git a/src/cobalt/webdriver_benchmarks/tests/README.md b/src/cobalt/webdriver_benchmarks/tests/README.md
index 032c41c..81fde36 100644
--- a/src/cobalt/webdriver_benchmarks/tests/README.md
+++ b/src/cobalt/webdriver_benchmarks/tests/README.md
@@ -1,26 +1,40 @@
-Cobalt Webdriver-driven Benchmarks
----------------------
+# Cobalt Webdriver-driven Benchmarks
 
 This directory contains a set of webdriver-driven benchmarks
 for Cobalt.
 
 Each file should contain a set of tests in Python "unittest" format.
 
-All tests in all of the files included within "all.py" will be run on the
+All tests included within [performance.py](performance.py) will be run on the
 build system. Results can be recorded in the build results database.
 
-To run an individual test, simply execute a script directly (or run
-all of them via "all.py"). Platform configuration will be inferred from
-the environment if set. Otherwise, it must be specified via commandline
-parameters.
+## Running the tests
 
-To make a new test:
+In most cases, you will want to run all performance tests, and you can do so by
+executing the script [performance.py](performance.py). You can call
+`python performance.py --help` to see a list of commandline parameters to call
+it with.  For example, to run tests on the raspi-2 QA build, you should run the
+following command:
+
+```
+python performance.py -p raspi-2 -c qa -d $RASPI_ADDR
+```
+
+Where `RASPI_ADDR` is set to the IP of the target Raspberry Pi device.
+
+To run individual tests, simply execute the script directly. For all tests,
+platform configuration will be inferred from the environment if set. Otherwise,
+it must be specified via commandline parameters.
+
+## Creating a new test
 
  1. If appropriate, create a new file borrowing the boilerplate from
-    an existing simple file, such as "browse_horizontal.py".
+    an existing simple file, such as
+    [browse_horizontal.py](performance/non_video/browse_horizontal.py).
 
- 2. Add the file name to the tests added within "all.py", causing it run
-    when "all.py" is run.
+ 2. Add the file name to the tests added within
+    [performance.py](performance.py), causing it run when
+    [performance](performance.py) is run.
 
  3. If this file contains internal names or details, consider adding it
     to the "EXCLUDE.FILES" list.
@@ -29,4 +43,250 @@
     appropriate.
 
  5. Results must be added to the build results database schema. See
-    the internal "README-Updating-Result-Schema.md" file
+    the internal
+    [README-Updating-Result-Schema.md](README-Updating-Result-Schema.md) file.
+
+## Testing against specific loaders/labels
+
+To run the benchmarks against any desired loader, a --url command line parameter
+can be provided. This will be the url that the tests will run against.
+
+It should have the following format:
+
+```
+python performance.py -p raspi-2 -c qa -d $RASPI_ADDR --url https://www.youtube.com/tv?loader=nllive
+```
+
+## Benchmark Results
+
+The results will be printed to stdout.  You should redirect output to a file
+if you would like to store the results.  Each line of the benchmark output
+prefixed with `webdriver_benchmark TEST_RESULT:` provides the result of one
+measurment.  Those lines have the following format:
+
+```
+webdriver_benchmark TEST_RESULT: result_name result_value
+```
+
+where `result_name` is the name of the result and `result_value` is a number
+providing the measured result for that metric.  For example,
+
+```
+webdriver_benchmark TEST RESULT: wbBrowseHorizontalDurLayoutBoxTreeUpdateUsedSizesUsPct50 3061.5
+```
+
+gives the 50th percentile of the duration Cobalt took to update the box tree's
+used sizes, on a horizontal scroll event, in microseconds.
+
+Note that most time-based measurements are in microseconds.
+
+### Interesting Timing-Related Benchmarks
+Some particularly interesting timing-related benchmark results are:
+
+#### Startup
+ - `wbStartupDurLaunchToBrowseUs`: Measures the time it takes to launch Cobalt
+   and load the desired URL. The measurement ends when all images finish loading
+   and the final render tree is produced. This is only run once so it will be
+   noiser than other values but provides the most accurate measurement of the
+   requirement startup time.
+ - `wbStartupDurLaunchToUsableUs`: Measures the time it takes to launch Cobalt,
+   and fully load the desired URL, including loading all scripts. The
+   measurement ends when the Player JavaScript code finishes loading on the
+   Browse page, which is the point when Cobalt is fully usable. This is only run
+   once so it will be noiser than other values but provides the most accurate
+   measurement of the time when Cobalt is usable.
+ - `wbStartupDurNavigateToBrowseUs*`: Measures the time it takes to navigate to
+   the desired URL when Cobalt is already loaded. The measurement ends when all
+   images finish loading and the final render tree is produced. This is run
+   multiple times, so it will be less noisy than `wbStartupDurLaunchToBrowseUs`,
+   but it does not include Cobalt initialization so it is not as accurate of a
+   measurement.
+ - `wbStartupDurNavigateToUsableUs`: Measures the time it takes to navigate to
+   the desired URL when Cobalt is already loaded, including loading all scripts.
+   The measurement ends when the Player JavaScript code finishes loading on the
+   Browse page, which is the point when Cobalt is fully usable. This is run
+   multiple times, so it will be less noisy than `wbStartupDurLaunchToUsableUs`,
+   but it does not include Cobalt initialization so it is not as accurate of a
+   measurement.
+
+#### Browse Horizontal Scroll Events
+ - `wbBrowseHorizontalDurTotalUs*`: Measures the latency (i.e. JavaScript
+   execution time + layout time) during horizontal scroll events from keypress
+   until the render tree is submitted to the rasterize thread. It does not
+   include the time taken to rasterize the render tree so it will be smaller
+   than the observed latency.
+ - `wbBrowseHorizontalDurAnimationsStartDelayUs*`: Measures the input latency
+   during horizontal scroll events from the keypress until the animation starts.
+   This is the most accurate measure of input latency.
+ - `wbBrowseHorizontalDurAnimationsEndDelayUs*`: Measures the latency during
+   horizontal scroll events from the keypress until the animation ends.
+ - `wbBrowseHorizontalDurFinalRenderTreeDelayUs*`: Measures the latency during
+   horizontal scroll events from the keypress until the final render tree is
+   rendered and processing stops.
+ - `wbBrowseHorizontalDurRasterizeAnimationsUs*`: Measures the time it takes to
+   render each frame of the animation triggered by a horizontal scroll event.
+   The inverse of this number is the framerate.
+
+#### Browse Vertical Scroll Events
+ - `wbBrowseVerticalDurTotalUs*`: Measures the latency (i.e. JavaScript
+   execution time + layout time) during vertical scroll events from keypress
+   until the render tree is submitted to the rasterize thread. It does not
+   include the time taken to rasterize the render tree so it will be smaller
+   than the observed latency.
+ - `wbBrowseVerticalDurAnimationsStartDelayUs*`: Measures the input latency
+   during vertical scroll events from the keypress until the animation starts.
+   This is the most accurate measure of input latency.
+ - `wbBrowseVerticalDurAnimationsEndDelayUs*`: Measures the latency during
+   vertical scroll events from the keypress until the animation ends.
+ - `wbBrowseVerticalDurFinalRenderTreeDelayUs*`: Measures the latency during
+   vertical scroll events from the keypress until the final render tree is
+   rendered and processing stops.
+ - `wbBrowseVerticalDurRasterizeAnimationsUs*`: Measures the time it takes to
+   render each frame of the animation triggered by a vertical scroll event.
+   The inverse of this number is the framerate.
+
+#### Browse-to-Watch
+ - `wbBrowseToWatchDurVideoStartDelay*`: Measures the browse-to-watch time.
+
+In each case above, the `*` symbol can be one of either `Mean`, `Pct25`,
+`Pct50`, `Pct75` or `Pct95`.  For example, `wbStartupDurBlankToBrowseUsMean` or
+`wbStartupDurBlankToBrowseUsPct95` are both valid measurements. The webdriver
+benchmarks runs its tests many times in order to obtain multiple samples, so you
+can drill into the data by exploring either the mean, or the various
+percentiles.
+
+### Interesting Count-Related Benchmarks
+Some particularly interesting count-related benchmark results are:
+
+#### Startup
+ - `wbStartupCntDomHtmlElements*`: Lists the number of HTML elements in
+   existence after startup completes. This includes HTML elements that are no
+   longer in the DOM but have not been garbage collected yet.
+ - `wbStartupCntDocumentDomHtmlElements*`: Lists the number of HTML
+   elements within the DOM tree after startup completes.
+ - `wbStartupCntLayoutBoxes*`: Lists the number of layout boxes within
+   the layout tree after startup completes.
+ - `wbStartupCntRenderTrees*`: Lists the number of render trees that were
+   generated during startup.
+ - `wbStartupCntRequestedImages*`: Lists the number of images that were
+   requested during startup.
+
+#### Browse Horizontal Scroll Events
+ - `wbBrowseHorizontalCntDomHtmlElements*`: Lists the number of HTML elements in
+   existence after the event. This includes HTML elements that are no longer in
+   the DOM but have not been garbage collected yet.
+ - `wbBrowseHorizontalCntDocumentDomHtmlElements*`: Lists the number of HTML
+   elements within the DOM tree after the event.
+ - `wbBrowseHorizontalCntLayoutBoxes*`: Lists the number of layout boxes within
+   the layout tree after the event.
+ - `wbBrowseHorizontalCntLayoutBoxesCreated*`: Lists the number of new layout
+   boxes that were created during the event.
+ - `wbBrowseHorizontalCntRenderTrees*`: Lists the number of render trees that
+   were generated by the event.
+ - `wbBrowseHorizontalCntRequestedImages*`: Lists the number of images that were
+   requested as a result of the event.
+
+#### Browse Vertical Scroll Events
+ - `wbBrowseVerticalCntDomHtmlElements*`: Lists the number of HTML elements in
+   existence after the event. This includes HTML elements that are no longer in
+   the DOM but have not been garbage collected yet.
+ - `wbBrowseVerticalCntDocumentDomHtmlElements*`: Lists the number of HTML
+   elements within the DOM tree after the event.
+ - `wbBrowseVerticalCntLayoutBoxes*`: Lists the number of layout boxes within
+   the layout tree after the event.
+ - `wbBrowseVerticalCntLayoutBoxesCreated*`: Lists the number of new layout
+   boxes that were created during the event.
+ - `wbBrowseVerticalCntRenderTrees*`: Lists the number of render trees that
+   were generated by the event.
+ - `wbBrowseVerticalCntRequestedImages*`: Lists the number of images that were
+   requested as a result of the event.
+
+In each case above,  the `*` symbol can be one of either `Max`, `Median`, or
+`Mean`. For example, `wbBrowseVerticalCntDomHtmlElementsMax` or
+`wbBrowseVerticalCntDomHtmlElementsMedian` are both valid measurements. The
+webdriver benchmarks runs its tests many times in order to obtain multiple
+samples, so you can drill into the data by exploring either the max, median, or
+mean.
+
+### Filtering results
+
+The webdriver benchmarks output many metrics, but you may only be interested
+in a few.  You will have to manually filter only the metrics that you are
+interested in.  You can do so with `grep`, for example:
+
+```
+python performance.py -p raspi-2 -c qa -d $RASPI_ADDR > results.txt
+echo "" > filtered_results.txt
+printf "=================================TIMING-RELATED=================================\n" >> filtered_results.txt
+printf "\nSTARTUP\n" >> filtered_results.txt
+grep -o "wbStartupDurLaunchToBrowseUs.*$" results.txt >> filtered_results.txt
+grep -o "wbStartupDurLaunchToUsableUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbStartupDurNavigateToBrowseUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbStartupDurNavigateToUsableUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+printf "\nBROWSE HORIZONTAL SCROLL EVENTS\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalDurTotalUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalDurAnimationsStartDelayUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalDurAnimationsEndDelayUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalDurFinalRenderTreeDelayUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalDurRasterizeAnimationsUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+printf "\nBROWSE VERTICAL SCROLL EVENTS\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalDurTotalUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalDurAnimationsStartDelayUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalDurAnimationsEndDelayUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalDurFinalRenderTreeDelayUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalDurRasterizeAnimationsUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+printf "\nBROWSE TO WATCH\n" >> filtered_results.txt
+grep -o "wbBrowseToWatchDurVideoStartDelay.*$" results.txt >> filtered_results.txt
+printf "\n\n=================================COUNT-RELATED==================================\n" >> filtered_results.txt
+printf "\nSTARTUP\n" >> filtered_results.txt
+grep -o "wbStartupCntDomHtmlElements.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbStartupCntDomDocumentHtmlElements.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbStartupCntLayoutBoxes.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbStartupCntRenderTrees.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbStartupCntRequestedImages.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+printf "\nBROWSE HORIZONTAL SCROLL EVENTS\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalCntDomHtmlElementsM.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalCntDomDocumentHtmlElements.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalCntLayoutBoxesM.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalCntLayoutBoxesCreated.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalCntRenderTrees.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalCntRequestedImages.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+printf "\nBROWSE VERTICAL SCROLL EVENTS\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalCntDomHtmlElementsM.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalCntDomDocumentHtmlElements.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalCntLayoutBoxesM.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalCntLayoutBoxesCreated.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalCntRenderTrees.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalCntRequestedImages.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+cat filtered_results.txt
+```
diff --git a/src/cobalt/webdriver_benchmarks/tests/browse_to_watch.py b/src/cobalt/webdriver_benchmarks/tests/browse_to_watch.py
deleted file mode 100755
index ed2f73e..0000000
--- a/src/cobalt/webdriver_benchmarks/tests/browse_to_watch.py
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/usr/bin/python2
-"""Simple benchmark for starting a video from browse."""
-
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import os
-import sys
-
-# The parent directory is a module
-sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
-
-# pylint: disable=C6204,C6203
-import tv_testcase
-import tv_testcase_event_recorder
-import tv_testcase_util
-
-# selenium imports
-keys = tv_testcase_util.import_selenium_module("webdriver.common.keys")
-
-NUM_LOAD_TV_CALLS = 1
-NUM_ITERATIONS_PER_LOAD_TV_CALL = 10
-
-BROWSE_TO_WATCH_EVENT_NAME = "wbBrowseToWatch"
-BROWSE_TO_WATCH_EVENT_TYPE = tv_testcase_util.EVENT_TYPE_KEY_UP
-
-WATCH_TO_BROWSE_EVENT_NAME = "wbWatchToBrowse"
-WATCH_TO_BROWSE_EVENT_TYPE = tv_testcase_util.EVENT_TYPE_KEY_UP
-
-
-class BrowseToWatchTest(tv_testcase.TvTestCase):
-
-  def test_simple(self):
-    recorder_options = tv_testcase_event_recorder.EventRecorderOptions(
-        self, BROWSE_TO_WATCH_EVENT_NAME, BROWSE_TO_WATCH_EVENT_TYPE)
-    recorder_options.record_rasterize_animations = False
-    recorder_options.record_video_start_delay = True
-    browse_to_watch_recorder = tv_testcase_event_recorder.EventRecorder(
-        recorder_options)
-
-    recorder_options = tv_testcase_event_recorder.EventRecorderOptions(
-        self, WATCH_TO_BROWSE_EVENT_NAME, WATCH_TO_BROWSE_EVENT_TYPE)
-    recorder_options.record_rasterize_animations = False
-    watch_to_browse_recorder = tv_testcase_event_recorder.EventRecorder(
-        recorder_options)
-
-    for _ in xrange(NUM_LOAD_TV_CALLS):
-      self.load_tv()
-
-      for _ in xrange(NUM_ITERATIONS_PER_LOAD_TV_CALL):
-        self.send_keys(keys.Keys.ARROW_DOWN)
-        self.wait_for_processing_complete_after_focused_shelf()
-
-        browse_to_watch_recorder.on_start_event()
-        self.send_keys(keys.Keys.ENTER)
-        self.wait_for_media_element_playing()
-        browse_to_watch_recorder.on_end_event()
-
-        # Wait for the title card hidden before sending the escape. Otherwise,
-        # two escapes are required to exit the video.
-        self.wait_for_title_card_hidden()
-
-        watch_to_browse_recorder.on_start_event()
-        self.send_keys(keys.Keys.ESCAPE)
-        self.wait_for_processing_complete_after_focused_shelf()
-        watch_to_browse_recorder.on_end_event()
-
-    browse_to_watch_recorder.on_end_test()
-    watch_to_browse_recorder.on_end_test()
-
-
-if __name__ == "__main__":
-  tv_testcase.main()
diff --git a/src/cobalt/webdriver_benchmarks/tests/all.py b/src/cobalt/webdriver_benchmarks/tests/performance.py
old mode 100755
new mode 100644
similarity index 64%
copy from src/cobalt/webdriver_benchmarks/tests/all.py
copy to src/cobalt/webdriver_benchmarks/tests/performance.py
index c08d93c..8495f71
--- a/src/cobalt/webdriver_benchmarks/tests/all.py
+++ b/src/cobalt/webdriver_benchmarks/tests/performance.py
@@ -1,5 +1,5 @@
 #!/usr/bin/python2
-"""Target for running all tests cases."""
+"""Target for running all performance test cases."""
 
 from __future__ import absolute_import
 from __future__ import division
@@ -30,15 +30,8 @@
   test_suite = unittest.TestSuite()
   dir_path = os.path.dirname(__file__)
 
-  # "time_to_shelf" must be the first test added. The timings that it
-  # records require it to run first.
-  _add_test(test_suite, dir_path, "startup")
-  _add_test(test_suite, dir_path, "browse_horizontal")
-  _add_test(test_suite, dir_path, "browse_vertical")
-  _add_test(test_suite, dir_path, "browse_to_guide")
-  _add_test(test_suite, dir_path, "browse_to_search")
-  _add_test(test_suite, dir_path, "browse_to_watch")
-  _add_test(test_suite, dir_path, "csi")
+  _add_test(test_suite, dir_path, "performance_non_video")
+  _add_test(test_suite, dir_path, "performance_video")
 
   return test_suite
 
diff --git a/src/cobalt/webdriver_benchmarks/tests/performance/__init__.py b/src/cobalt/webdriver_benchmarks/tests/performance/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tests/performance/__init__.py
diff --git a/src/cobalt/webdriver_benchmarks/tests/performance/non_video/__init__.py b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/__init__.py
diff --git a/src/cobalt/webdriver_benchmarks/tests/browse_horizontal.py b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_horizontal.py
similarity index 86%
rename from src/cobalt/webdriver_benchmarks/tests/browse_horizontal.py
rename to src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_horizontal.py
index dd21b3e..2da83c6 100755
--- a/src/cobalt/webdriver_benchmarks/tests/browse_horizontal.py
+++ b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_horizontal.py
@@ -8,8 +8,11 @@
 import os
 import sys
 
-# The parent directory is a module
-sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
+# Add the base webdriver_benchmarks path
+sys.path.insert(0,
+                os.path.dirname(
+                    os.path.dirname((os.path.dirname(
+                        os.path.dirname(os.path.realpath(__file__)))))))
 
 # pylint: disable=C6204,C6203
 import tv_testcase
diff --git a/src/cobalt/webdriver_benchmarks/tests/browse_to_guide.py b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_to_guide.py
similarity index 89%
rename from src/cobalt/webdriver_benchmarks/tests/browse_to_guide.py
rename to src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_to_guide.py
index 4a74c4a..7e5230f 100755
--- a/src/cobalt/webdriver_benchmarks/tests/browse_to_guide.py
+++ b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_to_guide.py
@@ -8,8 +8,11 @@
 import os
 import sys
 
-# The parent directory is a module
-sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
+# Add the base webdriver_benchmarks path
+sys.path.insert(0,
+                os.path.dirname(
+                    os.path.dirname((os.path.dirname(
+                        os.path.dirname(os.path.realpath(__file__)))))))
 
 # pylint: disable=C6204,C6203
 import tv
diff --git a/src/cobalt/webdriver_benchmarks/tests/browse_to_search.py b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_to_search.py
similarity index 86%
rename from src/cobalt/webdriver_benchmarks/tests/browse_to_search.py
rename to src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_to_search.py
index 688e939..a2d82de 100755
--- a/src/cobalt/webdriver_benchmarks/tests/browse_to_search.py
+++ b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_to_search.py
@@ -8,8 +8,11 @@
 import os
 import sys
 
-# The parent directory is a module
-sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
+# Add the base webdriver_benchmarks path
+sys.path.insert(0,
+                os.path.dirname(
+                    os.path.dirname((os.path.dirname(
+                        os.path.dirname(os.path.realpath(__file__)))))))
 
 # pylint: disable=C6204,C6203
 import tv
@@ -35,13 +38,13 @@
   def test_simple(self):
     recorder_options = tv_testcase_event_recorder.EventRecorderOptions(
         self, BROWSE_TO_SEARCH_EVENT_NAME, BROWSE_TO_SEARCH_EVENT_TYPE)
-    recorder_options.record_rasterize_animations = False
+    recorder_options.record_animations = False
     browse_to_search_recorder = tv_testcase_event_recorder.EventRecorder(
         recorder_options)
 
     recorder_options = tv_testcase_event_recorder.EventRecorderOptions(
         self, SEARCH_TO_BROWSE_EVENT_NAME, SEARCH_TO_BROWSE_EVENT_TYPE)
-    recorder_options.record_rasterize_animations = False
+    recorder_options.record_animations = False
     search_to_browse_recorder = tv_testcase_event_recorder.EventRecorder(
         recorder_options)
 
diff --git a/src/cobalt/webdriver_benchmarks/tests/browse_vertical.py b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_vertical.py
similarity index 85%
rename from src/cobalt/webdriver_benchmarks/tests/browse_vertical.py
rename to src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_vertical.py
index fb31b69..08a687e 100755
--- a/src/cobalt/webdriver_benchmarks/tests/browse_vertical.py
+++ b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_vertical.py
@@ -8,8 +8,11 @@
 import os
 import sys
 
-# The parent directory is a module
-sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
+# Add the base webdriver_benchmarks path
+sys.path.insert(0,
+                os.path.dirname(
+                    os.path.dirname((os.path.dirname(
+                        os.path.dirname(os.path.realpath(__file__)))))))
 
 # pylint: disable=C6204,C6203
 import tv_testcase
diff --git a/src/cobalt/webdriver_benchmarks/tests/performance/non_video/startup.py b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/startup.py
new file mode 100755
index 0000000..c37008e
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/startup.py
@@ -0,0 +1,164 @@
+#!/usr/bin/python2
+"""Simple benchmark for measuring startup time."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+import sys
+
+# Add the base webdriver_benchmarks path
+sys.path.insert(0,
+                os.path.dirname(
+                    os.path.dirname((os.path.dirname(
+                        os.path.dirname(os.path.realpath(__file__)))))))
+
+# pylint: disable=C6204,C6203
+import c_val_names
+import tv_testcase
+import tv_testcase_util
+
+NUM_LOAD_TV_ITERATIONS = 20
+
+STARTUP_RECORD_NAME = "wbStartup"
+
+
+class StartupTest(tv_testcase.TvTestCase):
+
+  def setUp(self):
+    # Override TvTestCase's setUp() so that blank startup can first be measured.
+    pass
+
+  def test_simple(self):
+    """This test tries to measure the startup time for the YouTube TV page."""
+    self.wait_for_processing_complete()
+    self.wait_for_html_script_element_execute_count(2)
+
+    # Measure durations for the intial launch.
+    launch_time = self.get_cval(c_val_names.time_cobalt_start())
+    navigate_time = self.get_cval(c_val_names.time_browser_navigate())
+    on_load_event_time = self.get_cval(c_val_names.time_browser_on_load_event())
+    browse_time = self.get_cval(c_val_names.time_rasterize_new_render_tree())
+    usable_time = self.get_cval(
+        c_val_names.time_dom_html_script_element_execute())
+
+    dur_launch_to_navigate_us = navigate_time - launch_time
+    dur_launch_to_on_load_event_us = on_load_event_time - launch_time
+    dur_launch_to_browse_us = browse_time - launch_time
+    dur_launch_to_usable_us = usable_time - launch_time
+
+    # Call TvTestCase's setUp() now that the launch times have been measured.
+    super(StartupTest, self).setUp()
+
+    # Count record strategies
+    count_record_strategies = []
+    count_record_strategies.append(tv_testcase_util.RecordStrategyMean())
+    count_record_strategies.append(tv_testcase_util.RecordStrategyMin())
+    count_record_strategies.append(tv_testcase_util.RecordStrategyMedian())
+    count_record_strategies.append(tv_testcase_util.RecordStrategyMax())
+
+    # Duration record strategies
+    duration_record_strategies = []
+    duration_record_strategies.append(tv_testcase_util.RecordStrategyMean())
+    duration_record_strategies.append(tv_testcase_util.RecordStrategyMin())
+    duration_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(25))
+    duration_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(50))
+    duration_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(75))
+    duration_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(95))
+    duration_record_strategies.append(tv_testcase_util.RecordStrategyMax())
+
+    # Count recorders
+    count_total_html_element_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "CntDomHtmlElements", count_record_strategies)
+    count_document_html_element_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "CntDomDocumentHtmlElements",
+        count_record_strategies)
+    count_layout_box_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "CntLayoutBoxes", count_record_strategies)
+    count_render_trees_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "CntRenderTrees", count_record_strategies)
+    count_requested_images_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "CntRequestedImages", count_record_strategies)
+
+    # Duration recorders
+    duration_navigate_to_on_load_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "DurNavigateToOnLoadUs",
+        duration_record_strategies)
+    duration_navigate_to_browse_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "DurNavigateToBrowseUs",
+        duration_record_strategies)
+    duration_navigate_to_usable_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "DurNavigateToUsableUs",
+        duration_record_strategies)
+
+    # Now measure counts and durations from reloading the URL.
+    for _ in range(NUM_LOAD_TV_ITERATIONS):
+      count_render_trees_start = self.get_cval(
+          c_val_names.count_rasterize_new_render_tree())
+
+      self.load_tv()
+
+      count_render_trees_end = self.get_cval(
+          c_val_names.count_rasterize_new_render_tree())
+
+      count_total_html_elements = self.get_cval(
+          c_val_names.count_dom_html_elements_total())
+      count_document_html_elements = self.get_cval(
+          c_val_names.count_dom_html_elements_document())
+      count_layout_boxes = self.get_cval(c_val_names.count_layout_boxes())
+      count_requested_images = self.get_cval(
+          c_val_names.count_image_cache_requested_resources())
+
+      navigate_time = self.get_cval(c_val_names.time_browser_navigate())
+      on_load_event_time = self.get_cval(
+          c_val_names.time_browser_on_load_event())
+      browse_time = self.get_cval(c_val_names.time_rasterize_new_render_tree())
+      usable_time = self.get_cval(
+          c_val_names.time_dom_html_script_element_execute())
+
+      count_total_html_element_recorder.collect_value(count_total_html_elements)
+      count_document_html_element_recorder.collect_value(
+          count_document_html_elements)
+      count_layout_box_recorder.collect_value(count_layout_boxes)
+      count_render_trees_recorder.collect_value(count_render_trees_end -
+                                                count_render_trees_start)
+      count_requested_images_recorder.collect_value(count_requested_images)
+
+      duration_navigate_to_on_load_recorder.collect_value(
+          on_load_event_time - navigate_time)
+      duration_navigate_to_browse_recorder.collect_value(
+          browse_time - navigate_time)
+      duration_navigate_to_usable_recorder.collect_value(
+          usable_time - navigate_time)
+
+    # Record the counts
+    count_total_html_element_recorder.on_end_test()
+    count_document_html_element_recorder.on_end_test()
+    count_layout_box_recorder.on_end_test()
+    count_render_trees_recorder.on_end_test()
+    count_requested_images_recorder.on_end_test()
+
+    # Record the durations
+    tv_testcase_util.record_test_result(
+        STARTUP_RECORD_NAME + "DurLaunchToNavigateUs",
+        dur_launch_to_navigate_us)
+    tv_testcase_util.record_test_result(
+        STARTUP_RECORD_NAME + "DurLaunchToOnLoadUs",
+        dur_launch_to_on_load_event_us)
+    tv_testcase_util.record_test_result(
+        STARTUP_RECORD_NAME + "DurLaunchToBrowseUs", dur_launch_to_browse_us)
+    tv_testcase_util.record_test_result(
+        STARTUP_RECORD_NAME + "DurLaunchToUsableUs", dur_launch_to_usable_us)
+
+    duration_navigate_to_on_load_recorder.on_end_test()
+    duration_navigate_to_browse_recorder.on_end_test()
+    duration_navigate_to_usable_recorder.on_end_test()
+
+
+if __name__ == "__main__":
+  tv_testcase.main()
diff --git a/src/cobalt/webdriver_benchmarks/tests/performance/video/__init__.py b/src/cobalt/webdriver_benchmarks/tests/performance/video/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tests/performance/video/__init__.py
diff --git a/src/cobalt/webdriver_benchmarks/tests/performance/video/browse_to_watch.py b/src/cobalt/webdriver_benchmarks/tests/performance/video/browse_to_watch.py
new file mode 100755
index 0000000..248e73c
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tests/performance/video/browse_to_watch.py
@@ -0,0 +1,111 @@
+#!/usr/bin/python2
+"""Simple benchmark for starting a video from browse."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+import sys
+
+# Add the base webdriver_benchmarks path
+sys.path.insert(0,
+                os.path.dirname(
+                    os.path.dirname((os.path.dirname(
+                        os.path.dirname(os.path.realpath(__file__)))))))
+
+# pylint: disable=C6204,C6203
+import tv_testcase
+import tv_testcase_event_recorder
+import tv_testcase_util
+
+# selenium imports
+keys = tv_testcase_util.import_selenium_module("webdriver.common.keys")
+
+MAX_VIDEO_FAILURE_COUNT = 10
+MAX_SKIPPABLE_AD_COUNT = 8
+
+NUM_LOAD_TV_CALLS = 4
+NUM_ITERATIONS_PER_LOAD_TV_CALL = 25
+
+BROWSE_TO_WATCH_EVENT_NAME = "wbBrowseToWatch"
+BROWSE_TO_WATCH_EVENT_TYPE = tv_testcase_util.EVENT_TYPE_KEY_UP
+
+WATCH_TO_BROWSE_EVENT_NAME = "wbWatchToBrowse"
+WATCH_TO_BROWSE_EVENT_TYPE = tv_testcase_util.EVENT_TYPE_KEY_UP
+
+
+class BrowseToWatchTest(tv_testcase.TvTestCase):
+
+  class VideoFailureException(BaseException):
+    """Exception thrown when MAX_VIDEO_FAILURE_COUNT is exceeded."""
+
+  class AdvertisementFailureException(BaseException):
+    """Exception thrown when MAX_SKIPPABLE_AD_COUNT is exceeded."""
+
+  def test_simple(self):
+    recorder_options = tv_testcase_event_recorder.EventRecorderOptions(
+        self, BROWSE_TO_WATCH_EVENT_NAME, BROWSE_TO_WATCH_EVENT_TYPE)
+    recorder_options.record_animations = False
+    recorder_options.record_video = True
+    browse_to_watch_recorder = tv_testcase_event_recorder.EventRecorder(
+        recorder_options)
+
+    recorder_options = tv_testcase_event_recorder.EventRecorderOptions(
+        self, WATCH_TO_BROWSE_EVENT_NAME, WATCH_TO_BROWSE_EVENT_TYPE)
+    recorder_options.record_animations = False
+    watch_to_browse_recorder = tv_testcase_event_recorder.EventRecorder(
+        recorder_options)
+
+    failure_count = 0
+    skippable_ad_count = 0
+
+    for _ in xrange(NUM_LOAD_TV_CALLS):
+      self.load_tv()
+
+      for _ in xrange(NUM_ITERATIONS_PER_LOAD_TV_CALL):
+        self.send_keys(keys.Keys.ARROW_DOWN)
+        self.wait_for_processing_complete_after_focused_shelf()
+
+        browse_to_watch_recorder.on_start_event()
+        self.send_keys(keys.Keys.ENTER)
+
+        if not self.wait_for_media_element_playing():
+          failure_count += 1
+          print("Video failed to play! {} events failed.".format(failure_count))
+          if failure_count > MAX_VIDEO_FAILURE_COUNT:
+            raise BrowseToWatchTest.VideoFailureException()
+
+          self.send_keys(keys.Keys.ESCAPE)
+          self.wait_for_processing_complete_after_focused_shelf()
+          continue
+
+        if self.skip_advertisement_if_playing():
+          skippable_ad_count += 1
+          print(
+              "Encountered skippable ad! {} total.".format(skippable_ad_count))
+          if skippable_ad_count > MAX_SKIPPABLE_AD_COUNT:
+            raise BrowseToWatchTest.AdvertisementFailureException()
+
+          self.wait_for_title_card_hidden()
+          self.send_keys(keys.Keys.ESCAPE)
+          self.wait_for_processing_complete_after_focused_shelf()
+          continue
+
+        browse_to_watch_recorder.on_end_event()
+
+        # Wait for the title card hidden before sending the escape. Otherwise,
+        # two escapes are required to exit the video.
+        self.wait_for_title_card_hidden()
+
+        watch_to_browse_recorder.on_start_event()
+        self.send_keys(keys.Keys.ESCAPE)
+        self.wait_for_processing_complete_after_focused_shelf()
+        watch_to_browse_recorder.on_end_event()
+
+    browse_to_watch_recorder.on_end_test()
+    watch_to_browse_recorder.on_end_test()
+
+
+if __name__ == "__main__":
+  tv_testcase.main()
diff --git a/src/cobalt/webdriver_benchmarks/tests/all.py b/src/cobalt/webdriver_benchmarks/tests/performance_non_video.py
old mode 100755
new mode 100644
similarity index 76%
rename from src/cobalt/webdriver_benchmarks/tests/all.py
rename to src/cobalt/webdriver_benchmarks/tests/performance_non_video.py
index c08d93c..f71a433
--- a/src/cobalt/webdriver_benchmarks/tests/all.py
+++ b/src/cobalt/webdriver_benchmarks/tests/performance_non_video.py
@@ -1,5 +1,5 @@
 #!/usr/bin/python2
-"""Target for running all tests cases."""
+"""Target for running non-video performance test cases."""
 
 from __future__ import absolute_import
 from __future__ import division
@@ -18,10 +18,11 @@
 
 
 def _add_test(test_suite, dir_path, test_name):
-  if os.path.isfile(os.path.join(dir_path, test_name + ".py")):
+  if os.path.isfile(
+      os.path.join(dir_path, "performance", "non_video", test_name + ".py")):
     print("Adding test: {}".format(test_name))
     test_suite.addTest(unittest.TestLoader().loadTestsFromModule(
-        importlib.import_module("tests." + test_name)))
+        importlib.import_module("tests.performance.non_video." + test_name)))
 
 
 # pylint: disable=unused-argument
@@ -30,15 +31,13 @@
   test_suite = unittest.TestSuite()
   dir_path = os.path.dirname(__file__)
 
-  # "time_to_shelf" must be the first test added. The timings that it
+  # "startup" must be the first test added. The timings that it
   # records require it to run first.
   _add_test(test_suite, dir_path, "startup")
   _add_test(test_suite, dir_path, "browse_horizontal")
   _add_test(test_suite, dir_path, "browse_vertical")
   _add_test(test_suite, dir_path, "browse_to_guide")
   _add_test(test_suite, dir_path, "browse_to_search")
-  _add_test(test_suite, dir_path, "browse_to_watch")
-  _add_test(test_suite, dir_path, "csi")
 
   return test_suite
 
diff --git a/src/cobalt/webdriver_benchmarks/tests/performance_video.py b/src/cobalt/webdriver_benchmarks/tests/performance_video.py
new file mode 100755
index 0000000..3784cc2
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tests/performance_video.py
@@ -0,0 +1,40 @@
+#!/usr/bin/python2
+"""Target for running video performance test cases."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import importlib
+import os
+import sys
+import unittest
+
+# The parent directory is a module
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
+
+# pylint: disable=C6204,C6203
+import tv_testcase
+
+
+def _add_test(test_suite, dir_path, test_name):
+  if os.path.isfile(
+      os.path.join(dir_path, "performance", "video", test_name + ".py")):
+    print("Adding test: {}".format(test_name))
+    test_suite.addTest(unittest.TestLoader().loadTestsFromModule(
+        importlib.import_module("tests.performance.video." + test_name)))
+
+
+# pylint: disable=unused-argument
+def load_tests(loader, tests, pattern):
+  """This is a Python unittest "load_tests protocol method."""
+  test_suite = unittest.TestSuite()
+  dir_path = os.path.dirname(__file__)
+
+  _add_test(test_suite, dir_path, "browse_to_watch")
+
+  return test_suite
+
+
+if __name__ == "__main__":
+  tv_testcase.main()
diff --git a/src/cobalt/webdriver_benchmarks/tests/startup.py b/src/cobalt/webdriver_benchmarks/tests/startup.py
deleted file mode 100755
index 4ea17fd..0000000
--- a/src/cobalt/webdriver_benchmarks/tests/startup.py
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/usr/bin/python2
-"""Simple benchmark for measuring startup time."""
-
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import os
-import sys
-
-# The parent directory is a module
-sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
-
-# pylint: disable=C6204,C6203
-import timer
-import tv_testcase
-import tv_testcase_util
-
-NUM_BLANK_TO_BROWSE_ITERATIONS = 10
-
-LAUNCH_TO_BLANK = "wbStartupDurLaunchToBlankUs"
-BLANK_TO_BROWSE = "wbStartupDurBlankToBrowseUs"
-
-
-class StartupTest(tv_testcase.TvTestCase):
-
-  def setUp(self):
-    # Override TvTestCase's setUp() so that blank startup can first be measured.
-    pass
-
-  def test_simple(self):
-    """This test tries to measure the startup time for the YouTube TV page.
-
-    Specifically, this test uses the Cobalt CVal Cobalt.Lifetime, which gets
-    updated ~60Hz on a best effort basis and is in microseconds, to determine
-    "wbStartupDurLaunchToBlankUs" and uses Timer to determine
-    "wbStartupDurBlankToBrowseUs".
-    """
-
-    dur_launch_to_blank_us = self.get_cval("Cobalt.Lifetime")
-
-    # Call TvTestCase's setUp() now that the blank startup time has been
-    # measured.
-    super(StartupTest, self).setUp()
-
-    # Blank to browser record strategies
-    blank_to_browse_record_strategies = []
-    blank_to_browse_record_strategies.append(
-        tv_testcase_util.RecordStrategyMean())
-    blank_to_browse_record_strategies.append(
-        tv_testcase_util.RecordStrategyPercentile(25))
-    blank_to_browse_record_strategies.append(
-        tv_testcase_util.RecordStrategyPercentile(50))
-    blank_to_browse_record_strategies.append(
-        tv_testcase_util.RecordStrategyPercentile(75))
-    blank_to_browse_record_strategies.append(
-        tv_testcase_util.RecordStrategyPercentile(95))
-
-    # Blank to browser recorder
-    blank_to_browse_recorder = tv_testcase_util.ResultsRecorder(
-        BLANK_TO_BROWSE, blank_to_browse_record_strategies)
-
-    for _ in range(NUM_BLANK_TO_BROWSE_ITERATIONS):
-      self.load_blank()
-      with timer.Timer(BLANK_TO_BROWSE) as t:
-        self.load_tv()
-      blank_to_browse_recorder.collect_value(int(t.seconds_elapsed * 1000000))
-
-    tv_testcase_util.record_test_result(LAUNCH_TO_BLANK, dur_launch_to_blank_us)
-    blank_to_browse_recorder.on_end_test()
-
-
-if __name__ == "__main__":
-  tv_testcase.main()
diff --git a/src/cobalt/webdriver_benchmarks/timer.py b/src/cobalt/webdriver_benchmarks/timer.py
deleted file mode 100644
index 0ee08dc..0000000
--- a/src/cobalt/webdriver_benchmarks/timer.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# Copyright 2016 Google Inc. 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.
-# ==============================================================================
-"""Contains a contextmanager for a timer.
-
-  Example usage:
-
-    from timer import Timer
-
-    with Timer('SomeTask') as t:
-      # Do some time consuming task
-      print('So far {} seconds have passed'.format(t.seconds_elapsed))
-
-  print(t)  # This will print something like 'SomeTask took 1.2 seconds'
-"""
-
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import timeit
-
-
-class Timer(object):
-  """ContextManager for measuring time since an event."""
-
-  def __init__(self, description):
-    """Initializes the timer.
-
-    Args:
-      description: A string containing the description of the timer.
-    """
-    self.description = description
-    self._timer = timeit.default_timer  # Choose best timer for the platform.
-    self.start_time = None
-    self._seconds_elapsed = None
-
-  def __enter__(self):
-    """Starts the timer.
-
-    __enter__ allows this class to be used as a ContextManager.
-
-    Returns:
-      The object itself so it works with the |with| statement.
-    """
-    self.start_time = self._timer()
-    return self
-
-  def __exit__(self, unused_ex_type, unused_ex, unused_ex_trace):
-    """Stops the timer and records the duration.
-
-    __enter__ allows this class to be used as a ContextManager.
-
-    Returns:
-      False.  Any exception raised within the body of the contextmanager will
-      propogate up the stack.
-    """
-    self._seconds_elapsed = self._timer() - self.start_time
-    return False
-
-  @property
-  def seconds_elapsed(self):
-    """Number of seconds elapsed since start until now or time when timer ended.
-
-    This property will return the number of seconds since the timer was started,
-    or the duration of the timer's context manager.
-
-    Returns:
-      A float containing the number of seconds elapsed.
-    """
-    if self._seconds_elapsed is None:
-      return self._timer() - self.start_time
-
-    return self._seconds_elapsed
-
-  def __str__(self):
-    """A magic method for generating a string representation of the object.
-
-    Returns:
-      A string containing a description and a human readable version of timer's
-      value.
-    """
-    return '{} took {} seconds.'.format(self.description, self.seconds_elapsed)
diff --git a/src/cobalt/webdriver_benchmarks/tv.py b/src/cobalt/webdriver_benchmarks/tv.py
index b3e6d29..332b3c9 100644
--- a/src/cobalt/webdriver_benchmarks/tv.py
+++ b/src/cobalt/webdriver_benchmarks/tv.py
@@ -8,4 +8,6 @@
 FOCUSED_SEARCH = '.focused.search'
 FOCUSED_SHELF = '.focused.selected.shelf'
 FOCUSED_SHELF_TITLE = '.focused.selected.shelf .shelf-header-title .main'
+SKIP_AD_BUTTON_CAN_SKIP = '.skip-ad-button.canskip'
+SKIP_AD_BUTTON_HIDDEN = '.skip-ad-button.hidden'
 TITLE_CARD_HIDDEN = '.title-card.hidden'
diff --git a/src/cobalt/webdriver_benchmarks/tv_testcase.py b/src/cobalt/webdriver_benchmarks/tv_testcase.py
index dfdb5d9..dc49278 100644
--- a/src/cobalt/webdriver_benchmarks/tv_testcase.py
+++ b/src/cobalt/webdriver_benchmarks/tv_testcase.py
@@ -19,16 +19,24 @@
 import tv_testcase_runner
 import tv_testcase_util
 
+try:
+  import custom_query_param_constants as query_param_constants
+except ImportError:
+  import default_query_param_constants as query_param_constants
+
 # selenium imports
 # pylint: disable=C0103
 ActionChains = tv_testcase_util.import_selenium_module(
     submodule="webdriver.common.action_chains").ActionChains
+keys = tv_testcase_util.import_selenium_module("webdriver.common.keys")
 
 WINDOWDRIVER_CREATED_TIMEOUT_SECONDS = 30
 WEBMODULE_LOADED_TIMEOUT_SECONDS = 30
 PAGE_LOAD_WAIT_SECONDS = 30
-PROCESSING_TIMEOUT_SECONDS = 15
+PROCESSING_TIMEOUT_SECONDS = 60
+HTML_SCRIPT_ELEMENT_EXECUTE_TIMEOUT_SECONDS = 30
 MEDIA_TIMEOUT_SECONDS = 30
+SKIP_AD_TIMEOUT_SECONDS = 30
 TITLE_CARD_HIDDEN_TIMEOUT_SECONDS = 30
 
 _is_initialized = False
@@ -50,8 +58,8 @@
   class ProcessingTimeoutException(BaseException):
     """Exception thrown when processing did not complete in time."""
 
-  class MediaTimeoutException(BaseException):
-    """Exception thrown when media did not complete in time."""
+  class HtmlScriptElementExecuteTimeoutException(BaseException):
+    """Exception thrown when processing did not complete in time."""
 
   class TitleCardHiddenTimeoutException(BaseException):
     """Exception thrown when title card did not disappear in time."""
@@ -67,17 +75,18 @@
   def setUp(self):
     global _is_initialized
     if not _is_initialized:
-      # Initialize the tests. This involves loading a URL which applies the
-      # forcedOffAllExperiments cookies, ensuring that no subsequent loads
-      # include experiments. Additionally, loading this URL triggers a reload.
-      query_params = {"env_forcedOffAllExperiments": True}
-      triggers_reload = True
-      self.load_tv(None, query_params, triggers_reload)
+      # Initialize the tests.
+      query_params = query_param_constants.INIT_QUERY_PARAMS
+      triggers_reload = query_param_constants.INIT_QUERY_PARAMS_TRIGGER_RELOAD
+      self.load_tv(query_params, triggers_reload)
       _is_initialized = True
 
   def get_webdriver(self):
     return tv_testcase_runner.GetWebDriver()
 
+  def get_default_url(self):
+    return tv_testcase_runner.GetDefaultUrl()
+
   def get_cval(self, cval_name):
     """Returns the Python object represented by a JSON cval string.
 
@@ -103,26 +112,19 @@
     self.get_webdriver().get("about:blank")
     self.wait_for_url_loaded_events()
 
-  def load_tv(self,
-              label=None,
-              additional_query_params=None,
-              triggers_reload=False):
+  def load_tv(self, query_params=None, triggers_reload=False):
     """Loads the main TV page and waits for it to display.
 
     Args:
-      label: A value for the label query parameter.
-      additional_query_params: A dict containing additional query parameters.
+      query_params: A dict containing additional query parameters.
       triggers_reload: Whether or not the navigation will trigger a reload.
     Raises:
       Underlying WebDriver exceptions
     """
-    query_params = {}
-    if label is not None:
-      query_params = {"label": label}
-    if additional_query_params is not None:
-      query_params.update(additional_query_params)
+    self.get_webdriver().execute_script("h5vcc.storage.clearCookies()")
     self.clear_url_loaded_events()
-    self.get_webdriver().get(tv_testcase_util.get_tv_url(query_params))
+    self.get_webdriver().get(
+        tv_testcase_util.generate_url(self.get_default_url(), query_params))
     self.wait_for_url_loaded_events()
     if triggers_reload:
       self.clear_url_loaded_events()
@@ -191,16 +193,16 @@
       self.assertEqual(len(elements), expected_num)
     return elements
 
-  def send_keys(self, keys):
+  def send_keys(self, key_events):
     """Sends keys to whichever element currently has focus.
 
     Args:
-      keys: key events
+      key_events: key events
 
     Raises:
       Underlying WebDriver exceptions
     """
-    ActionChains(self.get_webdriver()).send_keys(keys).perform()
+    ActionChains(self.get_webdriver()).send_keys(key_events).perform()
 
   def clear_url_loaded_events(self):
     """Clear the events that indicate that Cobalt finished loading a URL."""
@@ -240,6 +242,18 @@
       required time.
     """
     start_time = time.time()
+
+    # First simply check for whether or not the event is still processing.
+    # There's no need to check anything else while the event is still going on.
+    # Once it is done processing, it won't get re-set, so there's no need to
+    # re-check it.
+    while self.get_cval(c_val_names.event_is_processing()):
+      if time.time() - start_time > PROCESSING_TIMEOUT_SECONDS:
+        raise TvTestCase.ProcessingTimeoutException()
+
+      time.sleep(0.1)
+
+    # Now wait for all processing to complete in Cobalt.
     count = 0
     while count < 2:
       if self.is_processing(check_animations):
@@ -254,26 +268,62 @@
 
   def is_processing(self, check_animations):
     """Checks to see if Cobalt is currently processing."""
-    return (self.get_cval(c_val_names.count_dom_active_dispatch_events()) or
+    return (self.get_cval(c_val_names.count_dom_active_java_script_events()) or
             self.get_cval(c_val_names.layout_is_dirty()) or
             (check_animations and
              self.get_cval(c_val_names.renderer_has_active_animations())) or
             self.get_cval(c_val_names.count_image_cache_loading_resources()))
 
+  def wait_for_html_script_element_execute_count(self, required_count):
+    """Waits for specified number of html script element Execute() calls.
+
+    Args:
+      required_count: the number of executions that must occur
+
+    Raises:
+      HtmlScriptElementExecuteTimeoutException: The required html script element
+      executions did not occur within the required time.
+    """
+    start_time = time.time()
+    while self.get_cval(
+        c_val_names.count_dom_html_script_element_execute()) < required_count:
+      if time.time() - start_time > HTML_SCRIPT_ELEMENT_EXECUTE_TIMEOUT_SECONDS:
+        raise TvTestCase.HtmlScriptElementExecuteTimeoutException()
+      time.sleep(0.1)
+
   def wait_for_media_element_playing(self):
     """Waits for a video to begin playing.
 
-    Raises:
-      MediaTimeoutException: The video does not start playing within the
-      required time.
+    Returns:
+      Whether or not the video started.
     """
     start_time = time.time()
     while self.get_cval(
         c_val_names.event_duration_dom_video_start_delay()) == 0:
       if time.time() - start_time > MEDIA_TIMEOUT_SECONDS:
-        raise TvTestCase.MediaTimeoutException()
+        return False
       time.sleep(0.1)
 
+    return True
+
+  def skip_advertisement_if_playing(self):
+    """Waits to skip an ad if it is encountered.
+
+    Returns:
+      True if a skippable advertisement was encountered
+    """
+    start_time = time.time()
+    if not self.find_elements(tv.SKIP_AD_BUTTON_HIDDEN):
+      while not self.find_elements(tv.SKIP_AD_BUTTON_CAN_SKIP):
+        if time.time() - start_time > SKIP_AD_TIMEOUT_SECONDS:
+          return True
+        time.sleep(0.1)
+      self.send_keys(keys.Keys.ENTER)
+      self.wait_for_processing_complete(False)
+      return True
+
+    return False
+
   def wait_for_title_card_hidden(self):
     """Waits for the title to disappear while a video is playing.
 
diff --git a/src/cobalt/webdriver_benchmarks/tv_testcase_event_recorder.py b/src/cobalt/webdriver_benchmarks/tv_testcase_event_recorder.py
index cb49624..5905e84 100644
--- a/src/cobalt/webdriver_benchmarks/tv_testcase_event_recorder.py
+++ b/src/cobalt/webdriver_benchmarks/tv_testcase_event_recorder.py
@@ -24,8 +24,8 @@
     self.event_name = event_name
     self.event_type = event_type
 
-    self.record_rasterize_animations = True
-    self.record_video_start_delay = False
+    self.record_animations = True
+    self.record_video = False
 
 
 class EventRecorder(object):
@@ -37,8 +37,8 @@
   tv_testcase_util.py.
 
   Both rasterize animations and video start delay data can potentially be
-  recorded, depending on the |record_rasterize_animations| and
-  |record_video_start_delay| option flags.
+  recorded, depending on the |record_animations| and |record_video| option
+  flags.
   """
 
   def __init__(self, options):
@@ -59,21 +59,54 @@
     # Each entry in the list contains a tuple with a key and value recorder.
     self.value_dictionary_recorders = []
 
+    # Optional animations recorders
     self.animations_recorder = None
+    self.animations_start_delay_recorder = None
+    self.animations_end_delay_recorder = None
+
+    # Optional video recorders
     self.video_delay_recorder = None
 
     # Count record strategies
     count_record_strategies = []
-    count_record_strategies.append(tv_testcase_util.RecordStrategyMax())
-    count_record_strategies.append(tv_testcase_util.RecordStrategyMedian())
     count_record_strategies.append(tv_testcase_util.RecordStrategyMean())
+    count_record_strategies.append(tv_testcase_util.RecordStrategyMedian())
+    count_record_strategies.append(tv_testcase_util.RecordStrategyMax())
 
-    # Count recorders
+    # Duration record strategies
+    duration_record_strategies = []
+    duration_record_strategies.append(tv_testcase_util.RecordStrategyMean())
+    duration_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(25))
+    duration_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(50))
+    duration_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(75))
+    duration_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(95))
+
+    # Video delay record strategies
+    video_delay_record_strategies = []
+    video_delay_record_strategies.append(tv_testcase_util.RecordStrategyMean())
+    video_delay_record_strategies.append(tv_testcase_util.RecordStrategyMin())
+    video_delay_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(25))
+    video_delay_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(50))
+    video_delay_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(75))
+    video_delay_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(95))
+    video_delay_record_strategies.append(tv_testcase_util.RecordStrategyMax())
+
+    # Dictionary count recorders
     self._add_value_dictionary_recorder("CntDomEventListeners",
                                         count_record_strategies)
     self._add_value_dictionary_recorder("CntDomNodes", count_record_strategies)
     self._add_value_dictionary_recorder("CntDomHtmlElements",
                                         count_record_strategies)
+    self._add_value_dictionary_recorder("CntDomDocumentHtmlElements",
+                                        count_record_strategies)
     self._add_value_dictionary_recorder("CntDomHtmlElementsCreated",
                                         count_record_strategies)
     self._add_value_dictionary_recorder("CntDomUpdateMatchingRules",
@@ -95,19 +128,7 @@
     self._add_value_dictionary_recorder("CntLayoutUpdateCrossReferences",
                                         count_record_strategies)
 
-    # Duration record strategies
-    duration_record_strategies = []
-    duration_record_strategies.append(tv_testcase_util.RecordStrategyMean())
-    duration_record_strategies.append(
-        tv_testcase_util.RecordStrategyPercentile(25))
-    duration_record_strategies.append(
-        tv_testcase_util.RecordStrategyPercentile(50))
-    duration_record_strategies.append(
-        tv_testcase_util.RecordStrategyPercentile(75))
-    duration_record_strategies.append(
-        tv_testcase_util.RecordStrategyPercentile(95))
-
-    # Duration recorders
+    # Dictionary duration recorders
     self._add_value_dictionary_recorder("DurTotalUs",
                                         duration_record_strategies)
     self._add_value_dictionary_recorder("DurDomInjectEventUs",
@@ -125,16 +146,32 @@
     self._add_value_dictionary_recorder("DurLayoutRenderAndAnimateUs",
                                         duration_record_strategies)
 
-    # Optional rasterize animations recorders
-    if options.record_rasterize_animations:
+    self.count_render_trees_recorder = tv_testcase_util.ResultsRecorder(
+        self.event_name + "CntRenderTrees", count_record_strategies)
+    self.count_requested_images_recorder = tv_testcase_util.ResultsRecorder(
+        self.event_name + "CntRequestedImages", count_record_strategies)
+
+    # Optional animations recorder
+    if options.record_animations:
       self.animations_recorder = tv_testcase_util.ResultsRecorder(
           self.event_name + "DurRasterizeAnimationsUs",
           duration_record_strategies)
+      self.animations_start_delay_recorder = tv_testcase_util.ResultsRecorder(
+          self.event_name + "DurAnimationsStartDelayUs",
+          duration_record_strategies)
+      self.animations_end_delay_recorder = tv_testcase_util.ResultsRecorder(
+          self.event_name + "DurAnimationsEndDelayUs",
+          duration_record_strategies)
 
-    # Optional video start delay recorder
-    if options.record_video_start_delay:
+    self.final_render_tree_delay_recorder = tv_testcase_util.ResultsRecorder(
+        self.event_name + "DurFinalRenderTreeDelayUs",
+        duration_record_strategies)
+
+    # Optional video recorder
+    if options.record_video:
       self.video_delay_recorder = tv_testcase_util.ResultsRecorder(
-          self.event_name + "DurVideoStartDelayUs", duration_record_strategies)
+          self.event_name + "DurVideoStartDelayUs",
+          video_delay_record_strategies)
 
   def _add_value_dictionary_recorder(self, key, record_strategies):
     recorder = tv_testcase_util.ResultsRecorder(self.event_name + key,
@@ -143,7 +180,10 @@
 
   def on_start_event(self):
     """Handles logic related to the start of the event instance."""
-    pass
+    self.count_render_trees_start = self.test.get_cval(
+        c_val_names.count_rasterize_new_render_tree())
+    self.count_requested_images_start = self.test.get_cval(
+        c_val_names.count_image_cache_requested_resources())
 
   def on_end_event(self):
     """Handles logic related to the end of the event instance."""
@@ -159,18 +199,44 @@
           self.event_name, self.render_tree_failure_count))
       return
 
+    event_start_time = value_dictionary.get("StartTime")
+
     # Record all of the values from the event.
     for value_dictionary_recorder in self.value_dictionary_recorders:
       value = value_dictionary.get(value_dictionary_recorder[0])
       if value is not None:
         value_dictionary_recorder[1].collect_value(value)
 
+    self.count_render_trees_end = self.test.get_cval(
+        c_val_names.count_rasterize_new_render_tree())
+    self.count_render_trees_recorder.collect_value(
+        self.count_render_trees_end - self.count_render_trees_start)
+
+    self.count_requested_images_end = self.test.get_cval(
+        c_val_names.count_image_cache_requested_resources())
+    self.count_requested_images_recorder.collect_value(
+        self.count_requested_images_end - self.count_requested_images_start)
+
     if self.animations_recorder:
       animation_entries = self.test.get_cval(
           c_val_names.rasterize_animations_entry_list())
       for value in animation_entries:
         self.animations_recorder.collect_value(value)
 
+      animations_start_time = self.test.get_cval(
+          c_val_names.time_rasterize_animations_start())
+      self.animations_start_delay_recorder.collect_value(
+          animations_start_time - event_start_time)
+      animations_end_time = self.test.get_cval(
+          c_val_names.time_rasterize_animations_end())
+      self.animations_end_delay_recorder.collect_value(animations_end_time -
+                                                       event_start_time)
+
+    final_render_tree_time = self.test.get_cval(
+        c_val_names.time_rasterize_new_render_tree())
+    self.final_render_tree_delay_recorder.collect_value(final_render_tree_time -
+                                                        event_start_time)
+
     if self.video_delay_recorder:
       self.video_delay_recorder.collect_value(
           self.test.get_cval(
@@ -182,8 +248,15 @@
     for value_dictionary_recorder in self.value_dictionary_recorders:
       value_dictionary_recorder[1].on_end_test()
 
+    self.count_render_trees_recorder.on_end_test()
+    self.count_requested_images_recorder.on_end_test()
+
     if self.animations_recorder:
       self.animations_recorder.on_end_test()
+      self.animations_start_delay_recorder.on_end_test()
+      self.animations_end_delay_recorder.on_end_test()
+
+    self.final_render_tree_delay_recorder.on_end_test()
 
     if self.video_delay_recorder:
       self.video_delay_recorder.on_end_test()
diff --git a/src/cobalt/webdriver_benchmarks/tv_testcase_runner.py b/src/cobalt/webdriver_benchmarks/tv_testcase_runner.py
index 829c337..6f9981c 100755
--- a/src/cobalt/webdriver_benchmarks/tv_testcase_runner.py
+++ b/src/cobalt/webdriver_benchmarks/tv_testcase_runner.py
@@ -27,7 +27,7 @@
 arg_parser.add_argument(
     "-e",
     "--executable",
-    help="Path to cobalt executable. "
+    help="Path to Cobalt executable. "
     "Auto-derived if absent.")
 arg_parser.add_argument(
     "-c",
@@ -42,6 +42,12 @@
     help="Devkit or IP address for app_launcher."
     "Current hostname used if absent.")
 arg_parser.add_argument(
+    "--command_line",
+    nargs="*",
+    help="Command line arguments to pass to the Cobalt executable.")
+arg_parser.add_argument(
+    "--url", help="Specifies the URL to run the tests against.")
+arg_parser.add_argument(
     "-o", "--log_file", help="Logfile pathname. stdout if absent.")
 
 # Pattern to match Cobalt log line for when the WebDriver port has been
@@ -67,6 +73,7 @@
 _webdriver = None
 _windowdriver_created = threading.Event()
 _webmodule_loaded = threading.Event()
+_default_url = "https://www.youtube.com/tv"
 
 
 def GetWebDriver():
@@ -84,6 +91,11 @@
   return _webmodule_loaded
 
 
+def GetDefaultUrl():
+  """Returns the default url to use with tests."""
+  return _default_url
+
+
 class TimeoutException(Exception):
   pass
 
@@ -99,7 +111,12 @@
   failed = False
   should_exit = threading.Event()
 
-  def __init__(self, platform, executable, devkit_name, log_file_path):
+  def __init__(self, platform, executable, devkit_name, command_line_args,
+               default_url, log_file_path):
+    global _default_url
+    if default_url is not None:
+      _default_url = default_url
+
     self.selenium_webdriver_module = tv_testcase_util.import_selenium_module(
         "webdriver")
 
@@ -113,10 +130,13 @@
         platform, executable, devkit_name=devkit_name, close_output_file=False)
 
     args = []
+    if command_line_args is not None:
+      for command_line_arg in command_line_args:
+        args.append("--" + command_line_arg)
     args.append("--enable_webdriver")
     args.append("--null_savegame")
     args.append("--debug_console=off")
-    args.append("--url=about:blank")
+    args.append("--url=" + _default_url)
 
     self.launcher.SetArgs(args)
     self.launcher.SetOutputCallback(self._HandleLine)
@@ -138,8 +158,8 @@
   def __exit__(self, exc_type, exc_value, traceback):
     # The unittest module terminates with a SystemExit
     # If this is a successful exit, then this is a successful run
-    success = exc_type is None or (exc_type is SystemExit and not exc_value.code
-                                  )
+    success = exc_type is None or (exc_type is SystemExit and
+                                   not exc_value.code)
     self.SetShouldExit(failed=not success)
     self.thread.join(COBALT_EXIT_TIMEOUT_SECONDS)
 
@@ -263,8 +283,8 @@
     executable = GetCobaltExecutablePath(platform, args.config)
 
   try:
-    with CobaltRunner(platform, executable, args.devkit_name,
-                      args.log_file) as runner:
+    with CobaltRunner(platform, executable, args.devkit_name, args.command_line,
+                      args.url, args.log_file) as runner:
       unittest.main(testRunner=unittest.TextTestRunner(
           verbosity=0, stream=runner.log_file))
   except TimeoutException:
diff --git a/src/cobalt/webdriver_benchmarks/tv_testcase_util.py b/src/cobalt/webdriver_benchmarks/tv_testcase_util.py
index 34d574a..6cb9a52 100644
--- a/src/cobalt/webdriver_benchmarks/tv_testcase_util.py
+++ b/src/cobalt/webdriver_benchmarks/tv_testcase_util.py
@@ -14,6 +14,11 @@
 
 import container_util
 
+try:
+  import custom_query_param_constants as query_param_constants
+except ImportError:
+  import default_query_param_constants as query_param_constants
+
 # These are watched for in webdriver_benchmark_test.py
 TEST_RESULT = "webdriver_benchmark TEST RESULT"
 TEST_COMPLETE = "webdriver_benchmark TEST COMPLETE"
@@ -22,11 +27,6 @@
 EVENT_TYPE_KEY_DOWN = "KeyDown"
 EVENT_TYPE_KEY_UP = "KeyUp"
 
-# URL-related constants
-BASE_URL = "https://www.youtube.com/"
-TV_APP_PATH = "/tv"
-BASE_PARAMS = {}
-
 
 def import_selenium_module(submodule=None):
   """Dynamically imports a selenium.webdriver submodule.
@@ -61,23 +61,28 @@
   return module
 
 
-def get_url(path, query_params=None):
-  """Returns the URL indicated by the path and query parameters."""
-  parsed_url = list(urlparse.urlparse(BASE_URL))
-  parsed_url[2] = path
-  query_dict = BASE_PARAMS.copy()
-  if query_params:
-    query_dict.update(urlparse.parse_qsl(parsed_url[4]))
-    container_util.merge_dict(query_dict, query_params)
-  parsed_url[4] = urlencode(query_dict, doseq=True)
+def generate_url(default_url, query_params_override=None):
+  """Returns the URL indicated by the path and query parameters.
+
+  Args:
+    default_url: the default url to use; its query params may be overridden
+    query_params_override: optional query params that override the ones
+                           contained within the default URL
+  Returns:
+    the url generated from the parameters
+  """
+  parsed_url = list(urlparse.urlparse(default_url))
+
+  query_params = query_param_constants.BASE_QUERY_PARAMS
+  if query_params_override:
+    query_params.update(query_params_override)
+  else:
+    query_params.update(urlparse.parse_qsl(parsed_url[4]))
+
+  parsed_url[4] = urlencode(query_params, doseq=True)
   return urlparse.urlunparse(parsed_url)
 
 
-def get_tv_url(query_params=None):
-  """Returns the tv URL indicated by the query parameters."""
-  return get_url(TV_APP_PATH, query_params)
-
-
 def record_test_result(name, result):
   """Records an individual scalar result of a benchmark test.
 
diff --git a/src/media/base/video_resolution.h b/src/media/base/video_resolution.h
index 714cca1..29ae65f 100644
--- a/src/media/base/video_resolution.h
+++ b/src/media/base/video_resolution.h
@@ -17,6 +17,7 @@
 #ifndef MEDIA_BASE_VIDEO_RESOLUTION_H_
 #define MEDIA_BASE_VIDEO_RESOLUTION_H_
 
+#include "base/logging.h"
 #include "media/base/media_export.h"
 #include "ui/gfx/size.h"
 
@@ -24,11 +25,15 @@
 
 // Enumerates the various representations of the resolution of videos.  Note
 // that except |kVideoResolutionInvalid|, all other values are guaranteed to be
-// in the same order as its (width, height) pair.
+// in the same order as its (width, height) pair. Note, unlike the other valid
+// resolution levels, |kVideoResolutionHighRes| is not a 16:9 resolution.
 enum VideoResolution {
-  kVideoResolution1080p,  // 1920 x 1080
-  kVideoResolution2k,     // 2560 x 1440
-  kVideoResolution4k,     // 3840 x 2160
+  kVideoResolution1080p,    // 1920 x 1080
+  kVideoResolution2k,       // 2560 x 1440
+  kVideoResolution4k,       // 3840 x 2160
+  kVideoResolution5k,       // 5120 × 2880
+  kVideoResolution8k,       // 7680 x 4320
+  kVideoResolutionHighRes,  // 8192 x 8192
   kVideoResolutionInvalid
 };
 
@@ -42,6 +47,17 @@
   if (width <= 3840 && height <= 2160) {
     return kVideoResolution4k;
   }
+  if (width <= 5120 && height <= 2880) {
+    return kVideoResolution5k;
+  }
+  if (width <= 7680 && height <= 4320) {
+    return kVideoResolution8k;
+  }
+  if (width <= 8192 && height <= 8192) {
+    return kVideoResolutionHighRes;
+  }
+  DLOG(FATAL) << "Invalid VideoResolution: width: " << width
+              << " height: " << height;
   return kVideoResolutionInvalid;
 }
 
diff --git a/src/starboard/shared/starboard/application.h b/src/starboard/shared/starboard/application.h
index baf00f0..2469a6e 100644
--- a/src/starboard/shared/starboard/application.h
+++ b/src/starboard/shared/starboard/application.h
@@ -217,9 +217,7 @@
                    int y,
                    int width,
                    int height);
-#endif  // SB_HAS(PLAYER) && \
-           (SB_API_VERSION >= 4 || \
-            SB_IS(PLAYER_PUNCHED_OUT))
+#endif  // SB_HAS(PLAYER) && (SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT))
 
   // Registers a |callback| function that will be called when |Teardown| is
   // called.
@@ -256,9 +254,7 @@
                            int y,
                            int width,
                            int height) {}
-#endif  // SB_HAS(PLAYER) && \
-           (SB_API_VERSION >= 4 || \
-            SB_IS(PLAYER_PUNCHED_OUT))
+#endif  // SB_HAS(PLAYER) && (SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT))
 
   // Blocks until the next event is available. Subclasses must implement this
   // method to provide events for the platform. Gives ownership to the caller.