├── .gitignore ├── Cargo.toml ├── LICENCE ├── README.md ├── encoder ├── Cargo.toml └── src │ ├── attribute.rs │ ├── batch.rs │ ├── element.rs │ └── lib.rs ├── prebuild ├── Cargo.toml ├── examples │ └── macro.rs └── src │ └── lib.rs ├── src └── lib.rs └── web ├── Cargo.toml ├── examples └── web_sys_comparison.rs ├── interpreter.js ├── interpreter_manually_opt.js ├── interpreter_opt.js └── src ├── channel.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | /dist/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | 14 | # Added by cargo 15 | 16 | /target 17 | /dist 18 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sledgehammer-workspace" 3 | version = "0.2.0" 4 | authors = ["Evan Almloff "] 5 | edition = "2021" 6 | description = "Fast bindings for dom manipulations" 7 | documentation = "https://docs.rs/sledgehammer" 8 | readme = "README.md" 9 | repository = "https://github.com/demonthos/sledgehammer/" 10 | license = "MIT" 11 | keywords = ["web", "wasm", "dom"] 12 | categories = ["web-programming", "wasm", "api-bindings"] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | wasm-bindgen = "0.2.83" 18 | web-sys = { version = "0.3.60", features = ["console", "Window", "Document", "Element", "HtmlElement", "HtmlHeadElement"] } 19 | js-sys = "0.3.60" 20 | sledgehammer-encoder = { path = "./encoder" } 21 | sledgehammer-prebuild = { path = "./prebuild" } 22 | 23 | [workspace] 24 | members = [ 25 | "prebuild", 26 | "encoder", 27 | "web" 28 | ] -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Demonthos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

sledgehammer

