Import Cobalt 4.13403
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousIndexedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousIndexedGetterInterface.cc
index 9a486e9..ff82636 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousIndexedGetterInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousIndexedGetterInterface.cc
@@ -86,6 +86,7 @@
 
 bool IsSupportedIndexProperty(JSContext* context, JS::HandleObject object,
                               uint32_t index) {
+  TRACE_EVENT0("cobalt::bindings", "IsSupportedIndexProperty");
   WrapperPrivate* wrapper_private =
       WrapperPrivate::GetFromObject(context, object);
   AnonymousIndexedGetterInterface* impl =
@@ -108,6 +109,7 @@
 JSBool GetIndexedProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JS::MutableHandleValue vp) {
+  TRACE_EVENT0("cobalt::bindings", "GetIndexedProperty");
   JS::RootedValue id_value(context);
   if (!JS_IdToValue(context, id, id_value.address())) {
     NOTREACHED();
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousNamedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousNamedGetterInterface.cc
index 5431665..a896d35 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousNamedGetterInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousNamedGetterInterface.cc
@@ -86,6 +86,7 @@
 
 bool IsSupportedNamedProperty(JSContext* context, JS::HandleObject object,
                               const std::string& property_name) {
+  TRACE_EVENT0("cobalt::bindings", "IsSupportedNamedProperty");
   WrapperPrivate* wrapper_private =
       WrapperPrivate::GetFromObject(context, object);
   AnonymousNamedGetterInterface* impl =
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousNamedIndexedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousNamedIndexedGetterInterface.cc
index 0d132c4..8f883ff 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousNamedIndexedGetterInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousNamedIndexedGetterInterface.cc
@@ -86,6 +86,7 @@
 
 bool IsSupportedNamedProperty(JSContext* context, JS::HandleObject object,
                               const std::string& property_name) {
+  TRACE_EVENT0("cobalt::bindings", "IsSupportedNamedProperty");
   WrapperPrivate* wrapper_private =
       WrapperPrivate::GetFromObject(context, object);
   AnonymousNamedIndexedGetterInterface* impl =
@@ -177,6 +178,7 @@
 
 bool IsSupportedIndexProperty(JSContext* context, JS::HandleObject object,
                               uint32_t index) {
+  TRACE_EVENT0("cobalt::bindings", "IsSupportedIndexProperty");
   WrapperPrivate* wrapper_private =
       WrapperPrivate::GetFromObject(context, object);
   AnonymousNamedIndexedGetterInterface* impl =
@@ -199,6 +201,7 @@
 JSBool GetIndexedProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JS::MutableHandleValue vp) {
+  TRACE_EVENT0("cobalt::bindings", "GetIndexedProperty");
   JS::RootedValue id_value(context);
   if (!JS_IdToValue(context, id, id_value.address())) {
     NOTREACHED();
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedGetterSetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedGetterSetterInterface.cc
index 286e8b8..647ef55 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedGetterSetterInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedGetterSetterInterface.cc
@@ -86,6 +86,7 @@
 
 bool IsSupportedNamedProperty(JSContext* context, JS::HandleObject object,
                               const std::string& property_name) {
+  TRACE_EVENT0("cobalt::bindings", "IsSupportedNamedProperty");
   WrapperPrivate* wrapper_private =
       WrapperPrivate::GetFromObject(context, object);
   DerivedGetterSetterInterface* impl =
@@ -177,6 +178,7 @@
 
 bool IsSupportedIndexProperty(JSContext* context, JS::HandleObject object,
                               uint32_t index) {
+  TRACE_EVENT0("cobalt::bindings", "IsSupportedIndexProperty");
   WrapperPrivate* wrapper_private =
       WrapperPrivate::GetFromObject(context, object);
   DerivedGetterSetterInterface* impl =
@@ -199,6 +201,7 @@
 JSBool GetIndexedProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JS::MutableHandleValue vp) {
+  TRACE_EVENT0("cobalt::bindings", "GetIndexedProperty");
   JS::RootedValue id_value(context);
   if (!JS_IdToValue(context, id, id_value.address())) {
     NOTREACHED();
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsIndexedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsIndexedGetterInterface.cc
index 66f6497..b362b42 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsIndexedGetterInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsIndexedGetterInterface.cc
@@ -86,6 +86,7 @@
 
 bool IsSupportedIndexProperty(JSContext* context, JS::HandleObject object,
                               uint32_t index) {
+  TRACE_EVENT0("cobalt::bindings", "IsSupportedIndexProperty");
   WrapperPrivate* wrapper_private =
       WrapperPrivate::GetFromObject(context, object);
   IndexedGetterInterface* impl =
@@ -108,6 +109,7 @@
 JSBool GetIndexedProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JS::MutableHandleValue vp) {
+  TRACE_EVENT0("cobalt::bindings", "GetIndexedProperty");
   JS::RootedValue id_value(context);
   if (!JS_IdToValue(context, id, id_value.address())) {
     NOTREACHED();
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedGetterInterface.cc
index 51f1f54..79fb735 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedGetterInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedGetterInterface.cc
@@ -86,6 +86,7 @@
 
 bool IsSupportedNamedProperty(JSContext* context, JS::HandleObject object,
                               const std::string& property_name) {
+  TRACE_EVENT0("cobalt::bindings", "IsSupportedNamedProperty");
   WrapperPrivate* wrapper_private =
       WrapperPrivate::GetFromObject(context, object);
   NamedGetterInterface* impl =
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedIndexedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedIndexedGetterInterface.cc
index b1d1234..7f0e6b7 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedIndexedGetterInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedIndexedGetterInterface.cc
@@ -86,6 +86,7 @@
 
 bool IsSupportedNamedProperty(JSContext* context, JS::HandleObject object,
                               const std::string& property_name) {
+  TRACE_EVENT0("cobalt::bindings", "IsSupportedNamedProperty");
   WrapperPrivate* wrapper_private =
       WrapperPrivate::GetFromObject(context, object);
   NamedIndexedGetterInterface* impl =
@@ -177,6 +178,7 @@
 
 bool IsSupportedIndexProperty(JSContext* context, JS::HandleObject object,
                               uint32_t index) {
+  TRACE_EVENT0("cobalt::bindings", "IsSupportedIndexProperty");
   WrapperPrivate* wrapper_private =
       WrapperPrivate::GetFromObject(context, object);
   NamedIndexedGetterInterface* impl =
@@ -199,6 +201,7 @@
 JSBool GetIndexedProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JS::MutableHandleValue vp) {
+  TRACE_EVENT0("cobalt::bindings", "GetIndexedProperty");
   JS::RootedValue id_value(context);
   if (!JS_IdToValue(context, id, id_value.address())) {
     NOTREACHED();
diff --git a/src/cobalt/bindings/mozjs/templates/interface.cc.template b/src/cobalt/bindings/mozjs/templates/interface.cc.template
index 23575c7..2dfc9eb 100644
--- a/src/cobalt/bindings/mozjs/templates/interface.cc.template
+++ b/src/cobalt/bindings/mozjs/templates/interface.cc.template
@@ -126,6 +126,7 @@
 {% if named_property_getter %}
 bool IsSupportedNamedProperty(JSContext* context, JS::HandleObject object,
                               const std::string& property_name) {
+  TRACE_EVENT0("cobalt::bindings", "IsSupportedNamedProperty");
 {{ get_impl_class_instance(impl_class) }}
   return impl->CanQueryNamedProperty(property_name);
 }
@@ -214,6 +215,7 @@
 {% if indexed_property_getter %}
 bool IsSupportedIndexProperty(JSContext* context, JS::HandleObject object,
                               uint32_t index) {
+  TRACE_EVENT0("cobalt::bindings", "IsSupportedIndexProperty");
 {{ get_impl_class_instance(impl_class) }}
   return index < impl->length();
 }
@@ -230,6 +232,7 @@
 JSBool GetIndexedProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JS::MutableHandleValue vp) {
+  TRACE_EVENT0("cobalt::bindings", "GetIndexedProperty");
   JS::RootedValue id_value(context);
   if (!JS_IdToValue(context, id, id_value.address())) {
     NOTREACHED();
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index 438692d..9385cc0 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -60,6 +60,7 @@
 
 namespace {
 const int kStatUpdatePeriodMs = 1000;
+const int kLiteStatUpdatePeriodMs = 16;
 
 const char kDefaultURL[] = "https://www.youtube.com/tv";
 
@@ -292,7 +293,8 @@
     : message_loop_(MessageLoop::current()),
       quit_closure_(quit_closure),
       start_time_(base::TimeTicks::Now()),
-      stats_update_timer_(true, true) {
+      stats_update_timer_(true, true),
+      lite_stats_update_timer_(true, true) {
   DCHECK(MessageLoop::current());
   DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type());
 
@@ -307,6 +309,10 @@
   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)));
 
   // Check to see if a timed_trace has been set, indicating that we should
   // begin a timed trace upon startup.
@@ -692,6 +698,10 @@
   }
 }
 
+void Application::UpdatePeriodicLiteStats() {
+  c_val_stats_.app_lifetime = base::TimeTicks::Now() - start_time_;
+}
+
 void Application::UpdatePeriodicStats() {
 #if defined(__LB_SHELL__)
   bool memory_stats_updated = false;
@@ -731,8 +741,6 @@
     *c_val_stats_.used_gpu_memory = used_gpu_memory;
   }
 #endif
-
-  c_val_stats_.app_lifetime = base::TimeTicks::Now() - start_time_;
 }
 
 }  // namespace browser
diff --git a/src/cobalt/browser/application.h b/src/cobalt/browser/application.h
index dc4a362..da9f8b1 100644
--- a/src/cobalt/browser/application.h
+++ b/src/cobalt/browser/application.h
@@ -154,6 +154,7 @@
   void UpdateAndMaybeRegisterUserAgent();
 
   void UpdatePeriodicStats();
+  void UpdatePeriodicLiteStats();
 
   static ssize_t available_memory_;
   static int64 lifetime_in_ms_;
@@ -171,6 +172,7 @@
   CValStats c_val_stats_;
 
   base::Timer stats_update_timer_;
+  base::Timer lite_stats_update_timer_;
 };
 
 // Factory method for creating an application.  It should be implemented
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index 58405df..72f1dd8 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -179,8 +179,6 @@
   h5vcc_settings.account_manager = account_manager;
   h5vcc_settings.event_dispatcher = system_window->event_dispatcher();
   h5vcc_settings.initial_deep_link = options.initial_deep_link;
-  h5vcc_settings.on_set_record_stats = base::Bind(
-      &BrowserModule::OnSetRecordStats, base::Unretained(this));
   web_module_options_.injected_window_attributes["h5vcc"] =
       base::Bind(&CreateH5VCC, h5vcc_settings);
 
@@ -708,11 +706,5 @@
 }
 #endif  // OS_STARBOARD
 
-void BrowserModule::OnSetRecordStats(bool set) {
-  if (web_module_) {
-    web_module_->OnSetRecordStats(set);
-  }
-}
-
 }  // namespace browser
 }  // namespace cobalt
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index 677aa32..82322b3 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -210,9 +210,6 @@
   // Process all messages queued into the |render_tree_submission_queue_|.
   void ProcessRenderTreeSubmissionQueue();
 
-  // Called when h5vcc.system.record_stats is set
-  void OnSetRecordStats(bool set);
-
   // TODO:
   //     WeakPtr usage here can be avoided if BrowserModule has a thread to
   //     own where it can ensure that its tasks are all resolved when it is
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index 29a4b1d..a23cdb6 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -144,10 +144,6 @@
   void Suspend();
   void Resume(render_tree::ResourceProvider* resource_provider);
 
-  void OnSetRecordStats(bool set) {
-    web_module_stat_tracker_->OnSetRecordStats(set);
-  }
-
  private:
   class DocumentLoadedObserver;
 
@@ -394,7 +390,7 @@
 
   environment_settings_.reset(new dom::DOMSettings(
       kDOMMaxElementDepth, fetcher_factory_.get(), data.network_module, window_,
-      media_source_registry_.get(), javascript_engine_.get(),
+      media_source_registry_.get(), data.media_module, javascript_engine_.get(),
       global_environment_.get(), data.options.dom_settings_options));
   DCHECK(environment_settings_);
 
@@ -887,12 +883,5 @@
                             base::Unretained(impl_.get()), resource_provider));
 }
 
-void WebModule::OnSetRecordStats(bool set) {
-  DCHECK(message_loop());
-  DCHECK(impl_);
-  DCHECK_EQ(MessageLoop::current(), message_loop());
-  impl_->OnSetRecordStats(set);
-}
-
 }  // namespace browser
 }  // namespace cobalt
diff --git a/src/cobalt/browser/web_module.h b/src/cobalt/browser/web_module.h
index 5b6c8c2..b82f6f7 100644
--- a/src/cobalt/browser/web_module.h
+++ b/src/cobalt/browser/web_module.h
@@ -199,9 +199,6 @@
   // can only be called if we have previously suspended the WebModule.
   void Resume(render_tree::ResourceProvider* resource_provider);
 
-  // Called when h5vcc.system.recordStats is set.
-  void OnSetRecordStats(bool set);
-
 #if defined(COBALT_BUILD_TYPE_DEBUG)
   // Non-optimized builds require a bigger stack size.
   static const size_t kBaseStackSize = 2 * 1024 * 1024;
diff --git a/src/cobalt/browser/web_module_stat_tracker.cc b/src/cobalt/browser/web_module_stat_tracker.cc
index dc94278..aa4d634 100644
--- a/src/cobalt/browser/web_module_stat_tracker.cc
+++ b/src/cobalt/browser/web_module_stat_tracker.cc
@@ -20,9 +20,6 @@
 #include "cobalt/base/tokens.h"
 #include "cobalt/dom/event.h"
 
-// The maximum allowed string size of any recorded stat
-const std::string::size_type kMaxRecordedStatsBytes = 64 * 1024;
-
 namespace cobalt {
 namespace browser {
 
@@ -90,17 +87,6 @@
   layout_stat_tracker_->FlushPeriodicTracking();
 }
 
-void WebModuleStatTracker::OnSetRecordStats(bool set) {
-  record_stats_ = set;
-
-  // Every time this variable is set, we clear out our stats
-  for (ScopedVector<EventStats>::iterator it = event_stats_.begin();
-       it != event_stats_.end();
-       ++it) {
-    (*it)->event_durations = "[]";
-  }
-}
-
 WebModuleStatTracker::EventStats::EventStats(const std::string& name)
     : count_dom_html_elements_created(
           StringPrintf("Event.Count.%s.DOM.HtmlElement.Created", name.c_str()),
@@ -157,11 +143,7 @@
           StringPrintf("Event.Duration.%s.Layout.RenderAndAnimate",
                        name.c_str()),
           base::TimeDelta(),
-          "RenderAndAnimate duration for event (in microseconds)."),
-      event_durations(StringPrintf("Event.Durations.%s", name.c_str()),
-                     "[]",
-                     "JSON array of all event durations (in microseconds) "
-                     "since reset.") {}
+          "RenderAndAnimate duration for event (in microseconds).") {}
 
 bool WebModuleStatTracker::IsStopWatchEnabled(int /*id*/) const { return true; }
 
@@ -209,25 +191,9 @@
   // misleading as it merely indicates how long the user waited to initiate the
   // next event. When this occurs, the injection duration provides a much more
   // accurate picture of how long the event takes.
-  base::TimeDelta duration_total = was_render_tree_produced
+  event_stats->duration_total = was_render_tree_produced
                                     ? stop_watch_durations_[kStopWatchTypeEvent]
                                     : event_injection_duration;
-  event_stats->duration_total = duration_total;
-
-  if (record_stats_) {
-    std::string prev_durations = event_stats->event_durations.value();
-    if (prev_durations.size() <= 2) {
-      event_stats->event_durations =
-          StringPrintf("[%ld]", duration_total.InMicroseconds());
-    } else if (prev_durations.size() < kMaxRecordedStatsBytes) {
-      event_stats->event_durations
-          = StringPrintf("%s,%ld]",
-                         prev_durations.substr(
-                             0, prev_durations.size() - 1).c_str(),
-                             duration_total.InMicroseconds());
-    }
-  }
-
   event_stats->duration_dom_inject_event = event_injection_duration;
   event_stats->duration_dom_update_computed_style =
       dom_stat_tracker_->GetStopWatchTypeDuration(
diff --git a/src/cobalt/browser/web_module_stat_tracker.h b/src/cobalt/browser/web_module_stat_tracker.h
index 8ebf73d..5b317e7 100644
--- a/src/cobalt/browser/web_module_stat_tracker.h
+++ b/src/cobalt/browser/web_module_stat_tracker.h
@@ -54,9 +54,6 @@
   // triggers flushing of periodic counts within the stat trackers.
   void OnRenderTreeProduced();
 
-  // Called when h5vcc.system.record_stats is set
-  void OnSetRecordStats(bool set);
-
  private:
   enum EventType {
     kEventTypeInvalid = -1,
@@ -93,9 +90,6 @@
         duration_layout_update_used_sizes;
     base::CVal<base::TimeDelta, base::CValPublic>
         duration_layout_render_and_animate;
-
-    // Time series-related
-    base::CVal<std::string, base::CValPublic> event_durations;
   };
 
   // From base::StopWatchOwner
@@ -122,9 +116,6 @@
   std::vector<base::StopWatch> stop_watches_;
   std::vector<base::TimeDelta> stop_watch_durations_;
 
-  // Time series-related
-  bool record_stats_;
-
   std::string name_;
 
   base::CVal<int, base::CValPublic> event_is_processing_;
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index a658c30..d690671 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-13245
\ No newline at end of file
+13403
\ No newline at end of file
diff --git a/src/cobalt/build/config/base.gypi b/src/cobalt/build/config/base.gypi
index 36b2f95..8ba37eb 100644
--- a/src/cobalt/build/config/base.gypi
+++ b/src/cobalt/build/config/base.gypi
@@ -236,9 +236,6 @@
     'werror': '',
     # Cobalt doesn't currently support tcmalloc.
     'linux_use_tcmalloc': 0,
-
-    'enable_webdriver%': 0,
-
     # The event polling mechanism available on this platform to support libevent.
     # Platforms may redefine to 'poll' if necessary.
     # Other mechanisms, e.g. devpoll, kqueue, select, are not yet supported.
@@ -456,6 +453,7 @@
         'enable_network_logging': 1,
         'enable_remote_debugging%': 1,
         'enable_screenshot': 1,
+        'enable_webdriver%': 1,
       },
     },
     {
@@ -467,6 +465,7 @@
         'enable_network_logging': 0,
         'enable_remote_debugging%': 0,
         'enable_screenshot': 0,
+        'enable_webdriver': 0,
       },
     }],
   ],
