From 8323e45970220eb98665d8b64b696ee29fff9d4f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 5 Jan 2024 17:35:37 -0800 Subject: [PATCH] Call handlers from context of a runtime and scope --- examples/video_stream.rs | 33 +++-- packages/core/src/runtime.rs | 21 +++ packages/core/src/scope_context.rs | 7 +- packages/desktop/src/app.rs | 1 + packages/desktop/src/assets.rs | 176 +++++++----------------- packages/desktop/src/desktop_context.rs | 32 +++-- packages/desktop/src/hooks.rs | 36 ++--- packages/desktop/src/lib.rs | 3 +- packages/desktop/src/protocol.rs | 142 +++++++++---------- packages/desktop/src/webview.rs | 101 +++++++------- 10 files changed, 270 insertions(+), 282 deletions(-) diff --git a/examples/video_stream.rs b/examples/video_stream.rs index f495299f4..eaacf0a27 100644 --- a/examples/video_stream.rs +++ b/examples/video_stream.rs @@ -31,28 +31,33 @@ fn main() { } fn app(cx: Scope) -> Element { - use_asset_handler(cx, move |request: &AssetRequest| { - let request = request.clone(); - async move { + use_asset_handler(cx, "videos", move |request, responder| { + // Using dioxus::spawn works, but is slower than a dedicated thread + tokio::task::spawn(async move { let video_file = PathBuf::from(VIDEO_PATH); let mut file = tokio::fs::File::open(&video_file).await.unwrap(); - let response: Option>> = - match get_stream_response(&mut file, &request).await { - Ok(response) => Some(response.map(Cow::Owned)), - Err(err) => { - eprintln!("Error: {}", err); - None - } - }; - response - } + + match get_stream_response(&mut file, &request).await { + Ok(response) => responder.respond(response), + Err(err) => eprintln!("Error: {}", err), + } + }); }); render! { - div { video { src: "test_video.mp4", autoplay: true, controls: true, width: 640, height: 480 } } + div { + video { + src: "/videos/test_video.mp4", + autoplay: true, + controls: true, + width: 640, + height: 480 + } + } } } +/// This was taken from wry's example async fn get_stream_response( asset: &mut (impl tokio::io::AsyncSeek + tokio::io::AsyncRead + Unpin + Send + Sync), request: &AssetRequest, diff --git a/packages/core/src/runtime.rs b/packages/core/src/runtime.rs index 6b5e5408a..f3735b020 100644 --- a/packages/core/src/runtime.rs +++ b/packages/core/src/runtime.rs @@ -87,6 +87,16 @@ impl Runtime { self.scope_stack.borrow().last().copied() } + /// Call this function with the current scope set to the given scope + /// + /// Useful in a limited number of scenarios, not public. + pub(crate) fn with_scope(&self, id: ScopeId, f: impl FnOnce() -> O) -> O { + self.scope_stack.borrow_mut().push(id); + let o = f(); + self.scope_stack.borrow_mut().pop(); + o + } + /// Get the context for any scope given its ID /// /// This is useful for inserting or removing contexts from a scope, or rendering out its root node @@ -137,6 +147,17 @@ impl RuntimeGuard { push_runtime(runtime.clone()); Self(runtime) } + + /// Run a function with a given runtime and scope in context + pub fn with(runtime: Rc, scope: Option, f: impl FnOnce() -> O) -> O { + let guard = Self::new(runtime.clone()); + let o = match scope { + Some(scope) => Runtime::with_scope(&runtime, scope, f), + None => f(), + }; + drop(guard); + o + } } impl Drop for RuntimeGuard { diff --git a/packages/core/src/scope_context.rs b/packages/core/src/scope_context.rs index e4cb256d2..ffb9e74a1 100644 --- a/packages/core/src/scope_context.rs +++ b/packages/core/src/scope_context.rs @@ -318,11 +318,16 @@ pub fn spawn(fut: impl Future + 'static) { with_current_scope(|cx| cx.spawn(fut)); } +/// Spawn a future on a component given its [`ScopeId`]. +pub fn spawn_at(fut: impl Future + 'static, scope_id: ScopeId) -> Option { + with_runtime(|rt| rt.get_context(scope_id).unwrap().push_future(fut)) +} + /// Spawn a future that Dioxus won't clean up when this component is unmounted /// /// This is good for tasks that need to be run after the component has been dropped. pub fn spawn_forever(fut: impl Future + 'static) -> Option { - with_current_scope(|cx| cx.spawn_forever(fut)) + spawn_at(fut, ScopeId(0)) } /// Informs the scheduler that this task is no longer needed and should be removed. diff --git a/packages/desktop/src/app.rs b/packages/desktop/src/app.rs index 3471d6227..9a3bc7906 100644 --- a/packages/desktop/src/app.rs +++ b/packages/desktop/src/app.rs @@ -320,6 +320,7 @@ impl App

{ let Some(view) = self.webviews.get_mut(&id) else { return; }; + println!("poll_vdom"); view.poll_vdom(); } diff --git a/packages/desktop/src/assets.rs b/packages/desktop/src/assets.rs index 7f86b6184..f0f5a03fc 100644 --- a/packages/desktop/src/assets.rs +++ b/packages/desktop/src/assets.rs @@ -1,135 +1,61 @@ -use crate::DesktopContext; -use slab::Slab; -use std::{ - borrow::Cow, - future::Future, - ops::Deref, - path::{Path, PathBuf}, - pin::Pin, - rc::Rc, - sync::Arc, -}; -use tokio::{ - runtime::Handle, - sync::{OnceCell, RwLock}, -}; -use wry::http::{Request, Response}; +use dioxus_core::prelude::{Runtime, RuntimeGuard, ScopeId}; +use rustc_hash::FxHashMap; +use std::{cell::RefCell, rc::Rc}; +use wry::{http::Request, RequestAsyncResponder}; -/// An arbitrary asset is an HTTP response containing a binary body. -pub type AssetResponse = Response>; +/// +pub type AssetRequest = Request>; -/// A future that returns an [`AssetResponse`]. This future may be spawned in a new thread, -/// so it must be [`Send`], [`Sync`], and `'static`. -pub trait AssetFuture: Future> + Send + Sync + 'static {} -impl> + Send + Sync + 'static> AssetFuture for T {} - -#[derive(Debug, Clone)] -/// A request for an asset. This is a wrapper around [`Request>`] that provides methods specific to asset requests. -pub struct AssetRequest { - pub(crate) path: PathBuf, - pub(crate) request: Arc>>, +pub struct AssetHandler { + f: Box, + scope: ScopeId, } -impl AssetRequest { - /// Get the path the asset request is for - pub fn path(&self) -> &Path { - &self.path - } -} - -impl From>> for AssetRequest { - fn from(request: Request>) -> Self { - let decoded = urlencoding::decode(request.uri().path().trim_start_matches('/')) - .expect("expected URL to be UTF-8 encoded"); - let path = PathBuf::from(&*decoded); - Self { - request: Arc::new(request), - path, - } - } -} - -impl Deref for AssetRequest { - type Target = Request>; - - fn deref(&self) -> &Self::Target { - &self.request - } -} - -/// A handler that takes an [`AssetRequest`] and returns a future that either loads the asset, or returns `None`. -/// This handler is stashed indefinitely in a context object, so it must be `'static`. -pub trait AssetHandler: Send + Sync + 'static { - /// Handle an asset request, returning a future that either loads the asset, or returns `None` - fn handle_request(&self, request: &AssetRequest) -> F; -} - -impl F + Send + Sync + 'static> AssetHandler for T { - fn handle_request(&self, request: &AssetRequest) -> F { - self(request) - } -} - -type UserAssetHandler = - Box Pin> + Send + Sync + 'static>; - -type AssetHandlerRegistryInner = Slab; - #[derive(Clone)] -pub struct AssetHandlerRegistry(Arc>); +pub struct AssetHandlerRegistry { + dom_rt: Rc, + handlers: Rc>>, +} impl AssetHandlerRegistry { - pub fn new() -> Self { - AssetHandlerRegistry(Arc::new(RwLock::new(Slab::new()))) - } - - pub async fn register_handler(&self, f: impl AssetHandler) -> usize { - let mut registry = self.0.write().await; - registry.insert(Box::new(move |req| Box::pin(f.handle_request(req)))) - } - - pub async fn remove_handler(&self, id: usize) -> Option<()> { - let mut registry = self.0.write().await; - registry.try_remove(id).map(|_| ()) - } - - pub async fn try_handlers(&self, req: &AssetRequest) -> Option { - let registry = self.0.read().await; - for (_, handler) in registry.iter() { - if let Some(response) = handler(req).await { - return Some(response); - } + pub fn new(dom_rt: Rc) -> Self { + AssetHandlerRegistry { + dom_rt, + handlers: Default::default(), } - None - } -} - -/// A handle to a registered asset handler. -pub struct AssetHandlerHandle { - pub(crate) desktop: DesktopContext, - pub(crate) handler_id: Rc>, -} - -impl AssetHandlerHandle { - /// Returns the ID for this handle. - /// - /// Because registering an ID is asynchronous, this may return `None` if the - /// registration has not completed yet. - pub fn handler_id(&self) -> Option { - self.handler_id.get().copied() - } -} - -impl Drop for AssetHandlerHandle { - fn drop(&mut self) { - let cell = Rc::clone(&self.handler_id); - let desktop = Rc::clone(&self.desktop); - tokio::task::block_in_place(move || { - Handle::current().block_on(async move { - if let Some(id) = cell.get() { - desktop.asset_handlers.remove_handler(*id).await; - } - }) - }); + } + + pub fn has_handler(&self, name: &str) -> bool { + self.handlers.borrow().contains_key(name) + } + + pub fn handle_request( + &self, + name: &str, + request: AssetRequest, + responder: RequestAsyncResponder, + ) { + if let Some(handler) = self.handlers.borrow().get(name) { + // make sure the runtime is alive for the duration of the handler + // We should do this for all the things - not just asset handlers + RuntimeGuard::with(self.dom_rt.clone(), Some(handler.scope), || { + (handler.f)(request, responder) + }); + } + } + + pub fn register_handler( + &self, + name: String, + f: Box, + scope: ScopeId, + ) { + self.handlers + .borrow_mut() + .insert(name, AssetHandler { f, scope }); + } + + pub fn remove_handler(&self, name: &str) -> Option { + self.handlers.borrow_mut().remove(name) } } diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index e81b06656..46460b12e 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -1,14 +1,17 @@ use crate::{ app::SharedContext, - assets::{AssetFuture, AssetHandlerRegistry}, + assets::AssetHandlerRegistry, edits::EditQueue, ipc::{EventData, UserWindowEvent}, query::QueryEngine, shortcut::{HotKey, ShortcutId, ShortcutRegistryError}, webview::WebviewInstance, - AssetHandler, Config, + AssetRequest, Config, +}; +use dioxus_core::{ + prelude::{current_scope_id, ScopeId}, + Mutations, VirtualDom, }; -use dioxus_core::{Mutations, VirtualDom}; use dioxus_interpreter_js::binary_protocol::Channel; use rustc_hash::FxHashMap; use slab::Slab; @@ -18,7 +21,7 @@ use tao::{ event_loop::EventLoopWindowTarget, window::{Fullscreen as WryFullscreen, Window, WindowId}, }; -use wry::WebView; +use wry::{RequestAsyncResponder, WebView}; #[cfg(target_os = "ios")] use tao::platform::ios::WindowExtIOS; @@ -244,17 +247,30 @@ impl DesktopService { } /// Provide a callback to handle asset loading yourself. + /// If the ScopeId isn't provided, defaults to a global handler. + /// Note that the handler is namespaced by name, not ScopeId. + /// + /// When the component is dropped, the handler is removed. /// /// See [`use_asset_handle`](crate::use_asset_handle) for a convenient hook. - pub async fn register_asset_handler(&self, f: impl AssetHandler) -> usize { - self.asset_handlers.register_handler(f).await + pub fn register_asset_handler( + &self, + name: String, + f: Box, + scope: Option, + ) { + self.asset_handlers.register_handler( + name, + f, + scope.unwrap_or(current_scope_id().unwrap_or(ScopeId(0))), + ) } /// Removes an asset handler by its identifier. /// /// Returns `None` if the handler did not exist. - pub async fn remove_asset_handler(&self, id: usize) -> Option<()> { - self.asset_handlers.remove_handler(id).await + pub fn remove_asset_handler(&self, name: &str) -> Option<()> { + self.asset_handlers.remove_handler(name).map(|_| ()) } /// Push an objc view to the window diff --git a/packages/desktop/src/hooks.rs b/packages/desktop/src/hooks.rs index 24662872e..85d1a170b 100644 --- a/packages/desktop/src/hooks.rs +++ b/packages/desktop/src/hooks.rs @@ -3,8 +3,8 @@ use crate::{ ShortcutHandle, ShortcutRegistryError, WryEventHandler, }; use dioxus_core::ScopeState; -use std::rc::Rc; use tao::{event::Event, event_loop::EventLoopWindowTarget}; +use wry::RequestAsyncResponder; /// Get an imperative handle to the current window pub fn use_window(cx: &ScopeState) -> &DesktopContext { @@ -34,24 +34,28 @@ pub fn use_wry_event_handler( /// /// The callback takes a path as requested by the web view, and it should return `Some(response)` /// if you want to load the asset, and `None` if you want to fallback on the default behavior. -pub fn use_asset_handler( +pub fn use_asset_handler( cx: &ScopeState, - handler: impl AssetHandler, -) -> &AssetHandlerHandle { + name: &str, + handler: impl Fn(AssetRequest, RequestAsyncResponder) + 'static, +) { cx.use_hook(|| { - let desktop = crate::window(); - let handler_id = Rc::new(tokio::sync::OnceCell::new()); - let handler_id_ref = Rc::clone(&handler_id); - let desktop_ref = Rc::clone(&desktop); - cx.push_future(async move { - let id = desktop.asset_handlers.register_handler(handler).await; - handler_id.set(id).unwrap(); - }); - AssetHandlerHandle { - desktop: desktop_ref, - handler_id: handler_id_ref, + crate::window().asset_handlers.register_handler( + name.to_string(), + Box::new(handler), + cx.scope_id(), + ); + + Handler(name.to_string()) + }); + + // todo: can we just put ondrop in core? + struct Handler(String); + impl Drop for Handler { + fn drop(&mut self) { + _ = crate::window().asset_handlers.remove_handler(&self.0); } - }) + } } /// Get a closure that executes any JavaScript in the WebView context. diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 2b5d5c950..86f587862 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -38,7 +38,7 @@ pub use tao::window::WindowBuilder; pub use wry; // Public exports -pub use assets::{AssetFuture, AssetHandler, AssetRequest, AssetResponse}; +pub use assets::AssetRequest; pub use cfg::{Config, WindowCloseBehaviour}; pub use desktop_context::{ window, DesktopContext, DesktopService, WryEventHandler, WryEventHandlerId, @@ -46,3 +46,4 @@ pub use desktop_context::{ pub use hooks::{use_asset_handler, use_global_shortcut, use_window, use_wry_event_handler}; pub use menubar::build_default_menu_bar; pub use shortcut::{ShortcutHandle, ShortcutId, ShortcutRegistryError}; +pub use wry::RequestAsyncResponder; diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index bd9d1f7ac..b06bdbcb3 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -1,8 +1,5 @@ use crate::{assets::*, edits::EditQueue}; -use std::{ - borrow::Cow, - path::{Path, PathBuf}, -}; +use std::path::{Path, PathBuf}; use wry::{ http::{status::StatusCode, Request, Response}, RequestAsyncResponder, Result, @@ -11,69 +8,6 @@ use wry::{ static MINIFIED: &str = include_str!("./minified.js"); static DEFAULT_INDEX: &str = include_str!("./index.html"); -// todo: clean this up a bit -#[allow(clippy::too_many_arguments)] -pub(super) async fn desktop_handler( - request: Request>, - custom_head: Option, - custom_index: Option, - root_name: &str, - asset_handlers: &AssetHandlerRegistry, - edit_queue: &EditQueue, - headless: bool, - responder: RequestAsyncResponder, -) { - let request = AssetRequest::from(request); - - // If the request is for the root, we'll serve the index.html file. - if request.uri().path() == "/" { - match build_index_file(custom_index, custom_head, root_name, headless) { - Ok(response) => return responder.respond(response), - Err(err) => return tracing::error!("error building response: {}", err), - } - } - - // If the request is asking for edits (ie binary protocol streaming, do that) - if request.uri().path().trim_matches('/') == "edits" { - return edit_queue.handle_request(responder); - } - - // If the user provided a custom asset handler, then call it and return the response if the request was handled. - // todo(jon): I dont want this function to be async - we can probably just use a drop handler on the responder - if let Some(response) = asset_handlers.try_handlers(&request).await { - return responder.respond(response); - } - - // Else, try to serve a file from the filesystem. - match serve_from_fs(request) { - Ok(res) => responder.respond(res), - Err(e) => tracing::error!("Error serving request from filesystem {}", e), - } -} - -fn serve_from_fs(request: AssetRequest) -> Result { - // If the path is relative, we'll try to serve it from the assets directory. - let mut asset = get_asset_root_or_default().join(&request.path); - - // If we can't find it, make it absolute and try again - if !asset.exists() { - asset = PathBuf::from("/").join(request.path); - } - - if asset.exists() { - let content_type = get_mime_from_path(&asset)?; - let asset = std::fs::read(asset)?; - - Ok(Response::builder() - .header("Content-Type", content_type) - .body(Cow::from(asset))?) - } else { - Ok(Response::builder() - .status(StatusCode::NOT_FOUND) - .body(Cow::from(String::from("Not Found").into_bytes()))?) - } -} - /// Build the index.html file we use for bootstrapping a new app /// /// We use wry/webview by building a special index.html that forms a bridge between the webview and your rust code @@ -83,12 +17,18 @@ fn serve_from_fs(request: AssetRequest) -> Result { /// mess with UI elements. We make this decision since other renderers like LiveView are very separate and can /// never properly bridge the gap. Eventually of course, the idea is to build a custom CSS/HTML renderer where you /// *do* have native control over elements, but that still won't work with liveview. -fn build_index_file( - custom_index: Option, +pub(super) fn index_request( + request: &Request>, custom_head: Option, + custom_index: Option, root_name: &str, headless: bool, -) -> std::result::Result>, wry::http::Error> { +) -> Option>> { + // If the request is for the root, we'll serve the index.html file. + if request.uri().path() != "/" { + return None; + } + // Load a custom index file if provided let mut index = custom_index.unwrap_or_else(|| DEFAULT_INDEX.to_string()); @@ -110,6 +50,68 @@ fn build_index_file( .header("Content-Type", "text/html") .header("Access-Control-Allow-Origin", "*") .body(index.into()) + .ok() +} + +/// Handle a request from the webview +/// +/// - Tries to stream edits if they're requested. +/// - If that doesn't match, tries a user provided asset handler +/// - If that doesn't match, tries to serve a file from the filesystem +pub(super) fn desktop_handler( + request: Request>, + asset_handlers: AssetHandlerRegistry, + edit_queue: &EditQueue, + responder: RequestAsyncResponder, +) { + // If the request is asking for edits (ie binary protocol streaming, do that) + if request.uri().path().trim_matches('/') == "edits" { + return edit_queue.handle_request(responder); + } + + // If the user provided a custom asset handler, then call it and return the response if the request was handled. + // The path is the first part of the URI, so we need to trim the leading slash. + let path = PathBuf::from( + urlencoding::decode(request.uri().path().trim_start_matches('/')) + .expect("expected URL to be UTF-8 encoded") + .as_ref(), + ); + + let Some(name) = path.parent() else { + return tracing::error!("Asset request has no root {path:?}"); + }; + + if let Some(name) = name.to_str() { + if asset_handlers.has_handler(name) { + return asset_handlers.handle_request(name, request, responder); + } + } + + // Else, try to serve a file from the filesystem. + match serve_from_fs(path) { + Ok(res) => responder.respond(res), + Err(e) => tracing::error!("Error serving request from filesystem {}", e), + } +} + +fn serve_from_fs(path: PathBuf) -> Result>> { + // If the path is relative, we'll try to serve it from the assets directory. + let mut asset = get_asset_root_or_default().join(&path); + + // If we can't find it, make it absolute and try again + if !asset.exists() { + asset = PathBuf::from("/").join(path); + } + + if !asset.exists() { + return Ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(String::from("Not Found").into_bytes())?); + } + + Ok(Response::builder() + .header("Content-Type", get_mime_from_path(&asset)?) + .body(std::fs::read(asset)?)?) } /// Construct the inline script that boots up the page and bridges the webview with rust code. diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index b906211e8..c9f0720e9 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -11,7 +11,7 @@ use crate::{ use dioxus_core::VirtualDom; use futures_util::{pin_mut, FutureExt}; use std::{rc::Rc, task::Waker}; -use wry::{WebContext, WebViewBuilder}; +use wry::{RequestAsyncResponder, WebContext, WebViewBuilder}; pub struct WebviewInstance { pub dom: VirtualDom, @@ -33,12 +33,6 @@ impl WebviewInstance { build_menu_bar(build_default_menu_bar(), &window); } - let window_id = window.id(); - let file_handler = cfg.file_drop_handler.take(); - let custom_head = cfg.custom_head.clone(); - let index_file = cfg.custom_index.clone(); - let root_name = cfg.root_name.clone(); - // We assume that if the icon is None in cfg, then the user just didnt set it if cfg.window.window.window_icon.is_none() { window.set_window_icon(Some( @@ -53,52 +47,65 @@ impl WebviewInstance { let mut web_context = WebContext::new(cfg.data_dir.clone()); let edit_queue = EditQueue::default(); + let asset_handlers = AssetHandlerRegistry::new(dom.runtime()); let headless = !cfg.window.window.visible; - let asset_handlers = AssetHandlerRegistry::new(); - let asset_handlers_ref = asset_handlers.clone(); + + // Rust :( + let window_id = window.id(); + let file_handler = cfg.file_drop_handler.take(); + let custom_head = cfg.custom_head.clone(); + let index_file = cfg.custom_index.clone(); + let root_name = cfg.root_name.clone(); + let asset_handlers_ = asset_handlers.clone(); + let edit_queue_ = edit_queue.clone(); + let proxy_ = shared.proxy.clone(); + + let request_handler = move |request, responder: RequestAsyncResponder| { + // Try to serve the index file first + let index_bytes = protocol::index_request( + &request, + custom_head.clone(), + index_file.clone(), + &root_name, + headless, + ); + + // Otherwise, try to serve an asset, either from the user or the filesystem + match index_bytes { + Some(body) => return responder.respond(body), + None => { + // we need to do this in the context of the dioxus runtime since the user gave us these closures + protocol::desktop_handler( + request, + asset_handlers_.clone(), + &edit_queue_, + responder, + ); + } + } + }; + + let ipc_handler = move |payload: String| { + // defer the event to the main thread + if let Ok(message) = serde_json::from_str(&payload) { + _ = proxy_.send_event(UserWindowEvent(EventData::Ipc(message), window_id)); + } + }; + + let file_drop_handler = move |event| { + file_handler + .as_ref() + .map(|handler| handler(window_id, event)) + .unwrap_or_default() + }; let mut webview = WebViewBuilder::new(&window) .with_transparent(cfg.window.window.transparent) .with_url("dioxus://index.html/") .unwrap() - .with_ipc_handler({ - let proxy = shared.proxy.clone(); - move |payload: String| { - // defer the event to the main thread - if let Ok(message) = serde_json::from_str(&payload) { - _ = proxy.send_event(UserWindowEvent(EventData::Ipc(message), window_id)); - } - } - }) - .with_asynchronous_custom_protocol(String::from("dioxus"), { - let edit_queue = edit_queue.clone(); - move |request, responder| { - let custom_head = custom_head.clone(); - let index_file = index_file.clone(); - let root_name = root_name.clone(); - let asset_handlers_ref = asset_handlers_ref.clone(); - let edit_queue = edit_queue.clone(); - tokio::spawn(async move { - protocol::desktop_handler( - request, - custom_head.clone(), - index_file.clone(), - &root_name, - &asset_handlers_ref, - &edit_queue, - headless, - responder, - ) - .await; - }); - } - }) - .with_file_drop_handler(move |event| { - file_handler - .as_ref() - .map(|handler| handler(window_id, event)) - .unwrap_or_default() - }) + .with_ipc_handler(ipc_handler) + .with_asynchronous_custom_protocol(String::from("dioxus"), request_handler) + .with_file_drop_handler(file_drop_handler) .with_web_context(&mut web_context); #[cfg(windows)]