Skip to content

Commit af61023

Browse files
refactor(core)!: Window::close triggers RunEvent::CloseRequested (#8710)
* refactor(core): Window::close triggers RunEvent::CloseRequested * Update .changes/runtime-wry-window-close-event.md Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com> * add destroy [skip ci] * change files * delete files * fix tests * fix tests * fix test impl of the close flow * fmt * build bundle --------- Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
1 parent aa758a8 commit af61023

14 files changed

Lines changed: 175 additions & 32 deletions

File tree

.changes/destroy-api.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"tauri": patch:feat
3+
"@tauri-apps/api": patch:feat
4+
"tauri-runtime": patch:feat
5+
"tauri-runtime-wry": patch:feat
6+
---
7+
8+
Added `Window::destroy` to force close a window.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri-runtime-wry": patch:breaking
3+
---
4+
5+
`WindowDispatch::close` now triggers the `CloseRequested` flow.

.changes/window-close-requested.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri": patch:breaking
3+
"@tauri-apps/api": patch:breaking
4+
---
5+
6+
`Window::close` now triggers a close requested event instead of forcing the window to be closed.

core/tauri-runtime-wry/src/lib.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,7 @@ pub enum WindowMessage {
11311131
Show,
11321132
Hide,
11331133
Close,
1134+
Destroy,
11341135
SetDecorations(bool),
11351136
SetShadow(bool),
11361137
SetAlwaysOnBottom(bool),
@@ -1645,6 +1646,15 @@ impl<T: UserEvent> WindowDispatch<T> for WryWindowDispatcher<T> {
16451646
.map_err(|_| Error::FailedToSendMessage)
16461647
}
16471648

1649+
fn destroy(&self) -> Result<()> {
1650+
// NOTE: destroy cannot use the `send_user_message` function because it accesses the event loop callback
1651+
self
1652+
.context
1653+
.proxy
1654+
.send_event(Message::Window(self.window_id, WindowMessage::Destroy))
1655+
.map_err(|_| Error::FailedToSendMessage)
1656+
}
1657+
16481658
fn set_decorations(&self, decorations: bool) -> Result<()> {
16491659
send_user_message(
16501660
&self.context,
@@ -2543,6 +2553,9 @@ fn handle_user_message<T: UserEvent>(
25432553
WindowMessage::Close => {
25442554
panic!("cannot handle `WindowMessage::Close` on the main thread")
25452555
}
2556+
WindowMessage::Destroy => {
2557+
panic!("cannot handle `WindowMessage::Destroy` on the main thread")
2558+
}
25462559
WindowMessage::SetDecorations(decorations) => window.set_decorations(decorations),
25472560
WindowMessage::SetShadow(_enable) => {
25482561
#[cfg(windows)]
@@ -3018,6 +3031,9 @@ fn handle_event_loop<T: UserEvent>(
30183031
}
30193032
}
30203033
Message::Window(id, WindowMessage::Close) => {
3034+
on_close_requested(callback, id, windows.clone());
3035+
}
3036+
Message::Window(id, WindowMessage::Destroy) => {
30213037
on_window_close(id, windows.clone());
30223038
}
30233039
Message::UserEvent(t) => callback(RunEvent::UserEvent(t)),

core/tauri-runtime/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,9 @@ pub trait WindowDispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 's
597597
/// Closes the window.
598598
fn close(&self) -> Result<()>;
599599

600+
/// Destroys the window.
601+
fn destroy(&self) -> Result<()>;
602+
600603
/// Updates the decorations flag.
601604
fn set_decorations(&self, decorations: bool) -> Result<()>;
602605

core/tauri/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const PLUGINS: &[(&str, &[(&str, bool)])] = &[
7474
("show", false),
7575
("hide", false),
7676
("close", false),
77+
("destroy", false),
7778
("set_decorations", false),
7879
("set_shadow", false),
7980
("set_effects", false),
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2+
# SPDX-License-Identifier: Apache-2.0
3+
# SPDX-License-Identifier: MIT
4+
# Automatically generated - DO NOT EDIT!
5+
6+
"$schema" = "../../../schemas/schema.json"
7+
8+
[[permission]]
9+
identifier = "allow-destroy"
10+
description = "Enables the destroy command without any pre-configured scope."
11+
commands.allow = ["destroy"]
12+
13+
[[permission]]
14+
identifier = "deny-destroy"
15+
description = "Denies the destroy command without any pre-configured scope."
16+
commands.deny = ["destroy"]

core/tauri/scripts/bundle.global.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/tauri/src/test/mock_runtime.rs

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@ type ShortcutMap = HashMap<String, Box<dyn Fn() + Send + 'static>>;
4242
enum Message {
4343
Task(Box<dyn FnOnce() + Send>),
4444
CloseWindow(WindowId),
45+
DestroyWindow(WindowId),
4546
}
4647

4748
struct Webview;
4849

4950
struct Window {
51+
label: String,
5052
webviews: Vec<Webview>,
5153
}
5254

@@ -79,7 +81,7 @@ impl RuntimeContext {
7981
} else {
8082
match message {
8183
Message::Task(task) => task(),
82-
Message::CloseWindow(id) => {
84+
Message::CloseWindow(id) | Message::DestroyWindow(id) => {
8385
self.windows.borrow_mut().remove(&id);
8486
}
8587
}
@@ -136,11 +138,13 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
136138
(None, Vec::new())
137139
};
138140

139-
self
140-
.context
141-
.windows
142-
.borrow_mut()
143-
.insert(id, Window { webviews });
141+
self.context.windows.borrow_mut().insert(
142+
id,
143+
Window {
144+
label: pending.label.clone(),
145+
webviews,
146+
},
147+
);
144148

145149
let webview = webview_id.map(|id| DetachedWebview {
146150
label: pending.label.clone(),
@@ -666,11 +670,13 @@ impl<T: UserEvent> WindowDispatch<T> for MockWindowDispatcher {
666670
(None, Vec::new())
667671
};
668672

669-
self
670-
.context
671-
.windows
672-
.borrow_mut()
673-
.insert(id, Window { webviews });
673+
self.context.windows.borrow_mut().insert(
674+
id,
675+
Window {
676+
label: pending.label.clone(),
677+
webviews,
678+
},
679+
);
674680

675681
let webview = webview_id.map(|id| DetachedWebview {
676682
label: pending.label.clone(),
@@ -763,6 +769,11 @@ impl<T: UserEvent> WindowDispatch<T> for MockWindowDispatcher {
763769
Ok(())
764770
}
765771

772+
fn destroy(&self) -> Result<()> {
773+
self.context.send_message(Message::DestroyWindow(self.id))?;
774+
Ok(())
775+
}
776+
766777
fn set_decorations(&self, decorations: bool) -> Result<()> {
767778
Ok(())
768779
}
@@ -927,11 +938,13 @@ impl<T: UserEvent> Runtime<T> for MockRuntime {
927938
(None, Vec::new())
928939
};
929940

930-
self
931-
.context
932-
.windows
933-
.borrow_mut()
934-
.insert(id, Window { webviews });
941+
self.context.windows.borrow_mut().insert(
942+
id,
943+
Window {
944+
label: pending.label.clone(),
945+
webviews,
946+
},
947+
);
935948

936949
let webview = webview_id.map(|id| DetachedWebview {
937950
label: pending.label.clone(),
@@ -1018,6 +1031,39 @@ impl<T: UserEvent> Runtime<T> for MockRuntime {
10181031
match m {
10191032
Message::Task(p) => p(),
10201033
Message::CloseWindow(id) => {
1034+
let label = self
1035+
.context
1036+
.windows
1037+
.borrow()
1038+
.get(&id)
1039+
.map(|w| w.label.clone());
1040+
if let Some(label) = label {
1041+
let (tx, rx) = channel();
1042+
callback(RunEvent::WindowEvent {
1043+
label,
1044+
event: WindowEvent::CloseRequested { signal_tx: tx },
1045+
});
1046+
1047+
let should_prevent = matches!(rx.try_recv(), Ok(true));
1048+
if !should_prevent {
1049+
self.context.windows.borrow_mut().remove(&id);
1050+
1051+
let is_empty = self.context.windows.borrow().is_empty();
1052+
if is_empty {
1053+
let (tx, rx) = channel();
1054+
callback(RunEvent::ExitRequested { code: None, tx });
1055+
1056+
let recv = rx.try_recv();
1057+
let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent));
1058+
1059+
if !should_prevent {
1060+
break;
1061+
}
1062+
}
1063+
}
1064+
}
1065+
}
1066+
Message::DestroyWindow(id) => {
10211067
let removed = self.context.windows.borrow_mut().remove(&id).is_some();
10221068
if removed {
10231069
let is_empty = self.context.windows.borrow().is_empty();

core/tauri/src/webview/webview_window.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,17 +1301,16 @@ impl<R: Runtime> WebviewWindow<R> {
13011301
self.webview.window().hide()
13021302
}
13031303

1304-
/// Closes this window.
1305-
/// # Panics
1306-
///
1307-
/// - Panics if the event loop is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
1308-
/// - Panics when called on the main thread, usually on the [`run`](crate::App#method.run) closure.
1309-
///
1310-
/// You can spawn a task to use the API using [`crate::async_runtime::spawn`] or [`std::thread::spawn`] to prevent the panic.
1304+
/// Closes this window. It emits [`crate::RunEvent::CloseRequested`] first like a user-initiated close request so you can intercept it.
13111305
pub fn close(&self) -> crate::Result<()> {
13121306
self.webview.window().close()
13131307
}
13141308

1309+
/// Destroys this window. Similar to [`Self::close`] but does not emit any events and force close the window instead.
1310+
pub fn destroy(&self) -> crate::Result<()> {
1311+
self.webview.window().destroy()
1312+
}
1313+
13151314
/// Determines if this window should be [decorated].
13161315
///
13171316
/// [decorated]: https://en.wikipedia.org/wiki/Window_(computing)#Window_decoration

0 commit comments

Comments
 (0)