diff --git a/src/cobalt/build/config/win.gypi b/src/cobalt/build/config/win.gypi
index 6a8cab4..8504e8b 100644
--- a/src/cobalt/build/config/win.gypi
+++ b/src/cobalt/build/config/win.gypi
@@ -34,6 +34,9 @@
     # there for acceptable values for this variable.
     'javascript_engine': 'javascriptcore',
 
+    # Webdriver won't compile on MSVC because of UTF8 string constant issues
+    'enable_webdriver': 0,
+
     # Compile with "PREfast" on by default.
     'static_analysis%': 'true',
 
diff --git a/src/cobalt/css_parser/parser.cc b/src/cobalt/css_parser/parser.cc
index c3ff81a..25f4d35 100644
--- a/src/cobalt/css_parser/parser.cc
+++ b/src/cobalt/css_parser/parser.cc
@@ -24,6 +24,7 @@
 #include <string>
 
 #include "base/bind.h"
+#include "base/debug/trace_event.h"
 #include "base/hash_tables.h"
 #include "base/lazy_instance.h"
 #include "base/optional.h"
@@ -365,6 +366,7 @@
 bool ParserImpl::Parse() {
   // For more information on error codes
   // see http://www.gnu.org/software/bison/manual/html_node/Parser-Function.html
+  TRACE_EVENT0("cobalt::css_parser", "ParseImpl::Parse");
   last_syntax_error_location_ = base::nullopt;
   int error_code(yyparse(this));
   switch (error_code) {
diff --git a/src/cobalt/cssom/css_declared_style_declaration.cc b/src/cobalt/cssom/css_declared_style_declaration.cc
index c1d455c..6c4557e 100644
--- a/src/cobalt/cssom/css_declared_style_declaration.cc
+++ b/src/cobalt/cssom/css_declared_style_declaration.cc
@@ -16,6 +16,7 @@
 
 #include "cobalt/cssom/css_declared_style_declaration.h"
 
+#include "base/debug/trace_event.h"
 #include "base/lazy_instance.h"
 #include "cobalt/cssom/css_declared_style_data.h"
 #include "cobalt/cssom/css_parser.h"
@@ -63,6 +64,7 @@
 
 void CSSDeclaredStyleDeclaration::set_css_text(
     const std::string& css_text, script::ExceptionState* /*exception_state*/) {
+  TRACE_EVENT0("cobalt::cssom", "CSSDeclaredStyleDeclaration::set_css_text");
   DCHECK(css_parser_);
   scoped_refptr<CSSDeclaredStyleData> declaration =
       css_parser_->ParseStyleDeclarationList(
diff --git a/src/cobalt/dom/MediaSource.idl b/src/cobalt/dom/MediaSource.idl
index a53419c..65eeb35 100644
--- a/src/cobalt/dom/MediaSource.idl
+++ b/src/cobalt/dom/MediaSource.idl
@@ -28,4 +28,6 @@
   readonly attribute DOMString readyState;
 
   [RaisesException] void endOfStream(optional DOMString error);
+
+  [CallWith=EnvironmentSettings] static boolean isTypeSupported(DOMString type);
 };
diff --git a/src/cobalt/dom/dom_settings.cc b/src/cobalt/dom/dom_settings.cc
index 6588e93..1eca2a2 100644
--- a/src/cobalt/dom/dom_settings.cc
+++ b/src/cobalt/dom/dom_settings.cc
@@ -26,6 +26,7 @@
                          network::NetworkModule* network_module,
                          const scoped_refptr<Window>& window,
                          MediaSource::Registry* media_source_registry,
+                         media::CanPlayTypeHandler* can_play_type_handler,
                          script::JavaScriptEngine* engine,
                          script::GlobalEnvironment* global_environment,
                          const Options& options)
@@ -36,6 +37,7 @@
       array_buffer_allocator_(options.array_buffer_allocator),
       array_buffer_cache_(options.array_buffer_cache),
       media_source_registry_(media_source_registry),
+      can_play_type_handler_(can_play_type_handler),
       javascript_engine_(engine),
       global_environment_(global_environment) {
   if (array_buffer_allocator_) {
diff --git a/src/cobalt/dom/dom_settings.h b/src/cobalt/dom/dom_settings.h
index 8366e67..5bd39e1 100644
--- a/src/cobalt/dom/dom_settings.h
+++ b/src/cobalt/dom/dom_settings.h
@@ -22,6 +22,7 @@
 #include "cobalt/dom/array_buffer.h"
 #include "cobalt/dom/media_source.h"
 #include "cobalt/dom/window.h"
+#include "cobalt/media/can_play_type_handler.h"
 #include "cobalt/script/environment_settings.h"
 
 namespace cobalt {
@@ -62,6 +63,7 @@
               network::NetworkModule* network_module,
               const scoped_refptr<Window>& window,
               MediaSource::Registry* media_source_registry,
+              media::CanPlayTypeHandler* can_play_type_handler,
               script::JavaScriptEngine* engine,
               script::GlobalEnvironment* global_environment_proxy,
               const Options& options = Options());
@@ -95,6 +97,9 @@
   MediaSource::Registry* media_source_registry() const {
     return media_source_registry_;
   }
+  media::CanPlayTypeHandler* can_play_type_handler() const {
+    return can_play_type_handler_;
+  }
 
   // An absolute URL used to resolve relative URLs.
   virtual GURL base_url() const;
@@ -107,6 +112,7 @@
   ArrayBuffer::Allocator* array_buffer_allocator_;
   ArrayBuffer::Cache* array_buffer_cache_;
   MediaSource::Registry* media_source_registry_;
+  media::CanPlayTypeHandler* can_play_type_handler_;
   script::JavaScriptEngine* javascript_engine_;
   script::GlobalEnvironment* global_environment_;
 
diff --git a/src/cobalt/dom/media_source.cc b/src/cobalt/dom/media_source.cc
index fa881bf..f20ffb0 100644
--- a/src/cobalt/dom/media_source.cc
+++ b/src/cobalt/dom/media_source.cc
@@ -26,9 +26,12 @@
 #include "base/logging.h"
 #include "base/string_split.h"
 #include "base/string_util.h"
+#include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/dom/dom_exception.h"
+#include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/event.h"
+#include "cobalt/media/can_play_type_handler.h"
 
 namespace cobalt {
 namespace dom {
@@ -252,6 +255,19 @@
   player_->SourceEndOfStream(eos_status);
 }
 
+// static
+bool MediaSource::IsTypeSupported(script::EnvironmentSettings* settings,
+                                  const std::string& type) {
+  DOMSettings* dom_settings =
+      base::polymorphic_downcast<DOMSettings*>(settings);
+  DCHECK(dom_settings);
+  media::CanPlayTypeHandler* handler = dom_settings->can_play_type_handler();
+  DCHECK(handler);
+  std::string result = handler->CanPlayType(type, "");
+  DLOG(INFO) << "MediaSource::IsTypeSupported(" << type << ") -> " << result;
+  return result == "probably";
+}
+
 void MediaSource::SetPlayer(WebMediaPlayer* player) {
   // It is possible to reuse a MediaSource object but unlikely. DCHECK it until
   // it is used in this way.
diff --git a/src/cobalt/dom/media_source.h b/src/cobalt/dom/media_source.h
index ec769e1..a30e5c3 100644
--- a/src/cobalt/dom/media_source.h
+++ b/src/cobalt/dom/media_source.h
@@ -25,6 +25,7 @@
 #include "cobalt/dom/event_target.h"
 #include "cobalt/dom/source_buffer.h"
 #include "cobalt/dom/source_buffer_list.h"
+#include "cobalt/script/environment_settings.h"
 #include "cobalt/script/exception_state.h"
 #include "media/player/web_media_player.h"
 
@@ -87,6 +88,9 @@
   void EndOfStream(const std::string& error,
                    script::ExceptionState* exception_state);
 
+  static bool IsTypeSupported(script::EnvironmentSettings* settings,
+                              const std::string& type);
+
   // Custom, not in any spec.
   //
   // The player is set when the media source is attached to a media element.
diff --git a/src/cobalt/h5vcc/H5vccSystem.idl b/src/cobalt/h5vcc/H5vccSystem.idl
index abf2017..25bbe3b 100644
--- a/src/cobalt/h5vcc/H5vccSystem.idl
+++ b/src/cobalt/h5vcc/H5vccSystem.idl
@@ -20,7 +20,6 @@
   readonly attribute DOMString platform;
   readonly attribute DOMString region;
   readonly attribute DOMString version;
-  attribute boolean recordStats;
   boolean triggerHelp();
   DOMString getVideoContainerSizeOverride();
 };
diff --git a/src/cobalt/h5vcc/h5vcc.cc b/src/cobalt/h5vcc/h5vcc.cc
index 16cb6c9..9792fcb 100644
--- a/src/cobalt/h5vcc/h5vcc.cc
+++ b/src/cobalt/h5vcc/h5vcc.cc
@@ -27,7 +27,7 @@
       new H5vccRuntime(settings.event_dispatcher, settings.initial_deep_link);
   settings_ = new H5vccSettings(settings.media_module);
   storage_ = new H5vccStorage(settings.network_module);
-  system_ = new H5vccSystem(settings.on_set_record_stats);
+  system_ = new H5vccSystem();
 }
 
 }  // namespace h5vcc
diff --git a/src/cobalt/h5vcc/h5vcc.h b/src/cobalt/h5vcc/h5vcc.h
index 8f46d2f..369cad3 100644
--- a/src/cobalt/h5vcc/h5vcc.h
+++ b/src/cobalt/h5vcc/h5vcc.h
@@ -45,7 +45,6 @@
     account::AccountManager* account_manager;
     base::EventDispatcher* event_dispatcher;
     std::string initial_deep_link;
-    base::Callback<void(bool)> on_set_record_stats;
   };
 
   explicit H5vcc(const Settings& config);
diff --git a/src/cobalt/h5vcc/h5vcc_system.cc b/src/cobalt/h5vcc/h5vcc_system.cc
index 5b668cd..3bf8cf3 100644
--- a/src/cobalt/h5vcc/h5vcc_system.cc
+++ b/src/cobalt/h5vcc/h5vcc_system.cc
@@ -24,9 +24,7 @@
 namespace cobalt {
 namespace h5vcc {
 
-H5vccSystem::H5vccSystem(const base::Callback<void(bool)>& on_set_record_stats)
-    : on_set_record_stats_(on_set_record_stats) {
-}
+H5vccSystem::H5vccSystem() {}
 
 bool H5vccSystem::are_keys_reversed() const {
   return deprecated::PlatformDelegate::Get()->AreKeysReversed();
diff --git a/src/cobalt/h5vcc/h5vcc_system.h b/src/cobalt/h5vcc/h5vcc_system.h
index 3ce8764..be0db79 100644
--- a/src/cobalt/h5vcc/h5vcc_system.h
+++ b/src/cobalt/h5vcc/h5vcc_system.h
@@ -26,21 +26,13 @@
 
 class H5vccSystem : public script::Wrappable {
  public:
-  explicit H5vccSystem(const base::Callback<void(bool)>& on_set_record_stats);
+  H5vccSystem();
 
   bool are_keys_reversed() const;
   std::string build_id() const;
   std::string platform() const;
   std::string region() const;
   std::string version() const;
-  bool record_stats() const {
-    return record_stats_;
-  }
-  void set_record_stats(bool record_stats) {
-    record_stats_ = record_stats;
-
-    on_set_record_stats_.Run(record_stats);
-  }
 
   bool TriggerHelp() const;
   std::string GetVideoContainerSizeOverride() const;
@@ -48,8 +40,6 @@
   DEFINE_WRAPPABLE_TYPE(H5vccSystem);
 
  private:
-  base::Callback<void(bool)> on_set_record_stats_;
-  bool record_stats_;
   DISALLOW_COPY_AND_ASSIGN(H5vccSystem);
 };
 
diff --git a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
index 748aefd..e7225dc 100644
--- a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
+++ b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
@@ -366,7 +366,22 @@
 
 void RenderTreeNodeVisitor::Visit(render_tree::FilterNode* filter_node) {
   if (filter_node->data().map_to_mesh_filter) {
-    // TODO: Implement support for MapToMeshFilter.
+    // TODO: Implement support for MapToMeshFilter instead of punching out
+    //       the area that it occupies.
+    SkPaint paint;
+    paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+    paint.setARGB(0, 0, 0, 0);
+
+    math::RectF bounds = filter_node->GetBounds();
+    SkRect sk_rect = SkRect::MakeXYWH(bounds.x(), bounds.y(), bounds.width(),
+                                      bounds.height());
+
+    draw_state_.render_target->drawRect(sk_rect, paint);
+
+#if ENABLE_FLUSH_AFTER_EVERY_NODE
+    draw_state_.render_target->flush();
+#endif
+
     return;
   }
 
diff --git a/src/cobalt/script/mozjs/mozjs_callback_function.h b/src/cobalt/script/mozjs/mozjs_callback_function.h
index cb3abc6..71d8914 100644
--- a/src/cobalt/script/mozjs/mozjs_callback_function.h
+++ b/src/cobalt/script/mozjs/mozjs_callback_function.h
@@ -57,6 +57,7 @@
 
   CallbackResult<R> Run()
       const OVERRIDE {
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
     JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
@@ -110,6 +111,7 @@
   CallbackResult<R> Run(
       typename base::internal::CallbackParamTraits<A1>::ForwardType a1)
       const OVERRIDE {
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
     JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
@@ -169,6 +171,7 @@
       typename base::internal::CallbackParamTraits<A1>::ForwardType a1,
       typename base::internal::CallbackParamTraits<A2>::ForwardType a2)
       const OVERRIDE {
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
     JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
@@ -230,6 +233,7 @@
       typename base::internal::CallbackParamTraits<A2>::ForwardType a2,
       typename base::internal::CallbackParamTraits<A3>::ForwardType a3)
       const OVERRIDE {
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
     JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
@@ -293,6 +297,7 @@
       typename base::internal::CallbackParamTraits<A3>::ForwardType a3,
       typename base::internal::CallbackParamTraits<A4>::ForwardType a4)
       const OVERRIDE {
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
     JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
@@ -359,6 +364,7 @@
       typename base::internal::CallbackParamTraits<A4>::ForwardType a4,
       typename base::internal::CallbackParamTraits<A5>::ForwardType a5)
       const OVERRIDE {
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
     JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
@@ -427,6 +433,7 @@
       typename base::internal::CallbackParamTraits<A5>::ForwardType a5,
       typename base::internal::CallbackParamTraits<A6>::ForwardType a6)
       const OVERRIDE {
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
     JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
@@ -497,6 +504,7 @@
       typename base::internal::CallbackParamTraits<A6>::ForwardType a6,
       typename base::internal::CallbackParamTraits<A7>::ForwardType a7)
       const OVERRIDE {
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
     JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
diff --git a/src/cobalt/script/mozjs/mozjs_callback_function.h.pump b/src/cobalt/script/mozjs/mozjs_callback_function.h.pump
index 14067e9..861dcd3 100644
--- a/src/cobalt/script/mozjs/mozjs_callback_function.h.pump
+++ b/src/cobalt/script/mozjs/mozjs_callback_function.h.pump
@@ -76,6 +76,7 @@
 
       typename base::internal::CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]])
       const OVERRIDE {
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
     JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
diff --git a/src/cobalt/webdriver_benchmarks/tests/guide.py b/src/cobalt/webdriver_benchmarks/tests/guide.py
index 442eef1..2ebf58e 100755
--- a/src/cobalt/webdriver_benchmarks/tests/guide.py
+++ b/src/cobalt/webdriver_benchmarks/tests/guide.py
@@ -26,12 +26,11 @@
 class GuideTest(tv_testcase.TvTestCase):
 
   def test_simple(self):
+    layout_times_us = []
+
     self.load_tv()
     self.assert_displayed(tv.FOCUSED_SHELF)
 
-    print(str(self.get_webdriver().execute_script(
-        "h5vcc.system.recordStats = true")))
-
     for _ in xrange(REPEAT_COUNT):
       self.send_keys(tv.FOCUSED_SHELF, keys.Keys.ARROW_LEFT)
       self.assert_displayed(tv.FOCUSED_GUIDE)
@@ -40,8 +39,9 @@
       self.poll_until_found(tv.FOCUSED_SHELF)
       self.assert_displayed(tv.FOCUSED_SHELF_TITLE)
       self.wait_for_layout_complete()
+      layout_times_us.append(self.get_keyup_layout_duration_us())
 
-    self.record_results("GuideTest.test_simple")
+    self.record_results("GuideTest.test_simple", layout_times_us)
 
 
 if __name__ == "__main__":
diff --git a/src/cobalt/webdriver_benchmarks/tests/shelf.py b/src/cobalt/webdriver_benchmarks/tests/shelf.py
index b47e379..7372e55 100755
--- a/src/cobalt/webdriver_benchmarks/tests/shelf.py
+++ b/src/cobalt/webdriver_benchmarks/tests/shelf.py
@@ -27,25 +27,25 @@
 class ShelfTest(tv_testcase.TvTestCase):
 
   def test_simple(self):
+    layout_times_us = []
     self.load_tv()
     self.assert_displayed(tv.FOCUSED_SHELF)
 
-    print(str(self.get_webdriver().execute_script(
-        "h5vcc.system.recordStats = true")))
-
     for _ in xrange(DEFAULT_SHELVES_COUNT):
       self.send_keys(tv.FOCUSED_SHELF, keys.Keys.ARROW_DOWN)
       self.poll_until_found(tv.FOCUSED_SHELF)
       self.assert_displayed(tv.FOCUSED_SHELF_TITLE)
       self.wait_for_layout_complete()
+      layout_times_us.append(self.get_keyup_layout_duration_us())
 
     for _ in xrange(SHELF_ITEMS_COUNT):
       self.send_keys(tv.FOCUSED_TILE, keys.Keys.ARROW_RIGHT)
       self.poll_until_found(tv.FOCUSED_TILE)
       self.assert_displayed(tv.FOCUSED_SHELF_TITLE)
       self.wait_for_layout_complete()
+      layout_times_us.append(self.get_keyup_layout_duration_us())
 
-    self.record_results("ShelfTest.test_simple")
+    self.record_results("ShelfTest.test_simple", layout_times_us)
 
 
 if __name__ == "__main__":
diff --git a/src/cobalt/webdriver_benchmarks/tv_testcase.py b/src/cobalt/webdriver_benchmarks/tv_testcase.py
index 23649ec..3189523 100644
--- a/src/cobalt/webdriver_benchmarks/tv_testcase.py
+++ b/src/cobalt/webdriver_benchmarks/tv_testcase.py
@@ -4,6 +4,7 @@
 from __future__ import division
 from __future__ import print_function
 
+import json
 import os
 import sys
 import time
@@ -25,7 +26,7 @@
     partial_layout_benchmark.ImportSeleniumModule(
         submodule="common.exceptions").ElementNotVisibleException)
 
-BASE_URL = "https://www.youtube.com/tv"
+BASE_URL = "https://www.youtube.com/tv?env_forcedOffAllExperiments=true"
 PAGE_LOAD_WAIT_SECONDS = 30
 LAYOUT_TIMEOUT_SECONDS = 5
 
@@ -163,18 +164,21 @@
 
       time.sleep(0.1)
 
-  def record_results(self, name):
+  def get_keyup_layout_duration_us(self):
+    return int(self.get_webdriver().execute_script(
+        "return h5vcc.cVal.getValue('Event.Duration.MainWebModule.KeyUp')"))
+
+  def record_results(self, name, results):
     """Records results of benchmark.
 
     The duration of KeyUp events will be recorded.
 
     Args:
       name: name of test case
+      results: Test results. Must be JSON encodable
     """
     print("tv_testcase RESULT: " + name + " "
-          + str(self.get_webdriver().execute_script(
-              "return h5vcc.cVal.getValue("
-              "'Event.Durations.MainWebModule.KeyUp')")))
+          + json.JSONEncoder().encode(results))
 
 
 def main():
diff --git a/src/cobalt/xhr/xml_http_request_test.cc b/src/cobalt/xhr/xml_http_request_test.cc
index ac3b5c1..e39fe5f 100644
--- a/src/cobalt/xhr/xml_http_request_test.cc
+++ b/src/cobalt/xhr/xml_http_request_test.cc
@@ -92,7 +92,8 @@
 
 class FakeSettings : public dom::DOMSettings {
  public:
-  FakeSettings() : dom::DOMSettings(0, NULL, NULL, NULL, NULL, NULL, NULL) {}
+  FakeSettings()
+      : dom::DOMSettings(0, NULL, NULL, NULL, NULL, NULL, NULL, NULL) {}
   GURL base_url() const OVERRIDE { return GURL("http://example.com"); }
 };
 
diff --git a/src/nb/nb.gyp b/src/nb/nb.gyp
index b3b9a0d..3780eb9 100644
--- a/src/nb/nb.gyp
+++ b/src/nb/nb.gyp
@@ -63,6 +63,7 @@
         'fixed_no_free_allocator_test.cc',
         'reuse_allocator_test.cc',
         'run_all_unittests.cc',
+        'thread_local_object_test.cc',
       ],
       'dependencies': [
         'nb',
diff --git a/src/nb/thread_local_object.h b/src/nb/thread_local_object.h
new file mode 100644
index 0000000..4b6a7ab
--- /dev/null
+++ b/src/nb/thread_local_object.h
@@ -0,0 +1,214 @@
+/*
+ * 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.
+ */
+
+#ifndef BASE_THREADING_THREAD_LOCAL_OBJECT_H_
+#define BASE_THREADING_THREAD_LOCAL_OBJECT_H_
+
+#include <set>
+
+#include "starboard/configuration.h"
+#include "starboard/log.h"
+#include "starboard/mutex.h"
+#include "starboard/thread.h"
+
+namespace base {
+
+// Like base::ThreadLocalPointer<T> but destroys objects. This is important
+// for using ThreadLocalObjects that aren't tied to a singleton, or for
+// access by threads which will call join().
+//
+// FEATURE COMPARISON TABLE:
+//                         |  Thread Join       | Container Destroyed
+//   ------------------------------------------------------------------
+//   ThreadLocalPointer<T> | LEAKS              | LEAKS
+//   ThreadLocalObject<T>  | Object Destroyed   | Objects Destroyed
+//
+// EXAMPLE:
+//  ThreadLocalObject<std::map<std::string, int> > map_tls;
+//  Map* map = map_tls->GetOrCreate();
+//  (*map)["my string"] = 15;
+//  Thread t = new Thread(&map_tls);
+//  t->start();
+//  // t creates it's own thread local map.
+//  t->join();  // Make sure that thread joins before map_tls is destroyed!
+//
+// OBJECT DESTRUCTION:
+//   There are two ways for an object to be destroyed by the ThreadLocalObject.
+//   The first way is via a thread join. In this case only the object
+//   associated with the thread is deleted.
+//   The second way an object is destroyed is by the ThreadLocalObject
+//   container to be destroyed, in this case ALL thread local objects are
+//   destroyed.
+//
+// PERFORMANCE:
+//   ThreadLocalObject is fast for the Get() function if the object has
+//   has already been created, requiring one extra pointer dereference
+//   over ThreadLocalPointer<T>.
+template <typename Type>
+class ThreadLocalObject {
+ public:
+  ThreadLocalObject() : slot_() {
+    slot_ = SbThreadCreateLocalKey(DeleteEntry);
+    SB_DCHECK(kSbThreadLocalKeyInvalid != slot_);
+    constructing_thread_id_ = SbThreadGetId();
+  }
+
+  // Enables destruction by any other thread. Otherwise, the class instance
+  // will warn when a different thread than the constructing destroys this.
+  void EnableDestructionByAnyThread() {
+    constructing_thread_id_ = kSbThreadInvalidId;
+  }
+
+  // Thread Local Objects are destroyed after this call.
+  ~ThreadLocalObject() {
+    CheckCurrentThreadAllowedToDestruct();
+    SB_DCHECK(entry_set_.size() < 2)
+        << "Logic error: Some threads may still be accessing the objects that "
+        << "are about to be destroyed. Only one object is expected and that "
+        << "should be for the main thread. The caller should ensure that "
+        << "other threads that access this object are externally "
+        << "synchronized.";
+    // No locking is done because the entries should not be accessed by
+    // different threads while this object is shutting down. If access is
+    // occuring then the caller has a race condition, external to this class.
+    typedef typename Set::iterator Iter;
+    for (Iter it = entry_set_.begin(); it != entry_set_.end(); ++it) {
+      Entry* entry = *it;
+      SB_DCHECK(entry->owner_ == this);
+      delete entry->ptr_;
+      delete entry;
+    }
+
+    // Cleanup the thread local key.
+    SbThreadDestroyLocalKey(slot_);
+  }
+
+  // Warns if there is a misuse of this object.
+  void CheckCurrentThreadAllowedToDestruct() const {
+    if (kSbThreadInvalidId == constructing_thread_id_) {
+      return;   // EnableDestructionByAnyThread() called.
+    }
+    const SbThreadId curr_thread_id = SbThreadGetId();
+    if (curr_thread_id == constructing_thread_id_) {
+      return;   // Same thread that constructed this.
+    }
+
+    if (SB_DLOG_IS_ON(FATAL)) {
+      SB_DCHECK(false)
+          << "ThreadLocalObject<T> was created in thread "
+          << constructing_thread_id_ << "\nbut was destroyed by "
+          << curr_thread_id << ". If this is intentional then call "
+          << "EnableDestructionByAnyThread() to silence this "
+          << "warning.";
+    }
+  }
+
+  // Either returns the created pointer for the current thread, or otherwise
+  // constructs the object using the default constructor and returns it.
+  Type* GetOrCreate() {
+    Type* object = GetIfExists();
+    if (!object) {  // create object.
+      object = new Type();
+      Entry* entry = new Entry(this, object);
+      // Insert into the set of tls entries.
+      // Performance: Its assumed that creation of objects is much less
+      // frequent than getting an object.
+      {
+        starboard::ScopedLock lock(entry_set_mutex_);
+        entry_set_.insert(entry);
+      }
+      SbThreadSetLocalValue(slot_, entry);
+    }
+    return object;
+  }
+
+  // Returns the pointer if it exists in the current thread, otherwise NULL.
+  Type* GetIfExists() const {
+    Entry* entry = GetEntryIfExists();
+    if (!entry) { return NULL; }
+    return entry->ptr_;
+  }
+
+  // Releases ownership of the pointer FROM THE CURRENT THREAD.
+  // The caller has responsibility to make sure that the pointer is destroyed.
+  Type* Release() {
+    if (Entry* entry = GetEntryIfExists()) {
+      // The entry will no longer run it's destructor on thread join.
+      SbThreadSetLocalValue(slot_, NULL);  // NULL out pointer for TLS.
+      Type* object = entry->ptr_;
+      RemoveEntry(entry);
+      return object;
+    } else {
+      return NULL;
+    }
+  }
+
+ private:
+  struct Entry {
+    Entry(ThreadLocalObject* own, Type* ptr) : owner_(own), ptr_(ptr) {
+    }
+    ~Entry() {
+      ptr_ = NULL;
+      owner_ = NULL;
+    }
+    ThreadLocalObject* owner_;
+    Type* ptr_;
+  };
+
+  // Deletes the TLSEntry.
+  static void DeleteEntry(void* ptr) {
+    if (!ptr) {
+      SB_NOTREACHED();
+      return;
+    }
+    Entry* entry = reinterpret_cast<Entry*>(ptr);
+    ThreadLocalObject* tls = entry->owner_;
+    Type* object = entry->ptr_;
+    tls->RemoveEntry(entry);
+    delete object;
+  }
+
+  void RemoveEntry(Entry* entry) {
+    {
+      starboard::ScopedLock lock(entry_set_mutex_);
+      entry_set_.erase(entry);
+    }
+    delete entry;
+  }
+
+  Entry* GetEntryIfExists() const {
+    void* ptr = SbThreadGetLocalValue(slot_);
+    Entry* entry = static_cast<Entry*>(ptr);
+    return entry;
+  }
+
+  typedef std::set<Entry*> Set;
+  // Allows GetIfExists() to be const.
+  mutable SbThreadLocalKey slot_;
+  // entry_set_ contains all the outstanding entries for the thread local
+  // objects that have been created.
+  Set entry_set_;
+  mutable starboard::Mutex entry_set_mutex_;
+  // Used to warn when there is a mismatch between thread that constructed and
+  // thread that destroyed this object.
+  SbThreadId constructing_thread_id_;
+
+  SB_DISALLOW_COPY_AND_ASSIGN(ThreadLocalObject<Type>);
+};
+
+}  // namespace base
+
+#endif  // BASE_THREADING_THREAD_LOCAL_H_
diff --git a/src/nb/thread_local_object_test.cc b/src/nb/thread_local_object_test.cc
new file mode 100644
index 0000000..000c6f4
--- /dev/null
+++ b/src/nb/thread_local_object_test.cc
@@ -0,0 +1,346 @@
+// 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.
+
+#include <map>
+#include <string>
+
+#include "nb/thread_local_object.h"
+#include "nb/scoped_ptr.h"
+#include "starboard/mutex.h"
+#include "starboard/thread.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+// Similar to C++11 std::atomic<T>.
+// Atomic<T> may be instantiated with any TriviallyCopyable type T.
+// Atomic<T> is neither copyable nor movable.
+// TODO: Lift this class out into the library.
+template <typename T>
+class Atomic {
+ public:
+  // C++11 forbids a copy constructor for std::atomic<T>, it also forbids
+  // a move operation.
+  Atomic() : value_() {}
+  explicit Atomic(T v) : value_(v) {}
+
+  // Checks whether the atomic operations on all objects of this type
+  // are lock-free.
+  // Returns true if the atomic operations on the objects of this type
+  // are lock-free, false otherwise.
+  //
+  // All atomic types may be implemented using mutexes or other locking
+  // operations, rather than using the lock-free atomic CPU instructions.
+  // Atomic types are also allowed to be sometimes lock-free, e.g. if only
+  // aligned memory accesses are naturally atomic on a given architecture,
+  // misaligned objects of the same type have to use locks.
+  bool is_lock_free() const { return false; }
+  bool is_lock_free() const volatile { return false; }
+
+  // Atomically replaces the value of the atomic object
+  // and returns the value held previously.
+  T Swap(T new_val) {
+    int old_value = -1;
+    {
+      starboard::ScopedLock lock(mutex_);
+      old_value = value_;
+      value_ = new_val;
+    }
+    return old_value;
+  }
+
+  // Atomically obtains the value of the atomic object.
+  T Get() const {
+    starboard::ScopedLock lock(mutex_);
+    return value_;
+  }
+
+  // Returns the new updated value after the operation has been applied.
+  T Add(T val) {
+    starboard::ScopedLock lock(mutex_);
+    value_ += val;
+    return value_;
+  }
+
+  // TrySwap(...) sets the new value if and only if "expected_old_value"
+  // matches the actual value during the atomic assignment operation. If this
+  // succeeds then true is returned. If there is a mismatch then the value is
+  // left unchanged and false is returned.
+  // Inputs:
+  //  new_value: Attempt to set the value to this new value.
+  //  expected_old_value: A test condition for success. If the actual value
+  //    matches the expected_old_value then the swap will succeed.
+  //  optional_actual_value: If non-null, then the actual value at the time
+  //    of the attempted operation is set to this value.
+  bool TrySwap(T new_value, T expected_old_value,
+               T* optional_actual_value) {
+    starboard::ScopedLock lock(mutex_);
+    if (optional_actual_value) {
+      *optional_actual_value = value_;
+    }
+    if (expected_old_value == value_) {
+      value_ = new_value;
+      return true;
+    }
+    return false;
+  }
+
+ private:
+  T value_;
+  starboard::Mutex mutex_;
+};
+
+// Simple atomic int class. This could be optimized for speed using
+// compiler intrinsics for concurrent integer modification.
+class AtomicInt : public Atomic<int> {
+ public:
+  AtomicInt() : Atomic<int>(0) {}
+  explicit AtomicInt(int initial_val) : Atomic<int>(initial_val) {}
+  void Increment() { Add(1); }
+  void Decrement() { Add(-1); }
+};
+
+// Simple atomic bool class. This could be optimized for speed using
+// compiler intrinsics for concurrent integer modification.
+class AtomicBool : public Atomic<bool>  {
+ public:
+  AtomicBool() : Atomic<bool>(false) {}
+  explicit AtomicBool(bool initial_val) : Atomic<bool>(initial_val) {}
+};
+
+// AbstractTestThread that is a bare bones class wrapper around Starboard
+// thread. Subclasses must override Run().
+// TODO: Move this to nplb/thread_helpers.h
+class AbstractTestThread {
+ public:
+  explicit AbstractTestThread() : thread_(kSbThreadInvalid) {}
+  virtual ~AbstractTestThread() {}
+
+  // Subclasses should override the Run method.
+  virtual void Run() = 0;
+
+  // Calls SbThreadCreate() with default parameters.
+  void Start() {
+    SbThreadEntryPoint entry_point = ThreadEntryPoint;
+
+    thread_ = SbThreadCreate(
+        0,                     // default stack_size.
+        kSbThreadNoPriority,   // default priority.
+        kSbThreadNoAffinity,   // default affinity.
+        true,                  // joinable.
+        "AbstractTestThread",
+        entry_point,
+        this);
+
+    if (kSbThreadInvalid == thread_) {
+      ADD_FAILURE_AT(__FILE__, __LINE__) << "Invalid thread.";
+    }
+    return;
+  }
+
+  void Join() {
+    if (!SbThreadJoin(thread_, NULL)) {
+      ADD_FAILURE_AT(__FILE__, __LINE__) << "Could not join thread.";
+    }
+  }
+
+ private:
+  static void* ThreadEntryPoint(void* ptr) {
+    AbstractTestThread* this_ptr = static_cast<AbstractTestThread*>(ptr);
+    this_ptr->Run();
+    return NULL;
+  }
+
+  SbThread thread_;
+};
+
+// Simple class that counts the number of instances alive.
+struct CountsInstances {
+  CountsInstances() { s_instances_.Increment(); }
+  ~CountsInstances() { s_instances_.Decrement(); }
+  static AtomicInt s_instances_;
+  static int NumInstances() { return s_instances_.Get(); }
+  static void ResetNumInstances() { s_instances_.Swap(0); }
+};
+AtomicInt CountsInstances::s_instances_(0);
+
+// A simple thread that just creates the an object from the supplied
+// ThreadLocalObject<T> and then exits.
+template <typename TYPE>
+class CreateThreadLocalObjectThenExit : public AbstractTestThread {
+ public:
+  explicit CreateThreadLocalObjectThenExit(
+      ThreadLocalObject<TYPE>* tlo) : tlo_(tlo) {}
+
+  virtual void Run() {
+    // volatile as a defensive measure to prevent compiler from optimizing this
+    // statement out.
+    volatile TYPE* val = tlo_->GetOrCreate();
+  }
+  ThreadLocalObject<TYPE>* tlo_;
+};
+
+// A simple thread that just deletes the object supplied on a thread and then
+// exists.
+template <typename TYPE>
+class DestroyTypeOnThread : public AbstractTestThread {
+ public:
+  explicit DestroyTypeOnThread(TYPE* ptr)
+      : ptr_(ptr) {}
+  virtual void Run() {
+    ptr_.reset(NULL);  // Destroys the object.
+  }
+ private:
+  nb::scoped_ptr<TYPE> ptr_;
+};
+
+// Tests the expectation that a ThreadLocalObject can be simply used by
+// the main thread.
+TEST(ThreadLocalObject, MainThread) {
+  ThreadLocalObject<bool> tlo_bool;
+  EXPECT_TRUE(NULL == tlo_bool.GetIfExists());
+  bool* the_bool = tlo_bool.GetOrCreate();
+  EXPECT_TRUE(the_bool != NULL);
+  EXPECT_FALSE(*the_bool);
+  *the_bool = true;
+  EXPECT_TRUE(*(tlo_bool.GetIfExists()));
+}
+
+// Tests the expectation that a ThreadLocalObject can be used on
+// complex objects type (i.e. non pod types).
+TEST(ThreadLocalObject, MainThreadComplexObject) {
+  typedef std::map<std::string, int> Map;
+  ThreadLocalObject<Map> map_tlo;
+  EXPECT_FALSE(map_tlo.GetIfExists());
+  ASSERT_TRUE(map_tlo.GetOrCreate());
+  Map* map = map_tlo.GetIfExists();
+  const Map* const_map = map_tlo.GetIfExists();
+  ASSERT_TRUE(map);
+  ASSERT_TRUE(const_map);
+  // If the object is properly constructed then this find operation
+  // should succeed.
+  (*map)["my string"] = 15;
+  ASSERT_EQ(15, (*map)["my string"]);
+}
+
+// Tests that when a ThreadLocalObject is destroyed on the main thread that
+// the pointers it contained are also destroyed.
+TEST(ThreadLocalObject, DestroysObjectOnTLODestruction) {
+  CountsInstances::ResetNumInstances();
+  typedef ThreadLocalObject<CountsInstances> TLO;
+
+  // Create the TLO object and then immediately destroy it.
+  nb::scoped_ptr<TLO> tlo_ptr(new TLO);
+  tlo_ptr->GetOrCreate(); // Instantiate the internal object.
+  EXPECT_EQ(1, CountsInstances::NumInstances());
+  tlo_ptr.reset(NULL);    // Should destroy all outstanding allocs.
+  // Now the TLO is destroyed and therefore the destructor should run on the
+  // internal object.
+  EXPECT_EQ(0, CountsInstances::NumInstances());
+
+  CountsInstances::ResetNumInstances();
+}
+
+// Tests the expectation that the object can be released and that the pointer
+// won't be deleted when the ThreadLocalObject that created it is destroyed.
+TEST(ThreadLocalObject, ReleasesObject) {
+  CountsInstances::ResetNumInstances();
+  typedef ThreadLocalObject<CountsInstances> TLO;
+
+  nb::scoped_ptr<TLO> tlo_ptr(new TLO);
+  // Instantiate the internal object.
+  tlo_ptr->GetOrCreate();
+  // Now release the pointer into the container.
+  nb::scoped_ptr<CountsInstances> last_ref(tlo_ptr->Release());
+  // Destroying the TLO should not trigger the destruction of the object,
+  // because it was released.
+  tlo_ptr.reset(NULL);
+  // 1 instance left, which is held in last_ref.
+  EXPECT_EQ(1, CountsInstances::NumInstances());
+  last_ref.reset(NULL);   // Now the object should be destroyed and the
+                          // instance count drops to 0.
+  EXPECT_EQ(0, CountsInstances::NumInstances());
+  CountsInstances::ResetNumInstances();
+}
+
+// Tests the expectation that a thread that creates an object from
+// the ThreadLocalObject store will automatically be destroyed by the
+// thread joining.
+TEST(ThreadLocalObject, ThreadJoinDestroysObject) {
+  CountsInstances::ResetNumInstances();
+  typedef ThreadLocalObject<CountsInstances> TLO;
+
+  nb::scoped_ptr<TLO> tlo(new TLO);
+  {
+    AbstractTestThread* thread =
+        new CreateThreadLocalObjectThenExit<CountsInstances>(tlo.get());
+    thread->Start();
+    thread->Join();
+    // Once the thread joins, the object should be deleted and the instance
+    // counter falls to 0.
+    EXPECT_EQ(0, CountsInstances::NumInstances());
+    delete thread;
+  }
+
+  tlo.reset(NULL);  // Now TLO destructor runs.
+  EXPECT_EQ(0, CountsInstances::NumInstances());
+  CountsInstances::ResetNumInstances();
+}
+
+// Tests the expectation that objects created on the main thread are not
+// leaked.
+TEST(ThreadLocalObject, NoLeaksOnMainThread) {
+  CountsInstances::ResetNumInstances();
+
+  ThreadLocalObject<CountsInstances>* tlo =
+      new ThreadLocalObject<CountsInstances>;
+  tlo->EnableDestructionByAnyThread();
+
+  // Creates the object on the main thread. This is important because the
+  // main thread will never join and therefore at-exit functions won't get
+  // run.
+  CountsInstances* main_thread_object = tlo->GetOrCreate();
+
+  EXPECT_EQ(1, CountsInstances::NumInstances());
+
+  // Thread will simply create the thread local object (CountsInstances)
+  // and then return.
+  nb::scoped_ptr<AbstractTestThread> thread_ptr(
+        new CreateThreadLocalObjectThenExit<CountsInstances>(tlo));
+  thread_ptr->Start();  // Object is now created.
+  thread_ptr->Join();   // ...then destroyed.
+  thread_ptr.reset(NULL);
+
+  // Only main_thread_object should be alive now, therefore the count is 1.
+  EXPECT_EQ(1, CountsInstances::NumInstances());
+
+  // We COULD destroy the TLO on the main thread, but to be even fancier lets
+  // create a thread that will destroy the object on a back ground thread.
+  // The end result is that the TLO entry should be cleared out.
+  thread_ptr.reset(
+      new DestroyTypeOnThread<ThreadLocalObject<CountsInstances> >(tlo));
+  thread_ptr->Start();
+  thread_ptr->Join();
+  thread_ptr.reset(NULL);
+
+  // Now we expect that number of instances to be 0.
+  EXPECT_EQ(0, CountsInstances::NumInstances());
+  CountsInstances::ResetNumInstances();
+}
+
+}  // anonymous namespace
+}  // namespace base
+
diff --git a/src/starboard/file.h b/src/starboard/file.h
index bda6f3a..3f15daf 100644
--- a/src/starboard/file.h
+++ b/src/starboard/file.h
@@ -117,6 +117,9 @@
 // can happen with kSbFileCreateAlways), and false otherwise.  |out_error| can
 // be NULL. If creation failed, it will return kSbFileInvalid. The read/write
 // position is at the beginning of the file.
+//
+// It only guarantees the correct behavior when |path| points to a file. If
+// |path| points to directory, the behavior is undefined.
 SB_EXPORT SbFile SbFileOpen(const char* path,
                             int flags,
                             bool* out_created,
diff --git a/src/starboard/linux/shared/gyp_configuration.gypi b/src/starboard/linux/shared/gyp_configuration.gypi
index e4ca809..af152fe 100644
--- a/src/starboard/linux/shared/gyp_configuration.gypi
+++ b/src/starboard/linux/shared/gyp_configuration.gypi
@@ -21,7 +21,6 @@
     'target_os': 'linux',
     #'starboard_path%': 'starboard/linux/x64x11',
 
-    'enable_webdriver': '1',
     'in_app_dial%': 1,
     'gl_type%': 'system_gles3',
 
diff --git a/src/starboard/nplb/file_get_info_test.cc b/src/starboard/nplb/file_get_info_test.cc
index 1722577..fa61d8b 100644
--- a/src/starboard/nplb/file_get_info_test.cc
+++ b/src/starboard/nplb/file_get_info_test.cc
@@ -40,6 +40,11 @@
     // Assuming platforms have at least 1 second precision on filesystem
     // timestamps, we need to go back two seconds to avoid rounding issues.
     SbTime time = SbTimeGetNow() - (2 * kSbTimeSecond);
+#if SB_HAS_QUIRK(FILESYSTEM_COARSE_ACCESS_TIME)
+    // On platforms with coarse access time, we assume 1 day precision and go
+    // back 2 days to avoid rounding issues.
+    SbTime coarse_time = SbTimeGetNow() - (2 * kSbTimeDay);
+#endif
 
     const int kFileSize = 12;
     starboard::nplb::ScopedRandomFile random_file(kFileSize);
@@ -56,7 +61,11 @@
       EXPECT_FALSE(info.is_directory);
       EXPECT_FALSE(info.is_symbolic_link);
       EXPECT_LE(time, info.last_modified);
+#if SB_HAS_QUIRK(FILESYSTEM_COARSE_ACCESS_TIME)
+      EXPECT_LE(coarse_time, info.last_accessed);
+#else
       EXPECT_LE(time, info.last_accessed);
+#endif
       EXPECT_LE(time, info.creation_time);
     }
 
@@ -65,30 +74,6 @@
   }
 }
 
