فهرست منبع

Generalize Query system for use in use_eval and node querys

Evan Almloff 2 سال پیش
والد
کامیت
fa9f0d0f6c

+ 5 - 28
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<Value>,
-
-    /// 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}
-                        }}
-                    )()
-                }})
-            );
-            "#
-        );
-
-        if let Err(e) = self.webview.evaluate_script(&script) {
-            // send an error to the eval receiver
-            log::warn!("Eval script error: {e}");
-        }
+        // 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);
 
-        EvalResult::new(self.eval.clone())
+        EvalResult::new(query)
     }
 
     /// Create a wry event handler that listens for wry events.

+ 41 - 124
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<WebView>, 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<WebView> {
-        &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::<Option<Rect<f64, f64>>>(&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<Box<dyn futures_util::Future<Output = dioxus_html::MountedResult<()>>>> {
+        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::<bool>(&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<Box<dyn futures_util::Future<Output = dioxus_html::MountedResult<()>>>> {
+        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::<bool>(&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,
-                ))),
-            }
-        })
-    }
-}
-
-#[derive(Default, Clone)]
-struct SharedSlab {
-    slab: Rc<RefCell<Slab<()>>>,
-}
-
-#[derive(Clone)]
-pub(crate) struct QueryEngine {
-    sender: Rc<tokio::sync::broadcast::Sender<MountedReturn>>,
-    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<MountedReturn>,
-}
-
-impl Query {
-    async fn resolve(mut self) -> Option<MountedReturnData> {
-        let result = loop {
-            match self.reciever.recv().await {
-                Ok(result) => {
-                    if result.id == self.id {
-                        break result.data;
-                    }
-                }
-                Err(_) => {
-                    break None;
+                Err(err) => {
+                    MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new(err)))
                 }
             }
-        };
-
-        // 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")
-            }
         }
     }
 }

+ 9 - 13
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<serde_json::Value>,
+    pub(crate) query: Query<serde_json::Value>,
 }
 
 impl EvalResult {
-    pub(crate) fn new(sender: tokio::sync::broadcast::Sender<serde_json::Value>) -> Self {
-        Self { broadcast: sender }
+    pub(crate) fn new(query: Query<serde_json::Value>) -> Self {
+        Self { query }
     }
 }
 
 impl IntoFuture for EvalResult {
-    type Output = Result<serde_json::Value, serde_json::Error>;
+    type Output = Result<serde_json::Value, QueryError>;
 
-    type IntoFuture = Pin<Box<dyn Future<Output = Result<serde_json::Value, serde_json::Error>>>>;
+    type IntoFuture = Pin<Box<dyn Future<Output = Result<serde_json::Value, QueryError>>>>;
 
     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<dyn Future<Output = Result<serde_json::Value, serde_json::Error>>>>
+        Box::pin(self.query.resolve())
+            as Pin<Box<dyn Future<Output = Result<serde_json::Value, QueryError>>>>
     }
 }
 

+ 15 - 37
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<P: 'static>(root: Component<P>, 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::<MountedReturn>(params) {
-                        Ok(value) => value,
-                        Err(err) => {
-                            println!("node_update: {:?}", err);
-                            return;
-                        }
-                    };
-
-                    let view = webviews.get(&event.1).unwrap();
-                    let query = view
-                        .dom
-                        .base_scope()
-                        .consume_context::<DesktopContext>()
-                        .unwrap()
-                        .query;
-
-                    println!("node_update: {:?}", evt);
+                    if let Ok(result) = dbg!(serde_json::from_value::<QueryResult>(params)) {
+                        let view = webviews.get(&event.1).unwrap();
+                        let query = view
+                            .dom
+                            .base_scope()
+                            .consume_context::<DesktopContext>()
+                            .unwrap()
+                            .query;
 
-                    query.send(evt);
+                        query.send(result);
+                    }
                 }
 
                 EventData::Ipc(msg) if msg.method() == "initialize" => {
@@ -290,21 +283,6 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, 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::<DesktopContext>()
-                        .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") {

+ 105 - 0
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<RefCell<Slab<()>>>,
+}
+
+#[derive(Clone)]
+pub(crate) struct QueryEngine {
+    sender: Rc<tokio::sync::broadcast::Sender<QueryResult>>,
+    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<V: DeserializeOwned>(&self, script: &str, webview: &WebView) -> Query<V> {
+        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<V: DeserializeOwned> {
+    slab: SharedSlab,
+    id: usize,
+    reciever: tokio::sync::broadcast::Receiver<QueryResult>,
+    phantom: std::marker::PhantomData<V>,
+}
+
+impl<V: DeserializeOwned> Query<V> {
+    pub async fn resolve(mut self) -> Result<V, QueryError> {
+        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,
+}

+ 7 - 4
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<Box<dyn Future<Output = MountedResult<()>>>> {
+        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<Box<dyn Future<Output = MountedResult<()>>>> {
+        self.inner.set_focus(focus)
     }
 }
 

+ 0 - 47
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<MountedReturnData>,
-}
-
-/// 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<f64, f64>),
-    ScrollTo(()),
-}

+ 4 - 29
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) {