eval.rs 5.3 KB

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