feat(webview): add reparent API (#8939)

* feat(webview): add reparent API

* fix build

* fix import

* remove cfg

* fix windows

* clone

* clone value

* ()
This commit is contained in:
Lucas Fernandes Nogueira 2024-02-22 08:52:27 -03:00 committed by GitHub
parent b5eb64728a
commit fdcaf935fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 368 additions and 199 deletions

8
.changes/reparent.md Normal file
View File

@ -0,0 +1,8 @@
---
"@tauri-apps/api": patch:feat
"tauri": patch:feat
"tauri-runtime": patch:feat
"tauri-runtime-wry": patch:feat
---
Added the `reparent` function to the webview API.

View File

@ -170,7 +170,11 @@ macro_rules! webview_getter {
getter!( getter!(
$self, $self,
rx, rx,
Message::Webview($self.window_id, $self.webview_id, $message(tx)) Message::Webview(
*$self.window_id.lock().unwrap(),
$self.webview_id,
$message(tx)
)
) )
}}; }};
} }
@ -280,7 +284,7 @@ impl<T: UserEvent> Context<T> {
let detached_webview = webview_id.map(|id| DetachedWebview { let detached_webview = webview_id.map(|id| DetachedWebview {
label: label.clone(), label: label.clone(),
dispatcher: WryWebviewDispatcher { dispatcher: WryWebviewDispatcher {
window_id, window_id: Arc::new(Mutex::new(window_id)),
webview_id: id, webview_id: id,
context: self.clone(), context: self.clone(),
}, },
@ -304,6 +308,9 @@ impl<T: UserEvent> Context<T> {
let webview_id = self.next_webview_id(); let webview_id = self.next_webview_id();
let window_id_wrapper = Arc::new(Mutex::new(window_id));
let window_id_wrapper_ = window_id_wrapper.clone();
send_user_message( send_user_message(
self, self,
Message::CreateWebview( Message::CreateWebview(
@ -312,7 +319,7 @@ impl<T: UserEvent> Context<T> {
create_webview( create_webview(
WebviewKind::WindowChild, WebviewKind::WindowChild,
window, window,
window_id, window_id_wrapper_,
webview_id, webview_id,
&context, &context,
pending, pending,
@ -322,7 +329,7 @@ impl<T: UserEvent> Context<T> {
)?; )?;
let dispatcher = WryWebviewDispatcher { let dispatcher = WryWebviewDispatcher {
window_id, window_id: window_id_wrapper,
webview_id, webview_id,
context: self.clone(), context: self.clone(),
}; };
@ -1170,6 +1177,7 @@ pub enum WebviewMessage {
SetPosition(Position), SetPosition(Position),
SetSize(Size), SetSize(Size),
SetFocus, SetFocus,
Reparent(WindowId),
// Getters // Getters
Url(Sender<Url>), Url(Sender<Url>),
Position(Sender<PhysicalPosition<i32>>), Position(Sender<PhysicalPosition<i32>>),
@ -1220,7 +1228,7 @@ impl<T: UserEvent> Clone for Message<T> {
/// The Tauri [`WebviewDispatch`] for [`Wry`]. /// The Tauri [`WebviewDispatch`] for [`Wry`].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct WryWebviewDispatcher<T: UserEvent> { pub struct WryWebviewDispatcher<T: UserEvent> {
window_id: WindowId, window_id: Arc<Mutex<WindowId>>,
webview_id: WebviewId, webview_id: WebviewId,
context: Context<T>, context: Context<T>,
} }
@ -1235,7 +1243,7 @@ impl<T: UserEvent> WebviewDispatch<T> for WryWebviewDispatcher<T> {
fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) -> WindowEventId { fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) -> WindowEventId {
let id = self.context.next_webview_event_id(); let id = self.context.next_webview_event_id();
let _ = self.context.proxy.send_event(Message::Webview( let _ = self.context.proxy.send_event(Message::Webview(
self.window_id, *self.window_id.lock().unwrap(),
self.webview_id, self.webview_id,
WebviewMessage::AddEventListener(id, Box::new(f)), WebviewMessage::AddEventListener(id, Box::new(f)),
)); ));
@ -1246,7 +1254,7 @@ impl<T: UserEvent> WebviewDispatch<T> for WryWebviewDispatcher<T> {
send_user_message( send_user_message(
&self.context, &self.context,
Message::Webview( Message::Webview(
self.window_id, *self.window_id.lock().unwrap(),
self.webview_id, self.webview_id,
WebviewMessage::WithWebview(Box::new(move |webview| f(Box::new(webview)))), WebviewMessage::WithWebview(Box::new(move |webview| f(Box::new(webview)))),
), ),
@ -1258,7 +1266,7 @@ impl<T: UserEvent> WebviewDispatch<T> for WryWebviewDispatcher<T> {
let _ = send_user_message( let _ = send_user_message(
&self.context, &self.context,
Message::Webview( Message::Webview(
self.window_id, *self.window_id.lock().unwrap(),
self.webview_id, self.webview_id,
WebviewMessage::OpenDevTools, WebviewMessage::OpenDevTools,
), ),
@ -1270,7 +1278,7 @@ impl<T: UserEvent> WebviewDispatch<T> for WryWebviewDispatcher<T> {
let _ = send_user_message( let _ = send_user_message(
&self.context, &self.context,
Message::Webview( Message::Webview(
self.window_id, *self.window_id.lock().unwrap(),
self.webview_id, self.webview_id,
WebviewMessage::CloseDevTools, WebviewMessage::CloseDevTools,
), ),
@ -1303,7 +1311,7 @@ impl<T: UserEvent> WebviewDispatch<T> for WryWebviewDispatcher<T> {
send_user_message( send_user_message(
&self.context, &self.context,
Message::Webview( Message::Webview(
self.window_id, *self.window_id.lock().unwrap(),
self.webview_id, self.webview_id,
WebviewMessage::Navigate(url), WebviewMessage::Navigate(url),
), ),
@ -1313,14 +1321,22 @@ impl<T: UserEvent> WebviewDispatch<T> for WryWebviewDispatcher<T> {
fn print(&self) -> Result<()> { fn print(&self) -> Result<()> {
send_user_message( send_user_message(
&self.context, &self.context,
Message::Webview(self.window_id, self.webview_id, WebviewMessage::Print), Message::Webview(
*self.window_id.lock().unwrap(),
self.webview_id,
WebviewMessage::Print,
),
) )
} }
fn close(&self) -> Result<()> { fn close(&self) -> Result<()> {
send_user_message( send_user_message(
&self.context, &self.context,
Message::Webview(self.window_id, self.webview_id, WebviewMessage::Close), Message::Webview(
*self.window_id.lock().unwrap(),
self.webview_id,
WebviewMessage::Close,
),
) )
} }
@ -1328,7 +1344,7 @@ impl<T: UserEvent> WebviewDispatch<T> for WryWebviewDispatcher<T> {
send_user_message( send_user_message(
&self.context, &self.context,
Message::Webview( Message::Webview(
self.window_id, *self.window_id.lock().unwrap(),
self.webview_id, self.webview_id,
WebviewMessage::SetSize(size), WebviewMessage::SetSize(size),
), ),
@ -1339,7 +1355,7 @@ impl<T: UserEvent> WebviewDispatch<T> for WryWebviewDispatcher<T> {
send_user_message( send_user_message(
&self.context, &self.context,
Message::Webview( Message::Webview(
self.window_id, *self.window_id.lock().unwrap(),
self.webview_id, self.webview_id,
WebviewMessage::SetPosition(position), WebviewMessage::SetPosition(position),
), ),
@ -1349,10 +1365,29 @@ impl<T: UserEvent> WebviewDispatch<T> for WryWebviewDispatcher<T> {
fn set_focus(&self) -> Result<()> { fn set_focus(&self) -> Result<()> {
send_user_message( send_user_message(
&self.context, &self.context,
Message::Webview(self.window_id, self.webview_id, WebviewMessage::SetFocus), Message::Webview(
*self.window_id.lock().unwrap(),
self.webview_id,
WebviewMessage::SetFocus,
),
) )
} }
fn reparent(&self, window_id: WindowId) -> Result<()> {
let mut current_window_id = self.window_id.lock().unwrap();
send_user_message(
&self.context,
Message::Webview(
*current_window_id,
self.webview_id,
WebviewMessage::Reparent(window_id),
),
)?;
*current_window_id = window_id;
Ok(())
}
#[cfg(all(feature = "tracing", not(target_os = "android")))] #[cfg(all(feature = "tracing", not(target_os = "android")))]
fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> { fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> {
// use a channel so the EvaluateScript task uses the current span as parent // use a channel so the EvaluateScript task uses the current span as parent
@ -1361,7 +1396,7 @@ impl<T: UserEvent> WebviewDispatch<T> for WryWebviewDispatcher<T> {
self, self,
rx, rx,
Message::Webview( Message::Webview(
self.window_id, *self.window_id.lock().unwrap(),
self.webview_id, self.webview_id,
WebviewMessage::EvaluateScript(script.into(), tx, tracing::Span::current()), WebviewMessage::EvaluateScript(script.into(), tx, tracing::Span::current()),
) )
@ -1373,7 +1408,7 @@ impl<T: UserEvent> WebviewDispatch<T> for WryWebviewDispatcher<T> {
send_user_message( send_user_message(
&self.context, &self.context,
Message::Webview( Message::Webview(
self.window_id, *self.window_id.lock().unwrap(),
self.webview_id, self.webview_id,
WebviewMessage::EvaluateScript(script.into()), WebviewMessage::EvaluateScript(script.into()),
), ),
@ -2235,7 +2270,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
let detached_webview = webview_id.map(|id| DetachedWebview { let detached_webview = webview_id.map(|id| DetachedWebview {
label: label.clone(), label: label.clone(),
dispatcher: WryWebviewDispatcher { dispatcher: WryWebviewDispatcher {
window_id, window_id: Arc::new(Mutex::new(window_id)),
webview_id: id, webview_id: id,
context: self.context.clone(), context: self.context.clone(),
}, },
@ -2265,12 +2300,14 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
.get(&window_id) .get(&window_id)
.and_then(|w| w.inner.clone()); .and_then(|w| w.inner.clone());
if let Some(window) = window { if let Some(window) = window {
let window_id_wrapper = Arc::new(Mutex::new(window_id));
let webview_id = self.context.next_webview_id(); let webview_id = self.context.next_webview_id();
let webview = create_webview( let webview = create_webview(
WebviewKind::WindowChild, WebviewKind::WindowChild,
&window, &window,
window_id, window_id_wrapper.clone(),
webview_id, webview_id,
&self.context, &self.context,
pending, pending,
@ -2290,7 +2327,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
}); });
let dispatcher = WryWebviewDispatcher { let dispatcher = WryWebviewDispatcher {
window_id, window_id: window_id_wrapper,
webview_id, webview_id,
context: self.context.clone(), context: self.context.clone(),
}; };
@ -2685,87 +2722,33 @@ fn handle_user_message<T: UserEvent>(
} }
} }
} }
Message::Webview(window_id, webview_id, webview_message) => { Message::Webview(window_id, webview_id, webview_message) => {
let webview_handle = windows.0.borrow().get(&window_id).map(|w| { if let WebviewMessage::Reparent(new_parent_window_id) = webview_message {
( let webview_handle = windows.0.borrow_mut().get_mut(&window_id).and_then(|w| {
w.inner.clone(), w.webviews
w.webviews.iter().find(|w| w.id == webview_id).cloned(), .iter()
) .position(|w| w.id == webview_id)
}); .map(|webview_index| w.webviews.remove(webview_index))
if let Some((Some(window), Some(webview))) = webview_handle { });
match webview_message {
WebviewMessage::WebviewEvent(_) => { /* already handled */ }
WebviewMessage::SynthesizedWindowEvent(_) => { /* already handled */ }
WebviewMessage::AddEventListener(id, listener) => { if let Some(webview) = webview_handle {
webview if let Some((Some(new_parent_window), new_parent_window_webviews)) = windows
.webview_event_listeners .0
.lock() .borrow_mut()
.unwrap() .get_mut(&new_parent_window_id)
.insert(id, listener); .map(|w| (w.inner.clone(), &mut w.webviews))
} {
#[cfg(target_os = "macos")]
#[cfg(all(feature = "tracing", not(target_os = "android")))] {
WebviewMessage::EvaluateScript(script, tx, span) => { use wry::WebViewExtMacOS;
let _span = span.entered(); webview.inner.reparent(new_parent_window.ns_window() as _);
if let Err(e) = webview.evaluate_script(&script) { new_parent_window_webviews.push(webview);
debug_eprintln!("{}", e);
} }
tx.send(()).unwrap(); #[cfg(windows)]
} {
#[cfg(not(all(feature = "tracing", not(target_os = "android"))))] webview.inner.reparent(new_parent_window.hwnd());
WebviewMessage::EvaluateScript(script) => { new_parent_window_webviews.push(webview);
if let Err(e) = webview.evaluate_script(&script) {
debug_eprintln!("{}", e);
} }
}
WebviewMessage::Navigate(url) => webview.load_url(url.as_str()),
WebviewMessage::Print => {
let _ = webview.print();
}
WebviewMessage::Close => {
windows.0.borrow_mut().get_mut(&window_id).map(|window| {
if let Some(i) = window.webviews.iter().position(|w| w.id == webview.id) {
window.webviews.remove(i);
}
window
});
}
WebviewMessage::SetSize(size) => {
let mut bounds = webview.bounds();
let size = size.to_logical(window.scale_factor());
bounds.width = size.width;
bounds.height = size.height;
if let Some(b) = &webview.bounds {
let window_size = window.inner_size();
let mut bounds = b.lock().unwrap();
bounds.width_rate = size.width as f32 / window_size.width as f32;
bounds.height_rate = size.height as f32 / window_size.height as f32;
}
webview.set_bounds(bounds);
}
WebviewMessage::SetPosition(position) => {
let mut bounds = webview.bounds();
let position = position.to_logical(window.scale_factor());
bounds.x = position.x;
bounds.y = position.y;
if let Some(b) = &webview.bounds {
let window_size = window.inner_size();
let mut bounds = b.lock().unwrap();
bounds.width_rate = position.x as f32 / window_size.width as f32;
bounds.height_rate = position.y as f32 / window_size.height as f32;
}
webview.set_bounds(bounds);
}
WebviewMessage::SetFocus => {
webview.focus();
}
WebviewMessage::WithWebview(f) => {
#[cfg(any( #[cfg(any(
target_os = "linux", target_os = "linux",
target_os = "dragonfly", target_os = "dragonfly",
@ -2774,68 +2757,163 @@ fn handle_user_message<T: UserEvent>(
target_os = "openbsd" target_os = "openbsd"
))] ))]
{ {
f(webview.webview()); if let Some(container) = new_parent_window.default_vbox() {
webview.inner.reparent(container);
new_parent_window_webviews.push(webview);
}
} }
#[cfg(target_os = "macos")] }
{ }
use wry::WebViewExtMacOS; } else {
f(Webview { let webview_handle = windows.0.borrow().get(&window_id).map(|w| {
webview: webview.webview(), (
manager: webview.manager(), w.inner.clone(),
ns_window: webview.ns_window(), w.webviews.iter().find(|w| w.id == webview_id).cloned(),
)
});
if let Some((Some(window), Some(webview))) = webview_handle {
match webview_message {
WebviewMessage::WebviewEvent(_) => { /* already handled */ }
WebviewMessage::SynthesizedWindowEvent(_) => { /* already handled */ }
WebviewMessage::Reparent(_window_id) => { /* already handled */ }
WebviewMessage::AddEventListener(id, listener) => {
webview
.webview_event_listeners
.lock()
.unwrap()
.insert(id, listener);
}
#[cfg(all(feature = "tracing", not(target_os = "android")))]
WebviewMessage::EvaluateScript(script, tx, span) => {
let _span = span.entered();
if let Err(e) = webview.evaluate_script(&script) {
debug_eprintln!("{}", e);
}
tx.send(()).unwrap();
}
#[cfg(not(all(feature = "tracing", not(target_os = "android"))))]
WebviewMessage::EvaluateScript(script) => {
if let Err(e) = webview.evaluate_script(&script) {
debug_eprintln!("{}", e);
}
}
WebviewMessage::Navigate(url) => webview.load_url(url.as_str()),
WebviewMessage::Print => {
let _ = webview.print();
}
WebviewMessage::Close => {
windows.0.borrow_mut().get_mut(&window_id).map(|window| {
if let Some(i) = window.webviews.iter().position(|w| w.id == webview.id) {
window.webviews.remove(i);
}
window
}); });
} }
#[cfg(target_os = "ios")] WebviewMessage::SetSize(size) => {
{ let mut bounds = webview.bounds();
use tao::platform::ios::WindowExtIOS; let size = size.to_logical(window.scale_factor());
use wry::WebViewExtIOS; bounds.width = size.width;
bounds.height = size.height;
f(Webview { if let Some(b) = &webview.bounds {
webview: webview.inner.webview(), let window_size = window.inner_size();
manager: webview.inner.manager(), let mut bounds = b.lock().unwrap();
view_controller: window.ui_view_controller() as cocoa::base::id, bounds.width_rate = size.width as f32 / window_size.width as f32;
}); bounds.height_rate = size.height as f32 / window_size.height as f32;
} }
#[cfg(windows)]
{
f(Webview {
controller: webview.controller(),
});
}
#[cfg(target_os = "android")]
{
f(webview.handle())
}
}
#[cfg(any(debug_assertions, feature = "devtools"))] webview.set_bounds(bounds);
WebviewMessage::OpenDevTools => { }
webview.open_devtools(); WebviewMessage::SetPosition(position) => {
} let mut bounds = webview.bounds();
#[cfg(any(debug_assertions, feature = "devtools"))] let position = position.to_logical(window.scale_factor());
WebviewMessage::CloseDevTools => { bounds.x = position.x;
webview.close_devtools(); bounds.y = position.y;
}
#[cfg(any(debug_assertions, feature = "devtools"))]
WebviewMessage::IsDevToolsOpen(tx) => {
tx.send(webview.is_devtools_open()).unwrap();
}
// Getters if let Some(b) = &webview.bounds {
WebviewMessage::Url(tx) => { let window_size = window.inner_size();
tx.send(webview.url().parse().unwrap()).unwrap(); let mut bounds = b.lock().unwrap();
} bounds.width_rate = position.x as f32 / window_size.width as f32;
WebviewMessage::Position(tx) => { bounds.height_rate = position.y as f32 / window_size.height as f32;
let bounds = webview.bounds(); }
let position =
LogicalPosition::new(bounds.x, bounds.y).to_physical(window.scale_factor()); webview.set_bounds(bounds);
tx.send(position).unwrap(); }
} // Getters
WebviewMessage::Size(tx) => { WebviewMessage::Url(tx) => {
let bounds = webview.bounds(); tx.send(webview.url().parse().unwrap()).unwrap();
let size = }
LogicalSize::new(bounds.width, bounds.height).to_physical(window.scale_factor()); WebviewMessage::Position(tx) => {
tx.send(size).unwrap(); let bounds = webview.bounds();
let position =
LogicalPosition::new(bounds.x, bounds.y).to_physical(window.scale_factor());
tx.send(position).unwrap();
}
WebviewMessage::Size(tx) => {
let bounds = webview.bounds();
let size =
LogicalSize::new(bounds.width, bounds.height).to_physical(window.scale_factor());
tx.send(size).unwrap();
}
WebviewMessage::SetFocus => {
webview.focus();
}
WebviewMessage::WithWebview(f) => {
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
{
f(webview.webview());
}
#[cfg(target_os = "macos")]
{
use wry::WebViewExtMacOS;
f(Webview {
webview: webview.webview(),
manager: webview.manager(),
ns_window: webview.ns_window(),
});
}
#[cfg(target_os = "ios")]
{
use tao::platform::ios::WindowExtIOS;
use wry::WebViewExtIOS;
f(Webview {
webview: webview.inner.webview(),
manager: webview.inner.manager(),
view_controller: window.ui_view_controller() as cocoa::base::id,
});
}
#[cfg(windows)]
{
f(Webview {
controller: webview.controller(),
});
}
#[cfg(target_os = "android")]
{
f(webview.handle())
}
}
#[cfg(any(debug_assertions, feature = "devtools"))]
WebviewMessage::OpenDevTools => {
webview.open_devtools();
}
#[cfg(any(debug_assertions, feature = "devtools"))]
WebviewMessage::CloseDevTools => {
webview.close_devtools();
}
#[cfg(any(debug_assertions, feature = "devtools"))]
WebviewMessage::IsDevToolsOpen(tx) => {
tx.send(webview.is_devtools_open()).unwrap();
}
} }
} }
} }
@ -3341,7 +3419,7 @@ fn create_window<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(
webviews.push(create_webview( webviews.push(create_webview(
WebviewKind::WindowContent, WebviewKind::WindowContent,
&window, &window,
window_id, Arc::new(Mutex::new(window_id)),
webview_id, webview_id,
context, context,
webview, webview,
@ -3390,7 +3468,7 @@ enum WebviewKind {
WindowChild, WindowChild,
} }
#[derive(Clone)] #[derive(Debug, Clone)]
struct WebviewBounds { struct WebviewBounds {
x_rate: f32, x_rate: f32,
y_rate: f32, y_rate: f32,
@ -3401,7 +3479,7 @@ struct WebviewBounds {
fn create_webview<T: UserEvent>( fn create_webview<T: UserEvent>(
kind: WebviewKind, kind: WebviewKind,
window: &Window, window: &Window,
window_id: WindowId, window_id: Arc<Mutex<WindowId>>,
id: WebviewId, id: WebviewId,
context: &Context<T>, context: &Context<T>,
pending: PendingWebview<T, Wry<T>>, pending: PendingWebview<T, Wry<T>>,
@ -3470,6 +3548,7 @@ fn create_webview<T: UserEvent>(
if webview_attributes.file_drop_handler_enabled { if webview_attributes.file_drop_handler_enabled {
let proxy = context.proxy.clone(); let proxy = context.proxy.clone();
let window_id_ = window_id.clone();
webview_builder = webview_builder.with_file_drop_handler(move |event| { webview_builder = webview_builder.with_file_drop_handler(move |event| {
let event = match event { let event = match event {
WryFileDropEvent::Hovered { WryFileDropEvent::Hovered {
@ -3496,7 +3575,7 @@ fn create_webview<T: UserEvent>(
WebviewMessage::WebviewEvent(WebviewEvent::FileDrop(event)) WebviewMessage::WebviewEvent(WebviewEvent::FileDrop(event))
}; };
let _ = proxy.send_event(Message::Webview(window_id, id, message)); let _ = proxy.send_event(Message::Webview(*window_id_.lock().unwrap(), id, message));
true true
}); });
} }
@ -3598,7 +3677,7 @@ fn create_webview<T: UserEvent>(
webview_builder = webview_builder.with_ipc_handler(create_ipc_handler( webview_builder = webview_builder.with_ipc_handler(create_ipc_handler(
kind, kind,
window_id, window_id.clone(),
id, id,
context.clone(), context.clone(),
label.clone(), label.clone(),
@ -3696,12 +3775,13 @@ fn create_webview<T: UserEvent>(
let controller = webview.controller(); let controller = webview.controller();
let proxy = context.proxy.clone(); let proxy = context.proxy.clone();
let proxy_ = proxy.clone(); let proxy_ = proxy.clone();
let window_id_ = window_id.clone();
let mut token = EventRegistrationToken::default(); let mut token = EventRegistrationToken::default();
unsafe { unsafe {
controller.add_GotFocus( controller.add_GotFocus(
&FocusChangedEventHandler::create(Box::new(move |_, _| { &FocusChangedEventHandler::create(Box::new(move |_, _| {
let _ = proxy_.send_event(Message::Webview( let _ = proxy.send_event(Message::Webview(
window_id, *window_id_.lock().unwrap(),
id, id,
WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(true)), WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(true)),
)); ));
@ -3714,8 +3794,8 @@ fn create_webview<T: UserEvent>(
unsafe { unsafe {
controller.add_LostFocus( controller.add_LostFocus(
&FocusChangedEventHandler::create(Box::new(move |_, _| { &FocusChangedEventHandler::create(Box::new(move |_, _| {
let _ = proxy.send_event(Message::Webview( let _ = proxy_.send_event(Message::Webview(
window_id, *window_id.lock().unwrap(),
id, id,
WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(false)), WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(false)),
)); ));
@ -3745,7 +3825,7 @@ fn create_webview<T: UserEvent>(
/// Create a wry ipc handler from a tauri ipc handler. /// Create a wry ipc handler from a tauri ipc handler.
fn create_ipc_handler<T: UserEvent>( fn create_ipc_handler<T: UserEvent>(
_kind: WebviewKind, _kind: WebviewKind,
window_id: WindowId, window_id: Arc<Mutex<WindowId>>,
webview_id: WebviewId, webview_id: WebviewId,
context: Context<T>, context: Context<T>,
label: String, label: String,
@ -3754,7 +3834,7 @@ fn create_ipc_handler<T: UserEvent>(
Box::new(move |request| { Box::new(move |request| {
#[cfg(windows)] #[cfg(windows)]
if _kind == WebviewKind::WindowContent if _kind == WebviewKind::WindowContent
&& undecorated_resizing::handle_request(context.clone(), window_id, &request) && undecorated_resizing::handle_request(context.clone(), *window_id.lock().unwrap(), &request)
{ {
return; return;
} }
@ -3764,7 +3844,7 @@ fn create_ipc_handler<T: UserEvent>(
DetachedWebview { DetachedWebview {
label: label.clone(), label: label.clone(),
dispatcher: WryWebviewDispatcher { dispatcher: WryWebviewDispatcher {
window_id, window_id: window_id.clone(),
webview_id, webview_id,
context: context.clone(), context: context.clone(),
}, },

View File

@ -449,6 +449,9 @@ pub trait WebviewDispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + '
/// Executes javascript on the window this [`WindowDispatch`] represents. /// Executes javascript on the window this [`WindowDispatch`] represents.
fn eval_script<S: Into<String>>(&self, script: S) -> Result<()>; fn eval_script<S: Into<String>>(&self, script: S) -> Result<()>;
/// Moves the webview to the given window.
fn reparent(&self, window_id: WindowId) -> Result<()>;
} }
/// Window dispatcher. A thread-safe handle to the window APIs. /// Window dispatcher. A thread-safe handle to the window APIs.

View File

@ -122,6 +122,7 @@ const PLUGINS: &[(&str, &[(&str, bool)])] = &[
("set_webview_position", false), ("set_webview_position", false),
("set_webview_focus", false), ("set_webview_focus", false),
("print", false), ("print", false),
("reparent", false),
// internal // internal
("internal_toggle_devtools", true), ("internal_toggle_devtools", true),
], ],

View File

@ -32,6 +32,14 @@ Enables the print command without any pre-configured scope.
Denies the print command without any pre-configured scope. Denies the print command without any pre-configured scope.
## allow-reparent
Enables the reparent command without any pre-configured scope.
## deny-reparent
Denies the reparent command without any pre-configured scope.
## allow-set-webview-focus ## allow-set-webview-focus
Enables the set_webview_focus command without any pre-configured scope. Enables the set_webview_focus command without any pre-configured scope.

File diff suppressed because one or more lines are too long

View File

@ -244,11 +244,13 @@ fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, labe
} }
} }
match invoke_message.unwrap_or_else(|| { let message = invoke_message.unwrap_or_else(|| {
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
let _span = tracing::trace_span!("ipc::request::deserialize").entered(); let _span = tracing::trace_span!("ipc::request::deserialize").entered();
serde_json::from_str::<Message>(&message).map_err(Into::into) serde_json::from_str::<Message>(&message).map_err(Into::into)
}) { });
match message {
Ok(message) => { Ok(message) => {
let request = InvokeRequest { let request = InvokeRequest {
cmd: message.cmd, cmd: message.cmd,

View File

@ -540,6 +540,10 @@ impl<T: UserEvent> WebviewDispatch<T> for MockWebviewDispatcher {
fn set_focus(&self) -> Result<()> { fn set_focus(&self) -> Result<()> {
Ok(()) Ok(())
} }
fn reparent(&self, window_id: WindowId) -> Result<()> {
Ok(())
}
} }
impl<T: UserEvent> WindowDispatch<T> for MockWindowDispatcher { impl<T: UserEvent> WindowDispatch<T> for MockWindowDispatcher {

View File

@ -606,7 +606,7 @@ tauri::Builder::default()
.webviews_lock() .webviews_lock()
.values() .values()
.map(|w| WebviewLabelDef { .map(|w| WebviewLabelDef {
window_label: w.window.label().to_string(), window_label: w.window().label().to_string(),
label: w.label().to_string(), label: w.label().to_string(),
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -794,7 +794,10 @@ fn main() {
/// Webview. /// Webview.
#[default_runtime(crate::Wry, wry)] #[default_runtime(crate::Wry, wry)]
pub struct Webview<R: Runtime> { pub struct Webview<R: Runtime> {
pub(crate) window: Window<R>, window_label: Arc<Mutex<String>>,
/// The manager to associate this webview with.
pub(crate) manager: Arc<AppManager<R>>,
pub(crate) app_handle: AppHandle<R>,
/// The webview created by the runtime. /// The webview created by the runtime.
pub(crate) webview: DetachedWebview<EventLoopMessage, R>, pub(crate) webview: DetachedWebview<EventLoopMessage, R>,
} }
@ -802,7 +805,7 @@ pub struct Webview<R: Runtime> {
impl<R: Runtime> std::fmt::Debug for Webview<R> { impl<R: Runtime> std::fmt::Debug for Webview<R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Window") f.debug_struct("Window")
.field("window", &self.window) .field("window_label", &self.window_label)
.field("webview", &self.webview) .field("webview", &self.webview)
.finish() .finish()
} }
@ -811,7 +814,9 @@ impl<R: Runtime> std::fmt::Debug for Webview<R> {
impl<R: Runtime> Clone for Webview<R> { impl<R: Runtime> Clone for Webview<R> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
window: self.window.clone(), window_label: self.window_label.clone(),
manager: self.manager.clone(),
app_handle: self.app_handle.clone(),
webview: self.webview.clone(), webview: self.webview.clone(),
} }
} }
@ -836,7 +841,12 @@ impl<R: Runtime> PartialEq for Webview<R> {
impl<R: Runtime> Webview<R> { impl<R: Runtime> Webview<R> {
/// Create a new webview that is attached to the window. /// Create a new webview that is attached to the window.
pub(crate) fn new(window: Window<R>, webview: DetachedWebview<EventLoopMessage, R>) -> Self { pub(crate) fn new(window: Window<R>, webview: DetachedWebview<EventLoopMessage, R>) -> Self {
Self { window, webview } Self {
window_label: Arc::new(Mutex::new(window.label().into())),
manager: window.manager.clone(),
app_handle: window.app_handle.clone(),
webview,
}
} }
/// Initializes a webview builder with the given window label and URL to load on the webview. /// Initializes a webview builder with the given window label and URL to load on the webview.
@ -883,8 +893,9 @@ impl<R: Runtime> Webview<R> {
/// Closes this webview. /// Closes this webview.
pub fn close(&self) -> crate::Result<()> { pub fn close(&self) -> crate::Result<()> {
if self.window.is_webview_window { let window = self.window();
self.window.close() if window.is_webview_window {
window.close()
} else { } else {
self.webview.dispatcher.close()?; self.webview.dispatcher.close()?;
self.manager().on_webview_close(self.label()); self.manager().on_webview_close(self.label());
@ -894,8 +905,9 @@ impl<R: Runtime> Webview<R> {
/// Resizes this webview. /// Resizes this webview.
pub fn set_size<S: Into<Size>>(&self, size: S) -> crate::Result<()> { pub fn set_size<S: Into<Size>>(&self, size: S) -> crate::Result<()> {
if self.window.is_webview_window { let window = self.window();
self.window.set_size(size.into()) if window.is_webview_window {
window.set_size(size.into())
} else { } else {
self self
.webview .webview
@ -907,8 +919,9 @@ impl<R: Runtime> Webview<R> {
/// Sets this webviews's position. /// Sets this webviews's position.
pub fn set_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> { pub fn set_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {
if self.window.is_webview_window { let window = self.window();
self.window.set_position(position.into()) if window.is_webview_window {
window.set_position(position.into())
} else { } else {
self self
.webview .webview
@ -923,13 +936,23 @@ impl<R: Runtime> Webview<R> {
self.webview.dispatcher.set_focus().map_err(Into::into) self.webview.dispatcher.set_focus().map_err(Into::into)
} }
/// Move the webview to the given window.
pub fn reparent(&self, window: &Window<R>) -> crate::Result<()> {
let current_window = self.window();
if !current_window.is_webview_window {
self.webview.dispatcher.reparent(window.window.id)?;
}
Ok(())
}
/// Returns the webview position. /// Returns the webview position.
/// ///
/// - For child webviews, returns the position of the top-left hand corner of the webviews's client area relative to the top-left hand corner of the parent window. /// - For child webviews, returns the position of the top-left hand corner of the webviews's client area relative to the top-left hand corner of the parent window.
/// - For webview window, returns the inner position of the window. /// - For webview window, returns the inner position of the window.
pub fn position(&self) -> crate::Result<PhysicalPosition<i32>> { pub fn position(&self) -> crate::Result<PhysicalPosition<i32>> {
if self.window.is_webview_window { let window = self.window();
self.window.inner_position() if window.is_webview_window {
window.inner_position()
} else { } else {
self.webview.dispatcher.position().map_err(Into::into) self.webview.dispatcher.position().map_err(Into::into)
} }
@ -937,8 +960,9 @@ impl<R: Runtime> Webview<R> {
/// Returns the physical size of the webviews's client area. /// Returns the physical size of the webviews's client area.
pub fn size(&self) -> crate::Result<PhysicalSize<u32>> { pub fn size(&self) -> crate::Result<PhysicalSize<u32>> {
if self.window.is_webview_window { let window = self.window();
self.window.inner_size() if window.is_webview_window {
window.inner_size()
} else { } else {
self.webview.dispatcher.size().map_err(Into::into) self.webview.dispatcher.size().map_err(Into::into)
} }
@ -948,8 +972,11 @@ impl<R: Runtime> Webview<R> {
/// Webview APIs. /// Webview APIs.
impl<R: Runtime> Webview<R> { impl<R: Runtime> Webview<R> {
/// The window that is hosting this webview. /// The window that is hosting this webview.
pub fn window(&self) -> &Window<R> { pub fn window(&self) -> Window<R> {
&self.window self
.manager
.get_window(&self.window_label.lock().unwrap())
.expect("could not locate webview parent window")
} }
/// Executes a closure, providing it with the webview handle that is specific to the current platform. /// Executes a closure, providing it with the webview handle that is specific to the current platform.
@ -1099,7 +1126,7 @@ fn main() {
); );
#[cfg(mobile)] #[cfg(mobile)]
let app_handle = self.window.app_handle.clone(); let app_handle = self.app_handle.clone();
let message = InvokeMessage::new( let message = InvokeMessage::new(
self, self,
@ -1415,7 +1442,7 @@ tauri::Builder::default()
where where
F: Fn(Event) + Send + 'static, F: Fn(Event) + Send + 'static,
{ {
self.window.manager.listen( self.manager.listen(
event.into(), event.into(),
EventTarget::Webview { EventTarget::Webview {
label: self.label().to_string(), label: self.label().to_string(),
@ -1454,7 +1481,7 @@ tauri::Builder::default()
"#### "####
)] )]
pub fn unlisten(&self, id: EventId) { pub fn unlisten(&self, id: EventId) {
self.window.manager.unlisten(id) self.manager.unlisten(id)
} }
/// Listen to an event on this webview only once. /// Listen to an event on this webview only once.
@ -1464,7 +1491,7 @@ tauri::Builder::default()
where where
F: FnOnce(Event) + Send + 'static, F: FnOnce(Event) + Send + 'static,
{ {
self.window.manager.once( self.manager.once(
event.into(), event.into(),
EventTarget::Webview { EventTarget::Webview {
label: self.label().to_string(), label: self.label().to_string(),
@ -1478,19 +1505,19 @@ impl<R: Runtime> Manager<R> for Webview<R> {}
impl<R: Runtime> ManagerBase<R> for Webview<R> { impl<R: Runtime> ManagerBase<R> for Webview<R> {
fn manager(&self) -> &AppManager<R> { fn manager(&self) -> &AppManager<R> {
&self.window.manager &self.manager
} }
fn manager_owned(&self) -> Arc<AppManager<R>> { fn manager_owned(&self) -> Arc<AppManager<R>> {
self.window.manager.clone() self.manager.clone()
} }
fn runtime(&self) -> RuntimeOrDispatch<'_, R> { fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
self.window.app_handle.runtime() self.app_handle.runtime()
} }
fn managed_app_handle(&self) -> &AppHandle<R> { fn managed_app_handle(&self) -> &AppHandle<R> {
&self.window.app_handle &self.app_handle
} }
} }

View File

@ -159,6 +159,19 @@ mod desktop_commands {
setter!(set_webview_position, set_position, Position); setter!(set_webview_position, set_position, Position);
setter!(set_webview_focus, set_focus); setter!(set_webview_focus, set_focus);
#[command(root = "crate")]
pub async fn reparent<R: Runtime>(
webview: crate::Webview<R>,
label: Option<String>,
window: String,
) -> crate::Result<()> {
let webview = get_webview(webview, label)?;
if let Some(window) = webview.manager.get_window(&window) {
webview.reparent(&window)?;
}
Ok(())
}
#[cfg(any(debug_assertions, feature = "devtools"))] #[cfg(any(debug_assertions, feature = "devtools"))]
#[command(root = "crate")] #[command(root = "crate")]
pub async fn internal_toggle_devtools<R: Runtime>( pub async fn internal_toggle_devtools<R: Runtime>(
@ -227,6 +240,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
desktop_commands::set_webview_position, desktop_commands::set_webview_position,
desktop_commands::set_webview_focus, desktop_commands::set_webview_focus,
desktop_commands::print, desktop_commands::print,
desktop_commands::reparent,
#[cfg(any(debug_assertions, feature = "devtools"))] #[cfg(any(debug_assertions, feature = "devtools"))]
desktop_commands::internal_toggle_devtools, desktop_commands::internal_toggle_devtools,
]); ]);

View File

@ -572,7 +572,7 @@ impl<'a, R: Runtime, M: Manager<R>> WebviewWindowBuilder<'a, R, M> {
/// - **Linux**: This makes the new window transient for parent, see <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html> /// - **Linux**: This makes the new window transient for parent, see <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>
/// - **macOS**: This adds the window as a child of parent, see <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc> /// - **macOS**: This adds the window as a child of parent, see <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>
pub fn parent(mut self, parent: &WebviewWindow<R>) -> crate::Result<Self> { pub fn parent(mut self, parent: &WebviewWindow<R>) -> crate::Result<Self> {
self.window_builder = self.window_builder.parent(&parent.webview.window)?; self.window_builder = self.window_builder.parent(&parent.webview.window())?;
Ok(self) Ok(self)
} }
@ -586,7 +586,7 @@ impl<'a, R: Runtime, M: Manager<R>> WebviewWindowBuilder<'a, R, M> {
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows> /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
#[cfg(windows)] #[cfg(windows)]
pub fn owner(mut self, owner: &WebviewWindow<R>) -> crate::Result<Self> { pub fn owner(mut self, owner: &WebviewWindow<R>) -> crate::Result<Self> {
self.window_builder = self.window_builder.owner(&owner.webview.window)?; self.window_builder = self.window_builder.owner(&owner.webview.window())?;
Ok(self) Ok(self)
} }
@ -638,7 +638,9 @@ impl<'a, R: Runtime, M: Manager<R>> WebviewWindowBuilder<'a, R, M> {
target_os = "openbsd" target_os = "openbsd"
))] ))]
pub fn transient_for(mut self, parent: &WebviewWindow<R>) -> crate::Result<Self> { pub fn transient_for(mut self, parent: &WebviewWindow<R>) -> crate::Result<Self> {
self.window_builder = self.window_builder.transient_for(&parent.webview.window)?; self.window_builder = self
.window_builder
.transient_for(&parent.webview.window())?;
Ok(self) Ok(self)
} }
@ -868,7 +870,9 @@ impl<R: Runtime> raw_window_handle::HasWindowHandle for WebviewWindow<R> {
fn window_handle( fn window_handle(
&self, &self,
) -> std::result::Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> { ) -> std::result::Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
self.webview.window().window_handle() Ok(unsafe {
raw_window_handle::WindowHandle::borrow_raw(self.webview.window().window_handle()?.as_raw())
})
} }
} }

View File

@ -333,7 +333,7 @@ tauri::Builder::default()
.webviews_lock() .webviews_lock()
.values() .values()
.map(|w| WebviewLabelDef { .map(|w| WebviewLabelDef {
window_label: w.window.label().to_string(), window_label: w.window().label().to_string(),
label: w.label().to_string(), label: w.label().to_string(),
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -988,7 +988,7 @@ impl<R: Runtime> Window<R> {
.webview .webview
.webviews_lock() .webviews_lock()
.values() .values()
.filter(|w| w.window() == self) .filter(|w| &w.window() == self)
.cloned() .cloned()
.collect() .collect()
} }

View File

@ -31,6 +31,7 @@ import {
} from './event' } from './event'
import { invoke } from './core' import { invoke } from './core'
import { Window, getCurrent as getCurrentWindow } from './window' import { Window, getCurrent as getCurrentWindow } from './window'
import { WebviewWindow } from './webviewWindow'
interface FileDropPayload { interface FileDropPayload {
paths: string[] paths: string[]
@ -474,6 +475,23 @@ class Webview {
}) })
} }
/**
* Moves this webview to the given label.
* @example
* ```typescript
* import { getCurrent } from '@tauri-apps/api/webview';
* await getCurrent().reparent('other-window');
* ```
*
* @returns A promise indicating the success or failure of the operation.
*/
async reparent(window: Window | WebviewWindow | string): Promise<void> {
return invoke('plugin:webview|set_webview_focus', {
label: this.label,
window: typeof window === 'string' ? window : window.label
})
}
// Listeners // Listeners
/** /**