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:
THELOSTSOUL
2025-08-17 01:49:01 +08:00
committed by GitHub
parent f1232671ab
commit 33d0b3f0c1
12 changed files with 653 additions and 22 deletions

View 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`.

View 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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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 {

View File

@@ -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,
}
}

View File

@@ -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 = []

View File

@@ -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,
}
}

View File

@@ -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"]

View File

@@ -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.

View File

@@ -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`].

View File

@@ -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)))]
{