From cbc361105bbe02a9c86b0941311a30bceee8ba40 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 13 Apr 2020 12:15:36 +0200 Subject: [PATCH 1/7] refactor: call interpreter methods directly --- js/change-list-interpreter.js | 464 +++++++++++++++------------------- src/change_list/emitter.rs | 292 --------------------- src/change_list/js.rs | 146 +++++++++++ src/change_list/mod.rs | 121 ++++----- src/change_list/strings.rs | 20 +- src/diff.rs | 16 +- 6 files changed, 430 insertions(+), 629 deletions(-) delete mode 100755 src/change_list/emitter.rs diff --git a/js/change-list-interpreter.js b/js/change-list-interpreter.js index d840028..24957a4 100644 --- a/js/change-list-interpreter.js +++ b/js/change-list-interpreter.js @@ -9,19 +9,109 @@ function string(mem, pointer, length) { return decoder.decode(buf); } -const OP_TABLE = [ +export class ChangeListInterpreter { + constructor(container) { + this.trampoline = null; + this.container = container; + this.ranges = []; + this.stack = []; + this.strings = new Map(); + this.temporaries = []; + this.templates = new Map(); + } + + unmount() { + this.trampoline.mounted = false; + + // Null out all of our properties just to ensure that if we mistakenly ever + // call a method on this instance again, it will throw. + this.trampoline = null; + this.container = null; + this.ranges = null; + this.stack = null; + this.strings = null; + this.temporaries = null; + this.templates = null; + } + + addChangeListRange(start, len) { + this.ranges.push(start); + this.ranges.push(len); + } + + applyChanges(memory) { + if (this.ranges.length == 0) { + return; + } + + this.stack.push(this.container.firstChild); + const mem8 = new Uint8Array(memory.buffer); + const mem32 = new Uint32Array(memory.buffer); + + for (let i = 0; i < this.ranges.length; i += 2) { + const start = this.ranges[i]; + const len = this.ranges[i + 1]; + this.applyChangeRange(mem8, mem32, start, len, memory); + } + + this.reset(); + } + + start() { + this.stack.push(this.container.firstChild); + } + + reset() { + this.ranges.length = 0; + this.stack.length = 0; + this.temporaries.length = 0; + } + + applyChangeRange(mem8, mem32, start, len, memory) { + const end = (start + len) / 4; + for (let i = start / 4; i < end; ) { + const op = mem32[i++]; + i = OP_TABLE[op](this, mem8, mem32, i, memory); + } + } + + getCachedString(id) { + return this.strings.get(id); + } + + getTemplate(id) { + return this.templates.get(id); + } + + initEventsTrampoline(trampoline) { + this.trampoline = trampoline; + trampoline.mounted = true; + this.eventHandler = function(event) { + if (!trampoline.mounted) { + throw new Error("invocation of listener after VDOM has been unmounted"); + } + + // `this` always refers to the element the handler was added to. + // Since we're adding the handler to all elements our content wants + // to listen for events on, this ensures that we always get the right + // values for `a` and `b`. + const type = event.type; + const a = this[`dodrio-a-${type}`]; + const b = this[`dodrio-b-${type}`]; + trampoline(event, a, b); + } + } + // 0 - function setText(interpreter, mem8, mem32, i) { - const pointer = mem32[i++]; - const length = mem32[i++]; + setText(pointer, length, memory) { + const mem8 = new Uint8Array(memory.buffer); const str = string(mem8, pointer, length); - top(interpreter.stack).textContent = str; - return i; - }, + top(this.stack).textContent = str; + } // 1 - function removeSelfAndNextSiblings(interpreter, mem8, mem32, i) { - const node = interpreter.stack.pop(); + removeSelfAndNextSiblings(interpreter) { + const node = this.stack.pop(); let sibling = node.nextSibling; while (sibling) { const temp = sibling.nextSibling; @@ -29,25 +119,21 @@ const OP_TABLE = [ sibling = temp; } node.remove(); - return i; - }, + } // 2 - function replaceWith(interpreter, mem8, mem32, i) { - const newNode = interpreter.stack.pop(); - const oldNode = interpreter.stack.pop(); + replaceWith(interpreter) { + const newNode = this.stack.pop(); + const oldNode = this.stack.pop(); oldNode.replaceWith(newNode); - interpreter.stack.push(newNode); - return i; - }, + this.stack.push(newNode); + } // 3 - function setAttribute(interpreter, mem8, mem32, i) { - const nameId = mem32[i++]; - const valueId = mem32[i++]; - const name = interpreter.getCachedString(nameId); - const value = interpreter.getCachedString(valueId); - const node = top(interpreter.stack); + setAttribute(nameId, valueId) { + const name = this.getCachedString(nameId); + const value = this.getCachedString(valueId); + const node = top(this.stack); node.setAttribute(name, value); // Some attributes are "volatile" and don't work through `setAttribute`. @@ -60,15 +146,12 @@ const OP_TABLE = [ if (name === "selected") { node.selected = true; } - - return i; - }, + } // 4 - function removeAttribute(interpreter, mem8, mem32, i) { - const nameId = mem32[i++]; - const name = interpreter.getCachedString(nameId); - const node = top(interpreter.stack); + removeAttribute(nameId) { + const name = this.getCachedString(nameId); + const node = top(this.stack); node.removeAttribute(name); // Some attributes are "volatile" and don't work through `removeAttribute`. @@ -81,297 +164,152 @@ const OP_TABLE = [ if (name === "selected") { node.selected = false; } - - return i; - }, + } // 5 - function pushReverseChild(interpreter, mem8, mem32, i) { - const n = mem32[i++]; - const parent = top(interpreter.stack); + pushReverseChild(n) { + const parent = top(this.stack); const children = parent.childNodes; const child = children[children.length - n - 1]; - interpreter.stack.push(child); - return i; - }, + this.stack.push(child); + } // 6 - function popPushChild(interpreter, mem8, mem32, i) { - const n = mem32[i++]; - interpreter.stack.pop(); - const parent = top(interpreter.stack); + popPushChild(n) { + this.stack.pop(); + const parent = top(this.stack); const children = parent.childNodes; const child = children[n]; - interpreter.stack.push(child); - return i; - }, + this.stack.push(child); + } // 7 - function pop(interpreter, mem8, mem32, i) { - interpreter.stack.pop(); - return i; - }, + pop(interpreter) { + this.stack.pop(); + } // 8 - function appendChild(interpreter, mem8, mem32, i) { - const child = interpreter.stack.pop(); - top(interpreter.stack).appendChild(child); - return i; - }, + appendChild(interpreter) { + const child = this.stack.pop(); + top(this.stack).appendChild(child); + } // 9 - function createTextNode(interpreter, mem8, mem32, i) { - const pointer = mem32[i++]; - const length = mem32[i++]; + createTextNode(pointer, length, memory) { + const mem8 = new Uint8Array(memory.buffer); const text = string(mem8, pointer, length); - interpreter.stack.push(document.createTextNode(text)); - return i; - }, + this.stack.push(document.createTextNode(text)); + } // 10 - function createElement(interpreter, mem8, mem32, i) { - const tagNameId = mem32[i++]; - const tagName = interpreter.getCachedString(tagNameId); - interpreter.stack.push(document.createElement(tagName)); - return i; - }, + createElement(tagNameId) { + const tagName = this.getCachedString(tagNameId); + this.stack.push(document.createElement(tagName)); + } // 11 - function newEventListener(interpreter, mem8, mem32, i) { - const eventId = mem32[i++]; - const eventType = interpreter.getCachedString(eventId); - const a = mem32[i++]; - const b = mem32[i++]; - const el = top(interpreter.stack); - el.addEventListener(eventType, interpreter.eventHandler); + newEventListener(eventId, a, b) { + const eventType = this.getCachedString(eventId); + const el = top(this.stack); + el.addEventListener(eventType, this.eventHandler); el[`dodrio-a-${eventType}`] = a; el[`dodrio-b-${eventType}`] = b; - return i; - }, + } // 12 - function updateEventListener(interpreter, mem8, mem32, i) { - const eventId = mem32[i++]; - const eventType = interpreter.getCachedString(eventId); - const el = top(interpreter.stack); - el[`dodrio-a-${eventType}`] = mem32[i++]; - el[`dodrio-b-${eventType}`] = mem32[i++]; - return i; - }, + updateEventListener(eventId, a, b) { + const eventType = this.getCachedString(eventId); + const el = top(this.stack); + el[`dodrio-a-${eventType}`] = a; + el[`dodrio-b-${eventType}`] = b; + } // 13 - function removeEventListener(interpreter, mem8, mem32, i) { - const eventId = mem32[i++]; - const eventType = interpreter.getCachedString(eventId); - const el = top(interpreter.stack); - el.removeEventListener(eventType, interpreter.eventHandler); - return i; - }, + removeEventListener(eventId) { + const eventType = this.getCachedString(eventId); + const el = top(this.stack); + el.removeEventListener(eventType, this.eventHandler); + } // 14 - function addCachedString(interpreter, mem8, mem32, i) { - const pointer = mem32[i++]; - const length = mem32[i++]; - const id = mem32[i++]; + addCachedString(pointer, length, id, memory) { + const mem8 = new Uint8Array(memory.buffer); const str = string(mem8, pointer, length); - interpreter.addCachedString(str, id); - return i; - }, + this.strings.set(id, str); + } // 15 - function dropCachedString(interpreter, mem8, mem32, i) { - const id = mem32[i++]; - interpreter.dropCachedString(id); - return i; - }, + dropCachedString(id) { + this.strings.delete(id); + } // 16 - function createElementNS(interpreter, mem8, mem32, i) { - const tagNameId = mem32[i++]; - const tagName = interpreter.getCachedString(tagNameId); - const nsId = mem32[i++]; - const ns = interpreter.getCachedString(nsId); - interpreter.stack.push(document.createElementNS(ns, tagName)); - return i; - }, + createElementNS(tagNameId, nsId) { + const tagName = this.getCachedString(tagNameId); + const ns = this.getCachedString(nsId); + this.stack.push(document.createElementNS(ns, tagName)); + } // 17 - function saveChildrenToTemporaries(interpreter, mem8, mem32, i) { - let temp = mem32[i++]; - const start = mem32[i++]; - const end = mem32[i++]; - const parent = top(interpreter.stack); + saveChildrenToTemporaries(temp, start, end) { + const parent = top(this.stack); const children = parent.childNodes; for (let i = start; i < end; i++) { - interpreter.temporaries[temp++] = children[i]; + this.temporaries[temp++] = children[i]; } - return i; - }, + } // 18 - function pushChild(interpreter, mem8, mem32, i) { - const parent = top(interpreter.stack); - const n = mem32[i++]; + pushChild(n) { + const parent = top(this.stack); const child = parent.childNodes[n]; - interpreter.stack.push(child); - return i; - }, + this.stack.push(child); + } // 19 - function pushTemporary(interpreter, mem8, mem32, i) { - const temp = mem32[i++]; - interpreter.stack.push(interpreter.temporaries[temp]); - return i; - }, + pushTemporary(temp) { + this.stack.push(this.temporaries[temp]); + } // 20 - function insertBefore(interpreter, mem8, mem32, i) { - const before = interpreter.stack.pop(); - const after = interpreter.stack.pop(); + insertBefore(interpreter) { + const before = this.stack.pop(); + const after = this.stack.pop(); after.parentNode.insertBefore(before, after); - interpreter.stack.push(before); - return i; - }, + this.stack.push(before); + } // 21 - function popPushReverseChild(interpreter, mem8, mem32, i) { - const n = mem32[i++]; - interpreter.stack.pop(); - const parent = top(interpreter.stack); + popPushReverseChild(n) { + this.stack.pop(); + const parent = top(this.stack); const children = parent.childNodes; const child = children[children.length - n - 1]; - interpreter.stack.push(child); - return i; - }, + this.stack.push(child); + } // 22 - function removeChild(interpreter, mem8, mem32, i) { - const n = mem32[i++]; - const parent = top(interpreter.stack); + removeChild(n) { + const parent = top(this.stack); const child = parent.childNodes[n]; child.remove(); - return i; - }, - - // 23 - function setClass(interpreter, mem8, mem32, i) { - const classId = mem32[i++]; - const className = interpreter.getCachedString(classId); - top(interpreter.stack).className = className; - return i; - }, - - // 24 - function saveTemplate(interpreter, mem8, mem32, i) { - const id = mem32[i++]; - const template = top(interpreter.stack); - interpreter.saveTemplate(id, template.cloneNode(true)); - return i; - }, - - // 25 - function pushTemplate(interpreter, mem8, mem32, i) { - const id = mem32[i++]; - const template = interpreter.getTemplate(id); - interpreter.stack.push(template.cloneNode(true)); - return i; - } -]; - -export class ChangeListInterpreter { - constructor(container) { - this.trampoline = null; - this.container = container; - this.ranges = []; - this.stack = []; - this.strings = new Map(); - this.temporaries = []; - this.templates = new Map(); - } - - unmount() { - this.trampoline.mounted = false; - - // Null out all of our properties just to ensure that if we mistakenly ever - // call a method on this instance again, it will throw. - this.trampoline = null; - this.container = null; - this.ranges = null; - this.stack = null; - this.strings = null; - this.temporaries = null; - this.templates = null; - } - - addChangeListRange(start, len) { - this.ranges.push(start); - this.ranges.push(len); - } - - applyChanges(memory) { - if (this.ranges.length == 0) { - return; - } - - this.stack.push(this.container.firstChild); - const mem8 = new Uint8Array(memory.buffer); - const mem32 = new Uint32Array(memory.buffer); - - for (let i = 0; i < this.ranges.length; i += 2) { - const start = this.ranges[i]; - const len = this.ranges[i + 1]; - this.applyChangeRange(mem8, mem32, start, len); - } - - this.ranges.length = 0; - this.stack.length = 0; - this.temporaries.length = 0; - } - - applyChangeRange(mem8, mem32, start, len) { - const end = (start + len) / 4; - for (let i = start / 4; i < end; ) { - const op = mem32[i++]; - i = OP_TABLE[op](this, mem8, mem32, i); - } - } - - addCachedString(str, id) { - this.strings.set(id, str); - } - - dropCachedString(id) { - this.strings.delete(id); } - getCachedString(id) { - return this.strings.get(id); - } - - saveTemplate(id, template) { - this.templates.set(id, template); + // 23 + setClass(classId) { + const className = this.getCachedString(classId); + top(this.stack).className = className; } - getTemplate(id) { - return this.templates.get(id); + // 24 + saveTemplate(id) { + const template = top(this.stack); + this.templates.set(id, template.cloneNode(true)); } - initEventsTrampoline(trampoline) { - this.trampoline = trampoline; - trampoline.mounted = true; - this.eventHandler = function(event) { - if (!trampoline.mounted) { - throw new Error("invocation of listener after VDOM has been unmounted"); - } - - // `this` always refers to the element the handler was added to. - // Since we're adding the handler to all elements our content wants - // to listen for events on, this ensures that we always get the right - // values for `a` and `b`. - const type = event.type; - const a = this[`dodrio-a-${type}`]; - const b = this[`dodrio-b-${type}`]; - trampoline(event, a, b); - } + // 25 + pushTemplate(id) { + const template = this.getTemplate(id); + this.stack.push(template.cloneNode(true)); } } diff --git a/src/change_list/emitter.rs b/src/change_list/emitter.rs deleted file mode 100755 index c8c3794..0000000 --- a/src/change_list/emitter.rs +++ /dev/null @@ -1,292 +0,0 @@ -//! The `InstructionEmitter` is responsible for encoding change list -//! instructions and ensuring that each instruction has the correct number of -//! immediates. It ensures that the resulting change list instruction stream is -//! *syntactically* correct (opcodes have the correct arity of immediate -//! arguments, etc), while `ChangeListBuilder` wraps an emitter and additionally -//! ensures that the resulting change list program is *semantically* correct -//! (doesn't reference cached strings before they've been added to the cache, -//! etc). -//! -//! We encode the instructions directly into a dedicated bump arena. We -//! eventually pass the bump arena's chunks to the interpreter in JS, so it is -//! critical that nothing other than change list instructions are allocated -//! inside this bump, and that the instructions themselves do not contain any -//! padding or uninitialized memory. See the documentation for the the -//! `Bump::each_allocated_chunk` method for details. - -use bumpalo::Bump; - -#[derive(Debug)] -pub(crate) struct InstructionEmitter { - bump: Bump, -} - -impl InstructionEmitter { - /// Construct a new `InstructionEmitter` with its own bump arena. - pub fn new() -> InstructionEmitter { - let bump = Bump::new(); - InstructionEmitter { bump } - } - - /// Invoke the given function with each of the allocated instruction - /// sequences that this emitter has built up. - #[cfg_attr(feature = "xxx-unstable-internal-use-only", allow(dead_code))] - pub fn each_instruction_sequence(&mut self, f: F) - where - F: FnMut(&[u8]), - { - // Note: the safety invariants required for `each_allocated_chunk` are - // maintained by the fact that everything we encode as allocations in - // the bump arena are in the form of `u32`s, and therefore we don't have - // any uninitialized memory padding in the arena. - unsafe { - self.bump.each_allocated_chunk(f); - } - } - - /// Reset to an empty sequence of instructions. - pub fn reset(&mut self) { - self.bump.reset(); - } -} - -macro_rules! define_change_list_instructions { - ( $( - $( #[$attr:meta] )* - $name:ident ( - $($immediate:ident),* - ) = $discriminant:expr, - )* ) => { - impl InstructionEmitter { - $( - $( #[$attr] )* - #[inline] - pub fn $name(&self $(, $immediate: u32)*) { - self.bump.alloc_with(|| [$discriminant $(, $immediate )* ]); - } - )* - } - } -} - -define_change_list_instructions! { - /// Stack: `[... TextNode] -> [... TextNode]` - /// - /// ```text - /// stack.top().textContent = readString(pointer, length) - /// ``` - set_text(pointer, length) = 0, - - /// Stack: `[... Node] -> [...]` - /// - /// ```text - /// node = stack.pop() - /// while (node.nextSibling) { - /// node.nextSibling.remove(); - /// } - /// node.remove() - /// ``` - remove_self_and_next_siblings() = 1, - - /// Stack: `[... Node Node] -> [... Node]` - /// - /// ```text - /// new = stack.pop() - /// old = stack.pop() - /// old.replaceWith(new) - /// stack.push(new) - /// ``` - replace_with() = 2, - - /// Stack: `[... Node] -> [... Node]` - /// - /// ```text - /// stack.top().setAttribute(getCachedString(attribute_key), getCachedString(value_key)) - /// ``` - set_attribute(attribute_key, value_key) = 3, - - /// Stack: `[... Node] -> [... Node]` - /// - /// ```text - /// stack.top().removeAttribute(getCachedString(attribute_key)) - /// ``` - remove_attribute(attribute_key) = 4, - - /// Stack: `[... Node] -> [... Node Node]` - /// - /// ```text - /// parent = stack.top() - /// child = parent.childNodes[parent.childNodes.length - n - 1] - /// stack.push(child) - /// ``` - push_reverse_child(n) = 5, - - /// Stack: `[... Node Node] -> [... Node Node]` - /// - /// ```text - /// stack.pop(); - /// parent = stack.top(); - /// child = parent.childNodes[n] - /// stack.push(child) - /// ``` - pop_push_child(n) = 6, - - /// Stack: `[... T] -> [...]` - /// - /// ```text - /// stack.pop() - /// ``` - pop() = 7, - - /// Stack: `[... Node Node] -> [... Node]` - /// - /// ```text - /// child = stack.pop() - /// stack.top().appendChild(child) - /// ``` - append_child() = 8, - - /// Stack: `[...] -> [... Node]` - /// - /// ```text - /// stack.push(document.createTextNode(readString(pointer, length))) - /// ``` - create_text_node(pointer, length) = 9, - - /// Stack: `[...] -> [... Node]` - /// - /// ```text - /// stack.push(document.createElement(getCachedString(tag_name_key)) - /// ``` - create_element(tag_name_key) = 10, - - /// Stack: `[... Node] -> [... Node]` - /// - /// ```text - /// event = getCachedString(event_key) - /// callback = createProxyToRustCallback(a, b) - /// stack.top().addEventListener(event, callback) - /// ``` - new_event_listener(event_key, a, b) = 11, - - /// Stack: `[... Node] -> [... Node]` - /// - /// ```text - /// event = getCachedString(event_key) - /// new_callback = createProxyToRustCallback(a, b); - /// stack.top().updateEventlistener(new_callback) - /// ``` - update_event_listener(event_key, a, b) = 12, - - /// Stack: `[... Node] -> [... Node]` - /// - /// ```text - /// stack.top().removeEventListener(getCachedString(event_key)); - /// ``` - remove_event_listener(event_key) = 13, - - /// Stack: `[...] -> [...]` - /// - /// ```text - /// addCachedString(readString(pointer, length), key); - /// ``` - add_cached_string(pointer, length, key) = 14, - - /// Stack: `[...] -> [...]` - /// - /// ```text - /// dropCachedString(key); - /// ``` - drop_cached_string(key) = 15, - - /// Stack: `[...] -> [... Node]` - /// - /// ```text - /// tag_name = getCachedString(tag_name_key) - /// namespace = getCachedString(tag_name_key) - /// stack.push(document.createElementNS(tag_name, namespace)) - /// ``` - create_element_ns(tag_name_key, namespace_key) = 16, - - /// Stack: `[...] -> [...]` - /// - /// ```text - /// parent = stack.top() - /// children = parent.childNodes - /// temp = temp_base - /// for i in start .. end: - /// temporaries[temp] = children[i] - /// temp += 1 - /// ``` - save_children_to_temporaries(temp_base, start, end) = 17, - - /// Stack: `[... Node] -> [... Node Node]` - /// - /// ```text - /// parent = stack.top() - /// child = parent.childNodes[n] - /// stack.push(child) - /// ``` - push_child(n) = 18, - - /// Stack: `[...] -> [... Node]` - /// - /// ```text - /// stack.push(temporaries[temp]) - /// ``` - push_temporary(temp) = 19, - - /// Stack: `[... Node Node] -> [... Node]` - /// - /// ```text - /// before = stack.pop() - /// after = stack.pop() - /// after.insertBefore(before) - /// stack.push(before) - /// ``` - insert_before() = 20, - - /// Stack: `[... Node Node] -> [... Node Node]` - /// - /// ```text - /// stack.pop() - /// parent = stack.top() - /// child = parent.childNodes[parent.childNodes.length - n - 1] - /// stack.push(child) - /// ``` - pop_push_reverse_child(n) = 21, - - /// Stack: `[... Node] -> [... Node]` - /// - /// ```text - /// parent = stack.top() - /// child = parent.childNodes[n] - /// child.remove() - /// ``` - remove_child(n) = 22, - - /// Stack: `[... Node] -> [... Node]` - /// - /// ```text - /// class = getCachedString(class) - /// node = stack.top() - /// node.className = class - /// ``` - set_class(class) = 23, - - /// Stack: `[... Node] -> [... Node]` - /// - /// ```text - /// template = stack.top() - /// saveTemplate(id, template) - /// ``` - save_template(id) = 24, - - /// Stack: `[...] -> [... Node]` - /// - /// ```text - /// template = getTemplate(id) - /// stack.push(template.cloneNode(true)) - /// ``` - push_template(id) = 25, -} diff --git a/src/change_list/js.rs b/src/change_list/js.rs index 1a4b0c1..e451464 100644 --- a/src/change_list/js.rs +++ b/src/change_list/js.rs @@ -1,5 +1,7 @@ cfg_if::cfg_if! { if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] { + use wasm_bindgen::prelude::JsValue; + #[derive(Clone, Debug)] pub struct ChangeListInterpreter {} impl ChangeListInterpreter { @@ -9,6 +11,63 @@ cfg_if::cfg_if! { pub fn unmount(&self) {} pub fn add_change_list_range(&self, _start: usize, _len: usize) {} pub fn init_events_trampoline(&self, _trampoline: &crate::EventsTrampoline) {} + pub fn start(&self) {} + pub fn reset(&self) {} + + // -- ops + + // 0 + pub fn set_text(&self, pointer: u32, len: u32, memory: JsValue) {} + // 1 + pub fn remove_self_and_next_siblings(&self) {} + // 2 + pub fn replace_with(&self) {} + // 3 + pub fn set_attribute(&self, name_id: u32, value_id: u32) {} + // 4 + pub fn remove_attribute(&self, name_id: u32) {} + // 5 + pub fn push_reverse_child(&self, n: u32) {} + // 6 + pub fn pop_push_child(&self, n: u32) {} + // 7 + pub fn pop(&self) {} + // 8 + pub fn append_child(&self) {} + // 9 + pub fn create_text_node(&self, pointer: u32, len: u32, memory: JsValue) {} + // 10 + pub fn create_element(&self, tag_name_id: u32) {} + // 11 + pub fn new_event_listener(&self, event_id: u32, a: u32, b: u32) {} + // 12 + pub fn update_event_listener(&self, event_id: u32, a: u32, b: u32) {} + // 13 + pub fn remove_event_listener(&self, event_id: u32) {} + // 14 + pub fn add_cached_string(&self, pointer: u32, len: u32, id: u32, memory: JsValue) {} + // 15 + pub fn drop_cached_string(&self, id: u32) {} + // 16 + pub fn create_element_ns(&self, tag_name_id: u32, ns_id: u32) {} + // 17 + pub fn save_children_to_temporaries(&self, temp: u32, start: u32, end: u32) {} + // 18 + pub fn push_child(&self, n: u32) {} + // 19 + pub fn push_temporary(&self, temp: u32) {} + // 20 + pub fn insert_before(&self) {} + // 21 + pub fn pop_push_reverse_child(&self, n: u32) {} + // 22 + pub fn remove_child(&self, n: u32) {} + // 23 + pub fn set_class(&self, class_id: u32) {} + // 24 + pub fn save_template(&self, id: u32) {} + // 25 + pub fn push_template(&self, id: u32) {} } } else { use wasm_bindgen::prelude::*; @@ -35,6 +94,93 @@ cfg_if::cfg_if! { this: &ChangeListInterpreter, trampoline: &crate::EventsTrampoline, ); + + #[wasm_bindgen(structural, method, js_name = start)] + pub fn start(this: &ChangeListInterpreter); + + #[wasm_bindgen(structural, method, js_name = reset)] + pub fn reset(this: &ChangeListInterpreter); + + // -- ops + + // 0 + #[wasm_bindgen(structural, method, js_name = setText)] + pub fn set_text(this: &ChangeListInterpreter, pointer: u32, len: u32, memory: JsValue); + // 1 + #[wasm_bindgen(structural, method, js_name = removeSelfAndNextSiblings)] + pub fn remove_self_and_next_siblings(this: &ChangeListInterpreter); + // 2 + #[wasm_bindgen(structural, method, js_name = replaceWith)] + pub fn replace_with(this: &ChangeListInterpreter); + // 3 + #[wasm_bindgen(structural, method, js_name = setAttribute)] + pub fn set_attribute(this: &ChangeListInterpreter, name_id: u32, value_id: u32); + // 4 + #[wasm_bindgen(structural, method, js_name = removeAttribute)] + pub fn remove_attribute(this: &ChangeListInterpreter, name_id: u32); + // 5 + #[wasm_bindgen(structural, method, js_name = pushReverseChild)] + pub fn push_reverse_child(this: &ChangeListInterpreter, n: u32); + // 6 + #[wasm_bindgen(structural, method, js_name = popPushChild)] + pub fn pop_push_child(this: &ChangeListInterpreter, n: u32); + // 7 + #[wasm_bindgen(structural, method, js_name = pop)] + pub fn pop(this: &ChangeListInterpreter); + // 8 + #[wasm_bindgen(structural, method, js_name = appendChild)] + pub fn append_child(this: &ChangeListInterpreter); + // 9 + #[wasm_bindgen(structural, method, js_name = createTextNode)] + pub fn create_text_node(this: &ChangeListInterpreter, pointer: u32, len: u32, memory: JsValue); + // 10 + #[wasm_bindgen(structural, method, js_name = createElement)] + pub fn create_element(this: &ChangeListInterpreter, tag_name_id: u32); + // 11 + #[wasm_bindgen(structural, method, js_name = newEventListener)] + pub fn new_event_listener(this: &ChangeListInterpreter, event_id: u32, a: u32, b: u32); + // 12 + #[wasm_bindgen(structural, method, js_name = updateEventListener)] + pub fn update_event_listener(this: &ChangeListInterpreter, event_id: u32, a: u32, b: u32); + // 13 + #[wasm_bindgen(structural, method, js_name = removeEventListener)] + pub fn remove_event_listener(this: &ChangeListInterpreter, event_id: u32); + // 14 + #[wasm_bindgen(structural, method, js_name = addCachedString)] + pub fn add_cached_string(this: &ChangeListInterpreter, pointer: u32, len: u32, id: u32, memory: JsValue); + // 15 + #[wasm_bindgen(structural, method, js_name = dropCachedString)] + pub fn drop_cached_string(this: &ChangeListInterpreter, id: u32); + // 16 + #[wasm_bindgen(structural, method, js_name = createElementNS)] + pub fn create_element_ns(this: &ChangeListInterpreter, tag_name_id: u32, ns_id: u32); + // 17 + #[wasm_bindgen(structural, method, js_name = saveChildrenToTemporaries)] + pub fn save_children_to_temporaries(this: &ChangeListInterpreter, temp: u32, start: u32, end: u32); + // 18 + #[wasm_bindgen(structural, method, js_name = pushChild)] + pub fn push_child(this: &ChangeListInterpreter, n: u32); + // 19 + #[wasm_bindgen(structural, method, js_name = pushTemporary)] + pub fn push_temporary(this: &ChangeListInterpreter, temp: u32); + // 20 + #[wasm_bindgen(structural, method, js_name = insertBefore)] + pub fn insert_before(this: &ChangeListInterpreter); + // 21 + #[wasm_bindgen(structural, method, js_name = popPushReverseChild)] + pub fn pop_push_reverse_child(this: &ChangeListInterpreter, n: u32); + // 22 + #[wasm_bindgen(structural, method, js_name = removeChild)] + pub fn remove_child(this: &ChangeListInterpreter, n: u32); + // 23 + #[wasm_bindgen(structural, method, js_name = setClass)] + pub fn set_class(this: &ChangeListInterpreter, class_id: u32); + // 24 + #[wasm_bindgen(structural, method, js_name = saveTemplate)] + pub fn save_template(this: &ChangeListInterpreter, id: u32); + // 25 + #[wasm_bindgen(structural, method, js_name = pushTemplate)] + pub fn push_template(this: &ChangeListInterpreter, id: u32); } } } diff --git a/src/change_list/mod.rs b/src/change_list/mod.rs index e3af69d..51b8740 100644 --- a/src/change_list/mod.rs +++ b/src/change_list/mod.rs @@ -1,11 +1,9 @@ -pub(crate) mod emitter; pub(crate) mod strings; pub(crate) mod traversal; // Note: has to be `pub` because of `wasm-bindgen` visibility restrictions. pub mod js; -use self::emitter::InstructionEmitter; use self::strings::{StringKey, StringsCache}; use self::traversal::{MoveTo, Traversal}; use crate::{cached_set::CacheId, Listener}; @@ -14,7 +12,6 @@ use fxhash::FxHashSet; #[derive(Debug)] pub(crate) struct ChangeListPersistentState { strings: StringsCache, - emitter: InstructionEmitter, traversal: Traversal, interpreter: js::ChangeListInterpreter, templates: FxHashSet, @@ -35,13 +32,12 @@ impl Drop for ChangeListPersistentState { impl ChangeListPersistentState { pub(crate) fn new(container: &crate::Element) -> ChangeListPersistentState { let strings = StringsCache::new(); - let emitter = InstructionEmitter::new(); let traversal = Traversal::new(); let interpreter = js::ChangeListInterpreter::new(container); let templates = Default::default(); + ChangeListPersistentState { strings, - emitter, traversal, interpreter, templates, @@ -53,47 +49,30 @@ impl ChangeListPersistentState { } pub(crate) fn builder<'a>(&'a mut self) -> ChangeListBuilder<'a> { - ChangeListBuilder { + let builder = ChangeListBuilder { state: self, next_temporary: 0, forcing_new_listeners: false, - } + }; + builder.state.interpreter.start(); + + builder } } -cfg_if::cfg_if! { - if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] { - impl ChangeListBuilder<'_> { - pub(crate) fn finish(self) { - self.state.strings.drop_unused_strings(&self.state.emitter); - - // Nothing to actually apply the changes to. +impl ChangeListBuilder<'_> { + pub(crate) fn finish(self) { + self.state + .strings + .drop_unused_strings(&self.state.interpreter); - self.state.emitter.reset(); - self.state.traversal.reset(); - } - } - } else { - impl ChangeListBuilder<'_> { - pub(crate) fn finish(self) { - self.state.strings.drop_unused_strings(&self.state.emitter); - - // Apply the changes. - let interpreter = &self.state.interpreter; - self.state.emitter.each_instruction_sequence(|seq| { - interpreter.add_change_list_range(seq.as_ptr() as usize, seq.len()); - }); - interpreter.apply_changes(wasm_bindgen::memory()); - - self.state.emitter.reset(); - self.state.traversal.reset(); - } - } + debug!("emit: reset"); + self.state.interpreter.reset(); + self.state.traversal.reset(); } -} -/// Traversal methods. -impl ChangeListBuilder<'_> { + /// Traversal methods. + pub fn go_down(&mut self) { self.state.traversal.down(); } @@ -134,27 +113,27 @@ impl ChangeListBuilder<'_> { match mv { MoveTo::Parent => { debug!("emit: pop"); - self.state.emitter.pop(); + self.state.interpreter.pop(); } MoveTo::Child(n) => { debug!("emit: push_child({})", n); - self.state.emitter.push_child(n); + self.state.interpreter.push_child(n); } MoveTo::ReverseChild(n) => { debug!("emit: push_reverse_child({})", n); - self.state.emitter.push_reverse_child(n); + self.state.interpreter.push_reverse_child(n); } MoveTo::Sibling(n) => { debug!("emit: pop_push_child({})", n); - self.state.emitter.pop_push_child(n); + self.state.interpreter.pop_push_child(n); } MoveTo::ReverseSibling(n) => { debug!("emit: pop_push_reverse_child({})", n); - self.state.emitter.pop_push_reverse_child(n); + self.state.interpreter.pop_push_reverse_child(n); } MoveTo::TempChild(temp) => { debug!("emit: push_temporary({})", temp); - self.state.emitter.push_temporary(temp); + self.state.interpreter.push_temporary(temp); } } } @@ -184,7 +163,7 @@ impl ChangeListBuilder<'_> { ); self.next_temporary = temp_base + (end - start) as u32; self.state - .emitter + .interpreter .save_children_to_temporaries(temp_base, start as u32, end as u32); temp_base } @@ -192,45 +171,47 @@ impl ChangeListBuilder<'_> { pub fn push_temporary(&self, temp: u32) { debug_assert!(self.traversal_is_committed()); debug!("emit: push_temporary({})", temp); - self.state.emitter.push_temporary(temp); + self.state.interpreter.push_temporary(temp); } pub fn remove_child(&self, child: usize) { debug_assert!(self.traversal_is_committed()); debug!("emit: remove_child({})", child); - self.state.emitter.remove_child(child as u32); + self.state.interpreter.remove_child(child as u32); } pub fn insert_before(&self) { debug_assert!(self.traversal_is_committed()); debug!("emit: insert_before()"); - self.state.emitter.insert_before(); + self.state.interpreter.insert_before(); } pub fn ensure_string(&mut self, string: &str) -> StringKey { self.state .strings - .ensure_string(string, &self.state.emitter) + .ensure_string(string, &self.state.interpreter) } pub fn set_text(&self, text: &str) { debug_assert!(self.traversal_is_committed()); debug!("emit: set_text({:?})", text); - self.state - .emitter - .set_text(text.as_ptr() as u32, text.len() as u32); + self.state.interpreter.set_text( + text.as_ptr() as u32, + text.len() as u32, + wasm_bindgen::memory(), + ); } pub fn remove_self_and_next_siblings(&self) { debug_assert!(self.traversal_is_committed()); debug!("emit: remove_self_and_next_siblings()"); - self.state.emitter.remove_self_and_next_siblings(); + self.state.interpreter.remove_self_and_next_siblings(); } pub fn replace_with(&self) { debug_assert!(self.traversal_is_committed()); debug!("emit: replace_with()"); - self.state.emitter.replace_with(); + self.state.interpreter.replace_with(); } pub fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool) { @@ -238,13 +219,13 @@ impl ChangeListBuilder<'_> { if name == "class" && !is_namespaced { let class_id = self.ensure_string(value); debug!("emit: set_class({:?})", value); - self.state.emitter.set_class(class_id.into()); + self.state.interpreter.set_class(class_id.into()); } else { let name_id = self.ensure_string(name); let value_id = self.ensure_string(value); debug!("emit: set_attribute({:?}, {:?})", name, value); self.state - .emitter + .interpreter .set_attribute(name_id.into(), value_id.into()); } } @@ -253,28 +234,30 @@ impl ChangeListBuilder<'_> { debug_assert!(self.traversal_is_committed()); debug!("emit: remove_attribute({:?})", name); let name_id = self.ensure_string(name); - self.state.emitter.remove_attribute(name_id.into()); + self.state.interpreter.remove_attribute(name_id.into()); } pub fn append_child(&self) { debug_assert!(self.traversal_is_committed()); debug!("emit: append_child()"); - self.state.emitter.append_child(); + self.state.interpreter.append_child(); } pub fn create_text_node(&self, text: &str) { debug_assert!(self.traversal_is_committed()); debug!("emit: create_text_node({:?})", text); - self.state - .emitter - .create_text_node(text.as_ptr() as u32, text.len() as u32); + self.state.interpreter.create_text_node( + text.as_ptr() as u32, + text.len() as u32, + wasm_bindgen::memory(), + ); } pub fn create_element(&mut self, tag_name: &str) { debug_assert!(self.traversal_is_committed()); debug!("emit: create_element({:?})", tag_name); let tag_name_id = self.ensure_string(tag_name); - self.state.emitter.create_element(tag_name_id.into()); + self.state.interpreter.create_element(tag_name_id.into()); } pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) { @@ -283,7 +266,7 @@ impl ChangeListBuilder<'_> { let tag_name_id = self.ensure_string(tag_name); let ns_id = self.ensure_string(ns); self.state - .emitter + .interpreter .create_element_ns(tag_name_id.into(), ns_id.into()); } @@ -304,7 +287,9 @@ impl ChangeListBuilder<'_> { let (a, b) = listener.get_callback_parts(); debug_assert!(a != 0); let event_id = self.ensure_string(listener.event); - self.state.emitter.new_event_listener(event_id.into(), a, b); + self.state + .interpreter + .new_event_listener(event_id.into(), a, b); } pub fn update_event_listener(&mut self, listener: &Listener) { @@ -320,7 +305,7 @@ impl ChangeListBuilder<'_> { debug_assert!(a != 0); let event_id = self.ensure_string(listener.event); self.state - .emitter + .interpreter .update_event_listener(event_id.into(), a, b); } @@ -328,7 +313,9 @@ impl ChangeListBuilder<'_> { debug_assert!(self.traversal_is_committed()); debug!("emit: remove_event_listener({:?})", event); let event_id = self.ensure_string(event); - self.state.emitter.remove_event_listener(event_id.into()); + self.state + .interpreter + .remove_event_listener(event_id.into()); } #[inline] @@ -341,13 +328,13 @@ impl ChangeListBuilder<'_> { debug_assert!(!self.has_template(id)); debug!("emit: save_template({:?})", id); self.state.templates.insert(id); - self.state.emitter.save_template(id.into()); + self.state.interpreter.save_template(id.into()); } pub fn push_template(&mut self, id: CacheId) { debug_assert!(self.traversal_is_committed()); debug_assert!(self.has_template(id)); debug!("emit: push_template({:?})", id); - self.state.emitter.push_template(id.into()); + self.state.interpreter.push_template(id.into()); } } diff --git a/src/change_list/strings.rs b/src/change_list/strings.rs index d5bb554..a2b0c7b 100644 --- a/src/change_list/strings.rs +++ b/src/change_list/strings.rs @@ -1,4 +1,4 @@ -use crate::change_list::emitter::InstructionEmitter; +use super::js; use fxhash::FxHashMap; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -30,7 +30,11 @@ impl StringsCache { } /// Ensure that the given string is cached, and get its key. - pub fn ensure_string(&mut self, string: &str, emitter: &InstructionEmitter) -> StringKey { + pub fn ensure_string( + &mut self, + string: &str, + interpreter: &js::ChangeListInterpreter, + ) -> StringKey { if let Some(entry) = self.entries.get_mut(string) { entry.used = true; entry.key @@ -39,12 +43,18 @@ impl StringsCache { self.next_string_key += 1; let entry = StringsCacheEntry { key, used: true }; self.entries.insert(string.to_string(), entry); - emitter.add_cached_string(string.as_ptr() as u32, string.len() as u32, key.into()); + debug!("emit: add_cached_string({}, {:?})", string, key); + interpreter.add_cached_string( + string.as_ptr() as u32, + string.len() as u32, + key.into(), + wasm_bindgen::memory(), + ); key } } - pub fn drop_unused_strings(&mut self, emitter: &InstructionEmitter) { + pub fn drop_unused_strings(&mut self, interpreter: &js::ChangeListInterpreter) { self.entries.retain(|string, entry| { if entry.used { // Since this entry was used during while rendering this frame, @@ -58,7 +68,7 @@ impl StringsCache { } else { let key = entry.key.into(); debug!("emit: drop_cached_string({}) = {:?}", key, string); - emitter.drop_cached_string(key); + interpreter.drop_cached_string(key); false } }); diff --git a/src/diff.rs b/src/diff.rs index 139dda4..18c7442 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -26,6 +26,8 @@ pub(crate) fn diff( new: &Node, cached_roots: &mut FxHashSet, ) { + debug!("diff {:?} {:?}", new.kind, old.kind); + match (&new.kind, &old.kind) { ( &NodeKind::Text(TextNode { text: new_text }), @@ -78,7 +80,12 @@ pub(crate) fn diff( return; } diff_listeners(change_list, registry, old_listeners, new_listeners); - diff_attributes(change_list, old_attributes, new_attributes, new_namespace.is_some()); + diff_attributes( + change_list, + old_attributes, + new_attributes, + new_namespace.is_some(), + ); diff_children( cached_set, change_list, @@ -204,7 +211,12 @@ fn diff_listeners( // [... node] // // The change list stack is left unchanged. -fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new: &[Attribute], is_namespaced: bool) { +fn diff_attributes( + change_list: &mut ChangeListBuilder, + old: &[Attribute], + new: &[Attribute], + is_namespaced: bool, +) { // Do O(n^2) passes to add/update and remove attributes, since // there are almost always very few attributes. 'outer: for new_attr in new { From 5b7d2e6c813789f3f655add830af2a49f51a87fe Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 13 Apr 2020 23:07:09 +0200 Subject: [PATCH 2/7] drop js code, compiles --- Cargo.toml | 1 + src/change_list/interpreter.rs | 352 +++++++++++++++++++++++++++++++++ src/change_list/js.rs | 10 +- src/change_list/mod.rs | 42 ++-- src/change_list/strings.rs | 13 +- src/events.rs | 7 +- src/lib.rs | 2 +- src/vdom.rs | 7 +- 8 files changed, 385 insertions(+), 49 deletions(-) create mode 100644 src/change_list/interpreter.rs diff --git a/Cargo.toml b/Cargo.toml index 481938c..a1af607 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ features = [ "Event", "Node", "Window", + "Text", ] [dev-dependencies] diff --git a/src/change_list/interpreter.rs b/src/change_list/interpreter.rs new file mode 100644 index 0000000..f238432 --- /dev/null +++ b/src/change_list/interpreter.rs @@ -0,0 +1,352 @@ +use std::collections::HashMap; + +use crate::{Element, EventsTrampoline}; +use wasm_bindgen::{closure::Closure, JsCast}; +use web_sys::{window, Document, Event, Node}; + +#[derive(Debug)] +pub struct ChangeListInterpreter { + container: Element, + stack: Vec, + strings: HashMap, + temporaries: Vec, + templates: HashMap, + callback: Option>, + document: Document, +} + +impl ChangeListInterpreter { + pub fn new(container: Element) -> Self { + let document = window() + .expect("must have access to the window") + .document() + .expect("must have access to the Document"); + + Self { + container, + stack: Vec::with_capacity(5), + strings: Default::default(), + temporaries: Default::default(), + templates: Default::default(), + callback: None, + document, + } + } + + pub fn unmount(&mut self) { + self.stack.clear(); + self.strings.clear(); + self.temporaries.clear(); + self.templates.clear(); + } + + pub fn start(&mut self) { + if let Some(child) = self.container.first_child() { + self.stack.push(child); + } + } + + pub fn reset(&mut self) { + self.stack.clear(); + self.temporaries.clear(); + } + + pub fn get_cached_string(&self, id: u32) -> Option<&String> { + self.strings.get(&id) + } + + pub fn get_template(&self, id: u32) -> Option<&Node> { + self.templates.get(&id) + } + + pub fn init_events_trampoline(&mut self, mut trampoline: EventsTrampoline) { + self.callback = Some(Closure::wrap(Box::new(move |event: &web_sys::Event| { + let target = event + .target() + .expect("missing target") + .dyn_into::() + .expect("not a valid element"); + let typ = event.type_(); + let a: u32 = target + .get_attribute(&format!("dodrio-a-{}", typ)) + .and_then(|v| v.parse().ok()) + .unwrap_or_default(); + + let b: u32 = target + .get_attribute(&format!("dodrio-b-{}", typ)) + .and_then(|v| v.parse().ok()) + .unwrap_or_default(); + + // get a and b from the target + trampoline(event.clone(), a, b); + }) as Box)); + } + + /// Get the top value of the stack. + fn top(&self) -> &Node { + &self.stack[self.stack.len() - 1] + } + + // 0 + pub fn set_text(&mut self, text: &str) { + self.top().set_text_content(Some(text)); + } + + // 1 + pub fn remove_self_and_next_siblings(&mut self) { + let node = self.stack.pop().unwrap(); + let mut sibling = node.next_sibling(); + + while let Some(inner) = sibling { + let temp = inner.next_sibling(); + if let Some(sibling) = inner.dyn_ref::() { + sibling.remove(); + } + sibling = temp; + } + if let Some(node) = node.dyn_ref::() { + node.remove(); + } + } + + // 2 + pub fn replace_with(&mut self) { + let new_node = self.stack.pop().unwrap(); + let old_node = self.stack.pop().unwrap(); + old_node + .dyn_ref::() + .expect(&format!("not an element: {:?}", old_node)) + .replace_with_with_node_1(&new_node) + .unwrap(); + self.stack.push(new_node); + } + + // 3 + pub fn set_attribute(&mut self, name_id: u32, value_id: u32) { + let name = self.get_cached_string(name_id).unwrap(); + let value = self.get_cached_string(value_id).unwrap(); + let node = self.top(); + + if let Some(node) = node.dyn_ref::() { + node.set_attribute(name, value).unwrap(); + + // Some attributes are "volatile" and don't work through `setAttribute`. + // TODO: + // if name == "value" { + // node.set_value(value); + // } + // if name == "checked" { + // node.set_checked(true); + // } + // if name == "selected" { + // node.set_selected(true); + // } + } + } + + // 4 + pub fn remove_attribute(&mut self, name_id: u32) { + let name = self.get_cached_string(name_id).unwrap(); + let node = self.top(); + if let Some(node) = node.dyn_ref::() { + node.remove_attribute(name).unwrap(); + + // Some attributes are "volatile" and don't work through `removeAttribute`. + // TODO: + // if name == "value" { + // node.set_value(""); + // } + // if name == "checked" { + // node.set_checked(false); + // } + // if name == "selected" { + // node.set_selected(false); + // } + } + } + + // 5 + pub fn push_reverse_child(&mut self, n: u32) { + let parent = self.top(); + let children = parent.child_nodes(); + let child = children.get(children.length() - n - 1).unwrap(); + self.stack.push(child); + } + + // 6 + pub fn pop_push_child(&mut self, n: u32) { + self.stack.pop(); + let parent = self.top(); + let children = parent.child_nodes(); + let child = children.get(n).unwrap(); + self.stack.push(child); + } + + // 7 + pub fn pop(&mut self) { + self.stack.pop(); + } + + // 8 + pub fn append_child(&mut self) { + let child = self.stack.pop().unwrap(); + self.top().append_child(&child).unwrap(); + } + + // 9 + pub fn create_text_node(&mut self, text: &str) { + self.stack.push( + self.document + .create_text_node(text) + .dyn_into::() + .unwrap(), + ); + } + + // 10 + pub fn create_element(&mut self, tag_name_id: u32) { + let tag_name = self.get_cached_string(tag_name_id).unwrap(); + let el = self + .document + .create_element(tag_name) + .unwrap() + .dyn_into::() + .unwrap(); + self.stack.push(el); + } + + // 11 + pub fn new_event_listener(&mut self, event_id: u32, a: u32, b: u32) { + let event_type = self.get_cached_string(event_id).unwrap(); + if let Some(el) = self.top().dyn_ref::() { + el.add_event_listener_with_callback( + event_type, + self.callback.as_ref().unwrap().as_ref().unchecked_ref(), + ) + .unwrap(); + el.set_attribute(&format!("dodrio-a-{}", event_type), &a.to_string()) + .unwrap(); + el.set_attribute(&format!("dodrio-b-{}", event_type), &b.to_string()) + .unwrap(); + } + } + + // 12 + pub fn update_event_listener(&mut self, event_id: u32, a: u32, b: u32) { + let event_type = self.get_cached_string(event_id).unwrap(); + if let Some(el) = self.top().dyn_ref::() { + el.set_attribute(&format!("dodrio-a-{}", event_type), &a.to_string()) + .unwrap(); + el.set_attribute(&format!("dodrio-b-{}", event_type), &b.to_string()) + .unwrap(); + } + } + + // 13 + pub fn remove_event_listener(&mut self, event_id: u32) { + let event_type = self.get_cached_string(event_id).unwrap(); + if let Some(el) = self.top().dyn_ref::() { + el.remove_event_listener_with_callback( + event_type, + self.callback.as_ref().unwrap().as_ref().unchecked_ref(), + ) + .unwrap(); + } + } + + // 14 + pub fn add_cached_string(&mut self, string: &str, id: u32) { + self.strings.insert(id, string.into()); + } + + // 15 + pub fn drop_cached_string(&mut self, id: u32) { + self.strings.remove(&id); + } + + // 16 + pub fn create_element_ns(&mut self, tag_name_id: u32, ns_id: u32) { + let tag_name = self.get_cached_string(tag_name_id).unwrap(); + let ns = self.get_cached_string(ns_id).unwrap(); + let el = self + .document + .create_element_ns(Some(ns), tag_name) + .unwrap() + .dyn_into::() + .unwrap(); + self.stack.push(el); + } + + // 17 + pub fn save_children_to_temporaries(&mut self, mut temp: u32, start: u32, end: u32) { + let parent = self.top(); + let children = parent.child_nodes(); + for i in start..end { + temp += 1; + self.temporaries[temp as usize] = children.get(i).unwrap(); + } + } + + // 18 + pub fn push_child(&mut self, n: u32) { + let parent = self.top(); + let child = parent.child_nodes().get(n).unwrap(); + self.stack.push(child); + } + + // 19 + pub fn push_temporary(&mut self, temp: u32) { + self.stack.push(self.temporaries[temp as usize].clone()); + } + + // 20 + pub fn insert_before(&mut self) { + let before = self.stack.pop().unwrap(); + let after = self.stack.pop().unwrap(); + after + .parent_node() + .unwrap() + .insert_before(&before, Some(&after)) + .unwrap(); + self.stack.push(before); + } + + // 21 + pub fn pop_push_reverse_child(&mut self, n: u32) { + self.stack.pop(); + let parent = self.top(); + let children = parent.child_nodes(); + let child = children.get(children.length() - n - 1).unwrap(); + self.stack.push(child); + } + + // 22 + pub fn remove_child(&mut self, n: u32) { + let parent = self.top(); + if let Some(child) = parent.child_nodes().get(n).unwrap().dyn_ref::() { + child.remove(); + } + } + + // 23 + pub fn set_class(&mut self, class_id: u32) { + let class_name = self.get_cached_string(class_id).unwrap(); + if let Some(el) = self.top().dyn_ref::() { + el.set_class_name(class_name); + } + } + + // 24 + pub fn save_template(&mut self, id: u32) { + let template = self.top(); + let t = template.clone_node_with_deep(true).unwrap(); + self.templates.insert(id, t); + } + + // 25 + pub fn push_template(&mut self, id: u32) { + let template = self.get_template(id).unwrap(); + let t = template.clone_node_with_deep(true).unwrap(); + self.stack.push(t); + } +} diff --git a/src/change_list/js.rs b/src/change_list/js.rs index e451464..95c5a7d 100644 --- a/src/change_list/js.rs +++ b/src/change_list/js.rs @@ -89,11 +89,11 @@ cfg_if::cfg_if! { #[wasm_bindgen(structural, method, js_name = applyChanges)] pub fn apply_changes(this: &ChangeListInterpreter, memory: JsValue); - #[wasm_bindgen(structural, method, js_name = initEventsTrampoline)] - pub fn init_events_trampoline( - this: &ChangeListInterpreter, - trampoline: &crate::EventsTrampoline, - ); + // #[wasm_bindgen(structural, method, js_name = initEventsTrampoline)] + // pub fn init_events_trampoline( + // this: &ChangeListInterpreter, + // trampoline: &crate::EventsTrampoline, + // ); #[wasm_bindgen(structural, method, js_name = start)] pub fn start(this: &ChangeListInterpreter); diff --git a/src/change_list/mod.rs b/src/change_list/mod.rs index 51b8740..d206d50 100644 --- a/src/change_list/mod.rs +++ b/src/change_list/mod.rs @@ -1,9 +1,11 @@ +pub(crate) mod interpreter; pub(crate) mod strings; pub(crate) mod traversal; // Note: has to be `pub` because of `wasm-bindgen` visibility restrictions. pub mod js; +use self::interpreter::ChangeListInterpreter; use self::strings::{StringKey, StringsCache}; use self::traversal::{MoveTo, Traversal}; use crate::{cached_set::CacheId, Listener}; @@ -13,7 +15,7 @@ use fxhash::FxHashSet; pub(crate) struct ChangeListPersistentState { strings: StringsCache, traversal: Traversal, - interpreter: js::ChangeListInterpreter, + interpreter: ChangeListInterpreter, templates: FxHashSet, } @@ -33,7 +35,7 @@ impl ChangeListPersistentState { pub(crate) fn new(container: &crate::Element) -> ChangeListPersistentState { let strings = StringsCache::new(); let traversal = Traversal::new(); - let interpreter = js::ChangeListInterpreter::new(container); + let interpreter = ChangeListInterpreter::new(container.clone()); let templates = Default::default(); ChangeListPersistentState { @@ -44,11 +46,11 @@ impl ChangeListPersistentState { } } - pub(crate) fn init_events_trampoline(&mut self, trampoline: &crate::EventsTrampoline) { + pub(crate) fn init_events_trampoline(&mut self, trampoline: crate::EventsTrampoline) { self.interpreter.init_events_trampoline(trampoline); } - pub(crate) fn builder<'a>(&'a mut self) -> ChangeListBuilder<'a> { + pub(crate) fn builder(&mut self) -> ChangeListBuilder { let builder = ChangeListBuilder { state: self, next_temporary: 0, @@ -64,7 +66,7 @@ impl ChangeListBuilder<'_> { pub(crate) fn finish(self) { self.state .strings - .drop_unused_strings(&self.state.interpreter); + .drop_unused_strings(&mut self.state.interpreter); debug!("emit: reset"); self.state.interpreter.reset(); @@ -168,19 +170,19 @@ impl ChangeListBuilder<'_> { temp_base } - pub fn push_temporary(&self, temp: u32) { + pub fn push_temporary(&mut self, temp: u32) { debug_assert!(self.traversal_is_committed()); debug!("emit: push_temporary({})", temp); self.state.interpreter.push_temporary(temp); } - pub fn remove_child(&self, child: usize) { + pub fn remove_child(&mut self, child: usize) { debug_assert!(self.traversal_is_committed()); debug!("emit: remove_child({})", child); self.state.interpreter.remove_child(child as u32); } - pub fn insert_before(&self) { + pub fn insert_before(&mut self) { debug_assert!(self.traversal_is_committed()); debug!("emit: insert_before()"); self.state.interpreter.insert_before(); @@ -189,26 +191,22 @@ impl ChangeListBuilder<'_> { pub fn ensure_string(&mut self, string: &str) -> StringKey { self.state .strings - .ensure_string(string, &self.state.interpreter) + .ensure_string(string, &mut self.state.interpreter) } - pub fn set_text(&self, text: &str) { + pub fn set_text(&mut self, text: &str) { debug_assert!(self.traversal_is_committed()); debug!("emit: set_text({:?})", text); - self.state.interpreter.set_text( - text.as_ptr() as u32, - text.len() as u32, - wasm_bindgen::memory(), - ); + self.state.interpreter.set_text(text); } - pub fn remove_self_and_next_siblings(&self) { + pub fn remove_self_and_next_siblings(&mut self) { debug_assert!(self.traversal_is_committed()); debug!("emit: remove_self_and_next_siblings()"); self.state.interpreter.remove_self_and_next_siblings(); } - pub fn replace_with(&self) { + pub fn replace_with(&mut self) { debug_assert!(self.traversal_is_committed()); debug!("emit: replace_with()"); self.state.interpreter.replace_with(); @@ -237,20 +235,16 @@ impl ChangeListBuilder<'_> { self.state.interpreter.remove_attribute(name_id.into()); } - pub fn append_child(&self) { + pub fn append_child(&mut self) { debug_assert!(self.traversal_is_committed()); debug!("emit: append_child()"); self.state.interpreter.append_child(); } - pub fn create_text_node(&self, text: &str) { + pub fn create_text_node(&mut self, text: &str) { debug_assert!(self.traversal_is_committed()); debug!("emit: create_text_node({:?})", text); - self.state.interpreter.create_text_node( - text.as_ptr() as u32, - text.len() as u32, - wasm_bindgen::memory(), - ); + self.state.interpreter.create_text_node(text); } pub fn create_element(&mut self, tag_name: &str) { diff --git a/src/change_list/strings.rs b/src/change_list/strings.rs index a2b0c7b..1daefeb 100644 --- a/src/change_list/strings.rs +++ b/src/change_list/strings.rs @@ -1,4 +1,4 @@ -use super::js; +use super::interpreter::ChangeListInterpreter; use fxhash::FxHashMap; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -33,7 +33,7 @@ impl StringsCache { pub fn ensure_string( &mut self, string: &str, - interpreter: &js::ChangeListInterpreter, + interpreter: &mut ChangeListInterpreter, ) -> StringKey { if let Some(entry) = self.entries.get_mut(string) { entry.used = true; @@ -44,17 +44,12 @@ impl StringsCache { let entry = StringsCacheEntry { key, used: true }; self.entries.insert(string.to_string(), entry); debug!("emit: add_cached_string({}, {:?})", string, key); - interpreter.add_cached_string( - string.as_ptr() as u32, - string.len() as u32, - key.into(), - wasm_bindgen::memory(), - ); + interpreter.add_cached_string(string, key.into()); key } } - pub fn drop_unused_strings(&mut self, interpreter: &js::ChangeListInterpreter) { + pub fn drop_unused_strings(&mut self, interpreter: &mut ChangeListInterpreter) { self.entries.retain(|string, entry| { if entry.used { // Since this entry was used during while rendering this frame, diff --git a/src/events.rs b/src/events.rs index aac74b1..6ee762b 100644 --- a/src/events.rs +++ b/src/events.rs @@ -26,7 +26,6 @@ cfg_if::cfg_if! { use fxhash::FxHashMap; use std::fmt; use std::mem; - use wasm_bindgen::closure::Closure; use wasm_bindgen::prelude::*; /// The events registry manages event listeners for a virtual DOM. @@ -62,7 +61,7 @@ cfg_if::cfg_if! { })); let weak_registry = Rc::downgrade(®istry); - let closure = Closure::wrap(Box::new(move |event, a, b| { + let closure = move |event: web_sys::Event, a: u32, b: u32| { debug_assert!(a != 0); // if the VdomInnerExclusive is keeping this closure alive, then the @@ -86,9 +85,9 @@ cfg_if::cfg_if! { callback(component, vdom_weak, event); } } - }) as Box); + }; - (registry, closure) + (registry, Box::new(closure)) } pub(crate) fn remove(&mut self, listener: &Listener) { diff --git a/src/lib.rs b/src/lib.rs index a6c7fd6..9a3e8a5 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,7 +123,7 @@ cfg_if::cfg_if! { /// An element node in the physical DOM. pub type Element = web_sys::Element; - pub(crate) type EventsTrampoline = wasm_bindgen::closure::Closure; + pub(crate) type EventsTrampoline = Box; } } diff --git a/src/vdom.rs b/src/vdom.rs index c2bbe07..1963cb3 100644 --- a/src/vdom.rs +++ b/src/vdom.rs @@ -61,7 +61,6 @@ pub(crate) struct VdomInnerExclusive { change_list: ManuallyDrop, container: crate::Element, events_registry: Option>>, - events_trampoline: Option, cached_set: crate::RefCell, templates: FxHashMap>, @@ -96,7 +95,6 @@ impl fmt::Debug for VdomInnerExclusive { .field("change_list", &self.change_list) .field("container", &self.container) .field("events_registry", &self.events_registry) - .field("events_trampoline", &"..") .field("current_root", &self.current_root) .finish() } @@ -190,7 +188,6 @@ impl Vdom { container, current_root, events_registry: None, - events_trampoline: None, cached_set: crate::RefCell::new(Default::default()), templates: Default::default(), }), @@ -201,9 +198,7 @@ impl Vdom { { let mut inner = inner.exclusive.borrow_mut(); inner.events_registry = Some(events_registry); - inner.change_list.init_events_trampoline(&events_trampoline); - debug_assert!(inner.events_trampoline.is_none()); - inner.events_trampoline = Some(events_trampoline); + inner.change_list.init_events_trampoline(events_trampoline); // Diff and apply the `contents` against our dummy `
`. inner.render(); From 7b86793ed9539fdf9b1523739ebd9b17e3e549fe Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 13 Apr 2020 23:19:57 +0200 Subject: [PATCH 3/7] debugging --- src/change_list/interpreter.rs | 78 ++++++++++++++++++++++------------ src/change_list/mod.rs | 1 + 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/change_list/interpreter.rs b/src/change_list/interpreter.rs index f238432..d713f9f 100644 --- a/src/change_list/interpreter.rs +++ b/src/change_list/interpreter.rs @@ -7,7 +7,7 @@ use web_sys::{window, Document, Event, Node}; #[derive(Debug)] pub struct ChangeListInterpreter { container: Element, - stack: Vec, + stack: Stack, strings: HashMap, temporaries: Vec, templates: HashMap, @@ -15,6 +15,33 @@ pub struct ChangeListInterpreter { document: Document, } +#[derive(Debug, Default)] +struct Stack { + list: Vec, +} + +impl Stack { + pub fn push(&mut self, node: Node) { + debug!("stack-push: {:?}", node); + self.list.push(node); + } + + pub fn pop(&mut self) -> Node { + let res = self.list.pop().unwrap(); + debug!("stack-pop: {:?}", res); + + res + } + + pub fn clear(&mut self) { + self.list.clear(); + } + + pub fn top(&self) -> &Node { + &self.list[self.list.len() - 1] + } +} + impl ChangeListInterpreter { pub fn new(container: Element) -> Self { let document = window() @@ -24,7 +51,7 @@ impl ChangeListInterpreter { Self { container, - stack: Vec::with_capacity(5), + stack: Default::default(), strings: Default::default(), temporaries: Default::default(), templates: Default::default(), @@ -82,19 +109,14 @@ impl ChangeListInterpreter { }) as Box)); } - /// Get the top value of the stack. - fn top(&self) -> &Node { - &self.stack[self.stack.len() - 1] - } - // 0 pub fn set_text(&mut self, text: &str) { - self.top().set_text_content(Some(text)); + self.stack.top().set_text_content(Some(text)); } // 1 pub fn remove_self_and_next_siblings(&mut self) { - let node = self.stack.pop().unwrap(); + let node = self.stack.pop(); let mut sibling = node.next_sibling(); while let Some(inner) = sibling { @@ -111,8 +133,8 @@ impl ChangeListInterpreter { // 2 pub fn replace_with(&mut self) { - let new_node = self.stack.pop().unwrap(); - let old_node = self.stack.pop().unwrap(); + let new_node = self.stack.pop(); + let old_node = self.stack.pop(); old_node .dyn_ref::() .expect(&format!("not an element: {:?}", old_node)) @@ -125,7 +147,7 @@ impl ChangeListInterpreter { pub fn set_attribute(&mut self, name_id: u32, value_id: u32) { let name = self.get_cached_string(name_id).unwrap(); let value = self.get_cached_string(value_id).unwrap(); - let node = self.top(); + let node = self.stack.top(); if let Some(node) = node.dyn_ref::() { node.set_attribute(name, value).unwrap(); @@ -147,7 +169,7 @@ impl ChangeListInterpreter { // 4 pub fn remove_attribute(&mut self, name_id: u32) { let name = self.get_cached_string(name_id).unwrap(); - let node = self.top(); + let node = self.stack.top(); if let Some(node) = node.dyn_ref::() { node.remove_attribute(name).unwrap(); @@ -167,7 +189,7 @@ impl ChangeListInterpreter { // 5 pub fn push_reverse_child(&mut self, n: u32) { - let parent = self.top(); + let parent = self.stack.top(); let children = parent.child_nodes(); let child = children.get(children.length() - n - 1).unwrap(); self.stack.push(child); @@ -176,7 +198,7 @@ impl ChangeListInterpreter { // 6 pub fn pop_push_child(&mut self, n: u32) { self.stack.pop(); - let parent = self.top(); + let parent = self.stack.top(); let children = parent.child_nodes(); let child = children.get(n).unwrap(); self.stack.push(child); @@ -189,8 +211,8 @@ impl ChangeListInterpreter { // 8 pub fn append_child(&mut self) { - let child = self.stack.pop().unwrap(); - self.top().append_child(&child).unwrap(); + let child = self.stack.pop(); + self.stack.top().append_child(&child).unwrap(); } // 9 @@ -218,7 +240,7 @@ impl ChangeListInterpreter { // 11 pub fn new_event_listener(&mut self, event_id: u32, a: u32, b: u32) { let event_type = self.get_cached_string(event_id).unwrap(); - if let Some(el) = self.top().dyn_ref::() { + if let Some(el) = self.stack.top().dyn_ref::() { el.add_event_listener_with_callback( event_type, self.callback.as_ref().unwrap().as_ref().unchecked_ref(), @@ -234,7 +256,7 @@ impl ChangeListInterpreter { // 12 pub fn update_event_listener(&mut self, event_id: u32, a: u32, b: u32) { let event_type = self.get_cached_string(event_id).unwrap(); - if let Some(el) = self.top().dyn_ref::() { + if let Some(el) = self.stack.top().dyn_ref::() { el.set_attribute(&format!("dodrio-a-{}", event_type), &a.to_string()) .unwrap(); el.set_attribute(&format!("dodrio-b-{}", event_type), &b.to_string()) @@ -245,7 +267,7 @@ impl ChangeListInterpreter { // 13 pub fn remove_event_listener(&mut self, event_id: u32) { let event_type = self.get_cached_string(event_id).unwrap(); - if let Some(el) = self.top().dyn_ref::() { + if let Some(el) = self.stack.top().dyn_ref::() { el.remove_event_listener_with_callback( event_type, self.callback.as_ref().unwrap().as_ref().unchecked_ref(), @@ -279,7 +301,7 @@ impl ChangeListInterpreter { // 17 pub fn save_children_to_temporaries(&mut self, mut temp: u32, start: u32, end: u32) { - let parent = self.top(); + let parent = self.stack.top(); let children = parent.child_nodes(); for i in start..end { temp += 1; @@ -289,7 +311,7 @@ impl ChangeListInterpreter { // 18 pub fn push_child(&mut self, n: u32) { - let parent = self.top(); + let parent = self.stack.top(); let child = parent.child_nodes().get(n).unwrap(); self.stack.push(child); } @@ -301,8 +323,8 @@ impl ChangeListInterpreter { // 20 pub fn insert_before(&mut self) { - let before = self.stack.pop().unwrap(); - let after = self.stack.pop().unwrap(); + let before = self.stack.pop(); + let after = self.stack.pop(); after .parent_node() .unwrap() @@ -314,7 +336,7 @@ impl ChangeListInterpreter { // 21 pub fn pop_push_reverse_child(&mut self, n: u32) { self.stack.pop(); - let parent = self.top(); + let parent = self.stack.top(); let children = parent.child_nodes(); let child = children.get(children.length() - n - 1).unwrap(); self.stack.push(child); @@ -322,7 +344,7 @@ impl ChangeListInterpreter { // 22 pub fn remove_child(&mut self, n: u32) { - let parent = self.top(); + let parent = self.stack.top(); if let Some(child) = parent.child_nodes().get(n).unwrap().dyn_ref::() { child.remove(); } @@ -331,14 +353,14 @@ impl ChangeListInterpreter { // 23 pub fn set_class(&mut self, class_id: u32) { let class_name = self.get_cached_string(class_id).unwrap(); - if let Some(el) = self.top().dyn_ref::() { + if let Some(el) = self.stack.top().dyn_ref::() { el.set_class_name(class_name); } } // 24 pub fn save_template(&mut self, id: u32) { - let template = self.top(); + let template = self.stack.top(); let t = template.clone_node_with_deep(true).unwrap(); self.templates.insert(id, t); } diff --git a/src/change_list/mod.rs b/src/change_list/mod.rs index d206d50..20700dc 100644 --- a/src/change_list/mod.rs +++ b/src/change_list/mod.rs @@ -56,6 +56,7 @@ impl ChangeListPersistentState { next_temporary: 0, forcing_new_listeners: false, }; + debug!("emit: start"); builder.state.interpreter.start(); builder From c1b76f10a426e1f5019f8b75e24e3f55037ede9b Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 13 Apr 2020 23:46:33 +0200 Subject: [PATCH 4/7] almost all tests pass --- Cargo.toml | 2 ++ src/change_list/interpreter.rs | 59 +++++++++++++++++++++++----------- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a1af607..cce6883 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,8 @@ features = [ "Node", "Window", "Text", + "DocumentType", + "CharacterData", ] [dev-dependencies] diff --git a/src/change_list/interpreter.rs b/src/change_list/interpreter.rs index d713f9f..e836631 100644 --- a/src/change_list/interpreter.rs +++ b/src/change_list/interpreter.rs @@ -9,7 +9,7 @@ pub struct ChangeListInterpreter { container: Element, stack: Stack, strings: HashMap, - temporaries: Vec, + temporaries: HashMap, templates: HashMap, callback: Option>, document: Document, @@ -135,11 +135,29 @@ impl ChangeListInterpreter { pub fn replace_with(&mut self) { let new_node = self.stack.pop(); let old_node = self.stack.pop(); - old_node - .dyn_ref::() - .expect(&format!("not an element: {:?}", old_node)) - .replace_with_with_node_1(&new_node) - .unwrap(); + + if old_node.has_type::() { + old_node + .dyn_ref::() + .unwrap() + .replace_with_with_node_1(&new_node) + .unwrap(); + } else if old_node.has_type::() { + old_node + .dyn_ref::() + .unwrap() + .replace_with_with_node_1(&new_node) + .unwrap(); + } else if old_node.has_type::() { + old_node + .dyn_ref::() + .unwrap() + .replace_with_with_node_1(&new_node) + .unwrap(); + } else { + panic!("Cannot replace node: {:?}", old_node); + } + self.stack.push(new_node); } @@ -240,17 +258,21 @@ impl ChangeListInterpreter { // 11 pub fn new_event_listener(&mut self, event_id: u32, a: u32, b: u32) { let event_type = self.get_cached_string(event_id).unwrap(); - if let Some(el) = self.stack.top().dyn_ref::() { - el.add_event_listener_with_callback( - event_type, - self.callback.as_ref().unwrap().as_ref().unchecked_ref(), - ) + let el = self.stack.top(); + + let el = el + .dyn_ref::() + .expect(&format!("not an element: {:?}", el)); + el.add_event_listener_with_callback( + event_type, + self.callback.as_ref().unwrap().as_ref().unchecked_ref(), + ) + .unwrap(); + debug!("adding attributes: {}, {}", a, b); + el.set_attribute(&format!("dodrio-a-{}", event_type), &a.to_string()) + .unwrap(); + el.set_attribute(&format!("dodrio-b-{}", event_type), &b.to_string()) .unwrap(); - el.set_attribute(&format!("dodrio-a-{}", event_type), &a.to_string()) - .unwrap(); - el.set_attribute(&format!("dodrio-b-{}", event_type), &b.to_string()) - .unwrap(); - } } // 12 @@ -304,8 +326,8 @@ impl ChangeListInterpreter { let parent = self.stack.top(); let children = parent.child_nodes(); for i in start..end { + self.temporaries.insert(temp, children.get(i).unwrap()); temp += 1; - self.temporaries[temp as usize] = children.get(i).unwrap(); } } @@ -318,7 +340,8 @@ impl ChangeListInterpreter { // 19 pub fn push_temporary(&mut self, temp: u32) { - self.stack.push(self.temporaries[temp as usize].clone()); + let t = self.temporaries.get(&temp).unwrap().clone(); + self.stack.push(t); } // 20 From c0fe4a5ca7476e37d3c2c8373136bc458ca9e3eb Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 13 Apr 2020 23:59:38 +0200 Subject: [PATCH 5/7] remove strings cache --- examples/game-of-life/Cargo.toml | 3 ++ src/change_list/interpreter.rs | 43 ++++--------------- src/change_list/mod.rs | 49 +++++----------------- src/change_list/strings.rs | 71 -------------------------------- 4 files changed, 21 insertions(+), 145 deletions(-) delete mode 100644 src/change_list/strings.rs diff --git a/examples/game-of-life/Cargo.toml b/examples/game-of-life/Cargo.toml index 1f7a1cc..0168ded 100644 --- a/examples/game-of-life/Cargo.toml +++ b/examples/game-of-life/Cargo.toml @@ -23,6 +23,9 @@ features = [ "HtmlElement", "Node", "Window", + "Event", + "EventTarget", + "NodeList", ] [dev-dependencies] diff --git a/src/change_list/interpreter.rs b/src/change_list/interpreter.rs index e836631..29fedd8 100644 --- a/src/change_list/interpreter.rs +++ b/src/change_list/interpreter.rs @@ -8,7 +8,6 @@ use web_sys::{window, Document, Event, Node}; pub struct ChangeListInterpreter { container: Element, stack: Stack, - strings: HashMap, temporaries: HashMap, templates: HashMap, callback: Option>, @@ -52,7 +51,6 @@ impl ChangeListInterpreter { Self { container, stack: Default::default(), - strings: Default::default(), temporaries: Default::default(), templates: Default::default(), callback: None, @@ -62,7 +60,6 @@ impl ChangeListInterpreter { pub fn unmount(&mut self) { self.stack.clear(); - self.strings.clear(); self.temporaries.clear(); self.templates.clear(); } @@ -78,10 +75,6 @@ impl ChangeListInterpreter { self.temporaries.clear(); } - pub fn get_cached_string(&self, id: u32) -> Option<&String> { - self.strings.get(&id) - } - pub fn get_template(&self, id: u32) -> Option<&Node> { self.templates.get(&id) } @@ -162,9 +155,7 @@ impl ChangeListInterpreter { } // 3 - pub fn set_attribute(&mut self, name_id: u32, value_id: u32) { - let name = self.get_cached_string(name_id).unwrap(); - let value = self.get_cached_string(value_id).unwrap(); + pub fn set_attribute(&mut self, name: &str, value: &str) { let node = self.stack.top(); if let Some(node) = node.dyn_ref::() { @@ -185,8 +176,7 @@ impl ChangeListInterpreter { } // 4 - pub fn remove_attribute(&mut self, name_id: u32) { - let name = self.get_cached_string(name_id).unwrap(); + pub fn remove_attribute(&mut self, name: &str) { let node = self.stack.top(); if let Some(node) = node.dyn_ref::() { node.remove_attribute(name).unwrap(); @@ -244,8 +234,7 @@ impl ChangeListInterpreter { } // 10 - pub fn create_element(&mut self, tag_name_id: u32) { - let tag_name = self.get_cached_string(tag_name_id).unwrap(); + pub fn create_element(&mut self, tag_name: &str) { let el = self .document .create_element(tag_name) @@ -256,8 +245,7 @@ impl ChangeListInterpreter { } // 11 - pub fn new_event_listener(&mut self, event_id: u32, a: u32, b: u32) { - let event_type = self.get_cached_string(event_id).unwrap(); + pub fn new_event_listener(&mut self, event_type: &str, a: u32, b: u32) { let el = self.stack.top(); let el = el @@ -276,8 +264,7 @@ impl ChangeListInterpreter { } // 12 - pub fn update_event_listener(&mut self, event_id: u32, a: u32, b: u32) { - let event_type = self.get_cached_string(event_id).unwrap(); + pub fn update_event_listener(&mut self, event_type: &str, a: u32, b: u32) { if let Some(el) = self.stack.top().dyn_ref::() { el.set_attribute(&format!("dodrio-a-{}", event_type), &a.to_string()) .unwrap(); @@ -287,8 +274,7 @@ impl ChangeListInterpreter { } // 13 - pub fn remove_event_listener(&mut self, event_id: u32) { - let event_type = self.get_cached_string(event_id).unwrap(); + pub fn remove_event_listener(&mut self, event_type: &str) { if let Some(el) = self.stack.top().dyn_ref::() { el.remove_event_listener_with_callback( event_type, @@ -298,20 +284,8 @@ impl ChangeListInterpreter { } } - // 14 - pub fn add_cached_string(&mut self, string: &str, id: u32) { - self.strings.insert(id, string.into()); - } - - // 15 - pub fn drop_cached_string(&mut self, id: u32) { - self.strings.remove(&id); - } - // 16 - pub fn create_element_ns(&mut self, tag_name_id: u32, ns_id: u32) { - let tag_name = self.get_cached_string(tag_name_id).unwrap(); - let ns = self.get_cached_string(ns_id).unwrap(); + pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) { let el = self .document .create_element_ns(Some(ns), tag_name) @@ -374,8 +348,7 @@ impl ChangeListInterpreter { } // 23 - pub fn set_class(&mut self, class_id: u32) { - let class_name = self.get_cached_string(class_id).unwrap(); + pub fn set_class(&mut self, class_name: &str) { if let Some(el) = self.stack.top().dyn_ref::() { el.set_class_name(class_name); } diff --git a/src/change_list/mod.rs b/src/change_list/mod.rs index 20700dc..4474d42 100644 --- a/src/change_list/mod.rs +++ b/src/change_list/mod.rs @@ -1,19 +1,16 @@ pub(crate) mod interpreter; -pub(crate) mod strings; pub(crate) mod traversal; // Note: has to be `pub` because of `wasm-bindgen` visibility restrictions. pub mod js; use self::interpreter::ChangeListInterpreter; -use self::strings::{StringKey, StringsCache}; use self::traversal::{MoveTo, Traversal}; use crate::{cached_set::CacheId, Listener}; use fxhash::FxHashSet; #[derive(Debug)] pub(crate) struct ChangeListPersistentState { - strings: StringsCache, traversal: Traversal, interpreter: ChangeListInterpreter, templates: FxHashSet, @@ -33,13 +30,11 @@ impl Drop for ChangeListPersistentState { impl ChangeListPersistentState { pub(crate) fn new(container: &crate::Element) -> ChangeListPersistentState { - let strings = StringsCache::new(); let traversal = Traversal::new(); let interpreter = ChangeListInterpreter::new(container.clone()); let templates = Default::default(); ChangeListPersistentState { - strings, traversal, interpreter, templates, @@ -65,10 +60,6 @@ impl ChangeListPersistentState { impl ChangeListBuilder<'_> { pub(crate) fn finish(self) { - self.state - .strings - .drop_unused_strings(&mut self.state.interpreter); - debug!("emit: reset"); self.state.interpreter.reset(); self.state.traversal.reset(); @@ -189,12 +180,6 @@ impl ChangeListBuilder<'_> { self.state.interpreter.insert_before(); } - pub fn ensure_string(&mut self, string: &str) -> StringKey { - self.state - .strings - .ensure_string(string, &mut self.state.interpreter) - } - pub fn set_text(&mut self, text: &str) { debug_assert!(self.traversal_is_committed()); debug!("emit: set_text({:?})", text); @@ -216,24 +201,18 @@ impl ChangeListBuilder<'_> { pub fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool) { debug_assert!(self.traversal_is_committed()); if name == "class" && !is_namespaced { - let class_id = self.ensure_string(value); debug!("emit: set_class({:?})", value); - self.state.interpreter.set_class(class_id.into()); + self.state.interpreter.set_class(value); } else { - let name_id = self.ensure_string(name); - let value_id = self.ensure_string(value); debug!("emit: set_attribute({:?}, {:?})", name, value); - self.state - .interpreter - .set_attribute(name_id.into(), value_id.into()); + self.state.interpreter.set_attribute(name, value); } } pub fn remove_attribute(&mut self, name: &str) { debug_assert!(self.traversal_is_committed()); debug!("emit: remove_attribute({:?})", name); - let name_id = self.ensure_string(name); - self.state.interpreter.remove_attribute(name_id.into()); + self.state.interpreter.remove_attribute(name); } pub fn append_child(&mut self) { @@ -251,18 +230,13 @@ impl ChangeListBuilder<'_> { pub fn create_element(&mut self, tag_name: &str) { debug_assert!(self.traversal_is_committed()); debug!("emit: create_element({:?})", tag_name); - let tag_name_id = self.ensure_string(tag_name); - self.state.interpreter.create_element(tag_name_id.into()); + self.state.interpreter.create_element(tag_name); } pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) { debug_assert!(self.traversal_is_committed()); debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns); - let tag_name_id = self.ensure_string(tag_name); - let ns_id = self.ensure_string(ns); - self.state - .interpreter - .create_element_ns(tag_name_id.into(), ns_id.into()); + self.state.interpreter.create_element_ns(tag_name, ns); } pub fn push_force_new_listeners(&mut self) -> bool { @@ -281,10 +255,10 @@ impl ChangeListBuilder<'_> { debug!("emit: new_event_listener({:?})", listener); let (a, b) = listener.get_callback_parts(); debug_assert!(a != 0); - let event_id = self.ensure_string(listener.event); + self.state .interpreter - .new_event_listener(event_id.into(), a, b); + .new_event_listener(listener.event, a, b); } pub fn update_event_listener(&mut self, listener: &Listener) { @@ -298,19 +272,16 @@ impl ChangeListBuilder<'_> { debug!("emit: update_event_listener({:?})", listener); let (a, b) = listener.get_callback_parts(); debug_assert!(a != 0); - let event_id = self.ensure_string(listener.event); self.state .interpreter - .update_event_listener(event_id.into(), a, b); + .update_event_listener(listener.event, a, b); } pub fn remove_event_listener(&mut self, event: &str) { debug_assert!(self.traversal_is_committed()); debug!("emit: remove_event_listener({:?})", event); - let event_id = self.ensure_string(event); - self.state - .interpreter - .remove_event_listener(event_id.into()); + + self.state.interpreter.remove_event_listener(event); } #[inline] diff --git a/src/change_list/strings.rs b/src/change_list/strings.rs deleted file mode 100644 index 1daefeb..0000000 --- a/src/change_list/strings.rs +++ /dev/null @@ -1,71 +0,0 @@ -use super::interpreter::ChangeListInterpreter; -use fxhash::FxHashMap; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct StringKey(u32); - -impl From for u32 { - #[inline] - fn from(key: StringKey) -> u32 { - key.0 - } -} - -#[derive(Debug)] -struct StringsCacheEntry { - key: StringKey, - used: bool, -} - -#[derive(Debug, Default)] -pub(crate) struct StringsCache { - entries: FxHashMap, - next_string_key: u32, -} - -impl StringsCache { - /// Create a new, empty strings cache. - pub fn new() -> StringsCache { - Default::default() - } - - /// Ensure that the given string is cached, and get its key. - pub fn ensure_string( - &mut self, - string: &str, - interpreter: &mut ChangeListInterpreter, - ) -> StringKey { - if let Some(entry) = self.entries.get_mut(string) { - entry.used = true; - entry.key - } else { - let key = StringKey(self.next_string_key); - self.next_string_key += 1; - let entry = StringsCacheEntry { key, used: true }; - self.entries.insert(string.to_string(), entry); - debug!("emit: add_cached_string({}, {:?})", string, key); - interpreter.add_cached_string(string, key.into()); - key - } - } - - pub fn drop_unused_strings(&mut self, interpreter: &mut ChangeListInterpreter) { - self.entries.retain(|string, entry| { - if entry.used { - // Since this entry was used during while rendering this frame, - // it is likely going to be used for the next frame as well. So - // we keep it in the cache so that we don't have to repopulate - // and resync the cache next frame (assuming it is reused), but - // we set the `used` flag to false so that if it is not used - // next frame, it will be cleaned up. - entry.used = false; - true - } else { - let key = entry.key.into(); - debug!("emit: drop_cached_string({}) = {:?}", key, string); - interpreter.drop_cached_string(key); - false - } - }); - } -} From 13fd09e4bb4a5abfddf251cf0a4b2af0ecea75f3 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 14 Apr 2020 00:19:17 +0200 Subject: [PATCH 6/7] cleanup temps --- examples/sierpinski-triangle/Cargo.toml | 3 +++ src/change_list/interpreter.rs | 28 +++++++++++++++++-------- src/change_list/mod.rs | 11 +++------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/examples/sierpinski-triangle/Cargo.toml b/examples/sierpinski-triangle/Cargo.toml index ce964ce..ea6af16 100644 --- a/examples/sierpinski-triangle/Cargo.toml +++ b/examples/sierpinski-triangle/Cargo.toml @@ -22,6 +22,9 @@ features = [ "Node", "Performance", "Window", + "Event", + "EventTarget", + "NodeList", ] [dev-dependencies] diff --git a/src/change_list/interpreter.rs b/src/change_list/interpreter.rs index 29fedd8..2b011b1 100644 --- a/src/change_list/interpreter.rs +++ b/src/change_list/interpreter.rs @@ -1,15 +1,15 @@ -use std::collections::HashMap; - +use crate::cached_set::CacheId; use crate::{Element, EventsTrampoline}; +use fxhash::FxHashMap; use wasm_bindgen::{closure::Closure, JsCast}; use web_sys::{window, Document, Event, Node}; #[derive(Debug)] -pub struct ChangeListInterpreter { +pub(crate) struct ChangeListInterpreter { container: Element, stack: Stack, - temporaries: HashMap, - templates: HashMap, + temporaries: FxHashMap, + templates: FxHashMap, callback: Option>, document: Document, } @@ -20,6 +20,12 @@ struct Stack { } impl Stack { + pub fn with_capacity(cap: usize) -> Self { + Stack { + list: Vec::with_capacity(cap), + } + } + pub fn push(&mut self, node: Node) { debug!("stack-push: {:?}", node); self.list.push(node); @@ -50,7 +56,7 @@ impl ChangeListInterpreter { Self { container, - stack: Default::default(), + stack: Stack::with_capacity(20), temporaries: Default::default(), templates: Default::default(), callback: None, @@ -75,7 +81,7 @@ impl ChangeListInterpreter { self.temporaries.clear(); } - pub fn get_template(&self, id: u32) -> Option<&Node> { + pub fn get_template(&self, id: CacheId) -> Option<&Node> { self.templates.get(&id) } @@ -355,16 +361,20 @@ impl ChangeListInterpreter { } // 24 - pub fn save_template(&mut self, id: u32) { + pub fn save_template(&mut self, id: CacheId) { let template = self.stack.top(); let t = template.clone_node_with_deep(true).unwrap(); self.templates.insert(id, t); } // 25 - pub fn push_template(&mut self, id: u32) { + pub fn push_template(&mut self, id: CacheId) { let template = self.get_template(id).unwrap(); let t = template.clone_node_with_deep(true).unwrap(); self.stack.push(t); } + + pub fn has_template(&self, id: CacheId) -> bool { + self.templates.contains_key(&id) + } } diff --git a/src/change_list/mod.rs b/src/change_list/mod.rs index 4474d42..4ffd3f9 100644 --- a/src/change_list/mod.rs +++ b/src/change_list/mod.rs @@ -7,13 +7,11 @@ pub mod js; use self::interpreter::ChangeListInterpreter; use self::traversal::{MoveTo, Traversal}; use crate::{cached_set::CacheId, Listener}; -use fxhash::FxHashSet; #[derive(Debug)] pub(crate) struct ChangeListPersistentState { traversal: Traversal, interpreter: ChangeListInterpreter, - templates: FxHashSet, } pub(crate) struct ChangeListBuilder<'a> { @@ -32,12 +30,10 @@ impl ChangeListPersistentState { pub(crate) fn new(container: &crate::Element) -> ChangeListPersistentState { let traversal = Traversal::new(); let interpreter = ChangeListInterpreter::new(container.clone()); - let templates = Default::default(); ChangeListPersistentState { traversal, interpreter, - templates, } } @@ -286,21 +282,20 @@ impl ChangeListBuilder<'_> { #[inline] pub fn has_template(&mut self, id: CacheId) -> bool { - self.state.templates.contains(&id) + self.state.interpreter.has_template(id) } pub fn save_template(&mut self, id: CacheId) { debug_assert!(self.traversal_is_committed()); debug_assert!(!self.has_template(id)); debug!("emit: save_template({:?})", id); - self.state.templates.insert(id); - self.state.interpreter.save_template(id.into()); + self.state.interpreter.save_template(id); } pub fn push_template(&mut self, id: CacheId) { debug_assert!(self.traversal_is_committed()); debug_assert!(self.has_template(id)); debug!("emit: push_template({:?})", id); - self.state.interpreter.push_template(id.into()); + self.state.interpreter.push_template(id); } } From c989a49700e0595ea74b0c3a2f5608249794fb22 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 14 Apr 2020 00:38:34 +0200 Subject: [PATCH 7/7] cleanup and make all examples compile --- examples/counter/Cargo.toml | 1 + examples/hello-world/Cargo.toml | 3 + examples/input-form/Cargo.toml | 1 + examples/js-component/Cargo.toml | 3 + examples/moire/Cargo.toml | 1 + examples/todomvc/Cargo.toml | 1 + js/change-list-interpreter.js | 315 ------------------------------- src/change_list/js.rs | 186 ------------------ src/change_list/mod.rs | 3 - tests/web/render.rs | 24 ++- 10 files changed, 21 insertions(+), 517 deletions(-) delete mode 100644 js/change-list-interpreter.js delete mode 100644 src/change_list/js.rs diff --git a/examples/counter/Cargo.toml b/examples/counter/Cargo.toml index 5cb2587..3a0775d 100644 --- a/examples/counter/Cargo.toml +++ b/examples/counter/Cargo.toml @@ -27,6 +27,7 @@ features = [ "MouseEvent", "Node", "Window", + "NodeList", ] [dev-dependencies] diff --git a/examples/hello-world/Cargo.toml b/examples/hello-world/Cargo.toml index 12666c3..917d260 100644 --- a/examples/hello-world/Cargo.toml +++ b/examples/hello-world/Cargo.toml @@ -21,6 +21,9 @@ features = [ "HtmlElement", "Node", "Window", + "Event", + "EventTarget", + "NodeList", ] [dev-dependencies] diff --git a/examples/input-form/Cargo.toml b/examples/input-form/Cargo.toml index 7c1c3f2..04d9d59 100644 --- a/examples/input-form/Cargo.toml +++ b/examples/input-form/Cargo.toml @@ -26,6 +26,7 @@ features = [ "MouseEvent", "Node", "Window", + "NodeList", ] [dev-dependencies] diff --git a/examples/js-component/Cargo.toml b/examples/js-component/Cargo.toml index 122f3a8..6a2459e 100644 --- a/examples/js-component/Cargo.toml +++ b/examples/js-component/Cargo.toml @@ -23,4 +23,7 @@ features = [ "HtmlElement", "Node", "Window", + "Event", + "EventTarget", + "NodeList", ] diff --git a/examples/moire/Cargo.toml b/examples/moire/Cargo.toml index a609c41..6944c20 100644 --- a/examples/moire/Cargo.toml +++ b/examples/moire/Cargo.toml @@ -31,6 +31,7 @@ features = [ "Node", "Performance", "Window", + "NodeList", ] [dev-dependencies] diff --git a/examples/todomvc/Cargo.toml b/examples/todomvc/Cargo.toml index b21c6a9..a651750 100644 --- a/examples/todomvc/Cargo.toml +++ b/examples/todomvc/Cargo.toml @@ -31,6 +31,7 @@ features = [ "Storage", "Node", "Window", + "NodeList", ] [dev-dependencies] diff --git a/js/change-list-interpreter.js b/js/change-list-interpreter.js deleted file mode 100644 index 24957a4..0000000 --- a/js/change-list-interpreter.js +++ /dev/null @@ -1,315 +0,0 @@ -const decoder = new TextDecoder(); - -function top(stack) { - return stack[stack.length - 1]; -} - -function string(mem, pointer, length) { - const buf = mem.subarray(pointer, pointer + length); - return decoder.decode(buf); -} - -export class ChangeListInterpreter { - constructor(container) { - this.trampoline = null; - this.container = container; - this.ranges = []; - this.stack = []; - this.strings = new Map(); - this.temporaries = []; - this.templates = new Map(); - } - - unmount() { - this.trampoline.mounted = false; - - // Null out all of our properties just to ensure that if we mistakenly ever - // call a method on this instance again, it will throw. - this.trampoline = null; - this.container = null; - this.ranges = null; - this.stack = null; - this.strings = null; - this.temporaries = null; - this.templates = null; - } - - addChangeListRange(start, len) { - this.ranges.push(start); - this.ranges.push(len); - } - - applyChanges(memory) { - if (this.ranges.length == 0) { - return; - } - - this.stack.push(this.container.firstChild); - const mem8 = new Uint8Array(memory.buffer); - const mem32 = new Uint32Array(memory.buffer); - - for (let i = 0; i < this.ranges.length; i += 2) { - const start = this.ranges[i]; - const len = this.ranges[i + 1]; - this.applyChangeRange(mem8, mem32, start, len, memory); - } - - this.reset(); - } - - start() { - this.stack.push(this.container.firstChild); - } - - reset() { - this.ranges.length = 0; - this.stack.length = 0; - this.temporaries.length = 0; - } - - applyChangeRange(mem8, mem32, start, len, memory) { - const end = (start + len) / 4; - for (let i = start / 4; i < end; ) { - const op = mem32[i++]; - i = OP_TABLE[op](this, mem8, mem32, i, memory); - } - } - - getCachedString(id) { - return this.strings.get(id); - } - - getTemplate(id) { - return this.templates.get(id); - } - - initEventsTrampoline(trampoline) { - this.trampoline = trampoline; - trampoline.mounted = true; - this.eventHandler = function(event) { - if (!trampoline.mounted) { - throw new Error("invocation of listener after VDOM has been unmounted"); - } - - // `this` always refers to the element the handler was added to. - // Since we're adding the handler to all elements our content wants - // to listen for events on, this ensures that we always get the right - // values for `a` and `b`. - const type = event.type; - const a = this[`dodrio-a-${type}`]; - const b = this[`dodrio-b-${type}`]; - trampoline(event, a, b); - } - } - - // 0 - setText(pointer, length, memory) { - const mem8 = new Uint8Array(memory.buffer); - const str = string(mem8, pointer, length); - top(this.stack).textContent = str; - } - - // 1 - removeSelfAndNextSiblings(interpreter) { - const node = this.stack.pop(); - let sibling = node.nextSibling; - while (sibling) { - const temp = sibling.nextSibling; - sibling.remove(); - sibling = temp; - } - node.remove(); - } - - // 2 - replaceWith(interpreter) { - const newNode = this.stack.pop(); - const oldNode = this.stack.pop(); - oldNode.replaceWith(newNode); - this.stack.push(newNode); - } - - // 3 - setAttribute(nameId, valueId) { - const name = this.getCachedString(nameId); - const value = this.getCachedString(valueId); - const node = top(this.stack); - node.setAttribute(name, value); - - // Some attributes are "volatile" and don't work through `setAttribute`. - if (name === "value") { - node.value = value; - } - if (name === "checked") { - node.checked = true; - } - if (name === "selected") { - node.selected = true; - } - } - - // 4 - removeAttribute(nameId) { - const name = this.getCachedString(nameId); - const node = top(this.stack); - node.removeAttribute(name); - - // Some attributes are "volatile" and don't work through `removeAttribute`. - if (name === "value") { - node.value = null; - } - if (name === "checked") { - node.checked = false; - } - if (name === "selected") { - node.selected = false; - } - } - - // 5 - pushReverseChild(n) { - const parent = top(this.stack); - const children = parent.childNodes; - const child = children[children.length - n - 1]; - this.stack.push(child); - } - - // 6 - popPushChild(n) { - this.stack.pop(); - const parent = top(this.stack); - const children = parent.childNodes; - const child = children[n]; - this.stack.push(child); - } - - // 7 - pop(interpreter) { - this.stack.pop(); - } - - // 8 - appendChild(interpreter) { - const child = this.stack.pop(); - top(this.stack).appendChild(child); - } - - // 9 - createTextNode(pointer, length, memory) { - const mem8 = new Uint8Array(memory.buffer); - const text = string(mem8, pointer, length); - this.stack.push(document.createTextNode(text)); - } - - // 10 - createElement(tagNameId) { - const tagName = this.getCachedString(tagNameId); - this.stack.push(document.createElement(tagName)); - } - - // 11 - newEventListener(eventId, a, b) { - const eventType = this.getCachedString(eventId); - const el = top(this.stack); - el.addEventListener(eventType, this.eventHandler); - el[`dodrio-a-${eventType}`] = a; - el[`dodrio-b-${eventType}`] = b; - } - - // 12 - updateEventListener(eventId, a, b) { - const eventType = this.getCachedString(eventId); - const el = top(this.stack); - el[`dodrio-a-${eventType}`] = a; - el[`dodrio-b-${eventType}`] = b; - } - - // 13 - removeEventListener(eventId) { - const eventType = this.getCachedString(eventId); - const el = top(this.stack); - el.removeEventListener(eventType, this.eventHandler); - } - - // 14 - addCachedString(pointer, length, id, memory) { - const mem8 = new Uint8Array(memory.buffer); - const str = string(mem8, pointer, length); - this.strings.set(id, str); - } - - // 15 - dropCachedString(id) { - this.strings.delete(id); - } - - // 16 - createElementNS(tagNameId, nsId) { - const tagName = this.getCachedString(tagNameId); - const ns = this.getCachedString(nsId); - this.stack.push(document.createElementNS(ns, tagName)); - } - - // 17 - saveChildrenToTemporaries(temp, start, end) { - const parent = top(this.stack); - const children = parent.childNodes; - for (let i = start; i < end; i++) { - this.temporaries[temp++] = children[i]; - } - } - - // 18 - pushChild(n) { - const parent = top(this.stack); - const child = parent.childNodes[n]; - this.stack.push(child); - } - - // 19 - pushTemporary(temp) { - this.stack.push(this.temporaries[temp]); - } - - // 20 - insertBefore(interpreter) { - const before = this.stack.pop(); - const after = this.stack.pop(); - after.parentNode.insertBefore(before, after); - this.stack.push(before); - } - - // 21 - popPushReverseChild(n) { - this.stack.pop(); - const parent = top(this.stack); - const children = parent.childNodes; - const child = children[children.length - n - 1]; - this.stack.push(child); - } - - // 22 - removeChild(n) { - const parent = top(this.stack); - const child = parent.childNodes[n]; - child.remove(); - } - - // 23 - setClass(classId) { - const className = this.getCachedString(classId); - top(this.stack).className = className; - } - - // 24 - saveTemplate(id) { - const template = top(this.stack); - this.templates.set(id, template.cloneNode(true)); - } - - // 25 - pushTemplate(id) { - const template = this.getTemplate(id); - this.stack.push(template.cloneNode(true)); - } -} diff --git a/src/change_list/js.rs b/src/change_list/js.rs deleted file mode 100644 index 95c5a7d..0000000 --- a/src/change_list/js.rs +++ /dev/null @@ -1,186 +0,0 @@ -cfg_if::cfg_if! { - if #[cfg(all(feature = "xxx-unstable-internal-use-only", not(target_arch = "wasm32")))] { - use wasm_bindgen::prelude::JsValue; - - #[derive(Clone, Debug)] - pub struct ChangeListInterpreter {} - impl ChangeListInterpreter { - pub fn new(_container: &crate::Element) -> ChangeListInterpreter { - ChangeListInterpreter {} - } - pub fn unmount(&self) {} - pub fn add_change_list_range(&self, _start: usize, _len: usize) {} - pub fn init_events_trampoline(&self, _trampoline: &crate::EventsTrampoline) {} - pub fn start(&self) {} - pub fn reset(&self) {} - - // -- ops - - // 0 - pub fn set_text(&self, pointer: u32, len: u32, memory: JsValue) {} - // 1 - pub fn remove_self_and_next_siblings(&self) {} - // 2 - pub fn replace_with(&self) {} - // 3 - pub fn set_attribute(&self, name_id: u32, value_id: u32) {} - // 4 - pub fn remove_attribute(&self, name_id: u32) {} - // 5 - pub fn push_reverse_child(&self, n: u32) {} - // 6 - pub fn pop_push_child(&self, n: u32) {} - // 7 - pub fn pop(&self) {} - // 8 - pub fn append_child(&self) {} - // 9 - pub fn create_text_node(&self, pointer: u32, len: u32, memory: JsValue) {} - // 10 - pub fn create_element(&self, tag_name_id: u32) {} - // 11 - pub fn new_event_listener(&self, event_id: u32, a: u32, b: u32) {} - // 12 - pub fn update_event_listener(&self, event_id: u32, a: u32, b: u32) {} - // 13 - pub fn remove_event_listener(&self, event_id: u32) {} - // 14 - pub fn add_cached_string(&self, pointer: u32, len: u32, id: u32, memory: JsValue) {} - // 15 - pub fn drop_cached_string(&self, id: u32) {} - // 16 - pub fn create_element_ns(&self, tag_name_id: u32, ns_id: u32) {} - // 17 - pub fn save_children_to_temporaries(&self, temp: u32, start: u32, end: u32) {} - // 18 - pub fn push_child(&self, n: u32) {} - // 19 - pub fn push_temporary(&self, temp: u32) {} - // 20 - pub fn insert_before(&self) {} - // 21 - pub fn pop_push_reverse_child(&self, n: u32) {} - // 22 - pub fn remove_child(&self, n: u32) {} - // 23 - pub fn set_class(&self, class_id: u32) {} - // 24 - pub fn save_template(&self, id: u32) {} - // 25 - pub fn push_template(&self, id: u32) {} - } - } else { - use wasm_bindgen::prelude::*; - - #[wasm_bindgen(module = "/js/change-list-interpreter.js")] - extern "C" { - #[derive(Clone, Debug)] - pub type ChangeListInterpreter; - - #[wasm_bindgen(constructor)] - pub fn new(container: &web_sys::Element) -> ChangeListInterpreter; - - #[wasm_bindgen(structural, method)] - pub fn unmount(this: &ChangeListInterpreter); - - #[wasm_bindgen(structural, method, js_name = addChangeListRange)] - pub fn add_change_list_range(this: &ChangeListInterpreter, start: usize, len: usize); - - #[wasm_bindgen(structural, method, js_name = applyChanges)] - pub fn apply_changes(this: &ChangeListInterpreter, memory: JsValue); - - // #[wasm_bindgen(structural, method, js_name = initEventsTrampoline)] - // pub fn init_events_trampoline( - // this: &ChangeListInterpreter, - // trampoline: &crate::EventsTrampoline, - // ); - - #[wasm_bindgen(structural, method, js_name = start)] - pub fn start(this: &ChangeListInterpreter); - - #[wasm_bindgen(structural, method, js_name = reset)] - pub fn reset(this: &ChangeListInterpreter); - - // -- ops - - // 0 - #[wasm_bindgen(structural, method, js_name = setText)] - pub fn set_text(this: &ChangeListInterpreter, pointer: u32, len: u32, memory: JsValue); - // 1 - #[wasm_bindgen(structural, method, js_name = removeSelfAndNextSiblings)] - pub fn remove_self_and_next_siblings(this: &ChangeListInterpreter); - // 2 - #[wasm_bindgen(structural, method, js_name = replaceWith)] - pub fn replace_with(this: &ChangeListInterpreter); - // 3 - #[wasm_bindgen(structural, method, js_name = setAttribute)] - pub fn set_attribute(this: &ChangeListInterpreter, name_id: u32, value_id: u32); - // 4 - #[wasm_bindgen(structural, method, js_name = removeAttribute)] - pub fn remove_attribute(this: &ChangeListInterpreter, name_id: u32); - // 5 - #[wasm_bindgen(structural, method, js_name = pushReverseChild)] - pub fn push_reverse_child(this: &ChangeListInterpreter, n: u32); - // 6 - #[wasm_bindgen(structural, method, js_name = popPushChild)] - pub fn pop_push_child(this: &ChangeListInterpreter, n: u32); - // 7 - #[wasm_bindgen(structural, method, js_name = pop)] - pub fn pop(this: &ChangeListInterpreter); - // 8 - #[wasm_bindgen(structural, method, js_name = appendChild)] - pub fn append_child(this: &ChangeListInterpreter); - // 9 - #[wasm_bindgen(structural, method, js_name = createTextNode)] - pub fn create_text_node(this: &ChangeListInterpreter, pointer: u32, len: u32, memory: JsValue); - // 10 - #[wasm_bindgen(structural, method, js_name = createElement)] - pub fn create_element(this: &ChangeListInterpreter, tag_name_id: u32); - // 11 - #[wasm_bindgen(structural, method, js_name = newEventListener)] - pub fn new_event_listener(this: &ChangeListInterpreter, event_id: u32, a: u32, b: u32); - // 12 - #[wasm_bindgen(structural, method, js_name = updateEventListener)] - pub fn update_event_listener(this: &ChangeListInterpreter, event_id: u32, a: u32, b: u32); - // 13 - #[wasm_bindgen(structural, method, js_name = removeEventListener)] - pub fn remove_event_listener(this: &ChangeListInterpreter, event_id: u32); - // 14 - #[wasm_bindgen(structural, method, js_name = addCachedString)] - pub fn add_cached_string(this: &ChangeListInterpreter, pointer: u32, len: u32, id: u32, memory: JsValue); - // 15 - #[wasm_bindgen(structural, method, js_name = dropCachedString)] - pub fn drop_cached_string(this: &ChangeListInterpreter, id: u32); - // 16 - #[wasm_bindgen(structural, method, js_name = createElementNS)] - pub fn create_element_ns(this: &ChangeListInterpreter, tag_name_id: u32, ns_id: u32); - // 17 - #[wasm_bindgen(structural, method, js_name = saveChildrenToTemporaries)] - pub fn save_children_to_temporaries(this: &ChangeListInterpreter, temp: u32, start: u32, end: u32); - // 18 - #[wasm_bindgen(structural, method, js_name = pushChild)] - pub fn push_child(this: &ChangeListInterpreter, n: u32); - // 19 - #[wasm_bindgen(structural, method, js_name = pushTemporary)] - pub fn push_temporary(this: &ChangeListInterpreter, temp: u32); - // 20 - #[wasm_bindgen(structural, method, js_name = insertBefore)] - pub fn insert_before(this: &ChangeListInterpreter); - // 21 - #[wasm_bindgen(structural, method, js_name = popPushReverseChild)] - pub fn pop_push_reverse_child(this: &ChangeListInterpreter, n: u32); - // 22 - #[wasm_bindgen(structural, method, js_name = removeChild)] - pub fn remove_child(this: &ChangeListInterpreter, n: u32); - // 23 - #[wasm_bindgen(structural, method, js_name = setClass)] - pub fn set_class(this: &ChangeListInterpreter, class_id: u32); - // 24 - #[wasm_bindgen(structural, method, js_name = saveTemplate)] - pub fn save_template(this: &ChangeListInterpreter, id: u32); - // 25 - #[wasm_bindgen(structural, method, js_name = pushTemplate)] - pub fn push_template(this: &ChangeListInterpreter, id: u32); - } - } -} diff --git a/src/change_list/mod.rs b/src/change_list/mod.rs index 4ffd3f9..da96561 100644 --- a/src/change_list/mod.rs +++ b/src/change_list/mod.rs @@ -1,9 +1,6 @@ pub(crate) mod interpreter; pub(crate) mod traversal; -// Note: has to be `pub` because of `wasm-bindgen` visibility restrictions. -pub mod js; - use self::interpreter::ChangeListInterpreter; use self::traversal::{MoveTo, Traversal}; use crate::{cached_set::CacheId, Listener}; diff --git a/tests/web/render.rs b/tests/web/render.rs index 13c3f03..dcfcbc6 100644 --- a/tests/web/render.rs +++ b/tests/web/render.rs @@ -1,7 +1,7 @@ use super::{assert_rendered, before_after, create_element, RenderFn}; use dodrio::{builder::*, bumpalo::collections::String, Node, Render, RenderContext, Vdom}; use std::rc::Rc; -use wasm_bindgen::{ JsCast}; +use wasm_bindgen::JsCast; use wasm_bindgen_test::*; #[wasm_bindgen_test] @@ -37,7 +37,7 @@ fn container_is_emptied_upon_drop() { /// Renders a child with a lifetime scoped to the RenderContext bump arena. #[wasm_bindgen_test] -fn render_bump_scoped_node() { +fn render_bump_scoped_node() { struct Child<'a> { name: &'a str, } @@ -60,9 +60,7 @@ fn render_bump_scoped_node() { } } - let parent = Rc::new(RenderFn(|cx| { - Parent.render(cx) - })); + let parent = Rc::new(RenderFn(|cx| Parent.render(cx))); let container = create_element("div"); let _vdom = Vdom::new(&container, parent.clone()); @@ -71,7 +69,7 @@ fn render_bump_scoped_node() { } /// Originally, dodrio would use the className property for SVGs. -/// +/// /// This is problematic because when SVG elements are created, the className is flagged as a read /// only property, so setting it causes an exception to be thrown. Here's an example of how this /// happens: @@ -83,22 +81,21 @@ fn render_bump_scoped_node() { /// .create_element_ns(Some("http://www.w3.org/2000/svg"), "svg") /// .unwrap(); /// -/// elem.set_class_name("does-not-work"); -/// +/// elem.set_class_name("does-not-work"); +/// /// ----------------------------------------------------------------------------------------------- /// /// wasm-bindgen: imported JS function that was not marked as `catch` threw an error: /// setting getter-only property "className" -/// +/// /// ----------------------------------------------------------------------------------------------- -/// +/// /// Now, dodrio passes the 'class' attribute of all namespaced elements into set_attribute. This /// satisfies the restrictions on SVG and keeps the optimized path for non-namespaced elements #[wasm_bindgen_test(async)] async fn test_svg_set_class() { let container = create_element("div"); - let valid_svg = Rc::new(RenderFn(|cx| { ElementBuilder::new(cx.bump, "svg") .namespace(Some("http://www.w3.org/2000/svg")) @@ -112,8 +109,9 @@ async fn test_svg_set_class() { weak.render().await.unwrap(); assert_eq!( - "works", - container.first_child() + "works", + container + .first_child() .expect("unable to get svg") .dyn_ref::() .expect("svg should be an element")