1 use std
::convert
::TryFrom
;
2 #[cfg(feature = "dictionaries")]
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)]
10 macro_rules
! node_types
{
11 ($
($
(#[$meta:meta])* $ident:ident $(impl $($interface:ident),*)?;)*) => {
14 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
15 pub struct $
ident(Node
);
17 impl From
<$ident
> for Node
{
18 fn from(node
: $ident
) -> Node
{
25 impl $interface
for $ident {}
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.
47 pub trait ParentNode
: Into
<Node
> {}
48 pub trait ChildNode
: Into
<Node
> {}
49 pub trait CharacterData
: Into
<Node
> {}
53 /// { document: Document, tag_name: &'static str }
55 /// { document: Document, tag_name: &'static str }
56 #[cfg(feature = "svg")]
58 /// { document: Document, data: &str }
60 /// { document: Document, data: &str }
61 #[cfg(feature = "comment")]
63 /// { document: Document }
64 #[cfg(feature = "document-fragment")]
65 CreateDocumentFragment
= 4,
66 /// { node: CharacterData, data: &str }
68 /// { element: Element, name: &'static str, value: &str }
70 /// { element: Element, name: &'static str }
72 /// { parent: ParentNode, new_child: Node }
74 /// { parent: ParentNode, reference_child: Node, new_child: Node }
80 /// A writer of VM bytecode.
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
85 pub struct VmBytecodeWriter
{
86 instructions
: Vec
<u8>,
87 // node 0 is reserved for document.
91 impl VmBytecodeWriter
{
92 pub fn new() -> VmBytecodeWriter
{
94 instructions
: Vec
::new(),
99 fn push_opcode(&mut self, opcode
: Opcode
) {
100 self.instructions
.push(opcode
as u8);
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);
110 fn push_node(&mut self, node
: impl Into
<Node
>) {
111 self.push_u32(node
.into().0);
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);
120 self.instructions
.push(255);
121 self.push_string(string
);
125 fn push_string(&mut self, string
: &str) {
126 let len
= u32::try_from(string
.len()).expect("strings may not exceed 4GB");
128 self.instructions
.extend(string
.as_bytes());
131 fn next_node(&mut self) -> Node
{
132 self.last_node
.0 += 1;
136 /// Create an element in the default (HTML) namespace.
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())
149 /// Create an element in the SVG namespace.
151 /// This is equivalent to `document.createElementNS("http://www.w3.org/2000/svg", tag_name)` in
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())
169 /// Create a text node.
171 /// This is equivalent to `document.createTextNode(data)` in JavaScript.
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())
184 /// Create a comment node.
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())
195 /// Create a document fragment.
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())
205 /// Set the data of a text or comment node.
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
);
214 /// Set an attribute on an element.
216 /// This is equivalent to `element.setAttribute(name, value)` in JavaScript.
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
);
228 /// Remove an attribute on an element.
230 /// This is equivalent to `element.removeAttribute(name)` in JavaScript.
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
);
239 /// Append a node to the end of a parent.
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
);
248 /// Insert a child node at a position other than the end of its parent.
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.
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(
262 parent
: impl ParentNode
,
263 reference_child
: impl ChildNode
,
264 new_child
: impl ChildNode
,
266 self.push_opcode(Opcode
::InsertBefore
);
267 self.push_node(parent
);
268 self.push_node(reference_child
);
269 self.push_node(new_child
);
272 pub fn free(&mut self, node
: impl Into
<Node
>) {
273 self.push_opcode(Opcode
::Free
);
274 self.push_node(node
);
277 pub fn drain(&mut self) -> Drain
<u8> {
278 self.instructions
.drain(..)