From 7ca929b9c82d656e531e80e43744e3e8f8812cf3 Mon Sep 17 00:00:00 2001 From: kdletters Date: Sat, 20 Jun 2026 11:40:49 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=81=E5=AE=9A=E6=A1=8C=E9=9D=A2=E6=A0=87?= =?UTF-8?q?=E9=A2=98=E8=AF=B7=E6=B1=82=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 桌面标题 HostBridge 单测覆盖缺失和非法 payload 桌面标题 HostBridge 单测覆盖截断边界 桌面壳配置检查反查标题请求测试 --- apps/desktop-shell/scripts/check-config.mjs | 3 ++ .../src-tauri/src/host_bridge/title.rs | 52 ++++++++++++++++--- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index b13428111..bbba1db18 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -2465,9 +2465,12 @@ if ( for (const snippet of [ 'WINDOW_TITLE_MAX_LENGTH', 'normalize_window_title', + 'window_title_from_request', 'required_string_payload(request, "title")', 'window.set_title(&title)', '"title is required"', + 'window_title_request_rejects_missing_or_invalid_payload', + 'window_title_request_trims_and_truncates_shared_boundary', ]) { if (!desktopHostBridgeTitleSource.includes(snippet)) { throw new Error(`desktop shell title module is missing ${snippet}`); diff --git a/apps/desktop-shell/src-tauri/src/host_bridge/title.rs b/apps/desktop-shell/src-tauri/src/host_bridge/title.rs index 9e4bfdaae..43a6b0f45 100644 --- a/apps/desktop-shell/src-tauri/src/host_bridge/title.rs +++ b/apps/desktop-shell/src-tauri/src/host_bridge/title.rs @@ -15,16 +15,22 @@ fn normalize_window_title(raw_title: &str) -> Option { Some(title.chars().take(WINDOW_TITLE_MAX_LENGTH).collect()) } +fn window_title_from_request( + request: &HostBridgeRequest, +) -> Result { + required_string_payload(request, "title") + .ok() + .and_then(normalize_window_title) + .ok_or_else(|| failed(request.id.clone(), "invalid_request", "title is required")) +} + pub(crate) fn set_desktop_host_bridge_window_title( app: &tauri::AppHandle, request: &HostBridgeRequest, ) -> HostBridgeResponse { - let title = match required_string_payload(request, "title") - .ok() - .and_then(normalize_window_title) - { - Some(title) => title, - None => return failed(request.id.clone(), "invalid_request", "title is required"), + let title = match window_title_from_request(request) { + Ok(title) => title, + Err(response) => return response, }; match app.get_webview_window("main") { @@ -39,6 +45,8 @@ pub(crate) fn set_desktop_host_bridge_window_title( #[cfg(test)] mod tests { use super::*; + use crate::host_bridge::protocol::request; + use serde_json::json; #[test] fn window_title_normalization_requires_visible_text() { @@ -58,4 +66,36 @@ mod tests { 80 ); } + + #[test] + fn window_title_request_rejects_missing_or_invalid_payload() { + for payload in [ + None, + Some(json!({})), + Some(json!({ "title": 1 })), + Some(json!({ "title": " " })), + Some(json!({ "title": "Genarrative\nDev" })), + ] { + let mut request = request("app.setTitle"); + request.payload = payload; + + let response = window_title_from_request(&request).expect_err("invalid title"); + let error = response.error.expect("error"); + assert_eq!(error.code, "invalid_request"); + assert_eq!(error.message, "title is required"); + } + } + + #[test] + fn window_title_request_trims_and_truncates_shared_boundary() { + let mut request = request("app.setTitle"); + request.payload = Some(json!({ + "title": format!(" {} ", "甲".repeat(120)) + })); + + let title = window_title_from_request(&request).expect("window title"); + + assert_eq!(title.chars().count(), WINDOW_TITLE_MAX_LENGTH); + assert!(title.chars().all(|character| character == '甲')); + } }