修复 release 默认网页右键菜单
release 客户端关闭 WebView 默认上下文菜单 保留 contextmenu 事件供业务组件处理 补充 release/debug 行为测试与技术方案约定
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
use tauri::{
|
||||
plugin::{Builder, TauriPlugin},
|
||||
Runtime,
|
||||
};
|
||||
|
||||
/// Release 客户端只取消浏览器的默认上下文菜单。
|
||||
///
|
||||
/// 这里刻意只调用 `preventDefault`:`contextmenu` 事件仍会继续沿 DOM
|
||||
/// 传播,业务组件可以照常接收并打开自己的菜单。捕获阶段用于覆盖没有
|
||||
/// 自己右键处理的页面空白区域,但不会调用 `stopPropagation`。
|
||||
const RELEASE_CONTEXT_MENU_INIT_SCRIPT: &str = r#"
|
||||
document.addEventListener(
|
||||
'contextmenu',
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
},
|
||||
true,
|
||||
);
|
||||
"#;
|
||||
|
||||
fn context_menu_init_script(is_release: bool) -> Option<&'static str> {
|
||||
is_release.then_some(RELEASE_CONTEXT_MENU_INIT_SCRIPT)
|
||||
}
|
||||
|
||||
#[cfg(all(windows, not(debug_assertions)))]
|
||||
fn disable_windows_default_context_menus<R: Runtime>(webview: tauri::Webview<R>) {
|
||||
let _ = webview.with_webview(|platform_webview| {
|
||||
// WebView2 的原生设置只关闭浏览器菜单,不会取消 DOM 的
|
||||
// `contextmenu` 事件,因此业务右键菜单仍然可以正常工作。
|
||||
// SAFETY: Tauri 在 WebView2 UI 线程上提供当前存活的 controller,
|
||||
// 这些 COM 接口调用只使用该闭包期间有效的句柄。
|
||||
let _ = unsafe {
|
||||
platform_webview
|
||||
.controller()
|
||||
.CoreWebView2()
|
||||
.and_then(|webview| webview.Settings())
|
||||
.and_then(|settings| settings.SetAreDefaultContextMenusEnabled(false))
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/// 注册 release-only 的默认网页上下文菜单策略。
|
||||
pub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
let mut builder = Builder::new("release-context-menu");
|
||||
|
||||
if let Some(script) = context_menu_init_script(!cfg!(debug_assertions)) {
|
||||
builder = builder.js_init_script_on_all_frames(script);
|
||||
}
|
||||
|
||||
#[cfg(all(windows, not(debug_assertions)))]
|
||||
{
|
||||
builder = builder.on_webview_ready(disable_windows_default_context_menus);
|
||||
}
|
||||
|
||||
builder.build()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn release_build_gets_a_context_menu_init_script() {
|
||||
assert_eq!(
|
||||
context_menu_init_script(true),
|
||||
Some(RELEASE_CONTEXT_MENU_INIT_SCRIPT)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_build_keeps_the_default_context_menu_strategy_untouched() {
|
||||
assert_eq!(context_menu_init_script(false), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_script_prevents_only_the_default_action() {
|
||||
let script = RELEASE_CONTEXT_MENU_INIT_SCRIPT;
|
||||
assert!(script.contains("contextmenu"));
|
||||
assert!(script.contains("event.preventDefault()"));
|
||||
assert!(script.contains("true"));
|
||||
assert!(!script.contains("stopPropagation"));
|
||||
assert!(!script.contains("stopImmediatePropagation"));
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@ mod command_sandbox_trampoline;
|
||||
mod commands;
|
||||
mod config;
|
||||
mod context_compaction;
|
||||
mod context_menu;
|
||||
#[cfg(all(debug_assertions, not(test)))]
|
||||
mod debug;
|
||||
mod delegation;
|
||||
@@ -2021,6 +2022,7 @@ fn main() {
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(context_menu::init())
|
||||
.manage(game_creator_preview_registry())
|
||||
.manage(ProjectResourcePreviewReadManager::default())
|
||||
.setup(move |app| {
|
||||
|
||||
@@ -50,7 +50,7 @@ Runtime 确认卡与普通聊天确认卡必须共用“信息区 + 固定操作
|
||||
|
||||
此 seam 的纯测试覆盖嵌套 context、offset 反算、同树重挂载、子节点页面位置保持与 Inspector anchor preset 保位;几何修复应在此 Module 局部验证后由三个 adapter 复用。
|
||||
|
||||
左侧 `UI Tree` 与预览画布复用同一个节点右键菜单组件和条目模型,顺序固定为“新增子节点 → 新增同级节点 → 删除节点及子节点”。页面根仅显示“新增子节点”;虚拟超级根不提供菜单。预览仅在“编辑叠加”模式中响应已渲染节点的鼠标右键,右键会选中该节点并阻止嵌套节点事件冒泡;“最终预览”以及画布空白处保留原有行为,空白处不接管浏览器原生菜单。
|
||||
左侧 `UI Tree` 与预览画布复用同一个节点右键菜单组件和条目模型,顺序固定为“新增子节点 → 新增同级节点 → 删除节点及子节点”。页面根仅显示“新增子节点”;虚拟超级根不提供菜单。预览仅在“编辑叠加”模式中响应已渲染节点的鼠标右键,右键会选中该节点并阻止嵌套节点事件冒泡;“最终预览”以及画布空白处不新增业务菜单。debug/dev 构建保留浏览器默认网页上下文菜单,release 客户端关闭 WebView 的默认网页菜单,但仍派发 `contextmenu` 事件给业务组件,不以停止事件传播的方式屏蔽右键交互。
|
||||
|
||||
菜单由页面级 portal 呈现,使用屏幕坐标且在视口边缘内收,避免受预览画布缩放和容器裁切影响。点击菜单外、按 Escape、窗口失焦、滚动或调整窗口大小均关闭菜单。全局 `controller.editor.isLocked` 时,节点仍可选中和查看菜单,但所有结构修改项禁用;状态层的 mutation 校验继续作为最终防线。点击可用项先关闭菜单再执行现有 controller 动作,不改变既有新增或删除后的选择策略。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user