From 0ead4fbeb1e451218a952589843b36f853a87296 Mon Sep 17 00:00:00 2001 From: Ivan Pelipenko <82229453+peri4ko@users.noreply.github.com> Date: Thu, 23 Apr 2026 23:47:23 +0300 Subject: [PATCH 1/3] fix(windows): idle WebView2 when Tauri windows are hidden (#273) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(windows): stop GPU rendering when windows are hidden * perf(ui): tighten hidden-window rendering mitigation Align implementation and documentation with what the injected pause/resume scripts actually do, reduce main-thread and compositor wakeups while windows are invisible, and make lifecycle cleanup reliable. Rust (Tauri) - Document PAUSE/RESUME as compositor-oriented hints: set __psyHidden, optional --psy-anim-speed for CSS that opts in, and pause @keyframes via animation-play-state only (not CSS transitions, not arbitrary timers or rAF). - Walk descendants under #root instead of document-wide querySelectorAll('*') to cut cost on large pages; skip the walk if #root is missing (flag still set). - Call eval(PAUSE_RENDERING_JS) before hide() on all hide paths (tray menu, tray icon toggle, mini open/close/show-main, native mini close) so the webview sees __psyHidden while still fully schedulable. - Shorten pause_rendering command rustdoc to match the script. Frontend - WindowVisibilityProvider: replace self-rescheduling useCallback with a single effect that keeps one pending timeout id, uses a cancelled flag on unmount, and syncs hiddenRef from document.hidden at mount (no orphaned timers after tests/HMR). - WaveformSeek (preview + animated seekbar): while hidden, poll with a 400 ms timeout instead of requestAnimationFrame every frame; cancel both rAF and timeout on teardown. Why - Accurate comments prevent future refactors from assuming “full JS idle”. - Smaller DOM walks and fewer rAF wakeups reduce spikes when hiding the main window or mini player on Windows WebView2 and elsewhere. * perf(ui): pause global CSS + timers when Tauri hides the window Builds on PR #273 (`7803d8e` + `0e07a73`). Adds a second HTML flag driven only by Rust pause/resume inject so infinite animations still pause when WebView2 keeps `document.hidden === false` after `win.hide()`, and stops a few periodic JS timers while the window is invisible. - `lib.rs`: set/remove `data-psy-native-hidden` on `` in PAUSE/RESUME JS; document why in rustdoc (rest of pause/resume unchanged from PR tip). - `components.css`: same `animation-play-state: paused !important` rules as `data-app-hidden` for `[data-psy-native-hidden="true"]`; refresh the comment. - `App.tsx`: comment distinguishes tab visibility vs Tauri-native hide. - `Hero.tsx`: do not run the 10 s carousel interval while hidden (`useWindowVisibility` + `__psyHidden` in tick). - `PlaybackScheduleBadge.tsx`, `playbackScheduleFormat.ts`: no 400/500 ms intervals while hidden; skip ticks when `document.hidden` or `__psyHidden`. --- src-tauri/src/lib.rs | 87 +++++++++++++++++++++++- src/App.tsx | 52 +++++++------- src/components/Hero.tsx | 15 ++-- src/components/MiniPlayer.tsx | 5 ++ src/components/PlaybackScheduleBadge.tsx | 15 ++-- src/components/WaveformSeek.tsx | 50 ++++++++++++-- src/hooks/useWindowVisibility.tsx | 60 ++++++++++++++++ src/styles/components.css | 15 ++-- src/utils/playbackScheduleFormat.ts | 11 ++- 9 files changed, 258 insertions(+), 52 deletions(-) create mode 100644 src/hooks/useWindowVisibility.tsx diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e42a14cd..a0708390 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -2747,8 +2747,10 @@ fn build_tray_icon(app: &tauri::AppHandle) -> tauri::Result { "show_hide" => { if let Some(win) = app.get_webview_window("main") { if win.is_visible().unwrap_or(false) { + let _ = win.eval(PAUSE_RENDERING_JS); let _ = win.hide(); } else { + let _ = win.eval(RESUME_RENDERING_JS); let _ = win.show(); let _ = win.set_focus(); } @@ -2781,8 +2783,10 @@ fn build_tray_icon(app: &tauri::AppHandle) -> tauri::Result { let app = tray.app_handle(); if let Some(win) = app.get_webview_window("main") { if win.is_visible().unwrap_or(false) { + let _ = win.eval(PAUSE_RENDERING_JS); let _ = win.hide(); } else { + let _ = win.eval(RESUME_RENDERING_JS); let _ = win.show(); let _ = win.set_focus(); } @@ -3027,6 +3031,45 @@ fn default_mini_position(app: &tauri::AppHandle) -> Option