dioxus/packages/liveview/src/interpreter.js
Demonthos 047ed1e553
Subtree memorization / reactive templates (#488)
This commit adds subtree memoization to Dioxus.

Subtree memoization is basically a compile-time step that drastically 
reduces the amount of work the diffing engine needs to do at runtime by
extracting non-changing nodes out into a static "template." Templates 
are then understood by the various renderers in the ecosystem as a 
faster way of rendering the same items. 

For example, in the web, templates are simply a set of DOM Nodes created 
once and then cloned later. This is the same pattern frameworks like Lithtml
and SolidJS use to achieve near-perfect performance. 

Subtree memoization adds an additional level of complexity to Dioxus. The RSX
macro needs to be much smarter to identify changing/nonchanging nodes and
generate a mapping between the Template and its runtime counterparts.

This commit represents a working starter point for this work, adding support 
for templates for the web, desktop, liveview, ssr, and native-core renderers.
In the future we will try to shrink code generation, generally improve 
performance, and simplify our implementation.
2022-09-30 12:03:06 -07:00

1152 lines
No EOL
29 KiB
JavaScript

function main() {
let root = window.document.getElementById("main");
if (root != null) {
// create a new ipc
window.ipc = new IPC(root);
window.ipc.send(serializeIpcMessage("initialize"));
}
}
class IPC {
constructor(root) {
// connect to the websocket
window.interpreter = new Interpreter(root);
this.ws = new WebSocket(WS_ADDR);
this.ws.onopen = () => {
console.log("Connected to the websocket");
};
this.ws.onerror = (err) => {
console.error("Error: ", err);
};
this.ws.onmessage = (event) => {
let edits = JSON.parse(event.data);
window.interpreter.handleEdits(edits);
};
}
send(msg) {
this.ws.send(msg);
}
}
// id > Number.MAX_SAFE_INTEGER/2 in template ref
// id <= Number.MAX_SAFE_INTEGER/2 in global nodes
const templateIdLimit = BigInt((Number.MAX_SAFE_INTEGER - 1) / 2);
class TemplateRef {
constructor(fragment, dynamicNodePaths, roots, id) {
this.fragment = fragment;
this.dynamicNodePaths = dynamicNodePaths;
this.roots = roots;
this.id = id;
this.placed = false;
this.nodes = [];
}
build(id) {
if (!this.nodes[id]) {
let current = this.fragment;
const path = this.dynamicNodePaths[id];
for (let i = 0; i < path.length; i++) {
const idx = path[i];
current = current.firstChild;
for (let i2 = 0; i2 < idx; i2++) {
current = current.nextSibling;
}
}
this.nodes[id] = current;
}
}
get(id) {
this.build(id);
return this.nodes[id];
}
parent() {
return this.roots[0].parentNode;
}
first() {
return this.roots[0];
}
last() {
return this.roots[this.roots.length - 1];
}
move() {
// move the root nodes into a new template
this.fragment = new DocumentFragment();
for (let n of this.roots) {
this.fragment.appendChild(n);
}
}
getFragment() {
if (!this.placed) {
this.placed = true;
}
else {
this.move();
}
return this.fragment;
}
}
class Template {
constructor(template_id, id) {
this.nodes = [];
this.dynamicNodePaths = [];
this.template_id = template_id;
this.id = id;
this.template = document.createElement("template");
this.reconstructingRefrencesIndex = null;
}
finalize(roots) {
for (let i = 0; i < roots.length; i++) {
let node = roots[i];
let path = [i];
const is_element = node.nodeType == 1;
const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic");
if (!locally_static) {
this.dynamicNodePaths[node.tmplId] = [...path];
}
const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static");
if (traverse_children) {
this.createIds(path, node);
}
this.template.content.appendChild(node);
}
document.head.appendChild(this.template);
}
createIds(path, root) {
let i = 0;
for (let node = root.firstChild; node != null; node = node.nextSibling) {
let new_path = [...path, i];
const is_element = node.nodeType == 1;
const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic");
if (!locally_static) {
this.dynamicNodePaths[node.tmplId] = [...new_path];
}
const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static");
if (traverse_children) {
this.createIds(new_path, node);
}
i++;
}
}
ref(id) {
const template = this.template.content.cloneNode(true);
let roots = [];
this.reconstructingRefrencesIndex = 0;
for (let node = template.firstChild; node != null; node = node.nextSibling) {
roots.push(node);
}
return new TemplateRef(template, this.dynamicNodePaths, roots, id);
}
}
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);
}
}
}
class Interpreter {
constructor(root) {
this.root = root;
this.stack = [root];
this.templateInProgress = null;
this.insideTemplateRef = [];
this.listeners = new ListenerMap(root);
this.handlers = {};
this.nodes = [root];
this.templates = [];
}
top() {
return this.stack[this.stack.length - 1];
}
pop() {
return this.stack.pop();
}
currentTemplateId() {
if (this.insideTemplateRef.length) {
return this.insideTemplateRef[this.insideTemplateRef.length - 1].id;
}
else {
return null;
}
}
getId(id) {
if (this.templateInProgress !== null) {
return this.templates[this.templateInProgress].nodes[id - templateIdLimit];
}
else if (this.insideTemplateRef.length && id >= templateIdLimit) {
return this.insideTemplateRef[this.insideTemplateRef.length - 1].get(id - templateIdLimit);
}
else {
return this.nodes[id];
}
}
SetNode(id, node) {
if (this.templateInProgress !== null) {
id -= templateIdLimit;
node.tmplId = id;
this.templates[this.templateInProgress].nodes[id] = node;
}
else if (this.insideTemplateRef.length && id >= templateIdLimit) {
id -= templateIdLimit;
let last = this.insideTemplateRef[this.insideTemplateRef.length - 1];
last.childNodes[id] = node;
if (last.nodeCache[id]) {
last.nodeCache[id] = node;
}
}
else {
this.nodes[id] = node;
}
}
PushRoot(root) {
const node = this.getId(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++) {
const child = to_add[i];
if (child instanceof TemplateRef) {
root.appendChild(child.getFragment());
}
else {
root.appendChild(child);
}
}
}
ReplaceWith(root_id, m) {
let root = this.getId(root_id);
if (root instanceof TemplateRef) {
this.InsertBefore(root_id, m);
this.Remove(root_id);
}
else {
let els = this.stack.splice(this.stack.length - m).map(function (el) {
if (el instanceof TemplateRef) {
return el.getFragment();
}
else {
return el;
}
});
root.replaceWith(...els);
}
}
InsertAfter(root, n) {
const old = this.getId(root);
const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) {
if (el instanceof TemplateRef) {
return el.getFragment();
}
else {
return el;
}
});
if (old instanceof TemplateRef) {
const last = old.last();
last.after(...new_nodes);
}
else {
old.after(...new_nodes);
}
}
InsertBefore(root, n) {
const old = this.getId(root);
const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) {
if (el instanceof TemplateRef) {
return el.getFragment();
}
else {
return el;
}
});
if (old instanceof TemplateRef) {
const first = old.first();
first.before(...new_nodes);
}
else {
old.before(...new_nodes);
}
}
Remove(root) {
let node = this.getId(root);
if (node !== undefined) {
if (node instanceof TemplateRef) {
for (let child of node.roots) {
child.remove();
}
}
else {
node.remove();
}
}
}
CreateTextNode(text, root) {
const node = document.createTextNode(text);
this.stack.push(node);
this.SetNode(root, node);
}
CreateElement(tag, root) {
const el = document.createElement(tag);
this.stack.push(el);
this.SetNode(root, el);
}
CreateElementNs(tag, root, ns) {
let el = document.createElementNS(ns, tag);
this.stack.push(el);
this.SetNode(root, el);
}
CreatePlaceholder(root) {
let el = document.createElement("pre");
el.hidden = true;
this.stack.push(el);
this.SetNode(root, el);
}
NewEventListener(event_name, root, handler, bubbles) {
const element = this.getId(root);
if (root >= templateIdLimit) {
let currentTemplateRefId = this.currentTemplateId();
root -= templateIdLimit;
element.setAttribute("data-dioxus-id", `${currentTemplateRefId},${root}`);
}
else {
element.setAttribute("data-dioxus-id", `${root}`);
}
this.listeners.create(event_name, element, handler, bubbles);
}
RemoveEventListener(root, event_name, bubbles) {
const element = this.getId(root);
element.removeAttribute(`data-dioxus-id`);
this.listeners.remove(element, event_name, bubbles);
}
SetText(root, text) {
this.getId(root).data = text;
}
SetAttribute(root, field, value, ns) {
const name = field;
const node = this.getId(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;
const node = this.getId(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);
}
}
CreateTemplateRef(id, template_id) {
const el = this.templates[template_id].ref(id);
this.nodes[id] = el;
this.stack.push(el);
}
CreateTemplate(template_id) {
this.templateInProgress = template_id;
this.templates[template_id] = new Template(template_id, 0);
}
FinishTemplate(many) {
this.templates[this.templateInProgress].finalize(this.stack.splice(this.stack.length - many));
this.templateInProgress = null;
}
EnterTemplateRef(id) {
this.insideTemplateRef.push(this.nodes[id]);
}
ExitTemplateRef() {
this.insideTemplateRef.pop();
}
handleEdits(edits) {
for (let edit of edits) {
this.handleEdit(edit);
}
}
CreateElementTemplate(tag, root, locally_static, fully_static) {
const el = document.createElement(tag);
this.stack.push(el);
this.SetNode(root, el);
if (!locally_static)
el.setAttribute("data-dioxus-dynamic", "true");
if (fully_static)
el.setAttribute("data-dioxus-fully-static", fully_static);
}
CreateElementNsTemplate(tag, root, ns, locally_static, fully_static) {
const el = document.createElementNS(ns, tag);
this.stack.push(el);
this.SetNode(root, el);
if (!locally_static)
el.setAttribute("data-dioxus-dynamic", "true");
if (fully_static)
el.setAttribute("data-dioxus-fully-static", fully_static);
}
CreateTextNodeTemplate(text, root, locally_static) {
const node = document.createTextNode(text);
this.stack.push(node);
this.SetNode(root, node);
}
CreatePlaceholderTemplate(root) {
const el = document.createElement("pre");
el.setAttribute("data-dioxus-dynamic", "true");
el.hidden = true;
this.stack.push(el);
this.SetNode(root, el);
}
handleEdit(edit) {
switch (edit.type) {
case "PushRoot":
this.PushRoot(BigInt(edit.root));
break;
case "AppendChildren":
this.AppendChildren(edit.many);
break;
case "ReplaceWith":
this.ReplaceWith(BigInt(edit.root), edit.m);
break;
case "InsertAfter":
this.InsertAfter(BigInt(edit.root), edit.n);
break;
case "InsertBefore":
this.InsertBefore(BigInt(edit.root), edit.n);
break;
case "Remove":
this.Remove(BigInt(edit.root));
break;
case "CreateTextNode":
this.CreateTextNode(edit.text, BigInt(edit.root));
break;
case "CreateElement":
this.CreateElement(edit.tag, BigInt(edit.root));
break;
case "CreateElementNs":
this.CreateElementNs(edit.tag, BigInt(edit.root), edit.ns);
break;
case "CreatePlaceholder":
this.CreatePlaceholder(BigInt(edit.root));
break;
case "RemoveEventListener":
this.RemoveEventListener(BigInt(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.send(
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;
}
if (realId.includes(",")) {
realId = realId.split(',');
realId = {
template_ref_id: parseInt(realId[0]),
template_node_id: parseInt(realId[1]),
};
}
else {
realId = parseInt(realId);
}
window.ipc.send(
serializeIpcMessage("user_event", {
event: edit.event_name,
mounted_dom_id: realId,
contents: contents,
})
);
}
};
this.NewEventListener(edit.event_name, BigInt(edit.root), handler, event_bubbles(edit.event_name));
break;
case "SetText":
this.SetText(BigInt(edit.root), edit.text);
break;
case "SetAttribute":
this.SetAttribute(BigInt(edit.root), edit.field, edit.value, edit.ns);
break;
case "RemoveAttribute":
this.RemoveAttribute(BigInt(edit.root), edit.name, edit.ns);
break;
case "PopRoot":
this.PopRoot();
break;
case "CreateTemplateRef":
this.CreateTemplateRef(BigInt(edit.id), edit.template_id);
break;
case "CreateTemplate":
this.CreateTemplate(BigInt(edit.id));
break;
case "FinishTemplate":
this.FinishTemplate(edit.len);
break;
case "EnterTemplateRef":
this.EnterTemplateRef(BigInt(edit.root));
break;
case "ExitTemplateRef":
this.ExitTemplateRef();
break;
case "CreateElementTemplate":
this.CreateElementTemplate(edit.tag, BigInt(edit.root), edit.locally_static, edit.fully_static);
break;
case "CreateElementNsTemplate":
this.CreateElementNsTemplate(edit.tag, BigInt(edit.root), edit.ns, edit.locally_static, edit.fully_static);
break;
case "CreateTextNodeTemplate":
this.CreateTextNodeTemplate(edit.text, BigInt(edit.root), edit.locally_static);
break;
case "CreatePlaceholderTemplate":
this.CreatePlaceholderTemplate(BigInt(edit.root));
break;
}
}
}
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;
}
}