-TEST(SbFileGetInfoTest, WorksOnADirectory) {
-  char path[SB_FILE_MAX_PATH] = {0};
-  bool result =
-      SbSystemGetPath(kSbSystemPathTempDirectory, path, SB_FILE_MAX_PATH);
-  EXPECT_TRUE(result);
-
-  SbFile file = SbFileOpen(path, kSbFileOpenOnly | kSbFileRead, NULL, NULL);
-  ASSERT_TRUE(SbFileIsValid(file));
-
-  {
-    SbFileInfo info = {0};
-    bool result = SbFileGetInfo(file, &info);
-    EXPECT_LE(0, info.size);
-    EXPECT_TRUE(info.is_directory);
-    EXPECT_FALSE(info.is_symbolic_link);
-    EXPECT_LE(0, info.last_modified);
-    EXPECT_LE(0, info.last_accessed);
-    EXPECT_LE(0, info.creation_time);
-  }
-
-  result = SbFileClose(file);
-  EXPECT_TRUE(result);
-}
-
 }  // namespace
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/file_get_path_info_test.cc b/src/starboard/nplb/file_get_path_info_test.cc
index f96e205..fc024ff 100644
--- a/src/starboard/nplb/file_get_path_info_test.cc
+++ b/src/starboard/nplb/file_get_path_info_test.cc
@@ -52,6 +52,11 @@
     // Assuming platforms have at least 1 second precision on filesystem
     // timestamps, we need to go back two seconds to avoid rounding issues.
     SbTime time = SbTimeGetNow() - (2 * kSbTimeSecond);
