接入桌面壳窗口标题同步

HostBridge 契约新增 app.setTitle 方法和标题 payload

Tauri 桌面壳通过主窗口 API 同步非空窗口标题

桌面壳能力清单和配置守卫声明 app.setTitle

补充标题校验测试并更新宿主壳方案和团队共享决策记录
This commit is contained in:
2026-06-17 22:36:52 +08:00
parent 61d910400e
commit a87f3dcc82
6 changed files with 64 additions and 5 deletions

View File

@@ -1,6 +1,7 @@
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::sync::Mutex;
use tauri::Manager;
use tauri_plugin_clipboard_manager::ClipboardExt;
use tauri_plugin_opener::OpenerExt;
@@ -71,6 +72,7 @@ fn capabilities() -> Vec<&'static str> {
"share.open",
"share.setTarget",
"app.openExternalUrl",
"app.setTitle",
"clipboard.writeText",
]
}
@@ -157,6 +159,15 @@ fn normalize_external_url(raw_url: &str) -> Option<String> {
Some(url.to_string())
}
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(80).collect())
}
fn payload_string<'a>(value: &'a Value, field: &str) -> Option<&'a str> {
value
.get(field)
@@ -296,6 +307,23 @@ fn host_bridge_request(
Err(error) => failed(request.id, "host_error", error.to_string()),
}
}
"app.setTitle" => {
let title = match required_string_payload(&request, "title")
.ok()
.and_then(normalize_window_title)
{
Some(title) => title,
None => return failed(request.id, "invalid_request", "title is required"),
};
match app.get_webview_window("main") {
Some(window) => match window.set_title(&title) {
Ok(()) => ok(request.id, json!(true)),
Err(error) => failed(request.id, "host_error", error.to_string()),
},
None => failed(request.id, "host_error", "main window not found"),
}
}
"share.setTarget" => {
let target = request
.payload
@@ -381,6 +409,10 @@ mod tests {
.as_array()
.unwrap()
.contains(&json!("share.setTarget")));
assert!(result["capabilities"]
.as_array()
.unwrap()
.contains(&json!("app.setTitle")));
}
#[test]
@@ -433,6 +465,25 @@ mod tests {
assert_eq!(normalize_external_url("/relative/path"), None);
}
#[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 share_text_uses_direct_share_payload() {
let state = DesktopShareState::default();

View File

@@ -6,7 +6,7 @@
"build": {
"beforeDevCommand": "npm --prefix ../.. run dev:web",
"beforeBuildCommand": "npm --prefix ../.. run build:raw && npm run typecheck",
"devUrl": "http://127.0.0.1:3000/?clientRuntime=native_app&clientType=native_app&hostShell=tauri_desktop&hostPlatform=unknown&hostVersion=0.1.0&bridgeVersion=1&hostCapabilities=host.getRuntime,share.open,share.setTarget,app.openExternalUrl,clipboard.writeText",
"devUrl": "http://127.0.0.1:3000/?clientRuntime=native_app&clientType=native_app&hostShell=tauri_desktop&hostPlatform=unknown&hostVersion=0.1.0&bridgeVersion=1&hostCapabilities=host.getRuntime,share.open,share.setTarget,app.openExternalUrl,app.setTitle,clipboard.writeText",
"frontendDist": "../../../dist"
},
"app": {
@@ -14,7 +14,7 @@
{
"create": false,
"label": "main",
"url": "index.html?clientRuntime=native_app&clientType=native_app&hostShell=tauri_desktop&hostPlatform=unknown&hostVersion=0.1.0&bridgeVersion=1&hostCapabilities=host.getRuntime,share.open,share.setTarget,app.openExternalUrl,clipboard.writeText",
"url": "index.html?clientRuntime=native_app&clientType=native_app&hostShell=tauri_desktop&hostPlatform=unknown&hostVersion=0.1.0&bridgeVersion=1&hostCapabilities=host.getRuntime,share.open,share.setTarget,app.openExternalUrl,app.setTitle,clipboard.writeText",
"title": "Genarrative",
"width": 1280,
"height": 820,