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