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