dom.rs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. //! Implementation of a renderer for Dioxus on the web.
  2. //!
  3. //! Oustanding todos:
  4. //! - Removing event listeners (delegation)
  5. //! - Passive event listeners
  6. //! - no-op event listener patch for safari
  7. //! - tests to ensure dyn_into works for various event types.
  8. //! - Partial delegation?>
  9. use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent};
  10. use dioxus_interpreter_js::Interpreter;
  11. use js_sys::Function;
  12. use std::{any::Any, rc::Rc, sync::Arc};
  13. use wasm_bindgen::{closure::Closure, JsCast};
  14. use web_sys::{Document, Element, Event, HtmlElement};
  15. use crate::WebConfig;
  16. pub struct WebsysDom {
  17. pub interpreter: Interpreter,
  18. pub(crate) root: Element,
  19. pub handler: Closure<dyn FnMut(&Event)>,
  20. }
  21. impl WebsysDom {
  22. pub fn new(cfg: WebConfig, sender_callback: Rc<dyn Fn(SchedulerMsg)>) -> Self {
  23. // eventually, we just want to let the interpreter do all the work of decoding events into our event type
  24. let callback: Box<dyn FnMut(&Event)> = Box::new(move |event: &web_sys::Event| {
  25. let mut target = event
  26. .target()
  27. .expect("missing target")
  28. .dyn_into::<Element>()
  29. .expect("not a valid element");
  30. let typ = event.type_();
  31. let decoded: anyhow::Result<UserEvent> = loop {
  32. match target.get_attribute("data-dioxus-id").map(|f| f.parse()) {
  33. Some(Ok(id)) => {
  34. break Ok(UserEvent {
  35. name: event_name_from_typ(&typ),
  36. data: virtual_event_from_websys_event(event.clone(), target.clone()),
  37. element: Some(ElementId(id)),
  38. scope_id: None,
  39. priority: dioxus_core::EventPriority::Medium,
  40. });
  41. }
  42. Some(Err(e)) => {
  43. break Err(e.into());
  44. }
  45. None => {
  46. // walk the tree upwards until we actually find an event target
  47. if let Some(parent) = target.parent_element() {
  48. target = parent;
  49. } else {
  50. break Ok(UserEvent {
  51. name: event_name_from_typ(&typ),
  52. data: virtual_event_from_websys_event(
  53. event.clone(),
  54. target.clone(),
  55. ),
  56. element: None,
  57. scope_id: None,
  58. priority: dioxus_core::EventPriority::Low,
  59. });
  60. }
  61. }
  62. }
  63. };
  64. if let Ok(synthetic_event) = decoded {
  65. // Try to prevent default if the attribute is set
  66. if let Some(node) = target.dyn_ref::<HtmlElement>() {
  67. if let Some(name) = node.get_attribute("dioxus-prevent-default") {
  68. if name == synthetic_event.name
  69. || name.trim_start_matches("on") == synthetic_event.name
  70. {
  71. log::trace!("Preventing default");
  72. event.prevent_default();
  73. }
  74. }
  75. }
  76. sender_callback.as_ref()(SchedulerMsg::Event(synthetic_event))
  77. }
  78. });
  79. // a match here in order to avoid some error during runtime browser test
  80. let document = load_document();
  81. let root = match document.get_element_by_id(&cfg.rootname) {
  82. Some(root) => root,
  83. None => document.create_element("body").ok().unwrap(),
  84. };
  85. Self {
  86. interpreter: Interpreter::new(root.clone()),
  87. handler: Closure::wrap(callback),
  88. root,
  89. }
  90. }
  91. pub fn apply_edits(&mut self, mut edits: Vec<DomEdit>) {
  92. for edit in edits.drain(..) {
  93. match edit {
  94. DomEdit::PushRoot { root } => self.interpreter.PushRoot(root),
  95. DomEdit::PopRoot {} => self.interpreter.PopRoot(),
  96. DomEdit::AppendChildren { many } => self.interpreter.AppendChildren(many),
  97. DomEdit::ReplaceWith { root, m } => self.interpreter.ReplaceWith(root, m),
  98. DomEdit::InsertAfter { root, n } => self.interpreter.InsertAfter(root, n),
  99. DomEdit::InsertBefore { root, n } => self.interpreter.InsertBefore(root, n),
  100. DomEdit::Remove { root } => self.interpreter.Remove(root),
  101. DomEdit::CreateElement { tag, root } => self.interpreter.CreateElement(tag, root),
  102. DomEdit::CreateElementNs { tag, root, ns } => {
  103. self.interpreter.CreateElementNs(tag, root, ns)
  104. }
  105. DomEdit::CreatePlaceholder { root } => self.interpreter.CreatePlaceholder(root),
  106. DomEdit::NewEventListener {
  107. event_name, root, ..
  108. } => {
  109. let handler: &Function = self.handler.as_ref().unchecked_ref();
  110. self.interpreter.NewEventListener(event_name, root, handler);
  111. }
  112. DomEdit::RemoveEventListener { root, event } => {
  113. self.interpreter.RemoveEventListener(root, event)
  114. }
  115. DomEdit::RemoveAttribute { root, name, ns } => {
  116. self.interpreter.RemoveAttribute(root, name, ns)
  117. }
  118. DomEdit::CreateTextNode { text, root } => {
  119. let text = serde_wasm_bindgen::to_value(text).unwrap();
  120. self.interpreter.CreateTextNode(text, root)
  121. }
  122. DomEdit::SetText { root, text } => {
  123. let text = serde_wasm_bindgen::to_value(text).unwrap();
  124. self.interpreter.SetText(root, text)
  125. }
  126. DomEdit::SetAttribute {
  127. root,
  128. field,
  129. value,
  130. ns,
  131. } => {
  132. let value = serde_wasm_bindgen::to_value(&value).unwrap();
  133. self.interpreter.SetAttribute(root, field, value, ns)
  134. }
  135. }
  136. }
  137. }
  138. }
  139. pub struct DioxusWebsysEvent(web_sys::Event);
  140. // safety: currently the web is not multithreaded and our VirtualDom exists on the same thread
  141. #[allow(clippy::non_send_fields_in_send_ty)]
  142. unsafe impl Send for DioxusWebsysEvent {}
  143. unsafe impl Sync for DioxusWebsysEvent {}
  144. // todo: some of these events are being casted to the wrong event type.
  145. // We need tests that simulate clicks/etc and make sure every event type works.
  146. fn virtual_event_from_websys_event(
  147. event: web_sys::Event,
  148. target: Element,
  149. ) -> Arc<dyn Any + Send + Sync> {
  150. use dioxus_html::on::*;
  151. match event.type_().as_str() {
  152. "copy" | "cut" | "paste" => Arc::new(ClipboardData {}),
  153. "compositionend" | "compositionstart" | "compositionupdate" => {
  154. let evt: &web_sys::CompositionEvent = event.dyn_ref().unwrap();
  155. Arc::new(CompositionData {
  156. data: evt.data().unwrap_or_default(),
  157. })
  158. }
  159. "keydown" | "keypress" | "keyup" => Arc::new(KeyboardData::from(event)),
  160. "focus" | "blur" => Arc::new(FocusData {}),
  161. // todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
  162. // don't have a good solution with the serialized event problem
  163. "change" | "input" | "invalid" | "reset" | "submit" => {
  164. let value: String = (&target)
  165. .dyn_ref()
  166. .map(|input: &web_sys::HtmlInputElement| {
  167. // todo: special case more input types
  168. match input.type_().as_str() {
  169. "checkbox" => {
  170. match input.checked() {
  171. true => "true".to_string(),
  172. false => "false".to_string(),
  173. }
  174. },
  175. _ => {
  176. input.value()
  177. }
  178. }
  179. })
  180. .or_else(|| {
  181. target
  182. .dyn_ref()
  183. .map(|input: &web_sys::HtmlTextAreaElement| input.value())
  184. })
  185. // select elements are NOT input events - because - why woudn't they be??
  186. .or_else(|| {
  187. target
  188. .dyn_ref()
  189. .map(|input: &web_sys::HtmlSelectElement| input.value())
  190. })
  191. .or_else(|| {
  192. target
  193. .dyn_ref::<web_sys::HtmlElement>()
  194. .unwrap()
  195. .text_content()
  196. })
  197. .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
  198. let mut values = std::collections::HashMap::new();
  199. // try to fill in form values
  200. if let Some(form) = target.dyn_ref::<web_sys::HtmlFormElement>() {
  201. let elements = form.elements();
  202. for x in 0..elements.length() {
  203. let element = elements.item(x).unwrap();
  204. if let Some(name) = element.get_attribute("name") {
  205. let value: Option<String> = (&element)
  206. .dyn_ref()
  207. .map(|input: &web_sys::HtmlInputElement| {
  208. match input.type_().as_str() {
  209. "checkbox" => {
  210. match input.checked() {
  211. true => Some("true".to_string()),
  212. false => Some("false".to_string()),
  213. }
  214. },
  215. "radio" => {
  216. match input.checked() {
  217. true => Some(input.value()),
  218. false => None,
  219. }
  220. }
  221. _ => Some(input.value())
  222. }
  223. })
  224. .or_else(|| element.dyn_ref().map(|input: &web_sys::HtmlTextAreaElement| Some(input.value())))
  225. .or_else(|| element.dyn_ref().map(|input: &web_sys::HtmlSelectElement| Some(input.value())))
  226. .or_else(|| Some(element.dyn_ref::<web_sys::HtmlElement>().unwrap().text_content()))
  227. .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
  228. if let Some(value) = value {
  229. values.insert(name, value);
  230. }
  231. }
  232. }
  233. }
  234. Arc::new(FormData { value, values })
  235. }
  236. "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
  237. | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
  238. | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
  239. Arc::new(MouseData::from(event))
  240. }
  241. "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
  242. | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
  243. Arc::new(PointerData::from(event))
  244. }
  245. "select" => Arc::new(SelectionData {}),
  246. "touchcancel" | "touchend" | "touchmove" | "touchstart" => Arc::new(TouchData::from(event)),
  247. "scroll" => Arc::new(()),
  248. "wheel" => Arc::new(WheelData::from(event)),
  249. "animationstart" | "animationend" | "animationiteration" => {
  250. Arc::new(AnimationData::from(event))
  251. }
  252. "transitionend" => Arc::new(TransitionData::from(event)),
  253. "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
  254. | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
  255. | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
  256. | "timeupdate" | "volumechange" | "waiting" => Arc::new(MediaData {}),
  257. "toggle" => Arc::new(ToggleData {}),
  258. _ => Arc::new(()),
  259. }
  260. }
  261. pub(crate) fn load_document() -> Document {
  262. web_sys::window()
  263. .expect("should have access to the Window")
  264. .document()
  265. .expect("should have access to the Document")
  266. }
  267. fn event_name_from_typ(typ: &str) -> &'static str {
  268. match typ {
  269. "copy" => "copy",
  270. "cut" => "cut",
  271. "paste" => "paste",
  272. "compositionend" => "compositionend",
  273. "compositionstart" => "compositionstart",
  274. "compositionupdate" => "compositionupdate",
  275. "keydown" => "keydown",
  276. "keypress" => "keypress",
  277. "keyup" => "keyup",
  278. "focus" => "focus",
  279. "blur" => "blur",
  280. "change" => "change",
  281. "input" => "input",
  282. "invalid" => "invalid",
  283. "reset" => "reset",
  284. "submit" => "submit",
  285. "click" => "click",
  286. "contextmenu" => "contextmenu",
  287. "doubleclick" => "doubleclick",
  288. "drag" => "drag",
  289. "dragend" => "dragend",
  290. "dragenter" => "dragenter",
  291. "dragexit" => "dragexit",
  292. "dragleave" => "dragleave",
  293. "dragover" => "dragover",
  294. "dragstart" => "dragstart",
  295. "drop" => "drop",
  296. "mousedown" => "mousedown",
  297. "mouseenter" => "mouseenter",
  298. "mouseleave" => "mouseleave",
  299. "mousemove" => "mousemove",
  300. "mouseout" => "mouseout",
  301. "mouseover" => "mouseover",
  302. "mouseup" => "mouseup",
  303. "pointerdown" => "pointerdown",
  304. "pointermove" => "pointermove",
  305. "pointerup" => "pointerup",
  306. "pointercancel" => "pointercancel",
  307. "gotpointercapture" => "gotpointercapture",
  308. "lostpointercapture" => "lostpointercapture",
  309. "pointerenter" => "pointerenter",
  310. "pointerleave" => "pointerleave",
  311. "pointerover" => "pointerover",
  312. "pointerout" => "pointerout",
  313. "select" => "select",
  314. "touchcancel" => "touchcancel",
  315. "touchend" => "touchend",
  316. "touchmove" => "touchmove",
  317. "touchstart" => "touchstart",
  318. "scroll" => "scroll",
  319. "wheel" => "wheel",
  320. "animationstart" => "animationstart",
  321. "animationend" => "animationend",
  322. "animationiteration" => "animationiteration",
  323. "transitionend" => "transitionend",
  324. "abort" => "abort",
  325. "canplay" => "canplay",
  326. "canplaythrough" => "canplaythrough",
  327. "durationchange" => "durationchange",
  328. "emptied" => "emptied",
  329. "encrypted" => "encrypted",
  330. "ended" => "ended",
  331. "error" => "error",
  332. "loadeddata" => "loadeddata",
  333. "loadedmetadata" => "loadedmetadata",
  334. "loadstart" => "loadstart",
  335. "pause" => "pause",
  336. "play" => "play",
  337. "playing" => "playing",
  338. "progress" => "progress",
  339. "ratechange" => "ratechange",
  340. "seeked" => "seeked",
  341. "seeking" => "seeking",
  342. "stalled" => "stalled",
  343. "suspend" => "suspend",
  344. "timeupdate" => "timeupdate",
  345. "volumechange" => "volumechange",
  346. "waiting" => "waiting",
  347. "toggle" => "toggle",
  348. _ => {
  349. panic!("unsupported event type")
  350. }
  351. }
  352. }