From 47312630735882952fcec44bf5bfbc6aa0c185dc Mon Sep 17 00:00:00 2001 From: kdletters Date: Thu, 18 Jun 2026 14:53:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E5=8F=A3=E6=A1=8C=E9=9D=A2=E5=A3=B3We?= =?UTF-8?q?bView=E7=8A=B6=E6=80=81=E9=87=8D=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 桌面壳页面加载完成后回放生命周期状态 桌面壳页面加载完成后重装网络状态监听 桌面壳配置检查锁定页面状态重放路径 宿主壳方案和共享决策日志记录桌面状态重放约束 --- apps/desktop-shell/scripts/check-config.mjs | 6 ++++ apps/desktop-shell/src-tauri/src/main.rs | 33 ++++++++++++++++++- .../shared-memory/decision-log.md | 1 + ...ExpoReactNative与Tauri宿主壳方案-2026-06-17.md | 2 ++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index e7e100273..091bde42a 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -1070,9 +1070,14 @@ const requiredMainSnippets = [ 'window.theme()', 'WindowEvent::Focused', 'WindowEvent::DragDrop', + 'PageLoadEvent', 'host_bridge_event_script', 'origin: window.location.origin', 'source: window', + 'should_replay_desktop_webview_state_on_page_load', + 'PageLoadEvent::Finished', + 'replay_desktop_webview_state', + 'window.is_focused()', 'resolve_desktop_network_status', 'network.statusChanged', 'file.imageDropped', @@ -1085,6 +1090,7 @@ const requiredMainSnippets = [ 'open_desktop_external_navigation', '.on_navigation(move |url|', '.on_new_window(move |url, _features|', + '.on_page_load(|window, payload|', 'DownloadEvent', 'should_allow_desktop_webview_download', 'DownloadEvent::Requested { .. } => false', diff --git a/apps/desktop-shell/src-tauri/src/main.rs b/apps/desktop-shell/src-tauri/src/main.rs index 2f3a1afeb..88bca8883 100644 --- a/apps/desktop-shell/src-tauri/src/main.rs +++ b/apps/desktop-shell/src-tauri/src/main.rs @@ -9,7 +9,7 @@ use std::sync::{Arc, Condvar, Mutex}; use std::time::Duration; use tauri::menu::{Menu, MenuItem}; use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}; -use tauri::webview::{DownloadEvent, NewWindowResponse}; +use tauri::webview::{DownloadEvent, NewWindowResponse, PageLoadEvent}; use tauri::DragDropEvent; use tauri::Manager; use tauri::Theme; @@ -1153,6 +1153,22 @@ fn register_desktop_network_events(window: &WebviewWindow) -> tauri::Result<()> window.eval(script) } +fn should_replay_desktop_webview_state_on_page_load(event: PageLoadEvent) -> bool { + event == PageLoadEvent::Finished +} + +fn replay_desktop_webview_state(window: &WebviewWindow) { + let focused = window.is_focused().unwrap_or(true); + let (state, native_state) = if focused { + ("active", "focused") + } else { + ("inactive", "blurred") + }; + + let _ = emit_desktop_lifecycle_event(window, state, focused, native_state); + let _ = register_desktop_network_events(window); +} + #[derive(Debug, PartialEq, Eq)] enum DesktopTrayAction { ShowMainWindow, @@ -1800,6 +1816,11 @@ fn main() { open_desktop_external_navigation(&new_window_app_handle, &url); NewWindowResponse::Deny }) + .on_page_load(|window, payload| { + if should_replay_desktop_webview_state_on_page_load(payload.event()) { + replay_desktop_webview_state(&window); + } + }) .on_download(|_webview, event| should_allow_desktop_webview_download(&event)) .build()?; register_desktop_window_close_events(&window, tray_registered); @@ -2111,6 +2132,16 @@ mod tests { assert!(script.contains("\\\"focused\\\":true")); } + #[test] + fn desktop_page_load_replays_state_only_after_finished_load() { + assert!(should_replay_desktop_webview_state_on_page_load( + PageLoadEvent::Finished + )); + assert!(!should_replay_desktop_webview_state_on_page_load( + PageLoadEvent::Started + )); + } + #[test] fn desktop_network_status_payload_reports_reachability() { assert_eq!( diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 9ee614752..2ca86377b 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -35,6 +35,7 @@ - 2026-06-18 原生壳生命周期事件:新增 `app.lifecycle` HostBridge capability,Expo 壳通过 React Native `AppState` 派发 `active` / `inactive` / `background`,Tauri 壳通过主窗口 focus / blur 派发 `active` / `inactive`;H5 只通过 `subscribeHostAppLifecycle()` 订阅统一状态,后续游戏循环、音频和轮询暂停 / 恢复不得直接依赖 Expo / Tauri 平台细节。 - 2026-06-18 原生壳网络状态:新增 `network.status` 与 `network.statusChanged` HostBridge capability,Expo 壳通过 `expo-network` 查询和订阅真实系统网络状态,Tauri 壳通过短超时连接 `app.genarrative.world:443` 查询主站可达性,并通过 WebView `online` / `offline` 注入变化事件;H5 统一使用 `getHostNetworkStatus()` / `subscribeHostNetworkStatusChange()`,不得直接读取 Expo / Tauri 私有网络 API。 - 2026-06-18 移动壳 WebView 状态重放:Expo WebView 每次同源主站页面成功加载后都会补发当前 `app.lifecycle` 与 `network.statusChanged` 状态,覆盖首载、受控刷新、H5 刷新和系统回收 WebView 进程后的新 JS 上下文;补发不新增 HostBridge capability,也不向 `about:blank`、外域或错误页注入宿主状态。 +- 2026-06-18 桌面壳 WebView 状态重放:Tauri 主 WebView 每次页面加载完成后都会回放当前 `app.lifecycle` 并重新安装 `network.statusChanged` 监听脚本,覆盖托盘刷新、`app.reloadWebView` 和 H5 自刷新后的新 JS 上下文;回放不新增 HostBridge capability,不开放 Tauri event 插件或额外 command。 - 2026-06-18 外部生成队列轮询接入宿主网络状态:H5 新增 `useHostNetworkOnline()`,宿主未声明网络能力时按在线处理以保持浏览器和旧壳行为;宿主明确 `isConnected=false` 或 `isInternetReachable=false` 时,平台外部生成队列概览暂停 HTTP 轮询,恢复在线后重新刷新。该能力只减少离线请求,不改变外部生成队列、作品架、弹窗或后端任务状态事实。 - 2026-06-18 桌面图片导入:新增 `file.importImage` 与 `file.imageDropped` HostBridge capability,Tauri 壳通过系统文件选择框和主窗口拖拽事件读取用户选择 / 拖入的真实图片,只允许 `image/png`、`image/jpeg`、`image/webp` 且单次不超过 10 MiB;H5 统一使用 `importHostImageFile()` / `subscribeHostImageDrop()`,宿主只回传文件名、MIME、base64 内容、字节数和可选坐标,不暴露本地绝对路径,也不开放通用文件系统。 - 2026-06-18 移动图片导入:Expo 壳开始声明并实现 `file.importImage`,通过 `expo-image-picker` 请求相册权限并打开系统相册选择器,只允许 `image/png`、`image/jpeg`、`image/webp` 且单次不超过 10 MiB;成功只回传清洗后的文件名、MIME、base64 内容和字节数,不暴露设备本地 URI,用户取消返回 `cancelled` 并由 H5 facade 归为 `false`。 diff --git a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md index 1af819624..574ccd673 100644 --- a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md +++ b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md @@ -337,6 +337,8 @@ GameBridge 禁止: 2026-06-18 追加:H5 账号状态刷新开始消费 `app.reloadWebView`。用户登录成功、退出登录或其它身份边界变化需要整页重新初始化时,`AuthGate` 会优先请求 Tauri 主 WebViewWindow 刷新;宿主未声明或刷新失败时再回退浏览器刷新,Tauri 仍只暴露 `host_bridge_request` 这一受控命令入口。 +2026-06-18 追加:桌面壳 WebView 每次页面加载完成后都会回放当前 `app.lifecycle` 并重新安装 `network.statusChanged` 监听脚本,覆盖托盘刷新、`app.reloadWebView` 和 H5 自刷新后的新 JS 上下文。回放仍只通过 HostBridge event,不开放 Tauri event 插件或额外 command。 + 2026-06-18 追加:桌面壳启用 Tauri 真实系统托盘,并复用品牌图标。托盘菜单只提供宿主壳级动作:显示主窗口、刷新主窗口和退出应用;左键点击托盘图标恢复并聚焦主窗口。该能力不进入 HostBridge capability 清单,不向 H5 暴露托盘 API、菜单 API、shell API 或任意窗口控制;如果当前桌面环境无法注册托盘,壳会继续启动主窗口。托盘注册成功时,用户点击主窗口关闭按钮会隐藏到托盘,必须通过托盘“退出”动作结束应用;托盘注册失败时不拦截关闭,避免窗口消失后没有恢复入口。 2026-06-18 追加:桌面壳启用 Tauri 单实例。用户重复启动桌面 App 时,新实例会退出并唤醒已有主窗口;该回调只执行显示、取消最小化和聚焦主窗口,不把第二实例的命令行参数、工作目录或深链内容作为事件透传给 H5。桌面深链若后续需要接入,必须单独定义受控来源、路径归一和 HostBridge / GameBridge 边界。