Jonathan Kelley пре 1 година
родитељ
комит
2995647e99
2 измењених фајлова са 20 додато и 567 уклоњено
  1. 19 10
      packages/interpreter/build.rs
  2. 1 557
      packages/interpreter/src/js/hash.txt

+ 19 - 10
packages/interpreter/build.rs

@@ -8,7 +8,7 @@ fn main() {
     let hash = hash_ts_files();
 
     // If the hash matches the one on disk, we're good and don't need to update bindings
-    let expected = include_str!("./src/js/hash.txt").trim();
+    let expected = include_str!("src/js/hash.txt").trim();
     if expected == hash.to_string() {
         return;
     }
@@ -21,23 +21,32 @@ fn main() {
     gen_bindings("native", "native");
     gen_bindings("core", "core");
 
-    std::fs::write("src/js/hash.txt", hash).unwrap();
+    std::fs::write("src/js/hash.txt", hash.to_string()).unwrap();
 }
 
 /// Hashes the contents of a directory
-fn hash_ts_files() -> String {
-    let mut out = "".to_string();
+fn hash_ts_files() -> u128 {
+    let mut out = 0;
 
-    let files = &[
-        include_str!("./src/ts/common.ts"),
-        include_str!("./src/ts/native.ts"),
-        include_str!("./src/ts/core.ts"),
+    let files = [
+        include_str!("src/ts/common.ts"),
+        include_str!("src/ts/native.ts"),
+        include_str!("src/ts/core.ts"),
     ];
 
+    // Let's make the dumbest hasher by summing the bytes of the files
+    // The location is multiplied by the byte value to make sure that the order of the bytes matters
+    let mut idx = 0;
     for file in files {
-        out = format!("{out}{file}");
+        // windows + git does a weird thing with line endings, so we need to normalize them
+        for line in file.lines() {
+            idx += 1;
+            for byte in line.bytes() {
+                idx += 1;
+                out += (byte as u128) * (idx as u128);
+            }
+        }
     }
-
     out
 }
 

+ 1 - 557
packages/interpreter/src/js/hash.txt

@@ -1,557 +1 @@
-export { setAttributeInner } from "./set_attribute";
-export { retrieveFormValues } from "./form";
-// This file provides an extended variant of the interpreter used for desktop and liveview interaction
-//
-// This file lives on the renderer, not the host. It's basically a polyfill over functionality that the host can't
-// provide since it doesn't have access to the dom.
-
-import { BaseInterpreter, NodeId } from "./core";
-import { SerializedEvent, serializeEvent } from "./serialize";
-
-// okay so, we've got this JSChannel thing from sledgehammer, implicitly imported into our scope
-// we want to extend it, and it technically extends base intepreter. To make typescript happy,
-// we're going to bind the JSChannel_ object to the JSChannel object, and then extend it
-var JSChannel_: typeof BaseInterpreter;
-
-// @ts-ignore - this is coming from the host
-if (RawInterpreter !== undefined && RawInterpreter !== null) {
-  // @ts-ignore - this is coming from the host
-  JSChannel_ = RawInterpreter;
-};
-
-export class NativeInterpreter extends JSChannel_ {
-  intercept_link_redirects: boolean;
-  ipc: any;
-  editsPath: string;
-
-  // eventually we want to remove liveview and build it into the server-side-events of fullstack
-  // however, for now we need to support it since SSE in fullstack doesn't exist yet
-  liveview: boolean;
-
-  constructor(editsPath: string) {
-    super();
-    this.editsPath = editsPath;
-  }
-
-  initialize(root: HTMLElement): void {
-    this.intercept_link_redirects = true;
-    this.liveview = false;
-
-    // attach an event listener on the body that prevents file drops from navigating
-    // this is because the browser will try to navigate to the file if it's dropped on the window
-    window.addEventListener("dragover", function (e) {
-      // // check which element is our target
-      if (e.target instanceof Element && e.target.tagName != "INPUT") {
-        e.preventDefault();
-      }
-    }, false);
-
-    window.addEventListener("drop", function (e) {
-      let target = e.target;
-
-      if (!(target instanceof Element)) {
-        return;
-      }
-
-      // Dropping a file on the window will navigate to the file, which we don't want
-      e.preventDefault();
-    }, false);
-
-    // attach a listener to the route that listens for clicks and prevents the default file dialog
-    window.addEventListener("click", (event) => {
-      const target = event.target;
-      if (target instanceof HTMLInputElement && target.getAttribute("type") === "file") {
-        // Send a message to the host to open the file dialog if the target is a file input and has a dioxus id attached to it
-        let target_id = getTargetId(target);
-        if (target_id !== null) {
-          const message = this.serializeIpcMessage("file_dialog", {
-            event: "change&input",
-            accept: target.getAttribute("accept"),
-            directory: target.getAttribute("webkitdirectory") === "true",
-            multiple: target.hasAttribute("multiple"),
-            target: target_id,
-            bubbles: event.bubbles,
-          });
-          this.ipc.postMessage(message);
-        }
-
-        // Prevent default regardless - we don't want file dialogs and we don't want the browser to navigate
-        event.preventDefault();
-      }
-    });
-
-
-    // @ts-ignore - wry gives us this
-    this.ipc = window.ipc;
-
-    // make sure we pass the handler to the base interpreter
-    const handler: EventListener = (event) => this.handleEvent(event, event.type, true);
-    super.initialize(root, handler);
-  }
-
-  serializeIpcMessage(method: string, params = {}) {
-    return JSON.stringify({ method, params });
-  }
-
-  scrollTo(id: NodeId, behavior: ScrollBehavior) {
-    const node = this.nodes[id];
-    if (node instanceof HTMLElement) {
-      node.scrollIntoView({ behavior });
-    }
-  }
-
-  getClientRect(id: NodeId): { type: string; origin: number[]; size: number[]; } | undefined {
-    const node = this.nodes[id];
-    if (node instanceof HTMLElement) {
-      const rect = node.getBoundingClientRect();
-      return {
-        type: "GetClientRect",
-        origin: [rect.x, rect.y],
-        size: [rect.width, rect.height],
-      };
-    }
-  }
-
-  setFocus(id: NodeId, focus: boolean) {
-    const node = this.nodes[id];
-
-    if (node instanceof HTMLElement) {
-      if (focus) {
-        node.focus();
-      } else {
-        node.blur();
-      }
-    }
-  }
-
-  // ignore the fact the base interpreter uses ptr + len but we use array...
-  // @ts-ignore
-  loadChild(array: number[]) {
-    // iterate through each number and get that child
-    let node = this.stack[this.stack.length - 1];
-
-    for (let i = 0; i < array.length; i++) {
-      let end = array[i];
-      for (node = node.firstChild; end > 0; end--) {
-        node = node.nextSibling;
-      }
-    }
-
-    return node;
-  }
-
-  appendChildren(id: NodeId, many: number) {
-    const root = this.nodes[id];
-    const els = this.stack.splice(this.stack.length - many);
-
-    for (let k = 0; k < many; k++) {
-      root.appendChild(els[k]);
-    }
-  }
-
-  handleEvent(event: Event, name: string, bubbles: boolean) {
-    const target = event.target!;
-    const realId = getTargetId(target)!;
-    const contents = serializeEvent(event, target);
-
-    // Handle the event on the virtualdom and then preventDefault if it also preventsDefault
-    // Some listeners
-    let body = {
-      name: name,
-      data: contents,
-      element: realId,
-      bubbles,
-    };
-
-    // Run any prevent defaults the user might've set
-    // This is to support the prevent_default: "onclick" attribute that dioxus has had for a while, but is not necessary
-    // now that we expose preventDefault to the virtualdom on desktop
-    // Liveview will still need to use this
-    this.preventDefaults(event, target);
-
-    // liveview does not have syncronous event handling, so we need to send the event to the host
-    if (this.liveview) {
-      // Okay, so the user might've requested some files to be read
-      if (target instanceof HTMLInputElement && (event.type === "change" || event.type === "input")) {
-        if (target.getAttribute("type") === "file") {
-          this.readFiles(target, contents, bubbles, realId, name);
-        }
-      }
-    } else {
-
-      const message = this.serializeIpcMessage("user_event", body);
-      this.ipc.postMessage(message);
-
-      // // Run the event handler on the virtualdom
-      // // capture/prevent default of the event if the virtualdom wants to
-      // const res = handleVirtualdomEventSync(JSON.stringify(body));
-
-      // if (res.preventDefault) {
-      //   event.preventDefault();
-      // }
-
-      // if (res.stopPropagation) {
-      //   event.stopPropagation();
-      // }
-    }
-  }
-
-
-
-  // This should:
-  // - prevent form submissions from navigating
-  // - prevent anchor tags from navigating
-  // - prevent buttons from submitting forms
-  // - let the virtualdom attempt to prevent the event
-  preventDefaults(event: Event, target: EventTarget) {
-    let preventDefaultRequests: string | null = null;
-
-    // Some events can be triggered on text nodes, which don't have attributes
-    if (target instanceof Element) {
-      preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
-    }
-
-    if (preventDefaultRequests && preventDefaultRequests.includes(`on${event.type}`)) {
-      event.preventDefault();
-    }
-
-    if (event.type === "submit") {
-      event.preventDefault();
-    }
-
-    // Attempt to intercept if the event is a click
-    if (target instanceof Element && event.type === "click") {
-      this.handleClickNavigate(event, target, preventDefaultRequests);
-    }
-  }
-
-  handleClickNavigate(event: Event, target: Element, preventDefaultRequests: string) {
-    // todo call prevent default if it's the right type of event
-    if (!this.intercept_link_redirects) {
-      return;
-    }
-
-    // prevent buttons in forms from submitting the form
-    if (target.tagName === "BUTTON" && event.type == "submit") {
-      event.preventDefault();
-    }
-
-    // If the target is an anchor tag, we want to intercept the click too, to prevent the browser from navigating
-    let a_element = target.closest("a");
-    if (a_element == null) {
-      return;
-    }
-
-    event.preventDefault();
-
-    let elementShouldPreventDefault =
-      preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
-
-    let aElementShouldPreventDefault = a_element.getAttribute(
-      `dioxus-prevent-default`
-    );
-
-    let linkShouldPreventDefault =
-      aElementShouldPreventDefault &&
-      aElementShouldPreventDefault.includes(`onclick`);
-
-    if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
-      const href = a_element.getAttribute("href");
-      if (href !== "" && href !== null && href !== undefined) {
-        this.ipc.postMessage(
-          this.serializeIpcMessage("browser_open", { href })
-        );
-      }
-    }
-  }
-
-  waitForRequest(headless: boolean) {
-    fetch(new Request(this.editsPath))
-      .then(response => response.arrayBuffer())
-      .then(bytes => {
-        // In headless mode, the requestAnimationFrame callback is never called, so we need to run the bytes directly
-        if (headless) {
-          // @ts-ignore
-          this.run_from_bytes(bytes);
-        } else {
-          // @ts-ignore
-          requestAnimationFrame(() => this.run_from_bytes(bytes));
-        }
-        this.waitForRequest(headless);
-      });
-  }
-
-
-  //  A liveview only function
-  // Desktop will intercept the event before it hits this
-  async readFiles(target: HTMLInputElement, contents: SerializedEvent, bubbles: boolean, realId: NodeId, name: string) {
-    let files = target.files!;
-    let file_contents: { [name: string]: number[] } = {};
-
-    for (let i = 0; i < files.length; i++) {
-      const file = files[i];
-      file_contents[file.name] = Array.from(
-        new Uint8Array(await file.arrayBuffer())
-      );
-    }
-
-    contents.files = { files: file_contents };
-
-    const message = this.serializeIpcMessage("user_event", {
-      name: name,
-      element: realId,
-      data: contents,
-      bubbles,
-    });
-
-    this.ipc.postMessage(message);
-  }
-}
-
-type EventSyncResult = {
-  preventDefault: boolean;
-  stopPropagation: boolean;
-  stopImmediatePropagation: boolean;
-  filesRequested: boolean;
-};
-
-// This function sends the event to the virtualdom and then waits for the virtualdom to process it
-//
-// However, it's not really suitable for liveview, because it's synchronous and will block the main thread
-// We should definitely consider using a websocket if we want to block... or just not block on liveview
-// Liveview is a little bit of a tricky beast
-function handleVirtualdomEventSync(contents: string): EventSyncResult {
-  // Handle the event on the virtualdom and then process whatever its output was
-  const xhr = new XMLHttpRequest();
-
-  // Serialize the event and send it to the custom protocol in the Rust side of things
-  xhr.timeout = 1000;
-  xhr.open("GET", "/handle/event.please", false);
-  xhr.setRequestHeader("Content-Type", "application/json");
-  xhr.send(contents);
-
-  // Deserialize the response, and then prevent the default/capture the event if the virtualdom wants to
-  return JSON.parse(xhr.responseText);
-}
-
-function getTargetId(target: EventTarget): NodeId | null {
-  // Ensure that the target is a node, sometimes it's nota
-  if (!(target instanceof Node)) {
-    return null;
-  }
-
-  let ourTarget = target;
-  let realId = null;
-
-  while (realId == null) {
-    if (ourTarget === null) {
-      return null;
-    }
-
-    if (ourTarget instanceof Element) {
-      realId = ourTarget.getAttribute(`data-dioxus-id`);
-    }
-
-    ourTarget = ourTarget.parentNode;
-  }
-
-  return parseInt(realId);
-}
-
-
-// function applyFileUpload() {
-//   let inputs = document.querySelectorAll("input");
-//   for (let input of inputs) {
-//     if (!input.getAttribute("data-dioxus-file-listener")) {
-//       // prevent file inputs from opening the file dialog on click
-//       const type = input.getAttribute("type");
-//       if (type === "file") {
-//         input.setAttribute("data-dioxus-file-listener", true);
-//         input.addEventListener("click", (event) => {
-//           let target = event.target;
-//           let target_id = find_real_id(target);
-//           if (target_id !== null) {
-//             const send = (event_name) => {
-//               const message = window.interpreter.serializeIpcMessage("file_diolog", { accept: target.getAttribute("accept"), directory: target.getAttribute("webkitdirectory") === "true", multiple: target.hasAttribute("multiple"), target: parseInt(target_id), bubbles: event_bubbles(event_name), event: event_name });
-//               window.ipc.postMessage(message);
-//             };
-//             send("change&input");
-//           }
-//           event.preventDefault();
-//         });
-//       }
-//     }
-// }
-// The root interpreter class that holds state about the mapping between DOM and VirtualDom
-// This always lives in the JS side of things, and is extended by the native and web interpreters
-
-import { setAttributeInner } from "./set_attribute";
-
-export type NodeId = number;
-
-export class BaseInterpreter {
-  // non bubbling events listen at the element the listener was created at
-  global: {
-    [key: string]: { active: number, callback: EventListener }
-  };
-  // bubbling events can listen at the root element
-  local: {
-    [key: string]: {
-      [key: string]: EventListener
-    }
-  };
-
-  root: HTMLElement;
-  handler: EventListener;
-  nodes: Node[];
-  stack: Node[];
-  templates: {
-    [key: number]: Node[]
-  };
-
-  // sledgehammer is generating this...
-  m: any;
-
-  constructor() { }
-
-  initialize(root: HTMLElement, handler: EventListener | null = null) {
-    this.global = {};
-    this.local = {};
-    this.root = root;
-
-    this.nodes = [root];
-    this.stack = [root];
-    this.templates = {};
-
-    if (handler) {
-      this.handler = handler;
-    }
-  }
-
-  createListener(event_name: string, element: HTMLElement, bubbles: boolean) {
-    if (bubbles) {
-      if (this.global[event_name] === undefined) {
-        this.global[event_name] = { active: 1, callback: this.handler };
-        this.root.addEventListener(event_name, this.handler);
-      } else {
-        this.global[event_name].active++;
-      }
-    } else {
-      const id = element.getAttribute("data-dioxus-id");
-      if (!this.local[id]) {
-        this.local[id] = {};
-      }
-      element.addEventListener(event_name, this.handler);
-    }
-  }
-
-  removeListener(element: HTMLElement, event_name: string, bubbles: boolean) {
-    if (bubbles) {
-      this.removeBubblingListener(event_name);
-    } else {
-      this.removeNonBubblingListener(element, event_name);
-    }
-  }
-
-  removeBubblingListener(event_name: string) {
-    this.global[event_name].active--;
-    if (this.global[event_name].active === 0) {
-      this.root.removeEventListener(event_name, this.global[event_name].callback);
-      delete this.global[event_name];
-    }
-  }
-
-  removeNonBubblingListener(element: HTMLElement, event_name: string) {
-    const id = element.getAttribute("data-dioxus-id");
-    delete this.local[id][event_name];
-    if (Object.keys(this.local[id]).length === 0) {
-      delete this.local[id];
-    }
-    element.removeEventListener(event_name, this.handler);
-  }
-
-  removeAllNonBubblingListeners(element: HTMLElement) {
-    const id = element.getAttribute("data-dioxus-id");
-    delete this.local[id];
-  }
-
-  getNode(id: NodeId): Node {
-    return this.nodes[id];
-  }
-
-  appendChildren(id: NodeId, many: number) {
-    const root = this.nodes[id];
-    const els = this.stack.splice(this.stack.length - many);
-    for (let k = 0; k < many; k++) {
-      root.appendChild(els[k]);
-    }
-  }
-
-  loadChild(ptr: number, len: number): Node {
-    // iterate through each number and get that child
-    let node = this.stack[this.stack.length - 1] as Node;
-    let ptr_end = ptr + len;
-
-    for (; ptr < ptr_end; ptr++) {
-      let end = this.m.getUint8(ptr);
-      for (node = node.firstChild; end > 0; end--) {
-        node = node.nextSibling;
-      }
-    }
-
-    return node;
-  }
-
-  saveTemplate(nodes: HTMLElement[], tmpl_id: number) {
-    this.templates[tmpl_id] = nodes;
-  }
-
-  hydrate(ids: { [key: number]: number }) {
-    const hydrateNodes = document.querySelectorAll('[data-node-hydration]');
-
-    for (let i = 0; i < hydrateNodes.length; i++) {
-      const hydrateNode = hydrateNodes[i] as HTMLElement;
-      const hydration = hydrateNode.getAttribute('data-node-hydration');
-      const split = hydration!.split(',');
-      const id = ids[parseInt(split[0])];
-
-      this.nodes[id] = hydrateNode;
-
-      if (split.length > 1) {
-        // @ts-ignore
-        hydrateNode.listening = split.length - 1;
-        hydrateNode.setAttribute('data-dioxus-id', id.toString());
-        for (let j = 1; j < split.length; j++) {
-          const listener = split[j];
-          const split2 = listener.split(':');
-          const event_name = split2[0];
-          const bubbles = split2[1] === '1';
-          this.createListener(event_name, hydrateNode, bubbles);
-        }
-      }
-    }
-
-    const treeWalker = document.createTreeWalker(
-      document.body,
-      NodeFilter.SHOW_COMMENT,
-    );
-
-    let currentNode = treeWalker.nextNode();
-
-    while (currentNode) {
-      const id = currentNode.textContent!;
-      const split = id.split('node-id');
-
-      if (split.length > 1) {
-        this.nodes[ids[parseInt(split[1])]] = currentNode.nextSibling;
-      }
-
-      currentNode = treeWalker.nextNode();
-    }
-  }
-
-  setAttributeInner(node: HTMLElement, field: string, value: string, ns: string) {
-    setAttributeInner(node, field, value, ns);
-  }
-}
-
+12655652627