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