Skip to content

Commit e1eb911

Browse files
fix(menu): ensure init & drop is done on main thread (#8582)
* fix(menu): ensure init & drop is done on main thread * move macros back * fix doctests * fix macos doctests * generate inner types and add drop implementation on inner * clippy * fix leftoever merge conflicts * fix doctests * update api example * add missing change file * move macro * fix tray import --------- Co-authored-by: Lucas Nogueira <lucas@tauri.app> Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
1 parent 58fe2e8 commit e1eb911

22 files changed

Lines changed: 1053 additions & 845 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'tauri': 'patch:breaking'
3+
---
4+
5+
All menu item constructors `accelerator` argument have been changed to `Option<impl AsRef<str>>` so when providing `None` you need to specify the generic argument like `None::<&str>`.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'tauri': 'patch:bug'
3+
---
4+
5+
Ensure initalize logic and dropping of menu item is done on the main thread, this fixes the crash when a menu item is dropped on another thread.

.changes/menu-init-result.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'tauri': 'patch:breaking'
3+
---
4+
5+
All menu item constructors have been changed to return a `Result<Self>`

core/tauri/src/app.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,9 +1285,9 @@ tauri::Builder::default()
12851285
/// "File",
12861286
/// true,
12871287
/// &[
1288-
/// &PredefinedMenuItem::close_window(handle, None),
1288+
/// &PredefinedMenuItem::close_window(handle, None)?,
12891289
/// #[cfg(target_os = "macos")]
1290-
/// &MenuItem::new(handle, "Hello", true, None),
1290+
/// &MenuItem::new(handle, "Hello", true, None::<&str>)?,
12911291
/// ],
12921292
/// )?
12931293
/// ]));

core/tauri/src/lib.rs

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -954,51 +954,6 @@ pub(crate) mod sealed {
954954
}
955955
}
956956

957-
#[cfg(any(test, feature = "test"))]
958-
#[cfg_attr(docsrs, doc(cfg(feature = "test")))]
959-
pub mod test;
960-
961-
#[cfg(test)]
962-
mod tests {
963-
use cargo_toml::Manifest;
964-
use std::{env::var, fs::read_to_string, path::PathBuf, sync::OnceLock};
965-
966-
static MANIFEST: OnceLock<Manifest> = OnceLock::new();
967-
const CHECKED_FEATURES: &str = include_str!(concat!(env!("OUT_DIR"), "/checked_features"));
968-
969-
fn get_manifest() -> &'static Manifest {
970-
MANIFEST.get_or_init(|| {
971-
let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
972-
Manifest::from_path(manifest_dir.join("Cargo.toml")).expect("failed to parse Cargo manifest")
973-
})
974-
}
975-
976-
#[test]
977-
fn features_are_documented() {
978-
let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
979-
let lib_code = read_to_string(manifest_dir.join("src/lib.rs")).expect("failed to read lib.rs");
980-
981-
for f in get_manifest().features.keys() {
982-
if !(f.starts_with("__") || f == "default" || lib_code.contains(&format!("*{f}**"))) {
983-
panic!("Feature {f} is not documented");
984-
}
985-
}
986-
}
987-
988-
#[test]
989-
fn aliased_features_exist() {
990-
let checked_features = CHECKED_FEATURES.split(',');
991-
let manifest = get_manifest();
992-
for checked_feature in checked_features {
993-
if !manifest.features.iter().any(|(f, _)| f == checked_feature) {
994-
panic!(
995-
"Feature {checked_feature} was checked in the alias build step but it does not exist in core/tauri/Cargo.toml"
996-
);
997-
}
998-
}
999-
}
1000-
}
1001-
1002957
#[derive(Deserialize)]
1003958
#[serde(untagged)]
1004959
pub(crate) enum IconDto {
@@ -1035,22 +990,67 @@ impl From<IconDto> for Icon {
1035990

1036991
#[allow(unused)]
1037992
macro_rules! run_main_thread {
1038-
($self:ident, $ex:expr) => {{
993+
($handle:ident, $ex:expr) => {{
1039994
use std::sync::mpsc::channel;
1040995
let (tx, rx) = channel();
1041-
let self_ = $self.clone();
1042996
let task = move || {
1043997
let f = $ex;
1044-
let _ = tx.send(f(self_));
998+
let _ = tx.send(f());
1045999
};
1046-
$self.app_handle.run_on_main_thread(Box::new(task))?;
1047-
rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage)
1000+
$handle
1001+
.run_on_main_thread(task)
1002+
.and_then(|_| rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage))
10481003
}};
10491004
}
10501005

10511006
#[allow(unused)]
10521007
pub(crate) use run_main_thread;
10531008

1009+
#[cfg(any(test, feature = "test"))]
1010+
#[cfg_attr(docsrs, doc(cfg(feature = "test")))]
1011+
pub mod test;
1012+
1013+
#[cfg(test)]
1014+
mod tests {
1015+
use cargo_toml::Manifest;
1016+
use std::{env::var, fs::read_to_string, path::PathBuf, sync::OnceLock};
1017+
1018+
static MANIFEST: OnceLock<Manifest> = OnceLock::new();
1019+
const CHECKED_FEATURES: &str = include_str!(concat!(env!("OUT_DIR"), "/checked_features"));
1020+
1021+
fn get_manifest() -> &'static Manifest {
1022+
MANIFEST.get_or_init(|| {
1023+
let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
1024+
Manifest::from_path(manifest_dir.join("Cargo.toml")).expect("failed to parse Cargo manifest")
1025+
})
1026+
}
1027+
1028+
#[test]
1029+
fn features_are_documented() {
1030+
let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
1031+
let lib_code = read_to_string(manifest_dir.join("src/lib.rs")).expect("failed to read lib.rs");
1032+
1033+
for f in get_manifest().features.keys() {
1034+
if !(f.starts_with("__") || f == "default" || lib_code.contains(&format!("*{f}**"))) {
1035+
panic!("Feature {f} is not documented");
1036+
}
1037+
}
1038+
}
1039+
1040+
#[test]
1041+
fn aliased_features_exist() {
1042+
let checked_features = CHECKED_FEATURES.split(',');
1043+
let manifest = get_manifest();
1044+
for checked_feature in checked_features {
1045+
if !manifest.features.iter().any(|(f, _)| f == checked_feature) {
1046+
panic!(
1047+
"Feature {checked_feature} was checked in the alias build step but it does not exist in core/tauri/Cargo.toml"
1048+
);
1049+
}
1050+
}
1051+
}
1052+
}
1053+
10541054
#[cfg(test)]
10551055
mod test_utils {
10561056
use proptest::prelude::*;

core/tauri/src/menu/builders/check.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ impl CheckMenuItemBuilder {
6767
}
6868

6969
/// Build the menu item
70-
pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> CheckMenuItem<R> {
70+
pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> crate::Result<CheckMenuItem<R>> {
7171
if let Some(id) = self.id {
7272
CheckMenuItem::with_id(
7373
manager,

core/tauri/src/menu/builders/icon.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ impl IconMenuItemBuilder {
8787
}
8888

8989
/// Build the menu item
90-
pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> IconMenuItem<R> {
90+
pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> crate::Result<IconMenuItem<R>> {
9191
if self.icon.is_some() {
9292
if let Some(id) = self.id {
9393
IconMenuItem::with_id(

0 commit comments

Comments
 (0)