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.
- package/CONTRIBUTING.md +73 -0
- package/Cargo.lock +1051 -0
- package/Cargo.toml +36 -0
- package/LICENSE +21 -0
- package/README.md +252 -2
- package/RELEASING.md +138 -0
- package/THIRD_PARTY_NOTICES.md +7443 -0
- package/build.rs +3 -0
- package/dist/chunk-DKULX6VO.mjs +3185 -0
- package/dist/chunk-DKULX6VO.mjs.map +1 -0
- package/dist/chunk-OHPGEY3E.mjs +32 -0
- package/dist/chunk-OHPGEY3E.mjs.map +1 -0
- package/dist/index.cjs +3230 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +113 -0
- package/dist/index.d.ts +113 -0
- package/dist/index.mjs +38 -0
- package/dist/index.mjs.map +1 -0
- package/dist/slim.cjs +2441 -0
- package/dist/slim.cjs.map +1 -0
- package/dist/slim.d.mts +11 -0
- package/dist/slim.d.ts +11 -0
- package/dist/slim.mjs +19 -0
- package/dist/slim.mjs.map +1 -0
- package/dist/types-BP6TpxE5.d.mts +532 -0
- package/dist/types-BP6TpxE5.d.ts +532 -0
- package/dist/utils.cjs +61 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.mts +13 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.mjs +17 -0
- package/dist/utils.mjs.map +1 -0
- package/native/src/arena.rs +1781 -0
- package/native/src/attributes.rs +923 -0
- package/native/src/codec.rs +172 -0
- package/native/src/diagnostics.rs +1249 -0
- package/native/src/extraction.rs +226 -0
- package/native/src/gc.rs +166 -0
- package/native/src/hook_sink.rs +727 -0
- package/native/src/hook_stream.rs +513 -0
- package/native/src/lib.rs +2095 -0
- package/native/src/location.rs +392 -0
- package/native/src/runtime.rs +189 -0
- package/native/src/selector.rs +1287 -0
- package/native/src/sink.rs +261 -0
- package/native/src/stream.rs +334 -0
- package/native/src/tokenizer.rs +355 -0
- package/native/src/tracker.rs +232 -0
- package/native/vendor/README.md +25 -0
- package/native/vendor/html5ever-0.35.0/.cargo_vcs_info.json +6 -0
- package/native/vendor/html5ever-0.35.0/Cargo.lock +828 -0
- package/native/vendor/html5ever-0.35.0/Cargo.toml +85 -0
- package/native/vendor/html5ever-0.35.0/LICENSE-APACHE +201 -0
- package/native/vendor/html5ever-0.35.0/LICENSE-MIT +25 -0
- package/native/vendor/html5ever-0.35.0/README.md +60 -0
- package/native/vendor/html5ever-0.35.0/benches/html5ever.rs +86 -0
- package/native/vendor/html5ever-0.35.0/data/bench/lipsum-zh.html +19 -0
- package/native/vendor/html5ever-0.35.0/data/bench/lipsum.html +40 -0
- package/native/vendor/html5ever-0.35.0/data/bench/medium-fragment.html +24 -0
- package/native/vendor/html5ever-0.35.0/data/bench/small-fragment.html +7 -0
- package/native/vendor/html5ever-0.35.0/data/bench/strong.html +1 -0
- package/native/vendor/html5ever-0.35.0/data/bench/tiny-fragment.html +1 -0
- package/native/vendor/html5ever-0.35.0/examples/arena.rs +356 -0
- package/native/vendor/html5ever-0.35.0/examples/capi/tokenize.c +74 -0
- package/native/vendor/html5ever-0.35.0/examples/noop-tokenize.rs +47 -0
- package/native/vendor/html5ever-0.35.0/examples/noop-tree-builder.rs +132 -0
- package/native/vendor/html5ever-0.35.0/examples/print-tree-actions.rs +185 -0
- package/native/vendor/html5ever-0.35.0/examples/tokenize.rs +111 -0
- package/native/vendor/html5ever-0.35.0/src/driver.rs +148 -0
- package/native/vendor/html5ever-0.35.0/src/lib.rs +30 -0
- package/native/vendor/html5ever-0.35.0/src/macros.rs +34 -0
- package/native/vendor/html5ever-0.35.0/src/serialize/mod.rs +255 -0
- package/native/vendor/html5ever-0.35.0/src/tokenizer/char_ref/mod.rs +451 -0
- package/native/vendor/html5ever-0.35.0/src/tokenizer/interface.rs +99 -0
- package/native/vendor/html5ever-0.35.0/src/tokenizer/mod.rs +2305 -0
- package/native/vendor/html5ever-0.35.0/src/tokenizer/states.rs +97 -0
- package/native/vendor/html5ever-0.35.0/src/tree_builder/data.rs +170 -0
- package/native/vendor/html5ever-0.35.0/src/tree_builder/mod.rs +1848 -0
- package/native/vendor/html5ever-0.35.0/src/tree_builder/rules.rs +1494 -0
- package/native/vendor/html5ever-0.35.0/src/tree_builder/tag_sets.rs +114 -0
- package/native/vendor/html5ever-0.35.0/src/tree_builder/types.rs +91 -0
- package/native/vendor/html5ever-0.35.0/src/util/str.rs +52 -0
- package/native/vendor/html5gum-0.8.4/Cargo.lock +1006 -0
- package/native/vendor/html5gum-0.8.4/Cargo.toml +109 -0
- package/native/vendor/html5gum-0.8.4/LICENSE +19 -0
- package/native/vendor/html5gum-0.8.4/README.md +110 -0
- package/native/vendor/html5gum-0.8.4/benches/files/0050.html +2131 -0
- package/native/vendor/html5gum-0.8.4/benches/files/6D30.html +1004 -0
- package/native/vendor/html5gum-0.8.4/benches/files.rs +32 -0
- package/native/vendor/html5gum-0.8.4/benches/patterns.rs +31 -0
- package/native/vendor/html5gum-0.8.4/src/arrayvec.rs +29 -0
- package/native/vendor/html5gum-0.8.4/src/char_validator.rs +134 -0
- package/native/vendor/html5gum-0.8.4/src/emitters/callback.rs +676 -0
- package/native/vendor/html5gum-0.8.4/src/emitters/default.rs +207 -0
- package/native/vendor/html5gum-0.8.4/src/emitters/emitter.rs +344 -0
- package/native/vendor/html5gum-0.8.4/src/emitters/html5ever.rs +306 -0
- package/native/vendor/html5gum-0.8.4/src/emitters/mod.rs +25 -0
- package/native/vendor/html5gum-0.8.4/src/entities.rs +3342 -0
- package/native/vendor/html5gum-0.8.4/src/error.rs +96 -0
- package/native/vendor/html5gum-0.8.4/src/htmlstring.rs +130 -0
- package/native/vendor/html5gum-0.8.4/src/lib.rs +66 -0
- package/native/vendor/html5gum-0.8.4/src/machine.rs +2136 -0
- package/native/vendor/html5gum-0.8.4/src/machine_helper.rs +277 -0
- package/native/vendor/html5gum-0.8.4/src/read_helper.rs +244 -0
- package/native/vendor/html5gum-0.8.4/src/reader.rs +440 -0
- package/native/vendor/html5gum-0.8.4/src/span.rs +98 -0
- package/native/vendor/html5gum-0.8.4/src/state.rs +16 -0
- package/native/vendor/html5gum-0.8.4/src/testutils.rs +71 -0
- package/native/vendor/html5gum-0.8.4/src/tokenizer.rs +189 -0
- package/native/vendor/html5gum-0.8.4/src/utils.rs +88 -0
- package/native/vendor/markup5ever-0.35.0/.cargo_vcs_info.json +6 -0
- package/native/vendor/markup5ever-0.35.0/Cargo.toml +45 -0
- package/native/vendor/markup5ever-0.35.0/LICENSE-APACHE +201 -0
- package/native/vendor/markup5ever-0.35.0/LICENSE-MIT +25 -0
- package/native/vendor/markup5ever-0.35.0/interface/mod.rs +422 -0
- package/native/vendor/markup5ever-0.35.0/interface/tree_builder.rs +280 -0
- package/native/vendor/markup5ever-0.35.0/lib.rs +59 -0
- package/native/vendor/markup5ever-0.35.0/serialize.rs +77 -0
- package/native/vendor/markup5ever-0.35.0/util/buffer_queue.rs +333 -0
- package/native/vendor/markup5ever-0.35.0/util/smallcharset.rs +88 -0
- package/package.json +137 -4
- package/rusteerio.darwin-arm64.node +0 -0
- package/rusteerio.darwin-x64.node +0 -0
- package/rusteerio.linux-arm64-gnu.node +0 -0
- package/rusteerio.linux-arm64-musl.node +0 -0
- package/rusteerio.linux-x64-gnu.node +0 -0
- package/rusteerio.linux-x64-musl.node +0 -0
- package/rusteerio.win32-arm64-msvc.node +0 -0
- package/rusteerio.win32-x64-msvc.node +0 -0
- package/scripts/check-native.mjs +15 -0
- package/scripts/check-release.mjs +235 -0
- package/scripts/fix-declarations.mjs +27 -0
- package/scripts/generate-notices.mjs +176 -0
- package/scripts/notices/README.md +13 -0
- package/scripts/notices/cheerio-MIT.txt +21 -0
- package/scripts/notices/html5ever-AUTHORS.txt +20 -0
- package/scripts/notices/html5ever-COPYRIGHT.txt +8 -0
- package/scripts/notices/html5ever-LICENSE-APACHE.txt +201 -0
- package/scripts/notices/html5ever-LICENSE-MIT.txt +25 -0
- package/scripts/notices/napi-rs-MIT.txt +43 -0
- package/scripts/notices/neon-MIT.txt +19 -0
- package/scripts/notices/runtime-01c266bced4a434da005.txt +3 -0
- package/scripts/notices/runtime-0a54e647fe54104658b5.txt +14 -0
- package/scripts/notices/runtime-0b74dfa0bcee5c420c6b.txt +25 -0
- package/scripts/notices/runtime-0d542e0c8804e39aa7f3.txt +177 -0
- package/scripts/notices/runtime-0f96a83840e146e43c0e.txt +21 -0
- package/scripts/notices/runtime-123a331b5dbf04c30097.txt +25 -0
- package/scripts/notices/runtime-172020dbfd5b53a226df.txt +34 -0
- package/scripts/notices/runtime-209fbbe0ad52d9235e37.txt +26 -0
- package/scripts/notices/runtime-23f18e03dc49df91622f.txt +23 -0
- package/scripts/notices/runtime-268872b9816f90fd8e85.txt +220 -0
- package/scripts/notices/runtime-35242e7a83f69875e6ed.txt +176 -0
- package/scripts/notices/runtime-378f5840b258e2779c39.txt +25 -0
- package/scripts/notices/runtime-4108245a1f2df9d4e94d.txt +25 -0
- package/scripts/notices/runtime-4455bf75a91154108304.txt +21 -0
- package/scripts/notices/runtime-553fffcd9b1cb158bc3e.txt +13 -0
- package/scripts/notices/runtime-62c7a1e35f56406896d7.txt +176 -0
- package/scripts/notices/runtime-6485b8ed310d3f0340bf.txt +25 -0
- package/scripts/notices/runtime-69fef7b0f322a6555415.txt +201 -0
- package/scripts/notices/runtime-6df43f6f4b5d4587f3d8.txt +187 -0
- package/scripts/notices/runtime-799e9ca9d179295ef372.txt +24 -0
- package/scripts/notices/runtime-7b63ecd5f1902af1b637.txt +25 -0
- package/scripts/notices/runtime-7bc7d5de2a0b793d4e1a.txt +21 -0
- package/scripts/notices/runtime-7e12e5df4bae12cb2158.txt +24 -0
- package/scripts/notices/runtime-861399f8c21c042b1105.txt +12 -0
- package/scripts/notices/runtime-8ada45cd9f843acf64e4.txt +201 -0
- package/scripts/notices/runtime-90eb64f0279b0d9432ac.txt +12 -0
- package/scripts/notices/runtime-a60eea817514531668d7.txt +201 -0
- package/scripts/notices/runtime-a7c936ff1ed8fa340172.txt +25 -0
- package/scripts/notices/runtime-ab6eec6caf0fa5775e41.txt +275 -0
- package/scripts/notices/runtime-b1181a40b2a7b25cf66f.txt +19 -0
- package/scripts/notices/runtime-b71bd43a069ca0641a9e.txt +25 -0
- package/scripts/notices/runtime-c16f8dcf1a368b83be78.txt +201 -0
- package/scripts/notices/runtime-c2cfccb812fe482101a8.txt +21 -0
- package/scripts/notices/runtime-e99d88d232bf57d70f0f.txt +25 -0
- package/scripts/notices/runtime-f32fb3b417a194167cfa.txt +9 -0
- package/scripts/notices/runtime-f5062c9a188d81dfe66b.txt +39 -0
- package/scripts/notices/runtime-f9070a832833fa7488fd.txt +4 -0
- package/scripts/notices/runtime-fab3dd6bdab226f1c086.txt +373 -0
- package/scripts/notices/runtime-fba3318d0c600380177e.txt +62 -0
- package/scripts/notices/runtime-ff8f68cb076caf8cefe7.txt +25 -0
- package/scripts/notices/runtime-ff92bed461f50338dd70.txt +74 -0
- package/scripts/notices/runtime.json +1632 -0
- package/scripts/notices/sources.json +555 -0
- package/scripts/notices/xray-MIT.txt +21 -0
- package/src/attributes.ts +271 -0
- package/src/codec.ts +74 -0
- package/src/dom.ts +87 -0
- package/src/extract-plan.ts +97 -0
- package/src/extract.ts +130 -0
- package/src/hooks.ts +220 -0
- package/src/index.ts +223 -0
- package/src/io.ts +491 -0
- package/src/native.ts +135 -0
- package/src/nodes.ts +290 -0
- package/src/parse.ts +53 -0
- package/src/parser-types.ts +157 -0
- package/src/router.ts +327 -0
- package/src/selection.ts +566 -0
- package/src/slim.ts +12 -0
- package/src/transport.ts +104 -0
- package/src/types.ts +186 -0
- package/src/utils.ts +29 -0
- package/src/workers.ts +21 -0
- package/tsconfig.json +15 -0
- 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(¤t);
|
|
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
|
+
}
|