瀏覽代碼

refactor(core): remove async from app hooks, add InvokeMessage type (#1392)

Lucas Fernandes Nogueira 4 年之前
父節點
當前提交
1318ffb47c

+ 1 - 1
.changes/plugin-mutable.md

@@ -2,4 +2,4 @@
 "tauri": minor
 ---
 
-The plugin instance is now mutable and must be `Send + Sync`.
+The plugin instance is now mutable and must be `Send`.

+ 1 - 1
examples/api/src-tauri/src/main.rs

@@ -14,7 +14,7 @@ struct Reply {
 
 fn main() {
   tauri::AppBuilder::default()
-    .setup(|webview_manager| async move {
+    .setup(move |webview_manager| {
       let dispatcher = webview_manager.current_webview().unwrap();
       let dispatcher_ = dispatcher.clone();
       dispatcher.listen("js-event", move |event| {

+ 1 - 1
examples/multiwindow/src-tauri/src/main.rs

@@ -7,7 +7,7 @@ use tauri::WebviewBuilderExt;
 
 fn main() {
   tauri::AppBuilder::default()
-    .setup(|webview_manager| async move {
+    .setup(move |webview_manager| {
       if webview_manager.current_window_label() == "Main" {
         webview_manager.listen("clicked", move |_| {
           println!("got 'clicked' event on global channel");

+ 2 - 8
tauri-api/src/cli.rs

@@ -1,4 +1,4 @@
-use crate::config::{CliArg, CliConfig, Config};
+use crate::config::{CliArg, CliConfig};
 
 use clap::{App, Arg, ArgMatches, ErrorKind};
 use serde::Serialize;
@@ -53,13 +53,7 @@ impl Matches {
 }
 
 /// Gets the arg matches of the CLI definition.
-pub fn get_matches(config: &Config) -> crate::Result<Matches> {
-  let cli = config
-    .tauri
-    .cli
-    .as_ref()
-    .ok_or(crate::Error::CliNotConfigured)?;
-
+pub fn get_matches(cli: &CliConfig) -> crate::Result<Matches> {
   let about = cli
     .description()
     .unwrap_or(&crate_description!().to_string())

+ 0 - 3
tauri-api/src/error.rs

@@ -16,9 +16,6 @@ pub enum Error {
   /// The dialog operation was cancelled by the user.
   #[error("user cancelled the dialog")]
   DialogCancelled,
-  /// CLI config not set.
-  #[error("CLI configuration not set on tauri.conf.json")]
-  CliNotConfigured,
   /// The network error.
   #[error("Network Error: {0}")]
   Network(#[from] reqwest::Error),

+ 14 - 13
tauri-macros/src/command.rs

@@ -62,13 +62,10 @@ pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream
   // If function doesn't take the webview manager, wrapper just takes webview manager generically and ignores it
   // Otherwise the wrapper uses the specific type from the original function declaration
   let mut manager_arg_type = quote!(::tauri::WebviewManager<A>);
-  let mut application_ext_generic = quote!(<A: ::tauri::ApplicationExt>);
   let manager_arg_maybe = match types.first() {
     Some(first_type) if uses_manager => {
       // Give wrapper specific type
       manager_arg_type = quote!(#first_type);
-      // Generic is no longer needed
-      application_ext_generic = quote!();
       // Remove webview manager arg from list so it isn't expected as arg from JS
       types.drain(0..1);
       names.drain(0..1);
@@ -91,24 +88,28 @@ pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream
   let return_value = if returns_result {
     quote! {
       match #fn_name(#manager_arg_maybe #(parsed_args.#names),*)#await_maybe {
-        Ok(value) => ::core::result::Result::Ok(value.into()),
-        Err(e) => ::core::result::Result::Err(tauri::Error::Command(::serde_json::to_value(e)?)),
+        Ok(value) => ::core::result::Result::Ok(value),
+        Err(e) => ::core::result::Result::Err(e),
       }
     }
   } else {
-    quote! { ::core::result::Result::Ok(#fn_name(#manager_arg_maybe #(parsed_args.#names),*)#await_maybe.into()) }
+    quote! { ::core::result::Result::<_, ()>::Ok(#fn_name(#manager_arg_maybe #(parsed_args.#names),*)#await_maybe) }
   };
 
   quote! {
     #function
-    pub async fn #fn_wrapper #application_ext_generic(_manager: #manager_arg_type, arg: ::serde_json::Value) -> ::tauri::Result<::tauri::InvokeResponse> {
+    pub fn #fn_wrapper<A: ::tauri::ApplicationExt + 'static>(_manager: #manager_arg_type, message: ::tauri::InvokeMessage<A>) {
       #[derive(::serde::Deserialize)]
       #[serde(rename_all = "camelCase")]
       struct ParsedArgs {
         #(#names: #types),*
       }
-      let parsed_args: ParsedArgs = ::serde_json::from_value(arg).map_err(|e| ::tauri::Error::InvalidArgs(#fn_name_str, e))?;
-      #return_value
+      match ::serde_json::from_value::<ParsedArgs>(message.payload()) {
+        Ok(parsed_args) => message.respond_async(async move {
+          #return_value
+        }),
+        Err(e) => message.reject(::core::result::Result::<(), String>::Err(::tauri::Error::InvalidArgs(#fn_name_str, e).to_string())),
+      }
     }
   }
 }
@@ -133,10 +134,10 @@ pub fn generate_handler(item: proc_macro::TokenStream) -> TokenStream {
   });
 
   quote! {
-    |webview_manager, command, arg| async move {
-      match command.as_str() {
-        #(stringify!(#fn_names) => #fn_wrappers(webview_manager, arg).await,)*
-        _ => Err(tauri::Error::UnknownApi(None)),
+    move |webview_manager, message| {
+      match message.command() {
+        #(stringify!(#fn_names) => #fn_wrappers(webview_manager, message),)*
+        _ => {},
       }
     }
   }

+ 2 - 2
tauri-utils/src/config.rs

@@ -156,7 +156,7 @@ impl Default for WindowConfig {
 }
 
 /// A CLI argument definition
-#[derive(PartialEq, Deserialize, Debug, Default)]
+#[derive(PartialEq, Deserialize, Debug, Default, Clone)]
 #[serde(rename_all = "camelCase")]
 pub struct CliArg {
   /// The short version of the argument, without the preceding -.
@@ -243,7 +243,7 @@ pub struct CliArg {
 }
 
 /// The CLI root command definition.
-#[derive(PartialEq, Deserialize, Debug)]
+#[derive(PartialEq, Deserialize, Debug, Clone)]
 #[serde(tag = "cli", rename_all = "camelCase")]
 #[allow(missing_docs)] // TODO
 pub struct CliConfig {

+ 195 - 81
tauri/src/app.rs

@@ -1,7 +1,10 @@
-use futures::future::BoxFuture;
 use serde::{Deserialize, Serialize};
 use serde_json::Value as JsonValue;
-use tauri_api::{config::Config, private::AsTauriContext};
+use tauri_api::{
+  config::Config,
+  private::AsTauriContext,
+  rpc::{format_callback, format_callback_result},
+};
 
 use std::{
   collections::HashMap,
@@ -21,12 +24,165 @@ pub use webview::{
 };
 pub use webview_manager::{WebviewDispatcher, WebviewManager};
 
-type InvokeHandler<A> = dyn Fn(WebviewManager<A>, String, JsonValue) -> BoxFuture<'static, crate::Result<InvokeResponse>>
-  + Send
-  + Sync;
-type ManagerHook<A> = dyn Fn(WebviewManager<A>) -> BoxFuture<'static, ()> + Send + Sync;
-type PageLoadHook<A> =
-  dyn Fn(WebviewManager<A>, PageLoadPayload) -> BoxFuture<'static, ()> + Send + Sync;
+type InvokeHandler<A> = dyn Fn(WebviewManager<A>, InvokeMessage<A>) + Send;
+type ManagerHook<A> = dyn Fn(WebviewManager<A>) + Send;
+type PageLoadHook<A> = dyn Fn(WebviewManager<A>, PageLoadPayload) + Send;
+
+/// Payload from an invoke call.
+#[derive(Debug, Deserialize)]
+pub(crate) struct InvokePayload {
+  #[serde(rename = "__tauriModule")]
+  tauri_module: Option<String>,
+  callback: String,
+  error: String,
+  #[serde(rename = "mainThread", default)]
+  pub(crate) main_thread: bool,
+  #[serde(flatten)]
+  inner: JsonValue,
+}
+
+/// An invoke message.
+pub struct InvokeMessage<A: ApplicationExt> {
+  webview_manager: WebviewManager<A>,
+  command: String,
+  payload: InvokePayload,
+}
+
+impl<A: ApplicationExt + 'static> InvokeMessage<A> {
+  pub(crate) fn new(
+    webview_manager: WebviewManager<A>,
+    command: String,
+    payload: InvokePayload,
+  ) -> Self {
+    Self {
+      webview_manager,
+      command,
+      payload,
+    }
+  }
+
+  /// The invoke command.
+  pub fn command(&self) -> &str {
+    &self.command
+  }
+
+  /// The invoke payload.
+  pub fn payload(&self) -> JsonValue {
+    self.payload.inner.clone()
+  }
+
+  /// Reply to the invoke promise with a async task.
+  pub fn respond_async<
+    T: Serialize,
+    E: Serialize,
+    F: std::future::Future<Output = Result<T, E>> + Send + 'static,
+  >(
+    self,
+    task: F,
+  ) {
+    if self.payload.main_thread {
+      crate::async_runtime::block_on(async move {
+        return_task(
+          &self.webview_manager,
+          task,
+          self.payload.callback,
+          self.payload.error,
+        )
+        .await;
+      });
+    } else {
+      crate::async_runtime::spawn(async move {
+        return_task(
+          &self.webview_manager,
+          task,
+          self.payload.callback,
+          self.payload.error,
+        )
+        .await;
+      });
+    }
+  }
+
+  /// Reply to the invoke promise running the given closure.
+  pub fn respond_closure<T: Serialize, E: Serialize, F: FnOnce() -> Result<T, E>>(self, f: F) {
+    return_closure(
+      &self.webview_manager,
+      f,
+      self.payload.callback,
+      self.payload.error,
+    )
+  }
+
+  /// Resolve the invoke promise with a value.
+  pub fn resolve<S: Serialize>(self, value: S) {
+    return_result(
+      &self.webview_manager,
+      Result::<S, ()>::Ok(value),
+      self.payload.callback,
+      self.payload.error,
+    )
+  }
+
+  /// Reject the invoke promise with a value.
+  pub fn reject<S: Serialize>(self, value: S) {
+    return_result(
+      &self.webview_manager,
+      Result::<(), S>::Err(value),
+      self.payload.callback,
+      self.payload.error,
+    )
+  }
+}
+
+/// Asynchronously executes the given task
+/// and evaluates its Result to the JS promise described by the `success_callback` and `error_callback` function names.
+///
+/// If the Result `is_ok()`, the callback will be the `success_callback` function name and the argument will be the Ok value.
+/// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value.
+async fn return_task<
+  A: ApplicationExt + 'static,
+  T: Serialize,
+  E: Serialize,
+  F: std::future::Future<Output = Result<T, E>> + Send + 'static,
+>(
+  webview_manager: &crate::WebviewManager<A>,
+  task: F,
+  success_callback: String,
+  error_callback: String,
+) {
+  let result = task.await;
+  return_closure(webview_manager, || result, success_callback, error_callback)
+}
+
+fn return_closure<
+  A: ApplicationExt + 'static,
+  T: Serialize,
+  E: Serialize,
+  F: FnOnce() -> Result<T, E>,
+>(
+  webview_manager: &crate::WebviewManager<A>,
+  f: F,
+  success_callback: String,
+  error_callback: String,
+) {
+  return_result(webview_manager, f(), success_callback, error_callback)
+}
+
+fn return_result<A: ApplicationExt + 'static, T: Serialize, E: Serialize>(
+  webview_manager: &crate::WebviewManager<A>,
+  result: Result<T, E>,
+  success_callback: String,
+  error_callback: String,
+) {
+  let callback_string =
+    match format_callback_result(result, success_callback, error_callback.clone()) {
+      Ok(callback_string) => callback_string,
+      Err(e) => format_callback(error_callback, e.to_string()),
+    };
+  if let Ok(dispatcher) = webview_manager.current_webview() {
+    let _ = dispatcher.eval(callback_string.as_str());
+  }
+}
 
 /// `App` runtime information.
 pub struct Context {
@@ -51,19 +207,6 @@ pub(crate) struct Webview<A: ApplicationExt> {
   pub(crate) url: WindowUrl,
 }
 
-/// The response for a JS `invoke` call.
-pub struct InvokeResponse {
-  json: crate::Result<JsonValue>,
-}
-
-impl<T: Serialize> From<T> for InvokeResponse {
-  fn from(value: T) -> Self {
-    Self {
-      json: serde_json::to_value(value).map_err(Into::into),
-    }
-  }
-}
-
 /// The payload for the "page_load" hook.
 #[derive(Debug, Clone, Deserialize)]
 pub struct PageLoadPayload {
@@ -120,36 +263,27 @@ impl<A: ApplicationExt + 'static> App<A> {
   /// Runs the invoke handler if defined.
   /// Returns whether the message was consumed or not.
   /// The message is considered consumed if the handler exists and returns an Ok Result.
-  pub(crate) async fn run_invoke_handler(
+  pub(crate) fn run_invoke_handler(
     &self,
     dispatcher: &WebviewManager<A>,
-    command: String,
-    arg: &JsonValue,
-  ) -> crate::Result<Option<InvokeResponse>> {
+    message: InvokeMessage<A>,
+  ) {
     if let Some(ref invoke_handler) = self.invoke_handler {
-      invoke_handler(dispatcher.clone(), command, arg.clone())
-        .await
-        .map(Some)
-    } else {
-      Ok(None)
+      invoke_handler(dispatcher.clone(), message);
     }
   }
 
   /// Runs the setup hook if defined.
-  pub(crate) async fn run_setup(&self, dispatcher: WebviewManager<A>) {
+  pub(crate) fn run_setup(&self, dispatcher: WebviewManager<A>) {
     if let Some(ref setup) = self.setup {
-      setup(dispatcher).await;
+      setup(dispatcher);
     }
   }
 
   /// Runs the on page load hook if defined.
-  pub(crate) async fn run_on_page_load(
-    &self,
-    dispatcher: &WebviewManager<A>,
-    payload: PageLoadPayload,
-  ) {
+  pub(crate) fn run_on_page_load(&self, dispatcher: &WebviewManager<A>, payload: PageLoadPayload) {
     if let Some(ref on_page_load) = self.on_page_load {
-      on_page_load(dispatcher.clone(), payload).await;
+      on_page_load(dispatcher.clone(), payload);
     }
   }
 }
@@ -167,21 +301,22 @@ trait WebviewInitializer<A: ApplicationExt> {
   fn on_webview_created(&self, webview_label: String, dispatcher: A::Dispatcher);
 }
 
-impl<A: ApplicationExt + 'static> WebviewInitializer<A> for Arc<App<A>> {
+impl<A: ApplicationExt + 'static> WebviewInitializer<A> for Arc<Mutex<App<A>>> {
   fn init_webview(&self, webview: Webview<A>) -> crate::Result<WebviewContext<A>> {
+    let application = self.lock().unwrap();
     let webview_manager = WebviewManager::new(
       self.clone(),
-      self.dispatchers.clone(),
+      application.dispatchers.clone(),
       webview.label.to_string(),
     );
     let (webview_builder, rpc_handler, custom_protocol) = utils::build_webview(
       self.clone(),
       webview,
       &webview_manager,
-      &self.url,
-      &self.window_labels.lock().unwrap(),
-      &self.plugin_initialization_script,
-      &self.context,
+      &application.url,
+      &application.window_labels.lock().unwrap(),
+      &application.plugin_initialization_script,
+      &application.context,
     )?;
     let file_drop_handler: Box<dyn Fn(FileDropEvent) -> bool + Send> = Box::new(move |event| {
       let webview_manager = webview_manager.clone();
@@ -204,7 +339,7 @@ impl<A: ApplicationExt + 'static> WebviewInitializer<A> for Arc<App<A>> {
   }
 
   fn on_webview_created(&self, webview_label: String, dispatcher: A::Dispatcher) {
-    self.dispatchers.lock().unwrap().insert(
+    self.lock().unwrap().dispatchers.lock().unwrap().insert(
       webview_label.to_string(),
       WebviewDispatcher::new(dispatcher, webview_label),
     );
@@ -241,53 +376,32 @@ impl<A: ApplicationExt + 'static> AppBuilder<A> {
   }
 
   /// Defines the JS message handler callback.
-  pub fn invoke_handler<
-    T: futures::Future<Output = crate::Result<InvokeResponse>> + Send + Sync + 'static,
-    F: Fn(WebviewManager<A>, String, JsonValue) -> T + Send + Sync + 'static,
-  >(
+  pub fn invoke_handler<F: Fn(WebviewManager<A>, InvokeMessage<A>) + Send + 'static>(
     mut self,
     invoke_handler: F,
   ) -> Self {
-    self.invoke_handler = Some(Box::new(move |webview_manager, command, args| {
-      Box::pin(invoke_handler(webview_manager, command, args))
-    }));
+    self.invoke_handler = Some(Box::new(invoke_handler));
     self
   }
 
   /// Defines the setup hook.
-  pub fn setup<
-    T: futures::Future<Output = ()> + Send + Sync + 'static,
-    F: Fn(WebviewManager<A>) -> T + Send + Sync + 'static,
-  >(
-    mut self,
-    setup: F,
-  ) -> Self {
-    self.setup = Some(Box::new(move |webview_manager| {
-      Box::pin(setup(webview_manager))
-    }));
+  pub fn setup<F: Fn(WebviewManager<A>) + Send + 'static>(mut self, setup: F) -> Self {
+    self.setup = Some(Box::new(setup));
     self
   }
 
   /// Defines the page load hook.
-  pub fn on_page_load<
-    T: futures::Future<Output = ()> + Send + Sync + 'static,
-    F: Fn(WebviewManager<A>, PageLoadPayload) -> T + Send + Sync + 'static,
-  >(
+  pub fn on_page_load<F: Fn(WebviewManager<A>, PageLoadPayload) + Send + 'static>(
     mut self,
     on_page_load: F,
   ) -> Self {
-    self.on_page_load = Some(Box::new(move |webview_manager, payload| {
-      Box::pin(on_page_load(webview_manager, payload))
-    }));
+    self.on_page_load = Some(Box::new(on_page_load));
     self
   }
 
   /// Adds a plugin to the runtime.
-  pub fn plugin(
-    self,
-    plugin: impl crate::plugin::Plugin<A> + Send + Sync + Sync + 'static,
-  ) -> Self {
-    crate::async_runtime::block_on(crate::plugin::register(A::plugin_store(), plugin));
+  pub fn plugin(self, plugin: impl crate::plugin::Plugin<A> + Send + 'static) -> Self {
+    crate::plugin::register(A::plugin_store(), plugin);
     self
   }
 
@@ -310,8 +424,7 @@ impl<A: ApplicationExt + 'static> AppBuilder<A> {
   /// Builds the App.
   pub fn build(self, context: impl AsTauriContext) -> App<A> {
     let window_labels: Vec<String> = self.webviews.iter().map(|w| w.label.to_string()).collect();
-    let plugin_initialization_script =
-      crate::async_runtime::block_on(crate::plugin::initialization_script(A::plugin_store()));
+    let plugin_initialization_script = crate::plugin::initialization_script(A::plugin_store());
 
     let context = Context::new(context);
     let url = utils::get_url(&context);
@@ -339,11 +452,12 @@ impl Default for AppBuilder<Wry> {
 
 fn run<A: ApplicationExt + 'static>(mut application: App<A>) -> crate::Result<()> {
   let plugin_config = application.context.config.plugins.clone();
-  crate::async_runtime::block_on(crate::plugin::initialize(A::plugin_store(), plugin_config))?;
+  crate::plugin::initialize(A::plugin_store(), plugin_config)?;
 
   let webviews = application.webviews.take().unwrap();
 
-  let application = Arc::new(application);
+  let dispatchers = application.dispatchers.clone();
+  let application = Arc::new(Mutex::new(application));
   let mut webview_app = A::new()?;
   let mut main_webview_manager = None;
 
@@ -351,7 +465,7 @@ fn run<A: ApplicationExt + 'static>(mut application: App<A>) -> crate::Result<()
     let webview_label = webview.label.to_string();
     let webview_manager = WebviewManager::new(
       application.clone(),
-      application.dispatchers.clone(),
+      dispatchers.clone(),
       webview_label.to_string(),
     );
     if main_webview_manager.is_none() {
@@ -367,11 +481,11 @@ fn run<A: ApplicationExt + 'static>(mut application: App<A>) -> crate::Result<()
       file_drop_handler,
     )?;
     application.on_webview_created(webview_label, dispatcher);
-    crate::async_runtime::block_on(crate::plugin::created(A::plugin_store(), &webview_manager));
+    crate::plugin::created(A::plugin_store(), &webview_manager);
   }
 
   if let Some(main_webview_manager) = main_webview_manager {
-    crate::async_runtime::block_on(application.run_setup(main_webview_manager));
+    application.lock().unwrap().run_setup(main_webview_manager);
   }
 
   webview_app.run();

+ 33 - 130
tauri/src/app/utils.rs

@@ -1,33 +1,19 @@
 use crate::{
-  api::{
-    assets::Assets,
-    config::WindowUrl,
-    rpc::{format_callback, format_callback_result},
-  },
-  app::{Icon, InvokeResponse},
+  api::{assets::Assets, config::WindowUrl},
+  app::Icon,
   ApplicationExt, WebviewBuilderExt,
 };
 
 use super::{
   webview::{CustomProtocol, WebviewBuilderExtPrivate, WebviewRpcHandler},
-  App, Context, PageLoadPayload, RpcRequest, Webview, WebviewManager,
+  App, Context, InvokeMessage, InvokePayload, PageLoadPayload, RpcRequest, Webview, WebviewManager,
 };
 
-use serde::Deserialize;
 use serde_json::Value as JsonValue;
-use std::{borrow::Cow, sync::Arc};
-
-#[derive(Debug, Deserialize)]
-struct Message {
-  #[serde(rename = "__tauriModule")]
-  tauri_module: Option<String>,
-  callback: String,
-  error: String,
-  #[serde(rename = "mainThread", default)]
-  main_thread: bool,
-  #[serde(flatten)]
-  inner: JsonValue,
-}
+use std::{
+  borrow::Cow,
+  sync::{Arc, Mutex},
+};
 
 // setup content for dev-server
 #[cfg(dev)]
@@ -146,7 +132,7 @@ pub(super) type BuiltWebview<A> = (
 
 // build the webview.
 pub(super) fn build_webview<A: ApplicationExt + 'static>(
-  application: Arc<App<A>>,
+  application: Arc<Mutex<App<A>>>,
   webview: Webview<A>,
   webview_manager: &WebviewManager<A>,
   content_url: &str,
@@ -195,38 +181,9 @@ pub(super) fn build_webview<A: ApplicationExt + 'static>(
           .unwrap_or(&mut JsonValue::Null)
           .take();
         let webview_manager = webview_manager_.clone();
-        match serde_json::from_value::<Message>(arg) {
+        match serde_json::from_value::<InvokePayload>(arg) {
           Ok(message) => {
-            let application = application.clone();
-            let callback = message.callback.to_string();
-            let error = message.error.to_string();
-
-            if message.main_thread {
-              crate::async_runtime::block_on(async move {
-                execute_promise(
-                  &webview_manager,
-                  on_message(
-                    application,
-                    webview_manager.clone(),
-                    command.clone(),
-                    message,
-                  ),
-                  callback,
-                  error,
-                )
-                .await;
-              });
-            } else {
-              crate::async_runtime::spawn(async move {
-                execute_promise(
-                  &webview_manager,
-                  on_message(application, webview_manager.clone(), command, message),
-                  callback,
-                  error,
-                )
-                .await;
-              });
-            }
+            let _ = on_message(application.clone(), webview_manager, command, message);
           }
           Err(e) => {
             if let Ok(dispatcher) = webview_manager.current_webview() {
@@ -280,91 +237,37 @@ pub(super) fn build_webview<A: ApplicationExt + 'static>(
   Ok((webview_builder, rpc_handler, custom_protocol))
 }
 
-/// Asynchronously executes the given task
-/// and evaluates its Result to the JS promise described by the `success_callback` and `error_callback` function names.
-///
-/// If the Result `is_ok()`, the callback will be the `success_callback` function name and the argument will be the Ok value.
-/// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value.
-async fn execute_promise<
-  A: ApplicationExt + 'static,
-  F: std::future::Future<Output = crate::Result<InvokeResponse>> + Send + 'static,
->(
-  webview_manager: &crate::WebviewManager<A>,
-  task: F,
-  success_callback: String,
-  error_callback: String,
-) {
-  let callback_string = match format_callback_result(
-    task
-      .await
-      .and_then(|response| response.json)
-      .map_err(|err| err.to_string()),
-    success_callback,
-    error_callback.clone(),
-  ) {
-    Ok(callback_string) => callback_string,
-    Err(e) => format_callback(error_callback, e.to_string()),
-  };
-  if let Ok(dispatcher) = webview_manager.current_webview() {
-    let _ = dispatcher.eval(callback_string.as_str());
-  }
-}
-
-async fn on_message<A: ApplicationExt + 'static>(
-  application: Arc<App<A>>,
+fn on_message<A: ApplicationExt + 'static>(
+  application: Arc<Mutex<App<A>>>,
   webview_manager: WebviewManager<A>,
   command: String,
-  message: Message,
-) -> crate::Result<InvokeResponse> {
+  payload: InvokePayload,
+) -> crate::Result<()> {
+  let message = InvokeMessage::new(webview_manager.clone(), command.to_string(), payload);
   if &command == "__initialized" {
-    let payload: PageLoadPayload = serde_json::from_value(message.inner)?;
+    let payload: PageLoadPayload = serde_json::from_value(message.payload())?;
     application
-      .run_on_page_load(&webview_manager, payload.clone())
-      .await;
-    crate::plugin::on_page_load(A::plugin_store(), &webview_manager, payload).await;
-    Ok(().into())
-  } else if let Some(module) = &message.tauri_module {
+      .lock()
+      .unwrap()
+      .run_on_page_load(&webview_manager, payload.clone());
+    crate::plugin::on_page_load(A::plugin_store(), &webview_manager, payload);
+  } else if let Some(module) = &message.payload.tauri_module {
+    let module = module.to_string();
     crate::endpoints::handle(
       &webview_manager,
-      module.to_string(),
-      message.inner,
-      &application.context,
-    )
-    .await
+      module,
+      message,
+      &application.lock().unwrap().context,
+    );
+  } else if command.starts_with("plugin:") {
+    crate::plugin::extend_api(A::plugin_store(), &webview_manager, command, message);
   } else {
-    let mut response = match application
-      .run_invoke_handler(&webview_manager, command.clone(), &message.inner)
-      .await
-    {
-      Ok(value) => {
-        if let Some(value) = value {
-          Ok(value)
-        } else {
-          Err(crate::Error::UnknownApi(None))
-        }
-      }
-      Err(e) => Err(e),
-    };
-    if let Err(crate::Error::UnknownApi(_)) = response {
-      match crate::plugin::extend_api(A::plugin_store(), &webview_manager, command, &message.inner)
-        .await
-      {
-        Ok(value) => {
-          // If value is None, that means that no plugin matched the command
-          // and the UnknownApi error should be sent to the webview
-          // Otherwise, send the result of plugin handler
-          if value.is_some() {
-            response = Ok(value.into());
-          }
-        }
-        Err(e) => {
-          // A plugin handler was found but it failed
-          response = Err(e);
-        }
-      }
-    }
-    response
+    application
+      .lock()
+      .unwrap()
+      .run_invoke_handler(&webview_manager, message);
   }
+  Ok(())
 }
 
 #[cfg(test)]

+ 3 - 11
tauri/src/app/webview.rs

@@ -177,7 +177,7 @@ pub struct CustomProtocol {
   /// Name of the protocol
   pub name: String,
   /// Handler for protocol
-  pub handler: Box<dyn Fn(&str) -> crate::Result<Vec<u8>> + Send + Sync>,
+  pub handler: Box<dyn Fn(&str) -> crate::Result<Vec<u8>> + Send>,
 }
 
 /// The file drop event payload.
@@ -198,11 +198,7 @@ pub type FileDropHandler = Box<dyn Fn(FileDropEvent) -> bool + Send>;
 /// Webview dispatcher. A thread-safe handle to the webview API.
 pub trait ApplicationDispatcherExt: Clone + Send + Sized {
   /// The webview builder type.
-  type WebviewBuilder: WebviewBuilderExt
-    + WebviewBuilderExtPrivate
-    + From<WindowConfig>
-    + Send
-    + Sync;
+  type WebviewBuilder: WebviewBuilderExt + WebviewBuilderExtPrivate + From<WindowConfig> + Send;
   /// Creates a webview.
   fn create_webview(
     &self,
@@ -283,11 +279,7 @@ pub trait ApplicationDispatcherExt: Clone + Send + Sized {
 /// Manages windows and webviews.
 pub trait ApplicationExt: Sized {
   /// The webview builder.
-  type WebviewBuilder: WebviewBuilderExt
-    + WebviewBuilderExtPrivate
-    + From<WindowConfig>
-    + Send
-    + Sync;
+  type WebviewBuilder: WebviewBuilderExt + WebviewBuilderExtPrivate + From<WindowConfig> + Send;
   /// The message dispatcher.
   type Dispatcher: ApplicationDispatcherExt<WebviewBuilder = Self::WebviewBuilder>;
 

+ 15 - 3
tauri/src/app/webview_manager.rs

@@ -1,5 +1,6 @@
 use std::{
   collections::HashMap,
+  future::Future,
   sync::{Arc, Mutex},
 };
 
@@ -193,7 +194,7 @@ pub struct WebviewManager<A = Wry>
 where
   A: ApplicationExt,
 {
-  application: Arc<App<A>>,
+  application: Arc<Mutex<App<A>>>,
   dispatchers: Arc<Mutex<HashMap<String, WebviewDispatcher<A::Dispatcher>>>>,
   current_webview_window_label: String,
 }
@@ -210,7 +211,7 @@ impl<A: ApplicationExt> Clone for WebviewManager<A> {
 
 impl<A: ApplicationExt + 'static> WebviewManager<A> {
   pub(crate) fn new(
-    application: Arc<App<A>>,
+    application: Arc<Mutex<App<A>>>,
     dispatchers: Arc<Mutex<HashMap<String, WebviewDispatcher<A::Dispatcher>>>>,
     label: String,
   ) -> Self {
@@ -221,6 +222,15 @@ impl<A: ApplicationExt + 'static> WebviewManager<A> {
     }
   }
 
+  /// Spawns an asynchronous task
+  pub fn spawn<F>(task: F)
+  where
+    F: Future + Send + 'static,
+    F::Output: Send + 'static,
+  {
+    crate::async_runtime::spawn(task)
+  }
+
   /// Returns the label of the window associated with the current context.
   pub fn current_window_label(&self) -> &str {
     &self.current_webview_window_label
@@ -257,6 +267,8 @@ impl<A: ApplicationExt + 'static> WebviewManager<A> {
     };
     self
       .application
+      .lock()
+      .unwrap()
       .window_labels
       .lock()
       .unwrap()
@@ -278,7 +290,7 @@ impl<A: ApplicationExt + 'static> WebviewManager<A> {
     self
       .application
       .on_webview_created(label.to_string(), window_dispatcher.clone());
-    crate::plugin::created(A::plugin_store(), &webview_manager).await;
+    crate::plugin::created(A::plugin_store(), &webview_manager);
     Ok(WebviewDispatcher::new(window_dispatcher, label))
   }
 

+ 81 - 24
tauri/src/endpoints.rs

@@ -10,14 +10,24 @@ mod notification;
 mod shell;
 mod window;
 
-use crate::{
-  app::{Context, InvokeResponse},
-  ApplicationExt,
-};
+use crate::{app::Context, ApplicationExt, InvokeMessage};
 
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
 use serde_json::Value as JsonValue;
 
+/// The response for a JS `invoke` call.
+pub struct InvokeResponse {
+  json: crate::Result<JsonValue>,
+}
+
+impl<T: Serialize> From<T> for InvokeResponse {
+  fn from(value: T) -> Self {
+    Self {
+      json: serde_json::to_value(value).map_err(Into::into),
+    }
+  }
+}
+
 #[derive(Deserialize)]
 #[serde(tag = "module", content = "message")]
 enum Module {
@@ -34,35 +44,82 @@ enum Module {
 }
 
 impl Module {
-  async fn run<A: ApplicationExt + 'static>(
+  fn run<A: ApplicationExt + 'static>(
     self,
-    webview_manager: &crate::WebviewManager<A>,
+    webview_manager: crate::WebviewManager<A>,
+    message: InvokeMessage<A>,
     context: &Context,
-  ) -> crate::Result<InvokeResponse> {
+  ) {
     match self {
-      Self::Fs(cmd) => cmd.run(),
-      Self::Window(cmd) => cmd.run(webview_manager).await,
-      Self::Shell(cmd) => cmd.run(),
-      Self::Event(cmd) => cmd.run(webview_manager),
-      Self::Internal(cmd) => cmd.run(),
-      Self::Dialog(cmd) => cmd.run(),
-      Self::Cli(cmd) => cmd.run(context),
-      Self::Notification(cmd) => cmd.run(context),
-      Self::Http(cmd) => crate::async_runtime::block_on(cmd.run()),
-      Self::GlobalShortcut(cmd) => cmd.run(webview_manager),
+      Self::Fs(cmd) => message
+        .respond_async(async move { cmd.run().and_then(|r| r.json).map_err(|e| e.to_string()) }),
+      Self::Window(cmd) => message.respond_async(async move {
+        cmd
+          .run(&webview_manager)
+          .await
+          .and_then(|r| r.json)
+          .map_err(|e| e.to_string())
+      }),
+      Self::Shell(cmd) => message
+        .respond_async(async move { cmd.run().and_then(|r| r.json).map_err(|e| e.to_string()) }),
+      Self::Event(cmd) => message.respond_async(async move {
+        cmd
+          .run(&webview_manager)
+          .and_then(|r| r.json)
+          .map_err(|e| e.to_string())
+      }),
+      Self::Internal(cmd) => message
+        .respond_async(async move { cmd.run().and_then(|r| r.json).map_err(|e| e.to_string()) }),
+      Self::Dialog(cmd) => message
+        .respond_async(async move { cmd.run().and_then(|r| r.json).map_err(|e| e.to_string()) }),
+      Self::Cli(cmd) => {
+        if let Some(cli_config) = context.config.tauri.cli.clone() {
+          message.respond_async(async move {
+            cmd
+              .run(&cli_config)
+              .and_then(|r| r.json)
+              .map_err(|e| e.to_string())
+          })
+        }
+      }
+      Self::Notification(cmd) => {
+        let identifier = context.config.tauri.bundle.identifier.clone();
+        message.respond_async(async move {
+          cmd
+            .run(identifier)
+            .and_then(|r| r.json)
+            .map_err(|e| e.to_string())
+        })
+      }
+      Self::Http(cmd) => message.respond_async(async move {
+        cmd
+          .run()
+          .await
+          .and_then(|r| r.json)
+          .map_err(|e| e.to_string())
+      }),
+      Self::GlobalShortcut(cmd) => message.respond_async(async move {
+        cmd
+          .run(&webview_manager)
+          .and_then(|r| r.json)
+          .map_err(|e| e.to_string())
+      }),
     }
   }
 }
 
-pub(crate) async fn handle<A: ApplicationExt + 'static>(
+pub(crate) fn handle<A: ApplicationExt + 'static>(
   webview_manager: &crate::WebviewManager<A>,
   module: String,
-  mut arg: JsonValue,
+  message: InvokeMessage<A>,
   context: &Context,
-) -> crate::Result<InvokeResponse> {
-  if let JsonValue::Object(ref mut obj) = arg {
+) {
+  let mut payload = message.payload();
+  if let JsonValue::Object(ref mut obj) = payload {
     obj.insert("module".to_string(), JsonValue::String(module));
   }
-  let module: Module = serde_json::from_value(arg)?;
-  module.run(webview_manager, context).await
+  match serde_json::from_value::<Module>(payload) {
+    Ok(module) => module.run(webview_manager.clone(), message, context),
+    Err(e) => message.reject(e.to_string()),
+  }
 }

+ 4 - 3
tauri/src/endpoints/cli.rs

@@ -1,4 +1,5 @@
-use crate::app::InvokeResponse;
+use super::InvokeResponse;
+use crate::api::config::CliConfig;
 use serde::Deserialize;
 
 /// The API descriptor.
@@ -11,12 +12,12 @@ pub enum Cmd {
 
 impl Cmd {
   #[allow(unused_variables)]
-  pub fn run(self, context: &crate::app::Context) -> crate::Result<InvokeResponse> {
+  pub fn run(self, cli_config: &CliConfig) -> crate::Result<InvokeResponse> {
     match self {
       #[allow(unused_variables)]
       Self::CliMatches => {
         #[cfg(cli)]
-        return tauri_api::cli::get_matches(&context.config)
+        return tauri_api::cli::get_matches(&cli_config)
           .map_err(Into::into)
           .map(Into::into);
         #[cfg(not(cli))]

+ 2 - 4
tauri/src/endpoints/dialog.rs

@@ -1,9 +1,7 @@
+use super::InvokeResponse;
 #[cfg(any(dialog_open, dialog_save))]
 use crate::api::dialog::FileDialogBuilder;
-use crate::{
-  api::dialog::{ask as ask_dialog, message as message_dialog, AskResponse},
-  app::InvokeResponse,
-};
+use crate::api::dialog::{ask as ask_dialog, message as message_dialog, AskResponse};
 use serde::Deserialize;
 
 use std::path::PathBuf;

+ 1 - 1
tauri/src/endpoints/event.rs

@@ -1,4 +1,4 @@
-use crate::app::InvokeResponse;
+use super::InvokeResponse;
 use serde::Deserialize;
 
 /// The API descriptor.

+ 2 - 1
tauri/src/endpoints/file_system.rs

@@ -1,4 +1,5 @@
-use crate::{api::path::BaseDirectory, app::InvokeResponse, ApplicationDispatcherExt};
+use super::InvokeResponse;
+use crate::{api::path::BaseDirectory, ApplicationDispatcherExt};
 
 use serde::{Deserialize, Serialize};
 use tauri_api::{dir, file, path::resolve_path};

+ 2 - 1
tauri/src/endpoints/global_shortcut.rs

@@ -1,6 +1,7 @@
+use super::InvokeResponse;
 #[cfg(global_shortcut_all)]
 use crate::api::shortcuts::ShortcutManager;
-use crate::app::{InvokeResponse, WebviewDispatcher};
+use crate::app::WebviewDispatcher;
 use once_cell::sync::Lazy;
 use serde::Deserialize;
 

+ 2 - 2
tauri/src/endpoints/http.rs

@@ -1,4 +1,4 @@
-use crate::app::InvokeResponse;
+use super::InvokeResponse;
 
 use once_cell::sync::Lazy;
 use serde::Deserialize;
@@ -40,7 +40,7 @@ impl Cmd {
         let mut store = clients().lock().unwrap();
         let id = rand::random::<ClientId>();
         store.insert(id, client);
-        Ok(id.into())
+        Ok(InvokeResponse::from(id))
       }
       Self::DropClient { client } => {
         let mut store = clients().lock().unwrap();

+ 1 - 1
tauri/src/endpoints/internal.rs

@@ -1,4 +1,4 @@
-use crate::app::InvokeResponse;
+use super::InvokeResponse;
 use serde::Deserialize;
 
 /// The API descriptor.

+ 5 - 6
tauri/src/endpoints/notification.rs

@@ -1,7 +1,7 @@
-use crate::app::InvokeResponse;
+use super::InvokeResponse;
 use serde::Deserialize;
 #[cfg(notification_all)]
-use tauri_api::{config::Config, notification::Notification};
+use tauri_api::notification::Notification;
 
 /// The options for the notification API.
 #[derive(Deserialize)]
@@ -27,11 +27,11 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  pub fn run(self, context: &crate::app::Context) -> crate::Result<InvokeResponse> {
+  pub fn run(self, identifier: String) -> crate::Result<InvokeResponse> {
     match self {
       Self::Notification { options } => {
         #[cfg(notification_all)]
-        return send(options, &context.config).map(Into::into);
+        return send(options, identifier).map(Into::into);
         #[cfg(not(notification_all))]
         Err(crate::Error::ApiNotAllowlisted("notification".to_string()))
       }
@@ -52,8 +52,7 @@ impl Cmd {
 }
 
 #[cfg(notification_all)]
-pub fn send(options: NotificationOptions, config: &Config) -> crate::Result<InvokeResponse> {
-  let identifier = config.tauri.bundle.identifier.clone();
+pub fn send(options: NotificationOptions, identifier: String) -> crate::Result<InvokeResponse> {
   let mut notification = Notification::new(identifier).title(options.title);
   if let Some(body) = options.body {
     notification = notification.body(body);

+ 1 - 1
tauri/src/endpoints/shell.rs

@@ -1,4 +1,4 @@
-use crate::app::InvokeResponse;
+use super::InvokeResponse;
 use serde::Deserialize;
 
 /// The API descriptor.

+ 2 - 1
tauri/src/endpoints/window.rs

@@ -1,4 +1,5 @@
-use crate::app::{ApplicationExt, Icon, InvokeResponse};
+use super::InvokeResponse;
+use crate::app::{ApplicationExt, Icon};
 use serde::Deserialize;
 
 #[derive(Deserialize)]

+ 0 - 3
tauri/src/error.rs

@@ -43,9 +43,6 @@ pub enum Error {
   /// API not whitelisted on tauri.conf.json
   #[error("'{0}' not on the allowlist (https://tauri.studio/docs/api/config#tauri.allowlist)")]
   ApiNotAllowlisted(String),
-  /// Command error (userland).
-  #[error("{0}")]
-  Command(serde_json::Value),
   /// Invalid args when running a command.
   #[error("invalid args for command `{0}`: {1}")]
   InvalidArgs(&'static str, serde_json::Error),

+ 39 - 64
tauri/src/plugin.rs

@@ -1,21 +1,17 @@
 use crate::{
-  api::config::PluginConfig, async_runtime::Mutex, ApplicationExt, PageLoadPayload, WebviewManager,
+  api::config::PluginConfig, ApplicationExt, InvokeMessage, PageLoadPayload, WebviewManager,
 };
 
-use futures::future::join_all;
-use serde_json::Value as JsonValue;
-
-use std::sync::Arc;
+use std::sync::{Arc, Mutex};
 
 /// The plugin interface.
-#[async_trait::async_trait]
-pub trait Plugin<A: ApplicationExt + 'static>: Send + Sync {
+pub trait Plugin<A: ApplicationExt + 'static>: Send {
   /// The plugin name. Used as key on the plugin config object.
   fn name(&self) -> &'static str;
 
   /// Initialize the plugin.
   #[allow(unused_variables)]
-  async fn initialize(&mut self, config: String) -> crate::Result<()> {
+  fn initialize(&mut self, config: String) -> crate::Result<()> {
     Ok(())
   }
 
@@ -24,67 +20,53 @@ pub trait Plugin<A: ApplicationExt + 'static>: Send + Sync {
   /// so global variables must be assigned to `window` instead of implicity declared.
   ///
   /// It's guaranteed that this script is executed before the page is loaded.
-  async fn initialization_script(&self) -> Option<String> {
+  fn initialization_script(&self) -> Option<String> {
     None
   }
 
   /// Callback invoked when the webview is created.
   #[allow(unused_variables)]
-  async fn created(&mut self, webview_manager: WebviewManager<A>) {}
+  fn created(&mut self, webview_manager: WebviewManager<A>) {}
 
   /// Callback invoked when the webview performs a navigation.
   #[allow(unused_variables)]
-  async fn on_page_load(&mut self, webview_manager: WebviewManager<A>, payload: PageLoadPayload) {}
+  fn on_page_load(&mut self, webview_manager: WebviewManager<A>, payload: PageLoadPayload) {}
 
   /// Add invoke_handler API extension commands.
   #[allow(unused_variables)]
-  async fn extend_api(
-    &mut self,
-    webview_manager: WebviewManager<A>,
-    command: String,
-    payload: &JsonValue,
-  ) -> crate::Result<JsonValue> {
-    Err(crate::Error::UnknownApi(None))
-  }
+  fn extend_api(&mut self, webview_manager: WebviewManager<A>, message: InvokeMessage<A>) {}
 }
 
 /// Plugin collection type.
-pub type PluginStore<A> = Arc<Mutex<Vec<Box<dyn Plugin<A> + Sync + Send>>>>;
+pub type PluginStore<A> = Arc<Mutex<Vec<Box<dyn Plugin<A> + Send>>>>;
 
 /// Registers a plugin.
-pub async fn register<A: ApplicationExt + 'static>(
+pub fn register<A: ApplicationExt + 'static>(
   store: &PluginStore<A>,
-  plugin: impl Plugin<A> + Sync + Send + 'static,
+  plugin: impl Plugin<A> + Send + 'static,
 ) {
-  let mut plugins = store.lock().await;
+  let mut plugins = store.lock().unwrap();
   plugins.push(Box::new(plugin));
 }
 
-pub(crate) async fn initialize<A: ApplicationExt + 'static>(
+pub(crate) fn initialize<A: ApplicationExt + 'static>(
   store: &PluginStore<A>,
   plugins_config: PluginConfig,
 ) -> crate::Result<()> {
-  let mut plugins = store.lock().await;
+  let mut plugins = store.lock().unwrap();
   for plugin in plugins.iter_mut() {
     let plugin_config = plugins_config.get(plugin.name());
-    plugin.initialize(plugin_config).await?;
+    plugin.initialize(plugin_config)?;
   }
 
   Ok(())
 }
 
-pub(crate) async fn initialization_script<A: ApplicationExt + 'static>(
-  store: &PluginStore<A>,
-) -> String {
-  let mut plugins = store.lock().await;
-  let mut futures = Vec::new();
-  for plugin in plugins.iter_mut() {
-    futures.push(plugin.initialization_script());
-  }
-
+pub(crate) fn initialization_script<A: ApplicationExt + 'static>(store: &PluginStore<A>) -> String {
+  let mut plugins = store.lock().unwrap();
   let mut initialization_script = String::new();
-  for res in join_all(futures).await {
-    if let Some(plugin_initialization_script) = res {
+  for plugin in plugins.iter_mut() {
+    if let Some(plugin_initialization_script) = plugin.initialization_script() {
       initialization_script.push_str(&format!(
         "(function () {{ {} }})();",
         plugin_initialization_script
@@ -94,51 +76,44 @@ pub(crate) async fn initialization_script<A: ApplicationExt + 'static>(
   initialization_script
 }
 
-pub(crate) async fn created<A: ApplicationExt + 'static>(
+pub(crate) fn created<A: ApplicationExt + 'static>(
   store: &PluginStore<A>,
   webview_manager: &crate::WebviewManager<A>,
 ) {
-  let mut plugins = store.lock().await;
-  let mut futures = Vec::new();
+  let mut plugins = store.lock().unwrap();
   for plugin in plugins.iter_mut() {
-    futures.push(plugin.created(webview_manager.clone()));
+    plugin.created(webview_manager.clone());
   }
-  join_all(futures).await;
 }
 
-pub(crate) async fn on_page_load<A: ApplicationExt + 'static>(
+pub(crate) fn on_page_load<A: ApplicationExt + 'static>(
   store: &PluginStore<A>,
   webview_manager: &crate::WebviewManager<A>,
   payload: PageLoadPayload,
 ) {
-  let mut plugins = store.lock().await;
-  let mut futures = Vec::new();
+  let mut plugins = store.lock().unwrap();
   for plugin in plugins.iter_mut() {
-    futures.push(plugin.on_page_load(webview_manager.clone(), payload.clone()));
+    plugin.on_page_load(webview_manager.clone(), payload.clone());
   }
-  join_all(futures).await;
 }
 
-pub(crate) async fn extend_api<A: ApplicationExt + 'static>(
+pub(crate) fn extend_api<A: ApplicationExt + 'static>(
   store: &PluginStore<A>,
   webview_manager: &crate::WebviewManager<A>,
   command: String,
-  arg: &JsonValue,
-) -> crate::Result<Option<JsonValue>> {
-  let mut plugins = store.lock().await;
-  for ext in plugins.iter_mut() {
-    match ext
-      .extend_api(webview_manager.clone(), command.clone(), arg)
-      .await
-    {
-      Ok(value) => {
-        return Ok(Some(value));
-      }
-      Err(e) => match e {
-        crate::Error::UnknownApi(_) => {}
-        _ => return Err(e),
-      },
+  message: InvokeMessage<A>,
+) {
+  let mut plugins = store.lock().unwrap();
+  let target_plugin_name = command
+    .replace("plugin:", "")
+    .split('|')
+    .next()
+    .unwrap()
+    .to_string();
+  for plugin in plugins.iter_mut() {
+    if plugin.name() == target_plugin_name {
+      plugin.extend_api(webview_manager.clone(), message);
+      break;
     }
   }
-  Ok(None)
 }