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