feat: get desktop working with just creation

This commit is contained in:
Jonathan Kelley committed 2022-11-15 18:32:48 -08:00
1 parent 8caabb97d8
commit e8ae830cf2
16 files changed
+1301 -329

No files matched your search

+12 -7
View File
@@ -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",
}),
+1 -1
View File
@@ -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!(),
+11 -4
View File
@@ -6,11 +6,18 @@ use std::{
rc::Rc,
};
pub struct UiEvent<T> {
bubble_state: Cell<bool>,
data: Rc<T>,
pub struct UiEvent<T: 'static> {
pub(crate) bubble_state: Cell<bool>,
pub(crate) data: Rc<T>,
}
impl<T> Clone for UiEvent<T> {
fn clone(&self) -> Self {
Self {
bubble_state: self.bubble_state.clone(),
data: self.data.clone(),
}
}
}
impl<T> UiEvent<T> {
pub fn cancel_bubble(&self) {
self.bubble_state.set(false);
+68 -45
View File
@@ -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,
},
}
+23 -1
View File
@@ -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<T: 'static>(
cx: &'a ScopeState,
mut f: impl FnMut(UiEvent<T>) + 'a,
) -> AttributeValue<'a> {
let f = cx.bump().alloc(move |a: &dyn Any| {
a.downcast_ref::<UiEvent<T>>()
.map(|a| f(a.clone()))
.unwrap_or_else(|| {
panic!(
"Expected UiEvent<{}>, got {:?}",
std::any::type_name::<T>(),
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 {
+9 -4
View File
@@ -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<T: 'static>(
&mut self,
name: &str,
event: &dyn Any,
data: Rc<T>,
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())
}
}
+1 -1
View File
@@ -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",
+2 -2
View File
@@ -27,7 +27,7 @@ type DropHandler = Box<dyn Fn(&Window, FileDropEvent) -> bool>;
pub(crate) type WryProtocol = (
String,
Box<dyn Fn(&HttpRequest) -> WryResult<HttpResponse> + 'static>,
Box<dyn Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Vec<u8>>> + 'static>,
);
impl Config {
@@ -96,7 +96,7 @@ impl Config {
/// Set a custom protocol
pub fn with_custom_protocol<F>(mut self, name: String, handler: F) -> Self
where
F: Fn(&HttpRequest) -> WryResult<HttpResponse> + 'static,
F: Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Vec<u8>>> + 'static,
{
self.protocols.push((name, Box::new(handler)));
self
+27 -18
View File
@@ -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();
}
+11 -7
View File
@@ -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<dyn Fn(String)> {
let desktop = use_window(cx).clone();
&*cx.use_hook(|| Rc::new(move |script| desktop.eval(script)) as Rc<dyn Fn(String)>)
}
/// 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<S: std::string::ToString>(cx: &ScopeState) -> &dyn Fn(S) {
let desktop = use_window(cx).clone();
cx.use_hook(|| move |script| desktop.eval(script))
}
+10 -2
View File
@@ -105,19 +105,20 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, 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<P: 'static + Send>(root: Component<P>, 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<P: 'static + Send>(root: Component<P>, 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()
+23 -15
View File
@@ -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<Vec<u8>>,
asset_root: Option<PathBuf>,
custom_head: Option<String>,
custom_index: Option<String>,
) -> Result<Response> {
) -> Result<Response<Vec<u8>>> {
// 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("</body>", &format!("{}</body>", 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 -->", 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)
}
}
+6 -6
View File
@@ -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 },
+7 -1
View File
@@ -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,
}
}
)*
};
+146 -215
View File
@@ -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;
}
}
}
+944
View File
@@ -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;
}
}