rusteerio 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/CONTRIBUTING.md +73 -0
  2. package/Cargo.lock +1051 -0
  3. package/Cargo.toml +36 -0
  4. package/LICENSE +21 -0
  5. package/README.md +252 -2
  6. package/RELEASING.md +138 -0
  7. package/THIRD_PARTY_NOTICES.md +7443 -0
  8. package/build.rs +3 -0
  9. package/dist/chunk-DKULX6VO.mjs +3185 -0
  10. package/dist/chunk-DKULX6VO.mjs.map +1 -0
  11. package/dist/chunk-OHPGEY3E.mjs +32 -0
  12. package/dist/chunk-OHPGEY3E.mjs.map +1 -0
  13. package/dist/index.cjs +3230 -0
  14. package/dist/index.cjs.map +1 -0
  15. package/dist/index.d.mts +113 -0
  16. package/dist/index.d.ts +113 -0
  17. package/dist/index.mjs +38 -0
  18. package/dist/index.mjs.map +1 -0
  19. package/dist/slim.cjs +2441 -0
  20. package/dist/slim.cjs.map +1 -0
  21. package/dist/slim.d.mts +11 -0
  22. package/dist/slim.d.ts +11 -0
  23. package/dist/slim.mjs +19 -0
  24. package/dist/slim.mjs.map +1 -0
  25. package/dist/types-BP6TpxE5.d.mts +532 -0
  26. package/dist/types-BP6TpxE5.d.ts +532 -0
  27. package/dist/utils.cjs +61 -0
  28. package/dist/utils.cjs.map +1 -0
  29. package/dist/utils.d.mts +13 -0
  30. package/dist/utils.d.ts +13 -0
  31. package/dist/utils.mjs +17 -0
  32. package/dist/utils.mjs.map +1 -0
  33. package/native/src/arena.rs +1781 -0
  34. package/native/src/attributes.rs +923 -0
  35. package/native/src/codec.rs +172 -0
  36. package/native/src/diagnostics.rs +1249 -0
  37. package/native/src/extraction.rs +226 -0
  38. package/native/src/gc.rs +166 -0
  39. package/native/src/hook_sink.rs +727 -0
  40. package/native/src/hook_stream.rs +513 -0
  41. package/native/src/lib.rs +2095 -0
  42. package/native/src/location.rs +392 -0
  43. package/native/src/runtime.rs +189 -0
  44. package/native/src/selector.rs +1287 -0
  45. package/native/src/sink.rs +261 -0
  46. package/native/src/stream.rs +334 -0
  47. package/native/src/tokenizer.rs +355 -0
  48. package/native/src/tracker.rs +232 -0
  49. package/native/vendor/README.md +25 -0
  50. package/native/vendor/html5ever-0.35.0/.cargo_vcs_info.json +6 -0
  51. package/native/vendor/html5ever-0.35.0/Cargo.lock +828 -0
  52. package/native/vendor/html5ever-0.35.0/Cargo.toml +85 -0
  53. package/native/vendor/html5ever-0.35.0/LICENSE-APACHE +201 -0
  54. package/native/vendor/html5ever-0.35.0/LICENSE-MIT +25 -0
  55. package/native/vendor/html5ever-0.35.0/README.md +60 -0
  56. package/native/vendor/html5ever-0.35.0/benches/html5ever.rs +86 -0
  57. package/native/vendor/html5ever-0.35.0/data/bench/lipsum-zh.html +19 -0
  58. package/native/vendor/html5ever-0.35.0/data/bench/lipsum.html +40 -0
  59. package/native/vendor/html5ever-0.35.0/data/bench/medium-fragment.html +24 -0
  60. package/native/vendor/html5ever-0.35.0/data/bench/small-fragment.html +7 -0
  61. package/native/vendor/html5ever-0.35.0/data/bench/strong.html +1 -0
  62. package/native/vendor/html5ever-0.35.0/data/bench/tiny-fragment.html +1 -0
  63. package/native/vendor/html5ever-0.35.0/examples/arena.rs +356 -0
  64. package/native/vendor/html5ever-0.35.0/examples/capi/tokenize.c +74 -0
  65. package/native/vendor/html5ever-0.35.0/examples/noop-tokenize.rs +47 -0
  66. package/native/vendor/html5ever-0.35.0/examples/noop-tree-builder.rs +132 -0
  67. package/native/vendor/html5ever-0.35.0/examples/print-tree-actions.rs +185 -0
  68. package/native/vendor/html5ever-0.35.0/examples/tokenize.rs +111 -0
  69. package/native/vendor/html5ever-0.35.0/src/driver.rs +148 -0
  70. package/native/vendor/html5ever-0.35.0/src/lib.rs +30 -0
  71. package/native/vendor/html5ever-0.35.0/src/macros.rs +34 -0
  72. package/native/vendor/html5ever-0.35.0/src/serialize/mod.rs +255 -0
  73. package/native/vendor/html5ever-0.35.0/src/tokenizer/char_ref/mod.rs +451 -0
  74. package/native/vendor/html5ever-0.35.0/src/tokenizer/interface.rs +99 -0
  75. package/native/vendor/html5ever-0.35.0/src/tokenizer/mod.rs +2305 -0
  76. package/native/vendor/html5ever-0.35.0/src/tokenizer/states.rs +97 -0
  77. package/native/vendor/html5ever-0.35.0/src/tree_builder/data.rs +170 -0
  78. package/native/vendor/html5ever-0.35.0/src/tree_builder/mod.rs +1848 -0
  79. package/native/vendor/html5ever-0.35.0/src/tree_builder/rules.rs +1494 -0
  80. package/native/vendor/html5ever-0.35.0/src/tree_builder/tag_sets.rs +114 -0
  81. package/native/vendor/html5ever-0.35.0/src/tree_builder/types.rs +91 -0
  82. package/native/vendor/html5ever-0.35.0/src/util/str.rs +52 -0
  83. package/native/vendor/html5gum-0.8.4/Cargo.lock +1006 -0
  84. package/native/vendor/html5gum-0.8.4/Cargo.toml +109 -0
  85. package/native/vendor/html5gum-0.8.4/LICENSE +19 -0
  86. package/native/vendor/html5gum-0.8.4/README.md +110 -0
  87. package/native/vendor/html5gum-0.8.4/benches/files/0050.html +2131 -0
  88. package/native/vendor/html5gum-0.8.4/benches/files/6D30.html +1004 -0
  89. package/native/vendor/html5gum-0.8.4/benches/files.rs +32 -0
  90. package/native/vendor/html5gum-0.8.4/benches/patterns.rs +31 -0
  91. package/native/vendor/html5gum-0.8.4/src/arrayvec.rs +29 -0
  92. package/native/vendor/html5gum-0.8.4/src/char_validator.rs +134 -0
  93. package/native/vendor/html5gum-0.8.4/src/emitters/callback.rs +676 -0
  94. package/native/vendor/html5gum-0.8.4/src/emitters/default.rs +207 -0
  95. package/native/vendor/html5gum-0.8.4/src/emitters/emitter.rs +344 -0
  96. package/native/vendor/html5gum-0.8.4/src/emitters/html5ever.rs +306 -0
  97. package/native/vendor/html5gum-0.8.4/src/emitters/mod.rs +25 -0
  98. package/native/vendor/html5gum-0.8.4/src/entities.rs +3342 -0
  99. package/native/vendor/html5gum-0.8.4/src/error.rs +96 -0
  100. package/native/vendor/html5gum-0.8.4/src/htmlstring.rs +130 -0
  101. package/native/vendor/html5gum-0.8.4/src/lib.rs +66 -0
  102. package/native/vendor/html5gum-0.8.4/src/machine.rs +2136 -0
  103. package/native/vendor/html5gum-0.8.4/src/machine_helper.rs +277 -0
  104. package/native/vendor/html5gum-0.8.4/src/read_helper.rs +244 -0
  105. package/native/vendor/html5gum-0.8.4/src/reader.rs +440 -0
  106. package/native/vendor/html5gum-0.8.4/src/span.rs +98 -0
  107. package/native/vendor/html5gum-0.8.4/src/state.rs +16 -0
  108. package/native/vendor/html5gum-0.8.4/src/testutils.rs +71 -0
  109. package/native/vendor/html5gum-0.8.4/src/tokenizer.rs +189 -0
  110. package/native/vendor/html5gum-0.8.4/src/utils.rs +88 -0
  111. package/native/vendor/markup5ever-0.35.0/.cargo_vcs_info.json +6 -0
  112. package/native/vendor/markup5ever-0.35.0/Cargo.toml +45 -0
  113. package/native/vendor/markup5ever-0.35.0/LICENSE-APACHE +201 -0
  114. package/native/vendor/markup5ever-0.35.0/LICENSE-MIT +25 -0
  115. package/native/vendor/markup5ever-0.35.0/interface/mod.rs +422 -0
  116. package/native/vendor/markup5ever-0.35.0/interface/tree_builder.rs +280 -0
  117. package/native/vendor/markup5ever-0.35.0/lib.rs +59 -0
  118. package/native/vendor/markup5ever-0.35.0/serialize.rs +77 -0
  119. package/native/vendor/markup5ever-0.35.0/util/buffer_queue.rs +333 -0
  120. package/native/vendor/markup5ever-0.35.0/util/smallcharset.rs +88 -0
  121. package/package.json +137 -4
  122. package/rusteerio.darwin-arm64.node +0 -0
  123. package/rusteerio.darwin-x64.node +0 -0
  124. package/rusteerio.linux-arm64-gnu.node +0 -0
  125. package/rusteerio.linux-arm64-musl.node +0 -0
  126. package/rusteerio.linux-x64-gnu.node +0 -0
  127. package/rusteerio.linux-x64-musl.node +0 -0
  128. package/rusteerio.win32-arm64-msvc.node +0 -0
  129. package/rusteerio.win32-x64-msvc.node +0 -0
  130. package/scripts/check-native.mjs +15 -0
  131. package/scripts/check-release.mjs +235 -0
  132. package/scripts/fix-declarations.mjs +27 -0
  133. package/scripts/generate-notices.mjs +176 -0
  134. package/scripts/notices/README.md +13 -0
  135. package/scripts/notices/cheerio-MIT.txt +21 -0
  136. package/scripts/notices/html5ever-AUTHORS.txt +20 -0
  137. package/scripts/notices/html5ever-COPYRIGHT.txt +8 -0
  138. package/scripts/notices/html5ever-LICENSE-APACHE.txt +201 -0
  139. package/scripts/notices/html5ever-LICENSE-MIT.txt +25 -0
  140. package/scripts/notices/napi-rs-MIT.txt +43 -0
  141. package/scripts/notices/neon-MIT.txt +19 -0
  142. package/scripts/notices/runtime-01c266bced4a434da005.txt +3 -0
  143. package/scripts/notices/runtime-0a54e647fe54104658b5.txt +14 -0
  144. package/scripts/notices/runtime-0b74dfa0bcee5c420c6b.txt +25 -0
  145. package/scripts/notices/runtime-0d542e0c8804e39aa7f3.txt +177 -0
  146. package/scripts/notices/runtime-0f96a83840e146e43c0e.txt +21 -0
  147. package/scripts/notices/runtime-123a331b5dbf04c30097.txt +25 -0
  148. package/scripts/notices/runtime-172020dbfd5b53a226df.txt +34 -0
  149. package/scripts/notices/runtime-209fbbe0ad52d9235e37.txt +26 -0
  150. package/scripts/notices/runtime-23f18e03dc49df91622f.txt +23 -0
  151. package/scripts/notices/runtime-268872b9816f90fd8e85.txt +220 -0
  152. package/scripts/notices/runtime-35242e7a83f69875e6ed.txt +176 -0
  153. package/scripts/notices/runtime-378f5840b258e2779c39.txt +25 -0
  154. package/scripts/notices/runtime-4108245a1f2df9d4e94d.txt +25 -0
  155. package/scripts/notices/runtime-4455bf75a91154108304.txt +21 -0
  156. package/scripts/notices/runtime-553fffcd9b1cb158bc3e.txt +13 -0
  157. package/scripts/notices/runtime-62c7a1e35f56406896d7.txt +176 -0
  158. package/scripts/notices/runtime-6485b8ed310d3f0340bf.txt +25 -0
  159. package/scripts/notices/runtime-69fef7b0f322a6555415.txt +201 -0
  160. package/scripts/notices/runtime-6df43f6f4b5d4587f3d8.txt +187 -0
  161. package/scripts/notices/runtime-799e9ca9d179295ef372.txt +24 -0
  162. package/scripts/notices/runtime-7b63ecd5f1902af1b637.txt +25 -0
  163. package/scripts/notices/runtime-7bc7d5de2a0b793d4e1a.txt +21 -0
  164. package/scripts/notices/runtime-7e12e5df4bae12cb2158.txt +24 -0
  165. package/scripts/notices/runtime-861399f8c21c042b1105.txt +12 -0
  166. package/scripts/notices/runtime-8ada45cd9f843acf64e4.txt +201 -0
  167. package/scripts/notices/runtime-90eb64f0279b0d9432ac.txt +12 -0
  168. package/scripts/notices/runtime-a60eea817514531668d7.txt +201 -0
  169. package/scripts/notices/runtime-a7c936ff1ed8fa340172.txt +25 -0
  170. package/scripts/notices/runtime-ab6eec6caf0fa5775e41.txt +275 -0
  171. package/scripts/notices/runtime-b1181a40b2a7b25cf66f.txt +19 -0
  172. package/scripts/notices/runtime-b71bd43a069ca0641a9e.txt +25 -0
  173. package/scripts/notices/runtime-c16f8dcf1a368b83be78.txt +201 -0
  174. package/scripts/notices/runtime-c2cfccb812fe482101a8.txt +21 -0
  175. package/scripts/notices/runtime-e99d88d232bf57d70f0f.txt +25 -0
  176. package/scripts/notices/runtime-f32fb3b417a194167cfa.txt +9 -0
  177. package/scripts/notices/runtime-f5062c9a188d81dfe66b.txt +39 -0
  178. package/scripts/notices/runtime-f9070a832833fa7488fd.txt +4 -0
  179. package/scripts/notices/runtime-fab3dd6bdab226f1c086.txt +373 -0
  180. package/scripts/notices/runtime-fba3318d0c600380177e.txt +62 -0
  181. package/scripts/notices/runtime-ff8f68cb076caf8cefe7.txt +25 -0
  182. package/scripts/notices/runtime-ff92bed461f50338dd70.txt +74 -0
  183. package/scripts/notices/runtime.json +1632 -0
  184. package/scripts/notices/sources.json +555 -0
  185. package/scripts/notices/xray-MIT.txt +21 -0
  186. package/src/attributes.ts +271 -0
  187. package/src/codec.ts +74 -0
  188. package/src/dom.ts +87 -0
  189. package/src/extract-plan.ts +97 -0
  190. package/src/extract.ts +130 -0
  191. package/src/hooks.ts +220 -0
  192. package/src/index.ts +223 -0
  193. package/src/io.ts +491 -0
  194. package/src/native.ts +135 -0
  195. package/src/nodes.ts +290 -0
  196. package/src/parse.ts +53 -0
  197. package/src/parser-types.ts +157 -0
  198. package/src/router.ts +327 -0
  199. package/src/selection.ts +566 -0
  200. package/src/slim.ts +12 -0
  201. package/src/transport.ts +104 -0
  202. package/src/types.ts +186 -0
  203. package/src/utils.ts +29 -0
  204. package/src/workers.ts +21 -0
  205. package/tsconfig.json +15 -0
  206. package/tsup.config.ts +16 -0