+#if SB_HAS_QUIRK(FILESYSTEM_COARSE_ACCESS_TIME)
+    // On platforms with coarse access time, we assume 1 day precision and go
+    // back 2 days to avoid rounding issues.
+    SbTime coarse_time = SbTimeGetNow() - (2 * kSbTimeDay);
+#endif
 
     const int kFileSize = 12;
     ScopedRandomFile random_file(kFileSize);
@@ -64,7 +69,11 @@
       EXPECT_FALSE(info.is_directory);
       EXPECT_FALSE(info.is_symbolic_link);
       EXPECT_LE(time, info.last_modified);
+#if SB_HAS_QUIRK(FILESYSTEM_COARSE_ACCESS_TIME)
+      EXPECT_LE(coarse_time, info.last_accessed);
+#else
       EXPECT_LE(time, info.last_accessed);
+#endif
       EXPECT_LE(time, info.creation_time);
     }
   }
diff --git a/src/starboard/nplb/thread_helpers.h b/src/starboard/nplb/thread_helpers.h
index 0ebbd03..aa4e32d 100644
--- a/src/starboard/nplb/thread_helpers.h
+++ b/src/starboard/nplb/thread_helpers.h
@@ -16,10 +16,14 @@
 #define STARBOARD_NPLB_THREAD_HELPERS_H_
 
 #include "starboard/condition_variable.h"
