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
5#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
6
7use std::borrow::Cow;
8use std::cell::{Cell, RefCell};
9use std::mem;
10use std::rc::Rc;
11
12use base64::Engine as _;
13use base64::engine::general_purpose;
14use bytes::Bytes;
15use content_security_policy::sandboxing_directive::SandboxingFlagSet;
16use devtools_traits::ScriptToDevtoolsControlMsg;
17use dom_struct::dom_struct;
18use embedder_traits::resources::{self, Resource};
19use encoding_rs::{Encoding, UTF_8};
20use html5ever::buffer_queue::BufferQueue;
21use html5ever::tendril::StrTendril;
22use html5ever::tree_builder::{ElementFlags, NodeOrText, QuirksMode, TreeSink};
23use html5ever::{Attribute, ExpandedName, LocalName, QualName, local_name, ns};
24use hyper_serde::Serde;
25use js::context::JSContext;
26use markup5ever::TokenizerResult;
27use mime::{self, Mime};
28use net_traits::mime_classifier::{ApacheBugFlag, MediaType, MimeClassifier, NoSniffFlag};
29use net_traits::policy_container::PolicyContainer;
30use net_traits::{
31    FetchMetadata, LoadContext, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming,
32};
33use profile_traits::time::{
34    ProfilerCategory, ProfilerChan, TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType,
35};
36use profile_traits::time_profile;
37use script_bindings::cell::DomRefCell;
38use script_bindings::reflector::{Reflector, reflect_dom_object};
39use script_bindings::script_runtime::temp_cx;
40use script_traits::DocumentActivity;
41use servo_base::id::{PipelineId, WebViewId};
42use servo_config::pref;
43use servo_constellation_traits::TargetSnapshotParams;
44use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
45use style::context::QuirksMode as ServoQuirksMode;
46use tendril::stream::LossyDecoder;
47use tendril::{ByteTendril, TendrilSink};
48
49use crate::dom::SuppressObserver;
50use crate::dom::bindings::codegen::Bindings::DocumentBinding::{
51    DocumentMethods, DocumentReadyState,
52};
53use crate::dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods;
54use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaElementMethods;
55use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods;
56use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
57use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
58    ShadowRootMode, SlotAssignmentMode,
59};
60use crate::dom::bindings::inheritance::Castable;
61use crate::dom::bindings::refcounted::Trusted;
62use crate::dom::bindings::reflector::DomGlobal;
63use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
64use crate::dom::bindings::settings_stack::is_execution_stack_empty;
65use crate::dom::bindings::str::{DOMString, USVString};
66use crate::dom::characterdata::CharacterData;
67use crate::dom::comment::Comment;
68use crate::dom::csp::parse_csp_list_from_metadata;
69use crate::dom::customelementregistry::{CustomElementReactionStack, CustomElementRegistry};
70use crate::dom::document::{Document, HasBrowsingContext, IsHTMLDocument};
71use crate::dom::documentfragment::DocumentFragment;
72use crate::dom::documenttype::DocumentType;
73use crate::dom::domstringlist::DOMStringList;
74use crate::dom::element::create::create_element;
75use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator};
76use crate::dom::globalscope::GlobalScope;
77use crate::dom::html::document_metadata::processingoptions::{
78    LinkHeader, LinkProcessingPhase, extract_links_from_headers, process_link_headers,
79};
80use crate::dom::html::htmlformelement::{FormControlElementHelpers, HTMLFormElement};
81use crate::dom::html::htmlimageelement::HTMLImageElement;
82use crate::dom::html::htmlscriptelement::{HTMLScriptElement, ScriptResult};
83use crate::dom::html::htmltemplateelement::HTMLTemplateElement;
84use crate::dom::iterators::ShadowIncluding;
85use crate::dom::node::Node;
86use crate::dom::node::virtualmethods::vtable_for;
87use crate::dom::performance::performanceentry::PerformanceEntry;
88use crate::dom::performance::performancenavigationtiming::PerformanceNavigationTiming;
89use crate::dom::processinginstruction::ProcessingInstruction;
90use crate::dom::reporting::reportingendpoint::ReportingEndpoint;
91use crate::dom::security::csp::CspReporting;
92use crate::dom::security::xframeoptions::check_a_navigation_response_adherence_to_x_frame_options;
93use crate::dom::shadowroot::IsUserAgentWidget;
94use crate::dom::text::Text;
95use crate::dom::types::{HTMLElement, HTMLMediaElement, HTMLOptionElement};
96use crate::event_loop::document_loader::{DocumentLoader, LoadType};
97use crate::event_loop::script_thread::ScriptThread;
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    /// The source origin this load if this navigation was initiated by script.
942    /// This is used to ensure that `about:blank` and `about:srcdoc` pages are
943    /// able to inherit the aliased [`MutableOrigin`] of their initiating pages
944    /// properly. In the case that the initiator is in another event loop, this
945    /// will be a non-aliased copy of the origin.
946    source_origin: Option<MutableOrigin>,
947    document: Option<Trusted<Document>>,
948}
949
950impl ParserContext {
951    pub(crate) fn new(
952        webview_id: WebViewId,
953        pipeline_id: PipelineId,
954        url: ServoUrl,
955        creation_sandboxing_flag_set: SandboxingFlagSet,
956        parent_info: Option<PipelineId>,
957        target_snapshot_params: TargetSnapshotParams,
958        source_origin: Option<MutableOrigin>,
959    ) -> ParserContext {
960        ParserContext {
961            parser: None,
962            is_synthesized_document: false,
963            has_loaded_document: false,
964            webview_id,
965            pipeline_id,
966            url,
967            parent_info,
968            pushed_entry_index: None,
969            navigation_params: NavigationParams {
970                policy_container: Default::default(),
971                content_type: None,
972                link_headers: vec![],
973                final_sandboxing_flag_set: creation_sandboxing_flag_set,
974                resource_header: vec![],
975                about_base_url: Default::default(),
976                iframe_element_referrer_policy: Default::default(),
977            },
978            target_snapshot_params,
979            source_origin,
980            document: None,
981        }
982    }
983
984    pub(crate) fn set_policy_container(&mut self, policy_container: Option<&PolicyContainer>) {
985        let Some(policy_container) = policy_container else {
986            return;
987        };
988        self.navigation_params.policy_container = policy_container.clone();
989    }
990
991    pub(crate) fn set_about_base_url(&mut self, about_base_url: Option<ServoUrl>) {
992        self.navigation_params.about_base_url = about_base_url;
993    }
994
995    pub(crate) fn get_document(&self) -> Option<DomRoot<Document>> {
996        self.parser
997            .as_ref()
998            .map(|parser| parser.root().document.as_rooted())
999    }
1000
1001    pub(crate) fn parent_info(&self) -> Option<PipelineId> {
1002        self.parent_info
1003    }
1004
1005    /// <https://html.spec.whatwg.org/multipage/#creating-a-policy-container-from-a-fetch-response>
1006    fn create_policy_container_from_fetch_response(metadata: &Metadata) -> PolicyContainer {
1007        // TODO Step 1. If response's URL's scheme is "blob", then return a clone of response's
1008        // URL's blob URL entry's environment's policy container.
1009
1010        // Step 2. Let result be a new policy container.
1011        // TODO Step 6. Parse Integrity-Policy headers with response and result.
1012        // Step 7. Return result.
1013        PolicyContainer {
1014            // Step 3. Set result's CSP list to the result of parsing a response's Content Security Policies given response.
1015            csp_list: parse_csp_list_from_metadata(&metadata.headers),
1016            // TODO Step 4. If environment is non-null, then set result's embedder policy to the
1017            // result of obtaining an embedder policy given response and environment.
1018            // Otherwise, set it to "unsafe-none".
1019            embedder_policy: Default::default(),
1020            // Step 5. Set result's referrer policy to the result of parsing the `Referrer-Policy` header given response. [REFERRERPOLICY]
1021            referrer_policy: ReferrerPolicy::parse_header_for_response(&metadata.headers),
1022        }
1023    }
1024
1025    /// <https://html.spec.whatwg.org/multipage/#initialise-the-document-object>
1026    fn initialize_document_object(&self, cx: &mut JSContext, document: &Document) {
1027        // Step 9. Let document be a new Document, with
1028        // policy container: navigationParams's policy container
1029        document.set_policy_container(self.navigation_params.policy_container.clone());
1030        // active sandboxing flag: set navigationParams's final sandboxing flag set
1031        document.set_active_sandboxing_flag_set(self.navigation_params.final_sandboxing_flag_set);
1032        // current document readiness: "loading"
1033        document.set_document_readiness_to_loading_for_initialization();
1034        // about base URL: navigationParams's about base URL
1035        document.set_about_base_url(self.navigation_params.about_base_url.clone());
1036        // Step 11. Set document's internal ancestor origin objects list to the result of
1037        // running the internal ancestor origin objects list creation steps given
1038        // document and navigationParams's iframe element referrer policy.
1039        document.set_internal_ancestor_origin_objects_list(
1040            document.internal_ancestor_origin_objects_list_creation_steps(
1041                &self.navigation_params.iframe_element_referrer_policy,
1042            ),
1043        );
1044        // Step 12. Set document's ancestor origins list to the result of
1045        // running the ancestor origins list creation steps given document.
1046        document.set_ancestor_origins_list(&document.ancestor_origins_list_creation_steps(cx));
1047        // Step 17. Process link headers given document, navigationParams's response, and "pre-media".
1048        process_link_headers(
1049            &self.navigation_params.link_headers,
1050            document,
1051            LinkProcessingPhase::PreMedia,
1052        );
1053    }
1054
1055    /// Part of various load document methods
1056    fn process_link_headers_in_media_phase_with_task(&mut self, document: &Document) {
1057        // The first task that the networking task source places on the task queue
1058        // while fetching runs must process link headers given document,
1059        // navigationParams's response, and "media", after the task has been processed by the HTML parser.
1060        let link_headers = std::mem::take(&mut self.navigation_params.link_headers);
1061        if !link_headers.is_empty() {
1062            let window = document.window();
1063            let document = Trusted::new(document);
1064            window
1065                .upcast::<GlobalScope>()
1066                .task_manager()
1067                .networking_task_source()
1068                .queue(task!(process_link_headers_task: move || {
1069                    process_link_headers(&link_headers, &document.root(), LinkProcessingPhase::Media);
1070                }));
1071        }
1072    }
1073
1074    /// <https://html.spec.whatwg.org/multipage/#loading-a-document>
1075    fn load_document(
1076        &mut self,
1077        cx: &mut JSContext,
1078        parser: Option<&ServoParser>,
1079        document: &Document,
1080    ) {
1081        assert!(!self.has_loaded_document);
1082        self.has_loaded_document = true;
1083        // Step 1. Let type be the computed type of navigationParams's response.
1084        let content_type = &self.navigation_params.content_type;
1085        let mime_type = MimeClassifier::default().classify(
1086            LoadContext::Browsing,
1087            NoSniffFlag::Off,
1088            ApacheBugFlag::from_content_type(content_type.as_ref()),
1089            content_type,
1090            &self.navigation_params.resource_header,
1091        );
1092        // Step 2. If the user agent has been configured to process resources of the given type using
1093        // some mechanism other than rendering the content in a navigable, then skip this step.
1094        // Otherwise, if the type is one of the following types:
1095        let Some(media_type) = MimeClassifier::get_media_type(&mime_type) else {
1096            let page = format!(
1097                "<html><body><p>Unknown content type ({}).</p></body></html>",
1098                mime_type,
1099            );
1100            self.load_inline_unknown_content(
1101                cx,
1102                parser.expect("Must have a parser for unknown content"),
1103                page,
1104            );
1105            return;
1106        };
1107        match media_type {
1108            // Return the result of loading an HTML document, given navigationParams.
1109            MediaType::Html => self.load_html_document(cx, document),
1110            // Return the result of loading an XML document given navigationParams and type.
1111            MediaType::Xml => self.load_xml_document(cx, document),
1112            // Return the result of loading a text document given navigationParams and type.
1113            MediaType::JavaScript | MediaType::Text | MediaType::Css => {
1114                self.load_text_document(cx, parser.expect("Must have a parser for text"))
1115            },
1116            // Return the result of loading a json document given navigationParams and type.
1117            MediaType::Json => {
1118                self.load_json_document(cx, parser.expect("Must have a parser for JSON"))
1119            },
1120            // Return the result of loading a media document given navigationParams and type.
1121            MediaType::Image | MediaType::AudioVideo => {
1122                self.load_media_document(
1123                    cx,
1124                    parser.expect("Must have a parser for media"),
1125                    media_type,
1126                    &mime_type,
1127                );
1128                return;
1129            },
1130            MediaType::Font => {
1131                let page = format!(
1132                    "<html><body><p>Unable to load font with content type ({}).</p></body></html>",
1133                    mime_type,
1134                );
1135                self.load_inline_unknown_content(
1136                    cx,
1137                    parser.expect("Must have a parser for inline unknown"),
1138                    page,
1139                );
1140                return;
1141            },
1142        };
1143
1144        if let Some(parser) = parser {
1145            parser.parse_bytes_chunk(
1146                cx,
1147                std::mem::take(&mut self.navigation_params.resource_header).as_ref(),
1148            );
1149        }
1150    }
1151
1152    /// <https://html.spec.whatwg.org/multipage/#navigate-html>
1153    fn load_html_document(&mut self, cx: &mut JSContext, document: &Document) {
1154        // Step 1. Let document be the result of creating and initializing a
1155        // Document object given "html", "text/html", and navigationParams.
1156        self.initialize_document_object(cx, document);
1157        // Step 2. If document's URL is about:blank, then populate with html/head/body given document.
1158        if document.is_initial_about_blank() {
1159            populate_about_blank(cx, document);
1160        }
1161        // The first task that the networking task source places on the task queue while fetching
1162        // runs must process link headers given document, navigationParams's response, and "media",
1163        // after the task has been processed by the HTML parser.
1164        self.process_link_headers_in_media_phase_with_task(document);
1165    }
1166
1167    /// <https://html.spec.whatwg.org/multipage/#read-xml>
1168    fn load_xml_document(&mut self, cx: &mut JSContext, document: &Document) {
1169        // When faced with displaying an XML file inline, provided navigation params navigationParams
1170        // and a string type, user agents must follow the requirements defined in XML and Namespaces in XML,
1171        // XML Media Types, DOM, and other relevant specifications to create and initialize a
1172        // Document object document, given "xml", type, and navigationParams, and return that Document.
1173        // They must also create a corresponding XML parser. [XML] [XMLNS] [RFC7303] [DOM]
1174        self.initialize_document_object(cx, document);
1175        // The first task that the networking task source places on the task queue while fetching
1176        // runs must process link headers given document, navigationParams's response, and "media",
1177        // after the task has been processed by the XML parser.
1178        self.process_link_headers_in_media_phase_with_task(document);
1179    }
1180
1181    /// <https://html.spec.whatwg.org/multipage/#navigate-text>
1182    fn load_text_document(&mut self, cx: &mut JSContext, parser: &ServoParser) {
1183        // Step 1. Let document be the result of creating and initializing a Document
1184        // object given "html", type, and navigationParams.
1185        self.initialize_document_object(cx, &parser.document);
1186        // Step 4. Create an HTML parser and associate it with the document.
1187        // Act as if the tokenizer had emitted a start tag token with the tag name "pre" followed by
1188        // a single U+000A LINE FEED (LF) character, and switch the HTML parser's tokenizer to the PLAINTEXT state.
1189        // Each task that the networking task source places on the task queue while fetching runs must then
1190        // fill the parser's input byte stream with the fetched bytes and cause the HTML parser to perform
1191        // the appropriate processing of the input stream.
1192        let page = "<pre>\n".into();
1193        parser.push_string_input_chunk(page);
1194        parser.parse_sync(cx);
1195        parser.tokenizer.set_plaintext_state();
1196        // Step 3. Set document's mode to "no-quirks".
1197        parser.document.set_quirks_mode(ServoQuirksMode::NoQuirks);
1198        // The first task that the networking task source places on the task queue while fetching
1199        // runs must process link headers given document, navigationParams's response, and "media",
1200        // after the task has been processed by the HTML parser.
1201        self.process_link_headers_in_media_phase_with_task(&parser.document);
1202    }
1203
1204    /// <https://html.spec.whatwg.org/multipage/#navigate-media>
1205    fn load_media_document(
1206        &mut self,
1207        cx: &mut JSContext,
1208        parser: &ServoParser,
1209        media_type: MediaType,
1210        mime_type: &Mime,
1211    ) {
1212        // Step 1. Let document be the result of creating and initializing a Document
1213        // object given "html", type, and navigationParams.
1214        self.initialize_document_object(cx, &parser.document);
1215        // Step 8. Act as if the user agent had stopped parsing document.
1216        self.is_synthesized_document = true;
1217        parser.last_chunk_received.set(true);
1218        // Step 3. Populate with html/head/body given document.
1219        let page = "<html><body></body></html>".into();
1220        parser.push_string_input_chunk(page);
1221        parser.parse_sync(cx);
1222        // Step 2. Set document's mode to "no-quirks".
1223        parser.document.set_quirks_mode(ServoQuirksMode::NoQuirks);
1224
1225        let doc = &parser.document;
1226        // Step 5. Set the appropriate attribute of the element host element, as described below,
1227        // to the address of the image, video, or audio resource.
1228        let node = if media_type == MediaType::Image {
1229            let img = Element::create(
1230                cx,
1231                QualName::new(None, ns!(html), local_name!("img")),
1232                None,
1233                doc,
1234                ElementCreator::ParserCreated(1),
1235                CustomElementCreationMode::Asynchronous,
1236                None,
1237            );
1238            let img = DomRoot::downcast::<HTMLImageElement>(img).unwrap();
1239            img.SetSrc(cx, USVString(self.url.to_string()));
1240            DomRoot::upcast::<Node>(img)
1241        } else if mime_type.type_() == mime::AUDIO {
1242            let audio = Element::create(
1243                cx,
1244                QualName::new(None, ns!(html), local_name!("audio")),
1245                None,
1246                doc,
1247                ElementCreator::ParserCreated(1),
1248                CustomElementCreationMode::Asynchronous,
1249                None,
1250            );
1251            let audio = DomRoot::downcast::<HTMLMediaElement>(audio).unwrap();
1252            audio.SetControls(cx, true);
1253            audio.SetSrc(cx, USVString(self.url.to_string()));
1254            DomRoot::upcast::<Node>(audio)
1255        } else {
1256            let video = Element::create(
1257                cx,
1258                QualName::new(None, ns!(html), local_name!("video")),
1259                None,
1260                doc,
1261                ElementCreator::ParserCreated(1),
1262                CustomElementCreationMode::Asynchronous,
1263                None,
1264            );
1265            let video = DomRoot::downcast::<HTMLMediaElement>(video).unwrap();
1266            video.SetControls(cx, true);
1267            video.SetSrc(cx, USVString(self.url.to_string()));
1268            DomRoot::upcast::<Node>(video)
1269        };
1270        // Step 4. Append an element host element for the media, as described below, to the body element.
1271        let doc_body = DomRoot::upcast::<Node>(doc.GetBody().unwrap());
1272        doc_body.AppendChild(cx, &node).expect("Appending failed");
1273        // Step 7. Process link headers given document, navigationParams's response, and "media".
1274        let link_headers = std::mem::take(&mut self.navigation_params.link_headers);
1275        process_link_headers(&link_headers, doc, LinkProcessingPhase::Media);
1276    }
1277
1278    /// Load a JSON document with a pretty-printing, interactive viewer.
1279    fn load_json_document(&mut self, cx: &mut JSContext, parser: &ServoParser) {
1280        self.initialize_document_object(cx, &parser.document);
1281        parser.push_string_input_chunk(resources::read_string(Resource::JsonViewerHTML));
1282        parser.parse_sync(cx);
1283        parser.tokenizer.set_plaintext_state();
1284        self.process_link_headers_in_media_phase_with_task(&parser.document);
1285    }
1286
1287    /// <https://html.spec.whatwg.org/multipage/#navigate-ua-inline>
1288    fn load_inline_unknown_content(
1289        &mut self,
1290        cx: &mut JSContext,
1291        parser: &ServoParser,
1292        page: String,
1293    ) {
1294        self.is_synthesized_document = true;
1295        parser.document.mark_as_internal();
1296        parser.push_string_input_chunk(page);
1297        // Step 7. Act as if the user agent had stopped parsing document.
1298        parser.last_chunk_received.set(true);
1299        parser.parse_sync(cx);
1300    }
1301
1302    /// Store a PerformanceNavigationTiming entry in the globalscope's Performance buffer
1303    fn submit_resource_timing(&mut self, cx: &mut JSContext) {
1304        let Some(parser) = self.parser.as_ref() else {
1305            return;
1306        };
1307        let parser = parser.root();
1308        if parser.aborted.get() {
1309            return;
1310        }
1311
1312        let document = &parser.document;
1313
1314        let performance_entry = PerformanceNavigationTiming::new(cx, &document.global(), document);
1315        self.pushed_entry_index = document
1316            .global()
1317            .performance(cx)
1318            .queue_entry(performance_entry.upcast::<PerformanceEntry>());
1319    }
1320
1321    fn finish_synchronous_load_for_initial_about_blank(
1322        &self,
1323        cx: &mut JSContext,
1324        document: &Document,
1325    ) {
1326        // Synchronous loads for initial `about:blank` always start in the `Complete` state
1327        // which is why we do not notify the embedder of completion via
1328        // `Document::update_the_current_document_readiness`.
1329        debug_assert_eq!(document.ReadyState(), DocumentReadyState::Complete);
1330
1331        document.set_current_parser(None);
1332        document.finish_load(LoadType::PageSource(self.url.clone()), cx);
1333
1334        document.notify_embedder_of_load_completion();
1335    }
1336
1337    /// Implements parts of
1338    /// <https://html.spec.whatwg.org/multipage/#attempt-to-populate-the-history-entry's-document>
1339    pub(crate) fn process_response(
1340        &mut self,
1341        script_thread: &ScriptThread,
1342        cx: &mut JSContext,
1343        meta_result: Result<FetchMetadata, NetworkError>,
1344    ) {
1345        let (metadata, mut error) = match meta_result {
1346            Ok(meta) => (
1347                Some(match meta {
1348                    FetchMetadata::Unfiltered(m) => m,
1349                    FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
1350                }),
1351                None,
1352            ),
1353            Err(error) => (
1354                // Check variant without moving
1355                match &error {
1356                    NetworkError::LoadCancelled => {
1357                        return;
1358                    },
1359                    _ => {
1360                        let mut meta = Metadata::default(self.url.clone());
1361                        let mime: Option<Mime> = "text/html".parse().ok();
1362                        meta.set_content_type(mime.as_ref());
1363                        Some(meta)
1364                    },
1365                },
1366                Some(error),
1367            ),
1368        };
1369        let content_type: Option<Mime> = metadata
1370            .clone()
1371            .and_then(|meta| meta.content_type)
1372            .map(Serde::into_inner)
1373            .map(Into::into);
1374
1375        // <https://html.spec.whatwg.org/multipage/#create-navigation-params-by-fetching>
1376        // Step 21.9. Set responsePolicyContainer to the result of creating a
1377        // policy container from a fetch response given response and request's
1378        // reserved client.
1379        let (policy_container, endpoints_list, link_headers, content_language) = metadata
1380            .as_ref()
1381            .map(|metadata| {
1382                (
1383                    Self::create_policy_container_from_fetch_response(metadata),
1384                    ReportingEndpoint::parse_reporting_endpoints_header(
1385                        &self.url.clone(),
1386                        &metadata.headers,
1387                    ),
1388                    extract_links_from_headers(&metadata.headers),
1389                    metadata
1390                        .headers
1391                        .as_ref()
1392                        .and_then(|headers| headers.get("Content-Language"))
1393                        .and_then(|header| header.to_str().ok())
1394                        .map(|string| string.to_owned()),
1395                )
1396            })
1397            .unwrap_or_default();
1398
1399        // Step 21.10. Set finalSandboxFlags to the union of targetSnapshotParams's
1400        // sandboxing flags and responsePolicyContainer's CSP list's CSP-derived
1401        // sandboxing flags.
1402        let final_sandboxing_flag_set = policy_container
1403            .csp_list
1404            .as_ref()
1405            .and_then(|csp| csp.get_sandboxing_flag_set_for_document())
1406            .unwrap_or(SandboxingFlagSet::empty())
1407            .union(self.target_snapshot_params.sandboxing_flags);
1408
1409        // Step 21.11. Set responseOrigin to the result of determining the origin
1410        // given response's URL, finalSandboxFlags, and entry's document state's
1411        // initiator origin.
1412        let origin = determine_the_origin(
1413            metadata.as_ref().map(|metadata| &metadata.final_url),
1414            final_sandboxing_flag_set,
1415            self.source_origin.clone(),
1416        );
1417
1418        let Some(document) = script_thread.handle_page_headers_available(
1419            self.webview_id,
1420            self.pipeline_id,
1421            metadata.as_ref(),
1422            origin.clone(),
1423            cx,
1424        ) else {
1425            return;
1426        };
1427
1428        self.document = Some(Trusted::new(&*document));
1429
1430        if document
1431            .get_current_parser()
1432            .is_some_and(|parser| parser.aborted.get())
1433        {
1434            return;
1435        }
1436
1437        let mut realm = enter_auto_realm(cx, &*document);
1438        let cx = &mut realm;
1439        let window = document.window();
1440
1441        // https://html.spec.whatwg.org/multipage/#attempt-to-populate-the-history-entry%27s-document
1442        // Step 4. Otherwise, if any of the following are true:
1443        if
1444        // navigationParams is null;
1445        // TODO
1446        // the result of should navigation response to navigation request of
1447        // type in target be blocked by Content Security Policy? given
1448        // navigationParams's request, navigationParams's response, navigationParams's policy container's CSP list,
1449        // cspNavigationType, and navigable is "Blocked";
1450        policy_container.csp_list.should_navigation_response_to_navigation_request_be_blocked(
1451            cx,
1452            window,
1453            self.url.clone().into_url(),
1454            &origin.immutable().clone().into_url_origin(),
1455        )
1456        // navigationParams's reserved environment is non-null and the result of
1457        // checking a navigation response's adherence to its embedder policy given navigationParams's response,
1458        // navigable, and navigationParams's policy container's embedder policy is false; or
1459        // TODO
1460        // the result of checking a navigation response's adherence to `X-Frame-Options`
1461        // given navigationParams's response, navigable, navigationParams's policy container's CSP list,
1462        // and navigationParams's origin is false,
1463        || !check_a_navigation_response_adherence_to_x_frame_options(
1464            window,
1465            policy_container.csp_list.as_ref(),
1466            &origin,
1467            metadata
1468                .as_ref()
1469                .and_then(|metadata| metadata.headers.as_ref()),
1470        ) {
1471            // Step 4.1. Set entry's document state's document to the result of creating a document for inline content
1472            // that doesn't have a DOM, given navigable, null, navTimingType, and userInvolvement.
1473            // The inline content should indicate to the user the sort of error that occurred.
1474            error = Some(NetworkError::ContentSecurityPolicy);
1475            // Step 4.2. Make document unsalvageable given entry's document state's document and "navigation-failure".
1476            document.make_document_unsalvageable();
1477            // Step 4.3. Set saveExtraDocumentState to false.
1478            // TODO
1479            // Step 4.4. If navigationParams is not null, then:
1480            // TODO
1481        }
1482
1483        if let Some(endpoints) = endpoints_list {
1484            window.set_endpoints_list(endpoints);
1485        }
1486        // https://html.spec.whatwg.org/multipage/#language
1487        // > then language information from a higher-level protocol (such as HTTP),
1488        // > if any, must be used as the final fallback language instead
1489        document.set_default_language(content_language);
1490        if let Some(parser) = document.get_current_parser() {
1491            self.parser = Some(Trusted::new(&*parser));
1492        }
1493        self.navigation_params = NavigationParams {
1494            policy_container,
1495            content_type,
1496            final_sandboxing_flag_set,
1497            link_headers,
1498            about_base_url: document.about_base_url(),
1499            resource_header: vec![],
1500            iframe_element_referrer_policy: self
1501                .target_snapshot_params
1502                .iframe_element_referrer_policy,
1503        };
1504        self.submit_resource_timing(cx);
1505
1506        // Part of https://html.spec.whatwg.org/multipage/#loading-a-document
1507        //
1508        // Step 3. If, given type, the new resource is to be handled by displaying some sort of inline content,
1509        // e.g., a native rendering of the content or an error message because the specified type is not supported,
1510        // then return the result of creating a document for inline content that doesn't have a DOM given
1511        // navigationParams's navigable, navigationParams's id, navigationParams's navigation timing type,
1512        // and navigationParams's user involvement.
1513        if let Some(error) = error {
1514            let page = match error {
1515                NetworkError::SslValidation(reason, bytes) => {
1516                    let page = resources::read_string(Resource::BadCertHTML);
1517                    let page = page.replace("${reason}", &reason);
1518                    let encoded_bytes = general_purpose::STANDARD_NO_PAD.encode(bytes);
1519                    let page = page.replace("${bytes}", encoded_bytes.as_str());
1520                    page.replace("${secret}", &net_traits::PRIVILEGED_SECRET.to_string())
1521                },
1522                NetworkError::BlobURLStoreError(reason) |
1523                NetworkError::WebsocketConnectionFailure(reason) |
1524                NetworkError::HttpError(reason) |
1525                NetworkError::ResourceLoadError(reason) |
1526                NetworkError::MimeType(reason) => {
1527                    let page = resources::read_string(Resource::NetErrorHTML);
1528                    page.replace("${reason}", &reason)
1529                },
1530                NetworkError::Crash(details) => {
1531                    let page = resources::read_string(Resource::CrashHTML);
1532                    page.replace("${details}", &details)
1533                },
1534                NetworkError::UnsupportedScheme |
1535                NetworkError::CorsGeneral |
1536                NetworkError::CrossOriginResponse |
1537                NetworkError::CorsCredentials |
1538                NetworkError::CorsAllowMethods |
1539                NetworkError::CorsAllowHeaders |
1540                NetworkError::CorsMethod |
1541                NetworkError::CorsAuthorization |
1542                NetworkError::CorsHeaders |
1543                NetworkError::ConnectionFailure |
1544                NetworkError::RedirectError |
1545                NetworkError::TooManyRedirects |
1546                NetworkError::TooManyInFlightKeepAliveRequests |
1547                NetworkError::InvalidMethod |
1548                NetworkError::ContentSecurityPolicy |
1549                NetworkError::Nosniff |
1550                NetworkError::SubresourceIntegrity |
1551                NetworkError::MixedContent |
1552                NetworkError::CacheError |
1553                NetworkError::InvalidPort |
1554                NetworkError::LocalDirectoryError |
1555                NetworkError::PartialResponseToNonRangeRequestError |
1556                NetworkError::ProtocolHandlerSubstitutionError |
1557                NetworkError::DecompressionError => {
1558                    let page = resources::read_string(Resource::NetErrorHTML);
1559                    page.replace("${reason}", &format!("{:?}", error))
1560                },
1561                NetworkError::LoadCancelled => {
1562                    // The next load will show a page
1563                    return;
1564                },
1565            };
1566            let parser = document
1567                .get_current_parser()
1568                .expect("Must have a parser for errors");
1569            self.load_inline_unknown_content(cx, &parser, page);
1570        }
1571    }
1572
1573    pub(crate) fn process_response_chunk(&mut self, cx: &mut JSContext, payload: Bytes) {
1574        if self.is_synthesized_document {
1575            return;
1576        }
1577        let Some(parser) = self.parser.as_ref().map(|p| p.root()) else {
1578            return;
1579        };
1580        let Some(document) = self.document.as_ref().map(|document| document.root()) else {
1581            return;
1582        };
1583        if parser.aborted.get() {
1584            return;
1585        }
1586        if !self.has_loaded_document {
1587            // https://mimesniff.spec.whatwg.org/#read-the-resource-header
1588            self.navigation_params
1589                .resource_header
1590                .extend_from_slice(payload.as_ref());
1591            // the number of bytes in buffer is greater than or equal to 1445.
1592            if self.navigation_params.resource_header.len() >= 1445 {
1593                self.load_document(cx, Some(&parser), &document);
1594            }
1595        } else {
1596            parser.parse_bytes_chunk(cx, payload.as_ref());
1597        }
1598    }
1599
1600    // This method is called via script_thread::handle_fetch_eof, so we must call
1601    // submit_resource_timing in this function
1602    // Resource listeners are called via net_traits::Action::process, which handles submission for them
1603    pub(crate) fn process_response_eof(
1604        mut self,
1605        cx: &mut JSContext,
1606        status: Result<(), NetworkError>,
1607        timing: ResourceFetchTiming,
1608    ) {
1609        let parser = self.parser.as_ref().map(|parser| parser.root());
1610        if parser.as_ref().is_some_and(|parser| parser.aborted.get()) ||
1611            self.is_synthesized_document
1612        {
1613            return;
1614        }
1615
1616        if let Err(error) = &status {
1617            // TODO(Savago): we should send a notification to callers #5463.
1618            debug!("Failed to load page URL {}, error: {error:?}", self.url);
1619        }
1620
1621        let Some(document) = self.document.as_ref().map(|document| document.root()) else {
1622            return;
1623        };
1624
1625        // https://mimesniff.spec.whatwg.org/#read-the-resource-header
1626        //
1627        // the end of the resource is reached.
1628        if !self.has_loaded_document {
1629            self.load_document(cx, parser.as_deref(), &document);
1630        }
1631
1632        let mut realm = enter_auto_realm(cx, &*document);
1633        let cx = &mut realm;
1634
1635        if status.is_ok() {
1636            document.set_resource_fetch_timing(timing);
1637        }
1638
1639        if let Some(parser) = parser {
1640            parser.last_chunk_received.set(true);
1641            if !parser.suspended.get() {
1642                parser.parse_sync(cx);
1643            }
1644        }
1645
1646        // TODO: Only update if this is the current document resource.
1647        if let Some(pushed_index) = self.pushed_entry_index {
1648            let performance_entry =
1649                PerformanceNavigationTiming::new(cx, &document.global(), &document);
1650            document
1651                .global()
1652                .performance(cx)
1653                .update_entry(pushed_index, performance_entry.upcast::<PerformanceEntry>());
1654        }
1655
1656        if document.is_initial_about_blank() {
1657            self.finish_synchronous_load_for_initial_about_blank(cx, &document);
1658        }
1659    }
1660}
1661
1662pub(crate) struct FragmentContext<'a> {
1663    pub(crate) context_elem: &'a Node,
1664    pub(crate) form_elem: Option<&'a Node>,
1665    pub(crate) context_element_allows_scripting: bool,
1666}
1667
1668/// <https://html.spec.whatwg.org/multipage/#insert-an-element-at-the-adjusted-insertion-location>
1669#[cfg_attr(crown, expect(crown::unrooted_must_root))]
1670fn insert_an_element_at_the_adjusted_insertion_location(
1671    cx: &mut JSContext,
1672    node_to_insert: Dom<Node>,
1673    adjusted_insertion_location_parent: &Node,
1674    adjusted_insertion_location_child: Option<&Node>,
1675    parsing_algorithm: ParsingAlgorithm,
1676    custom_element_reaction_stack: &CustomElementReactionStack,
1677) {
1678    // Step 1: Let the adjusted insertion location be the appropriate place for inserting a node.
1679    //
1680    // Note: This is handled as part of the input.
1681
1682    // Step 2: If it is not possible to insert element at the adjusted insertion location,
1683    // abort these steps.
1684    if Node::ensure_pre_insertion_validity(
1685        cx.no_gc(),
1686        &node_to_insert,
1687        adjusted_insertion_location_parent,
1688        adjusted_insertion_location_child,
1689    )
1690    .is_err()
1691    {
1692        return;
1693    }
1694
1695    // Step 3. If the parser was not created as part of the HTML fragment parsing algorithm,
1696    // then push a new element queue onto element's relevant agent's custom element reactions
1697    // stack.
1698    let element_in_non_fragment =
1699        parsing_algorithm != ParsingAlgorithm::Fragment && node_to_insert.is::<Element>();
1700    if element_in_non_fragment {
1701        custom_element_reaction_stack.push_new_element_queue();
1702    }
1703
1704    // Step 4: Insert element at the adjusted insertion location.
1705    Node::insert(
1706        cx,
1707        &node_to_insert,
1708        adjusted_insertion_location_parent,
1709        adjusted_insertion_location_child,
1710        SuppressObserver::Unsuppressed,
1711    );
1712
1713    // Step 5: If the parser was not created as part of the HTML fragment parsing algorithm,
1714    // then pop the element queue from element's relevant agent's custom element reactions
1715    // stack, and invoke custom element reactions in that queue.
1716    //
1717    // Note: Handled as part of `pop_current_element_queue()`.
1718    if element_in_non_fragment {
1719        custom_element_reaction_stack.pop_current_element_queue(cx);
1720    }
1721}
1722
1723#[cfg_attr(crown, expect(crown::unrooted_must_root))]
1724fn insert(
1725    cx: &mut JSContext,
1726    parent: &Node,
1727    reference_child: Option<&Node>,
1728    child: NodeOrText<Dom<Node>>,
1729    parsing_algorithm: ParsingAlgorithm,
1730    custom_element_reaction_stack: &CustomElementReactionStack,
1731) {
1732    match child {
1733        NodeOrText::AppendNode(node) => {
1734            // This encompasses two parts of the specification:
1735            //  - https://html.spec.whatwg.org/multipage/#insert-a-foreign-element
1736            //  - https://html.spec.whatwg.org/multipage/#insert-a-comment
1737            //
1738            // TODO: This part of the code should match the specification more closely.
1739            insert_an_element_at_the_adjusted_insertion_location(
1740                cx,
1741                node,
1742                parent,
1743                reference_child,
1744                parsing_algorithm,
1745                custom_element_reaction_stack,
1746            );
1747        },
1748        NodeOrText::AppendText(t) => {
1749            // https://html.spec.whatwg.org/multipage/#insert-a-character
1750            let text = reference_child
1751                .and_then(Node::GetPreviousSibling)
1752                .or_else(|| parent.GetLastChild())
1753                .and_then(DomRoot::downcast::<Text>);
1754
1755            if let Some(text) = text {
1756                text.upcast::<CharacterData>().append_data(cx, &t);
1757            } else {
1758                let text = Text::new(cx, String::from(t).into(), &parent.owner_doc());
1759                parent
1760                    .InsertBefore(cx, text.upcast(), reference_child)
1761                    .unwrap();
1762            }
1763        },
1764    }
1765}
1766
1767#[derive(JSTraceable, MallocSizeOf)]
1768#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1769pub(crate) struct Sink {
1770    #[no_trace]
1771    base_url: ServoUrl,
1772    document: Dom<Document>,
1773    current_line: Cell<u64>,
1774    script: MutNullableDom<HTMLScriptElement>,
1775    parsing_algorithm: ParsingAlgorithm,
1776    #[conditional_malloc_size_of]
1777    custom_element_reaction_stack: Rc<CustomElementReactionStack>,
1778}
1779
1780impl Sink {
1781    fn same_tree(&self, x: &Dom<Node>, y: &Dom<Node>) -> bool {
1782        let x = x.downcast::<Element>().expect("Element node expected");
1783        let y = y.downcast::<Element>().expect("Element node expected");
1784
1785        x.is_in_same_home_subtree(y)
1786    }
1787
1788    fn has_parent_node(&self, node: &Dom<Node>) -> bool {
1789        node.GetParentNode().is_some()
1790    }
1791}
1792
1793impl TreeSink for Sink {
1794    type Output = Self;
1795
1796    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1797    fn finish(self) -> Self {
1798        self
1799    }
1800
1801    type Handle = Dom<Node>;
1802    type ElemName<'a>
1803        = ExpandedName<'a>
1804    where
1805        Self: 'a;
1806
1807    fn get_document(&self) -> Dom<Node> {
1808        Dom::from_ref(self.document.upcast())
1809    }
1810
1811    #[expect(unsafe_code)]
1812    fn get_template_contents(&self, target: &Dom<Node>) -> Dom<Node> {
1813        // TODO: https://github.com/servo/servo/issues/42839
1814        let mut cx = unsafe { temp_cx() };
1815        let cx = &mut cx;
1816        let template = target
1817            .downcast::<HTMLTemplateElement>()
1818            .expect("tried to get template contents of non-HTMLTemplateElement in HTML parsing");
1819        Dom::from_ref(template.Content(cx).upcast())
1820    }
1821
1822    fn same_node(&self, x: &Dom<Node>, y: &Dom<Node>) -> bool {
1823        x == y
1824    }
1825
1826    fn elem_name<'a>(&self, target: &'a Dom<Node>) -> ExpandedName<'a> {
1827        let elem = target
1828            .downcast::<Element>()
1829            .expect("tried to get name of non-Element in HTML parsing");
1830        ExpandedName {
1831            ns: elem.namespace(),
1832            local: elem.local_name(),
1833        }
1834    }
1835
1836    #[expect(unsafe_code)]
1837    fn create_element(
1838        &self,
1839        name: QualName,
1840        attrs: Vec<Attribute>,
1841        flags: ElementFlags,
1842    ) -> Dom<Node> {
1843        // TODO: https://github.com/servo/servo/issues/42839
1844        let mut cx = unsafe { temp_cx() };
1845        let cx = &mut cx;
1846        let attrs = attrs
1847            .into_iter()
1848            .map(|attr| ElementAttribute::new(attr.name, DOMString::from(String::from(attr.value))))
1849            .collect();
1850        let parsing_algorithm = if flags.template {
1851            ParsingAlgorithm::Fragment
1852        } else {
1853            self.parsing_algorithm
1854        };
1855        let element = create_element_for_token(
1856            cx,
1857            name,
1858            attrs,
1859            &self.document,
1860            ElementCreator::ParserCreated(self.current_line.get()),
1861            parsing_algorithm,
1862            &self.custom_element_reaction_stack,
1863            flags.had_duplicate_attributes,
1864        );
1865        Dom::from_ref(element.upcast())
1866    }
1867
1868    #[expect(unsafe_code)]
1869    fn create_comment(&self, text: StrTendril) -> Dom<Node> {
1870        // TODO: https://github.com/servo/servo/issues/42839
1871        let mut cx = unsafe { temp_cx() };
1872        let cx = &mut cx;
1873        let comment = Comment::new(
1874            cx,
1875            DOMString::from(String::from(text)),
1876            &self.document,
1877            None,
1878        );
1879        Dom::from_ref(comment.upcast())
1880    }
1881
1882    #[expect(unsafe_code)]
1883    fn create_pi(&self, target: StrTendril, data: StrTendril) -> Dom<Node> {
1884        // TODO: https://github.com/servo/servo/issues/42839
1885        let mut cx = unsafe { temp_cx() };
1886        let cx = &mut cx;
1887        let doc = &*self.document;
1888        let pi = ProcessingInstruction::new(
1889            cx,
1890            DOMString::from(String::from(target)),
1891            DOMString::from(String::from(data)),
1892            doc,
1893        );
1894        Dom::from_ref(pi.upcast())
1895    }
1896
1897    #[expect(unsafe_code)]
1898    fn associate_with_form(
1899        &self,
1900        target: &Dom<Node>,
1901        form: &Dom<Node>,
1902        nodes: (&Dom<Node>, Option<&Dom<Node>>),
1903    ) {
1904        // TODO: https://github.com/servo/servo/issues/42839
1905        let mut cx = unsafe { temp_cx() };
1906        let cx = &mut cx;
1907        let (element, prev_element) = nodes;
1908        let tree_node = prev_element.map_or(element, |prev| {
1909            if self.has_parent_node(element) {
1910                element
1911            } else {
1912                prev
1913            }
1914        });
1915        if !self.same_tree(tree_node, form) {
1916            return;
1917        }
1918
1919        let node = target;
1920        let form = DomRoot::downcast::<HTMLFormElement>(DomRoot::from_ref(&**form))
1921            .expect("Owner must be a form element");
1922
1923        let elem = node.downcast::<Element>();
1924        let control = elem.and_then(|e| e.as_maybe_form_control());
1925
1926        if let Some(control) = control {
1927            control.set_form_owner_from_parser(cx, &form);
1928        }
1929    }
1930
1931    #[expect(unsafe_code)]
1932    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1933    fn append_before_sibling(&self, sibling: &Dom<Node>, new_node: NodeOrText<Dom<Node>>) {
1934        // TODO: https://github.com/servo/servo/issues/42839
1935        let mut cx = unsafe { temp_cx() };
1936        let cx = &mut cx;
1937
1938        let parent = sibling
1939            .GetParentNode()
1940            .expect("append_before_sibling called on node without parent");
1941
1942        insert(
1943            cx,
1944            &parent,
1945            Some(sibling),
1946            new_node,
1947            self.parsing_algorithm,
1948            &self.custom_element_reaction_stack,
1949        );
1950    }
1951
1952    fn parse_error(&self, msg: Cow<'static, str>) {
1953        debug!("Parse error: {}", msg);
1954    }
1955
1956    fn set_quirks_mode(&self, mode: QuirksMode) {
1957        let mode = match mode {
1958            QuirksMode::Quirks => ServoQuirksMode::Quirks,
1959            QuirksMode::LimitedQuirks => ServoQuirksMode::LimitedQuirks,
1960            QuirksMode::NoQuirks => ServoQuirksMode::NoQuirks,
1961        };
1962        self.document.set_quirks_mode(mode);
1963    }
1964
1965    #[expect(unsafe_code)]
1966    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1967    fn append(&self, parent: &Dom<Node>, child: NodeOrText<Dom<Node>>) {
1968        // TODO: https://github.com/servo/servo/issues/42839
1969        let mut cx = unsafe { temp_cx() };
1970        let cx = &mut cx;
1971
1972        insert(
1973            cx,
1974            parent,
1975            None,
1976            child,
1977            self.parsing_algorithm,
1978            &self.custom_element_reaction_stack,
1979        );
1980    }
1981
1982    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1983    fn append_based_on_parent_node(
1984        &self,
1985        elem: &Dom<Node>,
1986        prev_elem: &Dom<Node>,
1987        child: NodeOrText<Dom<Node>>,
1988    ) {
1989        if self.has_parent_node(elem) {
1990            self.append_before_sibling(elem, child);
1991        } else {
1992            self.append(prev_elem, child);
1993        }
1994    }
1995
1996    #[expect(unsafe_code)]
1997    fn append_doctype_to_document(
1998        &self,
1999        name: StrTendril,
2000        public_id: StrTendril,
2001        system_id: StrTendril,
2002    ) {
2003        // TODO: https://github.com/servo/servo/issues/42839
2004        let mut cx = unsafe { temp_cx() };
2005        let cx = &mut cx;
2006
2007        let doc = &*self.document;
2008        let doctype = DocumentType::new(
2009            cx,
2010            DOMString::from(String::from(name)),
2011            Some(DOMString::from(String::from(public_id))),
2012            Some(DOMString::from(String::from(system_id))),
2013            doc,
2014        );
2015        doc.upcast::<Node>()
2016            .AppendChild(cx, doctype.upcast())
2017            .expect("Appending failed");
2018    }
2019
2020    #[expect(unsafe_code)]
2021    fn add_attrs_if_missing(&self, target: &Dom<Node>, attrs: Vec<Attribute>) {
2022        // TODO: https://github.com/servo/servo/issues/42839
2023        let mut cx = unsafe { temp_cx() };
2024        let cx = &mut cx;
2025
2026        let elem = target
2027            .downcast::<Element>()
2028            .expect("tried to set attrs on non-Element in HTML parsing");
2029        for attr in attrs {
2030            elem.set_attribute_from_parser(
2031                cx,
2032                attr.name,
2033                DOMString::from(String::from(attr.value)),
2034            );
2035        }
2036    }
2037
2038    #[expect(unsafe_code)]
2039    fn remove_from_parent(&self, target: &Dom<Node>) {
2040        // TODO: https://github.com/servo/servo/issues/42839
2041        let mut cx = unsafe { temp_cx() };
2042        let cx = &mut cx;
2043
2044        if let Some(ref parent) = target.GetParentNode() {
2045            parent.RemoveChild(cx, target).unwrap();
2046        }
2047    }
2048
2049    fn mark_script_already_started(&self, node: &Dom<Node>) {
2050        let script = node.downcast::<HTMLScriptElement>();
2051        if let Some(script) = script {
2052            script.set_already_started(true)
2053        }
2054    }
2055
2056    #[expect(unsafe_code)]
2057    fn reparent_children(&self, node: &Dom<Node>, new_parent: &Dom<Node>) {
2058        // TODO: https://github.com/servo/servo/issues/42839
2059        let mut cx = unsafe { temp_cx() };
2060        let cx = &mut cx;
2061
2062        while let Some(ref child) = node.GetFirstChild() {
2063            new_parent.AppendChild(cx, child).unwrap();
2064        }
2065    }
2066
2067    /// <https://html.spec.whatwg.org/multipage/#html-integration-point>
2068    /// Specifically, the `<annotation-xml>` cases.
2069    fn is_mathml_annotation_xml_integration_point(&self, handle: &Dom<Node>) -> bool {
2070        let elem = handle.downcast::<Element>().unwrap();
2071        elem.get_attribute_string_value(&local_name!("encoding"))
2072            .is_some_and(|value| {
2073                value.eq_ignore_ascii_case("text/html") ||
2074                    value.eq_ignore_ascii_case("application/xhtml+xml")
2075            })
2076    }
2077
2078    fn set_current_line(&self, line_number: u64) {
2079        self.current_line.set(line_number);
2080    }
2081
2082    #[expect(unsafe_code)]
2083    fn pop(&self, node: &Dom<Node>) {
2084        // TODO: https://github.com/servo/servo/issues/42839
2085        let mut cx = unsafe { temp_cx() };
2086        let cx = &mut cx;
2087
2088        let node = DomRoot::from_ref(&**node);
2089        vtable_for(&node).pop(cx);
2090    }
2091
2092    fn allow_declarative_shadow_roots(&self, intended_parent: &Dom<Node>) -> bool {
2093        intended_parent.owner_doc().allow_declarative_shadow_roots()
2094    }
2095
2096    /// <https://html.spec.whatwg.org/multipage/#parsing-main-inhead>
2097    /// A start tag whose tag name is "template"
2098    /// Attach shadow path
2099    #[expect(unsafe_code)]
2100    fn attach_declarative_shadow(
2101        &self,
2102        host: &Dom<Node>,
2103        template: &Dom<Node>,
2104        attributes: &[Attribute],
2105    ) -> bool {
2106        // TODO: https://github.com/servo/servo/issues/42839
2107        let mut cx = unsafe { temp_cx() };
2108        let cx = &mut cx;
2109
2110        attach_declarative_shadow_inner(cx, host, template, attributes)
2111    }
2112
2113    #[expect(unsafe_code)]
2114    fn maybe_clone_an_option_into_selectedcontent(&self, option: &Self::Handle) {
2115        // TODO: https://github.com/servo/servo/issues/42839
2116        let mut cx = unsafe { temp_cx() };
2117        let cx = &mut cx;
2118
2119        let Some(option) = option.downcast::<HTMLOptionElement>() else {
2120            if cfg!(debug_assertions) {
2121                unreachable!();
2122            }
2123            log::error!(
2124                "Received non-option element in maybe_clone_an_option_into_selectedcontent"
2125            );
2126            return;
2127        };
2128
2129        option.maybe_clone_an_option_into_selectedcontent(cx)
2130    }
2131}
2132
2133/// <https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token>
2134#[expect(clippy::too_many_arguments)]
2135fn create_element_for_token(
2136    cx: &mut JSContext,
2137    name: QualName,
2138    attrs: Vec<ElementAttribute>,
2139    document: &Document,
2140    creator: ElementCreator,
2141    parsing_algorithm: ParsingAlgorithm,
2142    custom_element_reaction_stack: &CustomElementReactionStack,
2143    had_duplicate_attributes: bool,
2144) -> DomRoot<Element> {
2145    // Step 1. If the active speculative HTML parser is not null, then return the result
2146    // of creating a speculative mock element given namespace, token's tag name, and
2147    // token's attributes.
2148    // TODO: Implement
2149
2150    // Step 2: Otherwise, optionally create a speculative mock element given namespace,
2151    // token's tag name, and token's attributes
2152    // TODO: Implement.
2153
2154    // Step 3. Let document be intendedParent's node document.
2155    // Passed as argument.
2156
2157    // Step 4. Let localName be token's tag name.
2158    // Passed as argument
2159
2160    // Step 5. Let is be the value of the "is" attribute in token, if such an attribute
2161    // exists; otherwise null.
2162    let is = attrs
2163        .iter()
2164        .find(|attr| attr.name.local.eq_str_ignore_ascii_case("is"))
2165        .map(|attr| LocalName::from(&attr.value));
2166
2167    // Step 6. Let registry be the result of looking up a custom element registry given intendedParent.
2168    // TODO: Implement registries other than `Document`.
2169
2170    // Step 7. Let definition be the result of looking up a custom element definition
2171    // given registry, namespace, localName, and is.
2172    let definition = CustomElementRegistry::lookup_custom_element_definition(
2173        document.custom_element_registry().as_deref(),
2174        &name.ns,
2175        &name.local,
2176        is.as_ref(),
2177    );
2178
2179    // Step 8. Let willExecuteScript be true if definition is non-null and the parser was
2180    // not created as part of the HTML fragment parsing algorithm; otherwise false.
2181    let will_execute_script =
2182        definition.is_some() && parsing_algorithm != ParsingAlgorithm::Fragment;
2183
2184    // Step 9. If willExecuteScript is true:
2185    if will_execute_script {
2186        // Step 9.1. Increment document's throw-on-dynamic-markup-insertion counter.
2187        document.increment_throw_on_dynamic_markup_insertion_counter();
2188        // Step 6.2. If the JavaScript execution context stack is empty, then perform a
2189        // microtask checkpoint.
2190        if is_execution_stack_empty() {
2191            document.window().perform_a_microtask_checkpoint(cx);
2192        }
2193        // Step 9.3. Push a new element queue onto document's relevant agent's custom
2194        // element reactions stack.
2195        custom_element_reaction_stack.push_new_element_queue()
2196    }
2197
2198    // Step 10. Let element be the result of creating an element given document,
2199    // localName, namespace, null, is, willExecuteScript, and registry.
2200    let creation_mode = if will_execute_script {
2201        CustomElementCreationMode::Synchronous
2202    } else {
2203        CustomElementCreationMode::Asynchronous
2204    };
2205    let element = Element::create(cx, name, is, document, creator, creation_mode, None);
2206
2207    // Step 11. Append each attribute in the given token to element.
2208    element.attrs().reserve_exact(attrs.len());
2209    for attr in attrs {
2210        element.set_attribute_from_parser(cx, attr.name, attr.value);
2211    }
2212
2213    // Record if the tokenizer saw duplicate attributes on this element,
2214    // used for CSP nonce validation (step 3 of "is element nonceable").
2215    if had_duplicate_attributes {
2216        element.set_had_duplicate_attributes(cx.no_gc());
2217    }
2218
2219    // Step 12. If willExecuteScript is true:
2220    if will_execute_script {
2221        // Step 12.1. Let queue be the result of popping from document's relevant agent's
2222        // custom element reactions stack. (This will be the same element queue as was
2223        // pushed above.)
2224        // Step 12.2 Invoke custom element reactions in queue.
2225        custom_element_reaction_stack.pop_current_element_queue(cx);
2226        // Step 12.3. Decrement document's throw-on-dynamic-markup-insertion counter.
2227        document.decrement_throw_on_dynamic_markup_insertion_counter();
2228    }
2229
2230    // Step 13. If element has an xmlns attribute in the XMLNS namespace whose value is
2231    // not exactly the same as the element's namespace, that is a parse error. Similarly,
2232    // if element has an xmlns:xlink attribute in the XMLNS namespace whose value is not
2233    // the XLink Namespace, that is a parse error.
2234    // TODO: Implement.
2235
2236    // Step 14. If element is a resettable element and not a form-associated custom
2237    // element, then invoke its reset algorithm. (This initializes the element's value and
2238    // checkedness based on the element's attributes.)
2239    if let Some(html_element) = element.downcast::<HTMLElement>() &&
2240        element.is_resettable() &&
2241        !html_element.is_form_associated_custom_element()
2242    {
2243        element.reset(cx);
2244    }
2245
2246    // Step 15. If element is a form-associated element and not a form-associated custom
2247    // element, the form element pointer is not null, there is no template element on the
2248    // stack of open elements, element is either not listed or doesn't have a form attribute,
2249    // and the intendedParent is in the same tree as the element pointed to by the form
2250    // element pointer, then associate element with the form element pointed to by the form
2251    // element pointer and set element's parser inserted flag.
2252    // TODO: Implement
2253
2254    // Step 16. Return element.
2255    element
2256}
2257
2258fn attach_declarative_shadow_inner(
2259    cx: &mut JSContext,
2260    host: &Node,
2261    template: &Node,
2262    attributes: &[Attribute],
2263) -> bool {
2264    let host_element = host.downcast::<Element>().unwrap();
2265
2266    if host_element.shadow_root().is_some() {
2267        return false;
2268    }
2269
2270    let template_element = template.downcast::<HTMLTemplateElement>().unwrap();
2271
2272    // Step 3. Let mode be templateStartTag's shadowrootmode attribute's value.
2273    // Step 4. Let slotAssignment be "named".
2274    // Step 5. If templateStartTag's shadowrootslotassignment attribute is in
2275    // the Manual state, then set slotAssignment to "manual".
2276    // Step 6. Let clonable be true if templateStartTag has a shadowrootclonable attribute; otherwise false.
2277    // Step 7. Let serializable be true if templateStartTag has a shadowrootserializable
2278    // attribute; otherwise false.
2279    // Step 8. Let delegatesFocus be true if templateStartTag has a shadowrootdelegatesfocus
2280    // attribute; otherwise false.
2281    let mut shadow_root_mode = ShadowRootMode::Open;
2282    let mut slot_assignment_mode = SlotAssignmentMode::Named;
2283    let mut clonable = false;
2284    let mut delegatesfocus = false;
2285    let mut serializable = false;
2286
2287    attributes
2288        .iter()
2289        .for_each(|attr: &Attribute| match attr.name.local {
2290            local_name!("shadowrootmode") => {
2291                if attr.value.eq_ignore_ascii_case("open") {
2292                    shadow_root_mode = ShadowRootMode::Open;
2293                } else if attr.value.eq_ignore_ascii_case("closed") {
2294                    shadow_root_mode = ShadowRootMode::Closed;
2295                } else {
2296                    unreachable!("shadowrootmode value is not open nor closed");
2297                }
2298            },
2299            local_name!("shadowrootclonable") => {
2300                clonable = true;
2301            },
2302            local_name!("shadowrootdelegatesfocus") => {
2303                delegatesfocus = true;
2304            },
2305            local_name!("shadowrootserializable") => {
2306                serializable = true;
2307            },
2308            local_name!("shadowrootslotassignment") => {
2309                if attr.value.eq_ignore_ascii_case("manual") {
2310                    slot_assignment_mode = SlotAssignmentMode::Manual;
2311                }
2312            },
2313            _ => {},
2314        });
2315
2316    // Step 8.1. Attach a shadow root with declarative shadow host element,
2317    // mode, clonable, serializable, delegatesFocus, and "named".
2318    match host_element.attach_shadow(
2319        cx,
2320        IsUserAgentWidget::No,
2321        shadow_root_mode,
2322        clonable,
2323        serializable,
2324        delegatesfocus,
2325        slot_assignment_mode,
2326    ) {
2327        Ok(shadow_root) => {
2328            // Step 8.3. Set shadow's declarative to true.
2329            shadow_root.set_declarative(true);
2330
2331            // Set 8.4. Set template's template contents property to shadow.
2332            let shadow = shadow_root.upcast::<DocumentFragment>();
2333            template_element.set_contents(Some(shadow));
2334
2335            // Step 8.5. Set shadow’s available to element internals to true.
2336            shadow_root.set_available_to_element_internals(true);
2337
2338            true
2339        },
2340        Err(_) => false,
2341    }
2342}
2343
2344/// <https://html.spec.whatwg.org/multipage/#populate-with-html/head/body>
2345fn populate_about_blank(cx: &mut JSContext, document: &Document) {
2346    let mut create_html_element = |name| {
2347        create_element(
2348            cx,
2349            QualName::new(None, ns!(html), name),
2350            None,
2351            document,
2352            ElementCreator::ParserCreated(0),
2353            CustomElementCreationMode::Synchronous,
2354            None,
2355        )
2356    };
2357    // Step 1. Let html be the result of creating an element given document, "html", and the HTML namespace.
2358    let html = create_html_element(local_name!("html"));
2359    // Step 2. Let head be the result of creating an element given document, "head", and the HTML namespace.
2360    let head = create_html_element(local_name!("head"));
2361    // Step 3. Let body be the result of creating an element given document, "body", and the HTML namespace.
2362    let body = create_html_element(local_name!("body"));
2363    // Step 4. Append html to document.
2364    let _ = document.upcast::<Node>().AppendChild(cx, html.upcast());
2365    // Step 5. Append head to html.
2366    let _ = html.upcast::<Node>().AppendChild(cx, head.upcast());
2367    // Step 6. Append body to html.
2368    let _ = html.upcast::<Node>().AppendChild(cx, body.upcast());
2369}
2370
2371impl Document {
2372    /// <https://html.spec.whatwg.org/multipage/#internal-ancestor-origin-objects-list-creation-steps>
2373    fn internal_ancestor_origin_objects_list_creation_steps(
2374        &self,
2375        referrer_policy: &ReferrerPolicy,
2376    ) -> Vec<ImmutableOrigin> {
2377        // Step 1. Let output be « ».
2378        let mut output = vec![];
2379        // Step 2. Let parentDoc be document's container document.
2380        // Step 4. Assert: parentDoc is fully active.
2381        // Step 5. Let ancestorOrigins be parentDoc's internal ancestor origin objects list.
2382        let window_proxy = self.window().window_proxy();
2383        let Some((parent_origin, ancestor_origins)) =
2384            window_proxy.parent_origin_and_internal_ancestor_origin_objects_list()
2385        else {
2386            // Step 3. If parentDoc is null, then return output.
2387            return output;
2388        };
2389        // Step 6. Let masked be false.
2390        let mut masked =
2391            // Step 7. If referrerPolicy is "no-referrer", then set masked to true.
2392            *referrer_policy == ReferrerPolicy::NoReferrer ||
2393            // Step 8. Otherwise, if referrerPolicy is "same-origin" and parentDoc's origin
2394            // is not same origin with document's origin, then set masked to true.
2395            (*referrer_policy == ReferrerPolicy::SameOrigin && !parent_origin.same_origin(&self.origin()));
2396        // Step 9. If masked is true, then append a new opaque origin to output.
2397        if masked {
2398            output.push(ImmutableOrigin::new_opaque());
2399        } else {
2400            // Step 10. Otherwise, append parentDoc's origin to output.
2401            output.push(parent_origin.immutable().clone());
2402        }
2403        // Step 11. For each ancestorOrigin of ancestorOrigins:
2404        for ancestor_origin in ancestor_origins {
2405            // Step 11.1. If masked is true and ancestorOrigin is same origin with parentDoc's origin,
2406            // then append a new opaque origin to output and continue.
2407            if masked && ancestor_origin.same_origin(&parent_origin) {
2408                output.push(ImmutableOrigin::new_opaque());
2409                continue;
2410            }
2411            // Step 11.2. Append ancestorOrigin to output and set masked to false.
2412            output.push(ancestor_origin.clone());
2413            masked = false;
2414        }
2415        // Step 12. Return output.
2416        output
2417    }
2418
2419    /// <https://html.spec.whatwg.org/multipage/#ancestor-origins-list-creation-steps>
2420    fn ancestor_origins_list_creation_steps(&self, cx: &mut JSContext) -> DomRoot<DOMStringList> {
2421        // Step 1. Let ancestorOrigins be document's internal ancestor origin objects list.
2422        // Step 2. Assert: ancestorOrigins is not null.
2423        let ancestor_origins = self.internal_ancestor_origin_objects_list();
2424        let ancestor_origins = ancestor_origins
2425            .as_ref()
2426            .expect("Must always have initialized ancestor origin objects list");
2427        // Step 3. Let output be « ».
2428        let mut output = Vec::with_capacity(ancestor_origins.len());
2429        // Step 4. For each origin of ancestorOrigins:
2430        for origin in ancestor_origins {
2431            // Step 4.1. Append the serialization of origin to output.
2432            output.push(origin.ascii_serialization().into());
2433        }
2434        // Step 5. Return a new DOMStringList object whose associated list is output.
2435        DOMStringList::new(cx, &self.global(), output)
2436    }
2437}