Import Cobalt 21.lts.2.289852
diff --git a/src/base/trace_event/trace_event_memory_overhead.h b/src/base/trace_event/trace_event_memory_overhead.h index 2687e93..7016f0f 100644 --- a/src/base/trace_event/trace_event_memory_overhead.h +++ b/src/base/trace_event/trace_event_memory_overhead.h
@@ -6,6 +6,7 @@ #define BASE_TRACE_EVENT_TRACE_EVENT_MEMORY_OVERHEAD_H_ #include <unordered_map> +#include <string> #include "base/base_export.h" #include "base/macros.h"
diff --git a/src/cobalt/CHANGELOG.md b/src/cobalt/CHANGELOG.md index f2872b4..644ecae 100644 --- a/src/cobalt/CHANGELOG.md +++ b/src/cobalt/CHANGELOG.md
@@ -119,6 +119,12 @@ the comment of `SbMediaCanPlayMimeAndKeySystem()` in `media.h` for more details. + - **Added support for controlling shutdown behavior of graphics system.** + + Cobalt normally clears the framebuffer to opaque black on suspend or exit. + This behavior can now be overridden by implementing the cobalt extension + function `CobaltExtensionGraphicsApi::ShouldClearFrameOnShutdown`. + ## Version 20 - **Support for QUIC and SPDY is now enabled.**
diff --git a/src/cobalt/bindings/run_cobalt_bindings_tests.py b/src/cobalt/bindings/run_cobalt_bindings_tests.py old mode 100644 new mode 100755 index a9e3be7..f852272 --- a/src/cobalt/bindings/run_cobalt_bindings_tests.py +++ b/src/cobalt/bindings/run_cobalt_bindings_tests.py
@@ -27,6 +27,7 @@ import sys import _env # pylint: disable=unused-import + from cobalt.bindings.idl_compiler_cobalt import IdlCompilerCobalt from cobalt.bindings.mozjs45.code_generator_mozjs45 import CodeGeneratorMozjs45 from cobalt.bindings.v8c.code_generator_v8c import CodeGeneratorV8c
diff --git a/src/cobalt/bindings/testing/date_bindings_test.cc b/src/cobalt/bindings/testing/date_bindings_test.cc index 602f2ee..bb58362 100644 --- a/src/cobalt/bindings/testing/date_bindings_test.cc +++ b/src/cobalt/bindings/testing/date_bindings_test.cc
@@ -14,8 +14,11 @@ #include "base/logging.h" +#include "base/time/time.h" #include "cobalt/bindings/testing/bindings_test_base.h" #include "cobalt/bindings/testing/interface_with_date.h" +#include "starboard/client_porting/eztime/eztime.h" +#include "starboard/time_zone.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -79,6 +82,45 @@ EXPECT_LT(std::abs(posix_now_ms - js_now_ms), 1000); } +TEST_F(DateBindingsTest, StarboardTimeZone) { + const char* month_table[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + std::string result; + + EvaluateScript("new Date().toString();", &result); + base::Time now = base::Time::Now(); + + SB_LOG(INFO) << "JavaScript Date now is : " << result; + SB_LOG(INFO) << "and base::Time is: " << now; + + base::Time::Exploded exploded; + now.LocalExplode(&exploded); + EXPECT_NE(result.find(std::to_string(exploded.year)), std::string::npos); + EXPECT_NE(result.find(month_table[exploded.month - 1]), std::string::npos); + EXPECT_NE(result.find(std::to_string(exploded.day_of_month)), + std::string::npos); + EXPECT_NE(result.find(std::to_string(exploded.hour)), std::string::npos); + EXPECT_NE(result.find(std::to_string(exploded.minute)), std::string::npos); + EXPECT_NE(result.find(std::to_string(exploded.second)), std::string::npos); +} + +TEST_F(DateBindingsTest, TimezoneOffset) { + EzTimeExploded ez_exploded; + + auto eztt = EzTimeTFromSbTime(SbTimeGetNow()); + EzTimeTExplodeLocal(&eztt, &ez_exploded); + // ez_exploded is already local time, use UTC method to convert to + // EzTimeT. + EzTimeT local_time_minutes = EzTimeTImplodeUTC(&ez_exploded) / 60; + EzTimeT utc_minutes = EzTimeTFromSbTime(SbTimeGetNow()) / 60; + EzTimeT timezone_offset = utc_minutes - local_time_minutes; + + std::string result; + EvaluateScript("new Date().getTimezoneOffset();", &result); + + EXPECT_EQ(result, std::to_string(utc_minutes - local_time_minutes)); +} + } // namespace } // namespace testing } // namespace bindings
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc index f5459ed..63e56d4 100644 --- a/src/cobalt/browser/application.cc +++ b/src/cobalt/browser/application.cc
@@ -18,6 +18,7 @@ #include "cobalt/browser/application.h" +#include <map> #include <memory> #include <string> #include <vector> @@ -255,6 +256,15 @@ return initial_url; } +bool ValidateSplashScreen(const base::Optional<GURL>& url) { + if (url->is_valid() && + (url->SchemeIsFile() || url->SchemeIs("h5vcc-embedded"))) { + return true; + } + LOG(FATAL) << "Ignoring invalid fallback splash screen: " << url->spec(); + return false; +} + base::Optional<GURL> GetFallbackSplashScreenURL() { base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); std::string fallback_splash_screen_string; @@ -270,15 +280,51 @@ } base::Optional<GURL> fallback_splash_screen_url = GURL(fallback_splash_screen_string); - if (!fallback_splash_screen_url->is_valid() || - !(fallback_splash_screen_url->SchemeIsFile() || - fallback_splash_screen_url->SchemeIs("h5vcc-embedded"))) { - LOG(FATAL) << "Ignoring invalid fallback splash screen: " - << fallback_splash_screen_string; - } + ValidateSplashScreen(fallback_splash_screen_url); return fallback_splash_screen_url; } +// Parses the fallback_splash_screen_topics command line parameter +// and maps topics to full file url locations, if valid. +void ParseFallbackSplashScreenTopics( + const base::Optional<GURL>& default_fallback_splash_screen_url, + std::map<std::string, GURL>* fallback_splash_screen_topic_map) { + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + std::string topics; + if (command_line->HasSwitch(switches::kFallbackSplashScreenTopics)) { + topics = command_line->GetSwitchValueASCII( + switches::kFallbackSplashScreenTopics); + } else { + topics = configuration::Configuration::GetInstance() + ->CobaltFallbackSplashScreenTopics(); + } + + // Note: values in topics_map may be either file paths or filenames. + std::map<std::string, std::string> topics_map; + BrowserModule::GetParamMap(topics, topics_map); + for (auto iterator = topics_map.begin(); iterator != topics_map.end(); + iterator++) { + std::string topic = iterator->first; + std::string location = iterator->second; + base::Optional<GURL> topic_fallback_url = GURL(location); + + // If not a valid url, check whether it is a valid filename in the + // same directory as the default fallback url. + if (!topic_fallback_url->is_valid()) { + if (default_fallback_splash_screen_url) { + topic_fallback_url = GURL( + default_fallback_splash_screen_url->GetWithoutFilename().spec() + + location); + } else { + break; + } + } + if (ValidateSplashScreen(topic_fallback_url)) { + (*fallback_splash_screen_topic_map)[topic] = topic_fallback_url.value(); + } + } +} + base::TimeDelta GetTimedTraceDuration() { #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); @@ -579,6 +625,9 @@ options.build_auto_mem_settings = memory_settings::GetDefaultBuildSettings(); options.fallback_splash_screen_url = fallback_splash_screen_url; + ParseFallbackSplashScreenTopics(fallback_splash_screen_url, + &options.fallback_splash_screen_topic_map); + if (command_line->HasSwitch(browser::switches::kFPSPrint)) { options.renderer_module_options.enable_fps_stdout = true; }
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc index 383de3e..e143407 100644 --- a/src/cobalt/browser/browser_module.cc +++ b/src/cobalt/browser/browser_module.cc
@@ -15,6 +15,7 @@ #include "cobalt/browser/browser_module.h" #include <algorithm> +#include <map> #include <memory> #include <vector> @@ -31,6 +32,7 @@ #include "base/time/time.h" #include "base/trace_event/trace_event.h" #include "cobalt/base/cobalt_paths.h" +#include "cobalt/base/init_cobalt.h" #include "cobalt/base/source_location.h" #include "cobalt/base/tokens.h" #include "cobalt/browser/on_screen_keyboard_starboard_bridge.h" @@ -452,6 +454,7 @@ base::Unretained(this)))); } + // Set the fallback splash screen url to the default fallback url. fallback_splash_screen_url_ = options.fallback_splash_screen_url; // Synchronously construct our WebModule object. @@ -562,15 +565,17 @@ DestroySplashScreen(base::TimeDelta()); if (options_.enable_splash_screen_on_reloads || main_web_module_generation_ == 1) { - base::Optional<std::string> key = SplashScreenCache::GetKeyForStartUrl(url); + base::Optional<std::string> topic = SetSplashScreenTopicFallback(url); + splash_screen_cache_->SetUrl(url, topic); + if (fallback_splash_screen_url_ || - (key && splash_screen_cache_->IsSplashScreenCached(*key))) { + splash_screen_cache_->IsSplashScreenCached()) { splash_screen_.reset(new SplashScreen( application_state_, base::Bind(&BrowserModule::QueueOnSplashScreenRenderTreeProduced, base::Unretained(this)), network_module_, viewport_size, GetResourceProvider(), - kLayoutMaxRefreshFrequencyInHz, fallback_splash_screen_url_, url, + kLayoutMaxRefreshFrequencyInHz, fallback_splash_screen_url_, splash_screen_cache_.get(), base::Bind(&BrowserModule::DestroySplashScreen, weak_this_))); lifecycle_observers_.AddObserver(splash_screen_.get()); @@ -1893,5 +1898,64 @@ return system_window_->GetSbWindow(); } +base::Optional<std::string> BrowserModule::SetSplashScreenTopicFallback( + const GURL& url) { + std::map<std::string, std::string> url_param_map; + // If this is the initial startup, use topic within deeplink, if specified. + if (main_web_module_generation_ == 1) { + GetParamMap(GetInitialDeepLink(), url_param_map); + } + // If this is not the initial startup, there was no deeplink specified, or + // the deeplink did not have a topic, check the current url for a topic. + if (url_param_map["topic"].empty()) { + GetParamMap(url.query(), url_param_map); + } + std::string splash_topic = url_param_map["topic"]; + // If a topic was found, check whether a fallback url was specified. + if (!splash_topic.empty()) { + GURL splash_url = options_.fallback_splash_screen_topic_map[splash_topic]; + if (!splash_url.spec().empty()) { + // Update fallback splash screen url to topic-specific URL. + fallback_splash_screen_url_ = splash_url; + } + return base::Optional<std::string>(splash_topic); + } + return base::Optional<std::string>(); +} + +void BrowserModule::GetParamMap(const std::string& url, + std::map<std::string, std::string>& map) { + bool next_is_option = true; + bool next_is_value = false; + std::string option = ""; + base::StringTokenizer tokenizer(url, "&="); + tokenizer.set_options(base::StringTokenizer::RETURN_DELIMS); + + while (tokenizer.GetNext()) { + if (tokenizer.token_is_delim()) { + switch (*tokenizer.token_begin()) { + case '&': + next_is_option = true; + break; + case '=': + next_is_value = true; + break; + } + } else { + std::string token = tokenizer.token(); + if (next_is_value && !option.empty()) { + // Overwrite previous value when an option appears more than once. + map[option] = token; + } + option = ""; + if (next_is_option) { + option = token; + } + next_is_option = false; + next_is_value = false; + } + } +} + } // namespace browser } // namespace cobalt
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h index 15e0b94..f5bce8f 100644 --- a/src/cobalt/browser/browser_module.h +++ b/src/cobalt/browser/browser_module.h
@@ -15,6 +15,7 @@ #ifndef COBALT_BROWSER_BROWSER_MODULE_H_ #define COBALT_BROWSER_BROWSER_MODULE_H_ +#include <map> #include <memory> #include <string> #include <vector> @@ -104,6 +105,7 @@ memory_settings::AutoMemSettings command_line_auto_mem_settings; memory_settings::AutoMemSettings build_auto_mem_settings; base::Optional<GURL> fallback_splash_screen_url; + std::map<std::string, GURL> fallback_splash_screen_topic_map; base::Optional<cssom::ViewportSize> requested_viewport_size; bool enable_splash_screen_on_reloads; bool enable_on_screen_keyboard = true; @@ -215,6 +217,11 @@ bool IsWebModuleLoaded() { return web_module_loaded_.IsSignaled(); } + // Parses url and defines a mapping of parameter values of the form + // &option=value&foo=bar. + static void GetParamMap(const std::string& url, + std::map<std::string, std::string>& map); + private: #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT) static void CoreDumpHandler(void* browser_module_as_void); @@ -431,6 +438,10 @@ // applied according to the current time. scoped_refptr<render_tree::Node> GetLastSubmissionAnimated(); + // Sets the fallback splash screen url to a topic-specific URL, if applicable. + // Returns the topic used, or an empty Optional if a topic isn't found. + base::Optional<std::string> SetSplashScreenTopicFallback(const GURL& url); + // TODO: // WeakPtr usage here can be avoided if BrowserModule has a thread to // own where it can ensure that its tasks are all resolved when it is
diff --git a/src/cobalt/browser/splash_screen.cc b/src/cobalt/browser/splash_screen.cc index 962142f..5521bec 100644 --- a/src/cobalt/browser/splash_screen.cc +++ b/src/cobalt/browser/splash_screen.cc
@@ -57,7 +57,6 @@ const cssom::ViewportSize& window_dimensions, render_tree::ResourceProvider* resource_provider, float layout_refresh_rate, const base::Optional<GURL>& fallback_splash_screen_url, - const GURL& initial_main_web_module_url, SplashScreenCache* splash_screen_cache, const base::Callback<void(base::TimeDelta)>& on_splash_screen_shutdown_complete) @@ -76,15 +75,10 @@ base::ThreadPriority::HIGHEST; base::Optional<GURL> url_to_pass = fallback_splash_screen_url; - // Use the cached URL rather than the passed in URL if it exists. - base::Optional<std::string> key = - SplashScreenCache::GetKeyForStartUrl(initial_main_web_module_url); DCHECK(fallback_splash_screen_url || - (key && splash_screen_cache && - splash_screen_cache->IsSplashScreenCached(*key))); - if (key && splash_screen_cache && - splash_screen_cache->IsSplashScreenCached(*key)) { - url_to_pass = GURL(loader::kCacheScheme + ("://" + *key)); + (splash_screen_cache && splash_screen_cache->IsSplashScreenCached())); + if (splash_screen_cache && splash_screen_cache->IsSplashScreenCached()) { + url_to_pass = splash_screen_cache->GetCachedSplashScreenUrl(); web_module_options.can_fetch_cache = true; web_module_options.splash_screen_cache = splash_screen_cache; }
diff --git a/src/cobalt/browser/splash_screen.h b/src/cobalt/browser/splash_screen.h index 853c584..5cce8e8 100644 --- a/src/cobalt/browser/splash_screen.h +++ b/src/cobalt/browser/splash_screen.h
@@ -42,7 +42,6 @@ render_tree::ResourceProvider* resource_provider, float layout_refresh_rate, const base::Optional<GURL>& fallback_splash_screen_url, - const GURL& initial_main_web_module_url, cobalt::browser::SplashScreenCache* splash_screen_cache, const base::Callback<void(base::TimeDelta)>& on_splash_screen_shutdown_complete);
diff --git a/src/cobalt/browser/splash_screen_cache.cc b/src/cobalt/browser/splash_screen_cache.cc index 43cb6f7..7631ae3 100644 --- a/src/cobalt/browser/splash_screen_cache.cc +++ b/src/cobalt/browser/splash_screen_cache.cc
@@ -18,6 +18,7 @@ #include <string> #include <vector> +#include "base/base64.h" #include "base/hash.h" #include "base/optional.h" #include "base/strings/string_util.h" @@ -59,10 +60,15 @@ base::AutoLock lock(lock_); } -bool SplashScreenCache::CacheSplashScreen(const std::string& key, - const std::string& content) const { +bool SplashScreenCache::CacheSplashScreen( + const std::string& content, + const base::Optional<std::string>& topic) const { base::AutoLock lock(lock_); - if (key.empty()) { + // Cache the content so that it's retrievable for the topic specified in the + // rel attribute. This topic may or may not match the topic-specified for this + // particular startup, tracked with "topic_". + base::Optional<std::string> key = GetKeyForStartConfig(url_, topic); + if (!key) { return false; } @@ -76,10 +82,11 @@ kSbFileMaxPath)) { return false; } - if (!CreateDirsForKey(key)) { + if (!CreateDirsForKey(key.value())) { return false; } - std::string full_path = std::string(path.data()) + kSbFileSepString + key; + std::string full_path = + std::string(path.data()) + kSbFileSepString + key.value(); starboard::ScopedFile cache_file( full_path.c_str(), kSbFileCreateAlways | kSbFileWrite, NULL, NULL); @@ -87,15 +94,18 @@ static_cast<int>(content.size())) > 0; } -bool SplashScreenCache::IsSplashScreenCached(const std::string& key) const { +bool SplashScreenCache::IsSplashScreenCached() const { base::AutoLock lock(lock_); std::vector<char> path(kSbFileMaxPath, 0); if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path.data(), kSbFileMaxPath)) { return false; } - std::string full_path = std::string(path.data()) + kSbFileSepString + key; - return !key.empty() && SbFileExists(full_path.c_str()); + base::Optional<std::string> key = GetKeyForStartConfig(url_, topic_); + if (!key) return false; + std::string full_path = + std::string(path.data()) + kSbFileSepString + key.value(); + return SbFileExists(full_path.c_str()); } int SplashScreenCache::ReadCachedSplashScreen( @@ -126,9 +136,8 @@ return result_size; } -// static -base::Optional<std::string> SplashScreenCache::GetKeyForStartUrl( - const GURL& url) { +base::Optional<std::string> SplashScreenCache::GetKeyForStartConfig( + const GURL& url, const base::Optional<std::string>& topic) const { base::Optional<std::string> encoded_url = base::GetApplicationKey(url); if (!encoded_url) { return base::nullopt; @@ -142,26 +151,36 @@ } std::string subpath = ""; - std::string subcomponent = kSbFileSepString + std::string("splash_screen"); - if (SbStringConcat(path.data(), subcomponent.c_str(), kSbFileMaxPath) >= - static_cast<int>(kSbFileMaxPath)) { + if (!AddPathDirectory(std::string("splash_screen"), path, subpath)) { return base::nullopt; } - subpath += "splash_screen"; - subcomponent = kSbFileSepString + *encoded_url; - if (SbStringConcat(path.data(), subcomponent.c_str(), kSbFileMaxPath) >= - static_cast<int>(kSbFileMaxPath)) { + if (!AddPathDirectory(*encoded_url, path, subpath)) { return base::nullopt; } - subpath += subcomponent; - subcomponent = kSbFileSepString + std::string("splash.html"); - if (SbStringConcat(path.data(), subcomponent.c_str(), kSbFileMaxPath) > - static_cast<int>(kSbFileMaxPath)) { + if (topic && !topic.value().empty()) { + std::string encoded_topic; + base::Base64Encode(topic.value(), &encoded_topic); + if (!AddPathDirectory(encoded_topic, path, subpath)) { + return base::nullopt; + } + } + if (!AddPathDirectory(std::string("splash.html"), path, subpath)) { return base::nullopt; } - subpath += subcomponent; - return subpath; + return subpath.erase(0, 1); // Remove leading separator +} + +bool SplashScreenCache::AddPathDirectory(const std::string& directory, + std::vector<char>& path, + std::string& subpath) const { + std::string subcomponent = kSbFileSepString + directory; + if (SbStringConcat(path.data(), subcomponent.c_str(), kSbFileMaxPath) >= + static_cast<int>(kSbFileMaxPath)) { + return false; + } + subpath += subcomponent; + return true; } } // namespace browser
diff --git a/src/cobalt/browser/splash_screen_cache.h b/src/cobalt/browser/splash_screen_cache.h index deacfc8..b10e8cd 100644 --- a/src/cobalt/browser/splash_screen_cache.h +++ b/src/cobalt/browser/splash_screen_cache.h
@@ -17,9 +17,11 @@ #include <memory> #include <string> +#include <vector> #include "base/optional.h" #include "base/synchronization/lock.h" +#include "cobalt/loader/cache_fetcher.h" #include "url/gurl.h" namespace cobalt { @@ -36,25 +38,46 @@ SplashScreenCache(); // Cache the splash screen. - bool CacheSplashScreen(const std::string& key, - const std::string& content) const; + bool CacheSplashScreen(const std::string& content, + const base::Optional<std::string>& topic) const; // Read the cached the splash screen. int ReadCachedSplashScreen(const std::string& key, std::unique_ptr<char[]>* result) const; - // Determine if a splash screen is cached corresponding to the key. - bool IsSplashScreenCached(const std::string& key) const; + // Determine if a splash screen is cached corresponding to the current url. + bool IsSplashScreenCached() const; - // Get the key that corresponds to a starting URL. Optionally create - // subdirectories along the path. - static base::Optional<std::string> GetKeyForStartUrl(const GURL& url); + // Set the URL of the currently requested splash screen. + void SetUrl(const GURL& url, const base::Optional<std::string>& topic) { + url_ = url; + topic_ = topic; + } + + // Get the cache location of the currently requested splash screen. + GURL GetCachedSplashScreenUrl() { + base::Optional<std::string> key = GetKeyForStartConfig(url_, topic_); + return GURL(loader::kCacheScheme + ("://" + *key)); + } private: + // Get the key that corresponds to the starting URL and (optional) topic. + base::Optional<std::string> GetKeyForStartConfig( + const GURL& url, const base::Optional<std::string>& topic) const; + + // Adds the directory to the path and subpath if the new path does not exceed + // maximum length. Returns true if successful. + bool AddPathDirectory(const std::string& directory, std::vector<char>& path, + std::string& subpath) const; + // Lock to protect access to the cache file. mutable base::Lock lock_; // Hash of the last read page contents. mutable uint32_t last_page_hash_; + // Latest url that was navigated to. + GURL url_; + // Splash topic associated with startup. + base::Optional<std::string> topic_; }; } // namespace browser
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc index 915b762..64653d5 100644 --- a/src/cobalt/browser/switches.cc +++ b/src/cobalt/browser/switches.cc
@@ -390,6 +390,17 @@ "no value is set, the URL in gyp_configuration.gypi or base.gypi will be " "used."; +const char kFallbackSplashScreenTopics[] = "fallback_splash_screen_topics"; +const char kFallbackSplashScreenTopicsHelp[] = + "Setting this switch defines a mapping of URL 'topics' to splash screen " + "URLs or filenames that Cobalt will use in the absence of a web cache, " + "(for example, music=music_splash_screen.html&foo=file:///bar.html). If a " + "URL is given it should match the format of 'fallback_splash_screen_url'. " + "A given filename should exist in the same directory as " + "'fallback_splash_screen_url'. If no fallback url exists for the topic of " + "the URL used to launch Cobalt, then the value of " + "'fallback_splash_screen_url' will be used."; + const char kVersion[] = "version"; const char kVersionHelp[] = "Prints the current version of Cobalt";
diff --git a/src/cobalt/browser/switches.h b/src/cobalt/browser/switches.h index f70c0f8..67c03f7 100644 --- a/src/cobalt/browser/switches.h +++ b/src/cobalt/browser/switches.h
@@ -151,6 +151,8 @@ extern const char kSoftwareSurfaceCacheSizeInBytesHelp[]; extern const char kFallbackSplashScreenURL[]; extern const char kFallbackSplashScreenURLHelp[]; +extern const char kFallbackSplashScreenTopics[]; +extern const char kFallbackSplashScreenTopicsHelp[]; extern const char kVersion[]; extern const char kVersionHelp[]; extern const char kViewport[];
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc index 1923513..7d0a353 100644 --- a/src/cobalt/browser/web_module.cc +++ b/src/cobalt/browser/web_module.cc
@@ -86,23 +86,20 @@ // deeper than this could be discarded, and will not be rendered. const int kDOMMaxElementDepth = 32; -bool CacheUrlContent(SplashScreenCache* splash_screen_cache, const GURL& url, - const std::string& content) { - base::Optional<std::string> key = SplashScreenCache::GetKeyForStartUrl(url); - if (key) { - return splash_screen_cache->SplashScreenCache::CacheSplashScreen(*key, - content); - } - return false; +void CacheUrlContent(SplashScreenCache* splash_screen_cache, + const std::string& content, + const base::Optional<std::string>& topic) { + splash_screen_cache->SplashScreenCache::CacheSplashScreen(content, topic); } -base::Callback<bool(const GURL&, const std::string&)> CacheUrlContentCallback( - SplashScreenCache* splash_screen_cache) { +base::Callback<void(const std::string&, const base::Optional<std::string>&)> +CacheUrlContentCallback(SplashScreenCache* splash_screen_cache) { // This callback takes in first the url, then the content string. if (splash_screen_cache) { return base::Bind(CacheUrlContent, base::Unretained(splash_screen_cache)); } else { - return base::Callback<bool(const GURL&, const std::string&)>(); + return base::Callback<void(const std::string&, + const base::Optional<std::string>&)>(); } }
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id index f24700a..e9bfa85 100644 --- a/src/cobalt/build/build.id +++ b/src/cobalt/build/build.id
@@ -1 +1 @@ -283720 \ No newline at end of file +289852 \ No newline at end of file
diff --git a/src/cobalt/build/cobalt_configuration.py b/src/cobalt/build/cobalt_configuration.py index 691c283..4815b88 100644 --- a/src/cobalt/build/cobalt_configuration.py +++ b/src/cobalt/build/cobalt_configuration.py
@@ -38,8 +38,17 @@ application_directory) def GetVariables(self, config_name): + + # Use env var to optimize build speed on CI + try: + # Force to int, so it's easy to pass in an override. + use_fastbuild = int(os.environ.get('IS_CI', 0)) + except (ValueError, TypeError): + use_fastbuild = 0 + variables = { - 'cobalt_fastbuild': os.environ.get('LB_FASTBUILD', 0), + # This is used to omit large debuginfo in files on CI environment + 'cobalt_fastbuild': use_fastbuild, # This is here rather than cobalt_configuration.gypi so that it's # available for browser_bindings_gen.gyp. @@ -114,7 +123,15 @@ # XMLHttpRequest: send() - Redirects (basics) (307). # Disabled because of: Flaky. - 'xhr/WebPlatformTest.Run/XMLHttpRequest_send_redirect_htm' + 'xhr/WebPlatformTest.Run/XMLHttpRequest_send_redirect_htm', + + # Disabled because of: Flaky on buildbot across multiple buildconfigs. + # Non-reproducible with local runs. + ('xhr/WebPlatformTest.Run/' + 'XMLHttpRequest_send_entity_body_get_head_async_htm'), + 'xhr/WebPlatformTest.Run/XMLHttpRequest_status_error_htm', + 'xhr/WebPlatformTest.Run/XMLHttpRequest_response_json_htm', + 'xhr/WebPlatformTest.Run/XMLHttpRequest_send_redirect_to_non_cors_htm', ] return filters
diff --git a/src/cobalt/build/gyp_utils.py b/src/cobalt/build/gyp_utils.py index cabcbe1..986d81c 100644 --- a/src/cobalt/build/gyp_utils.py +++ b/src/cobalt/build/gyp_utils.py
@@ -25,7 +25,7 @@ import _env # pylint: disable=unused-import from cobalt.tools import paths - +_REVINFO_KEY = 'cobalt_src' _VERSION_SERVER_URL = 'https://carbon-airlock-95823.appspot.com/build_version/generate' # pylint:disable=line-too-long _XSSI_PREFIX = ")]}'\n" @@ -34,32 +34,29 @@ def GetRevinfo(): - """Get absolute state of all git repos from gclient DEPS.""" + """Get absolute state of all git repos.""" + + git_get_remote_args = ['config', '--get', 'remote.origin.url'] + git_get_revision_args = ['rev-parse', 'HEAD'] try: - revinfo_cmd = ['gclient', 'revinfo', '-a'] - - if sys.platform.startswith('linux') or sys.platform == 'darwin': - use_shell = False - else: - # Windows needs shell to find gclient in the PATH. - use_shell = True - output = subprocess.check_output(revinfo_cmd, shell=use_shell) - revinfo = {} - lines = output.splitlines() - for line in lines: - repo, url = line.split(':', 1) - repo = repo.strip().replace('\\', '/') - url = url.strip() - revinfo[repo] = url - return revinfo - except (subprocess.CalledProcessError, ValueError) as e: - logging.warning('Failed to get revision information: %s', e) + cobalt_remote = subprocess.check_output(['git'] + + git_get_remote_args).strip() + cobalt_rev = subprocess.check_output(['git'] + + git_get_revision_args).strip() + return {_REVINFO_KEY: '{}@{}'.format(cobalt_remote, cobalt_rev)} + except subprocess.CalledProcessError: + logging.info( + 'Failed to get revision information. Trying again in src/ directory...') try: - logging.warning('Command output was: %s', line) - except NameError: - pass - return {} + cobalt_remote = subprocess.check_output(['git', '-C', 'src'] + + git_get_remote_args).strip() + cobalt_rev = subprocess.check_output(['git', '-C', 'src'] + + git_get_revision_args).strip() + return {_REVINFO_KEY: '{}@{}'.format(cobalt_remote, cobalt_rev)} + except subprocess.CalledProcessError as e: + logging.warning('Failed to get revision information: %s', e) + return {} def GetBuildNumber(version_server=_VERSION_SERVER_URL): @@ -119,10 +116,10 @@ match = search_re.search(f.read()) if not match: - logging.critical('Could not query constant value. The expression ' - 'should only have numbers, operators, spaces, and ' - 'parens. Please check "%s" in %s.\n', constant_name, - file_path) + logging.critical( + 'Could not query constant value. The expression ' + 'should only have numbers, operators, spaces, and ' + 'parens. Please check "%s" in %s.\n', constant_name, file_path) sys.exit(1) expression = match.group(1)
diff --git a/src/cobalt/configuration/configuration.cc b/src/cobalt/configuration/configuration.cc index 9c5087c..a5e9aad 100644 --- a/src/cobalt/configuration/configuration.cc +++ b/src/cobalt/configuration/configuration.cc
@@ -155,6 +155,13 @@ #endif } +const char* Configuration::CobaltFallbackSplashScreenTopics() { + if (configuration_api_ && configuration_api_->version >= 2) { + return configuration_api_->CobaltFallbackSplashScreenTopics(); + } + return ""; +} + bool Configuration::CobaltEnableQuic() { if (configuration_api_) { #if defined(COBALT_ENABLE_QUIC)
diff --git a/src/cobalt/configuration/configuration.h b/src/cobalt/configuration/configuration.h index 6ea8ba5..6913ea1 100644 --- a/src/cobalt/configuration/configuration.h +++ b/src/cobalt/configuration/configuration.h
@@ -59,6 +59,7 @@ bool CobaltGcZeal(); const char* CobaltRasterizerType(); bool CobaltEnableJit(); + const char* CobaltFallbackSplashScreenTopics(); private: Configuration();
diff --git a/src/cobalt/doc/splash_screen.md b/src/cobalt/doc/splash_screen.md index 9122555..fbb6b6d 100644 --- a/src/cobalt/doc/splash_screen.md +++ b/src/cobalt/doc/splash_screen.md
@@ -49,15 +49,15 @@ first time. 3. **Build-time fallback splash screen:** If a web cached splash screen is - unavailable and command line parameters are not passed by the system, a - `gyp_configuration.gypi` fallback splash screen may be used. Porters should - set the gypi variable `fallback_splash_screen_url` to the splash screen - URL. + unavailable and command line parameters are not passed by the system, + a CobaltExtensionConfigurationApi fallback splash screen may be used. + Porters should set the `CobaltFallbackSplashScreenUrl` value in + `configuration.cc` to the splash screen URL. - 4. **Default splash screen:** If no web cached splash screen is - available, and command line and `gyp_configuration.gypi` fallbacks are not - set, a default splash screen will be used. This is set in `base.gypi` via - `fallback_splash_screen_url%` to refer to a black splash screen. + 4. **Default splash screen:** If no web cached splash screen is available, and + command line and CobaltExtensionConfigurationApi fallbacks are not set, a + default splash screen will be used. This is set in + `configuration_defaults.cc` to refer to a black splash screen. ## Web-updatability @@ -85,6 +85,40 @@ Cobalt will also need to read the cached splash screen from the cache directory when starting up. +## Topic-specific splash screens + +It is possible to specify multiple splash screens for a given Cobalt-based +application, using a start-up 'topic' to select between the available splash +screens. This can be useful when an application has multiple entry points that +require different splash screens. The topic may be specified in the start-up url +or deeplink as a query parameter. For example, +`https://www.example.com/path?topic=foo`. If a splash-screen has been specified +for topic 'foo', it will be used. Otherwise, the topic is ignored. Topic values +should be URL encoded and limited to alphanumeric characters, hyphens, +underscores, and percent signs. + +There are three ways to specify topic-specific splash screens. These methods mirror +the types of splash screens listed above, and unless specified, the rules here +are the same as for non-topic-based splash screens. + + 1. **Web cached splash screen:** A custom `rel="<topic>_splashscreen"` + attribute on a link element is used to specify a topic-specific splash + screen. There can be any number of these elements with different topics, in + addition to the topic-neutral `rel="splashscreen"`. + + 2. **Command line fallback splash screen:** The command line argument + `--fallback_splash_screen_topics` can be used if the cache is unavailable. + The argument accepts a list of topic/file parameters. If a file is not a + valid URL path, then it will be used as a filename at the path specified by + `--fallback_splash_screen_url`. For example, + `foo_topic=file:///foo.html&bar=bar.html`. + + 3. **Build-time fallback splash screen:** If a web cached splash screen is + unavailable and command line parameters are not passed by the system, a + CobaltExtensionConfigurationApi fallback splash screen may be used. Porters + should set the `CobaltFallbackSplashScreenTopics` value in + `configuration.cc` and this value should look like the command line option. + ## Application-specific splash screens On systems that plan to support multiple Cobalt-based applications, an @@ -94,9 +128,8 @@ the Cobalt binary must be handled by the system. Alternatively, an application developer may use the default black splash screen -specified in base.gypi whenever a cached splash screen is not available and rely -on the web application to specify an application-specific cached splash screen -otherwise. +whenever a cached splash screen is not available and rely on the web application +to specify an application-specific cached splash screen otherwise. ## Provided embedded resource splash screens For convenience, we currently provide the following splash screens as embedded
diff --git a/src/cobalt/dom/dom_test.gyp b/src/cobalt/dom/dom_test.gyp index f0b181f..a2a464e 100644 --- a/src/cobalt/dom/dom_test.gyp +++ b/src/cobalt/dom/dom_test.gyp
@@ -44,6 +44,7 @@ 'font_cache_test.cc', 'html_element_factory_test.cc', 'html_element_test.cc', + 'html_link_element_test.cc', 'intersection_observer_test.cc', 'keyboard_event_test.cc', 'local_storage_database_test.cc',
diff --git a/src/cobalt/dom/html_link_element.cc b/src/cobalt/dom/html_link_element.cc index 8244909..b7150de 100644 --- a/src/cobalt/dom/html_link_element.cc +++ b/src/cobalt/dom/html_link_element.cc
@@ -20,6 +20,7 @@ #include <vector> #include "base/bind.h" +#include "base/strings/string_tokenizer.h" #include "base/trace_event/trace_event.h" #include "cobalt/cssom/css_parser.h" #include "cobalt/cssom/css_style_sheet.h" @@ -34,10 +35,34 @@ namespace dom { namespace { +bool IsValidRelChar(char const& c) { + return (isalnum(c) || c == '_' || c == '\\' || c == '-'); +} + +bool IsValidSplashScreenFormat(const std::string& rel) { + base::StringTokenizer tokenizer(rel, "_"); + tokenizer.set_options(base::StringTokenizer::RETURN_DELIMS); + bool is_valid_format = true; + while (tokenizer.GetNext()) { + std::string token = tokenizer.token(); + if (SbStringCompareAll(token.c_str(), "splashscreen") == 0) { + is_valid_format = true; + } else { + for (char const& c : token) { + if (!IsValidRelChar(c)) { + return false; + } + } + is_valid_format = false; + } + } + return is_valid_format; +} + CspDelegate::ResourceType GetCspResourceTypeForRel(const std::string& rel) { if (rel == "stylesheet") { return CspDelegate::kStyle; - } else if (rel == "splashscreen") { + } else if (IsValidSplashScreenFormat(rel)) { return CspDelegate::kLocation; } else { NOTIMPLEMENTED(); @@ -71,13 +96,15 @@ const char HTMLLinkElement::kTagName[] = "link"; // static const std::vector<std::string> HTMLLinkElement::kSupportedRelValues = { - "stylesheet", "splashscreen"}; + "stylesheet"}; void HTMLLinkElement::OnInsertedIntoDocument() { HTMLElement::OnInsertedIntoDocument(); if (std::find(kSupportedRelValues.begin(), kSupportedRelValues.end(), rel()) != kSupportedRelValues.end()) { Obtain(); + } else if (IsValidSplashScreenFormat(rel())) { + Obtain(); } else { LOG(WARNING) << "<link> has unsupported rel value: " << rel() << "."; } @@ -202,7 +229,7 @@ Document* document = node_document(); if (rel() == "stylesheet") { OnStylesheetLoaded(document, *content); - } else if (rel() == "splashscreen") { + } else if (IsValidSplashScreenFormat(rel())) { OnSplashscreenLoaded(document, *content); } else { NOTIMPLEMENTED(); @@ -257,7 +284,13 @@ void HTMLLinkElement::OnSplashscreenLoaded(Document* document, const std::string& content) { scoped_refptr<Window> window = document->window(); - window->CacheSplashScreen(content); + std::string link = rel(); + size_t last_underscore = link.find_last_of("_"); + base::Optional<std::string> topic; + if (last_underscore != std::string::npos) { + topic = link.substr(0, last_underscore); + } + window->CacheSplashScreen(content, topic); } void HTMLLinkElement::OnStylesheetLoaded(Document* document,
diff --git a/src/cobalt/dom/html_link_element.h b/src/cobalt/dom/html_link_element.h index ba4ee01..e38abea 100644 --- a/src/cobalt/dom/html_link_element.h +++ b/src/cobalt/dom/html_link_element.h
@@ -67,13 +67,14 @@ DEFINE_WRAPPABLE_TYPE(HTMLLinkElement); - private: + protected: ~HTMLLinkElement() override {} + private: void ResolveAndSetAbsoluteURL(); // From the spec: HTMLLinkElement. - void Obtain(); + virtual void Obtain(); void OnContentProduced(const loader::Origin& last_url_origin, std::unique_ptr<std::string> content);
diff --git a/src/cobalt/dom/html_link_element_test.cc b/src/cobalt/dom/html_link_element_test.cc new file mode 100644 index 0000000..380711e --- /dev/null +++ b/src/cobalt/dom/html_link_element_test.cc
@@ -0,0 +1,114 @@ +// Copyright 2020 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cobalt/dom/html_link_element.h" +#include "base/message_loop/message_loop.h" +#include "cobalt/cssom/testing/mock_css_parser.h" +#include "cobalt/dom/document.h" +#include "cobalt/dom/dom_stat_tracker.h" +#include "cobalt/dom/testing/stub_environment_settings.h" +#include "cobalt/dom/window.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::NiceMock; + +namespace cobalt { +namespace dom { + +class HTMLLinkElementMock : public HTMLLinkElement { + public: + explicit HTMLLinkElementMock(Document* document) + : HTMLLinkElement(document) {} + void Obtain() { obtained_ = true; } + bool obtained_ = false; + bool obtained() { return obtained_; } +}; + +class DocumentMock : public Document { + public: + explicit DocumentMock(HTMLElementContext* context) : Document(context) {} + scoped_refptr<HTMLLinkElementMock> CreateElement( + const std::string& local_name) { + return scoped_refptr<HTMLLinkElementMock>(new HTMLLinkElementMock(this)); + } +}; + +class HtmlLinkElementTest : public ::testing::Test { + protected: + HtmlLinkElementTest() + : dom_stat_tracker_(new DomStatTracker("HtmlLinkElementTest")), + html_element_context_(&environment_settings_, NULL, NULL, &css_parser_, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, dom_stat_tracker_.get(), + "", base::kApplicationStateStarted, NULL), + document_(new DocumentMock(&html_element_context_)), + message_loop_(base::MessageLoop::TYPE_DEFAULT) {} + + scoped_refptr<DocumentMock> document() { return document_; } + scoped_refptr<HTMLLinkElementMock> CreateDocumentWithLinkElement( + std::string rel = ""); + + private: + std::unique_ptr<DomStatTracker> dom_stat_tracker_; + testing::StubEnvironmentSettings environment_settings_; + NiceMock<cssom::testing::MockCSSParser> css_parser_; + HTMLElementContext html_element_context_; + scoped_refptr<DocumentMock> document_; + base::MessageLoop message_loop_; +}; + +scoped_refptr<HTMLLinkElementMock> +HtmlLinkElementTest::CreateDocumentWithLinkElement(std::string rel) { + scoped_refptr<HTMLLinkElementMock> element_ = + document_->CreateElement("link"); + if (!rel.empty()) { + element_->SetAttribute("rel", rel); + } + document_->AppendChild(element_); + return element_; +} + +TEST_F(HtmlLinkElementTest, StylesheetRelAttribute) { + scoped_refptr<HTMLLinkElementMock> el = + CreateDocumentWithLinkElement("stylesheet"); + EXPECT_TRUE(el->obtained()); +} + +TEST_F(HtmlLinkElementTest, SplashScreenRelAttribute) { + scoped_refptr<HTMLLinkElementMock> el = + CreateDocumentWithLinkElement("splashscreen"); + EXPECT_TRUE(el->obtained()); + + el = CreateDocumentWithLinkElement("music_splashscreen"); + EXPECT_TRUE(el->obtained()); + + el = CreateDocumentWithLinkElement("music-_\\2_splashscreen"); + EXPECT_TRUE(el->obtained()); +} + +TEST_F(HtmlLinkElementTest, BadSplashScreenRelAttribute) { + scoped_refptr<HTMLLinkElementMock> el = + CreateDocumentWithLinkElement("bad*_splashscreen"); + EXPECT_FALSE(el->obtained()); + + el = CreateDocumentWithLinkElement("badsplashscreen"); + EXPECT_FALSE(el->obtained()); + + el = CreateDocumentWithLinkElement("splashscreen_bad"); + EXPECT_FALSE(el->obtained()); +} + +} // namespace dom +} // namespace cobalt
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc index eac877d..989d187 100644 --- a/src/cobalt/dom/window.cc +++ b/src/cobalt/dom/window.cc
@@ -702,12 +702,13 @@ tracer->Trace(on_screen_keyboard_); } -void Window::CacheSplashScreen(const std::string& content) { +void Window::CacheSplashScreen(const std::string& content, + const base::Optional<std::string>& topic) { if (splash_screen_cache_callback_.is_null()) { return; } DLOG(INFO) << "Caching splash screen for URL " << location()->url(); - splash_screen_cache_callback_.Run(location()->url(), content); + splash_screen_cache_callback_.Run(content, topic); } const scoped_refptr<OnScreenKeyboard>& Window::on_screen_keyboard() const {
diff --git a/src/cobalt/dom/window.h b/src/cobalt/dom/window.h index 6fbfc81..e7320b4 100644 --- a/src/cobalt/dom/window.h +++ b/src/cobalt/dom/window.h
@@ -122,7 +122,9 @@ // close() was called. typedef base::Callback<void(base::TimeDelta)> CloseCallback; typedef UrlRegistry<MediaSource> MediaSourceRegistry; - typedef base::Callback<bool(const GURL&, const std::string&)> CacheCallback; + typedef base::Callback<void(const std::string&, + const base::Optional<std::string>&)> + CacheCallback; enum ClockType { kClockTypeTestRunner, @@ -379,7 +381,8 @@ void OnDocumentRootElementUnableToProvideOffsetDimensions(); // Cache the passed in splash screen content for the window.location URL. - void CacheSplashScreen(const std::string& content); + void CacheSplashScreen(const std::string& content, + const base::Optional<std::string>& topic); const scoped_refptr<loader::CORSPreflightCache> get_preflight_cache() { return preflight_cache_;
diff --git a/src/cobalt/extension/configuration.h b/src/cobalt/extension/configuration.h index 9d379c7..7c12817 100644 --- a/src/cobalt/extension/configuration.h +++ b/src/cobalt/extension/configuration.h
@@ -219,6 +219,11 @@ // See "cobalt/doc/performance_tuning.md" for more information on when this // should be used. bool (*CobaltEnableJit)(); + + // The fields below this point were added in version 2 or later. + + // A mapping of splash screen topics to fallback URLs. + const char* (*CobaltFallbackSplashScreenTopics)(); } CobaltExtensionConfigurationApi; #ifdef __cplusplus
diff --git a/src/cobalt/extension/extension_test.cc b/src/cobalt/extension/extension_test.cc index 3756cba..9f4caf0 100644 --- a/src/cobalt/extension/extension_test.cc +++ b/src/cobalt/extension/extension_test.cc
@@ -29,23 +29,22 @@ typedef CobaltExtensionPlatformServiceApi ExtensionApi; const char* kExtensionName = kCobaltExtensionPlatformServiceName; - const ExtensionApi* extension_api = static_cast<const ExtensionApi*>( - SbSystemGetExtension(kExtensionName)); + const ExtensionApi* extension_api = + static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName)); if (!extension_api) { return; } EXPECT_STREQ(extension_api->name, kExtensionName); - EXPECT_TRUE(extension_api->version == 1 || - extension_api->version == 2 || - extension_api->version == 3) << "Invalid version"; - EXPECT_TRUE(extension_api->Has != NULL); - EXPECT_TRUE(extension_api->Open != NULL); - EXPECT_TRUE(extension_api->Close != NULL); - EXPECT_TRUE(extension_api->Send != NULL); + EXPECT_GE(extension_api->version, 1u); + EXPECT_LE(extension_api->version, 3u); + EXPECT_NE(extension_api->Has, nullptr); + EXPECT_NE(extension_api->Open, nullptr); + EXPECT_NE(extension_api->Close, nullptr); + EXPECT_NE(extension_api->Send, nullptr); - const ExtensionApi* second_extension_api = static_cast<const ExtensionApi*>( - SbSystemGetExtension(kExtensionName)); + const ExtensionApi* second_extension_api = + static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName)); EXPECT_EQ(second_extension_api, extension_api) << "Extension struct should be a singleton"; } @@ -54,35 +53,50 @@ typedef CobaltExtensionGraphicsApi ExtensionApi; const char* kExtensionName = kCobaltExtensionGraphicsName; - const ExtensionApi* extension_api = static_cast<const ExtensionApi*>( - SbSystemGetExtension(kExtensionName)); + const ExtensionApi* extension_api = + static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName)); if (!extension_api) { return; } EXPECT_STREQ(extension_api->name, kExtensionName); - EXPECT_TRUE(extension_api->version == 1 || - extension_api->version == 2 || - extension_api->version == 3) << "Invalid version"; - EXPECT_TRUE(extension_api->GetMaximumFrameIntervalInMilliseconds != NULL); - if (extension_api->version >= 2) { - EXPECT_TRUE(extension_api->GetMinimumFrameIntervalInMilliseconds != NULL); - } - if (extension_api->version >= 3) { - EXPECT_TRUE(extension_api->IsMapToMeshEnabled != NULL); - } + EXPECT_GE(extension_api->version, 1u); + EXPECT_LE(extension_api->version, 4u); + EXPECT_NE(extension_api->GetMaximumFrameIntervalInMilliseconds, nullptr); float maximum_frame_interval = extension_api->GetMaximumFrameIntervalInMilliseconds(); EXPECT_FALSE(std::isnan(maximum_frame_interval)); if (extension_api->version >= 2) { + EXPECT_NE(extension_api->GetMinimumFrameIntervalInMilliseconds, nullptr); float minimum_frame_interval = - extension_api->GetMinimumFrameIntervalInMilliseconds(); + extension_api->GetMinimumFrameIntervalInMilliseconds(); EXPECT_GT(minimum_frame_interval, 0); } - const ExtensionApi* second_extension_api = static_cast<const ExtensionApi*>( - SbSystemGetExtension(kExtensionName)); + + if (extension_api->version >= 3) { + EXPECT_NE(extension_api->IsMapToMeshEnabled, nullptr); + } + + if (extension_api->version >= 4) { + EXPECT_NE(extension_api->ShouldClearFrameOnShutdown, nullptr); + float clear_color_r, clear_color_g, clear_color_b, clear_color_a; + if (extension_api->ShouldClearFrameOnShutdown( + &clear_color_r, &clear_color_g, &clear_color_b, &clear_color_a)) { + EXPECT_GE(clear_color_r, 0.0f); + EXPECT_LE(clear_color_r, 1.0f); + EXPECT_GE(clear_color_g, 0.0f); + EXPECT_LE(clear_color_g, 1.0f); + EXPECT_GE(clear_color_b, 0.0f); + EXPECT_LE(clear_color_b, 1.0f); + EXPECT_GE(clear_color_a, 0.0f); + EXPECT_LE(clear_color_a, 1.0f); + } + } + + const ExtensionApi* second_extension_api = + static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName)); EXPECT_EQ(second_extension_api, extension_api) << "Extension struct should be a singleton"; } @@ -98,18 +112,17 @@ } EXPECT_STREQ(extension_api->name, kExtensionName); - EXPECT_TRUE(extension_api->version == 1 || - extension_api->version == 2 || - extension_api->version == 3) << "Invalid version"; - EXPECT_TRUE(extension_api->GetCurrentInstallationIndex != NULL); - EXPECT_TRUE(extension_api->MarkInstallationSuccessful != NULL); - EXPECT_TRUE(extension_api->RequestRollForwardToInstallation != NULL); - EXPECT_TRUE(extension_api->GetInstallationPath != NULL); - EXPECT_TRUE(extension_api->SelectNewInstallationIndex != NULL); - EXPECT_TRUE(extension_api->GetAppKey != NULL); - EXPECT_TRUE(extension_api->GetMaxNumberInstallations != NULL); - EXPECT_TRUE(extension_api->ResetInstallation != NULL); - EXPECT_TRUE(extension_api->Reset != NULL); + EXPECT_GE(extension_api->version, 1u); + EXPECT_LE(extension_api->version, 3u); + EXPECT_NE(extension_api->GetCurrentInstallationIndex, nullptr); + EXPECT_NE(extension_api->MarkInstallationSuccessful, nullptr); + EXPECT_NE(extension_api->RequestRollForwardToInstallation, nullptr); + EXPECT_NE(extension_api->GetInstallationPath, nullptr); + EXPECT_NE(extension_api->SelectNewInstallationIndex, nullptr); + EXPECT_NE(extension_api->GetAppKey, nullptr); + EXPECT_NE(extension_api->GetMaxNumberInstallations, nullptr); + EXPECT_NE(extension_api->ResetInstallation, nullptr); + EXPECT_NE(extension_api->Reset, nullptr); const ExtensionApi* second_extension_api = static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName)); EXPECT_EQ(second_extension_api, extension_api) @@ -127,27 +140,32 @@ } EXPECT_STREQ(extension_api->name, kExtensionName); - EXPECT_TRUE(extension_api->version == 1); - EXPECT_TRUE(extension_api->CobaltUserOnExitStrategy != NULL); - EXPECT_TRUE(extension_api->CobaltRenderDirtyRegionOnly != NULL); - EXPECT_TRUE(extension_api->CobaltEglSwapInterval != NULL); - EXPECT_TRUE(extension_api->CobaltFallbackSplashScreenUrl != NULL); - EXPECT_TRUE(extension_api->CobaltEnableQuic != NULL); - EXPECT_TRUE(extension_api->CobaltSkiaCacheSizeInBytes != NULL); - EXPECT_TRUE(extension_api->CobaltOffscreenTargetCacheSizeInBytes != NULL); - EXPECT_TRUE(extension_api->CobaltEncodedImageCacheSizeInBytes != NULL); - EXPECT_TRUE(extension_api->CobaltImageCacheSizeInBytes != NULL); - EXPECT_TRUE(extension_api->CobaltLocalTypefaceCacheSizeInBytes != NULL); - EXPECT_TRUE(extension_api->CobaltRemoteTypefaceCacheSizeInBytes != NULL); - EXPECT_TRUE(extension_api->CobaltMeshCacheSizeInBytes != NULL); - EXPECT_TRUE(extension_api->CobaltSoftwareSurfaceCacheSizeInBytes != NULL); - EXPECT_TRUE(extension_api->CobaltImageCacheCapacityMultiplierWhenPlayingVideo != NULL); - EXPECT_TRUE(extension_api->CobaltSkiaGlyphAtlasWidth != NULL); - EXPECT_TRUE(extension_api->CobaltSkiaGlyphAtlasHeight != NULL); - EXPECT_TRUE(extension_api->CobaltJsGarbageCollectionThresholdInBytes != NULL); - EXPECT_TRUE(extension_api->CobaltReduceCpuMemoryBy != NULL); - EXPECT_TRUE(extension_api->CobaltReduceGpuMemoryBy != NULL); - EXPECT_TRUE(extension_api->CobaltGcZeal != NULL); + EXPECT_GE(extension_api->version, 1u); + EXPECT_LE(extension_api->version, 2u); + EXPECT_NE(extension_api->CobaltUserOnExitStrategy, nullptr); + EXPECT_NE(extension_api->CobaltRenderDirtyRegionOnly, nullptr); + EXPECT_NE(extension_api->CobaltEglSwapInterval, nullptr); + EXPECT_NE(extension_api->CobaltFallbackSplashScreenUrl, nullptr); + EXPECT_NE(extension_api->CobaltEnableQuic, nullptr); + EXPECT_NE(extension_api->CobaltSkiaCacheSizeInBytes, nullptr); + EXPECT_NE(extension_api->CobaltOffscreenTargetCacheSizeInBytes, nullptr); + EXPECT_NE(extension_api->CobaltEncodedImageCacheSizeInBytes, nullptr); + EXPECT_NE(extension_api->CobaltImageCacheSizeInBytes, nullptr); + EXPECT_NE(extension_api->CobaltLocalTypefaceCacheSizeInBytes, nullptr); + EXPECT_NE(extension_api->CobaltRemoteTypefaceCacheSizeInBytes, nullptr); + EXPECT_NE(extension_api->CobaltMeshCacheSizeInBytes, nullptr); + EXPECT_NE(extension_api->CobaltSoftwareSurfaceCacheSizeInBytes, nullptr); + EXPECT_NE(extension_api->CobaltImageCacheCapacityMultiplierWhenPlayingVideo, + nullptr); + EXPECT_NE(extension_api->CobaltSkiaGlyphAtlasWidth, nullptr); + EXPECT_NE(extension_api->CobaltSkiaGlyphAtlasHeight, nullptr); + EXPECT_NE(extension_api->CobaltJsGarbageCollectionThresholdInBytes, nullptr); + EXPECT_NE(extension_api->CobaltReduceCpuMemoryBy, nullptr); + EXPECT_NE(extension_api->CobaltReduceGpuMemoryBy, nullptr); + EXPECT_NE(extension_api->CobaltGcZeal, nullptr); + if (extension_api->version >= 2) { + EXPECT_NE(extension_api->CobaltFallbackSplashScreenTopics, nullptr); + } const ExtensionApi* second_extension_api = static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
diff --git a/src/cobalt/extension/graphics.h b/src/cobalt/extension/graphics.h index 9c993c5..a1a3b4e 100644 --- a/src/cobalt/extension/graphics.h +++ b/src/cobalt/extension/graphics.h
@@ -23,8 +23,7 @@ extern "C" { #endif -#define kCobaltExtensionGraphicsName \ - "dev.cobalt.extension.Graphics" +#define kCobaltExtensionGraphicsName "dev.cobalt.extension.Graphics" typedef struct CobaltExtensionGraphicsApi { // Name should be the string kCobaltExtensionGraphicsName. @@ -62,6 +61,20 @@ // Get whether the renderer should support 360 degree video or not. bool (*IsMapToMeshEnabled)(); + + // The fields below this point were added in version 4 or later. + + // Specify whether the framebuffer should be cleared when the graphics + // system is shutdown and color to use for clearing. The graphics system + // is shutdown on suspend or exit. The clear color values should be in the + // range of [0,1]; color values are only used if this function returns true. + // + // The default behavior is to clear to opaque black on shutdown unless this + // API specifies otherwise. + bool (*ShouldClearFrameOnShutdown)(float* clear_color_red, + float* clear_color_green, + float* clear_color_blue, + float* clear_color_alpha); } CobaltExtensionGraphicsApi; #ifdef __cplusplus
diff --git a/src/cobalt/layout/box_generator.cc b/src/cobalt/layout/box_generator.cc index c622e99..2bc8571 100644 --- a/src/cobalt/layout/box_generator.cc +++ b/src/cobalt/layout/box_generator.cc
@@ -1057,12 +1057,17 @@ container_box_before_split->SetUiNavItem(html_element->GetUiNavItem()); boxes_.push_back(container_box_before_split); - BoxIntersectionObserverModule::IntersectionObserverRootVector roots = - html_element->GetLayoutIntersectionObserverRoots(); - BoxIntersectionObserverModule::IntersectionObserverTargetVector targets = - html_element->GetLayoutIntersectionObserverTargets(); - container_box_before_split->AddIntersectionObserverRootsAndTargets( - std::move(roots), std::move(targets)); + // We already handle the case where the Intersection Observer root is the + // viewport with the initial containing block in layout. + if (html_element != + html_element->node_document()->document_element()->AsHTMLElement()) { + BoxIntersectionObserverModule::IntersectionObserverRootVector roots = + html_element->GetLayoutIntersectionObserverRoots(); + BoxIntersectionObserverModule::IntersectionObserverTargetVector targets = + html_element->GetLayoutIntersectionObserverTargets(); + container_box_before_split->AddIntersectionObserverRootsAndTargets( + std::move(roots), std::move(targets)); + } AppendPseudoElementToLine(html_element, dom::kBeforePseudoElementType);
diff --git a/src/cobalt/layout/intersection_observer_target.cc b/src/cobalt/layout/intersection_observer_target.cc index 26d863d..e114025 100644 --- a/src/cobalt/layout/intersection_observer_target.cc +++ b/src/cobalt/layout/intersection_observer_target.cc
@@ -27,108 +27,22 @@ namespace cobalt { namespace layout { +namespace { -void IntersectionObserverTarget::UpdateIntersectionObservationsForTarget( - ContainerBox* target_box) { - TRACE_EVENT0( - "cobalt::layout", - "IntersectionObserverTarget::UpdateIntersectionObservationsForTarget()"); - // Walk up the containing block chain looking for the box referencing the - // IntersectionObserverRoot corresponding to this IntersectionObserverTarget. - // Skip further processing for the target if it is not a descendant of the - // root in the containing block chain. - const ContainerBox* root_box = target_box->GetContainingBlock(); - while (!root_box->ContainsIntersectionObserverRoot( - intersection_observer_root_)) { - if (!root_box->parent()) { - return; - } - root_box = root_box->GetContainingBlock(); - } - - // Let targetRect be target's bounding border box. - RectLayoutUnit target_transformed_border_box( - target_box->GetTransformedBoxFromRoot( - target_box->GetBorderBoxFromMarginBox())); - math::RectF target_rect = - math::RectF(target_transformed_border_box.x().toFloat(), - target_transformed_border_box.y().toFloat(), - target_transformed_border_box.width().toFloat(), - target_transformed_border_box.height().toFloat()); - - // Let intersectionRect be the result of running the compute the intersection - // algorithm on target. - math::RectF root_bounds = GetRootBounds( - root_box, intersection_observer_root_->root_margin_property_value()); - math::RectF intersection_rect = ComputeIntersectionBetweenTargetAndRoot( - root_box, root_bounds, target_rect, target_box); - - // Let targetArea be targetRect's area. - float target_area = target_rect.size().GetArea(); - - // Let intersectionArea be intersectionRect's area. - float intersection_area = intersection_rect.size().GetArea(); - - // Let isIntersecting be true if targetRect and rootBounds intersect or are - // edge-adjacent, even if the intersection has zero area (because rootBounds - // or targetRect have zero area); otherwise, let isIntersecting be false. - bool is_intersecting = - intersection_rect.width() != 0 || intersection_rect.height() != 0; - - // If targetArea is non-zero, let intersectionRatio be intersectionArea - // divided by targetArea. Otherwise, let intersectionRatio be 1 if - // isIntersecting is true, or 0 if isIntersecting is false. - float intersection_ratio = target_area > 0 ? intersection_area / target_area - : is_intersecting ? 1.0f : 0.0f; - - // Let thresholdIndex be the index of the first entry in observer.thresholds - // whose value is greater than intersectionRatio, or the length of - // observer.thresholds if intersectionRatio is greater than or equal to the - // last entry in observer.thresholds. - const std::vector<double>& thresholds = - intersection_observer_root_->thresholds_vector(); - size_t threshold_index; - for (threshold_index = 0; threshold_index < thresholds.size(); - ++threshold_index) { - if (thresholds.at(threshold_index) > intersection_ratio) { - // isIntersecting is false if intersectionRatio is less than all - // thresholds, sorted ascending. Not in spec but follows Chrome behavior. - if (threshold_index == 0) { - is_intersecting = false; - } - break; - } - } - - // If thresholdIndex does not equal previousThresholdIndex or if - // isIntersecting does not equal previousIsIntersecting, queue an - // IntersectionObserverEntry, passing in observer, time, rootBounds, - // boundingClientRect, intersectionRect, isIntersecting, and target. - if (static_cast<int32>(threshold_index) != previous_threshold_index_ || - is_intersecting != previous_is_intersecting_) { - on_intersection_callback_.Run(root_bounds, target_rect, intersection_rect, - is_intersecting, intersection_ratio); - } - - // Update the previousThresholdIndex and previousIsIntersecting properties. - previous_threshold_index_ = static_cast<int32>(threshold_index); - previous_is_intersecting_ = is_intersecting; +int32 GetUsedLengthOfRootMarginPropertyValue( + const scoped_refptr<cssom::PropertyValue>& length_property_value, + LayoutUnit percentage_base) { + UsedLengthValueProvider used_length_provider(percentage_base); + length_property_value->Accept(&used_length_provider); + // Not explicitly stated in web spec, but has been observed that Chrome + // truncates root margin decimal values. + return static_cast<int32>( + used_length_provider.used_length().value_or(LayoutUnit(0.0f)).toFloat()); } -bool IntersectionObserverTarget::IsInContainingBlockChain( - const ContainerBox* potential_containing_block, - const ContainerBox* target_box) { - const ContainerBox* containing_block = target_box->GetContainingBlock(); - while (containing_block != potential_containing_block) { - if (!containing_block->parent()) { - return false; - } - containing_block = containing_block->GetContainingBlock(); - } - return true; -} - -math::RectF IntersectionObserverTarget::GetRootBounds( +// Rules for determining the root intersection rectangle bounds. +// https://www.w3.org/TR/intersection-observer/#intersectionobserver-root-intersection-rectangle +math::RectF GetRootBounds( const ContainerBox* root_box, scoped_refptr<cssom::PropertyListValue> root_margin_property_value) { math::RectF root_bounds_without_margins; @@ -175,18 +89,27 @@ return root_bounds; } -int32 IntersectionObserverTarget::GetUsedLengthOfRootMarginPropertyValue( - const scoped_refptr<cssom::PropertyValue>& length_property_value, - LayoutUnit percentage_base) { - UsedLengthValueProvider used_length_provider(percentage_base); - length_property_value->Accept(&used_length_provider); - // Not explicitly stated in web spec, but has been observed that Chrome - // truncates root margin decimal values. - return static_cast<int32>( - used_length_provider.used_length().value_or(LayoutUnit(0.0f)).toFloat()); +// Similar to the IntersectRects function in math::RectF, but handles edge +// adjacent intersections as valid intersections (instead of returning a +// rectangle with zero dimensions) +math::RectF IntersectIntersectionObserverRects(const math::RectF& a, + const math::RectF& b) { + float rx = std::max(a.x(), b.x()); + float ry = std::max(a.y(), b.y()); + float rr = std::min(a.right(), b.right()); + float rb = std::min(a.bottom(), b.bottom()); + + if (rx > rr || ry > rb) { + return math::RectF(0.0f, 0.0f, 0.0f, 0.0f); + } + + return math::RectF(rx, ry, rr - rx, rb - ry); } -math::RectF IntersectionObserverTarget::ComputeIntersectionBetweenTargetAndRoot( +// Compute the intersection between a target and the observer's intersection +// root. +// https://www.w3.org/TR/intersection-observer/#calculate-intersection-rect-algo +math::RectF ComputeIntersectionBetweenTargetAndRoot( const ContainerBox* root_box, const math::RectF& root_bounds, const math::RectF& target_rect, const ContainerBox* target_box) { // Let intersectionRect be target's bounding border box. @@ -280,18 +203,95 @@ return intersection_rect; } -math::RectF IntersectionObserverTarget::IntersectIntersectionObserverRects( - const math::RectF& a, const math::RectF& b) { - float rx = std::max(a.x(), b.x()); - float ry = std::max(a.y(), b.y()); - float rr = std::min(a.right(), b.right()); - float rb = std::min(a.bottom(), b.bottom()); +} // namespace - if (rx > rr || ry > rb) { - return math::RectF(0.0f, 0.0f, 0.0f, 0.0f); +void IntersectionObserverTarget::UpdateIntersectionObservationsForTarget( + ContainerBox* target_box) { + TRACE_EVENT0( + "cobalt::layout", + "IntersectionObserverTarget::UpdateIntersectionObservationsForTarget()"); + // Walk up the containing block chain looking for the box referencing the + // IntersectionObserverRoot corresponding to this IntersectionObserverTarget. + // Skip further processing for the target if it is not a descendant of the + // root in the containing block chain. + const ContainerBox* root_box = target_box->GetContainingBlock(); + while (!root_box->ContainsIntersectionObserverRoot( + intersection_observer_root_)) { + if (!root_box->parent()) { + return; + } + root_box = root_box->GetContainingBlock(); } - return math::RectF(rx, ry, rr - rx, rb - ry); + // Let targetRect be target's bounding border box. + RectLayoutUnit target_transformed_border_box( + target_box->GetTransformedBoxFromRoot( + target_box->GetBorderBoxFromMarginBox())); + const math::RectF target_rect = + math::RectF(target_transformed_border_box.x().toFloat(), + target_transformed_border_box.y().toFloat(), + target_transformed_border_box.width().toFloat(), + target_transformed_border_box.height().toFloat()); + + // Let intersectionRect be the result of running the compute the intersection + // algorithm on target. + const math::RectF root_bounds = GetRootBounds( + root_box, intersection_observer_root_->root_margin_property_value()); + const math::RectF intersection_rect = ComputeIntersectionBetweenTargetAndRoot( + root_box, root_bounds, target_rect, target_box); + + // Let targetArea be targetRect's area. + float target_area = target_rect.size().GetArea(); + + // Let intersectionArea be intersectionRect's area. + float intersection_area = intersection_rect.size().GetArea(); + + // Let isIntersecting be true if targetRect and rootBounds intersect or are + // edge-adjacent, even if the intersection has zero area (because rootBounds + // or targetRect have zero area); otherwise, let isIntersecting be false. + bool is_intersecting = + intersection_rect.width() != 0 || intersection_rect.height() != 0 || + (target_rect.width() == 0 && target_rect.height() == 0 && + root_bounds.Contains(target_rect)); + + // If targetArea is non-zero, let intersectionRatio be intersectionArea + // divided by targetArea. Otherwise, let intersectionRatio be 1 if + // isIntersecting is true, or 0 if isIntersecting is false. + float intersection_ratio = target_area > 0 ? intersection_area / target_area + : is_intersecting ? 1.0f : 0.0f; + + // Let thresholdIndex be the index of the first entry in observer.thresholds + // whose value is greater than intersectionRatio, or the length of + // observer.thresholds if intersectionRatio is greater than or equal to the + // last entry in observer.thresholds. + const std::vector<double>& thresholds = + intersection_observer_root_->thresholds_vector(); + size_t threshold_index; + for (threshold_index = 0; threshold_index < thresholds.size(); + ++threshold_index) { + if (thresholds.at(threshold_index) > intersection_ratio) { + // isIntersecting is false if intersectionRatio is less than all + // thresholds, sorted ascending. Not in spec but follows Chrome behavior. + if (threshold_index == 0) { + is_intersecting = false; + } + break; + } + } + + // If thresholdIndex does not equal previousThresholdIndex or if + // isIntersecting does not equal previousIsIntersecting, queue an + // IntersectionObserverEntry, passing in observer, time, rootBounds, + // boundingClientRect, intersectionRect, isIntersecting, and target. + if (static_cast<int32>(threshold_index) != previous_threshold_index_ || + is_intersecting != previous_is_intersecting_) { + on_intersection_callback_.Run(root_bounds, target_rect, intersection_rect, + is_intersecting, intersection_ratio); + } + + // Update the previousThresholdIndex and previousIsIntersecting properties. + previous_threshold_index_ = static_cast<int32>(threshold_index); + previous_is_intersecting_ = is_intersecting; } } // namespace layout
diff --git a/src/cobalt/layout/intersection_observer_target.h b/src/cobalt/layout/intersection_observer_target.h index a40d41d..23a8671 100644 --- a/src/cobalt/layout/intersection_observer_target.h +++ b/src/cobalt/layout/intersection_observer_target.h
@@ -75,34 +75,6 @@ } private: - // Walk up the containing block chain, as described in - // http://www.w3.org/TR/CSS2/visudet.html#containing-block-details - bool IsInContainingBlockChain(const ContainerBox* potential_containing_block, - const ContainerBox* target_box); - - int32 GetUsedLengthOfRootMarginPropertyValue( - const scoped_refptr<cssom::PropertyValue>& length_property_value, - LayoutUnit percentage_base); - - // Rules for determining the root intersection rectangle bounds. - // https://www.w3.org/TR/intersection-observer/#intersectionobserver-root-intersection-rectangle - math::RectF GetRootBounds( - const ContainerBox* root_box, - scoped_refptr<cssom::PropertyListValue> root_margin_property_value); - - // Compute the intersection between a target and the observer's intersection - // root. - // https://www.w3.org/TR/intersection-observer/#calculate-intersection-rect-algo - math::RectF ComputeIntersectionBetweenTargetAndRoot( - const ContainerBox* root_box, const math::RectF& root_bounds, - const math::RectF& target_rect, const ContainerBox* target_box); - - // Similar to the IntersectRects function in math::RectF, but handles edge - // adjacent intersections as valid intersections (instead of returning a - // rectangle with zero dimensions) - math::RectF IntersectIntersectionObserverRects(const math::RectF& a, - const math::RectF& b); - OnIntersectionCallback on_intersection_callback_; scoped_refptr<IntersectionObserverRoot> intersection_observer_root_;
diff --git a/src/cobalt/layout/topmost_event_target.cc b/src/cobalt/layout/topmost_event_target.cc index df2cd6a..8801fef 100644 --- a/src/cobalt/layout/topmost_event_target.cc +++ b/src/cobalt/layout/topmost_event_target.cc
@@ -241,9 +241,11 @@ for (scoped_refptr<dom::Element> element = target_element; element != nearest_common_ancestor; element = element->parent_element()) { - element->DispatchEvent(new dom::PointerEvent( - base::Tokens::pointerenter(), dom::Event::kNotBubbles, - dom::Event::kNotCancelable, view, *event_init)); + if (element) { + element->DispatchEvent(new dom::PointerEvent( + base::Tokens::pointerenter(), dom::Event::kNotBubbles, + dom::Event::kNotCancelable, view, *event_init)); + } } } @@ -254,9 +256,11 @@ for (scoped_refptr<dom::Element> element = target_element; element != nearest_common_ancestor; element = element->parent_element()) { - element->DispatchEvent(new dom::MouseEvent( - base::Tokens::mouseenter(), dom::Event::kNotBubbles, - dom::Event::kNotCancelable, view, *event_init)); + if (element) { + element->DispatchEvent(new dom::MouseEvent( + base::Tokens::mouseenter(), dom::Event::kNotBubbles, + dom::Event::kNotCancelable, view, *event_init)); + } } } }
diff --git a/src/cobalt/layout_tests/testdata/web-platform-tests/cobalt_special/web_platform_tests.txt b/src/cobalt/layout_tests/testdata/web-platform-tests/cobalt_special/web_platform_tests.txt index 25ffd6c..d6e13da 100644 --- a/src/cobalt/layout_tests/testdata/web-platform-tests/cobalt_special/web_platform_tests.txt +++ b/src/cobalt/layout_tests/testdata/web-platform-tests/cobalt_special/web_platform_tests.txt
@@ -1,4 +1,5 @@ # Cobalt's special tests that borrows WPT infrastructures. origin-clean.htm,PASS -preflight-cache-2.htm,PASS \ No newline at end of file +preflight-cache-2.htm,PASS +xhr_content_length.htm,PASS
diff --git a/src/cobalt/layout_tests/testdata/web-platform-tests/intersection-observer/web_platform_tests.txt b/src/cobalt/layout_tests/testdata/web-platform-tests/intersection-observer/web_platform_tests.txt index b830268..e543994 100644 --- a/src/cobalt/layout_tests/testdata/web-platform-tests/intersection-observer/web_platform_tests.txt +++ b/src/cobalt/layout_tests/testdata/web-platform-tests/intersection-observer/web_platform_tests.txt
@@ -7,33 +7,33 @@ # Empty rootMargin should evaluate to default, not cause error empty-root-margin.html,DISABLE initial-observation-with-threshold.html,PASS +# Unsupported functions measuring space width between adjacent inline elements +inline-client-rect.html,DISABLE inline-with-block-child-client-rect.html,PASS isIntersecting-change-events.html,PASS +# overflow: scroll results in incorrectly clipped intersection rect +isIntersecting-threshold.html,DISABLE +multiple-targets.html,PASS +multiple-thresholds.html,PASS # rootMargin default should be "0px 0px 0px 0px", not "0px" observer-attributes.html,DISABLE # WPT testharness needs to be rebased observer-exceptions.html,DISABLE +observer-without-js-reference.html,PASS #Deleting an element does not trigger an intersection remove-element.html,DISABLE root-margin-root-element.html,PASS +# Root margin calculations have rounding errors +root-margin.html,DISABLE # Setting IO target equal to document.documentElement crashes Cobalt root-margin-rounding.html,DISABLE rtl-clipped-root.html,PASS +same-document-no-root.html,PASS same-document-root.html,PASS +same-document-zero-size-target.html,PASS +text-target.html,PASS zero-area-element-hidden.html,PASS -#Zero-area target does not trigger an intersection -zero-area-element-visible.html,DISABLE - -#No root specified - intersections with viewport incorrectly reported -inline-client-rect.html,DISABLE -isIntersecting-threshold.html,DISABLE -multiple-targets.html,DISABLE -multiple-thresholds.html,DISABLE -observer-without-js-reference.html,DISABLE -root-margin.html,DISABLE -same-document-no-root.html,DISABLE -same-document-zero-size-target.html,DISABLE -text-target.html,DISABLE +zero-area-element-visible.html,PASS #IntersectionObserverV2 not implemented v2/blur-filter.html,DISABLE
diff --git a/src/cobalt/media/base/sbplayer_pipeline.cc b/src/cobalt/media/base/sbplayer_pipeline.cc index 4f4f9d1..8185f82 100644 --- a/src/cobalt/media/base/sbplayer_pipeline.cc +++ b/src/cobalt/media/base/sbplayer_pipeline.cc
@@ -277,8 +277,13 @@ VideoFrameProvider* video_frame_provider_; + // Read audio from the stream if |timestamp_of_last_written_audio_| is less + // than |seek_time_| + |kAudioPrerollLimit|, this effectively allows 10 + // seconds of audio to be written to the SbPlayer after playback startup or + // seek. + static const SbTime kAudioPrerollLimit = 10 * kSbTimeSecond; // Don't read audio from the stream more than |kAudioLimit| ahead of the - // current media time. + // current media time during playing. static const SbTime kAudioLimit = kSbTimeSecond; // Only call GetMediaTime() from OnNeedData if it has been // |kMediaTimeCheckInterval| since the last call to GetMediaTime(). @@ -1117,22 +1122,28 @@ kMediaTimeCheckInterval) { GetMediaTime(); } - // The estimated time ahead of playback may be negative if no audio has been - // written. - SbTime time_ahead_of_playback = - timestamp_of_last_written_audio_ - last_media_time_; - // Delay reading audio more than |kAudioLimit| ahead of playback, taking - // into account that our estimate of playback time might be behind by + + // Delay reading audio more than |kAudioLimit| ahead of playback after the + // player has received enough audio for preroll, taking into account that + // our estimate of playback time might be behind by // |kMediaTimeCheckInterval|. - if (time_ahead_of_playback > (kAudioLimit + kMediaTimeCheckInterval)) { - SbTime delay_time = (time_ahead_of_playback - kAudioLimit) / - std::max(playback_rate_, 1.0f); - task_runner_->PostDelayedTask( - FROM_HERE, base::Bind(&SbPlayerPipeline::DelayedNeedData, this), - base::TimeDelta::FromMicroseconds(delay_time)); - audio_read_delayed_ = true; - return; + if (timestamp_of_last_written_audio_ - seek_time_.ToSbTime() > + kAudioPrerollLimit) { + // The estimated time ahead of playback may be negative if no audio has + // been written. + SbTime time_ahead_of_playback = + timestamp_of_last_written_audio_ - last_media_time_; + if (time_ahead_of_playback > (kAudioLimit + kMediaTimeCheckInterval)) { + SbTime delay_time = (time_ahead_of_playback - kAudioLimit) / + std::max(playback_rate_, 1.0f); + task_runner_->PostDelayedTask( + FROM_HERE, base::Bind(&SbPlayerPipeline::DelayedNeedData, this), + base::TimeDelta::FromMicroseconds(delay_time)); + audio_read_delayed_ = true; + return; + } } + audio_read_delayed_ = false; #endif // SB_API_VERSION >= 11 audio_read_in_progress_ = true;
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi b/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi index 5e37306..a8801f8 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi +++ b/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi
@@ -21,8 +21,11 @@ { 'variables': { 'glsl_shaders_dir': '<(DEPTH)/cobalt/renderer/glimp_shaders/glsl', + 'glsl_shaders_0': [ + '<!@pymod_do_main(starboard.build.gyp_functions file_glob <(DEPTH)/cobalt/renderer/glimp_shaders/glsl/ *.glsl)' + ], 'glsl_shaders': [ - '<!@(ls -1 <(DEPTH)/cobalt/renderer/glimp_shaders/glsl/*.glsl |xargs -n 1 basename)', + '<!@pymod_do_main(starboard.build.gyp_functions basename <@(glsl_shaders_0) )', ], } }
diff --git a/src/cobalt/renderer/pipeline.cc b/src/cobalt/renderer/pipeline.cc index 97bef85..a78f0ed 100644 --- a/src/cobalt/renderer/pipeline.cc +++ b/src/cobalt/renderer/pipeline.cc
@@ -24,12 +24,14 @@ #include "cobalt/base/address_sanitizer.h" #include "cobalt/base/cobalt_paths.h" #include "cobalt/base/polymorphic_downcast.h" +#include "cobalt/extension/graphics.h" #include "cobalt/math/rect_f.h" #include "cobalt/render_tree/brush.h" #include "cobalt/render_tree/composition_node.h" #include "cobalt/render_tree/dump_render_tree_to_string.h" #include "cobalt/render_tree/rect_node.h" #include "nb/memory_scope.h" +#include "starboard/system.h" using cobalt::render_tree::Node; using cobalt::render_tree::animations::AnimateNode; @@ -38,6 +40,7 @@ namespace renderer { namespace { + #if !defined(COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS) // This default value has been moved from cobalt/build/cobalt_configuration.gypi // in favor of the usage of @@ -76,6 +79,34 @@ } } +bool ShouldClearFrameOnShutdown(render_tree::ColorRGBA* out_clear_color) { +#if SB_API_VERSION >= 11 + const CobaltExtensionGraphicsApi* graphics_extension = + static_cast<const CobaltExtensionGraphicsApi*>( + SbSystemGetExtension(kCobaltExtensionGraphicsName)); + if (graphics_extension && + strcmp(graphics_extension->name, kCobaltExtensionGraphicsName) == 0 && + graphics_extension->version >= 4) { + float r, g, b, a; + if (graphics_extension->ShouldClearFrameOnShutdown(&r, &g, &b, &a)) { + out_clear_color->set_r(r); + out_clear_color->set_g(g); + out_clear_color->set_b(b); + out_clear_color->set_a(a); + return true; + } + return false; + } +#endif + + // Default is to clear to opaque black. + out_clear_color->set_r(0.0f); + out_clear_color->set_g(0.0f); + out_clear_color->set_b(0.0f); + out_clear_color->set_a(1.0f); + return true; +} + } // namespace Pipeline::Pipeline(const CreateRasterizerFunction& create_rasterizer_function, @@ -329,13 +360,13 @@ minimum_frame_interval_milliseconds = COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS; } else { - DLOG(ERROR) << - "COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS and " - "CobaltExtensionGraphicsApi::GetMinimumFrameIntervalInMilliseconds" - "are both defined." - "Remove the 'cobalt_minimum_frame_time_in_milliseconds' "; - "from ../gyp_configuration.gypi in favor of the usage of " - "CobaltExtensionGraphicsApi::GetMinimumFrameIntervalInMilliseconds." + DLOG(ERROR) + << "COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS and " + "CobaltExtensionGraphicsApi::GetMinimumFrameIntervalInMilliseconds" + "are both defined." + "Remove the 'cobalt_minimum_frame_time_in_milliseconds' "; + "from ../gyp_configuration.gypi in favor of the usage of " + "CobaltExtensionGraphicsApi::GetMinimumFrameIntervalInMilliseconds." } #else if (minimum_frame_interval_milliseconds < 0.0f) { @@ -373,11 +404,13 @@ bool is_new_render_tree = submission.render_tree != last_render_tree_; bool has_render_tree_changed = !last_animations_expired_ || is_new_render_tree; - bool force_rasterize = submit_even_if_render_tree_is_unchanged_ || - fps_overlay_update_pending_; + bool force_rasterize = + submit_even_if_render_tree_is_unchanged_ || fps_overlay_update_pending_; - float maximum_frame_interval_milliseconds = graphics_context_ ? - graphics_context_->GetMaximumFrameIntervalInMilliseconds() : -1.0f; + float maximum_frame_interval_milliseconds = + graphics_context_ + ? graphics_context_->GetMaximumFrameIntervalInMilliseconds() + : -1.0f; if (maximum_frame_interval_milliseconds >= 0.0f) { base::TimeDelta max_time_between_rasterize = base::TimeDelta::FromMillisecondsD(maximum_frame_interval_milliseconds); @@ -617,18 +650,18 @@ // Shutdown the FPS overlay which may reference render trees. fps_overlay_ = base::nullopt; - // Submit a black fullscreen rect node to clear the display before shutting + // Submit a fullscreen rect node to clear the display before shutting // down. This can be helpful if we quit while playing a video via // punch-through, which may result in unexpected images/colors appearing for // a flicker behind the display. - if (render_target_ && (clear_on_shutdown_mode_ == kClearToBlack)) { - rasterizer_->Submit( - new render_tree::RectNode( - math::RectF(render_target_->GetSize()), - std::unique_ptr<render_tree::Brush>( - new render_tree::SolidColorBrush( - render_tree::ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f)))), - render_target_); + render_tree::ColorRGBA clear_color; + if (render_target_ && clear_on_shutdown_mode_ == kClearAccordingToPlatform && + ShouldClearFrameOnShutdown(&clear_color)) { + rasterizer_->Submit(new render_tree::RectNode( + math::RectF(render_target_->GetSize()), + std::unique_ptr<render_tree::Brush>( + new render_tree::SolidColorBrush(clear_color))), + render_target_); } // This potential reference to a render tree whose animations may have ended
diff --git a/src/cobalt/renderer/pipeline.h b/src/cobalt/renderer/pipeline.h index bbb9658..5848d95 100644 --- a/src/cobalt/renderer/pipeline.h +++ b/src/cobalt/renderer/pipeline.h
@@ -58,7 +58,12 @@ RasterizationCompleteCallback; enum ShutdownClearMode { - kClearToBlack, + // Query CobaltExtensionGraphicsApi's ShouldClearFrameOnShutdown for + // shutdown behavior. + kClearAccordingToPlatform, + + // Do not clear regardless of what CobaltExtensionGraphicsApi's + // ShouldClearFrameOnShutdown specifies. kNoClear, };
diff --git a/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc index 801112c..1f04c7a 100644 --- a/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc +++ b/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc
@@ -234,7 +234,7 @@ uint32_t untouched_states = kMSAAEnable_GrGLBackendState | kStencil_GrGLBackendState | kPixelStore_GrGLBackendState | kFixedFunction_GrGLBackendState | - kPathRendering_GrGLBackendState | kMisc_GrGLBackendState; + kPathRendering_GrGLBackendState; GetFallbackContext()->resetContext(~untouched_states & kAll_GrBackendState); }
diff --git a/src/cobalt/renderer/renderer_module.cc b/src/cobalt/renderer/renderer_module.cc index 6b52456..9783414 100644 --- a/src/cobalt/renderer/renderer_module.cc +++ b/src/cobalt/renderer/renderer_module.cc
@@ -102,7 +102,7 @@ // deprecate the submit_even_if_render_tree_is_unchanged. false, #endif - renderer::Pipeline::kClearToBlack, pipeline_options)); + renderer::Pipeline::kClearAccordingToPlatform, pipeline_options)); } }
diff --git a/src/cobalt/site/docs/development/setup-linux.md b/src/cobalt/site/docs/development/setup-linux.md index 3c4d49f..4fff20e 100644 --- a/src/cobalt/site/docs/development/setup-linux.md +++ b/src/cobalt/site/docs/development/setup-linux.md
@@ -32,12 +32,14 @@ Cobalt on Linux: ``` - $ sudo apt install -qqy --no-install-recommends pkgconf ninja-build bison \ - yasm binutils clang libgles2-mesa-dev mesa-common-dev libpulse-dev \ - libavresample-dev libasound2-dev libxrender-dev libxcomposite-dev + $ sudo apt install -qqy --no-install-recommends pkgconf ninja-build \ + bison yasm binutils clang libgles2-mesa-dev mesa-common-dev \ + libpulse-dev libavresample-dev libasound2-dev libxrender-dev \ + libxcomposite-dev ``` 1. Install Node.js via `nvm`: + ``` $ export NVM_DIR=~/.nvm $ export NODE_VERSION=12.17.0
diff --git a/src/cobalt/site/docs/reference/starboard/modules/10/media.md b/src/cobalt/site/docs/reference/starboard/modules/10/media.md index 7271b29..b9019a6 100644 --- a/src/cobalt/site/docs/reference/starboard/modules/10/media.md +++ b/src/cobalt/site/docs/reference/starboard/modules/10/media.md
@@ -435,11 +435,39 @@ `mime`: The mime information of the media in the form of `video/webm` or `video/mp4; codecs="avc1.42001E"`. It may include arbitrary parameters like "codecs", "channels", etc. Note that the "codecs" parameter may contain more -than one codec, delimited by comma. `key_system`: A lowercase value in fhe form +than one codec, delimited by comma. `key_system`: A lowercase value in the form of "com.example.somesystem" as suggested by [https://w3c.github.io/encrypted-media/#key-system](https://w3c.github.io/encrypted-media/#key-system)) that can be matched exactly with known DRM key systems of the platform. When `key_system` is an empty string, the return value is an indication for non-encrypted media. +An implementation may choose to support `key_system` with extra attributes, +separated by ';', like `com.example.somesystem; attribute_name1="value1"; +attribute_name2=value1`. If `key_system` with attributes is not supported by an +implementation, it should treat `key_system` as if it contains only the key +system, and reject any input containing extra attributes, i.e. it can keep using +its existing implementation. When an implementation supports `key_system` with +attributes, it has to support all attributes defined by the Starboard version +the implementation uses. An implementation should ignore any unknown attributes, +and make a decision solely based on the key system and the known attributes. For +example, if an implementation supports "com.widevine.alpha", it should also +return `kSbMediaSupportTypeProbably` kSbMediaSupportTypeProbably when +`key_system` is `com.widevine.alpha; invalid_attribute="invalid_value"`. +Currently the only attribute has to be supported is `encryptionscheme`. It +reflects the value passed to `encryptionScheme` encryptionScheme of +MediaKeySystemMediaCapability, as defined in [https://wicg.github.io/encrypted-media-encryption-scheme/,](https://wicg.github.io/encrypted-media-encryption-scheme/,),) which can take value "cenc", "cbcs", or "cbcs-1-9". Empty string is +not a valid value for `encryptionscheme` and the implementation should return +`kSbMediaSupportTypeNotSupported` kSbMediaSupportTypeNotSupported when +`encryptionscheme` is set to "". The implementation should return +`kSbMediaSupportTypeNotSupported` kSbMediaSupportTypeNotSupported for unknown +values of known attributes. For example, if an implementation supports +"encryptionscheme" with value "cenc", "cbcs", or "cbcs-1-9", then it should +return `kSbMediaSupportTypeProbably` kSbMediaSupportTypeProbably when +`key_system` is `com.widevine.alpha; encryptionscheme="cenc"`, and return +`kSbMediaSupportTypeNotSupported` kSbMediaSupportTypeNotSupported when +`key_system` is `com.widevine.alpha; encryptionscheme="invalid"`. If an +implementation supports key system with attributes on one key system, it has to +support key system with attributes on all key systems supported. + #### Declaration #### ```
diff --git a/src/cobalt/site/docs/reference/starboard/modules/11/media.md b/src/cobalt/site/docs/reference/starboard/modules/11/media.md index 377cb2b..22da43e 100644 --- a/src/cobalt/site/docs/reference/starboard/modules/11/media.md +++ b/src/cobalt/site/docs/reference/starboard/modules/11/media.md
@@ -429,11 +429,39 @@ `mime`: The mime information of the media in the form of `video/webm` or `video/mp4; codecs="avc1.42001E"`. It may include arbitrary parameters like "codecs", "channels", etc. Note that the "codecs" parameter may contain more -than one codec, delimited by comma. `key_system`: A lowercase value in fhe form +than one codec, delimited by comma. `key_system`: A lowercase value in the form of "com.example.somesystem" as suggested by [https://w3c.github.io/encrypted-media/#key-system](https://w3c.github.io/encrypted-media/#key-system)) that can be matched exactly with known DRM key systems of the platform. When `key_system` is an empty string, the return value is an indication for non-encrypted media. +An implementation may choose to support `key_system` with extra attributes, +separated by ';', like `com.example.somesystem; attribute_name1="value1"; +attribute_name2=value1`. If `key_system` with attributes is not supported by an +implementation, it should treat `key_system` as if it contains only the key +system, and reject any input containing extra attributes, i.e. it can keep using +its existing implementation. When an implementation supports `key_system` with +attributes, it has to support all attributes defined by the Starboard version +the implementation uses. An implementation should ignore any unknown attributes, +and make a decision solely based on the key system and the known attributes. For +example, if an implementation supports "com.widevine.alpha", it should also +return `kSbMediaSupportTypeProbably` kSbMediaSupportTypeProbably when +`key_system` is `com.widevine.alpha; invalid_attribute="invalid_value"`. +Currently the only attribute has to be supported is `encryptionscheme`. It +reflects the value passed to `encryptionScheme` encryptionScheme of +MediaKeySystemMediaCapability, as defined in [https://wicg.github.io/encrypted-media-encryption-scheme/,](https://wicg.github.io/encrypted-media-encryption-scheme/,),) which can take value "cenc", "cbcs", or "cbcs-1-9". Empty string is +not a valid value for `encryptionscheme` and the implementation should return +`kSbMediaSupportTypeNotSupported` kSbMediaSupportTypeNotSupported when +`encryptionscheme` is set to "". The implementation should return +`kSbMediaSupportTypeNotSupported` kSbMediaSupportTypeNotSupported for unknown +values of known attributes. For example, if an implementation supports +"encryptionscheme" with value "cenc", "cbcs", or "cbcs-1-9", then it should +return `kSbMediaSupportTypeProbably` kSbMediaSupportTypeProbably when +`key_system` is `com.widevine.alpha; encryptionscheme="cenc"`, and return +`kSbMediaSupportTypeNotSupported` kSbMediaSupportTypeNotSupported when +`key_system` is `com.widevine.alpha; encryptionscheme="invalid"`. If an +implementation supports key system with attributes on one key system, it has to +support key system with attributes on all key systems supported. + #### Declaration #### ```
diff --git a/src/cobalt/site/docs/reference/starboard/modules/12/media.md b/src/cobalt/site/docs/reference/starboard/modules/12/media.md index 5a50b33..1ca8e06 100644 --- a/src/cobalt/site/docs/reference/starboard/modules/12/media.md +++ b/src/cobalt/site/docs/reference/starboard/modules/12/media.md
@@ -429,11 +429,39 @@ `mime`: The mime information of the media in the form of `video/webm` or `video/mp4; codecs="avc1.42001E"`. It may include arbitrary parameters like "codecs", "channels", etc. Note that the "codecs" parameter may contain more -than one codec, delimited by comma. `key_system`: A lowercase value in fhe form +than one codec, delimited by comma. `key_system`: A lowercase value in the form of "com.example.somesystem" as suggested by [https://w3c.github.io/encrypted-media/#key-system](https://w3c.github.io/encrypted-media/#key-system)) that can be matched exactly with known DRM key systems of the platform. When `key_system` is an empty string, the return value is an indication for non-encrypted media. +An implementation may choose to support `key_system` with extra attributes, +separated by ';', like `com.example.somesystem; attribute_name1="value1"; +attribute_name2=value1`. If `key_system` with attributes is not supported by an +implementation, it should treat `key_system` as if it contains only the key +system, and reject any input containing extra attributes, i.e. it can keep using +its existing implementation. When an implementation supports `key_system` with +attributes, it has to support all attributes defined by the Starboard version +the implementation uses. An implementation should ignore any unknown attributes, +and make a decision solely based on the key system and the known attributes. For +example, if an implementation supports "com.widevine.alpha", it should also +return `kSbMediaSupportTypeProbably` kSbMediaSupportTypeProbably when +`key_system` is `com.widevine.alpha; invalid_attribute="invalid_value"`. +Currently the only attribute has to be supported is `encryptionscheme`. It +reflects the value passed to `encryptionScheme` encryptionScheme of +MediaKeySystemMediaCapability, as defined in [https://wicg.github.io/encrypted-media-encryption-scheme/,](https://wicg.github.io/encrypted-media-encryption-scheme/,),) which can take value "cenc", "cbcs", or "cbcs-1-9". Empty string is +not a valid value for `encryptionscheme` and the implementation should return +`kSbMediaSupportTypeNotSupported` kSbMediaSupportTypeNotSupported when +`encryptionscheme` is set to "". The implementation should return +`kSbMediaSupportTypeNotSupported` kSbMediaSupportTypeNotSupported for unknown +values of known attributes. For example, if an implementation supports +"encryptionscheme" with value "cenc", "cbcs", or "cbcs-1-9", then it should +return `kSbMediaSupportTypeProbably` kSbMediaSupportTypeProbably when +`key_system` is `com.widevine.alpha; encryptionscheme="cenc"`, and return +`kSbMediaSupportTypeNotSupported` kSbMediaSupportTypeNotSupported when +`key_system` is `com.widevine.alpha; encryptionscheme="invalid"`. If an +implementation supports key system with attributes on one key system, it has to +support key system with attributes on all key systems supported. + #### Declaration #### ```
diff --git a/src/cobalt/site/docs/reference/starboard/modules/media.md b/src/cobalt/site/docs/reference/starboard/modules/media.md index 5a50b33..1ca8e06 100644 --- a/src/cobalt/site/docs/reference/starboard/modules/media.md +++ b/src/cobalt/site/docs/reference/starboard/modules/media.md
@@ -429,11 +429,39 @@ `mime`: The mime information of the media in the form of `video/webm` or `video/mp4; codecs="avc1.42001E"`. It may include arbitrary parameters like "codecs", "channels", etc. Note that the "codecs" parameter may contain more -than one codec, delimited by comma. `key_system`: A lowercase value in fhe form +than one codec, delimited by comma. `key_system`: A lowercase value in the form of "com.example.somesystem" as suggested by [https://w3c.github.io/encrypted-media/#key-system](https://w3c.github.io/encrypted-media/#key-system)) that can be matched exactly with known DRM key systems of the platform. When `key_system` is an empty string, the return value is an indication for non-encrypted media. +An implementation may choose to support `key_system` with extra attributes, +separated by ';', like `com.example.somesystem; attribute_name1="value1"; +attribute_name2=value1`. If `key_system` with attributes is not supported by an +implementation, it should treat `key_system` as if it contains only the key +system, and reject any input containing extra attributes, i.e. it can keep using +its existing implementation. When an implementation supports `key_system` with +attributes, it has to support all attributes defined by the Starboard version +the implementation uses. An implementation should ignore any unknown attributes, +and make a decision solely based on the key system and the known attributes. For +example, if an implementation supports "com.widevine.alpha", it should also +return `kSbMediaSupportTypeProbably` kSbMediaSupportTypeProbably when +`key_system` is `com.widevine.alpha; invalid_attribute="invalid_value"`. +Currently the only attribute has to be supported is `encryptionscheme`. It +reflects the value passed to `encryptionScheme` encryptionScheme of +MediaKeySystemMediaCapability, as defined in [https://wicg.github.io/encrypted-media-encryption-scheme/,](https://wicg.github.io/encrypted-media-encryption-scheme/,),) which can take value "cenc", "cbcs", or "cbcs-1-9". Empty string is +not a valid value for `encryptionscheme` and the implementation should return +`kSbMediaSupportTypeNotSupported` kSbMediaSupportTypeNotSupported when +`encryptionscheme` is set to "". The implementation should return +`kSbMediaSupportTypeNotSupported` kSbMediaSupportTypeNotSupported for unknown +values of known attributes. For example, if an implementation supports +"encryptionscheme" with value "cenc", "cbcs", or "cbcs-1-9", then it should +return `kSbMediaSupportTypeProbably` kSbMediaSupportTypeProbably when +`key_system` is `com.widevine.alpha; encryptionscheme="cenc"`, and return +`kSbMediaSupportTypeNotSupported` kSbMediaSupportTypeNotSupported when +`key_system` is `com.widevine.alpha; encryptionscheme="invalid"`. If an +implementation supports key system with attributes on one key system, it has to +support key system with attributes on all key systems supported. + #### Declaration #### ```
diff --git a/src/cobalt/tools/automated_testing/cobalt_runner.py b/src/cobalt/tools/automated_testing/cobalt_runner.py index 4f627f7..22479f7 100644 --- a/src/cobalt/tools/automated_testing/cobalt_runner.py +++ b/src/cobalt/tools/automated_testing/cobalt_runner.py
@@ -9,10 +9,10 @@ import os import re import sys -import thread import threading import time import traceback +import thread import _env # pylint: disable=unused-import from cobalt.tools.automated_testing import c_val_names @@ -26,8 +26,8 @@ RE_WEBDRIVER_FAILED = re.compile(r'Could not start WebDriver server') # Pattern to match Cobalt log line for when a WindowDriver has been created. RE_WINDOWDRIVER_CREATED = re.compile( - r'^\[[\d:]+/[\d.]+:INFO:browser_module\.cc\(\d+\)\] Created WindowDriver: ID=\S+' -) + (r'^\[[\d:]+/[\d.]+:INFO:browser_module\.cc\(\d+\)\] Created WindowDriver: ' + r'ID=\S+')) # Pattern to match Cobalt log line for when a WebModule is has been loaded. RE_WEBMODULE_LOADED = re.compile( r'^\[[\d:]+/[\d.]+:INFO:browser_module\.cc\(\d+\)\] Loaded WebModule') @@ -231,7 +231,7 @@ self.WaitForStart() except KeyboardInterrupt: # potentially from thread.interrupt_main(). We will treat as - # a timeout regardless + # a timeout regardless. self.Exit(should_fail=True) raise TimeoutException @@ -270,8 +270,8 @@ self.runner_thread.join(COBALT_EXIT_TIMEOUT_SECONDS) if self.runner_thread.isAlive(): sys.stderr.write( - '***Runner thread still alive after sending graceful shutdown command, try again by killing app***\n' - ) + '***Runner thread still alive after sending graceful shutdown ' + 'command, try again by killing app***\n') self.launcher.Kill() # Once the write end of the pipe has been closed by the launcher, the reader # thread will get EOF and exit. @@ -312,7 +312,7 @@ logging.info('Cobalt terminated.') if not self.failed and self.success_message: print('{}\n'.format(self.success_message)) - logging.info('{}\n'.format(self.success_message)) + logging.info('%s\n', self.success_message) # pylint: disable=broad-except except Exception as ex: sys.stderr.write('Exception running Cobalt ' + str(ex)) @@ -329,7 +329,11 @@ def ExecuteJavaScript(self, js_code): retry_count = 0 - while retry_count < EXECUTE_JAVASCRIPT_RETRY_LIMIT: + while True: + if retry_count >= EXECUTE_JAVASCRIPT_RETRY_LIMIT: + raise TimeoutException( + 'Selenium element or window not found in {} tries'.format( + EXECUTE_JAVASCRIPT_RETRY_LIMIT)) retry_count += 1 try: result = self.webdriver.execute_script(js_code) @@ -337,9 +341,6 @@ selenium_exceptions.NoSuchWindowException): time.sleep(0.2) continue - except Exception: - sys.excepthook(*sys.exc_info()) - logging.exception("Failed with unexpected exception") break return result @@ -354,15 +355,14 @@ """ javascript_code = 'return h5vcc.cVal.getValue(\'{}\')'.format(cval_name) cval_string = self.ExecuteJavaScript(javascript_code) - if cval_string is None: - return None - else: + if cval_string: try: # Try to parse numbers and booleans. return json.loads(cval_string) except ValueError: # If we can't parse a value, return the cval string as-is. return cval_string + return None def GetCvalBatch(self, cval_name_list): """Retrieves a batch of cvals. @@ -454,7 +454,11 @@ # after navigation. We only introduced it because of limited time budget # at the moment, please don't introduce any code that relies on it. retry_count = 0 - while retry_count < FIND_ELEMENT_RETRY_LIMIT: + while True: + if retry_count >= FIND_ELEMENT_RETRY_LIMIT: + raise TimeoutException( + 'Selenium element or window not found in {} tries'.format( + FIND_ELEMENT_RETRY_LIMIT)) retry_count += 1 try: elements = self.webdriver.find_elements_by_css_selector(css_selector) @@ -462,11 +466,8 @@ selenium_exceptions.NoSuchWindowException): time.sleep(0.2) continue - except Exception: - sys.excepthook(*sys.exc_info()) - logging.exception("Failed with unexpected exception") break - if expected_num is not None and len(elements) != expected_num: + if expected_num and len(elements) != expected_num: raise CobaltRunner.AssertException( 'Expected number of element {} is: {}, got {}'.format( css_selector, expected_num, len(elements)))
diff --git a/src/cobalt/updater/configurator.cc b/src/cobalt/updater/configurator.cc index 210f088..a10bd03 100644 --- a/src/cobalt/updater/configurator.cc +++ b/src/cobalt/updater/configurator.cc
@@ -1,4 +1,4 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. +// Copyright 2020 The Cobalt Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -28,33 +28,35 @@ // Default time constants. const int kDelayOneMinute = 60; const int kDelayOneHour = kDelayOneMinute * 60; +const std::set<std::string> valid_channels = { + // Default channel for debug/devel builds. + "dev", + // Channel for dogfooders. + "dogfood", + // Default channel for gold builds. + "prod", + // Default channel for qa builds. A gold build can switch to this channel to + // get an official qa build. + "qa", + // Test an update with higher version than prod channel. + "test", + // Test an update with mismatched sabi. + "tmsabi", + // Test an update that does nothing. + "tnoop", + // Test an update that crashes. + "tcrash", + // Test an update that fails verification. + "tfailv", + // Test a series of continuous updates with two channels. + "tseries1", "tseries2", +}; #if defined(COBALT_BUILD_TYPE_DEBUG) || defined(COBALT_BUILD_TYPE_DEVEL) -const std::set<std::string> valid_channels = {"dev"}; const std::string kDefaultUpdaterChannel = "dev"; #elif defined(COBALT_BUILD_TYPE_QA) -// Find more information about these test channels in the Evergreen test plan. -const std::set<std::string> valid_channels = { - "qa", - // A normal test channel that serves a valid update - "test", - // Test an update with mismatched sabi - "tmsabi", - // Test an update that does nothing - "tnoop", - // Test an update that crashes - "tcrash", - // Test an update that fails verification - "tfailv", - // Test a series of continuous updates with two channels - "tseries1", - "tseries2", - // Test an update that's larger than the available storage on the device - "tistore", -}; const std::string kDefaultUpdaterChannel = "qa"; #elif defined(COBALT_BUILD_TYPE_GOLD) -const std::set<std::string> valid_channels = {"prod", "dogfood"}; const std::string kDefaultUpdaterChannel = "prod"; #endif @@ -146,8 +148,8 @@ base::flat_map<std::string, std::string> params; params.insert(std::make_pair("SABI", SB_SABI_JSON_ID)); params.insert(std::make_pair("sbversion", std::to_string(SB_API_VERSION))); - params.insert(std::make_pair( - "jsengine", script::GetJavaScriptEngineNameAndVersion())); + params.insert( + std::make_pair("jsengine", script::GetJavaScriptEngineNameAndVersion())); params.insert(std::make_pair( "updaterchannelchanged", SbAtomicNoBarrier_Load(&is_channel_changed_) == 1 ? "True" : "False")); @@ -252,5 +254,16 @@ updater_status_ = status; } +std::string Configurator::GetPreviousUpdaterStatus() const { + base::AutoLock auto_lock( + const_cast<base::Lock&>(previous_updater_status_lock_)); + return previous_updater_status_; +} + +void Configurator::SetPreviousUpdaterStatus(const std::string& status) { + base::AutoLock auto_lock(previous_updater_status_lock_); + previous_updater_status_ = status; +} + } // namespace updater } // namespace cobalt
diff --git a/src/cobalt/updater/configurator.h b/src/cobalt/updater/configurator.h index e335b4f..cfd1488 100644 --- a/src/cobalt/updater/configurator.h +++ b/src/cobalt/updater/configurator.h
@@ -81,9 +81,12 @@ bool IsChannelValid(const std::string& channel); - std::string GetUpdaterStatus() const; + std::string GetUpdaterStatus() const override; void SetUpdaterStatus(const std::string& status); + std::string GetPreviousUpdaterStatus() const override; + void SetPreviousUpdaterStatus(const std::string& status) override; + private: friend class base::RefCountedThreadSafe<Configurator>; ~Configurator() override; @@ -98,6 +101,8 @@ SbAtomic32 is_channel_changed_; std::string updater_status_; base::Lock updater_status_lock_; + std::string previous_updater_status_; + base::Lock previous_updater_status_lock_; DISALLOW_COPY_AND_ASSIGN(Configurator); };
diff --git a/src/cobalt/updater/noop_sandbox.cc b/src/cobalt/updater/noop_sandbox.cc index d9ce028..5f63965 100644 --- a/src/cobalt/updater/noop_sandbox.cc +++ b/src/cobalt/updater/noop_sandbox.cc
@@ -15,7 +15,12 @@ // This is a test app for Evergreen that does nothing. #include "starboard/event.h" +#include "starboard/system.h" +#include "starboard/thread.h" +#include "starboard/time.h" void SbEventHandle(const SbEvent* event) { - // noop + // No-op app. Exit after 1s. + SbThreadSleep(kSbTimeSecond); + SbSystemRequestStop(0); }
diff --git a/src/cobalt/updater/updater_module.cc b/src/cobalt/updater/updater_module.cc index 2f0452b..9cbbd52 100644 --- a/src/cobalt/updater/updater_module.cc +++ b/src/cobalt/updater/updater_module.cc
@@ -50,8 +50,8 @@ 0x9e, 0x8b, 0x2d, 0x22, 0x65, 0x19, 0xb1, 0xfa, 0xba, 0x02, 0x04, 0x3a, 0xb2, 0x7a, 0xf6, 0xfe, 0xd5, 0x35, 0xa1, 0x19, 0xd9}; -// The map to translate updater status from enum to readable string. -const std::map<ComponentState, const char*> updater_status_map = { +// The map to translate update state from ComponentState to readable string. +const std::map<ComponentState, const char*> update_state_map = { {ComponentState::kNew, "Will check for update soon"}, {ComponentState::kChecking, "Checking for update"}, {ComponentState::kCanUpdate, "Update is available"}, @@ -78,9 +78,15 @@ void Observer::OnEvent(Events event, const std::string& id) { std::string status; if (update_client_->GetCrxUpdateState(id, &crx_update_item_)) { - auto status_iterator = updater_status_map.find(crx_update_item_.state); - if (status_iterator == updater_status_map.end()) { + auto status_iterator = update_state_map.find(crx_update_item_.state); + if (status_iterator == update_state_map.end()) { status = "Status is unknown."; + } else if (crx_update_item_.state == ComponentState::kUpToDate && + updater_configurator_->GetPreviousUpdaterStatus().compare( + update_state_map.find(ComponentState::kUpdated)->second) == + 0) { + status = + std::string(update_state_map.find(ComponentState::kUpdated)->second); } else { status = std::string(status_iterator->second); }
diff --git a/src/cobalt/version.h b/src/cobalt/version.h index 08526a7..acdcdfa 100644 --- a/src/cobalt/version.h +++ b/src/cobalt/version.h
@@ -35,6 +35,6 @@ // release is cut. //. -#define COBALT_VERSION "21.lts.1" +#define COBALT_VERSION "21.lts.2" #endif // COBALT_VERSION_H_
diff --git a/src/cobalt/xhr/url_fetcher_buffer_writer.cc b/src/cobalt/xhr/url_fetcher_buffer_writer.cc index 3b43b8a..fd67bb4 100644 --- a/src/cobalt/xhr/url_fetcher_buffer_writer.cc +++ b/src/cobalt/xhr/url_fetcher_buffer_writer.cc
@@ -26,6 +26,9 @@ // Allocate 64KB if the total size is unknown to avoid allocating small buffer // too many times. const int64_t kDefaultPreAllocateSizeInBytes = 64 * 1024; +// Set max allocate size to avoid erroneous size estimate. +const int64_t kMaxPreAllocateSizeInBytes = 10 * 1024 * 1024; +const uint8_t kResizingMultiplier = 2; void ReleaseMemory(std::string* str) { DCHECK(str); @@ -158,9 +161,16 @@ if (capacity < 0) { capacity = kDefaultPreAllocateSizeInBytes; + } else if (capacity > kMaxPreAllocateSizeInBytes) { + LOG(WARNING) << "Allocation of " << capacity << " bytes is capped to " + << kMaxPreAllocateSizeInBytes; + capacity = kMaxPreAllocateSizeInBytes; } else { capacity_known_ = true; } + // Record the desired_capacity_ to avoid reserving unused memory during + // resizing. + desired_capacity_ = static_cast<size_t>(capacity); if (capacity == 0) { return; @@ -212,7 +222,16 @@ SB_LOG(WARNING) << "Data written is larger than the preset capacity " << data_as_array_buffer_.byte_length(); } - data_as_array_buffer_.Resize(data_as_array_buffer_size_ + num_bytes); + size_t new_size = std::max( + std::min(data_as_array_buffer_.byte_length() * kResizingMultiplier, + desired_capacity_), + data_as_array_buffer_size_ + num_bytes); + if (new_size > desired_capacity_) { + // Content-length is wrong, response size is completely unknown. + // Double the capacity to avoid frequent resizing. + new_size *= kResizingMultiplier; + } + data_as_array_buffer_.Resize(new_size); } auto destination = static_cast<uint8_t*>(data_as_array_buffer_.data()) +
diff --git a/src/cobalt/xhr/url_fetcher_buffer_writer.h b/src/cobalt/xhr/url_fetcher_buffer_writer.h index 8eb9736..35058ab 100644 --- a/src/cobalt/xhr/url_fetcher_buffer_writer.h +++ b/src/cobalt/xhr/url_fetcher_buffer_writer.h
@@ -74,6 +74,7 @@ Type type_; bool allow_preallocate_ = true; bool capacity_known_ = false; + size_t desired_capacity_ = 0; // This class can be accessed by both network and MainWebModule threads. mutable base::Lock lock_;
diff --git a/src/cobalt/xhr/xhr.gyp b/src/cobalt/xhr/xhr.gyp index 051822e..708f710 100644 --- a/src/cobalt/xhr/xhr.gyp +++ b/src/cobalt/xhr/xhr.gyp
@@ -23,8 +23,6 @@ 'sources': [ 'url_fetcher_buffer_writer.cc', 'url_fetcher_buffer_writer.h', - 'xhr_response_data.cc', - 'xhr_response_data.h', 'xml_http_request.cc', 'xml_http_request.h', 'xml_http_request_event_target.cc', @@ -58,7 +56,6 @@ 'target_name': 'xhr_test', 'type': '<(gtest_target_type)', 'sources': [ - 'xhr_response_data_test.cc', 'xml_http_request_test.cc', ], 'dependencies': [
diff --git a/src/cobalt/xhr/xhr_response_data.cc b/src/cobalt/xhr/xhr_response_data.cc deleted file mode 100644 index d2e5051..0000000 --- a/src/cobalt/xhr/xhr_response_data.cc +++ /dev/null
@@ -1,84 +0,0 @@ -// Copyright 2015 The Cobalt Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "cobalt/xhr/xhr_response_data.h" - -#include <algorithm> - -#include "cobalt/dom/global_stats.h" - -namespace cobalt { -namespace xhr { - -namespace { - -// When we don't have any data, we still want to return a non-null pointer to a -// valid memory location. Because even it will never be accessed, a null -// pointer may trigger undefined behavior in functions like memcpy. So we -// create this dummy value here and return its address when we don't have any -// data. -uint8 s_dummy; - -// We are using std::string to store binary data so we want to ensure that char -// occupies one byte. -COMPILE_ASSERT(sizeof(char) == 1, char_should_occupy_one_byte); - -} // namespace - -XhrResponseData::XhrResponseData() { IncreaseMemoryUsage(); } - -XhrResponseData::~XhrResponseData() { DecreaseMemoryUsage(); } - -void XhrResponseData::Clear() { - DecreaseMemoryUsage(); - // Use swap to force free the memory allocated. - std::string dummy; - data_.swap(dummy); - IncreaseMemoryUsage(); -} - -void XhrResponseData::Reserve(size_t new_capacity_bytes) { - DecreaseMemoryUsage(); - data_.reserve(new_capacity_bytes); - IncreaseMemoryUsage(); -} - -void XhrResponseData::Append(const uint8* source_data, size_t size_bytes) { - if (size_bytes == 0) { - return; - } - DecreaseMemoryUsage(); - data_.resize(data_.size() + size_bytes); - memcpy(&data_[data_.size() - size_bytes], source_data, size_bytes); - IncreaseMemoryUsage(); -} - -const uint8* XhrResponseData::data() const { - return data_.empty() ? &s_dummy : reinterpret_cast<const uint8*>(&data_[0]); -} - -uint8* XhrResponseData::data() { - return data_.empty() ? &s_dummy : reinterpret_cast<uint8*>(&data_[0]); -} - -void XhrResponseData::IncreaseMemoryUsage() { - dom::GlobalStats::GetInstance()->IncreaseXHRMemoryUsage(capacity()); -} - -void XhrResponseData::DecreaseMemoryUsage() { - dom::GlobalStats::GetInstance()->DecreaseXHRMemoryUsage(capacity()); -} - -} // namespace xhr -} // namespace cobalt
diff --git a/src/cobalt/xhr/xhr_response_data.h b/src/cobalt/xhr/xhr_response_data.h deleted file mode 100644 index 894cd3f..0000000 --- a/src/cobalt/xhr/xhr_response_data.h +++ /dev/null
@@ -1,57 +0,0 @@ -// Copyright 2015 The Cobalt Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef COBALT_XHR_XHR_RESPONSE_DATA_H_ -#define COBALT_XHR_XHR_RESPONSE_DATA_H_ - -#include <string> - -#include "base/basictypes.h" - -namespace cobalt { -namespace xhr { - -// Simple wrapper for an array of data. -// Used by XMLHttpRequest to construct the response body. -class XhrResponseData { - public: - XhrResponseData(); - ~XhrResponseData(); - - // Destroy the data_ and reset the size and capacity to 0. - void Clear(); - // Allocate storage for |new_capacity_bytes| of data. - void Reserve(size_t new_capacity_bytes); - // Append |source_data|, |size_bytes| in length, to the data array. - void Append(const uint8* source_data, size_t size_bytes); - - const uint8* data() const; - uint8* data(); - - const std::string& string() const { return data_; } - - size_t size() const { return data_.size(); } - size_t capacity() const { return data_.capacity(); } - - private: - void IncreaseMemoryUsage(); - void DecreaseMemoryUsage(); - - std::string data_; -}; - -} // namespace xhr -} // namespace cobalt - -#endif // COBALT_XHR_XHR_RESPONSE_DATA_H_
diff --git a/src/cobalt/xhr/xhr_response_data_test.cc b/src/cobalt/xhr/xhr_response_data_test.cc deleted file mode 100644 index f63d70e..0000000 --- a/src/cobalt/xhr/xhr_response_data_test.cc +++ /dev/null
@@ -1,55 +0,0 @@ -// Copyright 2015 The Cobalt Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "cobalt/xhr/xhr_response_data.h" - -#include "testing/gtest/include/gtest/gtest.h" - -namespace cobalt { -namespace xhr { - -namespace { - -TEST(XhrResponseData, InitialState) { - XhrResponseData empty; - EXPECT_EQ(0u, empty.size()); - EXPECT_TRUE(empty.data() != NULL); -} - -TEST(XhrResponseData, Append) { - XhrResponseData data; - uint8 raw_data[64]; - for (int i = 0; i < 64; ++i) { - raw_data[i] = static_cast<uint8>(i); - } - data.Append(raw_data, 64); - EXPECT_EQ(64u, data.size()); - EXPECT_LE(64u, data.capacity()); - - for (int i = 0; i < 64; ++i) { - EXPECT_EQ(raw_data[i], data.data()[i]); - } -} - -TEST(XhrResponseData, Reserve) { - XhrResponseData data; - data.Reserve(1); - EXPECT_LE(1u, data.capacity()); - EXPECT_EQ(0u, data.size()); - EXPECT_TRUE(data.data() != NULL); -} - -} // namespace -} // namespace xhr -} // namespace cobalt
diff --git a/src/components/update_client/component.cc b/src/components/update_client/component.cc index 50ec274..9be0c39 100644 --- a/src/components/update_client/component.cc +++ b/src/components/update_client/component.cc
@@ -559,6 +559,12 @@ DCHECK(thread_checker_.CalledOnValidThread()); auto& component = State::component(); + +#if defined(OS_STARBOARD) + auto& config = component.update_context_.config; + config->SetPreviousUpdaterStatus(config->GetUpdaterStatus()); +#endif + if (component.crx_component()) { TransitionState(std::make_unique<StateChecking>(&component)); } else {
diff --git a/src/components/update_client/configurator.h b/src/components/update_client/configurator.h index ab7e01e..64d931a 100644 --- a/src/components/update_client/configurator.h +++ b/src/components/update_client/configurator.h
@@ -171,6 +171,11 @@ // parameters. virtual void SetChannel(const std::string& channel) = 0; + virtual std::string GetPreviousUpdaterStatus() const = 0; + virtual void SetPreviousUpdaterStatus(const std::string& status) = 0; + + virtual std::string GetUpdaterStatus() const = 0; + // Compare and swap the is_channel_changed flag. virtual void CompareAndSwapChannelChanged(int old_value, int new_value) = 0;
diff --git a/src/starboard/android/apk/app/build.gradle b/src/starboard/android/apk/app/build.gradle index 5ab0feb..8e527e2 100644 --- a/src/starboard/android/apk/app/build.gradle +++ b/src/starboard/android/apk/app/build.gradle
@@ -105,8 +105,6 @@ signingConfig signingConfigs.debugConfig } qa { - debuggable true - jniDebuggable true externalNativeBuild { cmake.arguments "-DCOBALT_CONFIG=qa" }
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java index 9a609c0..cb9948d 100644 --- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java +++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
@@ -27,6 +27,7 @@ import android.view.ViewGroup.LayoutParams; import android.view.ViewParent; import android.widget.FrameLayout; +import dev.cobalt.media.MediaCodecUtil; import dev.cobalt.media.VideoSurfaceView; import dev.cobalt.util.Log; import dev.cobalt.util.UsedByNative; @@ -45,7 +46,9 @@ private static final java.lang.String META_DATA_APP_URL = "cobalt.APP_URL"; private static final String SPLASH_URL_ARG = "--fallback_splash_screen_url="; + private static final String SPLASH_TOPICS_ARG = "--fallback_splash_screen_topics="; private static final java.lang.String META_DATA_SPLASH_URL = "cobalt.SPLASH_URL"; + private static final java.lang.String META_DATA_SPLASH_TOPICS = "cobalt.SPLASH_TOPIC"; private static final String FORCE_MIGRATION_FOR_STORAGE_PARTITIONING = "--force_migration_for_storage_partitioning"; @@ -107,6 +110,9 @@ @Override protected void onStart() { + if (!isReleaseBuild()) { + MediaCodecUtil.dumpAllDecoders(); + } if (forceCreateNewVideoSurfaceView) { Log.w(TAG, "Force to create a new video surface."); createNewSurfaceView(); @@ -172,7 +178,9 @@ boolean hasUrlArg = hasArg(args, URL_ARG); // If the splash screen url arg isn't specified, get it from AndroidManifest.xml. boolean hasSplashUrlArg = hasArg(args, SPLASH_URL_ARG); - if (!hasUrlArg || !hasSplashUrlArg) { + // If the splash screen topics arg isn't specified, get it from AndroidManifest.xml. + boolean hasSplashTopicsArg = hasArg(args, SPLASH_TOPICS_ARG); + if (!hasUrlArg || !hasSplashUrlArg || !hasSplashTopicsArg) { try { ActivityInfo ai = getPackageManager() @@ -190,6 +198,12 @@ args.add(SPLASH_URL_ARG + splashUrl); } } + if (!hasSplashTopicsArg) { + String splashTopics = ai.metaData.getString(META_DATA_SPLASH_TOPICS); + if (splashTopics != null) { + args.add(SPLASH_TOPICS_ARG + splashTopics); + } + } if (ai.metaData.getBoolean(META_FORCE_MIGRATION_FOR_STORAGE_PARTITIONING)) { args.add(FORCE_MIGRATION_FOR_STORAGE_PARTITIONING); }
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/VoiceRecognizer.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/VoiceRecognizer.java index 06cc02f..799aef9 100644 --- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/VoiceRecognizer.java +++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/VoiceRecognizer.java
@@ -122,7 +122,8 @@ this.nativeSpeechRecognizerImpl = nativeSpeechRecognizer; if (this.audioPermissionRequester.requestRecordAudioPermission( - this.nativeSpeechRecognizerImpl)) { + this.nativeSpeechRecognizerImpl) && + SpeechRecognizer.isRecognitionAvailable(context)) { startRecognitionInternal(); } else { mainHandler.post(
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java index 554d8d5..b80fa03 100644 --- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java +++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
@@ -48,9 +48,18 @@ @SuppressWarnings("unused") @UsedByNative AudioTrackBridge createAudioTrackBridge( - int sampleType, int sampleRate, int channelCount, int preferredBufferSizeInBytes) { + int sampleType, + int sampleRate, + int channelCount, + int preferredBufferSizeInBytes, + int tunnelModeAudioSessionId) { AudioTrackBridge audioTrackBridge = - new AudioTrackBridge(sampleType, sampleRate, channelCount, preferredBufferSizeInBytes); + new AudioTrackBridge( + sampleType, + sampleRate, + channelCount, + preferredBufferSizeInBytes, + tunnelModeAudioSessionId); if (!audioTrackBridge.isAudioTrackValid()) { Log.e(TAG, "AudioTrackBridge has invalid audio track"); return null; @@ -128,4 +137,29 @@ } return AudioTrack.getMinBufferSize(sampleRate, channelConfig, sampleType); } + + /** Generate audio session id used by tunneled playback. */ + @SuppressWarnings("unused") + @UsedByNative + int generateTunnelModeAudioSessionId(int numberOfChannels) { + // Android 9.0 (Build.VERSION.SDK_INT >= 28) support v2 sync header that + // aligns sync header with audio frame size. V1 sync header has alignment + // issues for multi-channel audio. + if (Build.VERSION.SDK_INT < 28) { + // Currently we only support int16 under tunnel mode. + final int sampleSizeInBytes = 2; + final int frameSizeInBytes = sampleSizeInBytes * numberOfChannels; + if (AudioTrackBridge.AV_SYNC_HEADER_V1_SIZE % frameSizeInBytes != 0) { + Log.w( + TAG, + String.format( + "Disable tunnel mode due to sampleSizeInBytes (%d) * numberOfChannels (%d) isn't" + + " aligned to AV_SYNC_HEADER_V1_SIZE (%d).", + sampleSizeInBytes, numberOfChannels, AudioTrackBridge.AV_SYNC_HEADER_V1_SIZE)); + return -1; + } + } + AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + return audioManager.generateAudioSessionId(); + } }
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java index 419f5d8..b8d5a53 100644 --- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java +++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
@@ -26,18 +26,45 @@ import dev.cobalt.util.Log; import dev.cobalt.util.UsedByNative; import java.nio.ByteBuffer; +import java.nio.ByteOrder; -// A wrapper of the android AudioTrack class. -// Android AudioTrack would not start playing until the buffer is fully -// filled once. +/** + * A wrapper of the android AudioTrack class. Android AudioTrack would not start playing until the + * buffer is fully filled once. + */ @UsedByNative public class AudioTrackBridge { + // Also used by AudioOutputManager. + static final int AV_SYNC_HEADER_V1_SIZE = 16; + private AudioTrack audioTrack; private AudioTimestamp audioTimestamp = new AudioTimestamp(); private long maxFramePositionSoFar = 0; + private final boolean tunnelModeEnabled; + // The following variables are used only when |tunnelModeEnabled| is true. + private ByteBuffer avSyncHeader; + private int avSyncPacketBytesRemaining; + + private static int getBytesPerSample(int audioFormat) { + switch (audioFormat) { + case AudioFormat.ENCODING_PCM_16BIT: + return 2; + case AudioFormat.ENCODING_PCM_FLOAT: + return 4; + case AudioFormat.ENCODING_INVALID: + default: + throw new RuntimeException("Unsupport audio format " + audioFormat); + } + } + public AudioTrackBridge( - int sampleType, int sampleRate, int channelCount, int preferredBufferSizeInBytes) { + int sampleType, + int sampleRate, + int channelCount, + int preferredBufferSizeInBytes, + int tunnelModeAudioSessionId) { + tunnelModeEnabled = tunnelModeAudioSessionId != -1; int channelConfig; switch (channelCount) { case 1: @@ -53,11 +80,40 @@ throw new RuntimeException("Unsupported channel count: " + channelCount); } - AudioAttributes attributes = - new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) - .setUsage(AudioAttributes.USAGE_MEDIA) - .build(); + AudioAttributes attributes; + if (tunnelModeEnabled) { + // Android 9.0 (Build.VERSION.SDK_INT >= 28) support v2 sync header that aligns sync header + // with audio frame size. V1 sync header has alignment issues for multi-channel audio. + if (Build.VERSION.SDK_INT < 28) { + int frameSize = getBytesPerSample(sampleType) * channelCount; + // This shouldn't happen as it should have been checked in + // AudioOutputManager.generateTunnelModeAudioSessionId(). + if (AV_SYNC_HEADER_V1_SIZE % frameSize != 0) { + audioTrack = null; + String errorMessage = + String.format( + "Enable tunnel mode when frame size is unaligned, " + + "sampleType: %d, channel: %d, sync header size: %d.", + sampleType, channelCount, AV_SYNC_HEADER_V1_SIZE); + Log.e(TAG, errorMessage); + throw new RuntimeException(errorMessage); + } + } + attributes = + new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE) + .setFlags(AudioAttributes.FLAG_HW_AV_SYNC) + .setUsage(AudioAttributes.USAGE_MEDIA) + .build(); + } else { + // TODO: Investigate if we can use |CONTENT_TYPE_MOVIE| for AudioTrack + // used by video playback. + attributes = + new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .setUsage(AudioAttributes.USAGE_MEDIA) + .build(); + } AudioFormat format = new AudioFormat.Builder() .setEncoding(sampleType) @@ -66,6 +122,10 @@ .build(); int audioTrackBufferSize = preferredBufferSizeInBytes; + // TODO: Investigate if this implementation could be refined. + // It is not necessary to loop until 0 since there is new implementation based on + // AudioTrack.getMinBufferSize(). Especially for tunnel mode, it would fail if audio HAL does + // not support tunnel mode and then it is not helpful to retry. while (audioTrackBufferSize > 0) { try { audioTrack = @@ -74,7 +134,9 @@ format, audioTrackBufferSize, AudioTrack.MODE_STREAM, - AudioManager.AUDIO_SESSION_ID_GENERATE); + tunnelModeEnabled + ? tunnelModeAudioSessionId + : AudioManager.AUDIO_SESSION_ID_GENERATE); } catch (Exception e) { audioTrack = null; } @@ -104,6 +166,8 @@ audioTrack.release(); } audioTrack = null; + avSyncHeader = null; + avSyncPacketBytesRemaining = 0; } @SuppressWarnings("unused") @@ -144,15 +208,22 @@ return; } audioTrack.flush(); + avSyncHeader = null; + avSyncPacketBytesRemaining = 0; } @SuppressWarnings("unused") @UsedByNative - private int write(byte[] audioData, int sizeInBytes) { + private int write(byte[] audioData, int sizeInBytes, long presentationTimeInMicroseconds) { if (audioTrack == null) { Log.e(TAG, "Unable to write with NULL audio track."); return 0; } + + if (tunnelModeEnabled) { + return writeWithAvSync(audioData, sizeInBytes, presentationTimeInMicroseconds); + } + if (Build.VERSION.SDK_INT >= 23) { return audioTrack.write(audioData, 0, sizeInBytes, AudioTrack.WRITE_NON_BLOCKING); } else { @@ -161,6 +232,66 @@ } } + private int writeWithAvSync( + byte[] audioData, int sizeInBytes, long presentationTimeInMicroseconds) { + if (audioTrack == null) { + throw new RuntimeException("writeWithAvSync() is called when audioTrack is null."); + } + + if (!tunnelModeEnabled) { + throw new RuntimeException("writeWithAvSync() is called when tunnelModeEnabled is false."); + } + + long presentationTimeInNanoseconds = presentationTimeInMicroseconds * 1000; + + // Android support tunnel mode from 5.0 (API level 21), but the app has to manually write the + // sync header before API 23, where the write() function with presentation timestamp is + // introduced. + // Set the following constant to |false| to test manual sync header writing in API level 23 or + // later. Note that the code to write sync header manually only supports v1 sync header. + final boolean useAutoSyncHeaderWrite = false; + if (useAutoSyncHeaderWrite && Build.VERSION.SDK_INT >= 23) { + ByteBuffer byteBuffer = ByteBuffer.wrap(audioData); + return audioTrack.write( + byteBuffer, sizeInBytes, AudioTrack.WRITE_NON_BLOCKING, presentationTimeInNanoseconds); + } + + if (avSyncHeader == null) { + avSyncHeader = ByteBuffer.allocate(AV_SYNC_HEADER_V1_SIZE); + avSyncHeader.order(ByteOrder.BIG_ENDIAN); + avSyncHeader.putInt(0x55550001); + } + + if (avSyncPacketBytesRemaining == 0) { + avSyncHeader.putInt(4, sizeInBytes); + avSyncHeader.putLong(8, presentationTimeInNanoseconds); + avSyncHeader.position(0); + avSyncPacketBytesRemaining = sizeInBytes; + } + + if (avSyncHeader.remaining() > 0) { + int ret = + audioTrack.write(avSyncHeader, avSyncHeader.remaining(), AudioTrack.WRITE_NON_BLOCKING); + if (ret < 0) { + avSyncPacketBytesRemaining = 0; + return ret; + } + if (avSyncHeader.remaining() > 0) { + return 0; + } + } + + int sizeToWrite = Math.min(avSyncPacketBytesRemaining, sizeInBytes); + ByteBuffer byteBuffer = ByteBuffer.wrap(audioData); + int ret = audioTrack.write(byteBuffer, sizeToWrite, AudioTrack.WRITE_NON_BLOCKING); + if (ret < 0) { + avSyncPacketBytesRemaining = 0; + return ret; + } + avSyncPacketBytesRemaining -= ret; + return ret; + } + @SuppressWarnings("unused") @UsedByNative private int write(float[] audioData, int sizeInFloats) { @@ -168,6 +299,9 @@ Log.e(TAG, "Unable to write with NULL audio track."); return 0; } + if (tunnelModeEnabled) { + throw new RuntimeException("Float sample is not supported under tunnel mode."); + } return audioTrack.write(audioData, 0, sizeInFloats, AudioTrack.WRITE_NON_BLOCKING); }
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java index faa8742..d0585e9 100644 --- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java +++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
@@ -592,8 +592,25 @@ mediaFormat.setByteBuffer(MediaFormat.KEY_HDR_STATIC_INFO, colorInfo.hdrStaticInfo); } - int maxWidth = findVideoDecoderResult.videoCapabilities.getSupportedWidths().getUpper(); - int maxHeight = findVideoDecoderResult.videoCapabilities.getSupportedHeights().getUpper(); + VideoCapabilities videoCapabilities = findVideoDecoderResult.videoCapabilities; + int maxWidth = videoCapabilities.getSupportedWidths().getUpper(); + int maxHeight = videoCapabilities.getSupportedHeights().getUpper(); + if (!videoCapabilities.isSizeSupported(maxWidth, maxHeight)) { + if (maxHeight >= 4320 && videoCapabilities.isSizeSupported(7680, 4320)) { + maxWidth = 7680; + maxHeight = 4320; + } else if (maxHeight >= 2160 && videoCapabilities.isSizeSupported(3840, 2160)) { + maxWidth = 3840; + maxHeight = 2160; + } else if (maxHeight >= 1080 && videoCapabilities.isSizeSupported(1920, 1080)) { + maxWidth = 1920; + maxHeight = 1080; + } else { + Log.e(TAG, "Failed to find a compatible resolution"); + maxWidth = 1920; + maxHeight = 1080; + } + } if (!bridge.configureVideo( mediaFormat, surface, @@ -915,8 +932,9 @@ // adapt up to 8k at any point. We thus request 8k buffers up front, // unless the decoder claims to not be able to do 8k, in which case // we're ok, since we would've rejected a 8k stream when canPlayType - // was called, and then use those decoder values instead. - if (Build.VERSION.SDK_INT > 22) { + // was called, and then use those decoder values instead. We only + // support 8k for API level 29 and above. + if (Build.VERSION.SDK_INT > 28) { format.setInteger(MediaFormat.KEY_MAX_WIDTH, Math.min(7680, maxSupportedWidth)); format.setInteger(MediaFormat.KEY_MAX_HEIGHT, Math.min(4320, maxSupportedHeight)); } else {
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java index 40fcde0..03fb7cc 100644 --- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java +++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
@@ -573,21 +573,33 @@ } VideoCapabilities videoCapabilities = codecCapabilities.getVideoCapabilities(); - if (frameWidth != 0 && !videoCapabilities.getSupportedWidths().contains(frameWidth)) { - Log.v( - TAG, - String.format( - "Rejecting %s, reason: supported widths %s does not contain %d", - name, videoCapabilities.getSupportedWidths().toString(), frameWidth)); - continue; - } - if (frameHeight != 0 && !videoCapabilities.getSupportedHeights().contains(frameHeight)) { - Log.v( - TAG, - String.format( - "Rejecting %s, reason: supported heights %s does not contain %d", - name, videoCapabilities.getSupportedHeights().toString(), frameHeight)); - continue; + if (frameWidth != 0 && frameHeight != 0) { + if (!videoCapabilities.isSizeSupported(frameWidth, frameHeight)) { + Log.v( + TAG, + String.format( + "Rejecting %s, reason: width %s is not compatible with height %d", + name, frameWidth, frameHeight)); + continue; + } + } else if (frameWidth != 0) { + if (!videoCapabilities.getSupportedWidths().contains(frameWidth)) { + Log.v( + TAG, + String.format( + "Rejecting %s, reason: supported widths %s does not contain %d", + name, videoCapabilities.getSupportedWidths().toString(), frameWidth)); + continue; + } + } else if (frameHeight != 0) { + if (!videoCapabilities.getSupportedHeights().contains(frameHeight)) { + Log.v( + TAG, + String.format( + "Rejecting %s, reason: supported heights %s does not contain %d", + name, videoCapabilities.getSupportedHeights().toString(), frameHeight)); + continue; + } } if (bitrate != 0 && !videoCapabilities.getBitrateRange().contains(bitrate)) { Log.v( @@ -597,13 +609,31 @@ name, videoCapabilities.getBitrateRange().toString(), bitrate)); continue; } - if (fps != 0 && !videoCapabilities.getSupportedFrameRates().contains(fps)) { - Log.v( - TAG, - String.format( - "Rejecting %s, reason: supported frame rates %s does not contain %d", - name, videoCapabilities.getSupportedFrameRates().toString(), fps)); - continue; + if (fps != 0) { + if (frameHeight != 0 && frameWidth != 0) { + if (!videoCapabilities.areSizeAndRateSupported(frameWidth, frameHeight, fps)) { + Log.v( + TAG, + String.format( + "Rejecting %s, reason: supported frame rates %s does not contain %d", + name, + videoCapabilities + .getSupportedFrameRatesFor(frameWidth, frameHeight) + .toString(), + fps)); + continue; + } + } else { + // At least one of frameHeight or frameWidth is 0 + if (!videoCapabilities.getSupportedFrameRates().contains(fps)) { + Log.v( + TAG, + String.format( + "Rejecting %s, reason: supported frame rates %s does not contain %d", + name, videoCapabilities.getSupportedFrameRates().toString(), fps)); + continue; + } + } } String resultName = (secure && !name.endsWith(SECURE_DECODER_SUFFIX)) @@ -652,52 +682,69 @@ * Debug utility function that can be locally added to dump information about all decoders on a * particular system. */ - @SuppressWarnings("unused") - private static void dumpAllDecoders() { + public static void dumpAllDecoders() { + String decoderDumpString = ""; for (MediaCodecInfo info : new MediaCodecList(MediaCodecList.ALL_CODECS).getCodecInfos()) { if (info.isEncoder()) { continue; } for (String supportedType : info.getSupportedTypes()) { String name = info.getName(); - CodecCapabilities codecCapabilities = info.getCapabilitiesForType(supportedType); - Log.v(TAG, "=================================================="); - Log.v(TAG, String.format("name: %s", name)); - Log.v(TAG, String.format("supportedType: %s", supportedType)); - Log.v( - TAG, String.format("codecBlackList.contains(name): %b", codecBlackList.contains(name))); - Log.v( - TAG, + decoderDumpString += String.format( - "FEATURE_SecurePlayback: %b", - codecCapabilities.isFeatureSupported( - MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback))); + "name: %s (%s, %s):", + name, + supportedType, + codecBlackList.contains(name) ? "blacklisted" : "not blacklisted"); + CodecCapabilities codecCapabilities = info.getCapabilitiesForType(supportedType); VideoCapabilities videoCapabilities = codecCapabilities.getVideoCapabilities(); if (videoCapabilities != null) { - Log.v( - TAG, + decoderDumpString += String.format( - "videoCapabilities.getSupportedWidths(): %s", - videoCapabilities.getSupportedWidths().toString())); - Log.v( - TAG, - String.format( - "videoCapabilities.getSupportedHeights(): %s", - videoCapabilities.getSupportedHeights().toString())); - Log.v( - TAG, - String.format( - "videoCapabilities.getBitrateRange(): %s", - videoCapabilities.getBitrateRange().toString())); - Log.v( - TAG, - String.format( - "videoCapabilities.getSupportedFrameRates(): %s", - videoCapabilities.getSupportedFrameRates().toString())); + "\n\t\t" + + "widths: %s, " + + "heights: %s, " + + "bitrates: %s, " + + "framerates: %s, ", + videoCapabilities.getSupportedWidths().toString(), + videoCapabilities.getSupportedHeights().toString(), + videoCapabilities.getBitrateRange().toString(), + videoCapabilities.getSupportedFrameRates().toString()); } - Log.v(TAG, "=================================================="); - Log.v(TAG, ""); + boolean adaptivePlayback = + codecCapabilities.isFeatureSupported( + MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback); + boolean securePlayback = + codecCapabilities.isFeatureSupported( + MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback); + boolean tunneledPlayback = + codecCapabilities.isFeatureSupported( + MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback); + if (adaptivePlayback || securePlayback || tunneledPlayback) { + decoderDumpString += + String.format( + "(%s%s%s", + adaptivePlayback ? "AdaptivePlayback, " : "", + securePlayback ? "SecurePlayback, " : "", + tunneledPlayback ? "TunneledPlayback, " : ""); + // Remove trailing space and comma + decoderDumpString = decoderDumpString.substring(0, decoderDumpString.length() - 2); + decoderDumpString += ")"; + } else { + decoderDumpString += " No extra features supported"; + } + decoderDumpString += "\n"; } } + Log.v( + TAG, + String.format( + " \n" + + "==================================================\n" + + "Full list of decoder features: [AdaptivePlayback, SecurePlayback," + + " TunneledPlayback]\n" + + "Unsupported features for each codec are not listed\n" + + decoderDumpString + + "==================================================")); } }
diff --git a/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc b/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc index a5a77a7..88f3762 100644 --- a/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc +++ b/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
@@ -123,7 +123,8 @@ min_required_frames_ * task.number_of_channels * GetSampleSize(task.sample_type), &MinRequiredFramesTester::UpdateSourceStatusFunc, - &MinRequiredFramesTester::ConsumeFramesFunc, this); + &MinRequiredFramesTester::ConsumeFramesFunc, + &MinRequiredFramesTester::ErrorFunc, 0, -1, this); { ScopedLock scoped_lock(mutex_); wait_timeout = !condition_variable_.WaitTimed(kSbTimeSecond * 5); @@ -174,6 +175,14 @@ tester->ConsumeFrames(frames_consumed); } +// static +void MinRequiredFramesTester::ErrorFunc(bool capability_changed, + void* context) { + // TODO: Handle errors during minimum frames test, maybe by terminating the + // test earlier. + SB_NOTREACHED(); +} + void MinRequiredFramesTester::UpdateSourceStatus(int* frames_in_buffer, int* offset_in_frames, bool* is_playing,
diff --git a/src/starboard/android/shared/audio_sink_min_required_frames_tester.h b/src/starboard/android/shared/audio_sink_min_required_frames_tester.h index 290011d..e689e96 100644 --- a/src/starboard/android/shared/audio_sink_min_required_frames_tester.h +++ b/src/starboard/android/shared/audio_sink_min_required_frames_tester.h
@@ -85,6 +85,7 @@ static void ConsumeFramesFunc(int frames_consumed, SbTime frames_consumed_at, void* context); + static void ErrorFunc(bool capability_changed, void* context); void UpdateSourceStatus(int* frames_in_buffer, int* offset_in_frames, bool* is_playing,
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.cc b/src/starboard/android/shared/audio_track_audio_sink_type.cc index 44dffc8..cf9d744 100644 --- a/src/starboard/android/shared/audio_track_audio_sink_type.cc +++ b/src/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -29,11 +29,23 @@ namespace shared { namespace { +// The same as AudioTrack.ERROR_DEAD_OBJECT. +const int kAudioTrackErrorDeadObject = -6; + // The maximum number of frames that can be written to android audio track per // write request. If we don't set this cap for writing frames to audio track, // we will repeatedly allocate a large byte array which cannot be consumed by // audio track completely. const int kMaxFramesPerRequest = 65536; + +// Most Android audio HAL updates audio time for A/V synchronization on audio +// sync frames. For example, audio HAL may try to render when it gets an entire +// sync frame and then update audio time. Shorter duration of sync frame +// improves the accuracy of audio time, especially at the beginning of a +// playback, as otherwise the audio time during the initial update may be too +// large (non zero) and results in dropped video frames. +const SbTime kMaxDurationPerRequestInTunnelMode = 16 * kSbTimeMillisecond; + const jint kNoOffset = 0; const size_t kSilenceFramesPerAppend = 1024; @@ -74,6 +86,12 @@ return static_cast<uint8_t*>(pointer) + offset; } +int GetMaxFramesPerRequestForTunnelMode(int sampling_frequency_hz) { + auto max_frames = kMaxDurationPerRequestInTunnelMode * sampling_frequency_hz / + kSbTimeSecond; + return (max_frames + 15) / 16 * 16; // align to 16 +} + } // namespace AudioTrackAudioSink::AudioTrackAudioSink( @@ -86,6 +104,9 @@ int preferred_buffer_size_in_bytes, SbAudioSinkUpdateSourceStatusFunc update_source_status_func, ConsumeFramesFunc consume_frames_func, + SbAudioSinkPrivate::ErrorFunc error_func, + SbTime start_time, + int tunnel_mode_audio_session_id, void* context) : type_(type), channels_(channels), @@ -95,40 +116,56 @@ frames_per_channel_(frames_per_channel), update_source_status_func_(update_source_status_func), consume_frames_func_(consume_frames_func), - context_(context), - last_playback_head_position_(0), - j_audio_track_bridge_(NULL), - j_audio_data_(NULL), - quit_(false), - audio_out_thread_(kSbThreadInvalid), - playback_rate_(1.0f), - written_frames_(0) { + error_func_(error_func), + start_time_(start_time), + tunnel_mode_audio_session_id_(tunnel_mode_audio_session_id), + max_frames_per_request_( + tunnel_mode_audio_session_id_ == -1 + ? kMaxFramesPerRequest + : GetMaxFramesPerRequestForTunnelMode(sampling_frequency_hz_)), + context_(context) { SB_DCHECK(update_source_status_func_); SB_DCHECK(consume_frames_func_); SB_DCHECK(frame_buffer_); SB_DCHECK(SbAudioSinkIsAudioSampleTypeSupported(sample_type)); + // TODO: Support query if platform supports float type for tunnel mode. + if (tunnel_mode_audio_session_id_ != -1) { + SB_DCHECK(sample_type_ == kSbMediaAudioSampleTypeInt16Deprecated); + } + + SB_LOG(INFO) << "Creating audio sink starts at " << start_time_; + JniEnvExt* env = JniEnvExt::Get(); ScopedLocalJavaRef<jobject> j_audio_output_manager( env->CallStarboardObjectMethodOrAbort( "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;")); jobject j_audio_track_bridge = env->CallObjectMethodOrAbort( j_audio_output_manager.Get(), "createAudioTrackBridge", - "(IIII)Ldev/cobalt/media/AudioTrackBridge;", + "(IIIII)Ldev/cobalt/media/AudioTrackBridge;", GetAudioFormatSampleType(sample_type_), sampling_frequency_hz_, channels_, - preferred_buffer_size_in_bytes); + preferred_buffer_size_in_bytes, tunnel_mode_audio_session_id_); if (!j_audio_track_bridge) { + // One of the cases that this may hit is when output happened to be switched + // to a device that doesn't support tunnel mode. + // TODO: Find a way to exclude the device from tunnel mode playback, to + // avoid infinite loop in creating the audio sink on a device + // claims to support tunnel mode but fails to create the audio sink. + // TODO: Currently this will be reported as a general decode error, + // investigate if this can be reported as a capability changed error. return; } j_audio_track_bridge_ = env->ConvertLocalRefToGlobalRef(j_audio_track_bridge); if (sample_type_ == kSbMediaAudioSampleTypeFloat32) { - j_audio_data_ = env->NewFloatArray(channels_ * kMaxFramesPerRequest); + j_audio_data_ = env->NewFloatArray(channels_ * max_frames_per_request_); } else if (sample_type_ == kSbMediaAudioSampleTypeInt16Deprecated) { j_audio_data_ = env->NewByteArray(channels_ * GetSampleSize(sample_type_) * - kMaxFramesPerRequest); + max_frames_per_request_); } else { SB_NOTREACHED(); } + SB_CHECK(j_audio_data_) << "Failed to allocate |j_audio_data_|"; + j_audio_data_ = env->ConvertLocalRefToGlobalRef(j_audio_data_); audio_out_thread_ = SbThreadCreate( @@ -183,15 +220,21 @@ return NULL; } +// TODO: Break down the function into manageable pieces. void AudioTrackAudioSink::AudioThreadFunc() { JniEnvExt* env = JniEnvExt::Get(); bool was_playing = false; SB_LOG(INFO) << "AudioTrackAudioSink thread started."; -#if defined(SB_PLAYER_FILTER_ENABLE_STATE_CHECK) + int accumulated_written_frames = 0; + // TODO: |last_playback_head_changed_at| is also resetted when a warning is + // logged after the playback head position hasn't been updated for a + // while. We should refine the name to make it better reflect its + // usage. SbTime last_playback_head_changed_at = -1; -#endif // defined(SB_PLAYER_FILTER_ENABLE_STATE_CHECK) + SbTime playback_head_not_changed_duration = 0; + SbTime last_written_succeeded_at = -1; while (!quit_) { int playback_head_position = 0; @@ -214,12 +257,15 @@ std::max(playback_head_position, last_playback_head_position_); int frames_consumed = playback_head_position - last_playback_head_position_; + SbTime now = SbTimeGetMonotonicNow(); -#if defined(SB_PLAYER_FILTER_ENABLE_STATE_CHECK) + if (last_playback_head_changed_at == -1) { + last_playback_head_changed_at = now; + } if (last_playback_head_position_ == playback_head_position) { - auto now = SbTimeGetMonotonicNow(); SbTime elapsed = now - last_playback_head_changed_at; - if (was_playing && elapsed > 5 * kSbTimeSecond) { + if (elapsed > 5 * kSbTimeSecond) { + playback_head_not_changed_duration += elapsed; last_playback_head_changed_at = now; SB_LOG(INFO) << "last playback head position is " << last_playback_head_position_ @@ -227,9 +273,9 @@ << " microseconds."; } } else { - last_playback_head_changed_at = SbTimeGetMonotonicNow(); + last_playback_head_changed_at = now; + playback_head_not_changed_duration = 0; } -#endif // defined(SB_PLAYER_FILTER_ENABLE_STATE_CHECK) last_playback_head_position_ = playback_head_position; frames_consumed = std::min(frames_consumed, written_frames_); @@ -260,6 +306,9 @@ SB_LOG(INFO) << "AudioTrackAudioSink paused."; } else if (!was_playing && is_playing) { was_playing = true; + last_playback_head_changed_at = -1; + playback_head_not_changed_duration = 0; + last_written_succeeded_at = -1; env->CallVoidMethodOrAbort(j_audio_track_bridge_, "play", "()V"); SB_LOG(INFO) << "AudioTrackAudioSink playing."; } @@ -281,7 +330,7 @@ } expected_written_frames = - std::min(expected_written_frames, kMaxFramesPerRequest); + std::min(expected_written_frames, max_frames_per_request_); if (expected_written_frames == 0) { // It is possible that all the frames in buffer are written to the // soundcard, but those are not being consumed. If eos is reached, @@ -292,24 +341,84 @@ // Currently AudioDevice and AudioRenderer will write tail silence. // It should be reached only in tests. It's not ideal to allocate // a new silence buffer every time. - std::vector<uint8_t> silence_buffer( - channels_ * GetSampleSize(sample_type_) * kSilenceFramesPerAppend); - WriteData(env, silence_buffer.data(), kSilenceFramesPerAppend); + const int silence_frames_per_append = + std::min<int>(kSilenceFramesPerAppend, max_frames_per_request_); + std::vector<uint8_t> silence_buffer(channels_ * + GetSampleSize(sample_type_) * + silence_frames_per_append); + auto sync_time = start_time_ + accumulated_written_frames * + kSbTimeSecond / + sampling_frequency_hz_; + // Not necessary to handle error of WriteData(), even for + // kAudioTrackErrorDeadObject, as the audio has reached the end of + // stream. + // TODO: Ensure that the audio stream can still reach the end when an + // error occurs. + WriteData(env, silence_buffer.data(), silence_frames_per_append, + sync_time); } + + // While WriteData() returns kAudioTrackErrorDeadObject on dead object, + // getAudioTimestamp() doesn't, it just no longer updates its return + // value. If the dead object error occurs when there isn't any audio data + // to write, there is no way to detect it by checking the return value of + // getAudioTimestamp(). Instead, we have to check if its return value has + // been changed during a certain period of time, to detect the underlying + // dead object error. + // Note that dead object would occur while switching audio end points in + // tunnel mode. Under non-tunnel mode, the Android native AudioTrack will + // handle audio track dead object automatically if the new end point can + // support current audio format. + // TODO: Investigate to handle this error in non-tunnel mode. + if (tunnel_mode_audio_session_id_ != -1 && + last_written_succeeded_at != -1) { + const SbTime kAudioSinkBlockedDuration = kSbTimeSecond; + auto time_since_last_written_success = + SbTimeGetMonotonicNow() - last_written_succeeded_at; + if (time_since_last_written_success > kAudioSinkBlockedDuration && + playback_head_not_changed_duration > kAudioSinkBlockedDuration && + tunnel_mode_audio_session_id_ != -1) { + SB_LOG(INFO) << "Over one second without frames written and consumed"; + consume_frames_func_(written_frames_, SbTimeGetMonotonicNow(), + context_); + error_func_(kSbPlayerErrorCapabilityChanged, context_); + break; + } + } + SbThreadSleep(10 * kSbTimeMillisecond); continue; } SB_DCHECK(expected_written_frames > 0); + auto sync_time = start_time_ + accumulated_written_frames * kSbTimeSecond / + sampling_frequency_hz_; + + SB_CHECK(start_position + expected_written_frames <= frames_per_channel_); int written_frames = WriteData( env, IncrementPointerByBytes(frame_buffer_, start_position * channels_ * GetSampleSize(sample_type_)), - expected_written_frames); + expected_written_frames, sync_time); + SbTime now = SbTimeGetMonotonicNow(); + + if (written_frames < 0) { + // Take all |written_frames_| as consumed since audio track could be dead. + consume_frames_func_(written_frames_, now, context_); + error_func_(written_frames == kAudioTrackErrorDeadObject + ? kSbPlayerErrorCapabilityChanged + : kSbPlayerErrorDecode, + context_); + break; + } else if (written_frames > 0) { + last_written_succeeded_at = now; + } written_frames_ += written_frames; + accumulated_written_frames += written_frames; + bool written_fully = (written_frames == expected_written_frames); auto unplayed_frames_in_time = written_frames_ * kSbTimeSecond / sampling_frequency_hz_ - - (SbTimeGetMonotonicNow() - frames_consumed_at); + (now - frames_consumed_at); // As long as there is enough data in the buffer, run the loop in lower // frequency to avoid taking too much CPU. Note that the threshold should // be big enough to account for the unstable playback head reported at the @@ -332,8 +441,9 @@ } int AudioTrackAudioSink::WriteData(JniEnvExt* env, - const void* buffer, - int expected_written_frames) { + void* buffer, + int expected_written_frames, + SbTime sync_time) { if (sample_type_ == kSbMediaAudioSampleTypeFloat32) { int expected_written_size = expected_written_frames * channels_; env->SetFloatArrayRegion(static_cast<jfloatArray>(j_audio_data_), kNoOffset, @@ -342,7 +452,10 @@ int written = env->CallIntMethodOrAbort(j_audio_track_bridge_, "write", "([FI)I", j_audio_data_, expected_written_size); - SB_DCHECK(written >= 0); + if (written < 0) { + // Error code returned as negative value, like kAudioTrackErrorDeadObject. + return written; + } SB_DCHECK(written % channels_ == 0); return written / channels_; } @@ -352,10 +465,14 @@ env->SetByteArrayRegion(static_cast<jbyteArray>(j_audio_data_), kNoOffset, expected_written_size, static_cast<const jbyte*>(buffer)); - int written = - env->CallIntMethodOrAbort(j_audio_track_bridge_, "write", "([BI)I", - j_audio_data_, expected_written_size); - SB_DCHECK(written >= 0); + + int written = env->CallIntMethodOrAbort(j_audio_track_bridge_, "write", + "([BIJ)I", j_audio_data_, + expected_written_size, sync_time); + if (written < 0) { + // Error code returned as negative value, like kAudioTrackErrorDeadObject. + return written; + } SB_DCHECK(written % (channels_ * GetSampleSize(sample_type_)) == 0); return written / (channels_ * GetSampleSize(sample_type_)); } @@ -417,6 +534,27 @@ SbAudioSinkPrivate::ConsumeFramesFunc consume_frames_func, SbAudioSinkPrivate::ErrorFunc error_func, void* context) { + const SbTime kStartTime = 0; + const int kTunnelModeAudioSessionId = -1; // disable tunnel mode + return Create(channels, sampling_frequency_hz, audio_sample_type, + audio_frame_storage_type, frame_buffers, frames_per_channel, + update_source_status_func, consume_frames_func, error_func, + kStartTime, kTunnelModeAudioSessionId, context); +} + +SbAudioSink AudioTrackAudioSinkType::Create( + int channels, + int sampling_frequency_hz, + SbMediaAudioSampleType audio_sample_type, + SbMediaAudioFrameStorageType audio_frame_storage_type, + SbAudioSinkFrameBuffers frame_buffers, + int frames_per_channel, + SbAudioSinkUpdateSourceStatusFunc update_source_status_func, + SbAudioSinkPrivate::ConsumeFramesFunc consume_frames_func, + SbAudioSinkPrivate::ErrorFunc error_func, + SbTime start_media_time, + int tunnel_mode_audio_session_id, + void* context) { int min_required_frames = SbAudioSinkGetMinBufferSizeInFrames( channels, audio_sample_type, sampling_frequency_hz); SB_DCHECK(frames_per_channel >= min_required_frames); @@ -425,7 +563,8 @@ AudioTrackAudioSink* audio_sink = new AudioTrackAudioSink( this, channels, sampling_frequency_hz, audio_sample_type, frame_buffers, frames_per_channel, preferred_buffer_size_in_bytes, - update_source_status_func, consume_frames_func, context); + update_source_status_func, consume_frames_func, error_func, + start_media_time, tunnel_mode_audio_session_id, context); if (!audio_sink->IsAudioTrackValid()) { SB_DLOG(ERROR) << "AudioTrackAudioSinkType::Create failed to create audio track";
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.h b/src/starboard/android/shared/audio_track_audio_sink_type.h index 43b0c86..c5b1ba7 100644 --- a/src/starboard/android/shared/audio_track_audio_sink_type.h +++ b/src/starboard/android/shared/audio_track_audio_sink_type.h
@@ -18,6 +18,7 @@ #include <atomic> #include <functional> #include <map> +#include <vector> #include "starboard/android/shared/audio_sink_min_required_frames_tester.h" #include "starboard/android/shared/jni_env_ext.h" @@ -54,6 +55,19 @@ SbAudioSinkPrivate::ConsumeFramesFunc consume_frames_func, SbAudioSinkPrivate::ErrorFunc error_func, void* context) override; + SbAudioSink Create( + int channels, + int sampling_frequency_hz, + SbMediaAudioSampleType audio_sample_type, + SbMediaAudioFrameStorageType audio_frame_storage_type, + SbAudioSinkFrameBuffers frame_buffers, + int frames_per_channel, + SbAudioSinkUpdateSourceStatusFunc update_source_status_func, + SbAudioSinkPrivate::ConsumeFramesFunc consume_frames_func, + SbAudioSinkPrivate::ErrorFunc error_func, + SbTime start_time, + int tunnel_mode_audio_session_id, + void* context); bool IsValid(SbAudioSink audio_sink) override { return audio_sink != kSbAudioSinkInvalid && audio_sink->IsType(this); @@ -92,6 +106,9 @@ int preferred_buffer_size, SbAudioSinkUpdateSourceStatusFunc update_source_status_func, ConsumeFramesFunc consume_frames_func, + SbAudioSinkPrivate::ErrorFunc error_func, + SbTime start_media_time, + int tunnel_mode_audio_session_id, void* context); ~AudioTrackAudioSink() override; @@ -106,8 +123,9 @@ static void* ThreadEntryPoint(void* context); void AudioThreadFunc(); - int WriteData(JniEnvExt* env, const void* buffer, int size); + int WriteData(JniEnvExt* env, void* buffer, int size, SbTime sync_time); + // TODO: Add const to the following variables where appropriate. Type* type_; int channels_; int sampling_frequency_hz_; @@ -116,18 +134,25 @@ int frames_per_channel_; SbAudioSinkUpdateSourceStatusFunc update_source_status_func_; ConsumeFramesFunc consume_frames_func_; + SbAudioSinkPrivate::ErrorFunc error_func_; + const SbTime start_time_; + const int tunnel_mode_audio_session_id_; + const int max_frames_per_request_; + void* context_; - int last_playback_head_position_; - jobject j_audio_track_bridge_; - jobject j_audio_data_; + int last_playback_head_position_ = 0; + jobject j_audio_track_bridge_ = nullptr; + jobject j_audio_data_ = nullptr; - volatile bool quit_; - SbThread audio_out_thread_; + volatile bool quit_ = false; + SbThread audio_out_thread_ = kSbThreadInvalid; - starboard::Mutex mutex_; - double playback_rate_; + Mutex mutex_; + double playback_rate_ = 1.0; - int written_frames_; + // TODO: Rename to |frames_in_audio_track| and move it into AudioThreadFunc() + // as a local variable. + int written_frames_ = 0; }; } // namespace shared
diff --git a/src/starboard/android/shared/cobalt/__init__.py b/src/starboard/android/shared/cobalt/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/starboard/android/shared/cobalt/__init__.py
diff --git a/src/starboard/android/shared/configuration.cc b/src/starboard/android/shared/configuration.cc index 6f05a18..0de9388 100644 --- a/src/starboard/android/shared/configuration.cc +++ b/src/starboard/android/shared/configuration.cc
@@ -43,7 +43,7 @@ const CobaltExtensionConfigurationApi kConfigurationApi = { kCobaltExtensionConfigurationName, - 1, + 2, &CobaltUserOnExitStrategy, &common::CobaltRenderDirtyRegionOnlyDefault, &CobaltEglSwapInterval, @@ -66,6 +66,7 @@ &common::CobaltGcZealDefault, &common::CobaltRasterizerTypeDefault, &common::CobaltEnableJitDefault, + &common::CobaltFallbackSplashScreenTopicsDefault, }; } // namespace
diff --git a/src/starboard/android/shared/configuration_public.h b/src/starboard/android/shared/configuration_public.h index 371a413..2064399 100644 --- a/src/starboard/android/shared/configuration_public.h +++ b/src/starboard/android/shared/configuration_public.h
@@ -23,12 +23,6 @@ #ifndef STARBOARD_ANDROID_SHARED_CONFIGURATION_PUBLIC_H_ #define STARBOARD_ANDROID_SHARED_CONFIGURATION_PUBLIC_H_ -#if SB_API_VERSION != SB_EXPERIMENTAL_API_VERSION -#error \ - "This platform's sabi.json file is expected to track the experimental " \ -"Starboard API version." -#endif // SB_API_VERSION != SB_EXPERIMENTAL_API_VERSION - // --- Architecture Configuration -------------------------------------------- // Indicates that there is no support for alignment at greater than 16 bytes for
diff --git a/src/starboard/android/shared/launcher.py b/src/starboard/android/shared/launcher.py new file mode 100644 index 0000000..106b2b9 --- /dev/null +++ b/src/starboard/android/shared/launcher.py
@@ -0,0 +1,437 @@ +# +# Copyright 2017 The Cobalt Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Android implementation of Starboard launcher abstraction.""" + +import os +import re +import socket +import subprocess +import sys +import threading +import time +import Queue + +import _env # pylint: disable=unused-import,g-bad-import-order + +from starboard.android.shared import sdk_utils +from starboard.tools import abstract_launcher + +_APP_PACKAGE_NAME = 'dev.cobalt.coat' + +_APP_START_INTENT = 'dev.cobalt.coat/dev.cobalt.app.MainActivity' + +# Matches an "adb shell am monitor" error line. +_RE_ADB_AM_MONITOR_ERROR = re.compile(r'\*\* ERROR') + +# String added to queue to indicate process has crashed +_QUEUE_CODE_CRASHED = 'crashed' + +# How long to keep logging after a crash in order to emit the stack trace. +_CRASH_LOG_SECONDS = 1.0 + +_RUNTIME_PERMISSIONS = [ + 'android.permission.GET_ACCOUNTS', + 'android.permission.RECORD_AUDIO', +] + + +def TargetOsPathJoin(*path_elements): + """os.path.join for the target (Android).""" + return '/'.join(path_elements) + + +def CleanLine(line): + """Removes trailing carriages returns from ADB output.""" + return line.replace('\r', '') + + +class StepTimer(object): + """Class for timing how long install/run steps take.""" + + def __init__(self, step_name): + self.step_name = step_name + self.start_time = time.time() + self.end_time = None + + def Stop(self): + if self.start_time is None: + sys.stderr.write('Cannot stop timer; not started\n') + else: + self.end_time = time.time() + total_time = self.end_time - self.start_time + sys.stderr.write('Step \"{}\" took {} seconds.\n'.format( + self.step_name, total_time)) + + +class AdbCommandBuilder(object): + """Builder for 'adb' commands.""" + + def __init__(self, adb, device_id=None): + self.adb = adb + self.device_id = device_id + + def Build(self, *args): + """Builds an 'adb' commandline with the given args.""" + result = [self.adb] + if self.device_id: + result.append('-s') + result.append(self.device_id) + result += list(args) + return result + + +class AdbAmMonitorWatcher(object): + """Watches an "adb shell am monitor" process to detect crashes.""" + + def __init__(self, launcher, done_queue): + self.launcher = launcher + self.process = launcher._PopenAdb( + 'shell', 'am', 'monitor', stdout=subprocess.PIPE) + if abstract_launcher.ARG_DRYRUN in launcher.launcher_args: + self.thread = None + return + self.thread = threading.Thread(target=self._Run) + self.thread.start() + self.done_queue = done_queue + + def Shutdown(self): + self.process.kill() + if self.thread: + self.thread.join() + + def _Run(self): + while True: + line = CleanLine(self.process.stdout.readline()) + if not line: + return + # Show the crash lines reported by "am monitor". + sys.stderr.write(line) + if re.search(_RE_ADB_AM_MONITOR_ERROR, line): + self.done_queue.put(_QUEUE_CODE_CRASHED) + # This log line will wake up the main thread + self.launcher.CallAdb('shell', 'log', '-t', 'starboard', + 'am monitor detected crash') + + +class Launcher(abstract_launcher.AbstractLauncher): + """Run an application on Android.""" + + def __init__(self, platform, target_name, config, device_id, **kwargs): + + super(Launcher, self).__init__(platform, target_name, config, device_id, + **kwargs) + + if abstract_launcher.ARG_SYSTOOLS in self.launcher_args: + # Use default adb binary from path. + self.adb = 'adb' + else: + self.adb = os.path.join(sdk_utils.GetSdkPath(), 'platform-tools', 'adb') + + self.adb_builder = AdbCommandBuilder(self.adb) + + if not self.device_id: + self.device_id = self._IdentifyDevice() + else: + self._ConnectIfNecessary() + + self.adb_builder.device_id = self.device_id + + # Verify connection and dump target build fingerprint. + self._CheckCallAdb('shell', 'getprop', 'ro.build.fingerprint') + + out_directory = os.path.split(self.GetTargetPath())[0] + self.apk_path = os.path.join(out_directory, '{}.apk'.format(target_name)) + if not os.path.exists(self.apk_path): + raise Exception("Can't find APK {}".format(self.apk_path)) + + # This flag is set when the main Run() loop exits. If Kill() is called + # after this flag is set, it will not do anything. + self.killed = threading.Event() + + # Keep track of the port used by ADB forward in order to remove it later + # on. + self.local_port = None + + def _IsValidIPv4Address(self, address): + """Returns True if address is a valid IPv4 address, False otherwise.""" + try: + # inet_aton throws an exception if the address is not a valid IPv4 + # address. However addresses such as '127.1' might still be considered + # valid, hence the check for 3 '.' in the address. + # pylint: disable=g-socket-inet-aton + if socket.inet_aton(address) and address.count('.') == 3: + return True + except Exception: # pylint: disable=broad-except + pass + return False + + def _GetAdbDevices(self): + """Returns a list of names of connected devices, or empty list if none.""" + + # Does not use the ADBCommandBuilder class because this command should be + # run without targeting a specific device. + p = self._PopenAdb('devices', stdout=subprocess.PIPE) + result = p.stdout.readlines()[1:-1] + p.wait() + + names = [] + for device in result: + name_info = device.split('\t') + # Some devices may not have authorization for USB debugging. + try: + if 'unauthorized' not in name_info[1]: + names.append(name_info[0]) + # Sometimes happens when device is found, even though none are connected. + except IndexError: + continue + return names + + def _IdentifyDevice(self): + """Picks a device to be used to run the executable. + + In the event that no device_id is provided, but multiple + devices are connected, this method chooses the first device + listed. + + Returns: + The name of an attached device, or None if no devices are present. + """ + device_name = None + + devices = self._GetAdbDevices() + if devices: + device_name = devices[0] + + return device_name + + def _ConnectIfNecessary(self): + """Run ADB connect if needed for devices connected over IP.""" + if not self._IsValidIPv4Address(self.device_id): + return + for device in self._GetAdbDevices(): + # Devices returned by _GetAdbDevices might include port number, so cannot + # simply check if self.device_id is in the returned list. + if self.device_id in device: + return + + # Device isn't connected. Run ADB connect. + # Does not use the ADBCommandBuilder class because this command should be + # run without targeting a specific device. + p = self._PopenAdb( + 'connect', + '{}:5555'.format(self.device_id), + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + ) + result = p.stdout.readlines()[0] + p.wait() + + if 'connected to' not in result: + sys.stderr.write('Failed to connect to {}\n'.format(self.device_id)) + sys.stderr.write('connect command exited with code {} ' + 'and returned: {}'.format(p.returncode, result)) + + def _Call(self, *args): + sys.stderr.write('{}\n'.format(' '.join(args))) + if abstract_launcher.ARG_DRYRUN not in self.launcher_args: + subprocess.call(args, close_fds=True) + + def CallAdb(self, *in_args): + args = self.adb_builder.Build(*in_args) + self._Call(*args) + + def _CheckCall(self, *args): + sys.stderr.write('{}\n'.format(' '.join(args))) + if abstract_launcher.ARG_DRYRUN not in self.launcher_args: + subprocess.check_call(args, close_fds=True) + + def _CheckCallAdb(self, *in_args): + args = self.adb_builder.Build(*in_args) + self._CheckCall(*args) + + def _PopenAdb(self, *args, **kwargs): + build_args = self.adb_builder.Build(*args) + sys.stderr.write('{}\n'.format(' '.join(build_args))) + if abstract_launcher.ARG_DRYRUN in self.launcher_args: + return subprocess.Popen(['echo', 'dry-run']) + return subprocess.Popen(build_args, close_fds=True, **kwargs) + + def Run(self): + # The return code for binaries run on Android is read from a log line that + # it emitted in android_main.cc. This return_code variable will be assigned + # the value read when we see that line, or left at 1 in the event of a crash + # or early exit. + return_code = 1 + + # Setup for running executable + self._CheckCallAdb('wait-for-device') + self._Shutdown() + + # Clear logcat + self._CheckCallAdb('logcat', '-c') + + # Install the APK, unless "noinstall" was specified. + if abstract_launcher.ARG_NOINSTALL not in self.launcher_args: + install_timer = StepTimer('install') + self._CheckCallAdb('install', '-r', self.apk_path) + install_timer.Stop() + + # Send the wakeup key to ensure daydream isn't running, otherwise Activity + # Manager may get in a loop running the test over and over again. + self._CheckCallAdb('shell', 'input', 'keyevent', 'KEYCODE_WAKEUP') + + # Grant runtime permissions to avoid prompts during testing. + if abstract_launcher.ARG_NOINSTALL not in self.launcher_args: + for permission in _RUNTIME_PERMISSIONS: + self._CheckCallAdb('shell', 'pm', 'grant', _APP_PACKAGE_NAME, + permission) + + done_queue = Queue.Queue() + am_monitor = AdbAmMonitorWatcher(self, done_queue) + + # Increases the size of the logcat buffer. Without this, the log buffer + # will not flush quickly enough and output will be cut off. + self._CheckCallAdb('logcat', '-G', '2M') + + # Ctrl + C will kill this process + logcat_process = self._PopenAdb( + 'logcat', + '-v', + 'raw', + '-s', + '*:F', + '*:E', + 'DEBUG:*', + 'System.err:*', + 'starboard:*', + 'starboard_media:*', + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + + # Actually running executable + run_timer = StepTimer('running executable') + try: + args = ['shell', 'am', 'start'] + command_line_params = [ + '--android_log_sleep_time=1000', + '--disable_sign_in', + ] + for param in self.target_command_line_params: + if param.startswith('--link='): + # Android deeplinks go in the Intent data + link = param.split('=')[1] + args += ['-d', "'{}'".format(link)] + else: + command_line_params.append(param) + args += ['--esa', 'args', "'{}'".format(','.join(command_line_params))] + args += [_APP_START_INTENT] + + self._CheckCallAdb(*args) + + run_loop = abstract_launcher.ARG_DRYRUN not in self.launcher_args + + app_crashed = False + while run_loop: + if not done_queue.empty(): + done_queue_code = done_queue.get_nowait() + if done_queue_code == _QUEUE_CODE_CRASHED: + app_crashed = True + threading.Timer(_CRASH_LOG_SECONDS, logcat_process.kill).start() + + # Note we cannot use "for line in logcat_process.stdout" because + # that uses a large buffer which will cause us to deadlock. + line = CleanLine(logcat_process.stdout.readline()) + + # Some crashes are not caught by the am_monitor thread, but they do + # produce the following string in logcat before they exit. + if 'beginning of crash' in line: + app_crashed = True + threading.Timer(_CRASH_LOG_SECONDS, logcat_process.kill).start() + + if not line: # Logcat exited, or was killed + break + else: + self._WriteLine(line) + # Don't break until we see the below text in logcat, which should be + # written when the Starboard application event loop finishes. + if '***Application Stopped***' in line: + try: + return_code = int(line.split(' ')[-1]) + except ValueError: # Error message was printed to stdout + pass + logcat_process.kill() + break + + finally: + if app_crashed: + self._WriteLine('***Application Crashed***\n') + # Set return code to mimic segfault code on Linux + return_code = 11 + else: + self._Shutdown() + if self.local_port is not None: + self.CallAdb('forward', '--remove', 'tcp:{}'.format(self.local_port)) + am_monitor.Shutdown() + self.killed.set() + run_timer.Stop() + if logcat_process.poll() is None: + # This could happen when using SIGINT to kill the launcher + # (e.g. when using starboard/tools/example/app_launcher_client.py). + sys.stderr.write('Logcat process is still running. Killing it now.\n') + logcat_process.kill() + + return return_code + + def _Shutdown(self): + self.CallAdb('shell', 'am', 'force-stop', _APP_PACKAGE_NAME) + + def SupportsDeepLink(self): + return True + + def SendDeepLink(self, link): + shell_cmd = 'am start -d "{}" {}'.format(link, _APP_START_INTENT) + args = ['shell', shell_cmd] + self._CheckCallAdb(*args) + return True + + def Kill(self): + if not self.killed.is_set(): + sys.stderr.write('***Killing Launcher***\n') + self._CheckCallAdb('shell', 'log', '-t', 'starboard', + '***Application Stopped*** 1') + self._Shutdown() + else: + sys.stderr.write('Cannot kill launcher: already dead.\n') + + def _WriteLine(self, line): + """Write log output to stdout.""" + self.output_file.write(line) + self.output_file.flush() + + def GetHostAndPortGivenPort(self, port): + forward_p = self._PopenAdb( + 'forward', 'tcp:0', 'tcp:{}'.format(port), stdout=subprocess.PIPE) + forward_p.wait() + + self.local_port = CleanLine(forward_p.stdout.readline()).rstrip('\n') + sys.stderr.write('ADB forward local port {} ' + '=> device port {}\n'.format(self.local_port, port)) + # pylint: disable=g-socket-gethostbyname + return socket.gethostbyname('localhost'), self.local_port + + def GetDeviceIp(self): + """Gets the device IP. TODO: Implement.""" + return None
diff --git a/src/starboard/android/shared/player_components_factory.cc b/src/starboard/android/shared/player_components_factory.cc index 6ac984a..01dc7a6 100644 --- a/src/starboard/android/shared/player_components_factory.cc +++ b/src/starboard/android/shared/player_components_factory.cc
@@ -12,23 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/android/shared/audio_decoder.h" -#include "starboard/android/shared/video_decoder.h" -#include "starboard/android/shared/video_render_algorithm.h" -#include "starboard/common/log.h" -#include "starboard/common/ref_counted.h" -#include "starboard/common/scoped_ptr.h" -#include "starboard/media.h" -#include "starboard/shared/opus/opus_audio_decoder.h" -#include "starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.h" -#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h" -#include "starboard/shared/starboard/player/filter/audio_renderer_sink.h" -#include "starboard/shared/starboard/player/filter/audio_renderer_sink_impl.h" -#include "starboard/shared/starboard/player/filter/player_components.h" -#include "starboard/shared/starboard/player/filter/video_decoder_internal.h" -#include "starboard/shared/starboard/player/filter/video_render_algorithm.h" -#include "starboard/shared/starboard/player/filter/video_render_algorithm_impl.h" -#include "starboard/shared/starboard/player/filter/video_renderer_sink.h" +#include "starboard/android/shared/player_components_factory.h" namespace starboard { namespace shared { @@ -36,121 +20,10 @@ namespace player { namespace filter { -namespace { - -const int kAudioSinkFramesAlignment = 256; -const int kDefaultAudioSinkMinFramesPerAppend = 1024; -const int kDefaultAudioSinkMaxCachedFrames = - 8 * kDefaultAudioSinkMinFramesPerAppend; - -int AlignUp(int value, int alignment) { - return (value + alignment - 1) / alignment * alignment; -} - -class PlayerComponentsFactory : public PlayerComponents::Factory { - bool CreateSubComponents( - const CreationParameters& creation_parameters, - scoped_ptr<AudioDecoder>* audio_decoder, - scoped_ptr<AudioRendererSink>* audio_renderer_sink, - scoped_ptr<VideoDecoder>* video_decoder, - scoped_ptr<VideoRenderAlgorithm>* video_render_algorithm, - scoped_refptr<VideoRendererSink>* video_renderer_sink, - std::string* error_message) override { - SB_DCHECK(error_message); - - if (creation_parameters.audio_codec() != kSbMediaAudioCodecNone) { - SB_DCHECK(audio_decoder); - SB_DCHECK(audio_renderer_sink); - - auto decoder_creator = [](const SbMediaAudioSampleInfo& audio_sample_info, - SbDrmSystem drm_system) { - if (audio_sample_info.codec == kSbMediaAudioCodecAac) { - scoped_ptr<android::shared::AudioDecoder> audio_decoder_impl( - new android::shared::AudioDecoder(audio_sample_info.codec, - audio_sample_info, drm_system)); - if (audio_decoder_impl->is_valid()) { - return audio_decoder_impl.PassAs<AudioDecoder>(); - } - } else if (audio_sample_info.codec == kSbMediaAudioCodecOpus) { - scoped_ptr<opus::OpusAudioDecoder> audio_decoder_impl( - new opus::OpusAudioDecoder(audio_sample_info)); - if (audio_decoder_impl->is_valid()) { - return audio_decoder_impl.PassAs<AudioDecoder>(); - } - } else { - SB_NOTREACHED(); - } - return scoped_ptr<AudioDecoder>(); - }; - - audio_decoder->reset(new AdaptiveAudioDecoder( - creation_parameters.audio_sample_info(), - creation_parameters.drm_system(), decoder_creator)); - audio_renderer_sink->reset(new AudioRendererSinkImpl); - } - - if (creation_parameters.video_codec() != kSbMediaVideoCodecNone) { - SB_DCHECK(video_decoder); - SB_DCHECK(video_render_algorithm); - SB_DCHECK(video_renderer_sink); - SB_DCHECK(error_message); - - scoped_ptr<android::shared::VideoDecoder> video_decoder_impl( - new android::shared::VideoDecoder( - creation_parameters.video_codec(), - creation_parameters.drm_system(), - creation_parameters.output_mode(), - creation_parameters.decode_target_graphics_context_provider(), - creation_parameters.max_video_capabilities(), error_message)); - if (video_decoder_impl->is_valid()) { - video_render_algorithm->reset(new android::shared::VideoRenderAlgorithm( - video_decoder_impl.get())); - *video_renderer_sink = video_decoder_impl->GetSink(); - video_decoder->reset(video_decoder_impl.release()); - } else { - video_decoder->reset(); - *video_renderer_sink = NULL; - *error_message = - "Failed to create video decoder with error: " + *error_message; - return false; - } - } - - return true; - } - - void GetAudioRendererParams(const CreationParameters& creation_parameters, - int* max_cached_frames, - int* min_frames_per_append) const override { - SB_DCHECK(max_cached_frames); - SB_DCHECK(min_frames_per_append); - SB_DCHECK(kDefaultAudioSinkMinFramesPerAppend % kAudioSinkFramesAlignment == - 0); - *min_frames_per_append = kDefaultAudioSinkMinFramesPerAppend; - - // AudioRenderer prefers to use kSbMediaAudioSampleTypeFloat32 and only uses - // kSbMediaAudioSampleTypeInt16Deprecated when float32 is not supported. - int min_frames_required = SbAudioSinkGetMinBufferSizeInFrames( - creation_parameters.audio_sample_info().number_of_channels, - SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeFloat32) - ? kSbMediaAudioSampleTypeFloat32 - : kSbMediaAudioSampleTypeInt16Deprecated, - creation_parameters.audio_sample_info().samples_per_second); - // On Android 5.0, the size of audio renderer sink buffer need to be two - // times larger than AudioTrack minBufferSize. Otherwise, AudioTrack may - // stop working after pause. - *max_cached_frames = - min_frames_required * 2 + kDefaultAudioSinkMinFramesPerAppend; - *max_cached_frames = AlignUp(*max_cached_frames, kAudioSinkFramesAlignment); - } -}; - -} // namespace - // static scoped_ptr<PlayerComponents::Factory> PlayerComponents::Factory::Create() { return make_scoped_ptr<PlayerComponents::Factory>( - new PlayerComponentsFactory); + new android::shared::PlayerComponentsFactory); } // static
diff --git a/src/starboard/android/shared/player_components_factory.h b/src/starboard/android/shared/player_components_factory.h new file mode 100644 index 0000000..350b00f --- /dev/null +++ b/src/starboard/android/shared/player_components_factory.h
@@ -0,0 +1,173 @@ +// Copyright 2017 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_ANDROID_SHARED_PLAYER_COMPONENTS_FACTORY_H_ +#define STARBOARD_ANDROID_SHARED_PLAYER_COMPONENTS_FACTORY_H_ + +#include <string> + +#include "starboard/android/shared/audio_decoder.h" +#include "starboard/android/shared/video_decoder.h" +#include "starboard/android/shared/video_render_algorithm.h" +#include "starboard/common/log.h" +#include "starboard/common/ref_counted.h" +#include "starboard/common/scoped_ptr.h" +#include "starboard/media.h" +#include "starboard/shared/opus/opus_audio_decoder.h" +#include "starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.h" +#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h" +#include "starboard/shared/starboard/player/filter/audio_renderer_sink.h" +#include "starboard/shared/starboard/player/filter/audio_renderer_sink_impl.h" +#include "starboard/shared/starboard/player/filter/player_components.h" +#include "starboard/shared/starboard/player/filter/video_decoder_internal.h" +#include "starboard/shared/starboard/player/filter/video_render_algorithm.h" +#include "starboard/shared/starboard/player/filter/video_render_algorithm_impl.h" +#include "starboard/shared/starboard/player/filter/video_renderer_sink.h" + +namespace starboard { +namespace android { +namespace shared { + +class PlayerComponentsFactory : public starboard::shared::starboard::player:: + filter::PlayerComponents::Factory { + typedef starboard::shared::opus::OpusAudioDecoder OpusAudioDecoder; + typedef starboard::shared::starboard::player::filter::AdaptiveAudioDecoder + AdaptiveAudioDecoder; + typedef starboard::shared::starboard::player::filter::AudioDecoder + AudioDecoderBase; + typedef starboard::shared::starboard::player::filter::AudioRendererSink + AudioRendererSink; + typedef starboard::shared::starboard::player::filter::AudioRendererSinkImpl + AudioRendererSinkImpl; + typedef starboard::shared::starboard::player::filter::VideoDecoder + VideoDecoderBase; + typedef starboard::shared::starboard::player::filter::VideoRenderAlgorithm + VideoRenderAlgorithmBase; + typedef starboard::shared::starboard::player::filter::VideoRendererSink + VideoRendererSink; + + const int kAudioSinkFramesAlignment = 256; + const int kDefaultAudioSinkMinFramesPerAppend = 1024; + const int kDefaultAudioSinkMaxCachedFrames = + 8 * kDefaultAudioSinkMinFramesPerAppend; + + virtual SbDrmSystem GetExtendedDrmSystem(SbDrmSystem drm_system) { + return drm_system; + } + + static int AlignUp(int value, int alignment) { + return (value + alignment - 1) / alignment * alignment; + } + + bool CreateSubComponents( + const CreationParameters& creation_parameters, + scoped_ptr<AudioDecoderBase>* audio_decoder, + scoped_ptr<AudioRendererSink>* audio_renderer_sink, + scoped_ptr<VideoDecoderBase>* video_decoder, + scoped_ptr<VideoRenderAlgorithmBase>* video_render_algorithm, + scoped_refptr<VideoRendererSink>* video_renderer_sink, + std::string* error_message) override { + SB_DCHECK(error_message); + + if (creation_parameters.audio_codec() != kSbMediaAudioCodecNone) { + SB_DCHECK(audio_decoder); + SB_DCHECK(audio_renderer_sink); + + auto decoder_creator = [](const SbMediaAudioSampleInfo& audio_sample_info, + SbDrmSystem drm_system) { + if (audio_sample_info.codec == kSbMediaAudioCodecAac) { + scoped_ptr<AudioDecoder> audio_decoder_impl(new AudioDecoder( + audio_sample_info.codec, audio_sample_info, drm_system)); + if (audio_decoder_impl->is_valid()) { + return audio_decoder_impl.PassAs<AudioDecoderBase>(); + } + } else if (audio_sample_info.codec == kSbMediaAudioCodecOpus) { + scoped_ptr<OpusAudioDecoder> audio_decoder_impl( + new OpusAudioDecoder(audio_sample_info)); + if (audio_decoder_impl->is_valid()) { + return audio_decoder_impl.PassAs<AudioDecoderBase>(); + } + } else { + SB_NOTREACHED(); + } + return scoped_ptr<AudioDecoderBase>(); + }; + + audio_decoder->reset(new AdaptiveAudioDecoder( + creation_parameters.audio_sample_info(), + GetExtendedDrmSystem(creation_parameters.drm_system()), + decoder_creator)); + audio_renderer_sink->reset(new AudioRendererSinkImpl); + } + + if (creation_parameters.video_codec() != kSbMediaVideoCodecNone) { + SB_DCHECK(video_decoder); + SB_DCHECK(video_render_algorithm); + SB_DCHECK(video_renderer_sink); + SB_DCHECK(error_message); + + scoped_ptr<VideoDecoder> video_decoder_impl(new VideoDecoder( + creation_parameters.video_codec(), + GetExtendedDrmSystem(creation_parameters.drm_system()), + creation_parameters.output_mode(), + creation_parameters.decode_target_graphics_context_provider(), + creation_parameters.max_video_capabilities(), error_message)); + if (video_decoder_impl->is_valid()) { + video_render_algorithm->reset( + new VideoRenderAlgorithm(video_decoder_impl.get())); + *video_renderer_sink = video_decoder_impl->GetSink(); + video_decoder->reset(video_decoder_impl.release()); + } else { + video_decoder->reset(); + *video_renderer_sink = NULL; + *error_message = + "Failed to create video decoder with error: " + *error_message; + return false; + } + } + + return true; + } + + void GetAudioRendererParams(const CreationParameters& creation_parameters, + int* max_cached_frames, + int* min_frames_per_append) const override { + SB_DCHECK(max_cached_frames); + SB_DCHECK(min_frames_per_append); + SB_DCHECK(kDefaultAudioSinkMinFramesPerAppend % kAudioSinkFramesAlignment == + 0); + *min_frames_per_append = kDefaultAudioSinkMinFramesPerAppend; + + // AudioRenderer prefers to use kSbMediaAudioSampleTypeFloat32 and only uses + // kSbMediaAudioSampleTypeInt16Deprecated when float32 is not supported. + int min_frames_required = SbAudioSinkGetMinBufferSizeInFrames( + creation_parameters.audio_sample_info().number_of_channels, + SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeFloat32) + ? kSbMediaAudioSampleTypeFloat32 + : kSbMediaAudioSampleTypeInt16Deprecated, + creation_parameters.audio_sample_info().samples_per_second); + // On Android 5.0, the size of audio renderer sink buffer need to be two + // times larger than AudioTrack minBufferSize. Otherwise, AudioTrack may + // stop working after pause. + *max_cached_frames = + min_frames_required * 2 + kDefaultAudioSinkMinFramesPerAppend; + *max_cached_frames = AlignUp(*max_cached_frames, kAudioSinkFramesAlignment); + } +}; + +} // namespace shared +} // namespace android +} // namespace starboard + +#endif // STARBOARD_ANDROID_SHARED_PLAYER_COMPONENTS_FACTORY_H_
diff --git a/src/starboard/android/shared/starboard_platform.gypi b/src/starboard/android/shared/starboard_platform.gypi index 8ec8f70..8b27a29 100644 --- a/src/starboard/android/shared/starboard_platform.gypi +++ b/src/starboard/android/shared/starboard_platform.gypi
@@ -134,6 +134,7 @@ 'media_is_audio_supported.cc', 'media_is_video_supported.cc', 'microphone_impl.cc', + 'player_components_factory.h', 'player_create.cc', 'player_destroy.cc', 'player_get_preferred_output_mode.cc',
diff --git a/src/starboard/android/x86/__init__.py b/src/starboard/android/x86/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/starboard/android/x86/__init__.py
diff --git a/src/starboard/android/x86/cobalt/__init__.py b/src/starboard/android/x86/cobalt/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/starboard/android/x86/cobalt/__init__.py
diff --git a/src/starboard/android/x86/cobalt/configuration.py b/src/starboard/android/x86/cobalt/configuration.py new file mode 100644 index 0000000..5e11fd8 --- /dev/null +++ b/src/starboard/android/x86/cobalt/configuration.py
@@ -0,0 +1,61 @@ +# Copyright 2017-2020 The Cobalt Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Starboard Android x86 Cobalt configuration.""" + +from starboard.android.shared.cobalt import configuration +from starboard.tools.testing import test_filter + + +class CobaltAndroidX86Configuration(configuration.CobaltAndroidConfiguration): + """Starboard Android x86 Cobalt configuration.""" + + def GetTestFilters(self): + filters = super(CobaltAndroidX86Configuration, self).GetTestFilters() + for target, tests in self.__FILTERED_TESTS.iteritems(): + filters.extend(test_filter.TestFilter(target, test) for test in tests) + return filters + + # A map of failing or crashing tests per target + __FILTERED_TESTS = { # pylint: disable=invalid-name + 'graphics_system_test': [ + test_filter.FILTER_ALL + ], + 'layout_tests': [ # Old Android versions don't have matching fonts + 'CSS3FontsLayoutTests/Layout.Test' + '/5_2_use_first_available_listed_font_family', + 'CSS3FontsLayoutTests/Layout.Test' + '/5_2_use_specified_font_family_if_available', + 'CSS3FontsLayoutTests/Layout.Test' + '/5_2_use_system_fallback_if_no_matching_family_is_found*', + 'CSS3FontsLayoutTests/Layout.Test' + '/synthetic_bolding_should_not_occur_on_bold_font', + 'CSS3FontsLayoutTests/Layout.Test' + '/synthetic_bolding_should_occur_on_non_bold_font', + ], + 'nb_test': [ + 'BidirectionalFitReuseAllocatorTest.FallbackBlockMerge', + 'BidirectionalFitReuseAllocatorTest.FreeBlockMergingLeft', + 'BidirectionalFitReuseAllocatorTest.FreeBlockMergingRight', + 'FirstFitReuseAllocatorTest.FallbackBlockMerge', + 'FirstFitReuseAllocatorTest.FreeBlockMergingLeft', + 'FirstFitReuseAllocatorTest.FreeBlockMergingRight', + ], + 'net_unittests': [ # Net tests are very unstable on Android L + test_filter.FILTER_ALL + ], + 'renderer_test': [ + 'PixelTest.YUV422UYVYImageScaledUpSupport', + 'PixelTest.YUV422UYVYImageScaledAndTranslated', + ], + }
diff --git a/src/starboard/android/x86/gyp_configuration.py b/src/starboard/android/x86/gyp_configuration.py index 80134e6..e3e4c2c 100644 --- a/src/starboard/android/x86/gyp_configuration.py +++ b/src/starboard/android/x86/gyp_configuration.py
@@ -1,4 +1,4 @@ -# Copyright 2016 The Cobalt Authors. All Rights Reserved. +# Copyright 2016-2020 The Cobalt Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,10 +14,41 @@ """Starboard Android x86 platform build configuration.""" from starboard.android.shared import gyp_configuration as shared_configuration +from starboard.tools.testing import test_filter def CreatePlatformConfig(): - return shared_configuration.AndroidConfiguration( + return Androidx86Configuration( 'android-x86', 'x86', sabi_json_path='starboard/sabi/x86/sabi-v{sb_api_version}.json') + + +class Androidx86Configuration(shared_configuration.AndroidConfiguration): + + def GetTestFilters(self): + filters = super(Androidx86Configuration, self).GetTestFilters() + for target, tests in self.__FILTERED_TESTS.iteritems(): + filters.extend(test_filter.TestFilter(target, test) for test in tests) + return filters + + # A map of failing or crashing tests per target + __FILTERED_TESTS = { # pylint: disable=invalid-name + 'nplb': [ + 'SbAccessibilityTest.CallSetCaptionsEnabled', + 'SbAccessibilityTest.GetCaptionSettingsReturnIsValid', + 'SbAudioSinkTest.*', + 'SbMicrophoneCloseTest.*', + 'SbMicrophoneOpenTest.*', + 'SbMicrophoneReadTest.*', + 'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.*', + 'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest' + '.SunnyDaySourceForDestination/*', + 'SbMediaSetAudioWriteDurationTests/SbMediaSetAudioWriteDurationTest' + '.WriteContinuedLimitedInput/*', + ], + 'player_filter_tests': [ + 'VideoDecoderTests/VideoDecoderTest.MaxNumberOfCachedFrames/2', + 'AudioDecoderTests/*', + ], + }
diff --git a/src/starboard/build/base_configuration.gypi b/src/starboard/build/base_configuration.gypi index b8f6d94..a723315 100644 --- a/src/starboard/build/base_configuration.gypi +++ b/src/starboard/build/base_configuration.gypi
@@ -81,6 +81,9 @@ # Whether this is an evergreen build. 'sb_evergreen': 0, + # Whether to use crashpad. + 'sb_crashpad_enabled': 0, + # Whether this is an evergreen compatible platform. A compatible platform # can run the elf_loader and launch the evergreen build. 'sb_evergreen_compatible%': '<(sb_evergreen_compatible)',
diff --git a/src/starboard/build/gyp_functions.py b/src/starboard/build/gyp_functions.py new file mode 100644 index 0000000..e6f466d --- /dev/null +++ b/src/starboard/build/gyp_functions.py
@@ -0,0 +1,177 @@ +# Copyright 2020 The Cobalt Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Gyp extensions, called with pymod_do_main.""" +import argparse +import glob +import logging +import os +import stat +import sys + + +# lifted from standard lib webbrowser.py +def isexecutable(cmd): + """Returns whether the input file is an exectuable.""" + if sys.platform[:3] == 'win': + extensions = ('.exe', '.bat', '.cmd') + cmd = cmd.lower() + if cmd.endswith(extensions) and os.path.isfile(cmd): + return cmd + for ext in extensions: + if os.path.isfile(cmd + ext): + return cmd + ext + else: + if os.path.isfile(cmd): + mode = os.stat(cmd)[stat.ST_MODE] + if mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH): + return cmd + + +class ExtensionCommandParser(argparse.ArgumentParser): + """Helper class for parsing arguments to extended gyp functions.""" + + def __init__(self, list_args): + argparse.ArgumentParser.__init__(self) + for arg in list_args: + self.add_argument(arg) + + def error(self, message): + print('Parse error in gyp_functions.py:' + message) + raise NotImplementedError('Parse error:' + message) + + +class Extensions(object): + """Container class for extended functionality for gyp. + + Supports operations such as: + - file globbing + - checking file or directory existence + - string manipulations + - retrieving environment variables + - searching PATH and PYTHONPATH for programs. + """ + + def __init__(self, argv): + parser = argparse.ArgumentParser() + all_cmds = [x for x in dir(self) if not x.startswith('_')] + parser.add_argument('command', help='Command to run', choices=all_cmds) + args = parser.parse_args(argv[0:1]) + self.argv = argv[1:] + self.command = args.command + + def call(self): + return getattr(self, self.command)() + + def file_glob(self): + """Glob files in dir, with pattern glob.""" + args = ExtensionCommandParser(['dir', 'pattern']).parse_args(self.argv) + path = os.path.normpath(os.path.join(args.dir, args.pattern)) + ret = '' + for f in glob.iglob(path): + ret += f.replace(os.sep, '/') + ' ' + return ret.strip() + + def basename(self): + """Basename of list of files""" + parser = ExtensionCommandParser([]) + parser.add_argument('input_list', nargs='*') + args = parser.parse_args(self.argv) + ret = [os.path.basename(x) for x in args.input_list] + return ' '.join(ret) + + def replace_in_list(self): + """String replace in a list of arguments""" + parser = ExtensionCommandParser(['old', 'new']) + parser.add_argument('input_list', nargs='*') + args = parser.parse_args(self.argv) + inp = args.input_list + return ' '.join([x.replace(args.old, args.new) for x in inp]) + + def file_glob_sub(self): + """Glob files, but return filenames with string replace from->to applied.""" + args = ExtensionCommandParser( + ['dir', 'pattern', 'from_string', 'to_string']).parse_args(self.argv) + path = os.path.normpath(os.path.join(args.dir, args.pattern)) + ret = '' + for f in glob.iglob(path): + ret += f.replace(os.sep, '/').replace(args.from_string, + args.to_string) + ' ' + return ret.strip() + + def file_exists(self): + """Checks if a file exists, returning a string '1' if so, or '0'.""" + args = ExtensionCommandParser(['file']).parse_args(self.argv) + filepath = args.file.replace(os.sep, '/') + ret = os.path.isfile(filepath) + return str(int(ret)) + + def dir_exists(self): + """Checks if a directory exists, returning a string 'True' or 'False'.""" + args = ExtensionCommandParser(['dir']).parse_args(self.argv) + return str(os.path.isdir(args.dir)) + + def str_upper(self): + """Converts an input string to upper case.""" + if self.argv: + args = ExtensionCommandParser(['str']).parse_args(self.argv) + return args.str.upper() + return '' + + def find_program(self): + """Searches for the input program name (.exe, .cmd or .bat).""" + args = ExtensionCommandParser(['program']).parse_args(self.argv) + paths_to_check = [] + # Collect all paths in PYTHONPATH. + for i in sys.path: + paths_to_check.append(i.replace(os.sep, '/')) + # Then collect all paths in PATH. + for i in os.environ['PATH'].split(os.pathsep): + paths_to_check.append(i.replace(os.sep, '/')) + # Check all the collected paths for the program. + for path in paths_to_check: + exe = os.path.join(path, args.program) + prog = isexecutable(exe) + if prog: + return prog.replace(os.sep, '/') + # If not in PYTHONPATH and PATH, check upwards until root. + # Note: This is a rare case. + root_dir = os.path.dirname(os.path.abspath(__file__)) + previous_dir = os.path.abspath(__file__) + while root_dir and root_dir != previous_dir: + exe = os.path.join(root_dir, args.program) + prog = isexecutable(exe) + if prog: + return prog.replace(os.sep, '/') + previous_dir = root_dir + root_dir = os.path.dirname(root_dir) + logging.error('Failed to find program "{}".'.format(args.program)) + return None + + def getenv(self): + """Gets the stored value of an environment variable.""" + args = ExtensionCommandParser(['var']).parse_args(self.argv) + value = os.getenv(args.var) + if value is not None: + return value.strip() + return '' + + +def DoMain(argv): # pylint: disable=invalid-name + """Script main function.""" + return Extensions(argv).call() + + +if __name__ == '__main__': + print(DoMain(sys.argv[1:])) + sys.exit(0)
diff --git a/src/starboard/common/configuration_defaults.cc b/src/starboard/common/configuration_defaults.cc index 6dbeb7b..6a933b9 100644 --- a/src/starboard/common/configuration_defaults.cc +++ b/src/starboard/common/configuration_defaults.cc
@@ -30,7 +30,11 @@ } const char* CobaltFallbackSplashScreenUrlDefault() { - return "none"; + return "h5vcc-embedded://black_splash_screen.html"; +} + +const char* CobaltFallbackSplashScreenTopicsDefault() { + return ""; } bool CobaltEnableQuicDefault() {
diff --git a/src/starboard/common/configuration_defaults.h b/src/starboard/common/configuration_defaults.h index c79c210..06114f9 100644 --- a/src/starboard/common/configuration_defaults.h +++ b/src/starboard/common/configuration_defaults.h
@@ -26,6 +26,8 @@ const char* CobaltFallbackSplashScreenUrlDefault(); +const char* CobaltFallbackSplashScreenTopicsDefault(); + bool CobaltEnableQuicDefault(); int CobaltSkiaCacheSizeInBytesDefault();
diff --git a/src/starboard/elf_loader/elf_loader.gyp b/src/starboard/elf_loader/elf_loader.gyp index 7b6a565..4970ae0 100644 --- a/src/starboard/elf_loader/elf_loader.gyp +++ b/src/starboard/elf_loader/elf_loader.gyp
@@ -53,21 +53,17 @@ 'src/include', 'src/src/', ], + 'conditions': [ + ['sb_evergreen_compatible == 1', { + 'variables': { + 'sb_crashpad_enabled': 1, + }, + },], + ], 'dependencies': [ '<(DEPTH)/starboard/elf_loader/evergreen_config.gyp:evergreen_config', '<(DEPTH)/starboard/elf_loader/evergreen_info.gyp:evergreen_info', - '<(DEPTH)/starboard/starboard.gyp:starboard_base', - ], - 'conditions': [ - ['sb_evergreen_compatible == 1', { - 'dependencies': [ - '<(DEPTH)/third_party/crashpad/wrapper/wrapper.gyp:crashpad_wrapper', - ], - }, { - 'dependencies': [ - '<(DEPTH)/third_party/crashpad/wrapper/wrapper.gyp:crashpad_wrapper_stub', - ], - }], + '<(DEPTH)/starboard/starboard.gyp:starboard', ], 'sources': [ '<@(common_elf_loader_sources)', @@ -113,7 +109,7 @@ 'dependencies': [ 'elf_loader', '<(DEPTH)/cobalt/content/fonts/fonts.gyp:copy_font_data', - '<(DEPTH)/starboard/starboard.gyp:starboard_full', + '<(DEPTH)/starboard/starboard.gyp:starboard', # TODO: Remove this dependency once MediaSession is migrated to use CobaltExtensions. '<@(cobalt_platform_dependencies)', ], @@ -149,7 +145,7 @@ ], 'dependencies': [ 'elf_loader_sys', - '<(DEPTH)/starboard/starboard.gyp:starboard_full', + '<(DEPTH)/starboard/starboard.gyp:starboard', ], 'sources': [ 'sandbox.cc', @@ -166,7 +162,7 @@ '<(DEPTH)/starboard/common/test_main.cc', ], 'dependencies': [ - '<(DEPTH)/starboard/starboard.gyp:starboard_full', + '<(DEPTH)/starboard/starboard.gyp:starboard', # TODO: Remove this dependency once MediaSession is migrated to use CobaltExtensions. '<@(cobalt_platform_dependencies)', '<(DEPTH)/testing/gmock.gyp:gmock',
diff --git a/src/starboard/linux/shared/configuration.cc b/src/starboard/linux/shared/configuration.cc index 4b0f775..bb7371e 100644 --- a/src/starboard/linux/shared/configuration.cc +++ b/src/starboard/linux/shared/configuration.cc
@@ -32,7 +32,7 @@ const CobaltExtensionConfigurationApi kConfigurationApi = { kCobaltExtensionConfigurationName, - 1, + 2, &common::CobaltUserOnExitStrategyDefault, &common::CobaltRenderDirtyRegionOnlyDefault, &CobaltEglSwapInterval, @@ -55,6 +55,7 @@ &common::CobaltGcZealDefault, &common::CobaltRasterizerTypeDefault, &common::CobaltEnableJitDefault, + &common::CobaltFallbackSplashScreenTopicsDefault, }; } // namespace
diff --git a/src/starboard/linux/x64x11/configuration_public.h b/src/starboard/linux/x64x11/configuration_public.h index 38bcc2e..1fa3098 100644 --- a/src/starboard/linux/x64x11/configuration_public.h +++ b/src/starboard/linux/x64x11/configuration_public.h
@@ -22,12 +22,6 @@ #ifndef STARBOARD_LINUX_X64X11_CONFIGURATION_PUBLIC_H_ #define STARBOARD_LINUX_X64X11_CONFIGURATION_PUBLIC_H_ -#if SB_API_VERSION != SB_EXPERIMENTAL_API_VERSION -#error \ - "This platform's sabi.json file is expected to track the experimental " \ -"Starboard API version." -#endif // SB_API_VERSION != SB_EXPERIMENTAL_API_VERSION - // --- Architecture Configuration -------------------------------------------- // Configuration parameters that allow the application to make some general
diff --git a/src/starboard/linux/x64x11/gczeal/configuration.cc b/src/starboard/linux/x64x11/gczeal/configuration.cc index 6d80740..5a88df1 100644 --- a/src/starboard/linux/x64x11/gczeal/configuration.cc +++ b/src/starboard/linux/x64x11/gczeal/configuration.cc
@@ -38,7 +38,7 @@ const CobaltExtensionConfigurationApi kConfigurationApi = { kCobaltExtensionConfigurationName, - 1, + 2, &common::CobaltUserOnExitStrategyDefault, &common::CobaltRenderDirtyRegionOnlyDefault, &CobaltEglSwapInterval, @@ -61,6 +61,7 @@ &CobaltGcZeal, &common::CobaltRasterizerTypeDefault, &common::CobaltEnableJitDefault, + &common::CobaltFallbackSplashScreenTopicsDefault, }; } // namespace
diff --git a/src/starboard/linux/x64x11/skia/configuration.cc b/src/starboard/linux/x64x11/skia/configuration.cc index 3b2e26a..0c57f18 100644 --- a/src/starboard/linux/x64x11/skia/configuration.cc +++ b/src/starboard/linux/x64x11/skia/configuration.cc
@@ -38,7 +38,7 @@ const CobaltExtensionConfigurationApi kConfigurationApi = { kCobaltExtensionConfigurationName, - 1, + 2, &common::CobaltUserOnExitStrategyDefault, &common::CobaltRenderDirtyRegionOnlyDefault, &CobaltEglSwapInterval, @@ -61,6 +61,7 @@ &common::CobaltGcZealDefault, &CobaltRasterizerType, &common::CobaltEnableJitDefault, + &common::CobaltFallbackSplashScreenTopicsDefault, }; } // namespace
diff --git a/src/starboard/linux/x64x11/skia/configuration_public.h b/src/starboard/linux/x64x11/skia/configuration_public.h index 8879563..ed69d67a 100644 --- a/src/starboard/linux/x64x11/skia/configuration_public.h +++ b/src/starboard/linux/x64x11/skia/configuration_public.h
@@ -18,14 +18,6 @@ #ifndef STARBOARD_LINUX_X64X11_SKIA_CONFIGURATION_PUBLIC_H_ #define STARBOARD_LINUX_X64X11_SKIA_CONFIGURATION_PUBLIC_H_ -// This is not a released configuration, so it should implement the -// experimental API version to validate trunk's viability. -#if SB_API_VERSION != SB_EXPERIMENTAL_API_VERSION -#error \ - "This platform's sabi.json file is expected to track the experimental " \ -"Starboard API version." -#endif // SB_API_VERSION != SB_EXPERIMENTAL_API_VERSION - // Include the X64X11 Linux configuration. #include "starboard/linux/x64x11/configuration_public.h"
diff --git a/src/starboard/loader_app/installation_manager.cc b/src/starboard/loader_app/installation_manager.cc index 9325c94..0401b50 100644 --- a/src/starboard/loader_app/installation_manager.cc +++ b/src/starboard/loader_app/installation_manager.cc
@@ -574,20 +574,23 @@ bool InstallationManager::SaveInstallationStore() { ValidatePriorities(); - char buf[IM_MAX_INSTALLATION_STORE_SIZE]; + if (IM_MAX_INSTALLATION_STORE_SIZE < installation_store_.ByteSize()) { SB_LOG(ERROR) << "SaveInstallationStore: Data too large" << installation_store_.ByteSize(); return false; } + const size_t buf_size = installation_store_.ByteSize(); + std::vector<char> buf(buf_size, 0); loader_app::SetPendingRestart( installation_store_.roll_forward_to_installation() != -1); - installation_store_.SerializeToArray(buf, installation_store_.ByteSize()); + installation_store_.SerializeToArray(buf.data(), + installation_store_.ByteSize()); #if SB_API_VERSION >= 12 - if (!SbFileAtomicReplace(store_path_.c_str(), buf, + if (!SbFileAtomicReplace(store_path_.c_str(), buf.data(), installation_store_.ByteSize())) { SB_LOG(ERROR) << "SaveInstallationStore: Failed to store installation store: "
diff --git a/src/starboard/nplb/mutex_acquire_test.cc b/src/starboard/nplb/mutex_acquire_test.cc index c37a465..81ad198 100644 --- a/src/starboard/nplb/mutex_acquire_test.cc +++ b/src/starboard/nplb/mutex_acquire_test.cc
@@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/common/mutex.h" #include "starboard/configuration.h" +#include "starboard/mutex.h" #include "starboard/thread.h" #include "testing/gtest/include/gtest/gtest.h"
diff --git a/src/starboard/nplb/mutex_acquire_try_test.cc b/src/starboard/nplb/mutex_acquire_try_test.cc index ccd7e5b..e38e897 100644 --- a/src/starboard/nplb/mutex_acquire_try_test.cc +++ b/src/starboard/nplb/mutex_acquire_try_test.cc
@@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/common/mutex.h" #include "starboard/configuration.h" +#include "starboard/mutex.h" #include "testing/gtest/include/gtest/gtest.h" #if SB_API_VERSION >= 12
diff --git a/src/starboard/nplb/mutex_create_test.cc b/src/starboard/nplb/mutex_create_test.cc index 8c11eaa..f52ba08 100644 --- a/src/starboard/nplb/mutex_create_test.cc +++ b/src/starboard/nplb/mutex_create_test.cc
@@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/common/mutex.h" #include "starboard/configuration.h" +#include "starboard/mutex.h" #include "testing/gtest/include/gtest/gtest.h" #if SB_API_VERSION >= 12
diff --git a/src/starboard/nplb/mutex_destroy_test.cc b/src/starboard/nplb/mutex_destroy_test.cc index e555b70..5df8a3a 100644 --- a/src/starboard/nplb/mutex_destroy_test.cc +++ b/src/starboard/nplb/mutex_destroy_test.cc
@@ -14,8 +14,8 @@ // Destroy is mostly Sunny Day tested in Create. -#include "starboard/common/mutex.h" #include "starboard/configuration.h" +#include "starboard/mutex.h" #include "testing/gtest/include/gtest/gtest.h" namespace starboard {
diff --git a/src/starboard/nplb/nplb_evergreen_compat_tests/sabi_test.cc b/src/starboard/nplb/nplb_evergreen_compat_tests/sabi_test.cc index 60b6d6e..ac659ac 100644 --- a/src/starboard/nplb/nplb_evergreen_compat_tests/sabi_test.cc +++ b/src/starboard/nplb/nplb_evergreen_compat_tests/sabi_test.cc
@@ -155,6 +155,12 @@ }; TEST_F(SabiTest, VerifySABI) { + SB_LOG(INFO) << "Using SB_API_VERSION=" << SB_API_VERSION; + SB_LOG(INFO) << "Using SABI=" << SB_SABI_JSON_ID; + + ASSERT_LT(SB_API_VERSION, SB_EXPERIMENTAL_API_VERSION) + << "Evergreen should use SB_API_VERSION < SB_EXPERIMENTAL_API_VERSION"; + std::set<std::string> sabi_set; sabi_set.insert(kSabiJsonIdArmHardfp); sabi_set.insert(kSabiJsonIdArmSoftfp);
diff --git a/src/starboard/raspi/2/skia/configuration.cc b/src/starboard/raspi/2/skia/configuration.cc index 58a6f11..925b92b 100644 --- a/src/starboard/raspi/2/skia/configuration.cc +++ b/src/starboard/raspi/2/skia/configuration.cc
@@ -38,7 +38,7 @@ const CobaltExtensionConfigurationApi kConfigurationApi = { kCobaltExtensionConfigurationName, - 1, + 2, &common::CobaltUserOnExitStrategyDefault, &common::CobaltRenderDirtyRegionOnlyDefault, &common::CobaltEglSwapIntervalDefault, @@ -61,6 +61,7 @@ &common::CobaltGcZealDefault, &CobaltRasterizerType, &common::CobaltEnableJitDefault, + &common::CobaltFallbackSplashScreenTopicsDefault, }; } // namespace
diff --git a/src/starboard/raspi/shared/configuration.cc b/src/starboard/raspi/shared/configuration.cc index b3a9f0b..74ab33e 100644 --- a/src/starboard/raspi/shared/configuration.cc +++ b/src/starboard/raspi/shared/configuration.cc
@@ -32,7 +32,7 @@ } const CobaltExtensionConfigurationApi kConfigurationApi = { kCobaltExtensionConfigurationName, - 1, + 2, &common::CobaltUserOnExitStrategyDefault, &common::CobaltRenderDirtyRegionOnlyDefault, &common::CobaltEglSwapIntervalDefault, @@ -55,6 +55,7 @@ &common::CobaltGcZealDefault, &common::CobaltRasterizerTypeDefault, &common::CobaltEnableJitDefault, + &common::CobaltFallbackSplashScreenTopicsDefault, }; } // namespace
diff --git a/src/starboard/raspi/shared/configuration_public.h b/src/starboard/raspi/shared/configuration_public.h index 05988f6..3478725 100644 --- a/src/starboard/raspi/shared/configuration_public.h +++ b/src/starboard/raspi/shared/configuration_public.h
@@ -17,12 +17,6 @@ #ifndef STARBOARD_RASPI_SHARED_CONFIGURATION_PUBLIC_H_ #define STARBOARD_RASPI_SHARED_CONFIGURATION_PUBLIC_H_ -#if SB_API_VERSION != SB_EXPERIMENTAL_API_VERSION -#error \ - "This platform's sabi.json file is expected to track the experimental " \ -"Starboard API version." -#endif // SB_API_VERSION != SB_EXPERIMENTAL_API_VERSION - // --- Architecture Configuration -------------------------------------------- // --- System Header Configuration -------------------------------------------
diff --git a/src/starboard/sabi/sabi.py b/src/starboard/sabi/sabi.py index 09d04a3..3d95166 100644 --- a/src/starboard/sabi/sabi.py +++ b/src/starboard/sabi/sabi.py
@@ -13,4 +13,4 @@ # limitations under the License. """Source of truth of the default/experimental Starboard API version.""" -SB_API_VERSION = 13 +SB_API_VERSION = 12
diff --git a/src/starboard/shared/pthread/mutex_acquire.cc b/src/starboard/shared/pthread/mutex_acquire.cc index 67f26b4..6183f7d 100644 --- a/src/starboard/shared/pthread/mutex_acquire.cc +++ b/src/starboard/shared/pthread/mutex_acquire.cc
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/common/mutex.h" +#include "starboard/mutex.h" #include <pthread.h>
diff --git a/src/starboard/shared/pthread/mutex_acquire_try.cc b/src/starboard/shared/pthread/mutex_acquire_try.cc index 4c216a5..8c5c241 100644 --- a/src/starboard/shared/pthread/mutex_acquire_try.cc +++ b/src/starboard/shared/pthread/mutex_acquire_try.cc
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/common/mutex.h" +#include "starboard/mutex.h" #include <pthread.h>
diff --git a/src/starboard/shared/pthread/mutex_create.cc b/src/starboard/shared/pthread/mutex_create.cc index e72884d..55b6a94 100644 --- a/src/starboard/shared/pthread/mutex_create.cc +++ b/src/starboard/shared/pthread/mutex_create.cc
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/common/mutex.h" +#include "starboard/mutex.h" #include <pthread.h>
diff --git a/src/starboard/shared/pthread/mutex_destroy.cc b/src/starboard/shared/pthread/mutex_destroy.cc index c131595..f2d0b15 100644 --- a/src/starboard/shared/pthread/mutex_destroy.cc +++ b/src/starboard/shared/pthread/mutex_destroy.cc
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/common/mutex.h" +#include "starboard/mutex.h" #include <pthread.h>
diff --git a/src/starboard/shared/pthread/mutex_release.cc b/src/starboard/shared/pthread/mutex_release.cc index fcd7de7..d2ce183 100644 --- a/src/starboard/shared/pthread/mutex_release.cc +++ b/src/starboard/shared/pthread/mutex_release.cc
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/common/mutex.h" +#include "starboard/mutex.h" #include <pthread.h>
diff --git a/src/starboard/shared/signal/suspend_signals.cc b/src/starboard/shared/signal/suspend_signals.cc index e58e065..c728e5f 100644 --- a/src/starboard/shared/signal/suspend_signals.cc +++ b/src/starboard/shared/signal/suspend_signals.cc
@@ -58,26 +58,10 @@ ::sigaction(signal_id, &action, NULL); } -#if SB_IS(EVERGREEN_COMPATIBLE) -void RequestSuspendOrStop() { - if (loader_app::IsPendingRestart()) { - SbLogRawFormatF("\nPending update restart . Stopping.\n"); - SbLogFlush(); - SbSystemRequestStop(0); - } else { - SbSystemRequestSuspend(); - } -} -#endif - void Suspend(int signal_id) { SignalMask(kAllSignals, SIG_BLOCK); LogSignalCaught(signal_id); -#if SB_IS(EVERGREEN_COMPATIBLE) - RequestSuspendOrStop(); -#else SbSystemRequestSuspend(); -#endif SignalMask(kAllSignals, SIG_UNBLOCK); }
diff --git a/src/starboard/shared/signal/system_request_suspend.cc b/src/starboard/shared/signal/system_request_suspend.cc index e76ee39..0ea5fa8 100644 --- a/src/starboard/shared/signal/system_request_suspend.cc +++ b/src/starboard/shared/signal/system_request_suspend.cc
@@ -17,11 +17,26 @@ #include "starboard/shared/signal/signal_internal.h" #include "starboard/shared/starboard/application.h" +#if SB_IS(EVERGREEN_COMPATIBLE) +#include "starboard/loader_app/pending_restart.h" +#endif + void SuspendDone(void* context) { // Stop all thread execution after fully transitioning into Suspended. raise(SIGSTOP); } void SbSystemRequestSuspend() { +#if SB_IS(EVERGREEN_COMPATIBLE) + if (starboard::loader_app::IsPendingRestart()) { + SbLogRawFormatF("\nPending update restart . Stopping.\n"); + SbLogFlush(); + starboard::shared::starboard::Application::Get()->Stop(0); + } else { + starboard::shared::starboard::Application::Get()->Suspend(NULL, + &SuspendDone); + } +#else starboard::shared::starboard::Application::Get()->Suspend(NULL, &SuspendDone); +#endif }
diff --git a/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h b/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h index bffc40a..c1cf847 100644 --- a/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h +++ b/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h
@@ -27,6 +27,7 @@ // When |capability_changed| is true, it hints that the error is caused by a // a transisent capability on the platform. The app should retry playback to // recover from the error. + // TODO: Allow to pass an error message. typedef void (*ErrorFunc)(bool capability_changed, void* context); #endif // SB_API_VERSION >= 12
diff --git a/src/starboard/shared/starboard/media/media_util.cc b/src/starboard/shared/starboard/media/media_util.cc index 3269d04..d10b991 100644 --- a/src/starboard/shared/starboard/media/media_util.cc +++ b/src/starboard/shared/starboard/media/media_util.cc
@@ -623,6 +623,20 @@ return "Invalid"; } +#if SB_API_VERSION >= 11 +bool IsAudioSampleInfoSubstantiallyDifferent( + const SbMediaAudioSampleInfo& left, + const SbMediaAudioSampleInfo& right) { + return left.codec != right.codec || + left.samples_per_second != right.samples_per_second || + left.number_of_channels != right.number_of_channels || + left.audio_specific_config_size != right.audio_specific_config_size || + SbMemoryCompare(left.audio_specific_config, + right.audio_specific_config, + left.audio_specific_config_size) != 0; +} +#endif // SB_API_VERSION < 11 + } // namespace media } // namespace starboard } // namespace shared
diff --git a/src/starboard/shared/starboard/media/media_util.h b/src/starboard/shared/starboard/media/media_util.h index 72edc89..15a9742 100644 --- a/src/starboard/shared/starboard/media/media_util.h +++ b/src/starboard/shared/starboard/media/media_util.h
@@ -104,6 +104,14 @@ const char* GetMatrixIdName(SbMediaMatrixId matrix_id); const char* GetRangeIdName(SbMediaRangeId range_id); +#if SB_API_VERSION >= 11 +// When this function returns true, usually indicates that the two sample info +// cannot be processed by the same audio decoder. +bool IsAudioSampleInfoSubstantiallyDifferent( + const SbMediaAudioSampleInfo& left, + const SbMediaAudioSampleInfo& right); +#endif // SB_API_VERSION < 11 + } // namespace media } // namespace starboard } // namespace shared
diff --git a/src/starboard/shared/starboard/player/decoded_audio_internal.cc b/src/starboard/shared/starboard/player/decoded_audio_internal.cc index 1e21a40..aca60f5 100644 --- a/src/starboard/shared/starboard/player/decoded_audio_internal.cc +++ b/src/starboard/shared/starboard/player/decoded_audio_internal.cc
@@ -81,10 +81,11 @@ if (samples_per_second == 0 || frames_to_remove < 0 || frames_to_remove >= frames()) { - SB_LOG(WARNING) << "AdjustForSeekTime failed for seeking_to_time at " - << seeking_to_time << " for samples_per_second " - << samples_per_second << ", and there are " << frames() - << " frames in the DecodedAudio object."; + SB_LOG(WARNING) << "AdjustForSeekTime failed for seeking_to_time: " + << seeking_to_time + << ", samples_per_second: " << samples_per_second + << ", timestamp: " << timestamp() << ", and there are " + << frames() << " frames in the DecodedAudio object."; return; }
diff --git a/src/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.cc b/src/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.cc index ae3b448..8eba0ca 100644 --- a/src/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.cc +++ b/src/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.cc
@@ -17,6 +17,7 @@ #include "starboard/audio_sink.h" #include "starboard/common/log.h" #include "starboard/common/reset_and_return.h" +#include "starboard/shared/starboard/media/media_util.h" #include "starboard/shared/starboard/player/decoded_audio_internal.h" namespace starboard { @@ -34,29 +35,6 @@ kDefaultOutputSamplesPerSecond); } -bool IsResetDecoderNecessary(const SbMediaAudioSampleInfo& current_info, - const SbMediaAudioSampleInfo& new_info) { - if (current_info.codec != new_info.codec) { - return true; - } - if (current_info.samples_per_second != new_info.samples_per_second) { - return true; - } - if (current_info.number_of_channels != new_info.number_of_channels) { - return true; - } - if (current_info.audio_specific_config_size != - new_info.audio_specific_config_size) { - return true; - } - if (SbMemoryCompare(current_info.audio_specific_config, - new_info.audio_specific_config, - current_info.audio_specific_config_size) != 0) { - return true; - } - return false; -} - AdaptiveAudioDecoder::AdaptiveAudioDecoder( const SbMediaAudioSampleInfo& audio_sample_info, SbDrmSystem drm_system, @@ -107,8 +85,8 @@ } return; } - if (IsResetDecoderNecessary(input_audio_sample_info_, - input_buffer->audio_sample_info())) { + if (starboard::media::IsAudioSampleInfoSubstantiallyDifferent( + input_audio_sample_info_, input_buffer->audio_sample_info())) { flushing_ = true; pending_input_buffer_ = input_buffer; pending_consumed_cb_ = consumed_cb;
diff --git a/src/starboard/shared/stub/mutex_acquire.cc b/src/starboard/shared/stub/mutex_acquire.cc index cede4d1..423202e 100644 --- a/src/starboard/shared/stub/mutex_acquire.cc +++ b/src/starboard/shared/stub/mutex_acquire.cc
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/common/mutex.h" +#include "starboard/mutex.h" SbMutexResult SbMutexAcquire(SbMutex* mutex) { return kSbMutexDestroyed;
diff --git a/src/starboard/shared/stub/mutex_acquire_try.cc b/src/starboard/shared/stub/mutex_acquire_try.cc index a9f1ba8..efc87b6 100644 --- a/src/starboard/shared/stub/mutex_acquire_try.cc +++ b/src/starboard/shared/stub/mutex_acquire_try.cc
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/common/mutex.h" +#include "starboard/mutex.h" SbMutexResult SbMutexAcquireTry(SbMutex* mutex) { return kSbMutexDestroyed;
diff --git a/src/starboard/shared/stub/mutex_create.cc b/src/starboard/shared/stub/mutex_create.cc index 11586de..4439ee0 100644 --- a/src/starboard/shared/stub/mutex_create.cc +++ b/src/starboard/shared/stub/mutex_create.cc
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/common/mutex.h" +#include "starboard/mutex.h" bool SbMutexCreate(SbMutex* mutex) { return false;
diff --git a/src/starboard/shared/stub/mutex_destroy.cc b/src/starboard/shared/stub/mutex_destroy.cc index 7922ddd..b88ae22 100644 --- a/src/starboard/shared/stub/mutex_destroy.cc +++ b/src/starboard/shared/stub/mutex_destroy.cc
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/common/mutex.h" +#include "starboard/mutex.h" bool SbMutexDestroy(SbMutex* mutex) { return false;
diff --git a/src/starboard/shared/stub/mutex_release.cc b/src/starboard/shared/stub/mutex_release.cc index 88e6071..207b642 100644 --- a/src/starboard/shared/stub/mutex_release.cc +++ b/src/starboard/shared/stub/mutex_release.cc
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/common/mutex.h" +#include "starboard/mutex.h" bool SbMutexRelease(SbMutex* mutex) { return false;
diff --git a/src/starboard/starboard.gyp b/src/starboard/starboard.gyp index 0808490..9c74ba2 100644 --- a/src/starboard/starboard.gyp +++ b/src/starboard/starboard.gyp
@@ -19,7 +19,7 @@ { 'targets': [ { - 'target_name': 'starboard_base', + 'target_name': 'starboard', 'type': 'none', 'conditions': [ ['sb_evergreen == 1', { @@ -37,14 +37,6 @@ 'starboard_full', ], }], - ], - }, - { - 'target_name': 'starboard', - 'type': 'none', - 'dependencies': [ - '<(DEPTH)/third_party/crashpad/wrapper/wrapper.gyp:crashpad_wrapper_stub', - 'starboard_base', ], }, { @@ -60,6 +52,15 @@ '<(DEPTH)/<(starboard_path)/starboard_platform.gyp:starboard_platform', ], 'conditions': [ + ['sb_crashpad_enabled == 1', { + 'dependencies': [ + '<(DEPTH)/third_party/crashpad/wrapper/wrapper.gyp:crashpad_wrapper', + ], + }, { + 'dependencies': [ + '<(DEPTH)/third_party/crashpad/wrapper/wrapper.gyp:crashpad_wrapper_stub', + ], + }], ['final_executable_type=="shared_library"', { 'all_dependent_settings': { 'target_conditions': [
diff --git a/src/starboard/stub/configuration.cc b/src/starboard/stub/configuration.cc index 6af5309..1f9a6dd 100644 --- a/src/starboard/stub/configuration.cc +++ b/src/starboard/stub/configuration.cc
@@ -28,7 +28,7 @@ const CobaltExtensionConfigurationApi kConfigurationApi = { kCobaltExtensionConfigurationName, - 1, + 2, &common::CobaltUserOnExitStrategyDefault, &common::CobaltRenderDirtyRegionOnlyDefault, &common::CobaltEglSwapIntervalDefault, @@ -51,6 +51,7 @@ &common::CobaltGcZealDefault, &CobaltRasterizerType, &common::CobaltEnableJitDefault, + &common::CobaltFallbackSplashScreenTopicsDefault, }; } // namespace
diff --git a/src/starboard/stub/configuration_public.h b/src/starboard/stub/configuration_public.h index f27313c..cc64e9d 100644 --- a/src/starboard/stub/configuration_public.h +++ b/src/starboard/stub/configuration_public.h
@@ -22,12 +22,6 @@ #ifndef STARBOARD_STUB_CONFIGURATION_PUBLIC_H_ #define STARBOARD_STUB_CONFIGURATION_PUBLIC_H_ -#if SB_API_VERSION != SB_EXPERIMENTAL_API_VERSION -#error \ - "This platform's sabi.json file is expected to track the experimental " \ -"Starboard API version." -#endif // SB_API_VERSION != SB_EXPERIMENTAL_API_VERSION - // --- Architecture Configuration -------------------------------------------- // Some platforms will not align variables on the stack with an alignment
diff --git a/src/starboard/tools/abstract_launcher.py b/src/starboard/tools/abstract_launcher.py index 1ba0165..51a9d3c 100644 --- a/src/starboard/tools/abstract_launcher.py +++ b/src/starboard/tools/abstract_launcher.py
@@ -23,6 +23,10 @@ from starboard.tools import build from starboard.tools import paths +ARG_NOINSTALL = "noinstall" +ARG_SYSTOOLS = "systools" +ARG_DRYRUN = "dryrun" + def _GetLauncherForPlatform(platform_name): """Gets the module containing a platform's concrete launcher implementation. @@ -125,6 +129,11 @@ env_variables = {} self.env_variables = env_variables + launcher_args = kwargs.get("launcher_args", None) + if launcher_args is None: + launcher_args = [] + self.launcher_args = launcher_args + # Launchers that need different startup timeout times should reassign # this variable during initialization. self.startup_timeout_seconds = 2 * 60
diff --git a/src/starboard/tools/app_launcher_packager.py b/src/starboard/tools/app_launcher_packager.py index 9090373..ee8ade8 100644 --- a/src/starboard/tools/app_launcher_packager.py +++ b/src/starboard/tools/app_launcher_packager.py
@@ -24,6 +24,7 @@ import logging import os import shutil +import string import sys import tempfile @@ -32,21 +33,18 @@ from paths import THIRD_PARTY_ROOT sys.path.append(THIRD_PARTY_ROOT) # pylint: disable=g-import-not-at-top,g-bad-import-order -import jinja2 from starboard.tools import port_symlink import starboard.tools.platform # Default python directories to app launcher resources. _INCLUDE_FILE_PATTERNS = [ - ('buildbot', '*.py'), + ('buildbot', '_env.py'), # Only needed for device_server to execute + ('buildbot', '__init__.py'), # Only needed for device_server to execute + ('buildbot/device_server', '*.py'), ('buildbot/device_server/shared/ssl_certs', '*'), ('cobalt', '*.py'), - # TODO: Test and possibly prune. - ('lbshell', '*.py'), ('starboard', '*.py'), - # jinja2 required by this app_launcher_packager.py script. - ('third_party/jinja2', '*.py'), - ('third_party/markupsafe', '*.py'), # Required by third_party/jinja2 + ('starboard/tools', 'platform.py.template') ] _INCLUDE_BLACK_BOX_TESTS_PATTERNS = [ @@ -124,7 +122,7 @@ logging.info('Baking platform info files.') current_file = os.path.abspath(__file__) current_dir = os.path.dirname(current_file) - dest_dir = current_dir.replace(repo_root, dest_root) + dest_dir = os.path.join(dest_root, 'starboard', 'tools') platforms_map = {} for p in starboard.tools.platform.GetAll(): platform_path = os.path.relpath( @@ -132,10 +130,11 @@ # Store posix paths even on Windows so MH Linux hosts can use them. # The template has code to re-normalize them when used on Windows hosts. platforms_map[p] = platform_path.replace('\\', '/') - template = jinja2.Template( + template = string.Template( open(os.path.join(current_dir, 'platform.py.template')).read()) with open(os.path.join(dest_dir, 'platform.py'), 'w+') as f: - template.stream(platforms_map=platforms_map).dump(f, encoding='utf-8') + sub = template.substitute(platforms_map=platforms_map) + f.write(sub.encode('utf-8')) logging.info('Finished baking in platform info files.') @@ -257,7 +256,11 @@ help='List to stdout the application resources relative to the current ' 'directory.') parser.add_argument( - '-v', '--verbose', action='store_true', help='Verbose logging output.') + '-v', + '--verbose', + action='store_true', + help='Enables verbose logging. For more control over the ' + "logging level use '--log_level' instead.") args = parser.parse_args(command_args) if not args.verbose:
diff --git a/src/starboard/tools/platform.py.template b/src/starboard/tools/platform.py.template index 17d55b5..204d415 100644 --- a/src/starboard/tools/platform.py.template +++ b/src/starboard/tools/platform.py.template
@@ -20,7 +20,7 @@ from starboard.tools import environment # The name->platform path mapping. -_PATH_MAP = {{platforms_map}} +_PATH_MAP = $platforms_map _PATH_MAP = {k:os.path.normpath(v) for k,v in _PATH_MAP.iteritems()}
diff --git a/src/starboard/tools/testing/test_runner.py b/src/starboard/tools/testing/test_runner.py index e6ca771..562bb25 100755 --- a/src/starboard/tools/testing/test_runner.py +++ b/src/starboard/tools/testing/test_runner.py
@@ -218,7 +218,8 @@ application_name=None, dry_run=False, xml_output_dir=None, - log_xml_results=False): + log_xml_results=False, + launcher_args=None): self.platform = platform self.config = config self.loader_platform = loader_platform @@ -227,6 +228,7 @@ self.target_params = target_params self.out_directory = out_directory self.loader_out_directory = loader_out_directory + self.launcher_args = launcher_args if not self.out_directory: self.out_directory = paths.BuildOutputDirectory(self.platform, self.config) @@ -336,6 +338,7 @@ return final_targets def _GetTestFilters(self): + """Get test filters for a given platform and configuration.""" filters = self._platform_config.GetTestFilters() app_filters = self._app_config.GetTestFilters() if app_filters: @@ -348,10 +351,10 @@ loader_platform_config = build.GetPlatformConfig(self.loader_platform) loader_app_config = loader_platform_config.GetApplicationConfiguration( self.application_name) - for filter in (loader_platform_config.GetTestFilters() + - loader_app_config.GetTestFilters()): - if filter not in filters: - filters.append(filter) + for filter_ in (loader_platform_config.GetTestFilters() + + loader_app_config.GetTestFilters()): + if filter_ not in filters: + filters.append(filter_) return filters def _GetAllTestEnvVariables(self): @@ -430,7 +433,8 @@ env_variables=env, loader_platform=self.loader_platform, loader_config=self.loader_config, - loader_out_directory=self.loader_out_directory) + loader_out_directory=self.loader_out_directory, + launcher_args=self.launcher_args) test_reader = TestLineReader(read_pipe) test_launcher = TestLauncher(launcher) @@ -547,10 +551,10 @@ total_flaky_failed_count = 0 total_filtered_count = 0 - print # Explicit print for empty formatting line. + print() # Explicit print for empty formatting line. logging.info("TEST RUN COMPLETE.") if results: - print # Explicit print for empty formatting line. + print() # Explicit print for empty formatting line. # If the number of run tests from a test binary cannot be # determined, assume an error occurred while running it. @@ -594,7 +598,7 @@ # Sometimes the returned test "name" includes information about the # parameter that was passed to it. This needs to be stripped off. retry_result = self._RunTest(target_name, test_case.split(",")[0]) - print # Explicit print for empty formatting line. + print() # Explicit print for empty formatting line. if retry_result[2] == 1: flaky_passed_tests.append(test_case) logging.info("%s succeeded on run #%d!\n", test_case, retry + 2) @@ -707,11 +711,12 @@ # tests so we need to build it separately. if self.loader_platform: build_tests.BuildTargets( - [_LOADER_TARGET, _CRASHPAD_TARGET], self.loader_out_directory, self.dry_run, - extra_flags + [os.getenv('TEST_RUNNER_PLATFORM_BUILD_FLAGS', '')]) + [_LOADER_TARGET, _CRASHPAD_TARGET], self.loader_out_directory, + self.dry_run, + extra_flags + [os.getenv("TEST_RUNNER_PLATFORM_BUILD_FLAGS", "")]) build_tests.BuildTargets( self.test_targets, self.out_directory, self.dry_run, - extra_flags + [os.getenv('TEST_RUNNER_BUILD_FLAGS', '')]) + extra_flags + [os.getenv("TEST_RUNNER_BUILD_FLAGS", "")]) except subprocess.CalledProcessError as e: result = False @@ -827,6 +832,13 @@ action="store_true", help="If set, results will be logged in xml format after all tests are" " complete. --xml_output_dir will be ignored.") + arg_parser.add_argument( + "-w", + "--launcher_args", + help="Pass space-separated arguments to control launcher behaviour. " + "Arguments are plaform specific and may not be implemented for all " + "platforms. Common arguments are:\n\t'noinstall' - skip install steps " + "before running the test\n\t'systools' - use system-installed tools.") args = arg_parser.parse_args() if (args.loader_platform and not args.loader_config or @@ -840,12 +852,19 @@ if args.target_params: target_params = args.target_params.split(" ") + launcher_args = [] + if args.launcher_args: + launcher_args = args.launcher_args.split(" ") + + if args.dry_run: + launcher_args.append(abstract_launcher.ARG_DRYRUN) + runner = TestRunner(args.platform, args.config, args.loader_platform, args.loader_config, args.device_id, args.target_name, target_params, args.out_directory, args.loader_out_directory, args.platform_tests_only, args.application_name, args.dry_run, args.xml_output_dir, - args.log_xml_results) + args.log_xml_results, launcher_args) def Abort(signum, frame): del signum, frame # Unused.
diff --git a/src/starboard/tools/toolchain/cmd.py b/src/starboard/tools/toolchain/cmd.py index 15a7963..cb30844 100644 --- a/src/starboard/tools/toolchain/cmd.py +++ b/src/starboard/tools/toolchain/cmd.py
@@ -27,6 +27,10 @@ class Shell(abstract.Shell): """Constructs command lines using Cmd syntax.""" + def __init__(self, quote=True): + # Toggle whether or not to quote command line arguments. + self.quote = quote + def MaybeQuoteArgument(self, arg): # Rather than attempting to enumerate the bad shell characters, just # whitelist common OK ones and quote anything else. @@ -76,7 +80,10 @@ def Join(self, command): assert not isinstance(command, basestring) - return ' '.join(self.MaybeQuoteArgument(argument) for argument in command) + if self.quote: + return ' '.join(self.MaybeQuoteArgument(argument) for argument in command) + else: + return ' '.join(command) def And(self, *commands): return ' && '.join(_MaybeJoin(self, command) for command in commands)
diff --git a/src/starboard/tools/toolchain/python.py b/src/starboard/tools/toolchain/python.py index bee8841..0f32d88 100644 --- a/src/starboard/tools/toolchain/python.py +++ b/src/starboard/tools/toolchain/python.py
@@ -11,18 +11,19 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Allow to use gyp-win-tool via python as a copy and a stamp tools.""" +"""Allow use of gyp-win-tool via python as copy and stamp tools.""" import sys from starboard.tools.toolchain import abstract class Copy(abstract.Copy): - """Copies individual files using python.exe.""" + """Copies individual files.""" def __init__(self, **kwargs): self._path = kwargs.get('path', sys.executable) - self._extra_flags = kwargs.get('extra_flags', []) + self._extra_flags = kwargs.get('extra_flags', + ['gyp-win-tool', 'recursive-mirror']) def GetPath(self): return self._path @@ -55,7 +56,7 @@ def __init__(self, **kwargs): self._path = kwargs.get('path', sys.executable) - self._extra_flags = kwargs.get('extra_flags', []) + self._extra_flags = kwargs.get('extra_flags', ['gyp-win-tool', 'stamp']) def GetPath(self): return self._path
diff --git a/src/third_party/angle/include/platform/Platform.h b/src/third_party/angle/include/platform/Platform.h index 09505a3..1035f8e 100644 --- a/src/third_party/angle/include/platform/Platform.h +++ b/src/third_party/angle/include/platform/Platform.h
@@ -236,11 +236,11 @@ using ProgramKeyType = std::array<uint8_t, 20>; using CacheProgramFunc = void (*)(PlatformMethods *platform, const ProgramKeyType &key, - size_t programSize, + std::size_t programSize, const uint8_t *programBytes); inline void DefaultCacheProgram(PlatformMethods *platform, const ProgramKeyType &key, - size_t programSize, + std::size_t programSize, const uint8_t *programBytes) {}
diff --git a/src/third_party/crashpad/util/file/file_io.h b/src/third_party/crashpad/util/file/file_io.h index 6fa0f96..46b1ee6 100644 --- a/src/third_party/crashpad/util/file/file_io.h +++ b/src/third_party/crashpad/util/file/file_io.h
@@ -19,6 +19,7 @@ #include <string> +#include "base/base_wrapper.h" #include "build/build_config.h" #if defined(OS_POSIX)
diff --git a/src/third_party/crashpad/util/net/http_transport_socket.cc b/src/third_party/crashpad/util/net/http_transport_socket.cc index 5a88ccd..278a8b8 100644 --- a/src/third_party/crashpad/util/net/http_transport_socket.cc +++ b/src/third_party/crashpad/util/net/http_transport_socket.cc
@@ -144,7 +144,12 @@ kSbFileSepString + "cobalt" + kSbFileSepString + "content" + kSbFileSepString + "ssl" + kSbFileSepString + "certs"); - + // If this is not Cobalt Evergreen setup use the regular content path. + if (!SbFileExists(cert_location.c_str())) { + cert_location = buffer.data(); + cert_location.append(std::string(kSbFileSepString) + "ssl" + + kSbFileSepString + "certs"); + } if (SSL_CTX_load_verify_locations( ctx_.get(), nullptr, cert_location.c_str()) <= 0) { LOG(ERROR) << "SSL_CTX_load_verify_locations";
diff --git a/src/third_party/crashpad/wrapper/wrapper.cc b/src/third_party/crashpad/wrapper/wrapper.cc index 0ab09e5..ef36040 100644 --- a/src/third_party/crashpad/wrapper/wrapper.cc +++ b/src/third_party/crashpad/wrapper/wrapper.cc
@@ -25,6 +25,7 @@ #include "client/settings.h" #include "starboard/configuration_constants.h" #include "starboard/directory.h" +#include "starboard/file.h" #include "starboard/system.h" namespace third_party { @@ -179,6 +180,12 @@ ::crashpad::CrashpadClient* client = GetCrashpadClient(); const base::FilePath handler_path = GetPathToCrashpadHandlerBinary(); + if (!SbFileExists(handler_path.value().c_str())) { + LOG(WARNING) << "crashpad_handler not at expected location of " + << handler_path.value(); + return; + } + const base::FilePath database_directory_path = GetDatabasePath(); const base::FilePath default_metrics_dir; const std::string product_name = GetProductName();
diff --git a/src/third_party/crashpad/wrapper/wrapper.gyp b/src/third_party/crashpad/wrapper/wrapper.gyp index a9a4382..b7196e1 100644 --- a/src/third_party/crashpad/wrapper/wrapper.gyp +++ b/src/third_party/crashpad/wrapper/wrapper.gyp
@@ -22,22 +22,16 @@ 'wrapper.h', ], }, - ], - 'conditions': [ - ['sb_evergreen_compatible == 1', { - 'targets': [ - { - 'target_name': 'crashpad_wrapper', - 'type': 'static_library', - 'sources': [ - 'wrapper.cc', - 'wrapper.h', - ], - 'dependencies': [ - '<(DEPTH)/third_party/crashpad/client/client.gyp:crashpad_client', - ], - }, + { + 'target_name': 'crashpad_wrapper', + 'type': 'static_library', + 'sources': [ + 'wrapper.cc', + 'wrapper.h', ], - }], + 'dependencies': [ + '<(DEPTH)/third_party/crashpad/client/client.gyp:crashpad_client', + ], + }, ], }
diff --git a/src/third_party/freetype2/ChangeLog b/src/third_party/freetype2/ChangeLog index e4ea3c5..921d93e 100644 --- a/src/third_party/freetype2/ChangeLog +++ b/src/third_party/freetype2/ChangeLog
@@ -1,3 +1,11 @@ +2020-10-19 Werner Lemberg <wl@gnu.org> + + [sfnt] Fix heap buffer overflow (#59308). + + This is CVE-2020-15999. + + * src/sfnt/pngshim.c (Load_SBit_Png): Test bitmap size earlier. + 2020-05-09 Werner Lemberg <wl@gnu.org> * Version 2.10.2 released.
diff --git a/src/third_party/freetype2/src/sfnt/pngshim.c b/src/third_party/freetype2/src/sfnt/pngshim.c index 523b30a..5502108 100644 --- a/src/third_party/freetype2/src/sfnt/pngshim.c +++ b/src/third_party/freetype2/src/sfnt/pngshim.c
@@ -328,6 +328,13 @@ if ( populate_map_and_metrics ) { + /* reject too large bitmaps similarly to the rasterizer */ + if ( imgHeight > 0x7FFF || imgWidth > 0x7FFF ) + { + error = FT_THROW( Array_Too_Large ); + goto DestroyExit; + } + metrics->width = (FT_UShort)imgWidth; metrics->height = (FT_UShort)imgHeight; @@ -336,13 +343,6 @@ map->pixel_mode = FT_PIXEL_MODE_BGRA; map->pitch = (int)( map->width * 4 ); map->num_grays = 256; - - /* reject too large bitmaps similarly to the rasterizer */ - if ( map->rows > 0x7FFF || map->width > 0x7FFF ) - { - error = FT_THROW( Array_Too_Large ); - goto DestroyExit; - } } /* convert palette/gray image to rgb */
diff --git a/src/third_party/mini_chromium/base/base.gyp b/src/third_party/mini_chromium/base/base.gyp index 7d1dbee..71dd510 100644 --- a/src/third_party/mini_chromium/base/base.gyp +++ b/src/third_party/mini_chromium/base/base.gyp
@@ -43,6 +43,7 @@ 'atomicops_internals_atomicword_compat.h', 'atomicops_internals_portable.h', 'auto_reset.h', + 'base_wrapper.h', 'bit_cast.h', 'compiler_specific.h', 'debug/alias.cc',
diff --git a/src/third_party/mini_chromium/base/base_wrapper.h b/src/third_party/mini_chromium/base/base_wrapper.h new file mode 100644 index 0000000..ae57c30 --- /dev/null +++ b/src/third_party/mini_chromium/base/base_wrapper.h
@@ -0,0 +1,29 @@ +// Copyright 2020 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MINI_CHROMIUM_BASE_BASE_WRAPPER_H_ +#define MINI_CHROMIUM_BASE_BASE_WRAPPER_H_ + +// Change the symbol name to avoid collisions with //base +#define FilePath MFilePath +#define GetLogMessageHandler MGetLogMessageHandler +#define LogMessage MLogMessage +#define ReadUnicodeCharacter MReadUnicodeCharacter +#define SetLogMessageHandler MSetLogMessageHandler +#define UTF16ToUTF8 MUTF16ToUTF8 +#define UmaHistogramSparse MUmaHistogramSparse +#define WriteUnicodeCharacter MWriteUnicodeCharacter +#define c16len mc16len + +#endif // MINI_CHROMIUM_BASE_BASE_WRAPPER_H_
diff --git a/src/third_party/mini_chromium/base/files/file_path.h b/src/third_party/mini_chromium/base/files/file_path.h index 0ac91bf..ca20380 100644 --- a/src/third_party/mini_chromium/base/files/file_path.h +++ b/src/third_party/mini_chromium/base/files/file_path.h
@@ -107,6 +107,7 @@ #include <iosfwd> #include <string> +#include "base/base_wrapper.h" #include "base/compiler_specific.h" #include "build/build_config.h"
diff --git a/src/third_party/mini_chromium/base/logging.h b/src/third_party/mini_chromium/base/logging.h index 37bf23b..0270090 100644 --- a/src/third_party/mini_chromium/base/logging.h +++ b/src/third_party/mini_chromium/base/logging.h
@@ -12,6 +12,7 @@ #include <sstream> #include <string> +#include "base/base_wrapper.h" #include "base/macros.h" #include "build/build_config.h"
diff --git a/src/third_party/mini_chromium/base/metrics/histogram_functions.h b/src/third_party/mini_chromium/base/metrics/histogram_functions.h index a96cb9d..5f2af8f 100644 --- a/src/third_party/mini_chromium/base/metrics/histogram_functions.h +++ b/src/third_party/mini_chromium/base/metrics/histogram_functions.h
@@ -7,6 +7,8 @@ #include <string> +#include "base/base_wrapper.h" + // These are no-op stub versions of a subset of the functions from Chromium's // base/metrics/histogram_functions.h. This allows us to instrument the Crashpad // code as necessary, while not affecting out-of-Chromium builds.
diff --git a/src/third_party/mini_chromium/base/strings/string16.h b/src/third_party/mini_chromium/base/strings/string16.h index 04605e4..bb311c2 100644 --- a/src/third_party/mini_chromium/base/strings/string16.h +++ b/src/third_party/mini_chromium/base/strings/string16.h
@@ -10,6 +10,7 @@ #include <string> +#include "base/base_wrapper.h" #include "build/build_config.h" namespace base {
diff --git a/src/third_party/mini_chromium/base/strings/utf_string_conversion_utils.h b/src/third_party/mini_chromium/base/strings/utf_string_conversion_utils.h index 72f86ae..4d5353d 100644 --- a/src/third_party/mini_chromium/base/strings/utf_string_conversion_utils.h +++ b/src/third_party/mini_chromium/base/strings/utf_string_conversion_utils.h
@@ -5,6 +5,7 @@ #ifndef MINI_CHROMIUM_BASE_STRINGS_UTF_STRING_CONVERSION_UTILS_H_ #define MINI_CHROMIUM_BASE_STRINGS_UTF_STRING_CONVERSION_UTILS_H_ +#include "base/base_wrapper.h" #include "base/strings/string16.h" namespace base {
diff --git a/src/third_party/mini_chromium/base/strings/utf_string_conversions.h b/src/third_party/mini_chromium/base/strings/utf_string_conversions.h index c2ca674..8e97fcd 100644 --- a/src/third_party/mini_chromium/base/strings/utf_string_conversions.h +++ b/src/third_party/mini_chromium/base/strings/utf_string_conversions.h
@@ -7,6 +7,7 @@ #include <string> +#include "base/base_wrapper.h" #include "base/strings/string16.h" #include "base/strings/string_piece.h"
diff --git a/src/third_party/web_platform_tests/cobalt_special/resources/content_length.py b/src/third_party/web_platform_tests/cobalt_special/resources/content_length.py new file mode 100644 index 0000000..d4282ce --- /dev/null +++ b/src/third_party/web_platform_tests/cobalt_special/resources/content_length.py
@@ -0,0 +1,14 @@ +# Lint as: python3 +""" +Helper script to send XHR response with specified content length. +""" + +def main(request, response): + headers = [] + + content_length = request.GET.first("content_length") + headers.append(("Content-Length", content_length)); + + body = "this is body" + + return headers, body
diff --git a/src/third_party/web_platform_tests/cobalt_special/xhr_content_length.htm b/src/third_party/web_platform_tests/cobalt_special/xhr_content_length.htm new file mode 100644 index 0000000..fc07412 --- /dev/null +++ b/src/third_party/web_platform_tests/cobalt_special/xhr_content_length.htm
@@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>cobalt-special - xhr content length</title> + +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/common/utils.js></script> +<script src=resources/support.js?pipe=sub></script> + +<h1></h1> + +<div id=log></div> +<script> + var test = async_test(); + test.step(function() { + var client = new XMLHttpRequest(); + client.onloadstart = test.step_func(function() { + test.done(); + }); + // The purpose of this test is to verify that Cobalt does not crash when an + // erroneously long content length is specified in xhr response. + const too_large_buffer_size = "4987398465139"; + client.open("GET", "resources/content_length.py?content_length=" + too_large_buffer_size); + client.send(null); + }); +</script> +
diff --git a/src/third_party/web_platform_tests/intersection-observer/multiple-targets.html b/src/third_party/web_platform_tests/intersection-observer/multiple-targets.html index 22353e3..51ddeff 100644 --- a/src/third_party/web_platform_tests/intersection-observer/multiple-targets.html +++ b/src/third_party/web_platform_tests/intersection-observer/multiple-targets.html
@@ -5,13 +5,18 @@ <script src="./resources/intersection-observer-test-utils.js"></script> <style> +/* Cobalt does not implement HTML5 spec for body margin. */ +body { + margin: 8px; +} pre, #log { position: absolute; top: 0; left: 200px; } .spacer { - height: calc(100vh + 100px); + /* Cobalt does not support calc */ + height: 100vh; } .target { width: 100px; @@ -22,7 +27,7 @@ </style> <div class="spacer"></div> -<div id="target1" class="target"></div> +<div id="target1" class="target" style="margin-top:100px;"></div> <div id="target2" class="target"></div> <div id="target3" class="target"></div> @@ -38,7 +43,8 @@ target3 = document.getElementById("target3"); assert_true(!!target3, "target3 exists."); var observer = new IntersectionObserver(function(changes) { - entries = entries.concat(changes) + entries = entries.concat(changes); + window.testRunner.DoNonMeasuredLayout(); }); observer.observe(target1); observer.observe(target2); @@ -47,9 +53,12 @@ assert_equals(entries.length, 0, "No initial notifications."); runTestCycle(step0, "First rAF."); }, "One observer with multiple targets."); +window.testRunner.DoNonMeasuredLayout(); +window.testRunner.DoNonMeasuredLayout(); function step0() { - document.scrollingElement.scrollTop = 150; + target1.style.marginTop = "-50px"; + //document.scrollingElement.scrollTop = 150; runTestCycle(step1, "document.scrollingElement.scrollTop = 150"); assert_equals(entries.length, 3, "Three initial notifications."); assert_equals(entries[0].target, target1, "entries[0].target === target1"); @@ -58,14 +67,19 @@ } function step1() { - document.scrollingElement.scrollTop = 10000; + // document.scrollingElement.scrollTop = 10000 indicates that the page should + // scroll just far down enough to include all the elements in the DOM tree. + // Since we are using marginTop as a workaround, we need to adjust the value. + target1.style.marginTop = "-330px"; + //document.scrollingElement.scrollTop = 10000; runTestCycle(step2, "document.scrollingElement.scrollTop = 10000"); assert_equals(entries.length, 4, "Four notifications."); assert_equals(entries[3].target, target1, "entries[3].target === target1"); } function step2() { - document.scrollingElement.scrollTop = 0; + target1.style.marginTop = "100px"; + //document.scrollingElement.scrollTop = 0; runTestCycle(step3, "document.scrollingElement.scrollTop = 0"); assert_equals(entries.length, 6, "Six notifications."); assert_equals(entries[4].target, target2, "entries[4].target === target2");
diff --git a/src/third_party/web_platform_tests/intersection-observer/multiple-thresholds.html b/src/third_party/web_platform_tests/intersection-observer/multiple-thresholds.html index 3599e1f..3c92ea5 100644 --- a/src/third_party/web_platform_tests/intersection-observer/multiple-thresholds.html +++ b/src/third_party/web_platform_tests/intersection-observer/multiple-thresholds.html
@@ -5,18 +5,26 @@ <script src="./resources/intersection-observer-test-utils.js"></script> <style> +/* Cobalt does not implement HTML5 spec for body margin. */ +body { + margin: 8px; +} pre, #log { position: absolute; top: 0; left: 200px; } .spacer { - height: calc(100vh + 100px); + /* Cobalt does not support calc */ + height: 100vh; + } #target { width: 100px; height: 100px; background-color: green; + /* Adjust for subtracted 100px in class=spacer */ + margin-top: 100px; } </style> @@ -25,8 +33,8 @@ <div class="spacer"></div> <script> -var vw = document.documentElement.clientWidth; -var vh = document.documentElement.clientHeight; +var vw = window.innerWidth; +var vh = window.innerHeight; var entries = []; var target; @@ -34,64 +42,80 @@ runTestCycle(function() { target = document.getElementById("target"); var observer = new IntersectionObserver(function(changes) { - entries = entries.concat(changes) + entries = entries.concat(changes); + window.testRunner.DoNonMeasuredLayout(); }, { threshold: [0, 0.25, 0.5, 0.75, 1] }); observer.observe(target); entries = entries.concat(observer.takeRecords()); assert_equals(entries.length, 0, "No initial notifications."); runTestCycle(step0, "First rAF."); }, "Observer with multiple thresholds."); +window.testRunner.DoNonMeasuredLayout(); +window.testRunner.DoNonMeasuredLayout(); function step0() { - document.scrollingElement.scrollTop = 120; + target.style.marginTop = "-20px"; + //document.scrollingElement.scrollTop = 120; runTestCycle(step1, "document.scrollingElement.scrollTop = 120"); checkLastEntry(entries, 0, [8, 108, vh + 108, vh + 208, 0, 0, 0, 0, 0, vw, 0, vh, false]); } function step1() { - document.scrollingElement.scrollTop = 160; + target.style.marginTop = "-60px"; + //document.scrollingElement.scrollTop = 160; runTestCycle(step2, "document.scrollingElement.scrollTop = 160"); checkLastEntry(entries, 1, [8, 108, vh - 12, vh + 88, 8, 108, vh - 12, vh, 0, vw, 0, vh, true]); } function step2() { - document.scrollingElement.scrollTop = 200; + target.style.marginTop = "-100px"; + //document.scrollingElement.scrollTop = 200; runTestCycle(step3, "document.scrollingElement.scrollTop = 200"); checkLastEntry(entries, 2, [8, 108, vh - 52, vh + 48, 8, 108, vh - 52, vh, 0, vw, 0, vh, true]); } function step3() { - document.scrollingElement.scrollTop = 240; + target.style.marginTop = "-140px"; + //document.scrollingElement.scrollTop = 240; runTestCycle(step4, "document.scrollingElement.scrollTop = 240"); checkLastEntry(entries, 3, [8, 108, vh - 92, vh + 8, 8, 108, vh - 92, vh, 0, vw, 0, vh, true]); } function step4() { - document.scrollingElement.scrollTop = vh + 140; + var marginTop = (vh + 140) * -1 + 100 + "px"; + target.style.marginTop = marginTop; + //document.scrollingElement.scrollTop = vh + 140; runTestCycle(step5, "document.scrollingElement.scrollTop = window.innerHeight + 140"); checkLastEntry(entries, 4, [8, 108, vh - 132, vh - 32, 8, 108, vh - 132, vh - 32, 0, vw, 0, vh, true]); } function step5() { - document.scrollingElement.scrollTop = vh + 160; + var marginTop = (vh + 160) * -1 + 100 + "px"; + target.style.marginTop = marginTop; + //document.scrollingElement.scrollTop = vh + 160; runTestCycle(step6, "document.scrollingElement.scrollTop = window.innerHeight + 160"); checkLastEntry(entries, 5, [8, 108, -32, 68, 8, 108, 0, 68, 0, vw, 0, vh, true]); } function step6() { - document.scrollingElement.scrollTop = vh + 200; + var marginTop = (vh + 200) * -1 + 100 + "px"; + target.style.marginTop = marginTop; + //document.scrollingElement.scrollTop = vh + 200; runTestCycle(step7, "document.scrollingElement.scrollTop = window.innerHeight + 200"); checkLastEntry(entries, 6, [8, 108, -52, 48, 8, 108, 0, 48, 0, vw, 0, vh, true]); } function step7() { checkLastEntry(entries, 7, [8, 108, -92, 8, 8, 108, 0, 8, 0, vw, 0, vh, true]); - document.scrollingElement.scrollTop = vh + 220; + var marginTop = (vh + 220) * -1 + 100 + "px"; + target.style.marginTop = marginTop; + //document.scrollingElement.scrollTop = vh + 220; runTestCycle(step8, "document.scrollingElement.scrollTop = window.innerHeight + 220"); } function step8() { checkLastEntry(entries, 8, [8, 108, -112, -12, 0, 0, 0, 0, 0, vw, 0, vh, false]); - document.scrollingElement.scrollTop = 0; + target.style.marginTop = "100px"; + //document.scrollingElement.scrollTop = 0; } </script>
diff --git a/src/third_party/web_platform_tests/intersection-observer/observer-without-js-reference.html b/src/third_party/web_platform_tests/intersection-observer/observer-without-js-reference.html index 53100c50..afb28c6 100644 --- a/src/third_party/web_platform_tests/intersection-observer/observer-without-js-reference.html +++ b/src/third_party/web_platform_tests/intersection-observer/observer-without-js-reference.html
@@ -5,18 +5,25 @@ <script src="./resources/intersection-observer-test-utils.js"></script> <style> +/* Cobalt does not implement HTML5 spec for body margin. */ +body { + margin: 8px; +} pre, #log { position: absolute; top: 0; left: 200px; } .spacer { - height: calc(100vh + 100px); + /* Cobalt does not support calc */ + height: 100vh; } #target { width: 100px; height: 100px; background-color: green; + /* Adjust for subtracted 100px in class=spacer */ + margin-top: 100px; } </style> <div class="spacer"></div> @@ -31,21 +38,26 @@ assert_true(!!target, "Target exists"); function createObserver() { new IntersectionObserver(function(changes) { - entries = entries.concat(changes) + entries = entries.concat(changes); + window.testRunner.DoNonMeasuredLayout(); }).observe(target); } createObserver(); runTestCycle(step0, "First rAF"); }, "IntersectionObserver that is unreachable in js should still generate notifications."); +window.testRunner.DoNonMeasuredLayout(); +window.testRunner.DoNonMeasuredLayout(); function step0() { - document.scrollingElement.scrollTop = 300; + document.getElementById("target").style.marginTop = "-200px"; + //document.scrollingElement.scrollTop = 300; runTestCycle(step1, "document.scrollingElement.scrollTop = 300"); assert_equals(entries.length, 1, "One notification."); } function step1() { - document.scrollingElement.scrollTop = 0; + document.getElementById("target").style.marginTop = "100px"; + //document.scrollingElement.scrollTop = 0; assert_equals(entries.length, 2, "Two notifications."); } </script>
diff --git a/src/third_party/web_platform_tests/intersection-observer/same-document-no-root.html b/src/third_party/web_platform_tests/intersection-observer/same-document-no-root.html index 63e9f86..957ad77 100644 --- a/src/third_party/web_platform_tests/intersection-observer/same-document-no-root.html +++ b/src/third_party/web_platform_tests/intersection-observer/same-document-no-root.html
@@ -5,18 +5,25 @@ <script src="./resources/intersection-observer-test-utils.js"></script> <style> +/* Cobalt does not implement HTML5 spec for body margin. */ +body { + margin: 8px; +} pre, #log { position: absolute; top: 0; left: 200px; } .spacer { - height: calc(100vh + 100px); + /* Cobalt does not support calc */ + height: 100vh; } #target { width: 100px; height: 100px; background-color: green; + /* Adjust for subtracted 100px in class=spacer */ + margin-top: 100px; } </style> @@ -25,8 +32,8 @@ <div class="spacer"></div> <script> -var vw = document.documentElement.clientWidth; -var vh = document.documentElement.clientHeight; +var vw = window.innerWidth; +var vh = window.innerHeight; var entries = []; var target; @@ -35,28 +42,34 @@ target = document.getElementById("target"); assert_true(!!target, "target exists"); var observer = new IntersectionObserver(function(changes) { - entries = entries.concat(changes) + entries = entries.concat(changes); + window.testRunner.DoNonMeasuredLayout(); }); observer.observe(target); entries = entries.concat(observer.takeRecords()); assert_equals(entries.length, 0, "No initial notifications."); runTestCycle(step0, "First rAF."); }, "IntersectionObserver in a single document using the implicit root."); +window.testRunner.DoNonMeasuredLayout(); +window.testRunner.DoNonMeasuredLayout(); function step0() { - document.scrollingElement.scrollTop = 300; + target.style.marginTop = "-200px"; + //document.scrollingElement.scrollTop = 300; runTestCycle(step1, "document.scrollingElement.scrollTop = 300"); checkLastEntry(entries, 0, [8, 108, vh + 108, vh + 208, 0, 0, 0, 0, 0, vw, 0, vh, false]); } function step1() { - document.scrollingElement.scrollTop = 100; + target.style.marginTop = "0px"; + //document.scrollingElement.scrollTop = 100; runTestCycle(step2, "document.scrollingElement.scrollTop = 100"); checkLastEntry(entries, 1, [8, 108, vh - 192, vh - 92, 8, 108, vh - 192, vh - 92, 0, vw, 0, vh, true]); } function step2() { - document.scrollingElement.scrollTop = 0; + target.style.marginTop = "100px"; + //document.scrollingElement.scrollTop = 0; checkLastEntry(entries, 2, [8, 108, vh + 8, vh + 108, 0, 0, 0, 0, 0, vw, 0, vh, false]); } </script>
diff --git a/src/third_party/web_platform_tests/intersection-observer/same-document-zero-size-target.html b/src/third_party/web_platform_tests/intersection-observer/same-document-zero-size-target.html index 20bd11d..ddf6fec 100644 --- a/src/third_party/web_platform_tests/intersection-observer/same-document-zero-size-target.html +++ b/src/third_party/web_platform_tests/intersection-observer/same-document-zero-size-target.html
@@ -5,18 +5,25 @@ <script src="./resources/intersection-observer-test-utils.js"></script> <style> +/* Cobalt does not implement HTML5 spec for body margin. */ +body { + margin: 8px; +} pre, #log { position: absolute; top: 0; left: 200px; } .spacer { - height: calc(100vh + 100px); + /* Cobalt does not support calc */ + height: 100vh; } #target { width: 0px; height: 0px; background-color: green; + /* Adjust for subtracted 100px in class=spacer */ + margin-top: 100px; } </style> @@ -25,8 +32,8 @@ <div class="spacer"></div> <script> -var vw = document.documentElement.clientWidth; -var vh = document.documentElement.clientHeight; +var vw = window.innerWidth; +var vh = window.innerHeight; var entries = []; var target; @@ -35,28 +42,34 @@ target = document.getElementById("target"); assert_true(!!target, "Target exists"); var observer = new IntersectionObserver(function(changes) { - entries = entries.concat(changes) + entries = entries.concat(changes); + window.testRunner.DoNonMeasuredLayout(); }); observer.observe(target); entries = entries.concat(observer.takeRecords()); assert_equals(entries.length, 0, "No initial notifications."); runTestCycle(step0, "First rAF"); }, "Observing a zero-area target."); +window.testRunner.DoNonMeasuredLayout(); +window.testRunner.DoNonMeasuredLayout(); function step0() { - document.scrollingElement.scrollTop = 300; + target.style.marginTop = "-200px"; + //document.scrollingElement.scrollTop = 300; runTestCycle(step1, "document.scrollingElement.scrollTop = 300"); checkLastEntry(entries, 0, [8, 8, vh + 108, vh + 108, 0, 0, 0, 0, 0, vw, 0, vh, false]); } function step1() { - document.scrollingElement.scrollTop = 100; + target.style.marginTop = "0px"; + //document.scrollingElement.scrollTop = 100; runTestCycle(step2, "document.scrollingElement.scrollTop = 100"); checkLastEntry(entries, 1, [8, 8, vh - 192, vh - 192, 8, 8, vh - 192, vh - 192, 0, vw, 0, vh, true]); } function step2() { - document.scrollingElement.scrollTop = 0; + target.style.marginTop = "100px"; + //document.scrollingElement.scrollTop = 0; checkLastEntry(entries, 2, [8, 8, vh + 8, vh + 8, 0, 0, 0, 0, 0, vw, 0, vh, false]); } </script>
diff --git a/src/third_party/web_platform_tests/intersection-observer/text-target.html b/src/third_party/web_platform_tests/intersection-observer/text-target.html index 1abe535..2317a66 100644 --- a/src/third_party/web_platform_tests/intersection-observer/text-target.html +++ b/src/third_party/web_platform_tests/intersection-observer/text-target.html
@@ -5,23 +5,33 @@ <script src="./resources/intersection-observer-test-utils.js"></script> <style> +/* Cobalt does not implement HTML5 spec for body margin. */ +body { + margin: 8px; +} pre, #log { position: absolute; top: 0; left: 200px; } .spacer { - height: calc(100vh + 100px); + /* Cobalt does not support calc */ + height: 100vh; +} +#target { + /* Adjust for subtracted 100px in class=spacer */ + margin-top: 100px; } </style> <div class="spacer"></div> -<br id="target"> +<!--br gives incorrect bounding rect in Cobalt so we use p instead--> +<p id="target"> <div class="spacer"></div> <script> -var vw = document.documentElement.clientWidth; -var vh = document.documentElement.clientHeight; +var vw = window.innerWidth; +var vh = window.innerHeight; var entries = []; var target; @@ -34,16 +44,20 @@ th = target_rect.height; assert_true(!!target, "target exists"); var observer = new IntersectionObserver(function(changes) { - entries = entries.concat(changes) + entries = entries.concat(changes); + window.testRunner.DoNonMeasuredLayout(); }); observer.observe(target); entries = entries.concat(observer.takeRecords()); assert_equals(entries.length, 0, "No initial notifications."); runTestCycle(step0, "First rAF."); -}, "IntersectionObserver observing a br element."); +}, "IntersectionObserver observing a p element."); +window.testRunner.DoNonMeasuredLayout(); +window.testRunner.DoNonMeasuredLayout(); function step0() { - document.scrollingElement.scrollTop = 300; + target.style.marginTop = "-200px"; + //document.scrollingElement.scrollTop = 300; runTestCycle(step1, "document.scrollingElement.scrollTop = 300"); // The numbers in brackets are target client rect; intersection rect; // and root bounds. @@ -51,13 +65,15 @@ } function step1() { - document.scrollingElement.scrollTop = 100; + target.style.marginTop = "0px"; + //document.scrollingElement.scrollTop = 100; runTestCycle(step2, "document.scrollingElement.scrollTop = 100"); checkLastEntry(entries, 1, [8, 8 + tw, vh - 192, vh - 192 + th, 8, 8 + tw, vh - 192, vh - 192 + th, 0, vw, 0, vh, true]); } function step2() { - document.scrollingElement.scrollTop = 0; + target.style.marginTop = "100px"; + //document.scrollingElement.scrollTop = 0; checkLastEntry(entries, 2, [8, 8 + tw, vh + 8, vh + 8 + th, 0, 0, 0, 0, 0, vw, 0, vh, false]); } </script>
diff --git a/src/third_party/web_platform_tests/intersection-observer/zero-area-element-visible.html b/src/third_party/web_platform_tests/intersection-observer/zero-area-element-visible.html index 5431750..be8aa0a 100644 --- a/src/third_party/web_platform_tests/intersection-observer/zero-area-element-visible.html +++ b/src/third_party/web_platform_tests/intersection-observer/zero-area-element-visible.html
@@ -5,6 +5,10 @@ <script src="./resources/intersection-observer-test-utils.js"></script> <style> +/* Cobalt does not implement HTML5 spec for body margin. */ +body { + margin: 8px; +} pre, #log { position: absolute; top: 0; @@ -25,13 +29,16 @@ var target = document.getElementById('target'); assert_true(!!target, "target exists"); var observer = new IntersectionObserver(function(changes) { - entries = entries.concat(changes) + entries = entries.concat(changes); + window.testRunner.DoNonMeasuredLayout(); }); observer.observe(target); entries = entries.concat(observer.takeRecords()); assert_equals(entries.length, 0, "No initial notifications."); runTestCycle(step0, "First rAF should generate a notification."); }, "Ensure that a zero-area target intersecting root generates a notification with intersectionRatio == 1"); +window.testRunner.DoNonMeasuredLayout(); +window.testRunner.DoNonMeasuredLayout(); function step0() { assert_equals(entries.length, 1, "One notification.");
diff --git a/src/third_party/zlib/zlib.gyp b/src/third_party/zlib/zlib.gyp index aee6d67..1156168 100644 --- a/src/third_party/zlib/zlib.gyp +++ b/src/third_party/zlib/zlib.gyp
@@ -520,7 +520,6 @@ 'minizip', 'zip', 'zlib', - '<(DEPTH)/base/base.gyp:base', '<(DEPTH)/base/base.gyp:test_support_base', '<(DEPTH)/starboard/common/common.gyp:common', '<(DEPTH)/testing/gmock.gyp:gmock',
diff --git a/src/v8/src/base/platform/platform-starboard.cc b/src/v8/src/base/platform/platform-starboard.cc index 3cd69ec..27bfd38 100644 --- a/src/v8/src/base/platform/platform-starboard.cc +++ b/src/v8/src/base/platform/platform-starboard.cc
@@ -17,9 +17,8 @@ #include "src/base/platform/platform.h" #include "src/base/platform/time.h" #include "src/base/utils/random-number-generator.h" - #include "src/base/timezone-cache.h" - +#include "starboard/client_porting/eztime/eztime.h" #include "starboard/common/condition_variable.h" #include "starboard/common/log.h" #include "starboard/common/string.h" @@ -468,7 +467,6 @@ class StarboardTimezoneCache : public TimezoneCache { public: - double DaylightSavingsOffset(double time_ms) override { return 0.0; } void Clear(TimeZoneDetection time_zone_detection) override {} ~StarboardTimezoneCache() override {} @@ -482,7 +480,18 @@ return SbTimeZoneGetName(); } double LocalTimeOffset(double time_ms, bool is_utc) override { - return SbTimeZoneGetCurrent() * 60000.0; + // SbTimeZOneGetCurrent returns an offset west of Greenwich, which has the + // opposite sign V8 expects. + // The starboard function returns offset in minutes. We convert to return + // value in milliseconds. + return SbTimeZoneGetCurrent() * 60.0 * msPerSecond * (-1); + } + double DaylightSavingsOffset(double time_ms) override { + EzTimeValue value = EzTimeValueFromSbTime(SbTimeGetNow()); + EzTimeExploded ez_exploded; + bool result = EzTimeValueExplode(&value, kEzTimeZoneLocal, &ez_exploded, + NULL); + return ez_exploded.tm_isdst > 0 ? 3600 * msPerSecond : 0; } ~StarboardDefaultTimezoneCache() override {}