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