|
@@ -7,45 +7,6 @@ use slab::Slab;
|
|
use std::{cell::RefCell, rc::Rc};
|
|
use std::{cell::RefCell, rc::Rc};
|
|
use thiserror::Error;
|
|
use thiserror::Error;
|
|
|
|
|
|
-/*
|
|
|
|
-todo:
|
|
|
|
-- write this in the interpreter itself rather than in blobs of inline javascript...
|
|
|
|
-- it could also be simpler, probably?
|
|
|
|
-*/
|
|
|
|
-const DIOXUS_CODE: &str = r#"
|
|
|
|
-let dioxus = {
|
|
|
|
- recv: function () {
|
|
|
|
- return new Promise((resolve, _reject) => {
|
|
|
|
- // Ever 50 ms check for new data
|
|
|
|
- let timeout = setTimeout(() => {
|
|
|
|
- let __msg = null;
|
|
|
|
- while (true) {
|
|
|
|
- let __data = _message_queue.shift();
|
|
|
|
- if (__data) {
|
|
|
|
- __msg = __data;
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- clearTimeout(timeout);
|
|
|
|
- resolve(__msg);
|
|
|
|
- }, 50);
|
|
|
|
- });
|
|
|
|
- },
|
|
|
|
-
|
|
|
|
- send: function (value) {
|
|
|
|
- window.ipc.postMessage(
|
|
|
|
- JSON.stringify({
|
|
|
|
- "method":"query",
|
|
|
|
- "params": {
|
|
|
|
- "id": _request_id,
|
|
|
|
- "data": value,
|
|
|
|
- "returned_value": false
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
-}"#;
|
|
|
|
-
|
|
|
|
/// Tracks what query ids are currently active
|
|
/// Tracks what query ids are currently active
|
|
pub(crate) struct SharedSlab<T = ()> {
|
|
pub(crate) struct SharedSlab<T = ()> {
|
|
pub slab: Rc<RefCell<Slab<T>>>,
|
|
pub slab: Rc<RefCell<Slab<T>>>,
|
|
@@ -69,12 +30,10 @@ impl<T> Default for SharedSlab<T> {
|
|
|
|
|
|
pub(crate) struct QueryEntry {
|
|
pub(crate) struct QueryEntry {
|
|
channel_sender: futures_channel::mpsc::UnboundedSender<Value>,
|
|
channel_sender: futures_channel::mpsc::UnboundedSender<Value>,
|
|
- return_sender: Option<futures_channel::oneshot::Sender<Value>>,
|
|
|
|
|
|
+ return_sender: Option<futures_channel::oneshot::Sender<Result<Value, String>>>,
|
|
pub owner: Option<Owner>,
|
|
pub owner: Option<Owner>,
|
|
}
|
|
}
|
|
|
|
|
|
-const QUEUE_NAME: &str = "__msg_queues";
|
|
|
|
-
|
|
|
|
/// Handles sending and receiving arbitrary queries from the webview. Queries can be resolved non-sequentially, so we use ids to track them.
|
|
/// Handles sending and receiving arbitrary queries from the webview. Queries can be resolved non-sequentially, so we use ids to track them.
|
|
#[derive(Clone, Default)]
|
|
#[derive(Clone, Default)]
|
|
pub(crate) struct QueryEngine {
|
|
pub(crate) struct QueryEngine {
|
|
@@ -100,40 +59,51 @@ impl QueryEngine {
|
|
// We embed the return of the eval in a function so we can send it back to the main thread
|
|
// We embed the return of the eval in a function so we can send it back to the main thread
|
|
if let Err(err) = context.webview.evaluate_script(&format!(
|
|
if let Err(err) = context.webview.evaluate_script(&format!(
|
|
r#"(function(){{
|
|
r#"(function(){{
|
|
- (async (resolve, _reject) => {{
|
|
|
|
- {DIOXUS_CODE}
|
|
|
|
- if (!window.{QUEUE_NAME}) {{
|
|
|
|
- window.{QUEUE_NAME} = [];
|
|
|
|
- }}
|
|
|
|
-
|
|
|
|
- let _request_id = {request_id};
|
|
|
|
-
|
|
|
|
- if (!window.{QUEUE_NAME}[{request_id}]) {{
|
|
|
|
- window.{QUEUE_NAME}[{request_id}] = [];
|
|
|
|
- }}
|
|
|
|
- let _message_queue = window.{QUEUE_NAME}[{request_id}];
|
|
|
|
-
|
|
|
|
- {script}
|
|
|
|
- }})().then((result)=>{{
|
|
|
|
|
|
+ let dioxus = window.createQuery({request_id});
|
|
|
|
+ let post_error = function(err) {{
|
|
let returned_value = {{
|
|
let returned_value = {{
|
|
- "method":"query",
|
|
|
|
|
|
+ "method": "query",
|
|
"params": {{
|
|
"params": {{
|
|
"id": {request_id},
|
|
"id": {request_id},
|
|
- "data": result,
|
|
|
|
- "returned_value": true
|
|
|
|
|
|
+ "data": {{
|
|
|
|
+ "data": err,
|
|
|
|
+ "method": "return_error"
|
|
|
|
+ }}
|
|
}}
|
|
}}
|
|
}};
|
|
}};
|
|
window.ipc.postMessage(
|
|
window.ipc.postMessage(
|
|
JSON.stringify(returned_value)
|
|
JSON.stringify(returned_value)
|
|
);
|
|
);
|
|
- }})
|
|
|
|
|
|
+ }};
|
|
|
|
+ try {{
|
|
|
|
+ const AsyncFunction = async function () {{}}.constructor;
|
|
|
|
+ let promise = (new AsyncFunction("dioxus", {script:?}))(dioxus);
|
|
|
|
+ promise
|
|
|
|
+ .then((result)=>{{
|
|
|
|
+ let returned_value = {{
|
|
|
|
+ "method": "query",
|
|
|
|
+ "params": {{
|
|
|
|
+ "id": {request_id},
|
|
|
|
+ "data": {{
|
|
|
|
+ "data": result,
|
|
|
|
+ "method": "return"
|
|
|
|
+ }}
|
|
|
|
+ }}
|
|
|
|
+ }};
|
|
|
|
+ window.ipc.postMessage(
|
|
|
|
+ JSON.stringify(returned_value)
|
|
|
|
+ );
|
|
|
|
+ }})
|
|
|
|
+ .catch(err => post_error(`Error running JS: ${{err}}`));
|
|
|
|
+ }} catch (error) {{
|
|
|
|
+ post_error(`Invalid JS: ${{error}}`);
|
|
|
|
+ }}
|
|
}})();"#
|
|
}})();"#
|
|
)) {
|
|
)) {
|
|
tracing::warn!("Query error: {err}");
|
|
tracing::warn!("Query error: {err}");
|
|
}
|
|
}
|
|
|
|
|
|
Query {
|
|
Query {
|
|
- slab: self.active_requests.clone(),
|
|
|
|
id: request_id,
|
|
id: request_id,
|
|
receiver: rx,
|
|
receiver: rx,
|
|
return_receiver: Some(return_rx),
|
|
return_receiver: Some(return_rx),
|
|
@@ -144,19 +114,26 @@ impl QueryEngine {
|
|
|
|
|
|
/// Send a query channel message to the correct query
|
|
/// Send a query channel message to the correct query
|
|
pub fn send(&self, data: QueryResult) {
|
|
pub fn send(&self, data: QueryResult) {
|
|
- let QueryResult {
|
|
|
|
- id,
|
|
|
|
- data,
|
|
|
|
- returned_value,
|
|
|
|
- } = data;
|
|
|
|
|
|
+ let QueryResult { id, data } = data;
|
|
let mut slab = self.active_requests.slab.borrow_mut();
|
|
let mut slab = self.active_requests.slab.borrow_mut();
|
|
if let Some(entry) = slab.get_mut(id) {
|
|
if let Some(entry) = slab.get_mut(id) {
|
|
- if returned_value {
|
|
|
|
- if let Some(sender) = entry.return_sender.take() {
|
|
|
|
- let _ = sender.send(data);
|
|
|
|
|
|
+ match data {
|
|
|
|
+ QueryResultData::Return { data } => {
|
|
|
|
+ if let Some(sender) = entry.return_sender.take() {
|
|
|
|
+ let _ = sender.send(Ok(data.unwrap_or_default()));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ QueryResultData::ReturnError { data } => {
|
|
|
|
+ if let Some(sender) = entry.return_sender.take() {
|
|
|
|
+ let _ = sender.send(Err(data.to_string()));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ QueryResultData::Drop => {
|
|
|
|
+ slab.remove(id);
|
|
|
|
+ }
|
|
|
|
+ QueryResultData::Send { data } => {
|
|
|
|
+ let _ = entry.channel_sender.unbounded_send(data);
|
|
}
|
|
}
|
|
- } else {
|
|
|
|
- let _ = entry.channel_sender.unbounded_send(data);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -164,9 +141,8 @@ impl QueryEngine {
|
|
|
|
|
|
pub(crate) struct Query<V: DeserializeOwned> {
|
|
pub(crate) struct Query<V: DeserializeOwned> {
|
|
desktop: DesktopContext,
|
|
desktop: DesktopContext,
|
|
- slab: SharedSlab<QueryEntry>,
|
|
|
|
receiver: futures_channel::mpsc::UnboundedReceiver<Value>,
|
|
receiver: futures_channel::mpsc::UnboundedReceiver<Value>,
|
|
- return_receiver: Option<futures_channel::oneshot::Receiver<Value>>,
|
|
|
|
|
|
+ return_receiver: Option<futures_channel::oneshot::Receiver<Result<Value, String>>>,
|
|
pub id: usize,
|
|
pub id: usize,
|
|
phantom: std::marker::PhantomData<V>,
|
|
phantom: std::marker::PhantomData<V>,
|
|
}
|
|
}
|
|
@@ -183,18 +159,7 @@ impl<V: DeserializeOwned> Query<V> {
|
|
let queue_id = self.id;
|
|
let queue_id = self.id;
|
|
|
|
|
|
let data = message.to_string();
|
|
let data = message.to_string();
|
|
- let script = format!(
|
|
|
|
- r#"
|
|
|
|
- if (!window.{QUEUE_NAME}) {{
|
|
|
|
- window.{QUEUE_NAME} = [];
|
|
|
|
- }}
|
|
|
|
-
|
|
|
|
- if (!window.{QUEUE_NAME}[{queue_id}]) {{
|
|
|
|
- window.{QUEUE_NAME}[{queue_id}] = [];
|
|
|
|
- }}
|
|
|
|
- window.{QUEUE_NAME}[{queue_id}].push({data});
|
|
|
|
- "#
|
|
|
|
- );
|
|
|
|
|
|
+ let script = format!(r#"window.getQuery({queue_id}).rustSend({data});"#);
|
|
|
|
|
|
self.desktop
|
|
self.desktop
|
|
.webview
|
|
.webview
|
|
@@ -211,13 +176,17 @@ impl<V: DeserializeOwned> Query<V> {
|
|
) -> std::task::Poll<Result<Value, QueryError>> {
|
|
) -> std::task::Poll<Result<Value, QueryError>> {
|
|
self.receiver
|
|
self.receiver
|
|
.poll_next_unpin(cx)
|
|
.poll_next_unpin(cx)
|
|
- .map(|result| result.ok_or(QueryError::Recv))
|
|
|
|
|
|
+ .map(|result| result.ok_or(QueryError::Recv(String::from("Receive channel closed"))))
|
|
}
|
|
}
|
|
|
|
|
|
/// Receive the result of the query
|
|
/// Receive the result of the query
|
|
pub async fn result(&mut self) -> Result<Value, QueryError> {
|
|
pub async fn result(&mut self) -> Result<Value, QueryError> {
|
|
match self.return_receiver.take() {
|
|
match self.return_receiver.take() {
|
|
- Some(receiver) => receiver.await.map_err(|_| QueryError::Recv),
|
|
|
|
|
|
+ Some(receiver) => match receiver.await {
|
|
|
|
+ Ok(Ok(data)) => Ok(data),
|
|
|
|
+ Ok(Err(err)) => Err(QueryError::Recv(err)),
|
|
|
|
+ Err(err) => Err(QueryError::Recv(err.to_string())),
|
|
|
|
+ },
|
|
None => Err(QueryError::Finished),
|
|
None => Err(QueryError::Finished),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -228,36 +197,21 @@ impl<V: DeserializeOwned> Query<V> {
|
|
cx: &mut std::task::Context<'_>,
|
|
cx: &mut std::task::Context<'_>,
|
|
) -> std::task::Poll<Result<Value, QueryError>> {
|
|
) -> std::task::Poll<Result<Value, QueryError>> {
|
|
match self.return_receiver.as_mut() {
|
|
match self.return_receiver.as_mut() {
|
|
- Some(receiver) => receiver.poll_unpin(cx).map_err(|_| QueryError::Recv),
|
|
|
|
|
|
+ Some(receiver) => receiver.poll_unpin(cx).map(|result| match result {
|
|
|
|
+ Ok(Ok(data)) => Ok(data),
|
|
|
|
+ Ok(Err(err)) => Err(QueryError::Recv(err)),
|
|
|
|
+ Err(err) => Err(QueryError::Recv(err.to_string())),
|
|
|
|
+ }),
|
|
None => std::task::Poll::Ready(Err(QueryError::Finished)),
|
|
None => std::task::Poll::Ready(Err(QueryError::Finished)),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-impl<V: DeserializeOwned> Drop for Query<V> {
|
|
|
|
- fn drop(&mut self) {
|
|
|
|
- self.slab.slab.borrow_mut().remove(self.id);
|
|
|
|
- let queue_id = self.id;
|
|
|
|
-
|
|
|
|
- _ = self.desktop.webview.evaluate_script(&format!(
|
|
|
|
- r#"
|
|
|
|
- if (!window.{QUEUE_NAME}) {{
|
|
|
|
- window.{QUEUE_NAME} = [];
|
|
|
|
- }}
|
|
|
|
-
|
|
|
|
- if (window.{QUEUE_NAME}[{queue_id}]) {{
|
|
|
|
- window.{QUEUE_NAME}[{queue_id}] = [];
|
|
|
|
- }}
|
|
|
|
- "#
|
|
|
|
- ));
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
#[derive(Error, Debug)]
|
|
#[derive(Error, Debug)]
|
|
#[non_exhaustive]
|
|
#[non_exhaustive]
|
|
pub enum QueryError {
|
|
pub enum QueryError {
|
|
- #[error("Error receiving query result.")]
|
|
|
|
- Recv,
|
|
|
|
|
|
+ #[error("Error receiving query result: {0}")]
|
|
|
|
+ Recv(String),
|
|
#[error("Error sending message to query: {0}")]
|
|
#[error("Error sending message to query: {0}")]
|
|
Send(String),
|
|
Send(String),
|
|
#[error("Error deserializing query result: {0}")]
|
|
#[error("Error deserializing query result: {0}")]
|
|
@@ -269,7 +223,18 @@ pub enum QueryError {
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
pub(crate) struct QueryResult {
|
|
pub(crate) struct QueryResult {
|
|
id: usize,
|
|
id: usize,
|
|
- data: Value,
|
|
|
|
- #[serde(default)]
|
|
|
|
- returned_value: bool,
|
|
|
|
|
|
+ data: QueryResultData,
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#[derive(Clone, Debug, Deserialize)]
|
|
|
|
+#[serde(tag = "method")]
|
|
|
|
+enum QueryResultData {
|
|
|
|
+ #[serde(rename = "return")]
|
|
|
|
+ Return { data: Option<Value> },
|
|
|
|
+ #[serde(rename = "return_error")]
|
|
|
|
+ ReturnError { data: Value },
|
|
|
|
+ #[serde(rename = "send")]
|
|
|
|
+ Send { data: Value },
|
|
|
|
+ #[serde(rename = "drop")]
|
|
|
|
+ Drop,
|
|
}
|
|
}
|