| Crates.io | nwg-webview-ctrl |
| lib.rs | nwg-webview-ctrl |
| version | 0.1.2 |
| created_at | 2023-11-25 01:01:56.103178+00 |
| updated_at | 2023-11-26 23:31:04.589684+00 |
| description | 封装Microsoft Edge WebView2浏览器内核为Native Windows GUI (i.e. NWG crate)开发框架的WebView图形控件 |
| homepage | |
| repository | https://github.com/stuartZhang/nwg-webview-ctrl |
| max_upload_size | |
| id | 1047942 |
| size | 80,462 |
封装Microsoft Edge WebView2浏览器内核为Native Windows GUI (i.e. NWG crate)开发框架的WebView图形控件 — 具体包括
WebviewContainer自定义控件和WebviewContainerBuilder控件构建器。进而,给Rust Win32 Bindings增添WebView图形控件新成员。
相较人气爆棚的Tauri crate,nwg-webview-ctrl允许WebView参与原生图形控件的布局管理,包括但不限于:
GridLayoutFlexboxLayoutDynLayoutWebviewContainer图形控件的功能定位等同于OSX Cacao crate图形界面开发框架中的cacao::webview::WebView控件。它们都力图凭借构建功能丰富的原生图形界面,重塑原生图形交互在应用程序中的【主体地位】,而不只是陪衬作为H5网页程序的套壳浏览器“附属品”(— 至多也就是位“收租公”)。要说有差别,那也仅是
cacao::webview::WebView封装的是Apple Webkit WebviewWebviewContainer套壳的是Microsoft Edge Webview2Microsoft Edge 86+浏览器的Windows操作系统。或Windows操作系统。一般来讲,Windows 11与打过升级补丁的Windows 10都可以直接运行包含了此图形控件的应用程序。
Cargo Package配置已经锁定了nightly-x86_64-pc-windows-msvc工具链。虽然stable channel工具链也可成功编译,但工具链的GNU (ABI) build却会导致编译时链接WebView2 Runtime失败。
此外,编译环境也需要给Windows操作系统预安装Microsoft Edge 86+或Evergreen WebView2 Runtime。
nwg-webview-ctrl crate并没有直接调用浏览器内核的Win32 COM ABI。而是,站在巨人的肩膀上,将webview2 crate套壳封装于NWG图形开发框架的nwg::Frame控件内。最后,以nwg::Frame控件为“代理”参与原生控件布局。
题外话,
nwg::Frame控件本就是NWG图形开发框架对第三方扩展提供的“接入插槽”。nwg-webview-ctrl crate也算是第三方扩展了。
以图描述更直观,一图抵千词。
Webview初始化是异步的这不是我定的,而是从Win32 COM那一层开始就已经是异步回调了。然后,再经由webview2 crate透传至nwg-webview-ctrl封装代码。但,WebviewContainer还是做了些使生活更美好的工作 — 将【异步回调】变形为
要么,futures::future::FusedFuture的异步阻塞
// 这是【伪码】呀!真实的【返回值】类型会更复杂,但本质如下。
WebviewContainer::ready_fut(&self) -> FusedFuture<Output = (Environment, Controller, WebView)>
将该成员方法返回值直接注入【异步块Task】。再将NWG事件循环作为【反应器Reactor】对接futures crate的【执行器Executor】,以持续轮询推进【异步块Task】的程序执行。
这是我非常推荐的用法,也是examples采用的代码套路。
要么,futures::executor::block_on(Future)的同步阻塞
// 这是【伪码】呀!真实的【返回值】类型会更复杂,但本质如下。
WebviewContainer::ready_block(&self) -> (Environment, Controller, WebView)
该成员方法内部会调用futures::executor::block_on()阻塞当前线程。特别注意:该成员方法仅能在同步上下文中被调用。否则,会导致应用程序运行崩溃!
虽然Webview初始化是异步的,但WebviewContainerBuilder控件构造器自身却未执行任何(同/异步)阻塞操作,而仅只
nwg::Frame控件占位原生布局流webview2::Controller(i.e. Microsoft.Web.WebView2.Core.CoreWebView2Controller)异步初始化流程,却不等待初始化处理结束然后,由WebviewContainer控件“亲自”阻塞程序执行,和等待Webview完全就绪
除了简单,啥也不是。
let mut webview_container = WebviewContainer::default();
// builder 自身是不阻塞的
WebviewContainer::builder().parent(&window).window(&window).build(&mut webview_container)?;
// 由控件对象的成员方法阻塞主线程,和等待 Webview 完全就绪
let (_, _, webview) = webview_container.ready_block().unwrap();
webview.navigate("https://www.minxing365.com").unwrap();
仅四步便点亮Native GUI【异步编程】科技树 — 绝对值得拥有:
TaskExecutorNWG事件循环,和将NWG事件循环作为ReactorWebview初始化FusedFuture对象捕获入异步任务Tasklet mut webview_container = WebviewContainer::default();
// builder 自身是不阻塞的
WebviewContainer::builder()
.parent(&window) // nwg::Frame 控件的父控制是主窗体 window
.window(&window) // webview2::Controller 的关联主窗体也是相同的 window
.build(&mut webview_container).unwrap();
// 1. 构造一个异步任务
let webview_ready_fut = webview_container.ready_fut().unwrap();
// 2. 构造一个单线程异步执行器
let mut executor = {
let executor = LocalPool::new();
executor.spawner().spawn_local(async move {
// 4. 将异步任务注入异步执行器
let (_, _, webview) = webview_ready_fut.await;
webview.navigate("https://www.minxing365.com").unwrap();
Ok::<_, Box<dyn Error>>(())
}).unwrap();
executor
};
// 3. 将异步执行器对接`NWG`事件循环
nwg::dispatch_thread_events_with_callback(move || executor.run_until_stalled());
Webview初始化成功的返回值返回值是三元素元组。其三个子元素依次是
webview2::Environment(i.e. Microsoft.Web.WebView2.Core.CoreWebView2Environment)
在多TAB签场景下,此返回值允许多个webview2::Controller实例共享同一个webview2::Environment构造源。于是,多个同源webview2::Controller实例就能共用一套
webview2::Controller(i.e. Microsoft.Web.WebView2.Core.CoreWebView2Controller)
面向整个应用程序中的原生部分,实现
焦点传递
DPI级的整体缩放
改变整体背景色
挂起/恢复渲染进程。WebviewContainer控件内部正在调用该接口,并
NwgEvent::OnWindowMinimize时,挂起Webview渲染进程(WM_SYSCOMMAND, SC_RESTORE)时,恢复Webview渲染进程同步发送主窗体的UI状态信息给CoreWebView2Controller。WebviewContainer控件内部就正在监听
NwgEvent::OnMovenwg::Frame父控件的
NwgEvent::OnResizeNwgEvent::OnMove和传递最新的位置与尺寸信息给CoreWebView2Controller。
析构掉整个Webview控件(包括CoreWebView2Controller和CoreWebView2)。WebviewContainer控件已被实现为底层Webview控件的RAII守卫。即,只要WebviewContainer控件被析构,那么Webview控件也将同步地被释放。
webview2::WebView(i.e. Microsoft.Web.WebView2.Core.CoreWebView2)
面向应用程序中网页部分,实现
native <-> javascript桥目前,被用于布局占位的nwg::Frame控件实例还尚未对外可见。
WebviewContainer的构造与配置WebviewContainer控件支持API与【派生宏】两种实例化方式
API实例化模式// 构造主窗体
let mut window = Window::default();
// 配置主窗体
Window::builder().title("内嵌 WebView 例程").size((1024, 168)).build(&mut window)?;
nwg::full_bind_event_handler(&window.handle, move |event, _data, _handle| {
if let NwgEvent::OnWindowClose = event { // 关闭主窗体。
nwg::stop_thread_dispatch();
}
});
// 构造 Webview 控件
let mut webview_container = WebviewContainer::default();
// 配置 Webview 控件
WebviewContainer::builder()
.parent(&window) // 指定紧上一级控件
.window(&window) // 指定主窗体。在本例中,【主窗体】即是【紧上一级控件】
.flags(WebviewContainerFlags::VISIBLE) // 指定不显示控件边框
.build(&mut webview_container)?;
// 构造 网格布局
let mut grid = GridLayout::default();
// 配置 网格布局
GridLayout::builder()
.margin([0; 4]) // 白边
.max_column(Some(1)) // 网络总列数
.max_row(Some(1)) // 网络总行数
.parent(&window) // 指定给谁布局
.child(0, 0, &webview_container) // 给布局加入子控件。在本例中,唯一的子控件就是 Webview
.build(&mut grid)?;
// 构造【异步·执行器】与【异步·任务】
let mut executor = {
let executor = LocalPool::new();
let webview_ready_fut = webview_container.ready_fut()?;
executor.spawner().spawn_local(async move {
// 在这可以发起能够与 webview 初始化并行工作的异步任务。比如,
// 1. 请求后端接口。
// 2. 读取配置文件
// 然后,再将这些 Future 实例与 webview 初始化 FusedFuture 实例 futures::join! 在一起。
// ....
// ....
let (_, _, webview) = webview_ready_fut.await;
// 执行直接依赖于 webview 实例的业务处理功能。
// 比如,跳转至【欢迎页】
webview.navigate(&cli_params.url)?;
Ok::<_, Box<dyn Error>>(())
}.map(|result| {
if let Err(err) = result {
eprintln!("[app_main]{err}");
}
}))?;
executor
};
// 阻塞主线程,等待用户手动关闭主窗体
nwg::dispatch_thread_events_with_callback(move ||
// 以 win32 UI 的事件循环为【反应器】,对接 futures crate 的【执行器】
executor.run_until_stalled());
Ok(())
执行命令cargo run --example nwg-remote-page可直接运行该例程。
将层叠嵌套的数据结构映射为分区划块的图形界面。这似乎能少写相当一部分重复代码。
// 以数据结构定义与映射图形界面布局。
#[derive(Default, NwgUi)]
pub struct DemoUi {
// 主窗体
#[nwg_control(size: (1024, 168), title: "内嵌 WebView 例程", flags: "MAIN_WINDOW|VISIBLE")]
#[nwg_events(OnWindowClose: [nwg::stop_thread_dispatch()])]
window: Window,
// 布局对象
#[nwg_layout(margin: [0; 4], parent: window, max_column: Some(1), max_row: Some(1), spacing: 0)]
grid: GridLayout, // 网格布局主窗体
// webview 控件
#[nwg_control(flags: "VISIBLE", parent: window, window: &data.window)]
#[nwg_layout_item(layout: grid, row: 0, col: 0)]
webview_container: WebviewContainer, // 向网格布局填入唯一的子控件
}
impl DemoUi {
// 构造【异步·执行器】与【异步·任务】
fn executor(&self, url: &str) -> Result<LocalPool, Box<dyn Error>> {
let executor = LocalPool::new();
let webview_ready_fut = self.webview_container.ready_fut()?;
executor.spawner().spawn_local(async move {
// 在这可以发起能够与 webview 初始化并行工作的异步任务。比如,
// 1. 请求后端接口。
// 2. 读取配置文件
// 然后,再将这些 Future 实例与 webview 初始化 FusedFuture 实例 futures::join! 在一起。
// ....
// ....
let (_, _, webview) = webview_ready_fut.await;
// 执行直接依赖于 webview 实例的业务处理功能。
// 比如,跳转至【欢迎页】
webview.navigate(url)?;
Ok::<_, Box<dyn Error>>(())
}.map(|result| {
if let Err(err) = result {
eprintln!("[app_main]{err}");
}
}))?;
Ok(executor)
}
}
fn main() -> Result<(), Box<dyn Error>> {
let demo_ui_app = DemoUi::build_ui(Default::default())?;
// 构造【异步·执行器】
let mut executor = demo_ui_app.executor("https://www.minxing365.com")?;
// 阻塞主线程,等待用户手动关闭主窗体
nwg::dispatch_thread_events_with_callback(move ||
// 以 win32 UI 的事件循环为【反应器】,对接 futures crate 的【执行器】
executor.run_until_stalled());
Ok(())
}
执行命令cargo run --example nwd-remote-page可直接运行该例程。
WebviewContainerBuilder配置参数是nwg::Frame与webview2::Environment(i.e. Microsoft.Web.WebView2.Core.CoreWebView2Environment)的合集后续出现的文字链都直接关联至
Microsoft MSDN的Win32线上文档,因为
Rust docs实在太稀缺Rust Binding对Win32 COM ABI几乎是1:1映射的,所以直接阅读Microsoft MSDN就足够理解接口功能了。另外,Rust Binding仍尚未全面覆盖每个Win32 COM ABI,所以别看到什么高级功能就兴奋得不要不要的,还得确认它是否已经被webview2-sys crate绑定?
依赖于来自nwg::FrameBuilder的配置参数flags, size, position, enabled, 和parent。这些配置
nwg::FrameBuilder签名保持一致,而nwg::FrameBuilder实例webview2::Environment(i.e. CoreWebView2Environment)初始化依赖于来自webview2::EnvironmentBuilder的配置参数 browser_executable_folder, user_data_folder, language, target_compatible_browser_version, additional_browser_arguments,
allow_single_sign_on_using_osprimary_account。这些参数的含义与用法,请点开链接自己读吧。Microsoft MSDN文档写得极精细。
WebviewContainerBuilder独有的参数window: nwg::Window
WebviewContainer控件的父控件就是应用程序的主窗体,该参数也得显式地传递 — 像例程里那样。webview_env: webview2::Environment
TAB场景下,共享相同的webview2::Environment构造源Webview操控接口后续出现的文字链都直接关联至
Microsoft MSDN的Win32线上文档,因为
Rust docs实在太稀缺Rust Binding对Win32 COM ABI几乎是1:1映射的,所以直接阅读Microsoft MSDN就足够理解接口功能了。另外,Rust Binding仍尚未全面覆盖每个Win32 COM ABI,所以别看到什么高级功能就兴奋得不要不要的,还得确认它是否已经被webview2-sys crate绑定?
按照“(对外)面向原生图形界面上下文hosting-related”与“(对内)面向网页内容web-specific”的分类标准,Webview API被分别挂到
两个类实例上 — 对Webview API的分类也是从Win32 COM那一层就开始了,而不是我搞的。
在webview2::Controller上的Webview API包括:
Webview控件Webview默认背景色Webview显示尺寸Webview显示位置Webview控件
Webview控件,以降低耗电Webview控件。Webview原生控件。涵盖了:
Webview内的网页内容Webview的聚焦/失焦事件,以及焦点在不同原生控件之间转移的事件Ctrl / Alt +任意键的组合键敲击事件在webview2::WebView上的Webview API包括:
js桥
其它Webview API包括:
Webview的额外启动参数AdditionalBrowserArguments添加--pull-to-refreshPDF视图的工具条Webview主题色这个汇总列表直接参考自Microsoft MSDN文档。其中有些Win32 COM ABI接口还没有被webview2-sys crate封装,所以需要亲自动手编写FFI代码才能正常调用许多高级功能接口。