Import Cobalt 22.lts.1.304707
diff --git a/src/.pre-commit-config.yaml b/src/.pre-commit-config.yaml index 6240f63..7bc0f93 100644 --- a/src/.pre-commit-config.yaml +++ b/src/.pre-commit-config.yaml
@@ -117,6 +117,7 @@ language: python stages: [push] always_run: true + pass_filenames: false - id: run-py2-tests name: Run Python 2 Tests description: Run Python 2 unittests
diff --git a/src/CONTRIBUTING.md b/src/CONTRIBUTING.md index 71f6b83..332d6ab 100644 --- a/src/CONTRIBUTING.md +++ b/src/CONTRIBUTING.md
@@ -53,7 +53,7 @@ 1. Run `git clang-format HEAD~` to apply default C++ formatting rules, followed by `git commit -a --amend` to squash any formatting changes into your commit. - 1. Run `git cl upload` to upload the review to + 1. Run `git push origin HEAD:refs/for/master` to upload the review to [Cobalt's Gerrit instance](https://cobalt-review.googlesource.com/). 1. Someone from the maintainers team will review the code, putting up comments on any things that need to change for submission. @@ -62,4 +62,3 @@ 1. If you do not need to make any more changes, a maintainer will integrate the change into our private repository, and it will get pushed out to the public repository after some time. -
diff --git a/src/cobalt/black_box_tests/testdata/freeze_timers.html b/src/cobalt/black_box_tests/testdata/freeze_timers.html new file mode 100644 index 0000000..0ad6f4c --- /dev/null +++ b/src/cobalt/black_box_tests/testdata/freeze_timers.html
@@ -0,0 +1,130 @@ +<!DOCTYPE html> + +<head> + <title>Cobalt WindowTimers test with freeze state</title> + <script src='black_box_js_test_utils.js'></script> +</head> + +<body> + <h1> + <span>ID element</span> + </h1> + <script> + + // Delay for setInterval (ms) + var kSetIntervalDelay = 600; + // Delay for setTimeout (ms) + var kSetTimeoutDelay = 800; + // Delay for the long setTimeout (ms) + var kLongSetTimeoutDelay = 13000; + + // Allowed time difference for callback expected execution time (ms) + // This value is quite high because in devel builds on slower devices there + // may occesionally be larger delays. + var kAllowedTimeDifference = 100; + + // Last time when the app was resumed from frozen state. + var resumeTime = 0; + // Expected execution time for interval callback + var expectedIntervalTime = 0; + // Expected execution time for timeout callback + var expectedTimeoutTime = 0; + // Expected execution time for long timeout callback + var expectedLongTimeoutTime = 0; + + let wasEverFrozen = false; + let frozen = false; + let eventWhileFrozen = false; + + // Callback for registering entering freeze state. + function freezeCallback() { + console.log("document.onfreeze") + wasEverFrozen = true; + frozen = true; + } + + // Callback for registering leaving freeze state. + function resumeCallback() { + resumeTime = performance.now(); + console.log("document.onresume") + if (!frozen) { + console.log('ERROR: Resuming while not frozen'); + notReached(); + } + if (eventWhileFrozen) { + console.log('ERROR: Timer callback received while frozen'); + notReached(); + } + frozen = false; + } + + function logState(prefix) { + console.log(prefix + ": State:" + + " document.visibilityState == " + document.visibilityState + + " document.hidden == " + document.hidden + + " document.hasFocus() == " + document.hasFocus()); + } + + function verifyNotFrozen() { + eventWhileFrozen |= frozen; + } + + function CheckTiming(expectedTime) { + if (expectedTime != 0) { + // Callbacks are allowed to execute no more than kAllowedTimeDifference + // before or after the expected time, except when the app was frozen, + // in which case it should be executed immediately when resuming, while + // the document is still hidden. + if (wasEverFrozen && resumeTime > expectedTime && document.hidden) { + // The callback is too late, but that is because the document was + // frozen. + return; + } + + if (Math.abs(performance.now() - expectedTime) > kAllowedTimeDifference) { + console.log('ERROR: Timer callback timing out of allowed range (' + + Math.round(Math.abs(performance.now() - expectedTime)) + + 'ms difference)'); + notReached(); + } + } + } + + function DoTimeout() { + verifyNotFrozen(); + CheckTiming(expectedTimeoutTime); + logState('Timeout'); + expectedTimeoutTime = performance.now() + kSetTimeoutDelay; + setTimeout(DoTimeout, kSetTimeoutDelay); + } + + function DoLongTimeout() { + verifyNotFrozen(); + CheckTiming(expectedLongTimeoutTime); + logState('Long Timeout'); + if (wasEverFrozen && !eventWhileFrozen) { + onEndTest(); + } + + expectedLongTimeoutTime = performance.now() + kLongSetTimeoutDelay; + setTimeout(DoLongTimeout, kLongSetTimeoutDelay); + } + + function DoInterval() {performance.now() + var now = performance.now(); + verifyNotFrozen(); + CheckTiming(expectedIntervalTime); + logState('Interval'); + expectedIntervalTime = now + kSetIntervalDelay; + } + + document.addEventListener("freeze", freezeCallback); + document.addEventListener("resume", resumeCallback); + + DoLongTimeout(); + DoTimeout(); + setInterval(DoInterval, kSetIntervalDelay); + + setupFinished(); + </script> +</body>
diff --git a/src/cobalt/black_box_tests/testdata/override_ua_parameters.html b/src/cobalt/black_box_tests/testdata/override_ua_parameters.html new file mode 100644 index 0000000..d005b11 --- /dev/null +++ b/src/cobalt/black_box_tests/testdata/override_ua_parameters.html
@@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Cobalt override ua parameters test</title> + <script src='black_box_js_test_utils.js'></script> +</head> + +<body> + <div>Full User-Agent Data</div> + + <script> + // Enable User-Agent Client Hints API + h5vcc.settings.set("NavigatorUAData", 1); + + navigator.userAgentData.getHighEntropyValues( + ["model", "uaFullVersion", "cobaltBuildNumber", "cobaltBuildConfiguration", + "jsEngineVersion", "rasterizer", "evergreenVersion", + "starboardVersion", "originalDesignManufacturer", + "deviceType", "chipset", "modelYear", "deviceBrand", + "connectionType", "aux"]) + .then( + ua => { + assertEqual(ua.aux, "foo.bar.baz.qux/21.2.1.41.0"); + assertEqual(ua.deviceBrand, "Cobalt"); + assertEqual(ua.cobaltBuildConfiguration, "debug"); + assertEqual(ua.chipset, "foobar0000"); + assertEqual(ua.cobaltBuildNumber, "289852"); + assertEqual(ua.connectionType, "Wireless"); + assertEqual(ua.deviceType, "ATV"); + assertEqual(ua.evergreenVersion, ""); + assertEqual(ua.jsEngineVersion, "v8/7.7.299.8-jit"); + assertEqual(ua.model, "QUUX"); + assertEqual(ua.modelYear, "2018"); + assertEqual(ua.originalDesignManufacturer, "Quuz"); + assertEqual(ua.starboardVersion, "Starboard/12"); + assertEqual(ua.rasterizer, "gles"); + assertEqual(ua.uaFullVersion, "21.lts.2.289852-debug"); + + onEndTest(); + setupFinished(); + }); + </script> +</body> + +</html>
diff --git a/src/cobalt/black_box_tests/tests/freeze_timers.py b/src/cobalt/black_box_tests/tests/freeze_timers.py new file mode 100644 index 0000000..538743c --- /dev/null +++ b/src/cobalt/black_box_tests/tests/freeze_timers.py
@@ -0,0 +1,44 @@ +# Copyright 2021 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. +"""Tests WindowTimers and application freezing and resuming.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import _env # pylint: disable=unused-import + +import time + +from cobalt.black_box_tests import black_box_tests +from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer + + +class FreezeVisibilityTest(black_box_tests.BlackBoxTestCase): + """Verify correct timer suspend and resume during and after freeze event.""" + + def test_simple(self): + + with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server: + url = server.GetURL(file_name='testdata/freeze_timers.html') + + with self.CreateCobaltRunner(url=url) as runner: + runner.WaitForJSTestsSetup() + time.sleep(2.5) + runner.SendConceal() + time.sleep(2.5) + runner.SendFreeze() + time.sleep(3.333) + runner.SendFocus() + self.assertTrue(runner.JSTestsSucceeded())
diff --git a/src/cobalt/black_box_tests/tests/override_ua_parameters.py b/src/cobalt/black_box_tests/tests/override_ua_parameters.py new file mode 100644 index 0000000..4f5fd45 --- /dev/null +++ b/src/cobalt/black_box_tests/tests/override_ua_parameters.py
@@ -0,0 +1,87 @@ +# Copyright 2021 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. +"""Test overriding UA parameters when launching Cobalt.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import SimpleHTTPServer + +from cobalt.black_box_tests import black_box_tests +from cobalt.black_box_tests.threaded_web_server import MakeRequestHandlerClass +from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer + +# The base path of the requested assets is the parent directory. +_SERVER_ROOT_PATH = os.path.join(os.path.dirname(__file__), os.pardir) + + +class JavascriptRequestDetector(MakeRequestHandlerClass(_SERVER_ROOT_PATH)): + """Proxies everything to SimpleHTTPRequestHandler, except some paths.""" + + def do_GET(self): # pylint: disable=invalid-name + """Handles HTTP GET requests for resources.""" + # Check if UA string in the request header reflects correct UA params + # overrides + ua_request_header = self.headers.get('user-agent', '') + expected_ua_request_header = 'Mozilla/5.0 (Corge grault-v7a; '\ + 'Garply 7.1.2; Waldo OS 6.0) Cobalt/21.lts.2.289852-debug '\ + '(unlike Gecko) v8/7.7.299.8-jit gles Starboard/12, '\ + 'Quuz_ATV_foobar0000_2018/Unknown (Cobalt, QUUX, Wireless) '\ + 'foo.bar.baz.qux/21.2.1.41.0' + + if not ua_request_header == expected_ua_request_header: + raise ValueError('UA string in HTTP request header does not match with '\ + 'UA params overrides specified in command line\n'\ + 'UA string in HTTP request header:%s' % (ua_request_header)) + + return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + + +class OverrideUAParametersTest(black_box_tests.BlackBoxTestCase): + """Test overriding UA parameters.""" + + def test_simple(self): + """Set UA parameters when launching Cobalt.""" + + with ThreadedWebServer( + JavascriptRequestDetector, + binding_address=self.GetBindingAddress()) as server: + with self.CreateCobaltRunner( + url=server.GetURL(file_name='testdata/override_ua_parameters.html'), + target_params=[ + '--user_agent_client_hints='\ + 'aux_field=foo.bar.baz.qux/21.2.1.41.0;'\ + 'brand=Cobalt;'\ + 'build_configuration=debug;'\ + 'chipset_model_number=foobar0000;'\ + 'cobalt_build_version_number=289852;'\ + 'cobalt_version=21.lts.2;'\ + 'connection_type=Wireless;'\ + 'device_type=ATV;'\ + 'evergreen_type=;'\ + 'evergreen_version=;'\ + 'javascript_engine_version=v8/7.7.299.8-jit;'\ + 'firmware_version=;'\ + 'model=QUUX;'\ + 'model_year=2018;'\ + 'original_design_manufacturer=Quuz;'\ + 'os_name_and_version=Corge grault-v7a\\; Garply 7.1.2\\; '\ + 'Waldo OS 6.0;'\ + 'starboard_version=Starboard/12;'\ + 'rasterizer_type=gles' + ]) as runner: + runner.WaitForJSTestsSetup() + self.assertTrue(runner.JSTestsSucceeded())
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc index 984540e..05e4364 100644 --- a/src/cobalt/browser/application.cc +++ b/src/cobalt/browser/application.cc
@@ -591,9 +591,6 @@ ssize_t Application::available_memory_ = 0; int64 Application::lifetime_in_ms_ = 0; -Application::AppStatus Application::app_status_ = - Application::kUninitializedAppStatus; - Application::Application(const base::Closure& quit_closure, bool should_preload, SbTimeMonotonic timestamp) : message_loop_(base::MessageLoop::current()), @@ -860,8 +857,6 @@ timestamp); UpdateUserAgent(); - app_status_ = (should_preload ? kConcealedAppStatus : kRunningAppStatus); - // Register event callbacks. window_size_change_event_callback_ = base::Bind( &Application::OnWindowSizeChangedEvent, base::Unretained(this)); @@ -1009,21 +1004,19 @@ base::DateTimeConfigurationChangedEvent::TypeId(), on_date_time_configuration_changed_event_callback_); #endif - - app_status_ = kShutDownAppStatus; } void Application::Start(SbTimeMonotonic timestamp) { if (base::MessageLoop::current() != message_loop_) { message_loop_->task_runner()->PostTask( - FROM_HERE, base::Bind(&Application::Start, base::Unretained(this), - timestamp)); + FROM_HERE, + base::Bind(&Application::Start, base::Unretained(this), timestamp)); return; } OnApplicationEvent(kSbEventTypeStart, timestamp); - browser_module_->SetApplicationStartOrPreloadTimestamp( - false /*is_preload*/, timestamp); + browser_module_->SetApplicationStartOrPreloadTimestamp(false /*is_preload*/, + timestamp); } void Application::Quit() { @@ -1034,7 +1027,6 @@ } quit_closure_.Run(); - app_status_ = kQuitAppStatus; } void Application::HandleStarboardEvent(const SbEvent* starboard_event) { @@ -1103,10 +1095,10 @@ case kSbEventTypeLink: { #if SB_API_VERSION >= 13 DispatchDeepLink(static_cast<const char*>(starboard_event->data), - starboard_event->timestamp); -#else // SB_API_VERSION >= 13 + starboard_event->timestamp); +#else // SB_API_VERSION >= 13 DispatchDeepLink(static_cast<const char*>(starboard_event->data), - SbTimeGetMonotonicNow()); + SbTimeGetMonotonicNow()); #endif // SB_API_VERSION >= 13 break; }
diff --git a/src/cobalt/browser/application.h b/src/cobalt/browser/application.h index d902680..c08330c 100644 --- a/src/cobalt/browser/application.h +++ b/src/cobalt/browser/application.h
@@ -203,8 +203,6 @@ static ssize_t available_memory_; static int64 lifetime_in_ms_; - static AppStatus app_status_; - CValStats c_val_stats_; base::RepeatingTimer stats_update_timer_;
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc index bb922b0..1f6e058 100644 --- a/src/cobalt/browser/browser_module.cc +++ b/src/cobalt/browser/browser_module.cc
@@ -1471,7 +1471,6 @@ DCHECK(application_state_ == base::kApplicationStateBlurred); application_state_ = base::kApplicationStateConcealed; ConcealInternal(timestamp); - OnMaybeFreeze(); } void BrowserModule::Focus(SbTimeMonotonic timestamp) { @@ -1824,7 +1823,7 @@ } void BrowserModule::OnMaybeFreeze() { - TRACE_EVENT0("cobalt::browser", "BrowserModule::MaybeFreeze()"); + TRACE_EVENT0("cobalt::browser", "BrowserModule::OnMaybeFreeze()"); if (base::MessageLoop::current() != self_message_loop_) { self_message_loop_->task_runner()->PostTask( FROM_HERE, @@ -1846,6 +1845,7 @@ web_module_ready_to_freeze && application_state_ == base::kApplicationStateConcealed) { #if SB_API_VERSION >= 13 + DLOG(INFO) << "System request to freeze the app."; SbSystemRequestFreeze(); #endif // SB_API_VERSION >= 13 }
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc index 1411c37..439de72 100644 --- a/src/cobalt/browser/switches.cc +++ b/src/cobalt/browser/switches.cc
@@ -175,6 +175,24 @@ "'network_operator'_'device_type'_'chipset_model_number'_'model_year'/" "'firmware_version' ('brand', 'model', 'connection_type') 'aux_field'\"."; +const char kUserAgentClientHints[] = "user_agent_client_hints"; +const char kUserAgentClientHintsHelp[] = + "Specify custom user agent client hints for device simulations. " + "Configure user agent fields in a string delimited by semicolon ';'. " + "If semicolon is expected to be in value of any user agent field, " + "escape the semicolon by prefixing it with backslash '\\'. " + "Empty field value is allowed. " + "Example: " + "--user_agent_client_hints=\"device_type=GAME;" + "os_name_and_version=Linux armeabi-v7a\\; Android 7.1.2;evergreen_type=\" " + "The 18 supported UA fields for overriding are: aux_field, brand, " + "build_configuration, chipset_model_number, cobalt_build_version_number, " + "cobalt_build_version_number, connection_type, device_type, " + "evergreen_type,evergreen_version, firmware_version, " + "javascript_engine_version, model, model_year, " + "original_design_manufacturer, os_name_and_version, starboard_version, " + "rasterizer_type"; + const char kUserAgentOsNameVersion[] = "user_agent_os_name_version"; const char kUserAgentOsNameVersionHelp[] = "Specifies a custom 'os_name_and_version' user agent field with otherwise " @@ -441,6 +459,7 @@ {kStubImageDecoder, kStubImageDecoderHelp}, {kSuspendFuzzer, kSuspendFuzzerHelp}, {kTimedTrace, kTimedTraceHelp}, {kUserAgent, kUserAgentHelp}, + {kUserAgentClientHints, kUserAgentClientHintsHelp}, {kUserAgentOsNameVersion, kUserAgentOsNameVersionHelp}, {kUseTTS, kUseTTSHelp}, {kWebDriverListenIp, kWebDriverListenIpHelp}, {kWebDriverPort, kWebDriverPortHelp},
diff --git a/src/cobalt/browser/switches.h b/src/cobalt/browser/switches.h index 53fe622..0a95017 100644 --- a/src/cobalt/browser/switches.h +++ b/src/cobalt/browser/switches.h
@@ -78,6 +78,8 @@ extern const char kSuspendFuzzerHelp[]; extern const char kTimedTrace[]; extern const char kTimedTraceHelp[]; +extern const char kUserAgentClientHints[]; +extern const char kUserAgentClienthintsHelp[]; extern const char kUserAgentOsNameVersion[]; extern const char kUserAgentOsNameVersionHelp[]; extern const char kUseTTS[];
diff --git a/src/cobalt/browser/user_agent_platform_info.cc b/src/cobalt/browser/user_agent_platform_info.cc index 8c47fee..aab3d91 100644 --- a/src/cobalt/browser/user_agent_platform_info.cc +++ b/src/cobalt/browser/user_agent_platform_info.cc
@@ -14,6 +14,7 @@ #include "cobalt/browser/user_agent_platform_info.h" +#include <map> #include <memory> #include "base/command_line.h" @@ -37,49 +38,145 @@ namespace cobalt { namespace browser { -namespace { +void GetUserAgentInputMap( + const std::string& user_agent_input, + std::map<std::string, std::string>& user_agent_input_map) { + struct state { + std::string field{""}; + std::string value{""}; + bool field_value_delimiter_found{false}; -std::string CreateDeviceTypeString(SbSystemDeviceType device_type) { - switch (device_type) { - case kSbSystemDeviceTypeBlueRayDiskPlayer: - return "BDP"; - case kSbSystemDeviceTypeGameConsole: - return "GAME"; - case kSbSystemDeviceTypeOverTheTopBox: - return "OTT"; - case kSbSystemDeviceTypeSetTopBox: - return "STB"; - case kSbSystemDeviceTypeTV: - return "TV"; - case kSbSystemDeviceTypeAndroidTV: - return "ATV"; - case kSbSystemDeviceTypeDesktopPC: - return "DESKTOP"; - case kSbSystemDeviceTypeUnknown: - return "UNKNOWN"; - default: - NOTREACHED(); - return "UNKNOWN"; + void reset() { + field.clear(); + value.clear(); + field_value_delimiter_found = false; + } + } current_state; + + char escape_char = '\\'; + char override_delimit_char = ';'; + char field_value_delimit_char = '='; + bool prev_is_escape_char = false; + + for (auto cur_char : user_agent_input) { + if (cur_char == override_delimit_char) { + if (prev_is_escape_char) { + if (!current_state.value.empty() && + current_state.value.back() == escape_char) { // escape delimiter + // found in value. + + current_state.value.back() = override_delimit_char; + } else { // not a valid case for field, reset + current_state.reset(); + } + } else { + if (current_state.field_value_delimiter_found) { // valid field value + // pair found. + user_agent_input_map[current_state.field] = current_state.value; + } // else, in current captured override, field_value_delimit_char + // is not found, invalid. + current_state.reset(); + } + } else if (cur_char == field_value_delimit_char) { + if (current_state.field.empty()) { // field is not found when encounter + // field_value_delimit_char, + // invalid. + current_state.reset(); + } else { + current_state.field_value_delimiter_found = + true; // a field is found, next char is expected to be value + } + } else { + if (current_state.field_value_delimiter_found) { + current_state.value.push_back(cur_char); + } else { + current_state.field.push_back(cur_char); + } + } + if (cur_char == escape_char) { + prev_is_escape_char = true; + } else { + prev_is_escape_char = false; + } + } + if (current_state.field_value_delimiter_found) { + user_agent_input_map[current_state.field] = current_state.value; } } -const char kUnspecifiedConnectionTypeName[] = "UnspecifiedConnectionType"; +namespace { + +struct DeviceTypeName { + SbSystemDeviceType device_type; + char device_type_string[8]; +}; + +const DeviceTypeName kDeviceTypeStrings[] = { + {kSbSystemDeviceTypeBlueRayDiskPlayer, "BDP"}, + {kSbSystemDeviceTypeGameConsole, "GAME"}, + {kSbSystemDeviceTypeOverTheTopBox, "OTT"}, + {kSbSystemDeviceTypeSetTopBox, "STB"}, + {kSbSystemDeviceTypeTV, "TV"}, + {kSbSystemDeviceTypeAndroidTV, "ATV"}, + {kSbSystemDeviceTypeDesktopPC, "DESKTOP"}, + {kSbSystemDeviceTypeUnknown, "UNKNOWN"}}; + +std::string CreateDeviceTypeString(SbSystemDeviceType device_type) { + for (auto& map : kDeviceTypeStrings) { + if (map.device_type == device_type) { + return std::string(map.device_type_string); + } + } + NOTREACHED(); + return "UNKNOWN"; +} + +#if !defined(COBALT_BUILD_TYPE_GOLD) +SbSystemDeviceType GetDeviceType(std::string device_type_string) { + for (auto& map : kDeviceTypeStrings) { + if (!SbStringCompareNoCase(map.device_type_string, + device_type_string.c_str())) { + return map.device_type; + } + } + return kSbSystemDeviceTypeUnknown; +} +#endif + +struct ConnectionTypeName { + SbSystemConnectionType connection_type; + char connection_type_string[26]; +}; + +const ConnectionTypeName kConnectionTypeStrings[] = { + {kSbSystemConnectionTypeWired, "Wired"}, + {kSbSystemConnectionTypeWireless, "Wireless"}, + {kSbSystemConnectionTypeUnknown, "UnspecifiedConnectionType"}}; std::string CreateConnectionTypeString( const base::Optional<SbSystemConnectionType>& connection_type) { if (connection_type) { - switch (*connection_type) { - case kSbSystemConnectionTypeWired: - return "Wired"; - case kSbSystemConnectionTypeWireless: - return "Wireless"; - case kSbSystemConnectionTypeUnknown: - return kUnspecifiedConnectionTypeName; + for (auto& map : kConnectionTypeStrings) { + if (map.connection_type == connection_type) { + return std::string(map.connection_type_string); + } } } - return kUnspecifiedConnectionTypeName; + return "UnspecifiedConnectionType"; } +#if !defined(COBALT_BUILD_TYPE_GOLD) +SbSystemConnectionType GetConnectionType(std::string connection_type_string) { + for (auto& map : kConnectionTypeStrings) { + if (!SbStringCompareNoCase(map.connection_type_string, + connection_type_string.c_str())) { + return map.connection_type; + } + } + return kSbSystemConnectionTypeUnknown; +} +#endif + static bool isAsciiAlphaDigit(int c) { return base::IsAsciiAlpha(c) || base::IsAsciiDigit(c); } @@ -142,7 +239,6 @@ return base::Optional<std::string>(clean); } - // Function that will query Starboard and populate a UserAgentPlatformInfo // object based on those results. This is de-coupled from // CreateUserAgentString() so that the common logic in CreateUserAgentString() @@ -269,8 +365,85 @@ // Connection type info.set_connection_type(SbSystemGetConnectionType()); -} +// Apply overrides from command line +#if !defined(COBALT_BUILD_TYPE_GOLD) + if (base::CommandLine::InitializedForCurrentProcess()) { + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + if (command_line->HasSwitch(switches::kUserAgentClientHints)) { + LOG(INFO) << "Entering UA overrides"; + std::string user_agent_input = + command_line->GetSwitchValueASCII(switches::kUserAgentClientHints); + + std::map<std::string, std::string> user_agent_input_map; + GetUserAgentInputMap(user_agent_input, user_agent_input_map); + + for (const auto& input : user_agent_input_map) { + LOG(INFO) << "Overriding " << input.first << " to " << input.second; + + if (!input.first.compare("starboard_version")) { + info.set_starboard_version(input.second); + LOG(INFO) << "Set starboard version to " << input.second; + } else if (!input.first.compare("os_name_and_version")) { + info.set_os_name_and_version(input.second); + LOG(INFO) << "Set os name and version to " << input.second; + } else if (!input.first.compare("original_design_manufacturer")) { + info.set_original_design_manufacturer(input.second); + LOG(INFO) << "Set original design manufacturer to " << input.second; + } else if (!input.first.compare("device_type")) { + info.set_device_type(GetDeviceType(input.second)); + LOG(INFO) << "Set device type to " << input.second; + } else if (!input.first.compare("chipset_model_number")) { + info.set_chipset_model_number(input.second); + LOG(INFO) << "Set chipset model to " << input.second; + } else if (!input.first.compare("model_year")) { + info.set_model_year(input.second); + LOG(INFO) << "Set model year to " << input.second; + } else if (!input.first.compare("firmware_version")) { + info.set_firmware_version(input.second); + LOG(INFO) << "Set firmware version to " << input.second; + } else if (!input.first.compare("brand")) { + info.set_brand(input.second); + LOG(INFO) << "Set brand to " << input.second; + } else if (!input.first.compare("model")) { + info.set_model(input.second); + LOG(INFO) << "Set model to " << input.second; + } else if (!input.first.compare("aux_field")) { + info.set_aux_field(input.second); + LOG(INFO) << "Set aux field to " << input.second; + } else if (!input.first.compare("connection_type")) { + info.set_connection_type(GetConnectionType(input.second)); + LOG(INFO) << "Set connection type to " << input.second; + } else if (!input.first.compare("javascript_engine_version")) { + info.set_javascript_engine_version(input.second); + LOG(INFO) << "Set javascript engine version to " << input.second; + } else if (!input.first.compare("rasterizer_type")) { + info.set_rasterizer_type(input.second); + LOG(INFO) << "Set rasterizer type to " << input.second; + } else if (!input.first.compare("evergreen_type")) { + info.set_evergreen_type(input.second); + LOG(INFO) << "Set evergreen type to " << input.second; + } else if (!input.first.compare("evergreen_version")) { + info.set_evergreen_version(input.second); + LOG(INFO) << "Set evergreen version to " << input.second; + } else if (!input.first.compare("cobalt_version")) { + info.set_cobalt_version(input.second); + LOG(INFO) << "Set cobalt type to " << input.second; + } else if (!input.first.compare("cobalt_build_version_number")) { + info.set_cobalt_build_version_number(input.second); + LOG(INFO) << "Set cobalt build version to " << input.second; + } else if (!input.first.compare("build_configuration")) { + info.set_build_configuration(input.second); + LOG(INFO) << "Set build configuration to " << input.second; + } else { + LOG(WARNING) << "Unsupported user agent field: " << input.first; + } + } + } + } + +#endif +} } // namespace UserAgentPlatformInfo::UserAgentPlatformInfo() {
diff --git a/src/cobalt/browser/user_agent_platform_info.h b/src/cobalt/browser/user_agent_platform_info.h index db6c94d..7922671 100644 --- a/src/cobalt/browser/user_agent_platform_info.h +++ b/src/cobalt/browser/user_agent_platform_info.h
@@ -15,6 +15,7 @@ #ifndef COBALT_BROWSER_USER_AGENT_PLATFORM_INFO_H_ #define COBALT_BROWSER_USER_AGENT_PLATFORM_INFO_H_ +#include <map> #include <string> #include "cobalt/dom/user_agent_platform_info.h" @@ -22,6 +23,10 @@ namespace cobalt { namespace browser { +void GetUserAgentInputMap( + const std::string& user_agent_input, + std::map<std::string, std::string>& user_agent_input_map); + class UserAgentPlatformInfo : public dom::UserAgentPlatformInfo { public: UserAgentPlatformInfo();
diff --git a/src/cobalt/browser/user_agent_string_test.cc b/src/cobalt/browser/user_agent_string_test.cc index d2537e7..bbd66a7 100644 --- a/src/cobalt/browser/user_agent_string_test.cc +++ b/src/cobalt/browser/user_agent_string_test.cc
@@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "cobalt/browser/user_agent_string.h" +#include <map> +#include "cobalt/browser/user_agent_platform_info.h" +#include "cobalt/browser/user_agent_string.h" #include "testing/gtest/include/gtest/gtest.h" namespace cobalt { @@ -349,6 +351,87 @@ EXPECT_NE(std::string::npos, user_agent_string.find(" V8/6.5.254.28")); } +TEST(GetUserAgentInputMapTest, DelimitParamsBySemicolon) { + std::map<std::string, std::string> user_agent_input_map; + const std::string user_agent_input = + "model_year=2049;starboard_version=Starboard/" + "13;original_design_manufacturer=foo;device_type=GAME"; + GetUserAgentInputMap(user_agent_input, user_agent_input_map); + + std::map<std::string, std::string> expected_user_agent_input_map{ + {"device_type", "GAME"}, + {"model_year", "2049"}, + {"original_design_manufacturer", "foo"}, + {"starboard_version", "Starboard/13"}, + }; + EXPECT_TRUE(user_agent_input_map == expected_user_agent_input_map); +} + +TEST(GetUserAgentInputMapTest, HandleSpecialChar) { + std::map<std::string, std::string> user_agent_input_map; + const std::string user_agent_input = + "aux_field=foo.bar.baz.qux/" + "21.2.1.41.0;invalid-field~name=quux(#quuz)"; + GetUserAgentInputMap(user_agent_input, user_agent_input_map); + + std::map<std::string, std::string> expected_user_agent_input_map{ + {"aux_field", "foo.bar.baz.qux/21.2.1.41.0"}, + {"invalid-field~name", "quux(#quuz)"}, + }; + EXPECT_TRUE(user_agent_input_map == expected_user_agent_input_map); +} + +TEST(GetUserAgentInputMapTest, EscapeSemicolonInValue) { + std::map<std::string, std::string> user_agent_input_map; + const std::string user_agent_input = + "os_name_and_version=Foo bar-v7a\\; Baz 7.1.2\\; Qux OS " + "6.0;model=QUUX"; + GetUserAgentInputMap(user_agent_input, user_agent_input_map); + + std::map<std::string, std::string> expected_user_agent_input_map{ + {"model", "QUUX"}, + {"os_name_and_version", "Foo bar-v7a; Baz 7.1.2; Qux OS 6.0"}, + }; + EXPECT_TRUE(user_agent_input_map == expected_user_agent_input_map); +} + +TEST(GetUserAgentInputMapTest, OmitEscapeSemicolonInField) { + std::map<std::string, std::string> user_agent_input_map; + const std::string user_agent_input = "foo//;bar=baz"; + + GetUserAgentInputMap(user_agent_input, user_agent_input_map); + + std::map<std::string, std::string> expected_user_agent_input_map{ + {"bar", "baz"}, + }; + EXPECT_TRUE(user_agent_input_map == expected_user_agent_input_map); +} + +TEST(GetUserAgentInputMapTest, HandleEmptyFieldValue) { + std::map<std::string, std::string> user_agent_input_map; + const std::string user_agent_input = + "evergreen_type=;device_type=GAME;evergreen_version="; + GetUserAgentInputMap(user_agent_input, user_agent_input_map); + + std::map<std::string, std::string> expected_user_agent_input_map{ + {"device_type", "GAME"}, + {"evergreen_type", ""}, + {"evergreen_version", ""}, + }; + EXPECT_TRUE(user_agent_input_map == expected_user_agent_input_map); +} + +TEST(GetUserAgentInputMapTest, FailSafeWithInvalidInput) { + std::map<std::string, std::string> user_agent_input_map; + const std::string user_agent_input = + ";model;aux_field=;=dummy;device_type=GAME;invalid_field"; + GetUserAgentInputMap(user_agent_input, user_agent_input_map); + + std::map<std::string, std::string> expected_user_agent_input_map{ + {"aux_field", ""}, {"device_type", "GAME"}, + }; + EXPECT_TRUE(user_agent_input_map == expected_user_agent_input_map); +} } // namespace } // namespace browser
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc index 606938d..ef84f3b 100644 --- a/src/cobalt/browser/web_module.cc +++ b/src/cobalt/browser/web_module.cc
@@ -261,8 +261,8 @@ void DoSynchronousLayoutAndGetRenderTree( scoped_refptr<render_tree::Node>* render_tree); - void SetApplicationStartOrPreloadTimestamp( - bool is_preload, SbTimeMonotonic timestamp); + void SetApplicationStartOrPreloadTimestamp(bool is_preload, + SbTimeMonotonic timestamp); void SetDeepLinkTimestamp(SbTimeMonotonic timestamp); private: @@ -1031,8 +1031,8 @@ void WebModule::Impl::SetApplicationStartOrPreloadTimestamp( bool is_preload, SbTimeMonotonic timestamp) { DCHECK(window_); - window_->performance()->SetApplicationStartOrPreloadTimestamp( - is_preload, timestamp); + window_->performance()->SetApplicationStartOrPreloadTimestamp(is_preload, + timestamp); } void WebModule::Impl::SetDeepLinkTimestamp(SbTimeMonotonic timestamp) { @@ -1171,12 +1171,12 @@ SetApplicationState(base::kApplicationStateBlurred, timestamp); } -void WebModule::Impl::Conceal( - render_tree::ResourceProvider* resource_provider, - SbTimeMonotonic timestamp) { +void WebModule::Impl::Conceal(render_tree::ResourceProvider* resource_provider, + SbTimeMonotonic timestamp) { TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Conceal()"); SetResourceProvider(resource_provider); + SetApplicationState(base::kApplicationStateConcealed, timestamp); layout_manager_->Suspend(); // Purge the cached resources prior to the freeze. That may cancel pending // loads, allowing the freeze to occur faster and preventing unnecessary @@ -1201,21 +1201,25 @@ } loader_factory_->UpdateResourceProvider(resource_provider_); - SetApplicationState(base::kApplicationStateConcealed, timestamp); + + if (window_->media_session()->media_session_client() != NULL) { + window_->media_session() + ->media_session_client() + ->PostDelayedTaskForMaybeFreezeCallback(); + } } void WebModule::Impl::Freeze(SbTimeMonotonic timestamp) { TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Freeze()"); + SetApplicationState(base::kApplicationStateFrozen, timestamp); // Clear out the loader factory's resource provider, possibly aborting any // in-progress loads. loader_factory_->Suspend(); - SetApplicationState(base::kApplicationStateFrozen, timestamp); } -void WebModule::Impl::Unfreeze( - render_tree::ResourceProvider* resource_provider, - SbTimeMonotonic timestamp) { +void WebModule::Impl::Unfreeze(render_tree::ResourceProvider* resource_provider, + SbTimeMonotonic timestamp) { TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Unfreeze()"); synchronous_loader_interrupt_.Reset(); DCHECK(resource_provider); @@ -1224,9 +1228,8 @@ SetApplicationState(base::kApplicationStateConcealed, timestamp); } -void WebModule::Impl::Reveal( - render_tree::ResourceProvider* resource_provider, - SbTimeMonotonic timestamp) { +void WebModule::Impl::Reveal(render_tree::ResourceProvider* resource_provider, + SbTimeMonotonic timestamp) { TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Reveal()"); synchronous_loader_interrupt_.Reset(); DCHECK(resource_provider); @@ -1693,9 +1696,8 @@ impl_->CancelSynchronousLoads(); - auto impl_blur = - base::Bind(&WebModule::Impl::Blur, - base::Unretained(impl_.get()), timestamp); + auto impl_blur = base::Bind(&WebModule::Impl::Blur, + base::Unretained(impl_.get()), timestamp); #if defined(ENABLE_DEBUGGER) // We normally need to block here so that the call doesn't return until the @@ -1725,9 +1727,9 @@ // We must block here so that the call doesn't return until the web // application has had a chance to process the whole event. message_loop()->task_runner()->PostBlockingTask( - FROM_HERE, base::Bind(&WebModule::Impl::Conceal, - base::Unretained(impl_.get()), - resource_provider, timestamp)); + FROM_HERE, + base::Bind(&WebModule::Impl::Conceal, base::Unretained(impl_.get()), + resource_provider, timestamp)); } void WebModule::Freeze(SbTimeMonotonic timestamp) { @@ -1737,9 +1739,8 @@ // We must block here so that the call doesn't return until the web // application has had a chance to process the whole event. message_loop()->task_runner()->PostBlockingTask( - FROM_HERE, - base::Bind(&WebModule::Impl::Freeze, - base::Unretained(impl_.get()), timestamp)); + FROM_HERE, base::Bind(&WebModule::Impl::Freeze, + base::Unretained(impl_.get()), timestamp)); } void WebModule::Unfreeze(render_tree::ResourceProvider* resource_provider, @@ -1748,9 +1749,9 @@ DCHECK_NE(base::MessageLoop::current(), message_loop()); message_loop()->task_runner()->PostTask( - FROM_HERE, base::Bind(&WebModule::Impl::Unfreeze, - base::Unretained(impl_.get()), - resource_provider, timestamp)); + FROM_HERE, + base::Bind(&WebModule::Impl::Unfreeze, base::Unretained(impl_.get()), + resource_provider, timestamp)); } void WebModule::Reveal(render_tree::ResourceProvider* resource_provider, @@ -1759,9 +1760,9 @@ DCHECK_NE(base::MessageLoop::current(), message_loop()); message_loop()->task_runner()->PostTask( - FROM_HERE, base::Bind(&WebModule::Impl::Reveal, - base::Unretained(impl_.get()), - resource_provider, timestamp)); + FROM_HERE, + base::Bind(&WebModule::Impl::Reveal, base::Unretained(impl_.get()), + resource_provider, timestamp)); } void WebModule::Focus(SbTimeMonotonic timestamp) { @@ -1769,9 +1770,8 @@ DCHECK_NE(base::MessageLoop::current(), message_loop()); message_loop()->task_runner()->PostTask( - FROM_HERE, - base::Bind(&WebModule::Impl::Focus, - base::Unretained(impl_.get()), timestamp)); + FROM_HERE, base::Bind(&WebModule::Impl::Focus, + base::Unretained(impl_.get()), timestamp)); } void WebModule::ReduceMemory() { @@ -1834,24 +1834,22 @@ DCHECK(impl_); if (base::MessageLoop::current() != message_loop()) { message_loop()->task_runner()->PostBlockingTask( - FROM_HERE, - base::Bind(&WebModule::Impl::SetApplicationStartOrPreloadTimestamp, - base::Unretained(impl_.get()), is_preload, timestamp)); + FROM_HERE, + base::Bind(&WebModule::Impl::SetApplicationStartOrPreloadTimestamp, + base::Unretained(impl_.get()), is_preload, timestamp)); } else { impl_->SetApplicationStartOrPreloadTimestamp(is_preload, timestamp); } } void WebModule::SetDeepLinkTimestamp(SbTimeMonotonic timestamp) { - TRACE_EVENT0("cobalt::browser", - "WebModule::SetDeepLinkTimestamp()"); + TRACE_EVENT0("cobalt::browser", "WebModule::SetDeepLinkTimestamp()"); DCHECK(message_loop()); DCHECK(impl_); if (base::MessageLoop::current() != message_loop()) { message_loop()->task_runner()->PostBlockingTask( - FROM_HERE, - base::Bind(&WebModule::Impl::SetDeepLinkTimestamp, - base::Unretained(impl_.get()), timestamp)); + FROM_HERE, base::Bind(&WebModule::Impl::SetDeepLinkTimestamp, + base::Unretained(impl_.get()), timestamp)); } else { impl_->SetDeepLinkTimestamp(timestamp); }
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id index 6446170..ba0b6d4 100644 --- a/src/cobalt/build/build.id +++ b/src/cobalt/build/build.id
@@ -1 +1 @@ -304063 \ No newline at end of file +304707 \ No newline at end of file
diff --git a/src/cobalt/demos/content/performance-api-demo/performance-lifecycle-timing-demo.html b/src/cobalt/demos/content/performance-api-demo/performance-lifecycle-timing-demo.html new file mode 100644 index 0000000..72e472f --- /dev/null +++ b/src/cobalt/demos/content/performance-api-demo/performance-lifecycle-timing-demo.html
@@ -0,0 +1,65 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Performance Lifecycle Timing Demo</title> + <style> + body { + background-color: rgb(255, 255, 255); + color: #0047ab; + font-size: 50px; + } + </style> +</head> + +<body> + <script> + var lifecycleTiming = performance.getEntriesByType("lifecycle")[0]; + + function LogInfo(state, time) { + var message = state + ": " + time; + console.log(message); + document.getElementById('info').innerHTML += message + ".\n"; + } + + function handleVisibilityChange() { + if (document.visibilityState == "visible") { + ReportPerformanceLifecycleTimingInfo(); + } + } + + function ReportPerformanceLifecycleTimingInfo() { + if (lifecycleTiming.appBlur != 0) { + LogInfo("Blur time", lifecycleTiming.appBlur); + } + if (lifecycleTiming.appConceal != 0) { + LogInfo("Conceal time", lifecycleTiming.appConceal); + } + if (lifecycleTiming.appReveal != 0) { + LogInfo("Reveal time", lifecycleTiming.appReveal); + } + if (lifecycleTiming.appFocus != 0) { + LogInfo("Focus time", lifecycleTiming.appFocus); + } + if (lifecycleTiming.appFreeze != 0) { + LogInfo("Freeze time", lifecycleTiming.appFreeze); + } + if (lifecycleTiming.appUnfreeze != 0) { + LogInfo("Unfreeze time", lifecycleTiming.appUnfreeze); + } + } + + document.addEventListener("visibilitychange", handleVisibilityChange); + + window.onload = function () { + LogInfo("Performance Now", window.performance.now()); + LogInfo("Start time", lifecycleTiming.appStart); + }; + + </script> + <div> + <span id='info' style='white-space: pre; background-color:#00FF00'></span> + </div> +</body> + +</html> \ No newline at end of file
diff --git a/src/cobalt/demos/content/performance-api-demo/performance-api-demo.html b/src/cobalt/demos/content/performance-api-demo/performance-resource-timing-demo.html similarity index 100% rename from src/cobalt/demos/content/performance-api-demo/performance-api-demo.html rename to src/cobalt/demos/content/performance-api-demo/performance-resource-timing-demo.html
diff --git a/src/cobalt/doc/docker_build.md b/src/cobalt/doc/docker_build.md index 7a04812..4ba59a9 100644 --- a/src/cobalt/doc/docker_build.md +++ b/src/cobalt/doc/docker_build.md
@@ -21,7 +21,7 @@ where config is one of the four optimization levels, debug, devel, qa and gold, and target is the build target passed to `ninja` -See [Cobalt README](https://cobalt.googlesource.com/cobalt/+/master/src/README.md#build-types) +See [Cobalt README](../../README.md#build-types) for full details. Builds will be available in your `${COBALT_SRC}/out` directory. @@ -63,4 +63,4 @@ `docker-compose run linux-x64x11 /bin/bash` -and try to build cobalt [with the usual `gyp / ninja` flow](https://cobalt.googlesource.com/cobalt/+/master/src/README.md#building-and-running-the-code). +and try to build cobalt [with the usual `gyp / ninja` flow](../../README.md#building-and-running-the-code).
diff --git a/src/cobalt/doc/platform_services.md b/src/cobalt/doc/platform_services.md index d2991b3..dadf6ae 100644 --- a/src/cobalt/doc/platform_services.md +++ b/src/cobalt/doc/platform_services.md
@@ -53,7 +53,7 @@ The Platform Services extension is exposed to the web app via the following IDL: -* [src/cobalt/h5vcc/h5vcc\_platform\_service.idl](https://cobalt.googlesource.com/cobalt/+/refs/heads/master/src/cobalt/h5vcc/h5vcc_platform_service.idl) +* [src/cobalt/h5vcc/h5vcc\_platform\_service.idl](../h5vcc/h5vcc_platform_service.idl) The entrypoint for defined Platform Services extensions will be accessible in the `H5vccPlatformService` object. Note that ArrayBuffers are chosen to @@ -62,10 +62,10 @@ ### Starboard Interface -Implementing the Starboard layer of Platform Service extension support uses the +Implementing the Starboard layer of Platform Service extension support uses the following interface in parallel with the IDL interface: -* [src/cobalt/extension/platform\_service.h](https://cobalt.googlesource.com/cobalt/+/refs/heads/master/src/cobalt/extension/platform_service.h) +* [src/cobalt/extension/platform\_service.h](../extension/platform_service.h) `CobaltExtensionPlatformServiceApi` is the main interface for the Starboard layer.
diff --git a/src/cobalt/dom/font_cache.cc b/src/cobalt/dom/font_cache.cc index 93e64d6..ce58fe0 100644 --- a/src/cobalt/dom/font_cache.cc +++ b/src/cobalt/dom/font_cache.cc
@@ -159,21 +159,34 @@ return cached_font_info.font; } +std::vector<FontFace*> FontCache::GetFacesForFamilyAndStyle( + const std::string& family, render_tree::FontStyle style) { + std::vector<FontFace*> faces; + FontFaceMap::iterator font_face_map_iterator = font_face_map_->find(family); + if (font_face_map_iterator != font_face_map_->end()) { + // Add all font-face entries that match the family. + std::vector<const FontFaceStyleSet::Entry*> entries = + font_face_map_iterator->second.GetEntriesThatMatchStyle(style); + for (auto entry : entries) { + FontFace* face = new FontFace(); + face->entry = entry; + faces.push_back(face); + } + } else { + // This is a local font. One face can represent it. + FontFace* face = new FontFace(); + faces.push_back(face); + } + return faces; +} + scoped_refptr<render_tree::Font> FontCache::TryGetFont( const std::string& family, render_tree::FontStyle style, float size, - FontListFont::State* state) { + FontFace::State* state, + const FontFaceStyleSet::Entry* maybe_style_set_entry) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); - FontFaceMap::iterator font_face_map_iterator = font_face_map_->find(family); int64 request_time_start = base::TimeTicks::Now().ToInternalValue(); - if (font_face_map_iterator != font_face_map_->end()) { - // Retrieve the font face style set entry that most closely matches the - // desired style. Given that a font face was found for this family, it - // should never be NULL. - // https://www.w3.org/TR/css3-fonts/#font-prop-desc - const FontFaceStyleSet::Entry* style_set_entry = - font_face_map_iterator->second.MatchStyle(style); - DCHECK(style_set_entry != NULL); - + if (maybe_style_set_entry) { // Walk the entry's sources: // - If a remote source is encountered, always return the results of its // attempted retrieval, regardless of its success. @@ -183,8 +196,9 @@ // instead. // https://www.w3.org/TR/css3-fonts/#src-desc for (FontFaceSources::const_iterator source_iterator = - style_set_entry->sources.begin(); - source_iterator != style_set_entry->sources.end(); ++source_iterator) { + maybe_style_set_entry->sources.begin(); + source_iterator != maybe_style_set_entry->sources.end(); + ++source_iterator) { if (source_iterator->IsUrlSource()) { auto font = TryGetRemoteFont(source_iterator->GetUrl(), size, state); GlobalStats::GetInstance()->OnFontRequestComplete(request_time_start); @@ -199,7 +213,7 @@ } } - *state = FontListFont::kUnavailableState; + *state = FontFace::kUnavailableState; return NULL; } else { auto font = TryGetLocalFont(family, style, size, state); @@ -335,7 +349,7 @@ } scoped_refptr<render_tree::Font> FontCache::TryGetRemoteFont( - const GURL& url, float size, FontListFont::State* state) { + const GURL& url, float size, FontFace::State* state) { // Retrieve the font from the remote typeface cache, potentially triggering a // load. scoped_refptr<loader::font::CachedRemoteTypeface> cached_remote_typeface = @@ -370,16 +384,16 @@ scoped_refptr<render_tree::Typeface> typeface = cached_remote_typeface->TryGetResource(); if (typeface.get() != NULL) { - *state = FontListFont::kLoadedState; + *state = FontFace::kLoadedState; return GetFontFromTypefaceAndSize(typeface, size); } else { if (cached_remote_typeface->IsLoadingComplete()) { - *state = FontListFont::kUnavailableState; + *state = FontFace::kUnavailableState; } else if (requested_remote_typeface_iterator->second ->HasActiveRequestTimer()) { - *state = FontListFont::kLoadingWithTimerActiveState; + *state = FontFace::kLoadingWithTimerActiveState; } else { - *state = FontListFont::kLoadingWithTimerExpiredState; + *state = FontFace::kLoadingWithTimerExpiredState; } return NULL; } @@ -387,7 +401,7 @@ scoped_refptr<render_tree::Font> FontCache::TryGetLocalFont( const std::string& family, render_tree::FontStyle style, float size, - FontListFont::State* state) { + FontFace::State* state) { DCHECK(resource_provider()); DCHECK(resource_provider() != NULL); // Only request the local font from the resource provider if the family is @@ -398,10 +412,10 @@ // signifies using the default font. if (!family.empty() && !resource_provider()->HasLocalFontFamily(family.c_str())) { - *state = FontListFont::kUnavailableState; + *state = FontFace::kUnavailableState; return NULL; } else { - *state = FontListFont::kLoadedState; + *state = FontFace::kLoadedState; return GetFontFromTypefaceAndSize( GetCachedLocalTypeface( resource_provider()->GetLocalTypeface(family.c_str(), style)), @@ -410,7 +424,7 @@ } scoped_refptr<render_tree::Font> FontCache::TryGetLocalFontByFaceName( - const std::string& font_face, float size, FontListFont::State* state) { + const std::string& font_face, float size, FontFace::State* state) { do { if (font_face.empty()) { break; @@ -424,11 +438,11 @@ const scoped_refptr<render_tree::Typeface>& typeface_cached( GetCachedLocalTypeface(typeface)); - *state = FontListFont::kLoadedState; + *state = FontFace::kLoadedState; return GetFontFromTypefaceAndSize(typeface_cached, size); } while (false); - *state = FontListFont::kUnavailableState; + *state = FontFace::kUnavailableState; return NULL; }
diff --git a/src/cobalt/dom/font_cache.h b/src/cobalt/dom/font_cache.h index 44d10a9..932743c 100644 --- a/src/cobalt/dom/font_cache.h +++ b/src/cobalt/dom/font_cache.h
@@ -201,13 +201,19 @@ const scoped_refptr<render_tree::Font>& GetFontFromTypefaceAndSize( const scoped_refptr<render_tree::Typeface>& typeface, float size); + + // Retrieves a list of the font faces that match the given family name and + // style. If no explicit font faces exist, creates a faux face for + // representing a local font. + std::vector<FontFace*> GetFacesForFamilyAndStyle( + const std::string& family, render_tree::FontStyle style); + // Attempts to retrieve a font. If the family maps to a font face, then this // makes a request to |TryGetRemoteFont()|; otherwise, it makes a request // to |TryGetLocalFont()|. This function may return NULL. - scoped_refptr<render_tree::Font> TryGetFont(const std::string& family, - render_tree::FontStyle style, - float size, - FontListFont::State* state); + scoped_refptr<render_tree::Font> TryGetFont( + const std::string& family, render_tree::FontStyle style, float size, + FontFace::State* state, const FontFaceStyleSet::Entry* maybe_entry); // Returns the character fallback typeface map associated with the specified // style. Each unique style has its own exclusive map. If it doesn't already @@ -259,20 +265,20 @@ // font are registered with the remote typeface cache to be called when the // load finishes. scoped_refptr<render_tree::Font> TryGetRemoteFont(const GURL& url, float size, - FontListFont::State* state); + FontFace::State* state); // Returns NULL if the requested family is not empty and is not available in // the resource provider. Otherwise, returns the best matching local font. scoped_refptr<render_tree::Font> TryGetLocalFont(const std::string& family, render_tree::FontStyle style, float size, - FontListFont::State* state); + FontFace::State* state); // Lookup by a typeface (aka font_face), typeface is defined as font family + // style (weight, width, and style). // Returns NULL if the requested font face is not found. scoped_refptr<render_tree::Font> TryGetLocalFontByFaceName( - const std::string& font_face, float size, FontListFont::State* state); + const std::string& font_face, float size, FontFace::State* state); // Called when a remote typeface either successfully loads or fails to load. // In either case, the event can impact the fonts contained within the font
diff --git a/src/cobalt/dom/font_cache_test.cc b/src/cobalt/dom/font_cache_test.cc index 49656eb..1c22311 100644 --- a/src/cobalt/dom/font_cache_test.cc +++ b/src/cobalt/dom/font_cache_test.cc
@@ -100,6 +100,13 @@ const std::string postscript_font_name("DancingScript"); std::unique_ptr<FontCache::FontFaceMap> ffm = CreateFontFaceMapHelper(family_name, postscript_font_name); + + const FontFaceStyleSet::Entry* entry = nullptr; + FontCache::FontFaceMap::iterator ffm_iterator = ffm->find(family_name); + if (ffm_iterator != ffm->end()) { + entry = ffm_iterator->second.GetEntriesThatMatchStyle(kNormalUpright)[0]; + } + EXPECT_TRUE(entry); font_cache_->SetFontFaceMap(std::move(ffm)); EXPECT_CALL(loader_factory_, CreateTypefaceLoaderMock(_, _, _, _, _)) @@ -109,9 +116,9 @@ GetLocalTypefaceIfAvailableMock(postscript_font_name)) .WillOnce(Return(sample_typeface_)); - FontListFont::State state; + FontFace::State state; scoped_refptr<render_tree::Font> f = - font_cache_->TryGetFont(family_name, kNormalUpright, 12.0, &state); + font_cache_->TryGetFont(family_name, kNormalUpright, 12.0, &state, entry); EXPECT_TRUE(f); } @@ -121,6 +128,13 @@ const std::string family_name("Dancing Script"); std::unique_ptr<FontCache::FontFaceMap> ffm = CreateFontFaceMapHelper(family_name, invalid_postscript_font_name); + + const FontFaceStyleSet::Entry* entry = nullptr; + FontCache::FontFaceMap::iterator ffm_iterator = ffm->find(family_name); + if (ffm_iterator != ffm->end()) { + entry = ffm_iterator->second.GetEntriesThatMatchStyle(kNormalUpright)[0]; + } + EXPECT_TRUE(entry); font_cache_->SetFontFaceMap(std::move(ffm)); EXPECT_CALL(mock_resource_provider_, @@ -128,9 +142,9 @@ .Times(1); EXPECT_CALL(loader_factory_, CreateTypefaceLoaderMock(_, _, _, _, _)); - FontListFont::State state; + FontFace::State state; scoped_refptr<render_tree::Font> f = - font_cache_->TryGetFont(family_name, kNormalUpright, 12.0, &state); + font_cache_->TryGetFont(family_name, kNormalUpright, 12.0, &state, entry); EXPECT_FALSE(f); }
diff --git a/src/cobalt/dom/font_face.cc b/src/cobalt/dom/font_face.cc index 5f6fdb5..6c44d75 100644 --- a/src/cobalt/dom/font_face.cc +++ b/src/cobalt/dom/font_face.cc
@@ -39,25 +39,22 @@ } } -const FontFaceStyleSet::Entry* FontFaceStyleSet::MatchStyle( +std::vector<const FontFaceStyleSet::Entry*> +FontFaceStyleSet::GetEntriesThatMatchStyle( const render_tree::FontStyle& pattern) const { - return entries_.empty() ? NULL - : &entries_[GetClosestStyleEntryIndex(pattern)]; -} - -size_t FontFaceStyleSet::GetClosestStyleEntryIndex( - const render_tree::FontStyle& pattern) const { - size_t closest_index = 0; + std::vector<const FontFaceStyleSet::Entry*> entries; int max_score = std::numeric_limits<int>::min(); - for (size_t i = 0; i < entries_.size(); ++i) { - int score = MatchScore(pattern, entries_[i].style); - if (score > max_score) { - closest_index = i; - max_score = score; + for (const auto& entry : entries_) { + int score = MatchScore(pattern, entry.style); + if (score >= max_score) { + if (score > max_score) { + max_score = score; + entries.clear(); + } + entries.push_back(&entry); } } - - return closest_index; + return entries; } int FontFaceStyleSet::MatchScore(
diff --git a/src/cobalt/dom/font_face.h b/src/cobalt/dom/font_face.h index 1bfb40a..a5f80cc 100644 --- a/src/cobalt/dom/font_face.h +++ b/src/cobalt/dom/font_face.h
@@ -66,7 +66,19 @@ return style.weight == other.style.weight && style.slant == other.style.slant && sources == other.sources; } + struct UnicodeRange { + // Sorts ranges primarily based on start and secondarily based on end. + bool operator<(const UnicodeRange& range) const { + if (start == range.start) { + return end < range.end; + } + return start < range.start; + } + uint32 start; + uint32 end; + }; + std::set<UnicodeRange> unicode_range; render_tree::FontStyle style; FontFaceSources sources; }; @@ -77,10 +89,11 @@ // into the set. All pre-existing url entries within the set are retained. void CollectUrlSources(std::set<GURL>* urls) const; - // Returns the style set entry with the style most closely matching the - // requested pattern. If the style set contains any entries, it is guaranteed - // to not return NULL. - const Entry* MatchStyle(const render_tree::FontStyle& pattern) const; + + // Returns a list of entries with the style that most closesly matches the + // pattern. + std::vector<const Entry*> GetEntriesThatMatchStyle( + const render_tree::FontStyle& pattern) const; bool operator==(const FontFaceStyleSet& other) const { return entries_ == other.entries_; @@ -89,11 +102,6 @@ private: typedef std::vector<Entry> Entries; - // Returns the index of the entry with a style most closely matching the - // pattern (the lower the score, the more closely it matches). In the case of - // a tie, the earliest encountered entry is given priority. - size_t GetClosestStyleEntryIndex(const render_tree::FontStyle& pattern) const; - // Returns the match score between two patterns. The score logic matches that // within SkFontStyleSet_Cobalt::match_score(). int MatchScore(const render_tree::FontStyle& pattern,
diff --git a/src/cobalt/dom/font_face_updater.cc b/src/cobalt/dom/font_face_updater.cc index 2a486c6..940eaaa 100644 --- a/src/cobalt/dom/font_face_updater.cc +++ b/src/cobalt/dom/font_face_updater.cc
@@ -29,6 +29,7 @@ #include "cobalt/cssom/property_value_visitor.h" #include "cobalt/cssom/string_value.h" #include "cobalt/cssom/style_sheet_list.h" +#include "cobalt/cssom/unicode_range_value.h" #include "cobalt/cssom/url_src_value.h" #include "cobalt/cssom/url_value.h" @@ -214,8 +215,10 @@ void FontFaceProvider::VisitUnicodeRange( cssom::UnicodeRangeValue* unicode_range) { - NOTIMPLEMENTED() - << "FontFaceProvider::UnicodeRange support not implemented yet."; + FontFaceStyleSet::Entry::UnicodeRange range = { + static_cast<uint32>(unicode_range->start()), + static_cast<uint32>(unicode_range->end())}; + style_set_entry_.unicode_range.insert(range); } // Check for a supported format. If no format hints are supplied, then the user @@ -307,6 +310,9 @@ if (css_font_face_rule->data()->weight()) { css_font_face_rule->data()->weight()->Accept(&font_face_provider); } + if (css_font_face_rule->data()->unicode_range()) { + css_font_face_rule->data()->unicode_range()->Accept(&font_face_provider); + } if (font_face_provider.IsFontFaceValid()) { (*font_face_map_)[font_face_provider.font_family()].AddEntry(
diff --git a/src/cobalt/dom/font_list.cc b/src/cobalt/dom/font_list.cc index ae4cbb4..45e8d5c 100644 --- a/src/cobalt/dom/font_list.cc +++ b/src/cobalt/dom/font_list.cc
@@ -15,6 +15,7 @@ #include "cobalt/dom/font_list.h" #include "base/i18n/char_iterator.h" +#include "cobalt/base/unicode/character_values.h" #include "cobalt/dom/font_cache.h" namespace cobalt { @@ -24,6 +25,19 @@ const base::char16 kHorizontalEllipsisValue = 0x2026; +bool CharInRange( + const std::set<FontFaceStyleSet::Entry::UnicodeRange>& unicode_range, + uint32 utf32_character) { + if (unicode_range.empty()) return true; + for (const auto& range : unicode_range) { + if (range.start > utf32_character) break; + if ((range.start <= utf32_character) && (utf32_character <= range.end)) { + return true; + } + } + return false; +} + } // namespace FontList::FontList(FontCache* font_cache, const FontListKey& font_list_key) @@ -40,18 +54,34 @@ font_cache_->GetCharacterFallbackTypefaceMap(style_)) { // Add all of the family names to the font list fonts. for (size_t i = 0; i < font_list_key.family_names.size(); ++i) { - fonts_.push_back(FontListFont(font_list_key.family_names[i])); + FontListFont font = FontListFont(font_list_key.family_names[i]); + font.faces = + font_cache_->GetFacesForFamilyAndStyle(font.family_name, style_); + fonts_.push_back(font); } // Add an empty font at the end in order to fall back to the default typeface. - fonts_.push_back(FontListFont("")); + FontListFont default_font = FontListFont(""); + FontFace* default_face = new FontFace(); + default_font.faces = std::vector<FontFace*>{default_face}; + fonts_.push_back(default_font); +} + +FontList::~FontList() { + for (FontListFont font : fonts_) { + for (FontFace* face : font.faces) { + delete face; + } + } } void FontList::Reset() { for (size_t i = 0; i < fonts_.size(); ++i) { FontListFont& font_list_font = fonts_[i]; - font_list_font.set_state(FontListFont::kUnrequestedState); - font_list_font.set_font(NULL); + for (auto face : font_list_font.faces) { + face->state = FontFace::kUnrequestedState; + face->font = NULL; + } } primary_font_ = NULL; @@ -69,20 +99,22 @@ for (size_t i = 0; i < fonts_.size(); ++i) { FontListFont& font_list_font = fonts_[i]; - if (font_list_font.state() == FontListFont::kLoadingWithTimerActiveState || - font_list_font.state() == FontListFont::kLoadingWithTimerExpiredState) { - font_list_font.set_state(FontListFont::kUnrequestedState); - // If a loaded font hasn't been found yet, then the cached values need to - // be reset. It'll potentially change the primary font. - if (!found_loaded_font) { - primary_font_ = NULL; - is_font_metrics_set_ = false; - is_space_width_set_ = false; - is_ellipsis_info_set_ = false; - ellipsis_font_ = NULL; + for (auto face : font_list_font.faces) { + if (face->state == FontFace::kLoadingWithTimerActiveState || + face->state == FontFace::kLoadingWithTimerExpiredState) { + face->state = FontFace::kUnrequestedState; + // If a loaded font hasn't been found yet, then the cached values need + // to be reset. It'll potentially change the primary font. + if (!found_loaded_font) { + primary_font_ = NULL; + is_font_metrics_set_ = false; + is_space_width_set_ = false; + is_ellipsis_info_set_ = false; + ellipsis_font_ = NULL; + } + } else if (face->state == FontFace::kLoadedState) { + found_loaded_font = true; } - } else if (font_list_font.state() == FontListFont::kLoadedState) { - found_loaded_font = true; } } } @@ -97,8 +129,10 @@ // display text, simply leaving transparent text is considered // non-conformant behavior." // https://www.w3.org/TR/css3-fonts/#font-face-loading - if (fonts_[i].state() == FontListFont::kLoadingWithTimerActiveState) { - return false; + for (auto face : fonts_[i].faces) { + if (face->state == FontFace::kLoadingWithTimerActiveState) { + return false; + } } } @@ -197,15 +231,20 @@ for (size_t i = 0; i < fonts_.size(); ++i) { FontListFont& font_list_font = fonts_[i]; - if (font_list_font.state() == FontListFont::kUnrequestedState) { - RequestFont(i); - } + for (FontFace* face : font_list_font.faces) { + const FontFaceStyleSet::Entry* entry = face->entry; + if (entry && !CharInRange(entry->unicode_range, utf32_character)) { + continue; + } + if (face->state == FontFace::kUnrequestedState) { + RequestFont(i, face); + } - if (font_list_font.state() == FontListFont::kLoadedState) { - *glyph_index = - font_list_font.font()->GetGlyphForCharacter(utf32_character); - if (*glyph_index != render_tree::kInvalidGlyphIndex) { - return font_list_font.font(); + if (face->state == FontFace::kLoadedState) { + *glyph_index = face->font->GetGlyphForCharacter(utf32_character); + if (*glyph_index != render_tree::kInvalidGlyphIndex) { + return face->font; + } } } } @@ -241,18 +280,27 @@ // time to do it now. if (!primary_font_) { // Walk the list of fonts, requesting any encountered that are in an - // unrequested state. The first font encountered that is loaded is - // the primary font. + // unrequested state. The first font encountered that is loaded and whose + // unicode range includes the space character is the primary font. + // https://www.w3.org/TR/css-fonts-4/#first-available-font for (size_t i = 0; i < fonts_.size(); ++i) { FontListFont& font_list_font = fonts_[i]; - if (font_list_font.state() == FontListFont::kUnrequestedState) { - RequestFont(i); - } + for (FontFace* face : font_list_font.faces) { + const FontFaceStyleSet::Entry* entry = face->entry; + if (entry && !CharInRange(entry->unicode_range, + base::unicode::kSpaceCharacter)) { + continue; + } + if (face->state == FontFace::kUnrequestedState) { + RequestFont(i, face); + } - if (font_list_font.state() == FontListFont::kLoadedState) { - primary_font_ = font_list_font.font(); - break; + if (face->state == FontFace::kLoadedState) { + primary_font_ = face->font; + DCHECK(primary_font_); + return primary_font_; + } } } } @@ -261,40 +309,41 @@ return primary_font_; } -void FontList::RequestFont(size_t index) { +void FontList::RequestFont(size_t index, FontFace* used_face) { FontListFont& font_list_font = fonts_[index]; - FontListFont::State state; + FontFace::State state; // Request the font from the font cache; the state of the font will be set // during the call. scoped_refptr<render_tree::Font> render_tree_font = font_cache_->TryGetFont( - font_list_font.family_name(), style_, size_, &state); + font_list_font.family_name, style_, size_, &state, used_face->entry); - if (state == FontListFont::kLoadedState) { + if (state == FontFace::kLoadedState) { DCHECK(render_tree_font.get() != NULL); // Walk all of the fonts in the list preceding the loaded font. If they have - // the same typeface as the loaded font, then set the font list font as a - // duplicate. There's no reason to have multiple fonts in the list with the - // same typeface. + // the same typeface as the loaded font, then do not create a new face. + // There's no reason to have multiple fonts in the list with the same + // typeface. render_tree::TypefaceId typeface_id = render_tree_font->GetTypefaceId(); for (size_t i = 0; i < index; ++i) { FontListFont& check_font = fonts_[i]; - if (check_font.state() == FontListFont::kLoadedState && - check_font.font()->GetTypefaceId() == typeface_id) { - font_list_font.set_state(FontListFont::kDuplicateState); - break; + for (auto face : check_font.faces) { + if (face->state == FontFace::kLoadedState && + face->font->GetTypefaceId() == typeface_id) { + used_face->state = FontFace::kDuplicateState; + } } } // If this font wasn't a duplicate, then its time to initialize its font // data. This font is now available to use. - if (font_list_font.state() != FontListFont::kDuplicateState) { - font_list_font.set_state(FontListFont::kLoadedState); - font_list_font.set_font(render_tree_font); + if (used_face->state != FontFace::kDuplicateState) { + used_face->state = FontFace::kLoadedState; + used_face->font = render_tree_font; } } else { - font_list_font.set_state(state); + used_face->state = state; } } @@ -310,10 +359,10 @@ void FontList::GenerateSpaceWidth() { if (!is_space_width_set_) { - const scoped_refptr<render_tree::Font>& primary_font = GetPrimaryFont(); - render_tree::GlyphIndex space_glyph = - primary_font->GetGlyphForCharacter(' '); - space_width_ = primary_font->GetGlyphWidth(space_glyph); + render_tree::GlyphIndex space_glyph = render_tree::kInvalidGlyphIndex; + space_width_ = + GetCharacterFont(base::unicode::kSpaceCharacter, &space_glyph) + ->GetGlyphWidth(space_glyph); if (space_width_ == 0) { DLOG(WARNING) << "Font being used with space width of 0!"; }
diff --git a/src/cobalt/dom/font_list.h b/src/cobalt/dom/font_list.h index 8c95c94..6676b1f 100644 --- a/src/cobalt/dom/font_list.h +++ b/src/cobalt/dom/font_list.h
@@ -22,6 +22,7 @@ #include "base/containers/hash_tables.h" #include "base/containers/small_map.h" #include "base/memory/ref_counted.h" +#include "cobalt/dom/font_face.h" #include "cobalt/math/rect_f.h" #include "cobalt/render_tree/font.h" #include "cobalt/render_tree/font_provider.h" @@ -33,12 +34,11 @@ class FontCache; -// A specific font family within a font list. It has an internal state, which -// lets the font list know whether or not the font has already been requested, -// and if so, whether or not it was available. |font_| and |character_map_| -// will only be non-NULL in the case where |state_| is set to |kLoadedState|. -class FontListFont { - public: +// A font-face for a font-family. It has an internal state, which lets the font +// list know whether or not the font has already been requested, and if so, +// whether or not it was available. |font_| will only be non-NULL in the case +// where |state_| is set to |kLoadedState|. +struct FontFace { enum State { kUnrequestedState, kLoadingWithTimerActiveState, @@ -47,27 +47,29 @@ kUnavailableState, kDuplicateState, }; + State state = kUnrequestedState; + const FontFaceStyleSet::Entry* entry = nullptr; + // The render_tree::Font obtained via the font cache using |family_name_| in + // font list font, along with |style_| and |size_| from the containing font + // list, and the unicode range needed for the requested character. It is only + // non-NULL in the case where |state_| is set to |kLoadedState|. + scoped_refptr<render_tree::Font> font; +}; + +// A specific font family within a font list. A family may have more than one +// FontFace if the FontFaces have different unicode ranges specified. +struct FontListFont { explicit FontListFont(const std::string& family_name) - : family_name_(family_name), state_(kUnrequestedState) {} + : family_name(family_name) {} - const std::string& family_name() const { return family_name_; } - - State state() const { return state_; } - void set_state(State state) { state_ = state; } - - const scoped_refptr<render_tree::Font>& font() const { return font_; } - void set_font(const scoped_refptr<render_tree::Font>& font) { font_ = font; } - - private: - std::string family_name_; - State state_; + std::string family_name; // The render_tree::Font obtained via the font cache using |family_name_| in // font list font, along with |style_| and |size_| from the containing font // list. It is only non-NULL in the case where |state_| is set to // |kLoadedState|. - scoped_refptr<render_tree::Font> font_; + std::vector<FontFace*> faces; }; // The key used for maps with a |FontList| value. It is also used for @@ -178,6 +180,8 @@ int32 utf32_character, render_tree::GlyphIndex* glyph_index) override; private: + ~FontList() override; + const scoped_refptr<render_tree::Font>& GetFallbackCharacterFont( int32 utf32_character, render_tree::GlyphIndex* glyph_index); @@ -191,9 +195,10 @@ const scoped_refptr<render_tree::Font>& GetPrimaryFont(); // Request a font from the font cache and update its state depending on the - // results of the request. If the font is successfully set, then both its - // |font_| and |character_map_| are non-NULL after this call. - void RequestFont(size_t index); + // results of the request. If the font is successfully set, then its |font_| + // is non-NULL after this call. + void RequestFont(size_t index, FontFace* face); + // Lazily generates the ellipsis font and ellipsis width. If it is already // generated then it immediately returns.
diff --git a/src/cobalt/dom/html_style_element.cc b/src/cobalt/dom/html_style_element.cc index a83be61..aa80cde 100644 --- a/src/cobalt/dom/html_style_element.cc +++ b/src/cobalt/dom/html_style_element.cc
@@ -79,9 +79,8 @@ base::Optional<std::string> content = text_content(); const std::string& text = content.value_or(base::EmptyString()); - if (bypass_csp || text.empty() || - csp_delegate->AllowInline(CspDelegate::kStyle, inline_style_location_, - text)) { + if (bypass_csp || csp_delegate->AllowInline(CspDelegate::kStyle, + inline_style_location_, text)) { scoped_refptr<cssom::CSSStyleSheet> css_style_sheet = document->html_element_context()->css_parser()->ParseStyleSheet( text, inline_style_location_);
diff --git a/src/cobalt/dom/performance.cc b/src/cobalt/dom/performance.cc index 8154cc1..390fc55 100644 --- a/src/cobalt/dom/performance.cc +++ b/src/cobalt/dom/performance.cc
@@ -87,7 +87,10 @@ base::DefaultClock::GetInstance(), tick_clock_); lifecycle_timing_ = base::MakeRefCounted<PerformanceLifecycleTiming>( "lifecycle timing", time_origin()); + // Queue lifecycle timing. QueuePerformanceEntry(lifecycle_timing_); + // Add lifecycle timing to the performance entry buffer. + performance_entry_buffer_.push_back(lifecycle_timing_); } // static
diff --git a/src/cobalt/dom/performance_high_resolution_time.h b/src/cobalt/dom/performance_high_resolution_time.h index 0684e41..4388b2e 100644 --- a/src/cobalt/dom/performance_high_resolution_time.h +++ b/src/cobalt/dom/performance_high_resolution_time.h
@@ -43,19 +43,26 @@ // https://w3c.github.io/hr-time/#clock-resolution inline DOMHighResTimeStamp ClampTimeStampMinimumResolution( base::TimeTicks ticks, - int64_t min_resolution_in_microseconds) { - int64_t microseconds = ticks.ToInternalValue(); - return base::TimeDelta::FromMicroseconds(microseconds - - (microseconds % min_resolution_in_microseconds)).InMillisecondsF(); + int64_t min_resolution_in_microseconds) { + int64_t microseconds = ticks.since_origin().InMicroseconds(); + return base::TimeDelta::FromMicroseconds(microseconds - + (microseconds % min_resolution_in_microseconds)).InMillisecondsF(); } // Clamp customized minimum clock resolution in milliseconds. // https://w3c.github.io/hr-time/#clock-resolution inline DOMHighResTimeStamp ClampTimeStampMinimumResolution(base::TimeDelta delta, int64_t min_resolution_in_microseconds) { - int64_t microseconds = delta.InMicroseconds(); - return base::TimeDelta::FromMicroseconds(microseconds - - (microseconds % min_resolution_in_microseconds)).InMillisecondsF(); + int64_t microseconds = delta.InMicroseconds(); + return base::TimeDelta::FromMicroseconds(microseconds - + (microseconds % min_resolution_in_microseconds)).InMillisecondsF(); +} + +inline DOMHighResTimeStamp + ClampTimeStampMinimumResolution(DOMHighResTimeStamp time_delta, + int64_t min_resolution_in_microseconds) { + return time_delta - + (static_cast<int64_t>(time_delta) % min_resolution_in_microseconds); } } // namespace dom
diff --git a/src/cobalt/dom/performance_lifecycle_timing.cc b/src/cobalt/dom/performance_lifecycle_timing.cc index f09fee0..d81ebb0 100644 --- a/src/cobalt/dom/performance_lifecycle_timing.cc +++ b/src/cobalt/dom/performance_lifecycle_timing.cc
@@ -44,11 +44,8 @@ const DOMHighResTimeStamp time_origin, SbTimeMonotonic monotonic_time) { SbTimeMonotonic time_delta = SbTimeGetNow() - SbTimeGetMonotonicNow(); base::Time base_time = base::Time::FromSbTime(time_delta + monotonic_time); - base::TimeTicks time_ticks = - base::TimeTicks::FromInternalValue(static_cast<int64_t>( - base_time.ToJsTime())); - return ClampTimeStampMinimumResolution(time_ticks, - Performance::kPerformanceTimerMinResolutionInMicroseconds) - time_origin; + return ClampTimeStampMinimumResolution(base_time.ToJsTime() - time_origin, + Performance::kPerformanceTimerMinResolutionInMicroseconds); } } // namespace
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc index 52fe0bb..34e0c86 100644 --- a/src/cobalt/dom/window.cc +++ b/src/cobalt/dom/window.cc
@@ -164,8 +164,8 @@ captions, script_value_factory)), ALLOW_THIS_IN_INITIALIZER_LIST( relay_on_load_event_(new RelayLoadEvent(this))), - ALLOW_THIS_IN_INITIALIZER_LIST( - window_timers_(new WindowTimers(this, debugger_hooks()))), + ALLOW_THIS_IN_INITIALIZER_LIST(window_timers_( + new WindowTimers(this, debugger_hooks(), initial_application_state))), ALLOW_THIS_IN_INITIALIZER_LIST(animation_frame_request_callback_list_( new AnimationFrameRequestCallbackList(this, debugger_hooks()))), crypto_(new Crypto()), @@ -540,6 +540,7 @@ state); if (timestamp == 0) return; performance_->SetApplicationState(state, timestamp); + window_timers_->SetApplicationState(state); } bool Window::ReportScriptError(const script::ErrorReport& error_report) {
diff --git a/src/cobalt/dom/window_timers.cc b/src/cobalt/dom/window_timers.cc index 283208c..3b5c079 100644 --- a/src/cobalt/dom/window_timers.cc +++ b/src/cobalt/dom/window_timers.cc
@@ -19,15 +19,18 @@ #include "base/bind.h" #include "base/bind_helpers.h" +#include "base/threading/sequenced_task_runner_handle.h" #include "base/trace_event/trace_event.h" +#include "cobalt/base/application_state.h" +#include "cobalt/base/polymorphic_downcast.h" #include "cobalt/dom/global_stats.h" #include "nb/memory_scope.h" namespace cobalt { namespace dom { -int WindowTimers::SetTimeout(const TimerCallbackArg& handler, int timeout) { - TRACK_MEMORY_SCOPE("DOM"); +int WindowTimers::TryAddNewTimer(Timer::TimerType type, + const TimerCallbackArg& handler, int timeout) { int handle = GetFreeTimerHandle(); DCHECK(handle); @@ -37,15 +40,16 @@ } if (callbacks_active_) { - auto* timer = new base::OneShotTimer(); - timer->Start(FROM_HERE, base::TimeDelta::FromMilliseconds(timeout), - base::Bind(&WindowTimers::RunTimerCallback, - base::Unretained(this), handle)); - timers_[handle] = new TimerInfo( - owner_, std::unique_ptr<base::internal::TimerBase>(timer), handler); + auto* timer = new Timer(type, owner_, handler, timeout, handle, this); + if (application_state_ != base::kApplicationStateFrozen) { + timer->StartOrResume(); + } + timers_[handle] = timer; debugger_hooks_.AsyncTaskScheduled( - timers_[handle], "SetTimeout", - base::DebuggerHooks::AsyncTaskFrequency::kOneshot); + timers_[handle], type == Timer::kOneShot ? "SetTimeout" : "SetInterval", + type == Timer::kOneShot + ? base::DebuggerHooks::AsyncTaskFrequency::kOneshot + : base::DebuggerHooks::AsyncTaskFrequency::kRecurring); } else { timers_[handle] = nullptr; } @@ -53,32 +57,16 @@ return handle; } +int WindowTimers::SetTimeout(const TimerCallbackArg& handler, int timeout) { + TRACK_MEMORY_SCOPE("DOM"); + return TryAddNewTimer(Timer::kOneShot, handler, timeout); +} + void WindowTimers::ClearTimeout(int handle) { timers_.erase(handle); } int WindowTimers::SetInterval(const TimerCallbackArg& handler, int timeout) { - int handle = GetFreeTimerHandle(); - DCHECK(handle); - - if (handle == 0) { // unable to get a free timer handle - // avoid accidentally overwriting existing timers - return 0; - } - - if (callbacks_active_) { - auto* timer(new base::RepeatingTimer()); - timer->Start(FROM_HERE, base::TimeDelta::FromMilliseconds(timeout), - base::Bind(&WindowTimers::RunTimerCallback, - base::Unretained(this), handle)); - timers_[handle] = new TimerInfo( - owner_, std::unique_ptr<base::internal::TimerBase>(timer), handler); - debugger_hooks_.AsyncTaskScheduled( - timers_[handle], "SetInterval", - base::DebuggerHooks::AsyncTaskFrequency::kRecurring); - } else { - timers_[handle] = nullptr; - } - - return handle; + TRACK_MEMORY_SCOPE("DOM"); + return TryAddNewTimer(Timer::kRepeating, handler, timeout); } void WindowTimers::ClearInterval(int handle) { @@ -138,7 +126,7 @@ { // Keep a |TimerInfo| reference, so it won't be released when running the // callback. - scoped_refptr<TimerInfo> timer_info = timer->second; + scoped_refptr<Timer> timer_info = timer->second; base::ScopedAsyncTask async_task(debugger_hooks_, timer_info); timer_info->callback_reference().value().Run(); } @@ -157,5 +145,92 @@ GlobalStats::GetInstance()->StopJavaScriptEvent(); } +void WindowTimers::SetApplicationState(base::ApplicationState state) { + switch (state) { + case base::kApplicationStateFrozen: + DCHECK_EQ(application_state_, base::kApplicationStateConcealed); + for (auto timer : timers_) { + timer.second->Pause(); + } + break; + case base::kApplicationStateConcealed: + if (application_state_ == base::kApplicationStateFrozen) { + for (auto timer : timers_) { + timer.second->StartOrResume(); + } + } + break; + case base::kApplicationStateStopped: + case base::kApplicationStateBlurred: + case base::kApplicationStateStarted: + break; + } + application_state_ = state; +} + +WindowTimers::Timer::Timer(TimerType type, script::Wrappable* const owner, + const TimerCallbackArg& callback, int timeout, + int handle, WindowTimers* window_timers) + : type_(type), + callback_(owner, callback), + timeout_(timeout), + handle_(handle), + window_timers_(window_timers) {} + +void WindowTimers::Timer::Pause() { + if (timer_) { + // The desired runtime is preserved here to determine whether the timer + // should fire immediately when resuming. + desired_run_time_ = timer_->desired_run_time(); + timer_.reset(); + } +} + +void WindowTimers::Timer::StartOrResume() { + if (timer_ != nullptr) return; + switch (type_) { + case kOneShot: + if (desired_run_time_) { + // Adjust the new timeout for the time spent while paused. + auto now = base::TimeTicks::Now(); + if (desired_run_time_ <= now) { + // The previous desired run time was in the past or is right now, so + // it should fire immediately. + timeout_ = 0; + } else { + // Set the timeout to keep the same desired run time. Note that since + // the timer uses the timeout that we request to set a new desired + // run time from the clock, the resumed timer will likely fire + // slightly later. + base::TimeDelta time_delta(desired_run_time_.value() - now); + timeout_ = static_cast<int>(time_delta.InMilliseconds()); + } + } + timer_ = CreateAndStart<base::OneShotTimer>(); + break; + case kRepeating: + timer_ = CreateAndStart<base::RepeatingTimer>(); + if (timeout_ && desired_run_time_ && + desired_run_time_ < base::TimeTicks::Now()) { + // The timer was paused and the desired run time is in the past. + // Call the callback once before continuing the repeating timer. + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&WindowTimers::RunTimerCallback, + base::Unretained(window_timers_), handle_)); + } + break; + } +} + +template <class TimerClass> +std::unique_ptr<base::internal::TimerBase> +WindowTimers::Timer::CreateAndStart() { + auto* timer = new TimerClass(); + timer->Start(FROM_HERE, base::TimeDelta::FromMilliseconds(timeout_), + base::Bind(&WindowTimers::RunTimerCallback, + base::Unretained(window_timers_), handle_)); + return std::unique_ptr<base::internal::TimerBase>(timer); +} + } // namespace dom } // namespace cobalt
diff --git a/src/cobalt/dom/window_timers.h b/src/cobalt/dom/window_timers.h index 5d4442b..42dcf56 100644 --- a/src/cobalt/dom/window_timers.h +++ b/src/cobalt/dom/window_timers.h
@@ -16,10 +16,15 @@ #define COBALT_DOM_WINDOW_TIMERS_H_ #include <memory> +#include <utility> +#include <vector> #include "base/containers/hash_tables.h" #include "base/memory/ref_counted.h" +#include "base/optional.h" +#include "base/time/time.h" #include "base/timer/timer.h" +#include "cobalt/base/application_state.h" #include "cobalt/base/debugger_hooks.h" #include "cobalt/script/callback_function.h" #include "cobalt/script/script_value.h" @@ -33,10 +38,11 @@ typedef script::CallbackFunction<void()> TimerCallback; typedef script::ScriptValue<TimerCallback> TimerCallbackArg; explicit WindowTimers(script::Wrappable* const owner, - const base::DebuggerHooks& debugger_hooks) - : current_timer_index_(0), - owner_(owner), - debugger_hooks_(debugger_hooks) {} + const base::DebuggerHooks& debugger_hooks, + base::ApplicationState application_state) + : owner_(owner), + debugger_hooks_(debugger_hooks), + application_state_(application_state) {} ~WindowTimers() {} int SetTimeout(const TimerCallbackArg& handler, int timeout); @@ -55,25 +61,51 @@ // event queue without adding more on to the end of it. void DisableCallbacks(); + void SetApplicationState(base::ApplicationState state); + private: - class TimerInfo : public base::RefCounted<TimerInfo> { + class Timer : public base::RefCounted<Timer> { public: - TimerInfo(script::Wrappable* const owner, - std::unique_ptr<base::internal::TimerBase> timer, - const TimerCallbackArg& callback) - : timer_(std::move(timer)), callback_(owner, callback) {} + enum TimerType { kOneShot, kRepeating }; + + Timer(TimerType type, script::Wrappable* const owner, + const TimerCallbackArg& callback, int timeout, int handle, + WindowTimers* window_timers); base::internal::TimerBase* timer() { return timer_.get(); } TimerCallbackArg::Reference& callback_reference() { return callback_; } + // Pause this timer. The timer will not fire when paused. + void Pause(); + // Start or Resume this timer. If the timer was paused and the desired run + // time is in the past, it will fire immediately. + void StartOrResume(); + private: - ~TimerInfo() {} + ~Timer() {} + + // Create and start a timer of the specified TimerClass type. + template <class TimerClass> + std::unique_ptr<base::internal::TimerBase> CreateAndStart(); + + TimerType type_; std::unique_ptr<base::internal::TimerBase> timer_; TimerCallbackArg::Reference callback_; + int timeout_; + int handle_; + WindowTimers* window_timers_; - friend class base::RefCounted<TimerInfo>; + // Store the desired run tim of a paused timer. + base::Optional<base::TimeTicks> desired_run_time_; + + friend class base::RefCounted<Timer>; }; - typedef base::hash_map<int, scoped_refptr<TimerInfo> > Timers; + typedef base::hash_map<int, scoped_refptr<Timer> > Timers; + + // Try to add a new timer of the given type, return the handle or 0 when + // failed. + int TryAddNewTimer(Timer::TimerType type, const TimerCallbackArg& handler, + int timeout); // Returns a positive integer timer handle that hasn't been assigned, or 0 // if none can be found. @@ -84,7 +116,7 @@ void RunTimerCallback(int handle); Timers timers_; - int current_timer_index_; + int current_timer_index_ = 0; script::Wrappable* const owner_; const base::DebuggerHooks& debugger_hooks_; @@ -92,6 +124,8 @@ // is fired as we are waiting for it to drain. bool callbacks_active_ = true; + base::ApplicationState application_state_; + DISALLOW_COPY_AND_ASSIGN(WindowTimers); };
diff --git a/src/cobalt/h5vcc/h5vcc_platform_service.cc b/src/cobalt/h5vcc/h5vcc_platform_service.cc index f744176..2ee9d73 100644 --- a/src/cobalt/h5vcc/h5vcc_platform_service.cc +++ b/src/cobalt/h5vcc/h5vcc_platform_service.cc
@@ -99,9 +99,21 @@ } uint64_t output_length = 0; bool invalid_state = 0; + + // Make sure the data pointer is not null as the platform service may not + // handle that properly. + void* data_ptr = data->Data(); + size_t data_length = data->ByteLength(); + if (data_ptr == nullptr) { + // If the data length is 0, then it's okay to point to a static array. + DCHECK(data_length == 0); + static int null_data = 0; + data_ptr = &null_data; + data_length = 0; + } + void* output_data = platform_service_api_->Send( - ext_service_, data->Data(), data->ByteLength(), &output_length, - &invalid_state); + ext_service_, data_ptr, data_length, &output_length, &invalid_state); if (invalid_state) { dom::DOMException::Raise(dom::DOMException::kInvalidStateErr, "Service unable to accept data currently.",
diff --git a/src/cobalt/layout/box.cc b/src/cobalt/layout/box.cc index 3254864..a42730d 100644 --- a/src/cobalt/layout/box.cc +++ b/src/cobalt/layout/box.cc
@@ -567,6 +567,11 @@ return margin_top() + border_top_width(); } +void Box::SetPaddingInsets(LayoutUnit left, LayoutUnit top, LayoutUnit right, + LayoutUnit bottom) { + padding_insets_.SetInsets(left, top, right, bottom); +} + RectLayoutUnit Box::GetContentBoxFromMarginBox() const { return RectLayoutUnit(GetContentBoxLeftEdgeOffsetFromMarginBox(), GetContentBoxTopEdgeOffsetFromMarginBox(), width(), @@ -982,20 +987,11 @@ } } -bool IsBorderStyleNoneOrHidden( - const scoped_refptr<cssom::PropertyValue>& border_style) { - if (border_style == cssom::KeywordValue::GetNone() || - border_style == cssom::KeywordValue::GetHidden()) { - return true; - } - return false; -} - render_tree::BorderStyle GetRenderTreeBorderStyle( const scoped_refptr<cssom::PropertyValue>& border_style) { render_tree::BorderStyle render_tree_border_style = render_tree::kBorderStyleNone; - if (!IsBorderStyleNoneOrHidden(border_style)) { + if (!Box::IsBorderStyleNoneOrHidden(border_style)) { DCHECK_EQ(border_style, cssom::KeywordValue::GetSolid()); render_tree_border_style = render_tree::kBorderStyleSolid; } @@ -1003,25 +999,26 @@ return render_tree_border_style; } -Border CreateBorderFromStyle( - const scoped_refptr<const cssom::CSSComputedStyleData>& style) { +Border CreateBorderFromUsedStyle( + const scoped_refptr<const cssom::CSSComputedStyleData>& style, + InsetsLayoutUnit border_insets) { render_tree::BorderSide left( - GetUsedNonNegativeLength(style->border_left_width()).toFloat(), + border_insets.left().toFloat(), GetRenderTreeBorderStyle(style->border_left_style()), GetUsedColor(style->border_left_color())); render_tree::BorderSide right( - GetUsedNonNegativeLength(style->border_right_width()).toFloat(), + border_insets.right().toFloat(), GetRenderTreeBorderStyle(style->border_right_style()), GetUsedColor(style->border_right_color())); render_tree::BorderSide top( - GetUsedNonNegativeLength(style->border_top_width()).toFloat(), + border_insets.top().toFloat(), GetRenderTreeBorderStyle(style->border_top_style()), GetUsedColor(style->border_top_color())); render_tree::BorderSide bottom( - GetUsedNonNegativeLength(style->border_bottom_width()).toFloat(), + border_insets.bottom().toFloat(), GetRenderTreeBorderStyle(style->border_bottom_style()), GetUsedColor(style->border_bottom_color())); @@ -1033,38 +1030,35 @@ const scoped_refptr<cssom::MutableCSSComputedStyleData>& destination_style) { // NOTE: Properties set by PopulateBaseStyleForBorderNode() should match the - // properties used by SetupBorderNodeFromStyle(). + // properties used by SetupBorderNodeFromUsedStyle(), except for the border + // width which is not used to determine the render tree border. // Left - destination_style->set_border_left_width(source_style->border_left_width()); destination_style->set_border_left_style(source_style->border_left_style()); destination_style->set_border_left_color(source_style->border_left_color()); // Right - destination_style->set_border_right_width(source_style->border_right_width()); destination_style->set_border_right_style(source_style->border_right_style()); destination_style->set_border_right_color(source_style->border_right_color()); // Top - destination_style->set_border_top_width(source_style->border_top_width()); destination_style->set_border_top_style(source_style->border_top_style()); destination_style->set_border_top_color(source_style->border_top_color()); // Bottom - destination_style->set_border_bottom_width( - source_style->border_bottom_width()); destination_style->set_border_bottom_style( source_style->border_bottom_style()); destination_style->set_border_bottom_color( source_style->border_bottom_color()); } -void SetupBorderNodeFromStyle( +void SetupBorderNodeFromUsedStyle( const base::Optional<RoundedCorners>& rounded_corners, + const InsetsLayoutUnit border_insets, const scoped_refptr<const cssom::CSSComputedStyleData>& style, RectNode::Builder* rect_node_builder) { - rect_node_builder->border = - std::unique_ptr<Border>(new Border(CreateBorderFromStyle(style))); + rect_node_builder->border = std::unique_ptr<Border>( + new Border(CreateBorderFromUsedStyle(style, border_insets))); if (rounded_corners) { rect_node_builder->rounded_corners = @@ -1358,7 +1352,7 @@ IsBorderStyleNoneOrHidden(computed_style()->border_top_style()) && IsBorderStyleNoneOrHidden(computed_style()->border_right_style()) && IsBorderStyleNoneOrHidden(computed_style()->border_bottom_style())) { - border_insets_ = InsetsLayoutUnit(); + ResetBorderInsets(); return; } @@ -1631,18 +1625,25 @@ math::RectF rect(GetClampedBorderBoxSize()); RectNode::Builder rect_node_builder(rect); - SetupBorderNodeFromStyle(rounded_corners, computed_style(), - &rect_node_builder); + + // When an inline box is split, margins, borders, and padding + // have no visual effect where the split occurs. (or at any split, when there + // are several). + // https://www.w3.org/TR/CSS21/visuren.html#inline-formatting + + SetupBorderNodeFromUsedStyle(rounded_corners, border_insets_, + computed_style(), &rect_node_builder); scoped_refptr<RectNode> border_node( new RectNode(std::move(rect_node_builder))); border_node_builder->AddChild(border_node); if (has_animated_border) { - AddAnimations<RectNode>( - base::Bind(&PopulateBaseStyleForBorderNode), - base::Bind(&SetupBorderNodeFromStyle, rounded_corners), - *css_computed_style_declaration(), border_node, animate_node_builder); + AddAnimations<RectNode>(base::Bind(&PopulateBaseStyleForBorderNode), + base::Bind(&SetupBorderNodeFromUsedStyle, + rounded_corners, border_insets_), + *css_computed_style_declaration(), border_node, + animate_node_builder); } } @@ -2116,6 +2117,20 @@ } } +void Box::SetBorderInsets(LayoutUnit left, LayoutUnit top, LayoutUnit right, + LayoutUnit bottom) { + border_insets_.SetInsets(left, top, right, bottom); +} + +bool Box::IsBorderStyleNoneOrHidden( + const scoped_refptr<cssom::PropertyValue>& border_style) { + if (border_style == cssom::KeywordValue::GetNone() || + border_style == cssom::KeywordValue::GetHidden()) { + return true; + } + return false; +} + bool Box::ApplyTransformActionToCoordinate(TransformAction action, math::Vector2dF* coordinate) const { std::vector<math::Vector2dF> coordinate_vector;
diff --git a/src/cobalt/layout/box.h b/src/cobalt/layout/box.h index 06a2790..a36935a 100644 --- a/src/cobalt/layout/box.h +++ b/src/cobalt/layout/box.h
@@ -403,6 +403,8 @@ bool transform_forms_root) const; Vector2dLayoutUnit GetBorderBoxOffsetFromMarginBox() const; + void ResetBorderInsets() { border_insets_ = InsetsLayoutUnit(); } + // Padding box. LayoutUnit padding_left() const { return padding_insets_.left(); } LayoutUnit padding_top() const { return padding_insets_.top(); } @@ -419,6 +421,11 @@ LayoutUnit GetPaddingBoxLeftEdgeOffsetFromMarginBox() const; LayoutUnit GetPaddingBoxTopEdgeOffsetFromMarginBox() const; + // Set padding insets in InlineContainerBox UpdatePaddings to an empty + // LayoutUnit or the computed_style value using is_split_on_*_. + void SetPaddingInsets(LayoutUnit left, LayoutUnit top, LayoutUnit right, + LayoutUnit bottom); + // Content box. LayoutUnit width() const { return content_size_.width(); } LayoutUnit height() const { return content_size_.height(); } @@ -729,6 +736,9 @@ base::Optional<LayoutUnit> collapsed_margin_bottom_; base::Optional<LayoutUnit> collapsed_empty_margin_; + static bool IsBorderStyleNoneOrHidden( + const scoped_refptr<cssom::PropertyValue>& border_style); + protected: UsedStyleProvider* used_style_provider() const { return used_style_provider_; @@ -807,6 +817,11 @@ const base::Optional<LayoutUnit>& possibly_overconstrained_margin_left, const base::Optional<LayoutUnit>& possibly_overconstrained_margin_right); + // Set border insets in InlineContainerBox UpdateBorders to an empty + // LayoutUnit or the computed_style value using is_split_on_*_. + void SetBorderInsets(LayoutUnit left, LayoutUnit top, LayoutUnit right, + LayoutUnit bottom); + private: struct CachedRenderTreeNodeInfo { explicit CachedRenderTreeNodeInfo(const math::Vector2dF& offset) @@ -817,9 +832,9 @@ }; // Updates used values of "border" properties. - void UpdateBorders(); + virtual void UpdateBorders(); // Updates used values of "padding" properties. - void UpdatePaddings(const LayoutParams& layout_params); + virtual void UpdatePaddings(const LayoutParams& layout_params); // Computes the normalized "outer" rounded corners (if there are any) from the // border radii.
diff --git a/src/cobalt/layout/box_generator.cc b/src/cobalt/layout/box_generator.cc index ad09c7d..6a09ed1 100644 --- a/src/cobalt/layout/box_generator.cc +++ b/src/cobalt/layout/box_generator.cc
@@ -626,7 +626,7 @@ container_box_ = base::WrapRefCounted(new InlineContainerBox( css_computed_style_declaration_, context_->used_style_provider, - context_->layout_stat_tracker)); + context_->layout_stat_tracker, (*paragraph_)->base_direction())); break; // Generate an inline-level block container box. The inside of // an inline-block is formatted as a block box, and the element itself
diff --git a/src/cobalt/layout/inline_container_box.cc b/src/cobalt/layout/inline_container_box.cc index e59dd7f..2a9bc8d 100644 --- a/src/cobalt/layout/inline_container_box.cc +++ b/src/cobalt/layout/inline_container_box.cc
@@ -27,7 +27,7 @@ const scoped_refptr<cssom::CSSComputedStyleDeclaration>& css_computed_style_declaration, UsedStyleProvider* used_style_provider, - LayoutStatTracker* layout_stat_tracker) + LayoutStatTracker* layout_stat_tracker, BaseDirection base_direction) : ContainerBox(css_computed_style_declaration, used_style_provider, layout_stat_tracker), should_collapse_leading_white_space_(false), @@ -41,7 +41,10 @@ css_computed_style_declaration->data()->font_family(), css_computed_style_declaration->data()->font_size(), css_computed_style_declaration->data()->font_style(), - css_computed_style_declaration->data()->font_weight())) {} + css_computed_style_declaration->data()->font_weight())), + is_split_on_left_(false), + is_split_on_right_(false), + base_direction_(base_direction) {} InlineContainerBox::~InlineContainerBox() {} @@ -55,7 +58,7 @@ // container box. return false; } - // Fall through if out-of-flow. + // Fall through if out-of-flow. case kInlineLevel: // If the inline container box already contains a line break, then no @@ -74,13 +77,19 @@ } scoped_refptr<ContainerBox> InlineContainerBox::TrySplitAtEnd() { - scoped_refptr<InlineContainerBox> box_after_split( - new InlineContainerBox(css_computed_style_declaration(), - used_style_provider(), layout_stat_tracker())); - // When an inline box is split, margins, borders, and padding have no visual - // effect where the split occurs. - // https://www.w3.org/TR/CSS21/visuren.html#inline-formatting - // TODO: Implement the above comment. + scoped_refptr<InlineContainerBox> box_after_split(new InlineContainerBox( + css_computed_style_declaration(), used_style_provider(), + layout_stat_tracker(), base_direction_)); + // Set the state of where the sibling boxes are split using + // base_direction_ to determine the correct way to split the boxes for + // dir : rtl or ltr. + if (base_direction_ == kLeftToRightBaseDirection) { + is_split_on_right_ = true; + box_after_split->SetIsSplitOnLeft(true); + } else { + is_split_on_left_ = true; + box_after_split->SetIsSplitOnRight(true); + } return box_after_split; } @@ -93,6 +102,14 @@ return inline_top_margin_; } +void InlineContainerBox::SetIsSplitOnLeft(bool is_split_on_left) { + is_split_on_left_ = is_split_on_left; +} + +void InlineContainerBox::SetIsSplitOnRight(bool is_split_on_right) { + is_split_on_right_ = is_split_on_right; +} + void InlineContainerBox::UpdateContentSizeAndMargins( const LayoutParams& layout_params) { // Lay out child boxes as one line without width constraints and white space @@ -127,16 +144,36 @@ // https://www.w3.org/TR/CSS21/visuren.html#inline-formatting set_width(line_box.shrink_to_fit_width()); - base::Optional<LayoutUnit> maybe_margin_left = GetUsedMarginLeftIfNotAuto( - computed_style(), layout_params.containing_block_size); - base::Optional<LayoutUnit> maybe_margin_right = GetUsedMarginRightIfNotAuto( - computed_style(), layout_params.containing_block_size); + if (is_split_on_left_) { + // When an inline box is split, margins, borders, and padding + // have no visual effect where the split occurs. (or at any split, when + // there are several). + // https://www.w3.org/TR/CSS21/visuren.html#inline-formatting + set_margin_left(LayoutUnit()); + } else { + // A computed value of "auto" for "margin-left" or "margin-right" becomes + // a used value of "0". + // https://www.w3.org/TR/CSS21/visudet.html#inline-width + base::Optional<LayoutUnit> maybe_margin_left = GetUsedMarginLeftIfNotAuto( + computed_style(), layout_params.containing_block_size); + set_margin_left(maybe_margin_left.value_or(LayoutUnit())); + } - // A computed value of "auto" for "margin-left" or "margin-right" becomes - // a used value of "0". - // https://www.w3.org/TR/CSS21/visudet.html#inline-width - set_margin_left(maybe_margin_left.value_or(LayoutUnit())); - set_margin_right(maybe_margin_right.value_or(LayoutUnit())); + if (is_split_on_right_) { + // When an inline box is split, margins, borders, and padding + // have no visual effect where the split occurs. (or at any split, when + // there are several). + // https://www.w3.org/TR/CSS21/visuren.html#inline-formatting + set_margin_right(LayoutUnit()); + } else { + // A computed value of "auto" for "margin-left" or "margin-right" becomes + // a used value of "0". + // https://www.w3.org/TR/CSS21/visudet.html#inline-width + base::Optional<LayoutUnit> maybe_margin_right = + GetUsedMarginRightIfNotAuto(computed_style(), + layout_params.containing_block_size); + set_margin_right(maybe_margin_right.value_or(LayoutUnit())); + } } // The "height" property does not apply. The height of the content area should @@ -172,6 +209,44 @@ baseline_offset_from_margin_box_top_ = line_box.baseline_offset_from_top(); } +void InlineContainerBox::UpdateBorders() { + if (IsBorderStyleNoneOrHidden(computed_style()->border_left_style()) && + IsBorderStyleNoneOrHidden(computed_style()->border_top_style()) && + IsBorderStyleNoneOrHidden(computed_style()->border_right_style()) && + IsBorderStyleNoneOrHidden(computed_style()->border_bottom_style())) { + ResetBorderInsets(); + return; + } + // When an inline box is split, margins, borders, and padding + // have no visual effect where the split occurs. (or at any split, when there + // are several). + // https://www.w3.org/TR/CSS21/visuren.html#inline-formatting + SetBorderInsets( + is_split_on_left_ ? LayoutUnit() : GetUsedBorderLeft(computed_style()), + GetUsedBorderTop(computed_style()), + is_split_on_right_ ? LayoutUnit() : GetUsedBorderRight(computed_style()), + GetUsedBorderBottom(computed_style())); +} + +void InlineContainerBox::UpdatePaddings(const LayoutParams& layout_params) { + // When an inline box is split, margins, borders, and padding + // have no visual effect where the split occurs. (or at any split, when there + // are several). + // https://www.w3.org/TR/CSS21/visuren.html#inline-formatting + SetPaddingInsets( + is_split_on_left_ + ? LayoutUnit() + : GetUsedPaddingLeft(computed_style(), + layout_params.containing_block_size), + GetUsedPaddingTop(computed_style(), layout_params.containing_block_size), + is_split_on_right_ + ? LayoutUnit() + : GetUsedPaddingRight(computed_style(), + layout_params.containing_block_size), + GetUsedPaddingBottom(computed_style(), + layout_params.containing_block_size)); +} + WrapResult InlineContainerBox::TryWrapAt( WrapAtPolicy wrap_at_policy, WrapOpportunityPolicy wrap_opportunity_policy, bool is_line_existence_justified, LayoutUnit available_width, @@ -632,14 +707,21 @@ void InlineContainerBox::SplitAtIterator( Boxes::const_iterator child_split_iterator) { - // TODO: When an inline box is split, margins, borders, and padding - // have no visual effect where the split occurs. - // https://www.w3.org/TR/CSS21/visuren.html#inline-formatting - // Move the children after the split into a new box. - scoped_refptr<InlineContainerBox> box_after_split( - new InlineContainerBox(css_computed_style_declaration(), - used_style_provider(), layout_stat_tracker())); + scoped_refptr<InlineContainerBox> box_after_split(new InlineContainerBox( + css_computed_style_declaration(), used_style_provider(), + layout_stat_tracker(), base_direction_)); + + // Set the state of where the sibling boxes are split using + // base_direction_ to determine the correct way to split the boxes for + // dir : rtl or ltr. + if (base_direction_ == kLeftToRightBaseDirection) { + is_split_on_right_ = true; + box_after_split->SetIsSplitOnLeft(true); + } else { + is_split_on_left_ = true; + box_after_split->SetIsSplitOnRight(true); + } // Update the split sibling links. box_after_split->split_sibling_ = split_sibling_;
diff --git a/src/cobalt/layout/inline_container_box.h b/src/cobalt/layout/inline_container_box.h index 1c15c83..dbd3df7 100644 --- a/src/cobalt/layout/inline_container_box.h +++ b/src/cobalt/layout/inline_container_box.h
@@ -39,7 +39,8 @@ InlineContainerBox(const scoped_refptr<cssom::CSSComputedStyleDeclaration>& css_computed_style_declaration, UsedStyleProvider* used_style_provider, - LayoutStatTracker* layout_stat_tracker); + LayoutStatTracker* layout_stat_tracker, + BaseDirection base_direction); ~InlineContainerBox() override; // From |Box|. @@ -47,6 +48,10 @@ void UpdateContentSizeAndMargins(const LayoutParams& layout_params) override; + void UpdateBorders() override; + + void UpdatePaddings(const LayoutParams& layout_params) override; + WrapResult TryWrapAt(WrapAtPolicy wrap_at_policy, WrapOpportunityPolicy wrap_opportunity_policy, bool is_line_existence_justified, @@ -76,6 +81,8 @@ LayoutUnit GetBaselineOffsetFromTopMarginEdge() const override; LayoutUnit GetInlineLevelBoxHeight() const override; LayoutUnit GetInlineLevelTopMargin() const override; + void SetIsSplitOnLeft(bool is_split_on_left); + void SetIsSplitOnRight(bool is_split_on_right); // From |ContainerBox|. bool TryAddChild(const scoped_refptr<Box>& child_box) override; @@ -136,6 +143,11 @@ // A font used for text width and line height calculations. const scoped_refptr<dom::FontList> used_font_; + bool is_split_on_left_; + bool is_split_on_right_; + + BaseDirection base_direction_; + // A reference to the next inline container box in a linked list of inline // container boxes produced from splits of the initial text box. This enables // HTMLElement to retain access to all of its layout boxes after they are
diff --git a/src/cobalt/layout/used_style.cc b/src/cobalt/layout/used_style.cc index cd89a85..8ab844f 100644 --- a/src/cobalt/layout/used_style.cc +++ b/src/cobalt/layout/used_style.cc
@@ -722,7 +722,7 @@ // The specifications indicate that if positions are not specified for color // stops, then they should be filled in automatically by evenly spacing them -// between the two neighbooring color stops that DO have positions specified. +// between the two neighboring color stops that DO have positions specified. // This function implements this. It assumes that unspecified position values // are indicated by a value of -1.0f. void InterpolateUnspecifiedColorStopPositions( @@ -1706,8 +1706,7 @@ // https://www.w3.org/TR/css-flexbox-1/#flex-basis-property base::Optional<LayoutUnit> GetUsedFlexBasisIfNotContent( const scoped_refptr<const cssom::CSSComputedStyleData>& computed_style, - bool main_direction_is_horizontal, - LayoutUnit main_space, + bool main_direction_is_horizontal, LayoutUnit main_space, bool* flex_basis_depends_on_available_space) { // Percentage values of flex-basis are resolved against the flex item's // containing block (i.e. its flex container). @@ -1861,30 +1860,26 @@ LayoutUnit GetUsedBorderLeft( const scoped_refptr<const cssom::CSSComputedStyleData>& computed_style) { - return LayoutUnit(base::polymorphic_downcast<const cssom::LengthValue*>( - computed_style->border_left_width().get()) - ->value()); + return LayoutUnit( + GetUsedNonNegativeLength(computed_style->border_left_width())); } LayoutUnit GetUsedBorderTop( const scoped_refptr<const cssom::CSSComputedStyleData>& computed_style) { - return LayoutUnit(base::polymorphic_downcast<const cssom::LengthValue*>( - computed_style->border_top_width().get()) - ->value()); + return LayoutUnit( + GetUsedNonNegativeLength(computed_style->border_top_width())); } LayoutUnit GetUsedBorderRight( const scoped_refptr<const cssom::CSSComputedStyleData>& computed_style) { - return LayoutUnit(base::polymorphic_downcast<const cssom::LengthValue*>( - computed_style->border_right_width().get()) - ->value()); + return LayoutUnit( + GetUsedNonNegativeLength(computed_style->border_right_width())); } LayoutUnit GetUsedBorderBottom( const scoped_refptr<const cssom::CSSComputedStyleData>& computed_style) { - return LayoutUnit(base::polymorphic_downcast<const cssom::LengthValue*>( - computed_style->border_bottom_width().get()) - ->value()); + return LayoutUnit( + GetUsedNonNegativeLength(computed_style->border_bottom_width())); } LayoutUnit GetUsedPaddingLeft(
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-bidi-split-occurs-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-bidi-split-occurs-expected.png new file mode 100644 index 0000000..7e58ccb --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-bidi-split-occurs-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/DISABLED-9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-bidi-split-occurs.html b/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-bidi-split-occurs.html similarity index 96% rename from src/cobalt/layout_tests/testdata/css-2-1/DISABLED-9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-bidi-split-occurs.html rename to src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-bidi-split-occurs.html index cd91b37..de17dbd 100644 --- a/src/cobalt/layout_tests/testdata/css-2-1/DISABLED-9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-bidi-split-occurs.html +++ b/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-bidi-split-occurs.html
@@ -12,6 +12,7 @@ body { margin: 0px; font-family: Roboto; + font-size: 16px; } span { padding-right: 12px; @@ -24,4 +25,3 @@ </div> </body> </html> -
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs-2-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs-2-expected.png new file mode 100644 index 0000000..3bcf7d0 --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs-2-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs-2.html b/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs-2.html new file mode 100644 index 0000000..317def4 --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs-2.html
@@ -0,0 +1,28 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<!-- + | When an inline box is split, margins, borders, and padding have no visual + | effect where the split occurs (or at any split, when there are several). + | https://www.w3.org/TR/CSS21/visuren.html#inline-formatting + --> +<html> + <head> + <style> + .containing-block { + width: 15rem; + display: inline-block; + } + .text-block { + color: white; + background-color: blue; + margin-left: 0.5rem; + font-family: Roboto; + font-size: 16px; + } + </style> + </head> + <body> + <div class="containing-block"> + <span class="text-block"> BADGE </span> <span class="text-block"> FOO BAR BAZ, 11:00 AM - 7:00 PM </span> + </div> + </body> +</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs-expected.png new file mode 100644 index 0000000..f62eddb --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/DISABLED-9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs.html b/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs.html similarity index 94% rename from src/cobalt/layout_tests/testdata/css-2-1/DISABLED-9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs.html rename to src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs.html index 78fdaaf..0f30f53 100644 --- a/src/cobalt/layout_tests/testdata/css-2-1/DISABLED-9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs.html +++ b/src/cobalt/layout_tests/testdata/css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs.html
@@ -14,6 +14,8 @@ border: 5px solid green; margin: 20px; padding: 5px; + font-family: Roboto; + font-size: 16px; } </style> </head>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt b/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt index 7f7ba6b..3659681 100644 --- a/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt +++ b/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt
@@ -182,6 +182,9 @@ 9-4-2-inline-boxes-should-split-at-br-elements 9-4-2-long-inline-boxes-should-be-split 9-4-2-margin-should-not-overflow-line-box +9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-bidi-split-occurs +9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs +9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs-2 9-4-2-multiple-inline-boxes-exceeding-width-of-line-box-should-be-split 9-4-2-nested-inline-boxes-should-split-at-br-elements 9-4-2-splitting-of-boxes-can-affect-containing-box-of-children
diff --git a/src/cobalt/layout_tests/testdata/css-text-3/5-collapsible-leading-white-space-should-not-prevent-soft-wrap-opportunity-expected.png b/src/cobalt/layout_tests/testdata/css-text-3/5-collapsible-leading-white-space-should-not-prevent-soft-wrap-opportunity-expected.png index 42ad0ea..87075bf 100644 --- a/src/cobalt/layout_tests/testdata/css-text-3/5-collapsible-leading-white-space-should-not-prevent-soft-wrap-opportunity-expected.png +++ b/src/cobalt/layout_tests/testdata/css-text-3/5-collapsible-leading-white-space-should-not-prevent-soft-wrap-opportunity-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/4-5-prefer-later-face-with-close-style-over-earlier-face-with-unicode-range-expected.png b/src/cobalt/layout_tests/testdata/css3-fonts/4-5-prefer-later-face-with-close-style-over-earlier-face-with-unicode-range-expected.png new file mode 100644 index 0000000..2cd7bd0 --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-fonts/4-5-prefer-later-face-with-close-style-over-earlier-face-with-unicode-range-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/4-5-prefer-later-face-with-close-style-over-earlier-face-with-unicode-range.html b/src/cobalt/layout_tests/testdata/css3-fonts/4-5-prefer-later-face-with-close-style-over-earlier-face-with-unicode-range.html new file mode 100644 index 0000000..c257b7e --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-fonts/4-5-prefer-later-face-with-close-style-over-earlier-face-with-unicode-range.html
@@ -0,0 +1,37 @@ +<!DOCTYPE html> +<!-- + | Unicode range test. Tests that font style takes precedence over a matching unicode-range. + | https://www.w3.org/TR/css3-fonts/#font-prop-desc + --> +<html> +<head> + <style> + @font-face { + font-family: 'TestFont'; + src: local('Noto Serif'); + unicode-range: U+4d-5a; /* =, M-Z */ + font-weight: bold; + } + @font-face { + font-family: 'TestFont'; + src: local('Roboto'); + font-weight: normal; + } + @font-face { + font-family: 'TestFont2'; + src: local('Dancing Script'); + font-weight: bold; + } + body { + background-color: black; + font-size: 75px; + color: #fff; + font-weight: bold; + font-family: 'TestFont', 'TestFont2'; /*TestFont2 should be used. */ + } + </style> +</head> +<body> + <div>Hello</div> +</body> +</html> \ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/4-5-use-correct-font-with-unicode-range-expected.png b/src/cobalt/layout_tests/testdata/css3-fonts/4-5-use-correct-font-with-unicode-range-expected.png new file mode 100644 index 0000000..c32526d --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-fonts/4-5-use-correct-font-with-unicode-range-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/4-5-use-correct-font-with-unicode-range.html b/src/cobalt/layout_tests/testdata/css3-fonts/4-5-use-correct-font-with-unicode-range.html new file mode 100644 index 0000000..3ff6b53 --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-fonts/4-5-use-correct-font-with-unicode-range.html
@@ -0,0 +1,31 @@ +<!DOCTYPE html> +<!-- + | Unicode range test. Tests multiple unicode-range values and multiple + | font-families with the same style but different unicode ranges and sources. + | https://www.w3.org/TR/css3-fonts/#font-prop-desc + --> +<html> +<head> + <style> + @font-face { + font-family: 'TestFont'; + src: local('Dancing Script'); + unicode-range: U+3d, U+4d-5a; /* =, M-Z */ + } + @font-face { + font-family: 'TestFont'; + src: local('Noto Serif'); + unicode-range: U+26; /* & */ + } + body { + background-color: black; + font-size: 75px; + color: #fff; + font-family: 'TestFont', 'Roboto'; + } + </style> +</head> +<body> + <div>Me & You = Us</div> +</body> +</html> \ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/layout_tests.txt b/src/cobalt/layout_tests/testdata/css3-fonts/layout_tests.txt index aac78cf..8c922d4 100644 --- a/src/cobalt/layout_tests/testdata/css3-fonts/layout_tests.txt +++ b/src/cobalt/layout_tests/testdata/css3-fonts/layout_tests.txt
@@ -4,6 +4,8 @@ 4-3-use-first-available-local-font-face 4-3-use-next-font-family-if-font-face-sources-unavailable 4-4-use-correct-style-in-font-face-set +4-5-use-correct-font-with-unicode-range +4-5-prefer-later-face-with-close-style-over-earlier-face-with-unicode-range 5-2-use-correct-style-in-font-family 5-2-use-first-available-listed-font-family 5-2-use-numerical-font-weights-in-family-face-matching
diff --git a/src/cobalt/media/base/playback_statistics.cc b/src/cobalt/media/base/playback_statistics.cc index a17c72a..b5001ec 100644 --- a/src/cobalt/media/base/playback_statistics.cc +++ b/src/cobalt/media/base/playback_statistics.cc
@@ -17,6 +17,7 @@ #include <math.h> #include <stdint.h> +#include "base/strings/stringprintf.h" #include "starboard/atomic.h" #include "starboard/common/log.h" #include "starboard/common/string.h" @@ -77,6 +78,55 @@ } // namespace +PlaybackStatistics::PlaybackStatistics(const std::string& pipeline_identifier) + : seek_time_(base::StringPrintf("Media.Pipeline.%s.SeekTime", + pipeline_identifier.c_str()), + base::TimeDelta(), "The seek-to time of the media pipeline."), + first_written_audio_timestamp_( + base::StringPrintf("Media.Pipeline.%s.FirstWrittenAudioTimestamp", + pipeline_identifier.c_str()), + base::TimeDelta(), + "The timestamp of the first written audio buffer in the media " + "pipeline."), + first_written_video_timestamp_( + base::StringPrintf("Media.Pipeline.%s.FirstWrittenVideoTimestamp", + pipeline_identifier.c_str()), + base::TimeDelta(), + "The timestamp of the first written audio buffer in the media " + "pipeline."), + last_written_audio_timestamp_( + base::StringPrintf("Media.Pipeline.%s.LastWrittenAudioTimestamp", + pipeline_identifier.c_str()), + base::TimeDelta(), + "The timestamp of the last written audio buffer in the media " + "pipeline."), + last_written_video_timestamp_( + base::StringPrintf("Media.Pipeline.%s.LastWrittenVideoTimestamp", + pipeline_identifier.c_str()), + base::TimeDelta(), + "The timestamp of the last written video buffer in the media " + "pipeline."), + video_width_(base::StringPrintf("Media.Pipeline.%s.VideoWidth", + pipeline_identifier.c_str()), + 0, "The frame width of the video."), + video_height_(base::StringPrintf("Media.Pipeline.%s.VideoHeight", + pipeline_identifier.c_str()), + 0, "The frame height of the video."), + is_audio_eos_written_( + base::StringPrintf("Media.Pipeline.%s.IsAudioEOSWritten", + pipeline_identifier.c_str()), + false, "Indicator of if the audio eos is written."), + is_video_eos_written_( + base::StringPrintf("Media.Pipeline.%s.IsVideoEOSWritten", + pipeline_identifier.c_str()), + false, "Indicator of if the video eos is written."), + pipeline_status_(base::StringPrintf("Media.Pipeline.%s.PipelineStatus", + pipeline_identifier.c_str()), + PIPELINE_OK, "The status of the media pipeline."), + error_message_(base::StringPrintf("Media.Pipeline.%s.ErrorMessage", + pipeline_identifier.c_str()), + "", "The error message of the media pipeline error.") {} + PlaybackStatistics::~PlaybackStatistics() { SbAtomicNoBarrier_Increment(&s_active_instances, -1); } @@ -103,10 +153,11 @@ } } - const auto width = - static_cast<SbAtomic32>(video_config.natural_size().width()); - const auto height = - static_cast<SbAtomic32>(video_config.natural_size().height()); + video_width_ = video_config.natural_size().width(); + video_height_ = video_config.natural_size().height(); + + const auto width = static_cast<SbAtomic32>(video_width_); + const auto height = static_cast<SbAtomic32>(video_height_); UpdateMinValue(width, &s_min_video_width); UpdateMaxValue(width, &s_max_video_width); @@ -124,22 +175,38 @@ void PlaybackStatistics::OnSeek(const base::TimeDelta& seek_time) { seek_time_ = seek_time; - first_written_audio_timestamp_.reset(); - first_written_video_timestamp_.reset(); + is_first_audio_buffer_written_ = false; + is_first_video_buffer_written_ = false; } -void PlaybackStatistics::OnAudioAU(const base::TimeDelta& timestamp) { - last_written_audio_timestamp_ = timestamp; - if (!first_written_audio_timestamp_) { - first_written_audio_timestamp_.emplace(timestamp); +void PlaybackStatistics::OnAudioAU(const scoped_refptr<DecoderBuffer>& buffer) { + if (buffer->end_of_stream()) { + is_audio_eos_written_ = true; + return; + } + last_written_audio_timestamp_ = buffer->timestamp(); + if (!is_first_audio_buffer_written_) { + is_first_audio_buffer_written_ = true; + first_written_audio_timestamp_ = buffer->timestamp(); } } -void PlaybackStatistics::OnVideoAU(const base::TimeDelta& timestamp) { - last_written_video_timestamp_ = timestamp; - if (!first_written_video_timestamp_) { - first_written_video_timestamp_.emplace(timestamp); +void PlaybackStatistics::OnVideoAU(const scoped_refptr<DecoderBuffer>& buffer) { + if (buffer->end_of_stream()) { + is_video_eos_written_ = true; + return; } + last_written_video_timestamp_ = buffer->timestamp(); + if (!is_first_video_buffer_written_) { + is_first_video_buffer_written_ = true; + first_written_video_timestamp_ = buffer->timestamp(); + } +} + +void PlaybackStatistics::OnError(PipelineStatus status, + const std::string& error_message) { + pipeline_status_ = status; + error_message_ = error_message; } std::string PlaybackStatistics::GetStatistics( @@ -149,14 +216,12 @@ ", active_players (max): %d (%d), av1: ~%" PRId64 ", h264: ~%" PRId64 ", hevc: ~%" PRId64 ", vp9: ~%" PRId64 ", min_width: %d, min_height: %d, max_width: %d, max_height: %d" - ", last_working_codec: %s, seek_time: %" PRId64 + ", last_working_codec: %s, seek_time: %s" ", first_audio_time: ~%" PRId64 ", first_video_time: ~%" PRId64 ", last_audio_time: ~%" PRId64 ", last_video_time: ~%" PRId64, GetCodecName(current_video_config.codec()).c_str(), - (current_video_config.is_encrypted() ? "Y" : "N"), - static_cast<int>(current_video_config.natural_size().width()), - static_cast<int>(current_video_config.natural_size().height()), - SbAtomicNoBarrier_Load(&s_active_instances), + (current_video_config.is_encrypted() ? "Y" : "N"), video_width_.value(), + video_height_.value(), SbAtomicNoBarrier_Load(&s_active_instances), SbAtomicNoBarrier_Load(&s_max_active_instances), RoundValues(SbAtomicNoBarrier_Load(&s_av1_played)), RoundValues(SbAtomicNoBarrier_Load(&s_h264_played)), @@ -169,18 +234,18 @@ GetCodecName(static_cast<VideoCodec>( SbAtomicNoBarrier_Load(&s_last_working_codec))) .c_str(), - seek_time_.InMicroseconds(), - first_written_audio_timestamp_.has_value() - ? RoundValues(first_written_audio_timestamp_->InSeconds()) + ValToString(seek_time_).c_str(), + is_first_audio_buffer_written_ + ? RoundValues(first_written_audio_timestamp_.value().InSeconds()) : -1, - first_written_video_timestamp_.has_value() - ? RoundValues(first_written_video_timestamp_->InSeconds()) + is_first_video_buffer_written_ + ? RoundValues(first_written_video_timestamp_.value().InSeconds()) : -1, - first_written_audio_timestamp_.has_value() - ? RoundValues(last_written_audio_timestamp_.InSeconds()) + is_first_audio_buffer_written_ + ? RoundValues(last_written_audio_timestamp_.value().InSeconds()) : -1, - first_written_video_timestamp_.has_value() - ? RoundValues(last_written_video_timestamp_.InSeconds()) + is_first_video_buffer_written_ + ? RoundValues(last_written_video_timestamp_.value().InSeconds()) : -1); }
diff --git a/src/cobalt/media/base/playback_statistics.h b/src/cobalt/media/base/playback_statistics.h index 9cbb562..78808b3 100644 --- a/src/cobalt/media/base/playback_statistics.h +++ b/src/cobalt/media/base/playback_statistics.h
@@ -19,6 +19,9 @@ #include "base/optional.h" #include "base/time/time.h" +#include "cobalt/base/c_val.h" +#include "cobalt/media/base/decoder_buffer.h" +#include "cobalt/media/base/pipeline_status.h" #include "cobalt/media/base/video_decoder_config.h" namespace cobalt { @@ -26,24 +29,34 @@ class PlaybackStatistics { public: + explicit PlaybackStatistics(const std::string& pipeline_identifier); ~PlaybackStatistics(); void UpdateVideoConfig(const VideoDecoderConfig& video_config); void OnPresenting(const VideoDecoderConfig& video_config); void OnSeek(const base::TimeDelta& seek_time); - void OnAudioAU(const base::TimeDelta& timestamp); - void OnVideoAU(const base::TimeDelta& timestamp); + void OnAudioAU(const scoped_refptr<DecoderBuffer>& buffer); + void OnVideoAU(const scoped_refptr<DecoderBuffer>& buffer); + void OnError(PipelineStatus status, const std::string& error_message); std::string GetStatistics( const VideoDecoderConfig& current_video_config) const; private: - base::TimeDelta seek_time_; - base::Optional<base::TimeDelta> first_written_audio_timestamp_; - base::Optional<base::TimeDelta> first_written_video_timestamp_; - base::TimeDelta last_written_audio_timestamp_; - base::TimeDelta last_written_video_timestamp_; + base::CVal<base::TimeDelta> seek_time_; + base::CVal<base::TimeDelta> first_written_audio_timestamp_; + base::CVal<base::TimeDelta> first_written_video_timestamp_; + base::CVal<base::TimeDelta> last_written_audio_timestamp_; + base::CVal<base::TimeDelta> last_written_video_timestamp_; + base::CVal<int> video_width_; + base::CVal<int> video_height_; + base::CVal<bool> is_audio_eos_written_; + base::CVal<bool> is_video_eos_written_; + base::CVal<PipelineStatus> pipeline_status_; + base::CVal<std::string> error_message_; bool is_initial_config_ = true; + bool is_first_audio_buffer_written_ = false; + bool is_first_video_buffer_written_ = false; }; } // namespace media
diff --git a/src/cobalt/media/base/sbplayer_pipeline.cc b/src/cobalt/media/base/sbplayer_pipeline.cc index 3b7fcb4..5a9b541 100644 --- a/src/cobalt/media/base/sbplayer_pipeline.cc +++ b/src/cobalt/media/base/sbplayer_pipeline.cc
@@ -21,11 +21,13 @@ #include "base/callback_helpers.h" #include "base/logging.h" #include "base/optional.h" +#include "base/strings/stringprintf.h" #include "base/synchronization/lock.h" #include "base/synchronization/waitable_event.h" #include "base/task_runner.h" #include "base/time/time.h" #include "base/trace_event/trace_event.h" +#include "cobalt/base/c_val.h" #include "cobalt/math/size.h" #include "cobalt/media/base/audio_decoder_config.h" #include "cobalt/media/base/bind_to_current_loop.h" @@ -55,6 +57,8 @@ static const int kRetryDelayAtSuspendInMilliseconds = 100; +unsigned int g_pipeline_identifier_counter = 0; + // Used to post parameters to SbPlayerPipeline::StartTask() as the number of // parameters exceed what base::Bind() can support. struct StartTaskParameters { @@ -189,6 +193,10 @@ // Retrieve the statistics as a string and append to message. std::string AppendStatisticsString(const std::string& message) const; + // An identifier string for the pipeline, used in CVal to identify multiple + // pipelines. + const std::string pipeline_identifier_; + // Message loop used to execute pipeline tasks. It is thread-safe. scoped_refptr<base::SingleThreadTaskRunner> task_runner_; @@ -219,12 +227,12 @@ // Current volume level (from 0.0f to 1.0f). This value is set immediately // via SetVolume() and a task is dispatched on the message loop to notify the // filters. - float volume_ = 1.f; + base::CVal<float> volume_; // Current playback rate (>= 0.0f). This value is set immediately via // SetPlaybackRate() and a task is dispatched on the message loop to notify // the filters. - float playback_rate_ = 0.f; + base::CVal<float> playback_rate_; // The saved audio and video demuxer streams. Note that it is safe to store // raw pointers of the demuxer streams, as the Demuxer guarantees that its @@ -259,7 +267,7 @@ bool audio_read_in_progress_ = false; bool audio_read_delayed_ = false; bool video_read_in_progress_ = false; - TimeDelta duration_; + base::CVal<TimeDelta> duration_; #if SB_HAS(PLAYER_WITH_URL) TimeDelta start_date_; @@ -276,12 +284,14 @@ // Temporary callback used for Start() and Seek(). SeekCB seek_cb_; - base::TimeDelta seek_time_; + TimeDelta seek_time_; std::unique_ptr<StarboardPlayer> player_; bool is_initial_preroll_ = true; - bool suspended_ = false; - bool stopped_ = false; - bool ended_ = false; + base::CVal<bool> started_; + base::CVal<bool> suspended_; + base::CVal<bool> stopped_; + base::CVal<bool> ended_; + base::CVal<SbPlayerState> player_state_; VideoFrameProvider* video_frame_provider_; @@ -299,11 +309,11 @@ // Timestamp for the last written audio. SbTime timestamp_of_last_written_audio_ = 0; // Last media time reported by GetMediaTime(). - SbTime last_media_time_ = 0; + base::CVal<SbTime> last_media_time_; // Time when we last checked the media time. SbTime last_time_media_time_retrieved_ = 0; // The maximum video playback capabilities required for the playback. - std::string max_video_capabilities_; + base::CVal<std::string> max_video_capabilities_; PlaybackStatistics playback_statistics_; @@ -317,14 +327,52 @@ get_decode_target_graphics_context_provider_func, bool allow_resume_after_suspend, MediaLog* media_log, VideoFrameProvider* video_frame_provider) - : task_runner_(task_runner), + : pipeline_identifier_( + base::StringPrintf("%X", g_pipeline_identifier_counter++)), + task_runner_(task_runner), allow_resume_after_suspend_(allow_resume_after_suspend), window_(window), get_decode_target_graphics_context_provider_func_( get_decode_target_graphics_context_provider_func), natural_size_(0, 0), + volume_(base::StringPrintf("Media.Pipeline.%s.Volume", + pipeline_identifier_.c_str()), + 1.0f, "Volume of the media pipeline."), + playback_rate_(base::StringPrintf("Media.Pipeline.%s.PlaybackRate", + pipeline_identifier_.c_str()), + 0.0f, "Playback rate of the media pipeline."), + duration_(base::StringPrintf("Media.Pipeline.%s.Duration", + pipeline_identifier_.c_str()), + base::TimeDelta(), "Playback duration of the media pipeline."), set_bounds_helper_(new SbPlayerSetBoundsHelper), - video_frame_provider_(video_frame_provider) { + started_(base::StringPrintf("Media.Pipeline.%s.Started", + pipeline_identifier_.c_str()), + false, "Whether the media pipeline has started."), + suspended_(base::StringPrintf("Media.Pipeline.%s.Suspended", + pipeline_identifier_.c_str()), + false, + "Whether the media pipeline has been suspended. The " + "pipeline is usually suspended after the app enters " + "background mode."), + stopped_(base::StringPrintf("Media.Pipeline.%s.Stopped", + pipeline_identifier_.c_str()), + false, "Whether the media pipeline has stopped."), + ended_(base::StringPrintf("Media.Pipeline.%s.Ended", + pipeline_identifier_.c_str()), + false, "Whether the media pipeline has ended."), + player_state_(base::StringPrintf("Media.Pipeline.%s.PlayerState", + pipeline_identifier_.c_str()), + kSbPlayerStateInitialized, + "The underlying SbPlayer state of the media pipeline."), + video_frame_provider_(video_frame_provider), + last_media_time_(base::StringPrintf("Media.Pipeline.%s.LastMediaTime", + pipeline_identifier_.c_str()), + 0, "Last media time reported by the underlying player."), + max_video_capabilities_( + base::StringPrintf("Media.Pipeline.%s.MaxVideoCapabilities", + pipeline_identifier_.c_str()), + "", "The max video capabilities required for the media pipeline."), + playback_statistics_(pipeline_identifier_) { SbMediaSetAudioWriteDuration(kAudioLimit); } @@ -487,9 +535,9 @@ player = std::move(player_); } - DLOG(INFO) << "Destroying SbPlayer."; + LOG(INFO) << "Destroying SbPlayer."; player.reset(); - DLOG(INFO) << "SbPlayer destroyed."; + LOG(INFO) << "SbPlayer destroyed."; } // When Stop() is in progress, we no longer need to call |error_cb_|. @@ -773,6 +821,8 @@ BindToCurrentLoop(base::Bind( &SbPlayerPipeline::OnDemuxerInitialized, this)), kEnableTextTracks); + + started_ = true; } void SbPlayerPipeline::SetVolumeTask(float volume) { @@ -857,7 +907,7 @@ { base::AutoLock auto_lock(lock_); - DLOG(INFO) << "StarboardPlayer created with url: " << source_url; + LOG(INFO) << "Creating StarboardPlayer with url: " << source_url; player_.reset(new StarboardPlayer( task_runner_, source_url, window_, this, set_bounds_helper_.get(), allow_resume_after_suspend_, *decode_to_texture_output_mode_, @@ -867,6 +917,7 @@ SetVolumeTask(volume_); } else { player_.reset(); + LOG(INFO) << "Failed to create a valid StarboardPlayer."; } } @@ -891,8 +942,7 @@ base::AutoLock auto_lock(lock_); if (!player_) { - DLOG(INFO) - << "Player not set before calling SbPlayerPipeline::SetDrmSystem"; + LOG(INFO) << "Player not set before calling SbPlayerPipeline::SetDrmSystem"; return; } @@ -947,6 +997,7 @@ // available, reset the existing player first to reduce the number of active // players. player_.reset(); + LOG(INFO) << "Creating StarboardPlayer."; player_.reset(new StarboardPlayer( task_runner_, get_decode_target_graphics_context_provider_func_, audio_config, video_config, window_, drm_system, this, @@ -958,6 +1009,7 @@ SetVolumeTask(volume_); } else { player_.reset(); + LOG(INFO) << "Failed to create a valid StarboardPlayer."; } } @@ -1118,15 +1170,13 @@ if (type == DemuxerStream::AUDIO) { audio_read_in_progress_ = false; + playback_statistics_.OnAudioAU(buffer); if (!buffer->end_of_stream()) { - playback_statistics_.OnAudioAU(buffer->timestamp()); timestamp_of_last_written_audio_ = buffer->timestamp().ToSbTime(); } } else { video_read_in_progress_ = false; - if (!buffer->end_of_stream()) { - playback_statistics_.OnVideoAU(buffer->timestamp()); - } + playback_statistics_.OnVideoAU(buffer); } player_->WriteBuffer(type, buffer); @@ -1170,7 +1220,7 @@ 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); + std::max(playback_rate_.value(), 1.0f); task_runner_->PostDelayedTask( FROM_HERE, base::Bind(&SbPlayerPipeline::DelayedNeedData, this), base::TimeDelta::FromMicroseconds(delay_time)); @@ -1201,6 +1251,7 @@ if (!player_) { return; } + player_state_ = state; switch (state) { case kSbPlayerStateInitialized: NOTREACHED(); @@ -1338,6 +1389,11 @@ void SbPlayerPipeline::CallErrorCB(PipelineStatus status, const std::string& error_message) { DCHECK_NE(status, PIPELINE_OK); + // Only to record the first error. + if (error_cb_.is_null()) { + return; + } + playback_statistics_.OnError(status, error_message); ResetAndRunIfNotNull(&error_cb_, status, AppendStatisticsString(error_message)); }
diff --git a/src/cobalt/media/base/starboard_player.cc b/src/cobalt/media/base/starboard_player.cc index 3f34f40..7d19612 100644 --- a/src/cobalt/media/base/starboard_player.cc +++ b/src/cobalt/media/base/starboard_player.cc
@@ -517,7 +517,7 @@ DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(!on_encrypted_media_init_data_encountered_cb_.is_null()); - DLOG(INFO) << "CreateUrlPlayer passed url " << url; + LOG(INFO) << "CreateUrlPlayer passed url " << url; player_ = SbUrlPlayerCreate(url.c_str(), window_, &StarboardPlayer::PlayerStatusCB, &StarboardPlayer::EncryptedMediaInitDataEncounteredCB, @@ -778,10 +778,10 @@ break; #if SB_API_VERSION < 12 case kSbPlayerDecoderStateBufferFull: - DLOG(WARNING) << "kSbPlayerDecoderStateBufferFull has been deprecated."; + LOG(WARNING) << "kSbPlayerDecoderStateBufferFull has been deprecated."; return; case kSbPlayerDecoderStateDestroyed: - DLOG(WARNING) << "kSbPlayerDecoderStateDestroyed has been deprecated."; + LOG(WARNING) << "kSbPlayerDecoderStateDestroyed has been deprecated."; return; #endif // SB_API_VERSION < 12 }
diff --git a/src/cobalt/media/formats/mp4/box_definitions.cc b/src/cobalt/media/formats/mp4/box_definitions.cc index 0fc6975..631b713 100644 --- a/src/cobalt/media/formats/mp4/box_definitions.cc +++ b/src/cobalt/media/formats/mp4/box_definitions.cc
@@ -1304,7 +1304,8 @@ reader->ReadChild(&decode_time) && reader->MaybeReadChildren(&runs) && reader->MaybeReadChild(&auxiliary_offset) && reader->MaybeReadChild(&auxiliary_size) && - reader->MaybeReadChild(&sdtp)); + reader->MaybeReadChild(&sdtp) && + reader->MaybeReadChild(&sample_encryption)); // There could be multiple SampleGroupDescription and SampleToGroup boxes with // different grouping types. For common encryption, the relevant grouping type
diff --git a/src/cobalt/media/player/web_media_player_impl.cc b/src/cobalt/media/player/web_media_player_impl.cc index 480d7e8..69f5bd6 100644 --- a/src/cobalt/media/player/web_media_player_impl.cc +++ b/src/cobalt/media/player/web_media_player_impl.cc
@@ -33,9 +33,6 @@ namespace { -// Used to ensure that there is no more than one instance of WebMediaPlayerImpl. -WebMediaPlayerImpl* s_instance; - // Limits the range of playback rate. // // TODO(kylep): Revisit these. @@ -137,10 +134,6 @@ video_frame_provider_ = new VideoFrameProvider(); - DLOG_IF(ERROR, s_instance) - << "More than one WebMediaPlayerImpl has been created."; - s_instance = this; - DCHECK(buffer_allocator_); media_log_->AddEvent( media_log_->CreateEvent(MediaLogEvent::WEBMEDIAPLAYER_CREATED)); @@ -166,10 +159,6 @@ ON_INSTANCE_RELEASED(WebMediaPlayerImpl); - DLOG_IF(ERROR, s_instance != this) - << "More than one WebMediaPlayerImpl has been created."; - s_instance = NULL; - if (delegate_) { delegate_->UnregisterPlayer(this); } @@ -229,7 +218,7 @@ DCHECK_EQ(main_loop_, base::MessageLoop::current()); UMA_HISTOGRAM_ENUMERATION("Media.URLScheme", URLScheme(url), kMaxURLScheme); - DLOG(INFO) << "Start URL playback"; + LOG(INFO) << "Start URL playback"; // Handle any volume changes that occured before load(). SetVolume(GetClient()->Volume()); @@ -249,7 +238,7 @@ TRACE_EVENT0("cobalt::media", "WebMediaPlayerImpl::LoadMediaSource"); DCHECK_EQ(main_loop_, base::MessageLoop::current()); - DLOG(INFO) << "Start MEDIASOURCE playback"; + LOG(INFO) << "Start MEDIASOURCE playback"; // Handle any volume changes that occured before load(). SetVolume(GetClient()->Volume()); @@ -276,7 +265,7 @@ DCHECK_EQ(main_loop_, base::MessageLoop::current()); UMA_HISTOGRAM_ENUMERATION("Media.URLScheme", URLScheme(url), kMaxURLScheme); - DLOG(INFO) << "Start PROGRESSIVE playback"; + LOG(INFO) << "Start PROGRESSIVE playback"; // Handle any volume changes that occured before load(). SetVolume(GetClient()->Volume()); @@ -904,11 +893,11 @@ // Note: stopping the pipeline might block for a long time. base::WaitableEvent waiter(base::WaitableEvent::ResetPolicy::AUTOMATIC, base::WaitableEvent::InitialState::NOT_SIGNALED); - DLOG(INFO) << "Trying to stop media pipeline."; + LOG(INFO) << "Trying to stop media pipeline."; pipeline_->Stop( base::Bind(&base::WaitableEvent::Signal, base::Unretained(&waiter))); waiter.Wait(); - DLOG(INFO) << "Media pipeline stopped."; + LOG(INFO) << "Media pipeline stopped."; // And then detach the proxy, it may live on the render thread for a little // longer until all the tasks are finished.
diff --git a/src/cobalt/media_integration_tests/__init__.py b/src/cobalt/media_integration_tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/cobalt/media_integration_tests/__init__.py
diff --git a/src/cobalt/media_integration_tests/_env.py b/src/cobalt/media_integration_tests/_env.py new file mode 100644 index 0000000..a9d83bf --- /dev/null +++ b/src/cobalt/media_integration_tests/_env.py
@@ -0,0 +1,26 @@ +# +# Copyright 2021 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. +# +"""Ask the parent directory to load the project environment.""" + +from imp import load_source +from os import path +import sys + +_ENV = path.abspath(path.join(path.dirname(__file__), path.pardir, '_env.py')) +if not path.exists(_ENV): + print '%s: Can\'t find repo root.\nMissing parent: %s' % (__file__, _ENV) + sys.exit(1) +load_source('', _ENV)
diff --git a/src/cobalt/media_integration_tests/endurance/__init__.py b/src/cobalt/media_integration_tests/endurance/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/cobalt/media_integration_tests/endurance/__init__.py
diff --git a/src/cobalt/media_integration_tests/endurance/_env.py b/src/cobalt/media_integration_tests/endurance/_env.py new file mode 100644 index 0000000..a9d83bf --- /dev/null +++ b/src/cobalt/media_integration_tests/endurance/_env.py
@@ -0,0 +1,26 @@ +# +# Copyright 2021 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. +# +"""Ask the parent directory to load the project environment.""" + +from imp import load_source +from os import path +import sys + +_ENV = path.abspath(path.join(path.dirname(__file__), path.pardir, '_env.py')) +if not path.exists(_ENV): + print '%s: Can\'t find repo root.\nMissing parent: %s' % (__file__, _ENV) + sys.exit(1) +load_source('', _ENV)
diff --git a/src/cobalt/media_integration_tests/endurance/endurance_test.py b/src/cobalt/media_integration_tests/endurance/endurance_test.py new file mode 100644 index 0000000..18cc902 --- /dev/null +++ b/src/cobalt/media_integration_tests/endurance/endurance_test.py
@@ -0,0 +1,264 @@ +# Copyright 2021 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. +""" Endurance test of playbacks.""" + +import argparse +import logging +import random +import time + +import _env # pylint: disable=unused-import +from cobalt.media_integration_tests.test_app import Features, MediaSessionPlaybackState +from cobalt.media_integration_tests.test_case import TestCase +from cobalt.media_integration_tests.test_util import PlaybackUrls + +# The variables below are all in seconds. +STATUS_CHECK_INTERVAL = 0.5 +SINGLE_PLAYBACK_WATCH_TIME_MAXIMUM = 2.0 * 60 * 60 +PLAYER_INITIALIZATION_WAITING_TIMEOUT = 5.0 * 60 +MEDIA_TIME_UPDATE_WAITING_TIMEOUT = 10.0 +WRITTEN_INPUT_WAITING_TIMEOUT = 30.0 +PLAYBACK_END_WAITING_TIMEOUT = 30.0 +# Needs to interact with the app regularly to keep it active. Otherwise, the +# playback may be paused. +IDLE_TIME_MAXIMUM = 60 * 60 + + +class EnduranceTest(TestCase): + """ + Test case for playback endurance test. + """ + + def __init__(self, *args, **kwargs): + super(EnduranceTest, self).__init__(*args, **kwargs) + + parser = argparse.ArgumentParser() + parser.add_argument( + '--startup_url', default=PlaybackUrls.PLAYLIST, type=str) + # Stop the test after |max_running_time| (in hours). If |max_running_time| + # is 0 or negative, the test will not stop until it gets an error. + parser.add_argument('--max_running_time', default=20, type=float) + # Insert random actions if |random_action_interval| is greater than 0. + parser.add_argument('--random_action_interval', default=0.0, type=float) + args, _ = parser.parse_known_args() + + self.startup_url = args.startup_url + # Convert |max_running_time| to seconds. + self.max_running_time = args.max_running_time * 60 * 60 + self.needs_random_action = args.random_action_interval > 0 + self.random_action_interval = args.random_action_interval + + def ResetTimestamps(self): + self.last_media_time = -1 + self.last_media_time_update_time = -1 + self.last_written_audio_timestamp = -1 + self.last_written_audio_update_time = -1 + self.last_written_video_timestamp = -1 + self.last_written_video_update_time = -1 + self.audio_eos_written_time = -1 + self.video_eos_written_time = -1 + self.playback_end_time = -1 + + def OnPlayerStateChanged(self, player_state_handler, player_state): + current_running_time = time.time() - self.start_time + element_state = player_state.video_element_state + pipeline_state = player_state.pipeline_state + media_session_state = player_state.media_session_state + + if not player_state_handler.IsPlayerPlaying(): + return + + # Reset timestamps after player identifier is changed. + if self.player_identifier != pipeline_state.identifier: + self.player_identifier = pipeline_state.identifier + self.playback_start_time = current_running_time + self.ResetTimestamps() + + # Reset timestamps after resume. It could happen after pause, fastforward, + # rewind and playback switch. + if media_session_state.playback_state != MediaSessionPlaybackState.PLAYING: + self.playback_is_playing = False + elif not self.playback_is_playing: + self.playback_is_playing = True + self.ResetTimestamps() + + # Update media time. + if self.last_media_time != element_state.current_time: + self.last_media_time = element_state.current_time + self.last_media_time_update_time = current_running_time + + # Update written audio timestamp. + if (self.last_written_audio_timestamp != + pipeline_state.last_written_audio_timestamp): + self.last_written_audio_timestamp = ( + pipeline_state.last_written_audio_timestamp) + self.last_written_audio_update_time = current_running_time + + # Update written video timestamp. + if (self.last_written_video_timestamp != + pipeline_state.last_written_video_timestamp): + self.last_written_video_timestamp = ( + pipeline_state.last_written_video_timestamp) + self.last_written_video_update_time = current_running_time + + # Update audio eos timestamp. + if (self.audio_eos_written_time == -1 and + pipeline_state.is_audio_eos_written): + self.audio_eos_written_time = current_running_time + + # Update video eos timestamp. + if (self.video_eos_written_time == -1 and + pipeline_state.is_video_eos_written): + self.video_eos_written_time = current_running_time + + # Update playback end time. + if (self.audio_eos_written_time != -1 and + self.video_eos_written_time != -1 and self.playback_end_time == -1 and + element_state.current_time >= element_state.duration): + self.playback_end_time = current_running_time + + def GenerateErrorString(self, err_msg): + return ( + '%s (running time: %f, player identifier: "%s", is playing: %r, ' + 'playback start time: %f, last media time: %f (updated at %f), ' + 'last written audio timestamp: %d (updated at %f), ' + 'last written video timestamp: %d (updated at %f), ' + 'audio eos written at %f, video eos written at %f, ' + 'playback ended at %f).' % + (err_msg, time.time() - self.start_time, self.player_identifier, + self.playback_is_playing, self.playback_start_time, + self.last_media_time, self.last_media_time_update_time, + self.last_written_audio_timestamp, self.last_written_audio_update_time, + self.last_written_video_timestamp, self.last_written_video_update_time, + self.audio_eos_written_time, self.video_eos_written_time, + self.playback_end_time)) + + def SendRandomAction(self, app): + + def SuspendAndResume(app): + app.Suspend() + app.WaitUntilReachState( + lambda _app: not _app.ApplicationState().is_visible) + # Wait for 1 second before resume. + time.sleep(1) + app.Resume() + + actions = { + 'PlayPause': lambda _app: _app.PlayOrPause(), + 'Fastforward': lambda _app: _app.Fastforward(), + 'Rewind': lambda _app: _app.Rewind(), + 'PlayPrevious': lambda _app: _app.PlayPrevious(), + 'PlayNext': lambda _app: _app.PlayNext(), + } + if TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME): + actions['SuspendAndResume'] = SuspendAndResume + + action_name = random.choice(list(actions.keys())) + logging.info('Send random action (%s).', action_name) + actions[action_name](app) + + def test_playback_endurance(self): + self.start_time = time.time() + self.player_identifier = '' + self.playback_is_playing = False + self.playback_start_time = -1 + self.last_state_check_time = 0 + self.last_action_time = 0 + self.ResetTimestamps() + + app = self.CreateCobaltApp(self.startup_url) + app.AddPlayerStateChangeHandler(self.OnPlayerStateChanged) + with app: + while True: + # Put the sleep at the top of the loop. + time.sleep(STATUS_CHECK_INTERVAL) + + current_running_time = time.time() - self.start_time + # Stop the test after reaching max running time. + if (self.max_running_time > 0 and + current_running_time > self.max_running_time): + break + # Skip if there's no running player. + if not app.player_state_handler.IsPlayerPlaying(): + # TODO: identify network problem + self.assertTrue( + current_running_time - self.last_state_check_time < + PLAYER_INITIALIZATION_WAITING_TIMEOUT, + self.GenerateErrorString( + 'Timed out waiting for player initialization (waited: %f).' % + (current_running_time - self.last_state_check_time))) + continue + self.last_state_check_time = current_running_time + # Skip to next playback if it has been played for long time. + if (self.playback_start_time != -1 and + current_running_time - self.playback_start_time > + SINGLE_PLAYBACK_WATCH_TIME_MAXIMUM): + app.PlayNext() + # Set start time to -1 here to avoid send same command in next loop. + self.playback_start_time = -1 + continue + if self.playback_is_playing: + # Check media time. + if self.last_media_time_update_time > 0: + # TODO: identify network problem + self.assertTrue( + current_running_time - self.last_media_time_update_time < + MEDIA_TIME_UPDATE_WAITING_TIMEOUT, + self.GenerateErrorString( + 'Timed out waiting for media time update (waited: %f).' % + (current_running_time - self.last_media_time_update_time))) + # Check written audio timestamp. + if (self.last_written_audio_update_time > 0 and + self.audio_eos_written_time == -1): + self.assertTrue( + current_running_time - self.last_written_audio_update_time < + WRITTEN_INPUT_WAITING_TIMEOUT, + self.GenerateErrorString( + 'Timed out waiting for new audio input (waited: %f).' % + (current_running_time - + self.last_written_audio_update_time))) + # Check written video timestamp. + if (self.last_written_video_update_time > 0 and + self.video_eos_written_time == -1): + self.assertTrue( + current_running_time - self.last_written_video_update_time < + WRITTEN_INPUT_WAITING_TIMEOUT, + self.GenerateErrorString( + 'Timed out waiting for new video input (waited: %f).' % + (current_running_time - + self.last_written_video_update_time))) + # Check if the playback ends properly. + if (self.audio_eos_written_time > 0 and + self.video_eos_written_time > 0 and self.playback_end_time > 0): + self.assertTrue( + current_running_time - self.playback_end_time < + PLAYBACK_END_WAITING_TIMEOUT, + self.GenerateErrorString( + 'Timed out waiting for playback to end (waited: %f).' % + (current_running_time - self.playback_end_time,))) + + # Send random actions. + if (self.needs_random_action and + current_running_time - self.last_action_time > + self.random_action_interval): + self.SendRandomAction(app) + self.last_action_time = current_running_time + + # Interact with the app to keep it active. + if current_running_time - self.last_action_time > IDLE_TIME_MAXIMUM: + # Play the previous playback again. + app.PlayPrevious() + self.last_action_time = current_running_time + + app.RemovePlayerStateChangeHandler(self.OnPlayerStateChanged)
diff --git a/src/cobalt/media_integration_tests/functionality/__init__.py b/src/cobalt/media_integration_tests/functionality/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/cobalt/media_integration_tests/functionality/__init__.py
diff --git a/src/cobalt/media_integration_tests/functionality/_env.py b/src/cobalt/media_integration_tests/functionality/_env.py new file mode 100644 index 0000000..a9d83bf --- /dev/null +++ b/src/cobalt/media_integration_tests/functionality/_env.py
@@ -0,0 +1,26 @@ +# +# Copyright 2021 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. +# +"""Ask the parent directory to load the project environment.""" + +from imp import load_source +from os import path +import sys + +_ENV = path.abspath(path.join(path.dirname(__file__), path.pardir, '_env.py')) +if not path.exists(_ENV): + print '%s: Can\'t find repo root.\nMissing parent: %s' % (__file__, _ENV) + sys.exit(1) +load_source('', _ENV)
diff --git a/src/cobalt/media_integration_tests/functionality/general_playback.py b/src/cobalt/media_integration_tests/functionality/general_playback.py new file mode 100644 index 0000000..6edc0ca --- /dev/null +++ b/src/cobalt/media_integration_tests/functionality/general_playback.py
@@ -0,0 +1,60 @@ +# Copyright 2021 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. +""" Tests for general playbacks with different formats.""" + +import logging + +import _env # pylint: disable=unused-import +from cobalt.media_integration_tests.test_case import TestCase +from cobalt.media_integration_tests.test_util import MimeStrings, PlaybackUrls + + +class GeneralPlaybackTest(TestCase): + """ + Test cases for general playbacks. + """ + + def run_test(self, url, mime=None): + app = self.CreateCobaltApp(url) + with app: + # Skip the test if the mime is not supported. + if mime and not app.IsMediaTypeSupported(mime): + logging.info('Mime type (%s) is not supported. Skip the test.', mime) + return + # Wait until the playback starts. + app.WaitUntilPlayerStart() + app.WaitUntilAdsEnd() + # Let the playback play for 10 seconds. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 10) + + +TEST_PARAMETERS = [ + ('H264', PlaybackUrls.H264_ONLY, None), + ('PROGRESSIVE', PlaybackUrls.PROGRESSIVE, None), + ('ENCRYPTED', PlaybackUrls.ENCRYPTED, None), + ('VR', PlaybackUrls.VR, None), + ('VP9', PlaybackUrls.VP9, MimeStrings.VP9), + ('VP9_HFR', PlaybackUrls.VP9_HFR, MimeStrings.VP9_HFR), + ('AV1', PlaybackUrls.AV1, MimeStrings.AV1), + ('AV1_HFR', PlaybackUrls.AV1_HFR, MimeStrings.AV1_HFR), + ('VERTICAL', PlaybackUrls.VERTICAL, None), + ('SHORT', PlaybackUrls.SHORT, None), + ('VP9_HDR_HLG', PlaybackUrls.VP9_HDR_HLG, MimeStrings.VP9_HDR_HLG), + ('VP9_HDR_PQ', PlaybackUrls.VP9_HDR_PQ, MimeStrings.VP9_HDR_PQ), + ('HDR_PQ_HFR', PlaybackUrls.HDR_PQ_HFR, MimeStrings.VP9_HDR_PQ_HFR), +] + +for name, playback_url, mime_str in TEST_PARAMETERS: + TestCase.CreateTest(GeneralPlaybackTest, name, GeneralPlaybackTest.run_test, + playback_url, mime_str)
diff --git a/src/cobalt/media_integration_tests/functionality/live_playback.py b/src/cobalt/media_integration_tests/functionality/live_playback.py new file mode 100644 index 0000000..7390116 --- /dev/null +++ b/src/cobalt/media_integration_tests/functionality/live_playback.py
@@ -0,0 +1,57 @@ +# Copyright 2021 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. +""" Tests for live playbacks.""" + +import time + +import _env # pylint: disable=unused-import +from cobalt.media_integration_tests.test_case import TestCase +from cobalt.media_integration_tests.test_util import PlaybackUrls + + +class LivePlaybackTest(TestCase): + """ + Test cases for live playbacks. + """ + + def run_test(self, url): + app = self.CreateCobaltApp(url) + with app: + # Wait until the playback starts. + app.WaitUntilPlayerStart() + app.WaitUntilAdsEnd() + # Let the playback play for 5 seconds. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 5) + # Pause the playback and wait for some time. + app.PlayOrPause() + app.WaitUntilReachState( + lambda _app: _app.PlayerState().video_element_state.paused) + time.sleep(2) + # Resume the playback. + app.PlayOrPause() + app.WaitUntilReachState( + lambda _app: not _app.PlayerState().video_element_state.paused) + # Let the playback play for another 5 seconds. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 5) + + +TEST_PARAMETERS = [ + ('LIVE', PlaybackUrls.LIVE), + ('LIVE_ULL', PlaybackUrls.LIVE_ULL), + ('LIVE_VR', PlaybackUrls.LIVE_VR), +] + +for name, playback_url in TEST_PARAMETERS: + TestCase.CreateTest(LivePlaybackTest, name, LivePlaybackTest.run_test, + playback_url)
diff --git a/src/cobalt/media_integration_tests/functionality/playback_controls.py b/src/cobalt/media_integration_tests/functionality/playback_controls.py new file mode 100644 index 0000000..13b1ec8 --- /dev/null +++ b/src/cobalt/media_integration_tests/functionality/playback_controls.py
@@ -0,0 +1,101 @@ +# Copyright 2021 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. +""" Tests for playback control shortcuts.""" + +import logging + +import _env # pylint: disable=unused-import +from cobalt.media_integration_tests.test_case import TestCase +from cobalt.media_integration_tests.test_util import PlaybackUrls + + +class PlaybackControlsTest(TestCase): + """ + Test cases for playback control shortcuts. + """ + + def test_play_pause(self): + app = self.CreateCobaltApp(PlaybackUrls.H264_ONLY) + with app: + # Wait until the playback starts. + app.WaitUntilPlayerStart() + app.WaitUntilAdsEnd() + # Let the playback play for 2 seconds. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2) + # Pause the playback. + app.PlayOrPause() + app.WaitUntilReachState( + lambda _app: _app.PlayerState().video_element_state.paused) + # Resume the playback. + app.PlayOrPause() + app.WaitUntilReachState( + lambda _app: not _app.PlayerState().video_element_state.paused) + # Let the playback play for another 2 seconds. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2) + + def test_rewind_fastforward(self): + app = self.CreateCobaltApp(PlaybackUrls.H264_ONLY) + with app: + # Wait until the playback starts. + app.WaitUntilPlayerStart() + app.WaitUntilAdsEnd() + # Let the playback play for 2 seconds. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2) + + logging.info('Fast forward the playback.') + old_media_time = app.CurrentMediaTime() + # Fastforward the playback by 10 seconds. + app.Fastforward() + app.WaitUntilReachState( + lambda _app: _app.CurrentMediaTime() > old_media_time + 10) + # Let the playback play for another 2 seconds. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2) + + logging.info('Rewind the playback.') + old_media_time = app.CurrentMediaTime() + # Rewind the playback by 10 seconds. + app.Rewind() + app.WaitUntilReachState( + lambda _app: _app.CurrentMediaTime() < old_media_time) + # Let the playback play for another 2 seconds. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2) + + def test_play_next_and_previous(self): + app = self.CreateCobaltApp(PlaybackUrls.PLAYLIST) + with app: + # Wait until the playback starts. + app.WaitUntilPlayerStart() + app.WaitUntilAdsEnd() + # Let the playback play for 2 seconds. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2) + # Play next track. + logging.info('Play next playback.') + app.PlayNext() + # Wait until the player is destroyed. + app.WaitUntilPlayerDestroyed() + # Wait until the playback starts. + app.WaitUntilPlayerStart() + app.WaitUntilAdsEnd() + # Let the playback play for another 2 seconds. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2) + # Play previous track. + logging.info('Play previous playback.') + app.PlayPrevious() + # Wait until the player is destroyed. + app.WaitUntilPlayerDestroyed() + # Wait until the playback starts. + app.WaitUntilPlayerStart() + app.WaitUntilAdsEnd() + # Let the playback play for another 2 seconds. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2)
diff --git a/src/cobalt/media_integration_tests/functionality/suspend_resume.py b/src/cobalt/media_integration_tests/functionality/suspend_resume.py new file mode 100644 index 0000000..5b7de5e --- /dev/null +++ b/src/cobalt/media_integration_tests/functionality/suspend_resume.py
@@ -0,0 +1,74 @@ +# Copyright 2021 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. +""" Tests for suspend and resume during playing.""" + +import logging +import time + +import _env # pylint: disable=unused-import +from cobalt.media_integration_tests.test_app import Features +from cobalt.media_integration_tests.test_case import TestCase +from cobalt.media_integration_tests.test_util import MimeStrings, PlaybackUrls + + +class SuspendResumeTest(TestCase): + """ + Test cases for suspend and resume. + """ + + def run_test(self, url, mime=None): + app = self.CreateCobaltApp(url) + with app: + # Skip the test if the mime is not supported. + if mime and not app.IsMediaTypeSupported(mime): + logging.info('Mime type (%s) is not supported. Skip the test.', mime) + return + # Wait until the playback starts. + app.WaitUntilPlayerStart() + app.WaitUntilAdsEnd() + # Let the playback play for 2 seconds. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2) + # Suspend the app and wait the app enters background. + logging.info('Suspend the application.') + app.Suspend() + app.WaitUntilReachState( + lambda _app: not _app.ApplicationState().is_visible) + # Wait for 1 second before resume. + time.sleep(1) + # Resume the app and let it play for a few time. + logging.info('Resume the application.') + app.Resume() + app.WaitUntilPlayerStart() + app.WaitUntilAdsEnd() + # Let the playback play for 2 seconds. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 2) + + +TEST_PARAMETERS = [ + ('H264', PlaybackUrls.H264_ONLY, None), + ('PROGRESSIVE', PlaybackUrls.PROGRESSIVE, None), + ('ENCRYPTED', PlaybackUrls.ENCRYPTED, None), + ('LIVE', PlaybackUrls.LIVE, None), + ('VP9', PlaybackUrls.VP9, MimeStrings.VP9), + ('AV1', PlaybackUrls.AV1, MimeStrings.AV1), + ('VERTICAL', PlaybackUrls.VERTICAL, None), + ('VR', PlaybackUrls.VR, None), +] + +if not TestCase.IsFeatureSupported(Features.SUSPEND_AND_RESUME): + logging.info('Suspend and resume is not supported on this platform.') +else: + for name, playback_url, mime_str in TEST_PARAMETERS: + TestCase.CreateTest(SuspendResumeTest, name, SuspendResumeTest.run_test, + playback_url, mime_str)
diff --git a/src/cobalt/media_integration_tests/media_integration_tests_runner.py b/src/cobalt/media_integration_tests/media_integration_tests_runner.py new file mode 100644 index 0000000..7db2ab2 --- /dev/null +++ b/src/cobalt/media_integration_tests/media_integration_tests_runner.py
@@ -0,0 +1,143 @@ +# Copyright 2021 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. +"""The media integration test runner. + +Usage: + To run all tests of a specific type: + "python media_integration_tests_runner.py + -p android-arm64 -c devel -w noinstall -log-level info + --type functionality" + To run a specific test (suspend_resume): + python media_integration_tests_runner.py + -p android-arm64 -c devel -w noinstall -log-level info + --type functionality --test_name suspend_resume" + +""" + +import argparse +import importlib +import logging +import pkgutil +import sys +import unittest + +import _env # pylint: disable=unused-import +from cobalt.media_integration_tests.test_app import Features +from cobalt.media_integration_tests.test_case import SetLauncherParams, SetSupportedFeatures +from starboard.tools import abstract_launcher +from starboard.tools import command_line +from starboard.tools import log_level + +# Location of test files. +_TESTS_PATH = { + 'functionality': 'cobalt.media_integration_tests.functionality', + 'endurance': 'cobalt.media_integration_tests.endurance', + 'performance': 'cobalt.media_integration_tests.performance' +} + + +def GetSupportedFeatures(launcher_params): + launcher = abstract_launcher.LauncherFactory( + launcher_params.platform, + 'cobalt', + launcher_params.config, + device_id=launcher_params.device_id, + target_params=None, + output_file=None, + out_directory=launcher_params.out_directory, + loader_platform=launcher_params.loader_platform, + loader_config=launcher_params.loader_config, + loader_out_directory=launcher_params.loader_out_directory) + return { + Features.SUSPEND_AND_RESUME: launcher.SupportsSystemSuspendResume(), + Features.SEND_KEYS: True, + } + + +def GetTestPackagePath(test_type): + if _TESTS_PATH.has_key(test_type): + return _TESTS_PATH[test_type] + return None + + +def GetAllTestNamesInPackage(package_path): + test_names = [] + loader = pkgutil.get_loader(package_path) + for sub_module in pkgutil.walk_packages([loader.filename]): + _, sub_module_name, _ = sub_module + # Filter '_env' and '__init__'. + if sub_module_name[0] == '_': + continue + if not sub_module_name in test_names: + test_names.append(sub_module_name) + return test_names + + +def LoadAllTests(package_path, all_test_names): + test_suite = unittest.TestSuite() + for test_name in all_test_names: + module = importlib.import_module(package_path + '.' + test_name) + test_suite.addTest(unittest.TestLoader().loadTestsFromModule(module)) + return test_suite + + +def main(): + # TODO: Support filters. + parser = argparse.ArgumentParser() + parser.add_argument( + '--type', + required=True, + type=str, + help=('Type of the tests to run. It must be functionality, endurance or' + ' performance.')) + parser.add_argument( + '--test_name', + default=None, + type=str, + help=('Name of test to run. If not specified, it will run all tests in' + 'the directory.')) + args, _ = parser.parse_known_args() + + package_path = GetTestPackagePath(args.type) + if package_path is None: + logging.error('{%s} is not a valid test type.', args.type) + return 2 + + all_test_names = GetAllTestNamesInPackage(package_path) + specified_test_name = args.test_name + if specified_test_name is not None: + if not specified_test_name in all_test_names: + logging.error('{%s} is not a valid test name.', specified_test_name) + return 2 + else: + all_test_names = [specified_test_name] + + log_level.InitializeLogging(args) + launcher_params = command_line.CreateLauncherParams() + supported_features = GetSupportedFeatures(launcher_params) + + # Update global variables in test case. + SetLauncherParams(launcher_params) + SetSupportedFeatures(supported_features) + + unittest.installHandler() + + test_suite = LoadAllTests(package_path, all_test_names) + + return not unittest.TextTestRunner( + verbosity=0, stream=sys.stdout).run(test_suite).wasSuccessful() + + +if __name__ == '__main__': + sys.exit(main())
diff --git a/src/cobalt/media_integration_tests/performance/__init__.py b/src/cobalt/media_integration_tests/performance/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/cobalt/media_integration_tests/performance/__init__.py
diff --git a/src/cobalt/media_integration_tests/performance/_env.py b/src/cobalt/media_integration_tests/performance/_env.py new file mode 100644 index 0000000..a9d83bf --- /dev/null +++ b/src/cobalt/media_integration_tests/performance/_env.py
@@ -0,0 +1,26 @@ +# +# Copyright 2021 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. +# +"""Ask the parent directory to load the project environment.""" + +from imp import load_source +from os import path +import sys + +_ENV = path.abspath(path.join(path.dirname(__file__), path.pardir, '_env.py')) +if not path.exists(_ENV): + print '%s: Can\'t find repo root.\nMissing parent: %s' % (__file__, _ENV) + sys.exit(1) +load_source('', _ENV)
diff --git a/src/cobalt/media_integration_tests/performance/codec_capability.py b/src/cobalt/media_integration_tests/performance/codec_capability.py new file mode 100644 index 0000000..c117fd2 --- /dev/null +++ b/src/cobalt/media_integration_tests/performance/codec_capability.py
@@ -0,0 +1,78 @@ +# Copyright 2021 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. +""" Tests of platform codec capabilities.""" + +import logging + +import _env # pylint: disable=unused-import +from cobalt.media_integration_tests.test_case import TestCase +from cobalt.media_integration_tests.test_util import MimeStrings, PlaybackUrls + + +class CodecCapabilityTest(TestCase): + """ + Test cases for platform codec capabilities. + """ + + # Returns a string which shows the max supported resolution, or "n/a" if the + # codec is not supported. + @staticmethod + def run_video_codec_test(app, codec_name, codec_mime): + if app.IsMediaTypeSupported(codec_mime): + for res_name, _ in reversed(MimeStrings.RESOLUTIONS.items()): + if app.IsMediaTypeSupported( + MimeStrings.create_video_mime_string(codec_mime, res_name)): + return '[%s, %s]' % (codec_name, res_name) + return '[%s, n/a]' % (codec_name) + + # Returns a string which shows the max supported channels, or "n/a" if the + # codec is not supported. + @staticmethod + def run_audio_codec_test(app, codec_name, codec_mime): + if app.IsMediaTypeSupported(codec_mime): + for channels in [6, 4, 2]: + if app.IsMediaTypeSupported( + MimeStrings.create_audio_mime_string(codec_mime, channels)): + return '[%s, %s]' % (codec_name, channels) + return '[%s, n/a]' % (codec_name) + + def test_video_codec_capability(self): + app = self.CreateCobaltApp(PlaybackUrls.DEFAULT) + mimes = [ + ('H264', MimeStrings.H264), + ('VP9', MimeStrings.VP9), + ('VP9_HFR', MimeStrings.VP9_HFR), + ('AV1', MimeStrings.AV1), + ('AV1_HFR', MimeStrings.AV1_HFR), + ('VP9_HDR_HLG', MimeStrings.VP9_HDR_HLG), + ('VP9_HDR_PQ', MimeStrings.VP9_HDR_PQ), + ('VP9_HDR_PQ_HFR', MimeStrings.VP9_HDR_PQ_HFR), + ] + result = [' ***** Video codec capability test results: '] + with app: + for mime_name, mime_str in mimes: + result.append(self.run_video_codec_test(app, mime_name, mime_str)) + logging.info(', '.join(result)) + + def test_audio_codec_capability(self): + app = self.CreateCobaltApp(PlaybackUrls.DEFAULT) + mimes = [ + ('AAC', MimeStrings.AAC), + ('OPUS', MimeStrings.OPUS), + ] + result = [' ***** Audio codec capability test results: '] + with app: + for mime_name, mime_str in mimes: + result.append(self.run_audio_codec_test(app, mime_name, mime_str)) + logging.info(', '.join(result))
diff --git a/src/cobalt/media_integration_tests/performance/dropped_frames.py b/src/cobalt/media_integration_tests/performance/dropped_frames.py new file mode 100644 index 0000000..89f426c --- /dev/null +++ b/src/cobalt/media_integration_tests/performance/dropped_frames.py
@@ -0,0 +1,75 @@ +# Copyright 2021 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. +""" Performance tests of dropped frames.""" + +import argparse +import logging + +import _env # pylint: disable=unused-import +from cobalt.media_integration_tests.test_case import TestCase +from cobalt.media_integration_tests.test_util import MimeStrings, PlaybackUrls + + +class DroppedFrameTest(TestCase): + """ + Test cases for playback dropped frames. + """ + + def __init__(self, *args, **kwargs): + super(DroppedFrameTest, self).__init__(*args, **kwargs) + + parser = argparse.ArgumentParser() + parser.add_argument('--test_times', default=5, type=int) + args, _ = parser.parse_known_args() + self.test_times = args.test_times + + def run_test_with_url(self, test_name, url, mime=None): + test_results = [] + for _ in range(self.test_times): + app = self.CreateCobaltApp(url) + with app: + # Skip the test if the mime is not supported. + if mime and not app.IsMediaTypeSupported(mime): + logging.info(' ***** Dropped frame test result: [%s, n/a]', test_name) + return + # Wait until the playback starts. + app.WaitUntilPlayerStart() + app.WaitUntilAdsEnd() + # Let the playback play for 10 seconds. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 10) + + total_frames = app.PlayerState().video_element_state.total_video_frames + dropped_frames = app.PlayerState( + ).video_element_state.dropped_video_frames + dropped_ratio = dropped_frames * 1.0 / total_frames + test_results.append([dropped_ratio, dropped_frames, total_frames]) + logging.info(' ***** Dropped frame test result of %s : %r.', test_name, + test_results) + + +TEST_PARAMETERS = [ + ('H264', PlaybackUrls.H264_ONLY, MimeStrings.H264), + ('VP9', PlaybackUrls.VP9, MimeStrings.VP9), + ('VP9_HFR', PlaybackUrls.VP9_HFR, MimeStrings.VP9_HFR), + ('AV1', PlaybackUrls.AV1, MimeStrings.AV1), + ('AV1_HFR', PlaybackUrls.AV1_HFR, MimeStrings.AV1_HFR), + ('VP9_HDR_HLG', PlaybackUrls.VP9_HDR_HLG, MimeStrings.VP9_HDR_HLG), + ('VP9_HDR_PQ', PlaybackUrls.VP9_HDR_PQ, MimeStrings.VP9_HDR_PQ), + ('VP9_HDR_PQ_HFR', PlaybackUrls.HDR_PQ_HFR, MimeStrings.VP9_HDR_PQ_HFR), +] + +for name, playback_url, mime_str in TEST_PARAMETERS: + TestCase.CreateTest(DroppedFrameTest, name, + DroppedFrameTest.run_test_with_url, name, playback_url, + mime_str)
diff --git a/src/cobalt/media_integration_tests/performance/start_latency.py b/src/cobalt/media_integration_tests/performance/start_latency.py new file mode 100644 index 0000000..62b32f1 --- /dev/null +++ b/src/cobalt/media_integration_tests/performance/start_latency.py
@@ -0,0 +1,157 @@ +# Copyright 2021 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. +""" Performance tests of playback operation latency.""" + +import argparse +import logging +import time + +import _env # pylint: disable=unused-import +from cobalt.media_integration_tests.test_app import AdsState +from cobalt.media_integration_tests.test_case import TestCase +from cobalt.media_integration_tests.test_util import MimeStrings, PlaybackUrls + + +class StartLatencyTest(TestCase): + """ + Test cases for playback operation latency. + """ + + def __init__(self, *args, **kwargs): + super(StartLatencyTest, self).__init__(*args, **kwargs) + + parser = argparse.ArgumentParser() + parser.add_argument('--test_times', default=5, type=int) + args, _ = parser.parse_known_args() + self.test_times = args.test_times + + def run_first_start_latency_test(self, test_name, url, mime=None): + test_results = [] + while len(test_results) < self.test_times: + app = self.CreateCobaltApp(url) + with app: + # Skip the test if the mime is not supported. + if mime and not app.IsMediaTypeSupported(mime): + logging.info(' ***** First start latency test result of %s : [n/a].', + test_name) + return + app.WaitUntilPlayerStart() + start_time = time.time() + start_media_time = app.CurrentMediaTime() + app.WaitUntilReachState(lambda _app: _app.PlayerState( + ).pipeline_state.first_written_video_timestamp != _app.PlayerState(). + pipeline_state.last_written_video_timestamp) + # Record inputs received time to know the preliminary impact of network. + inputs_received_time = time.time() + inputs_received_latency = inputs_received_time - start_time + # Let the playback play for 1 second. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 1) + + # If it's playing ads, wait until ads end and run the test again. + if app.GetAdsState() != AdsState.NONE: + app.WaitUntilAdsEnd() + continue + media_time = app.CurrentMediaTime() - start_media_time + end_time = time.time() + start_latency = end_time - start_time - media_time + test_results.append([ + start_latency, inputs_received_latency, start_time, + inputs_received_time, media_time, end_time + ]) + logging.info(' ***** First start latency test result of %s : %r.', + test_name, test_results) + + def run_play_pause_latency_test(self, test_name, url, mime=None): + app = self.CreateCobaltApp(url) + with app: + # Skip the test if the mime is not supported. + if mime and not app.IsMediaTypeSupported(mime): + logging.info(' ***** Play/pause latency test result of %s : [n/a].', + test_name) + return + test_results = [] + # Wait until the playback starts. + app.WaitUntilPlayerStart() + app.WaitUntilAdsEnd() + while len(test_results) < self.test_times: + # Let the playback play for 1 second. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 1) + start_time = time.time() + # Pause the playback. + app.PlayOrPause() + app.WaitUntilReachState( + lambda _app: _app.PlayerState().video_element_state.paused) + pause_latency = time.time() - start_time + + start_time = time.time() + # Resume the playback. + app.PlayOrPause() + app.WaitUntilReachState( + lambda _app: not _app.PlayerState().video_element_state.paused) + + play_latency = time.time() - start_time + test_results.append([play_latency, pause_latency]) + logging.info(' ***** Play/pause latency test result of %s : %r.', + test_name, test_results) + + def run_fastforward_latency_test(self, test_name, url, mime=None): + app = self.CreateCobaltApp(url) + with app: + # Skip the test if the mime is not supported. + if mime and not app.IsMediaTypeSupported(mime): + logging.info(' ***** Fastforward latency test result of %s : [n/a].', + test_name) + return + test_results = [] + # Wait until the playback starts. + app.WaitUntilPlayerStart() + app.WaitUntilAdsEnd() + # Let the playback play for 1 second. + app.WaitUntilMediaTimeReached(app.CurrentMediaTime() + 1) + while len(test_results) < self.test_times: + old_media_time = app.CurrentMediaTime() + start_time = time.time() + app.Fastforward() + app.WaitUntilReachState(lambda _app: _app.PlayerState( + ).pipeline_state.first_written_video_timestamp != _app.PlayerState(). + pipeline_state.last_written_video_timestamp) + # Record inputs received time to know the preliminary impact of network. + inputs_received_latency = time.time() - start_time + app.WaitUntilReachState( + lambda _app: _app.CurrentMediaTime() > old_media_time + 10) + fastforward_latency = time.time() - start_time + test_results.append([fastforward_latency, inputs_received_latency]) + + logging.info(' ***** Fastforward latency test result of %s : %r.', + test_name, test_results) + + +TEST_PARAMETERS = [ + ('H264', PlaybackUrls.H264_ONLY, MimeStrings.H264), + ('VP9', PlaybackUrls.VP9, MimeStrings.VP9), + ('AV1', PlaybackUrls.AV1, MimeStrings.AV1), + ('VP9_HDR_HLG', PlaybackUrls.VP9_HDR_HLG, MimeStrings.VP9_HDR_HLG), + ('VP9_HDR_PQ', PlaybackUrls.VP9_HDR_PQ, MimeStrings.VP9_HDR_PQ), +] + +for name, playback_url, mime_str in TEST_PARAMETERS: + TestCase.CreateTest(StartLatencyTest, 'first_start_latency_%s' % (name), + StartLatencyTest.run_first_start_latency_test, name, + playback_url, mime_str) + TestCase.CreateTest(StartLatencyTest, 'play_pause_latency_%s' % (name), + StartLatencyTest.run_play_pause_latency_test, name, + playback_url, mime_str) + TestCase.CreateTest(StartLatencyTest, 'fastforward_latency_%s' % (name), + StartLatencyTest.run_fastforward_latency_test, name, + playback_url, mime_str)
diff --git a/src/cobalt/media_integration_tests/test_app.py b/src/cobalt/media_integration_tests/test_app.py new file mode 100644 index 0000000..8503dd5 --- /dev/null +++ b/src/cobalt/media_integration_tests/test_app.py
@@ -0,0 +1,855 @@ +# Copyright 2021 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. +"""A module to support fundamental communications with Cobalt application.""" + +import json +import logging +import threading +import time + +import _env # pylint: disable=unused-import +from cobalt.tools.automated_testing import cobalt_runner +from cobalt.tools.automated_testing import webdriver_utils + +webdriver_keys = webdriver_utils.import_selenium_module('webdriver.common.keys') + +PERIODIC_QUERIES_INTERVAL_SECONDS = 0.1 +THREAD_EXIT_TIMEOUT_SECONDS = 10 +WAIT_INTERVAL_SECONDS = 0.5 +WAIT_UNTIL_REACH_STATE_DEFAULT_TIMEOUT_SECONDS = 30 +WAIT_UNTIL_ADS_END_DEFAULT_TIMEOUT_SECONDS = 120 +WAIT_UNTIL_MEDIA_TIME_REACHED_DEFAULT_TIMEOUT_SECONDS = 30 + +ACCOUNT_SELECTOR_ADD_ACCOUNT_TEXT = u'Add account' + + +def GetValueFromQueryResult(query_result, key, default): + if query_result is None: + return default + + if not isinstance(query_result, dict): + return default + + if not query_result.has_key(key): + return default + + value = query_result[key] + default_value_type = type(default) + value_type = type(value) + + if value is None: + return default + + if value_type == default_value_type: + return value + + if value_type in (int, float) and default_value_type in (int, float): + return value + + # CVal returns unicode, so we need to decode them before using. + if default_value_type in (bool, int, float): + if value_type in (str, unicode): + try: + # Try to parse numbers and booleans. + parsed_value = json.loads(value) + except ValueError: + raise RuntimeError('Failed to parse query result.') + + return parsed_value + + elif default_value_type is str: + if value_type == unicode: + return value.encode('utf-8') + + raise NotImplementedError('Convertion from (%s) to (%s) is not suppoted.' % + (value_type, default_value_type)) + + +class AdditionalKeys(): + """ + Set of special keys codes for media control, corresponding to cobalt + webdriver AdditionalSpecialKey. + """ + MEDIA_NEXT_TRACK = u'\uf000' + MEDIA_PREV_TRACK = u'\uf001' + MEDIA_STOP = u'\uf002' + MEDIA_PLAY_PAUSE = u'\uf003' + MEDIA_REWIND = u'\uf004' + MEDIA_FAST_FORWARD = u'\uf005' + + +class Features(): + """Set of platform features.""" + SUSPEND_AND_RESUME = 1 + SEND_KEYS = 2 + + +class ApplicationState(): + """Set of platform features.""" + + def __init__(self, query_result=None): + if not query_result is None and not isinstance(query_result, dict): + raise NotImplementedError + + self.is_visible = GetValueFromQueryResult(query_result, 'is_visible', False) + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise NotImplementedError + return self.is_visible == other.is_visible + + def __ne__(self, other): + return not self.__eq__(other) + + @staticmethod + def QueryJsCode(): + return """ + return { + is_visible: document.visibilityState === "visible", + } + """ + + +class ApplicationStateHandler(): + """The handler of applicatoin state.""" + + def __init__(self, app): + self.app = app + self.state_change_handlers = [] + self.app_state = ApplicationState() + + self.app.RegisterPeriodicQuery('Application', + ApplicationState.QueryJsCode(), + self._ApplicatinStateQueryHandler) + + def AddAppStateChangeHandler(self, handler): + self.state_change_handlers.append(handler) + + def RemoveAppStateChangeHandler(self, handler): + self.state_change_handlers.remove(handler) + + def _NotifyHandlersOnStateChanged(self): + for handler in self.state_change_handlers: + handler(self, self.app, self.app_state) + + def _ApplicatinStateQueryHandler(self, app, query_name, query_result): + del app, query_name # Unused here. + # Note that the callback is invoked on a different thread. + new_state = ApplicationState(query_result) + if self.app_state != new_state: + self.app_state = new_state + self._NotifyHandlersOnStateChanged() + + +class PipelinePlayerState(int): + """Set of pipeline states, equals to SbPlayerState.""" + INITIALIZED = 0 # kSbPlayerStateInitialized + PREROLLING = 1 # kSbPlayerStatePrerolling + PRESENTING = 2 # kSbPlayerStatePresenting + ENDOFSTREAM = 3 # kSbPlayerStateEndOfStream + DESTROYED = 4 # kSbPlayerStateDestroyed + + +class PipelineState(): + """ + The states of SbPlayerPipeline. We update the states periodically via + webdriver and logs. Note that the duration and timestamps are in + milliseconds. + """ + + def __init__(self, query_result=None): + # If there's no player existing, the query return unicode code "null". + if (not query_result is None and not query_result == u'null' and + not isinstance(query_result, dict)): + raise NotImplementedError + + self.identifier = GetValueFromQueryResult(query_result, 'identifier', '') + self.is_started = GetValueFromQueryResult(query_result, 'is_started', False) + self.is_suspended = GetValueFromQueryResult(query_result, 'is_suspended', + False) + self.is_stopped = GetValueFromQueryResult(query_result, 'is_stopped', False) + self.is_ended = GetValueFromQueryResult(query_result, 'is_ended', False) + self.player_state = PipelinePlayerState( + GetValueFromQueryResult(query_result, 'player_state', 0)) + self.volume = GetValueFromQueryResult(query_result, 'volume', 1.0) + self.playback_rate = GetValueFromQueryResult(query_result, 'playback_rate', + 1.0) + self.duration = GetValueFromQueryResult(query_result, 'duration', 0) + self.last_media_time = GetValueFromQueryResult(query_result, + 'last_media_time', 0) + self.max_video_capabilities = GetValueFromQueryResult( + query_result, 'max_video_capabilities', '') + self.seek_time = GetValueFromQueryResult(query_result, 'seek_time', 0) + self.first_written_audio_timestamp = GetValueFromQueryResult( + query_result, 'first_written_audio_timestamp', 0) + self.first_written_video_timestamp = GetValueFromQueryResult( + query_result, 'first_written_video_timestamp', 0) + self.last_written_audio_timestamp = GetValueFromQueryResult( + query_result, 'last_written_audio_timestamp', 0) + self.last_written_video_timestamp = GetValueFromQueryResult( + query_result, 'last_written_video_timestamp', 0) + self.video_width = GetValueFromQueryResult(query_result, 'video_width', 0) + self.video_height = GetValueFromQueryResult(query_result, 'video_height', 0) + self.is_audio_eos_written = GetValueFromQueryResult(query_result, + 'is_audio_eos_written', + False) + self.is_video_eos_written = GetValueFromQueryResult(query_result, + 'is_video_eos_written', + False) + # If |pipeline_status| is not 0, it indicates there's an error + # in the pipeline. + self.pipeline_status = GetValueFromQueryResult(query_result, + 'pipeline_status', 0) + self.error_message = GetValueFromQueryResult(query_result, 'error_message', + '') + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise NotImplementedError + return (self.identifier == other.identifier and + self.is_started == other.is_started and + self.is_suspended == other.identifier and + self.is_stopped == other.identifier and + self.is_ended == other.is_ended and + self.player_state == other.player_state and + self.volume == other.volume and + self.playback_rate == other.playback_rate and + self.duration == other.duration and + self.last_media_time == other.last_media_time and + self.max_video_capabilities == other.max_video_capabilities and + self.seek_time == other.seek_time and + self.first_written_audio_timestamp + == other.first_written_audio_timestamp and + self.first_written_video_timestamp + == other.first_written_video_timestamp and + self.last_written_audio_timestamp + == other.last_written_audio_timestamp and + self.last_written_video_timestamp + == other.last_written_video_timestamp and + self.video_width == other.video_width and + self.video_height == other.video_height and + self.is_audio_eos_written == other.is_audio_eos_written and + self.is_video_eos_written == other.is_video_eos_written and + self.pipeline_status == other.pipeline_status and + self.error_message == other.error_message) + + def __ne__(self, other): + return not self.__eq__(other) + + @staticmethod + def QueryJsCode(): + return """ + const primary_pipeline_keys = h5vcc.cVal.keys().filter(key => + key.startsWith("Media.Pipeline.") && + key.endsWith("MaxVideoCapabilities") && + h5vcc.cVal.getValue(key).length === 0); + if (primary_pipeline_keys.length == 0) { + return "null"; + } + const key_prefix = primary_pipeline_keys[0].slice(0, + -".MaxVideoCapabilities".length); + return { + identifier: key_prefix.slice("Media.Pipeline.".length), + is_started: h5vcc.cVal.getValue(key_prefix + '.Started'), + is_suspended: h5vcc.cVal.getValue(key_prefix + '.Suspended'), + is_stopped: h5vcc.cVal.getValue(key_prefix + '.Stopped'), + is_ended: h5vcc.cVal.getValue(key_prefix + '.Ended'), + player_state: h5vcc.cVal.getValue(key_prefix + '.PlayerState'), + volume: h5vcc.cVal.getValue(key_prefix + '.Volume'), + playback_rate: h5vcc.cVal.getValue(key_prefix + '.PlaybackRate'), + duration: h5vcc.cVal.getValue(key_prefix + '.Duration'), + last_media_time: h5vcc.cVal.getValue(key_prefix + '.LastMediaTime'), + max_video_capabilities: h5vcc.cVal.getValue( + key_prefix + '.MaxVideoCapabilities'), + seek_time: h5vcc.cVal.getValue(key_prefix + '.SeekTime'), + first_written_audio_timestamp: + h5vcc.cVal.getValue(key_prefix + '.FirstWrittenAudioTimestamp'), + first_written_video_timestamp: + h5vcc.cVal.getValue(key_prefix + '.FirstWrittenVideoTimestamp'), + last_written_audio_timestamp: + h5vcc.cVal.getValue(key_prefix + '.LastWrittenAudioTimestamp'), + last_written_video_timestamp: + h5vcc.cVal.getValue(key_prefix + '.LastWrittenVideoTimestamp'), + video_width: h5vcc.cVal.getValue(key_prefix + '.VideoWidth'), + video_height: h5vcc.cVal.getValue(key_prefix + '.VideoHeight'), + is_audio_eos_written: h5vcc.cVal.getValue(key_prefix + + '.IsAudioEOSWritten'), + is_video_eos_written: h5vcc.cVal.getValue(key_prefix + + '.IsVideoEOSWritten'), + pipeline_status: h5vcc.cVal.getValue(key_prefix + '.PipelineStatus'), + error_message: h5vcc.cVal.getValue(key_prefix + '.ErrorMessage'), + }; + """ + + def IsPlaying(self): + return (len(self.identifier) > 0 and self.is_started and + not self.is_ended and not self.is_suspended and not self.is_stopped) + + +class MediaSessionPlaybackState(int): + """Set of media session playback states.""" + NONE = 0 + PAUSED = 1 + PLAYING = 2 + + @staticmethod + def FromString(str): + if str == 'none': + return 0 + if str == 'paused': + return 1 + if str == 'playing': + return 2 + raise NotImplementedError( + '"%s" is not a valid media session playback state.' % str) + + +class MediaSessionState(): + """ + The info we get from HTML MediaSession. We update the states periodically + via webdriver. + """ + + def __init__(self, query_result=None): + if not query_result is None and not isinstance(query_result, dict): + raise NotImplementedError + + metadata = None + if isinstance(query_result, dict) and query_result.has_key('metadata'): + metadata = query_result['metadata'] + + self.title = GetValueFromQueryResult(metadata, 'title', '') + self.artist = GetValueFromQueryResult(metadata, 'artist', '') + self.album = GetValueFromQueryResult(metadata, 'album', '') + self.playback_state = MediaSessionPlaybackState.FromString( + GetValueFromQueryResult(query_result, 'playbackState', 'none')) + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise NotImplementedError + return (self.title == other.title and self.artist == other.artist and + self.album == other.album and + self.playback_state == other.playback_state) + + def __ne__(self, other): + if self.__eq__(other): + return False + return True + + @staticmethod + def QueryJsCode(): + return """ + return { + metadata: navigator.mediaSession.metadata, + playbackState: navigator.mediaSession.playbackState, + }; + """ + + +class VideoElementState(): + """ + The info we get from HTML video element. We update the states periodically + via webdriver. + """ + + def __init__(self, query_result=None): + # If there's no player existing, the query return unicode code "null". + if (not query_result is None and not query_result == u'null' and + not isinstance(query_result, dict)): + raise NotImplementedError + + self.has_player = isinstance(query_result, dict) + self.current_time = GetValueFromQueryResult(query_result, 'current_time', + 0.0) + self.duration = GetValueFromQueryResult(query_result, 'duration', 0.0) + self.paused = GetValueFromQueryResult(query_result, 'paused', False) + self.playback_rate = GetValueFromQueryResult(query_result, 'playback_rate', + 0.0) + self.volume = GetValueFromQueryResult(query_result, 'volume', 0.0) + self.dropped_video_frames = GetValueFromQueryResult(query_result, + 'dropped_video_frames', + 0) + self.total_video_frames = GetValueFromQueryResult(query_result, + 'total_video_frames', 0) + + def __eq__(self, other): + if not isinstance(other, self.__class__): + raise NotImplementedError + return (self.has_player == other.has_player and + self.current_time == other.current_time and + self.duration == other.duration and self.paused == other.paused and + self.playback_rate == other.playback_rate and + self.volume == other.volume and + self.dropped_video_frames == other.dropped_video_frames and + self.total_video_frames == other.total_video_frames) + + def __ne__(self, other): + return not self.__eq__(other) + + @staticmethod + def QueryJsCode(): + # Looking for a full screen video tag and return that as the primary player. + return """ + const players = document.querySelectorAll("video"); + if (players && players.length > 0) { + for (let i = 0; i < players.length; i++) { + const player = players[i]; + const rect = player.getBoundingClientRect(); + if (rect.width === window.innerWidth || + rect.height === window.innerHeight) { + const quality = player.getVideoPlaybackQuality(); + return { + current_time: player.currentTime, + duration: player.duration, + paused: player.paused, + playback_rate: player.playbackRate, + volume: player.volume, + dropped_video_frames: quality.droppedVideoFrames, + total_video_frames: quality.totalVideoFrames, + }; + } + } + } + return "null"; + """ + + +class PlayerStateHandler(): + """The handler of player state.""" + + def __init__(self, app): + self.app = app + self.state_change_handlers = [] + self.player_state = type('', (), {})() + self.player_state.pipeline_state = PipelineState() + self.player_state.media_session_state = MediaSessionState() + self.player_state.video_element_state = VideoElementState() + + self.app.RegisterPeriodicQuery('MediaPipeline', PipelineState.QueryJsCode(), + self._PipelineStateQueryHandler) + self.app.RegisterPeriodicQuery('MediaSession', + MediaSessionState.QueryJsCode(), + self._MediaSessionStateQueryHandler) + self.app.RegisterPeriodicQuery('VideoElement', + VideoElementState.QueryJsCode(), + self._VideoElementStateQueryHandler) + + def AddPlayerStateChangeHandler(self, handler): + self.state_change_handlers.append(handler) + + def RemovePlayerStateChangeHandler(self, handler): + self.state_change_handlers.remove(handler) + + def IsPlayerPlaying(self): + return self.player_state.pipeline_state.IsPlaying() + + def _NotifyHandlersOnStateChanged(self): + for handler in self.state_change_handlers: + handler(self, self.player_state) + + def _PipelineStateQueryHandler(self, app, query_name, query_result): + # Note that the callback is invoked on a different thread. + del app, query_name # Unused here. + new_state = PipelineState(query_result) + if self.player_state.pipeline_state != new_state: + self.player_state.pipeline_state = new_state + self._NotifyHandlersOnStateChanged() + + def _MediaSessionStateQueryHandler(self, app, query_name, query_result): + # Note that the callback is invoked on a different thread. + del app, query_name # Unused here. + new_state = MediaSessionState(query_result) + if self.player_state.media_session_state != new_state: + self.player_state.media_session_state = new_state + self._NotifyHandlersOnStateChanged() + + def _VideoElementStateQueryHandler(self, app, query_name, query_result): + # Note that the callback is invoked on a different thread. + del app, query_name # Unused here. + new_state = VideoElementState(query_result) + if self.player_state.video_element_state != new_state: + self.player_state.video_element_state = new_state + self._NotifyHandlersOnStateChanged() + + +class AdsState(int): + """Set of ads states. The numeric values are used in ads state query.""" + NONE = 0 + PLAYING = 1 + PLAYING_AND_SKIPPABLE = 2 + + +class TestApp(): + """ + A class encapsulating fundamental functions to control and query data + from Cobalt application. + """ + + def __init__(self, + launcher_params, + url, + supported_features, + log_file=None, + target_params=None, + success_message=None): + self.supported_features = supported_features + self.runner = cobalt_runner.CobaltRunner( + launcher_params=launcher_params, + url=url, + log_handler=self._OnNewLogLine, + log_file=log_file, + target_params=target_params, + success_message=success_message) + self.log_handlers = [] + self.is_queries_changed = False + self.periodic_queries = {} + self.periodic_queries_lock = threading.Lock() + self.should_exit = threading.Event() + self.periodic_query_thread = threading.Thread( + target=self._RunPeriodicQueries) + + self.app_state_handler = ApplicationStateHandler(self) + self.player_state_handler = PlayerStateHandler(self) + + def __enter__(self): + try: + self.runner.__enter__() + except Exception: + # Potentially from thread.interrupt_main(). No need to start + # query thread. + return self + + self.periodic_query_thread.start() + return self + + def __exit__(self, *args): + self.should_exit.set() + if self.periodic_query_thread.is_alive(): + self.periodic_query_thread.join(THREAD_EXIT_TIMEOUT_SECONDS) + return self.runner.__exit__(*args) + + def ExecuteScript(self, script): + try: + result = self.runner.webdriver.execute_script(script) + except Exception as e: + raise RuntimeError('Fail to excute script with error (%s).' % (str(e))) + return result + + def _OnNewLogLine(self, line): + # Note that the function is called on cobalt runner reader thread. + # pass + self._NotifyHandlersOnNewLogLine(line) + + def _NotifyHandlersOnNewLogLine(self, line): + for handler in self.log_handlers: + handler(self, line) + + def ApplicationState(self): + return self.app_state_handler.app_state + + def PlayerState(self): + return self.player_state_handler.player_state + + def CurrentMediaTime(self): + # Convert timestamp from millisecond to second. + return float(self.PlayerState().pipeline_state.last_media_time) / 1000000.0 + + def Suspend(self): + if not self.supported_features[Features.SUSPEND_AND_RESUME]: + raise RuntimeError('Suspend is not supported.') + if not self.runner.launcher_is_running: + raise RuntimeError('Runner is not running.') + self.runner.SendSystemSuspend() + + def Resume(self): + if not self.supported_features[Features.SUSPEND_AND_RESUME]: + raise RuntimeError('Resume is not supported.') + if not self.runner.launcher_is_running: + raise RuntimeError('Runner is not running.') + self.runner.SendSystemResume() + + def SendKeys(self, keys): + if not self.supported_features[Features.SEND_KEYS]: + raise RuntimeError('SendKeys is not supported.') + if not self.runner.launcher_is_running: + raise RuntimeError('Runner is not running.') + self.runner.SendKeys(keys) + + # The handler will receive parameters (TestApp, String). + def AddLogHandler(self, handler): + self.log_handlers.append(handler) + + def RemoveLogHandler(self, handler): + self.log_handlers.remove(handler) + + # The handler will receive parameters (TestApp, ApplicationState). + def AddAppStateChangeHandler(self, handler): + self.app_state_handler.AddAppStateChangeHandler(handler) + + def RemoveAppStateChangeHandler(self, handler): + self.app_state_handler.RemoveAppStateChangeHandler(handler) + + # The handler will receive parameters (TestApp, PlayerState). + def AddPlayerStateChangeHandler(self, handler): + self.player_state_handler.AddPlayerStateChangeHandler(handler) + + def RemovePlayerStateChangeHandler(self, handler): + self.player_state_handler.RemovePlayerStateChangeHandler(handler) + + # The handler will receive parameters (TestApp, String, Dictionary). + def RegisterPeriodicQuery(self, query_name, query_js_code, result_handler): + if not isinstance(query_name, str): + raise RuntimeError('Query name must be a string.') + if self.periodic_queries.get(query_name): + raise RuntimeError('Duplicate query name is not allowed.') + # TODO: Validate js code. + with self.periodic_queries_lock: + self.is_queries_changed = True + self.periodic_queries[query_name] = (query_js_code, result_handler) + + def UnregisterPeriodicQuery(self, query_name): + if not isinstance(query_name, str): + raise RuntimeError('Query name must be a string.') + with self.periodic_queries_lock: + self.is_queries_changed = True + self.periodic_queries.pop(query_name) + + def _RunPeriodicQueries(self): + while True: + if self.should_exit.is_set(): + break + # Wait until webdriver client attached to Cobalt. + if self.runner.test_script_started.is_set(): + with self.periodic_queries_lock: + local_is_queries_changed = self.is_queries_changed + local_periodic_queries = self.periodic_queries + self.is_queries_changed = False + # Skip if there's no registered query. + if len(local_periodic_queries) == 0: + continue + # Generate javascript code and execute it. + js_code = self._GeneratePeriodicQueryJsCode(local_is_queries_changed, + local_periodic_queries) + result = self.ExecuteScript(js_code) + for query_name in local_periodic_queries.keys(): + if not result.get(query_name): + raise RuntimeError( + 'There\'s something wrong in the queries result.') + local_periodic_queries[query_name][1](self, query_name, + result[query_name]) + + time.sleep(PERIODIC_QUERIES_INTERVAL_SECONDS) + + _PERIODIC_QUERIES_JS_CODE = """ + let ret = {}; + for(let key in _media_integration_testing_queries) { + ret[key] = _media_integration_testing_queries[key](); + } + return ret; + """ + + def _GeneratePeriodicQueryJsCode(self, is_queries_changed, periodic_queries): + js_code = '' + # Update registered queries at host side. + if is_queries_changed: + js_code += '_media_integration_testing_queries = {' + for query_name in periodic_queries: + js_code += query_name + ': function() {' + js_code += periodic_queries[query_name][0] + js_code += '},' + js_code += '}' + # Run queries and collect results. + js_code += self._PERIODIC_QUERIES_JS_CODE + return js_code + + # The first input of |state_lambda| is an instance of TestApp. + def WaitUntilReachState( + self, + state_lambda, + timeout=WAIT_UNTIL_REACH_STATE_DEFAULT_TIMEOUT_SECONDS): + start_time = time.time() + while not state_lambda(self) and time.time() - start_time < timeout: + time.sleep(WAIT_INTERVAL_SECONDS) + execute_interval = time.time() - start_time + if execute_interval > timeout: + raise RuntimeError('WaitUntilReachState timed out after (%f) seconds.' % + (execute_interval)) + + # The result is an array of overlay header text contents. + _OVERLAY_QUERY_JS_CODE = """ + let result = []; + if (document.getElementsByTagName( + "YTLR-OVERLAY-PANEL-HEADER-RENDERER").length > 0) { + let childNodes = document.getElementsByTagName( + "YTLR-OVERLAY-PANEL-HEADER-RENDERER")[0].childNodes; + for (let i = 0; i < childNodes.length; i++) { + result.push(childNodes[i].textContent); + } + } + return result; + """ + + def WaitUntilPlayerStart( + self, timeout=WAIT_UNTIL_REACH_STATE_DEFAULT_TIMEOUT_SECONDS): + + def PlayerStateCheckCallback(app): + # Check if it's showing account selector page. + is_showing_account_selector = self.ExecuteScript( + 'return document.getElementsByTagName("YTLR-ACCOUNT-SELECTOR")' + '.length > 0;') + if is_showing_account_selector: + active_element_label_attr = self.ExecuteScript( + 'return document.activeElement.getAttribute("aria-label");') + if active_element_label_attr != ACCOUNT_SELECTOR_ADD_ACCOUNT_TEXT: + logging.info('Select an account (%s) to continue the test.', + active_element_label_attr) + self.SendKeys(webdriver_keys.Keys.ENTER) + else: + logging.info('Current selected item is "Add acount", move to next' + ' item.') + self.SendKeys(webdriver_keys.Keys.ARROW_RIGHT) + return False + # Check if it's showing a playback survey. + is_showing_skip_button = self.ExecuteScript( + 'return document.getElementsByTagName("YTLR-SKIP-BUTTON-RENDERER")' + '.length > 0;') + # When there's a skip button and no running player, it's showing a + # survey. + if (is_showing_skip_button and + not app.player_state_handler.IsPlayerPlaying()): + self.SendKeys(webdriver_keys.Keys.ENTER) + logging.info('Send enter key event to skip the survey.') + return False + # Check if it's showing an overlay. + overlay_query_result = self.ExecuteScript(self._OVERLAY_QUERY_JS_CODE) + if len(overlay_query_result) > 0: + # Note that if there're playback errors, after close the overlay, + # the test will end with timeout error. + self.SendKeys(webdriver_keys.Keys.ENTER) + logging.info( + 'Send enter key event to close the overlay. Overlay ' + 'headers : %r', overlay_query_result) + return False + + return app.player_state_handler.IsPlayerPlaying() + + self.WaitUntilReachState(PlayerStateCheckCallback, timeout) + + def WaitUntilPlayerDestroyed( + self, timeout=WAIT_UNTIL_REACH_STATE_DEFAULT_TIMEOUT_SECONDS): + current_identifier = self.PlayerState().pipeline_state.identifier + if not current_identifier or len(current_identifier) == 0: + raise RuntimeError('No existing player when calling' + 'WaitUntilPlayerDestroyed.') + self.WaitUntilReachState( + lambda _app: _app.PlayerState().pipeline_state.identifier != + current_identifier, timeout) + + """ + The return values of the query, exact mapping of AdsState defined earlier + in this file. + """ + _GET_ADS_STATE_QUERY_JS_CODE = """ + if (document.getElementsByTagName("YTLR-AD-PREVIEW-RENDERER").length > 0) { + return 1; + } + if (document.getElementsByTagName("YTLR-SKIP-BUTTON-RENDERER").length > 0) { + return 2; + } + return 0; + """ + + def GetAdsState(self): + if not self.runner.test_script_started.is_set(): + raise RuntimeError('Webdriver is not ready yet') + result = self.ExecuteScript(self._GET_ADS_STATE_QUERY_JS_CODE) + return AdsState(result) + + def WaitUntilAdsEnd(self, timeout=WAIT_UNTIL_ADS_END_DEFAULT_TIMEOUT_SECONDS): + start_time = time.time() + # There may be multiple ads and we need to wait all of them finished. + while time.time() - start_time < timeout: + # Wait until the content starts, otherwise the ads state may not be right. + self.WaitUntilReachState(lambda _app: _app.CurrentMediaTime() > 0, + timeout) + ads_state = self.GetAdsState() + if ads_state == AdsState.NONE: + break + while ads_state != AdsState.NONE and time.time() - start_time < timeout: + ads_state = self.GetAdsState() + if ads_state == AdsState.PLAYING_AND_SKIPPABLE: + self.SendKeys(webdriver_keys.Keys.ENTER) + logging.info('Send enter key event to skip the ad.') + time.sleep(WAIT_INTERVAL_SECONDS) + + execute_interval = time.time() - start_time + if execute_interval > timeout: + raise RuntimeError( + 'WaitUntilReachState timed out after (%f) seconds, ads_state: (%d).' % + (execute_interval, ads_state)) + + def WaitUntilMediaTimeReached( + self, + media_time, + timeout=WAIT_UNTIL_MEDIA_TIME_REACHED_DEFAULT_TIMEOUT_SECONDS): + start_media_time = self.CurrentMediaTime() + if start_media_time > media_time: + return + + # Wait until playback starts. + self.WaitUntilReachState( + lambda _app: _app.PlayerState().pipeline_state.playback_rate > 0, + timeout) + + duration = self.PlayerState().video_element_state.duration + if media_time > duration: + logging.info( + 'Requested media time (%f) is greater than the duration (%f)', + media_time, duration) + media_time = duration + + time_to_play = max(media_time - self.CurrentMediaTime(), 0) + playback_rate = self.PlayerState().pipeline_state.playback_rate + adjusted_timeout = time_to_play / playback_rate + timeout + self.WaitUntilReachState(lambda _app: _app.CurrentMediaTime() > media_time, + adjusted_timeout) + + def IsMediaTypeSupported(self, mime): + return self.ExecuteScript('return MediaSource.isTypeSupported("%s");' % + (mime)) + + def PlayOrPause(self): + self.SendKeys(AdditionalKeys.MEDIA_PLAY_PAUSE) + + def Fastforward(self): + # The first fastforward will only bring up the progress bar. + self.SendKeys(AdditionalKeys.MEDIA_FAST_FORWARD) + # The second fastforward will forward the playback by 10 seconds. + self.SendKeys(AdditionalKeys.MEDIA_FAST_FORWARD) + # Press play button to start the playback. + self.SendKeys(AdditionalKeys.MEDIA_PLAY_PAUSE) + + def Rewind(self): + # The first rewind will only bring up the progress bar. + self.SendKeys(AdditionalKeys.MEDIA_REWIND) + # The second rewind will rewind the playback by 10 seconds. + self.SendKeys(AdditionalKeys.MEDIA_REWIND) + # It needs to press play button to start the playback. + self.SendKeys(AdditionalKeys.MEDIA_PLAY_PAUSE) + + def PlayPrevious(self): + self.SendKeys(AdditionalKeys.MEDIA_PREV_TRACK) + + def PlayNext(self): + self.SendKeys(AdditionalKeys.MEDIA_NEXT_TRACK)
diff --git a/src/cobalt/media_integration_tests/test_case.py b/src/cobalt/media_integration_tests/test_case.py new file mode 100644 index 0000000..04b942a --- /dev/null +++ b/src/cobalt/media_integration_tests/test_case.py
@@ -0,0 +1,67 @@ +# Copyright 2021 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. +"""The module of base media integration test case.""" + +import logging +import unittest + +import _env # pylint: disable=unused-import +from cobalt.media_integration_tests.test_app import TestApp + +# Global variables +_launcher_params = None +_supported_features = None + + +def SetLauncherParams(launcher_params): + global _launcher_params + _launcher_params = launcher_params + + +def SetSupportedFeatures(supported_features): + global _supported_features + _supported_features = supported_features + + +class TestCase(unittest.TestCase): + """The base class for media integration test cases.""" + + def __init__(self, *args, **kwargs): + super(TestCase, self).__init__(*args, **kwargs) + self.launcher_params = _launcher_params + self.supported_features = _supported_features + + def setUp(self): + logging.info('Running %s', str(self.id())) + + def tearDown(self): + logging.info('Done %s', str(self.id())) + + def CreateCobaltApp(self, url): + app = TestApp( + launcher_params=self.launcher_params, + supported_features=self.supported_features, + url=url) + return app + + @staticmethod + def IsFeatureSupported(feature): + global _supported_features + return _supported_features[feature] + + @staticmethod + def CreateTest(test_class, test_name, test_function, *args): + test_method = lambda self: test_function(self, *args) + test_method.__name__ = 'test_%s' % test_name + setattr(test_class, test_method.__name__, test_method)
diff --git a/src/cobalt/media_integration_tests/test_util.py b/src/cobalt/media_integration_tests/test_util.py new file mode 100644 index 0000000..6bd08d9 --- /dev/null +++ b/src/cobalt/media_integration_tests/test_util.py
@@ -0,0 +1,88 @@ +# Copyright 2021 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. +"""The module of base media integration test case.""" + +from collections import OrderedDict + +import _env # pylint: disable=unused-import + + +class MimeStrings(): + """ + Set of playback mime strings. + """ + + H264 = 'video/mp4; codecs=\\"avc1.4d4015\\"' + VP9 = 'video/webm; codecs=\\"vp9\\"' + VP9_HFR = 'video/webm; codecs=\\"vp9\\"; framerate=60' + AV1 = 'video/mp4; codecs=\\"av01.0.08M.08\\"' + AV1_HFR = 'video/mp4; codecs=\\"av01.0.08M.08\\"; framerate=60' + VP9_HDR_HLG = 'video/webm; codecs=\\"vp09.02.51.10.01.09.18.09.00\\"' + VP9_HDR_PQ = 'video/webm; codecs=\\"vp09.02.51.10.01.09.16.09.00\\"' + VP9_HDR_PQ_HFR = ('video/webm; codecs=\\"vp09.02.51.10.01.09.16.09.00\\";' + 'framerate=60') + + AAC = 'audio/mp4; codecs=\\"mp4a.40.2\\"' + OPUS = 'audio/webm; codecs=\\"opus\\"' + + RESOLUTIONS = OrderedDict([('140P', 'width=256; height=144'), + ('240P', 'width=352; height=240'), + ('360P', 'width=480; height=360'), + ('480P', 'width=640; height=480'), + ('720P', 'width=1280; height=720'), + ('1080P', 'width=1920; height=1080'), + ('2K', 'width=2560; height=1440'), + ('4K', 'width=3840; height=2160'), + ('8K', 'width=7680; height=4320')]) + + @staticmethod + def create_video_mime_string(codec, resolution): + return '%s; %s' % (codec, MimeStrings.RESOLUTIONS[resolution]) + + @staticmethod + def create_audio_mime_string(codec, channels): + return '%s; channels=%d' % (codec, channels) + + +class PlaybackUrls(): + """ + Set of testing video urls. + """ + + DEFAULT = 'https://www.youtube.com/tv' + H264_ONLY = 'https://www.youtube.com/tv#/watch?v=RACW52qnJMI' + PROGRESSIVE = 'https://www.youtube.com/tv#/watch?v=w5tbgPJxyGA' + ENCRYPTED = 'https://www.youtube.com/tv#/watch?v=iNvUS1dnwfw' + + VP9 = 'https://www.youtube.com/tv#/watch?v=x7GkebUe6XQ' + VP9_HFR = 'https://www.youtube.com/tv#/watch?v=Jsjtt5dWDYU' + AV1 = 'https://www.youtube.com/tv#/watch?v=iXvy8ZeCs5M' + AV1_HFR = 'https://www.youtube.com/tv#/watch?v=9jZ01i92JI8' + VERTICAL = 'https://www.youtube.com/tv#/watch?v=jNQXAC9IVRw' + SHORT = 'https://www.youtube.com/tv#/watch?v=NEf8Ug49FEw' + VP9_HDR_HLG = 'https://www.youtube.com/tv#/watch?v=ebhEiRWGvZM' + VP9_HDR_PQ = 'https://www.youtube.com/tv#/watch?v=Rw-qEKR5uv8' + HDR_PQ_HFR = 'https://www.youtube.com/tv#/watch?v=LXb3EKWsInQ' + VR = 'https://www.youtube.com/tv#/watch?v=Ei0fgLfJ6Tk' + + LIVE = 'https://www.youtube.com/tv#/watch?v=o7UP6i4PAbk' + LIVE_ULL = 'https://www.youtube.com/tv#/watch?v=KI1XlTQrsa0' + LIVE_VR = 'https://www.youtube.com/tv#/watch?v=soeo5OPv7CA' + + PLAYLIST = ('https://www.youtube.com/tv#/watch?list=' + 'PLynQTqo4blSSCMPUGcH2YnbSrV84AnsRD') + # The link refers to a video in the middle of the playlist, so that + # it can be used to test both play previous and next. + PLAYLIST_MID = ('https://www.youtube.com/tv#/watch?' + 'v=9WrgRpOJr0I&list=PLynQTqo4blSSCMPUGcH2YnbSrV84AnsRD')
diff --git a/src/cobalt/media_session/media_session_client.cc b/src/cobalt/media_session/media_session_client.cc index 99097ef..7dd8853 100644 --- a/src/cobalt/media_session/media_session_client.cc +++ b/src/cobalt/media_session/media_session_client.cc
@@ -175,6 +175,15 @@ return result; } +void MediaSessionClient::PostDelayedTaskForMaybeFreezeCallback() { + if (is_active()) return; + + media_session_->task_runner_->PostDelayedTask( + FROM_HERE, base::Bind(&MediaSessionClient::RunMaybeFreezeCallback, + base::Unretained(this), ++sequence_number_), + kMaybeFreezeDelay); +} + void MediaSessionClient::UpdatePlatformPlaybackState( CobaltExtensionMediaSessionPlaybackState state) { DCHECK(media_session_->task_runner_); @@ -190,12 +199,7 @@ UpdateMediaSessionState(); } - if (!is_active()) { - media_session_->task_runner_->PostDelayedTask( - FROM_HERE, base::Bind(&MediaSessionClient::RunMaybeFreezeCallback, - base::Unretained(this), ++sequence_number_), - kMaybeFreezeDelay); - } + PostDelayedTaskForMaybeFreezeCallback(); } void MediaSessionClient::RunMaybeFreezeCallback(int sequence_number) {
diff --git a/src/cobalt/media_session/media_session_client.h b/src/cobalt/media_session/media_session_client.h index 115b852..35a9a31 100644 --- a/src/cobalt/media_session/media_session_client.h +++ b/src/cobalt/media_session/media_session_client.h
@@ -95,9 +95,8 @@ media_session_ = media_session; } - // If the media session is not active, then run MaybeFreezeCallback to - // suspend the App. - void RunMaybeFreezeCallback(int sequence_number); + // Post a delayed task for running MaybeFreeze callback. + void PostDelayedTaskForMaybeFreezeCallback(); private: THREAD_CHECKER(thread_checker_); @@ -135,6 +134,10 @@ MediaSessionAction ConvertMediaSessionAction( CobaltExtensionMediaSessionAction action); + // If the media session is not active, then run MaybeFreezeCallback to + // suspend the App. + void RunMaybeFreezeCallback(int sequence_number); + base::Closure maybe_freeze_callback_; // This is for checking the sequence number of PostDelayedTask. It should be
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold_2.glsl index 1380682..1be401c 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold_2.glsl
@@ -14,7 +14,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold_3.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold_3.glsl index 8ca842b..02f89e1 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold_3.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold_3.glsl
@@ -14,7 +14,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_thresholds.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_thresholds.glsl index 63ff1ed..11db997 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_thresholds.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_thresholds.glsl
@@ -19,7 +19,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold_2.glsl index 9c5c1f3..e7447a2 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold_2.glsl
@@ -14,7 +14,7 @@ varying mediump float vinCoverage_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold_3.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold_3.glsl index 3d40e8a..1ee4967 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold_3.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold_3.glsl
@@ -14,7 +14,7 @@ varying highp float vcoverage_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds.glsl index cbcba0c..7bf9ef3 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds.glsl
@@ -19,7 +19,7 @@ varying highp float vcoverage_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_2.glsl index 083f5e4..e6a6613 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_2.glsl
@@ -14,7 +14,7 @@ varying highp float vcoverage_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_3.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_3.glsl index e737a75..a5049c7 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_3.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_3.glsl
@@ -21,7 +21,7 @@ varying mediump float vinCoverage_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_decal_scaletranslate_texdom_texturew_threshold.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_decal_scaletranslate_texdom_texturew_threshold.glsl index d3a03a4..e338bcf 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_decal_scaletranslate_texdom_texturew_threshold.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_decal_scaletranslate_texdom_texturew_threshold.glsl
@@ -18,7 +18,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold.glsl index 5f2b1d3..667a564 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold.glsl
@@ -15,7 +15,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold_2.glsl index 084c066..35a3010 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold_2.glsl
@@ -15,7 +15,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold_3.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold_3.glsl index cb27f80..1f47fae 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold_3.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold_3.glsl
@@ -15,7 +15,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_thresholds.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_thresholds.glsl index 1ba24f6..b7223a3 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_thresholds.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_thresholds.glsl
@@ -18,7 +18,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_thresholds_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_thresholds_2.glsl index da56a36..ba5cadd 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_thresholds_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_thresholds_2.glsl
@@ -20,7 +20,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold.glsl index e2ec055..8bcc27a 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold.glsl
@@ -16,7 +16,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold_2.glsl index fe3a8ae..2ac7fcc 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold_2.glsl
@@ -16,7 +16,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold_3.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold_3.glsl index f1bbe9d..1cb8137 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold_3.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold_3.glsl
@@ -16,7 +16,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_thresholds.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_thresholds.glsl index b073544..ba8e732 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_thresholds.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_thresholds.glsl
@@ -19,7 +19,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texturew_threshold.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texturew_threshold.glsl index 987a9fd..8ea773f 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texturew_threshold.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texturew_threshold.glsl
@@ -15,7 +15,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_threshold.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_threshold.glsl index 3657419..2cec90c 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_threshold.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_threshold.glsl
@@ -13,7 +13,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_threshold_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_threshold_2.glsl index a80e7d6..8335e68 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_threshold_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_threshold_2.glsl
@@ -13,7 +13,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds.glsl index 6e1c100..7685597 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds.glsl
@@ -18,7 +18,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_2.glsl index e055c58..a1f1060 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_2.glsl
@@ -18,7 +18,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_3.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_3.glsl index be9d988..ecc4cb4 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_3.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_3.glsl
@@ -18,7 +18,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_4.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_4.glsl index cb3bcb1..71ce693 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_4.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_4.glsl
@@ -22,7 +22,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_5.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_5.glsl index 68b87cd..4fb093a 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_5.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_5.glsl
@@ -18,7 +18,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_6.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_6.glsl index 36f18a2..bffd97d 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_6.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_6.glsl
@@ -22,7 +22,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_7.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_7.glsl index a76b32a..9324401 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_7.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_7.glsl
@@ -18,7 +18,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_8.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_8.glsl index f5c5d14..39b4e76 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_8.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_8.glsl
@@ -16,7 +16,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_9.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_9.glsl index 58cdb65..0e27646 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_9.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_9.glsl
@@ -22,7 +22,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold.glsl index 62db91a..482238d 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold.glsl
@@ -25,7 +25,7 @@ } mediump vec4 stage_Stage1_c1_c0_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample453_c1_c0_c0_c0; - mediump float t = vTransformedCoords_1_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_1_Stage0.x + 9.999999747378752e-06; _sample453_c1_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample453_c1_c0_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_2.glsl index 77855e7..563d103 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_2.glsl
@@ -25,7 +25,7 @@ } mediump vec4 stage_Stage1_c1_c0_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample453_c1_c0_c0_c0; - mediump float t = vTransformedCoords_1_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_1_Stage0.x + 9.999999747378752e-06; _sample453_c1_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample453_c1_c0_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_3.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_3.glsl index 52553ba..37c926c 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_3.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_3.glsl
@@ -18,7 +18,7 @@ } mediump vec4 stage_Stage2_c1_c0_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample453_c1_c0_c0_c0; - mediump float t = vTransformedCoords_1_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_1_Stage0.x + 9.999999747378752e-06; _sample453_c1_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample453_c1_c0_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_4.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_4.glsl index 6522fd0..aa9cd37 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_4.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_4.glsl
@@ -11,7 +11,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample453_c0_c0_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample453_c0_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample453_c0_c0_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_thresholds.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_thresholds.glsl index a35702c..5eedd12 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_thresholds.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_thresholds.glsl
@@ -21,7 +21,7 @@ } mediump vec4 stage_Stage2_c1_c0_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample453_c1_c0_c0_c0; - mediump float t = vTransformedCoords_1_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_1_Stage0.x + 9.999999747378752e-06; _sample453_c1_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample453_c1_c0_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_thresholds_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_thresholds_2.glsl index 28eab3a..ddacd34 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_thresholds_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_thresholds_2.glsl
@@ -23,7 +23,7 @@ } mediump vec4 stage_Stage2_c1_c0_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample453_c1_c0_c0_c0; - mediump float t = vTransformedCoords_1_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_1_Stage0.x + 9.999999747378752e-06; _sample453_c1_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample453_c1_c0_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_thresholds.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_thresholds.glsl index e435cc9..8dd97f4 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_thresholds.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_thresholds.glsl
@@ -14,7 +14,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample453_c0_c0_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample453_c0_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample453_c0_c0_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color.glsl index 3dcf693..d0e6ce0 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color.glsl
@@ -10,7 +10,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_coverage.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_coverage.glsl index aa5970f..6adacd9 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_coverage.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_coverage.glsl
@@ -11,7 +11,7 @@ varying highp float vcoverage_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_coverage_texture.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_coverage_texture.glsl index 224674d..07a5ac3 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_coverage_texture.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_coverage_texture.glsl
@@ -10,7 +10,7 @@ varying highp float vcoverage_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_decal_ellipse_scaletranslate_texdom_texturew.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_decal_ellipse_scaletranslate_texdom_texturew.glsl index 572a984..70771b9 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_decal_ellipse_scaletranslate_texdom_texturew.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_decal_ellipse_scaletranslate_texdom_texturew.glsl
@@ -51,7 +51,7 @@ highp float test = dot(offset, offset) - 1.0; highp vec2 grad = (2.0 * offset) * (vEllipseOffsets_Stage0.z * vEllipseRadii_Stage0.xy); highp float grad_dot = dot(grad, grad); - grad_dot = max(grad_dot, 6.1036000261083245e-05); + grad_dot = max(grad_dot, 6.103600026108325e-05); highp float invlen = vEllipseOffsets_Stage0.z * inversesqrt(grad_dot); highp float edgeAlpha = clamp(0.5 - test * invlen, 0.0, 1.0); outputCoverage_Stage0 = vec4(edgeAlpha);
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_quad_texture.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_quad_texture.glsl index 1ea6bd8..3c26f70 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_quad_texture.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_quad_texture.glsl
@@ -11,7 +11,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_quad_texture_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_quad_texture_2.glsl index d8c1e31..298ccc0 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_quad_texture_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_quad_texture_2.glsl
@@ -11,7 +11,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texindex_texturew.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texindex_texturew.glsl index 4e004af..92cba89 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texindex_texturew.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texindex_texturew.glsl
@@ -12,7 +12,7 @@ varying highp vec2 vTransformedCoords_0_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texture.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texture.glsl index 4b56cd0..6f7f457 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texture.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texture.glsl
@@ -9,7 +9,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texture_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texture_2.glsl index fa2dffc..edebf92 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texture_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texture_2.glsl
@@ -9,7 +9,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texturew_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texturew_2.glsl index 84896ca..78b3466 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texturew_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texturew_2.glsl
@@ -12,7 +12,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample1099_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample1099_c0_c0; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_ellipse_scaletranslate_texdom_texture.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_ellipse_scaletranslate_texdom_texture.glsl index 98af413..b1749e3 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_ellipse_scaletranslate_texdom_texture.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_ellipse_scaletranslate_texdom_texture.glsl
@@ -21,14 +21,14 @@ highp float test = dot(offset, offset) - 1.0; highp vec2 grad = (2.0 * offset) * (vEllipseOffsets_Stage0.z * vEllipseRadii_Stage0.xy); highp float grad_dot = dot(grad, grad); - grad_dot = max(grad_dot, 6.1036000261083245e-05); + grad_dot = max(grad_dot, 6.103600026108325e-05); highp float invlen = vEllipseOffsets_Stage0.z * inversesqrt(grad_dot); highp float edgeAlpha = clamp(0.5 - test * invlen, 0.0, 1.0); offset = vEllipseOffsets_Stage0.xy * vEllipseRadii_Stage0.zw; test = dot(offset, offset) - 1.0; grad = (2.0 * offset) * (vEllipseOffsets_Stage0.z * vEllipseRadii_Stage0.zw); grad_dot = dot(grad, grad); - grad_dot = max(grad_dot, 6.1036000261083245e-05); + grad_dot = max(grad_dot, 6.103600026108325e-05); invlen = vEllipseOffsets_Stage0.z * inversesqrt(grad_dot); edgeAlpha *= clamp(0.5 + test * invlen, 0.0, 1.0); outputCoverage_Stage0 = vec4(edgeAlpha);
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texindex_texturew_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texindex_texturew_2.glsl index e7cd090..fda3d24 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texindex_texturew_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texindex_texturew_2.glsl
@@ -21,9 +21,9 @@ { texColor = texture2D(uTextureSampler_0_Stage0, uv).wwww; } - mediump float distance = 7.96875 * (texColor.x - 0.50196081399917603); + mediump float distance = 7.96875 * (texColor.x - 0.501960813999176); mediump float afwidth; - afwidth = abs(0.64999997615814209 * -dFdy(vIntTextureCoords_Stage0.y)); + afwidth = abs(0.6499999761581421 * -dFdy(vIntTextureCoords_Stage0.y)); mediump float val = smoothstep(-afwidth, afwidth, distance); outputCoverage_Stage0 = vec4(val); }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse.glsl index ee26f8b..8b55db4 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse.glsl
@@ -14,7 +14,7 @@ highp float test = dot(offset, offset) - 1.0; highp vec2 grad = (2.0 * offset) * (vEllipseOffsets_Stage0.z * vEllipseRadii_Stage0.xy); highp float grad_dot = dot(grad, grad); - grad_dot = max(grad_dot, 6.1036000261083245e-05); + grad_dot = max(grad_dot, 6.103600026108325e-05); highp float invlen = vEllipseOffsets_Stage0.z * inversesqrt(grad_dot); highp float edgeAlpha = clamp(0.5 - test * invlen, 0.0, 1.0); outputCoverage_Stage0 = vec4(edgeAlpha);
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_2.glsl index 0cb3d54..dd2801e 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_2.glsl
@@ -18,7 +18,7 @@ highp vec2 grad = vec2(vEllipseOffsets0_Stage0.x * duvdx.x + vEllipseOffsets0_Stage0.y * duvdx.y, vEllipseOffsets0_Stage0.x * duvdy.x + vEllipseOffsets0_Stage0.y * duvdy.y); grad *= vEllipseOffsets0_Stage0.z; highp float grad_dot = 4.0 * dot(grad, grad); - grad_dot = max(grad_dot, 6.1036000261083245e-05); + grad_dot = max(grad_dot, 6.103600026108325e-05); highp float invlen = inversesqrt(grad_dot); invlen *= vEllipseOffsets0_Stage0.z; highp float edgeAlpha = clamp(0.5 - test * invlen, 0.0, 1.0); @@ -29,7 +29,7 @@ grad = vec2(vEllipseOffsets1_Stage0.x * duvdx.x + vEllipseOffsets1_Stage0.y * duvdx.y, vEllipseOffsets1_Stage0.x * duvdy.x + vEllipseOffsets1_Stage0.y * duvdy.y); grad *= vEllipseOffsets0_Stage0.z; grad_dot = 4.0 * dot(grad, grad); - grad_dot = max(grad_dot, 6.1036000261083245e-05); + grad_dot = max(grad_dot, 6.103600026108325e-05); invlen = inversesqrt(grad_dot); invlen *= vEllipseOffsets0_Stage0.z; edgeAlpha *= clamp(0.5 + test * invlen, 0.0, 1.0);
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_3.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_3.glsl index 6148054..ff7654e 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_3.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_3.glsl
@@ -18,7 +18,7 @@ highp vec2 grad = vec2(vEllipseOffsets0_Stage0.x * duvdx.x + vEllipseOffsets0_Stage0.y * duvdx.y, vEllipseOffsets0_Stage0.x * duvdy.x + vEllipseOffsets0_Stage0.y * duvdy.y); grad *= vEllipseOffsets0_Stage0.z; highp float grad_dot = 4.0 * dot(grad, grad); - grad_dot = max(grad_dot, 6.1036000261083245e-05); + grad_dot = max(grad_dot, 6.103600026108325e-05); highp float invlen = inversesqrt(grad_dot); invlen *= vEllipseOffsets0_Stage0.z; highp float edgeAlpha = clamp(0.5 - test * invlen, 0.0, 1.0);
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_scale_texture.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_scale_texture.glsl index 0b4a811..4676e51 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_scale_texture.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_scale_texture.glsl
@@ -27,7 +27,7 @@ highp float implicit = dot(Z, d) - 1.0; highp float grad_dot = 4.0 * dot(Z, Z); { - grad_dot = max(grad_dot, 6.1036000261083245e-05); + grad_dot = max(grad_dot, 6.103600026108325e-05); } highp float approx_dist = implicit * inversesqrt(grad_dot); {
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew.glsl index 53a0a74..32a6af2 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew.glsl
@@ -18,9 +18,9 @@ { texColor = texture2D(uTextureSampler_0_Stage0, uv).wwww; } - mediump float distance = 7.96875 * (texColor.x - 0.50196081399917603); + mediump float distance = 7.96875 * (texColor.x - 0.501960813999176); mediump float afwidth; - afwidth = abs(0.64999997615814209 * -dFdy(vIntTextureCoords_Stage0.y)); + afwidth = abs(0.6499999761581421 * -dFdy(vIntTextureCoords_Stage0.y)); mediump float val = smoothstep(-afwidth, afwidth, distance); outputCoverage_Stage0 = vec4(val); }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_2.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_2.glsl index 1b61907..7c4db08 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_2.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_2.glsl
@@ -18,9 +18,9 @@ { texColor = texture2D(uTextureSampler_0_Stage0, uv).wwww; } - mediump float distance = 7.96875 * (texColor.x - 0.50196081399917603); + mediump float distance = 7.96875 * (texColor.x - 0.501960813999176); mediump float afwidth; - afwidth = abs(0.64999997615814209 * -dFdy(vIntTextureCoords_Stage0.y)); + afwidth = abs(0.6499999761581421 * -dFdy(vIntTextureCoords_Stage0.y)); mediump float val = smoothstep(-afwidth, afwidth, distance); outputCoverage_Stage0 = vec4(val); }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_3.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_3.glsl index f4db076..421494c 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_3.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_3.glsl
@@ -18,19 +18,19 @@ { texColor = texture2D(uTextureSampler_0_Stage0, uv).wwww; } - mediump float distance = 7.96875 * (texColor.x - 0.50196081399917603); + mediump float distance = 7.96875 * (texColor.x - 0.501960813999176); mediump float afwidth; mediump vec2 dist_grad = vec2(dFdx(distance), -dFdy(distance)); mediump float dg_len2 = dot(dist_grad, dist_grad); - if (dg_len2 < 9.9999997473787516e-05) { - dist_grad = vec2(0.70709997415542603, 0.70709997415542603); + if (dg_len2 < 9.999999747378752e-05) { + dist_grad = vec2(0.707099974155426, 0.707099974155426); } else { dist_grad = dist_grad * inversesqrt(dg_len2); } mediump vec2 Jdx = dFdx(vIntTextureCoords_Stage0); mediump vec2 Jdy = -dFdy(vIntTextureCoords_Stage0); mediump vec2 grad = vec2(dist_grad.x * Jdx.x + dist_grad.y * Jdy.x, dist_grad.x * Jdx.y + dist_grad.y * Jdy.y); - afwidth = 0.64999997615814209 * length(grad); + afwidth = 0.6499999761581421 * length(grad); mediump float val = smoothstep(-afwidth, afwidth, distance); outputCoverage_Stage0 = vec4(val); }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_4.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_4.glsl index 20eef11..663553b 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_4.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_4.glsl
@@ -18,10 +18,10 @@ { texColor = texture2D(uTextureSampler_0_Stage0, uv).wwww; } - mediump float distance = 7.96875 * (texColor.x - 0.50196081399917603); + mediump float distance = 7.96875 * (texColor.x - 0.501960813999176); mediump float afwidth; mediump float st_grad_len = length(-dFdy(vIntTextureCoords_Stage0)); - afwidth = abs(0.64999997615814209 * st_grad_len); + afwidth = abs(0.6499999761581421 * st_grad_len); mediump float val = smoothstep(-afwidth, afwidth, distance); outputCoverage_Stage0 = vec4(val); }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_5.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_5.glsl index ac92c03..80cd4a3 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_5.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_5.glsl
@@ -18,10 +18,10 @@ { texColor = texture2D(uTextureSampler_0_Stage0, uv).wwww; } - mediump float distance = 7.96875 * (texColor.x - 0.50196081399917603); + mediump float distance = 7.96875 * (texColor.x - 0.501960813999176); mediump float afwidth; mediump float st_grad_len = length(-dFdy(vIntTextureCoords_Stage0)); - afwidth = abs(0.64999997615814209 * st_grad_len); + afwidth = abs(0.6499999761581421 * st_grad_len); mediump float val = smoothstep(-afwidth, afwidth, distance); outputCoverage_Stage0 = vec4(val); }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_7.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_7.glsl index 85b6c4e..165aeed 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_7.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_7.glsl
@@ -18,19 +18,19 @@ { texColor = texture2D(uTextureSampler_0_Stage0, uv).wwww; } - mediump float distance = 7.96875 * (texColor.x - 0.50196081399917603); + mediump float distance = 7.96875 * (texColor.x - 0.501960813999176); mediump float afwidth; mediump vec2 dist_grad = vec2(dFdx(distance), -dFdy(distance)); mediump float dg_len2 = dot(dist_grad, dist_grad); - if (dg_len2 < 9.9999997473787516e-05) { - dist_grad = vec2(0.70709997415542603, 0.70709997415542603); + if (dg_len2 < 9.999999747378752e-05) { + dist_grad = vec2(0.707099974155426, 0.707099974155426); } else { dist_grad = dist_grad * inversesqrt(dg_len2); } mediump vec2 Jdx = dFdx(vIntTextureCoords_Stage0); mediump vec2 Jdy = -dFdy(vIntTextureCoords_Stage0); mediump vec2 grad = vec2(dist_grad.x * Jdx.x + dist_grad.y * Jdy.x, dist_grad.x * Jdx.y + dist_grad.y * Jdy.y); - afwidth = 0.64999997615814209 * length(grad); + afwidth = 0.6499999761581421 * length(grad); mediump float val = smoothstep(-afwidth, afwidth, distance); outputCoverage_Stage0 = vec4(val); }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_10.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_10.glsl index 07ec93a..525fe12 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_10.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_10.glsl
@@ -19,7 +19,7 @@ } mediump vec4 output_Stage2; { - mediump float luma = clamp(dot(vec3(0.2125999927520752, 0.71520000696182251, 0.072200000286102295), output_Stage1.xyz), 0.0, 1.0); + mediump float luma = clamp(dot(vec3(0.2125999927520752, 0.7152000069618225, 0.0722000002861023), output_Stage1.xyz), 0.0, 1.0); output_Stage2 = vec4(0.0, 0.0, 0.0, luma); } {
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_11.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_11.glsl index bc07de6..e8d7417 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_11.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_11.glsl
@@ -18,7 +18,7 @@ mediump vec4 out0_c1_c0; mediump vec4 inputColor = _input; { - mediump float nonZeroAlpha = max(inputColor.w, 9.9999997473787516e-05); + mediump float nonZeroAlpha = max(inputColor.w, 9.999999747378752e-05); inputColor = vec4(inputColor.xyz / nonZeroAlpha, nonZeroAlpha); } out0_c1_c0 = um_Stage2_c1_c0_c0_c0 * inputColor + uv_Stage2_c1_c0_c0_c0; @@ -32,7 +32,7 @@ } mediump vec4 stage_Stage2_c1_c0_c1_c0(mediump vec4 _input) { mediump vec4 _sample1278; - mediump float nonZeroAlpha = max(_input.w, 9.9999997473787516e-05); + mediump float nonZeroAlpha = max(_input.w, 9.999999747378752e-05); mediump vec4 coord = vec4(_input.xyz / nonZeroAlpha, nonZeroAlpha); coord = coord * 0.9960939884185791 + vec4(0.0019529999699443579, 0.0019529999699443579, 0.0019529999699443579, 0.0019529999699443579); _sample1278.w = texture2D(uTextureSampler_0_Stage2, vec2(coord.w, 0.125)).w;
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_12.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_12.glsl index a29ab60..1db4bf2 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_12.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_12.glsl
@@ -7,7 +7,7 @@ varying mediump vec4 vcolor_Stage0; mediump vec4 stage_Stage1_c0_c0_c0_c0_c0_c0(mediump vec4 _input) { mediump vec4 _sample453_c0_c0_c0_c0; - mediump float t = vTransformedCoords_0_Stage0.x + 9.9999997473787516e-06; + mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06; _sample453_c0_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0); return _sample453_c0_c0_c0_c0; }
diff --git a/src/cobalt/renderer/rasterizer/egl/shader_program_manager.cc b/src/cobalt/renderer/rasterizer/egl/shader_program_manager.cc index 7a4a543..eaf9838 100644 --- a/src/cobalt/renderer/rasterizer/egl/shader_program_manager.cc +++ b/src/cobalt/renderer/rasterizer/egl/shader_program_manager.cc
@@ -28,12 +28,17 @@ ShaderProgramManager::ShaderProgramManager() { // These are shaders that get instantiated during video playback when the - // users starts interacting with the transport controls. They are preloaded + // user starts interacting with the transport controls. They are preloaded // to prevent UI-hiccups. // These shaders are generated from egl/generated_shader_impl.h Preload<ShaderVertexOffsetRcorner, ShaderFragmentColorBlurRrects>(); Preload<ShaderVertexColorOffset, ShaderFragmentColorInclude>(); Preload<ShaderVertexRcornerTexcoord, ShaderFragmentRcornerTexcoordColor>(); + Preload<ShaderVertexRcornerTexcoord, + ShaderFragmentRcornerTexcoordColorYuv3>(); + Preload<ShaderVertexRcorner, ShaderFragmentRcornerColor>(); + Preload<ShaderVertexColorTexcoord, ShaderFragmentColorTexcoord>(); + Preload<ShaderVertexColorTexcoord, ShaderFragmentColorTexcoordYuv3>(); } ShaderProgramManager::~ShaderProgramManager() {
diff --git a/src/cobalt/site/docs/contributors/index.md b/src/cobalt/site/docs/contributors/index.md index ea93cc6..77811fa 100644 --- a/src/cobalt/site/docs/contributors/index.md +++ b/src/cobalt/site/docs/contributors/index.md
@@ -52,7 +52,7 @@ * Ensure you or your company have signed the appropriate CLA as discussed in the [Before You Contribute](#before-you-contribute) section above. * Rebase your changes down into a single git commit. -* Run `git cl upload` to upload the review to +* Run `git push origin HEAD:refs/for/master` to upload the review to [Cobalt's Gerrit instance](https://cobalt-review.googlesource.com/). * Someone from the maintainers team reviews the code, adding comments on any things that need to change before the code can be submitted.
diff --git a/src/cobalt/tools/automated_testing/cobalt_runner.py b/src/cobalt/tools/automated_testing/cobalt_runner.py index d1c878b..1c26ae3 100644 --- a/src/cobalt/tools/automated_testing/cobalt_runner.py +++ b/src/cobalt/tools/automated_testing/cobalt_runner.py
@@ -85,7 +85,8 @@ url, log_file=None, target_params=None, - success_message=None): + success_message=None, + log_handler=None): """CobaltRunner constructor. Args: @@ -112,6 +113,8 @@ 'webdriver') self.launcher_params = launcher_params + self.log_handler = log_handler + if log_file: self.log_file = open(log_file) logging.basicConfig(stream=self.log_file, level=logging.INFO) @@ -152,6 +155,14 @@ """Sends a system signal to put Cobalt into stopped state.""" self.launcher.SendStop() + def SendSystemSuspend(self): + """Ask the system to suspend Cobalt.""" + self.launcher.SendSystemSuspend() + + def SendSystemResume(self): + """Ask the system to resume Cobalt.""" + self.launcher.SendSystemResume() + def SendDeepLink(self, link): """Sends a deep link to Cobalt.""" return self.launcher.SendDeepLink(link) @@ -175,17 +186,21 @@ while True: line = self.launcher_read_pipe.readline() if line: + if self.log_handler is not None: + self.log_handler(line) self.log_file.write(line) # Calling flush() to ensure the logs are delivered timely. self.log_file.flush() else: break - if RE_WINDOWDRIVER_CREATED.search(line): + if not self.windowdriver_created.set() and \ + RE_WINDOWDRIVER_CREATED.search(line): self.windowdriver_created.set() continue - if RE_WEBMODULE_LOADED.search(line): + if not self.webmodule_loaded.set() and \ + RE_WEBMODULE_LOADED.search(line): self.webmodule_loaded.set() continue
diff --git a/src/cobalt/webdriver/keyboard.cc b/src/cobalt/webdriver/keyboard.cc index 285dc9e..59331ac 100644 --- a/src/cobalt/webdriver/keyboard.cc +++ b/src/cobalt/webdriver/keyboard.cc
@@ -162,6 +162,32 @@ dom::keycode::kLwin, // kSpecialKey_Meta }; +// Besides of selenium defined special keys, we need additional special keys +// for media control. The following utf-8 code could be provided as "keys" +// sent to WebDriver, and should be mapped to the corresponding keyboard code. +enum AdditionalSpecialKey { + kFirstAdditionalSpecialKey = 0xF000, + kSpecialKey_MediaNextTrack = kFirstAdditionalSpecialKey, + kSpecialKey_MediaPrevTrack, + kSpecialKey_MediaStop, + kSpecialKey_MediaPlayPause, + kSpecialKey_MediaRewind, + kSpecialKey_MediaFastForward, + kLastAdditionalSpecialKey = kSpecialKey_MediaFastForward, +}; + +// Mapping from an additional special keycode to virtual keycode. Subtract +// kFirstAdditionalSpecialKey from the integer value of the WebDriver keycode +// and index into this table. +const int32 additional_special_keycode_mapping[] = { + dom::keycode::kMediaNextTrack, // kMediaNextTrack, + dom::keycode::kMediaPrevTrack, // kMediaPrevTrack, + dom::keycode::kMediaStop, // kMediaStop, + dom::keycode::kMediaPlayPause, // kMediaPlayPause, + dom::keycode::kMediaRewind, // kMediaRewind, + dom::keycode::kMediaFastForward, // kMediaFastForward, +}; + // Check that the mapping is the expected size. const int kLargestMappingIndex = kLastSpecialKey - kFirstSpecialKey; COMPILE_ASSERT(arraysize(special_keycode_mapping) == kLargestMappingIndex + 1, @@ -249,6 +275,11 @@ return webdriver_key >= kFirstSpecialKey && webdriver_key < kLastSpecialKey; } +bool IsAdditionalSpecialKey(int webdriver_key) { + return webdriver_key >= kFirstAdditionalSpecialKey && + webdriver_key <= kLastAdditionalSpecialKey; +} + bool IsModifierKey(int webdriver_key) { return webdriver_key == kSpecialKey_Alt || webdriver_key == kSpecialKey_Shift || @@ -294,6 +325,15 @@ return KeyboardEvent::kDomKeyLocationStandard; } +// Returns the keycode that corresponds to this additional special key. +int32 GetAdditionalSpecialKeycode(int32 webdriver_key) { + DCHECK(IsAdditionalSpecialKey(webdriver_key)); + int index = webdriver_key - kFirstAdditionalSpecialKey; + DCHECK_GE(index, 0); + DCHECK_LT(index, arraysize(additional_special_keycode_mapping)); + return additional_special_keycode_mapping[index]; +} + class KeyTranslator { public: explicit KeyTranslator(Keyboard::KeyboardEventVector* event_vector) @@ -327,6 +367,14 @@ KeyLocationCode location = GetSpecialKeyLocation(webdriver_key); AddKeyDownEvent(key_code, char_code, location); AddKeyUpEvent(key_code, char_code, location); + } else if (IsAdditionalSpecialKey(webdriver_key)) { + // Else if it's an additional special key, translate to key_code and + // send key events. + int32 key_code = GetAdditionalSpecialKeycode(webdriver_key); + int32 char_code = 0; + KeyLocationCode location = KeyboardEvent::kDomKeyLocationStandard; + AddKeyDownEvent(key_code, char_code, location); + AddKeyUpEvent(key_code, char_code, location); } else { DCHECK_GE(webdriver_key, 0); DCHECK_LT(webdriver_key, std::numeric_limits<char>::max());
diff --git a/src/cobalt/webdriver/window_driver.cc b/src/cobalt/webdriver/window_driver.cc index 1cb4432..6a33518 100644 --- a/src/cobalt/webdriver/window_driver.cc +++ b/src/cobalt/webdriver/window_driver.cc
@@ -479,9 +479,6 @@ script_executor_ = base::AsWeakPtr(script_executor.get()); } - DLOG(INFO) << "Executing: " << script.function_body(); - DLOG(INFO) << "Arguments: " << script.argument_array(); - auto gc_prevented_params = ScriptExecutorParams::Create(global_environment, script.function_body(), script.argument_array(), async_timeout);
diff --git a/src/docker-compose.yml b/src/docker-compose.yml index 7cb3f94..2401d96 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml
@@ -259,6 +259,17 @@ PLATFORM: ${PLATFORM:-raspi-2} CONFIG: ${CONFIG:-debug} + raspi-gn: + <<: *build-common-definitions + build: + context: ./docker/linux/raspi + dockerfile: ./gn/Dockerfile + image: cobalt-build-raspi-gn + environment: + <<: *shared-build-env + PLATFORM: ${PLATFORM:-raspi-2} + CONFIG: ${CONFIG:-debug} + # Define common build container for Evergreen build-evergreen: <<: *build-common-definitions
diff --git a/src/docker/linux/raspi/Dockerfile b/src/docker/linux/raspi/Dockerfile index 5483068..5f61683 100644 --- a/src/docker/linux/raspi/Dockerfile +++ b/src/docker/linux/raspi/Dockerfile
@@ -24,6 +24,7 @@ && apt install -qqy --no-install-recommends \ g++-multilib \ bzip2 \ + libglib2.0-dev \ && apt-get clean autoclean \ && apt-get autoremove -y --purge \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
diff --git a/src/docker/linux/raspi/gn/Dockerfile b/src/docker/linux/raspi/gn/Dockerfile new file mode 100644 index 0000000..2952fef --- /dev/null +++ b/src/docker/linux/raspi/gn/Dockerfile
@@ -0,0 +1,18 @@ +# Copyright 2021 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. + +FROM cobalt-build-raspi + +CMD gn gen ${OUTDIR}/${PLATFORM}_${CONFIG} --args="target_platform=\"${PLATFORM}\" build_type=\"${CONFIG}\" target_cpu=\"arm\" treat_warnings_as_errors=false" && \ + ninja -j ${NINJA_PARALLEL} -C ${OUTDIR}/${PLATFORM}_${CONFIG}
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java index e5a4254..1f96ccd 100644 --- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java +++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java
@@ -14,10 +14,16 @@ package dev.cobalt.coat; +import static dev.cobalt.util.Log.TAG; + +import dev.cobalt.util.Log; import dev.cobalt.util.UsedByNative; /** Abstract class that provides an interface for Cobalt to interact with a platform service. */ public abstract class CobaltService { + // Indicate is the service opened, and be able to send data to client + protected boolean opened = true; + /** Interface that returns an object that extends CobaltService. */ public interface Factory { /** Create the service. */ @@ -59,14 +65,41 @@ @UsedByNative public abstract ResponseToClient receiveFromClient(byte[] data); - /** Close the service. */ + /** + * Close the service. + * + * <p>Once this function returns, it is invalid to call sendToClient for the nativeService, so + * synchronization must be used to protect against this. + */ @SuppressWarnings("unused") @UsedByNative + public void onClose() { + synchronized (this) { + opened = false; + close(); + } + } + public abstract void close(); - /** Send data from the service to the client. */ + /** + * Send data from the service to the client. + * + * <p>This may be called from a separate thread, do not call nativeSendToClient() once onClose() + * is processed. + */ protected void sendToClient(long nativeService, byte[] data) { - nativeSendToClient(nativeService, data); + synchronized (this) { + if (!opened) { + Log.w( + TAG, + "Platform service did not send data to client, because client already closed the" + + " platform service."); + return; + } + + nativeSendToClient(nativeService, data); + } } private native void nativeSendToClient(long nativeService, byte[] data);
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java index 455f47b..68d95ef 100644 --- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java +++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
@@ -24,6 +24,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.hardware.input.InputManager; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.net.ConnectivityManager; @@ -33,6 +34,7 @@ import android.util.Size; import android.util.SizeF; import android.view.Display; +import android.view.InputDevice; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.CaptioningManager; import androidx.annotation.Nullable; @@ -172,7 +174,7 @@ Service service = serviceHolder.get(); if (service == null) { if (appContext == null) { - Log.w(TAG, "Activiy already destoryed."); + Log.w(TAG, "Activiy already destroyed."); return; } Log.i(TAG, "Cold start - Instantiating a MediaPlaybackService."); @@ -250,6 +252,7 @@ @UsedByNative public void requestStop(int errorLevel) { if (!starboardStopped) { + Log.i(TAG, "Request to stop"); nativeStopApp(errorLevel); } } @@ -261,6 +264,7 @@ public void requestSuspend() { Activity activity = activityHolder.get(); if (activity != null) { + Log.i(TAG, "Request to suspend"); activity.finish(); } } @@ -435,7 +439,21 @@ // connected input audio device is a microphone. AudioManager audioManager = (AudioManager) appContext.getSystemService(AUDIO_SERVICE); AudioDeviceInfo[] devices = audioManager.getDevices(GET_DEVICES_INPUTS); - return devices.length > 0; + if (devices.length > 0) { + return true; + } + + // fallback to check for BT voice capable RCU + InputManager inputManager = (InputManager) appContext.getSystemService(Context.INPUT_SERVICE); + final int[] inputDeviceIds = inputManager.getInputDeviceIds(); + for (int inputDeviceId : inputDeviceIds) { + final InputDevice inputDevice = inputManager.getInputDevice(inputDeviceId); + final boolean hasMicrophone = inputDevice.hasMicrophone(); + if (hasMicrophone) { + return true; + } + } + return false; } /** @@ -682,9 +700,7 @@ cobaltServices.remove(serviceName); } - /** - * Returns the application start timestamp. - */ + /** Returns the application start timestamp. */ @SuppressWarnings("unused") @UsedByNative protected long getAppStartTimestamp() { @@ -693,8 +709,8 @@ long javaStartTimestamp = ((CobaltActivity) activity).getAppStartTimestamp(); long cppTimestamp = nativeSbTimeGetMonotonicNow(); long javaStopTimestamp = System.nanoTime(); - return cppTimestamp - - (javaStartTimestamp - javaStopTimestamp) / timeNanosecondsPerMicrosecond; + return cppTimestamp + - (javaStartTimestamp - javaStopTimestamp) / timeNanosecondsPerMicrosecond; } return 0; }
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 18418e8..f265e98 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
@@ -97,6 +97,11 @@ || info.getType() == AudioDeviceInfo.TYPE_HDMI)) { // TODO: Avoid destroying the AudioTrack if the new devices can support the current // AudioFormat. + Log.v( + TAG, + String.format( + "Setting |hasAudioDeviceChanged| to true for audio device %s, %s.", + info.getProductName(), getDeviceTypeNameV23(info.getType()))); hasAudioDeviceChanged.set(true); break; } @@ -105,6 +110,11 @@ @Override public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { + Log.v( + TAG, + String.format( + "onAudioDevicesAdded() called, |initialDevicesAdded| is: %b.", + initialDevicesAdded)); if (initialDevicesAdded) { handleConnectedDeviceChange(addedDevices); return; @@ -114,6 +124,7 @@ @Override public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { + Log.v(TAG, "onAudioDevicesRemoved() called."); handleConnectedDeviceChange(removedDevices); } },
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java index eaeb513..c31e017 100644 --- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java +++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
@@ -464,7 +464,8 @@ // focus if |explicitUserActionRequired| is true. Currently we're not able to recognize // it. But if we don't have window focus, we know the user is not interacting with our app // and we should not request media focus. - if (!explicitUserActionRequired || activityHolder.get().hasWindowFocus()) { + if (!explicitUserActionRequired + || (activityHolder.get() != null && activityHolder.get().hasWindowFocus())) { explicitUserActionRequired = false; configureMediaFocus(playbackState); } else { @@ -478,7 +479,7 @@ } // Ignore updates to the MediaSession metadata if playback is stopped. - if (playbackState == PLAYBACK_STATE_NONE) { + if (playbackState == PLAYBACK_STATE_NONE || mediaSession == null) { return; }
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 0ca198c..2473e28 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
@@ -351,7 +351,8 @@ float maxMasteringLuminance, float minMasteringLuminance, int maxCll, - int maxFall) { + int maxFall, + boolean forceBigEndianHdrMetadata) { this.colorRange = colorRange; this.colorStandard = colorStandard; this.colorTransfer = colorTransfer; @@ -365,7 +366,14 @@ // This logic is inspired by // https://github.com/google/ExoPlayer/blob/deb9b301b2c7ef66fdd7d8a3e58298a79ba9c619/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java#L1803. - ByteBuffer hdrStaticInfo = ByteBuffer.allocateDirect(25).order(ByteOrder.LITTLE_ENDIAN); + ByteBuffer hdrStaticInfo = ByteBuffer.allocateDirect(25); + // Force big endian in case the HDR metadata causes problems in production. + if (forceBigEndianHdrMetadata) { + hdrStaticInfo.order(ByteOrder.BIG_ENDIAN); + } else { + hdrStaticInfo.order(ByteOrder.LITTLE_ENDIAN); + } + hdrStaticInfo.put((byte) 0); hdrStaticInfo.putShort((short) ((primaryRChromaticityX * MAX_CHROMATICITY) + 0.5f)); hdrStaticInfo.putShort((short) ((primaryRChromaticityY * MAX_CHROMATICITY) + 0.5f));
diff --git a/src/starboard/android/shared/BUILD.gn b/src/starboard/android/shared/BUILD.gn index 66628a2..a84353e 100644 --- a/src/starboard/android/shared/BUILD.gn +++ b/src/starboard/android/shared/BUILD.gn
@@ -180,7 +180,6 @@ "//starboard/shared/signal/suspend_signals.cc", "//starboard/shared/signal/suspend_signals.h", "//starboard/shared/signal/system_request_conceal.cc", - "//starboard/shared/signal/system_request_freeze_no_freezedone_callback.cc", "//starboard/shared/starboard/application.cc", "//starboard/shared/starboard/application.h", "//starboard/shared/starboard/audio_sink/audio_sink_create.cc", @@ -381,6 +380,7 @@ "system_has_capability.cc", "system_network_is_disconnected.cc", "system_platform_error.cc", + "system_request_freeze_no_freezedone_callback.cc", "system_request_stop.cc", "system_request_suspend.cc", "thread_create.cc",
diff --git a/src/starboard/android/shared/launcher.py b/src/starboard/android/shared/launcher.py index 5f591dc..abd2787 100644 --- a/src/starboard/android/shared/launcher.py +++ b/src/starboard/android/shared/launcher.py
@@ -279,7 +279,11 @@ # Setup for running executable self._CheckCallAdb('wait-for-device') self._Shutdown() - + # TODO: Need to wait until cobalt fully shutdown. Otherwise, it may get + # dirty logs from previous test, and logs like "***Application Stopped***" + # will cause unexpected errors. + # Simply wait 2s as a temperary solution. + time.sleep(2) # Clear logcat self._CheckCallAdb('logcat', '-c')
diff --git a/src/starboard/android/shared/media_codec_bridge.cc b/src/starboard/android/shared/media_codec_bridge.cc index 0415adc..3b5427d 100644 --- a/src/starboard/android/shared/media_codec_bridge.cc +++ b/src/starboard/android/shared/media_codec_bridge.cc
@@ -198,6 +198,7 @@ const SbMediaColorMetadata* color_metadata, bool require_software_codec, int tunnel_mode_audio_session_id, + bool force_big_endian_hdr_metadata, std::string* error_message) { SB_DCHECK(error_message); @@ -222,7 +223,7 @@ color_range != COLOR_VALUE_UNKNOWN) { const auto& mastering_metadata = color_metadata->mastering_metadata; j_color_info.Reset(env->NewObjectOrAbort( - "dev/cobalt/media/MediaCodecBridge$ColorInfo", "(IIIFFFFFFFFFFII)V", + "dev/cobalt/media/MediaCodecBridge$ColorInfo", "(IIIFFFFFFFFFFIIZ)V", color_range, color_standard, color_transfer, mastering_metadata.primary_r_chromaticity_x, mastering_metadata.primary_r_chromaticity_y, @@ -233,7 +234,8 @@ mastering_metadata.white_point_chromaticity_x, mastering_metadata.white_point_chromaticity_y, mastering_metadata.luminance_max, mastering_metadata.luminance_min, - color_metadata->max_cll, color_metadata->max_fall)); + color_metadata->max_cll, color_metadata->max_fall, + force_big_endian_hdr_metadata)); } }
diff --git a/src/starboard/android/shared/media_codec_bridge.h b/src/starboard/android/shared/media_codec_bridge.h index 8ec1f94..60a6487 100644 --- a/src/starboard/android/shared/media_codec_bridge.h +++ b/src/starboard/android/shared/media_codec_bridge.h
@@ -112,6 +112,7 @@ const SbMediaColorMetadata* color_metadata, bool require_software_codec, int tunnel_mode_audio_session_id, + bool force_big_endian_hdr_metadata, std::string* error_message); ~MediaCodecBridge();
diff --git a/src/starboard/android/shared/media_decoder.cc b/src/starboard/android/shared/media_decoder.cc index 29567ed..69293d1 100644 --- a/src/starboard/android/shared/media_decoder.cc +++ b/src/starboard/android/shared/media_decoder.cc
@@ -111,6 +111,7 @@ bool require_software_codec, const FrameRenderedCB& frame_rendered_cb, int tunnel_mode_audio_session_id, + bool force_big_endian_hdr_metadata, std::string* error_message) : media_type_(kSbMediaTypeVideo), host_(host), @@ -125,7 +126,7 @@ media_codec_bridge_ = MediaCodecBridge::CreateVideoMediaCodecBridge( video_codec, width, height, fps, this, j_output_surface, j_media_crypto, color_metadata, require_software_codec, tunnel_mode_audio_session_id, - error_message); + force_big_endian_hdr_metadata, error_message); if (!media_codec_bridge_) { SB_LOG(ERROR) << "Failed to create video media codec bridge with error: " << *error_message;
diff --git a/src/starboard/android/shared/media_decoder.h b/src/starboard/android/shared/media_decoder.h index 6550cdd..569036a 100644 --- a/src/starboard/android/shared/media_decoder.h +++ b/src/starboard/android/shared/media_decoder.h
@@ -85,6 +85,7 @@ bool require_software_codec, const FrameRenderedCB& frame_rendered_cb, int tunnel_mode_audio_session_id, + bool force_big_endian_hdr_metadata, std::string* error_message); ~MediaDecoder();
diff --git a/src/starboard/android/shared/media_is_audio_supported.cc b/src/starboard/android/shared/media_is_audio_supported.cc index e7b04de..a9c63f6 100644 --- a/src/starboard/android/shared/media_is_audio_supported.cc +++ b/src/starboard/android/shared/media_is_audio_supported.cc
@@ -45,71 +45,54 @@ if (!mime) { return false; } + MimeType mime_type(content_type); - // Allows for disabling the use of the AudioDeviceCallback API to detect when - // audio peripherals are connected. Enabled by default. - // (https://developer.android.com/reference/android/media/AudioDeviceCallback) - auto enable_audio_device_callback_parameter_value = - mime_type.GetParamStringValue("enableaudiodevicecallback", ""); - if (!enable_audio_device_callback_parameter_value.empty() && - enable_audio_device_callback_parameter_value != "true" && - enable_audio_device_callback_parameter_value != "false") { - SB_LOG(INFO) << "Invalid value for audio mime parameter " - "\"enableaudiodevicecallback\": " - << enable_audio_device_callback_parameter_value << "."; - return false; + if (strlen(content_type) > 0) { + // Allows for disabling the use of the AudioDeviceCallback API to detect + // when audio peripherals are connected. Enabled by default. + // (https://developer.android.com/reference/android/media/AudioDeviceCallback) + mime_type.RegisterBoolParameter("enableaudiodevicecallback"); + // Allows for enabling tunneled playback. Disabled by default. + // (https://source.android.com/devices/tv/multimedia-tunneling) + mime_type.RegisterBoolParameter("tunnelmode"); + // Enables audio passthrough if the codec supports it. + mime_type.RegisterBoolParameter("audiopassthrough"); + + if (!mime_type.is_valid()) { + return false; + } } - // Allows for enabling tunneled playback. Disabled by default. - // (https://source.android.com/devices/tv/multimedia-tunneling) - auto enable_tunnel_mode_parameter_value = - mime_type.GetParamStringValue("tunnelmode", ""); - if (!enable_tunnel_mode_parameter_value.empty() && - enable_tunnel_mode_parameter_value != "true" && - enable_tunnel_mode_parameter_value != "false") { - SB_LOG(INFO) << "Invalid value for audio mime parameter \"tunnelmode\": " - << enable_tunnel_mode_parameter_value << "."; - return false; - } else if (enable_tunnel_mode_parameter_value == "true" && - !SbAudioSinkIsAudioSampleTypeSupported( - kSbMediaAudioSampleTypeInt16Deprecated)) { + + bool enable_tunnel_mode = mime_type.GetParamBoolValue("tunnelmode", false); + if (enable_tunnel_mode && !SbAudioSinkIsAudioSampleTypeSupported( + kSbMediaAudioSampleTypeInt16Deprecated)) { SB_LOG(WARNING) << "Tunnel mode is rejected because int16 sample is required " "but not supported."; return false; } - auto audio_passthrough_parameter_value = - mime_type.GetParamStringValue("audiopassthrough", ""); - if (!audio_passthrough_parameter_value.empty() && - audio_passthrough_parameter_value != "true" && - audio_passthrough_parameter_value != "false") { - SB_LOG(INFO) << "Invalid value for audio mime parameter " - "\"audiopassthrough\": " - << audio_passthrough_parameter_value - << ". Passthrough is disabled."; - return false; - } - if (audio_passthrough_parameter_value == "false" && is_passthrough) { - SB_LOG(INFO) << "Passthrough is rejected because audio mime parameter " - "\"audiopassthrough\" == false."; - return false; - } - JniEnvExt* env = JniEnvExt::Get(); ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime)); - const bool must_support_tunnel_mode = - enable_tunnel_mode_parameter_value == "true"; auto media_codec_supported = env->CallStaticBooleanMethodOrAbort( "dev/cobalt/media/MediaCodecUtil", "hasAudioDecoderFor", "(Ljava/lang/String;IZ)Z", j_mime.Get(), static_cast<jint>(bitrate), - must_support_tunnel_mode) == JNI_TRUE; + enable_tunnel_mode) == JNI_TRUE; if (!media_codec_supported) { return false; } + if (!is_passthrough) { return true; } + + if (!mime_type.GetParamBoolValue("audiopassthrough", true)) { + SB_LOG(INFO) << "Passthrough codec is rejected because passthrough is " + "disabled through mime param."; + return false; + } + SbMediaAudioCodingType coding_type; switch (audio_codec) { case kSbMediaAudioCodecAc3:
diff --git a/src/starboard/android/shared/media_is_video_supported.cc b/src/starboard/android/shared/media_is_video_supported.cc index 332e4b2..095a698 100644 --- a/src/starboard/android/shared/media_is_video_supported.cc +++ b/src/starboard/android/shared/media_is_video_supported.cc
@@ -95,29 +95,32 @@ if (!mime) { return false; } + // Check extended parameters for correctness and return false if any invalid + // invalid params are found. MimeType mime_type(content_type); // Allows for enabling tunneled playback. Disabled by default. - // (https://source.android.com/devices/tv/multimedia-tunneling) - auto enable_tunnel_mode_parameter_value = - mime_type.GetParamStringValue("tunnelmode", ""); - if (!enable_tunnel_mode_parameter_value.empty() && - enable_tunnel_mode_parameter_value != "true" && - enable_tunnel_mode_parameter_value != "false") { - SB_LOG(INFO) << "Invalid value for video mime parameter \"tunnelmode\": " - << enable_tunnel_mode_parameter_value << "."; + // https://source.android.com/devices/tv/multimedia-tunneling + mime_type.RegisterBoolParameter("tunnelmode"); + // Override endianness on HDR Info header. Defaults to little. + mime_type.RegisterStringParameter("hdrinfoendianness", "big|little"); + + if (!mime_type.is_valid()) { return false; - } else if (enable_tunnel_mode_parameter_value == "true" && - decode_to_texture_required) { + } + + bool must_support_tunnel_mode = + mime_type.GetParamBoolValue("tunnelmode", false); + if (must_support_tunnel_mode && decode_to_texture_required) { SB_LOG(WARNING) << "Tunnel mode is rejected because output mode decode to " "texture is required but not supported."; return false; } + JniEnvExt* env = JniEnvExt::Get(); ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime)); const bool must_support_hdr = (transfer_id != kSbMediaTransferIdBt709 && transfer_id != kSbMediaTransferIdUnspecified); - const bool must_support_tunnel_mode = - enable_tunnel_mode_parameter_value == "true"; + // We assume that if a device supports a format for clear playback, it will // also support it for encrypted playback. However, some devices require // tunneled playback to be encrypted, so we must align the tunnel mode
diff --git a/src/starboard/android/shared/platform_configuration/BUILD.gn b/src/starboard/android/shared/platform_configuration/BUILD.gn index 27b0a08..75b13be 100644 --- a/src/starboard/android/shared/platform_configuration/BUILD.gn +++ b/src/starboard/android/shared/platform_configuration/BUILD.gn
@@ -60,27 +60,6 @@ cflags += [ "-g" ] } - if (sb_pedantic_warnings) { - cflags += [ - "-Wall", - "-Wextra", - "-Wunreachable-code", - - # Don"t get pedantic about warnings from base macros. These must be - # disabled after the -Wall above, so this has to be done here rather - # than in the platform"s target toolchain. - # TODO: Rebase base and use static_assert instead of COMPILE_ASSERT - - "-Wno-unused-local-typedef", # COMPILE_ASSERT - "-Wno-missing-field-initializers", # LAZY_INSTANCE_INITIALIZER - - # It"s OK not to use some input parameters. Note that the order - # matters: Wall implies Wunused-parameter and Wno-unused-parameter - # has no effect if specified before Wall. - "-Wno-unused-parameter", - ] - } - if (use_asan) { cflags += [ "-fsanitize=address", @@ -206,3 +185,23 @@ config("library_config") { cflags = [ "-fPIC" ] } + +config("pedantic_warnings") { + cflags = [ + "-Wall", + "-Wextra", + "-Wunreachable-code", + + # Don't get pedantic about warnings from base macros. These must be + # disabled after the -Wall above, so this has to be done here rather + # than in the platform's target toolchain. + # TODO: Rebase base and use static_assert instead of COMPILE_ASSERT + "-Wno-unused-local-typedef", # COMPILE_ASSERT + "-Wno-missing-field-initializers", # LAZY_INSTANCE_INITIALIZER + + # It's OK not to use some input parameters. Note that the order + # matters: Wall implies Wunused-parameter and Wno-unused-parameter + # has no effect if specified before Wall. + "-Wno-unused-parameter", + ] +}
diff --git a/src/starboard/android/shared/platform_configuration/configuration.gni b/src/starboard/android/shared/platform_configuration/configuration.gni index 19850b6..f1c28dd 100644 --- a/src/starboard/android/shared/platform_configuration/configuration.gni +++ b/src/starboard/android/shared/platform_configuration/configuration.gni
@@ -39,6 +39,9 @@ size_config_path = "//starboard/android/shared/platform_configuration:size" speed_config_path = "//starboard/android/shared/platform_configuration:speed" +pedantic_warnings_config_path = + "//starboard/android/shared/platform_configuration:pedantic_warnings" + executable_configs += [ "//starboard/android/shared/platform_configuration:executable_config" ] shared_library_configs +=
diff --git a/src/starboard/android/shared/platform_service.cc b/src/starboard/android/shared/platform_service.cc index 7e3a479..0670bc8 100644 --- a/src/starboard/android/shared/platform_service.cc +++ b/src/starboard/android/shared/platform_service.cc
@@ -78,7 +78,7 @@ void Close(CobaltExtensionPlatformService service) { JniEnvExt* env = JniEnvExt::Get(); - env->CallVoidMethodOrAbort(service->cobalt_service, "close", "()V"); + env->CallVoidMethodOrAbort(service->cobalt_service, "onClose", "()V"); ScopedLocalJavaRef<jstring> j_name( env->NewStringStandardUTFOrAbort(service->name)); env->CallStarboardVoidMethodOrAbort("closeCobaltService",
diff --git a/src/starboard/android/shared/player_components_factory.h b/src/starboard/android/shared/player_components_factory.h index b2cbfed..037e757 100644 --- a/src/starboard/android/shared/player_components_factory.h +++ b/src/starboard/android/shared/player_components_factory.h
@@ -183,24 +183,6 @@ return (value + alignment - 1) / alignment * alignment; } - static bool IsAudioDeviceCallbackEnabled( - const CreationParameters& creation_parameters) { - using starboard::shared::starboard::media::MimeType; - - MimeType mime_type(creation_parameters.audio_mime()); - auto enable_audio_device_callback_parameter_value = - mime_type.GetParamStringValue("enableaudiodevicecallback", ""); - if (enable_audio_device_callback_parameter_value.empty() || - enable_audio_device_callback_parameter_value == "true") { - SB_LOG(INFO) << "AudioDeviceCallback is enabled."; - return true; - } - SB_LOG(INFO) << "Mime attribute \"enableaudiodevicecallback\" is set to: " - << enable_audio_device_callback_parameter_value - << ". AudioDeviceCallback is disabled."; - return false; - } - scoped_ptr<PlayerComponents> CreateComponents( const CreationParameters& creation_parameters, std::string* error_message) override { @@ -215,8 +197,21 @@ } MimeType audio_mime_type(creation_parameters.audio_mime()); - if (audio_mime_type.GetParamStringValue("audiopassthrough", "") == - "false") { + + if (strlen(creation_parameters.audio_mime()) > 0) { + audio_mime_type.RegisterBoolParameter("enableaudiodevicecallback"); + audio_mime_type.RegisterBoolParameter("audiopassthrough"); + if (!audio_mime_type.is_valid()) { + return scoped_ptr<PlayerComponents>(); + } + } + + bool enable_audio_device_callback = + audio_mime_type.GetParamBoolValue("enableaudiodevicecallback", true); + SB_LOG(INFO) << "AudioDeviceCallback is " + << (enable_audio_device_callback ? "enabled." : "disabled."); + + if (!audio_mime_type.GetParamBoolValue("audiopassthrough", true)) { SB_LOG(INFO) << "Mime attribute \"audiopassthrough\" is set to: " "false. Passthrough is disabled."; return scoped_ptr<PlayerComponents>(); @@ -228,7 +223,7 @@ audio_renderer.reset(new AudioRendererPassthrough( creation_parameters.audio_sample_info(), GetExtendedDrmSystem(creation_parameters.drm_system()), - IsAudioDeviceCallbackEnabled(creation_parameters))); + enable_audio_device_callback)); if (!audio_renderer->is_valid()) { return scoped_ptr<PlayerComponents>(); } @@ -272,31 +267,52 @@ using starboard::shared::starboard::media::MimeType; SB_DCHECK(error_message); + const char* audio_mime = + creation_parameters.audio_codec() != kSbMediaAudioCodecNone + ? creation_parameters.audio_mime() + : ""; + MimeType audio_mime_type(audio_mime); + if (creation_parameters.audio_codec() != kSbMediaAudioCodecNone && + strlen(creation_parameters.audio_mime()) > 0) { + audio_mime_type.RegisterBoolParameter("tunnelmode"); + audio_mime_type.RegisterBoolParameter("enableaudiodevicecallback"); + + if (!audio_mime_type.is_valid()) { + return false; + } + } + + const char* video_mime = + creation_parameters.video_codec() != kSbMediaVideoCodecNone + ? creation_parameters.video_mime() + : ""; + MimeType video_mime_type(video_mime); + if (creation_parameters.video_codec() != kSbMediaVideoCodecNone && + strlen(creation_parameters.video_mime()) > 0) { + video_mime_type.RegisterBoolParameter("tunnelmode"); + + if (!video_mime_type.is_valid()) { + return false; + } + } + int tunnel_mode_audio_session_id = -1; bool enable_tunnel_mode = false; if (creation_parameters.audio_codec() != kSbMediaAudioCodecNone && creation_parameters.video_codec() != kSbMediaVideoCodecNone) { - MimeType audio_mime_type(creation_parameters.audio_mime()); - MimeType video_mime_type(creation_parameters.video_mime()); - auto enable_tunnel_mode_audio_parameter_value = - audio_mime_type.GetParamStringValue("tunnelmode", ""); - auto enable_tunnel_mode_video_parameter_value = - video_mime_type.GetParamStringValue("tunnelmode", ""); - if (enable_tunnel_mode_audio_parameter_value == "true" && - enable_tunnel_mode_video_parameter_value == "true") { - enable_tunnel_mode = true; - } else { - if (enable_tunnel_mode_audio_parameter_value.empty()) { - enable_tunnel_mode_audio_parameter_value = "not provided"; - } - if (enable_tunnel_mode_video_parameter_value.empty()) { - enable_tunnel_mode_video_parameter_value = "not provided"; - } - SB_LOG(INFO) << "Tunnel mode is disabled. Audio mime parameter " - "\"tunnelmode\" value: " - << enable_tunnel_mode_audio_parameter_value + bool enable_tunnel_mode = + audio_mime_type.GetParamBoolValue("tunnelmode", false) && + video_mime_type.GetParamBoolValue("tunnelmode", false); + + if (!enable_tunnel_mode) { + SB_LOG(INFO) << "Tunnel mode is disabled. " + << "Audio mime parameter \"tunnelmode\" value: " + << audio_mime_type.GetParamStringValue("tunnelmode", + "<not provided>") << ", video mime parameter \"tunnelmode\" value: " - << enable_tunnel_mode_video_parameter_value << "."; + << video_mime_type.GetParamStringValue("tunnelmode", + "<not provided>") + << "."; } } else { SB_LOG(INFO) << "Tunnel mode requires both an audio and video stream. " @@ -359,7 +375,10 @@ decoder_creator)); bool enable_audio_device_callback = - IsAudioDeviceCallbackEnabled(creation_parameters); + audio_mime_type.GetParamBoolValue("enableaudiodevicecallback", true); + SB_LOG(INFO) << "AudioDeviceCallback is " + << (enable_audio_device_callback ? "enabled." : "disabled."); + if (tunnel_mode_audio_session_id != -1) { *audio_renderer_sink = TryToCreateTunnelModeAudioRendererSink( tunnel_mode_audio_session_id, creation_parameters, @@ -431,6 +450,20 @@ int tunnel_mode_audio_session_id, bool force_secure_pipeline_under_tunnel_mode, std::string* error_message) { + using starboard::shared::starboard::media::MimeType; + // Use mime param to determine endianness of HDR metadata. If param is + // missing or invalid it defaults to Little Endian. + MimeType video_mime_type(creation_parameters.video_mime()); + + if (strlen(creation_parameters.video_mime()) > 0) { + video_mime_type.RegisterStringParameter("hdrinfoendianness", + "big|little"); + } + const std::string& hdr_info_endianness = + video_mime_type.GetParamStringValue("hdrinfoendianness", + /*default=*/"little"); + bool force_big_endian_hdr_metadata = hdr_info_endianness == "big"; + scoped_ptr<VideoDecoder> video_decoder(new VideoDecoder( creation_parameters.video_codec(), GetExtendedDrmSystem(creation_parameters.drm_system()), @@ -438,7 +471,7 @@ creation_parameters.decode_target_graphics_context_provider(), creation_parameters.max_video_capabilities(), tunnel_mode_audio_session_id, force_secure_pipeline_under_tunnel_mode, - error_message)); + force_big_endian_hdr_metadata, error_message)); if (creation_parameters.video_codec() == kSbMediaVideoCodecAv1 || video_decoder->is_decoder_created()) { return video_decoder.Pass();
diff --git a/src/starboard/android/shared/starboard_platform.gypi b/src/starboard/android/shared/starboard_platform.gypi index 8338cbc..6e61339 100644 --- a/src/starboard/android/shared/starboard_platform.gypi +++ b/src/starboard/android/shared/starboard_platform.gypi
@@ -164,6 +164,7 @@ 'system_has_capability.cc', 'system_network_is_disconnected.cc', 'system_platform_error.cc', + 'system_request_freeze_no_freezedone_callback.cc', 'system_request_stop.cc', 'system_request_suspend.cc', 'thread_create.cc', @@ -357,7 +358,6 @@ '<(DEPTH)/starboard/shared/signal/suspend_signals.cc', '<(DEPTH)/starboard/shared/signal/suspend_signals.h', '<(DEPTH)/starboard/shared/signal/system_request_conceal.cc', - '<(DEPTH)/starboard/shared/signal/system_request_freeze_no_freezedone_callback.cc', '<(DEPTH)/starboard/shared/starboard/application.cc', '<(DEPTH)/starboard/shared/starboard/application.h', '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_create.cc',
diff --git a/src/starboard/android/shared/system_get_property.cc b/src/starboard/android/shared/system_get_property.cc index fa9b8ce..9d17a69 100644 --- a/src/starboard/android/shared/system_get_property.cc +++ b/src/starboard/android/shared/system_get_property.cc
@@ -106,10 +106,9 @@ value_length, kUnknownValue); case kSbSystemPropertyModelYear: { char key1[PROP_VALUE_MAX] = ""; - SB_DCHECK(GetAndroidSystemProperty("ro.oem.key1", key1, PROP_VALUE_MAX, - kUnknownValue)); - if (strcmp(key1, kUnknownValue) == 0 || - strlen(key1) < 10) { + GetAndroidSystemProperty("ro.oem.key1", key1, PROP_VALUE_MAX, + kUnknownValue); + if (strcmp(key1, kUnknownValue) == 0 || strlen(key1) < 10) { return CopyStringAndTestIfSuccess(out_value, value_length, kUnknownValue); }
diff --git a/src/starboard/shared/signal/system_request_freeze_no_freezedone_callback.cc b/src/starboard/android/shared/system_request_freeze_no_freezedone_callback.cc similarity index 74% rename from src/starboard/shared/signal/system_request_freeze_no_freezedone_callback.cc rename to src/starboard/android/shared/system_request_freeze_no_freezedone_callback.cc index ad245d6..d245e9e 100644 --- a/src/starboard/shared/signal/system_request_freeze_no_freezedone_callback.cc +++ b/src/starboard/android/shared/system_request_freeze_no_freezedone_callback.cc
@@ -14,9 +14,12 @@ #include "starboard/system.h" +#include "starboard/android/shared/jni_env_ext.h" #include "starboard/shared/signal/signal_internal.h" #include "starboard/shared/starboard/application.h" +using starboard::android::shared::JniEnvExt; + #if SB_IS(EVERGREEN_COMPATIBLE) && !SB_IS(EVERGREEN_COMPATIBLE_LITE) #include "starboard/loader_app/pending_restart.h" #endif // SB_IS(EVERGREEN_COMPATIBLE) && !SB_IS(EVERGREEN_COMPATIBLE_LITE) @@ -29,16 +32,22 @@ SbLogFlush(); starboard::shared::starboard::Application::Get()->Stop(0); } else { - // Let the platform decide if directly transit into Frozen. There - // is no FreezeDone callback for stopping all thread execution + // There is no FreezeDone callback for stopping all thread execution // after fully transitioning into Frozen. starboard::shared::starboard::Application::Get()->Freeze(NULL, NULL); + + // Let Android platform directly transit into Frozen. + JniEnvExt* env = JniEnvExt::Get(); + env->CallStarboardVoidMethodOrAbort("requestSuspend", "()V"); } #else - // Let the platform decide if directly transit into Frozen. There - // is no FreezeDone callback for stopping all thread execution + // There is no FreezeDone callback for stopping all thread execution // after fully transitioning into Frozen. starboard::shared::starboard::Application::Get()->Freeze(NULL, NULL); + + // Let Android platform directly transit into Frozen. + JniEnvExt* env = JniEnvExt::Get(); + env->CallStarboardVoidMethodOrAbort("requestSuspend", "()V"); #endif // SB_IS(EVERGREEN_COMPATIBLE) && !SB_IS(EVERGREEN_COMPATIBLE_LITE) } #endif // SB_API_VERSION >= 13
diff --git a/src/starboard/android/shared/video_decoder.cc b/src/starboard/android/shared/video_decoder.cc index bdd2226..775496c 100644 --- a/src/starboard/android/shared/video_decoder.cc +++ b/src/starboard/android/shared/video_decoder.cc
@@ -218,6 +218,7 @@ const char* max_video_capabilities, int tunnel_mode_audio_session_id, bool force_secure_pipeline_under_tunnel_mode, + bool force_big_endian_hdr_metadata, std::string* error_message) : video_codec_(video_codec), drm_system_(static_cast<DrmSystem*>(drm_system)), @@ -228,7 +229,8 @@ has_new_texture_available_(false), surface_condition_variable_(surface_destroy_mutex_), require_software_codec_(max_video_capabilities && - strlen(max_video_capabilities) > 0) { + strlen(max_video_capabilities) > 0), + force_big_endian_hdr_metadata_(force_big_endian_hdr_metadata) { SB_DCHECK(error_message); if (tunnel_mode_audio_session_id != -1) { @@ -556,7 +558,8 @@ drm_system_, color_metadata_ ? &*color_metadata_ : nullptr, require_software_codec_, std::bind(&VideoDecoder::OnTunnelModeFrameRendered, this, _1), - tunnel_mode_audio_session_id_, error_message)); + tunnel_mode_audio_session_id_, force_big_endian_hdr_metadata_, + error_message)); if (media_decoder_->is_valid()) { if (error_cb_) { media_decoder_->Initialize(
diff --git a/src/starboard/android/shared/video_decoder.h b/src/starboard/android/shared/video_decoder.h index 69c2cfd..cc5dc1e 100644 --- a/src/starboard/android/shared/video_decoder.h +++ b/src/starboard/android/shared/video_decoder.h
@@ -67,6 +67,7 @@ const char* max_video_capabilities, int tunnel_mode_audio_session_id, bool force_secure_pipeline_under_tunnel_mode, + bool force_big_endian_hdr_metadata, std::string* error_message); ~VideoDecoder() override; @@ -135,6 +136,9 @@ // the main player and SW decoder for sub players. const bool require_software_codec_; + // Force endianness of HDR Metadata. + const bool force_big_endian_hdr_metadata_; + const int tunnel_mode_audio_session_id_ = -1; // On some platforms tunnel mode is only supported in the secure pipeline. So // we create a dummy drm system to force the video playing in secure pipeline
diff --git a/src/starboard/android/x86/gyp_configuration.py b/src/starboard/android/x86/gyp_configuration.py index 2caad96..80de3ec 100644 --- a/src/starboard/android/x86/gyp_configuration.py +++ b/src/starboard/android/x86/gyp_configuration.py
@@ -47,9 +47,23 @@ '.SunnyDaySourceForDestination/*', 'SbMediaSetAudioWriteDurationTests/SbMediaSetAudioWriteDurationTest' '.WriteContinuedLimitedInput/*', + 'SbMediaSetAudioWriteDurationTests/SbMediaSetAudioWriteDurationTest' + '.WriteLimitedInput/*', ], 'player_filter_tests': [ - 'VideoDecoderTests/*', 'AudioDecoderTests/*', + 'VideoDecoderTests/*', + + 'PlayerComponentsTests/PlayerComponentsTest.Preroll/*', + 'PlayerComponentsTests/PlayerComponentsTest.Pause/*', + + 'PlayerComponentsTests/PlayerComponentsTest.*/2', + 'PlayerComponentsTests/PlayerComponentsTest.*/4', + 'PlayerComponentsTests/PlayerComponentsTest.*/9', + 'PlayerComponentsTests/PlayerComponentsTest.*/11', + 'PlayerComponentsTests/PlayerComponentsTest.*/16', + 'PlayerComponentsTests/PlayerComponentsTest.*/17', + 'PlayerComponentsTests/PlayerComponentsTest.*/20', + 'PlayerComponentsTests/PlayerComponentsTest.*/21', ], }
diff --git a/src/starboard/build/config/BUILD.gn b/src/starboard/build/config/BUILD.gn index 05310b8..e903cb3 100644 --- a/src/starboard/build/config/BUILD.gn +++ b/src/starboard/build/config/BUILD.gn
@@ -149,3 +149,15 @@ configs = [ size_config_path ] } } + +config("pedantic_warnings") { + if (defined(pedantic_warnings_config_path)) { + configs = [ pedantic_warnings_config_path ] + } +} + +config("no_pedantic_warnings") { + if (defined(no_pedantic_warnings_config_path)) { + configs = [ no_pedantic_warnings_config_path ] + } +}
diff --git a/src/starboard/build/config/BUILDCONFIG.gn b/src/starboard/build/config/BUILDCONFIG.gn index 31a8fd5..38169b8 100644 --- a/src/starboard/build/config/BUILDCONFIG.gn +++ b/src/starboard/build/config/BUILDCONFIG.gn
@@ -101,11 +101,12 @@ default_compiler_configs = [ "//build/config/compiler:default_include_dirs", "//build/config/compiler:no_exceptions", + "//$starboard_path/platform_configuration", "//starboard/build/config:base", "//starboard/build/config:host", "//starboard/build/config:size", "//starboard/build/config:target", - "//$starboard_path/platform_configuration", + "//starboard/build/config:no_pedantic_warnings", ] if (is_starboard) {
diff --git a/src/starboard/build/config/base_configuration.gni b/src/starboard/build/config/base_configuration.gni index 3b798ea..f2ea066 100644 --- a/src/starboard/build/config/base_configuration.gni +++ b/src/starboard/build/config/base_configuration.gni
@@ -23,10 +23,6 @@ # value is meant to be overridden by a Starboard ABI file. sb_api_version = 13 - # Enabling this variable enables pedantic levels of warnings for the current - # toolchain. - sb_pedantic_warnings = false - # Enables embedding Cobalt as a shared library within another app. This # requires a 'lib' starboard implementation for the corresponding platform. sb_enable_lib = false
diff --git a/src/starboard/build/doc/gn_migrate_stub_to_platform.md b/src/starboard/build/doc/gn_migrate_stub_to_platform.md new file mode 100644 index 0000000..f07e338 --- /dev/null +++ b/src/starboard/build/doc/gn_migrate_stub_to_platform.md
@@ -0,0 +1,128 @@ +# Stub to Platform GN Migration + +This document outlines a step by step process for converting the stub platform's +GN files to GN files that will be able to be built for your platform. It assumes +you have an already working port of Starboard using GYP. + +## Steps to Migrate Stub Files to your platform's GN Files + +This is **one** way for migrating your platform from GYP to GN. The benefit of +following this is that you can have regular checkpoints to see if your migration +is going correctly, rather than trying to do the entire migration at once where +it's uncertain how much progress is being made. \ +Here are the steps to do your migration: + +1. [Copy stub files over to your platform and build them](#copy-stub-files-over-to-your-platform-and-build-them). +2. [Replace stub toolchain with your platform's toolchain](#replace-stub-toolchain-with-your-platforms-toolchain). +3. [Replace stub configuration with your platform's configuration](#replace-stub-configuration-with-your-platforms-configuration). +4. [Replace stubbed starboard_platform target sources with your platform's + sources](#replace-stubbed-starboardplatform-sources-with-your-platforms-sources). + +After each step, you should be able to build the starboard_platform target. +For example, you would build raspi2 starboard_platform target with the following +commands: +``` +$gn gen out/raspi-2gn_devel --args='target_platform="raspi-2" build_type="devel"' +$ninja -C out/raspi-2gn_devel/ starboard +``` + +### Copy Stub Files Over to Your Platform and Build Them + +Here is a list of steps outlining which files to copy over and how to build +those files: + +1. Copy over files from the stub implementation to the platform folder. This + list gives you an example of which files to copy over for your platform. + This is an example for files to be copied over for your platform's port at + starboard/YOUR_PLATFORM + * starboard/stub/BUILD.gn > starboard/YOUR_PLATFORM/BUILD.gn + * starboard/stub/platform_configuration/BUILD.gn > + starboard/YOUR_PLATFORM/platform_configuration/BUILD.gn + * starboard/stub/platform_configuration/configuration.gni > + starboard/YOUR_PLATFORM/platform_configuration/configuration.gni + * starboard/stub/toolchain/BUILD.gn > + starboard/YOUR_PLATFORM/toolchain/BUILD.gn +2. Add your platform path to starboard/build/platforms.gni as referenced + [here](../migrating_gyp_to_gn.md#adding-your-platform-to-starboard) +3. Resolve any errors which come up for missing/incorrect file paths. Then, you + should be able to build your platform target with the stubbed out files + suggested in the above section. + +### Replace Stub Toolchain with Your Platform's Toolchain + +Follow instructions [here](../migrating_gyp_to_gn.md#migrating-a-toolchain) for +migrating the toolchain. Resolve errors and build the starboard_platform target +with the stubbed files. + +### Replace Stub Configuration with Your Platform's Configuration + +This involves migrating the compiler flags and build variables as referenced +[here](../migrating_gyp_to_gn.md#migrating-a-platform). + +> **Highly recommended** \ +> It’s good to turn off the `treat_warnings_as_errors flag` until you can compile +> the starboard_platform target with the platform files. +> If this flag is not disabled you might run into a lot of +> warnings turned errors and it might take time to solve all those errors. +> Meanwhile you won't be in a buildable state which might make it uncertain as to +> how much progress you are actually making. +> For disabling the flag you can pass that as an argument to gn. +> Here's an example for disabling the flag for raspi2: +> ``` +> $gn gen out/raspi-2gn_devel --args='target_platform="raspi-2" build_type="devel" treat_warnings_as_errors=false' +> ``` + +Resolve errors and build the starboard_platform target with the stubbed files. + +### Replace Stubbed starboard_platform Sources with Your Platform's Sources + +This involves adding files for the starboard_platform target as suggested +[here](../migrating_gyp_to_gn.md#migrating-a-platform). + +While building any target, follow the recommendation above of building the +target with `treat_warnings_as_errors=false`. + +Once you can build your platform files, you can remove the +`treat_warnings_as_errors=false` flag and resolve the warning errors. + +## FAQ + +1. **I’m getting a build error! What should I do?** \ + Some common questions to ask yourself: + + * Is the same target building with GYP + ninja (as GN + Ninja)? + + > For example if the `nplb` target is not being built by GN, check first + > if it can be built with GYP. If GYP cannot build it, this indicates + > that some flags are missing in GYP itself so it might be prudent to + > solve that first before migrating to GN. + + * Am I missing a config/dependency to include the missing file? + + > [gn check](https://cobalt.googlesource.com/third_party/gn/+/refs/heads/main/docs/reference.md#cmd_check) + > can help point out missing dependencies. + + * Is the same file being included in the build by GYP? + + > Add a preprocessor directive like #error "This file is included" in + > that file and see if GYP + Ninja prints out that error message. + + * Is the same code path being followed by GYP + ninja ? + + > Use the same method as above. + + * Are the compiler flags for this file the same as in GYP ? + + > To compare flags for GYP vs GN refer + > [section](../migrating_gyp_to_gn.md#validating-a-target). To check if + > the variables/flags you are compiling have changed since GYP, refer + > [page](../migration_changes.md). + + * Have you passed in the default arguments for your platform correctly? + + > Default variables such as `target_cpu`, `target_os` and others can be + > overridden by passing arguments to gn while building. Here's an + > example of passing the default argument `target_cpu` for raspi2: + > ``` + > $gn gen out/raspi-2gn_devel --args='target_platform="raspi-2" build_type="devel" target_cpu="arm"' + > ```
diff --git a/src/starboard/build/doc/migrating_gyp_to_gn.md b/src/starboard/build/doc/migrating_gyp_to_gn.md index 2333987..1a3d4bb 100644 --- a/src/starboard/build/doc/migrating_gyp_to_gn.md +++ b/src/starboard/build/doc/migrating_gyp_to_gn.md
@@ -203,6 +203,21 @@ comparison tool, i.e. [meld](https://meldmerge.org/). This will allow you to see any changes in commands, i.e. with flags or otherwise. +The name of the intermediate .o, .d files is different in both cases: this +doesn't cause any issues. Keep this in mind while comparing the ninja flags for +GYP vs GN. Here is an example for raspi2 while compiling the same source file +``` +starboard/common/new.cc +``` +GYP generates: +``` +obj/starboard/common/common.new.cc.o +``` +GN generates: +``` +obj/starboard/common/common/new.o +``` + ### Validating a Platform Checking that an entire platform has been migrated successfully is slightly more @@ -214,6 +229,12 @@ You can use the same comparison method of using `format_ninja.py` as discussed [in the section above](#validating-a-target). +### Step by Step Stub to Your Platform Migration Guide + +This [document](../gn_migrate_stub_to_platform.md) outlines a step by step +process for converting the stub platform's GN files to GN files that will be +able to be built for your platform. + [cobalt_porting_guide]: https://cobalt.dev/starboard/porting.html [gn_check_tool]: https://cobalt.googlesource.com/third_party/gn/+/refs/heads/main/docs/reference.md#cmd_check [gn_doc_home]: https://cobalt.googlesource.com/third_party/gn/+/refs/heads/main/docs
diff --git a/src/starboard/build/doc/migration_changes.md b/src/starboard/build/doc/migration_changes.md index 145ed7b..433d1a3 100644 --- a/src/starboard/build/doc/migration_changes.md +++ b/src/starboard/build/doc/migration_changes.md
@@ -38,6 +38,8 @@ `optimize_target_for_speed` (1) | `"//starboard/build/config:speed"` | Optimizations `compiler_flags_*_speed` | `speed_config_path` | Optimizations `compiler_flags_*_size` | `size_config_path` | Optimizations +`sb_pedantic_warnings` | `pedantic_warnings_config_path` | Compiler Options +`sb_pedantic_warnings` | `no_pedantic_warnings_config_path` | Compiler Options Notes: @@ -53,3 +55,14 @@ configurations for your platform by creating `config`s and pointing to the correct ones for `speed_config_path` and `size_config_path` in your platform's `platform_configuration/configuration.gni` file. + +* *Compiler Options:* Cobalt compiles some targets with stricter settings + than others, depending on the platform. Before these targets would opt into + the stricter settings by settings `sb_pedantic_warnings: 1` in their + `variables` section. Now they will add the appropriate config like so: + `configs += [ "//starboard/build/config:pedantic_warnings" ]` and remove + the default: `configs -= [ "//starboard/build/config:no_pedantic_warnings" + ]`. The additional config that is used to compile these targets is + specified with the `pedantic_warnings_config_path` and + `no_pedantic_warnings_config_path` variables in your platform's + `platform_configuration/configuration.gni` file.
diff --git a/src/starboard/build/platforms.gni b/src/starboard/build/platforms.gni index b3793c2..ed9af54 100644 --- a/src/starboard/build/platforms.gni +++ b/src/starboard/build/platforms.gni
@@ -57,4 +57,8 @@ name = "raspi-2" path = "starboard/raspi/2" }, + { + name = "raspi-2-skia" + path = "starboard/raspi/2/skia" + }, ]
diff --git a/src/starboard/doc/evergreen/cobalt_evergreen_lite.md b/src/starboard/doc/evergreen/cobalt_evergreen_lite.md index 1dcbbc1..06dacf9 100644 --- a/src/starboard/doc/evergreen/cobalt_evergreen_lite.md +++ b/src/starboard/doc/evergreen/cobalt_evergreen_lite.md
@@ -75,7 +75,7 @@ ## How is Evergreen different from porting Cobalt previously? -Same as the [Evergreen full doc](https://cobalt.googlesource.com/cobalt/+/refs/heads/master/src/starboard/doc/evergreen/cobalt_evergreen_overview.md). +Same as the [Evergreen full doc](cobalt_evergreen_overview.md). ## Building Cobalt Evergreen Components
diff --git a/src/starboard/doc/evergreen/cobalt_evergreen_overview.md b/src/starboard/doc/evergreen/cobalt_evergreen_overview.md index 75b6940..51130f1 100644 --- a/src/starboard/doc/evergreen/cobalt_evergreen_overview.md +++ b/src/starboard/doc/evergreen/cobalt_evergreen_overview.md
@@ -408,7 +408,7 @@ The number of installation slots is directly controlled using `kMaxNumInstallations`, defined in -[loader\_app.cc](https://cobalt.googlesource.com/cobalt/+/refs/heads/master/src/starboard/loader_app/loader_app.cc). +[loader\_app.cc](../../loader_app/loader_app.cc). It is worth noting that all slot configurations specify that the first installation slot (`SLOT_0`) will always be the read-only factory system image. @@ -549,7 +549,7 @@ suspending if there is a pending restart. Please see -[`suspend_signals.cc`](https://cobalt.googlesource.com/cobalt/+/refs/heads/master/src/starboard/shared/signal/suspend_signals.cc) +[`suspend_signals.cc`](../../shared/signal/suspend_signals.cc) for an example. ### Multi-App Support @@ -644,7 +644,7 @@ ``` Please see -[`loader_app_switches.cc`](https://cobalt.googlesource.com/cobalt/+/refs/heads/master/src/starboard/loader_app/loader_app.cc) +[`loader_app_switches.cc`](../../loader_app/loader_app.cc) for full list of available command-line flags. ### Platform Security
diff --git a/src/starboard/doc/evergreen/cobalt_evergreen_reference_port_raspi2.md b/src/starboard/doc/evergreen/cobalt_evergreen_reference_port_raspi2.md index b72d0f7..a9fec25 100644 --- a/src/starboard/doc/evergreen/cobalt_evergreen_reference_port_raspi2.md +++ b/src/starboard/doc/evergreen/cobalt_evergreen_reference_port_raspi2.md
@@ -53,8 +53,15 @@ $ cp -r out/evergreen-arm-hardfp-sbversion-12_qa/lib $COEG_PATH/content/app/cobalt/ $ cp -r out/evergreen-arm-hardfp-sbversion-12_qa/content $COEG_PATH/content/app/cobalt/ -## Download the manifest file -$ curl https://storage.googleapis.com/evergreen_public/latest/manifest.json -o $COEG_PATH/content/app/cobalt/manifest.json +## Create a file named manifest.json with the following content, and put it under $COEG_PATH/content/app/cobalt/ +$ cat > $COEG_PATH/content/app/cobalt/manifest.json <<EOF +{ + "manifest_version": 2, + "name": "Cobalt", + "description": "Cobalt", + "version": "1.0.0" +} +EOF ``` ## Deployment instructions
diff --git a/src/starboard/doc/starboard_abi.md b/src/starboard/doc/starboard_abi.md index f97a788..bd0d309 100644 --- a/src/starboard/doc/starboard_abi.md +++ b/src/starboard/doc/starboard_abi.md
@@ -36,18 +36,18 @@ With the Starboard ABI being the source of truth for all things architecture related, each platform must now include a Starboard ABI file in its build (see -[//starboard/sabi](https://cobalt.googlesource.com/cobalt/+/refs/heads/master/src/starboard/sabi) +[//starboard/sabi](../sabi) for examples). Starboard ABI files are JSON, and should all contain identical keys with the values being appropriate for the architecture. Each platform must override the new -[GetPathToSabiJsonFile](https://cobalt.googlesource.com/cobalt/+/refs/heads/master/src/starboard/build/platform_configuration.py##339) +[GetPathToSabiJsonFile](../build/platform_configuration.py##339) method in its platform configuration to return the path to the desired Starboard ABI file (e.g. -[//starboard/linux/shared/gyp\_configuration.py](https://cobalt.googlesource.com/cobalt/+/refs/heads/master/src/starboard/linux/shared/gyp_configuration.py)). +[//starboard/linux/shared/gyp\_configuration.py](../linux/shared/gyp_configuration.py)). By default, an empty and invalid Starboard ABI file is provided. Additionally, all platforms must include the -[sabi.gypi](https://cobalt.googlesource.com/cobalt/+/refs/heads/master/src/starboard/sabi/sabi.gypi) +[sabi.gypi](../sabi/sabi.gypi) in their build configuration. This file will consume the specified Starboard ABI file, resulting in the creation of a set of GYP variables and preprocessor macros. The preprocessor macros are passed directly to the compiler and replace @@ -56,7 +56,7 @@ The newly defined GYP variables need to be transformed into toolchain specific flags; these flags are what actually makes the build result in a binary for the desired architecture. These flags will, in most cases, be identical to the flags -already being used for building. +already being used for building. The process outlined above is shown in the diagram below. @@ -106,14 +106,14 @@ 1. When configuring your build, the Starboard ABI file that was specified will have its values sanity checked against a provided - [schema](https://cobalt.googlesource.com/cobalt/+/refs/heads/master/src/starboard/sabi/sabi.schema.json). + [schema](../sabi/sabi.schema.json). 1. When building, a number of static assertions will assert correctness of a number of features generated from the Starboard ABI file against the features of the binary. 1. The NPLB test suite has been expanded to include [additional - tests](https://cobalt.googlesource.com/cobalt/+/refs/heads/master/src/starboard/nplb/sabi/) + tests](../nplb/sabi/) capable of verifying the remaining features of the binary. Finally, binaries produced by the Cobalt team for your architecture, including NPLB, will be made available to ensure end-to-end correctness of the produced -binaries. \ No newline at end of file +binaries.
diff --git a/src/starboard/elf_loader/elf_loader.gyp b/src/starboard/elf_loader/elf_loader.gyp index 27cdce7..b93ed1d 100644 --- a/src/starboard/elf_loader/elf_loader.gyp +++ b/src/starboard/elf_loader/elf_loader.gyp
@@ -142,6 +142,7 @@ ], 'dependencies': [ 'elf_loader_sys', + '<(DEPTH)/starboard/elf_loader/sabi_string.gyp:sabi_string', '<(DEPTH)/starboard/starboard.gyp:starboard', ], 'sources': [
diff --git a/src/starboard/linux/shared/platform_configuration/BUILD.gn b/src/starboard/linux/shared/platform_configuration/BUILD.gn index e6bfc37..9920863 100644 --- a/src/starboard/linux/shared/platform_configuration/BUILD.gn +++ b/src/starboard/linux/shared/platform_configuration/BUILD.gn
@@ -99,33 +99,6 @@ "-Wl,-gc-sections", ] - if (sb_pedantic_warnings) { - cflags += [ - "-Wall", - "-Wextra", - "-Wunreachable-code", - ] - } else { - cflags += [ - # 'this' pointer cannot be NULL...pointer may be assumed - # to always convert to true. - "-Wno-undefined-bool-conversion", - - # Skia doesn't use overrides. - "-Wno-inconsistent-missing-override", - - # Do not warn for implicit type conversions that may change a value. - "-Wno-conversion", - - # shifting a negative signed value is undefined - "-Wno-shift-negative-value", - - # Width of bit-field exceeds width of its type- value will be truncated - "-Wno-bitfield-width", - "-Wno-undefined-var-template", - ] - } - if (use_asan) { cflags += [ "-fsanitize=address", @@ -212,3 +185,32 @@ "-fdata-sections", ] } + +config("pedantic_warnings") { + cflags = [ + "-Wall", + "-Wextra", + "-Wunreachable-code", + ] +} + +config("no_pedantic_warnings") { + cflags = [ + # 'this' pointer cannot be NULL...pointer may be assumed + # to always convert to true. + "-Wno-undefined-bool-conversion", + + # Skia doesn't use overrides. + "-Wno-inconsistent-missing-override", + + # Do not warn for implicit type conversions that may change a value. + "-Wno-conversion", + + # shifting a negative signed value is undefined + "-Wno-shift-negative-value", + + # Width of bit-field exceeds width of its type- value will be truncated + "-Wno-bitfield-width", + "-Wno-undefined-var-template", + ] +}
diff --git a/src/starboard/linux/shared/platform_configuration/configuration.gni b/src/starboard/linux/shared/platform_configuration/configuration.gni index 3618b00..eda9b87 100644 --- a/src/starboard/linux/shared/platform_configuration/configuration.gni +++ b/src/starboard/linux/shared/platform_configuration/configuration.gni
@@ -27,4 +27,9 @@ speed_config_path = "//starboard/linux/shared/platform_configuration:speed" size_config_path = "//starboard/linux/shared/platform_configuration:size" +pedantic_warnings_config_path = + "//starboard/linux/shared/platform_configuration:pedantic_warnings" +no_pedantic_warnings_config_path = + "//starboard/linux/shared/platform_configuration:no_pedantic_warnings" + sb_widevine_platform = "linux"
diff --git a/src/starboard/nplb/drm_helpers.h b/src/starboard/nplb/drm_helpers.h index be29b72..2b43c12 100644 --- a/src/starboard/nplb/drm_helpers.h +++ b/src/starboard/nplb/drm_helpers.h
@@ -132,75 +132,15 @@ 0xbc, 0x6a, 0x6b, 0xed, 0x13, 0xfb, 0x0d, 0x49, 0xd3, 0x8a, 0x45, 0xeb, 0x87, 0xa5, 0xf4}; +// Widevine-specific CENC Initialization data. +// https://www.w3.org/TR/eme-stream-mp4/ +// https://www.w3.org/TR/eme-initdata-cenc/#common-system static constexpr uint8_t kCencInitData[] = { - 0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68, 0x00, 0x00, 0x00, 0x00, - 0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, - 0xd5, 0x1d, 0x21, 0xed, 0x00, 0x00, 0x00, 0x14, 0x08, 0x01, 0x12, 0x10, - 0x31, 0xfd, 0x5b, 0x66, 0x19, 0xfc, 0x5e, 0xad, 0x86, 0x7c, 0xff, 0xb5, - 0x84, 0xed, 0x4c, 0x19, 0x00, 0x00, 0x02, 0xf4, 0x70, 0x73, 0x73, 0x68, - 0x00, 0x00, 0x00, 0x00, 0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, - 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95, 0x00, 0x00, 0x02, 0xd4, - 0xd4, 0x02, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0xca, 0x02, 0x3c, 0x00, - 0x57, 0x00, 0x52, 0x00, 0x4d, 0x00, 0x48, 0x00, 0x45, 0x00, 0x41, 0x00, - 0x44, 0x00, 0x45, 0x00, 0x52, 0x00, 0x20, 0x00, 0x78, 0x00, 0x6d, 0x00, - 0x6c, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x3d, 0x00, 0x22, 0x00, 0x68, 0x00, - 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x2f, 0x00, - 0x73, 0x00, 0x63, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x61, 0x00, - 0x73, 0x00, 0x2e, 0x00, 0x6d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, - 0x6f, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, - 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2f, 0x00, 0x44, 0x00, 0x52, 0x00, - 0x4d, 0x00, 0x2f, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x37, 0x00, - 0x2f, 0x00, 0x30, 0x00, 0x33, 0x00, 0x2f, 0x00, 0x50, 0x00, 0x6c, 0x00, - 0x61, 0x00, 0x79, 0x00, 0x52, 0x00, 0x65, 0x00, 0x61, 0x00, 0x64, 0x00, - 0x79, 0x00, 0x48, 0x00, 0x65, 0x00, 0x61, 0x00, 0x64, 0x00, 0x65, 0x00, - 0x72, 0x00, 0x22, 0x00, 0x20, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, - 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x3d, 0x00, 0x22, 0x00, - 0x34, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x2e, 0x00, - 0x30, 0x00, 0x22, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x44, 0x00, 0x41, 0x00, - 0x54, 0x00, 0x41, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x50, 0x00, 0x52, 0x00, - 0x4f, 0x00, 0x54, 0x00, 0x45, 0x00, 0x43, 0x00, 0x54, 0x00, 0x49, 0x00, - 0x4e, 0x00, 0x46, 0x00, 0x4f, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x4b, 0x00, - 0x45, 0x00, 0x59, 0x00, 0x4c, 0x00, 0x45, 0x00, 0x4e, 0x00, 0x3e, 0x00, - 0x31, 0x00, 0x36, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x4b, 0x00, 0x45, 0x00, - 0x59, 0x00, 0x4c, 0x00, 0x45, 0x00, 0x4e, 0x00, 0x3e, 0x00, 0x3c, 0x00, - 0x41, 0x00, 0x4c, 0x00, 0x47, 0x00, 0x49, 0x00, 0x44, 0x00, 0x3e, 0x00, - 0x41, 0x00, 0x45, 0x00, 0x53, 0x00, 0x43, 0x00, 0x54, 0x00, 0x52, 0x00, - 0x3c, 0x00, 0x2f, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x47, 0x00, 0x49, 0x00, - 0x44, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x50, 0x00, 0x52, 0x00, - 0x4f, 0x00, 0x54, 0x00, 0x45, 0x00, 0x43, 0x00, 0x54, 0x00, 0x49, 0x00, - 0x4e, 0x00, 0x46, 0x00, 0x4f, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x4b, 0x00, - 0x49, 0x00, 0x44, 0x00, 0x3e, 0x00, 0x5a, 0x00, 0x6c, 0x00, 0x76, 0x00, - 0x39, 0x00, 0x4d, 0x00, 0x66, 0x00, 0x77, 0x00, 0x5a, 0x00, 0x72, 0x00, - 0x56, 0x00, 0x36, 0x00, 0x47, 0x00, 0x66, 0x00, 0x50, 0x00, 0x2b, 0x00, - 0x31, 0x00, 0x68, 0x00, 0x4f, 0x00, 0x31, 0x00, 0x4d, 0x00, 0x47, 0x00, - 0x51, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x4b, 0x00, - 0x49, 0x00, 0x44, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x43, 0x00, 0x48, 0x00, - 0x45, 0x00, 0x43, 0x00, 0x4b, 0x00, 0x53, 0x00, 0x55, 0x00, 0x4d, 0x00, - 0x3e, 0x00, 0x4a, 0x00, 0x58, 0x00, 0x46, 0x00, 0x36, 0x00, 0x57, 0x00, - 0x38, 0x00, 0x41, 0x00, 0x64, 0x00, 0x51, 0x00, 0x2b, 0x00, 0x49, 0x00, - 0x3d, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x43, 0x00, 0x48, 0x00, 0x45, 0x00, - 0x43, 0x00, 0x4b, 0x00, 0x53, 0x00, 0x55, 0x00, 0x4d, 0x00, 0x3e, 0x00, - 0x3c, 0x00, 0x4c, 0x00, 0x41, 0x00, 0x5f, 0x00, 0x55, 0x00, 0x52, 0x00, - 0x4c, 0x00, 0x3e, 0x00, 0x68, 0x00, 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, - 0x73, 0x00, 0x3a, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x77, 0x00, 0x77, 0x00, - 0x77, 0x00, 0x2e, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00, - 0x75, 0x00, 0x62, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, - 0x6d, 0x00, 0x2f, 0x00, 0x61, 0x00, 0x70, 0x00, 0x69, 0x00, 0x2f, 0x00, - 0x64, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x2f, 0x00, 0x70, 0x00, 0x6c, 0x00, - 0x61, 0x00, 0x79, 0x00, 0x72, 0x00, 0x65, 0x00, 0x61, 0x00, 0x64, 0x00, - 0x79, 0x00, 0x3f, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, - 0x63, 0x00, 0x65, 0x00, 0x3d, 0x00, 0x59, 0x00, 0x4f, 0x00, 0x55, 0x00, - 0x54, 0x00, 0x55, 0x00, 0x42, 0x00, 0x45, 0x00, 0x26, 0x00, 0x61, 0x00, - 0x6d, 0x00, 0x70, 0x00, 0x3b, 0x00, 0x76, 0x00, 0x69, 0x00, 0x64, 0x00, - 0x65, 0x00, 0x6f, 0x00, 0x5f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x3d, 0x00, - 0x39, 0x00, 0x33, 0x00, 0x39, 0x00, 0x35, 0x00, 0x62, 0x00, 0x34, 0x00, - 0x36, 0x00, 0x64, 0x00, 0x37, 0x00, 0x64, 0x00, 0x63, 0x00, 0x64, 0x00, - 0x37, 0x00, 0x38, 0x00, 0x38, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x2f, 0x00, - 0x4c, 0x00, 0x41, 0x00, 0x5f, 0x00, 0x55, 0x00, 0x52, 0x00, 0x4c, 0x00, - 0x3e, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x44, 0x00, 0x41, 0x00, 0x54, 0x00, - 0x41, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x2f, 0x00, 0x57, 0x00, 0x52, 0x00, - 0x4d, 0x00, 0x48, 0x00, 0x45, 0x00, 0x41, 0x00, 0x44, 0x00, 0x45, 0x00, - 0x52, 0x00, 0x3e, 0x00, 0x00}; + 0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68, 0x00, 0x00, 0x00, + 0x00, 0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, 0xa3, 0xc8, + 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed, 0x00, 0x00, 0x00, 0x14, 0x08, + 0x01, 0x12, 0x10, 0x31, 0xfd, 0x5b, 0x66, 0x19, 0xfc, 0x5e, 0xad, + 0x86, 0x7c, 0xff, 0xb5, 0x84, 0xed, 0x4c, 0x19}; } // namespace nplb } // namespace starboard
diff --git a/src/starboard/nplb/nplb_evergreen_compat_tests/BUILD.gn b/src/starboard/nplb/nplb_evergreen_compat_tests/BUILD.gn index b6efbad..05753b8 100644 --- a/src/starboard/nplb/nplb_evergreen_compat_tests/BUILD.gn +++ b/src/starboard/nplb/nplb_evergreen_compat_tests/BUILD.gn
@@ -18,6 +18,7 @@ sources = [ "//starboard/common/test_main.cc", "checks.h", + "crashpad_config_test.cc", "executable_memory_test.cc", "fonts_test.cc", "sabi_test.cc",
diff --git a/src/starboard/raspi/2/BUILD.gn b/src/starboard/raspi/2/BUILD.gn index 3cf94e9..92dbb16 100644 --- a/src/starboard/raspi/2/BUILD.gn +++ b/src/starboard/raspi/2/BUILD.gn
@@ -14,7 +14,11 @@ static_library("starboard_platform") { check_includes = false + sources = [ + "//starboard/raspi/shared/configuration.cc", + "//starboard/raspi/shared/configuration.h", + "//starboard/raspi/shared/system_get_extensions.cc", + ] configs += [ "//starboard/build/config:starboard_implementation" ] - public_deps = [ "//starboard/raspi/shared:starboard_platform" ] }
diff --git a/src/starboard/raspi/2/skia/BUILD.gn b/src/starboard/raspi/2/skia/BUILD.gn new file mode 100644 index 0000000..bcb5b92 --- /dev/null +++ b/src/starboard/raspi/2/skia/BUILD.gn
@@ -0,0 +1,24 @@ +# Copyright 2021 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. + +static_library("starboard_platform") { + check_includes = false + sources = [ + "//starboard/raspi/2/skia/configuration.cc", + "//starboard/raspi/2/skia/configuration.h", + "//starboard/raspi/2/skia/system_get_extensions.cc", + ] + configs += [ "//starboard/build/config:starboard_implementation" ] + public_deps = [ "//starboard/raspi/shared:starboard_platform" ] +}
diff --git a/src/starboard/raspi/2/skia/platform_configuration/BUILD.gn b/src/starboard/raspi/2/skia/platform_configuration/BUILD.gn new file mode 100644 index 0000000..17a93b7 --- /dev/null +++ b/src/starboard/raspi/2/skia/platform_configuration/BUILD.gn
@@ -0,0 +1,19 @@ +# Copyright 2021 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. + +config("platform_configuration") { + configs = [ + "//starboard/raspi/2/platform_configuration", + ] +}
diff --git a/src/starboard/raspi/2/skia/platform_configuration/configuration.gni b/src/starboard/raspi/2/skia/platform_configuration/configuration.gni new file mode 100644 index 0000000..4cd4761 --- /dev/null +++ b/src/starboard/raspi/2/skia/platform_configuration/configuration.gni
@@ -0,0 +1,15 @@ +# Copyright 2021 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. + +import("//starboard/raspi/shared/platform_configuration/configuration.gni")
diff --git a/src/starboard/raspi/2/skia/toolchain/BUILD.gn b/src/starboard/raspi/2/skia/toolchain/BUILD.gn new file mode 100644 index 0000000..653ad00 --- /dev/null +++ b/src/starboard/raspi/2/skia/toolchain/BUILD.gn
@@ -0,0 +1,33 @@ +# Copyright 2021 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. + +import("//build/toolchain/gcc_toolchain.gni") +import("//starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni") + +clang_toolchain("host") { + clang_base_path = raspi_clang_base_path +} + +gcc_toolchain("target") { + cc = "$gcc_toolchain_cc" + cxx = "$gcc_toolchain_cxx" + ld = cxx + + ar = "$gcc_toolchain_ar" + strip = "$gcc_toolchain_strip" + + toolchain_args = { + is_clang = false + } +}
diff --git a/src/starboard/raspi/2/toolchain/BUILD.gn b/src/starboard/raspi/2/toolchain/BUILD.gn index 57a35ca..7e872a6 100644 --- a/src/starboard/raspi/2/toolchain/BUILD.gn +++ b/src/starboard/raspi/2/toolchain/BUILD.gn
@@ -13,23 +13,20 @@ # limitations under the License. import("//build/toolchain/gcc_toolchain.gni") - -_home_dir = getenv("HOME") -_clang_base_path = "$_home_dir/starboard-toolchains/x86_64-linux-gnu-clang-chromium-365097-f7e52fbd-8" -raspi_toolchain_path = "$_home_dir/raspi_tools/tools/arm-bcm2708/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin" +import("//starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni") clang_toolchain("host") { - clang_base_path = _clang_base_path + clang_base_path = raspi_clang_base_path } gcc_toolchain("target") { - cc = "$raspi_toolchain_path/arm-linux-gnueabihf-gcc" - cxx = "$raspi_toolchain_path/arm-linux-gnueabihf-g++" + cc = "$gcc_toolchain_cc" + cxx = "$gcc_toolchain_cxx" ld = cxx # We use whatever 'ar' resolves to in gyp. - ar = "ar" - strip = "$raspi_toolchain_path/arm-linux-gnueabihf-strip" + ar = "$gcc_toolchain_ar" + strip = "$gcc_toolchain_strip" toolchain_args = { is_clang = false
diff --git a/src/starboard/raspi/shared/BUILD.gn b/src/starboard/raspi/shared/BUILD.gn index 37c97e7..3828973 100644 --- a/src/starboard/raspi/shared/BUILD.gn +++ b/src/starboard/raspi/shared/BUILD.gn
@@ -18,6 +18,8 @@ } static_library("starboard_platform_sources") { + check_includes = false + sources = [ "//starboard/linux/shared/atomic_public.h", "//starboard/linux/shared/configuration_constants.cc", @@ -31,8 +33,6 @@ "//starboard/linux/shared/system_has_capability.cc", "//starboard/raspi/shared/application_dispmanx.cc", "//starboard/raspi/shared/audio_sink_type_dispatcher.cc", - "//starboard/raspi/shared/configuration.cc", - "//starboard/raspi/shared/configuration.h", "//starboard/raspi/shared/dispmanx_util.cc", "//starboard/raspi/shared/dispmanx_util.h", "//starboard/raspi/shared/graphics.cc", @@ -62,7 +62,6 @@ "//starboard/raspi/shared/open_max/video_decoder.h", "//starboard/raspi/shared/player_components_factory.cc", "//starboard/raspi/shared/system_get_device_type.cc", - "//starboard/raspi/shared/system_get_extensions.cc", "//starboard/raspi/shared/system_get_property.cc", "//starboard/raspi/shared/system_gles2.cc", "//starboard/raspi/shared/thread_create_priority.cc", @@ -376,7 +375,11 @@ sources += common_player_sources - configs += [ "//starboard/build/config:starboard_implementation" ] + configs += [ + "//starboard/build/config:pedantic_warnings", + "//starboard/build/config:starboard_implementation", + ] + configs -= [ "//starboard/build/config:no_pedantic_warnings" ] public_deps = [ ":starboard_base_symbolize", @@ -386,9 +389,7 @@ "//starboard/shared/starboard/media:media_util", "//starboard/shared/starboard/player/filter:filter_based_player_sources", ] - if (sb_is_evergreen_compatible) { - public_deps += [ "//starboard/elf_loader:evergreen_config" ] - } + if (sb_is_evergreen_compatible && !sb_evergreen_compatible_enable_lite) { public_deps += [ "//starboard/loader_app:pending_restart" ] } @@ -397,9 +398,17 @@ "//third_party/libevent", "//third_party/opus", ] + if (sb_evergreen_compatible_use_libunwind) { deps += [ "//third_party/llvm-project/libunwind:unwind_starboard" ] } + + if (sb_is_evergreen_compatible) { + public_deps += [ "//starboard/elf_loader:evergreen_config" ] + deps += [ "//third_party/crashpad/wrapper" ] + } else { + deps += [ "//third_party/crashpad/wrapper:wrapper_stub" ] + } } static_library("starboard_base_symbolize") {
diff --git a/src/starboard/raspi/shared/platform_configuration/BUILD.gn b/src/starboard/raspi/shared/platform_configuration/BUILD.gn index fbbab79..98d7b28 100644 --- a/src/starboard/raspi/shared/platform_configuration/BUILD.gn +++ b/src/starboard/raspi/shared/platform_configuration/BUILD.gn
@@ -125,29 +125,6 @@ "-std=gnu++14", "-Wno-literal-suffix", ] - - if (sb_pedantic_warnings) { - cflags += [ - "-Wall", - "-Wextra", - "-Wunreachable-code", - - # Raspi toolchain is based off an old version of gcc, which - # falsely flags some code. That same code does not warn with gcc 6.3. - # This decision should be revisited after raspi toolchain is upgraded. - "-Wno-maybe-uninitialized", - - #TODO: Renable -Werror after fixing all warnings. - #"-Werror", - "-Wno-expansion-to-defined", - "-Wno-implicit-fallthrough", - ] - } else { - cflags += [ - # Do not warn for implicit type conversions that may change a value. - "-Wno-conversion", - ] - } } config("platform_configuration") { @@ -193,3 +170,28 @@ "-fdata-sections", ] } + +config("pedantic_warnings") { + cflags = [ + "-Wall", + "-Wextra", + "-Wunreachable-code", + + # Raspi toolchain is based off an old version of gcc, which + # falsely flags some code. That same code does not warn with gcc 6.3. + # This decision should be revisited after raspi toolchain is upgraded. + "-Wno-maybe-uninitialized", + + #TODO: Renable -Werror after fixing all warnings. + #"-Werror", + "-Wno-expansion-to-defined", + "-Wno-implicit-fallthrough", + ] +} + +config("no_pedantic_warnings") { + cflags = [ + # Do not warn for implicit type conversions that may change a value. + "-Wno-conversion", + ] +}
diff --git a/src/starboard/raspi/shared/platform_configuration/configuration.gni b/src/starboard/raspi/shared/platform_configuration/configuration.gni index c96fc14..fd4a3ce 100644 --- a/src/starboard/raspi/shared/platform_configuration/configuration.gni +++ b/src/starboard/raspi/shared/platform_configuration/configuration.gni
@@ -14,7 +14,13 @@ import("//starboard/build/config/base_configuration.gni") +arm_float_abi = "hard" sb_pedantic_warnings = true sb_static_contents_output_data_dir = "$root_out_dir/content" +pedantic_warnings_config_path = + "//starboard/raspi/shared/platform_configuration:pedantic_warnings" +no_pedantic_warnings_config_path = + "//starboard/raspi/shared/platform_configuration:no_pedantic_warnings" + sabi_path = "//starboard/sabi/arm/hardfp/sabi-v$sb_api_version.json"
diff --git a/src/starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni b/src/starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni new file mode 100644 index 0000000..d773598 --- /dev/null +++ b/src/starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni
@@ -0,0 +1,26 @@ +# Copyright 2021 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. + +_home_dir = getenv("HOME") +_raspi_home_dir = getenv("RASPI_HOME") +assert(_raspi_home_dir != "", + "RasPi builds require the 'RASPI_HOME' environment variable to be set.") + +raspi_clang_base_path = "$_home_dir/starboard-toolchains/x86_64-linux-gnu-clang-chromium-365097-f7e52fbd-8" +_raspi_toolchain_path = "$_raspi_home_dir/tools/arm-bcm2708/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin" + +gcc_toolchain_ar = "ar" +gcc_toolchain_cc = "$_raspi_toolchain_path/arm-linux-gnueabihf-gcc" +gcc_toolchain_cxx = "$_raspi_toolchain_path/arm-linux-gnueabihf-g++" +gcc_toolchain_strip = "$_raspi_toolchain_path/arm-linux-gnueabihf-strip"
diff --git a/src/starboard/sabi/sabi.py b/src/starboard/sabi/sabi.py index a49ae2e..b7eab07 100644 --- a/src/starboard/sabi/sabi.py +++ b/src/starboard/sabi/sabi.py
@@ -11,6 +11,6 @@ # 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. -"""Source of truth of the default/experimental Starboard API version.""" +"""Source of truth of the default Starboard API version.""" -SB_API_VERSION = 14 +SB_API_VERSION = 13
diff --git a/src/starboard/shared/starboard/media/mime_type.cc b/src/starboard/shared/starboard/media/mime_type.cc index 0008844..035de69 100644 --- a/src/starboard/shared/starboard/media/mime_type.cc +++ b/src/starboard/shared/starboard/media/mime_type.cc
@@ -14,8 +14,12 @@ #include "starboard/shared/starboard/media/mime_type.h" +#include <algorithm> +#include <iosfwd> #include <locale> -#include <sstream> +#include <numeric> +#include <string> +#include <vector> #include "starboard/common/log.h" #include "starboard/common/string.h" @@ -43,6 +47,11 @@ if (!buffer.fail() && buffer.rdbuf()->in_avail() == 0) { return MimeType::kParamTypeFloat; } + + if (value == "true" || value == "false") { + return MimeType::kParamTypeBoolean; + } + return MimeType::kParamTypeString; } @@ -85,6 +94,22 @@ return result; } +const char* ParamTypeToString(MimeType::ParamType param_type) { + switch (param_type) { + case MimeType::kParamTypeInteger: + return "Integer"; + case MimeType::kParamTypeFloat: + return "Float"; + case MimeType::kParamTypeString: + return "String"; + case MimeType::kParamTypeBoolean: + return "Boolean"; + default: + SB_NOTREACHED(); + return "Unknown"; + } +} + } // namespace const int MimeType::kInvalidParamIndex = -1; @@ -117,8 +142,11 @@ for (Strings::iterator iter = components.begin(); iter != components.end(); ++iter) { std::vector<std::string> name_and_value = SplitAndTrim(*iter, '='); + // The parameter must be on the format 'name=value' and neither |name| nor + // |value| can be empty. |value| must also not contain '|'. if (name_and_value.size() != 2 || name_and_value[0].empty() || - name_and_value[1].empty()) { + name_and_value[1].empty() || + name_and_value[1].find('|') != std::string::npos) { return; } Param param; @@ -215,6 +243,17 @@ return params_[index].value; } +bool MimeType::GetParamBoolValue(int index) const { + SB_DCHECK(is_valid()); + SB_DCHECK(index < GetParamCount()); + + if (GetParamType(index) != kParamTypeBoolean) { + return false; + } + + return params_[index].value == "true"; +} + int MimeType::GetParamIntValue(const char* name, int default_value) const { int index = GetParamIndexByName(name); if (index != kInvalidParamIndex) { @@ -242,6 +281,60 @@ return default_value; } +bool MimeType::GetParamBoolValue(const char* name, bool default_value) const { + int index = GetParamIndexByName(name); + if (index != kInvalidParamIndex) { + return GetParamBoolValue(index); + } + return default_value; +} + +bool MimeType::RegisterBoolParameter(const char* name) { + return RegisterParameter(name, kParamTypeBoolean); +} + +bool MimeType::RegisterStringParameter(const char* name, + const std::string& pattern /* = "" */) { + if (!RegisterParameter(name, kParamTypeString)) { + return false; + } + + int param_index = GetParamIndexByName(name); + if (param_index == kInvalidParamIndex || pattern.empty()) { + return true; + } + + // Compare the parameter value with the provided pattern. + const std::string& param_value = GetParamStringValue(param_index); + bool matches = false; + size_t match_start = 0; + while (!matches) { + match_start = pattern.find(param_value, match_start); + if (match_start == std::string::npos) { + break; + } + + size_t match_end = match_start + param_value.length(); + matches = + // Matches if the match is at the start of the string or + // if the preceding character is the divider _and_ + (match_start <= 0 || pattern[match_start - 1] == '|') && + // if the end of the match is the end of the pattern or + // if the succeeding character is the divider. + (match_end >= pattern.length() || pattern[match_end] == '|'); + match_start = match_end + 1; + } + + if (matches) { + return true; + } + + SB_LOG(INFO) << "Extended Parameter '" << name << "=" << param_value + << "' does not match the supplied pattern: '" << pattern << "'"; + is_valid_ = false; + return false; +} + int MimeType::GetParamIndexByName(const char* name) const { for (size_t i = 0; i < params_.size(); ++i) { if (SbStringCompareNoCase(params_[i].name.c_str(), name) == 0) { @@ -251,6 +344,36 @@ return kInvalidParamIndex; } +bool MimeType::RegisterParameter(const char* name, ParamType param_type) { + if (!is_valid()) { + return false; + } + + int index = GetParamIndexByName(name); + if (index == kInvalidParamIndex) { + return true; + } + + const std::string& param_value = GetParamStringValue(index); + ParamType parsed_type = GetParamType(index); + + // Check that the parameter can be returned as the requested type. + // Allowed conversions: + // Any Type -> String, Int -> Float + bool convertible = + param_type == parsed_type || param_type == kParamTypeString || + (param_type == kParamTypeFloat && parsed_type == kParamTypeInteger); + if (!convertible) { + SB_LOG(INFO) << "Extended Parameter '" << name << "=" << param_value + << "' can't be converted to " << ParamTypeToString(param_type); + is_valid_ = false; + return false; + } + + // All validations succeeded. + return true; +} + } // namespace media } // namespace starboard } // namespace shared
diff --git a/src/starboard/shared/starboard/media/mime_type.h b/src/starboard/shared/starboard/media/mime_type.h index 7c3d619..a05a301 100644 --- a/src/starboard/shared/starboard/media/mime_type.h +++ b/src/starboard/shared/starboard/media/mime_type.h
@@ -50,6 +50,7 @@ kParamTypeInteger, kParamTypeFloat, kParamTypeString, + kParamTypeBoolean, }; static const int kInvalidParamIndex; @@ -71,12 +72,30 @@ int GetParamIntValue(int index) const; float GetParamFloatValue(int index) const; const std::string& GetParamStringValue(int index) const; + bool GetParamBoolValue(int index) const; int GetParamIntValue(const char* name, int default_value) const; float GetParamFloatValue(const char* name, float default_value) const; const std::string& GetParamStringValue( const char* name, const std::string& default_value) const; + bool GetParamBoolValue(const char* name, bool default_value) const; + + // Pre-register a mime parameter of type boolean. + // Returns true if the mime type is valid and the value passes validation. + // If the parameter validation fails this MimeType will be marked invalid. + // NOTE: The function returns true for missing parameters. + bool RegisterBoolParameter(const char* name); + + // Pre-register a mime parameter of type string. + // Returns true if the mime type is valid and the value passes validation. + // Allows passing a pattern on the format "value_1|...|value_n" + // where the parameter value must match one of the values in the pattern in + // order to be considered valid. + // If the parameter validation fails this MimeType will be marked invalid. + // NOTE: The function returns true for missing parameters. + bool RegisterStringParameter(const char* name, + const std::string& pattern = ""); private: struct Param { @@ -90,6 +109,7 @@ typedef std::vector<Param> Params; int GetParamIndexByName(const char* name) const; + bool RegisterParameter(const char* name, ParamType type); const std::string raw_content_type_; bool is_valid_;
diff --git a/src/starboard/shared/starboard/media/mime_type_test.cc b/src/starboard/shared/starboard/media/mime_type_test.cc index 54dfdfc..854444d 100644 --- a/src/starboard/shared/starboard/media/mime_type_test.cc +++ b/src/starboard/shared/starboard/media/mime_type_test.cc
@@ -41,7 +41,7 @@ } // Valid mime type must have a type/subtype without space in between. -TEST(MimeTypeTest, InvalidType) { +TEST(MimeTypeTest, InvalidMimeType) { { MimeType mime_type("video"); EXPECT_FALSE(mime_type.is_valid()); @@ -83,6 +83,21 @@ "param1=\" value1 \";codecs=\"abc, def\""); EXPECT_FALSE(mime_type.is_valid()); } + { + // Parameter name must not be empty. + MimeType mime_type("video/mp4; =value"); + EXPECT_FALSE(mime_type.is_valid()); + } + { + // Parameter value must not be empty. + MimeType mime_type("video/mp4; name="); + EXPECT_FALSE(mime_type.is_valid()); + } + { + // No '|' allowed. + MimeType mime_type("video/mp4; name=ab|c"); + EXPECT_FALSE(mime_type.is_valid()); + } } TEST(MimeTypeTest, ValidContentTypeWithTypeAndSubtypeOnly) { @@ -165,10 +180,13 @@ TEST(MimeTypeTest, GetParamType) { { - MimeType mime_type("video/mp4; name0=123; name1=1.2; name2=xyz"); + MimeType mime_type( + "video/mp4; name0=123; name1=1.2; name2=xyz; name3=true; name4=false"); EXPECT_EQ(MimeType::kParamTypeInteger, mime_type.GetParamType(0)); EXPECT_EQ(MimeType::kParamTypeFloat, mime_type.GetParamType(1)); EXPECT_EQ(MimeType::kParamTypeString, mime_type.GetParamType(2)); + EXPECT_EQ(MimeType::kParamTypeBoolean, mime_type.GetParamType(3)); + EXPECT_EQ(MimeType::kParamTypeBoolean, mime_type.GetParamType(4)); } { @@ -230,6 +248,12 @@ } } +TEST(MimeTypeTest, GetParamBoolValueWithIndex) { + MimeType mime_type("video/mp4; name0=true; name1=false"); + EXPECT_TRUE(mime_type.GetParamBoolValue(0)); + EXPECT_FALSE(mime_type.GetParamBoolValue(1)); +} + TEST(MimeTypeTest, GetParamValueInInvalidType) { MimeType mime_type("video/mp4; name0=abc; name1=123.4"); EXPECT_FLOAT_EQ(0, mime_type.GetParamIntValue(0)); @@ -280,6 +304,110 @@ } } +TEST(MimeTypeTest, GetParamBooleanValueWithName) { + MimeType mime_type("video/mp4; name0=true; name1=false; name2=trues"); + EXPECT_TRUE(mime_type.GetParamBoolValue("name0", false)); + EXPECT_FALSE(mime_type.GetParamBoolValue("name1", true)); + // Default value + EXPECT_FALSE(mime_type.GetParamBoolValue("name2", false)); +} + +TEST(MimeTypeTest, ReadParamOfDifferentTypes) { + // Ensure that the parameter type is enforced correctly. + MimeType mime_type("video/mp4; string=value; int=1; float=1.0; bool=true"); + ASSERT_TRUE(mime_type.is_valid()); + // Read params as their original types. + EXPECT_EQ(mime_type.GetParamStringValue("string", ""), "value"); + EXPECT_EQ(mime_type.GetParamIntValue("int", 0), 1); + EXPECT_EQ(mime_type.GetParamFloatValue("float", 0.0), 1.0); + EXPECT_EQ(mime_type.GetParamBoolValue("bool", false), true); + // All param types can be read as strings. + EXPECT_EQ(mime_type.GetParamStringValue("int", ""), "1"); + EXPECT_EQ(mime_type.GetParamStringValue("float", ""), "1.0"); + EXPECT_EQ(mime_type.GetParamStringValue("bool", ""), "true"); + // Int can also be read as float. + EXPECT_EQ(mime_type.GetParamFloatValue("int", 0.0), 1.0); + + // Test failing validation for ints, + EXPECT_EQ(mime_type.GetParamIntValue("string", 0), 0); + EXPECT_EQ(mime_type.GetParamIntValue("float", 0), 0); + EXPECT_EQ(mime_type.GetParamIntValue("bool", 0), 0); + + // floats, + EXPECT_EQ(mime_type.GetParamFloatValue("string", 0.0), 0.0); + EXPECT_EQ(mime_type.GetParamFloatValue("bool", 0.0), 0.0); + + // and bools. + EXPECT_FALSE(mime_type.GetParamBoolValue("string", false)); + EXPECT_FALSE(mime_type.GetParamBoolValue("int", false)); + EXPECT_FALSE(mime_type.GetParamBoolValue("float", false)); +} + +TEST(MimeTypeTest, RegisterAndValidateParamsWithPatterns) { + MimeType mime_type("video/mp4; string=yes"); + EXPECT_TRUE(mime_type.RegisterStringParameter("string", "yes")); + EXPECT_TRUE(mime_type.RegisterStringParameter("string", "yes|no")); + EXPECT_TRUE(mime_type.RegisterStringParameter("string", "no|yes|no")); + EXPECT_TRUE(mime_type.RegisterStringParameter("string", "no|no|yes")); + EXPECT_TRUE(mime_type.RegisterStringParameter("string", "noyes|yes")); + EXPECT_TRUE(mime_type.is_valid()); +} + +TEST(MimeTypeTest, RegisterAndValidateParamsWithShortPatterns) { + MimeType mime_type("video/mp4; string=y"); + EXPECT_TRUE(mime_type.RegisterStringParameter("string", "y")); + EXPECT_TRUE(mime_type.RegisterStringParameter("string", "y|n")); + EXPECT_TRUE(mime_type.RegisterStringParameter("string", "n|y")); + EXPECT_TRUE(mime_type.is_valid()); +} + +TEST(MimeTypeTest, RegisterAndValidateParamsWithPartialMatches) { + { + MimeType mime_type("video/mp4; string=yes"); + EXPECT_FALSE(mime_type.RegisterStringParameter("string", "yesno|no")); + EXPECT_FALSE(mime_type.is_valid()); + } + { + MimeType mime_type("video/mp4; string=yes"); + EXPECT_FALSE(mime_type.RegisterStringParameter("string", "noyes|no")); + EXPECT_FALSE(mime_type.is_valid()); + } + { + MimeType mime_type("video/mp4; string=yes"); + EXPECT_FALSE(mime_type.RegisterStringParameter("string", "no|yesno")); + EXPECT_FALSE(mime_type.is_valid()); + } + { + MimeType mime_type("video/mp4; string=yes"); + EXPECT_FALSE(mime_type.RegisterStringParameter("string", "no|noyes")); + EXPECT_FALSE(mime_type.is_valid()); + } +} + +TEST(MimeTypeTest, MissingParamReturnsTrueOnRegistration) { + MimeType mime_type("video/mp4"); + EXPECT_TRUE(mime_type.RegisterStringParameter("string")); + EXPECT_TRUE(mime_type.is_valid()); + EXPECT_EQ(mime_type.GetParamStringValue("string", "default"), "default"); +} + +TEST(MimeTypeTest, RegisterAndValidateParamsWithEmptyishPattern) { + { + MimeType mime_type("video/mp4; string=yes"); + EXPECT_FALSE(mime_type.RegisterStringParameter("string", "|")); + } + { + MimeType mime_type("video/mp4; string=yes"); + EXPECT_FALSE(mime_type.RegisterStringParameter("string", "||")); + } +} + +TEST(MimeTypeTest, CannotRegisterParamWithInvalidMimeType) { + MimeType mime_type("video/mp4; string="); + ASSERT_FALSE(mime_type.is_valid()); + EXPECT_FALSE(mime_type.RegisterStringParameter("string")); +} + } // namespace } // namespace media } // namespace starboard
diff --git a/src/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc b/src/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc index 5127ae4..e1ca12b 100644 --- a/src/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc +++ b/src/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc
@@ -362,8 +362,8 @@ return test_params; } - vector<const char*> supported_files = GetSupportedAudioTestFiles( - kExcludeHeaac, 6, "audiopassthrough=\"false\""); + vector<const char*> supported_files = + GetSupportedAudioTestFiles(kExcludeHeaac, 6, "audiopassthrough=false"); // Generate test cases. For example, we have |supported_files| [A, B, C]. // Add tests A->A, A->B, A->C, B->A, B->B, B->C, C->A, C->B and C->C.
diff --git a/src/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc b/src/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc index 00933f3..eee3730 100644 --- a/src/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc +++ b/src/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
@@ -637,7 +637,7 @@ AudioDecoderTest, Combine(ValuesIn(GetSupportedAudioTestFiles(kIncludeHeaac, 6, - "audiopassthrough=\"false\"")), + "audiopassthrough=false")), Bool())); } // namespace
diff --git a/src/starboard/shared/starboard/player/filter/testing/player_components_test.cc b/src/starboard/shared/starboard/player/filter/testing/player_components_test.cc index fe7932f..afa4fad 100644 --- a/src/starboard/shared/starboard/player/filter/testing/player_components_test.cc +++ b/src/starboard/shared/starboard/player/filter/testing/player_components_test.cc
@@ -38,6 +38,8 @@ namespace { using ::starboard::testing::FakeGraphicsContextProvider; +using std::placeholders::_1; +using std::placeholders::_2; using std::string; using std::unique_ptr; using std::vector; @@ -121,7 +123,7 @@ if (GetAudioRenderer()) { GetAudioRenderer()->Initialize( - std::bind(&PlayerComponentsTest::OnError, this), + std::bind(&PlayerComponentsTest::OnError, this, _1, _2), std::bind(&PlayerComponentsTest::OnPrerolled, this, kSbMediaTypeAudio), std::bind(&PlayerComponentsTest::OnEnded, this, kSbMediaTypeAudio)); @@ -129,7 +131,7 @@ SetPlaybackRate(playback_rate_); if (GetVideoRenderer()) { GetVideoRenderer()->Initialize( - std::bind(&PlayerComponentsTest::OnError, this), + std::bind(&PlayerComponentsTest::OnError, this, _1, _2), std::bind(&PlayerComponentsTest::OnPrerolled, this, kSbMediaTypeVideo), std::bind(&PlayerComponentsTest::OnEnded, this, kSbMediaTypeVideo)); @@ -401,7 +403,11 @@ // 1s in the tests. const SbTime kMaxWriteAheadDuration = kSbTimeSecond; - void OnError() { has_error_ = true; } + void OnError(SbPlayerError error, const std::string& error_message) { + has_error_ = true; + SB_LOG(ERROR) << "Caught renderer error (" << error + << "): " << error_message; + } void OnPrerolled(SbMediaType media_type) { if (media_type == kSbMediaTypeAudio) { audio_prerolled_ = true;
diff --git a/src/starboard/stub/platform_configuration/BUILD.gn b/src/starboard/stub/platform_configuration/BUILD.gn index ace2a65..a5bdb8f 100644 --- a/src/starboard/stub/platform_configuration/BUILD.gn +++ b/src/starboard/stub/platform_configuration/BUILD.gn
@@ -85,30 +85,32 @@ # Defined to get format macro constants from <inttypes.h>. defines = [ "__STDC_FORMAT_MACROS" ] +} - if (sb_pedantic_warnings) { - cflags += [ - "-Wall", - "-Wextra", - "-Wunreachable-code", - ] - } else { - cflags += [ - # 'this' pointer cannot be NULL...pointer may be assumed - # to always convert to true. - "-Wno-undefined-bool-conversion", +config("pedantic_warnings") { + cflags = [ + "-Wall", + "-Wextra", + "-Wunreachable-code", + ] +} - # Skia doesn't use overrides. - "-Wno-inconsistent-missing-override", +config("no_pedantic_warnings") { + cflags = [ + # 'this' pointer cannot be NULL...pointer may be assumed + # to always convert to true. + "-Wno-undefined-bool-conversion", - # Do not warn for implicit type conversions that may change a value. - "-Wno-conversion", + # Skia doesn't use overrides. + "-Wno-inconsistent-missing-override", - # shifting a negative signed value is undefined - "-Wno-shift-negative-value", + # Do not warn for implicit type conversions that may change a value. + "-Wno-conversion", - # Width of bit-field exceeds width of its type- value will be truncated - "-Wno-bitfield-width", - ] - } + # shifting a negative signed value is undefined + "-Wno-shift-negative-value", + + # Width of bit-field exceeds width of its type- value will be truncated + "-Wno-bitfield-width", + ] }
diff --git a/src/starboard/stub/platform_configuration/configuration.gni b/src/starboard/stub/platform_configuration/configuration.gni index 61a778f..7386286 100644 --- a/src/starboard/stub/platform_configuration/configuration.gni +++ b/src/starboard/stub/platform_configuration/configuration.gni
@@ -23,3 +23,8 @@ sabi_path = "//starboard/sabi/x64/sysv/sabi-v$sb_api_version.json" install_target_path = "//starboard/build/install/install_target.gni" + +pedantic_warnings_config_path = + "//starboard/stub/platform_configuration:pedantic_warnings" +no_pedantic_warnings_config_path = + "//starboard/stub/platform_configuration:no_pedantic_warnings"
diff --git a/src/third_party/crashpad/handler/handler.gyp b/src/third_party/crashpad/handler/handler.gyp index 1275a4d..d862d70 100644 --- a/src/third_party/crashpad/handler/handler.gyp +++ b/src/third_party/crashpad/handler/handler.gyp
@@ -102,6 +102,7 @@ 'ldflags': [ '-Wl,--as-needed', '-Wl,-gc-sections', + '-Wl,-z,noexecstack', ], }], ['OS=="win"', {
diff --git a/src/third_party/web_platform_tests/tools/wpt/requirements.txt b/src/third_party/web_platform_tests/tools/wpt/requirements.txt index 7369cb8..a8ed785 100644 --- a/src/third_party/web_platform_tests/tools/wpt/requirements.txt +++ b/src/third_party/web_platform_tests/tools/wpt/requirements.txt
@@ -1 +1 @@ -requests==2.14.2 +requests==2.26.0