Import Cobalt 11.132145
Change-Id: I61a98ecc60d7e8f59bb80efaa2cce4440e2ef99f
diff --git a/src/base/synchronization/waitable_event_posix.cc b/src/base/synchronization/waitable_event_posix.cc
index 7066aee..e7c1abf 100644
--- a/src/base/synchronization/waitable_event_posix.cc
+++ b/src/base/synchronization/waitable_event_posix.cc
@@ -159,7 +159,7 @@
bool WaitableEvent::TimedWait(const TimeDelta& max_time) {
base::ThreadRestrictions::AssertWaitAllowed();
- const Time end_time(Time::Now() + max_time);
+ const TimeTicks end_time(TimeTicks::Now() + max_time);
const bool finite_time = max_time.ToInternalValue() >= 0;
kernel_->lock_.Acquire();
@@ -184,7 +184,7 @@
// again before unlocking it.
for (;;) {
- const Time current_time(Time::Now());
+ const TimeTicks current_time(TimeTicks::Now());
if (sw.fired() || (finite_time && current_time >= end_time)) {
const bool return_value = sw.fired();
diff --git a/src/base/timer.cc b/src/base/timer.cc
index b2b7954..04b553e 100644
--- a/src/base/timer.cc
+++ b/src/base/timer.cc
@@ -236,9 +236,9 @@
// Setup member variables and the next tasks before the current one runs as
// we cannot access any member variables after calling task.Run().
NewScheduledTaskInfo task_info = SetupNewScheduledTask(delay_);
- base::Time task_start_time = base::Time::Now();
+ base::TimeTicks task_start_time = base::TimeTicks::Now();
task.Run();
- base::TimeDelta task_duration = base::Time::Now() - task_start_time;
+ base::TimeDelta task_duration = base::TimeTicks::Now() - task_start_time;
if (task_duration >= delay_) {
PostNewScheduledTask(task_info, base::TimeDelta::FromInternalValue(0));
} else {
diff --git a/src/cobalt/account/user_authorizer.h b/src/cobalt/account/user_authorizer.h
index a128c0b..863a122 100644
--- a/src/cobalt/account/user_authorizer.h
+++ b/src/cobalt/account/user_authorizer.h
@@ -73,6 +73,10 @@
// On success, a scoped_ptr holding a valid AccessToken is returned.
virtual scoped_ptr<AccessToken> RefreshAuthorization(SbUser user) = 0;
+ // Signals that the account manager is shutting down, and unblocks any pending
+ // request. Calling other methods after |Shutdown| may have no effect.
+ virtual void Shutdown() {}
+
// Instantiates an instance of the platform-specific implementation.
static UserAuthorizer* Create();
diff --git a/src/cobalt/base/clock.h b/src/cobalt/base/clock.h
index b3d418b..8867f4e 100644
--- a/src/cobalt/base/clock.h
+++ b/src/cobalt/base/clock.h
@@ -49,6 +49,31 @@
~SystemMonotonicClock() OVERRIDE {}
};
+// The MinimumResolutionClock modifies the output of an existing clock by
+// clamping its minimum resolution to a predefined amount. This is implemented
+// by rounding down the existing clock's time to the previous multiple of the
+// desired clock resolution.
+class MinimumResolutionClock : public Clock {
+ public:
+ MinimumResolutionClock(scoped_refptr<Clock> parent,
+ const base::TimeDelta& min_resolution)
+ : parent_(parent),
+ min_resolution_in_microseconds_(min_resolution.InMicroseconds()) {
+ DCHECK(parent);
+ }
+
+ base::TimeDelta Now() override {
+ base::TimeDelta now = parent_->Now();
+ int64 microseconds = now.InMicroseconds();
+ return base::TimeDelta::FromMicroseconds(
+ microseconds - (microseconds % min_resolution_in_microseconds_));
+ }
+
+ private:
+ scoped_refptr<Clock> parent_;
+ const int64_t min_resolution_in_microseconds_;
+};
+
// The OffsetClock takes a parent clock and an offset upon construction, and
// when queried for the time it returns the time of the parent clock offset by
// the specified offset.
diff --git a/src/cobalt/base/language.cc b/src/cobalt/base/language.cc
index 93869a1..fc72003 100644
--- a/src/cobalt/base/language.cc
+++ b/src/cobalt/base/language.cc
@@ -14,6 +14,8 @@
#include "cobalt/base/language.h"
+#include <algorithm>
+
#include "base/basictypes.h"
#include "base/logging.h"
#include "third_party/icu/source/common/unicode/uloc.h"
@@ -42,4 +44,28 @@
// We should end up with something like "en" or "en-US".
return language;
}
+
+std::string GetSystemLanguageScript() {
+ char buffer[ULOC_LANG_CAPACITY];
+ UErrorCode icu_result = U_ZERO_ERROR;
+
+ // Combine the ISO language and script.
+ uloc_getLanguage(NULL, buffer, arraysize(buffer), &icu_result);
+ if (!U_SUCCESS(icu_result)) {
+ DLOG(FATAL) << __FUNCTION__ << ": Unable to get language from ICU for "
+ << "default locale " << uloc_getDefault() << ".";
+ return "en";
+ }
+
+ std::string language = buffer;
+ uloc_getScript(NULL, buffer, arraysize(buffer), &icu_result);
+ if (U_SUCCESS(icu_result) && buffer[0]) {
+ language += "-";
+ language += buffer;
+ }
+
+ // We should end up with something like "en" or "en-Latn".
+ return language;
+}
+
} // namespace base
diff --git a/src/cobalt/base/language.h b/src/cobalt/base/language.h
index 174035f..4e59a72 100644
--- a/src/cobalt/base/language.h
+++ b/src/cobalt/base/language.h
@@ -19,12 +19,18 @@
namespace base {
-// Gets the system language.
+// Gets the system language and ISO 3166-1 country code.
// NOTE: should be in the format described by bcp47.
// http://www.rfc-editor.org/rfc/bcp/bcp47.txt
// Example: "en-US" or "de"
std::string GetSystemLanguage();
+// Gets the system language and ISO 15924 script code.
+// NOTE: should be in the format described by bcp47.
+// http://www.rfc-editor.org/rfc/bcp/bcp47.txt
+// Example: "en-US" or "de"
+std::string GetSystemLanguageScript();
+
} // namespace base
#endif // COBALT_BASE_LANGUAGE_H_
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index 35b7f2a..3e864d4 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -475,7 +475,6 @@
// Create the main components of our browser.
BrowserModule::Options options(web_options);
options.web_module_options.name = "MainWebModule";
- options.language = language;
options.initial_deep_link = GetInitialDeepLink();
options.network_module_options.preferred_language = language;
options.command_line_auto_mem_settings =
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index 5153bbb..01ece66 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -226,6 +226,7 @@
can_play_type_handler_(media::MediaModule::CreateCanPlayTypeHandler()),
network_module_(&storage_manager_, event_dispatcher_,
options_.network_module_options),
+ splash_screen_cache_(new SplashScreenCache()),
web_module_loaded_(true /* manually_reset */,
false /* initially_signalled */),
web_module_recreated_callback_(options_.web_module_recreated_callback),
@@ -259,7 +260,6 @@
waiting_for_error_retry_(false),
will_quit_(false),
application_state_(initial_application_state),
- splash_screen_cache_(new SplashScreenCache()),
main_web_module_generation_(0),
next_timeline_id_(1),
current_splash_screen_timeline_id_(-1),
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index a2ef629..db86c6d 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -84,7 +84,6 @@
storage::StorageManager::Options storage_manager_options;
WebModule::Options web_module_options;
media::MediaModule::Options media_module_options;
- std::string language;
std::string initial_deep_link;
base::Closure web_module_recreated_callback;
memory_settings::AutoMemSettings command_line_auto_mem_settings;
@@ -424,6 +423,9 @@
// that may be producing render trees.
base::MessageQueue render_tree_submission_queue_;
+ // The splash screen cache.
+ scoped_ptr<SplashScreenCache> splash_screen_cache_;
+
// Sets up everything to do with web page management, from loading and
// parsing the web page and all referenced files to laying it out. The
// web module will ultimately produce a render tree that can be passed
@@ -535,9 +537,6 @@
// screen will be displayed.
base::optional<GURL> fallback_splash_screen_url_;
- // The splash screen cache.
- scoped_ptr<SplashScreenCache> splash_screen_cache_;
-
// Number of main web modules that have take place so far, helpful for
// ditinguishing lingering events produced by older web modules as we switch
// from one to another. This is incremented with each navigation.
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index 54bf81a..87cfbf3 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -26,6 +26,7 @@
#include "base/memory/weak_ptr.h"
#include "base/message_loop_proxy.h"
#include "base/stringprintf.h"
+#include "cobalt/base/language.h"
#include "cobalt/base/startup_timer.h"
#include "cobalt/base/tokens.h"
#include "cobalt/base/type_id.h"
@@ -543,6 +544,9 @@
web_module_stat_tracker_->dom_stat_tracker(), data.initial_url,
data.network_module->GetUserAgent(),
data.network_module->preferred_language(),
+ data.options.font_language_script_override.empty()
+ ? base::GetSystemLanguageScript()
+ : data.options.font_language_script_override,
data.options.navigation_callback,
base::Bind(&WebModule::Impl::OnError, base::Unretained(this)),
data.network_module->cookie_jar(), data.network_module->GetPostSender(),
@@ -983,6 +987,8 @@
return;
}
+ layout_manager_->Purge();
+
// Retain the remote typeface cache when reducing memory.
PurgeResourceCaches(true /*should_retain_remote_typeface_cache*/);
window_->document()->PurgeCachedResources();
diff --git a/src/cobalt/browser/web_module.h b/src/cobalt/browser/web_module.h
index da3a9c1..b41e8c9 100644
--- a/src/cobalt/browser/web_module.h
+++ b/src/cobalt/browser/web_module.h
@@ -187,6 +187,11 @@
// the suspend state.
bool should_retain_remote_typeface_cache_on_suspend;
+ // The language and script to use with fonts. If left empty, then the
+ // language-script combination provided by base::GetSystemLanguageScript()
+ // is used.
+ std::string font_language_script_override;
+
// The splash screen cache object, owned by the BrowserModule.
SplashScreenCache* splash_screen_cache;
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 9ce0c99..ad8eeb1 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-119147
\ No newline at end of file
+132145
\ No newline at end of file
diff --git a/src/cobalt/dom/custom_event_test.cc b/src/cobalt/dom/custom_event_test.cc
index 5c3447b..6a834a1 100644
--- a/src/cobalt/dom/custom_event_test.cc
+++ b/src/cobalt/dom/custom_event_test.cc
@@ -63,7 +63,8 @@
1920, 1080, 1.f, base::kApplicationStateStarted, css_parser_.get(),
dom_parser_.get(), fetcher_factory_.get(), NULL, NULL, NULL, NULL,
NULL, NULL, &local_storage_database_, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, url_, "", "en-US", base::Callback<void(const GURL&)>(),
+ NULL, NULL, url_, "", "en-US", "en",
+ base::Callback<void(const GURL&)>(),
base::Bind(&MockErrorCallback::Run,
base::Unretained(&mock_error_callback_)),
NULL, network_bridge::PostSender(),
diff --git a/src/cobalt/dom/document.cc b/src/cobalt/dom/document.cc
index fa07b55..cc2cc92 100644
--- a/src/cobalt/dom/document.cc
+++ b/src/cobalt/dom/document.cc
@@ -123,7 +123,7 @@
html_element_context_->resource_provider(),
html_element_context_->remote_typeface_cache(),
base::Bind(&Document::OnTypefaceLoadEvent, base::Unretained(this)),
- html_element_context_->language()));
+ html_element_context_->font_language_script()));
if (HasBrowsingContext()) {
if (html_element_context_->remote_typeface_cache()) {
diff --git a/src/cobalt/dom/error_event_test.cc b/src/cobalt/dom/error_event_test.cc
index a115fb8..105ec2e 100644
--- a/src/cobalt/dom/error_event_test.cc
+++ b/src/cobalt/dom/error_event_test.cc
@@ -63,7 +63,8 @@
1920, 1080, 1.f, base::kApplicationStateStarted, css_parser_.get(),
dom_parser_.get(), fetcher_factory_.get(), NULL, NULL, NULL, NULL,
NULL, NULL, &local_storage_database_, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, url_, "", "en-US", base::Callback<void(const GURL&)>(),
+ NULL, NULL, url_, "", "en-US", "en",
+ base::Callback<void(const GURL&)>(),
base::Bind(&MockErrorCallback::Run,
base::Unretained(&mock_error_callback_)),
NULL, network_bridge::PostSender(),
diff --git a/src/cobalt/dom/font_cache.cc b/src/cobalt/dom/font_cache.cc
index 4fff414..d101291 100644
--- a/src/cobalt/dom/font_cache.cc
+++ b/src/cobalt/dom/font_cache.cc
@@ -43,12 +43,12 @@
FontCache::FontCache(render_tree::ResourceProvider** resource_provider,
loader::font::RemoteTypefaceCache* remote_typeface_cache,
const base::Closure& external_typeface_load_event_callback,
- const std::string& language)
+ const std::string& language_script)
: resource_provider_(resource_provider),
remote_typeface_cache_(remote_typeface_cache),
external_typeface_load_event_callback_(
external_typeface_load_event_callback),
- language_(language),
+ language_script_(language_script),
font_face_map_(new FontFaceMap()),
last_inactive_process_time_(base::TimeTicks::Now()) {}
@@ -92,13 +92,32 @@
void FontCache::PurgeCachedResources() {
DCHECK(thread_checker_.CalledOnValidThread());
- font_face_map_->clear();
- font_list_map_.clear();
- inactive_font_set_.clear();
- font_map_.clear();
- character_fallback_typeface_maps_.clear();
requested_remote_typeface_cache_.clear();
+
+ // Remove all font lists that are unreferenced outside of the cache and reset
+ // those that are retained to their initial state.
+ for (FontListMap::iterator iter = font_list_map_.begin();
+ iter != font_list_map_.end();) {
+ FontListInfo& font_list_info = iter->second;
+ if (font_list_info.font_list->HasOneRef()) {
+ font_list_map_.erase(iter++);
+ continue;
+ }
+ DLOG(WARNING) << "Unable to purge font list!";
+ font_list_info.font_list->Reset();
+ ++iter;
+ }
+
local_typeface_map_.clear();
+ font_map_.clear();
+ inactive_font_set_.clear();
+
+ // Walk the character fallback maps, clearing their typefaces.
+ for (CharacterFallbackTypefaceMaps::iterator iter =
+ character_fallback_typeface_maps_.begin();
+ iter != character_fallback_typeface_maps_.end(); ++iter) {
+ iter->second.clear();
+ }
}
void FontCache::ProcessInactiveFontListsAndFonts() {
@@ -192,7 +211,7 @@
DCHECK(resource_provider());
return GetCachedLocalTypeface(
resource_provider()->GetCharacterFallbackTypeface(utf32_character, style,
- language_));
+ language_script_));
}
scoped_refptr<render_tree::GlyphBuffer> FontCache::CreateGlyphBuffer(
@@ -200,7 +219,7 @@
FontList* font_list) {
DCHECK(resource_provider());
return resource_provider()->CreateGlyphBuffer(
- text_buffer, static_cast<size_t>(text_length), language_, is_rtl,
+ text_buffer, static_cast<size_t>(text_length), language_script_, is_rtl,
font_list);
}
@@ -209,7 +228,7 @@
render_tree::FontVector* maybe_used_fonts) {
DCHECK(resource_provider());
return resource_provider()->GetTextWidth(
- text_buffer, static_cast<size_t>(text_length), language_, is_rtl,
+ text_buffer, static_cast<size_t>(text_length), language_script_, is_rtl,
font_list, maybe_used_fonts);
}
@@ -340,14 +359,13 @@
*state = FontListFont::kLoadedState;
return GetFontFromTypefaceAndSize(typeface, size);
} else {
- if (cached_remote_typeface->IsLoading()) {
- if (requested_remote_typeface_iterator->second->HasActiveRequestTimer()) {
- *state = FontListFont::kLoadingWithTimerActiveState;
- } else {
- *state = FontListFont::kLoadingWithTimerExpiredState;
- }
- } else {
+ if (cached_remote_typeface->IsLoadingComplete()) {
*state = FontListFont::kUnavailableState;
+ } else if (requested_remote_typeface_iterator->second
+ ->HasActiveRequestTimer()) {
+ *state = FontListFont::kLoadingWithTimerActiveState;
+ } else {
+ *state = FontListFont::kLoadingWithTimerExpiredState;
}
return NULL;
}
diff --git a/src/cobalt/dom/font_cache.h b/src/cobalt/dom/font_cache.h
index e90adf9..a0d5761 100644
--- a/src/cobalt/dom/font_cache.h
+++ b/src/cobalt/dom/font_cache.h
@@ -173,7 +173,7 @@
FontCache(render_tree::ResourceProvider** resource_provider,
loader::font::RemoteTypefaceCache* remote_typeface_cache,
const base::Closure& external_typeface_load_event_callback,
- const std::string& language);
+ const std::string& language_script);
// Set a new font face map. If it matches the old font face map then nothing
// is done. Otherwise, it is updated with the new value and the remote
@@ -283,7 +283,7 @@
// logic into the font cache when the loader interface improves.
loader::font::RemoteTypefaceCache* const remote_typeface_cache_;
const base::Closure external_typeface_load_event_callback_;
- const std::string language_;
+ const std::string language_script_;
// Font-face related
// The cache contains a map of font faces and handles requesting typefaces by
diff --git a/src/cobalt/dom/font_cache_test.cc b/src/cobalt/dom/font_cache_test.cc
index 0f1f1dd..50b1c5e 100644
--- a/src/cobalt/dom/font_cache_test.cc
+++ b/src/cobalt/dom/font_cache_test.cc
@@ -79,6 +79,7 @@
&mock_resource_provider_)),
rtc(new loader::font::RemoteTypefaceCache(
"test_cache", 32 * 1024 /* 32 KB */,
+ true /*are_loading_retries_enabled*/,
base::Bind(&loader::MockLoaderFactory::CreateTypefaceLoader,
base::Unretained(&loader_factory_)))),
font_cache_(
diff --git a/src/cobalt/dom/font_list.cc b/src/cobalt/dom/font_list.cc
index 04743c0..f2d44ef 100644
--- a/src/cobalt/dom/font_list.cc
+++ b/src/cobalt/dom/font_list.cc
@@ -47,22 +47,20 @@
fonts_.push_back(FontListFont(""));
}
-bool FontList::IsVisible() const {
+void FontList::Reset() {
for (size_t i = 0; i < fonts_.size(); ++i) {
- // While any font in the font list is loading with an active timer, the font
- // is made transparent. "In cases where textual content is loaded before
- // downloadable fonts are available, user agents may... render text
- // transparently with fallback fonts to avoid a flash of text using a
- // fallback font. In cases where the font download fails user agents must
- // display text, simply leaving transparent text is considered
- // non-conformant behavior."
- // https://www.w3.org/TR/css3-fonts/#font-face-loading
- if (fonts_[i].state() == FontListFont::kLoadingWithTimerActiveState) {
- return false;
- }
+ FontListFont& font_list_font = fonts_[i];
+ font_list_font.set_state(FontListFont::kUnrequestedState);
+ font_list_font.set_font(NULL);
}
- return true;
+ primary_font_ = NULL;
+ is_font_metrics_set_ = false;
+ is_space_width_set_ = false;
+ is_ellipsis_info_set_ = false;
+ ellipsis_font_ = NULL;
+
+ fallback_typeface_to_font_map_.clear();
}
void FontList::ResetLoadingFonts() {
@@ -89,6 +87,24 @@
}
}
+bool FontList::IsVisible() const {
+ for (size_t i = 0; i < fonts_.size(); ++i) {
+ // While any font in the font list is loading with an active timer, the font
+ // is made transparent. "In cases where textual content is loaded before
+ // downloadable fonts are available, user agents may... render text
+ // transparently with fallback fonts to avoid a flash of text using a
+ // fallback font. In cases where the font download fails user agents must
+ // display text, simply leaving transparent text is considered
+ // non-conformant behavior."
+ // https://www.w3.org/TR/css3-fonts/#font-face-loading
+ if (fonts_[i].state() == FontListFont::kLoadingWithTimerActiveState) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
scoped_refptr<render_tree::GlyphBuffer> FontList::CreateGlyphBuffer(
const char16* text_buffer, int32 text_length, bool is_rtl) {
return font_cache_->CreateGlyphBuffer(text_buffer, text_length, is_rtl, this);
diff --git a/src/cobalt/dom/font_list.h b/src/cobalt/dom/font_list.h
index 3cbc097..28c5435 100644
--- a/src/cobalt/dom/font_list.h
+++ b/src/cobalt/dom/font_list.h
@@ -57,7 +57,7 @@
void set_state(State state) { state_ = state; }
const scoped_refptr<render_tree::Font>& font() const { return font_; }
- void set_font(const scoped_refptr<render_tree::Font> font) { font_ = font; }
+ void set_font(const scoped_refptr<render_tree::Font>& font) { font_ = font; }
private:
std::string family_name_;
@@ -116,7 +116,8 @@
FontList(FontCache* font_cache, const FontListKey& font_list_key);
- bool IsVisible() const;
+ // Resets the font list back to its initial state.
+ void Reset();
// Reset loading fonts sets all font list fonts with a state of
// |kLoadingState| back to |kUnrequestedState|, which will cause them to be
@@ -126,6 +127,8 @@
// reset, as they may change if the loading font is now available.
void ResetLoadingFonts();
+ bool IsVisible() const;
+
// Given a string of text, returns the glyph buffer needed to render it. In
// the case where |maybe_bounds| is non-NULL, it will also be populated with
// the bounds of the rect.
diff --git a/src/cobalt/dom/html_element_context.cc b/src/cobalt/dom/html_element_context.cc
index d9c502d..60d0e15 100644
--- a/src/cobalt/dom/html_element_context.cc
+++ b/src/cobalt/dom/html_element_context.cc
@@ -55,7 +55,7 @@
reduced_image_cache_capacity_manager,
loader::font::RemoteTypefaceCache* remote_typeface_cache,
loader::mesh::MeshCache* mesh_cache, DomStatTracker* dom_stat_tracker,
- const std::string& language,
+ const std::string& font_language_script,
base::ApplicationState initial_application_state,
float video_playback_rate_multiplier)
: fetcher_factory_(fetcher_factory),
@@ -74,7 +74,7 @@
remote_typeface_cache_(remote_typeface_cache),
mesh_cache_(mesh_cache),
dom_stat_tracker_(dom_stat_tracker),
- language_(language),
+ font_language_script_(font_language_script),
page_visibility_state_(initial_application_state),
video_playback_rate_multiplier_(video_playback_rate_multiplier),
sync_load_thread_("Synchronous Load"),
diff --git a/src/cobalt/dom/html_element_context.h b/src/cobalt/dom/html_element_context.h
index 7847423..cab1c71 100644
--- a/src/cobalt/dom/html_element_context.h
+++ b/src/cobalt/dom/html_element_context.h
@@ -63,7 +63,7 @@
reduced_image_cache_capacity_manager,
loader::font::RemoteTypefaceCache* remote_typeface_cache,
loader::mesh::MeshCache* mesh_cache, DomStatTracker* dom_stat_tracker,
- const std::string& language,
+ const std::string& font_language_script,
base::ApplicationState initial_application_state,
float video_playback_rate_multiplier = 1.0);
~HTMLElementContext();
@@ -113,7 +113,9 @@
DomStatTracker* dom_stat_tracker() { return dom_stat_tracker_; }
- const std::string& language() const { return language_; }
+ const std::string& font_language_script() const {
+ return font_language_script_;
+ }
float video_playback_rate_multiplier() const {
return video_playback_rate_multiplier_;
@@ -151,7 +153,7 @@
loader::font::RemoteTypefaceCache* const remote_typeface_cache_;
loader::mesh::MeshCache* const mesh_cache_;
DomStatTracker* const dom_stat_tracker_;
- const std::string language_;
+ const std::string font_language_script_;
page_visibility::PageVisibilityState page_visibility_state_;
const float video_playback_rate_multiplier_;
diff --git a/src/cobalt/dom/html_media_element.cc b/src/cobalt/dom/html_media_element.cc
index 2272cb8..380491c 100644
--- a/src/cobalt/dom/html_media_element.cc
+++ b/src/cobalt/dom/html_media_element.cc
@@ -124,7 +124,6 @@
muted_(false),
paused_(true),
seeking_(false),
- loop_(false),
controls_(false),
last_time_update_event_wall_time_(0),
last_time_update_event_movie_time_(std::numeric_limits<float>::max()),
@@ -569,13 +568,18 @@
}
bool HTMLMediaElement::loop() const {
- MLOG() << loop_;
- return loop_;
+ MLOG() << HasAttribute("loop");
+ return HasAttribute("loop");
}
void HTMLMediaElement::set_loop(bool loop) {
- MLOG() << loop;
- loop_ = loop;
+ // The value of 'loop' is true when the 'loop' attribute is present.
+ // The value of the attribute is irrelevant.
+ if (loop) {
+ SetAttribute("loop", "");
+ } else {
+ RemoveAttribute("loop");
+ }
}
void HTMLMediaElement::Play() {
diff --git a/src/cobalt/dom/html_media_element.h b/src/cobalt/dom/html_media_element.h
index 31314e3..dc0fdaa 100644
--- a/src/cobalt/dom/html_media_element.h
+++ b/src/cobalt/dom/html_media_element.h
@@ -296,7 +296,6 @@
bool muted_;
bool paused_;
bool seeking_;
- bool loop_;
bool controls_;
// The last time a timeupdate event was sent (wall clock).
diff --git a/src/cobalt/dom/local_storage_database.cc b/src/cobalt/dom/local_storage_database.cc
index 844f584..02fc976 100644
--- a/src/cobalt/dom/local_storage_database.cc
+++ b/src/cobalt/dom/local_storage_database.cc
@@ -103,6 +103,7 @@
write_statement.BindString(2, value);
bool ok = write_statement.Run();
DCHECK(ok);
+ sql_context->FlushOnChange();
}
void SqlDelete(const std::string& id, const std::string& key,
@@ -117,6 +118,7 @@
delete_statement.BindString(1, key);
bool ok = delete_statement.Run();
DCHECK(ok);
+ sql_context->FlushOnChange();
}
void SqlClear(const std::string& id, storage::SqlContext* sql_context) {
@@ -129,6 +131,7 @@
clear_statement.BindString(0, id);
bool ok = clear_statement.Run();
DCHECK(ok);
+ sql_context->FlushOnChange();
}
} // namespace
@@ -156,20 +159,17 @@
TRACK_MEMORY_SCOPE("Storage");
Init();
storage_->GetSqlContext(base::Bind(&SqlWrite, id, key, value));
- storage_->FlushOnChange();
}
void LocalStorageDatabase::Delete(const std::string& id,
const std::string& key) {
Init();
storage_->GetSqlContext(base::Bind(&SqlDelete, id, key));
- storage_->FlushOnChange();
}
void LocalStorageDatabase::Clear(const std::string& id) {
Init();
storage_->GetSqlContext(base::Bind(&SqlClear, id));
- storage_->FlushOnChange();
}
void LocalStorageDatabase::Flush(const base::Closure& callback) {
diff --git a/src/cobalt/dom/testing/stub_window.h b/src/cobalt/dom/testing/stub_window.h
index 10e3e53..53f53bd 100644
--- a/src/cobalt/dom/testing/stub_window.h
+++ b/src/cobalt/dom/testing/stub_window.h
@@ -53,7 +53,7 @@
1920, 1080, 1.f, base::kApplicationStateStarted, css_parser_.get(),
dom_parser_.get(), fetcher_factory_.get(), NULL, NULL, NULL, NULL, NULL,
NULL, &local_storage_database_, NULL, NULL, NULL, NULL, NULL, NULL,
- dom_stat_tracker_.get(), url_, "", "en-US",
+ dom_stat_tracker_.get(), url_, "", "en-US", "en",
base::Callback<void(const GURL&)>(), base::Bind(&StubErrorCallback),
NULL, network_bridge::PostSender(),
std::string() /* default security policy */, dom::kCspEnforcementEnable,
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc
index c11b61c..66dadf7 100644
--- a/src/cobalt/dom/window.cc
+++ b/src/cobalt/dom/window.cc
@@ -74,6 +74,14 @@
DISALLOW_COPY_AND_ASSIGN(RelayLoadEvent);
};
+namespace {
+// Ensure that the timer resolution is at the lowest 20 microseconds in
+// order to mitigate potential Spectre-related attacks. This is following
+// Mozilla's lead as described here:
+// https://www.mozilla.org/en-US/security/advisories/mfsa2018-01/
+const int64_t kPerformanceTimerMinResolutionInMicroseconds = 20;
+} // namespace
+
Window::Window(int width, int height, float device_pixel_ratio,
base::ApplicationState initial_application_state,
cssom::CSSParser* css_parser, Parser* dom_parser,
@@ -94,6 +102,7 @@
MediaSource::Registry* media_source_registry,
DomStatTracker* dom_stat_tracker, const GURL& url,
const std::string& user_agent, const std::string& language,
+ const std::string& font_language_script,
const base::Callback<void(const GURL&)> navigation_callback,
const base::Callback<void(const std::string&)>& error_callback,
network_bridge::CookieJar* cookie_jar,
@@ -122,13 +131,19 @@
web_media_player_factory, script_runner, script_value_factory,
media_source_registry, resource_provider, animated_image_tracker,
image_cache, reduced_image_cache_capacity_manager,
- remote_typeface_cache, mesh_cache, dom_stat_tracker, language,
- initial_application_state, video_playback_rate_multiplier)),
+ remote_typeface_cache, mesh_cache, dom_stat_tracker,
+ font_language_script, initial_application_state,
+ video_playback_rate_multiplier)),
performance_(new Performance(
#if defined(ENABLE_TEST_RUNNER)
- clock_type == kClockTypeTestRunner ? test_runner_->GetClock() :
+ clock_type == kClockTypeTestRunner
+ ? test_runner_->GetClock()
+ :
#endif
- new base::SystemMonotonicClock())),
+ new base::MinimumResolutionClock(
+ new base::SystemMonotonicClock(),
+ base::TimeDelta::FromMicroseconds(
+ kPerformanceTimerMinResolutionInMicroseconds)))),
ALLOW_THIS_IN_INITIALIZER_LIST(document_(new Document(
html_element_context_.get(),
Document::Options(
diff --git a/src/cobalt/dom/window.h b/src/cobalt/dom/window.h
index 080aa88..335353f 100644
--- a/src/cobalt/dom/window.h
+++ b/src/cobalt/dom/window.h
@@ -135,6 +135,7 @@
MediaSourceRegistry* media_source_registry,
DomStatTracker* dom_stat_tracker, const GURL& url,
const std::string& user_agent, const std::string& language,
+ const std::string& font_language_script,
const base::Callback<void(const GURL&)> navigation_callback,
const base::Callback<void(const std::string&)>& error_callback,
network_bridge::CookieJar* cookie_jar,
diff --git a/src/cobalt/dom/window_test.cc b/src/cobalt/dom/window_test.cc
index f1ca6f4..845c319 100644
--- a/src/cobalt/dom/window_test.cc
+++ b/src/cobalt/dom/window_test.cc
@@ -52,7 +52,8 @@
1920, 1080, 1.f, base::kApplicationStateStarted, css_parser_.get(),
dom_parser_.get(), fetcher_factory_.get(), NULL, NULL, NULL, NULL,
NULL, NULL, &local_storage_database_, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, url_, "", "en-US", base::Callback<void(const GURL &)>(),
+ NULL, NULL, url_, "", "en-US", "en",
+ base::Callback<void(const GURL &)>(),
base::Bind(&MockErrorCallback::Run,
base::Unretained(&mock_error_callback_)),
NULL, network_bridge::PostSender(),
diff --git a/src/cobalt/h5vcc/h5vcc_account_manager.cc b/src/cobalt/h5vcc/h5vcc_account_manager.cc
index 0bdfffd..2cb65fd 100644
--- a/src/cobalt/h5vcc/h5vcc_account_manager.cc
+++ b/src/cobalt/h5vcc/h5vcc_account_manager.cc
@@ -15,59 +15,62 @@
#include "cobalt/h5vcc/h5vcc_account_manager.h"
#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
#include "starboard/user.h"
namespace cobalt {
namespace h5vcc {
H5vccAccountManager::H5vccAccountManager()
- : thread_("AccountManager"), owning_message_loop_(MessageLoop::current()),
- user_authorizer_(account::UserAuthorizer::Create()) {
+ : user_authorizer_(account::UserAuthorizer::Create()),
+ owning_message_loop_(MessageLoop::current()), thread_("AccountManager") {
thread_.Start();
}
void H5vccAccountManager::GetAuthToken(
const AccessTokenCallbackHolder& callback) {
- DCHECK(thread_checker_.CalledOnValidThread());
DLOG(INFO) << "Get authorization token.";
- scoped_ptr<AccessTokenCallbackReference> token_callback(
- new AccessTokenCallbackHolder::Reference(this, callback));
- thread_.message_loop()->PostTask(
- FROM_HERE, base::Bind(&H5vccAccountManager::RequestOperationInternal,
- this, kGetToken, base::Passed(&token_callback)));
+ PostOperation(kGetToken, callback);
}
void H5vccAccountManager::RequestPairing(
const AccessTokenCallbackHolder& callback) {
- DCHECK(thread_checker_.CalledOnValidThread());
DLOG(INFO) << "Request application linking.";
- scoped_ptr<AccessTokenCallbackReference> token_callback(
- new AccessTokenCallbackHolder::Reference(this, callback));
- thread_.message_loop()->PostTask(
- FROM_HERE, base::Bind(&H5vccAccountManager::RequestOperationInternal,
- this, kPairing, base::Passed(&token_callback)));
+ PostOperation(kPairing, callback);
}
void H5vccAccountManager::RequestUnpairing(
const AccessTokenCallbackHolder& callback) {
- DCHECK(thread_checker_.CalledOnValidThread());
DLOG(INFO) << "Request application unlinking.";
- scoped_ptr<AccessTokenCallbackReference> token_callback(
- new AccessTokenCallbackHolder::Reference(this, callback));
+ PostOperation(kUnpairing, callback);
+}
+
+void H5vccAccountManager::PostOperation(
+ OperationType operation_type, const AccessTokenCallbackHolder& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ AccessTokenCallbackReference* token_callback =
+ new AccessTokenCallbackHolder::Reference(this, callback);
+ pending_callbacks_.push_back(token_callback);
thread_.message_loop()->PostTask(
FROM_HERE, base::Bind(&H5vccAccountManager::RequestOperationInternal,
- this, kUnpairing, base::Passed(&token_callback)));
+ user_authorizer_.get(), operation_type,
+ base::Bind(&H5vccAccountManager::PostResult,
+ owning_message_loop_,
+ base::AsWeakPtr(this),
+ token_callback)));
}
H5vccAccountManager::~H5vccAccountManager() {
DCHECK(thread_checker_.CalledOnValidThread());
+ // Give the UserAuthorizer a chance to abort any long running pending requests
+ // before the message loop gets shut down.
+ user_authorizer_->Shutdown();
}
+// static
void H5vccAccountManager::RequestOperationInternal(
- OperationType operation,
- scoped_ptr<AccessTokenCallbackReference> token_callback) {
- DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
-
+ account::UserAuthorizer* user_authorizer, OperationType operation,
+ const base::Callback<void(const std::string&, uint64_t)>& post_result) {
SbUser current_user = SbUserGetCurrent();
DCHECK(SbUserIsValid(current_user));
@@ -75,18 +78,18 @@
switch (operation) {
case kPairing:
- access_token = user_authorizer_->AuthorizeUser(current_user);
+ access_token = user_authorizer->AuthorizeUser(current_user);
DLOG_IF(INFO, !access_token) << "User authorization request failed.";
break;
case kUnpairing:
- if (user_authorizer_->DeauthorizeUser(current_user)) {
+ if (user_authorizer->DeauthorizeUser(current_user)) {
break;
}
// The user canceled the flow, or there was some error. Fall into the next
// case to get an access token if available and return that.
DLOG(INFO) << "User deauthorization request failed. Try to get token.";
case kGetToken:
- access_token = user_authorizer_->RefreshAuthorization(current_user);
+ access_token = user_authorizer->RefreshAuthorization(current_user);
DLOG_IF(INFO, !access_token) << "Authorization refresh request failed.";
break;
}
@@ -109,18 +112,37 @@
}
}
- owning_message_loop_->PostTask(
+ post_result.Run(token_value, expiration_in_seconds);
+}
+
+// static
+void H5vccAccountManager::PostResult(
+ MessageLoop* message_loop,
+ base::WeakPtr<H5vccAccountManager> h5vcc_account_manager,
+ AccessTokenCallbackReference* token_callback,
+ const std::string& token, uint64_t expiration_in_seconds) {
+ message_loop->PostTask(
FROM_HERE,
- base::Bind(&H5vccAccountManager::SendResult, this,
- base::Passed(&token_callback), token_value,
- expiration_in_seconds));
+ base::Bind(&H5vccAccountManager::SendResult, h5vcc_account_manager,
+ token_callback, token, expiration_in_seconds));
}
void H5vccAccountManager::SendResult(
- scoped_ptr<AccessTokenCallbackReference> token_callback,
+ AccessTokenCallbackReference* token_callback,
const std::string& token, uint64_t expiration_in_seconds) {
DCHECK(thread_checker_.CalledOnValidThread());
+ ScopedVector<AccessTokenCallbackReference>::iterator found = std::find(
+ pending_callbacks_.begin(), pending_callbacks_.end(), token_callback);
+ if (found == pending_callbacks_.end()) {
+ DLOG(ERROR) << "Account manager callback not valid.";
+ return;
+ }
+ // In case a new account manager request is made as part of the callback,
+ // erase the callback in the pending vector before running it, but we can't
+ // delete it until after we've made the callback.
+ pending_callbacks_.weak_erase(found);
token_callback->value().Run(token, expiration_in_seconds);
+ delete token_callback;
}
} // namespace h5vcc
diff --git a/src/cobalt/h5vcc/h5vcc_account_manager.h b/src/cobalt/h5vcc/h5vcc_account_manager.h
index 9885bb7..1a33ace 100644
--- a/src/cobalt/h5vcc/h5vcc_account_manager.h
+++ b/src/cobalt/h5vcc/h5vcc_account_manager.h
@@ -19,6 +19,8 @@
#include <string>
#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
#include "base/message_loop.h"
#include "base/threading/thread.h"
#include "base/threading/thread_checker.h"
@@ -34,7 +36,8 @@
// one-at-time on another thread in FIFO order. When a request is complete, the
// AccessTokenCallback will be fired on the thread that the H5vccAccountManager
// was created on.
-class H5vccAccountManager : public script::Wrappable {
+class H5vccAccountManager : public script::Wrappable,
+ public base::SupportsWeakPtr<H5vccAccountManager> {
public:
typedef script::CallbackFunction<bool(const std::string&, uint64_t)>
AccessTokenCallback;
@@ -59,27 +62,49 @@
~H5vccAccountManager();
- void RequestOperationInternal(
- OperationType operation,
- scoped_ptr<AccessTokenCallbackReference> token_callback);
- void SendResult(scoped_ptr<AccessTokenCallbackReference> token_callback,
- const std::string& token,
- uint64_t expiration_in_seconds);
+ // Posts an operation to the account manager thread.
+ void PostOperation(OperationType operation_type,
+ const AccessTokenCallbackHolder& callback);
+
+ // Processes an operation on the account manager thread. Static because
+ // H5vccAccountManager may have been destructed before this runs.
+ static void RequestOperationInternal(
+ account::UserAuthorizer* user_authorizer, OperationType operation,
+ const base::Callback<void(const std::string&, uint64_t)>& post_result);
+
+ // Posts the result of an operation from the account manager thread back to
+ // the owning thread. Static because H5vccAccountManager may have been
+ // destructed before this runs.
+ static void PostResult(
+ MessageLoop* message_loop,
+ base::WeakPtr<H5vccAccountManager> h5vcc_account_manager,
+ AccessTokenCallbackReference* token_callback,
+ const std::string& token, uint64_t expiration_in_seconds);
+
+ // Sends the result of an operation to the callback on the owning thread.
+ void SendResult(AccessTokenCallbackReference* token_callback,
+ const std::string& token, uint64_t expiration_in_seconds);
+
+ // The platform-specific user authorizer for getting access tokens.
+ scoped_ptr<account::UserAuthorizer> user_authorizer_;
+
+ // Scoped holder of the callbacks that are currently waiting for a response.
+ ScopedVector<AccessTokenCallbackReference> pending_callbacks_;
// Thread checker for the thread that creates this instance.
base::ThreadChecker thread_checker_;
- // Each incoming request will have a corresponding task posted to this
- // thread's message loop and will be handled in a FIFO manner.
- base::Thread thread_;
-
// The message loop that the H5vccAccountManager was created on. The public
// interface must be called from this message loop, and callbacks will be
// fired on this loop as well.
MessageLoop* owning_message_loop_;
- // The platform-specific user authorizer for getting access tokens.
- scoped_ptr<account::UserAuthorizer> user_authorizer_;
+ // Each incoming request will have a corresponding task posted to this
+ // thread's message loop and will be handled in a FIFO manner.
+ // This is last so that all the other fields are valid before the thread gets
+ // constructed, and they remain valid until the thread gets destructed and its
+ // message loop gets flushed.
+ base::Thread thread_;
friend class scoped_refptr<H5vccAccountManager>;
DISALLOW_COPY_AND_ASSIGN(H5vccAccountManager);
diff --git a/src/cobalt/layout/layout_manager.cc b/src/cobalt/layout/layout_manager.cc
index 2b48e1f..de6442c 100644
--- a/src/cobalt/layout/layout_manager.cc
+++ b/src/cobalt/layout/layout_manager.cc
@@ -60,6 +60,7 @@
void Suspend();
void Resume();
+ void Purge();
bool IsRenderTreePending() const;
@@ -253,7 +254,15 @@
void LayoutManager::Impl::Suspend() {
// Mark that we are suspended so that we don't try to perform any layouts.
suspended_ = true;
+ Purge();
+}
+void LayoutManager::Impl::Resume() {
+ // Re-enable layouts.
+ suspended_ = false;
+}
+
+void LayoutManager::Impl::Purge() {
// Invalidate any cached layout boxes from the document prior to clearing
// the initial containing block. That'll ensure that the full box tree is
// destroyed when the containing block is destroyed and that no children of
@@ -263,13 +272,8 @@
// Clear our reference to the initial containing block to allow any resources
// like images that were referenced by it to be released.
initial_containing_block_ = NULL;
-}
-void LayoutManager::Impl::Resume() {
- // Mark that we are no longer suspended and indicate that the layout is
- // dirty since when Suspend() was called we invalidated our previous layout.
DirtyLayout();
- suspended_ = false;
}
bool LayoutManager::Impl::IsRenderTreePending() const {
@@ -417,6 +421,7 @@
void LayoutManager::Suspend() { impl_->Suspend(); }
void LayoutManager::Resume() { impl_->Resume(); }
+void LayoutManager::Purge() { impl_->Purge(); }
bool LayoutManager::IsRenderTreePending() const {
return impl_->IsRenderTreePending();
}
diff --git a/src/cobalt/layout/layout_manager.h b/src/cobalt/layout/layout_manager.h
index 1a973a5..577cb6b 100644
--- a/src/cobalt/layout/layout_manager.h
+++ b/src/cobalt/layout/layout_manager.h
@@ -73,6 +73,7 @@
void Suspend();
void Resume();
+ void Purge();
bool IsRenderTreePending() const;
diff --git a/src/cobalt/loader/fetcher.h b/src/cobalt/loader/fetcher.h
index 3e00461..1064b92 100644
--- a/src/cobalt/loader/fetcher.h
+++ b/src/cobalt/loader/fetcher.h
@@ -61,7 +61,12 @@
};
// Concrete Fetcher subclass should start fetching immediately in constructor.
- explicit Fetcher(Handler* handler) : handler_(handler) {}
+ explicit Fetcher(Handler* handler)
+ : handler_(handler), did_fail_from_transient_error_(false) {}
+
+ bool did_fail_from_transient_error() const {
+ return did_fail_from_transient_error_;
+ }
// Concrete Fetcher subclass should cancel fetching in destructor.
virtual ~Fetcher() = 0;
@@ -69,8 +74,14 @@
protected:
Handler* handler() const { return handler_; }
+ void SetFailedFromTransientError() { did_fail_from_transient_error_ = true; }
+
private:
Handler* handler_;
+
+ // Whether or not the fetcher failed from an error that is considered
+ // transient, indicating that the same fetch may later succeed.
+ bool did_fail_from_transient_error_;
};
} // namespace loader
diff --git a/src/cobalt/loader/font/remote_typeface_cache.h b/src/cobalt/loader/font/remote_typeface_cache.h
index ba8240c..f3a04ed 100644
--- a/src/cobalt/loader/font/remote_typeface_cache.h
+++ b/src/cobalt/loader/font/remote_typeface_cache.h
@@ -57,7 +57,7 @@
const std::string& name, uint32 cache_capacity,
loader::LoaderFactory* loader_factory) {
return make_scoped_ptr<RemoteTypefaceCache>(new RemoteTypefaceCache(
- name, cache_capacity,
+ name, cache_capacity, true /*are_loading_retries_enabled*/,
base::Bind(&loader::LoaderFactory::CreateTypefaceLoader,
base::Unretained(loader_factory))));
}
diff --git a/src/cobalt/loader/image/image_cache.h b/src/cobalt/loader/image/image_cache.h
index 378d842..3a08252 100644
--- a/src/cobalt/loader/image/image_cache.h
+++ b/src/cobalt/loader/image/image_cache.h
@@ -48,10 +48,10 @@
inline static scoped_ptr<ImageCache> CreateImageCache(
const std::string& name, uint32 cache_capacity,
loader::LoaderFactory* loader_factory) {
- return make_scoped_ptr<ImageCache>(
- new ImageCache(name, cache_capacity,
- base::Bind(&loader::LoaderFactory::CreateImageLoader,
- base::Unretained(loader_factory))));
+ return make_scoped_ptr<ImageCache>(new ImageCache(
+ name, cache_capacity, false /*are_loading_retries_enabled*/,
+ base::Bind(&loader::LoaderFactory::CreateImageLoader,
+ base::Unretained(loader_factory))));
}
// The ReducedCacheCapacityManager is a helper class that manages state which
diff --git a/src/cobalt/loader/loader.cc b/src/cobalt/loader/loader.cc
index d5e453d..e42a21b 100644
--- a/src/cobalt/loader/loader.cc
+++ b/src/cobalt/loader/loader.cc
@@ -82,7 +82,7 @@
is_suspended_(is_suspended) {
DCHECK(!fetcher_creator_.is_null());
DCHECK(decoder_);
- DCHECK(!on_error.is_null());
+ DCHECK(!on_error_.is_null());
if (!is_suspended_) {
Start();
@@ -128,6 +128,11 @@
Start();
}
+bool Loader::DidFailFromTransientError() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return fetcher_ && fetcher_->did_fail_from_transient_error();
+}
+
void Loader::Start() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!is_suspended_);
diff --git a/src/cobalt/loader/loader.h b/src/cobalt/loader/loader.h
index 60c8b3f..ab9935c 100644
--- a/src/cobalt/loader/loader.h
+++ b/src/cobalt/loader/loader.h
@@ -24,15 +24,12 @@
#include "base/threading/thread_checker.h"
#include "cobalt/loader/decoder.h"
#include "cobalt/loader/fetcher.h"
-#include "cobalt/render_tree/resource_provider.h"
namespace cobalt {
namespace loader {
// Loader class consists of a Fetcher and a Decoder, that loads and decodes a
-// resource respectively. See the Loader design doc under the Cobalt intranet
-// home page.
-// TODO: Migrate Loader design doc to markdown in this directory.
+// resource respectively.
class Loader {
public:
typedef base::Callback<scoped_ptr<Fetcher>(Fetcher::Handler*)> FetcherCreator;
@@ -59,13 +56,16 @@
// called.
void Resume(render_tree::ResourceProvider* resource_provider);
+ bool DidFailFromTransientError() const;
+
private:
class FetcherToDecoderAdapter;
// Starts the fetch-and-decode.
void Start();
- FetcherCreator fetcher_creator_;
+ const FetcherCreator fetcher_creator_;
+
scoped_ptr<Decoder> decoder_;
scoped_ptr<FetcherToDecoderAdapter> fetcher_to_decoder_adaptor_;
scoped_ptr<Fetcher> fetcher_;
@@ -73,8 +73,8 @@
base::CancelableClosure fetcher_creator_error_closure_;
base::ThreadChecker thread_checker_;
- OnErrorFunction on_error_;
- OnDestructionFunction on_destruction_;
+ const OnErrorFunction on_error_;
+ const OnDestructionFunction on_destruction_;
bool is_suspended_;
diff --git a/src/cobalt/loader/mesh/mesh_cache.h b/src/cobalt/loader/mesh/mesh_cache.h
index b8ffe8c..6749032 100644
--- a/src/cobalt/loader/mesh/mesh_cache.h
+++ b/src/cobalt/loader/mesh/mesh_cache.h
@@ -48,9 +48,10 @@
inline static scoped_ptr<MeshCache> CreateMeshCache(
const std::string& name, uint32 cache_capacity,
loader::LoaderFactory* loader_factory) {
- return make_scoped_ptr<MeshCache>(new MeshCache(
- name, cache_capacity, base::Bind(&loader::LoaderFactory::CreateMeshLoader,
- base::Unretained(loader_factory))));
+ return make_scoped_ptr<MeshCache>(
+ new MeshCache(name, cache_capacity, false /*are_loading_retries_enabled*/,
+ base::Bind(&loader::LoaderFactory::CreateMeshLoader,
+ base::Unretained(loader_factory))));
}
} // namespace mesh
diff --git a/src/cobalt/loader/net_fetcher.cc b/src/cobalt/loader/net_fetcher.cc
index 08fd0a3..82ec0eb 100644
--- a/src/cobalt/loader/net_fetcher.cc
+++ b/src/cobalt/loader/net_fetcher.cc
@@ -133,6 +133,19 @@
if (status.is_success() && IsResponseCodeSuccess(response_code)) {
handler()->OnDone(this);
} else {
+ // Check for response codes and errors that are considered transient. These
+ // are the ones that net::URLFetcherCore is willing to attempt retries on,
+ // along with ERR_NAME_RESOLUTION_FAILED, which indicates a socket error.
+ if (response_code >= 500 ||
+ status.error() == net::ERR_TEMPORARILY_THROTTLED ||
+ status.error() == net::ERR_NETWORK_CHANGED ||
+ status.error() == net::ERR_NAME_RESOLUTION_FAILED ||
+ status.error() == net::ERR_CONNECTION_RESET ||
+ status.error() == net::ERR_CONNECTION_CLOSED ||
+ status.error() == net::ERR_CONNECTION_ABORTED) {
+ SetFailedFromTransientError();
+ }
+
std::string msg(
base::StringPrintf("NetFetcher error on %s: %s, response code %d",
source->GetURL().spec().c_str(),
diff --git a/src/cobalt/loader/resource_cache.h b/src/cobalt/loader/resource_cache.h
index ddb4300..ab01730 100644
--- a/src/cobalt/loader/resource_cache.h
+++ b/src/cobalt/loader/resource_cache.h
@@ -15,6 +15,7 @@
#ifndef COBALT_LOADER_RESOURCE_CACHE_H_
#define COBALT_LOADER_RESOURCE_CACHE_H_
+#include <algorithm>
#include <list>
#include <map>
#include <string>
@@ -27,6 +28,7 @@
#include "base/memory/scoped_vector.h"
#include "base/stringprintf.h"
#include "base/threading/thread_checker.h"
+#include "base/timer.h"
#include "cobalt/base/c_val.h"
#include "cobalt/csp/content_security_policy.h"
#include "cobalt/loader/decoder.h"
@@ -97,10 +99,7 @@
};
// Request fetching and decoding a single resource based on the url.
- CachedResource(const GURL& url,
- const csp::SecurityCallback& security_callback,
- const CreateLoaderFunction& create_loader_function,
- ResourceCacheType* resource_cache);
+ CachedResource(const GURL& url, ResourceCacheType* resource_cache);
// Resource is available. CachedResource is a wrapper of the resource
// and there is no need to fetch or load this resource again. |loader_|
@@ -114,7 +113,8 @@
// available.
scoped_refptr<ResourceType> TryGetResource();
- bool IsLoading();
+ // Whether not the resource located at |url_| is finished loading.
+ bool IsLoadingComplete();
const GURL& url() const { return url_; }
@@ -128,6 +128,17 @@
~CachedResource();
+ // Start loading the resource located at |url_|. This encompasses both
+ // fetching and decoding it.
+ void StartLoading();
+
+ // Schedule a loading retry on the resource located at |url_|. While there is
+ // no limit on the number of retry attempts that can occur, the retry
+ // scheduling uses an exponential backoff. The wait time doubles with each
+ // subsequent attempt until a maximum wait time of 1024 seconds (~17 minutes)
+ // is reached.
+ void ScheduleLoadingRetry();
+
// Callbacks for decoders.
//
// Notify that the resource is loaded successfully.
@@ -159,9 +170,14 @@
// triggered from within the resource initialization callstack, and we are
// not prepared to handle that. These members let us ensure that we are fully
// initialized before we proceed with any completion callbacks.
- bool completion_callbacks_enabled_;
+ bool are_completion_callbacks_enabled_;
base::Closure completion_callback_;
+ // When the resource cache is set to allow retries and a transient loading
+ // error causes a resource to fail to load, a retry is scheduled.
+ int retry_count_;
+ scoped_ptr<base::Timer> retry_timer_;
+
DISALLOW_COPY_AND_ASSIGN(CachedResource);
};
@@ -211,19 +227,14 @@
//////////////////////////////////////////////////////////////////////////
template <typename CacheType>
-CachedResource<CacheType>::CachedResource(
- const GURL& url, const csp::SecurityCallback& security_callback,
- const CreateLoaderFunction& create_loader_function,
- ResourceCacheType* resource_cache)
+CachedResource<CacheType>::CachedResource(const GURL& url,
+ ResourceCacheType* resource_cache)
: url_(url),
resource_cache_(resource_cache),
- completion_callbacks_enabled_(false) {
+ are_completion_callbacks_enabled_(false),
+ retry_count_(0) {
DCHECK(cached_resource_thread_checker_.CalledOnValidThread());
-
- loader_ = create_loader_function.Run(
- url, security_callback,
- base::Bind(&CachedResource::OnLoadingSuccess, base::Unretained(this)),
- base::Bind(&CachedResource::OnLoadingError, base::Unretained(this)));
+ StartLoading();
}
template <typename CacheType>
@@ -233,27 +244,19 @@
: url_(url),
resource_(resource),
resource_cache_(resource_cache),
- completion_callbacks_enabled_(false) {
+ are_completion_callbacks_enabled_(false),
+ retry_count_(0) {
DCHECK(cached_resource_thread_checker_.CalledOnValidThread());
}
template <typename CacheType>
-scoped_refptr<typename CacheType::ResourceType>
-CachedResource<CacheType>::TryGetResource() {
- DCHECK(cached_resource_thread_checker_.CalledOnValidThread());
-
- return resource_;
-}
-
-template <typename CacheType>
-bool CachedResource<CacheType>::IsLoading() {
- return loader_;
-}
-
-template <typename CacheType>
CachedResource<CacheType>::~CachedResource() {
DCHECK(cached_resource_thread_checker_.CalledOnValidThread());
+ if (retry_timer_) {
+ retry_timer_->Stop();
+ }
+
resource_cache_->NotifyResourceDestroyed(this);
for (int i = 0; i < kCallbackTypeCount; ++i) {
@@ -262,6 +265,51 @@
}
template <typename CacheType>
+scoped_refptr<typename CacheType::ResourceType>
+CachedResource<CacheType>::TryGetResource() {
+ DCHECK(cached_resource_thread_checker_.CalledOnValidThread());
+ return resource_;
+}
+
+template <typename CacheType>
+void CachedResource<CacheType>::StartLoading() {
+ DCHECK(cached_resource_thread_checker_.CalledOnValidThread());
+ DCHECK(!loader_);
+ loader_ = resource_cache_->StartLoadingResource(this);
+}
+
+template <typename CacheType>
+bool CachedResource<CacheType>::IsLoadingComplete() {
+ return !loader_ && !retry_timer_;
+}
+
+template <typename CacheType>
+void CachedResource<CacheType>::ScheduleLoadingRetry() {
+ DCHECK(cached_resource_thread_checker_.CalledOnValidThread());
+ DCHECK(!loader_);
+ DCHECK(!retry_timer_ || !retry_timer_->IsRunning());
+
+ LOG(WARNING) << "Scheduling loading retry for '" << url_ << "'";
+ resource_cache_->NotifyResourceLoadingRetryScheduled(this);
+
+ // The delay starts at 1 second and doubles every subsequent retry until the
+ // maxiumum delay of 1024 seconds (~17 minutes) is reached. After this, all
+ // additional attempts also wait 1024 seconds.
+ const int64 kBaseRetryDelayInMilliseconds = 1000;
+ const int kMaxRetryCountShift = 10;
+ int64 delay = kBaseRetryDelayInMilliseconds
+ << std::min(kMaxRetryCountShift, retry_count_++);
+
+ // The retry timer is lazily created the first time that it is needed.
+ if (!retry_timer_) {
+ retry_timer_.reset(new base::Timer(false, false));
+ }
+ retry_timer_->Start(
+ FROM_HERE, base::TimeDelta::FromMilliseconds(delay),
+ base::Bind(&CachedResource::StartLoading, base::Unretained(this)));
+}
+
+template <typename CacheType>
void CachedResource<CacheType>::OnLoadingSuccess(
const scoped_refptr<ResourceType>& resource) {
DCHECK(cached_resource_thread_checker_.CalledOnValidThread());
@@ -269,12 +317,13 @@
resource_ = resource;
loader_.reset();
+ retry_timer_.reset();
completion_callback_ =
base::Bind(&ResourceCacheType::NotifyResourceLoadingComplete,
base::Unretained(resource_cache_), base::Unretained(this),
kOnLoadingSuccessCallbackType);
- if (completion_callbacks_enabled_) {
+ if (are_completion_callbacks_enabled_) {
completion_callback_.Run();
}
}
@@ -283,15 +332,25 @@
void CachedResource<CacheType>::OnLoadingError(const std::string& error) {
DCHECK(cached_resource_thread_checker_.CalledOnValidThread());
- LOG(WARNING) << "Error while loading '" << url_ << "': " << error;
+ LOG(WARNING) << " Error while loading '" << url_ << "': " << error;
+
+ bool should_retry = resource_cache_->are_loading_retries_enabled() &&
+ loader_->DidFailFromTransientError();
loader_.reset();
- completion_callback_ =
- base::Bind(&ResourceCacheType::NotifyResourceLoadingComplete,
- base::Unretained(resource_cache_), base::Unretained(this),
- kOnLoadingErrorCallbackType);
- if (completion_callbacks_enabled_) {
- completion_callback_.Run();
+
+ if (should_retry) {
+ ScheduleLoadingRetry();
+ } else {
+ retry_timer_.reset();
+
+ completion_callback_ =
+ base::Bind(&ResourceCacheType::NotifyResourceLoadingComplete,
+ base::Unretained(resource_cache_), base::Unretained(this),
+ kOnLoadingErrorCallbackType);
+ if (are_completion_callbacks_enabled_) {
+ completion_callback_.Run();
+ }
}
}
@@ -330,7 +389,7 @@
template <typename CacheType>
void CachedResource<CacheType>::EnableCompletionCallbacks() {
- completion_callbacks_enabled_ = true;
+ are_completion_callbacks_enabled_ = true;
if (!completion_callback_.is_null()) {
completion_callback_.Run();
}
@@ -398,6 +457,7 @@
};
ResourceCache(const std::string& name, uint32 cache_capacity,
+ bool are_load_retries_enabled,
const CreateLoaderFunction& create_loader_function);
// |CreateCachedResource| returns CachedResource. If the CachedResource is not
@@ -441,10 +501,16 @@
ResourceMap;
typedef typename ResourceMap::iterator ResourceMapIterator;
+ scoped_ptr<Loader> StartLoadingResource(CachedResourceType* cached_resource);
+
// Called by CachedResource objects after they finish loading.
void NotifyResourceLoadingComplete(CachedResourceType* cached_resource,
CallbackType callback_type);
+ // Called by CachedResource objects when they fail to load as a result of a
+ // transient error and are scheduling a retry.
+ void NotifyResourceLoadingRetryScheduled(CachedResourceType* cached_resource);
+
// Called by the destructor of CachedResource to remove CachedResource from
// |cached_resource_map_| and either immediately free the resource from memory
// or add it to |unreference_cached_resource_map_|, depending on whether the
@@ -467,10 +533,15 @@
// |callback_blocking_loading_resource_set_| is empty.
void ProcessPendingCallbacksIfUnblocked();
+ bool are_loading_retries_enabled() const {
+ return are_loading_retries_enabled_;
+ }
+
// The name of this resource cache object, useful while debugging.
const std::string name_;
uint32 cache_capacity_;
+ bool are_loading_retries_enabled_;
CreateLoaderFunction create_loader_function_;
@@ -531,9 +602,11 @@
template <typename CacheType>
ResourceCache<CacheType>::ResourceCache(
const std::string& name, uint32 cache_capacity,
+ bool are_loading_retries_enabled,
const CreateLoaderFunction& create_loader_function)
: name_(name),
cache_capacity_(cache_capacity),
+ are_loading_retries_enabled_(are_loading_retries_enabled),
create_loader_function_(create_loader_function),
is_processing_pending_callbacks_(false),
are_callbacks_disabled_(false),
@@ -595,22 +668,9 @@
// If we reach this point, then the resource doesn't exist yet.
++count_resources_requested_;
- // Add the resource to a loading set. If no current resources have pending
- // callbacks, then this resource will block callbacks until it is decoded.
- // However, if there are resources with pending callbacks, then the decoding
- // of this resource won't block the callbacks from occurring. This ensures
- // that a steady stream of new resources won't prevent callbacks from ever
- // occurring.
- if (pending_callback_map_.empty()) {
- callback_blocking_loading_resource_set_.insert(url.spec());
- } else {
- non_callback_blocking_loading_resource_set_.insert(url.spec());
- }
- ++count_resources_loading_;
-
// Create the cached resource and fetch its resource based on the url.
- scoped_refptr<CachedResourceType> cached_resource(new CachedResourceType(
- url, security_callback_, create_loader_function_, this));
+ scoped_refptr<CachedResourceType> cached_resource(
+ new CachedResourceType(url, this));
cached_resource_map_.insert(
std::make_pair(url.spec(), cached_resource.get()));
@@ -668,6 +728,40 @@
}
template <typename CacheType>
+scoped_ptr<Loader> ResourceCache<CacheType>::StartLoadingResource(
+ CachedResourceType* cached_resource) {
+ DCHECK(resource_cache_thread_checker_.CalledOnValidThread());
+ const std::string& url = cached_resource->url().spec();
+
+ // The resource should not already be in either of the loading sets.
+ DCHECK(callback_blocking_loading_resource_set_.find(url) ==
+ callback_blocking_loading_resource_set_.end());
+ DCHECK(non_callback_blocking_loading_resource_set_.find(url) ==
+ non_callback_blocking_loading_resource_set_.end());
+
+ // Add the resource to a loading set. If no current resources have pending
+ // callbacks, then this resource will block callbacks until it is decoded.
+ // However, if there are resources with pending callbacks, then the decoding
+ // of this resource won't block the callbacks from occurring. This ensures
+ // that a steady stream of new resources won't prevent callbacks from ever
+ // occurring.
+ if (pending_callback_map_.empty()) {
+ callback_blocking_loading_resource_set_.insert(url);
+ } else {
+ non_callback_blocking_loading_resource_set_.insert(url);
+ }
+
+ ++count_resources_loading_;
+
+ return create_loader_function_.Run(
+ cached_resource->url(), security_callback_,
+ base::Bind(&CachedResourceType::OnLoadingSuccess,
+ base::Unretained(cached_resource)),
+ base::Bind(&CachedResourceType::OnLoadingError,
+ base::Unretained(cached_resource)));
+}
+
+template <typename CacheType>
void ResourceCache<CacheType>::NotifyResourceLoadingComplete(
CachedResourceType* cached_resource, CallbackType callback_type) {
DCHECK(resource_cache_thread_checker_.CalledOnValidThread());
@@ -707,6 +801,29 @@
}
template <typename CacheType>
+void ResourceCache<CacheType>::NotifyResourceLoadingRetryScheduled(
+ CachedResourceType* cached_resource) {
+ DCHECK(resource_cache_thread_checker_.CalledOnValidThread());
+ const std::string& url = cached_resource->url().spec();
+
+ // Remove the resource from those currently loading. It'll be re-added once
+ // the retry starts.
+
+ // Remove the resource from its loading set. It should exist in exactly one
+ // of the loading sets.
+ if (callback_blocking_loading_resource_set_.erase(url)) {
+ DCHECK(non_callback_blocking_loading_resource_set_.find(url) ==
+ non_callback_blocking_loading_resource_set_.end());
+ } else if (!non_callback_blocking_loading_resource_set_.erase(url)) {
+ DCHECK(false);
+ }
+
+ --count_resources_loading_;
+
+ ProcessPendingCallbacksIfUnblocked();
+}
+
+template <typename CacheType>
void ResourceCache<CacheType>::NotifyResourceDestroyed(
CachedResourceType* cached_resource) {
DCHECK(resource_cache_thread_checker_.CalledOnValidThread());
diff --git a/src/cobalt/media/base/decoder_buffer_cache.cc b/src/cobalt/media/base/decoder_buffer_cache.cc
index 21908d2..a7ee245 100644
--- a/src/cobalt/media/base/decoder_buffer_cache.cc
+++ b/src/cobalt/media/base/decoder_buffer_cache.cc
@@ -42,10 +42,10 @@
base::TimeDelta media_time) {
DCHECK(thread_checker_.CalledOnValidThread());
- ClearSegmentsBeforeMediaTime(media_time, &audio_buffers_,
- &audio_key_frame_timestamps_);
- ClearSegmentsBeforeMediaTime(media_time, &video_buffers_,
- &video_key_frame_timestamps_);
+ audio_buffer_index_ -= ClearSegmentsBeforeMediaTime(
+ media_time, &audio_buffers_, &audio_key_frame_timestamps_);
+ video_buffer_index_ -= ClearSegmentsBeforeMediaTime(
+ media_time, &video_buffers_, &video_key_frame_timestamps_);
}
void DecoderBufferCache::ClearAll() {
@@ -55,6 +55,8 @@
audio_key_frame_timestamps_.clear();
video_buffers_.clear();
video_key_frame_timestamps_.clear();
+ audio_buffer_index_ = 0;
+ video_buffer_index_ = 0;
}
void DecoderBufferCache::StartResuming() {
@@ -94,7 +96,7 @@
}
// static
-void DecoderBufferCache::ClearSegmentsBeforeMediaTime(
+size_t DecoderBufferCache::ClearSegmentsBeforeMediaTime(
base::TimeDelta media_time, Buffers* buffers,
KeyFrameTimestamps* key_frame_timestamps) {
// Use K to denote a key frame and N for non-key frame. If the cache contains
@@ -112,15 +114,20 @@
key_frame_timestamps->erase(key_frame_timestamps->begin());
}
if (key_frame_timestamps->empty()) {
- return;
+ return 0;
}
+
+ size_t buffers_removed = 0;
while (scoped_refptr<DecoderBuffer> buffer = buffers->front()) {
if (buffer->is_key_frame() &&
buffer->timestamp() == key_frame_timestamps->front()) {
break;
}
buffers->pop_front();
+ ++buffers_removed;
}
+
+ return buffers_removed;
}
} // namespace media
diff --git a/src/cobalt/media/base/decoder_buffer_cache.h b/src/cobalt/media/base/decoder_buffer_cache.h
index cff5a5c..0a37528 100644
--- a/src/cobalt/media/base/decoder_buffer_cache.h
+++ b/src/cobalt/media/base/decoder_buffer_cache.h
@@ -49,7 +49,7 @@
typedef std::deque<scoped_refptr<DecoderBuffer> > Buffers;
typedef std::deque<base::TimeDelta> KeyFrameTimestamps;
- static void ClearSegmentsBeforeMediaTime(
+ static size_t ClearSegmentsBeforeMediaTime(
base::TimeDelta media_time, Buffers* buffers,
KeyFrameTimestamps* key_frame_timestamps);
diff --git a/src/cobalt/media/base/sbplayer_pipeline.cc b/src/cobalt/media/base/sbplayer_pipeline.cc
index d32d468..fe2ae00 100644
--- a/src/cobalt/media/base/sbplayer_pipeline.cc
+++ b/src/cobalt/media/base/sbplayer_pipeline.cc
@@ -373,8 +373,10 @@
DCHECK(!seek_cb.is_null());
if (audio_read_in_progress_ || video_read_in_progress_) {
- message_loop_->PostTask(
- FROM_HERE, base::Bind(&SbPlayerPipeline::Seek, this, time, seek_cb));
+ const TimeDelta kDelay = TimeDelta::FromMilliseconds(50);
+ message_loop_->PostDelayedTask(
+ FROM_HERE, base::Bind(&SbPlayerPipeline::Seek, this, time, seek_cb),
+ kDelay);
return;
}
@@ -693,8 +695,6 @@
audio_stream_ = audio_stream;
video_stream_ = video_stream;
- buffering_state_cb_.Run(kHaveMetadata);
-
bool is_encrypted = audio_stream_->audio_decoder_config().is_encrypted();
natural_size_ = video_stream_->video_decoder_config().natural_size();
is_encrypted |= video_stream_->video_decoder_config().is_encrypted();
@@ -824,6 +824,7 @@
NOTREACHED();
break;
case kSbPlayerStatePrerolling:
+ buffering_state_cb_.Run(kHaveMetadata);
break;
case kSbPlayerStatePresenting:
buffering_state_cb_.Run(kPrerollCompleted);
diff --git a/src/cobalt/media/base/starboard_player.cc b/src/cobalt/media/base/starboard_player.cc
index 9b30cba..e17dd70 100644
--- a/src/cobalt/media/base/starboard_player.cc
+++ b/src/cobalt/media/base/starboard_player.cc
@@ -135,19 +135,24 @@
void StarboardPlayer::WriteBuffer(DemuxerStream::Type type,
const scoped_refptr<DecoderBuffer>& buffer) {
DCHECK(message_loop_->BelongsToCurrentThread());
+ DCHECK(buffer);
- // When |state_| is kPlaying, cache all buffer appended. When |state_| is
- // kSuspended, there may still be left-over buffers appended from the pipeline
- // so they also should be cached.
- // When |state_| is resuming, all buffers come from the cache and shouldn't be
- // cached.
- if (state_ != kResuming) {
- decoder_buffer_cache_.AddBuffer(type, buffer);
- }
+ decoder_buffer_cache_.AddBuffer(type, buffer);
- if (state_ == kSuspended) {
- return;
+ if (state_ != kSuspended) {
+ WriteNextBufferFromCache(type);
}
+}
+
+// TODO: Move this after CreatePlayer() in a follow up CL. To keep the function
+// here makes code review easier.
+void StarboardPlayer::WriteNextBufferFromCache(DemuxerStream::Type type) {
+ DCHECK(state_ != kSuspended);
+
+ const scoped_refptr<DecoderBuffer>& buffer =
+ decoder_buffer_cache_.GetBuffer(type);
+ DCHECK(buffer);
+ decoder_buffer_cache_.AdvanceToNextBuffer(type);
DCHECK(SbPlayerIsValid(player_));
@@ -494,8 +499,7 @@
if (state_ == kResuming) {
DemuxerStream::Type stream_type = SbMediaTypeToDemuxerStreamType(type);
if (decoder_buffer_cache_.GetBuffer(stream_type)) {
- WriteBuffer(stream_type, decoder_buffer_cache_.GetBuffer(stream_type));
- decoder_buffer_cache_.AdvanceToNextBuffer(stream_type);
+ WriteNextBufferFromCache(stream_type);
return;
}
if (!decoder_buffer_cache_.GetBuffer(DemuxerStream::AUDIO) &&
diff --git a/src/cobalt/media/base/starboard_player.h b/src/cobalt/media/base/starboard_player.h
index ed318e0..a2c9dc5 100644
--- a/src/cobalt/media/base/starboard_player.h
+++ b/src/cobalt/media/base/starboard_player.h
@@ -111,6 +111,9 @@
DecodingBuffers;
void CreatePlayer();
+
+ void WriteNextBufferFromCache(DemuxerStream::Type type);
+
void ClearDecoderBufferCache();
void OnDecoderStatus(SbPlayer player, SbMediaType type,
diff --git a/src/cobalt/media/filters/shell_demuxer.cc b/src/cobalt/media/filters/shell_demuxer.cc
index 1764db8..45e3af5 100644
--- a/src/cobalt/media/filters/shell_demuxer.cc
+++ b/src/cobalt/media/filters/shell_demuxer.cc
@@ -195,7 +195,8 @@
stopped_(false),
flushing_(false),
audio_reached_eos_(false),
- video_reached_eos_(false) {
+ video_reached_eos_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
DCHECK(message_loop_);
DCHECK(buffer_allocator_);
DCHECK(data_source_);
@@ -239,7 +240,10 @@
blocking_thread_.message_loop_proxy(), FROM_HERE,
base::Bind(&ShellDemuxer::ParseConfigBlocking, base::Unretained(this),
status_cb),
- base::Bind(&ShellDemuxer::ParseConfigDone, base::Unretained(this),
+ // ParseConfigDone() will run on the current thread. Use a WeakPtr to
+ // ensure that ParseConfigDone() won't run if the current instance is
+ // destroyed.
+ base::Bind(&ShellDemuxer::ParseConfigDone, weak_ptr_factory_.GetWeakPtr(),
status_cb));
}
@@ -289,6 +293,11 @@
void ShellDemuxer::ParseConfigDone(const PipelineStatusCB& status_cb,
PipelineStatus status) {
DCHECK(MessageLoopBelongsToCurrentThread());
+
+ if (stopped_) {
+ return;
+ }
+
// if the blocking parser thread cannot parse config we're done.
if (status != PIPELINE_OK) {
status_cb.Run(status);
@@ -442,7 +451,14 @@
// Notify host of each disjoint range.
host_->OnBufferedTimeRangesChanged(buffered);
- IssueNextRequest();
+ // Post the task with a delay to make the request loop a bit friendly to
+ // other tasks as otherwise IssueNextRequest(), Request(), AllocateBuffer(),
+ // and Download() can form a tight loop on the |blocking_thread_|.
+ const base::TimeDelta kDelay = base::TimeDelta::FromMilliseconds(5);
+ blocking_thread_.message_loop_proxy()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ShellDemuxer::IssueNextRequest, base::Unretained(this)),
+ kDelay);
}
void ShellDemuxer::IssueNextRequest() {
diff --git a/src/cobalt/media/filters/shell_demuxer.h b/src/cobalt/media/filters/shell_demuxer.h
index f976278..ca8ea29 100644
--- a/src/cobalt/media/filters/shell_demuxer.h
+++ b/src/cobalt/media/filters/shell_demuxer.h
@@ -20,6 +20,7 @@
#include <vector>
#include "base/logging.h"
+#include "base/memory/weak_ptr.h"
#include "base/message_loop.h"
#include "base/threading/thread.h"
#include "cobalt/media/base/decoder_buffer.h"
@@ -180,6 +181,8 @@
scoped_refptr<ShellAU> requested_au_;
bool audio_reached_eos_;
bool video_reached_eos_;
+
+ base::WeakPtrFactory<ShellDemuxer> weak_ptr_factory_;
};
} // namespace media
diff --git a/src/cobalt/media/filters/source_buffer_range.cc b/src/cobalt/media/filters/source_buffer_range.cc
index c708c57..5903ab5 100644
--- a/src/cobalt/media/filters/source_buffer_range.cc
+++ b/src/cobalt/media/filters/source_buffer_range.cc
@@ -98,7 +98,6 @@
}
void SourceBufferRange::Seek(DecodeTimestamp timestamp) {
- DCHECK(CanSeekTo(timestamp));
DCHECK(!keyframe_map_.empty());
KeyframeMap::iterator result = GetFirstKeyframeAtOrBefore(timestamp);
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontStyleSet_cobalt.cc b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontStyleSet_cobalt.cc
index 36315ef..152fb6e 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontStyleSet_cobalt.cc
+++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontStyleSet_cobalt.cc
@@ -361,7 +361,7 @@
// These are purged if they are unreferenced outside of the style set.
for (int i = 0; i < styles_.count(); ++i) {
SkAutoTUnref<SkTypeface>& typeface = styles_[i]->typeface;
- if (typeface.get() != NULL && typeface->getRefCnt() == 1) {
+ if (typeface.get() != NULL && typeface->unique()) {
typeface.reset(NULL);
}
}
diff --git a/src/cobalt/storage/storage_manager.cc b/src/cobalt/storage/storage_manager.cc
index 99e0414..7b75647 100644
--- a/src/cobalt/storage/storage_manager.cc
+++ b/src/cobalt/storage/storage_manager.cc
@@ -177,8 +177,14 @@
void StorageManager::GetSqlContext(const SqlCallback& callback) {
TRACE_EVENT0("cobalt::storage", __FUNCTION__);
- sql_message_loop_->PostTask(FROM_HERE,
- base::Bind(callback, sql_context_.get()));
+ if (MessageLoop::current()->message_loop_proxy() != sql_message_loop_) {
+ sql_message_loop_->PostTask(FROM_HERE,
+ base::Bind(&StorageManager::GetSqlContext,
+ base::Unretained(this), callback));
+ return;
+ }
+
+ callback.Run(sql_context_.get());
}
void StorageManager::FlushOnChange() {
@@ -266,6 +272,8 @@
return;
}
+ initialized_ = true;
+
vfs_.reset(new VirtualFileSystem());
sql_vfs_.reset(new SqlVfs("cobalt_vfs", vfs_.get()));
// Savegame has finished loading. Now initialize the database connection.
@@ -278,13 +286,14 @@
DCHECK(loaded_raw_bytes);
Savegame::ByteVector& raw_bytes = *loaded_raw_bytes;
VirtualFileSystem::SerializedHeader header = {};
+ bool has_upgrade_data = false;
if (raw_bytes.size() > 0) {
const char* buffer = reinterpret_cast<char*>(&raw_bytes[0]);
int buffer_size = static_cast<int>(raw_bytes.size());
// Is this upgrade data?
if (upgrade::UpgradeReader::IsUpgradeData(buffer, buffer_size)) {
- upgrade_handler_->OnUpgrade(this, buffer, buffer_size);
+ has_upgrade_data = true;
} else {
if (raw_bytes.size() >= sizeof(VirtualFileSystem::SerializedHeader)) {
memcpy(&header, &raw_bytes[0],
@@ -331,7 +340,11 @@
SqlCreateSchemaTable(connection_.get());
SqlUpdateDatabaseUserVersion(connection_.get());
- initialized_ = true;
+ if (has_upgrade_data) {
+ const char* buffer = reinterpret_cast<char*>(&raw_bytes[0]);
+ int buffer_size = static_cast<int>(raw_bytes.size());
+ upgrade_handler_->OnUpgrade(this, buffer, buffer_size);
+ }
}
void StorageManager::StopFlushOnChangeTimers() {
@@ -433,6 +446,11 @@
TRACE_EVENT0("cobalt::storage", __FUNCTION__);
DCHECK(!sql_message_loop_->BelongsToCurrentThread());
+ // Make sure that the on change timers fire if they're running.
+ sql_message_loop_->PostTask(
+ FROM_HERE, base::Bind(&StorageManager::FireRunningOnChangeTimers,
+ base::Unretained(this)));
+
// The SQL thread may be communicating with the savegame I/O thread still,
// flushing all pending updates. This process can require back and forth
// communication. This method exists to wait for that communication to
@@ -451,6 +469,16 @@
no_flushes_pending_.Wait();
}
+void StorageManager::FireRunningOnChangeTimers() {
+ TRACE_EVENT0("cobalt::storage", __FUNCTION__);
+ DCHECK(sql_message_loop_->BelongsToCurrentThread());
+
+ if (flush_on_last_change_timer_->IsRunning() ||
+ flush_on_change_max_delay_timer_->IsRunning()) {
+ OnFlushOnChangeTimerFired();
+ }
+}
+
void StorageManager::OnDestroy() {
TRACE_EVENT0("cobalt::storage", __FUNCTION__);
DCHECK(sql_message_loop_->BelongsToCurrentThread());
diff --git a/src/cobalt/storage/storage_manager.h b/src/cobalt/storage/storage_manager.h
index 26a990f..9988f92 100644
--- a/src/cobalt/storage/storage_manager.h
+++ b/src/cobalt/storage/storage_manager.h
@@ -115,7 +115,7 @@
friend class StorageManagerTest;
// Flushes all queued flushes to the savegame thread.
- virtual void FlushInternal();
+ void FlushInternal();
// Initialize the SQLite database. This blocks until the savegame load is
// complete.
@@ -137,6 +137,9 @@
// outside the SQL message loop (such as from StorageManager's destructor).
void FinishIO();
+ // This function will immediately the on change timers if they are running.
+ void FireRunningOnChangeTimers();
+
// Called by the destructor, to ensure we destroy certain objects on the
// sql thread.
void OnDestroy();
diff --git a/src/cobalt/storage/storage_manager_test.cc b/src/cobalt/storage/storage_manager_test.cc
index 2d94612..b0029f0 100644
--- a/src/cobalt/storage/storage_manager_test.cc
+++ b/src/cobalt/storage/storage_manager_test.cc
@@ -146,6 +146,11 @@
new StorageManagerType(upgrade_handler.Pass(), options));
}
+ template <typename StorageManagerType>
+ void FinishIO() {
+ storage_manager_->FinishIO();
+ }
+
MessageLoop message_loop_;
scoped_ptr<StorageManager> storage_manager_;
};
@@ -200,7 +205,6 @@
*dynamic_cast<MockStorageManager*>(storage_manager_.get());
// When QueueFlush() is called, have it also call FlushWaiter::OnFlushDone().
- // We will wait for this in TimedWait().
ON_CALL(storage_manager, QueueFlush(_))
.WillByDefault(InvokeWithoutArgs(&waiter, &FlushWaiter::OnFlushDone));
EXPECT_CALL(storage_manager, QueueFlush(_)).Times(1);
@@ -254,7 +258,6 @@
*dynamic_cast<MockStorageManager*>(storage_manager_.get());
// When QueueFlush() is called, have it also call FlushWaiter::OnFlushDone().
- // We will wait for this in TimedWait().
ON_CALL(storage_manager, QueueFlush(_))
.WillByDefault(InvokeWithoutArgs(&waiter, &FlushWaiter::OnFlushDone));
EXPECT_CALL(storage_manager, QueueFlush(_)).Times(1);
@@ -267,6 +270,29 @@
EXPECT_EQ(true, waiter.IsSignaled());
}
+TEST_F(StorageManagerTest, FlushOnShutdown) {
+ // Test that pending flushes are completed on shutdown.
+ Init<MockStorageManager>();
+
+ storage_manager_->GetSqlContext(base::Bind(&FlushCallback));
+ message_loop_.RunUntilIdle();
+
+ FlushWaiter waiter;
+ MockStorageManager& storage_manager =
+ *dynamic_cast<MockStorageManager*>(storage_manager_.get());
+
+ // When QueueFlush() is called, have it also call FlushWaiter::OnFlushDone().
+ ON_CALL(storage_manager, QueueFlush(_))
+ .WillByDefault(InvokeWithoutArgs(&waiter, &FlushWaiter::OnFlushDone));
+ EXPECT_CALL(storage_manager, QueueFlush(_)).Times(1);
+
+ storage_manager_->FlushOnChange();
+ FinishIO<StorageManager>();
+ storage_manager_.reset();
+
+ EXPECT_TRUE(waiter.IsSignaled());
+}
+
TEST_F(StorageManagerTest, Upgrade) {
Savegame::ByteVector initial_data;
initial_data.push_back('U');
diff --git a/src/cobalt/xhr/xml_http_request.cc b/src/cobalt/xhr/xml_http_request.cc
index 7af4b99..cc35227 100644
--- a/src/cobalt/xhr/xml_http_request.cc
+++ b/src/cobalt/xhr/xml_http_request.cc
@@ -163,7 +163,8 @@
error_(false),
sent_(false),
stop_timeout_(false),
- upload_complete_(false) {
+ upload_complete_(false),
+ active_requests_count_(0) {
DCHECK(settings_);
dom::GlobalStats::GetInstance()->Add(this);
xhr_id_ = ++s_xhr_sequence_num_;
@@ -365,8 +366,9 @@
// Step 9
sent_ = true;
// Now that a send is happening, prevent this object
- // from being collected until it's complete or aborted.
- PreventGarbageCollection();
+ // from being collected until it's complete or aborted
+ // if no currently active request has called it before.
+ IncrementActiveRequests();
FireProgressEvent(this, base::Tokens::loadstart());
if (!upload_complete_) {
FireProgressEvent(upload_, base::Tokens::loadstart());
@@ -677,7 +679,7 @@
ChangeState(kDone);
UpdateProgress();
// Undo the ref we added in Send()
- AllowGarbageCollection();
+ DecrementActiveRequests();
} else {
HandleRequestError(kNetworkError);
}
@@ -775,7 +777,7 @@
FireProgressEvent(this, base::Tokens::loadend());
fetch_callback_.reset();
- AllowGarbageCollection();
+ DecrementActiveRequests();
}
void XMLHttpRequest::OnTimeout() {
@@ -856,6 +858,21 @@
}
}
+void XMLHttpRequest::IncrementActiveRequests() {
+ if (active_requests_count_ == 0) {
+ PreventGarbageCollection();
+ }
+ active_requests_count_++;
+}
+
+void XMLHttpRequest::DecrementActiveRequests() {
+ DCHECK_GT(active_requests_count_, 0);
+ active_requests_count_--;
+ if (active_requests_count_ == 0) {
+ AllowGarbageCollection();
+ }
+}
+
void XMLHttpRequest::PreventGarbageCollection() {
settings_->global_environment()->PreventGarbageCollection(
make_scoped_refptr(this));
diff --git a/src/cobalt/xhr/xml_http_request.h b/src/cobalt/xhr/xml_http_request.h
index ab9a5e4..209292a 100644
--- a/src/cobalt/xhr/xml_http_request.h
+++ b/src/cobalt/xhr/xml_http_request.h
@@ -218,6 +218,13 @@
void AllowGarbageCollection();
void StartRequest(const std::string& request_body);
+ // The following two methods are used to determine if garbage collection is
+ // needed. It is legal to reuse XHR and send a new request in last request's
+ // onload event listener. We should not allow garbage collection until
+ // the last request is fetched.
+ void IncrementActiveRequests();
+ void DecrementActiveRequests();
+
// Accessors / mutators for testing.
const GURL& request_url() const { return request_url_; }
bool error() const { return error_; }
@@ -271,6 +278,7 @@
bool sent_;
bool stop_timeout_;
bool upload_complete_;
+ int active_requests_count_;
static bool verbose_;
// Unique ID for debugging.
diff --git a/src/net/base/net_errors_starboard.cc b/src/net/base/net_errors_starboard.cc
index 1b07eeb..d9d6532 100644
--- a/src/net/base/net_errors_starboard.cc
+++ b/src/net/base/net_errors_starboard.cc
@@ -46,6 +46,10 @@
return OK;
case kSbSocketPending:
return ERR_IO_PENDING;
+#if SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT)
+ case kSbSocketErrorConnectionReset:
+ return ERR_CONNECTION_RESET;
+#endif // SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT)
case kSbSocketErrorFailed:
return ERR_FAILED;
default:
diff --git a/src/starboard/nplb/socket_accept_test.cc b/src/starboard/nplb/socket_accept_test.cc
index 9c3357c..aceef33 100644
--- a/src/starboard/nplb/socket_accept_test.cc
+++ b/src/starboard/nplb/socket_accept_test.cc
@@ -54,7 +54,7 @@
// Accept should result in an error.
EXPECT_EQ(kSbSocketInvalid, SbSocketAccept(server_socket));
- EXPECT_EQ(kSbSocketErrorFailed, SbSocketGetLastError(server_socket));
+ EXPECT_SB_SOCKET_ERROR_IS_ERROR(SbSocketGetLastError(server_socket));
EXPECT_TRUE(SbSocketDestroy(server_socket));
}
@@ -67,7 +67,7 @@
// Accept should result in an error.
EXPECT_EQ(kSbSocketInvalid, SbSocketAccept(server_socket));
- EXPECT_EQ(kSbSocketErrorFailed, SbSocketGetLastError(server_socket));
+ EXPECT_SB_SOCKET_ERROR_IS_ERROR(SbSocketGetLastError(server_socket));
EXPECT_TRUE(SbSocketDestroy(server_socket));
}
diff --git a/src/starboard/nplb/socket_bind_test.cc b/src/starboard/nplb/socket_bind_test.cc
index f813c0a..d40a9a0 100644
--- a/src/starboard/nplb/socket_bind_test.cc
+++ b/src/starboard/nplb/socket_bind_test.cc
@@ -50,7 +50,7 @@
TEST_P(SbSocketBindTest, RainyDayNullSocket) {
SbSocketAddress address =
GetUnspecifiedAddress(GetAddressType(), GetPortNumberForTests());
- EXPECT_EQ(kSbSocketErrorFailed, SbSocketBind(kSbSocketInvalid, &address));
+ EXPECT_SB_SOCKET_ERROR_IS_ERROR(SbSocketBind(kSbSocketInvalid, &address));
}
TEST_P(SbSocketBindTest, RainyDayNullAddress) {
@@ -58,7 +58,7 @@
ASSERT_TRUE(SbSocketIsValid(server_socket));
// Binding with a NULL address should fail.
- EXPECT_EQ(kSbSocketErrorFailed, SbSocketBind(server_socket, NULL));
+ EXPECT_SB_SOCKET_ERROR_IS_ERROR(SbSocketBind(server_socket, NULL));
// Even though that failed, binding the same socket now with 0.0.0.0:2048
// should work.
@@ -70,7 +70,7 @@
}
TEST_F(SbSocketBindTest, RainyDayNullNull) {
- EXPECT_EQ(kSbSocketErrorFailed, SbSocketBind(kSbSocketInvalid, NULL));
+ EXPECT_SB_SOCKET_ERROR_IS_ERROR(SbSocketBind(kSbSocketInvalid, NULL));
}
#if SB_HAS(IPV6)
@@ -81,7 +81,7 @@
// Binding with the wrong address type should fail.
SbSocketAddress address =
GetUnspecifiedAddress(GetClientAddressType(), GetPortNumberForTests());
- EXPECT_EQ(kSbSocketErrorFailed, SbSocketBind(server_socket, &address));
+ EXPECT_SB_SOCKET_ERROR_IS_ERROR(SbSocketBind(server_socket, &address));
// Even though that failed, binding the same socket now with the server
// address type should work.
@@ -105,8 +105,8 @@
SbSocketResolve(kTestHostName, GetFilterType());
ASSERT_NE(kNull, resolution);
EXPECT_LT(0, resolution->address_count);
- EXPECT_EQ(kSbSocketErrorFailed,
- SbSocketBind(server_socket, &resolution->addresses[0]));
+ EXPECT_SB_SOCKET_ERROR_IS_ERROR(
+ SbSocketBind(server_socket, &resolution->addresses[0]));
EXPECT_TRUE(SbSocketDestroy(server_socket));
SbSocketFreeResolution(resolution);
diff --git a/src/starboard/nplb/socket_clear_last_error_test.cc b/src/starboard/nplb/socket_clear_last_error_test.cc
index bce6015..b5be314 100644
--- a/src/starboard/nplb/socket_clear_last_error_test.cc
+++ b/src/starboard/nplb/socket_clear_last_error_test.cc
@@ -30,7 +30,7 @@
// Accept on the unbound socket should result in an error.
EXPECT_EQ(kSbSocketInvalid, SbSocketAccept(server_socket));
- EXPECT_EQ(kSbSocketErrorFailed, SbSocketGetLastError(server_socket));
+ EXPECT_SB_SOCKET_ERROR_IS_ERROR(SbSocketGetLastError(server_socket));
// After we clear the error, it should be OK.
EXPECT_TRUE(SbSocketClearLastError(server_socket));
diff --git a/src/starboard/nplb/socket_connect_test.cc b/src/starboard/nplb/socket_connect_test.cc
index 34378cf..9b82d9c 100644
--- a/src/starboard/nplb/socket_connect_test.cc
+++ b/src/starboard/nplb/socket_connect_test.cc
@@ -31,18 +31,18 @@
TEST_P(SbSocketConnectTest, RainyDayNullSocket) {
SbSocketAddress address = GetUnspecifiedAddress(GetAddressType(), 2048);
- EXPECT_EQ(kSbSocketErrorFailed, SbSocketConnect(kSbSocketInvalid, &address));
+ EXPECT_SB_SOCKET_ERROR_IS_ERROR(SbSocketConnect(kSbSocketInvalid, &address));
}
TEST_P(SbSocketConnectTest, RainyDayNullAddress) {
SbSocket socket = SbSocketCreate(GetAddressType(), kSbSocketProtocolTcp);
ASSERT_TRUE(SbSocketIsValid(socket));
- EXPECT_EQ(kSbSocketErrorFailed, SbSocketConnect(socket, NULL));
+ EXPECT_SB_SOCKET_ERROR_IS_ERROR(SbSocketConnect(socket, NULL));
EXPECT_TRUE(SbSocketDestroy(socket));
}
TEST_F(SbSocketConnectTest, RainyDayNullNull) {
- EXPECT_EQ(kSbSocketErrorFailed, SbSocketConnect(kSbSocketInvalid, NULL));
+ EXPECT_SB_SOCKET_ERROR_IS_ERROR(SbSocketConnect(kSbSocketInvalid, NULL));
}
#if SB_HAS(IPV6)
diff --git a/src/starboard/nplb/socket_helpers.h b/src/starboard/nplb/socket_helpers.h
index 38086d4..558b249 100644
--- a/src/starboard/nplb/socket_helpers.h
+++ b/src/starboard/nplb/socket_helpers.h
@@ -15,6 +15,8 @@
#ifndef STARBOARD_NPLB_SOCKET_HELPERS_H_
#define STARBOARD_NPLB_SOCKET_HELPERS_H_
+#include <vector>
+
#include "starboard/common/scoped_ptr.h"
#include "starboard/socket.h"
#include "starboard/socket_waiter.h"
@@ -175,6 +177,34 @@
EXPECT_LE(timeout, TimedWaitTimed(waiter, timeout));
}
+// Socket operations may return specific (e.g. kSbSocketErrorConnectionReset) or
+// general (e.g. kSbSocketErrorFailed) error codes, and while in some cases
+// it may be important that we obtain a specific error message, in other cases
+// it will just be used as a hint and so these methods are provided to make
+// it easy to test against specific or general errors.
+static inline bool SocketErrorIn(
+ SbSocketError error,
+ const std::vector<SbSocketError>& expected_set) {
+ for (size_t i = 0; i < expected_set.size(); ++i) {
+ if (expected_set[i] == error) {
+ return true;
+ }
+ }
+ return false;
+}
+
+#define EXPECT_SB_SOCKET_ERROR_IN(error, ...) \
+ do { \
+ EXPECT_TRUE(SocketErrorIn(error, {__VA_ARGS__})) \
+ << "With " #error " = " << error; \
+ } while (false)
+
+#define EXPECT_SB_SOCKET_ERROR_IS_ERROR(error) \
+ do { \
+ EXPECT_FALSE(SocketErrorIn(error, {kSbSocketOk, kSbSocketPending})) \
+ << "With " #error " = " << error; \
+ } while (false)
+
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/socket_listen_test.cc b/src/starboard/nplb/socket_listen_test.cc
index aaeabab..57067e1 100644
--- a/src/starboard/nplb/socket_listen_test.cc
+++ b/src/starboard/nplb/socket_listen_test.cc
@@ -30,7 +30,7 @@
};
TEST_F(SbSocketListenTest, RainyDayInvalid) {
- EXPECT_EQ(kSbSocketErrorFailed, SbSocketListen(kSbSocketInvalid));
+ EXPECT_SB_SOCKET_ERROR_IS_ERROR(SbSocketListen(kSbSocketInvalid));
}
TEST_P(SbSocketListenTest, SunnyDayUnbound) {
diff --git a/src/starboard/nplb/socket_send_to_test.cc b/src/starboard/nplb/socket_send_to_test.cc
index fae82a2..88d4bd0 100644
--- a/src/starboard/nplb/socket_send_to_test.cc
+++ b/src/starboard/nplb/socket_send_to_test.cc
@@ -47,15 +47,14 @@
// Continue sending to the socket until it fails to send. It's expected that
// SbSocketSendTo will fail when the server socket closes, but the application
- // should
- // not terminate.
- SbTime start = SbTimeGetNow();
+ // should not terminate.
+ SbTime start = SbTimeGetMonotonicNow();
SbTime now = start;
SbTime kTimeout = kSbTimeSecond;
int result = 0;
while (result >= 0 && (now - start < kTimeout)) {
result = SbSocketSendTo(trio->server_socket, send_buf, kBufSize, NULL);
- now = SbTimeGetNow();
+ now = SbTimeGetMonotonicNow();
}
delete[] send_buf;
@@ -68,6 +67,26 @@
EXPECT_EQ(-1, result);
}
+TEST(SbSocketSendToTest, RainyDayUnconnectedSocket) {
+ SbSocket socket =
+ SbSocketCreate(kSbSocketAddressTypeIpv4, kSbSocketProtocolTcp);
+ ASSERT_TRUE(SbSocketIsValid(socket));
+
+ char buf[16];
+ int result = SbSocketSendTo(socket, buf, sizeof(buf), NULL);
+ EXPECT_EQ(-1, result);
+
+#if SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT)
+ EXPECT_SB_SOCKET_ERROR_IN(SbSocketGetLastError(socket),
+ kSbSocketErrorConnectionReset,
+ kSbSocketErrorFailed);
+#else
+ EXPECT_SB_SOCKET_ERROR_IS_ERROR(SbSocketGetLastError(socket));
+#endif // SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT)
+
+ EXPECT_TRUE(SbSocketDestroy(socket));
+}
+
TEST_P(PairSbSocketSendToTest, RainyDaySendToClosedSocket) {
ConnectedTrio trio =
CreateAndConnect(GetServerAddressType(), GetClientAddressType(),
@@ -91,8 +110,14 @@
// Wait for the thread to exit and check the last socket error.
void* thread_result;
EXPECT_TRUE(SbThreadJoin(send_thread, &thread_result));
- // Check that the server_socket failed, as expected.
- EXPECT_EQ(SbSocketGetLastError(trio.server_socket), kSbSocketErrorFailed);
+
+#if SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT)
+ EXPECT_SB_SOCKET_ERROR_IN(SbSocketGetLastError(trio.server_socket),
+ kSbSocketErrorConnectionReset,
+ kSbSocketErrorFailed);
+#else
+ EXPECT_SB_SOCKET_ERROR_IS_ERROR(SbSocketGetLastError(trio.server_socket));
+#endif // SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT)
// Clean up the server socket.
EXPECT_TRUE(SbSocketDestroy(trio.server_socket));
diff --git a/src/starboard/nplb/socket_wrapper_test.cc b/src/starboard/nplb/socket_wrapper_test.cc
index 1ec2ca4..95127f3 100644
--- a/src/starboard/nplb/socket_wrapper_test.cc
+++ b/src/starboard/nplb/socket_wrapper_test.cc
@@ -38,6 +38,7 @@
scoped_ptr<ConnectedTrioWrapped> trio =
CreateAndConnectWrapped(GetServerAddressType(), GetClientAddressType(),
GetPortNumberForTests(), kSocketTimeout);
+ ASSERT_TRUE(trio);
ASSERT_TRUE(trio->server_socket);
ASSERT_TRUE(trio->server_socket->IsValid());
diff --git a/src/starboard/shared/alsa/alsa_util.cc b/src/starboard/shared/alsa/alsa_util.cc
index 3d1c04d..b0f0287 100644
--- a/src/starboard/shared/alsa/alsa_util.cc
+++ b/src/starboard/shared/alsa/alsa_util.cc
@@ -231,14 +231,15 @@
}
}
-void AlsaDrain(void* playback_handle) {
+bool AlsaDrain(void* playback_handle) {
if (playback_handle) {
snd_pcm_t* handle = reinterpret_cast<snd_pcm_t*>(playback_handle);
int error;
error = snd_pcm_drain(handle);
- SB_DCHECK(error >= 0);
+ ALSA_CHECK(error, snd_pcm_drain, false);
}
+ return true;
}
} // namespace alsa
diff --git a/src/starboard/shared/alsa/alsa_util.h b/src/starboard/shared/alsa/alsa_util.h
index c0d068f..5815828 100644
--- a/src/starboard/shared/alsa/alsa_util.h
+++ b/src/starboard/shared/alsa/alsa_util.h
@@ -36,7 +36,7 @@
int frames_to_write);
int AlsaGetBufferedFrames(void* playback_handle);
void AlsaCloseDevice(void* playback_handle);
-void AlsaDrain(void* playback_handle);
+bool AlsaDrain(void* playback_handle);
} // namespace alsa
} // namespace shared
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
index 2984b6a..850838f 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
+++ b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
@@ -90,7 +90,11 @@
packet.data = const_cast<uint8_t*>(input_buffer.data());
packet.size = input_buffer.size();
+#if LIBAVUTIL_VERSION_MAJOR > 52
+ av_frame_unref(av_frame_);
+#else // LIBAVUTIL_VERSION_MAJOR > 52
avcodec_get_frame_defaults(av_frame_);
+#endif // LIBAVUTIL_VERSION_MAJOR > 52
int frame_decoded = 0;
int result =
avcodec_decode_audio4(codec_context_, av_frame_, &frame_decoded, &packet);
@@ -246,7 +250,11 @@
return;
}
+#if LIBAVUTIL_VERSION_MAJOR > 52
+ av_frame_ = av_frame_alloc();
+#else // LIBAVUTIL_VERSION_MAJOR > 52
av_frame_ = avcodec_alloc_frame();
+#endif // LIBAVUTIL_VERSION_MAJOR > 52
if (av_frame_ == NULL) {
SB_LOG(ERROR) << "Unable to allocate audio frame";
TeardownCodec();
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_common.h b/src/starboard/shared/ffmpeg/ffmpeg_common.h
index 4203e91..9c63c1d 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_common.h
+++ b/src/starboard/shared/ffmpeg/ffmpeg_common.h
@@ -27,6 +27,16 @@
#include "starboard/shared/internal_only.h"
+#if !defined(LIBAVUTIL_VERSION_MAJOR)
+#error "LIBAVUTIL_VERSION_MAJOR not defined"
+#endif // !defined(LIBAVUTIL_VERSION_MAJOR)
+
+#if LIBAVUTIL_VERSION_MAJOR > 52
+#define PIX_FMT_NONE AV_PIX_FMT_NONE
+#define PIX_FMT_YUV420P AV_PIX_FMT_YUV420P
+#define PIX_FMT_YUVJ420P AV_PIX_FMT_YUVJ420P
+#endif // LIBAVUTIL_VERSION_MAJOR > 52
+
namespace starboard {
namespace shared {
namespace ffmpeg {
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
index 955cade..e1e9129 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
+++ b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
@@ -39,6 +39,58 @@
return width * height * 3 / 2;
}
+#if LIBAVUTIL_VERSION_MAJOR > 52
+
+void ReleaseBuffer(void* opaque, uint8_t* data) {
+ SbMemorySet(data, 0, sizeof(data));
+ SbMemoryDeallocate(data);
+}
+
+int AllocateBuffer(AVCodecContext* codec_context, AVFrame* frame, int flags) {
+ if (codec_context->pix_fmt != PIX_FMT_YUV420P &&
+ codec_context->pix_fmt != PIX_FMT_YUVJ420P) {
+ SB_DLOG(WARNING) << "Unsupported pix_fmt " << codec_context->pix_fmt;
+ return AVERROR(EINVAL);
+ }
+
+ int ret =
+ av_image_check_size(codec_context->width, codec_context->height, 0, NULL);
+ if (ret < 0) {
+ return ret;
+ }
+
+ // Align to kAlignment * 2 as we will divide y_stride by 2 for u and v planes
+ size_t y_stride = AlignUp(codec_context->width, kAlignment * 2);
+ size_t uv_stride = y_stride / 2;
+ size_t aligned_height = AlignUp(codec_context->height, kAlignment * 2);
+
+ uint8_t* frame_buffer = reinterpret_cast<uint8_t*>(SbMemoryAllocateAligned(
+ kAlignment, GetYV12SizeInBytes(y_stride, aligned_height)));
+
+ frame->data[0] = frame_buffer;
+ frame->linesize[0] = y_stride;
+
+ frame->data[1] = frame_buffer + y_stride * aligned_height;
+ frame->linesize[1] = uv_stride;
+
+ frame->data[2] = frame->data[1] + uv_stride * aligned_height / 2;
+ frame->linesize[2] = uv_stride;
+
+ frame->opaque = frame;
+ frame->width = codec_context->width;
+ frame->height = codec_context->height;
+ frame->format = codec_context->pix_fmt;
+
+ frame->reordered_opaque = codec_context->reordered_opaque;
+
+ frame->buf[0] = av_buffer_create(frame_buffer,
+ GetYV12SizeInBytes(y_stride, aligned_height),
+ &ReleaseBuffer, frame->opaque, 0);
+ return 0;
+}
+
+#else // LIBAVUTIL_VERSION_MAJOR > 52
+
int AllocateBuffer(AVCodecContext* codec_context, AVFrame* frame) {
if (codec_context->pix_fmt != PIX_FMT_YUV420P &&
codec_context->pix_fmt != PIX_FMT_YUVJ420P) {
@@ -93,6 +145,7 @@
SbMemorySet(frame->data, 0, sizeof(frame->data));
}
+#endif // LIBAVUTIL_VERSION_MAJOR > 52
} // namespace
VideoDecoder::VideoDecoder(SbMediaVideoCodec video_codec,
@@ -219,7 +272,11 @@
bool VideoDecoder::DecodePacket(AVPacket* packet) {
SB_DCHECK(packet != NULL);
+#if LIBAVUTIL_VERSION_MAJOR > 52
+ av_frame_unref(av_frame_);
+#else // LIBAVUTIL_VERSION_MAJOR > 52
avcodec_get_frame_defaults(av_frame_);
+#endif // LIBAVUTIL_VERSION_MAJOR > 52
int frame_decoded = 0;
int result =
avcodec_decode_video2(codec_context_, av_frame_, &frame_decoded, packet);
@@ -286,8 +343,12 @@
codec_context_->thread_count = 2;
codec_context_->opaque = this;
codec_context_->flags |= CODEC_FLAG_EMU_EDGE;
+#if LIBAVUTIL_VERSION_MAJOR > 52
+ codec_context_->get_buffer2 = AllocateBuffer;
+#else // LIBAVUTIL_VERSION_MAJOR > 52
codec_context_->get_buffer = AllocateBuffer;
codec_context_->release_buffer = ReleaseBuffer;
+#endif // LIBAVUTIL_VERSION_MAJOR > 52
codec_context_->extradata = NULL;
codec_context_->extradata_size = 0;
@@ -307,7 +368,11 @@
return;
}
+#if LIBAVUTIL_VERSION_MAJOR > 52
+ av_frame_ = av_frame_alloc();
+#else // LIBAVUTIL_VERSION_MAJOR > 52
av_frame_ = avcodec_alloc_frame();
+#endif // LIBAVUTIL_VERSION_MAJOR > 52
if (av_frame_ == NULL) {
SB_LOG(ERROR) << "Unable to allocate audio frame";
TeardownCodec();
diff --git a/src/starboard/shared/posix/socket_internal.cc b/src/starboard/shared/posix/socket_internal.cc
index 7a74c9e..12778c6 100644
--- a/src/starboard/shared/posix/socket_internal.cc
+++ b/src/starboard/shared/posix/socket_internal.cc
@@ -46,6 +46,12 @@
case EWOULDBLOCK:
#endif
return kSbSocketPending;
+#if SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT)
+ case ECONNRESET:
+ case ENETRESET:
+ case EPIPE:
+ return kSbSocketErrorConnectionReset;
+#endif // #if SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT)
}
// Here's where we would be more nuanced if we need to be.
diff --git a/src/starboard/shared/starboard/player/filter/ffmpeg_player_components_impl.cc b/src/starboard/shared/starboard/player/filter/ffmpeg_player_components_impl.cc
index 033b543..bd9a428 100644
--- a/src/starboard/shared/starboard/player/filter/ffmpeg_player_components_impl.cc
+++ b/src/starboard/shared/starboard/player/filter/ffmpeg_player_components_impl.cc
@@ -15,6 +15,7 @@
#include "starboard/shared/starboard/player/filter/player_components.h"
#include "starboard/audio_sink.h"
+#include "starboard/common/scoped_ptr.h"
#include "starboard/shared/ffmpeg/ffmpeg_audio_decoder.h"
#include "starboard/shared/ffmpeg/ffmpeg_video_decoder.h"
#include "starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h"
@@ -41,18 +42,16 @@
return scoped_ptr<PlayerComponents>(NULL);
}
- AudioDecoderImpl* audio_decoder = new AudioDecoderImpl(
- audio_parameters.audio_codec, audio_parameters.audio_header);
+ scoped_ptr<AudioDecoderImpl> audio_decoder(new AudioDecoderImpl(
+ audio_parameters.audio_codec, audio_parameters.audio_header));
if (!audio_decoder->is_valid()) {
- delete audio_decoder;
return scoped_ptr<PlayerComponents>(NULL);
}
- VideoDecoderImpl* video_decoder = new VideoDecoderImpl(
+ scoped_ptr<VideoDecoderImpl> video_decoder(new VideoDecoderImpl(
video_parameters.video_codec, video_parameters.output_mode,
- video_parameters.decode_target_graphics_context_provider);
+ video_parameters.decode_target_graphics_context_provider));
if (!video_decoder->is_valid()) {
- delete video_decoder;
return scoped_ptr<PlayerComponents>(NULL);
}
diff --git a/src/starboard/shared/win32/socket_internal.cc b/src/starboard/shared/win32/socket_internal.cc
index d61dc1b..6691afa 100644
--- a/src/starboard/shared/win32/socket_internal.cc
+++ b/src/starboard/shared/win32/socket_internal.cc
@@ -34,9 +34,22 @@
switch (error) {
case 0:
return kSbSocketOk;
+
+ // Microsoft Winsock error codes:
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx
case WSAEINPROGRESS:
case WSAEWOULDBLOCK:
return kSbSocketPending;
+#if SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT)
+ case WSAECONNRESET:
+ case WSAENETRESET:
+ return kSbSocketErrorConnectionReset;
+
+ // Microsoft System Error codes:
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
+ case ERROR_BROKEN_PIPE:
+ return kSbSocketErrorConnectionReset;
+#endif // #if SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT)
}
// Here's where we would be more nuanced if we need to be.
diff --git a/src/starboard/socket.h b/src/starboard/socket.h
index e44ddc0..115b176 100644
--- a/src/starboard/socket.h
+++ b/src/starboard/socket.h
@@ -64,10 +64,14 @@
// clever and wait on it with a SbSocketWaiter.
kSbSocketPending,
- // The operation failed for some reason.
- //
- // TODO: It's unclear if we actually care about why, so leaving the rest
- // of this enumeration blank until it becomes clear that we do.
+#if SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT)
+ // This socket error is generated when the connection is reset unexpectedly
+ // and the connection is now invalid.
+ // This might happen for example if an read packet has the "TCP RST" bit set.
+ kSbSocketErrorConnectionReset,
+#endif // SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT)
+
+ // The operation failed for some other reason not specified above.
kSbSocketErrorFailed,
} SbSocketError;
diff --git a/src/starboard/tools/raspi/run_test.py b/src/starboard/tools/raspi/run_test.py
index 58c925c..326b654 100755
--- a/src/starboard/tools/raspi/run_test.py
+++ b/src/starboard/tools/raspi/run_test.py
@@ -132,7 +132,13 @@
process.expect(r'\S+ password:', timeout=_PEXPECT_EXPECT_TIMEOUT)
process.sendline(_RASPI_PASSWORD)
- test_command = raspi_test_path + ' ' + flags
+ # Escape command line metacharacters in the flags
+ meta_chars = '()[]{}%!^"<>&|'
+ meta_re = re.compile('(' + '|'.join(
+ re.escape(char) for char in list(meta_chars)) + ')')
+ escaped_flags = re.subn(meta_re, r'\\\1', flags)[0]
+
+ test_command = raspi_test_path + ' ' + escaped_flags
test_time_tag = 'TEST-{time}'.format(time=time.time())
test_success_tag = 'succeeded'
test_failure_tag = 'failed'
diff --git a/src/third_party/freetype2/src/cff/cffgload.c b/src/third_party/freetype2/src/cff/cffgload.c
index 1b664c3..482cc5a 100644
--- a/src/third_party/freetype2/src/cff/cffgload.c
+++ b/src/third_party/freetype2/src/cff/cffgload.c
@@ -594,6 +594,14 @@
first = outline->n_contours <= 1
? 0 : outline->contours[outline->n_contours - 2] + 1;
+ /* in malformed fonts it can happen that a contour was started */
+ /* but no points were added */
+ if ( outline->n_contours && first == outline->n_points )
+ {
+ outline->n_contours--;
+ return;
+ }
+
/* We must not include the last point in the path if it */
/* is located on the first point. */
if ( outline->n_points > 1 )
diff --git a/src/third_party/libxml/src/libxml.h b/src/third_party/libxml/src/libxml.h
index 572de43..7301d38 100644
--- a/src/third_party/libxml/src/libxml.h
+++ b/src/third_party/libxml/src/libxml.h
@@ -9,6 +9,8 @@
#ifndef __XML_LIBXML_H__
#define __XML_LIBXML_H__
+#include <libxml/xmlstring.h>
+
#ifndef NO_LARGEFILE_SOURCE
#ifndef _LARGEFILE_SOURCE
#define _LARGEFILE_SOURCE
@@ -95,6 +97,7 @@
int __xmlRandom(void);
#endif
+XMLPUBFUN xmlChar * XMLCALL xmlEscapeFormatString(xmlChar **msg);
int xmlNop(void);
#ifdef IN_LIBXML
diff --git a/src/third_party/libxml/src/parser.c b/src/third_party/libxml/src/parser.c
index 855b5ea..22dabb8 100644
--- a/src/third_party/libxml/src/parser.c
+++ b/src/third_party/libxml/src/parser.c
@@ -2105,7 +2105,6 @@
ctxt->input->line++; ctxt->input->col = 1; \
} else ctxt->input->col++; \
ctxt->input->cur += l; \
- if (*ctxt->input->cur == '%') xmlParserHandlePEReference(ctxt); \
} while (0)
#define CUR_CHAR(l) xmlCurrentChar(ctxt, &l)
@@ -3376,13 +3375,6 @@
len += l;
NEXTL(l);
c = CUR_CHAR(l);
- if (c == 0) {
- count = 0;
- GROW;
- if (ctxt->instate == XML_PARSER_EOF)
- return(NULL);
- c = CUR_CHAR(l);
- }
}
}
if ((len > XML_MAX_NAME_LENGTH) &&
@@ -3390,6 +3382,16 @@
xmlFatalErr(ctxt, XML_ERR_NAME_TOO_LONG, "Name");
return(NULL);
}
+ if (ctxt->input->cur - ctxt->input->base < len) {
+ /*
+ * There were a couple of bugs where PERefs lead to to a change
+ * of the buffer. Check the buffer size to avoid passing an invalid
+ * pointer to xmlDictLookup.
+ */
+ xmlFatalErr(ctxt, XML_ERR_INTERNAL_ERROR,
+ "unexpected change of input buffer");
+ return (NULL);
+ }
if ((*ctxt->input->cur == '\n') && (ctxt->input->cur[-1] == '\r'))
return(xmlDictLookup(ctxt->dict, ctxt->input->cur - (len + 1), len));
return(xmlDictLookup(ctxt->dict, ctxt->input->cur - len, len));
diff --git a/src/third_party/libxml/src/relaxng.c b/src/third_party/libxml/src/relaxng.c
index 68c3c24..9dccfd7 100644
--- a/src/third_party/libxml/src/relaxng.c
+++ b/src/third_party/libxml/src/relaxng.c
@@ -2219,7 +2219,8 @@
XML_SNPRINTF(msg, 1000, "Unknown error code %d\n", err);
}
msg[1000 - 1] = 0;
- return (xmlStrdup((xmlChar *) msg));
+ xmlChar *result = xmlCharStrdup(msg);
+ return (xmlEscapeFormatString(&result));
}
/**
diff --git a/src/third_party/libxml/src/xmlIO.c b/src/third_party/libxml/src/xmlIO.c
index 6d1fd67..2df8c26 100644
--- a/src/third_party/libxml/src/xmlIO.c
+++ b/src/third_party/libxml/src/xmlIO.c
@@ -1704,7 +1704,7 @@
else {
xmlChar msg[500];
xmlStrPrintf(msg, 500,
- (const xmlChar *) "xmlZMemBuffExtend: %s %lu bytes.\n",
+ "xmlZMemBuffExtend: %s %lu bytes.\n",
"Allocation failure extending output buffer to",
new_size );
xmlIOErr(XML_IO_WRITE, (const char *) msg);
@@ -1750,7 +1750,7 @@
if ( z_err != Z_OK ) {
xmlChar msg[500];
xmlStrPrintf(msg, 500,
- (const xmlChar *) "xmlZMemBuffAppend: %s %d %s - %d",
+ "xmlZMemBuffAppend: %s %d %s - %d",
"Compression error while appending",
len, "bytes to buffer. ZLIB error", z_err );
xmlIOErr(XML_IO_WRITE, (const char *) msg);
@@ -1823,7 +1823,7 @@
else {
xmlChar msg[500];
xmlStrPrintf(msg, 500,
- (const xmlChar *) "xmlZMemBuffGetContent: %s - %d\n",
+ "xmlZMemBuffGetContent: %s - %d\n",
"Error flushing zlib buffers. Error code", z_err );
xmlIOErr(XML_IO_WRITE, (const char *) msg);
}
@@ -2028,7 +2028,7 @@
if ( len < 0 ) {
xmlChar msg[500];
xmlStrPrintf(msg, 500,
- (const xmlChar *) "xmlIOHTTPWrite: %s\n%s '%s'.\n",
+ "xmlIOHTTPWrite: %s\n%s '%s'.\n",
"Error appending to internal buffer.",
"Error sending document to URI",
ctxt->uri );
@@ -2100,7 +2100,7 @@
if ( http_content == NULL ) {
xmlChar msg[500];
xmlStrPrintf(msg, 500,
- (const xmlChar *) "xmlIOHTTPCloseWrite: %s '%s' %s '%s'.\n",
+ "xmlIOHTTPCloseWrite: %s '%s' %s '%s'.\n",
"Error retrieving content.\nUnable to",
http_mthd, "data to URI", ctxt->uri );
xmlIOErr(XML_IO_WRITE, (const char *) msg);
@@ -2172,7 +2172,7 @@
else {
xmlChar msg[500];
xmlStrPrintf(msg, 500,
- (const xmlChar *) "xmlIOHTTPCloseWrite: HTTP '%s' of %d %s\n'%s' %s %d\n",
+ "xmlIOHTTPCloseWrite: HTTP '%s' of %d %s\n'%s' %s %d\n",
http_mthd, content_lgth,
"bytes to URI", ctxt->uri,
"failed. HTTP return code:", http_rtn );
diff --git a/src/third_party/libxml/src/xmlschemas.c b/src/third_party/libxml/src/xmlschemas.c
index efdf6a8..3755cab 100644
--- a/src/third_party/libxml/src/xmlschemas.c
+++ b/src/third_party/libxml/src/xmlschemas.c
@@ -1087,7 +1087,7 @@
static void
xmlSchemaInternalErr(xmlSchemaAbstractCtxtPtr actxt,
const char *funcName,
- const char *message);
+ const char *message) LIBXML_ATTR_FORMAT(3,0);
static int
xmlSchemaCheckCOSSTDerivedOK(xmlSchemaAbstractCtxtPtr ctxt,
xmlSchemaTypePtr type,
@@ -1771,7 +1771,7 @@
}
FREE_AND_NULL(str)
- return (*buf);
+ return (xmlEscapeFormatString(buf));
}
/**
@@ -1891,7 +1891,7 @@
*
* Handle a parser error
*/
-static void
+static void LIBXML_ATTR_FORMAT(4,0)
xmlSchemaPErr(xmlSchemaParserCtxtPtr ctxt, xmlNodePtr node, int error,
const char *msg, const xmlChar * str1, const xmlChar * str2)
{
@@ -1924,7 +1924,7 @@
*
* Handle a parser error
*/
-static void
+static void LIBXML_ATTR_FORMAT(5,0)
xmlSchemaPErr2(xmlSchemaParserCtxtPtr ctxt, xmlNodePtr node,
xmlNodePtr child, int error,
const char *msg, const xmlChar * str1, const xmlChar * str2)
@@ -1953,7 +1953,7 @@
*
* Handle a parser error
*/
-static void
+static void LIBXML_ATTR_FORMAT(7,0)
xmlSchemaPErrExt(xmlSchemaParserCtxtPtr ctxt, xmlNodePtr node, int error,
const xmlChar * strData1, const xmlChar * strData2,
const xmlChar * strData3, const char *msg, const xmlChar * str1,
@@ -2004,7 +2004,7 @@
extra);
}
-static void
+static void LIBXML_ATTR_FORMAT(2,0)
xmlSchemaPSimpleInternalErr(xmlNodePtr node,
const char *msg, const xmlChar *str)
{
@@ -2015,18 +2015,21 @@
#define WXS_ERROR_TYPE_ERROR 1
#define WXS_ERROR_TYPE_WARNING 2
/**
- * xmlSchemaErr3:
+ * xmlSchemaErr4Line:
* @ctxt: the validation context
- * @node: the context node
+ * @errorLevel: the error level
* @error: the error code
+ * @node: the context node
+ * @line: the line number
* @msg: the error message
* @str1: extra data
* @str2: extra data
* @str3: extra data
+ * @str4: extra data
*
* Handle a validation error
*/
-static void
+static void LIBXML_ATTR_FORMAT(6,0)
xmlSchemaErr4Line(xmlSchemaAbstractCtxtPtr ctxt,
xmlErrorLevel errorLevel,
int error, xmlNodePtr node, int line, const char *msg,
@@ -2141,7 +2144,7 @@
*
* Handle a validation error
*/
-static void
+static void LIBXML_ATTR_FORMAT(4,0)
xmlSchemaErr3(xmlSchemaAbstractCtxtPtr actxt,
int error, xmlNodePtr node, const char *msg,
const xmlChar *str1, const xmlChar *str2, const xmlChar *str3)
@@ -2150,7 +2153,7 @@
msg, str1, str2, str3, NULL);
}
-static void
+static void LIBXML_ATTR_FORMAT(4,0)
xmlSchemaErr4(xmlSchemaAbstractCtxtPtr actxt,
int error, xmlNodePtr node, const char *msg,
const xmlChar *str1, const xmlChar *str2,
@@ -2160,7 +2163,7 @@
msg, str1, str2, str3, str4);
}
-static void
+static void LIBXML_ATTR_FORMAT(4,0)
xmlSchemaErr(xmlSchemaAbstractCtxtPtr actxt,
int error, xmlNodePtr node, const char *msg,
const xmlChar *str1, const xmlChar *str2)
@@ -2183,7 +2186,7 @@
/*
* Don't try to format other nodes than element and
* attribute nodes.
- * Play save and return an empty string.
+ * Play safe and return an empty string.
*/
*msg = xmlStrdup(BAD_CAST "");
return(*msg);
@@ -2248,6 +2251,13 @@
TODO
return (NULL);
}
+
+ /*
+ * xmlSchemaFormatItemForReport() also returns an escaped format
+ * string, so do this before calling it below (in the future).
+ */
+ xmlEscapeFormatString(msg);
+
/*
* VAL TODO: The output of the given schema component is currently
* disabled.
@@ -2264,7 +2274,7 @@
return (*msg);
}
-static void
+static void LIBXML_ATTR_FORMAT(3,0)
xmlSchemaInternalErr2(xmlSchemaAbstractCtxtPtr actxt,
const char *funcName,
const char *message,
@@ -2275,24 +2285,21 @@
if (actxt == NULL)
return;
- msg = xmlStrdup(BAD_CAST "Internal error: ");
- msg = xmlStrcat(msg, BAD_CAST funcName);
- msg = xmlStrcat(msg, BAD_CAST ", ");
+ msg = xmlStrdup(BAD_CAST "Internal error: %s, ");
msg = xmlStrcat(msg, BAD_CAST message);
msg = xmlStrcat(msg, BAD_CAST ".\n");
if (actxt->type == XML_SCHEMA_CTXT_VALIDATOR)
- xmlSchemaErr(actxt, XML_SCHEMAV_INTERNAL, NULL,
- (const char *) msg, str1, str2);
-
+ xmlSchemaErr3(actxt, XML_SCHEMAV_INTERNAL, NULL,
+ (const char *) msg, (const xmlChar *) funcName, str1, str2);
else if (actxt->type == XML_SCHEMA_CTXT_PARSER)
- xmlSchemaErr(actxt, XML_SCHEMAP_INTERNAL, NULL,
- (const char *) msg, str1, str2);
+ xmlSchemaErr3(actxt, XML_SCHEMAP_INTERNAL, NULL,
+ (const char *) msg, (const xmlChar *) funcName, str1, str2);
FREE_AND_NULL(msg)
}
-static void
+static void LIBXML_ATTR_FORMAT(3,0)
xmlSchemaInternalErr(xmlSchemaAbstractCtxtPtr actxt,
const char *funcName,
const char *message)
@@ -2301,7 +2308,7 @@
}
#if 0
-static void
+static void LIBXML_ATTR_FORMAT(3,0)
xmlSchemaPInternalErr(xmlSchemaParserCtxtPtr pctxt,
const char *funcName,
const char *message,
@@ -2313,7 +2320,7 @@
}
#endif
-static void
+static void LIBXML_ATTR_FORMAT(5,0)
xmlSchemaCustomErr4(xmlSchemaAbstractCtxtPtr actxt,
xmlParserErrors error,
xmlNodePtr node,
@@ -2338,7 +2345,7 @@
FREE_AND_NULL(msg)
}
-static void
+static void LIBXML_ATTR_FORMAT(5,0)
xmlSchemaCustomErr(xmlSchemaAbstractCtxtPtr actxt,
xmlParserErrors error,
xmlNodePtr node,
@@ -2353,7 +2360,7 @@
-static void
+static void LIBXML_ATTR_FORMAT(5,0)
xmlSchemaCustomWarning(xmlSchemaAbstractCtxtPtr actxt,
xmlParserErrors error,
xmlNodePtr node,
@@ -2378,7 +2385,7 @@
-static void
+static void LIBXML_ATTR_FORMAT(5,0)
xmlSchemaKeyrefErr(xmlSchemaValidCtxtPtr vctxt,
xmlParserErrors error,
xmlSchemaPSVIIDCNodePtr idcNode,
@@ -2478,11 +2485,13 @@
msg = xmlStrcat(msg, BAD_CAST " '");
if (type->builtInType != 0) {
msg = xmlStrcat(msg, BAD_CAST "xs:");
- msg = xmlStrcat(msg, type->name);
- } else
- msg = xmlStrcat(msg,
- xmlSchemaFormatQName(&str,
- type->targetNamespace, type->name));
+ str = xmlStrdup(type->name);
+ } else {
+ const xmlChar *qName = xmlSchemaFormatQName(&str, type->targetNamespace, type->name);
+ if (!str)
+ str = xmlStrdup(qName);
+ }
+ msg = xmlStrcat(msg, xmlEscapeFormatString(&str));
msg = xmlStrcat(msg, BAD_CAST "'");
FREE_AND_NULL(str);
}
@@ -2527,7 +2536,7 @@
FREE_AND_NULL(msg)
}
-static void
+static void LIBXML_ATTR_FORMAT(5,0)
xmlSchemaComplexTypeErr(xmlSchemaAbstractCtxtPtr actxt,
xmlParserErrors error,
xmlNodePtr node,
@@ -2619,7 +2628,7 @@
str = xmlStrcat(str, BAD_CAST ", ");
}
str = xmlStrcat(str, BAD_CAST " ).\n");
- msg = xmlStrcat(msg, BAD_CAST str);
+ msg = xmlStrcat(msg, xmlEscapeFormatString(&str));
FREE_AND_NULL(str)
} else
msg = xmlStrcat(msg, BAD_CAST "\n");
@@ -2627,7 +2636,7 @@
xmlFree(msg);
}
-static void
+static void LIBXML_ATTR_FORMAT(8,0)
xmlSchemaFacetErr(xmlSchemaAbstractCtxtPtr actxt,
xmlParserErrors error,
xmlNodePtr node,
@@ -2918,7 +2927,7 @@
*
* Reports an error during parsing.
*/
-static void
+static void LIBXML_ATTR_FORMAT(5,0)
xmlSchemaPCustomErrExt(xmlSchemaParserCtxtPtr ctxt,
xmlParserErrors error,
xmlSchemaBasicItemPtr item,
@@ -2954,7 +2963,7 @@
*
* Reports an error during parsing.
*/
-static void
+static void LIBXML_ATTR_FORMAT(5,0)
xmlSchemaPCustomErr(xmlSchemaParserCtxtPtr ctxt,
xmlParserErrors error,
xmlSchemaBasicItemPtr item,
@@ -2979,7 +2988,7 @@
*
* Reports an attribute use error during parsing.
*/
-static void
+static void LIBXML_ATTR_FORMAT(6,0)
xmlSchemaPAttrUseErr4(xmlSchemaParserCtxtPtr ctxt,
xmlParserErrors error,
xmlNodePtr node,
@@ -3101,7 +3110,7 @@
* Reports a simple type validation error.
* TODO: Should this report the value of an element as well?
*/
-static void
+static void LIBXML_ATTR_FORMAT(8,0)
xmlSchemaPSimpleTypeErr(xmlSchemaParserCtxtPtr ctxt,
xmlParserErrors error,
xmlSchemaBasicItemPtr ownerItem ATTRIBUTE_UNUSED,
@@ -3143,11 +3152,13 @@
msg = xmlStrcat(msg, BAD_CAST " '");
if (type->builtInType != 0) {
msg = xmlStrcat(msg, BAD_CAST "xs:");
- msg = xmlStrcat(msg, type->name);
- } else
- msg = xmlStrcat(msg,
- xmlSchemaFormatQName(&str,
- type->targetNamespace, type->name));
+ str = xmlStrdup(type->name);
+ } else {
+ const xmlChar *qName = xmlSchemaFormatQName(&str, type->targetNamespace, type->name);
+ if (!str)
+ str = xmlStrdup(qName);
+ }
+ msg = xmlStrcat(msg, xmlEscapeFormatString(&str));
msg = xmlStrcat(msg, BAD_CAST "'.");
FREE_AND_NULL(str);
}
@@ -3160,7 +3171,9 @@
}
if (expected) {
msg = xmlStrcat(msg, BAD_CAST " Expected is '");
- msg = xmlStrcat(msg, BAD_CAST expected);
+ xmlChar *expectedEscaped = xmlCharStrdup(expected);
+ msg = xmlStrcat(msg, xmlEscapeFormatString(&expectedEscaped));
+ FREE_AND_NULL(expectedEscaped);
msg = xmlStrcat(msg, BAD_CAST "'.\n");
} else
msg = xmlStrcat(msg, BAD_CAST "\n");
diff --git a/src/third_party/libxml/src/xmlstring.c b/src/third_party/libxml/src/xmlstring.c
index 0a268fc..c0cbcb1 100644
--- a/src/third_party/libxml/src/xmlstring.c
+++ b/src/third_party/libxml/src/xmlstring.c
@@ -984,5 +984,60 @@
return(xmlUTF8Strndup(utf, len));
}
+/**
+ * xmlEscapeFormatString:
+ * @msg: a pointer to the string in which to escape '%' characters.
+ * Must be a heap-allocated buffer created by libxml2 that may be
+ * returned, or that may be freed and replaced.
+ *
+ * Replaces the string pointed to by 'msg' with an escaped string.
+ * Returns the same string with all '%' characters escaped.
+ */
+xmlChar *
+xmlEscapeFormatString(xmlChar **msg)
+{
+ xmlChar *msgPtr = NULL;
+ xmlChar *result = NULL;
+ xmlChar *resultPtr = NULL;
+ size_t count = 0;
+ size_t msgLen = 0;
+ size_t resultLen = 0;
+
+ if (!msg || !*msg)
+ return(NULL);
+
+ for (msgPtr = *msg; *msgPtr != '\0'; ++msgPtr) {
+ ++msgLen;
+ if (*msgPtr == '%')
+ ++count;
+ }
+
+ if (count == 0)
+ return(*msg);
+
+ resultLen = msgLen + count + 1;
+ result = (xmlChar *) xmlMallocAtomic(resultLen * sizeof(xmlChar));
+ if (result == NULL) {
+ /* Clear *msg to prevent format string vulnerabilities in
+ out-of-memory situations. */
+ xmlFree(*msg);
+ *msg = NULL;
+ xmlErrMemory(NULL, NULL);
+ return(NULL);
+ }
+
+ for (msgPtr = *msg, resultPtr = result; *msgPtr != '\0'; ++msgPtr, ++resultPtr) {
+ *resultPtr = *msgPtr;
+ if (*msgPtr == '%')
+ *(++resultPtr) = '%';
+ }
+ result[resultLen - 1] = '\0';
+
+ xmlFree(*msg);
+ *msg = result;
+
+ return *msg;
+}
+
#define bottom_xmlstring
#include "elfgcchack.h"