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) => {