mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-01-31 00:35:19 +01:00
feat: add WebviewBuilder::on_new_window and WebviewBuilder::on_document_title_changed (#13876)
* add "new window" and "document title changed" webview handler * take document title changed handler * update example, add missing api, change files * allow creating tauri window for the window.open call * set size and position, fix linux, example * enhance document title change * fix windows deadlock * wry 0.53 * update wry --------- Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
7
.changes/on-document-title-changed.md
Normal file
7
.changes/on-document-title-changed.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"tauri": minor:feat
|
||||
"tauri-runtime": minor:feat
|
||||
"tauri-runtime-wry": minor:feat
|
||||
---
|
||||
|
||||
Added `WebviewBuilder::on_document_title_changed` and `WebviewWindowBuilder::on_document_title_changed`.
|
||||
7
.changes/on_new_window.md
Normal file
7
.changes/on_new_window.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"tauri": minor:feat
|
||||
"tauri-runtime": minor:feat
|
||||
"tauri-runtime-wry": minor:feat
|
||||
---
|
||||
|
||||
Added `WebviewBuilder::on_new_window` and `WebviewWindowBuilder::on_new_window`.
|
||||
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -1319,7 +1319,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4311,7 +4311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5816,7 +5816,7 @@ dependencies = [
|
||||
"aes-gcm",
|
||||
"aes-kw",
|
||||
"argon2",
|
||||
"base64 0.21.7",
|
||||
"base64 0.22.1",
|
||||
"bitfield",
|
||||
"block-padding",
|
||||
"blowfish",
|
||||
@@ -8857,12 +8857,15 @@ dependencies = [
|
||||
"jni 0.21.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-ui-kit",
|
||||
"objc2-web-kit",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.12",
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows 0.61.1",
|
||||
]
|
||||
|
||||
@@ -10307,7 +10310,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10939,14 +10942,15 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.52.1"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12a714d9ba7075aae04a6e50229d6109e3d584774b99a6a8c60de1698ca111b9"
|
||||
checksum = "5698e50a589268aec06d2219f48b143222f7b5ad9aa690118b8dce0a8dcac574"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2 0.6.0",
|
||||
"cookie",
|
||||
"crossbeam-channel",
|
||||
"dirs 6.0.0",
|
||||
"dpi",
|
||||
"dunce",
|
||||
"gdkx11",
|
||||
|
||||
@@ -17,7 +17,7 @@ rustc-args = ["--cfg", "docsrs"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
wry = { version = "0.52", default-features = false, features = [
|
||||
wry = { version = "0.53.1", default-features = false, features = [
|
||||
"drag-drop",
|
||||
"protocol",
|
||||
"os-webview",
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
use self::monitor::MonitorExt;
|
||||
use http::Request;
|
||||
#[cfg(target_os = "macos")]
|
||||
use objc2::ClassType;
|
||||
use raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle};
|
||||
|
||||
use tauri_runtime::{
|
||||
@@ -43,6 +45,8 @@ use webview2_com::{ContainsFullScreenElementChangedEventHandler, FocusChangedEve
|
||||
use windows::Win32::Foundation::HWND;
|
||||
#[cfg(target_os = "ios")]
|
||||
use wry::WebViewBuilderExtIos;
|
||||
#[cfg(target_os = "macos")]
|
||||
use wry::WebViewBuilderExtMacos;
|
||||
#[cfg(windows)]
|
||||
use wry::WebViewBuilderExtWindows;
|
||||
#[cfg(target_vendor = "apple")]
|
||||
@@ -3777,6 +3781,7 @@ fn handle_user_message<T: UserEvent>(
|
||||
{
|
||||
f(Webview {
|
||||
controller: webview.controller(),
|
||||
environment: webview.environment(),
|
||||
});
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
@@ -3822,9 +3827,13 @@ fn handle_user_message<T: UserEvent>(
|
||||
}
|
||||
}
|
||||
Message::CreateWindow(window_id, handler) => match handler(event_loop) {
|
||||
Ok(webview) => {
|
||||
windows.0.borrow_mut().insert(window_id, webview);
|
||||
}
|
||||
// wait for borrow_mut to be available - on Windows we might poll for the window to be inserted
|
||||
Ok(webview) => loop {
|
||||
if let Ok(mut windows) = windows.0.try_borrow_mut() {
|
||||
windows.insert(window_id, webview);
|
||||
break;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("{e}");
|
||||
}
|
||||
@@ -4510,6 +4519,11 @@ You may have it installed on another user account, but it is not available for t
|
||||
.with_clipboard(webview_attributes.clipboard)
|
||||
.with_hotkeys_zoom(webview_attributes.zoom_hotkeys_enabled);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Some(webview_configuration) = webview_attributes.webview_configuration {
|
||||
webview_builder = webview_builder.with_webview_configuration(webview_configuration);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "android"))]
|
||||
{
|
||||
webview_builder = webview_builder.with_https_scheme(webview_attributes.use_https_scheme);
|
||||
@@ -4583,6 +4597,75 @@ You may have it installed on another user account, but it is not available for t
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(new_window_handler) = pending.new_window_handler {
|
||||
#[cfg(desktop)]
|
||||
let context = context.clone();
|
||||
webview_builder = webview_builder.with_new_window_req_handler(move |url, features| {
|
||||
url
|
||||
.parse()
|
||||
.map(|url| {
|
||||
let response = new_window_handler(
|
||||
url,
|
||||
tauri_runtime::webview::NewWindowFeatures::new(
|
||||
features.size,
|
||||
features.position,
|
||||
tauri_runtime::webview::NewWindowOpener {
|
||||
#[cfg(desktop)]
|
||||
webview: features.opener.webview,
|
||||
#[cfg(windows)]
|
||||
environment: features.opener.environment,
|
||||
#[cfg(target_os = "macos")]
|
||||
target_configuration: features.opener.target_configuration,
|
||||
},
|
||||
),
|
||||
);
|
||||
match response {
|
||||
tauri_runtime::webview::NewWindowResponse::Allow => wry::NewWindowResponse::Allow,
|
||||
#[cfg(desktop)]
|
||||
tauri_runtime::webview::NewWindowResponse::Create { window_id } => {
|
||||
let windows = &context.main_thread.windows.0;
|
||||
let webview = loop {
|
||||
if let Some(webview) = windows.try_borrow().ok().and_then(|windows| {
|
||||
windows
|
||||
.get(&window_id)
|
||||
.map(|window| window.webviews.first().unwrap().clone())
|
||||
}) {
|
||||
break webview;
|
||||
} else {
|
||||
// on Windows the window is created async so we should wait for it to be available
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
continue;
|
||||
};
|
||||
};
|
||||
|
||||
#[cfg(desktop)]
|
||||
wry::NewWindowResponse::Create {
|
||||
#[cfg(target_os = "macos")]
|
||||
webview: wry::WebViewExtMacOS::webview(&*webview).as_super().into(),
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
))]
|
||||
webview: webview.webview(),
|
||||
#[cfg(windows)]
|
||||
webview: webview.webview(),
|
||||
}
|
||||
}
|
||||
tauri_runtime::webview::NewWindowResponse::Deny => wry::NewWindowResponse::Deny,
|
||||
}
|
||||
})
|
||||
.unwrap_or(wry::NewWindowResponse::Deny)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(document_title_changed_handler) = pending.document_title_changed_handler {
|
||||
webview_builder =
|
||||
webview_builder.with_document_title_changed_handler(document_title_changed_handler)
|
||||
}
|
||||
|
||||
let webview_bounds = if let Some(bounds) = webview_attributes.bounds {
|
||||
let bounds: RectWrapper = bounds.into();
|
||||
let bounds = bounds.0;
|
||||
@@ -4672,6 +4755,10 @@ You may have it installed on another user account, but it is not available for t
|
||||
webview_builder = webview_builder.with_additional_browser_args(&additional_browser_args);
|
||||
}
|
||||
|
||||
if let Some(environment) = webview_attributes.environment {
|
||||
webview_builder = webview_builder.with_environment(environment);
|
||||
}
|
||||
|
||||
webview_builder = webview_builder.with_theme(match window.theme() {
|
||||
TaoTheme::Dark => wry::Theme::Dark,
|
||||
TaoTheme::Light => wry::Theme::Light,
|
||||
@@ -4699,6 +4786,19 @@ You may have it installed on another user account, but it is not available for t
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
{
|
||||
if let Some(related_view) = webview_attributes.related_view {
|
||||
webview_builder = webview_builder.with_related_view(related_view);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
{
|
||||
if let Some(data_store_identifier) = &webview_attributes.data_store_identifier {
|
||||
|
||||
@@ -29,9 +29,12 @@ mod imp {
|
||||
|
||||
#[cfg(windows)]
|
||||
mod imp {
|
||||
use webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Controller;
|
||||
use webview2_com::Microsoft::Web::WebView2::Win32::{
|
||||
ICoreWebView2Controller, ICoreWebView2Environment,
|
||||
};
|
||||
pub struct Webview {
|
||||
pub controller: ICoreWebView2Controller,
|
||||
pub environment: ICoreWebView2Environment,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,12 @@ cookie = "0.18"
|
||||
version = "0.61"
|
||||
features = ["Win32_Foundation", "Win32_System_WinRT"]
|
||||
|
||||
[target."cfg(windows)".dependencies]
|
||||
webview2-com = "0.38"
|
||||
|
||||
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
||||
gtk = { version = "0.18", features = ["v3_24"] }
|
||||
webkit2gtk = { version = "=2.0", features = ["v2_40"] }
|
||||
|
||||
[target."cfg(target_os = \"android\")".dependencies]
|
||||
jni = "0.21"
|
||||
@@ -56,6 +60,8 @@ objc2-ui-kit = { version = "0.3.0", default-features = false, features = [
|
||||
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
url = "2"
|
||||
objc2 = "0.6"
|
||||
objc2-web-kit = { version = "0.3", features = ["objc2-app-kit", "WKWebView"] }
|
||||
|
||||
[features]
|
||||
devtools = []
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
//! A layer between raw [`Runtime`] webviews and Tauri.
|
||||
//!
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::window::WindowId;
|
||||
use crate::{window::is_label_valid, Rect, Runtime, UserEvent};
|
||||
|
||||
use http::Request;
|
||||
@@ -30,8 +32,12 @@ type WebResourceRequestHandler =
|
||||
|
||||
type NavigationHandler = dyn Fn(&Url) -> bool + Send;
|
||||
|
||||
type NewWindowHandler = dyn Fn(Url, NewWindowFeatures) -> NewWindowResponse + Send + Sync;
|
||||
|
||||
type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send;
|
||||
|
||||
type DocumentTitleChangedHandler = dyn Fn(String) + Send + 'static;
|
||||
|
||||
type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync;
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
@@ -78,6 +84,92 @@ pub enum PageLoadEvent {
|
||||
Finished,
|
||||
}
|
||||
|
||||
/// Information about the webview that initiated a new window request.
|
||||
#[derive(Debug)]
|
||||
pub struct NewWindowOpener {
|
||||
/// The instance of the webview that initiated the new window request.
|
||||
///
|
||||
/// This must be set as the related view of the new webview. See [`WebviewAttributes::related_view`].
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
))]
|
||||
pub webview: webkit2gtk::WebView,
|
||||
/// The instance of the webview that initiated the new window request.
|
||||
///
|
||||
/// The target webview environment **MUST** match the environment of the opener webview. See [`WebviewAttributes::environment`].
|
||||
#[cfg(windows)]
|
||||
pub webview: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2,
|
||||
#[cfg(windows)]
|
||||
pub environment: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment,
|
||||
/// The instance of the webview that initiated the new window request.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub webview: objc2::rc::Retained<objc2_web_kit::WKWebView>,
|
||||
/// Configuration of the target webview.
|
||||
///
|
||||
/// This **MUST** be used when creating the target webview. See [`WebviewAttributes::webview_configuration`].
|
||||
#[cfg(target_os = "macos")]
|
||||
pub target_configuration: objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>,
|
||||
}
|
||||
|
||||
/// Window features of a window requested to open.
|
||||
#[derive(Debug)]
|
||||
pub struct NewWindowFeatures {
|
||||
pub(crate) size: Option<crate::dpi::LogicalSize<f64>>,
|
||||
pub(crate) position: Option<crate::dpi::LogicalPosition<f64>>,
|
||||
pub(crate) opener: NewWindowOpener,
|
||||
}
|
||||
|
||||
impl NewWindowFeatures {
|
||||
pub fn new(
|
||||
size: Option<crate::dpi::LogicalSize<f64>>,
|
||||
position: Option<crate::dpi::LogicalPosition<f64>>,
|
||||
opener: NewWindowOpener,
|
||||
) -> Self {
|
||||
Self {
|
||||
size,
|
||||
position,
|
||||
opener,
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies the size of the content area
|
||||
/// as defined by the user's operating system where the new window will be generated.
|
||||
pub fn size(&self) -> Option<crate::dpi::LogicalSize<f64>> {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Specifies the position of the window relative to the work area
|
||||
/// as defined by the user's operating system where the new window will be generated.
|
||||
pub fn position(&self) -> Option<crate::dpi::LogicalPosition<f64>> {
|
||||
self.position
|
||||
}
|
||||
|
||||
/// Returns information about the webview that initiated a new window request.
|
||||
pub fn opener(&self) -> &NewWindowOpener {
|
||||
&self.opener
|
||||
}
|
||||
}
|
||||
|
||||
/// Response for the new window request handler.
|
||||
pub enum NewWindowResponse {
|
||||
/// Allow the window to be opened with the default implementation.
|
||||
Allow,
|
||||
/// Allow the window to be opened, with the given window.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// **Linux**: The webview must be related to the caller webview. See [`WebviewAttributes::related_view`].
|
||||
/// **Windows**: The webview must use the same environment as the caller webview. See [`WebviewAttributes::environment`].
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Create { window_id: WindowId },
|
||||
/// Deny the window from being opened.
|
||||
Deny,
|
||||
}
|
||||
|
||||
/// A webview that has yet to be built.
|
||||
pub struct PendingWebview<T: UserEvent, R: Runtime<T>> {
|
||||
/// The label that the webview will be named.
|
||||
@@ -94,6 +186,10 @@ pub struct PendingWebview<T: UserEvent, R: Runtime<T>> {
|
||||
/// A handler to decide if incoming url is allowed to navigate.
|
||||
pub navigation_handler: Option<Box<NavigationHandler>>,
|
||||
|
||||
pub new_window_handler: Option<Box<NewWindowHandler>>,
|
||||
|
||||
pub document_title_changed_handler: Option<Box<DocumentTitleChangedHandler>>,
|
||||
|
||||
/// The resolved URL to load on the webview.
|
||||
pub url: String,
|
||||
|
||||
@@ -125,6 +221,8 @@ impl<T: UserEvent, R: Runtime<T>> PendingWebview<T, R> {
|
||||
label,
|
||||
ipc_handler: None,
|
||||
navigation_handler: None,
|
||||
new_window_handler: None,
|
||||
document_title_changed_handler: None,
|
||||
url: "tauri://localhost".to_string(),
|
||||
#[cfg(target_os = "android")]
|
||||
on_webview_created: None,
|
||||
@@ -255,8 +353,30 @@ pub struct WebviewAttributes {
|
||||
/// This relies on [`objc2_ui_kit`] which does not provide a stable API yet, so it can receive breaking changes in minor releases.
|
||||
#[cfg(target_os = "ios")]
|
||||
pub input_accessory_view_builder: Option<InputAccessoryViewBuilder>,
|
||||
|
||||
/// Set the environment for the webview.
|
||||
/// Useful if you need to share the same environment, for instance when using the [`PendingWebview::new_window_handler`].
|
||||
#[cfg(windows)]
|
||||
pub environment: Option<webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment>,
|
||||
|
||||
/// Creates a new webview sharing the same web process with the provided webview.
|
||||
/// Useful if you need to link a webview to another, for instance when using the [`PendingWebview::new_window_handler`].
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
))]
|
||||
pub related_view: Option<webkit2gtk::WebView>,
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub webview_configuration: Option<objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for WebviewAttributes {}
|
||||
unsafe impl Sync for WebviewAttributes {}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
#[non_exhaustive]
|
||||
pub struct InputAccessoryViewBuilder(pub Box<InputAccessoryViewBuilderFn>);
|
||||
@@ -360,6 +480,18 @@ impl WebviewAttributes {
|
||||
allow_link_preview: true,
|
||||
#[cfg(target_os = "ios")]
|
||||
input_accessory_view_builder: None,
|
||||
#[cfg(windows)]
|
||||
environment: None,
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
))]
|
||||
related_view: None,
|
||||
#[cfg(target_os = "macos")]
|
||||
webview_configuration: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -121,6 +121,11 @@ objc2-app-kit = { version = "0.3", default-features = false, features = [
|
||||
"NSWindow",
|
||||
"NSImage",
|
||||
] }
|
||||
objc2-web-kit = { version = "0.3", features = [
|
||||
"objc2-app-kit",
|
||||
"WKWebView",
|
||||
"WKUserContentController",
|
||||
] }
|
||||
window-vibrancy = "0.6"
|
||||
|
||||
# windows
|
||||
@@ -173,14 +178,6 @@ tokio = { version = "1", features = ["full"] }
|
||||
cargo_toml = "0.22"
|
||||
http-range = "0.1.5"
|
||||
|
||||
# macOS
|
||||
[target.'cfg(target_os = "macos")'.dev-dependencies]
|
||||
objc2-web-kit = { version = "0.3", features = [
|
||||
"objc2-app-kit",
|
||||
"WKWebView",
|
||||
"WKUserContentController",
|
||||
] }
|
||||
|
||||
[features]
|
||||
default = ["wry", "compression", "common-controls-v6", "dynamic-acl", "x11"]
|
||||
unstable = ["tauri-runtime-wry?/unstable"]
|
||||
|
||||
@@ -12,7 +12,7 @@ pub use webview_window::{WebviewWindow, WebviewWindowBuilder};
|
||||
use http::HeaderMap;
|
||||
use serde::Serialize;
|
||||
use tauri_macros::default_runtime;
|
||||
pub use tauri_runtime::webview::PageLoadEvent;
|
||||
pub use tauri_runtime::webview::{NewWindowFeatures, PageLoadEvent};
|
||||
pub use tauri_runtime::Cookie;
|
||||
#[cfg(desktop)]
|
||||
use tauri_runtime::{
|
||||
@@ -50,10 +50,12 @@ use std::{
|
||||
pub(crate) type WebResourceRequestHandler =
|
||||
dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
|
||||
pub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send;
|
||||
pub(crate) type NewWindowHandler<R> =
|
||||
dyn Fn(Url, NewWindowFeatures) -> NewWindowResponse<R> + Send + Sync;
|
||||
pub(crate) type UriSchemeProtocolHandler =
|
||||
Box<dyn Fn(&str, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync>;
|
||||
pub(crate) type OnPageLoad<R> = dyn Fn(Webview<R>, PageLoadPayload<'_>) + Send + Sync + 'static;
|
||||
|
||||
pub(crate) type OnDocumentTitleChanged<R> = dyn Fn(Webview<R>, String) + Send + 'static;
|
||||
pub(crate) type DownloadHandler<R> = dyn Fn(Webview<R>, DownloadEvent<'_>) -> bool + Send + Sync;
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
@@ -174,6 +176,15 @@ impl PlatformWebview {
|
||||
self.0.controller.clone()
|
||||
}
|
||||
|
||||
/// Returns the WebView2 environment.
|
||||
#[cfg(windows)]
|
||||
#[cfg_attr(docsrs, doc(cfg(windows)))]
|
||||
pub fn environment(
|
||||
&self,
|
||||
) -> webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment {
|
||||
self.0.environment.clone()
|
||||
}
|
||||
|
||||
/// Returns the [WKWebView] handle.
|
||||
///
|
||||
/// [WKWebView]: https://developer.apple.com/documentation/webkit/wkwebview
|
||||
@@ -217,6 +228,25 @@ impl PlatformWebview {
|
||||
}
|
||||
}
|
||||
|
||||
/// Response for the new window request handler.
|
||||
pub enum NewWindowResponse<R: Runtime> {
|
||||
/// Allow the window to be opened with the default implementation.
|
||||
Allow,
|
||||
/// Allow the window to be opened, with the given window.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// **Linux**: The webview must be related to the caller webview. See [`WebviewBuilder::related_view`].
|
||||
/// **Windows**: The webview must use the same environment as the caller webview. See [`WebviewBuilder::environment`].
|
||||
/// **macOS**: The webview must use the same webview configuration as the caller webview. See [`WebviewBuilder::with_webview_configuration`] and [`NewWindowFeatures::webview_configuration`].
|
||||
Create {
|
||||
/// Window that was created.
|
||||
window: crate::WebviewWindow<R>,
|
||||
},
|
||||
/// Deny the window from being opened.
|
||||
Deny,
|
||||
}
|
||||
|
||||
macro_rules! unstable_struct {
|
||||
(#[doc = $doc:expr] $($tokens:tt)*) => {
|
||||
#[cfg(any(test, feature = "unstable"))]
|
||||
@@ -236,7 +266,9 @@ unstable_struct!(
|
||||
pub(crate) webview_attributes: WebviewAttributes,
|
||||
pub(crate) web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
|
||||
pub(crate) navigation_handler: Option<Box<NavigationHandler>>,
|
||||
pub(crate) new_window_handler: Option<Box<NewWindowHandler<R>>>,
|
||||
pub(crate) on_page_load_handler: Option<Box<OnPageLoad<R>>>,
|
||||
pub(crate) document_title_changed_handler: Option<Box<OnDocumentTitleChanged<R>>>,
|
||||
pub(crate) download_handler: Option<Arc<DownloadHandler<R>>>,
|
||||
}
|
||||
);
|
||||
@@ -312,7 +344,9 @@ async fn create_window(app: tauri::AppHandle) {
|
||||
webview_attributes: WebviewAttributes::new(url),
|
||||
web_resource_request_handler: None,
|
||||
navigation_handler: None,
|
||||
new_window_handler: None,
|
||||
on_page_load_handler: None,
|
||||
document_title_changed_handler: None,
|
||||
download_handler: None,
|
||||
}
|
||||
}
|
||||
@@ -351,7 +385,9 @@ async fn create_window(app: tauri::AppHandle) {
|
||||
webview_attributes: WebviewAttributes::from(config),
|
||||
web_resource_request_handler: None,
|
||||
navigation_handler: None,
|
||||
new_window_handler: None,
|
||||
on_page_load_handler: None,
|
||||
document_title_changed_handler: None,
|
||||
download_handler: None,
|
||||
}
|
||||
}
|
||||
@@ -449,6 +485,78 @@ tauri::Builder::default()
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a new window request handler to decide if incoming url is allowed to be opened.
|
||||
///
|
||||
/// A new window is requested to be opened by the [window.open] API.
|
||||
///
|
||||
/// The closure take the URL to open and the window features object and returns [`NewWindowResponse`] to determine whether the window should open.
|
||||
///
|
||||
#[cfg_attr(
|
||||
feature = "unstable",
|
||||
doc = r####"
|
||||
```rust,no_run
|
||||
use tauri::{
|
||||
utils::config::{Csp, CspDirectiveSources, WebviewUrl},
|
||||
window::WindowBuilder,
|
||||
webview::WebviewBuilder,
|
||||
};
|
||||
use http::header::HeaderValue;
|
||||
use std::collections::HashMap;
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
let window = tauri::window::WindowBuilder::new(app, "label").build()?;
|
||||
|
||||
let app_ = app.handle().clone();
|
||||
let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
|
||||
.on_new_window(move |url, features| {
|
||||
let builder = tauri::WebviewWindowBuilder::new(
|
||||
&app_,
|
||||
// note: add an ID counter or random label generator to support multiple opened windows at the same time
|
||||
"opened-window",
|
||||
tauri::WebviewUrl::External("about:blank".parse().unwrap()),
|
||||
)
|
||||
.with_window_features(features)
|
||||
.on_document_title_changed(|window, title| {
|
||||
window.set_title(&title).unwrap();
|
||||
})
|
||||
.title(url.as_str());
|
||||
|
||||
let window = builder.build().unwrap();
|
||||
tauri::webview::NewWindowResponse::Create { window }
|
||||
});
|
||||
|
||||
let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
|
||||
Ok(())
|
||||
});
|
||||
```
|
||||
"####
|
||||
)]
|
||||
///
|
||||
/// # Platform-specific
|
||||
///
|
||||
/// - **Android / iOS**: Not supported.
|
||||
/// - **Windows**: The closure is executed on a separate thread to prevent a deadlock.
|
||||
///
|
||||
/// [window.open]: https://developer.mozilla.org/en-US/docs/Web/API/Window/open
|
||||
pub fn on_new_window<
|
||||
F: Fn(Url, NewWindowFeatures) -> NewWindowResponse<R> + Send + Sync + 'static,
|
||||
>(
|
||||
mut self,
|
||||
f: F,
|
||||
) -> Self {
|
||||
self.new_window_handler.replace(Box::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines a closure to be executed when document title change.
|
||||
pub fn on_document_title_changed<F: Fn(Webview<R>, String) + Send + 'static>(
|
||||
mut self,
|
||||
f: F,
|
||||
) -> Self {
|
||||
self.document_title_changed_handler.replace(Box::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a download event handler to be notified when a download is requested or finished.
|
||||
///
|
||||
/// Returning `false` prevents the download from happening on a [`DownloadEvent::Requested`] event.
|
||||
@@ -550,6 +658,42 @@ tauri::Builder::default()
|
||||
) -> crate::Result<PendingWebview<EventLoopMessage, R>> {
|
||||
let mut pending = PendingWebview::new(self.webview_attributes, self.label.clone())?;
|
||||
pending.navigation_handler = self.navigation_handler.take();
|
||||
pending.new_window_handler = self.new_window_handler.take().map(|handler| {
|
||||
Box::new(
|
||||
move |url, features: NewWindowFeatures| match handler(url, features) {
|
||||
NewWindowResponse::Allow => tauri_runtime::webview::NewWindowResponse::Allow,
|
||||
#[cfg(mobile)]
|
||||
NewWindowResponse::Create { window: _ } => {
|
||||
tauri_runtime::webview::NewWindowResponse::Allow
|
||||
}
|
||||
#[cfg(desktop)]
|
||||
NewWindowResponse::Create { window } => {
|
||||
tauri_runtime::webview::NewWindowResponse::Create {
|
||||
window_id: window.window.window.id,
|
||||
}
|
||||
}
|
||||
NewWindowResponse::Deny => tauri_runtime::webview::NewWindowResponse::Deny,
|
||||
},
|
||||
)
|
||||
as Box<
|
||||
dyn Fn(Url, NewWindowFeatures) -> tauri_runtime::webview::NewWindowResponse
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>
|
||||
});
|
||||
|
||||
if let Some(document_title_changed_handler) = self.document_title_changed_handler.take() {
|
||||
let label = pending.label.clone();
|
||||
let manager = manager.manager_owned();
|
||||
pending
|
||||
.document_title_changed_handler
|
||||
.replace(Box::new(move |title| {
|
||||
if let Some(w) = manager.get_webview(&label) {
|
||||
document_title_changed_handler(w, title);
|
||||
}
|
||||
}));
|
||||
}
|
||||
pending.web_resource_request_handler = self.web_resource_request_handler.take();
|
||||
|
||||
if let Some(download_handler) = self.download_handler.take() {
|
||||
@@ -1022,6 +1166,45 @@ fn main() {
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the environment for the webview.
|
||||
/// Useful if you need to share the same environment, for instance when using the [`Self::on_new_window`].
|
||||
#[cfg(windows)]
|
||||
pub fn with_environment(
|
||||
mut self,
|
||||
environment: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment,
|
||||
) -> Self {
|
||||
self.webview_attributes.environment.replace(environment);
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a new webview sharing the same web process with the provided webview.
|
||||
/// Useful if you need to link a webview to another, for instance when using the [`Self::on_new_window`].
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
))]
|
||||
pub fn with_related_view(mut self, related_view: webkit2gtk::WebView) -> Self {
|
||||
self.webview_attributes.related_view.replace(related_view);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the webview configuration.
|
||||
/// Useful if you need to share the use a predefined webview configuration, for instance when using the [`Self::on_new_window`].
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn with_webview_configuration(
|
||||
mut self,
|
||||
webview_configuration: objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>,
|
||||
) -> Self {
|
||||
self
|
||||
.webview_attributes
|
||||
.webview_configuration
|
||||
.replace(webview_configuration);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Webview.
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::{
|
||||
event::EventTarget,
|
||||
ipc::ScopeObject,
|
||||
runtime::dpi::{PhysicalPosition, PhysicalSize},
|
||||
webview::NewWindowResponse,
|
||||
window::Monitor,
|
||||
Emitter, EventName, Listener, ResourceTable, Window,
|
||||
};
|
||||
@@ -27,6 +28,7 @@ use crate::{
|
||||
UserAttentionType,
|
||||
},
|
||||
};
|
||||
use tauri_runtime::webview::NewWindowFeatures;
|
||||
use tauri_utils::config::{BackgroundThrottlingPolicy, Color, WebviewUrl, WindowConfig};
|
||||
use url::Url;
|
||||
|
||||
@@ -270,6 +272,82 @@ impl<'a, R: Runtime, M: Manager<R>> WebviewWindowBuilder<'a, R, M> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a new window request handler to decide if incoming url is allowed to be opened.
|
||||
///
|
||||
/// A new window is requested to be opened by the [window.open] API.
|
||||
///
|
||||
/// The closure take the URL to open and the window features object and returns [`NewWindowResponse`] to determine whether the window should open.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust,no_run
|
||||
/// use tauri::{
|
||||
/// utils::config::WebviewUrl,
|
||||
/// webview::WebviewWindowBuilder,
|
||||
/// };
|
||||
/// use http::header::HeaderValue;
|
||||
/// use std::collections::HashMap;
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let app_ = app.handle().clone();
|
||||
/// let webview_window = WebviewWindowBuilder::new(app, "core", WebviewUrl::App("index.html".into()))
|
||||
/// .on_new_window(move |url, features| {
|
||||
/// let builder = tauri::WebviewWindowBuilder::new(
|
||||
/// &app_,
|
||||
/// // note: add an ID counter or random label generator to support multiple opened windows at the same time
|
||||
/// "opened-window",
|
||||
/// tauri::WebviewUrl::External("about:blank".parse().unwrap()),
|
||||
/// )
|
||||
/// .with_window_features(features)
|
||||
/// .on_document_title_changed(|window, title| {
|
||||
/// window.set_title(&title).unwrap();
|
||||
/// })
|
||||
/// .title(url.as_str());
|
||||
///
|
||||
/// let window = builder.build().unwrap();
|
||||
/// tauri::webview::NewWindowResponse::Create { window }
|
||||
/// })
|
||||
/// .build()?;
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// # Platform-specific
|
||||
///
|
||||
/// - **Android / iOS**: Not supported.
|
||||
/// - **Windows**: The closure is executed on a separate thread to prevent a deadlock.
|
||||
///
|
||||
/// [window.open]: https://developer.mozilla.org/en-US/docs/Web/API/Window/open
|
||||
pub fn on_new_window<
|
||||
F: Fn(Url, NewWindowFeatures) -> NewWindowResponse<R> + Send + Sync + 'static,
|
||||
>(
|
||||
mut self,
|
||||
f: F,
|
||||
) -> Self {
|
||||
self.webview_builder = self.webview_builder.on_new_window(f);
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines a closure to be executed when the document title changes.
|
||||
///
|
||||
/// Note that it may run before or after the navigation event.
|
||||
pub fn on_document_title_changed<F: Fn(WebviewWindow<R>, String) + Send + 'static>(
|
||||
mut self,
|
||||
f: F,
|
||||
) -> Self {
|
||||
self.webview_builder = self
|
||||
.webview_builder
|
||||
.on_document_title_changed(move |webview, url| {
|
||||
f(
|
||||
WebviewWindow {
|
||||
window: webview.window(),
|
||||
webview,
|
||||
},
|
||||
url,
|
||||
)
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a download event handler to be notified when a download is requested or finished.
|
||||
///
|
||||
/// Returning `false` prevents the download from happening on a [`DownloadEvent::Requested`] event.
|
||||
@@ -1177,6 +1255,93 @@ impl<R: Runtime, M: Manager<R>> WebviewWindowBuilder<'_, R, M> {
|
||||
.with_input_accessory_view_builder(builder);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the environment for the webview.
|
||||
/// Useful if you need to share the same environment, for instance when using the [`Self::on_new_window`].
|
||||
#[cfg(windows)]
|
||||
pub fn with_environment(
|
||||
mut self,
|
||||
environment: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment,
|
||||
) -> Self {
|
||||
self.webview_builder = self.webview_builder.with_environment(environment);
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a new webview sharing the same web process with the provided webview.
|
||||
/// Useful if you need to link a webview to another, for instance when using the [`Self::on_new_window`].
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
pub fn with_related_view(mut self, related_view: webkit2gtk::WebView) -> Self {
|
||||
self.webview_builder = self.webview_builder.with_related_view(related_view);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the webview configuration.
|
||||
/// Useful if you need to share the same webview configuration, for instance when using the [`Self::on_new_window`].
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn with_webview_configuration(
|
||||
mut self,
|
||||
webview_configuration: objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>,
|
||||
) -> Self {
|
||||
self.webview_builder = self
|
||||
.webview_builder
|
||||
.with_webview_configuration(webview_configuration);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the window features.
|
||||
/// Useful if you need to share the same window features, for instance when using the [`Self::on_new_window`].
|
||||
#[cfg(any(
|
||||
target_os = "macos",
|
||||
windows,
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
pub fn with_window_features(mut self, features: NewWindowFeatures) -> Self {
|
||||
if let Some(position) = features.position() {
|
||||
self.window_builder = self.window_builder.position(position.x, position.y);
|
||||
}
|
||||
|
||||
if let Some(size) = features.size() {
|
||||
self.window_builder = self.window_builder.inner_size(size.width, size.height);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
self.webview_builder = self
|
||||
.webview_builder
|
||||
.with_webview_configuration(features.opener().target_configuration.clone());
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
self.webview_builder = self
|
||||
.webview_builder
|
||||
.with_environment(features.opener().environment.clone());
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
{
|
||||
self.webview_builder = self
|
||||
.webview_builder
|
||||
.with_related_view(features.opener().webview.clone());
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that wraps a [`Window`] together with a [`Webview`].
|
||||
|
||||
@@ -8,6 +8,8 @@ mod menu_plugin;
|
||||
#[cfg(desktop)]
|
||||
mod tray;
|
||||
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
|
||||
use serde::Serialize;
|
||||
use tauri::{
|
||||
ipc::Channel,
|
||||
@@ -66,7 +68,32 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
|
||||
.build()?,
|
||||
));
|
||||
|
||||
let mut window_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default());
|
||||
let app_ = app.handle().clone();
|
||||
|
||||
let mut created_window_count = AtomicUsize::new(0);
|
||||
let mut window_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
|
||||
.on_new_window(move |url, features| {
|
||||
println!("new window requested: {url:?} {features:?}");
|
||||
|
||||
let number = created_window_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
let builder = tauri::WebviewWindowBuilder::new(
|
||||
&app_,
|
||||
format!("new-{number}"),
|
||||
tauri::WebviewUrl::External("about:blank".parse().unwrap()),
|
||||
)
|
||||
.with_window_features(features)
|
||||
.on_document_title_changed(|window, title| {
|
||||
window.set_title(&title).unwrap();
|
||||
})
|
||||
.title(url.as_str());
|
||||
|
||||
let window = builder.build().unwrap();
|
||||
tauri::webview::NewWindowResponse::Create { window }
|
||||
})
|
||||
.on_document_title_changed(|_window, title| {
|
||||
println!("document title changed: {title}");
|
||||
});
|
||||
|
||||
#[cfg(all(desktop, not(test)))]
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user