+#include "starboard/configuration.h"
 #include "starboard/mutex.h"
+#include "starboard/thread.h"
 #include "starboard/time.h"
 #include "starboard/types.h"
 
+#include "testing/gtest/include/gtest/gtest.h"
+
 namespace starboard {
 namespace nplb {
 
@@ -93,6 +97,53 @@
   SbTime delay_after_signal;
 };
 
+// AbstractTestThread that is a bare bones class wrapper around Starboard
+// thread. Subclasses must override Run().
+class AbstractTestThread {
+ public:
+  AbstractTestThread() : thread_(kSbThreadInvalid) {}
+  virtual ~AbstractTestThread() {}
+
+  // Subclasses should override the Run method.
+  virtual void Run() = 0;
+
+  // Calls SbThreadCreate() with default parameters.
+  void Start() {
+    SbThreadEntryPoint entry_point = ThreadEntryPoint;
+
+    thread_ = SbThreadCreate(
+        0,                     // default stack_size.
+        kSbThreadNoPriority,   // default priority.
+        kSbThreadNoAffinity,   // default affinity.
+        true,                  // joinable.
+        "AbstractTestThread",
+        entry_point,
+        this);
+
+    if (kSbThreadInvalid == thread_) {
+      ADD_FAILURE_AT(__FILE__, __LINE__) << "Invalid thread.";
+    }
+    return;
+  }
+
+  void Join() {
+    if (!SbThreadJoin(thread_, NULL)) {
+      ADD_FAILURE_AT(__FILE__, __LINE__) << "Could not join thread.";
+    }
+  }
+
+ private:
+  static void* ThreadEntryPoint(void* ptr) {
+    AbstractTestThread* this_ptr = static_cast<AbstractTestThread*>(ptr);
+    this_ptr->Run();
+    return NULL;
+  }
+
+  SbThread thread_;
+
+  SB_DISALLOW_COPY_AND_ASSIGN(AbstractTestThread);
+};
+
 }  // namespace nplb
 }  // namespace starboard
 
