Import Cobalt 23.master.0.308707
diff --git a/cobalt/bindings/contexts.py b/cobalt/bindings/contexts.py index 9285877..fb03640 100644 --- a/cobalt/bindings/contexts.py +++ b/cobalt/bindings/contexts.py
@@ -244,7 +244,8 @@ not idl_type.is_callback_interface), 'Callback types not supported.' element_cobalt_type = self.idl_type_to_cobalt_type( self.resolve_typedef(result_idl_type)) - result = '::cobalt::script::Promise< %s >' % element_cobalt_type + result = 'std::unique_ptr<::cobalt::script::Promise< %s* > >' % ( + element_cobalt_type) return result def idl_union_type_to_cobalt(self, idl_type):
diff --git a/cobalt/bindings/v8c/templates/interface.cc.template b/cobalt/bindings/v8c/templates/interface.cc.template index a11e229..65f703c 100644 --- a/cobalt/bindings/v8c/templates/interface.cc.template +++ b/cobalt/bindings/v8c/templates/interface.cc.template
@@ -40,6 +40,7 @@ #include "cobalt/script/v8c/entry_scope.h" #include "cobalt/script/v8c/helpers.h" #include "cobalt/script/v8c/native_promise.h" +#include "cobalt/script/v8c/script_promise.h" #include "cobalt/script/v8c/type_traits.h" #include "cobalt/script/v8c/v8c_typed_arrays.h" #include "cobalt/script/v8c/v8c_data_view.h"
diff --git a/cobalt/black_box_tests/testdata/service_worker_test.js b/cobalt/black_box_tests/testdata/service_worker_test.js index 44ffc55..5b9e117 100644 --- a/cobalt/black_box_tests/testdata/service_worker_test.js +++ b/cobalt/black_box_tests/testdata/service_worker_test.js
@@ -53,10 +53,10 @@ self.clients.matchAll(options).then(function (clients) { console.log('(Expected) self.clients.matchAll():', clients.length, clients); for (var i = 0; i < clients.length; i++) { - console.log('Client with url', clients[i].url); - console.log('Client with frameType', clients[i].frameType); - console.log('Client with id', clients[i].id); - console.log('Client with type', clients[i].type); + console.log('Client with url', clients[i].url, + 'frameType', clients[i].frameType, + 'id', clients[i].id, + 'type', clients[i].type); } }, function (error) { console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error); @@ -106,10 +106,10 @@ console.log('(Expected) self.clients.matchAll():', clients.length, clients); // Note: This will return 0 clients if none are controlled so far. for (var i = 0; i < clients.length; i++) { - console.log('Client with url', clients[i].url); - console.log('Client with frameType', clients[i].frameType); - console.log('Client with id', clients[i].id); - console.log('Client with type', clients[i].type); + console.log('Client with url', clients[i].url, + 'frameType', clients[i].frameType, + 'id', clients[i].id, + 'type', clients[i].type); } }, function (error) { console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error); @@ -123,18 +123,18 @@ self.clients.matchAll(options).then(function (clients) { console.log('(Expected) self.clients.matchAll():', clients.length, clients); for (var i = 0; i < clients.length; i++) { - console.log('Client with url', clients[i].url); - console.log('Client with frameType', clients[i].frameType); - console.log('Client with id', clients[i].id); - console.log('Client with type', clients[i].type); + console.log('Client with url', clients[i].url, + 'frameType', clients[i].frameType, + 'id', clients[i].id, + 'type', clients[i].type); console.log('self.clients.get()'); self.clients.get(clients[i].id).then(function (client) { console.log('(Expected) self.clients.get():', client); - console.log('Client with url', client.url); - console.log('Client with frameType', client.frameType); - console.log('Client with id', client.id); - console.log('Client with type', client.type); + console.log('Client with url', client.url, + 'frameType', client.frameType, + 'id', client.id, + 'type', client.type); }, function (error) { console.log(`(Unexpected) self.clients.get(): ${error}`, error); });
diff --git a/cobalt/black_box_tests/testdata/service_worker_test_claimable.js b/cobalt/black_box_tests/testdata/service_worker_test_claimable.js index cc0fcde..21cb0d7 100644 --- a/cobalt/black_box_tests/testdata/service_worker_test_claimable.js +++ b/cobalt/black_box_tests/testdata/service_worker_test_claimable.js
@@ -27,37 +27,47 @@ }); } +function delay_promise(delay) { + return new Promise(function (resolve) { + setTimeout(resolve.bind(null), delay) + }); +} + self.oninstall = function (e) { console.log('oninstall event received', e); + e.waitUntil(delay_promise(500).then(() => console.log('Promised delay.'), () => console.log('\nPromised rejected.\n'))); } + self.onactivate = function (e) { console.log('onactivate event received', e); // Claim should pass here, since the state is activating. console.log('self.clients.claim()'); - self.clients.claim().then(function (result) { + e.waitUntil(self.clients.claim().then(function (result) { console.log('(Expected) self.clients.claim():', result); var options = { includeUncontrolled: false, type: 'window' }; - console.log('self.clients.matchAll(options)'); - self.clients.matchAll(options).then(function (clients) { - console.log('(Expected) self.clients.matchAll():', clients.length, clients); - for (var i = 0; i < clients.length; i++) { - console.log('Client with url', clients[i].url); - console.log('Client with frameType', clients[i].frameType); - console.log('Client with id', clients[i].id); - console.log('Client with type', clients[i].type); - clients[i].postMessage(`You have been claimed, client with id ${clients[i].id}`); - } - }, function (error) { - console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error); - }); + e.waitUntil(delay_promise(1000).then(function () { + console.log('self.clients.matchAll(options)'); + e.waitUntil(self.clients.matchAll(options).then(function (clients) { + console.log('(Expected) self.clients.matchAll():', clients.length, clients); + for (var i = 0; i < clients.length; i++) { + console.log('Client with url', clients[i].url, + 'frameType', clients[i].frameType, + 'id', clients[i].id, + 'type', clients[i].type); + clients[i].postMessage(`You have been claimed, client with id ${clients[i].id}`); + } + }, function (error) { + console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error); + })); + })); }, function (error) { console.log(`(Unexpected) self.clients.claim(): ${error}`, error); - }); + })); } console.log('self.registration', self.registration); @@ -73,10 +83,10 @@ self.clients.matchAll(options).then(function (clients) { console.log('(Expected) self.clients.matchAll():', clients.length, clients); for (var i = 0; i < clients.length; i++) { - console.log('Client with url', clients[i].url); - console.log('Client with frameType', clients[i].frameType); - console.log('Client with id', clients[i].id); - console.log('Client with type', clients[i].type); + console.log('Client with url', clients[i].url, + 'frameType', clients[i].frameType, + 'id', clients[i].id, + 'type', clients[i].type); } }, function (error) { console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error);
diff --git a/cobalt/browser/BUILD.gn b/cobalt/browser/BUILD.gn index 529dd2f..4c30f02 100644 --- a/cobalt/browser/BUILD.gn +++ b/cobalt/browser/BUILD.gn
@@ -155,6 +155,7 @@ "//cobalt/browser/memory_settings:browser_memory_settings", "//cobalt/browser/memory_tracker:memory_tracker_tool", "//cobalt/build:cobalt_build_id", + "//cobalt/cache", "//cobalt/configuration", "//cobalt/css_parser", "//cobalt/cssom",
diff --git a/cobalt/browser/application.cc b/cobalt/browser/application.cc index d710cae..5fa7318 100644 --- a/cobalt/browser/application.cc +++ b/cobalt/browser/application.cc
@@ -61,6 +61,7 @@ #include "cobalt/browser/switches.h" #include "cobalt/browser/user_agent_platform_info.h" #include "cobalt/browser/user_agent_string.h" +#include "cobalt/cache/cache.h" #include "cobalt/configuration/configuration.h" #include "cobalt/extension/crash_handler.h" #include "cobalt/extension/installation_manager.h" @@ -648,6 +649,9 @@ watchdog::Watchdog::CreateInstance(persistent_settings_.get()); DCHECK(watchdog); + cobalt::cache::Cache::GetInstance()->set_persistent_settings( + persistent_settings_.get()); + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); base::Optional<cssom::ViewportSize> requested_viewport_size = GetRequestedViewportSize(command_line); @@ -661,6 +665,7 @@ // Create the main components of our browser. BrowserModule::Options options(web_options); network_module_options.preferred_language = language; + network_module_options.persistent_settings = persistent_settings_.get(); options.persistent_settings = persistent_settings_.get(); options.command_line_auto_mem_settings = memory_settings::GetSettings(*command_line); @@ -860,7 +865,7 @@ #if SB_IS(EVERGREEN) updater_module_.get(), #endif - options, persistent_settings_.get())); + options)); UpdateUserAgent();
diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc index 8b7b1d4..79dd071 100644 --- a/cobalt/browser/browser_module.cc +++ b/cobalt/browser/browser_module.cc
@@ -219,16 +219,15 @@ } // namespace -BrowserModule::BrowserModule( - const GURL& url, base::ApplicationState initial_application_state, - base::EventDispatcher* event_dispatcher, - account::AccountManager* account_manager, - network::NetworkModule* network_module, +BrowserModule::BrowserModule(const GURL& url, + base::ApplicationState initial_application_state, + base::EventDispatcher* event_dispatcher, + account::AccountManager* account_manager, + network::NetworkModule* network_module, #if SB_IS(EVERGREEN) - updater::UpdaterModule* updater_module, + updater::UpdaterModule* updater_module, #endif - const Options& options, - persistent_storage::PersistentSettings* persistent_settings) + const Options& options) : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)), ALLOW_THIS_IN_INITIALIZER_LIST( weak_this_(weak_ptr_factory_.GetWeakPtr())), @@ -298,8 +297,7 @@ next_timeline_id_(1), current_splash_screen_timeline_id_(-1), current_main_web_module_timeline_id_(-1), - service_worker_registry_(network_module), - persistent_settings_(persistent_settings) { + service_worker_registry_(network_module) { TRACE_EVENT0("cobalt::browser", "BrowserModule::BrowserModule()"); // Apply platform memory setting adjustments and defaults. @@ -2075,7 +2073,7 @@ dom_settings->window()->navigator()->user_agent_data(); h5vcc_settings.global_environment = dom_settings->context()->global_environment(); - h5vcc_settings.persistent_settings = persistent_settings_; + h5vcc_settings.persistent_settings = options_.persistent_settings; auto* h5vcc_object = new h5vcc::H5vcc(h5vcc_settings); if (!web_module_created_callback_.is_null()) {
diff --git a/cobalt/browser/browser_module.h b/cobalt/browser/browser_module.h index 0706039..a12551e 100644 --- a/cobalt/browser/browser_module.h +++ b/cobalt/browser/browser_module.h
@@ -131,8 +131,7 @@ #if SB_IS(EVERGREEN) updater::UpdaterModule* updater_module, #endif - const Options& options, - persistent_storage::PersistentSettings* persistent_settings); + const Options& options); ~BrowserModule(); std::string GetUserAgent() { return network_module_->GetUserAgent(); } @@ -724,8 +723,6 @@ // Manages the Service Workers. ServiceWorkerRegistry service_worker_registry_; - - persistent_storage::PersistentSettings* persistent_settings_; }; } // namespace browser
diff --git a/cobalt/build/ninja/README b/cobalt/build/ninja/README deleted file mode 100644 index fe0f765..0000000 --- a/cobalt/build/ninja/README +++ /dev/null
@@ -1,7 +0,0 @@ -ninja-win.exe built with MSVC 2012 -from -https://github.com/REDACTED/ninja/commit/1287257fe343c4b6b0f760308a7bec52d86b0edf - -ninja-linux built with gcc -from -https://github.com/REDACTED/ninja/commit/613d89893e56acc8c3670a66a33fcd2caf7d9050
diff --git a/cobalt/build/ninja/ninja-linux b/cobalt/build/ninja/ninja-linux deleted file mode 100755 index 1f30022..0000000 --- a/cobalt/build/ninja/ninja-linux +++ /dev/null Binary files differ
diff --git a/cobalt/build/ninja/ninja-linux32.armv7l b/cobalt/build/ninja/ninja-linux32.armv7l deleted file mode 100644 index cb4c3ec..0000000 --- a/cobalt/build/ninja/ninja-linux32.armv7l +++ /dev/null Binary files differ
diff --git a/cobalt/build/ninja/ninja-win.exe b/cobalt/build/ninja/ninja-win.exe deleted file mode 100755 index 4a68328..0000000 --- a/cobalt/build/ninja/ninja-win.exe +++ /dev/null Binary files differ
diff --git a/cobalt/cache/BUILD.gn b/cobalt/cache/BUILD.gn index 7fe8ddd..edc1daf 100644 --- a/cobalt/cache/BUILD.gn +++ b/cobalt/cache/BUILD.gn
@@ -22,6 +22,7 @@ "//base", "//cobalt/base", "//cobalt/configuration", + "//cobalt/persistent_storage:persistent_settings", "//net", "//starboard:starboard_headers_only", ]
diff --git a/cobalt/cache/cache.cc b/cobalt/cache/cache.cc index 0600895..e2c568d 100644 --- a/cobalt/cache/cache.cc +++ b/cobalt/cache/cache.cc
@@ -24,6 +24,8 @@ #include "base/optional.h" #include "cobalt/configuration/configuration.h" #include "cobalt/extension/javascript_cache.h" +#include "cobalt/persistent_storage/persistent_settings.h" +#include "net/disk_cache/cobalt/cobalt_backend_impl.h" #include "starboard/configuration_constants.h" #include "starboard/system.h" @@ -86,14 +88,6 @@ return nullptr; } -bool CanCache(disk_cache::ResourceType resource_type, uint32_t data_size) { - return cobalt::configuration::Configuration::GetInstance() - ->CobaltCanStoreCompiledJavascript() && - data_size > 0u && - data_size >= GetMinSizeToCacheInBytes(resource_type) && - data_size <= GetMaxCacheStorageInBytes(resource_type); -} - } // namespace namespace cobalt { @@ -158,6 +152,19 @@ return data; } +void Cache::set_enabled(bool enabled) { enabled_ = enabled; } + +void Cache::set_persistent_settings( + persistent_storage::PersistentSettings* persistent_settings) { + persistent_settings_ = persistent_settings; + + // Guaranteed to be called before any calls to Retrieve() + // since set_persistent_settings() is called from the Application() + // constructor before the NetworkModule is initialized. + set_enabled(persistent_settings_->GetPersistentSettingAsBool( + disk_cache::kCacheEnabledPersistentSettingsKey, true)); +} + MemoryCappedDirectory* Cache::GetMemoryCappedDirectory( disk_cache::ResourceType resource_type) { base::AutoLock auto_lock(lock_); @@ -221,5 +228,15 @@ } } +bool Cache::CanCache(disk_cache::ResourceType resource_type, + uint32_t data_size) { + return enabled_ && + cobalt::configuration::Configuration::GetInstance() + ->CobaltCanStoreCompiledJavascript() && + data_size > 0u && + data_size >= GetMinSizeToCacheInBytes(resource_type) && + data_size <= GetMaxCacheStorageInBytes(resource_type); +} + } // namespace cache } // namespace cobalt
diff --git a/cobalt/cache/cache.h b/cobalt/cache/cache.h index 790da73..4f9e4fe 100644 --- a/cobalt/cache/cache.h +++ b/cobalt/cache/cache.h
@@ -27,6 +27,7 @@ #include "base/synchronization/lock.h" #include "base/synchronization/waitable_event.h" #include "cobalt/cache/memory_capped_directory.h" +#include "cobalt/persistent_storage/persistent_settings.h" #include "net/disk_cache/cobalt/resource_type.h" namespace base { @@ -45,6 +46,11 @@ disk_cache::ResourceType resource_type, uint32_t key, std::function<std::unique_ptr<std::vector<uint8_t>>()> generate); + void set_enabled(bool enabled); + + void set_persistent_settings( + persistent_storage::PersistentSettings* persistent_settings); + private: friend struct base::DefaultSingletonTraits<Cache>; Cache() {} @@ -56,6 +62,7 @@ void Notify(disk_cache::ResourceType resource_type, uint32_t key); void TryStore(disk_cache::ResourceType resource_type, uint32_t key, const std::vector<uint8_t>& data); + bool CanCache(disk_cache::ResourceType resource_type, uint32_t data_size); mutable base::Lock lock_; // The following map is only used when the JavaScript cache extension is @@ -65,6 +72,9 @@ std::map<disk_cache::ResourceType, std::map<uint32_t, std::vector<base::WaitableEvent*>>> pending_; + bool enabled_; + + persistent_storage::PersistentSettings* persistent_settings_; DISALLOW_COPY_AND_ASSIGN(Cache); }; // class Cache
diff --git a/cobalt/evergreen_tests/evergreen_tests.py b/cobalt/evergreen_tests/evergreen_tests.py index 74eafb0..00d23ae 100644 --- a/cobalt/evergreen_tests/evergreen_tests.py +++ b/cobalt/evergreen_tests/evergreen_tests.py
@@ -31,6 +31,7 @@ def _Exec(cmd, env=None): + """Executes a command in a subprocess and returns the result.""" try: msg = 'Executing:\n ' + ' '.join(cmd) logging.info(msg) @@ -46,31 +47,8 @@ return 1 -def main(): - arg_parser = argparse.ArgumentParser() - arg_parser.add_argument( - '--no-can_mount_tmpfs', - dest='can_mount_tmpfs', - action='store_false', - help='A temporary filesystem cannot be mounted on the target device.') - arg_parser.add_argument( - '--platform_under_test', - default=_DEFAULT_PLATFORM_UNDER_TEST, - help='The platform to run the tests on (e.g., linux or raspi).') - authentication_method = arg_parser.add_mutually_exclusive_group() - authentication_method.add_argument( - '--public-key-auth', - help='Public key authentication should be used with the remote device.', - action='store_true') - authentication_method.add_argument( - '--password-auth', - help='Password authentication should be used with the remote device.', - action='store_true') - command_line.AddLauncherArguments(arg_parser) - args = arg_parser.parse_args() - - log_level.InitializeLogging(args) - +def _RunTests(arg_parser, args, use_compressed_system_image): + """Runs an instance of the Evergreen tests for the provided configuration.""" launcher_params = command_line.CreateLauncherParams(arg_parser) # Creating an instance of the Evergreen abstract launcher implementation @@ -86,7 +64,8 @@ loader_platform=launcher_params.loader_platform, loader_config=launcher_params.loader_config, loader_target='loader_app', - loader_out_directory=launcher_params.loader_out_directory) + loader_out_directory=launcher_params.loader_out_directory, + use_compressed_library=use_compressed_system_image) # The automated tests use the |OUT| environment variable as the path to a # known directory structure containing the desired binaries. This path is @@ -118,10 +97,52 @@ command.append('-a') command.append('password') + if use_compressed_system_image: + command.append('-c') + command.append(args.platform_under_test) return _Exec(command, env) +def main(): + arg_parser = argparse.ArgumentParser() + arg_parser.add_argument( + '--no-can_mount_tmpfs', + dest='can_mount_tmpfs', + action='store_false', + help='A temporary filesystem cannot be mounted on the target device.') + arg_parser.add_argument( + '--platform_under_test', + default=_DEFAULT_PLATFORM_UNDER_TEST, + help='The platform to run the tests on (e.g., linux or raspi).') + authentication_method = arg_parser.add_mutually_exclusive_group() + authentication_method.add_argument( + '--public-key-auth', + help='Public key authentication should be used with the remote device.', + action='store_true') + authentication_method.add_argument( + '--password-auth', + help='Password authentication should be used with the remote device.', + action='store_true') + arg_parser.add_argument( + '--no-rerun_using_compressed_system_image', + dest='rerun_using_compressed_system_image', + action='store_false', + help='Do not run a second instance of the tests with a compressed system ' + 'image.') + command_line.AddLauncherArguments(arg_parser) + args = arg_parser.parse_args() + + log_level.InitializeLogging(args) + + uncompressed_system_image_result = _RunTests(arg_parser, args, False) + if args.rerun_using_compressed_system_image: + compressed_system_image_result = _RunTests(arg_parser, args, True) + return uncompressed_system_image_result or compressed_system_image_result + + return uncompressed_system_image_result + + if __name__ == '__main__': sys.exit(main())
diff --git a/cobalt/h5vcc/BUILD.gn b/cobalt/h5vcc/BUILD.gn index 246c396..9c5a145 100644 --- a/cobalt/h5vcc/BUILD.gn +++ b/cobalt/h5vcc/BUILD.gn
@@ -76,6 +76,7 @@ "//cobalt/base", "//cobalt/browser:browser_switches", "//cobalt/build:cobalt_build_id", + "//cobalt/cache", "//cobalt/configuration", "//cobalt/dom", "//cobalt/media",
diff --git a/cobalt/h5vcc/h5vcc_storage.cc b/cobalt/h5vcc/h5vcc_storage.cc index e9a9229..dbb6b3b 100644 --- a/cobalt/h5vcc/h5vcc_storage.cc +++ b/cobalt/h5vcc/h5vcc_storage.cc
@@ -19,10 +19,14 @@ #include "base/files/file_util.h" #include "base/values.h" +#include "cobalt/cache/cache.h" #include "cobalt/h5vcc/h5vcc_storage.h" #include "cobalt/persistent_storage/persistent_settings.h" #include "cobalt/storage/storage_manager.h" +#include "net/disk_cache/cobalt/cobalt_backend_impl.h" #include "net/disk_cache/cobalt/resource_type.h" +#include "net/http/http_cache.h" +#include "net/http/http_transaction_factory.h" #include "starboard/common/file.h" #include "starboard/common/string.h" @@ -31,6 +35,7 @@ namespace h5vcc { namespace { + const char kTestFileName[] = "cache_test_file.json"; const uint32 kWriteBufferSize = 1024 * 1024; @@ -69,7 +74,16 @@ network::NetworkModule* network_module, persistent_storage::PersistentSettings* persistent_settings) : network_module_(network_module), - persistent_settings_(persistent_settings) {} + persistent_settings_(persistent_settings) { + http_cache_ = nullptr; + if (network_module == nullptr) { + return; + } + auto url_request_context = network_module_->url_request_context(); + if (url_request_context->using_http_cache()) { + http_cache_ = url_request_context->http_transaction_factory()->GetCache(); + } +} void H5vccStorage::ClearCookies() { net::CookieStore* cookie_store = @@ -334,5 +348,29 @@ return quota; } +void H5vccStorage::EnableCache() { + persistent_settings_->SetPersistentSetting( + disk_cache::kCacheEnabledPersistentSettingsKey, + std::make_unique<base::Value>(true)); + + cobalt::cache::Cache::GetInstance()->set_enabled(true); + + if (http_cache_) { + http_cache_->set_mode(net::HttpCache::Mode::NORMAL); + } +} + +void H5vccStorage::DisableCache() { + persistent_settings_->SetPersistentSetting( + disk_cache::kCacheEnabledPersistentSettingsKey, + std::make_unique<base::Value>(false)); + + cobalt::cache::Cache::GetInstance()->set_enabled(false); + + if (http_cache_) { + http_cache_->set_mode(net::HttpCache::Mode::DISABLE); + } +} + } // namespace h5vcc } // namespace cobalt
diff --git a/cobalt/h5vcc/h5vcc_storage.h b/cobalt/h5vcc/h5vcc_storage.h index 7f6368b..ff96351 100644 --- a/cobalt/h5vcc/h5vcc_storage.h +++ b/cobalt/h5vcc/h5vcc_storage.h
@@ -26,6 +26,7 @@ #include "cobalt/network/network_module.h" #include "cobalt/persistent_storage/persistent_settings.h" #include "cobalt/script/wrappable.h" +#include "net/http/http_cache.h" namespace cobalt { namespace h5vcc { @@ -56,6 +57,10 @@ H5vccStorageSetQuotaResponse SetQuota( H5vccStorageResourceTypeQuotaBytesDictionary quota); + void EnableCache(); + + void DisableCache(); + DEFINE_WRAPPABLE_TYPE(H5vccStorage); private: @@ -63,6 +68,8 @@ persistent_storage::PersistentSettings* persistent_settings_; + net::HttpCache* http_cache_; + DISALLOW_COPY_AND_ASSIGN(H5vccStorage); };
diff --git a/cobalt/h5vcc/h5vcc_storage.idl b/cobalt/h5vcc/h5vcc_storage.idl index 3a45910..93a6d37 100644 --- a/cobalt/h5vcc/h5vcc_storage.idl +++ b/cobalt/h5vcc/h5vcc_storage.idl
@@ -24,4 +24,7 @@ H5vccStorageResourceTypeQuotaBytesDictionary getQuota(); H5vccStorageSetQuotaResponse setQuota(H5vccStorageResourceTypeQuotaBytesDictionary quota); + + void enableCache(); + void disableCache(); };
diff --git a/cobalt/network/BUILD.gn b/cobalt/network/BUILD.gn index a68d213..53821e9 100644 --- a/cobalt/network/BUILD.gn +++ b/cobalt/network/BUILD.gn
@@ -51,6 +51,7 @@ "//cobalt/build:cobalt_build_id", "//cobalt/configuration", "//cobalt/network_bridge", + "//cobalt/persistent_storage:persistent_settings", "//cobalt/storage", "//starboard/common", "//third_party/protobuf:protobuf_lite",
diff --git a/cobalt/network/network_module.cc b/cobalt/network/network_module.cc index 4eae9a8..f192961 100644 --- a/cobalt/network/network_module.cc +++ b/cobalt/network/network_module.cc
@@ -178,7 +178,8 @@ #endif url_request_context_.reset( new URLRequestContext(storage_manager_, options_.custom_proxy, net_log, - options_.ignore_certificate_errors, task_runner())); + options_.ignore_certificate_errors, task_runner(), + options_.persistent_settings)); network_delegate_.reset( new NetworkDelegate(options_.cookie_policy, options_.https_requirement)); url_request_context_->set_http_user_agent_settings(
diff --git a/cobalt/network/network_module.h b/cobalt/network/network_module.h index be655f9..ee04620 100644 --- a/cobalt/network/network_module.h +++ b/cobalt/network/network_module.h
@@ -28,6 +28,7 @@ #include "cobalt/network/network_delegate.h" #include "cobalt/network/url_request_context.h" #include "cobalt/network/url_request_context_getter.h" +#include "cobalt/persistent_storage/persistent_settings.h" #include "net/base/static_cookie_policy.h" #include "url/gurl.h" #if defined(DIAL_SERVER) @@ -60,13 +61,15 @@ ignore_certificate_errors(false), https_requirement(network::kHTTPSRequired), preferred_language("en-US"), - max_network_delay(0) {} + max_network_delay(0), + persistent_settings(nullptr) {} net::StaticCookiePolicy::Type cookie_policy; bool ignore_certificate_errors; HTTPSRequirement https_requirement; std::string preferred_language; std::string custom_proxy; SbTime max_network_delay; + persistent_storage::PersistentSettings* persistent_settings; }; // Simple constructor intended to be used only by tests.
diff --git a/cobalt/network/url_request_context.cc b/cobalt/network/url_request_context.cc index 020410c..34cc482 100644 --- a/cobalt/network/url_request_context.cc +++ b/cobalt/network/url_request_context.cc
@@ -26,12 +26,14 @@ #include "cobalt/network/persistent_cookie_store.h" #include "cobalt/network/proxy_config_service.h" #include "cobalt/network/switches.h" +#include "cobalt/persistent_storage/persistent_settings.h" #include "net/cert/cert_net_fetcher.h" #include "net/cert/cert_verifier.h" #include "net/cert/cert_verify_proc.h" #include "net/cert/ct_policy_enforcer.h" #include "net/cert/do_nothing_ct_verifier.h" #include "net/cert_net/cert_net_fetcher_impl.h" +#include "net/disk_cache/cobalt/cobalt_backend_impl.h" #include "net/dns/host_cache.h" #include "net/http/http_auth_handler_factory.h" #include "net/http/http_cache.h" @@ -68,7 +70,8 @@ URLRequestContext::URLRequestContext( storage::StorageManager* storage_manager, const std::string& custom_proxy, net::NetLog* net_log, bool ignore_certificate_errors, - scoped_refptr<base::SingleThreadTaskRunner> network_task_runner) + scoped_refptr<base::SingleThreadTaskRunner> network_task_runner, + persistent_storage::PersistentSettings* persistent_settings) : ALLOW_THIS_IN_INITIALIZER_LIST(storage_(this)) #if defined(ENABLE_DEBUGGER) , @@ -176,6 +179,8 @@ std::unique_ptr<net::HttpNetworkLayer>( new net::HttpNetworkLayer(storage_.http_network_session()))); } else { + using_http_cache_ = true; + // TODO: Set max size of cache in Starboard. const int cache_size_mb = 24; auto http_cache = std::make_unique<net::HttpCache>( @@ -185,6 +190,15 @@ base::FilePath(std::string(path.data())), /* max_bytes */ 1024 * 1024 * cache_size_mb), true); + if (persistent_settings != nullptr) { + auto cache_enabled = persistent_settings->GetPersistentSettingAsBool( + disk_cache::kCacheEnabledPersistentSettingsKey, true); + + if (!cache_enabled) { + http_cache->set_mode(net::HttpCache::Mode::DISABLE); + } + } + storage_.set_http_transaction_factory(std::move(http_cache)); } @@ -214,6 +228,8 @@ storage_.http_network_session()->SetEnableQuic(enable_quic); } +bool URLRequestContext::using_http_cache() { return using_http_cache_; } + #if defined(ENABLE_DEBUGGER) void URLRequestContext::OnQuicToggle(const std::string& message) { DCHECK(storage_.http_network_session());
diff --git a/cobalt/network/url_request_context.h b/cobalt/network/url_request_context.h index 074fcdb..8f673c3 100644 --- a/cobalt/network/url_request_context.h +++ b/cobalt/network/url_request_context.h
@@ -20,6 +20,7 @@ #include "base/basictypes.h" #include "base/macros.h" #include "base/threading/thread_checker.h" +#include "cobalt/persistent_storage/persistent_settings.h" #include "net/cookies/cookie_monster.h" #include "net/log/net_log.h" #include "net/url_request/url_request_context.h" @@ -42,19 +43,24 @@ URLRequestContext( storage::StorageManager* storage_manager, const std::string& custom_proxy, net::NetLog* net_log, bool ignore_certificate_errors, - scoped_refptr<base::SingleThreadTaskRunner> network_task_runner); + scoped_refptr<base::SingleThreadTaskRunner> network_task_runner, + persistent_storage::PersistentSettings* persistent_settings); ~URLRequestContext() override; void SetProxy(const std::string& custom_proxy_rules); void SetEnableQuic(bool enable_quic); + bool using_http_cache(); + private: THREAD_CHECKER(thread_checker_); net::URLRequestContextStorage storage_; scoped_refptr<net::CookieMonster::PersistentCookieStore> persistent_cookie_store_; + bool using_http_cache_; + #if defined(ENABLE_DEBUGGER) // Command handler object for toggling the input fuzzer on/off. debug::console::ConsoleCommandManager::CommandHandler
diff --git a/cobalt/persistent_storage/persistent_settings.cc b/cobalt/persistent_storage/persistent_settings.cc index d420c0e..ffebc2a 100644 --- a/cobalt/persistent_storage/persistent_settings.cc +++ b/cobalt/persistent_storage/persistent_settings.cc
@@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "cobalt/persistent_storage/persistent_settings.h" + #include <utility> #include <vector> #include "base/values.h" -#include "cobalt/persistent_storage/persistent_settings.h" #include "starboard/common/file.h" #include "starboard/common/log.h" #include "starboard/configuration_constants.h"
diff --git a/cobalt/script/promise.h b/cobalt/script/promise.h index 6fa4b54..d9e1c17 100644 --- a/cobalt/script/promise.h +++ b/cobalt/script/promise.h
@@ -15,6 +15,9 @@ #ifndef COBALT_SCRIPT_PROMISE_H_ #define COBALT_SCRIPT_PROMISE_H_ +#include <memory> + +#include "base/callback.h" #include "base/memory/ref_counted.h" #include "cobalt/script/exception_message.h" #include "cobalt/script/script_exception.h" @@ -42,21 +45,19 @@ public: // Call the |resolve| function that was passed as an argument to the Promise's // executor function supplying |result| as its argument. - virtual void Resolve(const T& result) const { NOTREACHED(); } + virtual void Resolve(const T& result) const = 0; // Call the |reject| function passed as an argument to the Promise's executor // function. - virtual void Reject() const { NOTREACHED(); } - virtual void Reject(SimpleExceptionType exception) const { NOTREACHED(); } - virtual void Reject(const scoped_refptr<ScriptException>& result) const { - NOTREACHED(); - } + virtual void Reject() const = 0; + virtual void Reject(SimpleExceptionType exception) const = 0; + virtual void Reject(const scoped_refptr<ScriptException>& result) const = 0; // Returns the value of the [[PromiseState]] field. - virtual PromiseState State() const { - NOTREACHED(); - return PromiseState::kRejected; - } + virtual PromiseState State() const = 0; + + virtual void AddStateChangeCallback( + std::unique_ptr<base::OnceCallback<void()>> callback) = 0; virtual ~Promise() {} };
diff --git a/cobalt/script/v8c/conversion_helpers.h b/cobalt/script/v8c/conversion_helpers.h index 85aa597..81bf303 100644 --- a/cobalt/script/v8c/conversion_helpers.h +++ b/cobalt/script/v8c/conversion_helpers.h
@@ -16,7 +16,6 @@ #define COBALT_SCRIPT_V8C_CONVERSION_HELPERS_H_ #include <cmath> - #include <limits> #include <string> #include <utility> @@ -29,6 +28,7 @@ #include "cobalt/base/compiler.h" #include "cobalt/base/enable_if.h" #include "cobalt/base/token.h" +#include "cobalt/script/promise.h" #include "cobalt/script/sequence.h" #include "cobalt/script/v8c/algorithm_helpers.h" #include "cobalt/script/v8c/helpers.h" @@ -713,11 +713,7 @@ template <typename T> void FromJSValue(v8::Isolate* isolate, v8::Local<v8::Value> value, int conversion_flags, ExceptionState* exception_state, - script::Promise<T>* out_promise) { - // TODO(b/228976500): Implement conversion from JS to native for Promise<T>. - // https://webidl.spec.whatwg.org/#es-promise - NOTIMPLEMENTED(); -} + script::Promise<T>* out_promise); // script::Handle<T> -> JSValue template <typename T>
diff --git a/cobalt/script/v8c/native_promise.h b/cobalt/script/v8c/native_promise.h index 85069c7..ab8b323 100644 --- a/cobalt/script/v8c/native_promise.h +++ b/cobalt/script/v8c/native_promise.h
@@ -15,12 +15,15 @@ #ifndef COBALT_SCRIPT_V8C_NATIVE_PROMISE_H_ #define COBALT_SCRIPT_V8C_NATIVE_PROMISE_H_ +#include <memory> + #include "base/logging.h" #include "base/threading/thread_checker.h" #include "cobalt/script/promise.h" #include "cobalt/script/v8c/conversion_helpers.h" #include "cobalt/script/v8c/entry_scope.h" #include "cobalt/script/v8c/scoped_persistent.h" +#include "cobalt/script/v8c/script_promise.h" #include "cobalt/script/v8c/type_traits.h" #include "cobalt/script/v8c/v8c_exception_state.h" #include "cobalt/script/v8c/v8c_user_object_holder.h" @@ -40,10 +43,9 @@ *out_value = v8::Undefined(isolate); } -// Shared functionality for NativePromise<T>. Does not implement the Resolve -// function, since that needs to be specialized for Promise<T>. +// Shared functionality for NativePromise<T>. template <typename T> -class NativePromise : public ScopedPersistent<v8::Value>, public Promise<T> { +class NativePromise : public ScriptPromise<T> { public: // ScriptValue boilerplate. typedef Promise<T> BaseType; @@ -63,20 +65,20 @@ PromiseResultUndefined, T>::type; NativePromise(v8::Isolate* isolate, v8::Local<v8::Value> resolver) - : isolate_(isolate), ScopedPersistent(isolate, resolver) { + : ScriptPromise<T>(isolate, resolver) { DCHECK(resolver->IsPromise()); } void Resolve(const ResolveType& value) const override { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(!this->IsEmpty()); - DCHECK(State() == PromiseState::kPending); - EntryScope entry_scope(isolate_); - v8::Local<v8::Context> context = isolate_->GetCurrentContext(); + DCHECK(this->State() == PromiseState::kPending); + EntryScope entry_scope(this->isolate()); + v8::Local<v8::Context> context = this->isolate()->GetCurrentContext(); v8::Local<v8::Promise::Resolver> promise_resolver = this->resolver(); v8::Local<v8::Value> converted_value; - ToJSValue(isolate_, value, &converted_value); + ToJSValue(this->isolate(), value, &converted_value); v8::Maybe<bool> reject_result = promise_resolver->Resolve(context, converted_value); DCHECK(reject_result.FromJust()); @@ -85,25 +87,26 @@ void Reject() const override { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(!this->IsEmpty()); - DCHECK(State() == PromiseState::kPending); - EntryScope entry_scope(isolate_); - v8::Local<v8::Context> context = isolate_->GetCurrentContext(); + DCHECK(this->State() == PromiseState::kPending); + EntryScope entry_scope(this->isolate()); + v8::Local<v8::Context> context = this->isolate()->GetCurrentContext(); v8::Local<v8::Promise::Resolver> promise_resolver = this->resolver(); v8::Maybe<bool> reject_result = - promise_resolver->Reject(context, v8::Undefined(isolate_)); + promise_resolver->Reject(context, v8::Undefined(this->isolate())); DCHECK(reject_result.FromJust()); } void Reject(SimpleExceptionType exception) const override { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(!this->IsEmpty()); - DCHECK(State() == PromiseState::kPending); - EntryScope entry_scope(isolate_); - v8::Local<v8::Context> context = isolate_->GetCurrentContext(); + DCHECK(this->State() == PromiseState::kPending); + EntryScope entry_scope(this->isolate()); + v8::Local<v8::Context> context = this->isolate()->GetCurrentContext(); v8::Local<v8::Promise::Resolver> promise_resolver = this->resolver(); - v8::Local<v8::Value> error_result = CreateErrorObject(isolate_, exception); + v8::Local<v8::Value> error_result = + CreateErrorObject(this->isolate(), exception); v8::Maybe<bool> reject_result = promise_resolver->Reject(context, error_result); DCHECK(reject_result.FromJust()); @@ -112,51 +115,22 @@ void Reject(const scoped_refptr<ScriptException>& result) const override { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(!this->IsEmpty()); - DCHECK(State() == PromiseState::kPending); - EntryScope entry_scope(isolate_); - v8::Local<v8::Context> context = isolate_->GetCurrentContext(); + DCHECK(this->State() == PromiseState::kPending); + EntryScope entry_scope(this->isolate()); + v8::Local<v8::Context> context = this->isolate()->GetCurrentContext(); v8::Local<v8::Promise::Resolver> promise_resolver = this->resolver(); v8::Local<v8::Value> converted_result; - ToJSValue(isolate_, result, &converted_result); + ToJSValue(this->isolate(), result, &converted_result); v8::Maybe<bool> reject_result = promise_resolver->Reject(context, converted_result); DCHECK(reject_result.FromJust()); } - PromiseState State() const override { - DCHECK(!this->IsEmpty()); - EntryScope entry_scope(isolate_); - - v8::Promise::PromiseState v8_promise_state = this->promise()->State(); - switch (v8_promise_state) { - case v8::Promise::kPending: - return PromiseState::kPending; - case v8::Promise::kFulfilled: - return PromiseState::kFulfilled; - case v8::Promise::kRejected: - return PromiseState::kRejected; - } - NOTREACHED(); - return PromiseState::kRejected; - } - - v8::Local<v8::Promise> promise() const { - DCHECK(!this->IsEmpty()); - return resolver()->GetPromise(); - } - private: - v8::Isolate* isolate_; - // Thread checker ensures all calls to the Promise are made from the same // thread that it is created in. THREAD_CHECKER(thread_checker_); - - v8::Local<v8::Promise::Resolver> resolver() const { - DCHECK(!this->IsEmpty()); - return this->Get().Get(isolate_).template As<v8::Promise::Resolver>(); - } }; template <typename T>
diff --git a/cobalt/script/v8c/script_promise.h b/cobalt/script/v8c/script_promise.h new file mode 100644 index 0000000..93c4561 --- /dev/null +++ b/cobalt/script/v8c/script_promise.h
@@ -0,0 +1,148 @@ +// Copyright 2022 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COBALT_SCRIPT_V8C_SCRIPT_PROMISE_H_ +#define COBALT_SCRIPT_V8C_SCRIPT_PROMISE_H_ + +#include <memory> +#include <utility> + +#include "base/logging.h" +#include "base/threading/thread_checker.h" +#include "cobalt/script/promise.h" +#include "cobalt/script/v8c/conversion_helpers.h" +#include "cobalt/script/v8c/entry_scope.h" +#include "cobalt/script/v8c/scoped_persistent.h" +#include "cobalt/script/v8c/type_traits.h" +#include "cobalt/script/v8c/v8c_exception_state.h" +#include "cobalt/script/v8c/v8c_user_object_holder.h" +#include "v8/include/v8.h" + +namespace cobalt { +namespace script { +namespace v8c { + +// Shared functionality for ScriptPromise<T>. Does not implement the Resolve +// function, since that needs to be specialized for Promise<T>. +template <typename T> +class ScriptPromise : public ScopedPersistent<v8::Value>, public Promise<T> { + public: + // ScriptValue boilerplate. + typedef Promise<T> BaseType; + + // Handle special case T=void, by swapping the input parameter |T| for + // |PromiseResultUndefined|. Combined with how |Promise| handles this + // special case, we're left with something like: + // + // NativePromise<T> -> Promise<T> + // ^ + // | (T=PromiseResultUndefined) + // / + // NativePromise<void> -> Promise<void> + // + using ResolveType = + typename std::conditional<std::is_same<T, void>::value, + PromiseResultUndefined, T>::type; + + ScriptPromise(v8::Isolate* isolate, v8::Local<v8::Value> resolver) + : isolate_(isolate), ScopedPersistent(isolate, resolver) { + DCHECK(resolver->IsPromise()); + } + + void Resolve(const ResolveType& value) const override { NOTREACHED(); } + + void Reject() const override { NOTREACHED(); } + + void Reject(SimpleExceptionType exception) const override { NOTREACHED(); } + + void Reject(const scoped_refptr<ScriptException>& result) const override { + NOTREACHED(); + } + + PromiseState State() const override { + DCHECK(!this->IsEmpty()); + EntryScope entry_scope(isolate_); + + v8::Promise::PromiseState v8_promise_state = this->promise()->State(); + switch (v8_promise_state) { + case v8::Promise::kPending: + return PromiseState::kPending; + case v8::Promise::kFulfilled: + return PromiseState::kFulfilled; + case v8::Promise::kRejected: + return PromiseState::kRejected; + } + NOTREACHED(); + return PromiseState::kRejected; + } + + void AddStateChangeCallback( + std::unique_ptr<base::OnceCallback<void()>> callback) override { + v8::Local<v8::Context> context = context_.NewLocal(isolate_); + + auto callback_lambda = [](const v8::FunctionCallbackInfo<v8::Value>& info) { + auto* callback = static_cast<base::OnceCallback<void()>*>( + info.Data().As<v8::External>()->Value()); + DCHECK(callback); + std::move(*callback).Run(); + delete callback; + }; + v8::Local<v8::Function> function = + v8::Function::New(isolate_->GetCurrentContext(), callback_lambda, + v8::External::New(isolate_, callback.release())) + .ToLocalChecked(); + v8::Local<v8::Promise> result_promise; + if (!promise() + ->Then(context, function, function) + .ToLocal(&result_promise)) { + DLOG(ERROR) << "Unable to add promise state change callback."; + NOTREACHED(); + } + } + + v8::Local<v8::Promise> promise() const { + DCHECK(!this->IsEmpty()); + return resolver()->GetPromise(); + } + + protected: + v8::Local<v8::Promise::Resolver> resolver() const { + DCHECK(!this->IsEmpty()); + return this->Get().Get(isolate_).template As<v8::Promise::Resolver>(); + } + + v8::Isolate* isolate() const { return isolate_; } + + private: + v8::Isolate* isolate_; + ScopedPersistent<v8::Context> context_; +}; + +// JSValue -> Promise +template <typename T> +void FromJSValue(v8::Isolate* isolate, v8::Local<v8::Value> value, + int conversion_flags, ExceptionState* exception_state, + std::unique_ptr<script::Promise<T*>>* out_promise) { + if (!value->IsPromise()) { + exception_state->SetSimpleException(kNotSupportedType); + return; + } + out_promise->reset(new ScriptPromise<T*>(isolate, value)); +} + +} // namespace v8c +} // namespace script +} // namespace cobalt + +#endif // COBALT_SCRIPT_V8C_SCRIPT_PROMISE_H_
diff --git a/cobalt/worker/extendable_event.h b/cobalt/worker/extendable_event.h index 4e5d4ff..1765ad2 100644 --- a/cobalt/worker/extendable_event.h +++ b/cobalt/worker/extendable_event.h
@@ -15,15 +15,26 @@ #ifndef COBALT_WORKER_EXTENDABLE_EVENT_H_ #define COBALT_WORKER_EXTENDABLE_EVENT_H_ +#include <memory> #include <string> +#include <utility> +#include "base/bind.h" #include "cobalt/base/token.h" #include "cobalt/script/promise.h" #include "cobalt/script/v8c/native_promise.h" #include "cobalt/script/value_handle.h" #include "cobalt/script/wrappable.h" +#include "cobalt/web/context.h" +#include "cobalt/web/dom_exception.h" +#include "cobalt/web/environment_settings.h" #include "cobalt/web/event.h" +#include "cobalt/web/window_or_worker_global_scope.h" #include "cobalt/worker/extendable_event_init.h" +#include "cobalt/worker/service_worker_global_scope.h" +#include "cobalt/worker/service_worker_jobs.h" +#include "cobalt/worker/service_worker_object.h" +#include "cobalt/worker/service_worker_registration_object.h" namespace cobalt { namespace worker { @@ -35,16 +46,87 @@ ExtendableEvent(const std::string& type, const ExtendableEventInit& init_dict) : Event(type, init_dict) {} - void WaitUntil(script::EnvironmentSettings* settings, - const script::Promise<script::ValueHandle>& promise) { - // TODO(b/228976500): Implement WaitUntil(). - NOTIMPLEMENTED(); + void WaitUntil( + script::EnvironmentSettings* settings, + std::unique_ptr<script::Promise<script::ValueHandle*>>& promise, + script::ExceptionState* exception_state) { + // Algorithm for waitUntil(), to add lifetime promise to event. + // https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil + + // 1. If event’s isTrusted attribute is false, throw an "InvalidStateError" + // DOMException. + // 2. If event is not active, throw an "InvalidStateError" DOMException. + if (!IsActive()) { + web::DOMException::Raise(web::DOMException::kInvalidStateErr, + exception_state); + return; + } + // 3. Add promise to event’s extend lifetime promises. + // 4. Increment event’s pending promises count by one. + ++pending_promise_count_; + // 5. Upon fulfillment or rejection of promise, queue a microtask to run + // these substeps: + std::unique_ptr<base::OnceCallback<void()>> callback( + new base::OnceCallback<void()>(std::move( + base::BindOnce(&ExtendableEvent::StateChange, + base::Unretained(this), settings, promise.get())))); + promise->AddStateChangeCallback(std::move(callback)); + promise.release(); + } + + void StateChange(script::EnvironmentSettings* settings, + const script::Promise<script::ValueHandle*>* promise) { + // Implement the microtask called upon fulfillment or rejection of a + // promise, as part of the algorithm for waitUntil(). + // https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil + DCHECK(promise); + has_rejected_promise_ |= + promise->State() == script::PromiseState::kRejected; + // 5.1. Decrement event’s pending promises count by one. + --pending_promise_count_; + // 5.2. If event’s pending promises count is 0, then: + if (0 == pending_promise_count_) { + web::Context* context = + base::polymorphic_downcast<web::EnvironmentSettings*>(settings) + ->context(); + ServiceWorkerJobs* jobs = context->service_worker_jobs(); + DCHECK(jobs); + // 5.2.1. Let registration be the current global object's associated + // service worker's containing service worker registration. + jobs->message_loop()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce(&ServiceWorkerJobs::WaitUntilSubSteps, + base::Unretained(jobs), + base::Unretained( + context->GetWindowOrWorkerGlobalScope() + ->AsServiceWorker() + ->service_worker_object() + ->containing_service_worker_registration()))); + } + delete promise; + } + + bool IsActive() { + // An ExtendableEvent object is said to be active when its timed out flag + // is unset and either its pending promises count is greater than zero or + // its dispatch flag is set. + // https://w3c.github.io/ServiceWorker/#extendableevent-active + return !timed_out_flag_ && + ((pending_promise_count_ > 0) || IsBeingDispatched()); } DEFINE_WRAPPABLE_TYPE(ExtendableEvent); protected: ~ExtendableEvent() override {} + + private: + // https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises + // std::list<script::Promise<script::ValueHandle*>> extend_lifetime_promises_; + int pending_promise_count_ = 0; + bool has_rejected_promise_ = false; + // https://w3c.github.io/ServiceWorker/#extendableevent-timed-out-flag + bool timed_out_flag_ = false; }; } // namespace worker
diff --git a/cobalt/worker/extendable_event.idl b/cobalt/worker/extendable_event.idl index 2297cbc..8a12344 100644 --- a/cobalt/worker/extendable_event.idl +++ b/cobalt/worker/extendable_event.idl
@@ -18,5 +18,5 @@ Exposed = ServiceWorker, Constructor(DOMString type, optional ExtendableEventInit eventInitDict) ] interface ExtendableEvent : Event { - [CallWith = EnvironmentSettings] void waitUntil(Promise<any> f); + [RaisesException, CallWith = EnvironmentSettings] void waitUntil(Promise<any> f); };
diff --git a/cobalt/worker/service_worker_jobs.cc b/cobalt/worker/service_worker_jobs.cc index f901854..9ba0101 100644 --- a/cobalt/worker/service_worker_jobs.cc +++ b/cobalt/worker/service_worker_jobs.cc
@@ -44,6 +44,7 @@ #include "cobalt/worker/client.h" #include "cobalt/worker/client_query_options.h" #include "cobalt/worker/client_type.h" +#include "cobalt/worker/extendable_event.h" #include "cobalt/worker/frame_type.h" #include "cobalt/worker/service_worker.h" #include "cobalt/worker/service_worker_container.h" @@ -891,7 +892,7 @@ // 11.3.1.2. Initialize e’s type attribute to install. // 11.3.1.3. Dispatch e at installingWorker’s global object. installing_worker->worker_global_scope()->DispatchEvent( - new web::Event(base::Tokens::install())); + new ExtendableEvent(base::Tokens::install())); // 11.3.1.4. WaitForAsynchronousExtensions: Run the // following substeps in parallel: // 11.3.1.4.1. Wait until e is not active. @@ -1137,7 +1138,7 @@ // 11.1.1.2. Initialize e’s type attribute to activate. // 11.1.1.3. Dispatch e at activeWorker’s global object. active_worker->worker_global_scope()->DispatchEvent( - new web::Event(base::Tokens::activate())); + new ExtendableEvent(base::Tokens::activate())); // 11.1.1.4. WaitForAsynchronousExtensions: Wait, in // parallel, until e is not active. }, @@ -1837,6 +1838,23 @@ std::move(promise_reference))); } +void ServiceWorkerJobs::WaitUntilSubSteps( + ServiceWorkerRegistrationObject* registration) { + TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::WaitUntilSubSteps()"); + DCHECK_EQ(message_loop_, base::MessageLoop::current()); + // Sub steps for WaitUntil. + // https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil + // 5.2.2. If registration is unregistered, invoke Try Clear Registration + // with registration. + if (scope_to_registration_map_.IsUnregistered(registration)) { + TryClearRegistration(registration); + } + // 5.2.3. If registration is not null, invoke Try Activate with + // registration. + if (registration) { + TryActivate(registration); + } +} void ServiceWorkerJobs::ClientsGetSubSteps( web::EnvironmentSettings* settings, ServiceWorkerObject* associated_service_worker,
diff --git a/cobalt/worker/service_worker_jobs.h b/cobalt/worker/service_worker_jobs.h index 89dd24a..8e09831 100644 --- a/cobalt/worker/service_worker_jobs.h +++ b/cobalt/worker/service_worker_jobs.h
@@ -204,6 +204,10 @@ const base::WeakPtr<ServiceWorkerObject>& service_worker, std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference); + // Sub steps for WaitUntil. + // https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil + void WaitUntilSubSteps(ServiceWorkerRegistrationObject* registration); + // Parallel sub steps (2) for algorithm for Clients.get(id): // https://w3c.github.io/ServiceWorker/#clients-get void ClientsGetSubSteps(
diff --git a/net/disk_cache/cobalt/cobalt_backend_impl.h b/net/disk_cache/cobalt/cobalt_backend_impl.h index c2b82f7..5a4443b 100644 --- a/net/disk_cache/cobalt/cobalt_backend_impl.h +++ b/net/disk_cache/cobalt/cobalt_backend_impl.h
@@ -31,6 +31,8 @@ namespace disk_cache { +const char kCacheEnabledPersistentSettingsKey[] = "cacheEnabled"; + // This class implements the Backend interface. An object of this class handles // the operations of the cache without writing to disk. class NET_EXPORT_PRIVATE CobaltBackendImpl final : public Backend {
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 a858ea3..4b05f41 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
@@ -29,6 +29,7 @@ import android.view.ViewGroup.LayoutParams; import android.view.ViewParent; import android.widget.FrameLayout; +import dev.cobalt.media.AudioOutputManager; import dev.cobalt.media.MediaCodecUtil; import dev.cobalt.media.VideoSurfaceView; import dev.cobalt.util.DisplayUtil; @@ -37,6 +38,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; /** Native activity that has the required JNI methods called by the Starboard implementation. */ public abstract class CobaltActivity extends NativeActivity { @@ -130,6 +132,8 @@ } DisplayUtil.cacheDefaultDisplay(this); + DisplayUtil.addDisplayListener(this); + AudioOutputManager.addAudioDeviceListener(this); getStarboardBridge().onActivityStart(this, keyboardEditor); super.onStart(); @@ -248,11 +252,12 @@ return; } - String customProxy = String.format("--proxy=\"http=http://%s:%d\"", config.first, port); + String customProxy = + String.format(Locale.US, "--proxy=\"http=http://%s:%d\"", config.first, port); Log.i(TAG, "addCustomProxyArgs: " + customProxy); args.add(customProxy); } catch (NumberFormatException e) { - Log.w(TAG, String.format("http.proxyPort: %s is not valid number", config.second), e); + Log.w(TAG, "http.proxyPort: %s is not valid number", config.second, e); } }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java index f131da7..758030d 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java
@@ -29,6 +29,7 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.util.Locale; /** Loads MediaImage artwork, and caches one image. */ public class ArtworkLoader { @@ -97,7 +98,7 @@ private Size parseImageSize(MediaImage image) { try { String sizeStr = image.sizes.split("\\s+", -1)[0]; - return Size.parseSize(sizeStr.toLowerCase()); + return Size.parseSize(sizeStr.toLowerCase(Locale.US)); } catch (NumberFormatException | NullPointerException e) { return new Size(0, 0); }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java index 5a13b01..ebd8967 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
@@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; /** Creates and destroys AudioTrackBridge and handles the volume change. */ @@ -103,9 +104,9 @@ // AudioFormat. Log.v( TAG, - String.format( - "Setting |hasAudioDeviceChanged| to true for audio device %s, %s.", - info.getProductName(), getDeviceTypeNameV23(info.getType()))); + "Setting |hasAudioDeviceChanged| to true for audio device %s, %s.", + info.getProductName(), + getDeviceTypeNameV23(info.getType())); hasAudioDeviceChanged.set(true); break; } @@ -116,9 +117,8 @@ public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { Log.v( TAG, - String.format( - "onAudioDevicesAdded() called, |initialDevicesAdded| is: %b.", - initialDevicesAdded)); + "onAudioDevicesAdded() called, |initialDevicesAdded| is: %b.", + initialDevicesAdded); if (initialDevicesAdded) { handleConnectedDeviceChange(addedDevices); return; @@ -236,7 +236,7 @@ return "TYPE_WIRED_HEADSET"; default: // This may include constants introduced after API 23. - return String.format("TYPE_UNKNOWN (%d)", device_type); + return String.format(Locale.US, "TYPE_UNKNOWN (%d)", device_type); } } @@ -278,7 +278,7 @@ break; default: // This may include constants introduced after API 23. - encodings_in_string.append(String.format("UNKNOWN (%d)", encodings[i])); + encodings_in_string.append(String.format(Locale.US, "UNKNOWN (%d)", encodings[i])); break; } if (i != encodings.length - 1) { @@ -304,12 +304,11 @@ for (AudioDeviceInfo info : deviceInfos) { Log.i( TAG, - String.format( - " Audio Device: %s, channels: %s, sample rates: %s, encodings: %s", - getDeviceTypeNameV23(info.getType()), - Arrays.toString(info.getChannelCounts()), - Arrays.toString(info.getSampleRates()), - getEncodingNames(info.getEncodings()))); + " Audio Device: %s, channels: %s, sample rates: %s, encodings: %s", + getDeviceTypeNameV23(info.getType()), + Arrays.toString(info.getChannelCounts()), + Arrays.toString(info.getSampleRates()), + getEncodingNames(info.getEncodings())); } } @@ -348,10 +347,11 @@ if (AudioTrackBridge.AV_SYNC_HEADER_V1_SIZE % frameSizeInBytes != 0) { Log.w( TAG, - String.format( - "Disable tunnel mode due to sampleSizeInBytes (%d) * numberOfChannels (%d) isn't" - + " aligned to AV_SYNC_HEADER_V1_SIZE (%d).", - sampleSizeInBytes, numberOfChannels, AudioTrackBridge.AV_SYNC_HEADER_V1_SIZE)); + "Disable tunnel mode due to sampleSizeInBytes (%d) * numberOfChannels (%d) isn't" + + " aligned to AV_SYNC_HEADER_V1_SIZE (%d).", + sampleSizeInBytes, + numberOfChannels, + AudioTrackBridge.AV_SYNC_HEADER_V1_SIZE); return -1; } } @@ -366,10 +366,10 @@ if (Build.VERSION.SDK_INT < 23) { Log.i( TAG, - String.format( - "Passthrough on encoding %d is rejected on api %d, as passthrough is only" - + " supported on api 23 or later.", - encoding, Build.VERSION.SDK_INT)); + "Passthrough on encoding %d is rejected on api %d, as passthrough is only" + + " supported on api 23 or later.", + encoding, + Build.VERSION.SDK_INT); return false; } @@ -382,10 +382,9 @@ if (info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) { Log.i( TAG, - String.format( - "Passthrough on encoding %d is disabled because Bluetooth output device is" - + " connected.", - encoding)); + "Passthrough on encoding %d is disabled because Bluetooth output device is" + + " connected.", + encoding); return false; } } @@ -396,34 +395,31 @@ if (hasPassthroughSupportForV23(deviceInfos, encoding)) { Log.i( TAG, - String.format( - "Passthrough on encoding %d is supported, as hasPassthroughSupportForV23() returns" - + " true.", - encoding)); + "Passthrough on encoding %d is supported, as hasPassthroughSupportForV23() returns" + + " true.", + encoding); } else { if (Build.VERSION.SDK_INT < 29) { Log.i( TAG, - String.format( - "Passthrough on encoding %d is rejected, as" - + " hasDirectSurroundPlaybackSupportForV29() is not called for api %d.", - encoding, Build.VERSION.SDK_INT)); + "Passthrough on encoding %d is rejected, as" + + " hasDirectSurroundPlaybackSupportForV29() is not called for api %d.", + encoding, + Build.VERSION.SDK_INT); return false; } if (hasDirectSurroundPlaybackSupportForV29(encoding, DEFAULT_SURROUND_SAMPLE_RATE)) { Log.i( TAG, - String.format( - "Passthrough on encoding %d is supported, as" - + " hasDirectSurroundPlaybackSupportForV29() returns true.", - encoding)); + "Passthrough on encoding %d is supported, as" + + " hasDirectSurroundPlaybackSupportForV29() returns true.", + encoding); } else { Log.i( TAG, - String.format( - "Passthrough on encoding %d is not supported, as" - + " hasDirectSurroundPlaybackSupportForV29() returns false.", - encoding)); + "Passthrough on encoding %d is not supported, as" + + " hasDirectSurroundPlaybackSupportForV29() returns false.", + encoding); return false; } } @@ -445,10 +441,9 @@ // HDMI and SPDIF are connected, where the output should fallback to AC3. Log.w( TAG, - String.format( - "Passthrough on encoding %d is disabled because creating AudioTrack raises" - + " exception: ", - encoding), + "Passthrough on encoding %d is disabled because creating AudioTrack raises" + + " exception: ", + encoding, e); return false; } @@ -475,31 +470,29 @@ // an empty array indicates that the device supports arbitrary encodings. Log.i( TAG, - String.format( - "Passthrough on encoding %d is supported on %s, because getEncodings() returns" - + " an empty array.", - encoding, getDeviceTypeNameV23(type))); + "Passthrough on encoding %d is supported on %s, because getEncodings() returns" + + " an empty array.", + encoding, + getDeviceTypeNameV23(type)); return true; } for (int i = 0; i < encodings.length; ++i) { if (encodings[i] == encoding) { Log.i( TAG, - String.format( - "Passthrough on encoding %d is supported on %s.", - encoding, getDeviceTypeNameV23(type))); + "Passthrough on encoding %d is supported on %s.", + encoding, + getDeviceTypeNameV23(type)); return true; } } Log.i( TAG, - String.format( - "Passthrough on encoding %d is not supported on %s.", - encoding, getDeviceTypeNameV23(type))); + "Passthrough on encoding %d is not supported on %s.", + encoding, + getDeviceTypeNameV23(type)); } - Log.i( - TAG, - String.format("Passthrough on encoding %d is not supported on any devices.", encoding)); + Log.i(TAG, "Passthrough on encoding %d is not supported on any devices.", encoding); return false; } @@ -511,19 +504,15 @@ && encoding != AudioFormat.ENCODING_E_AC3_JOC) { Log.w( TAG, - String.format( - "hasDirectSurroundPlaybackSupportForV29() encountered unsupported encoding %d.", - encoding)); + "hasDirectSurroundPlaybackSupportForV29() encountered unsupported encoding %d.", + encoding); return false; } boolean supported = AudioTrack.isDirectPlaybackSupported( getPassthroughAudioFormatFor(encoding, sampleRate), getDefaultAudioAttributes()); - Log.i( - TAG, - String.format( - "isDirectPlaybackSupported() for encoding %d returned %b.", encoding, supported)); + Log.i(TAG, "isDirectPlaybackSupported() for encoding %d returned %b.", encoding, supported); return supported; } @@ -550,4 +539,31 @@ private boolean getAndResetHasAudioDeviceChanged() { return hasAudioDeviceChanged.getAndSet(false); } + + private static AudioDeviceCallback audioDeviceCallback = + new AudioDeviceCallback() { + @Override + public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { + nativeOnAudioDeviceChanged(); + } + + @Override + public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { + nativeOnAudioDeviceChanged(); + } + }; + + private static boolean audioDeviceListenerAdded = false; + + public static void addAudioDeviceListener(Context context) { + if (audioDeviceListenerAdded) { + return; + } + + AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + audioManager.registerAudioDeviceCallback(audioDeviceCallback, null); + audioDeviceListenerAdded = true; + } + + private static native void nativeOnAudioDeviceChanged(); }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java index 4c11399..5050078 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
@@ -27,6 +27,7 @@ import dev.cobalt.util.UsedByNative; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Locale; /** * A wrapper of the android AudioTrack class. Android AudioTrack would not start playing until the @@ -96,9 +97,12 @@ audioTrack = null; String errorMessage = String.format( + Locale.US, "Enable tunnel mode when frame size is unaligned, " + "sampleType: %d, channel: %d, sync header size: %d.", - sampleType, channelCount, AV_SYNC_HEADER_V1_SIZE); + sampleType, + channelCount, + AV_SYNC_HEADER_V1_SIZE); Log.e(TAG, errorMessage); throw new RuntimeException(errorMessage); } @@ -160,12 +164,11 @@ } Log.i( TAG, - String.format( - "AudioTrack created with buffer size %d (preferred: %d). The minimum buffer size is" - + " %d.", - audioTrackBufferSize, - preferredBufferSizeInBytes, - AudioTrack.getMinBufferSize(sampleRate, channelConfig, sampleType))); + "AudioTrack created with buffer size %d (preferred: %d). The minimum buffer size is" + + " %d.", + audioTrackBufferSize, + preferredBufferSizeInBytes, + AudioTrack.getMinBufferSize(sampleRate, channelConfig, sampleType)); } public Boolean isAudioTrackValid() {
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java index a310b15..f36dad3 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
@@ -515,9 +515,11 @@ Log.i( TAG, - String.format( - "MediaSession state: %s, position: %d ms, speed: %f x, duration: %d ms", - stateName, positionMs, speed, duration)); + "MediaSession state: %s, position: %d ms, speed: %f x, duration: %d ms", + stateName, + positionMs, + speed, + duration); playbackStateBuilder = new PlaybackStateCompat.Builder()
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java index c5c66cd..eeed7a7 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
@@ -37,6 +37,7 @@ import dev.cobalt.util.UsedByNative; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Locale; /** A wrapper of the MediaCodec class. */ @SuppressWarnings("unused") @@ -141,7 +142,7 @@ return; } if (presentationTimeUs <= mLastFrameTimestampUs) { - Log.v(TAG, String.format("Invalid output presentation timestamp.")); + Log.v(TAG, "Invalid output presentation timestamp."); return; } @@ -537,13 +538,10 @@ } MediaCodec mediaCodec = null; try { - Log.i(TAG, String.format("Creating \"%s\" decoder.", decoderName)); + Log.i(TAG, "Creating \"%s\" decoder.", decoderName); mediaCodec = MediaCodec.createByCodecName(decoderName); } catch (Exception e) { - Log.e( - TAG, - String.format("Failed to create MediaCodec: %s, DecoderName: %s", mime, decoderName), - e); + Log.e(TAG, "Failed to create MediaCodec: %s, DecoderName: %s", mime, decoderName, e); return null; } if (mediaCodec == null) { @@ -609,13 +607,16 @@ } try { - Log.i(TAG, String.format("Creating \"%s\" decoder.", decoderName)); + Log.i(TAG, "Creating \"%s\" decoder.", decoderName); mediaCodec = MediaCodec.createByCodecName(decoderName); } catch (Exception e) { String message = String.format( + Locale.US, "Failed to create MediaCodec: %s, mustSupportSecure: %s," + " DecoderName: %s", - mime, crypto != null, decoderName); + mime, + crypto != null, + decoderName); Log.e(TAG, message, e); outCreateMediaCodecBridgeResult.mErrorMessage = message; return; @@ -1189,8 +1190,10 @@ + (configurationData == null ? "|configurationData| is null." : String.format( + Locale.US, "Configuration data size (%d) is less than the required size (%d).", - configurationData.length, MIN_OPUS_INITIALIZATION_DATA_BUFFER_SIZE))); + configurationData.length, + MIN_OPUS_INITIALIZATION_DATA_BUFFER_SIZE))); return false; } // Both the number of samples to skip from the beginning of the stream and the amount of time
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java index 2d023be..e2e9b43 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
@@ -436,7 +436,7 @@ // Filter blacklisted video decoders. String name = codecInfo.getName(); if (!isVp9AllowListed && videoCodecDenyList.contains(name)) { - Log.v(TAG, String.format("Rejecting %s, reason: codec is on deny list", name)); + Log.v(TAG, "Rejecting %s, reason: codec is on deny list", name); continue; } if (name.endsWith(SECURE_DECODER_SUFFIX)) { @@ -446,7 +446,7 @@ name.substring(0, name.length() - SECURE_DECODER_SUFFIX.length()); if (!isVp9AllowListed && videoCodecDenyList.contains(nameWithoutSecureSuffix)) { String format = "Rejecting %s, reason: offpsec denylisted secure decoder"; - Log.v(TAG, String.format(format, name)); + Log.v(TAG, format, name); continue; } } @@ -537,6 +537,7 @@ int fps) { String decoderInfo = String.format( + Locale.US, "Searching for video decoder with parameters mimeType: %s, secure: %b, frameWidth:" + " %d, frameHeight: %d, bitrate: %d, fps: %d, mustSupportHdr: %b," + " mustSupportSoftwareCodec: %b, mustSupportTunnelMode: %b," @@ -554,6 +555,7 @@ Log.v(TAG, decoderInfo); String deviceInfo = String.format( + Locale.US, "brand: %s, model: %s, version: %s, API level: %d, isVp9AllowListed: %b", Build.BRAND, Build.MODEL, @@ -606,9 +608,13 @@ || !mustSupportSecure && requiresSecurePlayback) { String message = String.format( + Locale.US, "Rejecting %s, reason: secure decoder requested: %b, " + "codec FEATURE_SecurePlayback supported: %b, required: %b", - name, mustSupportSecure, supportsSecurePlayback, requiresSecurePlayback); + name, + mustSupportSecure, + supportsSecurePlayback, + requiresSecurePlayback); Log.v(TAG, message); continue; } @@ -623,9 +629,13 @@ || !mustSupportTunnelMode && requiresTunneledPlayback) { String message = String.format( + Locale.US, "Rejecting %s, reason: tunneled playback requested: %b, " + "codec FEATURE_TunneledPlayback supported: %b, required: %b", - name, mustSupportTunnelMode, supportsTunneledPlayback, requiresTunneledPlayback); + name, + mustSupportTunnelMode, + supportsTunneledPlayback, + requiresTunneledPlayback); Log.v(TAG, message); continue; } @@ -652,31 +662,31 @@ if (frameWidth != 0 && frameHeight != 0) { if (!videoCapabilities.isSizeSupported(frameWidth, frameHeight)) { String format = "Rejecting %s, reason: width %s is not compatible with height %d"; - Log.v(TAG, String.format(format, name, frameWidth, frameHeight)); + Log.v(TAG, format, name, frameWidth, frameHeight); continue; } } else if (frameWidth != 0) { if (!supportedWidths.contains(frameWidth)) { String format = "Rejecting %s, reason: supported widths %s does not contain %d"; - Log.v(TAG, String.format(format, name, supportedWidths.toString(), frameWidth)); + Log.v(TAG, format, name, supportedWidths.toString(), frameWidth); continue; } } else if (frameHeight != 0) { if (!supportedHeights.contains(frameHeight)) { String format = "Rejecting %s, reason: supported heights %s does not contain %d"; - Log.v(TAG, String.format(format, name, supportedHeights.toString(), frameHeight)); + Log.v(TAG, format, name, supportedHeights.toString(), frameHeight); continue; } } } else { if (frameWidth != 0 && !supportedWidths.contains(frameWidth)) { String format = "Rejecting %s, reason: supported widths %s does not contain %d"; - Log.v(TAG, String.format(format, name, supportedWidths.toString(), frameWidth)); + Log.v(TAG, format, name, supportedWidths.toString(), frameWidth); continue; } if (frameHeight != 0 && !supportedHeights.contains(frameHeight)) { String format = "Rejecting %s, reason: supported heights %s does not contain %d"; - Log.v(TAG, String.format(format, name, supportedHeights.toString(), frameHeight)); + Log.v(TAG, format, name, supportedHeights.toString(), frameHeight); continue; } } @@ -684,7 +694,7 @@ Range<Integer> bitrates = videoCapabilities.getBitrateRange(); if (bitrate != 0 && !bitrates.contains(bitrate)) { String format = "Rejecting %s, reason: bitrate range %s does not contain %d"; - Log.v(TAG, String.format(format, name, bitrates.toString(), bitrate)); + Log.v(TAG, format, name, bitrates.toString(), bitrate); continue; } @@ -694,14 +704,14 @@ if (frameHeight != 0 && frameWidth != 0) { if (!videoCapabilities.areSizeAndRateSupported(frameWidth, frameHeight, fps)) { String format = "Rejecting %s, reason: supported frame rates %s does not contain %d"; - Log.v(TAG, String.format(format, name, supportedFrameRates.toString(), fps)); + Log.v(TAG, format, name, supportedFrameRates.toString(), fps); continue; } } else { // At least one of frameHeight or frameWidth is 0 if (!supportedFrameRates.contains(fps)) { String format = "Rejecting %s, reason: supported frame rates %s does not contain %d"; - Log.v(TAG, String.format(format, name, supportedFrameRates.toString(), fps)); + Log.v(TAG, format, name, supportedFrameRates.toString(), fps); continue; } } @@ -709,7 +719,7 @@ } else { if (fps != 0 && !supportedFrameRates.contains(fps)) { String format = "Rejecting %s, reason: supported frame rates %s does not contain %d"; - Log.v(TAG, String.format(format, name, supportedFrameRates.toString(), fps)); + Log.v(TAG, format, name, supportedFrameRates.toString(), fps); continue; } } @@ -855,6 +865,7 @@ for (ResolutionAndFrameRate resolutionAndFrameRate : supported) { frameRateAndResolutionString += String.format( + Locale.US, "[%d x %d, %.3f fps], ", resolutionAndFrameRate.width, resolutionAndFrameRate.height, @@ -878,6 +889,7 @@ String name = info.getName(); decoderDumpString += String.format( + Locale.US, "name: %s (%s, %s): ", name, supportedType, @@ -897,6 +909,7 @@ getSupportedResolutionsAndFrameRates(videoCapabilities, isHdrCapable); decoderDumpString += String.format( + Locale.US, "\n\t\t" + "widths: %s, " + "heights: %s, " @@ -923,6 +936,7 @@ || isTunneledPlaybackSupported) { decoderDumpString += String.format( + Locale.US, "(%s%s%s", isAdaptivePlaybackSupported ? "AdaptivePlayback, " : "", isSecurePlaybackSupported ? "SecurePlayback, " : "", @@ -938,13 +952,12 @@ } Log.v( TAG, - String.format( - " \n" - + "==================================================\n" - + "Full list of decoder features: [AdaptivePlayback, SecurePlayback," - + " TunneledPlayback]\n" - + "Unsupported features for each codec are not listed\n" - + decoderDumpString - + "==================================================")); + " \n" + + "==================================================\n" + + "Full list of decoder features: [AdaptivePlayback, SecurePlayback," + + " TunneledPlayback]\n" + + "Unsupported features for each codec are not listed\n" + + decoderDumpString + + "=================================================="); } }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java b/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java index 29a2f80..b96bb87 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java
@@ -16,6 +16,8 @@ import android.app.Activity; import android.content.Context; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; import android.util.DisplayMetrics; import android.util.Size; import android.util.SizeF; @@ -139,4 +141,40 @@ private static DisplayMetrics getDisplayMetrics() { return cachedDisplayMetrics; } + + private static DisplayListener displayerListener = + new DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + nativeOnDisplayChanged(); + } + + @Override + public void onDisplayChanged(int displayId) { + nativeOnDisplayChanged(); + } + + @Override + public void onDisplayRemoved(int displayId) { + nativeOnDisplayChanged(); + } + }; + + private static boolean displayerListenerAdded = false; + + public static void addDisplayListener(Context context) { + if (displayerListenerAdded) { + return; + } + + DisplayManager displayManager = context.getSystemService(DisplayManager.class); + displayManager.registerDisplayListener(displayerListener, null); + displayerListenerAdded = true; + + // Call nativeOnDisplayChanged() to reload supported hdr types here after a default + // Display created. + nativeOnDisplayChanged(); + } + + private static native void nativeOnDisplayChanged(); }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/util/Log.java b/starboard/android/apk/app/src/main/java/dev/cobalt/util/Log.java index 0a75c79..cca7c02 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/util/Log.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/util/Log.java
@@ -15,6 +15,7 @@ package dev.cobalt.util; import java.lang.reflect.Method; +import java.util.Locale; /** * Logging wrapper to allow for better control of Proguard log stripping. Many dependent @@ -59,9 +60,27 @@ } } - private static int logWithMethod(Method logMethod, String tag, String msg, Throwable tr) { + private static Throwable getThrowableToLog(Object[] args) { + if (args == null || args.length == 0) return null; + Object lastArg = args[args.length - 1]; + if (!(lastArg instanceof Throwable)) return null; + return (Throwable) lastArg; + } + + /** Returns a formatted log message, using the supplied format and arguments. */ + private static String formatLog(String messageTemplate, Throwable tr, Object... params) { + if ((params != null) && ((tr == null && params.length > 0) || params.length > 1)) { + messageTemplate = String.format(Locale.US, messageTemplate, params); + } + return messageTemplate; + } + + private static int logWithMethod( + Method logMethod, String tag, String messageTemplate, Object... args) { try { if (logMethod != null) { + Throwable tr = getThrowableToLog(args); + String msg = formatLog(messageTemplate, tr, args); return (int) logMethod.invoke(null, tag, msg, tr); } } catch (Throwable e) { @@ -70,47 +89,27 @@ return 0; } - public static int v(String tag, String msg) { - return logWithMethod(logV, tag, msg, null); + public static int v(String tag, String messageTemplate, Object... args) { + return logWithMethod(logV, tag, messageTemplate, args); } - public static int v(String tag, String msg, Throwable tr) { - return logWithMethod(logV, tag, msg, tr); + public static int d(String tag, String messageTemplate, Object... args) { + return logWithMethod(logD, tag, messageTemplate, args); } - public static int d(String tag, String msg) { - return logWithMethod(logD, tag, msg, null); + public static int i(String tag, String messageTemplate, Object... args) { + return logWithMethod(logI, tag, messageTemplate, args); } - public static int d(String tag, String msg, Throwable tr) { - return logWithMethod(logD, tag, msg, tr); - } - - public static int i(String tag, String msg) { - return logWithMethod(logI, tag, msg, null); - } - - public static int i(String tag, String msg, Throwable tr) { - return logWithMethod(logI, tag, msg, tr); - } - - public static int w(String tag, String msg) { - return logWithMethod(logW, tag, msg, null); - } - - public static int w(String tag, String msg, Throwable tr) { - return logWithMethod(logW, tag, msg, tr); + public static int w(String tag, String messageTemplate, Object... args) { + return logWithMethod(logW, tag, messageTemplate, args); } public static int w(String tag, Throwable tr) { return logWithMethod(logW, tag, "", tr); } - public static int e(String tag, String msg) { - return logWithMethod(logE, tag, msg, null); - } - - public static int e(String tag, String msg, Throwable tr) { - return logWithMethod(logE, tag, msg, tr); + public static int e(String tag, String messageTemplate, Object... args) { + return logWithMethod(logE, tag, messageTemplate, args); } }
diff --git a/starboard/android/shared/file_open.cc b/starboard/android/shared/file_open.cc index 83d924a..0f398a4 100644 --- a/starboard/android/shared/file_open.cc +++ b/starboard/android/shared/file_open.cc
@@ -30,7 +30,12 @@ // font file of the same name. const std::string kFontsXml("fonts.xml"); const std::string kSystemFontsDir("/system/fonts/"); + +#if SB_IS(EVERGREEN_COMPATIBLE) +const std::string kCobaltFontsDir("/cobalt/assets/app/cobalt/content/fonts/"); +#else const std::string kCobaltFontsDir("/cobalt/assets/fonts/"); +#endif // Returns the fallback for the given asset path, or an empty string if none. // NOTE: While Cobalt now provides a mechanism for loading system fonts through @@ -67,8 +72,8 @@ bool* out_created, SbFileError* out_error) { if (!IsAndroidAssetPath(path)) { - return ::starboard::shared::posix::impl::FileOpen( - path, flags, out_created, out_error); + return ::starboard::shared::posix::impl::FileOpen(path, flags, out_created, + out_error); } // Assets are never created and are always read-only, whether it's actually an
diff --git a/starboard/android/shared/media_capabilities_cache.cc b/starboard/android/shared/media_capabilities_cache.cc index 53360ea..e8090b5 100644 --- a/starboard/android/shared/media_capabilities_cache.cc +++ b/starboard/android/shared/media_capabilities_cache.cc
@@ -20,12 +20,17 @@ #include "starboard/android/shared/media_common.h" #include "starboard/common/log.h" #include "starboard/once.h" +#include "starboard/shared/starboard/media/key_system_supportability_cache.h" +#include "starboard/shared/starboard/media/mime_supportability_cache.h" namespace starboard { namespace android { namespace shared { namespace { +using ::starboard::shared::starboard::media::KeySystemSupportabilityCache; +using ::starboard::shared::starboard::media::MimeSupportabilityCache; + // https://developer.android.com/reference/android/view/Display.HdrCapabilities.html#HDR_TYPE_HDR10 const jint HDR_TYPE_DOLBY_VISION = 1; const jint HDR_TYPE_HDR10 = 2; @@ -79,6 +84,13 @@ JniEnvExt* env = JniEnvExt::Get(); jintArray j_supported_hdr_types = static_cast<jintArray>( env->CallStarboardObjectMethodOrAbort("getSupportedHdrTypes", "()[I")); + + if (!j_supported_hdr_types) { + // Failed to get supported hdr types. + SB_LOG(ERROR) << "Failed to load supported hdr types."; + return std::set<SbMediaTransferId>(); + } + jsize length = env->GetArrayLength(j_supported_hdr_types); jint* numbers = env->GetIntArrayElements(j_supported_hdr_types, 0); for (int i = 0; i < length; i++) { @@ -461,6 +473,31 @@ max_audio_output_channels_ = -1; } +void MediaCapabilitiesCache::ReloadSupportedHdrTypes() { + 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(); +} + +MediaCapabilitiesCache::MediaCapabilitiesCache() { + // Enable mime and key system caches. + MimeSupportabilityCache::GetInstance()->SetCacheEnabled(true); + KeySystemSupportabilityCache::GetInstance()->SetCacheEnabled(true); +} + void MediaCapabilitiesCache::LazyInitialize_Locked() { mutex_.DCheckAcquired(); @@ -526,6 +563,20 @@ } } +extern "C" SB_EXPORT_PLATFORM void +Java_dev_cobalt_util_DisplayUtil_nativeOnDisplayChanged() { + SB_DLOG(INFO) << "Display device has changed."; + MediaCapabilitiesCache::GetInstance()->ReloadSupportedHdrTypes(); + 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(); + MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities(); +} + } // 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 46da91a..21d735b 100644 --- a/starboard/android/shared/media_capabilities_cache.h +++ b/starboard/android/shared/media_capabilities_cache.h
@@ -160,8 +160,11 @@ void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; } void ClearCache(); + void ReloadSupportedHdrTypes(); + void ReloadAudioOutputChannels(); + private: - MediaCapabilitiesCache() {} + MediaCapabilitiesCache(); ~MediaCapabilitiesCache() {} MediaCapabilitiesCache(const MediaCapabilitiesCache&) = delete; @@ -183,7 +186,7 @@ std::map<std::string, AudioCodecCapabilities> audio_codec_capabilities_map_; std::map<std::string, VideoCodecCapabilities> video_codec_capabilities_map_; - std::atomic_bool is_enabled_{false}; + std::atomic_bool is_enabled_{true}; bool is_initialized_ = false; bool is_widevine_supported_ = false; bool is_cbcs_supported_ = false;
diff --git a/starboard/android/shared/media_is_video_supported.cc b/starboard/android/shared/media_is_video_supported.cc index 1f5c851..9b8fcf5 100644 --- a/starboard/android/shared/media_is_video_supported.cc +++ b/starboard/android/shared/media_is_video_supported.cc
@@ -80,6 +80,14 @@ decoder_cache_ttl_ms = mime_type->GetParamIntValue("decoder_cache_ttl_ms", -1); + + // Disable MediaCapabilitiesCache if "disablecache" option presented. + if (!mime_type->ValidateBoolParameter("disablecache")) { + return false; + } + if (mime_type->GetParamBoolValue("disablecache", false)) { + MediaCapabilitiesCache::GetInstance()->SetCacheEnabled(false); + } } if (must_support_tunnel_mode && decode_to_texture_required) {
diff --git a/starboard/android/shared/system_get_path.cc b/starboard/android/shared/system_get_path.cc index 2ca0815..5e4610a 100644 --- a/starboard/android/shared/system_get_path.cc +++ b/starboard/android/shared/system_get_path.cc
@@ -25,10 +25,35 @@ #include "starboard/common/string.h" #include "starboard/directory.h" +#if SB_IS(EVERGREEN_COMPATIBLE) +#include "starboard/elf_loader/evergreen_config.h" // nogncheck +#endif + using ::starboard::android::shared::g_app_assets_dir; using ::starboard::android::shared::g_app_cache_dir; +using ::starboard::android::shared::g_app_files_dir; using ::starboard::android::shared::g_app_lib_dir; +#if SB_IS(EVERGREEN_COMPATIBLE) +bool GetEvergreenContentPathOverride(char* out_path, int path_size) { + const starboard::elf_loader::EvergreenConfig* evergreen_config = + starboard::elf_loader::EvergreenConfig::GetInstance(); + if (!evergreen_config) { + return true; + } + if (evergreen_config->content_path_.empty()) { + return true; + } + + if (starboard::strlcpy(out_path, evergreen_config->content_path_.c_str(), + path_size) >= path_size) { + return false; + } + + return true; +} +#endif + bool SbSystemGetPath(SbSystemPathId path_id, char* out_path, int path_size) { if (!out_path || !path_size) { return false; @@ -43,9 +68,25 @@ if (starboard::strlcat(path, g_app_assets_dir, kPathSize) >= kPathSize) { return false; } + +#if SB_IS(EVERGREEN_COMPATIBLE) + if (!GetEvergreenContentPathOverride(path, kPathSize)) { + return false; + } +#endif break; } + case kSbSystemPathStorageDirectory: { + if (starboard::strlcpy(path, g_app_files_dir, kPathSize) >= kPathSize) { + return false; + } + if (starboard::strlcat(path, "/storage", kPathSize) >= kPathSize) { + return false; + } + SbDirectoryCreate(path); + break; + } case kSbSystemPathCacheDirectory: { if (!SbSystemGetPath(kSbSystemPathTempDirectory, path, kPathSize)) { return false;
diff --git a/starboard/evergreen/shared/launcher.py b/starboard/evergreen/shared/launcher.py index c8bf2d5..e6f331d 100644 --- a/starboard/evergreen/shared/launcher.py +++ b/starboard/evergreen/shared/launcher.py
@@ -75,6 +75,8 @@ self.loader_out_directory = paths.BuildOutputDirectory( self.loader_platform, self.loader_config) + self.use_compressed_library = kwargs.get('use_compressed_library') + # The relationship of loader platforms and configurations to evergreen # platforms and configurations is many-to-many. We need a separate directory # for each of them, i.e. linux-x64x11_debug__evergreen-x64_gold. @@ -92,9 +94,18 @@ # Ensure the path, relative to the content of the ELF Loader, to the # Evergreen target and its content are passed as command line switches. + library_path_param = '--evergreen_library=app/{}/lib/lib{}'.format( + self.target_name, self.target_name) + if self.use_compressed_library: + if self.target_name != 'cobalt': + raise ValueError( + '|use_compressed_library| only expected with |target_name| cobalt') + library_path_param += '.lz4' + else: + library_path_param += '.so' + target_command_line_params = [ - '--evergreen_library=app/{}/lib/lib{}.so'.format( - self.target_name, self.target_name), + library_path_param, '--evergreen_content=app/{}/content'.format(self.target_name) ] @@ -171,6 +182,7 @@ self._StageTargetsAndContentsGyp() def _StageTargetsAndContentsGnLinux(self): + """Stage targets and their contents for GN builds for Linux platforms.""" content_subdir = os.path.join('usr', 'share', 'cobalt') # Copy loader content and binaries @@ -199,13 +211,15 @@ target_content_dst = os.path.join(target_staging_dir, 'content') shutil.copytree(target_content_src, target_content_dst) - shlib_name = 'lib{}.so'.format(self.target_name) + shlib_name = 'lib{}'.format(self.target_name) + shlib_name += '.lz4' if self.use_compressed_library else '.so' target_binary_src = os.path.join(target_install_path, 'lib', shlib_name) target_binary_dst = os.path.join(target_staging_dir, 'lib', shlib_name) os.makedirs(os.path.join(target_staging_dir, 'lib')) shutil.copy(target_binary_src, target_binary_dst) def _StageTargetsAndContentsGnRaspi(self): + """Stage targets and their contents for GN builds for Raspi platforms.""" # TODO(b/218889313): `content` is hardcoded on raspi and must be in the same # directory as the binaries. if 'raspi' in self.loader_platform: @@ -251,13 +265,15 @@ target_content_dst = os.path.join(target_staging_dir, 'content') shutil.copytree(target_content_src, target_content_dst) - shlib_name = 'lib{}.so'.format(self.target_name) + shlib_name = 'lib{}'.format(self.target_name) + shlib_name += '.lz4' if self.use_compressed_library else '.so' target_binary_src = os.path.join(target_install_path, 'lib', shlib_name) target_binary_dst = os.path.join(target_staging_dir, 'lib', shlib_name) os.makedirs(os.path.join(target_staging_dir, 'lib')) shutil.copy(target_binary_src, target_binary_dst) def _StageTargetsAndContentsGyp(self): + """Stage targets and their contents for GYP builds.""" # <outpath>/deploy/elf_loader_sandbox staging_directory_loader = os.path.join(self.staging_directory, 'deploy', self.loader_target)
diff --git a/starboard/evergreen/testing/README.md b/starboard/evergreen/testing/README.md index bbd33fe..e401325 100644 --- a/starboard/evergreen/testing/README.md +++ b/starboard/evergreen/testing/README.md
@@ -121,9 +121,9 @@ +-- content <-- loader content +-- app +-- cobalt - +-- content <-- cobalt content + +-- content <-- cobalt content +-- lib - +-- libcobalt.so <-- cobalt binary + +-- libcobalt.{so,lz4} <-- cobalt binary ``` Note: This directory structure is the same as what would be generated by
diff --git a/starboard/evergreen/testing/linux/deploy_cobalt.sh b/starboard/evergreen/testing/linux/deploy_cobalt.sh index 6dc6e59..a461fae 100755 --- a/starboard/evergreen/testing/linux/deploy_cobalt.sh +++ b/starboard/evergreen/testing/linux/deploy_cobalt.sh
@@ -31,8 +31,8 @@ echo " Checking '${staging_dir}'" - PATHS=("${staging_dir}/loader_app" \ - "${staging_dir}/content/app/cobalt/lib/libcobalt.so" \ + PATHS=("${staging_dir}/loader_app" \ + "${staging_dir}/content/app/cobalt/lib/libcobalt${SYSTEM_IMAGE_EXTENSION}" \ "${staging_dir}/content/app/cobalt/content/") for file in "${PATHS[@]}"; do
diff --git a/starboard/evergreen/testing/raspi/deploy_cobalt.sh b/starboard/evergreen/testing/raspi/deploy_cobalt.sh index 8fdbc22..9012f3c 100755 --- a/starboard/evergreen/testing/raspi/deploy_cobalt.sh +++ b/starboard/evergreen/testing/raspi/deploy_cobalt.sh
@@ -31,8 +31,8 @@ echo " Checking '${staging_dir}'" - PATHS=("${staging_dir}/loader_app" \ - "${staging_dir}/content/app/cobalt/lib/libcobalt.so" \ + PATHS=("${staging_dir}/loader_app" \ + "${staging_dir}/content/app/cobalt/lib/libcobalt${SYSTEM_IMAGE_EXTENSION}" \ "${staging_dir}/content/app/cobalt/content/") for file in "${PATHS[@]}"; do @@ -60,7 +60,7 @@ eval "${SSH} \"mkdir -p /home/pi/coeg/content/app/cobalt/lib\"" echo " Copying cobalt to system image directory" - eval "${SCP} \"${staging_dir}/content/app/cobalt/lib/libcobalt.so pi@${RASPI_ADDR}:/home/pi/coeg/content/app/cobalt/lib/\"" + eval "${SCP} \"${staging_dir}/content/app/cobalt/lib/libcobalt${SYSTEM_IMAGE_EXTENSION} pi@${RASPI_ADDR}:/home/pi/coeg/content/app/cobalt/lib/\"" echo " Copying content to system image directory" eval "${SCP} \"-r ${staging_dir}/content/app/cobalt/content/ pi@${RASPI_ADDR}:/home/pi/coeg/content/app/cobalt/\""
diff --git a/starboard/evergreen/testing/run_all_tests.sh b/starboard/evergreen/testing/run_all_tests.sh index efb9d0b..91c53fb 100755 --- a/starboard/evergreen/testing/run_all_tests.sh +++ b/starboard/evergreen/testing/run_all_tests.sh
@@ -21,7 +21,9 @@ DIR="$(dirname "${0}")" AUTH_METHOD="public-key" -while getopts "d:a:" o; do +USE_COMPRESSED_SYSTEM_IMAGE="false" +SYSTEM_IMAGE_EXTENSION=".so" +while getopts "d:a:c" o; do case "${o}" in d) DEVICE_ID=${OPTARG} @@ -29,6 +31,10 @@ a) AUTH_METHOD=${OPTARG} ;; + c) + USE_COMPRESSED_SYSTEM_IMAGE="true" + SYSTEM_IMAGE_EXTENSION=".lz4" + ;; esac done shift $((OPTIND-1)) @@ -40,8 +46,14 @@ source $DIR/setup.sh -# Find all of the test files within the 'test' subdirectory. -TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name '*_test.sh'")) +if [[ "${USE_COMPRESSED_SYSTEM_IMAGE}" == "true" ]]; then + # It would be valid to run all test cases using a compressed system image but + # is probably excessive. Instead, just the Evergreen Lite case is run to test + # that the compressed system image can be successfully loaded. + TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name 'evergreen_lite_test.sh'")) +else + TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name '*_test.sh'")) +fi COUNT=0 RETRIED=() @@ -145,7 +157,7 @@ clean_up -log "info" " [==========] Finished." +log "info" " [==========] Finished testing with USE_COMPRESSED_SYSTEM_IMAGE=${USE_COMPRESSED_SYSTEM_IMAGE}." if [[ "${#FAILED[@]}" -eq 0 ]]; then exit 0
diff --git a/starboard/evergreen/testing/setup.sh b/starboard/evergreen/testing/setup.sh index 57bdbc4..393ea6d 100755 --- a/starboard/evergreen/testing/setup.sh +++ b/starboard/evergreen/testing/setup.sh
@@ -24,7 +24,7 @@ source $DIR/pprint.sh -log "info" " [==========] Preparing Cobalt." +log "info" " [==========] Preparing to test with USE_COMPRESSED_SYSTEM_IMAGE=${USE_COMPRESSED_SYSTEM_IMAGE}." if [[ -z ${1} ]]; then log "error" "A platform must be provided"
diff --git a/starboard/shared/starboard/media/BUILD.gn b/starboard/shared/starboard/media/BUILD.gn index 28ba606..7baaf99 100644 --- a/starboard/shared/starboard/media/BUILD.gn +++ b/starboard/shared/starboard/media/BUILD.gn
@@ -17,8 +17,6 @@ sources = [ "//starboard/shared/starboard/media/avc_util.cc", "//starboard/shared/starboard/media/avc_util.h", - "//starboard/shared/starboard/media/bitrate_supportability_cache.cc", - "//starboard/shared/starboard/media/bitrate_supportability_cache.h", "//starboard/shared/starboard/media/codec_util.cc", "//starboard/shared/starboard/media/codec_util.h", "//starboard/shared/starboard/media/key_system_supportability_cache.cc",
diff --git a/starboard/shared/starboard/media/bitrate_supportability_cache.cc b/starboard/shared/starboard/media/bitrate_supportability_cache.cc deleted file mode 100644 index 01c2917..0000000 --- a/starboard/shared/starboard/media/bitrate_supportability_cache.cc +++ /dev/null
@@ -1,150 +0,0 @@ -// Copyright 2022 The Cobalt Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "starboard/shared/starboard/media/bitrate_supportability_cache.h" - -#include <map> - -#include "starboard/common/log.h" -#include "starboard/common/mutex.h" -#include "starboard/log.h" -#include "starboard/media.h" -#include "starboard/once.h" - -namespace starboard { -namespace shared { -namespace starboard { -namespace media { - -namespace { - -template <typename T> -class BitrateSupportabilityContainer { - public: - Supportability GetSupportability(T codec, int bitrate) { - // Reject invalid parameters. - if (bitrate < 0) { - return kSupportabilityNotSupported; - } - // Bitrate 0 is always supported. - if (bitrate == 0) { - return kSupportabilitySupported; - } - - ScopedLock scoped_lock(mutex_); - auto iter = supported_bitrate_ranges_.find(codec); - if (iter == supported_bitrate_ranges_.end()) { - return kSupportabilityUnknown; - } - const Range& range = iter->second; - if (bitrate < range.minimum || bitrate > range.maximum) { - return kSupportabilityNotSupported; - } - return kSupportabilitySupported; - } - - void SetSupportedBitrate(T codec, int min, int max) { - SB_DCHECK(min >= 0 && max >= min); - - ScopedLock scoped_lock(mutex_); - supported_bitrate_ranges_[codec] = Range(min, max); - } - void ClearContainer() { - ScopedLock scoped_lock(mutex_); - supported_bitrate_ranges_.clear(); - } - - private: - struct Range { - Range() : minimum(0), maximum(0) {} - Range(int min, int max) : minimum(min), maximum(max) {} - int minimum; - int maximum; - }; - - Mutex mutex_; - std::map<T, Range> supported_bitrate_ranges_; -}; - -template <typename T> -SB_ONCE_INITIALIZE_FUNCTION(BitrateSupportabilityContainer<T>, GetContainer); - -} // namespace - -// static -SB_ONCE_INITIALIZE_FUNCTION(BitrateSupportabilityCache, - BitrateSupportabilityCache::GetInstance); - -Supportability BitrateSupportabilityCache::GetBitrateSupportability( - const ParsedMimeInfo& mime_info) { - SB_DCHECK(mime_info.is_valid()); - - if (!is_enabled_) { - return kSupportabilityUnknown; - } - - Supportability audio_supportability = kSupportabilitySupported; - if (mime_info.has_audio_info()) { - audio_supportability = GetContainer<SbMediaAudioCodec>()->GetSupportability( - mime_info.audio_info().codec, mime_info.audio_info().bitrate); - } - - Supportability video_supportability = kSupportabilitySupported; - if (mime_info.has_video_info()) { - video_supportability = GetContainer<SbMediaVideoCodec>()->GetSupportability( - mime_info.video_info().codec, mime_info.video_info().bitrate); - } - - if (audio_supportability == kSupportabilityNotSupported || - video_supportability == kSupportabilityNotSupported) { - return kSupportabilityNotSupported; - } - if (audio_supportability == kSupportabilityUnknown || - video_supportability == kSupportabilityUnknown) { - return kSupportabilityUnknown; - } - return kSupportabilitySupported; -} - -void BitrateSupportabilityCache::SetSupportedBitrate(SbMediaAudioCodec codec, - int min, - int max) { - SB_DCHECK(min >= 0 && min <= max) << "Invalid bitrate range."; - - if (!is_enabled_) { - return; - } - GetContainer<SbMediaAudioCodec>()->SetSupportedBitrate(codec, min, max); -} - -void BitrateSupportabilityCache::SetSupportedBitrate(SbMediaVideoCodec codec, - int min, - int max) { - SB_DCHECK(min >= 0 && min <= max) << "Invalid bitrate range."; - - if (!is_enabled_) { - return; - } - GetContainer<SbMediaVideoCodec>()->SetSupportedBitrate(codec, min, max); -} - -void BitrateSupportabilityCache::ClearCache() { - GetContainer<SbMediaAudioCodec>()->ClearContainer(); - GetContainer<SbMediaVideoCodec>()->ClearContainer(); -} - -} // namespace media -} // namespace starboard -} // namespace shared -} // namespace starboard
diff --git a/starboard/shared/starboard/media/bitrate_supportability_cache.h b/starboard/shared/starboard/media/bitrate_supportability_cache.h deleted file mode 100644 index fc1f553..0000000 --- a/starboard/shared/starboard/media/bitrate_supportability_cache.h +++ /dev/null
@@ -1,66 +0,0 @@ -// Copyright 2022 The Cobalt Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_BITRATE_SUPPORTABILITY_CACHE_H_ -#define STARBOARD_SHARED_STARBOARD_MEDIA_BITRATE_SUPPORTABILITY_CACHE_H_ - -#include "starboard/media.h" -#include "starboard/shared/internal_only.h" -#include "starboard/shared/starboard/media/mime_supportability_cache.h" -#include "starboard/shared/starboard/media/parsed_mime_info.h" - -namespace starboard { -namespace shared { -namespace starboard { -namespace media { - -// TODO: add unit tests for BitrateSupportabilityCache -class BitrateSupportabilityCache { - public: - static BitrateSupportabilityCache* GetInstance(); - - // When cache is not enabled, GetBitrateSupportability() will always return - // kSupportabilityUnknown, and SetSupportedBitrate() will do nothing. - bool IsEnabled() const { return is_enabled_; } - void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; } - - // Get bitrate supportability. - Supportability GetBitrateSupportability(const ParsedMimeInfo& mime_info); - // Set supported bitrate range for the |codec|. Note that if supported bitrate - // range is not set, MimeSupportabilityCache::GetMimeSupportability() will - // always return kSupportabilityUnknown. - void SetSupportedBitrate(SbMediaAudioCodec codec, int min, int max); - void SetSupportedBitrate(SbMediaVideoCodec codec, int min, int max); - - // Clear all cached supported bitrate ranges. - void ClearCache(); - - private: - // Class can only be instanced via the singleton - BitrateSupportabilityCache() {} - ~BitrateSupportabilityCache() {} - - BitrateSupportabilityCache(const BitrateSupportabilityCache&) = delete; - BitrateSupportabilityCache& operator=(const BitrateSupportabilityCache&) = - delete; - - std::atomic_bool is_enabled_{false}; -}; - -} // namespace media -} // namespace starboard -} // namespace shared -} // namespace starboard - -#endif // STARBOARD_SHARED_STARBOARD_MEDIA_BITRATE_SUPPORTABILITY_CACHE_H_
diff --git a/starboard/shared/starboard/media/key_system_supportability_cache.h b/starboard/shared/starboard/media/key_system_supportability_cache.h index a1f2f68..e9ad962 100644 --- a/starboard/shared/starboard/media/key_system_supportability_cache.h +++ b/starboard/shared/starboard/media/key_system_supportability_cache.h
@@ -25,7 +25,14 @@ namespace starboard { namespace media { -// TODO: add unit tests for KeySystemSupportabilityCache +// KeySystemSupportabilityCache caches the supportabilities of the combinations +// of codec and key system. +// +// Note: anytime the platform key system capabilities have changed, please +// call KeySystemSupportabilityCache::ClearCache() to clear the outdated +// results. +// +// TODO: add unit tests for KeySystemSupportabilityCache. class KeySystemSupportabilityCache { public: static KeySystemSupportabilityCache* GetInstance();
diff --git a/starboard/shared/starboard/media/mime_supportability_cache.cc b/starboard/shared/starboard/media/mime_supportability_cache.cc index 34024f3..d52761c 100644 --- a/starboard/shared/starboard/media/mime_supportability_cache.cc +++ b/starboard/shared/starboard/media/mime_supportability_cache.cc
@@ -14,6 +14,7 @@ #include "starboard/shared/starboard/media/mime_supportability_cache.h" +#include <cstring> #include <queue> #include <sstream> #include <string> @@ -34,147 +35,116 @@ namespace { -const size_t kDefaultCacheMaxSize = 2000; - -class MimeSupportabilityContainer { - public: - struct Entry { - ParsedMimeInfo mime_info; - Supportability supportability = kSupportabilityUnknown; - - explicit Entry(const std::string& mime_string) : mime_info(mime_string) {} - }; - - // GetParsedMimeAndSupportability() will first try to find a cached Entry for - // the mime string. If no cached entry, a new Entry will be created with - // parsed mime information and supportability kSupportabilityUnknown. - // Ideally, we should decouple mime parsing and cache functionality, but - // considering that the cache is only for internal use, to avoid repeated - // lookups, we do parsing in this function. - const Entry& GetParsedMimeAndSupportability(const std::string& mime_string) { - ScopedLock scoped_lock(mutex_); - auto entry_iter = entries_.find(mime_string); - if (entry_iter != entries_.end()) { - return entry_iter->second; - } - - // We can't find anything from the cache. Parse mime string and cache - // parsed MimeType and ParsedMimeInfo. - auto insert_result = entries_.insert({mime_string, Entry(mime_string)}); - - fifo_queue_.push(insert_result.first); - while (fifo_queue_.size() > max_size_) { - entries_.erase(fifo_queue_.front()); - fifo_queue_.pop(); - } - SB_DCHECK(entries_.size() == fifo_queue_.size()); - - return insert_result.first->second; +// RemoveAttributeFromMime() will return a new mime string with the specified +// attribute removed. If |attribute_string| is not null, the removed attribute +// string will be returned via |attribute_string|. Following are some examples: +// mime: "video/webm; codecs=\"vp9\"; bitrate=300000" +// attribute_name: "bitrate" +// return: "video/webm; codecs=\"vp9\"" +// attribute_string: "bitrate=300000" +// +// mime: "video/webm; codecs=\"vp9\"; bitrate=300000; eotf=bt709" +// attribute_name: "bitrate" +// return: "video/webm; codecs=\"vp9\"; eotf=bt709" +// attribute_string: "bitrate=300000" +// +// mime: "bitrate=300000" +// attribute_name: "bitrate" +// return: "" +// attribute_string: "bitrate=300000" +std::string RemoveAttributeFromMime(const char* mime, + const char* attribute_name, + std::string* attribute_string) { + size_t name_length = strlen(attribute_name); + if (name_length == 0) { + return mime; } - // CacheSupportability() will find the target entry and update the - // supportability. If there's no existing entry, it will parse the mime - // string and create one. - void CacheSupportability(const std::string& mime_string, - Supportability supportability) { - SB_DCHECK(!mime_string.empty()); - SB_DCHECK(supportability != kSupportabilityUnknown); + std::string mime_without_attribute; + const char* start_pos = strstr(mime, attribute_name); + while (start_pos) { + if ((start_pos == mime || start_pos[-1] == ';' || isspace(start_pos[-1])) && + (start_pos[name_length] && + (start_pos[name_length] == '=' || isspace(start_pos[name_length])))) { + break; + } + start_pos += name_length; + start_pos = strstr(start_pos, attribute_name); + } - { - ScopedLock scoped_lock(mutex_); - auto entry_iter = entries_.find(mime_string); - if (entry_iter != entries_.end()) { - entry_iter->second.supportability = supportability; - return; + if (!start_pos) { + // Target attribute is not found. + return std::string(mime); + } + const char* end_pos = strstr(start_pos, ";"); + if (end_pos) { + // There may be other attribute after target attribute. + if (attribute_string) { + // Returned |attribute_string| will not have a trailing ';'. + attribute_string->assign(start_pos, end_pos - start_pos); + } + + end_pos++; + // Remove leading spaces. + while (*end_pos && isspace(*end_pos)) { + end_pos++; + } + if (*end_pos) { + // Append the string after target attribute. + mime_without_attribute = std::string(mime, start_pos - mime); + mime_without_attribute.append(end_pos); + } else { + // Target attribute is the last one. Remove trailing spaces. + size_t mime_length = start_pos - mime; + while (mime_length > 0 && (isspace(mime[mime_length - 1]))) { + mime_length--; } + mime_without_attribute = std::string(mime, mime_length); } - - // Parse the mime string and create an entry. - GetParsedMimeAndSupportability(mime_string); - // Update the supportability again. - CacheSupportability(mime_string, supportability); - } - - // ClearCachedSupportabilities() will reset all cached |supportability|, but - // will not remove parsed mime infos. - void ClearCachedSupportabilities() { - ScopedLock scoped_lock(mutex_); - for (auto& iter : entries_) { - iter.second.supportability = kSupportabilityUnknown; + } else { + // It can't find a trailing ';'. The target attribute must be the last one. + size_t mime_length = start_pos - mime; + // Remove trailing spaces. + while (mime_length > 0 && (isspace(mime[mime_length - 1]))) { + mime_length--; + } + // Remove the trailing ';'. + if (mime_length > 0 && mime[mime_length - 1] == ';') { + mime_length--; + } + mime_without_attribute = std::string(mime, mime_length); + if (attribute_string) { + *attribute_string = std::string(start_pos); } } + return mime_without_attribute; +} - void SetCacheMaxSize(int size) { max_size_ = size; } +// Note that if bitrate parsing failed, |bitrate| will be set to -1. +void StripAndParseBitrate(const char* mime, + std::string* mime_without_bitrate, + int* bitrate) { + SB_DCHECK(mime_without_bitrate); + SB_DCHECK(bitrate); - void DumpCache() { - ScopedLock scoped_lock(mutex_); - std::stringstream ss; - ss << "\n========Dumping MimeInfoCache========"; - for (const auto& entry_iter : entries_) { - const ParsedMimeInfo& mime_info = entry_iter.second.mime_info; - ss << "\nMime: " << entry_iter.first; - ss << "\n ParsedMimeInfo:"; - ss << "\n MimeType : " << mime_info.mime_type().ToString(); - if (mime_info.is_valid()) { - if (mime_info.has_audio_info()) { - const ParsedMimeInfo::AudioCodecInfo& audio_info = - mime_info.audio_info(); - ss << "\n Audio Codec : " - << GetMediaAudioCodecName(audio_info.codec); - ss << "\n Channels : " << audio_info.channels; - } - if (mime_info.has_video_info()) { - const ParsedMimeInfo::VideoCodecInfo& video_info = - mime_info.video_info(); - ss << "\n Video Codec : " - << GetMediaVideoCodecName(video_info.codec); - ss << "\n Profile : " << video_info.profile; - ss << "\n Level : " << video_info.level; - ss << "\n BitDepth : " << video_info.bit_depth; - ss << "\n PrimaryId : " - << GetMediaPrimaryIdName(video_info.primary_id); - ss << "\n TransferId : " - << GetMediaTransferIdName(video_info.transfer_id); - ss << "\n MatrixId : " - << GetMediaMatrixIdName(video_info.matrix_id); - ss << "\n Width : " << video_info.frame_width; - ss << "\n Height : " << video_info.frame_height; - ss << "\n Fps : " << video_info.fps; - ss << "\n DecodeToTexture : " - << (video_info.decode_to_texture_required ? "true" : "false"); - } - } else { - ss << "\n Mime info is not valid"; - } + std::string bitrate_string; + *mime_without_bitrate = + RemoveAttributeFromMime(mime, "bitrate", &bitrate_string); - ss << "\n Supportability: "; - switch (entry_iter.second.supportability) { - case kSupportabilityUnknown: - ss << "Unknown"; - break; - case kSupportabilitySupported: - ss << "Supported"; - break; - case kSupportabilityNotSupported: - ss << "NotSupported"; - break; - } - } - ss << "\n========End of Dumping========"; - - SB_DLOG(INFO) << ss.str(); + if (bitrate_string.empty()) { + *bitrate = 0; + return; } - private: - typedef std::unordered_map<std::string, Entry> Entries; - - Mutex mutex_; - Entries entries_; - std::queue<Entries::iterator> fifo_queue_; - std::atomic_int max_size_{kDefaultCacheMaxSize}; -}; - -SB_ONCE_INITIALIZE_FUNCTION(MimeSupportabilityContainer, GetContainer); + MimeType::Param param; + if (!MimeType::ParseParamString(bitrate_string, ¶m) || + param.type != MimeType::kParamTypeInteger) { + *bitrate = -1; + return; + } + SB_DCHECK(param.name == "bitrate"); + *bitrate = param.int_value; +} } // namespace @@ -182,43 +152,182 @@ SB_ONCE_INITIALIZE_FUNCTION(MimeSupportabilityCache, MimeSupportabilityCache::GetInstance); -void MimeSupportabilityCache::SetCacheMaxSize(size_t size) { - GetContainer()->SetCacheMaxSize(size); -} - Supportability MimeSupportabilityCache::GetMimeSupportability( - const std::string& mime, + const char* mime, ParsedMimeInfo* mime_info) { - // Get cached parsed mime infos and supportability. If no cache is found, - // MimeSupportabilityContainer will parse the mime string, and return a parsed - // MimeType and its parsed audio/video information. - const MimeSupportabilityContainer::Entry& entry = - GetContainer()->GetParsedMimeAndSupportability(mime); + SB_DCHECK(mime); + SB_DCHECK(mime_info); - if (mime_info) { - // Return cached ParsedMimeInfo. - *mime_info = entry.mime_info; + // Strip the bitrate from mime string and check it separately. + std::string mime_without_bitrate; + int bitrate; + StripAndParseBitrate(mime, &mime_without_bitrate, &bitrate); + + if (bitrate < 0) { + // The mime string contains an invalid bitrate attribute. In that case, we + // return an invalid ParsedMimeInfo with kSbMediaSupportTypeNotSupported. + *mime_info = ParsedMimeInfo(mime); + return kSupportabilityNotSupported; } - return is_enabled_ ? entry.supportability : kSupportabilityUnknown; + ScopedLock scoped_lock(mutex_); + Entry& entry = GetEntry_Locked(mime_without_bitrate); + + // Return cached ParsedMimeInfo with real bitrate. + *mime_info = entry.mime_info; + mime_info->SetBitrate(bitrate); + + if (!mime_info->is_valid()) { + // Return kSupportabilityNotSupported if we can't get a valid + // ParsedMimeInfo. + return kSupportabilityNotSupported; + } + + return is_enabled_ ? IsBitrateSupported_Locked(entry, bitrate) + : kSupportabilityUnknown; } void MimeSupportabilityCache::CacheMimeSupportability( - const std::string& mime, + const char* mime, Supportability supportability) { + SB_DCHECK(mime); + SB_DCHECK(supportability != kSupportabilityUnknown); + if (!is_enabled_) { return; } - if (supportability == kSupportabilityUnknown) { - SB_LOG(WARNING) << "Rejected unknown supportability."; + + // Strip bitrate as what we do in GetMimeSupportability(). + std::string mime_without_bitrate; + int bitrate; + StripAndParseBitrate(mime, &mime_without_bitrate, &bitrate); + + if (bitrate < 0) { + // The mime string contains an invalid bitrate attribute. return; } - GetContainer()->CacheSupportability(mime, supportability); + ScopedLock scoped_lock(mutex_); + Entry& entry = GetEntry_Locked(mime_without_bitrate); + + if (entry.mime_info.is_valid()) { + UpdateBitrateSupportability_Locked(&entry, bitrate, supportability); + } } void MimeSupportabilityCache::ClearCachedMimeSupportabilities() { - GetContainer()->ClearCachedSupportabilities(); + ScopedLock scoped_lock(mutex_); + for (auto& iter : entries_) { + iter.second.max_supported_bitrate = -1; + iter.second.min_unsupported_bitrate = INT_MAX; + } +} + +void MimeSupportabilityCache::DumpCache() { + ScopedLock scoped_lock(mutex_); + std::stringstream ss; + ss << "\n========Dumping MimeSupportabilityCache========"; + for (const auto& entry_iter : entries_) { + const ParsedMimeInfo& mime_info = entry_iter.second.mime_info; + ss << "\nMime: " << entry_iter.first; + ss << "\n ParsedMimeInfo:"; + ss << "\n MimeType : " << mime_info.mime_type().ToString(); + if (mime_info.is_valid()) { + if (mime_info.has_audio_info()) { + const ParsedMimeInfo::AudioCodecInfo& audio_info = + mime_info.audio_info(); + ss << "\n Audio Codec : " + << GetMediaAudioCodecName(audio_info.codec); + ss << "\n Channels : " << audio_info.channels; + } + if (mime_info.has_video_info()) { + const ParsedMimeInfo::VideoCodecInfo& video_info = + mime_info.video_info(); + ss << "\n Video Codec : " + << GetMediaVideoCodecName(video_info.codec); + ss << "\n Profile : " << video_info.profile; + ss << "\n Level : " << video_info.level; + ss << "\n BitDepth : " << video_info.bit_depth; + ss << "\n PrimaryId : " + << GetMediaPrimaryIdName(video_info.primary_id); + ss << "\n TransferId : " + << GetMediaTransferIdName(video_info.transfer_id); + ss << "\n MatrixId : " << GetMediaMatrixIdName(video_info.matrix_id); + ss << "\n Width : " << video_info.frame_width; + ss << "\n Height : " << video_info.frame_height; + ss << "\n Fps : " << video_info.fps; + ss << "\n DecodeToTexture : " + << (video_info.decode_to_texture_required ? "true" : "false"); + } + } else { + ss << "\n Mime info is not valid"; + } + + ss << "\n MaxSupportedBitrate: " + << entry_iter.second.max_supported_bitrate; + ss << "\n MinUnsupportedBitrate: " + << entry_iter.second.min_unsupported_bitrate; + } + ss << "\n========End of Dumping========"; + + SB_DLOG(INFO) << ss.str(); +} + +MimeSupportabilityCache::Entry& MimeSupportabilityCache::GetEntry_Locked( + const std::string& mime_string) { + auto entry_iter = entries_.find(mime_string); + if (entry_iter != entries_.end()) { + return entry_iter->second; + } + + // We can't find anything from the cache. Parse mime string and cache + // parsed ParsedMimeInfo. + auto insert_result = entries_.insert({mime_string, Entry(mime_string)}); + + // Keep cached items not exceeding max size. + fifo_queue_.push(insert_result.first); + while (fifo_queue_.size() > max_size_) { + entries_.erase(fifo_queue_.front()); + fifo_queue_.pop(); + } + SB_DCHECK(entries_.size() == fifo_queue_.size()); + + return insert_result.first->second; +} + +Supportability MimeSupportabilityCache::IsBitrateSupported_Locked( + const Entry& entry, + int bitrate) const { + SB_DCHECK(bitrate >= 0); + + if (bitrate <= entry.max_supported_bitrate) { + return kSupportabilitySupported; + } + if (bitrate >= entry.min_unsupported_bitrate) { + return kSupportabilityNotSupported; + } + return kSupportabilityUnknown; +} + +void MimeSupportabilityCache::UpdateBitrateSupportability_Locked( + Entry* entry, + int bitrate, + Supportability supportability) { + SB_DCHECK(entry); + SB_DCHECK(bitrate >= 0); + SB_DCHECK(supportability != kSupportabilityUnknown); + + if (supportability == kSupportabilitySupported) { + SB_DCHECK(bitrate < entry->min_unsupported_bitrate); + if (bitrate > entry->max_supported_bitrate) { + entry->max_supported_bitrate = bitrate; + } + } else if (supportability == kSupportabilityNotSupported) { + SB_DCHECK(bitrate > entry->max_supported_bitrate); + if (bitrate < entry->min_unsupported_bitrate) { + entry->min_unsupported_bitrate = bitrate; + } + } } } // namespace media
diff --git a/starboard/shared/starboard/media/mime_supportability_cache.h b/starboard/shared/starboard/media/mime_supportability_cache.h index 7eb3d4f..c1e560b 100644 --- a/starboard/shared/starboard/media/mime_supportability_cache.h +++ b/starboard/shared/starboard/media/mime_supportability_cache.h
@@ -16,8 +16,11 @@ #define STARBOARD_SHARED_STARBOARD_MEDIA_MIME_SUPPORTABILITY_CACHE_H_ #include <atomic> +#include <queue> #include <string> +#include <unordered_map> +#include "starboard/common/mutex.h" #include "starboard/shared/internal_only.h" #include "starboard/shared/starboard/media/parsed_mime_info.h" @@ -32,34 +35,68 @@ kSupportabilityNotSupported, } Supportability; -// TODO: add unit tests for MimeSupportabilityCache +// MimeSupportabilityCache caches the supportabilities of raw mime strings. +// To increase cache hit rate, it strips bitrate from the raw mime string, and +// stores a supported bitrate range for mime strings with same other attributes. +// +// Note: MimeSupportabilityCache leverage the assumption that if the +// platform can support a codec with bitrate of n, the codec should also support +// any bitrate less than n. If that assumption is not true, please do NOT enable +// MimeSupportabilityCache. +// +// Note: anytime the platform codec capabilities have changed, please call +// MimeSupportabilityCache::ClearCachedMimeSupportabilities() to clear the +// outdated results. +// +// TODO: add unit tests for MimeSupportabilityCache. class MimeSupportabilityCache { public: static MimeSupportabilityCache* GetInstance(); - // When cache is not enabled, GetMimeSupportability() will always return - // kSupportabilityUnknown, and CacheMimeSupportability() will do nothing, - // but GetMimeSupportability() will still return parsed ParsedMimeInfo. + // When cache is not enabled, GetMimeSupportability() will return cached + // ParsedMimeInfo with kSupportabilityUnknown, and CacheMimeSupportability() + // will do nothing. bool IsEnabled() const { return is_enabled_; } void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; } - void SetCacheMaxSize(size_t size); + // Set the max number of the cached ParsedMimeInfos and its supportabilities. + void SetCacheMaxSize(size_t size) { max_size_ = size; } - // Get cached mime supportability. The parsed mime information would be - // returned via |mime_info| if it is not NULL. - Supportability GetMimeSupportability(const std::string& mime, + // Get cached ParsedMimeInfo and mime supportability. If there's no cached + // ParsedMimeInfo, it will parse the mime string and cache the result. + // If we cannot get a valid ParsedMimeInfo from |mime|, + // GetMimeSupportability() will return kSupportabilityNotSupported with an + // invalid ParsedMimeInfo. Ideally, we should decouple mime parsing and + // supportability cache, but considering that the cache is only for internal + // use, to avoid repeated lookups, we do parsing in this function for now. + // Note that |mime| and |mime_info| cannot be null. + Supportability GetMimeSupportability(const char* mime, ParsedMimeInfo* mime_info); - // Cache mime supportability. If there's no cached parsed mime info and - // supportability for the mime, the function will parse the mime first and - // then update its supportability. - void CacheMimeSupportability(const std::string& mime, - Supportability supportability); - // Clear all cached supportabilities. Note that it will not remove cached - // parsed mime infos. + // Update cached supportability of the mime string. + // Note that if |supportability| is kSupportabilityUnknown or we cannot + // get a valid ParsedMimeInfo from |mime|, CacheMimeSupportability() + // will not cache the supportability. + void CacheMimeSupportability(const char* mime, Supportability supportability); + + // Clear all cached supportabilities. But it will not remove cached + // ParsedMimeInfos, as for the same mime string, the parsed results should be + // always the same. void ClearCachedMimeSupportabilities(); + void DumpCache(); + private: + const int kDefaultCacheMaxSize = 2000; + + struct Entry { + ParsedMimeInfo mime_info; + int max_supported_bitrate = -1; + int min_unsupported_bitrate = INT_MAX; + + explicit Entry(const std::string& mime) : mime_info(mime) {} + }; + // Class can only be instanced via the singleton MimeSupportabilityCache() {} ~MimeSupportabilityCache() {} @@ -67,6 +104,19 @@ MimeSupportabilityCache(const MimeSupportabilityCache&) = delete; MimeSupportabilityCache& operator=(const MimeSupportabilityCache&) = delete; + Entry& GetEntry_Locked(const std::string& mime_string); + Supportability IsBitrateSupported_Locked(const Entry& entry, + int bitrate) const; + void UpdateBitrateSupportability_Locked(Entry* entry, + int bitrate, + Supportability supportability); + + typedef std::unordered_map<std::string, Entry> Entries; + + Mutex mutex_; + Entries entries_; + std::queue<Entries::iterator> fifo_queue_; + std::atomic_int max_size_{kDefaultCacheMaxSize}; std::atomic_bool is_enabled_{false}; };
diff --git a/starboard/shared/starboard/media/mime_util.cc b/starboard/shared/starboard/media/mime_util.cc index 518338f..6c53aa7 100644 --- a/starboard/shared/starboard/media/mime_util.cc +++ b/starboard/shared/starboard/media/mime_util.cc
@@ -21,7 +21,6 @@ #include "starboard/common/log.h" #include "starboard/common/media.h" #include "starboard/log.h" -#include "starboard/shared/starboard/media/bitrate_supportability_cache.h" #include "starboard/shared/starboard/media/key_system_supportability_cache.h" #include "starboard/shared/starboard/media/media_support_internal.h" #include "starboard/shared/starboard/media/mime_supportability_cache.h" @@ -35,91 +34,6 @@ namespace { -// RemoveAttributeFromMime() will return a new mime string with the specified -// attribute removed. If |attribute_string| is not null, the removed attribute -// string will be returned via |attribute_string|. Following are some examples: -// mime: "video/webm; codecs=\"vp9\"; bitrate=300000" -// attribute_name: "bitrate" -// return: "video/webm; codecs=\"vp9\"" -// attribute_string: "bitrate=300000" -// -// mime: "video/webm; codecs=\"vp9\"; bitrate=300000; eotf=bt709" -// attribute_name: "bitrate" -// return: "video/webm; codecs=\"vp9\"; eotf=bt709" -// attribute_string: "bitrate=300000" -// -// mime: "bitrate=300000" -// attribute_name: "bitrate" -// return: "" -// attribute_string: "bitrate=300000" -std::string RemoveAttributeFromMime(const char* mime, - const char* attribute_name, - std::string* attribute_string) { - size_t name_length = strlen(attribute_name); - if (name_length == 0) { - return mime; - } - - std::string mime_without_attribute; - const char* start_pos = strstr(mime, attribute_name); - while (start_pos) { - if ((start_pos == mime || start_pos[-1] == ';' || isspace(start_pos[-1])) && - (start_pos[name_length] && - (start_pos[name_length] == '=' || isspace(start_pos[name_length])))) { - break; - } - start_pos += name_length; - start_pos = strstr(start_pos, attribute_name); - } - - if (!start_pos) { - // Target attribute is not found. - return std::string(mime); - } - const char* end_pos = strstr(start_pos, ";"); - if (end_pos) { - // There may be other attribute after target attribute. - if (attribute_string) { - // Returned |attribute_string| will not have a trailing ';'. - attribute_string->assign(start_pos, end_pos - start_pos); - } - - end_pos++; - // Remove leading spaces. - while (*end_pos && isspace(*end_pos)) { - end_pos++; - } - if (*end_pos) { - // Append the string after target attribute. - mime_without_attribute = std::string(mime, start_pos - mime); - mime_without_attribute.append(end_pos); - } else { - // Target attribute is the last one. Remove trailing spaces. - size_t mime_length = start_pos - mime; - while (mime_length > 0 && (isspace(mime[mime_length - 1]))) { - mime_length--; - } - mime_without_attribute = std::string(mime, mime_length); - } - } else { - // It can't find a trailing ';'. The target attribute must be the last one. - size_t mime_length = start_pos - mime; - // Remove trailing spaces. - while (mime_length > 0 && (isspace(mime[mime_length - 1]))) { - mime_length--; - } - // Remove the trailing ';'. - if (mime_length > 0 && mime[mime_length - 1] == ';') { - mime_length--; - } - mime_without_attribute = std::string(mime, mime_length); - if (attribute_string) { - *attribute_string = std::string(start_pos); - } - } - return mime_without_attribute; -} - // Use SbMediaGetAudioConfiguration() to check if the platform can support // |channels|. bool IsAudioOutputSupported(SbMediaAudioCodingType coding_type, int channels) { @@ -255,22 +169,6 @@ video_info.decode_to_texture_required); } -bool ValidateAndParseBitrate(const std::string& bitrate_string, int* bitrate) { - SB_DCHECK(!bitrate_string.empty()); - - MimeType::Param param; - if (!MimeType::ParseParamString(bitrate_string, ¶m)) { - return false; - } - if (param.type != MimeType::kParamTypeInteger) { - return false; - } - if (bitrate) { - *bitrate = param.int_value; - } - return true; -} - } // namespace SbMediaSupportType CanPlayMimeAndKeySystem(const char* mime, @@ -278,38 +176,19 @@ SB_DCHECK(mime); SB_DCHECK(key_system); - // Remove bitrate from mime string and read bitrate if presents. - std::string bitrate_string; - std::string mime_without_bitrate = - RemoveAttributeFromMime(mime, "bitrate", &bitrate_string); - int bitrate = 0; - if (!bitrate_string.empty()) { - if (!ValidateAndParseBitrate(bitrate_string, &bitrate)) { - return kSbMediaSupportTypeNotSupported; - } - } - - if (bitrate < 0) { - // Reject invalid bitrate. - return kSbMediaSupportTypeNotSupported; - } - - // Get cached parsed mime infos and supportability. If it is not found in the - // cache, MimeSupportabilityCache would parse the mime string and return a - // ParsedMimeInfo. + // Get cached ParsedMimeInfo with its supportability. If it is not found in + // the cache, MimeSupportabilityCache would parse the mime string and return + // the ParsedMimeInfo with kSupportabilityUnknown. ParsedMimeInfo mime_info; Supportability mime_supportability = - MimeSupportabilityCache::GetInstance()->GetMimeSupportability( - mime_without_bitrate, &mime_info); - // Overwrite the bitrate. - mime_info.SetBitrate(bitrate); + MimeSupportabilityCache::GetInstance()->GetMimeSupportability(mime, + &mime_info); if (mime_info.disable_cache()) { // Disable all caches if required. mime_supportability = kSupportabilityUnknown; MimeSupportabilityCache::GetInstance()->SetCacheEnabled(false); KeySystemSupportabilityCache::GetInstance()->SetCacheEnabled(false); - BitrateSupportabilityCache::GetInstance()->SetCacheEnabled(false); } // Reject mime if cached result is not supported. @@ -317,10 +196,10 @@ return kSbMediaSupportTypeNotSupported; } - // Reject mime if parsed mime info is invalid. - if (!mime_info.is_valid()) { - return kSbMediaSupportTypeNotSupported; - } + // MimeSupportabilityCache::GetMimeSupportability() returns + // kSupportabilityNotSupported if ParsedMimeInfo is not valid, so |mime_info| + // must be valid here. + SB_DCHECK(mime_info.is_valid()); const MimeType& mime_type = mime_info.mime_type(); const std::vector<std::string>& codecs = mime_type.GetCodecs(); @@ -379,26 +258,14 @@ } } - // Get cached bitrate supportability. - Supportability bitrate_supportability = - BitrateSupportabilityCache::GetInstance()->GetBitrateSupportability( - mime_info); - - // Reject mime if bitrate is not supported. - if (bitrate_supportability == kSupportabilityNotSupported) { - return kSbMediaSupportTypeNotSupported; - } - - // Return supported if mime and bitrate are all supported. - if (mime_supportability == kSupportabilitySupported && - bitrate_supportability == kSupportabilitySupported) { + // At this point, |key_system| is supported. Return supported here if + // mime is also supported. Otherwise, |mime_supportability| must be unknown. + if (mime_supportability == kSupportabilitySupported) { return kSbMediaSupportTypeProbably; } + SB_DCHECK(mime_supportability == kSupportabilityUnknown); - // At this point, either mime or bitrate supportability must be unknown. - // Call platform functions to check if they are supported. - SB_DCHECK(mime_supportability == kSupportabilityUnknown || - bitrate_supportability == kSupportabilityUnknown); + // Call platform functions to check if it's supported. if (mime_info.has_audio_info() && !IsSupportedAudioCodec(mime_info)) { mime_supportability = kSupportabilityNotSupported; } else if (mime_info.has_video_info() && !IsSupportedVideoCodec(mime_info)) { @@ -407,13 +274,10 @@ mime_supportability = kSupportabilitySupported; } - // Cache mime supportability when bitrate supportability is known. - if (bitrate_supportability == kSupportabilitySupported) { - MimeSupportabilityCache::GetInstance()->CacheMimeSupportability( - mime_without_bitrate, mime_supportability); - } + // Cache mime supportability. + MimeSupportabilityCache::GetInstance()->CacheMimeSupportability( + mime, mime_supportability); - SB_DCHECK(mime_supportability != kSupportabilityUnknown); return mime_supportability == kSupportabilitySupported ? kSbMediaSupportTypeProbably : kSbMediaSupportTypeNotSupported;
diff --git a/starboard/shared/starboard/player/player_create.cc b/starboard/shared/starboard/player/player_create.cc index 16bc57f..7ffb717 100644 --- a/starboard/shared/starboard/player/player_create.cc +++ b/starboard/shared/starboard/player/player_create.cc
@@ -138,8 +138,9 @@ const int64_t kDefaultBitRate = 0; if (audio_codec != kSbMediaAudioCodecNone) { const MimeType audio_mime_type(audio_mime); - if (!SbMediaIsAudioSupported(audio_codec, &audio_mime_type, - kDefaultBitRate)) { + if (!SbMediaIsAudioSupported( + audio_codec, strlen(audio_mime) > 0 ? &audio_mime_type : nullptr, + kDefaultBitRate)) { SB_LOG(ERROR) << "Unsupported audio codec " << starboard::GetMediaAudioCodecName(audio_codec) << "."; player_error_func(kSbPlayerInvalid, context, kSbPlayerErrorDecode, @@ -160,11 +161,11 @@ if (video_codec != kSbMediaVideoCodecNone) { const MimeType video_mime_type(video_mime); if (!SbMediaIsVideoSupported( - video_codec, &video_mime_type, kDefaultProfile, kDefaultLevel, - kDefaultColorDepth, kSbMediaPrimaryIdUnspecified, - kSbMediaTransferIdUnspecified, kSbMediaMatrixIdUnspecified, - kDefaultFrameWidth, kDefaultFrameHeight, kDefaultBitRate, - kDefaultFrameRate, + video_codec, strlen(video_mime) > 0 ? &video_mime_type : nullptr, + kDefaultProfile, kDefaultLevel, kDefaultColorDepth, + kSbMediaPrimaryIdUnspecified, kSbMediaTransferIdUnspecified, + kSbMediaMatrixIdUnspecified, kDefaultFrameWidth, + kDefaultFrameHeight, kDefaultBitRate, kDefaultFrameRate, output_mode == kSbPlayerOutputModeDecodeToTexture)) { SB_LOG(ERROR) << "Unsupported video codec " << starboard::GetMediaVideoCodecName(video_codec) << ".";