interpreter.js 17 KB

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