Files
Genarrative/apps/desktop-shell/src-tauri/src/host_bridge/title.rs
T
kdletters be870c58df 收口桌面壳窗口标题诊断日志
桌面壳窗口标题失败只记录固定阶段标签

扩展桌面壳配置门禁禁止窗口标题日志输出 Tauri 错误细节

补充宿主壳方案和共享决策中的窗口标题诊断边界
2026-06-21 04:51:01 +08:00

138 lines
4.3 KiB
Rust

use crate::host_bridge::protocol::{
failed, ok, required_string_payload, HostBridgeRequest, HostBridgeResponse,
};
use serde_json::json;
use tauri::Manager;
const WINDOW_TITLE_MAX_LENGTH: usize = 80;
fn normalize_window_title(raw_title: &str) -> Option<String> {
let title = raw_title.trim();
if title.is_empty() || title.chars().any(char::is_control) {
return None;
}
Some(title.chars().take(WINDOW_TITLE_MAX_LENGTH).collect())
}
fn window_title_from_request(
request: &HostBridgeRequest,
) -> Result<String, HostBridgeResponse> {
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 window_title_from_request(request) {
Ok(title) => title,
Err(response) => return response,
};
match app.get_webview_window("main") {
Some(window) => match window.set_title(&title) {
Ok(()) => ok(request.id.clone(), json!(true)),
Err(_error) => {
log_desktop_window_title_failure("set");
window_title_unavailable_response(request)
}
},
None => {
log_desktop_window_title_failure("window");
window_title_unavailable_response(request)
}
}
}
fn window_title_unavailable_response(request: &HostBridgeRequest) -> HostBridgeResponse {
failed(request.id.clone(), "host_error", "window title unavailable")
}
fn log_desktop_window_title_failure(label: &str) -> bool {
eprintln!("desktop window title failed for {label}");
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::host_bridge::protocol::request;
use serde_json::json;
#[test]
fn window_title_normalization_requires_visible_text() {
assert_eq!(
normalize_window_title(" Genarrative "),
Some("Genarrative".to_string())
);
assert_eq!(normalize_window_title(""), None);
assert_eq!(normalize_window_title("Genarrative\nDev"), None);
let long_title = "甲".repeat(120);
assert_eq!(
normalize_window_title(&long_title)
.expect("truncated title")
.chars()
.count(),
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 == '甲'));
}
#[test]
fn window_title_unavailable_response_is_stable() {
let response = window_title_unavailable_response(&request("app.setTitle"));
assert!(!response.ok);
let error = response.error.expect("window title error");
assert_eq!(error.code, "host_error");
assert_eq!(error.message, "window title unavailable");
}
#[test]
fn window_title_failures_are_logged_without_exposing_native_detail() {
assert!(!log_desktop_window_title_failure("set"));
assert!(!log_desktop_window_title_failure("window"));
}
#[test]
fn window_title_failures_log_stable_label_only() {
assert!(!log_desktop_window_title_failure("set"));
}
}