dom.rs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  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::AppendChildren { many } => self.interpreter.AppendChildren(many),
  96. DomEdit::ReplaceWith { root, m } => self.interpreter.ReplaceWith(root, m),
  97. DomEdit::InsertAfter { root, n } => self.interpreter.InsertAfter(root, n),
  98. DomEdit::InsertBefore { root, n } => self.interpreter.InsertBefore(root, n),
  99. DomEdit::Remove { root } => self.interpreter.Remove(root),
  100. DomEdit::CreateElement { tag, root } => self.interpreter.CreateElement(tag, root),
  101. DomEdit::CreateElementNs { tag, root, ns } => {
  102. self.interpreter.CreateElementNs(tag, root, ns)
  103. }
  104. DomEdit::CreatePlaceholder { root } => self.interpreter.CreatePlaceholder(root),
  105. DomEdit::NewEventListener {
  106. event_name, root, ..
  107. } => {
  108. let handler: &Function = self.handler.as_ref().unchecked_ref();
  109. self.interpreter.NewEventListener(event_name, root, handler);
  110. }
  111. DomEdit::RemoveEventListener { root, event } => {
  112. self.interpreter.RemoveEventListener(root, event)
  113. }
  114. DomEdit::RemoveAttribute { root, name, ns } => {
  115. self.interpreter.RemoveAttribute(root, name, ns)
  116. }
  117. DomEdit::CreateTextNode { text, root } => {
  118. let text = serde_wasm_bindgen::to_value(text).unwrap();
  119. self.interpreter.CreateTextNode(text, root)
  120. }
  121. DomEdit::SetText { root, text } => {
  122. let text = serde_wasm_bindgen::to_value(text).unwrap();
  123. self.interpreter.SetText(root, text)
  124. }
  125. DomEdit::SetAttribute {
  126. root,
  127. field,
  128. value,
  129. ns,
  130. } => {
  131. let value = serde_wasm_bindgen::to_value(value).unwrap();
  132. self.interpreter.SetAttribute(root, field, value, ns)
  133. }
  134. }
  135. }
  136. }
  137. }
  138. pub struct DioxusWebsysEvent(web_sys::Event);
  139. // safety: currently the web is not multithreaded and our VirtualDom exists on the same thread
  140. #[allow(clippy::non_send_fields_in_send_ty)]
  141. unsafe impl Send for DioxusWebsysEvent {}
  142. unsafe impl Sync for DioxusWebsysEvent {}
  143. // todo: some of these events are being casted to the wrong event type.
  144. // We need tests that simulate clicks/etc and make sure every event type works.
  145. fn virtual_event_from_websys_event(
  146. event: web_sys::Event,
  147. target: Element,
  148. ) -> Arc<dyn Any + Send + Sync> {
  149. use dioxus_html::on::*;
  150. use dioxus_html::KeyCode;
  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" => {
  160. let evt: &web_sys::KeyboardEvent = event.dyn_ref().unwrap();
  161. Arc::new(KeyboardData {
  162. alt_key: evt.alt_key(),
  163. char_code: evt.char_code(),
  164. key: evt.key(),
  165. key_code: KeyCode::from_raw_code(evt.key_code() as u8),
  166. ctrl_key: evt.ctrl_key(),
  167. locale: "not implemented".to_string(),
  168. location: evt.location() as usize,
  169. meta_key: evt.meta_key(),
  170. repeat: evt.repeat(),
  171. shift_key: evt.shift_key(),
  172. which: evt.which() as usize,
  173. })
  174. }
  175. "focus" | "blur" => Arc::new(FocusData {}),
  176. // todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
  177. // don't have a good solution with the serialized event problem
  178. "change" | "input" | "invalid" | "reset" | "submit" => {
  179. let value: String = (&target)
  180. .dyn_ref()
  181. .map(|input: &web_sys::HtmlInputElement| {
  182. // todo: special case more input types
  183. match input.type_().as_str() {
  184. "checkbox" => {
  185. match input.checked() {
  186. true => "true".to_string(),
  187. false => "false".to_string(),
  188. }
  189. },
  190. _ => {
  191. input.value()
  192. }
  193. }
  194. })
  195. .or_else(|| {
  196. target
  197. .dyn_ref()
  198. .map(|input: &web_sys::HtmlTextAreaElement| input.value())
  199. })
  200. // select elements are NOT input events - because - why woudn't they be??
  201. .or_else(|| {
  202. target
  203. .dyn_ref()
  204. .map(|input: &web_sys::HtmlSelectElement| input.value())
  205. })
  206. .or_else(|| {
  207. target
  208. .dyn_ref::<web_sys::HtmlElement>()
  209. .unwrap()
  210. .text_content()
  211. })
  212. .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
  213. let mut values = std::collections::HashMap::new();
  214. // try to fill in form values
  215. if let Some(form) = target.dyn_ref::<web_sys::HtmlFormElement>() {
  216. let elements = form.elements();
  217. for x in 0..elements.length() {
  218. let element = elements.item(x).unwrap();
  219. if let Some(name) = element.get_attribute("name") {
  220. let value: Option<String> = (&element)
  221. .dyn_ref()
  222. .map(|input: &web_sys::HtmlInputElement| {
  223. log::info!("Input type: {}", input.type_());
  224. match input.type_().as_str() {
  225. "checkbox" => {
  226. match input.checked() {
  227. true => Some("true".to_string()),
  228. false => Some("false".to_string()),
  229. }
  230. },
  231. "radio" => {
  232. match input.checked() {
  233. true => Some(input.value()),
  234. false => None,
  235. }
  236. }
  237. _ => Some(input.value())
  238. }
  239. })
  240. .or_else(|| target.dyn_ref().map(|input: &web_sys::HtmlTextAreaElement| Some(input.value())))
  241. .or_else(|| target.dyn_ref().map(|input: &web_sys::HtmlSelectElement| Some(input.value())))
  242. .or_else(|| Some(target.dyn_ref::<web_sys::HtmlElement>().unwrap().text_content()))
  243. .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
  244. if let Some(value) = value {
  245. values.insert(name, value);
  246. }
  247. }
  248. }
  249. }
  250. Arc::new(FormData { value, values })
  251. }
  252. "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
  253. | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
  254. | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
  255. let evt: &web_sys::MouseEvent = event.dyn_ref().unwrap();
  256. Arc::new(MouseData {
  257. alt_key: evt.alt_key(),
  258. button: evt.button(),
  259. buttons: evt.buttons(),
  260. client_x: evt.client_x(),
  261. client_y: evt.client_y(),
  262. ctrl_key: evt.ctrl_key(),
  263. meta_key: evt.meta_key(),
  264. screen_x: evt.screen_x(),
  265. screen_y: evt.screen_y(),
  266. shift_key: evt.shift_key(),
  267. page_x: evt.page_x(),
  268. page_y: evt.page_y(),
  269. })
  270. }
  271. "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
  272. | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
  273. let evt: &web_sys::PointerEvent = event.dyn_ref().unwrap();
  274. Arc::new(PointerData {
  275. alt_key: evt.alt_key(),
  276. button: evt.button(),
  277. buttons: evt.buttons(),
  278. client_x: evt.client_x(),
  279. client_y: evt.client_y(),
  280. ctrl_key: evt.ctrl_key(),
  281. meta_key: evt.meta_key(),
  282. page_x: evt.page_x(),
  283. page_y: evt.page_y(),
  284. screen_x: evt.screen_x(),
  285. screen_y: evt.screen_y(),
  286. shift_key: evt.shift_key(),
  287. pointer_id: evt.pointer_id(),
  288. width: evt.width(),
  289. height: evt.height(),
  290. pressure: evt.pressure(),
  291. tangential_pressure: evt.tangential_pressure(),
  292. tilt_x: evt.tilt_x(),
  293. tilt_y: evt.tilt_y(),
  294. twist: evt.twist(),
  295. pointer_type: evt.pointer_type(),
  296. is_primary: evt.is_primary(),
  297. // get_modifier_state: evt.get_modifier_state(),
  298. })
  299. }
  300. "select" => Arc::new(SelectionData {}),
  301. "touchcancel" | "touchend" | "touchmove" | "touchstart" => {
  302. let evt: &web_sys::TouchEvent = event.dyn_ref().unwrap();
  303. Arc::new(TouchData {
  304. alt_key: evt.alt_key(),
  305. ctrl_key: evt.ctrl_key(),
  306. meta_key: evt.meta_key(),
  307. shift_key: evt.shift_key(),
  308. })
  309. }
  310. "scroll" => Arc::new(()),
  311. "wheel" => {
  312. let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap();
  313. Arc::new(WheelData {
  314. delta_x: evt.delta_x(),
  315. delta_y: evt.delta_y(),
  316. delta_z: evt.delta_z(),
  317. delta_mode: evt.delta_mode(),
  318. })
  319. }
  320. "animationstart" | "animationend" | "animationiteration" => {
  321. let evt: &web_sys::AnimationEvent = event.dyn_ref().unwrap();
  322. Arc::new(AnimationData {
  323. elapsed_time: evt.elapsed_time(),
  324. animation_name: evt.animation_name(),
  325. pseudo_element: evt.pseudo_element(),
  326. })
  327. }
  328. "transitionend" => {
  329. let evt: &web_sys::TransitionEvent = event.dyn_ref().unwrap();
  330. Arc::new(TransitionData {
  331. elapsed_time: evt.elapsed_time(),
  332. property_name: evt.property_name(),
  333. pseudo_element: evt.pseudo_element(),
  334. })
  335. }
  336. "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
  337. | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
  338. | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
  339. | "timeupdate" | "volumechange" | "waiting" => Arc::new(MediaData {}),
  340. "toggle" => Arc::new(ToggleData {}),
  341. _ => Arc::new(()),
  342. }
  343. }
  344. pub(crate) fn load_document() -> Document {
  345. web_sys::window()
  346. .expect("should have access to the Window")
  347. .document()
  348. .expect("should have access to the Document")
  349. }
  350. fn event_name_from_typ(typ: &str) -> &'static str {
  351. match typ {
  352. "copy" => "copy",
  353. "cut" => "cut",
  354. "paste" => "paste",
  355. "compositionend" => "compositionend",
  356. "compositionstart" => "compositionstart",
  357. "compositionupdate" => "compositionupdate",
  358. "keydown" => "keydown",
  359. "keypress" => "keypress",
  360. "keyup" => "keyup",
  361. "focus" => "focus",
  362. "blur" => "blur",
  363. "change" => "change",
  364. "input" => "input",
  365. "invalid" => "invalid",
  366. "reset" => "reset",
  367. "submit" => "submit",
  368. "click" => "click",
  369. "contextmenu" => "contextmenu",
  370. "doubleclick" => "doubleclick",
  371. "drag" => "drag",
  372. "dragend" => "dragend",
  373. "dragenter" => "dragenter",
  374. "dragexit" => "dragexit",
  375. "dragleave" => "dragleave",
  376. "dragover" => "dragover",
  377. "dragstart" => "dragstart",
  378. "drop" => "drop",
  379. "mousedown" => "mousedown",
  380. "mouseenter" => "mouseenter",
  381. "mouseleave" => "mouseleave",
  382. "mousemove" => "mousemove",
  383. "mouseout" => "mouseout",
  384. "mouseover" => "mouseover",
  385. "mouseup" => "mouseup",
  386. "pointerdown" => "pointerdown",
  387. "pointermove" => "pointermove",
  388. "pointerup" => "pointerup",
  389. "pointercancel" => "pointercancel",
  390. "gotpointercapture" => "gotpointercapture",
  391. "lostpointercapture" => "lostpointercapture",
  392. "pointerenter" => "pointerenter",
  393. "pointerleave" => "pointerleave",
  394. "pointerover" => "pointerover",
  395. "pointerout" => "pointerout",
  396. "select" => "select",
  397. "touchcancel" => "touchcancel",
  398. "touchend" => "touchend",
  399. "touchmove" => "touchmove",
  400. "touchstart" => "touchstart",
  401. "scroll" => "scroll",
  402. "wheel" => "wheel",
  403. "animationstart" => "animationstart",
  404. "animationend" => "animationend",
  405. "animationiteration" => "animationiteration",
  406. "transitionend" => "transitionend",
  407. "abort" => "abort",
  408. "canplay" => "canplay",
  409. "canplaythrough" => "canplaythrough",
  410. "durationchange" => "durationchange",
  411. "emptied" => "emptied",
  412. "encrypted" => "encrypted",
  413. "ended" => "ended",
  414. "error" => "error",
  415. "loadeddata" => "loadeddata",
  416. "loadedmetadata" => "loadedmetadata",
  417. "loadstart" => "loadstart",
  418. "pause" => "pause",
  419. "play" => "play",
  420. "playing" => "playing",
  421. "progress" => "progress",
  422. "ratechange" => "ratechange",
  423. "seeked" => "seeked",
  424. "seeking" => "seeking",
  425. "stalled" => "stalled",
  426. "suspend" => "suspend",
  427. "timeupdate" => "timeupdate",
  428. "volumechange" => "volumechange",
  429. "waiting" => "waiting",
  430. "toggle" => "toggle",
  431. _ => {
  432. panic!("unsupported event type")
  433. }
  434. }
  435. }