Import Cobalt 22.lts.4.307648
diff --git a/src/.codespellignorelines b/src/.codespellignorelines
index 4e787e0..e2b2905 100644
--- a/src/.codespellignorelines
+++ b/src/.codespellignorelines
@@ -1 +1,3 @@
- vp9WhiteList.get("Technicolor").add("STING");
+ vp9AllowList.get("Technicolor").add("STING");
+ return SkSurface::MakeRenderTarget(gr_context_.get(), SkBudgeted::kNo,
+ texture_infos.push_back(TextureInfo("uv", "ba"));
diff --git a/src/base/strings/utf_string_conversions.cc b/src/base/strings/utf_string_conversions.cc
index a83d106..b14410c 100644
--- a/src/base/strings/utf_string_conversions.cc
+++ b/src/base/strings/utf_string_conversions.cc
@@ -200,7 +200,10 @@
bool res = DoUTFConversion(src_str.data(), src_len32, dest, &dest_len32);
dest_str->resize(dest_len32);
- dest_str->shrink_to_fit();
+ // dest_str->shrink_to_fit();
+ // Workaround for missing symbols linking issue for Xcode 13.2.1
+ // Mac builds.
+ dest_str->reserve(0);
return res;
}
diff --git a/src/base/test/scoped_task_environment.cc b/src/base/test/scoped_task_environment.cc
index 50a4632..92daedf 100644
--- a/src/base/test/scoped_task_environment.cc
+++ b/src/base/test/scoped_task_environment.cc
@@ -267,6 +267,11 @@
mock_time_task_runner_->FastForwardBy(delta);
}
+void ScopedTaskEnvironment::AdvanceMockTickClock(TimeDelta delta) {
+ DCHECK(mock_time_task_runner_);
+ mock_time_task_runner_->AdvanceMockTickClock(delta);
+}
+
void ScopedTaskEnvironment::FastForwardUntilNoTasksRemain() {
DCHECK(mock_time_task_runner_);
mock_time_task_runner_->FastForwardUntilNoTasksRemain();
diff --git a/src/base/test/scoped_task_environment.h b/src/base/test/scoped_task_environment.h
index 2e46073..0f5aa59 100644
--- a/src/base/test/scoped_task_environment.h
+++ b/src/base/test/scoped_task_environment.h
@@ -116,6 +116,8 @@
// (currently only main thread time is mocked).
void FastForwardBy(TimeDelta delta);
+ void AdvanceMockTickClock(TimeDelta delta);
+
// Only valid for instances with a MOCK_TIME MainThreadType.
// Short for FastForwardBy(TimeDelta::Max()).
void FastForwardUntilNoTasksRemain();
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index 303a70b..36b5eff 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -605,6 +605,11 @@
#endif // defined(ENABLE_DEBUGGER) && defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
{
DCHECK(!quit_closure_.is_null());
+ if (should_preload) {
+ preload_timestamp_ = timestamp;
+ } else {
+ start_timestamp_ = timestamp;
+ }
// Check to see if a timed_trace has been set, indicating that we should
// begin a timed trace upon startup.
base::TimeDelta trace_duration = GetTimedTraceDuration();
@@ -823,6 +828,12 @@
options.web_module_options.csp_enforcement_mode = dom::kCspEnforcementEnable;
options.requested_viewport_size = requested_viewport_size;
+
+ // Set callback to collect unload event time before firing document's unload
+ // event.
+ options.web_module_options.collect_unload_event_time_callback = base::Bind(
+ &Application::CollectUnloadEventTimingInfo, base::Unretained(this));
+
account_manager_.reset(new account::AccountManager());
storage_manager_.reset(new storage::StorageManager(
@@ -866,9 +877,6 @@
#endif
options));
- DCHECK(browser_module_);
- browser_module_->SetApplicationStartOrPreloadTimestamp(should_preload,
- timestamp);
UpdateUserAgent();
// Register event callbacks.
@@ -1027,10 +1035,12 @@
base::Bind(&Application::Start, base::Unretained(this), timestamp));
return;
}
+ DCHECK_CALLED_ON_VALID_THREAD(application_event_thread_checker_);
+ if (!start_timestamp_.has_value()) {
+ start_timestamp_ = timestamp;
+ }
OnApplicationEvent(kSbEventTypeStart, timestamp);
- browser_module_->SetApplicationStartOrPreloadTimestamp(false /*is_preload*/,
- timestamp);
}
void Application::Quit() {
@@ -1039,6 +1049,7 @@
FROM_HERE, base::Bind(&Application::Quit, base::Unretained(this)));
return;
}
+ DCHECK_CALLED_ON_VALID_THREAD(application_event_thread_checker_);
quit_closure_.Run();
}
@@ -1399,14 +1410,34 @@
}
#endif
-void Application::WebModuleCreated() {
+void Application::WebModuleCreated(WebModule* web_module) {
TRACE_EVENT0("cobalt::browser", "Application::WebModuleCreated()");
+ DCHECK(web_module);
+ if (preload_timestamp_.has_value()) {
+ web_module->SetApplicationStartOrPreloadTimestamp(
+ true, preload_timestamp_.value());
+ }
+ if (start_timestamp_.has_value()) {
+ web_module->SetApplicationStartOrPreloadTimestamp(false,
+ start_timestamp_.value());
+ }
DispatchDeepLinkIfNotConsumed();
#if defined(ENABLE_WEBDRIVER)
if (web_driver_module_) {
web_driver_module_->OnWindowRecreated();
}
#endif
+ if (!unload_event_start_time_.is_null() ||
+ !unload_event_end_time_.is_null()) {
+ web_module->SetUnloadEventTimingInfo(unload_event_start_time_,
+ unload_event_end_time_);
+ }
+}
+
+void Application::CollectUnloadEventTimingInfo(base::TimeTicks start_time,
+ base::TimeTicks end_time) {
+ unload_event_start_time_ = start_time;
+ unload_event_end_time_ = end_time;
}
Application::CValStats::CValStats()
diff --git a/src/cobalt/browser/application.h b/src/cobalt/browser/application.h
index c08330c..229ba18 100644
--- a/src/cobalt/browser/application.h
+++ b/src/cobalt/browser/application.h
@@ -29,6 +29,7 @@
#include "cobalt/browser/browser_module.h"
#include "cobalt/browser/memory_tracker/tool.h"
#include "cobalt/system_window/system_window.h"
+#include "starboard/time.h"
#if SB_IS(EVERGREEN)
#include "cobalt/updater/updater_module.h"
#endif
@@ -68,8 +69,7 @@
void OnNetworkEvent(const base::Event* event);
// Called to handle an application event.
- void OnApplicationEvent(SbEventType event_type,
- SbTimeMonotonic timestamp);
+ void OnApplicationEvent(SbEventType event_type, SbTimeMonotonic timestamp);
// Called to handle a window size change event.
void OnWindowSizeChangedEvent(const base::Event* event);
@@ -95,7 +95,10 @@
#endif
// Called when a navigation occurs in the BrowserModule.
- void WebModuleCreated();
+ void WebModuleCreated(WebModule* web_module);
+
+ void CollectUnloadEventTimingInfo(base::TimeTicks start_time,
+ base::TimeTicks end_time);
// A conduit for system events.
base::EventDispatcher event_dispatcher_;
@@ -195,6 +198,14 @@
void UpdatePeriodicStats();
void DispatchEventInternal(base::Event* event);
+ base::Optional<SbTimeMonotonic> preload_timestamp_;
+ base::Optional<SbTimeMonotonic> start_timestamp_;
+
+ // These represent the 'document unload timing info' from the spec to be
+ // passed to the next document.
+ base::TimeTicks unload_event_start_time_;
+ base::TimeTicks unload_event_end_time_;
+
// The message loop that will handle UI events.
base::MessageLoop* message_loop_;
@@ -230,8 +241,7 @@
void OnDeepLinkConsumedCallback(const std::string& link);
// Dispatch events for deep links.
- void DispatchDeepLink(const char* link,
- SbTimeMonotonic timestamp);
+ void DispatchDeepLink(const char* link, SbTimeMonotonic timestamp);
void DispatchDeepLinkIfNotConsumed();
DISALLOW_COPY_AND_ASSIGN(Application);
diff --git a/src/cobalt/browser/browser_bindings_gen.gyp b/src/cobalt/browser/browser_bindings_gen.gyp
index bce6391..f7f97fe 100644
--- a/src/cobalt/browser/browser_bindings_gen.gyp
+++ b/src/cobalt/browser/browser_bindings_gen.gyp
@@ -137,6 +137,7 @@
'../dom/performance_lifecycle_timing.idl',
'../dom/performance_mark.idl',
'../dom/performance_measure.idl',
+ '../dom/performance_navigation_timing.idl',
'../dom/performance_observer.idl',
'../dom/performance_observer_entry_list.idl',
'../dom/performance_resource_timing.idl',
@@ -284,6 +285,7 @@
'../dom/media_source_ready_state.idl',
'../dom/mouse_event_init.idl',
'../dom/mutation_observer_init.idl',
+ '../dom/navigation_type.idl',
'../dom/navigator_ua_brand_version.idl',
'../dom/performance_observer_callback_options.idl',
'../dom/performance_observer_init.idl',
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index 5bea38e..7cf9008 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -71,55 +71,9 @@
namespace cobalt {
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
-namespace timestamp {
-// This is a temporary workaround.
-extern SbAtomic64 g_last_render_timestamp;
-} // namespace timestamp
-
-namespace {
-struct NonTrivialGlobalVariables {
- NonTrivialGlobalVariables();
-
- SbAtomic64* last_render_timestamp;
-};
-
-NonTrivialGlobalVariables::NonTrivialGlobalVariables() {
- last_render_timestamp = &cobalt::timestamp::g_last_render_timestamp;
- SbAtomicNoBarrier_Exchange64(last_render_timestamp,
- static_cast<SbAtomic64>(SbTimeGetNow()));
-}
-
-base::LazyInstance<NonTrivialGlobalVariables>::DestructorAtExit
- non_trivial_global_variables = LAZY_INSTANCE_INITIALIZER;
-} // namespace
-#endif // defined(COBALT_CHECK_RENDER_TIMEOUT)
-
namespace browser {
namespace {
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
-// Timeout for last render.
-const int kLastRenderTimeoutSeconds = 15;
-
-// Polling interval for timeout_polling_thread_.
-const int kRenderTimeOutPollingDelaySeconds = 1;
-
-// Minimum number of continuous times the timeout expirations. This is used to
-// prevent unintended behavior in situations such as when returning from
-// suspended state. Note that the timeout response trigger will be delayed
-// after the actual timeout expiration by this value times the polling delay.
-const int kMinimumContinuousRenderTimeoutExpirations = 2;
-
-// Name for timeout_polling_thread_.
-const char* kTimeoutPollingThreadName = "TimeoutPolling";
-
-// This specifies the percentage of calls to OnRenderTimeout() that result in a
-// call to OnError().
-const int kRenderTimeoutErrorPercentage = 99;
-
-#endif
-
// This constant defines the maximum rate at which the layout engine will
// refresh over time. Since there is little benefit in performing a layout
// faster than the display's refresh rate, we set this to 60Hz.
@@ -295,10 +249,6 @@
#endif // defined(ENABLE_DEBUGGER)
has_resumed_(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED),
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
- timeout_polling_thread_(kTimeoutPollingThreadName),
- render_timeout_count_(0),
-#endif
on_error_retry_count_(0),
waiting_for_error_retry_(false),
will_quit_(false),
@@ -315,21 +265,8 @@
#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
SbCoreDumpRegisterHandler(BrowserModule::CoreDumpHandler, this);
on_error_triggered_count_ = 0;
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
- recovery_mechanism_triggered_count_ = 0;
- timeout_response_trigger_count_ = 0;
-#endif // defined(COBALT_CHECK_RENDER_TIMEOUT)
#endif // SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
- timeout_polling_thread_.Start();
- timeout_polling_thread_.message_loop()->task_runner()->PostDelayedTask(
- FROM_HERE,
- base::Bind(&BrowserModule::OnPollForRenderTimeout, base::Unretained(this),
- url),
- base::TimeDelta::FromSeconds(kRenderTimeOutPollingDelaySeconds));
-#endif
-
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
// Create the main web module layer.
@@ -651,7 +588,7 @@
options));
lifecycle_observers_.AddObserver(web_module_.get());
if (!web_module_created_callback_.is_null()) {
- web_module_created_callback_.Run();
+ web_module_created_callback_.Run(web_module_.get());
}
if (system_window_) {
@@ -677,12 +614,6 @@
static_cast<BrowserModule*>(browser_module_as_void);
SbCoreDumpLogInteger("BrowserModule.on_error_triggered_count_",
browser_module->on_error_triggered_count_);
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
- SbCoreDumpLogInteger("BrowserModule.recovery_mechanism_triggered_count_",
- browser_module->recovery_mechanism_triggered_count_);
- SbCoreDumpLogInteger("BrowserModule.timeout_response_trigger_count_",
- browser_module->timeout_response_trigger_count_);
-#endif // defined(COBALT_CHECK_RENDER_TIMEOUT)
}
#endif // SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
@@ -1558,52 +1489,6 @@
}
}
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
-void BrowserModule::OnPollForRenderTimeout(const GURL& url) {
- SbTime last_render_timestamp = static_cast<SbTime>(SbAtomicAcquire_Load64(
- non_trivial_global_variables.Get().last_render_timestamp));
- base::Time last_render = base::Time::FromSbTime(last_render_timestamp);
- bool timeout_expiration = base::Time::Now() - base::TimeDelta::FromSeconds(
- kLastRenderTimeoutSeconds) >
- last_render;
- bool timeout_response_trigger = false;
- if (timeout_expiration) {
- // The timeout only triggers if the timeout expiration has been detected
- // without interruption at least kMinimumContinuousRenderTimeoutExpirations
- // times.
- ++render_timeout_count_;
- timeout_response_trigger =
- render_timeout_count_ >= kMinimumContinuousRenderTimeoutExpirations;
- } else {
- render_timeout_count_ = 0;
- }
-
- if (timeout_response_trigger) {
-#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
- timeout_response_trigger_count_++;
-#endif
- SbAtomicNoBarrier_Exchange64(
- non_trivial_global_variables.Get().last_render_timestamp,
- static_cast<SbAtomic64>(kSbTimeMax));
- if (SbSystemGetRandomUInt64() <
- kRenderTimeoutErrorPercentage * (UINT64_MAX / 100)) {
- OnError(url, std::string("Rendering Timeout"));
-#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
- recovery_mechanism_triggered_count_++;
-#endif
- } else {
- SB_DLOG(INFO) << "Received OnRenderTimeout, ignoring by random chance.";
- }
- } else {
- timeout_polling_thread_.message_loop()->task_runner()->PostDelayedTask(
- FROM_HERE,
- base::Bind(&BrowserModule::OnPollForRenderTimeout,
- base::Unretained(this), url),
- base::TimeDelta::FromSeconds(kRenderTimeOutPollingDelaySeconds));
- }
-}
-#endif
-
render_tree::ResourceProvider* BrowserModule::GetResourceProvider() {
if (application_state_ == base::kApplicationStateConcealed) {
DCHECK(resource_provider_stub_);
@@ -2109,12 +1994,6 @@
return scoped_refptr<script::Wrappable>(new h5vcc::H5vcc(h5vcc_settings));
}
-void BrowserModule::SetApplicationStartOrPreloadTimestamp(
- bool is_preload, SbTimeMonotonic timestamp) {
- DCHECK(web_module_);
- web_module_->SetApplicationStartOrPreloadTimestamp(is_preload, timestamp);
-}
-
void BrowserModule::SetDeepLinkTimestamp(SbTimeMonotonic timestamp) {
DCHECK(web_module_);
web_module_->SetDeepLinkTimestamp(timestamp);
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index cec410e..891a95e 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -21,6 +21,7 @@
#include <vector>
#include "base/observer_list.h"
+#include "base/optional.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
@@ -91,6 +92,7 @@
// different subsystems together.
class BrowserModule {
public:
+ typedef base::Callback<void(WebModule*)> WebModuleCreatedCallback;
// All browser subcomponent options should have default constructors that
// setup reasonable default options.
struct Options {
@@ -103,7 +105,7 @@
renderer::RendererModule::Options renderer_module_options;
WebModule::Options web_module_options;
media::MediaModule::Options media_module_options;
- base::Closure web_module_created_callback;
+ WebModuleCreatedCallback web_module_created_callback;
memory_settings::AutoMemSettings command_line_auto_mem_settings;
memory_settings::AutoMemSettings build_auto_mem_settings;
base::Optional<GURL> fallback_splash_screen_url;
@@ -229,19 +231,13 @@
static void GetParamMap(const std::string& url,
std::map<std::string, std::string>& map);
- // Pass the application preload or start timestamps from Starboard.
- void SetApplicationStartOrPreloadTimestamp(bool is_preload,
- SbTimeMonotonic timestamp);
// Pass the deeplink timestamp from Starboard.
void SetDeepLinkTimestamp(SbTimeMonotonic timestamp);
+
private:
#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
static void CoreDumpHandler(void* browser_module_as_void);
int on_error_triggered_count_;
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
- int recovery_mechanism_triggered_count_;
- int timeout_response_trigger_count_;
-#endif // defined(COBALT_CHECK_RENDER_TIMEOUT)
#endif // SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
// Called when the WebModule's Window.onload event is fired.
@@ -394,11 +390,6 @@
// Process all messages queued into the |render_tree_submission_queue_|.
void ProcessRenderTreeSubmissionQueue();
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
- // Poll for render timeout. Called from timeout_polling_thread_.
- void OnPollForRenderTimeout(const GURL& url);
-#endif
-
// Gets the current resource provider.
render_tree::ResourceProvider* GetResourceProvider();
@@ -587,7 +578,7 @@
// This will be called after a WebModule has been recreated, which could occur
// on navigation.
- base::Closure web_module_created_callback_;
+ WebModuleCreatedCallback web_module_created_callback_;
// The time when a URL navigation starts. This is recorded after the previous
// WebModule is destroyed.
@@ -648,14 +639,6 @@
// Reset when the browser is paused, signalled to resume.
base::WaitableEvent has_resumed_;
-#if defined(COBALT_CHECK_RENDER_TIMEOUT)
- base::Thread timeout_polling_thread_;
-
- // Counts the number of continuous render timeout expirations. This value is
- // updated and used from OnPollForRenderTimeout.
- int render_timeout_count_;
-#endif
-
// The URL that Cobalt will attempt to navigate to during an OnErrorRetry()
// and also when starting from a concealed state or unfreezing from a
// frozen state. This url is set within OnError() and also when a
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc
index 01ff75d..1ae119c 100644
--- a/src/cobalt/browser/switches.cc
+++ b/src/cobalt/browser/switches.cc
@@ -232,6 +232,10 @@
#endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
+const char kCompressUpdate[] = "compress_update";
+const char kCompressUpdateHelp[] =
+ "The updater should compress the update package";
+
const char kMinLogLevel[] = "min_log_level";
const char kMinLogLevelHelp[] =
"Set the minimum logging level: info|warning|error|fatal.";
@@ -478,6 +482,7 @@
#endif // SB_API_VERSION >= 12 ||
// SB_HAS(ON_SCREEN_KEYBOARD)
#endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
+ {kCompressUpdate, kCompressUpdateHelp},
{kDisableJavaScriptJit, kDisableJavaScriptJitHelp},
{kDisableMapToMesh, kDisableMapToMeshHelp},
{kDisableTimerResolutionLimit, kDisableTimerResolutionLimitHelp},
diff --git a/src/cobalt/browser/switches.h b/src/cobalt/browser/switches.h
index fa7f6a4..b2aa24e 100644
--- a/src/cobalt/browser/switches.h
+++ b/src/cobalt/browser/switches.h
@@ -98,6 +98,8 @@
// SB_HAS(ON_SCREEN_KEYBOARD)
#endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
+extern const char kCompressUpdate[];
+extern const char kCompressUpdateHelp[];
extern const char kDisableJavaScriptJit[];
extern const char kDisableJavaScriptJitHelp[];
extern const char kDisableMapToMesh[];
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index 06600b8..73e2e02 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -52,6 +52,7 @@
#include "cobalt/dom/keyboard_event_init.h"
#include "cobalt/dom/local_storage_database.h"
#include "cobalt/dom/mutation_observer_task_manager.h"
+#include "cobalt/dom/navigation_type.h"
#include "cobalt/dom/navigator.h"
#include "cobalt/dom/pointer_event.h"
#include "cobalt/dom/storage.h"
@@ -265,6 +266,9 @@
SbTimeMonotonic timestamp);
void SetDeepLinkTimestamp(SbTimeMonotonic timestamp);
+ void SetUnloadEventTimingInfo(base::TimeTicks start_time,
+ base::TimeTicks end_time);
+
private:
class DocumentLoadedObserver;
@@ -309,6 +313,22 @@
void OnLoadComplete(const base::Optional<std::string>& error) {
if (error) error_callback_.Run(window_->location()->url(), *error);
+
+ // Create Performance navigation timing info after document loading
+ // completed.
+ DCHECK(window_);
+ DCHECK(window_->performance());
+ if (window_->GetDocumentLoader()) {
+ net::LoadTimingInfo load_timing_info =
+ window_->GetDocumentLoader()->get_load_timing_info();
+ // Check if the load happens through net mdoule.
+ bool is_load_timing_info_valid =
+ !load_timing_info.request_start.is_null();
+ if (is_load_timing_info_valid) {
+ window_->document()->CreatePerformanceNavigationTiming(
+ window_->performance(), load_timing_info);
+ }
+ }
}
// Report an error encountered while running JS.
@@ -476,6 +496,9 @@
base::WaitableEvent synchronous_loader_interrupt_ = {
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED};
+
+ base::Callback<void(base::TimeTicks, base::TimeTicks)>
+ report_unload_timing_info_callback_;
};
class WebModule::Impl::DocumentLoadedObserver : public dom::DocumentObserver {
@@ -772,6 +795,9 @@
resource_provider_, window_, data.options.debugger_state));
#endif // ENABLE_DEBUGGER
+ report_unload_timing_info_callback_ =
+ data.options.collect_unload_event_time_callback;
+
is_running_ = true;
}
@@ -782,7 +808,20 @@
global_environment_->SetReportEvalCallback(base::Closure());
global_environment_->SetReportErrorCallback(
script::GlobalEnvironment::ReportErrorCallback());
+ // Collect document's unload event start time.
+ base::TimeTicks unload_event_start_time = base::TimeTicks::Now();
+
window_->DispatchEvent(new dom::Event(base::Tokens::unload()));
+
+ // Collect document's unload event end time.
+ base::TimeTicks unload_event_end_time = base::TimeTicks::Now();
+
+ // Send the unload event start/end time back to application.
+ if (!report_unload_timing_info_callback_.is_null()) {
+ report_unload_timing_info_callback_.Run(unload_event_start_time,
+ unload_event_end_time);
+ }
+
document_load_observer_.reset();
#if defined(ENABLE_DEBUGGER)
@@ -824,7 +863,7 @@
void WebModule::Impl::InjectInputEvent(scoped_refptr<dom::Element> element,
const scoped_refptr<dom::Event>& event) {
TRACE_EVENT1("cobalt::browser", "WebModule::Impl::InjectInputEvent()",
- "event", event->type().c_str());
+ "event", TRACE_STR_COPY(event->type().c_str()));
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(is_running_);
DCHECK(window_);
@@ -1031,6 +1070,7 @@
void WebModule::Impl::SetApplicationStartOrPreloadTimestamp(
bool is_preload, SbTimeMonotonic timestamp) {
DCHECK(window_);
+ DCHECK(window_->performance());
window_->performance()->SetApplicationStartOrPreloadTimestamp(is_preload,
timestamp);
}
@@ -1040,6 +1080,14 @@
window_->performance()->SetDeepLinkTimestamp(timestamp);
}
+void WebModule::Impl::SetUnloadEventTimingInfo(base::TimeTicks start_time,
+ base::TimeTicks end_time) {
+ DCHECK(window_);
+ if (window_->document()) {
+ window_->document()->SetUnloadEventTimingInfo(start_time, end_time);
+ }
+}
+
void WebModule::Impl::OnCspPolicyChanged() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(is_running_);
@@ -1446,7 +1494,7 @@
base::Token type, const dom::InputEventInit& event) {
TRACE_EVENT1("cobalt::browser",
"WebModule::InjectOnScreenKeyboardInputEvent()", "type",
- type.c_str());
+ TRACE_STR_COPY(type.c_str()));
DCHECK(message_loop());
DCHECK(impl_);
message_loop()->task_runner()->PostTask(
@@ -1519,7 +1567,7 @@
void WebModule::InjectKeyboardEvent(base::Token type,
const dom::KeyboardEventInit& event) {
TRACE_EVENT1("cobalt::browser", "WebModule::InjectKeyboardEvent()", "type",
- type.c_str());
+ TRACE_STR_COPY(type.c_str()));
DCHECK(message_loop());
DCHECK(impl_);
message_loop()->task_runner()->PostTask(
@@ -1531,7 +1579,7 @@
void WebModule::InjectPointerEvent(base::Token type,
const dom::PointerEventInit& event) {
TRACE_EVENT1("cobalt::browser", "WebModule::InjectPointerEvent()", "type",
- type.c_str());
+ TRACE_STR_COPY(type.c_str()));
DCHECK(message_loop());
DCHECK(impl_);
message_loop()->task_runner()->PostTask(
@@ -1543,7 +1591,7 @@
void WebModule::InjectWheelEvent(base::Token type,
const dom::WheelEventInit& event) {
TRACE_EVENT1("cobalt::browser", "WebModule::InjectWheelEvent()", "type",
- type.c_str());
+ TRACE_STR_COPY(type.c_str()));
DCHECK(message_loop());
DCHECK(impl_);
message_loop()->task_runner()->PostTask(
@@ -1832,13 +1880,12 @@
TRACE_EVENT0("cobalt::browser",
"WebModule::SetApplicationStartOrPreloadTimestamp()");
DCHECK(message_loop());
- DCHECK(impl_);
if (base::MessageLoop::current() != message_loop()) {
- message_loop()->task_runner()->PostBlockingTask(
- FROM_HERE,
- base::Bind(&WebModule::Impl::SetApplicationStartOrPreloadTimestamp,
- base::Unretained(impl_.get()), is_preload, timestamp));
+ message_loop()->task_runner()->PostTask(
+ FROM_HERE, base::Bind(&WebModule::SetApplicationStartOrPreloadTimestamp,
+ base::Unretained(this), is_preload, timestamp));
} else {
+ DCHECK(impl_);
impl_->SetApplicationStartOrPreloadTimestamp(is_preload, timestamp);
}
}
@@ -1856,5 +1903,20 @@
}
}
+void WebModule::SetUnloadEventTimingInfo(base::TimeTicks start_time,
+ base::TimeTicks end_time) {
+ TRACE_EVENT0("cobalt::browser", "WebModule::SetUnloadEventTimingInfo()");
+ DCHECK(message_loop());
+ DCHECK(impl_);
+ if (base::MessageLoop::current() != message_loop()) {
+ message_loop()->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&WebModule::Impl::SetUnloadEventTimingInfo,
+ base::Unretained(impl_.get()), start_time, end_time));
+ } else {
+ impl_->SetUnloadEventTimingInfo(start_time, end_time);
+ }
+}
+
} // namespace browser
} // namespace cobalt
diff --git a/src/cobalt/browser/web_module.h b/src/cobalt/browser/web_module.h
index 20b39bb..9e1690e 100644
--- a/src/cobalt/browser/web_module.h
+++ b/src/cobalt/browser/web_module.h
@@ -271,6 +271,11 @@
// there is no playback during Concealed state, we should provide a chance
// for Cobalt to freeze.
base::Closure maybe_freeze_callback;
+
+ // This callback is for collecting previous document unload event start/end
+ // time.
+ base::Callback<void(base::TimeTicks, base::TimeTicks)>
+ collect_unload_event_time_callback;
};
typedef layout::LayoutManager::LayoutResults LayoutResults;
@@ -409,10 +414,16 @@
scoped_refptr<render_tree::Node> DoSynchronousLayoutAndGetRenderTree();
+ // Pass the application preload or start timestamps from Starboard.
void SetApplicationStartOrPreloadTimestamp(bool is_preload,
SbTimeMonotonic timestamp);
void SetDeepLinkTimestamp(SbTimeMonotonic timestamp);
+ // Set document's load timing info's unload event start/end time.
+ void SetUnloadEventTimingInfo(base::TimeTicks start_time,
+ base::TimeTicks end_time);
+
+
private:
// Data required to construct a WebModule, initialized in the constructor and
// passed to |Initialize|.
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 74c5406..bc012d6 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-306369
\ No newline at end of file
+307648
\ No newline at end of file
diff --git a/src/cobalt/content/fonts/config/android/fonts.xml b/src/cobalt/content/fonts/config/android/fonts.xml
index d26ab07..1c6c6df 100644
--- a/src/cobalt/content/fonts/config/android/fonts.xml
+++ b/src/cobalt/content/fonts/config/android/fonts.xml
@@ -165,6 +165,8 @@
<family>
<font weight="400" style="normal">NotoSansEthiopic-Regular.ttf</font>
<font weight="700" style="normal">NotoSansEthiopic-Bold.ttf</font>
+ <font weight="400" style="normal">NotoSansEthiopic-VF.ttf</font>
+ <font weight="700" style="normal">NotoSansEthiopic-VF.ttf</font>
</family>
<family>
<font weight="400" style="normal">NotoSansHebrew-Regular.ttf</font>
@@ -179,6 +181,8 @@
<font weight="400" style="normal">NotoSansArmenian-Regular.otf</font>
<font weight="700" style="normal">NotoSansArmenian-Bold.ttf</font>
<font weight="700" style="normal">NotoSansArmenian-Bold.otf</font>
+ <font weight="400" style="normal">NotoSansArmenian-VF.ttf</font>
+ <font weight="700" style="normal">NotoSansArmenian-VF.ttf</font>
</family>
<family>
<font weight="400" style="normal">NotoSansGeorgian-Regular.ttf</font>
@@ -191,6 +195,8 @@
<font weight="400" style="normal">NotoSansDevanagariUI-Regular.otf</font>
<font weight="700" style="normal">NotoSansDevanagariUI-Bold.ttf</font>
<font weight="700" style="normal">NotoSansDevanagariUI-Bold.otf</font>
+ <font weight="400" style="normal">NotoSansDevanagariUI-VF.ttf</font>
+ <font weight="700" style="normal">NotoSansDevanagariUI-VF.ttf</font>
</family>
<!-- Gujarati should come after Devanagari -->
<family>
@@ -201,32 +207,44 @@
<family>
<font weight="400" style="normal">NotoSansGurmukhiUI-Regular.ttf</font>
<font weight="700" style="normal">NotoSansGurmukhiUI-Bold.ttf</font>
+ <font weight="400" style="normal">NotoSansGurmukhiUI-VF.ttf</font>
+ <font weight="700" style="normal">NotoSansGurmukhiUI-VF.ttf</font>
</family>
<family>
<font weight="400" style="normal">NotoSansTamilUI-Regular.ttf</font>
<font weight="400" style="normal">NotoSansTamilUI-Regular.otf</font>
<font weight="700" style="normal">NotoSansTamilUI-Bold.ttf</font>
<font weight="700" style="normal">NotoSansTamilUI-Bold.otf</font>
+ <font weight="400" style="normal">NotoSansTamilUI-VF.ttf</font>
+ <font weight="700" style="normal">NotoSansTamilUI-VF.ttf</font>
</family>
<family>
<font weight="400" style="normal">NotoSansMalayalamUI-Regular.ttf</font>
<font weight="400" style="normal">NotoSansMalayalamUI-Regular.otf</font>
<font weight="700" style="normal">NotoSansMalayalamUI-Bold.ttf</font>
<font weight="700" style="normal">NotoSansMalayalamUI-Bold.otf</font>
+ <font weight="400" style="normal">NotoSansMalayalamUI-VF.ttf</font>
+ <font weight="700" style="normal">NotoSansMalayalamUI-VF.ttf</font>
</family>
<family>
<font weight="400" style="normal">NotoSansBengaliUI-Regular.ttf</font>
<font weight="400" style="normal">NotoSansBengaliUI-Regular.otf</font>
<font weight="700" style="normal">NotoSansBengaliUI-Bold.ttf</font>
<font weight="700" style="normal">NotoSansBengaliUI-Bold.otf</font>
+ <font weight="400" style="normal">NotoSansBengaliUI-VF.ttf</font>
+ <font weight="700" style="normal">NotoSansBengaliUI-VF.ttf</font>
</family>
<family>
<font weight="400" style="normal">NotoSansTeluguUI-Regular.ttf</font>
<font weight="700" style="normal">NotoSansTeluguUI-Bold.ttf</font>
+ <font weight="400" style="normal">NotoSansTeluguUI-VF.ttf</font>
+ <font weight="700" style="normal">NotoSansTeluguUI-VF.ttf</font>
</family>
<family>
<font weight="400" style="normal">NotoSansKannadaUI-Regular.ttf</font>
<font weight="700" style="normal">NotoSansKannadaUI-Bold.ttf</font>
+ <font weight="400" style="normal">NotoSansKannadaUI-VF.ttf</font>
+ <font weight="700" style="normal">NotoSansKannadaUI-VF.ttf</font>
</family>
<family>
<font weight="400" style="normal">NotoSansOriyaUI-Regular.ttf</font>
@@ -237,6 +255,8 @@
<font weight="400" style="normal">NotoSansSinhala-Regular.otf</font>
<font weight="700" style="normal">NotoSansSinhala-Bold.ttf</font>
<font weight="700" style="normal">NotoSansSinhala-Bold.otf</font>
+ <font weight="400" style="normal">NotoSansSinhala-VF.ttf</font>
+ <font weight="700" style="normal">NotoSansSinhala-VF.ttf</font>
</family>
<family>
<font weight="400" style="normal">NotoSansKhmerUI-Regular.ttf</font>
diff --git a/src/cobalt/demos/content/hybrid-navigation/hybrid-navigation-grid-rtl.html b/src/cobalt/demos/content/hybrid-navigation/hybrid-navigation-grid-rtl.html
new file mode 100644
index 0000000..672141c
--- /dev/null
+++ b/src/cobalt/demos/content/hybrid-navigation/hybrid-navigation-grid-rtl.html
@@ -0,0 +1,180 @@
+<!DOCTYPE html>
+<!--
+ | This demo demonstrates how scroll container should behave in RTL mode.
+ -->
+<html>
+ <head>
+ <style>
+ body {
+ background-color: rgb(25,25,25);
+ color: rgb(200,200,200);
+ width: 1920px;
+ height: 720px;
+ }
+
+ .vscroll {
+ overflow: auto;
+ position: absolute;
+ transform: translate(100px,100px);
+ background-color: rgb(50,50,50);
+ width: 1080px;
+ height: 520px;
+ }
+
+ .hscroll {
+ overflow: auto;
+ position: relative;
+ background-color: rgb(75, 75, 75);
+ width: 1080px;
+ height: 320px;
+ }
+
+ /*
+ * focusItem is used as the actual focusable item. Its dimensions are
+ * static so that the underlying SbUiNavigation system knows how much to
+ * scroll to ensure it is fully visible. The scroll animation is usually
+ * calculated to bring the to-be-focused item into view, but if that item
+ * is resized after the animation starts, then the animation may no
+ * longer end with the larger item in full view.
+ */
+ .focusItem {
+ overflow: hidden;
+ position: absolute;
+ width: 300px;
+ height: 300px;
+ outline: none;
+ }
+
+ /*
+ * focusContent will react to focus changes. Its size will grow when
+ * its parent receives focus.
+ */
+ .focusContent {
+ overflow: hidden;
+ margin: 20px;
+ border: 5px solid rgb(0,0,0);
+ width: 250px;
+ height: 250px;
+ line-height: 250px;
+ text-align: center;
+ vertical-align: middle;
+ font-size: 100px;
+
+ /*
+ * The user may be scrolling through items quickly. Use a transition
+ * delay so that the transition is shown once the user settles on a
+ * focus item.
+ */
+ transition: transform 0.5s;
+ transition-delay: 0.1s;
+ }
+
+ /*
+ * Update focusContent when the parent is focused.
+ */
+ .focusItem:focus .focusContent {
+ background-color: rgb(240,240,240);
+ color: rgb(0,0,0);
+ transform: -cobalt-ui-nav-focus-transform() scale(1.1);
+ }
+
+ /*
+ * Add a spotlight to focusContent when the parent is focused.
+ */
+ .focusItem:focus .focusContent::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: radial-gradient(rgba(255,0,0,0.5) 0%, rgba(255,0,0,0) 30%);
+ transform: -cobalt-ui-nav-spotlight-transform();
+ }
+ </style>
+ </head>
+
+ <body class="body">
+ <div class="vscroll">
+ <div class="hscroll" dir="rtl">
+ <!--
+ | translateX() should be honored when computing items' position in rtl mode.
+ | The right edge of A0 should be 50px from the right edge of the container when the
+ | page is rendered.
+ | User should be able to scroll items using the entire width of the container,
+ | meaning A0 should be visibly translated from the initial position to the right edge
+ | of the container.
+ | A0 should stop 50px from the right edge of the container when scrolling back to
+ | A0.
+ -->
+ <div id="start" class="focusItem" tabindex="-2" style="transform: translateX(-50px)">
+ <div class="focusContent">A0</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-350px)">
+ <div class="focusContent">A1</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-650px)">
+ <div class="focusContent">A2</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-950px)">
+ <div class="focusContent">A3</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-1250px)">
+ <div class="focusContent">A4</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-1550px)">
+ <div class="focusContent">A5</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-1850px)">
+ <div class="focusContent">A6</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-2150px)">
+ <div class="focusContent">A7</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-2450px)">
+ <div class="focusContent">A8</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-2750px)">
+ <div class="focusContent">A9</div>
+ </div>
+ </div>
+ <div class="hscroll" dir="rtl">
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-50px)">
+ <div class="focusContent">B0</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-350px)">
+ <div class="focusContent">B1</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-650px)">
+ <div class="focusContent">B2</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-950px)">
+ <div class="focusContent">B3</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-1250px)">
+ <div class="focusContent">B4</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-1550px)">
+ <div class="focusContent">B5</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-1850px)">
+ <div class="focusContent">B6</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-2150px)">
+ <div class="focusContent">B7</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-2450px)">
+ <div class="focusContent">B8</div>
+ </div>
+ <div class="focusItem" tabindex="-2" style="transform: translateX(-2750px)">
+ <div class="focusContent">B9</div>
+ </div>
+ </div>
+ </div>
+ </body>
+ <script>
+ window.onload = function() {
+ document.getElementById("start").focus();
+ }
+ </script>
+</html>
diff --git a/src/cobalt/demos/content/network-status-demo/index.html b/src/cobalt/demos/content/network-status-demo/index.html
new file mode 100644
index 0000000..944a2d2
--- /dev/null
+++ b/src/cobalt/demos/content/network-status-demo/index.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+
+<head>
+ <title>Test network status</title>
+</head>
+
+<body style="color: white;">
+ <h1>
+ <span>Network Status Value:</span>
+ <span id="net_stat">n/a</span>
+ </h1>
+
+ <script>
+ window.addEventListener('offline', function (e) {
+ console.log('offline');
+ document.getElementById('net_stat').innerHTML = "False";
+ });
+ window.addEventListener('online', function (e) {
+ console.log('online');
+ document.getElementById('net_stat').innerHTML = "True";
+ });
+ </script>
+</body>
diff --git a/src/cobalt/dom/document.cc b/src/cobalt/dom/document.cc
index 949ff8d..082d661 100644
--- a/src/cobalt/dom/document.cc
+++ b/src/cobalt/dom/document.cc
@@ -59,6 +59,7 @@
#include "cobalt/dom/mouse_event.h"
#include "cobalt/dom/named_node_map.h"
#include "cobalt/dom/node_descendants_iterator.h"
+#include "cobalt/dom/performance.h"
#include "cobalt/dom/text.h"
#include "cobalt/dom/ui_event.h"
#include "cobalt/dom/wheel_event.h"
@@ -105,7 +106,8 @@
render_postponed_(false),
frozenness_(false),
ALLOW_THIS_IN_INITIALIZER_LIST(intersection_observer_task_manager_(
- new IntersectionObserverTaskManager())) {
+ new IntersectionObserverTaskManager())),
+ navigation_type_(kNavigationTypeNavigate) {
DCHECK(html_element_context_);
DCHECK(options.url.is_empty() || options.url.is_valid());
application_lifecycle_state_->AddObserver(this);
@@ -126,7 +128,8 @@
location_ = new Location(
options.url, options.hashchange_callback, options.navigation_callback,
base::Bind(&CspDelegate::CanLoad, base::Unretained(csp_delegate_.get()),
- CspDelegate::kLocation));
+ CspDelegate::kLocation),
+ base::Bind(&Document::SetNavigationType, base::Unretained(this)));
font_cache_.reset(new FontCache(
html_element_context_->resource_provider(),
@@ -1148,6 +1151,45 @@
tracer->Trace(initial_computed_style_declaration_);
}
+void Document::CreatePerformanceNavigationTiming(
+ Performance* performance, const net::LoadTimingInfo& timing_info) {
+ // To create the navigation timing entry for document, given a loadTiminginfo,
+ // a navigationType and a null service worker timing info, do the following:
+ // https://www.w3.org/TR/2022/WD-navigation-timing-2-20220224/#marking-navigation-timing
+ // 1. Let global be document's relevant global object.
+ // 2. Let navigationTimingEntry be a new PerformanceNavigationTiming object
+ // in global's realm.
+ // 3. Setup the resource timing entry for navigationTimingEntry given
+ // "navigation", document's address, and fetchTiming.
+ // 4. Set navigationTimingEntry's document load timing to document's
+ // load timing info.
+ // 5. Set navigationTimingEntry's previous document unload timing to
+ // document's previous document unload timing.
+ // 6. Set navigationTimingEntry's redirect count to redirectCount.
+ // 7. Set navigationTimingEntry's navigation type to navigationType.
+ // 8. Set navigationTimingEntry's service worker timing to
+ // serviceWorkerTiming.
+ scoped_refptr<PerformanceNavigationTiming> navigation_timing(
+ new PerformanceNavigationTiming(timing_info, location_->url().spec(),
+ performance, this,
+ performance->GetTimeOrigin()));
+
+ // 9. Set document's navigation timing entry to
+ // navigationTimingEntry.
+ navigation_timing_entry_ = navigation_timing;
+ // 10. add navigationTimingEntry to global's performance entry buffer.
+ performance->AddEntryToPerformanceEntryBuffer(navigation_timing_entry_);
+ // 11. To queue the navigation timing entry for Document document, queue
+ // document's navigation timing entry.
+ performance->QueuePerformanceEntry(navigation_timing_entry_);
+}
+
+void Document::SetUnloadEventTimingInfo(base::TimeTicks start_time,
+ base::TimeTicks end_time) {
+ document_load_timing_info_.unload_event_start = start_time;
+ document_load_timing_info_.unload_event_end = end_time;
+}
+
void Document::set_render_postponed(bool render_postponed) {
bool unpostponed = render_postponed_ && !render_postponed;
render_postponed_ = render_postponed;
@@ -1156,6 +1198,28 @@
}
}
+void Document::CollectTimingInfoAndDispatchEvent() {
+ DCHECK(html_element_context_);
+ Performance* performance = html_element_context_->performance();
+ bool is_performance_valid = performance != nullptr;
+
+ // Set document load timing info's dom content loaded event start time
+ // before user agent dispatches the DOMConentLoaded event.
+ if (is_performance_valid) {
+ document_load_timing_info_.dom_content_loaded_event_start =
+ performance->Now();
+ }
+
+ PostToDispatchEventName(FROM_HERE, base::Tokens::domcontentloaded());
+
+ // Set document load timing info's dom content loaded event end time
+ // after user agent completes handling the DOMConentLoaded event.
+ if (is_performance_valid) {
+ document_load_timing_info_.dom_content_loaded_event_end =
+ performance->Now();
+ }
+}
+
void Document::OnRootElementUnableToProvideOffsetDimensions() {
window_->OnDocumentRootElementUnableToProvideOffsetDimensions();
}
@@ -1173,6 +1237,16 @@
UpdateComputedStyles();
}
+ DCHECK(html_element_context_);
+ Performance* performance = html_element_context_->performance();
+ bool is_performance_valid = performance != nullptr;
+
+ // Set document load timing info's dom complete time before user agent set the
+ // current document readiness to "complete".
+ if (is_performance_valid) {
+ document_load_timing_info_.dom_complete = performance->Now();
+ }
+
// Adjust the document ready state to reflect the fact that the document has
// finished loading. Performing this update and firing the readystatechange
// event before the load event matches Chromium's behavior.
@@ -1182,9 +1256,21 @@
// have changed the document ready state.
DispatchEvent(new Event(base::Tokens::readystatechange()));
+ // Set document load timing info's load event start time before user agent
+ // dispatch the load event for the document.
+ if (is_performance_valid) {
+ document_load_timing_info_.load_event_start = performance->Now();
+ }
+
// Dispatch the document's onload event.
DispatchEvent(new Event(base::Tokens::load()));
+ // Set document load timing info's load event end time after user agent
+ // completes handling the load event for the document.
+ if (is_performance_valid) {
+ document_load_timing_info_.load_event_end = performance->Now();
+ }
+
// After all JavaScript OnLoad event handlers have executed, signal to let
// any Document observers know that a load event has occurred.
SignalOnLoadToObservers();
diff --git a/src/cobalt/dom/document.h b/src/cobalt/dom/document.h
index 32b4fbc..297fc92 100644
--- a/src/cobalt/dom/document.h
+++ b/src/cobalt/dom/document.h
@@ -41,6 +41,7 @@
#include "cobalt/cssom/viewport_size.h"
#include "cobalt/dom/application_lifecycle_state.h"
#include "cobalt/dom/csp_delegate_type.h"
+#include "cobalt/dom/document_load_timing_info.h"
#include "cobalt/dom/document_ready_state.h"
#include "cobalt/dom/document_timeline.h"
#include "cobalt/dom/event.h"
@@ -48,6 +49,7 @@
#include "cobalt/dom/intersection_observer_task_manager.h"
#include "cobalt/dom/location.h"
#include "cobalt/dom/node.h"
+#include "cobalt/dom/performance_navigation_timing.h"
#include "cobalt/dom/pointer_state.h"
#include "cobalt/dom/visibility_state.h"
#include "cobalt/math/size.h"
@@ -75,6 +77,7 @@
class HTMLMediaElement;
class HTMLScriptElement;
class Location;
+class Performance;
class Text;
class Window;
@@ -481,6 +484,43 @@
DEFINE_WRAPPABLE_TYPE(Document);
void TraceMembers(script::Tracer* tracer) override;
+ // PerformanceNavigationTiming related API.
+ void CreatePerformanceNavigationTiming(
+ Performance* performance, const net::LoadTimingInfo& timing_info);
+ base::TimeTicks GetDocumentUnloadEventStartTime() const {
+ return document_load_timing_info_.unload_event_start;
+ }
+ base::TimeTicks GetDocumentUnloadEventEndTime() const {
+ return document_load_timing_info_.unload_event_end;
+ }
+ DOMHighResTimeStamp GetDocumentContentLoadedEventStartTime() const {
+ return document_load_timing_info_.dom_content_loaded_event_start;
+ }
+ DOMHighResTimeStamp GetDocumentContentLoadedEventEndTime() const {
+ return document_load_timing_info_.dom_content_loaded_event_end;
+ }
+ DOMHighResTimeStamp GetDocumentDomCompleteTime() const {
+ return document_load_timing_info_.dom_complete;
+ }
+ DOMHighResTimeStamp GetDocumentLoadEventStartTime() const {
+ return document_load_timing_info_.load_event_start;
+ }
+ DOMHighResTimeStamp GetDocumentLoadEventEndTime() const {
+ return document_load_timing_info_.load_event_end;
+ }
+ NavigationType GetNavigationType() const { return navigation_type_; }
+
+ // Collect dom content loaded timing info and dispatch dom content loaded
+ // event.
+ void CollectTimingInfoAndDispatchEvent();
+
+ void SetNavigationType(NavigationType navigation_type) {
+ navigation_type_ = navigation_type;
+ }
+
+ void SetUnloadEventTimingInfo(base::TimeTicks start_time,
+ base::TimeTicks end_time);
+
protected:
~Document() override;
@@ -644,6 +684,11 @@
scoped_refptr<IntersectionObserverTaskManager>
intersection_observer_task_manager_;
+
+ scoped_refptr<PerformanceNavigationTiming> navigation_timing_entry_;
+
+ DocumentLoadTimingInfo document_load_timing_info_;
+ NavigationType navigation_type_;
};
} // namespace dom
diff --git a/src/cobalt/dom/document_load_timing_info.h b/src/cobalt/dom/document_load_timing_info.h
new file mode 100644
index 0000000..322d6d8
--- /dev/null
+++ b/src/cobalt/dom/document_load_timing_info.h
@@ -0,0 +1,64 @@
+// 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_DOM_DOCUMENT_LOAD_TIMING_INFO_H_
+#define COBALT_DOM_DOCUMENT_LOAD_TIMING_INFO_H_
+
+#include "cobalt/dom/performance_high_resolution_time.h"
+
+namespace cobalt {
+namespace dom {
+
+// Implements the DocumentLoadTimingInfo struct for saving document loading
+// related timing info.
+// https://html.spec.whatwg.org/multipage/dom.html#document-load-timing-info
+struct DocumentLoadTimingInfo {
+ DocumentLoadTimingInfo() = default;
+ DocumentLoadTimingInfo(const DocumentLoadTimingInfo& other) = default;
+ ~DocumentLoadTimingInfo() = default;
+
+ // If the previous document and the current document have the same origin,
+ // these timestamps are measured immediately after the user agent handles the
+ // unload event of the previous document. If there is no previous document
+ // or the previous document has a different origin than the current document,
+ // these attributes will be zero.
+ base::TimeTicks unload_event_start;
+ base::TimeTicks unload_event_end;
+
+ // This timestamp is measured before the user agent dispatches the
+ // DOMContentLoaded event.
+ DOMHighResTimeStamp dom_content_loaded_event_start = 0.0;
+
+ // This timestamp is measured after the user agent completes handling of
+ // the DOMContentLoaded event.
+ DOMHighResTimeStamp dom_content_loaded_event_end = 0.0;
+
+ // This timestamp is measured before the user agent sets the current
+ // document readiness to "complete". See document readiness for a precise
+ // definition.
+ DOMHighResTimeStamp dom_complete = 0.0;
+
+ // This timestamp is measured before the user agent dispatches the load
+ // event for the document.
+ DOMHighResTimeStamp load_event_start = 0.0;
+
+ // This timestamp is measured after the user agent completes handling
+ // the load event for the document.
+ DOMHighResTimeStamp load_event_end = 0.0;
+};
+
+} // namespace dom
+} // namespace cobalt
+
+#endif // COBALT_DOM_DOCUMENT_LOAD_TIMING_INFO_H_
diff --git a/src/cobalt/dom/dom.gyp b/src/cobalt/dom/dom.gyp
index 4039e2c..0163c9d 100644
--- a/src/cobalt/dom/dom.gyp
+++ b/src/cobalt/dom/dom.gyp
@@ -77,6 +77,7 @@
'directionality.h',
'document.cc',
'document.h',
+ 'document_load_timing_info.h',
'document_timeline.cc',
'document_timeline.h',
'document_type.cc',
@@ -268,6 +269,8 @@
'performance_mark.h',
'performance_measure.cc',
'performance_measure.h',
+ 'performance_navigation_timing.cc',
+ 'performance_navigation_timing.h',
'performance_observer.cc',
'performance_observer.h',
'performance_observer_entry_list.cc',
diff --git a/src/cobalt/dom/dom_stat_tracker.cc b/src/cobalt/dom/dom_stat_tracker.cc
index 544a218..586f8a3 100644
--- a/src/cobalt/dom/dom_stat_tracker.cc
+++ b/src/cobalt/dom/dom_stat_tracker.cc
@@ -26,10 +26,21 @@
count_html_element_document_(
base::StringPrintf("Count.%s.DOM.HtmlElement.Document", name.c_str()),
0, "Number of HTML elements in the document."),
+ count_window_timers_interval_(
+ base::StringPrintf("Count.%s.DOM.WindowTimers.Interval",
+ name.c_str()),
+ 0, "Number of active WindowTimer Intervals."),
+ count_window_timers_timeout_(
+ base::StringPrintf("Count.%s.DOM.WindowTimers.Timeout", name.c_str()),
+ 0, "Number of active WindowTimer Timeouts."),
count_html_element_created_(0),
count_html_element_destroyed_(0),
count_html_element_document_added_(0),
count_html_element_document_removed_(0),
+ count_window_timers_interval_created_(0),
+ count_window_timers_interval_destroyed_(0),
+ count_window_timers_timeout_created_(0),
+ count_window_timers_timeout_destroyed_(0),
script_element_execute_count_(
base::StringPrintf("Count.%s.DOM.HtmlScriptElement.Execute",
name.c_str()),
@@ -69,6 +80,8 @@
// destroyed.
DCHECK_EQ(count_html_element_, 0);
DCHECK_EQ(count_html_element_document_, 0);
+ DCHECK_EQ(count_window_timers_interval_, 0);
+ DCHECK_EQ(count_window_timers_timeout_, 0);
event_video_start_delay_stop_watch_.Stop();
}
@@ -146,11 +159,20 @@
count_html_element_document_ +=
count_html_element_document_added_ - count_html_element_document_removed_;
+ count_window_timers_interval_ += count_window_timers_interval_created_ -
+ count_window_timers_interval_destroyed_;
+ count_window_timers_timeout_ += count_window_timers_timeout_created_ -
+ count_window_timers_timeout_destroyed_;
// Now clear the values.
count_html_element_created_ = 0;
count_html_element_destroyed_ = 0;
count_html_element_document_added_ = 0;
count_html_element_document_removed_ = 0;
+
+ count_window_timers_interval_created_ = 0;
+ count_window_timers_interval_destroyed_ = 0;
+ count_window_timers_timeout_created_ = 0;
+ count_window_timers_timeout_destroyed_ = 0;
}
void DomStatTracker::StartTrackingEvent() {
diff --git a/src/cobalt/dom/dom_stat_tracker.h b/src/cobalt/dom/dom_stat_tracker.h
index 381b9f0..288444e 100644
--- a/src/cobalt/dom/dom_stat_tracker.h
+++ b/src/cobalt/dom/dom_stat_tracker.h
@@ -37,6 +37,19 @@
explicit DomStatTracker(const std::string& name);
~DomStatTracker();
+ void OnWindowTimersIntervalCreated() {
+ ++count_window_timers_interval_created_;
+ }
+ void OnWindowTimersIntervalDestroyed() {
+ ++count_window_timers_interval_destroyed_;
+ }
+ void OnWindowTimersTimeoutCreated() {
+ ++count_window_timers_timeout_created_;
+ }
+ void OnWindowTimersTimeoutDestroyed() {
+ ++count_window_timers_timeout_destroyed_;
+ }
+
void OnHtmlElementCreated();
void OnHtmlElementDestroyed();
void OnHtmlElementInsertedIntoDocument();
@@ -94,12 +107,19 @@
base::CVal<int, base::CValPublic> count_html_element_;
base::CVal<int, base::CValPublic> count_html_element_document_;
+ base::CVal<int, base::CValPublic> count_window_timers_interval_;
+ base::CVal<int, base::CValPublic> count_window_timers_timeout_;
+
// Periodic counts. The counts are cleared after the CVals are updated in
// |FlushPeriodicTracking|.
int count_html_element_created_;
int count_html_element_destroyed_;
int count_html_element_document_added_;
int count_html_element_document_removed_;
+ int count_window_timers_interval_created_;
+ int count_window_timers_interval_destroyed_;
+ int count_window_timers_timeout_created_;
+ int count_window_timers_timeout_destroyed_;
// Count of HtmlScriptElement::Execute() calls, their total size in bytes, and
// the time of last call.
diff --git a/src/cobalt/dom/dom_test.gyp b/src/cobalt/dom/dom_test.gyp
index 456965d..84aed7e 100644
--- a/src/cobalt/dom/dom_test.gyp
+++ b/src/cobalt/dom/dom_test.gyp
@@ -70,6 +70,7 @@
'url_utils_test.cc',
'user_agent_data_test.cc',
'window_test.cc',
+ 'window_timers_test.cc',
'xml_document_test.cc',
],
'dependencies': [
diff --git a/src/cobalt/dom/event_target.cc b/src/cobalt/dom/event_target.cc
index e663bb1..c125766 100644
--- a/src/cobalt/dom/event_target.cc
+++ b/src/cobalt/dom/event_target.cc
@@ -15,6 +15,7 @@
#include "cobalt/dom/event_target.h"
#include <memory>
+#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
@@ -90,8 +91,9 @@
DCHECK(event);
DCHECK(!event->IsBeingDispatched());
DCHECK(event->initialized_flag());
- TRACE_EVENT1("cobalt::dom", "EventTarget::DispatchEvent", "event",
- event->type().c_str());
+ TRACE_EVENT2("cobalt::dom", "EventTarget::DispatchEvent", "name",
+ TRACE_STR_COPY(GetDebugName().c_str()), "event", TRACE_STR_COPY(event->type().c_str()));
+
if (!event || event->IsBeingDispatched() || !event->initialized_flag()) {
return false;
}
diff --git a/src/cobalt/dom/html_element_context.h b/src/cobalt/dom/html_element_context.h
index aa4de99..cb4183d 100644
--- a/src/cobalt/dom/html_element_context.h
+++ b/src/cobalt/dom/html_element_context.h
@@ -25,8 +25,8 @@
#include "cobalt/dom/application_lifecycle_state.h"
#include "cobalt/dom/dom_stat_tracker.h"
#include "cobalt/dom/parser.h"
-#include "cobalt/dom/url_registry.h"
#include "cobalt/dom/performance.h"
+#include "cobalt/dom/url_registry.h"
#include "cobalt/loader/fetcher_factory.h"
#include "cobalt/loader/font/remote_typeface_cache.h"
#include "cobalt/loader/image/animated_image_tracker.h"
@@ -75,8 +75,7 @@
const std::string& font_language_script,
base::ApplicationState initial_application_state,
base::WaitableEvent* synchronous_loader_interrupt,
- Performance* performance,
- bool enable_inline_script_warnings = false,
+ Performance* performance, bool enable_inline_script_warnings = false,
float video_playback_rate_multiplier = 1.0);
~HTMLElementContext();
diff --git a/src/cobalt/dom/html_media_element.cc b/src/cobalt/dom/html_media_element.cc
index 84edb94..745faf2 100644
--- a/src/cobalt/dom/html_media_element.cc
+++ b/src/cobalt/dom/html_media_element.cc
@@ -271,8 +271,6 @@
NOTREACHED();
}
MLOG() << "(" << mime_type << ", " << key_system << ") => " << result;
- LOG(INFO) << "HTMLMediaElement::canPlayType(" << mime_type << ", "
- << key_system << ") -> " << result;
return result;
}
diff --git a/src/cobalt/dom/location.cc b/src/cobalt/dom/location.cc
index dfcbea6..e34db7c 100644
--- a/src/cobalt/dom/location.cc
+++ b/src/cobalt/dom/location.cc
@@ -23,12 +23,15 @@
Location::Location(const GURL& url, const base::Closure& hashchange_callback,
const base::Callback<void(const GURL&)>& navigation_callback,
- const csp::SecurityCallback& security_callback)
+ const csp::SecurityCallback& security_callback,
+ const base::Callback<void(NavigationType type)>&
+ set_navigation_type_callback)
: ALLOW_THIS_IN_INITIALIZER_LIST(url_utils_(
url, base::Bind(&Location::Replace, base::Unretained(this)))),
hashchange_callback_(hashchange_callback),
navigation_callback_(navigation_callback),
- security_callback_(security_callback) {}
+ security_callback_(security_callback),
+ set_navigation_type_callback_(set_navigation_type_callback) {}
// Algorithm for Replace:
// https://www.w3.org/TR/html50/browsers.html#dom-location-replace
@@ -78,6 +81,9 @@
if (!navigation_callback_.is_null()) {
navigation_callback_.Run(new_url);
}
+ if (!set_navigation_type_callback_.is_null()) {
+ set_navigation_type_callback_.Run(kNavigationTypeNavigate);
+ }
}
}
@@ -86,6 +92,9 @@
LOG(INFO) << "Reloading URL: " << url();
navigation_callback_.Run(url());
}
+ if (!set_navigation_type_callback_.is_null()) {
+ set_navigation_type_callback_.Run(kNavigationTypeReload);
+ }
}
} // namespace dom
diff --git a/src/cobalt/dom/location.h b/src/cobalt/dom/location.h
index d73dde0..b103683 100644
--- a/src/cobalt/dom/location.h
+++ b/src/cobalt/dom/location.h
@@ -19,6 +19,7 @@
#include "base/callback.h"
#include "cobalt/csp/content_security_policy.h"
+#include "cobalt/dom/navigation_type.h"
#include "cobalt/dom/url_utils.h"
#include "cobalt/script/wrappable.h"
#include "url/gurl.h"
@@ -37,7 +38,9 @@
// otherwise they can be empty.
Location(const GURL& url, const base::Closure& hashchange_callback,
const base::Callback<void(const GURL&)>& navigation_callback,
- const csp::SecurityCallback& security_callback);
+ const csp::SecurityCallback& security_callback,
+ const base::Callback<void(NavigationType type)>&
+ set_navigation_type_callback);
// Web API: Location
//
@@ -102,6 +105,7 @@
base::Closure hashchange_callback_;
base::Callback<void(const GURL&)> navigation_callback_;
csp::SecurityCallback security_callback_;
+ const base::Callback<void(NavigationType)> set_navigation_type_callback_;
DISALLOW_COPY_AND_ASSIGN(Location);
};
diff --git a/src/cobalt/dom/location_test.cc b/src/cobalt/dom/location_test.cc
index 70385a4..a63cab6 100644
--- a/src/cobalt/dom/location_test.cc
+++ b/src/cobalt/dom/location_test.cc
@@ -62,8 +62,10 @@
void Init(const GURL& url, const base::Closure& hashchange_callback,
const base::Callback<void(const GURL&)>& navigation_callback,
const csp::SecurityCallback& security_callback) {
+ base::Callback<void(NavigationType)> null_cb;
location_ = new Location(url, hashchange_callback, navigation_callback,
- security_callback);
+ security_callback,
+ null_cb /*set_navigation_type_callback*/);
}
scoped_refptr<Location> location_;
diff --git a/src/cobalt/dom/media_source.cc b/src/cobalt/dom/media_source.cc
index a970978..c070968 100644
--- a/src/cobalt/dom/media_source.cc
+++ b/src/cobalt/dom/media_source.cc
@@ -52,6 +52,7 @@
#include "base/compiler_specific.h"
#include "base/guid.h"
#include "base/logging.h"
+#include "base/trace_event/trace_event.h"
#include "cobalt/base/tokens.h"
#include "cobalt/dom/dom_exception.h"
#include "cobalt/dom/dom_settings.h"
@@ -149,6 +150,7 @@
scoped_refptr<SourceBuffer> MediaSource::AddSourceBuffer(
script::EnvironmentSettings* settings, const std::string& type,
script::ExceptionState* exception_state) {
+ TRACE_EVENT1("cobalt::dom", "MediaSource::AddSourceBuffer()", "type", type);
DLOG(INFO) << "add SourceBuffer with type " << type;
if (type.empty()) {
@@ -191,6 +193,7 @@
void MediaSource::RemoveSourceBuffer(
const scoped_refptr<SourceBuffer>& source_buffer,
script::ExceptionState* exception_state) {
+ TRACE_EVENT0("cobalt::dom", "MediaSource::RemoveSourceBuffer()");
if (source_buffer.get() == NULL) {
DOMException::Raise(DOMException::kInvalidAccessErr, exception_state);
return;
@@ -222,12 +225,14 @@
}
void MediaSource::EndOfStream(script::ExceptionState* exception_state) {
+ TRACE_EVENT0("cobalt::dom", "MediaSource::EndOfStream()");
// If there is no error string provided, treat it as empty.
EndOfStream(kMediaSourceEndOfStreamErrorNoError, exception_state);
}
void MediaSource::EndOfStream(MediaSourceEndOfStreamError error,
script::ExceptionState* exception_state) {
+ TRACE_EVENT1("cobalt::dom", "MediaSource::EndOfStream()", "error", error);
if (!IsOpen() || IsUpdating()) {
DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
return;
@@ -265,28 +270,24 @@
// static
bool MediaSource::IsTypeSupported(script::EnvironmentSettings* settings,
const std::string& type) {
+ TRACE_EVENT1("cobalt::dom", "MediaSource::IsTypeSupported()", "type", type);
DCHECK(settings);
DOMSettings* dom_settings =
base::polymorphic_downcast<DOMSettings*>(settings);
DCHECK(dom_settings->can_play_type_handler());
SbMediaSupportType support_type =
dom_settings->can_play_type_handler()->CanPlayAdaptive(type.c_str(), "");
- if (support_type == kSbMediaSupportTypeNotSupported) {
- LOG(INFO) << "MediaSource::IsTypeSupported(" << type
- << ") -> not supported/false";
- return false;
+ switch (support_type) {
+ case kSbMediaSupportTypeNotSupported:
+ return false;
+ case kSbMediaSupportTypeMaybe:
+ return true;
+ case kSbMediaSupportTypeProbably:
+ return true;
+ default:
+ NOTREACHED();
+ return false;
}
- if (support_type == kSbMediaSupportTypeMaybe) {
- LOG(INFO) << "MediaSource::IsTypeSupported(" << type << ") -> maybe/true";
- return true;
- }
- if (support_type == kSbMediaSupportTypeProbably) {
- LOG(INFO) << "MediaSource::IsTypeSupported(" << type
- << ") -> probably/true";
- return true;
- }
- NOTREACHED();
- return false;
}
bool MediaSource::AttachToElement(HTMLMediaElement* media_element) {
diff --git a/src/cobalt/dom/navigation_type.idl b/src/cobalt/dom/navigation_type.idl
new file mode 100644
index 0000000..4326021
--- /dev/null
+++ b/src/cobalt/dom/navigation_type.idl
@@ -0,0 +1,23 @@
+// 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.
+
+// https://www.w3.org/TR/2022/WD-navigation-timing-2-20220224/#sec-performance-navigation-types
+
+// Cobalt only supports "navigate" and "reload".
+enum NavigationType {
+ "navigate",
+ "reload",
+ "back_forward",
+ "prerender"
+};
diff --git a/src/cobalt/dom/node.cc b/src/cobalt/dom/node.cc
index 7df763a..0b39848 100644
--- a/src/cobalt/dom/node.cc
+++ b/src/cobalt/dom/node.cc
@@ -15,6 +15,7 @@
#include "cobalt/dom/node.h"
#include <memory>
+#include <utility>
#include <vector>
#include "base/lazy_instance.h"
@@ -58,7 +59,7 @@
DCHECK(event->initialized_flag());
TRACE_EVENT1("cobalt::dom", "Node::DispatchEvent", "event",
- event->type().c_str());
+ TRACE_STR_COPY(event->type().c_str()));
if (!event || event->IsBeingDispatched() || !event->initialized_flag()) {
return false;
diff --git a/src/cobalt/dom/performance.h b/src/cobalt/dom/performance.h
index 5193232..f6c3391 100644
--- a/src/cobalt/dom/performance.h
+++ b/src/cobalt/dom/performance.h
@@ -69,8 +69,7 @@
base::TimeTicks monotonic_time) const;
static DOMHighResTimeStamp MonotonicTimeToDOMHighResTimeStamp(
- base::TimeTicks time_origin,
- base::TimeTicks monotonic_time);
+ base::TimeTicks time_origin, base::TimeTicks monotonic_time);
// Web API: Performance Timeline extensions to the Performance.
// https://www.w3.org/TR/performance-timeline-2/#extensions-to-the-performance-interface
@@ -109,12 +108,13 @@
void CreatePerformanceResourceTiming(const net::LoadTimingInfo& timing_info,
const std::string& initiator_type,
const std::string& requested_url);
- void CreatePerformanceLifecycleTiming();
+ void AddEntryToPerformanceEntryBuffer(
+ const scoped_refptr<PerformanceEntry>& entry) {
+ performance_entry_buffer_.push_back(entry);
+ }
// Custom, not in any spec.
// Internal getter method for the time origin value.
- base::TimeTicks GetTimeOrigin() const {
- return time_origin_;
- }
+ base::TimeTicks GetTimeOrigin() const { return time_origin_; }
// Register and unregisterthe performance observer.
void UnregisterPerformanceObserver(
const scoped_refptr<PerformanceObserver>& observer);
@@ -133,8 +133,8 @@
void SetApplicationState(base::ApplicationState state,
SbTimeMonotonic timestamp);
- void SetApplicationStartOrPreloadTimestamp(
- bool is_preload, SbTimeMonotonic timestamp);
+ void SetApplicationStartOrPreloadTimestamp(bool is_preload,
+ SbTimeMonotonic timestamp);
void SetDeepLinkTimestamp(SbTimeMonotonic timestamp);
@@ -142,7 +142,8 @@
DEFINE_WRAPPABLE_TYPE(Performance);
private:
- unsigned long GetDroppedEntriesCount(const std::string& entry_type);
+ unsigned long GetDroppedEntriesCount( // NOLINT(runtime/int)
+ const std::string& entry_type);
base::TimeTicks time_origin_;
const base::TickClock* tick_clock_;
@@ -170,10 +171,10 @@
RegisteredPerformanceObserverList;
RegisteredPerformanceObserverList registered_performance_observers_;
- unsigned long resource_timing_buffer_size_limit_;
- unsigned long resource_timing_buffer_current_size_;
+ unsigned long resource_timing_buffer_size_limit_; // NOLINT(runtime/int)
+ unsigned long resource_timing_buffer_current_size_; // NOLINT(runtime/int)
bool resource_timing_buffer_full_event_pending_flag_;
- unsigned long
+ unsigned long // NOLINT(runtime/int)
resource_timing_secondary_buffer_current_size_;
std::deque<scoped_refptr<PerformanceResourceTiming>>
resource_timing_secondary_buffer_;
diff --git a/src/cobalt/dom/performance_lifecycle_timing.cc b/src/cobalt/dom/performance_lifecycle_timing.cc
index d81ebb0..e06763f 100644
--- a/src/cobalt/dom/performance_lifecycle_timing.cc
+++ b/src/cobalt/dom/performance_lifecycle_timing.cc
@@ -21,32 +21,32 @@
namespace {
- std::string TranslateApplicationStateToString(
- base::ApplicationState state) {
- switch (state) {
- case base::kApplicationStateBlurred:
- return "ApplicationStateBlurred";
- case base::kApplicationStateConcealed:
- return "ApplicationStateConcealed";
- case base::kApplicationStateFrozen:
- return "ApplicationStateFrozen";
- case base::kApplicationStateStarted:
- return "ApplicationStateStarted";
- case base::kApplicationStateStopped:
- return "ApplicationStateStopped";
- }
-
- NOTREACHED() << "state = " << state;
- return "INVALID_APPLICATION_STATE";
+std::string TranslateApplicationStateToString(base::ApplicationState state) {
+ switch (state) {
+ case base::kApplicationStateBlurred:
+ return "ApplicationStateBlurred";
+ case base::kApplicationStateConcealed:
+ return "ApplicationStateConcealed";
+ case base::kApplicationStateFrozen:
+ return "ApplicationStateFrozen";
+ case base::kApplicationStateStarted:
+ return "ApplicationStateStarted";
+ case base::kApplicationStateStopped:
+ return "ApplicationStateStopped";
}
- DOMHighResTimeStamp ConvertSbTimeMonotonicToDOMHiResTimeStamp(
- const DOMHighResTimeStamp time_origin, SbTimeMonotonic monotonic_time) {
- SbTimeMonotonic time_delta = SbTimeGetNow() - SbTimeGetMonotonicNow();
- base::Time base_time = base::Time::FromSbTime(time_delta + monotonic_time);
- return ClampTimeStampMinimumResolution(base_time.ToJsTime() - time_origin,
- Performance::kPerformanceTimerMinResolutionInMicroseconds);
- }
+ NOTREACHED() << "state = " << state;
+ return "INVALID_APPLICATION_STATE";
+}
+
+DOMHighResTimeStamp ConvertSbTimeMonotonicToDOMHiResTimeStamp(
+ const DOMHighResTimeStamp time_origin, SbTimeMonotonic monotonic_time) {
+ SbTimeMonotonic time_delta = SbTimeGetNow() - SbTimeGetMonotonicNow();
+ base::Time base_time = base::Time::FromSbTime(time_delta + monotonic_time);
+ return ClampTimeStampMinimumResolution(
+ base_time.ToJsTime() - time_origin,
+ Performance::kPerformanceTimerMinResolutionInMicroseconds);
+}
} // namespace
@@ -96,8 +96,7 @@
}
std::string PerformanceLifecycleTiming::last_state() const {
- return TranslateApplicationStateToString(
- lifecycle_timing_info_.last_state);
+ return TranslateApplicationStateToString(lifecycle_timing_info_.last_state);
}
void PerformanceLifecycleTiming::SetApplicationState(
@@ -112,10 +111,10 @@
} else if (GetCurrentState() == base::kApplicationStateConcealed) {
lifecycle_timing_info_.app_reveal = timestamp;
} else {
- DLOG(INFO) << "Current State: " <<
- TranslateApplicationStateToString(GetCurrentState());
- DLOG(INFO) << "Next State: " <<
- TranslateApplicationStateToString(state);
+ DLOG(INFO) << "Current State: "
+ << TranslateApplicationStateToString(GetCurrentState());
+ DLOG(INFO) << "Next State: "
+ << TranslateApplicationStateToString(state);
NOTREACHED() << "Invalid application state transition.";
}
break;
@@ -126,10 +125,10 @@
} else if (GetCurrentState() == base::kApplicationStateFrozen) {
lifecycle_timing_info_.app_unfreeze = timestamp;
} else {
- DLOG(INFO) << "Current State: " <<
- TranslateApplicationStateToString(GetCurrentState());
- DLOG(INFO) << "Next State: " <<
- TranslateApplicationStateToString(state);
+ DLOG(INFO) << "Current State: "
+ << TranslateApplicationStateToString(GetCurrentState());
+ DLOG(INFO) << "Next State: "
+ << TranslateApplicationStateToString(state);
NOTREACHED() << "Invalid application state transition.";
}
break;
@@ -138,7 +137,7 @@
break;
case base::kApplicationStateStarted:
if (GetCurrentState() == base::kApplicationStateBlurred) {
- if (lifecycle_timing_info_.app_preload != 0) {
+ if (lifecycle_timing_info_.app_start == 0) {
lifecycle_timing_info_.app_start = timestamp;
}
lifecycle_timing_info_.app_focus = timestamp;
@@ -176,19 +175,17 @@
void PerformanceLifecycleTiming::SetLifecycleTimingInfoState(
base::ApplicationState state) {
- lifecycle_timing_info_.last_state =
- lifecycle_timing_info_.current_state;
+ lifecycle_timing_info_.last_state = lifecycle_timing_info_.current_state;
lifecycle_timing_info_.current_state = state;
}
DOMHighResTimeStamp PerformanceLifecycleTiming::ReportDOMHighResTimeStamp(
SbTimeMonotonic timestamp) const {
if (timestamp != 0) {
- return ConvertSbTimeMonotonicToDOMHiResTimeStamp(time_origin_,
- timestamp);
+ return ConvertSbTimeMonotonicToDOMHiResTimeStamp(time_origin_, timestamp);
}
return PerformanceEntry::start_time();
}
} // namespace dom
-} // namespace cobalt
\ No newline at end of file
+} // namespace cobalt
diff --git a/src/cobalt/dom/performance_navigation_timing.cc b/src/cobalt/dom/performance_navigation_timing.cc
new file mode 100644
index 0000000..ca6c3eb
--- /dev/null
+++ b/src/cobalt/dom/performance_navigation_timing.cc
@@ -0,0 +1,92 @@
+// 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 "cobalt/dom/performance_navigation_timing.h"
+#include "cobalt/dom/performance.h"
+
+#include "cobalt/dom/document.h"
+
+namespace cobalt {
+namespace dom {
+
+namespace {
+// https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype
+const char kPerformanceNavigationInitiatorType[] = "navigation";
+} // namespace
+
+PerformanceNavigationTiming::PerformanceNavigationTiming(
+ const net::LoadTimingInfo& timing_info, const std::string& requested_url,
+ Performance* performance, Document* document, base::TimeTicks time_origin)
+ : PerformanceResourceTiming(timing_info,
+ kPerformanceNavigationInitiatorType,
+ requested_url, performance, time_origin),
+ document_(base::AsWeakPtr(document)),
+ time_origin_(time_origin) {}
+
+std::string PerformanceNavigationTiming::initiator_type() const {
+ return kPerformanceNavigationInitiatorType;
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::unload_event_start() const {
+ base::TimeTicks start_time = document_->GetDocumentUnloadEventStartTime();
+ if (start_time.is_null()) {
+ return 0.0;
+ }
+
+ return Performance::MonotonicTimeToDOMHighResTimeStamp(time_origin_,
+ start_time);
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::unload_event_end() const {
+ base::TimeTicks end_time = document_->GetDocumentUnloadEventEndTime();
+ if (end_time.is_null()) {
+ return 0.0;
+ }
+
+ return Performance::MonotonicTimeToDOMHighResTimeStamp(time_origin_,
+ end_time);
+}
+
+DOMHighResTimeStamp
+PerformanceNavigationTiming::dom_content_loaded_event_start() const {
+ return document_->GetDocumentContentLoadedEventStartTime();
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::dom_content_loaded_event_end()
+ const {
+ return document_->GetDocumentContentLoadedEventEndTime();
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::dom_complete() const {
+ return document_->GetDocumentDomCompleteTime();
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::load_event_start() const {
+ return document_->GetDocumentLoadEventStartTime();
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::load_event_end() const {
+ return document_->GetDocumentLoadEventEndTime();
+}
+
+NavigationType PerformanceNavigationTiming::type() const {
+ return document_->GetNavigationType();
+}
+
+DOMHighResTimeStamp PerformanceNavigationTiming::duration() const {
+ return load_event_end() - start_time();
+}
+
+} // namespace dom
+} // namespace cobalt
diff --git a/src/cobalt/dom/performance_navigation_timing.h b/src/cobalt/dom/performance_navigation_timing.h
new file mode 100644
index 0000000..49b1a4b
--- /dev/null
+++ b/src/cobalt/dom/performance_navigation_timing.h
@@ -0,0 +1,78 @@
+// 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_DOM_PERFORMANCE_NAVIGATION_TIMING_H_
+#define COBALT_DOM_PERFORMANCE_NAVIGATION_TIMING_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "cobalt/dom/navigation_type.h"
+#include "cobalt/dom/performance_entry.h"
+#include "cobalt/dom/performance_high_resolution_time.h"
+#include "cobalt/dom/performance_resource_timing.h"
+
+#include "cobalt/script/wrappable.h"
+#include "net/base/load_timing_info.h"
+
+namespace cobalt {
+namespace dom {
+
+class Document;
+class Performance;
+
+// Implements the Performance Navigation Timing IDL interface, as described
+// here:
+// https://www.w3.org/TR/navigation-timing-2/
+class PerformanceNavigationTiming : public PerformanceResourceTiming {
+ public:
+ PerformanceNavigationTiming(const net::LoadTimingInfo& timing_info,
+ const std::string& requested_url,
+ Performance* performance, Document* document,
+ base::TimeTicks time_origin);
+
+ // Web API.
+ DOMHighResTimeStamp unload_event_start() const;
+ DOMHighResTimeStamp unload_event_end() const;
+ DOMHighResTimeStamp dom_content_loaded_event_start() const;
+ DOMHighResTimeStamp dom_content_loaded_event_end() const;
+ DOMHighResTimeStamp dom_complete() const;
+ DOMHighResTimeStamp load_event_start() const;
+ DOMHighResTimeStamp load_event_end() const;
+ NavigationType type() const;
+
+ // PerformanceResourceTiming overrides.
+ std::string initiator_type() const override;
+
+ // PerformanceEntry overrides. Return a timestamp equal to the difference
+ // between loadEventEnd and its startTime.
+ DOMHighResTimeStamp duration() const override;
+
+ std::string entry_type() const override { return "navigation"; }
+ PerformanceEntryType EntryTypeEnum() const override {
+ return PerformanceEntry::kNavigation;
+ }
+
+ DEFINE_WRAPPABLE_TYPE(PerformanceNavigationTiming);
+
+ private:
+ base::WeakPtr<Document> document_;
+ base::TimeTicks time_origin_;
+
+ DISALLOW_COPY_AND_ASSIGN(PerformanceNavigationTiming);
+};
+} // namespace dom
+} // namespace cobalt
+
+#endif // COBALT_DOM_PERFORMANCE_NAVIGATION_TIMING_H_
diff --git a/src/cobalt/dom/performance_navigation_timing.idl b/src/cobalt/dom/performance_navigation_timing.idl
new file mode 100644
index 0000000..4883cc3
--- /dev/null
+++ b/src/cobalt/dom/performance_navigation_timing.idl
@@ -0,0 +1,27 @@
+// 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.
+
+// https://www.w3.org/TR/2022/WD-navigation-timing-2-20220224/
+
+[Exposed=Window]
+interface PerformanceNavigationTiming : PerformanceResourceTiming {
+ readonly attribute DOMHighResTimeStamp unloadEventStart;
+ readonly attribute DOMHighResTimeStamp unloadEventEnd;
+ readonly attribute DOMHighResTimeStamp domContentLoadedEventStart;
+ readonly attribute DOMHighResTimeStamp domContentLoadedEventEnd;
+ readonly attribute DOMHighResTimeStamp domComplete;
+ readonly attribute DOMHighResTimeStamp loadEventStart;
+ readonly attribute DOMHighResTimeStamp loadEventEnd;
+ readonly attribute NavigationType type;
+};
diff --git a/src/cobalt/dom/performance_observer.cc b/src/cobalt/dom/performance_observer.cc
index 6e0855a..eebf4b8 100644
--- a/src/cobalt/dom/performance_observer.cc
+++ b/src/cobalt/dom/performance_observer.cc
@@ -35,6 +35,9 @@
: callback_(observer, callback) {}
bool RunCallback(const scoped_refptr<PerformanceObserverEntryList>& entries,
const scoped_refptr<PerformanceObserver>& observer) override {
+ // ScriptCallback's lifetime is bined with JS PerformanceObserver object. We
+ // should not run callback, When JS object has been destroyed.
+ if (callback_.referenced_value().IsNull()) return false;
script::CallbackResult<void> result = callback_.value().Run(entries, observer);
return !result.exception;
}
diff --git a/src/cobalt/dom/performance_resource_timing.cc b/src/cobalt/dom/performance_resource_timing.cc
index 11dac39..4a0f314 100644
--- a/src/cobalt/dom/performance_resource_timing.cc
+++ b/src/cobalt/dom/performance_resource_timing.cc
@@ -41,7 +41,8 @@
cache_mode_(kPerformanceResourceTimingCacheMode),
transfer_size_(0),
timing_info_(timing_info),
- time_origin_(time_origin) {}
+ time_origin_(time_origin),
+ timing_info_response_end_(performance->Now()) {}
std::string PerformanceResourceTiming::initiator_type() const {
return initiator_type_;
@@ -112,7 +113,7 @@
}
DOMHighResTimeStamp PerformanceResourceTiming::response_end() const {
- return response_start();
+ return timing_info_response_end_;
}
unsigned long long PerformanceResourceTiming::transfer_size() const {
diff --git a/src/cobalt/dom/performance_resource_timing.h b/src/cobalt/dom/performance_resource_timing.h
index cab3c48..1809b4d 100644
--- a/src/cobalt/dom/performance_resource_timing.h
+++ b/src/cobalt/dom/performance_resource_timing.h
@@ -43,7 +43,7 @@
base::TimeTicks time_origin);
// Web API.
- std::string initiator_type() const;
+ virtual std::string initiator_type() const;
DOMHighResTimeStamp fetch_start() const;
DOMHighResTimeStamp domain_lookup_start() const;
DOMHighResTimeStamp domain_lookup_end() const;
@@ -52,9 +52,8 @@
DOMHighResTimeStamp secure_connection_start() const;
DOMHighResTimeStamp request_start() const;
DOMHighResTimeStamp response_start() const;
- // As we don't have response start in LoadTimingInfo, we use
- // response start instead.
- // TODO: Add response_end into LoadTimingInfo.
+ // As we don't have response end in LoadTimingInfo, we collect the timestamp
+ // of loading completes instead.
DOMHighResTimeStamp response_end() const;
unsigned long long transfer_size() const;
@@ -77,6 +76,7 @@
std::string requested_url_;
net::LoadTimingInfo timing_info_;
base::TimeTicks time_origin_;
+ DOMHighResTimeStamp timing_info_response_end_;
DISALLOW_COPY_AND_ASSIGN(PerformanceResourceTiming);
};
diff --git a/src/cobalt/dom/pointer_state.cc b/src/cobalt/dom/pointer_state.cc
index 7b2b2b4..49ae842 100644
--- a/src/cobalt/dom/pointer_state.cc
+++ b/src/cobalt/dom/pointer_state.cc
@@ -15,6 +15,7 @@
#include "cobalt/dom/pointer_state.h"
#include <algorithm>
+#include <utility>
#include "base/trace_event/trace_event.h"
#include "cobalt/dom/mouse_event.h"
@@ -27,7 +28,7 @@
void PointerState::QueuePointerEvent(const scoped_refptr<Event>& event) {
TRACE_EVENT1("cobalt::dom", "PointerState::QueuePointerEvent()", "event",
- event->type().c_str());
+ TRACE_STR_COPY(event->type().c_str()));
// Only accept this for event types that are MouseEvents or known derivatives.
SB_DCHECK(CanQueueEvent(event));
diff --git a/src/cobalt/dom/source_buffer.cc b/src/cobalt/dom/source_buffer.cc
index de6fd76..7fae5df 100644
--- a/src/cobalt/dom/source_buffer.cc
+++ b/src/cobalt/dom/source_buffer.cc
@@ -49,6 +49,7 @@
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
#include "cobalt/base/tokens.h"
#include "cobalt/dom/dom_exception.h"
#include "cobalt/dom/media_source.h"
@@ -215,6 +216,8 @@
void SourceBuffer::AppendBuffer(const script::Handle<script::ArrayBuffer>& data,
script::ExceptionState* exception_state) {
+ TRACE_EVENT1("cobalt::dom", "SourceBuffer::AppendBuffer()", "size",
+ data->ByteLength());
AppendBufferInternal(static_cast<const unsigned char*>(data->Data()),
data->ByteLength(), exception_state);
}
@@ -222,11 +225,15 @@
void SourceBuffer::AppendBuffer(
const script::Handle<script::ArrayBufferView>& data,
script::ExceptionState* exception_state) {
+ TRACE_EVENT1("cobalt::dom", "SourceBuffer::AppendBuffer()", "size",
+ data->ByteLength());
AppendBufferInternal(static_cast<const unsigned char*>(data->RawData()),
data->ByteLength(), exception_state);
}
void SourceBuffer::Abort(script::ExceptionState* exception_state) {
+ TRACE_EVENT0("cobalt::dom", "SourceBuffer::Abort()");
+
if (media_source_ == NULL) {
DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
return;
@@ -427,6 +434,7 @@
}
void SourceBuffer::OnAppendTimer() {
+ TRACE_EVENT0("cobalt::dom", "SourceBuffer::OnAppendTimer()");
const size_t kMaxAppendSize = 128 * 1024;
DCHECK(updating_);
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc
index 6fde6ea..9005f3c 100644
--- a/src/cobalt/dom/window.cc
+++ b/src/cobalt/dom/window.cc
@@ -165,7 +165,8 @@
ALLOW_THIS_IN_INITIALIZER_LIST(
relay_on_load_event_(new RelayLoadEvent(this))),
ALLOW_THIS_IN_INITIALIZER_LIST(window_timers_(
- new WindowTimers(this, debugger_hooks(), initial_application_state))),
+ new WindowTimers(this, dom_stat_tracker, debugger_hooks(),
+ initial_application_state))),
ALLOW_THIS_IN_INITIALIZER_LIST(animation_frame_request_callback_list_(
new AnimationFrameRequestCallbackList(this, debugger_hooks()))),
crypto_(new Crypto()),
@@ -418,7 +419,7 @@
int Window::SetInterval(const WindowTimers::TimerCallbackArg& handler,
int timeout) {
LOG_IF(WARNING, timeout < 0)
- << "Window::SetInterval received negative timeout: " << timeout;
+ << "Window::SetInterval received negative interval: " << timeout;
timeout = std::max(timeout, 0);
int return_value = 0;
@@ -510,7 +511,7 @@
void Window::InjectEvent(const scoped_refptr<Event>& event) {
TRACE_EVENT1("cobalt::dom", "Window::InjectEvent()", "event",
- event->type().c_str());
+ TRACE_STR_COPY(event->type().c_str()));
// Forward the event on to the correct object in DOM.
if (event->GetWrappableType() == base::GetTypeId<KeyboardEvent>()) {
diff --git a/src/cobalt/dom/window.h b/src/cobalt/dom/window.h
index d6bf1ab..70b11f7 100644
--- a/src/cobalt/dom/window.h
+++ b/src/cobalt/dom/window.h
@@ -409,6 +409,8 @@
const scoped_refptr<media_session::MediaSession> media_session() const;
+ loader::Loader* GetDocumentLoader() const { return document_loader_.get(); }
+
DEFINE_WRAPPABLE_TYPE(Window);
private:
diff --git a/src/cobalt/dom/window_timers.cc b/src/cobalt/dom/window_timers.cc
index 3b5c079..951f7ee 100644
--- a/src/cobalt/dom/window_timers.cc
+++ b/src/cobalt/dom/window_timers.cc
@@ -40,16 +40,13 @@
}
if (callbacks_active_) {
- auto* timer = new Timer(type, owner_, handler, timeout, handle, this);
+ scoped_refptr<Timer> timer =
+ new Timer(type, owner_, dom_stat_tracker_, debugger_hooks_, handler,
+ timeout, handle, this);
if (application_state_ != base::kApplicationStateFrozen) {
timer->StartOrResume();
}
timers_[handle] = timer;
- debugger_hooks_.AsyncTaskScheduled(
- timers_[handle], type == Timer::kOneShot ? "SetTimeout" : "SetInterval",
- type == Timer::kOneShot
- ? base::DebuggerHooks::AsyncTaskFrequency::kOneshot
- : base::DebuggerHooks::AsyncTaskFrequency::kRecurring);
} else {
timers_[handle] = nullptr;
}
@@ -62,33 +59,31 @@
return TryAddNewTimer(Timer::kOneShot, handler, timeout);
}
-void WindowTimers::ClearTimeout(int handle) { timers_.erase(handle); }
+void WindowTimers::ClearTimeout(int handle) {
+ Timers::iterator timer = timers_.find(handle);
+ if (timer == timers_.end()) {
+ return;
+ }
+ if (timer->second) {
+ timer->second->Disable();
+ }
+ timers_.erase(timer);
+}
int WindowTimers::SetInterval(const TimerCallbackArg& handler, int timeout) {
TRACK_MEMORY_SCOPE("DOM");
return TryAddNewTimer(Timer::kRepeating, handler, timeout);
}
-void WindowTimers::ClearInterval(int handle) {
- Timers::iterator timer = timers_.find(handle);
- if (timer != timers_.end()) {
- debugger_hooks_.AsyncTaskCanceled(timer->second);
- timers_.erase(timer);
- }
-}
-
-void WindowTimers::ClearAllIntervalsAndTimeouts() {
- for (auto& timer_entry : timers_) {
- debugger_hooks_.AsyncTaskCanceled(timer_entry.second);
- }
- timers_.clear();
-}
+void WindowTimers::ClearInterval(int handle) { ClearTimeout(handle); }
void WindowTimers::DisableCallbacks() {
callbacks_active_ = false;
// Immediately cancel any pending timers.
for (auto& timer_entry : timers_) {
- debugger_hooks_.AsyncTaskCanceled(timer_entry.second);
+ if (timer_entry.second) {
+ timer_entry.second->Disable();
+ }
timer_entry.second = nullptr;
}
}
@@ -113,38 +108,6 @@
return 0;
}
-void WindowTimers::RunTimerCallback(int handle) {
- TRACE_EVENT0("cobalt::dom", "WindowTimers::RunTimerCallback");
- DCHECK(callbacks_active_)
- << "All timer callbacks should have already been cancelled.";
- Timers::iterator timer = timers_.find(handle);
- DCHECK(timer != timers_.end());
-
- // The callback is now being run. Track it in the global stats.
- GlobalStats::GetInstance()->StartJavaScriptEvent();
-
- {
- // Keep a |TimerInfo| reference, so it won't be released when running the
- // callback.
- scoped_refptr<Timer> timer_info = timer->second;
- base::ScopedAsyncTask async_task(debugger_hooks_, timer_info);
- timer_info->callback_reference().value().Run();
- }
-
- // After running the callback, double check whether the timer is still there
- // since it might be deleted inside the callback.
- timer = timers_.find(handle);
- // If the timer is not deleted and is not running, it means it is an oneshot
- // timer and has just fired the shot, and it should be deleted now.
- if (timer != timers_.end() && !timer->second->timer()->IsRunning()) {
- debugger_hooks_.AsyncTaskCanceled(timer->second);
- timers_.erase(timer);
- }
-
- // The callback has finished running. Stop tracking it in the global stats.
- GlobalStats::GetInstance()->StopJavaScriptEvent();
-}
-
void WindowTimers::SetApplicationState(base::ApplicationState state) {
switch (state) {
case base::kApplicationStateFrozen:
@@ -169,13 +132,66 @@
}
WindowTimers::Timer::Timer(TimerType type, script::Wrappable* const owner,
+ DomStatTracker* dom_stat_tracker,
+ const base::DebuggerHooks& debugger_hooks,
const TimerCallbackArg& callback, int timeout,
int handle, WindowTimers* window_timers)
: type_(type),
callback_(owner, callback),
+ dom_stat_tracker_(dom_stat_tracker),
+ debugger_hooks_(debugger_hooks),
timeout_(timeout),
handle_(handle),
- window_timers_(window_timers) {}
+ active_(false),
+ window_timers_(window_timers) {
+ debugger_hooks_.AsyncTaskScheduled(
+ this, type == Timer::kOneShot ? "SetTimeout" : "SetInterval",
+ type == Timer::kOneShot
+ ? base::DebuggerHooks::AsyncTaskFrequency::kOneshot
+ : base::DebuggerHooks::AsyncTaskFrequency::kRecurring);
+ switch (type) {
+ case Timer::kOneShot:
+ dom_stat_tracker_->OnWindowTimersTimeoutCreated();
+ break;
+ case Timer::kRepeating:
+ dom_stat_tracker_->OnWindowTimersIntervalCreated();
+ break;
+ }
+}
+
+WindowTimers::Timer::~Timer() {
+ switch (type_) {
+ case Timer::kOneShot:
+ dom_stat_tracker_->OnWindowTimersTimeoutDestroyed();
+ break;
+ case Timer::kRepeating:
+ dom_stat_tracker_->OnWindowTimersIntervalDestroyed();
+ break;
+ }
+ debugger_hooks_.AsyncTaskCanceled(this);
+}
+
+void WindowTimers::Timer::Run() {
+ if (!active_) {
+ return;
+ }
+
+ // The callback is now being run. Track it in the global stats.
+ GlobalStats::GetInstance()->StartJavaScriptEvent();
+
+ {
+ base::ScopedAsyncTask async_task(debugger_hooks_, this);
+ callback_.value().Run();
+ }
+
+ // Remove one-shot timers from the timers list.
+ if (active_ && !timer_->IsRunning()) {
+ window_timers_->ClearTimeout(handle_);
+ }
+
+ // The callback has finished running. Stop tracking it in the global stats.
+ GlobalStats::GetInstance()->StopJavaScriptEvent();
+}
void WindowTimers::Timer::Pause() {
if (timer_) {
@@ -188,6 +204,7 @@
void WindowTimers::Timer::StartOrResume() {
if (timer_ != nullptr) return;
+ active_ = true;
switch (type_) {
case kOneShot:
if (desired_run_time_) {
@@ -215,20 +232,27 @@
// The timer was paused and the desired run time is in the past.
// Call the callback once before continuing the repeating timer.
base::SequencedTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(&WindowTimers::RunTimerCallback,
- base::Unretained(window_timers_), handle_));
+ FROM_HERE, base::Bind(&WindowTimers::Timer::Run, this));
}
break;
}
}
+void WindowTimers::Timer::Disable() {
+ // Prevent the timer callback from doing anything.
+ active_ = false;
+
+ // Clear the TimerBase object to release the reference to |this| in the
+ // user callback.
+ timer_.reset();
+}
+
template <class TimerClass>
std::unique_ptr<base::internal::TimerBase>
WindowTimers::Timer::CreateAndStart() {
auto* timer = new TimerClass();
timer->Start(FROM_HERE, base::TimeDelta::FromMilliseconds(timeout_),
- base::Bind(&WindowTimers::RunTimerCallback,
- base::Unretained(window_timers_), handle_));
+ base::Bind(&WindowTimers::Timer::Run, this));
return std::unique_ptr<base::internal::TimerBase>(timer);
}
diff --git a/src/cobalt/dom/window_timers.h b/src/cobalt/dom/window_timers.h
index 42dcf56..fc81784 100644
--- a/src/cobalt/dom/window_timers.h
+++ b/src/cobalt/dom/window_timers.h
@@ -26,6 +26,7 @@
#include "base/timer/timer.h"
#include "cobalt/base/application_state.h"
#include "cobalt/base/debugger_hooks.h"
+#include "cobalt/dom/dom_stat_tracker.h"
#include "cobalt/script/callback_function.h"
#include "cobalt/script/script_value.h"
#include "cobalt/script/wrappable.h"
@@ -38,12 +39,14 @@
typedef script::CallbackFunction<void()> TimerCallback;
typedef script::ScriptValue<TimerCallback> TimerCallbackArg;
explicit WindowTimers(script::Wrappable* const owner,
+ DomStatTracker* dom_stat_tracker,
const base::DebuggerHooks& debugger_hooks,
base::ApplicationState application_state)
: owner_(owner),
+ dom_stat_tracker_(dom_stat_tracker),
debugger_hooks_(debugger_hooks),
application_state_(application_state) {}
- ~WindowTimers() {}
+ ~WindowTimers() { DisableCallbacks(); }
int SetTimeout(const TimerCallbackArg& handler, int timeout);
@@ -53,8 +56,6 @@
void ClearInterval(int handle);
- void ClearAllIntervalsAndTimeouts();
-
// When called, it will irreversibly put the WindowTimers object in an
// inactive state where timer callbacks are ignored. This is useful when
// we're in the process of shutting down and wish to drain the JavaScript
@@ -69,11 +70,12 @@
enum TimerType { kOneShot, kRepeating };
Timer(TimerType type, script::Wrappable* const owner,
+ DomStatTracker* dom_stat_tracker,
+ const base::DebuggerHooks& debugger_hooks,
const TimerCallbackArg& callback, int timeout, int handle,
WindowTimers* window_timers);
- base::internal::TimerBase* timer() { return timer_.get(); }
- TimerCallbackArg::Reference& callback_reference() { return callback_; }
+ void Run();
// Pause this timer. The timer will not fire when paused.
void Pause();
@@ -81,8 +83,11 @@
// time is in the past, it will fire immediately.
void StartOrResume();
+ // Disable the timer callback.
+ void Disable();
+
private:
- ~Timer() {}
+ ~Timer();
// Create and start a timer of the specified TimerClass type.
template <class TimerClass>
@@ -91,8 +96,11 @@
TimerType type_;
std::unique_ptr<base::internal::TimerBase> timer_;
TimerCallbackArg::Reference callback_;
+ DomStatTracker* const dom_stat_tracker_;
+ const base::DebuggerHooks& debugger_hooks_;
int timeout_;
int handle_;
+ bool active_;
WindowTimers* window_timers_;
// Store the desired run tim of a paused timer.
@@ -111,13 +119,10 @@
// if none can be found.
int GetFreeTimerHandle();
- // This callback, when called by Timer, runs the callback in TimerInfo
- // and removes the handle if necessary.
- void RunTimerCallback(int handle);
-
Timers timers_;
int current_timer_index_ = 0;
script::Wrappable* const owner_;
+ DomStatTracker* const dom_stat_tracker_;
const base::DebuggerHooks& debugger_hooks_;
// Set to false when we're about to shutdown, to ensure that no new JavaScript
diff --git a/src/cobalt/dom/window_timers_test.cc b/src/cobalt/dom/window_timers_test.cc
new file mode 100644
index 0000000..57b233e
--- /dev/null
+++ b/src/cobalt/dom/window_timers_test.cc
@@ -0,0 +1,468 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/dom/window_timers.h"
+
+#include <memory>
+#include <string>
+
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "cobalt/dom/dom_stat_tracker.h"
+#include "cobalt/script/callback_function.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/javascript_engine.h"
+#include "cobalt/script/testing/fake_script_value.h"
+#include "net/test/test_with_scoped_task_environment.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace dom {
+
+namespace testing {
+
+using ::testing::_;
+using ::testing::Return;
+
+class MockTimerCallback : public WindowTimers::TimerCallback {
+ public:
+ MOCK_CONST_METHOD0(Run, script::CallbackResult<void>());
+ void ExpectRunCall(int times) {
+ EXPECT_CALL(*this, Run())
+ .Times(times)
+ .WillRepeatedly(Return(script::CallbackResult<void>()));
+ }
+};
+
+class MockDebuggerHooks : public base::DebuggerHooks {
+ public:
+ MOCK_CONST_METHOD2(ConsoleLog, void(::logging::LogSeverity, std::string));
+ MOCK_CONST_METHOD3(AsyncTaskScheduled,
+ void(const void*, const std::string&, AsyncTaskFrequency));
+ MOCK_CONST_METHOD1(AsyncTaskStarted, void(const void*));
+ MOCK_CONST_METHOD1(AsyncTaskFinished, void(const void*));
+ MOCK_CONST_METHOD1(AsyncTaskCanceled, void(const void*));
+
+ void ExpectAsyncTaskScheduled(int times) {
+ EXPECT_CALL(*this, AsyncTaskScheduled(_, _, _)).Times(times);
+ }
+ void ExpectAsyncTaskStarted(int times) {
+ EXPECT_CALL(*this, AsyncTaskStarted(_)).Times(times);
+ }
+ void ExpectAsyncTaskFinished(int times) {
+ EXPECT_CALL(*this, AsyncTaskFinished(_)).Times(times);
+ }
+ void ExpectAsyncTaskCanceled(int times) {
+ EXPECT_CALL(*this, AsyncTaskCanceled(_)).Times(times);
+ }
+};
+
+} // namespace testing
+
+namespace {
+const int kTimerDelayInMilliseconds = 100;
+} // namespace
+
+using ::cobalt::script::testing::FakeScriptValue;
+
+class WindowTimersTest : public ::testing::Test,
+ public net::WithScopedTaskEnvironment {
+ protected:
+ WindowTimersTest()
+ : WithScopedTaskEnvironment(
+ base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME),
+ dom_stat_tracker_("WindowTimersTest"),
+ callback_(&mock_timer_callback_) {
+ script::Wrappable* foo = nullptr;
+ timers_.reset(
+ new WindowTimers(foo, &dom_stat_tracker_, hooks_,
+ base::ApplicationState::kApplicationStateStarted));
+ }
+
+ ~WindowTimersTest() override {}
+
+ testing::MockDebuggerHooks hooks_;
+ DomStatTracker dom_stat_tracker_;
+ std::unique_ptr<WindowTimers> timers_;
+ testing::MockTimerCallback mock_timer_callback_;
+ FakeScriptValue<WindowTimers::TimerCallback> callback_;
+};
+
+TEST_F(WindowTimersTest, TimeoutIsNotCalledDirectly) {
+ hooks_.ExpectAsyncTaskScheduled(1);
+ hooks_.ExpectAsyncTaskCanceled(1);
+
+ mock_timer_callback_.ExpectRunCall(0);
+ timers_->SetTimeout(callback_, 0);
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(0));
+}
+
+TEST_F(WindowTimersTest, TimeoutZeroIsCalledImmediatelyFromTask) {
+ hooks_.ExpectAsyncTaskScheduled(1);
+ hooks_.ExpectAsyncTaskCanceled(1);
+ hooks_.ExpectAsyncTaskStarted(1);
+ hooks_.ExpectAsyncTaskFinished(1);
+
+ mock_timer_callback_.ExpectRunCall(1);
+ timers_->SetTimeout(callback_, 0);
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(0));
+
+ RunUntilIdle();
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 0);
+}
+
+TEST_F(WindowTimersTest, TimeoutIsNotCalledBeforeDelay) {
+ hooks_.ExpectAsyncTaskScheduled(1);
+ hooks_.ExpectAsyncTaskCanceled(1);
+
+ hooks_.ExpectAsyncTaskStarted(0);
+ hooks_.ExpectAsyncTaskFinished(0);
+ mock_timer_callback_.ExpectRunCall(0);
+ timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+ FastForwardBy(
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds - 1));
+ RunUntilIdle();
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+}
+
+TEST_F(WindowTimersTest, TimeoutIsCalledAfterDelay) {
+ hooks_.ExpectAsyncTaskScheduled(1);
+ hooks_.ExpectAsyncTaskCanceled(1);
+ hooks_.ExpectAsyncTaskStarted(1);
+ hooks_.ExpectAsyncTaskFinished(1);
+
+ mock_timer_callback_.ExpectRunCall(1);
+ timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+ FastForwardBy(base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+ RunUntilIdle();
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 0);
+}
+
+TEST_F(WindowTimersTest, TimeoutIsNotCalledRepeatedly) {
+ hooks_.ExpectAsyncTaskScheduled(1);
+ hooks_.ExpectAsyncTaskCanceled(1);
+ hooks_.ExpectAsyncTaskStarted(1);
+ hooks_.ExpectAsyncTaskFinished(1);
+
+ mock_timer_callback_.ExpectRunCall(1);
+ timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+ FastForwardBy(
+ base::TimeDelta::FromMilliseconds(10 * kTimerDelayInMilliseconds));
+ FastForwardUntilNoTasksRemain();
+ RunUntilIdle();
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 0);
+}
+
+TEST_F(WindowTimersTest, TimeoutIsCalledWhenDelayed) {
+ hooks_.ExpectAsyncTaskScheduled(1);
+ hooks_.ExpectAsyncTaskCanceled(1);
+ hooks_.ExpectAsyncTaskStarted(1);
+ hooks_.ExpectAsyncTaskFinished(1);
+
+ mock_timer_callback_.ExpectRunCall(1);
+ timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+ AdvanceMockTickClock(
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds + 1000));
+ RunUntilIdle();
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 0);
+}
+
+TEST_F(WindowTimersTest, MultipleTimeouts) {
+ hooks_.ExpectAsyncTaskScheduled(2);
+ hooks_.ExpectAsyncTaskCanceled(2);
+ hooks_.ExpectAsyncTaskStarted(2);
+ hooks_.ExpectAsyncTaskFinished(2);
+
+ mock_timer_callback_.ExpectRunCall(2);
+ timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
+ timers_->SetTimeout(callback_, kTimerDelayInMilliseconds * 3);
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+ FastForwardBy(
+ base::TimeDelta::FromMilliseconds(3 * kTimerDelayInMilliseconds));
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 0);
+}
+
+TEST_F(WindowTimersTest, ActiveTimeoutsAreCounted) {
+ hooks_.ExpectAsyncTaskScheduled(2);
+ hooks_.ExpectAsyncTaskCanceled(2);
+ hooks_.ExpectAsyncTaskStarted(2);
+ hooks_.ExpectAsyncTaskFinished(2);
+
+ mock_timer_callback_.ExpectRunCall(2);
+ timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
+ timers_->SetTimeout(callback_, kTimerDelayInMilliseconds * 3);
+
+ dom_stat_tracker_.FlushPeriodicTracking();
+ EXPECT_EQ("0", base::CValManager::GetInstance()
+ ->GetValueAsString(
+ "Count.WindowTimersTest.DOM.WindowTimers.Interval")
+ .value_or("Foo"));
+ EXPECT_EQ("2", base::CValManager::GetInstance()
+ ->GetValueAsString(
+ "Count.WindowTimersTest.DOM.WindowTimers.Timeout")
+ .value_or("Foo"));
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+ FastForwardBy(
+ base::TimeDelta::FromMilliseconds(3 * kTimerDelayInMilliseconds));
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 0);
+
+ dom_stat_tracker_.FlushPeriodicTracking();
+ EXPECT_EQ("0", base::CValManager::GetInstance()
+ ->GetValueAsString(
+ "Count.WindowTimersTest.DOM.WindowTimers.Interval")
+ .value_or("Foo"));
+ EXPECT_EQ("0", base::CValManager::GetInstance()
+ ->GetValueAsString(
+ "Count.WindowTimersTest.DOM.WindowTimers.Timeout")
+ .value_or("Foo"));
+}
+
+TEST_F(WindowTimersTest, IntervalZeroTaskIsScheduledImmediately) {
+ hooks_.ExpectAsyncTaskScheduled(1);
+ hooks_.ExpectAsyncTaskCanceled(1);
+
+ timers_->SetInterval(callback_, 0);
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(0));
+
+ // Note: We can't use RunUntilIdle() or FastForwardBy() in this case,
+ // because the task queue never gets idle.
+}
+
+TEST_F(WindowTimersTest, IntervalIsNotCalledBeforeDelay) {
+ hooks_.ExpectAsyncTaskScheduled(1);
+ hooks_.ExpectAsyncTaskCanceled(1);
+
+ hooks_.ExpectAsyncTaskStarted(0);
+ hooks_.ExpectAsyncTaskFinished(0);
+ mock_timer_callback_.ExpectRunCall(0);
+ timers_->SetInterval(callback_, kTimerDelayInMilliseconds);
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+ FastForwardBy(
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds - 1));
+ RunUntilIdle();
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+}
+
+TEST_F(WindowTimersTest, IntervalIsCalledAfterDelay) {
+ hooks_.ExpectAsyncTaskScheduled(1);
+ hooks_.ExpectAsyncTaskCanceled(1);
+ hooks_.ExpectAsyncTaskStarted(1);
+ hooks_.ExpectAsyncTaskFinished(1);
+
+ mock_timer_callback_.ExpectRunCall(1);
+ timers_->SetInterval(callback_, kTimerDelayInMilliseconds);
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+ FastForwardBy(base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+ RunUntilIdle();
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+}
+
+TEST_F(WindowTimersTest, IntervalIsCalledRepeatedly) {
+ int interval_count = 10;
+
+ hooks_.ExpectAsyncTaskScheduled(1);
+ hooks_.ExpectAsyncTaskCanceled(1);
+ hooks_.ExpectAsyncTaskStarted(interval_count);
+ hooks_.ExpectAsyncTaskFinished(interval_count);
+
+ mock_timer_callback_.ExpectRunCall(interval_count);
+ timers_->SetInterval(callback_, kTimerDelayInMilliseconds);
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+ FastForwardBy(base::TimeDelta::FromMilliseconds(interval_count *
+ kTimerDelayInMilliseconds));
+ RunUntilIdle();
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+}
+
+TEST_F(WindowTimersTest, IntervalDrifts) {
+ int interval_count = 10;
+
+ hooks_.ExpectAsyncTaskScheduled(1);
+ hooks_.ExpectAsyncTaskCanceled(1);
+ hooks_.ExpectAsyncTaskStarted(interval_count);
+ hooks_.ExpectAsyncTaskFinished(interval_count);
+
+ mock_timer_callback_.ExpectRunCall(interval_count);
+ timers_->SetInterval(callback_, kTimerDelayInMilliseconds);
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+ while (interval_count--) {
+ AdvanceMockTickClock(
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds + 1000));
+ RunUntilIdle();
+ }
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 1);
+}
+
+TEST_F(WindowTimersTest, MultipleIntervals) {
+ hooks_.ExpectAsyncTaskScheduled(2);
+ hooks_.ExpectAsyncTaskCanceled(2);
+ hooks_.ExpectAsyncTaskStarted(4);
+ hooks_.ExpectAsyncTaskFinished(4);
+
+ mock_timer_callback_.ExpectRunCall(4);
+ timers_->SetInterval(callback_, kTimerDelayInMilliseconds);
+ timers_->SetInterval(callback_, kTimerDelayInMilliseconds * 3);
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+ FastForwardBy(
+ base::TimeDelta::FromMilliseconds(3 * kTimerDelayInMilliseconds));
+ RunUntilIdle();
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
+}
+
+TEST_F(WindowTimersTest, ActiveIntervalsAreCounted) {
+ hooks_.ExpectAsyncTaskScheduled(2);
+ hooks_.ExpectAsyncTaskCanceled(2);
+ hooks_.ExpectAsyncTaskStarted(4);
+ hooks_.ExpectAsyncTaskFinished(4);
+
+ mock_timer_callback_.ExpectRunCall(4);
+ timers_->SetInterval(callback_, kTimerDelayInMilliseconds);
+ timers_->SetInterval(callback_, kTimerDelayInMilliseconds * 3);
+
+ dom_stat_tracker_.FlushPeriodicTracking();
+ EXPECT_EQ("2", base::CValManager::GetInstance()
+ ->GetValueAsString(
+ "Count.WindowTimersTest.DOM.WindowTimers.Interval")
+ .value_or("Foo"));
+ EXPECT_EQ("0", base::CValManager::GetInstance()
+ ->GetValueAsString(
+ "Count.WindowTimersTest.DOM.WindowTimers.Timeout")
+ .value_or("Foo"));
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+ FastForwardBy(
+ base::TimeDelta::FromMilliseconds(3 * kTimerDelayInMilliseconds));
+ RunUntilIdle();
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
+
+ dom_stat_tracker_.FlushPeriodicTracking();
+ EXPECT_EQ("2", base::CValManager::GetInstance()
+ ->GetValueAsString(
+ "Count.WindowTimersTest.DOM.WindowTimers.Interval")
+ .value_or("Foo"));
+ EXPECT_EQ("0", base::CValManager::GetInstance()
+ ->GetValueAsString(
+ "Count.WindowTimersTest.DOM.WindowTimers.Timeout")
+ .value_or("Foo"));
+}
+
+TEST_F(WindowTimersTest, ActiveIntervalsAndTimeoutsAreCounted) {
+ hooks_.ExpectAsyncTaskScheduled(4);
+ hooks_.ExpectAsyncTaskCanceled(4);
+ hooks_.ExpectAsyncTaskStarted(6);
+ hooks_.ExpectAsyncTaskFinished(6);
+
+ mock_timer_callback_.ExpectRunCall(6);
+ timers_->SetInterval(callback_, kTimerDelayInMilliseconds);
+ timers_->SetInterval(callback_, kTimerDelayInMilliseconds * 3);
+ timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
+ timers_->SetTimeout(callback_, kTimerDelayInMilliseconds * 3);
+
+ dom_stat_tracker_.FlushPeriodicTracking();
+ EXPECT_EQ("2", base::CValManager::GetInstance()
+ ->GetValueAsString(
+ "Count.WindowTimersTest.DOM.WindowTimers.Interval")
+ .value_or("Foo"));
+ EXPECT_EQ("2", base::CValManager::GetInstance()
+ ->GetValueAsString(
+ "Count.WindowTimersTest.DOM.WindowTimers.Timeout")
+ .value_or("Foo"));
+
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 4);
+ EXPECT_EQ(NextMainThreadPendingTaskDelay(),
+ base::TimeDelta::FromMilliseconds(kTimerDelayInMilliseconds));
+
+ FastForwardBy(
+ base::TimeDelta::FromMilliseconds(3 * kTimerDelayInMilliseconds));
+ RunUntilIdle();
+ EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
+
+ dom_stat_tracker_.FlushPeriodicTracking();
+ EXPECT_EQ("2", base::CValManager::GetInstance()
+ ->GetValueAsString(
+ "Count.WindowTimersTest.DOM.WindowTimers.Interval")
+ .value_or("Foo"));
+ EXPECT_EQ("0", base::CValManager::GetInstance()
+ ->GetValueAsString(
+ "Count.WindowTimersTest.DOM.WindowTimers.Timeout")
+ .value_or("Foo"));
+}
+
+
+} // namespace dom
+} // namespace cobalt
diff --git a/src/cobalt/dom_parser/libxml_parser_wrapper.cc b/src/cobalt/dom_parser/libxml_parser_wrapper.cc
index 4642ab1..d3bad2d 100644
--- a/src/cobalt/dom_parser/libxml_parser_wrapper.cc
+++ b/src/cobalt/dom_parser/libxml_parser_wrapper.cc
@@ -167,8 +167,8 @@
}
void Characters(void* context, const xmlChar* ch, int len) {
- ToLibxmlParserWrapper(context)
- ->OnCharacters(std::string(ToCString(ch), static_cast<size_t>(len)));
+ ToLibxmlParserWrapper(context)->OnCharacters(
+ std::string(ToCString(ch), static_cast<size_t>(len)));
}
void Comment(void* context, const xmlChar* value) {
@@ -200,8 +200,8 @@
}
void CDATABlock(void* context, const xmlChar* value, int len) {
- ToLibxmlParserWrapper(context)
- ->OnCDATABlock(std::string(ToCString(value), static_cast<size_t>(len)));
+ ToLibxmlParserWrapper(context)->OnCDATABlock(
+ std::string(ToCString(value), static_cast<size_t>(len)));
}
//////////////////////////////////////////////////////////////////
@@ -230,8 +230,9 @@
}
if (IsFullDocument()) {
- document_->PostToDispatchEventName(FROM_HERE,
- base::Tokens::domcontentloaded());
+ // Collect dom content loaded timing info and dispatch dom content
+ // loaded event.
+ document_->CollectTimingInfoAndDispatchEvent();
}
}
diff --git a/src/cobalt/extension/extension_test.cc b/src/cobalt/extension/extension_test.cc
index 43b4925..b7c0602 100644
--- a/src/cobalt/extension/extension_test.cc
+++ b/src/cobalt/extension/extension_test.cc
@@ -18,6 +18,7 @@
#include "cobalt/extension/crash_handler.h"
#include "cobalt/extension/cwrappers.h"
#include "cobalt/extension/font.h"
+#include "cobalt/extension/free_space.h"
#include "cobalt/extension/graphics.h"
#include "cobalt/extension/installation_manager.h"
#include "cobalt/extension/javascript_cache.h"
@@ -349,5 +350,24 @@
<< "Extension struct should be a singleton";
}
+TEST(ExtensionTest, FreeSpace) {
+ typedef CobaltExtensionFreeSpaceApi ExtensionApi;
+ const char* kExtensionName = kCobaltExtensionFreeSpaceName;
+
+ const ExtensionApi* extension_api =
+ static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+ if (!extension_api) {
+ return;
+ }
+
+ EXPECT_STREQ(extension_api->name, kExtensionName);
+ EXPECT_EQ(extension_api->version, 1u);
+ EXPECT_NE(extension_api->MeasureFreeSpace, nullptr);
+
+ const ExtensionApi* second_extension_api =
+ static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+ EXPECT_EQ(second_extension_api, extension_api)
+ << "Extension struct should be a singleton";
+}
} // namespace extension
} // namespace cobalt
diff --git a/src/cobalt/extension/free_space.h b/src/cobalt/extension/free_space.h
new file mode 100644
index 0000000..53d466b
--- /dev/null
+++ b/src/cobalt/extension/free_space.h
@@ -0,0 +1,50 @@
+// 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_EXTENSION_FREE_SPACE_H_
+#define COBALT_EXTENSION_FREE_SPACE_H_
+
+#include <stdint.h>
+
+#include "starboard/system.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define kCobaltExtensionFreeSpaceName "dev.cobalt.extension.FreeSpace"
+
+typedef struct CobaltExtensionFreeSpaceApi {
+ // Name should be the string |kCobaltExtensionFreeSpaceName|.
+ // This helps to validate that the extension API is correct.
+ const char* name;
+
+ // This specifies the version of the API that is implemented.
+ uint32_t version;
+
+ // The fields below this point were added in version 1 or later.
+
+ // Returns the free space in bytes for the provided |system_path_id|.
+ // If there is no implementation for the that |system_path_id| or
+ // if there was an error -1 is returned.
+ int64_t (*MeasureFreeSpace)(SbSystemPathId system_path_id);
+} CobaltExtensionFreeSpaceApi;
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // COBALT_EXTENSION_FREE_SPACE_H_
diff --git a/src/cobalt/h5vcc/h5vcc.cc b/src/cobalt/h5vcc/h5vcc.cc
index 196c708..e1e9e04 100644
--- a/src/cobalt/h5vcc/h5vcc.cc
+++ b/src/cobalt/h5vcc/h5vcc.cc
@@ -28,6 +28,9 @@
runtime_ = new H5vccRuntime(settings.event_dispatcher);
settings_ =
new H5vccSettings(settings.media_module, settings.network_module,
+#if SB_IS(EVERGREEN)
+ settings.updater_module,
+#endif
settings.user_agent_data, settings.global_environment);
#if defined(COBALT_ENABLE_SSO)
sso_ = new H5vccSso();
diff --git a/src/cobalt/h5vcc/h5vcc_settings.cc b/src/cobalt/h5vcc/h5vcc_settings.cc
index 2607bed..83bf08b 100644
--- a/src/cobalt/h5vcc/h5vcc_settings.cc
+++ b/src/cobalt/h5vcc/h5vcc_settings.cc
@@ -21,18 +21,29 @@
H5vccSettings::H5vccSettings(media::MediaModule* media_module,
cobalt::network::NetworkModule* network_module,
+#if SB_IS(EVERGREEN)
+ cobalt::updater::UpdaterModule* updater_module,
+#endif
dom::NavigatorUAData* user_agent_data,
script::GlobalEnvironment* global_environment)
: media_module_(media_module),
network_module_(network_module),
+#if SB_IS(EVERGREEN)
+ updater_module_(updater_module),
+#endif
user_agent_data_(user_agent_data),
- global_environment_(global_environment) {}
+ global_environment_(global_environment) {
+}
bool H5vccSettings::Set(const std::string& name, int32 value) const {
const char kMediaPrefix[] = "Media.";
const char kNavigatorUAData[] = "NavigatorUAData";
const char kQUIC[] = "QUIC";
+#if SB_IS(EVERGREEN)
+ const char kUpdaterMinFreeSpaceBytes[] = "Updater.MinFreeSpaceBytes";
+#endif
+
if (name.compare(kMediaPrefix) == 0) {
return media_module_ ? media_module_->SetConfiguration(name, value) : false;
}
@@ -51,6 +62,12 @@
}
}
+#if SB_IS(EVERGREEN)
+ if (name.compare(kUpdaterMinFreeSpaceBytes) == 0) {
+ updater_module_->SetMinFreeSpaceBytes(value);
+ return true;
+ }
+#endif
return false;
}
diff --git a/src/cobalt/h5vcc/h5vcc_settings.h b/src/cobalt/h5vcc/h5vcc_settings.h
index ec4d4c1..086a524 100644
--- a/src/cobalt/h5vcc/h5vcc_settings.h
+++ b/src/cobalt/h5vcc/h5vcc_settings.h
@@ -23,6 +23,10 @@
#include "cobalt/script/global_environment.h"
#include "cobalt/script/wrappable.h"
+#if SB_IS(EVERGREEN)
+#include "cobalt/updater/updater_module.h"
+#endif
+
namespace cobalt {
namespace h5vcc {
@@ -33,6 +37,9 @@
public:
explicit H5vccSettings(media::MediaModule* media_module,
cobalt::network::NetworkModule* network_module,
+#if SB_IS(EVERGREEN)
+ cobalt::updater::UpdaterModule* updater_module,
+#endif
dom::NavigatorUAData* user_agent_data,
script::GlobalEnvironment* global_environment);
@@ -46,6 +53,9 @@
private:
media::MediaModule* media_module_;
cobalt::network::NetworkModule* network_module_ = nullptr;
+#if SB_IS(EVERGREEN)
+ cobalt::updater::UpdaterModule* updater_module_ = nullptr;
+#endif
dom::NavigatorUAData* user_agent_data_;
script::GlobalEnvironment* global_environment_;
diff --git a/src/cobalt/h5vcc/h5vcc_trace_event.cc b/src/cobalt/h5vcc/h5vcc_trace_event.cc
index 5e27555..2a06dee 100644
--- a/src/cobalt/h5vcc/h5vcc_trace_event.cc
+++ b/src/cobalt/h5vcc/h5vcc_trace_event.cc
@@ -14,6 +14,8 @@
#include "cobalt/h5vcc/h5vcc_trace_event.h"
+#include "base/files/file_util.h"
+
namespace cobalt {
namespace h5vcc {
@@ -29,17 +31,35 @@
} else {
base::FilePath output_filepath(
output_filename.empty() ? kOutputTraceFilename : output_filename);
+ last_absolute_path_.clear();
trace_to_file_.reset(new trace_event::ScopedTraceToFile(output_filepath));
}
}
void H5vccTraceEvent::Stop() {
if (trace_to_file_) {
+ last_absolute_path_ = trace_to_file_->absolute_output_path();
trace_to_file_.reset();
} else {
DLOG(WARNING) << "H5vccTraceEvent is already stopped.";
}
}
+std::string H5vccTraceEvent::Read(const std::string& read_filename) {
+ if (trace_to_file_) {
+ Stop();
+ }
+ std::string trace;
+ if (!read_filename.empty()) {
+ auto path = trace_event::ScopedTraceToFile::filepath_to_absolute(
+ base::FilePath(read_filename));
+ ReadFileToString(path, &trace);
+ } else if (!last_absolute_path_.empty()) {
+ ReadFileToString(last_absolute_path_, &trace);
+ }
+ return trace;
+}
+
+
} // namespace h5vcc
} // namespace cobalt
diff --git a/src/cobalt/h5vcc/h5vcc_trace_event.h b/src/cobalt/h5vcc/h5vcc_trace_event.h
index c1f2f8f..5239af7 100644
--- a/src/cobalt/h5vcc/h5vcc_trace_event.h
+++ b/src/cobalt/h5vcc/h5vcc_trace_event.h
@@ -51,6 +51,7 @@
void Start(const std::string& output_filename);
void Stop();
+ std::string Read(const std::string& output_filename);
TRACE_EVENT0_FOR_EACH(DEFINE_H5VCC_TRACE_EVENT0)
TRACE_EVENT1_FOR_EACH(DEFINE_H5VCC_TRACE_EVENT1)
@@ -62,6 +63,8 @@
// While initialized, it means that a trace is on-going.
std::unique_ptr<trace_event::ScopedTraceToFile> trace_to_file_;
+ base::FilePath last_absolute_path_;
+
DISALLOW_COPY_AND_ASSIGN(H5vccTraceEvent);
};
diff --git a/src/cobalt/h5vcc/h5vcc_trace_event.idl b/src/cobalt/h5vcc/h5vcc_trace_event.idl
index 9fca122..0b2bccf 100644
--- a/src/cobalt/h5vcc/h5vcc_trace_event.idl
+++ b/src/cobalt/h5vcc/h5vcc_trace_event.idl
@@ -19,6 +19,7 @@
// will be used.
void start(optional DOMString output_filename = "");
void stop();
+ DOMString read(optional DOMString read_filename = "");
void traceBegin(DOMString category, DOMString name);
void traceEnd(DOMString category, DOMString name);
diff --git a/src/cobalt/media/base/format_support_query_metrics.cc b/src/cobalt/media/base/format_support_query_metrics.cc
new file mode 100644
index 0000000..5d43621
--- /dev/null
+++ b/src/cobalt/media/base/format_support_query_metrics.cc
@@ -0,0 +1,113 @@
+// 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 "cobalt/media/base/format_support_query_metrics.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "starboard/common/string.h"
+
+namespace cobalt {
+namespace media {
+
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+
+// static
+SbTimeMonotonic FormatSupportQueryMetrics::cached_query_durations_[] = {0};
+char FormatSupportQueryMetrics::max_query_description_[] = {};
+SbTimeMonotonic FormatSupportQueryMetrics::max_query_duration_ = 0;
+SbTimeMonotonic FormatSupportQueryMetrics::total_query_duration_ = 0;
+int FormatSupportQueryMetrics::total_num_queries_ = 0;
+
+FormatSupportQueryMetrics::FormatSupportQueryMetrics() {
+ query_start_time_ = SbTimeGetMonotonicNow();
+}
+
+
+void FormatSupportQueryMetrics::RecordQuery(const char* query_name,
+ const std::string& mime_type,
+ const std::string& key_system,
+ SbMediaSupportType support_type) {
+ auto get_support_type_str = [](SbMediaSupportType support_type) {
+ switch (support_type) {
+ case kSbMediaSupportTypeNotSupported:
+ return " -> not supported/false";
+ case kSbMediaSupportTypeMaybe:
+ return " -> maybe/true";
+ case kSbMediaSupportTypeProbably:
+ return " -> probably/true";
+ default:
+ NOTREACHED();
+ return "";
+ }
+ };
+
+ SbTimeMonotonic duration = SbTimeGetMonotonicNow() - query_start_time_;
+ total_query_duration_ += duration;
+
+ std::string query_description = starboard::FormatString(
+ "%s(%s%s%s, %" PRId64 " μs", query_name, mime_type.c_str(),
+ (key_system.empty() ? ")" : ", " + key_system + ")").c_str(),
+ get_support_type_str(support_type), duration);
+ LOG(INFO) << query_description;
+
+ if (total_num_queries_ < SB_ARRAY_SIZE_INT(cached_query_durations_)) {
+ cached_query_durations_[total_num_queries_] = duration;
+ }
+ ++total_num_queries_;
+ if (duration > max_query_duration_) {
+ max_query_duration_ = duration;
+ base::strlcpy(max_query_description_, query_description.c_str(),
+ SB_ARRAY_SIZE_INT(max_query_description_));
+ }
+}
+
+// static
+void FormatSupportQueryMetrics::PrintAndResetFormatSupportQueryMetrics() {
+ if (total_num_queries_ == 0) {
+ LOG(INFO) << "Format support query metrics:\n\tNumber of queries: 0";
+ return;
+ }
+
+ auto get_median = []() {
+ int num_elements = std::min(total_num_queries_,
+ SB_ARRAY_SIZE_INT(cached_query_durations_));
+ int middle_index = num_elements / 2;
+ std::nth_element(cached_query_durations_,
+ cached_query_durations_ + middle_index,
+ cached_query_durations_ + num_elements);
+ auto middle_element = cached_query_durations_[middle_index];
+ return middle_element;
+ };
+
+ LOG(INFO) << "Format support query metrics:\n\tNumber of queries: "
+ << total_num_queries_
+ << "\n\tTotal query time: " << total_query_duration_
+ << " μs\n\tAverage query time: "
+ << total_query_duration_ / total_num_queries_
+ << " μs\n\tMedian query time: ~" << get_median()
+ << " μs\n\tLongest query: " << max_query_description_;
+
+ max_query_description_[0] = 0;
+ max_query_duration_ = 0;
+ total_query_duration_ = 0;
+ total_num_queries_ = 0;
+}
+
+#endif // !defined(COBALT_BUILD_TYPE_GOLD)
+
+} // namespace media
+} // namespace cobalt
diff --git a/src/cobalt/media/base/format_support_query_metrics.h b/src/cobalt/media/base/format_support_query_metrics.h
new file mode 100644
index 0000000..4678d6e
--- /dev/null
+++ b/src/cobalt/media/base/format_support_query_metrics.h
@@ -0,0 +1,69 @@
+// 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_MEDIA_BASE_FORMAT_SUPPORT_QUERY_METRICS_H_
+#define COBALT_MEDIA_BASE_FORMAT_SUPPORT_QUERY_METRICS_H_
+
+#include <string>
+
+#include "starboard/media.h"
+#include "starboard/time.h"
+
+namespace cobalt {
+namespace media {
+
+#if defined(COBALT_BUILD_TYPE_GOLD)
+
+class FormatSupportQueryMetrics {
+ public:
+ FormatSupportQueryMetrics() = default;
+ ~FormatSupportQueryMetrics() = default;
+ void RecordQuery(const char* query_name, const std::string& mime_type,
+ const std::string& key_system,
+ SbMediaSupportType support_type) {}
+ static void PrintAndResetFormatSupportQueryMetrics() {}
+};
+
+#else // defined(COBALT_BUILD_TYPE_GOLD)
+
+class FormatSupportQueryMetrics {
+ public:
+ FormatSupportQueryMetrics();
+ ~FormatSupportQueryMetrics() {}
+
+ void RecordQuery(const char* query_name, const std::string& mime_type,
+ const std::string& key_system,
+ SbMediaSupportType support_type);
+
+ static void PrintAndResetFormatSupportQueryMetrics();
+
+ private:
+ static constexpr int kMaxCachedQueryDurations = 150;
+ static constexpr int kMaxQueryDescriptionLength = 350;
+
+ static SbTimeMonotonic cached_query_durations_[kMaxCachedQueryDurations];
+ static char max_query_description_[kMaxQueryDescriptionLength];
+ static SbTimeMonotonic max_query_duration_;
+ static SbTimeMonotonic total_query_duration_;
+ static int total_num_queries_;
+
+ SbTimeMonotonic query_start_time_ = 0;
+};
+
+#endif // defined(COBALT_BUILD_TYPE_GOLD)
+
+} // namespace media
+} // namespace cobalt
+
+#endif // COBALT_MEDIA_BASE_FORMAT_SUPPORT_QUERY_METRICS_H_
diff --git a/src/cobalt/media/base/sbplayer_pipeline.cc b/src/cobalt/media/base/sbplayer_pipeline.cc
index 52e44a4..9530bfd 100644
--- a/src/cobalt/media/base/sbplayer_pipeline.cc
+++ b/src/cobalt/media/base/sbplayer_pipeline.cc
@@ -14,6 +14,7 @@
#include <algorithm>
#include <memory>
+#include <string>
#include <vector>
#include "base/basictypes.h" // For COMPILE_ASSERT
@@ -28,6 +29,7 @@
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/base/c_val.h"
+#include "cobalt/base/startup_timer.h"
#include "cobalt/math/size.h"
#include "cobalt/media/base/audio_decoder_config.h"
#include "cobalt/media/base/bind_to_current_loop.h"
@@ -46,6 +48,7 @@
#include "cobalt/media/base/video_decoder_config.h"
#include "starboard/common/string.h"
#include "starboard/configuration_constants.h"
+#include "starboard/time.h"
namespace cobalt {
namespace media {
@@ -193,6 +196,12 @@
// Retrieve the statistics as a string and append to message.
std::string AppendStatisticsString(const std::string& message) const;
+ // Get information on the time since app start and the time since the last
+ // playback resume.
+ std::string GetTimeInformation() const;
+
+ void RunSetDrmSystemReadyCB();
+
// An identifier string for the pipeline, used in CVal to identify multiple
// pipelines.
const std::string pipeline_identifier_;
@@ -317,6 +326,10 @@
PlaybackStatistics playback_statistics_;
+ SbTimeMonotonic last_resume_time_ = -1;
+
+ SbTimeMonotonic set_drm_system_ready_cb_time_ = -1;
+
DISALLOW_COPY_AND_ASSIGN(SbPlayerPipeline);
};
@@ -506,8 +519,7 @@
on_encrypted_media_init_data_encountered_cb);
set_drm_system_ready_cb_ = parameters.set_drm_system_ready_cb;
DCHECK(!set_drm_system_ready_cb_.is_null());
- set_drm_system_ready_cb_.Run(
- base::Bind(&SbPlayerPipeline::SetDrmSystem, this));
+ RunSetDrmSystemReadyCB();
task_runner_->PostTask(
FROM_HERE, base::Bind(&SbPlayerPipeline::StartTask, this, parameters));
@@ -933,10 +945,15 @@
return;
}
+ std::string time_information = GetTimeInformation();
+ LOG(INFO) << "SbPlayerPipeline::CreateUrlPlayer failed to create a valid "
+ "StarboardPlayer - "
+ << time_information << " \'" << error_message << "\'";
+
CallSeekCB(DECODER_ERROR_NOT_SUPPORTED,
"SbPlayerPipeline::CreateUrlPlayer failed to create a valid "
- "StarboardPlayer: \'" +
- error_message + "\'");
+ "StarboardPlayer - " +
+ time_information + " \'" + error_message + "\'");
}
void SbPlayerPipeline::SetDrmSystem(SbDrmSystem drm_system) {
@@ -949,6 +966,7 @@
}
if (player_->IsValid()) {
+ player_->RecordSetDrmSystemReadyTime(set_drm_system_ready_cb_time_);
player_->SetDrmSystem(drm_system);
}
}
@@ -1018,6 +1036,7 @@
}
if (player_ && player_->IsValid()) {
+ player_->RecordSetDrmSystemReadyTime(set_drm_system_ready_cb_time_);
base::Closure output_mode_change_cb;
{
base::AutoLock auto_lock(lock_);
@@ -1035,10 +1054,15 @@
return;
}
+ std::string time_information = GetTimeInformation();
+ LOG(INFO) << "SbPlayerPipeline::CreatePlayer failed to create a valid "
+ "StarboardPlayer - "
+ << time_information << " \'" << error_message << "\'";
+
CallSeekCB(DECODER_ERROR_NOT_SUPPORTED,
"SbPlayerPipeline::CreatePlayer failed to create a valid "
- "StarboardPlayer: \'" +
- error_message + "\'");
+ "StarboardPlayer - " +
+ time_information + " \'" + error_message + "\'");
}
void SbPlayerPipeline::OnDemuxerInitialized(PipelineStatus status) {
@@ -1098,8 +1122,7 @@
content_size_change_cb_.Run();
}
if (is_encrypted) {
- set_drm_system_ready_cb_.Run(
- BindToCurrentLoop(base::Bind(&SbPlayerPipeline::CreatePlayer, this)));
+ RunSetDrmSystemReadyCB();
return;
}
}
@@ -1432,6 +1455,7 @@
DCHECK(suspended_);
if (!suspended_) {
+ last_resume_time_ = SbTimeGetMonotonicNow();
done_event->Signal();
return;
}
@@ -1447,17 +1471,22 @@
error_message = player_->GetPlayerCreationErrorMessage();
player_.reset();
}
+ std::string time_information = GetTimeInformation();
+ LOG(INFO) << "SbPlayerPipeline::ResumeTask failed to create a valid "
+ "StarboardPlayer - "
+ << time_information << " \'" << error_message << "\'";
// TODO: Determine if CallSeekCB() may be used here, as |seek_cb_| may be
// available if the app is suspended before a seek is completed.
- CallErrorCB(DECODER_ERROR_NOT_SUPPORTED,
- "SbPlayerPipeline::ResumeTask failed to create a valid "
- "StarboardPlayer: \'" +
- error_message + "\'");
+ CallSeekCB(DECODER_ERROR_NOT_SUPPORTED,
+ "SbPlayerPipeline::ResumeTask failed to create a valid "
+ "StarboardPlayer - " +
+ time_information + " \'" + error_message + "\'");
return;
}
}
suspended_ = false;
+ last_resume_time_ = SbTimeGetMonotonicNow();
done_event->Signal();
}
@@ -1474,6 +1503,46 @@
}
}
+std::string SbPlayerPipeline::GetTimeInformation() const {
+ auto round_time_in_seconds = [](const SbTime time) {
+ const int64_t seconds = time / kSbTimeSecond;
+ if (seconds < 15) {
+ return seconds / 5 * 5;
+ }
+ if (seconds < 60) {
+ return seconds / 15 * 15;
+ }
+ if (seconds < 3600) {
+ return std::max(static_cast<SbTime>(60), seconds / 600 * 600);
+ }
+ return std::max(static_cast<SbTime>(3600), seconds / 18000 * 18000);
+ };
+ std::string time_since_start =
+ std::to_string(
+ round_time_in_seconds(base::StartupTimer::TimeElapsed().ToSbTime())) +
+ "s";
+ std::string time_since_resume =
+ last_resume_time_ != -1
+ ? std::to_string(round_time_in_seconds(SbTimeGetMonotonicNow() -
+ last_resume_time_)) +
+ "s"
+ : "null";
+ return "time since app start: " + time_since_start +
+ ", time since last resume: " + time_since_resume;
+}
+
+void SbPlayerPipeline::RunSetDrmSystemReadyCB() {
+ TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::RunSetDrmSystemReadyCB");
+ set_drm_system_ready_cb_time_ = SbTimeGetMonotonicNow();
+#if SB_HAS(PLAYER_WITH_URL)
+ set_drm_system_ready_cb_.Run(
+ base::Bind(&SbPlayerPipeline::SetDrmSystem, this));
+#else // SB_HAS(PLAYER_WITH_URL)
+ set_drm_system_ready_cb_.Run(
+ BindToCurrentLoop(base::Bind(&SbPlayerPipeline::CreatePlayer, this)));
+#endif // SB_HAS(PLAYER_WITH_URL)
+}
+
} // namespace
// static
diff --git a/src/cobalt/media/base/starboard_player.cc b/src/cobalt/media/base/starboard_player.cc
index 47ad52b..30f3a4c 100644
--- a/src/cobalt/media/base/starboard_player.cc
+++ b/src/cobalt/media/base/starboard_player.cc
@@ -14,6 +14,8 @@
#include "cobalt/media/base/starboard_player.h"
+#include <algorithm>
+#include <iomanip>
#include <string>
#include <vector>
@@ -22,8 +24,10 @@
#include "base/location.h"
#include "base/logging.h"
#include "base/trace_event/trace_event.h"
+#include "cobalt/media/base/format_support_query_metrics.h"
#include "cobalt/media/base/starboard_utils.h"
#include "starboard/common/media.h"
+#include "starboard/common/string.h"
#include "starboard/configuration.h"
#include "starboard/memory.h"
@@ -518,6 +522,13 @@
DCHECK(!on_encrypted_media_init_data_encountered_cb_.is_null());
LOG(INFO) << "CreateUrlPlayer passed url " << url;
+
+ if (max_video_capabilities_.empty()) {
+ FormatSupportQueryMetrics::PrintAndResetFormatSupportQueryMetrics();
+ }
+
+ player_creation_time_ = SbTimeGetMonotonicNow();
+
player_ =
SbUrlPlayerCreate(url.c_str(), window_, &StarboardPlayer::PlayerStatusCB,
&StarboardPlayer::EncryptedMediaInitDataEncounteredCB,
@@ -556,6 +567,12 @@
is_creating_player_ = true;
+ if (max_video_capabilities_.empty()) {
+ FormatSupportQueryMetrics::PrintAndResetFormatSupportQueryMetrics();
+ }
+
+ player_creation_time_ = SbTimeGetMonotonicNow();
+
#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
SbPlayerCreationParam creation_param = {};
@@ -653,6 +670,14 @@
}
auto sample_type = DemuxerStreamTypeToSbMediaType(type);
+
+ if (sample_type == kSbMediaTypeAudio && first_audio_sample_time_ == 0) {
+ first_audio_sample_time_ = SbTimeGetMonotonicNow();
+ } else if (sample_type == kSbMediaTypeVideo &&
+ first_video_sample_time_ == 0) {
+ first_video_sample_time_ = SbTimeGetMonotonicNow();
+ }
+
SbDrmSampleInfo drm_info;
SbDrmSubSampleMapping subsample_mapping;
drm_info.subsample_count = 0;
@@ -807,6 +832,8 @@
void StarboardPlayer::OnPlayerStatus(SbPlayer player, SbPlayerState state,
int ticket) {
+ TRACE_EVENT1("cobalt::media", "StarboardPlayer::OnPlayerStatus", "state",
+ state);
DCHECK(task_runner_->BelongsToCurrentThread());
if (player_ != player) {
@@ -823,11 +850,24 @@
if (ticket_ == SB_PLAYER_INITIAL_TICKET) {
++ticket_;
}
+ if (sb_player_state_initialized_time_ == 0) {
+ sb_player_state_initialized_time_ = SbTimeGetMonotonicNow();
+ }
SbPlayerSeek2(player_, preroll_timestamp_.InMicroseconds(), ticket_);
SetVolume(volume_);
SbPlayerSetPlaybackRate(player_, playback_rate_);
return;
}
+ if (state == kSbPlayerStatePrerolling &&
+ sb_player_state_prerolling_time_ == 0) {
+ sb_player_state_prerolling_time_ = SbTimeGetMonotonicNow();
+ } else if (state == kSbPlayerStatePresenting &&
+ sb_player_state_presenting_time_ == 0) {
+ sb_player_state_presenting_time_ = SbTimeGetMonotonicNow();
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+ LogStartupLatency();
+#endif // !defined(COBALT_BUILD_TYPE_GOLD)
+ }
host_->OnPlayerStatus(state);
}
@@ -984,5 +1024,48 @@
#endif // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
}
+void StarboardPlayer::LogStartupLatency() const {
+ std::string first_events_str;
+ if (set_drm_system_ready_cb_time_ == -1) {
+ first_events_str =
+ starboard::FormatString("%-50s0 us", "SbPlayerCreate() called");
+
+ } else if (set_drm_system_ready_cb_time_ < player_creation_time_) {
+ first_events_str = starboard::FormatString(
+ "%-50s0 us\n%-50s%" PRId64 " us", "set_drm_system_ready_cb called",
+ "SbPlayerCreate() called",
+ player_creation_time_ - set_drm_system_ready_cb_time_);
+ } else {
+ first_events_str = starboard::FormatString(
+ "%-50s0 us\n%-50s%" PRId64 " us", "SbPlayerCreate() called",
+ "set_drm_system_ready_cb called",
+ set_drm_system_ready_cb_time_ - player_creation_time_);
+ }
+
+ SbTime player_initialization_time_delta =
+ sb_player_state_initialized_time_ -
+ std::max(player_creation_time_, set_drm_system_ready_cb_time_);
+ SbTime player_preroll_time_delta =
+ sb_player_state_prerolling_time_ - sb_player_state_initialized_time_;
+ SbTime first_audio_sample_time_delta = std::max(
+ first_audio_sample_time_ - sb_player_state_prerolling_time_, SbTime(0));
+ SbTime first_video_sample_time_delta = std::max(
+ first_video_sample_time_ - sb_player_state_prerolling_time_, SbTime(0));
+ SbTime player_presenting_time_delta =
+ sb_player_state_presenting_time_ -
+ std::max(first_audio_sample_time_, first_video_sample_time_);
+
+ LOG(INFO) << starboard::FormatString(
+ "SbPlayer startup latencies\n%-50s%s\n%s\n%-50s%" PRId64
+ " us\n%-50s%" PRId64 " us\n%-50s%" PRId64 "/%" PRId64 " us\n%-50s%" PRId64
+ " us",
+ "Event name", "time since last event", first_events_str.c_str(),
+ "kSbPlayerStateInitialized received", player_initialization_time_delta,
+ "kSbPlayerStatePrerolling received", player_preroll_time_delta,
+ "First media sample(s) written [audio/video]",
+ first_audio_sample_time_delta, first_video_sample_time_delta,
+ "kSbPlayerStatePresenting received", player_presenting_time_delta);
+}
+
} // namespace media
} // namespace cobalt
diff --git a/src/cobalt/media/base/starboard_player.h b/src/cobalt/media/base/starboard_player.h
index 6e40ac4..7e3ffd8 100644
--- a/src/cobalt/media/base/starboard_player.h
+++ b/src/cobalt/media/base/starboard_player.h
@@ -130,6 +130,10 @@
SbDecodeTarget GetCurrentSbDecodeTarget();
SbPlayerOutputMode GetSbPlayerOutputMode();
+ void RecordSetDrmSystemReadyTime(SbTimeMonotonic timestamp) {
+ set_drm_system_ready_cb_time_ = timestamp;
+ }
+
private:
enum State {
kPlaying,
@@ -216,6 +220,8 @@
SbPlayerOutputMode ComputeSbPlayerOutputMode(
bool prefer_decode_to_texture) const;
+ void LogStartupLatency() const;
+
// The following variables are initialized in the ctor and never changed.
#if SB_HAS(PLAYER_WITH_URL)
std::string url_;
@@ -269,6 +275,15 @@
bool is_creating_player_ = false;
std::string player_creation_error_message_;
+ // Variables related to tracking player startup latencies.
+ SbTimeMonotonic set_drm_system_ready_cb_time_ = -1;
+ SbTimeMonotonic player_creation_time_ = 0;
+ SbTimeMonotonic sb_player_state_initialized_time_ = 0;
+ SbTimeMonotonic sb_player_state_prerolling_time_ = 0;
+ SbTimeMonotonic first_audio_sample_time_ = 0;
+ SbTimeMonotonic first_video_sample_time_ = 0;
+ SbTimeMonotonic sb_player_state_presenting_time_ = 0;
+
#if SB_HAS(PLAYER_WITH_URL)
const bool is_url_based_;
#endif // SB_HAS(PLAYER_WITH_URL)
diff --git a/src/cobalt/media/media.gyp b/src/cobalt/media/media.gyp
index 42567fa..4757b3e 100644
--- a/src/cobalt/media/media.gyp
+++ b/src/cobalt/media/media.gyp
@@ -68,6 +68,8 @@
"base/encryption_pattern.h",
'base/encryption_scheme.cc',
'base/encryption_scheme.h',
+ 'base/format_support_query_metrics.cc',
+ 'base/format_support_query_metrics.h',
'base/hdr_metadata.cc',
'base/hdr_metadata.h',
'base/interleaved_sinc_resampler.cc',
diff --git a/src/cobalt/media/media_module.cc b/src/cobalt/media/media_module.cc
index 8d4e195..43ad61b 100644
--- a/src/cobalt/media/media_module.cc
+++ b/src/cobalt/media/media_module.cc
@@ -24,6 +24,7 @@
#include "base/logging.h"
#include "base/strings/string_split.h"
#include "base/synchronization/waitable_event.h"
+#include "cobalt/media/base/format_support_query_metrics.h"
#include "cobalt/media/base/media_log.h"
#include "cobalt/media/base/mime_util.h"
#include "nb/memory_scope.h"
@@ -120,18 +121,27 @@
// video/webm; codecs="vp9"
// We do a rough pre-filter to ensure that only video/mp4 is supported as
// progressive.
+ SbMediaSupportType support_type;
+ media::FormatSupportQueryMetrics metrics;
if (strstr(mime_type.c_str(), "video/mp4") == 0 &&
strstr(mime_type.c_str(), "application/x-mpegURL") == 0) {
- return kSbMediaSupportTypeNotSupported;
+ support_type = kSbMediaSupportTypeNotSupported;
+ } else {
+ support_type = CanPlayType(mime_type, "");
}
-
- return CanPlayType(mime_type, "");
+ metrics.RecordQuery("HTMLMediaElement::canPlayType", mime_type, "",
+ support_type);
+ return support_type;
}
SbMediaSupportType CanPlayAdaptive(
const std::string& mime_type,
const std::string& key_system) const override {
- return CanPlayType(mime_type, key_system);
+ media::FormatSupportQueryMetrics metrics;
+ SbMediaSupportType support_type = CanPlayType(mime_type, key_system);
+ metrics.RecordQuery("MediaSource::IsTypeSupported", mime_type, key_system,
+ support_type);
+ return support_type;
}
private:
diff --git a/src/cobalt/render_tree/image.h b/src/cobalt/render_tree/image.h
index 357052f..668f4f0 100644
--- a/src/cobalt/render_tree/image.h
+++ b/src/cobalt/render_tree/image.h
@@ -137,6 +137,9 @@
// A YUV image where each channel, Y, U and V, is stored as a separate
// single-channel 10bit unnormalized image plane.
kMultiPlaneImageFormatYUV3Plane10BitBT2020,
+ // A compacted YUV image where Y, U and V, are stored in 32-bit and each
+ // 32-bit represents 3 10-bit pixels with 2 bits of gap.
+ kMultiPlaneImageFormatYUV3Plane10BitCompactedBT2020,
};
// Like the ImageDataDescriptor object, a MultiPlaneImageDataDescriptor
diff --git a/src/cobalt/renderer/backend/egl/graphics_context.cc b/src/cobalt/renderer/backend/egl/graphics_context.cc
index ebc2d9d..480ac6d 100644
--- a/src/cobalt/renderer/backend/egl/graphics_context.cc
+++ b/src/cobalt/renderer/backend/egl/graphics_context.cc
@@ -17,6 +17,7 @@
#include <algorithm>
#include <memory>
+#include <utility>
#include "cobalt/renderer/backend/egl/graphics_context.h"
@@ -96,6 +97,10 @@
return base::polymorphic_downcast<GraphicsSystemEGL*>(system());
}
+const GraphicsSystemEGL* GraphicsContextEGL::system_egl() const {
+ return base::polymorphic_downcast<GraphicsSystemEGL*>(system());
+}
+
bool GraphicsContextEGL::ComputeReadPixelsNeedVerticalFlip() {
// This computation is expensive, so it is cached the first time that it is
// computed. Simply return the value if it is already cached.
@@ -204,10 +209,10 @@
GL_CALL(glCompileShader(blit_fragment_shader_));
GL_CALL(glAttachShader(blit_program_, blit_fragment_shader_));
- GL_CALL(glBindAttribLocation(
- blit_program_, kBlitPositionAttribute, "a_position"));
- GL_CALL(glBindAttribLocation(
- blit_program_, kBlitTexcoordAttribute, "a_tex_coord"));
+ GL_CALL(glBindAttribLocation(blit_program_, kBlitPositionAttribute,
+ "a_position"));
+ GL_CALL(glBindAttribLocation(blit_program_, kBlitTexcoordAttribute,
+ "a_tex_coord"));
GL_CALL(glLinkProgram(blit_program_));
@@ -286,8 +291,7 @@
}
void GraphicsContextEGL::MakeCurrentWithSurface(RenderTargetEGL* surface) {
- DCHECK_NE(EGL_NO_SURFACE, surface) <<
- "Use ReleaseCurrentContext().";
+ DCHECK_NE(EGL_NO_SURFACE, surface) << "Use ReleaseCurrentContext().";
// In some EGL implementations, like Angle, the first time we make current on
// a thread can result in global allocations being made that are never freed.
@@ -353,8 +357,8 @@
void GraphicsContextEGL::ReleaseCurrentContext() {
GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
- EGL_CALL(eglMakeCurrent(
- display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+ EGL_CALL(
+ eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
current_surface_ = NULL;
is_current_ = false;
@@ -380,8 +384,8 @@
scoped_refptr<RenderTarget> GraphicsContextEGL::CreateOffscreenRenderTarget(
const math::Size& dimensions) {
- scoped_refptr<RenderTarget> render_target(new PBufferRenderTargetEGL(
- display_, config_, dimensions));
+ scoped_refptr<RenderTarget> render_target(
+ new PBufferRenderTargetEGL(display_, config_, dimensions));
if (render_target->CreationError()) {
return scoped_refptr<RenderTarget>();
@@ -412,8 +416,7 @@
int half_height = height / 2;
for (int row = 0; row < half_height; ++row) {
uint8_t* top_row = pixels + row * pitch_in_bytes;
- uint8_t* bottom_row =
- pixels + (height - 1 - row) * pitch_in_bytes;
+ uint8_t* bottom_row = pixels + (height - 1 - row) * pitch_in_bytes;
for (int i = 0; i < pitch_in_bytes; ++i) {
std::swap(top_row[i], bottom_row[i]);
}
@@ -496,6 +499,10 @@
GL_CALL(glFinish());
}
+math::Size GraphicsContextEGL::GetWindowSize() const {
+ return system_egl()->GetWindowSize();
+}
+
void GraphicsContextEGL::Blit(GLuint texture, int x, int y, int width,
int height) {
// Render a texture to the specified output rectangle on the render target.
diff --git a/src/cobalt/renderer/backend/egl/graphics_context.h b/src/cobalt/renderer/backend/egl/graphics_context.h
index b19bb91..232c9ba 100644
--- a/src/cobalt/renderer/backend/egl/graphics_context.h
+++ b/src/cobalt/renderer/backend/egl/graphics_context.h
@@ -42,6 +42,7 @@
~GraphicsContextEGL() override;
GraphicsSystemEGL* system_egl();
+ const GraphicsSystemEGL* system_egl() const;
EGLContext GetContext() { return context_; }
@@ -66,6 +67,8 @@
void Finish() override;
+ math::Size GetWindowSize() const;
+
// Helper class to allow one to create a RAII object that will acquire the
// current context upon construction and release it upon destruction.
class ScopedMakeCurrent {
diff --git a/src/cobalt/renderer/backend/egl/graphics_system.cc b/src/cobalt/renderer/backend/egl/graphics_system.cc
index 8418244..d0148b9 100644
--- a/src/cobalt/renderer/backend/egl/graphics_system.cc
+++ b/src/cobalt/renderer/backend/egl/graphics_system.cc
@@ -283,6 +283,10 @@
#endif
}
+math::Size GraphicsSystemEGL::GetWindowSize() const {
+ return system_window_ ? system_window_->GetWindowSize() : math::Size();
+}
+
} // namespace backend
} // namespace renderer
} // namespace cobalt
diff --git a/src/cobalt/renderer/backend/egl/graphics_system.h b/src/cobalt/renderer/backend/egl/graphics_system.h
index 3ed462d..4920900 100644
--- a/src/cobalt/renderer/backend/egl/graphics_system.h
+++ b/src/cobalt/renderer/backend/egl/graphics_system.h
@@ -15,6 +15,8 @@
#ifndef COBALT_RENDERER_BACKEND_EGL_GRAPHICS_SYSTEM_H_
#define COBALT_RENDERER_BACKEND_EGL_GRAPHICS_SYSTEM_H_
+#include <memory>
+
#include "base/optional.h"
#include "cobalt/renderer/backend/egl/resource_context.h"
#include "cobalt/renderer/backend/egl/texture_data.h"
@@ -45,6 +47,8 @@
std::unique_ptr<RawTextureMemoryEGL> AllocateRawTextureMemory(
size_t size_in_bytes, size_t alignment);
+ math::Size GetWindowSize() const;
+
private:
EGLDisplay display_;
EGLConfig config_;
diff --git a/src/cobalt/renderer/backend/graphics_context.cc b/src/cobalt/renderer/backend/graphics_context.cc
index 1d3d0f6..3455980 100644
--- a/src/cobalt/renderer/backend/graphics_context.cc
+++ b/src/cobalt/renderer/backend/graphics_context.cc
@@ -63,11 +63,10 @@
graphics_context ? graphics_context->graphics_extension_ : nullptr;
#if SB_API_VERSION >= 12
#if defined(ENABLE_MAP_TO_MESH)
-#error \
- "ENABLE_MAP_TO_MESH is deprecated after Starboard version 12, use \
-the Cobalt graphics extension function IsMapToMeshEnabled() instead."
+#error "ENABLE_MAP_TO_MESH is deprecated after Starboard version 12, use "
+ "the Cobalt graphics extension function IsMapToMeshEnabled() instead."
#endif // defined(ENABLE_MAP_TO_MESH)
- if (graphics_ext && graphics_ext->version >= 3) {
+ if (graphics_ext && graphics_ext->version >= 3) {
return graphics_ext->IsMapToMeshEnabled();
}
diff --git a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
index 076aaf8..a1d3f9b 100644
--- a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
+++ b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
@@ -100,6 +100,10 @@
64 * 1.1678f, 64 * 2.1479f, 0.0f, -1.1469f,
0.0f, 0.0f, 0.0f, 1.0f};
+const float k10BitBT2020ColorMatrixCompacted[16] = {
+ 1.1678f, 0.0f, 1.6835f, -0.9147f, 1.1678f, -0.1878f, -0.6522f, 0.347f,
+ 1.1678f, 2.1479f, 0.0f, -1.1469f, 0.0f, 0.0f, 0.0f, 1.0f};
+
const float* GetColorMatrixForImageType(
TexturedMeshRenderer::Image::Type type) {
switch (type) {
@@ -115,6 +119,9 @@
case TexturedMeshRenderer::Image::YUV_UYVY_422_BT709: {
return kBT709ColorMatrix;
} break;
+ case TexturedMeshRenderer::Image::YUV_3PLANE_10BIT_COMPACT_BT2020: {
+ return k10BitBT2020ColorMatrixCompacted;
+ } break;
case TexturedMeshRenderer::Image::YUV_3PLANE_10BIT_BT2020: {
return k10BitBT2020ColorMatrix;
} break;
@@ -177,6 +184,25 @@
scale_translate_vector));
}
+ if (image.type == Image::YUV_3PLANE_10BIT_COMPACT_BT2020) {
+ math::Size viewport_size = graphics_context_->GetWindowSize();
+ GL_CALL(glUniform2f(blit_program.viewport_size_uniform,
+ viewport_size.width(), viewport_size.height()));
+
+ SB_DCHECK(image.num_textures() >= 1);
+ math::Size texture_size = image.textures[0].texture->GetSize();
+ GL_CALL(glUniform2f(
+ blit_program.viewport_to_texture_size_ratio_uniform,
+ viewport_size.width() / static_cast<float>(texture_size.width()),
+ viewport_size.height() / static_cast<float>(texture_size.height())));
+
+ // The pixel shader requires the actual frame size of the first plane
+ // for calculations.
+ size_t subtexture_width = (texture_size.width() + 2) / 3;
+ GL_CALL(glUniform2f(blit_program.subtexture_size_uniform, subtexture_width,
+ texture_size.height()));
+ }
+
GL_CALL(glDrawArrays(mode, 0, num_vertices));
for (int i = 0; i < image.num_textures(); ++i) {
@@ -472,6 +498,71 @@
return CompileShader(blit_fragment_shader_source);
}
+uint32 TexturedMeshRenderer::CreateYUVCompactedTexturesFragmentShader(
+ uint32 texture_target) {
+ SamplerInfo sampler_info = GetSamplerInfo(texture_target);
+
+ std::string blit_fragment_shader_source = sampler_info.preamble;
+
+ // The fragment shader below performs YUV->RGB transform for
+ // compacted 10-bit yuv420 textures
+ blit_fragment_shader_source +=
+ "precision mediump float;"
+ "varying vec2 v_tex_coord_y;"
+ "varying vec2 v_tex_coord_u;"
+ "varying vec2 v_tex_coord_v;"
+ "uniform sampler2D texture_y;"
+ "uniform sampler2D texture_u;"
+ "uniform sampler2D texture_v;"
+ "uniform vec2 viewport_size;"
+ "uniform vec2 viewport_to_texture_ratio;"
+ "uniform vec2 texture_size;"
+ "uniform mat4 to_rgb_color_matrix;"
+ // In a compacted YUV image each channel, Y, U and V, is stored as a
+ // separate single-channel image plane. Its pixels are stored in 32-bit
+ // and each represents 3 10-bit pixels with 2 bits of gap.
+ // In order to extract compacted pixels, the horizontal position at the
+ // compacted texture is calculated as:
+ // index = compacted_position % 3;
+ // decompacted_position = compacted_position / 3;
+ // pixel = CompactedTexture.Sample(Sampler, decompacted_position)[index];
+ "void main() {"
+ // Take into account display size to texture size ratio for Y component.
+ "vec2 DTid = vec2(floor(v_tex_coord_y * viewport_size));"
+ "vec2 compact_pos_Y = vec2((DTid + 0.5) / viewport_to_texture_ratio);"
+ // Calculate the position of 10-bit pixel for Y.
+ "vec2 decompact_pos_Y;"
+ "decompact_pos_Y.x = (floor(compact_pos_Y.x / 3.0) + 0.5) / "
+ "texture_size.x;"
+ "decompact_pos_Y.y = compact_pos_Y.y / texture_size.y;"
+ // Calculate the index of 10-bit pixel for Y.
+ "int index_Y = int(mod(floor(compact_pos_Y.x), 3.0));"
+ // Extract Y component.
+ "float Y_component = texture2D(texture_y, decompact_pos_Y)[index_Y];"
+ // For yuv420 U and V textures have dimensions twice less than Y.
+ "DTid = vec2(DTid / 2.0);"
+ "vec2 texture_size_UV = vec2(texture_size / 2.0);"
+ "vec2 compact_pos_UV = vec2((DTid + 0.5) / viewport_to_texture_ratio);"
+ // Calculate the position of 10-bit pixels for U and V.
+ "vec2 decompact_pos_UV;"
+ "decompact_pos_UV.x = (floor(compact_pos_UV.x / 3.0) + 0.5) / "
+ "texture_size_UV.x;"
+ "decompact_pos_UV.y = (floor(compact_pos_UV.y) + 0.5) / "
+ "texture_size_UV.y;"
+ // Calculate the index of 10-bit pixels for U and V.
+ "int index_UV = int(mod(floor(compact_pos_UV), 3.0));"
+ // Extract U and V components.
+ "float U_component = texture2D(texture_u, decompact_pos_UV)[index_UV];"
+ "float V_component = texture2D(texture_v, decompact_pos_UV)[index_UV];"
+ // Perform the YUV->RGB transform and output the color value.
+ "vec4 untransformed_color = vec4(Y_component, U_component, V_component, "
+ "1.0);"
+ "gl_FragColor = untransformed_color * to_rgb_color_matrix;"
+ "}";
+
+ return CompileShader(blit_fragment_shader_source);
+}
+
// static
TexturedMeshRenderer::ProgramInfo TexturedMeshRenderer::MakeBlitProgram(
const float* color_matrix, const std::vector<TextureInfo>& textures,
@@ -530,7 +621,20 @@
glGetUniformLocation(result.gl_program_id, "to_rgb_color_matrix"));
GL_CALL(glUniformMatrix4fv(to_rgb_color_matrix_uniform, 1, GL_FALSE,
color_matrix));
+ if (color_matrix == k10BitBT2020ColorMatrixCompacted) {
+ result.viewport_size_uniform = GL_CALL_SIMPLE(
+ glGetUniformLocation(result.gl_program_id, "viewport_size"));
+ DCHECK_EQ(GL_NO_ERROR, GL_CALL_SIMPLE(glGetError()));
+ result.viewport_to_texture_size_ratio_uniform =
+ GL_CALL_SIMPLE(glGetUniformLocation(result.gl_program_id,
+ "viewport_to_texture_ratio"));
+ DCHECK_EQ(GL_NO_ERROR, GL_CALL_SIMPLE(glGetError()));
+ result.subtexture_size_uniform = GL_CALL_SIMPLE(
+ glGetUniformLocation(result.gl_program_id, "texture_size"));
+ DCHECK_EQ(GL_NO_ERROR, GL_CALL_SIMPLE(glGetError()));
+ }
}
+
GL_CALL(glUseProgram(0));
GL_CALL(glDeleteShader(blit_fragment_shader));
@@ -599,7 +703,8 @@
} break;
case Image::YUV_3PLANE_BT601_FULL_RANGE:
case Image::YUV_3PLANE_BT709:
- case Image::YUV_3PLANE_10BIT_BT2020: {
+ case Image::YUV_3PLANE_10BIT_BT2020:
+ case Image::YUV_3PLANE_10BIT_COMPACT_BT2020: {
std::vector<TextureInfo> texture_infos;
#if defined(GL_RED_EXT)
if (image.textures[0].texture->GetFormat() == GL_RED_EXT) {
@@ -622,9 +727,15 @@
texture_infos.push_back(TextureInfo("u", "a"));
texture_infos.push_back(TextureInfo("v", "a"));
#endif // defined(GL_RED_EXT)
- result = MakeBlitProgram(
- color_matrix, texture_infos,
- CreateFragmentShader(texture_target, texture_infos, color_matrix));
+ uint32 shader_program;
+ if (type == Image::YUV_3PLANE_10BIT_COMPACT_BT2020) {
+ shader_program =
+ CreateYUVCompactedTexturesFragmentShader(texture_target);
+ } else {
+ shader_program =
+ CreateFragmentShader(texture_target, texture_infos, color_matrix);
+ }
+ result = MakeBlitProgram(color_matrix, texture_infos, shader_program);
} break;
case Image::YUV_UYVY_422_BT709: {
std::vector<TextureInfo> texture_infos;
diff --git a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
index e607bd7..2faac78 100644
--- a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
+++ b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
@@ -59,6 +59,10 @@
// YUV BT2020 image where the Y, U and V components are all on different
// 10bit unnormalized textures.
YUV_3PLANE_10BIT_BT2020,
+ // YUV BT2020 image where the Y, U and V components are all on different
+ // textures. Its pixels are stored in 32 bits, and each pixel represents
+ // three 10-bit pixels with two bits of gap.
+ YUV_3PLANE_10BIT_COMPACT_BT2020,
// 1 texture is used that contains RGBA pixels.
RGBA,
// 1 texture plane is used where Y is sampled twice for each UV sample
@@ -79,6 +83,7 @@
case YUV_3PLANE_BT601_FULL_RANGE:
case YUV_3PLANE_BT709:
case YUV_3PLANE_10BIT_BT2020:
+ case YUV_3PLANE_10BIT_COMPACT_BT2020:
return 3;
case RGBA:
case YUV_UYVY_422_BT709:
@@ -121,6 +126,9 @@
int32 texture_uniforms[3];
int32 texture_size_uniforms[3];
uint32 gl_program_id;
+ int32 viewport_size_uniform;
+ int32 viewport_to_texture_size_ratio_uniform;
+ int32 subtexture_size_uniform;
};
// We key each program off of their GL texture type and image type.
typedef std::tuple<uint32, Image::Type, base::Optional<int32> > CacheKey;
@@ -142,6 +150,9 @@
// of applying bilinear filtering within a texel between the two Y values.
static uint32 CreateUYVYFragmentShader(uint32 texture_target,
int32 texture_wrap_s);
+ // YUV compacted textures need a special fragment shader to handle compacted
+ // pixels. Compacted textures support YUV420 only.
+ static uint32 CreateYUVCompactedTexturesFragmentShader(uint32 texture_target);
backend::GraphicsContextEGL* graphics_context_;
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
index 3ec33fe..7070c83 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
@@ -328,6 +328,18 @@
return result;
}
+inline void SetImageTextures(egl::TexturedMeshRenderer::Image& result,
+ unsigned int textures_num,
+ const math::Matrix3F& local_transform,
+ HardwareMultiPlaneImage* hardware_image,
+ render_tree::StereoMode stereo_mode) {
+ for (auto i = 0; i < textures_num; i++) {
+ result.textures[i] = GetTextureFromHardwareFrontendImage(
+ local_transform, hardware_image->GetHardwareFrontendImage(i).get(),
+ stereo_mode);
+ }
+}
+
egl::TexturedMeshRenderer::Image SkiaImageToTexturedMeshRendererImage(
const math::Matrix3F& local_transform, Image* image,
render_tree::StereoMode stereo_mode) {
@@ -357,48 +369,25 @@
render_tree::kMultiPlaneImageFormatYUV3PlaneBT601FullRange) {
result.type =
egl::TexturedMeshRenderer::Image::YUV_3PLANE_BT601_FULL_RANGE;
- result.textures[0] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(0).get(),
- stereo_mode);
- result.textures[1] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(1).get(),
- stereo_mode);
- result.textures[2] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(2).get(),
- stereo_mode);
+ SetImageTextures(result, 3, local_transform, hardware_image, stereo_mode);
} else if (hardware_image->GetFormat() ==
render_tree::kMultiPlaneImageFormatYUV2PlaneBT709) {
result.type = egl::TexturedMeshRenderer::Image::YUV_2PLANE_BT709;
- result.textures[0] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(0).get(),
- stereo_mode);
- result.textures[1] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(1).get(),
- stereo_mode);
+ SetImageTextures(result, 2, local_transform, hardware_image, stereo_mode);
} else if (hardware_image->GetFormat() ==
render_tree::kMultiPlaneImageFormatYUV3PlaneBT709) {
result.type = egl::TexturedMeshRenderer::Image::YUV_3PLANE_BT709;
- result.textures[0] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(0).get(),
- stereo_mode);
- result.textures[1] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(1).get(),
- stereo_mode);
- result.textures[2] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(2).get(),
- stereo_mode);
+ SetImageTextures(result, 3, local_transform, hardware_image, stereo_mode);
} else if (hardware_image->GetFormat() ==
render_tree::kMultiPlaneImageFormatYUV3Plane10BitBT2020) {
result.type = egl::TexturedMeshRenderer::Image::YUV_3PLANE_10BIT_BT2020;
- result.textures[0] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(0).get(),
- stereo_mode);
- result.textures[1] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(1).get(),
- stereo_mode);
- result.textures[2] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(2).get(),
- stereo_mode);
+ SetImageTextures(result, 3, local_transform, hardware_image, stereo_mode);
+ } else if (hardware_image->GetFormat() ==
+ render_tree::
+ kMultiPlaneImageFormatYUV3Plane10BitCompactedBT2020) {
+ result.type =
+ egl::TexturedMeshRenderer::Image::YUV_3PLANE_10BIT_COMPACT_BT2020;
+ SetImageTextures(result, 3, local_transform, hardware_image, stereo_mode);
}
} else {
NOTREACHED();
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
index f3295a7..b12b510 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
@@ -19,6 +19,7 @@
#include "cobalt/renderer/rasterizer/skia/hardware_resource_provider.h"
#include <memory>
+#include <utility>
#include "base/synchronization/waitable_event.h"
#include "base/trace_event/trace_event.h"
@@ -205,6 +206,9 @@
}
} break;
case kSbDecodeTargetFormat3Plane10BitYUVI420:
+#if SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+ case kSbDecodeTargetFormat3Plane10BitYUVI420Compact:
+#endif // SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
case kSbDecodeTargetFormat3PlaneYUVI420: {
DCHECK_LT(plane, 3);
#if defined(GL_RED_EXT)
@@ -233,6 +237,11 @@
case kSbDecodeTargetFormat3Plane10BitYUVI420: {
return render_tree::kMultiPlaneImageFormatYUV3Plane10BitBT2020;
} break;
+#if SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+ case kSbDecodeTargetFormat3Plane10BitYUVI420Compact: {
+ return render_tree::kMultiPlaneImageFormatYUV3Plane10BitCompactedBT2020;
+ }
+#endif // SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
default: { NOTREACHED(); }
}
return render_tree::kMultiPlaneImageFormatYUV2PlaneBT709;
diff --git a/src/cobalt/site/docs/development/setup-android.md b/src/cobalt/site/docs/development/setup-android.md
index b2ddb55..621f5f3 100644
--- a/src/cobalt/site/docs/development/setup-android.md
+++ b/src/cobalt/site/docs/development/setup-android.md
@@ -9,7 +9,7 @@
## Preliminary Setup
<aside class="note">
-<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section **Set up your workstation**, then return and complete the following steps.
+<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section <b>Set up your workstation</b>, then return and complete the following steps.
</aside>
1. Additional build dependencies may need to be installed:
diff --git a/src/cobalt/site/docs/development/setup-docker.md b/src/cobalt/site/docs/development/setup-docker.md
new file mode 100644
index 0000000..afc7065
--- /dev/null
+++ b/src/cobalt/site/docs/development/setup-docker.md
@@ -0,0 +1,93 @@
+---
+layout: doc
+title: "Set up your environment - Docker"
+---
+
+We provide <a
+href="https://cobalt.googlesource.com/cobalt/+/refs/heads/22.lts.stable/src/docker/linux/">Docker image definitions</a> to simplify managing build environments.
+
+The instructions below assume Docker is installed and is able to run basic
+hello-world verification. `docker-compose` command is expected to be available as well.
+
+## Set up your workstation
+
+Clone the Cobalt code repository. The following `git` command creates a
+`cobalt` directory that contains the repository:
+
+```
+$ git clone https://cobalt.googlesource.com/cobalt
+$ cd cobalt
+```
+
+### Usage
+
+The simplest usage is:
+
+```
+docker-compose run <platform>
+```
+
+By default, a `debug` build will be built, with `cobalt` as a target.
+You can override this with an environment variable, e.g.
+
+```
+docker-compose run -e CONFIG=devel -e TARGET=nplb <platform>
+```
+
+where config is one of the four optimization levels, `debug`, `devel`,
+`qa` and `gold`, and target is the build target passed to ninja
+
+See <a
+href="https://cobalt.googlesource.com/cobalt/+/refs/heads/22.lts.stable/src/README.md#build-types">Cobalt README</a> for full details.
+
+Builds will be available in your `${COBALT_SRC}/out` directory.
+
+<aside class="note">
+Note that Docker runs processes as root user by default, hence
+output files in `src/out/<platform>` directory have `root` as file owner.
+</aside>
+
+### Customization
+
+To parametrize base operating system images used for the build, pass
+`BASE_OS` as an argument to `docker-compose` as follows:
+
+```
+docker-compose build --build-arg BASE_OS="ubuntu:bionic" base
+```
+
+This parameter is defined in `docker/linux/base/Dockerfile` and is passed
+to Docker `FROM ...` statement.
+
+Available parameters for customizing container execution are:
+
+**BASE_OS**: passed to `base` image at build time to select a Debian-based
+ base os image and version. Defaults to Debian 10. `ubuntu:bionic` and
+ `ubuntu:xenial` are other tested examples.
+
+**PLATFORM**: Cobalt build platform, passed to GYP
+
+**CONFIG**: Cobalt build config, passed to GYP. Defaults to `debug`
+
+**TARGET**: Build target, passed to `ninja`
+
+The `docker-compose.yml` contains the currently defined experimental build
+configurations. Edit or add new `service` entries as needed, to build custom
+configurations.
+
+
+### Pre-built images
+
+Note: Pre-built images from a public container registry are not yet available.
+
+### Troubleshooting
+
+To debug build issues, enter the shell of the corresponding build container
+by launching the bash shell, i.e.
+
+```
+docker-compose run linux-x64x11 /bin/bash
+```
+
+and try to build Cobalt with the <a
+href="https://cobalt.googlesource.com/cobalt/+/refs/heads/22.lts.stable/src/README.md#building-and-running-the-code">usual gyp / ninja flow.</a>
diff --git a/src/cobalt/site/docs/development/setup-linux.md b/src/cobalt/site/docs/development/setup-linux.md
index 288711b..21d07e5 100644
--- a/src/cobalt/site/docs/development/setup-linux.md
+++ b/src/cobalt/site/docs/development/setup-linux.md
@@ -9,16 +9,20 @@
on the machine that you are using to view the client. For example, you cannot
SSH into another machine and run the binary on that machine.
+These instructions were tested on a fresh ubuntu:20.04 Docker image. (1/12/22)
+Required libraries can differ depending on your Linux distribution and version.
+
## Set up your workstation
1. Run the following command to install packages needed to build and run
Cobalt on Linux:
```
- $ sudo apt install -qqy --no-install-recommends pkgconf ninja-build \
- bison yasm binutils clang libgles2-mesa-dev mesa-common-dev \
- libpulse-dev libavresample-dev libasound2-dev libxrender-dev \
- libxcomposite-dev
+ $ sudo apt update && sudo apt install -qqy --no-install-recommends \
+ pkgconf ninja-build bison yasm binutils clang libgles2-mesa-dev \
+ mesa-common-dev libpulse-dev libavresample-dev libasound2-dev \
+ libxrender-dev libxcomposite-dev libxml2-dev curl git \
+ python3.8-venv python2
```
1. Install Node.js via `nvm`:
@@ -48,6 +52,14 @@
$ ccache --max-size=20G
```
+1. Install necessary python2 packages for GYP. Until Cobalt 23, when we have
+ migrated our build system to GN, we still require some python2 packages:
+
+ ```
+ $ curl https://bootstrap.pypa.io/pip/2.7/get-pip.py | python2
+ $ python2 -m pip install --user requests selenium six
+ ```
+
1. Clone the Cobalt code repository. The following `git` command creates a
`cobalt` directory that contains the repository:
@@ -63,17 +75,17 @@
$ cd cobalt
```
-<aside class="note">
-<b>Note:</b> Pre-commit is only available on branches later than 22.lts.1+,
-including trunk. The below commands will fail on 22.lts.1+ and earlier branches.
-For earlier branches, run `cd src` and move on to the next section.
-</aside>
+ <aside class="note">
+ <b>Note:</b> Pre-commit is only available on branches later than 22.lts.1+,
+ including trunk. The below commands will fail on 22.lts.1+ and earlier branches.
+ For earlier branches, run `cd src` and move on to the next section.
+ </aside>
1. Create a Python 3 virtual environment for working on Cobalt (feel free to use `virtualenvwrapper` instead):
```
- $ python -m venv ~/.virtualenvs/cobalt_dev
- $ source ~/.virtualenvs/cobalt_dev
+ $ python3 -m venv ~/.virtualenvs/cobalt_dev
+ $ source ~/.virtualenvs/cobalt_dev/bin/activate
$ pip install -r requirements.txt
```
diff --git a/src/cobalt/site/docs/development/setup-raspi.md b/src/cobalt/site/docs/development/setup-raspi.md
index 48ca4cc..8badc0a 100644
--- a/src/cobalt/site/docs/development/setup-raspi.md
+++ b/src/cobalt/site/docs/development/setup-raspi.md
@@ -4,57 +4,42 @@
---
These instructions explain how to set up Cobalt for your workstation and your
-Raspberry Pi device.
+Raspberry Pi device. They have been tested with Ubuntu:20.04 and a Raspberry Pi
+3 Model B.
## Set up your device
-<aside class="note">
-<b>Note:</b> Raspberry Pi <em>cannot</em> have MesaGL installed and will return
-an error, like `DRI2 not supported` or `DRI2 failed to authenticate` if MesaGL
-is installed.
-</aside>
+Download the latest Cobalt customized Raspbian image from <a
+href="https://storage.googleapis.com/cobalt-static-storage/2020-02-13-raspbian-buster-lite_shrunk_20210427.img">GCS bucket</a>
+(this is built via <a
+href="https://github.com/youtube/cobalt/tree/master/tools/raspi_image#readme">this
+customization tool</a>)
-<aside class="note">
-<b>Note:</b> The current builds of Cobalt currently are verified to work only
-with a fairly old version of Raspbian Lite from 2017-07-05 (
-<a href="http://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2017-07-05/2017-07-05-raspbian-jessie-lite.zip">download link</a>
-). If you have a newer version, you may encounter linker errors when building
-Cobalt as the sysroot system libraries will differ in the latest version of
-Raspbian.
-</aside>
+On MacOS, use an image flashing tool like <a href="https://www.balena.io/etcher/">balenaEtcher</a> to write the image to a 32GB SD-card.
-Configure the Raspberry Pi memory split.
+On Linux, follow the steps below.
-1. `sudo raspi-config`
-1. Go to Advanced
-1. Memory Split: 256 for RasPi-0, 512 for all others.
-
-Cobalt assumes the Raspberry Pi is configured to use non-default thread
-schedulers and priorities. Ensure that **/etc/security/limits.conf** sets
-**rtprio** and **nice** limits for the user. For example, if the user is **pi**,
-then limits.conf should have the following lines:
+Check the location of your SD card (/dev/sdX or /dev/mmcblkX)
```
-@pi hard rtprio 99
-@pi soft rtprio 99
-@pi hard nice -20
-@pi soft nice -20
+$ sudo fdisk -l
```
+Make sure the card isn't mounted ( `umount /dev/sdX` ).
-The following commands update the package configuration on your Raspberry Pi
-so that Cobalt can run properly:
+Copy the downloaded image to your SD card (the disk, not the partition. E.g. /dev/sdX or /dev/mmcblkX):
```
-$ apt-get remove -y --purge --auto-remove libgl1-mesa-dev \
- libegl1-mesa-dev libgles2-mesa libgles2-mesa-dev
-$ apt-get install -y libpulse-dev libasound2-dev libavformat-dev \
- libavresample-dev rsync
+$ sudo dd bs=4M if=2020-02-13-raspbian-buster-lite_shrunk_20210427.img of=/dev/sdX
```
+After flashing your device, you'll still need to setup your wifi. Login with the
+default pi login, and run `sudo raspi-config`. You'll find wifi settings under
+`1. System Options`, then `S1 Wireless LAN`.
+
## Set up your workstation
<aside class="note">
-<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section **Set up your workstation**, then return and complete the following steps.
+<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section <b>Set up your workstation</b>, then return and complete the following steps.
</aside>
The following steps install the cross-compiling toolchain on your workstation.
@@ -66,7 +51,8 @@
```
$ sudo apt install -qqy --no-install-recommends g++-multilib \
- python-requests wget xz-utils
+ wget xz-utils libxml2 binutils-aarch64-linux-gnu \
+ binutils-arm-linux-gnueabi libglib2.0-dev
```
1. Choose a location for the installed toolchain – e.g. `raspi-tools`
@@ -77,32 +63,19 @@
1. Create the directory for the installed toolchain and go to it:
```
- mkdir -p $RASPI_HOME
- cd $RASPI_HOME
+ $ mkdir -p $RASPI_HOME
+ $ cd $RASPI_HOME
```
-1. Clone the GitHub repository for Raspberry Pi tools:
+1. Download the pre-packaged toolchain and extract it in `$RASPI_HOME`.
```
- git clone git://github.com/raspberrypi/tools.git
+ $ curl -O https://storage.googleapis.com/cobalt-static-storage/cobalt_raspi_tools.tar.bz2
+ $ tar xvpf cobalt_raspi_tools.tar.bz2
```
-1. Sync your sysroot by completing the following steps:
-
- 1. Boot up your RasPi, and set `$RASPI_ADDR` to the device's IP address.
- 1. Run `mkdir -p $RASPI_HOME/sysroot`
- 1. Run:
-
- ```
- rsync -avzh --safe-links \
- --delete-after pi@$RASPI_ADDR:/{opt,lib,usr} \
- --exclude="lib/firmware" --exclude="lib/modules" \
- --include="usr/lib" --include="usr/include" \
- --include="usr/local/include" --include="usr/local/lib" \
- --exclude="usr/*" --include="opt/vc" --exclude="opt/*" \
- $RASPI_HOME/sysroot
- password: raspberry
- ```
+ (This is a combination of old raspi tools and a newer one from linaro
+ to support older Raspbian Jessie and newer Raspbian Buster)
## Build, install, and run Cobalt for Raspberry Pi
@@ -123,7 +96,7 @@
on the device:
```
- rsync -avzLPh --exclude="obj*" --exclude="gen/" \
+ $ rsync -avzLPh --exclude="obj*" --exclude="gen/" \
$COBALT_SRC/out/raspi-2_debug pi@$RASPI_ADDR:~/
```
@@ -135,9 +108,9 @@
to quit or restart Cobalt.
```
- ssh pi@$RASPI_ADDR
- cd raspi-2_debug
- ./cobalt
+ $ ssh pi@$RASPI_ADDR
+ $ cd raspi-2_debug
+ $ ./cobalt
```
With this approach, you can just hit `[CTRL-C]` to close Cobalt. If you
@@ -147,21 +120,3 @@
Note that you can also exit YouTube on Cobalt by hitting the `[Esc]` key
enough times to bring up the "Do you want to quit YouTube?" dialog and
selecting "yes".
-
-### Improving Cobalt performance on Raspberry Pi
-
-1. You will find that there are some processes installed by default that run on the
- Raspberry Pi and can take away CPU time from Cobalt. You may wish to consider
- disabling these processes for maximum (and more consistent) performance, as they
- have been found to occasionally take >10% of the CPU according to `top`.
- You can do this by typing:
-
- ```
- apt-get remove -y --auto-remove [PACKAGE_NAME, ...]
- ```
-
- For example:
-
- ```
- apt-get remove -y --auto-remove avahi-daemon
- ```
diff --git a/src/cobalt/system_window/system_window.cc b/src/cobalt/system_window/system_window.cc
index 85dbe0c..7dc64ce 100644
--- a/src/cobalt/system_window/system_window.cc
+++ b/src/cobalt/system_window/system_window.cc
@@ -108,6 +108,14 @@
InputEvent::Type type, bool is_repeat) {
DCHECK(event);
DCHECK(event->data);
+ if (event == nullptr) {
+ LOG(ERROR) << "SystemWindow::DispatchInputEvent: missing event";
+ return;
+ }
+ if (event->data == nullptr) {
+ LOG(ERROR) << "SystemWindow::DispatchInputEvent: missing event data";
+ return;
+ }
SbInputData* input_data = static_cast<SbInputData*>(event->data);
const SbInputData& data = *input_data;
@@ -115,7 +123,7 @@
SbTimeMonotonic timestamp = 0;
#if SB_API_VERSION >= 13
timestamp = event->timestamp;
-#else // SB_API_VERSION >= 13
+#else // SB_API_VERSION >= 13
bool use_input_timestamp =
SbSystemHasCapability(kSbSystemCapabilitySetsInputTimestamp);
if (use_input_timestamp) {
diff --git a/src/cobalt/trace_event/scoped_trace_to_file.cc b/src/cobalt/trace_event/scoped_trace_to_file.cc
index d5d3d9d..ffbe948 100644
--- a/src/cobalt/trace_event/scoped_trace_to_file.cc
+++ b/src/cobalt/trace_event/scoped_trace_to_file.cc
@@ -28,12 +28,17 @@
namespace cobalt {
namespace trace_event {
+// static
+base::FilePath ScopedTraceToFile::filepath_to_absolute(
+ const base::FilePath& output_path_relative_to_logs) {
+ base::FilePath result;
+ base::PathService::Get(cobalt::paths::DIR_COBALT_DEBUG_OUT, &result);
+ return result.Append(output_path_relative_to_logs);
+}
+
ScopedTraceToFile::ScopedTraceToFile(
const base::FilePath& output_path_relative_to_logs) {
- base::PathService::Get(cobalt::paths::DIR_COBALT_DEBUG_OUT,
- &absolute_output_path_);
- absolute_output_path_ =
- absolute_output_path_.Append(output_path_relative_to_logs);
+ absolute_output_path_ = filepath_to_absolute(output_path_relative_to_logs);
DCHECK(!base::trace_event::TraceLog::GetInstance()->IsEnabled());
base::trace_event::TraceLog::GetInstance()->SetEnabled(
diff --git a/src/cobalt/trace_event/scoped_trace_to_file.h b/src/cobalt/trace_event/scoped_trace_to_file.h
index 49c6413..d03216f 100644
--- a/src/cobalt/trace_event/scoped_trace_to_file.h
+++ b/src/cobalt/trace_event/scoped_trace_to_file.h
@@ -38,6 +38,10 @@
return absolute_output_path_;
}
+ // Returns absolute output path from relative filename
+ static base::FilePath filepath_to_absolute(
+ const base::FilePath& output_path_relative_to_logs);
+
private:
base::FilePath absolute_output_path_;
};
diff --git a/src/cobalt/updater/configurator.cc b/src/cobalt/updater/configurator.cc
index 4c3b6cf..ee8b15b 100644
--- a/src/cobalt/updater/configurator.cc
+++ b/src/cobalt/updater/configurator.cc
@@ -8,6 +8,7 @@
#include <utility>
#include "base/version.h"
+#include "cobalt/browser/switches.h"
#include "cobalt/script/javascript_engine.h"
#include "cobalt/updater/network_fetcher.h"
#include "cobalt/updater/patcher.h"
@@ -43,6 +44,11 @@
return prop;
}
+bool CompressUpdate() {
+ return base::CommandLine::ForCurrentProcess()->HasSwitch(
+ cobalt::browser::switches::kCompressUpdate);
+}
+
} // namespace
namespace cobalt {
@@ -53,7 +59,7 @@
persisted_data_(std::make_unique<update_client::PersistedData>(
pref_service_.get(), nullptr)),
is_channel_changed_(0),
- unzip_factory_(base::MakeRefCounted<UnzipperFactory>()),
+ unzip_factory_(base::MakeRefCounted<UnzipperFactory>(CompressUpdate())),
network_fetcher_factory_(
base::MakeRefCounted<NetworkFetcherFactoryCobalt>(network_module)),
patch_factory_(base::MakeRefCounted<PatcherFactory>()) {
@@ -243,6 +249,16 @@
updater_status_ = status;
}
+void Configurator::SetMinFreeSpaceBytes(uint64_t bytes) {
+ base::AutoLock auto_lock(const_cast<base::Lock&>(min_free_space_bytes_lock_));
+ min_free_space_bytes_ = bytes;
+}
+
+uint64_t Configurator::GetMinFreeSpaceBytes() {
+ base::AutoLock auto_lock(const_cast<base::Lock&>(min_free_space_bytes_lock_));
+ return min_free_space_bytes_;
+}
+
std::string Configurator::GetPreviousUpdaterStatus() const {
base::AutoLock auto_lock(
const_cast<base::Lock&>(previous_updater_status_lock_));
diff --git a/src/cobalt/updater/configurator.h b/src/cobalt/updater/configurator.h
index 5314984..54bb6c0 100644
--- a/src/cobalt/updater/configurator.h
+++ b/src/cobalt/updater/configurator.h
@@ -1,6 +1,16 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+// Copyright 2019 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_UPDATER_CONFIGURATOR_H_
#define COBALT_UPDATER_CONFIGURATOR_H_
@@ -85,6 +95,9 @@
std::string GetPreviousUpdaterStatus() const override;
void SetPreviousUpdaterStatus(const std::string& status) override;
+ void SetMinFreeSpaceBytes(uint64_t bytes) override;
+ uint64_t GetMinFreeSpaceBytes() override;
+
private:
friend class base::RefCountedThreadSafe<Configurator>;
~Configurator() override;
@@ -102,6 +115,8 @@
std::string previous_updater_status_;
base::Lock previous_updater_status_lock_;
std::string user_agent_string_;
+ uint64_t min_free_space_bytes_ = 48 * 1024 * 1024;
+ base::Lock min_free_space_bytes_lock_;
DISALLOW_COPY_AND_ASSIGN(Configurator);
};
diff --git a/src/cobalt/updater/unzipper.cc b/src/cobalt/updater/unzipper.cc
index 1855b3a..5b1e2ba 100644
--- a/src/cobalt/updater/unzipper.cc
+++ b/src/cobalt/updater/unzipper.cc
@@ -15,22 +15,223 @@
#include "cobalt/updater/unzipper.h"
#include <utility>
+#include <vector>
+
+#include "base/bind.h"
#include "base/callback.h"
+#include "base/files/file.h"
#include "base/files/file_path.h"
+#include "starboard/event.h"
+#include "starboard/memory.h"
+#include "starboard/system.h"
#include "starboard/time.h"
+#include "third_party/lz4_lib/lz4frame.h"
#include "third_party/zlib/google/zip.h"
+#include "third_party/zlib/google/zip_reader.h"
namespace cobalt {
namespace updater {
namespace {
+constexpr size_t kChunkSize = 8 * 1024;
+constexpr LZ4F_preferences_t kPrefs = {
+ {LZ4F_max256KB, LZ4F_blockLinked, LZ4F_noContentChecksum, LZ4F_frame,
+ 0 /* unknown content size */, 0 /* no dictID */, LZ4F_noBlockChecksum},
+ 0,
+ /* compression level; 0 == default */ 0,
+ /* autoflush */ 0,
+ /* favor decompression speed */ {0, 0, 0},
+ /* reserved, must be set to 0 */};
+
+class LZ4WriterDelegate : public zip::WriterDelegate {
+ public:
+ explicit LZ4WriterDelegate(const base::FilePath& output_file_path)
+ : output_file_path_(output_file_path) {}
+
+ bool PrepareOutput() override {
+ if (!base::CreateDirectory(output_file_path_.DirName())) {
+ return false;
+ }
+
+ output_file_path_ = output_file_path_.ReplaceExtension("lz4");
+
+ LOG(INFO) << "LZ4WriterDelegate::PrepareOutput: output_file_path_="
+ << output_file_path_.value();
+ file_.Initialize(output_file_path_,
+ base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+ if (!file_.IsValid()) {
+ LOG(ERROR) << "LZ4WriterDelegate::PrepareOutput: invalid file"
+ << output_file_path_.value();
+ return false;
+ }
+
+ LZ4F_errorCode_t res = LZ4F_createCompressionContext(&ctx_, LZ4F_VERSION);
+ if (LZ4F_isError(res)) {
+ LOG(ERROR) << "LZ4WriterDelegate::PrepareOutput: lz4 error code= " << res;
+ return false;
+ }
+
+ out_capacity_ = LZ4F_compressBound(kChunkSize, &kPrefs);
+ LOG(INFO) << "out_capacity=" << out_capacity_;
+ out_buffer_.resize(out_capacity_);
+ if (!WriteHeader()) {
+ LOG(ERROR) << "LZ4WriterDelegate::PrepareOutput: Failed to write header";
+ return false;
+ }
+ return true;
+ }
+
+ bool WriteBytes(const char* data, int num_bytes) override {
+ LOG(INFO) << "LZ4WriterDelegate::WriteBytes: num_bytes=" << num_bytes
+ << " out_capacity=" << out_capacity_;
+ size_t compressed_size = LZ4F_compressUpdate(
+ ctx_, out_buffer_.data(), out_buffer_.size(), data, num_bytes, nullptr);
+ if (LZ4F_isError(compressed_size)) {
+ LOG(ERROR) << "LZ4WriterDelegate::WriteBytes: Compression failed: error "
+ << (unsigned)compressed_size;
+ return false;
+ }
+
+ LOG(INFO) << "LZ4WriterDelegate::WriteBytes: Writing "
+ << (unsigned)compressed_size << " bytes";
+ if (compressed_size !=
+ file_.WriteAtCurrentPos(out_buffer_.data(), compressed_size)) {
+ LOG(ERROR) << "LZ4WriterDelegate::WriteBytes: failed to write "
+ << compressed_size << " bytes";
+ return false;
+ }
+ return true;
+ }
+
+ void SetTimeModified(const base::Time& time) override {
+ size_t compressed_size =
+ LZ4F_compressEnd(ctx_, out_buffer_.data(), out_buffer_.size(), nullptr);
+ if (LZ4F_isError(compressed_size)) {
+ LOG(ERROR) << "LZ4WriterDelegate::SetTimeModified: Failed to end "
+ "compression: error "
+ << (unsigned)compressed_size;
+ return;
+ }
+
+ LOG(INFO) << "Writing " << (unsigned)compressed_size << " bytes";
+
+ if (compressed_size !=
+ file_.WriteAtCurrentPos(out_buffer_.data(), compressed_size)) {
+ LOG(ERROR) << "LZ4WriterDelegate::SetTimeModified: failed to write "
+ << compressed_size << " bytes";
+ return;
+ }
+
+ success_ = true;
+ }
+
+ bool IsSuccessful() override { return success_; }
+
+ ~LZ4WriterDelegate() {
+ if (ctx_) {
+ LZ4F_freeCompressionContext(ctx_);
+ }
+ file_.Close();
+ }
+
+ private:
+ bool WriteHeader() {
+ size_t header_size = LZ4F_compressBegin(ctx_, out_buffer_.data(),
+ out_buffer_.size(), &kPrefs);
+ if (LZ4F_isError(header_size)) {
+ LOG(ERROR) << "Failed to start compression: error "
+ << (unsigned)header_size;
+ return false;
+ }
+ LOG(INFO) << "Header size " << (unsigned)header_size;
+ return header_size ==
+ file_.WriteAtCurrentPos(out_buffer_.data(), header_size);
+ }
+
+ base::FilePath output_file_path_;
+ base::File file_;
+ LZ4F_compressionContext_t ctx_ = nullptr;
+ std::vector<char> out_buffer_;
+ size_t out_capacity_ = 64 * 1024;
+ bool success_ = false;
+};
+
class UnzipperImpl : public update_client::Unzipper {
public:
- UnzipperImpl() = default;
+ explicit UnzipperImpl(bool updater_compression)
+ : compress_update_(updater_compression) {}
void Unzip(const base::FilePath& zip_path, const base::FilePath& output_path,
UnzipCompleteCallback callback) override {
+ if (compress_update_) {
+ UnzipAndLZ4Compress(zip_path, output_path, std::move(callback));
+ } else {
+ UnzipToPath(zip_path, output_path, std::move(callback));
+ }
+ }
+
+ void UnzipAndLZ4Compress(const base::FilePath& zip_path,
+ const base::FilePath& output_path,
+ UnzipCompleteCallback callback) {
+ SbTimeMonotonic time_before_unzip = SbTimeGetMonotonicNow();
+
+ // Filter to unzip just libcobalt.so
+ auto filter = base::BindRepeating([](const base::FilePath& path) {
+ return (path.BaseName().value() == "libcobalt.so");
+ });
+ auto directory_creator = base::BindRepeating(
+ [](const base::FilePath& extract_dir,
+ const base::FilePath& entry_path) {
+ return base::CreateDirectory(extract_dir.Append(entry_path));
+ },
+ output_path);
+ auto writer_factory = base::BindRepeating(
+ [](const base::FilePath& extract_dir, const base::FilePath& entry_path)
+ -> std::unique_ptr<zip::WriterDelegate> {
+ return std::make_unique<LZ4WriterDelegate>(
+ extract_dir.Append(entry_path));
+ },
+ output_path);
+
+ if (!zip::UnzipWithFilterAndWriters(zip_path, writer_factory,
+ directory_creator, filter, true)) {
+ LOG(ERROR) << "Failed to unzip libcobalt.so";
+ std::move(callback).Run(false);
+ return;
+ } else {
+ LOG(INFO) << "Successfully unzipped and lz4 compressed libcobalt.so";
+ }
+
+ // Filter to unzip the rest of the CRX package.
+ filter = base::BindRepeating([](const base::FilePath& path) {
+ if (path.BaseName().value() == "libcobalt.so") {
+ return false;
+ }
+ return true;
+ });
+
+ if (!zip::UnzipWithFilterCallback(zip_path, output_path, filter, true)) {
+ LOG(ERROR) << "Failed to unzip the CRX package";
+ std::move(callback).Run(false);
+ return;
+ } else {
+ LOG(INFO) << "Successfully unzipped the CRX package";
+ }
+
+ std::move(callback).Run(true);
+
+ SbTimeMonotonic time_unzip_took =
+ SbTimeGetMonotonicNow() - time_before_unzip;
+ LOG(INFO) << "Unzip file path = " << zip_path;
+ LOG(INFO) << "output_path = " << output_path;
+ LOG(INFO) << "Unzip took " << time_unzip_took / kSbTimeMillisecond
+ << " milliseconds.";
+ }
+
+ void UnzipToPath(const base::FilePath& zip_path,
+ const base::FilePath& output_path,
+ UnzipCompleteCallback callback) {
SbTimeMonotonic time_before_unzip = SbTimeGetMonotonicNow();
std::move(callback).Run(zip::Unzip(zip_path, output_path));
SbTimeMonotonic time_unzip_took =
@@ -40,14 +241,18 @@
LOG(INFO) << "Unzip took " << time_unzip_took / kSbTimeMillisecond
<< " milliseconds.";
}
+
+ private:
+ bool compress_update_ = false;
};
} // namespace
-UnzipperFactory::UnzipperFactory() = default;
+UnzipperFactory::UnzipperFactory(bool compress_update)
+ : compress_update_(compress_update) {}
std::unique_ptr<update_client::Unzipper> UnzipperFactory::Create() const {
- return std::make_unique<UnzipperImpl>();
+ return std::make_unique<UnzipperImpl>(compress_update_);
}
UnzipperFactory::~UnzipperFactory() = default;
diff --git a/src/cobalt/updater/unzipper.h b/src/cobalt/updater/unzipper.h
index 46dfb7e..df9ecf5 100644
--- a/src/cobalt/updater/unzipper.h
+++ b/src/cobalt/updater/unzipper.h
@@ -1,6 +1,16 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+// Copyright 2019 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_UPDATER_UNZIPPER_H_
#define COBALT_UPDATER_UNZIPPER_H_
@@ -16,7 +26,7 @@
class UnzipperFactory : public update_client::UnzipperFactory {
public:
- UnzipperFactory();
+ explicit UnzipperFactory(bool compress_update);
std::unique_ptr<update_client::Unzipper> Create() const override;
@@ -24,6 +34,7 @@
~UnzipperFactory() override;
private:
+ bool compress_update_;
DISALLOW_COPY_AND_ASSIGN(UnzipperFactory);
};
diff --git a/src/cobalt/updater/updater.gyp b/src/cobalt/updater/updater.gyp
index e603703..efbef55 100644
--- a/src/cobalt/updater/updater.gyp
+++ b/src/cobalt/updater/updater.gyp
@@ -46,6 +46,7 @@
'<(DEPTH)/components/update_client/update_client.gyp:update_client',
'<(DEPTH)/starboard/loader_app/app_key_files.gyp:app_key_files',
'<(DEPTH)/starboard/loader_app/drain_file.gyp:drain_file',
+ '<(DEPTH)/third_party/lz4_lib/lz4.gyp:lz4',
'<(DEPTH)/third_party/zlib/zlib.gyp:zip',
'<(DEPTH)/url/url.gyp:url',
],
diff --git a/src/cobalt/updater/updater_module.cc b/src/cobalt/updater/updater_module.cc
index e2e4001..dfbb14b 100644
--- a/src/cobalt/updater/updater_module.cc
+++ b/src/cobalt/updater/updater_module.cc
@@ -37,6 +37,7 @@
#include "cobalt/updater/crash_reporter.h"
#include "cobalt/updater/utils.h"
#include "components/crx_file/crx_verifier.h"
+#include "components/update_client/cobalt_slot_management.h"
#include "components/update_client/utils.h"
#include "starboard/common/file.h"
#include "starboard/configuration_constants.h"
@@ -44,6 +45,7 @@
namespace {
using update_client::ComponentState;
+using update_client::CobaltSlotManagement;
// The SHA256 hash of the "cobalt_evergreen_public" key.
constexpr uint8_t kCobaltPublicKeyHash[] = {
@@ -92,7 +94,8 @@
namespace cobalt {
namespace updater {
-const uint64_t kDefaultUpdateCheckDelaySeconds = 60;
+// The delay in seconds before the first update check.
+const uint64_t kDefaultUpdateCheckDelaySeconds = 15;
void Observer::OnEvent(Events event, const std::string& id) {
LOG(INFO) << "Observer::OnEvent id=" << id;
@@ -220,6 +223,17 @@
}
updater_configurator_ = nullptr;
+
+ // Cleanup drain files
+ const auto installation_manager =
+ static_cast<const CobaltExtensionInstallationManagerApi*>(
+ SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
+ if (installation_manager) {
+ CobaltSlotManagement cobalt_slot_management;
+ if (cobalt_slot_management.Init(installation_manager)) {
+ cobalt_slot_management.CleanupAllDrainFiles();
+ }
+ }
LOG(INFO) << "UpdaterModule::Finalize end";
}
@@ -388,5 +402,12 @@
return index;
}
+void UpdaterModule::SetMinFreeSpaceBytes(uint64_t bytes) {
+ LOG(INFO) << "UpdaterModule::SetMinFreeSpaceBytes bytes=" << bytes;
+ if (updater_configurator_) {
+ updater_configurator_->SetMinFreeSpaceBytes(bytes);
+ }
+}
+
} // namespace updater
} // namespace cobalt
diff --git a/src/cobalt/updater/updater_module.h b/src/cobalt/updater/updater_module.h
index 353215e..0d6f793 100644
--- a/src/cobalt/updater/updater_module.h
+++ b/src/cobalt/updater/updater_module.h
@@ -151,6 +151,8 @@
int GetInstallationIndex() const;
+ void SetMinFreeSpaceBytes(uint64_t bytes);
+
private:
std::unique_ptr<base::Thread> updater_thread_;
scoped_refptr<update_client::UpdateClient> update_client_;
diff --git a/src/cobalt/version.h b/src/cobalt/version.h
index 67c2448..4d35aae 100644
--- a/src/cobalt/version.h
+++ b/src/cobalt/version.h
@@ -35,6 +35,6 @@
// release is cut.
//.
-#define COBALT_VERSION "22.lts.3"
+#define COBALT_VERSION "22.lts.4"
#endif // COBALT_VERSION_H_
diff --git a/src/components/update_client/cobalt_slot_management.cc b/src/components/update_client/cobalt_slot_management.cc
index 0c601d7..6abee58 100644
--- a/src/components/update_client/cobalt_slot_management.cc
+++ b/src/components/update_client/cobalt_slot_management.cc
@@ -14,8 +14,10 @@
#include "components/update_client/cobalt_slot_management.h"
+#include <algorithm>
#include <vector>
+#include "base/files/file_util.h"
#include "base/values.h"
#include "cobalt/updater/utils.h"
#include "components/update_client/utils.h"
@@ -37,6 +39,32 @@
return !bad_app_key_file_path.empty() &&
SbFileExists(bad_app_key_file_path.c_str());
}
+
+uint64_t ComputeSlotSize(
+ const CobaltExtensionInstallationManagerApi* installation_api,
+ int index) {
+ if (!installation_api) {
+ LOG(WARNING) << "ComputeSlotSize: "
+ << "Missing installation manager extension.";
+ return 0;
+ }
+ std::vector<char> installation_path(kSbFileMaxPath);
+ if (installation_api->GetInstallationPath(index, installation_path.data(),
+ kSbFileMaxPath) == IM_EXT_ERROR) {
+ LOG(WARNING) << "ComputeSlotSize: "
+ << "Failed to get installation path for slot " << index;
+ return 0;
+ }
+ int64_t slot_size =
+ base::ComputeDirectorySize(base::FilePath(installation_path.data()));
+ LOG(INFO) << "ComputeSlotSize: slot_size=" << slot_size;
+ if (slot_size <= 0) {
+ LOG(WARNING) << "ComputeSlotSize: "
+ << "Failed to compute slot " << index << " size";
+ return 0;
+ }
+ return slot_size;
+}
} // namespace
CobaltSlotManagement::CobaltSlotManagement() : installation_api_(nullptr) {}
@@ -61,11 +89,15 @@
return false;
}
app_key_ = app_key;
+ initialized_ = true;
return true;
}
bool CobaltSlotManagement::SelectSlot(base::FilePath* dir) {
- SB_DCHECK(installation_api_);
+ SB_DCHECK(initialized_);
+ if (!initialized_) {
+ return false;
+ }
LOG(INFO) << "CobaltSlotManagement::SelectSlot";
int max_slots = installation_api_->GetMaxNumberInstallations();
if (max_slots == IM_EXT_ERROR) {
@@ -97,16 +129,18 @@
base::FilePath installation_dir(
std::string(installation_path.begin(), installation_path.end()));
- // Cleanup expired drain files.
- DrainFileClear(installation_dir.value().c_str(), app_key_.c_str(), true);
+ // Cleanup all expired files from all apps.
+ DrainFileClearExpired(installation_dir.value().c_str());
// Cleanup all drain files from the current app.
- DrainFileRemove(installation_dir.value().c_str(), app_key_.c_str());
+ DrainFileClearForApp(installation_dir.value().c_str(), app_key_.c_str());
+
base::Version version =
cobalt::updater::ReadEvergreenVersion(installation_dir);
if (!version.IsValid()) {
LOG(INFO) << "CobaltSlotManagement::SelectSlot installed version invalid";
- if (!DrainFileDraining(installation_dir.value().c_str(), "")) {
+ if (!DrainFileIsAnotherAppDraining(installation_dir.value().c_str(),
+ app_key_.c_str())) {
LOG(INFO) << "CobaltSlotManagement::SelectSlot not draining";
// Found empty slot.
slot_candidate = i;
@@ -119,7 +153,8 @@
}
} else if ((!slot_candidate_version.IsValid() ||
slot_candidate_version > version)) {
- if (!DrainFileDraining(installation_dir.value().c_str(), "")) {
+ if (!DrainFileIsAnotherAppDraining(installation_dir.value().c_str(),
+ app_key_.c_str())) {
// Found a slot with older version that's not draining.
LOG(INFO) << "CobaltSlotManagement::SelectSlot slot candidate: " << i;
slot_candidate_version = version;
@@ -147,7 +182,10 @@
}
bool CobaltSlotManagement::ConfirmSlot(const base::FilePath& dir) {
- SB_DCHECK(installation_api_);
+ SB_DCHECK(initialized_);
+ if (!initialized_) {
+ return false;
+ }
LOG(INFO) << "CobaltSlotManagement::ConfirmSlot ";
if (!DrainFileRankAndCheck(dir.value().c_str(), app_key_.c_str())) {
LOG(INFO) << "CobaltSlotManagement::ConfirmSlot: failed to lock slot ";
@@ -170,13 +208,49 @@
return true;
}
-void CobaltSlotManagement::CleanupAllDrainFiles(const base::FilePath& dir) {
- if (!dir.empty() && !app_key_.empty()) {
- DrainFileRemove(dir.value().c_str(), app_key_.c_str());
+void CobaltSlotManagement::CleanupAllDrainFiles() {
+ SB_DCHECK(initialized_);
+ if (!initialized_) {
+ return;
+ }
+
+ int max_slots = installation_api_->GetMaxNumberInstallations();
+ if (max_slots == IM_EXT_ERROR) {
+ LOG(ERROR) << "CobaltSlotManagement::CleanupAllDrainFile: Failed to get "
+ "max number of slots";
+ return;
+ }
+
+ // Iterate over all writeable installation slots.
+ for (int i = 1; i < max_slots; i++) {
+ LOG(INFO) << "CobaltSlotManagement::CleanupAllDrainFile iterating slot="
+ << i;
+ std::vector<char> installation_path(kSbFileMaxPath);
+ if (installation_api_->GetInstallationPath(i, installation_path.data(),
+ installation_path.size()) ==
+ IM_EXT_ERROR) {
+ LOG(ERROR) << "CobaltSlotManagement::CleanupAllDrainFile: Failed to get "
+ "installation path for slot="
+ << i;
+ continue;
+ }
+
+ LOG(INFO)
+ << "CobaltSlotManagement::CleanupAllDrainFile: installation_path = "
+ << installation_path.data();
+
+ base::FilePath installation_dir(
+ std::string(installation_path.begin(), installation_path.end()));
+
+ DrainFileClearForApp(installation_dir.value().c_str(), app_key_.c_str());
}
}
int CobaltSlotManagement::GetInstallationIndex() {
+ SB_DCHECK(initialized_);
+ if (!initialized_) {
+ return false;
+ }
return installation_index_;
}
@@ -191,7 +265,7 @@
if (!starboard::loader_app::CreateAppKeyFile(good_app_key_file_path)) {
LOG(WARNING) << "Failed to create good app key file";
}
- DrainFileRemove(dir.c_str(), app_key.c_str());
+ DrainFileClearForApp(dir.c_str(), app_key.c_str());
int ret =
installation_api->RequestRollForwardToInstallation(installation_index);
if (ret == IM_EXT_ERROR) {
@@ -247,18 +321,24 @@
base::Version installed_version =
cobalt::updater::ReadEvergreenVersion(installation_dir);
+ std::string good_app_key_file_path =
+ starboard::loader_app::GetGoodAppKeyFilePath(
+ installation_dir.value().c_str(), app_key);
if (!installed_version.IsValid()) {
LOG(WARNING) << "CobaltQuickInstallation: invalid version ";
continue;
} else if (slot_candidate_version < installed_version &&
current_version < installed_version &&
- !DrainFileDraining(installation_dir.value().c_str(), "") &&
+ !DrainFileIsAnotherAppDraining(installation_dir.value().c_str(),
+ app_key) &&
!CheckBadFileExists(installation_dir.value().c_str(), app_key) &&
+ !SbFileExists(good_app_key_file_path.c_str()) &&
starboard::loader_app::AnyGoodAppKeyFile(
installation_dir.value().c_str())) {
// Found a slot with newer version than the current version that's not
// draining, and no bad file of current app exists, and a good file
- // exists. The final candidate is the newest version of the valid
+ // exists from a another app, but not from the current app.
+ // The final candidate is the newest version of the valid
// candidates.
LOG(INFO) << "CobaltQuickInstallation: slot candidate: " << i;
slot_candidate_version = installed_version;
@@ -277,4 +357,52 @@
return false;
}
+bool CobaltSkipUpdate(
+ const CobaltExtensionInstallationManagerApi* installation_api,
+ uint64_t min_free_space_bytes,
+ int64_t free_space_bytes,
+ uint64_t installation_cleanup_size) {
+ LOG(INFO) << "CobaltSkipUpdate: "
+ << " min_free_space_bytes=" << min_free_space_bytes
+ << " free_space_bytes=" << free_space_bytes
+ << " installation_cleanup_size=" << installation_cleanup_size;
+
+ if (free_space_bytes < 0) {
+ LOG(WARNING) << "CobaltSkipUpdate: "
+ << "Unable to determine free space";
+ return false;
+ }
+
+ if (free_space_bytes + installation_cleanup_size < min_free_space_bytes) {
+ LOG(WARNING) << "CobaltSkipUpdate: Not enough free space";
+ return true;
+ }
+
+ return false;
+}
+
+uint64_t CobaltInstallationCleanupSize(
+ const CobaltExtensionInstallationManagerApi* installation_api) {
+ if (!installation_api) {
+ LOG(WARNING) << "CobaltInstallationCleanupSize: "
+ << "Missing installation manager extension.";
+ return 0;
+ }
+ int max_slots = installation_api->GetMaxNumberInstallations();
+ if (max_slots == IM_EXT_ERROR) {
+ LOG(ERROR)
+ << "CobaltInstallationCleanupSize: Failed to get max number of slots.";
+ return 0;
+ }
+ // Ignore the system slot 0 and start with slot 1.
+ uint64_t min_slot_size = ComputeSlotSize(installation_api, 1);
+ for (int i = 2; i < max_slots; i++) {
+ uint64_t slot_size = ComputeSlotSize(installation_api, i);
+ if (slot_size < min_slot_size) {
+ min_slot_size = slot_size;
+ }
+ }
+
+ return min_slot_size;
+}
} // namespace update_client
diff --git a/src/components/update_client/cobalt_slot_management.h b/src/components/update_client/cobalt_slot_management.h
index e5d185c..8eb40c9 100644
--- a/src/components/update_client/cobalt_slot_management.h
+++ b/src/components/update_client/cobalt_slot_management.h
@@ -52,12 +52,13 @@
int GetInstallationIndex();
// Cleanup all drain files of the current app.
- void CleanupAllDrainFiles(const base::FilePath& dir);
+ void CleanupAllDrainFiles();
private:
int installation_index_ = IM_EXT_INVALID_INDEX;
const CobaltExtensionInstallationManagerApi* installation_api_;
std::string app_key_;
+ bool initialized_ = false;
};
// Creates a good file and rolls forward to the installation in
@@ -76,6 +77,22 @@
const CobaltExtensionInstallationManagerApi* installation_api,
const base::Version& current_version);
+// Computes whether Cobalt should skip the update based on the
+// |min_free_space_bytes|, the |free_space_bytes| and the amount of
+// space that can be recovered from |installation_cleanup_size|.
+// The default behavior is to NOT skip unless the measurements
+// show that there isn't enough space to perform the update.
+bool CobaltSkipUpdate(
+ const CobaltExtensionInstallationManagerApi* installation_api,
+ uint64_t min_free_space_bytes,
+ int64_t free_space_bytes,
+ uint64_t installation_cleanup_size);
+
+// Computes the installation cleanup size by taking the min space
+// from all the installation slots excluding the system slot 0.
+uint64_t CobaltInstallationCleanupSize(
+ const CobaltExtensionInstallationManagerApi* installation_api);
+
} // namespace update_client
#endif // COMPONENTS_UPDATE_CLIENT_COBALT_SLOT_MANAGEMENT_H_
diff --git a/src/components/update_client/cobalt_slot_management_test.cc b/src/components/update_client/cobalt_slot_management_test.cc
index 55256de..44463bd 100644
--- a/src/components/update_client/cobalt_slot_management_test.cc
+++ b/src/components/update_client/cobalt_slot_management_test.cc
@@ -14,7 +14,11 @@
#include "components/update_client/cobalt_slot_management.h"
+#include <algorithm>
+#include <vector>
+
#include "base/strings/string_util.h"
+#include "cobalt/extension/free_space.h"
#include "starboard/common/file.h"
#include "starboard/loader_app/app_key_files.h"
#include "starboard/loader_app/drain_file.h"
@@ -109,7 +113,7 @@
ASSERT_TRUE(cobalt_slot_management.Init(api_));
base::FilePath dir;
ASSERT_TRUE(cobalt_slot_management.SelectSlot(&dir));
- ASSERT_TRUE(DrainFileDraining(dir.value().c_str(), kTestAppKey1));
+ ASSERT_TRUE(DrainFileIsAppDraining(dir.value().c_str(), kTestAppKey1));
ASSERT_TRUE(base::EndsWith(dir.value(), "installation_1",
base::CompareCase::SENSITIVE));
}
@@ -148,7 +152,7 @@
ASSERT_TRUE(cobalt_slot_management.Init(api_));
base::FilePath dir;
cobalt_slot_management.SelectSlot(&dir);
- ASSERT_TRUE(DrainFileDraining(dir.value().c_str(), kTestAppKey1));
+ ASSERT_TRUE(DrainFileIsAppDraining(dir.value().c_str(), kTestAppKey1));
LOG(INFO) << "dir=" << dir;
ASSERT_TRUE(base::EndsWith(dir.value(), "installation_2",
@@ -174,7 +178,7 @@
ASSERT_TRUE(base::EndsWith(dir.value(), "installation_1",
base::CompareCase::SENSITIVE));
- ASSERT_TRUE(DrainFileDraining(dir.value().c_str(), kTestAppKey1));
+ ASSERT_TRUE(DrainFileIsAppDraining(dir.value().c_str(), kTestAppKey1));
ASSERT_TRUE(cobalt_slot_management.ConfirmSlot(dir));
@@ -182,6 +186,19 @@
ASSERT_EQ(IM_MAX_NUM_TRIES, ImGetInstallationNumTriesLeft(1));
}
+TEST_F(CobaltSlotManagementTest, CleanupAllDrainFiles) {
+ if (!storage_path_implemented_) {
+ return;
+ }
+ CobaltSlotManagement cobalt_slot_management;
+ ASSERT_TRUE(cobalt_slot_management.Init(api_));
+ base::FilePath dir;
+ ASSERT_TRUE(cobalt_slot_management.SelectSlot(&dir));
+ ASSERT_TRUE(DrainFileIsAppDraining(dir.value().c_str(), kTestAppKey1));
+ cobalt_slot_management.CleanupAllDrainFiles();
+ ASSERT_FALSE(DrainFileIsAppDraining(dir.value().c_str(), kTestAppKey1));
+}
+
TEST_F(CobaltSlotManagementTest, CobaltFinishInstallation) {
std::string slot_path = storage_path_;
slot_path += kSbFileSepString;
@@ -229,6 +246,55 @@
base::Version version("1.0.0");
ASSERT_FALSE(CobaltQuickUpdate(api_, version));
}
+
+TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateNoInstallationMoreSpace) {
+ ASSERT_FALSE(
+ CobaltSkipUpdate(api_, 1024 /* min */, 1025 /* free */, 0 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateNoInstallationExactSpace) {
+ ASSERT_FALSE(
+ CobaltSkipUpdate(api_, 1024 /* min */, 1024 /* free */, 0 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateNoInstallationNotEnoughSpace) {
+ ASSERT_TRUE(
+ CobaltSkipUpdate(api_, 1024 /* min */, 1023 /* free */, 0 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateWithInstallationMoreSpace) {
+ ASSERT_FALSE(
+ CobaltSkipUpdate(api_, 1024 /* min */, 1024 /* free */, 1 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateWithInstallationExactSpace) {
+ ASSERT_FALSE(
+ CobaltSkipUpdate(api_, 1024 /* min */, 1023 /* free */, 1 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest,
+ CobaltSkipUpdateWithInstallationNotEnoughSpace) {
+ ASSERT_TRUE(
+ CobaltSkipUpdate(api_, 1024 /* min */, 1022 /* free */, 1 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest, CobaltInstallationCleanupSizeNoInstallation) {
+ uint64_t size = CobaltInstallationCleanupSize(api_);
+ ASSERT_EQ(size, 0);
+}
+
+TEST_F(CobaltSlotManagementTest,
+ CobaltInstallationCleanupSizeTwoInstallations) {
+ int len1 = strlen(kManifestV1);
+ int len2 = strlen(kManifestV2);
+ ASSERT_NE(len1, len2);
+
+ CreateManifest("installation_1", kManifestV2, len1);
+ CreateManifest("installation_2", kManifestV1, len2);
+
+ uint64_t size = CobaltInstallationCleanupSize(api_);
+ ASSERT_EQ(size, std::min(len1, len2));
+}
} // namespace
} // namespace update_client
diff --git a/src/components/update_client/component.cc b/src/components/update_client/component.cc
index 8b4fa5d..59c0637 100644
--- a/src/components/update_client/component.cc
+++ b/src/components/update_client/component.cc
@@ -121,6 +121,9 @@
const std::string& public_key,
#if defined(STARBOARD)
const int installation_index,
+ PersistedData* metadata,
+ const std::string& id,
+ const std::string& version,
#endif
const std::string& fingerprint,
scoped_refptr<CrxInstaller> installer,
@@ -170,8 +173,16 @@
// TODO: add correct error code.
install_error = InstallError::GENERIC_ERROR;
} else {
- if (!CobaltFinishInstallation(installation_api, installation_index,
+ if (CobaltFinishInstallation(installation_api, installation_index,
unpack_path.value(), app_key)) {
+ // Write the version of the unpacked update package to the persisted data.
+ if (metadata != nullptr) {
+ main_task_runner->PostTask(
+ FROM_HERE, base::BindOnce(&PersistedData::SetLastInstalledVersion,
+ base::Unretained(metadata), id, version));
+ }
+ } else {
+
// TODO: add correct error code.
install_error = InstallError::GENERIC_ERROR;
}
@@ -215,8 +226,15 @@
// When there is an error unpacking the downloaded CRX, such as a failure to
// verify the package, we should remember to clear out any drain files.
if (base::DirectoryExists(crx_path.DirName())) {
- CobaltSlotManagement cobalt_slot_management;
- cobalt_slot_management.CleanupAllDrainFiles(crx_path.DirName());
+ const auto installation_api =
+ static_cast<const CobaltExtensionInstallationManagerApi*>(
+ SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
+ if (installation_api) {
+ CobaltSlotManagement cobalt_slot_management;
+ if (cobalt_slot_management.Init(installation_api)) {
+ cobalt_slot_management.CleanupAllDrainFiles();
+ }
+ }
}
#endif
main_task_runner->PostTask(
@@ -226,21 +244,15 @@
return;
}
-#if defined(STARBOARD)
- // Write the version of the unpacked update package to the persisted data.
- if (metadata != nullptr) {
- main_task_runner->PostTask(
- FROM_HERE, base::BindOnce(&PersistedData::SetLastUnpackedVersion,
- base::Unretained(metadata), id, version));
- }
-#endif
-
base::PostTaskWithTraits(
FROM_HERE, kTaskTraits,
base::BindOnce(&InstallOnBlockingTaskRunner, main_task_runner,
result.unpack_path, result.public_key,
#if defined(STARBOARD)
installation_index,
+ metadata,
+ id,
+ version,
#endif
fingerprint, installer, std::move(callback)));
}
@@ -717,8 +729,15 @@
DCHECK_NE(ErrorCategory::kNone, component.error_category_);
DCHECK_NE(0, component.error_code_);
+#if defined(STARBOARD)
+ // Create an event when the server response included an update, or when it's
+ // an update check error, like quick roll-forward or out of space
+ if (component.IsUpdateAvailable() ||
+ component.error_category_ == ErrorCategory::kUpdateCheck)
+#else
// Create an event only when the server response included an update.
if (component.IsUpdateAvailable())
+#endif
component.AppendEvent(component.MakeEventUpdateComplete());
EndState();
diff --git a/src/components/update_client/configurator.h b/src/components/update_client/configurator.h
index 16fd982..e4619e9 100644
--- a/src/components/update_client/configurator.h
+++ b/src/components/update_client/configurator.h
@@ -180,6 +180,8 @@
// Compare and swap the is_channel_changed flag.
virtual void CompareAndSwapChannelChanged(int old_value, int new_value) = 0;
+ virtual void SetMinFreeSpaceBytes(uint64_t bytes) = 0;
+ virtual uint64_t GetMinFreeSpaceBytes() = 0;
#endif
protected:
diff --git a/src/components/update_client/persisted_data.cc b/src/components/update_client/persisted_data.cc
index 2928b2e..0e80750 100644
--- a/src/components/update_client/persisted_data.cc
+++ b/src/components/update_client/persisted_data.cc
@@ -81,7 +81,7 @@
}
#if defined(STARBOARD)
-std::string PersistedData::GetLastUnpackedVersion(const std::string& id) const {
+std::string PersistedData::GetLastInstalledVersion(const std::string& id) const {
return GetString(id, "version");
}
std::string PersistedData::GetUpdaterChannel(const std::string& id) const {
@@ -145,7 +145,7 @@
}
#if defined(STARBOARD)
-void PersistedData::SetLastUnpackedVersion(const std::string& id,
+void PersistedData::SetLastInstalledVersion(const std::string& id,
const std::string& version) {
SetString(id, "version", version);
}
diff --git a/src/components/update_client/persisted_data.h b/src/components/update_client/persisted_data.h
index 84efa3e..a59b92c 100644
--- a/src/components/update_client/persisted_data.h
+++ b/src/components/update_client/persisted_data.h
@@ -51,10 +51,10 @@
int GetDateLastActive(const std::string& id) const;
#if defined(STARBOARD)
- // Returns the version of the update that was last successfully unpacked for
+ // Returns the version of the update that was last successfully installed for
// the specified |id|. "" indicates that there is no recorded version value
// for the |id|.
- std::string GetLastUnpackedVersion(const std::string& id) const;
+ std::string GetLastInstalledVersion(const std::string& id) const;
// Returns the updater channel that is set for the specified |id|. ""
// indicates that there is no recorded updater channel value for the |id|.
@@ -82,9 +82,9 @@
void SetDateLastActive(const std::vector<std::string>& ids, int datenum);
#if defined(STARBOARD)
- // Records the version of the update that is successfully unpacked for
+ // Records the version of the update that is successfully installed for
// the specified |id|.
- void SetLastUnpackedVersion(const std::string& id,
+ void SetLastInstalledVersion(const std::string& id,
const std::string& version);
// Records the updater channel that is set for the specified |id|.
diff --git a/src/components/update_client/task_update.cc b/src/components/update_client/task_update.cc
index b4894b7..ea331b4 100644
--- a/src/components/update_client/task_update.cc
+++ b/src/components/update_client/task_update.cc
@@ -43,6 +43,10 @@
DCHECK(thread_checker_.CalledOnValidThread());
#if defined(STARBOARD)
LOG(INFO) << "TaskUpdate::Run begin";
+ if(is_completed_) {
+ LOG(WARNING) << "TaskUpdate::Run already completed";
+ return;
+ }
#endif
if (ids_.empty()) {
diff --git a/src/components/update_client/test_configurator.h b/src/components/update_client/test_configurator.h
index 6a9e5eb..9b37687 100644
--- a/src/components/update_client/test_configurator.h
+++ b/src/components/update_client/test_configurator.h
@@ -135,6 +135,10 @@
std::string GetPreviousUpdaterStatus() const override { return ""; }
void SetPreviousUpdaterStatus(const std::string& status) override {}
+
+ void SetMinFreeSpaceBytes(uint64_t bytes) override {}
+
+ uint64_t GetMinFreeSpaceBytes() override { return 0; }
#else
network::TestURLLoaderFactory* test_url_loader_factory() {
return &test_url_loader_factory_;
diff --git a/src/components/update_client/update_checker.cc b/src/components/update_client/update_checker.cc
index 4951abf..124fe58 100644
--- a/src/components/update_client/update_checker.cc
+++ b/src/components/update_client/update_checker.cc
@@ -22,11 +22,11 @@
#include "base/threading/thread_checker.h"
#if defined(STARBOARD)
#include "base/threading/thread_id_name_manager.h"
+#include "cobalt/extension/free_space.h"
#endif
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#if defined(STARBOARD)
-#include "cobalt/extension/installation_manager.h"
#include "cobalt/updater/utils.h"
#include "components/update_client/cobalt_slot_management.h"
#endif
@@ -102,7 +102,8 @@
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates);
#if defined(STARBOARD)
- void Cancel();
+ void Cancel() ;
+ virtual bool SkipUpdate(const CobaltExtensionInstallationManagerApi* installation_api);
#endif
void OnRequestSenderComplete(int error,
const std::string& response,
@@ -228,22 +229,20 @@
base::Version current_version = crx_component->version;
#if defined(STARBOARD)
- std::string unpacked_version =
- GetPersistedData()->GetLastUnpackedVersion(app_id);
- // If the version of the last unpacked update package is higher than the
- // version of the running binary, use the former to indicate the current
- // update version in the update check request.
- if (!unpacked_version.empty() &&
- base::Version(unpacked_version).CompareTo(current_version) > 0) {
- current_version = base::Version(unpacked_version);
- }
-
// Check if there is an available update already for quick roll-forward
auto installation_api =
static_cast<const CobaltExtensionInstallationManagerApi*>(
SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
if (!installation_api) {
- LOG(ERROR) << "Failed to get installation manager extension.";
+ LOG(ERROR) << "UpdaterChecker: "
+ << "Failed to get installation manager extension.";
+ return;
+ }
+
+ if (SkipUpdate(installation_api)) {
+ LOG(WARNING) << "UpdaterChecker is skipping";
+ UpdateCheckFailed(ErrorCategory::kUpdateCheck,
+ static_cast<int>(UpdateCheckError::OUT_OF_SPACE), -1);
return;
}
@@ -259,6 +258,15 @@
return;
}
+ std::string last_installed_version =
+ GetPersistedData()->GetLastInstalledVersion(app_id);
+ // If the version of the last installed update package is higher than the
+ // version of the running binary, use the former to indicate the current
+ // update version in the update check request.
+ if (!last_installed_version.empty() &&
+ base::Version(last_installed_version).CompareTo(current_version) > 0) {
+ current_version = base::Version(last_installed_version);
+ }
// If the quick roll forward update slot candidate doesn't exist, continue
// with update check.
#endif
@@ -305,6 +313,24 @@
request_sender_->Cancel();
}
}
+
+bool UpdateCheckerImpl::SkipUpdate(
+ const CobaltExtensionInstallationManagerApi* installation_api) {
+ auto free_space_ext = static_cast<const CobaltExtensionFreeSpaceApi*>(
+ SbSystemGetExtension(kCobaltExtensionFreeSpaceName));
+ if (!installation_api) {
+ LOG(WARNING) << "UpdaterChecker::SkipUpdate: missing installation api";
+ return false;
+ }
+ if (!free_space_ext) {
+ LOG(WARNING) << "UpdaterChecker::SkipUpdate: No FreeSpace Cobalt extension";
+ return false;
+ }
+
+ return CobaltSkipUpdate(installation_api, config_->GetMinFreeSpaceBytes(),
+ free_space_ext->MeasureFreeSpace(kSbSystemPathStorageDirectory),
+ CobaltInstallationCleanupSize(installation_api)) ;
+}
#endif
void UpdateCheckerImpl::OnRequestSenderComplete(int error,
diff --git a/src/components/update_client/update_checker.h b/src/components/update_client/update_checker.h
index 78eb968..7c7c062 100644
--- a/src/components/update_client/update_checker.h
+++ b/src/components/update_client/update_checker.h
@@ -18,6 +18,10 @@
#include "components/update_client/protocol_parser.h"
#include "url/gurl.h"
+#if defined(STARBOARD)
+#include "cobalt/extension/installation_manager.h"
+#endif
+
namespace update_client {
class Configurator;
@@ -53,6 +57,7 @@
#if defined(STARBOARD)
virtual void Cancel() = 0;
+ virtual bool SkipUpdate(const CobaltExtensionInstallationManagerApi* installation_api) = 0;
#endif
static std::unique_ptr<UpdateChecker> Create(
diff --git a/src/components/update_client/update_client.cc b/src/components/update_client/update_client.cc
index 0ba76cd..9800348 100644
--- a/src/components/update_client/update_client.cc
+++ b/src/components/update_client/update_client.cc
@@ -79,7 +79,8 @@
DCHECK(tasks_.empty());
#if defined(STARBOARD)
- LOG(INFO) << "UpdateClientImpl::~UpdateClientImpl: task_queue_.size=" << task_queue_.size() << " tasks.size=" << tasks_.size();
+ LOG(INFO) << "UpdateClientImpl::~UpdateClientImpl: task_queue_.size="
+ << task_queue_.size() << " tasks.size=" << tasks_.size();
#endif
config_ = nullptr;
@@ -175,6 +176,12 @@
void UpdateClientImpl::NotifyObservers(Observer::Events event,
const std::string& id) {
DCHECK(thread_checker_.CalledOnValidThread());
+#if defined(STARBOARD)
+ if (is_stopped_) {
+ LOG(WARNING) << "UpdateClientImpl::NotifyObservers: already stopped";
+ return;
+ }
+#endif
for (auto& observer : observer_list_)
observer.OnEvent(event, id);
}
diff --git a/src/components/update_client/update_client_errors.h b/src/components/update_client/update_client_errors.h
index db08a24..4193ace 100644
--- a/src/components/update_client/update_client_errors.h
+++ b/src/components/update_client/update_client_errors.h
@@ -105,6 +105,7 @@
// Using 21 that doesn't conflict with the exsiting error codes and stays away
// from the other codes below 20.
QUICK_ROLL_FORWARD = 21,
+ OUT_OF_SPACE = 22,
};
#endif
diff --git a/src/components/update_client/url_fetcher_downloader.cc b/src/components/update_client/url_fetcher_downloader.cc
index 4536da6..1ae692c 100644
--- a/src/components/update_client/url_fetcher_downloader.cc
+++ b/src/components/update_client/url_fetcher_downloader.cc
@@ -194,7 +194,7 @@
#endif
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
#if defined(STARBOARD)
- cobalt_slot_management_.CleanupAllDrainFiles(download_dir_);
+ cobalt_slot_management_.CleanupAllDrainFiles();
#endif
Result result;
#if defined(STARBOARD)
diff --git a/src/glimp/egl/display.cc b/src/glimp/egl/display.cc
index f338b51..10b9fad 100644
--- a/src/glimp/egl/display.cc
+++ b/src/glimp/egl/display.cc
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All Rights Reserved.
+ * Copyright 2015 The Cobalt Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,10 +22,38 @@
#include "glimp/egl/config.h"
#include "glimp/egl/error.h"
#include "starboard/common/log.h"
+#include "starboard/event.h"
namespace glimp {
namespace egl {
+const SbTime kSubmitDoneDelay = kSbTimeSecond / 60;
+
+// Don't repeat the submitDone callback during suspension
+// until specified by eglTerminate.
+bool Display::repeat_submit_done_during_suspend = false;
+
+namespace {
+void ScheduleSubmitDoneCallback(void* context) {
+ if (Display::repeat_submit_done_during_suspend) {
+ DisplayImpl::CallSubmitDone();
+ Display::RepeatSubmitDoneDuringSuspend();
+ }
+}
+} // namespace
+
+void Display::RepeatSubmitDoneDuringSuspend() {
+ static SbEventId submit_done_repeating_callback_event = kSbEventIdInvalid;
+ if (Display::repeat_submit_done_during_suspend) {
+ submit_done_repeating_callback_event =
+ SbEventSchedule(&ScheduleSubmitDoneCallback, NULL, kSubmitDoneDelay);
+ } else {
+ if (submit_done_repeating_callback_event != kSbEventIdInvalid) {
+ SbEventCancel(submit_done_repeating_callback_event);
+ }
+ }
+}
+
Display::Display(nb::scoped_ptr<DisplayImpl> display_impl)
: impl_(display_impl.Pass()) {}
diff --git a/src/glimp/egl/display.h b/src/glimp/egl/display.h
index 8fc432d..b9333d6 100644
--- a/src/glimp/egl/display.h
+++ b/src/glimp/egl/display.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All Rights Reserved.
+ * Copyright 2015 The Cobalt Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,6 +35,9 @@
// upon construction.
class Display {
public:
+ static void RepeatSubmitDoneDuringSuspend();
+ static bool repeat_submit_done_during_suspend;
+
// In order to create a display, it must have a platform-specific
// implementation injected into it, where many methods will forward to.
explicit Display(nb::scoped_ptr<DisplayImpl> display_impl);
diff --git a/src/glimp/egl/display_impl.h b/src/glimp/egl/display_impl.h
index d98efb9..711f024 100644
--- a/src/glimp/egl/display_impl.h
+++ b/src/glimp/egl/display_impl.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All Rights Reserved.
+ * Copyright 2015 The Cobalt Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,6 +54,9 @@
// To be implemented by each implementing platform.
static nb::scoped_ptr<DisplayImpl> Create(EGLNativeDisplayType display_id);
+ // Submit done call
+ static void CallSubmitDone();
+
// Returns the EGL major and minor versions, if they are not NULL.
// Called by eglInitialize():
// https://www.khronos.org/registry/egl/sdk/docs/man/html/eglInitialize.xhtml
diff --git a/src/glimp/entry_points/egl.cc b/src/glimp/entry_points/egl.cc
index 4b6d649..71ad8be 100644
--- a/src/glimp/entry_points/egl.cc
+++ b/src/glimp/entry_points/egl.cc
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All Rights Reserved.
+ * Copyright 2015 The Cobalt Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -197,6 +197,8 @@
display->GetVersionInfo(major, minor);
egl::SetError(EGL_SUCCESS);
+ egl::Display::repeat_submit_done_during_suspend = false;
+ egl::Display::RepeatSubmitDoneDuringSuspend();
return true;
}
@@ -264,6 +266,8 @@
egl::DisplayRegistry::TerminateDisplay(dpy);
egl::SetError(EGL_SUCCESS);
+ egl::Display::repeat_submit_done_during_suspend = true;
+ egl::Display::RepeatSubmitDoneDuringSuspend();
return true;
}
diff --git a/src/net/cookies/cookie_monster_unittest.cc b/src/net/cookies/cookie_monster_unittest.cc
index c13af89..b137fb5 100644
--- a/src/net/cookies/cookie_monster_unittest.cc
+++ b/src/net/cookies/cookie_monster_unittest.cc
@@ -510,8 +510,8 @@
EXPECT_EQ(expected_secure, num_secure);
// Validate each priority.
- size_t expected_count[3] = {
- expected_low_count, expected_medium_count, expected_high_count};
+ size_t expected_count[3] = {expected_low_count, expected_medium_count,
+ expected_high_count};
for (int i = 0; i < 3; ++i) {
size_t num_for_priority =
surviving_id_list[0][i].size() + surviving_id_list[1][i].size();
@@ -1049,7 +1049,7 @@
TEST_F(DeferredCookieTaskTest, DeferredGetCookieList) {
DeclareLoadedCookie(http_www_foo_.url(),
- "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ "X=1; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
Time::Now() + TimeDelta::FromDays(3));
MockGetCookieListCallback get_cookie_list_callback;
@@ -1141,7 +1141,7 @@
TEST_F(DeferredCookieTaskTest, DeferredGetAllCookies) {
DeclareLoadedCookie(http_www_foo_.url(),
- "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ "X=1; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
Time::Now() + TimeDelta::FromDays(3));
MockGetCookieListCallback get_cookie_list_callback;
@@ -1163,7 +1163,7 @@
TEST_F(DeferredCookieTaskTest, DeferredGetAllForUrlCookies) {
DeclareLoadedCookie(http_www_foo_.url(),
- "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ "X=1; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
Time::Now() + TimeDelta::FromDays(3));
MockGetCookieListCallback get_cookie_list_callback;
@@ -1188,7 +1188,7 @@
TEST_F(DeferredCookieTaskTest, DeferredGetAllForUrlWithOptionsCookies) {
DeclareLoadedCookie(http_www_foo_.url(),
- "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ "X=1; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
Time::Now() + TimeDelta::FromDays(3));
MockGetCookieListCallback get_cookie_list_callback;
@@ -1312,7 +1312,7 @@
// being dispatched go to the end of the queue.
TEST_F(DeferredCookieTaskTest, DeferredTaskOrder) {
DeclareLoadedCookie(http_www_foo_.url(),
- "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ "X=1; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
Time::Now() + TimeDelta::FromDays(3));
MockGetCookieListCallback get_cookie_list_callback;
@@ -1367,7 +1367,7 @@
// Create a persistent cookie.
EXPECT_TRUE(SetCookie(
cm.get(), http_www_foo_.url(),
- std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-62 22:50:13 GMT"));
ASSERT_EQ(1u, store->commands().size());
EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[0].type);
@@ -1762,20 +1762,20 @@
// the import.
AddCookieToList(GURL("http://www.foo.com"),
- "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ "X=1; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
Time::Now() + TimeDelta::FromDays(3), &initial_cookies);
AddCookieToList(GURL("http://www.foo.com"),
- "X=2; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ "X=2; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
Time::Now() + TimeDelta::FromDays(1), &initial_cookies);
// ===> This one is the WINNER (biggest creation time). <====
AddCookieToList(GURL("http://www.foo.com"),
- "X=3; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ "X=3; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
Time::Now() + TimeDelta::FromDays(4), &initial_cookies);
AddCookieToList(GURL("http://www.foo.com"),
- "X=4; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ "X=4; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
Time::Now(), &initial_cookies);
// Insert 2 cookies with name "X" on path "/2", with varying creation
@@ -1783,16 +1783,16 @@
// ===> This one is the WINNER (biggest creation time). <====
AddCookieToList(GURL("http://www.foo.com"),
- "X=a1; path=/2; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ "X=a1; path=/2; expires=Mon, 18-Apr-62 22:50:14 GMT",
Time::Now() + TimeDelta::FromDays(9), &initial_cookies);
AddCookieToList(GURL("http://www.foo.com"),
- "X=a2; path=/2; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ "X=a2; path=/2; expires=Mon, 18-Apr-62 22:50:14 GMT",
Time::Now() + TimeDelta::FromDays(2), &initial_cookies);
// Insert 1 cookie with name "Y" on path "/".
AddCookieToList(GURL("http://www.foo.com"),
- "Y=a; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ "Y=a; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
Time::Now() + TimeDelta::FromDays(10), &initial_cookies);
// Inject our initial cookies into the mock PersistentCookieStore.
@@ -2654,7 +2654,7 @@
// Add a cookie.
EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(),
- "A=B; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ "A=B; expires=Mon, 18-Apr-62 22:50:13 GMT"));
this->MatchCookieLines("A=B", GetCookies(cm.get(), http_www_foo_.url()));
ASSERT_EQ(1u, store->commands().size());
EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[0].type);
@@ -2667,13 +2667,13 @@
// Add a cookie.
EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(),
- "A=B; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ "A=B; expires=Mon, 18-Apr-62 22:50:13 GMT"));
this->MatchCookieLines("A=B", GetCookies(cm.get(), http_www_foo_.url()));
ASSERT_EQ(3u, store->commands().size());
EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[2].type);
// Overwrite it.
EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(),
- "A=Foo; expires=Mon, 18-Apr-22 22:50:14 GMT"));
+ "A=Foo; expires=Mon, 18-Apr-62 22:50:14 GMT"));
this->MatchCookieLines("A=Foo", GetCookies(cm.get(), http_www_foo_.url()));
ASSERT_EQ(5u, store->commands().size());
EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[3].type);
@@ -3283,12 +3283,12 @@
// Set up a set of cookies with a duplicate.
std::vector<std::unique_ptr<CanonicalCookie>> initial_cookies;
AddCookieToList(GURL("http://www.foo.com"),
- "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ "X=1; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
base::Time::Now() + base::TimeDelta::FromDays(3),
&initial_cookies);
AddCookieToList(GURL("http://www.foo.com"),
- "X=2; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ "X=2; path=/; expires=Mon, 18-Apr-62 22:50:14 GMT",
base::Time::Now() + base::TimeDelta::FromDays(1),
&initial_cookies);
diff --git a/src/net/cookies/cookie_store_unittest.h b/src/net/cookies/cookie_store_unittest.h
index 6d24c79..ed997f4 100644
--- a/src/net/cookies/cookie_store_unittest.h
+++ b/src/net/cookies/cookie_store_unittest.h
@@ -1207,7 +1207,7 @@
// Create a persistent cookie.
EXPECT_TRUE(this->SetCookie(
cs, this->http_www_foo_.url(),
- std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-62 22:50:13 GMT"));
this->MatchCookieLines("A=B",
this->GetCookies(cs, this->http_www_foo_.url()));
@@ -1220,7 +1220,7 @@
// Create a persistent cookie.
EXPECT_TRUE(this->SetCookie(
cs, this->http_www_foo_.url(),
- std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-62 22:50:13 GMT"));
this->MatchCookieLines("A=B",
this->GetCookies(cs, this->http_www_foo_.url()));
// Delete it via Expires.
@@ -1233,13 +1233,13 @@
// Create a persistent cookie.
EXPECT_TRUE(this->SetCookie(
cs, this->http_www_foo_.url(),
- std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-62 22:50:13 GMT"));
this->MatchCookieLines("A=B",
this->GetCookies(cs, this->http_www_foo_.url()));
// Check that it is not deleted with significant enough clock skew.
base::Time server_time;
- EXPECT_TRUE(base::Time::FromString("Sun, 17-Apr-1977 22:50:13 GMT",
- &server_time));
+ EXPECT_TRUE(
+ base::Time::FromString("Sun, 17-Apr-1977 22:50:13 GMT", &server_time));
EXPECT_TRUE(this->SetCookieWithServerTime(
cs, this->http_www_foo_.url(),
std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-1977 22:50:13 GMT",
@@ -1250,7 +1250,7 @@
// Create a persistent cookie.
EXPECT_TRUE(this->SetCookie(
cs, this->http_www_foo_.url(),
- std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-62 22:50:13 GMT"));
this->MatchCookieLines("A=B",
this->GetCookies(cs, this->http_www_foo_.url()));
// Delete it via Expires, with a unix epoch of 0.
@@ -1270,7 +1270,7 @@
// Set a persistent cookie.
EXPECT_TRUE(this->SetCookie(cs, this->http_www_foo_.url(),
- "C=D; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ "C=D; expires=Mon, 18-Apr-62 22:50:13 GMT"));
EXPECT_EQ(2u, this->GetAllCookies(cs).size());
@@ -1420,12 +1420,12 @@
// Insert a cookie "a" for path "/path1"
EXPECT_TRUE(this->SetCookie(cs, url_foo,
"a=val1; path=/path1; "
- "expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ "expires=Mon, 18-Apr-62 22:50:13 GMT"));
// Insert a cookie "b" for path "/path1"
EXPECT_TRUE(this->SetCookie(cs, url_foo,
"b=val1; path=/path1; "
- "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+ "expires=Mon, 18-Apr-62 22:50:14 GMT"));
// Insert a cookie "b" for path "/path1", that is httponly. This should
// overwrite the non-http-only version.
@@ -1433,26 +1433,26 @@
allow_httponly.set_include_httponly();
EXPECT_TRUE(this->SetCookieWithOptions(cs, url_foo,
"b=val2; path=/path1; httponly; "
- "expires=Mon, 18-Apr-22 22:50:14 GMT",
+ "expires=Mon, 18-Apr-62 22:50:14 GMT",
allow_httponly));
// Insert a cookie "a" for path "/path1". This should overwrite.
EXPECT_TRUE(this->SetCookie(cs, url_foo,
"a=val33; path=/path1; "
- "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+ "expires=Mon, 18-Apr-62 22:50:14 GMT"));
// Insert a cookie "a" for path "/path2". This should NOT overwrite
// cookie "a", since the path is different.
EXPECT_TRUE(this->SetCookie(cs, url_foo,
"a=val9; path=/path2; "
- "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+ "expires=Mon, 18-Apr-62 22:50:14 GMT"));
// Insert a cookie "a" for path "/path1", but this time for "chromium.org".
// Although the name and path match, the hostnames do not, so shouldn't
// overwrite.
EXPECT_TRUE(this->SetCookie(cs, url_chromium,
"a=val99; path=/path1; "
- "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+ "expires=Mon, 18-Apr-62 22:50:14 GMT"));
if (TypeParam::supports_http_only) {
this->MatchCookieLines(
@@ -1654,7 +1654,7 @@
EXPECT_TRUE(this->SetCookie(
cs, this->http_www_foo_.url(),
this->http_www_foo_.Format("C=D; path=/; domain=%D;"
- "expires=Mon, 18-Apr-22 22:50:13 GMT")));
+ "expires=Mon, 18-Apr-62 22:50:13 GMT")));
this->MatchCookieLines("A=B; C=D",
this->GetCookies(cs, this->http_www_foo_.url()));
// Delete the session cookie.
diff --git a/src/net/test/test_with_scoped_task_environment.h b/src/net/test/test_with_scoped_task_environment.h
index 4ec5586..7abb7ea 100644
--- a/src/net/test/test_with_scoped_task_environment.h
+++ b/src/net/test/test_with_scoped_task_environment.h
@@ -18,7 +18,7 @@
namespace net {
// Inherit from this class if a ScopedTaskEnvironment is needed in a test.
-// Use in class hierachies where inheritance from ::testing::Test at the same
+// Use in class hierarchies where inheritance from ::testing::Test at the same
// time is not desirable or possible (for example, when inheriting from
// PlatformTest at the same time).
class WithScopedTaskEnvironment {
@@ -34,16 +34,30 @@
return scoped_task_environment_.MainThreadHasPendingTask();
}
+ // Executes all tasks that have no remaining delay. Tasks with a remaining
+ // delay greater than zero will remain enqueued, and no virtual time will
+ // elapse.
void RunUntilIdle() { scoped_task_environment_.RunUntilIdle(); }
+ // Fast-forwards virtual time by |delta|, causing all tasks with a remaining
+ // delay less than or equal to |delta| to be executed. |delta| must be
+ // non-negative.
void FastForwardBy(base::TimeDelta delta) {
scoped_task_environment_.FastForwardBy(delta);
}
+ // Fast-forwards virtual time by |delta| but not causing any task execution.
+ void AdvanceMockTickClock(base::TimeDelta delta) {
+ scoped_task_environment_.AdvanceMockTickClock(delta);
+ }
+
+ // Fast-forwards virtual time just until all tasks are executed.
void FastForwardUntilNoTasksRemain() {
scoped_task_environment_.FastForwardUntilNoTasksRemain();
}
+ // Returns a TickClock that uses the virtual time ticks of |this| as its tick
+ // source. The returned TickClock will hold a reference to |this|.
const base::TickClock* GetMockTickClock() WARN_UNUSED_RESULT {
return scoped_task_environment_.GetMockTickClock();
}
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
index d9082da..a858ea3 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
@@ -25,6 +25,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.util.Pair;
+import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewParent;
import android.widget.FrameLayout;
@@ -33,7 +34,6 @@
import dev.cobalt.util.DisplayUtil;
import dev.cobalt.util.Log;
import dev.cobalt.util.UsedByNative;
-import java.security.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -143,6 +143,10 @@
if (VideoSurfaceView.getCurrentSurface() != null) {
forceCreateNewVideoSurfaceView = true;
}
+
+ // Set the SurfaceView to fullscreen.
+ View rootView = getWindow().getDecorView();
+ setVideoSurfaceBounds(0, 0, rootView.getWidth(), rootView.getHeight());
}
@Override
@@ -298,6 +302,10 @@
}
public void setVideoSurfaceBounds(final int x, final int y, final int width, final int height) {
+ if (width == 0 || height == 0) {
+ // The SurfaceView should be covered by our UI layer in this case.
+ return;
+ }
runOnUiThread(
new Runnable() {
@Override
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ResourceOverlay.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ResourceOverlay.java
new file mode 100644
index 0000000..b80c6c8
--- /dev/null
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ResourceOverlay.java
@@ -0,0 +1,36 @@
+// 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.
+
+package dev.cobalt.coat;
+
+import android.content.Context;
+import dev.cobalt.util.UsedByNative;
+
+/**
+ * A class for managing the resource overlays of Cobalt. Client can turn on certain feature by
+ * setting the resource overlay.
+ */
+public class ResourceOverlay {
+ // To facilitate maintenance, these member names should match what is in the
+ // resource XML file.
+ @SuppressWarnings("MemberName")
+ @UsedByNative
+ public final boolean supports_spherical_videos;
+
+ public ResourceOverlay(Context context) {
+ // Load the values for all Overlay variables.
+ this.supports_spherical_videos =
+ context.getResources().getBoolean(R.bool.supports_spherical_videos);
+ }
+}
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
index 408ada0..95b4457 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
@@ -51,8 +51,10 @@
import dev.cobalt.util.Log;
import dev.cobalt.util.UsedByNative;
import java.lang.reflect.Method;
+import java.util.Calendar;
import java.util.HashMap;
import java.util.Locale;
+import java.util.TimeZone;
/** Implementation of the required JNI methods called by the Starboard C++ code. */
public class StarboardBridge {
@@ -72,6 +74,7 @@
private AudioPermissionRequester audioPermissionRequester;
private KeyboardEditor keyboardEditor;
private NetworkStatus networkStatus;
+ private ResourceOverlay resourceOverlay;
static {
// Even though NativeActivity already loads our library from C++,
@@ -97,6 +100,7 @@
private final HashMap<String, CobaltService.Factory> cobaltServiceFactories = new HashMap<>();
private final HashMap<String, CobaltService> cobaltServices = new HashMap<>();
+ private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone("America/Los_Angeles");
private final long timeNanosecondsPerMicrosecond = 1000;
public StarboardBridge(
@@ -124,6 +128,7 @@
new CobaltMediaSession(appContext, activityHolder, audioOutputManager);
this.audioPermissionRequester = new AudioPermissionRequester(appContext, activityHolder);
this.networkStatus = new NetworkStatus(appContext);
+ this.resourceOverlay = new ResourceOverlay(appContext);
}
private native boolean nativeInitialize();
@@ -363,7 +368,9 @@
return ttsHelper;
}
- /** @return A new CaptionSettings object with the current system caption settings. */
+ /**
+ * @return A new CaptionSettings object with the current system caption settings.
+ */
@SuppressWarnings("unused")
@UsedByNative
CaptionSettings getCaptionSettings() {
@@ -381,6 +388,18 @@
@SuppressWarnings("unused")
@UsedByNative
+ String getTimeZoneId() {
+ Locale locale = Locale.getDefault();
+ Calendar calendar = Calendar.getInstance(locale);
+ TimeZone timeZone = DEFAULT_TIME_ZONE;
+ if (calendar != null) {
+ timeZone = calendar.getTimeZone();
+ }
+ return timeZone.getID();
+ }
+
+ @SuppressWarnings("unused")
+ @UsedByNative
SizeF getDisplayDpi() {
return DisplayUtil.getDisplayDpi();
}
@@ -391,6 +410,12 @@
return DisplayUtil.getSystemDisplaySize();
}
+ @SuppressWarnings("unused")
+ @UsedByNative
+ public ResourceOverlay getResourceOverlay() {
+ return resourceOverlay;
+ }
+
@Nullable
private static String getSystemProperty(String name) {
try {
@@ -486,7 +511,9 @@
return audioManager.isMicrophoneMute();
}
- /** @return true if we have an active network connection and it's on an wireless network. */
+ /**
+ * @return true if we have an active network connection and it's on an wireless network.
+ */
@SuppressWarnings("unused")
@UsedByNative
boolean isCurrentNetworkWireless() {
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
index f265e98..5a13b01 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
@@ -60,14 +60,18 @@
int channelCount,
int preferredBufferSizeInBytes,
boolean enableAudioDeviceCallback,
- int tunnelModeAudioSessionId) {
+ boolean enablePcmContentTypeMovie,
+ int tunnelModeAudioSessionId,
+ boolean isWebAudio) {
AudioTrackBridge audioTrackBridge =
new AudioTrackBridge(
sampleType,
sampleRate,
channelCount,
preferredBufferSizeInBytes,
- tunnelModeAudioSessionId);
+ enablePcmContentTypeMovie,
+ tunnelModeAudioSessionId,
+ isWebAudio);
if (!audioTrackBridge.isAudioTrackValid()) {
Log.e(TAG, "AudioTrackBridge has invalid audio track");
return null;
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
index 0001fde..0a7270e 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
@@ -64,7 +64,9 @@
int sampleRate,
int channelCount,
int preferredBufferSizeInBytes,
- int tunnelModeAudioSessionId) {
+ boolean enablePcmContentTypeMovie,
+ int tunnelModeAudioSessionId,
+ boolean isWebAudio) {
tunnelModeEnabled = tunnelModeAudioSessionId != -1;
int channelConfig;
@@ -109,15 +111,13 @@
.build();
} else {
// TODO: Support ENCODING_E_AC3_JOC for api level 28 or later.
- final boolean is_surround =
+ final boolean isSurround =
sampleType == AudioFormat.ENCODING_AC3 || sampleType == AudioFormat.ENCODING_E_AC3;
- // TODO: We start to enforce |CONTENT_TYPE_MOVIE| for surround playback, investigate if we
- // can use |CONTENT_TYPE_MOVIE| for all non-surround AudioTrack used by video
- // playback.
+ final boolean useContentTypeMovie = isSurround || (!isWebAudio && enablePcmContentTypeMovie);
attributes =
new AudioAttributes.Builder()
.setContentType(
- is_surround
+ useContentTypeMovie
? AudioAttributes.CONTENT_TYPE_MOVIE
: AudioAttributes.CONTENT_TYPE_MUSIC)
.setUsage(AudioAttributes.USAGE_MEDIA)
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
index e47ce31..41a11e4 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
@@ -588,6 +588,7 @@
String mime,
boolean mustSupportSecure,
boolean mustSupportSoftwareCodec,
+ boolean forceImprovedSupportCheck,
int width,
int height,
int fps,
@@ -609,6 +610,7 @@
mustSupportHdr,
mustSupportSoftwareCodec,
mustSupportTunneled,
+ forceImprovedSupportCheck,
0,
0,
0,
@@ -622,6 +624,7 @@
false /* mustSupportHdr */,
mustSupportSoftwareCodec,
mustSupportTunneled,
+ forceImprovedSupportCheck,
0,
0,
0,
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
index e2632dd..c71644d 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
@@ -397,6 +397,7 @@
boolean mustSupportSecure,
boolean mustSupportHdr,
boolean mustSupportTunnelMode,
+ boolean forceImprovedSupportCheck,
int frameWidth,
int frameHeight,
int bitrate,
@@ -408,6 +409,7 @@
mustSupportHdr,
false /* mustSupportSoftwareCodec */,
mustSupportTunnelMode,
+ forceImprovedSupportCheck,
frameWidth,
frameHeight,
bitrate,
@@ -433,7 +435,8 @@
*/
@SuppressWarnings("unused")
@UsedByNative
- public static boolean hasHdrCapableVideoDecoder(String mimeType) {
+ public static boolean hasHdrCapableVideoDecoder(
+ String mimeType, boolean forceImprovedSupportCheck) {
// VP9Profile* values were not added until API level 24. See
// https://developer.android.com/reference/android/media/MediaCodecInfo.CodecProfileLevel.html.
if (Build.VERSION.SDK_INT < 24) {
@@ -446,7 +449,8 @@
}
FindVideoDecoderResult findVideoDecoderResult =
- findVideoDecoder(mimeType, false, true, false, false, 0, 0, 0, 0);
+ findVideoDecoder(
+ mimeType, false, true, false, false, forceImprovedSupportCheck, 0, 0, 0, 0);
return isHdrCapableVideoDecoder(mimeType, findVideoDecoderResult);
}
@@ -487,6 +491,7 @@
boolean mustSupportHdr,
boolean mustSupportSoftwareCodec,
boolean mustSupportTunnelMode,
+ boolean forceImprovedSupportCheck,
int frameWidth,
int frameHeight,
int bitrate,
@@ -496,7 +501,8 @@
String.format(
"Searching for video decoder with parameters mimeType: %s, secure: %b, frameWidth: %d,"
+ " frameHeight: %d, bitrate: %d, fps: %d, mustSupportHdr: %b,"
- + " mustSupportSoftwareCodec: %b, mustSupportTunnelMode: %b",
+ + " mustSupportSoftwareCodec: %b, mustSupportTunnelMode: %b,"
+ + " forceImprovedSupportCheck: %b",
mimeType,
mustSupportSecure,
frameWidth,
@@ -505,7 +511,8 @@
fps,
mustSupportHdr,
mustSupportSoftwareCodec,
- mustSupportTunnelMode));
+ mustSupportTunnelMode,
+ forceImprovedSupportCheck));
Log.v(
TAG,
String.format(
@@ -609,8 +616,9 @@
// Enable the improved support check based on more specific APIs, like isSizeSupported() or
// areSizeAndRateSupported(), for 8k content. These APIs are theoretically more accurate,
// but we are unsure about their level of support on various Android TV platforms.
- final boolean enableImprovedCheck = frameWidth > 3840 || frameHeight > 2160;
- if (enableImprovedCheck) {
+ final boolean enableImprovedSupportCheck =
+ forceImprovedSupportCheck || (frameWidth > 3840 || frameHeight > 2160);
+ if (enableImprovedSupportCheck) {
if (frameWidth != 0 && frameHeight != 0) {
if (!videoCapabilities.isSizeSupported(frameWidth, frameHeight)) {
String format = "Rejecting %s, reason: width %s is not compatible with height %d";
@@ -651,7 +659,7 @@
}
Range<Integer> supportedFrameRates = videoCapabilities.getSupportedFrameRates();
- if (enableImprovedCheck) {
+ if (enableImprovedSupportCheck) {
if (fps != 0) {
if (frameHeight != 0 && frameWidth != 0) {
if (!videoCapabilities.areSizeAndRateSupported(frameWidth, frameHeight, fps)) {
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
index abb56c8..c6db123 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
@@ -76,6 +76,14 @@
@SuppressWarnings("deprecation")
private static final int MEDIA_DRM_EVENT_KEY_EXPIRED = MediaDrm.EVENT_KEY_EXPIRED;
+ // Deprecated in API 23, but we still log it on earlier devices.
+ @SuppressWarnings("deprecation")
+ private static final int MEDIA_DRM_EVENT_PROVISION_REQUIRED = MediaDrm.EVENT_PROVISION_REQUIRED;
+
+ // Added in API 23.
+ private static final int MEDIA_DRM_EVENT_SESSION_RECLAIMED =
+ Build.VERSION.SDK_INT >= 23 ? MediaDrm.EVENT_SESSION_RECLAIMED : 5;
+
private MediaDrm mMediaDrm;
private long mNativeMediaDrmBridge;
private UUID mSchemeUUID;
@@ -400,47 +408,48 @@
String.format("EventListener: Invalid session %s", bytesToHexString(sessionId)));
return;
}
- switch (event) {
- case MediaDrm.EVENT_KEY_REQUIRED:
- Log.d(TAG, "MediaDrm.EVENT_KEY_REQUIRED");
- String mime = mSessionIds.get(ByteBuffer.wrap(sessionId));
- MediaDrm.KeyRequest request = null;
- try {
- request = getKeyRequest(sessionId, data, mime);
- } catch (NotProvisionedException e) {
- Log.e(TAG, "Device not provisioned", e);
- if (!attemptProvisioning()) {
- Log.e(TAG, "Failed to provision device when responding to EVENT_KEY_REQUIRED");
- return;
- }
- // If we supposedly successfully provisioned ourselves, then try to create a
- // request again.
- try {
- request = getKeyRequest(sessionId, data, mime);
- } catch (NotProvisionedException e2) {
- Log.e(
- TAG,
- "Device still not provisioned after supposedly successful provisioning",
- e2);
- return;
- }
- }
- if (request != null) {
- onSessionMessage(SB_DRM_TICKET_INVALID, sessionId, request);
- } else {
- Log.e(TAG, "EventListener: getKeyRequest failed.");
+
+ if (event == MediaDrm.EVENT_KEY_REQUIRED) {
+ Log.d(TAG, "MediaDrm.EVENT_KEY_REQUIRED");
+ String mime = mSessionIds.get(ByteBuffer.wrap(sessionId));
+ MediaDrm.KeyRequest request = null;
+ try {
+ request = getKeyRequest(sessionId, data, mime);
+ } catch (NotProvisionedException e) {
+ Log.e(TAG, "Device not provisioned", e);
+ if (!attemptProvisioning()) {
+ Log.e(TAG, "Failed to provision device when responding to EVENT_KEY_REQUIRED");
return;
}
- break;
- case MEDIA_DRM_EVENT_KEY_EXPIRED:
- Log.d(TAG, "MediaDrm.EVENT_KEY_EXPIRED");
- break;
- case MediaDrm.EVENT_VENDOR_DEFINED:
- Log.d(TAG, "MediaDrm.EVENT_VENDOR_DEFINED");
- break;
- default:
- Log.e(TAG, "Invalid DRM event " + event);
+ // If we supposedly successfully provisioned ourselves, then try to create a
+ // request again.
+ try {
+ request = getKeyRequest(sessionId, data, mime);
+ } catch (NotProvisionedException e2) {
+ Log.e(
+ TAG,
+ "Device still not provisioned after supposedly successful provisioning",
+ e2);
+ return;
+ }
+ }
+ if (request != null) {
+ onSessionMessage(SB_DRM_TICKET_INVALID, sessionId, request);
+ } else {
+ Log.e(TAG, "EventListener: getKeyRequest failed.");
return;
+ }
+ } else if (event == MEDIA_DRM_EVENT_KEY_EXPIRED) {
+ Log.d(TAG, "MediaDrm.EVENT_KEY_EXPIRED");
+ } else if (event == MediaDrm.EVENT_VENDOR_DEFINED) {
+ Log.d(TAG, "MediaDrm.EVENT_VENDOR_DEFINED");
+ } else if (event == MEDIA_DRM_EVENT_PROVISION_REQUIRED) {
+ Log.d(TAG, "MediaDrm.EVENT_PROVISION_REQUIRED");
+ } else if (event == MEDIA_DRM_EVENT_SESSION_RECLAIMED) {
+ Log.d(TAG, "MediaDrm.EVENT_SESSION_RECLAIMED");
+ } else {
+ Log.e(TAG, "Invalid DRM event " + event);
+ return;
}
}
});
diff --git a/src/starboard/android/apk/app/src/main/res/values/overlayable.xml b/src/starboard/android/apk/app/src/main/res/values/overlayable.xml
new file mode 100644
index 0000000..e892f68
--- /dev/null
+++ b/src/starboard/android/apk/app/src/main/res/values/overlayable.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<resources xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="DuplicateDefinition">
+ <overlayable name="CobaltOverlay" >
+ <policy type="vendor|product|system">
+ <!-- This flag indicates whether 360 videos can be played. -->
+ <item type="bool" name="supports_spherical_videos" />
+ </policy>
+ </overlayable>
+</resources>
diff --git a/src/starboard/android/apk/app/src/main/res/values/rro_variables.xml b/src/starboard/android/apk/app/src/main/res/values/rro_variables.xml
new file mode 100644
index 0000000..874c137
--- /dev/null
+++ b/src/starboard/android/apk/app/src/main/res/values/rro_variables.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!-- This flag indicates whether 360 videos can be played. -->
+ <bool name="supports_spherical_videos" tools:ignore="DuplicateDefinition">true</bool>
+</resources>
diff --git a/src/starboard/android/apk/build.gradle b/src/starboard/android/apk/build.gradle
index 27edbf5..302652a 100644
--- a/src/starboard/android/apk/build.gradle
+++ b/src/starboard/android/apk/build.gradle
@@ -20,7 +20,7 @@
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.6.3'
+ classpath 'com.android.tools.build:gradle:4.1.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -49,7 +49,7 @@
// Move the 'buildDir' for all projects into sub-directories of a shared top-level build directory,
// which is either the root's original 'buildDir' or a custom location when building for platform
// deploy. Note that the platform deploy action sets a custom 'cobaltGradleDir' property rather
-// than setting 'buildDir' directly on the command line since Gradle trys to get smart about
+// than setting 'buildDir' directly on the command line since Gradle tries to get smart about
// 'buildDir' which can end up putting it at the wrong depth in the file system.
def rootBuildDir = hasProperty('cobaltGradleDir') ? new File(cobaltGradleDir, 'build') : buildDir
allprojects { buildDir = new File(rootBuildDir, project.name).canonicalFile }
diff --git a/src/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties b/src/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties
index 0ebb310..186b715 100644
--- a/src/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties
+++ b/src/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/src/starboard/android/examples/overlays/AndroidManifest.xml b/src/starboard/android/examples/overlays/AndroidManifest.xml
new file mode 100644
index 0000000..a88fe39
--- /dev/null
+++ b/src/starboard/android/examples/overlays/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="dev.cobalt.coat.rro">
+
+ <application android:hasCode="false" />
+ <overlay android:targetPackage="dev.cobalt.coat"
+ android:targetName="CobaltOverlay"
+ android:resourcesMap="@xml/overlays"/>
+</manifest>
diff --git a/src/starboard/android/examples/overlays/res/xml/overlays.xml b/src/starboard/android/examples/overlays/res/xml/overlays.xml
new file mode 100644
index 0000000..7a3f011
--- /dev/null
+++ b/src/starboard/android/examples/overlays/res/xml/overlays.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<overlay>
+ <!-- This flag indicates whether 360 videos can be played. -->
+ <item target="bool/supports_spherical_videos" value="true" />
+</overlay>
diff --git a/src/starboard/android/shared/BUILD.gn b/src/starboard/android/shared/BUILD.gn
index dd4f76a..91baf57 100644
--- a/src/starboard/android/shared/BUILD.gn
+++ b/src/starboard/android/shared/BUILD.gn
@@ -336,6 +336,8 @@
"file_truncate.cc",
"file_write.cc",
"get_home_directory.cc",
+ "graphics.cc",
+ "graphics.h",
"input_events_generator.cc",
"input_events_generator.h",
"jni_env_ext.cc",
diff --git a/src/starboard/android/shared/application_android.cc b/src/starboard/android/shared/application_android.cc
index f2986e7..f0348d5 100644
--- a/src/starboard/android/shared/application_android.cc
+++ b/src/starboard/android/shared/application_android.cc
@@ -116,9 +116,20 @@
keyboard_inject_writefd_ = pipefd[1];
ALooper_addFd(looper_, keyboard_inject_readfd_, kLooperIdKeyboardInject,
ALOOPER_EVENT_INPUT, NULL, NULL);
+
+ JniEnvExt* env = JniEnvExt::Get();
+ jobject local_ref = env->CallStarboardObjectMethodOrAbort(
+ "getResourceOverlay", "()Ldev/cobalt/coat/ResourceOverlay;");
+ resource_overlay_ = env->ConvertLocalRefToGlobalRef(local_ref);
}
ApplicationAndroid::~ApplicationAndroid() {
+ // Release the global reference.
+ if (resource_overlay_) {
+ JniEnvExt* env = JniEnvExt::Get();
+ env->DeleteGlobalRef(resource_overlay_);
+ resource_overlay_ = nullptr;
+ }
ALooper_removeFd(looper_, android_command_readfd_);
close(android_command_readfd_);
close(android_command_writefd_);
@@ -681,6 +692,44 @@
ApplicationAndroid::Get()->SendDateTimeConfigurationChangedEvent();
}
+int ApplicationAndroid::GetOverlayedIntValue(const char* var_name) {
+ ScopedLock lock(overlay_mutex_);
+ if (overlayed_int_variables_.find(var_name) !=
+ overlayed_int_variables_.end()) {
+ return overlayed_int_variables_[var_name];
+ }
+ JniEnvExt* env = JniEnvExt::Get();
+ jint value = env->GetIntFieldOrAbort(resource_overlay_, var_name, "I");
+ overlayed_int_variables_[var_name] = value;
+ return value;
+}
+
+std::string ApplicationAndroid::GetOverlayedStringValue(const char* var_name) {
+ ScopedLock lock(overlay_mutex_);
+ if (overlayed_string_variables_.find(var_name) !=
+ overlayed_string_variables_.end()) {
+ return overlayed_string_variables_[var_name];
+ }
+ JniEnvExt* env = JniEnvExt::Get();
+ std::string value = env->GetStringStandardUTFOrAbort(
+ env->GetStringFieldOrAbort(resource_overlay_, var_name));
+ overlayed_string_variables_[var_name] = value;
+ return value;
+}
+
+bool ApplicationAndroid::GetOverlayedBoolValue(const char* var_name) {
+ ScopedLock lock(overlay_mutex_);
+ if (overlayed_bool_variables_.find(var_name) !=
+ overlayed_bool_variables_.end()) {
+ return overlayed_bool_variables_[var_name];
+ }
+ JniEnvExt* env = JniEnvExt::Get();
+ jboolean value =
+ env->GetBooleanFieldOrAbort(resource_overlay_, var_name, "Z");
+ overlayed_bool_variables_[var_name] = value;
+ return value;
+}
+
} // namespace shared
} // namespace android
} // namespace starboard
diff --git a/src/starboard/android/shared/application_android.h b/src/starboard/android/shared/application_android.h
index a1c92a6..2d147eb 100644
--- a/src/starboard/android/shared/application_android.h
+++ b/src/starboard/android/shared/application_android.h
@@ -18,6 +18,7 @@
#include <android/looper.h>
#include <android/native_window.h>
#include <string>
+#include <unordered_map>
#include <vector>
#include "starboard/android/shared/input_events_generator.h"
@@ -96,6 +97,14 @@
void SendDateTimeConfigurationChangedEvent();
+ // Methods to get the Runtime Resource Overlay variables.
+ // All RRO variables which can be retrieved here must be defined
+ // in res/values/rro_variables.xml and be loaded in
+ // dev/cobalt/coat/ResourceOverlay.java.
+ int GetOverlayedIntValue(const char* var_name);
+ std::string GetOverlayedStringValue(const char* var_name);
+ bool GetOverlayedBoolValue(const char* var_name);
+
// Methods to start/stop Media playback service.
void StartMediaPlaybackService();
void StopMediaPlaybackService();
@@ -139,6 +148,13 @@
bool last_is_accessibility_high_contrast_text_enabled_;
+ jobject resource_overlay_;
+
+ Mutex overlay_mutex_;
+ std::unordered_map<std::string, bool> overlayed_bool_variables_;
+ std::unordered_map<std::string, int> overlayed_int_variables_;
+ std::unordered_map<std::string, std::string> overlayed_string_variables_;
+
// Methods to process pipes attached to the Looper.
void ProcessAndroidCommand();
void ProcessAndroidInput();
diff --git a/src/starboard/android/shared/audio_renderer_passthrough.cc b/src/starboard/android/shared/audio_renderer_passthrough.cc
index 1f0849c..f80c44e 100644
--- a/src/starboard/android/shared/audio_renderer_passthrough.cc
+++ b/src/starboard/android/shared/audio_renderer_passthrough.cc
@@ -20,6 +20,7 @@
#include "starboard/android/shared/audio_decoder_passthrough.h"
#include "starboard/android/shared/jni_env_ext.h"
#include "starboard/android/shared/jni_utils.h"
+#include "starboard/common/string.h"
#include "starboard/memory.h"
namespace starboard {
@@ -384,7 +385,8 @@
optional<SbMediaAudioSampleType>(), // Not required in passthrough mode
audio_sample_info_.number_of_channels,
audio_sample_info_.samples_per_second, kPreferredBufferSizeInBytes,
- enable_audio_device_callback_, kTunnelModeAudioSessionId));
+ enable_audio_device_callback_, false /* enable_pcm_content_type_movie */,
+ kTunnelModeAudioSessionId, false /* is_web_audio */));
if (!audio_track_bridge->is_valid()) {
error_cb_(kSbPlayerErrorDecode, "Error creating AudioTrackBridge");
diff --git a/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc b/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
index 1c7d413..80656e2 100644
--- a/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
+++ b/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
@@ -125,7 +125,7 @@
GetSampleSize(task.sample_type),
&MinRequiredFramesTester::UpdateSourceStatusFunc,
&MinRequiredFramesTester::ConsumeFramesFunc,
- &MinRequiredFramesTester::ErrorFunc, 0, -1, false, this);
+ &MinRequiredFramesTester::ErrorFunc, 0, -1, false, false, false, this);
{
ScopedLock scoped_lock(mutex_);
wait_timeout = !condition_variable_.WaitTimed(kSbTimeSecond * 5);
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.cc b/src/starboard/android/shared/audio_track_audio_sink_type.cc
index 6af4aa6..0e9d29e 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.cc
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -83,6 +83,8 @@
SbTime start_time,
int tunnel_mode_audio_session_id,
bool enable_audio_device_callback,
+ bool enable_pcm_content_type_movie,
+ bool is_web_audio,
void* context)
: type_(type),
channels_(channels),
@@ -106,7 +108,9 @@
sampling_frequency_hz,
preferred_buffer_size_in_bytes,
enable_audio_device_callback,
- tunnel_mode_audio_session_id) {
+ enable_pcm_content_type_movie,
+ tunnel_mode_audio_session_id,
+ is_web_audio) {
SB_DCHECK(update_source_status_func_);
SB_DCHECK(consume_frames_func_);
SB_DCHECK(frame_buffer_);
@@ -420,11 +424,15 @@
const int kTunnelModeAudioSessionId = -1;
// Disable AudioDeviceCallback for WebAudio.
const bool kEnableAudioDeviceCallback = false;
+ const bool kIsWebAudio = true;
+ // Disable AudioAttributes::CONTENT_TYPE_MOVIE for WebAudio.
+ const bool kEnablePcmContentTypeMovie = false;
return Create(channels, sampling_frequency_hz, audio_sample_type,
audio_frame_storage_type, frame_buffers, frames_per_channel,
update_source_status_func, consume_frames_func, error_func,
kStartTime, kTunnelModeAudioSessionId,
- kEnableAudioDeviceCallback, context);
+ kEnableAudioDeviceCallback, kEnablePcmContentTypeMovie,
+ kIsWebAudio, context);
}
SbAudioSink AudioTrackAudioSinkType::Create(
@@ -440,6 +448,8 @@
SbTime start_media_time,
int tunnel_mode_audio_session_id,
bool enable_audio_device_callback,
+ bool enable_pcm_content_type_movie,
+ bool is_web_audio,
void* context) {
int min_required_frames = SbAudioSinkGetMinBufferSizeInFrames(
channels, audio_sample_type, sampling_frequency_hz);
@@ -451,7 +461,8 @@
frames_per_channel, preferred_buffer_size_in_bytes,
update_source_status_func, consume_frames_func, error_func,
start_media_time, tunnel_mode_audio_session_id,
- enable_audio_device_callback, context);
+ enable_audio_device_callback, enable_pcm_content_type_movie, is_web_audio,
+ context);
if (!audio_sink->IsAudioTrackValid()) {
SB_DLOG(ERROR)
<< "AudioTrackAudioSinkType::Create failed to create audio track";
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.h b/src/starboard/android/shared/audio_track_audio_sink_type.h
index adf050d..035b093 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.h
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.h
@@ -69,6 +69,8 @@
SbTime start_time,
int tunnel_mode_audio_session_id,
bool enable_audio_device_callback,
+ bool enable_pcm_content_type_movie,
+ bool is_web_audio,
void* context);
bool IsValid(SbAudioSink audio_sink) override {
@@ -112,6 +114,8 @@
SbTime start_media_time,
int tunnel_mode_audio_session_id,
bool enable_audio_device_callback,
+ bool enable_pcm_content_type_movie,
+ bool is_web_audio,
void* context);
~AudioTrackAudioSink() override;
diff --git a/src/starboard/android/shared/audio_track_bridge.cc b/src/starboard/android/shared/audio_track_bridge.cc
index 79fe83f..05b9c9b 100644
--- a/src/starboard/android/shared/audio_track_bridge.cc
+++ b/src/starboard/android/shared/audio_track_bridge.cc
@@ -40,7 +40,9 @@
int sampling_frequency_hz,
int preferred_buffer_size_in_bytes,
bool enable_audio_device_callback,
- int tunnel_mode_audio_session_id) {
+ bool enable_pcm_content_type_movie,
+ int tunnel_mode_audio_session_id,
+ bool is_web_audio) {
if (coding_type == kSbMediaAudioCodingTypePcm) {
SB_DCHECK(SbAudioSinkIsAudioSampleTypeSupported(sample_type.value()));
@@ -63,10 +65,11 @@
"getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
jobject j_audio_track_bridge = env->CallObjectMethodOrAbort(
j_audio_output_manager.Get(), "createAudioTrackBridge",
- "(IIIIZI)Ldev/cobalt/media/AudioTrackBridge;",
+ "(IIIIZZIZ)Ldev/cobalt/media/AudioTrackBridge;",
GetAudioFormatSampleType(coding_type, sample_type), sampling_frequency_hz,
channels, preferred_buffer_size_in_bytes, enable_audio_device_callback,
- tunnel_mode_audio_session_id);
+ enable_pcm_content_type_movie, tunnel_mode_audio_session_id,
+ is_web_audio);
if (!j_audio_track_bridge) {
// One of the cases that this may hit is when output happened to be switched
// to a device that doesn't support tunnel mode.
diff --git a/src/starboard/android/shared/audio_track_bridge.h b/src/starboard/android/shared/audio_track_bridge.h
index 607837d..3804b6a 100644
--- a/src/starboard/android/shared/audio_track_bridge.h
+++ b/src/starboard/android/shared/audio_track_bridge.h
@@ -42,7 +42,9 @@
int sampling_frequency_hz,
int preferred_buffer_size_in_bytes,
bool enable_audio_device_callback,
- int tunnel_mode_audio_session_id);
+ bool enable_pcm_content_type_movie,
+ int tunnel_mode_audio_session_id,
+ bool is_web_audio);
~AudioTrackBridge();
static int GetMinBufferSizeInFrames(SbMediaAudioSampleType sample_type,
diff --git a/src/starboard/android/shared/graphics.cc b/src/starboard/android/shared/graphics.cc
new file mode 100644
index 0000000..5ceed59
--- /dev/null
+++ b/src/starboard/android/shared/graphics.cc
@@ -0,0 +1,59 @@
+// 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/android/shared/graphics.h"
+
+#include "starboard/common/log.h"
+
+#include "cobalt/extension/graphics.h"
+#include "starboard/android/shared/application_android.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+namespace {
+
+float GetMaximumFrameIntervalInMilliseconds() {
+ return -1.0f;
+}
+
+float GetMinimumFrameIntervalInMilliseconds() {
+ return 16.0f;
+}
+
+bool IsMapToMeshEnabled() {
+ bool supports_spherical_videos =
+ starboard::android::shared::ApplicationAndroid::Get()
+ ->GetOverlayedBoolValue("supports_spherical_videos");
+ return supports_spherical_videos;
+}
+
+const CobaltExtensionGraphicsApi kGraphicsApi = {
+ kCobaltExtensionGraphicsName,
+ 3,
+ &GetMaximumFrameIntervalInMilliseconds,
+ &GetMinimumFrameIntervalInMilliseconds,
+ &IsMapToMeshEnabled,
+};
+
+} // namespace
+
+const void* GetGraphicsApi() {
+ return &kGraphicsApi;
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/graphics.h b/src/starboard/android/shared/graphics.h
new file mode 100644
index 0000000..34b3447
--- /dev/null
+++ b/src/starboard/android/shared/graphics.h
@@ -0,0 +1,28 @@
+// 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_ANDROID_SHARED_GRAPHICS_H_
+#define STARBOARD_ANDROID_SHARED_GRAPHICS_H_
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+const void* GetGraphicsApi();
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_GRAPHICS_H_
diff --git a/src/starboard/android/shared/media_codec_bridge.cc b/src/starboard/android/shared/media_codec_bridge.cc
index 57acdad..d663f08 100644
--- a/src/starboard/android/shared/media_codec_bridge.cc
+++ b/src/starboard/android/shared/media_codec_bridge.cc
@@ -207,6 +207,7 @@
bool require_software_codec,
int tunnel_mode_audio_session_id,
bool force_big_endian_hdr_metadata,
+ bool force_improved_support_check,
std::string* error_message) {
SB_DCHECK(error_message);
@@ -256,16 +257,16 @@
new MediaCodecBridge(handler));
env->CallStaticVoidMethodOrAbort(
"dev/cobalt/media/MediaCodecBridge", "createVideoMediaCodecBridge",
- "(JLjava/lang/String;ZZIIILandroid/view/Surface;"
+ "(JLjava/lang/String;ZZZIIILandroid/view/Surface;"
"Landroid/media/MediaCrypto;"
"Ldev/cobalt/media/MediaCodecBridge$ColorInfo;"
"I"
"Ldev/cobalt/media/MediaCodecBridge$CreateMediaCodecBridgeResult;)"
"V",
reinterpret_cast<jlong>(native_media_codec_bridge.get()), j_mime.Get(),
- !!j_media_crypto, require_software_codec, width, height, fps, j_surface,
- j_media_crypto, j_color_info.Get(), tunnel_mode_audio_session_id,
- j_create_media_codec_bridge_result.Get());
+ !!j_media_crypto, require_software_codec, force_improved_support_check,
+ width, height, fps, j_surface, j_media_crypto, j_color_info.Get(),
+ tunnel_mode_audio_session_id, j_create_media_codec_bridge_result.Get());
jobject j_media_codec_bridge = env->CallObjectMethodOrAbort(
j_create_media_codec_bridge_result.Get(), "mediaCodecBridge",
diff --git a/src/starboard/android/shared/media_codec_bridge.h b/src/starboard/android/shared/media_codec_bridge.h
index 60a6487..77b9c7a 100644
--- a/src/starboard/android/shared/media_codec_bridge.h
+++ b/src/starboard/android/shared/media_codec_bridge.h
@@ -113,6 +113,7 @@
bool require_software_codec,
int tunnel_mode_audio_session_id,
bool force_big_endian_hdr_metadata,
+ bool force_improved_support_check,
std::string* error_message);
~MediaCodecBridge();
diff --git a/src/starboard/android/shared/media_common.h b/src/starboard/android/shared/media_common.h
index aa17a47..f9e5a56 100644
--- a/src/starboard/android/shared/media_common.h
+++ b/src/starboard/android/shared/media_common.h
@@ -15,14 +15,12 @@
#ifndef STARBOARD_ANDROID_SHARED_MEDIA_COMMON_H_
#define STARBOARD_ANDROID_SHARED_MEDIA_COMMON_H_
-#include <deque>
-#include <queue>
+#include <cstring>
#include "starboard/android/shared/jni_env_ext.h"
#include "starboard/common/log.h"
#include "starboard/common/mutex.h"
#include "starboard/common/optional.h"
-#include "starboard/common/string.h"
#include "starboard/configuration.h"
#include "starboard/media.h"
#include "starboard/shared/starboard/player/filter/audio_frame_tracker.h"
@@ -36,10 +34,6 @@
strcmp(key_system, "com.widevine.alpha") == 0;
}
-inline bool IsWidevineL3(const char* key_system) {
- return strcmp(key_system, "com.youtube.widevine.l3") == 0;
-}
-
// Map a supported |SbMediaAudioCodec| into its corresponding mime type
// string. Returns |nullptr| if |audio_codec| is not supported.
// On return, |is_passthrough| will be set to true if the codec should be played
diff --git a/src/starboard/android/shared/media_decoder.cc b/src/starboard/android/shared/media_decoder.cc
index 44095f7..66bd437 100644
--- a/src/starboard/android/shared/media_decoder.cc
+++ b/src/starboard/android/shared/media_decoder.cc
@@ -117,6 +117,7 @@
const FrameRenderedCB& frame_rendered_cb,
int tunnel_mode_audio_session_id,
bool force_big_endian_hdr_metadata,
+ bool force_improved_support_check,
std::string* error_message)
: media_type_(kSbMediaTypeVideo),
host_(host),
@@ -131,7 +132,8 @@
media_codec_bridge_ = MediaCodecBridge::CreateVideoMediaCodecBridge(
video_codec, width, height, fps, this, j_output_surface, j_media_crypto,
color_metadata, require_software_codec, tunnel_mode_audio_session_id,
- force_big_endian_hdr_metadata, error_message);
+ force_big_endian_hdr_metadata, force_improved_support_check,
+ error_message);
if (!media_codec_bridge_) {
SB_LOG(ERROR) << "Failed to create video media codec bridge with error: "
<< *error_message;
diff --git a/src/starboard/android/shared/media_decoder.h b/src/starboard/android/shared/media_decoder.h
index 423d6ac..dbe73fc 100644
--- a/src/starboard/android/shared/media_decoder.h
+++ b/src/starboard/android/shared/media_decoder.h
@@ -89,6 +89,7 @@
const FrameRenderedCB& frame_rendered_cb,
int tunnel_mode_audio_session_id,
bool force_big_endian_hdr_metadata,
+ bool force_improved_support_check,
std::string* error_message);
~MediaDecoder();
diff --git a/src/starboard/android/shared/media_is_audio_supported.cc b/src/starboard/android/shared/media_is_audio_supported.cc
index 419de2a..d9efe42 100644
--- a/src/starboard/android/shared/media_is_audio_supported.cc
+++ b/src/starboard/android/shared/media_is_audio_supported.cc
@@ -52,6 +52,10 @@
mime_type.RegisterBoolParameter("tunnelmode");
// Enables audio passthrough if the codec supports it.
mime_type.RegisterBoolParameter("audiopassthrough");
+ // Allows for disabling the CONTENT_TYPE_MOVIE AudioAttribute for
+ // non-tunneled playbacks with PCM audio. Enabled by default.
+ // (https://developer.android.com/reference/android/media/AudioAttributes#CONTENT_TYPE_MOVIE)
+ mime_type.RegisterBoolParameter("enablepcmcontenttypemovie");
if (!mime_type.is_valid()) {
return false;
diff --git a/src/starboard/android/shared/media_is_video_supported.cc b/src/starboard/android/shared/media_is_video_supported.cc
index d4e8a48..b3d048e 100644
--- a/src/starboard/android/shared/media_is_video_supported.cc
+++ b/src/starboard/android/shared/media_is_video_supported.cc
@@ -48,7 +48,7 @@
bool has_hdr_capable_decoder =
JniEnvExt::Get()->CallStaticBooleanMethodOrAbort(
"dev/cobalt/media/MediaCodecUtil", "hasHdrCapableVideoDecoder",
- "(Ljava/lang/String;)Z", j_mime.Get()) == JNI_TRUE;
+ "(Ljava/lang/String;Z)Z", j_mime.Get(), false) == JNI_TRUE;
if (!has_hdr_capable_decoder) {
return false;
}
@@ -104,6 +104,9 @@
mime_type.RegisterBoolParameter("tunnelmode");
// Override endianness on HDR Info header. Defaults to little.
mime_type.RegisterStringParameter("hdrinfoendianness", "big|little");
+ // Forces the use of specific Android APIs (isSizeSupported() and
+ // areSizeAndRateSupported()) to determine format support.
+ mime_type.RegisterBoolParameter("forceimprovedsupportcheck");
if (!mime_type.is_valid()) {
return false;
@@ -128,10 +131,13 @@
// tunneled playback to be encrypted, so we must align the tunnel mode
// requirement with the secure playback requirement.
const bool require_secure_playback = must_support_tunnel_mode;
+ const bool force_improved_support_check =
+ mime_type.GetParamBoolValue("forceimprovedsupportcheck", true);
return env->CallStaticBooleanMethodOrAbort(
"dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor",
- "(Ljava/lang/String;ZZZIIII)Z", j_mime.Get(),
+ "(Ljava/lang/String;ZZZZIIII)Z", j_mime.Get(),
require_secure_playback, must_support_hdr,
- must_support_tunnel_mode, frame_width, frame_height,
- static_cast<jint>(bitrate), fps) == JNI_TRUE;
+ must_support_tunnel_mode, force_improved_support_check,
+ frame_width, frame_height, static_cast<jint>(bitrate),
+ fps) == JNI_TRUE;
}
diff --git a/src/starboard/android/shared/player_components_factory.h b/src/starboard/android/shared/player_components_factory.h
index a4041e2..35deb31 100644
--- a/src/starboard/android/shared/player_components_factory.h
+++ b/src/starboard/android/shared/player_components_factory.h
@@ -64,11 +64,16 @@
// TODO: Allow this to be configured per playback at run time from the web app.
constexpr bool kForceSecurePipelineInTunnelModeWhenRequired = true;
+// Forces video surface to reset after tunnel mode playbacks. This prevents
+// video distortion on some platforms.
+constexpr bool kForceResetSurfaceUnderTunnelMode = true;
+
// This class allows us to force int16 sample type when tunnel mode is enabled.
class AudioRendererSinkAndroid : public ::starboard::shared::starboard::player::
filter::AudioRendererSinkImpl {
public:
explicit AudioRendererSinkAndroid(bool enable_audio_device_callback,
+ bool enable_pcm_content_type_movie,
int tunnel_mode_audio_session_id = -1)
: AudioRendererSinkImpl(
[=](SbTime start_media_time,
@@ -92,6 +97,7 @@
frame_buffers_size_in_frames, update_source_status_func,
consume_frames_func, error_func, start_media_time,
tunnel_mode_audio_session_id, enable_audio_device_callback,
+ enable_pcm_content_type_movie, false, /* is_web_audio */
context);
}) {}
@@ -156,6 +162,7 @@
// into .cc file.
class PlayerComponentsFactory : public starboard::shared::starboard::player::
filter::PlayerComponents::Factory {
+ typedef starboard::shared::starboard::media::MimeType MimeType;
typedef starboard::shared::opus::OpusAudioDecoder OpusAudioDecoder;
typedef starboard::shared::starboard::player::filter::AdaptiveAudioDecoder
AdaptiveAudioDecoder;
@@ -190,7 +197,6 @@
scoped_ptr<PlayerComponents> CreateComponents(
const CreationParameters& creation_parameters,
std::string* error_message) override {
- using starboard::shared::starboard::media::MimeType;
SB_DCHECK(error_message);
if (creation_parameters.audio_codec() != kSbMediaAudioCodecAc3 &&
@@ -238,9 +244,20 @@
constexpr int kTunnelModeAudioSessionId = -1;
constexpr bool kForceSecurePipelineUnderTunnelMode = false;
- scoped_ptr<VideoDecoder> video_decoder = CreateVideoDecoder(
- creation_parameters, kTunnelModeAudioSessionId,
- kForceSecurePipelineUnderTunnelMode, error_message);
+ MimeType video_mime_type(creation_parameters.video_mime());
+ video_mime_type.RegisterBoolParameter("forceimprovedsupportcheck");
+ if (!video_mime_type.is_valid()) {
+ return scoped_ptr<PlayerComponents>();
+ }
+ const bool force_improved_support_check =
+ video_mime_type.GetParamBoolValue("forceimprovedsupportcheck", true);
+ SB_LOG_IF(INFO, !force_improved_support_check)
+ << "Improved support check is disabled for queries under 4K.";
+
+ scoped_ptr<VideoDecoder> video_decoder =
+ CreateVideoDecoder(creation_parameters, kTunnelModeAudioSessionId,
+ kForceSecurePipelineUnderTunnelMode,
+ force_improved_support_check, error_message);
if (video_decoder) {
using starboard::shared::starboard::player::filter::VideoRendererImpl;
@@ -268,7 +285,6 @@
scoped_ptr<VideoRenderAlgorithmBase>* video_render_algorithm,
scoped_refptr<VideoRendererSink>* video_renderer_sink,
std::string* error_message) override {
- using starboard::shared::starboard::media::MimeType;
SB_DCHECK(error_message);
const char* audio_mime =
@@ -280,6 +296,7 @@
strlen(creation_parameters.audio_mime()) > 0) {
audio_mime_type.RegisterBoolParameter("tunnelmode");
audio_mime_type.RegisterBoolParameter("enableaudiodevicecallback");
+ audio_mime_type.RegisterBoolParameter("enablepcmcontenttypemovie");
if (!audio_mime_type.is_valid()) {
return false;
@@ -294,6 +311,7 @@
if (creation_parameters.video_codec() != kSbMediaVideoCodecNone &&
strlen(creation_parameters.video_mime()) > 0) {
video_mime_type.RegisterBoolParameter("tunnelmode");
+ video_mime_type.RegisterBoolParameter("forceimprovedsupportcheck");
if (!video_mime_type.is_valid()) {
return false;
@@ -333,17 +351,26 @@
enable_tunnel_mode = true;
}
+ const bool force_improved_support_check =
+ video_mime_type.GetParamBoolValue("forceimprovedsupportcheck", true);
+ SB_LOG_IF(INFO, !force_improved_support_check)
+ << "Improved support check is disabled for queries under 4K.";
bool force_secure_pipeline_under_tunnel_mode = false;
if (enable_tunnel_mode &&
IsTunnelModeSupported(creation_parameters,
- &force_secure_pipeline_under_tunnel_mode)) {
- tunnel_mode_audio_session_id =
- GenerateAudioSessionId(creation_parameters);
+ &force_secure_pipeline_under_tunnel_mode,
+ force_improved_support_check)) {
+ tunnel_mode_audio_session_id = GenerateAudioSessionId(
+ creation_parameters, force_improved_support_check);
}
if (tunnel_mode_audio_session_id == -1) {
SB_LOG(INFO) << "Create non-tunnel mode pipeline.";
} else {
+ SB_LOG_IF(INFO, !kForceResetSurfaceUnderTunnelMode)
+ << "`kForceResetSurfaceUnderTunnelMode` is set to false, the video "
+ "surface will not be forced to reset after "
+ "tunneled playback.";
SB_LOG(INFO) << "Create tunnel mode pipeline with audio session id "
<< tunnel_mode_audio_session_id << '.';
}
@@ -386,6 +413,11 @@
audio_mime_type.GetParamBoolValue("enableaudiodevicecallback", true);
SB_LOG(INFO) << "AudioDeviceCallback is "
<< (enable_audio_device_callback ? "enabled." : "disabled.");
+ bool enable_pcm_content_type_movie =
+ audio_mime_type.GetParamBoolValue("enablepcmcontenttypemovie", true);
+ SB_LOG(INFO) << "AudioAttributes::CONTENT_TYPE_MOVIE is "
+ << (enable_pcm_content_type_movie ? "enabled" : "disabled")
+ << " for non-tunneled PCM audio playback.";
if (tunnel_mode_audio_session_id != -1) {
*audio_renderer_sink = TryToCreateTunnelModeAudioRendererSink(
@@ -396,8 +428,8 @@
}
}
if (!*audio_renderer_sink) {
- audio_renderer_sink->reset(
- new AudioRendererSinkAndroid(enable_audio_device_callback));
+ audio_renderer_sink->reset(new AudioRendererSinkAndroid(
+ enable_audio_device_callback, enable_pcm_content_type_movie));
}
}
@@ -411,9 +443,10 @@
force_secure_pipeline_under_tunnel_mode = false;
}
- scoped_ptr<VideoDecoder> video_decoder_impl = CreateVideoDecoder(
- creation_parameters, tunnel_mode_audio_session_id,
- force_secure_pipeline_under_tunnel_mode, error_message);
+ scoped_ptr<VideoDecoder> video_decoder_impl =
+ CreateVideoDecoder(creation_parameters, tunnel_mode_audio_session_id,
+ force_secure_pipeline_under_tunnel_mode,
+ force_improved_support_check, error_message);
if (video_decoder_impl) {
*video_render_algorithm = video_decoder_impl->GetRenderAlgorithm();
*video_renderer_sink = video_decoder_impl->GetSink();
@@ -457,8 +490,8 @@
const CreationParameters& creation_parameters,
int tunnel_mode_audio_session_id,
bool force_secure_pipeline_under_tunnel_mode,
+ bool force_improved_support_check,
std::string* error_message) {
- using starboard::shared::starboard::media::MimeType;
// Use mime param to determine endianness of HDR metadata. If param is
// missing or invalid it defaults to Little Endian.
MimeType video_mime_type(creation_parameters.video_mime());
@@ -479,7 +512,8 @@
creation_parameters.decode_target_graphics_context_provider(),
creation_parameters.max_video_capabilities(),
tunnel_mode_audio_session_id, force_secure_pipeline_under_tunnel_mode,
- force_big_endian_hdr_metadata, error_message));
+ kForceResetSurfaceUnderTunnelMode, force_big_endian_hdr_metadata,
+ force_improved_support_check, error_message));
if (creation_parameters.video_codec() == kSbMediaVideoCodecAv1 ||
video_decoder->is_decoder_created()) {
return video_decoder.Pass();
@@ -490,7 +524,8 @@
}
bool IsTunnelModeSupported(const CreationParameters& creation_parameters,
- bool* force_secure_pipeline_under_tunnel_mode) {
+ bool* force_secure_pipeline_under_tunnel_mode,
+ const bool force_improved_support_check) {
SB_DCHECK(force_secure_pipeline_under_tunnel_mode);
*force_secure_pipeline_under_tunnel_mode = false;
@@ -534,8 +569,8 @@
bool is_encrypted = !!j_media_crypto;
if (env->CallStaticBooleanMethodOrAbort(
"dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor",
- "(Ljava/lang/String;ZZZIIII)Z", j_mime.Get(), is_encrypted, false,
- true, 0, 0, 0, 0) == JNI_TRUE) {
+ "(Ljava/lang/String;ZZZZIIII)Z", j_mime.Get(), is_encrypted, false,
+ true, force_improved_support_check, 0, 0, 0, 0) == JNI_TRUE) {
return true;
}
@@ -544,8 +579,9 @@
auto support_tunnel_mode_under_secure_pipeline =
env->CallStaticBooleanMethodOrAbort(
"dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor",
- "(Ljava/lang/String;ZZZIIII)Z", j_mime.Get(), kIsEncrypted, false,
- true, 0, 0, 0, 0) == JNI_TRUE;
+ "(Ljava/lang/String;ZZZZIIII)Z", j_mime.Get(), kIsEncrypted,
+ false, true, force_improved_support_check, 0, 0, 0,
+ 0) == JNI_TRUE;
if (support_tunnel_mode_under_secure_pipeline) {
*force_secure_pipeline_under_tunnel_mode = true;
return true;
@@ -557,10 +593,12 @@
return false;
}
- int GenerateAudioSessionId(const CreationParameters& creation_parameters) {
+ int GenerateAudioSessionId(const CreationParameters& creation_parameters,
+ const bool force_improved_support_check) {
bool force_secure_pipeline_under_tunnel_mode = false;
SB_DCHECK(IsTunnelModeSupported(creation_parameters,
- &force_secure_pipeline_under_tunnel_mode));
+ &force_secure_pipeline_under_tunnel_mode,
+ force_improved_support_check));
JniEnvExt* env = JniEnvExt::Get();
ScopedLocalJavaRef<jobject> j_audio_output_manager(
@@ -585,7 +623,7 @@
const CreationParameters& creation_parameters,
bool enable_audio_device_callback) {
scoped_ptr<AudioRendererSink> audio_sink(new AudioRendererSinkAndroid(
- enable_audio_device_callback, tunnel_mode_audio_session_id));
+ enable_audio_device_callback, true, tunnel_mode_audio_session_id));
// We need to double check if the audio sink can actually be created.
int max_cached_frames, min_frames_per_append;
GetAudioRendererParams(creation_parameters, &max_cached_frames,
diff --git a/src/starboard/android/shared/starboard_platform.gypi b/src/starboard/android/shared/starboard_platform.gypi
index 95f0ba0..176d978 100644
--- a/src/starboard/android/shared/starboard_platform.gypi
+++ b/src/starboard/android/shared/starboard_platform.gypi
@@ -118,6 +118,8 @@
'file_truncate.cc',
'file_write.cc',
'get_home_directory.cc',
+ 'graphics.cc',
+ 'graphics.h',
'input_events_generator.cc',
'input_events_generator.h',
'jni_env_ext.cc',
diff --git a/src/starboard/android/shared/system_get_extensions.cc b/src/starboard/android/shared/system_get_extensions.cc
index 7e661ed..9d817f1 100644
--- a/src/starboard/android/shared/system_get_extensions.cc
+++ b/src/starboard/android/shared/system_get_extensions.cc
@@ -15,10 +15,12 @@
#include "starboard/system.h"
#include "cobalt/extension/configuration.h"
+#include "cobalt/extension/graphics.h"
#include "cobalt/extension/media_session.h"
#include "cobalt/extension/platform_service.h"
#include "starboard/android/shared/android_media_session_client.h"
#include "starboard/android/shared/configuration.h"
+#include "starboard/android/shared/graphics.h"
#include "starboard/android/shared/platform_service.h"
#include "starboard/common/log.h"
#include "starboard/common/string.h"
@@ -33,5 +35,8 @@
if (strcmp(name, kCobaltExtensionMediaSessionName) == 0) {
return starboard::android::shared::GetMediaSessionApi();
}
+ if (strcmp(name, kCobaltExtensionGraphicsName) == 0) {
+ return starboard::android::shared::GetGraphicsApi();
+ }
return NULL;
}
diff --git a/src/starboard/android/shared/time_zone_get_name.cc b/src/starboard/android/shared/time_zone_get_name.cc
index b2b6c41..f058c09 100644
--- a/src/starboard/android/shared/time_zone_get_name.cc
+++ b/src/starboard/android/shared/time_zone_get_name.cc
@@ -16,8 +16,21 @@
#include <time.h>
-const char* SbTimeZoneGetName() {
- // Note tzset() is called in ApplicationAndroid::Initialize()
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
- return tzname[0];
+using starboard::android::shared::JniEnvExt;
+using starboard::android::shared::ScopedLocalJavaRef;
+
+const char* SbTimeZoneGetName() {
+ static char s_time_zone_id[64];
+ // Note tzset() is called in ApplicationAndroid::Initialize()
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jstring> result(env->CallStarboardObjectMethodOrAbort(
+ "getTimeZoneId", "()Ljava/lang/String;"));
+ std::string time_zone_id = env->GetStringStandardUTFOrAbort(result.Get());
+ time_zone_id.push_back('\0');
+ strncpy(s_time_zone_id, time_zone_id.c_str(), sizeof(s_time_zone_id));
+ s_time_zone_id[sizeof(s_time_zone_id) - 1] = 0;
+ return s_time_zone_id;
}
diff --git a/src/starboard/android/shared/video_decoder.cc b/src/starboard/android/shared/video_decoder.cc
index 775496c..5078989 100644
--- a/src/starboard/android/shared/video_decoder.cc
+++ b/src/starboard/android/shared/video_decoder.cc
@@ -218,7 +218,9 @@
const char* max_video_capabilities,
int tunnel_mode_audio_session_id,
bool force_secure_pipeline_under_tunnel_mode,
+ bool force_reset_surface_under_tunnel_mode,
bool force_big_endian_hdr_metadata,
+ bool force_improved_support_check,
std::string* error_message)
: video_codec_(video_codec),
drm_system_(static_cast<DrmSystem*>(drm_system)),
@@ -226,11 +228,14 @@
decode_target_graphics_context_provider_(
decode_target_graphics_context_provider),
tunnel_mode_audio_session_id_(tunnel_mode_audio_session_id),
+ force_reset_surface_under_tunnel_mode_(
+ force_reset_surface_under_tunnel_mode),
has_new_texture_available_(false),
surface_condition_variable_(surface_destroy_mutex_),
require_software_codec_(max_video_capabilities &&
strlen(max_video_capabilities) > 0),
- force_big_endian_hdr_metadata_(force_big_endian_hdr_metadata) {
+ force_big_endian_hdr_metadata_(force_big_endian_hdr_metadata),
+ force_improved_support_check_(force_improved_support_check) {
SB_DCHECK(error_message);
if (tunnel_mode_audio_session_id != -1) {
@@ -264,7 +269,11 @@
VideoDecoder::~VideoDecoder() {
TeardownCodec();
- ClearVideoWindow();
+ if (tunnel_mode_audio_session_id_ != -1) {
+ ClearVideoWindow(force_reset_surface_under_tunnel_mode_);
+ } else {
+ ClearVideoWindow(false);
+ }
if (!require_software_codec_) {
number_of_hardware_decoders_--;
@@ -559,7 +568,7 @@
require_software_codec_,
std::bind(&VideoDecoder::OnTunnelModeFrameRendered, this, _1),
tunnel_mode_audio_session_id_, force_big_endian_hdr_metadata_,
- error_message));
+ force_improved_support_check_, error_message));
if (media_decoder_->is_valid()) {
if (error_cb_) {
media_decoder_->Initialize(
diff --git a/src/starboard/android/shared/video_decoder.h b/src/starboard/android/shared/video_decoder.h
index cc5dc1e..7859aa2 100644
--- a/src/starboard/android/shared/video_decoder.h
+++ b/src/starboard/android/shared/video_decoder.h
@@ -67,7 +67,9 @@
const char* max_video_capabilities,
int tunnel_mode_audio_session_id,
bool force_secure_pipeline_under_tunnel_mode,
+ bool force_reset_surface_under_tunnel_mode,
bool force_big_endian_hdr_metadata,
+ bool force_improved_support_check,
std::string* error_message);
~VideoDecoder() override;
@@ -136,10 +138,18 @@
// the main player and SW decoder for sub players.
const bool require_software_codec_;
+ // Forces the use of specific Android APIs (isSizeSupported() and
+ // areSizeAndRateSupported()) to determine format support.
+ const bool force_improved_support_check_;
+
// Force endianness of HDR Metadata.
const bool force_big_endian_hdr_metadata_;
const int tunnel_mode_audio_session_id_ = -1;
+
+ // Force resetting the video surface after tunnel mode playback, which
+ // prevents video distortion on some devices.
+ const bool force_reset_surface_under_tunnel_mode_;
// On some platforms tunnel mode is only supported in the secure pipeline. So
// we create a dummy drm system to force the video playing in secure pipeline
// to enable tunnel mode.
diff --git a/src/starboard/android/shared/video_window.cc b/src/starboard/android/shared/video_window.cc
index 88b7037..81d898d 100644
--- a/src/starboard/android/shared/video_window.cc
+++ b/src/starboard/android/shared/video_window.cc
@@ -14,10 +14,10 @@
#include "starboard/android/shared/video_window.h"
-#include <android/native_window.h>
-#include <android/native_window_jni.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
#include <jni.h>
#include "starboard/android/shared/jni_env_ext.h"
@@ -45,6 +45,87 @@
// vertical video.
bool g_reset_surface_on_clear_window = false;
+void ClearNativeWindow(ANativeWindow* native_window) {
+ EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ eglInitialize(display, NULL, NULL);
+ if (display == EGL_NO_DISPLAY) {
+ SB_DLOG(ERROR) << "Found no EGL display in ClearVideoWindow";
+ return;
+ }
+
+ const EGLint kAttributeList[] = {
+ EGL_RED_SIZE,
+ 8,
+ EGL_GREEN_SIZE,
+ 8,
+ EGL_BLUE_SIZE,
+ 8,
+ EGL_ALPHA_SIZE,
+ 8,
+ EGL_RENDERABLE_TYPE,
+ EGL_OPENGL_ES2_BIT,
+ EGL_NONE,
+ 0,
+ EGL_NONE,
+ };
+
+ // First, query how many configs match the given attribute list.
+ EGLint num_configs = 0;
+ EGL_CALL(eglChooseConfig(display, kAttributeList, NULL, 0, &num_configs));
+ SB_DCHECK(num_configs != 0);
+
+ // Allocate space to receive the matching configs and retrieve them.
+ EGLConfig* configs = new EGLConfig[num_configs];
+ EGL_CALL(eglChooseConfig(display, kAttributeList, configs, num_configs,
+ &num_configs));
+
+ EGLNativeWindowType egl_native_window =
+ static_cast<EGLNativeWindowType>(native_window);
+ EGLConfig config;
+
+ // Find the first config that successfully allow a window surface to be
+ // created.
+ EGLSurface surface;
+ for (int config_number = 0; config_number < num_configs; ++config_number) {
+ config = configs[config_number];
+ surface = eglCreateWindowSurface(display, config, egl_native_window, NULL);
+ if (eglGetError() == EGL_SUCCESS)
+ break;
+ }
+ if (surface == EGL_NO_SURFACE) {
+ SB_DLOG(ERROR) << "Found no EGL surface in ClearVideoWindow";
+ return;
+ }
+ SB_DCHECK(surface != EGL_NO_SURFACE);
+
+ delete[] configs;
+
+ // Create an OpenGL ES 2.0 context.
+ EGLContext context = EGL_NO_CONTEXT;
+ EGLint context_attrib_list[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE,
+ };
+ context =
+ eglCreateContext(display, config, EGL_NO_CONTEXT, context_attrib_list);
+ SB_DCHECK(eglGetError() == EGL_SUCCESS);
+ SB_DCHECK(context != EGL_NO_CONTEXT);
+
+ /* connect the context to the surface */
+ EGL_CALL(eglMakeCurrent(display, surface, surface, context));
+
+ GL_CALL(glClearColor(0, 0, 0, 1));
+ GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
+ GL_CALL(glFlush());
+ EGL_CALL(eglSwapBuffers(display, surface));
+
+ // Cleanup all used resources.
+ EGL_CALL(
+ eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+ EGL_CALL(eglDestroyContext(display, context));
+ EGL_CALL(eglDestroySurface(display, surface));
+ EGL_CALL(eglTerminate(display));
+}
+
} // namespace
extern "C" SB_EXPORT_PLATFORM void
@@ -68,6 +149,7 @@
if (surface) {
g_j_video_surface = env->NewGlobalRef(surface);
g_native_video_window = ANativeWindow_fromSurface(env, surface);
+ ClearNativeWindow(g_native_video_window);
}
}
@@ -118,7 +200,7 @@
}
}
-void VideoSurfaceHolder::ClearVideoWindow() {
+void VideoSurfaceHolder::ClearVideoWindow(bool force_reset_surface) {
// Lock *GetViewSurfaceMutex() here, to avoid releasing g_native_video_window
// during painting.
ScopedLock lock(*GetViewSurfaceMutex());
@@ -128,7 +210,11 @@
return;
}
- if (g_reset_surface_on_clear_window) {
+ if (force_reset_surface) {
+ JniEnvExt::Get()->CallStarboardVoidMethodOrAbort("resetVideoSurface",
+ "()V");
+ return;
+ } else if (g_reset_surface_on_clear_window) {
int width = ANativeWindow_getWidth(g_native_video_window);
int height = ANativeWindow_getHeight(g_native_video_window);
if (width <= height) {
@@ -138,84 +224,7 @@
}
}
- EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- eglInitialize(display, NULL, NULL);
- if (display == EGL_NO_DISPLAY) {
- SB_DLOG(ERROR) << "Found no EGL display in ClearVideoWindow";
- return;
- }
-
- const EGLint kAttributeList[] = {
- EGL_RED_SIZE,
- 8,
- EGL_GREEN_SIZE,
- 8,
- EGL_BLUE_SIZE,
- 8,
- EGL_ALPHA_SIZE,
- 8,
- EGL_RENDERABLE_TYPE,
- EGL_OPENGL_ES2_BIT,
- EGL_NONE,
- 0,
- EGL_NONE,
- };
-
- // First, query how many configs match the given attribute list.
- EGLint num_configs = 0;
- EGL_CALL(eglChooseConfig(display, kAttributeList, NULL, 0, &num_configs));
- SB_DCHECK(num_configs != 0);
-
- // Allocate space to receive the matching configs and retrieve them.
- EGLConfig* configs = new EGLConfig[num_configs];
- EGL_CALL(eglChooseConfig(display, kAttributeList, configs, num_configs,
- &num_configs));
-
- EGLNativeWindowType native_window =
- static_cast<EGLNativeWindowType>(g_native_video_window);
- EGLConfig config;
-
- // Find the first config that successfully allow a window surface to be
- // created.
- EGLSurface surface;
- for (int config_number = 0; config_number < num_configs; ++config_number) {
- config = configs[config_number];
- surface = eglCreateWindowSurface(display, config, native_window, NULL);
- if (eglGetError() == EGL_SUCCESS)
- break;
- }
- if (surface == EGL_NO_SURFACE) {
- SB_DLOG(ERROR) << "Found no EGL surface in ClearVideoWindow";
- return;
- }
- SB_DCHECK(surface != EGL_NO_SURFACE);
-
- delete[] configs;
-
- // Create an OpenGL ES 2.0 context.
- EGLContext context = EGL_NO_CONTEXT;
- EGLint context_attrib_list[] = {
- EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE,
- };
- context =
- eglCreateContext(display, config, EGL_NO_CONTEXT, context_attrib_list);
- SB_DCHECK(eglGetError() == EGL_SUCCESS);
- SB_DCHECK(context != EGL_NO_CONTEXT);
-
- /* connect the context to the surface */
- EGL_CALL(eglMakeCurrent(display, surface, surface, context));
-
- GL_CALL(glClearColor(0, 0, 0, 1));
- GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
- GL_CALL(glFlush());
- EGL_CALL(eglSwapBuffers(display, surface));
-
- // Cleanup all used resources.
- EGL_CALL(
- eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
- EGL_CALL(eglDestroyContext(display, context));
- EGL_CALL(eglDestroySurface(display, surface));
- EGL_CALL(eglTerminate(display));
+ ClearNativeWindow(g_native_video_window);
}
} // namespace shared
diff --git a/src/starboard/android/shared/video_window.h b/src/starboard/android/shared/video_window.h
index 17db407..4196cb9 100644
--- a/src/starboard/android/shared/video_window.h
+++ b/src/starboard/android/shared/video_window.h
@@ -48,7 +48,7 @@
bool GetVideoWindowSize(int* width, int* height);
// Clear the video window by painting it Black.
- void ClearVideoWindow();
+ void ClearVideoWindow(bool force_reset_surface);
};
} // namespace shared
diff --git a/src/starboard/configuration.h b/src/starboard/configuration.h
index fafa6e8..0aa01c1 100644
--- a/src/starboard/configuration.h
+++ b/src/starboard/configuration.h
@@ -65,6 +65,11 @@
// // exposes functionality for my new feature.
// #define SB_MY_EXPERIMENTAL_FEATURE_VERSION SB_EXPERIMENTAL_API_VERSION
+// Introduce a new format kSbDecodeTargetFormat3Plane10BitYUVI420Compact.
+// A decoder target format consisting of 10bit Y, U, and V planes.
+#define SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION \
+ SB_EXPERIMENTAL_API_VERSION
+
// --- Release Candidate Feature Defines -------------------------------------
// --- Common Detected Features ----------------------------------------------
diff --git a/src/starboard/decode_target.h b/src/starboard/decode_target.h
index ce00b88..9c18f79 100644
--- a/src/starboard/decode_target.h
+++ b/src/starboard/decode_target.h
@@ -136,9 +136,16 @@
kSbDecodeTargetFormat3PlaneYUVI420,
// A decoder target format consisting of 10bit Y, U, and V planes, in that
- // order.
+ // order. Each pixel is stored in 16 bits.
kSbDecodeTargetFormat3Plane10BitYUVI420,
+#if SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+ // A decoder target format consisting of 10bit Y, U, and V planes, in that
+ // order. The plane data is stored in a compact format. Every three 10-bit
+ // pixels are packed into 32 bits.
+ kSbDecodeTargetFormat3Plane10BitYUVI420Compact,
+#endif // SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+
// A decoder target format consisting of a single plane with pixels laid out
// in the format UYVY. Since there are two Y values per sample, but only one
// U value and only one V value, horizontally the Y resolution is twice the
@@ -341,6 +348,9 @@
case kSbDecodeTargetFormat2PlaneYUVNV12:
return 2;
case kSbDecodeTargetFormat3Plane10BitYUVI420:
+#if SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+ case kSbDecodeTargetFormat3Plane10BitYUVI420Compact:
+#endif // SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
case kSbDecodeTargetFormat3PlaneYUVI420:
return 3;
default:
diff --git a/src/starboard/elf_loader/elf_loader.cc b/src/starboard/elf_loader/elf_loader.cc
index f75f0f7..ba37533 100644
--- a/src/starboard/elf_loader/elf_loader.cc
+++ b/src/starboard/elf_loader/elf_loader.cc
@@ -59,7 +59,9 @@
bool ElfLoader::Load(const std::string& library_path,
const std::string& content_path,
bool is_relative_path,
- const void* (*custom_get_extension)(const char* name)) {
+ const void* (*custom_get_extension)(const char* name),
+ bool use_compression,
+ bool use_memory_mapped_file) {
if (is_relative_path) {
library_path_ = MakeRelativeToContentPath(library_path);
content_path_ = MakeRelativeToContentPath(content_path);
@@ -76,7 +78,8 @@
custom_get_extension);
SB_LOG(INFO) << "evergreen_config: content_path=" << content_path_;
SbTime start_time = SbTimeGetMonotonicNow();
- bool res = impl_->Load(library_path_.c_str(), custom_get_extension);
+ bool res = impl_->Load(library_path_.c_str(), custom_get_extension,
+ use_compression, use_memory_mapped_file);
SbTime end_time = SbTimeGetMonotonicNow();
SB_LOG(INFO) << "Loading took: "
<< (end_time - start_time) / kSbTimeMillisecond << " ms";
diff --git a/src/starboard/elf_loader/elf_loader.h b/src/starboard/elf_loader/elf_loader.h
index 587c7a6..f8f9e26 100644
--- a/src/starboard/elf_loader/elf_loader.h
+++ b/src/starboard/elf_loader/elf_loader.h
@@ -38,11 +38,15 @@
// Loads the shared library. Returns false if |library_path| or |content_path|
// is empty, or if the library could not be loaded.
// An optional |custom_get_extension| function pointer can be passed in order
- // to override the |SbSystemGetExtension| function.
+ // to override the |SbSystemGetExtension| function. The flags
+ // |use_compression| and |use_memory_mapped_file| are not compatible so only
+ // one of those can be turned on.
bool Load(const std::string& library_path,
const std::string& content_path,
bool is_relative_path,
- const void* (*custom_get_extension)(const char* name) = NULL);
+ const void* (*custom_get_extension)(const char* name) = NULL,
+ bool use_compression = false,
+ bool use_memory_mapped_file = false);
// Looks up the symbol address in the
// shared library.
diff --git a/src/starboard/elf_loader/elf_loader_impl.cc b/src/starboard/elf_loader/elf_loader_impl.cc
index cf392a9..4446e07 100644
--- a/src/starboard/elf_loader/elf_loader_impl.cc
+++ b/src/starboard/elf_loader/elf_loader_impl.cc
@@ -47,10 +47,16 @@
#endif
}
-bool ElfLoaderImpl::Load(
- const char* name,
- const void* (*custom_get_extension)(const char* name)) {
- if (EndsWith(name, kCompressionSuffix)) {
+bool ElfLoaderImpl::Load(const char* name,
+ const void* (*custom_get_extension)(const char* name),
+ bool use_compression,
+ bool use_memory_mapped_files) {
+ if (use_compression && use_memory_mapped_files) {
+ SB_LOG(ERROR) << "Loading " << name
+ << " Compression is not supported with memory mapped files.";
+ return false;
+ }
+ if (use_compression && EndsWith(name, kCompressionSuffix)) {
elf_file_.reset(new LZ4FileImpl());
SB_LOG(INFO) << "Loading " << name << " using compression";
} else {
@@ -67,13 +73,18 @@
SB_DLOG(INFO) << "Loaded ELF header";
- const auto* memory_mapped_file_extension =
- reinterpret_cast<const CobaltExtensionMemoryMappedFileApi*>(
- SbSystemGetExtension(kCobaltExtensionMemoryMappedFileName));
- if (memory_mapped_file_extension &&
- strcmp(memory_mapped_file_extension->name,
- kCobaltExtensionMemoryMappedFileName) == 0 &&
- memory_mapped_file_extension->version >= 1) {
+ if (use_memory_mapped_files) {
+ const auto* memory_mapped_file_extension =
+ reinterpret_cast<const CobaltExtensionMemoryMappedFileApi*>(
+ SbSystemGetExtension(kCobaltExtensionMemoryMappedFileName));
+
+ if (!memory_mapped_file_extension ||
+ strcmp(memory_mapped_file_extension->name,
+ kCobaltExtensionMemoryMappedFileName) != 0 ||
+ memory_mapped_file_extension->version < 1) {
+ SB_LOG(ERROR) << "CobaltExtensionMemoryMappedFileApi not implemented";
+ return false;
+ }
program_table_.reset(new ProgramTable(memory_mapped_file_extension));
} else {
program_table_.reset(new ProgramTable(nullptr));
diff --git a/src/starboard/elf_loader/elf_loader_impl.h b/src/starboard/elf_loader/elf_loader_impl.h
index 84c43c7..aad97b1 100644
--- a/src/starboard/elf_loader/elf_loader_impl.h
+++ b/src/starboard/elf_loader/elf_loader_impl.h
@@ -34,7 +34,9 @@
public:
ElfLoaderImpl();
bool Load(const char* file_name,
- const void* (*custom_get_extension)(const char* name));
+ const void* (*custom_get_extension)(const char* name),
+ bool use_compression,
+ bool use_memory_mapped_file);
void* LookupSymbol(const char* symbol);
~ElfLoaderImpl();
diff --git a/src/starboard/elf_loader/elf_loader_sys_impl.cc b/src/starboard/elf_loader/elf_loader_sys_impl.cc
index fd1c0a2..18fefb9 100644
--- a/src/starboard/elf_loader/elf_loader_sys_impl.cc
+++ b/src/starboard/elf_loader/elf_loader_sys_impl.cc
@@ -24,9 +24,10 @@
ElfLoaderImpl::ElfLoaderImpl() {}
-bool ElfLoaderImpl::Load(
- const char* name,
- const void* (*custom_get_extension)(const char* name)) {
+bool ElfLoaderImpl::Load(const char* name,
+ const void* (*custom_get_extension)(const char* name),
+ bool use_compression,
+ bool use_memory_mapped_file) {
SB_LOG(INFO) << "Loading: " << name;
// Creating the instance forces the binary to keep all the symbols.
diff --git a/src/starboard/elf_loader/elf_loader_sys_impl.h b/src/starboard/elf_loader/elf_loader_sys_impl.h
index 7f148a5..5913c0d 100644
--- a/src/starboard/elf_loader/elf_loader_sys_impl.h
+++ b/src/starboard/elf_loader/elf_loader_sys_impl.h
@@ -25,7 +25,9 @@
public:
ElfLoaderImpl();
bool Load(const char* file_name,
- const void* (*custom_get_extension)(const char* name) = NULL);
+ const void* (*custom_get_extension)(const char* name) = NULL,
+ bool use_compression = false,
+ bool use_memory_mapped_file = false);
void* LookupSymbol(const char* symbol);
~ElfLoaderImpl();
diff --git a/src/starboard/elf_loader/program_table.cc b/src/starboard/elf_loader/program_table.cc
index 61d823c..4bafa4a 100644
--- a/src/starboard/elf_loader/program_table.cc
+++ b/src/starboard/elf_loader/program_table.cc
@@ -80,7 +80,7 @@
SB_DLOG(INFO) << "page_max - page_min=" << page_max - page_min;
if (memory_mapped_file_extension_) {
- SB_DLOG(INFO) << "Memory mapped file for the program header";
+ SB_LOG(INFO) << "Using memory mapped file for the program header";
phdr_mmap_ = memory_mapped_file_extension_->MemoryMapFile(
NULL, elf_file->GetName().c_str(), kSbMemoryMapProtectRead, page_min,
phdr_size_);
diff --git a/src/starboard/evergreen/testing/tests/clean_drain_file_on_suspend_test.sh b/src/starboard/evergreen/testing/tests/clean_drain_file_on_suspend_test.sh
new file mode 100755
index 0000000..be0e547
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/clean_drain_file_on_suspend_test.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="CleanDrainFilesOnSuspend"
+TEST_FILE="test.html"
+
+function run_test() {
+ clear_storage
+
+ LOG="${TEST_NAME}.0.log"
+ start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${LOG}" LOADER --update_check_delay_seconds=1
+
+ VAR=1
+
+ for i in {1..5}
+ do
+ log "info" "${TEST_NAME} #${VAR}"
+ sleep ${VAR}
+
+ # suspend
+ kill -s USR1 "${LOADER}"
+ if [[ $? -ne 0 ]]; then
+ log "warning" "Failed to suspend"
+ break
+ fi
+
+ sleep 5
+
+ if ls ${STORAGE_DIR}/installation_?/d_* 1> /dev/null 2>&1; then
+ echo "drain file wasn't cleaned up ."
+ return 1
+ fi
+
+ # resume
+ kill -s CONT "${LOADER}"
+ if [[ $? -ne 0 ]]; then
+ log "warning" "Failed to resume"
+ break
+ fi
+
+ let "VAR=${VAR}+1"
+ done
+ return 0
+
+}
diff --git a/src/starboard/evergreen/testing/tests/evergreen_lite_test.sh b/src/starboard/evergreen/testing/tests/evergreen_lite_test.sh
index 42d37a3..106b145 100755
--- a/src/starboard/evergreen/testing/tests/evergreen_lite_test.sh
+++ b/src/starboard/evergreen/testing/tests/evergreen_lite_test.sh
@@ -46,5 +46,11 @@
return 1
fi
+ cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.2.log" "Loaded WebModule" "--evergreen_lite"
+
+ if [[ $? -ne 0 ]]; then
+ log "error" "Failed to run system installation"
+ return 1
+ fi
return 0
}
diff --git a/src/starboard/evergreen/testing/tests/out_of_storage_test.sh b/src/starboard/evergreen/testing/tests/out_of_storage_test.sh
index 3e13e55..03c0feb 100755
--- a/src/starboard/evergreen/testing/tests/out_of_storage_test.sh
+++ b/src/starboard/evergreen/testing/tests/out_of_storage_test.sh
@@ -45,9 +45,21 @@
OLD_TIMEOUT="${TIMEOUT}"
TIMEOUT=300
+ RESULT=0
- cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "Failed to update, log \"error\" code is 12"
+ cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "Failed to update, error code is 22"
+ if [[ $? -ne 0 ]]; then
+ log "error" "Failed to run out of storage"
+ RESULT=1
+ fi
+ if [[ $RESULT -ne 1 ]]; then
+ cycle_cobalt "file:///tests/${TEST_FILE}?channel=test\&min_storage=5" "${TEST_NAME}.0.log" "Failed to update, error code is 200"
+ if [[ $? -ne 0 ]]; then
+ log "error" "Failed to run out of storage"
+ RESULT=1
+ fi
+ fi
# Remove the symbolic link.
run_command "rm -f ${STORAGE_DIR}" 1> /dev/null
@@ -56,12 +68,7 @@
run_command "mv \"${STORAGE_DIR}.tmp\" \"${STORAGE_DIR}\"" 1> /dev/null
fi
- if [[ $? -ne 0 ]]; then
- log "error" "Failed to run out of storage"
- return 1
- fi
-
TIMEOUT="${OLD_TIMEOUT}"
- return 0
+ return $RESULT
}
diff --git a/src/starboard/evergreen/testing/tests/suspend_resume_test.sh b/src/starboard/evergreen/testing/tests/suspend_resume_test.sh
index 35b549f..6d2dcfa 100755
--- a/src/starboard/evergreen/testing/tests/suspend_resume_test.sh
+++ b/src/starboard/evergreen/testing/tests/suspend_resume_test.sh
@@ -39,7 +39,7 @@
clear_storage
LOG="${TEST_NAME}.0.log"
- start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${LOG}" LOADER --update_check_delay=1
+ start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${LOG}" LOADER --update_check_delay_seconds=1
VAR=1
diff --git a/src/starboard/evergreen/testing/tests/test.html b/src/starboard/evergreen/testing/tests/test.html
index e53f706..97f205a 100644
--- a/src/starboard/evergreen/testing/tests/test.html
+++ b/src/starboard/evergreen/testing/tests/test.html
@@ -53,6 +53,9 @@
}
query.forEach(part => {
+ if (part.startsWith("min_storage=")) {
+ window.h5vcc.settings.set("Updater.MinFreeSpaceBytes", part.split("=")[1]);
+ }
if (changeChannel) {
return;
}
@@ -61,6 +64,7 @@
targetChannel = part.split("=")[1];
changeChannel = setInterval(tryChangeChannel, 500);
}
+
});
</script>
</html>
diff --git a/src/starboard/evergreen/testing/tests/use_mmap_file_test.sh b/src/starboard/evergreen/testing/tests/use_mmap_file_test.sh
new file mode 100644
index 0000000..f82cd76
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/use_mmap_file_test.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="UseMemoryMappedFile"
+TEST_FILE="test.html"
+
+function run_test() {
+ clear_storage
+
+ cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.2.log" "Using memory mapped file for the program header" "--loader_use_mmap_file"
+
+ if [[ $? -ne 0 ]]; then
+ log "error" "Failed to run system installation"
+ return 1
+ fi
+
+ cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.2.log" "Using memory mapped file for the program header" "--loader_use_mmap_file --evergreen_lite"
+
+ if [[ $? -ne 0 ]]; then
+ log "error" "Failed to run system installation"
+ return 1
+ fi
+ return 0
+}
diff --git a/src/starboard/linux/shared/BUILD.gn b/src/starboard/linux/shared/BUILD.gn
index c720b16..0dde49f 100644
--- a/src/starboard/linux/shared/BUILD.gn
+++ b/src/starboard/linux/shared/BUILD.gn
@@ -176,6 +176,8 @@
"//starboard/shared/posix/file_seek.cc",
"//starboard/shared/posix/file_truncate.cc",
"//starboard/shared/posix/file_write.cc",
+ "//starboard/shared/posix/free_space.cc",
+ "//starboard/shared/posix/free_space.h",
"//starboard/shared/posix/log.cc",
"//starboard/shared/posix/log_flush.cc",
"//starboard/shared/posix/log_format.cc",
diff --git a/src/starboard/linux/shared/starboard_platform.gypi b/src/starboard/linux/shared/starboard_platform.gypi
index fef07e1..584b1fd 100644
--- a/src/starboard/linux/shared/starboard_platform.gypi
+++ b/src/starboard/linux/shared/starboard_platform.gypi
@@ -147,6 +147,8 @@
'<(DEPTH)/starboard/shared/posix/file_seek.cc',
'<(DEPTH)/starboard/shared/posix/file_truncate.cc',
'<(DEPTH)/starboard/shared/posix/file_write.cc',
+ '<(DEPTH)/starboard/shared/posix/free_space.cc',
+ '<(DEPTH)/starboard/shared/posix/free_space.h',
'<(DEPTH)/starboard/shared/posix/log_flush.cc',
'<(DEPTH)/starboard/shared/posix/log_format.cc',
'<(DEPTH)/starboard/shared/posix/log_is_tty.cc',
diff --git a/src/starboard/linux/shared/system_get_extensions.cc b/src/starboard/linux/shared/system_get_extensions.cc
index 3411d93..5a8479d 100644
--- a/src/starboard/linux/shared/system_get_extensions.cc
+++ b/src/starboard/linux/shared/system_get_extensions.cc
@@ -16,8 +16,10 @@
#include "cobalt/extension/configuration.h"
#include "cobalt/extension/crash_handler.h"
+#include "cobalt/extension/free_space.h"
#include "cobalt/extension/memory_mapped_file.h"
#include "starboard/common/string.h"
+#include "starboard/shared/posix/free_space.h"
#include "starboard/shared/posix/memory_mapped_file.h"
#include "starboard/shared/starboard/crash_handler.h"
#if SB_IS(EVERGREEN_COMPATIBLE)
@@ -46,5 +48,8 @@
if (strcmp(name, kCobaltExtensionMemoryMappedFileName) == 0) {
return starboard::shared::posix::GetMemoryMappedFileApi();
}
+ if (strcmp(name, kCobaltExtensionFreeSpaceName) == 0) {
+ return starboard::shared::posix::GetFreeSpaceApi();
+ }
return NULL;
}
diff --git a/src/starboard/loader_app/drain_file.cc b/src/starboard/loader_app/drain_file.cc
index b2a5e50..7efb6b5 100644
--- a/src/starboard/loader_app/drain_file.cc
+++ b/src/starboard/loader_app/drain_file.cc
@@ -195,7 +195,23 @@
return !strcmp(ranking_app_key.data(), app_key);
}
-bool Remove(const char* dir, const char* app_key) {
+void ClearExpired(const char* dir) {
+ SB_DCHECK(dir);
+ std::vector<std::string> filenames = FindAllWithPrefix(dir, kDrainFilePrefix);
+
+ for (const auto& filename : filenames) {
+ if (!IsExpired(filename)) {
+ continue;
+ }
+ const std::string path = dir + std::string(kSbFileSepString) + filename;
+ if (!SbFileDelete(path.c_str())) {
+ SB_LOG(ERROR) << "Failed to remove expired drain file at '" << path
+ << "'";
+ }
+ }
+}
+
+void ClearForApp(const char* dir, const char* app_key) {
SB_DCHECK(dir);
SB_DCHECK(app_key);
@@ -205,29 +221,9 @@
for (const auto& filename : filenames) {
const std::string path = dir + std::string(kSbFileSepString) + filename;
-
- if (!SbFileDelete(path.c_str()))
- return false;
- }
- return true;
-}
-
-void Clear(const char* dir, const char* app_key, bool expired) {
- SB_DCHECK(dir);
-
- std::vector<std::string> filenames = FindAllWithPrefix(dir, kDrainFilePrefix);
-
- for (const auto& filename : filenames) {
- if (expired && !IsExpired(filename))
- continue;
- if (app_key && (filename.find(app_key) != std::string::npos))
- continue;
-
- const std::string path = dir + std::string(kSbFileSepString) + filename;
-
- if (!SbFileDelete(path.c_str()))
- SB_LOG(ERROR) << "Failed to remove expired drain file at '" << filename
- << "'";
+ if (!SbFileDelete(path.c_str())) {
+ SB_LOG(ERROR) << "Failed to remove drain file at '" << path << "'";
+ }
}
}
@@ -250,13 +246,13 @@
}
}
-bool Draining(const char* dir, const char* app_key) {
+bool IsAppDraining(const char* dir, const char* app_key) {
SB_DCHECK(dir);
+ SB_DCHECK(app_key);
+ SB_DCHECK(strlen(app_key));
std::string prefix(kDrainFilePrefix);
-
- if (app_key)
- prefix.append(app_key);
+ prefix.append(app_key);
const std::vector<std::string> filenames =
FindAllWithPrefix(dir, prefix.c_str());
@@ -268,6 +264,23 @@
return false;
}
+bool IsAnotherAppDraining(const char* dir, const char* app_key) {
+ SB_DCHECK(dir);
+ SB_DCHECK(app_key);
+ SB_DCHECK(strlen(app_key));
+ std::vector<std::string> filenames = FindAllWithPrefix(dir, kDrainFilePrefix);
+
+ for (const auto& filename : filenames) {
+ if ((filename.find(app_key) != std::string::npos)) {
+ continue;
+ }
+ if (!IsExpired(filename)) {
+ return true;
+ }
+ }
+ return false;
+}
+
} // namespace drain_file
} // namespace loader_app
} // namespace starboard
@@ -284,20 +297,24 @@
return starboard::loader_app::drain_file::RankAndCheck(dir, app_key);
}
-bool DrainFileRemove(const char* dir, const char* app_key) {
- return starboard::loader_app::drain_file::Remove(dir, app_key);
+void DrainFileClearExpired(const char* dir) {
+ starboard::loader_app::drain_file::ClearExpired(dir);
}
-void DrainFileClear(const char* dir, const char* app_key, bool expired) {
- starboard::loader_app::drain_file::Clear(dir, app_key, expired);
+void DrainFileClearForApp(const char* dir, const char* app_key) {
+ starboard::loader_app::drain_file::ClearForApp(dir, app_key);
}
void DrainFilePrepareDirectory(const char* dir, const char* app_key) {
starboard::loader_app::drain_file::PrepareDirectory(dir, app_key);
}
-bool DrainFileDraining(const char* dir, const char* app_key) {
- return starboard::loader_app::drain_file::Draining(dir, app_key);
+bool DrainFileIsAppDraining(const char* dir, const char* app_key) {
+ return starboard::loader_app::drain_file::IsAppDraining(dir, app_key);
+}
+
+bool DrainFileIsAnotherAppDraining(const char* dir, const char* app_key) {
+ return starboard::loader_app::drain_file::IsAnotherAppDraining(dir, app_key);
}
#ifdef __cplusplus
diff --git a/src/starboard/loader_app/drain_file.h b/src/starboard/loader_app/drain_file.h
index 92a9dba..11d72a5 100644
--- a/src/starboard/loader_app/drain_file.h
+++ b/src/starboard/loader_app/drain_file.h
@@ -42,24 +42,23 @@
// match, otherwise |false|.
bool DrainFileRankAndCheck(const char* dir, const char* app_key);
-// Removed the drain files in |dir| whose app key matches |app_key|. Returns
-// |true| if no files were found, or they were removed, otherwise returns
-// |false|.
-bool DrainFileRemove(const char* dir, const char* app_key);
+// Clears all expired drain files in |dir| for all apps.
+void DrainFileClearExpired(const char* dir);
-// Clears the drain files in |dir|. If |app_key| is provided, all drain files
-// with matching app keys are ignored. If |expired| is |true|, all non-expired
-// drain files are ignored.
-void DrainFileClear(const char* dir, const char* app_key, bool expired);
+// Clears the drain files in |dir| with an app key matching |app_key|.
+void DrainFileClearForApp(const char* dir, const char* app_key);
// Clears all files and directories in |dir| except for the drain file with an
// app key matching |app_key|.
void DrainFilePrepareDirectory(const char* dir, const char* app_key);
-// Checks whether a non-expired drain file exists in |dir|. If |app_key| is
-// provided, only drain files with a matching |app_key| are considered. Returns
-// |true| if there is, otherwise |false|.
-bool DrainFileDraining(const char* dir, const char* app_key);
+// Checks whether a non-expired drain file exists in |dir| for an app
+// with key |app_key|.
+bool DrainFileIsAppDraining(const char* dir, const char* app_key);
+
+// Checks whether a non-expired drain file exists in |dir| for an app
+// with key different from |app_key|.
+bool DrainFileIsAnotherAppDraining(const char* dir, const char* app_key);
#ifdef __cplusplus
} // extern "C"
diff --git a/src/starboard/loader_app/drain_file_test.cc b/src/starboard/loader_app/drain_file_test.cc
index 35ae7cd..bf0863c 100644
--- a/src/starboard/loader_app/drain_file_test.cc
+++ b/src/starboard/loader_app/drain_file_test.cc
@@ -36,29 +36,20 @@
class DrainFileTest : public ::testing::Test {
protected:
- // This function is used to set the temporary directory used for testing once,
- // and to create a subdir in this test directory, once for all test cases.
- static void SetUpTestCase() {
- std::vector<char> path(kSbFileMaxPath, 0);
-
- ASSERT_TRUE(
- SbSystemGetPath(kSbSystemPathTempDirectory, path.data(), path.size()));
-
- dir_.assign(path.data());
+ void SetUp() override {
+ temp_dir_.resize(kSbFileMaxPath);
+ ASSERT_TRUE(SbSystemGetPath(kSbSystemPathTempDirectory, temp_dir_.data(),
+ temp_dir_.size()));
}
- void TearDown() override {
- DrainFileClear(GetTempDir(), NULL, false);
- }
+ void TearDown() override { DrainFileClearForApp(GetTempDir(), ""); }
- const char* GetTempDir() const { return dir_.c_str(); }
+ const char* GetTempDir() const { return temp_dir_.data(); }
private:
- static std::string dir_;
+ std::vector<char> temp_dir_;
};
-std::string DrainFileTest::dir_ = "";
-
// Typical drain file usage.
TEST_F(DrainFileTest, SunnyDay) {
EXPECT_TRUE(DrainFileTryDrain(GetTempDir(), kAppKeyOne));
@@ -71,9 +62,9 @@
ScopedDrainFile stale(GetTempDir(), kAppKeyOne,
SbTimeGetNow() - kDrainFileMaximumAge);
- EXPECT_FALSE(DrainFileDraining(GetTempDir(), kAppKeyOne));
+ EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
EXPECT_TRUE(DrainFileTryDrain(GetTempDir(), kAppKeyOne));
- EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyOne));
+ EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
}
// Previously created drain file should be reused if it has not expired.
@@ -85,56 +76,74 @@
// Draining status should return whether or not the file exists and has not yet
// expired.
TEST_F(DrainFileTest, SunnyDayDraining) {
- EXPECT_FALSE(DrainFileDraining(GetTempDir(), kAppKeyOne));
+ EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
EXPECT_TRUE(DrainFileTryDrain(GetTempDir(), kAppKeyOne));
- EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyOne));
+ EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
- DrainFileClear(GetTempDir(), NULL, false);
+ DrainFileClearForApp(GetTempDir(), kAppKeyOne);
- EXPECT_FALSE(DrainFileDraining(GetTempDir(), kAppKeyOne));
+ EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+}
+
+// Check if another app is draining.
+TEST_F(DrainFileTest, SunnyDayAnotherAppDraining) {
+ EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+ EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyTwo));
+ EXPECT_FALSE(DrainFileIsAnotherAppDraining(GetTempDir(), kAppKeyOne));
+ EXPECT_FALSE(DrainFileIsAnotherAppDraining(GetTempDir(), kAppKeyTwo));
+
+ EXPECT_TRUE(DrainFileTryDrain(GetTempDir(), kAppKeyOne));
+ EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+ EXPECT_TRUE(DrainFileIsAnotherAppDraining(GetTempDir(), kAppKeyTwo));
+
+ DrainFileClearForApp(GetTempDir(), kAppKeyOne);
+ EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+ EXPECT_FALSE(DrainFileIsAnotherAppDraining(GetTempDir(), kAppKeyTwo));
}
// Remove an existing drain file.
TEST_F(DrainFileTest, SunnyDayRemove) {
EXPECT_TRUE(DrainFileTryDrain(GetTempDir(), kAppKeyOne));
- EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyOne));
- EXPECT_TRUE(DrainFileRemove(GetTempDir(), kAppKeyOne));
- EXPECT_FALSE(DrainFileDraining(GetTempDir(), kAppKeyOne));
+ EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+ DrainFileClearForApp(GetTempDir(), kAppKeyOne);
+ EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
}
-// Try to remove a drain file that does not exist.
-TEST_F(DrainFileTest, SunnyDayRemoveNonexistentDrainFile) {
- EXPECT_FALSE(DrainFileDraining(GetTempDir(), kAppKeyOne));
- EXPECT_TRUE(DrainFileRemove(GetTempDir(), kAppKeyOne));
-}
-
-// When clearing drain files it should be possible to provide a file to ignore,
-// and it should be possible to only clear expire files if desired.
-TEST_F(DrainFileTest, SunnyDayClear) {
+// Clear only expired drain files.
+TEST_F(DrainFileTest, SunnyDayClearExpired) {
EXPECT_TRUE(DrainFileTryDrain(GetTempDir(), kAppKeyOne));
ScopedDrainFile valid_file(GetTempDir(), kAppKeyTwo, SbTimeGetNow());
ScopedDrainFile stale_file(GetTempDir(), kAppKeyThree,
SbTimeGetNow() - kDrainFileMaximumAge);
- EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyOne));
- EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyTwo));
+ EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+ EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyTwo));
EXPECT_TRUE(SbFileExists(stale_file.path().c_str()));
- DrainFileClear(GetTempDir(), kAppKeyOne, true);
+ DrainFileClearExpired(GetTempDir());
- EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyOne));
- EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyTwo));
EXPECT_FALSE(SbFileExists(stale_file.path().c_str()));
+}
- DrainFileClear(GetTempDir(), kAppKeyOne, false);
+// Clearing drain files for an app.
+TEST_F(DrainFileTest, SunnyDayClearForApp) {
+ EXPECT_TRUE(DrainFileTryDrain(GetTempDir(), kAppKeyOne));
- EXPECT_TRUE(DrainFileDraining(GetTempDir(), kAppKeyOne));
- EXPECT_FALSE(DrainFileDraining(GetTempDir(), kAppKeyTwo));
+ ScopedDrainFile valid_file(GetTempDir(), kAppKeyTwo, SbTimeGetNow());
+ ScopedDrainFile stale_file(GetTempDir(), kAppKeyThree,
+ SbTimeGetNow() - kDrainFileMaximumAge);
- DrainFileClear(GetTempDir(), NULL, false);
+ EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+ EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyTwo));
+ EXPECT_TRUE(SbFileExists(stale_file.path().c_str()));
- EXPECT_FALSE(DrainFileDraining(GetTempDir(), kAppKeyOne));
+ // clean all drain files for an app
+ DrainFileClearForApp(GetTempDir(), kAppKeyOne);
+
+ EXPECT_FALSE(DrainFileIsAppDraining(GetTempDir(), kAppKeyOne));
+ EXPECT_TRUE(DrainFileIsAppDraining(GetTempDir(), kAppKeyTwo));
+ EXPECT_TRUE(SbFileExists(stale_file.path().c_str()));
}
// Ranking drain files should first be done by timestamp, with the app key being
diff --git a/src/starboard/loader_app/loader_app.cc b/src/starboard/loader_app/loader_app.cc
index 2fdce8f..b60720d 100644
--- a/src/starboard/loader_app/loader_app.cc
+++ b/src/starboard/loader_app/loader_app.cc
@@ -45,6 +45,9 @@
// Relative path to the Cobalt's system image library.
const char kSystemImageLibraryPath[] = "app/cobalt/lib/libcobalt.so";
+// Relative path to the compressed Cobalt's system image library.
+const char kSystemImageCompressedLibraryPath[] = "app/cobalt/lib/libcobalt.lz4";
+
// Cobalt default URL.
const char kCobaltDefaultUrl[] = "https://www.youtube.com/tv";
@@ -58,9 +61,12 @@
class CobaltLibraryLoader : public starboard::loader_app::LibraryLoader {
public:
virtual bool Load(const std::string& library_path,
- const std::string& content_path) {
+ const std::string& content_path,
+ bool use_compression,
+ bool use_memory_mapped_file) {
return g_elf_loader.Load(library_path, content_path, false,
- &starboard::loader_app::SbSystemGetExtensionShim);
+ &starboard::loader_app::SbSystemGetExtensionShim,
+ use_compression, use_memory_mapped_file);
}
virtual void* Resolve(const std::string& symbol) {
return g_elf_loader.LookupSymbol(symbol.c_str());
@@ -79,7 +85,13 @@
return true;
}
-void LoadLibraryAndInitialize(const std::string& alternative_content_path) {
+void LoadLibraryAndInitialize(const std::string& alternative_content_path,
+ bool use_compression,
+ bool use_memory_mapped_file) {
+ if (use_compression && use_memory_mapped_file) {
+ SB_LOG(ERROR) << "Using both compression and mmap files is not supported";
+ return;
+ }
std::string content_dir;
if (!GetContentDir(&content_dir)) {
SB_LOG(ERROR) << "Failed to get the content dir";
@@ -95,13 +107,15 @@
}
std::string library_path = content_dir;
library_path += kSbFileSepString;
- library_path += kSystemImageLibraryPath;
- if (!SbFileExists(library_path.c_str())) {
- // Try the compressed path if the binary doesn't exits.
- library_path += starboard::elf_loader::kCompressionSuffix;
+
+ if (use_compression) {
+ library_path += kSystemImageCompressedLibraryPath;
+ } else {
+ library_path += kSystemImageLibraryPath;
}
- if (!g_elf_loader.Load(library_path, content_path, false)) {
+ if (!g_elf_loader.Load(library_path, content_path, false, nullptr,
+ use_compression, use_memory_mapped_file)) {
SB_NOTREACHED() << "Failed to load library at '"
<< g_elf_loader.GetLibraryPath() << "'.";
return;
@@ -189,8 +203,16 @@
command_line.GetSwitchValue(starboard::loader_app::kContent);
SB_LOG(INFO) << "alternative_content=" << alternative_content;
+ bool use_compression =
+ command_line.HasSwitch(starboard::loader_app::kLoaderUseCompression);
+
+ bool use_memory_mapped_file = command_line.HasSwitch(
+ starboard::loader_app::kLoaderUseMemoryMappedFile);
+ SB_LOG(INFO) << "loader_app: use_compression=" << use_compression
+ << " use_memory_mapped_file=" << use_memory_mapped_file;
if (is_evergreen_lite) {
- LoadLibraryAndInitialize(alternative_content);
+ LoadLibraryAndInitialize(alternative_content, use_compression,
+ use_memory_mapped_file);
} else {
std::string url =
command_line.GetSwitchValue(starboard::loader_app::kURL);
@@ -202,7 +224,8 @@
g_sb_event_func = reinterpret_cast<void (*)(const SbEvent*)>(
starboard::loader_app::LoadSlotManagedLibrary(
- app_key, alternative_content, &g_cobalt_library_loader));
+ app_key, alternative_content, &g_cobalt_library_loader,
+ use_compression, use_memory_mapped_file));
}
SB_CHECK(g_sb_event_func);
}
diff --git a/src/starboard/loader_app/loader_app_switches.cc b/src/starboard/loader_app/loader_app_switches.cc
index 7758dea..b40574c 100644
--- a/src/starboard/loader_app/loader_app_switches.cc
+++ b/src/starboard/loader_app/loader_app_switches.cc
@@ -22,6 +22,8 @@
const char kEvergreenLite[] = "evergreen_lite";
const char kLoaderAppVersion[] = "loader_app_version";
const char kShowSABI[] = "show_sabi";
+const char kLoaderUseCompression[] = "loader_use_compression";
+const char kLoaderUseMemoryMappedFile[] = "loader_use_mmap_file";
} // namespace loader_app
} // namespace starboard
diff --git a/src/starboard/loader_app/loader_app_switches.h b/src/starboard/loader_app/loader_app_switches.h
index 22030c5..bf62ffb 100644
--- a/src/starboard/loader_app/loader_app_switches.h
+++ b/src/starboard/loader_app/loader_app_switches.h
@@ -38,6 +38,11 @@
// Print the loader_app Starboard ABI string on the command line.
extern const char kShowSABI[];
+// The elf loader should use compression.
+extern const char kLoaderUseCompression[];
+
+// The elf laoder should use a Memory Mapped file.
+extern const char kLoaderUseMemoryMappedFile[];
} // namespace loader_app
} // namespace starboard
diff --git a/src/starboard/loader_app/slot_management.cc b/src/starboard/loader_app/slot_management.cc
index 0d8cf3f..b834f07 100644
--- a/src/starboard/loader_app/slot_management.cc
+++ b/src/starboard/loader_app/slot_management.cc
@@ -43,33 +43,41 @@
// Filename for the Cobalt binary.
const char kCobaltLibraryName[] = "libcobalt.so";
+// Filename for the compressed Cobalt binary.
+const char kCompressedCobaltLibraryName[] = "libcobalt.lz4";
+
// Relative path for the content directory of
// the Cobalt installation.
const char kCobaltContentPath[] = "content";
} // namespace
-int RevertBack(int current_installation, const std::string& app_key) {
+int RevertBack(int current_installation,
+ const std::string& app_key,
+ bool mark_bad) {
SB_LOG(INFO) << "RevertBack current_installation=" << current_installation;
SB_DCHECK(current_installation != 0);
- std::vector<char> installation_path(kSbFileMaxPath);
- if (ImGetInstallationPath(current_installation, installation_path.data(),
- kSbFileMaxPath) != IM_ERROR) {
- std::string bad_app_key_file_path =
- starboard::loader_app::GetBadAppKeyFilePath(installation_path.data(),
- app_key);
- if (bad_app_key_file_path.empty()) {
- SB_LOG(WARNING) << "Failed to get bad app key file path for path="
- << installation_path.data() << " and app_key=" << app_key;
- } else {
- if (!starboard::loader_app::CreateAppKeyFile(bad_app_key_file_path)) {
- SB_LOG(WARNING) << "Failed to create bad app key file: "
- << bad_app_key_file_path;
+ if (mark_bad) {
+ std::vector<char> installation_path(kSbFileMaxPath);
+ if (ImGetInstallationPath(current_installation, installation_path.data(),
+ kSbFileMaxPath) != IM_ERROR) {
+ std::string bad_app_key_file_path =
+ starboard::loader_app::GetBadAppKeyFilePath(installation_path.data(),
+ app_key);
+ if (bad_app_key_file_path.empty()) {
+ SB_LOG(WARNING) << "Failed to get bad app key file path for path="
+ << installation_path.data()
+ << " and app_key=" << app_key;
+ } else {
+ if (!starboard::loader_app::CreateAppKeyFile(bad_app_key_file_path)) {
+ SB_LOG(WARNING) << "Failed to create bad app key file: "
+ << bad_app_key_file_path;
+ }
}
+ } else {
+ SB_LOG(WARNING) << "Failed to get installation path for index: "
+ << current_installation;
}
- } else {
- SB_LOG(WARNING) << "Failed to get installation path for index: "
- << current_installation;
}
current_installation = ImRevertToSuccessfulInstallation();
return current_installation;
@@ -124,7 +132,13 @@
void* LoadSlotManagedLibrary(const std::string& app_key,
const std::string& alternative_content_path,
- LibraryLoader* library_loader) {
+ LibraryLoader* library_loader,
+ bool use_compression,
+ bool use_memory_mapped_file) {
+ if (use_compression && use_memory_mapped_file) {
+ SB_LOG(ERROR) << "Using both compression and mmap files is not supported";
+ return NULL;
+ }
// Initialize the Installation Manager.
SB_CHECK(ImInitialize(kMaxNumInstallations, app_key.c_str()) == IM_SUCCESS)
<< "Abort. Failed to initialize Installation Manager";
@@ -150,7 +164,8 @@
// discard the image and auto rollback, but only if
// the current image is not the system image.
if (current_installation != 0) {
- current_installation = RevertBack(current_installation, app_key);
+ current_installation =
+ RevertBack(current_installation, app_key, true /* mark_bad */);
}
}
}
@@ -167,7 +182,8 @@
// Hard failure. Discard the image and auto rollback, but only if
// the current image is not the system image.
if (current_installation != 0) {
- current_installation = RevertBack(current_installation, app_key);
+ current_installation =
+ RevertBack(current_installation, app_key, true /* mark_bad */);
continue;
} else {
// The system image at index 0 failed.
@@ -178,41 +194,49 @@
SB_DLOG(INFO) << "installation_path=" << installation_path.data();
if (current_installation != 0) {
- // Cleanup expired drain files
- DrainFileClear(installation_path.data(), app_key.c_str(), true);
+ // Cleanup all expired files from all apps.
+ DrainFileClearExpired(installation_path.data());
+
+ // Cleanup all drain files from the current app.
+ DrainFileClearForApp(installation_path.data(), app_key.c_str());
// Check for bad file.
if (CheckBadFileExists(installation_path.data(), app_key.c_str())) {
SB_LOG(INFO) << "Bad app key file";
- current_installation = RevertBack(current_installation, app_key);
+ current_installation =
+ RevertBack(current_installation, app_key, true /* mark_bad */);
continue;
}
// If the current installation is in use by an updater roll back.
- if (DrainFileDraining(installation_path.data(), "")) {
+ if (DrainFileIsAnotherAppDraining(installation_path.data(),
+ app_key.c_str())) {
SB_LOG(INFO) << "Active slot draining";
- current_installation = RevertBack(current_installation, app_key);
+ current_installation =
+ RevertBack(current_installation, app_key, false /* mark_bad */);
continue;
}
// Adopt installation performed from different app.
if (!AdoptInstallation(current_installation, installation_path.data(),
app_key.c_str())) {
SB_LOG(INFO) << "Unable to adopt installation";
- current_installation = RevertBack(current_installation, app_key);
+ current_installation =
+ RevertBack(current_installation, app_key, true /* mark_bad */);
continue;
}
}
// installation_n/lib/libcobalt.so
std::vector<char> lib_path(kSbFileMaxPath);
+ std::string library_name;
+ if (use_compression) {
+ library_name = kCompressedCobaltLibraryName;
+ } else {
+ library_name = kCobaltLibraryName;
+ }
SbStringFormatF(lib_path.data(), kSbFileMaxPath, "%s%s%s%s%s",
installation_path.data(), kSbFileSepString,
- kCobaltLibraryPath, kSbFileSepString, kCobaltLibraryName);
- if (!SbFileExists(lib_path.data())) {
- // Try the compressed path if the binary doesn't exits.
- starboard::strlcat(lib_path.data(),
- starboard::elf_loader::kCompressionSuffix,
- kSbFileMaxPath);
- }
+ kCobaltLibraryPath, kSbFileSepString, library_name.c_str());
+
SB_LOG(INFO) << "lib_path=" << lib_path.data();
std::string content;
@@ -229,13 +253,15 @@
SB_LOG(INFO) << "content=" << content;
- if (!library_loader->Load(lib_path.data(), content.c_str())) {
+ if (!library_loader->Load(lib_path.data(), content.c_str(), use_compression,
+ use_memory_mapped_file)) {
SB_LOG(WARNING) << "Failed to load Cobalt!";
// Hard failure. Discard the image and auto rollback, but only if
// the current image is not the system image.
if (current_installation != 0) {
- current_installation = RevertBack(current_installation, app_key);
+ current_installation =
+ RevertBack(current_installation, app_key, true /* mark_bad */);
continue;
} else {
// The system image at index 0 failed.
@@ -261,7 +287,8 @@
// Hard failure. Discard the image and auto rollback, but only if
// the current image is not the system image.
if (current_installation != 0) {
- current_installation = RevertBack(current_installation, app_key);
+ current_installation =
+ RevertBack(current_installation, app_key, true /* mark_bad */);
continue;
} else {
// The system image at index 0 failed.
@@ -294,7 +321,8 @@
// Hard failure. Discard the image and auto rollback, but only if
// the current image is not the system image.
if (current_installation != 0) {
- current_installation = RevertBack(current_installation, app_key);
+ current_installation =
+ RevertBack(current_installation, app_key, true /* mark_bad */);
continue;
} else {
// The system image at index 0 failed.
diff --git a/src/starboard/loader_app/slot_management.h b/src/starboard/loader_app/slot_management.h
index 319cec9..b5a2237 100644
--- a/src/starboard/loader_app/slot_management.h
+++ b/src/starboard/loader_app/slot_management.h
@@ -26,9 +26,14 @@
virtual ~LibraryLoader() {}
// Load the library with the provided full path to |library_path| and
- // |content_path|.
+ // |content_path|. If |use_compression| is true the library is assumed
+ // to be compressed. If |use_memory_mapped_file| is true the library
+ // would be loaded as a memory mapped file. The |use_compression| and
+ // |use_memory_mapped_file| are not compatible and can't be both enabled.
virtual bool Load(const std::string& library_path,
- const std::string& content_path) = 0;
+ const std::string& content_path,
+ bool use_compression,
+ bool use_memory_mapped_file) = 0;
// Resolve a symbol by name.
virtual void* Resolve(const std::string& symbol) = 0;
@@ -39,10 +44,16 @@
// The actual loading from the slot is performed by the |library_loader|.
// An alternative content can be used by specifying non-empty
// |alternative_content_path| with the full path to the content.
+// If |use_compression| is true the library is assumed
+// to be compressed. If |use_memory_mapped_file| is true the library
+// would be loaded as a memory mapped file. The |use_compression| and
+// |use_memory_mapped_file| are not compatible and can't be both enabled.
// Returns a pointer to the |SbEventHandle| symbol in the library.
void* LoadSlotManagedLibrary(const std::string& app_key,
const std::string& alternative_content_path,
- LibraryLoader* library_loader);
+ LibraryLoader* library_loader,
+ bool use_compression,
+ bool use_memory_mapped_file);
} // namespace loader_app
} // namespace starboard
diff --git a/src/starboard/loader_app/slot_management_test.cc b/src/starboard/loader_app/slot_management_test.cc
index 9f63b1a..1ed55d2 100644
--- a/src/starboard/loader_app/slot_management_test.cc
+++ b/src/starboard/loader_app/slot_management_test.cc
@@ -48,25 +48,23 @@
class MockLibraryLoader : public LibraryLoader {
public:
- MOCK_METHOD2(Load,
+ MOCK_METHOD4(Load,
bool(const std::string& library_path,
- const std::string& content_path));
+ const std::string& content_path,
+ bool use_compression,
+ bool use_memory_mapped_file));
MOCK_METHOD1(Resolve, void*(const std::string& symbol));
};
-class SlotManagementTest : public testing::Test {
+class SlotManagementTest : public testing::TestWithParam<bool> {
protected:
virtual void SetUp() {
slot_0_libcobalt_path_ =
- CreatePath({"content", "app", "cobalt", "lib", "libcobalt.so.lz4"});
+ CreatePath({"content", "app", "cobalt", "lib", "libcobalt"});
slot_0_content_path_ = CreatePath({"content", "app", "cobalt", "content"});
-
- slot_1_libcobalt_path_ =
- CreatePath({"installation_1", "lib", "libcobalt.so.lz4"});
+ slot_1_libcobalt_path_ = CreatePath({"installation_1", "lib", "libcobalt"});
slot_1_content_path_ = CreatePath({"installation_1", "content"});
-
- slot_2_libcobalt_path_ =
- CreatePath({"installation_2", "lib", "libcobalt.so.lz4"});
+ slot_2_libcobalt_path_ = CreatePath({"installation_2", "lib", "libcobalt"});
slot_2_content_path_ = CreatePath({"installation_2", "content"});
std::vector<char> buf(kSbFileMaxPath);
@@ -74,6 +72,14 @@
buf.data(), kSbFileMaxPath);
}
+ void AddFileExtension(std::string& path) {
+ if (GetParam()) {
+ path += ".lz4";
+ } else {
+ path += ".so";
+ }
+ }
+
std::string CreatePath(std::initializer_list<std::string> path_elements) {
std::string result;
for (const std::string& path : path_elements) {
@@ -135,10 +141,14 @@
}
void VerifyLoad(const std::string& lib, const std::string& content) {
+ bool use_compression = GetParam();
MockLibraryLoader library_loader;
+ std::string full_lib_path = lib;
+ AddFileExtension(full_lib_path);
EXPECT_CALL(library_loader,
- Load(testing::EndsWith(lib), testing::EndsWith(content)))
+ Load(testing::EndsWith(full_lib_path),
+ testing::EndsWith(content), use_compression, false))
.Times(1)
.WillOnce(testing::Return(true));
EXPECT_CALL(library_loader, Resolve("GetEvergreenSabiString"))
@@ -153,7 +163,8 @@
.Times(1)
.WillOnce(testing::Return(reinterpret_cast<void*>(&SbEventFake)));
ASSERT_EQ(&SbEventFake,
- LoadSlotManagedLibrary(kTestAppKey, "", &library_loader));
+ LoadSlotManagedLibrary(kTestAppKey, "", &library_loader,
+ use_compression, false));
}
protected:
@@ -166,7 +177,7 @@
bool storage_path_implemented_;
};
-TEST_F(SlotManagementTest, SystemSlot) {
+TEST_P(SlotManagementTest, SystemSlot) {
if (!storage_path_implemented_) {
return;
}
@@ -178,7 +189,7 @@
VerifyBadFile(0, kTestAppKey, false);
}
-TEST_F(SlotManagementTest, AdoptSlot) {
+TEST_P(SlotManagementTest, AdoptSlot) {
if (!storage_path_implemented_) {
return;
}
@@ -196,7 +207,7 @@
VerifyBadFile(1, kTestAppKey, false);
}
-TEST_F(SlotManagementTest, GoodSlot) {
+TEST_P(SlotManagementTest, GoodSlot) {
if (!storage_path_implemented_) {
return;
}
@@ -213,7 +224,7 @@
VerifyBadFile(2, kTestAppKey, false);
}
-TEST_F(SlotManagementTest, NotAdoptSlot) {
+TEST_P(SlotManagementTest, NotAdoptSlot) {
if (!storage_path_implemented_) {
return;
}
@@ -230,7 +241,7 @@
VerifyBadFile(2, kTestAppKey, true);
}
-TEST_F(SlotManagementTest, BadSlot) {
+TEST_P(SlotManagementTest, BadSlot) {
if (!storage_path_implemented_) {
return;
}
@@ -245,7 +256,7 @@
VerifyGoodFile(1, kTestAppKey, false);
}
-TEST_F(SlotManagementTest, DrainingSlot) {
+TEST_P(SlotManagementTest, DrainingSlot) {
if (!storage_path_implemented_) {
return;
}
@@ -258,10 +269,10 @@
ImUninitialize();
VerifyLoad(slot_0_libcobalt_path_, slot_0_content_path_);
VerifyGoodFile(1, kTestAppKey, false);
- VerifyBadFile(1, kTestAppKey, true);
+ VerifyBadFile(1, kTestAppKey, false);
}
-TEST_F(SlotManagementTest, AlternativeContent) {
+TEST_P(SlotManagementTest, AlternativeContent) {
if (!storage_path_implemented_) {
return;
}
@@ -274,8 +285,11 @@
MockLibraryLoader library_loader;
- EXPECT_CALL(library_loader, Load(testing::EndsWith(slot_0_libcobalt_path_),
- testing::EndsWith("/foo")))
+ std::string full_lib_path = slot_0_libcobalt_path_;
+ AddFileExtension(full_lib_path);
+ EXPECT_CALL(library_loader,
+ Load(testing::EndsWith(full_lib_path), testing::EndsWith("/foo"),
+ GetParam(), false))
.Times(1)
.WillOnce(testing::Return(true));
EXPECT_CALL(library_loader, Resolve("GetEvergreenSabiString"))
@@ -291,10 +305,11 @@
.Times(1)
.WillOnce(testing::Return(reinterpret_cast<void*>(&SbEventFake)));
ASSERT_EQ(&SbEventFake,
- LoadSlotManagedLibrary(kTestAppKey, "/foo", &library_loader));
+ LoadSlotManagedLibrary(kTestAppKey, "/foo", &library_loader,
+ GetParam(), false));
}
-TEST_F(SlotManagementTest, BadSabi) {
+TEST_P(SlotManagementTest, BadSabi) {
if (!storage_path_implemented_) {
return;
}
@@ -314,8 +329,11 @@
MockLibraryLoader library_loader;
- EXPECT_CALL(library_loader, Load(testing::EndsWith(slot_2_libcobalt_path_),
- testing::EndsWith(slot_2_content_path_)))
+ std::string slot2_libcobalt_full = slot_2_libcobalt_path_;
+ AddFileExtension(slot2_libcobalt_full);
+ EXPECT_CALL(library_loader,
+ Load(testing::EndsWith(slot2_libcobalt_full),
+ testing::EndsWith(slot_2_content_path_), GetParam(), false))
.Times(1)
.WillOnce(testing::Return(true));
@@ -326,8 +344,11 @@
.WillOnce(
testing::Return(reinterpret_cast<void*>(&GetEvergreenSabiString)));
- EXPECT_CALL(library_loader, Load(testing::EndsWith(slot_1_libcobalt_path_),
- testing::EndsWith(slot_1_content_path_)))
+ std::string slot1_libcobalt_full = slot_1_libcobalt_path_;
+ AddFileExtension(slot1_libcobalt_full);
+ EXPECT_CALL(library_loader,
+ Load(testing::EndsWith(slot1_libcobalt_full),
+ testing::EndsWith(slot_1_content_path_), GetParam(), false))
.Times(1)
.WillOnce(testing::Return(true));
@@ -341,9 +362,14 @@
.WillOnce(testing::Return(reinterpret_cast<void*>(&SbEventFake)));
ASSERT_EQ(&SbEventFake,
- LoadSlotManagedLibrary(kTestAppKey, "", &library_loader));
+ LoadSlotManagedLibrary(kTestAppKey, "", &library_loader, GetParam(),
+ false));
}
+INSTANTIATE_TEST_CASE_P(SlotManagementTests,
+ SlotManagementTest,
+ ::testing::Bool());
+
} // namespace
} // namespace loader_app
} // namespace starboard
diff --git a/src/starboard/nplb/media_can_play_mime_and_key_system_test.cc b/src/starboard/nplb/media_can_play_mime_and_key_system_test.cc
index 3d5317c..81f85e2 100644
--- a/src/starboard/nplb/media_can_play_mime_and_key_system_test.cc
+++ b/src/starboard/nplb/media_can_play_mime_and_key_system_test.cc
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <map>
+
#include "starboard/common/string.h"
#include "starboard/media.h"
#include "starboard/nplb/drm_helpers.h"
@@ -290,280 +292,190 @@
}
}
-// TODO: Create an abstraction to shorten the length of this test.
TEST(SbMediaCanPlayMimeAndKeySystem, PrintMaximumSupport) {
- // AVC
- std::string avc_resolution = "Unsupported";
- // 1080p
- SbMediaSupportType result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"avc1.4d402a\"; width=1920; height=1080; "
- "framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- avc_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"avc1.64002a\"; width=2560; height=1440; "
- "framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- avc_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"avc1.64002a\"; width=3840; height=2160; "
- "framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- avc_resolution = "4K";
+ const char* kResolution1080p = "1080p";
+ const char* kResolution2k = "2K";
+ const char* kResolution4k = "4K";
+ const char* kResolution8k = "8K";
+ auto get_max_video_codec_resolution =
+ [=](std::string video_type,
+ const std::map<const char*, std::string> codec_params,
+ std::string framerate) {
+ std::map<const char*, std::string> resolutions = {
+ {kResolution1080p, "width=1920; height=1080;"},
+ {kResolution2k, "width=2560; height=1440;"},
+ {kResolution4k, "width=3840; height=2160;"},
+ {kResolution8k, "width=7680; height=4320;"}};
+
+ std::string max_supported_resolution;
+ for (auto&& it : resolutions) {
+ if (codec_params.find(it.first) == codec_params.end()) {
+ break;
+ }
+ std::string content_type = video_type + "; codecs=\"" +
+ codec_params.at(it.first) + "\"; " +
+ it.second + " framerate=" + framerate;
+ if (SbMediaCanPlayMimeAndKeySystem(content_type.c_str(), "") !=
+ kSbMediaSupportTypeProbably) {
+ break;
+ }
+ max_supported_resolution = it.first;
+ }
+ return max_supported_resolution.empty() ? "Unsupported"
+ : max_supported_resolution;
+ };
+
+ auto get_drm_system_support = [](std::string key_system) {
+ std::map<const char*, std::string> content_types = {
+ {"video/mp4; codecs=\"avc1.4d402a\"", "AVC"},
+ {"video/webm; codecs=\"vp9\"", "VP9"},
+ {"video/mp4; codecs=\"av01.0.08M.08\"", "AV1"},
+ {"audio/mp4; codecs=\"mp4a.40.2\"", "AAC"},
+ {"audio/webm; codecs=\"opus\"", "Opus"},
+ {"audio/mp4; codecs=\"ac-3\"", "AC-3"},
+ {"audio/mp4; codecs=\"ec-3\"", "E-AC-3"}};
+ std::string supported_codecs;
+ for (auto&& it : content_types) {
+ if (SbMediaCanPlayMimeAndKeySystem(it.first, key_system.c_str()) ==
+ kSbMediaSupportTypeProbably) {
+ supported_codecs += it.second + " ";
}
}
+ return supported_codecs.empty() ? "Unsupported" : supported_codecs;
+ };
+
+ std::string avc_support =
+ get_max_video_codec_resolution("video/mp4",
+ {{kResolution1080p, "avc1.4d402a"},
+ {kResolution2k, "avc1.64002a"},
+ {kResolution4k, "avc1.64002a"}},
+ "30");
+ if (avc_support != "Unsupported") {
+ avc_support += "\n\tAVC HFR: " + get_max_video_codec_resolution(
+ "video/mp4",
+ {{kResolution1080p, "avc1.4d402a"},
+ {kResolution2k, "avc1.64402a"},
+ {kResolution4k, "avc1.64402a"}},
+ "60");
}
- // AVC HFR
- std::string avc_hfr_resolution = "Unsupported";
- // 1080p
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"avc1.4d402a\"; width=1920; height=1080; "
- "framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- avc_hfr_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"avc1.64402a\"; width=2560; height=1440; "
- "framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- avc_hfr_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"avc1.64402a\"; width=3840; height=2160; "
- "framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- avc_hfr_resolution = "4K";
- }
+ std::string vp9_support =
+ get_max_video_codec_resolution("video/webm",
+ {{kResolution1080p, "vp9"},
+ {kResolution2k, "vp9"},
+ {kResolution4k, "vp9"}},
+ "30");
+ if (vp9_support != "Unsupported") {
+ vp9_support += "\n\tVP9 HFR: " +
+ get_max_video_codec_resolution("video/webm",
+ {{kResolution1080p, "vp9"},
+ {kResolution2k, "vp9"},
+ {kResolution4k, "vp9"}},
+ "60");
+ std::string vp9_hdr_support = get_max_video_codec_resolution(
+ "video/webm",
+ {{kResolution1080p, "vp09.02.41.10.01.09.16.09.00"},
+ {kResolution2k, "vp09.02.51.10.01.09.16.09.00"},
+ {kResolution4k, "vp09.02.51.10.01.09.16.09.00"}},
+ "30");
+ if (vp9_hdr_support != "Unsupported") {
+ vp9_hdr_support +=
+ "\n\tVP9 HDR HFR: " +
+ get_max_video_codec_resolution(
+ "video/webm",
+ {{kResolution1080p, "vp09.02.41.10.01.09.16.09.00"},
+ {kResolution2k, "vp09.02.51.10.01.09.16.09.00"},
+ {kResolution4k, "vp09.02.51.10.01.09.16.09.00"}},
+ "60");
}
+ vp9_support += "\n\tVP9 HDR: " + vp9_hdr_support;
}
-
- // VP9
- std::string vp9_resolution = "Unsupported";
- // 1080p
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp9\"; width=1920; height=1080; framerate=30", "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp9\"; width=2560; height=1440; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp9\"; width=3840; height=2160; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_resolution = "4K";
- }
+ std::string av1_support =
+ get_max_video_codec_resolution("video/mp4",
+ {{kResolution1080p, "av01.0.08M.08"},
+ {kResolution2k, "av01.0.12M.08"},
+ {kResolution4k, "av01.0.12M.08"},
+ {kResolution8k, "av01.0.16M.08"}},
+ "30");
+ if (av1_support != "Unsupported") {
+ av1_support += "\n\tAV1 HFR: " + get_max_video_codec_resolution(
+ "video/mp4",
+ {{kResolution1080p, "av01.0.09M.08"},
+ {kResolution2k, "av01.0.12M.08"},
+ {kResolution4k, "av01.0.13M.08"},
+ {kResolution8k, "av01.0.17M.08"}},
+ "60");
+ std::string av1_hdr_support = get_max_video_codec_resolution(
+ "video/mp4",
+ {{kResolution1080p, "av01.0.09M.10.0.110.09.16.09.0"},
+ {kResolution2k, "av01.0.12M.10.0.110.09.16.09.0"},
+ {kResolution4k, "av01.0.13M.10.0.110.09.16.09.0"},
+ {kResolution8k, "av01.0.17M.10.0.110.09.16.09.0"}},
+ "30");
+ if (av1_hdr_support != "Unsupported") {
+ av1_hdr_support +=
+ "\n\tAV1 HDR HFR: " +
+ get_max_video_codec_resolution(
+ "video/mp4",
+ {{kResolution1080p, "av01.0.09M.10.0.110.09.16.09.0"},
+ {kResolution2k, "av01.0.12M.10.0.110.09.16.09.0"},
+ {kResolution4k, "av01.0.13M.10.0.110.09.16.09.0"},
+ {kResolution8k, "av01.0.17M.10.0.110.09.16.09.0"}},
+ "60");
}
+ av1_support += "\n\tAV1 HDR: " + av1_hdr_support;
}
- // VP9 HFR
- std::string vp9_hfr_resolution = "Unsupported";
- // 1080p
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp9\"; width=1920; height=1080; framerate=60", "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_hfr_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp9\"; width=2560; height=1440; framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_hfr_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp9\"; width=3840; height=2160; framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_hfr_resolution = "4K";
- }
- }
- }
+ std::string aac_support = SbMediaCanPlayMimeAndKeySystem(
+ "audio/mp4; codecs=\"mp4a.40.2\"; channels=2",
+ "") == kSbMediaSupportTypeProbably
+ ? "Supported"
+ : "Unsupported";
- // VP9 HDR
- std::string vp9_hdr_resolution = "Unsupported";
- // 1080p
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=1920; "
- "height=1080; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_hdr_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=2560; "
- "height=1440; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_hdr_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=3840; "
- "height=2160; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_hdr_resolution = "4K";
- }
- }
- }
+ std::string aac51_support = SbMediaCanPlayMimeAndKeySystem(
+ "audio/mp4; codecs=\"mp4a.40.2\"; channels=6",
+ "") == kSbMediaSupportTypeProbably
+ ? "Supported"
+ : "Unsupported";
- // AV1
- std::string av1_resolution = "Unsupported";
- // 1080p
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.09M.08\"; width=1920; "
- "height=1080; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.12M.08\"; width=2560; "
- "height=1440; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.12M.08\"; width=3840; "
- "height=2160; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_resolution = "4K";
- }
- }
- }
- // AV1 HFR
- std::string av1_hfr_resolution = "Unsupported";
- // 1080p
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.09M.08\"; width=1920; height=1080; "
- "framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_hfr_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.12M.08\"; width=2560; height=1440; "
- "framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_hfr_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.13M.08\"; width=3840; height=2160; "
- "framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_hfr_resolution = "4K";
- }
- }
- }
+ std::string opus_support = SbMediaCanPlayMimeAndKeySystem(
+ "audio/webm; codecs=\"opus\"; "
+ "channels=2; bitrate=128000;",
+ "") == kSbMediaSupportTypeProbably
+ ? "Supported"
+ : "Unsupported";
- // AV1 HDR
- std::string av1_hdr_resolution = "Unsupported";
- // 1080p
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.09M.10.0.110.09.16.09.0\"; width=1920; "
- "height=1080",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_hdr_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.12M.10.0.110.09.16.09.0\"; width=2560; "
- "height=1440",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_hdr_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.13M.10.0.110.09.16.09.0\"; width=3840; "
- "height=2160",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_hdr_resolution = "4K";
- }
- }
- }
+ std::string opus51_support = SbMediaCanPlayMimeAndKeySystem(
+ "audio/webm; codecs=\"opus\"; "
+ "channels=6; bitrate=576000;",
+ "") == kSbMediaSupportTypeProbably
+ ? "Supported"
+ : "Unsupported";
- // AAC
- std::string aac_support = "Unsupported";
- result = SbMediaCanPlayMimeAndKeySystem(
- "audio/mp4; codecs=\"mp4a.40.2\"; channels=2", "");
- if (result == kSbMediaSupportTypeProbably) {
- aac_support = "Supported";
- }
+ std::string ac3_support =
+ SbMediaCanPlayMimeAndKeySystem(
+ "audio/mp4; codecs=\"ac-3\"; channels=6; bitrate=512000", "") ==
+ kSbMediaSupportTypeProbably
+ ? "Supported"
+ : "Unsupported";
- // AAC51
- std::string aac51_support = "Unsupported";
- result = SbMediaCanPlayMimeAndKeySystem(
- "audio/mp4; codecs=\"mp4a.40.2\"; channels=6", "");
- if (result == kSbMediaSupportTypeProbably) {
- aac51_support = "Supported";
- }
+ std::string eac3_support =
+ SbMediaCanPlayMimeAndKeySystem(
+ "audio/mp4; codecs=\"ec-3\"; channels=6; bitrate=512000", "") ==
+ kSbMediaSupportTypeProbably
+ ? "Supported"
+ : "Unsupported";
- // Opus
- std::string opus_support = "Unsupported";
- result = SbMediaCanPlayMimeAndKeySystem(
- "audio/webm; codecs=\"opus\"; "
- "channels=2; bitrate=128000;",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- opus_support = "Supported";
- }
-
- // Opus51
- std::string opus51_support = "Unsupported";
- result = SbMediaCanPlayMimeAndKeySystem(
- "audio/webm; codecs=\"opus\"; "
- "channels=6; bitrate=576000;",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- opus51_support = "Supported";
- }
-
- // AC-3
- std::string ac3_support = "Unsupported";
- result = SbMediaCanPlayMimeAndKeySystem(
- "audio/mp4; codecs=\"ac-3\"; channels=6; bitrate=512000", "");
- if (result == kSbMediaSupportTypeProbably) {
- ac3_support = "Supported";
- }
-
- // E-AC-3
- std::string eac3_support = "Unsupported";
- result = SbMediaCanPlayMimeAndKeySystem(
- "audio/mp4; codecs=\"ec-3\"; channels=6; bitrate=512000", "");
- if (result == kSbMediaSupportTypeProbably) {
- eac3_support = "Supported";
- }
-
- SB_LOG(INFO) << "\nVideo Codec Capability:\n\tAVC: " << avc_resolution
- << "\n\tAVC HFR: " << avc_hfr_resolution
- << "\n\tVP9: " << vp9_resolution
- << "\n\tVP9 HFR: " << vp9_hfr_resolution
- << "\n\tVP9 HDR: " << vp9_hdr_resolution
- << "\n\tAV1: " << av1_resolution
- << "\n\tAV1 HFR:" << av1_hfr_resolution
- << "\n\tAV1 HDR:" << av1_hdr_resolution
+ SB_LOG(INFO) << "\nVideo Codec Capability:\n\tAVC: " << avc_support
+ << "\n\tVP9: " << vp9_support << "\n\tAV1: " << av1_support
<< "\n\nAudio Codec Capability:\n\tAAC: " << aac_support
<< "\n\tAAC 51: " << aac51_support
<< "\n\tOpus: " << opus_support
<< "\n\tOpus 51: " << opus51_support
- << "\n\tAC-3: " << ac3_support << "\n\tE-AC-3: " << eac3_support;
+ << "\n\tAC-3: " << ac3_support << "\n\tE-AC-3: " << eac3_support
+ << "\n\nDRM Capability:\n\tWidevine: "
+ << get_drm_system_support("com.widevine") << "\n\tPlayReady: "
+ << get_drm_system_support("com.youtube.playready");
}
// TODO: Create an abstraction to shorten the length of this test.
diff --git a/src/starboard/shared/libjpeg/jpeg_image_decoder.cc b/src/starboard/shared/libjpeg/jpeg_image_decoder.cc
index cbd6adb..65aba00 100644
--- a/src/starboard/shared/libjpeg/jpeg_image_decoder.cc
+++ b/src/starboard/shared/libjpeg/jpeg_image_decoder.cc
@@ -158,9 +158,13 @@
FillRow<2, 1, 0, 3>(static_cast<int>(info->image_width), pixel_data,
sample_buffer);
break;
+ // All the other decode formats are not supported.
case kSbDecodeTargetFormat2PlaneYUVNV12:
case kSbDecodeTargetFormat3PlaneYUVI420:
case kSbDecodeTargetFormat3Plane10BitYUVI420:
+#if SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+ case kSbDecodeTargetFormat3Plane10BitYUVI420Compact:
+#endif // SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
case kSbDecodeTargetFormat1PlaneUYVY:
case kSbDecodeTargetFormatInvalid:
SB_NOTREACHED();
diff --git a/src/starboard/shared/posix/free_space.cc b/src/starboard/shared/posix/free_space.cc
new file mode 100644
index 0000000..f66de59
--- /dev/null
+++ b/src/starboard/shared/posix/free_space.cc
@@ -0,0 +1,55 @@
+// 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/posix/free_space.h"
+
+#include <sys/statvfs.h>
+#include <vector>
+
+#include "cobalt/extension/free_space.h"
+#include "starboard/common/log.h"
+#include "starboard/configuration_constants.h"
+
+namespace starboard {
+namespace shared {
+namespace posix {
+
+namespace {
+
+int64_t MeasureFreeSpace(SbSystemPathId system_path_id) {
+ std::vector<char> path(kSbFileMaxPath + 1);
+ if (!SbSystemGetPath(system_path_id, path.data(), path.size())) {
+ return -1;
+ }
+ struct statvfs s;
+ if (statvfs(path.data(), &s) != 0) {
+ return -1;
+ }
+
+ return s.f_bsize * s.f_bfree;
+}
+
+const CobaltExtensionFreeSpaceApi kFreeSpaceApi = {
+ kCobaltExtensionFreeSpaceName, 1, &MeasureFreeSpace,
+};
+
+} // namespace
+
+const void* GetFreeSpaceApi() {
+ return &kFreeSpaceApi;
+}
+
+} // namespace posix
+} // namespace shared
+} // namespace starboard
diff --git a/src/starboard/shared/posix/free_space.h b/src/starboard/shared/posix/free_space.h
new file mode 100644
index 0000000..1d44447
--- /dev/null
+++ b/src/starboard/shared/posix/free_space.h
@@ -0,0 +1,28 @@
+// 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_POSIX_FREE_SPACE_H_
+#define STARBOARD_SHARED_POSIX_FREE_SPACE_H_
+
+namespace starboard {
+namespace shared {
+namespace posix {
+
+const void* GetFreeSpaceApi();
+
+} // namespace posix
+} // namespace shared
+} // namespace starboard
+
+#endif // STARBOARD_SHARED_POSIX_FREE_SPACE_H_
diff --git a/src/starboard/shared/posix/socket_bind.cc b/src/starboard/shared/posix/socket_bind.cc
index fcb5869..42f8335 100644
--- a/src/starboard/shared/posix/socket_bind.cc
+++ b/src/starboard/shared/posix/socket_bind.cc
@@ -28,21 +28,21 @@
const SbSocketAddress* local_address) {
if (!SbSocketIsValid(socket)) {
errno = EBADF;
- SB_DLOG(ERROR) << __FUNCTION__ << ": Invalid socket";
+ SB_LOG(ERROR) << __FUNCTION__ << ": Invalid socket";
return kSbSocketErrorFailed;
}
sbposix::SockAddr sock_addr;
if (!sock_addr.FromSbSocketAddress(local_address)) {
- SB_DLOG(ERROR) << __FUNCTION__ << ": Invalid address";
+ SB_LOG(ERROR) << __FUNCTION__ << ": Invalid address";
return (socket->error = sbposix::TranslateSocketErrno(EINVAL));
}
SB_DCHECK(socket->socket_fd >= 0);
if (local_address->type != socket->address_type) {
- SB_DLOG(ERROR) << __FUNCTION__ << ": Incompatible addresses: "
- << "socket type = " << socket->address_type
- << ", argument type = " << local_address->type;
+ SB_LOG(ERROR) << __FUNCTION__ << ": Incompatible addresses: "
+ << "socket type = " << socket->address_type
+ << ", argument type = " << local_address->type;
return (socket->error = sbposix::TranslateSocketErrno(EAFNOSUPPORT));
}
@@ -63,7 +63,7 @@
int result = HANDLE_EINTR(
bind(socket->socket_fd, sock_addr.sockaddr(), sock_addr.length));
if (result != 0) {
- SB_DLOG(ERROR) << __FUNCTION__ << ": Bind failed. errno=" << errno;
+ SB_LOG(ERROR) << __FUNCTION__ << ": Bind failed. errno=" << errno;
return (socket->error = sbposix::TranslateSocketErrno(errno));
}
diff --git a/src/starboard/shared/posix/socket_connect.cc b/src/starboard/shared/posix/socket_connect.cc
index 0172da5..aa47dea 100644
--- a/src/starboard/shared/posix/socket_connect.cc
+++ b/src/starboard/shared/posix/socket_connect.cc
@@ -30,15 +30,15 @@
sbposix::SockAddr sock_addr;
if (!sock_addr.FromSbSocketAddress(address)) {
- SB_DLOG(ERROR) << __FUNCTION__ << ": Invalid address";
+ SB_LOG(ERROR) << __FUNCTION__ << ": Invalid address";
return (socket->error = kSbSocketErrorFailed);
}
SB_DCHECK(socket->socket_fd >= 0);
if (address->type != socket->address_type) {
- SB_DLOG(ERROR) << __FUNCTION__ << ": Incompatible addresses: "
- << "socket type = " << socket->address_type
- << ", argument type = " << address->type;
+ SB_LOG(ERROR) << __FUNCTION__ << ": Incompatible addresses: "
+ << "socket type = " << socket->address_type
+ << ", argument type = " << address->type;
return (socket->error = kSbSocketErrorFailed);
}
@@ -49,7 +49,7 @@
}
if (result != 0) {
- SB_DLOG(ERROR) << __FUNCTION__ << ": connect failed: " << errno;
+ SB_LOG(ERROR) << __FUNCTION__ << ": connect failed: " << errno;
return (socket->error = kSbSocketErrorFailed);
}
diff --git a/src/starboard/shared/posix/socket_internal.cc b/src/starboard/shared/posix/socket_internal.cc
index 1b18007..3e60e5b 100644
--- a/src/starboard/shared/posix/socket_internal.cc
+++ b/src/starboard/shared/posix/socket_internal.cc
@@ -34,7 +34,7 @@
const socklen_t kAddressStructLengthIpv6 =
static_cast<socklen_t>(sizeof(struct sockaddr_in6));
#endif
-}
+} // namespace
SbSocketError TranslateSocketErrno(int error) {
switch (error) {
@@ -73,8 +73,8 @@
int result =
setsockopt(socket->socket_fd, level, option_code, &on, sizeof(on));
if (result != 0) {
- SB_DLOG(ERROR) << "Failed to set " << option_name << " on socket "
- << socket->socket_fd << ", errno = " << errno;
+ SB_LOG(ERROR) << "Failed to set " << option_name << " on socket "
+ << socket->socket_fd << ", errno = " << errno;
socket->error = TranslateSocketErrno(errno);
return false;
}
@@ -97,8 +97,8 @@
int result =
setsockopt(socket->socket_fd, level, option_code, &value, sizeof(value));
if (result != 0) {
- SB_DLOG(ERROR) << "Failed to set " << option_name << " on socket "
- << socket->socket_fd << ", errno = " << errno;
+ SB_LOG(ERROR) << "Failed to set " << option_name << " on socket "
+ << socket->socket_fd << ", errno = " << errno;
socket->error = TranslateSocketErrno(errno);
return false;
}
@@ -147,7 +147,7 @@
return false;
}
- // Check that we have been properly initialized.
+// Check that we have been properly initialized.
#if SB_HAS(IPV6)
SB_DCHECK(length == kAddressStructLengthIpv4 ||
length == kAddressStructLengthIpv6);
@@ -209,7 +209,7 @@
#endif
}
- SB_DLOG(WARNING) << "Unrecognized address family: " << family;
+ SB_LOG(WARNING) << "Unrecognized address family: " << family;
return false;
}
diff --git a/src/starboard/shared/posix/socket_join_multicast_group.cc b/src/starboard/shared/posix/socket_join_multicast_group.cc
index 72d3593..0dde43f 100644
--- a/src/starboard/shared/posix/socket_join_multicast_group.cc
+++ b/src/starboard/shared/posix/socket_join_multicast_group.cc
@@ -45,8 +45,8 @@
int result = setsockopt(socket->socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&imreq, sizeof(imreq));
if (result != 0) {
- SB_DLOG(ERROR) << "Failed to IP_ADD_MEMBERSHIP on socket "
- << socket->socket_fd << ", errno = " << errno;
+ SB_LOG(ERROR) << "Failed to IP_ADD_MEMBERSHIP on socket "
+ << socket->socket_fd << ", errno = " << errno;
socket->error = sbposix::TranslateSocketErrno(errno);
return false;
}
diff --git a/src/starboard/shared/posix/socket_receive_from.cc b/src/starboard/shared/posix/socket_receive_from.cc
index de1e2b8..8c0b779 100644
--- a/src/starboard/shared/posix/socket_receive_from.cc
+++ b/src/starboard/shared/posix/socket_receive_from.cc
@@ -49,14 +49,14 @@
int result = getpeername(socket->socket_fd, sock_addr.sockaddr(),
&sock_addr.length);
if (result < 0) {
- SB_DLOG(ERROR) << __FUNCTION__
- << ": getpeername failed, errno = " << errno;
+ SB_LOG(ERROR) << __FUNCTION__
+ << ": getpeername failed, errno = " << errno;
socket->error = sbposix::TranslateSocketErrno(errno);
return -1;
}
if (!sock_addr.ToSbSocketAddress(out_source)) {
- SB_DLOG(FATAL) << __FUNCTION__ << ": Bad TCP source address.";
+ SB_LOG(FATAL) << __FUNCTION__ << ": Bad TCP source address.";
socket->error = kSbSocketErrorFailed;
return -1;
}
@@ -71,7 +71,7 @@
if (IsReportableErrno(errno) &&
socket->error != sbposix::TranslateSocketErrno(errno)) {
- SB_DLOG(ERROR) << "recv failed, errno = " << errno;
+ SB_LOG(ERROR) << "recv failed, errno = " << errno;
}
socket->error = sbposix::TranslateSocketErrno(errno);
return -1;
@@ -84,7 +84,7 @@
if (bytes_read >= 0) {
if (out_source) {
if (!sock_addr.ToSbSocketAddress(out_source)) {
- SB_DLOG(FATAL) << __FUNCTION__ << ": Bad UDP source address.";
+ SB_LOG(FATAL) << __FUNCTION__ << ": Bad UDP source address.";
socket->error = kSbSocketErrorFailed;
return -1;
}
@@ -96,7 +96,7 @@
if (errno != EAGAIN && errno != EWOULDBLOCK &&
socket->error != sbposix::TranslateSocketErrno(errno)) {
- SB_DLOG(ERROR) << "recvfrom failed, errno = " << errno;
+ SB_LOG(ERROR) << "recvfrom failed, errno = " << errno;
}
socket->error = sbposix::TranslateSocketErrno(errno);
return -1;
diff --git a/src/starboard/shared/posix/socket_send_to.cc b/src/starboard/shared/posix/socket_send_to.cc
index cdee458..3e81ae0 100644
--- a/src/starboard/shared/posix/socket_send_to.cc
+++ b/src/starboard/shared/posix/socket_send_to.cc
@@ -39,7 +39,7 @@
SB_DCHECK(socket->socket_fd >= 0);
if (socket->protocol == kSbSocketProtocolTcp) {
if (destination) {
- SB_DLOG(FATAL) << "Destination passed to TCP send.";
+ SB_LOG(FATAL) << "Destination passed to TCP send.";
socket->error = kSbSocketErrorFailed;
return -1;
}
@@ -52,13 +52,13 @@
}
if (errno != EAGAIN && errno != EWOULDBLOCK) {
- SB_DLOG(ERROR) << "send failed, errno = " << errno;
+ SB_LOG(ERROR) << "send failed, errno = " << errno;
}
socket->error = sbposix::TranslateSocketErrno(errno);
return -1;
} else if (socket->protocol == kSbSocketProtocolUdp) {
if (!destination) {
- SB_DLOG(FATAL) << "No destination passed to UDP send.";
+ SB_LOG(FATAL) << "No destination passed to UDP send.";
socket->error = kSbSocketErrorFailed;
return -1;
}
@@ -68,7 +68,7 @@
socklen_t sockaddr_length = 0;
if (destination) {
if (!sock_addr.FromSbSocketAddress(destination)) {
- SB_DLOG(FATAL) << "Invalid destination passed to UDP send.";
+ SB_LOG(FATAL) << "Invalid destination passed to UDP send.";
socket->error = kSbSocketErrorFailed;
return -1;
}
@@ -84,7 +84,7 @@
}
if (errno != EAGAIN && errno != EWOULDBLOCK) {
- SB_DLOG(ERROR) << "sendto failed, errno = " << errno;
+ SB_LOG(ERROR) << "sendto failed, errno = " << errno;
}
socket->error = sbposix::TranslateSocketErrno(errno);
return -1;
diff --git a/src/third_party/devtools/devtools.gyp b/src/third_party/devtools/devtools.gyp
index d8a1f2c..17c7ce6 100644
--- a/src/third_party/devtools/devtools.gyp
+++ b/src/third_party/devtools/devtools.gyp
@@ -77,6 +77,7 @@
"front_end/cm_headless/module.json",
"front_end/cm_modes/module.json",
"front_end/cm_web_modes/module.json",
+ "front_end/cobalt/module.json",
"front_end/color_picker/module.json",
"front_end/color_picker/spectrum.css",
"front_end/common/module.json",
@@ -548,6 +549,7 @@
# (Not important since Cobalt doesn't need lighthouse)
'all_devtools_modules': [
+ "front_end/cobalt/cobalt.js",
"front_end/network/network.js",
"front_end/network/SignedExchangeInfoView.js",
"front_end/network/ResourceWebSocketFrameView.js",
@@ -1273,6 +1275,7 @@
],
'copied_devtools_modules': [
+ "<(resources_out_dir)/cobalt/cobalt.js",
"<(resources_out_dir)/network/network.js",
"<(resources_out_dir)/network/SignedExchangeInfoView.js",
"<(resources_out_dir)/network/ResourceWebSocketFrameView.js",
diff --git a/src/third_party/devtools/front_end/cobalt/cobalt.js b/src/third_party/devtools/front_end/cobalt/cobalt.js
new file mode 100644
index 0000000..a085c62
--- /dev/null
+++ b/src/third_party/devtools/front_end/cobalt/cobalt.js
@@ -0,0 +1,112 @@
+
+/**
+ * @implements {SDK.TargetManager.Observer}
+ * @unrestricted
+ */
+export default class CobaltPanel extends UI.VBox {
+ constructor() {
+ const trace_files = [
+ ['Trace', 'console_trace.json'],
+ ['Timed Trace', 'timed_trace.json']
+ ];
+ super(true, false);
+ SDK.targetManager.observeTargets(this);
+
+ this._target = UI.context.flavor(SDK.Target);
+ this._runtimeAgent = this._target.runtimeAgent();
+
+ this.element = this._shadowRoot.createChild('div');
+ this.element.textContent = 'Cobalt Console';
+ let download_element = this._shadowRoot.createChild('a', 'download');
+ download_element.style.display = 'none';
+
+ const traceContainer = this.element.createChild('div', 'trace-container');
+ traceContainer.appendChild(UI.createTextButton(Common.UIString('Start Trace'), event => {
+ console.log("Start Trace");
+ const filename = trace_files[0][1];
+ this.run(`(function() { window.h5vcc.traceEvent.start('${filename}');})()`);
+ console.log("Started Trace");
+ }));
+ traceContainer.appendChild(UI.createTextButton(Common.UIString('Stop Trace'), event => {
+ console.log("Stop Trace");
+ this.run(`(function() { window.h5vcc.traceEvent.stop();})()`);
+ console.log("Stopped Trace");
+ }));
+ trace_files.forEach( (file) => {
+ traceContainer.appendChild(UI.createTextButton(Common.UIString('Download ' + file[0]), event => {
+ console.log("Download Trace");
+ const filename = file[1];
+ this.run(`(function() { return window.h5vcc.traceEvent.read('${filename}');})()`).then(function (result) {
+ download_element.setAttribute('href', 'data:text/plain;charset=utf-8,' +
+ encodeURIComponent(result.result.value));
+ download_element.setAttribute('download', filename);
+ console.log("Downloaded Trace");
+ download_element.click();
+ download_element.setAttribute('href', undefined);
+ });
+ }));
+ });
+ }
+
+ async run(expression) {
+ return await this._runtimeAgent.invoke_evaluate({ expression, returnByValue: true });
+ }
+
+
+ /**
+ * @override
+ */
+ focus() {
+ }
+
+ /**
+ * @override
+ */
+ wasShown() {
+ super.wasShown();
+ if (this._model) {
+ this._model.enable();
+ }
+ }
+
+ /**
+ * @override
+ */
+ willHide() {
+ if (this._model) {
+ this._model.disable();
+ }
+ super.willHide();
+ }
+
+ /**
+ * @override
+ * @param {!SDK.Target} target
+ */
+ targetAdded(target) {
+ if (this._model) {
+ return;
+ }
+ if (!this._model) {
+ return;
+ }
+ }
+
+ /**
+ * @override
+ * @param {!SDK.Target} target
+ */
+ targetRemoved(target) {
+ if (!this._model || this._model.target() !== target) {
+ return;
+ }
+ }
+}
+
+/* Legacy exported object */
+self.Cobalt = self.Cobalt || {};
+
+/**
+ * @constructor
+ */
+self.Cobalt.CobaltPanel = CobaltPanel;
diff --git a/src/third_party/devtools/front_end/cobalt/module.json b/src/third_party/devtools/front_end/cobalt/module.json
new file mode 100644
index 0000000..7bc7faa
--- /dev/null
+++ b/src/third_party/devtools/front_end/cobalt/module.json
@@ -0,0 +1,19 @@
+{
+ "extensions": [
+ {
+ "type": "view",
+ "location": "panel",
+ "id": "cobalt",
+ "title": "Cobalt",
+ "order": 100,
+ "persistence": "permanent",
+ "className": "Cobalt.CobaltPanel"
+ }
+ ],
+ "dependencies": [],
+ "scripts": [],
+ "modules": [
+ "cobalt.js",
+ ],
+ "resources": []
+}
diff --git a/src/third_party/devtools/front_end/devtools_app.json b/src/third_party/devtools/front_end/devtools_app.json
index b29c5bb..c662566 100644
--- a/src/third_party/devtools/front_end/devtools_app.json
+++ b/src/third_party/devtools/front_end/devtools_app.json
@@ -8,6 +8,8 @@
// { "name": "animation" },
// { "name": "audits" },
{ "name": "browser_debugger" },
+ { "name": "cobalt" },
+
{ "name": "css_overview" },
{ "name": "cookie_table" },
// { "name": "dagre_layout", "type": "remote" },
diff --git a/src/third_party/zlib/google/zip_reader.cc b/src/third_party/zlib/google/zip_reader.cc
index f3e9d0f..90b4145 100644
--- a/src/third_party/zlib/google/zip_reader.cc
+++ b/src/third_party/zlib/google/zip_reader.cc
@@ -50,6 +50,10 @@
void SetTimeModified(const base::Time& time) override;
+#if defined(STARBOARD)
+ bool IsSuccessful();
+#endif
+
private:
size_t max_read_bytes_;
std::string* output_;
@@ -70,6 +74,12 @@
return true;
}
+#if defined(STARBOARD)
+bool StringWriterDelegate::IsSuccessful() {
+ return true;
+}
+#endif
+
bool StringWriterDelegate::WriteBytes(const char* data, int num_bytes) {
if (output_->size() + num_bytes > max_read_bytes_)
return false;
@@ -289,7 +299,7 @@
delegate->SetTimeModified(current_entry_info()->last_modified());
}
- return entire_file_extracted;
+ return entire_file_extracted && delegate->IsSuccessful();
}
void ZipReader::ExtractCurrentEntryToFilePathAsync(
@@ -467,6 +477,12 @@
return file_->Seek(base::File::FROM_BEGIN, 0) >= 0;
}
+#if defined(STARBOARD)
+bool FileWriterDelegate::IsSuccessful() {
+ return true;
+}
+#endif
+
bool FileWriterDelegate::WriteBytes(const char* data, int num_bytes) {
int bytes_written = file_->WriteAtCurrentPos(data, num_bytes);
if (bytes_written > 0)
@@ -497,6 +513,12 @@
return file_.IsValid();
}
+#ifdef STARBOARD
+bool FilePathWriterDelegate::IsSuccessful() {
+ return true;
+}
+#endif
+
bool FilePathWriterDelegate::WriteBytes(const char* data, int num_bytes) {
return num_bytes == file_.WriteAtCurrentPos(data, num_bytes);
}
diff --git a/src/third_party/zlib/google/zip_reader.h b/src/third_party/zlib/google/zip_reader.h
index d442d42..ca0414b 100644
--- a/src/third_party/zlib/google/zip_reader.h
+++ b/src/third_party/zlib/google/zip_reader.h
@@ -42,6 +42,11 @@
// Sets the last-modified time of the data.
virtual void SetTimeModified(const base::Time& time) = 0;
+
+#ifdef STARBOARD
+ // Returns true if the writer finished successfully.
+ virtual bool IsSuccessful() = 0;
+#endif
};
// This class is used for reading zip files. A typical use case of this
@@ -255,6 +260,10 @@
// Sets the last-modified time of the data.
void SetTimeModified(const base::Time& time) override;
+#ifdef STARBOARD
+ bool IsSuccessful() override;
+#endif
+
// Return the actual size of the file.
int64_t file_length() { return file_length_; }
@@ -289,6 +298,9 @@
// Sets the last-modified time of the data.
void SetTimeModified(const base::Time& time) override;
+#ifdef STARBOARD
+ bool IsSuccessful() override;
+#endif
private:
base::FilePath output_file_path_;
base::File file_;
diff --git a/src/third_party/zlib/google/zip_reader_unittest.cc b/src/third_party/zlib/google/zip_reader_unittest.cc
index a595102..cd4f1ae 100644
--- a/src/third_party/zlib/google/zip_reader_unittest.cc
+++ b/src/third_party/zlib/google/zip_reader_unittest.cc
@@ -115,6 +115,9 @@
class MockWriterDelegate : public zip::WriterDelegate {
public:
MOCK_METHOD0(PrepareOutput, bool());
+#if defined(STARBOARD)
+ MOCK_METHOD0(IsSuccessful, bool());
+#endif
MOCK_METHOD2(WriteBytes, bool(const char*, int));
MOCK_METHOD1(SetTimeModified, void(const base::Time&));
};
@@ -644,6 +647,10 @@
EXPECT_CALL(mock_writer, WriteBytes(_, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(mock_writer, SetTimeModified(_));
+#if defined(STARBOARD)
+ EXPECT_CALL(mock_writer, IsSuccessful())
+ .WillOnce(Return(true));
+#endif
base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
ZipReader reader;