script/dom/servoparser/
mod.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
5use std::borrow::Cow;
6use std::cell::{Cell, RefCell};
7use std::mem;
8use std::rc::Rc;
9
10use base::cross_process_instant::CrossProcessInstant;
11use base::id::{PipelineId, WebViewId};
12use base64::Engine as _;
13use base64::engine::general_purpose;
14use content_security_policy::sandboxing_directive::SandboxingFlagSet;
15use devtools_traits::ScriptToDevtoolsControlMsg;
16use dom_struct::dom_struct;
17use embedder_traits::resources::{self, Resource};
18use encoding_rs::{Encoding, UTF_8};
19use html5ever::buffer_queue::BufferQueue;
20use html5ever::tendril::StrTendril;
21use html5ever::tree_builder::{ElementFlags, NodeOrText, QuirksMode, TreeSink};
22use html5ever::{Attribute, ExpandedName, LocalName, QualName, local_name, ns};
23use hyper_serde::Serde;
24use markup5ever::TokenizerResult;
25use mime::{self, Mime};
26use net_traits::mime_classifier::{ApacheBugFlag, MediaType, MimeClassifier, NoSniffFlag};
27use net_traits::policy_container::PolicyContainer;
28use net_traits::request::RequestId;
29use net_traits::{
30    FetchMetadata, LoadContext, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming,
31};
32use profile_traits::time::{
33    ProfilerCategory, ProfilerChan, TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType,
34};
35use profile_traits::time_profile;
36use script_traits::DocumentActivity;
37use servo_config::pref;
38use servo_url::ServoUrl;
39use style::context::QuirksMode as ServoQuirksMode;
40use tendril::stream::LossyDecoder;
41use tendril::{ByteTendril, TendrilSink};
42
43use crate::document_loader::{DocumentLoader, LoadType};
44use crate::dom::bindings::cell::DomRefCell;
45use crate::dom::bindings::codegen::Bindings::DocumentBinding::{
46    DocumentMethods, DocumentReadyState,
47};
48use crate::dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods;
49use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaElementMethods;
50use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods;
51use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
52use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
53    ShadowRootMode, SlotAssignmentMode,
54};
55use crate::dom::bindings::inheritance::Castable;
56use crate::dom::bindings::refcounted::Trusted;
57use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
58use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
59use crate::dom::bindings::settings_stack::is_execution_stack_empty;
60use crate::dom::bindings::str::{DOMString, USVString};
61use crate::dom::characterdata::CharacterData;
62use crate::dom::comment::Comment;
63use crate::dom::csp::{GlobalCspReporting, Violation, parse_csp_list_from_metadata};
64use crate::dom::customelementregistry::CustomElementReactionStack;
65use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument};
66use crate::dom::documentfragment::DocumentFragment;
67use crate::dom::documenttype::DocumentType;
68use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator};
69use crate::dom::globalscope::GlobalScope;
70use crate::dom::html::htmlformelement::{FormControlElementHelpers, HTMLFormElement};
71use crate::dom::html::htmlimageelement::HTMLImageElement;
72use crate::dom::html::htmlscriptelement::{HTMLScriptElement, ScriptResult};
73use crate::dom::html::htmltemplateelement::HTMLTemplateElement;
74use crate::dom::node::{Node, ShadowIncluding};
75use crate::dom::performance::performanceentry::PerformanceEntry;
76use crate::dom::performance::performancenavigationtiming::PerformanceNavigationTiming;
77use crate::dom::processinginstruction::ProcessingInstruction;
78use crate::dom::processingoptions::{
79    LinkHeader, LinkProcessingPhase, extract_links_from_headers, process_link_headers,
80};
81use crate::dom::reportingendpoint::ReportingEndpoint;
82use crate::dom::shadowroot::IsUserAgentWidget;
83use crate::dom::text::Text;
84use crate::dom::types::{HTMLElement, HTMLMediaElement, HTMLOptionElement};
85use crate::dom::virtualmethods::vtable_for;
86use crate::network_listener::FetchResponseListener;
87use crate::realms::enter_realm;
88use crate::script_runtime::{CanGc, IntroductionType};
89use crate::script_thread::ScriptThread;
90
91mod async_html;
92pub(crate) mod encoding;
93pub(crate) mod html;
94mod prefetch;
95mod xml;
96
97use encoding::{NetworkDecoderState, NetworkSink};
98pub(crate) use html::serialize_html_fragment;
99
100#[dom_struct]
101/// The parser maintains two input streams: one for input from script through
102/// document.write(), and one for input from network.
103///
104/// There is no concrete representation of the insertion point, instead it
105/// always points to just before the next character from the network input,
106/// with all of the script input before itself.
107///
108/// ```text
109///     ... script input ... | ... network input ...
110///                          ^
111///                 insertion point
112/// ```
113pub(crate) struct ServoParser {
114    reflector: Reflector,
115    /// The document associated with this parser.
116    document: Dom<Document>,
117    /// The decoder used for the network input.
118    network_decoder: DomRefCell<NetworkDecoderState>,
119    /// Input received from network.
120    #[ignore_malloc_size_of = "Defined in html5ever"]
121    #[no_trace]
122    network_input: BufferQueue,
123    /// Input received from script. Used only to support document.write().
124    #[ignore_malloc_size_of = "Defined in html5ever"]
125    #[no_trace]
126    script_input: BufferQueue,
127    /// The tokenizer of this parser.
128    tokenizer: Tokenizer,
129    /// Whether to expect any further input from the associated network request.
130    last_chunk_received: Cell<bool>,
131    /// Whether this parser should avoid passing any further data to the tokenizer.
132    suspended: Cell<bool>,
133    /// <https://html.spec.whatwg.org/multipage/#script-nesting-level>
134    script_nesting_level: Cell<usize>,
135    /// <https://html.spec.whatwg.org/multipage/#abort-a-parser>
136    aborted: Cell<bool>,
137    /// <https://html.spec.whatwg.org/multipage/#script-created-parser>
138    script_created_parser: bool,
139    /// A decoder exclusively for input to the prefetch tokenizer.
140    ///
141    /// Unlike the actual decoder, this one takes a best guess at the encoding and starts
142    /// decoding immediately.
143    #[no_trace]
144    prefetch_decoder: RefCell<LossyDecoder<NetworkSink>>,
145    /// We do a quick-and-dirty parse of the input looking for resources to prefetch.
146    // TODO: if we had speculative parsing, we could do this when speculatively
147    // building the DOM. https://github.com/servo/servo/pull/19203
148    prefetch_tokenizer: prefetch::Tokenizer,
149    #[ignore_malloc_size_of = "Defined in html5ever"]
150    #[no_trace]
151    prefetch_input: BufferQueue,
152    // The whole input as a string, if needed for the devtools Sources panel.
153    // TODO: use a faster type for concatenating strings?
154    content_for_devtools: Option<DomRefCell<String>>,
155}
156
157pub(crate) struct ElementAttribute {
158    name: QualName,
159    value: DOMString,
160}
161
162#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
163pub(crate) enum ParsingAlgorithm {
164    Normal,
165    Fragment,
166}
167
168impl ElementAttribute {
169    pub(crate) fn new(name: QualName, value: DOMString) -> ElementAttribute {
170        ElementAttribute { name, value }
171    }
172}
173
174impl ServoParser {
175    pub(crate) fn parser_is_not_active(&self) -> bool {
176        self.can_write()
177    }
178
179    /// <https://html.spec.whatwg.org/multipage/#parse-html-from-a-string>
180    pub(crate) fn parse_html_document(
181        document: &Document,
182        input: Option<DOMString>,
183        url: ServoUrl,
184        encoding_hint_from_content_type: Option<&'static Encoding>,
185        encoding_of_container_document: Option<&'static Encoding>,
186        can_gc: CanGc,
187    ) {
188        // Step 1. Set document's type to "html".
189        //
190        // Set by callers of this function and asserted here
191        assert!(document.is_html_document());
192
193        // Step 2. Create an HTML parser parser, associated with document.
194        let parser = if pref!(dom_servoparser_async_html_tokenizer_enabled) {
195            ServoParser::new(
196                document,
197                Tokenizer::AsyncHtml(self::async_html::Tokenizer::new(document, url, None)),
198                ParserKind::Normal,
199                encoding_hint_from_content_type,
200                encoding_of_container_document,
201                can_gc,
202            )
203        } else {
204            ServoParser::new(
205                document,
206                Tokenizer::Html(self::html::Tokenizer::new(
207                    document,
208                    url,
209                    None,
210                    ParsingAlgorithm::Normal,
211                )),
212                ParserKind::Normal,
213                encoding_hint_from_content_type,
214                encoding_of_container_document,
215                can_gc,
216            )
217        };
218        // Step 3. Place html into the input stream for parser. The encoding confidence is irrelevant.
219        // Step 4. Start parser and let it run until it has consumed all the
220        // characters just inserted into the input stream.
221        //
222        // Set as the document's current parser and initialize with `input`, if given.
223        if let Some(input) = input {
224            parser.parse_complete_string_chunk(String::from(input), can_gc);
225        } else {
226            parser.document.set_current_parser(Some(&parser));
227        }
228    }
229
230    /// <https://html.spec.whatwg.org/multipage/#parsing-html-fragments>
231    pub(crate) fn parse_html_fragment(
232        context: &Element,
233        input: DOMString,
234        allow_declarative_shadow_roots: bool,
235        can_gc: CanGc,
236    ) -> impl Iterator<Item = DomRoot<Node>> + use<'_> {
237        let context_node = context.upcast::<Node>();
238        let context_document = context_node.owner_doc();
239        let window = context_document.window();
240        let url = context_document.url();
241
242        // Step 1. Let document be a Document node whose type is "html".
243        let loader = DocumentLoader::new_with_threads(
244            context_document.loader().resource_threads().clone(),
245            Some(url.clone()),
246        );
247        let document = Document::new(
248            window,
249            HasBrowsingContext::No,
250            Some(url.clone()),
251            context_document.about_base_url(),
252            context_document.origin().clone(),
253            IsHTMLDocument::HTMLDocument,
254            None,
255            None,
256            DocumentActivity::Inactive,
257            DocumentSource::FromParser,
258            loader,
259            None,
260            None,
261            Default::default(),
262            false,
263            allow_declarative_shadow_roots,
264            Some(context_document.insecure_requests_policy()),
265            context_document.has_trustworthy_ancestor_or_current_origin(),
266            context_document.custom_element_reaction_stack(),
267            context_document.creation_sandboxing_flag_set(),
268            can_gc,
269        );
270
271        // Step 2. If context's node document is in quirks mode, then set document's mode to "quirks".
272        // Step 3. Otherwise, if context's node document is in limited-quirks mode, then set document's
273        // mode to "limited-quirks".
274        document.set_quirks_mode(context_document.quirks_mode());
275
276        // NOTE: The following steps happened as part of Step 1.
277        // Step 4. If allowDeclarativeShadowRoots is true, then set document's
278        // allow declarative shadow roots to true.
279        // Step 5. Create a new HTML parser, and associate it with document.
280
281        // Step 11.
282        let form = context_node
283            .inclusive_ancestors(ShadowIncluding::No)
284            .find(|element| element.is::<HTMLFormElement>());
285
286        let fragment_context = FragmentContext {
287            context_elem: context_node,
288            form_elem: form.as_deref(),
289            context_element_allows_scripting: context_document.scripting_enabled(),
290        };
291
292        let parser = ServoParser::new(
293            &document,
294            Tokenizer::Html(self::html::Tokenizer::new(
295                &document,
296                url,
297                Some(fragment_context),
298                ParsingAlgorithm::Fragment,
299            )),
300            ParserKind::Normal,
301            None,
302            None,
303            can_gc,
304        );
305        parser.parse_complete_string_chunk(String::from(input), can_gc);
306
307        // Step 14.
308        let root_element = document.GetDocumentElement().expect("no document element");
309        FragmentParsingResult {
310            inner: root_element.upcast::<Node>().children(),
311        }
312    }
313
314    pub(crate) fn parse_html_script_input(document: &Document, url: ServoUrl) {
315        let parser = ServoParser::new(
316            document,
317            Tokenizer::Html(self::html::Tokenizer::new(
318                document,
319                url,
320                None,
321                ParsingAlgorithm::Normal,
322            )),
323            ParserKind::ScriptCreated,
324            None,
325            None,
326            CanGc::note(),
327        );
328        document.set_current_parser(Some(&parser));
329    }
330
331    pub(crate) fn parse_xml_document(
332        document: &Document,
333        input: Option<DOMString>,
334        url: ServoUrl,
335        encoding_hint_from_content_type: Option<&'static Encoding>,
336        can_gc: CanGc,
337    ) {
338        let parser = ServoParser::new(
339            document,
340            Tokenizer::Xml(self::xml::Tokenizer::new(document, url)),
341            ParserKind::Normal,
342            encoding_hint_from_content_type,
343            None,
344            can_gc,
345        );
346
347        // Set as the document's current parser and initialize with `input`, if given.
348        if let Some(input) = input {
349            parser.parse_complete_string_chunk(String::from(input), can_gc);
350        } else {
351            parser.document.set_current_parser(Some(&parser));
352        }
353    }
354
355    pub(crate) fn script_nesting_level(&self) -> usize {
356        self.script_nesting_level.get()
357    }
358
359    pub(crate) fn is_script_created(&self) -> bool {
360        self.script_created_parser
361    }
362
363    /// Corresponds to the latter part of the "Otherwise" branch of the 'An end
364    /// tag whose tag name is "script"' of
365    /// <https://html.spec.whatwg.org/multipage/#parsing-main-incdata>
366    ///
367    /// This first moves everything from the script input to the beginning of
368    /// the network input, effectively resetting the insertion point to just
369    /// before the next character to be consumed.
370    ///
371    ///
372    /// ```text
373    ///     | ... script input ... network input ...
374    ///     ^
375    ///     insertion point
376    /// ```
377    pub(crate) fn resume_with_pending_parsing_blocking_script(
378        &self,
379        script: &HTMLScriptElement,
380        result: ScriptResult,
381        can_gc: CanGc,
382    ) {
383        assert!(self.suspended.get());
384        self.suspended.set(false);
385
386        self.script_input.swap_with(&self.network_input);
387        while let Some(chunk) = self.script_input.pop_front() {
388            self.network_input.push_back(chunk);
389        }
390
391        let script_nesting_level = self.script_nesting_level.get();
392        assert_eq!(script_nesting_level, 0);
393
394        self.script_nesting_level.set(script_nesting_level + 1);
395        script.execute(result, can_gc);
396        self.script_nesting_level.set(script_nesting_level);
397
398        if !self.suspended.get() && !self.aborted.get() {
399            self.parse_sync(can_gc);
400        }
401    }
402
403    pub(crate) fn can_write(&self) -> bool {
404        self.script_created_parser || self.script_nesting_level.get() > 0
405    }
406
407    /// Steps 6-8 of <https://html.spec.whatwg.org/multipage/#document.write()>
408    pub(crate) fn write(&self, text: DOMString, can_gc: CanGc) {
409        assert!(self.can_write());
410
411        if self.document.has_pending_parsing_blocking_script() {
412            // There is already a pending parsing blocking script so the
413            // parser is suspended, we just append everything to the
414            // script input and abort these steps.
415            self.script_input.push_back(String::from(text).into());
416            return;
417        }
418
419        // There is no pending parsing blocking script, so all previous calls
420        // to document.write() should have seen their entire input tokenized
421        // and process, with nothing pushed to the parser script input.
422        assert!(self.script_input.is_empty());
423
424        let input = BufferQueue::default();
425        input.push_back(String::from(text).into());
426
427        let profiler_chan = self
428            .document
429            .window()
430            .as_global_scope()
431            .time_profiler_chan()
432            .clone();
433        let profiler_metadata = TimerMetadata {
434            url: self.document.url().as_str().into(),
435            iframe: TimerMetadataFrameType::RootWindow,
436            incremental: TimerMetadataReflowType::FirstReflow,
437        };
438        self.tokenize(
439            |tokenizer| {
440                tokenizer.feed(
441                    &input,
442                    can_gc,
443                    profiler_chan.clone(),
444                    profiler_metadata.clone(),
445                )
446            },
447            can_gc,
448        );
449
450        if self.suspended.get() {
451            // Parser got suspended, insert remaining input at end of
452            // script input, following anything written by scripts executed
453            // reentrantly during this call.
454            while let Some(chunk) = input.pop_front() {
455                self.script_input.push_back(chunk);
456            }
457            return;
458        }
459
460        assert!(input.is_empty());
461    }
462
463    // Steps 4-6 of https://html.spec.whatwg.org/multipage/#dom-document-close
464    pub(crate) fn close(&self, can_gc: CanGc) {
465        assert!(self.script_created_parser);
466
467        // Step 4.
468        self.last_chunk_received.set(true);
469
470        if self.suspended.get() {
471            // Step 5.
472            return;
473        }
474
475        // Step 6.
476        self.parse_sync(can_gc);
477    }
478
479    // https://html.spec.whatwg.org/multipage/#abort-a-parser
480    pub(crate) fn abort(&self, can_gc: CanGc) {
481        assert!(!self.aborted.get());
482        self.aborted.set(true);
483
484        // Step 1.
485        self.script_input.replace_with(BufferQueue::default());
486        self.network_input.replace_with(BufferQueue::default());
487
488        // Step 2.
489        self.document
490            .set_ready_state(DocumentReadyState::Interactive, can_gc);
491
492        // Step 3.
493        self.tokenizer.end(can_gc);
494        self.document.set_current_parser(None);
495
496        // Step 4.
497        self.document
498            .set_ready_state(DocumentReadyState::Complete, can_gc);
499    }
500
501    // https://html.spec.whatwg.org/multipage/#active-parser
502    pub(crate) fn is_active(&self) -> bool {
503        self.script_nesting_level() > 0 && !self.aborted.get()
504    }
505
506    pub(crate) fn get_current_line(&self) -> u32 {
507        self.tokenizer.get_current_line()
508    }
509
510    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
511    fn new_inherited(
512        document: &Document,
513        tokenizer: Tokenizer,
514        kind: ParserKind,
515        encoding_hint_from_content_type: Option<&'static Encoding>,
516        encoding_of_container_document: Option<&'static Encoding>,
517    ) -> Self {
518        // Store the whole input for the devtools Sources panel, if the devtools server is running
519        // and we are parsing for a document load (not just things like innerHTML).
520        // TODO: check if a devtools client is actually connected and/or wants the sources?
521        let content_for_devtools = (document.global().devtools_chan().is_some() &&
522            document.has_browsing_context())
523        .then_some(DomRefCell::new(String::new()));
524
525        ServoParser {
526            reflector: Reflector::new(),
527            document: Dom::from_ref(document),
528            network_decoder: DomRefCell::new(NetworkDecoderState::new(
529                encoding_hint_from_content_type,
530                encoding_of_container_document,
531            )),
532            network_input: BufferQueue::default(),
533            script_input: BufferQueue::default(),
534            tokenizer,
535            last_chunk_received: Cell::new(false),
536            suspended: Default::default(),
537            script_nesting_level: Default::default(),
538            aborted: Default::default(),
539            script_created_parser: kind == ParserKind::ScriptCreated,
540            prefetch_decoder: RefCell::new(LossyDecoder::new_encoding_rs(
541                encoding_hint_from_content_type.unwrap_or(UTF_8),
542                Default::default(),
543            )),
544            prefetch_tokenizer: prefetch::Tokenizer::new(document),
545            prefetch_input: BufferQueue::default(),
546            content_for_devtools,
547        }
548    }
549
550    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
551    fn new(
552        document: &Document,
553        tokenizer: Tokenizer,
554        kind: ParserKind,
555        encoding_hint_from_content_type: Option<&'static Encoding>,
556        encoding_of_container_document: Option<&'static Encoding>,
557        can_gc: CanGc,
558    ) -> DomRoot<Self> {
559        reflect_dom_object(
560            Box::new(ServoParser::new_inherited(
561                document,
562                tokenizer,
563                kind,
564                encoding_hint_from_content_type,
565                encoding_of_container_document,
566            )),
567            document.window(),
568            can_gc,
569        )
570    }
571
572    fn push_tendril_input_chunk(&self, chunk: StrTendril) {
573        if let Some(mut content_for_devtools) = self
574            .content_for_devtools
575            .as_ref()
576            .map(|content| content.borrow_mut())
577        {
578            // TODO: append these chunks more efficiently
579            content_for_devtools.push_str(chunk.as_ref());
580        }
581
582        if chunk.is_empty() {
583            return;
584        }
585
586        // Push the chunk into the network input stream,
587        // which is tokenized lazily.
588        self.network_input.push_back(chunk);
589    }
590
591    fn push_bytes_input_chunk(&self, chunk: Vec<u8>) {
592        // For byte input, we convert it to text using the network decoder.
593        if let Some(decoded_chunk) = self
594            .network_decoder
595            .borrow_mut()
596            .push(&chunk, &self.document)
597        {
598            self.push_tendril_input_chunk(decoded_chunk);
599        }
600
601        if self.should_prefetch() {
602            // Push the chunk into the prefetch input stream,
603            // which is tokenized eagerly, to scan for resources
604            // to prefetch. If the user script uses `document.write()`
605            // to overwrite the network input, this prefetching may
606            // have been wasted, but in most cases it won't.
607            let mut prefetch_decoder = self.prefetch_decoder.borrow_mut();
608            prefetch_decoder.process(ByteTendril::from(&*chunk));
609
610            self.prefetch_input
611                .push_back(mem::take(&mut prefetch_decoder.inner_sink_mut().output));
612            self.prefetch_tokenizer.feed(&self.prefetch_input);
613        }
614    }
615
616    fn should_prefetch(&self) -> bool {
617        // Per https://github.com/whatwg/html/issues/1495
618        // stylesheets should not be loaded for documents
619        // without browsing contexts.
620        // https://github.com/whatwg/html/issues/1495#issuecomment-230334047
621        // suggests that no content should be preloaded in such a case.
622        // We're conservative, and only prefetch for documents
623        // with browsing contexts.
624        self.document.browsing_context().is_some()
625    }
626
627    fn push_string_input_chunk(&self, chunk: String) {
628        // The input has already been decoded as a string, so doesn't need
629        // to be decoded by the network decoder again.
630        let chunk = StrTendril::from(chunk);
631        self.push_tendril_input_chunk(chunk);
632    }
633
634    fn parse_sync(&self, can_gc: CanGc) {
635        assert!(self.script_input.is_empty());
636
637        // This parser will continue to parse while there is either pending input or
638        // the parser remains unsuspended.
639
640        if self.last_chunk_received.get() {
641            let chunk = self.network_decoder.borrow_mut().finish(&self.document);
642            if !chunk.is_empty() {
643                self.push_tendril_input_chunk(chunk);
644            }
645        }
646
647        if self.aborted.get() {
648            return;
649        }
650
651        let profiler_chan = self
652            .document
653            .window()
654            .as_global_scope()
655            .time_profiler_chan()
656            .clone();
657        let profiler_metadata = TimerMetadata {
658            url: self.document.url().as_str().into(),
659            iframe: TimerMetadataFrameType::RootWindow,
660            incremental: TimerMetadataReflowType::FirstReflow,
661        };
662        self.tokenize(
663            |tokenizer| {
664                tokenizer.feed(
665                    &self.network_input,
666                    can_gc,
667                    profiler_chan.clone(),
668                    profiler_metadata.clone(),
669                )
670            },
671            can_gc,
672        );
673
674        if self.suspended.get() {
675            return;
676        }
677
678        assert!(self.network_input.is_empty());
679
680        if self.last_chunk_received.get() {
681            self.finish(can_gc);
682        }
683    }
684
685    fn parse_complete_string_chunk(&self, input: String, can_gc: CanGc) {
686        self.document.set_current_parser(Some(self));
687        self.push_string_input_chunk(input);
688        self.last_chunk_received.set(true);
689        if !self.suspended.get() {
690            self.parse_sync(can_gc);
691        }
692    }
693
694    fn parse_bytes_chunk(&self, input: Vec<u8>, can_gc: CanGc) {
695        let _realm = enter_realm(&*self.document);
696        self.document.set_current_parser(Some(self));
697        self.push_bytes_input_chunk(input);
698        if !self.suspended.get() {
699            self.parse_sync(can_gc);
700        }
701    }
702
703    fn tokenize<F>(&self, feed: F, can_gc: CanGc)
704    where
705        F: Fn(&Tokenizer) -> TokenizerResult<DomRoot<HTMLScriptElement>>,
706    {
707        loop {
708            assert!(!self.suspended.get());
709            assert!(!self.aborted.get());
710
711            self.document.window().reflow_if_reflow_timer_expired();
712            let script = match feed(&self.tokenizer) {
713                TokenizerResult::Done => return,
714                TokenizerResult::EncodingIndicator(_) => continue,
715                TokenizerResult::Script(script) => script,
716            };
717
718            // https://html.spec.whatwg.org/multipage/#parsing-main-incdata
719            // branch "An end tag whose tag name is "script"
720            // The spec says to perform the microtask checkpoint before
721            // setting the insertion mode back from Text, but this is not
722            // possible with the way servo and html5ever currently
723            // relate to each other, and hopefully it is not observable.
724            if is_execution_stack_empty() {
725                self.document
726                    .window()
727                    .perform_a_microtask_checkpoint(can_gc);
728            }
729
730            let script_nesting_level = self.script_nesting_level.get();
731
732            self.script_nesting_level.set(script_nesting_level + 1);
733            script.set_initial_script_text();
734            let introduction_type_override =
735                (script_nesting_level > 0).then_some(IntroductionType::INJECTED_SCRIPT);
736            script.prepare(introduction_type_override, can_gc);
737            self.script_nesting_level.set(script_nesting_level);
738
739            if self.document.has_pending_parsing_blocking_script() {
740                self.suspended.set(true);
741                return;
742            }
743            if self.aborted.get() {
744                return;
745            }
746        }
747    }
748
749    /// <https://html.spec.whatwg.org/multipage/#the-end>
750    fn finish(&self, can_gc: CanGc) {
751        assert!(!self.suspended.get());
752        assert!(self.last_chunk_received.get());
753        assert!(self.script_input.is_empty());
754        assert!(self.network_input.is_empty());
755        assert!(self.network_decoder.borrow().is_finished());
756
757        // Step 1.
758        self.document
759            .set_ready_state(DocumentReadyState::Interactive, can_gc);
760
761        // Step 2.
762        self.tokenizer.end(can_gc);
763        self.document.set_current_parser(None);
764
765        // Steps 3-12 are in another castle, namely finish_load.
766        let url = self.tokenizer.url().clone();
767        self.document.finish_load(LoadType::PageSource(url), can_gc);
768
769        // Send the source contents to devtools, if needed.
770        if let Some(content_for_devtools) = self
771            .content_for_devtools
772            .as_ref()
773            .map(|content| content.take())
774        {
775            let global = self.document.global();
776            let chan = global.devtools_chan().expect("Guaranteed by new");
777            let pipeline_id = self.document.global().pipeline_id();
778            let _ = chan.send(ScriptToDevtoolsControlMsg::UpdateSourceContent(
779                pipeline_id,
780                content_for_devtools,
781            ));
782        }
783    }
784}
785
786struct FragmentParsingResult<I>
787where
788    I: Iterator<Item = DomRoot<Node>>,
789{
790    inner: I,
791}
792
793impl<I> Iterator for FragmentParsingResult<I>
794where
795    I: Iterator<Item = DomRoot<Node>>,
796{
797    type Item = DomRoot<Node>;
798
799    fn next(&mut self) -> Option<DomRoot<Node>> {
800        let next = self.inner.next()?;
801        next.remove_self(CanGc::note());
802        Some(next)
803    }
804
805    fn size_hint(&self) -> (usize, Option<usize>) {
806        self.inner.size_hint()
807    }
808}
809
810#[derive(JSTraceable, MallocSizeOf, PartialEq)]
811enum ParserKind {
812    Normal,
813    ScriptCreated,
814}
815
816#[derive(JSTraceable, MallocSizeOf)]
817#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
818enum Tokenizer {
819    Html(self::html::Tokenizer),
820    AsyncHtml(self::async_html::Tokenizer),
821    Xml(self::xml::Tokenizer),
822}
823
824impl Tokenizer {
825    fn feed(
826        &self,
827        input: &BufferQueue,
828        can_gc: CanGc,
829        profiler_chan: ProfilerChan,
830        profiler_metadata: TimerMetadata,
831    ) -> TokenizerResult<DomRoot<HTMLScriptElement>> {
832        match *self {
833            Tokenizer::Html(ref tokenizer) => time_profile!(
834                ProfilerCategory::ScriptParseHTML,
835                Some(profiler_metadata),
836                profiler_chan,
837                || tokenizer.feed(input),
838            ),
839            Tokenizer::AsyncHtml(ref tokenizer) => time_profile!(
840                ProfilerCategory::ScriptParseHTML,
841                Some(profiler_metadata),
842                profiler_chan,
843                || tokenizer.feed(input, can_gc),
844            ),
845            Tokenizer::Xml(ref tokenizer) => time_profile!(
846                ProfilerCategory::ScriptParseXML,
847                Some(profiler_metadata),
848                profiler_chan,
849                || tokenizer.feed(input),
850            ),
851        }
852    }
853
854    fn end(&self, can_gc: CanGc) {
855        match *self {
856            Tokenizer::Html(ref tokenizer) => tokenizer.end(),
857            Tokenizer::AsyncHtml(ref tokenizer) => tokenizer.end(can_gc),
858            Tokenizer::Xml(ref tokenizer) => tokenizer.end(),
859        }
860    }
861
862    fn url(&self) -> &ServoUrl {
863        match *self {
864            Tokenizer::Html(ref tokenizer) => tokenizer.url(),
865            Tokenizer::AsyncHtml(ref tokenizer) => tokenizer.url(),
866            Tokenizer::Xml(ref tokenizer) => tokenizer.url(),
867        }
868    }
869
870    fn set_plaintext_state(&self) {
871        match *self {
872            Tokenizer::Html(ref tokenizer) => tokenizer.set_plaintext_state(),
873            Tokenizer::AsyncHtml(ref tokenizer) => tokenizer.set_plaintext_state(),
874            Tokenizer::Xml(_) => unimplemented!(),
875        }
876    }
877
878    fn get_current_line(&self) -> u32 {
879        match *self {
880            Tokenizer::Html(ref tokenizer) => tokenizer.get_current_line(),
881            Tokenizer::AsyncHtml(ref tokenizer) => tokenizer.get_current_line(),
882            Tokenizer::Xml(ref tokenizer) => tokenizer.get_current_line(),
883        }
884    }
885}
886
887/// <https://html.spec.whatwg.org/multipage/#navigation-params>
888/// This does not have the relevant fields, but mimics the intent
889/// of the struct when used in loading document spec algorithms.
890struct NavigationParams {
891    /// <https://html.spec.whatwg.org/multipage/#navigation-params-policy-container>
892    policy_container: PolicyContainer,
893    /// content-type of this document, if known. Otherwise need to sniff it
894    content_type: Option<Mime>,
895    /// link headers from the response
896    link_headers: Vec<LinkHeader>,
897    /// <https://html.spec.whatwg.org/multipage/#navigation-params-sandboxing>
898    final_sandboxing_flag_set: SandboxingFlagSet,
899    /// <https://mimesniff.spec.whatwg.org/#resource-header>
900    resource_header: Vec<u8>,
901    /// <https://html.spec.whatwg.org/multipage/#navigation-params-about-base-url>
902    about_base_url: Option<ServoUrl>,
903}
904
905/// The context required for asynchronously fetching a document
906/// and parsing it progressively.
907pub(crate) struct ParserContext {
908    /// The parser that initiated the request.
909    parser: Option<Trusted<ServoParser>>,
910    /// Is this a synthesized document
911    is_synthesized_document: bool,
912    /// Has a document already been loaded (relevant for checking the resource header)
913    has_loaded_document: bool,
914    /// The [`WebViewId`] of the `WebView` associated with this document.
915    webview_id: WebViewId,
916    /// The [`PipelineId`] of the `Pipeline` associated with this document.
917    pipeline_id: PipelineId,
918    /// The URL for this document.
919    url: ServoUrl,
920    /// pushed entry index
921    pushed_entry_index: Option<usize>,
922    /// params required in document load algorithms
923    navigation_params: NavigationParams,
924}
925
926impl ParserContext {
927    pub(crate) fn new(
928        webview_id: WebViewId,
929        pipeline_id: PipelineId,
930        url: ServoUrl,
931        creation_sandboxing_flag_set: SandboxingFlagSet,
932    ) -> ParserContext {
933        ParserContext {
934            parser: None,
935            is_synthesized_document: false,
936            has_loaded_document: false,
937            webview_id,
938            pipeline_id,
939            url,
940            pushed_entry_index: None,
941            navigation_params: NavigationParams {
942                policy_container: Default::default(),
943                content_type: None,
944                link_headers: vec![],
945                final_sandboxing_flag_set: creation_sandboxing_flag_set,
946                resource_header: vec![],
947                about_base_url: Default::default(),
948            },
949        }
950    }
951
952    pub(crate) fn set_policy_container(&mut self, policy_container: Option<&PolicyContainer>) {
953        let Some(policy_container) = policy_container else {
954            return;
955        };
956        self.navigation_params.policy_container = policy_container.clone();
957    }
958
959    pub(crate) fn set_about_base_url(&mut self, about_base_url: Option<ServoUrl>) {
960        self.navigation_params.about_base_url = about_base_url;
961    }
962
963    pub(crate) fn get_document(&self) -> Option<DomRoot<Document>> {
964        self.parser
965            .as_ref()
966            .map(|parser| parser.root().document.as_rooted())
967    }
968
969    /// <https://html.spec.whatwg.org/multipage/#creating-a-policy-container-from-a-fetch-response>
970    fn create_policy_container_from_fetch_response(metadata: &Metadata) -> PolicyContainer {
971        // Step 1. If response's URL's scheme is "blob", then return a clone of response's URL's blob URL entry's environment's policy container.
972        // TODO
973        // Step 2. Let result be a new policy container.
974        // Step 7. Return result.
975        PolicyContainer {
976            // Step 3. Set result's CSP list to the result of parsing a response's Content Security Policies given response.
977            csp_list: parse_csp_list_from_metadata(&metadata.headers),
978            // Step 5. Set result's referrer policy to the result of parsing the `Referrer-Policy` header given response. [REFERRERPOLICY]
979            referrer_policy: ReferrerPolicy::parse_header_for_response(&metadata.headers),
980        }
981    }
982
983    /// <https://html.spec.whatwg.org/multipage/#initialise-the-document-object>
984    fn initialize_document_object(&self, document: &Document) {
985        // Step 9. Let document be a new Document, with
986        document.set_policy_container(self.navigation_params.policy_container.clone());
987        document.set_active_sandboxing_flag_set(self.navigation_params.final_sandboxing_flag_set);
988        document.set_about_base_url(self.navigation_params.about_base_url.clone());
989        // Step 17. Process link headers given document, navigationParams's response, and "pre-media".
990        process_link_headers(
991            &self.navigation_params.link_headers,
992            document,
993            LinkProcessingPhase::PreMedia,
994        );
995    }
996
997    /// Part of various load document methods
998    fn process_link_headers_in_media_phase_with_task(&mut self, document: &Document) {
999        // The first task that the networking task source places on the task queue
1000        // while fetching runs must process link headers given document,
1001        // navigationParams's response, and "media", after the task has been processed by the HTML parser.
1002        let link_headers = std::mem::take(&mut self.navigation_params.link_headers);
1003        if !link_headers.is_empty() {
1004            let window = document.window();
1005            let document = Trusted::new(document);
1006            window
1007                .upcast::<GlobalScope>()
1008                .task_manager()
1009                .networking_task_source()
1010                .queue(task!(process_link_headers_task: move || {
1011                    process_link_headers(&link_headers, &document.root(), LinkProcessingPhase::Media);
1012                }));
1013        }
1014    }
1015
1016    /// <https://html.spec.whatwg.org/multipage/#loading-a-document>
1017    fn load_document(&mut self, can_gc: CanGc) {
1018        assert!(!self.has_loaded_document);
1019        self.has_loaded_document = true;
1020        let Some(ref parser) = self.parser.as_ref().map(|p| p.root()) else {
1021            return;
1022        };
1023        // Step 1. Let type be the computed type of navigationParams's response.
1024        let content_type = &self.navigation_params.content_type;
1025        let mime_type = MimeClassifier::default().classify(
1026            LoadContext::Browsing,
1027            NoSniffFlag::Off,
1028            ApacheBugFlag::from_content_type(content_type.as_ref()),
1029            content_type,
1030            &self.navigation_params.resource_header,
1031        );
1032        // Step 2. If the user agent has been configured to process resources of the given type using
1033        // some mechanism other than rendering the content in a navigable, then skip this step.
1034        // Otherwise, if the type is one of the following types:
1035        let Some(media_type) = MimeClassifier::get_media_type(&mime_type) else {
1036            let page = format!(
1037                "<html><body><p>Unknown content type ({}).</p></body></html>",
1038                &mime_type,
1039            );
1040            self.load_inline_unknown_content(parser, page);
1041            return;
1042        };
1043        match media_type {
1044            // Return the result of loading an HTML document, given navigationParams.
1045            MediaType::Html => self.load_html_document(parser),
1046            // Return the result of loading an XML document given navigationParams and type.
1047            MediaType::Xml => self.load_xml_document(parser),
1048            // Return the result of loading a text document given navigationParams and type.
1049            MediaType::JavaScript | MediaType::Json | MediaType::Text | MediaType::Css => {
1050                self.load_text_document(parser)
1051            },
1052            // Return the result of loading a media document given navigationParams and type.
1053            MediaType::Image | MediaType::AudioVideo => {
1054                self.load_media_document(parser, media_type, &mime_type);
1055                return;
1056            },
1057            MediaType::Font => {
1058                let page = format!(
1059                    "<html><body><p>Unable to load font with content type ({}).</p></body></html>",
1060                    &mime_type,
1061                );
1062                self.load_inline_unknown_content(parser, page);
1063                return;
1064            },
1065        };
1066
1067        parser.parse_bytes_chunk(
1068            std::mem::take(&mut self.navigation_params.resource_header),
1069            can_gc,
1070        );
1071    }
1072
1073    /// <https://html.spec.whatwg.org/multipage/#navigate-html>
1074    fn load_html_document(&mut self, parser: &ServoParser) {
1075        // Step 1. Let document be the result of creating and initializing a
1076        // Document object given "html", "text/html", and navigationParams.
1077        self.initialize_document_object(&parser.document);
1078        // The first task that the networking task source places on the task queue while fetching
1079        // runs must process link headers given document, navigationParams's response, and "media",
1080        // after the task has been processed by the HTML parser.
1081        self.process_link_headers_in_media_phase_with_task(&parser.document);
1082    }
1083
1084    /// <https://html.spec.whatwg.org/multipage/#read-xml>
1085    fn load_xml_document(&mut self, parser: &ServoParser) {
1086        // When faced with displaying an XML file inline, provided navigation params navigationParams
1087        // and a string type, user agents must follow the requirements defined in XML and Namespaces in XML,
1088        // XML Media Types, DOM, and other relevant specifications to create and initialize a
1089        // Document object document, given "xml", type, and navigationParams, and return that Document.
1090        // They must also create a corresponding XML parser. [XML] [XMLNS] [RFC7303] [DOM]
1091        self.initialize_document_object(&parser.document);
1092        // The first task that the networking task source places on the task queue while fetching
1093        // runs must process link headers given document, navigationParams's response, and "media",
1094        // after the task has been processed by the XML parser.
1095        self.process_link_headers_in_media_phase_with_task(&parser.document);
1096    }
1097
1098    /// <https://html.spec.whatwg.org/multipage/#navigate-text>
1099    fn load_text_document(&mut self, parser: &ServoParser) {
1100        // Step 1. Let document be the result of creating and initializing a Document
1101        // object given "html", type, and navigationParams.
1102        self.initialize_document_object(&parser.document);
1103        // Step 4. Create an HTML parser and associate it with the document.
1104        // Act as if the tokenizer had emitted a start tag token with the tag name "pre" followed by
1105        // a single U+000A LINE FEED (LF) character, and switch the HTML parser's tokenizer to the PLAINTEXT state.
1106        // Each task that the networking task source places on the task queue while fetching runs must then
1107        // fill the parser's input byte stream with the fetched bytes and cause the HTML parser to perform
1108        // the appropriate processing of the input stream.
1109        let page = "<pre>\n".into();
1110        parser.push_string_input_chunk(page);
1111        parser.parse_sync(CanGc::note());
1112        parser.tokenizer.set_plaintext_state();
1113        // The first task that the networking task source places on the task queue while fetching
1114        // runs must process link headers given document, navigationParams's response, and "media",
1115        // after the task has been processed by the HTML parser.
1116        self.process_link_headers_in_media_phase_with_task(&parser.document);
1117    }
1118
1119    /// <https://html.spec.whatwg.org/multipage/#navigate-media>
1120    fn load_media_document(
1121        &mut self,
1122        parser: &ServoParser,
1123        media_type: MediaType,
1124        mime_type: &Mime,
1125    ) {
1126        // Step 1. Let document be the result of creating and initializing a Document
1127        // object given "html", type, and navigationParams.
1128        self.initialize_document_object(&parser.document);
1129        // Step 8. Act as if the user agent had stopped parsing document.
1130        self.is_synthesized_document = true;
1131        // Step 3. Populate with html/head/body given document.
1132        let page = "<html><body></body></html>".into();
1133        parser.push_string_input_chunk(page);
1134        parser.parse_sync(CanGc::note());
1135
1136        let doc = &parser.document;
1137        // Step 5. Set the appropriate attribute of the element host element, as described below,
1138        // to the address of the image, video, or audio resource.
1139        let node = if media_type == MediaType::Image {
1140            let img = Element::create(
1141                QualName::new(None, ns!(html), local_name!("img")),
1142                None,
1143                doc,
1144                ElementCreator::ParserCreated(1),
1145                CustomElementCreationMode::Asynchronous,
1146                None,
1147                CanGc::note(),
1148            );
1149            let img = DomRoot::downcast::<HTMLImageElement>(img).unwrap();
1150            img.SetSrc(USVString(self.url.to_string()));
1151            DomRoot::upcast::<Node>(img)
1152        } else if mime_type.type_() == mime::AUDIO {
1153            let audio = Element::create(
1154                QualName::new(None, ns!(html), local_name!("audio")),
1155                None,
1156                doc,
1157                ElementCreator::ParserCreated(1),
1158                CustomElementCreationMode::Asynchronous,
1159                None,
1160                CanGc::note(),
1161            );
1162            let audio = DomRoot::downcast::<HTMLMediaElement>(audio).unwrap();
1163            audio.SetControls(true);
1164            audio.SetSrc(USVString(self.url.to_string()));
1165            DomRoot::upcast::<Node>(audio)
1166        } else {
1167            let video = Element::create(
1168                QualName::new(None, ns!(html), local_name!("video")),
1169                None,
1170                doc,
1171                ElementCreator::ParserCreated(1),
1172                CustomElementCreationMode::Asynchronous,
1173                None,
1174                CanGc::note(),
1175            );
1176            let video = DomRoot::downcast::<HTMLMediaElement>(video).unwrap();
1177            video.SetControls(true);
1178            video.SetSrc(USVString(self.url.to_string()));
1179            DomRoot::upcast::<Node>(video)
1180        };
1181        // Step 4. Append an element host element for the media, as described below, to the body element.
1182        let doc_body = DomRoot::upcast::<Node>(doc.GetBody().unwrap());
1183        doc_body
1184            .AppendChild(&node, CanGc::note())
1185            .expect("Appending failed");
1186        // Step 7. Process link headers given document, navigationParams's response, and "media".
1187        let link_headers = std::mem::take(&mut self.navigation_params.link_headers);
1188        process_link_headers(&link_headers, doc, LinkProcessingPhase::Media);
1189    }
1190
1191    /// <https://html.spec.whatwg.org/multipage/#read-ua-inline>
1192    fn load_inline_unknown_content(&mut self, parser: &ServoParser, page: String) {
1193        self.is_synthesized_document = true;
1194        parser.push_string_input_chunk(page);
1195        parser.parse_sync(CanGc::note());
1196    }
1197
1198    /// Store a PerformanceNavigationTiming entry in the globalscope's Performance buffer
1199    fn submit_resource_timing(&mut self) {
1200        let Some(parser) = self.parser.as_ref() else {
1201            return;
1202        };
1203        let parser = parser.root();
1204        if parser.aborted.get() {
1205            return;
1206        }
1207
1208        let document = &parser.document;
1209
1210        // TODO: Pass a proper fetch start time here.
1211        let performance_entry = PerformanceNavigationTiming::new(
1212            &document.global(),
1213            CrossProcessInstant::now(),
1214            document,
1215            CanGc::note(),
1216        );
1217        self.pushed_entry_index = document
1218            .global()
1219            .performance()
1220            .queue_entry(performance_entry.upcast::<PerformanceEntry>());
1221    }
1222}
1223
1224impl FetchResponseListener for ParserContext {
1225    fn process_request_body(&mut self, _: RequestId) {}
1226
1227    fn process_request_eof(&mut self, _: RequestId) {}
1228
1229    fn process_response(&mut self, _: RequestId, meta_result: Result<FetchMetadata, NetworkError>) {
1230        let (metadata, error) = match meta_result {
1231            Ok(meta) => (
1232                Some(match meta {
1233                    FetchMetadata::Unfiltered(m) => m,
1234                    FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
1235                }),
1236                None,
1237            ),
1238            Err(error) => (
1239                // Check variant without moving
1240                match &error {
1241                    NetworkError::LoadCancelled => {
1242                        return;
1243                    },
1244                    _ => {
1245                        let mut meta = Metadata::default(self.url.clone());
1246                        let mime: Option<Mime> = "text/html".parse().ok();
1247                        meta.set_content_type(mime.as_ref());
1248                        Some(meta)
1249                    },
1250                },
1251                Some(error),
1252            ),
1253        };
1254        let content_type: Option<Mime> = metadata
1255            .clone()
1256            .and_then(|meta| meta.content_type)
1257            .map(Serde::into_inner)
1258            .map(Into::into);
1259
1260        let (policy_container, endpoints_list, link_headers) = match metadata.as_ref() {
1261            None => (PolicyContainer::default(), None, vec![]),
1262            Some(metadata) => (
1263                Self::create_policy_container_from_fetch_response(metadata),
1264                ReportingEndpoint::parse_reporting_endpoints_header(
1265                    &self.url.clone(),
1266                    &metadata.headers,
1267                ),
1268                extract_links_from_headers(&metadata.headers),
1269            ),
1270        };
1271
1272        let parser = match ScriptThread::page_headers_available(
1273            self.webview_id,
1274            self.pipeline_id,
1275            metadata,
1276            CanGc::note(),
1277        ) {
1278            Some(parser) => parser,
1279            None => return,
1280        };
1281        if parser.aborted.get() {
1282            return;
1283        }
1284
1285        let _realm = enter_realm(&*parser.document);
1286        let window = parser.document.window();
1287
1288        // From Step 23.8.3 of https://html.spec.whatwg.org/multipage/#navigate
1289        // Let finalSandboxFlags be the union of targetSnapshotParams's sandboxing flags and
1290        // policyContainer's CSP list's CSP-derived sandboxing flags.
1291        //
1292        // TODO: This deviates a bit from the specification, because there isn't a `targetSnapshotParam`
1293        // concept yet.
1294        let final_sandboxing_flag_set = policy_container
1295            .csp_list
1296            .as_ref()
1297            .and_then(|csp| csp.get_sandboxing_flag_set_for_document())
1298            .unwrap_or(SandboxingFlagSet::empty())
1299            .union(parser.document.creation_sandboxing_flag_set());
1300
1301        if let Some(endpoints) = endpoints_list {
1302            window.set_endpoints_list(endpoints);
1303        }
1304        self.parser = Some(Trusted::new(&*parser));
1305        self.navigation_params = NavigationParams {
1306            policy_container,
1307            content_type,
1308            final_sandboxing_flag_set,
1309            link_headers,
1310            about_base_url: parser.document.about_base_url(),
1311            resource_header: vec![],
1312        };
1313        self.submit_resource_timing();
1314
1315        // Part of https://html.spec.whatwg.org/multipage/#loading-a-document
1316        //
1317        // Step 3. If, given type, the new resource is to be handled by displaying some sort of inline content,
1318        // e.g., a native rendering of the content or an error message because the specified type is not supported,
1319        // then return the result of creating a document for inline content that doesn't have a DOM given
1320        // navigationParams's navigable, navigationParams's id, navigationParams's navigation timing type,
1321        // and navigationParams's user involvement.
1322        if let Some(error) = error {
1323            let page = match error {
1324                NetworkError::SslValidation(reason, bytes) => {
1325                    let page = resources::read_string(Resource::BadCertHTML);
1326                    let page = page.replace("${reason}", &reason);
1327                    let encoded_bytes = general_purpose::STANDARD_NO_PAD.encode(bytes);
1328                    let page = page.replace("${bytes}", encoded_bytes.as_str());
1329                    page.replace("${secret}", &net_traits::PRIVILEGED_SECRET.to_string())
1330                },
1331                NetworkError::BlobURLStoreError(reason) |
1332                NetworkError::WebsocketConnectionFailure(reason) |
1333                NetworkError::HttpError(reason) |
1334                NetworkError::ResourceLoadError(reason) |
1335                NetworkError::MimeType(reason) => {
1336                    let page = resources::read_string(Resource::NetErrorHTML);
1337                    page.replace("${reason}", &reason)
1338                },
1339                NetworkError::Crash(details) => {
1340                    let page = resources::read_string(Resource::CrashHTML);
1341                    page.replace("${details}", &details)
1342                },
1343                NetworkError::UnsupportedScheme |
1344                NetworkError::CorsGeneral |
1345                NetworkError::CrossOriginResponse |
1346                NetworkError::CorsCredentials |
1347                NetworkError::CorsAllowMethods |
1348                NetworkError::CorsAllowHeaders |
1349                NetworkError::CorsMethod |
1350                NetworkError::CorsAuthorization |
1351                NetworkError::CorsHeaders |
1352                NetworkError::ConnectionFailure |
1353                NetworkError::RedirectError |
1354                NetworkError::TooManyRedirects |
1355                NetworkError::TooManyInFlightKeepAliveRequests |
1356                NetworkError::InvalidMethod |
1357                NetworkError::ContentSecurityPolicy |
1358                NetworkError::Nosniff |
1359                NetworkError::SubresourceIntegrity |
1360                NetworkError::MixedContent |
1361                NetworkError::CacheError |
1362                NetworkError::InvalidPort |
1363                NetworkError::LocalDirectoryError |
1364                NetworkError::PartialResponseToNonRangeRequestError |
1365                NetworkError::ProtocolHandlerSubstitutionError |
1366                NetworkError::DecompressionError => {
1367                    let page = resources::read_string(Resource::NetErrorHTML);
1368                    page.replace("${reason}", &format!("{:?}", error))
1369                },
1370                NetworkError::LoadCancelled => {
1371                    // The next load will show a page
1372                    return;
1373                },
1374            };
1375            self.load_inline_unknown_content(&parser, page);
1376        }
1377    }
1378
1379    fn process_response_chunk(&mut self, _: RequestId, payload: Vec<u8>) {
1380        if self.is_synthesized_document {
1381            return;
1382        }
1383        let Some(parser) = self.parser.as_ref().map(|p| p.root()) else {
1384            return;
1385        };
1386        if parser.aborted.get() {
1387            return;
1388        }
1389        if !self.has_loaded_document {
1390            // https://mimesniff.spec.whatwg.org/#read-the-resource-header
1391            self.navigation_params
1392                .resource_header
1393                .extend_from_slice(&payload);
1394            // the number of bytes in buffer is greater than or equal to 1445.
1395            if self.navigation_params.resource_header.len() >= 1445 {
1396                self.load_document(CanGc::note());
1397            }
1398        } else {
1399            parser.parse_bytes_chunk(payload, CanGc::note());
1400        }
1401    }
1402
1403    // This method is called via script_thread::handle_fetch_eof, so we must call
1404    // submit_resource_timing in this function
1405    // Resource listeners are called via net_traits::Action::process, which handles submission for them
1406    fn process_response_eof(
1407        mut self,
1408        cx: &mut js::context::JSContext,
1409        _: RequestId,
1410        status: Result<(), NetworkError>,
1411        timing: ResourceFetchTiming,
1412    ) {
1413        let parser = match self.parser.as_ref() {
1414            Some(parser) => parser.root(),
1415            None => return,
1416        };
1417        if parser.aborted.get() {
1418            return;
1419        }
1420
1421        if let Err(error) = &status {
1422            // TODO(Savago): we should send a notification to callers #5463.
1423            debug!("Failed to load page URL {}, error: {error:?}", self.url);
1424        }
1425
1426        // https://mimesniff.spec.whatwg.org/#read-the-resource-header
1427        //
1428        // the end of the resource is reached.
1429        if !self.has_loaded_document {
1430            self.load_document(CanGc::from_cx(cx));
1431        }
1432
1433        let _realm = enter_realm(&*parser);
1434
1435        if status.is_ok() {
1436            parser.document.set_redirect_count(timing.redirect_count);
1437        }
1438
1439        parser.last_chunk_received.set(true);
1440        if !parser.suspended.get() {
1441            parser.parse_sync(CanGc::from_cx(cx));
1442        }
1443
1444        // TODO: Only update if this is the current document resource.
1445        // TODO(mrobinson): Pass a proper fetch_start parameter here instead of `CrossProcessInstant::now()`.
1446        if let Some(pushed_index) = self.pushed_entry_index {
1447            let document = &parser.document;
1448            let performance_entry = PerformanceNavigationTiming::new(
1449                &document.global(),
1450                CrossProcessInstant::now(),
1451                document,
1452                CanGc::from_cx(cx),
1453            );
1454            document
1455                .global()
1456                .performance()
1457                .update_entry(pushed_index, performance_entry.upcast::<PerformanceEntry>());
1458        }
1459    }
1460
1461    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
1462        let parser = match self.parser.as_ref() {
1463            Some(parser) => parser.root(),
1464            None => return,
1465        };
1466        let document = &parser.document;
1467        let global = &document.global();
1468        // TODO(https://github.com/w3c/webappsec-csp/issues/687): Update after spec is resolved
1469        global.report_csp_violations(violations, None, None);
1470    }
1471}
1472
1473pub(crate) struct FragmentContext<'a> {
1474    pub(crate) context_elem: &'a Node,
1475    pub(crate) form_elem: Option<&'a Node>,
1476    pub(crate) context_element_allows_scripting: bool,
1477}
1478
1479#[cfg_attr(crown, expect(crown::unrooted_must_root))]
1480fn insert(
1481    parent: &Node,
1482    reference_child: Option<&Node>,
1483    child: NodeOrText<Dom<Node>>,
1484    parsing_algorithm: ParsingAlgorithm,
1485    custom_element_reaction_stack: &CustomElementReactionStack,
1486    can_gc: CanGc,
1487) {
1488    match child {
1489        NodeOrText::AppendNode(n) => {
1490            // https://html.spec.whatwg.org/multipage/#insert-a-foreign-element
1491            // applies if this is an element; if not, it may be
1492            // https://html.spec.whatwg.org/multipage/#insert-a-comment
1493            let element_in_non_fragment =
1494                parsing_algorithm != ParsingAlgorithm::Fragment && n.is::<Element>();
1495            if element_in_non_fragment {
1496                custom_element_reaction_stack.push_new_element_queue();
1497            }
1498            parent.InsertBefore(&n, reference_child, can_gc).unwrap();
1499            if element_in_non_fragment {
1500                custom_element_reaction_stack.pop_current_element_queue(can_gc);
1501            }
1502        },
1503        NodeOrText::AppendText(t) => {
1504            // https://html.spec.whatwg.org/multipage/#insert-a-character
1505            let text = reference_child
1506                .and_then(Node::GetPreviousSibling)
1507                .or_else(|| parent.GetLastChild())
1508                .and_then(DomRoot::downcast::<Text>);
1509
1510            if let Some(text) = text {
1511                text.upcast::<CharacterData>().append_data(&t);
1512            } else {
1513                let text = Text::new(String::from(t).into(), &parent.owner_doc(), can_gc);
1514                parent
1515                    .InsertBefore(text.upcast(), reference_child, can_gc)
1516                    .unwrap();
1517            }
1518        },
1519    }
1520}
1521
1522#[derive(JSTraceable, MallocSizeOf)]
1523#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1524pub(crate) struct Sink {
1525    #[no_trace]
1526    base_url: ServoUrl,
1527    document: Dom<Document>,
1528    current_line: Cell<u64>,
1529    script: MutNullableDom<HTMLScriptElement>,
1530    parsing_algorithm: ParsingAlgorithm,
1531    #[conditional_malloc_size_of]
1532    custom_element_reaction_stack: Rc<CustomElementReactionStack>,
1533}
1534
1535impl Sink {
1536    fn same_tree(&self, x: &Dom<Node>, y: &Dom<Node>) -> bool {
1537        let x = x.downcast::<Element>().expect("Element node expected");
1538        let y = y.downcast::<Element>().expect("Element node expected");
1539
1540        x.is_in_same_home_subtree(y)
1541    }
1542
1543    fn has_parent_node(&self, node: &Dom<Node>) -> bool {
1544        node.GetParentNode().is_some()
1545    }
1546}
1547
1548impl TreeSink for Sink {
1549    type Output = Self;
1550    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1551    fn finish(self) -> Self {
1552        self
1553    }
1554
1555    type Handle = Dom<Node>;
1556    type ElemName<'a>
1557        = ExpandedName<'a>
1558    where
1559        Self: 'a;
1560
1561    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1562    fn get_document(&self) -> Dom<Node> {
1563        Dom::from_ref(self.document.upcast())
1564    }
1565
1566    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1567    fn get_template_contents(&self, target: &Dom<Node>) -> Dom<Node> {
1568        let template = target
1569            .downcast::<HTMLTemplateElement>()
1570            .expect("tried to get template contents of non-HTMLTemplateElement in HTML parsing");
1571        Dom::from_ref(template.Content(CanGc::note()).upcast())
1572    }
1573
1574    fn same_node(&self, x: &Dom<Node>, y: &Dom<Node>) -> bool {
1575        x == y
1576    }
1577
1578    fn elem_name<'a>(&self, target: &'a Dom<Node>) -> ExpandedName<'a> {
1579        let elem = target
1580            .downcast::<Element>()
1581            .expect("tried to get name of non-Element in HTML parsing");
1582        ExpandedName {
1583            ns: elem.namespace(),
1584            local: elem.local_name(),
1585        }
1586    }
1587
1588    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1589    fn create_element(
1590        &self,
1591        name: QualName,
1592        attrs: Vec<Attribute>,
1593        flags: ElementFlags,
1594    ) -> Dom<Node> {
1595        let attrs = attrs
1596            .into_iter()
1597            .map(|attr| ElementAttribute::new(attr.name, DOMString::from(String::from(attr.value))))
1598            .collect();
1599        let parsing_algorithm = if flags.template {
1600            ParsingAlgorithm::Fragment
1601        } else {
1602            self.parsing_algorithm
1603        };
1604        let element = create_element_for_token(
1605            name,
1606            attrs,
1607            &self.document,
1608            ElementCreator::ParserCreated(self.current_line.get()),
1609            parsing_algorithm,
1610            &self.custom_element_reaction_stack,
1611            CanGc::note(),
1612        );
1613        Dom::from_ref(element.upcast())
1614    }
1615
1616    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1617    fn create_comment(&self, text: StrTendril) -> Dom<Node> {
1618        let comment = Comment::new(
1619            DOMString::from(String::from(text)),
1620            &self.document,
1621            None,
1622            CanGc::note(),
1623        );
1624        Dom::from_ref(comment.upcast())
1625    }
1626
1627    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1628    fn create_pi(&self, target: StrTendril, data: StrTendril) -> Dom<Node> {
1629        let doc = &*self.document;
1630        let pi = ProcessingInstruction::new(
1631            DOMString::from(String::from(target)),
1632            DOMString::from(String::from(data)),
1633            doc,
1634            CanGc::note(),
1635        );
1636        Dom::from_ref(pi.upcast())
1637    }
1638
1639    fn associate_with_form(
1640        &self,
1641        target: &Dom<Node>,
1642        form: &Dom<Node>,
1643        nodes: (&Dom<Node>, Option<&Dom<Node>>),
1644    ) {
1645        let (element, prev_element) = nodes;
1646        let tree_node = prev_element.map_or(element, |prev| {
1647            if self.has_parent_node(element) {
1648                element
1649            } else {
1650                prev
1651            }
1652        });
1653        if !self.same_tree(tree_node, form) {
1654            return;
1655        }
1656
1657        let node = target;
1658        let form = DomRoot::downcast::<HTMLFormElement>(DomRoot::from_ref(&**form))
1659            .expect("Owner must be a form element");
1660
1661        let elem = node.downcast::<Element>();
1662        let control = elem.and_then(|e| e.as_maybe_form_control());
1663
1664        if let Some(control) = control {
1665            control.set_form_owner_from_parser(&form, CanGc::note());
1666        }
1667    }
1668
1669    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1670    fn append_before_sibling(&self, sibling: &Dom<Node>, new_node: NodeOrText<Dom<Node>>) {
1671        let parent = sibling
1672            .GetParentNode()
1673            .expect("append_before_sibling called on node without parent");
1674
1675        insert(
1676            &parent,
1677            Some(sibling),
1678            new_node,
1679            self.parsing_algorithm,
1680            &self.custom_element_reaction_stack,
1681            CanGc::note(),
1682        );
1683    }
1684
1685    fn parse_error(&self, msg: Cow<'static, str>) {
1686        debug!("Parse error: {}", msg);
1687    }
1688
1689    fn set_quirks_mode(&self, mode: QuirksMode) {
1690        let mode = match mode {
1691            QuirksMode::Quirks => ServoQuirksMode::Quirks,
1692            QuirksMode::LimitedQuirks => ServoQuirksMode::LimitedQuirks,
1693            QuirksMode::NoQuirks => ServoQuirksMode::NoQuirks,
1694        };
1695        self.document.set_quirks_mode(mode);
1696    }
1697
1698    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1699    fn append(&self, parent: &Dom<Node>, child: NodeOrText<Dom<Node>>) {
1700        insert(
1701            parent,
1702            None,
1703            child,
1704            self.parsing_algorithm,
1705            &self.custom_element_reaction_stack,
1706            CanGc::note(),
1707        );
1708    }
1709
1710    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1711    fn append_based_on_parent_node(
1712        &self,
1713        elem: &Dom<Node>,
1714        prev_elem: &Dom<Node>,
1715        child: NodeOrText<Dom<Node>>,
1716    ) {
1717        if self.has_parent_node(elem) {
1718            self.append_before_sibling(elem, child);
1719        } else {
1720            self.append(prev_elem, child);
1721        }
1722    }
1723
1724    fn append_doctype_to_document(
1725        &self,
1726        name: StrTendril,
1727        public_id: StrTendril,
1728        system_id: StrTendril,
1729    ) {
1730        let doc = &*self.document;
1731        let doctype = DocumentType::new(
1732            DOMString::from(String::from(name)),
1733            Some(DOMString::from(String::from(public_id))),
1734            Some(DOMString::from(String::from(system_id))),
1735            doc,
1736            CanGc::note(),
1737        );
1738        doc.upcast::<Node>()
1739            .AppendChild(doctype.upcast(), CanGc::note())
1740            .expect("Appending failed");
1741    }
1742
1743    fn add_attrs_if_missing(&self, target: &Dom<Node>, attrs: Vec<Attribute>) {
1744        let elem = target
1745            .downcast::<Element>()
1746            .expect("tried to set attrs on non-Element in HTML parsing");
1747        for attr in attrs {
1748            elem.set_attribute_from_parser(
1749                attr.name,
1750                DOMString::from(String::from(attr.value)),
1751                None,
1752                CanGc::note(),
1753            );
1754        }
1755    }
1756
1757    fn remove_from_parent(&self, target: &Dom<Node>) {
1758        if let Some(ref parent) = target.GetParentNode() {
1759            parent.RemoveChild(target, CanGc::note()).unwrap();
1760        }
1761    }
1762
1763    fn mark_script_already_started(&self, node: &Dom<Node>) {
1764        let script = node.downcast::<HTMLScriptElement>();
1765        if let Some(script) = script {
1766            script.set_already_started(true)
1767        }
1768    }
1769
1770    fn reparent_children(&self, node: &Dom<Node>, new_parent: &Dom<Node>) {
1771        while let Some(ref child) = node.GetFirstChild() {
1772            new_parent.AppendChild(child, CanGc::note()).unwrap();
1773        }
1774    }
1775
1776    /// <https://html.spec.whatwg.org/multipage/#html-integration-point>
1777    /// Specifically, the `<annotation-xml>` cases.
1778    fn is_mathml_annotation_xml_integration_point(&self, handle: &Dom<Node>) -> bool {
1779        let elem = handle.downcast::<Element>().unwrap();
1780        elem.get_attribute(&ns!(), &local_name!("encoding"))
1781            .is_some_and(|attr| {
1782                attr.value().eq_ignore_ascii_case("text/html") ||
1783                    attr.value().eq_ignore_ascii_case("application/xhtml+xml")
1784            })
1785    }
1786
1787    fn set_current_line(&self, line_number: u64) {
1788        self.current_line.set(line_number);
1789    }
1790
1791    fn pop(&self, node: &Dom<Node>) {
1792        let node = DomRoot::from_ref(&**node);
1793        vtable_for(&node).pop();
1794    }
1795
1796    fn allow_declarative_shadow_roots(&self, intended_parent: &Dom<Node>) -> bool {
1797        intended_parent.owner_doc().allow_declarative_shadow_roots()
1798    }
1799
1800    /// <https://html.spec.whatwg.org/multipage/#parsing-main-inhead>
1801    /// A start tag whose tag name is "template"
1802    /// Attach shadow path
1803    fn attach_declarative_shadow(
1804        &self,
1805        host: &Dom<Node>,
1806        template: &Dom<Node>,
1807        attributes: &[Attribute],
1808    ) -> bool {
1809        attach_declarative_shadow_inner(host, template, attributes)
1810    }
1811
1812    fn maybe_clone_an_option_into_selectedcontent(&self, option: &Self::Handle) {
1813        let Some(option) = option.downcast::<HTMLOptionElement>() else {
1814            if cfg!(debug_assertions) {
1815                unreachable!();
1816            }
1817            log::error!(
1818                "Received non-option element in maybe_clone_an_option_into_selectedcontent"
1819            );
1820            return;
1821        };
1822
1823        option.maybe_clone_an_option_into_selectedcontent(CanGc::note())
1824    }
1825}
1826
1827/// <https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token>
1828fn create_element_for_token(
1829    name: QualName,
1830    attrs: Vec<ElementAttribute>,
1831    document: &Document,
1832    creator: ElementCreator,
1833    parsing_algorithm: ParsingAlgorithm,
1834    custom_element_reaction_stack: &CustomElementReactionStack,
1835    can_gc: CanGc,
1836) -> DomRoot<Element> {
1837    // Step 1. If the active speculative HTML parser is not null, then return the result
1838    // of creating a speculative mock element given namespace, token's tag name, and
1839    // token's attributes.
1840    // TODO: Implement
1841
1842    // Step 2: Otherwise, optionally create a speculative mock element given namespace,
1843    // token's tag name, and token's attributes
1844    // TODO: Implement.
1845
1846    // Step 3. Let document be intendedParent's node document.
1847    // Passed as argument.
1848
1849    // Step 4. Let localName be token's tag name.
1850    // Passed as argument
1851
1852    // Step 5. Let is be the value of the "is" attribute in token, if such an attribute
1853    // exists; otherwise null.
1854    let is = attrs
1855        .iter()
1856        .find(|attr| attr.name.local.eq_str_ignore_ascii_case("is"))
1857        .map(|attr| LocalName::from(&attr.value));
1858
1859    // Step 6. Let registry be the result of looking up a custom element registry given intendedParent.
1860    // TODO: Implement registries other than `Document`.
1861
1862    // Step 7. Let definition be the result of looking up a custom element definition
1863    // given registry, namespace, localName, and is.
1864    let definition = document.lookup_custom_element_definition(&name.ns, &name.local, is.as_ref());
1865
1866    // Step 8. Let willExecuteScript be true if definition is non-null and the parser was
1867    // not created as part of the HTML fragment parsing algorithm; otherwise false.
1868    let will_execute_script =
1869        definition.is_some() && parsing_algorithm != ParsingAlgorithm::Fragment;
1870
1871    // Step 9. If willExecuteScript is true:
1872    if will_execute_script {
1873        // Step 9.1. Increment document's throw-on-dynamic-markup-insertion counter.
1874        document.increment_throw_on_dynamic_markup_insertion_counter();
1875        // Step 6.2. If the JavaScript execution context stack is empty, then perform a
1876        // microtask checkpoint.
1877        if is_execution_stack_empty() {
1878            document.window().perform_a_microtask_checkpoint(can_gc);
1879        }
1880        // Step 9.3. Push a new element queue onto document's relevant agent's custom
1881        // element reactions stack.
1882        custom_element_reaction_stack.push_new_element_queue()
1883    }
1884
1885    // Step 10. Let element be the result of creating an element given document,
1886    // localName, namespace, null, is, willExecuteScript, and registry.
1887    let creation_mode = if will_execute_script {
1888        CustomElementCreationMode::Synchronous
1889    } else {
1890        CustomElementCreationMode::Asynchronous
1891    };
1892    let element = Element::create(name, is, document, creator, creation_mode, None, can_gc);
1893
1894    // Step 11. Append each attribute in the given token to element.
1895    for attr in attrs {
1896        element.set_attribute_from_parser(attr.name, attr.value, None, can_gc);
1897    }
1898
1899    // Step 12. If willExecuteScript is true:
1900    if will_execute_script {
1901        // Step 12.1. Let queue be the result of popping from document's relevant agent's
1902        // custom element reactions stack. (This will be the same element queue as was
1903        // pushed above.)
1904        // Step 12.2 Invoke custom element reactions in queue.
1905        custom_element_reaction_stack.pop_current_element_queue(can_gc);
1906        // Step 12.3. Decrement document's throw-on-dynamic-markup-insertion counter.
1907        document.decrement_throw_on_dynamic_markup_insertion_counter();
1908    }
1909
1910    // Step 13. If element has an xmlns attribute in the XMLNS namespace whose value is
1911    // not exactly the same as the element's namespace, that is a parse error. Similarly,
1912    // if element has an xmlns:xlink attribute in the XMLNS namespace whose value is not
1913    // the XLink Namespace, that is a parse error.
1914    // TODO: Implement.
1915
1916    // Step 14. If element is a resettable element and not a form-associated custom
1917    // element, then invoke its reset algorithm. (This initializes the element's value and
1918    // checkedness based on the element's attributes.)
1919    if let Some(html_element) = element.downcast::<HTMLElement>() {
1920        if element.is_resettable() && !html_element.is_form_associated_custom_element() {
1921            element.reset(can_gc);
1922        }
1923    }
1924
1925    // Step 15. If element is a form-associated element and not a form-associated custom
1926    // element, the form element pointer is not null, there is no template element on the
1927    // stack of open elements, element is either not listed or doesn't have a form attribute,
1928    // and the intendedParent is in the same tree as the element pointed to by the form
1929    // element pointer, then associate element with the form element pointed to by the form
1930    // element pointer and set element's parser inserted flag.
1931    // TODO: Implement
1932
1933    // Step 16. Return element.
1934    element
1935}
1936
1937fn attach_declarative_shadow_inner(host: &Node, template: &Node, attributes: &[Attribute]) -> bool {
1938    let host_element = host.downcast::<Element>().unwrap();
1939
1940    if host_element.shadow_root().is_some() {
1941        return false;
1942    }
1943
1944    let template_element = template.downcast::<HTMLTemplateElement>().unwrap();
1945
1946    // Step 3. Let mode be template start tag's shadowrootmode attribute's value.
1947    // Step 4. Let clonable be true if template start tag has a shadowrootclonable attribute; otherwise false.
1948    // Step 5. Let delegatesfocus be true if template start tag
1949    // has a shadowrootdelegatesfocus attribute; otherwise false.
1950    // Step 6. Let serializable be true if template start tag
1951    // has a shadowrootserializable attribute; otherwise false.
1952    let mut shadow_root_mode = ShadowRootMode::Open;
1953    let mut clonable = false;
1954    let mut delegatesfocus = false;
1955    let mut serializable = false;
1956
1957    let attributes: Vec<ElementAttribute> = attributes
1958        .iter()
1959        .map(|attr| {
1960            ElementAttribute::new(
1961                attr.name.clone(),
1962                DOMString::from(String::from(attr.value.clone())),
1963            )
1964        })
1965        .collect();
1966
1967    attributes
1968        .iter()
1969        .for_each(|attr: &ElementAttribute| match attr.name.local {
1970            local_name!("shadowrootmode") => {
1971                if attr.value.str().eq_ignore_ascii_case("open") {
1972                    shadow_root_mode = ShadowRootMode::Open;
1973                } else if attr.value.str().eq_ignore_ascii_case("closed") {
1974                    shadow_root_mode = ShadowRootMode::Closed;
1975                } else {
1976                    unreachable!("shadowrootmode value is not open nor closed");
1977                }
1978            },
1979            local_name!("shadowrootclonable") => {
1980                clonable = true;
1981            },
1982            local_name!("shadowrootdelegatesfocus") => {
1983                delegatesfocus = true;
1984            },
1985            local_name!("shadowrootserializable") => {
1986                serializable = true;
1987            },
1988            _ => {},
1989        });
1990
1991    // Step 8.1. Attach a shadow root with declarative shadow host element,
1992    // mode, clonable, serializable, delegatesFocus, and "named".
1993    match host_element.attach_shadow(
1994        IsUserAgentWidget::No,
1995        shadow_root_mode,
1996        clonable,
1997        serializable,
1998        delegatesfocus,
1999        SlotAssignmentMode::Named,
2000        CanGc::note(),
2001    ) {
2002        Ok(shadow_root) => {
2003            // Step 8.3. Set shadow's declarative to true.
2004            shadow_root.set_declarative(true);
2005
2006            // Set 8.4. Set template's template contents property to shadow.
2007            let shadow = shadow_root.upcast::<DocumentFragment>();
2008            template_element.set_contents(Some(shadow));
2009
2010            // Step 8.5. Set shadow’s available to element internals to true.
2011            shadow_root.set_available_to_element_internals(true);
2012
2013            true
2014        },
2015        Err(_) => false,
2016    }
2017}