diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index ccb8c7588..6af6401f3 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -3,9 +3,9 @@ use std::rc::Rc; use std::rc::Weak; use crate::create_new_window; -use crate::element::QueryEngine; use crate::eval::EvalResult; use crate::events::IpcMessage; +use crate::query::QueryEngine; use crate::shortcut::IntoKeyCode; use crate::shortcut::IntoModifersState; use crate::shortcut::ShortcutId; @@ -17,7 +17,6 @@ use dioxus_core::ScopeState; use dioxus_core::VirtualDom; #[cfg(all(feature = "hot-reload", debug_assertions))] use dioxus_hot_reload::HotReloadMsg; -use serde_json::Value; use slab::Slab; use wry::application::event::Event; use wry::application::event_loop::EventLoopProxy; @@ -60,10 +59,7 @@ pub struct DesktopContext { /// The proxy to the event loop pub proxy: ProxyType, - /// The receiver for eval results since eval is async - pub(super) eval: tokio::sync::broadcast::Sender, - - /// The receiver for queries about elements + /// The receiver for queries about the current window pub(super) query: QueryEngine, pub(super) pending_windows: WebviewQueue, @@ -100,7 +96,6 @@ impl DesktopContext { webview, proxy, event_loop, - eval: tokio::sync::broadcast::channel(8).0, query: Default::default(), pending_windows: webviews, event_handlers, @@ -215,28 +210,10 @@ impl DesktopContext { /// Evaluate a javascript expression pub fn eval(&self, code: &str) -> EvalResult { - // Embed the return of the eval in a function so we can send it back to the main thread - let script = format!( - r#" - window.ipc.postMessage( - JSON.stringify({{ - "method":"eval_result", - "params": ( - function(){{ - {code} - }} - )() - }}) - ); - "# - ); + // the query id lets us keep track of the eval result and send it back to the main thread + let query = self.query.new_query(code, &self.webview); - if let Err(e) = self.webview.evaluate_script(&script) { - // send an error to the eval receiver - log::warn!("Eval script error: {e}"); - } - - EvalResult::new(self.eval.clone()) + EvalResult::new(query) } /// Create a wry event handler that listens for wry events. diff --git a/packages/desktop/src/element.rs b/packages/desktop/src/element.rs index 60a995f1b..e94a481c7 100644 --- a/packages/desktop/src/element.rs +++ b/packages/desktop/src/element.rs @@ -1,13 +1,11 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Rc; use dioxus_core::ElementId; -use dioxus_html::{ - MountedResult, MountedReturn, MountedReturnData, NodeUpdate, NodeUpdateData, - RenderedElementBacking, -}; -use slab::Slab; +use dioxus_html::{geometry::euclid::Rect, MountedResult, RenderedElementBacking}; use wry::webview::WebView; +use crate::query::QueryEngine; + /// A mounted element passed to onmounted events pub struct DesktopElement { id: ElementId, @@ -19,16 +17,6 @@ impl DesktopElement { pub(crate) fn new(id: ElementId, webview: Rc, query: QueryEngine) -> Self { Self { id, webview, query } } - - /// Get the id of the element - pub fn id(&self) -> ElementId { - self.id - } - - /// Get the webview the element is mounted in - pub fn webview(&self) -> &Rc { - &self.webview - } } impl RenderedElementBacking for DesktopElement { @@ -45,19 +33,21 @@ impl RenderedElementBacking for DesktopElement { >, >, > { + let script = format!("return window.interpreter.GetClientRect({});", self.id.0); + let fut = self .query - .new_query(self.id, NodeUpdateData::GetClientRect {}, &self.webview) + .new_query::>>(&script, &self.webview) .resolve(); Box::pin(async move { match fut.await { - Some(MountedReturnData::GetClientRect(rect)) => Ok(rect), - Some(_) => MountedResult::Err(dioxus_html::MountedError::OperationFailed( - Box::new(DesktopQueryError::MismatchedReturn), + Ok(Some(rect)) => Ok(rect), + Ok(None) => MountedResult::Err(dioxus_html::MountedError::OperationFailed( + Box::new(DesktopQueryError::FailedToQuery), )), - None => MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new( - DesktopQueryError::FailedToQuery, - ))), + Err(err) => { + MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new(err))) + } } }) } @@ -66,23 +56,25 @@ impl RenderedElementBacking for DesktopElement { &self, behavior: dioxus_html::ScrollBehavior, ) -> std::pin::Pin>>> { + let script = format!( + "return window.interpreter.ScrollTo({}, {});", + self.id.0, + serde_json::to_string(&behavior).expect("Failed to serialize ScrollBehavior") + ); + let fut = self .query - .new_query( - self.id, - NodeUpdateData::ScrollTo { behavior }, - &self.webview, - ) + .new_query::(&script, &self.webview) .resolve(); Box::pin(async move { match fut.await { - Some(MountedReturnData::ScrollTo(())) => Ok(()), - Some(_) => MountedResult::Err(dioxus_html::MountedError::OperationFailed( - Box::new(DesktopQueryError::MismatchedReturn), + Ok(true) => Ok(()), + Ok(false) => MountedResult::Err(dioxus_html::MountedError::OperationFailed( + Box::new(DesktopQueryError::FailedToQuery), )), - None => MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new( - DesktopQueryError::FailedToQuery, - ))), + Err(err) => { + MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new(err))) + } } }) } @@ -91,116 +83,41 @@ impl RenderedElementBacking for DesktopElement { &self, focus: bool, ) -> std::pin::Pin>>> { + let script = format!( + "return window.interpreter.SetFocus({}, {});", + self.id.0, focus + ); + + println!("script: {}", script); let fut = self .query - .new_query(self.id, NodeUpdateData::SetFocus { focus }, &self.webview) + .new_query::(&script, &self.webview) .resolve(); + println!("fut"); + Box::pin(async move { match fut.await { - Some(MountedReturnData::SetFocus(())) => Ok(()), - Some(_) => MountedResult::Err(dioxus_html::MountedError::OperationFailed( - Box::new(DesktopQueryError::MismatchedReturn), + Ok(true) => Ok(()), + Ok(false) => MountedResult::Err(dioxus_html::MountedError::OperationFailed( + Box::new(DesktopQueryError::FailedToQuery), )), - None => MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new( - DesktopQueryError::FailedToQuery, - ))), + Err(err) => { + MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new(err))) + } } }) } } -#[derive(Default, Clone)] -struct SharedSlab { - slab: Rc>>, -} - -#[derive(Clone)] -pub(crate) struct QueryEngine { - sender: Rc>, - active_requests: SharedSlab, -} - -impl Default for QueryEngine { - fn default() -> Self { - let (sender, _) = tokio::sync::broadcast::channel(8); - Self { - sender: Rc::new(sender), - active_requests: SharedSlab::default(), - } - } -} - -impl QueryEngine { - fn new_query(&self, id: ElementId, update: NodeUpdateData, webview: &WebView) -> Query { - let request_id = self.active_requests.slab.borrow_mut().insert(()); - - let update = NodeUpdate { - id: id.0 as u32, - request_id, - data: update, - }; - - // start the query - webview - .evaluate_script(&format!( - "window.interpreter.handleNodeUpdate({})", - serde_json::to_string(&update).unwrap() - )) - .unwrap(); - - Query { - slab: self.active_requests.clone(), - id: request_id, - reciever: self.sender.subscribe(), - } - } - - pub fn send(&self, data: MountedReturn) { - self.sender.send(data).unwrap(); - } -} - -struct Query { - slab: SharedSlab, - id: usize, - reciever: tokio::sync::broadcast::Receiver, -} - -impl Query { - async fn resolve(mut self) -> Option { - let result = loop { - match self.reciever.recv().await { - Ok(result) => { - if result.id == self.id { - break result.data; - } - } - Err(_) => { - break None; - } - } - }; - - // Remove the query from the slab - self.slab.slab.borrow_mut().remove(self.id); - - result - } -} - #[derive(Debug)] enum DesktopQueryError { FailedToQuery, - MismatchedReturn, } impl std::fmt::Display for DesktopQueryError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { DesktopQueryError::FailedToQuery => write!(f, "Failed to query the element"), - DesktopQueryError::MismatchedReturn => { - write!(f, "The return type did not match the query") - } } } } diff --git a/packages/desktop/src/eval.rs b/packages/desktop/src/eval.rs index 7a92bedf5..1e11cad18 100644 --- a/packages/desktop/src/eval.rs +++ b/packages/desktop/src/eval.rs @@ -1,36 +1,32 @@ use std::rc::Rc; +use crate::query::Query; +use crate::query::QueryError; use crate::use_window; use dioxus_core::ScopeState; -use serde::de::Error; use std::future::Future; use std::future::IntoFuture; use std::pin::Pin; /// A future that resolves to the result of a JavaScript evaluation. pub struct EvalResult { - pub(crate) broadcast: tokio::sync::broadcast::Sender, + pub(crate) query: Query, } impl EvalResult { - pub(crate) fn new(sender: tokio::sync::broadcast::Sender) -> Self { - Self { broadcast: sender } + pub(crate) fn new(query: Query) -> Self { + Self { query } } } impl IntoFuture for EvalResult { - type Output = Result; + type Output = Result; - type IntoFuture = Pin>>>; + type IntoFuture = Pin>>>; fn into_future(self) -> Self::IntoFuture { - Box::pin(async move { - let mut reciever = self.broadcast.subscribe(); - match reciever.recv().await { - Ok(result) => Ok(result), - Err(_) => Err(serde_json::Error::custom("No result returned")), - } - }) as Pin>>> + Box::pin(self.query.resolve()) + as Pin>>> } } diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 079331fdb..8e92d90aa 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -10,17 +10,19 @@ mod escape; mod eval; mod events; mod protocol; +mod query; mod shortcut; mod waker; mod webview; +use crate::query::QueryResult; pub use cfg::Config; pub use desktop_context::{ use_window, use_wry_event_handler, DesktopContext, WryEventHandler, WryEventHandlerId, }; use desktop_context::{EventData, UserWindowEvent, WebviewQueue, WindowEventHandlers}; use dioxus_core::*; -use dioxus_html::{HtmlEvent, MountedData, MountedReturn}; +use dioxus_html::{HtmlEvent, MountedData}; use element::DesktopElement; pub use eval::{use_eval, EvalResult}; use futures_util::{pin_mut, FutureExt}; @@ -259,30 +261,21 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) send_edits(view.dom.render_immediate(), &view.webview); } - EventData::Ipc(msg) if msg.method() == "node_update" => { + // When the webview sends a query, we need to send it to the query manager which handles dispatching the data to the correct pending query + EventData::Ipc(msg) if msg.method() == "query" => { let params = msg.params(); - println!("node_update: {:?}", params); - // check for a mounted event - let evt = match serde_json::from_value::(params) { - Ok(value) => value, - Err(err) => { - println!("node_update: {:?}", err); - return; - } - }; + if let Ok(result) = dbg!(serde_json::from_value::(params)) { + let view = webviews.get(&event.1).unwrap(); + let query = view + .dom + .base_scope() + .consume_context::() + .unwrap() + .query; - let view = webviews.get(&event.1).unwrap(); - let query = view - .dom - .base_scope() - .consume_context::() - .unwrap() - .query; - - println!("node_update: {:?}", evt); - - query.send(evt); + query.send(result); + } } EventData::Ipc(msg) if msg.method() == "initialize" => { @@ -290,21 +283,6 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) send_edits(view.dom.rebuild(), &view.webview); } - // When the webview chirps back with the result of the eval, we send it to the active receiver - // - // This currently doesn't perform any targeting to the callsite, so if you eval multiple times at once, - // you might the wrong result. This should be fixed - EventData::Ipc(msg) if msg.method() == "eval_result" => { - webviews[&event.1] - .dom - .base_scope() - .consume_context::() - .unwrap() - .eval - .send(msg.params()) - .unwrap(); - } - EventData::Ipc(msg) if msg.method() == "browser_open" => { if let Some(temp) = msg.params().as_object() { if temp.contains_key("href") { diff --git a/packages/desktop/src/query.rs b/packages/desktop/src/query.rs new file mode 100644 index 000000000..8b0cf3fc3 --- /dev/null +++ b/packages/desktop/src/query.rs @@ -0,0 +1,105 @@ +use std::{cell::RefCell, rc::Rc}; + +use serde::{de::DeserializeOwned, Deserialize}; +use serde_json::Value; +use slab::Slab; +use thiserror::Error; +use tokio::sync::broadcast::error::RecvError; +use wry::webview::WebView; + +#[derive(Default, Clone)] +struct SharedSlab { + slab: Rc>>, +} + +#[derive(Clone)] +pub(crate) struct QueryEngine { + sender: Rc>, + active_requests: SharedSlab, +} + +impl Default for QueryEngine { + fn default() -> Self { + let (sender, _) = tokio::sync::broadcast::channel(8); + Self { + sender: Rc::new(sender), + active_requests: SharedSlab::default(), + } + } +} + +impl QueryEngine { + pub fn new_query(&self, script: &str, webview: &WebView) -> Query { + let request_id = self.active_requests.slab.borrow_mut().insert(()); + + // start the query + // We embed the return of the eval in a function so we can send it back to the main thread + if let Err(err) = webview.evaluate_script(&format!( + r#"window.ipc.postMessage( + JSON.stringify({{ + "method":"query", + "params": {{ + "id": {request_id}, + "data": (function(){{{script}}})() + }} + }}) + );"# + )) { + log::warn!("Query error: {err}"); + } + + Query { + slab: self.active_requests.clone(), + id: request_id, + reciever: self.sender.subscribe(), + phantom: std::marker::PhantomData, + } + } + + pub fn send(&self, data: QueryResult) { + let _ = self.sender.send(data); + } +} + +pub(crate) struct Query { + slab: SharedSlab, + id: usize, + reciever: tokio::sync::broadcast::Receiver, + phantom: std::marker::PhantomData, +} + +impl Query { + pub async fn resolve(mut self) -> Result { + let result = loop { + match self.reciever.recv().await { + Ok(result) => { + if result.id == self.id { + break V::deserialize(result.data).map_err(QueryError::DeserializeError); + } + } + Err(err) => { + break Err(QueryError::RecvError(err)); + } + } + }; + + // Remove the query from the slab + self.slab.slab.borrow_mut().remove(self.id); + + result + } +} + +#[derive(Error, Debug)] +pub enum QueryError { + #[error("Error receiving query result: {0}")] + RecvError(RecvError), + #[error("Error deserializing query result: {0}")] + DeserializeError(serde_json::Error), +} + +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct QueryResult { + id: usize, + data: Value, +} diff --git a/packages/html/src/events/mounted.rs b/packages/html/src/events/mounted.rs index 548359b5c..293b51b9e 100644 --- a/packages/html/src/events/mounted.rs +++ b/packages/html/src/events/mounted.rs @@ -79,13 +79,16 @@ impl MountedData { } /// Scroll to make the element visible - pub async fn scroll_to(&self, behavior: ScrollBehavior) -> MountedResult<()> { - self.inner.scroll_to(behavior).await + pub fn scroll_to( + &self, + behavior: ScrollBehavior, + ) -> Pin>>> { + self.inner.scroll_to(behavior) } /// Set the focus on the element - pub async fn set_focus(&self, focus: bool) -> MountedResult<()> { - self.inner.set_focus(focus).await + pub fn set_focus(&self, focus: bool) -> Pin>>> { + self.inner.set_focus(focus) } } diff --git a/packages/html/src/transit.rs b/packages/html/src/transit.rs index 12c35bd28..ef0e7c70d 100644 --- a/packages/html/src/transit.rs +++ b/packages/html/src/transit.rs @@ -2,7 +2,6 @@ use std::{any::Any, rc::Rc}; use crate::events::*; use dioxus_core::ElementId; -use euclid::Rect; use serde::{Deserialize, Serialize}; #[derive(Serialize, Debug, Clone, PartialEq)] @@ -221,49 +220,3 @@ fn test_back_and_forth() { assert_eq!(data, p); } - -/// Message to update a node to support MountedData -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -pub struct NodeUpdate { - /// The id of the node to update - pub id: u32, - /// The id of the request - pub request_id: usize, - /// The data to update the node with - pub data: NodeUpdateData, -} - -/// Message to update a node to support MountedData -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - serde(tag = "type") -)] -pub enum NodeUpdateData { - SetFocus { focus: bool }, - GetClientRect {}, - ScrollTo { behavior: ScrollBehavior }, -} - -/// The result of a element query -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, PartialEq)] -pub struct MountedReturn { - /// A unique id for the query - pub id: usize, - /// The result of the query - pub data: Option, -} - -/// The data of a element query -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - serde(tag = "type") -)] -pub enum MountedReturnData { - SetFocus(()), - GetClientRect(Rect), - ScrollTo(()), -} diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index ce2a010c0..2c919dcde 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -227,51 +227,26 @@ class Interpreter { ScrollTo(id, behavior) { const node = this.nodes[id]; if (!node) { - return; + return false; } node.scrollIntoView({ behavior: behavior }); - return { - type: "ScrollTo", - }; + return true; } /// Set the focus on the element SetFocus(id, focus) { const node = this.nodes[id]; if (!node) { - return; + return false; } if (focus) { node.focus(); } else { node.blur(); } - return { - type: "SetFocus", - }; - } - - handleNodeUpdate(edit) { - let data; - switch (edit.data.type) { - case "SetFocus": - data = this.SetFocus(edit.id, edit.data.focus); - break; - case "ScrollTo": - data = this.ScrollTo(edit.id, edit.data.behavior); - break; - case "GetClientRect": - data = this.GetClientRect(edit.id); - break; - } - window.ipc.postMessage( - serializeIpcMessage("node_update", { - id: edit.request_id, - data: data - }) - ); + return true; } handleEdits(edits) {