@@ -0,0 +1,727 @@
1
+ //! An active user-supplied tree adapter. Rust runs the HTML5 algorithm; the
2
+ //! adapter supplies every construction, mutation and tree-inspection decision.
3
+ use std::borrow::Cow;
4
+ use std::cell::{Ref, RefCell};
5
+ use std::collections::HashMap;
6
+ use std::rc::Rc;
7
+
8
+ use html5ever::tendril::{StrTendril, TendrilSink};
9
+ use html5ever::tree_builder::{ElementFlags, NodeOrText, QuirksMode, TreeSink};
10
+ use html5ever::{Attribute, LocalName, Namespace, QualName};
11
+ use napi::{Env, Error, JsFunction, JsString, Result, Status};
12
+ use serde_json::{json, Value};
13
+
14
+ use crate::arena::{DomArena, DomNode};
15
+ use crate::NativeDocument;
16
+
17
+ // HTML5 token identities used by the parser, independent of adapter DOM names.
18
+ fn known_html_tag(name: &str) -> bool {
19
+ matches!(
20
+ name,
21
+ "a" | "address"
22
+ | "annotation-xml"
23
+ | "applet"
24
+ | "area"
25
+ | "article"
26
+ | "aside"
27
+ | "b"
28
+ | "base"
29
+ | "basefont"
30
+ | "bgsound"
31
+ | "big"
32
+ | "blockquote"
33
+ | "body"
34
+ | "br"
35
+ | "button"
36
+ | "caption"
37
+ | "center"
38
+ | "code"
39
+ | "col"
40
+ | "colgroup"
41
+ | "dd"
42
+ | "desc"
43
+ | "details"
44
+ | "dialog"
45
+ | "dir"
46
+ | "div"
47
+ | "dl"
48
+ | "dt"
49
+ | "em"
50
+ | "embed"
51
+ | "fieldset"
52
+ | "figcaption"
53
+ | "figure"
54
+ | "font"
55
+ | "footer"
56
+ | "foreignObject"
57
+ | "form"
58
+ | "frame"
59
+ | "frameset"
60
+ | "h1"
61
+ | "h2"
62
+ | "h3"
63
+ | "h4"
64
+ | "h5"
65
+ | "h6"
66
+ | "head"
67
+ | "header"
68
+ | "hgroup"
69
+ | "hr"
70
+ | "html"
71
+ | "i"
72
+ | "img"
73
+ | "image"
74
+ | "input"
75
+ | "iframe"
76
+ | "keygen"
77
+ | "label"
78
+ | "li"
79
+ | "link"
80
+ | "listing"
81
+ | "main"
82
+ | "malignmark"
83
+ | "marquee"
84
+ | "math"
85
+ | "menu"
86
+ | "meta"
87
+ | "mglyph"
88
+ | "mi"
89
+ | "mo"
90
+ | "mn"
91
+ | "ms"
92
+ | "mtext"
93
+ | "nav"
94
+ | "nobr"
95
+ | "noframes"
96
+ | "noembed"
97
+ | "noscript"
98
+ | "object"
99
+ | "ol"
100
+ | "optgroup"
101
+ | "option"
102
+ | "p"
103
+ | "param"
104
+ | "plaintext"
105
+ | "pre"
106
+ | "rb"
107
+ | "rp"
108
+ | "rt"
109
+ | "rtc"
110
+ | "ruby"
111
+ | "s"
112
+ | "script"
113
+ | "search"
114
+ | "section"
115
+ | "select"
116
+ | "source"
117
+ | "small"
118
+ | "span"
119
+ | "strike"
120
+ | "strong"
121
+ | "style"
122
+ | "sub"
123
+ | "summary"
124
+ | "sup"
125
+ | "table"
126
+ | "tbody"
127
+ | "template"
128
+ | "textarea"
129
+ | "tfoot"
130
+ | "td"
131
+ | "th"
132
+ | "thead"
133
+ | "title"
134
+ | "tr"
135
+ | "track"
136
+ | "tt"
137
+ | "u"
138
+ | "ul"
139
+ | "svg"
140
+ | "var"
141
+ | "wbr"
142
+ | "xmp"
143
+ )
144
+ }
145
+
146
+ struct DirectBridge {
147
+ env: Env,
148
+ function: napi::Ref<()>,
149
+ }
150
+ impl DirectBridge {
151
+ fn call(&self, request: &str) -> Result<Value> {
152
+ let function: JsFunction = self.env.get_reference_value(&self.function)?;
153
+ let argument = self.env.create_string(request)?;
154
+ let result = function.call(None, &[argument])?;
155
+ let text: JsString = result.coerce_to_string()?;
156
+ serde_json::from_str(text.into_utf8()?.as_str()?)
157
+ .map_err(|error| Error::new(Status::InvalidArg, error.to_string()))
158
+ }
159
+ }
160
+ impl Drop for DirectBridge {
161
+ fn drop(&mut self) {
162
+ let _ = self.function.unref(self.env);
163
+ }
164
+ }
165
+
166
+ pub(crate) type HookCall = Box<dyn Fn(&str, Value) -> Result<Value>>;
167
+
168
+ enum Transport {
169
+ Direct(DirectBridge),
170
+ Threaded(HookCall),
171
+ }
172
+ impl Transport {
173
+ fn call(&self, method: &str, args: Value) -> Result<Value> {
174
+ let result = match self {
175
+ Self::Direct(bridge) => bridge.call(&json!({"method":method,"args":args}).to_string()),
176
+ Self::Threaded(bridge) => bridge(method, args),
177
+ }?;
178
+ if result.get("__rusteerioHookError").and_then(Value::as_bool) == Some(true) {
179
+ Err(Error::new(
180
+ Status::GenericFailure,
181
+ "User treeAdapter hook failed",
182
+ ))
183
+ } else {
184
+ Ok(result)
185
+ }
186
+ }
187
+ }
188
+
189
+ struct HookSink {
190
+ bridge: Transport,
191
+ names: RefCell<HashMap<u32, QualName>>,
192
+ error: RefCell<Option<Error>>,
193
+ options: Value,
194
+ fragment: bool,
195
+ source: String,
196
+ context: Option<Rc<crate::diagnostics::DiagnosticContext>>,
197
+ diagnostics: RefCell<Vec<Value>>,
198
+ locations: Option<crate::tracker::Locations<u32>>,
199
+ }
200
+
201
+ impl HookSink {
202
+ fn call(&self, method: &str, args: Value) -> Value {
203
+ if self.error.borrow().is_some() {
204
+ return Value::Null;
205
+ }
206
+ let result = self.bridge.call(method, args);
207
+ match result {
208
+ Ok(value) => value,
209
+ Err(error) => {
210
+ *self.error.borrow_mut() = Some(error);
211
+ Value::Null
212
+ }
213
+ }
214
+ }
215
+ fn node(&self, method: &str, args: Value) -> u32 {
216
+ self.call(method, args).as_u64().unwrap_or(0) as u32
217
+ }
218
+ fn attrs(attrs: Vec<Attribute>) -> Value {
219
+ Value::Array(
220
+ attrs
221
+ .into_iter()
222
+ .map(|attr| {
223
+ let mut value =
224
+ json!({"name":attr.name.local.as_ref(),"value":attr.value.as_ref()});
225
+ if !attr.name.ns.as_ref().is_empty() {
226
+ value["prefix"] = Value::String(
227
+ attr.name
228
+ .prefix
229
+ .as_ref()
230
+ .map_or("", |prefix| prefix.as_ref())
231
+ .into(),
232
+ );
233
+ value["namespace"] = Value::String(attr.name.ns.as_ref().into());
234
+ }
235
+ value
236
+ })
237
+ .collect(),
238
+ )
239
+ }
240
+ fn publish_location(&self, id: u32, update: bool) {
241
+ let Some(location) = self
242
+ .locations
243
+ .as_ref()
244
+ .and_then(|locations| locations.get(id))
245
+ else {
246
+ return;
247
+ };
248
+ if update {
249
+ if let Some(fields) = location.as_object() {
250
+ let patch: serde_json::Map<String, Value> =
251
+ ["endLine", "endCol", "endOffset", "endTag"]
252
+ .into_iter()
253
+ .filter_map(|field| {
254
+ fields
255
+ .get(field)
256
+ .map(|value| (field.to_owned(), value.clone()))
257
+ })
258
+ .collect();
259
+ if !patch.is_empty() {
260
+ self.call("updateNodeSourceCodeLocation", json!([id, patch]));
261
+ }
262
+ }
263
+ } else {
264
+ let mut location = location;
265
+ if let Some(fields) = location.as_object_mut() {
266
+ let start_tag = fields.get("startTag").cloned();
267
+ if let Some(start_tag) = start_tag {
268
+ for field in ["endLine", "endCol", "endOffset"] {
269
+ if !fields.contains_key(field) {
270
+ fields.insert(field.to_owned(), start_tag[field].clone());
271
+ }
272
+ }
273
+ }
274
+ }
275
+ self.call("setNodeSourceCodeLocation", json!([id, location]));
276
+ }
277
+ }
278
+ fn mark_text(&self, parent: u32, before: Option<u32>, data: &str) {
279
+ let children = self.children(parent);
280
+ let id = match before {
281
+ Some(before) => children
282
+ .iter()
283
+ .position(|id| *id == before)
284
+ .and_then(|index| index.checked_sub(1))
285
+ .and_then(|index| children.get(index))
286
+ .copied(),
287
+ None => children.last().copied(),
288
+ };
289
+ if let (Some(locations), Some(id)) = (self.locations.as_ref(), id) {
290
+ let existing = self.call("getNodeSourceCodeLocation", json!([id]));
291
+ locations.text(id, data);
292
+ self.publish_location(id, !existing.is_null());
293
+ }
294
+ }
295
+ fn children(&self, id: u32) -> Vec<u32> {
296
+ self.call("getChildNodes", json!([id]))
297
+ .as_array()
298
+ .map(|ids| {
299
+ ids.iter()
300
+ .filter_map(|id| id.as_u64().map(|id| id as u32))
301
+ .collect()
302
+ })
303
+ .unwrap_or_default()
304
+ }
305
+ }
306
+
307
+ impl TreeSink for HookSink {
308
+ type Handle = u32;
309
+ type Output = Result<NativeDocument>;
310
+ type ElemName<'a> = Ref<'a, QualName>;
311
+
312
+ fn finish(self) -> Self::Output {
313
+ if self.fragment {
314
+ // html5ever exposes a synthetic html element for fragments. The
315
+ // adapter sees the same fragment root shape as Cheerio's parser.
316
+ for child in self.children(0) {
317
+ if self.call("isElementNode", json!([child])).as_bool() == Some(true)
318
+ && self.call("getTagName", json!([child])).as_str() == Some("html")
319
+ {
320
+ for node in self.children(child) {
321
+ self.call("detachNode", json!([node]));
322
+ self.call("appendChild", json!([0, node]));
323
+ }
324
+ self.call("detachNode", json!([child]));
325
+ break;
326
+ }
327
+ }
328
+ }
329
+ let snapshot = self.call("__snapshot", json!([]));
330
+ if let Some(error) = self.error.borrow_mut().take() {
331
+ return Err(error);
332
+ }
333
+ let mut records: Vec<DomNode> = serde_json::from_value(snapshot).map_err(|error| {
334
+ Error::new(
335
+ Status::InvalidArg,
336
+ format!("Invalid treeAdapter DOM: {error}"),
337
+ )
338
+ })?;
339
+ let source = if self.source.is_empty() {
340
+ self.context
341
+ .as_ref()
342
+ .map(|context| context.source_between(0, context.source_len()))
343
+ .unwrap_or_default()
344
+ } else {
345
+ self.source.clone()
346
+ };
347
+ if self.options.get("sourceCodeLocationInfo") == Some(&Value::Bool(true))
348
+ && !source.is_empty()
349
+ {
350
+ let mut fallback = records.clone();
351
+ crate::location::annotate(
352
+ &source,
353
+ &mut fallback,
354
+ self.options.get("scriptingEnabled") != Some(&Value::Bool(false)),
355
+ self.options.get("__utf16Codec") == Some(&Value::Bool(true)),
356
+ );
357
+ // Explicit creation/text/close operations published locations live.
358
+ // Only deferred table text or adapter-internal nodes lack them.
359
+ let locations: Vec<_> = records
360
+ .iter()
361
+ .zip(&fallback)
362
+ .filter(|(node, _)| {
363
+ !node.extras.contains_key("sourceCodeLocation") && node.kind != "root"
364
+ })
365
+ .filter_map(|(node, fallback)| {
366
+ fallback
367
+ .extras
368
+ .get("sourceCodeLocation")
369
+ .map(|location| json!({"id":node.id,"location":location}))
370
+ })
371
+ .collect();
372
+ self.call("__locations", json!([locations]));
373
+ let snapshot = self.call("__snapshot", json!([]));
374
+ if let Some(error) = self.error.borrow_mut().take() {
375
+ return Err(error);
376
+ }
377
+ records = serde_json::from_value(snapshot)
378
+ .map_err(|error| Error::new(Status::InvalidArg, error.to_string()))?;
379
+ }
380
+ let options = self.options;
381
+ let document =
382
+ NativeDocument::from_arena(DomArena::from_records(records, options.clone()), options);
383
+ document.set_diagnostic_records(self.diagnostics.into_inner());
384
+ Ok(document)
385
+ }
386
+ fn parse_error(&self, message: Cow<'static, str>) {
387
+ if let Some(error) = self
388
+ .context
389
+ .as_ref()
390
+ .and_then(|context| context.error(&message))
391
+ {
392
+ self.diagnostics.borrow_mut().push(error.clone());
393
+ self.call("__parseError", json!([error]));
394
+ }
395
+ }
396
+ fn get_document(&self) -> u32 {
397
+ 0
398
+ }
399
+ fn elem_name<'a>(&'a self, target: &'a u32) -> Ref<'a, QualName> {
400
+ let namespace = self
401
+ .call("getNamespaceURI", json!([target]))
402
+ .as_str()
403
+ .unwrap_or(crate::HTML_NS)
404
+ .to_owned();
405
+ let original = self.names.borrow().get(target).cloned();
406
+ // parse5 records known HTML token IDs on its open-element stack. A
407
+ // user adapter can rename the resulting DOM element without changing
408
+ // how a later known closing tag addresses that parser stack entry.
409
+ let local = if namespace == crate::HTML_NS
410
+ && original
411
+ .as_ref()
412
+ .is_some_and(|name| known_html_tag(name.local.as_ref()))
413
+ {
414
+ original.unwrap().local.to_string()
415
+ } else {
416
+ self.call("getTagName", json!([target]))
417
+ .as_str()
418
+ .unwrap_or("unknown")
419
+ .to_owned()
420
+ };
421
+ self.names.borrow_mut().insert(
422
+ *target,
423
+ QualName::new(None, Namespace::from(namespace), LocalName::from(local)),
424
+ );
425
+ Ref::map(self.names.borrow(), |names| &names[target])
426
+ }
427
+ fn create_element(&self, name: QualName, attrs: Vec<Attribute>, flags: ElementFlags) -> u32 {
428
+ let id = self.node(
429
+ "createElement",
430
+ json!([name.local.as_ref(), name.ns.as_ref(), Self::attrs(attrs)]),
431
+ );
432
+ if let Some(locations) = self.locations.as_ref() {
433
+ locations.created(id, name.local.as_ref());
434
+ self.publish_location(id, false);
435
+ }
436
+ self.names.borrow_mut().insert(id, name);
437
+ if flags.template {
438
+ let content = self.node("createDocumentFragment", json!([]));
439
+ self.call("setTemplateContent", json!([id, content]));
440
+ }
441
+ id
442
+ }
443
+ fn create_comment(&self, text: StrTendril) -> u32 {
444
+ let id = self.node("createCommentNode", json!([text.as_ref()]));
445
+ if let Some(locations) = self.locations.as_ref() {
446
+ locations.token(id, "comment");
447
+ }
448
+ id
449
+ }
450
+ fn create_pi(&self, target: StrTendril, data: StrTendril) -> u32 {
451
+ self.create_comment(format!("?{target} {data}?").into())
452
+ }
453
+ fn append(&self, parent: &u32, child: NodeOrText<u32>) {
454
+ match child {
455
+ NodeOrText::AppendNode(child) => {
456
+ self.call("appendChild", json!([parent, child]));
457
+ if self
458
+ .context
459
+ .as_ref()
460
+ .is_some_and(|context| context.current_token().kind == "comment")
461
+ {
462
+ self.publish_location(child, false);
463
+ }
464
+ }
465
+ NodeOrText::AppendText(text) => {
466
+ self.call("insertText", json!([parent, text.as_ref()]));
467
+ self.mark_text(*parent, None, text.as_ref());
468
+ }
469
+ }
470
+ }
471
+ fn append_based_on_parent_node(&self, element: &u32, previous: &u32, child: NodeOrText<u32>) {
472
+ if self.call("getParentNode", json!([element])).is_null() {
473
+ self.append(previous, child);
474
+ } else {
475
+ self.append_before_sibling(element, child);
476
+ }
477
+ }
478
+ fn append_doctype_to_document(
479
+ &self,
480
+ name: StrTendril,
481
+ public_id: StrTendril,
482
+ system_id: StrTendril,
483
+ ) {
484
+ self.call(
485
+ "setDocumentType",
486
+ json!([0, name.as_ref(), public_id.as_ref(), system_id.as_ref()]),
487
+ );
488
+ if let Some(locations) = self.locations.as_ref() {
489
+ for id in self.children(0) {
490
+ if self.call("isDocumentTypeNode", json!([id])).as_bool() == Some(true) {
491
+ locations.token(id, "doctype");
492
+ self.publish_location(id, false);
493
+ break;
494
+ }
495
+ }
496
+ }
497
+ }
498
+ fn get_template_contents(&self, target: &u32) -> u32 {
499
+ self.node("getTemplateContent", json!([target]))
500
+ }
501
+ fn same_node(&self, first: &u32, second: &u32) -> bool {
502
+ first == second
503
+ }
504
+ fn pop(&self, node: &u32) {
505
+ if let Some(locations) = self.locations.as_ref() {
506
+ let name = self.call("getTagName", json!([node]));
507
+ locations.closed_as(*node, name.as_str().unwrap_or(""));
508
+ self.publish_location(*node, true);
509
+ }
510
+ }
511
+ fn on_stack_push(&self, node: &u32) {
512
+ self.call("onItemPush", json!([node]));
513
+ }
514
+ fn on_stack_pop(&self, node: &u32, current: Option<&u32>) {
515
+ self.call("onItemPop", json!([node, current]));
516
+ }
517
+ fn set_quirks_mode(&self, mode: QuirksMode) {
518
+ let mode = match mode {
519
+ QuirksMode::Quirks => "quirks",
520
+ QuirksMode::LimitedQuirks => "limited-quirks",
521
+ QuirksMode::NoQuirks => "no-quirks",
522
+ };
523
+ self.call("setDocumentMode", json!([0, mode]));
524
+ }
525
+ fn append_before_sibling(&self, sibling: &u32, new_node: NodeOrText<u32>) {
526
+ let parent = self.node("getParentNode", json!([sibling]));
527
+ match new_node {
528
+ NodeOrText::AppendNode(node) => {
529
+ self.call("detachNode", json!([node]));
530
+ self.call("insertBefore", json!([parent, node, sibling]));
531
+ }
532
+ NodeOrText::AppendText(text) => {
533
+ self.call("insertTextBefore", json!([parent, text.as_ref(), sibling]));
534
+ self.mark_text(parent, Some(*sibling), text.as_ref());
535
+ }
536
+ }
537
+ }
538
+ fn add_attrs_if_missing(&self, target: &u32, attrs: Vec<Attribute>) {
539
+ self.call("adoptAttributes", json!([target, Self::attrs(attrs)]));
540
+ }
541
+ fn remove_from_parent(&self, target: &u32) {
542
+ self.call("detachNode", json!([target]));
543
+ }
544
+ fn reparent_children(&self, node: &u32, new_parent: &u32) {
545
+ for child in self.children(*node) {
546
+ self.call("detachNode", json!([child]));
547
+ self.call("appendChild", json!([new_parent, child]));
548
+ }
549
+ }
550
+ fn is_mathml_annotation_xml_integration_point(&self, handle: &u32) -> bool {
551
+ let namespace = self.call("getNamespaceURI", json!([handle]));
552
+ let name = self.call("getTagName", json!([handle]));
553
+ namespace.as_str() == Some("http://www.w3.org/1998/Math/MathML")
554
+ && name.as_str() == Some("annotation-xml")
555
+ && self
556
+ .call("getAttrList", json!([handle]))
557
+ .as_array()
558
+ .is_some_and(|attrs| {
559
+ attrs.iter().any(|attr| {
560
+ attr["name"] == "encoding"
561
+ && attr["value"].as_str().is_some_and(|value| {
562
+ value.eq_ignore_ascii_case("text/html")
563
+ || value.eq_ignore_ascii_case("application/xhtml+xml")
564
+ })
565
+ })
566
+ })
567
+ }
568
+ }
569
+
570
+ fn parser(
571
+ env: Env,
572
+ is_document: bool,
573
+ options_json: String,
574
+ bridge: JsFunction,
575
+ ) -> Result<html5ever::Parser<HookSink>> {
576
+ let options: Value = serde_json::from_str(&options_json)
577
+ .map_err(|error| Error::new(Status::InvalidArg, error.to_string()))?;
578
+ let bridge = DirectBridge {
579
+ env,
580
+ function: env.create_reference(bridge)?,
581
+ };
582
+ let sink = HookSink {
583
+ bridge: Transport::Direct(bridge),
584
+ names: RefCell::new(HashMap::new()),
585
+ error: RefCell::new(None),
586
+ options: options.clone(),
587
+ fragment: !is_document,
588
+ source: String::new(),
589
+ context: None,
590
+ diagnostics: RefCell::new(Vec::new()),
591
+ locations: None,
592
+ };
593
+ sink.call(
594
+ if is_document {
595
+ "createDocument"
596
+ } else {
597
+ "createDocumentFragment"
598
+ },
599
+ json!([]),
600
+ );
601
+ let parse_options = html5ever::driver::ParseOpts {
602
+ tokenizer: html5ever::tokenizer::TokenizerOpts {
603
+ discard_bom: false,
604
+ rusteerio_codec: options.get("__utf16Codec") == Some(&Value::Bool(true)),
605
+ ..Default::default()
606
+ },
607
+ tree_builder: html5ever::tree_builder::TreeBuilderOpts {
608
+ scripting_enabled: options.get("scriptingEnabled") != Some(&Value::Bool(false)),
609
+ ..Default::default()
610
+ },
611
+ };
612
+ Ok(if is_document {
613
+ html5ever::driver::parse_document(sink, parse_options)
614
+ } else {
615
+ let context = QualName::new(
616
+ None,
617
+ Namespace::from(
618
+ options
619
+ .get("__contextNamespace")
620
+ .and_then(Value::as_str)
621
+ .unwrap_or(crate::HTML_NS),
622
+ ),
623
+ LocalName::from(
624
+ options
625
+ .get("__contextName")
626
+ .and_then(Value::as_str)
627
+ .unwrap_or("template"),
628
+ ),
629
+ );
630
+ html5ever::driver::parse_fragment(sink, parse_options, context, Vec::new(), false)
631
+ })
632
+ }
633
+
634
+ pub(crate) fn parse(
635
+ env: Env,
636
+ source: String,
637
+ is_document: bool,
638
+ options_json: String,
639
+ bridge: JsFunction,
640
+ ) -> Result<NativeDocument> {
641
+ let options: Value = serde_json::from_str(&options_json)
642
+ .map_err(|error| Error::new(Status::InvalidArg, error.to_string()))?;
643
+ if options.get("__collectParseErrors") == Some(&Value::Bool(true))
644
+ || options.get("sourceCodeLocationInfo") == Some(&Value::Bool(true))
645
+ {
646
+ let context = Rc::new(crate::diagnostics::DiagnosticContext::new(&source));
647
+ let bridge = DirectBridge {
648
+ env,
649
+ function: env.create_reference(bridge)?,
650
+ };
651
+ let sink = HookSink {
652
+ bridge: Transport::Direct(bridge),
653
+ names: RefCell::new(HashMap::new()),
654
+ error: RefCell::new(None),
655
+ options: options.clone(),
656
+ fragment: !is_document,
657
+ source: source.clone(),
658
+ context: Some(context.clone()),
659
+ diagnostics: RefCell::new(Vec::new()),
660
+ locations: (options.get("sourceCodeLocationInfo") == Some(&Value::Bool(true)))
661
+ .then(|| crate::tracker::Locations::new(context.clone())),
662
+ };
663
+ sink.call(
664
+ if is_document {
665
+ "createDocument"
666
+ } else {
667
+ "createDocumentFragment"
668
+ },
669
+ json!([]),
670
+ );
671
+ crate::diagnostics::parse_with_sink(
672
+ &source,
673
+ is_document,
674
+ &options,
675
+ sink,
676
+ context,
677
+ |_, _| {},
678
+ )
679
+ } else {
680
+ let mut parser = parser(env, is_document, options_json, bridge)?;
681
+ parser.tokenizer.sink.sink.source.push_str(&source);
682
+ parser.one(source)
683
+ }
684
+ }
685
+
686
+ /// A blocking reader is driven on a native worker. The supplied transport waits
687
+ /// for a main-thread JS callback response for every actual sink operation.
688
+ pub(crate) fn parse_reader<R: html5gum::Reader>(
689
+ reader: R,
690
+ options: Value,
691
+ is_document: bool,
692
+ context: Rc<crate::diagnostics::DiagnosticContext>,
693
+ call: HookCall,
694
+ ) -> Result<NativeDocument>
695
+ where
696
+ R::Error: std::fmt::Display,
697
+ {
698
+ let sink = HookSink {
699
+ bridge: Transport::Threaded(call),
700
+ names: RefCell::new(HashMap::new()),
701
+ error: RefCell::new(None),
702
+ options: options.clone(),
703
+ fragment: !is_document,
704
+ source: String::new(),
705
+ context: Some(context.clone()),
706
+ diagnostics: RefCell::new(Vec::new()),
707
+ locations: (options.get("sourceCodeLocationInfo") == Some(&Value::Bool(true)))
708
+ .then(|| crate::tracker::Locations::new(context.clone())),
709
+ };
710
+ sink.call(
711
+ if is_document {
712
+ "createDocument"
713
+ } else {
714
+ "createDocumentFragment"
715
+ },
716
+ json!([]),
717
+ );
718
+ crate::diagnostics::parse_reader_with_sink(
719
+ reader,
720
+ is_document,
721
+ &options,
722
+ sink,
723
+ context,
724
+ |_, _| {},
725
+ )
726
+ .map_err(|error| Error::new(Status::GenericFailure, error.to_string()))?
727
+ }