diff --git a/src/starboard/nplb/thread_join_test.cc b/src/starboard/nplb/thread_join_test.cc
index 42a2b6c..8b93e32 100644
--- a/src/starboard/nplb/thread_join_test.cc
+++ b/src/starboard/nplb/thread_join_test.cc
@@ -28,6 +28,45 @@
   EXPECT_EQ(NULL, result);
 }
 
+// Tests the expectation that SbThreadJoin() will block until
+// the thread function has been run.
+TEST(SbThreadLocalValueTest, ThreadJoinWaitsForFunctionRun) {
+  // Thread functionality needs to bind to functions. In C++11 we'd use a
+  // lambda function to tie everything together locally, but this
+  // function-scoped struct with static function emulates this functionality
+  // pretty well.
+  struct LocalStatic {
+    static void* ThreadEntryPoint(void* input) {
+      int* value = static_cast<int*>(input);
+      static const SbTime kSleepTime = 10*kSbTimeMillisecond;  // 10 ms.
+      // Wait to write the value to increase likelyhood of catching
+      // a race condition.
+      SbThreadSleep(kSleepTime);
+      (*value)++;
+      return NULL;
+    }
+  };
+
+  // Try to increase likelyhood of a race condition by running multiple times.
+  for (int i = 0; i < 10; ++i) {
+    int num_times_thread_entry_point_run = 0;
+    SbThread thread = SbThreadCreate(
+        0,                    // Signals automatic thread stack size.
+        kSbThreadNoPriority,  // Signals default priority.
+        kSbThreadNoAffinity,  // Signals default affinity.
+        true,                 // joinable thread.
+        "TestThread",
+        LocalStatic::ThreadEntryPoint,
+        &num_times_thread_entry_point_run);
+
+    ASSERT_NE(kSbThreadInvalid, thread) << "Thread creation not successful";
+    ASSERT_TRUE(SbThreadJoin(thread, NULL));
+
+    ASSERT_EQ(1, num_times_thread_entry_point_run)
+        << "Expected SbThreadJoin() to be blocked until ThreadFunction runs.";
+  }
+}
+
 }  // namespace
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/thread_local_value_test.cc b/src/starboard/nplb/thread_local_value_test.cc
index 43d32c4..4083684 100644
--- a/src/starboard/nplb/thread_local_value_test.cc
+++ b/src/starboard/nplb/thread_local_value_test.cc
@@ -92,6 +92,73 @@
   EXPECT_FALSE(my_value.destroyed);
 }
 
