Skip to main content

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