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);