windows-rs 之 windows-webview 实战:用 Rust 在桌面应用中托管 Microsoft Edge WebView2 📅 发布时间:2026/9/15 20:14:25 👁 浏览次数: windows-rs 之 windows-webview 实战用 Rust 在桌面应用中托管 Microsoft Edge WebView2【免费下载链接】windows-rsRust for Windows项目地址: https://gitcode.com/GitHub_Trending/wi/windows-rs导读本文围绕 windows-rs 工作区中的windows-webviewcrate源码位于 crates/libs/webview展开讲解如何在 Windows 桌面应用中用 Rust 安全地封装并托管 WebView2基于 Chromium 的 Microsoft Edge浏览器控件。读完本文你将掌握从创建一个 Environment 并导航到网址的最小示例到窗口尺寸同步、事件订阅、宿主与 JavaScript 双向通信、本地内容拦截、Profile/Cookie/下载/DevTools 等完整能力并了解其异步初始化在 UI 线程消息泵上的底层原理。一、crate 定位何时使用 windows-webviewwindows-webview是对 WebView2 与官方指南 docs/crates/windows-webview.md当你的 Windows 桌面应用需要以下能力时应选择本 crate在窗口HWND中托管 Web 内容与页面中的 JavaScript 交换消息使用浏览器的 Profile、Cookie、下载、Chrome DevTools ProtocolCDP等设施。crate 有两大托管路径默认路径在原生HWND窗口中托管 WebView2reactor 路径启用reactorfeature 后可将 WinUI XAML 的 WebView2 控件放入windows-reactor的视图树中。需要强调的是本 crate 是精选 WebView2 API 表面的封装而非完整 SDK 的暴露层。若应用需要未在此处体现的 API应改用原始 WebView2 绑定ICoreWebView2*直接绑定。二、前置条件与依赖配置2.1 运行时与环境要求WebView2 RuntimeHWND托管方式要求系统已安装 Microsoft Edge WebView2 运行时大多数现代 Windows 10/11 系统已内置 Edge 并附带该运行时。活着的父窗口 消息循环宿主必须提供一个存活的父窗口并在其 UI 线程上持续分发消息。windows-window是一个适合此用途的小型宿主窗口 crate。COM STA 线程必须在 COM 单线程单元STA上创建环境。Environment::new与Environment::with_options在必要时会把调用线程初始化为 STA如果该线程已被初始化为多线程单元MTA它们会返回错误。从 environment.rs 的实现可以看到这是通过CoInitializeEx(COINIT_APARTMENTTHREADED)完成的当返回RPC_E_CHANGED_MODE时会抛出明确的错误说明。生命周期顺序父窗口必须活得比它的Controller更久。2.2 依赖配置在Cargo.toml中添加[dependencies] windows-webview 0.100 windows-window { workspace true }windows-webview本身依赖windows-core与windows-window见 Cargo.tomlreactor为可选 feature开启时会引入windows-reactor[dependencies] windows-webview { version 0.100, features [reactor] }crate 的rust-version为 1.95采用 2024 edition默认目标平台为x86_64-pc-windows-msvc。三、最小托管示例从零到完成导航readme 给出的最小示例见 readme.md演示了完整的最小闭环——创建环境、创建控制器、取得 WebView、导航use windows_webview::*; use windows_window::Window; fn host(window: Window) - Result() { let environment Environment::new()?; let controller environment.create_controller(window)?; let webview controller.webview()?; webview.navigate(https://example.com)?; Ok(()) }对应的完整可运行示例见 crates/samples/webview/samples/examples/minimal.rs。两个关键注意点Environment与Controller的创建在 WebView2 中是异步操作本 crate 通过在 UI 线程上泵送消息队列、直到回调完成将其呈现为同步调用细节见下文异步初始化与消息泵。因此这些调用应发生在设置阶段、进入应用主消息循环之前。在浏览器被托管期间必须保持Controller存活。Controller被 drop 后其持有的WebView将不可用。四、首个完整工作流托管一个可缩放页面官方指南给出了从最小示例延伸出去的完整工作流docs/crates/windows-webview.md在 UI 线程上创建父窗口创建一个Environment再为该窗口创建一个Controller把控制器边界设置为当前客户区大小并在窗口的 resize 回调中重复该操作取得WebView注册应用所需的事件并保留每一个返回的EventRegistration导航然后进入宿主消息循环若关闭顺序由应用控制则在销毁父窗口之前先关闭控制器。其中关键的 resize 与事件订阅代码如下use windows_webview::*; fn configure(controller: Controller, webview: WebView, width: i32, height: i32) - ResultEventRegistration { controller.set_bounds(0, 0, width, height)?; let navigation webview.on_navigation_completed(|args| { println!(navigation succeeded: {}, args.is_success()); })?; webview.navigate(https://learn.microsoft.com/windows/apps/)?; Ok(navigation) }陷阱如果立刻 drop 掉navigation就会立即退订该处理器。WebView 示例在消息循环的整个生命周期内把注册保存在VecEventRegistration中。可参考 crates/samples/webview/samples/src/lib.rs 中关于 resize、注册、控制器与消息循环生命周期的正确写法。五、对象模型与回调生命周期5.1 三个核心对象的分工对象职责Environment拥有用户数据目录与浏览器进程上下文。复用它创建多个Controller即可共享同一上下文浏览器进程与数据。Controller拥有托管在父窗口中的浏览器控制边界、可见性、焦点与显示属性。WebView表示页面本身提供导航、脚本、消息、Profile 与事件 API。5.2 异步初始化与消息泵pump原理Environment与Controller的创建是异步 WebView2 操作。crate 通过泵送调用线程的消息队列直到回调完成把它们包装成同步调用。其底层实现在 pump.rs 中一个RcCellOptionResultT的一次性结果槽位配合GetMessageW/TranslateMessage/DispatchMessageW循环不断分发消息直到完成回调写入结果。pub(crate) fn waitT(slot: SlotT) - ResultT { let mut message MSG::default(); loop { if let Some(value) slot.take() { return value; } match GetMessageW(mut message, std::ptr::null_mut(), 0, 0).0 { -1 return Err(Error::from_thread()), 0 return Err(Error::empty()), // WM_QUIT 在完成回调执行前结束了嵌套泵 _ { let _ TranslateMessage(message); DispatchMessageW(message); } } } }这个机制之所以安全是因为创建与完成都发生在同一个 STA 线程上。而运行时操作如execute_script、Cookie 枚举、Profile 清理、DevTools 调用则保持回调式、在 UI 线程上完成避免嵌套的消息泵送。同一规则也适用于add_script_to_execute_on_document_created——它同样泵送消息因此应在设置阶段调用。5.3 显式关闭与安全边界需要显式关闭时调用Controller::close底层对应ICoreWebView2Controller::Close。任何情况下使用WebView期间都要保持Controller存活。通过unsafe create_*_for_hwnd传入的原始父窗口句柄必须在控制器的整个生命周期内保持有效。六、导航与页面状态6.1 基础导航 APIWebView支持navigate(uri)导航到指定 URInavigate_to_string(html)把给定 HTML 内容作为文档导航reload()/stop()/go_back()/go_forward()历史与加载控制source()/document_title()读取当前顶层文档的 URL 与标题。这些方法在 webview.rs 中直接映射到ICoreWebView2的对应 COM 调用。6.2 自定义导航请求NavigationRequest普通navigate无法携带自定义方法、请求头或请求体此时使用NavigationRequest。默认是GET 无头 无体可通过 fluent 方法定制use windows_webview::{NavigationRequest, Result, WebView}; fn submit(webview: WebView) - Result() { let request NavigationRequest::new(https://example.test/session) .method(POST) .header(Content-Type, application/json) .body(br#{active:true}#.to_vec()); webview.navigate_with_request(request) }从源码看navigate_with_request会把 headers 拼装为Name: Value\r\n格式并通过SHCreateMemStream把 body 转成IStream最终调用CreateWebResourceRequestNavigateWithWebResourceRequestwebview.rs。6.3 导航事件与故障处理导航生命周期通过三个事件观察on_navigation_starting导航开始前触发可检查目标并通过NavigationStartingArgs::set_cancel(true)取消on_content_loading新文档内容开始加载时触发on_navigation_completed导航完成时触发args.is_success()表示是否成功。各事件的参数均带navigation_id()用于关联同一导航的不同阶段。on_process_failed用于处理进程故障ProcessFailedKind::RenderProcessExited渲染进程崩溃之后可以reload恢复而BrowserProcessExited浏览器进程退出则必须创建新的WebView。完整的事件参数类型定义见 event.rs。七、事件、决策与清理EventRegistration 与 Deferral7.1 RAII 风格的事件注册每个on_*方法都返回一个#[must_use]的EventRegistrationevent.rs。丢弃它、或显式调用remove()都会自动退订回调——这符合 Rust 的 RAII 惯例避免了忘记退订导致的内存泄漏。事件回调运行在 UI 线程上且不是Send。因此回调内部工作要短小精悍耗时工作应移出回调避免阻塞消息分发。7.2 事件覆盖范围事件集合覆盖对应源码中的subscription!宏与手写订阅方法导航on_navigation_starting、on_content_loading、on_navigation_completed页面状态on_document_title_changed、on_contains_fullscreen_element_changed窗口行为on_window_close_requested、on_new_window_requested权限on_permission_requested下载on_download_starting进程故障on_process_failed消息on_web_message_received资源请求on_web_resource_requested焦点on_got_focus、on_lost_focus、on_move_focus_requested、on_accelerator_key_pressedDevToolson_dev_tools_protocol_event。7.3 Deferral事件返回后继续决策NewWindowRequestedArgs与PermissionRequestedArgs可以在事件返回之后通过defer()取得一个Deferral来完成决策例如等待用户在弹出的对话框中作答。Deferral 在 drop 时自动完成必须保持它存活直到决策被应用——过早 drop 会告诉 WebView2 处理已结束。下载进度订阅遵循同样的 RAII 规则不仅外层on_download_starting的注册要保留DownloadOperation::on_bytes_received_changed与on_state_changed返回的注册也必须保留。八、宿主集成尺寸、DPI、焦点与控制器选项8.1 尺寸、可见性与 DPIController::set_bounds(left, top, right, bottom)使用父窗口客户区像素坐标在父窗口的WM_MOVE处理中调用notify_parent_window_position_changed()让浏览器弹出的窗口与对话框跟随宿主移动set_visible(false)隐藏控制器隐藏期间可把 WebView 内存目标设为MemoryUsageTargetLevel::Low显示时恢复Normalwebview.rszoom_factor控制页面缩放1.0即 100%rasterization_scale控制渲染缩放默认会自动检测显示器 DPI 变化若缩放由应用自己管理先调用set_should_detect_monitor_scale_changes(false)关闭自动检测set_default_background_color控制页面绘制前的区域颜色。WebView2 仅支持完全不透明或完全透明的 alpha使用Color::TRANSPARENT可以让宿主窗口在页面透明处透出controller.rs。8.2 焦点与键盘父窗口收到WM_SETFOCUS时调用move_focus(MoveFocusReason::Programmatic)把键盘焦点移入浏览器on_move_focus_requested让宿主能继续把 Tab 导航带入另一个原生控件移动焦点并调用MoveFocusRequestedArgs::set_handled(true)on_accelerator_key_pressed可以在页面处理之前消费应用快捷键如 F 键、Ctrl/Alt 组合键相关浏览器加速键设置决定 WebView2 内置快捷键是否保持可用。8.3 控制器创建选项ControllerOptionsControllerOptions用于在创建时选择 Profile 名称、隐私模式与初始背景色let options ControllerOptions::new() .profile_name(app-profile) .in_private_mode(false) .default_background_color(Color::TRANSPARENT); let controller environment.create_controller_with_options(window, options)?;这些选项只在控制器创建时生效无法事后通过WebView修改。源码实现controller.rs通过CreateCoreWebView2ControllerOptions创建 COM 选项对象再调用CreateCoreWebView2ControllerWithOptions。九、环境与浏览器设置9.1 EnvironmentOptionsEnvironmentOptions通过 fluent 风格构建配置浏览器可执行文件夹、用户数据文件夹、浏览器命令行参数、语言、最低兼容浏览器版本、操作系统账户单点登录、浏览器扩展与滚动条样式use windows_webview::EnvironmentOptions; let options EnvironmentOptions::new() .user_data_folder(rC:\MyApp\WebView2) .additional_browser_arguments(--disable-featuresmsSmartScreenProtection) .language(en-US); let environment Environment::with_options(options)?;各配置项说明见 options.rs方法说明browser_executable_folder指定包含 WebView2 浏览器Edge二进制的文件夹替代已安装的运行时user_data_folderWebView2 存放用户数据缓存、Cookie 等的文件夹additional_browser_arguments传递给浏览器进程的额外命令行参数language默认显示语言如en-UStarget_compatible_browser_version环境要求的最低兼容浏览器版本未设置时使用 WebView2 GA 基线86.0.616.0WebView2 拒绝空值源码中的默认常量见 options.rsallow_single_sign_on_using_os_primary_account使用操作系统主账户的单点登录are_browser_extensions_enabled允许环境加载并运行浏览器扩展scrollbar_style滚动条样式Default浏览器默认或FluentOverlayFluent 风格细覆盖条数据目录建议默认位置不合适时选择可写、由应用拥有的用户数据文件夹。用同一 Environment 创建的多个 Controller 共享其浏览器进程与数据上下文在该上下文中命名 Profile 可以隔离 Cookie、缓存与存储。9.2 WebView 设置SettingsWebView::settings返回一组开关settings.rs包括脚本is_script_enabled / set_script_enabled消息is_web_message_enabled / set_web_message_enabled对话框are_default_script_dialogs_enabled / set_default_script_dialogs_enabled状态栏、DevTools、上下文菜单、宿主对象、缩放控件、错误页、加速键、自动填充、密码保存、捏合缩放、滑动导航、非客户区等开关用户代理覆盖user-agent override。注意Settings 的 setter 在下次导航时生效。十、宿主与 JavaScript 双向通信10.1 页面 → 宿主页面调用window.chrome.webview.postMessage(...)宿主通过on_web_message_received接收。收到消息后先检查WebMessageReceivedArgs::source()消息来源文档的 URI不要信任来自可导航内容的任意消息用web_message_as_json()获取任意 JavaScript 值的 JSON 序列化结果当协议要求字符串时用try_web_message_as_string()若页面发送的不是字符串则返回错误。10.2 宿主 → 页面post_web_message_as_json(json)以 JSON 值发送页面通过window.chrome.webview.addEventListener(message, ...)接收event.data为解析后的 JSONpost_web_message_as_string(message)以字符串发送execute_script(javascript, handler)在页面上下文异步执行 JavaScript回调在 UI 线程收到 JSON 编码的结果add_script_to_execute_on_document_created(javascript)注册在每个新文档中、先于页面脚本执行的注入脚本返回ScriptId若后续需要移除保留该 ID 并调用remove_script_to_execute_on_document_created(id)。ipc示例crates/samples/webview/samples/examples/ipc.rs与script示例script.rs演示了消息注入、收发与脚本增删的完整流程。十一、本地内容与请求拦截11.1 磁盘文件虚拟主机映射对于磁盘上的文件使用set_virtual_host_name_to_folder_mapping(host_name, folder_path, access_kind)把虚拟主机名映射到本地文件夹然后导航到 HTTPS 源例如https://app.example/index.html。HostResourceAccessKind控制跨源访问取值行为Deny其他源的资源不能访问映射内容Allow任意源的资源都可以访问映射内容DenyCors与Deny类似但允许通过 CORS 的跨源请求访问结束时调用clear_virtual_host_name_to_folder_mapping(host_name)清除映射。示例见 crates/samples/webview/samples/examples/local_files.rs。11.2 内存内容资源请求拦截对于动态生成或内嵌的字节内容使用on_web_resource_requested(uri_filter, handler)。其通配符过滤器限制哪些请求到达处理器。处理器返回Some(WebResourceResponse)提供状态码、请求头、内容类型与响应体None继续浏览器默认处理。处理器在 UI 线程上同步运行因此昂贵内容应提前准备好。从源码看webview.rs注册会同时添加请求过滤器注册被 drop 时会连同过滤器一起移除。示例见 crates/samples/webview/samples/examples/custom_protocol.rs它从内存中提供 HTML 与 CSS。十二、Profile、Cookie、下载与 DevToolsCookieCookieManager通过WebView::cookie_manager()取得支持创建、更新、枚举与删除 Cookie枚举是回调式的。ProfileProfileWebView::profile()暴露名称、路径、隐私状态、首选配色方案、下载文件夹以及回调式的浏览数据清理。下载on_download_starting可以修改结果路径、取消操作或保留DownloadOperation用于暂停、恢复、取消、进度、状态与中断原因查询。示例见 crates/samples/webview/samples/examples/downloads.rs。DevTools/CDPcall_dev_tools_protocol_method(method, params_json, handler)发送 CDP 方法与 JSON 参数无需打开远程调试端口。通过on_dev_tools_protocol_event(event_name, handler)订阅的多数 CDP 事件需要先用 CDP 方法启用其所属域domain才会触发。示例见 crates/samples/webview/samples/examples/devtools.rs。十三、Reactor 集成XAML 路径当浏览器应属于 Reactor 视觉树时启用reactorfeaturewebview()返回一个View并把就绪的WebView提供给其回调原生初始化出错时它会 panic若组件需要自行处理IntegrationError改用webview_result()返回的WebView与HWND路径支持完全相同的导航、消息、设置与事件 API。重要限制XAML 控件只有在进入活着的视觉树之后才会初始化因此不要在组件构造期间期待它的回调。自包含的 Reactor 应用还必须部署Microsoft.Web.WebView2.Core.dllwindows-reactor-setup负责暂存它。组件与部署布局可参考 crates/samples/reactor/webview 示例。底层机制上WinUI XAML 控件暴露的是 WinRT 的CoreWebView2而本 crate 封装的是 COM 的ICoreWebView2两者不能通过普通接口转换互相转换必须通过官方支持的桥接接口ICoreWebView2Interop2::GetComICoreWebView2获取 COM 内核见 reactor.rs 与 docs/crates/windows-webview.md。十四、示例程序一览使用以下命令运行 WebView 示例cargo run -p webview_samples --example name示例工作流minimal创建HWND宿主、设置控制器尺寸并导航events观察导航、弹窗、权限、关闭与进程故障事件ipc注入脚本、交换消息、执行 JavaScriptcustom_protocol从内存中提供 HTML 与 CSSlocal_files把文件夹映射为 HTTPS 虚拟主机downloads跟踪下载进度与状态cookies添加与枚举 Cookieprofile使用隐私模式、配色方案与浏览数据清理script添加、执行与移除文档创建时脚本devtools调用 CDP 方法并订阅 CDP 事件示例源码位于 crates/samples/webview/samples/examples共享辅助代码 crates/samples/webview/samples/src/lib.rs 演示了正确的 resize、注册、控制器与消息循环生命周期。十五、内部实现速览面向贡献者15.1 绑定生成WebView2 只提供 C/C 头文件而没有 Windows 元数据因此tool_webview分三阶段生成提交到仓库的绑定阶段实现输出头文件 → RDLwindows_clang::clang()target/webview/WebView2.rdlRDL → winmdwindows_rdl::reader()target/webview/WebView2.winmdwinmd → Rustwindows_bindgencrates/libs/webview/src/bindings.rs工具下载固定的Microsoft.Web.WebView2NuGet 包分别解析WebView2.h与WebView2Interop.h两个输入再合并翻译单元目标平台为x86_64-pc-windows-msvc启用 Microsoft 扩展。重新生成用cargo run -p tool_webview切勿手改src/bindings.rs。过滤规则在 crates/tools/webview/src/webview.txt。15.2 运行时与字符串handler.rs中的完成处理器与事件适配器使用implement_decl!宏避免引入windows-core的 proc-macro 依赖pump.rs的一次性结果槽与消息泵保证了创建流程的同步观感事件适配器把 COM 的 add/remove token 转换为EventRegistration资源拦截在注册 drop 时同时移除请求过滤器string.rs统一处理借用的 UTF-16 输入、借用的回调字符串、需要CoTaskMemFree释放的LPWSTR以及由实现的接口返回的任务分配器输出字符串。由于 WebView2 需要运行时、窗口与消息泵仓库没有无头集成测试套件示例应用本身就是端到端的托管与功能覆盖测试。结语windows-webview以不到数百行的安全封装把 WebView2 最常用的能力——导航、脚本、消息、Profile、Cookie、下载、DevTools、资源拦截、事件订阅——以符合 Rust 习惯RAII、Result、回调的方式呈现给桌面应用开发者。理解创建期同步泵送、运行期回调式的线程模型并牢记EventRegistration与Deferral的生命周期纪律即可在项目中稳定、高效地集成 Chromium 内核的 Web 托管能力。【免费下载链接】windows-rsRust for Windows项目地址: https://gitcode.com/GitHub_Trending/wi/windows-rs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考