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