From bfb90569121639aa93133b8db55b985a1cb28cb1 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Tue, 8 Oct 2024 07:47:43 +0300 Subject: [PATCH 01/11] refactor!: add webview id to protocol handlers and improve API ergonomics --- .changes/custom-protocol-label.md | 28 + README.md | 4 +- examples/async_custom_protocol.rs | 39 +- examples/custom_protocol.rs | 61 ++- examples/custom_titlebar.rs | 20 +- examples/gtk_multiwebview.rs | 63 +-- examples/multiwebview.rs | 16 +- examples/multiwindow.rs | 29 +- examples/reparent.rs | 12 +- examples/simple.rs | 44 +- examples/streaming.rs | 60 +- examples/transparent.rs | 40 +- examples/wgpu.rs | 4 +- examples/winit.rs | 4 +- src/android/binding.rs | 26 +- src/android/kotlin/RustWebView.kt | 2 +- src/android/kotlin/RustWebViewClient.kt | 5 +- src/android/main_pipe.rs | 34 +- src/android/mod.rs | 25 +- src/error.rs | 4 + src/lib.rs | 694 ++++++++++++++---------- src/web_context.rs | 60 +- src/webkitgtk/mod.rs | 25 +- src/webkitgtk/web_context.rs | 22 +- src/webview2/mod.rs | 27 +- src/wkwebview/mod.rs | 68 ++- 26 files changed, 845 insertions(+), 571 deletions(-) create mode 100644 .changes/custom-protocol-label.md diff --git a/.changes/custom-protocol-label.md b/.changes/custom-protocol-label.md new file mode 100644 index 000000000..a77b48b8c --- /dev/null +++ b/.changes/custom-protocol-label.md @@ -0,0 +1,28 @@ +--- +"wry": "minor" +--- + +This release contains quite the breaking changes, because even though wry@0.44, ignored duplicate custom protocols, On Linux when using a shared web context, the custom protocol handler can only be registered once so we are bringing the duplicate custom protocols on Linux again, Windows and macOS are not affected. If using a shared web context, make sure to register a protocol only once on Linux (other platforms should be registed multiple times), use `WebContext::is_custom_protocol_registerd` with `#[cfg(target_os = "linux")]`. + +We also noticed that it is hard to know which webview made a request to the custom protocol so we added a method to attach an ID to a webview, and changed relevant custom protocol APIs to take a new argument that passes specifed Id back to protocol handler. + +We also made a few changes to the builder, specficially `WebViewBuilder::new` and `WebViewBuilder::build` methods to make them more ergonomic to work with. + +- Added `Error::DuplicateCustomProtocol` enum variant. +- Added `Error::ContextDuplicateCustomProtocol` enum variant. +- On Linux, return an error in `WebViewBuilder::build` if registering a custom protocol multiple times. +- Added `WebContext::is_custom_protocol_registerd` to check if a protocol has been regsterd for this web context. +- Added `WebViewId` alias type. +- **Breaking** Changed `WebViewAttributes` to have a lifetime parameter. +- Added `WebViewAttributes.id` field to specify an id for the webview. +- Added `WebViewBuilder::with_id` method to specify an id for the webview. +- Added `WebViewAttributes.context` field to specify a shared context for the webview. +- **Breaking** Changed `WebViewAttributes.custom_protocols` field,`WebViewBuilder::with_custom_protocol` method and `WebViewBuilder::with_async_custom_protocol` method handler function to take `Option` as the first argument to check which webview made the request to the protocol. +- **Breaking** Changed `WebViewBuilder::with_web_context` to be a static method to create a builder with a webcontext, instead of it being a setter method. It is now an alternaitve to `WebviewBuilder::new` +- Added `WebViewBuilder::with_attributes` to create a webview builder with provided attributes. +- **Breaking** Changed `WebViewBuilder::new` to take zero arguments. +- **Breaking** Changed `WebViewBuilder::build` method to take a reference to a window to create the webview in it. +- **Breaking** Removed `WebViewBuilder::new_as_child`. +- Added `WebViewBuilder::build_as_child` method to take a reference to a window to create the webview in it. +- **Breaking** Removed `WebViewBuilderExtUnix::new_gtk` to take zero arguments. +- Added `WebViewBuilderExtUnix::build_gtk`. diff --git a/README.md b/README.md index 46e0ccfb3..0451d2ba9 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,9 @@ fn main() -> wry::Result<()> { .with_title("Hello World") .build(&event_loop) .unwrap(); - let _webview = WebViewBuilder::new(&window) + let _webview = WebViewBuilder::new() .with_url("https://tauri.app") - .build()?; + .build(&window)?; event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; diff --git a/examples/async_custom_protocol.rs b/examples/async_custom_protocol.rs index 757c5bbd9..131f9a7e4 100644 --- a/examples/async_custom_protocol.rs +++ b/examples/async_custom_protocol.rs @@ -18,42 +18,41 @@ fn main() -> wry::Result<()> { let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); + let builder = WebViewBuilder::new() + .with_asynchronous_custom_protocol("wry".into(), move |_webview_id, request, responder| { + match get_wry_response(request) { + Ok(http_response) => responder.respond(http_response), + Err(e) => responder.respond( + http::Response::builder() + .header(CONTENT_TYPE, "text/plain") + .status(500) + .body(e.to_string().as_bytes().to_vec()) + .unwrap(), + ), + } + }) + // tell the webview to load the custom protocol + .with_url("wry://localhost"); + #[cfg(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" ))] - let builder = WebViewBuilder::new(&window); - + let _webview = builder.build(&window)?; #[cfg(not(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" )))] - let builder = { + let _webview = { use tao::platform::unix::WindowExtUnix; use wry::WebViewBuilderExtUnix; let vbox = window.default_vbox().unwrap(); - WebViewBuilder::new_gtk(vbox) + builder.build_gtk(vbox)? }; - let _webview = builder - .with_asynchronous_custom_protocol("wry".into(), move |request, responder| { - match get_wry_response(request) { - Ok(http_response) => responder.respond(http_response), - Err(e) => responder.respond( - http::Response::builder() - .header(CONTENT_TYPE, "text/plain") - .status(500) - .body(e.to_string().as_bytes().to_vec()) - .unwrap(), - ), - } - }) - // tell the webview to load the custom protocol - .with_url("wry://localhost") - .build()?; event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; diff --git a/examples/custom_protocol.rs b/examples/custom_protocol.rs index b24adc8a4..e19a9a759 100644 --- a/examples/custom_protocol.rs +++ b/examples/custom_protocol.rs @@ -18,30 +18,50 @@ fn main() -> wry::Result<()> { let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); + let builder = WebViewBuilder::new() + .with_id("id2") + .with_custom_protocol( + "wry".into(), + move |_webview_id, request| match get_wry_response(request) { + Ok(r) => r.map(Into::into), + Err(e) => http::Response::builder() + .header(CONTENT_TYPE, "text/plain") + .status(500) + .body(e.to_string().as_bytes().to_vec()) + .unwrap() + .map(Into::into), + }, + ) + // tell the webview to load the custom protocol + .with_url("wry://localhost"); + #[cfg(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" ))] - let builder = WebViewBuilder::new(&window); - + let _webview = builder.build(&window)?; #[cfg(not(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" )))] - let builder = { + let _webview = { use tao::platform::unix::WindowExtUnix; use wry::WebViewBuilderExtUnix; let vbox = window.default_vbox().unwrap(); - WebViewBuilder::new_gtk(vbox) + builder.build_gtk(vbox)? }; - let _webview = builder - .with_custom_protocol("wry".into(), move |request| { - match get_wry_response(request) { + let window = WindowBuilder::new().build(&event_loop).unwrap(); + + let builder = WebViewBuilder::new() + .with_id("id1") + .with_custom_protocol( + "wry".into(), + move |_webview_id, request| match get_wry_response(request) { Ok(r) => r.map(Into::into), Err(e) => http::Response::builder() .header(CONTENT_TYPE, "text/plain") @@ -49,11 +69,30 @@ fn main() -> wry::Result<()> { .body(e.to_string().as_bytes().to_vec()) .unwrap() .map(Into::into), - } - }) + }, + ) // tell the webview to load the custom protocol - .with_url("wry://localhost") - .build()?; + .with_url("wry://localhost"); + + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + ))] + let _webview = builder.build(&window)?; + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + let _webview = { + use tao::platform::unix::WindowExtUnix; + use wry::WebViewBuilderExtUnix; + let vbox = window.default_vbox().unwrap(); + builder.build_gtk(vbox)? + }; event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; diff --git a/examples/custom_titlebar.rs b/examples/custom_titlebar.rs index dd3ecbb95..226207ac8 100644 --- a/examples/custom_titlebar.rs +++ b/examples/custom_titlebar.rs @@ -241,34 +241,32 @@ fn main() -> wry::Result<()> { } }; + let builder = WebViewBuilder::new() + .with_html(HTML) + .with_ipc_handler(handler) + .with_accept_first_mouse(true); + #[cfg(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" ))] - let builder = WebViewBuilder::new(&window); - + let webview = builder.build(&window)?; #[cfg(not(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" )))] - let builder = { + let webview = { use tao::platform::unix::WindowExtUnix; use wry::WebViewBuilderExtUnix; let vbox = window.default_vbox().unwrap(); - WebViewBuilder::new_gtk(vbox) + builder.build_gtk(vbox)? }; - let mut webview = Some( - builder - .with_html(HTML) - .with_ipc_handler(handler) - .with_accept_first_mouse(true) - .build()?, - ); + let mut webview = Some(webview); event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; diff --git a/examples/gtk_multiwebview.rs b/examples/gtk_multiwebview.rs index 92dfb5ac1..b6497c764 100644 --- a/examples/gtk_multiwebview.rs +++ b/examples/gtk_multiwebview.rs @@ -16,31 +16,14 @@ fn main() -> wry::Result<()> { let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); - #[cfg(not(any( - target_os = "windows", - target_os = "macos", - target_os = "ios", - target_os = "android" - )))] - let fixed = { - use gtk::prelude::*; - use tao::platform::unix::WindowExtUnix; - - let fixed = gtk::Fixed::new(); - let vbox = window.default_vbox().unwrap(); - vbox.pack_start(&fixed, true, true, 0); - fixed.show_all(); - fixed - }; - - let create_webview_builder = || { + let build_webview = |builder: WebViewBuilder<'_>| -> wry::Result { #[cfg(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" ))] - return WebViewBuilder::new_as_child(&window); + let webview = builder.build(&window)?; #[cfg(not(any( target_os = "windows", @@ -48,42 +31,54 @@ fn main() -> wry::Result<()> { target_os = "ios", target_os = "android" )))] - { + let webview = { + use gtk::prelude::*; + use tao::platform::unix::WindowExtUnix; use wry::WebViewBuilderExtUnix; - WebViewBuilder::new_gtk(&fixed) - } + + let fixed = gtk::Fixed::new(); + let vbox = window.default_vbox().unwrap(); + vbox.pack_start(&fixed, true, true, 0); + fixed.show_all(); + builder.build_gtk(&fixed)? + }; + + Ok(webview) }; let size = window.inner_size().to_logical::(window.scale_factor()); - let webview = create_webview_builder() + let builder = WebViewBuilder::new() .with_bounds(Rect { position: LogicalPosition::new(0, 0).into(), size: LogicalSize::new(size.width / 2, size.height / 2).into(), }) - .with_url("https://tauri.app") - .build()?; - let webview2 = create_webview_builder() + .with_url("https://tauri.app"); + let webview = build_webview(builder)?; + + let builder2 = WebViewBuilder::new() .with_bounds(Rect { position: LogicalPosition::new(size.width / 2, 0).into(), size: LogicalSize::new(size.width / 2, size.height / 2).into(), }) - .with_url("https://github.com/tauri-apps/wry") - .build()?; - let webview3 = create_webview_builder() + .with_url("https://github.com/tauri-apps/wry"); + let webview2 = build_webview(builder2)?; + + let builder3 = WebViewBuilder::new() .with_bounds(Rect { position: LogicalPosition::new(0, size.height / 2).into(), size: LogicalSize::new(size.width / 2, size.height / 2).into(), }) - .with_url("https://twitter.com/TauriApps") - .build()?; - let webview4 = create_webview_builder() + .with_url("https://twitter.com/TauriApps"); + let webview3 = build_webview(builder3)?; + + let builder4 = WebViewBuilder::new() .with_bounds(Rect { position: LogicalPosition::new(size.width / 2, size.height / 2).into(), size: LogicalSize::new(size.width / 2, size.height / 2).into(), }) - .with_url("https://google.com") - .build()?; + .with_url("https://google.com"); + let webview4 = build_webview(builder4)?; event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; diff --git a/examples/multiwebview.rs b/examples/multiwebview.rs index 8380b1320..f7ed2aae9 100644 --- a/examples/multiwebview.rs +++ b/examples/multiwebview.rs @@ -44,34 +44,34 @@ fn main() -> wry::Result<()> { let size = window.inner_size().to_logical::(window.scale_factor()); - let webview = WebViewBuilder::new_as_child(&window) + let webview = WebViewBuilder::new() .with_bounds(Rect { position: LogicalPosition::new(0, 0).into(), size: LogicalSize::new(size.width / 2, size.height / 2).into(), }) .with_url("https://tauri.app") - .build()?; - let webview2 = WebViewBuilder::new_as_child(&window) + .build(&window)?; + let webview2 = WebViewBuilder::new() .with_bounds(Rect { position: LogicalPosition::new(size.width / 2, 0).into(), size: LogicalSize::new(size.width / 2, size.height / 2).into(), }) .with_url("https://github.com/tauri-apps/wry") - .build()?; - let webview3 = WebViewBuilder::new_as_child(&window) + .build(&window)?; + let webview3 = WebViewBuilder::new() .with_bounds(Rect { position: LogicalPosition::new(0, size.height / 2).into(), size: LogicalSize::new(size.width / 2, size.height / 2).into(), }) .with_url("https://twitter.com/TauriApps") - .build()?; - let webview4 = WebViewBuilder::new_as_child(&window) + .build(&window)?; + let webview4 = WebViewBuilder::new() .with_bounds(Rect { position: LogicalPosition::new(size.width / 2, size.height / 2).into(), size: LogicalSize::new(size.width / 2, size.height / 2).into(), }) .with_url("https://google.com") - .build()?; + .build(&window)?; event_loop .run(move |event, evl| { diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index 1a95c09a1..6b8a15951 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -92,37 +92,34 @@ fn create_new_window( } }; + let builder = WebViewBuilder::new() + .with_html( + r#" + + + + "#, + ) + .with_ipc_handler(handler); + #[cfg(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" ))] - let builder = WebViewBuilder::new(&window); - + let webview = builder.build(&window).unwrap(); #[cfg(not(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" )))] - let builder = { + let webview = { use tao::platform::unix::WindowExtUnix; use wry::WebViewBuilderExtUnix; let vbox = window.default_vbox().unwrap(); - WebViewBuilder::new_gtk(vbox) + builder.build_gtk(vbox).unwrap() }; - - let webview = builder - .with_html( - r#" - - - - "#, - ) - .with_ipc_handler(handler) - .build() - .unwrap(); (window, webview) } diff --git a/examples/reparent.rs b/examples/reparent.rs index 3bb2649a9..7d682a428 100644 --- a/examples/reparent.rs +++ b/examples/reparent.rs @@ -37,27 +37,27 @@ fn main() -> wry::Result<()> { let window = WindowBuilder::new().build(&event_loop).unwrap(); let window2 = WindowBuilder::new().build(&event_loop).unwrap(); + let builder = WebViewBuilder::new().with_url("https://tauri.app"); + #[cfg(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" ))] - let builder = WebViewBuilder::new(&window); - + let webview = builder.build(&window)?; #[cfg(not(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" )))] - let builder = { + let webview = { + use tao::platform::unix::WindowExtUnix; let vbox = window.default_vbox().unwrap(); - WebViewBuilder::new_gtk(vbox) + builder.build_gtk(vbox)? }; - let webview = builder.with_url("https://tauri.app").build()?; - let mut webview_container = window.id(); event_loop.run(move |event, _event_loop, control_flow| { diff --git a/examples/simple.rs b/examples/simple.rs index 5528e41b9..d5d632b24 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -13,46 +13,44 @@ fn main() -> wry::Result<()> { let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); + let builder = WebViewBuilder::new() + .with_url("http://tauri.app") + .with_drag_drop_handler(|e| { + match e { + wry::DragDropEvent::Enter { paths, position } => { + println!("DragEnter: {position:?} {paths:?} ") + } + wry::DragDropEvent::Over { position } => println!("DragOver: {position:?} "), + wry::DragDropEvent::Drop { paths, position } => { + println!("DragDrop: {position:?} {paths:?} ") + } + wry::DragDropEvent::Leave => println!("DragLeave"), + _ => {} + } + + true + }); + #[cfg(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" ))] - let builder = WebViewBuilder::new(&window); - + let _webview = builder.build(&window)?; #[cfg(not(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" )))] - let builder = { + let _webview = { use tao::platform::unix::WindowExtUnix; use wry::WebViewBuilderExtUnix; let vbox = window.default_vbox().unwrap(); - WebViewBuilder::new_gtk(vbox) + builder.build_gtk(vbox)? }; - let _webview = builder - .with_url("http://tauri.app") - .with_drag_drop_handler(|e| { - match e { - wry::DragDropEvent::Enter { paths, position } => { - println!("DragEnter: {position:?} {paths:?} ") - } - wry::DragDropEvent::Over { position } => println!("DragOver: {position:?} "), - wry::DragDropEvent::Drop { paths, position } => { - println!("DragDrop: {position:?} {paths:?} ") - } - wry::DragDropEvent::Leave => println!("DragLeave"), - _ => {} - } - - true - }) - .build()?; - event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; diff --git a/examples/streaming.rs b/examples/streaming.rs index 86a9a0dc2..3c313a8ad 100644 --- a/examples/streaming.rs +++ b/examples/streaming.rs @@ -23,52 +23,54 @@ fn main() -> wry::Result<()> { let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); + let builder = WebViewBuilder::new() + .with_custom_protocol( + "wry".into(), + move |_webview_id, request| match wry_protocol(request) { + Ok(r) => r.map(Into::into), + Err(e) => http::Response::builder() + .header(CONTENT_TYPE, "text/plain") + .status(500) + .body(e.to_string().as_bytes().to_vec()) + .unwrap() + .map(Into::into), + }, + ) + .with_custom_protocol( + "stream".into(), + move |_webview_id, request| match stream_protocol(request) { + Ok(r) => r.map(Into::into), + Err(e) => http::Response::builder() + .header(CONTENT_TYPE, "text/plain") + .status(500) + .body(e.to_string().as_bytes().to_vec()) + .unwrap() + .map(Into::into), + }, + ) + // tell the webview to load the custom protocol + .with_url("wry://localhost"); + #[cfg(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" ))] - let builder = WebViewBuilder::new(&window); - + let _webview = builder.build(&window)?; #[cfg(not(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" )))] - let builder = { + let _webview = { use tao::platform::unix::WindowExtUnix; use wry::WebViewBuilderExtUnix; let vbox = window.default_vbox().unwrap(); - WebViewBuilder::new_gtk(vbox) + builder.build_gtk(vbox)? }; - let _webview = builder - .with_custom_protocol("wry".into(), move |request| match wry_protocol(request) { - Ok(r) => r.map(Into::into), - Err(e) => http::Response::builder() - .header(CONTENT_TYPE, "text/plain") - .status(500) - .body(e.to_string().as_bytes().to_vec()) - .unwrap() - .map(Into::into), - }) - .with_custom_protocol("stream".into(), move |request| { - match stream_protocol(request) { - Ok(r) => r.map(Into::into), - Err(e) => http::Response::builder() - .header(CONTENT_TYPE, "text/plain") - .status(500) - .body(e.to_string().as_bytes().to_vec()) - .unwrap() - .map(Into::into), - } - }) - // tell the webview to load the custom protocol - .with_url("wry://localhost") - .build()?; - event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; diff --git a/examples/transparent.rs b/examples/transparent.rs index 96b26ddcb..0802012dc 100644 --- a/examples/transparent.rs +++ b/examples/transparent.rs @@ -30,44 +30,42 @@ fn main() -> wry::Result<()> { window.set_undecorated_shadow(true); } + let builder = WebViewBuilder::new() + // The second is on webview... + // Feature `transparent` is required for transparency to work. + .with_transparent(true) + // And the last is in html. + .with_html( + r#" + + + "#, + ); + #[cfg(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" ))] - let builder = WebViewBuilder::new(&window); - + let _webview = builder.build(&window)?; #[cfg(not(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" )))] - let builder = { + let _webview = { use tao::platform::unix::WindowExtUnix; use wry::WebViewBuilderExtUnix; let vbox = window.default_vbox().unwrap(); - WebViewBuilder::new_gtk(vbox) + builder.build_gtk(vbox)? }; - let _webview = builder - // The second is on webview... - // Feature `transparent` is required for transparency to work. - .with_transparent(true) - // And the last is in html. - .with_html( - r#" - - - "#, - ) - .build()?; - event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; diff --git a/examples/wgpu.rs b/examples/wgpu.rs index c452e48f5..5fe3f9aa6 100644 --- a/examples/wgpu.rs +++ b/examples/wgpu.rs @@ -101,7 +101,7 @@ fn fs_main() -> @location(0) vec4 { surface.configure(&device, &config); - let _webview = WebViewBuilder::new_as_child(&window) + let _webview = WebViewBuilder::new() .with_bounds(Rect { position: LogicalPosition::new(100, 100).into(), size: LogicalSize::new(200, 200).into(), @@ -117,7 +117,7 @@ fn fs_main() -> @location(0) vec4 { "#, ) - .build() + .build_as_child(&window) .unwrap(); event_loop diff --git a/examples/winit.rs b/examples/winit.rs index 9afd8104b..dcbdad522 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -40,9 +40,9 @@ fn main() -> wry::Result<()> { .build(&event_loop) .unwrap(); - let webview = WebViewBuilder::new_as_child(&window) + let webview = WebViewBuilder::new() .with_url("https://tauri.app") - .build()?; + .build_as_child(&window)?; event_loop .run(move |event, evl| { diff --git a/src/android/binding.rs b/src/android/binding.rs index 1a4f0af07..29d692b98 100644 --- a/src/android/binding.rs +++ b/src/android/binding.rs @@ -37,7 +37,7 @@ macro_rules! android_binding { $package, RustWebViewClient, handleRequest, - [JObject, jboolean], + [JString, JObject, jboolean], jobject ); android_fn!( @@ -100,6 +100,7 @@ macro_rules! android_binding { fn handle_request( env: &mut JNIEnv, + webview_id: JString, request: JObject, is_document_start_script_enabled: jboolean, ) -> JniResult { @@ -163,10 +164,23 @@ fn handle_request( } }; + let webview_id = if webview_id.is_null() { + None + } else { + let id = env.get_string(&webview_id)?; + id.to_str().ok().map(|id| id.to_string()) + }; + + dbg!(&webview_id); + let response = { #[cfg(feature = "tracing")] let _span = tracing::info_span!("wry::custom_protocol::call_handler").entered(); - (handler.handler)(final_request, is_document_start_script_enabled != 0) + (handler.handler)( + webview_id, + final_request, + is_document_start_script_enabled != 0, + ) }; if let Some(response) = response { let status = response.status(); @@ -254,10 +268,16 @@ fn handle_request( pub unsafe fn handleRequest( mut env: JNIEnv, _: JClass, + webview_id: JString, request: JObject, is_document_start_script_enabled: jboolean, ) -> jobject { - match handle_request(&mut env, request, is_document_start_script_enabled) { + match handle_request( + &mut env, + webview_id, + request, + is_document_start_script_enabled, + ) { Ok(response) => response, Err(e) => { #[cfg(feature = "tracing")] diff --git a/src/android/kotlin/RustWebView.kt b/src/android/kotlin/RustWebView.kt index aef4b9d94..cb1b1237b 100644 --- a/src/android/kotlin/RustWebView.kt +++ b/src/android/kotlin/RustWebView.kt @@ -14,7 +14,7 @@ import androidx.webkit.WebViewFeature import kotlin.collections.Map @SuppressLint("RestrictedApi") -class RustWebView(context: Context, val initScripts: Array): WebView(context) { +class RustWebView(context: Context, val initScripts: Array, val id: String?): WebView(context) { val isDocumentStartScriptEnabled: Boolean init { diff --git a/src/android/kotlin/RustWebViewClient.kt b/src/android/kotlin/RustWebViewClient.kt index 402feebd1..df790d9de 100644 --- a/src/android/kotlin/RustWebViewClient.kt +++ b/src/android/kotlin/RustWebViewClient.kt @@ -39,7 +39,8 @@ class RustWebViewClient(context: Context): WebViewClient() { return if (withAssetLoader()) { assetLoader.shouldInterceptRequest(request.url) } else { - val response = handleRequest(request, (view as RustWebView).isDocumentStartScriptEnabled) + val rustWebview = view as RustWebView; + val response = handleRequest(rustWebview.id, request, rustWebview.isDocumentStartScriptEnabled) interceptedState[request.url.toString()] = response != null return response } @@ -95,7 +96,7 @@ class RustWebViewClient(context: Context): WebViewClient() { private external fun assetLoaderDomain(): String private external fun withAssetLoader(): Boolean - private external fun handleRequest(request: WebResourceRequest, isDocumentStartScriptEnabled: Boolean): WebResourceResponse? + private external fun handleRequest(webviewId: String?, request: WebResourceRequest, isDocumentStartScriptEnabled: Boolean): WebResourceResponse? private external fun shouldOverride(url: String): Boolean private external fun onPageLoading(url: String) private external fun onPageLoaded(url: String) diff --git a/src/android/main_pipe.rs b/src/android/main_pipe.rs index 37f5f8708..ff0b5353f 100644 --- a/src/android/main_pipe.rs +++ b/src/android/main_pipe.rs @@ -76,6 +76,12 @@ impl<'a> MainPipe<'a> { )?; } + let id = attrs.id.clone(); + let id = match id { + Some(id) => self.env.new_string(id)?, + None => JString::default(), + }; + // Create webview let rust_webview_class = find_class( &mut self.env, @@ -84,8 +90,12 @@ impl<'a> MainPipe<'a> { )?; let webview = self.env.new_object( &rust_webview_class, - "(Landroid/content/Context;[Ljava/lang/String;)V", - &[activity.into(), (&initialization_scripts_array).into()], + "(Landroid/content/Context;[Ljava/lang/String;Ljava/lang/String;)V", + &[ + activity.into(), + (&initialization_scripts_array).into(), + (&id).into(), + ], )?; // set media autoplay @@ -262,6 +272,24 @@ impl<'a> MainPipe<'a> { Err(e) => tx.send(Err(e.into())).unwrap(), } } + WebViewMessage::GetId(tx) => { + if let Some(webview) = &self.webview { + let url = self + .env + .call_method(webview.as_obj(), "getUrl", "()Ljava/lang/String;", &[]) + .and_then(|v| v.l()) + .and_then(|s| { + let s = JString::from(s); + self + .env + .get_string(&s) + .map(|v| v.to_string_lossy().to_string()) + }) + .unwrap_or_default(); + + tx.send(url).unwrap() + } + } WebViewMessage::GetUrl(tx) => { if let Some(webview) = &self.webview { let url = self @@ -372,6 +400,7 @@ pub(crate) enum WebViewMessage { Eval(String, Option), SetBackgroundColor(RGBA), GetWebViewVersion(Sender>), + GetId(Sender), GetUrl(Sender), Jni(Box), LoadUrl(String, Option), @@ -380,6 +409,7 @@ pub(crate) enum WebViewMessage { } pub(crate) struct CreateWebViewAttributes { + pub id: Option, pub url: Option, pub html: Option, #[cfg(any(debug_assertions, feature = "devtools"))] diff --git a/src/android/mod.rs b/src/android/mod.rs index 273117df8..2d3950044 100644 --- a/src/android/mod.rs +++ b/src/android/mod.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use super::{PageLoadEvent, WebContext, WebViewAttributes, RGBA}; +use super::{PageLoadEvent, WebViewAttributes, RGBA}; use crate::{RequestAsyncResponder, Result}; use base64::{engine::general_purpose, Engine}; use crossbeam_channel::*; @@ -58,7 +58,7 @@ macro_rules! define_static_handlers { define_static_handlers! { IPC = UnsafeIpc { handler: Box)> }; - REQUEST_HANDLER = UnsafeRequestHandler { handler: Box>, bool) -> Option>>> }; + REQUEST_HANDLER = UnsafeRequestHandler { handler: Box, Request>, bool) -> Option>>> }; TITLE_CHANGE_HANDLER = UnsafeTitleHandler { handler: Box }; URL_LOADING_OVERRIDE = UnsafeUrlLoadingOverride { handler: Box bool> }; ON_LOAD_HANDLER = UnsafeOnPageLoadHandler { handler: Box }; @@ -124,23 +124,23 @@ pub unsafe fn android_setup( .unwrap(); } -pub(crate) struct InnerWebView; +pub(crate) struct InnerWebView { + id: Option, +} impl InnerWebView { pub fn new_as_child( _window: &impl HasWindowHandle, attributes: WebViewAttributes, pl_attrs: super::PlatformSpecificWebViewAttributes, - _web_context: Option<&mut WebContext>, ) -> Result { - Self::new(_window, attributes, pl_attrs, _web_context) + Self::new(_window, attributes, pl_attrs) } pub fn new( _window: &impl HasWindowHandle, attributes: WebViewAttributes, pl_attrs: super::PlatformSpecificWebViewAttributes, - _web_context: Option<&mut WebContext>, ) -> Result { let WebViewAttributes { url, @@ -182,6 +182,7 @@ impl InnerWebView { }; MainPipe::send(WebViewMessage::CreateWebView(CreateWebViewAttributes { + id: attributes.id.map(|id| id.to_string()), url, html, #[cfg(any(debug_assertions, feature = "devtools"))] @@ -202,7 +203,7 @@ impl InnerWebView { REQUEST_HANDLER.get_or_init(move || { UnsafeRequestHandler::new(Box::new( - move |mut request, is_document_start_script_enabled| { + move |webview_id: Option, mut request, is_document_start_script_enabled| { let uri = request.uri().to_string(); if let Some(custom_protocol) = custom_protocols.iter().find(|(name, _)| { uri.starts_with(&format!("{scheme}://{}.", name)) @@ -276,7 +277,7 @@ impl InnerWebView { tx.send(response).unwrap(); }); - (custom_protocol.1)(request, RequestAsyncResponder { responder }); + (custom_protocol.1)(webview_id.as_deref(), request, RequestAsyncResponder { responder }); return Some(rx.recv().unwrap()); } None @@ -300,13 +301,19 @@ impl InnerWebView { ON_LOAD_HANDLER.get_or_init(move || UnsafeOnPageLoadHandler::new(h)); } - Ok(Self) + Ok(Self { + id: attributes.id.map(|id| id.to_string()), + }) } pub fn print(&self) -> crate::Result<()> { Ok(()) } + pub fn id(&self) -> Option { + self.id.as_deref() + } + pub fn url(&self) -> crate::Result { let (tx, rx) = bounded(1); MainPipe::send(WebViewMessage::GetUrl(tx)); diff --git a/src/error.rs b/src/error.rs index 7e3946b14..e74fb283d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -59,4 +59,8 @@ pub enum Error { CustomProtocolTaskInvalid, #[error("Failed to register URL scheme: {0}, could be due to invalid URL scheme or the scheme is already registered.")] UrlSchemeRegisterError(String), + #[error("Duplicate custom protocol registered on Linux: {0}")] + DuplicateCustomProtocol(String), + #[error("Duplicate custom protocol registered on the same web context on Linux: {0}")] + ContextDuplicateCustomProtocol(String), } diff --git a/src/lib.rs b/src/lib.rs index d6fdb94f0..968457440 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,9 +19,9 @@ //! let event_loop = EventLoop::new().unwrap(); //! let window = WindowBuilder::new().build(&event_loop).unwrap(); //! -//! let webview = WebViewBuilder::new(&window) +//! let webview = WebViewBuilder::new() //! .with_url("https://tauri.app") -//! .build() +//! .build(&window) //! .unwrap(); //! ``` //! @@ -38,15 +38,12 @@ //! let event_loop = EventLoop::new(); //! let window = WindowBuilder::new().build(&event_loop).unwrap(); //! +//! let builder = WebViewBuilder::new().with_url("https://tauri.app"); +//! //! #[cfg(not(target_os = "linux"))] -//! let builder = WebViewBuilder::new(&window); +//! let webview = builder.build(&window).unwrap(); //! #[cfg(target_os = "linux")] -//! let builder = WebViewBuilder::new_gtk(window.gtk_window()); -//! -//! let webview = builder -//! .with_url("https://tauri.app") -//! .build() -//! .unwrap(); +//! let webview = builder.build_gtk(window.gtk_window()).unwrap(); //! ``` //! //! ## Child webviews @@ -60,13 +57,13 @@ //! let event_loop = EventLoop::new().unwrap(); //! let window = WindowBuilder::new().build(&event_loop).unwrap(); //! -//! let webview = WebViewBuilder::new_as_child(&window) +//! let webview = WebViewBuilder::new() //! .with_url("https://tauri.app") //! .with_bounds(Rect { //! position: LogicalPosition::new(100, 100).into(), //! size: LogicalSize::new(200, 200).into(), //! }) -//! .build() +//! .build_as_child(&window) //! .unwrap(); //! ``` //! @@ -83,29 +80,24 @@ //! let event_loop = EventLoop::new(); //! let window = WindowBuilder::new().build(&event_loop).unwrap(); //! -//! #[cfg(not(target_os = "linux"))] -//! let builder = WebViewBuilder::new_as_child(&window); +//! let builder = WebViewBuilder::new() +//! .with_url("https://tauri.app") +//! .with_bounds(Rect { +//! position: LogicalPosition::new(100, 100).into(), +//! size: LogicalSize::new(200, 200).into(), +//! }); //! +//! #[cfg(not(target_os = "linux"))] +//! let webview = builder.build_as_child(&window).unwrap(); //! #[cfg(target_os = "linux")] -//! let gtk_fixed = { -//! # use gtk::prelude::BoxExt; +//! let webview = { +//! # use gtk::prelude::*; //! let vbox = window.default_vbox().unwrap(); // tao adds a gtk::Box by default //! let fixed = gtk::Fixed::new(); +//! fixed.show_all(); //! vbox.pack_start(&fixed, true, true, 0); -//! fixed +//! builder.build_gtk(&fixed).unwrap() //! }; -//! -//! #[cfg(target_os = "linux")] -//! let builder = WebViewBuilder::new_gtk(>k_fixed); -//! -//! let webview = builder -//! .with_url("https://tauri.app") -//! .with_bounds(Rect { -//! position: LogicalPosition::new(100, 100).into(), -//! size: LogicalSize::new(200, 200).into(), -//! }) -//! .build() -//! .unwrap(); //! ``` //! //! ## Platform Considerations @@ -292,7 +284,16 @@ impl RequestAsyncResponder { } } -pub struct WebViewAttributes { +/// An id for a webview +pub type WebViewId<'a> = &'a str; + +pub struct WebViewAttributes<'a> { + /// An id that will be passed when this webview makes requests in certain callbacks. + pub id: Option>, + + /// Web context to be shared with this webview. + pub context: Option<&'a mut WebContext>, + /// Whether the WebView should have a custom user-agent. pub user_agent: Option, @@ -360,7 +361,11 @@ pub struct WebViewAttributes { /// A list of custom loading protocols with pairs of scheme uri string and a handling /// closure. /// - /// The closure takes a [Request] and returns a [Response]. + /// The closure takes an Id ([WebViewId]), [Request] and [RequestAsyncResponder] as arguments and returns a [Response]. + /// + /// # Note + /// + /// If using a shared [WebContext], make sure custom protocols were not already registered on that web context on Linux. /// /// # Warning /// @@ -377,7 +382,8 @@ pub struct WebViewAttributes { /// - Android: Android has `assets` and `resource` path finder to /// locate your files in those directories. For more information, see [Loading in-app content](https://developer.android.com/guide/webapps/load-local-content) page. /// - iOS: To get the path of your assets, you can call [`CFBundle::resources_path`](https://docs.rs/core-foundation/latest/core_foundation/bundle/struct.CFBundle.html#method.resources_path). So url like `wry://assets/index.html` could get the html file in assets directory. - pub custom_protocols: HashMap>, RequestAsyncResponder)>>, + pub custom_protocols: + HashMap, Request>, RequestAsyncResponder)>>, /// The IPC handler to receive the message from Javascript on webview /// using `window.ipc.postMessage("insert_message_here")` to host Rust code. @@ -505,9 +511,11 @@ pub struct WebViewAttributes { pub bounds: Option, } -impl Default for WebViewAttributes { +impl<'a> Default for WebViewAttributes<'a> { fn default() -> Self { Self { + id: Default::default(), + context: None, user_agent: None, visible: true, transparent: false, @@ -545,94 +553,84 @@ impl Default for WebViewAttributes { } } +struct WebviewBuilderParts<'a> { + attrs: WebViewAttributes<'a>, + platform_specific: PlatformSpecificWebViewAttributes, +} + /// Builder type of [`WebView`]. /// /// [`WebViewBuilder`] / [`WebView`] are the basic building blocks to construct WebView contents and /// scripts for those who prefer to control fine grained window creation and event handling. /// [`WebViewBuilder`] provides ability to setup initialization before web engine starts. pub struct WebViewBuilder<'a> { - pub attrs: WebViewAttributes, - as_child: bool, - window: Option<&'a dyn HasWindowHandle>, - platform_specific: PlatformSpecificWebViewAttributes, - web_context: Option<&'a mut WebContext>, - #[cfg(gtk)] - gtk_widget: Option<&'a gtk::Container>, + inner: Result>, } impl<'a> WebViewBuilder<'a> { - /// Create a [`WebViewBuilder`] from a type that implements [`HasWindowHandle`]. - /// - /// # Platform-specific: - /// - /// - **Linux**: Only X11 is supported, if you want to support Wayland too, use [`WebViewBuilderExtUnix::new_gtk`]. - /// - /// Although this methods only needs an X11 window handle, we use webkit2gtk, so you still need to initialize gtk - /// by callling [`gtk::init`] and advance its loop alongside your event loop using [`gtk::main_iteration_do`]. - /// Checkout the [Platform Considerations](https://docs.rs/wry/latest/wry/#platform-considerations) section in the crate root documentation. - /// - **Windows**: The webview will auto-resize when the passed handle is resized. - /// - **Linux (X11)**: Unlike macOS and Windows, the webview will not auto-resize and you'll need to call [`WebView::set_bounds`] manually. - /// - /// # Panics: - /// - /// - Panics if the provided handle was not supported or invalid. - /// - Panics on Linux, if [`gtk::init`] was not called in this thread. - pub fn new(window: &'a impl HasWindowHandle) -> Self { + /// Create a new [`WebViewBuilder`]. + pub fn new() -> Self { Self { - attrs: WebViewAttributes::default(), - window: Some(window), - as_child: false, - #[allow(clippy::default_constructed_unit_structs)] - platform_specific: PlatformSpecificWebViewAttributes::default(), - web_context: None, - #[cfg(gtk)] - gtk_widget: None, + inner: Ok(WebviewBuilderParts { + attrs: WebViewAttributes::default(), + #[allow(clippy::default_constructed_unit_structs)] + platform_specific: PlatformSpecificWebViewAttributes::default(), + }), } } - /// Create [`WebViewBuilder`] as a child window inside the provided [`HasWindowHandle`]. - /// - /// ## Platform-specific - /// - /// - **Windows**: This will create the webview as a child window of the `parent` window. - /// - **macOS**: This will create the webview as a `NSView` subview of the `parent` window's - /// content view. - /// - **Linux**: This will create the webview as a child window of the `parent` window. Only X11 - /// is supported. This method won't work on Wayland. - /// - /// Although this methods only needs an X11 window handle, you use webkit2gtk, so you still need to initialize gtk - /// by callling [`gtk::init`] and advance its loop alongside your event loop using [`gtk::main_iteration_do`]. - /// Checkout the [Platform Considerations](https://docs.rs/wry/latest/wry/#platform-considerations) section in the crate root documentation. - /// - /// If you want to support child webviews on X11 and Wayland at the same time, - /// we recommend using [`WebViewBuilderExtUnix::new_gtk`] with [`gtk::Fixed`]. - /// - **Android/iOS:** Unsupported. - /// - /// # Panics: - /// - /// - Panics if the provided handle was not support or invalid. - /// - Panics on Linux, if [`gtk::init`] was not called in this thread. - pub fn new_as_child(parent: &'a impl HasWindowHandle) -> Self { + /// Create a new [`WebViewBuilder`] with a web context that can be shared with multiple [`WebView`]s. + pub fn with_web_context(web_context: &'a mut WebContext) -> Self { + let mut attrs = WebViewAttributes::default(); + attrs.context = Some(web_context); + Self { - attrs: WebViewAttributes::default(), - window: Some(parent), - as_child: true, - #[allow(clippy::default_constructed_unit_structs)] - platform_specific: PlatformSpecificWebViewAttributes::default(), - web_context: None, - #[cfg(gtk)] - gtk_widget: None, + inner: Ok(WebviewBuilderParts { + attrs, + #[allow(clippy::default_constructed_unit_structs)] + platform_specific: PlatformSpecificWebViewAttributes::default(), + }), } } + /// Create a new [`WebViewBuilder`] with the given [`WebViewAttributes`] + pub fn with_attributes(attrs: WebViewAttributes<'a>) -> Self { + Self { + inner: Ok(WebviewBuilderParts { + attrs, + #[allow(clippy::default_constructed_unit_structs)] + platform_specific: PlatformSpecificWebViewAttributes::default(), + }), + } + } + + fn and_then(self, func: F) -> Self + where + F: FnOnce(WebviewBuilderParts<'a>) -> Result>, + { + Self { + inner: self.inner.and_then(func), + } + } + + /// Set an id that will be passed when this webview makes requests in certain callbacks. + pub fn with_id(self, id: WebViewId<'a>) -> Self { + self.and_then(|mut b| { + b.attrs.id = Some(id); + Ok(b) + }) + } + /// Indicates whether horizontal swipe gestures trigger backward and forward page navigation. /// /// ## Platform-specific: /// /// - **Android / iOS:** Unsupported. - pub fn with_back_forward_navigation_gestures(mut self, gesture: bool) -> Self { - self.attrs.back_forward_navigation_gestures = gesture; - self + pub fn with_back_forward_navigation_gestures(self, gesture: bool) -> Self { + self.and_then(|mut b| { + b.attrs.back_forward_navigation_gestures = gesture; + Ok(b) + }) } /// Sets whether the WebView should be transparent. @@ -640,9 +638,11 @@ impl<'a> WebViewBuilder<'a> { /// ## Platform-specific: /// /// **Windows 7**: Not supported. - pub fn with_transparent(mut self, transparent: bool) -> Self { - self.attrs.transparent = transparent; - self + pub fn with_transparent(self, transparent: bool) -> Self { + self.and_then(|mut b| { + b.attrs.transparent = transparent; + Ok(b) + }) } /// Specify the webview background color. This will be ignored if `transparent` is set to `true`. @@ -655,21 +655,27 @@ impl<'a> WebViewBuilder<'a> { /// - **Windows**: /// - on Windows 7, transparency is not supported and the alpha value will be ignored. /// - on Windows higher than 7: translucent colors are not supported so any alpha value other than `0` will be replaced by `255` - pub fn with_background_color(mut self, background_color: RGBA) -> Self { - self.attrs.background_color = Some(background_color); - self + pub fn with_background_color(self, background_color: RGBA) -> Self { + self.and_then(|mut b| { + b.attrs.background_color = Some(background_color); + Ok(b) + }) } /// Sets whether the WebView should be visible or not. - pub fn with_visible(mut self, visible: bool) -> Self { - self.attrs.visible = visible; - self + pub fn with_visible(self, visible: bool) -> Self { + self.and_then(|mut b| { + b.attrs.visible = visible; + Ok(b) + }) } /// Sets whether all media can be played without user interaction. - pub fn with_autoplay(mut self, autoplay: bool) -> Self { - self.attrs.autoplay = autoplay; - self + pub fn with_autoplay(self, autoplay: bool) -> Self { + self.and_then(|mut b| { + b.attrs.autoplay = autoplay; + Ok(b) + }) } /// Initialize javascript code when loading new pages. When webview load a new page, this @@ -684,11 +690,13 @@ impl<'a> WebViewBuilder<'a> { /// /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E) /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap) - pub fn with_initialization_script(mut self, js: &str) -> Self { - if !js.is_empty() { - self.attrs.initialization_scripts.push(js.to_string()); - } - self + pub fn with_initialization_script(self, js: &str) -> Self { + self.and_then(|mut b| { + if !js.is_empty() { + b.attrs.initialization_scripts.push(js.to_string()); + } + Ok(b) + }) } /// Register custom loading protocols with pairs of scheme uri string and a handling @@ -717,18 +725,36 @@ impl<'a> WebViewBuilder<'a> { /// folder which lives within the apk. For the cases where this can be used, it works the same as in macOS and Linux. /// - iOS: To get the path of your assets, you can call [`CFBundle::resources_path`](https://docs.rs/core-foundation/latest/core_foundation/bundle/struct.CFBundle.html#method.resources_path). So url like `wry://assets/index.html` could get the html file in assets directory. #[cfg(feature = "protocol")] - pub fn with_custom_protocol(mut self, name: String, handler: F) -> Self + pub fn with_custom_protocol(self, name: String, handler: F) -> Self where - F: Fn(Request>) -> Response> + 'static, + F: Fn(Option, Request>) -> Response> + 'static, { - self.attrs.custom_protocols.insert( - name, - Box::new(move |request, responder| { - let http_response = handler(request); - responder.respond(http_response); - }), - ); - self + self.and_then(|mut b| { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + ))] + if let Some(context) = &mut b.attrs.context { + context.register_custom_protocol(name.clone())?; + } + + if b.attrs.custom_protocols.iter().any(|(n, _)| n == &name) { + return Err(Error::DuplicateCustomProtocol(name)); + } + + b.attrs.custom_protocols.insert( + name, + Box::new(move |id, request, responder| { + let http_response = handler(id, request); + responder.respond(http_response); + }), + ); + + Ok(b) + }) } /// Same as [`Self::with_custom_protocol`] but with an asynchronous responder. @@ -739,17 +765,7 @@ impl<'a> WebViewBuilder<'a> { /// /// ```no_run /// use wry::{WebViewBuilder, raw_window_handle}; - /// - /// # use raw_window_handle::{HasWindowHandle, WindowHandle, RawWindowHandle, Win32WindowHandle, HandleError}; - /// # struct T; - /// # impl HasWindowHandle for T { - /// # fn window_handle(&self) -> Result, HandleError> { - /// # let handle = RawWindowHandle::Win32(Win32WindowHandle::new(std::num::NonZeroIsize::new(0).unwrap())); - /// # unsafe { Ok(WindowHandle::borrow_raw(handle)) } - /// # } - /// # } - /// # let window = T; - /// WebViewBuilder::new(&window) + /// WebViewBuilder::new() /// .with_asynchronous_custom_protocol("wry".into(), |request, responder| { /// // here you can use a tokio task, thread pool or anything /// // to do heavy computation to resolve your request @@ -761,12 +777,30 @@ impl<'a> WebViewBuilder<'a> { /// }); /// ``` #[cfg(feature = "protocol")] - pub fn with_asynchronous_custom_protocol(mut self, name: String, handler: F) -> Self + pub fn with_asynchronous_custom_protocol(self, name: String, handler: F) -> Self where - F: Fn(Request>, RequestAsyncResponder) + 'static, + F: Fn(Option, Request>, RequestAsyncResponder) + 'static, { - self.attrs.custom_protocols.insert(name, Box::new(handler)); - self + self.and_then(|mut b| { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + ))] + if let Some(context) = &mut b.attrs.context { + context.register_custom_protocol(name.clone())?; + } + + if b.attrs.custom_protocols.iter().any(|(n, _)| n == &name) { + return Err(Error::DuplicateCustomProtocol(name)); + } + + b.attrs.custom_protocols.insert(name, Box::new(handler)); + + Ok(b) + }) } /// Set the IPC handler to receive the message from Javascript on webview @@ -775,12 +809,14 @@ impl<'a> WebViewBuilder<'a> { /// ## Platform-specific /// /// - **Linux / Android**: The request URL is not supported on iframes and the main frame URL is used instead. - pub fn with_ipc_handler(mut self, handler: F) -> Self + pub fn with_ipc_handler(self, handler: F) -> Self where F: Fn(Request) + 'static, { - self.attrs.ipc_handler = Some(Box::new(handler)); - self + self.and_then(|mut b| { + b.attrs.ipc_handler = Some(Box::new(handler)); + Ok(b) + }) } /// Set a handler closure to process incoming [`DragDropEvent`] of the webview. @@ -792,12 +828,14 @@ impl<'a> WebViewBuilder<'a> { /// Also note, that it's not possible to manually set the value of a `` via JavaScript for security reasons. #[cfg(feature = "drag-drop")] #[cfg_attr(docsrs, doc(cfg(feature = "drag-drop")))] - pub fn with_drag_drop_handler(mut self, handler: F) -> Self + pub fn with_drag_drop_handler(self, handler: F) -> Self where F: Fn(DragDropEvent) -> bool + 'static, { - self.attrs.drag_drop_handler = Some(Box::new(handler)); - self + self.and_then(|mut b| { + b.attrs.drag_drop_handler = Some(Box::new(handler)); + Ok(b) + }) } /// Load the provided URL with given headers when the builder calling [`WebViewBuilder::build`] to create the [`WebView`]. @@ -806,10 +844,12 @@ impl<'a> WebViewBuilder<'a> { /// ## Note /// /// Data URLs are not supported, use [`html`](Self::with_html) option instead. - pub fn with_url_and_headers(mut self, url: impl Into, headers: http::HeaderMap) -> Self { - self.attrs.url = Some(url.into()); - self.attrs.headers = Some(headers); - self + pub fn with_url_and_headers(self, url: impl Into, headers: http::HeaderMap) -> Self { + self.and_then(|mut b| { + b.attrs.url = Some(url.into()); + b.attrs.headers = Some(headers); + Ok(b) + }) } /// Load the provided URL when the builder calling [`WebViewBuilder::build`] to create the [`WebView`]. @@ -818,16 +858,20 @@ impl<'a> WebViewBuilder<'a> { /// ## Note /// /// Data URLs are not supported, use [`html`](Self::with_html) option instead. - pub fn with_url(mut self, url: impl Into) -> Self { - self.attrs.url = Some(url.into()); - self.attrs.headers = None; - self + pub fn with_url(self, url: impl Into) -> Self { + self.and_then(|mut b| { + b.attrs.url = Some(url.into()); + b.attrs.headers = None; + Ok(b) + }) } /// Set headers used when loading the requested [`url`](Self::with_url). - pub fn with_headers(mut self, headers: http::HeaderMap) -> Self { - self.attrs.headers = Some(headers); - self + pub fn with_headers(self, headers: http::HeaderMap) -> Self { + self.and_then(|mut b| { + b.attrs.headers = Some(headers); + Ok(b) + }) } /// Load the provided HTML string when the builder calling [`WebViewBuilder::build`] to create the [`WebView`]. @@ -840,15 +884,11 @@ impl<'a> WebViewBuilder<'a> { /// ## PLatform-specific: /// /// - **Windows:** the string can not be larger than 2 MB (2 * 1024 * 1024 bytes) in total size - pub fn with_html(mut self, html: impl Into) -> Self { - self.attrs.html = Some(html.into()); - self - } - - /// Set the web context that can be shared with multiple [`WebView`]s. - pub fn with_web_context(mut self, web_context: &'a mut WebContext) -> Self { - self.web_context = Some(web_context); - self + pub fn with_html(self, html: impl Into) -> Self { + self.and_then(|mut b| { + b.attrs.html = Some(html.into()); + Ok(b) + }) } /// Set a custom [user-agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) for the WebView. @@ -857,9 +897,11 @@ impl<'a> WebViewBuilder<'a> { /// /// - Windows: Requires WebView2 Runtime version 86.0.616.0 or higher, does nothing on older versions, /// see https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/archive?tabs=dotnetcsharp#10790-prerelease - pub fn with_user_agent(mut self, user_agent: &str) -> Self { - self.attrs.user_agent = Some(user_agent.to_string()); - self + pub fn with_user_agent(self, user_agent: impl Into) -> Self { + self.and_then(|mut b| { + b.attrs.html = Some(user_agent.into()); + Ok(b) + }) } /// Enable or disable web inspector which is usually called devtools. @@ -873,9 +915,11 @@ impl<'a> WebViewBuilder<'a> { /// but requires `devtools` feature flag to actually enable it in **release** builds. /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android. /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window. - pub fn with_devtools(mut self, devtools: bool) -> Self { - self.attrs.devtools = devtools; - self + pub fn with_devtools(self, devtools: bool) -> Self { + self.and_then(|mut b| { + b.attrs.devtools = devtools; + Ok(b) + }) } /// Whether page zooming by hotkeys or gestures is enabled @@ -886,18 +930,22 @@ impl<'a> WebViewBuilder<'a> { /// see https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/archive?tabs=dotnetcsharp#10865-prerelease /// /// - **macOS / Linux / Android / iOS**: Unsupported - pub fn with_hotkeys_zoom(mut self, zoom: bool) -> Self { - self.attrs.zoom_hotkeys_enabled = zoom; - self + pub fn with_hotkeys_zoom(self, zoom: bool) -> Self { + self.and_then(|mut b| { + b.attrs.zoom_hotkeys_enabled = zoom; + Ok(b) + }) } /// Set a navigation handler to decide if incoming url is allowed to navigate. /// /// The closure take a `String` parameter as url and returns a `bool` to determine whether the navigation should happen. /// `true` allows to navigate and `false` does not. - pub fn with_navigation_handler(mut self, callback: impl Fn(String) -> bool + 'static) -> Self { - self.attrs.navigation_handler = Some(Box::new(callback)); - self + pub fn with_navigation_handler(self, callback: impl Fn(String) -> bool + 'static) -> Self { + self.and_then(|mut b| { + b.attrs.navigation_handler = Some(Box::new(callback)); + Ok(b) + }) } /// Set a download started handler to manage incoming downloads. @@ -907,11 +955,13 @@ impl<'a> WebViewBuilder<'a> { /// parameter can be used to set the download location by assigning a new path to it, the assigned path _must_ be /// absolute. The closure returns a `bool` to allow or deny the download. pub fn with_download_started_handler( - mut self, - started_handler: impl FnMut(String, &mut PathBuf) -> bool + 'static, + self, + download_started_handler: impl FnMut(String, &mut PathBuf) -> bool + 'static, ) -> Self { - self.attrs.download_started_handler = Some(Box::new(started_handler)); - self + self.and_then(|mut b| { + b.attrs.download_started_handler = Some(Box::new(download_started_handler)); + Ok(b) + }) } /// Sets a download completion handler to manage downloads that have finished. @@ -928,32 +978,35 @@ impl<'a> WebViewBuilder<'a> { /// - **macOS**: The second parameter indicating the path the file was saved to, is always empty, /// due to API limitations. pub fn with_download_completed_handler( - mut self, + self, download_completed_handler: impl Fn(String, Option, bool) + 'static, ) -> Self { - self.attrs.download_completed_handler = Some(Rc::new(download_completed_handler)); - self + self.and_then(|mut b| { + b.attrs.download_completed_handler = Some(Rc::new(download_completed_handler)); + Ok(b) + }) } /// Enables clipboard access for the page rendered on **Linux** and **Windows**. /// /// macOS doesn't provide such method and is always enabled by default. But your app will still need to add menu /// item accelerators to use the clipboard shortcuts. - pub fn with_clipboard(mut self, clipboard: bool) -> Self { - self.attrs.clipboard = clipboard; - self + pub fn with_clipboard(self, clipboard: bool) -> Self { + self.and_then(|mut b| { + b.attrs.clipboard = clipboard; + Ok(b) + }) } /// Set a new window request handler to decide if incoming url is allowed to be opened. /// /// The closure take a `String` parameter as url and return `bool` to determine whether the window should open. /// `true` allows to open and `false` does not. - pub fn with_new_window_req_handler( - mut self, - callback: impl Fn(String) -> bool + 'static, - ) -> Self { - self.attrs.new_window_req_handler = Some(Box::new(callback)); - self + pub fn with_new_window_req_handler(self, callback: impl Fn(String) -> bool + 'static) -> Self { + self.and_then(|mut b| { + b.attrs.new_window_req_handler = Some(Box::new(callback)); + Ok(b) + }) } /// Sets whether clicking an inactive window also clicks through to the webview. Default is `false`. @@ -961,18 +1014,19 @@ impl<'a> WebViewBuilder<'a> { /// ## Platform-specific /// /// This configuration only impacts macOS. - pub fn with_accept_first_mouse(mut self, accept_first_mouse: bool) -> Self { - self.attrs.accept_first_mouse = accept_first_mouse; - self + pub fn with_accept_first_mouse(self, accept_first_mouse: bool) -> Self { + self.and_then(|mut b| { + b.attrs.accept_first_mouse = accept_first_mouse; + Ok(b) + }) } /// Set a handler closure to process the change of the webview's document title. - pub fn with_document_title_changed_handler( - mut self, - callback: impl Fn(String) + 'static, - ) -> Self { - self.attrs.document_title_changed_handler = Some(Box::new(callback)); - self + pub fn with_document_title_changed_handler(self, callback: impl Fn(String) + 'static) -> Self { + self.and_then(|mut b| { + b.attrs.document_title_changed_handler = Some(Box::new(callback)); + Ok(b) + }) } /// Run the WebView with incognito mode. Note that WebContext will be ingored if incognito is @@ -983,18 +1037,22 @@ impl<'a> WebViewBuilder<'a> { /// - Windows: Requires WebView2 Runtime version 101.0.1210.39 or higher, does nothing on older versions, /// see https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/archive?tabs=dotnetcsharp#10121039 /// - **Android:** Unsupported yet. - pub fn with_incognito(mut self, incognito: bool) -> Self { - self.attrs.incognito = incognito; - self + pub fn with_incognito(self, incognito: bool) -> Self { + self.and_then(|mut b| { + b.attrs.incognito = incognito; + Ok(b) + }) } /// Set a handler to process page loading events. pub fn with_on_page_load_handler( - mut self, + self, handler: impl Fn(PageLoadEvent, String) + 'static, ) -> Self { - self.attrs.on_page_load_handler = Some(Box::new(handler)); - self + self.and_then(|mut b| { + b.attrs.on_page_load_handler = Some(Box::new(handler)); + Ok(b) + }) } /// Set a proxy configuration for the webview. @@ -1002,9 +1060,11 @@ impl<'a> WebViewBuilder<'a> { /// - **macOS**: Requires macOS 14.0+ and the `mac-proxy` feature flag to be enabled. Supports HTTP CONNECT and SOCKSv5 proxies. /// - **Windows / Linux**: Supports HTTP CONNECT and SOCKSv5 proxies. /// - **Android / iOS:** Not supported. - pub fn with_proxy_config(mut self, configuration: ProxyConfig) -> Self { - self.attrs.proxy_config = Some(configuration); - self + pub fn with_proxy_config(self, configuration: ProxyConfig) -> Self { + self.and_then(|mut b| { + b.attrs.proxy_config = Some(configuration); + Ok(b) + }) } /// Set whether the webview should be focused when created. @@ -1012,46 +1072,74 @@ impl<'a> WebViewBuilder<'a> { /// ## Platform-specific: /// /// - **macOS / Android / iOS:** Unsupported. - pub fn with_focused(mut self, focused: bool) -> Self { - self.attrs.focused = focused; - self + pub fn with_focused(self, focused: bool) -> Self { + self.and_then(|mut b| { + b.attrs.focused = focused; + Ok(b) + }) } /// Specify the webview position relative to its parent if it will be created as a child /// or if created using [`WebViewBuilderExtUnix::new_gtk`] with [`gtk::Fixed`]. /// /// Defaults to `x: 0, y: 0, width: 200, height: 200`. - pub fn with_bounds(mut self, bounds: Rect) -> Self { - self.attrs.bounds.replace(bounds); - self + pub fn with_bounds(self, bounds: Rect) -> Self { + self.and_then(|mut b| { + b.attrs.bounds = Some(bounds); + Ok(b) + }) + } + + /// Consume the builder and create the [`WebView`] from a type that implements [`HasWindowHandle`]. + /// + /// # Platform-specific: + /// + /// - **Linux**: Only X11 is supported, if you want to support Wayland too, use [`WebViewBuilderExtUnix::new_gtk`]. + /// + /// Although this methods only needs an X11 window handle, we use webkit2gtk, so you still need to initialize gtk + /// by callling [`gtk::init`] and advance its loop alongside your event loop using [`gtk::main_iteration_do`]. + /// Checkout the [Platform Considerations](https://docs.rs/wry/latest/wry/#platform-considerations) section in the crate root documentation. + /// - **Windows**: The webview will auto-resize when the passed handle is resized. + /// - **Linux (X11)**: Unlike macOS and Windows, the webview will not auto-resize and you'll need to call [`WebView::set_bounds`] manually. + /// + /// # Panics: + /// + /// - Panics if the provided handle was not supported or invalid. + /// - Panics on Linux, if [`gtk::init`] was not called in this thread. + pub fn build(self, window: &'a W) -> Result { + let parts = self.inner?; + + InnerWebView::new(window, parts.attrs, parts.platform_specific) + .map(|webview| WebView { webview }) } - /// Consume the builder and create the [`WebView`]. + /// Consume the builder and create the [`WebView`] as a child window inside the provided [`HasWindowHandle`]. + /// + /// ## Platform-specific + /// + /// - **Windows**: This will create the webview as a child window of the `parent` window. + /// - **macOS**: This will create the webview as a `NSView` subview of the `parent` window's + /// content view. + /// - **Linux**: This will create the webview as a child window of the `parent` window. Only X11 + /// is supported. This method won't work on Wayland. + /// + /// Although this methods only needs an X11 window handle, you use webkit2gtk, so you still need to initialize gtk + /// by callling [`gtk::init`] and advance its loop alongside your event loop using [`gtk::main_iteration_do`]. + /// Checkout the [Platform Considerations](https://docs.rs/wry/latest/wry/#platform-considerations) section in the crate root documentation. + /// + /// If you want to support child webviews on X11 and Wayland at the same time, + /// we recommend using [`WebViewBuilderExtUnix::new_gtk`] with [`gtk::Fixed`]. + /// - **Android/iOS:** Unsupported. /// /// # Panics: /// /// - Panics if the provided handle was not support or invalid. /// - Panics on Linux, if [`gtk::init`] was not called in this thread. - pub fn build(self) -> Result { - let webview = if let Some(window) = &self.window { - if self.as_child { - InnerWebView::new_as_child(window, self.attrs, self.platform_specific, self.web_context)? - } else { - InnerWebView::new(window, self.attrs, self.platform_specific, self.web_context)? - } - } else { - #[cfg(gtk)] - if let Some(widget) = self.gtk_widget { - InnerWebView::new_gtk(widget, self.attrs, self.platform_specific, self.web_context)? - } else { - unreachable!() - } + pub fn build_as_child(self, window: &'a W) -> Result { + let parts = self.inner?; - #[cfg(not(gtk))] - unreachable!() - }; - - Ok(WebView { webview }) + InnerWebView::new_as_child(window, parts.attrs, parts.platform_specific) + .map(|webview| WebView { webview }) } } @@ -1082,8 +1170,10 @@ pub trait WebViewBuilderExtDarwin { #[cfg(any(target_os = "macos", target_os = "ios",))] impl WebViewBuilderExtDarwin for WebViewBuilder<'_> { fn with_data_store_identifier(mut self, identifier: [u8; 16]) -> Self { - self.platform_specific.data_store_identifier = Some(identifier); - self + self.and_then(|mut b| { + b.platform_specific.data_store_identifier = Some(identifier); + Ok(b) + }) } } @@ -1171,34 +1261,46 @@ pub trait WebViewBuilderExtWindows { #[cfg(windows)] impl WebViewBuilderExtWindows for WebViewBuilder<'_> { - fn with_additional_browser_args>(mut self, additional_args: S) -> Self { - self.platform_specific.additional_browser_args = Some(additional_args.into()); - self + fn with_additional_browser_args>(self, additional_args: S) -> Self { + self.and_then(|mut b| { + b.platform_specific.additional_browser_args = Some(additional_args.into()); + Ok(b) + }) } - fn with_browser_accelerator_keys(mut self, enabled: bool) -> Self { - self.platform_specific.browser_accelerator_keys = enabled; - self + fn with_browser_accelerator_keys(self, enabled: bool) -> Self { + self.and_then(|mut b| { + b.platform_specific.browser_accelerator_keys = enabled; + Ok(b) + }) } - fn with_theme(mut self, theme: Theme) -> Self { - self.platform_specific.theme = Some(theme); - self + fn with_theme(self, theme: Theme) -> Self { + self.and_then(|mut b| { + b.platform_specific.theme = Some(theme); + Ok(b) + }) } - fn with_https_scheme(mut self, enabled: bool) -> Self { - self.platform_specific.use_https = enabled; - self + fn with_https_scheme(self, enabled: bool) -> Self { + self.and_then(|mut b| { + b.platform_specific.use_https = enabled; + Ok(b) + }) } - fn with_scroll_bar_style(mut self, style: ScrollBarStyle) -> Self { - self.platform_specific.scroll_bar_style = style; - self + fn with_scroll_bar_style(self, style: ScrollBarStyle) -> Self { + self.and_then(|mut b| { + b.platform_specific.scroll_bar_style = style; + Ok(b) + }) } - fn with_browser_extensions_enabled(mut self, enabled: bool) -> Self { - self.platform_specific.browser_extensions_enabled = enabled; - self + fn with_browser_extensions_enabled(self, enabled: bool) -> Self { + self.and_then(|mut b| { + b.platform_specific.browser_extensions_enabled = enabled; + Ok(b) + }) } } @@ -1244,32 +1346,38 @@ impl WebViewBuilderExtAndroid for WebViewBuilder<'_> { fn on_webview_created< F: Fn(prelude::Context<'_, '_>) -> std::result::Result<(), jni::errors::Error> + Send + 'static, >( - mut self, + self, f: F, ) -> Self { - self.platform_specific.on_webview_created = Some(Box::new(f)); - self + self.and_then(|mut b| { + b.platform_specific.on_webview_created = Some(Box::new(f)); + Ok(b) + }) } #[cfg(feature = "protocol")] - fn with_asset_loader(mut self, protocol: String) -> Self { + fn with_asset_loader(self, protocol: String) -> Self { // register custom protocol with empty Response return, // this is necessary due to the need of fixing a domain // in WebViewAssetLoader. - self.attrs.custom_protocols.insert( - protocol.clone(), - Box::new(|_, api| { - api.respond(Response::builder().body(Vec::new()).unwrap()); - }), - ); - self.platform_specific.with_asset_loader = true; - self.platform_specific.asset_loader_domain = Some(format!("{}.assets", protocol)); - self - } - - fn with_https_scheme(mut self, enabled: bool) -> Self { - self.platform_specific.https_scheme = enabled; - self + self.and_then(|mut b| { + b.attrs.custom_protocols.insert( + protocol.clone(), + Box::new(|_, _, api| { + api.respond(Response::builder().body(Vec::new()).unwrap()); + }), + ); + b.platform_specific.with_asset_loader = true; + b.platform_specific.asset_loader_domain = Some(format!("{}.assets", protocol)); + Ok(b) + }) + } + + fn with_https_scheme(self, enabled: bool) -> Self { + self.and_then(|mut b| { + b.platform_specific.https_scheme = enabled; + Ok(b) + }) } } @@ -1281,7 +1389,7 @@ impl WebViewBuilderExtAndroid for WebViewBuilder<'_> { target_os = "openbsd", ))] pub trait WebViewBuilderExtUnix<'a> { - /// Create the webview inside a GTK container widget, such as GTK window. + /// Consume the builder and create the webview inside a GTK container widget, such as GTK window. /// /// - If the container is [`gtk::Box`], it is added using [`Box::pack_start(webview, true, true, 0)`](gtk::prelude::BoxExt::pack_start). /// - If the container is [`gtk::Fixed`], its [size request](gtk::prelude::WidgetExt::set_size_request) will be set using the (width, height) bounds passed in @@ -1291,7 +1399,7 @@ pub trait WebViewBuilderExtUnix<'a> { /// # Panics: /// /// - Panics if [`gtk::init`] was not called in this thread. - fn new_gtk(widget: &'a W) -> Self + fn build_gtk(self, widget: &'a W) -> Result where W: gtk::prelude::IsA; } @@ -1304,21 +1412,14 @@ pub trait WebViewBuilderExtUnix<'a> { target_os = "openbsd", ))] impl<'a> WebViewBuilderExtUnix<'a> for WebViewBuilder<'a> { - fn new_gtk(widget: &'a W) -> Self + fn build_gtk(self, widget: &'a W) -> Result where W: gtk::prelude::IsA, { - use gdkx11::glib::Cast; + let parts = self.inner?; - Self { - attrs: WebViewAttributes::default(), - window: None, - as_child: false, - #[allow(clippy::default_constructed_unit_structs)] - platform_specific: PlatformSpecificWebViewAttributes::default(), - web_context: None, - gtk_widget: Some(widget.dynamic_cast_ref().unwrap()), - } + InnerWebView::new_gtk(widget, parts.attrs, parts.platform_specific) + .map(|webview| WebView { webview }) } } @@ -1351,8 +1452,8 @@ impl WebView { /// /// - Panics if the provided handle was not supported or invalid. /// - Panics on Linux, if [`gtk::init`] was not called in this thread. - pub fn new(window: &impl HasWindowHandle) -> Result { - WebViewBuilder::new(window).build() + pub fn new(window: &impl HasWindowHandle, attrs: WebViewAttributes) -> Result { + WebViewBuilder::with_attributes(attrs).build(window) } /// Create [`WebViewBuilder`] as a child window inside the provided [`HasWindowHandle`]. @@ -1377,8 +1478,13 @@ impl WebView { /// /// - Panics if the provided handle was not support or invalid. /// - Panics on Linux, if [`gtk::init`] was not called in this thread. - pub fn new_as_child(parent: &impl HasWindowHandle) -> Result { - WebViewBuilder::new_as_child(parent).build() + pub fn new_as_child(parent: &impl HasWindowHandle, attrs: WebViewAttributes) -> Result { + WebViewBuilder::with_attributes(attrs).build_as_child(parent) + } + + /// Returns the id of this webview. + pub fn id(&self) -> Option { + self.webview.id() } /// Get the current url of the webview @@ -1640,7 +1746,7 @@ impl WebViewExtUnix for WebView { where W: gtk::prelude::IsA, { - WebViewBuilder::new_gtk(widget).build() + WebViewBuilder::new().build_gtk(widget) } fn webview(&self) -> webkit2gtk::WebView { diff --git a/src/web_context.rs b/src/web_context.rs index 271f847db..89610ff14 100644 --- a/src/web_context.rs +++ b/src/web_context.rs @@ -5,7 +5,10 @@ #[cfg(gtk)] use crate::webkitgtk::WebContextImpl; -use std::path::{Path, PathBuf}; +use std::{ + collections::HashSet, + path::{Path, PathBuf}, +}; /// A context that is shared between multiple [`WebView`]s. /// @@ -20,9 +23,11 @@ use std::path::{Path, PathBuf}; /// [`WebView`]: crate::WebView #[derive(Debug)] pub struct WebContext { - data: WebContextData, + data_directory: Option, #[allow(dead_code)] // It's not needed on Windows and macOS. pub(crate) os: WebContextImpl, + #[allow(dead_code)] // It's not needed on Windows and macOS. + pub(crate) custom_protocols: HashSet, } impl WebContext { @@ -32,21 +37,39 @@ impl WebContext { /// * Whether the WebView window should have a custom user data path. This is useful in Windows /// when a bundled application can't have the webview data inside `Program Files`. pub fn new(data_directory: Option) -> Self { - let data = WebContextData { data_directory }; - let os = WebContextImpl::new(&data); - Self { data, os } + Self { + os: WebContextImpl::new(data_directory.as_deref()), + data_directory, + custom_protocols: Default::default(), + } } #[cfg(gtk)] pub(crate) fn new_ephemeral() -> Self { - let data = WebContextData::default(); - let os = WebContextImpl::new_ephemeral(); - Self { data, os } + Self { + os: WebContextImpl::new_ephemeral(), + data_directory: None, + custom_protocols: Default::default(), + } } /// A reference to the data directory the context was created with. pub fn data_directory(&self) -> Option<&Path> { - self.data.data_directory() + self.data_directory.as_deref() + } + + #[allow(dead_code)] + pub(crate) fn register_custom_protocol(&mut self, name: String) -> Result<(), crate::Error> { + if self.custom_protocols.contains(&name) { + return Err(crate::Error::ContextDuplicateCustomProtocol(name)); + } + + Ok(()) + } + + /// Check if a custom protocol has been registered on this context. + pub fn is_custom_protocol_registerd(&self, name: String) -> bool { + self.custom_protocols.contains(&name) } /// Set if this context allows automation. @@ -60,22 +83,7 @@ impl WebContext { impl Default for WebContext { fn default() -> Self { - let data = WebContextData::default(); - let os = WebContextImpl::new(&data); - Self { data, os } - } -} - -/// Data that all [`WebContext`] share regardless of platform. -#[derive(Default, Debug)] -pub struct WebContextData { - data_directory: Option, -} - -impl WebContextData { - /// A reference to the data directory the context was created with. - pub fn data_directory(&self) -> Option<&Path> { - self.data_directory.as_deref() + Self::new(None) } } @@ -85,7 +93,7 @@ pub(crate) struct WebContextImpl; #[cfg(not(gtk))] impl WebContextImpl { - fn new(_data: &WebContextData) -> Self { + fn new(_: Option<&Path>) -> Self { Self } diff --git a/src/webkitgtk/mod.rs b/src/webkitgtk/mod.rs index 009adcc64..68d787ae4 100644 --- a/src/webkitgtk/mod.rs +++ b/src/webkitgtk/mod.rs @@ -47,6 +47,8 @@ use crate::{ use self::web_context::WebContextExt; +const WEBVIEW_ID: &str = "webview_id"; + mod drag_drop; mod synthetic_mouse_events; mod web_context; @@ -67,6 +69,7 @@ impl Drop for X11Data { } pub(crate) struct InnerWebView { + id: Option, pub webview: WebView, #[cfg(any(debug_assertions, feature = "devtools"))] is_inspector_open: Arc, @@ -87,25 +90,22 @@ impl InnerWebView { window: &W, attributes: WebViewAttributes, pl_attrs: super::PlatformSpecificWebViewAttributes, - web_context: Option<&mut WebContext>, ) -> Result { - Self::new_x11(window, attributes, pl_attrs, web_context, false) + Self::new_x11(window, attributes, pl_attrs, false) } pub fn new_as_child( parent: &W, attributes: WebViewAttributes, pl_attrs: super::PlatformSpecificWebViewAttributes, - web_context: Option<&mut WebContext>, ) -> Result { - Self::new_x11(parent, attributes, pl_attrs, web_context, true) + Self::new_x11(parent, attributes, pl_attrs, true) } fn new_x11( window: &W, attributes: WebViewAttributes, pl_attrs: super::PlatformSpecificWebViewAttributes, - web_context: Option<&mut WebContext>, is_child: bool, ) -> Result { let parent = match window.window_handle()?.as_raw() { @@ -130,7 +130,7 @@ impl InnerWebView { let visible = attributes.visible; - Self::new_gtk(&vbox, attributes, pl_attrs, web_context).map(|mut w| { + Self::new_gtk(&vbox, attributes, pl_attrs).map(|mut w| { // for some reason, if the webview starts as hidden, // we will need about 3 calls to `webview.set_visible` // with alternating value. @@ -209,7 +209,6 @@ impl InnerWebView { container: &W, mut attributes: WebViewAttributes, _pl_attrs: super::PlatformSpecificWebViewAttributes, - web_context: Option<&mut WebContext>, ) -> Result where W: IsA, @@ -220,7 +219,7 @@ impl InnerWebView { default_context = WebContext::new_ephemeral(); &mut default_context } else { - match web_context { + match attributes.context.take() { Some(w) => w, None => { default_context = Default::default(); @@ -280,7 +279,13 @@ impl InnerWebView { #[cfg(any(debug_assertions, feature = "devtools"))] let is_inspector_open = Self::attach_inspector_handlers(&webview); + let id = attributes.id.map(|id| id.to_string()); + if let Some(id) = &id { + unsafe { webview.set_data(WEBVIEW_ID, id.clone()) }; + } + let w = Self { + id, webview, pending_scripts: Arc::new(Mutex::new(Some(Vec::new()))), @@ -556,6 +561,10 @@ impl InnerWebView { is_inspector_open } + pub fn id(&self) -> Option { + self.id.as_deref() + } + pub fn print(&self) -> Result<()> { let print = webkit2gtk::PrintOperation::new(&self.webview); print.run_dialog(None::<>k::Window>); diff --git a/src/webkitgtk/web_context.rs b/src/webkitgtk/web_context.rs index b323245d6..8428f1fd1 100644 --- a/src/webkitgtk/web_context.rs +++ b/src/webkitgtk/web_context.rs @@ -4,15 +4,15 @@ //! Unix platform extensions for [`WebContext`](super::WebContext). -use crate::{web_context::WebContextData, Error, RequestAsyncResponder}; -use gtk::glib::{self, MainContext}; +use crate::{Error, RequestAsyncResponder}; +use gtk::glib::{self, MainContext, ObjectExt}; use http::{header::CONTENT_TYPE, HeaderName, HeaderValue, Request, Response as HttpResponse}; use soup::{MessageHeaders, MessageHeadersType}; use std::{ borrow::Cow, cell::RefCell, collections::VecDeque, - path::PathBuf, + path::{Path, PathBuf}, rc::Rc, sync::{ atomic::{AtomicBool, Ordering::SeqCst}, @@ -35,10 +35,10 @@ pub struct WebContextImpl { } impl WebContextImpl { - pub fn new(data: &WebContextData) -> Self { + pub fn new(data_directory: Option<&Path>) -> Self { use webkit2gtk::{CookieManagerExt, WebsiteDataManager, WebsiteDataManagerExt}; let mut context_builder = WebContext::builder(); - if let Some(data_directory) = data.data_directory() { + if let Some(data_directory) = data_directory { let data_manager = WebsiteDataManager::builder() .base_data_directory(data_directory.to_string_lossy()) .build(); @@ -102,7 +102,7 @@ pub trait WebContextExt { /// Register a custom protocol to the web context. fn register_uri_scheme(&mut self, name: &str, handler: F) -> crate::Result<()> where - F: Fn(Request>, RequestAsyncResponder) + 'static; + F: Fn(Option, Request>, RequestAsyncResponder) + 'static; /// Add a [`WebView`] to the queue waiting to be opened. /// @@ -135,7 +135,7 @@ impl WebContextExt for super::WebContext { fn register_uri_scheme(&mut self, name: &str, handler: F) -> crate::Result<()> where - F: Fn(Request>, RequestAsyncResponder) + 'static, + F: Fn(Option, Request>, RequestAsyncResponder) + 'static, { // Enable secure context self @@ -248,7 +248,13 @@ impl WebContextExt for super::WebContext { #[cfg(feature = "tracing")] let _span = tracing::info_span!("wry::custom_protocol::call_handler").entered(); - handler(http_request, RequestAsyncResponder { responder }); + + let webview_id = request + .web_view() + .and_then(|w| unsafe { w.data::(super::WEBVIEW_ID) }) + .map(|id| unsafe { id.as_ref().clone() }); + + handler(webview_id.as_deref(), http_request, RequestAsyncResponder { responder }); } else { request.finish_error(&mut glib::Error::new( glib::FileError::Exist, diff --git a/src/webview2/mod.rs b/src/webview2/mod.rs index 18ff184eb..042aef4d3 100644 --- a/src/webview2/mod.rs +++ b/src/webview2/mod.rs @@ -29,7 +29,7 @@ use self::drag_drop::DragDropController; use super::Theme; use crate::{ proxy::ProxyConfig, Error, MemoryUsageLevel, PageLoadEvent, Rect, RequestAsyncResponder, Result, - WebContext, WebViewAttributes, RGBA, + WebViewAttributes, RGBA, }; const PARENT_SUBCLASS_ID: u32 = WM_USER + 0x64; @@ -50,6 +50,7 @@ impl From for Error { } pub(crate) struct InnerWebView { + id: Option, parent: RefCell, hwnd: HWND, is_child: bool, @@ -78,13 +79,12 @@ impl InnerWebView { window: &impl HasWindowHandle, attributes: WebViewAttributes, pl_attrs: super::PlatformSpecificWebViewAttributes, - web_context: Option<&mut WebContext>, ) -> Result { let window = match window.window_handle()?.as_raw() { RawWindowHandle::Win32(window) => HWND(window.hwnd.get() as _), _ => return Err(Error::UnsupportedWindowHandle), }; - Self::new_in_hwnd(window, attributes, pl_attrs, web_context, false) + Self::new_in_hwnd(window, attributes, pl_attrs, false) } #[inline] @@ -92,14 +92,13 @@ impl InnerWebView { parent: &impl HasWindowHandle, attributes: WebViewAttributes, pl_attrs: super::PlatformSpecificWebViewAttributes, - web_context: Option<&mut WebContext>, ) -> Result { let parent = match parent.window_handle()?.as_raw() { RawWindowHandle::Win32(parent) => HWND(parent.hwnd.get() as _), _ => return Err(Error::UnsupportedWindowHandle), }; - Self::new_in_hwnd(parent, attributes, pl_attrs, web_context, true) + Self::new_in_hwnd(parent, attributes, pl_attrs, true) } #[inline] @@ -107,7 +106,6 @@ impl InnerWebView { parent: HWND, mut attributes: WebViewAttributes, pl_attrs: super::PlatformSpecificWebViewAttributes, - web_context: Option<&mut WebContext>, is_child: bool, ) -> Result { let _ = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) }; @@ -117,7 +115,9 @@ impl InnerWebView { let drop_handler = attributes.drag_drop_handler.take(); let bounds = attributes.bounds; - let env = Self::create_environment(&web_context, pl_attrs.clone(), &attributes)?; + let id = attributes.id.map(|id| id.to_string()); + + let env = Self::create_environment(&attributes, pl_attrs.clone())?; let controller = Self::create_controller(hwnd, &env, attributes.incognito)?; let webview = Self::init_webview( parent, @@ -132,6 +132,7 @@ impl InnerWebView { let drag_drop_controller = drop_handler.map(|handler| DragDropController::new(hwnd, handler)); let w = Self { + id, parent: RefCell::new(parent), hwnd, controller, @@ -247,11 +248,11 @@ impl InnerWebView { #[inline] fn create_environment( - web_context: &Option<&mut WebContext>, - pl_attrs: super::PlatformSpecificWebViewAttributes, attributes: &WebViewAttributes, + pl_attrs: super::PlatformSpecificWebViewAttributes, ) -> Result { - let data_directory = web_context + let data_directory = attributes + .context .as_deref() .and_then(|context| context.data_directory()) .map(HSTRING::from); @@ -798,6 +799,7 @@ impl InnerWebView { let env = env.clone(); let custom_protocols = std::mem::take(&mut attributes.custom_protocols); let main_thread_id = std::thread::current().id(); + let webview_id = attributes.id.map(|id| id.to_string()); webview.add_WebResourceRequested( &WebResourceRequestedEventHandler::create(Box::new(move |_, args| { @@ -866,6 +868,7 @@ impl InnerWebView { #[cfg(feature = "tracing")] let _span = tracing::info_span!("wry::custom_protocol::call_handler").entered(); custom_protocol_handler( + webview_id.as_deref(), request, RequestAsyncResponder { responder: async_responder, @@ -1175,6 +1178,10 @@ impl InnerWebView { /// Public APIs impl InnerWebView { + pub fn id(&self) -> Option { + self.id.as_deref() + } + pub fn eval( &self, js: &str, diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 6ff79df70..3d3bc9269 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -98,6 +98,7 @@ pub struct PrintOptions { } pub(crate) struct InnerWebView { + id: Option, pub webview: id, pub manager: id, is_child: bool, @@ -111,8 +112,9 @@ pub(crate) struct InnerWebView { #[cfg(target_os = "macos")] drag_drop_ptr: *mut Box bool>, download_delegate: id, - protocol_ptrs: Vec<*mut Box>, RequestAsyncResponder)>>, - webview_id: u32, + protocol_ptrs: + Vec<*mut Box, Request>, RequestAsyncResponder)>>, + internal_webview_id: u32, } impl InnerWebView { @@ -120,7 +122,6 @@ impl InnerWebView { window: &impl HasWindowHandle, attributes: WebViewAttributes, pl_attrs: super::PlatformSpecificWebViewAttributes, - _web_context: Option<&mut WebContext>, ) -> Result { let ns_view = match window.window_handle()?.as_raw() { #[cfg(target_os = "macos")] @@ -130,14 +131,13 @@ impl InnerWebView { _ => return Err(Error::UnsupportedWindowHandle), }; - Self::new_ns_view(ns_view as _, attributes, pl_attrs, _web_context, false) + Self::new_ns_view(ns_view as _, attributes, pl_attrs, false) } pub fn new_as_child( window: &impl HasWindowHandle, attributes: WebViewAttributes, pl_attrs: super::PlatformSpecificWebViewAttributes, - _web_context: Option<&mut WebContext>, ) -> Result { let ns_view = match window.window_handle()?.as_raw() { #[cfg(target_os = "macos")] @@ -147,14 +147,13 @@ impl InnerWebView { _ => return Err(Error::UnsupportedWindowHandle), }; - Self::new_ns_view(ns_view as _, attributes, pl_attrs, _web_context, true) + Self::new_ns_view(ns_view as _, attributes, pl_attrs, true) } fn new_ns_view( ns_view: id, attributes: WebViewAttributes, pl_attrs: super::PlatformSpecificWebViewAttributes, - _web_context: Option<&mut WebContext>, is_child: bool, ) -> Result { // Function for ipc handler @@ -199,12 +198,20 @@ impl InnerWebView { #[cfg(feature = "tracing")] let span = tracing::info_span!(parent: None, "wry::custom_protocol::handle", uri = tracing::field::Empty) .entered(); - let webview_id = *this.get_ivar::("webview_id"); + let internal_webview_id = *this.get_ivar::("internal_webview_id"); let function = this.get_ivar::<*mut c_void>("function"); if !function.is_null() { - let function = - &mut *(*function as *mut Box>, RequestAsyncResponder)>); + let function = &mut *(*function + as *mut Box, Request>, RequestAsyncResponder)>); + + let webview_id = this.get_ivar::<*mut c_void>("id"); + + let webview_id = if !webview_id.is_null() { + &*(*webview_id as *const &Option) + } else { + None + }; // Get url request let request: id = msg_send![task, request]; @@ -279,8 +286,8 @@ impl InnerWebView { let responder: Box>)> = Box::new( move |sent_response| { - fn check_webview_id_valid(webview_id: u32) -> crate::Result<()> { - match WEBVIEW_IDS.lock().unwrap().contains(&webview_id) { + fn check_webview_id_valid(internal_webview_id: u32) -> crate::Result<()> { + match WEBVIEW_IDS.lock().unwrap().contains(&internal_webview_id) { true => Ok(()), false => Err(crate::Error::CustomProtocolTaskInvalid), } @@ -288,7 +295,7 @@ impl InnerWebView { unsafe fn response( task: id, - webview_id: u32, + internal_webview_id: u32, url: id, /* NSURL */ sent_response: HttpResponse>, ) -> crate::Result<()> { @@ -318,7 +325,7 @@ impl InnerWebView { let urlresponse: id = msg_send![class!(NSHTTPURLResponse), alloc]; let response: id = msg_send![urlresponse, initWithURL:url statusCode: wanted_status_code HTTPVersion:NSString::new(&wanted_version) headerFields:headers]; - check_webview_id_valid(webview_id)?; + check_webview_id_valid(internal_webview_id)?; (*task) .send_message::<(id,), ()>(sel!(didReceiveResponse:), (response,)) .map_err(|_| crate::Error::CustomProtocolTaskInvalid)?; @@ -328,13 +335,13 @@ impl InnerWebView { let data: id = msg_send![class!(NSData), alloc]; let data: id = msg_send![data, initWithBytesNoCopy:bytes length:content.len() freeWhenDone: if content.len() == 0 { NO } else { YES }]; - check_webview_id_valid(webview_id)?; + check_webview_id_valid(internal_webview_id)?; (*task) .send_message::<(id,), ()>(sel!(didReceiveData:), (data,)) .map_err(|_| crate::Error::CustomProtocolTaskInvalid)?; // Finish - check_webview_id_valid(webview_id)?; + check_webview_id_valid(internal_webview_id)?; (*task) .send_message::<(), ()>(sel!(didFinish), ()) .map_err(|_| crate::Error::CustomProtocolTaskInvalid)?; @@ -342,14 +349,18 @@ impl InnerWebView { Ok(()) } - let _ = response(task, webview_id, url, sent_response); + let _ = response(task, internal_webview_id, url, sent_response); let () = msg_send![task, release]; }, ); #[cfg(feature = "tracing")] let _span = tracing::info_span!("wry::custom_protocol::call_handler").entered(); - function(final_request, RequestAsyncResponder { responder }); + function( + webview_id.as_deref(), + final_request, + RequestAsyncResponder { responder }, + ); } Err(_) => respond_with_404(), }; @@ -364,8 +375,8 @@ impl InnerWebView { extern "C" fn stop_task(_: &Object, _: Sel, _webview: id, _task: id) {} let mut wv_ids = WEBVIEW_IDS.lock().unwrap(); - let webview_id = COUNTER.next(); - wv_ids.insert(webview_id); + let internal_webview_id = COUNTER.next(); + wv_ids.insert(internal_webview_id); drop(wv_ids); // Safety: objc runtime calls are unsafe @@ -398,13 +409,16 @@ impl InnerWebView { _ => msg_send![class!(WKWebsiteDataStore), defaultDataStore], }; + let id = attributes.id.map(|id| id.to_string()); + for (name, function) in attributes.custom_protocols { let scheme_name = format!("{}URLSchemeHandler", name); let cls = ClassDecl::new(&scheme_name, class!(NSObject)); let cls = match cls { Some(mut cls) => { cls.add_ivar::<*mut c_void>("function"); - cls.add_ivar::("webview_id"); + cls.add_ivar::("internal_webview_id"); + cls.add_ivar::>("id"); cls.add_method( sel!(webView:startURLSchemeTask:), start_task as extern "C" fn(&Object, Sel, id, id), @@ -422,7 +436,8 @@ impl InnerWebView { protocol_ptrs.push(function); (*handler).set_ivar("function", function as *mut _ as *mut c_void); - (*handler).set_ivar("webview_id", webview_id); + (*handler).set_ivar("internal_webview_id", internal_webview_id); + (*handler).set_ivar("id", id.clone()); (*config) .send_message::<(id, NSString), ()>( @@ -965,7 +980,10 @@ impl InnerWebView { } } + let id = attributes.id.map(|id| id.to_string()); + let w = Self { + id, webview, manager, pending_scripts, @@ -978,7 +996,7 @@ impl InnerWebView { download_delegate, protocol_ptrs, is_child, - webview_id, + internal_webview_id, }; // Initialize scripts @@ -1057,6 +1075,10 @@ r#"Object.defineProperty(window, 'ipc', { } } + pub fn id(&self) -> Option { + self.id.as_deref() + } + pub fn url(&self) -> crate::Result { url_from_webview(self.webview) } From 4895c57a2fe48e0cd4f751fc1aa6804cafce0dd7 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Tue, 8 Oct 2024 08:05:23 +0300 Subject: [PATCH 02/11] try to fix macos --- src/lib.rs | 4 ++-- src/wkwebview/mod.rs | 15 ++++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 968457440..32e0bb6ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,7 +114,7 @@ //! let event_loop = EventLoop::new().unwrap(); //! //! let window = Window::new(&event_loop).unwrap(); -//! let webview = WebView::new(&window); +//! let webview = WebView::new(&window, WebViewAttributes::default()); //! //! event_loop.run(|_e, _evl|{ //! // process winit events @@ -766,7 +766,7 @@ impl<'a> WebViewBuilder<'a> { /// ```no_run /// use wry::{WebViewBuilder, raw_window_handle}; /// WebViewBuilder::new() - /// .with_asynchronous_custom_protocol("wry".into(), |request, responder| { + /// .with_asynchronous_custom_protocol("wry".into(), |_webview_id, request, responder| { /// // here you can use a tokio task, thread pool or anything /// // to do heavy computation to resolve your request /// // e.g. downloading files, opening the camera... diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 3d3bc9269..34bb6ed04 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -205,13 +205,7 @@ impl InnerWebView { let function = &mut *(*function as *mut Box, Request>, RequestAsyncResponder)>); - let webview_id = this.get_ivar::<*mut c_void>("id"); - - let webview_id = if !webview_id.is_null() { - &*(*webview_id as *const &Option) - } else { - None - }; + let webview_id = this.get_ivar::("id"); // Get url request let request: id = msg_send![task, request]; @@ -418,7 +412,7 @@ impl InnerWebView { Some(mut cls) => { cls.add_ivar::<*mut c_void>("function"); cls.add_ivar::("internal_webview_id"); - cls.add_ivar::>("id"); + cls.add_ivar::("id"); cls.add_method( sel!(webView:startURLSchemeTask:), start_task as extern "C" fn(&Object, Sel, id, id), @@ -1379,7 +1373,10 @@ pub fn platform_webview_version() -> Result { impl Drop for InnerWebView { fn drop(&mut self) { - WEBVIEW_IDS.lock().unwrap().remove(&self.webview_id); + WEBVIEW_IDS + .lock() + .unwrap() + .remove(&self.internal_webview_id); // We need to drop handler closures here unsafe { From 91408252ea25e74bec9908ac319956d888e174c2 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 8 Oct 2024 08:37:54 -0300 Subject: [PATCH 03/11] fix macos impl --- src/lib.rs | 2 +- src/wkwebview/mod.rs | 26 ++++++++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 32e0bb6ef..d024ba1bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1169,7 +1169,7 @@ pub trait WebViewBuilderExtDarwin { #[cfg(any(target_os = "macos", target_os = "ios",))] impl WebViewBuilderExtDarwin for WebViewBuilder<'_> { - fn with_data_store_identifier(mut self, identifier: [u8; 16]) -> Self { + fn with_data_store_identifier(self, identifier: [u8; 16]) -> Self { self.and_then(|mut b| { b.platform_specific.data_store_identifier = Some(identifier); Ok(b) diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 34bb6ed04..9f65a0118 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -25,7 +25,7 @@ use raw_window_handle::{HasWindowHandle, RawWindowHandle}; use std::{ borrow::Cow, collections::HashSet, - ffi::{c_void, CStr}, + ffi::{c_void, CStr, CString}, os::raw::c_char, ptr::{null, null_mut}, slice, str, @@ -63,7 +63,7 @@ use crate::{ }, navigation::{add_navigation_mathods, drop_navigation_methods, set_navigation_methods}, }, - Error, PageLoadEvent, Rect, RequestAsyncResponder, Result, WebContext, WebViewAttributes, RGBA, + Error, PageLoadEvent, Rect, RequestAsyncResponder, Result, WebViewAttributes, RGBA, }; use http::{ @@ -205,7 +205,12 @@ impl InnerWebView { let function = &mut *(*function as *mut Box, Request>, RequestAsyncResponder)>); - let webview_id = this.get_ivar::("id"); + let webview_id_ptr: *mut c_char = *this.get_ivar("id"); + let webview_id = if webview_id_ptr.is_null() { + None + } else { + CStr::from_ptr(webview_id_ptr).to_str().ok() + }; // Get url request let request: id = msg_send![task, request]; @@ -351,7 +356,7 @@ impl InnerWebView { #[cfg(feature = "tracing")] let _span = tracing::info_span!("wry::custom_protocol::call_handler").entered(); function( - webview_id.as_deref(), + webview_id, final_request, RequestAsyncResponder { responder }, ); @@ -412,7 +417,7 @@ impl InnerWebView { Some(mut cls) => { cls.add_ivar::<*mut c_void>("function"); cls.add_ivar::("internal_webview_id"); - cls.add_ivar::("id"); + cls.add_ivar::<*mut c_char>("id"); cls.add_method( sel!(webView:startURLSchemeTask:), start_task as extern "C" fn(&Object, Sel, id, id), @@ -431,7 +436,14 @@ impl InnerWebView { (*handler).set_ivar("function", function as *mut _ as *mut c_void); (*handler).set_ivar("internal_webview_id", internal_webview_id); - (*handler).set_ivar("id", id.clone()); + (*handler).set_ivar( + "id", + if let Some(id) = &id { + CString::new(id.as_bytes()).unwrap().into_raw() + } else { + std::ptr::null_mut() + }, + ); (*config) .send_message::<(id, NSString), ()>( @@ -974,8 +986,6 @@ impl InnerWebView { } } - let id = attributes.id.map(|id| id.to_string()); - let w = Self { id, webview, From d27d35145d7b9f8136de759478119af5b5f8c9a0 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Thu, 10 Oct 2024 09:22:11 +0300 Subject: [PATCH 04/11] fix doctests --- README.md | 3 ++- src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0451d2ba9..877db0dfa 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,8 @@ fn main() -> wry::Result<()> { .with_title("Hello World") .build(&event_loop) .unwrap(); - let _webview = WebViewBuilder::new() + + let webview = WebViewBuilder::new() .with_url("https://tauri.app") .build(&window)?; diff --git a/src/lib.rs b/src/lib.rs index d024ba1bf..c0ec178d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,7 +108,7 @@ //! //! ```no_run //! # use winit::{event_loop::EventLoop, window::Window}; -//! # use wry::WebView; +//! # use wry::{WebView, WebViewAttributes}; //! #[cfg(target_os = "linux")] //! gtk::init().unwrap(); // <----- IMPORTANT //! let event_loop = EventLoop::new().unwrap(); From b6c7bced45fced203d32a9d0b7793017088e7ed7 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 11 Oct 2024 05:11:14 +0300 Subject: [PATCH 05/11] generated id instead of defaulting to None --- src/android/binding.rs | 10 +--- src/android/kotlin/RustWebView.kt | 2 +- src/android/kotlin/RustWebViewClient.kt | 2 +- src/android/main_pipe.rs | 13 ++--- src/android/mod.rs | 31 +++++++----- src/lib.rs | 10 ++-- src/util.rs | 13 +++++ src/webkitgtk/mod.rs | 15 +++--- src/webkitgtk/web_context.rs | 9 ++-- src/webview2/mod.rs | 18 ++++--- src/wkwebview/mod.rs | 67 +++++++++++-------------- src/wkwebview/util.rs | 14 ------ 12 files changed, 99 insertions(+), 105 deletions(-) create mode 100644 src/util.rs diff --git a/src/android/binding.rs b/src/android/binding.rs index 29d692b98..2a61a8d08 100644 --- a/src/android/binding.rs +++ b/src/android/binding.rs @@ -164,14 +164,8 @@ fn handle_request( } }; - let webview_id = if webview_id.is_null() { - None - } else { - let id = env.get_string(&webview_id)?; - id.to_str().ok().map(|id| id.to_string()) - }; - - dbg!(&webview_id); + let webview_id = env.get_string(&webview_id)?; + let webview_id = webview_id.to_str().ok().unwrap_or_default(); let response = { #[cfg(feature = "tracing")] diff --git a/src/android/kotlin/RustWebView.kt b/src/android/kotlin/RustWebView.kt index cb1b1237b..1486791a6 100644 --- a/src/android/kotlin/RustWebView.kt +++ b/src/android/kotlin/RustWebView.kt @@ -14,7 +14,7 @@ import androidx.webkit.WebViewFeature import kotlin.collections.Map @SuppressLint("RestrictedApi") -class RustWebView(context: Context, val initScripts: Array, val id: String?): WebView(context) { +class RustWebView(context: Context, val initScripts: Array, val id: String): WebView(context) { val isDocumentStartScriptEnabled: Boolean init { diff --git a/src/android/kotlin/RustWebViewClient.kt b/src/android/kotlin/RustWebViewClient.kt index df790d9de..343ad1490 100644 --- a/src/android/kotlin/RustWebViewClient.kt +++ b/src/android/kotlin/RustWebViewClient.kt @@ -96,7 +96,7 @@ class RustWebViewClient(context: Context): WebViewClient() { private external fun assetLoaderDomain(): String private external fun withAssetLoader(): Boolean - private external fun handleRequest(webviewId: String?, request: WebResourceRequest, isDocumentStartScriptEnabled: Boolean): WebResourceResponse? + private external fun handleRequest(webviewId: String, request: WebResourceRequest, isDocumentStartScriptEnabled: Boolean): WebResourceResponse? private external fun shouldOverride(url: String): Boolean private external fun onPageLoading(url: String) private external fun onPageLoaded(url: String) diff --git a/src/android/main_pipe.rs b/src/android/main_pipe.rs index ff0b5353f..e5da92a51 100644 --- a/src/android/main_pipe.rs +++ b/src/android/main_pipe.rs @@ -59,6 +59,7 @@ impl<'a> MainPipe<'a> { autoplay, user_agent, initialization_scripts, + id, .. } = attrs; @@ -76,11 +77,7 @@ impl<'a> MainPipe<'a> { )?; } - let id = attrs.id.clone(); - let id = match id { - Some(id) => self.env.new_string(id)?, - None => JString::default(), - }; + let id = self.env.new_string(id)?; // Create webview let rust_webview_class = find_class( @@ -215,9 +212,7 @@ impl<'a> MainPipe<'a> { } WebViewMessage::Eval(script, callback) => { if let Some(webview) = &self.webview { - let id = EVAL_ID_GENERATOR - .get_or_init(Default::default) - .fetch_add(1, Ordering::Relaxed); + let id = EVAL_ID_GENERATOR.next() as i32; #[cfg(feature = "tracing")] let span = std::sync::Mutex::new(Some(SendEnteredSpan( @@ -409,7 +404,7 @@ pub(crate) enum WebViewMessage { } pub(crate) struct CreateWebViewAttributes { - pub id: Option, + pub id: String, pub url: Option, pub html: Option, #[cfg(any(debug_assertions, feature = "devtools"))] diff --git a/src/android/mod.rs b/src/android/mod.rs index 2d3950044..32fa40d11 100644 --- a/src/android/mod.rs +++ b/src/android/mod.rs @@ -25,13 +25,17 @@ use std::{ borrow::Cow, collections::HashMap, os::fd::{AsFd as _, AsRawFd as _}, - sync::{atomic::AtomicI32, mpsc::channel, Mutex}, + sync::{mpsc::channel, Mutex}, }; pub(crate) mod binding; mod main_pipe; use main_pipe::{CreateWebViewAttributes, MainPipe, WebViewMessage, MAIN_PIPE}; +use crate::util::Counter; + +static COUNTER: Counter = Counter::new(); + pub struct Context<'a, 'b> { pub env: &'a mut JNIEnv<'b>, pub activity: &'a JObject<'b>, @@ -58,7 +62,7 @@ macro_rules! define_static_handlers { define_static_handlers! { IPC = UnsafeIpc { handler: Box)> }; - REQUEST_HANDLER = UnsafeRequestHandler { handler: Box, Request>, bool) -> Option>>> }; + REQUEST_HANDLER = UnsafeRequestHandler { handler: Box>, bool) -> Option>>> }; TITLE_CHANGE_HANDLER = UnsafeTitleHandler { handler: Box }; URL_LOADING_OVERRIDE = UnsafeUrlLoadingOverride { handler: Box bool> }; ON_LOAD_HANDLER = UnsafeOnPageLoadHandler { handler: Box }; @@ -71,7 +75,7 @@ pub(crate) static PACKAGE: OnceCell = OnceCell::new(); type EvalCallback = Box; -pub static EVAL_ID_GENERATOR: OnceCell = OnceCell::new(); +pub static EVAL_ID_GENERATOR: Counter = Counter::new(); pub static EVAL_CALLBACKS: once_cell::sync::OnceCell>> = once_cell::sync::OnceCell::new(); @@ -125,7 +129,7 @@ pub unsafe fn android_setup( } pub(crate) struct InnerWebView { - id: Option, + id: String, } impl InnerWebView { @@ -181,8 +185,13 @@ impl InnerWebView { None }; + let id = attributes + .id + .map(|id| id.to_string()) + .unwrap_or_else(|| COUNTER.next().to_string()); + MainPipe::send(WebViewMessage::CreateWebView(CreateWebViewAttributes { - id: attributes.id.map(|id| id.to_string()), + id: id.clone(), url, html, #[cfg(any(debug_assertions, feature = "devtools"))] @@ -203,7 +212,7 @@ impl InnerWebView { REQUEST_HANDLER.get_or_init(move || { UnsafeRequestHandler::new(Box::new( - move |webview_id: Option, mut request, is_document_start_script_enabled| { + move |webview_id: &str, mut request, is_document_start_script_enabled| { let uri = request.uri().to_string(); if let Some(custom_protocol) = custom_protocols.iter().find(|(name, _)| { uri.starts_with(&format!("{scheme}://{}.", name)) @@ -277,7 +286,7 @@ impl InnerWebView { tx.send(response).unwrap(); }); - (custom_protocol.1)(webview_id.as_deref(), request, RequestAsyncResponder { responder }); + (custom_protocol.1)(webview_id, request, RequestAsyncResponder { responder }); return Some(rx.recv().unwrap()); } None @@ -301,17 +310,15 @@ impl InnerWebView { ON_LOAD_HANDLER.get_or_init(move || UnsafeOnPageLoadHandler::new(h)); } - Ok(Self { - id: attributes.id.map(|id| id.to_string()), - }) + Ok(Self { id }) } pub fn print(&self) -> crate::Result<()> { Ok(()) } - pub fn id(&self) -> Option { - self.id.as_deref() + pub fn id(&self) -> crate::WebViewId { + &self.id } pub fn url(&self) -> crate::Result { diff --git a/src/lib.rs b/src/lib.rs index c0ec178d8..2974f7bee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -196,6 +196,8 @@ extern crate objc; mod error; mod proxy; +#[cfg(any(target_os = "macos", target_os = "android"))] +mod util; mod web_context; #[cfg(target_os = "android")] @@ -383,7 +385,7 @@ pub struct WebViewAttributes<'a> { /// locate your files in those directories. For more information, see [Loading in-app content](https://developer.android.com/guide/webapps/load-local-content) page. /// - iOS: To get the path of your assets, you can call [`CFBundle::resources_path`](https://docs.rs/core-foundation/latest/core_foundation/bundle/struct.CFBundle.html#method.resources_path). So url like `wry://assets/index.html` could get the html file in assets directory. pub custom_protocols: - HashMap, Request>, RequestAsyncResponder)>>, + HashMap>, RequestAsyncResponder)>>, /// The IPC handler to receive the message from Javascript on webview /// using `window.ipc.postMessage("insert_message_here")` to host Rust code. @@ -727,7 +729,7 @@ impl<'a> WebViewBuilder<'a> { #[cfg(feature = "protocol")] pub fn with_custom_protocol(self, name: String, handler: F) -> Self where - F: Fn(Option, Request>) -> Response> + 'static, + F: Fn(WebViewId, Request>) -> Response> + 'static, { self.and_then(|mut b| { #[cfg(any( @@ -779,7 +781,7 @@ impl<'a> WebViewBuilder<'a> { #[cfg(feature = "protocol")] pub fn with_asynchronous_custom_protocol(self, name: String, handler: F) -> Self where - F: Fn(Option, Request>, RequestAsyncResponder) + 'static, + F: Fn(WebViewId, Request>, RequestAsyncResponder) + 'static, { self.and_then(|mut b| { #[cfg(any( @@ -1483,7 +1485,7 @@ impl WebView { } /// Returns the id of this webview. - pub fn id(&self) -> Option { + pub fn id(&self) -> WebViewId { self.webview.id() } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 000000000..62036d6b4 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,13 @@ +use std::sync::atomic::{AtomicU32, Ordering}; + +pub struct Counter(AtomicU32); + +impl Counter { + pub const fn new() -> Self { + Self(AtomicU32::new(1)) + } + + pub fn next(&self) -> u32 { + self.0.fetch_add(1, Ordering::Relaxed) + } +} diff --git a/src/webkitgtk/mod.rs b/src/webkitgtk/mod.rs index 68d787ae4..855524222 100644 --- a/src/webkitgtk/mod.rs +++ b/src/webkitgtk/mod.rs @@ -69,7 +69,7 @@ impl Drop for X11Data { } pub(crate) struct InnerWebView { - id: Option, + id: String, pub webview: WebView, #[cfg(any(debug_assertions, feature = "devtools"))] is_inspector_open: Arc, @@ -279,10 +279,11 @@ impl InnerWebView { #[cfg(any(debug_assertions, feature = "devtools"))] let is_inspector_open = Self::attach_inspector_handlers(&webview); - let id = attributes.id.map(|id| id.to_string()); - if let Some(id) = &id { - unsafe { webview.set_data(WEBVIEW_ID, id.clone()) }; - } + let id = attributes + .id + .map(|id| id.to_string()) + .unwrap_or_else(|| (webview.as_ptr() as isize).to_string()); + unsafe { webview.set_data(WEBVIEW_ID, id.clone()) }; let w = Self { id, @@ -561,8 +562,8 @@ impl InnerWebView { is_inspector_open } - pub fn id(&self) -> Option { - self.id.as_deref() + pub fn id(&self) -> crate::WebViewId { + &self.id } pub fn print(&self) -> Result<()> { diff --git a/src/webkitgtk/web_context.rs b/src/webkitgtk/web_context.rs index 8428f1fd1..09c002f6f 100644 --- a/src/webkitgtk/web_context.rs +++ b/src/webkitgtk/web_context.rs @@ -102,7 +102,7 @@ pub trait WebContextExt { /// Register a custom protocol to the web context. fn register_uri_scheme(&mut self, name: &str, handler: F) -> crate::Result<()> where - F: Fn(Option, Request>, RequestAsyncResponder) + 'static; + F: Fn(crate::WebViewId, Request>, RequestAsyncResponder) + 'static; /// Add a [`WebView`] to the queue waiting to be opened. /// @@ -135,7 +135,7 @@ impl WebContextExt for super::WebContext { fn register_uri_scheme(&mut self, name: &str, handler: F) -> crate::Result<()> where - F: Fn(Option, Request>, RequestAsyncResponder) + 'static, + F: Fn(crate::WebViewId, Request>, RequestAsyncResponder) + 'static, { // Enable secure context self @@ -252,9 +252,10 @@ impl WebContextExt for super::WebContext { let webview_id = request .web_view() .and_then(|w| unsafe { w.data::(super::WEBVIEW_ID) }) - .map(|id| unsafe { id.as_ref().clone() }); + .map(|id| unsafe { id.as_ref().clone() }) + .unwrap_or_default(); - handler(webview_id.as_deref(), http_request, RequestAsyncResponder { responder }); + handler(&webview_id, http_request, RequestAsyncResponder { responder }); } else { request.finish_error(&mut glib::Error::new( glib::FileError::Exist, diff --git a/src/webview2/mod.rs b/src/webview2/mod.rs index 042aef4d3..907292417 100644 --- a/src/webview2/mod.rs +++ b/src/webview2/mod.rs @@ -50,7 +50,7 @@ impl From for Error { } pub(crate) struct InnerWebView { - id: Option, + id: String, parent: RefCell, hwnd: HWND, is_child: bool, @@ -115,13 +115,17 @@ impl InnerWebView { let drop_handler = attributes.drag_drop_handler.take(); let bounds = attributes.bounds; - let id = attributes.id.map(|id| id.to_string()); + let id = attributes + .id + .map(|id| id.to_string()) + .unwrap_or_else(|| (hwnd.0 as isize).to_string()); let env = Self::create_environment(&attributes, pl_attrs.clone())?; let controller = Self::create_controller(hwnd, &env, attributes.incognito)?; let webview = Self::init_webview( parent, hwnd, + id.clone(), attributes, &env, &controller, @@ -371,6 +375,7 @@ impl InnerWebView { fn init_webview( parent: HWND, hwnd: HWND, + webview_id: String, mut attributes: WebViewAttributes, env: &ICoreWebView2Environment, controller: &ICoreWebView2Controller, @@ -432,6 +437,7 @@ impl InnerWebView { &webview, env, hwnd, + webview_id, scheme, &mut attributes, &mut token, @@ -785,6 +791,7 @@ impl InnerWebView { webview: &ICoreWebView2, env: &ICoreWebView2Environment, hwnd: HWND, + webview_id: String, scheme: &'static str, attributes: &mut WebViewAttributes, token: &mut EventRegistrationToken, @@ -799,7 +806,6 @@ impl InnerWebView { let env = env.clone(); let custom_protocols = std::mem::take(&mut attributes.custom_protocols); let main_thread_id = std::thread::current().id(); - let webview_id = attributes.id.map(|id| id.to_string()); webview.add_WebResourceRequested( &WebResourceRequestedEventHandler::create(Box::new(move |_, args| { @@ -868,7 +874,7 @@ impl InnerWebView { #[cfg(feature = "tracing")] let _span = tracing::info_span!("wry::custom_protocol::call_handler").entered(); custom_protocol_handler( - webview_id.as_deref(), + &webview_id, request, RequestAsyncResponder { responder: async_responder, @@ -1178,8 +1184,8 @@ impl InnerWebView { /// Public APIs impl InnerWebView { - pub fn id(&self) -> Option { - self.id.as_deref() + pub fn id(&self) -> crate::WebViewId { + &self.id } pub fn eval( diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 9f65a0118..ec47ba4ae 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -73,7 +73,7 @@ use http::{ Request, Response as HttpResponse, }; -use self::util::Counter; +use crate::util::Counter; const IPC_MESSAGE_HANDLER_NAME: &str = "ipc"; #[cfg(target_os = "macos")] @@ -82,7 +82,7 @@ const ACCEPT_FIRST_MOUSE: &str = "accept_first_mouse"; const NS_JSON_WRITING_FRAGMENTS_ALLOWED: u64 = 4; static COUNTER: Counter = Counter::new(); -static WEBVIEW_IDS: Lazy>> = Lazy::new(Default::default); +static WEBVIEW_IDS: Lazy>> = Lazy::new(Default::default); #[derive(Debug, Default, Copy, Clone)] pub struct PrintMargin { @@ -98,7 +98,7 @@ pub struct PrintOptions { } pub(crate) struct InnerWebView { - id: Option, + id: String, pub webview: id, pub manager: id, is_child: bool, @@ -114,7 +114,6 @@ pub(crate) struct InnerWebView { download_delegate: id, protocol_ptrs: Vec<*mut Box, Request>, RequestAsyncResponder)>>, - internal_webview_id: u32, } impl InnerWebView { @@ -198,19 +197,17 @@ impl InnerWebView { #[cfg(feature = "tracing")] let span = tracing::info_span!(parent: None, "wry::custom_protocol::handle", uri = tracing::field::Empty) .entered(); - let internal_webview_id = *this.get_ivar::("internal_webview_id"); + + let webview_id_ptr: *mut c_char = *this.get_ivar("id"); + let webview_id = CStr::from_ptr(webview_id_ptr) + .to_str() + .ok() + .unwrap_or_default(); let function = this.get_ivar::<*mut c_void>("function"); if !function.is_null() { let function = &mut *(*function - as *mut Box, Request>, RequestAsyncResponder)>); - - let webview_id_ptr: *mut c_char = *this.get_ivar("id"); - let webview_id = if webview_id_ptr.is_null() { - None - } else { - CStr::from_ptr(webview_id_ptr).to_str().ok() - }; + as *mut Box>, RequestAsyncResponder)>); // Get url request let request: id = msg_send![task, request]; @@ -285,8 +282,8 @@ impl InnerWebView { let responder: Box>)> = Box::new( move |sent_response| { - fn check_webview_id_valid(internal_webview_id: u32) -> crate::Result<()> { - match WEBVIEW_IDS.lock().unwrap().contains(&internal_webview_id) { + fn check_webview_id_valid(webview_id: &str) -> crate::Result<()> { + match WEBVIEW_IDS.lock().unwrap().contains(webview_id) { true => Ok(()), false => Err(crate::Error::CustomProtocolTaskInvalid), } @@ -294,7 +291,7 @@ impl InnerWebView { unsafe fn response( task: id, - internal_webview_id: u32, + webview_id: &str, url: id, /* NSURL */ sent_response: HttpResponse>, ) -> crate::Result<()> { @@ -324,7 +321,7 @@ impl InnerWebView { let urlresponse: id = msg_send![class!(NSHTTPURLResponse), alloc]; let response: id = msg_send![urlresponse, initWithURL:url statusCode: wanted_status_code HTTPVersion:NSString::new(&wanted_version) headerFields:headers]; - check_webview_id_valid(internal_webview_id)?; + check_webview_id_valid(&webview_id)?; (*task) .send_message::<(id,), ()>(sel!(didReceiveResponse:), (response,)) .map_err(|_| crate::Error::CustomProtocolTaskInvalid)?; @@ -334,13 +331,13 @@ impl InnerWebView { let data: id = msg_send![class!(NSData), alloc]; let data: id = msg_send![data, initWithBytesNoCopy:bytes length:content.len() freeWhenDone: if content.len() == 0 { NO } else { YES }]; - check_webview_id_valid(internal_webview_id)?; + check_webview_id_valid(&webview_id)?; (*task) .send_message::<(id,), ()>(sel!(didReceiveData:), (data,)) .map_err(|_| crate::Error::CustomProtocolTaskInvalid)?; // Finish - check_webview_id_valid(internal_webview_id)?; + check_webview_id_valid(&webview_id)?; (*task) .send_message::<(), ()>(sel!(didFinish), ()) .map_err(|_| crate::Error::CustomProtocolTaskInvalid)?; @@ -348,7 +345,7 @@ impl InnerWebView { Ok(()) } - let _ = response(task, internal_webview_id, url, sent_response); + let _ = response(task, &webview_id, url, sent_response); let () = msg_send![task, release]; }, ); @@ -373,9 +370,13 @@ impl InnerWebView { } extern "C" fn stop_task(_: &Object, _: Sel, _webview: id, _task: id) {} + let webview_id = attributes + .id + .map(|id| id.to_string()) + .unwrap_or_else(|| COUNTER.next().to_string()); + let mut wv_ids = WEBVIEW_IDS.lock().unwrap(); - let internal_webview_id = COUNTER.next(); - wv_ids.insert(internal_webview_id); + wv_ids.insert(webview_id); drop(wv_ids); // Safety: objc runtime calls are unsafe @@ -408,15 +409,12 @@ impl InnerWebView { _ => msg_send![class!(WKWebsiteDataStore), defaultDataStore], }; - let id = attributes.id.map(|id| id.to_string()); - for (name, function) in attributes.custom_protocols { let scheme_name = format!("{}URLSchemeHandler", name); let cls = ClassDecl::new(&scheme_name, class!(NSObject)); let cls = match cls { Some(mut cls) => { cls.add_ivar::<*mut c_void>("function"); - cls.add_ivar::("internal_webview_id"); cls.add_ivar::<*mut c_char>("id"); cls.add_method( sel!(webView:startURLSchemeTask:), @@ -435,14 +433,9 @@ impl InnerWebView { protocol_ptrs.push(function); (*handler).set_ivar("function", function as *mut _ as *mut c_void); - (*handler).set_ivar("internal_webview_id", internal_webview_id); (*handler).set_ivar( "id", - if let Some(id) = &id { - CString::new(id.as_bytes()).unwrap().into_raw() - } else { - std::ptr::null_mut() - }, + CString::new(webview_id.as_bytes()).unwrap().into_raw(), ); (*config) @@ -987,7 +980,7 @@ impl InnerWebView { } let w = Self { - id, + id: webview_id, webview, manager, pending_scripts, @@ -1000,7 +993,6 @@ impl InnerWebView { download_delegate, protocol_ptrs, is_child, - internal_webview_id, }; // Initialize scripts @@ -1079,8 +1071,8 @@ r#"Object.defineProperty(window, 'ipc', { } } - pub fn id(&self) -> Option { - self.id.as_deref() + pub fn id(&self) -> crate::WebViewId { + &self.id } pub fn url(&self) -> crate::Result { @@ -1383,10 +1375,7 @@ pub fn platform_webview_version() -> Result { impl Drop for InnerWebView { fn drop(&mut self) { - WEBVIEW_IDS - .lock() - .unwrap() - .remove(&self.internal_webview_id); + WEBVIEW_IDS.lock().unwrap().remove(&self.id); // We need to drop handler closures here unsafe { diff --git a/src/wkwebview/util.rs b/src/wkwebview/util.rs index 8ddfd8a7c..31dfe029b 100644 --- a/src/wkwebview/util.rs +++ b/src/wkwebview/util.rs @@ -3,20 +3,6 @@ // SPDX-License-Identifier: MIT use cocoa::{base::id, foundation::NSOperatingSystemVersion}; -use std::sync::atomic::{AtomicU32, Ordering}; - -pub struct Counter(AtomicU32); - -impl Counter { - #[allow(unused)] - pub const fn new() -> Self { - Self(AtomicU32::new(1)) - } - - pub fn next(&self) -> u32 { - self.0.fetch_add(1, Ordering::Relaxed) - } -} pub fn operating_system_version() -> (u64, u64, u64) { unsafe { From 24750d48a3e98d57e5907f01b5a88534e7257bb8 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 11 Oct 2024 05:11:57 +0300 Subject: [PATCH 06/11] update change file --- .changes/custom-protocol-label.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changes/custom-protocol-label.md b/.changes/custom-protocol-label.md index a77b48b8c..d2edb2d21 100644 --- a/.changes/custom-protocol-label.md +++ b/.changes/custom-protocol-label.md @@ -17,7 +17,7 @@ We also made a few changes to the builder, specficially `WebViewBuilder::new` an - Added `WebViewAttributes.id` field to specify an id for the webview. - Added `WebViewBuilder::with_id` method to specify an id for the webview. - Added `WebViewAttributes.context` field to specify a shared context for the webview. -- **Breaking** Changed `WebViewAttributes.custom_protocols` field,`WebViewBuilder::with_custom_protocol` method and `WebViewBuilder::with_async_custom_protocol` method handler function to take `Option` as the first argument to check which webview made the request to the protocol. +- **Breaking** Changed `WebViewAttributes.custom_protocols` field,`WebViewBuilder::with_custom_protocol` method and `WebViewBuilder::with_async_custom_protocol` method handler function to take `WebViewId` as the first argument to check which webview made the request to the protocol. - **Breaking** Changed `WebViewBuilder::with_web_context` to be a static method to create a builder with a webcontext, instead of it being a setter method. It is now an alternaitve to `WebviewBuilder::new` - Added `WebViewBuilder::with_attributes` to create a webview builder with provided attributes. - **Breaking** Changed `WebViewBuilder::new` to take zero arguments. From a783b31dfb8846b369e5196b635e53ad6d6073dc Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 11 Oct 2024 05:16:34 +0300 Subject: [PATCH 07/11] fix macos --- src/wkwebview/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index ec47ba4ae..a1877ea1b 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -112,8 +112,7 @@ pub(crate) struct InnerWebView { #[cfg(target_os = "macos")] drag_drop_ptr: *mut Box bool>, download_delegate: id, - protocol_ptrs: - Vec<*mut Box, Request>, RequestAsyncResponder)>>, + protocol_ptrs: Vec<*mut Box>, RequestAsyncResponder)>>, } impl InnerWebView { From c6a44eee01b1d97520ddbc1ae0917df1e81d4da9 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 11 Oct 2024 05:23:22 +0300 Subject: [PATCH 08/11] clone --- src/wkwebview/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index a1877ea1b..1aadc0a5b 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -375,7 +375,7 @@ impl InnerWebView { .unwrap_or_else(|| COUNTER.next().to_string()); let mut wv_ids = WEBVIEW_IDS.lock().unwrap(); - wv_ids.insert(webview_id); + wv_ids.insert(webview_id.clone()); drop(wv_ids); // Safety: objc runtime calls are unsafe From eb3df38d1a5ff22e371a1a21996d92ca117c84ef Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 11 Oct 2024 05:27:16 +0300 Subject: [PATCH 09/11] ios --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2974f7bee..ccced7c19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -196,7 +196,7 @@ extern crate objc; mod error; mod proxy; -#[cfg(any(target_os = "macos", target_os = "android"))] +#[cfg(any(target_os = "macos", target_os = "android", target_os = "ios"))] mod util; mod web_context; From 63c38602c5f8c4c3f4dac2af6f4dd28c1f84f2cf Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Fri, 11 Oct 2024 14:31:56 -0300 Subject: [PATCH 10/11] typos --- .changes/custom-protocol-label.md | 18 +++++++++--------- src/web_context.rs | 2 +- src/webview2/drag_drop.rs | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.changes/custom-protocol-label.md b/.changes/custom-protocol-label.md index d2edb2d21..bd0b377e0 100644 --- a/.changes/custom-protocol-label.md +++ b/.changes/custom-protocol-label.md @@ -2,27 +2,27 @@ "wry": "minor" --- -This release contains quite the breaking changes, because even though wry@0.44, ignored duplicate custom protocols, On Linux when using a shared web context, the custom protocol handler can only be registered once so we are bringing the duplicate custom protocols on Linux again, Windows and macOS are not affected. If using a shared web context, make sure to register a protocol only once on Linux (other platforms should be registed multiple times), use `WebContext::is_custom_protocol_registerd` with `#[cfg(target_os = "linux")]`. +This release contains quite the breaking changes, because even though wry@0.44, ignored duplicate custom protocols, On Linux when using a shared web context, the custom protocol handler can only be registered once so we are bringing the duplicate custom protocols on Linux again, Windows and macOS are not affected. If using a shared web context, make sure to register a protocol only once on Linux (other platforms should be registed multiple times), use `WebContext::is_custom_protocol_registered` with `#[cfg(target_os = "linux")]`. -We also noticed that it is hard to know which webview made a request to the custom protocol so we added a method to attach an ID to a webview, and changed relevant custom protocol APIs to take a new argument that passes specifed Id back to protocol handler. +We also noticed that it is hard to know which webview made a request to the custom protocol so we added a method to attach an ID to a webview, and changed relevant custom protocol APIs to take a new argument that passes the specified id back to protocol handler. -We also made a few changes to the builder, specficially `WebViewBuilder::new` and `WebViewBuilder::build` methods to make them more ergonomic to work with. +We also made a few changes to the builder, specifically `WebViewBuilder::new` and `WebViewBuilder::build` methods to make them more ergonomic to work with. - Added `Error::DuplicateCustomProtocol` enum variant. - Added `Error::ContextDuplicateCustomProtocol` enum variant. - On Linux, return an error in `WebViewBuilder::build` if registering a custom protocol multiple times. -- Added `WebContext::is_custom_protocol_registerd` to check if a protocol has been regsterd for this web context. +- Added `WebContext::is_custom_protocol_registered` to check if a protocol has been regsterd for this web context. - Added `WebViewId` alias type. - **Breaking** Changed `WebViewAttributes` to have a lifetime parameter. - Added `WebViewAttributes.id` field to specify an id for the webview. - Added `WebViewBuilder::with_id` method to specify an id for the webview. - Added `WebViewAttributes.context` field to specify a shared context for the webview. -- **Breaking** Changed `WebViewAttributes.custom_protocols` field,`WebViewBuilder::with_custom_protocol` method and `WebViewBuilder::with_async_custom_protocol` method handler function to take `WebViewId` as the first argument to check which webview made the request to the protocol. -- **Breaking** Changed `WebViewBuilder::with_web_context` to be a static method to create a builder with a webcontext, instead of it being a setter method. It is now an alternaitve to `WebviewBuilder::new` +- **Breaking** Changed `WebViewAttributes.custom_protocols` field,`WebViewBuilder::with_custom_protocol` method and `WebViewBuilder::with_asynchronous_custom_protocol` method handler function to take `WebViewId` as the first argument to check which webview made the request to the protocol. +- **Breaking** Changed `WebViewBuilder::with_web_context` to be a static method to create a builder with a webcontext, instead of it being a setter method. It is now an alternative to `WebviewBuilder::new` - Added `WebViewBuilder::with_attributes` to create a webview builder with provided attributes. -- **Breaking** Changed `WebViewBuilder::new` to take zero arguments. +- **Breaking** Changed `WebViewBuilder::new` to take no arguments. - **Breaking** Changed `WebViewBuilder::build` method to take a reference to a window to create the webview in it. - **Breaking** Removed `WebViewBuilder::new_as_child`. -- Added `WebViewBuilder::build_as_child` method to take a reference to a window to create the webview in it. -- **Breaking** Removed `WebViewBuilderExtUnix::new_gtk` to take zero arguments. +- Added `WebViewBuilder::build_as_child` method, which takes a reference to a window to create the webview in it. +- **Breaking** Removed `WebViewBuilderExtUnix::new_gtk`. - Added `WebViewBuilderExtUnix::build_gtk`. diff --git a/src/web_context.rs b/src/web_context.rs index 89610ff14..55735cbd1 100644 --- a/src/web_context.rs +++ b/src/web_context.rs @@ -68,7 +68,7 @@ impl WebContext { } /// Check if a custom protocol has been registered on this context. - pub fn is_custom_protocol_registerd(&self, name: String) -> bool { + pub fn is_custom_protocol_registered(&self, name: String) -> bool { self.custom_protocols.contains(&name) } diff --git a/src/webview2/drag_drop.rs b/src/webview2/drag_drop.rs index 0bdc3f59d..39213ec9a 100644 --- a/src/webview2/drag_drop.rs +++ b/src/webview2/drag_drop.rs @@ -23,7 +23,7 @@ use windows::{ System::{ Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL}, Ole::{ - IDropTarget, IDropTarget_Impl, RegisterDragDrop, RevokeDragDrop, CF_HDROP, DROPEFFECT, + registeredragDrop, IDropTarget, IDropTarget_Impl, RevokeDragDrop, CF_HDROP, DROPEFFECT, DROPEFFECT_COPY, DROPEFFECT_NONE, }, SystemServices::MODIFIERKEYS_FLAGS, @@ -67,7 +67,7 @@ impl DragDropController { fn inject_in_hwnd(&mut self, hwnd: HWND, handler: Rc bool>) -> bool { let drag_drop_target: IDropTarget = DragDropTarget::new(hwnd, handler).into(); if unsafe { RevokeDragDrop(hwnd) } != Err(DRAGDROP_E_INVALIDHWND.into()) - && unsafe { RegisterDragDrop(hwnd, &drag_drop_target) }.is_ok() + && unsafe { registeredragDrop(hwnd, &drag_drop_target) }.is_ok() { self.drop_targets.push(drag_drop_target); } From 6b62e260dfdd56740379db5bbb5510d2fa271bb0 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Fri, 11 Oct 2024 14:46:30 -0300 Subject: [PATCH 11/11] fix replace --- src/webview2/drag_drop.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webview2/drag_drop.rs b/src/webview2/drag_drop.rs index 39213ec9a..0bdc3f59d 100644 --- a/src/webview2/drag_drop.rs +++ b/src/webview2/drag_drop.rs @@ -23,7 +23,7 @@ use windows::{ System::{ Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL}, Ole::{ - registeredragDrop, IDropTarget, IDropTarget_Impl, RevokeDragDrop, CF_HDROP, DROPEFFECT, + IDropTarget, IDropTarget_Impl, RegisterDragDrop, RevokeDragDrop, CF_HDROP, DROPEFFECT, DROPEFFECT_COPY, DROPEFFECT_NONE, }, SystemServices::MODIFIERKEYS_FLAGS, @@ -67,7 +67,7 @@ impl DragDropController { fn inject_in_hwnd(&mut self, hwnd: HWND, handler: Rc bool>) -> bool { let drag_drop_target: IDropTarget = DragDropTarget::new(hwnd, handler).into(); if unsafe { RevokeDragDrop(hwnd) } != Err(DRAGDROP_E_INVALIDHWND.into()) - && unsafe { registeredragDrop(hwnd, &drag_drop_target) }.is_ok() + && unsafe { RegisterDragDrop(hwnd, &drag_drop_target) }.is_ok() { self.drop_targets.push(drag_drop_target); }