interpreter.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. class InterpreterConfig {
  2. constructor(intercept_link_redirects) {
  3. this.intercept_link_redirects = intercept_link_redirects;
  4. }
  5. }
  6. // this handler is only provided on the desktop and liveview implementations since this
  7. // method is not used by the web implementation
  8. function handler(event, name, bubbles, config) {
  9. let target = event.target;
  10. if (target != null) {
  11. let preventDefaultRequests = null;
  12. // Some events can be triggered on text nodes, which don't have attributes
  13. if (target instanceof Element) {
  14. preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
  15. }
  16. if (event.type === "click") {
  17. // todo call prevent default if it's the right type of event
  18. if (config.intercept_link_redirects) {
  19. let a_element = target.closest("a");
  20. if (a_element != null) {
  21. event.preventDefault();
  22. let elementShouldPreventDefault =
  23. preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
  24. let aElementShouldPreventDefault = a_element.getAttribute(
  25. `dioxus-prevent-default`
  26. );
  27. let linkShouldPreventDefault =
  28. aElementShouldPreventDefault &&
  29. aElementShouldPreventDefault.includes(`onclick`);
  30. if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
  31. const href = a_element.getAttribute("href");
  32. if (href !== "" && href !== null && href !== undefined) {
  33. window.ipc.postMessage(
  34. window.interpreter.serializeIpcMessage("browser_open", { href })
  35. );
  36. }
  37. }
  38. }
  39. }
  40. // also prevent buttons from submitting
  41. if (target.tagName === "BUTTON" && event.type == "submit") {
  42. event.preventDefault();
  43. }
  44. }
  45. const realId = find_real_id(target);
  46. if (
  47. preventDefaultRequests &&
  48. preventDefaultRequests.includes(`on${event.type}`)
  49. ) {
  50. event.preventDefault();
  51. }
  52. if (event.type === "submit") {
  53. event.preventDefault();
  54. }
  55. let contents = serialize_event(event);
  56. // TODO: this should be liveview only
  57. if (
  58. target.tagName === "INPUT" &&
  59. (event.type === "change" || event.type === "input")
  60. ) {
  61. const type = target.getAttribute("type");
  62. if (type === "file") {
  63. async function read_files() {
  64. const files = target.files;
  65. const file_contents = {};
  66. for (let i = 0; i < files.length; i++) {
  67. const file = files[i];
  68. file_contents[file.name] = Array.from(
  69. new Uint8Array(await file.arrayBuffer())
  70. );
  71. }
  72. let file_engine = {
  73. files: file_contents,
  74. };
  75. contents.files = file_engine;
  76. if (realId === null) {
  77. return;
  78. }
  79. const message = window.interpreter.serializeIpcMessage("user_event", {
  80. name: name,
  81. element: parseInt(realId),
  82. data: contents,
  83. bubbles,
  84. });
  85. window.ipc.postMessage(message);
  86. }
  87. read_files();
  88. return;
  89. }
  90. }
  91. if (
  92. target.tagName === "FORM" &&
  93. (event.type === "submit" || event.type === "input")
  94. ) {
  95. const formData = new FormData(target);
  96. for (let name of formData.keys()) {
  97. let value = formData.getAll(name);
  98. contents.values[name] = value;
  99. }
  100. }
  101. if (
  102. target.tagName === "SELECT" &&
  103. event.type === "input"
  104. ) {
  105. const selectData = target.options;
  106. contents.values["options"] = [];
  107. for (let i = 0; i < selectData.length; i++) {
  108. let option = selectData[i];
  109. if (option.selected) {
  110. contents.values["options"].push(option.value.toString());
  111. }
  112. }
  113. }
  114. if (realId === null) {
  115. return;
  116. }
  117. window.ipc.postMessage(
  118. window.interpreter.serializeIpcMessage("user_event", {
  119. name: name,
  120. element: parseInt(realId),
  121. data: contents,
  122. bubbles,
  123. })
  124. );
  125. }
  126. }
  127. function find_real_id(target) {
  128. let realId = null;
  129. if (target instanceof Element) {
  130. realId = target.getAttribute(`data-dioxus-id`);
  131. }
  132. // walk the tree to find the real element
  133. while (realId == null) {
  134. // we've reached the root we don't want to send an event
  135. if (target.parentElement === null) {
  136. return;
  137. }
  138. target = target.parentElement;
  139. if (target instanceof Element) {
  140. realId = target.getAttribute(`data-dioxus-id`);
  141. }
  142. }
  143. return realId;
  144. }
  145. class ListenerMap {
  146. constructor(root) {
  147. // bubbling events can listen at the root element
  148. this.global = {};
  149. // non bubbling events listen at the element the listener was created at
  150. this.local = {};
  151. this.root = null;
  152. }
  153. create(event_name, element, bubbles, handler) {
  154. if (bubbles) {
  155. if (this.global[event_name] === undefined) {
  156. this.global[event_name] = {};
  157. this.global[event_name].active = 1;
  158. this.root.addEventListener(event_name, handler);
  159. } else {
  160. this.global[event_name].active++;
  161. }
  162. }
  163. else {
  164. const id = element.getAttribute("data-dioxus-id");
  165. if (!this.local[id]) {
  166. this.local[id] = {};
  167. }
  168. element.addEventListener(event_name, handler);
  169. }
  170. }
  171. remove(element, event_name, bubbles) {
  172. if (bubbles) {
  173. this.global[event_name].active--;
  174. if (this.global[event_name].active === 0) {
  175. this.root.removeEventListener(event_name, this.global[event_name].callback);
  176. delete this.global[event_name];
  177. }
  178. }
  179. else {
  180. const id = element.getAttribute("data-dioxus-id");
  181. delete this.local[id][event_name];
  182. if (this.local[id].length === 0) {
  183. delete this.local[id];
  184. }
  185. element.removeEventListener(event_name, this.global[event_name].callback);
  186. }
  187. }
  188. removeAllNonBubbling(element) {
  189. const id = element.getAttribute("data-dioxus-id");
  190. delete this.local[id];
  191. }
  192. }
  193. function LoadChild(array) {
  194. // iterate through each number and get that child
  195. node = stack[stack.length - 1];
  196. for (let i = 0; i < array.length; i++) {
  197. end = array[i];
  198. for (node = node.firstChild; end > 0; end--) {
  199. node = node.nextSibling;
  200. }
  201. }
  202. return node;
  203. }
  204. const listeners = new ListenerMap();
  205. let nodes = [];
  206. let stack = [];
  207. let root;
  208. const templates = {};
  209. let node, els, end, k;
  210. function AppendChildren(id, many) {
  211. root = nodes[id];
  212. els = stack.splice(stack.length - many);
  213. for (k = 0; k < many; k++) {
  214. root.appendChild(els[k]);
  215. }
  216. }
  217. window.interpreter = {}
  218. window.interpreter.initialize = function (root) {
  219. nodes = [root];
  220. stack = [root];
  221. listeners.root = root;
  222. }
  223. window.interpreter.getClientRect = function (id) {
  224. const node = nodes[id];
  225. if (!node) {
  226. return;
  227. }
  228. const rect = node.getBoundingClientRect();
  229. return {
  230. type: "GetClientRect",
  231. origin: [rect.x, rect.y],
  232. size: [rect.width, rect.height],
  233. };
  234. }
  235. window.interpreter.scrollTo = function (id, behavior) {
  236. const node = nodes[id];
  237. if (!node) {
  238. return false;
  239. }
  240. node.scrollIntoView({
  241. behavior: behavior,
  242. });
  243. return true;
  244. }
  245. /// Set the focus on the element
  246. window.interpreter.setFocus = function (id, focus) {
  247. const node = nodes[id];
  248. if (!node) {
  249. return false;
  250. }
  251. if (focus) {
  252. node.focus();
  253. } else {
  254. node.blur();
  255. }
  256. return true;
  257. }
  258. function get_mouse_data(event) {
  259. const {
  260. altKey,
  261. button,
  262. buttons,
  263. clientX,
  264. clientY,
  265. ctrlKey,
  266. metaKey,
  267. offsetX,
  268. offsetY,
  269. pageX,
  270. pageY,
  271. screenX,
  272. screenY,
  273. shiftKey,
  274. } = event;
  275. return {
  276. alt_key: altKey,
  277. button: button,
  278. buttons: buttons,
  279. client_x: clientX,
  280. client_y: clientY,
  281. ctrl_key: ctrlKey,
  282. meta_key: metaKey,
  283. offset_x: offsetX,
  284. offset_y: offsetY,
  285. page_x: pageX,
  286. page_y: pageY,
  287. screen_x: screenX,
  288. screen_y: screenY,
  289. shift_key: shiftKey,
  290. };
  291. }
  292. function serialize_event(event) {
  293. switch (event.type) {
  294. case "copy":
  295. case "cut":
  296. case "past": {
  297. return {};
  298. }
  299. case "compositionend":
  300. case "compositionstart":
  301. case "compositionupdate": {
  302. let { data } = event;
  303. return {
  304. data,
  305. };
  306. }
  307. case "keydown":
  308. case "keypress":
  309. case "keyup": {
  310. let {
  311. charCode,
  312. key,
  313. altKey,
  314. ctrlKey,
  315. metaKey,
  316. keyCode,
  317. shiftKey,
  318. location,
  319. repeat,
  320. which,
  321. code,
  322. } = event;
  323. return {
  324. char_code: charCode,
  325. key: key,
  326. alt_key: altKey,
  327. ctrl_key: ctrlKey,
  328. meta_key: metaKey,
  329. key_code: keyCode,
  330. shift_key: shiftKey,
  331. location: location,
  332. repeat: repeat,
  333. which: which,
  334. code,
  335. };
  336. }
  337. case "focus":
  338. case "blur": {
  339. return {};
  340. }
  341. case "change": {
  342. let target = event.target;
  343. let value;
  344. if (target.type === "checkbox" || target.type === "radio") {
  345. value = target.checked ? "true" : "false";
  346. } else {
  347. value = target.value ?? target.textContent;
  348. }
  349. return {
  350. value: value,
  351. values: {},
  352. };
  353. }
  354. case "input":
  355. case "invalid":
  356. case "reset":
  357. case "submit": {
  358. let target = event.target;
  359. let value = target.value ?? target.textContent;
  360. if (target.type === "checkbox") {
  361. value = target.checked ? "true" : "false";
  362. }
  363. return {
  364. value: value,
  365. values: {},
  366. };
  367. }
  368. case "drag":
  369. case "dragend":
  370. case "dragenter":
  371. case "dragexit":
  372. case "dragleave":
  373. case "dragover":
  374. case "dragstart":
  375. case "drop": {
  376. return { mouse: get_mouse_data(event) };
  377. }
  378. case "click":
  379. case "contextmenu":
  380. case "doubleclick":
  381. case "dblclick":
  382. case "mousedown":
  383. case "mouseenter":
  384. case "mouseleave":
  385. case "mousemove":
  386. case "mouseout":
  387. case "mouseover":
  388. case "mouseup": {
  389. return get_mouse_data(event);
  390. }
  391. case "pointerdown":
  392. case "pointermove":
  393. case "pointerup":
  394. case "pointercancel":
  395. case "gotpointercapture":
  396. case "lostpointercapture":
  397. case "pointerenter":
  398. case "pointerleave":
  399. case "pointerover":
  400. case "pointerout": {
  401. const {
  402. altKey,
  403. button,
  404. buttons,
  405. clientX,
  406. clientY,
  407. ctrlKey,
  408. metaKey,
  409. pageX,
  410. pageY,
  411. screenX,
  412. screenY,
  413. shiftKey,
  414. pointerId,
  415. width,
  416. height,
  417. pressure,
  418. tangentialPressure,
  419. tiltX,
  420. tiltY,
  421. twist,
  422. pointerType,
  423. isPrimary,
  424. } = event;
  425. return {
  426. alt_key: altKey,
  427. button: button,
  428. buttons: buttons,
  429. client_x: clientX,
  430. client_y: clientY,
  431. ctrl_key: ctrlKey,
  432. meta_key: metaKey,
  433. page_x: pageX,
  434. page_y: pageY,
  435. screen_x: screenX,
  436. screen_y: screenY,
  437. shift_key: shiftKey,
  438. pointer_id: pointerId,
  439. width: width,
  440. height: height,
  441. pressure: pressure,
  442. tangential_pressure: tangentialPressure,
  443. tilt_x: tiltX,
  444. tilt_y: tiltY,
  445. twist: twist,
  446. pointer_type: pointerType,
  447. is_primary: isPrimary,
  448. };
  449. }
  450. case "select": {
  451. return {};
  452. }
  453. case "touchcancel":
  454. case "touchend":
  455. case "touchmove":
  456. case "touchstart": {
  457. const { altKey, ctrlKey, metaKey, shiftKey } = event;
  458. return {
  459. // changed_touches: event.changedTouches,
  460. // target_touches: event.targetTouches,
  461. // touches: event.touches,
  462. alt_key: altKey,
  463. ctrl_key: ctrlKey,
  464. meta_key: metaKey,
  465. shift_key: shiftKey,
  466. };
  467. }
  468. case "scroll": {
  469. return {};
  470. }
  471. case "wheel": {
  472. const { deltaX, deltaY, deltaZ, deltaMode } = event;
  473. return {
  474. delta_x: deltaX,
  475. delta_y: deltaY,
  476. delta_z: deltaZ,
  477. delta_mode: deltaMode,
  478. };
  479. }
  480. case "animationstart":
  481. case "animationend":
  482. case "animationiteration": {
  483. const { animationName, elapsedTime, pseudoElement } = event;
  484. return {
  485. animation_name: animationName,
  486. elapsed_time: elapsedTime,
  487. pseudo_element: pseudoElement,
  488. };
  489. }
  490. case "transitionend": {
  491. const { propertyName, elapsedTime, pseudoElement } = event;
  492. return {
  493. property_name: propertyName,
  494. elapsed_time: elapsedTime,
  495. pseudo_element: pseudoElement,
  496. };
  497. }
  498. case "abort":
  499. case "canplay":
  500. case "canplaythrough":
  501. case "durationchange":
  502. case "emptied":
  503. case "encrypted":
  504. case "ended":
  505. case "error":
  506. case "loadeddata":
  507. case "loadedmetadata":
  508. case "loadstart":
  509. case "pause":
  510. case "play":
  511. case "playing":
  512. case "progress":
  513. case "ratechange":
  514. case "seeked":
  515. case "seeking":
  516. case "stalled":
  517. case "suspend":
  518. case "timeupdate":
  519. case "volumechange":
  520. case "waiting": {
  521. return {};
  522. }
  523. case "toggle": {
  524. return {};
  525. }
  526. default: {
  527. return {};
  528. }
  529. }
  530. }
  531. window.interpreter.serializeIpcMessage = function (method, params = {}) {
  532. return JSON.stringify({ method, params });
  533. }
  534. function is_element_node(node) {
  535. return node.nodeType == 1;
  536. }
  537. function event_bubbles(event) {
  538. switch (event) {
  539. case "copy":
  540. return true;
  541. case "cut":
  542. return true;
  543. case "paste":
  544. return true;
  545. case "compositionend":
  546. return true;
  547. case "compositionstart":
  548. return true;
  549. case "compositionupdate":
  550. return true;
  551. case "keydown":
  552. return true;
  553. case "keypress":
  554. return true;
  555. case "keyup":
  556. return true;
  557. case "focus":
  558. return false;
  559. case "focusout":
  560. return true;
  561. case "focusin":
  562. return true;
  563. case "blur":
  564. return false;
  565. case "change":
  566. return true;
  567. case "input":
  568. return true;
  569. case "invalid":
  570. return true;
  571. case "reset":
  572. return true;
  573. case "submit":
  574. return true;
  575. case "click":
  576. return true;
  577. case "contextmenu":
  578. return true;
  579. case "doubleclick":
  580. return true;
  581. case "dblclick":
  582. return true;
  583. case "drag":
  584. return true;
  585. case "dragend":
  586. return true;
  587. case "dragenter":
  588. return false;
  589. case "dragexit":
  590. return false;
  591. case "dragleave":
  592. return true;
  593. case "dragover":
  594. return true;
  595. case "dragstart":
  596. return true;
  597. case "drop":
  598. return true;
  599. case "mousedown":
  600. return true;
  601. case "mouseenter":
  602. return false;
  603. case "mouseleave":
  604. return false;
  605. case "mousemove":
  606. return true;
  607. case "mouseout":
  608. return true;
  609. case "scroll":
  610. return false;
  611. case "mouseover":
  612. return true;
  613. case "mouseup":
  614. return true;
  615. case "pointerdown":
  616. return true;
  617. case "pointermove":
  618. return true;
  619. case "pointerup":
  620. return true;
  621. case "pointercancel":
  622. return true;
  623. case "gotpointercapture":
  624. return true;
  625. case "lostpointercapture":
  626. return true;
  627. case "pointerenter":
  628. return false;
  629. case "pointerleave":
  630. return false;
  631. case "pointerover":
  632. return true;
  633. case "pointerout":
  634. return true;
  635. case "select":
  636. return true;
  637. case "touchcancel":
  638. return true;
  639. case "touchend":
  640. return true;
  641. case "touchmove":
  642. return true;
  643. case "touchstart":
  644. return true;
  645. case "wheel":
  646. return true;
  647. case "abort":
  648. return false;
  649. case "canplay":
  650. return false;
  651. case "canplaythrough":
  652. return false;
  653. case "durationchange":
  654. return false;
  655. case "emptied":
  656. return false;
  657. case "encrypted":
  658. return true;
  659. case "ended":
  660. return false;
  661. case "error":
  662. return false;
  663. case "loadeddata":
  664. case "loadedmetadata":
  665. case "loadstart":
  666. case "load":
  667. return false;
  668. case "pause":
  669. return false;
  670. case "play":
  671. return false;
  672. case "playing":
  673. return false;
  674. case "progress":
  675. return false;
  676. case "ratechange":
  677. return false;
  678. case "seeked":
  679. return false;
  680. case "seeking":
  681. return false;
  682. case "stalled":
  683. return false;
  684. case "suspend":
  685. return false;
  686. case "timeupdate":
  687. return false;
  688. case "volumechange":
  689. return false;
  690. case "waiting":
  691. return false;
  692. case "animationstart":
  693. return true;
  694. case "animationend":
  695. return true;
  696. case "animationiteration":
  697. return true;
  698. case "transitionend":
  699. return true;
  700. case "toggle":
  701. return true;
  702. case "mounted":
  703. return false;
  704. }
  705. return true;
  706. }