3 |
4 |
5 | 6 | 7 | Crates.io version 9 | 10 | 11 | 12 | Download 14 | 15 | 16 | 17 | docs.rs docs 19 | 20 |
21 | 22 | # Development of this library has stoped in favor of [Sledgehammer Bindgen](https://github.com/Demonthos/sledgehammer_bindgen) which is a generalized version of this concept that works with non-DOM focused instruction sets. 23 | 24 | **Breaking the WASM<->JS performance boundary one brick at a time** 25 | ### Status: There are some holes in the wall. 26 | 27 | # What is Sledgehammer? 28 | Sledgehammer provides faster rust bindings for dom manipulations by batching calls to js. 29 | 30 | # Benchmarks 31 | 32 | - js-framework-benchmark that trusts the implementation and only measures dom operation time (not paint time): 33 | https://demonthos.github.io/wasm_bindgen_sledgehammer/ 34 | This gives more consistent results than the official js-framework-benchmark because it excludes the variation in paint time. Because sledgehammer and wasm-bindgen implementations result in the same dom calls, they should have the same paint time. 35 | 36 | - The official js-framework-benchmark results 37 |
38 | 39 |
40 | 41 | # How does this compare to wasm-bindgen/web-sys: 42 | wasm-bindgen is a lot more general, and ergonomic to use than sledgehammer. It has bindings to a lot of apis that sledgehammer does not. For most users wasm-bindgen is a beter choice. Sledgehammer is specifically designed for web frameworks that want low-level, fast access to the dom. 43 | 44 | # Why is it fast? 45 | 46 | ## String decoding 47 | 48 | - Decoding strings are expensive to decode, but the cost doesn't change much with the size of the string. Wasm-bindgen calls TextDecoder.decode for every string. Sledgehammer only calls TextEncoder.decode once per batch. 49 | 50 | - If the string is small, it is faster to decode the string in javascript to avoid the constant overhead of TextDecoder.decode 51 | 52 | - See this benchmark: https://jsbench.me/4vl97c05lb/5 53 | 54 | ## Single byte attributes and elements 55 | 56 | - In addition to making string decoding cheaper, sledgehammer also uses fewer strings. All elements and attribute names are encoded as a single byte instead of a string and then turned back into a string in the javascript interpreter. 57 | 58 | - To allow for custom elements and attributes, you can pass in a &str instead of an Attribute or Element enum. 59 | 60 | ## Byte encoded operations 61 | 62 | - In sledgehammer every operation is encoded as a sequence of bytes packed into an array. Every operation takes 1 byte plus whatever data is required for it. 63 | 64 | - Booleans are encoded as part of the operation byte to reduce the number of bytes read. 65 | 66 | - Each operation is encoded in a batch of four as a u32. Getting a number from an array buffer has a high constant cost, but getting a u32 instead of a u8 is not more expensive. Sledgehammer reads the u32 and then splits it into the 4 individual bytes. 67 | 68 | - See this benchmark: https://jsbench.me/csl9lfauwi/2 69 | 70 | ## Minimize passing ids 71 | 72 | - A common set of operations for webframeworks to perform is traversing dom nodes after cloning them. Instead of assigning an id to every node, sledgehammer allows you to perform operations on the last node that was created or navigated to. This means traversing id takes only one byte per operation instead of 5. 73 | -------------------------------------------------------------------------------- /encoder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sledgehammer-encoder" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | -------------------------------------------------------------------------------- /encoder/src/attribute.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | 3 | use self::sealed::Sealed; 4 | use crate::{batch::Batch, InNamespace}; 5 | 6 | mod sealed { 7 | use crate::{Attribute, InNamespace}; 8 | 9 | pub trait Sealed {} 10 | 11 | impl Sealed for Attribute {} 12 | impl<'a> Sealed for InNamespace<'a, Attribute> {} 13 | impl<'a> Sealed for &'a str {} 14 | impl<'a, 'b> Sealed for InNamespace<'b, &'a str> {} 15 | } 16 | 17 | #[derive(Clone, Copy)] 18 | pub enum AnyAttribute<'a, 'b> { 19 | Attribute(Attribute), 20 | InNamespace(InNamespace<'a, Attribute>), 21 | Str(&'a str), 22 | InNamespaceStr(InNamespace<'a, &'b str>), 23 | } 24 | 25 | impl AnyAttribute<'_, '_> { 26 | pub(crate) unsafe fn encode_u8_discriminant_prealloc(self, v: &mut Batch) { 27 | match self { 28 | AnyAttribute::Attribute(a) => a.encode_u8_discriminant_prealloc(v), 29 | AnyAttribute::InNamespace(a) => a.encode_u8_discriminant_prealloc(v), 30 | AnyAttribute::Str(a) => a.encode_u8_discriminant_prealloc(v), 31 | AnyAttribute::InNamespaceStr(a) => a.encode_u8_discriminant_prealloc(v), 32 | } 33 | } 34 | 35 | pub(crate) fn size_with_u8_discriminant(&self) -> usize { 36 | match self { 37 | AnyAttribute::Attribute(_) => 1, 38 | AnyAttribute::InNamespace(_) => 1 + 1 + 2, 39 | AnyAttribute::Str(_) => 1 + 2, 40 | AnyAttribute::InNamespaceStr(_) => 1 + 2 + 2, 41 | } 42 | } 43 | } 44 | 45 | /// Anything that can be turned into an attribute 46 | pub trait IntoAttribue<'a, 'b>: Sealed + Into> { 47 | /// If the attribute can be encoded in a single byte 48 | const SINGLE_BYTE: bool = false; 49 | 50 | /// Encode the attribute into the message channel 51 | fn encode(self, v: &mut Batch); 52 | 53 | /// Encode the attribute into the message channel with memory pre-allocated 54 | /// 55 | /// # Safety 56 | /// 57 | /// This is only safe if the batch is preallocated to the correct size 58 | unsafe fn encode_prealloc(self, v: &mut Batch) 59 | where 60 | Self: Sized, 61 | { 62 | self.encode(v); 63 | } 64 | 65 | /// Encode the attribute into the message channel with a u8 desciminant instead of bit packed bools 66 | /// 67 | /// # Safety 68 | /// 69 | /// This is only safe if the batch is preallocated to the correct size 70 | unsafe fn encode_u8_discriminant_prealloc(self, v: &mut Batch); 71 | } 72 | 73 | impl<'a, 'b> Attribute { 74 | /// Turn into an [`AnyAttribute`] in a const context 75 | pub const fn any_attr_const(self) -> AnyAttribute<'a, 'b> { 76 | AnyAttribute::Attribute(self) 77 | } 78 | } 79 | 80 | impl<'a, 'b> IntoAttribue<'a, 'b> for Attribute { 81 | const SINGLE_BYTE: bool = true; 82 | 83 | #[inline(always)] 84 | fn encode(self, v: &mut Batch) { 85 | v.encode_bool(false); 86 | v.encode_bool(false); 87 | v.msg.push(self as u8); 88 | } 89 | 90 | #[inline(always)] 91 | unsafe fn encode_prealloc(self, v: &mut Batch) { 92 | v.encode_bool(false); 93 | v.encode_bool(false); 94 | unsafe { 95 | let ptr: *mut u8 = v.msg.as_mut_ptr(); 96 | *ptr.add(v.msg.len()) = self as u8; 97 | v.msg.set_len(v.msg.len() + 1); 98 | } 99 | } 100 | 101 | #[inline(always)] 102 | unsafe fn encode_u8_discriminant_prealloc(self, v: &mut Batch) { 103 | v.encode_u8_prealloc(self as u8) 104 | } 105 | } 106 | 107 | impl<'a, 'b> From for AnyAttribute<'a, 'b> { 108 | fn from(a: Attribute) -> Self { 109 | AnyAttribute::Attribute(a) 110 | } 111 | } 112 | 113 | impl<'a, 'b> InNamespace<'a, Attribute> { 114 | pub const fn any_attr_const(self) -> AnyAttribute<'a, 'b> { 115 | AnyAttribute::InNamespace(self) 116 | } 117 | } 118 | 119 | impl<'a, 'b> IntoAttribue<'a, 'b> for InNamespace<'a, Attribute> { 120 | #[inline(always)] 121 | fn encode(self, v: &mut Batch) { 122 | v.encode_bool(false); 123 | v.msg.push(self.0 as u8); 124 | v.encode_bool(true); 125 | v.encode_str(self.1); 126 | } 127 | 128 | #[inline(always)] 129 | unsafe fn encode_u8_discriminant_prealloc(self, v: &mut Batch) { 130 | v.encode_u8_prealloc(255); 131 | v.encode_u8_prealloc(self.0 as u8); 132 | v.encode_str_prealloc(self.1); 133 | } 134 | } 135 | 136 | impl<'a, 'b> From> for AnyAttribute<'a, 'b> { 137 | fn from(a: InNamespace<'a, Attribute>) -> Self { 138 | AnyAttribute::InNamespace(a) 139 | } 140 | } 141 | 142 | impl<'a, 'b> IntoAttribue<'a, 'b> for &'a str { 143 | fn encode(self, v: &mut Batch) { 144 | v.encode_bool(true); 145 | v.encode_cachable_str(self); 146 | v.encode_bool(false); 147 | } 148 | 149 | unsafe fn encode_u8_discriminant_prealloc(self, v: &mut Batch) { 150 | v.encode_u8_prealloc(254); 151 | v.encode_str_prealloc(self); 152 | } 153 | } 154 | 155 | impl<'a, 'b> From<&'a str> for AnyAttribute<'a, 'b> { 156 | fn from(a: &'a str) -> Self { 157 | AnyAttribute::Str(a) 158 | } 159 | } 160 | 161 | impl<'a, 'b> InNamespace<'a, &'b str> { 162 | pub const fn any_attr_const(self) -> AnyAttribute<'a, 'b> { 163 | AnyAttribute::InNamespaceStr(self) 164 | } 165 | } 166 | 167 | impl<'a, 'b> IntoAttribue<'a, 'b> for InNamespace<'a, &'b str> { 168 | fn encode(self, v: &mut Batch) { 169 | v.encode_bool(true); 170 | v.encode_cachable_str(self.0); 171 | v.encode_bool(true); 172 | v.encode_cachable_str(self.1); 173 | } 174 | 175 | unsafe fn encode_u8_discriminant_prealloc(self, v: &mut Batch) { 176 | v.encode_u8_prealloc(253); 177 | v.encode_str_prealloc(self.0); 178 | v.encode_str_prealloc(self.1); 179 | } 180 | } 181 | 182 | impl<'a, 'b> From> for AnyAttribute<'a, 'b> { 183 | fn from(a: InNamespace<'a, &'b str>) -> Self { 184 | AnyAttribute::InNamespaceStr(a) 185 | } 186 | } 187 | 188 | macro_rules! attributes { 189 | ($($i: ident),*) => { 190 | /// All built-in attributes 191 | /// These are the attributes can be encoded with a single byte so they are more efficient (but less flexable) than a &str attribute 192 | #[derive(Copy, Clone)] 193 | pub enum Attribute { 194 | $( 195 | $i 196 | ),* 197 | } 198 | 199 | pub struct NotElementError; 200 | 201 | impl std::str::FromStr for Attribute { 202 | type Err = NotElementError; 203 | 204 | fn from_str(s: &str) -> Result { 205 | Ok(match s{ 206 | $( 207 | stringify!($i) => Self::$i, 208 | )* 209 | _ => return Err(NotElementError) 210 | }) 211 | } 212 | } 213 | }; 214 | } 215 | 216 | attributes! { 217 | accept_charset, 218 | accept, 219 | accesskey, 220 | action, 221 | align, 222 | allow, 223 | alt, 224 | aria_atomic, 225 | aria_busy, 226 | aria_controls, 227 | aria_current, 228 | aria_describedby, 229 | aria_description, 230 | aria_details, 231 | aria_disabled, 232 | aria_dropeffect, 233 | aria_errormessage, 234 | aria_flowto, 235 | aria_grabbed, 236 | aria_haspopup, 237 | aria_hidden, 238 | aria_invalid, 239 | aria_keyshortcuts, 240 | aria_label, 241 | aria_labelledby, 242 | aria_live, 243 | aria_owns, 244 | aria_relevant, 245 | aria_roledescription, 246 | r#async, 247 | autocapitalize, 248 | autocomplete, 249 | autofocus, 250 | autoplay, 251 | background, 252 | bgcolor, 253 | border, 254 | buffered, 255 | capture, 256 | challenge, 257 | charset, 258 | checked, 259 | cite, 260 | class, 261 | code, 262 | codebase, 263 | color, 264 | cols, 265 | colspan, 266 | content, 267 | contenteditable, 268 | contextmenu, 269 | controls, 270 | coords, 271 | crossorigin, 272 | csp, 273 | data, 274 | datetime, 275 | decoding, 276 | default, 277 | defer, 278 | dir, 279 | dirname, 280 | disabled, 281 | download, 282 | draggable, 283 | enctype, 284 | enterkeyhint, 285 | r#for, 286 | form, 287 | formaction, 288 | formenctype, 289 | formmethod, 290 | formnovalidate, 291 | formtarget, 292 | headers, 293 | height, 294 | hidden, 295 | high, 296 | href, 297 | hreflang, 298 | http_equiv, 299 | icon, 300 | id, 301 | importance, 302 | inputmode, 303 | integrity, 304 | intrinsicsize, 305 | ismap, 306 | itemprop, 307 | keytype, 308 | kind, 309 | label, 310 | lang, 311 | language, 312 | list, 313 | loading, 314 | r#loop, 315 | low, 316 | manifest, 317 | max, 318 | maxlength, 319 | media, 320 | method, 321 | min, 322 | minlength, 323 | multiple, 324 | muted, 325 | name, 326 | novalidate, 327 | open, 328 | optimum, 329 | pattern, 330 | ping, 331 | placeholder, 332 | poster, 333 | preload, 334 | radiogroup, 335 | readonly, 336 | referrerpolicy, 337 | rel, 338 | required, 339 | reversed, 340 | role, 341 | rows, 342 | rowspan, 343 | sandbox, 344 | scope, 345 | scoped, 346 | selected, 347 | shape, 348 | size, 349 | sizes, 350 | slot, 351 | span, 352 | spellcheck, 353 | src, 354 | srcdoc, 355 | srclang, 356 | srcset, 357 | start, 358 | step, 359 | style, 360 | summary, 361 | tabindex, 362 | target, 363 | title, 364 | translate, 365 | r#type, 366 | usemap, 367 | value, 368 | width, 369 | wrap 370 | } 371 | -------------------------------------------------------------------------------- /encoder/src/batch.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ElementBuilder, IntoAttribue, IntoElement, MaybeId, NodeId, TextBuilder, WritableText, 3 | }; 4 | 5 | // operations that have no booleans can be encoded as a half byte, these are placed first 6 | pub enum Op { 7 | /// Navigates to the last node to the first child of the current node. 8 | FirstChild = 0, 9 | 10 | /// Navigates to the last node to the last child of the current node. 11 | NextSibling = 1, 12 | 13 | /// Navigates to the last node to the parent of the current node. 14 | ParentNode = 2, 15 | 16 | /// Stores the last node with a new id. 17 | StoreWithId = 3, 18 | 19 | /// Manually set the last node. 20 | SetLastNode = 4, 21 | 22 | /// Stop 23 | Stop = 5, 24 | 25 | /// Build Full Element 26 | BuildFullElement = 6, 27 | 28 | /// Pop the topmost node from our stack and append them to the node 29 | AppendChildren = 7, 30 | 31 | /// Replace a given (single) node with a handful of nodes currently on the stack. 32 | ReplaceWith = 8, 33 | 34 | /// Insert a number of nodes after a given node. 35 | InsertAfter = 9, 36 | 37 | /// Insert a number of nodes before a given node. 38 | InsertBefore = 10, 39 | 40 | /// Remove a particular node from the DOM 41 | Remove = 11, 42 | 43 | /// Create a new text node 44 | CreateTextNode = 12, 45 | 46 | /// Create a new element node 47 | CreateElement = 13, 48 | 49 | /// Set the textcontent of a node. 50 | SetText = 14, 51 | 52 | /// Set the value of a node's attribute. 53 | SetAttribute = 15, 54 | 55 | /// Remove an attribute from a node. 56 | RemoveAttribute = 16, 57 | 58 | /// Set a style property on a node. 59 | SetStyle = 17, 60 | 61 | /// Remove a style property from a node. 62 | RemoveStyle = 18, 63 | 64 | /// Clones a node. 65 | CloneNode = 19, 66 | 67 | /// Does nothing, but allows us to skip a byte. 68 | NoOp = 20, 69 | } 70 | 71 | /// A batch of operations ready to perform on the DOM. 72 | pub trait PreparedBatch { 73 | fn msg(&self) -> &[u8]; 74 | fn str(&self) -> &[u8]; 75 | } 76 | 77 | /// A batch of operations ready to perform on the DOM. 78 | pub struct FinalizedBatch { 79 | pub msg: Vec, 80 | pub str: Vec, 81 | } 82 | 83 | impl PreparedBatch for FinalizedBatch { 84 | fn msg(&self) -> &[u8] { 85 | &self.msg 86 | } 87 | fn str(&self) -> &[u8] { 88 | &self.str 89 | } 90 | } 91 | 92 | impl<'a> PreparedBatch for &'a FinalizedBatch { 93 | fn msg(&self) -> &[u8] { 94 | &self.msg 95 | } 96 | fn str(&self) -> &[u8] { 97 | &self.str 98 | } 99 | } 100 | 101 | /// A batch of static operations ready to perform on the DOM. 102 | /// This is meant to be generated from FinalizedBatch from a macro. 103 | pub struct StaticBatch { 104 | pub msg: &'static [u8], 105 | pub str: &'static [u8], 106 | } 107 | 108 | impl PreparedBatch for StaticBatch { 109 | fn msg(&self) -> &[u8] { 110 | self.msg 111 | } 112 | fn str(&self) -> &[u8] { 113 | self.str 114 | } 115 | } 116 | 117 | impl<'a> PreparedBatch for &'a StaticBatch { 118 | fn msg(&self) -> &[u8] { 119 | self.msg 120 | } 121 | fn str(&self) -> &[u8] { 122 | self.str 123 | } 124 | } 125 | 126 | /// A batch of operations to perform on the DOM. 127 | /// 128 | /// This allows you to build up a batch of operations to perform on the DOM outside of the main MsgChannel batch. 129 | /// This is useful for building up a batch of operations to perform on the DOM many times. If the operation is only performed once, it is better to use the `MsgChannel` directly because it reuses the same allocation from the last batch of operations. 130 | /// See [`MsgChannel::append`] and [`MsgChannel::run_batch`] for examples. 131 | /// The methods on this struct are a subset of the methods on [`MsgChannel`] and work the same with the exception of [`Batch::finalize`]. 132 | pub struct Batch { 133 | #[doc(hidden)] 134 | pub msg: Vec, 135 | #[doc(hidden)] 136 | pub str_buf: Vec, 137 | #[doc(hidden)] 138 | pub current_op_batch_idx: usize, 139 | #[doc(hidden)] 140 | pub current_op_byte_idx: usize, 141 | #[doc(hidden)] 142 | pub current_op_bit_pack_index: u8, 143 | } 144 | 145 | impl Default for Batch { 146 | fn default() -> Self { 147 | Self { 148 | msg: Vec::new(), 149 | str_buf: Vec::new(), 150 | current_op_byte_idx: 3, 151 | current_op_bit_pack_index: 0, 152 | current_op_batch_idx: 0, 153 | } 154 | } 155 | } 156 | 157 | impl Batch { 158 | /// Finalizes the batch and prepares it to be run 159 | pub fn finalize(mut self) -> FinalizedBatch { 160 | self.encode_op(Op::Stop); 161 | FinalizedBatch { 162 | msg: self.msg, 163 | str: self.str_buf, 164 | } 165 | } 166 | 167 | /// Appends a number of nodes as children of the given node. 168 | pub fn append_child(&mut self, root: MaybeId, child: MaybeId) { 169 | self.encode_op(Op::AppendChildren); 170 | let size = root.encoded_size() + child.encoded_size(); 171 | self.msg.reserve(size as usize); 172 | unsafe { 173 | self.encode_maybe_id_prealloc(root); 174 | self.encode_maybe_id_prealloc(child); 175 | } 176 | } 177 | 178 | /// Replace a node with another node 179 | pub fn replace_with(&mut self, root: MaybeId, node: MaybeId) { 180 | self.encode_op(Op::ReplaceWith); 181 | let size = root.encoded_size() + node.encoded_size(); 182 | self.msg.reserve(size as usize); 183 | unsafe { 184 | self.encode_maybe_id_prealloc(root); 185 | self.encode_maybe_id_prealloc(node); 186 | } 187 | } 188 | 189 | /// Replace a node with many nodes 190 | pub fn replace_with_nodes(&mut self, root: MaybeId, nodes: &[MaybeId]) { 191 | self.encode_op(Op::ReplaceWith); 192 | self.encode_bool(true); 193 | self.encode_maybe_id(root); 194 | self.msg.push(nodes.len() as u8); 195 | for n in nodes { 196 | self.encode_maybe_id_u8_discriminant(*n); 197 | } 198 | } 199 | 200 | /// Insert a single node after a given node. 201 | pub fn insert_after(&mut self, root: MaybeId, node: MaybeId) { 202 | self.encode_op(Op::InsertAfter); 203 | let size = root.encoded_size() + node.encoded_size(); 204 | self.msg.reserve(size as usize); 205 | unsafe { 206 | self.encode_bool(false); 207 | self.encode_maybe_id_prealloc(root); 208 | self.encode_maybe_id_prealloc(node); 209 | } 210 | } 211 | 212 | /// Insert many nodes after a given node. 213 | pub fn insert_nodes_after(&mut self, root: MaybeId, nodes: &[MaybeId]) { 214 | self.encode_op(Op::InsertAfter); 215 | self.encode_bool(true); 216 | self.encode_maybe_id(root); 217 | self.msg.push(nodes.len() as u8); 218 | for n in nodes { 219 | self.encode_maybe_id_u8_discriminant(*n); 220 | } 221 | } 222 | 223 | /// Insert a single node before a given node. 224 | pub fn insert_before(&mut self, root: MaybeId, node: MaybeId) { 225 | self.encode_op(Op::InsertBefore); 226 | let size = root.encoded_size() + node.encoded_size(); 227 | self.msg.reserve(size as usize); 228 | unsafe { 229 | self.encode_bool(false); 230 | self.encode_maybe_id_prealloc(root); 231 | self.encode_maybe_id_prealloc(node); 232 | } 233 | } 234 | 235 | /// Insert many nodes before a given node. 236 | pub fn insert_nodes_before(&mut self, root: MaybeId, nodes: &[MaybeId]) { 237 | self.encode_op(Op::InsertBefore); 238 | self.encode_bool(true); 239 | self.encode_maybe_id(root); 240 | self.msg.push(nodes.len() as u8); 241 | for n in nodes { 242 | self.encode_maybe_id_u8_discriminant(*n); 243 | } 244 | } 245 | 246 | /// Remove a node from the DOM. 247 | pub fn remove(&mut self, id: MaybeId) { 248 | self.encode_op(Op::Remove); 249 | self.encode_maybe_id(id); 250 | } 251 | 252 | /// Create a new text node 253 | pub fn create_text_node(&mut self, text: impl WritableText, id: Option) { 254 | self.encode_op(Op::CreateTextNode); 255 | let size = (id.is_some() as u8) * 4 + 2; 256 | self.msg.reserve(size as usize); 257 | unsafe { 258 | self.encode_str_prealloc(text); 259 | self.encode_optional_id_prealloc(id); 260 | } 261 | } 262 | 263 | /// Create a new element node 264 | pub fn create_element<'a, 'b, E>(&mut self, tag: E, id: Option) 265 | where 266 | E: IntoElement<'a, 'b>, 267 | { 268 | self.encode_op(Op::CreateElement); 269 | self.msg 270 | .reserve((E::SINGLE_BYTE as u8 + (id.is_some() as u8) * 4) as usize); 271 | unsafe { 272 | tag.encode_prealloc(self); 273 | self.encode_optional_id_prealloc(id); 274 | } 275 | } 276 | 277 | /// Set the textcontent of a node. 278 | pub fn set_text(&mut self, text: impl WritableText, root: MaybeId) { 279 | self.encode_op(Op::SetText); 280 | let size = root.encoded_size() + 2; 281 | self.msg.reserve(size as usize); 282 | unsafe { 283 | self.encode_maybe_id_prealloc(root); 284 | self.encode_str_prealloc(text); 285 | } 286 | } 287 | 288 | /// Set the value of a node's attribute. 289 | pub fn set_attribute<'a, 'b, A>(&mut self, attr: A, value: impl WritableText, root: MaybeId) 290 | where 291 | A: IntoAttribue<'a, 'b>, 292 | { 293 | self.encode_op(Op::SetAttribute); 294 | self.msg 295 | .reserve((A::SINGLE_BYTE as u8 + root.encoded_size() + 2) as usize); 296 | unsafe { 297 | self.encode_maybe_id_prealloc(root); 298 | attr.encode_prealloc(self); 299 | self.encode_str_prealloc(value); 300 | } 301 | } 302 | 303 | /// Remove an attribute from a node. 304 | pub fn remove_attribute<'a, 'b, A>(&mut self, attr: A, root: MaybeId) 305 | where 306 | A: IntoAttribue<'a, 'b>, 307 | { 308 | self.encode_op(Op::RemoveAttribute); 309 | let size = A::SINGLE_BYTE as u8 + root.encoded_size(); 310 | self.msg.reserve(size as usize); 311 | unsafe { 312 | self.encode_maybe_id_prealloc(root); 313 | attr.encode_prealloc(self); 314 | } 315 | } 316 | 317 | /// Clone a node and store it with a new id. 318 | pub fn clone_node(&mut self, id: MaybeId, new_id: MaybeId) { 319 | self.encode_op(Op::CloneNode); 320 | let size = id.encoded_size() + new_id.encoded_size(); 321 | self.msg.reserve(size as usize); 322 | self.encode_maybe_id(id); 323 | self.encode_maybe_id(new_id); 324 | } 325 | 326 | /// Move the last node to the first child 327 | pub fn first_child(&mut self) { 328 | self.encode_op(Op::FirstChild); 329 | } 330 | 331 | /// Move the last node to the next sibling 332 | pub fn next_sibling(&mut self) { 333 | self.encode_op(Op::NextSibling); 334 | } 335 | 336 | /// Move the last node to the parent node 337 | pub fn parent_node(&mut self) { 338 | self.encode_op(Op::ParentNode); 339 | } 340 | 341 | /// Store the last node with the given id. This is useful when traversing the document tree. 342 | pub fn store_with_id(&mut self, id: NodeId) { 343 | self.encode_op(Op::StoreWithId); 344 | self.encode_id(id); 345 | } 346 | 347 | /// Set the last node to the given id. The last node can be used to traverse the document tree without passing objects between wasm and js every time. 348 | pub fn set_last_node(&mut self, id: NodeId) { 349 | self.encode_op(Op::SetLastNode); 350 | self.encode_id(id); 351 | } 352 | 353 | /// Build a full element, slightly more efficent than creating the element creating the element with `create_element` and then setting the attributes. 354 | pub fn build_full_element(&mut self, el: ElementBuilder) { 355 | self.encode_op(Op::BuildFullElement); 356 | el.encode(self); 357 | } 358 | 359 | /// Build a text node 360 | pub fn build_text_node(&mut self, text: TextBuilder) { 361 | self.create_text_node(text.text, text.id) 362 | } 363 | 364 | /// Set a style property on a node. 365 | pub fn set_style(&mut self, style: &str, value: &str, id: MaybeId) { 366 | self.encode_op(Op::SetStyle); 367 | let size = id.encoded_size() + 2 + 2; 368 | self.msg.reserve(size as usize); 369 | self.encode_maybe_id(id); 370 | unsafe { 371 | self.encode_str_prealloc(style); 372 | self.encode_str_prealloc(value); 373 | } 374 | } 375 | 376 | /// Remove a style property from a node. 377 | pub fn remove_style(&mut self, style: &str, id: MaybeId) { 378 | self.encode_op(Op::RemoveStyle); 379 | let size = id.encoded_size() + 2; 380 | self.msg.reserve(size as usize); 381 | unsafe { 382 | self.encode_maybe_id_prealloc(id); 383 | self.encode_str_prealloc(style); 384 | } 385 | } 386 | 387 | #[inline] 388 | pub(crate) unsafe fn encode_optional_id_prealloc(&mut self, id: Option) { 389 | match id { 390 | Some(id) => { 391 | self.encode_bool(true); 392 | self.encode_id_prealloc(id); 393 | } 394 | None => { 395 | self.encode_bool(false); 396 | } 397 | } 398 | } 399 | 400 | #[inline] 401 | pub(crate) unsafe fn encode_maybe_id_prealloc(&mut self, id: MaybeId) { 402 | match id { 403 | MaybeId::Node(id) => { 404 | self.encode_bool(true); 405 | self.encode_id_prealloc(id); 406 | } 407 | MaybeId::LastNode => { 408 | self.encode_bool(false); 409 | } 410 | } 411 | } 412 | 413 | #[inline] 414 | pub(crate) fn encode_maybe_id_u8_discriminant(&mut self, id: MaybeId) { 415 | match id { 416 | MaybeId::Node(id) => { 417 | self.msg.push(1); 418 | self.encode_id(id); 419 | } 420 | MaybeId::LastNode => { 421 | self.msg.push(0); 422 | } 423 | } 424 | } 425 | 426 | #[inline] 427 | pub(crate) fn encode_maybe_id(&mut self, id: MaybeId) { 428 | match id { 429 | MaybeId::Node(id) => { 430 | self.encode_bool(true); 431 | self.encode_id(id); 432 | } 433 | MaybeId::LastNode => { 434 | self.encode_bool(false); 435 | } 436 | } 437 | } 438 | 439 | #[inline(always)] 440 | pub(crate) unsafe fn encode_id_prealloc(&mut self, id: NodeId) { 441 | self.encode_u32_prealloc(id.0); 442 | } 443 | 444 | #[inline(always)] 445 | pub(crate) fn encode_id(&mut self, id: NodeId) { 446 | self.encode_u32(id.0); 447 | } 448 | 449 | #[inline(always)] 450 | pub(crate) fn encode_u32(&mut self, val: u32) { 451 | self.msg.reserve(4); 452 | unsafe { 453 | self.encode_u32_prealloc(val); 454 | } 455 | } 456 | 457 | #[inline(always)] 458 | pub(crate) unsafe fn encode_u32_prealloc(&mut self, val: u32) { 459 | let le = val.to_le(); 460 | unsafe { 461 | let len = self.msg.len(); 462 | self.msg.as_mut_ptr().add(len).cast::().write(le); 463 | self.msg.set_len(len + 4); 464 | } 465 | } 466 | 467 | #[inline(always)] 468 | pub(crate) fn encode_u16(&mut self, val: u16) { 469 | self.msg.reserve(2); 470 | unsafe { 471 | self.encode_u16_prealloc(val); 472 | } 473 | } 474 | 475 | #[inline(always)] 476 | pub(crate) unsafe fn encode_u16_prealloc(&mut self, val: u16) { 477 | let le = val.to_le(); 478 | #[allow(clippy::uninit_vec)] 479 | unsafe { 480 | let len = self.msg.len(); 481 | self.msg.as_mut_ptr().add(len).cast::().write(le); 482 | self.msg.set_len(len + 2); 483 | } 484 | } 485 | 486 | #[inline(always)] 487 | pub(crate) fn encode_u8_prealloc(&mut self, val: u8) { 488 | let le = val.to_le(); 489 | #[allow(clippy::uninit_vec)] 490 | unsafe { 491 | let len = self.msg.len(); 492 | self.msg.as_mut_ptr().add(len).write(le); 493 | self.msg.set_len(len + 1); 494 | } 495 | } 496 | 497 | #[inline] 498 | pub(crate) fn encode_str(&mut self, string: impl WritableText) { 499 | let prev_len = self.str_buf.len(); 500 | string.write_as_text(&mut self.str_buf); 501 | let len = self.str_buf.len() - prev_len; 502 | self.encode_u16(len as u16); 503 | } 504 | 505 | #[inline] 506 | pub(crate) unsafe fn encode_str_prealloc(&mut self, string: impl WritableText) { 507 | let prev_len = self.str_buf.len(); 508 | string.write_as_text(&mut self.str_buf); 509 | let len = self.str_buf.len() - prev_len; 510 | self.encode_u16_prealloc(len as u16); 511 | } 512 | 513 | #[inline] 514 | pub(crate) fn encode_cachable_str(&mut self, string: impl WritableText) { 515 | let prev_len = self.str_buf.len(); 516 | string.write_as_text(&mut self.str_buf); 517 | let len = self.str_buf.len() - prev_len; 518 | self.encode_u16(len as u16); 519 | } 520 | 521 | #[inline] 522 | #[doc(hidden)] 523 | pub fn encode_op(&mut self, op: Op) { 524 | let u8_op = op as u8; 525 | 526 | self.current_op_byte_idx += 1; 527 | if self.current_op_byte_idx - self.current_op_batch_idx < 4 { 528 | unsafe { 529 | *self.msg.get_unchecked_mut(self.current_op_byte_idx) = u8_op; 530 | } 531 | } else { 532 | self.current_op_batch_idx = self.msg.len(); 533 | self.current_op_byte_idx = self.current_op_batch_idx; 534 | // reserve four bytes for the op batch 535 | #[allow(clippy::uninit_vec)] 536 | unsafe { 537 | let len = self.msg.len(); 538 | self.msg.reserve(4); 539 | self.msg.set_len(len + 4); 540 | *self.msg.get_unchecked_mut(self.current_op_batch_idx) = u8_op; 541 | } 542 | } 543 | self.current_op_bit_pack_index = 0; 544 | } 545 | 546 | pub(crate) fn encode_bool(&mut self, value: bool) { 547 | if self.current_op_bit_pack_index < 3 { 548 | if value { 549 | unsafe { 550 | *self.msg.get_unchecked_mut(self.current_op_byte_idx) |= 551 | 1 << (self.current_op_bit_pack_index + 5); 552 | } 553 | } 554 | self.current_op_bit_pack_index += 1; 555 | } else { 556 | todo!("handle more than 3 bools in a op"); 557 | } 558 | } 559 | 560 | pub fn append(&mut self, mut batch: Self) { 561 | // add empty operations to the batch to make sure the batch is aligned 562 | let operations_left = 3 - (self.current_op_byte_idx - self.current_op_batch_idx); 563 | for _ in 0..operations_left { 564 | self.encode_op(Op::NoOp); 565 | } 566 | 567 | self.current_op_byte_idx = self.msg.len() + batch.current_op_byte_idx; 568 | self.current_op_batch_idx = self.msg.len() + batch.current_op_batch_idx; 569 | self.current_op_bit_pack_index = batch.current_op_bit_pack_index; 570 | self.str_buf.extend_from_slice(&batch.str_buf); 571 | self.msg.append(&mut batch.msg); 572 | } 573 | } 574 | -------------------------------------------------------------------------------- /encoder/src/element.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | 3 | use crate::{attribute::AnyAttribute, batch::Batch, InNamespace, NodeId}; 4 | 5 | use self::sealed::Sealed; 6 | 7 | mod sealed { 8 | use crate::{Element, InNamespace}; 9 | 10 | pub trait Sealed {} 11 | 12 | impl Sealed for Element {} 13 | impl<'a> Sealed for &'a str {} 14 | impl<'a> Sealed for InNamespace<'a, Element> {} 15 | impl<'a, 'b> Sealed for InNamespace<'a, &'b str> {} 16 | } 17 | 18 | pub enum AnyElement<'a, 'b> { 19 | Element(Element), 20 | InNamespace(InNamespace<'a, Element>), 21 | Str(&'a str), 22 | InNamespaceStr(InNamespace<'a, &'b str>), 23 | } 24 | 25 | impl AnyElement<'_, '_> { 26 | pub fn encode(&self, v: &mut Batch) { 27 | match self { 28 | AnyElement::Element(a) => a.encode(v), 29 | AnyElement::InNamespace(a) => a.encode(v), 30 | AnyElement::Str(a) => a.encode(v), 31 | AnyElement::InNamespaceStr(a) => a.encode(v), 32 | } 33 | } 34 | 35 | pub(crate) unsafe fn encode_prealloc(&self, v: &mut Batch) { 36 | match self { 37 | AnyElement::Element(a) => a.encode_prealloc(v), 38 | AnyElement::InNamespace(a) => a.encode_prealloc(v), 39 | AnyElement::Str(a) => a.encode_prealloc(v), 40 | AnyElement::InNamespaceStr(a) => a.encode_prealloc(v), 41 | } 42 | } 43 | 44 | pub(crate) fn size(&self) -> usize { 45 | match self { 46 | AnyElement::Element(_) => 1, 47 | AnyElement::InNamespace(_) => 1 + 2, 48 | AnyElement::Str(_) => 2, 49 | AnyElement::InNamespaceStr(_) => 2 + 2, 50 | } 51 | } 52 | } 53 | 54 | /// Anything that can be turned into an element name 55 | pub trait IntoElement<'a, 'b>: Sealed + Into> { 56 | /// If the element name can be encoded in a single byte 57 | const SINGLE_BYTE: bool = false; 58 | 59 | /// Encode the element into the message channel 60 | fn encode(&self, v: &mut Batch); 61 | 62 | /// Encode the element into the message channel with memory pre-allocated 63 | /// # Safety 64 | /// 65 | /// This is only safe if the batch is preallocated to the correct size 66 | unsafe fn encode_prealloc(&self, v: &mut Batch) 67 | where 68 | Self: Sized, 69 | { 70 | self.encode(v); 71 | } 72 | } 73 | 74 | impl<'a, 'b> Element { 75 | pub const fn any_element_const(self) -> AnyElement<'a, 'b> { 76 | AnyElement::Element(self) 77 | } 78 | } 79 | 80 | impl<'a, 'b> IntoElement<'a, 'b> for Element { 81 | const SINGLE_BYTE: bool = true; 82 | 83 | #[inline(always)] 84 | fn encode(&self, v: &mut Batch) { 85 | v.msg.push(*self as u8); 86 | } 87 | 88 | #[inline(always)] 89 | unsafe fn encode_prealloc(&self, v: &mut Batch) 90 | where 91 | Self: Sized, 92 | { 93 | unsafe { 94 | let ptr: *mut u8 = v.msg.as_mut_ptr(); 95 | let old_len = v.msg.len(); 96 | *ptr.add(old_len) = *self as u8; 97 | v.msg.set_len(old_len + 1); 98 | } 99 | } 100 | } 101 | 102 | impl<'a, 'b> From for AnyElement<'a, 'b> { 103 | fn from(e: Element) -> Self { 104 | AnyElement::Element(e) 105 | } 106 | } 107 | 108 | impl<'a, 'b> InNamespace<'a, Element> { 109 | /// Turn into an [`AnyElement`] in a const context 110 | pub const fn any_element_const(self) -> AnyElement<'a, 'b> { 111 | AnyElement::InNamespace(self) 112 | } 113 | } 114 | 115 | impl<'a, 'b> IntoElement<'a, 'b> for InNamespace<'a, Element> { 116 | fn encode(&self, v: &mut Batch) { 117 | v.msg.push(255); 118 | v.msg.push(self.0 as u8); 119 | v.encode_str(self.1); 120 | } 121 | } 122 | 123 | impl<'a, 'b> From> for AnyElement<'a, 'b> { 124 | fn from(e: InNamespace<'a, Element>) -> Self { 125 | AnyElement::InNamespace(e) 126 | } 127 | } 128 | 129 | impl<'a, 'b> IntoElement<'a, 'b> for &'a str { 130 | fn encode(&self, v: &mut Batch) { 131 | v.msg.push(254); 132 | v.encode_str(*self); 133 | } 134 | } 135 | 136 | impl<'a, 'b> From<&'a str> for AnyElement<'a, 'b> { 137 | fn from(e: &'a str) -> Self { 138 | AnyElement::Str(e) 139 | } 140 | } 141 | 142 | impl<'a, 'b> IntoElement<'a, 'b> for InNamespace<'a, &'b str> { 143 | fn encode(&self, v: &mut Batch) { 144 | v.msg.push(253); 145 | v.encode_str(self.0); 146 | v.encode_str(self.1); 147 | } 148 | } 149 | 150 | impl<'a, 'b> From> for AnyElement<'a, 'b> { 151 | fn from(e: InNamespace<'a, &'b str>) -> Self { 152 | AnyElement::InNamespaceStr(e) 153 | } 154 | } 155 | 156 | impl<'a, 'b> InNamespace<'a, &'b str> { 157 | pub const fn any_element_const(self) -> AnyElement<'a, 'b> { 158 | AnyElement::InNamespaceStr(self) 159 | } 160 | } 161 | 162 | /// A builder for any node 163 | pub enum NodeBuilder<'a> { 164 | Text(TextBuilder<'a>), 165 | Element(ElementBuilder<'a>), 166 | } 167 | 168 | impl NodeBuilder<'_> { 169 | /// Encode the node into a batch 170 | pub(crate) fn encode(&self, v: &mut Batch) { 171 | match self { 172 | NodeBuilder::Text(t) => t.encode(v), 173 | NodeBuilder::Element(e) => e.encode(v), 174 | } 175 | } 176 | } 177 | 178 | impl<'a> From> for NodeBuilder<'a> { 179 | fn from(t: TextBuilder<'a>) -> Self { 180 | NodeBuilder::Text(t) 181 | } 182 | } 183 | 184 | impl<'a> From> for NodeBuilder<'a> { 185 | fn from(e: ElementBuilder<'a>) -> Self { 186 | NodeBuilder::Element(e) 187 | } 188 | } 189 | 190 | /// A builder for an text node with a id, and text 191 | pub struct TextBuilder<'a> { 192 | pub(crate) id: Option, 193 | pub(crate) text: &'a str, 194 | } 195 | 196 | impl<'a> TextBuilder<'a> { 197 | /// Create a new text builder 198 | pub const fn new(text: &'a str) -> Self { 199 | Self { id: None, text } 200 | } 201 | 202 | /// Set the id of the text node 203 | pub const fn id(mut self, id: NodeId) -> Self { 204 | self.id = Some(id); 205 | self 206 | } 207 | 208 | /// Encode the text node into a batch 209 | pub(crate) fn encode(&self, v: &mut Batch) { 210 | match self.id { 211 | Some(id) => { 212 | v.msg.push(3); 213 | v.encode_id(id); 214 | } 215 | None => { 216 | v.msg.push(2); 217 | } 218 | } 219 | v.encode_str(self.text); 220 | } 221 | } 222 | 223 | /// A builder for a element with an id, kind, attributes, and children 224 | /// 225 | /// /// Example: 226 | /// ```rust 227 | /// let mut channel = MsgChannel::default(); 228 | 229 | /// // create an element using sledgehammer 230 | /// channel.build_full_element( 231 | /// ElementBuilder::new("div".into()) 232 | /// .id(NodeId(1)) 233 | /// .attrs(&[(Attribute::style.into(), "color: blue")]) 234 | /// .children(&[ 235 | /// ElementBuilder::new(Element::p.into()) 236 | /// .into(), 237 | /// TextBuilder::new("Hello from sledgehammer!").into(), 238 | /// ]), 239 | /// ); 240 | /// channel.flush(); 241 | /// ``` 242 | pub struct ElementBuilder<'a> { 243 | id: Option, 244 | kind: AnyElement<'a, 'a>, 245 | attrs: &'a [(AnyAttribute<'a, 'a>, &'a str)], 246 | children: &'a [NodeBuilder<'a>], 247 | } 248 | 249 | impl<'a> ElementBuilder<'a> { 250 | /// Create a new element builder 251 | pub const fn new(kind: AnyElement<'a, 'a>) -> Self { 252 | Self { 253 | id: None, 254 | kind, 255 | attrs: &[], 256 | children: &[], 257 | } 258 | } 259 | 260 | /// Set the id of the element 261 | pub const fn id(mut self, id: NodeId) -> Self { 262 | self.id = Some(id); 263 | self 264 | } 265 | 266 | /// Set the attributes of the element 267 | pub const fn attrs(mut self, attrs: &'a [(AnyAttribute<'a, 'a>, &'a str)]) -> Self { 268 | self.attrs = attrs; 269 | self 270 | } 271 | 272 | /// Set the children of the element 273 | pub const fn children(mut self, children: &'a [NodeBuilder<'a>]) -> Self { 274 | self.children = children; 275 | self 276 | } 277 | 278 | /// Encode the element into the a batch 279 | pub(crate) fn encode(&self, v: &mut Batch) { 280 | let size = 1 281 | + (self.id.is_some() as usize) * 4 282 | + self.kind.size() 283 | + 1 284 | + 1 285 | + self 286 | .attrs 287 | .iter() 288 | .map(|(k, _)| k.size_with_u8_discriminant() + 2) 289 | .sum::(); 290 | v.msg.reserve(size); 291 | unsafe { 292 | match self.id { 293 | Some(id) => { 294 | v.encode_u8_prealloc(1); 295 | v.encode_id_prealloc(id); 296 | } 297 | None => { 298 | v.encode_u8_prealloc(0); 299 | } 300 | } 301 | self.kind.encode_prealloc(v); 302 | // these are packed together so they can be read as a u16 303 | v.encode_u8_prealloc(self.attrs.len() as u8); 304 | v.encode_u8_prealloc(self.children.len() as u8); 305 | for (attr, value) in self.attrs { 306 | attr.encode_u8_discriminant_prealloc(v); 307 | v.encode_str_prealloc(*value); 308 | } 309 | } 310 | for child in self.children { 311 | child.encode(v); 312 | } 313 | } 314 | } 315 | 316 | macro_rules! elements { 317 | ($($i: ident),*) => { 318 | /// All built-in elements 319 | /// These are the element can be encoded with a single byte so they are more efficient (but less flexable) than a &str element 320 | #[allow(unused)] 321 | #[derive(Copy, Clone)] 322 | pub enum Element { 323 | $( 324 | $i 325 | ),* 326 | } 327 | 328 | pub struct NotElementError; 329 | 330 | impl std::str::FromStr for Element { 331 | type Err = NotElementError; 332 | 333 | fn from_str(s: &str) -> Result { 334 | Ok(match s{ 335 | $( 336 | stringify!($i) => Self::$i, 337 | )* 338 | _ => return Err(NotElementError) 339 | }) 340 | } 341 | } 342 | }; 343 | } 344 | 345 | elements! { 346 | a, 347 | abbr, 348 | acronym, 349 | address, 350 | applet, 351 | area, 352 | article, 353 | aside, 354 | audio, 355 | b, 356 | base, 357 | bdi, 358 | bdo, 359 | bgsound, 360 | big, 361 | blink, 362 | blockquote, 363 | body, 364 | br, 365 | button, 366 | canvas, 367 | caption, 368 | center, 369 | cite, 370 | code, 371 | col, 372 | colgroup, 373 | content, 374 | data, 375 | datalist, 376 | dd, 377 | del, 378 | details, 379 | dfn, 380 | dialog, 381 | dir, 382 | div, 383 | dl, 384 | dt, 385 | em, 386 | embed, 387 | fieldset, 388 | figcaption, 389 | figure, 390 | font, 391 | footer, 392 | form, 393 | frame, 394 | frameset, 395 | h1, 396 | head, 397 | header, 398 | hgroup, 399 | hr, 400 | html, 401 | i, 402 | iframe, 403 | image, 404 | img, 405 | input, 406 | ins, 407 | kbd, 408 | keygen, 409 | label, 410 | legend, 411 | li, 412 | link, 413 | main, 414 | map, 415 | mark, 416 | marquee, 417 | menu, 418 | menuitem, 419 | meta, 420 | meter, 421 | nav, 422 | nobr, 423 | noembed, 424 | noframes, 425 | noscript, 426 | object, 427 | ol, 428 | optgroup, 429 | option, 430 | output, 431 | p, 432 | param, 433 | picture, 434 | plaintext, 435 | portal, 436 | pre, 437 | progress, 438 | q, 439 | rb, 440 | rp, 441 | rt, 442 | rtc, 443 | ruby, 444 | s, 445 | samp, 446 | script, 447 | section, 448 | select, 449 | shadow, 450 | slot, 451 | small, 452 | source, 453 | spacer, 454 | span, 455 | strike, 456 | strong, 457 | style, 458 | sub, 459 | summary, 460 | sup, 461 | table, 462 | tbody, 463 | td, 464 | template, 465 | textarea, 466 | tfoot, 467 | th, 468 | thead, 469 | time, 470 | title, 471 | tr, 472 | track, 473 | tt, 474 | u, 475 | ul, 476 | var, 477 | video, 478 | wbr, 479 | xmp 480 | } 481 | -------------------------------------------------------------------------------- /encoder/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod attribute; 2 | pub mod batch; 3 | pub mod element; 4 | 5 | use std::{fmt::Arguments, io::Write}; 6 | 7 | pub use attribute::{Attribute, IntoAttribue}; 8 | pub use batch::{Op, StaticBatch}; 9 | pub use element::{Element, ElementBuilder, IntoElement, NodeBuilder, TextBuilder}; 10 | 11 | /// Something that lives in a namespace like a tag or attribute 12 | #[derive(Clone, Copy)] 13 | pub struct InNamespace<'a, T>(pub T, pub &'a str); 14 | 15 | /// Something that can live in a namespace 16 | pub trait WithNsExt { 17 | /// Moves the item into a namespace 18 | fn in_namespace(self, namespace: &str) -> InNamespace 19 | where 20 | Self: Sized, 21 | { 22 | InNamespace(self, namespace) 23 | } 24 | } 25 | 26 | impl WithNsExt for Element {} 27 | impl WithNsExt for Attribute {} 28 | impl<'a> WithNsExt for &'a str {} 29 | 30 | /// An id that may be either the last node or a node with an assigned id. 31 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 32 | pub enum MaybeId { 33 | /// The last node that was created or navigated to. 34 | LastNode, 35 | /// A node that was created and stored with an id 36 | Node(NodeId), 37 | } 38 | 39 | impl MaybeId { 40 | #[inline(always)] 41 | pub(crate) const fn encoded_size(&self) -> u8 { 42 | match self { 43 | MaybeId::LastNode => 0, 44 | MaybeId::Node(_) => 4, 45 | } 46 | } 47 | } 48 | 49 | /// A node that was created and stored with an id 50 | /// It is recommended to create and store ids with a slab allocator with an exposed slab index for example the excellent [slab](https://docs.rs/slab) crate. 51 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 52 | pub struct NodeId(pub u32); 53 | 54 | /// Something that can be written as a utf-8 string to a buffer 55 | pub trait WritableText { 56 | fn write_as_text(self, to: &mut Vec); 57 | } 58 | 59 | impl WritableText for char { 60 | fn write_as_text(self, to: &mut Vec) { 61 | to.push(self as u8); 62 | } 63 | } 64 | 65 | impl<'a> WritableText for &'a str { 66 | #[inline(always)] 67 | fn write_as_text(self, to: &mut Vec) { 68 | let len = self.len(); 69 | to.reserve(len); 70 | let old_len = to.len(); 71 | #[allow(clippy::uninit_vec)] 72 | unsafe { 73 | let ptr = to.as_mut_ptr(); 74 | let bytes = self.as_bytes(); 75 | let str_ptr = bytes.as_ptr(); 76 | for o in 0..len { 77 | *ptr.add(old_len + o) = *str_ptr.add(o); 78 | } 79 | to.set_len(old_len + len); 80 | } 81 | // let _ = to.write(self.as_bytes()); 82 | } 83 | } 84 | 85 | impl WritableText for Arguments<'_> { 86 | fn write_as_text(self, to: &mut Vec) { 87 | let _ = to.write_fmt(self); 88 | } 89 | } 90 | 91 | impl WritableText for F 92 | where 93 | F: FnOnce(&mut Vec), 94 | { 95 | fn write_as_text(self, to: &mut Vec) { 96 | self(to); 97 | } 98 | } 99 | 100 | macro_rules! write_unsized { 101 | ($t: ty) => { 102 | impl WritableText for $t { 103 | fn write_as_text(self, to: &mut Vec) { 104 | let mut n = self; 105 | let mut n2 = n; 106 | let mut num_digits = 0; 107 | while n2 > 0 { 108 | n2 /= 10; 109 | num_digits += 1; 110 | } 111 | let len = num_digits; 112 | to.reserve(len); 113 | let ptr = to.as_mut_ptr().cast::(); 114 | let old_len = to.len(); 115 | let mut i = len - 1; 116 | loop { 117 | unsafe { ptr.add(old_len + i).write((n % 10) as u8 + b'0') } 118 | n /= 10; 119 | 120 | if n == 0 { 121 | break; 122 | } else { 123 | i -= 1; 124 | } 125 | } 126 | 127 | #[allow(clippy::uninit_vec)] 128 | unsafe { 129 | to.set_len(old_len + (len - i)); 130 | } 131 | } 132 | } 133 | }; 134 | } 135 | 136 | macro_rules! write_sized { 137 | ($t: ty) => { 138 | impl WritableText for $t { 139 | fn write_as_text(self, to: &mut Vec) { 140 | let neg = self < 0; 141 | let mut n = if neg { 142 | match self.checked_abs() { 143 | Some(n) => n, 144 | None => <$t>::MAX / 2 + 1, 145 | } 146 | } else { 147 | self 148 | }; 149 | let mut n2 = n; 150 | let mut num_digits = 0; 151 | while n2 > 0 { 152 | n2 /= 10; 153 | num_digits += 1; 154 | } 155 | let len = if neg { num_digits + 1 } else { num_digits }; 156 | to.reserve(len); 157 | let ptr = to.as_mut_ptr().cast::(); 158 | let old_len = to.len(); 159 | let mut i = len - 1; 160 | loop { 161 | unsafe { ptr.add(old_len + i).write((n % 10) as u8 + b'0') } 162 | n /= 10; 163 | 164 | if n == 0 { 165 | break; 166 | } else { 167 | i -= 1; 168 | } 169 | } 170 | 171 | if neg { 172 | i -= 1; 173 | unsafe { ptr.add(old_len + i).write(b'-') } 174 | } 175 | 176 | #[allow(clippy::uninit_vec)] 177 | unsafe { 178 | to.set_len(old_len + (len - i)); 179 | } 180 | } 181 | } 182 | }; 183 | } 184 | 185 | write_unsized!(u8); 186 | write_unsized!(u16); 187 | write_unsized!(u32); 188 | write_unsized!(u64); 189 | write_unsized!(u128); 190 | write_unsized!(usize); 191 | 192 | write_sized!(i8); 193 | write_sized!(i16); 194 | write_sized!(i32); 195 | write_sized!(i64); 196 | write_sized!(i128); 197 | write_sized!(isize); 198 | -------------------------------------------------------------------------------- /prebuild/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sledgehammer-prebuild" 3 | version = "0.2.0" 4 | authors = ["Evan Almloff "] 5 | edition = "2021" 6 | description = "Prebuild messages for Sledgehammer" 7 | documentation = "https://docs.rs/sledgehammer" 8 | readme = "README.md" 9 | repository = "https://github.com/demonthos/sledgehammer/" 10 | license = "MIT" 11 | keywords = [] 12 | categories = [] 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | proc-macro2 = "1.0.47" 19 | quote = "1.0.21" 20 | syn = "1.0.102" 21 | syn-rsx = "0.9.0" 22 | sledgehammer-encoder = { path = "../encoder" } 23 | bumpalo = "3.11.1" -------------------------------------------------------------------------------- /prebuild/examples/macro.rs: -------------------------------------------------------------------------------- 1 | use sledgehammer_encoder::*; 2 | use sledgehammer_prebuild::html; 3 | 4 | fn test() { 5 | const EL: StaticBatch = html! { 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /prebuild/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{any::Any, convert::TryFrom, str::FromStr, thread::Builder}; 2 | 3 | use bumpalo::Bump; 4 | use proc_macro::TokenStream; 5 | use quote::quote; 6 | use sledgehammer_encoder::{ 7 | attribute::AnyAttribute, 8 | batch::{Batch, FinalizedBatch}, 9 | element::AnyElement, 10 | Attribute, Element, ElementBuilder, NodeBuilder, NodeId, TextBuilder, 11 | }; 12 | use syn::{Expr, Lit}; 13 | use syn_rsx::{parse, Node, NodeType}; 14 | 15 | enum NodeInProgress { 16 | Element(ElementInProgress), 17 | Text(String), 18 | } 19 | 20 | struct ElementInProgress { 21 | kind: String, 22 | attributes: Vec<(String, String)>, 23 | children: Vec, 24 | } 25 | 26 | fn walk_nodes(nodes: &Vec, inside: &mut Option) { 27 | for node in nodes { 28 | match node { 29 | Node::Doctype(_) => {} 30 | Node::Element(element) => { 31 | let name = element.name.to_string(); 32 | 33 | let mut builder = Some(ElementInProgress { 34 | kind: name, 35 | attributes: Vec::new(), 36 | children: Vec::new(), 37 | }); 38 | 39 | // attributes 40 | walk_nodes(&element.attributes, &mut builder); 41 | 42 | // children 43 | walk_nodes(&element.children, &mut builder); 44 | 45 | match inside { 46 | Some(el) => el.children.push(NodeInProgress::Element(builder.unwrap())), 47 | None => *inside = builder, 48 | } 49 | } 50 | Node::Attribute(attribute) => { 51 | let key_str = attribute.key.to_string(); 52 | if let Some(el) = inside { 53 | if let Some(val) = &attribute.value { 54 | el.attributes.push((key_str, as_str_lit(&val))) 55 | } 56 | } 57 | } 58 | Node::Text(txt) => { 59 | if let Some(el) = inside { 60 | el.children 61 | .push(NodeInProgress::Text(as_str_lit(&txt.value))) 62 | } 63 | } 64 | Node::Fragment(_) => { 65 | panic!("fragments are not supported") 66 | } 67 | Node::Comment(_) => {} 68 | Node::Block(_) => { 69 | panic!("blocks are not supported") 70 | } 71 | } 72 | } 73 | } 74 | 75 | fn as_str_lit(expr: &Expr) -> String { 76 | if let Expr::Lit(u) = expr { 77 | if let Lit::Str(s) = &u.lit { 78 | return s.value(); 79 | } 80 | } 81 | panic!("expected string") 82 | } 83 | 84 | /// Converts HTML to `String`. 85 | /// 86 | /// Values returned from braced blocks `{}` are expected to return something 87 | /// that implements `Display`. 88 | /// 89 | /// See [syn-rsx docs](https://docs.rs/syn-rsx/) for supported tags and syntax. 90 | /// 91 | /// # Example 92 | /// 93 | /// ``` 94 | /// use html_to_string_macro::html; 95 | /// 96 | /// let world = "planet"; 97 | /// assert_eq!(html!(
"hello "{world}
), "
hello planet
"); 98 | /// ``` 99 | #[proc_macro] 100 | pub fn html(tokens: TokenStream) -> TokenStream { 101 | match parse(tokens) { 102 | Ok(nodes) => { 103 | let mut builder = None; 104 | walk_nodes(&nodes, &mut builder); 105 | match builder { 106 | Some(builder) => { 107 | let bump = Bump::new(); 108 | let builder = NodeInProgress::Element(builder); 109 | let builder = build_in_progress(&bump, &builder); 110 | let mut batch = Batch::default(); 111 | match builder { 112 | NodeBuilder::Text(txt) => batch.build_text_node(txt), 113 | NodeBuilder::Element(el) => { 114 | batch.build_full_element(el); 115 | } 116 | } 117 | let finalized = batch.finalize(); 118 | let msg = &finalized.msg; 119 | let str = &finalized.str; 120 | quote! { 121 | StaticBatch{ 122 | msg: &[#(#msg,)*], 123 | str: &[#(#str,)*] 124 | } 125 | } 126 | } 127 | None => { 128 | panic!("empty html call"); 129 | } 130 | } 131 | } 132 | Err(error) => error.to_compile_error(), 133 | } 134 | .into() 135 | } 136 | 137 | fn build_in_progress<'a>(allocator: &'a Bump, node: &'a NodeInProgress) -> NodeBuilder<'a> { 138 | match node { 139 | NodeInProgress::Element(el) => { 140 | let mut builder = ElementBuilder::new(match Element::from_str(&el.kind) { 141 | Ok(el) => AnyElement::Element(el), 142 | Err(_) => AnyElement::Str(&el.kind), 143 | }); 144 | let children: Vec<_> = el 145 | .children 146 | .iter() 147 | .map(|node| build_in_progress(allocator, node)) 148 | .collect(); 149 | builder = builder.children(allocator.alloc(children)); 150 | let mut id = None; 151 | let attributes: Vec<(AnyAttribute<'_, '_>, &str)> = el 152 | .attributes 153 | .iter() 154 | .filter_map(|(attr, value)| { 155 | if attr == "sledgehammer-id" { 156 | id = Some(value.parse().unwrap()); 157 | None 158 | } else { 159 | Some(( 160 | match Attribute::from_str(attr) { 161 | Ok(a) => AnyAttribute::Attribute(a), 162 | Err(_) => AnyAttribute::Str(attr), 163 | }, 164 | &*allocator.alloc_str(value), 165 | )) 166 | } 167 | }) 168 | .collect(); 169 | if let Some(id) = id { 170 | builder = builder.id(NodeId(id)); 171 | } 172 | builder = builder.attrs(allocator.alloc(attributes)); 173 | NodeBuilder::Element(builder) 174 | } 175 | NodeInProgress::Text(txt) => NodeBuilder::Text(TextBuilder::new(txt)), 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /web/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sledgehammer" 3 | version = "0.2.0" 4 | authors = ["Evan Almloff "] 5 | edition = "2021" 6 | description = "Fast bindings for dom manipulations" 7 | documentation = "https://docs.rs/sledgehammer" 8 | readme = "README.md" 9 | repository = "https://github.com/demonthos/sledgehammer/" 10 | license = "MIT" 11 | keywords = ["web", "wasm", "dom"] 12 | categories = ["web-programming", "wasm", "api-bindings"] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | wasm-bindgen = "0.2.83" 18 | web-sys = { version = "0.3.60", features = ["console", "Window", "Document", "Element", "HtmlElement", "HtmlHeadElement"] } 19 | js-sys = "0.3.60" 20 | sledgehammer-encoder = { path = "../encoder" } 21 | sledgehammer-prebuild = { path = "../prebuild" } 22 | -------------------------------------------------------------------------------- /web/examples/web_sys_comparison.rs: -------------------------------------------------------------------------------- 1 | use sledgehammer::{channel::MaybeId, *}; 2 | use wasm_bindgen::JsCast; 3 | 4 | fn main() { 5 | let window = web_sys::window().unwrap(); 6 | let document = window.document().unwrap(); 7 | let body = document.body().unwrap(); 8 | 9 | // create an element using web-sys 10 | let div = document.create_element("div").unwrap(); 11 | let web_sys_element = document.create_element("p").unwrap(); 12 | web_sys_element 13 | .set_attribute("style", "color: blue") 14 | .unwrap(); 15 | web_sys_element.set_text_content(Some("Hello from web-sys!")); 16 | let svg = document 17 | .create_element_ns(Some("http://www.w3.org/2000/svg"), "svg") 18 | .unwrap(); 19 | svg.set_attribute_ns(Some("http://www.w3.org/2000/svg"), "width", "100%") 20 | .unwrap(); 21 | div.append_child(&web_sys_element).unwrap(); 22 | div.append_child(&svg).unwrap(); 23 | 24 | // append the new node to the body 25 | body.append_child(&div).unwrap(); 26 | 27 | let mut channel = MsgChannel::default(); 28 | 29 | // assign the NodeId(0) to the body element from web-sys 30 | channel.set_node(NodeId(0), JsCast::dyn_into(body).unwrap()); 31 | 32 | // create an element using sledgehammer 33 | channel.build_full_element( 34 | ElementBuilder::new(Element::div.into()) 35 | .id(NodeId(1)) 36 | .children(&[ 37 | ElementBuilder::new(Element::p.into()) 38 | .id(NodeId(2)) 39 | .attrs(&[(Attribute::style.into(), "color: blue")]) 40 | .into(), 41 | ElementBuilder::new("svg".in_namespace("http://www.w3.org/2000/svg").into()) 42 | .attrs(&[( 43 | "width".in_namespace("http://www.w3.org/2000/svg").into(), 44 | "100%", 45 | )]) 46 | .into(), 47 | ]), 48 | ); 49 | 50 | channel.set_text("Hello from sledehammer!", MaybeId::Node(NodeId(2))); 51 | 52 | // append the new node to the body 53 | channel.append_child( 54 | MaybeId::Node(NodeId(0)), 55 | sledgehammer::channel::MaybeId::Node(NodeId(1)), 56 | ); 57 | 58 | // execute the queued operations 59 | channel.flush(); 60 | 61 | // we can also get web-sys nodes out of sledgehammer 62 | let element = channel.get_node(NodeId(2)); 63 | let text = element.text_content().map(|t| t + " + web-sys"); 64 | element.set_text_content(text.as_deref()); 65 | } 66 | -------------------------------------------------------------------------------- /web/interpreter.js: -------------------------------------------------------------------------------- 1 | let op, len, ns, attr, i, j, value, element, ptr, pos, end, out, char, numAttributes, endRounded, inptr, buffer, metadata, parent, numNodes, children, node, name, id, nodes; 2 | 3 | export function work_last_created() { 4 | inptr.Work(); 5 | } 6 | 7 | export function update_last_memory(mem) { 8 | inptr.UpdateMemory(mem); 9 | } 10 | 11 | function exOp() { 12 | // first bool: op & 0x20 13 | // second bool: op & 0x40 14 | 15 | switch (op & 0x1F) { 16 | // first child 17 | case 0: 18 | inptr.lastNode = inptr.lastNode.firstChild; 19 | break; 20 | // next sibling 21 | case 1: 22 | inptr.lastNode = inptr.lastNode.nextSibling; 23 | break; 24 | // parent 25 | case 2: 26 | inptr.lastNode = inptr.lastNode.parentNode; 27 | break; 28 | // store with id 29 | case 3: 30 | inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)] = inptr.lastNode; 31 | inptr.u8BufPos += 4; 32 | break; 33 | // set last node 34 | case 4: 35 | inptr.lastNode = inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]; 36 | inptr.u8BufPos += 4; 37 | break; 38 | // stop 39 | case 5: 40 | return true; 41 | // create full element 42 | case 6: 43 | inptr.lastNode = inptr.createFullElement(); 44 | break; 45 | // append children 46 | case 7: 47 | // the first bool is encoded as op & (1 << 5) 48 | if (op & 0x20) { 49 | parent = inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]; 50 | inptr.u8BufPos += 4; 51 | } 52 | else { 53 | parent = inptr.lastNode; 54 | } 55 | // the second bool is encoded as op & (1 << 6) 56 | if (op & 0x40) { 57 | parent.appendChild(inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]); 58 | inptr.u8BufPos += 4; 59 | } 60 | else { 61 | parent.appendChild(inptr.lastNode); 62 | } 63 | break; 64 | // replace with 65 | case 8: 66 | // the second bool is encoded as op & (1 << 6) 67 | if (op & 0x40) { 68 | parent = inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]; 69 | inptr.u8BufPos += 4; 70 | } 71 | else { 72 | parent = inptr.lastNode; 73 | } 74 | // the first bool is encoded as op & (1 << 5) 75 | if (op & 0x20) { 76 | numNodes = inptr.view.getUint8(inptr.u8BufPos++, true); 77 | nodes = []; 78 | for (i = 0; i < numNodes; i++) { 79 | if (inptr.view.getUint8(inptr.u8BufPos++, true)) { 80 | nodes.push(inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]); 81 | inptr.u8BufPos += 4; 82 | } 83 | else { 84 | nodes.push(inptr.lastNode); 85 | } 86 | } 87 | parent.replaceWith(...nodes); 88 | } 89 | else { 90 | // the third bool is encoded as op & (1 << 7) 91 | if (op & 0x80) { 92 | parent.replaceWith(inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]); 93 | inptr.u8BufPos += 4; 94 | } 95 | else { 96 | parent.replaceWith(inptr.lastNode); 97 | } 98 | } 99 | break; 100 | // insert after 101 | case 9: 102 | // the second bool is encoded as op & (1 << 6) 103 | if (op & 0x40) { 104 | parent = inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]; 105 | inptr.u8BufPos += 4; 106 | } 107 | else { 108 | parent = inptr.lastNode; 109 | } 110 | // the first bool is encoded as op & (1 << 5) 111 | if (op & 0x20) { 112 | numNodes = inptr.view.getUint8(inptr.u8BufPos++, true); 113 | nodes = []; 114 | for (i = 0; i < numNodes; i++) { 115 | if (inptr.view.getUint8(inptr.u8BufPos++, true)) { 116 | nodes.push(inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]); 117 | inptr.u8BufPos += 4; 118 | } 119 | else { 120 | nodes.push(inptr.lastNode); 121 | } 122 | } 123 | parent.after(...nodes); 124 | } else { 125 | // the third bool is encoded as op & (1 << 7) 126 | if (op & 0x80) { 127 | parent.after(inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]); 128 | inptr.u8BufPos += 4; 129 | } 130 | else { 131 | parent.after(inptr.lastNode); 132 | } 133 | } 134 | break; 135 | // insert before 136 | case 10: 137 | // the second bool is encoded as op & (1 << 6) 138 | if (op & 0x40) { 139 | parent = inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]; 140 | inptr.u8BufPos += 4; 141 | } 142 | else { 143 | parent = inptr.lastNode; 144 | } 145 | // the first bool is encoded as op & (1 << 5) 146 | if (op & 0x20) { 147 | numNodes = inptr.view.getUint8(inptr.u8BufPos++, true); 148 | nodes = []; 149 | for (i = 0; i < numNodes; i++) { 150 | if (inptr.view.getUint8(inptr.u8BufPos++, true)) { 151 | nodes.push(inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]); 152 | inptr.u8BufPos += 4; 153 | } 154 | else { 155 | nodes.push(inptr.lastNode); 156 | } 157 | } 158 | parent.before(...nodes); 159 | } else { 160 | // the third bool is encoded as op & (1 << 7) 161 | if (op & 0x80) { 162 | parent.before(inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]); 163 | inptr.u8BufPos += 4; 164 | } 165 | else { 166 | parent.before(inptr.lastNode); 167 | } 168 | } 169 | break; 170 | // remove 171 | case 11: 172 | // the first bool is encoded as op & (1 << 5) 173 | if (op & 0x20) { 174 | inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)].remove(); 175 | inptr.u8BufPos += 4; 176 | } 177 | else { 178 | inptr.lastNode.remove(); 179 | } 180 | break; 181 | // create text node 182 | case 12: 183 | inptr.lastNode = document.createTextNode(inptr.strings.substring(inptr.strPos, inptr.strPos += inptr.view.getUint16(inptr.u8BufPos, true))); 184 | inptr.u8BufPos += 2; 185 | // the first bool is encoded as op & (1 << 5) 186 | if (op & 0x20) { 187 | inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)] = inptr.lastNode; 188 | inptr.u8BufPos += 4; 189 | } 190 | break; 191 | // create element 192 | case 13: 193 | inptr.lastNode = inptr.createElement(); 194 | // the second bool is encoded as op & (1 << 6) 195 | if (op & 0x20) { 196 | inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)] = inptr.lastNode; 197 | inptr.u8BufPos += 4; 198 | } 199 | break; 200 | // set text 201 | case 14: 202 | // the first bool is encoded as op & (1 << 5) 203 | if (op & 0x20) { 204 | id = inptr.view.getUint32(inptr.u8BufPos, true); 205 | inptr.u8BufPos += 4; 206 | inptr.nodes[id].textContent = inptr.strings.substring(inptr.strPos, inptr.strPos += inptr.view.getUint16(inptr.u8BufPos, true)); 207 | inptr.u8BufPos += 2; 208 | } 209 | else { 210 | inptr.lastNode.textContent = inptr.strings.substring(inptr.strPos, inptr.strPos += inptr.view.getUint16(inptr.u8BufPos, true)); 211 | inptr.u8BufPos += 2; 212 | } 213 | break; 214 | // set attribute 215 | case 15: 216 | // the first bool is encoded as op & (1 << 5) 217 | if (op & 0x20) { 218 | node = inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]; 219 | inptr.u8BufPos += 4; 220 | } 221 | else { 222 | node = inptr.lastNode; 223 | } 224 | // the second bool is encoded as op & (1 << 6) 225 | // first bool encodes if the attribute is a string 226 | if (op & 0x40) { 227 | // the first two lengths 228 | i = inptr.view.getUint32(inptr.u8BufPos, true); 229 | inptr.u8BufPos += 4; 230 | attr = inptr.strings.substring(inptr.strPos, inptr.strPos += i & 0xFFFF); 231 | // the third bool is encoded as op & (1 << 7) 232 | // second bool encodes if the attribute has a namespace 233 | if (op & 0x80) { 234 | node.setAttributeNS(inptr.strings.substring(inptr.strPos, inptr.strPos += (i & 0xFFFF0000) >>> 16), attr, inptr.strings.substring(inptr.strPos, inptr.strPos += inptr.view.getUint16(inptr.u8BufPos, true))); 235 | inptr.u8BufPos += 2; 236 | } 237 | else { 238 | node.setAttribute(attr, inptr.strings.substring(inptr.strPos, inptr.strPos += (i & 0xFFFF0000) >>> 16)); 239 | } 240 | } else { 241 | // the first length and attribute id or the attribute id and the first length 242 | i = inptr.view.getUint32(inptr.u8BufPos, true); 243 | // we only read 3 bytes out of the 4 244 | inptr.u8BufPos += 3; 245 | // the third bool is encoded as op & (1 << 7) 246 | // second bool encodes if the attribute has a namespace 247 | if (op & 0x80) { 248 | ns = inptr.strings.substring(inptr.strPos, inptr.strPos += i & 0xFFFF); 249 | node.setAttributeNS(ns, attrs[(i & 0xFF0000) >>> 16], inptr.strings.substring(inptr.strPos, inptr.strPos += inptr.view.getUint16(inptr.u8BufPos, true))); 250 | inptr.u8BufPos += 2; 251 | } 252 | else { 253 | node.setAttribute(attrs[i & 0xFF], inptr.strings.substring(inptr.strPos, inptr.strPos += (i & 0xFFFF00) >>> 8)); 254 | } 255 | } 256 | break; 257 | // remove attribute 258 | case 16: 259 | // the first bool is encoded as op & (1 << 5) 260 | if (op & 0x20) { 261 | node = inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]; 262 | inptr.u8BufPos += 4; 263 | } 264 | else { 265 | node = inptr.lastNode; 266 | } 267 | // the second bool is encoded as op & (1 << 6) 268 | // second bool encodes if the attribute is a string 269 | if (op & 0x40) { 270 | // the third bool is encoded as op & (1 << 7) 271 | // second bool encodes if the attribute has a namespace 272 | if (op & 0x80) { 273 | i = inptr.view.getUint32(inptr.u8BufPos, true); 274 | inptr.u8BufPos += 4; 275 | attr = inptr.strings.substring(inptr.strPos, inptr.strPos += i & 0xFFFF); 276 | node.removeAttributeNS(inptr.strings.substring(inptr.strPos, inptr.strPos += (i & 0xFFFF0000) >>> 16), attr); 277 | } else { 278 | node.removeAttribute(inptr.strings.substring(inptr.strPos, inptr.strPos += inptr.view.getUint16(inptr.u8BufPos, true))); 279 | inptr.u8BufPos += 2; 280 | } 281 | } else { 282 | // the third bool is encoded as op & (1 << 7) 283 | // second bool encodes if the attribute has a namespace 284 | if (op & 0x80) { 285 | i = inptr.view.getUint32(inptr.u8BufPos, true); 286 | // we only read 3 bytes out of the 4 287 | inptr.u8BufPos += 3; 288 | attr = attrs[i & 0xFF]; 289 | node.removeAttributeNS(inptr.strings.substring(inptr.strPos, inptr.strPos += (i & 0xFFFF00) >>> 8), attr); 290 | } 291 | else { 292 | node.removeAttribute(attrs[inptr.view.getUint8(inptr.u8BufPos++)]); 293 | } 294 | } 295 | break; 296 | // set style 297 | case 17: 298 | // the first bool is encoded as op & (1 << 5) 299 | if (op & 0x20) { 300 | node = inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]; 301 | inptr.u8BufPos += 4; 302 | } 303 | else { 304 | node = inptr.lastNode; 305 | } 306 | i = inptr.view.getUint32(inptr.u8BufPos, true); 307 | inptr.u8BufPos += 4; 308 | node.style.setProperty(inptr.strings.substring(inptr.strPos, inptr.strPos += i & 0xFFFF), inptr.strings.substring(inptr.strPos, inptr.strPos += (i & 0xFFFF0000) >>> 16)); 309 | break; 310 | // remove style 311 | case 18: 312 | // the first bool is encoded as op & (1 << 5) 313 | if (op & 0x20) { 314 | node = inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)]; 315 | inptr.u8BufPos += 4; 316 | } 317 | else { 318 | node = inptr.lastNode; 319 | } 320 | node.style.removeProperty(inptr.strings.substring(inptr.strPos, inptr.strPos += inptr.view.getUint16(inptr.u8BufPos, true))); 321 | inptr.u8BufPos += 2; 322 | break; 323 | // clone node 324 | case 19: 325 | // the first bool is encoded as op & (1 << 5) 326 | if (op & 0x20) { 327 | inptr.lastNode = inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)].cloneNode(true); 328 | inptr.u8BufPos += 4; 329 | } 330 | else { 331 | inptr.lastNode = inptr.lastNode.cloneNode(true); 332 | } 333 | // the second bool is encoded as op & (1 << 6) 334 | if (op & 0x40) { 335 | inptr.nodes[inptr.view.getUint32(inptr.u8BufPos, true)] = inptr.lastNode; 336 | inptr.u8BufPos += 4; 337 | } 338 | break; 339 | default: 340 | break; 341 | } 342 | } 343 | 344 | export class JsInterpreter { 345 | constructor(mem, _metadata_ptr, _ptr_ptr, _str_ptr_ptr, _str_len_ptr) { 346 | this.lastNode; 347 | this.nodes = []; 348 | this.parents = []; 349 | this.UpdateMemory(mem); 350 | this.last_start_pos; 351 | this.last_str_start; 352 | this.metadata_ptr = _metadata_ptr; 353 | this.ptr_ptr = _ptr_ptr; 354 | this.str_ptr_ptr = _str_ptr_ptr; 355 | this.str_len_ptr = _str_len_ptr; 356 | this.strings = ""; 357 | this.strPos = 0; 358 | this.decoder = new TextDecoder(); 359 | this.idSize = 1; 360 | inptr = this; 361 | } 362 | 363 | NeedsMemory() { 364 | return this.view.buffer.byteLength === 0; 365 | } 366 | 367 | UpdateMemory(mem) { 368 | this.view = new DataView(mem.buffer); 369 | buffer = mem.buffer; 370 | } 371 | 372 | Work() { 373 | metadata = this.view.getUint8(this.metadata_ptr); 374 | if (metadata & 0x01) { 375 | this.last_start_pos = this.view.getUint32(this.ptr_ptr, true); 376 | } 377 | this.u8BufPos = this.last_start_pos; 378 | if (metadata & 0x04) { 379 | len = this.view.getUint32(this.str_len_ptr, true); 380 | if (metadata & 0x02) { 381 | this.last_str_start = this.view.getUint32(this.str_ptr_ptr, true); 382 | } 383 | // for small strings decoding them in javascript to avoid the overhead of native calls is faster 384 | // the fourth boolean contains information about whether the string is all ascii or utf8 and small 385 | if (metadata & 0x08) { 386 | pos = this.last_str_start; 387 | this.strings = ""; 388 | endRounded = pos + ((len / 4) | 0) * 4; 389 | while (pos < endRounded) { 390 | char = this.view.getUint32(pos); 391 | this.strings += String.fromCharCode(char >> 24, (char & 0x00FF0000) >> 16, (char & 0x0000FF00) >> 8, (char & 0x000000FF)); 392 | pos += 4; 393 | } 394 | switch (this.last_str_start + len - pos) { 395 | case 3: 396 | char = this.view.getUint32(pos); 397 | this.strings += String.fromCharCode(char >> 24, (char & 0x00FF0000) >> 16, (char & 0x0000FF00) >> 8); 398 | break; 399 | case 2: 400 | char = this.view.getUint16(pos); 401 | this.strings += String.fromCharCode(char >> 8, char & 0xFF); 402 | break; 403 | case 1: 404 | this.strings += String.fromCharCode(this.view.getUint8(pos)); 405 | break; 406 | case 0: 407 | break; 408 | } 409 | } 410 | else { 411 | this.strings = this.decoder.decode(new DataView(this.view.buffer, this.last_str_start, len)); 412 | } 413 | this.strPos = 0; 414 | } 415 | 416 | // this is faster than a while(true) loop 417 | for (; ;) { 418 | // op = this.view.getUint8(this.u8BufPos++); 419 | // if (this.exOp(op & 0x1F)) return; 420 | op = this.view.getUint32(this.u8BufPos, true); 421 | this.u8BufPos += 4; 422 | if (exOp()) return; 423 | op >>>= 8; 424 | if (exOp()) return; 425 | op >>>= 8; 426 | if (exOp()) return; 427 | op >>>= 8; 428 | if (exOp()) return; 429 | } 430 | } 431 | 432 | createElement() { 433 | j = this.view.getUint32(this.u8BufPos, true); 434 | element = j & 0xFF; 435 | switch (element) { 436 | case 255: 437 | // the element is encoded as an enum and the namespace is encoded as a string 438 | // we use all 4 bytes of i just read 439 | this.u8BufPos += 4; 440 | element = document.createElement(els[(j & 0xFF00) >>> 8], this.strings.substring(this.strPos, this.strPos += (j & 0xFFFF0000) >>> 16)); 441 | return element; 442 | case 254: 443 | // the element is encoded as a string 444 | // we use 3 bytes of i just read 445 | this.u8BufPos += 3; 446 | element = document.createElement(this.strings.substring(this.strPos, this.strPos += (j & 0xFFFF00) >>> 8)); 447 | return element; 448 | case 253: 449 | // the element and namespace are encoded as strings 450 | // we use 3 bytes of i just read 451 | this.u8BufPos += 3; 452 | element = this.strings.substring(this.strPos, this.strPos += (j & 0xFFFF00) >>> 8); 453 | element = document.createElementNS(this.strings.substring(this.strPos, this.strPos += this.view.getUint16(this.u8BufPos, true)), element); 454 | this.u8BufPos += 2; 455 | return element; 456 | default: 457 | this.u8BufPos++; 458 | // the element is encoded as an enum 459 | return document.createElement(els[element]); 460 | } 461 | } 462 | 463 | createFullElement() { 464 | let parent_id; 465 | j = this.view.getUint8(this.u8BufPos++); 466 | if (j & 0x1) { 467 | parent_id = this.view.getUint32(this.u8BufPos, true); 468 | this.u8BufPos += 4; 469 | } 470 | if (j & 0x2) { 471 | node = document.createTextNode(this.strings.substring(this.strPos, this.strPos += this.view.getUint16(this.u8BufPos, true))); 472 | this.u8BufPos += 2; 473 | if (parent_id !== null) { 474 | this.nodes[parent_id] = node; 475 | } 476 | return node; 477 | } 478 | else { 479 | const parent_element = this.createElement(); 480 | j = this.view.getUint16(this.u8BufPos, true); 481 | this.u8BufPos += 2; 482 | numAttributes = j & 0xFF; 483 | const numChildren = (j & 0xFF00) >>> 8; 484 | for (i = 0; i < numAttributes; i++) { 485 | j = this.view.getUint32(this.u8BufPos, true); 486 | attr = j & 0xFF; 487 | switch (attr) { 488 | case 255: 489 | // the attribute is encoded as an enum and the namespace is encoded as a string 490 | // we use all 4 bytes of j just read 491 | this.u8BufPos += 4; 492 | attr = attrs[this.view.getUint8((j & 0xFF00) >>> 8)]; 493 | parent_element.setAttributeNS(this.strings.substring(this.strPos, this.strPos += (j & 0xFFFF0000) >>> 16), attr); 494 | break; 495 | case 254: 496 | // the attribute is encoded as a string 497 | // move one byte forward to skip the byte for attr 498 | this.u8BufPos++; 499 | j = this.view.getUint32(this.u8BufPos, true); 500 | this.u8BufPos += 4; 501 | attr = this.strings.substring(this.strPos, this.strPos += j & 0xFFFF); 502 | parent_element.setAttribute(attr, this.strings.substring(this.strPos, this.strPos += (j & 0xFFFF0000) >>> 16)); 503 | break; 504 | case 253: 505 | // the attribute and namespace are encoded as strings 506 | // we use 3 bytes of j just read 507 | this.u8BufPos += 3; 508 | attr = this.strings.substring(this.strPos, this.strPos += (j & 0xFFFF00) >>> 8); 509 | j = this.view.getUint32(this.u8BufPos, true); 510 | this.u8BufPos += 4; 511 | ns = this.strings.substring(this.strPos, this.strPos += j & 0xFFFF); 512 | value = this.strings.substring(this.strPos, this.strPos += (j & 0xFFFF0000) >>> 16); 513 | parent_element.setAttributeNS(ns, attr, value); 514 | break; 515 | default: 516 | // we use 3 bytes of j just read 517 | this.u8BufPos += 3; 518 | parent_element.setAttribute(attrs[attr], this.strings.substring(this.strPos, this.strPos += (j & 0xFFFF00) >>> 8)); 519 | break; 520 | } 521 | } 522 | for (let w = 0; w < numChildren; w++) { 523 | parent_element.appendChild(this.createFullElement()); 524 | } 525 | if (parent_id !== null) { 526 | this.nodes[parent_id] = parent_element; 527 | } 528 | return parent_element; 529 | } 530 | } 531 | 532 | decodeU32() { 533 | this.u8BufPos += 4; 534 | return this.view.getUint32(this.u8BufPos - 4, true); 535 | } 536 | 537 | SetNode(id, node) { 538 | this.nodes[id] = node; 539 | } 540 | 541 | GetNode(id) { 542 | return this.nodes[id]; 543 | } 544 | } 545 | 546 | const els = [ 547 | "a", 548 | "abbr", 549 | "acronym", 550 | "address", 551 | "applet", 552 | "area", 553 | "article", 554 | "aside", 555 | "audio", 556 | "b", 557 | "base", 558 | "bdi", 559 | "bdo", 560 | "bgsound", 561 | "big", 562 | "blink", 563 | "blockquote", 564 | "body", 565 | "br", 566 | "button", 567 | "canvas", 568 | "caption", 569 | "center", 570 | "cite", 571 | "code", 572 | "col", 573 | "colgroup", 574 | "content", 575 | "data", 576 | "datalist", 577 | "dd", 578 | "del", 579 | "details", 580 | "dfn", 581 | "dialog", 582 | "dir", 583 | "div", 584 | "dl", 585 | "dt", 586 | "em", 587 | "embed", 588 | "fieldset", 589 | "figcaption", 590 | "figure", 591 | "font", 592 | "footer", 593 | "form", 594 | "frame", 595 | "frameset", 596 | "h1", 597 | "head", 598 | "header", 599 | "hgroup", 600 | "hr", 601 | "html", 602 | "i", 603 | "iframe", 604 | "image", 605 | "img", 606 | "input", 607 | "ins", 608 | "kbd", 609 | "keygen", 610 | "label", 611 | "legend", 612 | "li", 613 | "link", 614 | "main", 615 | "map", 616 | "mark", 617 | "marquee", 618 | "menu", 619 | "menuitem", 620 | "meta", 621 | "meter", 622 | "nav", 623 | "nobr", 624 | "noembed", 625 | "noframes", 626 | "noscript", 627 | "object", 628 | "ol", 629 | "optgroup", 630 | "option", 631 | "output", 632 | "p", 633 | "param", 634 | "picture", 635 | "plaintext", 636 | "portal", 637 | "pre", 638 | "progress", 639 | "q", 640 | "rb", 641 | "rp", 642 | "rt", 643 | "rtc", 644 | "ruby", 645 | "s", 646 | "samp", 647 | "script", 648 | "section", 649 | "select", 650 | "shadow", 651 | "slot", 652 | "small", 653 | "source", 654 | "spacer", 655 | "span", 656 | "strike", 657 | "strong", 658 | "style", 659 | "sub", 660 | "summary", 661 | "sup", 662 | "table", 663 | "tbody", 664 | "td", 665 | "template", 666 | "textarea", 667 | "tfoot", 668 | "th", 669 | "thead", 670 | "time", 671 | "title", 672 | "tr", 673 | "track", 674 | "tt", 675 | "u", 676 | "ul", 677 | "var", 678 | "video", 679 | "wbr", 680 | "xmp", 681 | ]; 682 | 683 | const attrs = [ 684 | "accept-charset", 685 | "accept", 686 | "accesskey", 687 | "action", 688 | "align", 689 | "allow", 690 | "alt", 691 | "aria-atomic", 692 | "aria-busy", 693 | "aria-controls", 694 | "aria-current", 695 | "aria-describedby", 696 | "aria-description", 697 | "aria-details", 698 | "aria-disabled", 699 | "aria-dropeffect", 700 | "aria-errormessage", 701 | "aria-flowto", 702 | "aria-grabbed", 703 | "aria-haspopup", 704 | "aria-hidden", 705 | "aria-invalid", 706 | "aria-keyshortcuts", 707 | "aria-label", 708 | "aria-labelledby", 709 | "aria-live", 710 | "aria-owns", 711 | "aria-relevant", 712 | "aria-roledescription", 713 | "async", 714 | "autocapitalize", 715 | "autocomplete", 716 | "autofocus", 717 | "autoplay", 718 | "background", 719 | "bgcolor", 720 | "border", 721 | "buffered", 722 | "capture", 723 | "challenge", 724 | "charset", 725 | "checked", 726 | "cite", 727 | "class", 728 | "code", 729 | "codebase", 730 | "color", 731 | "cols", 732 | "colspan", 733 | "content", 734 | "contenteditable", 735 | "contextmenu", 736 | "controls", 737 | "coords", 738 | "crossorigin", 739 | "csp", 740 | "data", 741 | "datetime", 742 | "decoding", 743 | "default", 744 | "defer", 745 | "dir", 746 | "dirname", 747 | "disabled", 748 | "download", 749 | "draggable", 750 | "enctype", 751 | "enterkeyhint", 752 | "for", 753 | "form", 754 | "formaction", 755 | "formenctype", 756 | "formmethod", 757 | "formnovalidate", 758 | "formtarget", 759 | "headers", 760 | "height", 761 | "hidden", 762 | "high", 763 | "href", 764 | "hreflang", 765 | "http-equiv", 766 | "icon", 767 | "id", 768 | "importance", 769 | "inputmode", 770 | "integrity", 771 | "intrinsicsize", 772 | "ismap", 773 | "itemprop", 774 | "keytype", 775 | "kind", 776 | "label", 777 | "lang", 778 | "language", 779 | "list", 780 | "loading", 781 | "loop", 782 | "low", 783 | "manifest", 784 | "max", 785 | "maxlength", 786 | "media", 787 | "method", 788 | "min", 789 | "minlength", 790 | "multiple", 791 | "muted", 792 | "name", 793 | "novalidate", 794 | "open", 795 | "optimum", 796 | "pattern", 797 | "ping", 798 | "placeholder", 799 | "poster", 800 | "preload", 801 | "radiogroup", 802 | "readonly", 803 | "referrerpolicy", 804 | "rel", 805 | "required", 806 | "reversed", 807 | "role", 808 | "rows", 809 | "rowspan", 810 | "sandbox", 811 | "scope", 812 | "scoped", 813 | "selected", 814 | "shape", 815 | "size", 816 | "sizes", 817 | "slot", 818 | "span", 819 | "spellcheck", 820 | "src", 821 | "srcdoc", 822 | "srclang", 823 | "srcset", 824 | "start", 825 | "step", 826 | "style", 827 | "summary", 828 | "tabindex", 829 | "target", 830 | "title", 831 | "translate", 832 | "type", 833 | "usemap", 834 | "value", 835 | "width", 836 | "wrap", 837 | ]; 838 | -------------------------------------------------------------------------------- /web/interpreter_manually_opt.js: -------------------------------------------------------------------------------- 1 | let op, len, ns, attr, i, j, value, element, pos, char, numAttributes, endRounded, inptr, metadata, parent, numNodes, node, id, nodes; 2 | 3 | export function work_last_created() { 4 | inptr.Work(); 5 | } 6 | 7 | export function update_last_memory(mem) { 8 | inptr.UpdateMemory(mem); 9 | } 10 | 11 | function exOp() { 12 | // first bool: op & 0x20 13 | // second bool: op & 0x40 14 | 15 | switch (op & 0x1F) { 16 | // first child 17 | case 0: 18 | inptr.l = inptr.l.firstChild; 19 | break; 20 | // next sibling 21 | case 1: 22 | inptr.l = inptr.l.nextSibling; 23 | break; 24 | // parent 25 | case 2: 26 | inptr.l = inptr.l.parentNode; 27 | break; 28 | // store with id 29 | case 3: 30 | inptr.n[inptr.v.u32(inptr.u, true)] = inptr.l; 31 | inptr.u += 4; 32 | break; 33 | // set last node 34 | case 4: 35 | inptr.l = inptr.n[inptr.v.u32(inptr.u, true)]; 36 | inptr.u += 4; 37 | break; 38 | // stop 39 | case 5: 40 | return true; 41 | // create full element 42 | case 6: 43 | inptr.l = inptr.createFullElement(); 44 | break; 45 | // append children 46 | case 7: 47 | // the first bool is encoded as op & (1 << 5) 48 | if (op & 0x20) { 49 | parent = inptr.n[inptr.v.u32(inptr.u, true)]; 50 | inptr.u += 4; 51 | } 52 | else { 53 | parent = inptr.l; 54 | } 55 | // the second bool is encoded as op & (1 << 6) 56 | if (op & 0x40) { 57 | parent.appendChild(inptr.n[inptr.v.u32(inptr.u, true)]); 58 | inptr.u += 4; 59 | } 60 | else { 61 | parent.appendChild(inptr.l); 62 | } 63 | break; 64 | // replace with 65 | case 8: 66 | // the second bool is encoded as op & (1 << 6) 67 | if (op & 0x40) { 68 | parent = inptr.n[inptr.v.u32(inptr.u, true)]; 69 | inptr.u += 4; 70 | } 71 | else { 72 | parent = inptr.l; 73 | } 74 | // the first bool is encoded as op & (1 << 5) 75 | if (op & 0x20) { 76 | numNodes = inptr.v.u8(inptr.u++, true); 77 | nodes = []; 78 | for (i = 0; i < numNodes; i++) { 79 | if (inptr.v.u8(inptr.u++, true)) { 80 | nodes.push(inptr.n[inptr.v.u32(inptr.u, true)]); 81 | inptr.u += 4; 82 | } 83 | else { 84 | nodes.push(inptr.l); 85 | } 86 | } 87 | parent.replaceWith(...n); 88 | } 89 | else { 90 | // the third bool is encoded as op & (1 << 7) 91 | if (op & 0x80) { 92 | parent.replaceWith(inptr.n[inptr.v.u32(inptr.u, true)]); 93 | inptr.u += 4; 94 | } 95 | else { 96 | parent.replaceWith(inptr.l); 97 | } 98 | } 99 | break; 100 | // insert after 101 | case 9: 102 | // the second bool is encoded as op & (1 << 6) 103 | if (op & 0x40) { 104 | parent = inptr.n[inptr.v.u32(inptr.u, true)]; 105 | inptr.u += 4; 106 | } 107 | else { 108 | parent = inptr.l; 109 | } 110 | // the first bool is encoded as op & (1 << 5) 111 | if (op & 0x20) { 112 | numNodes = inptr.v.u8(inptr.u++, true); 113 | nodes = []; 114 | for (i = 0; i < numNodes; i++) { 115 | if (inptr.v.u8(inptr.u++, true)) { 116 | nodes.push(inptr.n[inptr.v.u32(inptr.u, true)]); 117 | inptr.u += 4; 118 | } 119 | else { 120 | nodes.push(inptr.l); 121 | } 122 | } 123 | parent.after(...n); 124 | } else { 125 | // the third bool is encoded as op & (1 << 7) 126 | if (op & 0x80) { 127 | parent.after(inptr.n[inptr.v.u32(inptr.u, true)]); 128 | inptr.u += 4; 129 | } 130 | else { 131 | parent.after(inptr.l); 132 | } 133 | } 134 | break; 135 | // insert before 136 | case 10: 137 | // the second bool is encoded as op & (1 << 6) 138 | if (op & 0x40) { 139 | parent = inptr.n[inptr.v.u32(inptr.u, true)]; 140 | inptr.u += 4; 141 | } 142 | else { 143 | parent = inptr.l; 144 | } 145 | // the first bool is encoded as op & (1 << 5) 146 | if (op & 0x20) { 147 | numNodes = inptr.v.u8(inptr.u++, true); 148 | nodes = []; 149 | for (i = 0; i < numNodes; i++) { 150 | if (inptr.v.u8(inptr.u++, true)) { 151 | nodes.push(inptr.n[inptr.v.u32(inptr.u, true)]); 152 | inptr.u += 4; 153 | } 154 | else { 155 | nodes.push(inptr.l); 156 | } 157 | } 158 | parent.before(...n); 159 | } else { 160 | // the third bool is encoded as op & (1 << 7) 161 | if (op & 0x80) { 162 | parent.before(inptr.n[inptr.v.u32(inptr.u, true)]); 163 | inptr.u += 4; 164 | } 165 | else { 166 | parent.before(inptr.l); 167 | } 168 | } 169 | break; 170 | // remove 171 | case 11: 172 | // the first bool is encoded as op & (1 << 5) 173 | if (op & 0x20) { 174 | inptr.n[inptr.v.u32(inptr.u, true)].remove(); 175 | inptr.u += 4; 176 | } 177 | else { 178 | inptr.l.remove(); 179 | } 180 | break; 181 | // create text node 182 | case 12: 183 | inptr.l = document.createTextNode(inptr.s.substring(inptr.o, inptr.o += inptr.v.u16(inptr.u, true))); 184 | inptr.u += 2; 185 | // the first bool is encoded as op & (1 << 5) 186 | if (op & 0x20) { 187 | inptr.n[inptr.v.u32(inptr.u, true)] = inptr.l; 188 | inptr.u += 4; 189 | } 190 | break; 191 | // create element 192 | case 13: 193 | inptr.l = inptr.createElement(); 194 | // the second bool is encoded as op & (1 << 6) 195 | if (op & 0x20) { 196 | inptr.n[inptr.v.u32(inptr.u, true)] = inptr.l; 197 | inptr.u += 4; 198 | } 199 | break; 200 | // set text 201 | case 14: 202 | // the first bool is encoded as op & (1 << 5) 203 | if (op & 0x20) { 204 | id = inptr.v.u32(inptr.u, true); 205 | inptr.u += 4; 206 | inptr.n[id].textContent = inptr.s.substring(inptr.o, inptr.o += inptr.v.u16(inptr.u, true)); 207 | inptr.u += 2; 208 | } 209 | else { 210 | inptr.l.textContent = inptr.s.substring(inptr.o, inptr.o += inptr.v.u16(inptr.u, true)); 211 | inptr.u += 2; 212 | } 213 | break; 214 | // set attribute 215 | case 15: 216 | // the first bool is encoded as op & (1 << 5) 217 | if (op & 0x20) { 218 | node = inptr.n[inptr.v.u32(inptr.u, true)]; 219 | inptr.u += 4; 220 | } 221 | else { 222 | node = inptr.l; 223 | } 224 | // the second bool is encoded as op & (1 << 6) 225 | // first bool encodes if the attribute is a string 226 | if (op & 0x40) { 227 | // the first two lengths 228 | i = inptr.v.u32(inptr.u, true); 229 | inptr.u += 4; 230 | attr = inptr.s.substring(inptr.o, inptr.o += i & 0xFFFF); 231 | // the third bool is encoded as op & (1 << 7) 232 | // second bool encodes if the attribute has a namespace 233 | if (op & 0x80) { 234 | node.setAttributeNS(inptr.s.substring(inptr.o, inptr.o += (i & 0xFFFF0000) >>> 16), attr, inptr.s.substring(inptr.o, inptr.o += inptr.v.u16(inptr.u, true))); 235 | inptr.u += 2; 236 | } 237 | else { 238 | node.setAttribute(attr, inptr.s.substring(inptr.o, inptr.o += (i & 0xFFFF0000) >>> 16)); 239 | } 240 | } else { 241 | // the first length and attribute id or the attribute id and the first length 242 | i = inptr.v.u32(inptr.u, true); 243 | // we only read 3 bytes out of the 4 244 | inptr.u += 3; 245 | // the third bool is encoded as op & (1 << 7) 246 | // second bool encodes if the attribute has a namespace 247 | if (op & 0x80) { 248 | ns = inptr.s.substring(inptr.o, inptr.o += i & 0xFFFF); 249 | node.setAttributeNS(ns, attrs[(i & 0xFF0000) >>> 16], inptr.s.substring(inptr.o, inptr.o += inptr.v.u16(inptr.u, true))); 250 | inptr.u += 2; 251 | } 252 | else { 253 | node.setAttribute(attrs[i & 0xFF], inptr.s.substring(inptr.o, inptr.o += (i & 0xFFFF00) >>> 8)); 254 | } 255 | } 256 | break; 257 | // remove attribute 258 | case 16: 259 | // the first bool is encoded as op & (1 << 5) 260 | if (op & 0x20) { 261 | node = inptr.n[inptr.v.u32(inptr.u, true)]; 262 | inptr.u += 4; 263 | } 264 | else { 265 | node = inptr.l; 266 | } 267 | // the second bool is encoded as op & (1 << 6) 268 | // second bool encodes if the attribute is a string 269 | if (op & 0x40) { 270 | // the third bool is encoded as op & (1 << 7) 271 | // second bool encodes if the attribute has a namespace 272 | if (op & 0x80) { 273 | i = inptr.v.u32(inptr.u, true); 274 | inptr.u += 4; 275 | attr = inptr.s.substring(inptr.o, inptr.o += i & 0xFFFF); 276 | node.removeAttributeNS(inptr.s.substring(inptr.o, inptr.o += (i & 0xFFFF0000) >>> 16), attr); 277 | } else { 278 | node.removeAttribute(inptr.s.substring(inptr.o, inptr.o += inptr.v.u16(inptr.u, true))); 279 | inptr.u += 2; 280 | } 281 | } else { 282 | // the third bool is encoded as op & (1 << 7) 283 | // second bool encodes if the attribute has a namespace 284 | if (op & 0x80) { 285 | i = inptr.v.u32(inptr.u, true); 286 | // we only read 3 bytes out of the 4 287 | inptr.u += 3; 288 | attr = attrs[i & 0xFF]; 289 | node.removeAttributeNS(inptr.s.substring(inptr.o, inptr.o += (i & 0xFFFF00) >>> 8), attr); 290 | } 291 | else { 292 | node.removeAttribute(attrs[inptr.v.u8(inptr.u++)]); 293 | } 294 | } 295 | break; 296 | // set style 297 | case 17: 298 | // the first bool is encoded as op & (1 << 5) 299 | if (op & 0x20) { 300 | node = inptr.n[inptr.v.u32(inptr.u, true)]; 301 | inptr.u += 4; 302 | } 303 | else { 304 | node = inptr.l; 305 | } 306 | i = inptr.v.u32(inptr.u, true); 307 | inptr.u += 4; 308 | node.style.setProperty(inptr.s.substring(inptr.o, inptr.o += i & 0xFFFF), inptr.s.substring(inptr.o, inptr.o += (i & 0xFFFF0000) >>> 16)); 309 | break; 310 | // remove style 311 | case 18: 312 | // the first bool is encoded as op & (1 << 5) 313 | if (op & 0x20) { 314 | node = inptr.n[inptr.v.u32(inptr.u, true)]; 315 | inptr.u += 4; 316 | } 317 | else { 318 | node = inptr.l; 319 | } 320 | node.style.removeProperty(inptr.s.substring(inptr.o, inptr.o += inptr.v.u16(inptr.u, true))); 321 | inptr.u += 2; 322 | break; 323 | // clone node 324 | case 19: 325 | // the first bool is encoded as op & (1 << 5) 326 | if (op & 0x20) { 327 | inptr.l = inptr.n[inptr.v.u32(inptr.u, true)].cloneNode(true); 328 | inptr.u += 4; 329 | } 330 | else { 331 | inptr.l = inptr.l.cloneNode(true); 332 | } 333 | // the second bool is encoded as op & (1 << 6) 334 | if (op & 0x40) { 335 | inptr.n[inptr.v.u32(inptr.u, true)] = inptr.l; 336 | inptr.u += 4; 337 | } 338 | break; 339 | default: 340 | break; 341 | } 342 | } 343 | 344 | export class JsInterpreter { 345 | constructor(mem, _metadata_ptr, _ptr_ptr, _str_ptr_ptr, _str_len_ptr) { 346 | this.l; 347 | this.n = []; 348 | this.p = []; 349 | this.UpdateMemory(mem); 350 | this.lp; 351 | this.ls; 352 | this.m = _metadata_ptr; 353 | this.pt = _ptr_ptr; 354 | this.sp = _str_ptr_ptr; 355 | this.sl = _str_len_ptr; 356 | this.s = ""; 357 | this.o = 0; 358 | this.d = new TextDecoder(); 359 | this.i = 1; 360 | inptr = this; 361 | } 362 | 363 | NeedsMemory() { 364 | return this.v.buffer.byteLength === 0; 365 | } 366 | 367 | UpdateMemory(mem) { 368 | this.v = new DataView(mem.buffer); 369 | this.v.u32 = this.v.getUint32; 370 | this.v.u16 = this.v.getUint16; 371 | this.v.u8 = this.v.getUint8; 372 | } 373 | 374 | Work() { 375 | metadata = this.v.u8(this.m); 376 | if (metadata & 0x01) { 377 | this.lp = this.v.u32(this.pt, true); 378 | } 379 | this.u = this.lp; 380 | if (metadata & 0x04) { 381 | len = this.v.u32(this.sl, true); 382 | if (metadata & 0x02) { 383 | this.ls = this.v.u32(this.sp, true); 384 | } 385 | // for small strings decoding them in javascript to avoid the overhead of native calls is faster 386 | // the fourth boolean contains information about whether the string is all ascii or utf8 and small 387 | if (metadata & 0x08) { 388 | pos = this.ls; 389 | this.s = ""; 390 | endRounded = pos + ((len / 4) | 0) * 4; 391 | while (pos < endRounded) { 392 | char = this.v.u32(pos); 393 | this.s += String.fromCharCode(char >> 24, (char & 0x00FF0000) >> 16, (char & 0x0000FF00) >> 8, (char & 0x000000FF)); 394 | pos += 4; 395 | } 396 | switch (this.ls + len - pos) { 397 | case 3: 398 | char = this.v.u32(pos); 399 | this.s += String.fromCharCode(char >> 24, (char & 0x00FF0000) >> 16, (char & 0x0000FF00) >> 8); 400 | break; 401 | case 2: 402 | char = this.v.u16(pos); 403 | this.s += String.fromCharCode(char >> 8, char & 0xFF); 404 | break; 405 | case 1: 406 | this.s += String.fromCharCode(this.v.u8(pos)); 407 | break; 408 | case 0: 409 | break; 410 | } 411 | } 412 | else { 413 | this.s = this.d.decode(new DataView(this.v.buffer, this.ls, len)); 414 | } 415 | this.o = 0; 416 | } 417 | 418 | // this is faster than a while(true) loop 419 | for (; ;) { 420 | // op = this.v.u8(this.u++); 421 | // if (this.exOp(op & 0x1F)) return; 422 | op = this.v.u32(this.u, true); 423 | this.u += 4; 424 | if (exOp()) return; 425 | op >>>= 8; 426 | if (exOp()) return; 427 | op >>>= 8; 428 | if (exOp()) return; 429 | op >>>= 8; 430 | if (exOp()) return; 431 | } 432 | } 433 | 434 | createElement() { 435 | j = this.v.u32(this.u, true); 436 | element = j & 0xFF; 437 | switch (element) { 438 | case 255: 439 | // the element is encoded as an enum and the namespace is encoded as a string 440 | // we use all 4 bytes of i just read 441 | this.u += 4; 442 | element = document.createElement(els[(j & 0xFF00) >>> 8], this.s.substring(this.o, this.o += (j & 0xFFFF0000) >>> 16)); 443 | return element; 444 | case 254: 445 | // the element is encoded as a string 446 | // we use 3 bytes of i just read 447 | this.u += 3; 448 | element = document.createElement(this.s.substring(this.o, this.o += (j & 0xFFFF00) >>> 8)); 449 | return element; 450 | case 253: 451 | // the element and namespace are encoded as strings 452 | // we use 3 bytes of i just read 453 | this.u += 3; 454 | element = this.s.substring(this.o, this.o += (j & 0xFFFF00) >>> 8); 455 | element = document.createElementNS(this.s.substring(this.o, this.o += this.v.u16(this.u, true)), element); 456 | this.u += 2; 457 | return element; 458 | default: 459 | this.u++; 460 | // the element is encoded as an enum 461 | return document.createElement(els[element]); 462 | } 463 | } 464 | 465 | createFullElement() { 466 | let parent_id; 467 | j = this.v.u8(this.u++); 468 | if (j & 0x1) { 469 | parent_id = this.v.u32(this.u, true); 470 | this.u += 4; 471 | } 472 | if (j & 0x2) { 473 | node = document.createTextNode(this.s.substring(this.o, this.o += this.v.u16(this.u, true))); 474 | this.u += 2; 475 | if (parent_id !== null) { 476 | this.n[parent_id] = node; 477 | } 478 | return node; 479 | } 480 | else { 481 | const parent_element = this.createElement(); 482 | j = this.v.u16(this.u, true); 483 | this.u += 2; 484 | numAttributes = j & 0xFF; 485 | const numChildren = (j & 0xFF00) >>> 8; 486 | for (i = 0; i < numAttributes; i++) { 487 | j = this.v.u32(this.u, true); 488 | attr = j & 0xFF; 489 | switch (attr) { 490 | case 255: 491 | // the attribute is encoded as an enum and the namespace is encoded as a string 492 | // we use all 4 bytes of j just read 493 | this.u += 4; 494 | attr = attrs[this.v.u8((j & 0xFF00) >>> 8)]; 495 | parent_element.setAttributeNS(this.s.substring(this.o, this.o += (j & 0xFFFF0000) >>> 16), attr); 496 | break; 497 | case 254: 498 | // the attribute is encoded as a string 499 | // move one byte forward to skip the byte for attr 500 | this.u++; 501 | j = this.v.u32(this.u, true); 502 | this.u += 4; 503 | attr = this.s.substring(this.o, this.o += j & 0xFFFF); 504 | parent_element.setAttribute(attr, this.s.substring(this.o, this.o += (j & 0xFFFF0000) >>> 16)); 505 | break; 506 | case 253: 507 | // the attribute and namespace are encoded as strings 508 | // we use 3 bytes of j just read 509 | this.u += 3; 510 | attr = this.s.substring(this.o, this.o += (j & 0xFFFF00) >>> 8); 511 | j = this.v.u32(this.u, true); 512 | this.u += 4; 513 | ns = this.s.substring(this.o, this.o += j & 0xFFFF); 514 | value = this.s.substring(this.o, this.o += (j & 0xFFFF0000) >>> 16); 515 | parent_element.setAttributeNS(ns, attr, value); 516 | break; 517 | default: 518 | // we use 3 bytes of j just read 519 | this.u += 3; 520 | parent_element.setAttribute(attrs[attr], this.s.substring(this.o, this.o += (j & 0xFFFF00) >>> 8)); 521 | break; 522 | } 523 | } 524 | for (let w = 0; w < numChildren; w++) { 525 | parent_element.appendChild(this.createFullElement()); 526 | } 527 | if (parent_id !== null) { 528 | this.n[parent_id] = parent_element; 529 | } 530 | return parent_element; 531 | } 532 | } 533 | 534 | decodeU32() { 535 | this.u += 4; 536 | return this.v.u32(this.u - 4, true); 537 | } 538 | 539 | SetNode(id, node) { 540 | this.n[id] = node; 541 | } 542 | 543 | GetNode(id) { 544 | return this.n[id]; 545 | } 546 | } 547 | 548 | const els = [ 549 | "a", 550 | "abbr", 551 | "acronym", 552 | "address", 553 | "applet", 554 | "area", 555 | "article", 556 | "aside", 557 | "audio", 558 | "b", 559 | "base", 560 | "bdi", 561 | "bdo", 562 | "bgsound", 563 | "big", 564 | "blink", 565 | "blockquote", 566 | "body", 567 | "br", 568 | "button", 569 | "canvas", 570 | "caption", 571 | "center", 572 | "cite", 573 | "code", 574 | "col", 575 | "colgroup", 576 | "content", 577 | "data", 578 | "datalist", 579 | "dd", 580 | "del", 581 | "details", 582 | "dfn", 583 | "dialog", 584 | "dir", 585 | "div", 586 | "dl", 587 | "dt", 588 | "em", 589 | "embed", 590 | "fieldset", 591 | "figcaption", 592 | "figure", 593 | "font", 594 | "footer", 595 | "form", 596 | "frame", 597 | "frameset", 598 | "h1", 599 | "head", 600 | "header", 601 | "hgroup", 602 | "hr", 603 | "html", 604 | "i", 605 | "iframe", 606 | "image", 607 | "img", 608 | "input", 609 | "ins", 610 | "kbd", 611 | "keygen", 612 | "label", 613 | "legend", 614 | "li", 615 | "link", 616 | "main", 617 | "map", 618 | "mark", 619 | "marquee", 620 | "menu", 621 | "menuitem", 622 | "meta", 623 | "meter", 624 | "nav", 625 | "nobr", 626 | "noembed", 627 | "noframes", 628 | "noscript", 629 | "object", 630 | "ol", 631 | "optgroup", 632 | "option", 633 | "output", 634 | "p", 635 | "param", 636 | "picture", 637 | "plaintext", 638 | "portal", 639 | "pre", 640 | "progress", 641 | "q", 642 | "rb", 643 | "rp", 644 | "rt", 645 | "rtc", 646 | "ruby", 647 | "s", 648 | "samp", 649 | "script", 650 | "section", 651 | "select", 652 | "shadow", 653 | "slot", 654 | "small", 655 | "source", 656 | "spacer", 657 | "span", 658 | "strike", 659 | "strong", 660 | "style", 661 | "sub", 662 | "summary", 663 | "sup", 664 | "table", 665 | "tbody", 666 | "td", 667 | "template", 668 | "textarea", 669 | "tfoot", 670 | "th", 671 | "thead", 672 | "time", 673 | "title", 674 | "tr", 675 | "track", 676 | "tt", 677 | "u", 678 | "ul", 679 | "var", 680 | "video", 681 | "wbr", 682 | "xmp", 683 | ]; 684 | 685 | const attrs = [ 686 | "accept-charset", 687 | "accept", 688 | "accesskey", 689 | "action", 690 | "align", 691 | "allow", 692 | "alt", 693 | "aria-atomic", 694 | "aria-busy", 695 | "aria-controls", 696 | "aria-current", 697 | "aria-describedby", 698 | "aria-description", 699 | "aria-details", 700 | "aria-disabled", 701 | "aria-dropeffect", 702 | "aria-errormessage", 703 | "aria-flowto", 704 | "aria-grabbed", 705 | "aria-haspopup", 706 | "aria-hidden", 707 | "aria-invalid", 708 | "aria-keyshortcuts", 709 | "aria-label", 710 | "aria-labelledby", 711 | "aria-live", 712 | "aria-owns", 713 | "aria-relevant", 714 | "aria-roledescription", 715 | "async", 716 | "autocapitalize", 717 | "autocomplete", 718 | "autofocus", 719 | "autoplay", 720 | "background", 721 | "bgcolor", 722 | "border", 723 | "buffered", 724 | "capture", 725 | "challenge", 726 | "charset", 727 | "checked", 728 | "cite", 729 | "class", 730 | "code", 731 | "codebase", 732 | "color", 733 | "cols", 734 | "colspan", 735 | "content", 736 | "contenteditable", 737 | "contextmenu", 738 | "controls", 739 | "coords", 740 | "crossorigin", 741 | "csp", 742 | "data", 743 | "datetime", 744 | "decoding", 745 | "default", 746 | "defer", 747 | "dir", 748 | "dirname", 749 | "disabled", 750 | "download", 751 | "draggable", 752 | "enctype", 753 | "enterkeyhint", 754 | "for", 755 | "form", 756 | "formaction", 757 | "formenctype", 758 | "formmethod", 759 | "formnovalidate", 760 | "formtarget", 761 | "headers", 762 | "height", 763 | "hidden", 764 | "high", 765 | "href", 766 | "hreflang", 767 | "http-equiv", 768 | "icon", 769 | "id", 770 | "importance", 771 | "inputmode", 772 | "integrity", 773 | "intrinsicsize", 774 | "ismap", 775 | "itemprop", 776 | "keytype", 777 | "kind", 778 | "label", 779 | "lang", 780 | "language", 781 | "list", 782 | "loading", 783 | "loop", 784 | "low", 785 | "manifest", 786 | "max", 787 | "maxlength", 788 | "media", 789 | "method", 790 | "min", 791 | "minlength", 792 | "multiple", 793 | "muted", 794 | "name", 795 | "novalidate", 796 | "open", 797 | "optimum", 798 | "pattern", 799 | "ping", 800 | "placeholder", 801 | "poster", 802 | "preload", 803 | "radiogroup", 804 | "readonly", 805 | "referrerpolicy", 806 | "rel", 807 | "required", 808 | "reversed", 809 | "role", 810 | "rows", 811 | "rowspan", 812 | "sandbox", 813 | "scope", 814 | "scoped", 815 | "selected", 816 | "shape", 817 | "size", 818 | "sizes", 819 | "slot", 820 | "span", 821 | "spellcheck", 822 | "src", 823 | "srcdoc", 824 | "srclang", 825 | "srcset", 826 | "start", 827 | "step", 828 | "style", 829 | "summary", 830 | "tabindex", 831 | "target", 832 | "title", 833 | "translate", 834 | "type", 835 | "usemap", 836 | "value", 837 | "width", 838 | "wrap", 839 | ]; 840 | -------------------------------------------------------------------------------- /web/interpreter_opt.js: -------------------------------------------------------------------------------- 1 | let e,t,s,r,i,u,a,o,l,h,c,d,b,p,f,m,v,g,k;export function work_last_created(){b.Work()}export function update_last_memory(e){b.UpdateMemory(e)}function y(){switch(e&31){case 0:b.l=b.l.firstChild;break;case 1:b.l=b.l.nextSibling;break;case 2:b.l=b.l.parentNode;break;case 3:b.n[b.v.u32(b.u,true)]=b.l;b.u+=4;break;case 4:b.l=b.n[b.v.u32(b.u,true)];b.u+=4;break;case 5:return true;case 6:b.l=b.createFullElement();break;case 7:if(e&32){f=b.n[b.v.u32(b.u,true)];b.u+=4}else{f=b.l}if(e&64){f.appendChild(b.n[b.v.u32(b.u,true)]);b.u+=4}else{f.appendChild(b.l)}break;case 8:if(e&64){f=b.n[b.v.u32(b.u,true)];b.u+=4}else{f=b.l}if(e&32){m=b.v.u8(b.u++,true);k=[];for(i=0;i>>16),r,b.s.substring(b.o,b.o+=b.v.u16(b.u,true)));b.u+=2}else{v.setAttribute(r,b.s.substring(b.o,b.o+=(i&4294901760)>>>16))}}else{i=b.v.u32(b.u,true);b.u+=3;if(e&128){s=b.s.substring(b.o,b.o+=i&65535);v.setAttributeNS(s,x[(i&16711680)>>>16],b.s.substring(b.o,b.o+=b.v.u16(b.u,true)));b.u+=2}else{v.setAttribute(x[i&255],b.s.substring(b.o,b.o+=(i&16776960)>>>8))}}break;case 16:if(e&32){v=b.n[b.v.u32(b.u,true)];b.u+=4}else{v=b.l}if(e&64){if(e&128){i=b.v.u32(b.u,true);b.u+=4;r=b.s.substring(b.o,b.o+=i&65535);v.removeAttributeNS(b.s.substring(b.o,b.o+=(i&4294901760)>>>16),r)}else{v.removeAttribute(b.s.substring(b.o,b.o+=b.v.u16(b.u,true)));b.u+=2}}else{if(e&128){i=b.v.u32(b.u,true);b.u+=3;r=x[i&255];v.removeAttributeNS(b.s.substring(b.o,b.o+=(i&16776960)>>>8),r)}else{v.removeAttribute(x[b.v.u8(b.u++)])}}break;case 17:if(e&32){v=b.n[b.v.u32(b.u,true)];b.u+=4}else{v=b.l}i=b.v.u32(b.u,true);b.u+=4;v.style.setProperty(b.s.substring(b.o,b.o+=i&65535),b.s.substring(b.o,b.o+=(i&4294901760)>>>16));break;case 18:if(e&32){v=b.n[b.v.u32(b.u,true)];b.u+=4}else{v=b.l}v.style.removeProperty(b.s.substring(b.o,b.o+=b.v.u16(b.u,true)));b.u+=2;break;case 19:if(e&32){b.l=b.n[b.v.u32(b.u,true)].cloneNode(true);b.u+=4}else{b.l=b.l.cloneNode(true)}if(e&64){b.n[b.v.u32(b.u,true)]=b.l;b.u+=4}break;default:break}}export class JsInterpreter{constructor(e,t,s,r,i){this.l;this.n=[];this.p=[];this.UpdateMemory(e);this.lp;this.ls;this.m=t;this.pt=s;this.sp=r;this.sl=i;this.s="";this.o=0;this.d=new TextDecoder;this.i=1;b=this}NeedsMemory(){return this.v.buffer.byteLength===0}UpdateMemory(e){this.v=new DataView(e.buffer);this.v.u32=this.v.getUint32;this.v.u16=this.v.getUint16;this.v.u8=this.v.getUint8}Work(){p=this.v.u8(this.m);if(p&1){this.lp=this.v.u32(this.pt,true)}this.u=this.lp;if(p&4){t=this.v.u32(this.sl,true);if(p&2){this.ls=this.v.u32(this.sp,true)}if(p&8){l=this.ls;this.s="";d=l+(t/4|0)*4;while(l>24,(h&16711680)>>16,(h&65280)>>8,h&255);l+=4}switch(this.ls+t-l){case 3:h=this.v.u32(l);this.s+=String.fromCharCode(h>>24,(h&16711680)>>16,(h&65280)>>8);break;case 2:h=this.v.u16(l);this.s+=String.fromCharCode(h>>8,h&255);break;case 1:this.s+=String.fromCharCode(this.v.u8(l));break;case 0:break}}else{this.s=this.d.decode(new DataView(this.v.buffer,this.ls,t))}this.o=0}for(;;){e=this.v.u32(this.u,true);this.u+=4;if(y())return;e>>>=8;if(y())return;e>>>=8;if(y())return;e>>>=8;if(y())return}}createElement(){u=this.v.u32(this.u,true);o=u&255;switch(o){case 255:this.u+=4;o=document.createElement(w[(u&65280)>>>8],this.s.substring(this.o,this.o+=(u&4294901760)>>>16));return o;case 254:this.u+=3;o=document.createElement(this.s.substring(this.o,this.o+=(u&16776960)>>>8));return o;case 253:this.u+=3;o=this.s.substring(this.o,this.o+=(u&16776960)>>>8);o=document.createElementNS(this.s.substring(this.o,this.o+=this.v.u16(this.u,true)),o);this.u+=2;return o;default:this.u++;return document.createElement(w[o])}}createFullElement(){let e;u=this.v.u8(this.u++);if(u&1){e=this.v.u32(this.u,true);this.u+=4}if(u&2){v=document.createTextNode(this.s.substring(this.o,this.o+=this.v.u16(this.u,true)));this.u+=2;if(e!==null){this.n[e]=v}return v}else{const t=this.createElement();u=this.v.u16(this.u,true);this.u+=2;c=u&255;const o=(u&65280)>>>8;for(i=0;i>>8)];t.setAttributeNS(this.s.substring(this.o,this.o+=(u&4294901760)>>>16),r);break;case 254:this.u++;u=this.v.u32(this.u,true);this.u+=4;r=this.s.substring(this.o,this.o+=u&65535);t.setAttribute(r,this.s.substring(this.o,this.o+=(u&4294901760)>>>16));break;case 253:this.u+=3;r=this.s.substring(this.o,this.o+=(u&16776960)>>>8);u=this.v.u32(this.u,true);this.u+=4;s=this.s.substring(this.o,this.o+=u&65535);a=this.s.substring(this.o,this.o+=(u&4294901760)>>>16);t.setAttributeNS(s,r,a);break;default:this.u+=3;t.setAttribute(x[r],this.s.substring(this.o,this.o+=(u&16776960)>>>8));break}}for(let l=0;l Self { 31 | unsafe { 32 | debug_assert!( 33 | !INTERPRETER_EXISTS, 34 | "Found another MsgChannel. Only one MsgChannel can be created" 35 | ); 36 | INTERPRETER_EXISTS = true; 37 | } 38 | debug_assert!(0x1F > Op::NoOp as u8); 39 | // format!( 40 | // "init: {:?}, {:?}, {:?}", 41 | // unsafe { MSG_PTR_PTR as usize }, 42 | // unsafe { STR_PTR_PTR as usize }, 43 | // unsafe { STR_LEN_PTR as usize } 44 | // ); 45 | let js_interpreter = unsafe { 46 | JsInterpreter::new( 47 | wasm_bindgen::memory(), 48 | MSG_METADATA_PTR as usize, 49 | MSG_PTR_PTR as usize, 50 | STR_PTR_PTR as usize, 51 | STR_LEN_PTR as usize, 52 | ) 53 | }; 54 | 55 | Self { 56 | js_interpreter, 57 | last_mem_size: 0, 58 | batch: Batch::default(), 59 | } 60 | } 61 | } 62 | 63 | impl MsgChannel { 64 | /// IMPORTANT: This method is exicuted immediatly and does not wait for the next flush 65 | /// 66 | /// Example: 67 | /// ```no_run 68 | /// let window = web_sys::window().unwrap(); 69 | /// let document = window.document().unwrap(); 70 | /// let body = document.body().unwrap(); 71 | /// let mut channel = MsgChannel::default(); 72 | /// // assign the NodeId(0) to the body element from web-sys 73 | /// channel.set_node(NodeId(0), JsCast::dyn_into(body).unwrap()); 74 | /// // no need to call flush here because set_node is exicuted immediatly 75 | /// ``` 76 | pub fn set_node(&mut self, id: NodeId, node: Node) { 77 | self.js_interpreter.SetNode(id.0, node); 78 | } 79 | 80 | /// IMPORTANT: This method is exicuted immediatly and does not wait for the next flush 81 | /// 82 | /// Example: 83 | /// ```no_run 84 | /// let mut channel = MsgChannel::default(); 85 | /// channel.create_element("div", Some(NodeId(0))); 86 | /// channel.flush(); 87 | /// let element = channel.get_node(NodeId(0)); 88 | /// let text = element.text_content().map(|t| t + " + web-sys"); 89 | /// element.set_text_content(text.as_deref()); 90 | /// // no need to call flush here because get_node is exicuted immediatly 91 | /// ``` 92 | pub fn get_node(&mut self, id: NodeId) -> Node { 93 | self.js_interpreter.GetNode(id.0) 94 | } 95 | 96 | /// Exicutes any queued operations in the order they were added 97 | /// 98 | /// Example: 99 | /// 100 | /// ```no_run 101 | /// let mut channel = MsgChannel::default(); 102 | /// // this does not immediatly create a
or

103 | /// channel.create_element("div", None); 104 | /// channel.create_element("p", None); 105 | /// // this creates the

and

elements 106 | /// channel.flush(); 107 | /// ``` 108 | pub fn flush(&mut self) { 109 | self.batch.encode_op(Op::Stop); 110 | run_batch( 111 | &self.batch.msg, 112 | &self.batch.str_buf, 113 | &mut self.last_mem_size, 114 | ); 115 | self.batch.msg.clear(); 116 | self.batch.current_op_batch_idx = 0; 117 | self.batch.current_op_byte_idx = 3; 118 | self.batch.str_buf.clear(); 119 | } 120 | 121 | /// Appends a number of nodes as children of the given node. 122 | /// 123 | /// Example: 124 | /// 125 | /// ```no_run 126 | /// let mut channel = MsgChannel::default(); 127 | /// channel.create_element("div", Some(NodeId(0))); 128 | /// channel.create_element("p", None); 129 | /// // append the

element to the

element 130 | /// channel.append_child(MaybeId::Node(NodeId(0)), MaybeId::LastNode); 131 | /// channel.flush(); 132 | /// ``` 133 | pub fn append_child(&mut self, root: MaybeId, child: MaybeId) { 134 | self.batch.append_child(root, child) 135 | } 136 | 137 | /// Replace a node with another node 138 | /// 139 | /// Example: 140 | /// ```no_run 141 | /// let mut channel = MsgChannel::default(); 142 | /// channel.create_element("div", Some(NodeId(0))); 143 | /// channel.create_element("p", None); 144 | /// // replace the

element with the

element 145 | /// channel.replace_with(MaybeId::Node(NodeId(0)), MaybeId::LastNode); 146 | /// channel.flush(); 147 | /// ``` 148 | pub fn replace_with(&mut self, root: MaybeId, node: MaybeId) { 149 | self.batch.replace_with(root, node) 150 | } 151 | 152 | /// Replace a node with many nodes 153 | /// 154 | /// Example: 155 | /// ```no_run 156 | /// let mut channel = MsgChannel::default(); 157 | /// channel.create_element("div", Some(NodeId(0))); 158 | /// channel.create_element("p", None); 159 | /// // replace the

element with the

element 160 | /// channel.replace_with_nodes(MaybeId::Node(NodeId(0)), MaybeId::LastNode); 161 | /// channel.flush(); 162 | /// ``` 163 | pub fn replace_with_nodes(&mut self, root: MaybeId, nodes: &[MaybeId]) { 164 | self.batch.replace_with_nodes(root, nodes) 165 | } 166 | 167 | /// Insert a single node after a given node. 168 | /// 169 | /// Example: 170 | /// ```no_run 171 | /// let mut channel = MsgChannel::default(); 172 | /// channel.create_element("div", Some(NodeId(0))); 173 | /// channel.create_element("p", None); 174 | /// // insert the

element after the

element 175 | /// channel.insert_after(MaybeId::Node(NodeId(0)), MaybeId::LastNode); 176 | /// channel.flush(); 177 | /// ``` 178 | pub fn insert_after(&mut self, root: MaybeId, node: MaybeId) { 179 | self.batch.insert_after(root, node) 180 | } 181 | 182 | /// Insert a many nodes after a given node. 183 | /// 184 | /// Example: 185 | /// ```no_run 186 | /// let mut channel = MsgChannel::default(); 187 | /// channel.create_element("div", Some(NodeId(0))); 188 | /// channel.create_element("p", None); 189 | /// // insert the

element after the

element 190 | /// channel.insert_nodes_after(MaybeId::Node(NodeId(0)), &[MaybeId::LastNode]); 191 | /// channel.flush(); 192 | /// ``` 193 | pub fn insert_nodes_after(&mut self, root: MaybeId, nodes: &[MaybeId]) { 194 | self.batch.insert_nodes_after(root, nodes) 195 | } 196 | 197 | /// Insert a single node before a given node. 198 | /// 199 | /// Example: 200 | /// ```no_run 201 | /// let mut channel = MsgChannel::default(); 202 | /// channel.create_element("div", Some(NodeId(0))); 203 | /// channel.create_element("p", None); 204 | /// // insert the

element before the

element 205 | /// channel.insert_before(MaybeId::Node(NodeId(0)), MaybeId::LastNode); 206 | /// channel.flush(); 207 | /// ``` 208 | pub fn insert_before(&mut self, root: MaybeId, node: MaybeId) { 209 | self.batch.insert_before(root, node) 210 | } 211 | 212 | /// Insert many nodes before a given node. 213 | /// 214 | /// Example: 215 | /// ```no_run 216 | /// let mut channel = MsgChannel::default(); 217 | /// channel.create_element("div", Some(NodeId(0))); 218 | /// channel.create_element("p", None); 219 | /// // insert the

element before the

element 220 | /// channel.insert_nodes_before(MaybeId::Node(NodeId(0)), &[MaybeId::LastNode]); 221 | /// channel.flush(); 222 | /// ``` 223 | pub fn insert_nodes_before(&mut self, root: MaybeId, nodes: &[MaybeId]) { 224 | self.batch.insert_nodes_before(root, nodes) 225 | } 226 | 227 | /// Remove a node from the DOM. 228 | /// 229 | /// Example: 230 | /// ```no_run 231 | /// let mut channel = MsgChannel::default(); 232 | /// channel.create_element("p", None); 233 | /// // remove the

element 234 | /// channel.remove(MaybeId::LastNode); 235 | /// channel.flush(); 236 | /// ``` 237 | pub fn remove(&mut self, id: MaybeId) { 238 | self.batch.remove(id) 239 | } 240 | 241 | /// Create a new text node 242 | /// 243 | /// Example: 244 | /// ```no_run 245 | /// let mut channel = MsgChannel::default(); 246 | /// // create a text node with the text "Hello World" 247 | /// channel.create_text_node("Hello World", None); 248 | /// channel.flush(); 249 | pub fn create_text_node(&mut self, text: impl WritableText, id: Option) { 250 | self.batch.create_text_node(text, id) 251 | } 252 | 253 | /// Create a new element node 254 | /// 255 | /// Example: 256 | /// ```no_run 257 | /// let mut channel = MsgChannel::default(); 258 | /// // create a

element 259 | /// channel.create_element("div", None); 260 | /// channel.flush(); 261 | /// ``` 262 | pub fn create_element<'a, 'b>(&mut self, tag: impl IntoElement<'a, 'b>, id: Option) { 263 | self.batch.create_element(tag, id) 264 | } 265 | 266 | /// Set the textcontent of a node. 267 | /// 268 | /// Example: 269 | /// ```no_run 270 | /// let mut channel = MsgChannel::default(); 271 | /// // create a text node with the text "Hello World" 272 | /// channel.create_text_node("Hello ", None); 273 | /// // set the text content of the text node to "Hello World!!!" 274 | /// channel.set_text_content("World!!!", MaybeId::LastNode); 275 | /// channel.flush(); 276 | /// ``` 277 | pub fn set_text(&mut self, text: impl WritableText, root: MaybeId) { 278 | self.batch.set_text(text, root) 279 | } 280 | 281 | /// Set the value of a node's attribute. 282 | /// 283 | /// Example: 284 | /// ```no_run 285 | /// let mut channel = MsgChannel::default(); 286 | /// // create a
element 287 | /// channel.create_element("div", None); 288 | /// // set the attribute "id" to "my-div" on the
element 289 | /// channel.set_attribute(Attribute::id, "my-div", MaybeId::LastNode); 290 | /// channel.flush(); 291 | /// ``` 292 | pub fn set_attribute<'a, 'b>( 293 | &mut self, 294 | attr: impl IntoAttribue<'a, 'b>, 295 | value: impl WritableText, 296 | root: MaybeId, 297 | ) { 298 | self.batch.set_attribute(attr, value, root) 299 | } 300 | 301 | /// Remove an attribute from a node. 302 | /// 303 | /// Example: 304 | /// ```no_run 305 | /// let mut channel = MsgChannel::default(); 306 | /// // create a
element 307 | /// channel.create_element("div", None); 308 | /// channel.set_attribute(Attribute::id, "my-div", MaybeId::LastNode); 309 | /// // remove the attribute "id" from the
element 310 | /// channel.remove_attribute(Attribute::id, MaybeId::LastNode); 311 | /// channel.flush(); 312 | /// ``` 313 | pub fn remove_attribute<'a, 'b>(&mut self, attr: impl IntoAttribue<'a, 'b>, root: MaybeId) { 314 | self.batch.remove_attribute(attr, root) 315 | } 316 | 317 | /// Clone a node and store it with a new id. 318 | /// 319 | /// Example: 320 | /// ```no_run 321 | /// let mut channel = MsgChannel::default(); 322 | /// // create a
element 323 | /// channel.create_element("div", None); 324 | /// // clone the
element and store it with the id 1 325 | /// channel.clone_node(MaybeId::LastNode, Some(NodeId(1))); 326 | /// channel.flush(); 327 | /// ``` 328 | pub fn clone_node(&mut self, id: MaybeId, new_id: MaybeId) { 329 | self.batch.clone_node(id, new_id) 330 | } 331 | 332 | /// Move the last node to the first child 333 | /// 334 | /// Example: 335 | /// ```no_run 336 | /// let mut channel = MsgChannel::default(); 337 | /// // create a element:

338 | /// channel.build_full_element( 339 | /// ElementBuilder::new("div".into()) 340 | /// .children(&[ 341 | /// ElementBuilder::new(Element::p.into()) 342 | /// .into(), 343 | /// ]), 344 | /// ); 345 | /// // move from the
to the

346 | /// channel.first_child(); 347 | /// // operatons modifing the

element... 348 | /// channel.flush(); 349 | /// ``` 350 | pub fn first_child(&mut self) { 351 | self.batch.first_child() 352 | } 353 | 354 | /// Move the last node to the next sibling 355 | /// 356 | /// Example: 357 | /// ```no_run 358 | /// let mut channel = MsgChannel::default(); 359 | /// // create a element:

360 | /// channel.build_full_element( 361 | /// ElementBuilder::new("div".into()) 362 | /// .children(&[ 363 | /// ElementBuilder::new(Element::h1.into()) 364 | /// .into(), 365 | /// ElementBuilder::new(Element::p.into()) 366 | /// ]), 367 | /// ); 368 | /// // move from the
to the

369 | /// channel.first_child(); 370 | /// // move from the

to the

371 | /// channel.next_sibling(); 372 | /// // operatons modifing the

element... 373 | /// channel.flush(); 374 | /// ``` 375 | pub fn next_sibling(&mut self) { 376 | self.batch.next_sibling() 377 | } 378 | 379 | /// Move the last node to the parent node 380 | /// 381 | /// Example: 382 | /// ```no_run 383 | /// let mut channel = MsgChannel::default(); 384 | /// // create a element:

385 | /// channel.build_full_element( 386 | /// ElementBuilder::new("div".into()) 387 | /// .children(&[ 388 | /// ElementBuilder::new(Element::p.into()) 389 | /// .id(NodeId(0)) 390 | /// .into(), 391 | /// ]), 392 | /// ); 393 | /// // move to the

element 394 | /// channel.set_last_node(NodeId(0)); 395 | /// // move from the

to the

396 | /// channel.parent_node(); 397 | /// // operatons modifing the

element... 398 | /// channel.flush(); 399 | /// ``` 400 | pub fn parent_node(&mut self) { 401 | self.batch.parent_node() 402 | } 403 | 404 | /// Store the last node with the given id. This is useful when traversing the document tree. 405 | /// 406 | /// Example: 407 | /// ```no_run 408 | /// let mut channel = MsgChannel::default(); 409 | /// // create a element without an id 410 | /// channel.create_element("div", None); 411 | /// // store the

element with the id 0 412 | /// channel.set_last_node(NodeId(0)); 413 | /// channel.flush(); 414 | /// ``` 415 | pub fn store_with_id(&mut self, id: NodeId) { 416 | self.batch.store_with_id(id) 417 | } 418 | 419 | /// Set the last node to the given id. The last node can be used to traverse the document tree without passing objects between wasm and js every time. 420 | /// 421 | /// Example: 422 | /// ```no_run 423 | /// let mut channel = MsgChannel::default(); 424 | /// // create a element:

425 | /// channel.build_full_element( 426 | /// ElementBuilder::new("div".into()) 427 | /// .children(&[ 428 | /// ElementBuilder::new(Element::h1.into()) 429 | /// .children(&[ 430 | /// ElementBuilder::new(Element::h2.into()) 431 | /// .into(), 432 | /// ]).into(), 433 | /// ElementBuilder::new(Element::p.into()) 434 | /// .into(), 435 | /// ]), 436 | /// ); 437 | /// // move from the
to the

438 | /// channel.first_child(); 439 | /// // store the

element with the id 0 440 | /// channel.store_with_id(NodeId(0)); 441 | /// // move from the

to the

442 | /// channel.first_child(); 443 | /// // update something in the

element... 444 | /// // restore the

element 445 | /// channel.set_last_node(NodeId(0)); 446 | /// // move from the

to the

447 | /// channel.next_sibling(); 448 | /// // operatons modifing the

element... 449 | /// channel.flush(); 450 | /// ``` 451 | pub fn set_last_node(&mut self, id: NodeId) { 452 | self.batch.set_last_node(id) 453 | } 454 | 455 | /// Build a full element, slightly more efficent than creating the element creating the element with `create_element` and then setting the attributes. 456 | /// 457 | /// Example: 458 | /// ```rust 459 | /// let mut channel = MsgChannel::default(); 460 | /// // create an element using sledgehammer 461 | /// channel.build_full_element( 462 | /// ElementBuilder::new("div".into()) 463 | /// .id(NodeId(0)) 464 | /// .attrs(&[(Attribute::style.into(), "color: blue")]) 465 | /// .children(&[ 466 | /// ElementBuilder::new(Element::p.into()) 467 | /// .into(), 468 | /// TextBuilder::new("Hello from sledgehammer!").into(), 469 | /// ]), 470 | /// ); 471 | /// channel.flush(); 472 | /// ``` 473 | pub fn build_full_element(&mut self, el: ElementBuilder) { 474 | self.batch.build_full_element(el) 475 | } 476 | 477 | /// Build a text node 478 | /// 479 | /// Example: 480 | /// ```rust 481 | /// let mut channel = MsgChannel::default(); 482 | /// // create an element using sledgehammer 483 | /// channel.build_text_node( 484 | /// TextBuilder::new("div".into()) 485 | /// ); 486 | /// channel.flush(); 487 | /// ``` 488 | pub fn build_text_node(&mut self, text: TextBuilder) { 489 | self.batch.build_text_node(text) 490 | } 491 | 492 | /// Set a style property on a node. 493 | /// 494 | /// Example: 495 | /// ```rust 496 | /// let mut channel = MsgChannel::default(); 497 | /// channel.create_element("div", None); 498 | /// // set the style property "color" to "blue" 499 | /// channel.set_style("color", "blue", MaybeId::LastNode); 500 | /// channel.flush(); 501 | /// ``` 502 | pub fn set_style(&mut self, style: &str, value: &str, id: MaybeId) { 503 | self.batch.set_style(style, value, id) 504 | } 505 | 506 | /// Remove a style property from a node. 507 | /// 508 | /// Example: 509 | /// ```rust 510 | /// let mut channel = MsgChannel::default(); 511 | /// channel.create_element("div", None); 512 | /// channel.set_style("color", "blue", MaybeId::LastNode); 513 | /// // remove the color style 514 | /// channel.remove_style("color", MaybeId::LastNode); 515 | /// channel.flush(); 516 | /// ``` 517 | pub fn remove_style(&mut self, style: &str, id: MaybeId) { 518 | self.batch.remove_style(style, id) 519 | } 520 | 521 | /// Adds a batch of operations to the current batch. 522 | /// 523 | /// Example: 524 | /// ```rust 525 | /// let mut channel = MsgChannel::default(); 526 | /// let mut batch = Batch::default(); 527 | /// batch.create_element("div", None); 528 | /// // add the batch to the channel 529 | /// channel.append(batch); 530 | /// channel.flush(); 531 | /// ``` 532 | pub fn append(&mut self, batch: Batch) { 533 | self.batch.append(batch); 534 | } 535 | 536 | /// IMPORTANT: This method is exicuted immediatly and does not wait for the next flush 537 | /// 538 | /// Run a batch of operations on the DOM immediately. This only runs the operations that are in the batch, not the operations that are queued in the [`MsgChannel`]. 539 | /// 540 | /// Example: 541 | /// ```rust 542 | /// let mut channel = MsgChannel::default(); 543 | /// let mut batch = Batch::default(); 544 | /// batch.create_element("div", None); 545 | /// // add the batch to the channel 546 | /// channel.run_batch(&batch.finalize()); 547 | /// ``` 548 | pub fn run_batch(&mut self, batch: impl PreparedBatch) { 549 | run_batch(batch.msg(), batch.str(), &mut self.last_mem_size); 550 | } 551 | } 552 | 553 | fn run_batch(msg: &[u8], str_buf: &[u8], last_mem_size: &mut usize) { 554 | debug_assert_eq!(0usize.to_le_bytes().len(), 32 / 8); 555 | let msg_ptr = msg.as_ptr() as usize; 556 | let str_ptr = str_buf.as_ptr() as usize; 557 | // the pointer will only be updated when the message vec is resized, so we have a flag to check if the pointer has changed to avoid unnecessary decoding 558 | if unsafe { *MSG_METADATA_PTR } == 255 { 559 | // this is the first message, so we need to encode all the metadata 560 | unsafe { 561 | let mut_ptr_ptr: *mut usize = std::mem::transmute(MSG_PTR_PTR); 562 | *mut_ptr_ptr = msg_ptr; 563 | let mut_metadata_ptr: *mut u8 = std::mem::transmute(MSG_METADATA_PTR); 564 | // the first bit encodes if the msg pointer has changed 565 | *mut_metadata_ptr = 1; 566 | let mut_str_ptr_ptr: *mut usize = std::mem::transmute(STR_PTR_PTR); 567 | *mut_str_ptr_ptr = str_ptr as usize; 568 | // the second bit encodes if the str pointer has changed 569 | *mut_metadata_ptr |= 2; 570 | } 571 | } else { 572 | if unsafe { *MSG_PTR_PTR } != msg_ptr { 573 | unsafe { 574 | let mut_ptr_ptr: *mut usize = std::mem::transmute(MSG_PTR_PTR); 575 | *mut_ptr_ptr = msg_ptr; 576 | let mut_ptr_ptr: *mut u8 = std::mem::transmute(MSG_METADATA_PTR); 577 | // the first bit encodes if the msg pointer has changed 578 | *mut_ptr_ptr = 1; 579 | } 580 | } else { 581 | unsafe { 582 | let mut_ptr_ptr: *mut u8 = std::mem::transmute(MSG_METADATA_PTR); 583 | // the first bit encodes if the msg pointer has changed 584 | *mut_ptr_ptr = 0; 585 | } 586 | } 587 | if unsafe { *STR_PTR_PTR } != str_ptr { 588 | unsafe { 589 | let mut_str_ptr_ptr: *mut usize = std::mem::transmute(STR_PTR_PTR); 590 | *mut_str_ptr_ptr = str_ptr as usize; 591 | let mut_metadata_ptr: *mut u8 = std::mem::transmute(MSG_METADATA_PTR); 592 | // the second bit encodes if the str pointer has changed 593 | *mut_metadata_ptr |= 1 << 1; 594 | } 595 | } 596 | } 597 | unsafe { 598 | let mut_metadata_ptr: *mut u8 = std::mem::transmute(MSG_METADATA_PTR); 599 | if !str_buf.is_empty() { 600 | // the third bit encodes if there is any strings 601 | *mut_metadata_ptr |= 1 << 2; 602 | let mut_str_len_ptr: *mut usize = std::mem::transmute(STR_LEN_PTR); 603 | *mut_str_len_ptr = str_buf.len() as usize; 604 | if *mut_str_len_ptr < 100 { 605 | // the fourth bit encodes if the strings are entirely ascii and small 606 | *mut_metadata_ptr |= (str_buf.is_ascii() as u8) << 3; 607 | } 608 | } 609 | } 610 | let new_mem_size = core::arch::wasm32::memory_size(0); 611 | // we need to update the memory if the memory has grown 612 | if new_mem_size != *last_mem_size { 613 | *last_mem_size = new_mem_size; 614 | update_last_memory(wasm_bindgen::memory()); 615 | } 616 | 617 | work_last_created(); 618 | } 619 | -------------------------------------------------------------------------------- /web/src/lib.rs: -------------------------------------------------------------------------------- 1 | //!

2 | //! 3 | //! 4 | //! Crates.io version 6 | //! 7 | //! 8 | //! 9 | //! Download 11 | //! 12 | //! 13 | //! 14 | //! docs.rs docs 16 | //! 17 | //!
18 | //! 19 | //!**Breaking the WASM<->JS peformance boundry one brick at a time** 20 | //!### Status: There are some holes in the wall. 21 | //! 22 | //!# What is Sledgehammer? 23 | //!Sledgehammer provides faster rust bindings for dom manipuations by batching calls to js. 24 | //! 25 | //! # Getting started 26 | //! - All operations go through a [`MsgChannel`] which handles the communication with js. 27 | //! 28 | //!# Benchmarks 29 | //! 30 | //!- run this benchmark in your browser: [dom operation time only (not paint time) js-framework-benchmark](https://demonthos.github.io/wasm_bindgen_sledgehammer/) 31 | //! 32 | //!This gives more consistant results than the official js-framework-benchmark because it excludes the variation in paint time. Because sledgehammer and wasm-bindgen implementations result in the same dom calls they should have the same paint time. 33 | //! 34 | //!- A few runs of [a fork of the js-framework-benchmark:](https://github.com/demonthos/js-framework-benchmark/tree/testing) 35 | //!
36 | //! 37 | //! 38 | //! 39 | //!
40 | //! 41 | //!# How does this compare to wasm-bindgen/web-sys: 42 | //!wasm-bindgen is a lot more general, and ergonomic to use than sledgehammer. It has bindings to a lot of apis that sledgehammer does not. For most users wasm-bindgen is a beter choice. Sledgehammer is specifically designed for web frameworks that want low level, fast access to the dom. 43 | //! 44 | //!# Why is it fast? 45 | //! 46 | //!## String decoding 47 | //! 48 | //!- Decoding strings are expensive to decode, but the cost doesn't change much with the size of the string. Wasm-bindgen calls TextDecoder.decode for every string. Sledehammer only calls TextEncoder.decode once per batch. 49 | //! 50 | //!- If the string is small it is faster to decode the string in javascript to avoid the constant overhead of TextDecoder.decode 51 | //! 52 | //!- See this benchmark: 53 | //! 54 | //!## Single byte attributes and elements 55 | //! 56 | //!- In addition to making string decoding cheaper, sledehammer also uses less strings. All elements and attribute names are encoded as a single byte instead of a string and then turned back into a string in the javascipt intepreter. 57 | //! 58 | //!- To allow for custom elements and attributes, you can pass in a &str instead of a Attribute or Element enum. 59 | //! 60 | //!## Byte encoded operations 61 | //! 62 | //!- In sledehammer every operation is encoded as a sequence of bytes packed into an array. Every operation takes 1 byte plus whatever data is required for it. 63 | //! 64 | //!- Booleans are encoded as part of the operation byte to reduce the number of bytes read. 65 | //! 66 | //!- Each operation is encoded in a batch of four as a u32. Getting a number from an array buffer has a high constant cost, but getting a u32 instead of a u8 is not more expensive. Sledgehammer reads the u32 and then splits it into the 4 individual bytes. 67 | //! 68 | //!- See this benchmark: 69 | //! 70 | //!## Minimize passing ids 71 | //! 72 | //!- A common set of operations for webframeworks to perform is traversing dom nodes after cloning them. Instead of assigning an id to every node, sledgehammer allows you to perform operations on the last node that was created or navigated to. This means traversing id takes only one byte per operation instead of 5. 73 | 74 | #![allow(non_camel_case_types)] 75 | 76 | pub mod channel; 77 | 78 | pub use channel::MsgChannel; 79 | pub use sledgehammer_encoder::{ 80 | Attribute, Element, ElementBuilder, IntoAttribue, IntoElement, MaybeId, NodeBuilder, NodeId, 81 | StaticBatch, TextBuilder, WritableText, 82 | }; 83 | 84 | pub use sledgehammer_encoder; 85 | 86 | use wasm_bindgen::prelude::*; 87 | use web_sys::Node; 88 | 89 | #[used] 90 | static mut MSG_PTR: usize = 0; 91 | #[used] 92 | static mut MSG_PTR_PTR: *const usize = unsafe { &MSG_PTR } as *const usize; 93 | #[used] 94 | static mut MSG_POS_UPDATED: u8 = 255; 95 | #[used] 96 | static mut MSG_METADATA_PTR: *const u8 = unsafe { &MSG_POS_UPDATED } as *const u8; 97 | #[used] 98 | static mut STR_PTR: usize = 0; 99 | #[used] 100 | static mut STR_PTR_PTR: *const usize = unsafe { &STR_PTR } as *const usize; 101 | #[used] 102 | static mut STR_LEN: usize = 0; 103 | #[used] 104 | static mut STR_LEN_PTR: *const usize = unsafe { &STR_LEN } as *const usize; 105 | 106 | #[wasm_bindgen(module = "/interpreter_opt.js")] 107 | // #[wasm_bindgen(module = "/interpreter.js")] 108 | extern "C" { 109 | fn work_last_created(); 110 | 111 | fn update_last_memory(mem: JsValue); 112 | 113 | pub(crate) type JsInterpreter; 114 | 115 | #[wasm_bindgen(constructor)] 116 | pub(crate) fn new( 117 | mem: JsValue, 118 | msg_pos_updated_ptr: usize, 119 | msg_ptr: usize, 120 | str_ptr: usize, 121 | str_len_ptr: usize, 122 | ) -> JsInterpreter; 123 | 124 | #[wasm_bindgen(method)] 125 | pub(crate) fn UpdateMemory(this: &JsInterpreter, mem: JsValue); 126 | 127 | #[wasm_bindgen(method)] 128 | pub(crate) fn SetNode(this: &JsInterpreter, id: u32, node: Node); 129 | 130 | #[allow(unused)] 131 | #[wasm_bindgen(method)] 132 | pub(crate) fn GetNode(this: &JsInterpreter, id: u32) -> Node; 133 | } 134 | --------------------------------------------------------------------------------