Skip to content

Commit e1a7e99

Browse files
jellefoksgemini-code-assist[bot]
authored andcommitted
Implement basic application preload functionality. (#8928)
Implement background preloading support for instantaneous application launching. This feature allows Cobalt to initialize in a hidden state, deferring heavy resource creation until the application is revealed to the foreground. To further minimize footprint, the splash screen creation is skipped entirely during preloading. - Support starting Cobalt in a background/hidden state via the kSbEventTypePreload signal. - Defer creation of native windows and UI widgets until the application is revealed, minimizing initial memory and CPU footprint. - Skip creation of splash screen WebContents when preloaded to further reduce background resource usage. - Handle kSbEventTypeReveal events to transition the application from a background to a visible state and initialize deferred UI components. - Ensure standard Web APIs (document.visibilityState, document.hidden) correctly reflect the application's hidden state during preloading and its revealed state. - Standardize on visibility state propagation throughout the browser initialization chain (MainDelegate, BrowserClient, MainParts), keeping "preload" logic encapsulated at the Starboard entry point. - Consolidate window creation logic into a private internal helper to ensure consistent initialization for both normal and preloaded startups. - Refactor shell unit tests into a reusable ShellTestBase and extract common mock infrastructure into shell_test_support.h. - Add comprehensive unit tests: - LifecycleTest: Verifies startup states, revelation, and signal idempotency. - SplashScreenTest: Verifies that splash screen is skipped during preloading. - Add a comprehensive automated integration test (test_preload.sh) to verify preloading, revelation, and clean shutdown. - Add detailed documentation for developers, device manufacturers, and web designers in cobalt/doc/preload.md. - Enhanced developer tools with usage examples and captured test output. Bug: 447660888 --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> (cherry picked from commit 97ca4ee)
1 parent 0646489 commit e1a7e99

24 files changed

+866
-76
lines changed

cobalt/BUILD.gn

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,11 @@ test("cobalt_unittests") {
274274
}
275275

276276
if (!is_android) {
277-
sources += [ "//cobalt/shell/browser/splash_screen_unittest.cc" ]
277+
sources += [
278+
"//cobalt/shell/browser/lifecycle_unittest.cc",
279+
"//cobalt/shell/browser/shell_test_support.h",
280+
"//cobalt/shell/browser/splash_screen_unittest.cc",
281+
]
278282
}
279283

280284
public_deps = [ "//third_party/zlib/google:compression_utils" ]

cobalt/app/cobalt.cc

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -108,26 +108,16 @@ int InitCobalt(int argc, const char** argv, const char* initial_deep_link) {
108108

109109
void SbEventHandle(const SbEvent* event) {
110110
switch (event->type) {
111-
case kSbEventTypePreload: {
112-
#if BUILDFLAG(IS_COBALT_HERMETIC_BUILD)
113-
init_musl();
114-
#endif
115-
SbEventStartData* data = static_cast<SbEventStartData*>(event->data);
116-
g_exit_manager = new base::AtExitManager();
117-
g_content_main_delegate = new cobalt::CobaltMainDelegate();
118-
g_platform_event_source = new PlatformEventSourceStarboard();
119-
InitCobalt(data->argument_count,
120-
const_cast<const char**>(data->argument_values), data->link);
121-
122-
break;
123-
}
111+
case kSbEventTypePreload:
124112
case kSbEventTypeStart: {
125113
#if BUILDFLAG(IS_COBALT_HERMETIC_BUILD)
126114
init_musl();
127115
#endif
128116
SbEventStartData* data = static_cast<SbEventStartData*>(event->data);
129117
g_exit_manager = new base::AtExitManager();
130-
g_content_main_delegate = new cobalt::CobaltMainDelegate();
118+
g_content_main_delegate =
119+
new cobalt::CobaltMainDelegate(false /* is_content_browsertests */,
120+
event->type == kSbEventTypeStart);
131121
g_platform_event_source = new PlatformEventSourceStarboard();
132122
InitCobalt(data->argument_count,
133123
const_cast<const char**>(data->argument_values), data->link);
@@ -174,7 +164,9 @@ void SbEventHandle(const SbEvent* event) {
174164
break;
175165
}
176166
case kSbEventTypeConceal:
167+
break;
177168
case kSbEventTypeReveal:
169+
<<<<<<< HEAD
178170
break;
179171
case kSbEventTypeFreeze: {
180172
auto* client = cobalt::CobaltContentBrowserClient::Get();
@@ -183,6 +175,11 @@ void SbEventHandle(const SbEvent* event) {
183175
}
184176
break;
185177
}
178+
=======
179+
content::Shell::OnReveal();
180+
break;
181+
case kSbEventTypeFreeze:
182+
>>>>>>> 97ca4ee459 (Implement basic application preload functionality. (#8928))
186183
case kSbEventTypeUnfreeze:
187184
break;
188185
case kSbEventTypeInput:

cobalt/app/cobalt_main_delegate.cc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
namespace cobalt {
3232

33+
<<<<<<< HEAD
3334
CobaltMainDelegate::CobaltMainDelegate() : content::ShellMainDelegate() {
3435
CHECK_CALLED_ON_VALID_THREAD(thread_checker_);
3536
}
@@ -40,6 +41,15 @@ CobaltMainDelegate::~CobaltMainDelegate() {
4041

4142
std::optional<int> CobaltMainDelegate::BasicStartupComplete() {
4243
CHECK_CALLED_ON_VALID_THREAD(thread_checker_);
44+
=======
45+
CobaltMainDelegate::CobaltMainDelegate(bool is_content_browsertests,
46+
bool is_visible)
47+
: content::ShellMainDelegate(is_content_browsertests),
48+
is_visible_(is_visible) {}
49+
50+
CobaltMainDelegate::~CobaltMainDelegate() {}
51+
absl::optional<int> CobaltMainDelegate::BasicStartupComplete() {
52+
>>>>>>> 97ca4ee459 (Implement basic application preload functionality. (#8928))
4353
base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
4454
cl->AppendSwitch(switches::kEnableAggressiveDOMStorageFlushing);
4555
cl->AppendSwitch(switches::kDisableGpuShaderDiskCache);
@@ -48,8 +58,12 @@ std::optional<int> CobaltMainDelegate::BasicStartupComplete() {
4858

4959
content::ContentBrowserClient*
5060
CobaltMainDelegate::CreateContentBrowserClient() {
61+
<<<<<<< HEAD
5162
CHECK_CALLED_ON_VALID_THREAD(thread_checker_);
5263
browser_client_ = std::make_unique<CobaltContentBrowserClient>();
64+
=======
65+
browser_client_ = std::make_unique<CobaltContentBrowserClient>(is_visible_);
66+
>>>>>>> 97ca4ee459 (Implement basic application preload functionality. (#8928))
5367
return browser_client_.get();
5468
}
5569

cobalt/app/cobalt_main_delegate.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ namespace cobalt {
3030

3131
class CobaltMainDelegate : public content::ShellMainDelegate {
3232
public:
33+
<<<<<<< HEAD
3334
explicit CobaltMainDelegate();
35+
=======
36+
explicit CobaltMainDelegate(bool is_content_browsertests = false,
37+
bool is_visible = true);
38+
>>>>>>> 97ca4ee459 (Implement basic application preload functionality. (#8928))
3439

3540
CobaltMainDelegate(const CobaltMainDelegate&) = delete;
3641
CobaltMainDelegate& operator=(const CobaltMainDelegate&) = delete;
@@ -56,6 +61,7 @@ class CobaltMainDelegate : public content::ShellMainDelegate {
5661
~CobaltMainDelegate() override;
5762

5863
private:
64+
bool is_visible_;
5965
std::unique_ptr<content::BrowserMainRunner> main_runner_;
6066
std::unique_ptr<CobaltContentBrowserClient> browser_client_;
6167
std::unique_ptr<CobaltContentGpuClient> gpu_client_;

cobalt/browser/cobalt_browser_main_parts.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838

3939
namespace cobalt {
4040

41+
CobaltBrowserMainParts::CobaltBrowserMainParts(bool is_visible)
42+
: ShellBrowserMainParts(is_visible) {}
43+
4144
int CobaltBrowserMainParts::PreCreateThreads() {
4245
SetupMetrics();
4346
#if BUILDFLAG(IS_ANDROIDTV)

cobalt/browser/cobalt_browser_main_parts.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class GlobalFeatures;
3939
// ShellContentBrowserClient, this should implement BrowserMainParts.
4040
class CobaltBrowserMainParts : public content::ShellBrowserMainParts {
4141
public:
42-
CobaltBrowserMainParts() = default;
42+
explicit CobaltBrowserMainParts(bool is_visible = true);
4343

4444
CobaltBrowserMainParts(const CobaltBrowserMainParts&) = delete;
4545
CobaltBrowserMainParts& operator=(const CobaltBrowserMainParts&) = delete;

cobalt/browser/cobalt_content_browser_client.cc

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,9 @@ blink::UserAgentMetadata GetCobaltUserAgentMetadata() {
170170
return metadata;
171171
}
172172

173-
CobaltContentBrowserClient::CobaltContentBrowserClient()
174-
: video_geometry_setter_service_(
173+
CobaltContentBrowserClient::CobaltContentBrowserClient(bool is_visible)
174+
: is_visible_(is_visible),
175+
video_geometry_setter_service_(
175176
std::unique_ptr<cobalt::media::VideoGeometrySetterService,
176177
base::OnTaskRunnerDeleter>(
177178
nullptr,
@@ -200,8 +201,14 @@ CobaltContentBrowserClient* CobaltContentBrowserClient::Get() {
200201
std::unique_ptr<content::BrowserMainParts>
201202
CobaltContentBrowserClient::CreateBrowserMainParts(
202203
bool /* is_integration_test */) {
204+
<<<<<<< HEAD
203205
CHECK_CALLED_ON_VALID_THREAD(thread_checker_);
204206
auto browser_main_parts = std::make_unique<CobaltBrowserMainParts>();
207+
=======
208+
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
209+
auto browser_main_parts =
210+
std::make_unique<CobaltBrowserMainParts>(is_visible_);
211+
>>>>>>> 97ca4ee459 (Implement basic application preload functionality. (#8928))
205212
set_browser_main_parts(browser_main_parts.get());
206213
return browser_main_parts;
207214
}

cobalt/browser/cobalt_content_browser_client.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class CobaltWebContentsObserver;
6262
// a demo around Content.
6363
class CobaltContentBrowserClient : public content::ShellContentBrowserClient {
6464
public:
65-
CobaltContentBrowserClient();
65+
explicit CobaltContentBrowserClient(bool is_visible = true);
6666

6767
CobaltContentBrowserClient(const CobaltContentBrowserClient&) = delete;
6868
CobaltContentBrowserClient& operator=(const CobaltContentBrowserClient&) =
@@ -152,6 +152,7 @@ class CobaltContentBrowserClient : public content::ShellContentBrowserClient {
152152
void DispatchEvent(const std::string&, base::OnceClosure);
153153
void OnSbWindowCreated(SbWindow window);
154154

155+
bool is_visible_;
155156
std::unique_ptr<CobaltWebContentsObserver> web_contents_observer_;
156157
std::unique_ptr<media::VideoGeometrySetterService, base::OnTaskRunnerDeleter>
157158
video_geometry_setter_service_;

cobalt/doc/preload.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Application Preload
2+
3+
Preloading allows Cobalt to start and initialize in the background without
4+
displaying any user interface. This enables a "background-to-foreground"
5+
transition that appears instantaneous to the user when they eventually choose to
6+
launch the application.
7+
8+
## For Web Designers
9+
10+
When an application is preloaded, it starts in a **hidden** state. Standard Web
11+
APIs correctly reflect this state:
12+
13+
- `document.visibilityState` will be `"hidden"`.
14+
- `document.hidden` will be `true`.
15+
16+
### Best Practices
17+
18+
- Avoid starting audio playback or heavy graphical animations while in the
19+
hidden state.
20+
- Listen for the `visibilitychange` event on the `document` object to detect
21+
when the application transitions from preloaded to visible.
22+
23+
## For Device Manufacturers (Starboard Porters)
24+
25+
Preloading is managed through the Starboard lifecycle.
26+
27+
- **Startup:** To start in preload mode, the Starboard implementation should
28+
send the `kSbEventTypePreload` event instead of `kSbEventTypeStart`.
29+
- In the shared Starboard application framework (`starboard/shared/starboard/application.cc`),
30+
the `--preload` command-line flag is recognized via the `kPreloadSwitch` constant.
31+
- If a platform implementation's `Application::IsPreloadImmediate()` returns true
32+
(typically by checking `HasPreloadSwitch()`), the application will
33+
automatically call `DispatchPreload()`.
34+
- `DispatchPreload()` then creates and dispatches the `kSbEventTypePreload`
35+
initial event, which is the signal to the application that it should
36+
initialize in a hidden state.
37+
- **Revelation:** To bring a preloaded application to the foreground, the
38+
platform should send a `kSbEventTypeReveal` signal.
39+
- On Linux-based platforms, this is often triggered by sending a `SIGCONT`
40+
signal to the process.
41+
- An example of this mapping can be found in `starboard/shared/signal/suspend_signals.cc`,
42+
where `SIGCONT` is handled by requesting a focus change.
43+
- The shared Starboard application logic in `starboard/shared/starboard/application.cc`
44+
automatically injects a `kSbEventTypeReveal` event if a focus request is
45+
received while the application is in the preloaded (concealed) state.
46+
- **Resource Management:** Cobalt defers the creation of the native window and
47+
associated graphics resources until the first `Reveal` signal is received.
48+
This minimizes the memory and CPU footprint of the application while it
49+
resides in the background.
50+
- **Splash Screen:** The creation of the splash screen's `WebContents` is also
51+
skipped when the application is preloaded, further reducing the background
52+
footprint.
53+
54+
## For Cobalt Developers
55+
56+
The "preload" signal is converted into a generic "visibility" state as soon as
57+
it enters the application layer.
58+
59+
### Implementation Flow
60+
61+
1. **Entry Point:** `SbEventHandle` (in `cobalt/app/cobalt.cc`) receives
62+
`kSbEventTypePreload`.
63+
2. **State Propagation:** An `is_visible` boolean (set to `false`) is passed
64+
through the constructor chain: `CobaltMainDelegate` ->
65+
`CobaltContentBrowserClient` -> `CobaltBrowserMainParts` ->
66+
`ShellBrowserMainParts`.
67+
3. **Initialization:** `ShellBrowserMainParts::PreMainMessageLoopRun` calls
68+
`Shell::Initialize`, passing the visibility state.
69+
4. **Splash Screen Skip:** `Shell::CreateNewWindow` checks the visibility state
70+
and skips creating the splash screen `WebContents` if the application is
71+
initially hidden.
72+
5. **Platform Delegate:** `Shell::Initialize` passes the state to
73+
`ShellPlatformDelegate::Initialize`. Each platform implementation (e.g.,
74+
Aura, Views) stores this in a member variable.
75+
6. **Deferred Resource Creation:** `CreatePlatformWindow` checks `IsVisible()`
76+
(or `IsConcealed()`) and defers creating the `NativeWindow` and `Widget` if
77+
the application is not yet visible.
78+
7. **Revelation:** When `kSbEventTypeReveal` is received, `Shell::OnReveal()` is
79+
triggered. This calls `ShellPlatformDelegate::RevealShell`, which creates
80+
the deferred window resources and calls `WasShown()` on the `WebContents`,
81+
triggering the `visibilitychange` event for the web application.
82+
83+
### Unit Testing
84+
85+
Application lifecycle and visibility transitions are covered by the following
86+
unit tests in the `cobalt_unittests` binary:
87+
88+
- **`LifecycleTest`** (`cobalt/shell/browser/lifecycle_unittest.cc`): Verifies
89+
correct window creation and visibility state propagation during startup,
90+
revelation, and redundant signals.
91+
- **`SplashScreenTest`** (`cobalt/shell/browser/splash_screen_unittest.cc`):
92+
Includes tests for ensuring the splash screen is skipped during preloading.
93+
94+
### Integration Testing
95+
96+
A robust integration test is provided in `cobalt/tools/test_preload.sh`. This
97+
test:
98+
99+
1. Launches Cobalt in preload mode.
100+
2. Uses the Chrome DevTools Protocol (CDP) to verify that
101+
`document.visibilityState` is initially `"hidden"`.
102+
3. Sends a `SIGCONT` signal to reveal the application.
103+
4. Verifies via CDP that `document.visibilityState` transitions to `"visible"`.
104+
5. Sends a `SIGPWR` signal to verify clean shutdown.

0 commit comments

Comments
 (0)