From e8ae830cf20a8989cdc9ca549e7662c7fd6bf0b5 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Nov 2022 18:32:48 -0800 Subject: [PATCH] feat: get desktop working with just creation --- packages/core/src/create.rs | 19 +- packages/core/src/diff.rs | 2 +- packages/core/src/events.rs | 15 +- packages/core/src/mutations.rs | 113 ++- packages/core/src/nodes.rs | 24 +- packages/core/src/virtual_dom.rs | 13 +- packages/desktop/Cargo.toml | 2 +- packages/desktop/src/cfg.rs | 4 +- packages/desktop/src/controller.rs | 45 +- packages/desktop/src/desktop_context.rs | 18 +- packages/desktop/src/lib.rs | 12 +- packages/desktop/src/protocol.rs | 38 +- packages/dioxus/tests/rsx_syntax.rs | 12 +- packages/html/src/events.rs | 8 +- packages/interpreter/src/interpreter.js | 361 +++----- packages/interpreter/src/interpreter.old.js | 944 ++++++++++++++++++++ 16 files changed, 1301 insertions(+), 329 deletions(-) create mode 100644 packages/interpreter/src/interpreter.old.js diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index ccbdce71c..ac936bb8f 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -47,6 +47,7 @@ impl VirtualDom { // todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS) let mut dynamic_attrs = template.template.attr_paths.iter().enumerate().peekable(); let mut dynamic_nodes = template.template.node_paths.iter().enumerate().peekable(); + let cur_scope = self.scope_stack.last().copied().unwrap(); let mut on_stack = 0; for (root_idx, root) in template.template.roots.iter().enumerate() { @@ -78,21 +79,25 @@ impl VirtualDom { }); loop { - let attribute = &template.dynamic_attrs[attr_id]; + let attribute = template.dynamic_attrs.get(attr_id).unwrap(); attribute.mounted_element.set(id); - match attribute.value { + match &attribute.value { AttributeValue::Text(value) => mutations.push(SetAttribute { name: attribute.name, - value, + value: *value, id, }), AttributeValue::Bool(value) => mutations.push(SetBoolAttribute { name: attribute.name, - value, + value: *value, + id, + }), + AttributeValue::Listener(_) => mutations.push(NewEventListener { + event_name: attribute.name, + scope: cur_scope, id, }), - AttributeValue::Listener(_) => todo!("create listener attributes"), AttributeValue::Float(_) => todo!(), AttributeValue::Int(_) => todo!(), AttributeValue::Any(_) => todo!(), @@ -138,8 +143,8 @@ impl VirtualDom { let id = self.next_element(template); mutations.push(CreatePlaceholder { id }) } - TemplateNode::Text(value) => mutations.push(CreateText { value }), - TemplateNode::DynamicText { .. } => mutations.push(CreateText { + TemplateNode::Text(value) => mutations.push(CreateTextNode { value }), + TemplateNode::DynamicText { .. } => mutations.push(CreateTextNode { value: "placeholder", }), diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 7e9e10e44..81efd95e1 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -189,7 +189,7 @@ impl<'b> VirtualDom { (DynamicNode::Text { id: lid, .. }, right) => { let m = self.create_dynamic_node(muts, right_template, right, idx); - muts.push(Mutation::Replace { id: lid.get(), m }); + muts.push(Mutation::ReplaceWith { id: lid.get(), m }); } (DynamicNode::Placeholder(_), DynamicNode::Placeholder(_)) => todo!(), diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 1c44b727b..8bf544ce6 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -6,11 +6,18 @@ use std::{ rc::Rc, }; -pub struct UiEvent { - bubble_state: Cell, - data: Rc, +pub struct UiEvent { + pub(crate) bubble_state: Cell, + pub(crate) data: Rc, +} +impl Clone for UiEvent { + fn clone(&self) -> Self { + Self { + bubble_state: self.bubble_state.clone(), + data: self.data.clone(), + } + } } - impl UiEvent { pub fn cancel_bubble(&self) { self.bubble_state.set(false); diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 4d0c10079..5653a7fa4 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -1,4 +1,4 @@ -use crate::arena::ElementId; +use crate::{arena::ElementId, ScopeId}; #[derive(Debug)] pub struct Mutations<'a> { @@ -35,9 +35,61 @@ impl std::ops::DerefMut for Mutations<'_> { each subtree has its own numbering scheme */ -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type") +)] #[derive(Debug, PartialEq, Eq)] pub enum Mutation<'a> { + AppendChildren { + m: usize, + }, + + AssignId { + path: &'static [u8], + id: ElementId, + }, + + CreateElement { + name: &'a str, + namespace: Option<&'a str>, + id: ElementId, + }, + + CreatePlaceholder { + id: ElementId, + }, + + CreateTextNode { + value: &'a str, + }, + HydrateText { + path: &'static [u8], + value: &'a str, + id: ElementId, + }, + LoadTemplate { + name: &'static str, + index: usize, + }, + + // Take the current element and replace it with the element with the given id. + ReplaceWith { + id: ElementId, + m: usize, + }, + + ReplacePlaceholder { + m: usize, + path: &'static [u8], + }, + + SaveTemplate { + name: &'static str, + m: usize, + }, + SetAttribute { name: &'a str, value: &'a str, @@ -50,20 +102,8 @@ pub enum Mutation<'a> { id: ElementId, }, - LoadTemplate { - name: &'static str, - index: usize, - }, - - SaveTemplate { - name: &'static str, - m: usize, - }, - - HydrateText { - path: &'static [u8], + SetInnerText { value: &'a str, - id: ElementId, }, SetText { @@ -71,41 +111,24 @@ pub enum Mutation<'a> { id: ElementId, }, - ReplacePlaceholder { - m: usize, - path: &'static [u8], - }, + /// Create a new Event Listener. + NewEventListener { + /// The name of the event to listen for. + event_name: &'a str, - AssignId { - path: &'static [u8], + /// The ID of the node to attach the listener to. + scope: ScopeId, + + /// The ID of the node to attach the listener to. id: ElementId, }, - // Take the current element and replace it with the element with the given id. - Replace { + /// Remove an existing Event Listener. + RemoveEventListener { + /// The ID of the node to remove. id: ElementId, - m: usize, - }, - CreateElement { - name: &'a str, - namespace: Option<&'a str>, - id: ElementId, - }, - - SetInnerText { - value: &'a str, - }, - - CreateText { - value: &'a str, - }, - - CreatePlaceholder { - id: ElementId, - }, - - AppendChildren { - m: usize, + /// The name of the event to remove. + event: &'a str, }, } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 8fc38e34e..71be193a2 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -1,8 +1,9 @@ -use crate::{any_props::AnyProps, arena::ElementId, ScopeId, ScopeState}; +use crate::{any_props::AnyProps, arena::ElementId, ScopeId, ScopeState, UiEvent}; use std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, hash::Hasher, + rc::Rc, }; pub type TemplateId = &'static str; @@ -154,6 +155,27 @@ pub enum AttributeValue<'a> { None, } +impl<'a> AttributeValue<'a> { + pub fn new_listener( + cx: &'a ScopeState, + mut f: impl FnMut(UiEvent) + 'a, + ) -> AttributeValue<'a> { + let f = cx.bump().alloc(move |a: &dyn Any| { + a.downcast_ref::>() + .map(|a| f(a.clone())) + .unwrap_or_else(|| { + panic!( + "Expected UiEvent<{}>, got {:?}", + std::any::type_name::(), + a + ) + }) + }) as &mut dyn FnMut(&dyn Any); + + AttributeValue::Listener(RefCell::new(f)) + } +} + impl<'a> std::fmt::Debug for AttributeValue<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 373813a8c..6575de6ef 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -9,7 +9,7 @@ use crate::{ nodes::{Template, TemplateId}, scheduler::{SuspenseBoundary, SuspenseId}, scopes::{ScopeId, ScopeState}, - Attribute, AttributeValue, Element, EventPriority, Scope, SuspenseContext, + Attribute, AttributeValue, Element, EventPriority, Scope, SuspenseContext, UiEvent, }; use futures_util::{pin_mut, FutureExt, StreamExt}; use slab::Slab; @@ -288,10 +288,10 @@ impl VirtualDom { /// /// /// - pub fn handle_event( + pub fn handle_event( &mut self, name: &str, - event: &dyn Any, + data: Rc, element: ElementId, bubbles: bool, priority: EventPriority, @@ -304,6 +304,11 @@ impl VirtualDom { - send event to virtualdom */ + let event = UiEvent { + bubble_state: std::cell::Cell::new(true), + data, + }; + let path = &self.elements[element.0]; let template = unsafe { &*path.template }; let dynamic = &template.dynamic_nodes[path.element]; @@ -339,7 +344,7 @@ impl VirtualDom { for listener in listeners { if let AttributeValue::Listener(listener) = &listener.value { - (listener.borrow_mut())(event) + (listener.borrow_mut())(&event.clone()) } } diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 277f96dd3..fe13a6f8c 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -20,7 +20,7 @@ serde = "1.0.136" serde_json = "1.0.79" thiserror = "1.0.30" log = "0.4.14" -wry = { version = "0.20.2" } +wry = { version = "0.22.0" } futures-channel = "0.3.21" tokio = { version = "1.16.1", features = [ "sync", diff --git a/packages/desktop/src/cfg.rs b/packages/desktop/src/cfg.rs index 85be2727f..060e95ce2 100644 --- a/packages/desktop/src/cfg.rs +++ b/packages/desktop/src/cfg.rs @@ -27,7 +27,7 @@ type DropHandler = Box bool>; pub(crate) type WryProtocol = ( String, - Box WryResult + 'static>, + Box>) -> WryResult>> + 'static>, ); impl Config { @@ -96,7 +96,7 @@ impl Config { /// Set a custom protocol pub fn with_custom_protocol(mut self, name: String, handler: F) -> Self where - F: Fn(&HttpRequest) -> WryResult + 'static, + F: Fn(&HttpRequest>) -> WryResult>> + 'static, { self.protocols.push((name, Box::new(handler))); self diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index edbc8c0c4..d69ee5a1c 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -40,24 +40,23 @@ impl DesktopController { .unwrap(); runtime.block_on(async move { + println!("starting vdom"); + let mut dom = VirtualDom::new_with_props(root, props); let window_context = DesktopContext::new(desktop_context_proxy); dom.base_scope().provide_context(window_context); - // // allow other proccesses to send the new rsx text to the @dioxusin ipc channel and recieve erros on the @dioxusout channel - // #[cfg(any(feature = "hot-reload", debug_assertions))] - // crate::hot_reload::init(&dom); - let edits = dom.rebuild(); - let mut queue = edit_queue.lock().unwrap(); - queue.push(serde_json::to_string(&edits.template_mutations).unwrap()); - queue.push(serde_json::to_string(&edits.edits).unwrap()); - - // Make sure the window is ready for any new updates - proxy.send_event(UserWindowEvent::Update).unwrap(); + { + let mut queue = edit_queue.lock().unwrap(); + queue.push(serde_json::to_string(&edits.template_mutations).unwrap()); + queue.push(serde_json::to_string(&edits.edits).unwrap()); + proxy.send_event(UserWindowEvent::Update).unwrap(); + drop(queue); + } loop { // todo: add the channel of the event loop in @@ -71,14 +70,18 @@ impl DesktopController { )) .await; - let mut queue = edit_queue.lock().unwrap(); + { + let mut queue = edit_queue.lock().unwrap(); - for edit in muts.template_mutations { - queue.push(serde_json::to_string(&edit).unwrap()); - } + for edit in muts.template_mutations { + queue.push(serde_json::to_string(&edit).unwrap()); + } - for edit in muts.edits { - queue.push(serde_json::to_string(&edit).unwrap()); + for edit in muts.edits { + queue.push(serde_json::to_string(&edit).unwrap()); + } + + drop(queue); } let _ = proxy.send_event(UserWindowEvent::Update); @@ -104,10 +107,16 @@ impl DesktopController { pub(super) fn try_load_ready_webviews(&mut self) { if self.is_ready.load(std::sync::atomic::Ordering::Relaxed) { - let mut queue = self.pending_edits.lock().unwrap(); + let mut new_queue = Vec::new(); + + { + let mut queue = self.pending_edits.lock().unwrap(); + std::mem::swap(&mut new_queue, &mut *queue); + } + let (_id, view) = self.webviews.iter_mut().next().unwrap(); - for edit in queue.drain(..) { + for edit in new_queue.drain(..) { view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit)) .unwrap(); } diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 89d0e2fe4..43f2b5f15 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use crate::controller::DesktopController; use dioxus_core::ScopeState; use wry::application::event_loop::ControlFlow; @@ -15,6 +17,13 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext { .unwrap() } +/// Get a closure that executes any JavaScript in the WebView context. +pub fn use_eval(cx: &ScopeState) -> &Rc { + let desktop = use_window(cx).clone(); + + &*cx.use_hook(|| Rc::new(move |script| desktop.eval(script)) as Rc) +} + /// An imperative interface to the current window. /// /// To get a handle to the current window, use the [`use_window`] hook. @@ -176,6 +185,8 @@ pub(super) fn handler( let webview = desktop.webviews.values().next().unwrap(); let window = webview.window(); + println!("user_event: {:?}", user_event); + match user_event { Update => desktop.try_load_ready_webviews(), CloseWindow => *control_flow = ControlFlow::Exit, @@ -227,10 +238,3 @@ pub(super) fn handler( } } } - -/// Get a closure that executes any JavaScript in the WebView context. -pub fn use_eval(cx: &ScopeState) -> &dyn Fn(S) { - let desktop = use_window(cx).clone(); - - cx.use_hook(|| move |script| desktop.eval(script)) -} diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index a19633b8a..d0d9322a3 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -105,19 +105,20 @@ pub fn launch_with_props(root: Component

, props: P, mut cf // We assume that if the icon is None, then the user just didnt set it if cfg.window.window.window_icon.is_none() { - cfg.window.window.window_icon = Some( + cfg.window = cfg.window.with_window_icon(Some( tao::window::Icon::from_rgba( include_bytes!("./assets/default_icon.bin").to_vec(), 460, 460, ) .expect("image parse failed"), - ); + )); } event_loop.run(move |window_event, event_loop, control_flow| { *control_flow = ControlFlow::Wait; + // println!("window event: {:?}", window_event); match window_event { Event::NewEvents(StartCause::Init) => { let builder = cfg.window.clone(); @@ -151,6 +152,7 @@ pub fn launch_with_props(root: Component

, props: P, mut cf } "initialize" => { is_ready.store(true, std::sync::atomic::Ordering::Relaxed); + println!("initializing..."); let _ = proxy.send_event(UserWindowEvent::Update); } "browser_open" => { @@ -179,6 +181,12 @@ pub fn launch_with_props(root: Component

, props: P, mut cf index_file.clone(), ) }) + // passing edits via the custom protocol is faster than using eval, maybe? + .with_custom_protocol(String::from("edits"), move |r| { + // + // Ok(Response::body()) + todo!() + }) .with_file_drop_handler(move |window, evet| { file_handler .as_ref() diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index 1c8aaa54f..b8e5cc341 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; use wry::{ - http::{status::StatusCode, Request, Response, ResponseBuilder}, + http::{status::StatusCode, Request, Response}, Result, }; @@ -13,14 +13,14 @@ const MODULE_LOADER: &str = r#" "#; pub(super) fn desktop_handler( - request: &Request, + request: &Request>, asset_root: Option, custom_head: Option, custom_index: Option, -) -> Result { +) -> Result>> { // Any content that uses the `dioxus://` scheme will be shuttled through this handler as a "special case". // For now, we only serve two pieces of content which get included as bytes into the final binary. - let path = request.uri().replace("dioxus://", ""); + let path = request.uri().to_string().replace("dioxus://", ""); // all assets should be called from index.html let trimmed = path.trim_start_matches("index.html/"); @@ -32,7 +32,10 @@ pub(super) fn desktop_handler( let rendered = custom_index .replace("", &format!("{}", MODULE_LOADER)) .into_bytes(); - ResponseBuilder::new().mimetype("text/html").body(rendered) + Response::builder() + .header("Content-Type", "text/html") + .body(rendered) + .map_err(From::from) } else { // Otherwise, we'll serve the default index.html and apply a custom head if that's specified. let mut template = include_str!("./index.html").to_string(); @@ -41,14 +44,16 @@ pub(super) fn desktop_handler( } template = template.replace("", MODULE_LOADER); - ResponseBuilder::new() - .mimetype("text/html") + Response::builder() + .header("Content-Type", "text/html") .body(template.into_bytes()) + .map_err(From::from) } } else if trimmed == "index.js" { - ResponseBuilder::new() - .mimetype("text/javascript") + Response::builder() + .header("Content-Type", "text/javascript") .body(dioxus_interpreter_js::INTERPRETER_JS.as_bytes().to_vec()) + .map_err(From::from) } else { let asset_root = asset_root .unwrap_or_else(|| get_asset_root().unwrap_or_else(|| Path::new(".").to_path_buf())) @@ -57,20 +62,23 @@ pub(super) fn desktop_handler( let asset = asset_root.join(trimmed).canonicalize()?; if !asset.starts_with(asset_root) { - return ResponseBuilder::new() + return Response::builder() .status(StatusCode::FORBIDDEN) - .body(String::from("Forbidden").into_bytes()); + .body(String::from("Forbidden").into_bytes()) + .map_err(From::from); } if !asset.exists() { - return ResponseBuilder::new() + return Response::builder() .status(StatusCode::NOT_FOUND) - .body(String::from("Not Found").into_bytes()); + .body(String::from("Not Found").into_bytes()) + .map_err(From::from); } - ResponseBuilder::new() - .mimetype(get_mime_from_path(trimmed)?) + Response::builder() + .header("Content-Type", get_mime_from_path(trimmed)?) .body(std::fs::read(asset)?) + .map_err(From::from) } } diff --git a/packages/dioxus/tests/rsx_syntax.rs b/packages/dioxus/tests/rsx_syntax.rs index 9c5440508..4401e1ed0 100644 --- a/packages/dioxus/tests/rsx_syntax.rs +++ b/packages/dioxus/tests/rsx_syntax.rs @@ -42,25 +42,25 @@ fn dual_stream() { AppendChildren { m: 1 }, CreateElement { name: "div", namespace: None, id: ElementId(4) }, CreateElement { name: "h1", namespace: None, id: ElementId(5) }, - CreateText { value: "var" }, + CreateTextNode { value: "var" }, AppendChildren { m: 1 }, CreateElement { name: "p", namespace: None, id: ElementId(6) }, - CreateText { value: "you're great!" }, + CreateTextNode { value: "you're great!" }, AppendChildren { m: 1 }, CreateElement { name: "div", namespace: None, id: ElementId(7) }, SetAttribute { name: "background-color", value: "red", id: ElementId(7) }, CreateElement { name: "h1", namespace: None, id: ElementId(8) }, - CreateText { value: "var" }, + CreateTextNode { value: "var" }, AppendChildren { m: 1 }, CreateElement { name: "div", namespace: None, id: ElementId(9) }, CreateElement { name: "b", namespace: None, id: ElementId(10) }, - CreateText { value: "asd" }, + CreateTextNode { value: "asd" }, AppendChildren { m: 1 }, - CreateText { value: "not great" }, + CreateTextNode { value: "not great" }, AppendChildren { m: 2 }, AppendChildren { m: 2 }, CreateElement { name: "p", namespace: None, id: ElementId(11) }, - CreateText { value: "you're great!" }, + CreateTextNode { value: "you're great!" }, AppendChildren { m: 1 }, AppendChildren { m: 4 }, AppendChildren { m: 2 }, diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 303d27a99..a9705ef9c 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -12,7 +12,13 @@ macro_rules! impl_event { $( $( #[$attr] )* pub fn $name<'a>(_cx: &'a ::dioxus_core::ScopeState, _f: impl FnMut(::dioxus_core::UiEvent<$data>) + 'a) -> ::dioxus_core::Attribute<'a> { - todo!() + ::dioxus_core::Attribute { + name: stringify!($name), + value: ::dioxus_core::AttributeValue::new_listener(_cx, _f), + namespace: None, + mounted_element: Default::default(), + volatile: false, + } } )* }; diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 9e95fc991..34ac09ad9 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -53,160 +53,110 @@ class ListenerMap { element.removeEventListener(event_name, handler); } } + + removeAllNonBubbling(element) { + const id = element.getAttribute("data-dioxus-id"); + delete this.local[id]; + } } export class Interpreter { constructor(root) { this.root = root; - this.lastNode = root; + this.stack = [root]; this.listeners = new ListenerMap(root); this.handlers = {}; + this.lastNodeWasText = false; this.nodes = [root]; - this.parents = []; + this.templates = {}; } - checkAppendParent() { - if (this.parents.length > 0) { - const lastParent = this.parents[this.parents.length - 1]; - lastParent[1]--; - if (lastParent[1] === 0) { - this.parents.pop(); - } - lastParent[0].appendChild(this.lastNode); + top() { + return this.stack[this.stack.length - 1]; + } + pop() { + return this.stack.pop(); + } + SetNode(id, node) { + this.nodes[id] = node; + } + PushRoot(root) { + const node = this.nodes[root]; + this.stack.push(node); + } + PopRoot() { + this.stack.pop(); + } + AppendChildren(many) { + let root = this.stack[this.stack.length - (1 + many)]; + let to_add = this.stack.splice(this.stack.length - many); + for (let i = 0; i < many; i++) { + root.appendChild(to_add[i]); } } - AppendChildren(root, children) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - for (let i = 0; i < children.length; i++) { - node.appendChild(this.nodes[children[i]]); + ReplaceWith(root_id, m) { + let root = this.nodes[root_id]; + let els = this.stack.splice(this.stack.length - m); + if (is_element_node(root.nodeType)) { + this.listeners.removeAllNonBubbling(root); } + root.replaceWith(...els); } - ReplaceWith(root, nodes) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - let els = []; - for (let i = 0; i < nodes.length; i++) { - els.push(this.nodes[nodes[i]]) - } - node.replaceWith(...els); + InsertAfter(root, n) { + let old = this.nodes[root]; + let new_nodes = this.stack.splice(this.stack.length - n); + old.after(...new_nodes); } - InsertAfter(root, nodes) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - let els = []; - for (let i = 0; i < nodes.length; i++) { - els.push(this.nodes[nodes[i]]) - } - node.after(...els); - } - InsertBefore(root, nodes) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - let els = []; - for (let i = 0; i < nodes.length; i++) { - els.push(this.nodes[nodes[i]]) - } - node.before(...els); + InsertBefore(root, n) { + let old = this.nodes[root]; + let new_nodes = this.stack.splice(this.stack.length - n); + old.before(...new_nodes); } Remove(root) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } + let node = this.nodes[root]; if (node !== undefined) { + if (is_element_node(node)) { + this.listeners.removeAllNonBubbling(node); + } node.remove(); } } CreateTextNode(text, root) { - this.lastNode = document.createTextNode(text); - this.checkAppendParent(); - if (root != null) { - this.nodes[root] = this.lastNode; - } + const node = document.createTextNode(text); + this.nodes[root] = node; + this.stack.push(node); } - CreateElement(tag, root, children) { - this.lastNode = document.createElement(tag); - this.checkAppendParent(); - if (root != null) { - this.nodes[root] = this.lastNode; - } - if (children > 0) { - this.parents.push([this.lastNode, children]); - } + CreateElement(tag, root) { + const el = document.createElement(tag); + this.nodes[root] = el; + this.stack.push(el); } - CreateElementNs(tag, root, ns, children) { - this.lastNode = document.createElementNS(ns, tag); - this.checkAppendParent(); - if (root != null) { - this.nodes[root] = this.lastNode; - } - if (children > 0) { - this.parents.push([this.lastNode, children]); - } + CreateElementNs(tag, root, ns) { + let el = document.createElementNS(ns, tag); + this.stack.push(el); + this.nodes[root] = el; } CreatePlaceholder(root) { - this.lastNode = document.createElement("pre"); - this.lastNode.hidden = true; - this.checkAppendParent(); - if (root != null) { - this.nodes[root] = this.lastNode; - } + let el = document.createElement("pre"); + el.hidden = true; + this.stack.push(el); + this.nodes[root] = el; } NewEventListener(event_name, root, handler, bubbles) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - node.setAttribute("data-dioxus-id", `${root}`); - this.listeners.create(event_name, node, handler, bubbles); + const element = this.nodes[root]; + element.setAttribute("data-dioxus-id", `${root}`); + this.listeners.create(event_name, element, handler, bubbles); } RemoveEventListener(root, event_name, bubbles) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - node.removeAttribute(`data-dioxus-id`); - this.listeners.remove(node, event_name, bubbles); + const element = this.nodes[root]; + element.removeAttribute(`data-dioxus-id`); + this.listeners.remove(element, event_name, bubbles); } SetText(root, text) { - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } - node.data = text; + this.nodes[root].textContent = text; } SetAttribute(root, field, value, ns) { const name = field; - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } + const node = this.nodes[root]; if (ns === "style") { // @ts-ignore node.style[name] = value; @@ -240,12 +190,7 @@ export class Interpreter { } RemoveAttribute(root, field, ns) { const name = field; - let node; - if (root == null) { - node = this.lastNode; - } else { - node = this.nodes[root]; - } + const node = this.nodes[root]; if (ns == "style") { node.style.removeProperty(name); } else if (ns !== null || ns !== undefined) { @@ -262,87 +207,95 @@ export class Interpreter { node.removeAttribute(name); } } - CloneNode(old, new_id) { - let node; - if (old === null) { - node = this.lastNode; - } else { - node = this.nodes[old]; - } - this.nodes[new_id] = node.cloneNode(true); - } - CloneNodeChildren(old, new_ids) { - let node; - if (old === null) { - node = this.lastNode; - } else { - node = this.nodes[old]; - } - const old_node = node.cloneNode(true); - let i = 0; - for (let node = old_node.firstChild; i < new_ids.length; node = node.nextSibling) { - this.nodes[new_ids[i++]] = node; - } - } - FirstChild() { - this.lastNode = this.lastNode.firstChild; - } - NextSibling() { - this.lastNode = this.lastNode.nextSibling; - } - ParentNode() { - this.lastNode = this.lastNode.parentNode; - } - StoreWithId(id) { - this.nodes[id] = this.lastNode; - } - SetLastNode(root) { - this.lastNode = this.nodes[root]; - } handleEdits(edits) { + console.log("handling edits", edits); + for (let edit of edits) { this.handleEdit(edit); + console.log(this.stack); } + + console.log(this.stack); + } + AssignId(path, id) { + this.nodes[id] = this.LoadChild(path); + } + LoadChild(path) { + // iterate through each number and get that child + let node = this.stack[this.stack.length - 1]; + + for (let i = 0; i < path.length; i++) { + node = node.childNodes[path[i]]; + } + + return node; + } + HydrateText(path, value, id) { + let node = this.LoadChild(path); + node.textContent = value; + this.nodes[id] = node; + } + LoadTemplate(name, index) { + console.log("loading template", name, index); + let node = this.templates[name][index].cloneNode(true); + this.stack.push(node); + } + SaveTemplate(name, m) { + this.templates[name] = this.stack.splice(-m); } handleEdit(edit) { + console.log("handling edit", edit); switch (edit.type) { - case "PushRoot": - this.PushRoot(edit.root); - break; case "AppendChildren": - this.AppendChildren(edit.root, edit.children); + this.AppendChildren(edit.m); break; - case "ReplaceWith": - this.ReplaceWith(edit.root, edit.nodes); - break; - case "InsertAfter": - this.InsertAfter(edit.root, edit.nodes); - break; - case "InsertBefore": - this.InsertBefore(edit.root, edit.nodes); - break; - case "Remove": - this.Remove(edit.root); - break; - case "CreateTextNode": - this.CreateTextNode(edit.text, edit.root); + case "AssignId": + this.AssignId(edit.path, edit.id); break; case "CreateElement": - this.CreateElement(edit.tag, edit.root, edit.children); - break; - case "CreateElementNs": - this.CreateElementNs(edit.tag, edit.root, edit.ns, edit.children); + this.CreateElement(edit.name, edit.id); break; case "CreatePlaceholder": - this.CreatePlaceholder(edit.root); + this.CreatePlaceholder(edit.id); + break; + case "CreateTextNode": + this.CreateTextNode(edit.value); + break; + case "HydrateText": + this.HydrateText(edit.path, edit.value, edit.id); + break; + case "PushRoot": + this.PushRoot(edit.id); + break; + case "ReplaceWith": + this.ReplaceWith(edit.id, edit.m); + break; + case "InsertAfter": + this.InsertAfter(edit.id, edit.n); + break; + case "InsertBefore": + this.InsertBefore(edit.id, edit.n); + break; + case "Remove": + this.Remove(edit.id); + break; + case "LoadTemplate": + this.LoadTemplate(edit.name, edit.index); + break; + case "SaveTemplate": + this.SaveTemplate(edit.name, edit.m); + break; + case "CreateElementNs": + this.CreateElementNs(edit.tag, edit.id, edit.ns); break; case "RemoveEventListener": - this.RemoveEventListener(edit.root, edit.event_name); + this.RemoveEventListener(edit.id, edit.event_name); break; case "NewEventListener": // this handler is only provided on desktop implementations since this // method is not used by the web implementation let handler = (event) => { + let target = event.target; if (target != null) { let realId = target.getAttribute(`data-dioxus-id`); @@ -421,48 +374,26 @@ export class Interpreter { if (realId === null) { return; } - realId = parseInt(realId); window.ipc.postMessage( serializeIpcMessage("user_event", { event: edit.event_name, - mounted_dom_id: realId, + mounted_dom_id: parseInt(realId), contents: contents, }) ); } }; - this.NewEventListener(edit.event_name, edit.root, handler, event_bubbles(edit.event_name)); + this.NewEventListener(edit.event_name, edit.id, handler, event_bubbles(edit.event_name)); break; case "SetText": - this.SetText(edit.root, edit.text); + this.SetText(edit.id, edit.text); break; case "SetAttribute": - this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); + this.SetAttribute(edit.id, edit.field, edit.value, edit.ns); break; case "RemoveAttribute": - this.RemoveAttribute(edit.root, edit.name, edit.ns); - break; - case "CloneNode": - this.CloneNode(edit.id, edit.new_id); - break; - case "CloneNodeChildren": - this.CloneNodeChildren(edit.id, edit.new_ids); - break; - case "FirstChild": - this.FirstChild(); - break; - case "NextSibling": - this.NextSibling(); - break; - case "ParentNode": - this.ParentNode(); - break; - case "StoreWithId": - this.StoreWithId(BigInt(edit.id)); - break; - case "SetLastNode": - this.SetLastNode(BigInt(edit.id)); + this.RemoveAttribute(edit.id, edit.name, edit.ns); break; } } @@ -941,4 +872,4 @@ function event_bubbles(event) { case "toggle": return true; } -} \ No newline at end of file +} diff --git a/packages/interpreter/src/interpreter.old.js b/packages/interpreter/src/interpreter.old.js new file mode 100644 index 000000000..a12639053 --- /dev/null +++ b/packages/interpreter/src/interpreter.old.js @@ -0,0 +1,944 @@ +export function main() { + let root = window.document.getElementById("main"); + if (root != null) { + window.interpreter = new Interpreter(root); + window.ipc.postMessage(serializeIpcMessage("initialize")); + } +} + +class ListenerMap { + constructor(root) { + // bubbling events can listen at the root element + this.global = {}; + // non bubbling events listen at the element the listener was created at + this.local = {}; + this.root = root; + } + + create(event_name, element, handler, bubbles) { + if (bubbles) { + if (this.global[event_name] === undefined) { + this.global[event_name] = {}; + this.global[event_name].active = 1; + this.global[event_name].callback = handler; + this.root.addEventListener(event_name, handler); + } else { + this.global[event_name].active++; + } + } + else { + const id = element.getAttribute("data-dioxus-id"); + if (!this.local[id]) { + this.local[id] = {}; + } + this.local[id][event_name] = handler; + element.addEventListener(event_name, handler); + } + } + + remove(element, event_name, bubbles) { + if (bubbles) { + 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]; + } + } + else { + const id = element.getAttribute("data-dioxus-id"); + delete this.local[id][event_name]; + if (this.local[id].length === 0) { + delete this.local[id]; + } + element.removeEventListener(event_name, handler); + } + } +} + +export class Interpreter { + constructor(root) { + this.root = root; + this.lastNode = root; + this.listeners = new ListenerMap(root); + this.handlers = {}; + this.nodes = [root]; + this.parents = []; + } + checkAppendParent() { + if (this.parents.length > 0) { + const lastParent = this.parents[this.parents.length - 1]; + lastParent[1]--; + if (lastParent[1] === 0) { + this.parents.pop(); + } + lastParent[0].appendChild(this.lastNode); + } + } + AppendChildren(root, children) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + for (let i = 0; i < children.length; i++) { + node.appendChild(this.nodes[children[i]]); + } + } + ReplaceWith(root, nodes) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + let els = []; + for (let i = 0; i < nodes.length; i++) { + els.push(this.nodes[nodes[i]]) + } + node.replaceWith(...els); + } + InsertAfter(root, nodes) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + let els = []; + for (let i = 0; i < nodes.length; i++) { + els.push(this.nodes[nodes[i]]) + } + node.after(...els); + } + InsertBefore(root, nodes) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + let els = []; + for (let i = 0; i < nodes.length; i++) { + els.push(this.nodes[nodes[i]]) + } + node.before(...els); + } + Remove(root) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + if (node !== undefined) { + node.remove(); + } + } + CreateTextNode(text, root) { + this.lastNode = document.createTextNode(text); + this.checkAppendParent(); + if (root != null) { + this.nodes[root] = this.lastNode; + } + } + CreateElement(tag, root, children) { + this.lastNode = document.createElement(tag); + this.checkAppendParent(); + if (root != null) { + this.nodes[root] = this.lastNode; + } + if (children > 0) { + this.parents.push([this.lastNode, children]); + } + } + CreateElementNs(tag, root, ns, children) { + this.lastNode = document.createElementNS(ns, tag); + this.checkAppendParent(); + if (root != null) { + this.nodes[root] = this.lastNode; + } + if (children > 0) { + this.parents.push([this.lastNode, children]); + } + } + CreatePlaceholder(root) { + this.lastNode = document.createElement("pre"); + this.lastNode.hidden = true; + this.checkAppendParent(); + if (root != null) { + this.nodes[root] = this.lastNode; + } + } + NewEventListener(event_name, root, handler, bubbles) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + node.setAttribute("data-dioxus-id", `${root}`); + this.listeners.create(event_name, node, handler, bubbles); + } + RemoveEventListener(root, event_name, bubbles) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + node.removeAttribute(`data-dioxus-id`); + this.listeners.remove(node, event_name, bubbles); + } + SetText(root, text) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + node.data = text; + } + SetAttribute(root, field, value, ns) { + const name = field; + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + if (ns === "style") { + // @ts-ignore + node.style[name] = value; + } else if (ns != null || ns != undefined) { + node.setAttributeNS(ns, name, value); + } else { + switch (name) { + case "value": + if (value !== node.value) { + node.value = value; + } + break; + case "checked": + node.checked = value === "true"; + break; + case "selected": + node.selected = value === "true"; + break; + case "dangerous_inner_html": + node.innerHTML = value; + break; + default: + // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 + if (value === "false" && bool_attrs.hasOwnProperty(name)) { + node.removeAttribute(name); + } else { + node.setAttribute(name, value); + } + } + } + } + RemoveAttribute(root, field, ns) { + const name = field; + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + if (ns == "style") { + node.style.removeProperty(name); + } else if (ns !== null || ns !== undefined) { + node.removeAttributeNS(ns, name); + } else if (name === "value") { + node.value = ""; + } else if (name === "checked") { + node.checked = false; + } else if (name === "selected") { + node.selected = false; + } else if (name === "dangerous_inner_html") { + node.innerHTML = ""; + } else { + node.removeAttribute(name); + } + } + CloneNode(old, new_id) { + let node; + if (old === null) { + node = this.lastNode; + } else { + node = this.nodes[old]; + } + this.nodes[new_id] = node.cloneNode(true); + } + CloneNodeChildren(old, new_ids) { + let node; + if (old === null) { + node = this.lastNode; + } else { + node = this.nodes[old]; + } + const old_node = node.cloneNode(true); + let i = 0; + for (let node = old_node.firstChild; i < new_ids.length; node = node.nextSibling) { + this.nodes[new_ids[i++]] = node; + } + } + FirstChild() { + this.lastNode = this.lastNode.firstChild; + } + NextSibling() { + this.lastNode = this.lastNode.nextSibling; + } + ParentNode() { + this.lastNode = this.lastNode.parentNode; + } + StoreWithId(id) { + this.nodes[id] = this.lastNode; + } + SetLastNode(root) { + this.lastNode = this.nodes[root]; + } + handleEdits(edits) { + for (let edit of edits) { + this.handleEdit(edit); + } + } + handleEdit(edit) { + switch (edit.type) { + case "PushRoot": + this.PushRoot(edit.root); + break; + case "AppendChildren": + this.AppendChildren(edit.root, edit.children); + break; + case "ReplaceWith": + this.ReplaceWith(edit.root, edit.nodes); + break; + case "InsertAfter": + this.InsertAfter(edit.root, edit.nodes); + break; + case "InsertBefore": + this.InsertBefore(edit.root, edit.nodes); + break; + case "Remove": + this.Remove(edit.root); + break; + case "CreateTextNode": + this.CreateTextNode(edit.text, edit.root); + break; + case "CreateElement": + this.CreateElement(edit.tag, edit.root, edit.children); + break; + case "CreateElementNs": + this.CreateElementNs(edit.tag, edit.root, edit.ns, edit.children); + break; + case "CreatePlaceholder": + this.CreatePlaceholder(edit.root); + break; + case "RemoveEventListener": + this.RemoveEventListener(edit.root, edit.event_name); + break; + case "NewEventListener": + // this handler is only provided on desktop implementations since this + // method is not used by the web implementation + let handler = (event) => { + let target = event.target; + if (target != null) { + let realId = target.getAttribute(`data-dioxus-id`); + let shouldPreventDefault = target.getAttribute( + `dioxus-prevent-default` + ); + + if (event.type === "click") { + // todo call prevent default if it's the right type of event + if (shouldPreventDefault !== `onclick`) { + if (target.tagName === "A") { + event.preventDefault(); + const href = target.getAttribute("href"); + if (href !== "" && href !== null && href !== undefined) { + window.ipc.postMessage( + serializeIpcMessage("browser_open", { href }) + ); + } + } + } + + // also prevent buttons from submitting + if (target.tagName === "BUTTON" && event.type == "submit") { + event.preventDefault(); + } + } + // walk the tree to find the real element + while (realId == null) { + // we've reached the root we don't want to send an event + if (target.parentElement === null) { + return; + } + + target = target.parentElement; + realId = target.getAttribute(`data-dioxus-id`); + } + + shouldPreventDefault = target.getAttribute( + `dioxus-prevent-default` + ); + + let contents = serialize_event(event); + + if (shouldPreventDefault === `on${event.type}`) { + event.preventDefault(); + } + + if (event.type === "submit") { + event.preventDefault(); + } + + if ( + target.tagName === "FORM" && + (event.type === "submit" || event.type === "input") + ) { + for (let x = 0; x < target.elements.length; x++) { + let element = target.elements[x]; + let name = element.getAttribute("name"); + if (name != null) { + if (element.getAttribute("type") === "checkbox") { + // @ts-ignore + contents.values[name] = element.checked ? "true" : "false"; + } else if (element.getAttribute("type") === "radio") { + if (element.checked) { + contents.values[name] = element.value; + } + } else { + // @ts-ignore + contents.values[name] = + element.value ?? element.textContent; + } + } + } + } + + if (realId === null) { + return; + } + realId = parseInt(realId); + window.ipc.postMessage( + serializeIpcMessage("user_event", { + event: edit.event_name, + mounted_dom_id: realId, + contents: contents, + }) + ); + } + }; + this.NewEventListener(edit.event_name, edit.root, handler, event_bubbles(edit.event_name)); + + break; + case "SetText": + this.SetText(edit.root, edit.text); + break; + case "SetAttribute": + this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); + break; + case "RemoveAttribute": + this.RemoveAttribute(edit.root, edit.name, edit.ns); + break; + case "CloneNode": + this.CloneNode(edit.id, edit.new_id); + break; + case "CloneNodeChildren": + this.CloneNodeChildren(edit.id, edit.new_ids); + break; + case "FirstChild": + this.FirstChild(); + break; + case "NextSibling": + this.NextSibling(); + break; + case "ParentNode": + this.ParentNode(); + break; + case "StoreWithId": + this.StoreWithId(BigInt(edit.id)); + break; + case "SetLastNode": + this.SetLastNode(BigInt(edit.id)); + break; + } + } +} + +export function serialize_event(event) { + switch (event.type) { + case "copy": + case "cut": + case "past": { + return {}; + } + case "compositionend": + case "compositionstart": + case "compositionupdate": { + let { data } = event; + return { + data, + }; + } + case "keydown": + case "keypress": + case "keyup": { + let { + charCode, + key, + altKey, + ctrlKey, + metaKey, + keyCode, + shiftKey, + location, + repeat, + which, + code, + } = event; + return { + char_code: charCode, + key: key, + alt_key: altKey, + ctrl_key: ctrlKey, + meta_key: metaKey, + key_code: keyCode, + shift_key: shiftKey, + location: location, + repeat: repeat, + which: which, + code, + }; + } + case "focus": + case "blur": { + return {}; + } + case "change": { + let target = event.target; + let value; + if (target.type === "checkbox" || target.type === "radio") { + value = target.checked ? "true" : "false"; + } else { + value = target.value ?? target.textContent; + } + return { + value: value, + values: {}, + }; + } + case "input": + case "invalid": + case "reset": + case "submit": { + let target = event.target; + let value = target.value ?? target.textContent; + + if (target.type === "checkbox") { + value = target.checked ? "true" : "false"; + } + + return { + value: value, + values: {}, + }; + } + case "click": + case "contextmenu": + case "doubleclick": + case "dblclick": + case "drag": + case "dragend": + case "dragenter": + case "dragexit": + case "dragleave": + case "dragover": + case "dragstart": + case "drop": + case "mousedown": + case "mouseenter": + case "mouseleave": + case "mousemove": + case "mouseout": + case "mouseover": + case "mouseup": { + const { + altKey, + button, + buttons, + clientX, + clientY, + ctrlKey, + metaKey, + offsetX, + offsetY, + pageX, + pageY, + screenX, + screenY, + shiftKey, + } = event; + return { + alt_key: altKey, + button: button, + buttons: buttons, + client_x: clientX, + client_y: clientY, + ctrl_key: ctrlKey, + meta_key: metaKey, + offset_x: offsetX, + offset_y: offsetY, + page_x: pageX, + page_y: pageY, + screen_x: screenX, + screen_y: screenY, + shift_key: shiftKey, + }; + } + case "pointerdown": + case "pointermove": + case "pointerup": + case "pointercancel": + case "gotpointercapture": + case "lostpointercapture": + case "pointerenter": + case "pointerleave": + case "pointerover": + case "pointerout": { + const { + altKey, + button, + buttons, + clientX, + clientY, + ctrlKey, + metaKey, + pageX, + pageY, + screenX, + screenY, + shiftKey, + pointerId, + width, + height, + pressure, + tangentialPressure, + tiltX, + tiltY, + twist, + pointerType, + isPrimary, + } = event; + return { + alt_key: altKey, + button: button, + buttons: buttons, + client_x: clientX, + client_y: clientY, + ctrl_key: ctrlKey, + meta_key: metaKey, + page_x: pageX, + page_y: pageY, + screen_x: screenX, + screen_y: screenY, + shift_key: shiftKey, + pointer_id: pointerId, + width: width, + height: height, + pressure: pressure, + tangential_pressure: tangentialPressure, + tilt_x: tiltX, + tilt_y: tiltY, + twist: twist, + pointer_type: pointerType, + is_primary: isPrimary, + }; + } + case "select": { + return {}; + } + case "touchcancel": + case "touchend": + case "touchmove": + case "touchstart": { + const { altKey, ctrlKey, metaKey, shiftKey } = event; + return { + // changed_touches: event.changedTouches, + // target_touches: event.targetTouches, + // touches: event.touches, + alt_key: altKey, + ctrl_key: ctrlKey, + meta_key: metaKey, + shift_key: shiftKey, + }; + } + case "scroll": { + return {}; + } + case "wheel": { + const { deltaX, deltaY, deltaZ, deltaMode } = event; + return { + delta_x: deltaX, + delta_y: deltaY, + delta_z: deltaZ, + delta_mode: deltaMode, + }; + } + case "animationstart": + case "animationend": + case "animationiteration": { + const { animationName, elapsedTime, pseudoElement } = event; + return { + animation_name: animationName, + elapsed_time: elapsedTime, + pseudo_element: pseudoElement, + }; + } + case "transitionend": { + const { propertyName, elapsedTime, pseudoElement } = event; + return { + property_name: propertyName, + elapsed_time: elapsedTime, + pseudo_element: pseudoElement, + }; + } + case "abort": + case "canplay": + case "canplaythrough": + case "durationchange": + case "emptied": + case "encrypted": + case "ended": + case "error": + case "loadeddata": + case "loadedmetadata": + case "loadstart": + case "pause": + case "play": + case "playing": + case "progress": + case "ratechange": + case "seeked": + case "seeking": + case "stalled": + case "suspend": + case "timeupdate": + case "volumechange": + case "waiting": { + return {}; + } + case "toggle": { + return {}; + } + default: { + return {}; + } + } +} +function serializeIpcMessage(method, params = {}) { + return JSON.stringify({ method, params }); +} +const bool_attrs = { + allowfullscreen: true, + allowpaymentrequest: true, + async: true, + autofocus: true, + autoplay: true, + checked: true, + controls: true, + default: true, + defer: true, + disabled: true, + formnovalidate: true, + hidden: true, + ismap: true, + itemscope: true, + loop: true, + multiple: true, + muted: true, + nomodule: true, + novalidate: true, + open: true, + playsinline: true, + readonly: true, + required: true, + reversed: true, + selected: true, + truespeed: true, +}; + +function is_element_node(node) { + return node.nodeType == 1; +} + +function event_bubbles(event) { + switch (event) { + case "copy": + return true; + case "cut": + return true; + case "paste": + return true; + case "compositionend": + return true; + case "compositionstart": + return true; + case "compositionupdate": + return true; + case "keydown": + return true; + case "keypress": + return true; + case "keyup": + return true; + case "focus": + return false; + case "focusout": + return true; + case "focusin": + return true; + case "blur": + return false; + case "change": + return true; + case "input": + return true; + case "invalid": + return true; + case "reset": + return true; + case "submit": + return true; + case "click": + return true; + case "contextmenu": + return true; + case "doubleclick": + return true; + case "dblclick": + return true; + case "drag": + return true; + case "dragend": + return true; + case "dragenter": + return false; + case "dragexit": + return false; + case "dragleave": + return true; + case "dragover": + return true; + case "dragstart": + return true; + case "drop": + return true; + case "mousedown": + return true; + case "mouseenter": + return false; + case "mouseleave": + return false; + case "mousemove": + return true; + case "mouseout": + return true; + case "scroll": + return false; + case "mouseover": + return true; + case "mouseup": + return true; + case "pointerdown": + return true; + case "pointermove": + return true; + case "pointerup": + return true; + case "pointercancel": + return true; + case "gotpointercapture": + return true; + case "lostpointercapture": + return true; + case "pointerenter": + return false; + case "pointerleave": + return false; + case "pointerover": + return true; + case "pointerout": + return true; + case "select": + return true; + case "touchcancel": + return true; + case "touchend": + return true; + case "touchmove": + return true; + case "touchstart": + return true; + case "wheel": + return true; + case "abort": + return false; + case "canplay": + return false; + case "canplaythrough": + return false; + case "durationchange": + return false; + case "emptied": + return false; + case "encrypted": + return true; + case "ended": + return false; + case "error": + return false; + case "loadeddata": + return false; + case "loadedmetadata": + return false; + case "loadstart": + return false; + case "pause": + return false; + case "play": + return false; + case "playing": + return false; + case "progress": + return false; + case "ratechange": + return false; + case "seeked": + return false; + case "seeking": + return false; + case "stalled": + return false; + case "suspend": + return false; + case "timeupdate": + return false; + case "volumechange": + return false; + case "waiting": + return false; + case "animationstart": + return true; + case "animationend": + return true; + case "animationiteration": + return true; + case "transitionend": + return true; + case "toggle": + return true; + } +}