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,1848 @@
1
+ // Copyright 2014-2017 The html5ever Project Developers. See the
2
+ // COPYRIGHT file at the top-level directory of this distribution.
3
+ //
4
+ // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5
+ // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6
+ // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7
+ // option. This file may not be copied, modified, or distributed
8
+ // except according to those terms.
9
+
10
+ //! The HTML5 tree builder.
11
+
12
+ pub use crate::interface::{create_element, ElemName, ElementFlags, Tracer, TreeSink};
13
+ pub use crate::interface::{AppendNode, AppendText, Attribute, NodeOrText};
14
+ pub use crate::interface::{LimitedQuirks, NoQuirks, Quirks, QuirksMode};
15
+
16
+ use self::types::*;
17
+
18
+ use crate::tendril::StrTendril;
19
+ use crate::{ExpandedName, LocalName, Namespace, QualName};
20
+
21
+ use crate::tokenizer;
22
+ use crate::tokenizer::states as tok_state;
23
+ use crate::tokenizer::{Doctype, EndTag, StartTag, Tag, TokenSink, TokenSinkResult};
24
+
25
+ use std::borrow::Cow::{self, Borrowed};
26
+ use std::cell::{Cell, Ref, RefCell};
27
+ use std::collections::VecDeque;
28
+ use std::iter::{Enumerate, Rev};
29
+ use std::{fmt, slice};
30
+
31
+ use crate::tokenizer::states::RawKind;
32
+ use crate::tree_builder::tag_sets::*;
33
+ use crate::util::str::to_escaped_string;
34
+ use log::{debug, log_enabled, warn, Level};
35
+ use markup5ever::{expanded_name, local_name, namespace_prefix, ns};
36
+
37
+ #[macro_use]
38
+ mod tag_sets;
39
+
40
+ mod data;
41
+ mod rules;
42
+ mod types;
43
+
44
+ /// Tree builder options, with an impl for Default.
45
+ #[derive(Copy, Clone)]
46
+ pub struct TreeBuilderOpts {
47
+ /// Report all parse errors described in the spec, at some
48
+ /// performance penalty? Default: false
49
+ pub exact_errors: bool,
50
+
51
+ /// Is scripting enabled?
52
+ ///
53
+ /// This affects how `<noscript>` elements are parsed:
54
+ /// - If scripting **is** enabled then the contents of a `<noscript>` element are parsed as a single text node
55
+ /// - If scriping is **not** enabled then the contents of a `<noscript>` element are parsed as a normal tree of nodes
56
+ pub scripting_enabled: bool,
57
+
58
+ /// Is this document being parsed from the `srcdoc` attribute of an `<iframe>` element?
59
+ ///
60
+ /// This affects heuristics that infer `QuirksMode` from `<!DOCTYPE>`.
61
+ pub iframe_srcdoc: bool,
62
+
63
+ /// Should we drop the DOCTYPE (if any) from the tree?
64
+ pub drop_doctype: bool,
65
+
66
+ /// Initial TreeBuilder quirks mode. Default: NoQuirks
67
+ pub quirks_mode: QuirksMode,
68
+ }
69
+
70
+ impl Default for TreeBuilderOpts {
71
+ fn default() -> TreeBuilderOpts {
72
+ TreeBuilderOpts {
73
+ exact_errors: false,
74
+ scripting_enabled: true,
75
+ iframe_srcdoc: false,
76
+ drop_doctype: false,
77
+ quirks_mode: NoQuirks,
78
+ }
79
+ }
80
+ }
81
+
82
+ /// The HTML tree builder.
83
+ pub struct TreeBuilder<Handle, Sink> {
84
+ /// Options controlling the behavior of the tree builder.
85
+ opts: TreeBuilderOpts,
86
+
87
+ /// Consumer of tree modifications.
88
+ pub sink: Sink,
89
+
90
+ /// Insertion mode.
91
+ mode: Cell<InsertionMode>,
92
+
93
+ /// Original insertion mode, used by Text and InTableText modes.
94
+ orig_mode: Cell<Option<InsertionMode>>,
95
+
96
+ /// Stack of template insertion modes.
97
+ template_modes: RefCell<Vec<InsertionMode>>,
98
+
99
+ /// Pending table character tokens.
100
+ pending_table_text: RefCell<Vec<(SplitStatus, StrTendril)>>,
101
+
102
+ /// Quirks mode as set by the parser.
103
+ /// FIXME: can scripts etc. change this?
104
+ quirks_mode: Cell<QuirksMode>,
105
+
106
+ /// The document node, which is created by the sink.
107
+ doc_handle: Handle,
108
+
109
+ /// Stack of open elements, most recently added at end.
110
+ open_elems: RefCell<Vec<Handle>>,
111
+
112
+ /// List of active formatting elements.
113
+ active_formatting: RefCell<Vec<FormatEntry<Handle>>>,
114
+
115
+ //§ the-element-pointers
116
+ /// Head element pointer.
117
+ head_elem: RefCell<Option<Handle>>,
118
+
119
+ /// Form element pointer.
120
+ form_elem: RefCell<Option<Handle>>,
121
+ //§ END
122
+ /// Frameset-ok flag.
123
+ frameset_ok: Cell<bool>,
124
+
125
+ /// Ignore a following U+000A LINE FEED?
126
+ ignore_lf: Cell<bool>,
127
+
128
+ /// Is foster parenting enabled?
129
+ foster_parenting: Cell<bool>,
130
+
131
+ /// The context element for the fragment parsing algorithm.
132
+ context_elem: RefCell<Option<Handle>>,
133
+
134
+ /// Track current line
135
+ current_line: Cell<u64>,
136
+ // WARNING: If you add new fields that contain Handles, you
137
+ // must add them to trace_handles() below to preserve memory
138
+ // safety!
139
+ //
140
+ // FIXME: Auto-generate the trace hooks like Servo does.
141
+ }
142
+
143
+ impl<Handle, Sink> TreeBuilder<Handle, Sink>
144
+ where
145
+ Handle: Clone,
146
+ Sink: TreeSink<Handle = Handle>,
147
+ {
148
+ /// Create a new tree builder which sends tree modifications to a particular `TreeSink`.
149
+ ///
150
+ /// The tree builder is also a `TokenSink`.
151
+ pub fn new(sink: Sink, opts: TreeBuilderOpts) -> TreeBuilder<Handle, Sink> {
152
+ let doc_handle = sink.get_document();
153
+ TreeBuilder {
154
+ opts,
155
+ sink,
156
+ mode: Cell::new(InsertionMode::Initial),
157
+ orig_mode: Cell::new(None),
158
+ template_modes: Default::default(),
159
+ pending_table_text: Default::default(),
160
+ quirks_mode: Cell::new(opts.quirks_mode),
161
+ doc_handle,
162
+ open_elems: Default::default(),
163
+ active_formatting: Default::default(),
164
+ head_elem: Default::default(),
165
+ form_elem: Default::default(),
166
+ frameset_ok: Cell::new(true),
167
+ ignore_lf: Default::default(),
168
+ foster_parenting: Default::default(),
169
+ context_elem: Default::default(),
170
+ current_line: Cell::new(1),
171
+ }
172
+ }
173
+
174
+ /// Create a new tree builder which sends tree modifications to a particular `TreeSink`.
175
+ /// This is for parsing fragments.
176
+ ///
177
+ /// The tree builder is also a `TokenSink`.
178
+ pub fn new_for_fragment(
179
+ sink: Sink,
180
+ context_elem: Handle,
181
+ form_elem: Option<Handle>,
182
+ opts: TreeBuilderOpts,
183
+ ) -> TreeBuilder<Handle, Sink> {
184
+ let doc_handle = sink.get_document();
185
+ let context_is_template =
186
+ sink.elem_name(&context_elem).expanded() == expanded_name!(html "template");
187
+ let template_modes = if context_is_template {
188
+ RefCell::new(vec![InsertionMode::InTemplate])
189
+ } else {
190
+ RefCell::new(vec![])
191
+ };
192
+
193
+ let tb = TreeBuilder {
194
+ opts,
195
+ sink,
196
+ mode: Cell::new(InsertionMode::Initial),
197
+ orig_mode: Cell::new(None),
198
+ template_modes,
199
+ pending_table_text: Default::default(),
200
+ quirks_mode: Cell::new(opts.quirks_mode),
201
+ doc_handle,
202
+ open_elems: Default::default(),
203
+ active_formatting: Default::default(),
204
+ head_elem: Default::default(),
205
+ form_elem: RefCell::new(form_elem),
206
+ frameset_ok: Cell::new(true),
207
+ ignore_lf: Default::default(),
208
+ foster_parenting: Default::default(),
209
+ context_elem: RefCell::new(Some(context_elem)),
210
+ current_line: Cell::new(1),
211
+ };
212
+
213
+ // https://html.spec.whatwg.org/multipage/#parsing-html-fragments
214
+ // 5. Let root be a new html element with no attributes.
215
+ // 6. Append the element root to the Document node created above.
216
+ // 7. Set up the parser's stack of open elements so that it contains just the single element root.
217
+ tb.create_root(vec![]);
218
+ // 10. Reset the parser's insertion mode appropriately.
219
+ let old_insertion_mode = tb.reset_insertion_mode();
220
+ tb.mode.set(old_insertion_mode);
221
+
222
+ tb
223
+ }
224
+
225
+ // https://html.spec.whatwg.org/multipage/#concept-frag-parse-context
226
+ // Step 4. Set the state of the HTML parser's tokenization stage as follows:
227
+ pub fn tokenizer_state_for_context_elem(
228
+ &self,
229
+ context_element_allows_scripting: bool,
230
+ ) -> tok_state::State {
231
+ let context_elem = self.context_elem.borrow();
232
+ let elem = context_elem.as_ref().expect("no context element");
233
+ let elem_name = self.sink.elem_name(elem);
234
+ let name = match elem_name.expanded() {
235
+ ExpandedName {
236
+ ns: &ns!(html),
237
+ local,
238
+ } => local,
239
+ _ => return tok_state::Data,
240
+ };
241
+ match *name {
242
+ local_name!("title") | local_name!("textarea") => tok_state::RawData(tok_state::Rcdata),
243
+
244
+ local_name!("style")
245
+ | local_name!("xmp")
246
+ | local_name!("iframe")
247
+ | local_name!("noembed")
248
+ | local_name!("noframes") => tok_state::RawData(tok_state::Rawtext),
249
+
250
+ local_name!("script") => tok_state::RawData(tok_state::ScriptData),
251
+
252
+ local_name!("noscript") => {
253
+ if context_element_allows_scripting {
254
+ tok_state::RawData(tok_state::Rawtext)
255
+ } else {
256
+ tok_state::Data
257
+ }
258
+ },
259
+
260
+ local_name!("plaintext") => tok_state::Plaintext,
261
+
262
+ _ => tok_state::Data,
263
+ }
264
+ }
265
+
266
+ /// Call the `Tracer`'s `trace_handle` method on every `Handle` in the tree builder's
267
+ /// internal state. This is intended to support garbage-collected DOMs.
268
+ pub fn trace_handles(&self, tracer: &dyn Tracer<Handle = Handle>) {
269
+ tracer.trace_handle(&self.doc_handle);
270
+ for e in &*self.open_elems.borrow() {
271
+ tracer.trace_handle(e);
272
+ }
273
+
274
+ for e in &*self.active_formatting.borrow() {
275
+ if let FormatEntry::Element(handle, _) = e {
276
+ tracer.trace_handle(handle);
277
+ }
278
+ }
279
+
280
+ if let Some(head_elem) = self.head_elem.borrow().as_ref() {
281
+ tracer.trace_handle(head_elem);
282
+ }
283
+
284
+ if let Some(form_elem) = self.form_elem.borrow().as_ref() {
285
+ tracer.trace_handle(form_elem);
286
+ }
287
+
288
+ if let Some(context_elem) = self.context_elem.borrow().as_ref() {
289
+ tracer.trace_handle(context_elem);
290
+ }
291
+ }
292
+
293
+ #[allow(dead_code)]
294
+ fn dump_state(&self, label: String) {
295
+ println!("dump_state on {label}");
296
+ print!(" open_elems:");
297
+ for node in self.open_elems.borrow().iter() {
298
+ let name = self.sink.elem_name(node);
299
+ match *name.ns() {
300
+ ns!(html) => print!(" {}", name.local_name()),
301
+ _ => panic!(),
302
+ }
303
+ }
304
+ println!();
305
+ print!(" active_formatting:");
306
+ for entry in self.active_formatting.borrow().iter() {
307
+ match entry {
308
+ &FormatEntry::Marker => print!(" Marker"),
309
+ FormatEntry::Element(h, _) => {
310
+ let name = self.sink.elem_name(h);
311
+ match *name.ns() {
312
+ ns!(html) => print!(" {}", name.local_name()),
313
+ _ => panic!(),
314
+ }
315
+ },
316
+ }
317
+ }
318
+ println!();
319
+ }
320
+
321
+ fn debug_step(&self, mode: InsertionMode, token: &Token) {
322
+ if log_enabled!(Level::Debug) {
323
+ debug!(
324
+ "processing {} in insertion mode {:?}",
325
+ to_escaped_string(token),
326
+ mode
327
+ );
328
+ }
329
+ }
330
+
331
+ fn process_to_completion(&self, mut token: Token) -> TokenSinkResult<Handle> {
332
+ // Queue of additional tokens yet to be processed.
333
+ // This stays empty in the common case where we don't split whitespace.
334
+ let mut more_tokens = VecDeque::new();
335
+
336
+ loop {
337
+ let should_have_acknowledged_self_closing_flag = matches!(
338
+ token,
339
+ Token::Tag(Tag {
340
+ self_closing: true,
341
+ kind: StartTag,
342
+ ..
343
+ })
344
+ );
345
+ let result = if self.is_foreign(&token) {
346
+ self.step_foreign(token)
347
+ } else {
348
+ let mode = self.mode.get();
349
+ self.step(mode, token)
350
+ };
351
+ match result {
352
+ ProcessResult::Done => {
353
+ if should_have_acknowledged_self_closing_flag {
354
+ self.sink
355
+ .parse_error(Borrowed("Unacknowledged self-closing tag"));
356
+ }
357
+ let Some(new_token) = more_tokens.pop_front() else {
358
+ return tokenizer::TokenSinkResult::Continue;
359
+ };
360
+ token = new_token;
361
+ },
362
+ ProcessResult::DoneAckSelfClosing => {
363
+ let Some(new_token) = more_tokens.pop_front() else {
364
+ return tokenizer::TokenSinkResult::Continue;
365
+ };
366
+ token = new_token;
367
+ },
368
+ ProcessResult::Reprocess(m, t) => {
369
+ self.mode.set(m);
370
+ token = t;
371
+ },
372
+ ProcessResult::ReprocessForeign(t) => {
373
+ token = t;
374
+ },
375
+ ProcessResult::SplitWhitespace(mut buf) => {
376
+ let p = buf.pop_front_char_run(|c| c.is_ascii_whitespace());
377
+ let Some((first, is_ws)) = p else {
378
+ return tokenizer::TokenSinkResult::Continue;
379
+ };
380
+ let status = if is_ws {
381
+ SplitStatus::Whitespace
382
+ } else {
383
+ SplitStatus::NotWhitespace
384
+ };
385
+ token = Token::Characters(status, first);
386
+
387
+ if buf.len32() > 0 {
388
+ more_tokens.push_back(Token::Characters(SplitStatus::NotSplit, buf));
389
+ }
390
+ },
391
+ ProcessResult::Script(node) => {
392
+ assert!(more_tokens.is_empty());
393
+ return tokenizer::TokenSinkResult::Script(node);
394
+ },
395
+ ProcessResult::ToPlaintext => {
396
+ assert!(more_tokens.is_empty());
397
+ return tokenizer::TokenSinkResult::Plaintext;
398
+ },
399
+ ProcessResult::ToRawData(k) => {
400
+ assert!(more_tokens.is_empty());
401
+ return tokenizer::TokenSinkResult::RawData(k);
402
+ },
403
+ }
404
+ }
405
+ }
406
+
407
+ /// Are we parsing a HTML fragment?
408
+ pub fn is_fragment(&self) -> bool {
409
+ self.context_elem.borrow().is_some()
410
+ }
411
+
412
+ /// https://html.spec.whatwg.org/multipage/#appropriate-place-for-inserting-a-node
413
+ fn appropriate_place_for_insertion(
414
+ &self,
415
+ override_target: Option<Handle>,
416
+ ) -> InsertionPoint<Handle> {
417
+ use self::tag_sets::*;
418
+
419
+ declare_tag_set!(foster_target = "table" "tbody" "tfoot" "thead" "tr");
420
+ let target = override_target.unwrap_or_else(|| self.current_node().clone());
421
+ if !(self.foster_parenting.get() && self.elem_in(&target, foster_target)) {
422
+ if self.html_elem_named(&target, local_name!("template")) {
423
+ // No foster parenting (inside template).
424
+ let contents = self.sink.get_template_contents(&target);
425
+ return InsertionPoint::LastChild(contents);
426
+ } else {
427
+ // No foster parenting (the common case).
428
+ return InsertionPoint::LastChild(target);
429
+ }
430
+ }
431
+
432
+ // Foster parenting
433
+ let open_elems = self.open_elems.borrow();
434
+ let mut iter = open_elems.iter().rev().peekable();
435
+ while let Some(elem) = iter.next() {
436
+ if self.html_elem_named(elem, local_name!("template")) {
437
+ let contents = self.sink.get_template_contents(elem);
438
+ return InsertionPoint::LastChild(contents);
439
+ } else if self.html_elem_named(elem, local_name!("table")) {
440
+ return InsertionPoint::TableFosterParenting {
441
+ element: elem.clone(),
442
+ prev_element: (*iter.peek().unwrap()).clone(),
443
+ };
444
+ }
445
+ }
446
+ let html_elem = self.html_elem();
447
+ InsertionPoint::LastChild(html_elem.clone())
448
+ }
449
+
450
+ fn insert_at(&self, insertion_point: InsertionPoint<Handle>, child: NodeOrText<Handle>) {
451
+ match insertion_point {
452
+ InsertionPoint::LastChild(parent) => self.sink.append(&parent, child),
453
+ InsertionPoint::BeforeSibling(sibling) => {
454
+ self.sink.append_before_sibling(&sibling, child)
455
+ },
456
+ InsertionPoint::TableFosterParenting {
457
+ element,
458
+ prev_element,
459
+ } => self
460
+ .sink
461
+ .append_based_on_parent_node(&element, &prev_element, child),
462
+ }
463
+ }
464
+ }
465
+
466
+ impl<Handle, Sink> TokenSink for TreeBuilder<Handle, Sink>
467
+ where
468
+ Handle: Clone,
469
+ Sink: TreeSink<Handle = Handle>,
470
+ {
471
+ type Handle = Handle;
472
+
473
+ fn process_token(&self, token: tokenizer::Token, line_number: u64) -> TokenSinkResult<Handle> {
474
+ if line_number != self.current_line.get() {
475
+ self.sink.set_current_line(line_number);
476
+ }
477
+ let ignore_lf = self.ignore_lf.take();
478
+
479
+ // Handle `ParseError` and `DoctypeToken`; convert everything else to the local `Token` type.
480
+ let token = match token {
481
+ tokenizer::ParseError(e) => {
482
+ self.sink.parse_error(e);
483
+ return tokenizer::TokenSinkResult::Continue;
484
+ },
485
+
486
+ tokenizer::DoctypeToken(dt) => {
487
+ if self.mode.get() == InsertionMode::Initial {
488
+ let (err, quirk) = data::doctype_error_and_quirks(&dt, self.opts.iframe_srcdoc);
489
+ if err {
490
+ self.sink.parse_error(if self.opts.exact_errors {
491
+ Cow::from(format!("Bad DOCTYPE: {dt:?}"))
492
+ } else {
493
+ Cow::from("Bad DOCTYPE")
494
+ });
495
+ }
496
+ let Doctype {
497
+ name,
498
+ public_id,
499
+ system_id,
500
+ force_quirks: _,
501
+ } = dt;
502
+ if !self.opts.drop_doctype {
503
+ self.sink.append_doctype_to_document(
504
+ name.unwrap_or(StrTendril::new()),
505
+ public_id.unwrap_or(StrTendril::new()),
506
+ system_id.unwrap_or(StrTendril::new()),
507
+ );
508
+ }
509
+ self.set_quirks_mode(quirk);
510
+
511
+ self.mode.set(InsertionMode::BeforeHtml);
512
+ return tokenizer::TokenSinkResult::Continue;
513
+ } else {
514
+ self.sink.parse_error(if self.opts.exact_errors {
515
+ Cow::from(format!("DOCTYPE in insertion mode {:?}", self.mode.get()))
516
+ } else {
517
+ Cow::from("DOCTYPE in body")
518
+ });
519
+ return tokenizer::TokenSinkResult::Continue;
520
+ }
521
+ },
522
+
523
+ tokenizer::TagToken(x) => Token::Tag(x),
524
+ tokenizer::CommentToken(x) => Token::Comment(x),
525
+ tokenizer::NullCharacterToken => Token::NullCharacter,
526
+ tokenizer::EOFToken => Token::Eof,
527
+
528
+ tokenizer::CharacterTokens(mut x) => {
529
+ if ignore_lf && x.starts_with("\n") {
530
+ x.pop_front(1);
531
+ }
532
+ if x.is_empty() {
533
+ return tokenizer::TokenSinkResult::Continue;
534
+ }
535
+ Token::Characters(SplitStatus::NotSplit, x)
536
+ },
537
+ };
538
+
539
+ self.process_to_completion(token)
540
+ }
541
+
542
+ fn end(&self) {
543
+ for elem in self.open_elems.borrow_mut().drain(..).rev() {
544
+ self.sink.pop(&elem);
545
+ }
546
+ }
547
+
548
+ fn adjusted_current_node_present_but_not_in_html_namespace(&self) -> bool {
549
+ !self.open_elems.borrow().is_empty()
550
+ && *self.sink.elem_name(&self.adjusted_current_node()).ns() != ns!(html)
551
+ }
552
+ }
553
+
554
+ pub fn html_elem<Handle>(open_elems: &[Handle]) -> &Handle {
555
+ &open_elems[0]
556
+ }
557
+
558
+ struct ActiveFormattingView<'a, Handle: 'a> {
559
+ data: Ref<'a, Vec<FormatEntry<Handle>>>,
560
+ }
561
+
562
+ impl<'a, Handle: 'a> ActiveFormattingView<'a, Handle> {
563
+ fn iter(&'a self) -> impl Iterator<Item = (usize, &'a Handle, &'a Tag)> + 'a {
564
+ ActiveFormattingIter {
565
+ iter: self.data.iter().enumerate().rev(),
566
+ }
567
+ }
568
+ }
569
+
570
+ pub struct ActiveFormattingIter<'a, Handle: 'a> {
571
+ iter: Rev<Enumerate<slice::Iter<'a, FormatEntry<Handle>>>>,
572
+ }
573
+
574
+ impl<'a, Handle> Iterator for ActiveFormattingIter<'a, Handle> {
575
+ type Item = (usize, &'a Handle, &'a Tag);
576
+ fn next(&mut self) -> Option<(usize, &'a Handle, &'a Tag)> {
577
+ match self.iter.next() {
578
+ None | Some((_, &FormatEntry::Marker)) => None,
579
+ Some((i, FormatEntry::Element(h, t))) => Some((i, h, t)),
580
+ }
581
+ }
582
+ }
583
+
584
+ pub enum PushFlag {
585
+ Push,
586
+ NoPush,
587
+ }
588
+
589
+ enum Bookmark<Handle> {
590
+ Replace(Handle),
591
+ InsertAfter(Handle),
592
+ }
593
+
594
+ macro_rules! qualname {
595
+ ("", $local:tt) => {
596
+ QualName {
597
+ prefix: None,
598
+ ns: ns!(),
599
+ local: local_name!($local),
600
+ }
601
+ };
602
+ ($prefix: tt $ns:tt $local:tt) => {
603
+ QualName {
604
+ prefix: Some(namespace_prefix!($prefix)),
605
+ ns: ns!($ns),
606
+ local: local_name!($local),
607
+ }
608
+ };
609
+ }
610
+
611
+ #[doc(hidden)]
612
+ impl<Handle, Sink> TreeBuilder<Handle, Sink>
613
+ where
614
+ Handle: Clone,
615
+ Sink: TreeSink<Handle = Handle>,
616
+ {
617
+ fn unexpected<T: fmt::Debug>(&self, _thing: &T) -> ProcessResult<Handle> {
618
+ self.sink.parse_error(if self.opts.exact_errors {
619
+ Cow::from(format!(
620
+ "Unexpected token {} in insertion mode {:?}",
621
+ to_escaped_string(_thing),
622
+ self.mode.get()
623
+ ))
624
+ } else {
625
+ Cow::from("Unexpected token")
626
+ });
627
+ ProcessResult::Done
628
+ }
629
+
630
+ fn assert_named(&self, node: &Handle, name: LocalName) {
631
+ assert!(self.html_elem_named(node, name));
632
+ }
633
+
634
+ /// Iterate over the active formatting elements (with index in the list) from the end
635
+ /// to the last marker, or the beginning if there are no markers.
636
+ fn active_formatting_end_to_marker(&self) -> ActiveFormattingView<'_, Handle> {
637
+ ActiveFormattingView {
638
+ data: self.active_formatting.borrow(),
639
+ }
640
+ }
641
+
642
+ fn position_in_active_formatting(&self, element: &Handle) -> Option<usize> {
643
+ self.active_formatting
644
+ .borrow()
645
+ .iter()
646
+ .position(|n| match n {
647
+ FormatEntry::Marker => false,
648
+ FormatEntry::Element(ref handle, _) => self.sink.same_node(handle, element),
649
+ })
650
+ }
651
+
652
+ fn set_quirks_mode(&self, mode: QuirksMode) {
653
+ self.quirks_mode.set(mode);
654
+ self.sink.set_quirks_mode(mode);
655
+ }
656
+
657
+ fn stop_parsing(&self) -> ProcessResult<Handle> {
658
+ ProcessResult::Done
659
+ }
660
+
661
+ //§ parsing-elements-that-contain-only-text
662
+ // Switch to `Text` insertion mode, save the old mode, and
663
+ // switch the tokenizer to a raw-data state.
664
+ // The latter only takes effect after the current / next
665
+ // `process_token` of a start tag returns!
666
+ fn to_raw_text_mode(&self, k: RawKind) -> ProcessResult<Handle> {
667
+ self.orig_mode.set(Some(self.mode.get()));
668
+ self.mode.set(InsertionMode::Text);
669
+ ProcessResult::ToRawData(k)
670
+ }
671
+
672
+ // The generic raw text / RCDATA parsing algorithm.
673
+ fn parse_raw_data(&self, tag: Tag, k: RawKind) -> ProcessResult<Handle> {
674
+ self.insert_element_for(tag);
675
+ self.to_raw_text_mode(k)
676
+ }
677
+ //§ END
678
+
679
+ fn current_node(&self) -> Ref<'_, Handle> {
680
+ Ref::map(self.open_elems.borrow(), |elems| {
681
+ elems.last().expect("no current element")
682
+ })
683
+ }
684
+
685
+ fn adjusted_current_node(&self) -> Ref<'_, Handle> {
686
+ if self.open_elems.borrow().len() == 1 {
687
+ let context_elem = self.context_elem.borrow();
688
+ let ctx = Ref::filter_map(context_elem, |e| e.as_ref());
689
+ if let Ok(ctx) = ctx {
690
+ return ctx;
691
+ }
692
+ }
693
+ self.current_node()
694
+ }
695
+
696
+ fn current_node_in<TagSet>(&self, set: TagSet) -> bool
697
+ where
698
+ TagSet: Fn(ExpandedName) -> bool,
699
+ {
700
+ set(self.sink.elem_name(&self.current_node()).expanded())
701
+ }
702
+
703
+ // Insert at the "appropriate place for inserting a node".
704
+ fn insert_appropriately(&self, child: NodeOrText<Handle>, override_target: Option<Handle>) {
705
+ let insertion_point = self.appropriate_place_for_insertion(override_target);
706
+ self.insert_at(insertion_point, child);
707
+ }
708
+
709
+ fn adoption_agency(&self, subject: LocalName) {
710
+ // 1.
711
+ if self.current_node_named(subject.clone())
712
+ && self
713
+ .position_in_active_formatting(&self.current_node())
714
+ .is_none()
715
+ {
716
+ self.pop();
717
+ return;
718
+ }
719
+
720
+ // 2. 3. 4.
721
+ for _ in 0..8 {
722
+ // 5.
723
+ // We clone the Handle and Tag so they don't cause an immutable borrow of self.
724
+ let maybe_fmt_entry = self
725
+ .active_formatting_end_to_marker()
726
+ .iter()
727
+ .find(|&(_, _, tag)| tag.name == subject)
728
+ .map(|(i, h, t)| (i, h.clone(), t.clone()));
729
+
730
+ let Some((fmt_elem_index, fmt_elem, fmt_elem_tag)) = maybe_fmt_entry else {
731
+ return self.process_end_tag_in_body(Tag {
732
+ kind: EndTag,
733
+ name: subject,
734
+ self_closing: false,
735
+ attrs: vec![],
736
+ });
737
+ };
738
+
739
+ let Some(fmt_elem_stack_index) = self
740
+ .open_elems
741
+ .borrow()
742
+ .iter()
743
+ .rposition(|n| self.sink.same_node(n, &fmt_elem))
744
+ else {
745
+ self.sink
746
+ .parse_error(Borrowed("Formatting element not open"));
747
+ self.active_formatting.borrow_mut().remove(fmt_elem_index);
748
+ return;
749
+ };
750
+
751
+ // 7.
752
+ if !self.in_scope(default_scope, |n| self.sink.same_node(&n, &fmt_elem)) {
753
+ self.sink
754
+ .parse_error(Borrowed("Formatting element not in scope"));
755
+ return;
756
+ }
757
+
758
+ // 8.
759
+ if !self.sink.same_node(&self.current_node(), &fmt_elem) {
760
+ self.sink
761
+ .parse_error(Borrowed("Formatting element not current node"));
762
+ }
763
+
764
+ // 9.
765
+ let maybe_furthest_block = self
766
+ .open_elems
767
+ .borrow()
768
+ .iter()
769
+ .enumerate()
770
+ .skip(fmt_elem_stack_index)
771
+ .find(|&(_, open_element)| self.elem_in(open_element, special_tag))
772
+ .map(|(i, h)| (i, h.clone()));
773
+
774
+ let Some((furthest_block_index, furthest_block)) = maybe_furthest_block else {
775
+ // 10.
776
+ self.truncate_stack(fmt_elem_stack_index);
777
+ self.active_formatting.borrow_mut().remove(fmt_elem_index);
778
+ return;
779
+ };
780
+
781
+ // 11.
782
+ let common_ancestor = self.open_elems.borrow()[fmt_elem_stack_index - 1].clone();
783
+
784
+ // 12.
785
+ let mut bookmark = Bookmark::Replace(fmt_elem.clone());
786
+
787
+ // 13.
788
+ let mut node;
789
+ let mut node_index = furthest_block_index;
790
+ let mut last_node = furthest_block.clone();
791
+
792
+ // 13.1.
793
+ let mut inner_counter = 0;
794
+ loop {
795
+ // 13.2.
796
+ inner_counter += 1;
797
+
798
+ // 13.3.
799
+ node_index -= 1;
800
+ node = self.open_elems.borrow()[node_index].clone();
801
+
802
+ // 13.4.
803
+ if self.sink.same_node(&node, &fmt_elem) {
804
+ break;
805
+ }
806
+
807
+ // 13.5.
808
+ if inner_counter > 3 {
809
+ self.position_in_active_formatting(&node)
810
+ .map(|position| self.active_formatting.borrow_mut().remove(position));
811
+ self.remove_from_stack(&node);
812
+ continue;
813
+ }
814
+
815
+ let Some(node_formatting_index) = self.position_in_active_formatting(&node) else {
816
+ // 13.6.
817
+ self.remove_from_stack(&node);
818
+ continue;
819
+ };
820
+
821
+ // 13.7.
822
+ let tag = match self.active_formatting.borrow()[node_formatting_index] {
823
+ FormatEntry::Element(ref h, ref t) => {
824
+ assert!(self.sink.same_node(h, &node));
825
+ t.clone()
826
+ },
827
+ FormatEntry::Marker => panic!("Found marker during adoption agency"),
828
+ };
829
+ // FIXME: Is there a way to avoid cloning the attributes twice here (once on their
830
+ // own, once as part of t.clone() above)?
831
+ let new_element = create_element(
832
+ &self.sink,
833
+ QualName::new(None, ns!(html), tag.name.clone()),
834
+ tag.attrs.clone(),
835
+ );
836
+ self.open_elems.borrow_mut()[node_index] = new_element.clone();
837
+ self.active_formatting.borrow_mut()[node_formatting_index] =
838
+ FormatEntry::Element(new_element.clone(), tag);
839
+ node = new_element;
840
+
841
+ // 13.8.
842
+ if self.sink.same_node(&last_node, &furthest_block) {
843
+ bookmark = Bookmark::InsertAfter(node.clone());
844
+ }
845
+
846
+ // 13.9.
847
+ self.sink.remove_from_parent(&last_node);
848
+ self.sink.append(&node, AppendNode(last_node.clone()));
849
+
850
+ // 13.10.
851
+ last_node = node.clone();
852
+
853
+ // 13.11.
854
+ }
855
+
856
+ // 14.
857
+ self.sink.remove_from_parent(&last_node);
858
+ self.insert_appropriately(AppendNode(last_node.clone()), Some(common_ancestor));
859
+
860
+ // 15.
861
+ // FIXME: Is there a way to avoid cloning the attributes twice here (once on their own,
862
+ // once as part of t.clone() above)?
863
+ let new_element = create_element(
864
+ &self.sink,
865
+ QualName::new(None, ns!(html), fmt_elem_tag.name.clone()),
866
+ fmt_elem_tag.attrs.clone(),
867
+ );
868
+ let new_entry = FormatEntry::Element(new_element.clone(), fmt_elem_tag);
869
+
870
+ // 16.
871
+ self.sink.reparent_children(&furthest_block, &new_element);
872
+
873
+ // 17.
874
+ self.sink
875
+ .append(&furthest_block, AppendNode(new_element.clone()));
876
+
877
+ // 18.
878
+ // FIXME: We could probably get rid of the position_in_active_formatting() calls here
879
+ // if we had a more clever Bookmark representation.
880
+ match bookmark {
881
+ Bookmark::Replace(to_replace) => {
882
+ let index = self
883
+ .position_in_active_formatting(&to_replace)
884
+ .expect("bookmark not found in active formatting elements");
885
+ self.active_formatting.borrow_mut()[index] = new_entry;
886
+ },
887
+ Bookmark::InsertAfter(previous) => {
888
+ let index = self
889
+ .position_in_active_formatting(&previous)
890
+ .expect("bookmark not found in active formatting elements")
891
+ + 1;
892
+ self.active_formatting.borrow_mut().insert(index, new_entry);
893
+ let old_index = self
894
+ .position_in_active_formatting(&fmt_elem)
895
+ .expect("formatting element not found in active formatting elements");
896
+ self.active_formatting.borrow_mut().remove(old_index);
897
+ },
898
+ }
899
+
900
+ // 19.
901
+ self.remove_from_stack(&fmt_elem);
902
+ let new_furthest_block_index = self
903
+ .open_elems
904
+ .borrow()
905
+ .iter()
906
+ .position(|n| self.sink.same_node(n, &furthest_block))
907
+ .expect("furthest block missing from open element stack");
908
+ self.open_elems
909
+ .borrow_mut()
910
+ .insert(new_furthest_block_index + 1, new_element);
911
+ self.sink.on_stack_push(&self.current_node());
912
+
913
+ // 20.
914
+ }
915
+ }
916
+
917
+ fn push(&self, elem: &Handle) {
918
+ self.open_elems.borrow_mut().push(elem.clone());
919
+ self.sink.on_stack_push(elem);
920
+ }
921
+
922
+ fn truncate_stack(&self, length: usize) {
923
+ while self.open_elems.borrow().len() > length { self.pop(); }
924
+ }
925
+
926
+ fn pop(&self) -> Handle {
927
+ let elem = self
928
+ .open_elems
929
+ .borrow_mut()
930
+ .pop()
931
+ .expect("no current element");
932
+ self.sink.pop(&elem);
933
+ let current = self.open_elems.borrow().last().cloned();
934
+ self.sink.on_stack_pop(&elem, current.as_ref());
935
+ elem
936
+ }
937
+
938
+ fn remove_from_stack(&self, elem: &Handle) {
939
+ let position = self
940
+ .open_elems
941
+ .borrow()
942
+ .iter()
943
+ .rposition(|x| self.sink.same_node(elem, x));
944
+ if let Some(position) = position {
945
+ self.open_elems.borrow_mut().remove(position);
946
+ self.sink.pop(elem);
947
+ let current = self.open_elems.borrow().last().cloned();
948
+ self.sink.on_stack_pop(elem, current.as_ref());
949
+ }
950
+ }
951
+
952
+ fn is_marker_or_open(&self, entry: &FormatEntry<Handle>) -> bool {
953
+ match *entry {
954
+ FormatEntry::Marker => true,
955
+ FormatEntry::Element(ref node, _) => self
956
+ .open_elems
957
+ .borrow()
958
+ .iter()
959
+ .rev()
960
+ .any(|n| self.sink.same_node(n, node)),
961
+ }
962
+ }
963
+
964
+ /// <https://html.spec.whatwg.org/#reconstruct-the-active-formatting-elements>
965
+ fn reconstruct_active_formatting_elements(&self) {
966
+ {
967
+ let active_formatting = self.active_formatting.borrow();
968
+
969
+ // Step 1. If there are no entries in the list of active formatting elements,
970
+ // then there is nothing to reconstruct; stop this algorithm.
971
+ let Some(last) = active_formatting.last() else {
972
+ return;
973
+ };
974
+
975
+ // Step 2. If the last (most recently added) entry in the list of active formatting elements is a marker,
976
+ // or if it is an element that is in the stack of open elements, then there is nothing to reconstruct;
977
+ // stop this algorithm.
978
+ if self.is_marker_or_open(last) {
979
+ return;
980
+ }
981
+ }
982
+
983
+ // Step 3. Let entry be the last (most recently added) element in the list of active formatting elements.
984
+ // NOTE: We track the index of the element instead
985
+ let mut entry_index = self.active_formatting.borrow().len() - 1;
986
+ loop {
987
+ // Step 4. Rewind: If there are no entries before entry in the list of active formatting elements,
988
+ // then jump to the step labeled create.
989
+ if entry_index == 0 {
990
+ break;
991
+ }
992
+
993
+ // Step 5. Let entry be the entry one earlier than entry in the list of active formatting elements.
994
+ entry_index -= 1;
995
+
996
+ // Step 6. If entry is neither a marker nor an element that is also in the stack of open elements,
997
+ // go to the step labeled rewind.
998
+ // Step 7. Advance: Let entry be the element one later than entry in the list
999
+ // of active formatting elements.
1000
+ if self.is_marker_or_open(&self.active_formatting.borrow()[entry_index]) {
1001
+ entry_index += 1;
1002
+ break;
1003
+ }
1004
+ }
1005
+
1006
+ loop {
1007
+ // Step 8. Create: Insert an HTML element for the token for which the element entry was created,
1008
+ // to obtain new element.
1009
+ let tag = match self.active_formatting.borrow()[entry_index] {
1010
+ FormatEntry::Element(_, ref t) => t.clone(),
1011
+ FormatEntry::Marker => {
1012
+ panic!("Found marker during formatting element reconstruction")
1013
+ },
1014
+ };
1015
+
1016
+ // FIXME: Is there a way to avoid cloning the attributes twice here (once on their own,
1017
+ // once as part of t.clone() above)?
1018
+ let new_element = self.insert_element(
1019
+ PushFlag::Push,
1020
+ ns!(html),
1021
+ tag.name.clone(),
1022
+ tag.attrs.clone(),
1023
+ );
1024
+
1025
+ // Step 9. Replace the entry for entry in the list with an entry for new element.
1026
+ self.active_formatting.borrow_mut()[entry_index] =
1027
+ FormatEntry::Element(new_element, tag);
1028
+
1029
+ // Step 10. If the entry for new element in the list of active formatting elements is
1030
+ // not the last entry in the list, return to the step labeled advance.
1031
+ if entry_index == self.active_formatting.borrow().len() - 1 {
1032
+ break;
1033
+ }
1034
+ entry_index += 1;
1035
+ }
1036
+ }
1037
+
1038
+ /// Get the first element on the stack, which will be the <html> element.
1039
+ fn html_elem(&self) -> Ref<'_, Handle> {
1040
+ Ref::map(self.open_elems.borrow(), |elems| &elems[0])
1041
+ }
1042
+
1043
+ /// Get the second element on the stack, if it's a HTML body element.
1044
+ fn body_elem(&self) -> Option<Ref<'_, Handle>> {
1045
+ if self.open_elems.borrow().len() <= 1 {
1046
+ return None;
1047
+ }
1048
+
1049
+ let node = Ref::map(self.open_elems.borrow(), |elems| &elems[1]);
1050
+ if self.html_elem_named(&node, local_name!("body")) {
1051
+ Some(node)
1052
+ } else {
1053
+ None
1054
+ }
1055
+ }
1056
+
1057
+ /// Signal an error depending on the state of the stack of open elements at
1058
+ /// the end of the body.
1059
+ fn check_body_end(&self) {
1060
+ declare_tag_set!(body_end_ok =
1061
+ "dd" "dt" "li" "optgroup" "option" "p" "rp" "rt" "tbody" "td" "tfoot" "th"
1062
+ "thead" "tr" "body" "html");
1063
+
1064
+ for elem in self.open_elems.borrow().iter() {
1065
+ let error = {
1066
+ let elem_name = self.sink.elem_name(elem);
1067
+ let name = elem_name.expanded();
1068
+ if body_end_ok(name) {
1069
+ continue;
1070
+ }
1071
+
1072
+ if self.opts.exact_errors {
1073
+ Cow::from(format!("Unexpected open tag {name:?} at end of body"))
1074
+ } else {
1075
+ Cow::from("Unexpected open tag at end of body")
1076
+ }
1077
+ };
1078
+ self.sink.parse_error(error);
1079
+ // FIXME: Do we keep checking after finding one bad tag?
1080
+ // The spec suggests not.
1081
+ return;
1082
+ }
1083
+ }
1084
+
1085
+ fn in_scope<TagSet, Pred>(&self, scope: TagSet, pred: Pred) -> bool
1086
+ where
1087
+ TagSet: Fn(ExpandedName) -> bool,
1088
+ Pred: Fn(Handle) -> bool,
1089
+ {
1090
+ for node in self.open_elems.borrow().iter().rev() {
1091
+ if pred(node.clone()) {
1092
+ return true;
1093
+ }
1094
+ if scope(self.sink.elem_name(node).expanded()) {
1095
+ return false;
1096
+ }
1097
+ }
1098
+
1099
+ // supposed to be impossible, because <html> is always in scope
1100
+
1101
+ false
1102
+ }
1103
+
1104
+ fn elem_in<TagSet>(&self, elem: &Handle, set: TagSet) -> bool
1105
+ where
1106
+ TagSet: Fn(ExpandedName) -> bool,
1107
+ {
1108
+ set(self.sink.elem_name(elem).expanded())
1109
+ }
1110
+
1111
+ fn html_elem_named(&self, elem: &Handle, name: LocalName) -> bool {
1112
+ let elem_name = self.sink.elem_name(elem);
1113
+ *elem_name.ns() == ns!(html) && *elem_name.local_name() == name
1114
+ }
1115
+
1116
+ fn in_html_elem_named(&self, name: LocalName) -> bool {
1117
+ self.open_elems
1118
+ .borrow()
1119
+ .iter()
1120
+ .any(|elem| self.html_elem_named(elem, name.clone()))
1121
+ }
1122
+
1123
+ fn current_node_named(&self, name: LocalName) -> bool {
1124
+ self.html_elem_named(&self.current_node(), name)
1125
+ }
1126
+
1127
+ fn in_scope_named<TagSet>(&self, scope: TagSet, name: LocalName) -> bool
1128
+ where
1129
+ TagSet: Fn(ExpandedName) -> bool,
1130
+ {
1131
+ self.in_scope(scope, |elem| self.html_elem_named(&elem, name.clone()))
1132
+ }
1133
+
1134
+ /// <https://html.spec.whatwg.org/#generate-implied-end-tags>
1135
+ fn generate_implied_end_tags<TagSet>(&self, set: TagSet)
1136
+ where
1137
+ TagSet: Fn(ExpandedName) -> bool,
1138
+ {
1139
+ loop {
1140
+ {
1141
+ let open_elems = self.open_elems.borrow();
1142
+ let Some(elem) = open_elems.last() else {
1143
+ return;
1144
+ };
1145
+ let elem_name = self.sink.elem_name(elem);
1146
+ if !set(elem_name.expanded()) {
1147
+ return;
1148
+ }
1149
+ }
1150
+ self.pop();
1151
+ }
1152
+ }
1153
+
1154
+ fn generate_implied_end_except(&self, except: LocalName) {
1155
+ self.generate_implied_end_tags(|p| {
1156
+ if *p.ns == ns!(html) && *p.local == except {
1157
+ false
1158
+ } else {
1159
+ cursory_implied_end(p)
1160
+ }
1161
+ });
1162
+ }
1163
+ //§ END
1164
+
1165
+ // Pop elements until the current element is in the set.
1166
+ fn pop_until_current<TagSet>(&self, tag_set: TagSet)
1167
+ where
1168
+ TagSet: Fn(ExpandedName) -> bool,
1169
+ {
1170
+ while !self.current_node_in(&tag_set) {
1171
+ self.pop();
1172
+ }
1173
+ }
1174
+
1175
+ // Pop elements until an element from the set has been popped. Returns the
1176
+ // number of elements popped.
1177
+ fn pop_until<P>(&self, pred: P) -> usize
1178
+ where
1179
+ P: Fn(ExpandedName) -> bool,
1180
+ {
1181
+ let mut n = 0;
1182
+ loop {
1183
+ n += 1;
1184
+ if self.open_elems.borrow().is_empty() { break; }
1185
+ let elem = self.pop();
1186
+ if pred(self.sink.elem_name(&elem).expanded()) { break; }
1187
+ }
1188
+ n
1189
+ }
1190
+
1191
+ fn pop_until_named(&self, name: LocalName) -> usize {
1192
+ self.pop_until(|p| *p.ns == ns!(html) && *p.local == name)
1193
+ }
1194
+
1195
+ /// Pop elements until one with the specified name has been popped.
1196
+ /// Signal an error if it was not the first one.
1197
+ fn expect_to_close(&self, name: LocalName) {
1198
+ if self.pop_until_named(name.clone()) != 1 {
1199
+ self.sink.parse_error(if self.opts.exact_errors {
1200
+ Cow::from(format!("Unexpected open element while closing {name:?}"))
1201
+ } else {
1202
+ Cow::from("Unexpected open element")
1203
+ });
1204
+ }
1205
+ }
1206
+
1207
+ fn close_p_element(&self) {
1208
+ declare_tag_set!(implied = [cursory_implied_end] - "p");
1209
+ self.generate_implied_end_tags(implied);
1210
+ self.expect_to_close(local_name!("p"));
1211
+ }
1212
+
1213
+ fn close_p_element_in_button_scope(&self) {
1214
+ if self.in_scope_named(button_scope, local_name!("p")) {
1215
+ self.close_p_element();
1216
+ }
1217
+ }
1218
+
1219
+ // Check <input> tags for type=hidden
1220
+ fn is_type_hidden(&self, tag: &Tag) -> bool {
1221
+ match tag
1222
+ .attrs
1223
+ .iter()
1224
+ .find(|&at| at.name.expanded() == expanded_name!("", "type"))
1225
+ {
1226
+ None => false,
1227
+ Some(at) => at.value.eq_ignore_ascii_case("hidden"),
1228
+ }
1229
+ }
1230
+
1231
+ fn foster_parent_in_body(&self, token: Token) -> ProcessResult<Handle> {
1232
+ warn!("foster parenting not implemented");
1233
+ self.foster_parenting.set(true);
1234
+ let res = self.step(InsertionMode::InBody, token);
1235
+ // FIXME: what if res is Reprocess?
1236
+ self.foster_parenting.set(false);
1237
+ res
1238
+ }
1239
+
1240
+ fn process_chars_in_table(&self, token: Token) -> ProcessResult<Handle> {
1241
+ declare_tag_set!(table_outer = "table" "tbody" "tfoot" "thead" "tr");
1242
+ if self.current_node_in(table_outer) {
1243
+ assert!(self.pending_table_text.borrow().is_empty());
1244
+ self.orig_mode.set(Some(self.mode.get()));
1245
+ ProcessResult::Reprocess(InsertionMode::InTableText, token)
1246
+ } else {
1247
+ self.sink.parse_error(if self.opts.exact_errors {
1248
+ Cow::from(format!(
1249
+ "Unexpected characters {} in table",
1250
+ to_escaped_string(&token)
1251
+ ))
1252
+ } else {
1253
+ Cow::from("Unexpected characters in table")
1254
+ });
1255
+ self.foster_parent_in_body(token)
1256
+ }
1257
+ }
1258
+
1259
+ // https://html.spec.whatwg.org/multipage/#reset-the-insertion-mode-appropriately
1260
+ fn reset_insertion_mode(&self) -> InsertionMode {
1261
+ let open_elems = self.open_elems.borrow();
1262
+ for (i, mut node) in open_elems.iter().enumerate().rev() {
1263
+ let last = i == 0usize;
1264
+ let context_elem = self.context_elem.borrow();
1265
+ if let (true, Some(ctx)) = (last, context_elem.as_ref()) {
1266
+ node = ctx;
1267
+ }
1268
+ let elem_name = self.sink.elem_name(node);
1269
+ let name = match elem_name.expanded() {
1270
+ ExpandedName {
1271
+ ns: &ns!(html),
1272
+ local,
1273
+ } => local,
1274
+ _ => continue,
1275
+ };
1276
+ match *name {
1277
+ local_name!("select") => {
1278
+ for ancestor in self.open_elems.borrow()[0..i].iter().rev() {
1279
+ if self.html_elem_named(ancestor, local_name!("template")) {
1280
+ return InsertionMode::InSelect;
1281
+ } else if self.html_elem_named(ancestor, local_name!("table")) {
1282
+ return InsertionMode::InSelectInTable;
1283
+ }
1284
+ }
1285
+ return InsertionMode::InSelect;
1286
+ },
1287
+ local_name!("td") | local_name!("th") => {
1288
+ if !last {
1289
+ return InsertionMode::InCell;
1290
+ }
1291
+ },
1292
+ local_name!("tr") => return InsertionMode::InRow,
1293
+ local_name!("tbody") | local_name!("thead") | local_name!("tfoot") => {
1294
+ return InsertionMode::InTableBody;
1295
+ },
1296
+ local_name!("caption") => return InsertionMode::InCaption,
1297
+ local_name!("colgroup") => return InsertionMode::InColumnGroup,
1298
+ local_name!("table") => return InsertionMode::InTable,
1299
+ local_name!("template") => return *self.template_modes.borrow().last().unwrap(),
1300
+ local_name!("head") => {
1301
+ if !last {
1302
+ return InsertionMode::InHead;
1303
+ }
1304
+ },
1305
+ local_name!("body") => return InsertionMode::InBody,
1306
+ local_name!("frameset") => return InsertionMode::InFrameset,
1307
+ local_name!("html") => match *self.head_elem.borrow() {
1308
+ None => return InsertionMode::BeforeHead,
1309
+ Some(_) => return InsertionMode::AfterHead,
1310
+ },
1311
+
1312
+ _ => (),
1313
+ }
1314
+ }
1315
+ InsertionMode::InBody
1316
+ }
1317
+
1318
+ fn close_the_cell(&self) {
1319
+ self.generate_implied_end_tags(cursory_implied_end);
1320
+ if self.pop_until(td_th) != 1 {
1321
+ self.sink
1322
+ .parse_error(Borrowed("expected to close <td> or <th> with cell"));
1323
+ }
1324
+ self.clear_active_formatting_to_marker();
1325
+ }
1326
+
1327
+ fn append_text(&self, text: StrTendril) -> ProcessResult<Handle> {
1328
+ self.insert_appropriately(AppendText(text), None);
1329
+ ProcessResult::Done
1330
+ }
1331
+
1332
+ fn append_comment(&self, text: StrTendril) -> ProcessResult<Handle> {
1333
+ let comment = self.sink.create_comment(text);
1334
+ self.insert_appropriately(AppendNode(comment), None);
1335
+ ProcessResult::Done
1336
+ }
1337
+
1338
+ fn append_comment_to_doc(&self, text: StrTendril) -> ProcessResult<Handle> {
1339
+ let comment = self.sink.create_comment(text);
1340
+ self.sink.append(&self.doc_handle, AppendNode(comment));
1341
+ ProcessResult::Done
1342
+ }
1343
+
1344
+ fn append_comment_to_html(&self, text: StrTendril) -> ProcessResult<Handle> {
1345
+ let open_elems = self.open_elems.borrow();
1346
+ let target = html_elem(&open_elems);
1347
+ let comment = self.sink.create_comment(text);
1348
+ self.sink.append(target, AppendNode(comment));
1349
+ ProcessResult::Done
1350
+ }
1351
+
1352
+ //§ creating-and-inserting-nodes
1353
+ fn create_root(&self, attrs: Vec<Attribute>) {
1354
+ let elem = create_element(
1355
+ &self.sink,
1356
+ QualName::new(None, ns!(html), local_name!("html")),
1357
+ attrs,
1358
+ );
1359
+ self.push(&elem);
1360
+ self.sink.append(&self.doc_handle, AppendNode(elem));
1361
+ // FIXME: application cache selection algorithm
1362
+ }
1363
+
1364
+ // https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token
1365
+ fn insert_element(
1366
+ &self,
1367
+ push: PushFlag,
1368
+ ns: Namespace,
1369
+ name: LocalName,
1370
+ attrs: Vec<Attribute>,
1371
+ ) -> Handle {
1372
+ declare_tag_set!(form_associatable =
1373
+ "button" "fieldset" "input" "object"
1374
+ "output" "select" "textarea" "img");
1375
+
1376
+ declare_tag_set!(listed = [form_associatable] - "img");
1377
+
1378
+ // Step 7.
1379
+ let qname = QualName::new(None, ns, name);
1380
+ let elem = create_element(&self.sink, qname.clone(), attrs.clone());
1381
+
1382
+ let insertion_point = self.appropriate_place_for_insertion(None);
1383
+ let (node1, node2) = match insertion_point {
1384
+ InsertionPoint::LastChild(ref p) | InsertionPoint::BeforeSibling(ref p) => {
1385
+ (p.clone(), None)
1386
+ },
1387
+ InsertionPoint::TableFosterParenting {
1388
+ ref element,
1389
+ ref prev_element,
1390
+ } => (element.clone(), Some(prev_element.clone())),
1391
+ };
1392
+
1393
+ // Step 12.
1394
+ if form_associatable(qname.expanded())
1395
+ && self.form_elem.borrow().is_some()
1396
+ && !self.in_html_elem_named(local_name!("template"))
1397
+ && !(listed(qname.expanded())
1398
+ && attrs
1399
+ .iter()
1400
+ .any(|a| a.name.expanded() == expanded_name!("", "form")))
1401
+ {
1402
+ let form = self.form_elem.borrow().as_ref().unwrap().clone();
1403
+ self.sink
1404
+ .associate_with_form(&elem, &form, (&node1, node2.as_ref()));
1405
+ }
1406
+
1407
+ self.insert_at(insertion_point, AppendNode(elem.clone()));
1408
+
1409
+ match push {
1410
+ PushFlag::Push => self.push(&elem),
1411
+ PushFlag::NoPush => (),
1412
+ }
1413
+ // FIXME: Remove from the stack if we can't append?
1414
+ elem
1415
+ }
1416
+
1417
+ fn insert_element_for(&self, tag: Tag) -> Handle {
1418
+ self.insert_element(PushFlag::Push, ns!(html), tag.name, tag.attrs)
1419
+ }
1420
+
1421
+ fn insert_and_pop_element_for(&self, tag: Tag) -> Handle {
1422
+ self.insert_element(PushFlag::NoPush, ns!(html), tag.name, tag.attrs)
1423
+ }
1424
+
1425
+ fn insert_phantom(&self, name: LocalName) -> Handle {
1426
+ self.insert_element(PushFlag::Push, ns!(html), name, vec![])
1427
+ }
1428
+
1429
+ /// <https://html.spec.whatwg.org/multipage/parsing.html#insert-an-element-at-the-adjusted-insertion-location>
1430
+ fn insert_foreign_element(
1431
+ &self,
1432
+ tag: Tag,
1433
+ ns: Namespace,
1434
+ only_add_to_element_stack: bool,
1435
+ ) -> Handle {
1436
+ let adjusted_insertion_location = self.appropriate_place_for_insertion(None);
1437
+ let qname = QualName::new(None, ns, tag.name);
1438
+ let elem = create_element(&self.sink, qname.clone(), tag.attrs.clone());
1439
+
1440
+ if !only_add_to_element_stack {
1441
+ self.insert_at(adjusted_insertion_location, AppendNode(elem.clone()));
1442
+ }
1443
+
1444
+ self.push(&elem);
1445
+
1446
+ elem
1447
+ }
1448
+ //§ END
1449
+
1450
+ /// <https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead>
1451
+ ///
1452
+ /// A start tag whose tag name is "template"
1453
+ fn should_attach_declarative_shadow(&self, tag: &Tag) -> bool {
1454
+ let adjusted_insertion_location = self.appropriate_place_for_insertion(None);
1455
+
1456
+ let (intended_parent, _node2) = match adjusted_insertion_location {
1457
+ InsertionPoint::LastChild(ref p) | InsertionPoint::BeforeSibling(ref p) => {
1458
+ (p.clone(), None)
1459
+ },
1460
+ InsertionPoint::TableFosterParenting {
1461
+ ref element,
1462
+ ref prev_element,
1463
+ } => (element.clone(), Some(prev_element.clone())),
1464
+ };
1465
+
1466
+ // template start tag's shadowrootmode is not in the none state
1467
+ let is_shadow_root_mode = tag.attrs.iter().any(|attr| {
1468
+ attr.name.local == local_name!("shadowrootmode")
1469
+ && (attr.value.as_ref() == "open" || attr.value.as_ref() == "closed")
1470
+ });
1471
+
1472
+ // Check if intended_parent's document allows declarative shadow roots
1473
+ let allow_declarative_shadow_roots =
1474
+ self.sink.allow_declarative_shadow_roots(&intended_parent);
1475
+
1476
+ // the adjusted current node is not the topmost element in the stack of open elements
1477
+ let adjusted_current_node_not_topmost = match self.open_elems.borrow().first() {
1478
+ // The stack grows downwards; the topmost node on the stack is the first one added to the stack
1479
+ // The current node is the bottommost node in this stack of open elements.
1480
+ //
1481
+ // (1) The adjusted current node is the context element if the parser was created as part of the HTML fragment parsing algorithm
1482
+ // and the stack of open elements has only one element in it (fragment case);
1483
+ // (2) otherwise, the adjusted current node is the current node (the bottomost node)
1484
+ //
1485
+ // => adjusted current node != topmost element in the stack when the stack size > 1
1486
+ Some(_) => self.open_elems.borrow().len() > 1,
1487
+ None => true,
1488
+ };
1489
+
1490
+ is_shadow_root_mode && allow_declarative_shadow_roots && adjusted_current_node_not_topmost
1491
+ }
1492
+
1493
+ /// <https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead>
1494
+ ///
1495
+ /// A start tag whose tag name is "template"
1496
+ fn attach_declarative_shadow(
1497
+ &self,
1498
+ tag: &Tag,
1499
+ shadow_host: &Handle,
1500
+ template: &Handle,
1501
+ ) -> bool {
1502
+ self.sink
1503
+ .attach_declarative_shadow(shadow_host, template, &tag.attrs)
1504
+ }
1505
+
1506
+ fn create_formatting_element_for(&self, tag: Tag) -> Handle {
1507
+ // FIXME: This really wants unit tests.
1508
+ let mut first_match = None;
1509
+ let mut matches = 0usize;
1510
+ for (i, _, old_tag) in self.active_formatting_end_to_marker().iter() {
1511
+ if tag.equiv_modulo_attr_order(old_tag) {
1512
+ first_match = Some(i);
1513
+ matches += 1;
1514
+ }
1515
+ }
1516
+
1517
+ if matches >= 3 {
1518
+ self.active_formatting
1519
+ .borrow_mut()
1520
+ .remove(first_match.expect("matches with no index"));
1521
+ }
1522
+
1523
+ let elem = self.insert_element(
1524
+ PushFlag::Push,
1525
+ ns!(html),
1526
+ tag.name.clone(),
1527
+ tag.attrs.clone(),
1528
+ );
1529
+ self.active_formatting
1530
+ .borrow_mut()
1531
+ .push(FormatEntry::Element(elem.clone(), tag));
1532
+ elem
1533
+ }
1534
+
1535
+ fn clear_active_formatting_to_marker(&self) {
1536
+ loop {
1537
+ match self.active_formatting.borrow_mut().pop() {
1538
+ None | Some(FormatEntry::Marker) => break,
1539
+ _ => (),
1540
+ }
1541
+ }
1542
+ }
1543
+
1544
+ fn process_end_tag_in_body(&self, tag: Tag) {
1545
+ // Look back for a matching open element.
1546
+ let mut match_idx = None;
1547
+ for (i, elem) in self.open_elems.borrow().iter().enumerate().rev() {
1548
+ if self.html_elem_named(elem, tag.name.clone()) {
1549
+ match_idx = Some(i);
1550
+ break;
1551
+ }
1552
+
1553
+ if self.elem_in(elem, special_tag) {
1554
+ self.sink
1555
+ .parse_error(Borrowed("Found special tag while closing generic tag"));
1556
+ return;
1557
+ }
1558
+ }
1559
+
1560
+ let Some(match_idx) = match_idx else {
1561
+ // I believe this is impossible, because the root
1562
+ // <html> element is in special_tag.
1563
+ self.unexpected(&tag);
1564
+ return;
1565
+ };
1566
+
1567
+ self.generate_implied_end_except(tag.name.clone());
1568
+
1569
+ if match_idx != self.open_elems.borrow().len() - 1 {
1570
+ // mis-nested tags
1571
+ self.unexpected(&tag);
1572
+ }
1573
+ self.truncate_stack(match_idx);
1574
+ }
1575
+
1576
+ fn handle_misnested_a_tags(&self, tag: &Tag) {
1577
+ let Some(node) = self
1578
+ .active_formatting_end_to_marker()
1579
+ .iter()
1580
+ .find(|&(_, n, _)| self.html_elem_named(n, local_name!("a")))
1581
+ .map(|(_, n, _)| n.clone())
1582
+ else {
1583
+ return;
1584
+ };
1585
+
1586
+ self.unexpected(tag);
1587
+ self.adoption_agency(local_name!("a"));
1588
+ self.position_in_active_formatting(&node)
1589
+ .map(|index| self.active_formatting.borrow_mut().remove(index));
1590
+ self.remove_from_stack(&node);
1591
+ }
1592
+
1593
+ //§ tree-construction
1594
+ fn is_foreign(&self, token: &Token) -> bool {
1595
+ if let Token::Eof = *token {
1596
+ return false;
1597
+ }
1598
+
1599
+ if self.open_elems.borrow().is_empty() {
1600
+ return false;
1601
+ }
1602
+
1603
+ let current = self.adjusted_current_node();
1604
+ let elem_name = self.sink.elem_name(&current);
1605
+ let name = elem_name.expanded();
1606
+ if let ns!(html) = *name.ns {
1607
+ return false;
1608
+ }
1609
+
1610
+ if mathml_text_integration_point(name) {
1611
+ match *token {
1612
+ Token::Characters(..) | Token::NullCharacter => return false,
1613
+ Token::Tag(Tag {
1614
+ kind: StartTag,
1615
+ ref name,
1616
+ ..
1617
+ }) if !matches!(*name, local_name!("mglyph") | local_name!("malignmark")) => {
1618
+ return false;
1619
+ },
1620
+ _ => (),
1621
+ }
1622
+ }
1623
+
1624
+ if svg_html_integration_point(name) {
1625
+ match *token {
1626
+ Token::Characters(..) | Token::NullCharacter => return false,
1627
+ Token::Tag(Tag { kind: StartTag, .. }) => return false,
1628
+ _ => (),
1629
+ }
1630
+ }
1631
+
1632
+ if let expanded_name!(mathml "annotation-xml") = name {
1633
+ match *token {
1634
+ Token::Tag(Tag {
1635
+ kind: StartTag,
1636
+ name: local_name!("svg"),
1637
+ ..
1638
+ }) => return false,
1639
+ Token::Characters(..)
1640
+ | Token::NullCharacter
1641
+ | Token::Tag(Tag { kind: StartTag, .. }) => {
1642
+ return !self
1643
+ .sink
1644
+ .is_mathml_annotation_xml_integration_point(&self.adjusted_current_node());
1645
+ },
1646
+ _ => {},
1647
+ };
1648
+ }
1649
+
1650
+ true
1651
+ }
1652
+ //§ END
1653
+
1654
+ fn enter_foreign(&self, mut tag: Tag, ns: Namespace) -> ProcessResult<Handle> {
1655
+ match ns {
1656
+ ns!(mathml) => self.adjust_mathml_attributes(&mut tag),
1657
+ ns!(svg) => self.adjust_svg_attributes(&mut tag),
1658
+ _ => (),
1659
+ }
1660
+ self.adjust_foreign_attributes(&mut tag);
1661
+
1662
+ if tag.self_closing {
1663
+ self.insert_element(PushFlag::NoPush, ns, tag.name, tag.attrs);
1664
+ ProcessResult::DoneAckSelfClosing
1665
+ } else {
1666
+ self.insert_element(PushFlag::Push, ns, tag.name, tag.attrs);
1667
+ ProcessResult::Done
1668
+ }
1669
+ }
1670
+
1671
+ fn adjust_svg_tag_name(&self, tag: &mut Tag) {
1672
+ let Tag { ref mut name, .. } = *tag;
1673
+ match *name {
1674
+ local_name!("altglyph") => *name = local_name!("altGlyph"),
1675
+ local_name!("altglyphdef") => *name = local_name!("altGlyphDef"),
1676
+ local_name!("altglyphitem") => *name = local_name!("altGlyphItem"),
1677
+ local_name!("animatecolor") => *name = local_name!("animateColor"),
1678
+ local_name!("animatemotion") => *name = local_name!("animateMotion"),
1679
+ local_name!("animatetransform") => *name = local_name!("animateTransform"),
1680
+ local_name!("clippath") => *name = local_name!("clipPath"),
1681
+ local_name!("feblend") => *name = local_name!("feBlend"),
1682
+ local_name!("fecolormatrix") => *name = local_name!("feColorMatrix"),
1683
+ local_name!("fecomponenttransfer") => *name = local_name!("feComponentTransfer"),
1684
+ local_name!("fecomposite") => *name = local_name!("feComposite"),
1685
+ local_name!("feconvolvematrix") => *name = local_name!("feConvolveMatrix"),
1686
+ local_name!("fediffuselighting") => *name = local_name!("feDiffuseLighting"),
1687
+ local_name!("fedisplacementmap") => *name = local_name!("feDisplacementMap"),
1688
+ local_name!("fedistantlight") => *name = local_name!("feDistantLight"),
1689
+ local_name!("fedropshadow") => *name = local_name!("feDropShadow"),
1690
+ local_name!("feflood") => *name = local_name!("feFlood"),
1691
+ local_name!("fefunca") => *name = local_name!("feFuncA"),
1692
+ local_name!("fefuncb") => *name = local_name!("feFuncB"),
1693
+ local_name!("fefuncg") => *name = local_name!("feFuncG"),
1694
+ local_name!("fefuncr") => *name = local_name!("feFuncR"),
1695
+ local_name!("fegaussianblur") => *name = local_name!("feGaussianBlur"),
1696
+ local_name!("feimage") => *name = local_name!("feImage"),
1697
+ local_name!("femerge") => *name = local_name!("feMerge"),
1698
+ local_name!("femergenode") => *name = local_name!("feMergeNode"),
1699
+ local_name!("femorphology") => *name = local_name!("feMorphology"),
1700
+ local_name!("feoffset") => *name = local_name!("feOffset"),
1701
+ local_name!("fepointlight") => *name = local_name!("fePointLight"),
1702
+ local_name!("fespecularlighting") => *name = local_name!("feSpecularLighting"),
1703
+ local_name!("fespotlight") => *name = local_name!("feSpotLight"),
1704
+ local_name!("fetile") => *name = local_name!("feTile"),
1705
+ local_name!("feturbulence") => *name = local_name!("feTurbulence"),
1706
+ local_name!("foreignobject") => *name = local_name!("foreignObject"),
1707
+ local_name!("glyphref") => *name = local_name!("glyphRef"),
1708
+ local_name!("lineargradient") => *name = local_name!("linearGradient"),
1709
+ local_name!("radialgradient") => *name = local_name!("radialGradient"),
1710
+ local_name!("textpath") => *name = local_name!("textPath"),
1711
+ _ => (),
1712
+ }
1713
+ }
1714
+
1715
+ fn adjust_attributes<F>(&self, tag: &mut Tag, mut map: F)
1716
+ where
1717
+ F: FnMut(LocalName) -> Option<QualName>,
1718
+ {
1719
+ for &mut Attribute { ref mut name, .. } in &mut tag.attrs {
1720
+ if let Some(replacement) = map(name.local.clone()) {
1721
+ *name = replacement;
1722
+ }
1723
+ }
1724
+ }
1725
+
1726
+ fn adjust_svg_attributes(&self, tag: &mut Tag) {
1727
+ self.adjust_attributes(tag, |k| match k {
1728
+ local_name!("attributename") => Some(qualname!("", "attributeName")),
1729
+ local_name!("attributetype") => Some(qualname!("", "attributeType")),
1730
+ local_name!("basefrequency") => Some(qualname!("", "baseFrequency")),
1731
+ local_name!("baseprofile") => Some(qualname!("", "baseProfile")),
1732
+ local_name!("calcmode") => Some(qualname!("", "calcMode")),
1733
+ local_name!("clippathunits") => Some(qualname!("", "clipPathUnits")),
1734
+ local_name!("diffuseconstant") => Some(qualname!("", "diffuseConstant")),
1735
+ local_name!("edgemode") => Some(qualname!("", "edgeMode")),
1736
+ local_name!("filterunits") => Some(qualname!("", "filterUnits")),
1737
+ local_name!("glyphref") => Some(qualname!("", "glyphRef")),
1738
+ local_name!("gradienttransform") => Some(qualname!("", "gradientTransform")),
1739
+ local_name!("gradientunits") => Some(qualname!("", "gradientUnits")),
1740
+ local_name!("kernelmatrix") => Some(qualname!("", "kernelMatrix")),
1741
+ local_name!("kernelunitlength") => Some(qualname!("", "kernelUnitLength")),
1742
+ local_name!("keypoints") => Some(qualname!("", "keyPoints")),
1743
+ local_name!("keysplines") => Some(qualname!("", "keySplines")),
1744
+ local_name!("keytimes") => Some(qualname!("", "keyTimes")),
1745
+ local_name!("lengthadjust") => Some(qualname!("", "lengthAdjust")),
1746
+ local_name!("limitingconeangle") => Some(qualname!("", "limitingConeAngle")),
1747
+ local_name!("markerheight") => Some(qualname!("", "markerHeight")),
1748
+ local_name!("markerunits") => Some(qualname!("", "markerUnits")),
1749
+ local_name!("markerwidth") => Some(qualname!("", "markerWidth")),
1750
+ local_name!("maskcontentunits") => Some(qualname!("", "maskContentUnits")),
1751
+ local_name!("maskunits") => Some(qualname!("", "maskUnits")),
1752
+ local_name!("numoctaves") => Some(qualname!("", "numOctaves")),
1753
+ local_name!("pathlength") => Some(qualname!("", "pathLength")),
1754
+ local_name!("patterncontentunits") => Some(qualname!("", "patternContentUnits")),
1755
+ local_name!("patterntransform") => Some(qualname!("", "patternTransform")),
1756
+ local_name!("patternunits") => Some(qualname!("", "patternUnits")),
1757
+ local_name!("pointsatx") => Some(qualname!("", "pointsAtX")),
1758
+ local_name!("pointsaty") => Some(qualname!("", "pointsAtY")),
1759
+ local_name!("pointsatz") => Some(qualname!("", "pointsAtZ")),
1760
+ local_name!("preservealpha") => Some(qualname!("", "preserveAlpha")),
1761
+ local_name!("preserveaspectratio") => Some(qualname!("", "preserveAspectRatio")),
1762
+ local_name!("primitiveunits") => Some(qualname!("", "primitiveUnits")),
1763
+ local_name!("refx") => Some(qualname!("", "refX")),
1764
+ local_name!("refy") => Some(qualname!("", "refY")),
1765
+ local_name!("repeatcount") => Some(qualname!("", "repeatCount")),
1766
+ local_name!("repeatdur") => Some(qualname!("", "repeatDur")),
1767
+ local_name!("requiredextensions") => Some(qualname!("", "requiredExtensions")),
1768
+ local_name!("requiredfeatures") => Some(qualname!("", "requiredFeatures")),
1769
+ local_name!("specularconstant") => Some(qualname!("", "specularConstant")),
1770
+ local_name!("specularexponent") => Some(qualname!("", "specularExponent")),
1771
+ local_name!("spreadmethod") => Some(qualname!("", "spreadMethod")),
1772
+ local_name!("startoffset") => Some(qualname!("", "startOffset")),
1773
+ local_name!("stddeviation") => Some(qualname!("", "stdDeviation")),
1774
+ local_name!("stitchtiles") => Some(qualname!("", "stitchTiles")),
1775
+ local_name!("surfacescale") => Some(qualname!("", "surfaceScale")),
1776
+ local_name!("systemlanguage") => Some(qualname!("", "systemLanguage")),
1777
+ local_name!("tablevalues") => Some(qualname!("", "tableValues")),
1778
+ local_name!("targetx") => Some(qualname!("", "targetX")),
1779
+ local_name!("targety") => Some(qualname!("", "targetY")),
1780
+ local_name!("textlength") => Some(qualname!("", "textLength")),
1781
+ local_name!("viewbox") => Some(qualname!("", "viewBox")),
1782
+ local_name!("viewtarget") => Some(qualname!("", "viewTarget")),
1783
+ local_name!("xchannelselector") => Some(qualname!("", "xChannelSelector")),
1784
+ local_name!("ychannelselector") => Some(qualname!("", "yChannelSelector")),
1785
+ local_name!("zoomandpan") => Some(qualname!("", "zoomAndPan")),
1786
+ _ => None,
1787
+ });
1788
+ }
1789
+
1790
+ fn adjust_mathml_attributes(&self, tag: &mut Tag) {
1791
+ self.adjust_attributes(tag, |k| match k {
1792
+ local_name!("definitionurl") => Some(qualname!("", "definitionURL")),
1793
+ _ => None,
1794
+ });
1795
+ }
1796
+
1797
+ fn adjust_foreign_attributes(&self, tag: &mut Tag) {
1798
+ self.adjust_attributes(tag, |k| match k {
1799
+ local_name!("xlink:actuate") => Some(qualname!("xlink" xlink "actuate")),
1800
+ local_name!("xlink:arcrole") => Some(qualname!("xlink" xlink "arcrole")),
1801
+ local_name!("xlink:href") => Some(qualname!("xlink" xlink "href")),
1802
+ local_name!("xlink:role") => Some(qualname!("xlink" xlink "role")),
1803
+ local_name!("xlink:show") => Some(qualname!("xlink" xlink "show")),
1804
+ local_name!("xlink:title") => Some(qualname!("xlink" xlink "title")),
1805
+ local_name!("xlink:type") => Some(qualname!("xlink" xlink "type")),
1806
+ local_name!("xml:lang") => Some(qualname!("xml" xml "lang")),
1807
+ local_name!("xml:space") => Some(qualname!("xml" xml "space")),
1808
+ local_name!("xmlns") => Some(qualname!("" xmlns "xmlns")),
1809
+ local_name!("xmlns:xlink") => Some(qualname!("xmlns" xmlns "xlink")),
1810
+ _ => None,
1811
+ });
1812
+ }
1813
+
1814
+ fn foreign_start_tag(&self, mut tag: Tag) -> ProcessResult<Handle> {
1815
+ let current_ns = self
1816
+ .sink
1817
+ .elem_name(&self.adjusted_current_node())
1818
+ .ns()
1819
+ .clone();
1820
+ match current_ns {
1821
+ ns!(mathml) => self.adjust_mathml_attributes(&mut tag),
1822
+ ns!(svg) => {
1823
+ self.adjust_svg_tag_name(&mut tag);
1824
+ self.adjust_svg_attributes(&mut tag);
1825
+ },
1826
+ _ => (),
1827
+ }
1828
+ self.adjust_foreign_attributes(&mut tag);
1829
+ if tag.self_closing {
1830
+ // FIXME(#118): <script /> in SVG
1831
+ self.insert_element(PushFlag::NoPush, current_ns, tag.name, tag.attrs);
1832
+ ProcessResult::DoneAckSelfClosing
1833
+ } else {
1834
+ self.insert_element(PushFlag::Push, current_ns, tag.name, tag.attrs);
1835
+ ProcessResult::Done
1836
+ }
1837
+ }
1838
+
1839
+ fn unexpected_start_tag_in_foreign_content(&self, tag: Tag) -> ProcessResult<Handle> {
1840
+ self.unexpected(&tag);
1841
+ while !self.current_node_in(|n| {
1842
+ *n.ns == ns!(html) || mathml_text_integration_point(n) || svg_html_integration_point(n)
1843
+ }) {
1844
+ self.pop();
1845
+ }
1846
+ self.step(self.mode.get(), Token::Tag(tag))
1847
+ }
1848
+ }