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