type u32 = number; type Offset = u32; const enum Opcode { /// { document: Document, tag_name: &'static str } CreateElement = 0, #ifdef WITH_SVG /// { document: Document, tag_name: &'static str } CreateSvgElement = 1, #endif /// { document: Document, data: &str } CreateTextNode = 2, #ifdef WITH_COMMENT /// { document: Document, data: &str } CreateComment = 3, #endif #ifdef WITH_DOCUMENT_FRAGMENT /// { document: Document } CreateDocumentFragment = 4, #endif /// { node: CharacterData, data: &str } SetData = 5, /// { element: Element, name: &'static str, value: &str } SetAttribute = 6, /// { element: Element, name: &'static str } RemoveAttribute = 7, /// { parent: Element, new_child: Node } AppendChild = 8, /// { parent: Element, reference_child: Node, new_child: Node } InsertBefore = 9, /// { reference: Node } Free = 10, } class VM { #ifdef WITH_REALLOCATOR private next_id: u32; private nodes: (Node | u32)[]; # define PUSH_NODE push_node #else private nodes: Node[]; # define PUSH_NODE nodes.push #endif private textDecoder: TextDecoder; constructor() { // FIXME: is 0 document or what? (Perhaps I should define a “document” opcode rather than prefilling it?) #ifdef WITH_REALLOCATOR this.next_id = 0; #endif this.nodes = []; this.textDecoder = new TextDecoder(); } #ifdef WITH_REALLOCATOR private push_node(node: Node) { if (this.next_id === this.nodes.length) { this.nodes.push(this.next_id + 1); } const id = this.next_id; this.next_id = this.nodes[id] as u32; this.nodes[id] = node; } #endif execute(instructions: Uint8Array) { const length = instructions.byteLength; let offset: Offset = 0; while (offset < length) { // Variable-width instruction set. First byte is the opcode. const opcode = instructions[offset++]; switch (opcode) { case Opcode.CreateElement: #ifdef WITH_SVG case Opcode.CreateSvgElement: #endif { const [document, offset1] = this.readNode(instructions, offset) as [Document, Offset]; const [tagName, offset2] = this.readString( instructions, offset1, ); offset = offset2; #ifdef WITH_SVG const element = (opcode == Opcode.CreateElement) ? document.createElement(tagName) : document.createElementNS('http://www.w3.org/2000/svg', tagName); #else const element = document.createElement(tagName); #endif this.PUSH_NODE(element); break; } case Opcode.CreateTextNode: { const [document, offset1] = this.readNode(instructions, offset) as [Document, Offset]; const [data, offset2] = this.readString(instructions, offset1); offset = offset2; this.PUSH_NODE(document.createTextNode(data)); break; } #ifdef WITH_COMMENT case Opcode.CreateComment: { const [document, offset1] = this.readNode(instructions, offset) as [Document, Offset]; const [data, offset2] = this.readString(instructions, offset1); offset = offset2; this.PUSH_NODE(document.createComment(data)); break; } #endif #ifdef WITH_DOCUMENT_FRAGMENT case Opcode.CreateDocumentFragment: { const [document, offset1] = this.readNode(instructions, offset) as [Document, Offset]; offset = offset1; this.PUSH_NODE(document.createDocumentFragment()); break; } #endif case Opcode.SetData: { const [characterData, offset1] = this.readNode(instructions, offset) as [CharacterData, Offset]; const [data, offset2] = this.readString(instructions, offset1); offset = offset2; characterData.data = data; break; } case Opcode.SetAttribute: { const [element, offset1] = this.readNode(instructions, offset) as [Element, Offset]; const [name, offset2] = this.readString(instructions, offset1); const [value, offset3] = this.readString(instructions, offset2); offset = offset2; element.setAttribute(name, value); break; } case Opcode.RemoveAttribute: { const [element, offset1] = this.readNode(instructions, offset) as [Element, Offset]; const [attributeName, offset2] = this.readString(instructions, offset1); offset = offset2; element.removeAttribute(attributeName); break; } case Opcode.AppendChild: { const [parentNode, offset1] = this.readNode(instructions, offset) as [Document, Offset]; const [newChild, offset2] = this.readNode(instructions, offset1); offset = offset2; parentNode.appendChild(newChild); break; } case Opcode.InsertBefore: { const [parentNode, offset1] = this.readNode(instructions, offset) as [Document, Offset]; const [referenceChild, offset2] = this.readNode(instructions, offset1); const [newChild, offset3] = this.readNode(instructions, offset2); offset = offset3; parentNode.insertBefore(newChild, referenceChild); break; } case Opcode.Free: { const [node, offset1] = this.readU32(instructions, offset); offset = offset1; #ifdef WITH_REALLOCATOR this.nodes[node] = this.next_id; this.next_id = node; #else delete this.nodes[node]; #endif break; } default: { const error = new Error('Unknown opcode '); error.name = 'VmError'; throw error; } } } } private readStringWithDictionary(instructions: Uint8Array, offset: Offset, dictionary: string[]): [string, Offset] { const index = instructions[offset++]; if (index == 255) { return this.readString(instructions, offset); } else { return [dictionary[index], offset]; } } private readString(instructions: Uint8Array, offset: Offset): [string, Offset] { const [length, offset1] = this.readU32(instructions, offset); const string = this.textDecoder.decode(new Uint8Array(instructions.buffer, instructions.byteOffset + offset1, length)); return [string, offset1 + length]; } private readU32(instructions: Uint8Array, offset: Offset): [u32, Offset] { return [ ( (instructions[offset++] << 24) | (instructions[offset++] << 16) | (instructions[offset++] << 8) | (instructions[offset++] << 0) ), offset, ]; } private readNode(instructions: Uint8Array, offset: Offset): [Node, Offset] { const [node, offset1] = this.readU32(instructions, offset); return [this.nodes[node], offset1]; } }