diff --git a/.changes/OnBackPressedCallback.md b/.changes/OnBackPressedCallback.md new file mode 100644 index 000000000..4b396213c --- /dev/null +++ b/.changes/OnBackPressedCallback.md @@ -0,0 +1,5 @@ +--- +"wry": patch +--- + +Use OnBackPressedCallback instead of the deprecated onKeyDown for back navigation on Android. diff --git a/.changes/remove-webkitgtk-request-workaround.md b/.changes/remove-webkitgtk-request-workaround.md deleted file mode 100644 index ac32d3375..000000000 --- a/.changes/remove-webkitgtk-request-workaround.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -wry: patch ---- - -On Linux, removed a workaround which forced inital requests for multiple webviews to be handled sequentially. -The workaround was intended to fix a concurrency bug with loading multiple URIs at the same time on WebKitGTK. -But it prevented parallelization and could cause a deadlock in certain situations. -It is no longer needed with newer WebKitGTK versions. diff --git a/.changes/web-content-process-termination.md b/.changes/web-content-process-termination.md new file mode 100644 index 000000000..4e298a07a --- /dev/null +++ b/.changes/web-content-process-termination.md @@ -0,0 +1,5 @@ +--- +"wry": minor +--- + +Add handler for web content process termination. diff --git a/CHANGELOG.md b/CHANGELOG.md index d36485e9e..11ed2c6c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## \[0.53.5] + +- [#1622](https://github.com/tauri-apps/wry/pull/1626) Fixed an issue that caused docs.rs builds to fail. No user facing changes. + +## \[0.53.4] + +- [`093856a`](https://github.com/tauri-apps/wry/commit/093856a2a53a6fc1aaa759e048c7e1fe31bb09fa) ([#1622](https://github.com/tauri-apps/wry/pull/1622) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Add flag to opt out of automatic back navigation handling on Android via `WryActivity#handleBackNavigation`. +- [`0f51d67`](https://github.com/tauri-apps/wry/commit/0f51d67485d84fd9c72391379a67567eea3cbbfe) ([#1605](https://github.com/tauri-apps/wry/pull/1605) by [@dgerhardt](https://github.com/tauri-apps/wry/../../dgerhardt)) On Linux, removed a workaround which forced inital requests for multiple webviews to be handled sequentially. + The workaround was intended to fix a concurrency bug with loading multiple URIs at the same time on WebKitGTK. + But it prevented parallelization and could cause a deadlock in certain situations. + It is no longer needed with newer WebKitGTK versions. + ## \[0.53.3] - [`6aa5854`](https://github.com/tauri-apps/wry/commit/6aa5854b0371a4828638cd722e18d9b1ab235a8b) ([#1609](https://github.com/tauri-apps/wry/pull/1609) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Enhance error handling of the `webview_version` function on macOS. diff --git a/Cargo.lock b/Cargo.lock index 2a8661377..c250fb04b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4128,7 +4128,7 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "wry" -version = "0.53.3" +version = "0.53.5" dependencies = [ "base64", "block2 0.6.0", diff --git a/Cargo.toml b/Cargo.toml index 9cef321d7..5b6cd126c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ workspace = {} [package] name = "wry" -version = "0.53.3" +version = "0.53.5" authors = ["Tauri Programme within The Commons Conservancy"] edition = "2021" license = "Apache-2.0 OR MIT" @@ -22,8 +22,6 @@ targets = [ "x86_64-pc-windows-msvc", "x86_64-apple-darwin", ] -rustc-args = ["--cfg", "docsrs"] -rustdoc-args = ["--cfg", "docsrs"] [features] default = ["drag-drop", "protocol", "os-webview", "x11"] diff --git a/README.md b/README.md index 4b80c2be6..a0284e7e2 100644 --- a/README.md +++ b/README.md @@ -189,24 +189,8 @@ sudo dnf install gtk3-devel webkit2gtk4.1-devel ###### Nix & NixOS -```nix -# shell.nix - -let - # Unstable Channel | Rolling Release - pkgs = import (fetchTarball("channel:nixpkgs-unstable")) { }; - packages = with pkgs; [ - pkg-config - webkitgtk_4_1 - ]; - in - pkgs.mkShell { - buildInputs = packages; - } -``` - ```sh -nix-shell shell.nix +nix-shell -p pkg-config webkitgtk_4_1 ``` ###### GUIX diff --git a/examples/simple.rs b/examples/simple.rs index 6b3657338..832eb9fae 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -14,7 +14,7 @@ fn main() -> wry::Result<()> { let window = WindowBuilder::new().build(&event_loop).unwrap(); let builder = WebViewBuilder::new() - .with_url("http://tauri.app") + .with_url("https://webrtc.github.io/samples/src/content/getusermedia/getdisplaymedia/") .with_new_window_req_handler(|url, features| { println!("new window req: {url} {features:?}"); wry::NewWindowResponse::Allow @@ -39,11 +39,18 @@ fn main() -> wry::Result<()> { #[cfg(any( target_os = "windows", - target_os = "macos", target_os = "ios", target_os = "android" ))] let _webview = builder.build(&window)?; + #[cfg(target_os = "macos")] + let _webview = { + use wry::WebViewBuilderExtMacos; + builder.with_display_capture_decision_handler(|_capture_type| { + wry::WKDisplayCapturePermissionDecision::ScreenPrompt + }).build(&window)? + }; + #[cfg(not(any( target_os = "windows", target_os = "macos", @@ -57,6 +64,14 @@ fn main() -> wry::Result<()> { builder.build_gtk(vbox)? }; + #[cfg(target_os = "macos")] + { + use wry::WebViewExtMacOS; + _webview.set_display_capture_decision_handler(|_capture_type| { + wry::WKDisplayCapturePermissionDecision::ScreenPrompt + }); + } + event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; diff --git a/src/android/kotlin/WryActivity.kt b/src/android/kotlin/WryActivity.kt index 58b355d85..73291e44f 100644 --- a/src/android/kotlin/WryActivity.kt +++ b/src/android/kotlin/WryActivity.kt @@ -10,15 +10,33 @@ import android.os.Build import android.os.Bundle import android.webkit.WebView import android.view.KeyEvent +import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity abstract class WryActivity : AppCompatActivity() { private lateinit var mWebView: RustWebView + open val handleBackNavigation: Boolean = true open fun onWebViewCreate(webView: WebView) { } fun setWebView(webView: RustWebView) { mWebView = webView + + if (handleBackNavigation) { + val callback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (this@WryActivity.mWebView.canGoBack()) { + this@WryActivity.mWebView.goBack() + } else { + this.isEnabled = false + this@WryActivity.onBackPressed() + this.isEnabled = true + } + } + } + onBackPressedDispatcher.addCallback(this, callback) + } + onWebViewCreate(webView) } @@ -101,14 +119,6 @@ abstract class WryActivity : AppCompatActivity() { memory() } - override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { - if (keyCode == KeyEvent.KEYCODE_BACK && mWebView.canGoBack()) { - mWebView.goBack() - return true - } - return super.onKeyDown(keyCode, event) - } - fun getAppClass(name: String): Class<*> { return Class.forName(name) } diff --git a/src/lib.rs b/src/lib.rs index 77a54ccd8..81ceae966 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -379,6 +379,8 @@ use webkitgtk::*; use objc2::rc::Retained; #[cfg(target_os = "macos")] use objc2_app_kit::NSWindow; +#[cfg(target_os = "macos")] +use objc2_web_kit::WKMediaCaptureType; #[cfg(any(target_os = "macos", target_os = "ios"))] use objc2_web_kit::WKUserContentController; #[cfg(any(target_os = "macos", target_os = "ios"))] @@ -387,6 +389,8 @@ pub(crate) mod wkwebview; use wkwebview::*; #[cfg(any(target_os = "macos", target_os = "ios"))] pub use wkwebview::{PrintMargin, PrintOptions, WryWebView}; +#[cfg(target_os = "macos")] +pub use wkwebview::WKDisplayCapturePermissionDecision; #[cfg(target_os = "windows")] pub(crate) mod webview2; @@ -1462,12 +1466,19 @@ pub(crate) struct PlatformSpecificWebViewAttributes { data_store_identifier: Option<[u8; 16]>, traffic_light_inset: Option, allow_link_preview: bool, + on_web_content_process_terminate_handler: Option>, #[cfg(target_os = "ios")] input_accessory_view_builder: Option>, #[cfg(target_os = "ios")] limit_navigations_to_app_bound_domains: bool, #[cfg(target_os = "macos")] webview_configuration: Option>, + #[cfg(target_os = "macos")] + /// A closure that make the permission decision for display capture (e.g. getDisplayMedia()) requests. + /// + /// Only available on macOS 13+. + pub display_capture_decision_handler: + Option WKDisplayCapturePermissionDecision>>, } #[cfg(any(target_os = "macos", target_os = "ios"))] @@ -1478,12 +1489,15 @@ impl Default for PlatformSpecificWebViewAttributes { traffic_light_inset: None, // platform default for this is true allow_link_preview: true, + on_web_content_process_terminate_handler: None, #[cfg(target_os = "ios")] input_accessory_view_builder: None, #[cfg(target_os = "ios")] limit_navigations_to_app_bound_domains: false, #[cfg(target_os = "macos")] webview_configuration: None, + #[cfg(target_os = "macos")] + display_capture_decision_handler: Some(Box::new(|_| WKDisplayCapturePermissionDecision::ScreenPrompt)), } } } @@ -1509,6 +1523,8 @@ pub trait WebViewBuilderExtDarwin { /// /// See https://developer.apple.com/documentation/webkit/wkwebview/allowslinkpreview fn with_allow_link_preview(self, allow_link_preview: bool) -> Self; + /// Set a handler closure to respond to web content process termination. Available on macOS and iOS only. + fn with_on_web_content_process_terminate_handler(self, handler: impl Fn() + 'static) -> Self; } #[cfg(any(target_os = "macos", target_os = "ios"))] @@ -1527,6 +1543,13 @@ impl WebViewBuilderExtDarwin for WebViewBuilder<'_> { self.platform_specific.allow_link_preview = allow_link_preview; self } + + fn with_on_web_content_process_terminate_handler(mut self, handler: impl Fn() + 'static) -> Self { + self + .platform_specific + .on_web_content_process_terminate_handler = Some(Box::new(handler)); + self + } } #[cfg(target_os = "macos")] @@ -1536,6 +1559,10 @@ pub trait WebViewBuilderExtMacos { self, configuration: Retained, ) -> Self; + + fn with_display_capture_decision_handler(self, handler: F) -> Self + where + F: Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision + 'static; } #[cfg(target_os = "macos")] @@ -1550,6 +1577,14 @@ impl WebViewBuilderExtMacos for WebViewBuilder<'_> { .replace(configuration); self } + + fn with_display_capture_decision_handler(mut self, handler: F) -> Self + where + F: Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision + 'static, + { + self.platform_specific.display_capture_decision_handler = Some(Box::new(handler)); + self + } } #[cfg(target_os = "ios")] @@ -2362,6 +2397,10 @@ pub trait WebViewExtMacOS { /// Warning: Do not use this if your chosen window library does not support traffic light insets. /// Warning: Only use this in **decorated** windows with a **hidden titlebar**! fn set_traffic_light_inset>(&self, position: P) -> Result<()>; + /// Set display capture decision handler to decide if incoming display capture request is allowed and its target. + fn set_display_capture_decision_handler(&self, handler: F) + where + F: Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision + 'static; } #[cfg(target_os = "macos")] @@ -2389,6 +2428,13 @@ impl WebViewExtMacOS for WebView { fn set_traffic_light_inset>(&self, position: P) -> Result<()> { self.webview.set_traffic_light_inset(position.into()) } + + fn set_display_capture_decision_handler(&self, handler: F) + where + F: Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision + 'static, + { + self.webview.set_display_capture_decision_handler(handler); + } } /// Additional methods on `WebView` that are specific to iOS. diff --git a/src/webview2/mod.rs b/src/webview2/mod.rs index a2f5d5b99..d24f813ca 100644 --- a/src/webview2/mod.rs +++ b/src/webview2/mod.rs @@ -1351,7 +1351,7 @@ impl InnerWebView { /// Public APIs impl InnerWebView { - pub fn id(&self) -> crate::WebViewId { + pub fn id(&self) -> crate::WebViewId<'_> { &self.id } diff --git a/src/wkwebview/class/wry_navigation_delegate.rs b/src/wkwebview/class/wry_navigation_delegate.rs index 5919949b6..3325fafc8 100644 --- a/src/wkwebview/class/wry_navigation_delegate.rs +++ b/src/wkwebview/class/wry_navigation_delegate.rs @@ -22,6 +22,7 @@ use crate::{ download::{navigation_download_action, navigation_download_response}, navigation::{ did_commit_navigation, did_finish_navigation, navigation_policy, navigation_policy_response, + web_content_process_did_terminate, }, }, PageLoadEvent, WryWebView, @@ -35,6 +36,7 @@ pub struct WryNavigationDelegateIvars { pub navigation_policy_function: Box bool>, pub download_delegate: Option>, pub on_page_load_handler: Option>, + pub on_web_content_process_terminate_handler: Option>, } define_class!( @@ -96,6 +98,11 @@ define_class!( ) { navigation_download_response(self, webview, response, download); } + + #[unsafe(method(webViewWebContentProcessDidTerminate:))] + fn web_content_process_did_terminate(&self, webview: &WKWebView) { + web_content_process_did_terminate(self, webview); + } } ); @@ -108,6 +115,7 @@ impl WryNavigationDelegate { navigation_handler: Option bool>>, download_delegate: Option>, on_page_load_handler: Option>, + on_web_content_process_terminate_handler: Option>, mtm: MainThreadMarker, ) -> Retained { let navigation_policy_function = Box::new(move |url: String| -> bool { @@ -125,6 +133,16 @@ impl WryNavigationDelegate { None }; + let on_web_content_process_terminate_handler = + if let Some(handler) = on_web_content_process_terminate_handler { + let custom_handler = Box::new(move || { + handler(); + }) as Box; + Some(custom_handler) + } else { + None + }; + let delegate = mtm .alloc::() .set_ivars(WryNavigationDelegateIvars { @@ -133,6 +151,7 @@ impl WryNavigationDelegate { has_download_handler, download_delegate, on_page_load_handler, + on_web_content_process_terminate_handler, }); unsafe { msg_send![super(delegate), init] } diff --git a/src/wkwebview/class/wry_web_view_ui_delegate.rs b/src/wkwebview/class/wry_web_view_ui_delegate.rs index 223fcadcc..850f4eb81 100644 --- a/src/wkwebview/class/wry_web_view_ui_delegate.rs +++ b/src/wkwebview/class/wry_web_view_ui_delegate.rs @@ -21,7 +21,7 @@ use objc2_web_kit::{ WKFrameInfo, WKMediaCaptureType, WKPermissionDecision, WKSecurityOrigin, WKUIDelegate, }; -use crate::{NewWindowFeatures, NewWindowResponse, WryWebView}; +use crate::{NewWindowFeatures, NewWindowResponse, WKDisplayCapturePermissionDecision, WryWebView}; #[cfg(target_os = "macos")] struct NewWindow { @@ -86,6 +86,8 @@ pub struct WryWebViewUIDelegateIvars { Option NewWindowResponse + Send + Sync>>, #[cfg(target_os = "macos")] new_windows: Rc>>, + #[cfg(target_os = "macos")] + pub(crate) display_capture_decision_handler: RefCell WKDisplayCapturePermissionDecision + 'static>>>, } define_class!( @@ -263,6 +265,27 @@ define_class!( } } } + + impl WryWebViewUIDelegate { + // This might fail to compile on macOS 12 and below. + #[unsafe(method(_webView:requestDisplayCapturePermissionForOrigin:initiatedByFrame:withSystemAudio:decisionHandler:))] + fn request_display_capture_permission( + &self, + _webview: &WryWebView, + _origin: &WKSecurityOrigin, + _frame: &WKFrameInfo, + capture_type: WKMediaCaptureType, + decision_handler: &Block, + ) { + let function = &self.ivars().display_capture_decision_handler; + if let Some(function) = &*function.borrow() { + let decision = (*function)(capture_type); + (*decision_handler).call((decision,)); + } else { + (*decision_handler).call((WKDisplayCapturePermissionDecision::Deny,)); + } + } + } ); impl WryWebViewUIDelegate { @@ -271,6 +294,9 @@ impl WryWebViewUIDelegate { new_window_req_handler: Option< Box NewWindowResponse + Send + Sync>, >, + display_capture_decision_handler: Option< + Box WKDisplayCapturePermissionDecision> + >, ) -> Retained { #[cfg(target_os = "ios")] let _new_window_req_handler = new_window_req_handler; @@ -282,6 +308,8 @@ impl WryWebViewUIDelegate { new_window_req_handler, #[cfg(target_os = "macos")] new_windows: Rc::new(RefCell::new(vec![])), + #[cfg(target_os = "macos")] + display_capture_decision_handler: RefCell::new(display_capture_decision_handler), }); unsafe { msg_send![super(delegate), init] } } diff --git a/src/wkwebview/display_capture.rs b/src/wkwebview/display_capture.rs new file mode 100644 index 000000000..cfcfdf7ec --- /dev/null +++ b/src/wkwebview/display_capture.rs @@ -0,0 +1,42 @@ +use crate::wkwebview::class::wry_web_view_ui_delegate::WryWebViewUIDelegate; +use crate::wkwebview::util::operating_system_version; +use crate::WryWebView; +use objc2::{msg_send, DefinedClass, Encode, Encoding}; +use objc2_web_kit::WKMediaCaptureType; + +#[repr(isize)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum WKDisplayCapturePermissionDecision { + Deny = 0, + ScreenPrompt, + WindowPrompt, +} + +unsafe impl Encode for WKDisplayCapturePermissionDecision { + const ENCODING: Encoding = isize::ENCODING; +} + +impl From for WKDisplayCapturePermissionDecision { + fn from(value: isize) -> Self { + match value { + 0 => WKDisplayCapturePermissionDecision::Deny, + 1 => WKDisplayCapturePermissionDecision::ScreenPrompt, + 2 => WKDisplayCapturePermissionDecision::WindowPrompt, + _ => panic!("Invalid WKDisplayCapturePermissionDecision value"), + } + } +} + +#[cfg(target_os = "macos")] +pub(crate) fn set_decision_handler( + webview: &WryWebView, + handler: Option WKDisplayCapturePermissionDecision + 'static>>, +) { + #[cfg(target_os = "macos")] + if operating_system_version().0 >= 13 { + if let Some(handler) = handler { + let ui_delegate: &WryWebViewUIDelegate = unsafe { msg_send![webview, UIDelegate] }; + ui_delegate.ivars().display_capture_decision_handler.replace(Some(handler)); + } + } +} diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 0bbf6711a..3843addbc 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +mod display_capture; mod download; #[cfg(target_os = "macos")] mod drag_drop; @@ -63,16 +64,13 @@ use once_cell::sync::Lazy; #[cfg(target_os = "ios")] use crate::wkwebview::ios::WKWebView::WKWebView; -#[cfg(target_os = "ios")] -use crate::wkwebview::util::operating_system_version; #[cfg(target_os = "macos")] use objc2_web_kit::WKWebView; +#[cfg(target_os = "macos")] +pub use display_capture::{WKDisplayCapturePermissionDecision}; -use objc2_web_kit::{ - WKAudiovisualMediaTypes, WKInactiveSchedulingPolicy, WKURLSchemeHandler, WKUserContentController, - WKUserScript, WKUserScriptInjectionTime, WKWebViewConfiguration, WKWebsiteDataStore, -}; +use objc2_web_kit::{WKAudiovisualMediaTypes, WKInactiveSchedulingPolicy, WKMediaCaptureType, WKURLSchemeHandler, WKUserContentController, WKUserScript, WKUserScriptInjectionTime, WKWebViewConfiguration, WKWebsiteDataStore}; use raw_window_handle::{HasWindowHandle, RawWindowHandle}; use std::{ @@ -104,6 +102,7 @@ use crate::{ use http::Request; use crate::util::Counter; +use crate::wkwebview::util::operating_system_version; static COUNTER: Counter = Counter::new(); @@ -542,14 +541,21 @@ impl InnerWebView { attributes.navigation_handler, download_delegate.clone(), attributes.on_page_load_handler, + pl_attrs.on_web_content_process_terminate_handler, mtm, ); let proto_navigation_policy_delegate = ProtocolObject::from_ref(&*navigation_policy_delegate); webview.setNavigationDelegate(Some(proto_navigation_policy_delegate)); + + #[cfg(target_os = "macos")] + let display_capture_decision_handler = pl_attrs.display_capture_decision_handler + .filter(|_| operating_system_version().0 >= 13); + #[cfg(not(target_os = "macos"))] + let display_capture_decision_handler = None; let ui_delegate: Retained = - WryWebViewUIDelegate::new(mtm, attributes.new_window_req_handler); + WryWebViewUIDelegate::new(mtm, attributes.new_window_req_handler, display_capture_decision_handler); let proto_ui_delegate = ProtocolObject::from_ref(&*ui_delegate); webview.setUIDelegate(Some(proto_ui_delegate)); @@ -1238,6 +1244,14 @@ r#"Object.defineProperty(window, 'ipc', { WKWebsiteDataStore::removeDataStoreForIdentifier_completionHandler(&identifier, &block, mtm); } } + + #[cfg(target_os = "macos")] + pub fn set_display_capture_decision_handler(&self, handler: F) + where + F: Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision + 'static, + { + display_capture::set_decision_handler(&self.webview, Some(Box::new(handler))); + } } pub fn url_from_webview(webview: &WKWebView) -> Result { diff --git a/src/wkwebview/navigation.rs b/src/wkwebview/navigation.rs index 358353697..9dfeebb1a 100644 --- a/src/wkwebview/navigation.rs +++ b/src/wkwebview/navigation.rs @@ -102,3 +102,14 @@ pub(crate) fn navigation_policy_response( (*handler).call((WKNavigationResponsePolicy::Allow,)); } } + +pub(crate) fn web_content_process_did_terminate( + this: &WryNavigationDelegate, + _webview: &WKWebView, +) { + if let Some(on_web_content_process_terminate) = + &this.ivars().on_web_content_process_terminate_handler + { + on_web_content_process_terminate(); + } +}