lib.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. //! Dioxus WebSys
  2. //! --------------
  3. //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
  4. use dioxus::prelude::{Context, Properties, VNode};
  5. use futures_util::{pin_mut, Stream, StreamExt};
  6. use fxhash::FxHashMap;
  7. use web_sys::{window, Document, Element, Event, Node};
  8. // use futures::{channel::mpsc, SinkExt, StreamExt};
  9. use dioxus::virtual_dom::VirtualDom;
  10. pub use dioxus_core as dioxus;
  11. use dioxus_core::{events::EventTrigger, prelude::FC};
  12. pub use dioxus_core::prelude;
  13. // pub mod interpreter;
  14. pub mod new;
  15. /// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM.
  16. /// Under the hood, we leverage WebSys and interact directly with the DOM
  17. ///
  18. pub struct WebsysRenderer {
  19. internal_dom: VirtualDom,
  20. }
  21. impl WebsysRenderer {
  22. /// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering.
  23. /// See DioxusErrors for more information on how these errors could occour.
  24. ///
  25. /// ```ignore
  26. /// fn main() {
  27. /// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
  28. /// }
  29. /// ```
  30. ///
  31. /// Run the app to completion, panicing if any error occurs while rendering.
  32. /// Pairs well with the wasm_bindgen async handler
  33. pub async fn start(root: FC<()>) {
  34. Self::new(root).run().await.expect("Virtual DOM failed :(");
  35. }
  36. /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
  37. ///
  38. /// This means that the root component must either consumes its own context, or statics are used to generate the page.
  39. /// The root component can access things like routing in its context.
  40. pub fn new(root: FC<()>) -> Self {
  41. Self::new_with_props(root, ())
  42. }
  43. /// Create a new text-renderer instance from a functional component root.
  44. /// Automatically progresses the creation of the VNode tree to completion.
  45. ///
  46. /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
  47. pub fn new_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) -> Self {
  48. Self::from_vdom(VirtualDom::new_with_props(root, root_props))
  49. }
  50. /// Create a new text renderer from an existing Virtual DOM.
  51. pub fn from_vdom(dom: VirtualDom) -> Self {
  52. Self { internal_dom: dom }
  53. }
  54. pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
  55. use wasm_bindgen::JsCast;
  56. let root = prepare_websys_dom();
  57. let root_node = root.clone().dyn_into::<Node>().unwrap();
  58. let mut websys_dom = crate::new::WebsysDom::new(root.clone());
  59. websys_dom.stack.push(root_node.clone());
  60. websys_dom.stack.push(root_node);
  61. let mut edits = Vec::new();
  62. self.internal_dom.rebuild(&mut websys_dom, &mut edits)?;
  63. websys_dom.process_edits(&mut edits);
  64. log::info!("Going into event loop");
  65. loop {
  66. let trigger = {
  67. let real_queue = websys_dom.wait_for_event();
  68. if self.internal_dom.tasks.is_empty() {
  69. log::info!("tasks is empty, waiting for dom event to trigger soemthing");
  70. real_queue.await
  71. } else {
  72. log::info!("tasks is not empty, waiting for either tasks or event system");
  73. let task_queue = (&mut self.internal_dom.tasks).next();
  74. pin_mut!(real_queue);
  75. pin_mut!(task_queue);
  76. match futures_util::future::select(real_queue, task_queue).await {
  77. futures_util::future::Either::Left((trigger, _)) => trigger,
  78. futures_util::future::Either::Right((trigger, _)) => trigger,
  79. }
  80. }
  81. };
  82. if let Some(real_trigger) = trigger {
  83. log::info!("event received");
  84. self.internal_dom.queue_event(real_trigger)?;
  85. let mut edits = Vec::new();
  86. self.internal_dom
  87. .progress_with_event(&mut websys_dom, &mut edits)
  88. .await?;
  89. websys_dom.process_edits(&mut edits);
  90. }
  91. }
  92. // should actually never return from this, should be an error, rustc just cant see it
  93. Ok(())
  94. }
  95. }
  96. fn prepare_websys_dom() -> Element {
  97. // Initialize the container on the dom
  98. // Hook up the body as the root component to render tinto
  99. let window = web_sys::window().expect("should have access to the Window");
  100. let document = window
  101. .document()
  102. .expect("should have access to the Document");
  103. // let body = document.body().unwrap();
  104. let el = document.get_element_by_id("dioxusroot").unwrap();
  105. // Build a dummy div
  106. // let container: &Element = body.as_ref();
  107. // container.set_inner_html("");
  108. // container
  109. // .append_child(
  110. // document
  111. // .create_element("div")
  112. // .expect("should create element OK")
  113. // .as_ref(),
  114. // )
  115. // .expect("should append child OK");
  116. el
  117. // container.clone()
  118. }
  119. // Progress the mount of the root component
  120. // Iterate through the nodes, attaching the closure and sender to the listener
  121. // {
  122. // let mut remote_sender = sender.clone();
  123. // let listener = move || {
  124. // let event = EventTrigger::new();
  125. // wasm_bindgen_futures::spawn_local(async move {
  126. // remote_sender
  127. // .send(event)
  128. // .await
  129. // .expect("Updating receiver failed");
  130. // })
  131. // };
  132. // }
  133. /// Wasm-bindgen has a performance option to intern commonly used phrases
  134. /// This saves the decoding cost, making the interaction of Rust<->JS more performant.
  135. /// We intern all the HTML tags and attributes, making most operations much faster.
  136. ///
  137. /// Interning takes about 1ms at the start of the app, but saves a *ton* of time later on.
  138. pub fn intern_cache() {
  139. let cached_words = [
  140. // All the HTML Tags
  141. "a",
  142. "abbr",
  143. "address",
  144. "area",
  145. "article",
  146. "aside",
  147. "audio",
  148. "b",
  149. "base",
  150. "bdi",
  151. "bdo",
  152. "big",
  153. "blockquote",
  154. "body",
  155. "br",
  156. "button",
  157. "canvas",
  158. "caption",
  159. "cite",
  160. "code",
  161. "col",
  162. "colgroup",
  163. "command",
  164. "data",
  165. "datalist",
  166. "dd",
  167. "del",
  168. "details",
  169. "dfn",
  170. "dialog",
  171. "div",
  172. "dl",
  173. "dt",
  174. "em",
  175. "embed",
  176. "fieldset",
  177. "figcaption",
  178. "figure",
  179. "footer",
  180. "form",
  181. "h1",
  182. "h2",
  183. "h3",
  184. "h4",
  185. "h5",
  186. "h6",
  187. "head",
  188. "header",
  189. "hr",
  190. "html",
  191. "i",
  192. "iframe",
  193. "img",
  194. "input",
  195. "ins",
  196. "kbd",
  197. "keygen",
  198. "label",
  199. "legend",
  200. "li",
  201. "link",
  202. "main",
  203. "map",
  204. "mark",
  205. "menu",
  206. "menuitem",
  207. "meta",
  208. "meter",
  209. "nav",
  210. "noscript",
  211. "object",
  212. "ol",
  213. "optgroup",
  214. "option",
  215. "output",
  216. "p",
  217. "param",
  218. "picture",
  219. "pre",
  220. "progress",
  221. "q",
  222. "rp",
  223. "rt",
  224. "ruby",
  225. "s",
  226. "samp",
  227. "script",
  228. "section",
  229. "select",
  230. "small",
  231. "source",
  232. "span",
  233. "strong",
  234. "style",
  235. "sub",
  236. "summary",
  237. "sup",
  238. "table",
  239. "tbody",
  240. "td",
  241. "textarea",
  242. "tfoot",
  243. "th",
  244. "thead",
  245. "time",
  246. "title",
  247. "tr",
  248. "track",
  249. "u",
  250. "ul",
  251. "var",
  252. "video",
  253. "wbr",
  254. // All the event handlers
  255. "Attribute",
  256. "accept",
  257. "accept-charset",
  258. "accesskey",
  259. "action",
  260. "alt",
  261. "async",
  262. "autocomplete",
  263. "autofocus",
  264. "autoplay",
  265. "charset",
  266. "checked",
  267. "cite",
  268. "class",
  269. "cols",
  270. "colspan",
  271. "content",
  272. "contenteditable",
  273. "controls",
  274. "coords",
  275. "data",
  276. "data-*",
  277. "datetime",
  278. "default",
  279. "defer",
  280. "dir",
  281. "dirname",
  282. "disabled",
  283. "download",
  284. "draggable",
  285. "enctype",
  286. "for",
  287. "form",
  288. "formaction",
  289. "headers",
  290. "height",
  291. "hidden",
  292. "high",
  293. "href",
  294. "hreflang",
  295. "http-equiv",
  296. "id",
  297. "ismap",
  298. "kind",
  299. "label",
  300. "lang",
  301. "list",
  302. "loop",
  303. "low",
  304. "max",
  305. "maxlength",
  306. "media",
  307. "method",
  308. "min",
  309. "multiple",
  310. "muted",
  311. "name",
  312. "novalidate",
  313. "onabort",
  314. "onafterprint",
  315. "onbeforeprint",
  316. "onbeforeunload",
  317. "onblur",
  318. "oncanplay",
  319. "oncanplaythrough",
  320. "onchange",
  321. "onclick",
  322. "oncontextmenu",
  323. "oncopy",
  324. "oncuechange",
  325. "oncut",
  326. "ondblclick",
  327. "ondrag",
  328. "ondragend",
  329. "ondragenter",
  330. "ondragleave",
  331. "ondragover",
  332. "ondragstart",
  333. "ondrop",
  334. "ondurationchange",
  335. "onemptied",
  336. "onended",
  337. "onerror",
  338. "onfocus",
  339. "onhashchange",
  340. "oninput",
  341. "oninvalid",
  342. "onkeydown",
  343. "onkeypress",
  344. "onkeyup",
  345. "onload",
  346. "onloadeddata",
  347. "onloadedmetadata",
  348. "onloadstart",
  349. "onmousedown",
  350. "onmousemove",
  351. "onmouseout",
  352. "onmouseover",
  353. "onmouseup",
  354. "onmousewheel",
  355. "onoffline",
  356. "ononline",
  357. "<body>",
  358. "onpageshow",
  359. "onpaste",
  360. "onpause",
  361. "onplay",
  362. "onplaying",
  363. "<body>",
  364. "onprogress",
  365. "onratechange",
  366. "onreset",
  367. "onresize",
  368. "onscroll",
  369. "onsearch",
  370. "onseeked",
  371. "onseeking",
  372. "onselect",
  373. "onstalled",
  374. "<body>",
  375. "onsubmit",
  376. "onsuspend",
  377. "ontimeupdate",
  378. "ontoggle",
  379. "onunload",
  380. "onvolumechange",
  381. "onwaiting",
  382. "onwheel",
  383. "open",
  384. "optimum",
  385. "pattern",
  386. "placeholder",
  387. "poster",
  388. "preload",
  389. "readonly",
  390. "rel",
  391. "required",
  392. "reversed",
  393. "rows",
  394. "rowspan",
  395. "sandbox",
  396. "scope",
  397. "selected",
  398. "shape",
  399. "size",
  400. "sizes",
  401. "span",
  402. "spellcheck",
  403. "src",
  404. "srcdoc",
  405. "srclang",
  406. "srcset",
  407. "start",
  408. "step",
  409. "style",
  410. "tabindex",
  411. "target",
  412. "title",
  413. "translate",
  414. "type",
  415. "usemap",
  416. "value",
  417. "width",
  418. "wrap",
  419. ];
  420. for s in cached_words {
  421. wasm_bindgen::intern(s);
  422. }
  423. }