+// Helper function that ensures that the returned key is not recycled.
+SbThreadLocalKey CreateTLSKey_NoRecycle(SbThreadLocalDestructor dtor) {
+  SbThreadLocalKey key = SbThreadCreateLocalKey(NULL);
+  EXPECT_EQ(NULL, SbThreadGetLocalValue(key));
+  // Some Starboard implementations may recycle the original key, so this test
+  // ensures that in that case it will be reset to NULL.
+  SbThreadSetLocalValue(key, reinterpret_cast<void*>(1));
+  SbThreadDestroyLocalKey(key);
+  key = SbThreadCreateLocalKey(DestroyThreadLocalValue);
+  return key;
+}
+
+// Tests the expectation that thread at-exit destructors don't
+// run for ThreadLocal pointers that are set to NULL.
+TEST(SbThreadLocalValueTest, NoDestructorsForNullValue) {
+  static int s_num_destructor_calls = 0;  // Must be initialized to 0.
+  s_num_destructor_calls = 0;             // Allows test to be re-run.
+
+  // Thread functionality needs to bind to functions. In C++11 we'd use a
+  // lambda function to tie everything together locally, but this
+  // function-scoped struct with static members emulates this functionality
+  // pretty well.
+  struct LocalStatic {
+    // Used as a fake destructor for thread-local-storage objects in this
+    // test.
+    static void CountsDestructorCalls(void* /*value*/) {
+      s_num_destructor_calls++;
+    }
+
+    // Sets a thread local non-NULL value, and then sets it back to NULL.
+    static void* ThreadEntryPoint(void* ptr) {
+      SbThreadLocalKey key = *static_cast<SbThreadLocalKey*>(ptr);
+      EXPECT_EQ(NULL, SbThreadGetLocalValue(key));
+      // Set the value and then NULL it out. We expect that because the final
+      // value set was NULL, that the destructor attached to the thread's
+      // at-exit function will not run.
+      SbThreadSetLocalValue(key, reinterpret_cast<void*>(1));
+      SbThreadSetLocalValue(key, NULL);
+      return NULL;
+    }
+  };
+
+  // Setup the thread key and bind the fake test destructor.
+  SbThreadLocalKey key =
+      CreateTLSKey_NoRecycle(LocalStatic::CountsDestructorCalls);
+  EXPECT_EQ(NULL, SbThreadGetLocalValue(key));
+
+  // Spawn the thread.
+  SbThread thread = SbThreadCreate(
+      0,                    // Signals automatic thread stack size.
+      kSbThreadNoPriority,  // Signals default priority.
+      kSbThreadNoAffinity,  // Signals default affinity.
+      true,                 // joinable thread.
+      "TestThread",
+      LocalStatic::ThreadEntryPoint,
+      static_cast<void*>(&key));
+
+  ASSERT_NE(kSbThreadInvalid, thread) << "Thread creation not successful";
+  // 2nd param is return value from ThreadEntryPoint, which is always NULL.
+  ASSERT_TRUE(SbThreadJoin(thread, NULL));
+
+  // No destructors should have run.
+  EXPECT_EQ(0, s_num_destructor_calls);
+
+  SbThreadDestroyLocalKey(key);
+}
+
 TEST(SbThreadLocalValueTest, SunnyDay) {
   DoSunnyDayTest(true);
 }
