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    /// <https://html.spec.whatwg.org/multipage/#creating-a-policy-container-from-a-fetch-response>
964    fn create_policy_container_from_fetch_response(metadata: &Metadata) -> PolicyContainer {
965        // 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.
966        // TODO
967        // Step 2. Let result be a new policy container.
968        // Step 7. Return result.
969        PolicyContainer {
970            // Step 3. Set result's CSP list to the result of parsing a response's Content Security Policies given response.
971            csp_list: parse_csp_list_from_metadata(&metadata.headers),
972            // Step 5. Set result's referrer policy to the result of parsing the `Referrer-Policy` header given response. [REFERRERPOLICY]
973            referrer_policy: ReferrerPolicy::parse_header_for_response(&metadata.headers),
974        }
975    }
976
977    /// <https://html.spec.whatwg.org/multipage/#initialise-the-document-object>
978    fn initialize_document_object(&self, document: &Document) {
979        // Step 9. Let document be a new Document, with
980        document.set_policy_container(self.navigation_params.policy_container.clone());
981        document.set_active_sandboxing_flag_set(self.navigation_params.final_sandboxing_flag_set);
982        document.set_about_base_url(self.navigation_params.about_base_url.clone());
983        // Step 17. Process link headers given document, navigationParams's response, and "pre-media".
984        process_link_headers(
985            &self.navigation_params.link_headers,
986            document,
987            LinkProcessingPhase::PreMedia,
988        );
989    }
990
991    /// Part of various load document methods
992    fn process_link_headers_in_media_phase_with_task(&mut self, document: &Document) {
993        // The first task that the networking task source places on the task queue
994        // while fetching runs must process link headers given document,
995        // navigationParams's response, and "media", after the task has been processed by the HTML parser.
996        let link_headers = std::mem::take(&mut self.navigation_params.link_headers);
997        if !link_headers.is_empty() {
998            let window = document.window();
999            let document = Trusted::new(document);
1000            window
1001                .upcast::<GlobalScope>()
1002                .task_manager()
1003                .networking_task_source()
1004                .queue(task!(process_link_headers_task: move || {
1005                    process_link_headers(&link_headers, &document.root(), LinkProcessingPhase::Media);
1006                }));
1007        }
1008    }
1009
1010    /// <https://html.spec.whatwg.org/multipage/#loading-a-document>
1011    fn load_document(&mut self, can_gc: CanGc) {
1012        assert!(!self.has_loaded_document);
1013        self.has_loaded_document = true;
1014        let Some(ref parser) = self.parser.as_ref().map(|p| p.root()) else {
1015            return;
1016        };
1017        // Step 1. Let type be the computed type of navigationParams's response.
1018        let content_type = &self.navigation_params.content_type;
1019        let mime_type = MimeClassifier::default().classify(
1020            LoadContext::Browsing,
1021            NoSniffFlag::Off,
1022            ApacheBugFlag::from_content_type(content_type.as_ref()),
1023            content_type,
1024            &self.navigation_params.resource_header,
1025        );
1026        // Step 2. If the user agent has been configured to process resources of the given type using
1027        // some mechanism other than rendering the content in a navigable, then skip this step.
1028        // Otherwise, if the type is one of the following types:
1029        let Some(media_type) = MimeClassifier::get_media_type(&mime_type) else {
1030            let page = format!(
1031                "<html><body><p>Unknown content type ({}).</p></body></html>",
1032                &mime_type,
1033            );
1034            self.load_inline_unknown_content(parser, page);
1035            return;
1036        };
1037        match media_type {
1038            // Return the result of loading an HTML document, given navigationParams.
1039            MediaType::Html => self.load_html_document(parser),
1040            // Return the result of loading an XML document given navigationParams and type.
1041            MediaType::Xml => self.load_xml_document(parser),
1042            // Return the result of loading a text document given navigationParams and type.
1043            MediaType::JavaScript | MediaType::Json | MediaType::Text | MediaType::Css => {
1044                self.load_text_document(parser)
1045            },
1046            // Return the result of loading a media document given navigationParams and type.
1047            MediaType::Image | MediaType::AudioVideo => {
1048                self.load_media_document(parser, media_type, &mime_type);
1049                return;
1050            },
1051            MediaType::Font => {
1052                let page = format!(
1053                    "<html><body><p>Unable to load font with content type ({}).</p></body></html>",
1054                    &mime_type,
1055                );
1056                self.load_inline_unknown_content(parser, page);
1057                return;
1058            },
1059        };
1060
1061        parser.parse_bytes_chunk(
1062            std::mem::take(&mut self.navigation_params.resource_header),
1063            can_gc,
1064        );
1065    }
1066
1067    /// <https://html.spec.whatwg.org/multipage/#navigate-html>
1068    fn load_html_document(&mut self, parser: &ServoParser) {
1069        // Step 1. Let document be the result of creating and initializing a
1070        // Document object given "html", "text/html", and navigationParams.
1071        self.initialize_document_object(&parser.document);
1072        // The first task that the networking task source places on the task queue while fetching
1073        // runs must process link headers given document, navigationParams's response, and "media",
1074        // after the task has been processed by the HTML parser.
1075        self.process_link_headers_in_media_phase_with_task(&parser.document);
1076    }
1077
1078    /// <https://html.spec.whatwg.org/multipage/#read-xml>
1079    fn load_xml_document(&mut self, parser: &ServoParser) {
1080        // When faced with displaying an XML file inline, provided navigation params navigationParams
1081        // and a string type, user agents must follow the requirements defined in XML and Namespaces in XML,
1082        // XML Media Types, DOM, and other relevant specifications to create and initialize a
1083        // Document object document, given "xml", type, and navigationParams, and return that Document.
1084        // They must also create a corresponding XML parser. [XML] [XMLNS] [RFC7303] [DOM]
1085        self.initialize_document_object(&parser.document);
1086        // The first task that the networking task source places on the task queue while fetching
1087        // runs must process link headers given document, navigationParams's response, and "media",
1088        // after the task has been processed by the XML parser.
1089        self.process_link_headers_in_media_phase_with_task(&parser.document);
1090    }
1091
1092    /// <https://html.spec.whatwg.org/multipage/#navigate-text>
1093    fn load_text_document(&mut self, parser: &ServoParser) {
1094        // Step 1. Let document be the result of creating and initializing a Document
1095        // object given "html", type, and navigationParams.
1096        self.initialize_document_object(&parser.document);
1097        // Step 4. Create an HTML parser and associate it with the document.
1098        // Act as if the tokenizer had emitted a start tag token with the tag name "pre" followed by
1099        // a single U+000A LINE FEED (LF) character, and switch the HTML parser's tokenizer to the PLAINTEXT state.
1100        // Each task that the networking task source places on the task queue while fetching runs must then
1101        // fill the parser's input byte stream with the fetched bytes and cause the HTML parser to perform
1102        // the appropriate processing of the input stream.
1103        let page = "<pre>\n".into();
1104        parser.push_string_input_chunk(page);
1105        parser.parse_sync(CanGc::note());
1106        parser.tokenizer.set_plaintext_state();
1107        // The first task that the networking task source places on the task queue while fetching
1108        // runs must process link headers given document, navigationParams's response, and "media",
1109        // after the task has been processed by the HTML parser.
1110        self.process_link_headers_in_media_phase_with_task(&parser.document);
1111    }
1112
1113    /// <https://html.spec.whatwg.org/multipage/#navigate-media>
1114    fn load_media_document(
1115        &mut self,
1116        parser: &ServoParser,
1117        media_type: MediaType,
1118        mime_type: &Mime,
1119    ) {
1120        // Step 1. Let document be the result of creating and initializing a Document
1121        // object given "html", type, and navigationParams.
1122        self.initialize_document_object(&parser.document);
1123        // Step 8. Act as if the user agent had stopped parsing document.
1124        self.is_synthesized_document = true;
1125        // Step 3. Populate with html/head/body given document.
1126        let page = "<html><body></body></html>".into();
1127        parser.push_string_input_chunk(page);
1128        parser.parse_sync(CanGc::note());
1129
1130        let doc = &parser.document;
1131        // Step 5. Set the appropriate attribute of the element host element, as described below,
1132        // to the address of the image, video, or audio resource.
1133        let node = if media_type == MediaType::Image {
1134            let img = Element::create(
1135                QualName::new(None, ns!(html), local_name!("img")),
1136                None,
1137                doc,
1138                ElementCreator::ParserCreated(1),
1139                CustomElementCreationMode::Asynchronous,
1140                None,
1141                CanGc::note(),
1142            );
1143            let img = DomRoot::downcast::<HTMLImageElement>(img).unwrap();
1144            img.SetSrc(USVString(self.url.to_string()));
1145            DomRoot::upcast::<Node>(img)
1146        } else if mime_type.type_() == mime::AUDIO {
1147            let audio = Element::create(
1148                QualName::new(None, ns!(html), local_name!("audio")),
1149                None,
1150                doc,
1151                ElementCreator::ParserCreated(1),
1152                CustomElementCreationMode::Asynchronous,
1153                None,
1154                CanGc::note(),
1155            );
1156            let audio = DomRoot::downcast::<HTMLMediaElement>(audio).unwrap();
1157            audio.SetSrc(USVString(self.url.to_string()));
1158            DomRoot::upcast::<Node>(audio)
1159        } else {
1160            let video = Element::create(
1161                QualName::new(None, ns!(html), local_name!("video")),
1162                None,
1163                doc,
1164                ElementCreator::ParserCreated(1),
1165                CustomElementCreationMode::Asynchronous,
1166                None,
1167                CanGc::note(),
1168            );
1169            let video = DomRoot::downcast::<HTMLMediaElement>(video).unwrap();
1170            video.SetSrc(USVString(self.url.to_string()));
1171            DomRoot::upcast::<Node>(video)
1172        };
1173        // Step 4. Append an element host element for the media, as described below, to the body element.
1174        let doc_body = DomRoot::upcast::<Node>(doc.GetBody().unwrap());
1175        doc_body
1176            .AppendChild(&node, CanGc::note())
1177            .expect("Appending failed");
1178        // Step 7. Process link headers given document, navigationParams's response, and "media".
1179        let link_headers = std::mem::take(&mut self.navigation_params.link_headers);
1180        process_link_headers(&link_headers, doc, LinkProcessingPhase::Media);
1181    }
1182
1183    /// <https://html.spec.whatwg.org/multipage/#read-ua-inline>
1184    fn load_inline_unknown_content(&mut self, parser: &ServoParser, page: String) {
1185        self.is_synthesized_document = true;
1186        parser.push_string_input_chunk(page);
1187        parser.parse_sync(CanGc::note());
1188    }
1189
1190    /// Store a PerformanceNavigationTiming entry in the globalscope's Performance buffer
1191    fn submit_resource_timing(&mut self) {
1192        let Some(parser) = self.parser.as_ref() else {
1193            return;
1194        };
1195        let parser = parser.root();
1196        if parser.aborted.get() {
1197            return;
1198        }
1199
1200        let document = &parser.document;
1201
1202        // TODO: Pass a proper fetch start time here.
1203        let performance_entry = PerformanceNavigationTiming::new(
1204            &document.global(),
1205            CrossProcessInstant::now(),
1206            document,
1207            CanGc::note(),
1208        );
1209        self.pushed_entry_index = document
1210            .global()
1211            .performance()
1212            .queue_entry(performance_entry.upcast::<PerformanceEntry>());
1213    }
1214}
1215
1216impl FetchResponseListener for ParserContext {
1217    fn process_request_body(&mut self, _: RequestId) {}
1218
1219    fn process_request_eof(&mut self, _: RequestId) {}
1220
1221    fn process_response(&mut self, _: RequestId, meta_result: Result<FetchMetadata, NetworkError>) {
1222        let (metadata, error) = match meta_result {
1223            Ok(meta) => (
1224                Some(match meta {
1225                    FetchMetadata::Unfiltered(m) => m,
1226                    FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
1227                }),
1228                None,
1229            ),
1230            Err(error) => (
1231                // Check variant without moving
1232                match &error {
1233                    NetworkError::LoadCancelled => {
1234                        return;
1235                    },
1236                    _ => {
1237                        let mut meta = Metadata::default(self.url.clone());
1238                        let mime: Option<Mime> = "text/html".parse().ok();
1239                        meta.set_content_type(mime.as_ref());
1240                        Some(meta)
1241                    },
1242                },
1243                Some(error),
1244            ),
1245        };
1246        let content_type: Option<Mime> = metadata
1247            .clone()
1248            .and_then(|meta| meta.content_type)
1249            .map(Serde::into_inner)
1250            .map(Into::into);
1251
1252        let (policy_container, endpoints_list, link_headers) = match metadata.as_ref() {
1253            None => (PolicyContainer::default(), None, vec![]),
1254            Some(metadata) => (
1255                Self::create_policy_container_from_fetch_response(metadata),
1256                ReportingEndpoint::parse_reporting_endpoints_header(
1257                    &self.url.clone(),
1258                    &metadata.headers,
1259                ),
1260                extract_links_from_headers(&metadata.headers),
1261            ),
1262        };
1263
1264        let parser = match ScriptThread::page_headers_available(
1265            self.webview_id,
1266            self.pipeline_id,
1267            metadata,
1268            CanGc::note(),
1269        ) {
1270            Some(parser) => parser,
1271            None => return,
1272        };
1273        if parser.aborted.get() {
1274            return;
1275        }
1276
1277        let _realm = enter_realm(&*parser.document);
1278        let window = parser.document.window();
1279
1280        // From Step 23.8.3 of https://html.spec.whatwg.org/multipage/#navigate
1281        // Let finalSandboxFlags be the union of targetSnapshotParams's sandboxing flags and
1282        // policyContainer's CSP list's CSP-derived sandboxing flags.
1283        //
1284        // TODO: This deviates a bit from the specification, because there isn't a `targetSnapshotParam`
1285        // concept yet.
1286        let final_sandboxing_flag_set = policy_container
1287            .csp_list
1288            .as_ref()
1289            .and_then(|csp| csp.get_sandboxing_flag_set_for_document())
1290            .unwrap_or(SandboxingFlagSet::empty())
1291            .union(parser.document.creation_sandboxing_flag_set());
1292
1293        if let Some(endpoints) = endpoints_list {
1294            window.set_endpoints_list(endpoints);
1295        }
1296        self.parser = Some(Trusted::new(&*parser));
1297        self.navigation_params = NavigationParams {
1298            policy_container,
1299            content_type,
1300            final_sandboxing_flag_set,
1301            link_headers,
1302            about_base_url: parser.document.about_base_url(),
1303            resource_header: vec![],
1304        };
1305        self.submit_resource_timing();
1306
1307        // Part of https://html.spec.whatwg.org/multipage/#loading-a-document
1308        //
1309        // Step 3. If, given type, the new resource is to be handled by displaying some sort of inline content,
1310        // e.g., a native rendering of the content or an error message because the specified type is not supported,
1311        // then return the result of creating a document for inline content that doesn't have a DOM given
1312        // navigationParams's navigable, navigationParams's id, navigationParams's navigation timing type,
1313        // and navigationParams's user involvement.
1314        if let Some(error) = error {
1315            let page = match error {
1316                NetworkError::SslValidation(reason, bytes) => {
1317                    let page = resources::read_string(Resource::BadCertHTML);
1318                    let page = page.replace("${reason}", &reason);
1319                    let encoded_bytes = general_purpose::STANDARD_NO_PAD.encode(bytes);
1320                    let page = page.replace("${bytes}", encoded_bytes.as_str());
1321                    page.replace("${secret}", &net_traits::PRIVILEGED_SECRET.to_string())
1322                },
1323                NetworkError::BlobURLStoreError(reason) |
1324                NetworkError::WebsocketConnectionFailure(reason) |
1325                NetworkError::HttpError(reason) |
1326                NetworkError::ResourceLoadError(reason) |
1327                NetworkError::MimeType(reason) => {
1328                    let page = resources::read_string(Resource::NetErrorHTML);
1329                    page.replace("${reason}", &reason)
1330                },
1331                NetworkError::Crash(details) => {
1332                    let page = resources::read_string(Resource::CrashHTML);
1333                    page.replace("${details}", &details)
1334                },
1335                NetworkError::UnsupportedScheme |
1336                NetworkError::CorsGeneral |
1337                NetworkError::CrossOriginResponse |
1338                NetworkError::CorsCredentials |
1339                NetworkError::CorsAllowMethods |
1340                NetworkError::CorsAllowHeaders |
1341                NetworkError::CorsMethod |
1342                NetworkError::CorsAuthorization |
1343                NetworkError::CorsHeaders |
1344                NetworkError::ConnectionFailure |
1345                NetworkError::RedirectError |
1346                NetworkError::TooManyRedirects |
1347                NetworkError::TooManyInFlightKeepAliveRequests |
1348                NetworkError::InvalidMethod |
1349                NetworkError::ContentSecurityPolicy |
1350                NetworkError::Nosniff |
1351                NetworkError::SubresourceIntegrity |
1352                NetworkError::MixedContent |
1353                NetworkError::CacheError |
1354                NetworkError::InvalidPort |
1355                NetworkError::LocalDirectoryError |
1356                NetworkError::PartialResponseToNonRangeRequestError |
1357                NetworkError::ProtocolHandlerSubstitutionError |
1358                NetworkError::DecompressionError => {
1359                    let page = resources::read_string(Resource::NetErrorHTML);
1360                    page.replace("${reason}", &format!("{:?}", error))
1361                },
1362                NetworkError::LoadCancelled => {
1363                    // The next load will show a page
1364                    return;
1365                },
1366            };
1367            self.load_inline_unknown_content(&parser, page);
1368        }
1369    }
1370
1371    fn process_response_chunk(&mut self, _: RequestId, payload: Vec<u8>) {
1372        if self.is_synthesized_document {
1373            return;
1374        }
1375        let Some(parser) = self.parser.as_ref().map(|p| p.root()) else {
1376            return;
1377        };
1378        if parser.aborted.get() {
1379            return;
1380        }
1381        if !self.has_loaded_document {
1382            // https://mimesniff.spec.whatwg.org/#read-the-resource-header
1383            self.navigation_params
1384                .resource_header
1385                .extend_from_slice(&payload);
1386            // the number of bytes in buffer is greater than or equal to 1445.
1387            if self.navigation_params.resource_header.len() >= 1445 {
1388                self.load_document(CanGc::note());
1389            }
1390        } else {
1391            parser.parse_bytes_chunk(payload, CanGc::note());
1392        }
1393    }
1394
1395    // This method is called via script_thread::handle_fetch_eof, so we must call
1396    // submit_resource_timing in this function
1397    // Resource listeners are called via net_traits::Action::process, which handles submission for them
1398    fn process_response_eof(
1399        mut self,
1400        _: RequestId,
1401        status: Result<(), NetworkError>,
1402        timing: ResourceFetchTiming,
1403    ) {
1404        let parser = match self.parser.as_ref() {
1405            Some(parser) => parser.root(),
1406            None => return,
1407        };
1408        if parser.aborted.get() {
1409            return;
1410        }
1411
1412        if let Err(error) = &status {
1413            // TODO(Savago): we should send a notification to callers #5463.
1414            debug!("Failed to load page URL {}, error: {error:?}", self.url);
1415        }
1416
1417        // https://mimesniff.spec.whatwg.org/#read-the-resource-header
1418        //
1419        // the end of the resource is reached.
1420        if !self.has_loaded_document {
1421            self.load_document(CanGc::note());
1422        }
1423
1424        let _realm = enter_realm(&*parser);
1425
1426        if status.is_ok() {
1427            parser.document.set_redirect_count(timing.redirect_count);
1428        }
1429
1430        parser.last_chunk_received.set(true);
1431        if !parser.suspended.get() {
1432            parser.parse_sync(CanGc::note());
1433        }
1434
1435        // TODO: Only update if this is the current document resource.
1436        // TODO(mrobinson): Pass a proper fetch_start parameter here instead of `CrossProcessInstant::now()`.
1437        if let Some(pushed_index) = self.pushed_entry_index {
1438            let document = &parser.document;
1439            let performance_entry = PerformanceNavigationTiming::new(
1440                &document.global(),
1441                CrossProcessInstant::now(),
1442                document,
1443                CanGc::note(),
1444            );
1445            document
1446                .global()
1447                .performance()
1448                .update_entry(pushed_index, performance_entry.upcast::<PerformanceEntry>());
1449        }
1450    }
1451
1452    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
1453        let parser = match self.parser.as_ref() {
1454            Some(parser) => parser.root(),
1455            None => return,
1456        };
1457        let document = &parser.document;
1458        let global = &document.global();
1459        // TODO(https://github.com/w3c/webappsec-csp/issues/687): Update after spec is resolved
1460        global.report_csp_violations(violations, None, None);
1461    }
1462}
1463
1464pub(crate) struct FragmentContext<'a> {
1465    pub(crate) context_elem: &'a Node,
1466    pub(crate) form_elem: Option<&'a Node>,
1467    pub(crate) context_element_allows_scripting: bool,
1468}
1469
1470#[cfg_attr(crown, expect(crown::unrooted_must_root))]
1471fn insert(
1472    parent: &Node,
1473    reference_child: Option<&Node>,
1474    child: NodeOrText<Dom<Node>>,
1475    parsing_algorithm: ParsingAlgorithm,
1476    custom_element_reaction_stack: &CustomElementReactionStack,
1477    can_gc: CanGc,
1478) {
1479    match child {
1480        NodeOrText::AppendNode(n) => {
1481            // https://html.spec.whatwg.org/multipage/#insert-a-foreign-element
1482            // applies if this is an element; if not, it may be
1483            // https://html.spec.whatwg.org/multipage/#insert-a-comment
1484            let element_in_non_fragment =
1485                parsing_algorithm != ParsingAlgorithm::Fragment && n.is::<Element>();
1486            if element_in_non_fragment {
1487                custom_element_reaction_stack.push_new_element_queue();
1488            }
1489            parent.InsertBefore(&n, reference_child, can_gc).unwrap();
1490            if element_in_non_fragment {
1491                custom_element_reaction_stack.pop_current_element_queue(can_gc);
1492            }
1493        },
1494        NodeOrText::AppendText(t) => {
1495            // https://html.spec.whatwg.org/multipage/#insert-a-character
1496            let text = reference_child
1497                .and_then(Node::GetPreviousSibling)
1498                .or_else(|| parent.GetLastChild())
1499                .and_then(DomRoot::downcast::<Text>);
1500
1501            if let Some(text) = text {
1502                text.upcast::<CharacterData>().append_data(&t);
1503            } else {
1504                let text = Text::new(String::from(t).into(), &parent.owner_doc(), can_gc);
1505                parent
1506                    .InsertBefore(text.upcast(), reference_child, can_gc)
1507                    .unwrap();
1508            }
1509        },
1510    }
1511}
1512
1513#[derive(JSTraceable, MallocSizeOf)]
1514#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1515pub(crate) struct Sink {
1516    #[no_trace]
1517    base_url: ServoUrl,
1518    document: Dom<Document>,
1519    current_line: Cell<u64>,
1520    script: MutNullableDom<HTMLScriptElement>,
1521    parsing_algorithm: ParsingAlgorithm,
1522    #[conditional_malloc_size_of]
1523    custom_element_reaction_stack: Rc<CustomElementReactionStack>,
1524}
1525
1526impl Sink {
1527    fn same_tree(&self, x: &Dom<Node>, y: &Dom<Node>) -> bool {
1528        let x = x.downcast::<Element>().expect("Element node expected");
1529        let y = y.downcast::<Element>().expect("Element node expected");
1530
1531        x.is_in_same_home_subtree(y)
1532    }
1533
1534    fn has_parent_node(&self, node: &Dom<Node>) -> bool {
1535        node.GetParentNode().is_some()
1536    }
1537}
1538
1539impl TreeSink for Sink {
1540    type Output = Self;
1541    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1542    fn finish(self) -> Self {
1543        self
1544    }
1545
1546    type Handle = Dom<Node>;
1547    type ElemName<'a>
1548        = ExpandedName<'a>
1549    where
1550        Self: 'a;
1551
1552    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1553    fn get_document(&self) -> Dom<Node> {
1554        Dom::from_ref(self.document.upcast())
1555    }
1556
1557    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1558    fn get_template_contents(&self, target: &Dom<Node>) -> Dom<Node> {
1559        let template = target
1560            .downcast::<HTMLTemplateElement>()
1561            .expect("tried to get template contents of non-HTMLTemplateElement in HTML parsing");
1562        Dom::from_ref(template.Content(CanGc::note()).upcast())
1563    }
1564
1565    fn same_node(&self, x: &Dom<Node>, y: &Dom<Node>) -> bool {
1566        x == y
1567    }
1568
1569    fn elem_name<'a>(&self, target: &'a Dom<Node>) -> ExpandedName<'a> {
1570        let elem = target
1571            .downcast::<Element>()
1572            .expect("tried to get name of non-Element in HTML parsing");
1573        ExpandedName {
1574            ns: elem.namespace(),
1575            local: elem.local_name(),
1576        }
1577    }
1578
1579    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1580    fn create_element(
1581        &self,
1582        name: QualName,
1583        attrs: Vec<Attribute>,
1584        flags: ElementFlags,
1585    ) -> Dom<Node> {
1586        let attrs = attrs
1587            .into_iter()
1588            .map(|attr| ElementAttribute::new(attr.name, DOMString::from(String::from(attr.value))))
1589            .collect();
1590        let parsing_algorithm = if flags.template {
1591            ParsingAlgorithm::Fragment
1592        } else {
1593            self.parsing_algorithm
1594        };
1595        let element = create_element_for_token(
1596            name,
1597            attrs,
1598            &self.document,
1599            ElementCreator::ParserCreated(self.current_line.get()),
1600            parsing_algorithm,
1601            &self.custom_element_reaction_stack,
1602            CanGc::note(),
1603        );
1604        Dom::from_ref(element.upcast())
1605    }
1606
1607    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1608    fn create_comment(&self, text: StrTendril) -> Dom<Node> {
1609        let comment = Comment::new(
1610            DOMString::from(String::from(text)),
1611            &self.document,
1612            None,
1613            CanGc::note(),
1614        );
1615        Dom::from_ref(comment.upcast())
1616    }
1617
1618    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1619    fn create_pi(&self, target: StrTendril, data: StrTendril) -> Dom<Node> {
1620        let doc = &*self.document;
1621        let pi = ProcessingInstruction::new(
1622            DOMString::from(String::from(target)),
1623            DOMString::from(String::from(data)),
1624            doc,
1625            CanGc::note(),
1626        );
1627        Dom::from_ref(pi.upcast())
1628    }
1629
1630    fn associate_with_form(
1631        &self,
1632        target: &Dom<Node>,
1633        form: &Dom<Node>,
1634        nodes: (&Dom<Node>, Option<&Dom<Node>>),
1635    ) {
1636        let (element, prev_element) = nodes;
1637        let tree_node = prev_element.map_or(element, |prev| {
1638            if self.has_parent_node(element) {
1639                element
1640            } else {
1641                prev
1642            }
1643        });
1644        if !self.same_tree(tree_node, form) {
1645            return;
1646        }
1647
1648        let node = target;
1649        let form = DomRoot::downcast::<HTMLFormElement>(DomRoot::from_ref(&**form))
1650            .expect("Owner must be a form element");
1651
1652        let elem = node.downcast::<Element>();
1653        let control = elem.and_then(|e| e.as_maybe_form_control());
1654
1655        if let Some(control) = control {
1656            control.set_form_owner_from_parser(&form, CanGc::note());
1657        }
1658    }
1659
1660    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1661    fn append_before_sibling(&self, sibling: &Dom<Node>, new_node: NodeOrText<Dom<Node>>) {
1662        let parent = sibling
1663            .GetParentNode()
1664            .expect("append_before_sibling called on node without parent");
1665
1666        insert(
1667            &parent,
1668            Some(sibling),
1669            new_node,
1670            self.parsing_algorithm,
1671            &self.custom_element_reaction_stack,
1672            CanGc::note(),
1673        );
1674    }
1675
1676    fn parse_error(&self, msg: Cow<'static, str>) {
1677        debug!("Parse error: {}", msg);
1678    }
1679
1680    fn set_quirks_mode(&self, mode: QuirksMode) {
1681        let mode = match mode {
1682            QuirksMode::Quirks => ServoQuirksMode::Quirks,
1683            QuirksMode::LimitedQuirks => ServoQuirksMode::LimitedQuirks,
1684            QuirksMode::NoQuirks => ServoQuirksMode::NoQuirks,
1685        };
1686        self.document.set_quirks_mode(mode);
1687    }
1688
1689    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1690    fn append(&self, parent: &Dom<Node>, child: NodeOrText<Dom<Node>>) {
1691        insert(
1692            parent,
1693            None,
1694            child,
1695            self.parsing_algorithm,
1696            &self.custom_element_reaction_stack,
1697            CanGc::note(),
1698        );
1699    }
1700
1701    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1702    fn append_based_on_parent_node(
1703        &self,
1704        elem: &Dom<Node>,
1705        prev_elem: &Dom<Node>,
1706        child: NodeOrText<Dom<Node>>,
1707    ) {
1708        if self.has_parent_node(elem) {
1709            self.append_before_sibling(elem, child);
1710        } else {
1711            self.append(prev_elem, child);
1712        }
1713    }
1714
1715    fn append_doctype_to_document(
1716        &self,
1717        name: StrTendril,
1718        public_id: StrTendril,
1719        system_id: StrTendril,
1720    ) {
1721        let doc = &*self.document;
1722        let doctype = DocumentType::new(
1723            DOMString::from(String::from(name)),
1724            Some(DOMString::from(String::from(public_id))),
1725            Some(DOMString::from(String::from(system_id))),
1726            doc,
1727            CanGc::note(),
1728        );
1729        doc.upcast::<Node>()
1730            .AppendChild(doctype.upcast(), CanGc::note())
1731            .expect("Appending failed");
1732    }
1733
1734    fn add_attrs_if_missing(&self, target: &Dom<Node>, attrs: Vec<Attribute>) {
1735        let elem = target
1736            .downcast::<Element>()
1737            .expect("tried to set attrs on non-Element in HTML parsing");
1738        for attr in attrs {
1739            elem.set_attribute_from_parser(
1740                attr.name,
1741                DOMString::from(String::from(attr.value)),
1742                None,
1743                CanGc::note(),
1744            );
1745        }
1746    }
1747
1748    fn remove_from_parent(&self, target: &Dom<Node>) {
1749        if let Some(ref parent) = target.GetParentNode() {
1750            parent.RemoveChild(target, CanGc::note()).unwrap();
1751        }
1752    }
1753
1754    fn mark_script_already_started(&self, node: &Dom<Node>) {
1755        let script = node.downcast::<HTMLScriptElement>();
1756        if let Some(script) = script {
1757            script.set_already_started(true)
1758        }
1759    }
1760
1761    fn reparent_children(&self, node: &Dom<Node>, new_parent: &Dom<Node>) {
1762        while let Some(ref child) = node.GetFirstChild() {
1763            new_parent.AppendChild(child, CanGc::note()).unwrap();
1764        }
1765    }
1766
1767    /// <https://html.spec.whatwg.org/multipage/#html-integration-point>
1768    /// Specifically, the `<annotation-xml>` cases.
1769    fn is_mathml_annotation_xml_integration_point(&self, handle: &Dom<Node>) -> bool {
1770        let elem = handle.downcast::<Element>().unwrap();
1771        elem.get_attribute(&ns!(), &local_name!("encoding"))
1772            .is_some_and(|attr| {
1773                attr.value().eq_ignore_ascii_case("text/html") ||
1774                    attr.value().eq_ignore_ascii_case("application/xhtml+xml")
1775            })
1776    }
1777
1778    fn set_current_line(&self, line_number: u64) {
1779        self.current_line.set(line_number);
1780    }
1781
1782    fn pop(&self, node: &Dom<Node>) {
1783        let node = DomRoot::from_ref(&**node);
1784        vtable_for(&node).pop();
1785    }
1786
1787    fn allow_declarative_shadow_roots(&self, intended_parent: &Dom<Node>) -> bool {
1788        intended_parent.owner_doc().allow_declarative_shadow_roots()
1789    }
1790
1791    /// <https://html.spec.whatwg.org/multipage/#parsing-main-inhead>
1792    /// A start tag whose tag name is "template"
1793    /// Attach shadow path
1794    fn attach_declarative_shadow(
1795        &self,
1796        host: &Dom<Node>,
1797        template: &Dom<Node>,
1798        attributes: &[Attribute],
1799    ) -> bool {
1800        attach_declarative_shadow_inner(host, template, attributes)
1801    }
1802
1803    fn maybe_clone_an_option_into_selectedcontent(&self, option: &Self::Handle) {
1804        let Some(option) = option.downcast::<HTMLOptionElement>() else {
1805            if cfg!(debug_assertions) {
1806                unreachable!();
1807            }
1808            log::error!(
1809                "Received non-option element in maybe_clone_an_option_into_selectedcontent"
1810            );
1811            return;
1812        };
1813
1814        option.maybe_clone_an_option_into_selectedcontent(CanGc::note())
1815    }
1816}
1817
1818/// <https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token>
1819fn create_element_for_token(
1820    name: QualName,
1821    attrs: Vec<ElementAttribute>,
1822    document: &Document,
1823    creator: ElementCreator,
1824    parsing_algorithm: ParsingAlgorithm,
1825    custom_element_reaction_stack: &CustomElementReactionStack,
1826    can_gc: CanGc,
1827) -> DomRoot<Element> {
1828    // Step 1. If the active speculative HTML parser is not null, then return the result
1829    // of creating a speculative mock element given namespace, token's tag name, and
1830    // token's attributes.
1831    // TODO: Implement
1832
1833    // Step 2: Otherwise, optionally create a speculative mock element given namespace,
1834    // token's tag name, and token's attributes
1835    // TODO: Implement.
1836
1837    // Step 3. Let document be intendedParent's node document.
1838    // Passed as argument.
1839
1840    // Step 4. Let localName be token's tag name.
1841    // Passed as argument
1842
1843    // Step 5. Let is be the value of the "is" attribute in token, if such an attribute
1844    // exists; otherwise null.
1845    let is = attrs
1846        .iter()
1847        .find(|attr| attr.name.local.eq_str_ignore_ascii_case("is"))
1848        .map(|attr| LocalName::from(&attr.value));
1849
1850    // Step 6. Let registry be the result of looking up a custom element registry given intendedParent.
1851    // TODO: Implement registries other than `Document`.
1852
1853    // Step 7. Let definition be the result of looking up a custom element definition
1854    // given registry, namespace, localName, and is.
1855    let definition = document.lookup_custom_element_definition(&name.ns, &name.local, is.as_ref());
1856
1857    // Step 8. Let willExecuteScript be true if definition is non-null and the parser was
1858    // not created as part of the HTML fragment parsing algorithm; otherwise false.
1859    let will_execute_script =
1860        definition.is_some() && parsing_algorithm != ParsingAlgorithm::Fragment;
1861
1862    // Step 9. If willExecuteScript is true:
1863    if will_execute_script {
1864        // Step 9.1. Increment document's throw-on-dynamic-markup-insertion counter.
1865        document.increment_throw_on_dynamic_markup_insertion_counter();
1866        // Step 6.2. If the JavaScript execution context stack is empty, then perform a
1867        // microtask checkpoint.
1868        if is_execution_stack_empty() {
1869            document.window().perform_a_microtask_checkpoint(can_gc);
1870        }
1871        // Step 9.3. Push a new element queue onto document's relevant agent's custom
1872        // element reactions stack.
1873        custom_element_reaction_stack.push_new_element_queue()
1874    }
1875
1876    // Step 10. Let element be the result of creating an element given document,
1877    // localName, namespace, null, is, willExecuteScript, and registry.
1878    let creation_mode = if will_execute_script {
1879        CustomElementCreationMode::Synchronous
1880    } else {
1881        CustomElementCreationMode::Asynchronous
1882    };
1883    let element = Element::create(name, is, document, creator, creation_mode, None, can_gc);
1884
1885    // Step 11. Append each attribute in the given token to element.
1886    for attr in attrs {
1887        element.set_attribute_from_parser(attr.name, attr.value, None, can_gc);
1888    }
1889
1890    // Step 12. If willExecuteScript is true:
1891    if will_execute_script {
1892        // Step 12.1. Let queue be the result of popping from document's relevant agent's
1893        // custom element reactions stack. (This will be the same element queue as was
1894        // pushed above.)
1895        // Step 12.2 Invoke custom element reactions in queue.
1896        custom_element_reaction_stack.pop_current_element_queue(can_gc);
1897        // Step 12.3. Decrement document's throw-on-dynamic-markup-insertion counter.
1898        document.decrement_throw_on_dynamic_markup_insertion_counter();
1899    }
1900
1901    // Step 13. If element has an xmlns attribute in the XMLNS namespace whose value is
1902    // not exactly the same as the element's namespace, that is a parse error. Similarly,
1903    // if element has an xmlns:xlink attribute in the XMLNS namespace whose value is not
1904    // the XLink Namespace, that is a parse error.
1905    // TODO: Implement.
1906
1907    // Step 14. If element is a resettable element and not a form-associated custom
1908    // element, then invoke its reset algorithm. (This initializes the element's value and
1909    // checkedness based on the element's attributes.)
1910    if let Some(html_element) = element.downcast::<HTMLElement>() {
1911        if element.is_resettable() && !html_element.is_form_associated_custom_element() {
1912            element.reset(can_gc);
1913        }
1914    }
1915
1916    // Step 15. If element is a form-associated element and not a form-associated custom
1917    // element, the form element pointer is not null, there is no template element on the
1918    // stack of open elements, element is either not listed or doesn't have a form attribute,
1919    // and the intendedParent is in the same tree as the element pointed to by the form
1920    // element pointer, then associate element with the form element pointed to by the form
1921    // element pointer and set element's parser inserted flag.
1922    // TODO: Implement
1923
1924    // Step 16. Return element.
1925    element
1926}
1927
1928fn attach_declarative_shadow_inner(host: &Node, template: &Node, attributes: &[Attribute]) -> bool {
1929    let host_element = host.downcast::<Element>().unwrap();
1930
1931    if host_element.shadow_root().is_some() {
1932        return false;
1933    }
1934
1935    let template_element = template.downcast::<HTMLTemplateElement>().unwrap();
1936
1937    // Step 3. Let mode be template start tag's shadowrootmode attribute's value.
1938    // Step 4. Let clonable be true if template start tag has a shadowrootclonable attribute; otherwise false.
1939    // Step 5. Let delegatesfocus be true if template start tag
1940    // has a shadowrootdelegatesfocus attribute; otherwise false.
1941    // Step 6. Let serializable be true if template start tag
1942    // has a shadowrootserializable attribute; otherwise false.
1943    let mut shadow_root_mode = ShadowRootMode::Open;
1944    let mut clonable = false;
1945    let mut delegatesfocus = false;
1946    let mut serializable = false;
1947
1948    let attributes: Vec<ElementAttribute> = attributes
1949        .iter()
1950        .map(|attr| {
1951            ElementAttribute::new(
1952                attr.name.clone(),
1953                DOMString::from(String::from(attr.value.clone())),
1954            )
1955        })
1956        .collect();
1957
1958    attributes
1959        .iter()
1960        .for_each(|attr: &ElementAttribute| match attr.name.local {
1961            local_name!("shadowrootmode") => {
1962                if attr.value.str().eq_ignore_ascii_case("open") {
1963                    shadow_root_mode = ShadowRootMode::Open;
1964                } else if attr.value.str().eq_ignore_ascii_case("closed") {
1965                    shadow_root_mode = ShadowRootMode::Closed;
1966                } else {
1967                    unreachable!("shadowrootmode value is not open nor closed");
1968                }
1969            },
1970            local_name!("shadowrootclonable") => {
1971                clonable = true;
1972            },
1973            local_name!("shadowrootdelegatesfocus") => {
1974                delegatesfocus = true;
1975            },
1976            local_name!("shadowrootserializable") => {
1977                serializable = true;
1978            },
1979            _ => {},
1980        });
1981
1982    // Step 8.1. Attach a shadow root with declarative shadow host element,
1983    // mode, clonable, serializable, delegatesFocus, and "named".
1984    match host_element.attach_shadow(
1985        IsUserAgentWidget::No,
1986        shadow_root_mode,
1987        clonable,
1988        serializable,
1989        delegatesfocus,
1990        SlotAssignmentMode::Named,
1991        CanGc::note(),
1992    ) {
1993        Ok(shadow_root) => {
1994            // Step 8.3. Set shadow's declarative to true.
1995            shadow_root.set_declarative(true);
1996
1997            // Set 8.4. Set template's template contents property to shadow.
1998            let shadow = shadow_root.upcast::<DocumentFragment>();
1999            template_element.set_contents(Some(shadow));
2000
2001            // Step 8.5. Set shadow’s available to element internals to true.
2002            shadow_root.set_available_to_element_internals(true);
2003
2004            true
2005        },
2006        Err(_) => false,
2007    }
2008}