Skip to content

Commit ff59785

Browse files
authored
fix: reveal startup window after initial page load (#1338)
fix: reveal startup window after initial page load
2 parents 10d3f81 + ae4fa74 commit ff59785

2 files changed

Lines changed: 122 additions & 22 deletions

File tree

src-tauri/src/lib.rs

Lines changed: 91 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@
22
mod app;
33
mod util;
44

5-
use tauri::Manager;
5+
use std::sync::{
6+
atomic::{AtomicBool, Ordering},
7+
Arc,
8+
};
9+
use tauri::{webview::PageLoadEvent, Manager, Url, WebviewWindow};
610
use tauri_plugin_window_state::Builder as WindowStatePlugin;
711
use tauri_plugin_window_state::StateFlags;
812

913
#[cfg(target_os = "macos")]
1014
use std::time::Duration;
1115

12-
const WINDOW_SHOW_DELAY: u64 = 50;
16+
// Fallback when PageLoadEvent::Finished never arrives (offline / stalled).
17+
// Deliberately longer than a paint tick so the normal path can win first.
18+
const STARTUP_WINDOW_FALLBACK_DELAY: u64 = 3_000;
1319
#[cfg(target_os = "linux")]
1420
const PAKE_LINUX_WEBKIT_SAFE_MODE: &str = "PAKE_LINUX_WEBKIT_SAFE_MODE";
1521
#[cfg(target_os = "linux")]
@@ -29,6 +35,43 @@ use app::{
2935
};
3036
use util::get_pake_config;
3137

38+
/// Placeholder documents used before the real target URL navigates (e.g. macOS
39+
/// cert-bypass starts on about:blank). Revealing on these would reintroduce the
40+
/// blank-window flash the page-load gate is meant to prevent.
41+
fn is_placeholder_startup_url(url: &Url) -> bool {
42+
url.scheme().eq_ignore_ascii_case("about")
43+
}
44+
45+
fn reveal_startup_window(window: WebviewWindow, init_fullscreen: bool, revealed: &Arc<AtomicBool>) {
46+
if revealed.swap(true, Ordering::AcqRel) {
47+
return;
48+
}
49+
50+
tauri::async_runtime::spawn(async move {
51+
let _ = window.show();
52+
reapply_window_icon(&window);
53+
54+
// Fixed: Linux fullscreen issue with virtual keyboard
55+
#[cfg(target_os = "linux")]
56+
{
57+
if init_fullscreen {
58+
let _ = window.set_fullscreen(true);
59+
// Ensure webview maintains focus for input after fullscreen
60+
let _ = window.set_focus();
61+
} else {
62+
// Fix: Ubuntu 24.04/GNOME window buttons non-functional until resize (#1122)
63+
// The window manager needs time to process the MapWindow event before
64+
// accepting focus requests. Without this, decorations remain non-interactive.
65+
tokio::time::sleep(tokio::time::Duration::from_millis(30)).await;
66+
let _ = window.set_focus();
67+
}
68+
}
69+
70+
#[cfg(not(target_os = "linux"))]
71+
let _ = init_fullscreen;
72+
});
73+
}
74+
3275
#[cfg(any(target_os = "linux", test))]
3376
fn is_disabled_env_value(value: &str) -> bool {
3477
matches!(
@@ -149,6 +192,7 @@ pub fn run_app() {
149192
let multi_instance = pake_config.multi_instance;
150193
let multi_window = pake_config.multi_window;
151194
let _enable_find = pake_config.windows[0].enable_find;
195+
let startup_window_revealed = Arc::new(AtomicBool::new(false));
152196

153197
let window_state_plugin = WindowStatePlugin::default()
154198
.with_state_flags(if init_fullscreen {
@@ -186,6 +230,30 @@ pub fn run_app() {
186230
));
187231
}
188232

233+
// Reveal the main window after the first real document finishes loading so
234+
// slow WKWebView cold starts do not expose an empty but interactive shell.
235+
// start_to_tray keeps the window hidden for the whole session until the user
236+
// opens it from the tray / shortcut.
237+
if !start_to_tray {
238+
let page_load_revealed = startup_window_revealed.clone();
239+
app_builder = app_builder.on_page_load(move |webview, payload| {
240+
if webview.label() != "pake" {
241+
return;
242+
}
243+
if !matches!(payload.event(), PageLoadEvent::Finished) {
244+
return;
245+
}
246+
// Skip about:blank (and other about: placeholders) used by the macOS
247+
// cert-bypass path before the real target URL navigates.
248+
if is_placeholder_startup_url(payload.url()) {
249+
return;
250+
}
251+
if let Some(window) = webview.app_handle().get_webview_window("pake") {
252+
reveal_startup_window(window, init_fullscreen, &page_load_revealed);
253+
}
254+
});
255+
}
256+
189257
app_builder
190258
.invoke_handler(tauri::generate_handler![
191259
download_file,
@@ -226,29 +294,17 @@ pub fn run_app() {
226294
set_global_shortcut(app.app_handle(), activation_shortcut, init_fullscreen)?;
227295

228296
// Show window after state restoration to prevent position flashing
229-
// Unless start_to_tray is enabled, then keep it hidden
297+
// once its first page finishes. A fallback keeps offline or stalled
298+
// pages reachable without exposing a blank webview during normal startup.
230299
if !start_to_tray {
231300
let window_clone = window.clone();
301+
let fallback_revealed = startup_window_revealed.clone();
232302
tauri::async_runtime::spawn(async move {
233-
tokio::time::sleep(tokio::time::Duration::from_millis(WINDOW_SHOW_DELAY)).await;
234-
let _ = window_clone.show();
235-
reapply_window_icon(&window_clone);
236-
237-
// Fixed: Linux fullscreen issue with virtual keyboard
238-
#[cfg(target_os = "linux")]
239-
{
240-
if init_fullscreen {
241-
let _ = window_clone.set_fullscreen(true);
242-
// Ensure webview maintains focus for input after fullscreen
243-
let _ = window_clone.set_focus();
244-
} else {
245-
// Fix: Ubuntu 24.04/GNOME window buttons non-functional until resize (#1122)
246-
// The window manager needs time to process the MapWindow event before
247-
// accepting focus requests. Without this, decorations remain non-interactive.
248-
tokio::time::sleep(tokio::time::Duration::from_millis(30)).await;
249-
let _ = window_clone.set_focus();
250-
}
251-
}
303+
tokio::time::sleep(tokio::time::Duration::from_millis(
304+
STARTUP_WINDOW_FALLBACK_DELAY,
305+
))
306+
.await;
307+
reveal_startup_window(window_clone, init_fullscreen, &fallback_revealed);
252308
});
253309
}
254310

@@ -318,6 +374,19 @@ pub fn run() {
318374
mod tests {
319375
use super::*;
320376

377+
#[test]
378+
fn placeholder_startup_urls_cover_about_blank() {
379+
let blank: Url = "about:blank".parse().unwrap();
380+
let srcdoc: Url = "about:srcdoc".parse().unwrap();
381+
let https: Url = "https://github.com/".parse().unwrap();
382+
let tauri: Url = "tauri://localhost/".parse().unwrap();
383+
384+
assert!(is_placeholder_startup_url(&blank));
385+
assert!(is_placeholder_startup_url(&srcdoc));
386+
assert!(!is_placeholder_startup_url(&https));
387+
assert!(!is_placeholder_startup_url(&tauri));
388+
}
389+
321390
#[test]
322391
fn linux_webkit_safe_mode_stays_on_by_default() {
323392
assert!(should_enable_linux_webkit_safe_mode_from_values(
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
import { describe, expect, it } from 'vitest';
4+
5+
const libSource = fs.readFileSync(
6+
path.join(process.cwd(), 'src-tauri', 'src', 'lib.rs'),
7+
'utf8',
8+
);
9+
10+
describe('startup window reveal', () => {
11+
it('waits for the first real page finish instead of a fixed short delay', () => {
12+
expect(libSource).toContain('.on_page_load(');
13+
expect(libSource).toContain('PageLoadEvent::Finished');
14+
expect(libSource).toContain('revealed.swap(true');
15+
expect(libSource).toContain('STARTUP_WINDOW_FALLBACK_DELAY');
16+
expect(libSource).toContain('is_placeholder_startup_url');
17+
expect(libSource).toMatch(
18+
/if !start_to_tray \{[\s\S]*?app_builder = app_builder\.on_page_load/,
19+
);
20+
expect(libSource).not.toContain('WINDOW_SHOW_DELAY');
21+
});
22+
23+
it('does not treat about:blank as a ready first paint', () => {
24+
expect(libSource).toMatch(
25+
/is_placeholder_startup_url\(payload\.url\(\)\)/,
26+
);
27+
expect(libSource).toMatch(
28+
/url\.scheme\(\)\.eq_ignore_ascii_case\("about"\)/,
29+
);
30+
});
31+
});

0 commit comments

Comments
 (0)