@@ -101,15 +168,9 @@
 }
 
 TEST(SbThreadLocalValueTest, SunnyDayFreshlyCreatedValuesAreNull) {
-  SbThreadLocalKey key = SbThreadCreateLocalKey(NULL);
+  SbThreadLocalKey key = CreateTLSKey_NoRecycle(NULL);  // NULL dtor.
   EXPECT_EQ(NULL, SbThreadGetLocalValue(key));
 
-  // Some Starboard implementations may recycle the original key, so this test
-  // ensures that in that case it will be reset to NULL.
-  SbThreadSetLocalValue(key, reinterpret_cast<void*>(1));
-  SbThreadDestroyLocalKey(key);
-
-  key = SbThreadCreateLocalKey(NULL);
   EXPECT_EQ(NULL, SbThreadGetLocalValue(key));
   SbThreadDestroyLocalKey(key);
 }
diff --git a/src/starboard/raspi/1/gyp_configuration.gypi b/src/starboard/raspi/1/gyp_configuration.gypi
index 1b41d36..fbccc08 100644
--- a/src/starboard/raspi/1/gyp_configuration.gypi
+++ b/src/starboard/raspi/1/gyp_configuration.gypi
@@ -17,7 +17,6 @@
     'target_arch': 'arm',
     'target_os': 'linux',
 
-    'enable_webdriver': '1',
     'in_app_dial%': 0,
     'sysroot%': '/',
     'gl_type': 'system_gles2',
diff --git a/src/starboard/stub/configuration_public.h b/src/starboard/stub/configuration_public.h
index f243cfa..90b83b7 100644
--- a/src/starboard/stub/configuration_public.h
+++ b/src/starboard/stub/configuration_public.h
@@ -241,6 +241,11 @@
 // The string form of SB_PATH_SEP_CHAR.
 #define SB_PATH_SEP_STRING ":"
 
+// On some platforms the file system stores access times at a coarser
+// granularity than other times. When this quirk is defined, we assume the
+// access time is of 1 day precision.
+#undef SB_HAS_QUIRK_FILESYSTEM_COARSE_ACCESS_TIME
+
 // --- Memory Configuration --------------------------------------------------
 
 // The memory page size, which controls the size of chunks on memory that
diff --git a/src/starboard/stub/gyp_configuration.gypi b/src/starboard/stub/gyp_configuration.gypi
index 24cabf4..c1dc7d2 100644
--- a/src/starboard/stub/gyp_configuration.gypi
+++ b/src/starboard/stub/gyp_configuration.gypi
@@ -16,8 +16,6 @@
     'target_arch': 'x64',
     'target_os': 'linux',
 
-    'enable_webdriver': '1',
-
     # Use a stub rasterizer and graphical setup.
     'rasterizer_type': 'stub',
 
diff --git a/src/starboard/thread.h b/src/starboard/thread.h
index 4396c6c..2ad1006 100644
--- a/src/starboard/thread.h
+++ b/src/starboard/thread.h
@@ -172,8 +172,8 @@
 // Yields the currently executing thread, so another thread has a chance to run.
 SB_EXPORT void SbThreadYield();
 
-// Sleeps the currently executing thread for at least the given |duration|. A
-// negative duration does nothing.
+// Sleeps the currently executing thread for at least the given |duration| in
+// microseconds. A negative duration does nothing.
 SB_EXPORT void SbThreadSleep(SbTime duration);
 
 // Gets the handle of the currently executing thread.
@@ -198,7 +198,8 @@
 //
 // When does |destructor| get called? It can only be called in the owning
 // thread, and let's just say thread interruption isn't viable. The destructor,
-// if specified, is called on every thread's local values when the thread exits.
+// if specified, is called on every thread's local values when the thread exits,
+// if and only if the value in the key is non-NULL.
 SB_EXPORT SbThreadLocalKey
 SbThreadCreateLocalKey(SbThreadLocalDestructor destructor);