فهرست منبع

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>
Amr Bashir 1 سال پیش
والد
کامیت
e1eb911f5e

+ 5 - 0
.changes/menu-accelerator-generics.md

@@ -0,0 +1,5 @@
+---
+'tauri': 'patch:breaking'
+---
+
+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>`.

+ 5 - 0
.changes/menu-init-drop-main-thread.md

@@ -0,0 +1,5 @@
+---
+'tauri': 'patch:bug'
+---
+
+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.

+ 5 - 0
.changes/menu-init-result.md

@@ -0,0 +1,5 @@
+---
+'tauri': 'patch:breaking'
+---
+
+All menu item constructors have been changed to return a `Result<Self>`

+ 2 - 2
core/tauri/src/app.rs

@@ -1285,9 +1285,9 @@ tauri::Builder::default()
   ///       "File",
   ///       true,
   ///       &[
-  ///         &PredefinedMenuItem::close_window(handle, None),
+  ///         &PredefinedMenuItem::close_window(handle, None)?,
   ///         #[cfg(target_os = "macos")]
-  ///         &MenuItem::new(handle, "Hello", true, None),
+  ///         &MenuItem::new(handle, "Hello", true, None::<&str>)?,
   ///       ],
   ///     )?
   ///   ]));

+ 50 - 50
core/tauri/src/lib.rs

@@ -954,51 +954,6 @@ pub(crate) mod sealed {
   }
 }
 
-#[cfg(any(test, feature = "test"))]
-#[cfg_attr(docsrs, doc(cfg(feature = "test")))]
-pub mod test;
-
-#[cfg(test)]
-mod tests {
-  use cargo_toml::Manifest;
-  use std::{env::var, fs::read_to_string, path::PathBuf, sync::OnceLock};
-
-  static MANIFEST: OnceLock<Manifest> = OnceLock::new();
-  const CHECKED_FEATURES: &str = include_str!(concat!(env!("OUT_DIR"), "/checked_features"));
-
-  fn get_manifest() -> &'static Manifest {
-    MANIFEST.get_or_init(|| {
-      let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
-      Manifest::from_path(manifest_dir.join("Cargo.toml")).expect("failed to parse Cargo manifest")
-    })
-  }
-
-  #[test]
-  fn features_are_documented() {
-    let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
-    let lib_code = read_to_string(manifest_dir.join("src/lib.rs")).expect("failed to read lib.rs");
-
-    for f in get_manifest().features.keys() {
-      if !(f.starts_with("__") || f == "default" || lib_code.contains(&format!("*{f}**"))) {
-        panic!("Feature {f} is not documented");
-      }
-    }
-  }
-
-  #[test]
-  fn aliased_features_exist() {
-    let checked_features = CHECKED_FEATURES.split(',');
-    let manifest = get_manifest();
-    for checked_feature in checked_features {
-      if !manifest.features.iter().any(|(f, _)| f == checked_feature) {
-        panic!(
-          "Feature {checked_feature} was checked in the alias build step but it does not exist in core/tauri/Cargo.toml"
-        );
-      }
-    }
-  }
-}
-
 #[derive(Deserialize)]
 #[serde(untagged)]
 pub(crate) enum IconDto {
@@ -1035,22 +990,67 @@ impl From<IconDto> for Icon {
 
 #[allow(unused)]
 macro_rules! run_main_thread {
-  ($self:ident, $ex:expr) => {{
+  ($handle:ident, $ex:expr) => {{
     use std::sync::mpsc::channel;
     let (tx, rx) = channel();
-    let self_ = $self.clone();
     let task = move || {
       let f = $ex;
-      let _ = tx.send(f(self_));
+      let _ = tx.send(f());
     };
-    $self.app_handle.run_on_main_thread(Box::new(task))?;
-    rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage)
+    $handle
+      .run_on_main_thread(task)
+      .and_then(|_| rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage))
   }};
 }
 
 #[allow(unused)]
 pub(crate) use run_main_thread;
 
+#[cfg(any(test, feature = "test"))]
+#[cfg_attr(docsrs, doc(cfg(feature = "test")))]
+pub mod test;
+
+#[cfg(test)]
+mod tests {
+  use cargo_toml::Manifest;
+  use std::{env::var, fs::read_to_string, path::PathBuf, sync::OnceLock};
+
+  static MANIFEST: OnceLock<Manifest> = OnceLock::new();
+  const CHECKED_FEATURES: &str = include_str!(concat!(env!("OUT_DIR"), "/checked_features"));
+
+  fn get_manifest() -> &'static Manifest {
+    MANIFEST.get_or_init(|| {
+      let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
+      Manifest::from_path(manifest_dir.join("Cargo.toml")).expect("failed to parse Cargo manifest")
+    })
+  }
+
+  #[test]
+  fn features_are_documented() {
+    let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
+    let lib_code = read_to_string(manifest_dir.join("src/lib.rs")).expect("failed to read lib.rs");
+
+    for f in get_manifest().features.keys() {
+      if !(f.starts_with("__") || f == "default" || lib_code.contains(&format!("*{f}**"))) {
+        panic!("Feature {f} is not documented");
+      }
+    }
+  }
+
+  #[test]
+  fn aliased_features_exist() {
+    let checked_features = CHECKED_FEATURES.split(',');
+    let manifest = get_manifest();
+    for checked_feature in checked_features {
+      if !manifest.features.iter().any(|(f, _)| f == checked_feature) {
+        panic!(
+          "Feature {checked_feature} was checked in the alias build step but it does not exist in core/tauri/Cargo.toml"
+        );
+      }
+    }
+  }
+}
+
 #[cfg(test)]
 mod test_utils {
   use proptest::prelude::*;

+ 1 - 1
core/tauri/src/menu/builders/check.rs

@@ -67,7 +67,7 @@ impl CheckMenuItemBuilder {
   }
 
   /// Build the menu item
-  pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> CheckMenuItem<R> {
+  pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> crate::Result<CheckMenuItem<R>> {
     if let Some(id) = self.id {
       CheckMenuItem::with_id(
         manager,

+ 1 - 1
core/tauri/src/menu/builders/icon.rs

@@ -87,7 +87,7 @@ impl IconMenuItemBuilder {
   }
 
   /// Build the menu item
-  pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> IconMenuItem<R> {
+  pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> crate::Result<IconMenuItem<R>> {
     if self.icon.is_some() {
       if let Some(id) = self.id {
         IconMenuItem::with_id(

+ 42 - 46
core/tauri/src/menu/builders/menu.rs

@@ -19,10 +19,10 @@ use crate::{menu::*, Icon, Manager, Runtime};
 ///     #   height: 0,
 ///     # };
 ///     let menu = MenuBuilder::new(handle)
-///       .item(&MenuItem::new(handle, "MenuItem 1", true, None))
+///       .item(&MenuItem::new(handle, "MenuItem 1", true, None::<&str>)?)
 ///       .items(&[
-///         &CheckMenuItem::new(handle, "CheckMenuItem 1", true, true, None),
-///         &IconMenuItem::new(handle, "IconMenuItem 1", true, Some(icon1), None),
+///         &CheckMenuItem::new(handle, "CheckMenuItem 1", true, true, None::<&str>)?,
+///         &IconMenuItem::new(handle, "IconMenuItem 1", true, Some(icon1), None::<&str>)?,
 ///       ])
 ///       .separator()
 ///       .cut()
@@ -40,7 +40,7 @@ use crate::{menu::*, Icon, Manager, Runtime};
 pub struct MenuBuilder<'m, R: Runtime, M: Manager<R>> {
   id: Option<MenuId>,
   manager: &'m M,
-  items: Vec<MenuItemKind<R>>,
+  items: Vec<crate::Result<MenuItemKind<R>>>,
 }
 
 impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
@@ -70,7 +70,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
 
   /// Add this item to the menu.
   pub fn item(mut self, item: &dyn IsMenuItem<R>) -> Self {
-    self.items.push(item.kind());
+    self.items.push(Ok(item.kind()));
     self
   }
 
@@ -86,23 +86,24 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn text<I: Into<MenuId>, S: AsRef<str>>(mut self, id: I, text: S) -> Self {
     self
       .items
-      .push(MenuItem::with_id(self.manager, id, text, true, None).kind());
+      .push(MenuItem::with_id(self.manager, id, text, true, None::<&str>).map(|i| i.kind()));
     self
   }
 
   /// Add a [CheckMenuItem] to the menu.
   pub fn check<I: Into<MenuId>, S: AsRef<str>>(mut self, id: I, text: S) -> Self {
-    self
-      .items
-      .push(CheckMenuItem::with_id(self.manager, id, text, true, true, None).kind());
+    self.items.push(
+      CheckMenuItem::with_id(self.manager, id, text, true, true, None::<&str>).map(|i| i.kind()),
+    );
     self
   }
 
   /// Add an [IconMenuItem] to the menu.
   pub fn icon<I: Into<MenuId>, S: AsRef<str>>(mut self, id: I, text: S, icon: Icon) -> Self {
-    self
-      .items
-      .push(IconMenuItem::with_id(self.manager, id, text, true, Some(icon), None).kind());
+    self.items.push(
+      IconMenuItem::with_id(self.manager, id, text, true, Some(icon), None::<&str>)
+        .map(|i| i.kind()),
+    );
     self
   }
 
@@ -118,7 +119,8 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
     icon: NativeIcon,
   ) -> Self {
     self.items.push(
-      IconMenuItem::with_id_and_native_icon(self.manager, id, text, true, Some(icon), None).kind(),
+      IconMenuItem::with_id_and_native_icon(self.manager, id, text, true, Some(icon), None::<&str>)
+        .map(|i| i.kind()),
     );
     self
   }
@@ -127,7 +129,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn separator(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::separator(self.manager).kind());
+      .push(PredefinedMenuItem::separator(self.manager).map(|i| i.kind()));
     self
   }
 
@@ -135,7 +137,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn copy(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::copy(self.manager, None).kind());
+      .push(PredefinedMenuItem::copy(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -143,7 +145,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn cut(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::cut(self.manager, None).kind());
+      .push(PredefinedMenuItem::cut(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -151,7 +153,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn paste(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::paste(self.manager, None).kind());
+      .push(PredefinedMenuItem::paste(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -159,7 +161,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn select_all(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::select_all(self.manager, None).kind());
+      .push(PredefinedMenuItem::select_all(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -171,7 +173,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn undo(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::undo(self.manager, None).kind());
+      .push(PredefinedMenuItem::undo(self.manager, None).map(|i| i.kind()));
     self
   }
   /// Add Redo menu item to the menu.
@@ -182,7 +184,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn redo(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::redo(self.manager, None).kind());
+      .push(PredefinedMenuItem::redo(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -194,7 +196,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn minimize(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::minimize(self.manager, None).kind());
+      .push(PredefinedMenuItem::minimize(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -206,7 +208,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn maximize(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::maximize(self.manager, None).kind());
+      .push(PredefinedMenuItem::maximize(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -218,7 +220,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn fullscreen(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::fullscreen(self.manager, None).kind());
+      .push(PredefinedMenuItem::fullscreen(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -230,7 +232,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn hide(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::hide(self.manager, None).kind());
+      .push(PredefinedMenuItem::hide(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -242,7 +244,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn hide_others(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::hide_others(self.manager, None).kind());
+      .push(PredefinedMenuItem::hide_others(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -254,7 +256,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn show_all(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::show_all(self.manager, None).kind());
+      .push(PredefinedMenuItem::show_all(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -266,7 +268,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn close_window(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::close_window(self.manager, None).kind());
+      .push(PredefinedMenuItem::close_window(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -278,7 +280,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn quit(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::quit(self.manager, None).kind());
+      .push(PredefinedMenuItem::quit(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -286,7 +288,7 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn about(mut self, metadata: Option<AboutMetadata>) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::about(self.manager, None, metadata).kind());
+      .push(PredefinedMenuItem::about(self.manager, None, metadata).map(|i| i.kind()));
     self
   }
 
@@ -298,29 +300,23 @@ impl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {
   pub fn services(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::services(self.manager, None).kind());
+      .push(PredefinedMenuItem::services(self.manager, None).map(|i| i.kind()));
     self
   }
 
   /// Builds this menu
   pub fn build(self) -> crate::Result<Menu<R>> {
-    if self.items.is_empty() {
-      Ok(if let Some(id) = self.id {
-        Menu::with_id(self.manager, id)
-      } else {
-        Menu::new(self.manager)
-      })
+    let menu = if let Some(id) = self.id {
+      Menu::with_id(self.manager, id)?
     } else {
-      let items = self
-        .items
-        .iter()
-        .map(|i| i as &dyn IsMenuItem<R>)
-        .collect::<Vec<_>>();
-      if let Some(id) = self.id {
-        Menu::with_id_and_items(self.manager, id, &items)
-      } else {
-        Menu::with_items(self.manager, &items)
-      }
+      Menu::new(self.manager)?
+    };
+
+    for item in self.items {
+      let item = item?;
+      menu.append(&item)?;
     }
+
+    Ok(menu)
   }
 }

+ 1 - 1
core/tauri/src/menu/builders/normal.rs

@@ -58,7 +58,7 @@ impl MenuItemBuilder {
   }
 
   /// Build the menu item
-  pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> MenuItem<R> {
+  pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> crate::Result<MenuItem<R>> {
     if let Some(id) = self.id {
       MenuItem::with_id(manager, id, self.text, self.enabled, self.accelerator)
     } else {

+ 43 - 47
core/tauri/src/menu/builders/submenu.rs

@@ -19,12 +19,12 @@ use crate::{menu::*, Icon, Manager, Runtime};
 ///     #   height: 0,
 ///     # };
 ///     # let icon2 = icon1.clone();
-///     let menu = Menu::new(handle);
+///     let menu = Menu::new(handle)?;
 ///     let submenu = SubmenuBuilder::new(handle, "File")
-///       .item(&MenuItem::new(handle, "MenuItem 1", true, None))
+///       .item(&MenuItem::new(handle, "MenuItem 1", true, None::<&str>)?)
 ///       .items(&[
-///         &CheckMenuItem::new(handle, "CheckMenuItem 1", true, true, None),
-///         &IconMenuItem::new(handle, "IconMenuItem 1", true, Some(icon1), None),
+///         &CheckMenuItem::new(handle, "CheckMenuItem 1", true, true, None::<&str>)?,
+///         &IconMenuItem::new(handle, "IconMenuItem 1", true, Some(icon1), None::<&str>)?,
 ///       ])
 ///       .separator()
 ///       .cut()
@@ -45,7 +45,7 @@ pub struct SubmenuBuilder<'m, R: Runtime, M: Manager<R>> {
   manager: &'m M,
   text: String,
   enabled: bool,
-  items: Vec<MenuItemKind<R>>,
+  items: Vec<crate::Result<MenuItemKind<R>>>,
 }
 
 impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
@@ -91,7 +91,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
 
   /// Add this item to the submenu.
   pub fn item(mut self, item: &dyn IsMenuItem<R>) -> Self {
-    self.items.push(item.kind());
+    self.items.push(Ok(item.kind()));
     self
   }
 
@@ -107,23 +107,24 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn text<I: Into<MenuId>, S: AsRef<str>>(mut self, id: I, text: S) -> Self {
     self
       .items
-      .push(MenuItem::with_id(self.manager, id, text, true, None).kind());
+      .push(MenuItem::with_id(self.manager, id, text, true, None::<&str>).map(|i| i.kind()));
     self
   }
 
   /// Add a [CheckMenuItem] to the submenu.
   pub fn check<I: Into<MenuId>, S: AsRef<str>>(mut self, id: I, text: S) -> Self {
-    self
-      .items
-      .push(CheckMenuItem::with_id(self.manager, id, text, true, true, None).kind());
+    self.items.push(
+      CheckMenuItem::with_id(self.manager, id, text, true, true, None::<&str>).map(|i| i.kind()),
+    );
     self
   }
 
   /// Add an [IconMenuItem] to the submenu.
   pub fn icon<I: Into<MenuId>, S: AsRef<str>>(mut self, id: I, text: S, icon: Icon) -> Self {
-    self
-      .items
-      .push(IconMenuItem::with_id(self.manager, id, text, true, Some(icon), None).kind());
+    self.items.push(
+      IconMenuItem::with_id(self.manager, id, text, true, Some(icon), None::<&str>)
+        .map(|i| i.kind()),
+    );
     self
   }
 
@@ -139,7 +140,8 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
     icon: NativeIcon,
   ) -> Self {
     self.items.push(
-      IconMenuItem::with_id_and_native_icon(self.manager, id, text, true, Some(icon), None).kind(),
+      IconMenuItem::with_id_and_native_icon(self.manager, id, text, true, Some(icon), None::<&str>)
+        .map(|i| i.kind()),
     );
     self
   }
@@ -148,7 +150,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn separator(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::separator(self.manager).kind());
+      .push(PredefinedMenuItem::separator(self.manager).map(|i| i.kind()));
     self
   }
 
@@ -156,7 +158,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn copy(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::copy(self.manager, None).kind());
+      .push(PredefinedMenuItem::copy(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -164,7 +166,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn cut(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::cut(self.manager, None).kind());
+      .push(PredefinedMenuItem::cut(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -172,7 +174,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn paste(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::paste(self.manager, None).kind());
+      .push(PredefinedMenuItem::paste(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -180,7 +182,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn select_all(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::select_all(self.manager, None).kind());
+      .push(PredefinedMenuItem::select_all(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -192,7 +194,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn undo(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::undo(self.manager, None).kind());
+      .push(PredefinedMenuItem::undo(self.manager, None).map(|i| i.kind()));
     self
   }
   /// Add Redo menu item to the submenu.
@@ -203,7 +205,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn redo(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::redo(self.manager, None).kind());
+      .push(PredefinedMenuItem::redo(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -215,7 +217,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn minimize(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::minimize(self.manager, None).kind());
+      .push(PredefinedMenuItem::minimize(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -227,7 +229,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn maximize(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::maximize(self.manager, None).kind());
+      .push(PredefinedMenuItem::maximize(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -239,7 +241,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn fullscreen(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::fullscreen(self.manager, None).kind());
+      .push(PredefinedMenuItem::fullscreen(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -251,7 +253,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn hide(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::hide(self.manager, None).kind());
+      .push(PredefinedMenuItem::hide(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -263,7 +265,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn hide_others(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::hide_others(self.manager, None).kind());
+      .push(PredefinedMenuItem::hide_others(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -275,7 +277,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn show_all(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::show_all(self.manager, None).kind());
+      .push(PredefinedMenuItem::show_all(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -287,7 +289,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn close_window(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::close_window(self.manager, None).kind());
+      .push(PredefinedMenuItem::close_window(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -299,7 +301,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn quit(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::quit(self.manager, None).kind());
+      .push(PredefinedMenuItem::quit(self.manager, None).map(|i| i.kind()));
     self
   }
 
@@ -307,7 +309,7 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn about(mut self, metadata: Option<AboutMetadata>) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::about(self.manager, None, metadata).kind());
+      .push(PredefinedMenuItem::about(self.manager, None, metadata).map(|i| i.kind()));
     self
   }
 
@@ -319,29 +321,23 @@ impl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {
   pub fn services(mut self) -> Self {
     self
       .items
-      .push(PredefinedMenuItem::services(self.manager, None).kind());
+      .push(PredefinedMenuItem::services(self.manager, None).map(|i| i.kind()));
     self
   }
 
   /// Builds this submenu
   pub fn build(self) -> crate::Result<Submenu<R>> {
-    if self.items.is_empty() {
-      Ok(if let Some(id) = self.id {
-        Submenu::with_id(self.manager, id, self.text, self.enabled)
-      } else {
-        Submenu::new(self.manager, self.text, self.enabled)
-      })
+    let submenu = if let Some(id) = self.id {
+      Submenu::with_id(self.manager, id, self.text, self.enabled)?
     } else {
-      let items = self
-        .items
-        .iter()
-        .map(|i| i as &dyn IsMenuItem<R>)
-        .collect::<Vec<_>>();
-      if let Some(id) = self.id {
-        Submenu::with_id_and_items(self.manager, id, self.text, self.enabled, &items)
-      } else {
-        Submenu::with_items(self.manager, self.text, self.enabled, &items)
-      }
+      Submenu::new(self.manager, self.text, self.enabled)?
+    };
+
+    for item in self.items {
+      let item = item?;
+      submenu.append(&item)?;
     }
+
+    Ok(submenu)
   }
 }

+ 72 - 87
core/tauri/src/menu/check.rs

@@ -2,116 +2,100 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use crate::{menu::MenuId, resources::Resource, run_main_thread, AppHandle, Manager, Runtime};
-
-/// A menu item inside a [`Menu`] or [`Submenu`]
-/// and usually contains a text and a check mark or a similar toggle
-/// that corresponds to a checked and unchecked states.
-///
-/// [`Menu`]: super::Menu
-/// [`Submenu`]: super::Submenu
-pub struct CheckMenuItem<R: Runtime> {
-  pub(crate) id: MenuId,
-  pub(crate) inner: muda::CheckMenuItem,
-  pub(crate) app_handle: AppHandle<R>,
-}
-
-impl<R: Runtime> Clone for CheckMenuItem<R> {
-  fn clone(&self) -> Self {
-    Self {
-      id: self.id.clone(),
-      inner: self.inner.clone(),
-      app_handle: self.app_handle.clone(),
-    }
-  }
-}
-
-/// # Safety
-///
-/// We make sure it always runs on the main thread.
-unsafe impl<R: Runtime> Sync for CheckMenuItem<R> {}
-unsafe impl<R: Runtime> Send for CheckMenuItem<R> {}
-
-impl<R: Runtime> super::sealed::IsMenuItemBase for CheckMenuItem<R> {
-  fn inner_muda(&self) -> &dyn muda::IsMenuItem {
-    &self.inner
-  }
-}
+use std::sync::Arc;
 
-impl<R: Runtime> super::IsMenuItem<R> for CheckMenuItem<R> {
-  fn kind(&self) -> super::MenuItemKind<R> {
-    super::MenuItemKind::Check(self.clone())
-  }
+use super::run_item_main_thread;
+use crate::menu::CheckMenuItemInner;
+use crate::run_main_thread;
+use crate::{menu::MenuId, AppHandle, Manager, Runtime};
 
-  fn id(&self) -> &MenuId {
-    &self.id
-  }
-}
+use super::CheckMenuItem;
 
 impl<R: Runtime> CheckMenuItem<R> {
   /// Create a new menu item.
   ///
   /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
   /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
-  pub fn new<M: Manager<R>, S: AsRef<str>>(
+  pub fn new<M, T, A>(
     manager: &M,
-    text: S,
+    text: T,
     enabled: bool,
     checked: bool,
-    acccelerator: Option<S>,
-  ) -> Self {
-    let item = muda::CheckMenuItem::new(
-      text,
-      enabled,
-      checked,
-      acccelerator.and_then(|s| s.as_ref().parse().ok()),
-    );
-    Self {
-      id: item.id().clone(),
-      inner: item,
-      app_handle: manager.app_handle().clone(),
-    }
+    accelerator: Option<A>,
+  ) -> crate::Result<Self>
+  where
+    M: Manager<R>,
+    T: AsRef<str>,
+    A: AsRef<str>,
+  {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.as_ref().to_owned();
+    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::CheckMenuItem::new(text, enabled, checked, accelerator);
+      CheckMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Create a new menu item with the specified id.
   ///
   /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
   /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
-  pub fn with_id<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(
+  pub fn with_id<M, I, T, A>(
     manager: &M,
     id: I,
-    text: S,
+    text: T,
     enabled: bool,
     checked: bool,
-    acccelerator: Option<S>,
-  ) -> Self {
-    let item = muda::CheckMenuItem::with_id(
-      id,
-      text,
-      enabled,
-      checked,
-      acccelerator.and_then(|s| s.as_ref().parse().ok()),
-    );
-    Self {
-      id: item.id().clone(),
-      inner: item,
-      app_handle: manager.app_handle().clone(),
-    }
+    accelerator: Option<A>,
+  ) -> crate::Result<Self>
+  where
+    M: Manager<R>,
+    I: Into<MenuId>,
+    T: AsRef<str>,
+    A: AsRef<str>,
+  {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let id = id.into();
+    let text = text.as_ref().to_owned();
+    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::CheckMenuItem::with_id(id.clone(), text, enabled, checked, accelerator);
+      CheckMenuItemInner {
+        id,
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// The application handle associated with this type.
   pub fn app_handle(&self) -> &AppHandle<R> {
-    &self.app_handle
+    &self.0.app_handle
   }
 
   /// Returns a unique identifier associated with this menu item.
   pub fn id(&self) -> &MenuId {
-    &self.id
+    &self.0.id
   }
 
   /// Get the text for this menu item.
   pub fn text(&self) -> crate::Result<String> {
-    run_main_thread!(self, |self_: Self| self_.inner.text())
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().text())
   }
 
   /// Set the text for this menu item. `text` could optionally contain
@@ -119,34 +103,35 @@ impl<R: Runtime> CheckMenuItem<R> {
   /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
   pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {
     let text = text.as_ref().to_string();
-    run_main_thread!(self, |self_: Self| self_.inner.set_text(text))
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_text(text))
   }
 
   /// Get whether this menu item is enabled or not.
   pub fn is_enabled(&self) -> crate::Result<bool> {
-    run_main_thread!(self, |self_: Self| self_.inner.is_enabled())
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().is_enabled())
   }
 
   /// Enable or disable this menu item.
   pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {
-    run_main_thread!(self, |self_: Self| self_.inner.set_enabled(enabled))
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_enabled(enabled))
   }
 
   /// Set this menu item accelerator.
-  pub fn set_accelerator<S: AsRef<str>>(&self, acccelerator: Option<S>) -> crate::Result<()> {
-    let accel = acccelerator.and_then(|s| s.as_ref().parse().ok());
-    run_main_thread!(self, |self_: Self| self_.inner.set_accelerator(accel))?.map_err(Into::into)
+  pub fn set_accelerator<S: AsRef<str>>(&self, accelerator: Option<S>) -> crate::Result<()> {
+    let accel = accelerator.and_then(|s| s.as_ref().parse().ok());
+    run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
+      .set_accelerator(accel))?
+    .map_err(Into::into)
   }
 
   /// Get whether this check menu item is checked or not.
   pub fn is_checked(&self) -> crate::Result<bool> {
-    run_main_thread!(self, |self_: Self| self_.inner.is_checked())
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().is_checked())
   }
 
   /// Check or Uncheck this check menu item.
   pub fn set_checked(&self, checked: bool) -> crate::Result<()> {
-    run_main_thread!(self, |self_: Self| self_.inner.set_checked(checked))
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_checked(checked))
   }
 }
-
-impl<R: Runtime> Resource for CheckMenuItem<R> {}

+ 131 - 123
core/tauri/src/menu/icon.rs

@@ -2,103 +2,86 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::NativeIcon;
-use crate::{
-  menu::MenuId, resources::Resource, run_main_thread, AppHandle, Icon, Manager, Runtime,
-};
-
-/// A menu item inside a [`Menu`] or [`Submenu`]
-/// and usually contains an icon and a text.
-///
-/// [`Menu`]: super::Menu
-/// [`Submenu`]: super::Submenu
-pub struct IconMenuItem<R: Runtime> {
-  pub(crate) id: MenuId,
-  pub(crate) inner: muda::IconMenuItem,
-  pub(crate) app_handle: AppHandle<R>,
-}
-
-impl<R: Runtime> Clone for IconMenuItem<R> {
-  fn clone(&self) -> Self {
-    Self {
-      id: self.id.clone(),
-      inner: self.inner.clone(),
-      app_handle: self.app_handle.clone(),
-    }
-  }
-}
+use std::sync::Arc;
 
-/// # Safety
-///
-/// We make sure it always runs on the main thread.
-unsafe impl<R: Runtime> Sync for IconMenuItem<R> {}
-unsafe impl<R: Runtime> Send for IconMenuItem<R> {}
-
-impl<R: Runtime> super::sealed::IsMenuItemBase for IconMenuItem<R> {
-  fn inner_muda(&self) -> &dyn muda::IsMenuItem {
-    &self.inner
-  }
-}
-
-impl<R: Runtime> super::IsMenuItem<R> for IconMenuItem<R> {
-  fn kind(&self) -> super::MenuItemKind<R> {
-    super::MenuItemKind::Icon(self.clone())
-  }
-
-  fn id(&self) -> &MenuId {
-    &self.id
-  }
-}
+use super::run_item_main_thread;
+use super::{IconMenuItem, NativeIcon};
+use crate::menu::IconMenuItemInner;
+use crate::run_main_thread;
+use crate::{menu::MenuId, AppHandle, Icon, Manager, Runtime};
 
 impl<R: Runtime> IconMenuItem<R> {
   /// Create a new menu item.
   ///
   /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
   /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
-  pub fn new<M: Manager<R>, S: AsRef<str>>(
+  pub fn new<M, T, A>(
     manager: &M,
-    text: S,
+    text: T,
     enabled: bool,
     icon: Option<Icon>,
-    acccelerator: Option<S>,
-  ) -> Self {
-    let item = muda::IconMenuItem::new(
-      text,
-      enabled,
-      icon.and_then(|i| i.try_into().ok()),
-      acccelerator.and_then(|s| s.as_ref().parse().ok()),
-    );
-    Self {
-      id: item.id().clone(),
-      inner: item,
-      app_handle: manager.app_handle().clone(),
-    }
+    accelerator: Option<A>,
+  ) -> crate::Result<Self>
+  where
+    M: Manager<R>,
+    T: AsRef<str>,
+    A: AsRef<str>,
+  {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.as_ref().to_owned();
+    let icon = icon.and_then(|i| i.try_into().ok());
+    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::IconMenuItem::new(text, enabled, icon, accelerator);
+      IconMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Create a new menu item with the specified id.
   ///
   /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
   /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
-  pub fn with_id<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(
+  pub fn with_id<M, I, T, A>(
     manager: &M,
     id: I,
-    text: S,
+    text: T,
     enabled: bool,
     icon: Option<Icon>,
-    acccelerator: Option<S>,
-  ) -> Self {
-    let item = muda::IconMenuItem::with_id(
-      id,
-      text,
-      enabled,
-      icon.and_then(|i| i.try_into().ok()),
-      acccelerator.and_then(|s| s.as_ref().parse().ok()),
-    );
-    Self {
-      id: item.id().clone(),
-      inner: item,
-      app_handle: manager.app_handle().clone(),
-    }
+    accelerator: Option<A>,
+  ) -> crate::Result<Self>
+  where
+    M: Manager<R>,
+    I: Into<MenuId>,
+    T: AsRef<str>,
+    A: AsRef<str>,
+  {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let id = id.into();
+    let text = text.as_ref().to_owned();
+    let icon = icon.and_then(|i| i.try_into().ok());
+    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::IconMenuItem::with_id(id.clone(), text, enabled, icon, accelerator);
+      IconMenuItemInner {
+        id,
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Create a new icon menu item but with a native icon.
@@ -108,24 +91,35 @@ impl<R: Runtime> IconMenuItem<R> {
   /// ## Platform-specific:
   ///
   /// - **Windows / Linux**: Unsupported.
-  pub fn with_native_icon<M: Manager<R>, S: AsRef<str>>(
+  pub fn with_native_icon<M, T, A>(
     manager: &M,
-    text: S,
+    text: T,
     enabled: bool,
     native_icon: Option<NativeIcon>,
-    acccelerator: Option<S>,
-  ) -> Self {
-    let item = muda::IconMenuItem::with_native_icon(
-      text,
-      enabled,
-      native_icon.map(Into::into),
-      acccelerator.and_then(|s| s.as_ref().parse().ok()),
-    );
-    Self {
-      id: item.id().clone(),
-      inner: item,
-      app_handle: manager.app_handle().clone(),
-    }
+    accelerator: Option<A>,
+  ) -> crate::Result<Self>
+  where
+    M: Manager<R>,
+    T: AsRef<str>,
+    A: AsRef<str>,
+  {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.as_ref().to_owned();
+    let icon = native_icon.map(Into::into);
+    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::IconMenuItem::with_native_icon(text, enabled, icon, accelerator);
+      IconMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Create a new icon menu item with the specified id but with a native icon.
@@ -135,41 +129,54 @@ impl<R: Runtime> IconMenuItem<R> {
   /// ## Platform-specific:
   ///
   /// - **Windows / Linux**: Unsupported.
-  pub fn with_id_and_native_icon<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(
+  pub fn with_id_and_native_icon<M, I, T, A>(
     manager: &M,
     id: I,
-    text: S,
+    text: T,
     enabled: bool,
     native_icon: Option<NativeIcon>,
-    acccelerator: Option<S>,
-  ) -> Self {
-    let item = muda::IconMenuItem::with_id_and_native_icon(
-      id,
-      text,
-      enabled,
-      native_icon.map(Into::into),
-      acccelerator.and_then(|s| s.as_ref().parse().ok()),
-    );
-    Self {
-      id: item.id().clone(),
-      inner: item,
-      app_handle: manager.app_handle().clone(),
-    }
+    accelerator: Option<A>,
+  ) -> crate::Result<Self>
+  where
+    M: Manager<R>,
+    I: Into<MenuId>,
+    T: AsRef<str>,
+    A: AsRef<str>,
+  {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let id = id.into();
+    let text = text.as_ref().to_owned();
+    let icon = native_icon.map(Into::into);
+    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());
+
+    let item = run_main_thread!(handle, || {
+      let item =
+        muda::IconMenuItem::with_id_and_native_icon(id.clone(), text, enabled, icon, accelerator);
+      IconMenuItemInner {
+        id,
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// The application handle associated with this type.
   pub fn app_handle(&self) -> &AppHandle<R> {
-    &self.app_handle
+    &self.0.app_handle
   }
 
   /// Returns a unique identifier associated with this menu item.
   pub fn id(&self) -> &MenuId {
-    &self.id
+    &self.0.id
   }
 
   /// Get the text for this menu item.
   pub fn text(&self) -> crate::Result<String> {
-    run_main_thread!(self, |self_: Self| self_.inner.text())
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().text())
   }
 
   /// Set the text for this menu item. `text` could optionally contain
@@ -177,29 +184,32 @@ impl<R: Runtime> IconMenuItem<R> {
   /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
   pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {
     let text = text.as_ref().to_string();
-    run_main_thread!(self, |self_: Self| self_.inner.set_text(text))
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_text(text))
   }
 
   /// Get whether this menu item is enabled or not.
   pub fn is_enabled(&self) -> crate::Result<bool> {
-    run_main_thread!(self, |self_: Self| self_.inner.is_enabled())
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().is_enabled())
   }
 
   /// Enable or disable this menu item.
   pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {
-    run_main_thread!(self, |self_: Self| self_.inner.set_enabled(enabled))
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_enabled(enabled))
   }
 
   /// Set this menu item accelerator.
-  pub fn set_accelerator<S: AsRef<str>>(&self, acccelerator: Option<S>) -> crate::Result<()> {
-    let accel = acccelerator.and_then(|s| s.as_ref().parse().ok());
-    run_main_thread!(self, |self_: Self| self_.inner.set_accelerator(accel))?.map_err(Into::into)
+  pub fn set_accelerator<S: AsRef<str>>(&self, accelerator: Option<S>) -> crate::Result<()> {
+    let accel = accelerator.and_then(|s| s.as_ref().parse().ok());
+    run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
+      .set_accelerator(accel))?
+    .map_err(Into::into)
   }
 
   /// Change this menu item icon or remove it.
   pub fn set_icon(&self, icon: Option<Icon>) -> crate::Result<()> {
-    run_main_thread!(self, |self_: Self| self_
-      .inner
+    run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
       .set_icon(icon.and_then(|i| i.try_into().ok())))
   }
 
@@ -210,12 +220,10 @@ impl<R: Runtime> IconMenuItem<R> {
   /// - **Windows / Linux**: Unsupported.
   pub fn set_native_icon(&self, _icon: Option<NativeIcon>) -> crate::Result<()> {
     #[cfg(target_os = "macos")]
-    return run_main_thread!(self, |self_: Self| self_
-      .inner
+    return run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
       .set_native_icon(_icon.map(Into::into)));
     #[allow(unreachable_code)]
     Ok(())
   }
 }
-
-impl<R: Runtime> Resource for IconMenuItem<R> {}

+ 83 - 89
core/tauri/src/menu/menu.rs

@@ -2,11 +2,16 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+use std::sync::Arc;
+
+use super::run_item_main_thread;
 use super::sealed::ContextMenuBase;
-use super::{AboutMetadata, IsMenuItem, MenuItemKind, PredefinedMenuItem, Submenu};
-use crate::resources::Resource;
+use super::{
+  AboutMetadata, IsMenuItem, Menu, MenuInner, MenuItemKind, PredefinedMenuItem, Submenu,
+};
+use crate::run_main_thread;
 use crate::Window;
-use crate::{run_main_thread, AppHandle, Manager, Position, Runtime};
+use crate::{AppHandle, Manager, Position, Runtime};
 use muda::ContextMenu;
 use muda::MenuId;
 
@@ -15,30 +20,6 @@ pub const WINDOW_SUBMENU_ID: &str = "__tauri_window_menu__";
 /// Expected submenu id of the Help menu for macOS.
 pub const HELP_SUBMENU_ID: &str = "__tauri_help_menu__";
 
-/// A type that is either a menu bar on the window
-/// on Windows and Linux or as a global menu in the menubar on macOS.
-pub struct Menu<R: Runtime> {
-  pub(crate) id: MenuId,
-  pub(crate) inner: muda::Menu,
-  pub(crate) app_handle: AppHandle<R>,
-}
-
-/// # Safety
-///
-/// We make sure it always runs on the main thread.
-unsafe impl<R: Runtime> Sync for Menu<R> {}
-unsafe impl<R: Runtime> Send for Menu<R> {}
-
-impl<R: Runtime> Clone for Menu<R> {
-  fn clone(&self) -> Self {
-    Self {
-      id: self.id.clone(),
-      inner: self.inner.clone(),
-      app_handle: self.app_handle.clone(),
-    }
-  }
-}
-
 impl<R: Runtime> super::ContextMenu for Menu<R> {
   fn popup<T: Runtime>(&self, window: Window<T>) -> crate::Result<()> {
     self.popup_inner(window, None::<Position>)
@@ -60,7 +41,7 @@ impl<R: Runtime> ContextMenuBase for Menu<R> {
     position: Option<P>,
   ) -> crate::Result<()> {
     let position = position.map(Into::into).map(super::into_position);
-    run_main_thread!(self, move |self_: Self| {
+    run_item_main_thread!(self, move |self_: Self| {
       #[cfg(target_os = "macos")]
       if let Ok(view) = window.ns_view() {
         self_
@@ -88,33 +69,48 @@ impl<R: Runtime> ContextMenuBase for Menu<R> {
     })
   }
   fn inner_context(&self) -> &dyn muda::ContextMenu {
-    &self.inner
+    (*self.0).as_ref()
   }
 
   fn inner_context_owned(&self) -> Box<dyn muda::ContextMenu> {
-    Box::new(self.inner.clone())
+    Box::new((*self.0).as_ref().clone())
   }
 }
 
 impl<R: Runtime> Menu<R> {
   /// Creates a new menu.
-  pub fn new<M: Manager<R>>(manager: &M) -> Self {
-    let menu = muda::Menu::new();
-    Self {
-      id: menu.id().clone(),
-      inner: menu,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn new<M: Manager<R>>(manager: &M) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let menu = run_main_thread!(handle, || {
+      let menu = muda::Menu::new();
+      MenuInner {
+        id: menu.id().clone(),
+        inner: Some(menu),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(menu)))
   }
 
   /// Creates a new menu with the specified id.
-  pub fn with_id<M: Manager<R>, I: Into<MenuId>>(manager: &M, id: I) -> Self {
-    let menu = muda::Menu::with_id(id);
-    Self {
-      id: menu.id().clone(),
-      inner: menu,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn with_id<M: Manager<R>, I: Into<MenuId>>(manager: &M, id: I) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let id = id.into();
+    let menu = run_main_thread!(handle, || {
+      let menu = muda::Menu::with_id(id.clone());
+      MenuInner {
+        id,
+        inner: Some(menu),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(menu)))
   }
 
   /// Creates a new menu with given `items`. It calls [`Menu::new`] and [`Menu::append_items`] internally.
@@ -122,7 +118,7 @@ impl<R: Runtime> Menu<R> {
     manager: &M,
     items: &[&dyn IsMenuItem<R>],
   ) -> crate::Result<Self> {
-    let menu = Self::new(manager);
+    let menu = Self::new(manager)?;
     menu.append_items(items)?;
     Ok(menu)
   }
@@ -134,7 +130,7 @@ impl<R: Runtime> Menu<R> {
     id: I,
     items: &[&dyn IsMenuItem<R>],
   ) -> crate::Result<Self> {
-    let menu = Self::with_id(manager, id);
+    let menu = Self::with_id(manager, id)?;
     menu.append_items(items)?;
     Ok(menu)
   }
@@ -157,11 +153,11 @@ impl<R: Runtime> Menu<R> {
       "Window",
       true,
       &[
-        &PredefinedMenuItem::minimize(app_handle, None),
-        &PredefinedMenuItem::maximize(app_handle, None),
+        &PredefinedMenuItem::minimize(app_handle, None)?,
+        &PredefinedMenuItem::maximize(app_handle, None)?,
         #[cfg(target_os = "macos")]
-        &PredefinedMenuItem::separator(app_handle),
-        &PredefinedMenuItem::close_window(app_handle, None),
+        &PredefinedMenuItem::separator(app_handle)?,
+        &PredefinedMenuItem::close_window(app_handle, None)?,
       ],
     )?;
 
@@ -172,7 +168,7 @@ impl<R: Runtime> Menu<R> {
       true,
       &[
         #[cfg(not(target_os = "macos"))]
-        &PredefinedMenuItem::about(app_handle, None, Some(about_metadata)),
+        &PredefinedMenuItem::about(app_handle, None, Some(about_metadata))?,
       ],
     )?;
 
@@ -185,14 +181,14 @@ impl<R: Runtime> Menu<R> {
           pkg_info.name.clone(),
           true,
           &[
-            &PredefinedMenuItem::about(app_handle, None, Some(about_metadata)),
-            &PredefinedMenuItem::separator(app_handle),
-            &PredefinedMenuItem::services(app_handle, None),
-            &PredefinedMenuItem::separator(app_handle),
-            &PredefinedMenuItem::hide(app_handle, None),
-            &PredefinedMenuItem::hide_others(app_handle, None),
-            &PredefinedMenuItem::separator(app_handle),
-            &PredefinedMenuItem::quit(app_handle, None),
+            &PredefinedMenuItem::about(app_handle, None, Some(about_metadata))?,
+            &PredefinedMenuItem::separator(app_handle)?,
+            &PredefinedMenuItem::services(app_handle, None)?,
+            &PredefinedMenuItem::separator(app_handle)?,
+            &PredefinedMenuItem::hide(app_handle, None)?,
+            &PredefinedMenuItem::hide_others(app_handle, None)?,
+            &PredefinedMenuItem::separator(app_handle)?,
+            &PredefinedMenuItem::quit(app_handle, None)?,
           ],
         )?,
         #[cfg(not(any(
@@ -207,9 +203,9 @@ impl<R: Runtime> Menu<R> {
           "File",
           true,
           &[
-            &PredefinedMenuItem::close_window(app_handle, None),
+            &PredefinedMenuItem::close_window(app_handle, None)?,
             #[cfg(not(target_os = "macos"))]
-            &PredefinedMenuItem::quit(app_handle, None),
+            &PredefinedMenuItem::quit(app_handle, None)?,
           ],
         )?,
         &Submenu::with_items(
@@ -217,13 +213,13 @@ impl<R: Runtime> Menu<R> {
           "Edit",
           true,
           &[
-            &PredefinedMenuItem::undo(app_handle, None),
-            &PredefinedMenuItem::redo(app_handle, None),
-            &PredefinedMenuItem::separator(app_handle),
-            &PredefinedMenuItem::cut(app_handle, None),
-            &PredefinedMenuItem::copy(app_handle, None),
-            &PredefinedMenuItem::paste(app_handle, None),
-            &PredefinedMenuItem::select_all(app_handle, None),
+            &PredefinedMenuItem::undo(app_handle, None)?,
+            &PredefinedMenuItem::redo(app_handle, None)?,
+            &PredefinedMenuItem::separator(app_handle)?,
+            &PredefinedMenuItem::cut(app_handle, None)?,
+            &PredefinedMenuItem::copy(app_handle, None)?,
+            &PredefinedMenuItem::paste(app_handle, None)?,
+            &PredefinedMenuItem::select_all(app_handle, None)?,
           ],
         )?,
         #[cfg(target_os = "macos")]
@@ -231,7 +227,7 @@ impl<R: Runtime> Menu<R> {
           app_handle,
           "View",
           true,
-          &[&PredefinedMenuItem::fullscreen(app_handle, None)],
+          &[&PredefinedMenuItem::fullscreen(app_handle, None)?],
         )?,
         &window_menu,
         &help_menu,
@@ -242,17 +238,17 @@ impl<R: Runtime> Menu<R> {
   }
 
   pub(crate) fn inner(&self) -> &muda::Menu {
-    &self.inner
+    (*self.0).as_ref()
   }
 
   /// The application handle associated with this type.
   pub fn app_handle(&self) -> &AppHandle<R> {
-    &self.app_handle
+    &self.0.app_handle
   }
 
   /// Returns a unique identifier associated with this menu.
   pub fn id(&self) -> &MenuId {
-    &self.id
+    &self.0.id
   }
 
   /// Add a menu item to the end of this menu.
@@ -264,8 +260,8 @@ impl<R: Runtime> Menu<R> {
   /// [`Submenu`]: super::Submenu
   pub fn append(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
     let kind = item.kind();
-    run_main_thread!(self, |self_: Self| self_
-      .inner
+    run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
       .append(kind.inner().inner_muda()))?
     .map_err(Into::into)
   }
@@ -294,8 +290,8 @@ impl<R: Runtime> Menu<R> {
   /// [`Submenu`]: super::Submenu
   pub fn prepend(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
     let kind = item.kind();
-    run_main_thread!(self, |self_: Self| self_
-      .inner
+    run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
       .prepend(kind.inner().inner_muda()))?
     .map_err(Into::into)
   }
@@ -320,8 +316,8 @@ impl<R: Runtime> Menu<R> {
   /// [`Submenu`]: super::Submenu
   pub fn insert(&self, item: &dyn IsMenuItem<R>, position: usize) -> crate::Result<()> {
     let kind = item.kind();
-    run_main_thread!(self, |self_: Self| self_
-      .inner
+    run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
       .insert(kind.inner().inner_muda(), position))?
     .map_err(Into::into)
   }
@@ -344,18 +340,18 @@ impl<R: Runtime> Menu<R> {
   /// Remove a menu item from this menu.
   pub fn remove(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
     let kind = item.kind();
-    run_main_thread!(self, |self_: Self| self_
-      .inner
+    run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
       .remove(kind.inner().inner_muda()))?
     .map_err(Into::into)
   }
 
   /// Remove the menu item at the specified position from this menu and returns it.
   pub fn remove_at(&self, position: usize) -> crate::Result<Option<MenuItemKind<R>>> {
-    run_main_thread!(self, |self_: Self| self_
-      .inner
+    run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
       .remove_at(position)
-      .map(|i| MenuItemKind::from_muda(self_.app_handle.clone(), i)))
+      .map(|i| MenuItemKind::from_muda(self_.0.app_handle.clone(), i)))
   }
 
   /// Retrieves the menu item matching the given identifier.
@@ -373,11 +369,11 @@ impl<R: Runtime> Menu<R> {
 
   /// Returns a list of menu items that has been added to this menu.
   pub fn items(&self) -> crate::Result<Vec<MenuItemKind<R>>> {
-    run_main_thread!(self, |self_: Self| self_
-      .inner
+    run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
       .items()
       .into_iter()
-      .map(|i| MenuItemKind::from_muda(self_.app_handle.clone(), i))
+      .map(|i| MenuItemKind::from_muda(self_.0.app_handle.clone(), i))
       .collect::<Vec<_>>())
   }
 
@@ -385,7 +381,7 @@ impl<R: Runtime> Menu<R> {
   ///
   /// This is an alias for [`AppHandle::set_menu`].
   pub fn set_as_app_menu(&self) -> crate::Result<Option<Menu<R>>> {
-    self.app_handle.set_menu(self.clone())
+    self.0.app_handle.set_menu(self.clone())
   }
 
   /// Set this menu as the window menu.
@@ -395,5 +391,3 @@ impl<R: Runtime> Menu<R> {
     window.set_menu(self.clone())
   }
 }
-
-impl<R: Runtime> Resource for Menu<R> {}

+ 142 - 25
core/tauri/src/menu/mod.rs

@@ -6,8 +6,6 @@
 
 //! Menu types and utilities.
 
-// TODO(muda-migration): figure out js events
-
 mod builders;
 mod check;
 mod icon;
@@ -17,18 +15,33 @@ mod normal;
 pub(crate) mod plugin;
 mod predefined;
 mod submenu;
+use std::sync::Arc;
+
 pub use builders::*;
-pub use check::CheckMenuItem;
-pub use icon::IconMenuItem;
-pub use menu::{Menu, HELP_SUBMENU_ID, WINDOW_SUBMENU_ID};
-pub use normal::MenuItem;
-pub use predefined::PredefinedMenuItem;
+pub use menu::{HELP_SUBMENU_ID, WINDOW_SUBMENU_ID};
 use serde::{Deserialize, Serialize};
-pub use submenu::Submenu;
 
 use crate::{AppHandle, Icon, Runtime};
 pub use muda::MenuId;
 
+macro_rules! run_item_main_thread {
+  ($self:ident, $ex:expr) => {{
+    use std::sync::mpsc::channel;
+    let (tx, rx) = channel();
+    let self_ = $self.clone();
+    let task = move || {
+      let f = $ex;
+      let _ = tx.send(f(self_));
+    };
+    $self
+      .app_handle()
+      .run_on_main_thread(task)
+      .and_then(|_| rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage))
+  }};
+}
+
+pub(crate) use run_item_main_thread;
+
 /// Describes a menu event emitted when a menu item is activated
 #[derive(Debug, Clone, Serialize)]
 pub struct MenuEvent {
@@ -49,6 +62,108 @@ impl From<muda::MenuEvent> for MenuEvent {
   }
 }
 
+macro_rules! gen_wrappers {
+  (
+    $(
+      $(#[$attr:meta])*
+      $type:ident($inner:ident$(, $kind:ident)?)
+    ),*
+  ) => {
+    $(
+      pub(crate) struct $inner<R: $crate::Runtime> {
+        id: $crate::menu::MenuId,
+        inner: ::std::option::Option<::muda::$type>,
+        app_handle: $crate::AppHandle<R>,
+      }
+
+
+      /// # Safety
+      ///
+      /// We make sure it always runs on the main thread.
+      unsafe impl<R: $crate::Runtime> Sync for $inner<R> {}
+      unsafe impl<R: $crate::Runtime> Send for $inner<R> {}
+
+      impl<R: Runtime> $crate::Resource for $type<R> {}
+
+      impl<R: $crate::Runtime> Clone for $inner<R> {
+        fn clone(&self) -> Self {
+          Self {
+            id: self.id.clone(),
+            inner: self.inner.clone(),
+            app_handle: self.app_handle.clone(),
+          }
+        }
+      }
+
+      impl<R: Runtime> Drop for $inner<R> {
+        fn drop(&mut self) {
+          struct SafeSend<T>(T);
+          unsafe impl<T> Send for SafeSend<T> {}
+
+          let inner = self.inner.take();
+          let inner = SafeSend(inner);
+          let _ = self.app_handle.run_on_main_thread(move || {
+            drop(inner);
+          });
+        }
+      }
+
+      impl<R: Runtime> AsRef<::muda::$type> for $inner<R> {
+        fn as_ref(&self) -> &::muda::$type {
+          self.inner.as_ref().unwrap()
+        }
+      }
+
+
+      $(#[$attr])*
+      pub struct $type<R: $crate::Runtime>(::std::sync::Arc<$inner<R>>);
+
+      impl<R: $crate::Runtime> Clone for $type<R> {
+        fn clone(&self) -> Self {
+          Self(self.0.clone())
+        }
+      }
+
+      $(
+        impl<R: $crate::Runtime> $crate::menu::sealed::IsMenuItemBase for $type<R> {
+          fn inner_muda(&self) -> &dyn muda::IsMenuItem {
+            (*self.0).as_ref()
+          }
+        }
+
+        impl<R: $crate::Runtime> $crate::menu::IsMenuItem<R> for $type<R> {
+          fn kind(&self) -> MenuItemKind<R> {
+            MenuItemKind::$kind(self.clone())
+          }
+
+          fn id(&self) -> &MenuId {
+            &self.0.id
+          }
+        }
+      )*
+    )*
+  };
+}
+
+gen_wrappers!(
+  /// A type that is either a menu bar on the window
+  /// on Windows and Linux or as a global menu in the menubar on macOS.
+  Menu(MenuInner),
+  /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text.
+  MenuItem(MenuItemInner, MenuItem),
+  /// A type that is a submenu inside a [`Menu`] or [`Submenu`]
+  Submenu(SubmenuInner, Submenu),
+  /// A predefined (native) menu item which has a predfined behavior by the OS or by this crate.
+  PredefinedMenuItem(PredefinedMenuItemInner, Predefined),
+  /// A menu item inside a [`Menu`] or [`Submenu`]
+  /// and usually contains a text and a check mark or a similar toggle
+  /// that corresponds to a checked and unchecked states.
+  CheckMenuItem(CheckMenuItemInner, Check),
+  /// A menu item inside a [`Menu`] or [`Submenu`]
+  /// and usually contains an icon and a text.
+  IconMenuItem(IconMenuItemInner, Icon)
+);
+
 /// Application metadata for the [`PredefinedMenuItem::about`].
 #[derive(Debug, Clone, Default)]
 pub struct AboutMetadata {
@@ -453,31 +568,33 @@ impl<R: Runtime> MenuItemKind<R> {
 
   pub(crate) fn from_muda(app_handle: AppHandle<R>, i: muda::MenuItemKind) -> Self {
     match i {
-      muda::MenuItemKind::MenuItem(i) => Self::MenuItem(MenuItem {
-        id: i.id().clone(),
-        inner: i,
-        app_handle,
-      }),
-      muda::MenuItemKind::Submenu(i) => Self::Submenu(Submenu {
+      muda::MenuItemKind::MenuItem(i) => Self::MenuItem(MenuItem(Arc::new(MenuItemInner {
         id: i.id().clone(),
-        inner: i,
+        inner: i.into(),
         app_handle,
-      }),
-      muda::MenuItemKind::Predefined(i) => Self::Predefined(PredefinedMenuItem {
+      }))),
+      muda::MenuItemKind::Submenu(i) => Self::Submenu(Submenu(Arc::new(SubmenuInner {
         id: i.id().clone(),
-        inner: i,
+        inner: i.into(),
         app_handle,
-      }),
-      muda::MenuItemKind::Check(i) => Self::Check(CheckMenuItem {
+      }))),
+      muda::MenuItemKind::Predefined(i) => {
+        Self::Predefined(PredefinedMenuItem(Arc::new(PredefinedMenuItemInner {
+          id: i.id().clone(),
+          inner: i.into(),
+          app_handle,
+        })))
+      }
+      muda::MenuItemKind::Check(i) => Self::Check(CheckMenuItem(Arc::new(CheckMenuItemInner {
         id: i.id().clone(),
-        inner: i,
+        inner: i.into(),
         app_handle,
-      }),
-      muda::MenuItemKind::Icon(i) => Self::Icon(IconMenuItem {
+      }))),
+      muda::MenuItemKind::Icon(i) => Self::Icon(IconMenuItem(Arc::new(IconMenuItemInner {
         id: i.id().clone(),
-        inner: i,
+        inner: i.into(),
         app_handle,
-      }),
+      }))),
     }
   }
 

+ 70 - 81
core/tauri/src/menu/normal.rs

@@ -2,110 +2,98 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use crate::{menu::MenuId, resources::Resource, run_main_thread, AppHandle, Manager, Runtime};
-
-/// A menu item inside a [`Menu`] or [`Submenu`] and contains only text.
-///
-/// [`Menu`]: super::Menu
-/// [`Submenu`]: super::Submenu
-pub struct MenuItem<R: Runtime> {
-  pub(crate) id: MenuId,
-  pub(crate) inner: muda::MenuItem,
-  pub(crate) app_handle: AppHandle<R>,
-}
-
-impl<R: Runtime> Clone for MenuItem<R> {
-  fn clone(&self) -> Self {
-    Self {
-      id: self.id.clone(),
-      inner: self.inner.clone(),
-      app_handle: self.app_handle.clone(),
-    }
-  }
-}
-
-/// # Safety
-///
-/// We make sure it always runs on the main thread.
-unsafe impl<R: Runtime> Sync for MenuItem<R> {}
-unsafe impl<R: Runtime> Send for MenuItem<R> {}
-
-impl<R: Runtime> super::sealed::IsMenuItemBase for MenuItem<R> {
-  fn inner_muda(&self) -> &dyn muda::IsMenuItem {
-    &self.inner
-  }
-}
+use std::sync::Arc;
 
-impl<R: Runtime> super::IsMenuItem<R> for MenuItem<R> {
-  fn kind(&self) -> super::MenuItemKind<R> {
-    super::MenuItemKind::MenuItem(self.clone())
-  }
+use super::run_item_main_thread;
+use crate::menu::MenuItemInner;
+use crate::run_main_thread;
+use crate::{menu::MenuId, AppHandle, Manager, Runtime};
 
-  fn id(&self) -> &MenuId {
-    &self.id
-  }
-}
+use super::MenuItem;
 
 impl<R: Runtime> MenuItem<R> {
   /// Create a new menu item.
   ///
   /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
   /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
-  pub fn new<M: Manager<R>, S: AsRef<str>>(
+  pub fn new<M, T, A>(
     manager: &M,
-    text: S,
+    text: T,
     enabled: bool,
-    acccelerator: Option<S>,
-  ) -> Self {
-    let item = muda::MenuItem::new(
-      text,
-      enabled,
-      acccelerator.and_then(|s| s.as_ref().parse().ok()),
-    );
-    Self {
-      id: item.id().clone(),
-      inner: item,
-      app_handle: manager.app_handle().clone(),
-    }
+    accelerator: Option<A>,
+  ) -> crate::Result<Self>
+  where
+    M: Manager<R>,
+    T: AsRef<str>,
+    A: AsRef<str>,
+  {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.as_ref().to_owned();
+    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::MenuItem::new(text, enabled, accelerator);
+      MenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Create a new menu item with the specified id.
   ///
   /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
   /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
-  pub fn with_id<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(
+  pub fn with_id<M, I, T, A>(
     manager: &M,
     id: I,
-    text: S,
+    text: T,
     enabled: bool,
-    acccelerator: Option<S>,
-  ) -> Self {
-    let item = muda::MenuItem::with_id(
-      id,
-      text,
-      enabled,
-      acccelerator.and_then(|s| s.as_ref().parse().ok()),
-    );
-    Self {
-      id: item.id().clone(),
-      inner: item,
-      app_handle: manager.app_handle().clone(),
-    }
+    accelerator: Option<A>,
+  ) -> crate::Result<Self>
+  where
+    M: Manager<R>,
+    I: Into<MenuId>,
+    T: AsRef<str>,
+    A: AsRef<str>,
+  {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let id = id.into();
+    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());
+    let text = text.as_ref().to_owned();
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::MenuItem::with_id(id.clone(), text, enabled, accelerator);
+      MenuItemInner {
+        id,
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// The application handle associated with this type.
   pub fn app_handle(&self) -> &AppHandle<R> {
-    &self.app_handle
+    &self.0.app_handle
   }
 
   /// Returns a unique identifier associated with this menu item.
   pub fn id(&self) -> &MenuId {
-    &self.id
+    &self.0.id
   }
 
   /// Get the text for this menu item.
   pub fn text(&self) -> crate::Result<String> {
-    run_main_thread!(self, |self_: Self| self_.inner.text())
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().text())
   }
 
   /// Set the text for this menu item. `text` could optionally contain
@@ -113,24 +101,25 @@ impl<R: Runtime> MenuItem<R> {
   /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
   pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {
     let text = text.as_ref().to_string();
-    run_main_thread!(self, |self_: Self| self_.inner.set_text(text))
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_text(text))
   }
 
   /// Get whether this menu item is enabled or not.
   pub fn is_enabled(&self) -> crate::Result<bool> {
-    run_main_thread!(self, |self_: Self| self_.inner.is_enabled())
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().is_enabled())
   }
 
   /// Enable or disable this menu item.
   pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {
-    run_main_thread!(self, |self_: Self| self_.inner.set_enabled(enabled))
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_enabled(enabled))
   }
 
   /// Set this menu item accelerator.
-  pub fn set_accelerator<S: AsRef<str>>(&self, acccelerator: Option<S>) -> crate::Result<()> {
-    let accel = acccelerator.and_then(|s| s.as_ref().parse().ok());
-    run_main_thread!(self, |self_: Self| self_.inner.set_accelerator(accel))?.map_err(Into::into)
+  pub fn set_accelerator<S: AsRef<str>>(&self, accelerator: Option<S>) -> crate::Result<()> {
+    let accel = accelerator.and_then(|s| s.as_ref().parse().ok());
+    run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
+      .set_accelerator(accel))?
+    .map_err(Into::into)
   }
 }
-
-impl<R: Runtime> Resource for MenuItem<R> {}

+ 21 - 18
core/tauri/src/menu/plugin.rs

@@ -128,7 +128,7 @@ struct CheckMenuItemPayload {
 }
 
 impl CheckMenuItemPayload {
-  pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> CheckMenuItem<R> {
+  pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> crate::Result<CheckMenuItem<R>> {
     let mut builder = if let Some(id) = self.id {
       CheckMenuItemBuilder::with_id(id, self.text)
     } else {
@@ -141,7 +141,7 @@ impl CheckMenuItemPayload {
       builder = builder.enabled(enabled);
     }
 
-    let item = builder.checked(self.checked).build(webview);
+    let item = builder.checked(self.checked).build(webview)?;
 
     if let Some(handler) = self.handler {
       let handler = handler.channel_on(webview.clone());
@@ -153,7 +153,7 @@ impl CheckMenuItemPayload {
         .insert(item.id().clone(), handler);
     }
 
-    item
+    Ok(item)
   }
 }
 
@@ -176,7 +176,7 @@ struct IconMenuItemPayload {
 }
 
 impl IconMenuItemPayload {
-  pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> IconMenuItem<R> {
+  pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> crate::Result<IconMenuItem<R>> {
     let mut builder = if let Some(id) = self.id {
       IconMenuItemBuilder::with_id(id, self.text)
     } else {
@@ -193,7 +193,7 @@ impl IconMenuItemPayload {
       Icon::Icon(icon) => builder.icon(icon.into()),
     };
 
-    let item = builder.build(webview);
+    let item = builder.build(webview)?;
 
     if let Some(handler) = self.handler {
       let handler = handler.channel_on(webview.clone());
@@ -205,7 +205,7 @@ impl IconMenuItemPayload {
         .insert(item.id().clone(), handler);
     }
 
-    item
+    Ok(item)
   }
 }
 
@@ -219,7 +219,7 @@ struct MenuItemPayload {
 }
 
 impl MenuItemPayload {
-  pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> MenuItem<R> {
+  pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> crate::Result<MenuItem<R>> {
     let mut builder = if let Some(id) = self.id {
       MenuItemBuilder::with_id(id, self.text)
     } else {
@@ -232,7 +232,7 @@ impl MenuItemPayload {
       builder = builder.enabled(enabled);
     }
 
-    let item = builder.build(webview);
+    let item = builder.build(webview)?;
 
     if let Some(handler) = self.handler {
       let handler = handler.channel_on(webview.clone());
@@ -244,7 +244,7 @@ impl MenuItemPayload {
         .insert(item.id().clone(), handler);
     }
 
-    item
+    Ok(item)
   }
 }
 
@@ -255,7 +255,10 @@ struct PredefinedMenuItemPayload {
 }
 
 impl PredefinedMenuItemPayload {
-  pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> PredefinedMenuItem<R> {
+  pub fn create_item<R: Runtime>(
+    self,
+    webview: &Webview<R>,
+  ) -> crate::Result<PredefinedMenuItem<R>> {
     match self.item {
       Predefined::Separator => PredefinedMenuItem::separator(webview),
       Predefined::Copy => PredefinedMenuItem::copy(webview, self.text.as_deref()),
@@ -303,10 +306,10 @@ impl MenuItemPayloadKind {
         do_menu_item!(resources_table, rid, kind, |i| f(&*i))
       }
       Self::Submenu(i) => f(&i.create_item(webview, resources_table)?),
-      Self::Predefined(i) => f(&i.create_item(webview)),
-      Self::Check(i) => f(&i.create_item(webview)),
-      Self::Icon(i) => f(&i.create_item(webview)),
-      Self::MenuItem(i) => f(&i.create_item(webview)),
+      Self::Predefined(i) => f(&i.create_item(webview)?),
+      Self::Check(i) => f(&i.create_item(webview)?),
+      Self::Icon(i) => f(&i.create_item(webview)?),
+      Self::MenuItem(i) => f(&i.create_item(webview)?),
     }
   }
 }
@@ -378,7 +381,7 @@ fn new<R: Runtime>(
         enabled: options.enabled,
         accelerator: options.accelerator,
       }
-      .create_item(&webview);
+      .create_item(&webview)?;
       let id = item.id().clone();
       let rid = resources_table.add(item);
       (rid, id)
@@ -389,7 +392,7 @@ fn new<R: Runtime>(
         item: options.predefined_item.unwrap(),
         text: options.text,
       }
-      .create_item(&webview);
+      .create_item(&webview)?;
       let id = item.id().clone();
       let rid = resources_table.add(item);
       (rid, id)
@@ -405,7 +408,7 @@ fn new<R: Runtime>(
         enabled: options.enabled,
         accelerator: options.accelerator,
       }
-      .create_item(&webview);
+      .create_item(&webview)?;
       let id = item.id().clone();
       let rid = resources_table.add(item);
       (rid, id)
@@ -421,7 +424,7 @@ fn new<R: Runtime>(
         enabled: options.enabled,
         accelerator: options.accelerator,
       }
-      .create_item(&webview);
+      .create_item(&webview)?;
       let id = item.id().clone();
       let rid = resources_table.add(item);
       (rid, id)

+ 279 - 164
core/tauri/src/menu/predefined.rs

@@ -2,97 +2,106 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::AboutMetadata;
-use crate::{menu::MenuId, resources::Resource, run_main_thread, AppHandle, Manager, Runtime};
-
-/// A predefined (native) menu item which has a predfined behavior by the OS or by this crate.
-pub struct PredefinedMenuItem<R: Runtime> {
-  pub(crate) id: MenuId,
-  pub(crate) inner: muda::PredefinedMenuItem,
-  pub(crate) app_handle: AppHandle<R>,
-}
+use std::sync::Arc;
 
-impl<R: Runtime> Clone for PredefinedMenuItem<R> {
-  fn clone(&self) -> Self {
-    Self {
-      id: self.id.clone(),
-      inner: self.inner.clone(),
-      app_handle: self.app_handle.clone(),
-    }
-  }
-}
+use super::run_item_main_thread;
+use super::{AboutMetadata, PredefinedMenuItem};
+use crate::menu::PredefinedMenuItemInner;
+use crate::run_main_thread;
+use crate::{menu::MenuId, AppHandle, Manager, Runtime};
 
-/// # Safety
-///
-/// We make sure it always runs on the main thread.
-unsafe impl<R: Runtime> Sync for PredefinedMenuItem<R> {}
-unsafe impl<R: Runtime> Send for PredefinedMenuItem<R> {}
+impl<R: Runtime> PredefinedMenuItem<R> {
+  /// Separator menu item
+  pub fn separator<M: Manager<R>>(manager: &M) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
 
-impl<R: Runtime> super::sealed::IsMenuItemBase for PredefinedMenuItem<R> {
-  fn inner_muda(&self) -> &dyn muda::IsMenuItem {
-    &self.inner
-  }
-}
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::separator();
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
 
-impl<R: Runtime> super::IsMenuItem<R> for PredefinedMenuItem<R> {
-  fn kind(&self) -> super::MenuItemKind<R> {
-    super::MenuItemKind::Predefined(self.clone())
+    Ok(Self(Arc::new(item)))
   }
 
-  fn id(&self) -> &MenuId {
-    self.id()
-  }
-}
+  /// Copy menu item
+  pub fn copy<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
 
-impl<R: Runtime> PredefinedMenuItem<R> {
-  /// Separator menu item
-  pub fn separator<M: Manager<R>>(manager: &M) -> Self {
-    let inner = muda::PredefinedMenuItem::separator();
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
-  }
+    let text = text.map(|t| t.to_owned());
 
-  /// Copy menu item
-  pub fn copy<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
-    let inner = muda::PredefinedMenuItem::copy(text);
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::copy(text.as_deref());
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Cut menu item
-  pub fn cut<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
-    let inner = muda::PredefinedMenuItem::cut(text);
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn cut<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.map(|t| t.to_owned());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::cut(text.as_deref());
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Paste menu item
-  pub fn paste<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
-    let inner = muda::PredefinedMenuItem::paste(text);
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn paste<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.map(|t| t.to_owned());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::paste(text.as_deref());
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// SelectAll menu item
-  pub fn select_all<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
-    let inner = muda::PredefinedMenuItem::select_all(text);
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn select_all<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.map(|t| t.to_owned());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::select_all(text.as_deref());
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Undo menu item
@@ -100,26 +109,44 @@ impl<R: Runtime> PredefinedMenuItem<R> {
   /// ## Platform-specific:
   ///
   /// - **Windows / Linux:** Unsupported.
-  pub fn undo<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
-    let inner = muda::PredefinedMenuItem::undo(text);
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn undo<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.map(|t| t.to_owned());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::undo(text.as_deref());
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
   /// Redo menu item
   ///
   /// ## Platform-specific:
   ///
   /// - **Windows / Linux:** Unsupported.
-  pub fn redo<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
-    let inner = muda::PredefinedMenuItem::redo(text);
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn redo<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.map(|t| t.to_owned());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::redo(text.as_deref());
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Minimize window menu item
@@ -127,13 +154,22 @@ impl<R: Runtime> PredefinedMenuItem<R> {
   /// ## Platform-specific:
   ///
   /// - **Linux:** Unsupported.
-  pub fn minimize<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
-    let inner = muda::PredefinedMenuItem::minimize(text);
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn minimize<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.map(|t| t.to_owned());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::minimize(text.as_deref());
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Maximize window menu item
@@ -141,13 +177,22 @@ impl<R: Runtime> PredefinedMenuItem<R> {
   /// ## Platform-specific:
   ///
   /// - **Linux:** Unsupported.
-  pub fn maximize<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
-    let inner = muda::PredefinedMenuItem::maximize(text);
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn maximize<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.map(|t| t.to_owned());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::maximize(text.as_deref());
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Fullscreen menu item
@@ -155,13 +200,22 @@ impl<R: Runtime> PredefinedMenuItem<R> {
   /// ## Platform-specific:
   ///
   /// - **Windows / Linux:** Unsupported.
-  pub fn fullscreen<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
-    let inner = muda::PredefinedMenuItem::fullscreen(text);
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn fullscreen<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.map(|t| t.to_owned());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::fullscreen(text.as_deref());
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Hide window menu item
@@ -169,13 +223,22 @@ impl<R: Runtime> PredefinedMenuItem<R> {
   /// ## Platform-specific:
   ///
   /// - **Linux:** Unsupported.
-  pub fn hide<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
-    let inner = muda::PredefinedMenuItem::hide(text);
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn hide<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.map(|t| t.to_owned());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::hide(text.as_deref());
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Hide other windows menu item
@@ -183,13 +246,22 @@ impl<R: Runtime> PredefinedMenuItem<R> {
   /// ## Platform-specific:
   ///
   /// - **Linux:** Unsupported.
-  pub fn hide_others<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
-    let inner = muda::PredefinedMenuItem::hide_others(text);
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn hide_others<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.map(|t| t.to_owned());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::hide_others(text.as_deref());
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Show all app windows menu item
@@ -197,13 +269,22 @@ impl<R: Runtime> PredefinedMenuItem<R> {
   /// ## Platform-specific:
   ///
   /// - **Windows / Linux:** Unsupported.
-  pub fn show_all<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
-    let inner = muda::PredefinedMenuItem::show_all(text);
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn show_all<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.map(|t| t.to_owned());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::show_all(text.as_deref());
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Close window menu item
@@ -211,13 +292,22 @@ impl<R: Runtime> PredefinedMenuItem<R> {
   /// ## Platform-specific:
   ///
   /// - **Linux:** Unsupported.
-  pub fn close_window<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
-    let inner = muda::PredefinedMenuItem::close_window(text);
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn close_window<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.map(|t| t.to_owned());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::close_window(text.as_deref());
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Quit app menu item
@@ -225,13 +315,22 @@ impl<R: Runtime> PredefinedMenuItem<R> {
   /// ## Platform-specific:
   ///
   /// - **Linux:** Unsupported.
-  pub fn quit<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
-    let inner = muda::PredefinedMenuItem::quit(text);
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn quit<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.map(|t| t.to_owned());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::quit(text.as_deref());
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// About app menu item
@@ -239,13 +338,22 @@ impl<R: Runtime> PredefinedMenuItem<R> {
     manager: &M,
     text: Option<&str>,
     metadata: Option<AboutMetadata>,
-  ) -> Self {
-    let inner = muda::PredefinedMenuItem::about(text, metadata.map(Into::into));
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+  ) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.map(|t| t.to_owned());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::about(text.as_deref(), metadata.map(Into::into));
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Services menu item
@@ -253,23 +361,32 @@ impl<R: Runtime> PredefinedMenuItem<R> {
   /// ## Platform-specific:
   ///
   /// - **Windows / Linux:** Unsupported.
-  pub fn services<M: Manager<R>>(manager: &M, text: Option<&str>) -> Self {
-    let inner = muda::PredefinedMenuItem::services(text);
-    Self {
-      id: inner.id().clone(),
-      inner,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn services<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.map(|t| t.to_owned());
+
+    let item = run_main_thread!(handle, || {
+      let item = muda::PredefinedMenuItem::services(text.as_deref());
+      PredefinedMenuItemInner {
+        id: item.id().clone(),
+        inner: Some(item),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(item)))
   }
 
   /// Returns a unique identifier associated with this menu item.
   pub fn id(&self) -> &MenuId {
-    &self.id
+    &self.0.id
   }
 
   /// Get the text for this menu item.
   pub fn text(&self) -> crate::Result<String> {
-    run_main_thread!(self, |self_: Self| self_.inner.text())
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().text())
   }
 
   /// Set the text for this menu item. `text` could optionally contain
@@ -277,13 +394,11 @@ impl<R: Runtime> PredefinedMenuItem<R> {
   /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
   pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {
     let text = text.as_ref().to_string();
-    run_main_thread!(self, |self_: Self| self_.inner.set_text(text))
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_text(text))
   }
 
   /// The application handle associated with this type.
   pub fn app_handle(&self) -> &AppHandle<R> {
-    &self.app_handle
+    &self.0.app_handle
   }
 }
-
-impl<R: Runtime> Resource for PredefinedMenuItem<R> {}

+ 78 - 89
core/tauri/src/menu/submenu.rs

@@ -2,52 +2,16 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+use std::sync::Arc;
+
+use super::run_item_main_thread;
+use super::Submenu;
 use super::{sealed::ContextMenuBase, IsMenuItem, MenuItemKind};
-use crate::{resources::Resource, run_main_thread, AppHandle, Manager, Position, Runtime, Window};
+use crate::menu::SubmenuInner;
+use crate::run_main_thread;
+use crate::{AppHandle, Manager, Position, Runtime, Window};
 use muda::{ContextMenu, MenuId};
 
-/// A type that is a submenu inside a [`Menu`] or [`Submenu`]
-///
-/// [`Menu`]: super::Menu
-/// [`Submenu`]: super::Submenu
-pub struct Submenu<R: Runtime> {
-  pub(crate) id: MenuId,
-  pub(crate) inner: muda::Submenu,
-  pub(crate) app_handle: AppHandle<R>,
-}
-
-/// # Safety
-///
-/// We make sure it always runs on the main thread.
-unsafe impl<R: Runtime> Sync for Submenu<R> {}
-unsafe impl<R: Runtime> Send for Submenu<R> {}
-
-impl<R: Runtime> Clone for Submenu<R> {
-  fn clone(&self) -> Self {
-    Self {
-      id: self.id.clone(),
-      inner: self.inner.clone(),
-      app_handle: self.app_handle.clone(),
-    }
-  }
-}
-
-impl<R: Runtime> super::sealed::IsMenuItemBase for Submenu<R> {
-  fn inner_muda(&self) -> &dyn muda::IsMenuItem {
-    &self.inner
-  }
-}
-
-impl<R: Runtime> super::IsMenuItem<R> for Submenu<R> {
-  fn kind(&self) -> super::MenuItemKind<R> {
-    super::MenuItemKind::Submenu(self.clone())
-  }
-
-  fn id(&self) -> &MenuId {
-    &self.id
-  }
-}
-
 impl<R: Runtime> super::ContextMenu for Submenu<R> {
   fn popup<T: Runtime>(&self, window: Window<T>) -> crate::Result<()> {
     self.popup_inner(window, None::<Position>)
@@ -69,7 +33,7 @@ impl<R: Runtime> ContextMenuBase for Submenu<R> {
     position: Option<P>,
   ) -> crate::Result<()> {
     let position = position.map(Into::into).map(super::into_position);
-    run_main_thread!(self, move |self_: Self| {
+    run_item_main_thread!(self, move |self_: Self| {
       #[cfg(target_os = "macos")]
       if let Ok(view) = window.ns_view() {
         self_
@@ -98,23 +62,36 @@ impl<R: Runtime> ContextMenuBase for Submenu<R> {
   }
 
   fn inner_context(&self) -> &dyn muda::ContextMenu {
-    &self.inner
+    (*self.0).as_ref()
   }
 
   fn inner_context_owned(&self) -> Box<dyn muda::ContextMenu> {
-    Box::new(self.clone().inner)
+    Box::new((*self.0).as_ref().clone())
   }
 }
 
 impl<R: Runtime> Submenu<R> {
   /// Creates a new submenu.
-  pub fn new<M: Manager<R>, S: AsRef<str>>(manager: &M, text: S, enabled: bool) -> Self {
-    let submenu = muda::Submenu::new(text, enabled);
-    Self {
-      id: submenu.id().clone(),
-      inner: submenu,
-      app_handle: manager.app_handle().clone(),
-    }
+  pub fn new<M: Manager<R>, S: AsRef<str>>(
+    manager: &M,
+    text: S,
+    enabled: bool,
+  ) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let text = text.as_ref().to_owned();
+
+    let submenu = run_main_thread!(handle, || {
+      let submenu = muda::Submenu::new(text, enabled);
+      SubmenuInner {
+        id: submenu.id().clone(),
+        inner: Some(submenu),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(submenu)))
   }
 
   /// Creates a new submenu with the specified id.
@@ -123,13 +100,23 @@ impl<R: Runtime> Submenu<R> {
     id: I,
     text: S,
     enabled: bool,
-  ) -> Self {
-    let menu = muda::Submenu::with_id(id, text, enabled);
-    Self {
-      id: menu.id().clone(),
-      inner: menu,
-      app_handle: manager.app_handle().clone(),
-    }
+  ) -> crate::Result<Self> {
+    let handle = manager.app_handle();
+    let app_handle = handle.clone();
+
+    let id = id.into();
+    let text = text.as_ref().to_owned();
+
+    let submenu = run_main_thread!(handle, || {
+      let submenu = muda::Submenu::with_id(id.clone(), text, enabled);
+      SubmenuInner {
+        id,
+        inner: Some(submenu),
+        app_handle,
+      }
+    })?;
+
+    Ok(Self(Arc::new(submenu)))
   }
 
   /// Creates a new menu with given `items`. It calls [`Submenu::new`] and [`Submenu::append_items`] internally.
@@ -139,7 +126,7 @@ impl<R: Runtime> Submenu<R> {
     enabled: bool,
     items: &[&dyn IsMenuItem<R>],
   ) -> crate::Result<Self> {
-    let menu = Self::new(manager, text, enabled);
+    let menu = Self::new(manager, text, enabled)?;
     menu.append_items(items)?;
     Ok(menu)
   }
@@ -153,30 +140,30 @@ impl<R: Runtime> Submenu<R> {
     enabled: bool,
     items: &[&dyn IsMenuItem<R>],
   ) -> crate::Result<Self> {
-    let menu = Self::with_id(manager, id, text, enabled);
+    let menu = Self::with_id(manager, id, text, enabled)?;
     menu.append_items(items)?;
     Ok(menu)
   }
 
   pub(crate) fn inner(&self) -> &muda::Submenu {
-    &self.inner
+    (*self.0).as_ref()
   }
 
   /// The application handle associated with this type.
   pub fn app_handle(&self) -> &AppHandle<R> {
-    &self.app_handle
+    &self.0.app_handle
   }
 
   /// Returns a unique identifier associated with this submenu.
   pub fn id(&self) -> &MenuId {
-    &self.id
+    &self.0.id
   }
 
   /// Add a menu item to the end of this submenu.
   pub fn append(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
     let kind = item.kind();
-    run_main_thread!(self, |self_: Self| self_
-      .inner
+    run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
       .append(kind.inner().inner_muda()))?
     .map_err(Into::into)
   }
@@ -193,8 +180,8 @@ impl<R: Runtime> Submenu<R> {
   /// Add a menu item to the beginning of this submenu.
   pub fn prepend(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
     let kind = item.kind();
-    run_main_thread!(self, |self_: Self| {
-      self_.inner.prepend(kind.inner().inner_muda())
+    run_item_main_thread!(self, |self_: Self| {
+      (*self_.0).as_ref().prepend(kind.inner().inner_muda())
     })?
     .map_err(Into::into)
   }
@@ -207,8 +194,10 @@ impl<R: Runtime> Submenu<R> {
   /// Insert a menu item at the specified `postion` in this submenu.
   pub fn insert(&self, item: &dyn IsMenuItem<R>, position: usize) -> crate::Result<()> {
     let kind = item.kind();
-    run_main_thread!(self, |self_: Self| {
-      self_.inner.insert(kind.inner().inner_muda(), position)
+    run_item_main_thread!(self, |self_: Self| {
+      (*self_.0)
+        .as_ref()
+        .insert(kind.inner().inner_muda(), position)
     })?
     .map_err(Into::into)
   }
@@ -225,18 +214,18 @@ impl<R: Runtime> Submenu<R> {
   /// Remove a menu item from this submenu.
   pub fn remove(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
     let kind = item.kind();
-    run_main_thread!(self, |self_: Self| self_
-      .inner
+    run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
       .remove(kind.inner().inner_muda()))?
     .map_err(Into::into)
   }
 
   /// Remove the menu item at the specified position from this submenu and returns it.
   pub fn remove_at(&self, position: usize) -> crate::Result<Option<MenuItemKind<R>>> {
-    run_main_thread!(self, |self_: Self| self_
-      .inner
+    run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
       .remove_at(position)
-      .map(|i| MenuItemKind::from_muda(self_.app_handle.clone(), i)))
+      .map(|i| MenuItemKind::from_muda(self_.0.app_handle.clone(), i)))
   }
 
   /// Retrieves the menu item matching the given identifier.
@@ -254,19 +243,19 @@ impl<R: Runtime> Submenu<R> {
 
   /// Returns a list of menu items that has been added to this submenu.
   pub fn items(&self) -> crate::Result<Vec<MenuItemKind<R>>> {
-    run_main_thread!(self, |self_: Self| {
-      self_
-        .inner
+    run_item_main_thread!(self, |self_: Self| {
+      (*self_.0)
+        .as_ref()
         .items()
         .into_iter()
-        .map(|i| MenuItemKind::from_muda(self_.app_handle.clone(), i))
+        .map(|i| MenuItemKind::from_muda(self_.0.app_handle.clone(), i))
         .collect::<Vec<_>>()
     })
   }
 
   /// Get the text for this submenu.
   pub fn text(&self) -> crate::Result<String> {
-    run_main_thread!(self, |self_: Self| self_.inner.text())
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().text())
   }
 
   /// Set the text for this submenu. `text` could optionally contain
@@ -274,17 +263,17 @@ impl<R: Runtime> Submenu<R> {
   /// for this submenu. To display a `&` without assigning a mnemenonic, use `&&`.
   pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {
     let text = text.as_ref().to_string();
-    run_main_thread!(self, |self_: Self| self_.inner.set_text(text))
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_text(text))
   }
 
   /// Get whether this submenu is enabled or not.
   pub fn is_enabled(&self) -> crate::Result<bool> {
-    run_main_thread!(self, |self_: Self| self_.inner.is_enabled())
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().is_enabled())
   }
 
   /// Enable or disable this submenu.
   pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {
-    run_main_thread!(self, |self_: Self| self_.inner.set_enabled(enabled))
+    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_enabled(enabled))
   }
 
   /// Set this submenu as the Window menu for the application on macOS.
@@ -293,8 +282,8 @@ impl<R: Runtime> Submenu<R> {
   /// certain other items to the menu.
   #[cfg(target_os = "macos")]
   pub fn set_as_windows_menu_for_nsapp(&self) -> crate::Result<()> {
-    run_main_thread!(self, |self_: Self| self_
-      .inner
+    run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
       .set_as_windows_menu_for_nsapp())?;
     Ok(())
   }
@@ -307,9 +296,9 @@ impl<R: Runtime> Submenu<R> {
   /// which has a title matching the localized word "Help".
   #[cfg(target_os = "macos")]
   pub fn set_as_help_menu_for_nsapp(&self) -> crate::Result<()> {
-    run_main_thread!(self, |self_: Self| self_.inner.set_as_help_menu_for_nsapp())?;
+    run_item_main_thread!(self, |self_: Self| (*self_.0)
+      .as_ref()
+      .set_as_help_menu_for_nsapp())?;
     Ok(())
   }
 }
-
-impl<R: Runtime> Resource for Submenu<R> {}

+ 9 - 9
core/tauri/src/tray/mod.rs

@@ -12,7 +12,7 @@ use crate::app::{GlobalMenuEventListener, GlobalTrayIconEventListener};
 use crate::menu::ContextMenu;
 use crate::menu::MenuEvent;
 use crate::resources::Resource;
-use crate::{run_main_thread, AppHandle, Icon, Manager, Runtime};
+use crate::{menu::run_item_main_thread, AppHandle, Icon, Manager, Runtime};
 use serde::Serialize;
 use std::path::Path;
 pub use tray_icon::TrayIconId;
@@ -366,7 +366,7 @@ impl<R: Runtime> TrayIcon<R> {
   /// Sets a new tray icon. If `None` is provided, it will remove the icon.
   pub fn set_icon(&self, icon: Option<Icon>) -> crate::Result<()> {
     let icon = icon.and_then(|i| i.try_into().ok());
-    run_main_thread!(self, |self_: Self| self_.inner.set_icon(icon))?.map_err(Into::into)
+    run_item_main_thread!(self, |self_: Self| self_.inner.set_icon(icon))?.map_err(Into::into)
   }
 
   /// Sets a new tray menu.
@@ -375,7 +375,7 @@ impl<R: Runtime> TrayIcon<R> {
   ///
   /// - **Linux**: once a menu is set it cannot be removed so `None` has no effect
   pub fn set_menu<M: ContextMenu + 'static>(&self, menu: Option<M>) -> crate::Result<()> {
-    run_main_thread!(self, |self_: Self| self_
+    run_item_main_thread!(self, |self_: Self| self_
       .inner
       .set_menu(menu.map(|m| m.inner_context_owned())))
   }
@@ -387,7 +387,7 @@ impl<R: Runtime> TrayIcon<R> {
   /// - **Linux:** Unsupported
   pub fn set_tooltip<S: AsRef<str>>(&self, tooltip: Option<S>) -> crate::Result<()> {
     let s = tooltip.map(|s| s.as_ref().to_string());
-    run_main_thread!(self, |self_: Self| self_.inner.set_tooltip(s))?.map_err(Into::into)
+    run_item_main_thread!(self, |self_: Self| self_.inner.set_tooltip(s))?.map_err(Into::into)
   }
 
   /// Sets the title for this tray icon.
@@ -402,12 +402,12 @@ impl<R: Runtime> TrayIcon<R> {
   /// - **Windows:** Unsupported
   pub fn set_title<S: AsRef<str>>(&self, title: Option<S>) -> crate::Result<()> {
     let s = title.map(|s| s.as_ref().to_string());
-    run_main_thread!(self, |self_: Self| self_.inner.set_title(s))
+    run_item_main_thread!(self, |self_: Self| self_.inner.set_title(s))
   }
 
   /// Show or hide this tray icon.
   pub fn set_visible(&self, visible: bool) -> crate::Result<()> {
-    run_main_thread!(self, |self_: Self| self_.inner.set_visible(visible))?.map_err(Into::into)
+    run_item_main_thread!(self, |self_: Self| self_.inner.set_visible(visible))?.map_err(Into::into)
   }
 
   /// Sets the tray icon temp dir path. **Linux only**.
@@ -418,14 +418,14 @@ impl<R: Runtime> TrayIcon<R> {
     #[allow(unused)]
     let p = path.map(|p| p.as_ref().to_path_buf());
     #[cfg(target_os = "linux")]
-    run_main_thread!(self, |self_: Self| self_.inner.set_temp_dir_path(p))?;
+    run_item_main_thread!(self, |self_: Self| self_.inner.set_temp_dir_path(p))?;
     Ok(())
   }
 
   /// Sets the current icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.
   pub fn set_icon_as_template(&self, #[allow(unused)] is_template: bool) -> crate::Result<()> {
     #[cfg(target_os = "macos")]
-    run_main_thread!(self, |self_: Self| self_
+    run_item_main_thread!(self, |self_: Self| self_
       .inner
       .set_icon_as_template(is_template))?;
     Ok(())
@@ -434,7 +434,7 @@ impl<R: Runtime> TrayIcon<R> {
   /// Disable or enable showing the tray menu on left click. **macOS only**.
   pub fn set_show_menu_on_left_click(&self, #[allow(unused)] enable: bool) -> crate::Result<()> {
     #[cfg(target_os = "macos")]
-    run_main_thread!(self, |self_: Self| self_
+    run_item_main_thread!(self, |self_: Self| self_
       .inner
       .set_show_menu_on_left_click(enable))?;
     Ok(())

+ 2 - 2
core/tauri/src/webview/webview_window.rs

@@ -153,7 +153,7 @@ async fn reopen_window(app: tauri::AppHandle) {
   /// tauri::Builder::default()
   ///   .setup(|app| {
   ///     let handle = app.handle();
-  ///     let save_menu_item = MenuItem::new(handle, "Save", true, None);
+  ///     let save_menu_item = MenuItem::new(handle, "Save", true, None::<&str>)?;
   ///     let menu = Menu::with_items(handle, &[
   ///       &Submenu::with_items(handle, "File", true, &[
   ///         &save_menu_item,
@@ -880,7 +880,7 @@ use tauri::menu::{Menu, Submenu, MenuItem};
 tauri::Builder::default()
   .setup(|app| {
     let handle = app.handle();
-    let save_menu_item = MenuItem::new(handle, "Save", true, None);
+    let save_menu_item = MenuItem::new(handle, "Save", true, None::<&str>)?;
     let menu = Menu::with_items(handle, &[
       &Submenu::with_items(handle, "File", true, &[
         &save_menu_item,

+ 2 - 2
core/tauri/src/window/mod.rs

@@ -269,7 +269,7 @@ use tauri::menu::{Menu, Submenu, MenuItem};
 tauri::Builder::default()
   .setup(|app| {
     let handle = app.handle();
-    let save_menu_item = MenuItem::new(handle, "Save", true, None);
+    let save_menu_item = MenuItem::new(handle, "Save", true, None::<&str>)?;
     let menu = Menu::with_items(handle, &[
       &Submenu::with_items(handle, "File", true, &[
         &save_menu_item,
@@ -978,7 +978,7 @@ use tauri::menu::{Menu, Submenu, MenuItem};
 tauri::Builder::default()
   .setup(|app| {
     let handle = app.handle();
-    let save_menu_item = MenuItem::new(handle, "Save", true, None);
+    let save_menu_item = MenuItem::new(handle, "Save", true, None::<&str>)?;
     let menu = Menu::with_items(handle, &[
       &Submenu::with_items(handle, "File", true, &[
         &save_menu_item,

+ 9 - 8
examples/api/src-tauri/src/tray.rs

@@ -10,15 +10,16 @@ use tauri::{
 };
 
 pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
-  let toggle_i = MenuItem::with_id(app, "toggle", "Toggle", true, None);
-  let new_window_i = MenuItem::with_id(app, "new-window", "New window", true, None);
-  let icon_i_1 = MenuItem::with_id(app, "icon-1", "Icon 1", true, None);
-  let icon_i_2 = MenuItem::with_id(app, "icon-2", "Icon 2", true, None);
+  let toggle_i = MenuItem::with_id(app, "toggle", "Toggle", true, None::<&str>)?;
+  let new_window_i = MenuItem::with_id(app, "new-window", "New window", true, None::<&str>)?;
+  let icon_i_1 = MenuItem::with_id(app, "icon-1", "Icon 1", true, None::<&str>)?;
+  let icon_i_2 = MenuItem::with_id(app, "icon-2", "Icon 2", true, None::<&str>)?;
   #[cfg(target_os = "macos")]
-  let set_title_i = MenuItem::with_id(app, "set-title", "Set Title", true, None);
-  let switch_i = MenuItem::with_id(app, "switch-menu", "Switch Menu", true, None);
-  let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None);
-  let remove_tray_i = MenuItem::with_id(app, "remove-tray", "Remove Tray icon", true, None);
+  let set_title_i = MenuItem::with_id(app, "set-title", "Set Title", true, None::<&str>)?;
+  let switch_i = MenuItem::with_id(app, "switch-menu", "Switch Menu", true, None::<&str>)?;
+  let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
+  let remove_tray_i =
+    MenuItem::with_id(app, "remove-tray", "Remove Tray icon", true, None::<&str>)?;
   let menu1 = Menu::with_items(
     app,
     &[