From f7c340413724802fa755c8947d0e74845477c94f Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Sat, 6 May 2023 22:52:30 +1000 Subject: [PATCH 1/1] Code dump MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit I haven’t finished this, but this is basically where I had reached last year, and I feel like dumping the code out there for anyone interested, since I probably won’t finish it any time soon. I haven’t included *quite* all that I did, but it’s fairly close. The stuff I haven’t included I’m confident I’d want to redo. --- .gitignore | 5 + COPYING | 17 + README.ttt | 195 +++++ bytecode.ttt | 177 +++++ rust-client/Cargo.toml | 19 + rust-client/src/dictionaries.rs | 1199 +++++++++++++++++++++++++++++++ rust-client/src/lib.rs | 280 ++++++++ rust-client/tests/web.rs | 13 + todo.ttt | 262 +++++++ vm/src/build.rs | 41 ++ vm/src/vm.min.js.template | 140 ++++ vm/src/vm.ts.template | 206 ++++++ 12 files changed, 2554 insertions(+) create mode 100644 .gitignore create mode 100644 COPYING create mode 100644 README.ttt create mode 100644 bytecode.ttt create mode 100644 rust-client/Cargo.toml create mode 100644 rust-client/src/dictionaries.rs create mode 100644 rust-client/src/lib.rs create mode 100644 rust-client/tests/web.rs create mode 100644 todo.ttt create mode 100644 vm/src/build.rs create mode 100644 vm/src/vm.min.js.template create mode 100644 vm/src/vm.ts.template diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b0f34a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/rust-client/target/ +/rust-client/bin/ +/rust-client/pkg/ +Cargo.lock +wasm-pack.log diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..8c3fdef --- /dev/null +++ b/COPYING @@ -0,0 +1,17 @@ +Copyright © 2019– Chris Morgan + +This project is distributed under the terms of three different licenses, +at your choice: + + • Blue Oak Model License 1.0.0: https://blueoakcouncil.org/license/1.0.0 + • MIT License: https://opensource.org/licenses/MIT + • Apache License, Version 2.0: https://www.apache.org/licenses/LICENSE-2.0 + +If you do not have particular cause to select the MIT or the Apache-2.0 +license, Chris Morgan recommends that you select BlueOak-1.0.0, which is +better and simpler than both MIT and Apache-2.0, which are only offered +due to their greater recognition and their conventional use in the Rust +ecosystem. (BlueOak-1.0.0 was only published in March 2019.) + +When using this code, ensure you comply with the terms of at least one of +these licenses. diff --git a/README.ttt b/README.ttt new file mode 100644 index 0000000..6b3fab2 --- /dev/null +++ b/README.ttt @@ -0,0 +1,195 @@ +# remote-dom-vm: efficiently render to DOM from a worker + + Run all your UI code from inside a web worker, rather than on the main thread. + + This project provides a minimal instruction set for efficiently driving the DOM in a write-only fashion from Rust WASM code in a web worker. + That is its scope. + The “web worker” and “Rust WASM” bits are technically negotiable, + but if you want to use it outside of a worker or in a different language then you’ll have to implement those parts yourself. + + “Write-only” means that you can perform write operations like Document.createElement, Element.setAttribute and Node.appendChild, + but not read operations like Element.getAttribute, ParentNode.children or Element.getBoundingClientRect. + +## The architecture + + The main thread runs a small VM, written in JavaScript, + which takes byte code instructions and executes them. + + The worker runs Rust code that accepts and queues DOM mutations into byte code, + then can flush them to the main thread to be executed by the TypeScript VM. + + Logically, communication only operates in a single direction: from worker to main thread, write-only. + This is more efficient than bidirectional communication. + + DOM nodes are accessed by index, with nodes assigned incremental indexes. + (It’s possible that in the future the worker will assign IDs; + but for now, auto-incrementing is adequate.) + + Since DOM operations are only performed by this VM when queued instructions are flushed, + this gets you efficient batching with no hazard of triggering layout multiple times in a frame. + Indeed, at this time you can’t query anything about the DOM or layout. + +## Background: DOM interop from WASM + + At the time of writing, WASM code can’t interact with the DOM directly. + Instead, it has to go through JavaScript FFI, + where the FFI performs the operation and gives the Rust what amounts to a pointer. + Here’s an approximation of how it happens: + + JavaScript code (written here as TypeScript): + + ◊code.ts` + type Ref = number; + let refs: {[ref: Ref]: Node} = {}; + let next_ref: Ref = 0; + exports.get_document = function (): Ref { + const document_ref = next_ref++; + refs[document_ref] = document; + return document_ref; + }; + + exports.create_element = function (document_ref: number, element_name: string) { + const document = refs[document_ref]; + const result = document.createElement(element_name); + }; + ` + + And the Rust code that uses it: + + ◊code.rs` + let document_ref = get_document(); + let element_ref = create_element(document_ref, "div"); + ` + + It is intended that eventually WASM be able to work with browser GC objects directly; + when that is done, this JS layer will be able to disappear. + (Rust’s wasm-bindgen has been designed with such future-compatibility in mind.) + + I have decided to instead view this FFI layer as a feature, and take the separation further, + performing not synchronous FFI, but asynchronous cross-thread FFI. + + This also explains part of why a bytecode VM is used, + rather than using objects for structure, as WorkerDOM does + (though even with it, you will notice that it uses a dictionary to compress its objects, + which suggests to me that they found that so doing improved memory usage and/or performance). + In the end, even once WASM can work with GC objects directly, + if you use structured objects, + you’ll still be needing to do some forms of type conversion, + so it’s fastest to just do all the work in an `ArrayBuffer` to avoid needing more than one format shift. + +## Why? + + The reasons in the first 41 slides of apply. + + This is an experiment in various things: + + • efficient batching of DOM operations; + • seeing how much can be shifted off the main thread; + • seeing if even event handling can be shifted so. + + The less you run on the main thread, the greater your chances of avoiding all jank. + There are two logical extremes here: + ① run no code; and + ② run all code on workers. + This experiment runs with the second of those logical extremes, + seeing how close we can get to it. + + A possible goal is effective full-powered rendering to popup windows from the same code, + which frameworks don’t tend to consider. + + A stretch goal is seeing if we can run all of this not in a worker on the concerned document, but on the service worker; + if it can be done, this could introduce interesting possibilities in totally synced multiple-tab support, + and improved memory efficiency with respect to local object caches. + Not sure whether it’ll pan out; my service worker is rusty. + +## Event handling + + Event handling will be most interesting, to see whether it is actually possible to manage without maintaining at least the structure of the DOM on the worker side, + because I’m pairing it with doing event dispatch entirely manually, + so that we just register one event handler for each type on the window or document, + and traverse our own *component* tree to dispatch. + (The most interesting thing there will be preventDefault(); + that must be done synchronously, + so we’ll need to define on the main thread a pure function to decide whether that should be called. + I think it should generally be reasonable, + but it will probably require breaking out of the component model occasionally. + Why am I writing all this here? + This stuff belongs in a section of its own, not in the “Why?” section.) + +## Performing other operations (especially getters) from the worker + + That is, things like `Window.getComputedStyle`, + `Element.getBoundingClientRect` and `Element.scrollTop` for the inevitable occasions when you need them. + Also other mutating methods that aren’t particularly useful without such getters, + like `Element.scrollTo` which is only useful for zero and infinity unless you can get the coordinates of something inside it. + + Answer: I don’t know. + I’m going to start without them and see what happens when I need them. + I have a few ideas: I may relax the “write-only” nature of the instruction set; + or introduce some kind of FFI layer in the byte code; + or declare it out of scope, just providing a `get_node(Ref)` function on the VM in the main thread; + or genuinely refuse them and see where that constraint leaves me. + +## Similar or related projects + +### [WorkerDOM ] + + I had the idea for this project fairly firmly mapped out in my mind, + then I went and searched the web to see if someone else had implemented something like it. + I found WorkerDOM, published about nine months prior, which implements the full DOM API inside a Web Worker. + + Most of WorkerDOM’s rationale applies to remote-dom-vm as well; + the talk in which it was announced, + [*WorkerDOM: JavaScript Concurrency and the DOM* ], + is great. + + WorkerDOM maintains its own representation of the structure of the real DOM inside the worker. DOM methods then fall into three categories: + + 1. Methods that can be implemented based upon this record of the DOM structure, + by copying the algorithm browsers use (this is almost everything); + + 2. Methods that need to be run asynchronously instead: mostly for methods that interact with layout, + such as getBoundingClientRect; + asynchronous alternatives are defined which call the main thread to perform the operation and yield the answer. + + 3. Methods that are unimplementable with no alternative: + synchronous methods on events, like event.preventDefault(). + You can’t do those asynchronously, so you’ll need a bit of main thread code there. + + By comparison, remote-dom-vm is deliberately write-only, + and does not maintain any knowledge of DOM structure, + exposing only a few mutator method calls and no getters. + You’re expected to maintain any knowledge of the structure that you need yourself. + + This makes it *much* more flexible than remote-dom-vm, which roughly just defines an assembly code. + + Notably, remote-dom-vm has no interest at this time in providing *read* access to *anything*. Even if it ever does, it’s not going to be done as a DOM shim. + +### [Glimmer ] + + Glimmer manages to be a rather fast DOM rendering engine by using a VM architecture. + Its instruction set is a good deal more complicated than remote-dom-vm’s, as it represents a lot more semantics; + remote-dom-vm’s instruction set, by comparison, is limited to just a few DOM things. + Glimmer is CISC to remote-dom-vm’s RISC, if you like. + +## Author + + [Chris Morgan ] is the primary author and maintainer of remote-dom-vm. + +## License + + Copyright © 2019– Chris Morgan + + This project is distributed under the terms of three different licenses, at your choice: + + • [Blue Oak Model License 1.0.0 ] + • [MIT License ] + • [Apache License, Version 2.0 ] + + If you do not have particular cause to select the MIT or the Apache-2.0 license, + Chris Morgan recommends that you select BlueOak-1.0.0, + which is better and simpler than both MIT and Apache-2.0, + which are only offered due to their greater recognition and their conventional use in the Rust ecosystem. + (BlueOak-1.0.0 was only published in March 2019.) + + When using this code, ensure you comply with the terms of at least one of these licenses. diff --git a/bytecode.ttt b/bytecode.ttt new file mode 100644 index 0000000..156bb8e --- /dev/null +++ b/bytecode.ttt @@ -0,0 +1,177 @@ +# remote-dom-vm bytecode format description + + **Précis:** + write-only instruction set; + single byte opcodes; + variable-width instructions; + big-endian; + UTF-8 strings prefixed by 32-bit length. + +## Types + + ### u32 (unsigned 32-bit integers) + + Four bytes in big-endian order. + + ◊code` + 0 1 2 3 + ├───┴───┴───┴───┤ + │ number │ + └───────────────┘ + ` + + As an example, the bytes \[0x12, 0x34, 0x56, 0x78\] represent the number 0x12345678 (305,419,896₁₀). + + ### String + + ◊code` + 0 1 2 3 ... + ├───┴───┴───┴───┼─────────────────── + │ length: u32 │ ... data + └───────────────┴─────────────────── + ` + + A string is represented as its length in UTF-8 code units as a u32, + followed by the UTF-8 code points. + + Although JavaScript strings allow unmatched surrogate code points, + this layer only allows legal Unicode, + for performance, simplicity and brevity of implementation. + + As a 32-bit unsigned integer, a string’s length lies in the range ◊`[0, 2³²)`. + It is not possible to represent strings greater than 4,294,967,295 UTF-8 code units long. + If you feel a pressing need to put strings even one *hundredth* of this size into the DOM, you should rethink things. + + ### NodeId + + DOM nodes (regardless of subtype, whether document, element, text or comment) are identified by an unsigned 32-bit integer. + NodeId 0 is currently reserved for the document being operated upon. + (This may change in the future, to a single VM host supporting working with multiple documents.) + Certain instructions (opcodes 0–4) reserve a new NodeId. + The client and host agree at compile time which of the following NodeId allocation schemes will be used, + so that they can both calculate what the next NodeId will be, + so that the VM can be write-only. + +## NodeId allocation schemes + + ### Simplistic + + Each new NodeId is one greater than the previous NodeId, + with no reuse of past NodeIds. + + • Excellent for static or almost-static pages, + having the lowest baseline memory usage and highest performance. + + • Cannot create more than 2\³\² nodes in total. + In practice, this is not a serious concern: + if you create ten thousand nodes per second, + which would suggest you were doing something wildly wrong, + it still takes five days to exhaust this. + (In practice, the memory usage thing will crash your page long before this.) + + • Memory usage is likely to be proportional to the number of nodes ever created, + so heavily dynamic pages will use more memory than they should. + (On the VM host side, it will become a sparse array, + and the JavaScript engine will probably optimise it so, + leaving memory usage proportional to number of current nodes. + But on the client side, this is unlikely to be the case, + the node map probably being something like ◊code.rs`Vec>`, + and so it’ll probably be using at least four bytes per node ever allocated. + This, incidentally, will cause an out-of-memory crash much earlier, + probably by no more than a billion nodes.) + + ### Compact + + NodeIds are reused in most-recently-freed order, + and if there are no freed NodeIds remaining then a new NodeId is minted, + one higher than the previous highest. + + (The implementation of this is straightforward, + but a prose description cumbersome; + look at the code if you want more. + It involves leaving tombstones and swapping next NodeIds.) + + • Somewhat higher baseline memory per node, + and probably very slightly lower performance. + + • Memory usage is proportional to the largest number of nodes that existed at one time. + +## Instructions + + The first byte of an instruction is the opcode. + After that, instruction widths vary. + + ### 0: CreateElement + + • document (NodeId of a Document in JavaScript-land) + • tag_name (string) + + Allocates a new NodeId, corresponding to an HTMLElement in JavaScript-land. + + ### 1: CreateSvgElement + + • document (NodeId of a Document in JavaScript-land) + • tag_name (string) + + Allocates a new NodeId, corresponding to an SVGElement in JavaScript-land. + + ### 2: CreateTextNode + + • document (NodeId of a Document in JavaScript-land) + • data (string) + + Allocates a new NodeId, corresponding to a Text in JavaScript-land. + + ### 3: CreateComment + + • document (NodeId of a Document in JavaScript-land) + • data (string) + + Allocates a new NodeId, corresponding to a Comment in JavaScript-land. + + ### 4: CreateDocumentFragment + + • document (NodeId of a Document in JavaScript-land) + + Allocates a new NodeId, corresponding to a DocumentFragment in JavaScript-land. + + ### 5: SetData + + • node (NodeId of a CharacterData in JavaScript-land, meaning a Text or a Comment) + • data (string) + + ### 6: SetAttribute + + • element (NodeId of an Element in JavaScript-land) + • name (string) + • value (string) + + ### 7: RemoveAttribute + + • element (NodeId of an Element in JavaScript-land) + • name (string) + + ### 8: AppendChild + + • parent (NodeId of an Element in JavaScript-land) + • new_child (NodeId, typically of a DocumentFragment, Element, Text or Comment) + + ### 9: InsertBefore + + • parent (NodeId of an Element in JavaScript-land) + • reference_child (NodeId, typically of a Element, Text or Comment) + • new_child (NodeId, typically of a DocumentFragment, Element, Text or Comment) + + ### 10: Free + + • node (NodeId of any Node) + +## Error handling + + Current implementations assume correct usage and are not robust against incorrect input. + Behaviour in the presence of incorrect input is undefined. + + Client and VM implementations may coordinate on which instructions to support; + for example, on an app that never creates any SVG, + the code implementing opcode 1 (CreateSvgElement) may be removed; + calling it would then produce undefined behaviour. diff --git a/rust-client/Cargo.toml b/rust-client/Cargo.toml new file mode 100644 index 0000000..13ea522 --- /dev/null +++ b/rust-client/Cargo.toml @@ -0,0 +1,19 @@ +[package] +authors = ["Chris Morgan "] +edition = "2018" +license = "BlueOak-1.0.0 OR MIT OR Apache-2.0" +name = "remote-dom-client" +version = "0.1.0" + +[features] +default = ["svg", "document-fragment", "comment"] +dictionaries = [] +# CreateSvgElement opcode and corresponding create_svg_element method +svg = [] +# CreateDocumentFragment opcode and corresponding create_document_fragment method +document-fragment = [] +# CreateComment opcode and corresponding create_comment method +comment = [] + +[profile.release] +opt-level = "s" diff --git a/rust-client/src/dictionaries.rs b/rust-client/src/dictionaries.rs new file mode 100644 index 0000000..11eaf51 --- /dev/null +++ b/rust-client/src/dictionaries.rs @@ -0,0 +1,1199 @@ +//! Dictionaries used in the byte code. Each dictionary must have no more than 255 entries. + +/// All known HTML tag names. +/// +/// Taken from https://html.spec.whatwg.org/#elements-3, minus and . +pub static CREATE_ELEMENT: [&str; 112] = [ + "a", + "abbr", + "address", + "area", + "article", + "aside", + "audio", + "b", + "base", + "bdi", + "bdo", + "blockquote", + "body", + "br", + "button", + "canvas", + "caption", + "cite", + "code", + "col", + "colgroup", + "data", + "datalist", + "dd", + "del", + "details", + "dfn", + "dialog", + "div", + "dl", + "dt", + "em", + "embed", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "hgroup", + "hr", + "html", + "i", + "iframe", + "img", + "input", + "ins", + "kbd", + "label", + "legend", + "li", + "link", + "main", + "map", + "mark", + "menu", + "meta", + "meter", + "nav", + "noscript", + "object", + "ol", + "optgroup", + "option", + "output", + "p", + "param", + "picture", + "pre", + "progress", + "q", + "rp", + "rt", + "ruby", + "s", + "samp", + "script", + "section", + "select", + "slot", + "small", + "source", + "span", + "strong", + "style", + "sub", + "summary", + "sup", + "table", + "tbody", + "td", + "template", + "textarea", + "tfoot", + "th", + "thead", + "time", + "title", + "tr", + "track", + "u", + "ul", + "var", + "video", + "wbr", +]; + +/// All known SVG tag names. +/// +/// Taken from https://svgwg.org/svg2-draft/eltindex.html. +pub static CREATE_SVG_ELEMENT: [&str; 64] = [ + "a", + "animate", + "animateMotion", + "animateTransform", + "circle", + "clipPath", + "defs", + "desc", + "discard", + "ellipse", + "feBlend", + "feColorMatrix", + "feComponentTransfer", + "feComposite", + "feConvolveMatrix", + "feDiffuseLighting", + "feDisplacementMap", + "feDistantLight", + "feDropShadow", + "feFlood", + "feFuncA", + "feFuncB", + "feFuncG", + "feFuncR", + "feGaussianBlur", + "feImage", + "feMerge", + "feMergeNode", + "feMorphology", + "feOffset", + "fePointLight", + "feSpecularLighting", + "feSpotLight", + "feTile", + "feTurbulence", + "filter", + "foreignObject", + "g", + "image", + "line", + "linearGradient", + "marker", + "mask", + "metadata", + "mpath", + "path", + "pattern", + "polygon", + "polyline", + "radialGradient", + "rect", + "script", + "set", + "stop", + "style", + "svg", + "switch", + "symbol", + "text", + "textPath", + "title", + "tspan", + "use", + "view", +]; + +/* +/// All known HTML attribute names. +/// +/// Taken from https://html.spec.whatwg.org/#attributes-3, *excluding* the event handler content +/// attributes. +/// +/// Also added all the ARIA attributes (using the list from the SVG spec, TODO source better). +pub static HTML_ATTRIBUTE_NAMES: [&str; 172] = [ + "abbr", + "accept", + "accept-charset", + "accesskey", + "action", + "allow", + "allowfullscreen", + "allowpaymentrequest", + "alt", + "aria-activedescendant", + "aria-atomic", + "aria-autocomplete", + "aria-busy", + "aria-checked", + "aria-colcount", + "aria-colindex", + "aria-colspan", + "aria-controls", + "aria-current", + "aria-describedby", + "aria-details", + "aria-disabled", + "aria-dropeffect", + "aria-errormessage", + "aria-expanded", + "aria-flowto", + "aria-grabbed", + "aria-haspopup", + "aria-hidden", + "aria-invalid", + "aria-keyshortcuts", + "aria-label", + "aria-labelledby", + "aria-level", + "aria-live", + "aria-modal", + "aria-multiline", + "aria-multiselectable", + "aria-orientation", + "aria-owns", + "aria-placeholder", + "aria-posinset", + "aria-pressed", + "aria-readonly", + "aria-relevant", + "aria-required", + "aria-roledescription", + "aria-rowcount", + "aria-rowindex", + "aria-rowspan", + "aria-selected", + "aria-setsize", + "aria-sort", + "aria-valuemax", + "aria-valuemin", + "aria-valuenow", + "aria-valuetext", + "as", + "async", + "autocapitalize", + "autocomplete", + "autofocus", + "autoplay", + "charset", + "checked", + "cite", + "class", + "color", + "cols", + "colspan", + "content", + "contenteditable", + "controls", + "coords", + "crossorigin", + "data", + "datetime", + "decoding", + "default", + "defer", + "dir", + "dirname", + "disabled", + "download", + "draggable", + "enctype", + "enterkeyhint", + "for", + "form", + "formaction", + "formenctype", + "formmethod", + "formnovalidate", + "formtarget", + "headers", + "height", + "hidden", + "high", + "href", + "hreflang", + "http-equiv", + "id", + "imagesizes", + "imagesrcset", + "inputmode", + "integrity", + "is", + "ismap", + "itemid", + "itemprop", + "itemref", + "itemscope", + "itemtype", + "kind", + "label", + "lang", + "list", + "loop", + "low", + "manifest", + "max", + "maxlength", + "media", + "method", + "min", + "minlength", + "multiple", + "muted", + "name", + "nomodule", + "nonce", + "novalidate", + "open", + "optimum", + "pattern", + "ping", + "placeholder", + "playsinline", + "poster", + "preload", + "readonly", + "referrerpolicy", + "rel", + "required", + "reversed", + "rows", + "rowspan", + "sandbox", + "scope", + "selected", + "shape", + "size", + "sizes", + "slot", + "span", + "spellcheck", + "src", + "srcdoc", + "srclang", + "srcset", + "start", + "step", + "style", + "tabindex", + "target", + "title", + "translate", + "type", + "usemap", + "value", + "width", + "wrap", +]; + +/// All known SVG attribute names. +/// +/// Taken from https://svgwg.org/svg2-draft/attindex.html#RegularAttributes, *excluding* the “on*” +/// event handler attributes. Presentation attributes are also excluded, not being in that section. +pub static SVG_ATTRIBUTE_NAMES: [&str; 184] = [ + "accumulate", + "additive", + "amplitude", + "aria-activedescendant", + "aria-atomic", + "aria-autocomplete", + "aria-busy", + "aria-checked", + "aria-colcount", + "aria-colindex", + "aria-colspan", + "aria-controls", + "aria-current", + "aria-describedby", + "aria-details", + "aria-disabled", + "aria-dropeffect", + "aria-errormessage", + "aria-expanded", + "aria-flowto", + "aria-grabbed", + "aria-haspopup", + "aria-hidden", + "aria-invalid", + "aria-keyshortcuts", + "aria-label", + "aria-labelledby", + "aria-level", + "aria-live", + "aria-modal", + "aria-multiline", + "aria-multiselectable", + "aria-orientation", + "aria-owns", + "aria-placeholder", + "aria-posinset", + "aria-pressed", + "aria-readonly", + "aria-relevant", + "aria-required", + "aria-roledescription", + "aria-rowcount", + "aria-rowindex", + "aria-rowspan", + "aria-selected", + "aria-setsize", + "aria-sort", + "aria-valuemax", + "aria-valuemin", + "aria-valuenow", + "aria-valuetext", + "attributeName", + "azimuth", + "baseFrequency", + "begin", + "bias", + "by", + "calcMode", + "class", + "clipPathUnits", + "crossorigin", + "cx", + "cy", + "diffuseConstant", + "divisor", + "download", + "dur", + "dx", + "dy", + "edgeMode", + "elevation", + "end", + "exponent", + "fill", + "filterUnits", + "fr", + "from", + "fx", + "fy", + "gradientTransform", + "gradientUnits", + "height", + "href", + "hreflang", + "id", + "in", + "in2", + "intercept", + "k1", + "k2", + "k3", + "k4", + "kernelMatrix", + "kernelUnitLength", + "keyPoints", + "keySplines", + "keyTimes", + "lang", + "lengthAdjust", + "limitingConeAngle", + "markerHeight", + "markerUnits", + "markerWidth", + "maskContentUnits", + "maskUnits", + "max", + "media", + "method", + "min", + "mode", + "numOctaves", + "offset", + "operator", + "order", + "orient", + "origin", + "path", + "pathLength", + "patternContentUnits", + "patternTransform", + "patternUnits", + "ping", + "playbackorder", + "points", + "pointsAtX", + "pointsAtY", + "pointsAtZ", + "preserveAlpha", + "preserveAspectRatio", + "primitiveUnits", + "r", + "radius", + "refX", + "refY", + "referrerpolicy", + "rel", + "repeatCount", + "repeatDur", + "requiredExtensions", + "restart", + "result", + "role", + "rotate", + "scale", + "seed", + "side", + "slope", + "spacing", + "specularConstant", + "specularExponent", + "spreadMethod", + "startOffset", + "stdDeviation", + "stitchTiles", + "style", + "surfaceScale", + "systemLanguage", + "tabindex", + "tableValues", + "target", + "targetX", + "targetY", + "textLength", + "timelinebegin", + "title", + "to", + "transform", + "type", + "values", + "viewBox", + "width", + "x", + "x1", + "x2", + "xChannelSelector", + "xlink:href", + "xlink:title", + "xml:space", + "y", + "y1", + "y2", + "yChannelSelector", + "z", + "zoomAndPan", +]; + +/// A unified dictionary of HTML and SVG attributes. A work in progress, because there are too many +/// in total, so I want to remove some obsolete or extremely uncommon ones. +pub static ATTRIBUTE_NAMES: [&str; 287] = [ // XXX: 287 is too long, cull some I suppose. + "abbr", + "accept", + "accept-charset", + "accesskey", + "accumulate", + "action", + "additive", + "allow", + "allowfullscreen", + "allowpaymentrequest", + "alt", + "amplitude", + "aria-activedescendant", + "aria-atomic", + "aria-autocomplete", + "aria-busy", + "aria-checked", + "aria-colcount", + "aria-colindex", + "aria-colspan", + "aria-controls", + "aria-current", + "aria-describedby", + "aria-details", + "aria-disabled", + "aria-dropeffect", + "aria-errormessage", + "aria-expanded", + "aria-flowto", + "aria-grabbed", + "aria-haspopup", + "aria-hidden", + "aria-invalid", + "aria-keyshortcuts", + "aria-label", + "aria-labelledby", + "aria-level", + "aria-live", + "aria-modal", + "aria-multiline", + "aria-multiselectable", + "aria-orientation", + "aria-owns", + "aria-placeholder", + "aria-posinset", + "aria-pressed", + "aria-readonly", + "aria-relevant", + "aria-required", + "aria-roledescription", + "aria-rowcount", + "aria-rowindex", + "aria-rowspan", + "aria-selected", + "aria-setsize", + "aria-sort", + "aria-valuemax", + "aria-valuemin", + "aria-valuenow", + "aria-valuetext", + "as", + "async", + "attributeName", + "autocapitalize", + "autocomplete", + "autofocus", + "autoplay", + "azimuth", + "baseFrequency", + "begin", + "bias", + "by", + "calcMode", + "charset", + "checked", + "cite", + "class", + "clipPathUnits", + "color", + "cols", + "colspan", + "content", + "contenteditable", + "controls", + "coords", + "crossorigin", + "cx", + "cy", + "data", + "datetime", + "decoding", + "default", + "defer", + "diffuseConstant", + "dir", + "dirname", + "disabled", + "divisor", + "download", + "draggable", + "dur", + "dx", + "dy", + "edgeMode", + "elevation", + "enctype", + "end", + "enterkeyhint", + "exponent", + "fill", + "filterUnits", + "for", + "form", + "formaction", + "formenctype", + "formmethod", + "formnovalidate", + "formtarget", + "fr", + "from", + "fx", + "fy", + "gradientTransform", + "gradientUnits", + "headers", + "height", + "hidden", + "high", + "href", + "hreflang", + "http-equiv", + "id", + "imagesizes", + "imagesrcset", + "in", + "in2", + "inputmode", + "integrity", + "intercept", + "is", + "ismap", + "itemid", + "itemprop", + "itemref", + "itemscope", + "itemtype", + "k1", + "k2", + "k3", + "k4", + "kernelMatrix", + "kernelUnitLength", + "keyPoints", + "keySplines", + "keyTimes", + "kind", + "label", + "lang", + "lengthAdjust", + "limitingConeAngle", + "list", + "loop", + "low", + "manifest", + "markerHeight", + "markerUnits", + "markerWidth", + "maskContentUnits", + "maskUnits", + "max", + "maxlength", + "media", + "method", + "min", + "minlength", + "mode", + "multiple", + "muted", + "name", + "nomodule", + "nonce", + "novalidate", + "numOctaves", + "offset", + "open", + "operator", + "optimum", + "order", + "orient", + "origin", + "path", + "pathLength", + "pattern", + "patternContentUnits", + "patternTransform", + "patternUnits", + "ping", + "placeholder", + "playbackorder", + "playsinline", + "points", + "pointsAtX", + "pointsAtY", + "pointsAtZ", + "poster", + "preload", + "preserveAlpha", + "preserveAspectRatio", + "primitiveUnits", + "r", + "radius", + "readonly", + "refX", + "refY", + "referrerpolicy", + "rel", + "repeatCount", + "repeatDur", + "required", + "requiredExtensions", + "restart", + "result", + "reversed", + "role", + "rotate", + "rows", + "rowspan", + "sandbox", + "scale", + "scope", + "seed", + "selected", + "shape", + "side", + "size", + "sizes", + "slope", + "slot", + "spacing", + "span", + "specularConstant", + "specularExponent", + "spellcheck", + "spreadMethod", + "src", + "srcdoc", + "srclang", + "srcset", + "start", + "startOffset", + "stdDeviation", + "step", + "stitchTiles", + "style", + "surfaceScale", + "systemLanguage", + "tabindex", + "tableValues", + "target", + "targetX", + "targetY", + "textLength", + "timelinebegin", + "title", + "to", + "transform", + "translate", + "type", + "usemap", + "value", + "values", + "viewBox", + "width", + "wrap", + "x", + "x1", + "x2", + "xChannelSelector", + "xlink:href", + "xlink:title", + "xml:space", + "y", + "y1", + "y2", + "yChannelSelector", + "z", + "zoomAndPan", +]; + +/// All known SVG event names. +/// +/// Taken from https://svgwg.org/svg2-draft/attindex.html, all the “on…” attribute names. +/// TODO better source. +pub static SVG_EVENT_NAMES: [&str; 77] = [ + "abort", + "afterprint", + "beforeprint", + "begin", + "cancel", + "canplay", + "canplaythrough", + "change", + "click", + "close", + "copy", + "cuechange", + "cut", + "dblclick", + "drag", + "dragend", + "dragenter", + "dragexit", + "dragleave", + "dragover", + "dragstart", + "drop", + "durationchange", + "emptied", + "end", + "ended", + "error", + "focus", + "focusin", + "focusout", + "hashchange", + "input", + "invalid", + "keydown", + "keypress", + "keyup", + "load", + "loadeddata", + "loadedmetadata", + "loadstart", + "message", + "mousedown", + "mouseenter", + "mouseleave", + "mousemove", + "mouseout", + "mouseover", + "mouseup", + "offline", + "online", + "pagehide", + "pageshow", + "paste", + "pause", + "play", + "playing", + "popstate", + "progress", + "ratechange", + "repeat", + "reset", + "resize", + "scroll", + "seeked", + "seeking", + "select", + "show", + "stalled", + "storage", + "submit", + "suspend", + "timeupdate", + "toggle", + "unload", + "volumechange", + "waiting", + "wheel", +]; + +// TODO: make a dictionary of known attribute values (e.g. "", "true", "false", "stylesheet", "icon") + +/// All known HTML event names. +/// +/// Taken from https://html.spec.whatwg.org/#events-2, https://html.spec.whatwg.org/#mediaevents, +/// https://html.spec.whatwg.org/#appcacheevents and https://html.spec.whatwg.org/#dndevents. +/// TODO what about other things like upgrade? +pub static HTML_EVENT_NAMES: [&str; 77] = [ + "DOMContentLoaded", + "abort", + "afterprint", + "beforeprint", + "beforeunload", + "blur", + "cached", + "cancel", + "canplay", + "canplaythrough", + "change", + "checking", + "click", + "close", + "connect", + "contextmenu", + "copy", + "cut", + "downloading", + "drag", + "dragend", + "dragenter", + "dragexit", + "dragleave", + "dragover", + "dragstart", + "drop", + "durationchange", + "emptied", + "ended", + "error", + "focus", + "formdata", + "hashchange", + "input", + "invalid", + "languagechange", + "load", + "loadeddata", + "loadedmetadata", + "loadend", + "loadstart", + "message", + "messageerror", + "noupdate", + "obsolete", + "offline", + "online", + "open", + "pagehide", + "pageshow", + "paste", + "pause", + "play", + "playing", + "popstate", + "progress", + "ratechange", + "readystatechange", + "rejectionhandled", + "reset", + "resize", + "securitypolicyviolation", + "seeked", + "seeking", + "select", + "stalled", + "storage", + "submit", + "suspend", + "timeupdate", + "toggle", + "unhandledrejection", + "unload ", + "updateready", + "volumechange ", + "waiting", +]; + +pub static ATTRIBUTE_VALUES: [&str; 120] = [ + // Various + "", + "on", + "off", + "none", + "auto", + + // autocapitalize + "sentences", + "words", + "characters", + "true", + "false", + + // + "utf-8", + + // + "preload", + "modulepreload", + + // + "fetch", + "audio", + "audioworklet", + "document", + "embed", + "font", + "image", + "manifest", + "object", + "paintworklet", + "report", + "script", + "serviceworker", + "sharedworker", + "style", + "track", + "video", + "worker", + "xslt", + + // crossorigin + "anonymous", + "use-credentials", + + // + "sync", + "async", + + // dir + "rtl", + "ltr", + + //
, formenctype + "application/x-www-form-urlencoded", + "multipart/form-data", + "text/plain" , + + // enterkeyhint + "enter", + "done", + "go", + "next", + "previous", + "search", + "send", + + // formmethod + "get", + "post", + "dialog", + + // http-equiv + "content-type", + "default-style", + "refresh", + "x-ua-compatible", + "content-security-policy", + + // inputmode + "text", + "tel", + "email", + "url", + "numeric", + "decimal", + "search", + + // + "subtitles", + "captions", + "descriptions", + "chapters", + "metadata", + + // preload + "metadata", + + // referrerpolicy + "no-referrer", + "no-referrer-when-downgrade", + "same-origin", + "origin", + "strict-origin", + "origin-when-cross-origin", + "strict-origin-when-cross-origin", + "unsafe-url", + + // rel: TODO + + // scope + "row", + "col", + "rowgroup", + "colgroup", + + // + "circle", + "default", + "poly", + "rect", + + // step + "any", + + // translate + "yes", + "no", + + //