Skip to main content

script/dom/servoparser/
html.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![cfg_attr(crown, expect(crown::unrooted_must_root))]
6
7use std::cell::Cell;
8use std::io;
9
10use html5ever::buffer_queue::BufferQueue;
11use html5ever::serialize::TraversalScope::IncludeNode;
12use html5ever::serialize::{AttrRef, Serialize, Serializer, TraversalScope};
13use html5ever::tokenizer::{Tokenizer as HtmlTokenizer, TokenizerOpts};
14use html5ever::tree_builder::{QuirksMode as HTML5EverQuirksMode, TreeBuilder, TreeBuilderOpts};
15use html5ever::{QualName, local_name, ns};
16use markup5ever::TokenizerResult;
17use script_bindings::script_runtime::temp_cx;
18use script_bindings::trace::CustomTraceable;
19use servo_url::ServoUrl;
20use style::attr::AttrValue;
21use style::context::QuirksMode as StyleContextQuirksMode;
22use xml5ever::LocalName;
23
24use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods;
25use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootMode;
26use crate::dom::bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
27use crate::dom::bindings::inheritance::{Castable, CharacterDataTypeId, NodeTypeId};
28use crate::dom::bindings::root::{Dom, DomRoot};
29use crate::dom::characterdata::CharacterData;
30use crate::dom::document::Document;
31use crate::dom::documentfragment::DocumentFragment;
32use crate::dom::documenttype::DocumentType;
33use crate::dom::element::Element;
34use crate::dom::html::htmlscriptelement::HTMLScriptElement;
35use crate::dom::html::htmltemplateelement::HTMLTemplateElement;
36use crate::dom::node::Node;
37use crate::dom::processinginstruction::ProcessingInstruction;
38use crate::dom::servoparser::{ParsingAlgorithm, Sink};
39use crate::dom::shadowroot::ShadowRoot;
40
41#[derive(JSTraceable, MallocSizeOf)]
42#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
43pub(crate) struct Tokenizer {
44    #[ignore_malloc_size_of = "Defined in html5ever"]
45    inner: HtmlTokenizer<TreeBuilder<Dom<Node>, Sink>>,
46}
47
48impl Tokenizer {
49    pub(crate) fn new(
50        document: &Document,
51        url: ServoUrl,
52        fragment_context: Option<super::FragmentContext>,
53        parsing_algorithm: ParsingAlgorithm,
54    ) -> Self {
55        let custom_element_reaction_stack = document.custom_element_reaction_stack();
56        let sink = Sink {
57            base_url: url,
58            document: Dom::from_ref(document),
59            current_line: Cell::new(1),
60            script: Default::default(),
61            parsing_algorithm,
62            custom_element_reaction_stack,
63        };
64
65        let quirks_mode = match document.quirks_mode() {
66            StyleContextQuirksMode::Quirks => HTML5EverQuirksMode::Quirks,
67            StyleContextQuirksMode::LimitedQuirks => HTML5EverQuirksMode::LimitedQuirks,
68            StyleContextQuirksMode::NoQuirks => HTML5EverQuirksMode::NoQuirks,
69        };
70
71        let options = TreeBuilderOpts {
72            scripting_enabled: document.scripting_enabled(),
73            iframe_srcdoc: document.url().as_str() == "about:srcdoc",
74            quirks_mode,
75            ..Default::default()
76        };
77
78        let inner = if let Some(fragment_context) = fragment_context {
79            let tree_builder = TreeBuilder::new_for_fragment(
80                sink,
81                Dom::from_ref(fragment_context.context_elem),
82                fragment_context.form_elem.map(Dom::from_ref),
83                options,
84            );
85
86            let tokenizer_options = TokenizerOpts {
87                initial_state: Some(tree_builder.tokenizer_state_for_context_elem(
88                    fragment_context.context_element_allows_scripting,
89                )),
90                ..Default::default()
91            };
92
93            HtmlTokenizer::new(tree_builder, tokenizer_options)
94        } else {
95            HtmlTokenizer::new(TreeBuilder::new(sink, options), Default::default())
96        };
97
98        Tokenizer { inner }
99    }
100
101    pub(crate) fn feed(&self, input: &BufferQueue) -> TokenizerResult<DomRoot<HTMLScriptElement>> {
102        match self.inner.feed(input) {
103            TokenizerResult::Done => TokenizerResult::Done,
104            TokenizerResult::Script(script) => {
105                TokenizerResult::Script(DomRoot::from_ref(script.downcast().unwrap()))
106            },
107            TokenizerResult::EncodingIndicator(encoding) => {
108                TokenizerResult::EncodingIndicator(encoding)
109            },
110        }
111    }
112
113    pub(crate) fn end(&self) {
114        self.inner.end();
115    }
116
117    pub(crate) fn url(&self) -> &ServoUrl {
118        &self.inner.sink.sink.base_url
119    }
120
121    pub(crate) fn set_plaintext_state(&self) {
122        self.inner.set_plaintext_state();
123    }
124
125    pub(crate) fn get_current_line(&self) -> u32 {
126        self.inner.sink.sink.current_line.get() as u32
127    }
128}
129
130/// <https://html.spec.whatwg.org/multipage/#html-fragment-serialisation-algorithm>
131fn start_element<S: Serializer>(element: &Element, serializer: &mut S) -> io::Result<()> {
132    let name = QualName::new(
133        None,
134        element.namespace().clone(),
135        element.local_name().clone(),
136    );
137
138    let mut attributes = vec![];
139
140    // The "is" value of an element is treated as if it was an attribute and it is serialized before all
141    // other attributes. If the element already has an "is" attribute then the "is" value is ignored.
142    if !element.has_attribute(&LocalName::from("is")) &&
143        let Some(is_value) = element.get_is()
144    {
145        let qualified_name = QualName::new(None, ns!(), LocalName::from("is"));
146
147        attributes.push((qualified_name, AttrValue::String(is_value.to_string())));
148    }
149
150    // Collect all the "normal" attributes
151    attributes.extend(element.attrs().borrow().iter().map(|attr| {
152        let qname = QualName::new(None, attr.namespace().clone(), attr.local_name().clone());
153        let value = attr.value().clone();
154        (qname, value)
155    }));
156
157    let attr_refs = attributes.iter().map(|(qname, value)| {
158        let ar: AttrRef = (qname, &**value);
159        ar
160    });
161    serializer.start_elem(name, attr_refs)?;
162    Ok(())
163}
164
165enum SerializationCommand {
166    OpenElement(DomRoot<Element>),
167    CloseElement(QualName),
168    SerializeNonelement(DomRoot<Node>),
169    SerializeShadowRoot(DomRoot<ShadowRoot>),
170}
171
172struct SerializationIterator {
173    stack: Vec<SerializationCommand>,
174
175    /// Whether or not shadow roots should be serialized
176    serialize_shadow_roots: bool,
177
178    /// List of shadow root objects that should be serialized
179    shadow_roots: Vec<DomRoot<ShadowRoot>>,
180}
181
182impl SerializationIterator {
183    fn new(
184        cx: &mut js::context::JSContext,
185        node: &Node,
186        skip_first: bool,
187        serialize_shadow_roots: bool,
188        shadow_roots: Vec<DomRoot<ShadowRoot>>,
189    ) -> SerializationIterator {
190        let mut ret = SerializationIterator {
191            stack: vec![],
192            serialize_shadow_roots,
193            shadow_roots,
194        };
195        if skip_first || node.is::<DocumentFragment>() || node.is::<Document>() {
196            ret.handle_node_contents(cx, node);
197        } else {
198            ret.push_node(node);
199        }
200        ret
201    }
202
203    fn handle_node_contents(&mut self, cx: &mut js::context::JSContext, node: &Node) {
204        if node.downcast::<Element>().is_some_and(Element::is_void) {
205            return;
206        }
207
208        if let Some(template_element) = node.downcast::<HTMLTemplateElement>() {
209            for child in template_element.Content(cx).upcast::<Node>().rev_children() {
210                self.push_node(&child);
211            }
212        } else {
213            for child in node.rev_children() {
214                self.push_node(&child);
215            }
216        }
217
218        if let Some(shadow_root) = node.downcast::<Element>().and_then(Element::shadow_root) {
219            let should_be_serialized = (self.serialize_shadow_roots && shadow_root.Serializable()) ||
220                self.shadow_roots.contains(&shadow_root);
221            if !shadow_root.is_user_agent_widget() && should_be_serialized {
222                self.stack
223                    .push(SerializationCommand::SerializeShadowRoot(shadow_root));
224            }
225        }
226    }
227
228    fn push_node(&mut self, node: &Node) {
229        let Some(element) = node.downcast::<Element>() else {
230            self.stack.push(SerializationCommand::SerializeNonelement(
231                DomRoot::from_ref(node),
232            ));
233            return;
234        };
235
236        self.stack
237            .push(SerializationCommand::OpenElement(DomRoot::from_ref(
238                element,
239            )));
240    }
241}
242
243impl Iterator for SerializationIterator {
244    type Item = SerializationCommand;
245
246    #[expect(unsafe_code)]
247    fn next(&mut self) -> Option<SerializationCommand> {
248        // TODO: https://github.com/servo/servo/issues/42839
249        let mut cx = unsafe { temp_cx() };
250        let cx = &mut cx;
251        let res = self.stack.pop()?;
252
253        match &res {
254            SerializationCommand::OpenElement(element) => {
255                let name = QualName::new(
256                    None,
257                    element.namespace().clone(),
258                    element.local_name().clone(),
259                );
260                self.stack.push(SerializationCommand::CloseElement(name));
261                self.handle_node_contents(cx, element.upcast());
262            },
263            SerializationCommand::SerializeShadowRoot(shadow_root) => {
264                self.stack
265                    .push(SerializationCommand::CloseElement(QualName::new(
266                        None,
267                        ns!(),
268                        local_name!("template"),
269                    )));
270                self.handle_node_contents(cx, shadow_root.upcast());
271            },
272            _ => {},
273        }
274
275        Some(res)
276    }
277}
278
279/// <https://html.spec.whatwg.org/multipage/#html-fragment-serialisation-algorithm>
280pub(crate) fn serialize_html_fragment<S: Serializer>(
281    cx: &mut js::context::JSContext,
282    node: &Node,
283    serializer: &mut S,
284    traversal_scope: TraversalScope,
285    serialize_shadow_roots: bool,
286    shadow_roots: Vec<DomRoot<ShadowRoot>>,
287) -> io::Result<()> {
288    let iter = SerializationIterator::new(
289        cx,
290        node,
291        traversal_scope != IncludeNode,
292        serialize_shadow_roots,
293        shadow_roots,
294    );
295
296    for cmd in iter {
297        match cmd {
298            SerializationCommand::OpenElement(n) => {
299                start_element(&n, serializer)?;
300            },
301            SerializationCommand::CloseElement(name) => {
302                serializer.end_elem(name)?;
303            },
304            SerializationCommand::SerializeNonelement(n) => match n.type_id() {
305                NodeTypeId::DocumentType => {
306                    let doctype = n.downcast::<DocumentType>().unwrap();
307                    serializer.write_doctype(&doctype.name().str())?;
308                },
309
310                NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => {
311                    let cdata = n.downcast::<CharacterData>().unwrap();
312                    serializer.write_text(&cdata.data())?;
313                },
314
315                NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => {
316                    let cdata = n.downcast::<CharacterData>().unwrap();
317                    serializer.write_comment(&cdata.data())?;
318                },
319
320                NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => {
321                    let pi = n.downcast::<ProcessingInstruction>().unwrap();
322                    let data = pi.upcast::<CharacterData>().data();
323                    serializer.write_processing_instruction(&pi.target().str(), &data)?;
324                },
325
326                NodeTypeId::DocumentFragment(_) | NodeTypeId::Attr => {},
327
328                NodeTypeId::Document(_) => panic!("Can't serialize Document node itself"),
329                NodeTypeId::Element(_) => panic!("Element shouldn't appear here"),
330            },
331            SerializationCommand::SerializeShadowRoot(shadow_root) => {
332                // Shadow roots are serialized as template elements with a fixed set of
333                // attributes. Because these template elements don't actually exist in the DOM
334                // we have to make up a vector of attributes ourselves.
335                let mut attributes = vec![];
336                let mut push_attribute = |name, value| {
337                    let qualified_name = QualName::new(None, ns!(), LocalName::from(name));
338                    attributes.push((qualified_name, value))
339                };
340
341                let mode = if shadow_root.Mode() == ShadowRootMode::Open {
342                    "open"
343                } else {
344                    "closed"
345                };
346                push_attribute("shadowrootmode", mode);
347
348                if shadow_root.DelegatesFocus() {
349                    push_attribute("shadowrootdelegatesfocus", "");
350                }
351
352                if shadow_root.Serializable() {
353                    push_attribute("shadowrootserializable", "");
354                }
355
356                if shadow_root.Clonable() {
357                    push_attribute("shadowrootclonable", "");
358                }
359
360                let name = QualName::new(None, ns!(), local_name!("template"));
361                serializer.start_elem(name, attributes.iter().map(|(a, b)| (a, *b)))?;
362            },
363        }
364    }
365
366    Ok(())
367}
368
369pub(crate) struct HtmlSerialize<'a> {
370    node: &'a Node,
371}
372
373impl<'a> HtmlSerialize<'a> {
374    pub(crate) fn new(node: &'a Node) -> HtmlSerialize<'a> {
375        HtmlSerialize { node }
376    }
377}
378
379impl Serialize for HtmlSerialize<'_> {
380    #[expect(unsafe_code)]
381    fn serialize<S>(&self, serializer: &mut S, traversal_scope: TraversalScope) -> io::Result<()>
382    where
383        S: Serializer,
384    {
385        // TODO: https://github.com/servo/servo/issues/42839
386        let mut cx = unsafe { temp_cx() };
387        let cx = &mut cx;
388        serialize_html_fragment(cx, self.node, serializer, traversal_scope, false, vec![])
389    }
390}