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 {}