eval.rs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. #![allow(clippy::await_holding_refcell_ref)]
  2. use dioxus_core::prelude::*;
  3. use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};
  4. use std::future::{poll_fn, Future, IntoFuture};
  5. use std::pin::Pin;
  6. use std::rc::Rc;
  7. use std::task::{Context, Poll};
  8. /// A struct that implements EvalProvider is sent through [`ScopeState`]'s provide_context function
  9. /// so that [`use_eval`] can provide a platform agnostic interface for evaluating JavaScript code.
  10. pub trait EvalProvider {
  11. fn new_evaluator(&self, js: String) -> GenerationalBox<Box<dyn Evaluator>>;
  12. }
  13. /// The platform's evaluator.
  14. pub trait Evaluator {
  15. /// Sends a message to the evaluated JavaScript.
  16. fn send(&self, data: serde_json::Value) -> Result<(), EvalError>;
  17. /// Receive any queued messages from the evaluated JavaScript.
  18. fn poll_recv(
  19. &mut self,
  20. context: &mut Context<'_>,
  21. ) -> Poll<Result<serde_json::Value, EvalError>>;
  22. /// Gets the return value of the JavaScript
  23. fn poll_join(
  24. &mut self,
  25. context: &mut Context<'_>,
  26. ) -> Poll<Result<serde_json::Value, EvalError>>;
  27. }
  28. type EvalCreator = Rc<dyn Fn(&str) -> UseEval>;
  29. /// Get a struct that can execute any JavaScript.
  30. ///
  31. /// # Safety
  32. ///
  33. /// Please be very careful with this function. A script with too many dynamic
  34. /// parts is practically asking for a hacker to find an XSS vulnerability in
  35. /// it. **This applies especially to web targets, where the JavaScript context
  36. /// has access to most, if not all of your application data.**
  37. #[must_use]
  38. pub fn eval_provider() -> EvalCreator {
  39. let eval_provider = consume_context::<Rc<dyn EvalProvider>>();
  40. Rc::new(move |script: &str| UseEval::new(eval_provider.new_evaluator(script.to_string())))
  41. as Rc<dyn Fn(&str) -> UseEval>
  42. }
  43. pub fn eval(script: &str) -> UseEval {
  44. let eval_provider = dioxus_core::prelude::try_consume_context::<Rc<dyn EvalProvider>>()
  45. // Create a dummy provider that always hiccups when trying to evaluate
  46. // That way, we can still compile and run the code without a real provider
  47. .unwrap_or_else(|| {
  48. struct DummyProvider;
  49. impl EvalProvider for DummyProvider {
  50. fn new_evaluator(&self, _js: String) -> GenerationalBox<Box<dyn Evaluator>> {
  51. UnsyncStorage::owner().insert(Box::new(DummyEvaluator))
  52. }
  53. }
  54. struct DummyEvaluator;
  55. impl Evaluator for DummyEvaluator {
  56. fn send(&self, _data: serde_json::Value) -> Result<(), EvalError> {
  57. Err(EvalError::Unsupported)
  58. }
  59. fn poll_recv(
  60. &mut self,
  61. _context: &mut Context<'_>,
  62. ) -> Poll<Result<serde_json::Value, EvalError>> {
  63. Poll::Ready(Err(EvalError::Unsupported))
  64. }
  65. fn poll_join(
  66. &mut self,
  67. _context: &mut Context<'_>,
  68. ) -> Poll<Result<serde_json::Value, EvalError>> {
  69. Poll::Ready(Err(EvalError::Unsupported))
  70. }
  71. }
  72. Rc::new(DummyProvider) as Rc<dyn EvalProvider>
  73. });
  74. UseEval::new(eval_provider.new_evaluator(script.to_string()))
  75. }
  76. /// A wrapper around the target platform's evaluator.
  77. #[derive(Clone, Copy)]
  78. pub struct UseEval {
  79. evaluator: GenerationalBox<Box<dyn Evaluator>>,
  80. }
  81. impl UseEval {
  82. /// Creates a new UseEval
  83. pub fn new(evaluator: GenerationalBox<Box<dyn Evaluator + 'static>>) -> Self {
  84. Self { evaluator }
  85. }
  86. /// Sends a [`serde_json::Value`] to the evaluated JavaScript.
  87. pub fn send(&self, data: serde_json::Value) -> Result<(), EvalError> {
  88. self.evaluator.read().send(data)
  89. }
  90. /// Gets an UnboundedReceiver to receive messages from the evaluated JavaScript.
  91. pub async fn recv(&mut self) -> Result<serde_json::Value, EvalError> {
  92. poll_fn(|cx| match self.evaluator.try_write() {
  93. Ok(mut evaluator) => evaluator.poll_recv(cx),
  94. Err(_) => Poll::Ready(Err(EvalError::Finished)),
  95. })
  96. .await
  97. }
  98. /// Gets the return value of the evaluated JavaScript.
  99. pub async fn join(self) -> Result<serde_json::Value, EvalError> {
  100. poll_fn(|cx| match self.evaluator.try_write() {
  101. Ok(mut evaluator) => evaluator.poll_join(cx),
  102. Err(_) => Poll::Ready(Err(EvalError::Finished)),
  103. })
  104. .await
  105. }
  106. }
  107. impl IntoFuture for UseEval {
  108. type Output = Result<serde_json::Value, EvalError>;
  109. type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;
  110. fn into_future(self) -> Self::IntoFuture {
  111. Box::pin(self.join())
  112. }
  113. }
  114. /// Represents an error when evaluating JavaScript
  115. #[derive(Debug)]
  116. pub enum EvalError {
  117. /// The platform does not support evaluating JavaScript.
  118. Unsupported,
  119. /// The provided JavaScript has already been ran.
  120. Finished,
  121. /// The provided JavaScript is not valid and can't be ran.
  122. InvalidJs(String),
  123. /// Represents an error communicating between JavaScript and Rust.
  124. Communication(String),
  125. }