Code dump
[remote-dom-vm] / rust-client / src / lib.rs
1 use std::convert::TryFrom;
2 #[cfg(feature = "dictionaries")]
3 mod dictionaries;
4 use std::vec::Drain;
5
6 // It is an invariant that in the VM bytecode a node takes up four bytes, like a 32-bit integer.
7 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
8 pub struct Node(u32);
9
10 macro_rules! node_types {
11 ($($(#[$meta:meta])* $ident:ident $(impl $($interface:ident),*)?;)*) => {
12 $(
13 $(#[$meta])*
14 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
15 pub struct $ident(Node);
16
17 impl From<$ident> for Node {
18 fn from(node: $ident) -> Node {
19 node.0
20 }
21 }
22
23 $(
24 $(
25 impl $interface for $ident {}
26 )*
27 )?
28 )*
29 }
30 }
31
32 node_types! {
33 /// Since the VM is currently write-only, this offers a mild predicament for getting the
34 /// document; and indeed, at some point it may be necessary to get another document; but for
35 /// now, we can just assume we’ll work with the global document, and so the VM defines as
36 /// initial state that node 0 is assigned to `Window.document`.
37 Document impl ParentNode;
38 #[cfg(feature = "document-fragment")]
39 DocumentFragment impl ParentNode;
40 Element impl ParentNode, ChildNode;
41 Text impl ChildNode, CharacterData;
42 #[cfg(feature = "comment")]
43 Comment impl ChildNode, CharacterData;
44 // Deliberately not implemented: DocumentType, ProcessingInstruction.
45 }
46
47 pub trait ParentNode: Into<Node> {}
48 pub trait ChildNode: Into<Node> {}
49 pub trait CharacterData: Into<Node> {}
50
51 #[repr(u8)]
52 enum Opcode {
53 /// { document: Document, tag_name: &'static str }
54 CreateElement = 0,
55 /// { document: Document, tag_name: &'static str }
56 #[cfg(feature = "svg")]
57 CreateSvgElement = 1,
58 /// { document: Document, data: &str }
59 CreateTextNode = 2,
60 /// { document: Document, data: &str }
61 #[cfg(feature = "comment")]
62 CreateComment = 3,
63 /// { document: Document }
64 #[cfg(feature = "document-fragment")]
65 CreateDocumentFragment = 4,
66 /// { node: CharacterData, data: &str }
67 SetData = 5,
68 /// { element: Element, name: &'static str, value: &str }
69 SetAttribute = 6,
70 /// { element: Element, name: &'static str }
71 RemoveAttribute = 7,
72 /// { parent: ParentNode, new_child: Node }
73 AppendChild = 8,
74 /// { parent: ParentNode, reference_child: Node, new_child: Node }
75 InsertBefore = 9,
76 /// { node: Node }
77 Free = 10,
78 }
79
80 /// A writer of VM bytecode.
81 ///
82 /// This type does not concern itself with logic or any particular sanity checking; it faithfully
83 /// transcribes the instructions requested. The most notable part of this is that freeing is
84 /// entirely manual.
85 pub struct VmBytecodeWriter {
86 instructions: Vec<u8>,
87 // node 0 is reserved for document.
88 last_node: Node,
89 }
90
91 impl VmBytecodeWriter {
92 pub fn new() -> VmBytecodeWriter {
93 VmBytecodeWriter {
94 instructions: Vec::new(),
95 last_node: Node(0),
96 }
97 }
98
99 fn push_opcode(&mut self, opcode: Opcode) {
100 self.instructions.push(opcode as u8);
101 }
102
103 fn push_u32(&mut self, u32: u32) {
104 self.instructions.push(((u32 & 0xff000000) >> 24) as u8);
105 self.instructions.push(((u32 & 0x00ff0000) >> 16) as u8);
106 self.instructions.push(((u32 & 0x0000ff00) >> 8) as u8);
107 self.instructions.push(((u32 & 0x000000ff) >> 0) as u8);
108 }
109
110 fn push_node(&mut self, node: impl Into<Node>) {
111 self.push_u32(node.into().0);
112 }
113
114 #[cfg(feature = "dictionaries")]
115 fn push_string_with_dictionary(&mut self, string: &str, dictionary: &'static [&'static str]) {
116 // TODO: speed-compare a phf or similar.
117 if let Ok(index) = dictionary.binary_search(&string) {
118 self.instructions.push(index as u8);
119 } else {
120 self.instructions.push(255);
121 self.push_string(string);
122 }
123 }
124
125 fn push_string(&mut self, string: &str) {
126 let len = u32::try_from(string.len()).expect("strings may not exceed 4GB");
127 self.push_u32(len);
128 self.instructions.extend(string.as_bytes());
129 }
130
131 fn next_node(&mut self) -> Node {
132 self.last_node.0 += 1;
133 self.last_node
134 }
135
136 /// Create an element in the default (HTML) namespace.
137 ///
138 /// This is equivalent to `document.createElement(tag_name)` in JavaScript.
139 pub fn create_element(&mut self, document: Document, tag_name: &str) -> Element {
140 self.push_opcode(Opcode::CreateElement);
141 self.push_node(document);
142 #[cfg(feature = "dictionaries")]
143 self.push_string_with_dictionary(tag_name, &dictionaries::CREATE_ELEMENT);
144 #[cfg(not(feature = "dictionaries"))]
145 self.push_string(tag_name);
146 Element(self.next_node())
147 }
148
149 /// Create an element in the SVG namespace.
150 ///
151 /// This is equivalent to `document.createElementNS("http://www.w3.org/2000/svg", tag_name)` in
152 /// JavaScript.
153 ///
154 /// Note how this is deliberately *not* generic like the Document.createElementNS method.
155 /// That’s for efficiency, operating under the belief that no other values will be used.
156 /// Look, strictly that may not be true; there’s also MathML. If anyone pipes up *really*
157 /// wanting MathML support, we can add an opcode for it.
158 #[cfg(feature = "svg")]
159 pub fn create_svg_element(&mut self, document: Document, tag_name: &str) -> Element {
160 self.push_opcode(Opcode::CreateSvgElement);
161 self.push_node(document);
162 #[cfg(feature = "dictionaries")]
163 self.push_string_with_dictionary(tag_name, &dictionaries::CREATE_SVG_ELEMENT);
164 #[cfg(not(feature = "dictionaries"))]
165 self.push_string(tag_name);
166 Element(self.next_node())
167 }
168
169 /// Create a text node.
170 ///
171 /// This is equivalent to `document.createTextNode(data)` in JavaScript.
172 ///
173 /// At this time, this is the only way of creating text nodes; there is nothing equivalent to
174 /// `ParentNode.append`’s ability to take strings, or `Element.innerHTML` or `Node.textContent`
175 /// setters. This means that all text nodes are tracked by the VM with an ID of their own,
176 /// which may be suboptimal for constant strings. This decision that will be reviewed later.
177 pub fn create_text_node(&mut self, document: Document, data: &str) -> Text {
178 self.push_opcode(Opcode::CreateTextNode);
179 self.push_node(document);
180 self.push_string(data);
181 Text(self.next_node())
182 }
183
184 /// Create a comment node.
185 ///
186 /// This is equivalent to `document.createComment(data)` in JavaScript.
187 #[cfg(feature = "comment")]
188 pub fn create_comment_node(&mut self, document: Document, data: &str) -> Comment {
189 self.push_opcode(Opcode::CreateComment);
190 self.push_node(document);
191 self.push_string(data);
192 Comment(self.next_node())
193 }
194
195 /// Create a document fragment.
196 ///
197 /// This is equivalent to `document.createDocumentFragment()` in JavaScript.
198 #[cfg(feature = "document-fragment")]
199 pub fn create_document_fragment(&mut self, document: Document) -> DocumentFragment {
200 self.push_opcode(Opcode::CreateDocumentFragment);
201 self.push_node(document);
202 DocumentFragment(self.next_node())
203 }
204
205 /// Set the data of a text or comment node.
206 ///
207 /// This is equivalent to `node.data = data` in JavaScript.
208 pub fn set_data(&mut self, node: impl CharacterData, data: &str) {
209 self.push_opcode(Opcode::SetData);
210 self.push_node(node);
211 self.push_string(data);
212 }
213
214 /// Set an attribute on an element.
215 ///
216 /// This is equivalent to `element.setAttribute(name, value)` in JavaScript.
217 ///
218 /// This is used for SVG elements as well as HTML attributes; although the *element* is created
219 /// in the SVG namespace, *attributes* are created in the default namespace. This is why there
220 /// is no equivalent to `Element.setAttributeNS`: it’s basically obsolete in HTML5.
221 pub fn set_attribute(&mut self, element: Element, name: &str, value: &str) {
222 self.push_opcode(Opcode::SetAttribute);
223 self.push_node(element);
224 self.push_string(name);
225 self.push_string(value);
226 }
227
228 /// Remove an attribute on an element.
229 ///
230 /// This is equivalent to `element.removeAttribute(name)` in JavaScript.
231 ///
232 /// As with `set_attribute`, this is used for SVG elements as well as HTML elements.
233 pub fn remove_attribute(&mut self, element: Element, name: &str) {
234 self.push_opcode(Opcode::RemoveAttribute);
235 self.push_node(element);
236 self.push_string(name);
237 }
238
239 /// Append a node to the end of a parent.
240 ///
241 /// This is equivalent to `parent.appendChild(new_child)` in JavaScript.
242 pub fn append_child(&mut self, parent: impl ParentNode, new_child: impl ChildNode) {
243 self.push_opcode(Opcode::AppendChild);
244 self.push_node(parent);
245 self.push_node(new_child);
246 }
247
248 /// Insert a child node at a position other than the end of its parent.
249 ///
250 /// This is equivalent to `parent.insertBefore(new_child, reference_child)` in JavaScript.
251 /// Note, however, the different argument order, which is a conscious design decision,
252 /// explained in the source if you’re at all interested.
253 //
254 // And here is the explanation as a not-doc-comment: once you’re taking the parent as an
255 // argument rather than method receiver, I think this is the order that makes sense. I’ve also,
256 // incidentally, made a bit of a hobby of asking developers (web and otherwise) which order
257 // they’d expect insertBefore to take its arguments, and almost all think the other. (For
258 // myself, I’ve memorised it as “insert new node before reference node”; the other way would
259 // read more like “insert, before reference node, new node”.)
260 pub fn insert_before(
261 &mut self,
262 parent: impl ParentNode,
263 reference_child: impl ChildNode,
264 new_child: impl ChildNode,
265 ) {
266 self.push_opcode(Opcode::InsertBefore);
267 self.push_node(parent);
268 self.push_node(reference_child);
269 self.push_node(new_child);
270 }
271
272 pub fn free(&mut self, node: impl Into<Node>) {
273 self.push_opcode(Opcode::Free);
274 self.push_node(node);
275 }
276
277 pub fn drain(&mut self) -> Drain<u8> {
278 self.instructions.drain(..)
279 }
280 }