Import Cobalt 23.lts.5.1031871
diff --git a/cobalt/build/build.id b/cobalt/build/build.id index 0a3476e..3d15dd7 100644 --- a/cobalt/build/build.id +++ b/cobalt/build/build.id
@@ -1 +1 @@ -311259 \ No newline at end of file +1031871 \ No newline at end of file
diff --git a/cobalt/build/get_build_id.py b/cobalt/build/get_build_id.py index 14f4f0d..9c19e4c 100755 --- a/cobalt/build/get_build_id.py +++ b/cobalt/build/get_build_id.py
@@ -15,20 +15,65 @@ """Prints out the Cobalt Build ID.""" import os +import re +import subprocess + from cobalt.build.build_number import GetOrGenerateNewBuildNumber +_FILE_DIR = os.path.dirname(__file__) +COMMIT_COUNT_BUILD_NUMBER_OFFSET = 1000000 + +# Matches numbers > 1000000. The pattern is basic so git log --grep is able to +# interpret it. +GIT_BUILD_NUMBER_PATTERN = r'[1-9]' + r'[0-9]' * 6 + r'[0-9]*' +BUILD_NUMBER_TAG_PATTERN = r'^BUILD_NUMBER={}$' + +# git log --grep can't handle capture groups. +BUILD_NUBER_PATTERN_WITH_CAPTURE = f'({GIT_BUILD_NUMBER_PATTERN})' + + +def get_build_number_from_commits(): + full_pattern = BUILD_NUMBER_TAG_PATTERN.format(GIT_BUILD_NUMBER_PATTERN) + output = subprocess.check_output( + ['git', 'log', '--grep', full_pattern, '-1', '--pretty=%b'], + cwd=_FILE_DIR).decode() + + full_pattern_with_capture = re.compile( + BUILD_NUMBER_TAG_PATTERN.format(BUILD_NUBER_PATTERN_WITH_CAPTURE), + flags=re.MULTILINE) + match = full_pattern_with_capture.search(output) + return match.group(1) if match else None + + +def get_build_number_from_server(): + # Note $BUILD_ID_SERVER_URL will always be set in CI. + build_id_server_url = os.environ.get('BUILD_ID_SERVER_URL') + if not build_id_server_url: + return None + + build_num = GetOrGenerateNewBuildNumber(version_server=build_id_server_url) + if build_num == 0: + raise ValueError('The build number received was zero.') + return build_num + + +def get_build_number_from_commit_count(): + output = subprocess.check_output(['git', 'rev-list', '--count', 'HEAD'], + cwd=_FILE_DIR) + build_number = int(output.strip().decode('utf-8')) + return build_number + COMMIT_COUNT_BUILD_NUMBER_OFFSET + def main(): - # Note $BUILD_ID_SERVER_URL will always be set in CI. - build_id_server_url = os.environ.get('BUILD_ID_SERVER_URL') - if build_id_server_url: - build_num = GetOrGenerateNewBuildNumber(version_server=build_id_server_url) - if build_num == 0: - raise ValueError('The build number received was zero.') - print(build_num) - else: - # No need to generate a build id for local builds. - print('0') + build_number = get_build_number_from_commits() + + if not build_number: + build_number = get_build_number_from_server() + + if not build_number: + build_number = get_build_number_from_commit_count() + + print(build_number) if __name__ == '__main__':
diff --git a/cobalt/content/fonts/config/common/fonts.xml b/cobalt/content/fonts/config/common/fonts.xml index abba022..2ddeaec 100644 --- a/cobalt/content/fonts/config/common/fonts.xml +++ b/cobalt/content/fonts/config/common/fonts.xml
@@ -184,7 +184,7 @@ <font weight="400" style="normal">NotoSansArmenian-Regular.woff2</font> <font weight="700" style="normal">NotoSansArmenian-Bold.woff2</font> </family> - <family lang="und-Geor,und-Geok" pages="0,5,16,45,254"> + <family lang="und-Geor,und-Geok" pages="0,5,16,28,45,254"> <font weight="400" style="normal">NotoSansGeorgian-Regular.woff2</font> <font weight="700" style="normal">NotoSansGeorgian-Bold.woff2</font> </family>
diff --git a/cobalt/content/fonts/font_files/NotoSansGeorgian-Bold.woff2 b/cobalt/content/fonts/font_files/NotoSansGeorgian-Bold.woff2 index a7f9336..03a41c8 100644 --- a/cobalt/content/fonts/font_files/NotoSansGeorgian-Bold.woff2 +++ b/cobalt/content/fonts/font_files/NotoSansGeorgian-Bold.woff2 Binary files differ
diff --git a/cobalt/content/fonts/font_files/NotoSansGeorgian-Regular.woff2 b/cobalt/content/fonts/font_files/NotoSansGeorgian-Regular.woff2 index fb2fe49..68e26b4 100644 --- a/cobalt/content/fonts/font_files/NotoSansGeorgian-Regular.woff2 +++ b/cobalt/content/fonts/font_files/NotoSansGeorgian-Regular.woff2 Binary files differ
diff --git a/cobalt/dom/serialized_algorithm_runner.h b/cobalt/dom/serialized_algorithm_runner.h index db625ad..358f7ef 100644 --- a/cobalt/dom/serialized_algorithm_runner.h +++ b/cobalt/dom/serialized_algorithm_runner.h
@@ -15,6 +15,7 @@ #ifndef COBALT_DOM_SERIALIZED_ALGORITHM_RUNNER_H_ #define COBALT_DOM_SERIALIZED_ALGORITHM_RUNNER_H_ +#include <algorithm> #include <memory> #include <utility> @@ -26,6 +27,7 @@ #include "base/single_thread_task_runner.h" #include "base/trace_event/trace_event.h" #include "starboard/common/mutex.h" +#include "starboard/time.h" namespace cobalt { namespace dom { @@ -84,11 +86,31 @@ const starboard::Mutex& mutex) : synchronization_required_(synchronization_required), mutex_(mutex) { if (synchronization_required_) { - mutex_.Acquire(); + // Crash if we are trying to re-acquire again on the same thread. + CHECK_NE(acquired_thread_id_, SbThreadGetId()); + + SbTime start = SbTimeGetMonotonicNow(); + SbTime wait_interval = kSbTimeMillisecond; + constexpr SbTime kMaxWaitInterval = kSbTimeMillisecond * 16; + + for (;;) { + if (mutex_.AcquireTry()) { + break; + } + SbThreadSleep(wait_interval); + // Double the wait interval upon every failure, but cap it at + // kMaxWaitInterval. + wait_interval = std::min(wait_interval * 2, kMaxWaitInterval); + // Crash if we've been waiting for too long. + CHECK_LT(SbTimeGetMonotonicNow() - start, kSbTimeSecond); + } + acquired_thread_id_ = SbThreadGetId(); } } ~ScopedLockWhenRequired() { if (synchronization_required_) { + CHECK_EQ(acquired_thread_id_, SbThreadGetId()); + acquired_thread_id_ = kSbThreadInvalidId; mutex_.Release(); } } @@ -96,6 +118,7 @@ private: const bool synchronization_required_; const starboard::Mutex& mutex_; + SbThreadId acquired_thread_id_ = kSbThreadInvalidId; }; Handle(bool synchronization_required,
diff --git a/cobalt/h5vcc/h5vcc_runtime.cc b/cobalt/h5vcc/h5vcc_runtime.cc index faf56ce..15f682b 100644 --- a/cobalt/h5vcc/h5vcc_runtime.cc +++ b/cobalt/h5vcc/h5vcc_runtime.cc
@@ -91,7 +91,7 @@ message_loop_->task_runner()->PostTask( FROM_HERE, base::Bind(&H5vccRuntime::OnDeepLinkEvent, base::Unretained(this), - base::Passed(&deep_link_event))); + base::Passed(std::move(deep_link_event)))); return; } OnDeepLinkEvent(std::move(deep_link_event));
diff --git a/cobalt/layout/topmost_event_target.cc b/cobalt/layout/topmost_event_target.cc index d7ce92e..3981de4 100644 --- a/cobalt/layout/topmost_event_target.cc +++ b/cobalt/layout/topmost_event_target.cc
@@ -57,7 +57,8 @@ document->DoSynchronousLayout(); html_element_ = document->html(); - ConsiderElement(html_element_, coordinate); + bool consider_only_fixed_elements = false; + ConsiderElement(html_element_, coordinate, consider_only_fixed_elements); box_ = NULL; render_sequence_.clear(); scoped_refptr<dom::HTMLElement> topmost_element; @@ -178,8 +179,8 @@ } }; -bool ShouldConsiderElementAndChildren(dom::Element* element, - math::Vector2dF* coordinate) { +bool CanTargetElementAndChildren(dom::Element* element, + math::Vector2dF* coordinate) { LayoutBoxes* layout_boxes = GetLayoutBoxesIfNotEmpty(element); const Boxes boxes = layout_boxes->boxes(); const Box* box = boxes.front(); @@ -434,17 +435,43 @@ return complete_matrix; } +bool LayoutBoxesAreFixed(LayoutBoxes* layout_boxes) { + const Boxes boxes = layout_boxes->boxes(); + const Box* box = boxes.front(); + if (!box->computed_style()) { + return false; + } + + return box->computed_style()->position() == cssom::KeywordValue::GetFixed(); +} + +bool ShouldConsiderElementAndChildren(dom::Element* element, + math::Vector2dF* coordinate, + bool consider_only_fixed_elements) { + LayoutBoxes* layout_boxes = GetLayoutBoxesIfNotEmpty(element); + if (!layout_boxes) { + return false; + } + + bool is_fixed_element = LayoutBoxesAreFixed(layout_boxes); + if (consider_only_fixed_elements && !is_fixed_element) { + return false; + } + return CanTargetElementAndChildren(element, coordinate); +} + } // namespace void TopmostEventTarget::ConsiderElement(dom::Element* element, - const math::Vector2dF& coordinate) { + const math::Vector2dF& coordinate, + bool consider_only_fixed_elements) { if (!element) return; math::Vector2dF element_coordinate(coordinate); LayoutBoxes* layout_boxes = GetLayoutBoxesIfNotEmpty(element); - if (layout_boxes) { - if (!ShouldConsiderElementAndChildren(element, &element_coordinate)) { - return; - } + bool consider_element_and_children = ShouldConsiderElementAndChildren( + element, &element_coordinate, consider_only_fixed_elements); + + if (consider_element_and_children) { scoped_refptr<dom::HTMLElement> html_element = element->AsHTMLElement(); if (html_element && html_element->CanBeDesignatedByPointerIfDisplayed()) { ConsiderBoxes(html_element, layout_boxes, element_coordinate); @@ -453,7 +480,8 @@ for (dom::Element* child_element = element->first_element_child(); child_element; child_element = child_element->next_element_sibling()) { - ConsiderElement(child_element, element_coordinate); + ConsiderElement(child_element, element_coordinate, + !consider_element_and_children); } }
diff --git a/cobalt/layout/topmost_event_target.h b/cobalt/layout/topmost_event_target.h index 1d1dd9b..2a8148a 100644 --- a/cobalt/layout/topmost_event_target.h +++ b/cobalt/layout/topmost_event_target.h
@@ -51,8 +51,8 @@ void CancelScrollsInParentNavItems( scoped_refptr<dom::HTMLElement> target_element); - void ConsiderElement(dom::Element* element, - const math::Vector2dF& coordinate); + void ConsiderElement(dom::Element* element, const math::Vector2dF& coordinate, + bool consider_only_fixed_elements); void ConsiderBoxes(const scoped_refptr<dom::HTMLElement>& html_element, LayoutBoxes* layout_boxes, const math::Vector2dF& coordinate);
diff --git a/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt b/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt index 686eabe..95fe188 100644 --- a/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt +++ b/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt
@@ -1,34 +1,44 @@ # Service Worker API tests +service-worker/activation-after-registration.https.html, PASS +service-worker/activate-event-after-install-state-change.https.html, PASS service-worker/clients-matchall-on-evaluation.https.html, PASS service-worker/fetch-event-add-async.https.html, PASS +service-worker/import-scripts-cross-origin.https.html, PASS +service-worker/import-scripts-resource-map.https.html, PASS +service-worker/import-scripts-updated-flag.https.html, PASS +service-worker/multiple-update.https.html, PASS service-worker/register-default-scope.https.html, PASS service-worker/registration-basic.https.html, PASS service-worker/registration-security-error.https.html, PASS +service-worker/registration-service-worker-attributes.https.html, PASS service-worker/registration-script-url.https.html, PASS service-worker/rejections.https.html, PASS service-worker/serviceworkerobject-scripturl.https.html, PASS service-worker/service-worker-csp-default.https.html, PASS service-worker/service-worker-csp-connect.https.html, PASS -service-worker/service-worker-header.https.html, PASS service-worker/service-worker-csp-script.https.html, PASS +service-worker/service-worker-header.https.html, PASS service-worker/Service-Worker-Allowed-header.https.html, PASS service-worker/skip-waiting-without-client.https.html, PASS +service-worker/state.https.html, PASS +service-worker/synced-state.https.html, PASS service-worker/uncontrolled-page.https.html, PASS service-worker/unregister.https.html, PASS -service-worker/update-missing-import-scripts.https.html, PASS -service-worker/update-result.https.html, PASS +service-worker/update-no-cache-request-headers.https.html, PASS -# Tests pass with memory leakage issue. -service-worker/update-no-cache-request-headers.https.html, DISABLE +# b/274011216 flaky test +service-worker/update-result.https.html, DISABLE +# TODO(b/275914032): Flaky test. +service-worker/update-missing-import-scripts.https.html, DISABLE + +# b/275643772 MIME type check is flaky +service-worker/import-scripts-mime-types.https.html, DISABLE # b/234788479 Implement waiting for update worker state tasks in Install algorithm. -service-worker/activation-after-registration.https.html, DISABLE -service-worker/activate-event-after-install-state-change.https.html, DISABLE -service-worker/multiple-update.https.html, DISABLE +service-worker/import-scripts-redirect.https.html, DISABLE service-worker/register-wait-forever-in-install-worker.https.html, DISABLE -service-worker/state.https.html, DISABLE # "Module" type of dedicated worker is supported in Cobalt service-worker/dedicated-worker-service-worker-interception.https.html, DISABLE @@ -53,11 +63,6 @@ service-worker/fetch-event-is-reload-navigation-manual.https.html, DISABLE # b/265841607 Unhandled rejection with value: object "TypeError" -service-worker/import-scripts-cross-origin.https.html, DISABLE -service-worker/import-scripts-mime-types.https.html, DISABLE -service-worker/import-scripts-redirect.https.html, DISABLE -service-worker/import-scripts-resource-map.https.html, DISABLE -service-worker/import-scripts-updated-flag.https.html, DISABLE service-worker/same-site-cookies.https.html, DISABLE # b/265844662 @@ -87,12 +92,6 @@ service-worker/unregister-immediately-before-installed.https.html, DISABLE service-worker/update-not-allowed.https.html, DISABLE -# b/265981629 -service-worker/registration-service-worker-attributes.https.html, DISABLE - -# b/265983449 -service-worker/synced-state.https.html, DISABLE - # websocket is not supported in Cobalt service-worker/websocket-in-service-worker.https.html, DISABLE
diff --git a/cobalt/layout_tests/web_platform_tests.cc b/cobalt/layout_tests/web_platform_tests.cc index ac6e3fd..acca2ad 100644 --- a/cobalt/layout_tests/web_platform_tests.cc +++ b/cobalt/layout_tests/web_platform_tests.cc
@@ -213,33 +213,36 @@ web::CspDelegateFactory::GetInstance()->OverrideCreator( web::kCspEnforcementEnable, CspDelegatePermissive::Create); + + std::unique_ptr<browser::UserAgentPlatformInfo> platform_info( + new browser::UserAgentPlatformInfo()); + std::unique_ptr<browser::ServiceWorkerRegistry> service_worker_registry( + new browser::ServiceWorkerRegistry(&web_settings, &network_module, + platform_info.get(), url)); + + browser::WebModule::Options web_module_options; // Use test runner mode to allow the content itself to dictate when it is // ready for layout should be performed. See cobalt/dom/test_runner.h. - browser::WebModule::Options web_module_options; web_module_options.layout_trigger = layout::LayoutManager::kTestRunnerMode; + // We assume that we won't suspend/resume while running the tests, and so // we take advantage of the convenience of inline script tags. web_module_options.enable_inline_script_warnings = false; web_module_options.web_options.web_settings = &web_settings; web_module_options.web_options.network_module = &network_module; + web_module_options.web_options.service_worker_jobs = + service_worker_registry->service_worker_jobs(); + web_module_options.web_options.platform_info = platform_info.get(); // Prepare a slot for our results to be placed when ready. base::Optional<browser::WebModule::LayoutResults> results; base::RunLoop run_loop; - // Create the WebModule and wait for a layout to occur. - browser::WebModule web_module("RunWebPlatformTest"); - - // Create Service Worker Registry - browser::ServiceWorkerRegistry* service_worker_registry = - new browser::ServiceWorkerRegistry(&web_settings, &network_module, - new browser::UserAgentPlatformInfo(), - url); - web_module_options.web_options.service_worker_jobs = - service_worker_registry->service_worker_jobs(); - - web_module.Run( + // Run the WebModule and wait for a layout to occur. + std::unique_ptr<browser::WebModule> web_module( + new browser::WebModule("RunWebPlatformTest")); + web_module->Run( url, base::kApplicationStateStarted, nullptr /* scroll_engine */, base::Bind(&WebModuleOnRenderTreeProducedCallback, &results), base::Bind(&WebModuleErrorCallback, &run_loop, @@ -249,12 +252,16 @@ can_play_type_handler.get(), media_module.get(), kDefaultViewportSize, &resource_provider, 60.0f, web_module_options); run_loop.Run(); + const std::string extract_results = "document.getElementById(\"__testharness__results__\").textContent;"; std::string output; - web_module.ExecuteJavascript(extract_results, - base::SourceLocation(__FILE__, __LINE__, 1), - &output, got_results); + web_module->ExecuteJavascript(extract_results, + base::SourceLocation(__FILE__, __LINE__, 1), + &output, got_results); + // Ensure that the WebModule stops before stopping the ServiceWorkerRegistry. + web_module.reset(); + service_worker_registry.reset(); return output; }
diff --git a/cobalt/loader/fetcher_cache.cc b/cobalt/loader/fetcher_cache.cc index 8d6b707..1e6adb5 100644 --- a/cobalt/loader/fetcher_cache.cc +++ b/cobalt/loader/fetcher_cache.cc
@@ -33,7 +33,7 @@ const std::string& url, const scoped_refptr<net::HttpResponseHeaders>& headers, const Origin& last_url_origin, bool did_fail_from_transient_error, - std::string* data)> + std::string data)> SuccessCallback; CachedFetcherHandler(const std::string& url, Fetcher::Handler* handler, @@ -52,6 +52,7 @@ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(!wrapping_fetcher_); DCHECK(wrapping_fetcher); + wrapping_fetcher_ = wrapping_fetcher; } @@ -63,6 +64,7 @@ // TODO: Respect HttpResponseHeaders::GetMaxAgeValue(). DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(wrapping_fetcher_); + auto response = handler_->OnResponseStarted(wrapping_fetcher_, headers); if (response == kLoadResponseContinue && headers) { headers_ = headers; @@ -77,6 +79,7 @@ void OnReceived(Fetcher*, const char* data, size_t size) override { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(wrapping_fetcher_); + data_.insert(data_.end(), data, data + size); handler_->OnReceived(wrapping_fetcher_, data, size); } @@ -84,6 +87,7 @@ void OnReceivedPassed(Fetcher*, std::unique_ptr<std::string> data) override { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(wrapping_fetcher_); + data_.insert(data_.end(), data->begin(), data->end()); handler_->OnReceivedPassed(wrapping_fetcher_, std::move(data)); } @@ -91,15 +95,17 @@ void OnDone(Fetcher*) override { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(wrapping_fetcher_); + handler_->OnDone(wrapping_fetcher_); on_success_callback_.Run( url_, headers_, wrapping_fetcher_->last_url_origin(), - wrapping_fetcher_->did_fail_from_transient_error(), &data_); + wrapping_fetcher_->did_fail_from_transient_error(), std::move(data_)); } void OnError(Fetcher*, const std::string& error) override { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(wrapping_fetcher_); + handler_->OnError(wrapping_fetcher_, error); } @@ -184,13 +190,12 @@ public: CacheEntry(const scoped_refptr<net::HttpResponseHeaders>& headers, const Origin& last_url_origin, bool did_fail_from_transient_error, - std::string* data) + std::string data) : headers_(headers), last_url_origin_(last_url_origin), - did_fail_from_transient_error_(did_fail_from_transient_error) { - DCHECK(data); - data_.swap(*data); - } + did_fail_from_transient_error_(did_fail_from_transient_error), + data_(std::move(data)), + capacity_(data_.capacity()) {} const scoped_refptr<net::HttpResponseHeaders>& headers() const { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); @@ -213,16 +218,24 @@ } size_t capacity() const { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); - return data_.capacity(); + CHECK_EQ(capacity_, data_.capacity()); + return capacity_; } private: + CacheEntry(const CacheEntry&) = delete; + CacheEntry& operator=(const CacheEntry&) = delete; + THREAD_CHECKER(thread_checker_); scoped_refptr<net::HttpResponseHeaders> headers_; - Origin last_url_origin_; - bool did_fail_from_transient_error_; - std::string data_; + const Origin last_url_origin_; + const bool did_fail_from_transient_error_; + const std::string data_; + + // TODO(b/270993319): For debugging cache integrity issues in production only, + // remove after identifying the root cause. + const size_t capacity_; }; FetcherCache::FetcherCache(const char* name, size_t capacity) @@ -236,12 +249,15 @@ FetcherCache::~FetcherCache() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + CHECK_EQ(thread_id_, SbThreadGetId()); + CHECK(destroy_soon_called_); while (!cache_entries_.empty()) { delete cache_entries_.begin()->second; - cache_entries_.erase(cache_entries_.begin()); + cache_entries_.pop_front(); } + total_size_ = 0; memory_size_in_bytes_ = 0; count_resources_cached_ = 0; } @@ -251,12 +267,13 @@ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(!real_fetcher_creator.is_null()); - return base::Bind(&FetcherCache::CreateCachedFetcher, base::Unretained(this), - url, real_fetcher_creator); + return base::Bind(&FetcherCache::CreateCachedFetcher, this, url, + real_fetcher_creator); } void FetcherCache::NotifyResourceRequested(const std::string& url) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + CHECK_EQ(thread_id_, SbThreadGetId()); auto iter = cache_entries_.find(url); if (iter != cache_entries_.end()) { @@ -266,6 +283,14 @@ } } +void FetcherCache::DestroySoon() { +#if !defined(COBALT_BUILD_TYPE_GOLD) + CHECK(HasOneRef()); +#endif //! defined(COBALT_BUILD_TYPE_GOLD) + + destroy_soon_called_ = true; +} + std::unique_ptr<Fetcher> FetcherCache::CreateCachedFetcher( const GURL& url, const Loader::FetcherCreator& real_fetcher_creator, Fetcher::Handler* handler) { @@ -273,6 +298,11 @@ DCHECK(!real_fetcher_creator.is_null()); DCHECK(handler); +#if !defined(COBALT_BUILD_TYPE_GOLD) + CHECK(!destroy_soon_called_); +#endif // !defined(COBALT_BUILD_TYPE_GOLD) + CHECK_EQ(thread_id_, SbThreadGetId()); + auto iterator = cache_entries_.find(url.spec()); if (iterator != cache_entries_.end()) { auto entry = iterator->second; @@ -285,8 +315,7 @@ } std::unique_ptr<CachedFetcherHandler> cached_handler(new CachedFetcherHandler( - url.spec(), handler, - base::Bind(&FetcherCache::OnFetchSuccess, base::Unretained(this)))); + url.spec(), handler, base::Bind(&FetcherCache::OnFetchSuccess, this))); return std::unique_ptr<Fetcher>( new OngoingFetcher(std::move(cached_handler), real_fetcher_creator)); } @@ -295,32 +324,44 @@ const std::string& url, const scoped_refptr<net::HttpResponseHeaders>& headers, const Origin& last_url_origin, bool did_fail_from_transient_error, - std::string* data) { + std::string data) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); - DCHECK(data); - if (data->size() <= capacity_) { - auto entry = new CacheEntry(headers, last_url_origin, - did_fail_from_transient_error, data); +#if !defined(COBALT_BUILD_TYPE_GOLD) + CHECK(!destroy_soon_called_); +#endif // !defined(COBALT_BUILD_TYPE_GOLD) + CHECK_EQ(thread_id_, SbThreadGetId()); - bool inserted = cache_entries_.insert(std::make_pair(url, entry)).second; - if (!inserted) { - // The resource is already cached. - delete entry; - return; - } - - total_size_ += entry->capacity(); - while (total_size_ > capacity_) { - DCHECK(!cache_entries_.empty()); - total_size_ -= cache_entries_.begin()->second->capacity(); - delete cache_entries_.begin()->second; - cache_entries_.erase(cache_entries_.begin()); - --count_resources_cached_; - } - ++count_resources_cached_; - memory_size_in_bytes_ = total_size_; + if (data.capacity() > capacity_) { + return; } + + auto entry = new CacheEntry(headers, last_url_origin, + did_fail_from_transient_error, std::move(data)); + + bool inserted = cache_entries_.insert(std::make_pair(url, entry)).second; + if (!inserted) { + // The resource is already cached. + delete entry; + return; + } + + total_size_ += entry->capacity(); + ++count_resources_cached_; + + while (total_size_ > capacity_) { + // TODO(b/270993319): For debugging cache integrity issues in production + // only, remove after identifying the root cause. + CHECK(!cache_entries_.empty()); + CHECK_GE(total_size_, cache_entries_.begin()->second->capacity()); + + total_size_ -= cache_entries_.begin()->second->capacity(); + delete cache_entries_.begin()->second; + cache_entries_.pop_front(); + --count_resources_cached_; + } + + memory_size_in_bytes_ = total_size_; } } // namespace loader
diff --git a/cobalt/loader/fetcher_cache.h b/cobalt/loader/fetcher_cache.h index 32a4466..8f6e2b0 100644 --- a/cobalt/loader/fetcher_cache.h +++ b/cobalt/loader/fetcher_cache.h
@@ -15,6 +15,7 @@ #ifndef COBALT_LOADER_FETCHER_CACHE_H_ #define COBALT_LOADER_FETCHER_CACHE_H_ +#include <atomic> #include <memory> #include <string> @@ -25,6 +26,7 @@ #include "cobalt/loader/loader.h" #include "net/base/linked_hash_map.h" #include "net/http/http_response_headers.h" +#include "starboard/thread.h" #include "url/gurl.h" #include "url/origin.h" @@ -32,7 +34,7 @@ namespace loader { // Manages a cache for data fetched by Fetchers. -class FetcherCache { +class FetcherCache : public base::RefCountedThreadSafe<FetcherCache> { public: FetcherCache(const char* name, size_t capacity); ~FetcherCache(); @@ -41,6 +43,13 @@ const GURL& url, const Loader::FetcherCreator& real_fetcher_creator); void NotifyResourceRequested(const std::string& url); + // To signal the imminent destruction of this object. If everything is + // working as expected, there shouldn't be any other reference of this object, + // and all usages of this object should be completed. + // TODO(b/270993319): For debugging cache integrity issues in production only, + // remove after identifying the root cause. + void DestroySoon(); + private: class CacheEntry; @@ -50,10 +59,15 @@ void OnFetchSuccess(const std::string& url, const scoped_refptr<net::HttpResponseHeaders>& headers, const Origin& last_url_origin, - bool did_fail_from_transient_error, std::string* data); + bool did_fail_from_transient_error, std::string data); THREAD_CHECKER(thread_checker_); + // TODO(b/270993319): For debugging cache integrity issues in production only, + // remove after identifying the root cause. + const SbThreadId thread_id_ = SbThreadGetId(); + std::atomic_bool destroy_soon_called_{false}; + const size_t capacity_; size_t total_size_ = 0;
diff --git a/cobalt/loader/loader_factory.cc b/cobalt/loader/loader_factory.cc index 0cb575e..fcbe2d2 100644 --- a/cobalt/loader/loader_factory.cc +++ b/cobalt/loader/loader_factory.cc
@@ -31,7 +31,13 @@ debugger_hooks_(debugger_hooks), resource_provider_(resource_provider) { if (encoded_image_cache_capacity > 0) { - fetcher_cache_.reset(new FetcherCache(name, encoded_image_cache_capacity)); + fetcher_cache_ = new FetcherCache(name, encoded_image_cache_capacity); + } +} + +LoaderFactory::~LoaderFactory() { + if (fetcher_cache_) { + fetcher_cache_->DestroySoon(); } }
diff --git a/cobalt/loader/loader_factory.h b/cobalt/loader/loader_factory.h index e3f80ff..7602602 100644 --- a/cobalt/loader/loader_factory.h +++ b/cobalt/loader/loader_factory.h
@@ -47,6 +47,7 @@ const base::DebuggerHooks& debugger_hooks, size_t encoded_image_cache_capacity, base::ThreadPriority loader_thread_priority); + ~LoaderFactory(); // Creates a loader that fetches and decodes an image. std::unique_ptr<Loader> CreateImageLoader( @@ -108,7 +109,7 @@ // Used to cache the fetched raw data. Note that currently the cache is only // used to cache Image data. We may introduce more caches once we want to // cache fetched data for other resource types. - std::unique_ptr<FetcherCache> fetcher_cache_; + scoped_refptr<FetcherCache> fetcher_cache_; // Used with CLOG to report errors with the image source. const base::DebuggerHooks& debugger_hooks_;
diff --git a/cobalt/version.h b/cobalt/version.h index a191328..3182bc9 100644 --- a/cobalt/version.h +++ b/cobalt/version.h
@@ -35,6 +35,6 @@ // release is cut. //. -#define COBALT_VERSION "23.lts.4" +#define COBALT_VERSION "23.lts.5" #endif // COBALT_VERSION_H_
diff --git a/cobalt/web/csp_delegate.cc b/cobalt/web/csp_delegate.cc index 3fe54b5..92d0520 100644 --- a/cobalt/web/csp_delegate.cc +++ b/cobalt/web/csp_delegate.cc
@@ -80,8 +80,8 @@ if (csp_header_policy_ == csp::kCSPRequired || should_allow) { return should_allow; } else { - DLOG(WARNING) << "Page must include Content-Security-Policy header, it " - "will fail to load in production builds of Cobalt!"; + LOG(WARNING) << "Page must include Content-Security-Policy header, it " + "will fail to load in production builds of Cobalt!"; } } @@ -185,7 +185,7 @@ } else { // Didn't find Content-Security-Policy header. if (!headers.content_security_policy_report_only().empty()) { - DLOG(INFO) + LOG(INFO) << "Content-Security-Policy-Report-Only headers were " "received, but Content-Security-Policy headers are required."; }
diff --git a/cobalt/webdriver/web_driver_module.cc b/cobalt/webdriver/web_driver_module.cc index 3a4d360..64eaa39 100644 --- a/cobalt/webdriver/web_driver_module.cc +++ b/cobalt/webdriver/web_driver_module.cc
@@ -294,11 +294,18 @@ base::StringPrintf("/session/%s/execute", kSessionIdVariable), current_window_command_factory->GetCommandHandler( base::Bind(&WindowDriver::Execute))); + // https://www.w3.org/TR/2015/WD-webdriver-20150808/#execute-async-script webdriver_dispatcher_->RegisterCommand( WebDriverServer::kPost, base::StringPrintf("/session/%s/execute_async", kSessionIdVariable), current_window_command_factory->GetCommandHandler( base::Bind(&WindowDriver::ExecuteAsync))); + // https://www.w3.org/TR/2015/WD-webdriver-20150827/#execute-async-script + webdriver_dispatcher_->RegisterCommand( + WebDriverServer::kPost, + base::StringPrintf("/session/%s/execute/async", kSessionIdVariable), + current_window_command_factory->GetCommandHandler( + base::Bind(&WindowDriver::ExecuteAsync))); webdriver_dispatcher_->RegisterCommand( WebDriverServer::kPost, base::StringPrintf("/session/%s/element", kSessionIdVariable),
diff --git a/cobalt/worker/service_worker_consts.cc b/cobalt/worker/service_worker_consts.cc index a168caf..4254b85 100644 --- a/cobalt/worker/service_worker_consts.cc +++ b/cobalt/worker/service_worker_consts.cc
@@ -45,5 +45,23 @@ const char ServiceWorkerConsts::kSettingsJson[] = "service_worker_settings.json"; + +const char* const ServiceWorkerConsts::kJavaScriptMimeTypes[16] = { + "application/ecmascript", + "application/javascript", + "application/x-ecmascript", + "application/x-javascript", + "text/ecmascript", + "text/javascript", + "text/javascript1.0", + "text/javascript1.1", + "text/javascript1.2", + "text/javascript1.3", + "text/javascript1.4", + "text/javascript1.5", + "text/jscript", + "text/livescript", + "text/x-ecmascript", + "text/x-javascript"}; } // namespace worker } // namespace cobalt
diff --git a/cobalt/worker/service_worker_consts.h b/cobalt/worker/service_worker_consts.h index adb4d31..5bb8dd1 100644 --- a/cobalt/worker/service_worker_consts.h +++ b/cobalt/worker/service_worker_consts.h
@@ -30,6 +30,10 @@ // Constants for ServiceWorkerPersistentSettings static const char kSettingsJson[]; + + // Array of JavaScript mime types, according to the MIME Sniffinc spec: + // https://mimesniff.spec.whatwg.org/#javascript-mime-type + static const char* const kJavaScriptMimeTypes[16]; }; } // namespace worker
diff --git a/cobalt/worker/service_worker_global_scope.cc b/cobalt/worker/service_worker_global_scope.cc index e71735f..1598b81 100644 --- a/cobalt/worker/service_worker_global_scope.cc +++ b/cobalt/worker/service_worker_global_scope.cc
@@ -122,6 +122,7 @@ // last update check time to the current time. // 11. If response’s unsafe response is a bad import script // response, then return a network error. + // This is checked in WorkerGlobalScope. // 12. Set map[url] to response. service_worker->SetScriptResource(url, content);
diff --git a/cobalt/worker/service_worker_jobs.cc b/cobalt/worker/service_worker_jobs.cc index 3052d63..bf19a94 100644 --- a/cobalt/worker/service_worker_jobs.cc +++ b/cobalt/worker/service_worker_jobs.cc
@@ -733,28 +733,6 @@ /*skip_fetch_intercept=*/true); } -namespace { -// Array of JavaScript mime types, according to the MIME Sniffinc spec: -// https://mimesniff.spec.whatwg.org/#javascript-mime-type -static const char* const kJavaScriptMimeTypes[] = {"application/ecmascript", - "application/javascript", - "application/x-ecmascript", - "application/x-javascript", - "text/ecmascript", - "text/javascript", - "text/javascript1.0", - "text/javascript1.1", - "text/javascript1.2", - "text/javascript1.3", - "text/javascript1.4", - "text/javascript1.5", - "text/jscript", - "text/livescript", - "text/x-ecmascript", - "text/x-javascript"}; - -} // namespace - bool ServiceWorkerJobs::UpdateOnResponseStarted( scoped_refptr<UpdateJobState> state, loader::Fetcher* fetcher, const scoped_refptr<net::HttpResponseHeaders>& headers) { @@ -771,7 +749,7 @@ ServiceWorkerConsts::kServiceWorkerRegisterNoMIMEError)); return true; } - for (auto mime_type : kJavaScriptMimeTypes) { + for (auto mime_type : ServiceWorkerConsts::kJavaScriptMimeTypes) { if (net::MatchesMimeType(mime_type, content_type)) { mime_type_is_javascript = true; break; @@ -1148,9 +1126,9 @@ if (context->environment_settings()->GetOrigin() == registration_origin) { // 9. ... queue a task on settingsObject’s responsible event loop in the // DOM manipulation task source to run the following steps: - context->message_loop()->task_runner()->PostTask( + context->message_loop()->task_runner()->PostBlockingTask( FROM_HERE, - base::BindOnce( + base::Bind( [](web::Context* context, scoped_refptr<ServiceWorkerRegistrationObject> registration) { // 9.1. Let registrationObjects be every @@ -1699,9 +1677,9 @@ // 2.2. For each registrationObject in registrationObjects: for (auto& context : web_context_registrations_) { // 2.2.1. Queue a task to... - context->message_loop()->task_runner()->PostTask( + context->message_loop()->task_runner()->PostBlockingTask( FROM_HERE, - base::BindOnce( + base::Bind( [](web::Context* context, ServiceWorkerRegistrationObject* registration) { // 2.2.1. ... set the installing attribute of @@ -1729,9 +1707,9 @@ // 3.2. For each registrationObject in registrationObjects: for (auto& context : web_context_registrations_) { // 3.2.1. Queue a task to... - context->message_loop()->task_runner()->PostTask( + context->message_loop()->task_runner()->PostBlockingTask( FROM_HERE, - base::BindOnce( + base::Bind( [](web::Context* context, ServiceWorkerRegistrationObject* registration) { // 3.2.1. ... set the waiting attribute of registrationObject @@ -1757,9 +1735,9 @@ // 4.2. For each registrationObject in registrationObjects: for (auto& context : web_context_registrations_) { // 4.2.1. Queue a task to... - context->message_loop()->task_runner()->PostTask( + context->message_loop()->task_runner()->PostBlockingTask( FROM_HERE, - base::BindOnce( + base::Bind( [](web::Context* context, ServiceWorkerRegistrationObject* registration) { // 4.2.1. ... set the active attribute of registrationObject @@ -1807,34 +1785,31 @@ // 4. ... queue a task on // settingsObject's responsible event loop in the DOM manipulation task // source to run the following steps: - context->message_loop()->task_runner()->PostTask( - FROM_HERE, - base::BindOnce( - [](web::Context* context, ServiceWorkerObject* worker, - ServiceWorkerState state) { - DCHECK_EQ(context->message_loop(), - base::MessageLoop::current()); - // 4.1. Let objectMap be settingsObject's service worker object - // map. - // 4.2. If objectMap[worker] does not exist, then abort these - // steps. - // 4.3. Let workerObj be objectMap[worker]. - auto worker_obj = context->LookupServiceWorker(worker); - if (worker_obj) { - // 4.4. Set workerObj's state to state. - worker_obj->set_state(state); - // 4.5. Fire an event named statechange at workerObj. - context->message_loop()->task_runner()->PostTask( - FROM_HERE, - base::BindOnce( - [](scoped_refptr<ServiceWorker> worker_obj) { - worker_obj->DispatchEvent( - new web::Event(base::Tokens::statechange())); - }, - worker_obj)); - } - }, - context, base::Unretained(worker), state)); + context->message_loop()->task_runner()->PostBlockingTask( + FROM_HERE, base::Bind( + [](web::Context* context, ServiceWorkerObject* worker, + ServiceWorkerState state) { + DCHECK_EQ(context->message_loop(), + base::MessageLoop::current()); + // 4.1. Let objectMap be settingsObject's service + // worker object + // map. + // 4.2. If objectMap[worker] does not exist, then + // abort these + // steps. + // 4.3. Let workerObj be objectMap[worker]. + auto worker_obj = + context->LookupServiceWorker(worker); + if (worker_obj) { + // 4.4. Set workerObj's state to state. + worker_obj->set_state(state); + // 4.5. Fire an event named statechange at + // workerObj. + worker_obj->DispatchEvent( + new web::Event(base::Tokens::statechange())); + } + }, + context, base::Unretained(worker), state)); } } } @@ -1916,6 +1891,12 @@ context->message_loop()->task_runner()->PostBlockingTask( FROM_HERE, base::Bind( [](web::Context* context, ServiceWorkerObject* worker) { + auto worker_obj = context->LookupServiceWorker(worker); + if (worker_obj) { + worker_obj->set_state(kServiceWorkerStateRedundant); + worker_obj->DispatchEvent( + new web::Event(base::Tokens::statechange())); + } context->RemoveServiceWorker(worker); }, context, base::Unretained(worker)));
diff --git a/cobalt/worker/service_worker_jobs.h b/cobalt/worker/service_worker_jobs.h index 1b18ff4..d121554 100644 --- a/cobalt/worker/service_worker_jobs.h +++ b/cobalt/worker/service_worker_jobs.h
@@ -42,6 +42,7 @@ #include "cobalt/worker/client_query_options.h" #include "cobalt/worker/frame_type.h" #include "cobalt/worker/service_worker.h" +#include "cobalt/worker/service_worker_consts.h" #include "cobalt/worker/service_worker_object.h" #include "cobalt/worker/service_worker_registration.h" #include "cobalt/worker/service_worker_registration_map.h"
diff --git a/cobalt/worker/service_worker_object.cc b/cobalt/worker/service_worker_object.cc index b16feb6..9fcbbd1 100644 --- a/cobalt/worker/service_worker_object.cc +++ b/cobalt/worker/service_worker_object.cc
@@ -233,8 +233,8 @@ if (!service_worker_global_scope->csp_delegate()->OnReceiveHeaders( csp_headers)) { // https://www.w3.org/TR/service-workers/#content-security-policy - DLOG(WARNING) << "Warning: No Content Security Header received for the " - "service worker."; + LOG(WARNING) << "Warning: No Content Security Header received for the " + "service worker."; } web_context_->SetupFinished(); // 8.11. If serviceWorker is an active worker, and there are any tasks queued @@ -298,8 +298,8 @@ // set of event types to handle remains an empty set. The user agents are // encouraged to show a warning that the event listeners must be added on // the very first evaluation of the worker script. - DLOG(WARNING) << "ServiceWorkerGlobalScope's event listeners must be " - "added on the first evaluation of the worker script."; + LOG(WARNING) << "ServiceWorkerGlobalScope's event listeners must be " + "added on the first evaluation of the worker script."; } event_types.clear(); }
diff --git a/cobalt/worker/service_worker_persistent_settings.cc b/cobalt/worker/service_worker_persistent_settings.cc index ba47191..41a689d 100644 --- a/cobalt/worker/service_worker_persistent_settings.cc +++ b/cobalt/worker/service_worker_persistent_settings.cc
@@ -90,9 +90,6 @@ ServiceWorkerConsts::kSettingsJson)); persistent_settings_->ValidatePersistentSettings(); DCHECK(persistent_settings_); - - cache_.reset(cobalt::cache::Cache::GetInstance()); - DCHECK(cache_); } void ServiceWorkerPersistentSettings::ReadServiceWorkerRegistrationMapSettings( @@ -243,9 +240,10 @@ if (script_url_value.is_string()) { auto script_url_string = script_url_value.GetString(); auto script_url = GURL(script_url_string); - std::unique_ptr<std::vector<uint8_t>> data = cache_->Retrieve( - disk_cache::ResourceType::kServiceWorkerScript, - web::cache_utils::GetKey(key_string + script_url_string)); + std::unique_ptr<std::vector<uint8_t>> data = + cobalt::cache::Cache::GetInstance()->Retrieve( + disk_cache::ResourceType::kServiceWorkerScript, + web::cache_utils::GetKey(key_string + script_url_string)); if (data == nullptr) { return false; } @@ -382,7 +380,7 @@ // Use Cache::Store to persist the script resource. std::string resource = *(script_resource.second.content.get()); std::vector<uint8_t> data(resource.begin(), resource.end()); - cache_->Store( + cobalt::cache::Cache::GetInstance()->Store( disk_cache::ResourceType::kServiceWorkerScript, web::cache_utils::GetKey(registration_key_string + script_url_string), data, @@ -448,7 +446,7 @@ auto script_url_value = std::move(script_urls_list[i]); if (script_url_value.is_string()) { auto script_url_string = script_url_value.GetString(); - cache_->Delete( + cobalt::cache::Cache::GetInstance()->Delete( disk_cache::ResourceType::kServiceWorkerScript, web::cache_utils::GetKey(key_string + script_url_string)); }
diff --git a/cobalt/worker/service_worker_persistent_settings.h b/cobalt/worker/service_worker_persistent_settings.h index efda66f..1d4fd2e 100644 --- a/cobalt/worker/service_worker_persistent_settings.h +++ b/cobalt/worker/service_worker_persistent_settings.h
@@ -94,8 +94,6 @@ persistent_settings_; std::set<std::string> key_set_; - - std::unique_ptr<cobalt::cache::Cache> cache_; }; } // namespace worker
diff --git a/cobalt/worker/worker_global_scope.cc b/cobalt/worker/worker_global_scope.cc index 150313e..33c7b86 100644 --- a/cobalt/worker/worker_global_scope.cc +++ b/cobalt/worker/worker_global_scope.cc
@@ -31,9 +31,11 @@ #include "cobalt/web/user_agent_platform_info.h" #include "cobalt/web/window_or_worker_global_scope.h" #include "cobalt/web/window_timers.h" +#include "cobalt/worker/service_worker_consts.h" #include "cobalt/worker/service_worker_object.h" #include "cobalt/worker/worker_location.h" #include "cobalt/worker/worker_navigator.h" +#include "net/base/mime_util.h" #include "starboard/atomic.h" #include "url/gurl.h" @@ -135,9 +137,11 @@ *output_content = std::move(content); }, content), + base::Bind(&ScriptLoader::UpdateOnResponseStarted, + base::Unretained(this), error), base::Bind(&ScriptLoader::LoadingCompleteCallback, base::Unretained(this), loader, error), - skip_fetch_intercept); + net::HttpRequestHeaders(), skip_fetch_intercept); } void LoadingCompleteCallback(std::unique_ptr<loader::Loader>* loader, @@ -159,6 +163,31 @@ } } + bool UpdateOnResponseStarted( + std::unique_ptr<std::string>* error, loader::Fetcher* fetcher, + const scoped_refptr<net::HttpResponseHeaders>& headers) { + std::string content_type; + bool mime_type_is_javascript = false; + if (headers->GetNormalizedHeader("Content-type", &content_type)) { + for (auto mime_type : ServiceWorkerConsts::kJavaScriptMimeTypes) { + if (net::MatchesMimeType(mime_type, content_type)) { + mime_type_is_javascript = true; + break; + } + } + } + if (content_type.empty()) { + error->reset(new std::string(base::StringPrintf( + ServiceWorkerConsts::kServiceWorkerRegisterNoMIMEError, + content_type.c_str()))); + } else if (!mime_type_is_javascript) { + error->reset(new std::string(base::StringPrintf( + ServiceWorkerConsts::kServiceWorkerRegisterBadMIMEError, + content_type.c_str()))); + } + return true; + } + std::unique_ptr<std::string>& GetContents(int index) { return contents_[index]; }
diff --git a/cobalt/worker/worker_global_scope.h b/cobalt/worker/worker_global_scope.h index d3780d9..9c3468f 100644 --- a/cobalt/worker/worker_global_scope.h +++ b/cobalt/worker/worker_global_scope.h
@@ -35,6 +35,7 @@ #include "cobalt/web/user_agent_platform_info.h" #include "cobalt/web/window_or_worker_global_scope.h" #include "cobalt/web/window_timers.h" +#include "cobalt/worker/service_worker_consts.h" #include "cobalt/worker/worker_location.h" #include "cobalt/worker/worker_navigator.h" #include "net/http/http_response_headers.h"
diff --git a/components/prefs/json_pref_store.cc b/components/prefs/json_pref_store.cc index 7d8d189..d0d7481 100644 --- a/components/prefs/json_pref_store.cc +++ b/components/prefs/json_pref_store.cc
@@ -206,9 +206,13 @@ base::Value* old_value = nullptr; prefs_->Get(key, &old_value); if (!old_value || !value->Equals(old_value)) { +#if defined(STARBOARD) // Value::DictionaryValue::Set creates a nested dictionary treating a URL // key as a path, SetKey avoids this. - prefs_->SetKey(key, std::move(*value.get())); + prefs_->SetKey(key, base::Value::FromUniquePtrValue(std::move(value))); +#else + prefs_->Set(key, std::move(value)); +#endif ReportValueChanged(key, flags); } } @@ -222,7 +226,11 @@ base::Value* old_value = nullptr; prefs_->Get(key, &old_value); if (!old_value || !value->Equals(old_value)) { - prefs_->SetPath({key}, base::Value::FromUniquePtrValue(std::move(value))); +#if defined(STARBOARD) + prefs_->SetKey(key, base::Value::FromUniquePtrValue(std::move(value))); +#else + prefs_->Set(key, std::move(value)); +#endif ScheduleWrite(flags); } }
diff --git a/docker/linux/base/build/Dockerfile b/docker/linux/base/build/Dockerfile index a3f0a3b..60533d6 100644 --- a/docker/linux/base/build/Dockerfile +++ b/docker/linux/base/build/Dockerfile
@@ -22,6 +22,7 @@ && apt install -qqy --no-install-recommends \ binutils \ bison \ + nasm \ ninja-build \ pkgconf \ unzip \
diff --git a/docker/linux/clang-3-9/Dockerfile b/docker/linux/clang-3-9/Dockerfile index d79ccd3..abbb506 100644 --- a/docker/linux/clang-3-9/Dockerfile +++ b/docker/linux/clang-3-9/Dockerfile
@@ -17,7 +17,11 @@ ARG DEBIAN_FRONTEND=noninteractive RUN apt update -qqy \ - && apt install -qqy --no-install-recommends clang-3.9 \ + && apt install -qqy --no-install-recommends \ + clang-3.9 software-properties-common \ + && add-apt-repository -y ppa:git-core/ppa \ + && apt update -qqy \ + && apt install -qqy --no-install-recommends git \ && /opt/clean-after-apt.sh CMD gn gen ${OUTDIR}/${PLATFORM}_${CONFIG} --args="target_platform=\"${PLATFORM}\" build_type=\"${CONFIG}\" using_old_compiler=true" && \
diff --git a/docker/linux/gcc-6-3/Dockerfile b/docker/linux/gcc-6-3/Dockerfile index 2ead07d..18e431a 100644 --- a/docker/linux/gcc-6-3/Dockerfile +++ b/docker/linux/gcc-6-3/Dockerfile
@@ -17,7 +17,11 @@ ARG DEBIAN_FRONTEND=noninteractive RUN apt update -qqy \ - && apt install -qqy gcc-6 g++-6 \ + && apt install -qqy --no-install-recommends \ + gcc-6 g++-6 software-properties-common \ + && add-apt-repository -y ppa:git-core/ppa \ + && apt update -qqy \ + && apt install -qqy --no-install-recommends git \ && /opt/clean-after-apt.sh CMD gn gen ${OUTDIR}/${PLATFORM}_${CONFIG} --args="target_platform=\"${PLATFORM}\" build_type=\"${CONFIG}\" is_clang=false using_old_compiler=true" && \
diff --git a/net/cookies/cookie_monster_unittest.cc b/net/cookies/cookie_monster_unittest.cc index b137fb5..c55ddd0 100644 --- a/net/cookies/cookie_monster_unittest.cc +++ b/net/cookies/cookie_monster_unittest.cc
@@ -1482,6 +1482,7 @@ static const base::TimeDelta kAccessDelay = kLastAccessThreshold + base::TimeDelta::FromMilliseconds(20); +#if !defined(STARBOARD) TEST_F(CookieMonsterTest, TestLastAccess) { std::unique_ptr<CookieMonster> cm( new CookieMonster(nullptr, kLastAccessThreshold, &net_log_)); @@ -1520,6 +1521,7 @@ GetCookiesWithOptions(cm.get(), http_www_foo_.url(), options)); EXPECT_FALSE(last_access_date == GetFirstCookieAccessDate(cm.get())); } +#endif TEST_F(CookieMonsterTest, TestHostGarbageCollection) { TestHostGarbageCollectHelper();
diff --git a/net/cookies/cookie_options.cc b/net/cookies/cookie_options.cc index 1b97f85..fe87f26 100644 --- a/net/cookies/cookie_options.cc +++ b/net/cookies/cookie_options.cc
@@ -12,7 +12,11 @@ CookieOptions::CookieOptions() : exclude_httponly_(true), same_site_cookie_mode_(SameSiteCookieMode::DO_NOT_INCLUDE), +#if defined(STARBOARD) + update_access_time_(false), +#else update_access_time_(true), +#endif server_time_() {} } // namespace net
diff --git a/net/cookies/cookie_options.h b/net/cookies/cookie_options.h index 0e312b0..8cc6658 100644 --- a/net/cookies/cookie_options.h +++ b/net/cookies/cookie_options.h
@@ -59,7 +59,9 @@ bool has_server_time() const { return !server_time_.is_null(); } base::Time server_time() const { return server_time_; } +#if !defined(STARBOARD) void set_update_access_time() { update_access_time_ = true; } +#endif void set_do_not_update_access_time() { update_access_time_ = false; } bool update_access_time() const { return update_access_time_; }
diff --git a/starboard/android/apk/apk_sources.gni b/starboard/android/apk/apk_sources.gni index 07912f6..d1f668c 100644 --- a/starboard/android/apk/apk_sources.gni +++ b/starboard/android/apk/apk_sources.gni
@@ -28,11 +28,13 @@ "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java", "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltSystemConfigChangeReceiver.java", "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltTextToSpeechHelper.java", + "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/CrashContextUpdateHandler.java", "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/ErrorDialog.java", "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/KeyboardEditor.java", "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/KeyboardInputConnection.java", "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java", "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/NetworkStatus.java", + "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/NullCobaltFactory.java", "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/PlatformError.java", "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/ResourceOverlay.java", "//starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java",
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java index 0592441..d942102 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
@@ -159,6 +159,8 @@ getStarboardBridge().onActivityStart(this, keyboardEditor); super.onStart(); + + nativeInitializeMediaCapabilitiesInBackground(); } @Override @@ -394,4 +396,6 @@ public long getAppStartTimestamp() { return timeInNanoseconds; } + + private static native void nativeInitializeMediaCapabilitiesInBackground(); }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CrashContextUpdateHandler.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CrashContextUpdateHandler.java new file mode 100644 index 0000000..3dca27b --- /dev/null +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CrashContextUpdateHandler.java
@@ -0,0 +1,5 @@ +package dev.cobalt.coat; + +public interface CrashContextUpdateHandler { + public void onCrashContextUpdate(); +}
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/NullCobaltFactory.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/NullCobaltFactory.java new file mode 100644 index 0000000..e239e40 --- /dev/null +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/NullCobaltFactory.java
@@ -0,0 +1,16 @@ +package dev.cobalt.coat; + +// Provide a Null factory as some platform service might need it for legacy +// apk versions. +public class NullCobaltFactory implements CobaltService.Factory { + + @Override + public CobaltService createCobaltService(long nativeService) { + return null; + } + + @Override + public String getServiceName() { + return null; + } +}
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java index e1cf4f7..2e25fc9 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
@@ -81,6 +81,7 @@ private ResourceOverlay resourceOverlay; private AdvertisingId advertisingId; private VolumeStateReceiver volumeStateReceiver; + private CrashContextUpdateHandler crashContextUpdateHandler; static { // Even though NativeActivity already loads our library from C++, @@ -827,9 +828,16 @@ public void setCrashContext(String key, String value) { Log.i(TAG, "setCrashContext Called: " + key + ", " + value); crashContext.put(key, value); + if (this.crashContextUpdateHandler != null) { + this.crashContextUpdateHandler.onCrashContextUpdate(); + } } public HashMap<String, String> getCrashContext() { return this.crashContext; } + + public void registerCrashContextUpdateHandler(CrashContextUpdateHandler handler) { + this.crashContextUpdateHandler = handler; + } }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java index 1d3428e..9722d93 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
@@ -136,7 +136,7 @@ * @param nativeMediaDrmBridge The native owner of this class. */ @UsedByNative - static MediaDrmBridge create(long nativeMediaDrmBridge) { + static MediaDrmBridge create(String keySystem, long nativeMediaDrmBridge) { UUID cryptoScheme = WIDEVINE_UUID; if (!MediaDrm.isCryptoSchemeSupported(cryptoScheme)) { return null; @@ -144,7 +144,7 @@ MediaDrmBridge mediaDrmBridge = null; try { - mediaDrmBridge = new MediaDrmBridge(cryptoScheme, nativeMediaDrmBridge); + mediaDrmBridge = new MediaDrmBridge(keySystem, cryptoScheme, nativeMediaDrmBridge); Log.d(TAG, "MediaDrmBridge successfully created."); } catch (UnsupportedSchemeException e) { Log.e(TAG, "Unsupported DRM scheme", e); @@ -373,7 +373,7 @@ return mMediaCrypto; } - private MediaDrmBridge(UUID schemeUUID, long nativeMediaDrmBridge) + private MediaDrmBridge(String keySystem, UUID schemeUUID, long nativeMediaDrmBridge) throws android.media.UnsupportedSchemeException { mSchemeUUID = schemeUUID; mMediaDrm = new MediaDrm(schemeUUID); @@ -450,6 +450,10 @@ mMediaDrm.setPropertyString("privacyMode", "enable"); mMediaDrm.setPropertyString("sessionSharing", "enable"); + if (keySystem.equals("com.youtube.widevine.l3") + && mMediaDrm.getPropertyString("securityLevel") != "L3") { + mMediaDrm.setPropertyString("securityLevel", "L3"); + } } @RequiresApi(23)
diff --git a/starboard/android/shared/BUILD.gn b/starboard/android/shared/BUILD.gn index ca4c2e2..cf01d39 100644 --- a/starboard/android/shared/BUILD.gn +++ b/starboard/android/shared/BUILD.gn
@@ -336,6 +336,7 @@ "directory_get_next.cc", "directory_internal.h", "directory_open.cc", + "drm_create_system.cc", "drm_system.cc", "drm_system.h", "egl_swap_buffers.cc", @@ -385,10 +386,12 @@ "media_get_video_buffer_budget.cc", "media_is_audio_supported.cc", "media_is_buffer_pool_allocate_on_demand.cc", + "media_is_supported.cc", "media_is_video_supported.cc", "microphone_impl.cc", "network_status_impl.cc", "platform_service.cc", + "player_components_factory.cc", "player_components_factory.h", "player_create.cc", "player_destroy.cc", @@ -481,13 +484,6 @@ "internal/input_events_filter.h", ] defines = [ "STARBOARD_INPUT_EVENTS_FILTER" ] - deps += [ "//starboard/android/shared/drm_system_extension" ] - } else { - sources += [ - "drm_create_system.cc", - "media_is_supported.cc", - "player_components_factory.cc", - ] } if (sb_is_evergreen_compatible) {
diff --git a/starboard/android/shared/android_main.cc b/starboard/android/shared/android_main.cc index 755210d..40fa072 100644 --- a/starboard/android/shared/android_main.cc +++ b/starboard/android/shared/android_main.cc
@@ -224,6 +224,25 @@ JniEnvExt::Initialize(env, starboard_bridge); } +extern "C" SB_EXPORT_PLATFORM void +Java_dev_cobalt_coat_VolumeStateReceiver_nativeVolumeChanged(JNIEnv* env, + jobject jcaller, + jint volumeDelta) { + if (g_app_running) { + SbKey key = + volumeDelta > 0 ? SbKey::kSbKeyVolumeUp : SbKey::kSbKeyVolumeDown; + ApplicationAndroid::Get()->SendKeyboardInject(key); + } +} + +extern "C" SB_EXPORT_PLATFORM void +Java_dev_cobalt_coat_VolumeStateReceiver_nativeMuteChanged(JNIEnv* env, + jobject jcaller) { + if (g_app_running) { + ApplicationAndroid::Get()->SendKeyboardInject(SbKey::kSbKeyVolumeMute); + } +} + } // namespace } // namespace shared } // namespace android
diff --git a/starboard/android/shared/application_android.cc b/starboard/android/shared/application_android.cc index 18bf1fc..deeed10 100644 --- a/starboard/android/shared/application_android.cc +++ b/starboard/android/shared/application_android.cc
@@ -736,21 +736,6 @@ overlayed_bool_variables_[var_name] = value; return value; } - -extern "C" SB_EXPORT_PLATFORM void -Java_dev_cobalt_coat_VolumeStateReceiver_nativeVolumeChanged(JNIEnv* env, - jobject jcaller, - jint volumeDelta) { - SbKey key = volumeDelta > 0 ? SbKey::kSbKeyVolumeUp : SbKey::kSbKeyVolumeDown; - ApplicationAndroid::Get()->SendKeyboardInject(key); -} - -extern "C" SB_EXPORT_PLATFORM void -Java_dev_cobalt_coat_VolumeStateReceiver_nativeMuteChanged(JNIEnv* env, - jobject jcaller) { - ApplicationAndroid::Get()->SendKeyboardInject(SbKey::kSbKeyVolumeMute); -} - } // namespace shared } // namespace android } // namespace starboard
diff --git a/starboard/android/shared/audio_renderer_passthrough.cc b/starboard/android/shared/audio_renderer_passthrough.cc index 34f5bed..3f94eb0 100644 --- a/starboard/android/shared/audio_renderer_passthrough.cc +++ b/starboard/android/shared/audio_renderer_passthrough.cc
@@ -135,12 +135,6 @@ &AudioRendererPassthrough::CreateAudioTrackAndStartProcessing, this)); } - if (frames_per_input_buffer_ == 0) { - frames_per_input_buffer_ = ParseAc3SyncframeAudioSampleCount( - input_buffers.front()->data(), input_buffers.front()->size()); - SB_LOG(INFO) << "Got frames per input buffer " << frames_per_input_buffer_; - } - can_accept_more_data_.store(false); decoder_->Decode( @@ -597,6 +591,25 @@ auto decoded_audio = decoder_->Read(&decoded_audio_sample_rate); SB_DCHECK(decoded_audio); + if (!decoded_audio->is_end_of_stream()) { + SB_DCHECK(decoded_audio->size() > 0); + // We set |frames_per_input_buffer_| before adding first |decoded_audio| + // into |decoded_audios_|. The usage of |frames_per_input_buffer_| in + // UpdateStatusAndWriteData() from another thread only happens when there is + // audio decoded, so it's thread-safe even if the code is not synchronized + // using a lock. + if (frames_per_input_buffer_ == 0) { + frames_per_input_buffer_ = ParseAc3SyncframeAudioSampleCount( + decoded_audio->buffer(), decoded_audio->size()); + SB_LOG(INFO) << "Got frames per input buffer " + << frames_per_input_buffer_; + } else { + SB_DCHECK(frames_per_input_buffer_ == + ParseAc3SyncframeAudioSampleCount(decoded_audio->buffer(), + decoded_audio->size())); + } + } + ScopedLock scoped_lock(mutex_); decoded_audios_.push(decoded_audio); }
diff --git a/starboard/android/shared/audio_track_audio_sink_type.cc b/starboard/android/shared/audio_track_audio_sink_type.cc index d055a29..ada20e9 100644 --- a/starboard/android/shared/audio_track_audio_sink_type.cc +++ b/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -181,8 +181,8 @@ if (bridge_.GetAndResetHasAudioDeviceChanged(env)) { SB_LOG(INFO) << "Audio device changed, raising a capability changed " "error to restart playback."; - error_func_(kSbPlayerErrorCapabilityChanged, - "Audio device capability changed", context_); + ReportError(kSbPlayerErrorCapabilityChanged, + "Audio device capability changed"); break; } @@ -325,10 +325,9 @@ bool capabilities_changed = written_frames == AudioTrackBridge::kAudioTrackErrorDeadObject; - error_func_( + ReportError( capabilities_changed, - FormatString("Error while writing frames: %d", written_frames), - context_); + FormatString("Error while writing frames: %d", written_frames)); SB_LOG(INFO) << "Restarting playback."; break; } else if (written_frames > 0) { @@ -381,6 +380,14 @@ return samples_written / channels_; } +void AudioTrackAudioSink::ReportError(bool capability_changed, + const std::string& error_message) { + SB_LOG(INFO) << "AudioTrackAudioSink error: " << error_message; + if (error_func_) { + error_func_(capability_changed, error_message, context_); + } +} + void AudioTrackAudioSink::SetVolume(double volume) { bridge_.SetVolume(volume); }
diff --git a/starboard/android/shared/audio_track_audio_sink_type.h b/starboard/android/shared/audio_track_audio_sink_type.h index 38c7014..88974ff 100644 --- a/starboard/android/shared/audio_track_audio_sink_type.h +++ b/starboard/android/shared/audio_track_audio_sink_type.h
@@ -18,6 +18,7 @@ #include <atomic> #include <functional> #include <map> +#include <string> #include <vector> #include "starboard/android/shared/audio_sink_min_required_frames_tester.h" @@ -133,6 +134,8 @@ int WriteData(JniEnvExt* env, const void* buffer, int size, SbTime sync_time); + void ReportError(bool capability_changed, const std::string& error_message); + Type* const type_; const int channels_; const int sampling_frequency_hz_;
diff --git a/starboard/android/shared/drm_create_system.cc b/starboard/android/shared/drm_create_system.cc index 1e97b89..6e9bc88 100644 --- a/starboard/android/shared/drm_create_system.cc +++ b/starboard/android/shared/drm_create_system.cc
@@ -26,6 +26,7 @@ SbDrmSessionClosedFunc session_closed_callback) { using starboard::android::shared::DrmSystem; using starboard::android::shared::IsWidevineL1; + using starboard::android::shared::IsWidevineL3; if (!update_request_callback || !session_updated_callback || !key_statuses_changed_callback || !server_certificate_updated_callback || @@ -33,13 +34,13 @@ return kSbDrmSystemInvalid; } - if (!IsWidevineL1(key_system)) { + if (!IsWidevineL1(key_system) && !IsWidevineL3(key_system)) { return kSbDrmSystemInvalid; } DrmSystem* drm_system = - new DrmSystem(context, update_request_callback, session_updated_callback, - key_statuses_changed_callback); + new DrmSystem(key_system, context, update_request_callback, + session_updated_callback, key_statuses_changed_callback); if (!drm_system->is_valid()) { delete drm_system; return kSbDrmSystemInvalid;
diff --git a/starboard/android/shared/drm_system.cc b/starboard/android/shared/drm_system.cc index a2edadd..3b195e5 100644 --- a/starboard/android/shared/drm_system.cc +++ b/starboard/android/shared/drm_system.cc
@@ -67,8 +67,7 @@ if (left.identifier_size != right.identifier_size) { return false; } - return memcmp(left.identifier, right.identifier, - left.identifier_size) == 0; + return memcmp(left.identifier, right.identifier, left.identifier_size) == 0; } extern "C" SB_EXPORT_PLATFORM void @@ -112,8 +111,9 @@ SB_DCHECK(session_id_elements); // NULL array indicates key status isn't supported (i.e. Android API < 23) - jsize length = (j_key_status_array == NULL) ? 0 - : env->GetArrayLength(j_key_status_array); + jsize length = (j_key_status_array == NULL) + ? 0 + : env->GetArrayLength(j_key_status_array); std::vector<SbDrmKeyId> drm_key_ids(length); std::vector<SbDrmKeyStatus> drm_key_statuses(length); @@ -172,11 +172,13 @@ } // namespace DrmSystem::DrmSystem( + const char* key_system, void* context, SbDrmSessionUpdateRequestFunc update_request_callback, SbDrmSessionUpdatedFunc session_updated_callback, SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback) - : context_(context), + : key_system_(key_system), + context_(context), update_request_callback_(update_request_callback), session_updated_callback_(session_updated_callback), key_statuses_changed_callback_(key_statuses_changed_callback), @@ -186,9 +188,12 @@ ON_INSTANCE_CREATED(AndroidDrmSystem); JniEnvExt* env = JniEnvExt::Get(); + ScopedLocalJavaRef<jstring> j_key_system( + env->NewStringStandardUTFOrAbort(key_system)); j_media_drm_bridge_ = env->CallStaticObjectMethodOrAbort( "dev/cobalt/media/MediaDrmBridge", "create", - "(J)Ldev/cobalt/media/MediaDrmBridge;", reinterpret_cast<jlong>(this)); + "(Ljava/lang/String;J)Ldev/cobalt/media/MediaDrmBridge;", + j_key_system.Get(), reinterpret_cast<jlong>(this)); if (!j_media_drm_bridge_) { SB_LOG(ERROR) << "Failed to create MediaDrmBridge."; return;
diff --git a/starboard/android/shared/drm_system.h b/starboard/android/shared/drm_system.h index 19b4bfe..48051c5 100644 --- a/starboard/android/shared/drm_system.h +++ b/starboard/android/shared/drm_system.h
@@ -23,6 +23,7 @@ #include <unordered_map> #include <vector> +#include "starboard/android/shared/media_common.h" #include "starboard/common/log.h" #include "starboard/common/mutex.h" #include "starboard/types.h" @@ -33,7 +34,8 @@ class DrmSystem : public ::SbDrmSystemPrivate { public: - DrmSystem(void* context, + DrmSystem(const char* key_system, + void* context, SbDrmSessionUpdateRequestFunc update_request_callback, SbDrmSessionUpdatedFunc session_updated_callback, SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback); @@ -54,8 +56,7 @@ bool IsServerCertificateUpdatable() override { return false; } void UpdateServerCertificate(int ticket, const void* certificate, - int certificate_size) override { - } + int certificate_size) override {} const void* GetMetrics(int* size) override; jobject GetMediaCrypto() const { return j_media_crypto_; } @@ -76,10 +77,14 @@ bool is_valid() const { return j_media_drm_bridge_ != NULL && j_media_crypto_ != NULL; } + bool require_secured_decoder() const { + return IsWidevineL1(key_system_.c_str()); + } private: void CallKeyStatusesChangedCallbackWithKeyStatusRestricted_Locked(); + const std::string key_system_; void* context_; SbDrmSessionUpdateRequestFunc update_request_callback_; SbDrmSessionUpdatedFunc session_updated_callback_;
diff --git a/starboard/android/shared/media_capabilities_cache.cc b/starboard/android/shared/media_capabilities_cache.cc index 2a5fc0e..7b8997b 100644 --- a/starboard/android/shared/media_capabilities_cache.cc +++ b/starboard/android/shared/media_capabilities_cache.cc
@@ -22,6 +22,7 @@ #include "starboard/once.h" #include "starboard/shared/starboard/media/key_system_supportability_cache.h" #include "starboard/shared/starboard/media/mime_supportability_cache.h" +#include "starboard/thread.h" namespace starboard { namespace android { @@ -268,7 +269,7 @@ return GetIsWidevineSupported(); } ScopedLock scoped_lock(mutex_); - LazyInitialize_Locked(); + UpdateMediaCapabilities_Locked(); return is_widevine_supported_; } @@ -277,7 +278,7 @@ return GetIsCbcsSupported(); } ScopedLock scoped_lock(mutex_); - LazyInitialize_Locked(); + UpdateMediaCapabilities_Locked(); return is_cbcs_supported_; } @@ -289,7 +290,7 @@ supported_transfer_ids.end(); } ScopedLock scoped_lock(mutex_); - LazyInitialize_Locked(); + UpdateMediaCapabilities_Locked(); return supported_transfer_ids_.find(transfer_id) != supported_transfer_ids_.end(); } @@ -316,7 +317,7 @@ } ScopedLock scoped_lock(mutex_); - LazyInitialize_Locked(); + UpdateMediaCapabilities_Locked(); return max_audio_output_channels_; } @@ -361,7 +362,7 @@ } ScopedLock scoped_lock(mutex_); - LazyInitialize_Locked(); + UpdateMediaCapabilities_Locked(); for (auto& audio_capability : audio_codec_capabilities_map_[mime_type]) { // Reject if tunnel mode is required but codec doesn't support it. @@ -407,7 +408,7 @@ } ScopedLock scoped_lock(mutex_); - LazyInitialize_Locked(); + UpdateMediaCapabilities_Locked(); for (auto& video_capability : video_codec_capabilities_map_[mime_type]) { // Reject if secure decoder is required but codec doesn't support it. @@ -465,6 +466,8 @@ void MediaCapabilitiesCache::ClearCache() { ScopedLock scoped_lock(mutex_); is_initialized_ = false; + supported_hdr_types_is_dirty_ = true; + audio_output_channels_is_dirty_ = true; is_widevine_supported_ = false; is_cbcs_supported_ = false; supported_transfer_ids_.clear(); @@ -474,23 +477,9 @@ max_audio_output_channels_ = -1; } -void MediaCapabilitiesCache::ReloadSupportedHdrTypes() { +void MediaCapabilitiesCache::Initialize() { ScopedLock scoped_lock(mutex_); - if (!is_initialized_) { - LazyInitialize_Locked(); - return; - } - supported_transfer_ids_ = GetSupportedHdrTypes(); -} - -void MediaCapabilitiesCache::ReloadAudioOutputChannels() { - ScopedLock scoped_lock(mutex_); - if (!is_initialized_) { - LazyInitialize_Locked(); - return; - } - max_audio_output_channels_ = - ::starboard::android::shared::GetMaxAudioOutputChannels(); + UpdateMediaCapabilities_Locked(); } MediaCapabilitiesCache::MediaCapabilitiesCache() { @@ -499,20 +488,23 @@ KeySystemSupportabilityCache::GetInstance()->SetCacheEnabled(true); } -void MediaCapabilitiesCache::LazyInitialize_Locked() { +void MediaCapabilitiesCache::UpdateMediaCapabilities_Locked() { mutex_.DCheckAcquired(); + if (supported_hdr_types_is_dirty_.exchange(false)) { + supported_transfer_ids_ = GetSupportedHdrTypes(); + } + if (audio_output_channels_is_dirty_.exchange(false)) { + max_audio_output_channels_ = + ::starboard::android::shared::GetMaxAudioOutputChannels(); + } + if (is_initialized_) { return; } is_widevine_supported_ = GetIsWidevineSupported(); is_cbcs_supported_ = GetIsCbcsSupported(); - supported_transfer_ids_ = GetSupportedHdrTypes(); - max_audio_output_channels_ = - ::starboard::android::shared::GetMaxAudioOutputChannels(); - LoadCodecInfos_Locked(); - is_initialized_ = true; } @@ -569,17 +561,30 @@ extern "C" SB_EXPORT_PLATFORM void Java_dev_cobalt_util_DisplayUtil_nativeOnDisplayChanged() { SB_DLOG(INFO) << "Display device has changed."; - MediaCapabilitiesCache::GetInstance()->ReloadSupportedHdrTypes(); + MediaCapabilitiesCache::GetInstance()->ClearSupportedHdrTypes(); MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities(); } extern "C" SB_EXPORT_PLATFORM void Java_dev_cobalt_media_AudioOutputManager_nativeOnAudioDeviceChanged() { SB_DLOG(INFO) << "Audio device has changed."; - MediaCapabilitiesCache::GetInstance()->ReloadAudioOutputChannels(); + MediaCapabilitiesCache::GetInstance()->ClearAudioOutputChannels(); MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities(); } +void* MediaCapabilitiesCacheInitializationThreadEntry(void* context) { + SB_LOG(INFO) << "Initialize MediaCapabilitiesCache in background."; + MediaCapabilitiesCache::GetInstance()->Initialize(); + return 0; +} + +extern "C" SB_EXPORT_PLATFORM void +Java_dev_cobalt_coat_CobaltActivity_nativeInitializeMediaCapabilitiesInBackground() { + SbThreadCreate(0, kSbThreadPriorityNormal, kSbThreadNoAffinity, false, + "media_capabilities_cache_thread", + MediaCapabilitiesCacheInitializationThreadEntry, nullptr); +} + } // namespace shared } // namespace android } // namespace starboard
diff --git a/starboard/android/shared/media_capabilities_cache.h b/starboard/android/shared/media_capabilities_cache.h index 21d735b..db3879e 100644 --- a/starboard/android/shared/media_capabilities_cache.h +++ b/starboard/android/shared/media_capabilities_cache.h
@@ -160,8 +160,9 @@ void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; } void ClearCache(); - void ReloadSupportedHdrTypes(); - void ReloadAudioOutputChannels(); + void Initialize(); + void ClearSupportedHdrTypes() { supported_hdr_types_is_dirty_ = true; } + void ClearAudioOutputChannels() { audio_output_channels_is_dirty_ = true; } private: MediaCapabilitiesCache(); @@ -170,7 +171,7 @@ MediaCapabilitiesCache(const MediaCapabilitiesCache&) = delete; MediaCapabilitiesCache& operator=(const MediaCapabilitiesCache&) = delete; - void LazyInitialize_Locked(); + void UpdateMediaCapabilities_Locked(); void LoadCodecInfos_Locked(); Mutex mutex_; @@ -187,6 +188,8 @@ std::map<std::string, VideoCodecCapabilities> video_codec_capabilities_map_; std::atomic_bool is_enabled_{true}; + std::atomic_bool supported_hdr_types_is_dirty_{true}; + std::atomic_bool audio_output_channels_is_dirty_{true}; bool is_initialized_ = false; bool is_widevine_supported_ = false; bool is_cbcs_supported_ = false;
diff --git a/starboard/android/shared/media_codec_bridge.cc b/starboard/android/shared/media_codec_bridge.cc index 2e2cd1a..f09bd35 100644 --- a/starboard/android/shared/media_codec_bridge.cc +++ b/starboard/android/shared/media_codec_bridge.cc
@@ -170,8 +170,8 @@ std::string decoder_name = MediaCapabilitiesCache::GetInstance()->FindAudioDecoder( - mime, 0, /* bitrate */ - false /* must_support_tunnel_mode */); + mime, /* bitrate = */ 0, + /* must_support_tunnel_mode = */ false); if (decoder_name.empty()) { SB_LOG(ERROR) << "Failed to find decoder for " << audio_codec << "."; @@ -220,6 +220,7 @@ jobject j_surface, jobject j_media_crypto, const SbMediaColorMetadata* color_metadata, + bool require_secured_decoder, bool require_software_codec, int tunnel_mode_audio_session_id, bool force_big_endian_hdr_metadata, @@ -233,34 +234,28 @@ return scoped_ptr<MediaCodecBridge>(NULL); } - const bool must_support_secure = !!j_media_crypto; + const bool must_support_secure = require_secured_decoder; const bool must_support_hdr = color_metadata; const bool must_support_tunnel_mode = tunnel_mode_audio_session_id != -1; // On first pass, try to find a decoder with HDR if the color info is // non-null. std::string decoder_name = MediaCapabilitiesCache::GetInstance()->FindVideoDecoder( - mime, must_support_secure, /* must_support_secure */ - must_support_hdr, /* must_support_hdr */ - require_software_codec, /* is_software_codec */ - must_support_tunnel_mode, /* must_support_tunnel_mode */ - force_improved_support_check, /* force_improved_support_check */ - 0, /* frame_width */ - 0, /* frame_height */ - 0, /* bitrate */ - 0 /* fps */); + mime, must_support_secure, must_support_hdr, require_software_codec, + must_support_tunnel_mode, force_improved_support_check, + /* frame_width = */ 0, + /* frame_height = */ 0, + /* bitrate = */ 0, + /* fps = */ 0); if (decoder_name.empty() && color_metadata) { // On second pass, forget HDR. decoder_name = MediaCapabilitiesCache::GetInstance()->FindVideoDecoder( - mime, must_support_secure, /* must_support_secure */ - false, /* must_support_hdr */ - require_software_codec, /* is_software_codec */ - must_support_tunnel_mode, /* must_support_tunnel_mode */ - force_improved_support_check, /* force_improved_support_check */ - 0, /* frame_width */ - 0, /* frame_height */ - 0, /* bitrate */ - 0 /* fps */); + mime, must_support_secure, /* must_support_hdr = */ false, + require_software_codec, must_support_tunnel_mode, + force_improved_support_check, /* frame_width = */ 0, + /* frame_height = */ 0, + /* bitrate = */ 0, + /* fps = */ 0); } if (decoder_name.empty()) {
diff --git a/starboard/android/shared/media_codec_bridge.h b/starboard/android/shared/media_codec_bridge.h index 77b9c7a..d3999af 100644 --- a/starboard/android/shared/media_codec_bridge.h +++ b/starboard/android/shared/media_codec_bridge.h
@@ -110,6 +110,7 @@ jobject j_surface, jobject j_media_crypto, const SbMediaColorMetadata* color_metadata, + bool require_secured_decoder, bool require_software_codec, int tunnel_mode_audio_session_id, bool force_big_endian_hdr_metadata,
diff --git a/starboard/android/shared/media_common.h b/starboard/android/shared/media_common.h index f9e5a56..dcbd0b3 100644 --- a/starboard/android/shared/media_common.h +++ b/starboard/android/shared/media_common.h
@@ -34,6 +34,10 @@ strcmp(key_system, "com.widevine.alpha") == 0; } +inline bool IsWidevineL3(const char* key_system) { + return strcmp(key_system, "com.youtube.widevine.l3") == 0; +} + // Map a supported |SbMediaAudioCodec| into its corresponding mime type // string. Returns |nullptr| if |audio_codec| is not supported. // On return, |is_passthrough| will be set to true if the codec should be played
diff --git a/starboard/android/shared/media_decoder.cc b/starboard/android/shared/media_decoder.cc index 90beda3..eb9079c 100644 --- a/starboard/android/shared/media_decoder.cc +++ b/starboard/android/shared/media_decoder.cc
@@ -128,12 +128,14 @@ SB_DCHECK(frame_rendered_cb_); jobject j_media_crypto = drm_system_ ? drm_system_->GetMediaCrypto() : NULL; + const bool require_secured_decoder = + drm_system_ && drm_system_->require_secured_decoder(); SB_DCHECK(!drm_system_ || j_media_crypto); 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, - force_big_endian_hdr_metadata, force_improved_support_check, - error_message); + color_metadata, require_secured_decoder, require_software_codec, + tunnel_mode_audio_session_id, force_big_endian_hdr_metadata, + force_improved_support_check, error_message); if (!media_codec_bridge_) { SB_LOG(ERROR) << "Failed to create video media codec bridge with error: " << *error_message;
diff --git a/starboard/android/shared/media_is_supported.cc b/starboard/android/shared/media_is_supported.cc index fcd6806..c27e0d2 100644 --- a/starboard/android/shared/media_is_supported.cc +++ b/starboard/android/shared/media_is_supported.cc
@@ -26,6 +26,7 @@ SbMediaAudioCodec audio_codec, const char* key_system) { using starboard::android::shared::IsWidevineL1; + using starboard::android::shared::IsWidevineL3; using starboard::android::shared::MediaCapabilitiesCache; using starboard::shared::starboard::media::MimeType; @@ -48,7 +49,7 @@ return false; } const char* key_system_type = mime_type.subtype().c_str(); - if (!IsWidevineL1(key_system_type)) { + if (!IsWidevineL1(key_system_type) && !IsWidevineL3(key_system_type)) { return false; }
diff --git a/starboard/android/shared/player_components_factory.cc b/starboard/android/shared/player_components_factory.cc index 01dc7a6..c2761f0 100644 --- a/starboard/android/shared/player_components_factory.cc +++ b/starboard/android/shared/player_components_factory.cc
@@ -14,6 +14,8 @@ #include "starboard/android/shared/player_components_factory.h" +#include "starboard/android/shared/drm_system.h" + namespace starboard { namespace shared { namespace starboard { @@ -30,12 +32,18 @@ bool VideoDecoder::OutputModeSupported(SbPlayerOutputMode output_mode, SbMediaVideoCodec codec, SbDrmSystem drm_system) { + using ::starboard::android::shared::DrmSystem; + if (output_mode == kSbPlayerOutputModePunchOut) { return true; } if (output_mode == kSbPlayerOutputModeDecodeToTexture) { - return !SbDrmSystemIsValid(drm_system); + if (!SbDrmSystemIsValid(drm_system)) { + return true; + } + DrmSystem* android_drm_system = static_cast<DrmSystem*>(drm_system); + return !android_drm_system->require_secured_decoder(); } return false;
diff --git a/starboard/android/shared/player_components_factory.h b/starboard/android/shared/player_components_factory.h index 92a2b78..5271243 100644 --- a/starboard/android/shared/player_components_factory.h +++ b/starboard/android/shared/player_components_factory.h
@@ -194,10 +194,6 @@ const int kDefaultAudioSinkMaxCachedFrames = 8 * kDefaultAudioSinkMinFramesPerAppend; - virtual SbDrmSystem GetExtendedDrmSystem(SbDrmSystem drm_system) { - return drm_system; - } - static int AlignUp(int value, int alignment) { return (value + alignment - 1) / alignment * alignment; } @@ -241,8 +237,7 @@ scoped_ptr<AudioRendererPassthrough> audio_renderer; audio_renderer.reset(new AudioRendererPassthrough( creation_parameters.audio_sample_info(), - GetExtendedDrmSystem(creation_parameters.drm_system()), - enable_audio_device_callback)); + creation_parameters.drm_system(), enable_audio_device_callback)); if (!audio_renderer->is_valid()) { return scoped_ptr<PlayerComponents>(); } @@ -420,8 +415,7 @@ audio_decoder->reset(new AdaptiveAudioDecoder( creation_parameters.audio_sample_info(), - GetExtendedDrmSystem(creation_parameters.drm_system()), - decoder_creator)); + creation_parameters.drm_system(), decoder_creator)); bool enable_audio_device_callback = audio_mime_type.GetParamBoolValue("enableaudiodevicecallback", true); @@ -520,8 +514,7 @@ } scoped_ptr<VideoDecoder> video_decoder(new VideoDecoder( - creation_parameters.video_codec(), - GetExtendedDrmSystem(creation_parameters.drm_system()), + creation_parameters.video_codec(), creation_parameters.drm_system(), creation_parameters.output_mode(), creation_parameters.decode_target_graphics_context_provider(), creation_parameters.max_video_capabilities(),
diff --git a/starboard/android/shared/video_decoder.cc b/starboard/android/shared/video_decoder.cc index d1ce65f..2faa26b 100644 --- a/starboard/android/shared/video_decoder.cc +++ b/starboard/android/shared/video_decoder.cc
@@ -254,8 +254,8 @@ SB_DCHECK(tunnel_mode_audio_session_id != -1); SB_DCHECK(!drm_system_); drm_system_to_enforce_tunnel_mode_.reset(new DrmSystem( - nullptr, StubDrmSessionUpdateRequestFunc, StubDrmSessionUpdatedFunc, - StubDrmSessionKeyStatusesChangedFunc)); + "com.youtube.widevine.l3", nullptr, StubDrmSessionUpdateRequestFunc, + StubDrmSessionUpdatedFunc, StubDrmSessionKeyStatusesChangedFunc)); drm_system_ = drm_system_to_enforce_tunnel_mode_.get(); }
diff --git a/starboard/build/config/base_configuration.gni b/starboard/build/config/base_configuration.gni index 33b3763..ae010ae 100644 --- a/starboard/build/config/base_configuration.gni +++ b/starboard/build/config/base_configuration.gni
@@ -21,11 +21,11 @@ # Please follow the formatting in this file when adding new ones. declare_args() { - # Enables the yasm compiler to be used to compile .asm files. - yasm_exists = false + # Enables the nasm compiler to be used to compile .asm files. + nasm_exists = false # Where yasm can be found on the host device. - path_to_yasm = "yasm" + path_to_nasm = "nasm" # The Starboard API version of the current build configuration. The default # value is meant to be overridden by a Starboard ABI file.
diff --git a/starboard/build/nasm_assemble.gni b/starboard/build/nasm_assemble.gni index 9b23cd9..737db6c 100644 --- a/starboard/build/nasm_assemble.gni +++ b/starboard/build/nasm_assemble.gni
@@ -69,7 +69,7 @@ ]) # Flags. - args = [ "$path_to_yasm" ] + args = [ "$path_to_nasm" ] args += _nasm_flags if (defined(invoker.nasm_flags)) { args += invoker.nasm_flags @@ -105,9 +105,7 @@ } # Output file. - outputs = [ - "$root_out_dir/obj/third_party/libjpeg-turbo/{{source_name_part}}.asm.o", - ] + outputs = [ "$target_out_dir/$source_set_name/{{source_name_part}}.asm.o" ] args += [ "-o", rebase_path(outputs[0], root_build_dir),
diff --git a/starboard/evergreen/shared/platform_configuration/configuration.gni b/starboard/evergreen/shared/platform_configuration/configuration.gni index 5b331dc..a2b90ac 100644 --- a/starboard/evergreen/shared/platform_configuration/configuration.gni +++ b/starboard/evergreen/shared/platform_configuration/configuration.gni
@@ -18,7 +18,7 @@ cobalt_font_package = "empty" -yasm_exists = true +nasm_exists = true # Override that omits the "data" subdirectory. # TODO: Remove when omitted for all platforms in base_configuration.gni.
diff --git a/starboard/linux/shared/cobalt/configuration.py b/starboard/linux/shared/cobalt/configuration.py index 62d27f5..f415970 100644 --- a/starboard/linux/shared/cobalt/configuration.py +++ b/starboard/linux/shared/cobalt/configuration.py
@@ -17,6 +17,8 @@ from starboard.tools.testing import test_filter _FILTERED_TESTS = { + # Tracked by b/185820828 + 'net_unittests': ['SpdyNetworkTransactionTest.SpdyBasicAuth',], 'base_unittests': [ # Fails when run in a sharded configuration: b/233108722, b/216774170 'TaskQueueSelectorTest.TestHighestPriority',
diff --git a/starboard/linux/shared/platform_configuration/configuration.gni b/starboard/linux/shared/platform_configuration/configuration.gni index ef6eba3..e98ebe7 100644 --- a/starboard/linux/shared/platform_configuration/configuration.gni +++ b/starboard/linux/shared/platform_configuration/configuration.gni
@@ -14,7 +14,7 @@ import("//starboard/build/config/base_configuration.gni") -yasm_exists = true +nasm_exists = true sb_static_contents_output_data_dir = "$root_out_dir/content"
diff --git a/starboard/linux/x64x11/clang/3.9/platform_configuration/configuration.gni b/starboard/linux/x64x11/clang/3.9/platform_configuration/configuration.gni index e3b7857..188d57d 100644 --- a/starboard/linux/x64x11/clang/3.9/platform_configuration/configuration.gni +++ b/starboard/linux/x64x11/clang/3.9/platform_configuration/configuration.gni
@@ -14,7 +14,7 @@ import("//starboard/build/config/base_configuration.gni") -yasm_exists = true +nasm_exists = true sb_static_contents_output_data_dir = "$root_out_dir/content"
diff --git a/starboard/raspi/shared/launcher.py b/starboard/raspi/shared/launcher.py index ba6605b..844e9f9 100644 --- a/starboard/raspi/shared/launcher.py +++ b/starboard/raspi/shared/launcher.py
@@ -23,13 +23,15 @@ import sys import threading import time +import contextlib import pexpect from starboard.tools import abstract_launcher +from starboard.raspi.shared import retry # pylint: disable=unused-argument -def _SigIntOrSigTermHandler(signum, frame): +def _sigint_or_sigterm_handler(signum, frame): """Clean up and exit with status |signum|. Args: @@ -42,7 +44,7 @@ # First call returns True, otherwise return false. -def FirstRun(): +def first_run(): v = globals() if 'first_run' not in v: v['first_run'] = False @@ -64,16 +66,35 @@ # pexpect times out each second to allow Kill to quickly stop a test run _PEXPECT_TIMEOUT = 1 - # Wait up to 30 seconds for the password prompt from the raspi - _PEXPECT_PASSWORD_TIMEOUT_MAX_RETRIES = 30 + # SSH shell command retries + _PEXPECT_SPAWN_RETRIES = 20 + + # pexpect.sendline retries + _PEXPECT_SENDLINE_RETRIES = 3 + + # Old process kill retries + _KILL_RETRIES = 3 + + _PEXPECT_SHUTDOWN_SLEEP_TIME = 3 + # Time to wait after processes were killed + _PROCESS_KILL_SLEEP_TIME = 10 + + # Retrys for getting a clean prompt + _PROMPT_WAIT_MAX_RETRIES = 5 + # Wait up to 10 seconds for the password prompt from the raspi + _PEXPECT_PASSWORD_TIMEOUT_MAX_RETRIES = 10 # Wait up to 900 seconds for new output from the raspi _PEXPECT_READLINE_TIMEOUT_MAX_RETRIES = 900 # Delay between subsequent SSH commands - _INTER_COMMAND_DELAY_SECONDS = 0.5 + _INTER_COMMAND_DELAY_SECONDS = 1.5 # This is used to strip ansi color codes from pexpect output. _PEXPECT_SANITIZE_LINE_RE = re.compile(r'\x1b[^m]*m') + # Exceptions to retry + _RETRY_EXCEPTIONS = (pexpect.TIMEOUT, pexpect.ExceptionPexpect, + pexpect.exceptions.EOF, OSError) + def __init__(self, platform, target_name, config, device_id, **kwargs): # pylint: disable=super-with-arguments super(Launcher, self).__init__(platform, target_name, config, device_id, @@ -101,14 +122,17 @@ self.log_targets = kwargs.get('log_targets', True) - signal.signal(signal.SIGINT, functools.partial(_SigIntOrSigTermHandler)) - signal.signal(signal.SIGTERM, functools.partial(_SigIntOrSigTermHandler)) + signal.signal(signal.SIGINT, functools.partial(_sigint_or_sigterm_handler)) + signal.signal(signal.SIGTERM, functools.partial(_sigint_or_sigterm_handler)) self.last_run_pexpect_cmd = '' def _InitPexpectCommands(self): """Initializes all of the pexpect commands needed for running the test.""" + # Ensure no trailing slashes + self.out_directory = self.out_directory.rstrip('/') + # TODO(b/218889313): This should reference the bin/ subdir when that's # used. test_dir = os.path.join(self.out_directory, 'install', self.target_name) @@ -155,6 +179,18 @@ self.test_command = (f'{test_base_command} {test_success_output}' f'{test_failure_output}') + # pylint: disable=no-method-argument + def _CommandBackoff(): + time.sleep(Launcher._INTER_COMMAND_DELAY_SECONDS) + + def _ShutdownBackoff(self): + Launcher._CommandBackoff() + return self.shutdown_initiated.is_set() + + @retry.retry( + exceptions=_RETRY_EXCEPTIONS, + retries=_PEXPECT_SPAWN_RETRIES, + backoff=_CommandBackoff) def _PexpectSpawnAndConnect(self, command): """Spawns a process with pexpect and connect to the raspi. @@ -168,70 +204,67 @@ command, timeout=Launcher._PEXPECT_TIMEOUT, **kwargs) # Let pexpect output directly to our output stream self.pexpect_process.logfile_read = self.output_file - retry_count = 0 expected_prompts = [ r'.*Are\syou\ssure.*', # Fingerprint verification r'.* password:', # Password prompt '.*[a-zA-Z]+.*', # Any other text input ] - while True: - try: - i = self.pexpect_process.expect(expected_prompts) - if i == 0: - self._PexpectSendLine('yes') - elif i == 1: - self._PexpectSendLine(Launcher._RASPI_PASSWORD) - break - else: - # If any other input comes in, maybe we've logged in with rsa key or - # raspi does not have password. Check if we've logged in by echoing - # a special sentence and expect it back. - self._PexpectSendLine('echo ' + Launcher._SSH_LOGIN_SIGNAL) - i = self.pexpect_process.expect([Launcher._SSH_LOGIN_SIGNAL]) - break - except pexpect.TIMEOUT: - if self.shutdown_initiated.is_set(): - return - retry_count += 1 - # Check if the max retry count has been exceeded. If it has, then - # re-raise the timeout exception. - if retry_count > Launcher._PEXPECT_PASSWORD_TIMEOUT_MAX_RETRIES: - raise + # pylint: disable=unnecessary-lambda + @retry.retry( + exceptions=Launcher._RETRY_EXCEPTIONS, + retries=Launcher._PEXPECT_PASSWORD_TIMEOUT_MAX_RETRIES, + backoff=lambda: self._ShutdownBackoff(), + wrap_exceptions=False) + def _inner(): + i = self.pexpect_process.expect(expected_prompts) + if i == 0: + self._PexpectSendLine('yes') + elif i == 1: + self._PexpectSendLine(Launcher._RASPI_PASSWORD) + else: + # If any other input comes in, maybe we've logged in with rsa key or + # raspi does not have password. Check if we've logged in by echoing + # a special sentence and expect it back. + self._PexpectSendLine('echo ' + Launcher._SSH_LOGIN_SIGNAL) + i = self.pexpect_process.expect([Launcher._SSH_LOGIN_SIGNAL]) + + _inner() + + @retry.retry( + exceptions=_RETRY_EXCEPTIONS, + retries=_PEXPECT_SENDLINE_RETRIES, + wrap_exceptions=False) def _PexpectSendLine(self, cmd): """Send lines to Pexpect and record the last command for logging purposes""" + logging.info('sending >> : %s ', cmd) self.last_run_pexpect_cmd = cmd self.pexpect_process.sendline(cmd) def _PexpectReadLines(self): """Reads all lines from the pexpect process.""" - - retry_count = 0 - while True: - try: + # pylint: disable=unnecessary-lambda + @retry.retry( + exceptions=Launcher._RETRY_EXCEPTIONS, + retries=Launcher._PEXPECT_READLINE_TIMEOUT_MAX_RETRIES, + backoff=lambda: self.shutdown_initiated.is_set(), + wrap_exceptions=False) + def _readloop(): + while True: # Sanitize the line to remove ansi color codes. line = Launcher._PEXPECT_SANITIZE_LINE_RE.sub( '', self.pexpect_process.readline()) self.output_file.flush() if not line: - break + return # Check for the test complete tag. It will be followed by either a # success or failure tag. if line.startswith(self.test_complete_tag): if line.find(self.test_success_tag) != -1: self.return_value = 0 - break - # A line was successfully read without timing out; reset the retry - # count before attempting to read the next line. - retry_count = 0 - except pexpect.TIMEOUT: - if self.shutdown_initiated.is_set(): return - retry_count += 1 - # Check if the max retry count has been exceeded. If it has, then - # re-raise the timeout exception. - if retry_count > Launcher._PEXPECT_READLINE_TIMEOUT_MAX_RETRIES: - raise + + _readloop() def _Sleep(self, val): self._PexpectSendLine(f'sleep {val};echo {Launcher._SSH_SLEEP_SIGNAL}') @@ -244,39 +277,38 @@ # Check if kernel logged OOM kill or any other system failure message if self.return_value: logging.info('Sending dmesg') - self._PexpectSendLine('dmesg -P --color=never | tail -n 100') - time.sleep(3) - try: + with contextlib.suppress(Launcher._RETRY_EXCEPTIONS): + self._PexpectSendLine('dmesg -P --color=never | tail -n 100') + time.sleep(self._PEXPECT_SHUTDOWN_SLEEP_TIME) + with contextlib.suppress(Launcher._RETRY_EXCEPTIONS): self.pexpect_process.readlines() - except pexpect.TIMEOUT: - logging.info('Timeout exception during cleanup command: %s', - self.last_run_pexpect_cmd) - pass logging.info('Done sending dmesg') # Send ctrl-c to the raspi and close the process. - self._PexpectSendLine(chr(3)) - time.sleep(1) # Allow a second for normal shutdown - self.pexpect_process.close() + with contextlib.suppress(Launcher._RETRY_EXCEPTIONS): + self._PexpectSendLine(chr(3)) + time.sleep(self._PEXPECT_TIMEOUT) # Allow time for normal shutdown + with contextlib.suppress(Launcher._RETRY_EXCEPTIONS): + self.pexpect_process.close() def _WaitForPrompt(self): """Sends empty commands, until a bash prompt is returned""" - retry_count = 5 - while True: - try: - self.pexpect_process.expect(self._RASPI_PROMPT) - break - except pexpect.TIMEOUT: - logging.info('Timeout exception during WaitForPrompt command: %s', - self.last_run_pexpect_cmd) - if self.shutdown_initiated.is_set(): - return - retry_count -= 1 - if not retry_count: - raise - self._PexpectSendLine('echo ' + Launcher._SSH_SLEEP_SIGNAL) - time.sleep(self._INTER_COMMAND_DELAY_SECONDS) + def backoff(): + self._PexpectSendLine('echo ' + Launcher._SSH_SLEEP_SIGNAL) + return self._ShutdownBackoff() + + retry.with_retry( + lambda: self.pexpect_process.expect(self._RASPI_PROMPT), + exceptions=Launcher._RETRY_EXCEPTIONS, + retries=Launcher._PROMPT_WAIT_MAX_RETRIES, + backoff=backoff, + wrap_exceptions=False) + + @retry.retry( + exceptions=_RETRY_EXCEPTIONS, + retries=_KILL_RETRIES, + backoff=_CommandBackoff) def _KillExistingCobaltProcesses(self): """If there are leftover Cobalt processes, kill them. @@ -295,7 +327,7 @@ logging.warning('Forced to pkill existing instance(s) of cobalt. ' 'Pausing to ensure no further operations are run ' 'before processes shut down.') - time.sleep(10) + time.sleep(Launcher._PROCESS_KILL_SLEEP_TIME) logging.info('Done killing existing processes') def Run(self): @@ -331,12 +363,19 @@ if self.test_result_xml_path: first_run_commands.append(f'touch {self.test_result_xml_path}') first_run_commands.extend(['free -mh', 'ps -ux', 'df -h']) - if FirstRun(): + if first_run(): for cmd in first_run_commands: if not self.shutdown_initiated.is_set(): self._PexpectSendLine(cmd) - line = self.pexpect_process.readline() - self.output_file.write(line) + + def _readline(): + line = self.pexpect_process.readline() + self.output_file.write(line) + + retry.with_retry( + _readline, + exceptions=Launcher._RETRY_EXCEPTIONS, + retries=Launcher._PROMPT_WAIT_MAX_RETRIES) self._WaitForPrompt() self.output_file.flush() self._Sleep(self._INTER_COMMAND_DELAY_SECONDS) @@ -347,6 +386,9 @@ self._PexpectSendLine(self.test_command) self._PexpectReadLines() + except retry.RetriesExceeded: + logging.exception('Command retry exceeded (cmd: %s)', + self.last_run_pexpect_cmd) except pexpect.EOF: logging.exception('pexpect encountered EOF while reading line. (cmd: %s)', self.last_run_pexpect_cmd) @@ -378,7 +420,7 @@ # Initiate the shutdown. This causes the run to abort within one second. self.shutdown_initiated.set() # Wait up to three seconds for the run to be set to inactive. - self.run_inactive.wait(3) + self.run_inactive.wait(Launcher._PEXPECT_SHUTDOWN_SLEEP_TIME) def GetDeviceIp(self): """Gets the device IP."""
diff --git a/starboard/raspi/shared/launcher_test.py b/starboard/raspi/shared/launcher_test.py new file mode 100644 index 0000000..5fe6794 --- /dev/null +++ b/starboard/raspi/shared/launcher_test.py
@@ -0,0 +1,208 @@ +# +# Copyright 2023 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 Raspi launcher""" + +import logging +from starboard.raspi.shared import launcher +import sys +import argparse +import unittest +import os +from unittest.mock import patch, ANY, call, Mock +import tempfile +from pathlib import Path +import pexpect + +# pylint: disable=missing-class-docstring + + +class LauncherTest(unittest.TestCase): + + def setUp(self): + self.target = 'baz' + self.device_id = '198.51.100.1' # Reserved address + # Current launcher requires real files, so we generate one + # pylint: disable=consider-using-with + self.tmpdir = tempfile.TemporaryDirectory() + target_path = os.path.join(self.tmpdir.name, 'install', self.target) + os.makedirs(target_path) + Path(os.path.join(target_path, self.target)).touch() + # Minimal set of params required to crete one + self.params = { + 'device_id': self.device_id, + 'platform': 'raspi-2', + 'target_name': self.target, + 'config': 'test', + 'out_directory': self.tmpdir.name + } + self.fake_timeout = 0.11 + + # pylint: disable=protected-access + def _make_launcher(self): + launcher.Launcher._PEXPECT_TIMEOUT = self.fake_timeout + launcher.Launcher._PEXPECT_PASSWORD_TIMEOUT_MAX_RETRIES = 0 + launcher.Launcher._PEXPECT_SHUTDOWN_SLEEP_TIME = 0.12 + launcher.Launcher._INTER_COMMAND_DELAY_SECONDS = 0.013 + launcher.Launcher._PEXPECT_READLINE_TIMEOUT_MAX_RETRIES = 2 + launch = launcher.Launcher(**self.params) + return launch + + +class LauncherAPITest(LauncherTest): + + def test_construct(self): + launch = self._make_launcher() + self.assertIsNotNone(launch) + self.assertEqual(launch.device_id, self.device_id) + self.assertEqual(launch.platform_name, 'raspi-2') + self.assertEqual(launch.target_name, self.target) + self.assertEqual(launch.config, 'test') + self.assertEqual(launch.out_directory, self.tmpdir.name) + + def test_run(self): + result = self._make_launcher().Run() + # Expect test failure + self.assertEqual(result, 1) + + def test_ip(self): + self.assertEqual(self._make_launcher().GetDeviceIp(), self.device_id) + + def test_output(self): + # The path is hardcoded in the launcher + self.assertEqual(self._make_launcher().GetDeviceOutputPath(), '/tmp') + + def test_kill(self): + self.assertIsNone(self._make_launcher().Kill()) + + +class StringContains(str): + + def __eq__(self, value): + return self in value + + +# Tests here test implementation details, rather than behavior. +# pylint: disable=protected-access +class LauncherInternalsTest(LauncherTest): + + def setUp(self): + super().setUp() + self.launch = self._make_launcher() + self.launch.pexpect_process = Mock( + spec_set=['expect', 'sendline', 'readline']) + + @patch('starboard.raspi.shared.launcher.pexpect.spawn') + def test_spawn(self, spawn): + mock_pexpect = spawn.return_value + self.launch._PexpectSpawnAndConnect('echo test') + spawn.assert_called_once_with('echo test', timeout=ANY, encoding=ANY) + mock_pexpect.sendline.assert_called_once_with( + 'echo cobalt-launcher-login-success') + mock_pexpect.expect.assert_any_call(['cobalt-launcher-login-success']) + + def test_sleep(self): + self.launch._Sleep(42) + self.launch.pexpect_process.sendline.assert_called_once_with( + 'sleep 42;echo cobalt-launcher-done-sleeping') + self.launch.pexpect_process.expect.assert_called_once_with( + ['cobalt-launcher-done-sleeping']) + + def test_waitforconnect(self): + self.launch._WaitForPrompt() + self.launch.pexpect_process.expect.assert_called_once_with( + 'pi@raspberrypi:') + + # trigger one timeout + self.launch.pexpect_process.expect = Mock( + side_effect=[pexpect.TIMEOUT(1), None]) + self.launch._WaitForPrompt() + self.launch.pexpect_process.expect.assert_has_calls([ + call('pi@raspberrypi:'), + call('pi@raspberrypi:'), + ]) + + # infinite timeout + self.launch.pexpect_process.expect = Mock(side_effect=pexpect.TIMEOUT(1)) + with self.assertRaises(pexpect.TIMEOUT): + self.launch._WaitForPrompt() + + def test_readlines(self): + # Return empty string + self.launch.pexpect_process.readline = Mock(return_value='') + self.launch._PexpectReadLines() + self.launch.pexpect_process.readline.assert_called_once() + self.assertIsNone(getattr(self.launch, 'return_value', None)) + + # Return default success tag + self.launch.pexpect_process.readline = Mock( + return_value=self.launch.test_complete_tag) + self.launch._PexpectReadLines() + self.launch.pexpect_process.readline.assert_called_once() + # This is a bug + self.assertIsNone(getattr(self.launch, 'return_value', None)) + + line = self.launch.test_complete_tag + self.launch.test_success_tag + self.launch.pexpect_process.readline = Mock(return_value=line) + self.launch._PexpectReadLines() + self.assertEqual(self.launch.return_value, 0) + + self.launch.pexpect_process.readline = Mock(side_effect=pexpect.TIMEOUT(1)) + with self.assertRaises(pexpect.TIMEOUT): + self.launch._PexpectReadLines() + + def test_readlines_multiple(self): + self.launch.pexpect_process.readline = Mock(side_effect=['abc', 'bbc', '']) + self.launch._PexpectReadLines() + self.assertEqual(3, self.launch.pexpect_process.readline.call_count) + + self.launch.pexpect_process.readline = Mock( + side_effect=['abc', 'bbc', '', 'none']) + self.launch._PexpectReadLines() + self.assertEqual(3, self.launch.pexpect_process.readline.call_count) + + def test_kill_processes(self): + self.launch._KillExistingCobaltProcesses() + self.launch.pexpect_process.sendline.assert_any_call( + StringContains('pkill')) + + @patch('starboard.raspi.shared.launcher.pexpect.spawn') + def test_run_with_mock(self, spawn): + pexpect_ = Mock() + pexpect_.readline = Mock(return_value='') + spawn.return_value = pexpect_ + self.launch.Run() + self.assertEqual(self.launch.return_value, 1) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('device_id') + parser.add_argument('--target', default='eztime_test') + parser.add_argument('--out_directory') + parser.add_argument('--config', default='devel') + parser.add_argument('--verbose', '-v', action='store_true') + args = parser.parse_args() + logging.basicConfig( + stream=sys.stdout, level=logging.DEBUG if args.verbose else logging.INFO) + path = os.path.join( + os.path.dirname(launcher.__file__), f'../../../out/raspi-2_{args.config}') + logging.info('path: %s', path) + launch_test = launcher.Launcher( + platform='raspi-2', + target_name=args.target, + config=args.config, + device_id=args.device_id, + out_directory=path) + launch_test.Run()
diff --git a/starboard/raspi/shared/retry.py b/starboard/raspi/shared/retry.py new file mode 100644 index 0000000..1e8fd0e --- /dev/null +++ b/starboard/raspi/shared/retry.py
@@ -0,0 +1,121 @@ +# +# Copyright 2023 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. +"""General retry wrapper module + +Allows retrying a function call either with a decorator or inline call. +This is a substitute for more comprehensive Python retry wrapper packages like +`tenacity`, `retry`, `backoff` and others. +The only reason this exists is that Python package deployment for on-device +tests cannot currently dynamically include dependencies. +TODO(b/279249837): Remove this and use an off the shelf package. +""" + +from typing import Sequence, Callable +import functools +import logging + + +class RetriesExceeded(RuntimeError): + """Exception recording retry failure conditions""" + + def __init__(self, retries: int, function: Callable, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.retries = retries + self.function = function + + def __str__(self) -> str: + callable_str = getattr(self.function, '__name__', repr(self.function)) + return (f'Retries exceeded while calling {callable_str}' + f' with max {self.retries}') + super().__str__() + + +def _retry_function(function: Callable, exceptions: Sequence, retries: int, + backoff: Callable, wrap_exceptions: bool): + current_retry = 0 + while current_retry <= retries: + try: + return function() + except exceptions as inner: + current_retry += 1 + logging.debug('Exception running %s, retry %d/%d', function, retry, + retries) + if current_retry > retries: + # If 0 retries were attempted, pass up original exception + if not retries or not wrap_exceptions: + raise + raise RetriesExceeded(retries, function) from inner + if backoff: + if backoff(): + raise StopIteration() from inner + + raise RuntimeError('Bug: we should never get here') + + +def with_retry(function: Callable, + args: tuple = (), + kwargs: dict = None, + exceptions: Sequence = (Exception,), + retries: int = 0, + backoff: Callable = None, + wrap_exceptions: bool = True): + """Call a function with retry on exception + + :param args: Called function positional args. + :param kwargs: Called function named args. + :param exceptions: Sequence of exception types that will be retried. + :param retries: Max retries attempted. + :param backoff: Optional backoff callable. Truthy return from callable + terminates the loop. + :param wrap_exceptions: If true ( default ) wrap underlying exceptions in + RetriesExceeded exception type + : + """ + return _retry_function( + functools.partial(function, *args, **(kwargs if kwargs else {})), + exceptions=exceptions, + retries=retries, + backoff=backoff, + wrap_exceptions=wrap_exceptions, + ) + + +def retry(exceptions: Sequence = (Exception,), + retries: int = 0, + backoff: Callable = None, + wrap_exceptions: bool = True): + """Decorator for self-retrying function on thrown exception + + :param exceptions: Sequence of exception types that will be retried. + :param retries: Max retries attempted. + :param backoff: Optional backoff callable. Truthy return from callable + terminates the loop. + :param wrap_exceptions: If true ( default ) wrap underlying exceptions in + RetriesExceeded exception type + """ + + def decorator(function): + + @functools.wraps(function) + def wrapper(*args, **kwargs): + return _retry_function( + functools.partial(function, *args, **kwargs), + exceptions=exceptions, + retries=retries, + backoff=backoff, + wrap_exceptions=wrap_exceptions) + + return wrapper + + return decorator
diff --git a/starboard/raspi/shared/retry_test.py b/starboard/raspi/shared/retry_test.py new file mode 100644 index 0000000..465edda --- /dev/null +++ b/starboard/raspi/shared/retry_test.py
@@ -0,0 +1,257 @@ +# +# Copyright 2023 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 `retry` module""" + +import unittest +from starboard.raspi.shared import retry +import argparse +import logging +import sys +import time +from enum import IntEnum + + +class Behavior(IntEnum): + OK = 0 + OS_ERROR = 1 + RUNTIME_ERROR = 2 + OTHER_ERROR = 3 + + +def _problem(param: int, caller=str): + logging.info('%s: param=%d', caller, param) + if param == Behavior.OS_ERROR: + raise OSError('OS made an oops') + if param == Behavior.RUNTIME_ERROR: + raise RuntimeError('Runtime oops') + if param == Behavior.OTHER_ERROR: + raise MemoryError('Download more RAM') + return 100 + param * 3 + + +def problem(param: int): + return _problem(param, 'undecorated problem') + + +@retry.retry(exceptions=(RuntimeError,), retries=1) +def decorated_runtimeerror(param: int): + return _problem(param, 'decorated with runtimeerror') + + +@retry.retry(exceptions=(OSError,), retries=1) +def decorated_oserror(param: int): + return _problem(param, 'decorated with oserror') + + +@retry.retry(exceptions=(OSError, RuntimeError), retries=1) +def decorated_both(param: int): + return _problem(param, 'decorated with oserror+runtimeerror') + + +@retry.retry( + exceptions=(OSError,), + retries=2, + backoff=lambda: (logging.info('sleeping 0.2'), time.sleep(0.2))) +def decorated_oserror_backoff_2(param: int): + return _problem(param, 'decorated with oserror, 2 retries and sleep backoff') + + +class RetryTest(unittest.TestCase): + + def setUp(self) -> None: + self.actual_calls = 0 + self.call_counter = 0 + return super().setUp() + + def problem(self, param): + self.actual_calls += 1 + return _problem(param, 'undecorated problem method') + + @retry.retry(exceptions=(OSError,), retries=1) + def decorated_os_problem(self, param): + self.actual_calls += 1 + return _problem(param, 'decorated problem method') + + @retry.retry(exceptions=(OSError,), retries=1, wrap_exceptions=False) + def decorated_os_problem_nowrap(self, param): + self.actual_calls += 1 + return _problem(param, 'decorated problem method, pass-through exceptions') + + @retry.retry(exceptions=(OSError,), retries=5) + def decorated_os_problem_3(self, param): + self.actual_calls += 1 + self.call_counter += 1 + if self.call_counter == 3: + return 200 + return _problem(param, 'decorated problem that succeeds on 3rd try') + + def test_ok_call_undecorated(self): + self.assertEqual(100, retry.with_retry(problem, (Behavior.OK,))) + self.assertEqual(100, retry.with_retry(self.problem, (Behavior.OK,))) + self.assertEqual(self.actual_calls, 1) + + def test_ok_call_decorated(self): + self.assertEqual(100, decorated_both(Behavior.OK)) + self.assertEqual(100, self.decorated_os_problem(Behavior.OK)) + self.assertEqual(self.actual_calls, 1) + + def test_retry_exceeds(self): + with self.assertRaises(OSError): + retry.with_retry(problem, (Behavior.OS_ERROR,), retries=0) + with self.assertRaises(retry.RetriesExceeded): + retry.with_retry(problem, (Behavior.OS_ERROR,), retries=1) + with self.assertRaises(retry.RetriesExceeded): + retry.with_retry(problem, (Behavior.OS_ERROR,), retries=50) + with self.assertRaises(retry.RetriesExceeded): + retry.with_retry(self.problem, (Behavior.OS_ERROR,), retries=1) + self.assertEqual(self.actual_calls, 2) + + def test_retry_exceeds_decorated(self): + with self.assertRaises(retry.RetriesExceeded): + decorated_oserror(Behavior.OS_ERROR) + with self.assertRaises(retry.RetriesExceeded): + self.decorated_os_problem(Behavior.OS_ERROR) + self.assertEqual(self.actual_calls, 2) + with self.assertRaises(retry.RetriesExceeded): + decorated_runtimeerror(Behavior.RUNTIME_ERROR) + + def test_other_exceptions_propagate(self): + with self.assertRaises(RuntimeError): + retry.with_retry( + problem, (Behavior.RUNTIME_ERROR,), + exceptions=(OSError, MemoryError), + retries=0) + with self.assertRaises(RuntimeError): + retry.with_retry( + problem, (Behavior.RUNTIME_ERROR,), + exceptions=(OSError, MemoryError), + retries=4) + with self.assertRaises(RuntimeError): + retry.with_retry( + self.problem, (Behavior.RUNTIME_ERROR,), + exceptions=(OSError, MemoryError), + retries=0) + self.assertEqual(self.actual_calls, 1) + with self.assertRaises(RuntimeError): + retry.with_retry( + self.problem, (Behavior.RUNTIME_ERROR,), + exceptions=(OSError, MemoryError), + retries=50) + self.assertEqual(self.actual_calls, 2) + + def test_original_exceptions(self): + with self.assertRaises(OSError): + retry.with_retry( + problem, (Behavior.OS_ERROR,), retries=0, wrap_exceptions=False) + with self.assertRaises(OSError): + retry.with_retry( + problem, (Behavior.OS_ERROR,), retries=1, wrap_exceptions=False) + with self.assertRaises(OSError): + retry.with_retry( + problem, (Behavior.OS_ERROR,), retries=50, wrap_exceptions=False) + with self.assertRaises(OSError): + self.decorated_os_problem_nowrap(Behavior.OS_ERROR) + + def test_call_can_succeed_1(self): + self.assertEqual(100, self.decorated_os_problem_3(Behavior.OK)) + self.assertEqual(self.actual_calls, 1) + + def test_call_can_succeed_2(self): + self.assertEqual(200, self.decorated_os_problem_3(Behavior.OS_ERROR)) + self.assertEqual(self.actual_calls, 3) + with self.assertRaises(RuntimeError): # ensure other errors still throw + self.decorated_os_problem_3(Behavior.RUNTIME_ERROR) + + def test_backoff_gets_called(self): + backoff_calls = 0 + + def inrcement(): + nonlocal backoff_calls + backoff_calls += 1 + + with self.assertRaises(RuntimeError): + retry.with_retry( + self.problem, (Behavior.RUNTIME_ERROR,), + exceptions=(OSError), + retries=2, + backoff=inrcement) + self.assertEqual(backoff_calls, 0) + with self.assertRaises(RuntimeError): + retry.with_retry( + self.problem, (Behavior.RUNTIME_ERROR,), + exceptions=(RuntimeError), + retries=2, + backoff=inrcement) + self.assertEqual(backoff_calls, 2) + + def test_backoff_terminates_loop(self): + with self.assertRaises(StopIteration): + retry.with_retry( + self.problem, (Behavior.RUNTIME_ERROR,), + exceptions=(RuntimeError), + retries=2, + backoff=lambda: True) + + def test_exception_has_details(self): + with self.assertRaises(retry.RetriesExceeded) as context: + retry.with_retry(problem, (Behavior.OS_ERROR,), retries=50) + self.assertIn('50', str(context.exception)) + self.assertIn('problem', str(context.exception)) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--verbose', '-v', action='store_true') + parser.add_argument('func_behavior', type=int) + parser.add_argument('--oserror', action='store_true') + parser.add_argument('--runtimeerror', action='store_true') + parser.add_argument('--retries', type=int, default=1) + parser.add_argument('--backoff', action='store_true') + parser.add_argument('--decorated', action='store_true') + args = parser.parse_args() + logging.basicConfig( + stream=sys.stdout, level=logging.DEBUG if args.verbose else logging.INFO) + exceptions = [] + if args.oserror: + exceptions.append(OSError) + if args.runtimeerror: + exceptions.append(RuntimeError) + backoff = None if not args.backoff else lambda: (print('Backoff'), + time.sleep(1)) + if not args.decorated: + if exceptions: + print( + retry.with_retry( + problem, (args.func_behavior,), + retries=args.retries, + exceptions=tuple(exceptions), + backoff=backoff)) + else: # default, accept all exceptions + print( + retry.with_retry( + problem, (args.func_behavior,), + retries=args.retries, + backoff=backoff)) + else: + if args.backoff: + print(decorated_oserror_backoff_2(args.func_behavior)) + elif args.oserror and args.runtimeerror: + print(decorated_both(args.func_behavior)) + elif args.oserror: + print(decorated_oserror(args.func_behavior)) + elif args.runtimeerror: + print(decorated_runtimeerror(args.func_behavior)) + else: + raise NotImplementedError('No test implemented for these args')
diff --git a/starboard/shared/crash/BUILD.gn b/starboard/shared/crash/BUILD.gn new file mode 100644 index 0000000..fe4e454 --- /dev/null +++ b/starboard/shared/crash/BUILD.gn
@@ -0,0 +1,20 @@ +# Copyright 2023 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("//third_party/protobuf/proto_library.gni") + +proto_library("crash_annotations") { + sources = [ "crash_annotations.proto" ] + generate_python = false +}
diff --git a/starboard/shared/crash/crash_annotations.proto b/starboard/shared/crash/crash_annotations.proto new file mode 100644 index 0000000..6b4c2c6 --- /dev/null +++ b/starboard/shared/crash/crash_annotations.proto
@@ -0,0 +1,24 @@ +// Copyright 2023 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. + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +package starboard.shared.crash; + +message CrashAnnotations { + // Key/value pairs that should be included in a crash report. + map<string, string> runtime_annotations = 1; +}
diff --git a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc index 4b21032..359a065 100644 --- a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc +++ b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
@@ -312,11 +312,11 @@ return; } -#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 - av_frame_ = ffmpeg_->av_frame_alloc(); -#else // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 - av_frame_ = ffmpeg_->avcodec_alloc_frame(); -#endif // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 + if (ffmpeg_->avcodec_version() > kAVCodecSupportsAvFrameAlloc) { + av_frame_ = ffmpeg_->av_frame_alloc(); + } else { + av_frame_ = ffmpeg_->avcodec_alloc_frame(); + } if (av_frame_ == NULL) { SB_LOG(ERROR) << "Unable to allocate audio frame"; TeardownCodec();
diff --git a/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc index ef15ed2..0c78c4c 100644 --- a/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc +++ b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc
@@ -479,12 +479,13 @@ return; } auto* packet = static_cast<AVPacket*>(ptr); -#if LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100 - GetDispatch()->av_packet_free(&packet); -#else - GetDispatch()->av_free_packet(packet); - GetDispatch()->av_free(packet); -#endif // LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100 + + if (GetDispatch()->avcodec_version() > kAVCodecSupportsAvPacketAlloc) { + GetDispatch()->av_packet_free(&packet); + } else { + GetDispatch()->av_free_packet(packet); + GetDispatch()->av_free(packet); + } } FFmpegDemuxerImpl<FFMPEG>::FFmpegDemuxerImpl( @@ -714,15 +715,15 @@ FFmpegDemuxerImpl<FFMPEG>::CreateScopedAVPacket() { ScopedAVPacket packet; -#if LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100 - packet.reset(GetDispatch()->av_packet_alloc()); -#else - // av_packet_alloc is not available. - packet.reset( - static_cast<AVPacket*>(GetDispatch()->av_malloc(sizeof(AVPacket)))); - memset(packet.get(), 0, sizeof(AVPacket)); - GetDispatch()->av_init_packet(packet.get()); -#endif // LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100 + if (GetDispatch()->avcodec_version() > kAVCodecSupportsAvPacketAlloc) { + packet.reset(GetDispatch()->av_packet_alloc()); + } else { + // av_packet_alloc is not available. + packet.reset( + static_cast<AVPacket*>(GetDispatch()->av_malloc(sizeof(AVPacket)))); + memset(packet.get(), 0, sizeof(AVPacket)); + GetDispatch()->av_init_packet(packet.get()); + } return packet; } @@ -775,13 +776,13 @@ continue; } -// This is a packet for a stream we don't care about. Unref it (clear the -// fields) and keep searching. -#if LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100 - GetDispatch()->av_packet_unref(packet.get()); -#else - GetDispatch()->av_free_packet(packet.get()); -#endif // LIBAVCODEC_VERSION_INT >= LIBAVCODEC_VERSION_57_100 + // This is a packet for a stream we don't care about. Unref it (clear the + // fields) and keep searching. + if (GetDispatch()->avcodec_version() > kAVCodecSupportsAvPacketAlloc) { + GetDispatch()->av_packet_unref(packet.get()); + } else { + GetDispatch()->av_free_packet(packet.get()); + } } SB_NOTREACHED(); @@ -919,9 +920,9 @@ const double aspect_ratio = video_stream->sample_aspect_ratio.num ? get_aspect_ratio(video_stream->sample_aspect_ratio) - : codec_context->sample_aspect_ratio.num - ? get_aspect_ratio(codec_context->sample_aspect_ratio) - : 0.0; + : codec_context->sample_aspect_ratio.num + ? get_aspect_ratio(codec_context->sample_aspect_ratio) + : 0.0; { double width = config->visible_rect_width; double height = config->visible_rect_height; @@ -976,7 +977,7 @@ codec_context->profile > #ifdef FF_PROFILE_HEVC_REXT FF_PROFILE_HEVC_REXT -#else // FF_PROFILE_HEVC_REXT +#else // FF_PROFILE_HEVC_REXT FF_PROFILE_HEVC_MAIN_STILL_PICTURE #endif // FF_PROFILE_HEVC_REXT ) &&
diff --git a/starboard/shared/ffmpeg/ffmpeg_dispatch.cc b/starboard/shared/ffmpeg/ffmpeg_dispatch.cc index 6344706..b858a1f 100644 --- a/starboard/shared/ffmpeg/ffmpeg_dispatch.cc +++ b/starboard/shared/ffmpeg/ffmpeg_dispatch.cc
@@ -17,6 +17,7 @@ #include "starboard/shared/ffmpeg/ffmpeg_dispatch.h" +#include "starboard/common/log.h" #include "starboard/common/mutex.h" #include "starboard/once.h" @@ -43,22 +44,19 @@ } void FFMPEGDispatch::FreeFrame(AVFrame** frame) { -#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 - av_frame_free(frame); -#else // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 - av_freep(frame); -#endif // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 + if (avcodec_version() > kAVCodecSupportsAvFrameAlloc) { + av_frame_free(frame); + } else { + av_freep(frame); + } } void FFMPEGDispatch::FreeContext(AVCodecContext** avctx) { -#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 - avcodec_free_context(avctx); -#else // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 - if (avctx->extradata_size) { - av_freep(&((*avctx)->extradata)); + if (avcodec_version() > kAVCodecSupportsAvcodecFreeContext) { + avcodec_free_context(avctx); + } else { + av_freep(avctx); } - av_freep(avctx); -#endif // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 } } // namespace ffmpeg
diff --git a/starboard/shared/ffmpeg/ffmpeg_dispatch.h b/starboard/shared/ffmpeg/ffmpeg_dispatch.h index 347566b..e26f6d1 100644 --- a/starboard/shared/ffmpeg/ffmpeg_dispatch.h +++ b/starboard/shared/ffmpeg/ffmpeg_dispatch.h
@@ -36,6 +36,15 @@ namespace shared { namespace ffmpeg { +// derived from AV_VERSION_INT(a, b, c) ((a)<<16 | (b)<<8 | (c)) +// https://github.com/FFmpeg/FFmpeg/blob/master/doc/APIchanges#L1981 +constexpr int kAVCodecSupportsAvFrameAlloc = 3616101; +constexpr int kAVCodecSupportsAvcodecFreeContext = 3620708; +constexpr int kAVCodecSupportsAvPacketAlloc = 3738724; +// https://github.com/libav/libav/blob/8e401dbe90cc77b1f3067a917d9fa48cefa3fcdb/libavutil/version.h +// AV_VERSION_INT(52, 8, 0) +constexpr int kAVUtilSupportsBufferCreate = 3409920; + class FFMPEGDispatch { public: FFMPEGDispatch(); @@ -58,9 +67,7 @@ void* (*av_malloc)(size_t size); void (*av_freep)(void* ptr); AVFrame* (*av_frame_alloc)(void); -#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 void (*av_frame_free)(AVFrame** frame); -#endif // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 void (*av_frame_unref)(AVFrame* frame); int (*av_samples_get_buffer_size)(int* linesize, int nb_channels, @@ -83,9 +90,7 @@ unsigned (*avcodec_version)(void); AVCodecContext* (*avcodec_alloc_context3)(const AVCodec* codec); -#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 void (*avcodec_free_context)(AVCodecContext** avctx); -#endif // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 AVCodec* (*avcodec_find_decoder)(int id); int (*avcodec_close)(AVCodecContext* avctx); int (*avcodec_open2)(AVCodecContext* avctx,
diff --git a/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc b/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc index 6fc39d1..987d42d 100644 --- a/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc +++ b/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc
@@ -247,39 +247,59 @@ void FFMPEGDispatchImpl::LoadSymbols() { SB_DCHECK(is_valid()); -// Load the desired symbols from the shared libraries. Note: If a symbol is -// listed as a '.text' entry in the output of 'objdump -T' on the shared -// library file, then it is directly available from it. + // Load the desired symbols from the shared libraries. Note: If a symbol is + // listed as a '.text' entry in the output of 'objdump -T' on the shared + // library file, then it is directly available from it. + char* errstr; + errstr = dlerror(); + if (errstr != NULL) { + SB_LOG(INFO) << "LoadSymbols - checking dlerror shows:" << errstr; + } #define INITSYMBOL(library, symbol) \ ffmpeg_->symbol = reinterpret_cast<decltype(FFMPEGDispatch::symbol)>( \ - dlsym(library, #symbol)); + dlsym(library, #symbol)); \ + errstr = dlerror(); \ + if (errstr != NULL) { \ + SB_LOG(INFO) << "Load Symbols ran into error:" << errstr; \ + } // Load symbols from the avutil shared library. INITSYMBOL(avutil_, avutil_version); SB_DCHECK(ffmpeg_->avutil_version); + SB_LOG(INFO) << "Opened avutil - version is:" << ffmpeg_->avutil_version() + << std::endl; INITSYMBOL(avutil_, av_malloc); INITSYMBOL(avutil_, av_freep); - INITSYMBOL(avutil_, av_frame_alloc); INITSYMBOL(avutil_, av_free); INITSYMBOL(avutil_, av_rescale_rnd); -#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 - INITSYMBOL(avutil_, av_frame_free); - INITSYMBOL(avutil_, av_dict_get); -#endif // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 - INITSYMBOL(avutil_, av_frame_unref); INITSYMBOL(avutil_, av_samples_get_buffer_size); INITSYMBOL(avutil_, av_opt_set_int); INITSYMBOL(avutil_, av_image_check_size); - INITSYMBOL(avutil_, av_buffer_create); + if (ffmpeg_->avutil_version() > kAVUtilSupportsBufferCreate) { + INITSYMBOL(avutil_, av_buffer_create); + } // Load symbols from the avcodec shared library. INITSYMBOL(avcodec_, avcodec_version); SB_DCHECK(ffmpeg_->avcodec_version); + SB_LOG(INFO) << "Opened libavcodec - version is:" + << ffmpeg_->avcodec_version() << std::endl; + + if (ffmpeg_->avcodec_version() > kAVCodecSupportsAvFrameAlloc) { + INITSYMBOL(avcodec_, av_frame_alloc); + INITSYMBOL(avcodec_, av_frame_unref); + INITSYMBOL(avcodec_, av_frame_free); + INITSYMBOL(avcodec_, av_dict_get); + } else { + INITSYMBOL(avcodec_, avcodec_alloc_frame); + INITSYMBOL(avcodec_, avcodec_get_frame_defaults); + } + INITSYMBOL(avcodec_, avcodec_alloc_context3); -#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 - INITSYMBOL(avcodec_, avcodec_free_context); -#endif // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 + if (ffmpeg_->avcodec_version() > kAVCodecSupportsAvcodecFreeContext) { + INITSYMBOL(avcodec_, avcodec_free_context); + } INITSYMBOL(avcodec_, avcodec_find_decoder); INITSYMBOL(avcodec_, avcodec_close); INITSYMBOL(avcodec_, avcodec_open2); @@ -287,18 +307,22 @@ INITSYMBOL(avcodec_, avcodec_decode_audio4); INITSYMBOL(avcodec_, avcodec_decode_video2); INITSYMBOL(avcodec_, avcodec_flush_buffers); - INITSYMBOL(avcodec_, avcodec_alloc_frame); - INITSYMBOL(avcodec_, avcodec_get_frame_defaults); INITSYMBOL(avcodec_, avcodec_align_dimensions2); - INITSYMBOL(avcodec_, av_packet_alloc); - INITSYMBOL(avcodec_, av_packet_free); - INITSYMBOL(avcodec_, av_packet_unref); - INITSYMBOL(avcodec_, avcodec_parameters_to_context); - INITSYMBOL(avcodec_, av_free_packet); + + if (ffmpeg_->avcodec_version() > kAVCodecSupportsAvPacketAlloc) { + INITSYMBOL(avcodec_, av_packet_alloc); + INITSYMBOL(avcodec_, av_packet_free); + INITSYMBOL(avcodec_, av_packet_unref); + INITSYMBOL(avcodec_, avcodec_parameters_to_context); + } else { + INITSYMBOL(avcodec_, av_free_packet); + } // Load symbols from the avformat shared library. INITSYMBOL(avformat_, avformat_version); SB_DCHECK(ffmpeg_->avformat_version); + SB_LOG(INFO) << "Opened libavformat - version is:" + << ffmpeg_->avformat_version() << std::endl; INITSYMBOL(avformat_, av_register_all); SB_DCHECK(ffmpeg_->av_register_all); INITSYMBOL(avformat_, av_read_frame);
diff --git a/starboard/shared/ffmpeg/ffmpeg_linked_dispatch_impl.cc b/starboard/shared/ffmpeg/ffmpeg_linked_dispatch_impl.cc index c3d2f52..b2fb431 100644 --- a/starboard/shared/ffmpeg/ffmpeg_linked_dispatch_impl.cc +++ b/starboard/shared/ffmpeg/ffmpeg_linked_dispatch_impl.cc
@@ -41,9 +41,9 @@ void LoadSymbols(FFMPEGDispatch* ffmpeg) { SB_DCHECK(ffmpeg->is_valid()); -// Load the desired symbols from the shared libraries. Note: If a symbol is -// listed as a '.text' entry in the output of 'objdump -T' on the shared -// library file, then it is directly available from it. + // Load the desired symbols from the shared libraries. Note: If a symbol is + // listed as a '.text' entry in the output of 'objdump -T' on the shared + // library file, then it is directly available from it. #define INITSYMBOL(symbol) \ ffmpeg->symbol = reinterpret_cast<decltype(FFMPEGDispatch::symbol)>(symbol);
diff --git a/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc b/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc index 3b6a9bb..8a7c898 100644 --- a/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc +++ b/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc
@@ -263,9 +263,11 @@ bool VideoDecoderImpl<FFMPEG>::DecodePacket(AVPacket* packet) { SB_DCHECK(packet != NULL); -#if LIBAVUTIL_VERSION_INT < LIBAVUTIL_VERSION_52_8 - ffmpeg_->avcodec_get_frame_defaults(av_frame_); -#endif // LIBAVUTIL_VERSION_INT < LIBAVUTIL_VERSION_52_8 + if (ffmpeg_->avcodec_version() > kAVCodecSupportsAvFrameAlloc) { + ffmpeg_->av_frame_unref(av_frame_); + } else { + ffmpeg_->avcodec_get_frame_defaults(av_frame_); + } int frame_decoded = 0; int decode_result = ffmpeg_->avcodec_decode_video2(codec_context_, av_frame_, &frame_decoded, packet);
diff --git a/starboard/shared/starboard/player/testdata/heaac.dmp.sha1 b/starboard/shared/starboard/player/testdata/heaac.dmp.sha1 index f8b1691..b29e1f2 100644 --- a/starboard/shared/starboard/player/testdata/heaac.dmp.sha1 +++ b/starboard/shared/starboard/player/testdata/heaac.dmp.sha1
@@ -1 +1 @@ -27ef8edb9991163ef5c391c60f959ef2cc334762 \ No newline at end of file +fd4777e4d51ec22837d642771f38406b02af7e7d
diff --git a/starboard/shared/starboard/player/testdata/sintel_329_ec3.dmp.sha1 b/starboard/shared/starboard/player/testdata/sintel_329_ec3.dmp.sha1 index 1afa457..16c6718 100644 --- a/starboard/shared/starboard/player/testdata/sintel_329_ec3.dmp.sha1 +++ b/starboard/shared/starboard/player/testdata/sintel_329_ec3.dmp.sha1
@@ -1 +1 @@ -e69024d74c0d49e75e0447ce10b45ac18abbd1cc +d9e8510d2457731e70884075b47af2fd35715172
diff --git a/starboard/shared/starboard/player/testdata/sintel_381_ac3.dmp.sha1 b/starboard/shared/starboard/player/testdata/sintel_381_ac3.dmp.sha1 index f7d8625..805d2c0 100644 --- a/starboard/shared/starboard/player/testdata/sintel_381_ac3.dmp.sha1 +++ b/starboard/shared/starboard/player/testdata/sintel_381_ac3.dmp.sha1
@@ -1 +1 @@ -3ab684539ae21c22b983e167a07d354eed545bc0 +90dac969ea01a51e3e90cd482ae76d909d123d98
diff --git a/starboard/win/win32/test_filters.py b/starboard/win/win32/test_filters.py index a14fd92..8219947 100644 --- a/starboard/win/win32/test_filters.py +++ b/starboard/win/win32/test_filters.py
@@ -41,6 +41,10 @@ 'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceForDestination/type_ipv6', 'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceNotLoopback/type_ipv6', 'SbSocketAddressTypes/SbSocketResolveTest.SunnyDayFiltered/filter_ipv6_type_ipv6', + 'SbSocketAddressTypes/SbSocketSetOptionsTest.RainyDayInvalidSocket/type_ipv4', + 'SbSocketAddressTypes/SbSocketSetOptionsTest.RainyDayInvalidSocket/type_ipv6', + # Flakiness is tracked in b/278276779. + 'Semaphore.ThreadTakesWait_TimeExpires', ], 'player_filter_tests': [ # These tests fail on our VMs for win-win32 builds due to missing
diff --git a/third_party/jinja2/bccache.py b/third_party/jinja2/bccache.py index 1e89b46..da6c477 100644 --- a/third_party/jinja2/bccache.py +++ b/third_party/jinja2/bccache.py
@@ -14,7 +14,7 @@ :copyright: (c) 2010 by the Jinja Team. :license: BSD. """ -from os import path, listdir, pardir +from os import path, listdir, pardir, sep import sys import marshal import tempfile @@ -175,9 +175,13 @@ """Return a cache bucket for the given template. All arguments are mandatory but filename may be `None`. """ + # TODO(b/263289873): To get correct GN outputs the file list of cached + # templates should be printed to a depfile. # Use the path to the template relative to the project root to get a - # consistent hash. - key = self.get_cache_key(name, path.relpath(filename, _REPOSITORY_ROOT)) + # consistent hash. To have hashes consistent across platforms we have to + # replace the path separator. + rel_path = path.relpath(filename, _REPOSITORY_ROOT) + key = self.get_cache_key(name, rel_path.replace(sep, '/')) checksum = self.get_source_checksum(source) bucket = Bucket(environment, key, checksum) self.load_bytecode(bucket)
diff --git a/third_party/libjpeg-turbo/BUILD.gn b/third_party/libjpeg-turbo/BUILD.gn index f1ff3f3..0630c11 100644 --- a/third_party/libjpeg-turbo/BUILD.gn +++ b/third_party/libjpeg-turbo/BUILD.gn
@@ -29,7 +29,7 @@ } } -if (yasm_exists && (current_cpu == "x86" || current_cpu == "x64")) { +if (nasm_exists && (current_cpu == "x86" || current_cpu == "x64")) { if (is_starboard) { import("//starboard/build/nasm_assemble.gni") } else { @@ -162,10 +162,10 @@ defines = [] } - if (current_cpu == "x86" && yasm_exists) { + if (current_cpu == "x86" && nasm_exists) { deps += [ ":simd_asm" ] sources = [ "simd/i386/jsimd.c" ] - } else if (current_cpu == "x64" && yasm_exists) { + } else if (current_cpu == "x64" && nasm_exists) { deps += [ ":simd_asm" ] sources = [ "simd/x86_64/jsimd.c" ] } else if ((current_cpu == "arm" || current_cpu == "arm64") && arm_use_neon) {
diff --git a/third_party/web_platform_tests/service-workers/service-worker/import-scripts-mime-types.https.html b/third_party/web_platform_tests/service-workers/service-worker/import-scripts-mime-types.https.html index 1679831..86a7e1f 100644 --- a/third_party/web_platform_tests/service-workers/service-worker/import-scripts-mime-types.https.html +++ b/third_party/web_platform_tests/service-workers/service-worker/import-scripts-mime-types.https.html
@@ -20,7 +20,15 @@ await wait_for_state(t, registration.installing, 'activated'); - serviceWorker = registration.active; + // TODO(b/234788479) Implement waiting for update worker state tasks in + // Install algorithm, otherwise the worker is activated too early + if (registration.active) { + serviceWorker = registration.active; + } else if (registration.waiting) { + serviceWorker = registration.waiting; + } else { + serviceWorker = registration.installing; + } }, 'Global setup'); promise_test(async t => {
diff --git a/third_party/web_platform_tests/service-workers/service-worker/import-scripts-updated-flag.https.html b/third_party/web_platform_tests/service-workers/service-worker/import-scripts-updated-flag.https.html index 09b4496..a658d07 100644 --- a/third_party/web_platform_tests/service-workers/service-worker/import-scripts-updated-flag.https.html +++ b/third_party/web_platform_tests/service-workers/service-worker/import-scripts-updated-flag.https.html
@@ -38,10 +38,18 @@ // other sub-tests in this file are declared synchronously, this test // will be the final test executed. promise_test(function(t) { - return registration.unregister(); - }); - - return registration.active; + return registration.unregister(); + }); + // TODO(b/234788479) Implement waiting for update worker state tasks in + // Install algorithm, otherwise the worker is activated too early + if (registration.active) { + serviceWorker = registration.active; + } else if (registration.waiting) { + serviceWorker = registration.waiting; + } else { + serviceWorker = registration.installing; + } + return serviceWorker; }); return register;
diff --git a/third_party/web_platform_tests/service-workers/service-worker/registration-service-worker-attributes.https.html b/third_party/web_platform_tests/service-workers/service-worker/registration-service-worker-attributes.https.html index f7b52d5..0891b10 100644 --- a/third_party/web_platform_tests/service-workers/service-worker/registration-service-worker-attributes.https.html +++ b/third_party/web_platform_tests/service-workers/service-worker/registration-service-worker-attributes.https.html
@@ -65,8 +65,10 @@ // immediately followed by setting active to null, which means by the // time the event loop turns and the Promise for statechange is // resolved, this will be gone. - assert_equals(registration.active, null, - 'active should be null after redundant'); + // For Cobalt, this is not the case. Setting active to null can happen + // a little bit later after the Update State change. + // assert_equals(registration.active, null, + // 'active should be null after redundant'); }); }, 'installing/waiting/active after registration'); </script>
diff --git a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-echo.py b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-echo.py index d38d660..7d92794 100644 --- a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-echo.py +++ b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-echo.py
@@ -1,6 +1,6 @@ def main(req, res): return ([ - (b'Cache-Control', b'no-cache, must-revalidate'), - (b'Pragma', b'no-cache'), - (b'Content-Type', b'application/javascript')], - b'echo_output = "%s";\n' % req.GET[b'msg']) + ('Cache-Control', 'no-cache, must-revalidate'), + ('Pragma', 'no-cache'), + ('Content-Type', 'application/javascript')], + 'echo_output = "%s";\n' % req.GET['msg'])
diff --git a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-get.py b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-get.py index ab7b84e..9e376bc 100644 --- a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-get.py +++ b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-get.py
@@ -1,6 +1,6 @@ def main(req, res): return ([ - (b'Cache-Control', b'no-cache, must-revalidate'), - (b'Pragma', b'no-cache'), - (b'Content-Type', b'application/javascript')], - b'%s = "%s";\n' % (req.GET[b'output'], req.GET[b'msg'])) + ('Cache-Control', 'no-cache, must-revalidate'), + ('Pragma', 'no-cache'), + ('Content-Type', 'application/javascript')], + '%s = "%s";\n' % (req.GET['output'], req.GET['msg']))
diff --git a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-mime-types-worker.js b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-mime-types-worker.js index d4f1f3e..2c585ac 100644 --- a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-mime-types-worker.js +++ b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-mime-types-worker.js
@@ -30,7 +30,7 @@ for (const mimeType of badMimeTypes) { test(() => { - assert_throws_dom( + assert_throws( 'NetworkError', () => { importScriptsWithMimeType(mimeType); }, `importScripts with ${mimeType ? 'bad' : 'no'} MIME type ${mimeType || ''} throws NetworkError`,
diff --git a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-version.py b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-version.py index cde2854..fcac3b0 100644 --- a/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-version.py +++ b/third_party/web_platform_tests/service-workers/service-worker/resources/import-scripts-version.py
@@ -11,7 +11,7 @@ now = (datetime.datetime.now() - epoch).total_seconds() return ([ - (b'Cache-Control', b'no-cache, must-revalidate'), - (b'Pragma', b'no-cache'), - (b'Content-Type', b'application/javascript')], - u'version = "%s";\n' % now) + ('Cache-Control', 'no-cache, must-revalidate'), + ('Pragma', 'no-cache'), + ('Content-Type', 'application/javascript')], + 'version = "%s";\n' % now)
diff --git a/third_party/web_platform_tests/service-workers/service-worker/resources/mime-type-worker.py b/third_party/web_platform_tests/service-workers/service-worker/resources/mime-type-worker.py index 92a602e..a16684d 100644 --- a/third_party/web_platform_tests/service-workers/service-worker/resources/mime-type-worker.py +++ b/third_party/web_platform_tests/service-workers/service-worker/resources/mime-type-worker.py
@@ -1,4 +1,4 @@ def main(request, response): - if b'mime' in request.GET: - return [(b'Content-Type', request.GET[b'mime'])], b"" - return [], b"" + if 'mime' in request.GET: + return [('Content-Type', request.GET['mime'])], "" + return [], ""
diff --git a/third_party/web_platform_tests/service-workers/service-worker/resources/test-helpers.sub.js b/third_party/web_platform_tests/service-workers/service-worker/resources/test-helpers.sub.js index 28b657a..7430152 100644 --- a/third_party/web_platform_tests/service-workers/service-worker/resources/test-helpers.sub.js +++ b/third_party/web_platform_tests/service-workers/service-worker/resources/test-helpers.sub.js
@@ -86,15 +86,7 @@ return new Promise(test.step_func(function(resolve) { var handler = test.step_func(function() { registration.removeEventListener('updatefound', handler); - // b/234788479 Implement waiting for update worker state tasks in - // Install algorithm, otherwise the worker is activated too early - if (registration.installing) { - resolve(registration.installing); - } else if (registration.waiting) { - resolve(registration.waiting); - } else { - resolve(registration.active); - } + resolve(registration.installing); }); registration.addEventListener('updatefound', handler); }));
diff --git a/third_party/web_platform_tests/service-workers/service-worker/update-no-cache-request-headers.https.html b/third_party/web_platform_tests/service-workers/service-worker/update-no-cache-request-headers.https.html index 6ebad4b..e2677bf 100644 --- a/third_party/web_platform_tests/service-workers/service-worker/update-no-cache-request-headers.https.html +++ b/third_party/web_platform_tests/service-workers/service-worker/update-no-cache-request-headers.https.html
@@ -25,12 +25,13 @@ await service_worker_unregister(t, scope); const registration = await navigator.serviceWorker.register( script, {scope, updateViaCache: 'none'}); - await wait_for_state(t, registration.installing, 'activated'); + // b/234788479 Implement waiting for update worker state tasks in + // Install algorithm, otherwise the worker is activated too early + await wait_for_state(t, get_newest_worker(registration), 'activated'); // Do an update. await registration.update(); - // Ask the new worker what the request headers were. const newWorker = registration.installing; const sawMessage = new Promise((resolve) => { navigator.serviceWorker.onmessage = (event) => {