diff --git a/docs/PRD.md b/docs/PRD.md index bdfa08b4..767d806a 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -94,7 +94,7 @@ TauriTavern将逐步实现SillyTavern的所有核心功能,包括但不限于 ### 3.2 兼容性要求 - 支持Windows 10+, macOS 10.15+, 主流Linux发行版 -- 支持iOS 15+, Android 10+ +- 支持iOS 16+, Android 10+ - 保持与SillyTavern扩展和插件的兼容性 - 支持导入/导出SillyTavern数据 diff --git a/docs/iOSDevelopment.md b/docs/iOSDevelopment.md index 43494945..f53041b2 100644 --- a/docs/iOSDevelopment.md +++ b/docs/iOSDevelopment.md @@ -39,7 +39,7 @@ WKWebView 内部是 `UIScrollView` 承载 Web 内容;在默认行为下,iOS 实现位置: -- iOS 配置入口:`src-tauri/src/infrastructure/ios_webview.rs` +- iOS 配置入口:`src-tauri/src/infrastructure/ios_webview.rs` 的 `configure_main_wkwebview()` - 调用时机:`src-tauri/src/lib.rs`(主窗口 build 后立刻调用) ### 1.5 验收建议 @@ -148,3 +148,25 @@ iOS 上“文件选择 / 文件导出”必须交给系统级能力完成: - 下载桥:`src/tauri/main/download-bridge.js` - 导出反馈:`src/scripts/download-feedback.js` - iOS share 命令:`src-tauri/src/presentation/commands/ios_file_bridge_commands.rs` + +## 4. WKWebView Fullscreen API(iOS 16+) + +### 4.1 现象 + +- 角色卡或扩展内的同源 iframe 页面在桌面/Android 可进入全屏,但 iOS 上 `requestFullscreen()` / `webkitRequestFullscreen()` 不生效。 + +### 4.2 根因 + +- 问题不在前端按钮或 JS-Slash-Runner 事件语义,而在宿主 WKWebView 默认没有开启 element fullscreen 能力。 +- TauriTavern 的兼容目标仍然是让上游页面继续使用标准浏览器 Fullscreen API,而不是引入额外 JS-native bridge。 + +### 4.3 已落地方案 + +- 继续复用 `src-tauri/src/infrastructure/ios_webview.rs` 的主 WebView 配置入口,在 `configure_main_wkwebview()` 内统一完成两类 native 配置: + - 关闭 `scrollView` 的 safe-area 自动 inset 调整; + - 开启 `WKPreferences.setElementFullscreenEnabled(true)`。 +- 这样角色卡、JS-Slash-Runner、同源 iframe 的 fullscreen 事件、退出语义和上游契约保持一致,宿主只补齐平台能力,不改前端行为。 + +### 4.4 支持边界 + +- 该能力依赖 iOS 16 的 app-embedded WKWebView Fullscreen API,因此项目 iOS 支持线提升到 `iOS 16+`。 diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 75362fb5..ded6eafc 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -19,7 +19,7 @@ tauri-build = { version = "2.5.5", features = [] } serde_json = "1" [dependencies] -tauri = { version = "2.10.2", features = ["protocol-asset", "tray-icon"] } +tauri = { version = "2.10.2", features = ["macos-private-api", "protocol-asset", "tray-icon"] } tauri-plugin-barcode-scanner = "2.4.4" tauri-plugin-opener = "2.5.3" tauri-plugin-fs = "2.4.5" diff --git a/src-tauri/gen/apple/Podfile b/src-tauri/gen/apple/Podfile index efd6ceb8..d9002ddc 100644 --- a/src-tauri/gen/apple/Podfile +++ b/src-tauri/gen/apple/Podfile @@ -1,7 +1,7 @@ # Uncomment the next line to define a global platform for your project target 'tauritavern_iOS' do -platform :ios, '14.0' +platform :ios, '16.0' # Pods for tauritavern_iOS end diff --git a/src-tauri/gen/apple/project.yml b/src-tauri/gen/apple/project.yml index 860fccf2..8c20daee 100644 --- a/src-tauri/gen/apple/project.yml +++ b/src-tauri/gen/apple/project.yml @@ -2,7 +2,7 @@ name: tauritavern options: bundleIdPrefix: com.tauritavern.client deploymentTarget: - iOS: 14.0 + iOS: 16.0 fileGroups: [../../src] configs: debug: debug diff --git a/src-tauri/gen/apple/tauritavern.xcodeproj/project.pbxproj b/src-tauri/gen/apple/tauritavern.xcodeproj/project.pbxproj index 090ecd50..b3cafc1b 100644 --- a/src-tauri/gen/apple/tauritavern.xcodeproj/project.pbxproj +++ b/src-tauri/gen/apple/tauritavern.xcodeproj/project.pbxproj @@ -970,7 +970,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1059,7 +1059,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/src-tauri/src/infrastructure/ios_webview.rs b/src-tauri/src/infrastructure/ios_webview.rs index 1c20d353..a288a86d 100644 --- a/src-tauri/src/infrastructure/ios_webview.rs +++ b/src-tauri/src/infrastructure/ios_webview.rs @@ -1,12 +1,9 @@ use tauri::WebviewWindow; -pub fn disable_wkwebview_content_inset_adjustment(window: &WebviewWindow) -> tauri::Result<()> { +/// Applies the iOS WKWebView host policy required by TauriTavern's browser contract. +pub fn configure_main_wkwebview(window: &WebviewWindow) -> tauri::Result<()> { window.with_webview(|webview| unsafe { - use objc2::rc::Retained; use objc2::runtime::AnyObject; - use objc2_ui_kit::{ - UIEdgeInsetsZero, UIScrollView, UIScrollViewContentInsetAdjustmentBehavior, - }; let wkwebview_ptr = webview.inner(); assert!( @@ -15,12 +12,31 @@ pub fn disable_wkwebview_content_inset_adjustment(window: &WebviewWindow) -> tau ); let wkwebview = &*wkwebview_ptr.cast::(); - let scroll_view: Retained = objc2::msg_send![wkwebview, scrollView]; - - scroll_view - .setContentInsetAdjustmentBehavior(UIScrollViewContentInsetAdjustmentBehavior::Never); - scroll_view.setContentInset(UIEdgeInsetsZero); - scroll_view.setScrollIndicatorInsets(UIEdgeInsetsZero); - scroll_view.setAutomaticallyAdjustsScrollIndicatorInsets(false); + disable_content_inset_adjustment(wkwebview); + enable_element_fullscreen(wkwebview); }) } + +unsafe fn disable_content_inset_adjustment(wkwebview: &objc2::runtime::AnyObject) { + use objc2::rc::Retained; + use objc2_ui_kit::{ + UIEdgeInsetsZero, UIScrollView, UIScrollViewContentInsetAdjustmentBehavior, + }; + + let scroll_view: Retained = objc2::msg_send![wkwebview, scrollView]; + let zero_insets = unsafe { UIEdgeInsetsZero }; + scroll_view + .setContentInsetAdjustmentBehavior(UIScrollViewContentInsetAdjustmentBehavior::Never); + scroll_view.setContentInset(zero_insets); + scroll_view.setScrollIndicatorInsets(zero_insets); + scroll_view.setAutomaticallyAdjustsScrollIndicatorInsets(false); +} + +unsafe fn enable_element_fullscreen(wkwebview: &objc2::runtime::AnyObject) { + use objc2::rc::Retained; + use objc2::runtime::AnyObject; + + let configuration: Retained = objc2::msg_send![wkwebview, configuration]; + let preferences: Retained = objc2::msg_send![&*configuration, preferences]; + let _: () = objc2::msg_send![&*preferences, setElementFullscreenEnabled: true]; +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index cb89853b..7f792c69 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -22,6 +22,7 @@ use presentation::web_resources::third_party_endpoint::handle_third_party_asset_ use presentation::web_resources::thumbnail_endpoint::handle_thumbnail_web_request; use presentation::web_resources::user_data_endpoint::handle_user_data_asset_web_request; use tauri::Manager; +#[cfg(any(target_os = "macos", windows, target_os = "linux"))] use tauri_plugin_opener::OpenerExt; #[cfg(any(target_os = "macos", windows, target_os = "linux"))] @@ -264,7 +265,7 @@ fn create_main_window( let window = builder.build()?; #[cfg(target_os = "ios")] - infrastructure::ios_webview::disable_wkwebview_content_inset_adjustment(&window)?; + infrastructure::ios_webview::configure_main_wkwebview(&window)?; #[cfg(any(target_os = "macos", windows, target_os = "linux"))] { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 9de94732..032e31fd 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -8,6 +8,7 @@ "removeUnusedCommands": true }, "app": { + "macOSPrivateApi": true, "withGlobalTauri": true, "windows": [ {